A chat app I wrote for myself. I type something, the reply streams back from the model. Several conversations can be open at once and I switch between them in the sidebar; each one has its own model and its own optional instructions.
Built with Next.js and the Vercel AI SDK. Messages go to a route handler at
/api/chat, which calls the model and streams the answer back.
The conversations live in memory and nowhere else. Reload the page and they are gone — there is no database, no file, nothing in the browser's storage. That is the next thing that needs solving.
npm install
cp .env.example .env.local # fill in the model provider key
npm run devThen open http://localhost:3000.
app/page.tsx— the sidebar and whichever conversation is on screenapp/api/chat/route.ts— the one route that talks to the modelchat/index.tsx— the provider and the functions components callchat/store.ts— the conversations, as an external store React can subscribe tochat/conversations.ts— every change to the list, as pure functionschat/session.ts— the only file that builds the AI SDK's client objectschat/schema.ts— the two shapes (Conversation,Session)chat/title.ts— names a conversation after the first thing said in itcomponents/— the sidebar, the thread, its title and model row, a message, the composerlib/models.ts— the models the picker offerslib/provider.ts— the provider, built lazily from the env var
- Everything that changes the list of conversations goes through
chat/store.ts. Components call the functions offuseConversations()and never reach into the snapshot themselves. - Nothing is written down. Reload the page, close the tab, or hit the × next to a conversation, and those messages are gone — there is no second copy of them anywhere. That is fine for the one-off questions this mostly gets used for, and not fine the rest of the time; I have already lost a long one to a stray refresh.
- Same problem in a smaller way: the tab is the session. A second window is a second empty app, and none of it is on my phone.
- Whatever I replace this with has to hold the message parts the SDK produces and not just strings, or it breaks the first time a reply is anything other than plain text.
- No accounts — it assumes one person on one machine.
- The model and the instructions are read at the moment a message is sent, so changing either applies from the next message on and the earlier ones stay as they were. That is what I want, but it does mean a conversation can be half one model and half another, and the label above every reply is whichever model happens to be picked now rather than the one that wrote it. Nothing records that, because nothing records anything.
- The composer is a textarea that sends on Enter. There is no editing a message after it has gone, and no way to fork a conversation from halfway up.