|
| 1 | +# MVM — Frontend Plan |
| 2 | + |
| 3 | +UI for comparing Claude model outputs side-by-side with real-time streaming. Talks to MVM backend at `localhost:8080`. |
| 4 | + |
| 5 | +Backend design doc: `backies/docs/mvm/` |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## Code structure |
| 10 | + |
| 11 | +``` |
| 12 | +src/pages/mvm/index.tsx # Page wrapper |
| 13 | +src/components/mvm/ |
| 14 | + mvm-view.tsx # Main container |
| 15 | + connection-banner.tsx # Warning when local API unreachable |
| 16 | + prompt-form.tsx # Model toggles + prompt textarea + submit |
| 17 | + results-grid.tsx # Responsive grid of result cards |
| 18 | + result-card.tsx # Single model result display (streaming text) |
| 19 | +src/hooks/useMvm.ts # SSE stream consumer, loading, connection health |
| 20 | +src/types/mvm.ts # TypeScript types mirroring API/SSE events |
| 21 | +``` |
| 22 | + |
| 23 | +## Modified files |
| 24 | + |
| 25 | +`src/constants/api.ts` — add `MVM_API_BASE = "http://localhost:8080"`. |
| 26 | + |
| 27 | +--- |
| 28 | + |
| 29 | +## UI flow |
| 30 | + |
| 31 | +1. On mount: hook pings `GET /api/mvm/health` (3s timeout). Fetches model list from `GET /api/mvm/models`. |
| 32 | +2. If unreachable: amber banner — "Local API not running. Start with `make run` in backies/". Submit disabled. |
| 33 | +3. Select models (toggle buttons rendered dynamically from `/models` response) |
| 34 | +4. Enter prompt in textarea. Optional "Advanced" disclosure for system prompt. |
| 35 | +5. Click "Compare" — cards appear immediately per model (from `model_start` events) |
| 36 | +6. Text streams into each card in real-time as `token` events arrive |
| 37 | +7. When `model_done` arrives, card footer fills in with tokens + cost + duration |
| 38 | +8. If `model_error` arrives, card shows error state |
| 39 | +9. Stream ends on `done` event — loading state clears |
| 40 | + |
| 41 | +--- |
| 42 | + |
| 43 | +## SSE consumption |
| 44 | + |
| 45 | +`EventSource` doesn't support POST. Use `fetch` + `ReadableStream`: |
| 46 | + |
| 47 | +```typescript |
| 48 | +const res = await fetch(`${MVM_API_BASE}/api/mvm/compare`, { |
| 49 | + method: 'POST', |
| 50 | + headers: { 'Content-Type': 'application/json' }, |
| 51 | + body: JSON.stringify({ prompt, models, system_prompt }), |
| 52 | +}); |
| 53 | + |
| 54 | +const reader = res.body.getReader(); |
| 55 | +const decoder = new TextDecoder(); |
| 56 | + |
| 57 | +while (true) { |
| 58 | + const { done, value } = await reader.read(); |
| 59 | + if (done) break; |
| 60 | + const chunk = decoder.decode(value, { stream: true }); |
| 61 | + // parse SSE events (split by \n\n, extract event: and data: lines) |
| 62 | + // dispatch by event type: model_start, token, model_done, model_error, done |
| 63 | +} |
| 64 | +``` |
| 65 | + |
| 66 | +State shape per model: |
| 67 | + |
| 68 | +```typescript |
| 69 | +{ |
| 70 | + [model: string]: { |
| 71 | + text: string; // accumulated from token events |
| 72 | + status: 'streaming' | 'done' | 'error'; |
| 73 | + usage?: ArenaUsage; // filled on model_done |
| 74 | + duration_ms?: number; |
| 75 | + total_cost_usd?: number; |
| 76 | + error?: string; // filled on model_error |
| 77 | + } |
| 78 | +} |
| 79 | +``` |
| 80 | + |
| 81 | +--- |
| 82 | + |
| 83 | +## Reusable components |
| 84 | + |
| 85 | +- `<Page>` wrapper from `src/components/ui/page.tsx` |
| 86 | +- `Button` from `src/components/ui/button.tsx` (outline variant for model toggles) |
| 87 | +- `Card`/`CardHeader`/`CardContent`/`CardFooter` from `src/components/ui/card.tsx` |
| 88 | +- `useMateria` hook as fetch pattern reference (for `/models` and `/health` calls) |
| 89 | + |
| 90 | +--- |
| 91 | + |
| 92 | +## Constraints |
| 93 | + |
| 94 | +- **Always visible** on prod site. Shows connection banner if local API unreachable. |
| 95 | +- **Mixed content**: prod (`https://chanjunren.github.io`) calling `http://localhost:8080`. Chrome/Firefox allow localhost as secure context exception. Safari may block — recommend running Docusaurus dev server locally. |
| 96 | +- No new dependencies beyond what's already in the project. |
| 97 | + |
| 98 | +--- |
| 99 | + |
| 100 | +## Future extensions |
| 101 | + |
| 102 | +- Diff view between outputs |
| 103 | +- Markdown rendering in result cards |
| 104 | +- History of past comparisons |
0 commit comments