@refraction-ui/react 0.19.1 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/dist/chunk-533MCMSU.js +2355 -0
  2. package/dist/chunk-533MCMSU.js.map +7 -0
  3. package/dist/chunk-AMK4VCBG.js +250 -0
  4. package/dist/chunk-AMK4VCBG.js.map +7 -0
  5. package/dist/chunk-BOLFYVI6.js +90 -0
  6. package/dist/chunk-BOLFYVI6.js.map +7 -0
  7. package/dist/chunk-F7B64KT3.js +187 -0
  8. package/dist/chunk-F7B64KT3.js.map +7 -0
  9. package/dist/chunk-FD37DFCC.js +77 -0
  10. package/dist/chunk-FD37DFCC.js.map +7 -0
  11. package/dist/chunk-H54FLNAF.js +132 -0
  12. package/dist/chunk-H54FLNAF.js.map +7 -0
  13. package/dist/chunk-INP46354.js +664 -0
  14. package/dist/chunk-INP46354.js.map +7 -0
  15. package/dist/chunk-ONACRMG4.js +116 -0
  16. package/dist/chunk-ONACRMG4.js.map +7 -0
  17. package/dist/chunk-SDZ3LROZ.js +7 -0
  18. package/dist/chunk-SDZ3LROZ.js.map +7 -0
  19. package/dist/chunk-TOZBR4GL.js +154 -0
  20. package/dist/chunk-TOZBR4GL.js.map +7 -0
  21. package/dist/chunk-UNEM2TRI.js +225 -0
  22. package/dist/chunk-UNEM2TRI.js.map +7 -0
  23. package/dist/chunk-VK3EQ34F.js +92 -0
  24. package/dist/chunk-VK3EQ34F.js.map +7 -0
  25. package/dist/chunk-WK2IIUFO.js +316 -0
  26. package/dist/chunk-WK2IIUFO.js.map +7 -0
  27. package/dist/chunk-WSPJTQAV.js +59 -0
  28. package/dist/chunk-WSPJTQAV.js.map +7 -0
  29. package/dist/chunk-XWP763SH.js +3 -2
  30. package/dist/chunk-XWP763SH.js.map +7 -1
  31. package/dist/chunk-Y2FNNAIL.js +80 -0
  32. package/dist/chunk-Y2FNNAIL.js.map +1 -0
  33. package/dist/{chunk-4A4BW3GR.js → chunk-ZF3OHCGV.js} +28 -5
  34. package/dist/chunk-ZF3OHCGV.js.map +7 -0
  35. package/dist/form.cjs +5 -11
  36. package/dist/form.cjs.map +1 -1
  37. package/dist/form.js +6 -12
  38. package/dist/form.js.map +1 -1
  39. package/dist/index.cjs +2879 -33936
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +1 -0
  42. package/dist/index.d.ts +1 -0
  43. package/dist/index.js +126 -28780
  44. package/dist/internal/react-app-shell/index.d.cts +2 -22
  45. package/dist/internal/react-app-shell/index.d.ts +2 -22
  46. package/dist/internal/react-badge/index.d.cts +5 -0
  47. package/dist/internal/react-badge/index.d.ts +5 -0
  48. package/dist/internal/react-callout/index.d.cts +4 -0
  49. package/dist/internal/react-callout/index.d.ts +4 -0
  50. package/dist/internal/react-card/index.d.cts +8 -1
  51. package/dist/internal/react-card/index.d.ts +8 -1
  52. package/dist/internal/react-collapsible/index.d.cts +0 -3
  53. package/dist/internal/react-collapsible/index.d.ts +0 -3
  54. package/dist/internal/react-combobox/index.d.cts +0 -3
  55. package/dist/internal/react-combobox/index.d.ts +0 -3
  56. package/dist/internal/react-command/index.d.cts +0 -3
  57. package/dist/internal/react-command/index.d.ts +0 -3
  58. package/dist/internal/react-data-table/index.d.cts +0 -3
  59. package/dist/internal/react-data-table/index.d.ts +0 -3
  60. package/dist/internal/react-dialog/index.d.cts +0 -3
  61. package/dist/internal/react-dialog/index.d.ts +0 -3
  62. package/dist/internal/react-dropdown-menu/index.d.cts +0 -3
  63. package/dist/internal/react-dropdown-menu/index.d.ts +0 -3
  64. package/dist/internal/react-feedback-dialog/index.d.cts +0 -3
  65. package/dist/internal/react-feedback-dialog/index.d.ts +0 -3
  66. package/dist/internal/react-form/index.d.cts +2 -21
  67. package/dist/internal/react-form/index.d.ts +2 -21
  68. package/dist/internal/react-keyboard-shortcut/index.d.cts +0 -6
  69. package/dist/internal/react-keyboard-shortcut/index.d.ts +0 -6
  70. package/dist/internal/react-link-card/index.d.cts +8 -2
  71. package/dist/internal/react-link-card/index.d.ts +8 -2
  72. package/dist/internal/react-popover/index.d.cts +0 -3
  73. package/dist/internal/react-popover/index.d.ts +0 -3
  74. package/dist/internal/react-sheet/index.d.cts +0 -3
  75. package/dist/internal/react-sheet/index.d.ts +0 -3
  76. package/dist/internal/react-slot/index.d.cts +23 -0
  77. package/dist/internal/react-slot/index.d.ts +23 -0
  78. package/dist/internal/react-toast/index.d.cts +0 -3
  79. package/dist/internal/react-toast/index.d.ts +0 -3
  80. package/dist/internal/react-tooltip/index.d.cts +0 -3
  81. package/dist/internal/react-tooltip/index.d.ts +0 -3
  82. package/dist/react-accordion.js +125 -0
  83. package/dist/react-accordion.js.map +7 -0
  84. package/dist/react-ai.js +257 -0
  85. package/dist/react-ai.js.map +7 -0
  86. package/dist/react-analytics.js +1698 -0
  87. package/dist/react-analytics.js.map +7 -0
  88. package/dist/react-animated-text.js +164 -0
  89. package/dist/react-animated-text.js.map +7 -0
  90. package/dist/react-app-shell.js +631 -0
  91. package/dist/react-app-shell.js.map +7 -0
  92. package/dist/react-audience-feature-card.js +53 -0
  93. package/dist/react-audience-feature-card.js.map +7 -0
  94. package/dist/react-audio-room.js +166 -0
  95. package/dist/react-audio-room.js.map +7 -0
  96. package/dist/react-auth.js +211 -0
  97. package/dist/react-auth.js.map +7 -0
  98. package/dist/react-avatar-group.js +154 -0
  99. package/dist/react-avatar-group.js.map +7 -0
  100. package/dist/react-avatar.js +168 -0
  101. package/dist/react-avatar.js.map +7 -0
  102. package/dist/react-badge.js +86 -0
  103. package/dist/react-badge.js.map +7 -0
  104. package/dist/react-bottom-nav.js +67 -0
  105. package/dist/react-bottom-nav.js.map +7 -0
  106. package/dist/react-brand-network-cell.js +92 -0
  107. package/dist/react-brand-network-cell.js.map +7 -0
  108. package/dist/react-breadcrumbs.js +104 -0
  109. package/dist/react-breadcrumbs.js.map +7 -0
  110. package/dist/react-browser-chrome-mock.js +89 -0
  111. package/dist/react-browser-chrome-mock.js.map +7 -0
  112. package/dist/react-button.js +13 -0
  113. package/dist/react-button.js.map +7 -0
  114. package/dist/react-calendar.js +301 -0
  115. package/dist/react-calendar.js.map +7 -0
  116. package/dist/react-call-controls.js +113 -0
  117. package/dist/react-call-controls.js.map +7 -0
  118. package/dist/react-callout.js +152 -0
  119. package/dist/react-callout.js.map +7 -0
  120. package/dist/react-card-grid.js +34 -0
  121. package/dist/react-card-grid.js.map +7 -0
  122. package/dist/react-card.js +200 -0
  123. package/dist/react-card.js.map +7 -0
  124. package/dist/react-carousel.js +132 -0
  125. package/dist/react-carousel.js.map +7 -0
  126. package/dist/react-charts.js +509 -0
  127. package/dist/react-charts.js.map +7 -0
  128. package/dist/react-checkbox.js +156 -0
  129. package/dist/react-checkbox.js.map +7 -0
  130. package/dist/react-checklist.js +168 -0
  131. package/dist/react-checklist.js.map +7 -0
  132. package/dist/react-code-block.js +78 -0
  133. package/dist/react-code-block.js.map +7 -0
  134. package/dist/react-code-editor.js +184 -0
  135. package/dist/react-code-editor.js.map +7 -0
  136. package/dist/react-collapsible.js +192 -0
  137. package/dist/react-collapsible.js.map +7 -0
  138. package/dist/react-combobox.js +609 -0
  139. package/dist/react-combobox.js.map +7 -0
  140. package/dist/react-command-input.js +202 -0
  141. package/dist/react-command-input.js.map +7 -0
  142. package/dist/react-command.js +364 -0
  143. package/dist/react-command.js.map +7 -0
  144. package/dist/react-composer.js +2036 -0
  145. package/dist/react-composer.js.map +7 -0
  146. package/dist/react-content-protection.js +133 -0
  147. package/dist/react-content-protection.js.map +7 -0
  148. package/dist/react-conversation.js +1226 -0
  149. package/dist/react-conversation.js.map +7 -0
  150. package/dist/react-cookie-consent.js +291 -0
  151. package/dist/react-cookie-consent.js.map +7 -0
  152. package/dist/react-data-table.js +310 -0
  153. package/dist/react-data-table.js.map +7 -0
  154. package/dist/react-date-picker.js +95 -0
  155. package/dist/react-date-picker.js.map +7 -0
  156. package/dist/react-device-frame.js +137 -0
  157. package/dist/react-device-frame.js.map +7 -0
  158. package/dist/react-dialog.js +218 -0
  159. package/dist/react-dialog.js.map +7 -0
  160. package/dist/react-diff-viewer.js +460 -0
  161. package/dist/react-diff-viewer.js.map +7 -0
  162. package/dist/react-dropdown-menu.js +292 -0
  163. package/dist/react-dropdown-menu.js.map +7 -0
  164. package/dist/react-editor-status-bar.js +148 -0
  165. package/dist/react-editor-status-bar.js.map +7 -0
  166. package/dist/react-editor-tabs.js +159 -0
  167. package/dist/react-editor-tabs.js.map +7 -0
  168. package/dist/react-emoji-picker.js +238 -0
  169. package/dist/react-emoji-picker.js.map +7 -0
  170. package/dist/react-empty-state.js +90 -0
  171. package/dist/react-empty-state.js.map +7 -0
  172. package/dist/react-feedback-dialog.js +275 -0
  173. package/dist/react-feedback-dialog.js.map +7 -0
  174. package/dist/react-file-tree.js +376 -0
  175. package/dist/react-file-tree.js.map +7 -0
  176. package/dist/react-file-upload.js +360 -0
  177. package/dist/react-file-upload.js.map +7 -0
  178. package/dist/react-floating-reactions.js +124 -0
  179. package/dist/react-floating-reactions.js.map +7 -0
  180. package/dist/react-flow-editor.js +198 -0
  181. package/dist/react-flow-editor.js.map +7 -0
  182. package/dist/react-footer.js +67 -0
  183. package/dist/react-footer.js.map +7 -0
  184. package/dist/react-graph-view.js +197 -0
  185. package/dist/react-graph-view.js.map +7 -0
  186. package/dist/react-icon-system.js +9 -0
  187. package/dist/react-icon-system.js.map +7 -0
  188. package/dist/react-infinite-canvas.js +8 -0
  189. package/dist/react-infinite-canvas.js.map +7 -0
  190. package/dist/react-inline-editor.js +216 -0
  191. package/dist/react-inline-editor.js.map +7 -0
  192. package/dist/react-input-group.js +150 -0
  193. package/dist/react-input-group.js.map +7 -0
  194. package/dist/react-input.js +10 -0
  195. package/dist/react-input.js.map +7 -0
  196. package/dist/react-install-prompt.js +132 -0
  197. package/dist/react-install-prompt.js.map +7 -0
  198. package/dist/react-kanban-board.js +198 -0
  199. package/dist/react-kanban-board.js.map +7 -0
  200. package/dist/react-keyboard-shortcut.js +36 -0
  201. package/dist/react-keyboard-shortcut.js.map +7 -0
  202. package/dist/react-language-selector.js +311 -0
  203. package/dist/react-language-selector.js.map +7 -0
  204. package/dist/react-link-card.js +48 -0
  205. package/dist/react-link-card.js.map +7 -0
  206. package/dist/react-live-captions.js +92 -0
  207. package/dist/react-live-captions.js.map +7 -0
  208. package/dist/react-live-cursors.js +145 -0
  209. package/dist/react-live-cursors.js.map +7 -0
  210. package/dist/react-live-transcript.js +117 -0
  211. package/dist/react-live-transcript.js.map +7 -0
  212. package/dist/react-location-selector.js +252 -0
  213. package/dist/react-location-selector.js.map +7 -0
  214. package/dist/react-logger.js +415 -0
  215. package/dist/react-logger.js.map +7 -0
  216. package/dist/react-markdown-renderer.js +12 -0
  217. package/dist/react-markdown-renderer.js.map +7 -0
  218. package/dist/react-marquee-strip.js +62 -0
  219. package/dist/react-marquee-strip.js.map +7 -0
  220. package/dist/react-mascot.js +367 -0
  221. package/dist/react-mascot.js.map +7 -0
  222. package/dist/react-mastery-bar.js +97 -0
  223. package/dist/react-mastery-bar.js.map +7 -0
  224. package/dist/react-mini-map.js +207 -0
  225. package/dist/react-mini-map.js.map +7 -0
  226. package/dist/react-mobile-nav.js +216 -0
  227. package/dist/react-mobile-nav.js.map +7 -0
  228. package/dist/react-navbar.js +96 -0
  229. package/dist/react-navbar.js.map +7 -0
  230. package/dist/react-numbered-steps.js +67 -0
  231. package/dist/react-numbered-steps.js.map +7 -0
  232. package/dist/react-otp-input.js +318 -0
  233. package/dist/react-otp-input.js.map +7 -0
  234. package/dist/react-pagination.js +199 -0
  235. package/dist/react-pagination.js.map +7 -0
  236. package/dist/react-password-input.js +104 -0
  237. package/dist/react-password-input.js.map +7 -0
  238. package/dist/react-payment.js +81 -0
  239. package/dist/react-payment.js.map +7 -0
  240. package/dist/react-popover.js +176 -0
  241. package/dist/react-popover.js.map +7 -0
  242. package/dist/react-pre-call-lobby.js +309 -0
  243. package/dist/react-pre-call-lobby.js.map +7 -0
  244. package/dist/react-presence-indicator.js +86 -0
  245. package/dist/react-presence-indicator.js.map +7 -0
  246. package/dist/react-pricing-card.js +142 -0
  247. package/dist/react-pricing-card.js.map +7 -0
  248. package/dist/react-progress-display.js +188 -0
  249. package/dist/react-progress-display.js.map +7 -0
  250. package/dist/react-radial-gauge.js +193 -0
  251. package/dist/react-radial-gauge.js.map +7 -0
  252. package/dist/react-radio.js +169 -0
  253. package/dist/react-radio.js.map +7 -0
  254. package/dist/react-rating-scale.js +171 -0
  255. package/dist/react-rating-scale.js.map +7 -0
  256. package/dist/react-reaction-bar.js +111 -0
  257. package/dist/react-reaction-bar.js.map +7 -0
  258. package/dist/react-resizable-layout.js +298 -0
  259. package/dist/react-resizable-layout.js.map +7 -0
  260. package/dist/react-rich-editor.js +1 -0
  261. package/dist/react-rich-editor.js.map +7 -0
  262. package/dist/react-search-bar.js +285 -0
  263. package/dist/react-search-bar.js.map +7 -0
  264. package/dist/react-section-head.js +65 -0
  265. package/dist/react-section-head.js.map +7 -0
  266. package/dist/react-segmented-control.js +215 -0
  267. package/dist/react-segmented-control.js.map +7 -0
  268. package/dist/react-select.js +389 -0
  269. package/dist/react-select.js.map +7 -0
  270. package/dist/react-separator.js +86 -0
  271. package/dist/react-separator.js.map +7 -0
  272. package/dist/react-sheet.js +310 -0
  273. package/dist/react-sheet.js.map +7 -0
  274. package/dist/react-sidebar.js +100 -0
  275. package/dist/react-sidebar.js.map +7 -0
  276. package/dist/react-skeleton.js +91 -0
  277. package/dist/react-skeleton.js.map +7 -0
  278. package/dist/react-skip-to-content.js +38 -0
  279. package/dist/react-skip-to-content.js.map +7 -0
  280. package/dist/react-slide-viewer.js +264 -0
  281. package/dist/react-slide-viewer.js.map +7 -0
  282. package/dist/react-slider.js +105 -0
  283. package/dist/react-slider.js.map +7 -0
  284. package/dist/react-slot-picker.js +184 -0
  285. package/dist/react-slot-picker.js.map +7 -0
  286. package/dist/react-slot.js +7 -0
  287. package/dist/react-slot.js.map +7 -0
  288. package/dist/react-social-auth-button.js +169 -0
  289. package/dist/react-social-auth-button.js.map +7 -0
  290. package/dist/react-sortable-list.js +204 -0
  291. package/dist/react-sortable-list.js.map +7 -0
  292. package/dist/react-stat-grid.js +69 -0
  293. package/dist/react-stat-grid.js.map +7 -0
  294. package/dist/react-status-indicator.js +117 -0
  295. package/dist/react-status-indicator.js.map +7 -0
  296. package/dist/react-steps.js +167 -0
  297. package/dist/react-steps.js.map +7 -0
  298. package/dist/react-sticky-note.js +131 -0
  299. package/dist/react-sticky-note.js.map +7 -0
  300. package/dist/react-switch.js +139 -0
  301. package/dist/react-switch.js.map +7 -0
  302. package/dist/react-table-of-contents.js +80 -0
  303. package/dist/react-table-of-contents.js.map +7 -0
  304. package/dist/react-tabs.js +230 -0
  305. package/dist/react-tabs.js.map +7 -0
  306. package/dist/react-terminal.js +101 -0
  307. package/dist/react-terminal.js.map +7 -0
  308. package/dist/react-test-results.js +158 -0
  309. package/dist/react-test-results.js.map +7 -0
  310. package/dist/react-textarea.js +89 -0
  311. package/dist/react-textarea.js.map +7 -0
  312. package/dist/react-thread-view.js +139 -0
  313. package/dist/react-thread-view.js.map +7 -0
  314. package/dist/react-timeline.js +153 -0
  315. package/dist/react-timeline.js.map +7 -0
  316. package/dist/react-toast.js +253 -0
  317. package/dist/react-toast.js.map +7 -0
  318. package/dist/react-tooltip.js +221 -0
  319. package/dist/react-tooltip.js.map +7 -0
  320. package/dist/react-version-selector.js +270 -0
  321. package/dist/react-version-selector.js.map +7 -0
  322. package/dist/react-video-grid.js +129 -0
  323. package/dist/react-video-grid.js.map +7 -0
  324. package/dist/react-video-player.js +206 -0
  325. package/dist/react-video-player.js.map +7 -0
  326. package/dist/react-video-tile.js +8 -0
  327. package/dist/react-video-tile.js.map +7 -0
  328. package/dist/react-voice-pill.js +314 -0
  329. package/dist/react-voice-pill.js.map +7 -0
  330. package/dist/react-waveform.js +558 -0
  331. package/dist/react-waveform.js.map +7 -0
  332. package/dist/react-wizard.js +337 -0
  333. package/dist/react-wizard.js.map +7 -0
  334. package/dist/styles.css +7 -0
  335. package/dist/theme.js +1 -1
  336. package/package.json +3 -2
  337. package/dist/applicationinsights-web-H7OBLIVJ.js +0 -23393
  338. package/dist/applicationinsights-web-H7OBLIVJ.js.map +0 -1
  339. package/dist/chunk-4A4BW3GR.js.map +0 -1
  340. package/dist/index.js.map +0 -1
  341. package/dist/module-X3HTTRQK.js +0 -6438
  342. package/dist/module-X3HTTRQK.js.map +0 -1
@@ -0,0 +1,1226 @@
1
+ import {
2
+ formatRelativeTime,
3
+ formatTimestamp
4
+ } from "./chunk-ONACRMG4.js";
5
+ import {
6
+ MarkdownRenderer
7
+ } from "./chunk-UNEM2TRI.js";
8
+ import {
9
+ detectTriggerInText
10
+ } from "./chunk-BOLFYVI6.js";
11
+ import "./chunk-533MCMSU.js";
12
+ import {
13
+ cn,
14
+ generateId
15
+ } from "./chunk-ZF3OHCGV.js";
16
+
17
+ // ../react-conversation/dist/index.js
18
+ import * as React2 from "react";
19
+
20
+ // ../conversation/dist/index.js
21
+ function byTime(a, b) {
22
+ return a.timestamp.getTime() - b.timestamp.getTime();
23
+ }
24
+ function findMessage(messages, id) {
25
+ return messages.find((m) => m.id === id);
26
+ }
27
+ function rootIdOf(messages, id) {
28
+ const m = findMessage(messages, id);
29
+ if (!m) return id;
30
+ return m.parentId ?? m.id;
31
+ }
32
+ function selectRoots(messages) {
33
+ return messages.filter((m) => !m.parentId).sort(byTime);
34
+ }
35
+ function selectReplies(messages, rootId) {
36
+ return messages.filter((m) => m.parentId === rootId).sort(byTime);
37
+ }
38
+ function selectThreadMessages(messages, rootId) {
39
+ const root = findMessage(messages, rootId);
40
+ const replies = selectReplies(messages, rootId);
41
+ return root ? [root, ...replies] : replies;
42
+ }
43
+ function getReplyCount(messages, rootId) {
44
+ return messages.reduce((n, m) => m.parentId === rootId ? n + 1 : n, 0);
45
+ }
46
+ function selectMainTimeline(messages, mode) {
47
+ if (mode === "panel") return selectRoots(messages);
48
+ return [...messages].sort(byTime);
49
+ }
50
+ function selectTimelineFromState(state) {
51
+ return selectMainTimeline(state.messages, state.threadingMode);
52
+ }
53
+ var DEFAULT_ASSISTANT = { id: "assistant", name: "Assistant" };
54
+ var DEFAULT_USER = { id: "user", name: "You" };
55
+ var DEFAULT_TITLE = "New conversation";
56
+ function defaultGenerateTitle(firstMessage) {
57
+ const firstLine = firstMessage.split("\n", 1)[0].trim();
58
+ if (firstLine.length <= 48) return firstLine || DEFAULT_TITLE;
59
+ return `${firstLine.slice(0, 47).trimEnd()}\u2026`;
60
+ }
61
+ function createConversation(config = {}) {
62
+ const assistant = config.assistant ?? DEFAULT_ASSISTANT;
63
+ const currentUser = config.currentUser ?? DEFAULT_USER;
64
+ const generateTitle = config.generateTitle ?? defaultGenerateTitle;
65
+ const now = config.now ?? (() => /* @__PURE__ */ new Date());
66
+ let transport = config.transport;
67
+ let threadingMode = config.threadingMode ?? "inline";
68
+ const conversations = /* @__PURE__ */ new Map();
69
+ const messagesByConv = /* @__PURE__ */ new Map();
70
+ let activeConversationId = config.activeConversationId ?? null;
71
+ let openThreadRootId = null;
72
+ let replyTarget = null;
73
+ let status = "idle";
74
+ let error = null;
75
+ let abortController = null;
76
+ for (const c of config.conversations ?? []) {
77
+ conversations.set(c.id, c);
78
+ messagesByConv.set(c.id, []);
79
+ }
80
+ for (const [conversationId, msgs] of Object.entries(config.messages ?? {})) {
81
+ messagesByConv.set(conversationId, [...msgs]);
82
+ }
83
+ if (activeConversationId === null && config.conversations?.length) {
84
+ activeConversationId = config.conversations[0].id;
85
+ }
86
+ const listeners = /* @__PURE__ */ new Set();
87
+ let snapshot = buildSnapshot();
88
+ function buildSnapshot() {
89
+ return {
90
+ conversations: orderedConversations(),
91
+ activeConversationId,
92
+ messages: activeConversationId ? messagesByConv.get(activeConversationId) ?? [] : [],
93
+ openThreadRootId,
94
+ replyTarget,
95
+ threadingMode,
96
+ status,
97
+ error
98
+ };
99
+ }
100
+ function emit() {
101
+ snapshot = buildSnapshot();
102
+ for (const l of listeners) l();
103
+ }
104
+ function orderedConversations() {
105
+ return [...conversations.values()].sort(
106
+ (a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()
107
+ );
108
+ }
109
+ function touch(conversationId) {
110
+ const c = conversations.get(conversationId);
111
+ if (c) conversations.set(conversationId, { ...c, updatedAt: now() });
112
+ }
113
+ function ensureActiveConversation(opts) {
114
+ if (opts?.conversationId) {
115
+ if (!conversations.has(opts.conversationId)) createConversationInternal({}, opts.conversationId);
116
+ return opts.conversationId;
117
+ }
118
+ if (activeConversationId && conversations.has(activeConversationId)) return activeConversationId;
119
+ return createConversationInternal({}).id;
120
+ }
121
+ function createConversationInternal(opts, id) {
122
+ const timestamp = now();
123
+ const conversation = {
124
+ id: id ?? generateId("rfr-conv"),
125
+ title: opts.title ?? DEFAULT_TITLE,
126
+ createdAt: timestamp,
127
+ updatedAt: timestamp,
128
+ metadata: opts.metadata
129
+ };
130
+ conversations.set(conversation.id, conversation);
131
+ messagesByConv.set(conversation.id, []);
132
+ activeConversationId = conversation.id;
133
+ return conversation;
134
+ }
135
+ async function streamReply(conversationId, assistantMsg, userMsg, history) {
136
+ abortController = new AbortController();
137
+ status = "streaming";
138
+ emit();
139
+ try {
140
+ const stream = transport.send({
141
+ conversationId,
142
+ message: userMsg,
143
+ history,
144
+ parentId: assistantMsg.parentId,
145
+ signal: abortController.signal
146
+ });
147
+ for await (const chunk of stream) {
148
+ if (chunk.content !== void 0) assistantMsg.content = chunk.content;
149
+ if (chunk.delta) assistantMsg.content += chunk.delta;
150
+ if (chunk.metadata) {
151
+ assistantMsg.metadata = { ...assistantMsg.metadata, ...chunk.metadata };
152
+ }
153
+ emit();
154
+ if (abortController.signal.aborted) break;
155
+ }
156
+ assistantMsg.status = "sent";
157
+ status = "idle";
158
+ error = null;
159
+ } catch (err) {
160
+ if (abortController.signal.aborted) {
161
+ assistantMsg.status = "sent";
162
+ status = "idle";
163
+ } else {
164
+ const message = err instanceof Error ? err.message : String(err);
165
+ assistantMsg.status = "error";
166
+ assistantMsg.error = message;
167
+ status = "error";
168
+ error = message;
169
+ }
170
+ } finally {
171
+ abortController = null;
172
+ touch(conversationId);
173
+ emit();
174
+ }
175
+ }
176
+ function historyFor(list, parentId) {
177
+ const scope = parentId ? selectThreadMessages(list, parentId) : selectRoots(list);
178
+ return scope.filter((m) => m.status !== "error");
179
+ }
180
+ const api = {
181
+ getState() {
182
+ return snapshot;
183
+ },
184
+ subscribe(listener) {
185
+ listeners.add(listener);
186
+ return () => listeners.delete(listener);
187
+ },
188
+ newConversation(opts) {
189
+ const conversation = createConversationInternal(opts ?? {});
190
+ openThreadRootId = null;
191
+ replyTarget = null;
192
+ emit();
193
+ return conversation;
194
+ },
195
+ selectConversation(conversationId) {
196
+ if (!conversations.has(conversationId) || activeConversationId === conversationId) return;
197
+ activeConversationId = conversationId;
198
+ openThreadRootId = null;
199
+ replyTarget = null;
200
+ emit();
201
+ },
202
+ deleteConversation(conversationId) {
203
+ if (!conversations.has(conversationId)) return;
204
+ conversations.delete(conversationId);
205
+ messagesByConv.delete(conversationId);
206
+ if (activeConversationId === conversationId) {
207
+ activeConversationId = orderedConversations()[0]?.id ?? null;
208
+ openThreadRootId = null;
209
+ replyTarget = null;
210
+ }
211
+ emit();
212
+ },
213
+ renameConversation(conversationId, title) {
214
+ const c = conversations.get(conversationId);
215
+ if (!c) return;
216
+ conversations.set(conversationId, { ...c, title });
217
+ emit();
218
+ },
219
+ appendMessage(message) {
220
+ const list = messagesByConv.get(message.conversationId);
221
+ if (!list) return;
222
+ list.push(message);
223
+ touch(message.conversationId);
224
+ emit();
225
+ },
226
+ editMessage(messageId, content) {
227
+ if (!activeConversationId) return;
228
+ const list = messagesByConv.get(activeConversationId);
229
+ const msg = list.find((m) => m.id === messageId);
230
+ if (!msg) return;
231
+ msg.content = content;
232
+ msg.edited = true;
233
+ emit();
234
+ },
235
+ deleteMessage(messageId) {
236
+ if (!activeConversationId) return;
237
+ const list = messagesByConv.get(activeConversationId);
238
+ const msg = list.find((m) => m.id === messageId);
239
+ if (!msg) return;
240
+ const removeIds = /* @__PURE__ */ new Set([messageId]);
241
+ if (!msg.parentId) {
242
+ for (const r of list) if (r.parentId === messageId) removeIds.add(r.id);
243
+ }
244
+ const next = list.filter((m) => !removeIds.has(m.id));
245
+ messagesByConv.set(activeConversationId, next);
246
+ if (openThreadRootId && removeIds.has(openThreadRootId)) openThreadRootId = null;
247
+ if (replyTarget && removeIds.has(replyTarget)) replyTarget = openThreadRootId;
248
+ emit();
249
+ },
250
+ react(messageId, emoji) {
251
+ if (!activeConversationId) return;
252
+ const list = messagesByConv.get(activeConversationId);
253
+ const msg = list.find((m) => m.id === messageId);
254
+ if (!msg) return;
255
+ const reactions = [...msg.reactions ?? []];
256
+ const idx = reactions.findIndex((r) => r.emoji === emoji);
257
+ if (idx === -1) {
258
+ reactions.push({ emoji, count: 1, userReacted: true });
259
+ } else {
260
+ const r = reactions[idx];
261
+ if (r.userReacted) {
262
+ const count = r.count - 1;
263
+ if (count <= 0) reactions.splice(idx, 1);
264
+ else reactions[idx] = { ...r, count, userReacted: false };
265
+ } else {
266
+ reactions[idx] = { ...r, count: r.count + 1, userReacted: true };
267
+ }
268
+ }
269
+ msg.reactions = reactions;
270
+ emit();
271
+ },
272
+ async sendMessage(content, opts) {
273
+ const trimmed = content.trim();
274
+ if (!trimmed && !opts?.attachments?.length) return;
275
+ const conversationId = ensureActiveConversation(opts);
276
+ const list = messagesByConv.get(conversationId);
277
+ const parentId = opts?.replyTo ? rootIdOf(list, opts.replyTo) : void 0;
278
+ const replyToId = opts?.replyTo;
279
+ const isFirstRoot = !parentId && selectRoots(list).length === 0;
280
+ const userMsg = {
281
+ id: generateId("rfr-msg"),
282
+ conversationId,
283
+ role: "user",
284
+ author: currentUser,
285
+ content: trimmed,
286
+ timestamp: now(),
287
+ status: "sent",
288
+ parentId,
289
+ replyToId,
290
+ attachments: opts?.attachments,
291
+ metadata: opts?.metadata
292
+ };
293
+ list.push(userMsg);
294
+ const conversation = conversations.get(conversationId);
295
+ if (isFirstRoot && conversation.title === DEFAULT_TITLE) {
296
+ conversations.set(conversationId, { ...conversation, title: generateTitle(trimmed) });
297
+ }
298
+ touch(conversationId);
299
+ if (!transport) {
300
+ status = "idle";
301
+ emit();
302
+ return;
303
+ }
304
+ const history = historyFor(list, parentId).filter((m) => m.id !== userMsg.id);
305
+ const assistantMsg = {
306
+ id: generateId("rfr-msg"),
307
+ conversationId,
308
+ role: "assistant",
309
+ author: assistant,
310
+ content: "",
311
+ timestamp: now(),
312
+ status: "streaming",
313
+ parentId
314
+ };
315
+ list.push(assistantMsg);
316
+ await streamReply(conversationId, assistantMsg, userMsg, history);
317
+ },
318
+ async retryLast() {
319
+ if (!activeConversationId || !transport) return;
320
+ const list = messagesByConv.get(activeConversationId);
321
+ const last = list[list.length - 1];
322
+ const parentId = last?.parentId;
323
+ if (last && last.role === "assistant" && last.status === "error") list.pop();
324
+ const scope = parentId ? selectThreadMessages(list, parentId) : selectRoots(list);
325
+ const lastUser = [...scope].reverse().find((m) => m.role === "user");
326
+ if (!lastUser) return;
327
+ const history = historyFor(list, parentId);
328
+ const assistantMsg = {
329
+ id: generateId("rfr-msg"),
330
+ conversationId: activeConversationId,
331
+ role: "assistant",
332
+ author: assistant,
333
+ content: "",
334
+ timestamp: now(),
335
+ status: "streaming",
336
+ parentId
337
+ };
338
+ list.push(assistantMsg);
339
+ await streamReply(activeConversationId, assistantMsg, lastUser, history);
340
+ },
341
+ stop() {
342
+ abortController?.abort();
343
+ },
344
+ openThread(rootId) {
345
+ openThreadRootId = rootId;
346
+ replyTarget = rootId;
347
+ emit();
348
+ },
349
+ replyTo(messageId) {
350
+ if (!activeConversationId) return;
351
+ const list = messagesByConv.get(activeConversationId);
352
+ openThreadRootId = rootIdOf(list, messageId);
353
+ replyTarget = messageId;
354
+ emit();
355
+ },
356
+ closeThread() {
357
+ if (openThreadRootId === null && replyTarget === null) return;
358
+ openThreadRootId = null;
359
+ replyTarget = null;
360
+ emit();
361
+ },
362
+ setThreadingMode(mode) {
363
+ if (threadingMode === mode) return;
364
+ threadingMode = mode;
365
+ emit();
366
+ },
367
+ setTransport(next) {
368
+ transport = next;
369
+ }
370
+ };
371
+ return api;
372
+ }
373
+
374
+ // ../react-conversation/dist/index.js
375
+ function useConversation(config) {
376
+ const apiRef = React2.useRef(null);
377
+ if (apiRef.current === null) {
378
+ apiRef.current = createConversation(config);
379
+ }
380
+ const api = apiRef.current;
381
+ const state = React2.useSyncExternalStore(api.subscribe, api.getState, api.getState);
382
+ const transport = config?.transport;
383
+ React2.useEffect(() => {
384
+ if (transport) api.setTransport(transport);
385
+ }, [api, transport]);
386
+ return {
387
+ state,
388
+ api,
389
+ sendMessage: api.sendMessage,
390
+ newConversation: api.newConversation,
391
+ selectConversation: api.selectConversation,
392
+ deleteConversation: api.deleteConversation,
393
+ renameConversation: api.renameConversation,
394
+ editMessage: api.editMessage,
395
+ deleteMessage: api.deleteMessage,
396
+ react: api.react,
397
+ retryLast: api.retryLast,
398
+ stop: api.stop,
399
+ openThread: api.openThread,
400
+ replyTo: api.replyTo,
401
+ closeThread: api.closeThread,
402
+ setThreadingMode: api.setThreadingMode
403
+ };
404
+ }
405
+ var h = React2.createElement;
406
+ var svg = (children, size = 16) => h(
407
+ "svg",
408
+ {
409
+ xmlns: "http://www.w3.org/2000/svg",
410
+ viewBox: "0 0 24 24",
411
+ width: size,
412
+ height: size,
413
+ fill: "none",
414
+ stroke: "currentColor",
415
+ strokeWidth: 2,
416
+ strokeLinecap: "round",
417
+ strokeLinejoin: "round",
418
+ "aria-hidden": true
419
+ },
420
+ children
421
+ );
422
+ var IconAttach = () => svg(h("path", { d: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 17.93 8.81l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48" }));
423
+ var IconBold = () => svg([
424
+ h("path", { key: "a", d: "M6 4h8a4 4 0 0 1 0 8H6z" }),
425
+ h("path", { key: "b", d: "M6 12h9a4 4 0 0 1 0 8H6z" })
426
+ ]);
427
+ var IconItalic = () => svg([
428
+ h("line", { key: "a", x1: 19, y1: 4, x2: 10, y2: 4 }),
429
+ h("line", { key: "b", x1: 14, y1: 20, x2: 5, y2: 20 }),
430
+ h("line", { key: "c", x1: 15, y1: 4, x2: 9, y2: 20 })
431
+ ]);
432
+ var IconCode = () => svg([
433
+ h("polyline", { key: "a", points: "16 18 22 12 16 6" }),
434
+ h("polyline", { key: "b", points: "8 6 2 12 8 18" })
435
+ ]);
436
+ var IconLink = () => svg([
437
+ h("path", { key: "a", d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
438
+ h("path", { key: "b", d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
439
+ ]);
440
+ var IconQuote = () => svg([
441
+ h("path", { key: "a", d: "M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z" }),
442
+ h("path", { key: "b", d: "M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z" })
443
+ ]);
444
+ var IconListUl = () => svg([
445
+ h("line", { key: "a", x1: 8, y1: 6, x2: 21, y2: 6 }),
446
+ h("line", { key: "b", x1: 8, y1: 12, x2: 21, y2: 12 }),
447
+ h("line", { key: "c", x1: 8, y1: 18, x2: 21, y2: 18 }),
448
+ h("line", { key: "d", x1: 3, y1: 6, x2: 3.01, y2: 6 }),
449
+ h("line", { key: "e", x1: 3, y1: 12, x2: 3.01, y2: 12 }),
450
+ h("line", { key: "f", x1: 3, y1: 18, x2: 3.01, y2: 18 })
451
+ ]);
452
+ var IconListOl = () => svg([
453
+ h("line", { key: "a", x1: 10, y1: 6, x2: 21, y2: 6 }),
454
+ h("line", { key: "b", x1: 10, y1: 12, x2: 21, y2: 12 }),
455
+ h("line", { key: "c", x1: 10, y1: 18, x2: 21, y2: 18 }),
456
+ h("path", { key: "d", d: "M4 6h1v4" }),
457
+ h("path", { key: "e", d: "M4 10h2" }),
458
+ h("path", { key: "f", d: "M6 18H4c0-1 2-2 2-3s-1-1.5-2-1" })
459
+ ]);
460
+ var IconArrowUp = () => svg([
461
+ h("path", { key: "a", d: "M12 19V5" }),
462
+ h("path", { key: "b", d: "m5 12 7-7 7 7" })
463
+ ]);
464
+ var IconStop = () => svg(h("rect", { x: 7, y: 7, width: 10, height: 10, rx: 1.5 }));
465
+ var EMOJI = {
466
+ smile: "\u{1F604}",
467
+ grin: "\u{1F601}",
468
+ joy: "\u{1F602}",
469
+ rofl: "\u{1F923}",
470
+ wink: "\u{1F609}",
471
+ heart_eyes: "\u{1F60D}",
472
+ thinking: "\u{1F914}",
473
+ neutral: "\u{1F610}",
474
+ sob: "\u{1F62D}",
475
+ scream: "\u{1F631}",
476
+ tada: "\u{1F389}",
477
+ fire: "\u{1F525}",
478
+ heart: "\u2764\uFE0F",
479
+ thumbsup: "\u{1F44D}",
480
+ thumbsdown: "\u{1F44E}",
481
+ clap: "\u{1F44F}",
482
+ pray: "\u{1F64F}",
483
+ eyes: "\u{1F440}",
484
+ rocket: "\u{1F680}",
485
+ sparkles: "\u2728",
486
+ star: "\u2B50",
487
+ check: "\u2705",
488
+ x: "\u274C",
489
+ warning: "\u26A0\uFE0F",
490
+ bulb: "\u{1F4A1}",
491
+ bug: "\u{1F41B}",
492
+ wave: "\u{1F44B}",
493
+ ok_hand: "\u{1F44C}",
494
+ muscle: "\u{1F4AA}",
495
+ "100": "\u{1F4AF}",
496
+ poop: "\u{1F4A9}",
497
+ ghost: "\u{1F47B}",
498
+ robot: "\u{1F916}",
499
+ cat: "\u{1F431}",
500
+ dog: "\u{1F436}",
501
+ coffee: "\u2615",
502
+ pizza: "\u{1F355}",
503
+ beer: "\u{1F37A}",
504
+ sun: "\u2600\uFE0F",
505
+ moon: "\u{1F319}",
506
+ zap: "\u26A1"
507
+ };
508
+ var TRIGGER_CHARS = ["/", "@", ":"];
509
+ var TRIGGER_QUERY_PATTERN = /^[\w+-]*$/;
510
+ function detectTrigger(text, caret) {
511
+ let best = null;
512
+ for (const type of TRIGGER_CHARS) {
513
+ const hit = detectTriggerInText(text, caret, type);
514
+ if (!hit || !TRIGGER_QUERY_PATTERN.test(hit.query)) continue;
515
+ if (!best || hit.start > best.start) {
516
+ best = { type, query: hit.query, start: hit.start, end: caret };
517
+ }
518
+ }
519
+ return best;
520
+ }
521
+ var Composer = React2.forwardRef(
522
+ function Composer2({ placeholder = "Send a message\u2026", busy = false, slashCommands = [], mentions, toolbar = true, emoji = true, attachments = true, error, onRetry, onSubmit, onStop, onSlashCommand, autoFocus }, ref) {
523
+ const [value, setValue] = React2.useState("");
524
+ const [pending, setPending] = React2.useState([]);
525
+ const [trigger, setTrigger] = React2.useState(null);
526
+ const [active, setActive] = React2.useState(0);
527
+ const [mentionItems, setMentionItems] = React2.useState([]);
528
+ const textareaRef = React2.useRef(null);
529
+ const fileRef = React2.useRef(null);
530
+ React2.useEffect(() => {
531
+ if (trigger?.type !== "@" || !mentions) {
532
+ setMentionItems([]);
533
+ return;
534
+ }
535
+ if (Array.isArray(mentions)) {
536
+ const q = trigger.query.toLowerCase();
537
+ setMentionItems(mentions.filter((m) => m.label.toLowerCase().includes(q)).slice(0, 8));
538
+ return;
539
+ }
540
+ let cancelled = false;
541
+ Promise.resolve(mentions(trigger.query)).then((res) => {
542
+ if (!cancelled) setMentionItems(res.slice(0, 8));
543
+ });
544
+ return () => {
545
+ cancelled = true;
546
+ };
547
+ }, [trigger, mentions]);
548
+ const items = React2.useMemo(() => {
549
+ if (!trigger) return [];
550
+ const q = trigger.query.toLowerCase();
551
+ if (trigger.type === "/") {
552
+ return slashCommands.filter((c) => c.label.toLowerCase().includes(q) || c.id.toLowerCase().includes(q)).slice(0, 8).map((c) => ({ key: c.id, primary: c.label, secondary: c.description, icon: c.icon, apply: c.insertText ?? "", runCmd: c }));
553
+ }
554
+ if (trigger.type === "@") {
555
+ return mentionItems.map((m) => ({ key: m.id, primary: m.label, icon: m.avatarUrl ? "" : "@", apply: `@${m.label} ` }));
556
+ }
557
+ if (trigger.type === ":" && emoji) {
558
+ return Object.entries(EMOJI).filter(([name]) => name.includes(q)).slice(0, 8).map(([name, char]) => ({ key: name, primary: `${char} :${name}:`, apply: char }));
559
+ }
560
+ return [];
561
+ }, [trigger, slashCommands, mentionItems, emoji]);
562
+ const menuOpen = trigger !== null && items.length > 0;
563
+ React2.useEffect(() => setActive(0), [trigger?.type, trigger?.query]);
564
+ function syncFromTextarea(el) {
565
+ setValue(el.value);
566
+ setTrigger(detectTrigger(el.value, el.selectionStart ?? el.value.length));
567
+ }
568
+ function selectItem(i) {
569
+ const item = items[i];
570
+ if (!item || !trigger) return;
571
+ if (trigger.type === "/" && !item.apply && item.runCmd) {
572
+ const next2 = value.slice(0, trigger.start) + value.slice(trigger.end);
573
+ setValue(next2);
574
+ setTrigger(null);
575
+ onSlashCommand?.(item.runCmd);
576
+ queueCaret(trigger.start);
577
+ return;
578
+ }
579
+ const next = value.slice(0, trigger.start) + item.apply + value.slice(trigger.end);
580
+ setValue(next);
581
+ setTrigger(null);
582
+ queueCaret(trigger.start + item.apply.length);
583
+ }
584
+ function queueCaret(pos) {
585
+ requestAnimationFrame(() => {
586
+ const el = textareaRef.current;
587
+ if (!el) return;
588
+ el.focus();
589
+ el.setSelectionRange(pos, pos);
590
+ });
591
+ }
592
+ function format(kind) {
593
+ const el = textareaRef.current;
594
+ if (!el) return;
595
+ const start = el.selectionStart ?? 0;
596
+ const end = el.selectionEnd ?? 0;
597
+ const sel = value.slice(start, end);
598
+ let replacement = sel;
599
+ let caretOffset = 0;
600
+ if (kind === "bold") replacement = `**${sel || "bold"}**`;
601
+ else if (kind === "italic") replacement = `*${sel || "italic"}*`;
602
+ else if (kind === "code") replacement = sel.includes("\n") ? `
603
+ \`\`\`
604
+ ${sel}
605
+ \`\`\`
606
+ ` : `\`${sel || "code"}\``;
607
+ else if (kind === "link") {
608
+ replacement = `[${sel || "text"}](url)`;
609
+ caretOffset = replacement.length - 4;
610
+ } else if (kind === "quote" || kind === "ul" || kind === "ol") {
611
+ const prefix = kind === "quote" ? "> " : kind === "ul" ? "- " : "1. ";
612
+ const block = (sel || "item").split("\n").map((l) => prefix + l).join("\n");
613
+ replacement = (start > 0 && value[start - 1] !== "\n" ? "\n" : "") + block;
614
+ }
615
+ const next = value.slice(0, start) + replacement + value.slice(end);
616
+ setValue(next);
617
+ const caret = caretOffset ? start + caretOffset : start + replacement.length;
618
+ queueCaret(caret);
619
+ }
620
+ function submit() {
621
+ const text = value.trim();
622
+ if (!text && pending.length === 0 || busy) return;
623
+ onSubmit(text, pending.length ? pending : void 0);
624
+ setValue("");
625
+ setPending([]);
626
+ setTrigger(null);
627
+ }
628
+ function onFiles(files) {
629
+ if (!files) return;
630
+ setPending((p) => [
631
+ ...p,
632
+ ...Array.from(files).map((f) => ({
633
+ id: `${f.name}-${f.size}-${f.lastModified}`,
634
+ name: f.name,
635
+ url: URL.createObjectURL(f),
636
+ type: f.type || "application/octet-stream",
637
+ size: f.size
638
+ }))
639
+ ]);
640
+ }
641
+ function onKeyDown(e) {
642
+ if (menuOpen) {
643
+ if (e.key === "ArrowDown") return e.preventDefault(), setActive((a) => (a + 1) % items.length);
644
+ if (e.key === "ArrowUp") return e.preventDefault(), setActive((a) => (a - 1 + items.length) % items.length);
645
+ if (e.key === "Enter" || e.key === "Tab") return e.preventDefault(), selectItem(active);
646
+ if (e.key === "Escape") return e.preventDefault(), setTrigger(null);
647
+ }
648
+ const mod = e.metaKey || e.ctrlKey;
649
+ if (mod && e.key.toLowerCase() === "b") return e.preventDefault(), format("bold");
650
+ if (mod && e.key.toLowerCase() === "i") return e.preventDefault(), format("italic");
651
+ if (mod && e.key.toLowerCase() === "e") return e.preventDefault(), format("code");
652
+ if (mod && e.key.toLowerCase() === "k") return e.preventDefault(), format("link");
653
+ if (e.key === "Enter" && !e.shiftKey) {
654
+ e.preventDefault();
655
+ submit();
656
+ }
657
+ }
658
+ const iconBtn = "flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent hover:text-foreground";
659
+ const toolbarBtn = (icon, title, kind) => h(
660
+ "button",
661
+ {
662
+ key: kind,
663
+ type: "button",
664
+ title,
665
+ "aria-label": title,
666
+ className: iconBtn,
667
+ onMouseDown: (e) => e.preventDefault(),
668
+ // keep textarea selection
669
+ onClick: () => format(kind)
670
+ },
671
+ icon
672
+ );
673
+ const divider = () => h("span", { className: "mx-1 h-5 w-px bg-border", "aria-hidden": true });
674
+ const menu = menuOpen ? h(
675
+ "div",
676
+ {
677
+ className: "absolute bottom-full left-0 z-20 mb-2 w-72 overflow-hidden rounded-xl border border-border bg-popover shadow-lg",
678
+ role: "listbox"
679
+ },
680
+ h(
681
+ "div",
682
+ { className: "border-b border-border px-3 py-1.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground" },
683
+ trigger?.type === "/" ? "Commands" : trigger?.type === "@" ? "Mentions" : "Emoji"
684
+ ),
685
+ ...items.map(
686
+ (it, i) => h(
687
+ "button",
688
+ {
689
+ key: it.key,
690
+ type: "button",
691
+ role: "option",
692
+ "aria-selected": i === active,
693
+ className: cn(
694
+ "flex w-full items-center gap-2 px-3 py-2 text-left text-sm",
695
+ i === active ? "bg-accent" : "hover:bg-accent/50"
696
+ ),
697
+ onMouseEnter: () => setActive(i),
698
+ onMouseDown: (e) => e.preventDefault(),
699
+ onClick: () => selectItem(i)
700
+ },
701
+ it.icon ? h("span", { className: "w-4 text-center text-muted-foreground" }, it.icon) : null,
702
+ h("span", { className: "flex-1 truncate" }, it.primary),
703
+ it.secondary ? h("span", { className: "truncate text-xs text-muted-foreground" }, it.secondary) : null
704
+ )
705
+ )
706
+ ) : null;
707
+ return h(
708
+ "div",
709
+ { ref, className: "p-3" },
710
+ h(
711
+ "div",
712
+ { className: "relative" },
713
+ menu,
714
+ // unified input card
715
+ h(
716
+ "div",
717
+ {
718
+ className: (
719
+ // Subtle focus: slight border darkening + a soft shadow lift — no
720
+ // bright colored ring (matches Claude/ChatGPT).
721
+ "overflow-hidden rounded-2xl border border-border bg-background shadow-sm transition-shadow focus-within:border-foreground/25 focus-within:shadow-md"
722
+ )
723
+ },
724
+ // error banner
725
+ error ? h(
726
+ "div",
727
+ { className: "flex items-center gap-2 border-b border-border bg-destructive/5 px-3 py-2 text-xs text-destructive", role: "alert" },
728
+ h("span", { className: "flex-1 truncate" }, error),
729
+ onRetry ? h("button", { type: "button", className: "font-medium underline", onClick: () => onRetry() }, "Retry") : null
730
+ ) : null,
731
+ // attachment chips
732
+ pending.length > 0 ? h(
733
+ "div",
734
+ { className: "flex flex-wrap gap-2 px-3 pt-3" },
735
+ ...pending.map(
736
+ (a) => h(
737
+ "span",
738
+ { key: a.id, className: "inline-flex items-center gap-1 rounded-md bg-muted px-2 py-0.5 text-xs" },
739
+ a.name,
740
+ h(
741
+ "button",
742
+ { type: "button", className: "text-muted-foreground hover:text-destructive", onClick: () => setPending((p) => p.filter((x) => x.id !== a.id)) },
743
+ "\u2715"
744
+ )
745
+ )
746
+ )
747
+ ) : null,
748
+ // textarea (borderless)
749
+ h("textarea", {
750
+ ref: textareaRef,
751
+ className: "block max-h-40 w-full resize-none bg-transparent px-3.5 py-3 text-sm placeholder:text-muted-foreground focus:outline-none",
752
+ rows: 1,
753
+ value,
754
+ placeholder,
755
+ autoFocus,
756
+ "aria-label": "Message",
757
+ onChange: (e) => syncFromTextarea(e.target),
758
+ onClick: (e) => syncFromTextarea(e.currentTarget),
759
+ onKeyUp: (e) => syncFromTextarea(e.currentTarget),
760
+ onKeyDown,
761
+ onBlur: () => setTimeout(() => setTrigger(null), 120)
762
+ }),
763
+ // bottom action bar
764
+ h(
765
+ "div",
766
+ { className: "flex items-center gap-0.5 px-2 pb-2" },
767
+ attachments ? h(
768
+ React2.Fragment,
769
+ null,
770
+ h("input", {
771
+ ref: fileRef,
772
+ type: "file",
773
+ accept: "image/*",
774
+ multiple: true,
775
+ className: "hidden",
776
+ onChange: (e) => {
777
+ onFiles(e.target.files);
778
+ e.target.value = "";
779
+ }
780
+ }),
781
+ h("button", { type: "button", className: iconBtn, "aria-label": "Attach image or GIF", onClick: () => fileRef.current?.click() }, h(IconAttach))
782
+ ) : null,
783
+ attachments && toolbar ? divider() : null,
784
+ toolbar ? h(
785
+ React2.Fragment,
786
+ null,
787
+ toolbarBtn(h(IconBold), "Bold (\u2318B)", "bold"),
788
+ toolbarBtn(h(IconItalic), "Italic (\u2318I)", "italic"),
789
+ toolbarBtn(h(IconCode), "Code (\u2318E)", "code"),
790
+ toolbarBtn(h(IconLink), "Link (\u2318K)", "link"),
791
+ divider(),
792
+ toolbarBtn(h(IconQuote), "Quote", "quote"),
793
+ toolbarBtn(h(IconListUl), "Bulleted list", "ul"),
794
+ toolbarBtn(h(IconListOl), "Numbered list", "ol")
795
+ ) : null,
796
+ h("div", { className: "flex-1" }),
797
+ busy ? h(
798
+ "button",
799
+ { type: "button", "aria-label": "Stop", className: "flex h-9 w-9 items-center justify-center rounded-full bg-primary text-primary-foreground transition hover:opacity-90", onClick: () => onStop?.() },
800
+ h(IconStop)
801
+ ) : h(
802
+ "button",
803
+ {
804
+ type: "button",
805
+ "aria-label": "Send",
806
+ className: "flex h-9 w-9 items-center justify-center rounded-full bg-primary text-primary-foreground transition hover:opacity-90 disabled:bg-muted disabled:text-muted-foreground disabled:opacity-100",
807
+ disabled: !value.trim() && pending.length === 0,
808
+ onClick: submit
809
+ },
810
+ h(IconArrowUp)
811
+ )
812
+ )
813
+ )
814
+ )
815
+ );
816
+ }
817
+ );
818
+ var h2 = React2.createElement;
819
+ var QUICK_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F389}", "\u{1F440}", "\u{1F64F}"];
820
+ var chatMd = cn(
821
+ "max-w-none text-sm",
822
+ "[&_p]:my-1 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0",
823
+ "[&_pre]:my-2 [&_pre]:rounded-md [&_pre]:text-xs [&_pre]:leading-relaxed",
824
+ "[&_ul]:my-1 [&_ol]:my-1 [&_li]:my-0.5",
825
+ "[&_h1]:text-base [&_h1]:font-semibold [&_h1]:mt-2 [&_h1]:mb-1",
826
+ "[&_h2]:text-sm [&_h2]:font-semibold [&_h2]:mt-2 [&_h2]:mb-1",
827
+ "[&_h3]:text-sm [&_h3]:font-semibold",
828
+ "[&_blockquote]:my-1 [&_blockquote]:border-l-2 [&_blockquote]:pl-2 [&_blockquote]:not-italic [&_blockquote]:text-muted-foreground",
829
+ "[&_img]:my-1 [&_img]:max-h-60 [&_img]:rounded-md",
830
+ "[&_code]:text-[0.85em]"
831
+ );
832
+ function Avatar({ name, avatarUrl, size = 8 }) {
833
+ return h2(
834
+ "div",
835
+ {
836
+ className: cn(
837
+ "flex-shrink-0 overflow-hidden rounded-full bg-muted flex items-center justify-center text-xs font-medium",
838
+ size === 8 ? "h-8 w-8" : "h-7 w-7"
839
+ )
840
+ },
841
+ avatarUrl ? h2("img", { src: avatarUrl, alt: name, className: "h-full w-full object-cover" }) : (name.charAt(0) || "?").toUpperCase()
842
+ );
843
+ }
844
+ function TypingDots() {
845
+ return h2(
846
+ "div",
847
+ { className: "flex items-center gap-1 py-1.5", "aria-label": "Assistant is typing" },
848
+ ...[0, 150, 300].map(
849
+ (delay) => h2("span", {
850
+ key: delay,
851
+ className: "h-1.5 w-1.5 rounded-full bg-muted-foreground/60 animate-bounce",
852
+ style: { animationDelay: `${delay}ms` }
853
+ })
854
+ )
855
+ );
856
+ }
857
+ function Attachments({ attachments }) {
858
+ return h2(
859
+ "div",
860
+ { className: "mt-2 flex flex-wrap gap-2" },
861
+ ...attachments.map(
862
+ (a) => a.type.startsWith("image/") ? h2("img", { key: a.id, src: a.url, alt: a.name, className: "max-h-60 rounded-md border border-border object-contain" }) : h2(
863
+ "a",
864
+ {
865
+ key: a.id,
866
+ href: a.url,
867
+ target: "_blank",
868
+ rel: "noreferrer",
869
+ className: "inline-flex items-center gap-2 rounded-md border border-border bg-background/60 px-2 py-1 text-xs"
870
+ },
871
+ "\u{1F4CE} ",
872
+ a.name
873
+ )
874
+ )
875
+ );
876
+ }
877
+ function MessageBody({ message }) {
878
+ if (message.status === "streaming" && message.content === "") return h2(TypingDots);
879
+ return h2(
880
+ React2.Fragment,
881
+ null,
882
+ message.content ? h2(MarkdownRenderer, { content: message.content, size: "sm", className: chatMd }) : null,
883
+ message.attachments && message.attachments.length > 0 ? h2(Attachments, { attachments: message.attachments }) : null,
884
+ message.status === "error" ? h2("div", { className: "mt-1 text-xs text-destructive", role: "alert" }, message.error ?? "Failed to send.") : null
885
+ );
886
+ }
887
+ function Reactions({ message, onReact, align }) {
888
+ if (!message.reactions || message.reactions.length === 0) return null;
889
+ return h2(
890
+ "div",
891
+ { className: cn("mt-1 flex flex-wrap gap-1", align === "end" && "justify-end") },
892
+ ...message.reactions.map(
893
+ (r) => h2(
894
+ "button",
895
+ {
896
+ key: r.emoji,
897
+ type: "button",
898
+ onClick: () => onReact(r.emoji),
899
+ className: cn(
900
+ "inline-flex items-center gap-1 rounded-full border px-1.5 py-0.5 text-xs",
901
+ r.userReacted ? "border-primary bg-primary/10" : "border-border bg-background"
902
+ )
903
+ },
904
+ `${r.emoji} ${r.count}`
905
+ )
906
+ )
907
+ );
908
+ }
909
+ function QuotedParent({ parent, onClick }) {
910
+ const snippet = parent.content.length > 80 ? `${parent.content.slice(0, 80)}\u2026` : parent.content;
911
+ return h2(
912
+ "button",
913
+ {
914
+ type: "button",
915
+ onClick,
916
+ className: "mb-1 flex max-w-full items-start gap-2 rounded-md border-l-2 border-primary/50 bg-muted/50 px-2 py-1 text-left text-xs text-muted-foreground hover:bg-muted"
917
+ },
918
+ h2("span", { className: "font-medium" }, parent.author.name),
919
+ h2("span", { className: "truncate" }, snippet)
920
+ );
921
+ }
922
+ function HoverActions({
923
+ message,
924
+ conversation,
925
+ isOwn,
926
+ onEdit,
927
+ onToggleEmojis,
928
+ align
929
+ }) {
930
+ const { replyTo, deleteMessage } = conversation;
931
+ return h2(
932
+ "div",
933
+ {
934
+ className: cn(
935
+ "mt-1 flex items-center gap-3 text-xs text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100",
936
+ align === "end" && "justify-end"
937
+ )
938
+ },
939
+ // Reply targets this specific message but groups under the originating root.
940
+ h2("button", { type: "button", className: "hover:text-foreground", onClick: () => replyTo(message.id) }, "Reply"),
941
+ h2("button", { type: "button", className: "hover:text-foreground", onClick: onToggleEmojis }, "React"),
942
+ isOwn ? h2("button", { type: "button", className: "hover:text-foreground", onClick: onEdit }, "Edit") : null,
943
+ isOwn ? h2("button", { type: "button", className: "hover:text-destructive", onClick: () => deleteMessage(message.id) }, "Delete") : null
944
+ );
945
+ }
946
+ function EmojiRow({ onPick, align }) {
947
+ return h2(
948
+ "div",
949
+ { className: cn("mt-1 flex gap-1", align === "end" && "justify-end") },
950
+ ...QUICK_EMOJIS.map(
951
+ (emoji) => h2("button", { key: emoji, type: "button", className: "rounded px-1 text-base hover:bg-accent", onClick: () => onPick(emoji) }, emoji)
952
+ )
953
+ );
954
+ }
955
+ function EditField({ message, conversation, onDone }) {
956
+ const [draft, setDraft] = React2.useState(message.content);
957
+ function save() {
958
+ const t = draft.trim();
959
+ if (t && t !== message.content) conversation.editMessage(message.id, t);
960
+ onDone();
961
+ }
962
+ return h2(
963
+ "div",
964
+ { className: "mt-1" },
965
+ h2("textarea", {
966
+ className: "w-full resize-none rounded-md border border-border bg-background px-2 py-1 text-sm",
967
+ value: draft,
968
+ autoFocus: true,
969
+ onChange: (e) => setDraft(e.target.value),
970
+ onKeyDown: (e) => {
971
+ if (e.key === "Enter" && !e.shiftKey) {
972
+ e.preventDefault();
973
+ save();
974
+ }
975
+ if (e.key === "Escape") onDone();
976
+ }
977
+ }),
978
+ h2(
979
+ "div",
980
+ { className: "mt-1 flex gap-2 text-xs" },
981
+ h2("button", { type: "button", className: "text-primary", onClick: save }, "Save"),
982
+ h2("button", { type: "button", className: "text-muted-foreground", onClick: onDone }, "Cancel")
983
+ )
984
+ );
985
+ }
986
+ function MessageRow({
987
+ message,
988
+ conversation,
989
+ currentUserId,
990
+ showThreadAffordance,
991
+ quotedParent
992
+ }) {
993
+ const { state, react, openThread } = conversation;
994
+ const [showEmojis, setShowEmojis] = React2.useState(false);
995
+ const [editing, setEditing] = React2.useState(false);
996
+ const isUser = currentUserId ? message.author.id === currentUserId : message.role === "user";
997
+ const replyCount = getReplyCount(state.messages, message.id);
998
+ const align = isUser ? "end" : "start";
999
+ const inner = h2(
1000
+ React2.Fragment,
1001
+ null,
1002
+ quotedParent ? h2(QuotedParent, { parent: quotedParent, onClick: () => openThread(rootIdOf(state.messages, quotedParent.id)) }) : null,
1003
+ editing ? h2(EditField, { message, conversation, onDone: () => setEditing(false) }) : isUser ? h2("div", { className: "inline-block rounded-2xl rounded-br-sm bg-primary/10 px-3 py-2 text-left" }, h2(MessageBody, { message })) : h2(MessageBody, { message }),
1004
+ h2(Reactions, { message, onReact: (e) => react(message.id, e), align }),
1005
+ showThreadAffordance && replyCount > 0 ? h2(
1006
+ "button",
1007
+ { type: "button", className: cn("mt-1 text-xs font-medium text-primary hover:underline", align === "end" && "self-end"), onClick: () => openThread(message.id) },
1008
+ `\u{1F4AC} ${replyCount} ${replyCount === 1 ? "reply" : "replies"}`
1009
+ ) : null,
1010
+ h2(HoverActions, { message, conversation, isOwn: isUser, onEdit: () => setEditing(true), onToggleEmojis: () => setShowEmojis((v) => !v), align }),
1011
+ showEmojis ? h2(EmojiRow, {
1012
+ onPick: (e) => {
1013
+ react(message.id, e);
1014
+ setShowEmojis(false);
1015
+ },
1016
+ align
1017
+ }) : null
1018
+ );
1019
+ if (!isUser) {
1020
+ return h2(
1021
+ "div",
1022
+ { className: "group flex gap-3 rounded-md px-3 py-2 hover:bg-accent/30", role: "article", "aria-label": `Message from ${message.author.name}`, "data-message-id": message.id },
1023
+ h2(Avatar, { name: message.author.name, avatarUrl: message.author.avatarUrl }),
1024
+ h2(
1025
+ "div",
1026
+ { className: "min-w-0 flex-1" },
1027
+ h2(
1028
+ "div",
1029
+ { className: "flex items-baseline gap-2" },
1030
+ h2("span", { className: "text-sm font-semibold" }, message.author.name),
1031
+ h2("span", { className: "text-xs text-muted-foreground", title: message.timestamp.toISOString() }, formatTimestamp(message.timestamp)),
1032
+ message.edited ? h2("span", { className: "text-xs text-muted-foreground" }, "(edited)") : null
1033
+ ),
1034
+ inner
1035
+ )
1036
+ );
1037
+ }
1038
+ return h2(
1039
+ "div",
1040
+ { className: "group flex flex-col items-end px-3 py-1.5", role: "article", "aria-label": `Message from ${message.author.name}`, "data-message-id": message.id },
1041
+ h2(
1042
+ "div",
1043
+ { className: "flex items-baseline gap-2" },
1044
+ message.edited ? h2("span", { className: "text-xs text-muted-foreground" }, "(edited)") : null,
1045
+ h2("span", { className: "text-xs text-muted-foreground", title: message.timestamp.toISOString() }, formatTimestamp(message.timestamp)),
1046
+ h2("span", { className: "text-sm font-semibold" }, message.author.name)
1047
+ ),
1048
+ h2("div", { className: "mt-0.5 flex max-w-[80%] flex-col items-end" }, inner)
1049
+ );
1050
+ }
1051
+ function ConversationSidebar({ conversation }) {
1052
+ const { state, newConversation, selectConversation, deleteConversation } = conversation;
1053
+ return h2(
1054
+ "aside",
1055
+ { className: "flex w-56 flex-col gap-1 overflow-y-auto border-r border-border bg-muted/20 p-2", "aria-label": "Conversations" },
1056
+ h2(
1057
+ "button",
1058
+ { type: "button", className: "mb-1 w-full rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground hover:opacity-90", onClick: () => newConversation() },
1059
+ "+ New chat"
1060
+ ),
1061
+ ...state.conversations.map(
1062
+ (conv) => h2(
1063
+ "div",
1064
+ {
1065
+ key: conv.id,
1066
+ role: "button",
1067
+ "aria-current": conv.id === state.activeConversationId,
1068
+ onClick: () => selectConversation(conv.id),
1069
+ className: cn(
1070
+ "group flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent/60",
1071
+ conv.id === state.activeConversationId && "bg-accent font-medium"
1072
+ )
1073
+ },
1074
+ h2(
1075
+ "div",
1076
+ { className: "min-w-0" },
1077
+ h2("div", { className: "truncate" }, conv.title),
1078
+ h2("div", { className: "truncate text-xs text-muted-foreground" }, formatRelativeTime(conv.updatedAt))
1079
+ ),
1080
+ h2(
1081
+ "button",
1082
+ {
1083
+ type: "button",
1084
+ className: "text-xs text-muted-foreground opacity-0 hover:text-destructive group-hover:opacity-100",
1085
+ "aria-label": `Delete ${conv.title}`,
1086
+ onClick: (e) => {
1087
+ e.stopPropagation();
1088
+ deleteConversation(conv.id);
1089
+ }
1090
+ },
1091
+ "\u2715"
1092
+ )
1093
+ )
1094
+ )
1095
+ );
1096
+ }
1097
+ function ThreadPanel({ conversation, currentUserId, composer }) {
1098
+ const { state } = conversation;
1099
+ const rootId = state.openThreadRootId;
1100
+ if (!rootId) return null;
1101
+ const messages = selectThreadMessages(state.messages, rootId);
1102
+ const target = state.replyTarget && state.replyTarget !== rootId ? findMessage(state.messages, state.replyTarget) : void 0;
1103
+ return h2(
1104
+ "aside",
1105
+ { className: "flex w-80 flex-col border-l border-border", "aria-label": "Thread" },
1106
+ h2(
1107
+ "div",
1108
+ { className: "flex items-center justify-between border-b border-border px-3 py-2" },
1109
+ h2("span", { className: "text-sm font-semibold" }, `Thread \xB7 ${messages.length - 1} ${messages.length - 1 === 1 ? "reply" : "replies"}`),
1110
+ h2("button", { type: "button", className: "text-muted-foreground hover:text-foreground", "aria-label": "Close thread", onClick: () => conversation.closeThread() }, "\u2715")
1111
+ ),
1112
+ h2(
1113
+ "div",
1114
+ { className: "flex-1 overflow-y-auto p-1" },
1115
+ ...messages.map((m) => h2(MessageRow, { key: m.id, message: m, conversation, currentUserId, showThreadAffordance: false }))
1116
+ ),
1117
+ target ? h2(
1118
+ "div",
1119
+ { className: "flex items-center justify-between gap-2 border-t border-border bg-muted/40 px-3 py-1 text-xs text-muted-foreground" },
1120
+ h2("span", { className: "truncate" }, `\u21B3 Replying to ${target.author.name}`),
1121
+ h2("button", { type: "button", className: "hover:text-foreground", onClick: () => conversation.openThread(rootId) }, "Reply to thread instead")
1122
+ ) : null,
1123
+ composer
1124
+ );
1125
+ }
1126
+ function ModeToggle({ conversation }) {
1127
+ const { state, setThreadingMode } = conversation;
1128
+ const opt = (mode, label) => h2(
1129
+ "button",
1130
+ {
1131
+ type: "button",
1132
+ onClick: () => setThreadingMode(mode),
1133
+ className: cn("rounded px-2 py-0.5 text-xs", state.threadingMode === mode ? "bg-background shadow-sm" : "text-muted-foreground")
1134
+ },
1135
+ label
1136
+ );
1137
+ return h2(
1138
+ "div",
1139
+ { className: "inline-flex rounded-md bg-muted p-0.5", role: "group", "aria-label": "Threading mode" },
1140
+ opt("inline", "Inline"),
1141
+ opt("panel", "Threads")
1142
+ );
1143
+ }
1144
+ var Chat = React2.forwardRef(
1145
+ function Chat2({ conversation, showConversationList = true, showModeToggle = true, placeholder, currentUserId, emptyState, className, slashCommands, mentions, onSlashCommand, composerToolbar = true }, ref) {
1146
+ const { state, sendMessage } = conversation;
1147
+ const timeline = selectMainTimeline(state.messages, state.threadingMode);
1148
+ const activeConv = state.conversations.find((c) => c.id === state.activeConversationId);
1149
+ const busy = state.status === "sending" || state.status === "streaming";
1150
+ const error = state.status === "error" ? state.error : null;
1151
+ const onRetry = () => void conversation.retryLast();
1152
+ const mainComposer = h2(Composer, {
1153
+ placeholder,
1154
+ busy,
1155
+ error,
1156
+ onRetry,
1157
+ slashCommands,
1158
+ mentions,
1159
+ onSlashCommand,
1160
+ toolbar: composerToolbar,
1161
+ onSubmit: (content, atts) => void sendMessage(content, { attachments: atts }),
1162
+ onStop: () => conversation.stop()
1163
+ });
1164
+ const threadComposer = state.openThreadRootId ? h2(Composer, {
1165
+ placeholder: "Reply\u2026",
1166
+ busy,
1167
+ error,
1168
+ onRetry,
1169
+ slashCommands,
1170
+ mentions,
1171
+ onSlashCommand,
1172
+ toolbar: composerToolbar,
1173
+ onSubmit: (content, atts) => void sendMessage(content, { replyTo: state.replyTarget ?? state.openThreadRootId, attachments: atts }),
1174
+ onStop: () => conversation.stop()
1175
+ }) : null;
1176
+ const body = timeline.length === 0 ? h2("div", { className: "flex flex-1 items-center justify-center p-6 text-sm text-muted-foreground" }, emptyState ?? "No messages yet. Say hello \u{1F44B}") : h2(
1177
+ "div",
1178
+ { className: "flex-1 space-y-0.5 overflow-y-auto p-2" },
1179
+ ...timeline.map(
1180
+ (m) => h2(MessageRow, {
1181
+ key: m.id,
1182
+ message: m,
1183
+ conversation,
1184
+ currentUserId,
1185
+ // Show the "N replies" count on originating messages in BOTH modes.
1186
+ showThreadAffordance: true,
1187
+ // Inline: quote the specific message replied to (falls back to the root).
1188
+ quotedParent: state.threadingMode === "inline" && m.parentId ? findMessage(state.messages, m.replyToId ?? m.parentId) : void 0
1189
+ })
1190
+ )
1191
+ );
1192
+ return h2(
1193
+ "div",
1194
+ { ref, className: cn("flex h-full min-h-0 overflow-hidden rounded-xl border border-border bg-background", className) },
1195
+ showConversationList ? h2(ConversationSidebar, { conversation }) : null,
1196
+ h2(
1197
+ "div",
1198
+ { className: "flex min-w-0 flex-1 flex-col" },
1199
+ h2(
1200
+ "div",
1201
+ { className: "flex items-center justify-between border-b border-border px-3 py-2" },
1202
+ h2("span", { className: "truncate text-sm font-semibold" }, activeConv?.title ?? "Chat"),
1203
+ showModeToggle ? h2(ModeToggle, { conversation }) : null
1204
+ ),
1205
+ body,
1206
+ mainComposer
1207
+ ),
1208
+ h2(ThreadPanel, { conversation, currentUserId, composer: threadComposer })
1209
+ );
1210
+ }
1211
+ );
1212
+ export {
1213
+ Chat,
1214
+ Composer,
1215
+ createConversation,
1216
+ findMessage,
1217
+ getReplyCount,
1218
+ rootIdOf,
1219
+ selectMainTimeline,
1220
+ selectReplies,
1221
+ selectRoots,
1222
+ selectThreadMessages,
1223
+ selectTimelineFromState,
1224
+ useConversation
1225
+ };
1226
+ //# sourceMappingURL=react-conversation.js.map