-
-
Save simonw/d225134d348435c8106070cb74c78888 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ╭─── Claude Code v2.0.22 ──────────────────────────────────────────────────────╮ | |
| │ │ Tips for getting started │ | |
| │ Welcome back Simon! │ Run /init to create a CLAUDE.md file with ins… │ | |
| │ │ ────────────────────────────────────────────── │ | |
| │ ▗ ▗ ▖ ▖ │ Recent activity │ | |
| │ │ No recent activity │ | |
| │ ▘▘ ▝▝ │ │ | |
| │ │ │ | |
| │ Sonnet 4.5 · Claude Max │ │ | |
| │ /private/tmp/geo │ │ | |
| ╰──────────────────────────────────────────────────────────────────────────────╯ | |
| > That full.json file has a full transcript from a deep research session. Build an HTML page that loads that via | |
| fetch() and displays it in a nice readable format, just the things the session says it is thinking about, the searches | |
| it runs, the formatted tool calls it makes to code interpreter and the fital result at the end | |
| ⏺ I'll help you create an HTML page to display the transcript from full.json. Let me first check the structure of that | |
| file to understand what we're working with. | |
| ⏺ Read(full.json) | |
| ⎿ Error: File content (36647 tokens) exceeds maximum allowed tokens (25000). Please use offset and limit parameters to | |
| read specific portions of the file, or use the GrepTool to search for specific content. | |
| ⏺ Read(full.json) | |
| ⎿ Read 100 lines | |
| ⏺ Search(pattern: ""type": "(reasoning|web_search_call|code_interpreter_call|text)"", path: "full.json", output_mode: | |
| "content", head_limit: 50) | |
| ⎿ Found 50 lines (ctrl+o to expand) | |
| ⏺ Bash(tail -100 /private/tmp/geo/full.json | head -50) | |
| ⎿ "url": "https://bnr.bg/ru/post/101067363/edinstvennii-orkestrion-v-bolgarii-hranitsa-v-istoricheskom | |
| -muzee-g-gabrovo#:~:text=%D0%B8%D0%B7%D0%B3%D0%BE%D1%82%D0%BE%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D1%8E%20%D1%82%D0%BE%D | |
| 0%BD%D0%BA%D0%B8%D1%85%20%D1%82%D0%BA%D0%B0%D0%BD%D0%B5%D0%B9%20%D0%B8%D0%B7%20%D1%88%D0%B5%D1%80%D1%81%D1%82%D0%B | |
| … +221 lines (ctrl+o to expand) | |
| ⏺ Now I'll create a nice HTML page to display the transcript. Based on the structure, I can see it contains reasoning | |
| summaries, web searches, and a final JSON result. | |
| ⏺ Write(viewer.html) | |
| ⎿ Wrote 417 lines to viewer.html | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Deep Research Session Viewer</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| … +407 lines (ctrl+o to expand) | |
| ⏺ I've created viewer.html that will display the deep research transcript from full.json. The page features: | |
| Display sections: | |
| - Header - Shows model info, status, and timestamp | |
| - Statistics - Count of thinking steps, searches, pages visited, and total steps | |
| - Timeline view - Chronological display of: | |
| - 🧠 Thinking sections (reasoning summaries) | |
| - 🔍 Search queries | |
| - 🌐 Pages visited (with clickable links) | |
| - Final Result - The JSON output at the end (syntax highlighted in a dark theme code block) | |
| Design features: | |
| - Beautiful gradient purple header | |
| - Color-coded sections (blue for thinking, orange for searches, green for page visits) | |
| - Responsive layout that works on mobile and desktop | |
| - Timeline visualization with connecting line | |
| - Professional dark code editor theme for the final JSON result | |
| To view it, simply open viewer.html in your browser. The page will automatically load and parse full.json via fetch() | |
| and display everything in a clean, readable format. | |
| > Now read https://tools.simonwillison.net/json-string-extractor using curl and imitate the mechanism it uses for both | |
| allowing users to paste in JSON or allowing them to provide a Gist URL | |
| ⏺ Bash(curl -s "https://tools.simonwillison.net/json-string-extractor") | |
| ⎿ <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| … +440 lines (ctrl+o to expand) | |
| ⏺ Now I'll update the viewer to support both pasting JSON directly and loading from a Gist URL: | |
| ⏺ Read(viewer.html) | |
| ⎿ Read 417 lines | |
| ⏺ Write(viewer.html) | |
| ⎿ Updated viewer.html with 295 additions and 20 removals | |
| 48 padding: 30px; | |
| 49 } | |
| 50 | |
| 51 + .input-section { | |
| 52 + margin-bottom: 30px; | |
| 53 + padding: 20px; | |
| 54 + background: #f8f9fa; | |
| 55 + border-radius: 8px; | |
| 56 + } | |
| 57 + | |
| 58 + .button-group { | |
| 59 + display: flex; | |
| 60 + gap: 10px; | |
| 61 + margin-bottom: 15px; | |
| 62 + flex-wrap: wrap; | |
| 63 + } | |
| 64 + | |
| 65 + button { | |
| 66 + padding: 10px 20px; | |
| 67 + border: none; | |
| 68 + border-radius: 6px; | |
| 69 + cursor: pointer; | |
| 70 + font-size: 14px; | |
| 71 + font-weight: 500; | |
| 72 + transition: all 0.2s; | |
| 73 + font-family: inherit; | |
| 74 + } | |
| 75 + | |
| 76 + button.primary { | |
| 77 + background: #667eea; | |
| 78 + color: white; | |
| 79 + } | |
| 80 + | |
| 81 + button.primary:hover { | |
| 82 + background: #5568d3; | |
| 83 + } | |
| 84 + | |
| 85 + button.secondary { | |
| 86 + background: #6c757d; | |
| 87 + color: white; | |
| 88 + } | |
| 89 + | |
| 90 + button.secondary:hover { | |
| 91 + background: #5a6268; | |
| 92 + } | |
| 93 + | |
| 94 + button:disabled { | |
| 95 + background: #ccc; | |
| 96 + cursor: not-allowed; | |
| 97 + } | |
| 98 + | |
| 99 + textarea { | |
| 100 + width: 100%; | |
| 101 + min-height: 150px; | |
| 102 + padding: 12px; | |
| 103 + border: 2px solid #ddd; | |
| 104 + border-radius: 6px; | |
| 105 + font-family: 'Monaco', 'Courier New', monospace; | |
| 106 + font-size: 14px; | |
| 107 + resize: vertical; | |
| 108 + margin-top: 10px; | |
| 109 + } | |
| 110 + | |
| 111 + textarea:focus { | |
| 112 + outline: none; | |
| 113 + border-color: #667eea; | |
| 114 + } | |
| 115 + | |
| 116 .section { | |
| 117 margin-bottom: 30px; | |
| 118 border-left: 4px solid #667eea; | |
| ... | |
| 202 color: #c62828; | |
| 203 padding: 20px; | |
| 204 border-radius: 8px; | |
| 205 - margin: 20px; | |
| 205 + margin-bottom: 20px; | |
| 206 } | |
| 207 | |
| 208 .timeline { | |
| ... | |
| 238 background: #e0e0e0; | |
| 239 } | |
| 240 | |
| 241 + .welcome { | |
| 242 + text-align: center; | |
| 243 + padding: 50px 20px; | |
| 244 + color: #666; | |
| 245 + } | |
| 246 + | |
| 247 + .welcome h2 { | |
| 248 + color: #667eea; | |
| 249 + margin-bottom: 15px; | |
| 250 + } | |
| 251 + | |
| 252 @media (max-width: 768px) { | |
| 253 body { | |
| 254 padding: 10px; | |
| ... | |
| 250 .stats { | |
| 251 flex-direction: column; | |
| 252 } | |
| 253 + | |
| 254 + .button-group { | |
| 255 + flex-direction: column; | |
| 256 + } | |
| 257 + | |
| 258 + button { | |
| 259 + width: 100%; | |
| 260 + } | |
| 261 } | |
| 262 </style> | |
| 263 </head> | |
| 264 <body> | |
| 265 <div class="container"> | |
| 266 <header> | |
| 267 - <h1>Deep Research Session</h1> | |
| 268 - <div class="meta" id="meta-info">Loading session details...</div> | |
| 267 + <h1>Deep Research Session Viewer</h1> | |
| 268 + <div class="meta" id="meta-info">Load a transcript to view details</div> | |
| 269 </header> | |
| 270 | |
| 271 <div class="content"> | |
| 272 - <div class="loading" id="loading">Loading transcript...</div> | |
| 272 + <div class="input-section"> | |
| 273 + <div class="button-group"> | |
| 274 + <button id="loadLocal" class="primary">Load local full.json</button> | |
| 275 + <button id="loadGist" class="secondary">Load from Gist</button> | |
| 276 + <button id="togglePaste" class="secondary">Paste JSON</button> | |
| 277 + </div> | |
| 278 + <textarea id="jsonInput" placeholder="Paste your deep research transcript JSON | |
| + here..." style="display: none;"></textarea> | |
| 279 + </div> | |
| 280 + | |
| 281 + <div id="error" style="display: none;"></div> | |
| 282 + | |
| 283 + <div id="welcome" class="welcome"> | |
| 284 + <h2>Welcome!</h2> | |
| 285 + <p>Load a deep research transcript using one of the options above.</p> | |
| 286 + </div> | |
| 287 + | |
| 288 <div id="main-content" style="display: none;"> | |
| 289 <div class="stats" id="stats"></div> | |
| 290 <div class="timeline" id="timeline"></div> | |
| 291 <div class="final-result" id="final-result"></div> | |
| 292 </div> | |
| 293 - <div id="error" style="display: none;"></div> | |
| 293 </div> | |
| 294 </div> | |
| 295 | |
| 296 - <script> | |
| 297 - async function loadTranscript() { | |
| 296 + <script type="module"> | |
| 297 + const jsonInput = document.getElementById('jsonInput'); | |
| 298 + const errorDiv = document.getElementById('error'); | |
| 299 + const welcomeDiv = document.getElementById('welcome'); | |
| 300 + const mainContent = document.getElementById('main-content'); | |
| 301 + const loadLocalBtn = document.getElementById('loadLocal'); | |
| 302 + const loadGistBtn = document.getElementById('loadGist'); | |
| 303 + const togglePasteBtn = document.getElementById('togglePaste'); | |
| 304 + | |
| 305 + function showError(message) { | |
| 306 + errorDiv.textContent = message; | |
| 307 + errorDiv.style.display = 'block'; | |
| 308 + welcomeDiv.style.display = 'none'; | |
| 309 + mainContent.style.display = 'none'; | |
| 310 + } | |
| 311 + | |
| 312 + function hideError() { | |
| 313 + errorDiv.style.display = 'none'; | |
| 314 + } | |
| 315 + | |
| 316 + function extractGistId(input) { | |
| 317 + // Try to extract Gist ID from URL or use input directly | |
| 318 + const urlMatch = input.match(/gist\.github\.com\/[^\/]+\/([a-fA-F0-9]+)/i); | |
| 319 + if (urlMatch) { | |
| 320 + return urlMatch[1]; | |
| 321 + } | |
| 322 + // Check if it's just a Gist ID (alphanumeric string) | |
| 323 + if (/^[a-fA-F0-9]+$/i.test(input.trim())) { | |
| 324 + return input.trim(); | |
| 325 + } | |
| 326 + return null; | |
| 327 + } | |
| 328 + | |
| 329 + async function loadGistJSON(gistId) { | |
| 330 + const response = await fetch(`https://api.github.com/gists/${gistId}`); | |
| 331 + | |
| 332 + if (!response.ok) { | |
| 333 + throw new Error(`Failed to fetch Gist: ${response.status} ${response.statusText}`); | |
| 334 + } | |
| 335 + | |
| 336 + const gistData = await response.json(); | |
| 337 + | |
| 338 + // Find the first file that contains valid JSON | |
| 339 + for (const filename in gistData.files) { | |
| 340 + const file = gistData.files[filename]; | |
| 341 + try { | |
| 342 + // Try to parse the content as JSON | |
| 343 + const parsed = JSON.parse(file.content); | |
| 344 + return parsed; | |
| 345 + } catch (e) { | |
| 346 + // Not valid JSON, continue to next file | |
| 347 + continue; | |
| 348 + } | |
| 349 + } | |
| 350 + | |
| 351 + throw new Error('No valid JSON file found in the Gist'); | |
| 352 + } | |
| 353 + | |
| 354 + async function loadGist() { | |
| 355 + const input = prompt('Enter a Gist URL or Gist ID:'); | |
| 356 + | |
| 357 + if (!input) { | |
| 358 + return; // User cancelled | |
| 359 + } | |
| 360 + | |
| 361 + const gistId = extractGistId(input); | |
| 362 + | |
| 363 + if (!gistId) { | |
| 364 + showError('Invalid Gist URL or ID. Please provide a valid GitHub Gist URL or ID.'); | |
| 365 + return; | |
| 366 + } | |
| 367 + | |
| 368 + loadGistBtn.disabled = true; | |
| 369 + loadGistBtn.textContent = 'Loading...'; | |
| 370 + | |
| 371 try { | |
| 372 + const data = await loadGistJSON(gistId); | |
| 373 + displayTranscript(data); | |
| 374 + | |
| 375 + // Update the URL hash | |
| 376 + window.location.hash = `gist=${gistId}`; | |
| 377 + | |
| 378 + loadGistBtn.textContent = 'Load from Gist'; | |
| 379 + } catch (e) { | |
| 380 + showError(`Failed to load Gist: ${e.message}`); | |
| 381 + loadGistBtn.textContent = 'Load from Gist'; | |
| 382 + } finally { | |
| 383 + loadGistBtn.disabled = false; | |
| 384 + } | |
| 385 + } | |
| 386 + | |
| 387 + async function loadGistFromHash() { | |
| 388 + const hash = window.location.hash; | |
| 389 + const gistMatch = hash.match(/gist=([a-fA-F0-9]+)/i); | |
| 390 + | |
| 391 + if (gistMatch) { | |
| 392 + const gistId = gistMatch[1]; | |
| 393 + try { | |
| 394 + const data = await loadGistJSON(gistId); | |
| 395 + displayTranscript(data); | |
| 396 + } catch (e) { | |
| 397 + showError(`Failed to load Gist from URL: ${e.message}`); | |
| 398 + } | |
| 399 + } | |
| 400 + } | |
| 401 + | |
| 402 + async function loadLocalFile() { | |
| 403 + loadLocalBtn.disabled = true; | |
| 404 + loadLocalBtn.textContent = 'Loading...'; | |
| 405 + | |
| 406 + try { | |
| 407 const response = await fetch('full.json'); | |
| 408 if (!response.ok) { | |
| 409 throw new Error(`HTTP error! status: ${response.status}`); | |
| 410 } | |
| 411 const data = await response.json(); | |
| 412 displayTranscript(data); | |
| 413 + | |
| 414 + // Clear hash | |
| 415 + window.location.hash = ''; | |
| 416 + | |
| 417 + loadLocalBtn.textContent = 'Load local full.json'; | |
| 418 } catch (error) { | |
| 419 - document.getElementById('loading').style.display = 'none'; | |
| 420 - const errorDiv = document.getElementById('error'); | |
| 421 - errorDiv.style.display = 'block'; | |
| 422 - errorDiv.innerHTML = ` | |
| 423 - <h2>Error Loading Transcript</h2> | |
| 424 - <p>${error.message}</p> | |
| 425 - <p>Make sure <code>full.json</code> is in the same directory as this HTML | |
| - file.</p> | |
| 426 - `; | |
| 419 + showError(`Error loading local file: ${error.message}. Make sure full.json is in the | |
| + same directory.`); | |
| 420 + loadLocalBtn.textContent = 'Load local full.json'; | |
| 421 + } finally { | |
| 422 + loadLocalBtn.disabled = false; | |
| 423 } | |
| 424 } | |
| 425 | |
| 426 + function togglePasteArea() { | |
| 427 + if (jsonInput.style.display === 'none') { | |
| 428 + jsonInput.style.display = 'block'; | |
| 429 + togglePasteBtn.textContent = 'Hide paste area'; | |
| 430 + jsonInput.focus(); | |
| 431 + } else { | |
| 432 + jsonInput.style.display = 'none'; | |
| 433 + togglePasteBtn.textContent = 'Paste JSON'; | |
| 434 + } | |
| 435 + } | |
| 436 + | |
| 437 + function processJSON() { | |
| 438 + const input = jsonInput.value.trim(); | |
| 439 + | |
| 440 + if (!input) { | |
| 441 + return; | |
| 442 + } | |
| 443 + | |
| 444 + try { | |
| 445 + const parsed = JSON.parse(input); | |
| 446 + hideError(); | |
| 447 + displayTranscript(parsed); | |
| 448 + | |
| 449 + // Clear hash | |
| 450 + window.location.hash = ''; | |
| 451 + } catch (e) { | |
| 452 + showError(`Invalid JSON: ${e.message}`); | |
| 453 + } | |
| 454 + } | |
| 455 + | |
| 456 function displayTranscript(data) { | |
| 457 - document.getElementById('loading').style.display = 'none'; | |
| 458 - document.getElementById('main-content').style.display = 'block'; | |
| 457 + hideError(); | |
| 458 + welcomeDiv.style.display = 'none'; | |
| 459 + mainContent.style.display = 'block'; | |
| 460 | |
| 461 // Display meta info | |
| 462 const metaInfo = document.getElementById('meta-info'); | |
| ... | |
| 308 let thinkingCount = 0; | |
| 309 let searchCount = 0; | |
| 310 let pageOpenCount = 0; | |
| 311 + let codeInterpreterCount = 0; | |
| 312 let finalResult = null; | |
| 313 | |
| 314 data.output.forEach(item => { | |
| ... | |
| 318 if (item.action.type === 'search') searchCount++; | |
| 319 if (item.action.type === 'open_page') pageOpenCount++; | |
| 320 } | |
| 321 + if (item.type === 'code_interpreter_call') { | |
| 322 + codeInterpreterCount++; | |
| 323 + } | |
| 324 }); | |
| 325 | |
| 326 // Find final result | |
| ... | |
| 328 | |
| 329 // Display stats | |
| 330 const statsDiv = document.getElementById('stats'); | |
| 331 - statsDiv.innerHTML = ` | |
| 331 + let statsHtml = ` | |
| 332 <div class="stat"> | |
| 333 <div class="stat-value">${thinkingCount}</div> | |
| 334 <div class="stat-label">Thinking Steps</div> | |
| ... | |
| 341 <div class="stat-value">${pageOpenCount}</div> | |
| 342 <div class="stat-label">Pages Visited</div> | |
| 343 </div> | |
| 344 + `; | |
| 345 + | |
| 346 + if (codeInterpreterCount > 0) { | |
| 347 + statsHtml += ` | |
| 348 + <div class="stat"> | |
| 349 + <div class="stat-value">${codeInterpreterCount}</div> | |
| 350 + <div class="stat-label">Code Executions</div> | |
| 351 + </div> | |
| 352 + `; | |
| 353 + } | |
| 354 + | |
| 355 + statsHtml += ` | |
| 356 <div class="stat"> | |
| 357 <div class="stat-value">${data.output.length}</div> | |
| 358 <div class="stat-label">Total Steps</div> | |
| 359 </div> | |
| 360 `; | |
| 361 | |
| 362 + statsDiv.innerHTML = statsHtml; | |
| 363 + | |
| 364 // Display timeline | |
| 365 const timeline = document.getElementById('timeline'); | |
| 366 let timelineHtml = ''; | |
| ... | |
| 387 `; | |
| 388 } | |
| 389 } | |
| 390 + | |
| 391 + if (item.type === 'code_interpreter_call' && item.action) { | |
| 392 + const code = item.action.code || ''; | |
| 393 + timelineHtml += ` | |
| 394 + <div class="timeline-item"> | |
| 395 + <div class="thinking" style="background: #fff9e6; border-left-color: | |
| + #ffc107;"> | |
| 396 + <h3 style="color: #ffc107;">Code Execution</h3> | |
| 397 + <pre style="background: #1e1e1e; color: #d4d4d4; padding: 10px; | |
| + border-radius: 4px; overflow-x: auto; font-size: 0.85em;">${escapeHtml(code)}</pre> | |
| 398 + </div> | |
| 399 + </div> | |
| 400 + `; | |
| 401 + } | |
| 402 }); | |
| 403 | |
| 404 timeline.innerHTML = timelineHtml; | |
| ... | |
| 394 // Display final result | |
| 395 const finalResultDiv = document.getElementById('final-result'); | |
| 396 if (finalResult) { | |
| 397 + finalResultDiv.style.display = 'block'; | |
| 398 finalResultDiv.innerHTML = ` | |
| 399 <h2>Final Result</h2> | |
| 400 <pre>${escapeHtml(finalResult)}</pre> | |
| ... | |
| 409 return div.innerHTML; | |
| 410 } | |
| 411 | |
| 412 - // Load transcript on page load | |
| 413 - loadTranscript(); | |
| 412 + // Event listeners | |
| 413 + loadLocalBtn.addEventListener('click', loadLocalFile); | |
| 414 + loadGistBtn.addEventListener('click', loadGist); | |
| 415 + togglePasteBtn.addEventListener('click', togglePasteArea); | |
| 416 + jsonInput.addEventListener('input', processJSON); | |
| 417 + | |
| 418 + // Load Gist from hash if present on page load | |
| 419 + loadGistFromHash(); | |
| 420 </script> | |
| 421 </body> | |
| 422 </html> | |
| ⏺ Perfect! I've updated viewer.html to support multiple loading methods, just like Simon Willison's tool. Here's what's | |
| new: | |
| New Features | |
| Three ways to load transcripts: | |
| 1. Load local full.json - Fetches the local file via fetch() (original behavior) | |
| 2. Load from Gist - Prompts for a GitHub Gist URL or ID | |
| - Extracts Gist ID from full URLs like https://gist.github.com/username/abc123 | |
| - Accepts just the ID like abc123 | |
| - Uses GitHub API to fetch the first valid JSON file | |
| - Updates URL hash with #gist=<id> for sharing | |
| 3. Paste JSON - Toggles a textarea where you can paste JSON directly | |
| - Parses as you type | |
| - Clears the hash when using pasted content | |
| Key Implementation Details (from Simon's tool) | |
| - Gist ID extraction: Regex pattern matches GitHub Gist URLs or validates raw IDs | |
| - Hash-based loading: On page load, checks for #gist=<id> in URL and auto-loads | |
| - GitHub API integration: Fetches from https://api.github.com/gists/{id} and finds first valid JSON file | |
| - Live parsing: Input event listener on textarea for real-time processing | |
| Additional Improvements | |
| - Added support for code interpreter calls (displayed in yellow with code syntax) | |
| - Welcome screen when no transcript is loaded | |
| - Better error handling with descriptive messages | |
| - Mobile-responsive button layout | |
| Now you can share deep research transcripts via Gist URL, paste them directly, or load local files! | |
| > change the "Load local full.json" button to be "load example" instead, it should load | |
| https://gist.github.com/simonw/3454a4ce40f8547a5c65c911de611ff4 | |
| ⏺ Update(viewer.html) | |
| ⎿ Updated viewer.html with 1 addition and 1 removal | |
| 347 <div class="content"> | |
| 348 <div class="input-section"> | |
| 349 <div class="button-group"> | |
| 350 - <button id="loadLocal" class="primary">Load local full.json</button> | |
| 350 + <button id="loadExample" class="primary">Load example</button> | |
| 351 <button id="loadGist" class="secondary">Load from Gist</button> | |
| 352 <button id="togglePaste" class="secondary">Paste JSON</button> | |
| 353 </div> | |
| ⏺ Update(viewer.html) | |
| ⎿ Updated viewer.html with 1 addition and 1 removal | |
| 374 const errorDiv = document.getElementById('error'); | |
| 375 const welcomeDiv = document.getElementById('welcome'); | |
| 376 const mainContent = document.getElementById('main-content'); | |
| 377 - const loadLocalBtn = document.getElementById('loadLocal'); | |
| 377 + const loadExampleBtn = document.getElementById('loadExample'); | |
| 378 const loadGistBtn = document.getElementById('loadGist'); | |
| 379 const togglePasteBtn = document.getElementById('togglePaste'); | |
| 380 | |
| ⏺ Update(viewer.html) | |
| ⎿ Updated viewer.html with 11 additions and 14 removals | |
| 475 } | |
| 476 } | |
| 477 | |
| 478 - async function loadLocalFile() { | |
| 479 - loadLocalBtn.disabled = true; | |
| 480 - loadLocalBtn.textContent = 'Loading...'; | |
| 478 + async function loadExample() { | |
| 479 + loadExampleBtn.disabled = true; | |
| 480 + loadExampleBtn.textContent = 'Loading...'; | |
| 481 | |
| 482 try { | |
| 483 - const response = await fetch('full.json'); | |
| 484 - if (!response.ok) { | |
| 485 - throw new Error(`HTTP error! status: ${response.status}`); | |
| 486 - } | |
| 487 - const data = await response.json(); | |
| 483 + const gistId = '3454a4ce40f8547a5c65c911de611ff4'; | |
| 484 + const data = await loadGistJSON(gistId); | |
| 485 displayTranscript(data); | |
| 486 | |
| 487 - // Clear hash | |
| 488 - window.location.hash = ''; | |
| 487 + // Update the URL hash | |
| 488 + window.location.hash = `gist=${gistId}`; | |
| 489 | |
| 490 - loadLocalBtn.textContent = 'Load local full.json'; | |
| 490 + loadExampleBtn.textContent = 'Load example'; | |
| 491 } catch (error) { | |
| 492 - showError(`Error loading local file: ${error.message} | |
| - . Make sure full.json is in the same directory.`); | |
| 493 - loadLocalBtn.textContent = 'Load local full.json'; | |
| 492 + showError(`Error loading example: ${error.message}`); | |
| 493 + loadExampleBtn.textContent = 'Load example'; | |
| 494 } finally { | |
| 495 - loadLocalBtn.disabled = false; | |
| 495 + loadExampleBtn.disabled = false; | |
| 496 } | |
| 497 } | |
| 498 | |
| ⏺ Update(viewer.html) | |
| ⎿ Updated viewer.html with 1 addition and 1 removal | |
| 676 } | |
| 677 | |
| 678 // Event listeners | |
| 679 - loadLocalBtn.addEventListener('click', loadLocalFile); | |
| 679 + loadExampleBtn.addEventListener('click', loadExample); | |
| 680 loadGistBtn.addEventListener('click', loadGist); | |
| 681 togglePasteBtn.addEventListener('click', togglePasteArea); | |
| 682 jsonInput.addEventListener('input', processJSON); | |
| ⏺ Done! I've updated the viewer so the "Load local full.json" button is now "Load example" and it loads the gist at | |
| https://gist.github.com/simonw/3454a4ce40f8547a5c65c911de611ff4. | |
| The button now: | |
| - Shows "Load example" text | |
| - Loads the specified gist when clicked | |
| - Updates the URL hash to #gist=3454a4ce40f8547a5c65c911de611ff4 so the example can be shared via link | |
| - Shows loading state while fetching | |
| Users can still load their own transcripts using the "Load from Gist" or "Paste JSON" options. | |
| ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── | |
| > | |
| ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── | |
| ⏵⏵ bypass permissions on (shift+tab to cycle) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment