@cossistant/react 0.0.1

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 (372) hide show
  1. package/_virtual/rolldown_runtime.js +13 -0
  2. package/conversation.d.ts +312 -0
  3. package/conversation.d.ts.map +1 -0
  4. package/hooks/index.d.ts +23 -0
  5. package/hooks/index.js +24 -0
  6. package/hooks/private/store/use-conversations-store.d.ts +13 -0
  7. package/hooks/private/store/use-conversations-store.d.ts.map +1 -0
  8. package/hooks/private/store/use-conversations-store.js +34 -0
  9. package/hooks/private/store/use-conversations-store.js.map +1 -0
  10. package/hooks/private/store/use-store-selector.d.ts +10 -0
  11. package/hooks/private/store/use-store-selector.d.ts.map +1 -0
  12. package/hooks/private/store/use-store-selector.js +17 -0
  13. package/hooks/private/store/use-store-selector.js.map +1 -0
  14. package/hooks/private/store/use-website-store.d.ts +18 -0
  15. package/hooks/private/store/use-website-store.d.ts.map +1 -0
  16. package/hooks/private/store/use-website-store.js +39 -0
  17. package/hooks/private/store/use-website-store.js.map +1 -0
  18. package/hooks/private/use-client-query.d.ts +25 -0
  19. package/hooks/private/use-client-query.d.ts.map +1 -0
  20. package/hooks/private/use-client-query.js +122 -0
  21. package/hooks/private/use-client-query.js.map +1 -0
  22. package/hooks/private/use-default-messages.d.ts +18 -0
  23. package/hooks/private/use-default-messages.d.ts.map +1 -0
  24. package/hooks/private/use-default-messages.js +45 -0
  25. package/hooks/private/use-default-messages.js.map +1 -0
  26. package/hooks/private/use-grouped-messages.d.ts +54 -0
  27. package/hooks/private/use-grouped-messages.d.ts.map +1 -0
  28. package/hooks/private/use-grouped-messages.js +157 -0
  29. package/hooks/private/use-grouped-messages.js.map +1 -0
  30. package/hooks/private/use-multimodal-input.d.ts +40 -0
  31. package/hooks/private/use-multimodal-input.d.ts.map +1 -0
  32. package/hooks/private/use-multimodal-input.js +129 -0
  33. package/hooks/private/use-multimodal-input.js.map +1 -0
  34. package/hooks/private/use-rest-client.d.ts +17 -0
  35. package/hooks/private/use-rest-client.d.ts.map +1 -0
  36. package/hooks/private/use-rest-client.js +41 -0
  37. package/hooks/private/use-rest-client.js.map +1 -0
  38. package/hooks/private/use-visitor-typing-reporter.d.ts +19 -0
  39. package/hooks/private/use-visitor-typing-reporter.d.ts.map +1 -0
  40. package/hooks/private/use-visitor-typing-reporter.js +140 -0
  41. package/hooks/private/use-visitor-typing-reporter.js.map +1 -0
  42. package/hooks/use-composer-refocus.d.ts +20 -0
  43. package/hooks/use-composer-refocus.d.ts.map +1 -0
  44. package/hooks/use-composer-refocus.js +32 -0
  45. package/hooks/use-composer-refocus.js.map +1 -0
  46. package/hooks/use-conversation-auto-seen.d.ts +54 -0
  47. package/hooks/use-conversation-auto-seen.d.ts.map +1 -0
  48. package/hooks/use-conversation-auto-seen.js +106 -0
  49. package/hooks/use-conversation-auto-seen.js.map +1 -0
  50. package/hooks/use-conversation-history-page.d.ts +86 -0
  51. package/hooks/use-conversation-history-page.d.ts.map +1 -0
  52. package/hooks/use-conversation-history-page.js +97 -0
  53. package/hooks/use-conversation-history-page.js.map +1 -0
  54. package/hooks/use-conversation-lifecycle.d.ts +80 -0
  55. package/hooks/use-conversation-lifecycle.d.ts.map +1 -0
  56. package/hooks/use-conversation-lifecycle.js +54 -0
  57. package/hooks/use-conversation-lifecycle.js.map +1 -0
  58. package/hooks/use-conversation-page.d.ts +82 -0
  59. package/hooks/use-conversation-page.d.ts.map +1 -0
  60. package/hooks/use-conversation-page.js +138 -0
  61. package/hooks/use-conversation-page.js.map +1 -0
  62. package/hooks/use-conversation-seen.d.ts +17 -0
  63. package/hooks/use-conversation-seen.d.ts.map +1 -0
  64. package/hooks/use-conversation-seen.js +58 -0
  65. package/hooks/use-conversation-seen.js.map +1 -0
  66. package/hooks/use-conversation-timeline-items.d.ts +21 -0
  67. package/hooks/use-conversation-timeline-items.d.ts.map +1 -0
  68. package/hooks/use-conversation-timeline-items.js +87 -0
  69. package/hooks/use-conversation-timeline-items.js.map +1 -0
  70. package/hooks/use-conversation-typing.d.ts +13 -0
  71. package/hooks/use-conversation-typing.d.ts.map +1 -0
  72. package/hooks/use-conversation-typing.js +34 -0
  73. package/hooks/use-conversation-typing.js.map +1 -0
  74. package/hooks/use-conversation.d.ts +18 -0
  75. package/hooks/use-conversation.d.ts.map +1 -0
  76. package/hooks/use-conversation.js +44 -0
  77. package/hooks/use-conversation.js.map +1 -0
  78. package/hooks/use-conversations.d.ts +20 -0
  79. package/hooks/use-conversations.d.ts.map +1 -0
  80. package/hooks/use-conversations.js +68 -0
  81. package/hooks/use-conversations.js.map +1 -0
  82. package/hooks/use-create-conversation.d.ts +30 -0
  83. package/hooks/use-create-conversation.d.ts.map +1 -0
  84. package/hooks/use-create-conversation.js +67 -0
  85. package/hooks/use-create-conversation.js.map +1 -0
  86. package/hooks/use-home-page.d.ts +82 -0
  87. package/hooks/use-home-page.d.ts.map +1 -0
  88. package/hooks/use-home-page.js +89 -0
  89. package/hooks/use-home-page.js.map +1 -0
  90. package/hooks/use-message-composer.d.ts +88 -0
  91. package/hooks/use-message-composer.d.ts.map +1 -0
  92. package/hooks/use-message-composer.js +94 -0
  93. package/hooks/use-message-composer.js.map +1 -0
  94. package/hooks/use-realtime-support.d.ts +25 -0
  95. package/hooks/use-realtime-support.d.ts.map +1 -0
  96. package/hooks/use-realtime-support.js +29 -0
  97. package/hooks/use-realtime-support.js.map +1 -0
  98. package/hooks/use-send-message.d.ts +34 -0
  99. package/hooks/use-send-message.d.ts.map +1 -0
  100. package/hooks/use-send-message.js +118 -0
  101. package/hooks/use-send-message.js.map +1 -0
  102. package/hooks/use-visitor.d.ts +28 -0
  103. package/hooks/use-visitor.d.ts.map +1 -0
  104. package/hooks/use-visitor.js +59 -0
  105. package/hooks/use-visitor.js.map +1 -0
  106. package/hooks/use-window-visibility-focus.d.ts +9 -0
  107. package/hooks/use-window-visibility-focus.d.ts.map +1 -0
  108. package/hooks/use-window-visibility-focus.js +53 -0
  109. package/hooks/use-window-visibility-focus.js.map +1 -0
  110. package/identify-visitor.d.ts +18 -0
  111. package/identify-visitor.d.ts.map +1 -0
  112. package/identify-visitor.js +26 -0
  113. package/identify-visitor.js.map +1 -0
  114. package/index.d.ts +38 -0
  115. package/index.js +38 -0
  116. package/package.json +121 -0
  117. package/primitives/avatar/avatar.d.ts +31 -0
  118. package/primitives/avatar/avatar.d.ts.map +1 -0
  119. package/primitives/avatar/avatar.js +49 -0
  120. package/primitives/avatar/avatar.js.map +1 -0
  121. package/primitives/avatar/fallback.d.ts +24 -0
  122. package/primitives/avatar/fallback.d.ts.map +1 -0
  123. package/primitives/avatar/fallback.js +57 -0
  124. package/primitives/avatar/fallback.js.map +1 -0
  125. package/primitives/avatar/image.d.ts +27 -0
  126. package/primitives/avatar/image.d.ts.map +1 -0
  127. package/primitives/avatar/image.js +58 -0
  128. package/primitives/avatar/image.js.map +1 -0
  129. package/primitives/avatar/index.d.ts +4 -0
  130. package/primitives/avatar/index.js +5 -0
  131. package/primitives/avatar/index.parts.d.ts +4 -0
  132. package/primitives/avatar/index.parts.js +5 -0
  133. package/primitives/bubble.d.ts +28 -0
  134. package/primitives/bubble.d.ts.map +1 -0
  135. package/primitives/bubble.js +43 -0
  136. package/primitives/bubble.js.map +1 -0
  137. package/primitives/button.d.ts +19 -0
  138. package/primitives/button.d.ts.map +1 -0
  139. package/primitives/button.js +27 -0
  140. package/primitives/button.js.map +1 -0
  141. package/primitives/conversation-timeline.d.ts +86 -0
  142. package/primitives/conversation-timeline.d.ts.map +1 -0
  143. package/primitives/conversation-timeline.js +119 -0
  144. package/primitives/conversation-timeline.js.map +1 -0
  145. package/primitives/index.d.ts +20 -0
  146. package/primitives/index.d.ts.map +1 -0
  147. package/primitives/index.js +45 -0
  148. package/primitives/index.js.map +1 -0
  149. package/primitives/index.parts.d.ts +13 -0
  150. package/primitives/index.parts.js +14 -0
  151. package/primitives/multimodal-input.d.ts +53 -0
  152. package/primitives/multimodal-input.d.ts.map +1 -0
  153. package/primitives/multimodal-input.js +106 -0
  154. package/primitives/multimodal-input.js.map +1 -0
  155. package/primitives/timeline-item-group.d.ts +166 -0
  156. package/primitives/timeline-item-group.d.ts.map +1 -0
  157. package/primitives/timeline-item-group.js +204 -0
  158. package/primitives/timeline-item-group.js.map +1 -0
  159. package/primitives/timeline-item.d.ts +75 -0
  160. package/primitives/timeline-item.d.ts.map +1 -0
  161. package/primitives/timeline-item.js +145 -0
  162. package/primitives/timeline-item.js.map +1 -0
  163. package/primitives/window.d.ts +31 -0
  164. package/primitives/window.d.ts.map +1 -0
  165. package/primitives/window.js +58 -0
  166. package/primitives/window.js.map +1 -0
  167. package/provider.d.ts +95 -0
  168. package/provider.d.ts.map +1 -0
  169. package/provider.js +124 -0
  170. package/provider.js.map +1 -0
  171. package/realtime/event-filter.d.ts +8 -0
  172. package/realtime/event-filter.d.ts.map +1 -0
  173. package/realtime/event-filter.js +21 -0
  174. package/realtime/event-filter.js.map +1 -0
  175. package/realtime/index.d.ts +6 -0
  176. package/realtime/index.js +7 -0
  177. package/realtime/provider.d.ts +57 -0
  178. package/realtime/provider.d.ts.map +1 -0
  179. package/realtime/provider.js +351 -0
  180. package/realtime/provider.js.map +1 -0
  181. package/realtime/seen-store.d.ts +23 -0
  182. package/realtime/seen-store.d.ts.map +1 -0
  183. package/realtime/seen-store.js +34 -0
  184. package/realtime/seen-store.js.map +1 -0
  185. package/realtime/support-provider.d.ts +17 -0
  186. package/realtime/support-provider.d.ts.map +1 -0
  187. package/realtime/support-provider.js +54 -0
  188. package/realtime/support-provider.js.map +1 -0
  189. package/realtime/typing-store.d.ts +30 -0
  190. package/realtime/typing-store.d.ts.map +1 -0
  191. package/realtime/typing-store.js +34 -0
  192. package/realtime/typing-store.js.map +1 -0
  193. package/realtime/use-realtime.d.ts +29 -0
  194. package/realtime/use-realtime.d.ts.map +1 -0
  195. package/realtime/use-realtime.js +47 -0
  196. package/realtime/use-realtime.js.map +1 -0
  197. package/realtime-events.d.ts +344 -0
  198. package/realtime-events.d.ts.map +1 -0
  199. package/schemas.d.ts +90 -0
  200. package/schemas.d.ts.map +1 -0
  201. package/support/components/avatar-stack.d.ts +45 -0
  202. package/support/components/avatar-stack.d.ts.map +1 -0
  203. package/support/components/avatar-stack.js +72 -0
  204. package/support/components/avatar-stack.js.map +1 -0
  205. package/support/components/avatar.d.ts +15 -0
  206. package/support/components/avatar.d.ts.map +1 -0
  207. package/support/components/avatar.js +23 -0
  208. package/support/components/avatar.js.map +1 -0
  209. package/support/components/bubble.d.ts +10 -0
  210. package/support/components/bubble.d.ts.map +1 -0
  211. package/support/components/bubble.js +95 -0
  212. package/support/components/bubble.js.map +1 -0
  213. package/support/components/button.d.ts +20 -0
  214. package/support/components/button.d.ts.map +1 -0
  215. package/support/components/button.js +41 -0
  216. package/support/components/button.js.map +1 -0
  217. package/support/components/container.d.ts +14 -0
  218. package/support/components/container.d.ts.map +1 -0
  219. package/support/components/container.js +115 -0
  220. package/support/components/container.js.map +1 -0
  221. package/support/components/conversation-button-link.d.ts +34 -0
  222. package/support/components/conversation-button-link.d.ts.map +1 -0
  223. package/support/components/conversation-button-link.js +195 -0
  224. package/support/components/conversation-button-link.js.map +1 -0
  225. package/support/components/conversation-event.d.ts +14 -0
  226. package/support/components/conversation-event.d.ts.map +1 -0
  227. package/support/components/conversation-event.js +76 -0
  228. package/support/components/conversation-event.js.map +1 -0
  229. package/support/components/conversation-timeline.d.ts +17 -0
  230. package/support/components/conversation-timeline.d.ts.map +1 -0
  231. package/support/components/conversation-timeline.js +95 -0
  232. package/support/components/conversation-timeline.js.map +1 -0
  233. package/support/components/cossistant-branding.d.ts +12 -0
  234. package/support/components/cossistant-branding.d.ts.map +1 -0
  235. package/support/components/cossistant-branding.js +22 -0
  236. package/support/components/cossistant-branding.js.map +1 -0
  237. package/support/components/header.d.ts +11 -0
  238. package/support/components/header.d.ts.map +1 -0
  239. package/support/components/header.js +43 -0
  240. package/support/components/header.js.map +1 -0
  241. package/support/components/icons.d.ts +21 -0
  242. package/support/components/icons.d.ts.map +1 -0
  243. package/support/components/icons.js +131 -0
  244. package/support/components/icons.js.map +1 -0
  245. package/support/components/index.d.ts +11 -0
  246. package/support/components/index.js +12 -0
  247. package/support/components/multimodal-input.d.ts +28 -0
  248. package/support/components/multimodal-input.d.ts.map +1 -0
  249. package/support/components/multimodal-input.js +138 -0
  250. package/support/components/multimodal-input.js.map +1 -0
  251. package/support/components/navigation-tab.d.ts +7 -0
  252. package/support/components/navigation-tab.d.ts.map +1 -0
  253. package/support/components/navigation-tab.js +40 -0
  254. package/support/components/navigation-tab.js.map +1 -0
  255. package/support/components/support-content.d.ts +22 -0
  256. package/support/components/support-content.d.ts.map +1 -0
  257. package/support/components/support-content.js +50 -0
  258. package/support/components/support-content.js.map +1 -0
  259. package/support/components/text-effect.d.ts +49 -0
  260. package/support/components/text-effect.d.ts.map +1 -0
  261. package/support/components/text-effect.js +221 -0
  262. package/support/components/text-effect.js.map +1 -0
  263. package/support/components/timeline-message-group.d.ts +16 -0
  264. package/support/components/timeline-message-group.d.ts.map +1 -0
  265. package/support/components/timeline-message-group.js +117 -0
  266. package/support/components/timeline-message-group.js.map +1 -0
  267. package/support/components/timeline-message-item.d.ts +17 -0
  268. package/support/components/timeline-message-item.d.ts.map +1 -0
  269. package/support/components/timeline-message-item.js +42 -0
  270. package/support/components/timeline-message-item.js.map +1 -0
  271. package/support/components/typing-indicator.d.ts +26 -0
  272. package/support/components/typing-indicator.d.ts.map +1 -0
  273. package/support/components/typing-indicator.js +37 -0
  274. package/support/components/typing-indicator.js.map +1 -0
  275. package/support/components/watermark.d.ts +8 -0
  276. package/support/components/watermark.d.ts.map +1 -0
  277. package/support/components/watermark.js +34 -0
  278. package/support/components/watermark.js.map +1 -0
  279. package/support/context/config.d.ts +32 -0
  280. package/support/context/config.d.ts.map +1 -0
  281. package/support/context/config.js +27 -0
  282. package/support/context/config.js.map +1 -0
  283. package/support/context/websocket.d.ts +22 -0
  284. package/support/context/websocket.d.ts.map +1 -0
  285. package/support/context/websocket.js +113 -0
  286. package/support/context/websocket.js.map +1 -0
  287. package/support/index.d.ts +39 -0
  288. package/support/index.d.ts.map +1 -0
  289. package/support/index.js +43 -0
  290. package/support/index.js.map +1 -0
  291. package/support/pages/articles.d.ts +7 -0
  292. package/support/pages/articles.d.ts.map +1 -0
  293. package/support/pages/articles.js +39 -0
  294. package/support/pages/articles.js.map +1 -0
  295. package/support/pages/conversation-history.d.ts +18 -0
  296. package/support/pages/conversation-history.d.ts.map +1 -0
  297. package/support/pages/conversation-history.js +120 -0
  298. package/support/pages/conversation-history.js.map +1 -0
  299. package/support/pages/conversation.d.ts +32 -0
  300. package/support/pages/conversation.d.ts.map +1 -0
  301. package/support/pages/conversation.js +92 -0
  302. package/support/pages/conversation.js.map +1 -0
  303. package/support/pages/home.d.ts +20 -0
  304. package/support/pages/home.d.ts.map +1 -0
  305. package/support/pages/home.js +184 -0
  306. package/support/pages/home.js.map +1 -0
  307. package/support/router.d.ts +14 -0
  308. package/support/router.d.ts.map +1 -0
  309. package/support/router.js +31 -0
  310. package/support/router.js.map +1 -0
  311. package/support/store/index.d.ts +2 -0
  312. package/support/store/index.js +3 -0
  313. package/support/store/support-store.d.ts +42 -0
  314. package/support/store/support-store.d.ts.map +1 -0
  315. package/support/store/support-store.js +66 -0
  316. package/support/store/support-store.js.map +1 -0
  317. package/support/support-CMoDLQoC.css +408 -0
  318. package/support/support-CMoDLQoC.css.map +1 -0
  319. package/support/support.js +1 -0
  320. package/support/text/index.d.ts +35 -0
  321. package/support/text/index.d.ts.map +1 -0
  322. package/support/text/index.js +71 -0
  323. package/support/text/index.js.map +1 -0
  324. package/support/text/locales/en.d.ts +7 -0
  325. package/support/text/locales/en.d.ts.map +1 -0
  326. package/support/text/locales/en.js +65 -0
  327. package/support/text/locales/en.js.map +1 -0
  328. package/support/text/locales/es.d.ts +7 -0
  329. package/support/text/locales/es.d.ts.map +1 -0
  330. package/support/text/locales/es.js +64 -0
  331. package/support/text/locales/es.js.map +1 -0
  332. package/support/text/locales/fr.d.ts +7 -0
  333. package/support/text/locales/fr.d.ts.map +1 -0
  334. package/support/text/locales/fr.js +64 -0
  335. package/support/text/locales/fr.js.map +1 -0
  336. package/support/text/locales/keys.d.ts +216 -0
  337. package/support/text/locales/keys.d.ts.map +1 -0
  338. package/support/text/locales/keys.js +54 -0
  339. package/support/text/locales/keys.js.map +1 -0
  340. package/support/text/runtime.d.ts +17 -0
  341. package/support/text/runtime.d.ts.map +1 -0
  342. package/support/text/runtime.js +156 -0
  343. package/support/text/runtime.js.map +1 -0
  344. package/support/utils/index.d.ts +7 -0
  345. package/support/utils/index.d.ts.map +1 -0
  346. package/support/utils/index.js +11 -0
  347. package/support/utils/index.js.map +1 -0
  348. package/support/utils/time.d.ts +5 -0
  349. package/support/utils/time.d.ts.map +1 -0
  350. package/support/utils/time.js +28 -0
  351. package/support/utils/time.js.map +1 -0
  352. package/support-config.d.ts +20 -0
  353. package/support-config.d.ts.map +1 -0
  354. package/support-config.js +25 -0
  355. package/support-config.js.map +1 -0
  356. package/support.css +2 -0
  357. package/timeline-item.d.ts +133 -0
  358. package/timeline-item.d.ts.map +1 -0
  359. package/utils/id.d.ts +6 -0
  360. package/utils/id.d.ts.map +1 -0
  361. package/utils/id.js +13 -0
  362. package/utils/id.js.map +1 -0
  363. package/utils/index.d.ts +3 -0
  364. package/utils/index.js +4 -0
  365. package/utils/text.d.ts +5 -0
  366. package/utils/text.d.ts.map +1 -0
  367. package/utils/text.js +9 -0
  368. package/utils/text.js.map +1 -0
  369. package/utils/use-render-element.d.ts +22 -0
  370. package/utils/use-render-element.d.ts.map +1 -0
  371. package/utils/use-render-element.js +35 -0
  372. package/utils/use-render-element.js.map +1 -0
@@ -0,0 +1,122 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+
3
+ //#region src/hooks/private/use-client-query.ts
4
+ function toError(error) {
5
+ if (error instanceof Error) return error;
6
+ return new Error(typeof error === "string" ? error : "Unknown error");
7
+ }
8
+ const EMPTY_DEPENDENCIES = [];
9
+ function useClientQuery(options) {
10
+ const { client, queryFn, enabled = true, refetchInterval = false, refetchOnWindowFocus = true, refetchOnMount = true, initialData, initialArgs, dependencies = EMPTY_DEPENDENCIES } = options;
11
+ const [data, setData] = useState(initialData);
12
+ const [error, setError] = useState(null);
13
+ const [isLoading, setIsLoading] = useState(initialData === void 0 && Boolean(enabled));
14
+ const dataRef = useRef(data);
15
+ dataRef.current = data;
16
+ const argsRef = useRef(initialArgs);
17
+ const fetchIdRef = useRef(0);
18
+ const hasMountedRef = useRef(false);
19
+ const hasFetchedRef = useRef(initialData !== void 0);
20
+ const isMountedRef = useRef(true);
21
+ const queryFnRef = useRef(queryFn);
22
+ queryFnRef.current = queryFn;
23
+ useEffect(() => () => {
24
+ isMountedRef.current = false;
25
+ }, []);
26
+ useEffect(() => {
27
+ argsRef.current = initialArgs;
28
+ }, [initialArgs]);
29
+ const execute = useCallback(async (args, ignoreEnabled = false) => {
30
+ if (!(enabled || ignoreEnabled)) return dataRef.current;
31
+ const nextArgs = args ?? argsRef.current;
32
+ argsRef.current = nextArgs;
33
+ const fetchId = fetchIdRef.current + 1;
34
+ fetchIdRef.current = fetchId;
35
+ setIsLoading(true);
36
+ setError(null);
37
+ try {
38
+ const result = await queryFnRef.current(client, nextArgs);
39
+ if (!isMountedRef.current || fetchId !== fetchIdRef.current) return dataRef.current;
40
+ dataRef.current = result;
41
+ setData(result);
42
+ setError(null);
43
+ setIsLoading(false);
44
+ hasFetchedRef.current = true;
45
+ return result;
46
+ } catch (raw) {
47
+ if (!isMountedRef.current || fetchId !== fetchIdRef.current) return dataRef.current;
48
+ const normalized = toError(raw);
49
+ setError(normalized);
50
+ setIsLoading(false);
51
+ throw normalized;
52
+ }
53
+ }, [client, enabled]);
54
+ useEffect(() => {
55
+ if (!enabled) {
56
+ setIsLoading(false);
57
+ return;
58
+ }
59
+ const shouldFetchInitially = hasMountedRef.current ? true : refetchOnMount || !hasFetchedRef.current;
60
+ hasMountedRef.current = true;
61
+ if (!shouldFetchInitially) return;
62
+ execute(argsRef.current);
63
+ }, [
64
+ enabled,
65
+ execute,
66
+ refetchOnMount,
67
+ ...dependencies
68
+ ]);
69
+ useEffect(() => {
70
+ if (!enabled) return;
71
+ if (refetchInterval === false || refetchInterval === null || refetchInterval <= 0 || typeof window === "undefined") return;
72
+ const timer = window.setInterval(() => {
73
+ execute(argsRef.current);
74
+ }, refetchInterval);
75
+ return () => {
76
+ window.clearInterval(timer);
77
+ };
78
+ }, [
79
+ enabled,
80
+ execute,
81
+ refetchInterval
82
+ ]);
83
+ useEffect(() => {
84
+ if (!refetchOnWindowFocus || typeof window === "undefined" || typeof document === "undefined") return;
85
+ const handleRefetch = () => {
86
+ if (!enabled) return;
87
+ execute(argsRef.current);
88
+ };
89
+ const onFocus = () => {
90
+ handleRefetch();
91
+ };
92
+ const onVisibilityChange = () => {
93
+ if (document.visibilityState === "visible") handleRefetch();
94
+ };
95
+ window.addEventListener("focus", onFocus);
96
+ document.addEventListener("visibilitychange", onVisibilityChange);
97
+ return () => {
98
+ window.removeEventListener("focus", onFocus);
99
+ document.removeEventListener("visibilitychange", onVisibilityChange);
100
+ };
101
+ }, [
102
+ enabled,
103
+ execute,
104
+ refetchOnWindowFocus
105
+ ]);
106
+ const refetch = useCallback(async (args) => execute(args, true), [execute]);
107
+ return useMemo(() => ({
108
+ data,
109
+ error,
110
+ isLoading,
111
+ refetch
112
+ }), [
113
+ data,
114
+ error,
115
+ isLoading,
116
+ refetch
117
+ ]);
118
+ }
119
+
120
+ //#endregion
121
+ export { useClientQuery };
122
+ //# sourceMappingURL=use-client-query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-client-query.js","names":["EMPTY_DEPENDENCIES: readonly unknown[]","raw: unknown"],"sources":["../../../src/hooks/private/use-client-query.ts"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\ntype QueryFn<TData, TArgs> = (\n\tclient: CossistantClient,\n\targs?: TArgs | undefined\n) => Promise<TData>;\n\ntype UseClientQueryOptions<TData, TArgs> = {\n\tclient: CossistantClient;\n\tqueryFn: QueryFn<TData, TArgs>;\n\tenabled?: boolean;\n\trefetchInterval?: number | false;\n\trefetchOnWindowFocus?: boolean;\n\trefetchOnMount?: boolean;\n\tinitialData?: TData;\n\tinitialArgs?: TArgs;\n\tdependencies?: readonly unknown[];\n};\n\ntype UseClientQueryResult<TData, TArgs> = {\n\tdata: TData | undefined;\n\terror: Error | null;\n\tisLoading: boolean;\n\trefetch: (args?: TArgs) => Promise<TData | undefined>;\n};\n\nfunction toError(error: unknown): Error {\n\tif (error instanceof Error) {\n\t\treturn error;\n\t}\n\n\treturn new Error(typeof error === \"string\" ? error : \"Unknown error\");\n}\n\nconst EMPTY_DEPENDENCIES: readonly unknown[] = [];\n\nexport function useClientQuery<TData, TArgs = void>(\n\toptions: UseClientQueryOptions<TData, TArgs>\n): UseClientQueryResult<TData, TArgs> {\n\tconst {\n\t\tclient,\n\t\tqueryFn,\n\t\tenabled = true,\n\t\trefetchInterval = false,\n\t\trefetchOnWindowFocus = true,\n\t\trefetchOnMount = true,\n\t\tinitialData,\n\t\tinitialArgs,\n\t\tdependencies = EMPTY_DEPENDENCIES,\n\t} = options;\n\n\tconst [data, setData] = useState<TData | undefined>(initialData);\n\tconst [error, setError] = useState<Error | null>(null);\n\tconst [isLoading, setIsLoading] = useState(\n\t\tinitialData === undefined && Boolean(enabled)\n\t);\n\n\tconst dataRef = useRef(data);\n\tdataRef.current = data;\n\n\tconst argsRef = useRef<TArgs | undefined>(initialArgs);\n\tconst fetchIdRef = useRef(0);\n\tconst hasMountedRef = useRef(false);\n\tconst hasFetchedRef = useRef(initialData !== undefined);\n\tconst isMountedRef = useRef(true);\n\tconst queryFnRef = useRef(queryFn);\n\n\tqueryFnRef.current = queryFn;\n\n\tuseEffect(\n\t\t() => () => {\n\t\t\tisMountedRef.current = false;\n\t\t},\n\t\t[]\n\t);\n\n\tuseEffect(() => {\n\t\targsRef.current = initialArgs;\n\t}, [initialArgs]);\n\n\tconst execute = useCallback(\n\t\tasync (args?: TArgs, ignoreEnabled = false): Promise<TData | undefined> => {\n\t\t\tif (!(enabled || ignoreEnabled)) {\n\t\t\t\treturn dataRef.current;\n\t\t\t}\n\n\t\t\tconst nextArgs = args ?? argsRef.current;\n\t\t\targsRef.current = nextArgs;\n\n\t\t\tconst fetchId = fetchIdRef.current + 1;\n\t\t\tfetchIdRef.current = fetchId;\n\n\t\t\tsetIsLoading(true);\n\t\t\tsetError(null);\n\n\t\t\ttry {\n\t\t\t\tconst result = await queryFnRef.current(client, nextArgs);\n\n\t\t\t\tif (!isMountedRef.current || fetchId !== fetchIdRef.current) {\n\t\t\t\t\treturn dataRef.current;\n\t\t\t\t}\n\n\t\t\t\tdataRef.current = result;\n\t\t\t\tsetData(result);\n\t\t\t\tsetError(null);\n\t\t\t\tsetIsLoading(false);\n\t\t\t\thasFetchedRef.current = true;\n\n\t\t\t\treturn result;\n\t\t\t} catch (raw: unknown) {\n\t\t\t\tif (!isMountedRef.current || fetchId !== fetchIdRef.current) {\n\t\t\t\t\treturn dataRef.current;\n\t\t\t\t}\n\n\t\t\t\tconst normalized = toError(raw);\n\t\t\t\tsetError(normalized);\n\t\t\t\tsetIsLoading(false);\n\n\t\t\t\tthrow normalized;\n\t\t\t}\n\t\t},\n\t\t[client, enabled]\n\t);\n\n\tuseEffect(() => {\n\t\tif (!enabled) {\n\t\t\tsetIsLoading(false);\n\t\t\treturn;\n\t\t}\n\n\t\tconst shouldFetchInitially = hasMountedRef.current\n\t\t\t? true\n\t\t\t: refetchOnMount || !hasFetchedRef.current;\n\n\t\thasMountedRef.current = true;\n\n\t\tif (!shouldFetchInitially) {\n\t\t\treturn;\n\t\t}\n\n\t\tvoid execute(argsRef.current);\n\t}, [enabled, execute, refetchOnMount, ...dependencies]);\n\n\tuseEffect(() => {\n\t\tif (!enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (\n\t\t\trefetchInterval === false ||\n\t\t\trefetchInterval === null ||\n\t\t\trefetchInterval <= 0 ||\n\t\t\ttypeof window === \"undefined\"\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst timer = window.setInterval(() => {\n\t\t\tvoid execute(argsRef.current);\n\t\t}, refetchInterval);\n\n\t\treturn () => {\n\t\t\twindow.clearInterval(timer);\n\t\t};\n\t}, [enabled, execute, refetchInterval]);\n\n\tuseEffect(() => {\n\t\tif (\n\t\t\t!refetchOnWindowFocus ||\n\t\t\ttypeof window === \"undefined\" ||\n\t\t\ttypeof document === \"undefined\"\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst handleRefetch = () => {\n\t\t\tif (!enabled) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvoid execute(argsRef.current);\n\t\t};\n\n\t\tconst onFocus = () => {\n\t\t\tvoid handleRefetch();\n\t\t};\n\n\t\tconst onVisibilityChange = () => {\n\t\t\tif (document.visibilityState === \"visible\") {\n\t\t\t\tvoid handleRefetch();\n\t\t\t}\n\t\t};\n\n\t\twindow.addEventListener(\"focus\", onFocus);\n\t\tdocument.addEventListener(\"visibilitychange\", onVisibilityChange);\n\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"focus\", onFocus);\n\t\t\tdocument.removeEventListener(\"visibilitychange\", onVisibilityChange);\n\t\t};\n\t}, [enabled, execute, refetchOnWindowFocus]);\n\n\tconst refetch = useCallback(\n\t\tasync (args?: TArgs) => execute(args, true),\n\t\t[execute]\n\t);\n\n\treturn useMemo(\n\t\t() => ({\n\t\t\tdata,\n\t\t\terror,\n\t\t\tisLoading,\n\t\t\trefetch,\n\t\t}),\n\t\t[data, error, isLoading, refetch]\n\t);\n}\n"],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { TimelineItem } from "../../timeline-item.js";
2
+
3
+ //#region src/hooks/private/use-default-messages.d.ts
4
+ type UseDefaultMessagesParams = {
5
+ conversationId: string;
6
+ };
7
+ /**
8
+ * Mirrors the provider-configured default messages into timeline items so
9
+ * that welcome content renders immediately while the backend conversation is
10
+ * still being created. Agent fallbacks are resolved against available humans
11
+ * and AI agents exposed by the provider context.
12
+ */
13
+ declare function useDefaultMessages({
14
+ conversationId
15
+ }: UseDefaultMessagesParams): TimelineItem[];
16
+ //#endregion
17
+ export { useDefaultMessages };
18
+ //# sourceMappingURL=use-default-messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-default-messages.d.ts","names":[],"sources":["../../../src/hooks/private/use-default-messages.ts"],"sourcesContent":[],"mappings":";;;KAMK,wBAAA;;AAJmE,CAAA;AAcxE;;;;;;iBAAgB,kBAAA;;GAEb,2BAA2B"}
@@ -0,0 +1,45 @@
1
+ import { useSupport } from "../../provider.js";
2
+ import { useMemo } from "react";
3
+ import { generateMessageId } from "@cossistant/core";
4
+ import { SenderType } from "@cossistant/types";
5
+
6
+ //#region src/hooks/private/use-default-messages.ts
7
+ /**
8
+ * Mirrors the provider-configured default messages into timeline items so
9
+ * that welcome content renders immediately while the backend conversation is
10
+ * still being created. Agent fallbacks are resolved against available humans
11
+ * and AI agents exposed by the provider context.
12
+ */
13
+ function useDefaultMessages({ conversationId }) {
14
+ const { defaultMessages, availableAIAgents, availableHumanAgents } = useSupport();
15
+ return useMemo(() => defaultMessages.map((message, index) => {
16
+ const messageId = generateMessageId();
17
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
18
+ return {
19
+ id: messageId,
20
+ conversationId,
21
+ organizationId: "",
22
+ type: "message",
23
+ text: message.content,
24
+ parts: [{
25
+ type: "text",
26
+ text: message.content
27
+ }],
28
+ visibility: "public",
29
+ userId: message.senderType === SenderType.TEAM_MEMBER ? message.senderId || availableHumanAgents[0]?.id || null : null,
30
+ aiAgentId: message.senderType === SenderType.AI ? message.senderId || availableAIAgents[0]?.id || null : null,
31
+ visitorId: message.senderType === SenderType.VISITOR ? message.senderId || null : null,
32
+ createdAt: timestamp,
33
+ deletedAt: null
34
+ };
35
+ }), [
36
+ defaultMessages,
37
+ availableHumanAgents[0]?.id,
38
+ availableAIAgents[0]?.id,
39
+ conversationId
40
+ ]);
41
+ }
42
+
43
+ //#endregion
44
+ export { useDefaultMessages };
45
+ //# sourceMappingURL=use-default-messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-default-messages.js","names":[],"sources":["../../../src/hooks/private/use-default-messages.ts"],"sourcesContent":["import { generateMessageId } from \"@cossistant/core\";\nimport { SenderType } from \"@cossistant/types\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport { useMemo } from \"react\";\nimport { useSupport } from \"../../provider\";\n\ntype UseDefaultMessagesParams = {\n\tconversationId: string;\n};\n\n/**\n * Mirrors the provider-configured default messages into timeline items so\n * that welcome content renders immediately while the backend conversation is\n * still being created. Agent fallbacks are resolved against available humans\n * and AI agents exposed by the provider context.\n */\nexport function useDefaultMessages({\n\tconversationId,\n}: UseDefaultMessagesParams): TimelineItem[] {\n\tconst { defaultMessages, availableAIAgents, availableHumanAgents } =\n\t\tuseSupport();\n\n\tconst memoisedDefaultTimelineItems = useMemo(\n\t\t() =>\n\t\t\tdefaultMessages.map((message, index) => {\n\t\t\t\tconst messageId = generateMessageId();\n\t\t\t\tconst timestamp = new Date().toISOString();\n\n\t\t\t\treturn {\n\t\t\t\t\tid: messageId,\n\t\t\t\t\tconversationId,\n\t\t\t\t\torganizationId: \"\", // Not available for default messages\n\t\t\t\t\ttype: \"message\" as const,\n\t\t\t\t\ttext: message.content,\n\t\t\t\t\tparts: [{ type: \"text\" as const, text: message.content }],\n\t\t\t\t\tvisibility: \"public\" as const,\n\t\t\t\t\tuserId:\n\t\t\t\t\t\tmessage.senderType === SenderType.TEAM_MEMBER\n\t\t\t\t\t\t\t? message.senderId || availableHumanAgents[0]?.id || null\n\t\t\t\t\t\t\t: null,\n\t\t\t\t\taiAgentId:\n\t\t\t\t\t\tmessage.senderType === SenderType.AI\n\t\t\t\t\t\t\t? message.senderId || availableAIAgents[0]?.id || null\n\t\t\t\t\t\t\t: null,\n\t\t\t\t\tvisitorId:\n\t\t\t\t\t\tmessage.senderType === SenderType.VISITOR\n\t\t\t\t\t\t\t? message.senderId || null\n\t\t\t\t\t\t\t: null,\n\t\t\t\t\tcreatedAt: timestamp,\n\t\t\t\t\tdeletedAt: null,\n\t\t\t\t} satisfies TimelineItem;\n\t\t\t}),\n\t\t[\n\t\t\tdefaultMessages,\n\t\t\tavailableHumanAgents[0]?.id,\n\t\t\tavailableAIAgents[0]?.id,\n\t\t\tconversationId,\n\t\t]\n\t);\n\n\treturn memoisedDefaultTimelineItems;\n}\n"],"mappings":";;;;;;;;;;;;AAgBA,SAAgB,mBAAmB,EAClC,kBAC4C;CAC5C,MAAM,EAAE,iBAAiB,mBAAmB,yBAC3C,YAAY;AAwCb,QAtCqC,cAEnC,gBAAgB,KAAK,SAAS,UAAU;EACvC,MAAM,YAAY,mBAAmB;EACrC,MAAM,6BAAY,IAAI,MAAM,EAAC,aAAa;AAE1C,SAAO;GACN,IAAI;GACJ;GACA,gBAAgB;GAChB,MAAM;GACN,MAAM,QAAQ;GACd,OAAO,CAAC;IAAE,MAAM;IAAiB,MAAM,QAAQ;IAAS,CAAC;GACzD,YAAY;GACZ,QACC,QAAQ,eAAe,WAAW,cAC/B,QAAQ,YAAY,qBAAqB,IAAI,MAAM,OACnD;GACJ,WACC,QAAQ,eAAe,WAAW,KAC/B,QAAQ,YAAY,kBAAkB,IAAI,MAAM,OAChD;GACJ,WACC,QAAQ,eAAe,WAAW,UAC/B,QAAQ,YAAY,OACpB;GACJ,WAAW;GACX,WAAW;GACX;GACA,EACH;EACC;EACA,qBAAqB,IAAI;EACzB,kBAAkB,IAAI;EACtB;EACA,CACD"}
@@ -0,0 +1,54 @@
1
+ import { TimelineItem } from "../../timeline-item.js";
2
+ import { ConversationSeen } from "../../schemas.js";
3
+ import { SenderType } from "@cossistant/types";
4
+
5
+ //#region src/hooks/private/use-grouped-messages.d.ts
6
+ type GroupedMessage = {
7
+ type: "message_group";
8
+ senderId: string;
9
+ senderType: SenderType;
10
+ items: TimelineItem[];
11
+ firstMessageId: string;
12
+ lastMessageId: string;
13
+ firstMessageTime: Date;
14
+ lastMessageTime: Date;
15
+ };
16
+ type TimelineEventItem = {
17
+ type: "timeline_event";
18
+ item: TimelineItem;
19
+ timestamp: Date;
20
+ };
21
+ type ConversationItem = GroupedMessage | TimelineEventItem;
22
+ type UseGroupedMessagesOptions = {
23
+ items: TimelineItem[];
24
+ seenData?: ConversationSeen[];
25
+ currentViewerId?: string;
26
+ viewerType?: SenderType;
27
+ };
28
+ type UseGroupedMessagesProps = UseGroupedMessagesOptions;
29
+ /**
30
+ * Batches sequential timeline items from the same sender into groups and enriches
31
+ * them with read-receipt helpers so UIs can render conversation timelines with
32
+ * minimal effort. Seen data is normalised into quick lookup maps for unread
33
+ * indicators.
34
+ */
35
+ declare const useGroupedMessages: ({
36
+ items,
37
+ seenData,
38
+ currentViewerId,
39
+ viewerType
40
+ }: UseGroupedMessagesOptions) => {
41
+ items: (GroupedMessage | TimelineEventItem)[];
42
+ seenByMap: Map<string, Set<string>>;
43
+ lastReadMessageMap: Map<string, string>;
44
+ unreadCountMap: Map<string, number>;
45
+ isMessageSeenByViewer: (messageId: string) => boolean;
46
+ getMessageSeenBy: (messageId: string) => string[];
47
+ getLastReadMessageId: (userId: string) => string | undefined;
48
+ isLastReadMessage: (messageId: string, userId: string) => boolean;
49
+ getUnreadCount: (userId: string) => number;
50
+ hasUnreadAfter: (messageId: string, userId: string) => boolean;
51
+ };
52
+ //#endregion
53
+ export { ConversationItem, GroupedMessage, TimelineEventItem, UseGroupedMessagesOptions, UseGroupedMessagesProps, useGroupedMessages };
54
+ //# sourceMappingURL=use-grouped-messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-grouped-messages.d.ts","names":[],"sources":["../../../src/hooks/private/use-grouped-messages.ts"],"sourcesContent":[],"mappings":";;;;;KAKY,cAAA;;EAAA,QAAA,EAAA,MAAA;EAAc,UAAA,EAGb,UAHa;OAGb,EACL,YADK,EAAA;gBACL,EAAA,MAAA;eAGW,EAAA,MAAA;kBACD,EADC,IACD;EAAI,eAAA,EAAJ,IAAI;AAGtB,CAAA;AAA6B,KAAjB,iBAAA,GAAiB;MAEtB,EAAA,gBAAA;MACK,EADL,YACK;EAAI,SAAA,EAAJ,IAAI;AAGhB,CAAA;AAA4B,KAAhB,gBAAA,GAAmB,cAAH,GAAoB,iBAApB;AAAG,KAEnB,yBAAA,GAFmB;OAAiB,EAGxC,YAHwC,EAAA;EAAiB,QAAA,CAAA,EAIrD,gBAJqD,EAAA;EAErD,eAAA,CAAA,EAAA,MAAA;EAAyB,UAAA,CAAA,EAIvB,UAJuB;;AAEzB,KAKA,uBAAA,GAA0B,yBAL1B;;;AAKZ;AAuMA;;;AAAmC,cAAtB,kBAAsB,EAAA,CAAA;EAAA,KAAA;EAAA,QAAA;EAAA,eAAA;EAAA;AAAA,CAAA,EAKhC,yBALgC,EAAA,GAAA;OAAA,EAAA,eAAA,oBAAA,CAAA,EAAA;WAAA,KAAA,CAAA,MAAA,KAAA,CAAA,MAAA,CAAA,CAAA;oBAKhC,KAAA,CAAA,MAAA,EAAA,MAAA,CAAA"}
@@ -0,0 +1,157 @@
1
+ import { useMemo } from "react";
2
+ import { SenderType } from "@cossistant/types";
3
+
4
+ //#region src/hooks/private/use-grouped-messages.ts
5
+ const getTimestamp = (date) => {
6
+ if (!date) return 0;
7
+ if (typeof date === "string") return new Date(date).getTime();
8
+ return date.getTime();
9
+ };
10
+ const toDate = (date) => {
11
+ if (!date) return /* @__PURE__ */ new Date();
12
+ if (typeof date === "string") return new Date(date);
13
+ return date;
14
+ };
15
+ const getSenderIdAndTypeFromTimelineItem = (item) => {
16
+ if (item.visitorId) return {
17
+ senderId: item.visitorId,
18
+ senderType: SenderType.VISITOR
19
+ };
20
+ if (item.aiAgentId) return {
21
+ senderId: item.aiAgentId,
22
+ senderType: SenderType.AI
23
+ };
24
+ if (item.userId) return {
25
+ senderId: item.userId,
26
+ senderType: SenderType.TEAM_MEMBER
27
+ };
28
+ return {
29
+ senderId: item.id || "default-sender",
30
+ senderType: SenderType.TEAM_MEMBER
31
+ };
32
+ };
33
+ const groupTimelineItems = (items) => {
34
+ const result = [];
35
+ let currentGroup = null;
36
+ for (const item of items) {
37
+ if (item.type === "event") {
38
+ if (currentGroup) {
39
+ result.push(currentGroup);
40
+ currentGroup = null;
41
+ }
42
+ result.push({
43
+ type: "timeline_event",
44
+ item,
45
+ timestamp: toDate(item.createdAt)
46
+ });
47
+ continue;
48
+ }
49
+ const { senderId, senderType } = getSenderIdAndTypeFromTimelineItem(item);
50
+ if (currentGroup && currentGroup.senderId === senderId) {
51
+ currentGroup.items.push(item);
52
+ currentGroup.lastMessageId = item.id || currentGroup.lastMessageId;
53
+ currentGroup.lastMessageTime = toDate(item.createdAt);
54
+ } else {
55
+ if (currentGroup) result.push(currentGroup);
56
+ currentGroup = {
57
+ type: "message_group",
58
+ senderId,
59
+ senderType,
60
+ items: [item],
61
+ firstMessageId: item.id || "",
62
+ lastMessageId: item.id || "",
63
+ firstMessageTime: toDate(item.createdAt),
64
+ lastMessageTime: toDate(item.createdAt)
65
+ };
66
+ }
67
+ }
68
+ if (currentGroup) result.push(currentGroup);
69
+ return result;
70
+ };
71
+ const buildTimelineReadReceiptData = (seenData, items) => {
72
+ const seenByMap = /* @__PURE__ */ new Map();
73
+ const lastReadMessageMap = /* @__PURE__ */ new Map();
74
+ const unreadCountMap = /* @__PURE__ */ new Map();
75
+ for (const item of items) if (item.type === "message" && item.id) seenByMap.set(item.id, /* @__PURE__ */ new Set());
76
+ const sortedItems = [...items].filter((item) => item.type === "message").sort((a, b) => getTimestamp(a.createdAt) - getTimestamp(b.createdAt));
77
+ for (const seen of seenData) {
78
+ let seenTime = getTimestamp(seen.updatedAt);
79
+ const viewerId = seen.userId || seen.visitorId || seen.aiAgentId;
80
+ if (!viewerId) continue;
81
+ const lastItemByViewer = sortedItems.filter((item) => {
82
+ if (seen.userId) return item.userId === viewerId;
83
+ if (seen.visitorId) return item.visitorId === viewerId;
84
+ if (seen.aiAgentId) return item.aiAgentId === viewerId;
85
+ return false;
86
+ }).at(-1);
87
+ if (lastItemByViewer) {
88
+ const lastItemTime = getTimestamp(lastItemByViewer.createdAt);
89
+ if (lastItemTime > seenTime) seenTime = lastItemTime;
90
+ }
91
+ let lastReadItem = null;
92
+ let unreadCount = 0;
93
+ let hasPassedLastSeen = false;
94
+ for (const item of sortedItems) if (getTimestamp(item.createdAt) <= seenTime && !hasPassedLastSeen) {
95
+ if (item.id) {
96
+ const seenBy = seenByMap.get(item.id);
97
+ if (seenBy) seenBy.add(viewerId);
98
+ }
99
+ lastReadItem = item;
100
+ } else {
101
+ hasPassedLastSeen = true;
102
+ unreadCount++;
103
+ }
104
+ if (lastReadItem?.id) lastReadMessageMap.set(viewerId, lastReadItem.id);
105
+ unreadCountMap.set(viewerId, unreadCount);
106
+ }
107
+ return {
108
+ seenByMap,
109
+ lastReadMessageMap,
110
+ unreadCountMap
111
+ };
112
+ };
113
+ /**
114
+ * Batches sequential timeline items from the same sender into groups and enriches
115
+ * them with read-receipt helpers so UIs can render conversation timelines with
116
+ * minimal effort. Seen data is normalised into quick lookup maps for unread
117
+ * indicators.
118
+ */
119
+ const useGroupedMessages = ({ items, seenData = [], currentViewerId, viewerType }) => {
120
+ return useMemo(() => {
121
+ const groupedItems = groupTimelineItems(items);
122
+ const { seenByMap, lastReadMessageMap, unreadCountMap } = buildTimelineReadReceiptData(seenData, items);
123
+ return {
124
+ items: groupedItems,
125
+ seenByMap,
126
+ lastReadMessageMap,
127
+ unreadCountMap,
128
+ isMessageSeenByViewer: (messageId) => {
129
+ if (!currentViewerId) return false;
130
+ const seenBy = seenByMap.get(messageId);
131
+ return seenBy ? seenBy.has(currentViewerId) : false;
132
+ },
133
+ getMessageSeenBy: (messageId) => {
134
+ const seenBy = seenByMap.get(messageId);
135
+ return seenBy ? Array.from(seenBy) : [];
136
+ },
137
+ getLastReadMessageId: (userId) => lastReadMessageMap.get(userId),
138
+ isLastReadMessage: (messageId, userId) => lastReadMessageMap.get(userId) === messageId,
139
+ getUnreadCount: (userId) => unreadCountMap.get(userId) || 0,
140
+ hasUnreadAfter: (messageId, userId) => {
141
+ const lastRead = lastReadMessageMap.get(userId);
142
+ if (!lastRead) return true;
143
+ const messageIndex = items.findIndex((item) => item.id === messageId);
144
+ const lastReadIndex = items.findIndex((item) => item.id === lastRead);
145
+ return messageIndex < lastReadIndex;
146
+ }
147
+ };
148
+ }, [
149
+ items,
150
+ seenData,
151
+ currentViewerId
152
+ ]);
153
+ };
154
+
155
+ //#endregion
156
+ export { useGroupedMessages };
157
+ //# sourceMappingURL=use-grouped-messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-grouped-messages.js","names":["result: Array<GroupedMessage | TimelineEventItem>","currentGroup: GroupedMessage | null","lastReadItem: TimelineItem | null"],"sources":["../../../src/hooks/private/use-grouped-messages.ts"],"sourcesContent":["import { SenderType } from \"@cossistant/types\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport type { ConversationSeen } from \"@cossistant/types/schemas\";\nimport { useMemo } from \"react\";\n\nexport type GroupedMessage = {\n\ttype: \"message_group\";\n\tsenderId: string;\n\tsenderType: SenderType;\n\titems: TimelineItem[];\n\tfirstMessageId: string;\n\tlastMessageId: string;\n\tfirstMessageTime: Date;\n\tlastMessageTime: Date;\n};\n\nexport type TimelineEventItem = {\n\ttype: \"timeline_event\";\n\titem: TimelineItem;\n\ttimestamp: Date;\n};\n\nexport type ConversationItem = GroupedMessage | TimelineEventItem;\n\nexport type UseGroupedMessagesOptions = {\n\titems: TimelineItem[];\n\tseenData?: ConversationSeen[];\n\tcurrentViewerId?: string; // The ID of the current viewer (visitor, user, or AI agent)\n\tviewerType?: SenderType; // Type of the current viewer\n};\n\nexport type UseGroupedMessagesProps = UseGroupedMessagesOptions;\n\n// Helper function to safely get timestamp from Date or string\nconst getTimestamp = (date: Date | string | null | undefined): number => {\n\tif (!date) {\n\t\treturn 0;\n\t}\n\tif (typeof date === \"string\") {\n\t\treturn new Date(date).getTime();\n\t}\n\treturn date.getTime();\n};\n\n// Helper function to safely convert to Date\nconst toDate = (date: Date | string | null | undefined): Date => {\n\tif (!date) {\n\t\treturn new Date();\n\t}\n\tif (typeof date === \"string\") {\n\t\treturn new Date(date);\n\t}\n\treturn date;\n};\n\n// Helper to determine sender ID and type from a timeline item\nconst getSenderIdAndTypeFromTimelineItem = (\n\titem: TimelineItem\n): { senderId: string; senderType: SenderType } => {\n\tif (item.visitorId) {\n\t\treturn { senderId: item.visitorId, senderType: SenderType.VISITOR };\n\t}\n\tif (item.aiAgentId) {\n\t\treturn { senderId: item.aiAgentId, senderType: SenderType.AI };\n\t}\n\tif (item.userId) {\n\t\treturn { senderId: item.userId, senderType: SenderType.TEAM_MEMBER };\n\t}\n\n\t// Fallback\n\treturn {\n\t\tsenderId: item.id || \"default-sender\",\n\t\tsenderType: SenderType.TEAM_MEMBER,\n\t};\n};\n\n// Helper function to group timeline items (messages only, events stay separate)\nconst groupTimelineItems = (\n\titems: TimelineItem[]\n): Array<GroupedMessage | TimelineEventItem> => {\n\tconst result: Array<GroupedMessage | TimelineEventItem> = [];\n\tlet currentGroup: GroupedMessage | null = null;\n\n\tfor (const item of items) {\n\t\t// Events don't get grouped\n\t\tif (item.type === \"event\") {\n\t\t\t// Finalize any existing group\n\t\t\tif (currentGroup) {\n\t\t\t\tresult.push(currentGroup);\n\t\t\t\tcurrentGroup = null;\n\t\t\t}\n\n\t\t\t// Add event as standalone item\n\t\t\tresult.push({\n\t\t\t\ttype: \"timeline_event\",\n\t\t\t\titem,\n\t\t\t\ttimestamp: toDate(item.createdAt),\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Group messages by sender\n\t\tconst { senderId, senderType } = getSenderIdAndTypeFromTimelineItem(item);\n\n\t\tif (currentGroup && currentGroup.senderId === senderId) {\n\t\t\t// Add to existing group\n\t\t\tcurrentGroup.items.push(item);\n\t\t\tcurrentGroup.lastMessageId = item.id || currentGroup.lastMessageId;\n\t\t\tcurrentGroup.lastMessageTime = toDate(item.createdAt);\n\t\t} else {\n\t\t\t// Finalize previous group if exists\n\t\t\tif (currentGroup) {\n\t\t\t\tresult.push(currentGroup);\n\t\t\t}\n\n\t\t\t// Start new group\n\t\t\tcurrentGroup = {\n\t\t\t\ttype: \"message_group\",\n\t\t\t\tsenderId,\n\t\t\t\tsenderType,\n\t\t\t\titems: [item],\n\t\t\t\tfirstMessageId: item.id || \"\",\n\t\t\t\tlastMessageId: item.id || \"\",\n\t\t\t\tfirstMessageTime: toDate(item.createdAt),\n\t\t\t\tlastMessageTime: toDate(item.createdAt),\n\t\t\t};\n\t\t}\n\t}\n\n\tif (currentGroup) {\n\t\tresult.push(currentGroup);\n\t}\n\n\treturn result;\n};\n\n// Build read receipt data for timeline items\nconst buildTimelineReadReceiptData = (\n\tseenData: ConversationSeen[],\n\titems: TimelineItem[]\n) => {\n\tconst seenByMap = new Map<string, Set<string>>();\n\tconst lastReadMessageMap = new Map<string, string>();\n\tconst unreadCountMap = new Map<string, number>();\n\n\t// Initialize map for all message-type timeline items\n\tfor (const item of items) {\n\t\tif (item.type === \"message\" && item.id) {\n\t\t\tseenByMap.set(item.id, new Set());\n\t\t}\n\t}\n\n\t// Sort items by time to process in order\n\tconst sortedItems = [...items]\n\t\t.filter((item) => item.type === \"message\")\n\t\t.sort((a, b) => getTimestamp(a.createdAt) - getTimestamp(b.createdAt));\n\n\t// Process seen data for each viewer\n\tfor (const seen of seenData) {\n\t\tlet seenTime = getTimestamp(seen.updatedAt);\n\t\tconst viewerId = seen.userId || seen.visitorId || seen.aiAgentId;\n\t\tif (!viewerId) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Find the last message sent by this viewer\n\t\tconst lastItemByViewer = sortedItems\n\t\t\t.filter((item) => {\n\t\t\t\tif (seen.userId) {\n\t\t\t\t\treturn item.userId === viewerId;\n\t\t\t\t}\n\t\t\t\tif (seen.visitorId) {\n\t\t\t\t\treturn item.visitorId === viewerId;\n\t\t\t\t}\n\t\t\t\tif (seen.aiAgentId) {\n\t\t\t\t\treturn item.aiAgentId === viewerId;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t})\n\t\t\t.at(-1);\n\n\t\tif (lastItemByViewer) {\n\t\t\tconst lastItemTime = getTimestamp(lastItemByViewer.createdAt);\n\t\t\tif (lastItemTime > seenTime) {\n\t\t\t\tseenTime = lastItemTime;\n\t\t\t}\n\t\t}\n\n\t\tlet lastReadItem: TimelineItem | null = null;\n\t\tlet unreadCount = 0;\n\t\tlet hasPassedLastSeen = false;\n\n\t\t// Process items in chronological order\n\t\tfor (const item of sortedItems) {\n\t\t\tconst itemTime = getTimestamp(item.createdAt);\n\n\t\t\tif (itemTime <= seenTime && !hasPassedLastSeen) {\n\t\t\t\t// This item has been seen\n\t\t\t\tif (item.id) {\n\t\t\t\t\tconst seenBy = seenByMap.get(item.id);\n\t\t\t\t\tif (seenBy) {\n\t\t\t\t\t\tseenBy.add(viewerId);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlastReadItem = item;\n\t\t\t} else {\n\t\t\t\t// This item is unread\n\t\t\t\thasPassedLastSeen = true;\n\t\t\t\tunreadCount++;\n\t\t\t}\n\t\t}\n\n\t\t// Store the last read item for this viewer\n\t\tif (lastReadItem?.id) {\n\t\t\tlastReadMessageMap.set(viewerId, lastReadItem.id);\n\t\t}\n\n\t\t// Store unread count\n\t\tunreadCountMap.set(viewerId, unreadCount);\n\t}\n\n\treturn { seenByMap, lastReadMessageMap, unreadCountMap };\n};\n\n/**\n * Batches sequential timeline items from the same sender into groups and enriches\n * them with read-receipt helpers so UIs can render conversation timelines with\n * minimal effort. Seen data is normalised into quick lookup maps for unread\n * indicators.\n */\nexport const useGroupedMessages = ({\n\titems,\n\tseenData = [],\n\tcurrentViewerId,\n\tviewerType,\n}: UseGroupedMessagesOptions) => {\n\treturn useMemo(() => {\n\t\tconst groupedItems = groupTimelineItems(items);\n\n\t\t// Build read receipt data\n\t\tconst { seenByMap, lastReadMessageMap, unreadCountMap } =\n\t\t\tbuildTimelineReadReceiptData(seenData, items);\n\n\t\treturn {\n\t\t\titems: groupedItems,\n\t\t\tseenByMap,\n\t\t\tlastReadMessageMap,\n\t\t\tunreadCountMap,\n\n\t\t\tisMessageSeenByViewer: (messageId: string): boolean => {\n\t\t\t\tif (!currentViewerId) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tconst seenBy = seenByMap.get(messageId);\n\t\t\t\treturn seenBy ? seenBy.has(currentViewerId) : false;\n\t\t\t},\n\n\t\t\tgetMessageSeenBy: (messageId: string): string[] => {\n\t\t\t\tconst seenBy = seenByMap.get(messageId);\n\t\t\t\treturn seenBy ? Array.from(seenBy) : [];\n\t\t\t},\n\n\t\t\tgetLastReadMessageId: (userId: string): string | undefined =>\n\t\t\t\tlastReadMessageMap.get(userId),\n\n\t\t\tisLastReadMessage: (messageId: string, userId: string): boolean =>\n\t\t\t\tlastReadMessageMap.get(userId) === messageId,\n\n\t\t\tgetUnreadCount: (userId: string): number =>\n\t\t\t\tunreadCountMap.get(userId) || 0,\n\n\t\t\thasUnreadAfter: (messageId: string, userId: string): boolean => {\n\t\t\t\tconst lastRead = lastReadMessageMap.get(userId);\n\t\t\t\tif (!lastRead) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tconst messageIndex = items.findIndex((item) => item.id === messageId);\n\t\t\t\tconst lastReadIndex = items.findIndex((item) => item.id === lastRead);\n\n\t\t\t\treturn messageIndex < lastReadIndex;\n\t\t\t},\n\t\t};\n\t}, [items, seenData, currentViewerId]);\n};\n"],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ //#region src/hooks/private/use-multimodal-input.d.ts
2
+ type UseMultimodalInputOptions = {
3
+ onSubmit?: (data: {
4
+ message: string;
5
+ files: File[];
6
+ }) => void | Promise<void>;
7
+ onError?: (error: Error) => void;
8
+ maxFileSize?: number;
9
+ maxFiles?: number;
10
+ allowedFileTypes?: string[];
11
+ };
12
+ type UseMultimodalInputReturn = {
13
+ message: string;
14
+ files: File[];
15
+ isSubmitting: boolean;
16
+ error: Error | null;
17
+ setMessage: (message: string) => void;
18
+ addFiles: (files: File[]) => void;
19
+ removeFile: (index: number) => void;
20
+ clearFiles: () => void;
21
+ submit: () => Promise<void>;
22
+ reset: () => void;
23
+ isValid: boolean;
24
+ canSubmit: boolean;
25
+ };
26
+ /**
27
+ * Manages message text, file attachments and validation for the multimodal
28
+ * composer component. Provides ergonomic helpers for submit flows and error
29
+ * reporting.
30
+ */
31
+ declare const useMultimodalInput: ({
32
+ onSubmit,
33
+ onError,
34
+ maxFileSize,
35
+ maxFiles,
36
+ allowedFileTypes
37
+ }?: UseMultimodalInputOptions) => UseMultimodalInputReturn;
38
+ //#endregion
39
+ export { UseMultimodalInputOptions, UseMultimodalInputReturn, useMultimodalInput };
40
+ //# sourceMappingURL=use-multimodal-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-multimodal-input.d.ts","names":[],"sources":["../../../src/hooks/private/use-multimodal-input.ts"],"sourcesContent":[],"mappings":";KAEY,yBAAA;EAAA,QAAA,CAAA,EAAA,CAAA,IAAA,EAAA;IAAyB,OAAA,EAAA,MAAA;IACQ,KAAA,EAAA,IAAA,EAAA;KAAoB,GAAA,IAAA,GAAA,OAAA,CAAA,IAAA,CAAA;SAC9C,CAAA,EAAA,CAAA,KAAA,EAAA,KAAA,EAAA,GAAA,IAAA;EAAK,WAAA,CAAA,EAAA,MAAA;EAMZ,QAAA,CAAA,EAAA,MAAA;EAAwB,gBAAA,CAAA,EAAA,MAAA,EAAA;;AAK5B,KALI,wBAAA,GAKJ;SAIW,EAAA,MAAA;OAGJ,EATP,IASO,EAAA;EAAO,YAAA,EAAA,OAAA;EAaT,KAAA,EApBL,KAoBK,GAAA,IAAA;EA4JZ,UAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;UA5JkC,EAAA,CAAA,KAAA,EAhBhB,IAgBgB,EAAA,EAAA,GAAA,IAAA;YAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA;YAAA,EAAA,GAAA,GAAA,IAAA;QAAA,EAAA,GAAA,GAbpB,OAaoB,CAAA,IAAA,CAAA;OAAA,EAAA,GAAA,GAAA,IAAA;SAMhC,EAAA,OAAA;WAAiC,EAAA,OAAA;CAsJnC;;;;;;cA5JY;;;;;;IAMV,8BAAiC"}
@@ -0,0 +1,129 @@
1
+ import { useCallback, useRef, useState } from "react";
2
+
3
+ //#region src/hooks/private/use-multimodal-input.ts
4
+ /**
5
+ * Manages message text, file attachments and validation for the multimodal
6
+ * composer component. Provides ergonomic helpers for submit flows and error
7
+ * reporting.
8
+ */
9
+ const useMultimodalInput = ({ onSubmit, onError, maxFileSize = 10 * 1024 * 1024, maxFiles = 5, allowedFileTypes = [
10
+ "image/*",
11
+ "application/pdf",
12
+ "text/*"
13
+ ] } = {}) => {
14
+ const [message, setMessage] = useState("");
15
+ const [files, setFiles] = useState([]);
16
+ const [isSubmitting, setIsSubmitting] = useState(false);
17
+ const [error, setError] = useState(null);
18
+ const fileUrlsRef = useRef([]);
19
+ const validateFile = useCallback((file) => {
20
+ if (file.size > maxFileSize) return `File "${file.name}" exceeds maximum size of ${maxFileSize / 1024 / 1024}MB`;
21
+ if (allowedFileTypes.length > 0) {
22
+ if (!allowedFileTypes.some((type) => {
23
+ if (type.endsWith("/*")) {
24
+ const baseType = type.slice(0, -2);
25
+ return file.type.startsWith(baseType);
26
+ }
27
+ return file.type === type;
28
+ })) return `File type "${file.type}" is not allowed`;
29
+ }
30
+ return null;
31
+ }, [maxFileSize, allowedFileTypes]);
32
+ const addFiles = useCallback((newFiles) => {
33
+ setError(null);
34
+ if (files.length + newFiles.length > maxFiles) {
35
+ const err = /* @__PURE__ */ new Error(`Cannot add files: maximum ${maxFiles} files allowed`);
36
+ setError(err);
37
+ onError?.(err);
38
+ return;
39
+ }
40
+ for (const file of newFiles) {
41
+ const validationError = validateFile(file);
42
+ if (validationError) {
43
+ const err = new Error(validationError);
44
+ setError(err);
45
+ onError?.(err);
46
+ return;
47
+ }
48
+ }
49
+ setFiles((prev) => [...prev, ...newFiles]);
50
+ }, [
51
+ files.length,
52
+ maxFiles,
53
+ validateFile,
54
+ onError
55
+ ]);
56
+ const removeFile = useCallback((index) => {
57
+ setFiles((prev) => {
58
+ const newFiles = [...prev];
59
+ newFiles.splice(index, 1);
60
+ if (fileUrlsRef.current[index]) {
61
+ URL.revokeObjectURL(fileUrlsRef.current[index]);
62
+ fileUrlsRef.current.splice(index, 1);
63
+ }
64
+ return newFiles;
65
+ });
66
+ setError(null);
67
+ }, []);
68
+ const clearFiles = useCallback(() => {
69
+ for (const url of fileUrlsRef.current) URL.revokeObjectURL(url);
70
+ fileUrlsRef.current = [];
71
+ setFiles([]);
72
+ setError(null);
73
+ }, []);
74
+ const reset = useCallback(() => {
75
+ setMessage("");
76
+ clearFiles();
77
+ setError(null);
78
+ setIsSubmitting(false);
79
+ }, [clearFiles]);
80
+ const submit = useCallback(async () => {
81
+ if (!onSubmit) return;
82
+ if (!message.trim() && files.length === 0) {
83
+ const err = /* @__PURE__ */ new Error("Please provide a message or attach files");
84
+ setError(err);
85
+ onError?.(err);
86
+ return;
87
+ }
88
+ setIsSubmitting(true);
89
+ setError(null);
90
+ try {
91
+ await onSubmit({
92
+ message: message.trim(),
93
+ files
94
+ });
95
+ reset();
96
+ } catch (err) {
97
+ const _error = err instanceof Error ? err : /* @__PURE__ */ new Error("Failed to submit");
98
+ setError(_error);
99
+ onError?.(_error);
100
+ } finally {
101
+ setIsSubmitting(false);
102
+ }
103
+ }, [
104
+ message,
105
+ files,
106
+ onSubmit,
107
+ onError,
108
+ reset
109
+ ]);
110
+ const isValid = message.trim().length > 0 || files.length > 0;
111
+ return {
112
+ message,
113
+ files,
114
+ isSubmitting,
115
+ error,
116
+ setMessage,
117
+ addFiles,
118
+ removeFile,
119
+ clearFiles,
120
+ submit,
121
+ reset,
122
+ isValid,
123
+ canSubmit: isValid && !isSubmitting && !error
124
+ };
125
+ };
126
+
127
+ //#endregion
128
+ export { useMultimodalInput };
129
+ //# sourceMappingURL=use-multimodal-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-multimodal-input.js","names":[],"sources":["../../../src/hooks/private/use-multimodal-input.ts"],"sourcesContent":["import { useCallback, useRef, useState } from \"react\";\n\nexport type UseMultimodalInputOptions = {\n\tonSubmit?: (data: { message: string; files: File[] }) => void | Promise<void>;\n\tonError?: (error: Error) => void;\n\tmaxFileSize?: number; // in bytes\n\tmaxFiles?: number;\n\tallowedFileTypes?: string[]; // MIME types\n};\n\nexport type UseMultimodalInputReturn = {\n\t// State\n\tmessage: string;\n\tfiles: File[];\n\tisSubmitting: boolean;\n\terror: Error | null;\n\n\t// Actions\n\tsetMessage: (message: string) => void;\n\taddFiles: (files: File[]) => void;\n\tremoveFile: (index: number) => void;\n\tclearFiles: () => void;\n\tsubmit: () => Promise<void>;\n\treset: () => void;\n\n\t// Validation\n\tisValid: boolean;\n\tcanSubmit: boolean;\n};\n\n/**\n * Manages message text, file attachments and validation for the multimodal\n * composer component. Provides ergonomic helpers for submit flows and error\n * reporting.\n */\nexport const useMultimodalInput = ({\n\tonSubmit,\n\tonError,\n\tmaxFileSize = 10 * 1024 * 1024, // 10MB default\n\tmaxFiles = 5,\n\tallowedFileTypes = [\"image/*\", \"application/pdf\", \"text/*\"],\n}: UseMultimodalInputOptions = {}): UseMultimodalInputReturn => {\n\tconst [message, setMessage] = useState(\"\");\n\tconst [files, setFiles] = useState<File[]>([]);\n\tconst [isSubmitting, setIsSubmitting] = useState(false);\n\tconst [error, setError] = useState<Error | null>(null);\n\n\t// Use ref to prevent re-renders when tracking file URLs\n\tconst fileUrlsRef = useRef<string[]>([]);\n\n\t// Validation helpers\n\tconst validateFile = useCallback(\n\t\t(file: File): string | null => {\n\t\t\tif (file.size > maxFileSize) {\n\t\t\t\treturn `File \"${file.name}\" exceeds maximum size of ${maxFileSize / 1024 / 1024}MB`;\n\t\t\t}\n\n\t\t\tif (allowedFileTypes.length > 0) {\n\t\t\t\tconst isAllowed = allowedFileTypes.some((type) => {\n\t\t\t\t\tif (type.endsWith(\"/*\")) {\n\t\t\t\t\t\tconst baseType = type.slice(0, -2);\n\t\t\t\t\t\treturn file.type.startsWith(baseType);\n\t\t\t\t\t}\n\t\t\t\t\treturn file.type === type;\n\t\t\t\t});\n\n\t\t\t\tif (!isAllowed) {\n\t\t\t\t\treturn `File type \"${file.type}\" is not allowed`;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn null;\n\t\t},\n\t\t[maxFileSize, allowedFileTypes]\n\t);\n\n\t// Actions\n\tconst addFiles = useCallback(\n\t\t(newFiles: File[]) => {\n\t\t\tsetError(null);\n\n\t\t\t// Check max files limit\n\t\t\tif (files.length + newFiles.length > maxFiles) {\n\t\t\t\tconst err = new Error(\n\t\t\t\t\t`Cannot add files: maximum ${maxFiles} files allowed`\n\t\t\t\t);\n\t\t\t\tsetError(err);\n\t\t\t\tonError?.(err);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Validate each file\n\t\t\tfor (const file of newFiles) {\n\t\t\t\tconst validationError = validateFile(file);\n\t\t\t\tif (validationError) {\n\t\t\t\t\tconst err = new Error(validationError);\n\t\t\t\t\tsetError(err);\n\t\t\t\t\tonError?.(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsetFiles((prev) => [...prev, ...newFiles]);\n\t\t},\n\t\t[files.length, maxFiles, validateFile, onError]\n\t);\n\n\tconst removeFile = useCallback((index: number) => {\n\t\tsetFiles((prev) => {\n\t\t\tconst newFiles = [...prev];\n\t\t\tnewFiles.splice(index, 1);\n\n\t\t\t// Clean up object URL if it exists\n\t\t\tif (fileUrlsRef.current[index]) {\n\t\t\t\tURL.revokeObjectURL(fileUrlsRef.current[index]);\n\t\t\t\tfileUrlsRef.current.splice(index, 1);\n\t\t\t}\n\n\t\t\treturn newFiles;\n\t\t});\n\t\tsetError(null);\n\t}, []);\n\n\tconst clearFiles = useCallback(() => {\n\t\t// Clean up all object URLs\n\t\tfor (const url of fileUrlsRef.current) {\n\t\t\tURL.revokeObjectURL(url);\n\t\t}\n\t\tfileUrlsRef.current = [];\n\n\t\tsetFiles([]);\n\t\tsetError(null);\n\t}, []);\n\n\tconst reset = useCallback(() => {\n\t\tsetMessage(\"\");\n\t\tclearFiles();\n\t\tsetError(null);\n\t\tsetIsSubmitting(false);\n\t}, [clearFiles]);\n\n\tconst submit = useCallback(async () => {\n\t\tif (!onSubmit) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!message.trim() && files.length === 0) {\n\t\t\tconst err = new Error(\"Please provide a message or attach files\");\n\t\t\tsetError(err);\n\t\t\tonError?.(err);\n\t\t\treturn;\n\t\t}\n\n\t\tsetIsSubmitting(true);\n\t\tsetError(null);\n\n\t\ttry {\n\t\t\tawait onSubmit({ message: message.trim(), files });\n\t\t\treset();\n\t\t} catch (err) {\n\t\t\tconst _error = err instanceof Error ? err : new Error(\"Failed to submit\");\n\t\t\tsetError(_error);\n\t\t\tonError?.(_error);\n\t\t} finally {\n\t\t\tsetIsSubmitting(false);\n\t\t}\n\t}, [message, files, onSubmit, onError, reset]);\n\n\t// Computed values\n\tconst isValid = message.trim().length > 0 || files.length > 0;\n\tconst canSubmit = isValid && !isSubmitting && !error;\n\n\treturn {\n\t\t// State\n\t\tmessage,\n\t\tfiles,\n\t\tisSubmitting,\n\t\terror,\n\n\t\t// Actions\n\t\tsetMessage,\n\t\taddFiles,\n\t\tremoveFile,\n\t\tclearFiles,\n\t\tsubmit,\n\t\treset,\n\n\t\t// Validation\n\t\tisValid,\n\t\tcanSubmit,\n\t};\n};\n"],"mappings":"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"}