website-client @sushrutalgs.ai

See the full portfolio →

Built with

  • Next.js 16
  • React 19
  • TypeScript
  • Tailwind CSS v4
  • Supabase
  • Cloudflare R2
  • Vercel

Links

  • live product↗
  • Request repo access→

Source is private; sushrutalgs.ai is a live product. Happy to walk through the code or grant read access on request.

The web application for sushrutalgs.ai, an AI study assistant for advanced surgical exam prep that answers questions with citations traced back to standard textbooks, with a streaming chat interface, branching conversations, inline figures and tables, and the marketing, sign-up and onboarding flows around it.

The problem

Surgery residents revising for advanced exams cannot act on an answer they cannot check. A chat box that streams confident prose is worse than useless there: if a claim does not point back to a page, a figure or a table in a standard textbook, it is not revisable, it is just fluent. So the web client's job was never to render text. It was to keep every answer attached to its sources while the answer is still arriving, from a browser that must never hold a backend key.

Why it is hard

An answer does not arrive as a document. It arrives as an interleaved stream of eight server-sent-event frame types (thinking steps, metadata, text, citations, figure and table artifacts, error, done), any of which can stop mid-flight when the connection drops or a token expires. Buffering until the stream ends is the easy version, and it makes a long answer feel broken, so the client has to render as frames land and keep message state consistent through partial and aborted answers. Studying is also not linear: a user retries a question, or edits it to ask a sharper one, and a flat message list can only serve that by destroying the answer they were comparing against.

The design

  • A tree, not a list. Retry and edit fork sibling branches in a tree-structured message model held in a reducer store and mirrored into Postgres, so a conversation resumes anywhere and both answers survive. The cheaper option, an append-only list where regenerate overwrites the previous reply, was rejected because comparing two answers is exactly what a revising resident does.
  • Parse frames, do not buffer. A dedicated chat service decodes all eight frame types as they arrive, so thinking steps and citations can land before the prose finishes, and error and done are handled as terminal states rather than edge cases.
  • No secrets in the browser. Every inference call goes through the Cloudflare Worker gateway, which verifies the Supabase session and debits the daily quota, so the client holds no backend key and cannot be talked into spending someone else's allowance. Refresh-on-401 keeps that invisible to the user.
  • Licensed assets behind a proxy. Textbook figures (WebP) and tables (JSON) sit in two private R2 buckets served through auth-gated routes instead of public URLs. That costs a hop and rules out plain public CDN caching, but the content is licensed, and public object URLs would hand it to anyone with the link.
System architecture. Tap to enlarge.

Where it stands

It is live at sushrutalgs.ai as the primary client, carrying the streaming chat, the citation and figure rendering, and the sign-up and onboarding path into the product. Session verification is cheap in practice: the JWKS cache behind it runs above a 99.9 percent hit rate, so a signed-in request almost never pays for a key fetch. The honest cost is weight. The production build ships roughly 2 MB of client JavaScript, which is more than a text-first reading interface should need, and trimming it is the open work; the proxy in front of the asset buckets is the other standing tax, and that one I would pay again.

A study thread in the web app: a question, the streamed answer, and the textbook citations behind it.

In numbers

8
SSE frame types parsed
2
R2 buckets, auth-gated
>99.9%
JWKS cache hit rate

The 2 MB is the one number here I would not defend on principle, only on priority order: getting citations to render correctly while a stream is still arriving came first, and trimming the bundle is next, not skipped.