@tambo-ai/react 0.68.0 → 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 (326) hide show
  1. package/README.md +1 -1
  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 -15
  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/hooks/use-message-images.test.js +174 -37
  15. package/dist/hooks/use-message-images.test.js.map +1 -1
  16. package/dist/hooks/use-tambo-voice.d.ts +1 -1
  17. package/dist/hooks/use-tambo-voice.js +1 -1
  18. package/dist/hooks/use-tambo-voice.js.map +1 -1
  19. package/dist/hooks/use-tambo-voice.test.d.ts +2 -0
  20. package/dist/hooks/use-tambo-voice.test.d.ts.map +1 -0
  21. package/dist/hooks/use-tambo-voice.test.js +239 -0
  22. package/dist/hooks/use-tambo-voice.test.js.map +1 -0
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/mcp/elicitation.d.ts.map +1 -1
  27. package/dist/mcp/elicitation.js +12 -0
  28. package/dist/mcp/elicitation.js.map +1 -1
  29. package/dist/mcp/elicitation.test.js +8 -1
  30. package/dist/mcp/elicitation.test.js.map +1 -1
  31. package/dist/mcp/mcp-client.d.ts +6 -10
  32. package/dist/mcp/mcp-client.d.ts.map +1 -1
  33. package/dist/mcp/mcp-client.js.map +1 -1
  34. package/dist/mcp/mcp-hooks.d.ts +12 -60
  35. package/dist/mcp/mcp-hooks.d.ts.map +1 -1
  36. package/dist/mcp/mcp-hooks.js +90 -10
  37. package/dist/mcp/mcp-hooks.js.map +1 -1
  38. package/dist/mcp/mcp-hooks.test.js +423 -0
  39. package/dist/mcp/mcp-hooks.test.js.map +1 -1
  40. package/dist/mcp/tambo-mcp-provider.d.ts.map +1 -1
  41. package/dist/mcp/tambo-mcp-provider.js +3 -0
  42. package/dist/mcp/tambo-mcp-provider.js.map +1 -1
  43. package/dist/mcp/tambo-mcp-provider.test.js +37 -0
  44. package/dist/mcp/tambo-mcp-provider.test.js.map +1 -1
  45. package/dist/model/component-metadata.d.ts +53 -20
  46. package/dist/model/component-metadata.d.ts.map +1 -1
  47. package/dist/model/component-metadata.js.map +1 -1
  48. package/dist/model/tambo-interactable.d.ts +6 -0
  49. package/dist/model/tambo-interactable.d.ts.map +1 -1
  50. package/dist/model/tambo-interactable.js.map +1 -1
  51. package/dist/providers/index.d.ts +1 -1
  52. package/dist/providers/index.d.ts.map +1 -1
  53. package/dist/providers/index.js.map +1 -1
  54. package/dist/providers/tambo-client-provider.d.ts +8 -0
  55. package/dist/providers/tambo-client-provider.d.ts.map +1 -1
  56. package/dist/providers/tambo-client-provider.js +10 -11
  57. package/dist/providers/tambo-client-provider.js.map +1 -1
  58. package/dist/providers/tambo-client-provider.test.d.ts +2 -0
  59. package/dist/providers/tambo-client-provider.test.d.ts.map +1 -0
  60. package/dist/providers/tambo-client-provider.test.js +208 -0
  61. package/dist/providers/tambo-client-provider.test.js.map +1 -0
  62. package/dist/providers/tambo-context-attachment-provider.d.ts +34 -92
  63. package/dist/providers/tambo-context-attachment-provider.d.ts.map +1 -1
  64. package/dist/providers/tambo-context-attachment-provider.js +62 -105
  65. package/dist/providers/tambo-context-attachment-provider.js.map +1 -1
  66. package/dist/providers/tambo-context-attachment-provider.test.js +229 -463
  67. package/dist/providers/tambo-context-attachment-provider.test.js.map +1 -1
  68. package/dist/providers/tambo-interactable-provider.d.ts +2 -0
  69. package/dist/providers/tambo-interactable-provider.d.ts.map +1 -1
  70. package/dist/providers/tambo-interactable-provider.js +29 -4
  71. package/dist/providers/tambo-interactable-provider.js.map +1 -1
  72. package/dist/providers/tambo-interactable-provider.test.js +1 -1
  73. package/dist/providers/tambo-interactable-provider.test.js.map +1 -1
  74. package/dist/providers/tambo-interactables-additional-context.test.js +2 -5
  75. package/dist/providers/tambo-interactables-additional-context.test.js.map +1 -1
  76. package/dist/providers/tambo-provider.d.ts +2 -3
  77. package/dist/providers/tambo-provider.d.ts.map +1 -1
  78. package/dist/providers/tambo-provider.js +5 -6
  79. package/dist/providers/tambo-provider.js.map +1 -1
  80. package/dist/providers/tambo-registry-provider.test.js +16 -0
  81. package/dist/providers/tambo-registry-provider.test.js.map +1 -1
  82. package/dist/providers/tambo-registry-schema-compat.test.js +31 -0
  83. package/dist/providers/tambo-registry-schema-compat.test.js.map +1 -1
  84. package/dist/providers/tambo-thread-input-provider.d.ts +1 -1
  85. package/dist/providers/tambo-thread-input-provider.d.ts.map +1 -1
  86. package/dist/providers/tambo-thread-input-provider.js +5 -1
  87. package/dist/providers/tambo-thread-input-provider.js.map +1 -1
  88. package/dist/providers/tambo-thread-provider-initial-messages.test.js +84 -2
  89. package/dist/providers/tambo-thread-provider-initial-messages.test.js.map +1 -1
  90. package/dist/providers/tambo-thread-provider.d.ts.map +1 -1
  91. package/dist/providers/tambo-thread-provider.js +53 -42
  92. package/dist/providers/tambo-thread-provider.js.map +1 -1
  93. package/dist/providers/tambo-thread-provider.test.js +368 -262
  94. package/dist/providers/tambo-thread-provider.test.js.map +1 -1
  95. package/dist/schema/json-schema.js +29 -29
  96. package/dist/schema/json-schema.js.map +1 -1
  97. package/dist/schema/schema.test.js +237 -0
  98. package/dist/schema/schema.test.js.map +1 -1
  99. package/dist/schema/standard-schema.d.ts +1 -0
  100. package/dist/schema/standard-schema.d.ts.map +1 -1
  101. package/dist/schema/standard-schema.js +18 -13
  102. package/dist/schema/standard-schema.js.map +1 -1
  103. package/dist/schema/standard-schema.test.d.ts +2 -0
  104. package/dist/schema/standard-schema.test.d.ts.map +1 -0
  105. package/dist/schema/standard-schema.test.js +165 -0
  106. package/dist/schema/standard-schema.test.js.map +1 -0
  107. package/dist/schema/validate.test.js +149 -0
  108. package/dist/schema/validate.test.js.map +1 -1
  109. package/dist/schema/zod.d.ts +7 -4
  110. package/dist/schema/zod.d.ts.map +1 -1
  111. package/dist/schema/zod.js +65 -22
  112. package/dist/schema/zod.js.map +1 -1
  113. package/dist/schema/zod.test.js +112 -0
  114. package/dist/schema/zod.test.js.map +1 -1
  115. package/dist/testing/tools.d.ts +4 -1
  116. package/dist/testing/tools.d.ts.map +1 -1
  117. package/dist/testing/tools.js +6 -1
  118. package/dist/testing/tools.js.map +1 -1
  119. package/dist/util/generate-component.d.ts.map +1 -1
  120. package/dist/util/generate-component.js +18 -3
  121. package/dist/util/generate-component.js.map +1 -1
  122. package/dist/util/generate-component.test.d.ts +2 -0
  123. package/dist/util/generate-component.test.d.ts.map +1 -0
  124. package/dist/util/generate-component.test.js +340 -0
  125. package/dist/util/generate-component.test.js.map +1 -0
  126. package/dist/util/is-promise.d.ts +9 -0
  127. package/dist/util/is-promise.d.ts.map +1 -0
  128. package/dist/util/is-promise.js +20 -0
  129. package/dist/util/is-promise.js.map +1 -0
  130. package/dist/util/is-promise.test.d.ts +2 -0
  131. package/dist/util/is-promise.test.d.ts.map +1 -0
  132. package/dist/util/is-promise.test.js +48 -0
  133. package/dist/util/is-promise.test.js.map +1 -0
  134. package/dist/util/query-utils.test.d.ts +2 -0
  135. package/dist/util/query-utils.test.d.ts.map +1 -0
  136. package/dist/util/query-utils.test.js +382 -0
  137. package/dist/util/query-utils.test.js.map +1 -0
  138. package/dist/util/registry-validators.d.ts.map +1 -1
  139. package/dist/util/registry-validators.js +7 -0
  140. package/dist/util/registry-validators.js.map +1 -1
  141. package/dist/util/registry-validators.test.js +57 -0
  142. package/dist/util/registry-validators.test.js.map +1 -1
  143. package/dist/util/registry.d.ts.map +1 -1
  144. package/dist/util/registry.js +9 -0
  145. package/dist/util/registry.js.map +1 -1
  146. package/dist/util/registry.test.js +323 -1
  147. package/dist/util/registry.test.js.map +1 -1
  148. package/dist/util/resource-validators.test.d.ts +2 -0
  149. package/dist/util/resource-validators.test.d.ts.map +1 -0
  150. package/dist/util/resource-validators.test.js +90 -0
  151. package/dist/util/resource-validators.test.js.map +1 -0
  152. package/dist/util/tool-caller.d.ts +2 -2
  153. package/dist/util/tool-caller.d.ts.map +1 -1
  154. package/dist/util/tool-caller.js +8 -8
  155. package/dist/util/tool-caller.js.map +1 -1
  156. package/dist/util/validate-component-name.test.d.ts +2 -0
  157. package/dist/util/validate-component-name.test.d.ts.map +1 -0
  158. package/dist/util/validate-component-name.test.js +35 -0
  159. package/dist/util/validate-component-name.test.js.map +1 -0
  160. package/esm/context-helpers/context-helpers.test.js +16 -4
  161. package/esm/context-helpers/context-helpers.test.js.map +1 -1
  162. package/esm/context-helpers/current-interactables-context-helper.d.ts +2 -2
  163. package/esm/context-helpers/current-interactables-context-helper.d.ts.map +1 -1
  164. package/esm/context-helpers/current-interactables-context-helper.js +31 -15
  165. package/esm/context-helpers/current-interactables-context-helper.js.map +1 -1
  166. package/esm/context-helpers/registry.d.ts +2 -2
  167. package/esm/context-helpers/registry.d.ts.map +1 -1
  168. package/esm/context-helpers/registry.js.map +1 -1
  169. package/esm/context-helpers/types.d.ts +2 -2
  170. package/esm/context-helpers/types.d.ts.map +1 -1
  171. package/esm/context-helpers/types.js.map +1 -1
  172. package/esm/hooks/use-message-images.test.js +174 -37
  173. package/esm/hooks/use-message-images.test.js.map +1 -1
  174. package/esm/hooks/use-tambo-voice.d.ts +1 -1
  175. package/esm/hooks/use-tambo-voice.js +1 -1
  176. package/esm/hooks/use-tambo-voice.js.map +1 -1
  177. package/esm/hooks/use-tambo-voice.test.d.ts +2 -0
  178. package/esm/hooks/use-tambo-voice.test.d.ts.map +1 -0
  179. package/esm/hooks/use-tambo-voice.test.js +234 -0
  180. package/esm/hooks/use-tambo-voice.test.js.map +1 -0
  181. package/esm/index.d.ts +2 -2
  182. package/esm/index.d.ts.map +1 -1
  183. package/esm/index.js.map +1 -1
  184. package/esm/mcp/elicitation.d.ts.map +1 -1
  185. package/esm/mcp/elicitation.js +12 -0
  186. package/esm/mcp/elicitation.js.map +1 -1
  187. package/esm/mcp/elicitation.test.js +8 -1
  188. package/esm/mcp/elicitation.test.js.map +1 -1
  189. package/esm/mcp/mcp-client.d.ts +6 -10
  190. package/esm/mcp/mcp-client.d.ts.map +1 -1
  191. package/esm/mcp/mcp-client.js.map +1 -1
  192. package/esm/mcp/mcp-hooks.d.ts +12 -60
  193. package/esm/mcp/mcp-hooks.d.ts.map +1 -1
  194. package/esm/mcp/mcp-hooks.js +57 -10
  195. package/esm/mcp/mcp-hooks.js.map +1 -1
  196. package/esm/mcp/mcp-hooks.test.js +423 -0
  197. package/esm/mcp/mcp-hooks.test.js.map +1 -1
  198. package/esm/mcp/tambo-mcp-provider.d.ts.map +1 -1
  199. package/esm/mcp/tambo-mcp-provider.js +3 -0
  200. package/esm/mcp/tambo-mcp-provider.js.map +1 -1
  201. package/esm/mcp/tambo-mcp-provider.test.js +37 -0
  202. package/esm/mcp/tambo-mcp-provider.test.js.map +1 -1
  203. package/esm/model/component-metadata.d.ts +53 -20
  204. package/esm/model/component-metadata.d.ts.map +1 -1
  205. package/esm/model/component-metadata.js.map +1 -1
  206. package/esm/model/tambo-interactable.d.ts +6 -0
  207. package/esm/model/tambo-interactable.d.ts.map +1 -1
  208. package/esm/model/tambo-interactable.js.map +1 -1
  209. package/esm/providers/index.d.ts +1 -1
  210. package/esm/providers/index.d.ts.map +1 -1
  211. package/esm/providers/index.js.map +1 -1
  212. package/esm/providers/tambo-client-provider.d.ts +8 -0
  213. package/esm/providers/tambo-client-provider.d.ts.map +1 -1
  214. package/esm/providers/tambo-client-provider.js +11 -12
  215. package/esm/providers/tambo-client-provider.js.map +1 -1
  216. package/esm/providers/tambo-client-provider.test.d.ts +2 -0
  217. package/esm/providers/tambo-client-provider.test.d.ts.map +1 -0
  218. package/esm/providers/tambo-client-provider.test.js +203 -0
  219. package/esm/providers/tambo-client-provider.test.js.map +1 -0
  220. package/esm/providers/tambo-context-attachment-provider.d.ts +34 -92
  221. package/esm/providers/tambo-context-attachment-provider.d.ts.map +1 -1
  222. package/esm/providers/tambo-context-attachment-provider.js +63 -106
  223. package/esm/providers/tambo-context-attachment-provider.js.map +1 -1
  224. package/esm/providers/tambo-context-attachment-provider.test.js +230 -464
  225. package/esm/providers/tambo-context-attachment-provider.test.js.map +1 -1
  226. package/esm/providers/tambo-interactable-provider.d.ts +2 -0
  227. package/esm/providers/tambo-interactable-provider.d.ts.map +1 -1
  228. package/esm/providers/tambo-interactable-provider.js +29 -4
  229. package/esm/providers/tambo-interactable-provider.js.map +1 -1
  230. package/esm/providers/tambo-interactable-provider.test.js +1 -1
  231. package/esm/providers/tambo-interactable-provider.test.js.map +1 -1
  232. package/esm/providers/tambo-interactables-additional-context.test.js +2 -5
  233. package/esm/providers/tambo-interactables-additional-context.test.js.map +1 -1
  234. package/esm/providers/tambo-provider.d.ts +2 -3
  235. package/esm/providers/tambo-provider.d.ts.map +1 -1
  236. package/esm/providers/tambo-provider.js +5 -6
  237. package/esm/providers/tambo-provider.js.map +1 -1
  238. package/esm/providers/tambo-registry-provider.test.js +16 -0
  239. package/esm/providers/tambo-registry-provider.test.js.map +1 -1
  240. package/esm/providers/tambo-registry-schema-compat.test.js +31 -0
  241. package/esm/providers/tambo-registry-schema-compat.test.js.map +1 -1
  242. package/esm/providers/tambo-thread-input-provider.d.ts +1 -1
  243. package/esm/providers/tambo-thread-input-provider.d.ts.map +1 -1
  244. package/esm/providers/tambo-thread-input-provider.js +5 -1
  245. package/esm/providers/tambo-thread-input-provider.js.map +1 -1
  246. package/esm/providers/tambo-thread-provider-initial-messages.test.js +84 -2
  247. package/esm/providers/tambo-thread-provider-initial-messages.test.js.map +1 -1
  248. package/esm/providers/tambo-thread-provider.d.ts.map +1 -1
  249. package/esm/providers/tambo-thread-provider.js +53 -42
  250. package/esm/providers/tambo-thread-provider.js.map +1 -1
  251. package/esm/providers/tambo-thread-provider.test.js +368 -262
  252. package/esm/providers/tambo-thread-provider.test.js.map +1 -1
  253. package/esm/schema/json-schema.js +1 -1
  254. package/esm/schema/json-schema.js.map +1 -1
  255. package/esm/schema/schema.test.js +238 -1
  256. package/esm/schema/schema.test.js.map +1 -1
  257. package/esm/schema/standard-schema.d.ts +1 -0
  258. package/esm/schema/standard-schema.d.ts.map +1 -1
  259. package/esm/schema/standard-schema.js +18 -13
  260. package/esm/schema/standard-schema.js.map +1 -1
  261. package/esm/schema/standard-schema.test.d.ts +2 -0
  262. package/esm/schema/standard-schema.test.d.ts.map +1 -0
  263. package/esm/schema/standard-schema.test.js +130 -0
  264. package/esm/schema/standard-schema.test.js.map +1 -0
  265. package/esm/schema/validate.test.js +149 -0
  266. package/esm/schema/validate.test.js.map +1 -1
  267. package/esm/schema/zod.d.ts +7 -4
  268. package/esm/schema/zod.d.ts.map +1 -1
  269. package/esm/schema/zod.js +65 -22
  270. package/esm/schema/zod.js.map +1 -1
  271. package/esm/schema/zod.test.js +113 -1
  272. package/esm/schema/zod.test.js.map +1 -1
  273. package/esm/testing/tools.d.ts +4 -1
  274. package/esm/testing/tools.d.ts.map +1 -1
  275. package/esm/testing/tools.js +6 -1
  276. package/esm/testing/tools.js.map +1 -1
  277. package/esm/util/generate-component.d.ts.map +1 -1
  278. package/esm/util/generate-component.js +18 -3
  279. package/esm/util/generate-component.js.map +1 -1
  280. package/esm/util/generate-component.test.d.ts +2 -0
  281. package/esm/util/generate-component.test.d.ts.map +1 -0
  282. package/esm/util/generate-component.test.js +302 -0
  283. package/esm/util/generate-component.test.js.map +1 -0
  284. package/esm/util/is-promise.d.ts +9 -0
  285. package/esm/util/is-promise.d.ts.map +1 -0
  286. package/esm/util/is-promise.js +17 -0
  287. package/esm/util/is-promise.js.map +1 -0
  288. package/esm/util/is-promise.test.d.ts +2 -0
  289. package/esm/util/is-promise.test.d.ts.map +1 -0
  290. package/esm/util/is-promise.test.js +46 -0
  291. package/esm/util/is-promise.test.js.map +1 -0
  292. package/esm/util/query-utils.test.d.ts +2 -0
  293. package/esm/util/query-utils.test.d.ts.map +1 -0
  294. package/esm/util/query-utils.test.js +380 -0
  295. package/esm/util/query-utils.test.js.map +1 -0
  296. package/esm/util/registry-validators.d.ts.map +1 -1
  297. package/esm/util/registry-validators.js +7 -0
  298. package/esm/util/registry-validators.js.map +1 -1
  299. package/esm/util/registry-validators.test.js +57 -0
  300. package/esm/util/registry-validators.test.js.map +1 -1
  301. package/esm/util/registry.d.ts.map +1 -1
  302. package/esm/util/registry.js +9 -0
  303. package/esm/util/registry.js.map +1 -1
  304. package/esm/util/registry.test.js +324 -2
  305. package/esm/util/registry.test.js.map +1 -1
  306. package/esm/util/resource-validators.test.d.ts +2 -0
  307. package/esm/util/resource-validators.test.d.ts.map +1 -0
  308. package/esm/util/resource-validators.test.js +88 -0
  309. package/esm/util/resource-validators.test.js.map +1 -0
  310. package/esm/util/tool-caller.d.ts +2 -2
  311. package/esm/util/tool-caller.d.ts.map +1 -1
  312. package/esm/util/tool-caller.js +8 -8
  313. package/esm/util/tool-caller.js.map +1 -1
  314. package/esm/util/validate-component-name.test.d.ts +2 -0
  315. package/esm/util/validate-component-name.test.d.ts.map +1 -0
  316. package/esm/util/validate-component-name.test.js +33 -0
  317. package/esm/util/validate-component-name.test.js.map +1 -0
  318. package/package.json +15 -23
  319. package/dist/schema/alias.d.ts +0 -3
  320. package/dist/schema/alias.d.ts.map +0 -1
  321. package/dist/schema/alias.js +0 -6
  322. package/dist/schema/alias.js.map +0 -1
  323. package/esm/schema/alias.d.ts +0 -3
  324. package/esm/schema/alias.d.ts.map +0 -1
  325. package/esm/schema/alias.js +0 -13
  326. package/esm/schema/alias.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/context-helpers/registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,QAAQ,GAAG,MACnB,GAAG,GACH,IAAI,GACJ,SAAS,GACT,OAAO,CAAC,GAAG,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAEpC;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAChC,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,EAAE,CAAC,CAkB3C"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/context-helpers/registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,QAAQ,GAAG,MACnB,OAAO,GACP,IAAI,GACJ,SAAS,GACT,OAAO,CAAC,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAExC;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAChC,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,EAAE,CAAC,CAkB/C"}
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/context-helpers/registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,OAAiC;IAEjC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEpC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE;QAC/B,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,EAAE,EAAE,CAAC;YACzB,IAAI,KAAK,IAAI,IAAI;gBAAE,OAAO,IAAI,CAAC;YAC/B,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,gCAAgC,IAAI,GAAG,EAAE,KAAK,CAAC,CAAC;YAC9D,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IAEF,OAAO,OAAO,CAAC,MAAM,CAAC,OAAO,CAAqC,CAAC;AACrE,CAAC","sourcesContent":["/**\n * Global context helpers registry.\n * Consumers can add/remove helpers and resolve additional context anywhere.\n */\n\nexport type HelperFn = () =>\n | any\n | null\n | undefined\n | Promise<any | null | undefined>;\n\n/**\n * Resolve all helpers to AdditionalContext entries, skipping null/undefined and errors.\n * @returns The resolved additional context.\n */\nexport async function resolveAdditionalContext(\n helpers: Record<string, HelperFn>,\n): Promise<{ name: string; context: any }[]> {\n const entries = Object.entries(helpers);\n if (entries.length === 0) return [];\n\n const results = await Promise.all(\n entries.map(async ([name, fn]) => {\n try {\n const value = await fn();\n if (value == null) return null;\n return { name, context: value };\n } catch (error) {\n console.error(`Error running context helper ${name}:`, error);\n return null;\n }\n }),\n );\n\n return results.filter(Boolean) as { name: string; context: any }[];\n}\n"]}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/context-helpers/registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,OAAiC;IAEjC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEpC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE;QAC/B,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,EAAE,EAAE,CAAC;YACzB,IAAI,KAAK,IAAI,IAAI;gBAAE,OAAO,IAAI,CAAC;YAC/B,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,gCAAgC,IAAI,GAAG,EAAE,KAAK,CAAC,CAAC;YAC9D,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IAEF,OAAO,OAAO,CAAC,MAAM,CAAC,OAAO,CAAyC,CAAC;AACzE,CAAC","sourcesContent":["/**\n * Global context helpers registry.\n * Consumers can add/remove helpers and resolve additional context anywhere.\n */\n\nexport type HelperFn = () =>\n | unknown\n | null\n | undefined\n | Promise<unknown | null | undefined>;\n\n/**\n * Resolve all helpers to AdditionalContext entries, skipping null/undefined and errors.\n * @returns The resolved additional context.\n */\nexport async function resolveAdditionalContext(\n helpers: Record<string, HelperFn>,\n): Promise<{ name: string; context: unknown }[]> {\n const entries = Object.entries(helpers);\n if (entries.length === 0) return [];\n\n const results = await Promise.all(\n entries.map(async ([name, fn]) => {\n try {\n const value = await fn();\n if (value == null) return null;\n return { name, context: value };\n } catch (error) {\n console.error(`Error running context helper ${name}:`, error);\n return null;\n }\n }),\n );\n\n return results.filter(Boolean) as { name: string; context: unknown }[];\n}\n"]}
@@ -5,13 +5,13 @@ export interface AdditionalContext {
5
5
  /** The name of the context type */
6
6
  name: string;
7
7
  /** The context data */
8
- context: any;
8
+ context: unknown;
9
9
  }
10
10
  /**
11
11
  * A context helper is a function that returns data to include in the context,
12
12
  * or null/undefined to skip including anything.
13
13
  */
14
- export type ContextHelperFn = () => any | null | undefined | Promise<any | null | undefined>;
14
+ export type ContextHelperFn = () => unknown | null | undefined | Promise<unknown | null | undefined>;
15
15
  /**
16
16
  * A collection of context helpers keyed by their context name.
17
17
  * The key becomes the AdditionalContext.name sent to the model.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/context-helpers/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,OAAO,EAAE,GAAG,CAAC;CACd;AAED;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,MAC1B,GAAG,GACH,IAAI,GACJ,SAAS,GACT,OAAO,CAAC,GAAG,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAEpC;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/context-helpers/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,MAC1B,OAAO,GACP,IAAI,GACJ,SAAS,GACT,OAAO,CAAC,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAExC;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/context-helpers/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Interface for additional context that can be added to messages\n */\nexport interface AdditionalContext {\n /** The name of the context type */\n name: string;\n /** The context data */\n context: any;\n}\n\n/**\n * A context helper is a function that returns data to include in the context,\n * or null/undefined to skip including anything.\n */\nexport type ContextHelperFn = () =>\n | any\n | null\n | undefined\n | Promise<any | null | undefined>;\n\n/**\n * A collection of context helpers keyed by their context name.\n * The key becomes the AdditionalContext.name sent to the model.\n */\nexport type ContextHelpers = Record<string, ContextHelperFn>;\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/context-helpers/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Interface for additional context that can be added to messages\n */\nexport interface AdditionalContext {\n /** The name of the context type */\n name: string;\n /** The context data */\n context: unknown;\n}\n\n/**\n * A context helper is a function that returns data to include in the context,\n * or null/undefined to skip including anything.\n */\nexport type ContextHelperFn = () =>\n | unknown\n | null\n | undefined\n | Promise<unknown | null | undefined>;\n\n/**\n * A collection of context helpers keyed by their context name.\n * The key becomes the AdditionalContext.name sent to the model.\n */\nexport type ContextHelpers = Record<string, ContextHelperFn>;\n"]}
@@ -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":"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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// 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"]}
@@ -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