@rohansguliani/mux-chat-ui 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rohan Guliani
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # @rohansguliani/mux-chat-ui
2
+
3
+ Mux's chat as a drop-in panel for your app: the streaming transcript, tool
4
+ activity rows, reasoning, question cards, the model picker, Stop, and New chat.
5
+ It renders with Mux's own stylesheets (synced from Mux, see below) inside a
6
+ shadow root, so it looks like Mux and neither affects nor inherits your page's
7
+ CSS.
8
+
9
+ Your server mounts one route with
10
+ [`@rohansguliani/mux-chat-runtime`](https://www.npmjs.com/package/@rohansguliani/mux-chat-runtime):
11
+ `app.all("/api/mux/*", (c) => mux.handle(c.req.raw, { subject: userID }))`.
12
+
13
+ ```ts
14
+ import { mountMuxChat } from "@rohansguliani/mux-chat-ui"
15
+
16
+ const chat = mountMuxChat(document.getElementById("assistant")!, {
17
+ title: "Assistant",
18
+ subtitle: "Reads and edits your todos",
19
+ suggestions: [{ label: "What's due today?", prompt: "What is due today?" }],
20
+ theme: "dark",
21
+ onToolResult: ({ readOnly }) => { if (!readOnly) refreshTodos() },
22
+ })
23
+
24
+ chat.send("Summarize my week") // e.g. from a button elsewhere in your app
25
+ ```
26
+
27
+ Tool rows use each tool's `label` from your tool definitions, and
28
+ `onToolResult` reports `readOnly` from them too, so the app never lists its
29
+ tools again on the client.
30
+
31
+ Give the host element a height (a sidebar column); the chat fills it. In a
32
+ Solid app, `<MuxChat {...options} class="sidebar" ref={(handle) => ...} />`
33
+ does the same.
34
+
35
+ | Option | Default | |
36
+ | --- | --- | --- |
37
+ | `url` | `/api/mux` | where the app mounts `mux.handle` |
38
+ | `title`, `subtitle`, `placeholder` | `Assistant` | header and empty state |
39
+ | `suggestions` | none | starter prompts in an empty chat |
40
+ | `toolLabels` | each tool's `label` | overrides names in the transcript |
41
+ | `preferredModel` | runtime default | `provider/model`, until the user picks one |
42
+ | `onToolResult` | | a tool call finished while the chat was open; `readOnly: false` means it may have changed app data |
43
+ | `theme` | follows the OS | `light` or `dark` |
44
+ | `storageKey` | `mux-chat` | prefix for the remembered chat and model (localStorage) |
45
+
46
+ It resumes the user's last chat on reload, keeps its grant renewed (retrying
47
+ through outages), reconnects the stream, and pages older history as you scroll
48
+ up (all through [`@rohansguliani/mux-chat-core`](https://www.npmjs.com/package/@rohansguliani/mux-chat-core)). A message the
49
+ server refuses (for example over a per-user limit) goes back into the
50
+ composer; one whose delivery is unconfirmed stays in the transcript with a
51
+ Retry button. Questions and permission requests from the assistant appear
52
+ above the composer. `chat.send()` from your page waits until the chat is
53
+ ready.
54
+
55
+ `solid-js` is a peer dependency (npm installs it); apps on other frameworks
56
+ only call `mountMuxChat`.
57
+
58
+ ## Testing your app
59
+
60
+ `@rohansguliani/mux-chat-ui/testing` gives app tests stable hooks, so they
61
+ never depend on the chat's markup:
62
+
63
+ ```js
64
+ import { chatSelectors, readChat, replyFinished } from "@rohansguliani/mux-chat-ui/testing"
65
+
66
+ await page.fill(chatSelectors.input, "Add NVDA to Tech") // selectors pierce the shadow root in Playwright
67
+ await page.keyboard.press("Enter")
68
+ await page.waitForFunction(replyFinished, await page.$("#assistant"))
69
+ const { replies, tools, sessionID } = await page.locator("#assistant").evaluate(readChat)
70
+ ```
71
+
72
+ ## Styles
73
+
74
+ `src/styles/mux/` holds Mux's stylesheets (theme tokens, markdown, tool rows,
75
+ collapsible, shimmer, dock surfaces, message parts), copied by a script that
76
+ rewrites `:root` to `:host` and makes the dark palette selectable. The
77
+ components render the markup those stylesheets target. `src/styles/chat.css`
78
+ adds the embed layout and the few composer rules Mux writes as Tailwind
79
+ classes. After Mux's styles change:
80
+
81
+ ```bash
82
+ MUX_SOURCE=../mux npm run sync-styles
83
+ ```
@@ -0,0 +1,66 @@
1
+ import { type ChatSessionView, type ChatState, type PermissionRequest, type QuestionRequest, type Turn } from "@rohansguliani/mux-chat-core";
2
+ import { type ModelGroup } from "./model.js";
3
+ export type { ChatGrant, ChatTool } from "@rohansguliani/mux-chat-core";
4
+ /**
5
+ * A tool call that finished while the chat was open. `readOnly` comes from
6
+ * the app's tool definitions (built-in tools such as web search are read-only),
7
+ * so `!readOnly` means the call may have changed app data.
8
+ */
9
+ export type ToolResult = {
10
+ tool: string;
11
+ title?: string;
12
+ input: Record<string, unknown>;
13
+ output: string;
14
+ sessionID: string;
15
+ readOnly: boolean;
16
+ };
17
+ export type ControllerOptions = {
18
+ /** Where the app mounts `mux.handle`. */
19
+ url: string;
20
+ agent: string;
21
+ sessionTitle: string;
22
+ storageKey: string;
23
+ preferredModel?: string;
24
+ onToolResult?: (result: ToolResult) => void;
25
+ };
26
+ export type Status = {
27
+ tone: "ok" | "busy" | "bad";
28
+ text: string;
29
+ };
30
+ /**
31
+ * What happened to a message: delivered; shown but unconfirmed (it stays in
32
+ * the transcript and `retry()` completes it); or not delivered at all, so the
33
+ * caller still owns the text.
34
+ */
35
+ export type SendResult = "sent" | "unconfirmed" | "failed";
36
+ export declare function createChatController(options: ControllerOptions): {
37
+ /** Changes once per rendered frame while the transcript updates. */
38
+ changes: import("solid-js").Accessor<ChatState>;
39
+ sessionID: import("solid-js").Accessor<string | undefined>;
40
+ /** Labels from the app's tool definitions. */
41
+ toolLabels: () => Record<string, string>;
42
+ messageIDs: import("solid-js").Accessor<string[]>;
43
+ turn: (id: string) => Turn | undefined;
44
+ questions: import("solid-js").Accessor<readonly QuestionRequest[]>;
45
+ permissions: import("solid-js").Accessor<readonly PermissionRequest[]>;
46
+ streaming: import("solid-js").Accessor<boolean>;
47
+ ready: () => boolean;
48
+ view: import("solid-js").Accessor<ChatSessionView>;
49
+ status: import("solid-js").Accessor<Status>;
50
+ error: () => string;
51
+ /** A message is shown but its delivery was not confirmed; `retry()` completes it. */
52
+ unconfirmed: () => boolean;
53
+ modelGroups: import("solid-js").Accessor<ModelGroup[]>;
54
+ modelValue: import("solid-js").Accessor<string>;
55
+ selectModel: (value: string) => void;
56
+ send(text: string): Promise<SendResult>;
57
+ retry(): Promise<void>;
58
+ abort: () => Promise<void>;
59
+ answer: (requestID: string, answers: string[][]) => Promise<void>;
60
+ reject: (requestID: string) => Promise<void>;
61
+ replyPermission: (requestID: string, response: "once" | "always" | "reject") => Promise<void>;
62
+ /** Loads the next older page of history, if any. */
63
+ older: () => void;
64
+ newChat(): Promise<void>;
65
+ };
66
+ export type ChatController = ReturnType<typeof createChatController>;
@@ -0,0 +1,2 @@
1
+ export { mountMuxChat, MuxChat, type MuxChatHandle, type MuxChatOptions } from "./mount.jsx";
2
+ export type { ChatGrant, ToolResult } from "./controller.js";