@djangocfg/ui-tools 2.1.335 → 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 (193) 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-6WMS4CIY.cjs.map → JsonSchemaForm-DD7CLRIG.cjs.map} +1 -1
  18. package/dist/JsonSchemaForm-XKUIVELK.mjs +4 -0
  19. package/dist/{JsonSchemaForm-KX4JT3M4.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-NRKD4F5X.cjs → chunk-FEN5S772.cjs} +36 -36
  63. package/dist/{chunk-NRKD4F5X.cjs.map → chunk-FEN5S772.cjs.map} +1 -1
  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-SE5IERVH.mjs → chunk-GYIO7W7M.mjs} +3 -3
  69. package/dist/{chunk-SE5IERVH.mjs.map → chunk-GYIO7W7M.mjs.map} +1 -1
  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/Map/README.md +384 -0
  177. package/dist/DocsLayout-CTJINVBM.mjs.map +0 -1
  178. package/dist/DocsLayout-XLDB6CJ2.cjs.map +0 -1
  179. package/dist/JsonSchemaForm-6WMS4CIY.cjs +0 -13
  180. package/dist/JsonSchemaForm-KX4JT3M4.mjs +0 -4
  181. package/dist/JsonTree-F27RMYSI.cjs +0 -11
  182. package/dist/JsonTree-QTJYSHCV.mjs +0 -5
  183. package/dist/Player-M3GC3VPE.mjs +0 -4
  184. package/dist/Player-ZL2X5LGG.cjs +0 -13
  185. package/dist/TreeRoot-A3J65L6F.mjs +0 -4
  186. package/dist/TreeRoot-DSK5JILT.cjs +0 -19
  187. package/dist/chunk-62Y65TGK.mjs.map +0 -1
  188. package/dist/chunk-TKSFZHCG.cjs +0 -1597
  189. package/dist/chunk-TKSFZHCG.cjs.map +0 -1
  190. package/dist/components-5UXYNAKR.cjs +0 -22
  191. package/dist/components-CFXOEVPN.mjs +0 -5
  192. package/dist/components-WYEZL5TE.cjs +0 -26
  193. package/dist/components-ZAGG2PBO.mjs +0 -5
@@ -0,0 +1,2218 @@
1
+ import { MarkdownMessage } from './chunk-2ZLKZ5VR.mjs';
2
+ import { __name } from './chunk-N2XQF2OL.mjs';
3
+ import { createContext, forwardRef, memo, useCallback, useReducer, useRef, useEffect, useState, useMemo, useSyncExternalStore, useContext } from 'react';
4
+ import { cn } from '@djangocfg/ui-core/lib';
5
+ import { useLocalStorage, useMediaQuery } from '@djangocfg/ui-core/hooks';
6
+ import { create } from 'zustand';
7
+ import { persist, createJSONStorage } from 'zustand/middleware';
8
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
+ import { Paperclip, Square, Send, File, X, Sparkles, AlertCircle, RefreshCw, ArrowDown, ExternalLink, ChevronDown, ChevronRight, Loader2, Copy, Pencil, Trash } from 'lucide-react';
10
+ import { Button, Textarea, Spinner, Avatar, AvatarImage, AvatarFallback } from '@djangocfg/ui-core/components';
11
+
12
+ // src/tools/Chat/types.ts
13
+ var DEFAULT_LABELS = {
14
+ send: "Send",
15
+ cancel: "Stop",
16
+ copy: "Copy",
17
+ regenerate: "Regenerate",
18
+ edit: "Edit",
19
+ delete: "Delete",
20
+ retry: "Retry",
21
+ newChat: "New chat",
22
+ loadMore: "Load more",
23
+ jumpToLatest: "Jump to latest",
24
+ attach: "Attach files",
25
+ voice: "Voice input",
26
+ errorGeneric: "Something went wrong",
27
+ cancelledSuffix: "[cancelled]"
28
+ };
29
+
30
+ // src/tools/Chat/config.ts
31
+ var STORAGE_KEYS = {
32
+ mode: "djc-chat-mode",
33
+ sidebarWidth: "djc-chat-sidebar-width",
34
+ composerHistory: "djc-chat-composer-history"
35
+ };
36
+ var CSS_VARS = {
37
+ reserve: "--djc-chat-reserve"
38
+ };
39
+ var DEFAULT_Z_INDEX = 9e3;
40
+ var LIMITS = {
41
+ /** Max characters per single message. */
42
+ messageMaxLength: 8e3,
43
+ /** Max attachments per message. */
44
+ attachmentsMax: 10,
45
+ /** Composer history slots. */
46
+ composerHistorySize: 50,
47
+ /** Coalesce stream tokens within this window before dispatching. */
48
+ streamCoalesceMs: 16,
49
+ /** Default history page size. */
50
+ pageSize: 50,
51
+ /** Virtualize list when >= this many messages (host-controlled threshold). */
52
+ virtualizeThreshold: 50,
53
+ /** SSE idle timeout. */
54
+ sseIdleMs: 45e3
55
+ };
56
+ var DEFAULT_SIDEBAR = {
57
+ width: 420,
58
+ min: 320,
59
+ max: 720
60
+ };
61
+ var HOTKEYS = {
62
+ send: "mod+enter",
63
+ cancel: "esc",
64
+ newChat: "mod+shift+n",
65
+ toggleOpen: "mod+/",
66
+ focusComposer: "mod+l"
67
+ };
68
+ var CHAT_EVENT_NAME = "djc:chat:send";
69
+
70
+ // src/tools/Chat/core/reducer.ts
71
+ var initialState = {
72
+ sessionId: null,
73
+ messages: [],
74
+ isLoading: false,
75
+ isStreaming: false,
76
+ isLoadingMore: false,
77
+ hasMore: true,
78
+ oldestCursor: null,
79
+ error: null
80
+ };
81
+ function updateLastStreaming(messages, patch) {
82
+ const idx = findLastStreamingIndex(messages);
83
+ if (idx === -1) return messages;
84
+ const next = messages.slice();
85
+ next[idx] = patch(messages[idx]);
86
+ return next;
87
+ }
88
+ __name(updateLastStreaming, "updateLastStreaming");
89
+ function findLastStreamingIndex(messages) {
90
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
91
+ if (messages[i].isStreaming) return i;
92
+ }
93
+ return -1;
94
+ }
95
+ __name(findLastStreamingIndex, "findLastStreamingIndex");
96
+ function patchMessageById(messages, id, patch) {
97
+ const idx = messages.findIndex((m) => m.id === id);
98
+ if (idx === -1) return messages;
99
+ const next = messages.slice();
100
+ next[idx] = patch(messages[idx]);
101
+ return next;
102
+ }
103
+ __name(patchMessageById, "patchMessageById");
104
+ function reducer(state, action) {
105
+ switch (action.type) {
106
+ case "SESSION_SET":
107
+ return {
108
+ ...state,
109
+ sessionId: action.sessionId,
110
+ messages: action.messages ?? state.messages,
111
+ hasMore: action.hasMore ?? state.hasMore,
112
+ oldestCursor: action.cursor ?? state.oldestCursor,
113
+ error: null
114
+ };
115
+ case "HISTORY_LOAD_START":
116
+ return { ...state, isLoading: true, error: null };
117
+ case "HISTORY_LOAD_DONE":
118
+ return {
119
+ ...state,
120
+ isLoading: false,
121
+ messages: action.messages,
122
+ hasMore: action.hasMore,
123
+ oldestCursor: action.cursor
124
+ };
125
+ case "HISTORY_MORE_START":
126
+ return { ...state, isLoadingMore: true };
127
+ case "HISTORY_MORE_DONE":
128
+ return {
129
+ ...state,
130
+ isLoadingMore: false,
131
+ // Older messages prepend to the top.
132
+ messages: [...action.messages, ...state.messages],
133
+ hasMore: action.hasMore,
134
+ oldestCursor: action.cursor
135
+ };
136
+ case "MESSAGE_USER_ADD":
137
+ return {
138
+ ...state,
139
+ messages: [...state.messages, action.message],
140
+ error: null
141
+ };
142
+ case "STREAM_START": {
143
+ if (state.isStreaming) {
144
+ if (typeof console !== "undefined") {
145
+ console.warn("[chat] STREAM_START while already streaming, ignoring");
146
+ }
147
+ return state;
148
+ }
149
+ const placeholder = {
150
+ id: action.id,
151
+ role: "assistant",
152
+ content: "",
153
+ createdAt: action.createdAt ?? Date.now(),
154
+ isStreaming: true
155
+ };
156
+ return {
157
+ ...state,
158
+ isStreaming: true,
159
+ messages: [...state.messages, placeholder]
160
+ };
161
+ }
162
+ case "STREAM_CHUNK": {
163
+ const messages = updateLastStreaming(state.messages, (m) => ({
164
+ ...m,
165
+ content: m.content + action.delta
166
+ }));
167
+ return { ...state, messages };
168
+ }
169
+ case "STREAM_TOOL_ACTIVITY": {
170
+ const messages = updateLastStreaming(state.messages, (m) => ({
171
+ ...m,
172
+ toolActivity: action.tool
173
+ }));
174
+ return { ...state, messages };
175
+ }
176
+ case "TOOL_CALL_START": {
177
+ const messages = patchMessageById(state.messages, action.messageId, (m) => ({
178
+ ...m,
179
+ toolCalls: [...m.toolCalls ?? [], action.toolCall]
180
+ }));
181
+ return { ...state, messages };
182
+ }
183
+ case "TOOL_CALL_DELTA": {
184
+ const messages = patchMessageById(state.messages, action.messageId, (m) => {
185
+ if (!m.toolCalls) return m;
186
+ return {
187
+ ...m,
188
+ toolCalls: m.toolCalls.map(
189
+ (tc) => tc.id === action.toolId ? { ...tc, streamingText: (tc.streamingText ?? "") + action.delta } : tc
190
+ )
191
+ };
192
+ });
193
+ return { ...state, messages };
194
+ }
195
+ case "TOOL_CALL_END": {
196
+ const messages = patchMessageById(state.messages, action.messageId, (m) => {
197
+ if (!m.toolCalls) return m;
198
+ return {
199
+ ...m,
200
+ toolCalls: m.toolCalls.map(
201
+ (tc) => tc.id === action.toolId ? {
202
+ ...tc,
203
+ output: action.output,
204
+ status: action.status,
205
+ streamingText: void 0,
206
+ endedAt: Date.now()
207
+ } : tc
208
+ )
209
+ };
210
+ });
211
+ return { ...state, messages };
212
+ }
213
+ case "STREAM_DONE": {
214
+ const messages = patchMessageById(state.messages, action.id, (m) => ({
215
+ ...m,
216
+ isStreaming: false,
217
+ tokensIn: action.tokensIn ?? m.tokensIn,
218
+ tokensOut: action.tokensOut ?? m.tokensOut,
219
+ sources: action.sources ?? m.sources
220
+ }));
221
+ return { ...state, isStreaming: false, messages };
222
+ }
223
+ case "STREAM_CANCELLED": {
224
+ const suffix = action.label ?? "[cancelled]";
225
+ const messages = patchMessageById(state.messages, action.id, (m) => ({
226
+ ...m,
227
+ isStreaming: false,
228
+ content: action.partialText + (action.partialText ? `
229
+
230
+ _${suffix}_` : `_${suffix}_`)
231
+ }));
232
+ return { ...state, isStreaming: false, messages };
233
+ }
234
+ case "STREAM_ERROR": {
235
+ const messages = action.id ? patchMessageById(state.messages, action.id, (m) => ({
236
+ ...m,
237
+ isStreaming: false,
238
+ isError: true
239
+ })) : state.messages;
240
+ return { ...state, isStreaming: false, error: action.message, messages };
241
+ }
242
+ case "MESSAGE_EDIT": {
243
+ const messages = patchMessageById(state.messages, action.id, (m) => ({
244
+ ...m,
245
+ content: action.content,
246
+ version: (m.version ?? 0) + 1
247
+ }));
248
+ return { ...state, messages };
249
+ }
250
+ case "MESSAGE_DELETE":
251
+ return {
252
+ ...state,
253
+ messages: state.messages.filter((m) => m.id !== action.id)
254
+ };
255
+ case "MESSAGES_CLEAR":
256
+ return {
257
+ ...state,
258
+ messages: [],
259
+ isStreaming: false,
260
+ error: null
261
+ };
262
+ case "ERROR_SET":
263
+ return { ...state, error: action.error };
264
+ case "ATTACHMENT_PROGRESS": {
265
+ const messages = patchMessageById(state.messages, action.messageId, (m) => {
266
+ if (!m.attachments) return m;
267
+ return {
268
+ ...m,
269
+ attachments: m.attachments.map(
270
+ (a) => a.id === action.attachmentId ? {
271
+ ...a,
272
+ progress: action.progress ?? a.progress,
273
+ status: action.status ?? a.status
274
+ } : a
275
+ )
276
+ };
277
+ });
278
+ return { ...state, messages };
279
+ }
280
+ default: {
281
+ return state;
282
+ }
283
+ }
284
+ }
285
+ __name(reducer, "reducer");
286
+
287
+ // src/tools/Chat/core/ids.ts
288
+ var counter = 0;
289
+ function createId(prefix = "m") {
290
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
291
+ return `${prefix}_${crypto.randomUUID()}`;
292
+ }
293
+ counter += 1;
294
+ return `${prefix}_${Date.now().toString(36)}_${counter.toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
295
+ }
296
+ __name(createId, "createId");
297
+
298
+ // src/tools/Chat/core/markdown.ts
299
+ function createTokenBuffer(onFlush, windowMs = LIMITS.streamCoalesceMs) {
300
+ let pending = "";
301
+ let timer = null;
302
+ let closed = false;
303
+ const flush = /* @__PURE__ */ __name(() => {
304
+ if (timer) {
305
+ clearTimeout(timer);
306
+ timer = null;
307
+ }
308
+ if (pending) {
309
+ const out = pending;
310
+ pending = "";
311
+ onFlush(out);
312
+ }
313
+ }, "flush");
314
+ return {
315
+ push(delta) {
316
+ if (closed || !delta) return;
317
+ pending += delta;
318
+ if (windowMs <= 0) {
319
+ flush();
320
+ return;
321
+ }
322
+ if (timer === null) {
323
+ timer = setTimeout(flush, windowMs);
324
+ }
325
+ },
326
+ flush,
327
+ close() {
328
+ closed = true;
329
+ flush();
330
+ }
331
+ };
332
+ }
333
+ __name(createTokenBuffer, "createTokenBuffer");
334
+
335
+ // src/tools/Chat/hooks/useChat.ts
336
+ function useChat(config) {
337
+ const [state, dispatch] = useReducer(reducer, initialState);
338
+ const stateRef = useRef(state);
339
+ stateRef.current = state;
340
+ const abortRef = useRef(null);
341
+ const lastErrorRef = useRef(null);
342
+ const initRef = useRef(false);
343
+ const streamingMsgIdRef = useRef(null);
344
+ const { transport, autoCreateSession = true, streaming = true, pageSize = LIMITS.pageSize } = config;
345
+ useEffect(() => {
346
+ if (initRef.current) return;
347
+ initRef.current = true;
348
+ let cancelled = false;
349
+ const run = /* @__PURE__ */ __name(async () => {
350
+ try {
351
+ if (config.initialSessionId) {
352
+ dispatch({
353
+ type: "SESSION_SET",
354
+ sessionId: config.initialSessionId
355
+ });
356
+ dispatch({ type: "HISTORY_LOAD_START" });
357
+ const page = await transport.loadHistory(config.initialSessionId, null, pageSize);
358
+ if (cancelled) return;
359
+ dispatch({
360
+ type: "HISTORY_LOAD_DONE",
361
+ messages: page.messages,
362
+ hasMore: page.hasMore,
363
+ cursor: page.nextCursor
364
+ });
365
+ } else if (autoCreateSession) {
366
+ const info = await transport.createSession({ metadata: config.metadata });
367
+ if (cancelled) return;
368
+ dispatch({
369
+ type: "SESSION_SET",
370
+ sessionId: info.sessionId,
371
+ messages: info.messages ?? [],
372
+ hasMore: info.hasMore ?? false,
373
+ cursor: info.cursor ?? null
374
+ });
375
+ }
376
+ } catch (err) {
377
+ const e = err instanceof Error ? err : new Error(String(err));
378
+ lastErrorRef.current = e;
379
+ dispatch({ type: "ERROR_SET", error: e.message });
380
+ config.onError?.(e);
381
+ }
382
+ }, "run");
383
+ void run();
384
+ return () => {
385
+ cancelled = true;
386
+ };
387
+ }, []);
388
+ const consumeStream = useCallback(
389
+ async (sessionId, content, attachments) => {
390
+ const ctrl = new AbortController();
391
+ abortRef.current = ctrl;
392
+ const assistantId = createId("a");
393
+ streamingMsgIdRef.current = assistantId;
394
+ dispatch({ type: "STREAM_START", id: assistantId });
395
+ config.onStreamStart?.(assistantId);
396
+ const tokenBuffer = createTokenBuffer(
397
+ (delta) => dispatch({ type: "STREAM_CHUNK", delta })
398
+ );
399
+ try {
400
+ const iterator = transport.stream(sessionId, content, {
401
+ signal: ctrl.signal,
402
+ attachments,
403
+ metadata: config.metadata
404
+ });
405
+ for await (const ev of iterator) {
406
+ if (ctrl.signal.aborted) break;
407
+ handleEvent(ev);
408
+ }
409
+ tokenBuffer.flush();
410
+ if (stateRef.current.isStreaming) {
411
+ dispatch({ type: "STREAM_DONE", id: assistantId });
412
+ }
413
+ const finalMsg = stateRef.current.messages.find((m) => m.id === assistantId);
414
+ if (finalMsg) config.onMessageEnd?.(finalMsg);
415
+ } catch (err) {
416
+ tokenBuffer.close();
417
+ if (ctrl.signal.aborted) {
418
+ const partial = stateRef.current.messages.find((m) => m.id === assistantId)?.content ?? "";
419
+ dispatch({ type: "STREAM_CANCELLED", id: assistantId, partialText: partial });
420
+ return;
421
+ }
422
+ const e = err instanceof Error ? err : new Error(String(err));
423
+ lastErrorRef.current = e;
424
+ dispatch({ type: "STREAM_ERROR", id: assistantId, message: e.message });
425
+ config.onError?.(e);
426
+ } finally {
427
+ tokenBuffer.close();
428
+ if (abortRef.current === ctrl) abortRef.current = null;
429
+ streamingMsgIdRef.current = null;
430
+ }
431
+ function handleEvent(ev) {
432
+ switch (ev.type) {
433
+ case "message_start":
434
+ ev.messageId;
435
+ return;
436
+ case "chunk":
437
+ tokenBuffer.push(ev.delta);
438
+ return;
439
+ case "tool_activity":
440
+ tokenBuffer.flush();
441
+ dispatch({ type: "STREAM_TOOL_ACTIVITY", tool: ev.tool });
442
+ return;
443
+ case "tool_call_start": {
444
+ tokenBuffer.flush();
445
+ const toolCall = {
446
+ id: ev.toolId,
447
+ name: ev.name,
448
+ input: ev.input,
449
+ status: "running",
450
+ startedAt: Date.now(),
451
+ sourceHostname: ev.sourceHostname
452
+ };
453
+ dispatch({
454
+ type: "TOOL_CALL_START",
455
+ messageId: assistantId,
456
+ toolCall
457
+ });
458
+ return;
459
+ }
460
+ case "tool_call_delta":
461
+ dispatch({
462
+ type: "TOOL_CALL_DELTA",
463
+ messageId: assistantId,
464
+ toolId: ev.toolId,
465
+ delta: ev.delta
466
+ });
467
+ return;
468
+ case "tool_call_end":
469
+ dispatch({
470
+ type: "TOOL_CALL_END",
471
+ messageId: assistantId,
472
+ toolId: ev.toolId,
473
+ output: ev.output,
474
+ status: ev.status
475
+ });
476
+ return;
477
+ case "message_end":
478
+ tokenBuffer.flush();
479
+ dispatch({
480
+ type: "STREAM_DONE",
481
+ id: assistantId,
482
+ tokensIn: ev.tokensIn,
483
+ tokensOut: ev.tokensOut,
484
+ sources: ev.sources
485
+ });
486
+ return;
487
+ case "error":
488
+ tokenBuffer.flush();
489
+ dispatch({
490
+ type: "STREAM_ERROR",
491
+ id: assistantId,
492
+ message: ev.message
493
+ });
494
+ return;
495
+ }
496
+ }
497
+ __name(handleEvent, "handleEvent");
498
+ },
499
+ [transport, config]
500
+ );
501
+ const consumeBuffered = useCallback(
502
+ async (sessionId, content, attachments) => {
503
+ const ctrl = new AbortController();
504
+ abortRef.current = ctrl;
505
+ try {
506
+ const reply = await transport.send(sessionId, content, {
507
+ signal: ctrl.signal,
508
+ attachments,
509
+ metadata: config.metadata
510
+ });
511
+ const placeholderId = createId("a");
512
+ dispatch({ type: "STREAM_START", id: placeholderId });
513
+ config.onStreamStart?.(placeholderId);
514
+ dispatch({ type: "STREAM_CHUNK", delta: reply.content });
515
+ dispatch({ type: "STREAM_DONE", id: placeholderId });
516
+ config.onMessageEnd?.(reply);
517
+ } catch (err) {
518
+ const e = err instanceof Error ? err : new Error(String(err));
519
+ lastErrorRef.current = e;
520
+ dispatch({ type: "STREAM_ERROR", message: e.message });
521
+ config.onError?.(e);
522
+ } finally {
523
+ if (abortRef.current === ctrl) abortRef.current = null;
524
+ }
525
+ },
526
+ [transport, config]
527
+ );
528
+ const sendMessage = useCallback(
529
+ async (content, attachments) => {
530
+ const sessionId = stateRef.current.sessionId;
531
+ if (!sessionId) {
532
+ const e = new Error("No active session");
533
+ lastErrorRef.current = e;
534
+ dispatch({ type: "ERROR_SET", error: e.message });
535
+ config.onError?.(e);
536
+ return;
537
+ }
538
+ if (!content.trim() && !(attachments && attachments.length > 0)) return;
539
+ if (stateRef.current.isStreaming) return;
540
+ const userMsg = {
541
+ id: createId("u"),
542
+ role: "user",
543
+ content,
544
+ createdAt: Date.now(),
545
+ attachments,
546
+ sender: config.userPersona
547
+ };
548
+ dispatch({ type: "MESSAGE_USER_ADD", message: userMsg });
549
+ config.onMessageSent?.(userMsg);
550
+ if (streaming) {
551
+ await consumeStream(sessionId, content, attachments);
552
+ } else {
553
+ await consumeBuffered(sessionId, content, attachments);
554
+ }
555
+ },
556
+ [streaming, consumeStream, consumeBuffered, config]
557
+ );
558
+ const cancelStream = useCallback(() => {
559
+ abortRef.current?.abort();
560
+ }, []);
561
+ const regenerate = useCallback(
562
+ async (messageId) => {
563
+ const messages = stateRef.current.messages;
564
+ let targetUserIdx = -1;
565
+ if (messageId) {
566
+ const idx = messages.findIndex((m) => m.id === messageId);
567
+ if (idx !== -1) {
568
+ targetUserIdx = messages[idx].role === "user" ? idx : findPreviousUserIndex(messages, idx);
569
+ }
570
+ } else {
571
+ targetUserIdx = findLastUserIndex(messages);
572
+ }
573
+ if (targetUserIdx === -1) return;
574
+ const userMsg = messages[targetUserIdx];
575
+ for (let i = messages.length - 1; i > targetUserIdx; i -= 1) {
576
+ dispatch({ type: "MESSAGE_DELETE", id: messages[i].id });
577
+ }
578
+ const sessionId = stateRef.current.sessionId;
579
+ if (!sessionId) return;
580
+ if (streaming) {
581
+ await consumeStream(sessionId, userMsg.content, userMsg.attachments);
582
+ } else {
583
+ await consumeBuffered(sessionId, userMsg.content, userMsg.attachments);
584
+ }
585
+ },
586
+ [streaming, consumeStream, consumeBuffered]
587
+ );
588
+ const editMessage = useCallback(
589
+ async (id, content) => {
590
+ dispatch({ type: "MESSAGE_EDIT", id, content });
591
+ const msg = stateRef.current.messages.find((m) => m.id === id);
592
+ if (msg?.role === "user") {
593
+ await regenerate(id);
594
+ }
595
+ },
596
+ [regenerate]
597
+ );
598
+ const deleteMessage = useCallback((id) => {
599
+ dispatch({ type: "MESSAGE_DELETE", id });
600
+ }, []);
601
+ const clearMessages = useCallback(() => {
602
+ abortRef.current?.abort();
603
+ dispatch({ type: "MESSAGES_CLEAR" });
604
+ }, []);
605
+ const loadMore = useCallback(async () => {
606
+ const sessionId = stateRef.current.sessionId;
607
+ if (!sessionId) return;
608
+ if (stateRef.current.isLoadingMore || !stateRef.current.hasMore) return;
609
+ dispatch({ type: "HISTORY_MORE_START" });
610
+ try {
611
+ const page = await transport.loadHistory(
612
+ sessionId,
613
+ stateRef.current.oldestCursor,
614
+ pageSize
615
+ );
616
+ dispatch({
617
+ type: "HISTORY_MORE_DONE",
618
+ messages: page.messages,
619
+ hasMore: page.hasMore,
620
+ cursor: page.nextCursor
621
+ });
622
+ } catch (err) {
623
+ const e = err instanceof Error ? err : new Error(String(err));
624
+ lastErrorRef.current = e;
625
+ dispatch({ type: "ERROR_SET", error: e.message });
626
+ config.onError?.(e);
627
+ }
628
+ }, [transport, pageSize, config]);
629
+ const newSession = useCallback(async () => {
630
+ abortRef.current?.abort();
631
+ const previous = stateRef.current.sessionId;
632
+ if (previous) {
633
+ try {
634
+ await transport.closeSession(previous);
635
+ } catch {
636
+ }
637
+ }
638
+ dispatch({ type: "MESSAGES_CLEAR" });
639
+ try {
640
+ const info = await transport.createSession({ metadata: config.metadata });
641
+ dispatch({
642
+ type: "SESSION_SET",
643
+ sessionId: info.sessionId,
644
+ messages: info.messages ?? [],
645
+ hasMore: info.hasMore ?? false,
646
+ cursor: info.cursor ?? null
647
+ });
648
+ } catch (err) {
649
+ const e = err instanceof Error ? err : new Error(String(err));
650
+ lastErrorRef.current = e;
651
+ dispatch({ type: "ERROR_SET", error: e.message });
652
+ config.onError?.(e);
653
+ }
654
+ }, [transport, config]);
655
+ return {
656
+ ...state,
657
+ sendMessage,
658
+ cancelStream,
659
+ regenerate,
660
+ editMessage,
661
+ deleteMessage,
662
+ clearMessages,
663
+ loadMore,
664
+ newSession,
665
+ lastError: lastErrorRef.current
666
+ };
667
+ }
668
+ __name(useChat, "useChat");
669
+ function findLastUserIndex(messages) {
670
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
671
+ if (messages[i].role === "user") return i;
672
+ }
673
+ return -1;
674
+ }
675
+ __name(findLastUserIndex, "findLastUserIndex");
676
+ function findPreviousUserIndex(messages, from) {
677
+ for (let i = from - 1; i >= 0; i -= 1) {
678
+ if (messages[i].role === "user") return i;
679
+ }
680
+ return -1;
681
+ }
682
+ __name(findPreviousUserIndex, "findPreviousUserIndex");
683
+ var DEFAULT_MOBILE_QUERY = "(max-width: 640px)";
684
+ function useChatLayout(config = {}) {
685
+ const {
686
+ defaultMode = "closed",
687
+ storageKey = STORAGE_KEYS.mode,
688
+ sidebarStorageKey = STORAGE_KEYS.sidebarWidth,
689
+ reserveCssVar = CSS_VARS.reserve,
690
+ defaultSidebarWidth = DEFAULT_SIDEBAR.width,
691
+ minSidebarWidth = DEFAULT_SIDEBAR.min,
692
+ maxSidebarWidth = DEFAULT_SIDEBAR.max,
693
+ mobileQuery = DEFAULT_MOBILE_QUERY
694
+ } = config;
695
+ const [mode, setMode] = useLocalStorage(storageKey, defaultMode);
696
+ const [sidebarWidthRaw, setSidebarWidthRaw] = useLocalStorage(
697
+ sidebarStorageKey,
698
+ defaultSidebarWidth
699
+ );
700
+ const isMobile = useMediaQuery(mobileQuery);
701
+ const setSidebarWidth = useCallback(
702
+ (w) => {
703
+ const clamped = Math.max(minSidebarWidth, Math.min(maxSidebarWidth, w));
704
+ setSidebarWidthRaw(clamped);
705
+ },
706
+ [setSidebarWidthRaw, minSidebarWidth, maxSidebarWidth]
707
+ );
708
+ const [previousOpenMode, setPreviousOpenMode] = useState(
709
+ mode === "closed" ? "embedded" : mode
710
+ );
711
+ useEffect(() => {
712
+ if (mode !== "closed") setPreviousOpenMode(mode);
713
+ }, [mode]);
714
+ const open = useCallback(() => {
715
+ setMode(previousOpenMode === "closed" ? "embedded" : previousOpenMode);
716
+ }, [setMode, previousOpenMode]);
717
+ const close = useCallback(() => setMode("closed"), [setMode]);
718
+ const toggle = useCallback(() => {
719
+ setMode(mode === "closed" ? previousOpenMode : "closed");
720
+ }, [setMode, mode, previousOpenMode]);
721
+ const effectiveMode = useMemo(() => {
722
+ if (mode === "closed") return "closed";
723
+ if (isMobile && (mode === "sidebar" || mode === "floating")) return "fullscreen";
724
+ return mode;
725
+ }, [mode, isMobile]);
726
+ useEffect(() => {
727
+ if (typeof document === "undefined") return;
728
+ const root = document.documentElement;
729
+ if (effectiveMode === "sidebar") {
730
+ root.style.setProperty(reserveCssVar, `${sidebarWidthRaw}px`);
731
+ } else {
732
+ root.style.removeProperty(reserveCssVar);
733
+ }
734
+ return () => {
735
+ root.style.removeProperty(reserveCssVar);
736
+ };
737
+ }, [effectiveMode, sidebarWidthRaw, reserveCssVar]);
738
+ return {
739
+ mode,
740
+ setMode,
741
+ open,
742
+ close,
743
+ toggle,
744
+ sidebarWidth: sidebarWidthRaw,
745
+ setSidebarWidth,
746
+ isMobile,
747
+ effectiveMode
748
+ };
749
+ }
750
+ __name(useChatLayout, "useChatLayout");
751
+
752
+ // src/tools/Chat/core/audio/audioBus.ts
753
+ var unlocked = false;
754
+ var unlockListeners = /* @__PURE__ */ new Set();
755
+ function setUnlocked(value) {
756
+ if (unlocked === value) return;
757
+ unlocked = value;
758
+ for (const cb of unlockListeners) cb(value);
759
+ }
760
+ __name(setUnlocked, "setUnlocked");
761
+ function createAudioBus(options) {
762
+ if (typeof window === "undefined") {
763
+ return noopBus();
764
+ }
765
+ let sounds = options.sounds;
766
+ const cache = /* @__PURE__ */ new Map();
767
+ const getOrCreate = /* @__PURE__ */ __name((url) => {
768
+ const hit = cache.get(url);
769
+ if (hit) return hit;
770
+ const el = new Audio(url);
771
+ el.preload = "auto";
772
+ el.crossOrigin = "anonymous";
773
+ cache.set(url, el);
774
+ return el;
775
+ }, "getOrCreate");
776
+ const resolveUrl = /* @__PURE__ */ __name((event) => {
777
+ const v = sounds[event];
778
+ if (!v) return null;
779
+ return v;
780
+ }, "resolveUrl");
781
+ const play = /* @__PURE__ */ __name((event) => {
782
+ if (options.getMuted()) return;
783
+ if (!options.isEnabled(event)) return;
784
+ const url = resolveUrl(event);
785
+ if (!url) return;
786
+ getOrCreate(url);
787
+ const fresh = new Audio(url);
788
+ fresh.preload = "auto";
789
+ fresh.volume = options.getVolume();
790
+ const p = fresh.play();
791
+ if (p && typeof p.catch === "function") {
792
+ p.catch(() => {
793
+ });
794
+ }
795
+ }, "play");
796
+ const preload = /* @__PURE__ */ __name((event) => {
797
+ const url = resolveUrl(event);
798
+ if (!url) return;
799
+ const el = getOrCreate(url);
800
+ try {
801
+ el.load();
802
+ } catch {
803
+ }
804
+ }, "preload");
805
+ const unlock = /* @__PURE__ */ __name(() => {
806
+ if (unlocked) return;
807
+ for (const el of cache.values()) {
808
+ const wasMuted = el.muted;
809
+ el.muted = true;
810
+ const p = el.play();
811
+ if (p && typeof p.then === "function") {
812
+ p.then(() => {
813
+ el.pause();
814
+ el.currentTime = 0;
815
+ el.muted = wasMuted;
816
+ }).catch(() => {
817
+ el.muted = wasMuted;
818
+ });
819
+ } else {
820
+ el.pause();
821
+ el.muted = wasMuted;
822
+ }
823
+ }
824
+ setUnlocked(true);
825
+ }, "unlock");
826
+ return {
827
+ play,
828
+ preload,
829
+ unlock,
830
+ isUnlocked: /* @__PURE__ */ __name(() => unlocked, "isUnlocked"),
831
+ subscribeUnlock(cb) {
832
+ unlockListeners.add(cb);
833
+ return () => unlockListeners.delete(cb);
834
+ },
835
+ setSounds(next) {
836
+ sounds = next;
837
+ },
838
+ dispose() {
839
+ cache.clear();
840
+ }
841
+ };
842
+ }
843
+ __name(createAudioBus, "createAudioBus");
844
+ function noopBus() {
845
+ return {
846
+ play: /* @__PURE__ */ __name(() => void 0, "play"),
847
+ preload: /* @__PURE__ */ __name(() => void 0, "preload"),
848
+ unlock: /* @__PURE__ */ __name(() => void 0, "unlock"),
849
+ isUnlocked: /* @__PURE__ */ __name(() => false, "isUnlocked"),
850
+ subscribeUnlock: /* @__PURE__ */ __name(() => () => void 0, "subscribeUnlock"),
851
+ setSounds: /* @__PURE__ */ __name(() => void 0, "setSounds"),
852
+ dispose: /* @__PURE__ */ __name(() => void 0, "dispose")
853
+ };
854
+ }
855
+ __name(noopBus, "noopBus");
856
+ var STORAGE_KEY = "djangocfg-chat-audio:prefs";
857
+ var clamp01 = /* @__PURE__ */ __name((v) => {
858
+ if (!Number.isFinite(v)) return 1;
859
+ return v < 0 ? 0 : v > 1 ? 1 : v;
860
+ }, "clamp01");
861
+ var useChatAudioPrefs = create()(
862
+ persist(
863
+ (set) => ({
864
+ volume: 1,
865
+ muted: false,
866
+ enabled: {},
867
+ setVolume: /* @__PURE__ */ __name((v) => set({ volume: clamp01(v) }), "setVolume"),
868
+ setMuted: /* @__PURE__ */ __name((m) => set({ muted: !!m }), "setMuted"),
869
+ setEventEnabled: /* @__PURE__ */ __name((event, enabled) => set((s) => ({ enabled: { ...s.enabled, [event]: enabled } })), "setEventEnabled")
870
+ }),
871
+ {
872
+ name: STORAGE_KEY,
873
+ storage: createJSONStorage(() => {
874
+ if (typeof window === "undefined") {
875
+ return {
876
+ getItem: /* @__PURE__ */ __name(() => null, "getItem"),
877
+ setItem: /* @__PURE__ */ __name(() => void 0, "setItem"),
878
+ removeItem: /* @__PURE__ */ __name(() => void 0, "removeItem")
879
+ };
880
+ }
881
+ return window.localStorage;
882
+ }),
883
+ partialize: /* @__PURE__ */ __name((s) => ({ volume: s.volume, muted: s.muted, enabled: s.enabled }), "partialize"),
884
+ version: 1
885
+ }
886
+ )
887
+ );
888
+
889
+ // src/tools/Chat/hooks/useChatAudio.ts
890
+ var ALL_EVENTS = [
891
+ "messageSent",
892
+ "messageReceived",
893
+ "streamStart",
894
+ "error",
895
+ "mention",
896
+ "notification"
897
+ ];
898
+ function readReducedMotion() {
899
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
900
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
901
+ }
902
+ __name(readReducedMotion, "readReducedMotion");
903
+ function readReducedData() {
904
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
905
+ return window.matchMedia("(prefers-reduced-data: reduce)").matches;
906
+ }
907
+ __name(readReducedData, "readReducedData");
908
+ function readVisibilityHidden() {
909
+ if (typeof document === "undefined") return false;
910
+ return document.visibilityState === "hidden";
911
+ }
912
+ __name(readVisibilityHidden, "readVisibilityHidden");
913
+ function useChatAudio(config = {}) {
914
+ const {
915
+ sounds = {},
916
+ volume: volumeOverride,
917
+ muted: mutedOverride,
918
+ shouldPlay,
919
+ respectReducedMotion = true,
920
+ respectReducedData = true,
921
+ muteWhenHidden = true
922
+ } = config;
923
+ const volume = useChatAudioPrefs((s) => volumeOverride != null ? volumeOverride : s.volume);
924
+ const mutedPersisted = useChatAudioPrefs((s) => s.muted);
925
+ const muted = mutedOverride != null ? mutedOverride : mutedPersisted;
926
+ const enabledMap = useChatAudioPrefs((s) => s.enabled);
927
+ const setVolumePref = useChatAudioPrefs((s) => s.setVolume);
928
+ const setMutedPref = useChatAudioPrefs((s) => s.setMuted);
929
+ const setEventEnabledPref = useChatAudioPrefs((s) => s.setEventEnabled);
930
+ const volumeRef = useRef(volume);
931
+ volumeRef.current = volume;
932
+ const mutedRef = useRef(muted);
933
+ mutedRef.current = muted;
934
+ const enabledRef = useRef(enabledMap);
935
+ enabledRef.current = enabledMap;
936
+ const reducedMotionRef = useRef(readReducedMotion());
937
+ const reducedDataRef = useRef(readReducedData());
938
+ const hiddenRef = useRef(readVisibilityHidden());
939
+ const shouldPlayRef = useRef(shouldPlay);
940
+ shouldPlayRef.current = shouldPlay;
941
+ useEffect(() => {
942
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
943
+ const mqMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
944
+ const mqData = window.matchMedia("(prefers-reduced-data: reduce)");
945
+ const onMotion = /* @__PURE__ */ __name(() => {
946
+ reducedMotionRef.current = mqMotion.matches;
947
+ }, "onMotion");
948
+ const onData = /* @__PURE__ */ __name(() => {
949
+ reducedDataRef.current = mqData.matches;
950
+ }, "onData");
951
+ mqMotion.addEventListener("change", onMotion);
952
+ mqData.addEventListener("change", onData);
953
+ return () => {
954
+ mqMotion.removeEventListener("change", onMotion);
955
+ mqData.removeEventListener("change", onData);
956
+ };
957
+ }, []);
958
+ useEffect(() => {
959
+ if (!muteWhenHidden || typeof document === "undefined") return;
960
+ const onVis = /* @__PURE__ */ __name(() => {
961
+ hiddenRef.current = document.visibilityState === "hidden";
962
+ }, "onVis");
963
+ document.addEventListener("visibilitychange", onVis);
964
+ return () => document.removeEventListener("visibilitychange", onVis);
965
+ }, [muteWhenHidden]);
966
+ const busRef = useRef(null);
967
+ if (busRef.current === null) {
968
+ busRef.current = createAudioBus({
969
+ sounds,
970
+ getVolume: /* @__PURE__ */ __name(() => volumeRef.current, "getVolume"),
971
+ getMuted: /* @__PURE__ */ __name(() => effectiveMuted(), "getMuted"),
972
+ isEnabled: /* @__PURE__ */ __name((event) => isEnabledImpl(event), "isEnabled")
973
+ });
974
+ }
975
+ function effectiveMuted() {
976
+ if (mutedRef.current) return true;
977
+ if (muteWhenHidden && hiddenRef.current) return true;
978
+ if (respectReducedMotion && reducedMotionRef.current) return true;
979
+ if (respectReducedData && reducedDataRef.current) return true;
980
+ return false;
981
+ }
982
+ __name(effectiveMuted, "effectiveMuted");
983
+ function isEnabledImpl(event) {
984
+ if (shouldPlayRef.current && shouldPlayRef.current(event) === false) return false;
985
+ const flag = enabledRef.current[event];
986
+ if (flag === false) return false;
987
+ return true;
988
+ }
989
+ __name(isEnabledImpl, "isEnabledImpl");
990
+ useEffect(() => {
991
+ busRef.current?.setSounds(sounds);
992
+ }, [sounds]);
993
+ useEffect(() => {
994
+ const bus = busRef.current;
995
+ if (!bus) return;
996
+ for (const event of ALL_EVENTS) {
997
+ bus.preload(event);
998
+ }
999
+ }, [sounds]);
1000
+ useEffect(() => {
1001
+ const bus = busRef.current;
1002
+ return () => {
1003
+ bus?.dispose();
1004
+ };
1005
+ }, []);
1006
+ const isUnlocked = useSyncExternalStore(
1007
+ useCallback((cb) => busRef.current?.subscribeUnlock(cb) ?? (() => void 0), []),
1008
+ () => busRef.current?.isUnlocked() ?? false,
1009
+ () => false
1010
+ );
1011
+ const play = useCallback((event) => {
1012
+ busRef.current?.play(event);
1013
+ }, []);
1014
+ const preload = useCallback((event) => {
1015
+ busRef.current?.preload(event);
1016
+ }, []);
1017
+ const unlock = useCallback(() => {
1018
+ busRef.current?.unlock();
1019
+ }, []);
1020
+ const isEventEnabled = useCallback(
1021
+ (event) => isEnabledImpl(event),
1022
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1023
+ []
1024
+ );
1025
+ const api = useMemo(
1026
+ () => ({
1027
+ play,
1028
+ preload,
1029
+ unlock,
1030
+ isUnlocked,
1031
+ muted,
1032
+ setMuted: /* @__PURE__ */ __name((m) => setMutedPref(m), "setMuted"),
1033
+ volume,
1034
+ setVolume: /* @__PURE__ */ __name((v) => setVolumePref(v), "setVolume"),
1035
+ isEventEnabled,
1036
+ setEventEnabled: /* @__PURE__ */ __name((event, enabled) => setEventEnabledPref(event, enabled), "setEventEnabled")
1037
+ }),
1038
+ [play, preload, unlock, isUnlocked, muted, volume, isEventEnabled, setMutedPref, setVolumePref, setEventEnabledPref]
1039
+ );
1040
+ return api;
1041
+ }
1042
+ __name(useChatAudio, "useChatAudio");
1043
+ var Ctx = createContext(null);
1044
+ function ChatProvider({
1045
+ transport,
1046
+ config = {},
1047
+ initialSessionId,
1048
+ autoCreateSession,
1049
+ streaming,
1050
+ audio,
1051
+ children
1052
+ }) {
1053
+ const audioApi = useChatAudio(audio ?? {});
1054
+ const audioRef = useRef(audioApi);
1055
+ audioRef.current = audioApi;
1056
+ const onMessageSent = useCallback(() => audioRef.current.play("messageSent"), []);
1057
+ const onMessageEnd = useCallback(() => audioRef.current.play("messageReceived"), []);
1058
+ const onStreamStart = useCallback(() => audioRef.current.play("streamStart"), []);
1059
+ const onError = useCallback(() => audioRef.current.play("error"), []);
1060
+ const chat = useChat({
1061
+ transport,
1062
+ initialSessionId,
1063
+ autoCreateSession,
1064
+ streaming,
1065
+ metadata: {
1066
+ locale: config.locale ?? config.prefs?.locale,
1067
+ slug: config.slug
1068
+ },
1069
+ userPersona: config.user,
1070
+ onMessageSent,
1071
+ onMessageEnd,
1072
+ onStreamStart,
1073
+ onError
1074
+ });
1075
+ const layout = useChatLayout({ defaultMode: "embedded" });
1076
+ const rootRef = useRef(null);
1077
+ useEffect(() => {
1078
+ if (audioApi.isUnlocked) return;
1079
+ const root = rootRef.current;
1080
+ if (!root) return;
1081
+ const handler = /* @__PURE__ */ __name(() => {
1082
+ audioApi.unlock();
1083
+ }, "handler");
1084
+ root.addEventListener("pointerdown", handler, { once: true, capture: true });
1085
+ root.addEventListener("keydown", handler, { once: true, capture: true });
1086
+ return () => {
1087
+ root.removeEventListener("pointerdown", handler, { capture: true });
1088
+ root.removeEventListener("keydown", handler, { capture: true });
1089
+ };
1090
+ }, [audioApi]);
1091
+ const labels = useMemo(
1092
+ () => ({ ...DEFAULT_LABELS, ...config.labels ?? {} }),
1093
+ [config.labels]
1094
+ );
1095
+ const value = useMemo(
1096
+ () => ({ ...chat, layout, config, labels, audio: audioApi }),
1097
+ [chat, layout, config, labels, audioApi]
1098
+ );
1099
+ return /* @__PURE__ */ jsx(Ctx.Provider, { value, children: /* @__PURE__ */ jsx("div", { ref: rootRef, style: { display: "contents" }, children }) });
1100
+ }
1101
+ __name(ChatProvider, "ChatProvider");
1102
+ function useChatContext() {
1103
+ const v = useContext(Ctx);
1104
+ if (!v) throw new Error("useChatContext must be used inside <ChatProvider>");
1105
+ return v;
1106
+ }
1107
+ __name(useChatContext, "useChatContext");
1108
+ function useChatContextOptional() {
1109
+ return useContext(Ctx);
1110
+ }
1111
+ __name(useChatContextOptional, "useChatContextOptional");
1112
+ var MAX_TEXTAREA_HEIGHT = 240;
1113
+ function useChatComposer(options) {
1114
+ const {
1115
+ onSubmit,
1116
+ initialValue = "",
1117
+ maxLength = LIMITS.messageMaxLength,
1118
+ maxAttachments = LIMITS.attachmentsMax,
1119
+ disabled = false,
1120
+ submitOn = "enter",
1121
+ history = { enabled: true, size: LIMITS.composerHistorySize },
1122
+ onPasteFiles
1123
+ } = options;
1124
+ const [value, setValueState] = useState(initialValue);
1125
+ const [attachments, setAttachments] = useState([]);
1126
+ const [isSubmitting, setIsSubmitting] = useState(false);
1127
+ const textareaRef = useRef(null);
1128
+ const historyRef = useRef({ items: [], index: -1 });
1129
+ const setValue = useCallback(
1130
+ (next) => {
1131
+ setValueState(next.length > maxLength ? next.slice(0, maxLength) : next);
1132
+ },
1133
+ [maxLength]
1134
+ );
1135
+ useEffect(() => {
1136
+ const el = textareaRef.current;
1137
+ if (!el) return;
1138
+ el.style.height = "auto";
1139
+ el.style.height = `${Math.min(el.scrollHeight, MAX_TEXTAREA_HEIGHT)}px`;
1140
+ }, [value]);
1141
+ const reset = useCallback(() => {
1142
+ setValueState("");
1143
+ setAttachments([]);
1144
+ historyRef.current.index = -1;
1145
+ }, []);
1146
+ const focus = useCallback(() => {
1147
+ requestAnimationFrame(() => textareaRef.current?.focus());
1148
+ }, []);
1149
+ const submit = useCallback(async () => {
1150
+ const trimmed = value.trim();
1151
+ if (!trimmed && attachments.length === 0 || isSubmitting || disabled) return;
1152
+ setIsSubmitting(true);
1153
+ try {
1154
+ if (history.enabled !== false && trimmed) {
1155
+ const buf = historyRef.current.items;
1156
+ if (buf[buf.length - 1] !== trimmed) {
1157
+ buf.push(trimmed);
1158
+ if (buf.length > (history.size ?? LIMITS.composerHistorySize)) buf.shift();
1159
+ }
1160
+ historyRef.current.index = -1;
1161
+ }
1162
+ const snapshot = [...attachments];
1163
+ const text = value;
1164
+ reset();
1165
+ await onSubmit(text, snapshot);
1166
+ } finally {
1167
+ setIsSubmitting(false);
1168
+ }
1169
+ }, [value, attachments, isSubmitting, disabled, history, onSubmit, reset]);
1170
+ const addAttachment = useCallback(
1171
+ (a) => {
1172
+ setAttachments((prev) => {
1173
+ if (prev.some((p) => p.id === a.id)) {
1174
+ return prev.map((p) => p.id === a.id ? a : p);
1175
+ }
1176
+ if (prev.length >= maxAttachments) return prev;
1177
+ return [...prev, a];
1178
+ });
1179
+ },
1180
+ [maxAttachments]
1181
+ );
1182
+ const removeAttachment = useCallback((id) => {
1183
+ setAttachments((prev) => prev.filter((a) => a.id !== id));
1184
+ }, []);
1185
+ const recallPrevious = useCallback(() => {
1186
+ const { items } = historyRef.current;
1187
+ if (!items.length) return;
1188
+ const next = historyRef.current.index < 0 ? items.length - 1 : Math.max(0, historyRef.current.index - 1);
1189
+ historyRef.current.index = next;
1190
+ setValueState(items[next]);
1191
+ }, []);
1192
+ const recallNext = useCallback(() => {
1193
+ const { items } = historyRef.current;
1194
+ if (!items.length || historyRef.current.index < 0) return;
1195
+ const next = historyRef.current.index + 1;
1196
+ if (next >= items.length) {
1197
+ historyRef.current.index = -1;
1198
+ setValueState("");
1199
+ return;
1200
+ }
1201
+ historyRef.current.index = next;
1202
+ setValueState(items[next]);
1203
+ }, []);
1204
+ const onChange = useCallback(
1205
+ (e) => {
1206
+ setValue(e.target.value);
1207
+ },
1208
+ [setValue]
1209
+ );
1210
+ const onKeyDown = useCallback(
1211
+ (e) => {
1212
+ if (e.key === "Enter") {
1213
+ const isCmd = e.metaKey || e.ctrlKey;
1214
+ const shouldSend = submitOn === "cmd+enter" ? isCmd : !e.shiftKey;
1215
+ if (shouldSend) {
1216
+ e.preventDefault();
1217
+ void submit();
1218
+ }
1219
+ return;
1220
+ }
1221
+ if (history.enabled !== false && value === "" && attachments.length === 0) {
1222
+ if (e.key === "ArrowUp") {
1223
+ e.preventDefault();
1224
+ recallPrevious();
1225
+ } else if (e.key === "ArrowDown" && historyRef.current.index >= 0) {
1226
+ e.preventDefault();
1227
+ recallNext();
1228
+ }
1229
+ }
1230
+ },
1231
+ [submitOn, submit, history, value, attachments.length, recallPrevious, recallNext]
1232
+ );
1233
+ const onPaste = useCallback(
1234
+ (e) => {
1235
+ const files = Array.from(e.clipboardData?.files ?? []);
1236
+ if (files.length && onPasteFiles) {
1237
+ e.preventDefault();
1238
+ onPasteFiles(files);
1239
+ }
1240
+ },
1241
+ [onPasteFiles]
1242
+ );
1243
+ const canSubmit = !disabled && !isSubmitting && (value.trim().length > 0 || attachments.length > 0);
1244
+ return {
1245
+ value,
1246
+ setValue,
1247
+ attachments,
1248
+ addAttachment,
1249
+ removeAttachment,
1250
+ isSubmitting,
1251
+ canSubmit,
1252
+ submit,
1253
+ reset,
1254
+ focus,
1255
+ textareaRef,
1256
+ textareaProps: {
1257
+ ref: textareaRef,
1258
+ value,
1259
+ disabled,
1260
+ onChange,
1261
+ onKeyDown,
1262
+ onPaste
1263
+ },
1264
+ recallPrevious,
1265
+ recallNext
1266
+ };
1267
+ }
1268
+ __name(useChatComposer, "useChatComposer");
1269
+ function useChatScroll(options) {
1270
+ const {
1271
+ containerRef,
1272
+ bottomRef,
1273
+ isStreaming = false,
1274
+ bottomThresholdPx = 80,
1275
+ messagesCount = 0
1276
+ } = options;
1277
+ const [isAtBottom, setIsAtBottom] = useState(true);
1278
+ const [unreadCount, setUnreadCount] = useState(0);
1279
+ const lastCountRef = useRef(messagesCount);
1280
+ const stickyRef = useRef(true);
1281
+ const wasStreamingRef = useRef(isStreaming);
1282
+ const scrollToBottom = useCallback(
1283
+ (smooth = false) => {
1284
+ const el = containerRef.current;
1285
+ if (!el) return;
1286
+ el.scrollTo({
1287
+ top: el.scrollHeight,
1288
+ behavior: smooth ? "smooth" : "auto"
1289
+ });
1290
+ stickyRef.current = true;
1291
+ setIsAtBottom(true);
1292
+ setUnreadCount(0);
1293
+ },
1294
+ [containerRef]
1295
+ );
1296
+ const resetUnread = useCallback(() => setUnreadCount(0), []);
1297
+ useEffect(() => {
1298
+ const el = containerRef.current;
1299
+ if (!el) return;
1300
+ const onScroll = /* @__PURE__ */ __name(() => {
1301
+ const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
1302
+ const atBottom = distance <= bottomThresholdPx;
1303
+ stickyRef.current = atBottom;
1304
+ setIsAtBottom(atBottom);
1305
+ if (atBottom) setUnreadCount(0);
1306
+ }, "onScroll");
1307
+ onScroll();
1308
+ el.addEventListener("scroll", onScroll, { passive: true });
1309
+ return () => {
1310
+ el.removeEventListener("scroll", onScroll);
1311
+ };
1312
+ }, [containerRef, bottomThresholdPx]);
1313
+ useEffect(() => {
1314
+ const el = containerRef.current;
1315
+ if (!el) return;
1316
+ if (isStreaming) {
1317
+ wasStreamingRef.current = true;
1318
+ if (!stickyRef.current) return;
1319
+ let raf = 0;
1320
+ const tick = /* @__PURE__ */ __name(() => {
1321
+ if (!stickyRef.current) return;
1322
+ el.scrollTop = el.scrollHeight;
1323
+ raf = requestAnimationFrame(tick);
1324
+ }, "tick");
1325
+ raf = requestAnimationFrame(tick);
1326
+ return () => cancelAnimationFrame(raf);
1327
+ }
1328
+ if (wasStreamingRef.current && stickyRef.current) {
1329
+ wasStreamingRef.current = false;
1330
+ let raf1 = 0;
1331
+ let raf2 = 0;
1332
+ raf1 = requestAnimationFrame(() => {
1333
+ el.scrollTop = el.scrollHeight;
1334
+ raf2 = requestAnimationFrame(() => {
1335
+ el.scrollTop = el.scrollHeight;
1336
+ });
1337
+ });
1338
+ return () => {
1339
+ cancelAnimationFrame(raf1);
1340
+ cancelAnimationFrame(raf2);
1341
+ };
1342
+ }
1343
+ wasStreamingRef.current = false;
1344
+ return;
1345
+ }, [containerRef, isStreaming]);
1346
+ useEffect(() => {
1347
+ if (messagesCount > lastCountRef.current) {
1348
+ if (stickyRef.current) {
1349
+ const el = containerRef.current;
1350
+ if (el) el.scrollTop = el.scrollHeight;
1351
+ } else {
1352
+ setUnreadCount((n) => n + (messagesCount - lastCountRef.current));
1353
+ }
1354
+ }
1355
+ lastCountRef.current = messagesCount;
1356
+ }, [containerRef, messagesCount]);
1357
+ useEffect(() => {
1358
+ }, [bottomRef]);
1359
+ return { isAtBottom, unreadCount, scrollToBottom, resetUnread };
1360
+ }
1361
+ __name(useChatScroll, "useChatScroll");
1362
+ function useChatHistory(options) {
1363
+ const { enabled = true, containerRef, topSentinelRef, hasMore, isLoadingMore, loadMore } = options;
1364
+ const heightBeforeRef = useRef(null);
1365
+ useEffect(() => {
1366
+ if (heightBeforeRef.current == null) return;
1367
+ const el = containerRef.current;
1368
+ if (!el) {
1369
+ heightBeforeRef.current = null;
1370
+ return;
1371
+ }
1372
+ if (!isLoadingMore) {
1373
+ const delta = el.scrollHeight - heightBeforeRef.current;
1374
+ if (delta > 0) {
1375
+ el.scrollTop += delta;
1376
+ }
1377
+ heightBeforeRef.current = null;
1378
+ }
1379
+ }, [containerRef, isLoadingMore]);
1380
+ useEffect(() => {
1381
+ if (!enabled || !hasMore) return;
1382
+ const sentinel = topSentinelRef.current;
1383
+ const root = containerRef.current;
1384
+ if (!sentinel || !root) return;
1385
+ const observer = new IntersectionObserver(
1386
+ (entries) => {
1387
+ const entry = entries[0];
1388
+ if (!entry?.isIntersecting) return;
1389
+ if (isLoadingMore) return;
1390
+ const el = containerRef.current;
1391
+ if (el) heightBeforeRef.current = el.scrollHeight;
1392
+ void loadMore();
1393
+ },
1394
+ { root, threshold: 0, rootMargin: "200px 0px 0px 0px" }
1395
+ );
1396
+ observer.observe(sentinel);
1397
+ return () => observer.disconnect();
1398
+ }, [enabled, hasMore, isLoadingMore, containerRef, topSentinelRef, loadMore]);
1399
+ }
1400
+ __name(useChatHistory, "useChatHistory");
1401
+ function AttachmentsGrid({
1402
+ attachments,
1403
+ maxVisible,
1404
+ onClick,
1405
+ onRemove,
1406
+ isInComposer = false,
1407
+ layout = "wrap",
1408
+ className
1409
+ }) {
1410
+ if (!attachments?.length) return null;
1411
+ const visible = maxVisible ? attachments.slice(0, maxVisible) : attachments;
1412
+ return /* @__PURE__ */ jsx(
1413
+ "div",
1414
+ {
1415
+ className: cn(
1416
+ layout === "grid" ? "grid grid-cols-3 gap-2" : "flex flex-wrap gap-2",
1417
+ className
1418
+ ),
1419
+ children: visible.map((a) => /* @__PURE__ */ jsx(
1420
+ AttachmentTile,
1421
+ {
1422
+ attachment: a,
1423
+ isInComposer,
1424
+ onClick: onClick ? () => onClick(a) : void 0,
1425
+ onRemove: onRemove ? () => onRemove(a) : void 0
1426
+ },
1427
+ a.id
1428
+ ))
1429
+ }
1430
+ );
1431
+ }
1432
+ __name(AttachmentsGrid, "AttachmentsGrid");
1433
+ function AttachmentsList({
1434
+ attachments,
1435
+ maxVisible,
1436
+ onClick,
1437
+ onRemove,
1438
+ renderers,
1439
+ isInComposer = false,
1440
+ className
1441
+ }) {
1442
+ if (!attachments?.length) return null;
1443
+ const visible = maxVisible ? attachments.slice(0, maxVisible) : attachments;
1444
+ return /* @__PURE__ */ jsx("div", { className: cn("flex w-full flex-col gap-2", className), children: visible.map((a) => {
1445
+ const renderer = renderers?.[a.type] ?? renderers?.default;
1446
+ const args = {
1447
+ attachment: a,
1448
+ isInComposer,
1449
+ onClick: onClick ? () => onClick(a) : void 0,
1450
+ onRemove: onRemove ? () => onRemove(a) : void 0
1451
+ };
1452
+ if (renderer) {
1453
+ return /* @__PURE__ */ jsxs("div", { className: "relative w-full min-w-0", children: [
1454
+ renderer(args),
1455
+ args.onRemove ? /* @__PURE__ */ jsx(RemoveBtn, { onRemove: args.onRemove }) : null
1456
+ ] }, a.id);
1457
+ }
1458
+ return /* @__PURE__ */ jsx(AttachmentTile, { ...args }, a.id);
1459
+ }) });
1460
+ }
1461
+ __name(AttachmentsList, "AttachmentsList");
1462
+ function Attachments(props) {
1463
+ const { renderers, layout, ...rest } = props;
1464
+ if (renderers) {
1465
+ return /* @__PURE__ */ jsx(AttachmentsList, { ...rest, renderers });
1466
+ }
1467
+ return /* @__PURE__ */ jsx(AttachmentsGrid, { ...rest, layout: layout === "grid" ? "grid" : "wrap" });
1468
+ }
1469
+ __name(Attachments, "Attachments");
1470
+ function AttachmentTile({ attachment, onClick, onRemove }) {
1471
+ const isImage = attachment.type === "image";
1472
+ const isUploading = attachment.status === "uploading";
1473
+ const inner = isImage ? /* @__PURE__ */ jsx(
1474
+ "img",
1475
+ {
1476
+ src: attachment.thumbnailUrl ?? attachment.url,
1477
+ alt: attachment.name ?? "attachment",
1478
+ className: "h-16 w-16 rounded-md object-cover",
1479
+ loading: "lazy"
1480
+ }
1481
+ ) : /* @__PURE__ */ jsxs("div", { className: "flex max-w-44 items-center gap-2 rounded-md border border-border bg-background/60 px-2 py-1.5 text-xs", children: [
1482
+ /* @__PURE__ */ jsx(File, { "aria-hidden": true, className: "size-4 shrink-0 text-muted-foreground" }),
1483
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: attachment.name ?? "file" })
1484
+ ] });
1485
+ return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1486
+ onClick ? /* @__PURE__ */ jsx("button", { type: "button", onClick, className: "block", children: inner }) : inner,
1487
+ isUploading ? /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center rounded-md bg-background/70 text-[10px] font-medium", children: attachment.progress != null ? `${Math.round(attachment.progress * 100)}%` : "\u2026" }) : null,
1488
+ onRemove ? /* @__PURE__ */ jsx(RemoveBtn, { onRemove }) : null
1489
+ ] });
1490
+ }
1491
+ __name(AttachmentTile, "AttachmentTile");
1492
+ function RemoveBtn({ onRemove }) {
1493
+ return /* @__PURE__ */ jsx(
1494
+ "button",
1495
+ {
1496
+ type: "button",
1497
+ "aria-label": "Remove attachment",
1498
+ onClick: onRemove,
1499
+ className: "absolute -right-1.5 -top-1.5 grid h-4 w-4 place-items-center rounded-full border border-border bg-background text-muted-foreground hover:bg-destructive hover:text-destructive-foreground",
1500
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": true, className: "size-2.5" })
1501
+ }
1502
+ );
1503
+ }
1504
+ __name(RemoveBtn, "RemoveBtn");
1505
+ var Composer = forwardRef(/* @__PURE__ */ __name(function Composer2({
1506
+ composer,
1507
+ placeholder = "Type a message...",
1508
+ disabled,
1509
+ showAttachmentButton = false,
1510
+ onPickFiles,
1511
+ toolbarStart,
1512
+ toolbarEnd,
1513
+ attachmentTray,
1514
+ className,
1515
+ textareaClassName,
1516
+ isStreaming: isStreamingProp,
1517
+ onCancel: onCancelProp
1518
+ }, ref) {
1519
+ const ctx = useChatContextOptional();
1520
+ const isStreaming = isStreamingProp ?? ctx?.isStreaming ?? false;
1521
+ const onCancel = onCancelProp ?? ctx?.cancelStream;
1522
+ const isDisabled = disabled ?? isStreaming;
1523
+ return /* @__PURE__ */ jsxs(
1524
+ "div",
1525
+ {
1526
+ ref,
1527
+ className: cn(
1528
+ "border-t border-border bg-background/95 px-2.5 pt-2 pb-[max(0.5rem,env(safe-area-inset-bottom))]",
1529
+ className
1530
+ ),
1531
+ children: [
1532
+ composer.attachments.length > 0 ? /* @__PURE__ */ jsx("div", { className: "mb-1.5", children: attachmentTray ?? /* @__PURE__ */ jsx(
1533
+ Attachments,
1534
+ {
1535
+ attachments: composer.attachments,
1536
+ onRemove: (a) => composer.removeAttachment(a.id)
1537
+ }
1538
+ ) }) : null,
1539
+ /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-1.5 [&>:not(textarea)]:shrink-0 [&>:not(textarea)]:h-9", children: [
1540
+ showAttachmentButton ? /* @__PURE__ */ jsx(
1541
+ Button,
1542
+ {
1543
+ type: "button",
1544
+ variant: "ghost",
1545
+ size: "icon",
1546
+ onClick: onPickFiles,
1547
+ "aria-label": "Attach files",
1548
+ disabled: isDisabled,
1549
+ className: "h-9 w-9",
1550
+ children: /* @__PURE__ */ jsx(Paperclip, { "aria-hidden": true, className: "size-4" })
1551
+ }
1552
+ ) : null,
1553
+ toolbarStart,
1554
+ /* @__PURE__ */ jsx(
1555
+ Textarea,
1556
+ {
1557
+ ...composer.textareaProps,
1558
+ rows: 1,
1559
+ placeholder,
1560
+ "aria-label": placeholder,
1561
+ "aria-multiline": "true",
1562
+ disabled: isDisabled,
1563
+ className: cn(
1564
+ "min-h-9 max-h-60 flex-1 resize-none rounded-2xl px-3.5 py-2 text-base sm:text-sm",
1565
+ textareaClassName
1566
+ )
1567
+ }
1568
+ ),
1569
+ toolbarEnd,
1570
+ isStreaming ? /* @__PURE__ */ jsx(
1571
+ Button,
1572
+ {
1573
+ type: "button",
1574
+ variant: "secondary",
1575
+ size: "icon",
1576
+ onClick: onCancel,
1577
+ "aria-label": "Stop",
1578
+ "aria-keyshortcuts": "Escape",
1579
+ className: "h-9 w-9",
1580
+ children: /* @__PURE__ */ jsx(Square, { "aria-hidden": true, className: "size-3.5" })
1581
+ }
1582
+ ) : /* @__PURE__ */ jsx(
1583
+ Button,
1584
+ {
1585
+ type: "button",
1586
+ size: "icon",
1587
+ onClick: () => void composer.submit(),
1588
+ disabled: !composer.canSubmit,
1589
+ "aria-label": "Send",
1590
+ "aria-keyshortcuts": "Enter",
1591
+ className: "h-9 w-9",
1592
+ children: /* @__PURE__ */ jsx(Send, { "aria-hidden": true, className: "size-4" })
1593
+ }
1594
+ )
1595
+ ] })
1596
+ ]
1597
+ }
1598
+ );
1599
+ }, "Composer"));
1600
+ function EmptyState({
1601
+ greeting,
1602
+ description,
1603
+ suggestions,
1604
+ onPickSuggestion,
1605
+ className
1606
+ }) {
1607
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center gap-3 px-4 py-12 text-center", className), children: [
1608
+ /* @__PURE__ */ jsx("div", { className: "grid size-10 place-items-center rounded-full bg-muted", children: /* @__PURE__ */ jsx(Sparkles, { "aria-hidden": true, className: "size-5 text-muted-foreground" }) }),
1609
+ greeting ? /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold", children: greeting }) : null,
1610
+ description ? /* @__PURE__ */ jsx("p", { className: "max-w-md text-sm text-muted-foreground", children: description }) : null,
1611
+ suggestions?.length ? /* @__PURE__ */ jsx("div", { className: "mt-2 grid w-full max-w-md grid-cols-1 gap-2 sm:grid-cols-2", children: suggestions.map((s) => /* @__PURE__ */ jsx(
1612
+ "button",
1613
+ {
1614
+ type: "button",
1615
+ onClick: () => onPickSuggestion?.(s.prompt),
1616
+ className: "rounded-lg border border-border bg-background/60 px-3 py-2 text-left text-xs hover:bg-accent",
1617
+ children: s.label
1618
+ },
1619
+ s.prompt
1620
+ )) }) : null
1621
+ ] });
1622
+ }
1623
+ __name(EmptyState, "EmptyState");
1624
+ function ErrorBanner({ error, onDismiss, onRetry, className }) {
1625
+ if (!error) return null;
1626
+ return /* @__PURE__ */ jsxs(
1627
+ "div",
1628
+ {
1629
+ role: "alert",
1630
+ className: cn(
1631
+ "mx-2.5 my-2 flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive",
1632
+ className
1633
+ ),
1634
+ children: [
1635
+ /* @__PURE__ */ jsx(AlertCircle, { "aria-hidden": true, className: "mt-0.5 size-3.5 shrink-0" }),
1636
+ /* @__PURE__ */ jsx("p", { className: "min-w-0 flex-1 break-words", children: error }),
1637
+ onRetry ? /* @__PURE__ */ jsxs(
1638
+ "button",
1639
+ {
1640
+ type: "button",
1641
+ onClick: onRetry,
1642
+ className: "inline-flex items-center gap-1 rounded px-1.5 py-0.5 hover:bg-destructive/15",
1643
+ children: [
1644
+ /* @__PURE__ */ jsx(RefreshCw, { "aria-hidden": true, className: "size-3" }),
1645
+ " Retry"
1646
+ ]
1647
+ }
1648
+ ) : null,
1649
+ onDismiss ? /* @__PURE__ */ jsx(
1650
+ "button",
1651
+ {
1652
+ type: "button",
1653
+ "aria-label": "Dismiss",
1654
+ onClick: onDismiss,
1655
+ className: "rounded p-0.5 hover:bg-destructive/15",
1656
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": true, className: "size-3" })
1657
+ }
1658
+ ) : null
1659
+ ]
1660
+ }
1661
+ );
1662
+ }
1663
+ __name(ErrorBanner, "ErrorBanner");
1664
+ function JumpToLatest({ visible, unreadCount = 0, onClick, className }) {
1665
+ if (!visible) return null;
1666
+ return /* @__PURE__ */ jsxs(
1667
+ "button",
1668
+ {
1669
+ type: "button",
1670
+ onClick,
1671
+ "aria-live": "polite",
1672
+ className: cn(
1673
+ "pointer-events-auto inline-flex items-center gap-1.5 rounded-full border border-border bg-background px-3 py-1 text-xs shadow-md hover:bg-accent",
1674
+ className
1675
+ ),
1676
+ children: [
1677
+ /* @__PURE__ */ jsx(ArrowDown, { "aria-hidden": true, className: "size-3.5" }),
1678
+ unreadCount > 0 ? `${unreadCount} new` : "Jump to latest"
1679
+ ]
1680
+ }
1681
+ );
1682
+ }
1683
+ __name(JumpToLatest, "JumpToLatest");
1684
+
1685
+ // src/tools/Chat/core/persona.ts
1686
+ var FALLBACK_USER = { name: "You", initials: "You" };
1687
+ var FALLBACK_ASSISTANT = { name: "AI", initials: "AI" };
1688
+ function resolvePersona(message, user, assistant) {
1689
+ if (message.sender) return message.sender;
1690
+ if (message.role === "user") return user ?? FALLBACK_USER;
1691
+ if (message.role === "assistant") return assistant ?? FALLBACK_ASSISTANT;
1692
+ return { name: message.role };
1693
+ }
1694
+ __name(resolvePersona, "resolvePersona");
1695
+ function deriveInitials(persona, role) {
1696
+ if (persona.initials) return persona.initials;
1697
+ if (persona.name) {
1698
+ const parts = persona.name.trim().split(/\s+/);
1699
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
1700
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
1701
+ }
1702
+ if (role === "user") return "You";
1703
+ if (role === "assistant") return "AI";
1704
+ return "?";
1705
+ }
1706
+ __name(deriveInitials, "deriveInitials");
1707
+ function StreamingIndicator({ variant = "dots", label, className }) {
1708
+ return /* @__PURE__ */ jsxs(
1709
+ "span",
1710
+ {
1711
+ className: cn("inline-flex items-center gap-1.5 text-xs text-muted-foreground", className),
1712
+ "aria-live": "off",
1713
+ children: [
1714
+ variant === "dots" ? /* @__PURE__ */ jsxs("span", { className: "inline-flex gap-0.5", "aria-hidden": true, children: [
1715
+ /* @__PURE__ */ jsx("span", { className: "size-1 animate-bounce rounded-full bg-current [animation-delay:-0.2s]" }),
1716
+ /* @__PURE__ */ jsx("span", { className: "size-1 animate-bounce rounded-full bg-current [animation-delay:-0.1s]" }),
1717
+ /* @__PURE__ */ jsx("span", { className: "size-1 animate-bounce rounded-full bg-current" })
1718
+ ] }) : /* @__PURE__ */ jsx("span", { className: "inline-block size-1.5 animate-pulse rounded-full bg-current", "aria-hidden": true }),
1719
+ label ? /* @__PURE__ */ jsx("span", { className: "italic", children: label }) : null
1720
+ ]
1721
+ }
1722
+ );
1723
+ }
1724
+ __name(StreamingIndicator, "StreamingIndicator");
1725
+ function Sources({ sources, layout = "inline", maxVisible, onClick, className }) {
1726
+ if (!sources?.length) return null;
1727
+ const visible = maxVisible ? sources.slice(0, maxVisible) : sources;
1728
+ const remaining = maxVisible ? Math.max(0, sources.length - maxVisible) : 0;
1729
+ return /* @__PURE__ */ jsxs(
1730
+ "div",
1731
+ {
1732
+ className: cn(
1733
+ "mt-2 flex flex-wrap gap-1.5",
1734
+ layout === "grid" && "grid grid-cols-2",
1735
+ className
1736
+ ),
1737
+ children: [
1738
+ visible.map((s, i) => {
1739
+ const handle = onClick ? () => onClick(s) : void 0;
1740
+ const Tag = handle ? "button" : "a";
1741
+ const props = handle ? { type: "button", onClick: handle } : { href: s.url, target: "_blank", rel: "noopener noreferrer" };
1742
+ return /* @__PURE__ */ jsxs(
1743
+ Tag,
1744
+ {
1745
+ ...props,
1746
+ className: "inline-flex max-w-full items-center gap-1 rounded-md border border-border bg-background/60 px-2 py-1 text-xs text-foreground/80 hover:bg-accent hover:text-foreground",
1747
+ title: s.snippet ?? s.title,
1748
+ children: [
1749
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: s.title || s.url }),
1750
+ /* @__PURE__ */ jsx(ExternalLink, { "aria-hidden": true, className: "size-3 shrink-0 opacity-60" })
1751
+ ]
1752
+ },
1753
+ `${s.url}-${i}`
1754
+ );
1755
+ }),
1756
+ remaining > 0 ? /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center rounded-md border border-dashed border-border px-2 py-1 text-xs text-muted-foreground", children: [
1757
+ "+",
1758
+ remaining
1759
+ ] }) : null
1760
+ ]
1761
+ }
1762
+ );
1763
+ }
1764
+ __name(Sources, "Sources");
1765
+ function ToolCalls({
1766
+ calls,
1767
+ defaultExpanded = false,
1768
+ expandWhileStreaming = true,
1769
+ renderInput,
1770
+ renderOutput,
1771
+ renderStreaming,
1772
+ renderPayload,
1773
+ className
1774
+ }) {
1775
+ if (!calls?.length) return null;
1776
+ return /* @__PURE__ */ jsx("div", { className: cn("mt-2 space-y-1.5", className), children: calls.map((call) => /* @__PURE__ */ jsx(
1777
+ ToolCallItem,
1778
+ {
1779
+ call,
1780
+ defaultExpanded,
1781
+ expandWhileStreaming,
1782
+ renderInput,
1783
+ renderOutput,
1784
+ renderStreaming,
1785
+ renderPayload
1786
+ },
1787
+ call.id
1788
+ )) });
1789
+ }
1790
+ __name(ToolCalls, "ToolCalls");
1791
+ function ToolCallItem({
1792
+ call,
1793
+ defaultExpanded,
1794
+ expandWhileStreaming,
1795
+ renderInput,
1796
+ renderOutput,
1797
+ renderStreaming,
1798
+ renderPayload
1799
+ }) {
1800
+ const isRunning = call.status === "running";
1801
+ const initialOpen = defaultExpanded || expandWhileStreaming && isRunning;
1802
+ const [open, setOpen] = useState(initialOpen);
1803
+ const userToggledRef = useRef(false);
1804
+ const wasRunningRef = useRef(isRunning);
1805
+ useEffect(() => {
1806
+ if (wasRunningRef.current && !isRunning) {
1807
+ if (!userToggledRef.current && !defaultExpanded) {
1808
+ setOpen(false);
1809
+ }
1810
+ }
1811
+ wasRunningRef.current = isRunning;
1812
+ }, [isRunning, defaultExpanded]);
1813
+ const handleToggle = /* @__PURE__ */ __name(() => {
1814
+ userToggledRef.current = true;
1815
+ setOpen((v) => !v);
1816
+ }, "handleToggle");
1817
+ const Icon = open ? ChevronDown : ChevronRight;
1818
+ const statusColor = call.status === "success" ? "text-emerald-500" : call.status === "error" ? "text-destructive" : call.status === "cancelled" ? "text-muted-foreground" : "text-amber-500";
1819
+ const renderValue = /* @__PURE__ */ __name((value, kind) => {
1820
+ if (kind === "input" && renderInput) return renderInput(value, call);
1821
+ if (kind === "output" && renderOutput) return renderOutput(value, call);
1822
+ if (kind === "streaming" && renderStreaming)
1823
+ return renderStreaming(typeof value === "string" ? value : String(value), call);
1824
+ if (renderPayload) return renderPayload(value, kind, call);
1825
+ return /* @__PURE__ */ jsx(DefaultPayload, { value, kind });
1826
+ }, "renderValue");
1827
+ return /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-md border border-border bg-muted/30", children: [
1828
+ /* @__PURE__ */ jsxs(
1829
+ "button",
1830
+ {
1831
+ type: "button",
1832
+ onClick: handleToggle,
1833
+ "aria-expanded": open,
1834
+ className: "flex w-full items-center gap-2 px-2 py-1.5 text-left text-xs hover:bg-muted/60",
1835
+ children: [
1836
+ /* @__PURE__ */ jsx(Icon, { "aria-hidden": true, className: "size-3 shrink-0 text-muted-foreground" }),
1837
+ isRunning ? /* @__PURE__ */ jsx(Loader2, { "aria-hidden": true, className: "size-3 shrink-0 animate-spin text-amber-500" }) : /* @__PURE__ */ jsx("span", { className: cn("size-2 shrink-0 rounded-full", statusColor.replace("text-", "bg-")) }),
1838
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-foreground", children: call.name }),
1839
+ /* @__PURE__ */ jsx("span", { className: cn("ml-auto", statusColor), children: call.status })
1840
+ ]
1841
+ }
1842
+ ),
1843
+ open ? /* @__PURE__ */ jsxs("div", { className: "space-y-1 border-t border-border px-2 py-1.5 text-[11px]", children: [
1844
+ call.input != null ? renderValue(call.input, "input") : null,
1845
+ call.streamingText != null ? renderValue(call.streamingText, "streaming") : call.output !== void 0 ? renderValue(call.output, "output") : null
1846
+ ] }) : null
1847
+ ] });
1848
+ }
1849
+ __name(ToolCallItem, "ToolCallItem");
1850
+ function DefaultPayload({ value, kind }) {
1851
+ const isStreamingOrString = kind === "streaming" || typeof value === "string";
1852
+ const muted = kind === "input";
1853
+ return /* @__PURE__ */ jsx(
1854
+ "pre",
1855
+ {
1856
+ className: cn(
1857
+ "overflow-auto rounded bg-background/60 p-1.5 font-mono",
1858
+ kind === "input" ? "max-h-32" : "max-h-48",
1859
+ muted ? "text-muted-foreground" : "text-foreground/90"
1860
+ ),
1861
+ children: isStreamingOrString ? String(value) : safeStringify(value)
1862
+ }
1863
+ );
1864
+ }
1865
+ __name(DefaultPayload, "DefaultPayload");
1866
+ function safeStringify(value) {
1867
+ try {
1868
+ return JSON.stringify(value, null, 2);
1869
+ } catch {
1870
+ return String(value);
1871
+ }
1872
+ }
1873
+ __name(safeStringify, "safeStringify");
1874
+ function MessageActions({
1875
+ role,
1876
+ onCopy,
1877
+ onRegenerate,
1878
+ onEdit,
1879
+ onDelete,
1880
+ hideOn,
1881
+ className
1882
+ }) {
1883
+ if (hideOn?.includes(role)) return null;
1884
+ return /* @__PURE__ */ jsxs(
1885
+ "div",
1886
+ {
1887
+ className: cn(
1888
+ "mt-1 flex items-center gap-0.5 opacity-0 transition-opacity group-hover/msg:opacity-100 focus-within:opacity-100",
1889
+ className
1890
+ ),
1891
+ children: [
1892
+ onCopy ? /* @__PURE__ */ jsx(ActionButton, { onClick: onCopy, label: "Copy", icon: Copy }) : null,
1893
+ onRegenerate && role === "assistant" ? /* @__PURE__ */ jsx(ActionButton, { onClick: onRegenerate, label: "Regenerate", icon: RefreshCw }) : null,
1894
+ onEdit && role === "user" ? /* @__PURE__ */ jsx(ActionButton, { onClick: onEdit, label: "Edit", icon: Pencil }) : null,
1895
+ onDelete ? /* @__PURE__ */ jsx(ActionButton, { onClick: onDelete, label: "Delete", icon: Trash, destructive: true }) : null
1896
+ ]
1897
+ }
1898
+ );
1899
+ }
1900
+ __name(MessageActions, "MessageActions");
1901
+ function ActionButton({
1902
+ onClick,
1903
+ label,
1904
+ icon: Icon,
1905
+ destructive
1906
+ }) {
1907
+ return /* @__PURE__ */ jsx(
1908
+ "button",
1909
+ {
1910
+ type: "button",
1911
+ onClick,
1912
+ "aria-label": label,
1913
+ className: cn(
1914
+ "rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground",
1915
+ destructive && "hover:bg-destructive/15 hover:text-destructive"
1916
+ ),
1917
+ children: /* @__PURE__ */ jsx(Icon, { "aria-hidden": true, className: "size-3" })
1918
+ }
1919
+ );
1920
+ }
1921
+ __name(ActionButton, "ActionButton");
1922
+ var MessageBubbleInner = /* @__PURE__ */ __name(({
1923
+ message,
1924
+ isUser: isUserProp,
1925
+ showAvatar = true,
1926
+ avatarSrc,
1927
+ avatarFallback,
1928
+ user,
1929
+ assistant,
1930
+ showTimestamp = false,
1931
+ showActions = true,
1932
+ isCompact = false,
1933
+ className,
1934
+ beforeContent,
1935
+ afterContent,
1936
+ toolCallsRenderer,
1937
+ toolCallsProps,
1938
+ sourcesRenderer,
1939
+ attachmentsRenderer,
1940
+ attachmentRenderers,
1941
+ onAttachmentOpen,
1942
+ onCopy,
1943
+ onRegenerate,
1944
+ onEdit,
1945
+ onDelete
1946
+ }) => {
1947
+ const isUser = isUserProp ?? message.role === "user";
1948
+ const isStreaming = !!message.isStreaming;
1949
+ const isErr = !!message.isError;
1950
+ const ctx = useChatContextOptional();
1951
+ const persona = resolvePersona(
1952
+ message,
1953
+ user ?? ctx?.config.user,
1954
+ assistant ?? ctx?.config.assistant
1955
+ );
1956
+ const initials = deriveInitials(persona, message.role);
1957
+ const personaName = persona.name ?? (isUser ? "You" : "Assistant");
1958
+ return /* @__PURE__ */ jsxs(
1959
+ "div",
1960
+ {
1961
+ role: "article",
1962
+ "aria-label": `${personaName} said: ${message.content.slice(0, 80)}`,
1963
+ "aria-busy": isStreaming || void 0,
1964
+ "data-role": message.role,
1965
+ className: cn(
1966
+ "group/msg flex gap-2.5 px-2.5 py-2",
1967
+ isUser ? "flex-row-reverse" : "flex-row",
1968
+ className
1969
+ ),
1970
+ children: [
1971
+ showAvatar ? /* @__PURE__ */ jsxs(
1972
+ Avatar,
1973
+ {
1974
+ className: "size-7 shrink-0",
1975
+ title: persona.description ?? personaName,
1976
+ children: [
1977
+ avatarSrc || persona.avatarUrl ? /* @__PURE__ */ jsx(AvatarImage, { src: avatarSrc ?? persona.avatarUrl, alt: personaName }) : null,
1978
+ /* @__PURE__ */ jsx(AvatarFallback, { className: "text-[10px]", children: avatarFallback ?? initials })
1979
+ ]
1980
+ }
1981
+ ) : null,
1982
+ /* @__PURE__ */ jsxs("div", { className: cn("min-w-0 flex-1", isUser && "flex flex-col items-end"), children: [
1983
+ beforeContent,
1984
+ message.attachments?.length ? attachmentsRenderer ? attachmentsRenderer(message.attachments) : /* @__PURE__ */ jsx("div", { className: "mb-1.5 w-full", children: attachmentRenderers ? /* @__PURE__ */ jsx(
1985
+ AttachmentsList,
1986
+ {
1987
+ attachments: message.attachments,
1988
+ renderers: attachmentRenderers,
1989
+ onClick: onAttachmentOpen,
1990
+ className: isUser ? "items-end" : void 0
1991
+ }
1992
+ ) : /* @__PURE__ */ jsx(
1993
+ AttachmentsGrid,
1994
+ {
1995
+ attachments: message.attachments,
1996
+ onClick: onAttachmentOpen,
1997
+ className: isUser ? "justify-end" : void 0
1998
+ }
1999
+ ) }) : null,
2000
+ /* @__PURE__ */ jsxs(
2001
+ "div",
2002
+ {
2003
+ className: cn(
2004
+ "inline-block max-w-full rounded-2xl px-3.5 py-2 text-sm",
2005
+ isUser ? "bg-primary text-primary-foreground rounded-tr-md" : isErr ? "bg-destructive/10 text-destructive rounded-tl-md border border-destructive/30" : "bg-muted text-foreground rounded-tl-md"
2006
+ ),
2007
+ children: [
2008
+ isStreaming && message.toolActivity ? /* @__PURE__ */ jsx("div", { className: "mb-1.5", children: /* @__PURE__ */ jsx(StreamingIndicator, { label: message.toolActivity }) }) : null,
2009
+ message.content || !isStreaming ? /* @__PURE__ */ jsx(
2010
+ MarkdownMessage,
2011
+ {
2012
+ content: message.content || (isErr ? "*Failed to generate a response.*" : ""),
2013
+ isUser,
2014
+ isCompact,
2015
+ plainText: isStreaming
2016
+ }
2017
+ ) : /* @__PURE__ */ jsx(StreamingIndicator, {})
2018
+ ]
2019
+ }
2020
+ ),
2021
+ message.toolCalls?.length ? toolCallsRenderer ? toolCallsRenderer(message.toolCalls) : /* @__PURE__ */ jsx(ToolCalls, { calls: message.toolCalls, ...toolCallsProps }) : null,
2022
+ message.sources?.length && !isStreaming ? sourcesRenderer ? sourcesRenderer(message.sources) : /* @__PURE__ */ jsx(Sources, { sources: message.sources }) : null,
2023
+ showActions && !isStreaming ? /* @__PURE__ */ jsx(
2024
+ MessageActions,
2025
+ {
2026
+ role: message.role,
2027
+ onCopy,
2028
+ onRegenerate,
2029
+ onEdit,
2030
+ onDelete
2031
+ }
2032
+ ) : null,
2033
+ showTimestamp ? /* @__PURE__ */ jsx("div", { className: "mt-1 text-[10px] text-muted-foreground", children: new Date(message.createdAt).toLocaleTimeString() }) : null,
2034
+ afterContent
2035
+ ] })
2036
+ ]
2037
+ }
2038
+ );
2039
+ }, "MessageBubbleInner");
2040
+ var MessageBubble = memo(MessageBubbleInner, (prev, next) => {
2041
+ const a = prev.message;
2042
+ const b = next.message;
2043
+ return a.id === b.id && a.content === b.content && a.isStreaming === b.isStreaming && a.isError === b.isError && (a.version ?? 0) === (b.version ?? 0) && a.toolActivity === b.toolActivity && a.toolCalls === b.toolCalls && a.sources === b.sources && a.attachments === b.attachments;
2044
+ });
2045
+ MessageBubble.displayName = "MessageBubble";
2046
+ var MessageList = forwardRef(/* @__PURE__ */ __name(function MessageList2({
2047
+ messages: messagesProp,
2048
+ renderItem,
2049
+ renderEmpty,
2050
+ isLoadingMore: isLoadingMoreProp,
2051
+ topSentinelRef,
2052
+ bottomRef,
2053
+ className,
2054
+ itemClassName
2055
+ }, ref) {
2056
+ const ctx = useChatContextOptional();
2057
+ const messages = messagesProp ?? ctx?.messages ?? [];
2058
+ const isLoadingMore = isLoadingMoreProp ?? ctx?.isLoadingMore ?? false;
2059
+ const defaultRenderItem = useCallback(
2060
+ (m) => /* @__PURE__ */ jsx("div", { className: itemClassName, children: /* @__PURE__ */ jsx(
2061
+ MessageBubble,
2062
+ {
2063
+ message: m,
2064
+ onCopy: () => copy(m.content),
2065
+ onRegenerate: ctx ? () => void ctx.regenerate(m.id) : void 0,
2066
+ onDelete: ctx ? () => ctx.deleteMessage(m.id) : void 0
2067
+ }
2068
+ ) }, m.id),
2069
+ [itemClassName, ctx]
2070
+ );
2071
+ const itemRenderer = renderItem ?? defaultRenderItem;
2072
+ return /* @__PURE__ */ jsxs(
2073
+ "div",
2074
+ {
2075
+ ref,
2076
+ role: "log",
2077
+ "aria-live": "polite",
2078
+ "aria-atomic": "false",
2079
+ className: cn("flex-1 overflow-y-auto", className),
2080
+ children: [
2081
+ /* @__PURE__ */ jsx("div", { ref: topSentinelRef, "aria-hidden": true }),
2082
+ isLoadingMore ? /* @__PURE__ */ jsx("div", { className: "flex justify-center py-2", children: /* @__PURE__ */ jsx(Spinner, { className: "size-4 text-muted-foreground" }) }) : null,
2083
+ messages.length === 0 ? renderEmpty?.() ?? null : messages.map((m, i) => itemRenderer(m, i)),
2084
+ /* @__PURE__ */ jsx("div", { ref: bottomRef, "aria-hidden": true })
2085
+ ]
2086
+ }
2087
+ );
2088
+ }, "MessageList"));
2089
+ function copy(text) {
2090
+ if (typeof navigator !== "undefined" && navigator.clipboard) {
2091
+ void navigator.clipboard.writeText(text);
2092
+ }
2093
+ }
2094
+ __name(copy, "copy");
2095
+ function ChatRoot(props) {
2096
+ const { transport, config, initialSessionId, autoCreateSession, streaming, audio, className, ...slots } = props;
2097
+ return /* @__PURE__ */ jsx(
2098
+ ChatProvider,
2099
+ {
2100
+ transport,
2101
+ config,
2102
+ initialSessionId,
2103
+ autoCreateSession,
2104
+ streaming,
2105
+ audio,
2106
+ children: /* @__PURE__ */ jsx(ChatRootShell, { className, slots })
2107
+ }
2108
+ );
2109
+ }
2110
+ __name(ChatRoot, "ChatRoot");
2111
+ function ChatRootShell({ className, slots }) {
2112
+ const chat = useChatContext();
2113
+ const composer = useChatComposer({
2114
+ onSubmit: /* @__PURE__ */ __name((content, attachments) => chat.sendMessage(content, attachments), "onSubmit"),
2115
+ disabled: chat.isStreaming
2116
+ });
2117
+ const containerRef = useRef(null);
2118
+ const bottomRef = useRef(null);
2119
+ const topRef = useRef(null);
2120
+ const scroll = useChatScroll({
2121
+ containerRef,
2122
+ bottomRef,
2123
+ isStreaming: chat.isStreaming,
2124
+ messagesCount: chat.messages.length
2125
+ });
2126
+ useChatHistory({
2127
+ containerRef,
2128
+ topSentinelRef: topRef,
2129
+ hasMore: chat.hasMore,
2130
+ isLoadingMore: chat.isLoadingMore,
2131
+ loadMore: chat.loadMore
2132
+ });
2133
+ const greeting = chat.config.greeting ?? "How can I help?";
2134
+ const description = chat.config.description;
2135
+ const suggestions = chat.config.suggestions;
2136
+ const headerNode = slots.renderHeader ? slots.renderHeader(chat) : slots.header;
2137
+ const emptyNode = slots.empty ?? (slots.renderEmpty ? slots.renderEmpty({ setValue: composer.setValue, focus: composer.focus }) : /* @__PURE__ */ jsx(
2138
+ EmptyState,
2139
+ {
2140
+ greeting,
2141
+ description,
2142
+ suggestions,
2143
+ onPickSuggestion: (prompt) => {
2144
+ composer.setValue(prompt);
2145
+ composer.focus();
2146
+ }
2147
+ }
2148
+ ));
2149
+ const renderItem = slots.renderMessage ?? ((m) => /* @__PURE__ */ jsx(
2150
+ MessageBubble,
2151
+ {
2152
+ message: m,
2153
+ toolCallsProps: slots.toolCallsProps,
2154
+ attachmentRenderers: slots.attachmentRenderers,
2155
+ onAttachmentOpen: slots.onAttachmentOpen,
2156
+ onCopy: () => copy2(m.content),
2157
+ onRegenerate: () => void chat.regenerate(m.id),
2158
+ onDelete: () => chat.deleteMessage(m.id)
2159
+ },
2160
+ m.id
2161
+ ));
2162
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative flex h-full min-h-0 flex-col overflow-hidden", className), children: [
2163
+ slots.banner ?? null,
2164
+ headerNode ?? null,
2165
+ /* @__PURE__ */ jsxs("div", { className: "relative flex min-h-0 flex-1 flex-col", children: [
2166
+ /* @__PURE__ */ jsx(
2167
+ ErrorBanner,
2168
+ {
2169
+ error: chat.error,
2170
+ onDismiss: chat.error ? () => chat.clearMessages() : void 0,
2171
+ onRetry: chat.error ? () => void chat.regenerate() : void 0
2172
+ }
2173
+ ),
2174
+ /* @__PURE__ */ jsx(
2175
+ MessageList,
2176
+ {
2177
+ ref: containerRef,
2178
+ topSentinelRef: topRef,
2179
+ bottomRef,
2180
+ renderItem,
2181
+ renderEmpty: () => /* @__PURE__ */ jsx(Fragment, { children: emptyNode })
2182
+ }
2183
+ ),
2184
+ /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-x-0 bottom-2 flex justify-center", children: slots.jumpToLatest ?? /* @__PURE__ */ jsx(
2185
+ JumpToLatest,
2186
+ {
2187
+ visible: !scroll.isAtBottom,
2188
+ unreadCount: scroll.unreadCount,
2189
+ onClick: () => scroll.scrollToBottom(true)
2190
+ }
2191
+ ) })
2192
+ ] }),
2193
+ /* @__PURE__ */ jsx(
2194
+ Composer,
2195
+ {
2196
+ composer,
2197
+ placeholder: chat.config.placeholder,
2198
+ showAttachmentButton: slots.showAttachmentButton,
2199
+ onPickFiles: slots.onPickFiles,
2200
+ toolbarStart: slots.composerToolbarStart,
2201
+ toolbarEnd: slots.composerToolbarEnd,
2202
+ attachmentTray: slots.composerAttachmentTray
2203
+ }
2204
+ ),
2205
+ slots.footer ?? null
2206
+ ] });
2207
+ }
2208
+ __name(ChatRootShell, "ChatRootShell");
2209
+ function copy2(text) {
2210
+ if (typeof navigator !== "undefined" && navigator.clipboard) {
2211
+ void navigator.clipboard.writeText(text);
2212
+ }
2213
+ }
2214
+ __name(copy2, "copy");
2215
+
2216
+ export { Attachments, AttachmentsGrid, AttachmentsList, CHAT_EVENT_NAME, CSS_VARS, ChatProvider, ChatRoot, Composer, DEFAULT_LABELS, DEFAULT_SIDEBAR, DEFAULT_Z_INDEX, EmptyState, ErrorBanner, HOTKEYS, JumpToLatest, LIMITS, MessageActions, MessageBubble, MessageList, STORAGE_KEYS, Sources, StreamingIndicator, ToolCalls, createId, createTokenBuffer, deriveInitials, initialState, reducer, resolvePersona, useChat, useChatAudio, useChatAudioPrefs, useChatComposer, useChatContext, useChatContextOptional, useChatHistory, useChatLayout, useChatScroll };
2217
+ //# sourceMappingURL=chunk-KRETIZU6.mjs.map
2218
+ //# sourceMappingURL=chunk-KRETIZU6.mjs.map