infinite-agents-public/claude_code_devtools/claude_devtool_1.html

861 lines
29 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Transcript Loader - Claude Code DevTools</title>
<style>
/* Modern developer-focused UI with dark theme */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--bg-primary: #1e1e1e;
--bg-secondary: #252526;
--bg-tertiary: #2d2d30;
--bg-hover: #37373d;
--border: #3e3e42;
--text-primary: #cccccc;
--text-secondary: #858585;
--accent: #007acc;
--accent-hover: #0098ff;
--success: #4ec9b0;
--warning: #ce9178;
--error: #f48771;
--code-bg: #1e1e1e;
}
body {
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
background-color: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
}
header {
background-color: var(--bg-secondary);
padding: 2rem;
border-bottom: 1px solid var(--border);
}
header h1 {
color: var(--accent);
font-size: 1.8rem;
margin-bottom: 0.5rem;
}
.tagline {
color: var(--text-secondary);
font-size: 0.95rem;
}
main {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
}
/* File Upload Interface */
.tool-interface {
background-color: var(--bg-secondary);
border: 2px dashed var(--border);
border-radius: 8px;
padding: 2rem;
margin-bottom: 2rem;
transition: all 0.3s ease;
}
.tool-interface.drag-over {
border-color: var(--accent);
background-color: var(--bg-tertiary);
}
.upload-area {
text-align: center;
}
.upload-area h2 {
color: var(--text-primary);
font-size: 1.3rem;
margin-bottom: 1rem;
}
.upload-area p {
color: var(--text-secondary);
margin-bottom: 1.5rem;
}
.file-input-wrapper {
position: relative;
display: inline-block;
}
input[type="file"] {
display: none;
}
.btn-upload {
background-color: var(--accent);
color: white;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-family: inherit;
font-size: 1rem;
transition: background-color 0.2s;
}
.btn-upload:hover {
background-color: var(--accent-hover);
}
/* Progress Bar */
.progress-container {
display: none;
margin-top: 1.5rem;
}
.progress-bar {
width: 100%;
height: 4px;
background-color: var(--bg-tertiary);
border-radius: 2px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background-color: var(--accent);
transition: width 0.3s ease;
width: 0%;
}
.progress-text {
color: var(--text-secondary);
font-size: 0.85rem;
margin-top: 0.5rem;
text-align: center;
}
/* File Info */
.file-info {
display: none;
background-color: var(--bg-tertiary);
padding: 1rem;
border-radius: 4px;
margin-top: 1rem;
}
.file-info.show {
display: block;
}
.file-info-item {
display: flex;
justify-content: space-between;
padding: 0.3rem 0;
font-size: 0.9rem;
}
.file-info-label {
color: var(--text-secondary);
}
.file-info-value {
color: var(--success);
}
/* Results Section */
.results {
display: none;
}
.results.show {
display: block;
}
.results-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border);
}
.results-header h2 {
color: var(--accent);
}
.stats {
display: flex;
gap: 2rem;
}
.stat-item {
text-align: right;
}
.stat-label {
color: var(--text-secondary);
font-size: 0.85rem;
}
.stat-value {
color: var(--success);
font-size: 1.3rem;
font-weight: bold;
}
/* Message List */
.message-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.message {
background-color: var(--bg-secondary);
border-left: 3px solid var(--border);
border-radius: 4px;
padding: 1rem;
transition: all 0.2s;
}
.message:hover {
background-color: var(--bg-hover);
}
.message.role-user {
border-left-color: var(--accent);
}
.message.role-assistant {
border-left-color: var(--success);
}
.message-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--border);
}
.message-role {
font-weight: bold;
text-transform: uppercase;
font-size: 0.85rem;
letter-spacing: 0.5px;
}
.message.role-user .message-role {
color: var(--accent);
}
.message.role-assistant .message-role {
color: var(--success);
}
.message-timestamp {
color: var(--text-secondary);
font-size: 0.8rem;
}
.message-content {
color: var(--text-primary);
white-space: pre-wrap;
word-wrap: break-word;
line-height: 1.5;
}
.message-meta {
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid var(--border);
display: flex;
gap: 1.5rem;
font-size: 0.8rem;
color: var(--text-secondary);
}
.meta-item {
display: flex;
gap: 0.5rem;
}
.meta-label {
color: var(--text-secondary);
}
.meta-value {
color: var(--warning);
font-family: monospace;
}
/* Code Blocks */
code {
background-color: var(--code-bg);
color: var(--warning);
padding: 0.2rem 0.4rem;
border-radius: 3px;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 0.9em;
}
pre {
background-color: var(--code-bg);
border: 1px solid var(--border);
border-radius: 4px;
padding: 1rem;
overflow-x: auto;
margin: 0.5rem 0;
}
pre code {
background: none;
padding: 0;
}
/* Error Messages */
.error-message {
background-color: var(--bg-secondary);
border-left: 3px solid var(--error);
color: var(--error);
padding: 1rem;
border-radius: 4px;
margin-bottom: 1rem;
display: none;
}
.error-message.show {
display: block;
}
/* Documentation Section */
.docs {
background-color: var(--bg-secondary);
border-radius: 8px;
padding: 2rem;
margin-top: 3rem;
}
.docs h2 {
color: var(--accent);
margin-bottom: 1.5rem;
font-size: 1.5rem;
}
.doc-content h3 {
color: var(--success);
margin-top: 1.5rem;
margin-bottom: 0.75rem;
font-size: 1.2rem;
}
.doc-content p {
margin-bottom: 1rem;
color: var(--text-primary);
}
.doc-content ul, .doc-content ol {
margin-left: 2rem;
margin-bottom: 1rem;
}
.doc-content li {
margin-bottom: 0.5rem;
color: var(--text-primary);
}
.doc-content a {
color: var(--accent);
text-decoration: none;
}
.doc-content a:hover {
text-decoration: underline;
}
.doc-content strong {
color: var(--success);
}
/* Footer */
footer {
text-align: center;
padding: 2rem;
color: var(--text-secondary);
font-size: 0.85rem;
border-top: 1px solid var(--border);
margin-top: 3rem;
}
footer a {
color: var(--accent);
text-decoration: none;
}
footer a:hover {
text-decoration: underline;
}
/* Utility Classes */
.hidden {
display: none !important;
}
/* Scrollbar Styling */
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: var(--bg-primary);
}
::-webkit-scrollbar-thumb {
background: var(--bg-tertiary);
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--border);
}
</style>
</head>
<body>
<header>
<h1>Transcript Loader</h1>
<p class="tagline">Load and view Claude Code JSONL transcripts with syntax highlighting</p>
</header>
<main>
<!-- File Upload Interface -->
<section class="tool-interface" id="uploadArea">
<div class="upload-area">
<h2>Load Transcript File</h2>
<p>Drag and drop a .jsonl transcript file here, or click to browse</p>
<div class="file-input-wrapper">
<input type="file" id="fileInput" accept=".jsonl,.json">
<button class="btn-upload" onclick="document.getElementById('fileInput').click()">
Select File
</button>
</div>
<!-- Progress Bar -->
<div class="progress-container" id="progressContainer">
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<div class="progress-text" id="progressText">Loading...</div>
</div>
<!-- File Info -->
<div class="file-info" id="fileInfo">
<div class="file-info-item">
<span class="file-info-label">File Name:</span>
<span class="file-info-value" id="fileName"></span>
</div>
<div class="file-info-item">
<span class="file-info-label">File Size:</span>
<span class="file-info-value" id="fileSize"></span>
</div>
<div class="file-info-item">
<span class="file-info-label">Status:</span>
<span class="file-info-value" id="fileStatus">Ready</span>
</div>
</div>
</div>
</section>
<!-- Error Message -->
<div class="error-message" id="errorMessage"></div>
<!-- Results Section -->
<section class="results" id="results">
<div class="results-header">
<h2>Transcript Messages</h2>
<div class="stats">
<div class="stat-item">
<div class="stat-label">Total Messages</div>
<div class="stat-value" id="totalMessages">0</div>
</div>
<div class="stat-item">
<div class="stat-label">User Messages</div>
<div class="stat-value" id="userMessages">0</div>
</div>
<div class="stat-item">
<div class="stat-label">Assistant Messages</div>
<div class="stat-value" id="assistantMessages">0</div>
</div>
</div>
</div>
<div class="message-list" id="messageList"></div>
</section>
<!-- Documentation -->
<section class="docs">
<h2>About This Tool</h2>
<div class="doc-content">
<h3>Purpose</h3>
<p>The Transcript Loader enables Claude Code developers to load, parse, and view conversation transcripts stored in JSONL format. It provides a clean, readable interface for inspecting message history, understanding conversation flow, and debugging AI interactions.</p>
<h3>Features</h3>
<ul>
<li><strong>Drag-and-Drop Loading:</strong> Simply drag your .jsonl transcript file onto the upload area</li>
<li><strong>Progress Tracking:</strong> Real-time progress bar for large file uploads</li>
<li><strong>JSONL Parsing:</strong> Line-by-line JSON parsing for efficient memory usage</li>
<li><strong>Message Display:</strong> Clean, color-coded display of user and assistant messages</li>
<li><strong>Metadata Visibility:</strong> Shows timestamps, session IDs, working directory, and git branch</li>
<li><strong>Error Handling:</strong> Graceful error messages for invalid files or parsing issues</li>
<li><strong>Statistics Dashboard:</strong> Quick overview of message counts by role</li>
<li><strong>Syntax Highlighting:</strong> Code-friendly monospace fonts and color schemes</li>
<li><strong>Dark Theme:</strong> Developer-friendly dark UI for long viewing sessions</li>
</ul>
<h3>Web Research Integration</h3>
<p><strong>Source:</strong> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FileReader" target="_blank">MDN Web Docs - FileReader API</a></p>
<p><strong>Techniques Applied:</strong></p>
<ul>
<li><strong>readAsText() Method:</strong> Using FileReader.readAsText() to read the entire JSONL file as text, then parsing line-by-line for efficient memory usage</li>
<li><strong>onload Event Handling:</strong> Implementing the onload event handler to process file contents after reading completes, with validation before parsing</li>
<li><strong>Progress Tracking:</strong> Using the onprogress event to display a real-time progress bar (percentage loaded) for large transcript files</li>
<li><strong>Error Handling Pattern:</strong> Implementing onerror handler with user-friendly error messages and visual feedback</li>
<li><strong>File Validation:</strong> Checking file type and size before reading to prevent issues with invalid files</li>
</ul>
<h3>Usage</h3>
<ol>
<li><strong>Locate Your Transcript:</strong> Find a Claude Code transcript file (typically .jsonl format)</li>
<li><strong>Load the File:</strong> Either drag-and-drop the file onto the upload area, or click "Select File" to browse</li>
<li><strong>Monitor Progress:</strong> Watch the progress bar as the file loads (especially useful for large files)</li>
<li><strong>View Messages:</strong> Once loaded, scroll through the message list to inspect conversation history</li>
<li><strong>Examine Metadata:</strong> Check timestamps, session IDs, working directories, and other metadata for each message</li>
<li><strong>Review Statistics:</strong> See the total message count and breakdown by role at the top of the results</li>
</ol>
<h3>Technical Notes</h3>
<p>This tool parses JSONL (JSON Lines) format where each line is a separate JSON object. The parser handles:</p>
<ul>
<li>Line-by-line parsing to avoid loading entire file into memory at once</li>
<li>Malformed JSON lines (skips and continues)</li>
<li>Missing or optional fields with graceful defaults</li>
<li>Different content formats (string vs. array of content blocks)</li>
<li>Timestamp formatting and display</li>
</ul>
<h3>Browser Compatibility</h3>
<p>This tool requires modern browser support for FileReader API. Compatible with:</p>
<ul>
<li>Chrome/Edge 88+</li>
<li>Firefox 78+</li>
<li>Safari 14+</li>
</ul>
</div>
</section>
</main>
<footer>
<p>Claude Code DevTools | Generated via web-enhanced infinite loop</p>
<p>Web Source: <a href="https://developer.mozilla.org/en-US/docs/Web/API/FileReader" target="_blank">MDN Web Docs - FileReader API</a></p>
</footer>
<script>
// Global state
let transcriptData = [];
// DOM Elements
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const progressContainer = document.getElementById('progressContainer');
const progressFill = document.getElementById('progressFill');
const progressText = document.getElementById('progressText');
const fileInfo = document.getElementById('fileInfo');
const fileName = document.getElementById('fileName');
const fileSize = document.getElementById('fileSize');
const fileStatus = document.getElementById('fileStatus');
const errorMessage = document.getElementById('errorMessage');
const results = document.getElementById('results');
const messageList = document.getElementById('messageList');
const totalMessages = document.getElementById('totalMessages');
const userMessages = document.getElementById('userMessages');
const assistantMessages = document.getElementById('assistantMessages');
// File Input Event Listener
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
loadTranscriptFile(file);
}
});
// Drag and Drop Event Listeners
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('drag-over');
});
uploadArea.addEventListener('dragleave', (e) => {
e.preventDefault();
uploadArea.classList.remove('drag-over');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('drag-over');
const file = e.dataTransfer.files[0];
if (file) {
loadTranscriptFile(file);
}
});
// Main File Loading Function (Using FileReader API)
function loadTranscriptFile(file) {
// Reset state
transcriptData = [];
errorMessage.classList.remove('show');
results.classList.remove('show');
// Display file info
fileName.textContent = file.name;
fileSize.textContent = formatFileSize(file.size);
fileStatus.textContent = 'Loading...';
fileInfo.classList.add('show');
// Show progress container
progressContainer.style.display = 'block';
progressFill.style.width = '0%';
progressText.textContent = 'Loading file...';
// Create FileReader instance
const reader = new FileReader();
// TECHNIQUE 1: Progress Event Handler for Large Files
reader.onprogress = (event) => {
if (event.lengthComputable) {
const percentLoaded = Math.round((event.loaded / event.total) * 100);
progressFill.style.width = percentLoaded + '%';
progressText.textContent = `Loading: ${percentLoaded}%`;
}
};
// TECHNIQUE 2: onload Event Handler with File Validation
reader.onload = () => {
try {
progressText.textContent = 'Parsing transcript...';
// Validate file type (basic check)
if (!file.name.endsWith('.jsonl') && !file.name.endsWith('.json')) {
showError('Invalid file type. Please select a .jsonl or .json file.');
resetUploadUI();
return;
}
// Parse JSONL content
const content = reader.result;
parseJSONL(content);
// Update UI
fileStatus.textContent = 'Loaded Successfully';
progressFill.style.width = '100%';
progressText.textContent = 'Complete!';
// Display results
displayTranscript();
// Hide progress after a delay
setTimeout(() => {
progressContainer.style.display = 'none';
}, 1000);
} catch (error) {
showError(`Error parsing transcript: ${error.message}`);
resetUploadUI();
}
};
// TECHNIQUE 3: Error Handling Pattern
reader.onerror = () => {
showError('Error reading file. Please try again.');
resetUploadUI();
};
// TECHNIQUE 4: readAsText() Method for JSONL Files
reader.readAsText(file);
}
// Parse JSONL (line-by-line JSON)
function parseJSONL(content) {
const lines = content.trim().split('\n');
lines.forEach((line, index) => {
if (!line.trim()) return; // Skip empty lines
try {
const message = JSON.parse(line);
transcriptData.push(message);
} catch (error) {
console.warn(`Failed to parse line ${index + 1}:`, error);
// Continue parsing other lines
}
});
if (transcriptData.length === 0) {
throw new Error('No valid messages found in transcript');
}
}
// Display Transcript Messages
function displayTranscript() {
messageList.innerHTML = '';
// Calculate statistics
let userCount = 0;
let assistantCount = 0;
transcriptData.forEach((msg) => {
const role = msg.message?.role || msg.role || 'unknown';
if (role === 'user') userCount++;
if (role === 'assistant') assistantCount++;
});
// Update statistics
totalMessages.textContent = transcriptData.length;
userMessages.textContent = userCount;
assistantMessages.textContent = assistantCount;
// Render messages
transcriptData.forEach((msg, index) => {
const messageEl = createMessageElement(msg, index);
messageList.appendChild(messageEl);
});
// Show results section
results.classList.add('show');
}
// Create Message Element
function createMessageElement(msg, index) {
const messageDiv = document.createElement('div');
const role = msg.message?.role || msg.role || 'unknown';
messageDiv.className = `message role-${role}`;
// Message Header
const header = document.createElement('div');
header.className = 'message-header';
const roleSpan = document.createElement('span');
roleSpan.className = 'message-role';
roleSpan.textContent = role;
const timestamp = document.createElement('span');
timestamp.className = 'message-timestamp';
timestamp.textContent = formatTimestamp(msg.timestamp);
header.appendChild(roleSpan);
header.appendChild(timestamp);
// Message Content
const content = document.createElement('div');
content.className = 'message-content';
let contentText = '';
if (msg.message?.content) {
if (typeof msg.message.content === 'string') {
contentText = msg.message.content;
} else if (Array.isArray(msg.message.content)) {
// Handle content blocks
contentText = msg.message.content
.map(block => {
if (typeof block === 'string') return block;
if (block.text) return block.text;
if (block.type === 'tool_use') return `[Tool: ${block.name}]`;
return JSON.stringify(block, null, 2);
})
.join('\n\n');
}
}
content.textContent = contentText || '[No content]';
// Message Metadata
const meta = document.createElement('div');
meta.className = 'message-meta';
if (msg.sessionId) {
const sessionItem = document.createElement('div');
sessionItem.className = 'meta-item';
sessionItem.innerHTML = `<span class="meta-label">Session:</span><span class="meta-value">${msg.sessionId.substring(0, 8)}...</span>`;
meta.appendChild(sessionItem);
}
if (msg.cwd) {
const cwdItem = document.createElement('div');
cwdItem.className = 'meta-item';
cwdItem.innerHTML = `<span class="meta-label">CWD:</span><span class="meta-value">${msg.cwd}</span>`;
meta.appendChild(cwdItem);
}
if (msg.gitBranch) {
const branchItem = document.createElement('div');
branchItem.className = 'meta-item';
branchItem.innerHTML = `<span class="meta-label">Branch:</span><span class="meta-value">${msg.gitBranch}</span>`;
meta.appendChild(branchItem);
}
// Assemble message
messageDiv.appendChild(header);
messageDiv.appendChild(content);
if (meta.children.length > 0) {
messageDiv.appendChild(meta);
}
return messageDiv;
}
// Utility Functions
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
}
function formatTimestamp(timestamp) {
if (!timestamp) return 'No timestamp';
const date = new Date(timestamp);
return date.toLocaleString();
}
function showError(message) {
errorMessage.textContent = message;
errorMessage.classList.add('show');
}
function resetUploadUI() {
progressContainer.style.display = 'none';
fileStatus.textContent = 'Error';
progressFill.style.width = '0%';
}
// Initialize
console.log('Claude Code Transcript Loader initialized');
console.log('FileReader API techniques applied:');
console.log('- readAsText() for JSONL parsing');
console.log('- onprogress for file loading progress');
console.log('- onload event handling with validation');
console.log('- onerror for graceful error handling');
</script>
</body>
</html>