@cossistant/react 0.0.25 → 0.0.28

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 (225) hide show
  1. package/api.d.ts +1 -1
  2. package/api.d.ts.map +1 -1
  3. package/checks.d.ts +1 -1
  4. package/checks.d.ts.map +1 -1
  5. package/clsx.d.ts +1 -1
  6. package/clsx.d.ts.map +1 -1
  7. package/coerce.d.ts +1 -1
  8. package/coerce.d.ts.map +1 -1
  9. package/conversation.d.ts +3 -0
  10. package/conversation.d.ts.map +1 -1
  11. package/core.d.ts +1 -1
  12. package/core.d.ts.map +1 -1
  13. package/errors.d.ts +1 -1
  14. package/errors.d.ts.map +1 -1
  15. package/errors2.d.ts +1 -1
  16. package/errors2.d.ts.map +1 -1
  17. package/hooks/index.d.ts +2 -1
  18. package/hooks/index.js +6 -5
  19. package/hooks/private/store/use-website-store.js +2 -1
  20. package/hooks/private/store/use-website-store.js.map +1 -1
  21. package/hooks/private/use-client-query.d.ts +6 -0
  22. package/hooks/private/use-client-query.d.ts.map +1 -1
  23. package/hooks/private/use-client-query.js +26 -3
  24. package/hooks/private/use-client-query.js.map +1 -1
  25. package/hooks/private/use-multimodal-input.d.ts.map +1 -1
  26. package/hooks/private/use-multimodal-input.js +7 -5
  27. package/hooks/private/use-multimodal-input.js.map +1 -1
  28. package/hooks/private/use-visitor-typing-reporter.d.ts +18 -1
  29. package/hooks/private/use-visitor-typing-reporter.d.ts.map +1 -1
  30. package/hooks/private/use-visitor-typing-reporter.js +34 -4
  31. package/hooks/private/use-visitor-typing-reporter.js.map +1 -1
  32. package/hooks/use-conversation-page.d.ts +1 -0
  33. package/hooks/use-conversation-page.d.ts.map +1 -1
  34. package/hooks/use-conversation-page.js +6 -1
  35. package/hooks/use-conversation-page.js.map +1 -1
  36. package/hooks/use-conversation-preview.d.ts +2 -1
  37. package/hooks/use-conversation-preview.d.ts.map +1 -1
  38. package/hooks/use-conversation-preview.js +1 -1
  39. package/hooks/use-conversation-preview.js.map +1 -1
  40. package/hooks/use-conversation-timeline-items.js +2 -1
  41. package/hooks/use-conversation-timeline-items.js.map +1 -1
  42. package/hooks/use-conversation.js +2 -1
  43. package/hooks/use-conversation.js.map +1 -1
  44. package/hooks/use-conversations.js +1 -0
  45. package/hooks/use-conversations.js.map +1 -1
  46. package/hooks/use-file-upload.d.ts +55 -0
  47. package/hooks/use-file-upload.d.ts.map +1 -0
  48. package/hooks/use-file-upload.js +100 -0
  49. package/hooks/use-file-upload.js.map +1 -0
  50. package/hooks/use-message-composer.d.ts +11 -0
  51. package/hooks/use-message-composer.d.ts.map +1 -1
  52. package/hooks/use-message-composer.js +7 -3
  53. package/hooks/use-message-composer.js.map +1 -1
  54. package/hooks/use-realtime-support.d.ts.map +1 -1
  55. package/hooks/use-send-message.d.ts +1 -0
  56. package/hooks/use-send-message.d.ts.map +1 -1
  57. package/hooks/use-send-message.js +63 -11
  58. package/hooks/use-send-message.js.map +1 -1
  59. package/index.d.ts +6 -3
  60. package/index.js +13 -10
  61. package/openapi30.d.ts +1 -1
  62. package/openapi30.d.ts.map +1 -1
  63. package/openapi31.d.ts +1 -1
  64. package/openapi31.d.ts.map +1 -1
  65. package/package.json +4 -3
  66. package/parse.d.ts +1 -1
  67. package/parse.d.ts.map +1 -1
  68. package/primitives/conversation-timeline.d.ts.map +1 -1
  69. package/primitives/conversation-timeline.js +10 -5
  70. package/primitives/conversation-timeline.js.map +1 -1
  71. package/primitives/index.d.ts +4 -3
  72. package/primitives/index.js +12 -5
  73. package/primitives/index.parts.d.ts +3 -2
  74. package/primitives/index.parts.js +4 -3
  75. package/primitives/timeline-item-attachments.d.ts +100 -0
  76. package/primitives/timeline-item-attachments.d.ts.map +1 -0
  77. package/primitives/timeline-item-attachments.js +151 -0
  78. package/primitives/timeline-item-attachments.js.map +1 -0
  79. package/primitives/trigger.d.ts +91 -0
  80. package/primitives/trigger.d.ts.map +1 -0
  81. package/primitives/trigger.js +74 -0
  82. package/primitives/trigger.js.map +1 -0
  83. package/primitives/window.d.ts +22 -1
  84. package/primitives/window.d.ts.map +1 -1
  85. package/primitives/window.js +91 -5
  86. package/primitives/window.js.map +1 -1
  87. package/provider.d.ts.map +1 -1
  88. package/provider.js +8 -3
  89. package/provider.js.map +1 -1
  90. package/realtime/index.js +1 -1
  91. package/realtime/provider.js +1 -1
  92. package/realtime/support-provider.js +1 -1
  93. package/realtime/support-provider.js.map +1 -1
  94. package/realtime-events.d.ts +39 -0
  95. package/realtime-events.d.ts.map +1 -1
  96. package/registries.d.ts +1 -1
  97. package/registries.d.ts.map +1 -1
  98. package/schemas.d.ts +1 -1
  99. package/schemas.d.ts.map +1 -1
  100. package/schemas2.d.ts +1 -1
  101. package/schemas2.d.ts.map +1 -1
  102. package/schemas3.d.ts +1 -0
  103. package/schemas3.d.ts.map +1 -1
  104. package/specification-extension.d.ts +1 -1
  105. package/specification-extension.d.ts.map +1 -1
  106. package/standard-schema.d.ts +1 -1
  107. package/standard-schema.d.ts.map +1 -1
  108. package/support/components/button.d.ts +1 -1
  109. package/support/components/content.d.ts +30 -0
  110. package/support/components/content.d.ts.map +1 -0
  111. package/support/components/content.js +282 -0
  112. package/support/components/content.js.map +1 -0
  113. package/support/components/conversation-button-link.js +1 -1
  114. package/support/components/conversation-timeline.js +3 -3
  115. package/support/components/conversation-timeline.js.map +1 -1
  116. package/support/components/header.js +1 -1
  117. package/support/components/image-lightbox.d.ts +49 -0
  118. package/support/components/image-lightbox.d.ts.map +1 -0
  119. package/support/components/image-lightbox.js +142 -0
  120. package/support/components/image-lightbox.js.map +1 -0
  121. package/support/components/index.d.ts +5 -4
  122. package/support/components/index.js +4 -4
  123. package/support/components/multimodal-input.d.ts +4 -1
  124. package/support/components/multimodal-input.d.ts.map +1 -1
  125. package/support/components/multimodal-input.js +71 -45
  126. package/support/components/multimodal-input.js.map +1 -1
  127. package/support/components/navigation-tab.js +1 -1
  128. package/support/components/root.d.ts +23 -0
  129. package/support/components/root.d.ts.map +1 -0
  130. package/support/components/root.js +36 -0
  131. package/support/components/root.js.map +1 -0
  132. package/support/components/timeline-message-item.d.ts.map +1 -1
  133. package/support/components/timeline-message-item.js +82 -18
  134. package/support/components/timeline-message-item.js.map +1 -1
  135. package/support/components/trigger.d.ts +14 -0
  136. package/support/components/trigger.d.ts.map +1 -0
  137. package/support/components/{bubble.js → trigger.js} +16 -12
  138. package/support/components/trigger.js.map +1 -0
  139. package/support/components/typing-indicator.d.ts.map +1 -1
  140. package/support/components/typing-indicator.js +1 -0
  141. package/support/components/typing-indicator.js.map +1 -1
  142. package/support/context/controlled-state.d.ts +46 -0
  143. package/support/context/controlled-state.d.ts.map +1 -0
  144. package/support/context/controlled-state.js +34 -0
  145. package/support/context/controlled-state.js.map +1 -0
  146. package/support/context/events.d.ts +103 -0
  147. package/support/context/events.d.ts.map +1 -0
  148. package/support/context/events.js +139 -0
  149. package/support/context/events.js.map +1 -0
  150. package/support/context/handle.d.ts +90 -0
  151. package/support/context/handle.d.ts.map +1 -0
  152. package/support/context/handle.js +79 -0
  153. package/support/context/handle.js.map +1 -0
  154. package/support/context/positioning.d.ts +17 -0
  155. package/support/context/positioning.d.ts.map +1 -0
  156. package/support/context/positioning.js +26 -0
  157. package/support/context/positioning.js.map +1 -0
  158. package/support/context/slots.d.ts +85 -0
  159. package/support/context/slots.d.ts.map +1 -0
  160. package/support/context/slots.js +115 -0
  161. package/support/context/slots.js.map +1 -0
  162. package/support/context/websocket.d.ts +8 -1
  163. package/support/context/websocket.d.ts.map +1 -1
  164. package/support/context/websocket.js +8 -1
  165. package/support/context/websocket.js.map +1 -1
  166. package/support/index.d.ts +239 -54
  167. package/support/index.d.ts.map +1 -1
  168. package/support/index.js +254 -33
  169. package/support/index.js.map +1 -1
  170. package/support/pages/articles.d.ts.map +1 -1
  171. package/support/pages/articles.js +3 -4
  172. package/support/pages/articles.js.map +1 -1
  173. package/support/pages/conversation-history.js +2 -2
  174. package/support/pages/conversation.js +6 -5
  175. package/support/pages/conversation.js.map +1 -1
  176. package/support/pages/home.js +2 -2
  177. package/support/router.d.ts +52 -12
  178. package/support/router.d.ts.map +1 -1
  179. package/support/router.js +78 -30
  180. package/support/router.js.map +1 -1
  181. package/support/store/index.d.ts +2 -2
  182. package/support/store/support-store.d.ts +26 -20
  183. package/support/store/support-store.d.ts.map +1 -1
  184. package/support/store/support-store.js +47 -6
  185. package/support/store/support-store.js.map +1 -1
  186. package/support/{support-D2EgfIts.css → support-C7Xaw-N6.css} +1 -2
  187. package/support/support-C7Xaw-N6.css.map +1 -0
  188. package/support/text/index.js.map +1 -1
  189. package/support/types.d.ts +75 -12
  190. package/support/types.d.ts.map +1 -1
  191. package/support.css +2 -2
  192. package/tailwind.css +0 -1
  193. package/timeline-item.d.ts +68 -2
  194. package/timeline-item.d.ts.map +1 -1
  195. package/util.d.ts +1 -1
  196. package/util.d.ts.map +1 -1
  197. package/utils/index.d.ts +2 -1
  198. package/utils/index.js +2 -1
  199. package/utils/merge-refs.d.ts +30 -0
  200. package/utils/merge-refs.d.ts.map +1 -0
  201. package/utils/merge-refs.js +46 -0
  202. package/utils/merge-refs.js.map +1 -0
  203. package/utils/use-render-element.d.ts.map +1 -1
  204. package/utils/use-render-element.js +20 -7
  205. package/utils/use-render-element.js.map +1 -1
  206. package/versions.d.ts +1 -1
  207. package/versions.d.ts.map +1 -1
  208. package/zod-extensions.d.ts +1 -1
  209. package/zod-extensions.d.ts.map +1 -1
  210. package/primitives/bubble.d.ts +0 -38
  211. package/primitives/bubble.d.ts.map +0 -1
  212. package/primitives/bubble.js +0 -57
  213. package/primitives/bubble.js.map +0 -1
  214. package/support/components/bubble.d.ts +0 -10
  215. package/support/components/bubble.d.ts.map +0 -1
  216. package/support/components/bubble.js.map +0 -1
  217. package/support/components/container.d.ts +0 -13
  218. package/support/components/container.d.ts.map +0 -1
  219. package/support/components/container.js +0 -109
  220. package/support/components/container.js.map +0 -1
  221. package/support/components/support-content.d.ts +0 -22
  222. package/support/components/support-content.d.ts.map +0 -1
  223. package/support/components/support-content.js +0 -48
  224. package/support/components/support-content.js.map +0 -1
  225. package/support/support-D2EgfIts.css.map +0 -1
package/errors2.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"errors2.d.ts","names":["core","$ZodError","ZodIssue","$ZodIssue","ZodError","U","T","$ZodFormattedError","$ZodFlattenedError","$constructor","ZodRealError","ZodFlattenedError","ZodFormattedError","$ZodErrorMap","ZodErrorMap","IssueData","$ZodRawIssue"],"sources":["../../../node_modules/.bun/zod@4.1.12/node_modules/zod/v4/classic/errors.d.cts"],"sourcesContent":["import * as core from \"../core/index.cjs\";\nimport { $ZodError } from \"../core/index.cjs\";\n/** @deprecated Use `z.core.$ZodIssue` from `@zod/core` instead, especially if you are building a library on top of Zod. */\nexport type ZodIssue = core.$ZodIssue;\n/** An Error-like class used to store Zod validation issues. */\nexport interface ZodError<T = unknown> extends $ZodError<T> {\n /** @deprecated Use the `z.treeifyError(err)` function instead. */\n format(): core.$ZodFormattedError<T>;\n format<U>(mapper: (issue: core.$ZodIssue) => U): core.$ZodFormattedError<T, U>;\n /** @deprecated Use the `z.treeifyError(err)` function instead. */\n flatten(): core.$ZodFlattenedError<T>;\n flatten<U>(mapper: (issue: core.$ZodIssue) => U): core.$ZodFlattenedError<T, U>;\n /** @deprecated Push directly to `.issues` instead. */\n addIssue(issue: core.$ZodIssue): void;\n /** @deprecated Push directly to `.issues` instead. */\n addIssues(issues: core.$ZodIssue[]): void;\n /** @deprecated Check `err.issues.length === 0` instead. */\n isEmpty: boolean;\n}\nexport declare const ZodError: core.$constructor<ZodError>;\nexport declare const ZodRealError: core.$constructor<ZodError>;\nexport type { \n/** @deprecated Use `z.core.$ZodFlattenedError` instead. */\n$ZodFlattenedError as ZodFlattenedError, \n/** @deprecated Use `z.core.$ZodFormattedError` instead. */\n$ZodFormattedError as ZodFormattedError, \n/** @deprecated Use `z.core.$ZodErrorMap` instead. */\n$ZodErrorMap as ZodErrorMap, } from \"../core/index.cjs\";\n/** @deprecated Use `z.core.$ZodRawIssue` instead. */\nexport type IssueData = core.$ZodRawIssue;\n"],"x_google_ignoreList":[0],"mappings":";;;;;AAKA;AAAyDM,UAAxCF,QAAwCE,CAAAA,IAAAA,OAAAA,CAAAA,SAAVL,SAAUK,CAAAA,CAAAA,CAAAA,CAAAA;EAEnBA;EAAxBN,MAAAA,EAAAA,EAAAA,kBAAAA,CAAwBM,CAAxBN,CAAAA;EACgBA,MAAAA,CAAAA,CAAAA,CAAAA,CAAAA,MAAAA,EAAAA,CAAAA,KAAAA,EAAAA,SAAAA,EAAAA,GAAmBK,CAAnBL,CAAAA,EAAuBA,kBAAvBA,CAA+CM,CAA/CN,EAAkDK,CAAlDL,CAAAA;EAAmBK;EAA4BC,OAAAA,EAAAA,EAE9DN,kBAF8DM,CAEtCA,CAFsCA,CAAAA;EAAGD,OAAAA,CAAAA,CAAAA,CAAAA,CAAAA,MAAAA,EAAAA,CAAAA,KAAAA,EAGjDL,SAHiDK,EAAAA,GAG9BA,CAH8BA,CAAAA,EAG1BL,kBAH0BK,CAGFC,CAHED,EAGCA,CAHDA,CAAAA;EAA3BL;EAEdM,QAAAA,CAAAA,KAAAA,EAGnBN,SAHmBM,CAAAA,EAAAA,IAAAA;EAAxBN;EACgBA,SAAAA,CAAAA,MAAAA,EAITA,SAJSA,EAAAA,CAAAA,EAAAA,IAAAA;EAAmBK;EAA4BC,OAAAA,EAAAA,OAAAA;;AAAxBN,cAQjCI,QARiCJ,EAQvBA,YARuBA,CAQLI,QARKJ,CAAAA"}
1
+ {"version":3,"file":"errors2.d.ts","names":["core","$ZodError","ZodIssue","$ZodIssue","ZodError","U","T","$ZodFormattedError","$ZodFlattenedError","$constructor","ZodRealError","ZodFlattenedError","ZodFormattedError","$ZodErrorMap","ZodErrorMap","IssueData","$ZodRawIssue"],"sources":["../../../node_modules/zod/v4/classic/errors.d.cts"],"sourcesContent":["import * as core from \"../core/index.cjs\";\nimport { $ZodError } from \"../core/index.cjs\";\n/** @deprecated Use `z.core.$ZodIssue` from `@zod/core` instead, especially if you are building a library on top of Zod. */\nexport type ZodIssue = core.$ZodIssue;\n/** An Error-like class used to store Zod validation issues. */\nexport interface ZodError<T = unknown> extends $ZodError<T> {\n /** @deprecated Use the `z.treeifyError(err)` function instead. */\n format(): core.$ZodFormattedError<T>;\n format<U>(mapper: (issue: core.$ZodIssue) => U): core.$ZodFormattedError<T, U>;\n /** @deprecated Use the `z.treeifyError(err)` function instead. */\n flatten(): core.$ZodFlattenedError<T>;\n flatten<U>(mapper: (issue: core.$ZodIssue) => U): core.$ZodFlattenedError<T, U>;\n /** @deprecated Push directly to `.issues` instead. */\n addIssue(issue: core.$ZodIssue): void;\n /** @deprecated Push directly to `.issues` instead. */\n addIssues(issues: core.$ZodIssue[]): void;\n /** @deprecated Check `err.issues.length === 0` instead. */\n isEmpty: boolean;\n}\nexport declare const ZodError: core.$constructor<ZodError>;\nexport declare const ZodRealError: core.$constructor<ZodError>;\nexport type { \n/** @deprecated Use `z.core.$ZodFlattenedError` instead. */\n$ZodFlattenedError as ZodFlattenedError, \n/** @deprecated Use `z.core.$ZodFormattedError` instead. */\n$ZodFormattedError as ZodFormattedError, \n/** @deprecated Use `z.core.$ZodErrorMap` instead. */\n$ZodErrorMap as ZodErrorMap, } from \"../core/index.cjs\";\n/** @deprecated Use `z.core.$ZodRawIssue` instead. */\nexport type IssueData = core.$ZodRawIssue;\n"],"x_google_ignoreList":[0],"mappings":";;;;;AAKA;AAAyDM,UAAxCF,QAAwCE,CAAAA,IAAAA,OAAAA,CAAAA,SAAVL,SAAUK,CAAAA,CAAAA,CAAAA,CAAAA;EAEnBA;EAAxBN,MAAAA,EAAAA,EAAAA,kBAAAA,CAAwBM,CAAxBN,CAAAA;EACgBA,MAAAA,CAAAA,CAAAA,CAAAA,CAAAA,MAAAA,EAAAA,CAAAA,KAAAA,EAAAA,SAAAA,EAAAA,GAAmBK,CAAnBL,CAAAA,EAAuBA,kBAAvBA,CAA+CM,CAA/CN,EAAkDK,CAAlDL,CAAAA;EAAmBK;EAA4BC,OAAAA,EAAAA,EAE9DN,kBAF8DM,CAEtCA,CAFsCA,CAAAA;EAAGD,OAAAA,CAAAA,CAAAA,CAAAA,CAAAA,MAAAA,EAAAA,CAAAA,KAAAA,EAGjDL,SAHiDK,EAAAA,GAG9BA,CAH8BA,CAAAA,EAG1BL,kBAH0BK,CAGFC,CAHED,EAGCA,CAHDA,CAAAA;EAA3BL;EAEdM,QAAAA,CAAAA,KAAAA,EAGnBN,SAHmBM,CAAAA,EAAAA,IAAAA;EAAxBN;EACgBA,SAAAA,CAAAA,MAAAA,EAITA,SAJSA,EAAAA,CAAAA,EAAAA,IAAAA;EAAmBK;EAA4BC,OAAAA,EAAAA,OAAAA;;AAAxBN,cAQjCI,QARiCJ,EAQvBA,YARuBA,CAQLI,QARKJ,CAAAA"}
package/hooks/index.d.ts CHANGED
@@ -16,6 +16,7 @@ import { ConversationTypingParticipant, useConversationTyping } from "./use-conv
16
16
  import { ConversationTimelineTypingParticipant, UseConversationTimelineOptions, UseConversationTimelineReturn, useConversationTimeline } from "./use-conversation-timeline.js";
17
17
  import { UseConversationsOptions, UseConversationsResult, useConversations } from "./use-conversations.js";
18
18
  import { CreateConversationVariables, UseCreateConversationOptions, UseCreateConversationResult, useCreateConversation } from "./use-create-conversation.js";
19
+ import { FileUploadPart, UseFileUploadOptions, UseFileUploadReturn, useFileUpload } from "./use-file-upload.js";
19
20
  import { UseHomePageOptions, UseHomePageReturn, useHomePage } from "./use-home-page.js";
20
21
  import { UseMessageComposerOptions, UseMessageComposerReturn, useMessageComposer } from "./use-message-composer.js";
21
22
  import { useNewMessageSound } from "./use-new-message-sound.js";
@@ -26,4 +27,4 @@ import { UseSoundEffectOptions, UseSoundEffectReturn, useSoundEffect } from "./u
26
27
  import { useTypingSound } from "./use-typing-sound.js";
27
28
  import { UseVisitorReturn, useVisitor } from "./use-visitor.js";
28
29
  import { WindowVisibilityFocusState, useWindowVisibilityFocus } from "./use-window-visibility-focus.js";
29
- export { CONVERSATION_AUTO_SEEN_DELAY_MS, ConversationItem, ConversationLifecycleState, ConversationPreviewAssignedAgent, ConversationPreviewLastMessage, ConversationPreviewTypingParticipant, ConversationPreviewTypingState, ConversationTimelineTypingParticipant, ConversationTypingParticipant, CreateConversationVariables, GroupedMessage, SendMessageOptions, SendMessageResult, TimelineEventItem, TimelineToolItem, UseClientResult, UseComposerRefocusOptions, UseComposerRefocusReturn, UseConversationAutoSeenOptions, UseConversationHistoryPageOptions, UseConversationHistoryPageReturn, UseConversationLifecycleOptions, UseConversationLifecycleReturn, UseConversationOptions, UseConversationPageOptions, UseConversationPageReturn, UseConversationPreviewOptions, UseConversationPreviewReturn, UseConversationResult, UseConversationTimelineItemsOptions, UseConversationTimelineItemsResult, UseConversationTimelineOptions, UseConversationTimelineReturn, UseConversationsOptions, UseConversationsResult, UseCreateConversationOptions, UseCreateConversationResult, UseGroupedMessagesOptions, UseGroupedMessagesProps, UseHomePageOptions, UseHomePageReturn, UseMessageComposerOptions, UseMessageComposerReturn, UseMultimodalInputOptions, UseMultimodalInputReturn, UseRealtimeSupportOptions, UseRealtimeSupportResult, UseScrollMaskOptions, UseScrollMaskReturn, UseSendMessageOptions, UseSendMessageResult, UseSoundEffectOptions, UseSoundEffectReturn, UseVisitorReturn, WindowVisibilityFocusState, useClient, useClientQuery, useComposerRefocus, useConversation, useConversationAutoSeen, useConversationHistoryPage, useConversationLifecycle, useConversationPage, useConversationPreview, useConversationSeen, useConversationTimeline, useConversationTimelineItems, useConversationTyping, useConversations, useCreateConversation, useDebouncedConversationSeen, useDefaultMessages, useGroupedMessages, useHomePage, useMessageComposer, useMultimodalInput, useNewMessageSound, useRealtimeSupport, useScrollMask, useSendMessage, useSoundEffect, useTypingSound, useVisitor, useWindowVisibilityFocus };
30
+ export { CONVERSATION_AUTO_SEEN_DELAY_MS, ConversationItem, ConversationLifecycleState, ConversationPreviewAssignedAgent, ConversationPreviewLastMessage, ConversationPreviewTypingParticipant, ConversationPreviewTypingState, ConversationTimelineTypingParticipant, ConversationTypingParticipant, CreateConversationVariables, FileUploadPart, GroupedMessage, SendMessageOptions, SendMessageResult, TimelineEventItem, TimelineToolItem, UseClientResult, UseComposerRefocusOptions, UseComposerRefocusReturn, UseConversationAutoSeenOptions, UseConversationHistoryPageOptions, UseConversationHistoryPageReturn, UseConversationLifecycleOptions, UseConversationLifecycleReturn, UseConversationOptions, UseConversationPageOptions, UseConversationPageReturn, UseConversationPreviewOptions, UseConversationPreviewReturn, UseConversationResult, UseConversationTimelineItemsOptions, UseConversationTimelineItemsResult, UseConversationTimelineOptions, UseConversationTimelineReturn, UseConversationsOptions, UseConversationsResult, UseCreateConversationOptions, UseCreateConversationResult, UseFileUploadOptions, UseFileUploadReturn, UseGroupedMessagesOptions, UseGroupedMessagesProps, UseHomePageOptions, UseHomePageReturn, UseMessageComposerOptions, UseMessageComposerReturn, UseMultimodalInputOptions, UseMultimodalInputReturn, UseRealtimeSupportOptions, UseRealtimeSupportResult, UseScrollMaskOptions, UseScrollMaskReturn, UseSendMessageOptions, UseSendMessageResult, UseSoundEffectOptions, UseSoundEffectReturn, UseVisitorReturn, WindowVisibilityFocusState, useClient, useClientQuery, useComposerRefocus, useConversation, useConversationAutoSeen, useConversationHistoryPage, useConversationLifecycle, useConversationPage, useConversationPreview, useConversationSeen, useConversationTimeline, useConversationTimelineItems, useConversationTyping, useConversations, useCreateConversation, useDebouncedConversationSeen, useDefaultMessages, useFileUpload, useGroupedMessages, useHomePage, useMessageComposer, useMultimodalInput, useNewMessageSound, useRealtimeSupport, useScrollMask, useSendMessage, useSoundEffect, useTypingSound, useVisitor, useWindowVisibilityFocus };
package/hooks/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { useClientQuery } from "./private/use-client-query.js";
2
2
  import { useClient } from "./private/use-rest-client.js";
3
3
  import { useScrollMask } from "./use-scroll-mask.js";
4
- import { useConversation } from "./use-conversation.js";
4
+ import { useSoundEffect } from "./use-sound-effect.js";
5
+ import { useNewMessageSound } from "./use-new-message-sound.js";
6
+ import { useTypingSound } from "./use-typing-sound.js";
5
7
  import { useWindowVisibilityFocus } from "./use-window-visibility-focus.js";
6
8
  import { CONVERSATION_AUTO_SEEN_DELAY_MS, useConversationAutoSeen } from "./use-conversation-auto-seen.js";
7
9
  import { useConversationLifecycle } from "./use-conversation-lifecycle.js";
@@ -10,13 +12,10 @@ import { useMultimodalInput } from "./private/use-multimodal-input.js";
10
12
  import { useSendMessage } from "./use-send-message.js";
11
13
  import { useMessageComposer } from "./use-message-composer.js";
12
14
  import { useConversationPage } from "./use-conversation-page.js";
13
- import { useSoundEffect } from "./use-sound-effect.js";
14
- import { useNewMessageSound } from "./use-new-message-sound.js";
15
15
  import { useGroupedMessages } from "./private/use-grouped-messages.js";
16
16
  import { useConversationSeen, useDebouncedConversationSeen } from "./use-conversation-seen.js";
17
17
  import { useConversationTyping } from "./use-conversation-typing.js";
18
18
  import { useConversationTimeline } from "./use-conversation-timeline.js";
19
- import { useTypingSound } from "./use-typing-sound.js";
20
19
  import { useComposerRefocus } from "./use-composer-refocus.js";
21
20
  import { useVisitor } from "./use-visitor.js";
22
21
  import { useConversations } from "./use-conversations.js";
@@ -24,7 +23,9 @@ import { useConversationHistoryPage } from "./use-conversation-history-page.js";
24
23
  import { useConversationPreview } from "./use-conversation-preview.js";
25
24
  import { useHomePage } from "./use-home-page.js";
26
25
  import { useDefaultMessages } from "./private/use-default-messages.js";
26
+ import { useConversation } from "./use-conversation.js";
27
27
  import { useCreateConversation } from "./use-create-conversation.js";
28
+ import { useFileUpload } from "./use-file-upload.js";
28
29
  import { useRealtimeSupport } from "./use-realtime-support.js";
29
30
 
30
- export { CONVERSATION_AUTO_SEEN_DELAY_MS, useClient, useClientQuery, useComposerRefocus, useConversation, useConversationAutoSeen, useConversationHistoryPage, useConversationLifecycle, useConversationPage, useConversationPreview, useConversationSeen, useConversationTimeline, useConversationTimelineItems, useConversationTyping, useConversations, useCreateConversation, useDebouncedConversationSeen, useDefaultMessages, useGroupedMessages, useHomePage, useMessageComposer, useMultimodalInput, useNewMessageSound, useRealtimeSupport, useScrollMask, useSendMessage, useSoundEffect, useTypingSound, useVisitor, useWindowVisibilityFocus };
31
+ export { CONVERSATION_AUTO_SEEN_DELAY_MS, useClient, useClientQuery, useComposerRefocus, useConversation, useConversationAutoSeen, useConversationHistoryPage, useConversationLifecycle, useConversationPage, useConversationPreview, useConversationSeen, useConversationTimeline, useConversationTimelineItems, useConversationTyping, useConversations, useCreateConversation, useDebouncedConversationSeen, useDefaultMessages, useFileUpload, useGroupedMessages, useHomePage, useMessageComposer, useMultimodalInput, useNewMessageSound, useRealtimeSupport, useScrollMask, useSendMessage, useSoundEffect, useTypingSound, useVisitor, useWindowVisibilityFocus };
@@ -18,10 +18,11 @@ function useWebsiteStore(client, options = {}) {
18
18
  })(), (current) => current);
19
19
  const query = useClientQuery({
20
20
  client,
21
+ queryKey: "website",
21
22
  queryFn: (instance, params) => instance.fetchWebsite(params ?? {}),
22
23
  enabled: true,
23
24
  refetchInterval: options.refetchInterval ?? false,
24
- refetchOnWindowFocus: options.refetchOnWindowFocus ?? true,
25
+ refetchOnWindowFocus: options.refetchOnWindowFocus ?? false,
25
26
  refetchOnMount: state.status === "idle",
26
27
  initialData: state.website ?? void 0
27
28
  });
@@ -1 +1 @@
1
- {"version":3,"file":"use-website-store.js","names":[],"sources":["../../../../src/hooks/private/store/use-website-store.ts"],"sourcesContent":["import type {\n\tCossistantClient,\n\tWebsiteState,\n\tWebsiteStore,\n} from \"@cossistant/core\";\nimport type { PublicWebsiteResponse } from \"@cossistant/types\";\nimport { useMemo } from \"react\";\nimport { useClientQuery } from \"../use-client-query\";\nimport { useStoreSelector } from \"./use-store-selector\";\n\nconst EMPTY_STATE: WebsiteState = {\n\twebsite: null,\n\tstatus: \"idle\",\n\terror: null,\n};\n\nexport type UseWebsiteStoreResult = {\n\twebsite: WebsiteState[\"website\"];\n\tstatus: WebsiteState[\"status\"];\n\tisLoading: boolean;\n\terror: Error | null;\n\trefresh: () => Promise<WebsiteState[\"website\"] | null>;\n};\n\nexport type UseWebsiteStoreOptions = {\n\trefetchInterval?: number | false;\n\trefetchOnWindowFocus?: boolean;\n};\n\nfunction toError(state: WebsiteState, fallback: Error | null): Error | null {\n\tif (fallback) {\n\t\treturn fallback;\n\t}\n\n\tif (!state.error) {\n\t\treturn null;\n\t}\n\n\treturn new Error(state.error.message);\n}\n\n/**\n * Subscribes to the shared website store on the SDK client and exposes\n * convenient loading/error state plus a manual refresh helper.\n */\nexport function useWebsiteStore(\n\tclient: CossistantClient,\n\toptions: UseWebsiteStoreOptions = {}\n): UseWebsiteStoreResult {\n\tconst store =\n\t\tclient.websiteStore ??\n\t\t((): WebsiteStore => {\n\t\t\tthrow new Error(\"Website store is not available on the client instance\");\n\t\t})();\n\tconst state = useStoreSelector(store, (current) => current);\n\n\tconst query = useClientQuery<PublicWebsiteResponse, { force?: boolean }>({\n\t\tclient,\n\t\tqueryFn: (instance, params) => instance.fetchWebsite(params ?? {}),\n\t\tenabled: true,\n\t\trefetchInterval: options.refetchInterval ?? false,\n\t\trefetchOnWindowFocus: options.refetchOnWindowFocus ?? true,\n\t\trefetchOnMount: state.status === \"idle\",\n\t\tinitialData: state.website ?? undefined,\n\t});\n\n\tconst error = useMemo(\n\t\t() => toError(state, query.error),\n\t\t[state, query.error]\n\t);\n\tconst isLoading =\n\t\tquery.isLoading || state.status === \"loading\" || state.status === \"idle\";\n\n\tconst refresh = () =>\n\t\tquery\n\t\t\t.refetch({ force: true })\n\t\t\t.then((result) => result ?? client.websiteStore.getState().website)\n\t\t\t.catch(() => client.websiteStore.getState().website)\n\t\t\t.then((website) => website ?? null);\n\n\treturn {\n\t\twebsite: state.website,\n\t\tstatus: state.status,\n\t\tisLoading,\n\t\terror,\n\t\trefresh,\n\t};\n}\n"],"mappings":";;;;;AA6BA,SAAS,QAAQ,OAAqB,UAAsC;AAC3E,KAAI,SACH,QAAO;AAGR,KAAI,CAAC,MAAM,MACV,QAAO;AAGR,QAAO,IAAI,MAAM,MAAM,MAAM,QAAQ;;;;;;AAOtC,SAAgB,gBACf,QACA,UAAkC,EAAE,EACZ;CAMxB,MAAM,QAAQ,iBAJb,OAAO,uBACc;AACpB,QAAM,IAAI,MAAM,wDAAwD;KACrE,GACkC,YAAY,QAAQ;CAE3D,MAAM,QAAQ,eAA2D;EACxE;EACA,UAAU,UAAU,WAAW,SAAS,aAAa,UAAU,EAAE,CAAC;EAClE,SAAS;EACT,iBAAiB,QAAQ,mBAAmB;EAC5C,sBAAsB,QAAQ,wBAAwB;EACtD,gBAAgB,MAAM,WAAW;EACjC,aAAa,MAAM,WAAW;EAC9B,CAAC;CAEF,MAAM,QAAQ,cACP,QAAQ,OAAO,MAAM,MAAM,EACjC,CAAC,OAAO,MAAM,MAAM,CACpB;CACD,MAAM,YACL,MAAM,aAAa,MAAM,WAAW,aAAa,MAAM,WAAW;CAEnE,MAAM,gBACL,MACE,QAAQ,EAAE,OAAO,MAAM,CAAC,CACxB,MAAM,WAAW,UAAU,OAAO,aAAa,UAAU,CAAC,QAAQ,CAClE,YAAY,OAAO,aAAa,UAAU,CAAC,QAAQ,CACnD,MAAM,YAAY,WAAW,KAAK;AAErC,QAAO;EACN,SAAS,MAAM;EACf,QAAQ,MAAM;EACd;EACA;EACA;EACA"}
1
+ {"version":3,"file":"use-website-store.js","names":[],"sources":["../../../../src/hooks/private/store/use-website-store.ts"],"sourcesContent":["import type {\n\tCossistantClient,\n\tWebsiteState,\n\tWebsiteStore,\n} from \"@cossistant/core\";\nimport type { PublicWebsiteResponse } from \"@cossistant/types\";\nimport { useMemo } from \"react\";\nimport { useClientQuery } from \"../use-client-query\";\nimport { useStoreSelector } from \"./use-store-selector\";\n\nconst EMPTY_STATE: WebsiteState = {\n\twebsite: null,\n\tstatus: \"idle\",\n\terror: null,\n};\n\nexport type UseWebsiteStoreResult = {\n\twebsite: WebsiteState[\"website\"];\n\tstatus: WebsiteState[\"status\"];\n\tisLoading: boolean;\n\terror: Error | null;\n\trefresh: () => Promise<WebsiteState[\"website\"] | null>;\n};\n\nexport type UseWebsiteStoreOptions = {\n\trefetchInterval?: number | false;\n\trefetchOnWindowFocus?: boolean;\n};\n\nfunction toError(state: WebsiteState, fallback: Error | null): Error | null {\n\tif (fallback) {\n\t\treturn fallback;\n\t}\n\n\tif (!state.error) {\n\t\treturn null;\n\t}\n\n\treturn new Error(state.error.message);\n}\n\n/**\n * Subscribes to the shared website store on the SDK client and exposes\n * convenient loading/error state plus a manual refresh helper.\n */\nexport function useWebsiteStore(\n\tclient: CossistantClient,\n\toptions: UseWebsiteStoreOptions = {}\n): UseWebsiteStoreResult {\n\tconst store =\n\t\tclient.websiteStore ??\n\t\t((): WebsiteStore => {\n\t\t\tthrow new Error(\"Website store is not available on the client instance\");\n\t\t})();\n\tconst state = useStoreSelector(store, (current) => current);\n\n\tconst query = useClientQuery<PublicWebsiteResponse, { force?: boolean }>({\n\t\tclient,\n\t\tqueryKey: \"website\",\n\t\tqueryFn: (instance, params) => instance.fetchWebsite(params ?? {}),\n\t\tenabled: true,\n\t\trefetchInterval: options.refetchInterval ?? false,\n\t\trefetchOnWindowFocus: options.refetchOnWindowFocus ?? false,\n\t\trefetchOnMount: state.status === \"idle\",\n\t\tinitialData: state.website ?? undefined,\n\t});\n\n\tconst error = useMemo(\n\t\t() => toError(state, query.error),\n\t\t[state, query.error]\n\t);\n\tconst isLoading =\n\t\tquery.isLoading || state.status === \"loading\" || state.status === \"idle\";\n\n\tconst refresh = () =>\n\t\tquery\n\t\t\t.refetch({ force: true })\n\t\t\t.then((result) => result ?? client.websiteStore.getState().website)\n\t\t\t.catch(() => client.websiteStore.getState().website)\n\t\t\t.then((website) => website ?? null);\n\n\treturn {\n\t\twebsite: state.website,\n\t\tstatus: state.status,\n\t\tisLoading,\n\t\terror,\n\t\trefresh,\n\t};\n}\n"],"mappings":";;;;;AA6BA,SAAS,QAAQ,OAAqB,UAAsC;AAC3E,KAAI,SACH,QAAO;AAGR,KAAI,CAAC,MAAM,MACV,QAAO;AAGR,QAAO,IAAI,MAAM,MAAM,MAAM,QAAQ;;;;;;AAOtC,SAAgB,gBACf,QACA,UAAkC,EAAE,EACZ;CAMxB,MAAM,QAAQ,iBAJb,OAAO,uBACc;AACpB,QAAM,IAAI,MAAM,wDAAwD;KACrE,GACkC,YAAY,QAAQ;CAE3D,MAAM,QAAQ,eAA2D;EACxE;EACA,UAAU;EACV,UAAU,UAAU,WAAW,SAAS,aAAa,UAAU,EAAE,CAAC;EAClE,SAAS;EACT,iBAAiB,QAAQ,mBAAmB;EAC5C,sBAAsB,QAAQ,wBAAwB;EACtD,gBAAgB,MAAM,WAAW;EACjC,aAAa,MAAM,WAAW;EAC9B,CAAC;CAEF,MAAM,QAAQ,cACP,QAAQ,OAAO,MAAM,MAAM,EACjC,CAAC,OAAO,MAAM,MAAM,CACpB;CACD,MAAM,YACL,MAAM,aAAa,MAAM,WAAW,aAAa,MAAM,WAAW;CAEnE,MAAM,gBACL,MACE,QAAQ,EAAE,OAAO,MAAM,CAAC,CACxB,MAAM,WAAW,UAAU,OAAO,aAAa,UAAU,CAAC,QAAQ,CAClE,YAAY,OAAO,aAAa,UAAU,CAAC,QAAQ,CACnD,MAAM,YAAY,WAAW,KAAK;AAErC,QAAO;EACN,SAAS,MAAM;EACf,QAAQ,MAAM;EACd;EACA;EACA;EACA"}
@@ -5,6 +5,12 @@ type QueryFn<TData, TArgs> = (client: CossistantClient, args?: TArgs | undefined
5
5
  type UseClientQueryOptions<TData, TArgs> = {
6
6
  client: CossistantClient;
7
7
  queryFn: QueryFn<TData, TArgs>;
8
+ /**
9
+ * Unique key to identify this query for deduplication.
10
+ * When provided, concurrent requests with the same key will share a single
11
+ * in-flight promise instead of making duplicate API calls.
12
+ */
13
+ queryKey?: string;
8
14
  enabled?: boolean;
9
15
  refetchInterval?: number | false;
10
16
  refetchOnWindowFocus?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"use-client-query.d.ts","names":[],"sources":["../../../src/hooks/private/use-client-query.ts"],"sourcesContent":[],"mappings":";;;KAGK,iCACI,yBACD,sBACH,QAAQ;KAER;EALA,MAAA,EAMI,gBANG;EACH,OAAA,EAMC,OAND,CAMS,KANT,EAMgB,KANhB,CAAA;EACD,OAAA,CAAA,EAAA,OAAA;EACK,eAAA,CAAA,EAAA,MAAA,GAAA,KAAA;EAAR,oBAAA,CAAA,EAAA,OAAA;EAAO,cAAA,CAAA,EAAA,OAAA;EAEP,WAAA,CAAA,EAOU,KAPV;EACI,WAAA,CAAA,EAOM,KAPN;EACS,YAAA,CAAA,EAAA,SAAA,OAAA,EAAA;CAAO;KAUpB,oBAVK,CAAA,KAAA,EAAA,KAAA,CAAA,GAAA;EAKK,IAAA,EAMR,KANQ,GAAA,SAAA;EACA,KAAA,EAMP,KANO,GAAA,IAAA;EAAK,SAAA,EAAA,OAAA;EAIf,OAAA,EAAA,CAAA,IAAA,CAAA,EAIa,KAJb,EAAoB,GAIG,OAJH,CAIW,KAJX,GAAA,SAAA,CAAA;CAClB;;;;;;AAqBS,iBAAA,cAAc,CAAA,KAAA,EAAA,QAAA,IAAA,CAAA,CAAA,OAAA,EACpB,qBADoB,CACE,KADF,EACS,KADT,CAAA,CAAA,EAE3B,oBAF2B,CAEN,KAFM,EAEC,KAFD,CAAA"}
1
+ {"version":3,"file":"use-client-query.d.ts","names":[],"sources":["../../../src/hooks/private/use-client-query.ts"],"sourcesContent":[],"mappings":";;;KAGK,iCACI,yBACD,sBACH,QAAQ;KAER;EALA,MAAA,EAMI,gBANG;EACH,OAAA,EAMC,OAND,CAMS,KANT,EAMgB,KANhB,CAAA;EACD;;;;AACI;EAGH,QAAA,CAAA,EAAA,MAAA;EACS,OAAA,CAAA,EAAA,OAAA;EAAO,eAAA,CAAA,EAAA,MAAA,GAAA,KAAA;EAAf,oBAAA,CAAA,EAAA,OAAA;EAWK,cAAA,CAAA,EAAA,OAAA;EACA,WAAA,CAAA,EADA,KACA;EAAK,WAAA,CAAA,EAAL,KAAK;EAIf,YAAA,CAAA,EAAA,SAAoB,OAAA,EAAA;CAClB;KADF,oBAEG,CAAA,KAAA,EAAA,KAAA,CAAA,GAAA;EAEU,IAAA,EAHX,KAGW,GAAA,SAAA;EAAkB,KAAA,EAF5B,KAE4B,GAAA,IAAA;EAAR,SAAA,EAAA,OAAA;EAAO,OAAA,EAAA,CAAA,IAAA,CAAA,EAAjB,KAAiB,EAAA,GAAP,OAAO,CAAC,KAAD,GAAA,SAAA,CAAA;AAqDnC,CAAA;;;;;;AAEG,iBAFa,cAEb,CAAA,KAAA,EAAA,QAAA,IAAA,CAAA,CAAA,OAAA,EADO,qBACP,CAD6B,KAC7B,EADoC,KACpC,CAAA,CAAA,EAAA,oBAAA,CAAqB,KAArB,EAA4B,KAA5B,CAAA"}
@@ -7,12 +7,31 @@ function toError(error) {
7
7
  }
8
8
  const EMPTY_DEPENDENCIES = [];
9
9
  /**
10
+ * Module-level cache for in-flight requests.
11
+ * Maps query keys to their pending promises for deduplication.
12
+ */
13
+ const inFlightRequests = /* @__PURE__ */ new Map();
14
+ /**
15
+ * Execute a query with deduplication support.
16
+ * If a query with the same key is already in flight, returns the existing promise.
17
+ */
18
+ function executeWithDeduplication(queryKey, queryFn) {
19
+ if (!queryKey) return queryFn();
20
+ const existing = inFlightRequests.get(queryKey);
21
+ if (existing) return existing;
22
+ const promise = queryFn().finally(() => {
23
+ inFlightRequests.delete(queryKey);
24
+ });
25
+ inFlightRequests.set(queryKey, promise);
26
+ return promise;
27
+ }
28
+ /**
10
29
  * Lightweight data-fetching abstraction that plugs into the SDK client instead
11
30
  * of React Query. It tracks loading/error state, supports polling, window
12
31
  * focus refetching and exposes a typed refetch helper.
13
32
  */
14
33
  function useClientQuery(options) {
15
- const { client, queryFn, enabled = true, refetchInterval = false, refetchOnWindowFocus = true, refetchOnMount = true, initialData, initialArgs, dependencies = EMPTY_DEPENDENCIES } = options;
34
+ const { client, queryFn, queryKey, enabled = true, refetchInterval = false, refetchOnWindowFocus = false, refetchOnMount = true, initialData, initialArgs, dependencies = EMPTY_DEPENDENCIES } = options;
16
35
  const [data, setData] = useState(initialData);
17
36
  const [error, setError] = useState(null);
18
37
  const [isLoading, setIsLoading] = useState(initialData === void 0 && Boolean(enabled));
@@ -40,7 +59,7 @@ function useClientQuery(options) {
40
59
  setIsLoading(true);
41
60
  setError(null);
42
61
  try {
43
- const result = await queryFnRef.current(client, nextArgs);
62
+ const result = await executeWithDeduplication(queryKey, () => queryFnRef.current(client, nextArgs));
44
63
  if (!isMountedRef.current || fetchId !== fetchIdRef.current) return dataRef.current;
45
64
  dataRef.current = result;
46
65
  setData(result);
@@ -55,7 +74,11 @@ function useClientQuery(options) {
55
74
  setIsLoading(false);
56
75
  throw normalized;
57
76
  }
58
- }, [client, enabled]);
77
+ }, [
78
+ client,
79
+ enabled,
80
+ queryKey
81
+ ]);
59
82
  useEffect(() => {
60
83
  if (!enabled) {
61
84
  setIsLoading(false);
@@ -1 +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\n/**\n * Lightweight data-fetching abstraction that plugs into the SDK client instead\n * of React Query. It tracks loading/error state, supports polling, window\n * focus refetching and exposes a typed refetch helper.\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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"}
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\t/**\n\t * Unique key to identify this query for deduplication.\n\t * When provided, concurrent requests with the same key will share a single\n\t * in-flight promise instead of making duplicate API calls.\n\t */\n\tqueryKey?: string;\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\n/**\n * Module-level cache for in-flight requests.\n * Maps query keys to their pending promises for deduplication.\n */\nconst inFlightRequests = new Map<string, Promise<unknown>>();\n\n/**\n * Execute a query with deduplication support.\n * If a query with the same key is already in flight, returns the existing promise.\n */\nfunction executeWithDeduplication<TData>(\n\tqueryKey: string | undefined,\n\tqueryFn: () => Promise<TData>\n): Promise<TData> {\n\t// No deduplication if no key provided\n\tif (!queryKey) {\n\t\treturn queryFn();\n\t}\n\n\t// Check for existing in-flight request\n\tconst existing = inFlightRequests.get(queryKey);\n\tif (existing) {\n\t\treturn existing as Promise<TData>;\n\t}\n\n\t// Create new request and track it\n\tconst promise = queryFn().finally(() => {\n\t\t// Clean up after request completes (success or error)\n\t\tinFlightRequests.delete(queryKey);\n\t});\n\n\tinFlightRequests.set(queryKey, promise);\n\treturn promise;\n}\n\n/**\n * Lightweight data-fetching abstraction that plugs into the SDK client instead\n * of React Query. It tracks loading/error state, supports polling, window\n * focus refetching and exposes a typed refetch helper.\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\tqueryKey,\n\t\tenabled = true,\n\t\trefetchInterval = false,\n\t\trefetchOnWindowFocus = false,\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\t// Use deduplication to share in-flight requests with the same key\n\t\t\t\tconst result = await executeWithDeduplication(queryKey, () =>\n\t\t\t\t\tqueryFnRef.current(client, nextArgs)\n\t\t\t\t);\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, queryKey]\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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"}
@@ -1 +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;IACiC,OAAA,EAAA,MAAA;IAAoB,KAAA,EAApB,IAAoB,EAAA;EAC9C,CAAA,EAAA,GAAA,IAAA,GAD8C,OAC9C,CAAA,IAAA,CAAA;EAAK,OAAA,CAAA,EAAA,CAAA,KAAA,EAAL,KAAK,EAAA,GAAA,IAAA;EAMZ,WAAA,CAAA,EAAA,MAAA;EAGJ,QAAA,CAAA,EAAA,MAAA;EAEA,gBAAA,CAAA,EAAA,MAAA,EAAA;CAIW;AAGJ,KAZH,wBAAA,GAYG;EAAO,OAAA,EAAA,MAAA;EAaT,KAAA,EAtBL,IAsBK,EAAA;EAAsB,YAAA,EAAA,OAAA;EAAA,KAAA,EApB3B,KAoB2B,GAAA,IAAA;EAAA,UAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EAAA,QAAA,EAAA,CAAA,KAAA,EAhBhB,IAgBgB,EAAA,EAAA,GAAA,IAAA;EAAA,UAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EAMhC,UAAA,EAAA,GAAA,GAAA,IAAA;EAAiC,MAAA,EAAA,GAAA,GAnBrB,OAmBqB,CAAA,IAAA,CAAA;EAwKnC,KAAA,EAAA,GAAA,GAAA,IAAA;;;;;;;;;cA9KY;;;;;;IAMV,8BAAiC"}
1
+ {"version":3,"file":"use-multimodal-input.d.ts","names":[],"sources":["../../../src/hooks/private/use-multimodal-input.ts"],"sourcesContent":[],"mappings":";KAYY,yBAAA;EAAA,QAAA,CAAA,EAAA,CAAA,IAAA,EAAA;IACiC,OAAA,EAAA,MAAA;IAAoB,KAAA,EAApB,IAAoB,EAAA;EAC9C,CAAA,EAAA,GAAA,IAAA,GAD8C,OAC9C,CAAA,IAAA,CAAA;EAAK,OAAA,CAAA,EAAA,CAAA,KAAA,EAAL,KAAK,EAAA,GAAA,IAAA;EAMZ,WAAA,CAAA,EAAA,MAAA;EAGJ,QAAA,CAAA,EAAA,MAAA;EAEA,gBAAA,CAAA,EAAA,MAAA,EAAA;CAIW;AAGJ,KAZH,wBAAA,GAYG;EAAO,OAAA,EAAA,MAAA;EAaT,KAAA,EAtBL,IAsBK,EAAA;EAAsB,YAAA,EAAA,OAAA;EAAA,KAAA,EApB3B,KAoB2B,GAAA,IAAA;EAAA,UAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EAAA,QAAA,EAAA,CAAA,KAAA,EAhBhB,IAgBgB,EAAA,EAAA,GAAA,IAAA;EAAA,UAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EAMhC,UAAA,EAAA,GAAA,GAAA,IAAA;EAAiC,MAAA,EAAA,GAAA,GAnBrB,OAmBqB,CAAA,IAAA,CAAA;EAwKnC,KAAA,EAAA,GAAA,GAAA,IAAA;;;;;;;;;cA9KY;;;;;;IAMV,8BAAiC"}
@@ -1,16 +1,18 @@
1
1
  import { useCallback, useRef, useState } from "react";
2
+ import { ALLOWED_MIME_TYPES } from "@cossistant/core";
2
3
 
3
4
  //#region src/hooks/private/use-multimodal-input.ts
5
+ const DEFAULT_ALLOWED_FILE_TYPES = [
6
+ ...ALLOWED_MIME_TYPES.filter((type) => !(type.startsWith("image/") || type.startsWith("text/"))),
7
+ "image/*",
8
+ "text/*"
9
+ ];
4
10
  /**
5
11
  * Manages message text, file attachments and validation for the multimodal
6
12
  * composer component. Provides ergonomic helpers for submit flows and error
7
13
  * reporting.
8
14
  */
9
- const useMultimodalInput = ({ onSubmit, onError, maxFileSize = 10 * 1024 * 1024, maxFiles = 5, allowedFileTypes = [
10
- "image/*",
11
- "application/pdf",
12
- "text/*"
13
- ] } = {}) => {
15
+ const useMultimodalInput = ({ onSubmit, onError, maxFileSize = 10 * 1024 * 1024, maxFiles = 5, allowedFileTypes = DEFAULT_ALLOWED_FILE_TYPES } = {}) => {
14
16
  const [message, setMessage] = useState("");
15
17
  const [files, setFiles] = useState([]);
16
18
  const [isSubmitting, setIsSubmitting] = useState(false);
@@ -1 +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\tconst trimmedMessage = message.trim();\n\t\tif (!trimmedMessage && 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\tconst previousState = {\n\t\t\tmessage,\n\t\t\tfiles,\n\t\t\tfileUrls: [...fileUrlsRef.current],\n\t\t};\n\n\t\tsetIsSubmitting(true);\n\t\tsetError(null);\n\t\tsetMessage(\"\");\n\t\tsetFiles([]);\n\t\tfileUrlsRef.current = [];\n\n\t\ttry {\n\t\t\tawait onSubmit({ message: trimmedMessage, files: previousState.files });\n\n\t\t\tfor (const url of previousState.fileUrls) {\n\t\t\t\tURL.revokeObjectURL(url);\n\t\t\t}\n\t\t\treset();\n\t\t} catch (err) {\n\t\t\tsetMessage(previousState.message);\n\t\t\tsetFiles(previousState.files);\n\t\t\tfileUrlsRef.current = previousState.fileUrls;\n\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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"}
1
+ {"version":3,"file":"use-multimodal-input.js","names":[],"sources":["../../../src/hooks/private/use-multimodal-input.ts"],"sourcesContent":["import { ALLOWED_MIME_TYPES } from \"@cossistant/core\";\nimport { useCallback, useRef, useState } from \"react\";\n\n// Convert ALLOWED_MIME_TYPES to validation-friendly format with wildcards for image/* and text/*\nconst DEFAULT_ALLOWED_FILE_TYPES = [\n\t...ALLOWED_MIME_TYPES.filter(\n\t\t(type) => !(type.startsWith(\"image/\") || type.startsWith(\"text/\"))\n\t),\n\t\"image/*\",\n\t\"text/*\",\n];\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 = DEFAULT_ALLOWED_FILE_TYPES,\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\tconst trimmedMessage = message.trim();\n\t\tif (!trimmedMessage && 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\tconst previousState = {\n\t\t\tmessage,\n\t\t\tfiles,\n\t\t\tfileUrls: [...fileUrlsRef.current],\n\t\t};\n\n\t\tsetIsSubmitting(true);\n\t\tsetError(null);\n\t\tsetMessage(\"\");\n\t\tsetFiles([]);\n\t\tfileUrlsRef.current = [];\n\n\t\ttry {\n\t\t\tawait onSubmit({ message: trimmedMessage, files: previousState.files });\n\n\t\t\tfor (const url of previousState.fileUrls) {\n\t\t\t\tURL.revokeObjectURL(url);\n\t\t\t}\n\t\t\treset();\n\t\t} catch (err) {\n\t\t\tsetMessage(previousState.message);\n\t\t\tsetFiles(previousState.files);\n\t\t\tfileUrlsRef.current = previousState.fileUrls;\n\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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"}
@@ -1,9 +1,21 @@
1
+ import { AnyRealtimeEvent } from "../../realtime-events.js";
1
2
  import { CossistantClient } from "@cossistant/core";
2
3
 
3
4
  //#region src/hooks/private/use-visitor-typing-reporter.d.ts
5
+ type RealtimeSendFn = (event: AnyRealtimeEvent) => void;
4
6
  type UseVisitorTypingReporterOptions = {
5
7
  client: CossistantClient | null;
6
8
  conversationId: string | null;
9
+ /**
10
+ * Optional WebSocket send function. When provided and the connection is available,
11
+ * typing events will be sent via WebSocket instead of HTTP for better performance.
12
+ */
13
+ realtimeSend?: RealtimeSendFn | null;
14
+ /**
15
+ * Whether the WebSocket connection is currently established.
16
+ * Required when using realtimeSend to determine when to use HTTP fallback.
17
+ */
18
+ isRealtimeConnected?: boolean;
7
19
  };
8
20
  type UseVisitorTypingReporterResult = {
9
21
  handleInputChange: (value: string) => void;
@@ -15,10 +27,15 @@ type UseVisitorTypingReporterResult = {
15
27
  *
16
28
  * Handles throttling, keep-alive pings, inactivity fallbacks and ensures a
17
29
  * `stop` event is emitted when the component unmounts.
30
+ *
31
+ * When `realtimeSend` is provided and connected, typing events are sent via WebSocket
32
+ * for reduced latency. Falls back to HTTP when WebSocket is unavailable.
18
33
  */
19
34
  declare function useVisitorTypingReporter({
20
35
  client,
21
- conversationId
36
+ conversationId,
37
+ realtimeSend,
38
+ isRealtimeConnected
22
39
  }: UseVisitorTypingReporterOptions): UseVisitorTypingReporterResult;
23
40
  //#endregion
24
41
  export { useVisitorTypingReporter };
@@ -1 +1 @@
1
- {"version":3,"file":"use-visitor-typing-reporter.d.ts","names":[],"sources":["../../../src/hooks/private/use-visitor-typing-reporter.ts"],"sourcesContent":[],"mappings":";;;KAQK,+BAAA;UACI;EADJ,cAAA,EAAA,MAAA,GAAA,IAAA;AACoB,CAAA;AAgBzB,KAZK,8BAAA,GAYmC;EACvC,iBAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EACA,YAAA,EAAA,GAAA,GAAA,IAAA;EACE,IAAA,EAAA,GAAA,GAAA,IAAA;CAAkC;;;;;;;iBAHrB,wBAAA;;;GAGb,kCAAkC"}
1
+ {"version":3,"file":"use-visitor-typing-reporter.d.ts","names":[],"sources":["../../../src/hooks/private/use-visitor-typing-reporter.ts"],"sourcesContent":[],"mappings":";;;;KASK,cAAA,WAAyB;KAEzB,+BAAA;EAFA,MAAA,EAGI,gBAHU,GAAA,IAAW;EAEzB,cAAA,EAAA,MAAA,GAAA,IAAA;EAeA;AAeL;;;EAGC,YAAA,CAAA,EA1Be,cA0Bf,GAAA,IAAA;EACA;;;;;;KAnBI,8BAAA;;;;;;;;;;;;;;iBAeW,wBAAA;;;;;GAKb,kCAAkC"}
@@ -10,8 +10,11 @@ const STOP_TYPING_DELAY_MS = 2e3;
10
10
  *
11
11
  * Handles throttling, keep-alive pings, inactivity fallbacks and ensures a
12
12
  * `stop` event is emitted when the component unmounts.
13
+ *
14
+ * When `realtimeSend` is provided and connected, typing events are sent via WebSocket
15
+ * for reduced latency. Falls back to HTTP when WebSocket is unavailable.
13
16
  */
14
- function useVisitorTypingReporter({ client, conversationId }) {
17
+ function useVisitorTypingReporter({ client, conversationId, realtimeSend, isRealtimeConnected = false }) {
15
18
  const typingActiveRef = useRef(false);
16
19
  const lastSentAtRef = useRef(0);
17
20
  const latestPreviewRef = useRef("");
@@ -30,7 +33,26 @@ function useVisitorTypingReporter({ client, conversationId }) {
30
33
  }
31
34
  }, []);
32
35
  const sendTyping = useCallback(async (isTyping, preview) => {
33
- if (!(client && conversationId)) return;
36
+ if (!conversationId) return;
37
+ if (realtimeSend && isRealtimeConnected) try {
38
+ realtimeSend({
39
+ type: "conversationTyping",
40
+ payload: {
41
+ conversationId,
42
+ isTyping,
43
+ visitorPreview: isTyping && preview ? preview.slice(0, PREVIEW_MAX_LENGTH) : null,
44
+ websiteId: "",
45
+ organizationId: "",
46
+ visitorId: null,
47
+ userId: null,
48
+ aiAgentId: null
49
+ }
50
+ });
51
+ return;
52
+ } catch (error) {
53
+ console.warn("[Support] WebSocket typing send failed, falling back to HTTP", error);
54
+ }
55
+ if (!client) return;
34
56
  try {
35
57
  await client.setVisitorTyping({
36
58
  conversationId,
@@ -40,7 +62,12 @@ function useVisitorTypingReporter({ client, conversationId }) {
40
62
  } catch (error) {
41
63
  console.error("[Support] Failed to send typing event", error);
42
64
  }
43
- }, [client, conversationId]);
65
+ }, [
66
+ client,
67
+ conversationId,
68
+ realtimeSend,
69
+ isRealtimeConnected
70
+ ]);
44
71
  const scheduleKeepAlive = useCallback(() => {
45
72
  clearKeepAlive();
46
73
  keepAliveTimeoutRef.current = setTimeout(() => {
@@ -65,7 +92,8 @@ function useVisitorTypingReporter({ client, conversationId }) {
65
92
  sendTyping
66
93
  ]);
67
94
  const handleInputChange = useCallback((value) => {
68
- if (!(client && conversationId)) return;
95
+ if (!conversationId) return;
96
+ if (!(realtimeSend && isRealtimeConnected || Boolean(client))) return;
69
97
  const trimmed = value.trim();
70
98
  latestPreviewRef.current = trimmed.slice(0, PREVIEW_MAX_LENGTH);
71
99
  const now = typeof window !== "undefined" ? Date.now() : 0;
@@ -95,6 +123,8 @@ function useVisitorTypingReporter({ client, conversationId }) {
95
123
  }, [
96
124
  client,
97
125
  conversationId,
126
+ realtimeSend,
127
+ isRealtimeConnected,
98
128
  sendTyping,
99
129
  scheduleKeepAlive,
100
130
  scheduleStopTyping,
@@ -1 +1 @@
1
- {"version":3,"file":"use-visitor-typing-reporter.js","names":[],"sources":["../../../src/hooks/private/use-visitor-typing-reporter.ts"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport { useCallback, useEffect, useRef } from \"react\";\n\nconst PREVIEW_MAX_LENGTH = 2000;\nconst SEND_INTERVAL_MS = 800;\nconst KEEP_ALIVE_MS = 4000;\nconst STOP_TYPING_DELAY_MS = 2000; // Send isTyping: false after 2 seconds of inactivity\n\ntype UseVisitorTypingReporterOptions = {\n\tclient: CossistantClient | null;\n\tconversationId: string | null;\n};\n\ntype UseVisitorTypingReporterResult = {\n\thandleInputChange: (value: string) => void;\n\thandleSubmit: () => void;\n\tstop: () => void;\n};\n\n/**\n * Tracks visitor composer activity and reports typing previews to the backend.\n *\n * Handles throttling, keep-alive pings, inactivity fallbacks and ensures a\n * `stop` event is emitted when the component unmounts.\n */\nexport function useVisitorTypingReporter({\n\tclient,\n\tconversationId,\n}: UseVisitorTypingReporterOptions): UseVisitorTypingReporterResult {\n\tconst typingActiveRef = useRef(false);\n\tconst lastSentAtRef = useRef(0);\n\tconst latestPreviewRef = useRef<string>(\"\");\n\tconst keepAliveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(\n\t\tnull\n\t);\n\tconst stopTypingTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(\n\t\tnull\n\t);\n\n\tconst clearKeepAlive = useCallback(() => {\n\t\tif (keepAliveTimeoutRef.current) {\n\t\t\tclearTimeout(keepAliveTimeoutRef.current);\n\t\t\tkeepAliveTimeoutRef.current = null;\n\t\t}\n\t}, []);\n\n\tconst clearStopTypingTimeout = useCallback(() => {\n\t\tif (stopTypingTimeoutRef.current) {\n\t\t\tclearTimeout(stopTypingTimeoutRef.current);\n\t\t\tstopTypingTimeoutRef.current = null;\n\t\t}\n\t}, []);\n\n\tconst sendTyping = useCallback(\n\t\tasync (isTyping: boolean, preview?: string | null) => {\n\t\t\tif (!(client && conversationId)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tawait client.setVisitorTyping({\n\t\t\t\t\tconversationId,\n\t\t\t\t\tisTyping,\n\t\t\t\t\tvisitorPreview: preview ?? undefined,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"[Support] Failed to send typing event\", error);\n\t\t\t}\n\t\t},\n\t\t[client, conversationId]\n\t);\n\n\tconst scheduleKeepAlive = useCallback(() => {\n\t\tclearKeepAlive();\n\t\tkeepAliveTimeoutRef.current = setTimeout(() => {\n\t\t\tif (typingActiveRef.current) {\n\t\t\t\tvoid sendTyping(true, latestPreviewRef.current);\n\t\t\t\tscheduleKeepAlive();\n\t\t\t}\n\t\t}, KEEP_ALIVE_MS);\n\t}, [clearKeepAlive, sendTyping]);\n\n\tconst scheduleStopTyping = useCallback(() => {\n\t\tclearStopTypingTimeout();\n\t\tstopTypingTimeoutRef.current = setTimeout(() => {\n\t\t\tif (typingActiveRef.current) {\n\t\t\t\ttypingActiveRef.current = false;\n\t\t\t\tclearKeepAlive();\n\t\t\t\tvoid sendTyping(false);\n\t\t\t}\n\t\t}, STOP_TYPING_DELAY_MS);\n\t}, [clearStopTypingTimeout, clearKeepAlive, sendTyping]);\n\n\tconst handleInputChange = useCallback(\n\t\t(value: string) => {\n\t\t\tif (!(client && conversationId)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst trimmed = value.trim();\n\t\t\tlatestPreviewRef.current = trimmed.slice(0, PREVIEW_MAX_LENGTH);\n\t\t\tconst now = typeof window !== \"undefined\" ? Date.now() : 0;\n\n\t\t\tif (trimmed.length === 0) {\n\t\t\t\tif (typingActiveRef.current) {\n\t\t\t\t\ttypingActiveRef.current = false;\n\t\t\t\t\tlastSentAtRef.current = now;\n\t\t\t\t\tclearKeepAlive();\n\t\t\t\t\tclearStopTypingTimeout();\n\t\t\t\t\tvoid sendTyping(false);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Schedule auto-stop after inactivity\n\t\t\tscheduleStopTyping();\n\n\t\t\tif (!typingActiveRef.current) {\n\t\t\t\ttypingActiveRef.current = true;\n\t\t\t\tlastSentAtRef.current = now;\n\t\t\t\tvoid sendTyping(true, latestPreviewRef.current);\n\t\t\t\tscheduleKeepAlive();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (now - lastSentAtRef.current >= SEND_INTERVAL_MS) {\n\t\t\t\tlastSentAtRef.current = now;\n\t\t\t\tvoid sendTyping(true, latestPreviewRef.current);\n\t\t\t\tscheduleKeepAlive();\n\t\t\t}\n\t\t},\n\t\t[\n\t\t\tclient,\n\t\t\tconversationId,\n\t\t\tsendTyping,\n\t\t\tscheduleKeepAlive,\n\t\t\tscheduleStopTyping,\n\t\t\tclearKeepAlive,\n\t\t\tclearStopTypingTimeout,\n\t\t]\n\t);\n\n\tconst handleSubmit = useCallback(() => {\n\t\tif (!typingActiveRef.current) {\n\t\t\treturn;\n\t\t}\n\n\t\ttypingActiveRef.current = false;\n\t\tlastSentAtRef.current = typeof window !== \"undefined\" ? Date.now() : 0;\n\t\tclearKeepAlive();\n\t\tclearStopTypingTimeout();\n\t\tvoid sendTyping(false);\n\t}, [clearKeepAlive, clearStopTypingTimeout, sendTyping]);\n\n\tconst stop = useCallback(() => {\n\t\tif (!typingActiveRef.current) {\n\t\t\treturn;\n\t\t}\n\n\t\ttypingActiveRef.current = false;\n\t\tlastSentAtRef.current = typeof window !== \"undefined\" ? Date.now() : 0;\n\t\tclearKeepAlive();\n\t\tclearStopTypingTimeout();\n\t\tvoid sendTyping(false);\n\t}, [clearKeepAlive, clearStopTypingTimeout, sendTyping]);\n\n\tuseEffect(\n\t\t() => () => {\n\t\t\tif (typingActiveRef.current) {\n\t\t\t\tvoid sendTyping(false);\n\t\t\t}\n\t\t\tclearKeepAlive();\n\t\t\tclearStopTypingTimeout();\n\t\t},\n\t\t[clearKeepAlive, clearStopTypingTimeout, sendTyping]\n\t);\n\n\treturn {\n\t\thandleInputChange,\n\t\thandleSubmit,\n\t\tstop,\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-visitor-typing-reporter.js","names":[],"sources":["../../../src/hooks/private/use-visitor-typing-reporter.ts"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport type { AnyRealtimeEvent } from \"@cossistant/types/realtime-events\";\nimport { useCallback, useEffect, useRef } from \"react\";\n\nconst PREVIEW_MAX_LENGTH = 2000;\nconst SEND_INTERVAL_MS = 800;\nconst KEEP_ALIVE_MS = 4000;\nconst STOP_TYPING_DELAY_MS = 2000; // Send isTyping: false after 2 seconds of inactivity\n\ntype RealtimeSendFn = (event: AnyRealtimeEvent) => void;\n\ntype UseVisitorTypingReporterOptions = {\n\tclient: CossistantClient | null;\n\tconversationId: string | null;\n\t/**\n\t * Optional WebSocket send function. When provided and the connection is available,\n\t * typing events will be sent via WebSocket instead of HTTP for better performance.\n\t */\n\trealtimeSend?: RealtimeSendFn | null;\n\t/**\n\t * Whether the WebSocket connection is currently established.\n\t * Required when using realtimeSend to determine when to use HTTP fallback.\n\t */\n\tisRealtimeConnected?: boolean;\n};\n\ntype UseVisitorTypingReporterResult = {\n\thandleInputChange: (value: string) => void;\n\thandleSubmit: () => void;\n\tstop: () => void;\n};\n\n/**\n * Tracks visitor composer activity and reports typing previews to the backend.\n *\n * Handles throttling, keep-alive pings, inactivity fallbacks and ensures a\n * `stop` event is emitted when the component unmounts.\n *\n * When `realtimeSend` is provided and connected, typing events are sent via WebSocket\n * for reduced latency. Falls back to HTTP when WebSocket is unavailable.\n */\nexport function useVisitorTypingReporter({\n\tclient,\n\tconversationId,\n\trealtimeSend,\n\tisRealtimeConnected = false,\n}: UseVisitorTypingReporterOptions): UseVisitorTypingReporterResult {\n\tconst typingActiveRef = useRef(false);\n\tconst lastSentAtRef = useRef(0);\n\tconst latestPreviewRef = useRef<string>(\"\");\n\tconst keepAliveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(\n\t\tnull\n\t);\n\tconst stopTypingTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(\n\t\tnull\n\t);\n\n\tconst clearKeepAlive = useCallback(() => {\n\t\tif (keepAliveTimeoutRef.current) {\n\t\t\tclearTimeout(keepAliveTimeoutRef.current);\n\t\t\tkeepAliveTimeoutRef.current = null;\n\t\t}\n\t}, []);\n\n\tconst clearStopTypingTimeout = useCallback(() => {\n\t\tif (stopTypingTimeoutRef.current) {\n\t\t\tclearTimeout(stopTypingTimeoutRef.current);\n\t\t\tstopTypingTimeoutRef.current = null;\n\t\t}\n\t}, []);\n\n\tconst sendTyping = useCallback(\n\t\tasync (isTyping: boolean, preview?: string | null) => {\n\t\t\tif (!conversationId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Try WebSocket first if available and connected\n\t\t\tif (realtimeSend && isRealtimeConnected) {\n\t\t\t\ttry {\n\t\t\t\t\tconst event: AnyRealtimeEvent = {\n\t\t\t\t\t\ttype: \"conversationTyping\",\n\t\t\t\t\t\tpayload: {\n\t\t\t\t\t\t\tconversationId,\n\t\t\t\t\t\t\tisTyping,\n\t\t\t\t\t\t\tvisitorPreview:\n\t\t\t\t\t\t\t\tisTyping && preview\n\t\t\t\t\t\t\t\t\t? preview.slice(0, PREVIEW_MAX_LENGTH)\n\t\t\t\t\t\t\t\t\t: null,\n\t\t\t\t\t\t\t// These will be enriched by the server with the actual values\n\t\t\t\t\t\t\twebsiteId: \"\",\n\t\t\t\t\t\t\torganizationId: \"\",\n\t\t\t\t\t\t\tvisitorId: null,\n\t\t\t\t\t\t\tuserId: null,\n\t\t\t\t\t\t\taiAgentId: null,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t\trealtimeSend(event);\n\t\t\t\t\treturn;\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// WebSocket send failed, fall through to HTTP\n\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\"[Support] WebSocket typing send failed, falling back to HTTP\",\n\t\t\t\t\t\terror\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Fall back to HTTP via client\n\t\t\tif (!client) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tawait client.setVisitorTyping({\n\t\t\t\t\tconversationId,\n\t\t\t\t\tisTyping,\n\t\t\t\t\tvisitorPreview: preview ?? undefined,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"[Support] Failed to send typing event\", error);\n\t\t\t}\n\t\t},\n\t\t[client, conversationId, realtimeSend, isRealtimeConnected]\n\t);\n\n\tconst scheduleKeepAlive = useCallback(() => {\n\t\tclearKeepAlive();\n\t\tkeepAliveTimeoutRef.current = setTimeout(() => {\n\t\t\tif (typingActiveRef.current) {\n\t\t\t\tvoid sendTyping(true, latestPreviewRef.current);\n\t\t\t\tscheduleKeepAlive();\n\t\t\t}\n\t\t}, KEEP_ALIVE_MS);\n\t}, [clearKeepAlive, sendTyping]);\n\n\tconst scheduleStopTyping = useCallback(() => {\n\t\tclearStopTypingTimeout();\n\t\tstopTypingTimeoutRef.current = setTimeout(() => {\n\t\t\tif (typingActiveRef.current) {\n\t\t\t\ttypingActiveRef.current = false;\n\t\t\t\tclearKeepAlive();\n\t\t\t\tvoid sendTyping(false);\n\t\t\t}\n\t\t}, STOP_TYPING_DELAY_MS);\n\t}, [clearStopTypingTimeout, clearKeepAlive, sendTyping]);\n\n\tconst handleInputChange = useCallback(\n\t\t(value: string) => {\n\t\t\tif (!conversationId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Need either WebSocket or HTTP client to be available\n\t\t\tconst hasWebSocket = realtimeSend && isRealtimeConnected;\n\t\t\tconst hasHttpClient = Boolean(client);\n\t\t\tif (!(hasWebSocket || hasHttpClient)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst trimmed = value.trim();\n\t\t\tlatestPreviewRef.current = trimmed.slice(0, PREVIEW_MAX_LENGTH);\n\t\t\tconst now = typeof window !== \"undefined\" ? Date.now() : 0;\n\n\t\t\tif (trimmed.length === 0) {\n\t\t\t\tif (typingActiveRef.current) {\n\t\t\t\t\ttypingActiveRef.current = false;\n\t\t\t\t\tlastSentAtRef.current = now;\n\t\t\t\t\tclearKeepAlive();\n\t\t\t\t\tclearStopTypingTimeout();\n\t\t\t\t\tvoid sendTyping(false);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Schedule auto-stop after inactivity\n\t\t\tscheduleStopTyping();\n\n\t\t\tif (!typingActiveRef.current) {\n\t\t\t\ttypingActiveRef.current = true;\n\t\t\t\tlastSentAtRef.current = now;\n\t\t\t\tvoid sendTyping(true, latestPreviewRef.current);\n\t\t\t\tscheduleKeepAlive();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (now - lastSentAtRef.current >= SEND_INTERVAL_MS) {\n\t\t\t\tlastSentAtRef.current = now;\n\t\t\t\tvoid sendTyping(true, latestPreviewRef.current);\n\t\t\t\tscheduleKeepAlive();\n\t\t\t}\n\t\t},\n\t\t[\n\t\t\tclient,\n\t\t\tconversationId,\n\t\t\trealtimeSend,\n\t\t\tisRealtimeConnected,\n\t\t\tsendTyping,\n\t\t\tscheduleKeepAlive,\n\t\t\tscheduleStopTyping,\n\t\t\tclearKeepAlive,\n\t\t\tclearStopTypingTimeout,\n\t\t]\n\t);\n\n\tconst handleSubmit = useCallback(() => {\n\t\tif (!typingActiveRef.current) {\n\t\t\treturn;\n\t\t}\n\n\t\ttypingActiveRef.current = false;\n\t\tlastSentAtRef.current = typeof window !== \"undefined\" ? Date.now() : 0;\n\t\tclearKeepAlive();\n\t\tclearStopTypingTimeout();\n\t\tvoid sendTyping(false);\n\t}, [clearKeepAlive, clearStopTypingTimeout, sendTyping]);\n\n\tconst stop = useCallback(() => {\n\t\tif (!typingActiveRef.current) {\n\t\t\treturn;\n\t\t}\n\n\t\ttypingActiveRef.current = false;\n\t\tlastSentAtRef.current = typeof window !== \"undefined\" ? Date.now() : 0;\n\t\tclearKeepAlive();\n\t\tclearStopTypingTimeout();\n\t\tvoid sendTyping(false);\n\t}, [clearKeepAlive, clearStopTypingTimeout, sendTyping]);\n\n\tuseEffect(\n\t\t() => () => {\n\t\t\tif (typingActiveRef.current) {\n\t\t\t\tvoid sendTyping(false);\n\t\t\t}\n\t\t\tclearKeepAlive();\n\t\t\tclearStopTypingTimeout();\n\t\t},\n\t\t[clearKeepAlive, clearStopTypingTimeout, sendTyping]\n\t);\n\n\treturn {\n\t\thandleInputChange,\n\t\thandleSubmit,\n\t\tstop,\n\t};\n}\n"],"mappings":"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"}
@@ -37,6 +37,7 @@ type UseConversationPageReturn = {
37
37
  message: string;
38
38
  files: File[];
39
39
  isSubmitting: boolean;
40
+ isUploading: boolean;
40
41
  canSubmit: boolean;
41
42
  setMessage: (message: string) => void;
42
43
  addFiles: (files: File[]) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"use-conversation-page.d.ts","names":[],"sources":["../../src/hooks/use-conversation-page.ts"],"sourcesContent":[],"mappings":";;;KAaY,0BAAA;;AAAZ;AA+BA;;EAMQ,cAAA,EAAA,MAAA;EAKC;;;EAWsB,cAAA,CAAA,EAAA,MAAA;EAuCf;;;;;;;;UAvEP;;;;;;;;KAUG,yBAAA;;;SAIJ;;SAEA;;;WAKC;;;;sBAIW;;;;;oBAOD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuCH,mBAAA,UACN,6BACP"}
1
+ {"version":3,"file":"use-conversation-page.d.ts","names":[],"sources":["../../src/hooks/use-conversation-page.ts"],"sourcesContent":[],"mappings":";;;KAcY,0BAAA;;AAAZ;AA+BA;;EAMQ,cAAA,EAAA,MAAA;EAKC;;;EAYsB,cAAA,CAAA,EAAA,MAAA;EAuCf;;;;;;;;UAxEP;;;;;;;;KAUG,yBAAA;;;SAIJ;;SAEA;;;WAKC;;;;;sBAKW;;;;;oBAOD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuCH,mBAAA,UACN,6BACP"}
@@ -1,3 +1,4 @@
1
+ import { useWebSocketSafe } from "../support/context/websocket.js";
1
2
  import { useConversationAutoSeen } from "./use-conversation-auto-seen.js";
2
3
  import { useConversationLifecycle } from "./use-conversation-lifecycle.js";
3
4
  import { useConversationTimelineItems } from "./use-conversation-timeline-items.js";
@@ -47,6 +48,7 @@ import { ConversationTimelineType, TimelineItemVisibility } from "@cossistant/ty
47
48
  function useConversationPage(options) {
48
49
  const { conversationId: initialConversationId, initialMessage, onConversationIdChange, items: passedItems = [], autoSeenEnabled = true } = options;
49
50
  const { client, visitor } = useSupport();
51
+ const websocket = useWebSocketSafe();
50
52
  const trimmedInitialMessage = initialMessage?.trim() ?? "";
51
53
  const hasInitialMessage = trimmedInitialMessage.length > 0;
52
54
  const lifecycle = useConversationLifecycle({
@@ -110,7 +112,9 @@ function useConversationPage(options) {
110
112
  visitorId: visitor?.id,
111
113
  onMessageSent: (newConversationId) => {
112
114
  if (lifecycle.isPending) lifecycle.setConversationId(newConversationId);
113
- }
115
+ },
116
+ realtimeSend: websocket?.send ?? null,
117
+ isRealtimeConnected: websocket?.isConnected ?? false
114
118
  });
115
119
  const initialMessageSubmittedRef = useRef(false);
116
120
  const lastInitialMessageRef = useRef(null);
@@ -160,6 +164,7 @@ function useConversationPage(options) {
160
164
  message: composer.message,
161
165
  files: composer.files,
162
166
  isSubmitting: composer.isSubmitting,
167
+ isUploading: composer.isUploading,
163
168
  canSubmit: composer.canSubmit,
164
169
  setMessage: composer.setMessage,
165
170
  addFiles: composer.addFiles,
@@ -1 +1 @@
1
- {"version":3,"file":"use-conversation-page.js","names":["identificationItem: TimelineItem"],"sources":["../../src/hooks/use-conversation-page.ts"],"sourcesContent":["import type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport {\n\tConversationTimelineType,\n\tTimelineItemVisibility,\n} from \"@cossistant/types/enums\";\nimport { useEffect, useMemo, useRef } from \"react\";\nimport { useSupport } from \"../provider\";\nimport { useDefaultMessages } from \"./private/use-default-messages\";\nimport { useConversationAutoSeen } from \"./use-conversation-auto-seen\";\nimport { useConversationLifecycle } from \"./use-conversation-lifecycle\";\nimport { useConversationTimelineItems } from \"./use-conversation-timeline-items\";\nimport { useMessageComposer } from \"./use-message-composer\";\n\nexport type UseConversationPageOptions = {\n\t/**\n\t * Initial conversation ID (from URL params, navigation state, etc.)\n\t * Can be PENDING_CONVERSATION_ID or a real ID.\n\t */\n\tconversationId: string;\n\n\t/**\n\t * Optional initial message to send when the conversation opens.\n\t */\n\tinitialMessage?: string;\n\n\t/**\n\t * Callback when conversation ID changes (e.g., after creation).\n\t * Use this to update navigation state or URL.\n\t */\n\tonConversationIdChange?: (conversationId: string) => void;\n\n\t/**\n\t * Optional timeline items to pass through (e.g., optimistic updates).\n\t */\n\titems?: TimelineItem[];\n\n\t/**\n\t * Whether automatic \"seen\" tracking should be enabled.\n\t * Set to false when the conversation isn't visible (e.g. widget closed).\n\t * Default: true\n\t */\n\tautoSeenEnabled?: boolean;\n};\n\nexport type UseConversationPageReturn = {\n\t// Conversation state\n\tconversationId: string;\n\tisPending: boolean;\n\titems: TimelineItem[];\n\tisLoading: boolean;\n\terror: Error | null;\n\n\t// Message composer\n\tcomposer: {\n\t\tmessage: string;\n\t\tfiles: File[];\n\t\tisSubmitting: boolean;\n\t\tcanSubmit: boolean;\n\t\tsetMessage: (message: string) => void;\n\t\taddFiles: (files: File[]) => void;\n\t\tremoveFile: (index: number) => void;\n\t\tsubmit: () => void;\n\t};\n\n\t// UI helpers\n\thasItems: boolean;\n\tlastTimelineItem: TimelineItem | null;\n};\n\n/**\n * Main orchestrator hook for the conversation page.\n *\n * This hook combines all conversation-related logic:\n * - Lifecycle management (pending → real conversation)\n * - Message fetching and display\n * - Message composition and sending\n * - Automatic seen tracking\n * - Default/welcome messages before conversation is created\n *\n * It provides a clean, simple API for building conversation UIs.\n *\n * @example\n * ```tsx\n * export function ConversationPage({ conversationId: initialId }) {\n * const conversation = useConversationPage({\n * conversationId: initialId,\n * onConversationIdChange: (newId) => {\n * // Update URL or navigation state\n * navigate(`/conversation/${newId}`);\n * },\n * });\n *\n * return (\n * <>\n * <MessageList messages={conversation.messages} />\n * <MessageInput\n * value={conversation.composer.message}\n * onChange={conversation.composer.setMessage}\n * onSubmit={conversation.composer.submit}\n * />\n * </>\n * );\n * }\n * ```\n */\nexport function useConversationPage(\n\toptions: UseConversationPageOptions\n): UseConversationPageReturn {\n\tconst {\n\t\tconversationId: initialConversationId,\n\t\tinitialMessage,\n\t\tonConversationIdChange,\n\t\titems: passedItems = [],\n\t\tautoSeenEnabled = true,\n\t} = options;\n\n\tconst { client, visitor } = useSupport();\n\n\tconst trimmedInitialMessage = initialMessage?.trim() ?? \"\";\n\tconst hasInitialMessage = trimmedInitialMessage.length > 0;\n\n\t// 1. Manage conversation lifecycle (pending vs real)\n\tconst lifecycle = useConversationLifecycle({\n\t\tinitialConversationId,\n\t\tonConversationCreated: onConversationIdChange,\n\t});\n\n\t// 2. Get default timeline items for pending state\n\tconst defaultTimelineItems = useDefaultMessages({\n\t\tconversationId: lifecycle.conversationId,\n\t});\n\n\tconst effectiveDefaultTimelineItems = hasInitialMessage\n\t\t? []\n\t\t: defaultTimelineItems;\n\n\t// 3. Fetch timeline items from backend if real conversation exists\n\tconst timelineQuery = useConversationTimelineItems(lifecycle.conversationId, {\n\t\tenabled: !lifecycle.isPending,\n\t});\n\n\t// 4. Determine which items to display\n\tconst baseItems = useMemo(() => {\n\t\t// If we have fetched timeline items, use them\n\t\tif (timelineQuery.items.length > 0) {\n\t\t\treturn timelineQuery.items;\n\t\t}\n\n\t\t// If pending, use default timeline items\n\t\tif (lifecycle.isPending && effectiveDefaultTimelineItems.length > 0) {\n\t\t\treturn effectiveDefaultTimelineItems;\n\t\t}\n\n\t\t// Use passed items as fallback\n\t\tif (passedItems.length > 0) {\n\t\t\treturn passedItems;\n\t\t}\n\n\t\treturn [];\n\t}, [\n\t\ttimelineQuery.items,\n\t\tlifecycle.isPending,\n\t\teffectiveDefaultTimelineItems,\n\t\tpassedItems,\n\t]);\n\n\tconst shouldShowIdentificationTool = useMemo(() => {\n\t\tif (lifecycle.isPending) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (visitor?.contact) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn !baseItems.some(\n\t\t\t(item) => item.type === ConversationTimelineType.IDENTIFICATION\n\t\t);\n\t}, [baseItems, lifecycle.isPending, visitor?.contact]);\n\n\tconst displayItems = useMemo(() => {\n\t\tif (!shouldShowIdentificationTool) {\n\t\t\treturn baseItems;\n\t\t}\n\n\t\tconst organizationId =\n\t\t\tbaseItems.at(-1)?.organizationId ??\n\t\t\tclient.getConfiguration().organizationId ??\n\t\t\t\"\";\n\n\t\tconst identificationItem: TimelineItem = {\n\t\t\tid: `identification-${lifecycle.conversationId}`,\n\t\t\tconversationId: lifecycle.conversationId,\n\t\t\torganizationId,\n\t\t\tvisibility: TimelineItemVisibility.PUBLIC,\n\t\t\ttype: ConversationTimelineType.IDENTIFICATION,\n\t\t\ttext: null,\n\t\t\ttool: \"identification\",\n\t\t\tparts: [],\n\t\t\tuserId: null,\n\t\t\tvisitorId: visitor?.id ?? null,\n\t\t\taiAgentId: null,\n\t\t\tcreatedAt: typeof window !== \"undefined\" ? new Date().toISOString() : \"\",\n\t\t\tdeletedAt: null,\n\t\t};\n\n\t\treturn [...baseItems, identificationItem];\n\t}, [\n\t\tbaseItems,\n\t\tclient,\n\t\tlifecycle.conversationId,\n\t\tshouldShowIdentificationTool,\n\t\tvisitor?.id,\n\t]);\n\n\tconst lastTimelineItem = useMemo(\n\t\t() => displayItems.at(-1) ?? null,\n\t\t[displayItems]\n\t);\n\n\t// 5. Set up message composer\n\tconst composer = useMessageComposer({\n\t\tclient,\n\t\tconversationId: lifecycle.realConversationId,\n\t\tdefaultTimelineItems: effectiveDefaultTimelineItems,\n\t\tvisitorId: visitor?.id,\n\t\tonMessageSent: (newConversationId) => {\n\t\t\t// Transition from pending to real conversation\n\t\t\tif (lifecycle.isPending) {\n\t\t\t\tlifecycle.setConversationId(newConversationId);\n\t\t\t}\n\t\t},\n\t});\n\n\tconst initialMessageSubmittedRef = useRef(false);\n\tconst lastInitialMessageRef = useRef<string | null>(null);\n\n\tuseEffect(() => {\n\t\tif (!hasInitialMessage) {\n\t\t\tinitialMessageSubmittedRef.current = false;\n\t\t\tlastInitialMessageRef.current = null;\n\t\t\treturn;\n\t\t}\n\n\t\tif (lastInitialMessageRef.current !== trimmedInitialMessage) {\n\t\t\tinitialMessageSubmittedRef.current = false;\n\t\t\tlastInitialMessageRef.current = trimmedInitialMessage;\n\t\t}\n\n\t\tif (!lifecycle.isPending) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (composer.message !== trimmedInitialMessage) {\n\t\t\tcomposer.setMessage(trimmedInitialMessage);\n\t\t\treturn;\n\t\t}\n\n\t\tif (\n\t\t\tinitialMessageSubmittedRef.current ||\n\t\t\tcomposer.isSubmitting ||\n\t\t\t!composer.canSubmit\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tinitialMessageSubmittedRef.current = true;\n\t\tcomposer.submit();\n\t}, [\n\t\thasInitialMessage,\n\t\tlifecycle.isPending,\n\t\tcomposer.message,\n\t\tcomposer.setMessage,\n\t\tcomposer.isSubmitting,\n\t\tcomposer.canSubmit,\n\t\tcomposer.submit,\n\t\ttrimmedInitialMessage,\n\t]);\n\n\t// 6. Auto-mark messages as seen\n\tuseConversationAutoSeen({\n\t\tclient,\n\t\tconversationId: lifecycle.realConversationId,\n\t\tvisitorId: visitor?.id,\n\t\tlastTimelineItem,\n\t\tenabled: autoSeenEnabled,\n\t\tisWidgetOpen: autoSeenEnabled,\n\t});\n\n\treturn {\n\t\tconversationId: lifecycle.conversationId,\n\t\tisPending: lifecycle.isPending,\n\t\titems: displayItems,\n\t\tisLoading: timelineQuery.isLoading,\n\t\terror: timelineQuery.error || composer.error,\n\t\tcomposer: {\n\t\t\tmessage: composer.message,\n\t\t\tfiles: composer.files,\n\t\t\tisSubmitting: composer.isSubmitting,\n\t\t\tcanSubmit: composer.canSubmit,\n\t\t\tsetMessage: composer.setMessage,\n\t\t\taddFiles: composer.addFiles,\n\t\t\tremoveFile: composer.removeFile,\n\t\t\tsubmit: composer.submit,\n\t\t},\n\t\thasItems: displayItems.length > 0,\n\t\tlastTimelineItem,\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-conversation-page.js","names":["identificationItem: TimelineItem"],"sources":["../../src/hooks/use-conversation-page.ts"],"sourcesContent":["import type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport {\n\tConversationTimelineType,\n\tTimelineItemVisibility,\n} from \"@cossistant/types/enums\";\nimport { useEffect, useMemo, useRef } from \"react\";\nimport { useSupport } from \"../provider\";\nimport { useWebSocketSafe } from \"../support/context/websocket\";\nimport { useDefaultMessages } from \"./private/use-default-messages\";\nimport { useConversationAutoSeen } from \"./use-conversation-auto-seen\";\nimport { useConversationLifecycle } from \"./use-conversation-lifecycle\";\nimport { useConversationTimelineItems } from \"./use-conversation-timeline-items\";\nimport { useMessageComposer } from \"./use-message-composer\";\n\nexport type UseConversationPageOptions = {\n\t/**\n\t * Initial conversation ID (from URL params, navigation state, etc.)\n\t * Can be PENDING_CONVERSATION_ID or a real ID.\n\t */\n\tconversationId: string;\n\n\t/**\n\t * Optional initial message to send when the conversation opens.\n\t */\n\tinitialMessage?: string;\n\n\t/**\n\t * Callback when conversation ID changes (e.g., after creation).\n\t * Use this to update navigation state or URL.\n\t */\n\tonConversationIdChange?: (conversationId: string) => void;\n\n\t/**\n\t * Optional timeline items to pass through (e.g., optimistic updates).\n\t */\n\titems?: TimelineItem[];\n\n\t/**\n\t * Whether automatic \"seen\" tracking should be enabled.\n\t * Set to false when the conversation isn't visible (e.g. widget closed).\n\t * Default: true\n\t */\n\tautoSeenEnabled?: boolean;\n};\n\nexport type UseConversationPageReturn = {\n\t// Conversation state\n\tconversationId: string;\n\tisPending: boolean;\n\titems: TimelineItem[];\n\tisLoading: boolean;\n\terror: Error | null;\n\n\t// Message composer\n\tcomposer: {\n\t\tmessage: string;\n\t\tfiles: File[];\n\t\tisSubmitting: boolean;\n\t\tisUploading: boolean;\n\t\tcanSubmit: boolean;\n\t\tsetMessage: (message: string) => void;\n\t\taddFiles: (files: File[]) => void;\n\t\tremoveFile: (index: number) => void;\n\t\tsubmit: () => void;\n\t};\n\n\t// UI helpers\n\thasItems: boolean;\n\tlastTimelineItem: TimelineItem | null;\n};\n\n/**\n * Main orchestrator hook for the conversation page.\n *\n * This hook combines all conversation-related logic:\n * - Lifecycle management (pending → real conversation)\n * - Message fetching and display\n * - Message composition and sending\n * - Automatic seen tracking\n * - Default/welcome messages before conversation is created\n *\n * It provides a clean, simple API for building conversation UIs.\n *\n * @example\n * ```tsx\n * export function ConversationPage({ conversationId: initialId }) {\n * const conversation = useConversationPage({\n * conversationId: initialId,\n * onConversationIdChange: (newId) => {\n * // Update URL or navigation state\n * navigate(`/conversation/${newId}`);\n * },\n * });\n *\n * return (\n * <>\n * <MessageList messages={conversation.messages} />\n * <MessageInput\n * value={conversation.composer.message}\n * onChange={conversation.composer.setMessage}\n * onSubmit={conversation.composer.submit}\n * />\n * </>\n * );\n * }\n * ```\n */\nexport function useConversationPage(\n\toptions: UseConversationPageOptions\n): UseConversationPageReturn {\n\tconst {\n\t\tconversationId: initialConversationId,\n\t\tinitialMessage,\n\t\tonConversationIdChange,\n\t\titems: passedItems = [],\n\t\tautoSeenEnabled = true,\n\t} = options;\n\n\tconst { client, visitor } = useSupport();\n\tconst websocket = useWebSocketSafe();\n\n\tconst trimmedInitialMessage = initialMessage?.trim() ?? \"\";\n\tconst hasInitialMessage = trimmedInitialMessage.length > 0;\n\n\t// 1. Manage conversation lifecycle (pending vs real)\n\tconst lifecycle = useConversationLifecycle({\n\t\tinitialConversationId,\n\t\tonConversationCreated: onConversationIdChange,\n\t});\n\n\t// 2. Get default timeline items for pending state\n\tconst defaultTimelineItems = useDefaultMessages({\n\t\tconversationId: lifecycle.conversationId,\n\t});\n\n\tconst effectiveDefaultTimelineItems = hasInitialMessage\n\t\t? []\n\t\t: defaultTimelineItems;\n\n\t// 3. Fetch timeline items from backend if real conversation exists\n\tconst timelineQuery = useConversationTimelineItems(lifecycle.conversationId, {\n\t\tenabled: !lifecycle.isPending,\n\t});\n\n\t// 4. Determine which items to display\n\tconst baseItems = useMemo(() => {\n\t\t// If we have fetched timeline items, use them\n\t\tif (timelineQuery.items.length > 0) {\n\t\t\treturn timelineQuery.items;\n\t\t}\n\n\t\t// If pending, use default timeline items\n\t\tif (lifecycle.isPending && effectiveDefaultTimelineItems.length > 0) {\n\t\t\treturn effectiveDefaultTimelineItems;\n\t\t}\n\n\t\t// Use passed items as fallback\n\t\tif (passedItems.length > 0) {\n\t\t\treturn passedItems;\n\t\t}\n\n\t\treturn [];\n\t}, [\n\t\ttimelineQuery.items,\n\t\tlifecycle.isPending,\n\t\teffectiveDefaultTimelineItems,\n\t\tpassedItems,\n\t]);\n\n\tconst shouldShowIdentificationTool = useMemo(() => {\n\t\tif (lifecycle.isPending) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (visitor?.contact) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn !baseItems.some(\n\t\t\t(item) => item.type === ConversationTimelineType.IDENTIFICATION\n\t\t);\n\t}, [baseItems, lifecycle.isPending, visitor?.contact]);\n\n\tconst displayItems = useMemo(() => {\n\t\tif (!shouldShowIdentificationTool) {\n\t\t\treturn baseItems;\n\t\t}\n\n\t\tconst organizationId =\n\t\t\tbaseItems.at(-1)?.organizationId ??\n\t\t\tclient.getConfiguration().organizationId ??\n\t\t\t\"\";\n\n\t\tconst identificationItem: TimelineItem = {\n\t\t\tid: `identification-${lifecycle.conversationId}`,\n\t\t\tconversationId: lifecycle.conversationId,\n\t\t\torganizationId,\n\t\t\tvisibility: TimelineItemVisibility.PUBLIC,\n\t\t\ttype: ConversationTimelineType.IDENTIFICATION,\n\t\t\ttext: null,\n\t\t\ttool: \"identification\",\n\t\t\tparts: [],\n\t\t\tuserId: null,\n\t\t\tvisitorId: visitor?.id ?? null,\n\t\t\taiAgentId: null,\n\t\t\tcreatedAt: typeof window !== \"undefined\" ? new Date().toISOString() : \"\",\n\t\t\tdeletedAt: null,\n\t\t};\n\n\t\treturn [...baseItems, identificationItem];\n\t}, [\n\t\tbaseItems,\n\t\tclient,\n\t\tlifecycle.conversationId,\n\t\tshouldShowIdentificationTool,\n\t\tvisitor?.id,\n\t]);\n\n\tconst lastTimelineItem = useMemo(\n\t\t() => displayItems.at(-1) ?? null,\n\t\t[displayItems]\n\t);\n\n\t// 5. Set up message composer\n\tconst composer = useMessageComposer({\n\t\tclient,\n\t\tconversationId: lifecycle.realConversationId,\n\t\tdefaultTimelineItems: effectiveDefaultTimelineItems,\n\t\tvisitorId: visitor?.id,\n\t\tonMessageSent: (newConversationId) => {\n\t\t\t// Transition from pending to real conversation\n\t\t\tif (lifecycle.isPending) {\n\t\t\t\tlifecycle.setConversationId(newConversationId);\n\t\t\t}\n\t\t},\n\t\t// Pass WebSocket connection for real-time typing events\n\t\trealtimeSend: websocket?.send ?? null,\n\t\tisRealtimeConnected: websocket?.isConnected ?? false,\n\t});\n\n\tconst initialMessageSubmittedRef = useRef(false);\n\tconst lastInitialMessageRef = useRef<string | null>(null);\n\n\tuseEffect(() => {\n\t\tif (!hasInitialMessage) {\n\t\t\tinitialMessageSubmittedRef.current = false;\n\t\t\tlastInitialMessageRef.current = null;\n\t\t\treturn;\n\t\t}\n\n\t\tif (lastInitialMessageRef.current !== trimmedInitialMessage) {\n\t\t\tinitialMessageSubmittedRef.current = false;\n\t\t\tlastInitialMessageRef.current = trimmedInitialMessage;\n\t\t}\n\n\t\tif (!lifecycle.isPending) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (composer.message !== trimmedInitialMessage) {\n\t\t\tcomposer.setMessage(trimmedInitialMessage);\n\t\t\treturn;\n\t\t}\n\n\t\tif (\n\t\t\tinitialMessageSubmittedRef.current ||\n\t\t\tcomposer.isSubmitting ||\n\t\t\t!composer.canSubmit\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tinitialMessageSubmittedRef.current = true;\n\t\tcomposer.submit();\n\t}, [\n\t\thasInitialMessage,\n\t\tlifecycle.isPending,\n\t\tcomposer.message,\n\t\tcomposer.setMessage,\n\t\tcomposer.isSubmitting,\n\t\tcomposer.canSubmit,\n\t\tcomposer.submit,\n\t\ttrimmedInitialMessage,\n\t]);\n\n\t// 6. Auto-mark messages as seen\n\tuseConversationAutoSeen({\n\t\tclient,\n\t\tconversationId: lifecycle.realConversationId,\n\t\tvisitorId: visitor?.id,\n\t\tlastTimelineItem,\n\t\tenabled: autoSeenEnabled,\n\t\tisWidgetOpen: autoSeenEnabled,\n\t});\n\n\treturn {\n\t\tconversationId: lifecycle.conversationId,\n\t\tisPending: lifecycle.isPending,\n\t\titems: displayItems,\n\t\tisLoading: timelineQuery.isLoading,\n\t\terror: timelineQuery.error || composer.error,\n\t\tcomposer: {\n\t\t\tmessage: composer.message,\n\t\t\tfiles: composer.files,\n\t\t\tisSubmitting: composer.isSubmitting,\n\t\t\tisUploading: composer.isUploading,\n\t\t\tcanSubmit: composer.canSubmit,\n\t\t\tsetMessage: composer.setMessage,\n\t\t\taddFiles: composer.addFiles,\n\t\t\tremoveFile: composer.removeFile,\n\t\t\tsubmit: composer.submit,\n\t\t},\n\t\thasItems: displayItems.length > 0,\n\t\tlastTimelineItem,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2GA,SAAgB,oBACf,SAC4B;CAC5B,MAAM,EACL,gBAAgB,uBAChB,gBACA,wBACA,OAAO,cAAc,EAAE,EACvB,kBAAkB,SACf;CAEJ,MAAM,EAAE,QAAQ,YAAY,YAAY;CACxC,MAAM,YAAY,kBAAkB;CAEpC,MAAM,wBAAwB,gBAAgB,MAAM,IAAI;CACxD,MAAM,oBAAoB,sBAAsB,SAAS;CAGzD,MAAM,YAAY,yBAAyB;EAC1C;EACA,uBAAuB;EACvB,CAAC;CAGF,MAAM,uBAAuB,mBAAmB,EAC/C,gBAAgB,UAAU,gBAC1B,CAAC;CAEF,MAAM,gCAAgC,oBACnC,EAAE,GACF;CAGH,MAAM,gBAAgB,6BAA6B,UAAU,gBAAgB,EAC5E,SAAS,CAAC,UAAU,WACpB,CAAC;CAGF,MAAM,YAAY,cAAc;AAE/B,MAAI,cAAc,MAAM,SAAS,EAChC,QAAO,cAAc;AAItB,MAAI,UAAU,aAAa,8BAA8B,SAAS,EACjE,QAAO;AAIR,MAAI,YAAY,SAAS,EACxB,QAAO;AAGR,SAAO,EAAE;IACP;EACF,cAAc;EACd,UAAU;EACV;EACA;EACA,CAAC;CAEF,MAAM,+BAA+B,cAAc;AAClD,MAAI,UAAU,UACb,QAAO;AAGR,MAAI,SAAS,QACZ,QAAO;AAGR,SAAO,CAAC,UAAU,MAChB,SAAS,KAAK,SAAS,yBAAyB,eACjD;IACC;EAAC;EAAW,UAAU;EAAW,SAAS;EAAQ,CAAC;CAEtD,MAAM,eAAe,cAAc;AAClC,MAAI,CAAC,6BACJ,QAAO;EAGR,MAAM,iBACL,UAAU,GAAG,GAAG,EAAE,kBAClB,OAAO,kBAAkB,CAAC,kBAC1B;EAED,MAAMA,qBAAmC;GACxC,IAAI,kBAAkB,UAAU;GAChC,gBAAgB,UAAU;GAC1B;GACA,YAAY,uBAAuB;GACnC,MAAM,yBAAyB;GAC/B,MAAM;GACN,MAAM;GACN,OAAO,EAAE;GACT,QAAQ;GACR,WAAW,SAAS,MAAM;GAC1B,WAAW;GACX,WAAW,OAAO,WAAW,+BAAc,IAAI,MAAM,EAAC,aAAa,GAAG;GACtE,WAAW;GACX;AAED,SAAO,CAAC,GAAG,WAAW,mBAAmB;IACvC;EACF;EACA;EACA,UAAU;EACV;EACA,SAAS;EACT,CAAC;CAEF,MAAM,mBAAmB,cAClB,aAAa,GAAG,GAAG,IAAI,MAC7B,CAAC,aAAa,CACd;CAGD,MAAM,WAAW,mBAAmB;EACnC;EACA,gBAAgB,UAAU;EAC1B,sBAAsB;EACtB,WAAW,SAAS;EACpB,gBAAgB,sBAAsB;AAErC,OAAI,UAAU,UACb,WAAU,kBAAkB,kBAAkB;;EAIhD,cAAc,WAAW,QAAQ;EACjC,qBAAqB,WAAW,eAAe;EAC/C,CAAC;CAEF,MAAM,6BAA6B,OAAO,MAAM;CAChD,MAAM,wBAAwB,OAAsB,KAAK;AAEzD,iBAAgB;AACf,MAAI,CAAC,mBAAmB;AACvB,8BAA2B,UAAU;AACrC,yBAAsB,UAAU;AAChC;;AAGD,MAAI,sBAAsB,YAAY,uBAAuB;AAC5D,8BAA2B,UAAU;AACrC,yBAAsB,UAAU;;AAGjC,MAAI,CAAC,UAAU,UACd;AAGD,MAAI,SAAS,YAAY,uBAAuB;AAC/C,YAAS,WAAW,sBAAsB;AAC1C;;AAGD,MACC,2BAA2B,WAC3B,SAAS,gBACT,CAAC,SAAS,UAEV;AAGD,6BAA2B,UAAU;AACrC,WAAS,QAAQ;IACf;EACF;EACA,UAAU;EACV,SAAS;EACT,SAAS;EACT,SAAS;EACT,SAAS;EACT,SAAS;EACT;EACA,CAAC;AAGF,yBAAwB;EACvB;EACA,gBAAgB,UAAU;EAC1B,WAAW,SAAS;EACpB;EACA,SAAS;EACT,cAAc;EACd,CAAC;AAEF,QAAO;EACN,gBAAgB,UAAU;EAC1B,WAAW,UAAU;EACrB,OAAO;EACP,WAAW,cAAc;EACzB,OAAO,cAAc,SAAS,SAAS;EACvC,UAAU;GACT,SAAS,SAAS;GAClB,OAAO,SAAS;GAChB,cAAc,SAAS;GACvB,aAAa,SAAS;GACtB,WAAW,SAAS;GACpB,YAAY,SAAS;GACrB,UAAU,SAAS;GACnB,YAAY,SAAS;GACrB,QAAQ,SAAS;GACjB;EACD,UAAU,aAAa,SAAS;EAChC;EACA"}
@@ -27,7 +27,8 @@ type UseConversationPreviewOptions = {
27
27
  conversation: Conversation;
28
28
  /**
29
29
  * Whether the hook should fetch timeline items for the conversation.
30
- * Enabled by default.
30
+ * Disabled by default to reduce API calls - conversation.lastTimelineItem
31
+ * is typically sufficient for previews.
31
32
  */
32
33
  includeTimelineItems?: boolean;
33
34
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"use-conversation-preview.d.ts","names":[],"sources":["../../src/hooks/use-conversation-preview.ts"],"sourcesContent":[],"mappings":";;;;;;KAcY,8BAAA;;EAAA,IAAA,EAAA,MAAA;EAQA,aAAA,EAAA,OAAA;EAMA,UAAA,CAAA,EAAA,MAAA;EAEA,WAAA,CAAA,EAAA,MAAA,GAAA,IAAA;AAOZ,CAAA;AAqBY,KApCA,gCAAA,GAoC4B;EACzB,IAAA,EAAA,MAAA;EAED,KAAA,EAAA,MAAA,GAAA,IAAA;EACE,IAAA,EAAA,OAAA,GAAA,IAAA,GAAA,UAAA;CACP;AACoB,KApCjB,oCAAA,GAAuC,wBAoCtB;AAAlB,KAlCC,8BAAA,GAkCD;EAAU,YAAA,EAjCN,oCAiCM,EAAA;EA0BL,kBAAA,EA1DK,oCA2DX,GAAA,IAAA;;;;KAtDE,6BAAA;gBACG;;;;;;;;;yBASS;;;;;;;;;;KAWZ,4BAAA;gBACG;;eAED;iBACE;UACP;YACE,kBAAkB;;;;;;iBA0Bb,sBAAA,UACN,gCACP"}
1
+ {"version":3,"file":"use-conversation-preview.d.ts","names":[],"sources":["../../src/hooks/use-conversation-preview.ts"],"sourcesContent":[],"mappings":";;;;;;KAcY,8BAAA;;EAAA,IAAA,EAAA,MAAA;EAQA,aAAA,EAAA,OAAA;EAMA,UAAA,CAAA,EAAA,MAAA;EAEA,WAAA,CAAA,EAAA,MAAA,GAAA,IAAA;AAOZ,CAAA;AAsBY,KArCA,gCAAA,GAqC4B;EACzB,IAAA,EAAA,MAAA;EAED,KAAA,EAAA,MAAA,GAAA,IAAA;EACE,IAAA,EAAA,OAAA,GAAA,IAAA,GAAA,UAAA;CACP;AACoB,KArCjB,oCAAA,GAAuC,wBAqCtB;AAAlB,KAnCC,8BAAA,GAmCD;EAAU,YAAA,EAlCN,oCAkCM,EAAA;EA0BL,kBAAA,EA3DK,oCA4DX,GAAA,IAAA;;;;KAvDE,6BAAA;gBACG;;;;;;;;;;yBAUS;;;;;;;;;;KAWZ,4BAAA;gBACG;;eAED;iBACE;UACP;YACE,kBAAkB;;;;;;iBA0Bb,sBAAA,UACN,gCACP"}
@@ -20,7 +20,7 @@ function resolveLastTimelineMessage(items, fallback) {
20
20
  * snippets and typing state for use in lists.
21
21
  */
22
22
  function useConversationPreview(options) {
23
- const { conversation, includeTimelineItems = true, initialTimelineItems = [], typing } = options;
23
+ const { conversation, includeTimelineItems = false, initialTimelineItems = [], typing } = options;
24
24
  const { availableHumanAgents, availableAIAgents, visitor } = useSupport();
25
25
  const text = useSupportText();
26
26
  const timeline = useConversationTimelineItems(conversation.id, { enabled: includeTimelineItems });