@sushindustries/assistant

0.1.0

A chat panel that streams Markdown, told which machine it is running on. TanStack AI on the wire, Groq behind it, and its whole personality in a Markdown file.

pnpm add @sushindustries/assistant
2 min read
View as Markdown

A chat panel that streams Markdown, told which machine it is running on.

Install

pnpm add @sushindustries/assistant @sushindustries/atoms
shell

Use

import { AssistantPanel } from "@sushindustries/assistant";

<AssistantPanel
	messages={messages}
	streaming={status === "streaming"}
	onSend={(text) => sendMessage({ content: text })}
	renderMarkdown={(source) => <MarkdownView source={source} />}
/>;
tsx

It holds nothing and talks to nothing

Messages, status and "here is a new line of text" are all props. The transport is the host's - on this site that is TanStack AI's useChat over a server route talking to Groq, and the panel cannot tell.

That is what makes it installable. A panel that owned its own fetch would be a panel you could only see working by having an API key.

renderMarkdown is the same idea one level down: this package has no business picking a parser, a highlighter or a theme for its host. Leave it out and messages render as plain text, which is correct rather than broken.

The personality is a Markdown file

persona.md holds the model, the temperature, and the system message under a ## System heading. Everything above that heading is for whoever opens the file and is never sent - a note explaining why a rule exists would otherwise become another instruction, which is how a prompt starts arguing with itself.

situate(persona, "phone") appends the machine to the system message. It is a fact about the situation rather than something anybody typed, so it is not a user turn - a user turn would also show up in the transcript, and the reader did not say "I am on a phone".

The file is public

It ships in the package, and a system message is one jailbreak away from being quoted back. Nothing secret goes in it. The key is read from the environment inside the host's handler.

What it does about streaming

ThingWhy
Follows the bottom, unless you scrolled upGetting yanked away from an answer you went back to reread is the most annoying thing a chat panel does. Sixty pixels of slack.
A caret only while the reply is emptyThe gap before the first token is where a panel looks broken. Once there is text, the text is the progress.
aria-live="polite", aria-atomic="false"Assertive would interrupt a screen reader on every token; atomic would reread the whole transcript each time.
Enter sends, Shift+Enter breaksThe convention every chat box has and none of them explain.