Welcome
Sarvam
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.
Overview
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.
Screens
Screens 1
Screens 2
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.
Naive
Myers
Patience
LCS
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
Easy to review
Yes
No
No
Yes
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.
Techstack
Vite
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