ATProto-based AI Assistant Chat interface.

Commits on admin-config-export

hadsie.com
committed
pouriade.com
committed
pouriade.com
committed
pouriade.com
committed
pouriade.com
committed
hadsie.com
committed
hadsie.com
committed
hadsie.com
committed
pouriade.com
Fixes from the review of the performance work. A reply's stream batch is sent on the next frame or after 100ms, whichever comes first, so a reply streamed in a hidden tab, where frames don't run, still reaches its views and their step clocks as it goes, and a cancelled stream closes its connection and sends nothing more, where a late event could write to the cancelled stream and throw. A code block is colored from its first frame only when its finished code was highlighted before, as when a reply's record replaces its stream, since Shiki answers through a promise even once its grammar has loaded: highlight.ts keeps the tokens of the last 200 finished blocks for that, and the first render reads them without starting a second highlight, which it did for every new block, and the web UI spec now says so rather than claiming every block of a loaded language. The step clock keeps the times of the latest 500 steps, forgetting the oldest first. Starting or ending a regeneration rerenders only the Try again buttons, which read it from a Regenerating context, rather than every message. Both blob routes set their headers through one setBlobHeaders, which adds Vary: Cookie, so another account signed in on the same browser asks the server again rather than reading a blob from the cache. A row's sibling fields come from Siblings, the hook behind a step's body is named body, the chat list asks router.matchRoute whether a deleted chat is open rather than casting the router's params, sign-out passes the mutation's own function, and ConversationView takes pick and send out of chat with the others. The web UI spec says images load as they near the screen, and the attachments spec names Vary: Cookie. Tests cover the hidden tab, the cancelled stream, and the step clock's limit.
committed
pouriade.com
committed
pouriade.com
committed
pouriade.com
A reply streams at an even pace, a word at a time, or a character at a time for Chinese and Japanese, where before it arrived in whatever bursts the provider sent. The turn's streamText uses the AI SDK's smoothStream with a chunking pattern of our own, since the SDK's word chunking waits for a space and would hold back Chinese and Japanese until a line ended. It waits 10 ms after each piece of text and reasoning alike, which caps a reply at about 100 words a second, so a faster model's record is written once smoothing catches up, within the turn's timeout. OpenAI-compatible models now show reasoning a server leaves in the reply inside <think> tags, such as llama.cpp with --reasoning-format none, as reasoning rather than as part of the answer, through the AI SDK's extractReasoningMiddleware, so that plugin now depends on the ai package. Its new startWithReasoning option is for models such as DeepSeek R1 whose template opens the tag in the prompt, so a reply has only the closing tag. The titles plugin refuses output that stopped at its output limit, such as reasoning that never reached its closing tag. The chat turns, providers, and titles specs describe it, along with the reasoning middleware dropping a reply's last characters when they could start a tag, which is a bug in the AI SDK, and docs/plugins.md says when a provider needs the ai package. Tests cover the smoothing, reasoning in think tags with and without the opening tag, whether streamed or not, and the cut-off title.
committed
pouriade.com
committed
pouriade.com
useChat, in features/conversation/chat.ts, is the open conversation as a chat, in the shape of the AI SDK's useChat without a message list of its own: the branch on screen and the messages behind it from the browser store, whether they are on their way or known empty, the reply running at the end of the branch, and send, regenerate, and stop, on top of useConversation, the reply follower, and useBranch. Every turn, from a send, an edit, or a regeneration, follows its reply, puts its new message on the branch, replacing m, and calls the view's onTurnStarted, with which ConversationView scrolls to the bottom, so regenerating now scrolls there too, as sending and editing did. useStartChat sends a new chat's first message, creating the chat and reusing it after a failed send. The composer only builds the message and hands it to send, so it no longer knows the chat's key, creates chats, or posts, and BranchComposer and NewConversation pass send through. useBranch absorbs useScrollToFocus, scrolling to a message the URL focuses while a picked one keeps the scroll, so the shared page now scrolls to a linked message too. useConversationActions keeps renaming, the instructions, and syncing, and the conversation's one alert shows whichever action or turn ran last. Its tests cover sending, regenerating, stopping, the running reply, starting a chat, and scrolling to a focus, and the composer's tests check the message it hands to send. The web UI spec describes the chat.
committed