|
2 | 2 |
|
3 | 3 | ## Overview |
4 | 4 |
|
5 | | -HTML visualization generators for training dashboards, benchmark results, and capture playback. Generates standalone HTML files that can be opened in any browser. |
| 5 | +**Reusable component library** for OpenAdapt visualization. Provides building blocks (components) and high-level builders for creating standalone HTML viewers. |
| 6 | + |
| 7 | +Used by: |
| 8 | +- **openadapt-ml**: Training dashboards |
| 9 | +- **openadapt-evals**: Benchmark result viewers |
| 10 | +- **openadapt-capture**: Capture playback viewers |
| 11 | +- **openadapt-retrieval**: Demo search result viewers |
6 | 12 |
|
7 | 13 | ## Quick Start |
8 | 14 |
|
9 | 15 | ```bash |
10 | 16 | # Install |
| 17 | +cd /Users/abrichr/oa/src/openadapt-viewer |
11 | 18 | uv sync |
12 | 19 |
|
| 20 | +# Run tests |
| 21 | +uv run pytest tests/ -v |
| 22 | + |
13 | 23 | # Generate demo benchmark viewer |
14 | 24 | uv run openadapt-viewer demo --tasks 5 --output viewer.html |
15 | 25 |
|
16 | | -# Open in browser |
17 | | -open viewer.html |
| 26 | +# Run examples |
| 27 | +uv run python -m openadapt_viewer.examples.benchmark_example |
| 28 | +uv run python -m openadapt_viewer.examples.training_example |
| 29 | +uv run python -m openadapt_viewer.examples.capture_example |
| 30 | +uv run python -m openadapt_viewer.examples.retrieval_example |
18 | 31 | ``` |
19 | 32 |
|
20 | | -## CLI Commands |
21 | | - |
22 | | -| Command | Description | |
23 | | -|---------|-------------| |
24 | | -| `demo` | Generate demo viewer with sample data | |
25 | | -| `benchmark` | Generate viewer from benchmark results | |
26 | | - |
27 | | -## Key Files |
28 | | - |
29 | | -- `viewers/benchmark/generator.py` - Main HTML generator |
30 | | -- `viewers/benchmark/data.py` - Data models (BenchmarkRun, TaskExecution) |
31 | | -- `viewers/benchmark/templates/` - Jinja2 templates |
32 | | -- `__main__.py` - CLI entry point |
33 | | - |
34 | 33 | ## Architecture |
35 | 34 |
|
36 | 35 | ``` |
37 | 36 | openadapt_viewer/ |
38 | | -├── viewers/ |
39 | | -│ └── benchmark/ |
| 37 | +├── components/ # Reusable UI building blocks |
| 38 | +│ ├── screenshot.py # Screenshot with overlays |
| 39 | +│ ├── playback.py # Play/pause/speed controls |
| 40 | +│ ├── timeline.py # Step progress bar |
| 41 | +│ ├── action_display.py # Format actions (click, type, etc.) |
| 42 | +│ ├── metrics.py # Stats cards and grids |
| 43 | +│ ├── filters.py # Filter dropdowns |
| 44 | +│ ├── list_view.py # Selectable list component |
| 45 | +│ └── badge.py # Status badges |
| 46 | +│ |
| 47 | +├── builders/ # High-level page builders |
| 48 | +│ └── page_builder.py # PageBuilder class |
| 49 | +│ |
| 50 | +├── styles/ # Shared CSS |
| 51 | +│ └── core.css # CSS variables and base styles |
| 52 | +│ |
| 53 | +├── core/ # Core utilities |
| 54 | +│ ├── types.py # Pydantic data models |
| 55 | +│ ├── data_loader.py # Data loading utilities |
| 56 | +│ └── html_builder.py # Jinja2 HTMLBuilder |
| 57 | +│ |
| 58 | +├── viewers/ # Full viewer implementations |
| 59 | +│ └── benchmark/ # Benchmark results viewer |
40 | 60 | │ ├── generator.py # generate_benchmark_html() |
41 | | -│ ├── data.py # BenchmarkRun, TaskExecution, ExecutionStep |
42 | | -│ └── templates/ |
43 | | -│ └── benchmark.html.j2 |
44 | | -├── __init__.py |
45 | | -└── __main__.py # CLI |
| 61 | +│ └── data.py # Data loading |
| 62 | +│ |
| 63 | +├── examples/ # Reference implementations |
| 64 | +│ ├── benchmark_example.py # openadapt-evals usage |
| 65 | +│ ├── training_example.py # openadapt-ml usage |
| 66 | +│ ├── capture_example.py # openadapt-capture usage |
| 67 | +│ └── retrieval_example.py # openadapt-retrieval usage |
| 68 | +│ |
| 69 | +├── templates/ # Jinja2 templates |
| 70 | +│ └── base.html # Base HTML template |
| 71 | +│ |
| 72 | +└── __init__.py # Package exports |
46 | 73 | ``` |
47 | 74 |
|
48 | | -## HTML Features |
| 75 | +## Component Usage |
49 | 76 |
|
50 | | -The generated viewer includes: |
51 | | -- Summary statistics (pass/fail, success rate) |
52 | | -- Domain breakdown charts |
53 | | -- Task list with filtering |
54 | | -- Step-by-step screenshot playback |
55 | | -- Playback controls (prev/next, play/pause, speed) |
56 | | -- Keyboard shortcuts |
| 77 | +### Individual Components |
57 | 78 |
|
58 | | -## Integration |
| 79 | +Each component returns an HTML string: |
| 80 | + |
| 81 | +```python |
| 82 | +from openadapt_viewer.components import ( |
| 83 | + screenshot_display, |
| 84 | + playback_controls, |
| 85 | + metrics_grid, |
| 86 | + filter_bar, |
| 87 | + selectable_list, |
| 88 | + badge, |
| 89 | +) |
| 90 | + |
| 91 | +# Screenshot with overlays |
| 92 | +html = screenshot_display( |
| 93 | + image_path="screenshot.png", |
| 94 | + overlays=[ |
| 95 | + {"type": "click", "x": 0.5, "y": 0.3, "label": "H", "variant": "human"}, |
| 96 | + {"type": "click", "x": 0.6, "y": 0.4, "label": "AI", "variant": "predicted"}, |
| 97 | + ], |
| 98 | + caption="Step 5", |
| 99 | +) |
| 100 | + |
| 101 | +# Metrics cards |
| 102 | +html = metrics_grid([ |
| 103 | + {"label": "Total", "value": 100}, |
| 104 | + {"label": "Passed", "value": 75, "color": "success"}, |
| 105 | + {"label": "Failed", "value": 25, "color": "error"}, |
| 106 | +]) |
| 107 | + |
| 108 | +# Playback controls (requires Alpine.js) |
| 109 | +html = playback_controls(step_count=20, initial_step=0) |
| 110 | + |
| 111 | +# Filter bar |
| 112 | +html = filter_bar( |
| 113 | + filters=[ |
| 114 | + {"id": "domain", "label": "Domain", "options": ["office", "browser"]}, |
| 115 | + {"id": "status", "label": "Status", "options": ["passed", "failed"]}, |
| 116 | + ], |
| 117 | + search_placeholder="Search tasks...", |
| 118 | +) |
| 119 | +``` |
59 | 120 |
|
60 | | -Used by openadapt-ml and openadapt-evals to visualize results: |
| 121 | +### Page Builder |
| 122 | + |
| 123 | +Build complete pages from components: |
61 | 124 |
|
62 | 125 | ```python |
63 | | -from openadapt_viewer import generate_benchmark_html, BenchmarkRun |
| 126 | +from openadapt_viewer.builders import PageBuilder |
| 127 | +from openadapt_viewer.components import metrics_grid, screenshot_display |
| 128 | + |
| 129 | +builder = PageBuilder(title="My Viewer", include_alpine=True) |
| 130 | + |
| 131 | +builder.add_header( |
| 132 | + title="Results", |
| 133 | + subtitle="Model: gpt-5.1", |
| 134 | + nav_tabs=[ |
| 135 | + {"href": "dashboard.html", "label": "Training"}, |
| 136 | + {"href": "viewer.html", "label": "Viewer", "active": True}, |
| 137 | + ], |
| 138 | +) |
| 139 | + |
| 140 | +builder.add_section( |
| 141 | + metrics_grid([...]), |
| 142 | + title="Summary", |
| 143 | +) |
| 144 | + |
| 145 | +builder.add_section( |
| 146 | + screenshot_display("screenshot.png"), |
| 147 | +) |
| 148 | + |
| 149 | +# Render to string |
| 150 | +html = builder.render() |
64 | 151 |
|
65 | | -run = BenchmarkRun.from_results(results) |
66 | | -html = generate_benchmark_html(run, embed_screenshots=True) |
67 | | -Path("results.html").write_text(html) |
| 152 | +# Or write to file |
| 153 | +path = builder.render_to_file("output.html") |
68 | 154 | ``` |
69 | 155 |
|
70 | | -## Development |
| 156 | +### Ready-to-Use Viewers |
| 157 | + |
| 158 | +```python |
| 159 | +# Benchmark viewer |
| 160 | +from openadapt_viewer.viewers.benchmark import generate_benchmark_html |
| 161 | +generate_benchmark_html(data_path="results/", output_path="viewer.html") |
| 162 | + |
| 163 | +# Or with data |
| 164 | +from openadapt_viewer import BenchmarkRun, generate_benchmark_html |
| 165 | +run = BenchmarkRun(...) |
| 166 | +generate_benchmark_html(run_data=run, output_path="viewer.html") |
| 167 | +``` |
| 168 | + |
| 169 | +## CSS Classes |
| 170 | + |
| 171 | +All component classes use the `oa-` prefix: |
| 172 | + |
| 173 | +| Component | Classes | |
| 174 | +|-----------|---------| |
| 175 | +| Screenshot | `oa-screenshot-container`, `oa-overlay`, `oa-overlay-click` | |
| 176 | +| Playback | `oa-playback-controls`, `oa-playback-btn`, `oa-playback-speed` | |
| 177 | +| Timeline | `oa-timeline`, `oa-timeline-track`, `oa-timeline-progress` | |
| 178 | +| Metrics | `oa-metrics-card`, `oa-metrics-grid`, `oa-metrics-value` | |
| 179 | +| Filters | `oa-filter-bar`, `oa-filter-dropdown` | |
| 180 | +| List | `oa-list`, `oa-list-item`, `oa-list-item-selected` | |
| 181 | +| Badge | `oa-badge`, `oa-badge-success`, `oa-badge-error` | |
| 182 | + |
| 183 | +## CSS Variables |
| 184 | + |
| 185 | +Core CSS variables in `styles/core.css`: |
| 186 | + |
| 187 | +```css |
| 188 | +:root { |
| 189 | + --oa-bg-primary: #0a0a0f; |
| 190 | + --oa-bg-secondary: #12121a; |
| 191 | + --oa-text-primary: #f0f0f0; |
| 192 | + --oa-accent: #00d4aa; |
| 193 | + --oa-success: #34d399; |
| 194 | + --oa-error: #ff5f5f; |
| 195 | + /* ... */ |
| 196 | +} |
| 197 | +``` |
| 198 | + |
| 199 | +## Testing |
71 | 200 |
|
72 | 201 | ```bash |
73 | | -# Run tests (when added) |
| 202 | +# Run all tests |
74 | 203 | uv run pytest tests/ -v |
75 | 204 |
|
76 | | -# Check imports |
77 | | -uv run python -c "from openadapt_viewer import generate_benchmark_html; print('OK')" |
| 205 | +# Run component tests |
| 206 | +uv run pytest tests/test_components/ -v |
| 207 | + |
| 208 | +# Check imports work |
| 209 | +uv run python -c "from openadapt_viewer import screenshot_display, PageBuilder; print('OK')" |
| 210 | +``` |
| 211 | + |
| 212 | +## Integration Examples |
| 213 | + |
| 214 | +### openadapt-ml (Training Dashboard) |
| 215 | + |
| 216 | +```python |
| 217 | +from openadapt_viewer.builders import PageBuilder |
| 218 | +from openadapt_viewer.components import metrics_grid, screenshot_display |
| 219 | + |
| 220 | +def generate_training_dashboard(state, config): |
| 221 | + builder = PageBuilder(title="Training", include_chartjs=True) |
| 222 | + builder.add_header(title="Training Dashboard", subtitle=f"Model: {state.model_name}") |
| 223 | + builder.add_section(metrics_grid([ |
| 224 | + {"label": "Epoch", "value": state.epoch}, |
| 225 | + {"label": "Loss", "value": f"{state.loss:.4f}"}, |
| 226 | + ])) |
| 227 | + return builder.render() |
78 | 228 | ``` |
| 229 | + |
| 230 | +### openadapt-evals (Benchmark Viewer) |
| 231 | + |
| 232 | +```python |
| 233 | +from openadapt_viewer import generate_benchmark_html, BenchmarkRun |
| 234 | + |
| 235 | +run = BenchmarkRun.from_directory("results/run_001/") |
| 236 | +generate_benchmark_html(run_data=run, output_path="benchmark.html") |
| 237 | +``` |
| 238 | + |
| 239 | +### openadapt-retrieval (Search Results) |
| 240 | + |
| 241 | +```python |
| 242 | +from openadapt_viewer.builders import PageBuilder |
| 243 | +from openadapt_viewer.components import screenshot_display, selectable_list |
| 244 | + |
| 245 | +def generate_retrieval_viewer(results): |
| 246 | + builder = PageBuilder(title="Search Results") |
| 247 | + for result in results: |
| 248 | + builder.add_section(f''' |
| 249 | + {screenshot_display(result.screenshot)} |
| 250 | + <div>Similarity: {result.score:.3f}</div> |
| 251 | + ''') |
| 252 | + return builder.render() |
| 253 | +``` |
| 254 | + |
| 255 | +## Related Projects |
| 256 | + |
| 257 | +- [openadapt-ml](../openadapt-ml/) - ML engine, uses this for dashboards |
| 258 | +- [openadapt-evals](../openadapt-evals/) - Benchmark evaluation, uses this for viewers |
| 259 | +- [openadapt-capture](../openadapt-capture/) - Recording capture |
| 260 | +- [openadapt-retrieval](../openadapt-retrieval/) - Demo retrieval |
0 commit comments