@aleph-alpha/chat-kit 0.9.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/dist/{CkInput.vue_vue_type_script_setup_true_lang-DmcLXAgB.js → CkInput.vue_vue_type_script_setup_true_lang-DpYyyCgU.js} +481 -79
  2. package/dist/chat-kit.css +9 -14
  3. package/dist/components/base/CkAgentMessage/CkAgentMessage.stories.d.ts +20 -0
  4. package/dist/components/base/CkAgentMessage/CkAgentMessage.stories.d.ts.map +1 -1
  5. package/dist/components/base/CkAgentMessage/CkAgentMessage.vue.d.ts.map +1 -1
  6. package/dist/components/base/CkAgentMessage/index.d.ts +1 -0
  7. package/dist/components/base/CkAgentMessage/index.d.ts.map +1 -1
  8. package/dist/components/base/CkAgentMessage/types.d.ts +12 -0
  9. package/dist/components/base/CkAgentMessage/types.d.ts.map +1 -0
  10. package/dist/components/base/CkConversationLayout/CkConversationLayout.stories.d.ts +13 -0
  11. package/dist/components/base/CkConversationLayout/CkConversationLayout.stories.d.ts.map +1 -1
  12. package/dist/components/base/CkConversationLayout/CkConversationLayout.vue.d.ts.map +1 -1
  13. package/dist/components/base/CkConversationLayout/types.d.ts +9 -2
  14. package/dist/components/base/CkConversationLayout/types.d.ts.map +1 -1
  15. package/dist/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.stories.d.ts +8 -0
  16. package/dist/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.stories.d.ts.map +1 -1
  17. package/dist/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.vue.d.ts +3 -15
  18. package/dist/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.vue.d.ts.map +1 -1
  19. package/dist/components/base/CkCopyMessageActionButton/index.d.ts +1 -0
  20. package/dist/components/base/CkCopyMessageActionButton/index.d.ts.map +1 -1
  21. package/dist/components/base/CkCopyMessageActionButton/types.d.ts +45 -0
  22. package/dist/components/base/CkCopyMessageActionButton/types.d.ts.map +1 -0
  23. package/dist/components/base/CkHistoryItem/CkHistoryItem.stories.d.ts +31 -0
  24. package/dist/components/base/CkHistoryItem/CkHistoryItem.stories.d.ts.map +1 -0
  25. package/dist/components/base/CkHistoryItem/CkHistoryItem.vue.d.ts +31 -0
  26. package/dist/components/base/CkHistoryItem/CkHistoryItem.vue.d.ts.map +1 -0
  27. package/dist/components/base/CkHistoryItem/activeIndicator.d.ts +15 -0
  28. package/dist/components/base/CkHistoryItem/activeIndicator.d.ts.map +1 -0
  29. package/dist/components/base/CkHistoryItem/index.d.ts +3 -0
  30. package/dist/components/base/CkHistoryItem/index.d.ts.map +1 -0
  31. package/dist/components/base/CkHistoryItem/types.d.ts +96 -0
  32. package/dist/components/base/CkHistoryItem/types.d.ts.map +1 -0
  33. package/dist/components/base/CkHistoryWrapper/CkHistoryWrapper.stories.d.ts +16 -0
  34. package/dist/components/base/CkHistoryWrapper/CkHistoryWrapper.stories.d.ts.map +1 -0
  35. package/dist/components/base/CkHistoryWrapper/CkHistoryWrapper.vue.d.ts +17 -0
  36. package/dist/components/base/CkHistoryWrapper/CkHistoryWrapper.vue.d.ts.map +1 -0
  37. package/dist/components/base/CkHistoryWrapper/index.d.ts +3 -0
  38. package/dist/components/base/CkHistoryWrapper/index.d.ts.map +1 -0
  39. package/dist/components/base/CkHistoryWrapper/types.d.ts +17 -0
  40. package/dist/components/base/CkHistoryWrapper/types.d.ts.map +1 -0
  41. package/dist/components/base/CkMarkdownRenderer/CkMarkdownRenderer.stories.d.ts +28 -0
  42. package/dist/components/base/CkMarkdownRenderer/CkMarkdownRenderer.stories.d.ts.map +1 -1
  43. package/dist/components/base/CkMarkdownRenderer/CkMarkdownRenderer.vue.d.ts.map +1 -1
  44. package/dist/components/base/CkMarkdownRenderer/index.d.ts +1 -1
  45. package/dist/components/base/CkMarkdownRenderer/index.d.ts.map +1 -1
  46. package/dist/components/base/CkMarkdownRenderer/types.d.ts +16 -0
  47. package/dist/components/base/CkMarkdownRenderer/types.d.ts.map +1 -1
  48. package/dist/components/base/CkMessageActionButton/CkMessageActionButton.stories.d.ts +4 -0
  49. package/dist/components/base/CkMessageActionButton/CkMessageActionButton.stories.d.ts.map +1 -1
  50. package/dist/components/base/CkMessageActionButton/CkMessageActionButton.vue.d.ts +2 -5
  51. package/dist/components/base/CkMessageActionButton/CkMessageActionButton.vue.d.ts.map +1 -1
  52. package/dist/components/base/CkMessageActionButton/index.d.ts +1 -0
  53. package/dist/components/base/CkMessageActionButton/index.d.ts.map +1 -1
  54. package/dist/components/base/CkMessageActionButton/types.d.ts +22 -0
  55. package/dist/components/base/CkMessageActionButton/types.d.ts.map +1 -0
  56. package/dist/components/base/CkPromptActionButton/CkPromptActionButton.stories.d.ts +12 -0
  57. package/dist/components/base/CkPromptActionButton/CkPromptActionButton.stories.d.ts.map +1 -1
  58. package/dist/components/base/CkPromptActionButton/CkPromptActionButton.vue.d.ts +2 -7
  59. package/dist/components/base/CkPromptActionButton/CkPromptActionButton.vue.d.ts.map +1 -1
  60. package/dist/components/base/CkPromptActionButton/index.d.ts +1 -0
  61. package/dist/components/base/CkPromptActionButton/index.d.ts.map +1 -1
  62. package/dist/components/base/CkPromptActionButton/types.d.ts +33 -0
  63. package/dist/components/base/CkPromptActionButton/types.d.ts.map +1 -0
  64. package/dist/components/base/CkPromptInput/CkPromptInput.stories.d.ts +17 -0
  65. package/dist/components/base/CkPromptInput/CkPromptInput.stories.d.ts.map +1 -1
  66. package/dist/components/base/CkPromptInput/CkPromptInput.vue.d.ts +3 -9
  67. package/dist/components/base/CkPromptInput/CkPromptInput.vue.d.ts.map +1 -1
  68. package/dist/components/base/CkPromptInput/index.d.ts +1 -0
  69. package/dist/components/base/CkPromptInput/index.d.ts.map +1 -1
  70. package/dist/components/base/CkPromptInput/types.d.ts +39 -0
  71. package/dist/components/base/CkPromptInput/types.d.ts.map +1 -0
  72. package/dist/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.stories.d.ts +12 -0
  73. package/dist/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.stories.d.ts.map +1 -1
  74. package/dist/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.vue.d.ts +2 -4
  75. package/dist/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.vue.d.ts.map +1 -1
  76. package/dist/components/base/CkPromptInputDisclaimer/index.d.ts +1 -0
  77. package/dist/components/base/CkPromptInputDisclaimer/index.d.ts.map +1 -1
  78. package/dist/components/base/CkPromptInputDisclaimer/types.d.ts +18 -0
  79. package/dist/components/base/CkPromptInputDisclaimer/types.d.ts.map +1 -0
  80. package/dist/components/base/CkPromptStopButton/CkPromptStopButton.stories.d.ts +8 -0
  81. package/dist/components/base/CkPromptStopButton/CkPromptStopButton.stories.d.ts.map +1 -1
  82. package/dist/components/base/CkPromptStopButton/CkPromptStopButton.vue.d.ts +3 -6
  83. package/dist/components/base/CkPromptStopButton/CkPromptStopButton.vue.d.ts.map +1 -1
  84. package/dist/components/base/CkPromptStopButton/index.d.ts +1 -0
  85. package/dist/components/base/CkPromptStopButton/index.d.ts.map +1 -1
  86. package/dist/components/base/CkPromptStopButton/types.d.ts +28 -0
  87. package/dist/components/base/CkPromptStopButton/types.d.ts.map +1 -0
  88. package/dist/components/base/CkUserMessage/CkUserMessage.stories.d.ts +15 -0
  89. package/dist/components/base/CkUserMessage/CkUserMessage.stories.d.ts.map +1 -1
  90. package/dist/components/base/CkUserMessage/CkUserMessage.vue.d.ts.map +1 -1
  91. package/dist/components/base/CkUserMessage/types.d.ts +9 -2
  92. package/dist/components/base/CkUserMessage/types.d.ts.map +1 -1
  93. package/dist/components/base/icons/CkStopIcon.vue.d.ts.map +1 -1
  94. package/dist/components/base/index.d.ts +5 -1
  95. package/dist/components/base/index.d.ts.map +1 -1
  96. package/dist/components/composed/CkConversation/CkConversation.stories.d.ts +55 -0
  97. package/dist/components/composed/CkConversation/CkConversation.stories.d.ts.map +1 -1
  98. package/dist/components/composed/CkConversation/CkConversation.vue.d.ts.map +1 -1
  99. package/dist/components/composed/CkConversation/types.d.ts +25 -6
  100. package/dist/components/composed/CkConversation/types.d.ts.map +1 -1
  101. package/dist/components/composed/CkHistory/CkHistory.stories.d.ts +33 -0
  102. package/dist/components/composed/CkHistory/CkHistory.stories.d.ts.map +1 -0
  103. package/dist/components/composed/CkHistory/CkHistory.vue.d.ts +50 -0
  104. package/dist/components/composed/CkHistory/CkHistory.vue.d.ts.map +1 -0
  105. package/dist/components/composed/CkHistory/index.d.ts +3 -0
  106. package/dist/components/composed/CkHistory/index.d.ts.map +1 -0
  107. package/dist/components/composed/CkHistory/types.d.ts +71 -0
  108. package/dist/components/composed/CkHistory/types.d.ts.map +1 -0
  109. package/dist/components/composed/CkInput/CkInput.stories.d.ts +31 -1
  110. package/dist/components/composed/CkInput/CkInput.stories.d.ts.map +1 -1
  111. package/dist/components/composed/CkInput/CkInput.vue.d.ts +3 -12
  112. package/dist/components/composed/CkInput/CkInput.vue.d.ts.map +1 -1
  113. package/dist/components/composed/CkInput/index.d.ts +1 -0
  114. package/dist/components/composed/CkInput/index.d.ts.map +1 -1
  115. package/dist/components/composed/CkInput/types.d.ts +60 -0
  116. package/dist/components/composed/CkInput/types.d.ts.map +1 -0
  117. package/dist/components/composed/index.d.ts +2 -0
  118. package/dist/components/composed/index.d.ts.map +1 -1
  119. package/dist/components/index.d.ts +4 -4
  120. package/dist/components/index.d.ts.map +1 -1
  121. package/dist/components/index.js +14 -11
  122. package/dist/composables/index.d.ts +2 -0
  123. package/dist/composables/index.d.ts.map +1 -1
  124. package/dist/composables/index.js +3 -3
  125. package/dist/composables/useConversations.d.ts +72 -0
  126. package/dist/composables/useConversations.d.ts.map +1 -0
  127. package/dist/generateId-BHf0W4Rq.js +6 -0
  128. package/dist/handlers/index.js +2 -2
  129. package/dist/handlers/useChatKitLabels.d.ts +8 -0
  130. package/dist/handlers/useChatKitLabels.d.ts.map +1 -1
  131. package/dist/handlers/useMarkdownParser.d.ts.map +1 -1
  132. package/dist/helpers/conversationTree.d.ts.map +1 -1
  133. package/dist/helpers/index.d.ts +2 -0
  134. package/dist/helpers/index.d.ts.map +1 -1
  135. package/dist/helpers/secondsToDate.d.ts +6 -0
  136. package/dist/helpers/secondsToDate.d.ts.map +1 -0
  137. package/dist/helpers/toError.d.ts +7 -0
  138. package/dist/helpers/toError.d.ts.map +1 -0
  139. package/dist/index.d.ts +6 -6
  140. package/dist/index.d.ts.map +1 -1
  141. package/dist/index.js +20 -16
  142. package/dist/services/ChatService.d.ts.map +1 -1
  143. package/dist/services/MockChatService.d.ts.map +1 -1
  144. package/dist/services/index.d.ts +1 -1
  145. package/dist/services/index.d.ts.map +1 -1
  146. package/dist/services/index.js +4 -1
  147. package/dist/services/types.d.ts +53 -0
  148. package/dist/services/types.d.ts.map +1 -1
  149. package/dist/types.d.ts +13 -0
  150. package/dist/types.d.ts.map +1 -1
  151. package/dist/{useChatKitLabels-DNwoZ1x6.js → useChatKitLabels-Dvi2FmjE.js} +8 -2
  152. package/dist/useConversations-Ca242D2P.js +195 -0
  153. package/dist/{useMessageHandler-D7OdrrcB.js → useMessageHandler-uLHNac39.js} +1 -3
  154. package/package.json +5 -4
  155. package/src/components/base/CkAgentMessage/CkAgentMessage.spec.ts +91 -0
  156. package/src/components/base/CkAgentMessage/CkAgentMessage.stories.ts +215 -0
  157. package/src/components/base/CkAgentMessage/CkAgentMessage.vue +25 -0
  158. package/src/components/base/CkAgentMessage/index.ts +2 -0
  159. package/src/components/base/CkAgentMessage/types.ts +11 -0
  160. package/src/components/base/CkConversationLayout/CkConversationLayout.spec.ts +129 -0
  161. package/src/components/base/CkConversationLayout/CkConversationLayout.stories.ts +251 -0
  162. package/src/components/base/CkConversationLayout/CkConversationLayout.vue +206 -0
  163. package/src/components/base/CkConversationLayout/index.ts +2 -0
  164. package/src/components/base/CkConversationLayout/types.ts +18 -0
  165. package/src/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.spec.ts +89 -0
  166. package/src/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.stories.ts +70 -0
  167. package/src/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.vue +110 -0
  168. package/src/components/base/CkCopyMessageActionButton/index.ts +2 -0
  169. package/src/components/base/CkCopyMessageActionButton/types.ts +43 -0
  170. package/src/components/base/CkHistoryItem/CkHistoryItem.spec.ts +42 -0
  171. package/src/components/base/CkHistoryItem/CkHistoryItem.stories.ts +230 -0
  172. package/src/components/base/CkHistoryItem/CkHistoryItem.vue +285 -0
  173. package/src/components/base/CkHistoryItem/activeIndicator.ts +15 -0
  174. package/src/components/base/CkHistoryItem/index.ts +2 -0
  175. package/src/components/base/CkHistoryItem/types.ts +111 -0
  176. package/src/components/base/CkHistoryWrapper/CkHistoryWrapper.spec.ts +26 -0
  177. package/src/components/base/CkHistoryWrapper/CkHistoryWrapper.stories.ts +149 -0
  178. package/src/components/base/CkHistoryWrapper/CkHistoryWrapper.vue +36 -0
  179. package/src/components/base/CkHistoryWrapper/index.ts +2 -0
  180. package/src/components/base/CkHistoryWrapper/types.ts +16 -0
  181. package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.spec.ts +348 -0
  182. package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.stories.ts +382 -0
  183. package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.vue +89 -0
  184. package/src/components/base/CkMarkdownRenderer/defaultMarkdownClasses.ts +19 -0
  185. package/src/components/base/CkMarkdownRenderer/index.ts +5 -0
  186. package/src/components/base/CkMarkdownRenderer/types.ts +53 -0
  187. package/src/components/base/CkMessageActionButton/CkMessageActionButton.spec.ts +40 -0
  188. package/src/components/base/CkMessageActionButton/CkMessageActionButton.stories.ts +56 -0
  189. package/src/components/base/CkMessageActionButton/CkMessageActionButton.vue +23 -0
  190. package/src/components/base/CkMessageActionButton/index.ts +2 -0
  191. package/src/components/base/CkMessageActionButton/types.ts +21 -0
  192. package/src/components/base/CkPromptActionButton/CkPromptActionButton.spec.ts +52 -0
  193. package/src/components/base/CkPromptActionButton/CkPromptActionButton.stories.ts +126 -0
  194. package/src/components/base/CkPromptActionButton/CkPromptActionButton.vue +25 -0
  195. package/src/components/base/CkPromptActionButton/index.ts +2 -0
  196. package/src/components/base/CkPromptActionButton/types.ts +32 -0
  197. package/src/components/base/CkPromptInput/CkPromptInput.spec.ts +181 -0
  198. package/src/components/base/CkPromptInput/CkPromptInput.stories.ts +224 -0
  199. package/src/components/base/CkPromptInput/CkPromptInput.vue +104 -0
  200. package/src/components/base/CkPromptInput/index.ts +2 -0
  201. package/src/components/base/CkPromptInput/types.ts +39 -0
  202. package/src/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.spec.ts +40 -0
  203. package/src/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.stories.ts +103 -0
  204. package/src/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.vue +16 -0
  205. package/src/components/base/CkPromptInputDisclaimer/index.ts +2 -0
  206. package/src/components/base/CkPromptInputDisclaimer/types.ts +17 -0
  207. package/src/components/base/CkPromptStopButton/CkPromptStopButton.spec.ts +64 -0
  208. package/src/components/base/CkPromptStopButton/CkPromptStopButton.stories.ts +81 -0
  209. package/src/components/base/CkPromptStopButton/CkPromptStopButton.vue +27 -0
  210. package/src/components/base/CkPromptStopButton/index.ts +2 -0
  211. package/src/components/base/CkPromptStopButton/types.ts +28 -0
  212. package/src/components/base/CkUserMessage/CkUserMessage.spec.ts +20 -0
  213. package/src/components/base/CkUserMessage/CkUserMessage.stories.ts +112 -0
  214. package/src/components/base/CkUserMessage/CkUserMessage.vue +19 -0
  215. package/src/components/base/CkUserMessage/index.ts +2 -0
  216. package/src/components/base/CkUserMessage/types.ts +14 -0
  217. package/src/components/base/icons/CkSendIcon.vue +22 -0
  218. package/src/components/base/icons/CkStopIcon.vue +18 -0
  219. package/src/components/base/icons/index.ts +2 -0
  220. package/src/components/base/index.ts +21 -0
  221. package/src/components/composed/CkConversation/CkConversation.spec.ts +439 -0
  222. package/src/components/composed/CkConversation/CkConversation.stories.ts +642 -0
  223. package/src/components/composed/CkConversation/CkConversation.vue +146 -0
  224. package/src/components/composed/CkConversation/index.ts +2 -0
  225. package/src/components/composed/CkConversation/types.ts +37 -0
  226. package/src/components/composed/CkHistory/CkHistory.spec.ts +327 -0
  227. package/src/components/composed/CkHistory/CkHistory.stories.ts +373 -0
  228. package/src/components/composed/CkHistory/CkHistory.vue +126 -0
  229. package/src/components/composed/CkHistory/index.ts +2 -0
  230. package/src/components/composed/CkHistory/types.ts +72 -0
  231. package/src/components/composed/CkInput/CkInput.spec.ts +191 -0
  232. package/src/components/composed/CkInput/CkInput.stories.ts +438 -0
  233. package/src/components/composed/CkInput/CkInput.vue +104 -0
  234. package/src/components/composed/CkInput/index.ts +2 -0
  235. package/src/components/composed/CkInput/types.ts +60 -0
  236. package/src/components/composed/index.ts +5 -0
  237. package/src/components/index.ts +34 -0
  238. package/src/composables/index.ts +9 -0
  239. package/src/composables/types.ts +36 -0
  240. package/src/composables/useChat.ts +18 -0
  241. package/src/composables/useConversations.spec.ts +557 -0
  242. package/src/composables/useConversations.ts +321 -0
  243. package/src/handlers/index.ts +9 -0
  244. package/src/handlers/useChatKitLabels.spec.ts +290 -0
  245. package/src/handlers/useChatKitLabels.ts +96 -0
  246. package/src/handlers/useMarkdownParser.spec.ts +112 -0
  247. package/src/handlers/useMarkdownParser.ts +47 -0
  248. package/src/handlers/useMessageHandler.spec.ts +535 -0
  249. package/src/handlers/useMessageHandler.ts +138 -0
  250. package/src/helpers/conversationTree.spec.ts +423 -0
  251. package/src/helpers/conversationTree.ts +117 -0
  252. package/src/helpers/generateId.ts +5 -0
  253. package/src/helpers/index.ts +4 -0
  254. package/src/helpers/secondsToDate.ts +7 -0
  255. package/src/helpers/toError.ts +8 -0
  256. package/src/index.ts +79 -0
  257. package/src/services/ChatService.ts +60 -0
  258. package/src/services/MockChatService.ts +47 -0
  259. package/src/services/index.ts +12 -0
  260. package/src/services/types.ts +92 -0
  261. package/src/test-setup.ts +12 -0
  262. package/src/test-utils/createTextPlugin.ts +9 -0
  263. package/src/types.ts +47 -0
@@ -0,0 +1,557 @@
1
+ import type {
2
+ HttpClient,
3
+ RawConversation,
4
+ RawConversationList,
5
+ } from '../services/types';
6
+ import { useConversations } from './useConversations';
7
+ import { nextTick } from 'vue';
8
+
9
+ function makeRawConversation(
10
+ overrides: Partial<RawConversation> & { id: string },
11
+ ): RawConversation {
12
+ return {
13
+ object: 'conversation',
14
+ metadata: { title: 'Untitled', application: 'tiger-chat' },
15
+ created_at: 1_777_456_700,
16
+ updated_at: 1_777_456_700,
17
+ ...overrides,
18
+ };
19
+ }
20
+
21
+ function makeRawList(
22
+ data: RawConversation[],
23
+ options: { hasMore?: boolean; lastId?: string | null } = {},
24
+ ): RawConversationList {
25
+ return {
26
+ object: 'list',
27
+ data,
28
+ has_more: options.hasMore ?? false,
29
+ first_id: data[0]?.id ?? null,
30
+ last_id: options.lastId ?? data[data.length - 1]?.id ?? null,
31
+ };
32
+ }
33
+
34
+ interface FakeHttp {
35
+ get: ReturnType<typeof vi.fn>;
36
+ post: ReturnType<typeof vi.fn>;
37
+ delete: ReturnType<typeof vi.fn>;
38
+ }
39
+
40
+ function makeFakeHttp(): FakeHttp {
41
+ return {
42
+ get: vi.fn(),
43
+ post: vi.fn(),
44
+ delete: vi.fn(),
45
+ };
46
+ }
47
+
48
+ function asHttp(fake: FakeHttp): HttpClient {
49
+ return fake as unknown as HttpClient;
50
+ }
51
+
52
+ describe('useConversations', () => {
53
+ describe('fetch', () => {
54
+ it('maps the raw list response into ConversationSummary[]', async () => {
55
+ const http = makeFakeHttp();
56
+ http.get.mockResolvedValue(
57
+ makeRawList([
58
+ makeRawConversation({
59
+ id: 'conv_a',
60
+ metadata: { title: 'hey again' },
61
+ created_at: 1_777_456_746,
62
+ updated_at: 1_777_456_750,
63
+ }),
64
+ ]),
65
+ );
66
+
67
+ const conv = useConversations({
68
+ httpClient: asHttp(http),
69
+ path: '/conversations',
70
+ });
71
+ await conv.fetch();
72
+
73
+ expect(http.get).toHaveBeenCalledWith('/conversations', {
74
+ params: { limit: 20, order: 'desc' },
75
+ });
76
+ expect(conv.conversations.value).toHaveLength(1);
77
+ expect(conv.conversations.value[0]).toEqual({
78
+ id: 'conv_a',
79
+ title: 'hey again',
80
+ createdAt: new Date(1_777_456_746 * 1000),
81
+ updatedAt: new Date(1_777_456_750 * 1000),
82
+ });
83
+ });
84
+
85
+ it('falls back to an empty title when metadata.title is missing', async () => {
86
+ const http = makeFakeHttp();
87
+ http.get.mockResolvedValue(
88
+ makeRawList([makeRawConversation({ id: 'conv_a', metadata: null })]),
89
+ );
90
+
91
+ const conv = useConversations({
92
+ httpClient: asHttp(http),
93
+ path: '/conversations',
94
+ });
95
+ await conv.fetch();
96
+
97
+ expect(conv.conversations.value[0].title).toBe('');
98
+ });
99
+
100
+ it('uses the configured pageSize as the default limit', async () => {
101
+ const http = makeFakeHttp();
102
+ http.get.mockResolvedValue(makeRawList([]));
103
+
104
+ const conv = useConversations({
105
+ httpClient: asHttp(http),
106
+ path: '/conversations',
107
+ pageSize: 5,
108
+ });
109
+ await conv.fetch();
110
+
111
+ expect(http.get).toHaveBeenCalledWith('/conversations', {
112
+ params: { limit: 5, order: 'desc' },
113
+ });
114
+ });
115
+
116
+ it('lets the caller override the limit per call', async () => {
117
+ const http = makeFakeHttp();
118
+ http.get.mockResolvedValue(makeRawList([]));
119
+
120
+ const conv = useConversations({
121
+ httpClient: asHttp(http),
122
+ path: '/conversations',
123
+ });
124
+ await conv.fetch({ limit: 50 });
125
+
126
+ expect(http.get).toHaveBeenCalledWith('/conversations', {
127
+ params: { limit: 50, order: 'desc' },
128
+ });
129
+ });
130
+
131
+ it('replaces the list on subsequent fetches', async () => {
132
+ const http = makeFakeHttp();
133
+ http.get
134
+ .mockResolvedValueOnce(makeRawList([makeRawConversation({ id: 'a' })]))
135
+ .mockResolvedValueOnce(
136
+ makeRawList([
137
+ makeRawConversation({ id: 'b' }),
138
+ makeRawConversation({ id: 'c' }),
139
+ ]),
140
+ );
141
+
142
+ const conv = useConversations({
143
+ httpClient: asHttp(http),
144
+ path: '/conversations',
145
+ });
146
+ await conv.fetch();
147
+ await conv.fetch();
148
+
149
+ expect(conv.conversations.value.map((c) => c.id)).toEqual(['b', 'c']);
150
+ });
151
+
152
+ it('sets hasMore=true and stores the cursor when has_more is true', async () => {
153
+ const http = makeFakeHttp();
154
+ http.get.mockResolvedValue(
155
+ makeRawList([makeRawConversation({ id: 'a' })], {
156
+ hasMore: true,
157
+ lastId: 'a',
158
+ }),
159
+ );
160
+
161
+ const conv = useConversations({
162
+ httpClient: asHttp(http),
163
+ path: '/conversations',
164
+ });
165
+ await conv.fetch();
166
+
167
+ expect(conv.hasMore.value).toBe(true);
168
+ });
169
+
170
+ it('sets hasMore=false when has_more is false', async () => {
171
+ const http = makeFakeHttp();
172
+ http.get.mockResolvedValue(
173
+ makeRawList([makeRawConversation({ id: 'a' })]),
174
+ );
175
+
176
+ const conv = useConversations({
177
+ httpClient: asHttp(http),
178
+ path: '/conversations',
179
+ });
180
+ await conv.fetch();
181
+
182
+ expect(conv.hasMore.value).toBe(false);
183
+ });
184
+
185
+ it('toggles isLoading around the call', async () => {
186
+ const http = makeFakeHttp();
187
+ let resolve!: (value: RawConversationList) => void;
188
+ http.get.mockReturnValue(new Promise((r) => (resolve = r)));
189
+
190
+ const conv = useConversations({
191
+ httpClient: asHttp(http),
192
+ path: '/conversations',
193
+ });
194
+ const promise = conv.fetch();
195
+
196
+ await nextTick();
197
+ expect(conv.isLoading.value).toBe(true);
198
+
199
+ resolve(makeRawList([]));
200
+ await promise;
201
+
202
+ expect(conv.isLoading.value).toBe(false);
203
+ });
204
+
205
+ it('records and re-throws errors', async () => {
206
+ const http = makeFakeHttp();
207
+ http.get.mockRejectedValue(new Error('boom'));
208
+
209
+ const conv = useConversations({
210
+ httpClient: asHttp(http),
211
+ path: '/conversations',
212
+ });
213
+
214
+ await expect(conv.fetch()).rejects.toThrow('boom');
215
+ expect(conv.error.value?.message).toBe('boom');
216
+ expect(conv.isLoading.value).toBe(false);
217
+ });
218
+ });
219
+
220
+ describe('fetchMore', () => {
221
+ it('appends the next page using the stored cursor', async () => {
222
+ const http = makeFakeHttp();
223
+ http.get
224
+ .mockResolvedValueOnce(
225
+ makeRawList([makeRawConversation({ id: 'a' })], {
226
+ hasMore: true,
227
+ lastId: 'a',
228
+ }),
229
+ )
230
+ .mockResolvedValueOnce(
231
+ makeRawList([makeRawConversation({ id: 'b' })], {
232
+ hasMore: false,
233
+ }),
234
+ );
235
+
236
+ const conv = useConversations({
237
+ httpClient: asHttp(http),
238
+ path: '/conversations',
239
+ });
240
+ await conv.fetch();
241
+ await conv.fetchMore();
242
+
243
+ expect(http.get).toHaveBeenNthCalledWith(2, '/conversations', {
244
+ params: { limit: 20, order: 'desc', after: 'a' },
245
+ });
246
+ expect(conv.conversations.value.map((c) => c.id)).toEqual(['a', 'b']);
247
+ expect(conv.hasMore.value).toBe(false);
248
+ });
249
+
250
+ it('is a no-op when hasMore is false', async () => {
251
+ const http = makeFakeHttp();
252
+ http.get.mockResolvedValue(
253
+ makeRawList([makeRawConversation({ id: 'a' })]),
254
+ );
255
+
256
+ const conv = useConversations({
257
+ httpClient: asHttp(http),
258
+ path: '/conversations',
259
+ });
260
+ await conv.fetch();
261
+ await conv.fetchMore();
262
+
263
+ expect(http.get).toHaveBeenCalledTimes(1);
264
+ });
265
+ });
266
+
267
+ describe('fetchById', () => {
268
+ it('GETs the conversation path and returns the mapped result', async () => {
269
+ const http = makeFakeHttp();
270
+ http.get.mockResolvedValue(
271
+ makeRawConversation({
272
+ id: 'conv_22',
273
+ metadata: { title: 'far away' },
274
+ created_at: 1_777_456_700,
275
+ updated_at: 1_777_456_701,
276
+ }),
277
+ );
278
+
279
+ const conv = useConversations({
280
+ httpClient: asHttp(http),
281
+ path: '/conversations',
282
+ });
283
+
284
+ const result = await conv.fetchById('conv_22');
285
+
286
+ expect(http.get).toHaveBeenCalledWith('/conversations/conv_22');
287
+ expect(result).toEqual({
288
+ id: 'conv_22',
289
+ title: 'far away',
290
+ createdAt: new Date(1_777_456_700 * 1000),
291
+ updatedAt: new Date(1_777_456_701 * 1000),
292
+ });
293
+ });
294
+
295
+ it('does not modify the list (no insert when missing)', async () => {
296
+ const http = makeFakeHttp();
297
+ http.get
298
+ .mockResolvedValueOnce(makeRawList([makeRawConversation({ id: 'a' })]))
299
+ .mockResolvedValueOnce(makeRawConversation({ id: 'conv_22' }));
300
+
301
+ const conv = useConversations({
302
+ httpClient: asHttp(http),
303
+ path: '/conversations',
304
+ });
305
+ await conv.fetch();
306
+ await conv.fetchById('conv_22');
307
+
308
+ expect(conv.conversations.value.map((c) => c.id)).toEqual(['a']);
309
+ });
310
+
311
+ it('does not modify the list (no replace when present)', async () => {
312
+ const http = makeFakeHttp();
313
+ http.get
314
+ .mockResolvedValueOnce(
315
+ makeRawList([
316
+ makeRawConversation({ id: 'a', metadata: { title: 'old' } }),
317
+ makeRawConversation({ id: 'b' }),
318
+ ]),
319
+ )
320
+ .mockResolvedValueOnce(
321
+ makeRawConversation({ id: 'a', metadata: { title: 'fresh' } }),
322
+ );
323
+
324
+ const conv = useConversations({
325
+ httpClient: asHttp(http),
326
+ path: '/conversations',
327
+ });
328
+ await conv.fetch();
329
+ const result = await conv.fetchById('a');
330
+
331
+ expect(result.title).toBe('fresh');
332
+ expect(conv.conversations.value[0].title).toBe('old');
333
+ });
334
+ });
335
+
336
+ describe('create', () => {
337
+ it('posts the title under metadata and prepends the result', async () => {
338
+ const http = makeFakeHttp();
339
+ http.get.mockResolvedValue(
340
+ makeRawList([makeRawConversation({ id: 'existing' })]),
341
+ );
342
+ http.post.mockResolvedValue(
343
+ makeRawConversation({ id: 'new', metadata: { title: 'A new chat' } }),
344
+ );
345
+
346
+ const conv = useConversations({
347
+ httpClient: asHttp(http),
348
+ path: '/conversations',
349
+ });
350
+ await conv.fetch();
351
+
352
+ const created = await conv.create({ title: 'A new chat' });
353
+
354
+ expect(http.post).toHaveBeenCalledWith('/conversations', {
355
+ metadata: { title: 'A new chat' },
356
+ });
357
+ expect(created.id).toBe('new');
358
+ expect(conv.conversations.value.map((c) => c.id)).toEqual([
359
+ 'new',
360
+ 'existing',
361
+ ]);
362
+ });
363
+
364
+ it('merges caller-provided metadata into the request body', async () => {
365
+ const http = makeFakeHttp();
366
+ http.post.mockResolvedValue(makeRawConversation({ id: 'new' }));
367
+
368
+ const conv = useConversations({
369
+ httpClient: asHttp(http),
370
+ path: '/conversations',
371
+ });
372
+ await conv.create({
373
+ title: 'hi',
374
+ metadata: { application: 'tiger-chat', custom: 42 },
375
+ });
376
+
377
+ expect(http.post).toHaveBeenCalledWith('/conversations', {
378
+ metadata: { application: 'tiger-chat', custom: 42, title: 'hi' },
379
+ });
380
+ });
381
+
382
+ it('omits title from metadata when not provided', async () => {
383
+ const http = makeFakeHttp();
384
+ http.post.mockResolvedValue(makeRawConversation({ id: 'new' }));
385
+
386
+ const conv = useConversations({
387
+ httpClient: asHttp(http),
388
+ path: '/conversations',
389
+ });
390
+ await conv.create({ metadata: { application: 'tiger-chat' } });
391
+
392
+ expect(http.post).toHaveBeenCalledWith('/conversations', {
393
+ metadata: { application: 'tiger-chat' },
394
+ });
395
+ });
396
+
397
+ it('prepends a placeholder immediately when optimistic and replaces it on success', async () => {
398
+ const http = makeFakeHttp();
399
+ let resolve!: (value: RawConversation) => void;
400
+ http.post.mockReturnValue(new Promise((r) => (resolve = r)));
401
+
402
+ const conv = useConversations({
403
+ httpClient: asHttp(http),
404
+ path: '/conversations',
405
+ });
406
+ const promise = conv.create({ title: 'pending' }, { optimistic: true });
407
+
408
+ await nextTick();
409
+ expect(conv.conversations.value).toHaveLength(1);
410
+ expect(conv.conversations.value[0].title).toBe('pending');
411
+ expect(conv.conversations.value[0].id).toMatch(/^pending-/);
412
+
413
+ resolve(
414
+ makeRawConversation({ id: 'real', metadata: { title: 'pending' } }),
415
+ );
416
+ await promise;
417
+
418
+ expect(conv.conversations.value).toHaveLength(1);
419
+ expect(conv.conversations.value[0].id).toBe('real');
420
+ });
421
+
422
+ it('rolls back the placeholder on failure when optimistic', async () => {
423
+ const http = makeFakeHttp();
424
+ http.get.mockResolvedValue(
425
+ makeRawList([makeRawConversation({ id: 'existing' })]),
426
+ );
427
+ http.post.mockRejectedValue(new Error('nope'));
428
+
429
+ const conv = useConversations({
430
+ httpClient: asHttp(http),
431
+ path: '/conversations',
432
+ });
433
+ await conv.fetch();
434
+
435
+ await expect(
436
+ conv.create({ title: 'x' }, { optimistic: true }),
437
+ ).rejects.toThrow('nope');
438
+ expect(conv.conversations.value.map((c) => c.id)).toEqual(['existing']);
439
+ expect(conv.error.value?.message).toBe('nope');
440
+ });
441
+ });
442
+
443
+ describe('rename', () => {
444
+ it('posts to the conversation path and replaces the entry in place', async () => {
445
+ const http = makeFakeHttp();
446
+ http.get.mockResolvedValue(
447
+ makeRawList([
448
+ makeRawConversation({ id: 'a', metadata: { title: 'old' } }),
449
+ makeRawConversation({ id: 'b' }),
450
+ ]),
451
+ );
452
+ http.post.mockResolvedValue(
453
+ makeRawConversation({ id: 'a', metadata: { title: 'new' } }),
454
+ );
455
+
456
+ const conv = useConversations({
457
+ httpClient: asHttp(http),
458
+ path: '/conversations',
459
+ });
460
+ await conv.fetch();
461
+ await conv.rename('a', 'new');
462
+
463
+ expect(http.post).toHaveBeenCalledWith('/conversations/a', {
464
+ metadata: { title: 'new' },
465
+ });
466
+ expect(conv.conversations.value[0]).toMatchObject({
467
+ id: 'a',
468
+ title: 'new',
469
+ });
470
+ expect(conv.conversations.value[1].id).toBe('b');
471
+ });
472
+
473
+ it('updates the title immediately when optimistic and rolls back on failure', async () => {
474
+ const http = makeFakeHttp();
475
+ http.get.mockResolvedValue(
476
+ makeRawList([
477
+ makeRawConversation({ id: 'a', metadata: { title: 'old' } }),
478
+ ]),
479
+ );
480
+ http.post.mockRejectedValue(new Error('nope'));
481
+
482
+ const conv = useConversations({
483
+ httpClient: asHttp(http),
484
+ path: '/conversations',
485
+ });
486
+ await conv.fetch();
487
+
488
+ await expect(
489
+ conv.rename('a', 'new', { optimistic: true }),
490
+ ).rejects.toThrow('nope');
491
+ expect(conv.conversations.value[0].title).toBe('old');
492
+ });
493
+ });
494
+
495
+ describe('remove', () => {
496
+ it('deletes on the server and removes the entry from the list', async () => {
497
+ const http = makeFakeHttp();
498
+ http.get.mockResolvedValue(
499
+ makeRawList([
500
+ makeRawConversation({ id: 'a' }),
501
+ makeRawConversation({ id: 'b' }),
502
+ ]),
503
+ );
504
+ http.delete.mockResolvedValue(undefined);
505
+
506
+ const conv = useConversations({
507
+ httpClient: asHttp(http),
508
+ path: '/conversations',
509
+ });
510
+ await conv.fetch();
511
+ await conv.remove('a');
512
+
513
+ expect(http.delete).toHaveBeenCalledWith('/conversations/a');
514
+ expect(conv.conversations.value.map((c) => c.id)).toEqual(['b']);
515
+ });
516
+
517
+ it('rolls back when optimistic and the request fails', async () => {
518
+ const http = makeFakeHttp();
519
+ http.get.mockResolvedValue(
520
+ makeRawList([
521
+ makeRawConversation({ id: 'a' }),
522
+ makeRawConversation({ id: 'b' }),
523
+ ]),
524
+ );
525
+ http.delete.mockRejectedValue(new Error('nope'));
526
+
527
+ const conv = useConversations({
528
+ httpClient: asHttp(http),
529
+ path: '/conversations',
530
+ });
531
+ await conv.fetch();
532
+
533
+ await expect(conv.remove('a', { optimistic: true })).rejects.toThrow(
534
+ 'nope',
535
+ );
536
+ expect(conv.conversations.value.map((c) => c.id)).toEqual(['a', 'b']);
537
+ });
538
+ });
539
+
540
+ describe('getById', () => {
541
+ it('returns the matching conversation without fetching', async () => {
542
+ const http = makeFakeHttp();
543
+ http.get.mockResolvedValue(
544
+ makeRawList([makeRawConversation({ id: 'a' })]),
545
+ );
546
+
547
+ const conv = useConversations({
548
+ httpClient: asHttp(http),
549
+ path: '/conversations',
550
+ });
551
+ await conv.fetch();
552
+
553
+ expect(conv.getById('a')?.id).toBe('a');
554
+ expect(conv.getById('missing')).toBeUndefined();
555
+ });
556
+ });
557
+ });