Spaces:
Running
Running
| <!-- Trace Explorer: Interactive trace-to-symbol mapper --> | |
| <div class="trace-explorer"></div> | |
| <style> | |
| .trace-explorer { position: relative; width: 100%; font-family: inherit; } | |
| .trace-explorer .seg-control { | |
| display: inline-flex; background: var(--surface-bg); | |
| border: 1px solid var(--border-color); border-radius: 8px; | |
| padding: 3px; gap: 2px; | |
| } | |
| .trace-explorer .seg-control button { | |
| padding: 5px 14px; border-radius: 6px; border: none; | |
| background: transparent; font-size: 12px; font-weight: 500; | |
| color: var(--text-color); cursor: pointer; transition: all 0.15s ease; | |
| opacity: 0.6; | |
| } | |
| .trace-explorer .seg-control button:hover { opacity: 0.8; } | |
| .trace-explorer .seg-control button.active { | |
| background: var(--text-color); color: var(--page-bg); | |
| opacity: 1; font-weight: 600; | |
| } | |
| .trace-explorer .top-bar { | |
| display: flex; align-items: center; gap: 12px; margin-bottom: 14px; flex-wrap: wrap; | |
| } | |
| .trace-explorer .step-info { | |
| font-size: 12px; color: var(--text-color); opacity: 0.5; margin-left: auto; | |
| font-variant-numeric: tabular-nums; | |
| } | |
| .trace-explorer .progress-track { | |
| width: 100%; height: 3px; border-radius: 2px; | |
| background: var(--border-color); margin-bottom: 14px; overflow: hidden; | |
| } | |
| .trace-explorer .progress-fill { | |
| height: 100%; border-radius: 2px; background: var(--text-color); | |
| transition: width 0.3s ease; opacity: 0.5; | |
| } | |
| .trace-explorer .panels { | |
| display: grid; grid-template-columns: 1fr 1fr; gap: 16px; | |
| } | |
| @media (max-width: 700px) { .trace-explorer .panels { grid-template-columns: 1fr; } } | |
| .trace-explorer .panel { | |
| border: 1px solid var(--border-color); border-radius: 10px; | |
| background: var(--surface-bg); overflow: hidden; | |
| backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); | |
| } | |
| .trace-explorer .panel-header { | |
| padding: 10px 14px; font-size: 11px; font-weight: 700; text-transform: uppercase; | |
| letter-spacing: 1px; border-bottom: 1px solid var(--border-color); | |
| color: var(--text-color); opacity: 0.5; | |
| } | |
| .trace-explorer .panel-body { | |
| padding: 10px 12px; font-size: 12px; line-height: 1.6; max-height: 340px; | |
| overflow-y: auto; color: var(--text-color); | |
| } | |
| .trace-explorer .msg { | |
| padding: 7px 10px; margin: 2px 0; border-radius: 6px; transition: all .25s ease; | |
| border-left: 3px solid transparent; cursor: default; | |
| } | |
| .trace-explorer .msg.active-system { background: rgba(107, 143, 113, 0.08); border-left-color: #6b8f71; } | |
| .trace-explorer .msg.active-user { background: rgba(61, 90, 128, 0.08); border-left-color: #8fa6c4; } | |
| .trace-explorer .msg.active-assistant { background: rgba(61, 90, 128, 0.08); border-left-color: #3d5a80; } | |
| .trace-explorer .msg.active-tool { background: rgba(196, 154, 58, 0.08); border-left-color: #c49a3a; } | |
| .trace-explorer .msg .role { | |
| font-weight: 700; font-size: 10px; text-transform: uppercase; letter-spacing: 0.6px; | |
| margin-bottom: 2px; | |
| } | |
| .trace-explorer .msg .role.system { color: #6b8f71; } | |
| .trace-explorer .msg .role.user { color: #8fa6c4; } | |
| .trace-explorer .msg .role.assistant { color: #3d5a80; } | |
| .trace-explorer .msg .role.tool { color: #c49a3a; } | |
| .trace-explorer .msg .content { | |
| font-size: 11px; opacity: 0.55; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; | |
| } | |
| .trace-explorer .symbol-flow { | |
| display: flex; flex-wrap: wrap; gap: 4px; align-items: center; | |
| } | |
| .trace-explorer .symbol { | |
| padding: 4px 10px; border-radius: 6px; font-size: 10px; font-weight: 600; | |
| font-family: var(--font-mono, monospace); background: rgba(150,150,150,0.06); | |
| border: 1px solid var(--border-color); opacity: 0.2; transition: all .25s ease; | |
| transform: scale(0.92); | |
| } | |
| .trace-explorer .symbol.revealed { opacity: 1; transform: scale(1); } | |
| .trace-explorer .symbol.current { | |
| border-color: var(--text-color); | |
| box-shadow: 0 0 0 2px rgba(61, 90, 128, 0.15); | |
| } | |
| .trace-explorer .symbol.sym-system { border-color: #6b8f71; } | |
| .trace-explorer .symbol.sym-user { border-color: #8fa6c4; } | |
| .trace-explorer .symbol.sym-assistant { border-color: #3d5a80; } | |
| .trace-explorer .symbol.sym-tool { border-color: #c49a3a; } | |
| .trace-explorer .symbol.revealed.sym-system { background: rgba(107, 143, 113, 0.1); } | |
| .trace-explorer .symbol.revealed.sym-user { background: rgba(61, 90, 128, 0.1); } | |
| .trace-explorer .symbol.revealed.sym-assistant { background: rgba(61, 90, 128, 0.1); } | |
| .trace-explorer .symbol.revealed.sym-tool { background: rgba(196, 154, 58, 0.1); } | |
| .trace-explorer .arrow { | |
| color: var(--text-color); opacity: 0.15; font-size: 12px; | |
| transition: opacity .25s ease; | |
| } | |
| .trace-explorer .arrow.revealed { opacity: 0.4; } | |
| .trace-explorer .rule-callout { | |
| margin-top: 12px; padding: 10px 14px; border-radius: 8px; | |
| background: var(--surface-bg); border: 1px solid var(--border-color); | |
| font-size: 11px; line-height: 1.5; color: var(--text-color); | |
| backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); | |
| transition: opacity .2s ease; | |
| } | |
| .trace-explorer .rule-callout .rule-icon { | |
| font-weight: 700; margin-right: 6px; opacity: 0.6; | |
| } | |
| </style> | |
| <script> | |
| (() => { | |
| const container = document.querySelector('.trace-explorer:not([data-mounted])'); | |
| if (!container) return; | |
| container.dataset.mounted = 'true'; | |
| const trace = [ | |
| { role: 'system', content: 'You are a coding assistant. Use bash and str_replace_editor tools...', activity: 'system:text', rule: 'Default: role:content_type' }, | |
| { role: 'user', content: 'Fix the failing test in tests/test_parser.py. The error is IndexError on line 42.', activity: 'user:text', rule: 'Default: role:content_type' }, | |
| { role: 'assistant', content: 'tool_call: bash({"command": "cat tests/test_parser.py"})', activity: 'assistant:tool:bash', rule: 'Tool call: function name = bash' }, | |
| { role: 'tool', content: '# test_parser.py\ndef test_parse_input():\n result = parse("hello world")\n assert result[5] == ...', activity: 'tool:text', rule: 'Default: role:content_type' }, | |
| { role: 'assistant', content: 'tool_call: str_replace_editor({"file": "parser.py", "old": "result[5]", "new": "result[min(5,len(result)-1)]"})', activity: 'assistant:tool:str_replace_editor', rule: 'Tool call: function name = str_replace_editor' }, | |
| { role: 'tool', content: 'File edited successfully.', activity: 'tool:text', rule: 'Default: role:content_type' }, | |
| { role: 'assistant', content: 'tool_call: bash({"command": "python -m pytest tests/test_parser.py -v"})', activity: 'assistant:tool:bash', rule: 'Tool call: function name = bash' }, | |
| { role: 'tool', content: 'tests/test_parser.py::test_parse_input PASSED\n1 passed in 0.02s', activity: 'tool:text', rule: 'Default: role:content_type' }, | |
| { role: 'assistant', content: 'tool_call: submit()', activity: 'assistant:tool:submit', rule: 'Tool call: function name = submit' }, | |
| { role: 'tool', content: 'Submission accepted.', activity: 'tool:text', rule: 'Default: role:content_type' }, | |
| ]; | |
| let step = -1; | |
| // Top bar with segmented control | |
| const topBar = document.createElement('div'); | |
| topBar.className = 'top-bar'; | |
| const seg = document.createElement('div'); | |
| seg.className = 'seg-control'; | |
| const prevBtn = document.createElement('button'); | |
| prevBtn.textContent = '\u2190 Prev'; | |
| const nextBtn = document.createElement('button'); | |
| nextBtn.textContent = 'Next \u2192'; | |
| nextBtn.className = 'active'; | |
| const playBtn = document.createElement('button'); | |
| playBtn.textContent = '\u25B6 Play'; | |
| const resetBtn = document.createElement('button'); | |
| resetBtn.textContent = 'Reset'; | |
| seg.append(prevBtn, nextBtn, playBtn, resetBtn); | |
| const stepInfo = document.createElement('span'); | |
| stepInfo.className = 'step-info'; | |
| topBar.append(seg, stepInfo); | |
| container.appendChild(topBar); | |
| // Progress bar | |
| const progressTrack = document.createElement('div'); | |
| progressTrack.className = 'progress-track'; | |
| const progressFill = document.createElement('div'); | |
| progressFill.className = 'progress-fill'; | |
| progressFill.style.width = '0%'; | |
| progressTrack.appendChild(progressFill); | |
| container.appendChild(progressTrack); | |
| // Panels | |
| const panels = document.createElement('div'); | |
| panels.className = 'panels'; | |
| // Left: Raw trace | |
| const leftPanel = document.createElement('div'); | |
| leftPanel.className = 'panel'; | |
| leftPanel.innerHTML = '<div class="panel-header">Raw Trace Messages</div>'; | |
| const leftBody = document.createElement('div'); | |
| leftBody.className = 'panel-body'; | |
| trace.forEach((msg, i) => { | |
| const div = document.createElement('div'); | |
| div.className = 'msg'; | |
| div.dataset.idx = i; | |
| div.innerHTML = '<div class="role ' + msg.role + '">' + msg.role + '</div><div class="content">' + msg.content.replace(/</g,'<').slice(0, 80) + (msg.content.length > 80 ? '...' : '') + '</div>'; | |
| leftBody.appendChild(div); | |
| }); | |
| leftPanel.appendChild(leftBody); | |
| // Right: Symbol sequence + rule | |
| const rightPanel = document.createElement('div'); | |
| rightPanel.className = 'panel'; | |
| rightPanel.innerHTML = '<div class="panel-header">Activity Sequence</div>'; | |
| const rightBody = document.createElement('div'); | |
| rightBody.className = 'panel-body'; | |
| const flow = document.createElement('div'); | |
| flow.className = 'symbol-flow'; | |
| trace.forEach((msg, i) => { | |
| if (i > 0) { | |
| const arrow = document.createElement('span'); | |
| arrow.className = 'arrow'; | |
| arrow.textContent = '\u2192'; | |
| arrow.dataset.idx = i; | |
| flow.appendChild(arrow); | |
| } | |
| const sym = document.createElement('span'); | |
| sym.className = 'symbol sym-' + msg.role; | |
| sym.textContent = msg.activity; | |
| sym.dataset.idx = i; | |
| flow.appendChild(sym); | |
| }); | |
| rightBody.appendChild(flow); | |
| const ruleBox = document.createElement('div'); | |
| ruleBox.className = 'rule-callout'; | |
| ruleBox.innerHTML = '<span class="rule-icon">f(x)</span> Click "Next" to step through the trace'; | |
| rightBody.appendChild(ruleBox); | |
| rightPanel.appendChild(rightBody); | |
| panels.append(leftPanel, rightPanel); | |
| container.appendChild(panels); | |
| function update() { | |
| const pct = step < 0 ? 0 : Math.max(0, ((step + 1) / trace.length) * 100); | |
| progressFill.style.width = pct + '%'; | |
| stepInfo.textContent = step < 0 ? 'Ready' : 'Step ' + (step + 1) + ' / ' + trace.length; | |
| // Update active button highlights | |
| prevBtn.classList.toggle('active', false); | |
| nextBtn.classList.toggle('active', step < trace.length - 1); | |
| // Highlight messages with role-specific accent | |
| leftBody.querySelectorAll('.msg').forEach(m => { | |
| const idx = parseInt(m.dataset.idx); | |
| m.className = 'msg'; | |
| if (idx === step) { | |
| m.classList.add('active-' + trace[idx].role); | |
| } | |
| }); | |
| // Reveal symbols | |
| flow.querySelectorAll('.symbol').forEach(s => { | |
| const idx = parseInt(s.dataset.idx); | |
| s.classList.toggle('revealed', idx <= step); | |
| s.classList.toggle('current', idx === step); | |
| }); | |
| flow.querySelectorAll('.arrow').forEach(a => { | |
| const idx = parseInt(a.dataset.idx); | |
| a.classList.toggle('revealed', idx <= step); | |
| }); | |
| // Rule callout | |
| if (step >= 0 && step < trace.length) { | |
| ruleBox.innerHTML = '<span class="rule-icon">f(x)</span> ' + trace[step].rule; | |
| ruleBox.style.opacity = '1'; | |
| const activeMsg = leftBody.querySelector('.msg[class*="active-"]'); | |
| if (activeMsg) { | |
| const msgTop = activeMsg.offsetTop - leftBody.offsetTop; | |
| const msgBot = msgTop + activeMsg.offsetHeight; | |
| const viewTop = leftBody.scrollTop; | |
| const viewBot = viewTop + leftBody.clientHeight; | |
| if (msgTop < viewTop || msgBot > viewBot) { | |
| leftBody.scrollTo({ top: msgTop - leftBody.clientHeight / 2 + activeMsg.offsetHeight / 2, behavior: 'smooth' }); | |
| } | |
| } | |
| } else if (step >= trace.length - 1 && step >= 0) { | |
| ruleBox.innerHTML = '<span class="rule-icon">\u2713</span> Extraction complete: 10 messages \u2192 10 symbols from alphabet of 5'; | |
| ruleBox.style.opacity = '1'; | |
| } else { | |
| ruleBox.innerHTML = '<span class="rule-icon">f(x)</span> Click "Next" to step through the trace'; | |
| ruleBox.style.opacity = '0.6'; | |
| } | |
| } | |
| prevBtn.addEventListener('click', () => { if (step > -1) { step--; update(); } }); | |
| nextBtn.addEventListener('click', () => { if (step < trace.length - 1) { step++; update(); } }); | |
| resetBtn.addEventListener('click', () => { step = -1; update(); clearInterval(playTimer); playBtn.textContent = '\u25B6 Play'; playBtn.classList.remove('active'); }); | |
| let playTimer = null; | |
| playBtn.addEventListener('click', () => { | |
| if (playTimer) { clearInterval(playTimer); playTimer = null; playBtn.textContent = '\u25B6 Play'; playBtn.classList.remove('active'); return; } | |
| playBtn.textContent = '\u23F8 Pause'; | |
| playBtn.classList.add('active'); | |
| playTimer = setInterval(() => { | |
| if (step < trace.length - 1) { step++; update(); } | |
| else { clearInterval(playTimer); playTimer = null; playBtn.textContent = '\u25B6 Play'; playBtn.classList.remove('active'); } | |
| }, 1000); | |
| }); | |
| update(); | |
| })(); | |
| </script> | |