@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.
Files changed (196) hide show
  1. package/README.md +68 -2
  2. package/dist/ChatRoot-IIYQEWUU.mjs +5 -0
  3. package/dist/ChatRoot-IIYQEWUU.mjs.map +1 -0
  4. package/dist/ChatRoot-PNNGQCYF.css +7 -0
  5. package/dist/ChatRoot-PNNGQCYF.css.map +1 -0
  6. package/dist/ChatRoot-UUKTYM4N.cjs +14 -0
  7. package/dist/ChatRoot-UUKTYM4N.cjs.map +1 -0
  8. package/dist/{CronScheduler.client-3O3VU4CI.mjs → CronScheduler.client-DLMXCPAJ.mjs} +4 -4
  9. package/dist/{CronScheduler.client-3O3VU4CI.mjs.map → CronScheduler.client-DLMXCPAJ.mjs.map} +1 -1
  10. package/dist/{CronScheduler.client-A4GO6YBY.cjs → CronScheduler.client-WEJF4PWQ.cjs} +14 -14
  11. package/dist/{CronScheduler.client-A4GO6YBY.cjs.map → CronScheduler.client-WEJF4PWQ.cjs.map} +1 -1
  12. package/dist/{DocsLayout-XLDB6CJ2.cjs → DocsLayout-N5ZJZPBY.cjs} +200 -199
  13. package/dist/DocsLayout-N5ZJZPBY.cjs.map +1 -0
  14. package/dist/{DocsLayout-CTJINVBM.mjs → DocsLayout-VFPPNKSQ.mjs} +7 -6
  15. package/dist/DocsLayout-VFPPNKSQ.mjs.map +1 -0
  16. package/dist/JsonSchemaForm-DD7CLRIG.cjs +13 -0
  17. package/dist/{JsonSchemaForm-OSPUUUHM.cjs.map → JsonSchemaForm-DD7CLRIG.cjs.map} +1 -1
  18. package/dist/JsonSchemaForm-XKUIVELK.mjs +4 -0
  19. package/dist/{JsonSchemaForm-TSLX2GRO.mjs.map → JsonSchemaForm-XKUIVELK.mjs.map} +1 -1
  20. package/dist/JsonTree-55625VVH.mjs +5 -0
  21. package/dist/{JsonTree-F27RMYSI.cjs.map → JsonTree-55625VVH.mjs.map} +1 -1
  22. package/dist/JsonTree-DCM5QGWF.cjs +11 -0
  23. package/dist/{JsonTree-QTJYSHCV.mjs.map → JsonTree-DCM5QGWF.cjs.map} +1 -1
  24. package/dist/{LottiePlayer.client-6WVWDO75.cjs → LottiePlayer.client-2S7ISJ2S.cjs} +6 -6
  25. package/dist/{LottiePlayer.client-6WVWDO75.cjs.map → LottiePlayer.client-2S7ISJ2S.cjs.map} +1 -1
  26. package/dist/{LottiePlayer.client-B4I6WNZM.mjs → LottiePlayer.client-5LDSSJWS.mjs} +4 -4
  27. package/dist/{LottiePlayer.client-B4I6WNZM.mjs.map → LottiePlayer.client-5LDSSJWS.mjs.map} +1 -1
  28. package/dist/{MapContainer-RYG4HPH4.cjs → MapContainer-76YL2JXL.cjs} +8 -8
  29. package/dist/{MapContainer-RYG4HPH4.cjs.map → MapContainer-76YL2JXL.cjs.map} +1 -1
  30. package/dist/{MapContainer-GXQLP5WY.mjs → MapContainer-7HXBI3OH.mjs} +3 -3
  31. package/dist/{MapContainer-GXQLP5WY.mjs.map → MapContainer-7HXBI3OH.mjs.map} +1 -1
  32. package/dist/{Mermaid.client-SXRRI2YW.mjs → Mermaid.client-NL4SVR7F.mjs} +4 -4
  33. package/dist/{Mermaid.client-SXRRI2YW.mjs.map → Mermaid.client-NL4SVR7F.mjs.map} +1 -1
  34. package/dist/{Mermaid.client-W76R5AKJ.cjs → Mermaid.client-NNTI6DFX.cjs} +26 -26
  35. package/dist/{Mermaid.client-W76R5AKJ.cjs.map → Mermaid.client-NNTI6DFX.cjs.map} +1 -1
  36. package/dist/Player-BRV7XTWR.mjs +4 -0
  37. package/dist/{Player-M3GC3VPE.mjs.map → Player-BRV7XTWR.mjs.map} +1 -1
  38. package/dist/Player-PM7F7DD7.cjs +13 -0
  39. package/dist/{Player-ZL2X5LGG.cjs.map → Player-PM7F7DD7.cjs.map} +1 -1
  40. package/dist/{PrettyCode.client-RPDIE5CH.cjs → PrettyCode.client-KOHDVPPN.cjs} +13 -13
  41. package/dist/{PrettyCode.client-RPDIE5CH.cjs.map → PrettyCode.client-KOHDVPPN.cjs.map} +1 -1
  42. package/dist/{PrettyCode.client-SPMTQEG4.mjs → PrettyCode.client-ZGYGKE7G.mjs} +4 -4
  43. package/dist/{PrettyCode.client-SPMTQEG4.mjs.map → PrettyCode.client-ZGYGKE7G.mjs.map} +1 -1
  44. package/dist/TreeRoot-N72OYKXU.cjs +19 -0
  45. package/dist/{TreeRoot-A3J65L6F.mjs.map → TreeRoot-N72OYKXU.cjs.map} +1 -1
  46. package/dist/TreeRoot-VGAIXCUA.mjs +4 -0
  47. package/dist/{TreeRoot-DSK5JILT.cjs.map → TreeRoot-VGAIXCUA.mjs.map} +1 -1
  48. package/dist/chunk-2ZLKZ5VR.mjs +631 -0
  49. package/dist/chunk-2ZLKZ5VR.mjs.map +1 -0
  50. package/dist/{chunk-LFWQ36LJ.mjs → chunk-5G5YBFS6.mjs} +4 -4
  51. package/dist/{chunk-LFWQ36LJ.mjs.map → chunk-5G5YBFS6.mjs.map} +1 -1
  52. package/dist/{chunk-IHAY6FO6.cjs → chunk-5I5QNGUG.cjs} +17 -17
  53. package/dist/{chunk-IHAY6FO6.cjs.map → chunk-5I5QNGUG.cjs.map} +1 -1
  54. package/dist/{chunk-F2CMIIOH.cjs → chunk-76NNDZH6.cjs} +42 -42
  55. package/dist/{chunk-F2CMIIOH.cjs.map → chunk-76NNDZH6.cjs.map} +1 -1
  56. package/dist/chunk-B5AWZOHJ.cjs +649 -0
  57. package/dist/chunk-B5AWZOHJ.cjs.map +1 -0
  58. package/dist/{chunk-KR6B3LVY.mjs → chunk-B6IR5KSC.mjs} +3 -3
  59. package/dist/{chunk-KR6B3LVY.mjs.map → chunk-B6IR5KSC.mjs.map} +1 -1
  60. package/dist/{chunk-5LBDYFWH.mjs → chunk-C6GXVH5J.mjs} +3 -3
  61. package/dist/{chunk-5LBDYFWH.mjs.map → chunk-C6GXVH5J.mjs.map} +1 -1
  62. package/dist/{chunk-4IW7GZFQ.cjs → chunk-FEN5S772.cjs} +74 -48
  63. package/dist/chunk-FEN5S772.cjs.map +1 -0
  64. package/dist/{chunk-2SMCH62O.cjs → chunk-FP2RLYQZ.cjs} +11 -11
  65. package/dist/{chunk-2SMCH62O.cjs.map → chunk-FP2RLYQZ.cjs.map} +1 -1
  66. package/dist/{chunk-MOME6KYD.mjs → chunk-G5IEC7SR.mjs} +3 -3
  67. package/dist/{chunk-MOME6KYD.mjs.map → chunk-G5IEC7SR.mjs.map} +1 -1
  68. package/dist/{chunk-EXGXUK2N.mjs → chunk-GYIO7W7M.mjs} +41 -15
  69. package/dist/chunk-GYIO7W7M.mjs.map +1 -0
  70. package/dist/{chunk-3Z3A7FHA.cjs → chunk-IEEAENLX.cjs} +48 -48
  71. package/dist/{chunk-3Z3A7FHA.cjs.map → chunk-IEEAENLX.cjs.map} +1 -1
  72. package/dist/{chunk-DFTVB66S.cjs → chunk-KNDLV4PI.cjs} +85 -85
  73. package/dist/{chunk-DFTVB66S.cjs.map → chunk-KNDLV4PI.cjs.map} +1 -1
  74. package/dist/{chunk-SSUOENAZ.mjs → chunk-KNEQRUBA.mjs} +3 -3
  75. package/dist/{chunk-SSUOENAZ.mjs.map → chunk-KNEQRUBA.mjs.map} +1 -1
  76. package/dist/chunk-KRETIZU6.mjs +2218 -0
  77. package/dist/chunk-KRETIZU6.mjs.map +1 -0
  78. package/dist/{chunk-CGILA3WO.mjs → chunk-N2XQF2OL.mjs} +5 -3
  79. package/dist/{chunk-CGILA3WO.mjs.map → chunk-N2XQF2OL.mjs.map} +1 -1
  80. package/dist/{chunk-EUADAUBQ.mjs → chunk-N4MZYNR4.mjs} +4 -4
  81. package/dist/{chunk-EUADAUBQ.mjs.map → chunk-N4MZYNR4.mjs.map} +1 -1
  82. package/dist/chunk-NRXYYO5V.cjs +2257 -0
  83. package/dist/chunk-NRXYYO5V.cjs.map +1 -0
  84. package/dist/{chunk-GGKGH5PM.mjs → chunk-OBRSGM64.mjs} +4 -4
  85. package/dist/{chunk-GGKGH5PM.mjs.map → chunk-OBRSGM64.mjs.map} +1 -1
  86. package/dist/{chunk-6JTB2X72.mjs → chunk-ODO4GMW7.mjs} +3 -3
  87. package/dist/{chunk-6JTB2X72.mjs.map → chunk-ODO4GMW7.mjs.map} +1 -1
  88. package/dist/{chunk-WGEGR3DF.cjs → chunk-OLISEQHS.cjs} +5 -2
  89. package/dist/{chunk-WGEGR3DF.cjs.map → chunk-OLISEQHS.cjs.map} +1 -1
  90. package/dist/{chunk-PZKAH7WQ.mjs → chunk-PVAX67JG.mjs} +3 -3
  91. package/dist/{chunk-PZKAH7WQ.mjs.map → chunk-PVAX67JG.mjs.map} +1 -1
  92. package/dist/{chunk-PRPG2T2E.cjs → chunk-QJ6GTUCO.cjs} +6 -6
  93. package/dist/{chunk-PRPG2T2E.cjs.map → chunk-QJ6GTUCO.cjs.map} +1 -1
  94. package/dist/chunk-QW4RBGHN.cjs +961 -0
  95. package/dist/chunk-QW4RBGHN.cjs.map +1 -0
  96. package/dist/{chunk-33AMWFBZ.cjs → chunk-SGP7V2UW.cjs} +15 -15
  97. package/dist/{chunk-33AMWFBZ.cjs.map → chunk-SGP7V2UW.cjs.map} +1 -1
  98. package/dist/{chunk-FX2QFYWF.mjs → chunk-VWQ5WOIL.mjs} +3 -3
  99. package/dist/{chunk-FX2QFYWF.mjs.map → chunk-VWQ5WOIL.mjs.map} +1 -1
  100. package/dist/{chunk-ZLQHUZDU.cjs → chunk-YDPDTOSP.cjs} +139 -139
  101. package/dist/{chunk-ZLQHUZDU.cjs.map → chunk-YDPDTOSP.cjs.map} +1 -1
  102. package/dist/{chunk-77HQWEQ6.cjs → chunk-YW5IVWHQ.cjs} +33 -33
  103. package/dist/{chunk-77HQWEQ6.cjs.map → chunk-YW5IVWHQ.cjs.map} +1 -1
  104. package/dist/{chunk-YXBOAGIM.cjs → chunk-YXZ6GU7H.cjs} +7 -7
  105. package/dist/{chunk-YXBOAGIM.cjs.map → chunk-YXZ6GU7H.cjs.map} +1 -1
  106. package/dist/{chunk-62Y65TGK.mjs → chunk-ZUFTH5IR.mjs} +8 -631
  107. package/dist/chunk-ZUFTH5IR.mjs.map +1 -0
  108. package/dist/components-EHOGXATG.cjs +22 -0
  109. package/dist/{components-5UXYNAKR.cjs.map → components-EHOGXATG.cjs.map} +1 -1
  110. package/dist/components-MQ6DR7TX.cjs +26 -0
  111. package/dist/{components-CFXOEVPN.mjs.map → components-MQ6DR7TX.cjs.map} +1 -1
  112. package/dist/components-XRX7QGLB.mjs +5 -0
  113. package/dist/{components-WYEZL5TE.cjs.map → components-XRX7QGLB.mjs.map} +1 -1
  114. package/dist/components-YATKRWLH.mjs +5 -0
  115. package/dist/{components-ZAGG2PBO.mjs.map → components-YATKRWLH.mjs.map} +1 -1
  116. package/dist/file-icon/index.cjs +6 -6
  117. package/dist/file-icon/index.mjs +1 -1
  118. package/dist/index.cjs +735 -215
  119. package/dist/index.cjs.map +1 -1
  120. package/dist/index.d.cts +972 -39
  121. package/dist/index.d.ts +972 -39
  122. package/dist/index.mjs +387 -31
  123. package/dist/index.mjs.map +1 -1
  124. package/dist/tree/index.cjs +38 -38
  125. package/dist/tree/index.d.cts +2 -2
  126. package/dist/tree/index.d.ts +2 -2
  127. package/dist/tree/index.mjs +3 -3
  128. package/package.json +6 -6
  129. package/src/index.ts +5 -0
  130. package/src/stories/index.ts +3 -1
  131. package/src/tools/Chat/Chat.story.tsx +1006 -0
  132. package/src/tools/Chat/README.md +528 -0
  133. package/src/tools/Chat/components/Attachments.tsx +192 -0
  134. package/src/tools/Chat/components/ChatRoot.tsx +201 -0
  135. package/src/tools/Chat/components/Composer.tsx +134 -0
  136. package/src/tools/Chat/components/EmptyState.tsx +47 -0
  137. package/src/tools/Chat/components/ErrorBanner.tsx +47 -0
  138. package/src/tools/Chat/components/JumpToLatest.tsx +30 -0
  139. package/src/tools/Chat/components/MessageActions.tsx +72 -0
  140. package/src/tools/Chat/components/MessageBubble.tsx +228 -0
  141. package/src/tools/Chat/components/MessageList.tsx +82 -0
  142. package/src/tools/Chat/components/Sources.tsx +55 -0
  143. package/src/tools/Chat/components/StreamingIndicator.tsx +29 -0
  144. package/src/tools/Chat/components/ToolCalls.tsx +172 -0
  145. package/src/tools/Chat/components/index.ts +24 -0
  146. package/src/tools/Chat/config.ts +55 -0
  147. package/src/tools/Chat/context/ChatProvider.tsx +122 -0
  148. package/src/tools/Chat/context/index.ts +9 -0
  149. package/src/tools/Chat/core/audio/audioBus.ts +172 -0
  150. package/src/tools/Chat/core/audio/index.ts +8 -0
  151. package/src/tools/Chat/core/audio/preferences.ts +68 -0
  152. package/src/tools/Chat/core/audio/types.ts +49 -0
  153. package/src/tools/Chat/core/ids.ts +16 -0
  154. package/src/tools/Chat/core/index.ts +5 -0
  155. package/src/tools/Chat/core/markdown.ts +56 -0
  156. package/src/tools/Chat/core/payload-dispatch.ts +54 -0
  157. package/src/tools/Chat/core/persona.ts +35 -0
  158. package/src/tools/Chat/core/reducer.ts +335 -0
  159. package/src/tools/Chat/core/transport/http.ts +167 -0
  160. package/src/tools/Chat/core/transport/index.ts +13 -0
  161. package/src/tools/Chat/core/transport/mock.ts +134 -0
  162. package/src/tools/Chat/core/transport/sse.ts +116 -0
  163. package/src/tools/Chat/core/transport/types.ts +24 -0
  164. package/src/tools/Chat/hooks/index.ts +26 -0
  165. package/src/tools/Chat/hooks/useChat.ts +440 -0
  166. package/src/tools/Chat/hooks/useChatAudio.ts +191 -0
  167. package/src/tools/Chat/hooks/useChatComposer.ts +227 -0
  168. package/src/tools/Chat/hooks/useChatHistory.ts +59 -0
  169. package/src/tools/Chat/hooks/useChatLayout.ts +111 -0
  170. package/src/tools/Chat/hooks/useChatLightbox.ts +34 -0
  171. package/src/tools/Chat/hooks/useChatScroll.ts +132 -0
  172. package/src/tools/Chat/index.ts +158 -0
  173. package/src/tools/Chat/lazy.tsx +14 -0
  174. package/src/tools/Chat/types.ts +237 -0
  175. package/src/tools/Chat/utils/collectImageAttachments.ts +13 -0
  176. package/src/tools/JsonForm/JsonSchemaForm.tsx +32 -1
  177. package/src/tools/Map/README.md +384 -0
  178. package/dist/DocsLayout-CTJINVBM.mjs.map +0 -1
  179. package/dist/DocsLayout-XLDB6CJ2.cjs.map +0 -1
  180. package/dist/JsonSchemaForm-OSPUUUHM.cjs +0 -13
  181. package/dist/JsonSchemaForm-TSLX2GRO.mjs +0 -4
  182. package/dist/JsonTree-F27RMYSI.cjs +0 -11
  183. package/dist/JsonTree-QTJYSHCV.mjs +0 -5
  184. package/dist/Player-M3GC3VPE.mjs +0 -4
  185. package/dist/Player-ZL2X5LGG.cjs +0 -13
  186. package/dist/TreeRoot-A3J65L6F.mjs +0 -4
  187. package/dist/TreeRoot-DSK5JILT.cjs +0 -19
  188. package/dist/chunk-4IW7GZFQ.cjs.map +0 -1
  189. package/dist/chunk-62Y65TGK.mjs.map +0 -1
  190. package/dist/chunk-EXGXUK2N.mjs.map +0 -1
  191. package/dist/chunk-TKSFZHCG.cjs +0 -1597
  192. package/dist/chunk-TKSFZHCG.cjs.map +0 -1
  193. package/dist/components-5UXYNAKR.cjs +0 -22
  194. package/dist/components-CFXOEVPN.mjs +0 -5
  195. package/dist/components-WYEZL5TE.cjs +0 -26
  196. package/dist/components-ZAGG2PBO.mjs +0 -5
@@ -0,0 +1,132 @@
1
+ 'use client';
2
+
3
+ import { type RefObject, useCallback, useEffect, useRef, useState } from 'react';
4
+
5
+ export interface UseChatScrollOptions {
6
+ containerRef: RefObject<HTMLElement | null>;
7
+ bottomRef: RefObject<HTMLElement | null>;
8
+ isStreaming?: boolean;
9
+ /** Distance from bottom (px) considered "at bottom". */
10
+ bottomThresholdPx?: number;
11
+ /** Bump key — increment when a new message arrives so the hook re-evaluates auto-scroll. */
12
+ messagesCount?: number;
13
+ }
14
+
15
+ export interface UseChatScrollReturn {
16
+ isAtBottom: boolean;
17
+ unreadCount: number;
18
+ scrollToBottom: (smooth?: boolean) => void;
19
+ resetUnread: () => void;
20
+ }
21
+
22
+ export function useChatScroll(options: UseChatScrollOptions): UseChatScrollReturn {
23
+ const {
24
+ containerRef,
25
+ bottomRef,
26
+ isStreaming = false,
27
+ bottomThresholdPx = 80,
28
+ messagesCount = 0,
29
+ } = options;
30
+
31
+ const [isAtBottom, setIsAtBottom] = useState(true);
32
+ const [unreadCount, setUnreadCount] = useState(0);
33
+ const lastCountRef = useRef(messagesCount);
34
+ const stickyRef = useRef(true);
35
+ const wasStreamingRef = useRef(isStreaming);
36
+
37
+ const scrollToBottom = useCallback(
38
+ (smooth = false) => {
39
+ const el = containerRef.current;
40
+ if (!el) return;
41
+ el.scrollTo({
42
+ top: el.scrollHeight,
43
+ behavior: smooth ? 'smooth' : 'auto',
44
+ });
45
+ stickyRef.current = true;
46
+ setIsAtBottom(true);
47
+ setUnreadCount(0);
48
+ },
49
+ [containerRef],
50
+ );
51
+
52
+ const resetUnread = useCallback(() => setUnreadCount(0), []);
53
+
54
+ // Track scroll position relative to bottom.
55
+ useEffect(() => {
56
+ const el = containerRef.current;
57
+ if (!el) return;
58
+ const onScroll = () => {
59
+ const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
60
+ const atBottom = distance <= bottomThresholdPx;
61
+ stickyRef.current = atBottom;
62
+ setIsAtBottom(atBottom);
63
+ if (atBottom) setUnreadCount(0);
64
+ };
65
+ onScroll();
66
+ el.addEventListener('scroll', onScroll, { passive: true });
67
+ return () => {
68
+ el.removeEventListener('scroll', onScroll);
69
+ };
70
+ }, [containerRef, bottomThresholdPx]);
71
+
72
+ // Stick to bottom while streaming, and one extra rAF after stream ends so
73
+ // the final layout (markdown re-render, sources/tool-call panels) doesn't
74
+ // push the latest content out of view.
75
+ useEffect(() => {
76
+ const el = containerRef.current;
77
+ if (!el) return;
78
+
79
+ if (isStreaming) {
80
+ wasStreamingRef.current = true;
81
+ if (!stickyRef.current) return;
82
+ let raf = 0;
83
+ const tick = () => {
84
+ if (!stickyRef.current) return;
85
+ el.scrollTop = el.scrollHeight;
86
+ raf = requestAnimationFrame(tick);
87
+ };
88
+ raf = requestAnimationFrame(tick);
89
+ return () => cancelAnimationFrame(raf);
90
+ }
91
+
92
+ // Stream just ended — flush one more scroll on the next two frames so
93
+ // both markdown swap and any post-stream panel insertions are reflected.
94
+ if (wasStreamingRef.current && stickyRef.current) {
95
+ wasStreamingRef.current = false;
96
+ let raf1 = 0;
97
+ let raf2 = 0;
98
+ raf1 = requestAnimationFrame(() => {
99
+ el.scrollTop = el.scrollHeight;
100
+ raf2 = requestAnimationFrame(() => {
101
+ el.scrollTop = el.scrollHeight;
102
+ });
103
+ });
104
+ return () => {
105
+ cancelAnimationFrame(raf1);
106
+ cancelAnimationFrame(raf2);
107
+ };
108
+ }
109
+ wasStreamingRef.current = false;
110
+ return;
111
+ }, [containerRef, isStreaming]);
112
+
113
+ // On message count increase, decide whether to scroll or bump unread.
114
+ useEffect(() => {
115
+ if (messagesCount > lastCountRef.current) {
116
+ if (stickyRef.current) {
117
+ const el = containerRef.current;
118
+ if (el) el.scrollTop = el.scrollHeight;
119
+ } else {
120
+ setUnreadCount((n) => n + (messagesCount - lastCountRef.current));
121
+ }
122
+ }
123
+ lastCountRef.current = messagesCount;
124
+ }, [containerRef, messagesCount]);
125
+
126
+ // Watch bottom sentinel just for symmetry/future hooks.
127
+ useEffect(() => {
128
+ void bottomRef;
129
+ }, [bottomRef]);
130
+
131
+ return { isAtBottom, unreadCount, scrollToBottom, resetUnread };
132
+ }
@@ -0,0 +1,158 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * @djangocfg/ui-tools — Chat
5
+ *
6
+ * Decomposed, transport-agnostic chat. See @dev/@refactoring7-chat/ for design.
7
+ */
8
+
9
+ // Types
10
+ export type {
11
+ ChatRole,
12
+ ChatMessage,
13
+ ChatPersona,
14
+ ChatToolCall,
15
+ ChatAttachment,
16
+ ChatSource,
17
+ ChatDisplayMode,
18
+ ChatUserContext,
19
+ ChatAssistantContext,
20
+ ChatPrefs,
21
+ ChatConfig,
22
+ ChatLabels,
23
+ ChatTransport,
24
+ ChatStreamEvent,
25
+ CreateSessionOptions,
26
+ SessionInfo,
27
+ HistoryPage,
28
+ StreamOptions,
29
+ SendOptions,
30
+ } from './types';
31
+ export { DEFAULT_LABELS } from './types';
32
+
33
+ // Config
34
+ export {
35
+ STORAGE_KEYS,
36
+ CSS_VARS,
37
+ DEFAULT_Z_INDEX,
38
+ LIMITS,
39
+ DEFAULT_SIDEBAR,
40
+ HOTKEYS,
41
+ CHAT_EVENT_NAME,
42
+ type ChatEventDetail,
43
+ } from './config';
44
+
45
+ // Core (pure)
46
+ export {
47
+ reducer,
48
+ initialState,
49
+ createId,
50
+ createTokenBuffer,
51
+ resolvePersona,
52
+ deriveInitials,
53
+ type ChatState,
54
+ type ChatAction,
55
+ type TokenBuffer,
56
+ } from './core';
57
+
58
+ // Transport
59
+ export {
60
+ createHttpTransport,
61
+ createMockTransport,
62
+ parseSSE,
63
+ TransportError,
64
+ type HttpTransportConfig,
65
+ type MockTransportOptions,
66
+ type ParseSSEOptions,
67
+ } from './core/transport';
68
+
69
+ // Hooks
70
+ export {
71
+ useChat,
72
+ useChatComposer,
73
+ useChatScroll,
74
+ useChatHistory,
75
+ useChatLayout,
76
+ useChatAudio,
77
+ type UseChatConfig,
78
+ type UseChatReturn,
79
+ type UseChatComposerOptions,
80
+ type UseChatComposerReturn,
81
+ type UseChatScrollOptions,
82
+ type UseChatScrollReturn,
83
+ type UseChatHistoryOptions,
84
+ type UseChatLayoutConfig,
85
+ type UseChatLayoutReturn,
86
+ } from './hooks';
87
+
88
+ // Audio
89
+ export type {
90
+ ChatAudioEvent,
91
+ ChatAudioSounds,
92
+ ChatAudioConfig,
93
+ UseChatAudioReturn,
94
+ } from './core/audio';
95
+ export { useChatAudioPrefs } from './core/audio';
96
+
97
+ // Tool-call payload dispatcher
98
+ export {
99
+ dispatchToolPayload,
100
+ isPlainObject,
101
+ isLatLng,
102
+ isGeoJSONFeatureCollection,
103
+ isStringValue,
104
+ type ToolPayloadMatcher,
105
+ type ToolPayloadFallback,
106
+ } from './core/payload-dispatch';
107
+
108
+ // Lightbox helpers
109
+ export { useChatLightbox, type UseChatLightboxReturn, type ChatLightboxState } from './hooks';
110
+ export { collectImageAttachments } from './utils/collectImageAttachments';
111
+
112
+ // Context
113
+ export {
114
+ ChatProvider,
115
+ useChatContext,
116
+ useChatContextOptional,
117
+ type ChatContextValue,
118
+ type ChatProviderProps,
119
+ } from './context';
120
+
121
+ // Components
122
+ export {
123
+ ChatRoot,
124
+ MessageList,
125
+ MessageBubble,
126
+ MessageActions,
127
+ Composer,
128
+ Sources,
129
+ ToolCalls,
130
+ Attachments,
131
+ AttachmentsGrid,
132
+ AttachmentsList,
133
+ EmptyState,
134
+ ErrorBanner,
135
+ JumpToLatest,
136
+ StreamingIndicator,
137
+ type ChatRootProps,
138
+ type MessageListProps,
139
+ type MessageBubbleProps,
140
+ type MessageActionsProps,
141
+ type ComposerProps,
142
+ type SourcesProps,
143
+ type ToolCallsProps,
144
+ type ToolPayloadKind,
145
+ type AttachmentsProps,
146
+ type AttachmentsGridProps,
147
+ type AttachmentsListProps,
148
+ type AttachmentRenderer,
149
+ type AttachmentRendererArgs,
150
+ type AttachmentRendererMap,
151
+ type EmptyStateProps,
152
+ type ErrorBannerProps,
153
+ type JumpToLatestProps,
154
+ type StreamingIndicatorProps,
155
+ } from './components';
156
+
157
+ // Lazy preset
158
+ export { LazyChat } from './lazy';
@@ -0,0 +1,14 @@
1
+ 'use client';
2
+
3
+ import { createLazyComponent, LoadingFallback } from '../../components/lazy-wrapper';
4
+ import type { ChatRootProps } from './components/ChatRoot';
5
+
6
+ export const LazyChat = createLazyComponent<ChatRootProps>(
7
+ () => import('./components/ChatRoot').then((m) => ({ default: m.ChatRoot })),
8
+ {
9
+ displayName: 'LazyChat',
10
+ fallback: <LoadingFallback minHeight={320} text="Loading chat…" />,
11
+ },
12
+ );
13
+
14
+ export type { ChatRootProps };
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Public types for the Chat tool.
3
+ *
4
+ * Shapes here are the contract between transport adapters, the reducer,
5
+ * the hooks layer and the components. Keep serializable (no Date, no Map).
6
+ */
7
+
8
+ export type ChatRole = 'user' | 'assistant' | 'system';
9
+
10
+ export interface ChatToolCall {
11
+ id: string;
12
+ name: string;
13
+ input: unknown;
14
+ output?: unknown;
15
+ /** Live buffer accumulated during streaming. Replaced by `output` on completion. */
16
+ streamingText?: string;
17
+ status: 'running' | 'success' | 'error' | 'cancelled';
18
+ startedAt: number;
19
+ endedAt?: number;
20
+ /** Optional grouping label for parallel/fan-out execution. */
21
+ sourceHostname?: string;
22
+ }
23
+
24
+ export interface ChatAttachment {
25
+ id: string;
26
+ type: 'image' | 'file' | 'audio' | 'video';
27
+ url: string;
28
+ thumbnailUrl?: string;
29
+ name?: string;
30
+ mimeType?: string;
31
+ sizeBytes?: number;
32
+ status?: 'uploading' | 'ready' | 'error';
33
+ /** 0..1 while uploading. */
34
+ progress?: number;
35
+ /** Extracted text from images (OCR). */
36
+ ocrText?: string;
37
+ }
38
+
39
+ export interface ChatSource {
40
+ title: string;
41
+ url: string;
42
+ snippet?: string;
43
+ chunkIndex?: number;
44
+ }
45
+
46
+ export interface ChatPersona {
47
+ /** Display name. */
48
+ name?: string;
49
+ /** Avatar image URL. */
50
+ avatarUrl?: string;
51
+ /** Initials fallback when no avatar. Auto-derived from `name` if absent. */
52
+ initials?: string;
53
+ /** Tooltip / aria description. */
54
+ description?: string;
55
+ }
56
+
57
+ export interface ChatMessage {
58
+ id: string;
59
+ role: ChatRole;
60
+ content: string;
61
+ /** epoch ms — serializable. */
62
+ createdAt: number;
63
+ isStreaming?: boolean;
64
+ isError?: boolean;
65
+ /** Bumps on edit so memo keys invalidate. */
66
+ version?: number;
67
+ /** Per-message persona override (multi-user / multi-bot). Falls back to
68
+ * `ChatConfig.user` / `ChatConfig.assistant` when absent. */
69
+ sender?: ChatPersona;
70
+ /** Simple status indicator above message ("Searching knowledge base..."). */
71
+ toolActivity?: string;
72
+ toolCalls?: ChatToolCall[];
73
+ attachments?: ChatAttachment[];
74
+ sources?: ChatSource[];
75
+ tokensIn?: number;
76
+ tokensOut?: number;
77
+ }
78
+
79
+ export type ChatDisplayMode = 'closed' | 'embedded' | 'floating' | 'sidebar' | 'fullscreen';
80
+
81
+ export interface ChatUserContext extends ChatPersona {
82
+ email?: string;
83
+ language?: string;
84
+ role?: string;
85
+ /** Free-form custom data forwarded to transport metadata. */
86
+ custom?: Record<string, unknown>;
87
+ }
88
+
89
+ export interface ChatAssistantContext extends ChatPersona {
90
+ /** Model identifier (gpt-4o, claude-opus, …). */
91
+ model?: string;
92
+ }
93
+
94
+ export interface ChatPrefs {
95
+ /** Submit hotkey for the composer. */
96
+ submitOn?: 'enter' | 'cmd+enter';
97
+ /** UI density. */
98
+ density?: 'comfortable' | 'compact';
99
+ /** Locale forwarded to transport metadata. */
100
+ locale?: string;
101
+ /** Show timestamps on each bubble. */
102
+ showTimestamps?: boolean;
103
+ }
104
+
105
+ export interface ChatConfig {
106
+ /** Window title / aria-label. */
107
+ title?: string;
108
+ /** Composer placeholder. */
109
+ placeholder?: string;
110
+ /** Empty-state greeting. */
111
+ greeting?: string;
112
+ /** Empty-state description. */
113
+ description?: string;
114
+ /** Suggested prompts shown on empty conversation. */
115
+ suggestions?: Array<{ label: string; prompt: string }>;
116
+ /** Locale forwarded to the transport via stream metadata. */
117
+ locale?: string;
118
+ /** Project / chat slug forwarded to the transport. */
119
+ slug?: string;
120
+ /** UI density. Use `prefs.density` for the same effect; this stays for
121
+ * backwards compatibility. */
122
+ density?: 'comfortable' | 'compact';
123
+ /** Identity of the human author. Renders avatar / name on user bubbles
124
+ * and gets stamped on outgoing messages as `message.sender`. */
125
+ user?: ChatUserContext;
126
+ /** Identity of the assistant. Renders avatar / name on assistant bubbles. */
127
+ assistant?: ChatAssistantContext;
128
+ /** UI preferences. */
129
+ prefs?: ChatPrefs;
130
+ /** Visual labels (i18n is the host's job). */
131
+ labels?: Partial<ChatLabels>;
132
+ }
133
+
134
+ export interface ChatLabels {
135
+ send: string;
136
+ cancel: string;
137
+ copy: string;
138
+ regenerate: string;
139
+ edit: string;
140
+ delete: string;
141
+ retry: string;
142
+ newChat: string;
143
+ loadMore: string;
144
+ jumpToLatest: string;
145
+ attach: string;
146
+ voice: string;
147
+ errorGeneric: string;
148
+ cancelledSuffix: string;
149
+ }
150
+
151
+ export const DEFAULT_LABELS: ChatLabels = {
152
+ send: 'Send',
153
+ cancel: 'Stop',
154
+ copy: 'Copy',
155
+ regenerate: 'Regenerate',
156
+ edit: 'Edit',
157
+ delete: 'Delete',
158
+ retry: 'Retry',
159
+ newChat: 'New chat',
160
+ loadMore: 'Load more',
161
+ jumpToLatest: 'Jump to latest',
162
+ attach: 'Attach files',
163
+ voice: 'Voice input',
164
+ errorGeneric: 'Something went wrong',
165
+ cancelledSuffix: '[cancelled]',
166
+ };
167
+
168
+ // ---- Transport ------------------------------------------------------------
169
+
170
+ export interface CreateSessionOptions {
171
+ metadata?: Record<string, unknown>;
172
+ }
173
+
174
+ export interface SessionInfo {
175
+ sessionId: string;
176
+ /** If the server resumed an existing session, it may return prior messages. */
177
+ messages?: ChatMessage[];
178
+ hasMore?: boolean;
179
+ cursor?: string | null;
180
+ resumed?: boolean;
181
+ }
182
+
183
+ export interface HistoryPage {
184
+ messages: ChatMessage[];
185
+ hasMore: boolean;
186
+ nextCursor: string | null;
187
+ }
188
+
189
+ export interface StreamOptions {
190
+ signal: AbortSignal;
191
+ attachments?: ChatAttachment[];
192
+ metadata?: Record<string, unknown>;
193
+ }
194
+
195
+ export interface SendOptions {
196
+ signal?: AbortSignal;
197
+ attachments?: ChatAttachment[];
198
+ metadata?: Record<string, unknown>;
199
+ }
200
+
201
+ export type ChatStreamEvent =
202
+ | { type: 'message_start'; messageId: string; sessionId: string }
203
+ | { type: 'chunk'; delta: string }
204
+ | { type: 'tool_activity'; tool: string; status: string }
205
+ | {
206
+ type: 'tool_call_start';
207
+ toolId: string;
208
+ name: string;
209
+ input: unknown;
210
+ sourceHostname?: string;
211
+ }
212
+ | { type: 'tool_call_delta'; toolId: string; delta: string }
213
+ | {
214
+ type: 'tool_call_end';
215
+ toolId: string;
216
+ output: unknown;
217
+ status: 'success' | 'error';
218
+ }
219
+ | {
220
+ type: 'message_end';
221
+ tokensIn?: number;
222
+ tokensOut?: number;
223
+ sources?: ChatSource[];
224
+ }
225
+ | { type: 'error'; code: string; message: string };
226
+
227
+ export interface ChatTransport {
228
+ createSession(opts?: CreateSessionOptions): Promise<SessionInfo>;
229
+ loadHistory(sessionId: string, cursor?: string | null, limit?: number): Promise<HistoryPage>;
230
+ stream(
231
+ sessionId: string,
232
+ content: string,
233
+ options: StreamOptions,
234
+ ): AsyncGenerator<ChatStreamEvent, void, void>;
235
+ send(sessionId: string, content: string, options?: SendOptions): Promise<ChatMessage>;
236
+ closeSession(sessionId: string): Promise<void>;
237
+ }
@@ -0,0 +1,13 @@
1
+ import type { ChatAttachment, ChatMessage } from '../types';
2
+
3
+ /** Walk the conversation and collect image attachments in chronological order. */
4
+ export function collectImageAttachments(messages: ChatMessage[]): ChatAttachment[] {
5
+ const out: ChatAttachment[] = [];
6
+ for (const m of messages) {
7
+ if (!m.attachments) continue;
8
+ for (const a of m.attachments) {
9
+ if (a.type === 'image') out.push(a);
10
+ }
11
+ }
12
+ return out;
13
+ }
@@ -121,6 +121,37 @@ export function JsonSchemaForm<T = any>(props: JsonSchemaFormProps<T>) {
121
121
  range: SliderWidget, // alias
122
122
  }), []);
123
123
 
124
+ // Validate uiSchema widget references and strip unknown ones so RJSF
125
+ // doesn't crash with "No widget 'xxx' for type 'yyy'".
126
+ const safeUiSchema = useMemo(() => {
127
+ if (!uiSchema) return uiSchema;
128
+ const known = new Set(Object.keys(widgets));
129
+ let dirty = false;
130
+
131
+ function walk(node: unknown): unknown {
132
+ if (!node || typeof node !== 'object') return node;
133
+ if (Array.isArray(node)) return node.map(walk);
134
+ const out: Record<string, unknown> = {};
135
+ for (const [k, v] of Object.entries(node as Record<string, unknown>)) {
136
+ if (k === 'ui:widget' && typeof v === 'string' && !known.has(v)) {
137
+ dirty = true;
138
+ if (isDev) {
139
+ consola.error(
140
+ `[JsonSchemaForm] Unknown widget "${v}" — falling back to default. ` +
141
+ `Available: ${[...known].filter((w) => w[0]?.toLowerCase() === w[0]).join(', ')}`,
142
+ );
143
+ }
144
+ continue; // drop unknown widget so RJSF picks the default
145
+ }
146
+ out[k] = walk(v);
147
+ }
148
+ return out;
149
+ }
150
+
151
+ const cleaned = walk(uiSchema) as UiSchema | undefined;
152
+ return dirty ? cleaned : uiSchema;
153
+ }, [uiSchema, widgets]);
154
+
124
155
  // Memoize templates to prevent recreation on every render
125
156
  const templates = useMemo(() => ({
126
157
  FieldTemplate,
@@ -181,7 +212,7 @@ export function JsonSchemaForm<T = any>(props: JsonSchemaFormProps<T>) {
181
212
  <div className={className} data-jsonform-density={density}>
182
213
  <Form
183
214
  schema={validatedSchema}
184
- uiSchema={uiSchema as UiSchema | undefined}
215
+ uiSchema={safeUiSchema as UiSchema | undefined}
185
216
  formData={normalizedFormData}
186
217
  validator={validator}
187
218
  widgets={widgets}