@djangocfg/ui-tools 2.1.334 → 2.1.336
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/README.md +68 -2
- package/dist/ChatRoot-IIYQEWUU.mjs +5 -0
- package/dist/ChatRoot-IIYQEWUU.mjs.map +1 -0
- package/dist/ChatRoot-PNNGQCYF.css +7 -0
- package/dist/ChatRoot-PNNGQCYF.css.map +1 -0
- package/dist/ChatRoot-UUKTYM4N.cjs +14 -0
- package/dist/ChatRoot-UUKTYM4N.cjs.map +1 -0
- package/dist/{CronScheduler.client-3O3VU4CI.mjs → CronScheduler.client-DLMXCPAJ.mjs} +4 -4
- package/dist/{CronScheduler.client-3O3VU4CI.mjs.map → CronScheduler.client-DLMXCPAJ.mjs.map} +1 -1
- package/dist/{CronScheduler.client-A4GO6YBY.cjs → CronScheduler.client-WEJF4PWQ.cjs} +14 -14
- package/dist/{CronScheduler.client-A4GO6YBY.cjs.map → CronScheduler.client-WEJF4PWQ.cjs.map} +1 -1
- package/dist/{DocsLayout-XLDB6CJ2.cjs → DocsLayout-N5ZJZPBY.cjs} +200 -199
- package/dist/DocsLayout-N5ZJZPBY.cjs.map +1 -0
- package/dist/{DocsLayout-CTJINVBM.mjs → DocsLayout-VFPPNKSQ.mjs} +7 -6
- package/dist/DocsLayout-VFPPNKSQ.mjs.map +1 -0
- package/dist/JsonSchemaForm-DD7CLRIG.cjs +13 -0
- package/dist/{JsonSchemaForm-OSPUUUHM.cjs.map → JsonSchemaForm-DD7CLRIG.cjs.map} +1 -1
- package/dist/JsonSchemaForm-XKUIVELK.mjs +4 -0
- package/dist/{JsonSchemaForm-TSLX2GRO.mjs.map → JsonSchemaForm-XKUIVELK.mjs.map} +1 -1
- package/dist/JsonTree-55625VVH.mjs +5 -0
- package/dist/{JsonTree-F27RMYSI.cjs.map → JsonTree-55625VVH.mjs.map} +1 -1
- package/dist/JsonTree-DCM5QGWF.cjs +11 -0
- package/dist/{JsonTree-QTJYSHCV.mjs.map → JsonTree-DCM5QGWF.cjs.map} +1 -1
- package/dist/{LottiePlayer.client-6WVWDO75.cjs → LottiePlayer.client-2S7ISJ2S.cjs} +6 -6
- package/dist/{LottiePlayer.client-6WVWDO75.cjs.map → LottiePlayer.client-2S7ISJ2S.cjs.map} +1 -1
- package/dist/{LottiePlayer.client-B4I6WNZM.mjs → LottiePlayer.client-5LDSSJWS.mjs} +4 -4
- package/dist/{LottiePlayer.client-B4I6WNZM.mjs.map → LottiePlayer.client-5LDSSJWS.mjs.map} +1 -1
- package/dist/{MapContainer-RYG4HPH4.cjs → MapContainer-76YL2JXL.cjs} +8 -8
- package/dist/{MapContainer-RYG4HPH4.cjs.map → MapContainer-76YL2JXL.cjs.map} +1 -1
- package/dist/{MapContainer-GXQLP5WY.mjs → MapContainer-7HXBI3OH.mjs} +3 -3
- package/dist/{MapContainer-GXQLP5WY.mjs.map → MapContainer-7HXBI3OH.mjs.map} +1 -1
- package/dist/{Mermaid.client-SXRRI2YW.mjs → Mermaid.client-NL4SVR7F.mjs} +4 -4
- package/dist/{Mermaid.client-SXRRI2YW.mjs.map → Mermaid.client-NL4SVR7F.mjs.map} +1 -1
- package/dist/{Mermaid.client-W76R5AKJ.cjs → Mermaid.client-NNTI6DFX.cjs} +26 -26
- package/dist/{Mermaid.client-W76R5AKJ.cjs.map → Mermaid.client-NNTI6DFX.cjs.map} +1 -1
- package/dist/Player-BRV7XTWR.mjs +4 -0
- package/dist/{Player-M3GC3VPE.mjs.map → Player-BRV7XTWR.mjs.map} +1 -1
- package/dist/Player-PM7F7DD7.cjs +13 -0
- package/dist/{Player-ZL2X5LGG.cjs.map → Player-PM7F7DD7.cjs.map} +1 -1
- package/dist/{PrettyCode.client-RPDIE5CH.cjs → PrettyCode.client-KOHDVPPN.cjs} +13 -13
- package/dist/{PrettyCode.client-RPDIE5CH.cjs.map → PrettyCode.client-KOHDVPPN.cjs.map} +1 -1
- package/dist/{PrettyCode.client-SPMTQEG4.mjs → PrettyCode.client-ZGYGKE7G.mjs} +4 -4
- package/dist/{PrettyCode.client-SPMTQEG4.mjs.map → PrettyCode.client-ZGYGKE7G.mjs.map} +1 -1
- package/dist/TreeRoot-N72OYKXU.cjs +19 -0
- package/dist/{TreeRoot-A3J65L6F.mjs.map → TreeRoot-N72OYKXU.cjs.map} +1 -1
- package/dist/TreeRoot-VGAIXCUA.mjs +4 -0
- package/dist/{TreeRoot-DSK5JILT.cjs.map → TreeRoot-VGAIXCUA.mjs.map} +1 -1
- package/dist/chunk-2ZLKZ5VR.mjs +631 -0
- package/dist/chunk-2ZLKZ5VR.mjs.map +1 -0
- package/dist/{chunk-LFWQ36LJ.mjs → chunk-5G5YBFS6.mjs} +4 -4
- package/dist/{chunk-LFWQ36LJ.mjs.map → chunk-5G5YBFS6.mjs.map} +1 -1
- package/dist/{chunk-IHAY6FO6.cjs → chunk-5I5QNGUG.cjs} +17 -17
- package/dist/{chunk-IHAY6FO6.cjs.map → chunk-5I5QNGUG.cjs.map} +1 -1
- package/dist/{chunk-F2CMIIOH.cjs → chunk-76NNDZH6.cjs} +42 -42
- package/dist/{chunk-F2CMIIOH.cjs.map → chunk-76NNDZH6.cjs.map} +1 -1
- package/dist/chunk-B5AWZOHJ.cjs +649 -0
- package/dist/chunk-B5AWZOHJ.cjs.map +1 -0
- package/dist/{chunk-KR6B3LVY.mjs → chunk-B6IR5KSC.mjs} +3 -3
- package/dist/{chunk-KR6B3LVY.mjs.map → chunk-B6IR5KSC.mjs.map} +1 -1
- package/dist/{chunk-5LBDYFWH.mjs → chunk-C6GXVH5J.mjs} +3 -3
- package/dist/{chunk-5LBDYFWH.mjs.map → chunk-C6GXVH5J.mjs.map} +1 -1
- package/dist/{chunk-4IW7GZFQ.cjs → chunk-FEN5S772.cjs} +74 -48
- package/dist/chunk-FEN5S772.cjs.map +1 -0
- package/dist/{chunk-2SMCH62O.cjs → chunk-FP2RLYQZ.cjs} +11 -11
- package/dist/{chunk-2SMCH62O.cjs.map → chunk-FP2RLYQZ.cjs.map} +1 -1
- package/dist/{chunk-MOME6KYD.mjs → chunk-G5IEC7SR.mjs} +3 -3
- package/dist/{chunk-MOME6KYD.mjs.map → chunk-G5IEC7SR.mjs.map} +1 -1
- package/dist/{chunk-EXGXUK2N.mjs → chunk-GYIO7W7M.mjs} +41 -15
- package/dist/chunk-GYIO7W7M.mjs.map +1 -0
- package/dist/{chunk-3Z3A7FHA.cjs → chunk-IEEAENLX.cjs} +48 -48
- package/dist/{chunk-3Z3A7FHA.cjs.map → chunk-IEEAENLX.cjs.map} +1 -1
- package/dist/{chunk-DFTVB66S.cjs → chunk-KNDLV4PI.cjs} +85 -85
- package/dist/{chunk-DFTVB66S.cjs.map → chunk-KNDLV4PI.cjs.map} +1 -1
- package/dist/{chunk-SSUOENAZ.mjs → chunk-KNEQRUBA.mjs} +3 -3
- package/dist/{chunk-SSUOENAZ.mjs.map → chunk-KNEQRUBA.mjs.map} +1 -1
- package/dist/chunk-KRETIZU6.mjs +2218 -0
- package/dist/chunk-KRETIZU6.mjs.map +1 -0
- package/dist/{chunk-CGILA3WO.mjs → chunk-N2XQF2OL.mjs} +5 -3
- package/dist/{chunk-CGILA3WO.mjs.map → chunk-N2XQF2OL.mjs.map} +1 -1
- package/dist/{chunk-EUADAUBQ.mjs → chunk-N4MZYNR4.mjs} +4 -4
- package/dist/{chunk-EUADAUBQ.mjs.map → chunk-N4MZYNR4.mjs.map} +1 -1
- package/dist/chunk-NRXYYO5V.cjs +2257 -0
- package/dist/chunk-NRXYYO5V.cjs.map +1 -0
- package/dist/{chunk-GGKGH5PM.mjs → chunk-OBRSGM64.mjs} +4 -4
- package/dist/{chunk-GGKGH5PM.mjs.map → chunk-OBRSGM64.mjs.map} +1 -1
- package/dist/{chunk-6JTB2X72.mjs → chunk-ODO4GMW7.mjs} +3 -3
- package/dist/{chunk-6JTB2X72.mjs.map → chunk-ODO4GMW7.mjs.map} +1 -1
- package/dist/{chunk-WGEGR3DF.cjs → chunk-OLISEQHS.cjs} +5 -2
- package/dist/{chunk-WGEGR3DF.cjs.map → chunk-OLISEQHS.cjs.map} +1 -1
- package/dist/{chunk-PZKAH7WQ.mjs → chunk-PVAX67JG.mjs} +3 -3
- package/dist/{chunk-PZKAH7WQ.mjs.map → chunk-PVAX67JG.mjs.map} +1 -1
- package/dist/{chunk-PRPG2T2E.cjs → chunk-QJ6GTUCO.cjs} +6 -6
- package/dist/{chunk-PRPG2T2E.cjs.map → chunk-QJ6GTUCO.cjs.map} +1 -1
- package/dist/chunk-QW4RBGHN.cjs +961 -0
- package/dist/chunk-QW4RBGHN.cjs.map +1 -0
- package/dist/{chunk-33AMWFBZ.cjs → chunk-SGP7V2UW.cjs} +15 -15
- package/dist/{chunk-33AMWFBZ.cjs.map → chunk-SGP7V2UW.cjs.map} +1 -1
- package/dist/{chunk-FX2QFYWF.mjs → chunk-VWQ5WOIL.mjs} +3 -3
- package/dist/{chunk-FX2QFYWF.mjs.map → chunk-VWQ5WOIL.mjs.map} +1 -1
- package/dist/{chunk-ZLQHUZDU.cjs → chunk-YDPDTOSP.cjs} +139 -139
- package/dist/{chunk-ZLQHUZDU.cjs.map → chunk-YDPDTOSP.cjs.map} +1 -1
- package/dist/{chunk-77HQWEQ6.cjs → chunk-YW5IVWHQ.cjs} +33 -33
- package/dist/{chunk-77HQWEQ6.cjs.map → chunk-YW5IVWHQ.cjs.map} +1 -1
- package/dist/{chunk-YXBOAGIM.cjs → chunk-YXZ6GU7H.cjs} +7 -7
- package/dist/{chunk-YXBOAGIM.cjs.map → chunk-YXZ6GU7H.cjs.map} +1 -1
- package/dist/{chunk-62Y65TGK.mjs → chunk-ZUFTH5IR.mjs} +8 -631
- package/dist/chunk-ZUFTH5IR.mjs.map +1 -0
- package/dist/components-EHOGXATG.cjs +22 -0
- package/dist/{components-5UXYNAKR.cjs.map → components-EHOGXATG.cjs.map} +1 -1
- package/dist/components-MQ6DR7TX.cjs +26 -0
- package/dist/{components-CFXOEVPN.mjs.map → components-MQ6DR7TX.cjs.map} +1 -1
- package/dist/components-XRX7QGLB.mjs +5 -0
- package/dist/{components-WYEZL5TE.cjs.map → components-XRX7QGLB.mjs.map} +1 -1
- package/dist/components-YATKRWLH.mjs +5 -0
- package/dist/{components-ZAGG2PBO.mjs.map → components-YATKRWLH.mjs.map} +1 -1
- package/dist/file-icon/index.cjs +6 -6
- package/dist/file-icon/index.mjs +1 -1
- package/dist/index.cjs +735 -215
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +972 -39
- package/dist/index.d.ts +972 -39
- package/dist/index.mjs +387 -31
- package/dist/index.mjs.map +1 -1
- package/dist/tree/index.cjs +38 -38
- package/dist/tree/index.d.cts +2 -2
- package/dist/tree/index.d.ts +2 -2
- package/dist/tree/index.mjs +3 -3
- package/package.json +6 -6
- package/src/index.ts +5 -0
- package/src/stories/index.ts +3 -1
- package/src/tools/Chat/Chat.story.tsx +1006 -0
- package/src/tools/Chat/README.md +528 -0
- package/src/tools/Chat/components/Attachments.tsx +192 -0
- package/src/tools/Chat/components/ChatRoot.tsx +201 -0
- package/src/tools/Chat/components/Composer.tsx +134 -0
- package/src/tools/Chat/components/EmptyState.tsx +47 -0
- package/src/tools/Chat/components/ErrorBanner.tsx +47 -0
- package/src/tools/Chat/components/JumpToLatest.tsx +30 -0
- package/src/tools/Chat/components/MessageActions.tsx +72 -0
- package/src/tools/Chat/components/MessageBubble.tsx +228 -0
- package/src/tools/Chat/components/MessageList.tsx +82 -0
- package/src/tools/Chat/components/Sources.tsx +55 -0
- package/src/tools/Chat/components/StreamingIndicator.tsx +29 -0
- package/src/tools/Chat/components/ToolCalls.tsx +172 -0
- package/src/tools/Chat/components/index.ts +24 -0
- package/src/tools/Chat/config.ts +55 -0
- package/src/tools/Chat/context/ChatProvider.tsx +122 -0
- package/src/tools/Chat/context/index.ts +9 -0
- package/src/tools/Chat/core/audio/audioBus.ts +172 -0
- package/src/tools/Chat/core/audio/index.ts +8 -0
- package/src/tools/Chat/core/audio/preferences.ts +68 -0
- package/src/tools/Chat/core/audio/types.ts +49 -0
- package/src/tools/Chat/core/ids.ts +16 -0
- package/src/tools/Chat/core/index.ts +5 -0
- package/src/tools/Chat/core/markdown.ts +56 -0
- package/src/tools/Chat/core/payload-dispatch.ts +54 -0
- package/src/tools/Chat/core/persona.ts +35 -0
- package/src/tools/Chat/core/reducer.ts +335 -0
- package/src/tools/Chat/core/transport/http.ts +167 -0
- package/src/tools/Chat/core/transport/index.ts +13 -0
- package/src/tools/Chat/core/transport/mock.ts +134 -0
- package/src/tools/Chat/core/transport/sse.ts +116 -0
- package/src/tools/Chat/core/transport/types.ts +24 -0
- package/src/tools/Chat/hooks/index.ts +26 -0
- package/src/tools/Chat/hooks/useChat.ts +440 -0
- package/src/tools/Chat/hooks/useChatAudio.ts +191 -0
- package/src/tools/Chat/hooks/useChatComposer.ts +227 -0
- package/src/tools/Chat/hooks/useChatHistory.ts +59 -0
- package/src/tools/Chat/hooks/useChatLayout.ts +111 -0
- package/src/tools/Chat/hooks/useChatLightbox.ts +34 -0
- package/src/tools/Chat/hooks/useChatScroll.ts +132 -0
- package/src/tools/Chat/index.ts +158 -0
- package/src/tools/Chat/lazy.tsx +14 -0
- package/src/tools/Chat/types.ts +237 -0
- package/src/tools/Chat/utils/collectImageAttachments.ts +13 -0
- package/src/tools/JsonForm/JsonSchemaForm.tsx +32 -1
- package/src/tools/Map/README.md +384 -0
- package/dist/DocsLayout-CTJINVBM.mjs.map +0 -1
- package/dist/DocsLayout-XLDB6CJ2.cjs.map +0 -1
- package/dist/JsonSchemaForm-OSPUUUHM.cjs +0 -13
- package/dist/JsonSchemaForm-TSLX2GRO.mjs +0 -4
- package/dist/JsonTree-F27RMYSI.cjs +0 -11
- package/dist/JsonTree-QTJYSHCV.mjs +0 -5
- package/dist/Player-M3GC3VPE.mjs +0 -4
- package/dist/Player-ZL2X5LGG.cjs +0 -13
- package/dist/TreeRoot-A3J65L6F.mjs +0 -4
- package/dist/TreeRoot-DSK5JILT.cjs +0 -19
- package/dist/chunk-4IW7GZFQ.cjs.map +0 -1
- package/dist/chunk-62Y65TGK.mjs.map +0 -1
- package/dist/chunk-EXGXUK2N.mjs.map +0 -1
- package/dist/chunk-TKSFZHCG.cjs +0 -1597
- package/dist/chunk-TKSFZHCG.cjs.map +0 -1
- package/dist/components-5UXYNAKR.cjs +0 -22
- package/dist/components-CFXOEVPN.mjs +0 -5
- package/dist/components-WYEZL5TE.cjs +0 -26
- package/dist/components-ZAGG2PBO.mjs +0 -5
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
# Chat
|
|
2
|
+
|
|
3
|
+
Decomposed, transport-agnostic chat. Streaming-aware, markdown-native, mobile-ready.
|
|
4
|
+
|
|
5
|
+
## TL;DR
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { ChatRoot, createHttpTransport } from '@djangocfg/ui-tools';
|
|
9
|
+
|
|
10
|
+
const transport = createHttpTransport({
|
|
11
|
+
baseUrl: '/api/chat',
|
|
12
|
+
getAuthHeader: () => ({ Authorization: `Bearer ${getToken()}` }),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export function MyChat() {
|
|
16
|
+
return (
|
|
17
|
+
<div className="h-[600px]">
|
|
18
|
+
<ChatRoot transport={transport} config={{ greeting: 'How can I help?' }} />
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## What you get
|
|
25
|
+
|
|
26
|
+
- **Headless first.** Pure reducer + hooks; UI is optional and replaceable.
|
|
27
|
+
- **Decomposed.** `MessageList`, `MessageBubble`, `Composer`, `Sources`, `ToolCalls`, `Attachments`, `EmptyState`, `ErrorBanner`, `JumpToLatest`, `StreamingIndicator` — every part exported.
|
|
28
|
+
- **Markdown-native.** Sits on top of `MarkdownMessage` (GFM, code, mermaid, sanitized HTML).
|
|
29
|
+
- **Streaming.** SSE-based `AsyncGenerator` transport. Token coalescing, cancel, regenerate, partial-keep on cancel.
|
|
30
|
+
- **Tool calls.** Live streaming panels — auto-open while running, auto-close on completion. User toggles are remembered.
|
|
31
|
+
- **Personas.** `config.user` + `config.assistant` for default identity; `message.sender` for per-message overrides (multi-user / multi-bot).
|
|
32
|
+
- **Audio triggers.** Optional sounds on `messageSent`, `messageReceived`, `streamStart`, `error`, `mention`, `notification`. iOS/Safari unlock handled automatically.
|
|
33
|
+
- **Rich attachments.** `AttachmentsGrid` for thumbnails, `AttachmentsList` for custom renderers; `onAttachmentOpen` for host-side lightbox.
|
|
34
|
+
- **Tool-payload dispatcher.** `dispatchToolPayload(matchers, fallback)` — pluggable predicates choose `<LazyJsonTree>` / `<LazyMap>` / etc. without ui-tools owning those deps.
|
|
35
|
+
- **Sources.** RAG citation chips on assistant messages.
|
|
36
|
+
- **Slots everywhere.** `header`, `footer`, `banner`, `empty`, `composerToolbarStart/End`, `composerAttachmentTray`, `jumpToLatest` — plus render-prop variants.
|
|
37
|
+
- **Mobile.** `100dvh`, safe-area inset, 16px textarea (no iOS zoom), responsive `useChatLayout`.
|
|
38
|
+
- **A11y.** `role="log"` + polite live region, `aria-busy` on streaming bubbles, `role="alert"` errors, focus-visible actions.
|
|
39
|
+
|
|
40
|
+
## Architecture
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
Transport (interface) ← HTTP+SSE / Wails / WebSocket / mock
|
|
44
|
+
↓
|
|
45
|
+
Reducer (pure state machine)
|
|
46
|
+
↓
|
|
47
|
+
Hooks (useChat / useChatComposer / useChatScroll / useChatHistory / useChatLayout)
|
|
48
|
+
↓
|
|
49
|
+
ChatProvider (context)
|
|
50
|
+
↓
|
|
51
|
+
Components (MessageList, MessageBubble, Composer, Sources, ToolCalls, …)
|
|
52
|
+
↓
|
|
53
|
+
ChatRoot (one-line preset)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Module boundaries:
|
|
57
|
+
|
|
58
|
+
| Layer | May import | May NOT import |
|
|
59
|
+
| ---------------- | -------------------------------- | ------------------------ |
|
|
60
|
+
| `core/transport` | `core/types` | React, hooks, components |
|
|
61
|
+
| `core/reducer` | `core/types` | React, transport |
|
|
62
|
+
| `hooks` | `core/*`, `ui-core` hooks | components |
|
|
63
|
+
| `context` | `hooks` | components |
|
|
64
|
+
| `components` | `hooks`, `context`, `ui-core` UI | transport implementations |
|
|
65
|
+
|
|
66
|
+
## Slots
|
|
67
|
+
|
|
68
|
+
`<ChatRoot>` exposes named-prop slots for the most common roles. None are required; omit any slot to fall back to the default.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
<ChatRoot
|
|
72
|
+
transport={transport}
|
|
73
|
+
config={{ greeting: 'Hi!' }}
|
|
74
|
+
|
|
75
|
+
// ReactNode slots
|
|
76
|
+
banner={<UpgradeBanner />} // sticky note above the conversation
|
|
77
|
+
header={<MyChatHeader />} // title / actions row
|
|
78
|
+
footer={<Disclaimer />} // below the composer
|
|
79
|
+
empty={<MyEmptyState />} // replaces default <EmptyState>
|
|
80
|
+
composerToolbarStart={<VoiceButton />} // left of the textarea
|
|
81
|
+
composerToolbarEnd={<MentionButton />} // right of the textarea
|
|
82
|
+
composerAttachmentTray={<MyTray />} // replaces default attachment tray
|
|
83
|
+
jumpToLatest={<MyJumpPill />} // replaces floating "↓ N new" pill
|
|
84
|
+
|
|
85
|
+
// Render-prop slots (need access to data)
|
|
86
|
+
renderMessage={(m, i) => <MyBubble message={m} />}
|
|
87
|
+
renderHeader={(ctx) => (ctx.isStreaming ? <Streaming /> : <Idle />)}
|
|
88
|
+
renderEmpty={({ setValue, focus }) => <MyOnboarding onPick={setValue} />}
|
|
89
|
+
|
|
90
|
+
// Tool-call payload renderers — forwarded to <MessageBubble toolCallsProps>
|
|
91
|
+
toolCallsProps={{
|
|
92
|
+
defaultExpanded: true,
|
|
93
|
+
renderInput: (v) => <LazyJsonTree data={v} mode="compact" />,
|
|
94
|
+
renderOutput: (v) => <LazyJsonTree data={v} mode="compact" />,
|
|
95
|
+
}}
|
|
96
|
+
/>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Slot inventory
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
┌───────────────────────────────┐
|
|
103
|
+
│ banner (sticky) │
|
|
104
|
+
├───────────────────────────────┤
|
|
105
|
+
│ header │
|
|
106
|
+
├───────────────────────────────┤
|
|
107
|
+
│ messages (jumpToLatest) │
|
|
108
|
+
├───────────────────────────────┤
|
|
109
|
+
│ composerToolbarStart │
|
|
110
|
+
│ [textarea] composerToolbarEnd │
|
|
111
|
+
│ composerAttachmentTray │
|
|
112
|
+
├───────────────────────────────┤
|
|
113
|
+
│ footer │
|
|
114
|
+
└───────────────────────────────┘
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### ToolCalls — expand behavior
|
|
118
|
+
|
|
119
|
+
Panels are **collapsed by default**. While a tool is `running`, the panel auto-expands so you can see live `streamingText`; on completion it auto-collapses again. Manual user toggles (open or close) are remembered and override the auto-behavior for that call.
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
<ToolCalls
|
|
123
|
+
calls={message.toolCalls}
|
|
124
|
+
// Defaults:
|
|
125
|
+
defaultExpanded={false} // start closed
|
|
126
|
+
expandWhileStreaming={true} // open during run, close on completion
|
|
127
|
+
/>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Force every panel open from the start (e.g. for debug views) with `defaultExpanded`.
|
|
131
|
+
|
|
132
|
+
### ToolCalls payload renderers
|
|
133
|
+
|
|
134
|
+
`<ToolCalls>` (and `<MessageBubble toolCallsProps>` / `<ChatRoot toolCallsProps>`) accept payload renderers so you control how tool input / output / streaming text is displayed. Defaults render a cheap `<pre>`. For a richer experience, plug in `LazyJsonTree` from `@djangocfg/ui-tools/json-tree` with `mode="compact"`:
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
import { LazyJsonTree } from '@djangocfg/ui-tools/json-tree';
|
|
138
|
+
|
|
139
|
+
<ToolCalls
|
|
140
|
+
calls={message.toolCalls}
|
|
141
|
+
renderInput={(input) => <LazyJsonTree data={input} mode="compact" />}
|
|
142
|
+
renderOutput={(output) => <LazyJsonTree data={output} mode="compact" />}
|
|
143
|
+
/>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`renderInput` / `renderOutput` / `renderStreaming` take precedence; `renderPayload(value, kind, call)` is a single fallback if you want one renderer for all three.
|
|
147
|
+
|
|
148
|
+
We don't import `LazyJsonTree` automatically — keeping the chat dep-light. The host opts in.
|
|
149
|
+
|
|
150
|
+
## Personas (user & assistant identity)
|
|
151
|
+
|
|
152
|
+
Pass identities once on `<ChatRoot config>` — every bubble gets the right name, avatar and `aria-label`. Outgoing user messages get the `sender` field auto-stamped from `config.user`.
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
<ChatRoot
|
|
156
|
+
transport={transport}
|
|
157
|
+
config={{
|
|
158
|
+
user: {
|
|
159
|
+
name: 'Mark',
|
|
160
|
+
avatarUrl: '/me.jpg',
|
|
161
|
+
description: 'Senior engineer',
|
|
162
|
+
},
|
|
163
|
+
assistant: {
|
|
164
|
+
name: 'Claude',
|
|
165
|
+
avatarUrl: '/claude.png',
|
|
166
|
+
model: 'claude-opus-4-7',
|
|
167
|
+
},
|
|
168
|
+
}}
|
|
169
|
+
/>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Resolution cascade per bubble:
|
|
173
|
+
|
|
174
|
+
1. `message.sender` — per-message override (multi-user / multi-bot chats)
|
|
175
|
+
2. `config.user` / `config.assistant` — provider-level default
|
|
176
|
+
3. Hardcoded `'You'` / `'AI'` fallback
|
|
177
|
+
|
|
178
|
+
Multi-user example — server returns `sender` per message:
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
{
|
|
182
|
+
id: 'm3',
|
|
183
|
+
role: 'user',
|
|
184
|
+
content: 'Flag the auth migration section.',
|
|
185
|
+
sender: { name: 'Anna', avatarUrl: '/anna.jpg' },
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Initials are auto-derived from `name` (`'Mark Olofsen'` → `MO`); set `initials` explicitly to override.
|
|
190
|
+
|
|
191
|
+
Helpers exposed for hosts that build their own bubbles:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { resolvePersona, deriveInitials } from '@djangocfg/ui-tools';
|
|
195
|
+
const persona = resolvePersona(message, config.user, config.assistant);
|
|
196
|
+
const fallback = deriveInitials(persona, message.role);
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Attachments
|
|
200
|
+
|
|
201
|
+
Two specialised components plus a backwards-compatible facade:
|
|
202
|
+
|
|
203
|
+
| Component | Layout | Use it for |
|
|
204
|
+
| ------------------- | -------------- | -------------------------------------------- |
|
|
205
|
+
| `<AttachmentsGrid>` | `flex-wrap` | Thumbnails, file chips, no custom renderers |
|
|
206
|
+
| `<AttachmentsList>` | `flex-col` | Custom renderers (audio, video, doc viewer) |
|
|
207
|
+
| `<Attachments>` | picks for you | Backwards-compat: stacks when `renderers` is given, wraps otherwise |
|
|
208
|
+
|
|
209
|
+
`<MessageBubble>` automatically uses `AttachmentsList` when you pass `attachmentRenderers` and `AttachmentsGrid` otherwise — `flex-wrap` shrinks block-level renderers to min-content, so picking the right component matters.
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
import { AttachmentsList } from '@djangocfg/ui-tools';
|
|
213
|
+
|
|
214
|
+
<AttachmentsList
|
|
215
|
+
attachments={msg.attachments}
|
|
216
|
+
renderers={{
|
|
217
|
+
audio: ({ attachment }) => <LazyAudioPlayer src={attachment.url} variant="compact" />,
|
|
218
|
+
}}
|
|
219
|
+
/>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Audio triggers
|
|
223
|
+
|
|
224
|
+
Optional sound effects on chat events. Off by default — you opt in with a `sounds` map. Six events:
|
|
225
|
+
|
|
226
|
+
| Event | Fires when |
|
|
227
|
+
| ------------------ | --------------------------------------------------- |
|
|
228
|
+
| `messageSent` | After `useChat.sendMessage()` adds the user message |
|
|
229
|
+
| `messageReceived` | On `STREAM_DONE` (final assistant message) |
|
|
230
|
+
| `streamStart` | First token / placeholder created |
|
|
231
|
+
| `error` | `STREAM_ERROR` or transport throw |
|
|
232
|
+
| `mention` | Host-fired (e.g. assistant addressed the user) |
|
|
233
|
+
| `notification` | Generic — host triggers manually |
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
<ChatRoot
|
|
237
|
+
transport={transport}
|
|
238
|
+
audio={{
|
|
239
|
+
sounds: {
|
|
240
|
+
messageSent: '/audio/chat/sent.mp3',
|
|
241
|
+
messageReceived: '/audio/chat/received.mp3',
|
|
242
|
+
streamStart: '/audio/chat/start.mp3',
|
|
243
|
+
error: '/audio/chat/error.mp3',
|
|
244
|
+
},
|
|
245
|
+
// Defaults: muteWhenHidden: true, respectReducedMotion: true, respectReducedData: true
|
|
246
|
+
}}
|
|
247
|
+
/>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Pitfalls handled inside (lifted from `AudioPlayer/audio`):
|
|
251
|
+
|
|
252
|
+
- **iOS/Safari autoplay block.** First `pointerdown` / `keydown` inside `<ChatProvider>` runs a transactional unlock — every cached `<audio>` is poked once with `muted=true`, then released.
|
|
253
|
+
- **Same-element race.** Each `play()` clones a fresh `Audio(url)` so two rapid events don't cancel each other; the cached element warms the HTTP cache.
|
|
254
|
+
- **Cross-tab sync.** Volume / mute / per-event toggles live in a Zustand `persist` store with `localStorage` + the native `storage` event.
|
|
255
|
+
- **Visibility.** Auto-mute while `document.visibilityState === 'hidden'`.
|
|
256
|
+
- **Reduced motion / data.** Auto-suppress on `prefers-reduced-motion: reduce` and `prefers-reduced-data: reduce` (configurable).
|
|
257
|
+
- **SSR-safe.** `audioBus` falls back to a no-op when `window` is undefined.
|
|
258
|
+
|
|
259
|
+
For programmatic control (e.g. mute toggle in your header):
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
const ctx = useChatContext();
|
|
263
|
+
ctx.audio.setMuted(!ctx.audio.muted);
|
|
264
|
+
ctx.audio.setVolume(0.6);
|
|
265
|
+
ctx.audio.setEventEnabled('messageSent', false);
|
|
266
|
+
ctx.audio.play('mention'); // host-fired
|
|
267
|
+
ctx.audio.isUnlocked; // boolean
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Or directly: `useChatAudio(config)` — returns the same surface, no provider required.
|
|
271
|
+
|
|
272
|
+
## Attachment renderers (registry)
|
|
273
|
+
|
|
274
|
+
`<Attachments>` and `<ChatRoot>` accept a per-type renderer map. Default tile is used when no renderer matches. Plug in heavy viewers (`LazyAudioPlayer`, `LazyImageViewer`, `LazyMap`) host-side without forcing `ui-tools/Chat` to depend on them.
|
|
275
|
+
|
|
276
|
+
```tsx
|
|
277
|
+
import { LazyPlayer as LazyAudioPlayer } from '@djangocfg/ui-tools/audio-player';
|
|
278
|
+
|
|
279
|
+
<ChatRoot
|
|
280
|
+
transport={transport}
|
|
281
|
+
attachmentRenderers={{
|
|
282
|
+
audio: ({ attachment }) => (
|
|
283
|
+
<div className="my-1 max-w-md">
|
|
284
|
+
<LazyAudioPlayer src={attachment.url} title={attachment.name} variant="compact" />
|
|
285
|
+
</div>
|
|
286
|
+
),
|
|
287
|
+
// image / video / file / default — all optional
|
|
288
|
+
}}
|
|
289
|
+
onAttachmentOpen={(att) => openLightbox(att)}
|
|
290
|
+
/>
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Renderer signature:
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
type AttachmentRenderer = (args: {
|
|
297
|
+
attachment: ChatAttachment;
|
|
298
|
+
isInComposer: boolean; // true in the composer staging tray
|
|
299
|
+
onClick?: () => void; // forwarded from <ChatRoot onAttachmentOpen>
|
|
300
|
+
onRemove?: () => void; // present in the composer tray
|
|
301
|
+
}) => ReactNode;
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
`<MessageBubble attachmentsRenderer>` (a wholesale slot) still wins over the registry — use it when you need to swap the entire attachments block.
|
|
305
|
+
|
|
306
|
+
## Image lightbox helpers
|
|
307
|
+
|
|
308
|
+
```tsx
|
|
309
|
+
import {
|
|
310
|
+
useChatLightbox,
|
|
311
|
+
collectImageAttachments,
|
|
312
|
+
} from '@djangocfg/ui-tools';
|
|
313
|
+
import { LazyImageViewer } from '@djangocfg/ui-tools/image-viewer';
|
|
314
|
+
|
|
315
|
+
function MyChat() {
|
|
316
|
+
const lightbox = useChatLightbox();
|
|
317
|
+
const ctx = useChatContext();
|
|
318
|
+
const gallery = useMemo(() => collectImageAttachments(ctx.messages), [ctx.messages]);
|
|
319
|
+
|
|
320
|
+
return (
|
|
321
|
+
<>
|
|
322
|
+
<ChatRoot
|
|
323
|
+
transport={transport}
|
|
324
|
+
onAttachmentOpen={(att) => att.type === 'image' && lightbox.open(att, gallery)}
|
|
325
|
+
/>
|
|
326
|
+
<Dialog open={!!lightbox.state} onOpenChange={(o) => !o && lightbox.close()}>
|
|
327
|
+
<DialogContent className="max-w-5xl">
|
|
328
|
+
{lightbox.state && (
|
|
329
|
+
<LazyImageViewer
|
|
330
|
+
images={lightbox.state.gallery.map((a) => ({
|
|
331
|
+
file: { name: a.name ?? a.id, path: a.id },
|
|
332
|
+
src: a.url,
|
|
333
|
+
}))}
|
|
334
|
+
initialIndex={lightbox.state.index}
|
|
335
|
+
inDialog
|
|
336
|
+
/>
|
|
337
|
+
)}
|
|
338
|
+
</DialogContent>
|
|
339
|
+
</Dialog>
|
|
340
|
+
</>
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
`useChatLightbox` is just a tiny `{ gallery, index } | null` state container — the host owns the modal + viewer mount, so `<LazyImageViewer>` (~50 KB) stays out of `ui-tools/Chat`.
|
|
346
|
+
|
|
347
|
+
## Tool-payload dispatcher
|
|
348
|
+
|
|
349
|
+
For tool-call panels, `dispatchToolPayload(matchers, fallback)` lets you pick a renderer per payload shape — without writing your own switch each time.
|
|
350
|
+
|
|
351
|
+
```tsx
|
|
352
|
+
import {
|
|
353
|
+
dispatchToolPayload,
|
|
354
|
+
isLatLng,
|
|
355
|
+
isPlainObject,
|
|
356
|
+
isGeoJSONFeatureCollection,
|
|
357
|
+
} from '@djangocfg/ui-tools';
|
|
358
|
+
import { LazyJsonTree } from '@djangocfg/ui-tools/json-tree';
|
|
359
|
+
import { LazyMap } from '@djangocfg/ui-tools';
|
|
360
|
+
|
|
361
|
+
const renderPayload = dispatchToolPayload(
|
|
362
|
+
[
|
|
363
|
+
{
|
|
364
|
+
match: (v) => isGeoJSONFeatureCollection(v),
|
|
365
|
+
render: (v) => <LazyMap geojson={v as GeoJSON.FeatureCollection} />,
|
|
366
|
+
},
|
|
367
|
+
{ match: (v) => isLatLng(v), render: (v) => <LazyMap markers={[v as { lat: number; lng: number }]} /> },
|
|
368
|
+
{ match: (v) => isPlainObject(v), render: (v) => <LazyJsonTree data={v} mode="compact" /> },
|
|
369
|
+
],
|
|
370
|
+
(v) => <pre className="text-[11px]">{String(v)}</pre>,
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
<ChatRoot toolCallsProps={{ defaultExpanded: true, renderPayload }} />;
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
First match wins. Predicates `isLatLng` / `isPlainObject` / `isGeoJSONFeatureCollection` / `isStringValue` are exported as building blocks.
|
|
377
|
+
|
|
378
|
+
## Three usage patterns
|
|
379
|
+
|
|
380
|
+
### 1. One-line preset
|
|
381
|
+
|
|
382
|
+
```tsx
|
|
383
|
+
<ChatRoot transport={transport} config={{ greeting: 'Hi!' }} />
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### 2. Composition
|
|
387
|
+
|
|
388
|
+
Bring your own layout, reuse the parts.
|
|
389
|
+
|
|
390
|
+
```tsx
|
|
391
|
+
<ChatProvider transport={transport}>
|
|
392
|
+
<MyHeader />
|
|
393
|
+
<MessageList renderEmpty={() => <EmptyState greeting="Custom" />} />
|
|
394
|
+
<MyComposer />
|
|
395
|
+
</ChatProvider>
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### 3. Headless (just hooks)
|
|
399
|
+
|
|
400
|
+
```tsx
|
|
401
|
+
const chat = useChat({ transport });
|
|
402
|
+
const composer = useChatComposer({ onSubmit: chat.sendMessage });
|
|
403
|
+
// render however you want
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
## Transport contract
|
|
407
|
+
|
|
408
|
+
A single I/O seam.
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
interface ChatTransport {
|
|
412
|
+
createSession(opts?): Promise<SessionInfo>;
|
|
413
|
+
loadHistory(sessionId, cursor?, limit?): Promise<HistoryPage>;
|
|
414
|
+
stream(sessionId, content, { signal, attachments, metadata }): AsyncGenerator<ChatStreamEvent>;
|
|
415
|
+
send(sessionId, content, options?): Promise<ChatMessage>;
|
|
416
|
+
closeSession(sessionId): Promise<void>;
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Shipped implementations:
|
|
421
|
+
|
|
422
|
+
- **`createHttpTransport({ baseUrl, getAuthHeader, slug })`** — fetch + SSE, default for web.
|
|
423
|
+
- **`createMockTransport({ replies, latencyMs })`** — scripted in-memory replies for stories/tests.
|
|
424
|
+
|
|
425
|
+
Custom hosts (Wails RPC, WebSocket, gRPC) implement the same interface — see [`@dev/@refactoring7-chat/06-integration.md`](../../../@dev/@refactoring7-chat/06-integration.md) for adapter recipes.
|
|
426
|
+
|
|
427
|
+
## Public surface
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
// Types
|
|
431
|
+
ChatMessage, ChatRole, ChatPersona, ChatToolCall, ChatAttachment, ChatSource,
|
|
432
|
+
ChatConfig, ChatUserContext, ChatAssistantContext, ChatPrefs, ChatLabels,
|
|
433
|
+
ChatTransport, ChatStreamEvent, ChatDisplayMode,
|
|
434
|
+
SessionInfo, HistoryPage, StreamOptions, SendOptions
|
|
435
|
+
|
|
436
|
+
// Core (pure)
|
|
437
|
+
reducer, initialState, createId, createTokenBuffer,
|
|
438
|
+
resolvePersona, deriveInitials
|
|
439
|
+
type ChatState, type ChatAction
|
|
440
|
+
|
|
441
|
+
// Transport
|
|
442
|
+
createHttpTransport, createMockTransport, parseSSE, TransportError
|
|
443
|
+
|
|
444
|
+
// Hooks
|
|
445
|
+
useChat, useChatComposer, useChatScroll, useChatHistory, useChatLayout,
|
|
446
|
+
useChatAudio, useChatLightbox
|
|
447
|
+
|
|
448
|
+
// Audio
|
|
449
|
+
ChatAudioConfig, ChatAudioEvent, ChatAudioSounds, UseChatAudioReturn,
|
|
450
|
+
useChatAudioPrefs
|
|
451
|
+
|
|
452
|
+
// Tool-payload dispatch
|
|
453
|
+
dispatchToolPayload, isPlainObject, isLatLng,
|
|
454
|
+
isGeoJSONFeatureCollection, isStringValue,
|
|
455
|
+
type ToolPayloadMatcher, type ToolPayloadFallback
|
|
456
|
+
|
|
457
|
+
// Lightbox helpers
|
|
458
|
+
collectImageAttachments
|
|
459
|
+
|
|
460
|
+
// Context
|
|
461
|
+
ChatProvider, useChatContext, useChatContextOptional
|
|
462
|
+
|
|
463
|
+
// Components
|
|
464
|
+
ChatRoot, MessageList, MessageBubble, MessageActions, Composer,
|
|
465
|
+
Sources, ToolCalls, Attachments, EmptyState, ErrorBanner,
|
|
466
|
+
JumpToLatest, StreamingIndicator
|
|
467
|
+
|
|
468
|
+
// Lazy preset
|
|
469
|
+
LazyChat
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
## Mobile & a11y notes
|
|
473
|
+
|
|
474
|
+
- `useChatLayout` collapses `sidebar`/`floating` to `fullscreen` below `(max-width: 640px)`.
|
|
475
|
+
- Composer textarea is `font-size: 16px` to disable iOS focus-zoom; container uses `100dvh` and `env(safe-area-inset-bottom)`.
|
|
476
|
+
- `MessageList` is `role="log"` with `aria-live="polite"`; streaming bubbles set `aria-busy`.
|
|
477
|
+
- `ErrorBanner` is `role="alert"`.
|
|
478
|
+
- `JumpToLatest` announces unread count via `aria-live="polite"`.
|
|
479
|
+
|
|
480
|
+
## Hotkeys
|
|
481
|
+
|
|
482
|
+
| Key | Behavior |
|
|
483
|
+
| ------------------ | --------------------------------------- |
|
|
484
|
+
| `Enter` | Send (configurable to `Cmd/Ctrl+Enter`) |
|
|
485
|
+
| `Shift+Enter` | Newline |
|
|
486
|
+
| `↑` (empty input) | Recall previous submission |
|
|
487
|
+
| `↓` | Recall next |
|
|
488
|
+
| `Esc` | (host-bound) cancel stream |
|
|
489
|
+
|
|
490
|
+
## Performance
|
|
491
|
+
|
|
492
|
+
- **Token coalescing.** `createTokenBuffer` aggregates stream chunks within ~16ms before dispatching → ≤1 render per frame.
|
|
493
|
+
- **Plain text during stream.** `MessageBubble` skips ReactMarkdown until the message finishes, then re-renders once with full markdown.
|
|
494
|
+
- **Memoized bubbles.** Memo key `(id, content, isStreaming, version, toolActivity, toolCalls, sources, attachments)` — references only.
|
|
495
|
+
- **Virtualization is opt-in.** `@tanstack/react-virtual` stays a host-side decision via `MessageList renderItem` slot.
|
|
496
|
+
|
|
497
|
+
## Design docs
|
|
498
|
+
|
|
499
|
+
Full implementation plan and rationale lives at [`@dev/@refactoring7-chat/`](../../../@dev/@refactoring7-chat/):
|
|
500
|
+
|
|
501
|
+
- `00-overview.md` — file layout & public API
|
|
502
|
+
- `01-architecture.md` — layered model, dataflow, transport contract
|
|
503
|
+
- `02-state-model.md` — reducer actions and invariants
|
|
504
|
+
- `03-hooks.md` — every hook signature
|
|
505
|
+
- `04-components.md` — every component prop shape
|
|
506
|
+
- `05-mobile-and-a11y.md` — breakpoints, iOS, ARIA, focus, RTL
|
|
507
|
+
- `06-integration.md` — adapter recipes
|
|
508
|
+
- `07-testing.md` — reducer / hook / component / a11y tests
|
|
509
|
+
- `08-migration.md` — step-by-step rollout
|
|
510
|
+
|
|
511
|
+
## Storybook
|
|
512
|
+
|
|
513
|
+
`Tools/Chat` covers:
|
|
514
|
+
|
|
515
|
+
- `Default` — full ChatRoot with mock transport and suggestions
|
|
516
|
+
- `WithToolCalls` — scripted tool invocation + sources
|
|
517
|
+
- `Composition` — bring your own layout (`<ChatProvider>` + parts)
|
|
518
|
+
- `Bubbles` — visual matrix of message states
|
|
519
|
+
- `Parts` — every decomposed component on its own (incl. `ToolCalls` + `LazyJsonTree` compact)
|
|
520
|
+
- `WithSlots` — every named slot at once (banner / header / empty / composer toolbar / footer)
|
|
521
|
+
- `WithJsonTreePayload` — `<ChatRoot toolCallsProps>` with `LazyJsonTree` payload renderers
|
|
522
|
+
- `WithAudio` — chat-event sound triggers + unlock-state badge
|
|
523
|
+
- `WithAudioAttachment` — attachment registry mounts `<LazyAudioPlayer>` for `audio` items
|
|
524
|
+
- `WithImageLightbox` — `useChatLightbox` + host-side `<Dialog>` + `<LazyImageViewer>`
|
|
525
|
+
- `WithMapPayload` — `dispatchToolPayload` with `LazyJsonTree` fallback
|
|
526
|
+
- `WithPersonas` — config-level `user` + `assistant` identity (avatar, name)
|
|
527
|
+
- `MultiUser` — per-message `sender` overrides (multi-user / multi-bot)
|
|
528
|
+
- `Playground` — knobs (latency, streaming, suggestions)
|