Skip to content

Commit 3396594

Browse files
committed
feat(viewer): add parseModelOutput for SoM parsing and truncation
1 parent 2dd29fb commit 3396594

File tree

2 files changed

+66
-0
lines changed

2 files changed

+66
-0
lines changed

CLAUDE.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,22 @@ az ml workspace sync-keys -n openadapt-ml -g openadapt-agents
381381
- Full model output (not truncated)
382382
- Filter/search evaluations by epoch or correctness
383383

384+
### Viewer Code Consolidation
385+
**Status**: TODO - HIGH PRIORITY
386+
387+
**Problem**: Viewer code is fragmented across multiple locations:
388+
1. `generate_training_dashboard()` - generates unified viewer template
389+
2. `_enhance_comparison_to_unified_viewer()` - injects checkpoint_script into comparison.html
390+
3. `comparison.html` from capture - has its own display logic
391+
392+
This causes issues where changes don't propagate correctly.
393+
394+
**Solution**: Create a single `generate_unified_viewer()` function that:
395+
- Takes comparison data as input
396+
- Generates a complete standalone viewer.html
397+
- Has all display logic in one place
398+
- Doesn't rely on injecting scripts into existing HTML
399+
384400
### README API Documentation
385401
**Status**: VERIFIED
386402

openadapt_ml/training/trainer.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2747,6 +2747,44 @@ def _enhance_comparison_to_unified_viewer(
27472747
window.comparisonData = comparisonData;
27482748
}}
27492749
2750+
// Parse model output for SoM actions
2751+
window.parseModelOutput = function(rawOutput) {{
2752+
if (!rawOutput) return {{ html: '<em style="color:var(--text-muted);">No prediction</em>' }};
2753+
2754+
// Try to extract SoM actions: CLICK([N]), TYPE([N], "text"), TYPE("text")
2755+
const clickSomMatch = rawOutput.match(/CLICK\\s*\\(\\s*\\[\\s*(\\d+)\\s*\\]\\s*\\)/);
2756+
const typeSomMatch = rawOutput.match(/TYPE\\s*\\(\\s*\\[\\s*(\\d+)\\s*\\]\\s*,\\s*["']([^"']*)["']\\s*\\)/);
2757+
const typeSimpleMatch = rawOutput.match(/TYPE\\s*\\(\\s*["']([^"']*)["']\\s*\\)/);
2758+
const clickCoordMatch = rawOutput.match(/CLICK\\s*\\(\\s*x\\s*=\\s*([\\d.]+)\\s*,\\s*y\\s*=\\s*([\\d.]+)\\s*\\)/);
2759+
2760+
let html = '';
2761+
2762+
if (clickSomMatch) {{
2763+
html = `<div style="font-weight:600;color:#00d4aa;">CLICK([${{clickSomMatch[1]}}])</div>`;
2764+
}} else if (typeSomMatch) {{
2765+
html = `<div style="font-weight:600;color:#00d4aa;">TYPE([${{typeSomMatch[1]}}], "${{typeSomMatch[2]}}")</div>`;
2766+
}} else if (typeSimpleMatch) {{
2767+
html = `<div style="font-weight:600;color:#00d4aa;">TYPE("${{typeSimpleMatch[1]}}")</div>`;
2768+
}} else if (clickCoordMatch) {{
2769+
html = `<div style="font-weight:600;color:#00d4aa;">CLICK(x=${{clickCoordMatch[1]}}, y=${{clickCoordMatch[2]}})</div>`;
2770+
}} else {{
2771+
// No structured action - show truncated output
2772+
const truncated = rawOutput.replace(/\\n/g, ' ').substring(0, 150);
2773+
html = `<div style="font-size:0.85rem;color:var(--text-muted);max-height:60px;overflow:hidden;">${{truncated}}${{rawOutput.length > 150 ? '...' : ''}}</div>`;
2774+
}}
2775+
2776+
return {{ html }};
2777+
}};
2778+
2779+
// Override prediction display in comparison viewer
2780+
window.formatPrediction = function(pred) {{
2781+
if (!pred) return '<em style="color:var(--text-muted);">No prediction</em>';
2782+
if (pred.x !== undefined) {{
2783+
return `<div>Type: ${{pred.type || 'click'}}</div><div>Position: (${{(pred.x * 100).toFixed(1)}}%, ${{(pred.y * 100).toFixed(1)}}%)</div>`;
2784+
}}
2785+
return window.parseModelOutput(pred.raw_output || JSON.stringify(pred)).html;
2786+
}};
2787+
27502788
// Use window. prefix for cross-script variable access
27512789
window.predictionsByCheckpoint = {predictions_json};
27522790
window.availableCaptures = {captures_json};
@@ -2813,6 +2851,18 @@ def _enhance_comparison_to_unified_viewer(
28132851
updateComparison(idx);
28142852
}}
28152853
2854+
// Reformat prediction display after original updateComparison runs
2855+
setTimeout(() => {{
2856+
const predEl = document.getElementById('predicted-action') ||
2857+
document.querySelector('.action-box.predicted .action-details');
2858+
if (predEl && window.comparisonData && window.comparisonData[idx]) {{
2859+
const pred = window.comparisonData[idx].predicted_action;
2860+
if (pred) {{
2861+
predEl.innerHTML = window.formatPrediction(pred);
2862+
}}
2863+
}}
2864+
}}, 50);
2865+
28162866
// Update metrics if setupMetricsSummary exists
28172867
if (typeof window.setupMetricsSummary === 'function') {{
28182868
window.setupMetricsSummary();

0 commit comments

Comments
 (0)