@tambo-ai/react 0.67.1 → 0.69.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 (424) hide show
  1. package/README.md +3 -5
  2. package/dist/context-helpers/context-helpers.test.js +16 -4
  3. package/dist/context-helpers/context-helpers.test.js.map +1 -1
  4. package/dist/context-helpers/current-interactables-context-helper.d.ts +2 -2
  5. package/dist/context-helpers/current-interactables-context-helper.d.ts.map +1 -1
  6. package/dist/context-helpers/current-interactables-context-helper.js +31 -12
  7. package/dist/context-helpers/current-interactables-context-helper.js.map +1 -1
  8. package/dist/context-helpers/registry.d.ts +2 -2
  9. package/dist/context-helpers/registry.d.ts.map +1 -1
  10. package/dist/context-helpers/registry.js.map +1 -1
  11. package/dist/context-helpers/types.d.ts +2 -2
  12. package/dist/context-helpers/types.d.ts.map +1 -1
  13. package/dist/context-helpers/types.js.map +1 -1
  14. package/dist/hoc/with-tambo-interactable.d.ts +50 -4
  15. package/dist/hoc/with-tambo-interactable.d.ts.map +1 -1
  16. package/dist/hoc/with-tambo-interactable.js +20 -5
  17. package/dist/hoc/with-tambo-interactable.js.map +1 -1
  18. package/dist/hooks/use-component-state.d.ts +3 -8
  19. package/dist/hooks/use-component-state.d.ts.map +1 -1
  20. package/dist/hooks/use-component-state.js +8 -0
  21. package/dist/hooks/use-component-state.js.map +1 -1
  22. package/dist/hooks/use-component-state.test.js +37 -0
  23. package/dist/hooks/use-component-state.test.js.map +1 -1
  24. package/dist/hooks/use-message-images.test.js +174 -37
  25. package/dist/hooks/use-message-images.test.js.map +1 -1
  26. package/dist/hooks/use-tambo-threads.js +1 -1
  27. package/dist/hooks/use-tambo-threads.js.map +1 -1
  28. package/dist/hooks/use-tambo-voice.d.ts +1 -1
  29. package/dist/hooks/use-tambo-voice.js +1 -1
  30. package/dist/hooks/use-tambo-voice.js.map +1 -1
  31. package/dist/hooks/use-tambo-voice.test.d.ts +2 -0
  32. package/dist/hooks/use-tambo-voice.test.d.ts.map +1 -0
  33. package/dist/hooks/use-tambo-voice.test.js +239 -0
  34. package/dist/hooks/use-tambo-voice.test.js.map +1 -0
  35. package/dist/index.d.ts +2 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/mcp/elicitation.d.ts.map +1 -1
  39. package/dist/mcp/elicitation.js +12 -0
  40. package/dist/mcp/elicitation.js.map +1 -1
  41. package/dist/mcp/elicitation.test.js +8 -1
  42. package/dist/mcp/elicitation.test.js.map +1 -1
  43. package/dist/mcp/mcp-client.d.ts +6 -10
  44. package/dist/mcp/mcp-client.d.ts.map +1 -1
  45. package/dist/mcp/mcp-client.js.map +1 -1
  46. package/dist/mcp/mcp-constants.d.ts +19 -0
  47. package/dist/mcp/mcp-constants.d.ts.map +1 -0
  48. package/dist/mcp/mcp-constants.js +21 -0
  49. package/dist/mcp/mcp-constants.js.map +1 -0
  50. package/dist/mcp/mcp-hooks.d.ts +21 -40
  51. package/dist/mcp/mcp-hooks.d.ts.map +1 -1
  52. package/dist/mcp/mcp-hooks.js +130 -39
  53. package/dist/mcp/mcp-hooks.js.map +1 -1
  54. package/dist/mcp/mcp-hooks.test.js +431 -5
  55. package/dist/mcp/mcp-hooks.test.js.map +1 -1
  56. package/dist/mcp/tambo-mcp-provider.d.ts +7 -0
  57. package/dist/mcp/tambo-mcp-provider.d.ts.map +1 -1
  58. package/dist/mcp/tambo-mcp-provider.js +205 -155
  59. package/dist/mcp/tambo-mcp-provider.js.map +1 -1
  60. package/dist/mcp/tambo-mcp-provider.test.js +37 -0
  61. package/dist/mcp/tambo-mcp-provider.test.js.map +1 -1
  62. package/dist/model/component-metadata.d.ts +54 -21
  63. package/dist/model/component-metadata.d.ts.map +1 -1
  64. package/dist/model/component-metadata.js.map +1 -1
  65. package/dist/model/tambo-interactable.d.ts +13 -5
  66. package/dist/model/tambo-interactable.d.ts.map +1 -1
  67. package/dist/model/tambo-interactable.js.map +1 -1
  68. package/dist/providers/__tests__/thread-input-resource-resolution.test.d.ts +2 -0
  69. package/dist/providers/__tests__/thread-input-resource-resolution.test.d.ts.map +1 -0
  70. package/dist/providers/__tests__/thread-input-resource-resolution.test.js +592 -0
  71. package/dist/providers/__tests__/thread-input-resource-resolution.test.js.map +1 -0
  72. package/dist/providers/index.d.ts +1 -1
  73. package/dist/providers/index.d.ts.map +1 -1
  74. package/dist/providers/index.js.map +1 -1
  75. package/dist/providers/tambo-client-provider.d.ts +8 -0
  76. package/dist/providers/tambo-client-provider.d.ts.map +1 -1
  77. package/dist/providers/tambo-client-provider.js +10 -11
  78. package/dist/providers/tambo-client-provider.js.map +1 -1
  79. package/dist/providers/tambo-client-provider.test.d.ts +2 -0
  80. package/dist/providers/tambo-client-provider.test.d.ts.map +1 -0
  81. package/dist/providers/tambo-client-provider.test.js +208 -0
  82. package/dist/providers/tambo-client-provider.test.js.map +1 -0
  83. package/dist/providers/tambo-context-attachment-provider.d.ts +34 -92
  84. package/dist/providers/tambo-context-attachment-provider.d.ts.map +1 -1
  85. package/dist/providers/tambo-context-attachment-provider.js +62 -105
  86. package/dist/providers/tambo-context-attachment-provider.js.map +1 -1
  87. package/dist/providers/tambo-context-attachment-provider.test.js +229 -463
  88. package/dist/providers/tambo-context-attachment-provider.test.js.map +1 -1
  89. package/dist/providers/tambo-interactable-provider-partial-updates.test.js +22 -21
  90. package/dist/providers/tambo-interactable-provider-partial-updates.test.js.map +1 -1
  91. package/dist/providers/tambo-interactable-provider.d.ts +5 -2
  92. package/dist/providers/tambo-interactable-provider.d.ts.map +1 -1
  93. package/dist/providers/tambo-interactable-provider.js +126 -17
  94. package/dist/providers/tambo-interactable-provider.js.map +1 -1
  95. package/dist/providers/tambo-interactable-provider.test.js +242 -0
  96. package/dist/providers/tambo-interactable-provider.test.js.map +1 -1
  97. package/dist/providers/tambo-interactables-additional-context.test.js +2 -5
  98. package/dist/providers/tambo-interactables-additional-context.test.js.map +1 -1
  99. package/dist/providers/tambo-provider.d.ts +2 -3
  100. package/dist/providers/tambo-provider.d.ts.map +1 -1
  101. package/dist/providers/tambo-provider.js +6 -5
  102. package/dist/providers/tambo-provider.js.map +1 -1
  103. package/dist/providers/tambo-registry-provider.test.js +16 -0
  104. package/dist/providers/tambo-registry-provider.test.js.map +1 -1
  105. package/dist/providers/tambo-registry-schema-compat.test.js +31 -0
  106. package/dist/providers/tambo-registry-schema-compat.test.js.map +1 -1
  107. package/dist/providers/tambo-thread-input-provider.d.ts +1 -1
  108. package/dist/providers/tambo-thread-input-provider.d.ts.map +1 -1
  109. package/dist/providers/tambo-thread-input-provider.js +26 -4
  110. package/dist/providers/tambo-thread-input-provider.js.map +1 -1
  111. package/dist/providers/tambo-thread-provider-initial-messages.test.js +84 -2
  112. package/dist/providers/tambo-thread-provider-initial-messages.test.js.map +1 -1
  113. package/dist/providers/tambo-thread-provider.d.ts.map +1 -1
  114. package/dist/providers/tambo-thread-provider.js +53 -42
  115. package/dist/providers/tambo-thread-provider.js.map +1 -1
  116. package/dist/providers/tambo-thread-provider.test.js +368 -262
  117. package/dist/providers/tambo-thread-provider.test.js.map +1 -1
  118. package/dist/schema/index.d.ts +1 -1
  119. package/dist/schema/index.d.ts.map +1 -1
  120. package/dist/schema/index.js +2 -1
  121. package/dist/schema/index.js.map +1 -1
  122. package/dist/schema/json-schema.d.ts +7 -0
  123. package/dist/schema/json-schema.d.ts.map +1 -1
  124. package/dist/schema/json-schema.js +40 -29
  125. package/dist/schema/json-schema.js.map +1 -1
  126. package/dist/schema/json-schema.test.d.ts +2 -0
  127. package/dist/schema/json-schema.test.d.ts.map +1 -0
  128. package/dist/schema/json-schema.test.js +204 -0
  129. package/dist/schema/json-schema.test.js.map +1 -0
  130. package/dist/schema/schema.test.js +237 -0
  131. package/dist/schema/schema.test.js.map +1 -1
  132. package/dist/schema/standard-schema.d.ts +1 -0
  133. package/dist/schema/standard-schema.d.ts.map +1 -1
  134. package/dist/schema/standard-schema.js +18 -13
  135. package/dist/schema/standard-schema.js.map +1 -1
  136. package/dist/schema/standard-schema.test.d.ts +2 -0
  137. package/dist/schema/standard-schema.test.d.ts.map +1 -0
  138. package/dist/schema/standard-schema.test.js +165 -0
  139. package/dist/schema/standard-schema.test.js.map +1 -0
  140. package/dist/schema/validate.test.js +149 -0
  141. package/dist/schema/validate.test.js.map +1 -1
  142. package/dist/schema/zod.d.ts +7 -4
  143. package/dist/schema/zod.d.ts.map +1 -1
  144. package/dist/schema/zod.js +65 -22
  145. package/dist/schema/zod.js.map +1 -1
  146. package/dist/schema/zod.test.js +112 -0
  147. package/dist/schema/zod.test.js.map +1 -1
  148. package/dist/setupTests.js +3 -0
  149. package/dist/setupTests.js.map +1 -1
  150. package/dist/testing/tools.d.ts +4 -1
  151. package/dist/testing/tools.d.ts.map +1 -1
  152. package/dist/testing/tools.js +6 -1
  153. package/dist/testing/tools.js.map +1 -1
  154. package/dist/util/generate-component.d.ts.map +1 -1
  155. package/dist/util/generate-component.js +18 -3
  156. package/dist/util/generate-component.js.map +1 -1
  157. package/dist/util/generate-component.test.d.ts +2 -0
  158. package/dist/util/generate-component.test.d.ts.map +1 -0
  159. package/dist/util/generate-component.test.js +340 -0
  160. package/dist/util/generate-component.test.js.map +1 -0
  161. package/dist/util/is-promise.d.ts +9 -0
  162. package/dist/util/is-promise.d.ts.map +1 -0
  163. package/dist/util/is-promise.js +20 -0
  164. package/dist/util/is-promise.js.map +1 -0
  165. package/dist/util/is-promise.test.d.ts +2 -0
  166. package/dist/util/is-promise.test.d.ts.map +1 -0
  167. package/dist/util/is-promise.test.js +48 -0
  168. package/dist/util/is-promise.test.js.map +1 -0
  169. package/dist/util/message-builder.d.ts +3 -1
  170. package/dist/util/message-builder.d.ts.map +1 -1
  171. package/dist/util/message-builder.js +20 -3
  172. package/dist/util/message-builder.js.map +1 -1
  173. package/dist/util/message-builder.test.js +269 -0
  174. package/dist/util/message-builder.test.js.map +1 -1
  175. package/dist/util/query-utils.test.d.ts +2 -0
  176. package/dist/util/query-utils.test.d.ts.map +1 -0
  177. package/dist/util/query-utils.test.js +382 -0
  178. package/dist/util/query-utils.test.js.map +1 -0
  179. package/dist/util/registry-validators.d.ts.map +1 -1
  180. package/dist/util/registry-validators.js +7 -0
  181. package/dist/util/registry-validators.js.map +1 -1
  182. package/dist/util/registry-validators.test.js +57 -0
  183. package/dist/util/registry-validators.test.js.map +1 -1
  184. package/dist/util/registry.d.ts.map +1 -1
  185. package/dist/util/registry.js +9 -0
  186. package/dist/util/registry.js.map +1 -1
  187. package/dist/util/registry.test.js +323 -1
  188. package/dist/util/registry.test.js.map +1 -1
  189. package/dist/util/resource-content-resolver.d.ts +20 -0
  190. package/dist/util/resource-content-resolver.d.ts.map +1 -0
  191. package/dist/util/resource-content-resolver.js +93 -0
  192. package/dist/util/resource-content-resolver.js.map +1 -0
  193. package/dist/util/resource-content-resolver.test.d.ts +2 -0
  194. package/dist/util/resource-content-resolver.test.d.ts.map +1 -0
  195. package/dist/util/resource-content-resolver.test.js +254 -0
  196. package/dist/util/resource-content-resolver.test.js.map +1 -0
  197. package/dist/util/resource-validators.test.d.ts +2 -0
  198. package/dist/util/resource-validators.test.d.ts.map +1 -0
  199. package/dist/util/resource-validators.test.js +90 -0
  200. package/dist/util/resource-validators.test.js.map +1 -0
  201. package/dist/util/tool-caller.d.ts +2 -2
  202. package/dist/util/tool-caller.d.ts.map +1 -1
  203. package/dist/util/tool-caller.js +8 -8
  204. package/dist/util/tool-caller.js.map +1 -1
  205. package/dist/util/validate-component-name.test.d.ts +2 -0
  206. package/dist/util/validate-component-name.test.d.ts.map +1 -0
  207. package/dist/util/validate-component-name.test.js +35 -0
  208. package/dist/util/validate-component-name.test.js.map +1 -0
  209. package/esm/context-helpers/context-helpers.test.js +16 -4
  210. package/esm/context-helpers/context-helpers.test.js.map +1 -1
  211. package/esm/context-helpers/current-interactables-context-helper.d.ts +2 -2
  212. package/esm/context-helpers/current-interactables-context-helper.d.ts.map +1 -1
  213. package/esm/context-helpers/current-interactables-context-helper.js +31 -12
  214. package/esm/context-helpers/current-interactables-context-helper.js.map +1 -1
  215. package/esm/context-helpers/registry.d.ts +2 -2
  216. package/esm/context-helpers/registry.d.ts.map +1 -1
  217. package/esm/context-helpers/registry.js.map +1 -1
  218. package/esm/context-helpers/types.d.ts +2 -2
  219. package/esm/context-helpers/types.d.ts.map +1 -1
  220. package/esm/context-helpers/types.js.map +1 -1
  221. package/esm/hoc/with-tambo-interactable.d.ts +50 -4
  222. package/esm/hoc/with-tambo-interactable.d.ts.map +1 -1
  223. package/esm/hoc/with-tambo-interactable.js +20 -5
  224. package/esm/hoc/with-tambo-interactable.js.map +1 -1
  225. package/esm/hooks/use-component-state.d.ts +3 -8
  226. package/esm/hooks/use-component-state.d.ts.map +1 -1
  227. package/esm/hooks/use-component-state.js +8 -0
  228. package/esm/hooks/use-component-state.js.map +1 -1
  229. package/esm/hooks/use-component-state.test.js +37 -0
  230. package/esm/hooks/use-component-state.test.js.map +1 -1
  231. package/esm/hooks/use-message-images.test.js +174 -37
  232. package/esm/hooks/use-message-images.test.js.map +1 -1
  233. package/esm/hooks/use-tambo-threads.js +1 -1
  234. package/esm/hooks/use-tambo-threads.js.map +1 -1
  235. package/esm/hooks/use-tambo-voice.d.ts +1 -1
  236. package/esm/hooks/use-tambo-voice.js +1 -1
  237. package/esm/hooks/use-tambo-voice.js.map +1 -1
  238. package/esm/hooks/use-tambo-voice.test.d.ts +2 -0
  239. package/esm/hooks/use-tambo-voice.test.d.ts.map +1 -0
  240. package/esm/hooks/use-tambo-voice.test.js +234 -0
  241. package/esm/hooks/use-tambo-voice.test.js.map +1 -0
  242. package/esm/index.d.ts +2 -2
  243. package/esm/index.d.ts.map +1 -1
  244. package/esm/index.js.map +1 -1
  245. package/esm/mcp/elicitation.d.ts.map +1 -1
  246. package/esm/mcp/elicitation.js +12 -0
  247. package/esm/mcp/elicitation.js.map +1 -1
  248. package/esm/mcp/elicitation.test.js +8 -1
  249. package/esm/mcp/elicitation.test.js.map +1 -1
  250. package/esm/mcp/mcp-client.d.ts +6 -10
  251. package/esm/mcp/mcp-client.d.ts.map +1 -1
  252. package/esm/mcp/mcp-client.js.map +1 -1
  253. package/esm/mcp/mcp-constants.d.ts +19 -0
  254. package/esm/mcp/mcp-constants.d.ts.map +1 -0
  255. package/esm/mcp/mcp-constants.js +18 -0
  256. package/esm/mcp/mcp-constants.js.map +1 -0
  257. package/esm/mcp/mcp-hooks.d.ts +21 -40
  258. package/esm/mcp/mcp-hooks.d.ts.map +1 -1
  259. package/esm/mcp/mcp-hooks.js +97 -40
  260. package/esm/mcp/mcp-hooks.js.map +1 -1
  261. package/esm/mcp/mcp-hooks.test.js +431 -5
  262. package/esm/mcp/mcp-hooks.test.js.map +1 -1
  263. package/esm/mcp/tambo-mcp-provider.d.ts +7 -0
  264. package/esm/mcp/tambo-mcp-provider.d.ts.map +1 -1
  265. package/esm/mcp/tambo-mcp-provider.js +204 -154
  266. package/esm/mcp/tambo-mcp-provider.js.map +1 -1
  267. package/esm/mcp/tambo-mcp-provider.test.js +37 -0
  268. package/esm/mcp/tambo-mcp-provider.test.js.map +1 -1
  269. package/esm/model/component-metadata.d.ts +54 -21
  270. package/esm/model/component-metadata.d.ts.map +1 -1
  271. package/esm/model/component-metadata.js.map +1 -1
  272. package/esm/model/tambo-interactable.d.ts +13 -5
  273. package/esm/model/tambo-interactable.d.ts.map +1 -1
  274. package/esm/model/tambo-interactable.js.map +1 -1
  275. package/esm/providers/__tests__/thread-input-resource-resolution.test.d.ts +2 -0
  276. package/esm/providers/__tests__/thread-input-resource-resolution.test.d.ts.map +1 -0
  277. package/esm/providers/__tests__/thread-input-resource-resolution.test.js +587 -0
  278. package/esm/providers/__tests__/thread-input-resource-resolution.test.js.map +1 -0
  279. package/esm/providers/index.d.ts +1 -1
  280. package/esm/providers/index.d.ts.map +1 -1
  281. package/esm/providers/index.js.map +1 -1
  282. package/esm/providers/tambo-client-provider.d.ts +8 -0
  283. package/esm/providers/tambo-client-provider.d.ts.map +1 -1
  284. package/esm/providers/tambo-client-provider.js +11 -12
  285. package/esm/providers/tambo-client-provider.js.map +1 -1
  286. package/esm/providers/tambo-client-provider.test.d.ts +2 -0
  287. package/esm/providers/tambo-client-provider.test.d.ts.map +1 -0
  288. package/esm/providers/tambo-client-provider.test.js +203 -0
  289. package/esm/providers/tambo-client-provider.test.js.map +1 -0
  290. package/esm/providers/tambo-context-attachment-provider.d.ts +34 -92
  291. package/esm/providers/tambo-context-attachment-provider.d.ts.map +1 -1
  292. package/esm/providers/tambo-context-attachment-provider.js +63 -106
  293. package/esm/providers/tambo-context-attachment-provider.js.map +1 -1
  294. package/esm/providers/tambo-context-attachment-provider.test.js +230 -464
  295. package/esm/providers/tambo-context-attachment-provider.test.js.map +1 -1
  296. package/esm/providers/tambo-interactable-provider-partial-updates.test.js +22 -21
  297. package/esm/providers/tambo-interactable-provider-partial-updates.test.js.map +1 -1
  298. package/esm/providers/tambo-interactable-provider.d.ts +5 -2
  299. package/esm/providers/tambo-interactable-provider.d.ts.map +1 -1
  300. package/esm/providers/tambo-interactable-provider.js +126 -17
  301. package/esm/providers/tambo-interactable-provider.js.map +1 -1
  302. package/esm/providers/tambo-interactable-provider.test.js +242 -0
  303. package/esm/providers/tambo-interactable-provider.test.js.map +1 -1
  304. package/esm/providers/tambo-interactables-additional-context.test.js +2 -5
  305. package/esm/providers/tambo-interactables-additional-context.test.js.map +1 -1
  306. package/esm/providers/tambo-provider.d.ts +2 -3
  307. package/esm/providers/tambo-provider.d.ts.map +1 -1
  308. package/esm/providers/tambo-provider.js +6 -5
  309. package/esm/providers/tambo-provider.js.map +1 -1
  310. package/esm/providers/tambo-registry-provider.test.js +16 -0
  311. package/esm/providers/tambo-registry-provider.test.js.map +1 -1
  312. package/esm/providers/tambo-registry-schema-compat.test.js +31 -0
  313. package/esm/providers/tambo-registry-schema-compat.test.js.map +1 -1
  314. package/esm/providers/tambo-thread-input-provider.d.ts +1 -1
  315. package/esm/providers/tambo-thread-input-provider.d.ts.map +1 -1
  316. package/esm/providers/tambo-thread-input-provider.js +26 -4
  317. package/esm/providers/tambo-thread-input-provider.js.map +1 -1
  318. package/esm/providers/tambo-thread-provider-initial-messages.test.js +84 -2
  319. package/esm/providers/tambo-thread-provider-initial-messages.test.js.map +1 -1
  320. package/esm/providers/tambo-thread-provider.d.ts.map +1 -1
  321. package/esm/providers/tambo-thread-provider.js +53 -42
  322. package/esm/providers/tambo-thread-provider.js.map +1 -1
  323. package/esm/providers/tambo-thread-provider.test.js +368 -262
  324. package/esm/providers/tambo-thread-provider.test.js.map +1 -1
  325. package/esm/schema/index.d.ts +1 -1
  326. package/esm/schema/index.d.ts.map +1 -1
  327. package/esm/schema/index.js +1 -1
  328. package/esm/schema/index.js.map +1 -1
  329. package/esm/schema/json-schema.d.ts +7 -0
  330. package/esm/schema/json-schema.d.ts.map +1 -1
  331. package/esm/schema/json-schema.js +11 -1
  332. package/esm/schema/json-schema.js.map +1 -1
  333. package/esm/schema/json-schema.test.d.ts +2 -0
  334. package/esm/schema/json-schema.test.d.ts.map +1 -0
  335. package/esm/schema/json-schema.test.js +202 -0
  336. package/esm/schema/json-schema.test.js.map +1 -0
  337. package/esm/schema/schema.test.js +238 -1
  338. package/esm/schema/schema.test.js.map +1 -1
  339. package/esm/schema/standard-schema.d.ts +1 -0
  340. package/esm/schema/standard-schema.d.ts.map +1 -1
  341. package/esm/schema/standard-schema.js +18 -13
  342. package/esm/schema/standard-schema.js.map +1 -1
  343. package/esm/schema/standard-schema.test.d.ts +2 -0
  344. package/esm/schema/standard-schema.test.d.ts.map +1 -0
  345. package/esm/schema/standard-schema.test.js +130 -0
  346. package/esm/schema/standard-schema.test.js.map +1 -0
  347. package/esm/schema/validate.test.js +149 -0
  348. package/esm/schema/validate.test.js.map +1 -1
  349. package/esm/schema/zod.d.ts +7 -4
  350. package/esm/schema/zod.d.ts.map +1 -1
  351. package/esm/schema/zod.js +65 -22
  352. package/esm/schema/zod.js.map +1 -1
  353. package/esm/schema/zod.test.js +113 -1
  354. package/esm/schema/zod.test.js.map +1 -1
  355. package/esm/setupTests.js +3 -0
  356. package/esm/setupTests.js.map +1 -1
  357. package/esm/testing/tools.d.ts +4 -1
  358. package/esm/testing/tools.d.ts.map +1 -1
  359. package/esm/testing/tools.js +6 -1
  360. package/esm/testing/tools.js.map +1 -1
  361. package/esm/util/generate-component.d.ts.map +1 -1
  362. package/esm/util/generate-component.js +18 -3
  363. package/esm/util/generate-component.js.map +1 -1
  364. package/esm/util/generate-component.test.d.ts +2 -0
  365. package/esm/util/generate-component.test.d.ts.map +1 -0
  366. package/esm/util/generate-component.test.js +302 -0
  367. package/esm/util/generate-component.test.js.map +1 -0
  368. package/esm/util/is-promise.d.ts +9 -0
  369. package/esm/util/is-promise.d.ts.map +1 -0
  370. package/esm/util/is-promise.js +17 -0
  371. package/esm/util/is-promise.js.map +1 -0
  372. package/esm/util/is-promise.test.d.ts +2 -0
  373. package/esm/util/is-promise.test.d.ts.map +1 -0
  374. package/esm/util/is-promise.test.js +46 -0
  375. package/esm/util/is-promise.test.js.map +1 -0
  376. package/esm/util/message-builder.d.ts +3 -1
  377. package/esm/util/message-builder.d.ts.map +1 -1
  378. package/esm/util/message-builder.js +20 -3
  379. package/esm/util/message-builder.js.map +1 -1
  380. package/esm/util/message-builder.test.js +269 -0
  381. package/esm/util/message-builder.test.js.map +1 -1
  382. package/esm/util/query-utils.test.d.ts +2 -0
  383. package/esm/util/query-utils.test.d.ts.map +1 -0
  384. package/esm/util/query-utils.test.js +380 -0
  385. package/esm/util/query-utils.test.js.map +1 -0
  386. package/esm/util/registry-validators.d.ts.map +1 -1
  387. package/esm/util/registry-validators.js +7 -0
  388. package/esm/util/registry-validators.js.map +1 -1
  389. package/esm/util/registry-validators.test.js +57 -0
  390. package/esm/util/registry-validators.test.js.map +1 -1
  391. package/esm/util/registry.d.ts.map +1 -1
  392. package/esm/util/registry.js +9 -0
  393. package/esm/util/registry.js.map +1 -1
  394. package/esm/util/registry.test.js +324 -2
  395. package/esm/util/registry.test.js.map +1 -1
  396. package/esm/util/resource-content-resolver.d.ts +20 -0
  397. package/esm/util/resource-content-resolver.d.ts.map +1 -0
  398. package/esm/util/resource-content-resolver.js +89 -0
  399. package/esm/util/resource-content-resolver.js.map +1 -0
  400. package/esm/util/resource-content-resolver.test.d.ts +2 -0
  401. package/esm/util/resource-content-resolver.test.d.ts.map +1 -0
  402. package/esm/util/resource-content-resolver.test.js +252 -0
  403. package/esm/util/resource-content-resolver.test.js.map +1 -0
  404. package/esm/util/resource-validators.test.d.ts +2 -0
  405. package/esm/util/resource-validators.test.d.ts.map +1 -0
  406. package/esm/util/resource-validators.test.js +88 -0
  407. package/esm/util/resource-validators.test.js.map +1 -0
  408. package/esm/util/tool-caller.d.ts +2 -2
  409. package/esm/util/tool-caller.d.ts.map +1 -1
  410. package/esm/util/tool-caller.js +8 -8
  411. package/esm/util/tool-caller.js.map +1 -1
  412. package/esm/util/validate-component-name.test.d.ts +2 -0
  413. package/esm/util/validate-component-name.test.d.ts.map +1 -0
  414. package/esm/util/validate-component-name.test.js +33 -0
  415. package/esm/util/validate-component-name.test.js.map +1 -0
  416. package/package.json +15 -23
  417. package/dist/schema/alias.d.ts +0 -3
  418. package/dist/schema/alias.d.ts.map +0 -1
  419. package/dist/schema/alias.js +0 -6
  420. package/dist/schema/alias.js.map +0 -1
  421. package/esm/schema/alias.d.ts +0 -3
  422. package/esm/schema/alias.d.ts.map +0 -1
  423. package/esm/schema/alias.js +0 -13
  424. package/esm/schema/alias.js.map +0 -1
@@ -4,15 +4,16 @@ import { useMessageImages } from "./use-message-images";
4
4
  global.crypto = {
5
5
  randomUUID: jest.fn(() => "mock-uuid-" + Math.random()),
6
6
  };
7
- // Mock FileReader
8
- const mockFileReader = {
9
- readAsDataURL: jest.fn(),
10
- onload: null,
11
- onerror: null,
12
- result: "data:image/png;base64,mock-data",
13
- };
14
- global.FileReader = jest.fn(() => {
15
- const reader = { ...mockFileReader };
7
+ // Track FileReader instances for error simulation
8
+ let fileReaderInstances = [];
9
+ // Default FileReader mock that succeeds
10
+ const createSuccessfulFileReader = () => {
11
+ const reader = {
12
+ readAsDataURL: jest.fn(),
13
+ onload: null,
14
+ onerror: null,
15
+ result: "data:image/png;base64,mock-data",
16
+ };
16
17
  reader.readAsDataURL = jest.fn(() => {
17
18
  setTimeout(() => {
18
19
  if (reader.onload) {
@@ -20,45 +21,181 @@ global.FileReader = jest.fn(() => {
20
21
  }
21
22
  }, 0);
22
23
  });
24
+ fileReaderInstances.push(reader);
23
25
  return reader;
24
- });
26
+ };
27
+ // FileReader mock that fails
28
+ const createFailingFileReader = () => {
29
+ const reader = {
30
+ readAsDataURL: jest.fn(),
31
+ onload: null,
32
+ onerror: null,
33
+ result: "",
34
+ };
35
+ reader.readAsDataURL = jest.fn(() => {
36
+ setTimeout(() => {
37
+ if (reader.onerror) {
38
+ reader.onerror(new Error("Failed to read file"));
39
+ }
40
+ }, 0);
41
+ });
42
+ fileReaderInstances.push(reader);
43
+ return reader;
44
+ };
45
+ // Default to successful FileReader
46
+ global.FileReader = jest.fn(() => createSuccessfulFileReader());
25
47
  describe("useMessageImages", () => {
26
48
  beforeEach(() => {
27
49
  jest.clearAllMocks();
50
+ fileReaderInstances = [];
51
+ // Reset to default successful FileReader
52
+ global.FileReader = jest.fn(() => createSuccessfulFileReader());
28
53
  });
29
- it("should initialize with empty images array", () => {
30
- const { result } = renderHook(() => useMessageImages());
31
- expect(result.current.images).toEqual([]);
54
+ describe("Initialization", () => {
55
+ it("should initialize with empty images array", () => {
56
+ const { result } = renderHook(() => useMessageImages());
57
+ expect(result.current.images).toEqual([]);
58
+ });
59
+ it("should expose all management functions", () => {
60
+ const { result } = renderHook(() => useMessageImages());
61
+ expect(typeof result.current.addImage).toBe("function");
62
+ expect(typeof result.current.addImages).toBe("function");
63
+ expect(typeof result.current.removeImage).toBe("function");
64
+ expect(typeof result.current.clearImages).toBe("function");
65
+ });
32
66
  });
33
- it("should reject non-image files", async () => {
34
- const { result } = renderHook(() => useMessageImages());
35
- const mockFile = new File(["test"], "test-document.pdf", {
36
- type: "application/pdf",
67
+ describe("addImage", () => {
68
+ it("should add a valid image file", async () => {
69
+ const { result } = renderHook(() => useMessageImages());
70
+ const mockFile = new File(["image data"], "photo.png", {
71
+ type: "image/png",
72
+ });
73
+ await act(async () => {
74
+ await result.current.addImage(mockFile);
75
+ });
76
+ expect(result.current.images).toHaveLength(1);
77
+ expect(result.current.images[0].name).toBe("photo.png");
78
+ expect(result.current.images[0].type).toBe("image/png");
79
+ expect(result.current.images[0].dataUrl).toBe("data:image/png;base64,mock-data");
80
+ });
81
+ it("should reject non-image files", async () => {
82
+ const { result } = renderHook(() => useMessageImages());
83
+ const mockFile = new File(["test"], "test-document.pdf", {
84
+ type: "application/pdf",
85
+ });
86
+ await expect(result.current.addImage(mockFile)).rejects.toThrow("Only image files are allowed");
87
+ });
88
+ it("should reject when FileReader fails", async () => {
89
+ // Use failing FileReader
90
+ global.FileReader = jest.fn(() => createFailingFileReader());
91
+ const { result } = renderHook(() => useMessageImages());
92
+ const mockFile = new File(["image data"], "photo.png", {
93
+ type: "image/png",
94
+ });
95
+ await expect(result.current.addImage(mockFile)).rejects.toThrow();
37
96
  });
38
- await expect(result.current.addImage(mockFile)).rejects.toThrow("Only image files are allowed");
39
97
  });
40
- it("should clear all images", () => {
41
- const { result } = renderHook(() => useMessageImages());
42
- act(() => {
43
- result.current.clearImages();
98
+ describe("addImages (batch)", () => {
99
+ it("should add multiple valid images at once", async () => {
100
+ const { result } = renderHook(() => useMessageImages());
101
+ const mockFiles = [
102
+ new File(["image1"], "photo1.png", { type: "image/png" }),
103
+ new File(["image2"], "photo2.jpg", { type: "image/jpeg" }),
104
+ new File(["image3"], "photo3.gif", { type: "image/gif" }),
105
+ ];
106
+ await act(async () => {
107
+ await result.current.addImages(mockFiles);
108
+ });
109
+ expect(result.current.images).toHaveLength(3);
110
+ expect(result.current.images[0].name).toBe("photo1.png");
111
+ expect(result.current.images[1].name).toBe("photo2.jpg");
112
+ expect(result.current.images[2].name).toBe("photo3.gif");
113
+ });
114
+ it("should filter non-images from batch and add valid ones", async () => {
115
+ const { result } = renderHook(() => useMessageImages());
116
+ const mockFiles = [
117
+ new File(["image"], "photo.png", { type: "image/png" }),
118
+ new File(["pdf"], "document.pdf", { type: "application/pdf" }),
119
+ new File(["image"], "another.jpg", { type: "image/jpeg" }),
120
+ ];
121
+ await act(async () => {
122
+ await result.current.addImages(mockFiles);
123
+ });
124
+ // Should only add the 2 valid images
125
+ expect(result.current.images).toHaveLength(2);
126
+ expect(result.current.images[0].name).toBe("photo.png");
127
+ expect(result.current.images[1].name).toBe("another.jpg");
128
+ });
129
+ it("should reject batch with zero valid images", async () => {
130
+ const { result } = renderHook(() => useMessageImages());
131
+ const mockFiles = [
132
+ new File(["test"], "document.pdf", { type: "application/pdf" }),
133
+ new File(["test"], "text.txt", { type: "text/plain" }),
134
+ ];
135
+ await expect(result.current.addImages(mockFiles)).rejects.toThrow("No valid image files provided");
44
136
  });
45
- expect(result.current.images).toHaveLength(0);
46
137
  });
47
- it("should handle image validation correctly", () => {
48
- const { result } = renderHook(() => useMessageImages());
49
- // Test that hooks are available
50
- expect(typeof result.current.addImage).toBe("function");
51
- expect(typeof result.current.addImages).toBe("function");
52
- expect(typeof result.current.removeImage).toBe("function");
53
- expect(typeof result.current.clearImages).toBe("function");
138
+ describe("removeImage", () => {
139
+ it("should remove image by id", async () => {
140
+ const { result } = renderHook(() => useMessageImages());
141
+ const mockFile = new File(["image"], "photo.png", { type: "image/png" });
142
+ await act(async () => {
143
+ await result.current.addImage(mockFile);
144
+ });
145
+ const imageId = result.current.images[0].id;
146
+ act(() => {
147
+ result.current.removeImage(imageId);
148
+ });
149
+ expect(result.current.images).toHaveLength(0);
150
+ });
151
+ it("should handle removing non-existent image gracefully", async () => {
152
+ const { result } = renderHook(() => useMessageImages());
153
+ const mockFile = new File(["image"], "photo.png", { type: "image/png" });
154
+ await act(async () => {
155
+ await result.current.addImage(mockFile);
156
+ });
157
+ // Try to remove with a fake ID - should not throw or affect existing images
158
+ act(() => {
159
+ result.current.removeImage("non-existent-id");
160
+ });
161
+ // Original image should still be there
162
+ expect(result.current.images).toHaveLength(1);
163
+ });
164
+ it("should only remove the targeted image", async () => {
165
+ const { result } = renderHook(() => useMessageImages());
166
+ const files = [
167
+ new File(["image1"], "photo1.png", { type: "image/png" }),
168
+ new File(["image2"], "photo2.png", { type: "image/png" }),
169
+ new File(["image3"], "photo3.png", { type: "image/png" }),
170
+ ];
171
+ await act(async () => {
172
+ await result.current.addImages(files);
173
+ });
174
+ const middleImageId = result.current.images[1].id;
175
+ act(() => {
176
+ result.current.removeImage(middleImageId);
177
+ });
178
+ expect(result.current.images).toHaveLength(2);
179
+ expect(result.current.images[0].name).toBe("photo1.png");
180
+ expect(result.current.images[1].name).toBe("photo3.png");
181
+ });
54
182
  });
55
- it("should reject when no valid image files provided to addImages", async () => {
56
- const { result } = renderHook(() => useMessageImages());
57
- const mockFiles = [
58
- new File(["test"], "document.pdf", { type: "application/pdf" }),
59
- new File(["test"], "text.txt", { type: "text/plain" }),
60
- ];
61
- await expect(result.current.addImages(mockFiles)).rejects.toThrow("No valid image files provided");
183
+ describe("clearImages", () => {
184
+ it("should remove all images", async () => {
185
+ const { result } = renderHook(() => useMessageImages());
186
+ const files = [
187
+ new File(["image1"], "photo1.png", { type: "image/png" }),
188
+ new File(["image2"], "photo2.png", { type: "image/png" }),
189
+ ];
190
+ await act(async () => {
191
+ await result.current.addImages(files);
192
+ });
193
+ expect(result.current.images).toHaveLength(2);
194
+ act(() => {
195
+ result.current.clearImages();
196
+ });
197
+ expect(result.current.images).toHaveLength(0);
198
+ });
62
199
  });
63
200
  });
64
201
  //# sourceMappingURL=use-message-images.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-message-images.test.js","sourceRoot":"","sources":["../../src/hooks/use-message-images.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { act, renderHook } from \"@testing-library/react\";\nimport { useMessageImages } from \"./use-message-images\";\n\n// Mock crypto.randomUUID\nglobal.crypto = {\n randomUUID: jest.fn(() => \"mock-uuid-\" + Math.random()),\n} as any;\n\n// Mock FileReader\nconst mockFileReader = {\n readAsDataURL: jest.fn(),\n onload: null as any,\n onerror: null as any,\n result: \"data:image/png;base64,mock-data\",\n};\n\n(global as any).FileReader = jest.fn(() => {\n const reader = { ...mockFileReader };\n reader.readAsDataURL = jest.fn(() => {\n setTimeout(() => {\n if (reader.onload) {\n reader.onload({} as any);\n }\n }, 0);\n });\n return reader;\n});\n\ndescribe(\"useMessageImages\", () => {\n beforeEach(() => {\n jest.clearAllMocks();\n });\n\n it(\"should initialize with empty images array\", () => {\n const { result } = renderHook(() => useMessageImages());\n expect(result.current.images).toEqual([]);\n });\n\n it(\"should reject non-image files\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFile = new File([\"test\"], \"test-document.pdf\", {\n type: \"application/pdf\",\n });\n\n await expect(result.current.addImage(mockFile)).rejects.toThrow(\n \"Only image files are allowed\",\n );\n });\n\n it(\"should clear all images\", () => {\n const { result } = renderHook(() => useMessageImages());\n\n act(() => {\n result.current.clearImages();\n });\n\n expect(result.current.images).toHaveLength(0);\n });\n\n it(\"should handle image validation correctly\", () => {\n const { result } = renderHook(() => useMessageImages());\n\n // Test that hooks are available\n expect(typeof result.current.addImage).toBe(\"function\");\n expect(typeof result.current.addImages).toBe(\"function\");\n expect(typeof result.current.removeImage).toBe(\"function\");\n expect(typeof result.current.clearImages).toBe(\"function\");\n });\n\n it(\"should reject when no valid image files provided to addImages\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFiles = [\n new File([\"test\"], \"document.pdf\", { type: \"application/pdf\" }),\n new File([\"test\"], \"text.txt\", { type: \"text/plain\" }),\n ];\n\n await expect(result.current.addImages(mockFiles)).rejects.toThrow(\n \"No valid image files provided\",\n );\n });\n});\n"]}
1
+ {"version":3,"file":"use-message-images.test.js","sourceRoot":"","sources":["../../src/hooks/use-message-images.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,yBAAyB;AACzB,MAAM,CAAC,MAAM,GAAG;IACd,UAAU,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,YAAY,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;CACjD,CAAC;AAET,kDAAkD;AAClD,IAAI,mBAAmB,GAKjB,EAAE,CAAC;AAET,wCAAwC;AACxC,MAAM,0BAA0B,GAAG,GAAG,EAAE;IACtC,MAAM,MAAM,GAAG;QACb,aAAa,EAAE,IAAI,CAAC,EAAE,EAAE;QACxB,MAAM,EAAE,IAAiC;QACzC,OAAO,EAAE,IAAiC;QAC1C,MAAM,EAAE,iCAAiC;KAC1C,CAAC;IACF,MAAM,CAAC,aAAa,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE;QAClC,UAAU,CAAC,GAAG,EAAE;YACd,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClB,MAAM,CAAC,MAAM,CAAC,EAAS,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC,EAAE,CAAC,CAAC,CAAC;IACR,CAAC,CAAC,CAAC;IACH,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjC,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,6BAA6B;AAC7B,MAAM,uBAAuB,GAAG,GAAG,EAAE;IACnC,MAAM,MAAM,GAAG;QACb,aAAa,EAAE,IAAI,CAAC,EAAE,EAAE;QACxB,MAAM,EAAE,IAAiC;QACzC,OAAO,EAAE,IAAiC;QAC1C,MAAM,EAAE,EAAE;KACX,CAAC;IACF,MAAM,CAAC,aAAa,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE;QAClC,UAAU,CAAC,GAAG,EAAE;YACd,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC,CAAC;YACnD,CAAC;QACH,CAAC,EAAE,CAAC,CAAC,CAAC;IACR,CAAC,CAAC,CAAC;IACH,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjC,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,mCAAmC;AAClC,MAAc,CAAC,UAAU,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,0BAA0B,EAAE,CAAC,CAAC;AAEzE,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,UAAU,CAAC,GAAG,EAAE;QACd,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,mBAAmB,GAAG,EAAE,CAAC;QACzB,yCAAyC;QACxC,MAAc,CAAC,UAAU,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,0BAA0B,EAAE,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;QAC9B,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;YACnD,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACxD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;YAChD,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAAC,CAAC;YAExD,MAAM,CAAC,OAAO,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACxD,MAAM,CAAC,OAAO,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACzD,MAAM,CAAC,OAAO,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC3D,MAAM,CAAC,OAAO,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;QACxB,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;YAC7C,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACxD,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,CAAC,YAAY,CAAC,EAAE,WAAW,EAAE;gBACrD,IAAI,EAAE,WAAW;aAClB,CAAC,CAAC;YAEH,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE;gBACnB,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAC1C,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC9C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACxD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACxD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAC3C,iCAAiC,CAClC,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;YAC7C,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACxD,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,mBAAmB,EAAE;gBACvD,IAAI,EAAE,iBAAiB;aACxB,CAAC,CAAC;YAEH,MAAM,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAC7D,8BAA8B,CAC/B,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;YACnD,yBAAyB;YACxB,MAAc,CAAC,UAAU,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,uBAAuB,EAAE,CAAC,CAAC;YAEtE,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACxD,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,CAAC,YAAY,CAAC,EAAE,WAAW,EAAE;gBACrD,IAAI,EAAE,WAAW;aAClB,CAAC,CAAC;YAEH,MAAM,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACpE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;QACjC,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;YACxD,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACxD,MAAM,SAAS,GAAG;gBAChB,IAAI,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;gBACzD,IAAI,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;gBAC1D,IAAI,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;aAC1D,CAAC;YAEF,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE;gBACnB,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YAC5C,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC9C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACzD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACzD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;YACtE,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACxD,MAAM,SAAS,GAAG;gBAChB,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;gBACvD,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,EAAE,cAAc,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC;gBAC9D,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;aAC3D,CAAC;YAEF,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE;gBACnB,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YAC5C,CAAC,CAAC,CAAC;YAEH,qCAAqC;YACrC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC9C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACxD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;YAC1D,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACxD,MAAM,SAAS,GAAG;gBAChB,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC;gBAC/D,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;aACvD,CAAC;YAEF,MAAM,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAC/D,+BAA+B,CAChC,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;QAC3B,EAAE,CAAC,2BAA2B,EAAE,KAAK,IAAI,EAAE;YACzC,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACxD,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;YAEzE,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE;gBACnB,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAC1C,CAAC,CAAC,CAAC;YAEH,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAE5C,GAAG,CAAC,GAAG,EAAE;gBACP,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;YACtC,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;YACpE,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACxD,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;YAEzE,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE;gBACnB,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAC1C,CAAC,CAAC,CAAC;YAEH,4EAA4E;YAC5E,GAAG,CAAC,GAAG,EAAE;gBACP,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,iBAAiB,CAAC,CAAC;YAChD,CAAC,CAAC,CAAC;YAEH,uCAAuC;YACvC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;YACrD,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACxD,MAAM,KAAK,GAAG;gBACZ,IAAI,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;gBACzD,IAAI,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;gBACzD,IAAI,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;aAC1D,CAAC;YAEF,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE;gBACnB,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACxC,CAAC,CAAC,CAAC;YAEH,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAElD,GAAG,CAAC,GAAG,EAAE;gBACP,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC;YAC5C,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC9C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACzD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;QAC3B,EAAE,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE;YACxC,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACxD,MAAM,KAAK,GAAG;gBACZ,IAAI,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;gBACzD,IAAI,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;aAC1D,CAAC;YAEF,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE;gBACnB,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACxC,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAE9C,GAAG,CAAC,GAAG,EAAE;gBACP,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;YAC/B,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { act, renderHook } from \"@testing-library/react\";\nimport { useMessageImages } from \"./use-message-images\";\n\n// Mock crypto.randomUUID\nglobal.crypto = {\n randomUUID: jest.fn(() => \"mock-uuid-\" + Math.random()),\n} as any;\n\n// Track FileReader instances for error simulation\nlet fileReaderInstances: {\n readAsDataURL: jest.Mock;\n onload: ((e: unknown) => void) | null;\n onerror: ((e: unknown) => void) | null;\n result: string;\n}[] = [];\n\n// Default FileReader mock that succeeds\nconst createSuccessfulFileReader = () => {\n const reader = {\n readAsDataURL: jest.fn(),\n onload: null as ((e: any) => void) | null,\n onerror: null as ((e: any) => void) | null,\n result: \"data:image/png;base64,mock-data\",\n };\n reader.readAsDataURL = jest.fn(() => {\n setTimeout(() => {\n if (reader.onload) {\n reader.onload({} as any);\n }\n }, 0);\n });\n fileReaderInstances.push(reader);\n return reader;\n};\n\n// FileReader mock that fails\nconst createFailingFileReader = () => {\n const reader = {\n readAsDataURL: jest.fn(),\n onload: null as ((e: any) => void) | null,\n onerror: null as ((e: any) => void) | null,\n result: \"\",\n };\n reader.readAsDataURL = jest.fn(() => {\n setTimeout(() => {\n if (reader.onerror) {\n reader.onerror(new Error(\"Failed to read file\"));\n }\n }, 0);\n });\n fileReaderInstances.push(reader);\n return reader;\n};\n\n// Default to successful FileReader\n(global as any).FileReader = jest.fn(() => createSuccessfulFileReader());\n\ndescribe(\"useMessageImages\", () => {\n beforeEach(() => {\n jest.clearAllMocks();\n fileReaderInstances = [];\n // Reset to default successful FileReader\n (global as any).FileReader = jest.fn(() => createSuccessfulFileReader());\n });\n\n describe(\"Initialization\", () => {\n it(\"should initialize with empty images array\", () => {\n const { result } = renderHook(() => useMessageImages());\n expect(result.current.images).toEqual([]);\n });\n\n it(\"should expose all management functions\", () => {\n const { result } = renderHook(() => useMessageImages());\n\n expect(typeof result.current.addImage).toBe(\"function\");\n expect(typeof result.current.addImages).toBe(\"function\");\n expect(typeof result.current.removeImage).toBe(\"function\");\n expect(typeof result.current.clearImages).toBe(\"function\");\n });\n });\n\n describe(\"addImage\", () => {\n it(\"should add a valid image file\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFile = new File([\"image data\"], \"photo.png\", {\n type: \"image/png\",\n });\n\n await act(async () => {\n await result.current.addImage(mockFile);\n });\n\n expect(result.current.images).toHaveLength(1);\n expect(result.current.images[0].name).toBe(\"photo.png\");\n expect(result.current.images[0].type).toBe(\"image/png\");\n expect(result.current.images[0].dataUrl).toBe(\n \"data:image/png;base64,mock-data\",\n );\n });\n\n it(\"should reject non-image files\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFile = new File([\"test\"], \"test-document.pdf\", {\n type: \"application/pdf\",\n });\n\n await expect(result.current.addImage(mockFile)).rejects.toThrow(\n \"Only image files are allowed\",\n );\n });\n\n it(\"should reject when FileReader fails\", async () => {\n // Use failing FileReader\n (global as any).FileReader = jest.fn(() => createFailingFileReader());\n\n const { result } = renderHook(() => useMessageImages());\n const mockFile = new File([\"image data\"], \"photo.png\", {\n type: \"image/png\",\n });\n\n await expect(result.current.addImage(mockFile)).rejects.toThrow();\n });\n });\n\n describe(\"addImages (batch)\", () => {\n it(\"should add multiple valid images at once\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFiles = [\n new File([\"image1\"], \"photo1.png\", { type: \"image/png\" }),\n new File([\"image2\"], \"photo2.jpg\", { type: \"image/jpeg\" }),\n new File([\"image3\"], \"photo3.gif\", { type: \"image/gif\" }),\n ];\n\n await act(async () => {\n await result.current.addImages(mockFiles);\n });\n\n expect(result.current.images).toHaveLength(3);\n expect(result.current.images[0].name).toBe(\"photo1.png\");\n expect(result.current.images[1].name).toBe(\"photo2.jpg\");\n expect(result.current.images[2].name).toBe(\"photo3.gif\");\n });\n\n it(\"should filter non-images from batch and add valid ones\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFiles = [\n new File([\"image\"], \"photo.png\", { type: \"image/png\" }),\n new File([\"pdf\"], \"document.pdf\", { type: \"application/pdf\" }),\n new File([\"image\"], \"another.jpg\", { type: \"image/jpeg\" }),\n ];\n\n await act(async () => {\n await result.current.addImages(mockFiles);\n });\n\n // Should only add the 2 valid images\n expect(result.current.images).toHaveLength(2);\n expect(result.current.images[0].name).toBe(\"photo.png\");\n expect(result.current.images[1].name).toBe(\"another.jpg\");\n });\n\n it(\"should reject batch with zero valid images\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFiles = [\n new File([\"test\"], \"document.pdf\", { type: \"application/pdf\" }),\n new File([\"test\"], \"text.txt\", { type: \"text/plain\" }),\n ];\n\n await expect(result.current.addImages(mockFiles)).rejects.toThrow(\n \"No valid image files provided\",\n );\n });\n });\n\n describe(\"removeImage\", () => {\n it(\"should remove image by id\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFile = new File([\"image\"], \"photo.png\", { type: \"image/png\" });\n\n await act(async () => {\n await result.current.addImage(mockFile);\n });\n\n const imageId = result.current.images[0].id;\n\n act(() => {\n result.current.removeImage(imageId);\n });\n\n expect(result.current.images).toHaveLength(0);\n });\n\n it(\"should handle removing non-existent image gracefully\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFile = new File([\"image\"], \"photo.png\", { type: \"image/png\" });\n\n await act(async () => {\n await result.current.addImage(mockFile);\n });\n\n // Try to remove with a fake ID - should not throw or affect existing images\n act(() => {\n result.current.removeImage(\"non-existent-id\");\n });\n\n // Original image should still be there\n expect(result.current.images).toHaveLength(1);\n });\n\n it(\"should only remove the targeted image\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const files = [\n new File([\"image1\"], \"photo1.png\", { type: \"image/png\" }),\n new File([\"image2\"], \"photo2.png\", { type: \"image/png\" }),\n new File([\"image3\"], \"photo3.png\", { type: \"image/png\" }),\n ];\n\n await act(async () => {\n await result.current.addImages(files);\n });\n\n const middleImageId = result.current.images[1].id;\n\n act(() => {\n result.current.removeImage(middleImageId);\n });\n\n expect(result.current.images).toHaveLength(2);\n expect(result.current.images[0].name).toBe(\"photo1.png\");\n expect(result.current.images[1].name).toBe(\"photo3.png\");\n });\n });\n\n describe(\"clearImages\", () => {\n it(\"should remove all images\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const files = [\n new File([\"image1\"], \"photo1.png\", { type: \"image/png\" }),\n new File([\"image2\"], \"photo2.png\", { type: \"image/png\" }),\n ];\n\n await act(async () => {\n await result.current.addImages(files);\n });\n\n expect(result.current.images).toHaveLength(2);\n\n act(() => {\n result.current.clearImages();\n });\n\n expect(result.current.images).toHaveLength(0);\n });\n });\n});\n"]}
@@ -32,7 +32,7 @@ export function useTamboThreadList({ projectId } = {}, options = {}) {
32
32
  return null;
33
33
  }
34
34
  const threadIter = await client.beta.threads.list(currentProjectId, {
35
- contextKey: contextKey,
35
+ contextKey,
36
36
  });
37
37
  return threadIter;
38
38
  },
@@ -1 +1 @@
1
- {"version":3,"file":"use-tambo-threads.js","sourceRoot":"","sources":["../../src/hooks/use-tambo-threads.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAUpD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAChC,EAAE,SAAS,KAA+B,EAAE,EAC5C,UAEI,EAAE;IAEN,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAChC,MAAM,EAAE,UAAU,EAAE,GAAG,cAAc,EAAE,CAAC;IACxC,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,GAAG,cAAc,EAAE,GAAG,aAAa,CAAC;QAClE,GAAI,OAA8C;QAClD,QAAQ,EAAE,CAAC,WAAW,CAAC;QACvB,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,OAAO,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC;QACtD,CAAC;KACF,CAAC,CAAC;IACH,MAAM,gBAAgB,GAAG,SAAS,IAAI,gBAAgB,CAAC;IAEvD,MAAM,WAAW,GAAG,aAAa,CAAC;QAChC,GAAG,OAAO;QACV,OAAO,EAAE,CAAC,CAAC,gBAAgB;QAC3B,QAAQ,EAAE,CAAC,SAAS,EAAE,gBAAgB,EAAE,UAAU,CAAC;QACnD,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACtB,OAAO,IAAI,CAAC;YACd,CAAC;YACD,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE;gBAClE,UAAU,EAAE,UAAU;aACvB,CAAC,CAAC;YACH,OAAO,UAAU,CAAC;QACpB,CAAC;KACF,CAAC,CAAC;IAEH,OAAO,gBAAgB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,cAAc,EAAE,CAAC;AAC5E,CAAC","sourcesContent":["import type TamboAI from \"@tambo-ai/typescript-sdk\";\nimport { UseQueryOptions } from \"@tanstack/react-query\";\nimport { useTamboThread } from \"../providers\";\nimport { useTamboClient } from \"../providers/tambo-client-provider\";\nimport { useTamboQuery } from \"./react-query-hooks\";\n\ninterface UseTamboThreadListConfig {\n /**\n * The projectId to get the threads for. If not provided, the current project\n * will be used.\n */\n projectId?: string;\n}\n\n/**\n * Get all the threads for the specified project.\n *\n * If contextKey is empty, then all threads for the project will be returned.\n * If contextKey is not empty, then only the threads for the specified context\n * key will be returned. The contextKey is obtained from the TamboThreadProvider\n * via useTamboThread().\n * @param config - The config for the useTamboThreadList hook\n * @param config.projectId - The projectId to get the threads for\n * @returns The threads for the specified project and optional context key\n */\nexport function useTamboThreadList(\n { projectId }: UseTamboThreadListConfig = {},\n options: Partial<\n UseQueryOptions<TamboAI.Beta.Threads.ThreadsOffsetAndLimit | null>\n > = {},\n) {\n const client = useTamboClient();\n const { contextKey } = useTamboThread();\n const { data: queriedProjectId, ...projectIdState } = useTamboQuery({\n ...(options as unknown as UseQueryOptions<string>),\n queryKey: [\"projectId\"],\n queryFn: async () => {\n return (await client.beta.projects.getCurrent()).id;\n },\n });\n const currentProjectId = projectId ?? queriedProjectId;\n\n const threadState = useTamboQuery({\n ...options,\n enabled: !!currentProjectId,\n queryKey: [\"threads\", currentProjectId, contextKey],\n queryFn: async () => {\n if (!currentProjectId) {\n return null;\n }\n const threadIter = await client.beta.threads.list(currentProjectId, {\n contextKey: contextKey,\n });\n return threadIter;\n },\n });\n\n return currentProjectId ? threadState : { data: null, ...projectIdState };\n}\n"]}
1
+ {"version":3,"file":"use-tambo-threads.js","sourceRoot":"","sources":["../../src/hooks/use-tambo-threads.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAUpD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAChC,EAAE,SAAS,KAA+B,EAAE,EAC5C,UAEI,EAAE;IAEN,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAChC,MAAM,EAAE,UAAU,EAAE,GAAG,cAAc,EAAE,CAAC;IACxC,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,GAAG,cAAc,EAAE,GAAG,aAAa,CAAC;QAClE,GAAI,OAA8C;QAClD,QAAQ,EAAE,CAAC,WAAW,CAAC;QACvB,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,OAAO,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC;QACtD,CAAC;KACF,CAAC,CAAC;IACH,MAAM,gBAAgB,GAAG,SAAS,IAAI,gBAAgB,CAAC;IAEvD,MAAM,WAAW,GAAG,aAAa,CAAC;QAChC,GAAG,OAAO;QACV,OAAO,EAAE,CAAC,CAAC,gBAAgB;QAC3B,QAAQ,EAAE,CAAC,SAAS,EAAE,gBAAgB,EAAE,UAAU,CAAC;QACnD,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACtB,OAAO,IAAI,CAAC;YACd,CAAC;YACD,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE;gBAClE,UAAU;aACX,CAAC,CAAC;YACH,OAAO,UAAU,CAAC;QACpB,CAAC;KACF,CAAC,CAAC;IAEH,OAAO,gBAAgB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,cAAc,EAAE,CAAC;AAC5E,CAAC","sourcesContent":["import type TamboAI from \"@tambo-ai/typescript-sdk\";\nimport { UseQueryOptions } from \"@tanstack/react-query\";\nimport { useTamboThread } from \"../providers\";\nimport { useTamboClient } from \"../providers/tambo-client-provider\";\nimport { useTamboQuery } from \"./react-query-hooks\";\n\ninterface UseTamboThreadListConfig {\n /**\n * The projectId to get the threads for. If not provided, the current project\n * will be used.\n */\n projectId?: string;\n}\n\n/**\n * Get all the threads for the specified project.\n *\n * If contextKey is empty, then all threads for the project will be returned.\n * If contextKey is not empty, then only the threads for the specified context\n * key will be returned. The contextKey is obtained from the TamboThreadProvider\n * via useTamboThread().\n * @param config - The config for the useTamboThreadList hook\n * @param config.projectId - The projectId to get the threads for\n * @returns The threads for the specified project and optional context key\n */\nexport function useTamboThreadList(\n { projectId }: UseTamboThreadListConfig = {},\n options: Partial<\n UseQueryOptions<TamboAI.Beta.Threads.ThreadsOffsetAndLimit | null>\n > = {},\n) {\n const client = useTamboClient();\n const { contextKey } = useTamboThread();\n const { data: queriedProjectId, ...projectIdState } = useTamboQuery({\n ...(options as unknown as UseQueryOptions<string>),\n queryKey: [\"projectId\"],\n queryFn: async () => {\n return (await client.beta.projects.getCurrent()).id;\n },\n });\n const currentProjectId = projectId ?? queriedProjectId;\n\n const threadState = useTamboQuery({\n ...options,\n enabled: !!currentProjectId,\n queryKey: [\"threads\", currentProjectId, contextKey],\n queryFn: async () => {\n if (!currentProjectId) {\n return null;\n }\n const threadIter = await client.beta.threads.list(currentProjectId, {\n contextKey,\n });\n return threadIter;\n },\n });\n\n return currentProjectId ? threadState : { data: null, ...projectIdState };\n}\n"]}
@@ -13,7 +13,7 @@ export declare function useTamboVoice(): {
13
13
  startRecording: () => void;
14
14
  stopRecording: () => void;
15
15
  isRecording: boolean;
16
- mediaAccessError: string;
16
+ mediaAccessError: string | null;
17
17
  isTranscribing: boolean;
18
18
  transcript: string | null;
19
19
  transcriptionError: string | null;
@@ -62,7 +62,7 @@ export function useTamboVoice() {
62
62
  startRecording,
63
63
  stopRecording,
64
64
  isRecording,
65
- mediaAccessError: mediaAccessError ?? null,
65
+ mediaAccessError: mediaAccessError === "" ? null : mediaAccessError,
66
66
  isTranscribing: transcriptionMutation.isPending,
67
67
  transcript,
68
68
  transcriptionError: transcriptionMutation.error?.message ?? null,
@@ -1 +1 @@
1
- {"version":3,"file":"use-tambo-voice.js","sourceRoot":"","sources":["../../src/hooks/use-tambo-voice.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAChC,MAAM,EACJ,MAAM,EACN,cAAc,EAAE,mBAAmB,EACnC,aAAa,EAAE,kBAAkB,EACjC,YAAY,EACZ,KAAK,EAAE,gBAAgB,GACxB,GAAG,qBAAqB,CAAC;QACxB,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,KAAK;QACZ,eAAe,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE;KACxC,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,MAAM,KAAK,WAAW,CAAC;IAE3C,MAAM,qBAAqB,GAAG,gBAAgB,CAI5C;QACA,UAAU,EAAE,KAAK,EAAE,OAAe,EAAE,EAAE;YACpC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;YACtC,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACxC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE;gBACnD,IAAI,EAAE,YAAY;aACnB,CAAC,CAAC;YAEH,OAAO,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,SAAS,EAAE,CAAC,aAAqB,EAAE,EAAE;YACnC,aAAa,CAAC,aAAa,CAAC,CAAC;QAC/B,CAAC;KACF,CAAC,CAAC;IAEH,oEAAoE;IACpE,MAAM,gBAAgB,GACpB,MAAM,KAAK,SAAS;QACpB,YAAY;QACZ,CAAC,qBAAqB,CAAC,SAAS;QAChC,CAAC,qBAAqB,CAAC,SAAS,CAAC;IAEnC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,gBAAgB,EAAE,CAAC;YACrB,qBAAqB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC,EAAE,CAAC,gBAAgB,EAAE,YAAY,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAE5D,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;QACtC,IAAI,WAAW;YAAE,OAAO;QAExB,0CAA0C;QAC1C,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,qBAAqB,CAAC,KAAK,EAAE,CAAC,CAAC,oCAAoC;QACnE,mBAAmB,EAAE,CAAC;IACxB,CAAC,EAAE,CAAC,WAAW,EAAE,mBAAmB,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAE9D,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE;QACrC,IAAI,WAAW,EAAE,CAAC;YAChB,kBAAkB,EAAE,CAAC;QACvB,CAAC;IACH,CAAC,EAAE,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAEtC,OAAO;QACL,cAAc;QACd,aAAa;QACb,WAAW;QACX,gBAAgB,EAAE,gBAAgB,IAAI,IAAI;QAC1C,cAAc,EAAE,qBAAqB,CAAC,SAAS;QAC/C,UAAU;QACV,kBAAkB,EAAE,qBAAqB,CAAC,KAAK,EAAE,OAAO,IAAI,IAAI;KACjE,CAAC;AACJ,CAAC","sourcesContent":["import { useCallback, useEffect, useState } from \"react\";\nimport { useReactMediaRecorder } from \"react-media-recorder\";\nimport { useTamboClient } from \"../providers/tambo-client-provider\";\nimport { useTamboMutation } from \"./react-query-hooks\";\n\n/**\n * Exposes functionality to record speech and transcribe it using the Tambo API.\n * @returns An object with:\n * - startRecording: A function to start recording audio and reset the current transcript.\n * - stopRecording: A function to stop recording audio and automatically kick off transcription.\n * - isRecording: A boolean indicating if the user is recording audio.\n * - isTranscribing: A boolean indicating if the audio is being transcribed.\n * - transcript: The transcript of the recorded audio.\n * - transcriptionError: An error message if the transcription fails.\n * - mediaAccessError: An error message if microphone access fails.\n */\nexport function useTamboVoice() {\n const [transcript, setTranscript] = useState<string | null>(null);\n const client = useTamboClient();\n const {\n status,\n startRecording: startMediaRecording,\n stopRecording: stopMediaRecording,\n mediaBlobUrl,\n error: mediaAccessError,\n } = useReactMediaRecorder({\n audio: true,\n video: false,\n blobPropertyBag: { type: \"audio/webm\" },\n });\n\n const isRecording = status === \"recording\";\n\n const transcriptionMutation = useTamboMutation<\n string,\n Error,\n string // blobUrl parameter\n >({\n mutationFn: async (blobUrl: string) => {\n const response = await fetch(blobUrl);\n const audioBlob = await response.blob();\n const file = new File([audioBlob], \"recording.webm\", {\n type: \"audio/webm\",\n });\n\n return await client.beta.audio.transcribe({ file });\n },\n onSuccess: (transcription: string) => {\n setTranscript(transcription);\n },\n });\n\n // Trigger transcription when recording stops and we have a blob URL\n const shouldTranscribe =\n status === \"stopped\" &&\n mediaBlobUrl &&\n !transcriptionMutation.isPending &&\n !transcriptionMutation.isSuccess;\n\n useEffect(() => {\n if (shouldTranscribe) {\n transcriptionMutation.mutate(mediaBlobUrl);\n }\n }, [shouldTranscribe, mediaBlobUrl, transcriptionMutation]);\n\n const startRecording = useCallback(() => {\n if (isRecording) return;\n\n // Reset state when starting new recording\n setTranscript(null);\n transcriptionMutation.reset(); // Clear any previous mutation state\n startMediaRecording();\n }, [isRecording, startMediaRecording, transcriptionMutation]);\n\n const stopRecording = useCallback(() => {\n if (isRecording) {\n stopMediaRecording();\n }\n }, [isRecording, stopMediaRecording]);\n\n return {\n startRecording,\n stopRecording,\n isRecording,\n mediaAccessError: mediaAccessError ?? null,\n isTranscribing: transcriptionMutation.isPending,\n transcript,\n transcriptionError: transcriptionMutation.error?.message ?? null,\n };\n}\n"]}
1
+ {"version":3,"file":"use-tambo-voice.js","sourceRoot":"","sources":["../../src/hooks/use-tambo-voice.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAChC,MAAM,EACJ,MAAM,EACN,cAAc,EAAE,mBAAmB,EACnC,aAAa,EAAE,kBAAkB,EACjC,YAAY,EACZ,KAAK,EAAE,gBAAgB,GACxB,GAAG,qBAAqB,CAAC;QACxB,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,KAAK;QACZ,eAAe,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE;KACxC,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,MAAM,KAAK,WAAW,CAAC;IAE3C,MAAM,qBAAqB,GAAG,gBAAgB,CAI5C;QACA,UAAU,EAAE,KAAK,EAAE,OAAe,EAAE,EAAE;YACpC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;YACtC,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACxC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE;gBACnD,IAAI,EAAE,YAAY;aACnB,CAAC,CAAC;YAEH,OAAO,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,SAAS,EAAE,CAAC,aAAqB,EAAE,EAAE;YACnC,aAAa,CAAC,aAAa,CAAC,CAAC;QAC/B,CAAC;KACF,CAAC,CAAC;IAEH,oEAAoE;IACpE,MAAM,gBAAgB,GACpB,MAAM,KAAK,SAAS;QACpB,YAAY;QACZ,CAAC,qBAAqB,CAAC,SAAS;QAChC,CAAC,qBAAqB,CAAC,SAAS,CAAC;IAEnC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,gBAAgB,EAAE,CAAC;YACrB,qBAAqB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC,EAAE,CAAC,gBAAgB,EAAE,YAAY,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAE5D,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;QACtC,IAAI,WAAW;YAAE,OAAO;QAExB,0CAA0C;QAC1C,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,qBAAqB,CAAC,KAAK,EAAE,CAAC,CAAC,oCAAoC;QACnE,mBAAmB,EAAE,CAAC;IACxB,CAAC,EAAE,CAAC,WAAW,EAAE,mBAAmB,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAE9D,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE;QACrC,IAAI,WAAW,EAAE,CAAC;YAChB,kBAAkB,EAAE,CAAC;QACvB,CAAC;IACH,CAAC,EAAE,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAEtC,OAAO;QACL,cAAc;QACd,aAAa;QACb,WAAW;QACX,gBAAgB,EAAE,gBAAgB,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB;QACnE,cAAc,EAAE,qBAAqB,CAAC,SAAS;QAC/C,UAAU;QACV,kBAAkB,EAAE,qBAAqB,CAAC,KAAK,EAAE,OAAO,IAAI,IAAI;KACjE,CAAC;AACJ,CAAC","sourcesContent":["import { useCallback, useEffect, useState } from \"react\";\nimport { useReactMediaRecorder } from \"react-media-recorder\";\nimport { useTamboClient } from \"../providers/tambo-client-provider\";\nimport { useTamboMutation } from \"./react-query-hooks\";\n\n/**\n * Exposes functionality to record speech and transcribe it using the Tambo API.\n * @returns An object with:\n * - startRecording: A function to start recording audio and reset the current transcript.\n * - stopRecording: A function to stop recording audio and automatically kick off transcription.\n * - isRecording: A boolean indicating if the user is recording audio.\n * - isTranscribing: A boolean indicating if the audio is being transcribed.\n * - transcript: The transcript of the recorded audio.\n * - transcriptionError: An error message if the transcription fails.\n * - mediaAccessError: An error message if microphone access fails.\n */\nexport function useTamboVoice() {\n const [transcript, setTranscript] = useState<string | null>(null);\n const client = useTamboClient();\n const {\n status,\n startRecording: startMediaRecording,\n stopRecording: stopMediaRecording,\n mediaBlobUrl,\n error: mediaAccessError,\n } = useReactMediaRecorder({\n audio: true,\n video: false,\n blobPropertyBag: { type: \"audio/webm\" },\n });\n\n const isRecording = status === \"recording\";\n\n const transcriptionMutation = useTamboMutation<\n string,\n Error,\n string // blobUrl parameter\n >({\n mutationFn: async (blobUrl: string) => {\n const response = await fetch(blobUrl);\n const audioBlob = await response.blob();\n const file = new File([audioBlob], \"recording.webm\", {\n type: \"audio/webm\",\n });\n\n return await client.beta.audio.transcribe({ file });\n },\n onSuccess: (transcription: string) => {\n setTranscript(transcription);\n },\n });\n\n // Trigger transcription when recording stops and we have a blob URL\n const shouldTranscribe =\n status === \"stopped\" &&\n mediaBlobUrl &&\n !transcriptionMutation.isPending &&\n !transcriptionMutation.isSuccess;\n\n useEffect(() => {\n if (shouldTranscribe) {\n transcriptionMutation.mutate(mediaBlobUrl);\n }\n }, [shouldTranscribe, mediaBlobUrl, transcriptionMutation]);\n\n const startRecording = useCallback(() => {\n if (isRecording) return;\n\n // Reset state when starting new recording\n setTranscript(null);\n transcriptionMutation.reset(); // Clear any previous mutation state\n startMediaRecording();\n }, [isRecording, startMediaRecording, transcriptionMutation]);\n\n const stopRecording = useCallback(() => {\n if (isRecording) {\n stopMediaRecording();\n }\n }, [isRecording, stopMediaRecording]);\n\n return {\n startRecording,\n stopRecording,\n isRecording,\n mediaAccessError: mediaAccessError === \"\" ? null : mediaAccessError,\n isTranscribing: transcriptionMutation.isPending,\n transcript,\n transcriptionError: transcriptionMutation.error?.message ?? null,\n };\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=use-tambo-voice.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-tambo-voice.test.d.ts","sourceRoot":"","sources":["../../src/hooks/use-tambo-voice.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,234 @@
1
+ import { act, renderHook, waitFor } from "@testing-library/react";
2
+ import React from "react";
3
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4
+ import { useReactMediaRecorder } from "react-media-recorder";
5
+ import { useTamboVoice } from "./use-tambo-voice";
6
+ import { TamboClientContext } from "../providers/tambo-client-provider";
7
+ // Override the global mock from setupTests.ts with a controllable version
8
+ jest.mock("react-media-recorder", () => ({
9
+ useReactMediaRecorder: jest.fn(),
10
+ }));
11
+ // Mock the client provider
12
+ jest.mock("../providers/tambo-client-provider", () => ({
13
+ ...jest.requireActual("../providers/tambo-client-provider"),
14
+ useTamboClient: jest.fn(),
15
+ }));
16
+ import { useTamboClient } from "../providers/tambo-client-provider";
17
+ // Mock fetch globally
18
+ const mockFetch = jest.fn();
19
+ describe("useTamboVoice", () => {
20
+ let previousFetch;
21
+ let mockStartRecording;
22
+ let mockStopRecording;
23
+ let mockTranscribe;
24
+ let queryClient;
25
+ const createWrapper = () => {
26
+ const mockClient = {
27
+ beta: {
28
+ audio: {
29
+ transcribe: mockTranscribe,
30
+ },
31
+ },
32
+ };
33
+ const Wrapper = ({ children }) => (React.createElement(TamboClientContext.Provider, { value: {
34
+ client: mockClient,
35
+ queryClient,
36
+ isUpdatingToken: false,
37
+ } },
38
+ React.createElement(QueryClientProvider, { client: queryClient }, children)));
39
+ Wrapper.displayName = "TestWrapper";
40
+ return Wrapper;
41
+ };
42
+ const setupMediaRecorderMock = (overrides = {}) => {
43
+ const value = {
44
+ status: "idle",
45
+ startRecording: mockStartRecording,
46
+ stopRecording: mockStopRecording,
47
+ mediaBlobUrl: undefined,
48
+ error: "",
49
+ ...overrides,
50
+ };
51
+ jest
52
+ .mocked(useReactMediaRecorder)
53
+ .mockReturnValue(value);
54
+ return value;
55
+ };
56
+ beforeEach(() => {
57
+ jest.clearAllMocks();
58
+ mockFetch.mockReset();
59
+ previousFetch = global.fetch;
60
+ global.fetch = mockFetch;
61
+ mockStartRecording = jest.fn();
62
+ mockStopRecording = jest.fn();
63
+ mockTranscribe = jest.fn();
64
+ queryClient = new QueryClient({
65
+ defaultOptions: {
66
+ queries: { retry: false },
67
+ mutations: { retry: false },
68
+ },
69
+ });
70
+ // Setup default client mock
71
+ const mockClient = {
72
+ beta: {
73
+ audio: {
74
+ transcribe: mockTranscribe,
75
+ },
76
+ },
77
+ };
78
+ jest
79
+ .mocked(useTamboClient)
80
+ .mockReturnValue(mockClient);
81
+ // Setup default media recorder mock
82
+ setupMediaRecorderMock();
83
+ // Setup default fetch mock
84
+ mockFetch.mockResolvedValue({
85
+ blob: async () => await Promise.resolve(new Blob(["audio data"], { type: "audio/webm" })),
86
+ });
87
+ });
88
+ afterEach(() => {
89
+ global.fetch = previousFetch;
90
+ });
91
+ describe("Initial State", () => {
92
+ it("should initialize with idle state and no transcript", () => {
93
+ const { result } = renderHook(() => useTamboVoice(), {
94
+ wrapper: createWrapper(),
95
+ });
96
+ expect(result.current.isRecording).toBe(false);
97
+ expect(result.current.isTranscribing).toBe(false);
98
+ expect(result.current.transcript).toBeNull();
99
+ expect(result.current.transcriptionError).toBeNull();
100
+ expect(result.current.mediaAccessError).toBeNull();
101
+ });
102
+ });
103
+ describe("Recording Flow", () => {
104
+ it("should expose isRecording=true during active recording", () => {
105
+ setupMediaRecorderMock({ status: "recording" });
106
+ const { result } = renderHook(() => useTamboVoice(), {
107
+ wrapper: createWrapper(),
108
+ });
109
+ expect(result.current.isRecording).toBe(true);
110
+ });
111
+ it("should call startRecording on the media recorder", () => {
112
+ const { result } = renderHook(() => useTamboVoice(), {
113
+ wrapper: createWrapper(),
114
+ });
115
+ act(() => {
116
+ result.current.startRecording();
117
+ });
118
+ expect(mockStartRecording).toHaveBeenCalled();
119
+ });
120
+ it("should call stopRecording on the media recorder when recording", () => {
121
+ setupMediaRecorderMock({ status: "recording" });
122
+ const { result } = renderHook(() => useTamboVoice(), {
123
+ wrapper: createWrapper(),
124
+ });
125
+ act(() => {
126
+ result.current.stopRecording();
127
+ });
128
+ expect(mockStopRecording).toHaveBeenCalled();
129
+ });
130
+ it("should reset transcript when starting a new recording", async () => {
131
+ mockTranscribe.mockResolvedValue("first transcript");
132
+ // Start with completed transcription
133
+ setupMediaRecorderMock({
134
+ status: "stopped",
135
+ mediaBlobUrl: "blob:http://localhost/audio1",
136
+ });
137
+ const { result, rerender } = renderHook(() => useTamboVoice(), {
138
+ wrapper: createWrapper(),
139
+ });
140
+ // Wait for transcription to complete
141
+ await waitFor(() => {
142
+ expect(result.current.transcript).toBe("first transcript");
143
+ });
144
+ // Now simulate starting a new recording
145
+ setupMediaRecorderMock({ status: "idle" });
146
+ rerender();
147
+ act(() => {
148
+ result.current.startRecording();
149
+ });
150
+ expect(result.current.transcript).toBeNull();
151
+ });
152
+ });
153
+ describe("Transcription", () => {
154
+ it("should trigger transcription after recording stops with blob URL", async () => {
155
+ mockTranscribe.mockResolvedValue("Hello world");
156
+ setupMediaRecorderMock({
157
+ status: "stopped",
158
+ mediaBlobUrl: "blob:http://localhost/audio",
159
+ });
160
+ const { result } = renderHook(() => useTamboVoice(), {
161
+ wrapper: createWrapper(),
162
+ });
163
+ await waitFor(() => {
164
+ expect(result.current.transcript).toBe("Hello world");
165
+ });
166
+ expect(mockFetch).toHaveBeenCalledWith("blob:http://localhost/audio");
167
+ expect(mockTranscribe).toHaveBeenCalled();
168
+ });
169
+ it("should expose isTranscribing=true during API call", async () => {
170
+ let resolveTranscription;
171
+ mockTranscribe.mockImplementation(async () => await new Promise((resolve) => {
172
+ resolveTranscription = resolve;
173
+ }));
174
+ setupMediaRecorderMock({
175
+ status: "stopped",
176
+ mediaBlobUrl: "blob:http://localhost/audio",
177
+ });
178
+ const { result } = renderHook(() => useTamboVoice(), {
179
+ wrapper: createWrapper(),
180
+ });
181
+ await waitFor(() => {
182
+ expect(result.current.isTranscribing).toBe(true);
183
+ });
184
+ // Complete the transcription
185
+ act(() => {
186
+ resolveTranscription("transcribed text");
187
+ });
188
+ await waitFor(() => {
189
+ expect(result.current.isTranscribing).toBe(false);
190
+ });
191
+ });
192
+ });
193
+ describe("Error Handling", () => {
194
+ it("should expose mediaAccessError when microphone access fails", () => {
195
+ setupMediaRecorderMock({
196
+ error: "Permission denied",
197
+ });
198
+ const { result } = renderHook(() => useTamboVoice(), {
199
+ wrapper: createWrapper(),
200
+ });
201
+ expect(result.current.mediaAccessError).toBe("Permission denied");
202
+ });
203
+ it("should expose transcriptionError when API call fails", async () => {
204
+ mockTranscribe.mockRejectedValue(new Error("Transcription service unavailable"));
205
+ setupMediaRecorderMock({
206
+ status: "stopped",
207
+ mediaBlobUrl: "blob:http://localhost/audio",
208
+ });
209
+ const { result } = renderHook(() => useTamboVoice(), {
210
+ wrapper: createWrapper(),
211
+ });
212
+ await waitFor(() => {
213
+ expect(result.current.transcriptionError).toBe("Transcription service unavailable");
214
+ });
215
+ expect(result.current.isTranscribing).toBe(false);
216
+ });
217
+ it("should handle blob fetch failure gracefully", async () => {
218
+ mockFetch.mockRejectedValue(new Error("Network error"));
219
+ setupMediaRecorderMock({
220
+ status: "stopped",
221
+ mediaBlobUrl: "blob:http://localhost/audio",
222
+ });
223
+ const { result } = renderHook(() => useTamboVoice(), {
224
+ wrapper: createWrapper(),
225
+ });
226
+ await waitFor(() => {
227
+ expect(result.current.transcriptionError).toBe("Network error");
228
+ });
229
+ expect(result.current.isTranscribing).toBe(false);
230
+ expect(result.current.transcript).toBeNull();
231
+ });
232
+ });
233
+ });
234
+ //# sourceMappingURL=use-tambo-voice.test.js.map