@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 +21 -0
- package/README.md +83 -0
- package/dist/controller.d.ts +66 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1237 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown.d.ts +2 -0
- package/dist/model.d.ts +33 -0
- package/dist/mount.d.ts +56 -0
- package/dist/styles.d.ts +2 -0
- package/dist/testing.d.ts +49 -0
- package/dist/testing.js +61 -0
- package/dist/testing.js.map +1 -0
- package/dist/view/Chat.d.ts +26 -0
- package/dist/view/parts.d.ts +19 -0
- package/dist/view/primitives.d.ts +10 -0
- package/package.json +35 -0
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>;
|
package/dist/index.d.ts
ADDED