A developer-portal prototype built for the Sarvam AI frontend assignment — an inference playground with token-by-token streaming, text and voice input, and live metrics, plus a side-by-side diff view that compares two model outputs token-by-token.Built solo with no streaming SDK and no diff library — both the streaming engine and the LCS diff are hand-written. Styled 1:1 with Sarvam's dashboard: cream-and-ink palette, Matter + Season Mix typefaces, pill buttons, gradient orbs.OverviewTwo surfaces, one design language. `/inference` is the streaming playground; `/diff` is the token-level comparison view. Both share the same primitives — Button, Card, Slider, Dropdown, SegmentedControl, Switch.Every colour, size, and radius is a TypeScript constant in `src/constants/` — never a hex value or Tailwind class in a component. I inspected the live Sarvam dashboard DOM to lock exact spacing, weights, and sizes, then exposed them as tokens so every screen stays consistent.
Inference PlaygroundPart A — streaming, multi-modal, observable.Token-by-token streamingFetch + `ReadableStream` reads SSE-style frames from a mock backend that speaks the same wire format as real LLM APIs — swapping in OpenAI / Anthropic / Sarvam is a one-line URL change.
Text & voice inputWeb Speech API transcription with a live mic-level waveform driven by time-domain audio sampling.
Live metricsReal-time token count and tokens/sec from `performance.now()` deltas, isolated in their own hook so the stream doesn't re-render every tick.
Graceful failureOutput is append-only — a mid-stream error never wipes partial text. An Error demo toggle injects a failure at 30% to exercise the path live.
Streaming engine`useStream` owns the `fetch` + `getReader()` loop and the `output / status / error` state, wrapped in an `AbortController` so Stop or unmount cancel cleanly. `useStreamMetrics` tracks throughput separately; `useAudioRecording` + `useMicLevels` handle voice.Three failure paths — network drop, mid-stream interruption, and manual abort — all funnel through one `try/catch`. Aborts are treated as `done`, not `error`: stopping is not failing. The error banner renders inline at the tail of the partial response with a Try again button — never a blank screen.
Diff viewPart B compares two outputs token-by-token with a hand-rolled LCS diff. Tokenization keeps whitespace as its own token so the renderer reproduces the original verbatim and highlight chips stay word-shaped.The algorithm trims shared prefix/suffix first, then builds the LCS table on `Uint32Array` rows (~4× smaller than `number[][]`) and backtracks to emit `equal / insert / delete` ops. O(n·m), under 1ms for ~500 tokens. A "changes only" view collapses unchanged runs into fold pills.
Why LCSDiff algorithm candidates, side by side.Correct for prose
No
Yes
No
Yes
Time complexity
O(n)
O((n+m)·D)
O(n·m)
O(n·m)
Hand-writable
Trivial
Error-prone
Moderate
~120 lines
AccessibilityTargeted WCAG AA, built keyboard-first. `⌘/Ctrl+Enter` submits, `Esc` stops, arrow keys drive the sliders, and every interactive element has a focus ring.Streamed output is a `role="log"` + `aria-live="polite"` region; errors are `role="alert"`; diff chips are semantic `<mark>` with `aria-label`. Colour is never the sole cue — deletions are red and struck-through, insertions green and restyled.
TechstackVite
React 19
TypeScript
Tailwind 4
React Router
Web Speech API
BuildBuilt for — Sarvam AI frontend intern assignmentScope — two tasks, built soloConstraints — no streaming SDK, no diff library, hand-written from scratchResponsive — fully responsive, mobile + desktop