AI Browser Chat: A Streaming Chat That Saves the Wrong Messages
ai-browser-chat is a public repository of mine under github.com/yethikrishna. It is a Next.js 14 app with a chat page and a few API routes. I read app/chat/page.tsx, app/api/blink/chat/route.ts, app/api/conversations/route.ts and the stack auth helper. I did not run it.
What it does
The chat route is an edge-runtime GET handler. It checks the Stack Auth session, returns 401 when there is no user id, then streams a gpt-4o-mini answer capped at 1500 tokens as server-sent events, ending with a [DONE] marker. The page opens an EventSource, appends each chunk to the last assistant message, and on [DONE] posts the conversation to /api/conversations, which inserts a row into a Supabase table with the user id.
Putting the auth check first and the user id into the request metadata is the right order. The route never calls the model for an anonymous visitor.
Where it goes wrong
The save step reads messages inside the onmessage handler. That handler is created in the same call as send, so it closes over the messages array from the render before the new question and the empty assistant message were added. When [DONE] arrives, the body it posts is the conversation as it stood before this turn. By my reading, the latest question and answer are never in the saved row, and each turn inserts a new row instead of updating one. I have not run it against a database to confirm, so treat this as a reading of the code.
The prompt travels as a query string because EventSource only does GET. That caps how long a message can be and puts user text in URLs. Separately, the page keeps its own iframe browser pane pointed at example.com, and a SideBrowser component with the same markup sits unused in components.
What I would change
My opinion: send the prompt with fetch and read the response body as a stream, which allows POST. Build the final message list from a ref, or save on the server after the stream ends, so the stored conversation is the one the user saw. Delete the unused SideBrowser or use it. None of these are large changes.