@rohansguliani/mux-chat-ui
v0.4.6
Published
Mux's own chat as Solid pieces an app lays out itself (transcript, composer, model and reasoning pickers, a ready-made panel), built from Mux and talking to the app's @rohansguliani/mux-chat-runtime mount.
Readme
@rohansguliani/mux-chat-ui
Mux's own chat as pieces you lay out in your app: the conversation, the composer, the model and reasoning menus, and a ready-made panel. They are Mux's actual components (built from the Mux commit the runtime package pins), styled from their own stylesheet, scoped so they never restyle your page.
Your server mounts one route with
@rohansguliani/mux-chat-runtime:
app.all("/api/mux/*", (c) => mux.handle(c.req.raw, { subject: userID })).
import { createMuxChat, MuxChat, MuxTranscript, MuxComposer, MuxModelPicker } from "@rohansguliani/mux-chat-ui"
import "@rohansguliani/mux-chat-ui/style.css"
const chat = createMuxChat({
storageKey: "todos",
onToolResult: ({ readOnly }) => { if (!readOnly) refreshTodos() },
})
<MuxChat chat={chat} colorScheme="dark" tokens={{ "background-base": "#0f1114", "text-interactive-base": "#8ab4ff" }}>
<header class="my-header">Assistant <MuxModelPicker /> <button onClick={() => chat.newChat()}>New chat</button></header>
<MuxTranscript empty={<MyStarterPrompts onPick={(text) => chat.send(text)} />} />
<MuxComposer placeholder="Ask about your todos…" pickers={false} />
</MuxChat>
chat.send("Summarize my week") // e.g. from a button elsewhere in your app| Piece | |
| --- | --- |
| createMuxChat(options) | the signed-in user's chat: grant (renewed before it expires), their last chat or a new one, send, newChat, retry, focus, dispose |
| <MuxChat chat colorScheme tokens class style> | the root the pieces sit in; your own elements can sit in it too |
| <MuxTranscript empty> | the conversation, with its loading, failed, and ended states |
| <MuxComposer placeholder pickers> | the composer, or the assistant's open question or permission request |
| <MuxModelPicker>, <MuxReasoningPicker> | the menus, wherever you want them |
| <MuxChatPanel title subtitle placeholder suggestions> | a ready-made layout of all of the above |
| mountMuxChat(element, options) | the ready-made panel for apps not written in Solid |
createMuxChat options: url (where mux.handle is mounted, default
/api/mux), storageKey, sessionTitle, preferredModel (provider/model
until the user picks one), toolLabels (default: each tool's label), and
onToolResult (a tool call finished while the chat was open; readOnly:
false means it may have changed app data).
Styling. tokens sets Mux's theme values (names without --), e.g.
background-base, background-stronger, surface-raised-base,
text-strong, text-base, text-weak, border-weak-base,
text-interactive-base, font-family-sans. Your own CSS can target the
pieces' data-component/data-action attributes too; Mux's rules sit in
cascade layers (mux-*), so unlayered app rules win. Every Mux rule is scoped
to the pieces' root (the build fails otherwise), and menus render in an
overlay root that carries the same theme.
Bundling. solid-js is a peer dependency (one Solid for the app and the
pieces). The markdown and diff workers are ES-module workers: with Vite, set
worker: { format: "es" }. Code highlighting, math, and diagrams load on
demand.
Testing your app
@rohansguliani/mux-chat-ui/testing gives app tests stable hooks, so they
never depend on the chat's markup:
import { chatSelectors, readChat, replyFinished } from "@rohansguliani/mux-chat-ui/testing"
await page.locator(`#assistant ${chatSelectors.input}`).fill("Add NVDA to Tech")
await page.keyboard.press("Enter")
await page.waitForFunction(replyFinished, await page.$("#assistant"))
const { replies, tools, sessionID } = await page.locator("#assistant").evaluate(readChat)To pin a model in tests, set ${storageKey}:model in localStorage to
provider/model before starting a new chat.
