Frontend modernization for AI Products teams
Streaming LLM UIs, agent surfaces, and cost-aware token UX that survive a model swap without the on-call rota lighting up at 2am.
Domain context
AI product frontends fail in ways generic dashboards never do. A streaming response that stalls halfway through a token sequence reads as an outage even though the server is fine. A "stop generating" button that doesn't actually cancel the upstream call is a cost incident nobody notices until the monthly bill arrives. Copy-to-clipboard that grabs half-rendered markdown becomes a support ticket. A skeleton state that times out before time-to-first-token on a slow model is a churn moment. We rebuild these surfaces assuming the model is slow, occasionally wrong, sometimes refused, and getting swapped for a newer one in six weeks. The UI has to absorb all of that without the user feeling any of it.
Why this combination
Most modernization shops treat the LLM as a black-box API and ship a chat box. AI-product modernization is the work that happens around the chat box. It's the streaming reconciliation that handles a partial response cleanly when the socket drops. It's the abort semantics that actually cancel the upstream inference instead of just hiding the UI. It's the token-budget hint that shows the user what a long prompt will cost before they hit send, the citation panel that ties every claim back to a retrieved source, and the eval hooks that let the product team A/B a prompt change against a golden dataset before it ever touches production traffic. None of that is framework code. It's the discipline that decides whether the surface still works when the underlying model changes next month.
“We rebuilt a chat surface for an AI-native B2B platform with token-by-token streaming, working abort semantics, and a citation panel that grounded every assertion. Perceived latency dropped 62% on the same model.”
Frequently asked
- Because perceived speed is the product. Time-to-first-token is what users actually feel. Time-to-completion is what the model controls. A surface that streams cleanly feels twice as fast as one that waits for the full response, even on the exact same model. Getting streaming right means handling backpressure, partial markdown, mid-stream errors, and abort semantics. None of which a generic chat-component library does well by default.
- We wire the abort path end-to-end. The UI's stop button cancels the in-flight fetch, the server-side handler propagates the abort to the inference provider's cancel endpoint, and the request gets logged as cancelled with the partial token count for cost accounting. Half the implementations we audit cancel the UI but keep paying for the full completion. That's the gap we close.
- Evals are a frontend concern, not just a model-team concern. The frontend owns the prompt-response contract, which is what the surface promises the user it will do, and a regression in that contract is a frontend regression. We instrument every model call from the client with input, output, model version, and a structured score, then wire eval datasets into the dev loop so a prompt change can be replayed against a golden set before it ships.
- Treat the model as a versioned dependency, not a constant. The frontend should never know which exact model it's talking to. It should know the contract: input shape, output shape, latency budget, refusal modes. When the model swaps, the contract test catches the regression before the user does. We build the eval harness, the prompt-version pinning, and the fallback surface together, so a model upgrade is a controlled rollout rather than a blast radius.
- Yes, and the patterns are domain-specific. Image uploads need client-side resizing and a preview that shows what the model will actually see. Audio needs a recorder with chunked upload and a transcription progress UI. Document parsing needs a fallback when the PDF is image-only and OCR has to run server-side. The general principle: the surface should be honest about what the model can process, what it's processing right now, and what failed silently.