tempest-react-sdk 0.57.0 → 0.59.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 (396) hide show
  1. package/README.md +14 -2
  2. package/bin/lib/css/findings.mjs +1 -0
  3. package/bin/lib/css/semantic.mjs +70 -6
  4. package/bin/lib/css/semantic.test.mjs +52 -1
  5. package/bin/lib/icons/generate.test.mjs +4 -4
  6. package/dist/br/geocode.cjs +1 -1
  7. package/dist/br/geocode.cjs.map +1 -1
  8. package/dist/br/geocode.js +1 -1
  9. package/dist/br/geocode.js.map +1 -1
  10. package/dist/capture/use-barcode-scanner.cjs +1 -1
  11. package/dist/capture/use-barcode-scanner.cjs.map +1 -1
  12. package/dist/capture/use-barcode-scanner.js +1 -1
  13. package/dist/capture/use-barcode-scanner.js.map +1 -1
  14. package/dist/components/AIChat/AIChat.cjs +1 -1
  15. package/dist/components/AIChat/AIChat.cjs.map +1 -1
  16. package/dist/components/AIChat/AIChat.js +1 -1
  17. package/dist/components/AIChat/AIChat.js.map +1 -1
  18. package/dist/components/AIChat/AIChatTurn.cjs +1 -1
  19. package/dist/components/AIChat/AIChatTurn.cjs.map +1 -1
  20. package/dist/components/AIChat/AIChatTurn.js +1 -1
  21. package/dist/components/AIChat/AIChatTurn.js.map +1 -1
  22. package/dist/components/Alert/Alert.module.cjs.map +1 -1
  23. package/dist/components/Alert/Alert.module.js.map +1 -1
  24. package/dist/components/AppBar/use-sticky-body-warning.cjs +1 -1
  25. package/dist/components/AppBar/use-sticky-body-warning.cjs.map +1 -1
  26. package/dist/components/AppBar/use-sticky-body-warning.js +1 -1
  27. package/dist/components/AppBar/use-sticky-body-warning.js.map +1 -1
  28. package/dist/components/AudioPlayer/AudioPlayer.cjs +1 -1
  29. package/dist/components/AudioPlayer/AudioPlayer.cjs.map +1 -1
  30. package/dist/components/AudioPlayer/AudioPlayer.js +2 -2
  31. package/dist/components/AudioPlayer/AudioPlayer.js.map +1 -1
  32. package/dist/components/AudioRecorder/AudioRecorder.cjs +1 -1
  33. package/dist/components/AudioRecorder/AudioRecorder.cjs.map +1 -1
  34. package/dist/components/AudioRecorder/AudioRecorder.js +1 -1
  35. package/dist/components/AudioRecorder/AudioRecorder.js.map +1 -1
  36. package/dist/components/Badge/Badge.module.cjs.map +1 -1
  37. package/dist/components/Badge/Badge.module.js.map +1 -1
  38. package/dist/components/BarList/BarList.module.cjs.map +1 -1
  39. package/dist/components/BarList/BarList.module.js.map +1 -1
  40. package/dist/components/BottomSheet/BottomSheet.module.cjs.map +1 -1
  41. package/dist/components/BottomSheet/BottomSheet.module.js.map +1 -1
  42. package/dist/components/Chat/Chat.cjs +1 -1
  43. package/dist/components/Chat/Chat.cjs.map +1 -1
  44. package/dist/components/Chat/Chat.js +1 -1
  45. package/dist/components/Chat/Chat.js.map +1 -1
  46. package/dist/components/DataTable/DataTable.module.cjs.map +1 -1
  47. package/dist/components/DataTable/DataTable.module.js.map +1 -1
  48. package/dist/components/Dropzone/Dropzone.module.cjs.map +1 -1
  49. package/dist/components/Dropzone/Dropzone.module.js.map +1 -1
  50. package/dist/components/ErrorText/ErrorText.module.cjs.map +1 -1
  51. package/dist/components/ErrorText/ErrorText.module.js.map +1 -1
  52. package/dist/components/FilterBar/FilterBar.cjs +1 -1
  53. package/dist/components/FilterBar/FilterBar.cjs.map +1 -1
  54. package/dist/components/FilterBar/FilterBar.js +1 -1
  55. package/dist/components/FilterBar/FilterBar.js.map +1 -1
  56. package/dist/components/FilterBar/filter-apply.cjs +1 -1
  57. package/dist/components/FilterBar/filter-apply.cjs.map +1 -1
  58. package/dist/components/FilterBar/filter-apply.js +1 -1
  59. package/dist/components/FilterBar/filter-apply.js.map +1 -1
  60. package/dist/components/ImageCropper/ImageCropper.cjs +1 -1
  61. package/dist/components/ImageCropper/ImageCropper.cjs.map +1 -1
  62. package/dist/components/ImageCropper/ImageCropper.js +1 -1
  63. package/dist/components/ImageCropper/ImageCropper.js.map +1 -1
  64. package/dist/components/Markdown/Markdown.module.cjs.map +1 -1
  65. package/dist/components/Markdown/Markdown.module.js.map +1 -1
  66. package/dist/components/PasswordInput/PasswordInput.module.cjs.map +1 -1
  67. package/dist/components/PasswordInput/PasswordInput.module.js.map +1 -1
  68. package/dist/components/SegmentedControl/SegmentedControl.module.cjs.map +1 -1
  69. package/dist/components/SegmentedControl/SegmentedControl.module.js.map +1 -1
  70. package/dist/components/Sidebar/Sidebar.module.cjs.map +1 -1
  71. package/dist/components/Sidebar/Sidebar.module.js.map +1 -1
  72. package/dist/components/StepperInput/StepperInput.module.cjs.map +1 -1
  73. package/dist/components/StepperInput/StepperInput.module.js.map +1 -1
  74. package/dist/components/Tag/Tag.module.cjs.map +1 -1
  75. package/dist/components/Tag/Tag.module.js.map +1 -1
  76. package/dist/components/Timeline/Timeline.module.cjs.map +1 -1
  77. package/dist/components/Timeline/Timeline.module.js.map +1 -1
  78. package/dist/components/Toggle/Toggle.module.cjs.map +1 -1
  79. package/dist/components/Toggle/Toggle.module.js.map +1 -1
  80. package/dist/components/ToggleGroup/ToggleGroup.module.cjs.map +1 -1
  81. package/dist/components/ToggleGroup/ToggleGroup.module.js.map +1 -1
  82. package/dist/components/VideoPlayer/VideoPlayer.cjs +1 -1
  83. package/dist/components/VideoPlayer/VideoPlayer.cjs.map +1 -1
  84. package/dist/components/VideoPlayer/VideoPlayer.js +2 -2
  85. package/dist/components/VideoPlayer/VideoPlayer.js.map +1 -1
  86. package/dist/components/VirtualTable/VirtualTable.cjs +1 -1
  87. package/dist/components/VirtualTable/VirtualTable.cjs.map +1 -1
  88. package/dist/components/VirtualTable/VirtualTable.js +1 -1
  89. package/dist/components/VirtualTable/VirtualTable.js.map +1 -1
  90. package/dist/feature-flags/growthbook-adapter.cjs +1 -1
  91. package/dist/feature-flags/growthbook-adapter.cjs.map +1 -1
  92. package/dist/feature-flags/growthbook-adapter.js +1 -1
  93. package/dist/feature-flags/growthbook-adapter.js.map +1 -1
  94. package/dist/hooks/use-focus-trap.cjs +1 -1
  95. package/dist/hooks/use-focus-trap.cjs.map +1 -1
  96. package/dist/hooks/use-focus-trap.js +1 -1
  97. package/dist/hooks/use-focus-trap.js.map +1 -1
  98. package/dist/hooks/use-push-to-talk.cjs +1 -1
  99. package/dist/hooks/use-push-to-talk.cjs.map +1 -1
  100. package/dist/hooks/use-push-to-talk.js +1 -1
  101. package/dist/hooks/use-push-to-talk.js.map +1 -1
  102. package/dist/http/describe-api-error.cjs +1 -1
  103. package/dist/http/describe-api-error.cjs.map +1 -1
  104. package/dist/http/describe-api-error.js +12 -8
  105. package/dist/http/describe-api-error.js.map +1 -1
  106. package/dist/http/errors.cjs +1 -1
  107. package/dist/http/errors.cjs.map +1 -1
  108. package/dist/http/errors.js +21 -16
  109. package/dist/http/errors.js.map +1 -1
  110. package/dist/http/resumable-upload.cjs +1 -1
  111. package/dist/http/resumable-upload.cjs.map +1 -1
  112. package/dist/http/resumable-upload.js +1 -1
  113. package/dist/http/resumable-upload.js.map +1 -1
  114. package/dist/icons/generated/aliases.cjs +1 -1
  115. package/dist/icons/generated/aliases.cjs.map +1 -1
  116. package/dist/icons/generated/aliases.js +1 -0
  117. package/dist/icons/generated/aliases.js.map +1 -1
  118. package/dist/icons/generated/icon-names.cjs +1 -1
  119. package/dist/icons/generated/icon-names.cjs.map +1 -1
  120. package/dist/icons/generated/icon-names.js +1 -1
  121. package/dist/icons/generated/icon-names.js.map +1 -1
  122. package/dist/icons/generated/loaders.cjs +1 -1
  123. package/dist/icons/generated/loaders.cjs.map +1 -1
  124. package/dist/icons/generated/loaders.js +48 -42
  125. package/dist/icons/generated/loaders.js.map +1 -1
  126. package/dist/icons/generated/shard-02.cjs +1 -1
  127. package/dist/icons/generated/shard-02.cjs.map +1 -1
  128. package/dist/icons/generated/shard-02.js +20 -20
  129. package/dist/icons/generated/shard-02.js.map +1 -1
  130. package/dist/icons/generated/shard-03.cjs +1 -1
  131. package/dist/icons/generated/shard-03.cjs.map +1 -1
  132. package/dist/icons/generated/shard-03.js +41 -41
  133. package/dist/icons/generated/shard-03.js.map +1 -1
  134. package/dist/icons/generated/shard-04.cjs +1 -1
  135. package/dist/icons/generated/shard-04.cjs.map +1 -1
  136. package/dist/icons/generated/shard-04.js +41 -41
  137. package/dist/icons/generated/shard-04.js.map +1 -1
  138. package/dist/icons/generated/shard-05.cjs +1 -1
  139. package/dist/icons/generated/shard-05.cjs.map +1 -1
  140. package/dist/icons/generated/shard-05.js +41 -41
  141. package/dist/icons/generated/shard-05.js.map +1 -1
  142. package/dist/icons/generated/shard-06.cjs +1 -1
  143. package/dist/icons/generated/shard-06.cjs.map +1 -1
  144. package/dist/icons/generated/shard-06.js +41 -41
  145. package/dist/icons/generated/shard-06.js.map +1 -1
  146. package/dist/icons/generated/shard-07.cjs +1 -1
  147. package/dist/icons/generated/shard-07.cjs.map +1 -1
  148. package/dist/icons/generated/shard-07.js +41 -41
  149. package/dist/icons/generated/shard-07.js.map +1 -1
  150. package/dist/icons/generated/shard-08.cjs +1 -1
  151. package/dist/icons/generated/shard-08.cjs.map +1 -1
  152. package/dist/icons/generated/shard-08.js +41 -41
  153. package/dist/icons/generated/shard-08.js.map +1 -1
  154. package/dist/icons/generated/shard-09.cjs +1 -1
  155. package/dist/icons/generated/shard-09.cjs.map +1 -1
  156. package/dist/icons/generated/shard-09.js +41 -41
  157. package/dist/icons/generated/shard-09.js.map +1 -1
  158. package/dist/icons/generated/shard-10.cjs +1 -1
  159. package/dist/icons/generated/shard-10.cjs.map +1 -1
  160. package/dist/icons/generated/shard-10.js +41 -41
  161. package/dist/icons/generated/shard-10.js.map +1 -1
  162. package/dist/icons/generated/shard-11.cjs +1 -1
  163. package/dist/icons/generated/shard-11.cjs.map +1 -1
  164. package/dist/icons/generated/shard-11.js +41 -41
  165. package/dist/icons/generated/shard-11.js.map +1 -1
  166. package/dist/icons/generated/shard-12.cjs +1 -1
  167. package/dist/icons/generated/shard-12.cjs.map +1 -1
  168. package/dist/icons/generated/shard-12.js +41 -41
  169. package/dist/icons/generated/shard-12.js.map +1 -1
  170. package/dist/icons/generated/shard-13.cjs +1 -1
  171. package/dist/icons/generated/shard-13.cjs.map +1 -1
  172. package/dist/icons/generated/shard-13.js +41 -41
  173. package/dist/icons/generated/shard-13.js.map +1 -1
  174. package/dist/icons/generated/shard-14.cjs +1 -1
  175. package/dist/icons/generated/shard-14.cjs.map +1 -1
  176. package/dist/icons/generated/shard-14.js +41 -41
  177. package/dist/icons/generated/shard-14.js.map +1 -1
  178. package/dist/icons/generated/shard-15.cjs +1 -1
  179. package/dist/icons/generated/shard-15.cjs.map +1 -1
  180. package/dist/icons/generated/shard-15.js +41 -41
  181. package/dist/icons/generated/shard-15.js.map +1 -1
  182. package/dist/icons/generated/shard-16.cjs +1 -1
  183. package/dist/icons/generated/shard-16.cjs.map +1 -1
  184. package/dist/icons/generated/shard-16.js +41 -41
  185. package/dist/icons/generated/shard-16.js.map +1 -1
  186. package/dist/icons/generated/shard-17.cjs +1 -1
  187. package/dist/icons/generated/shard-17.cjs.map +1 -1
  188. package/dist/icons/generated/shard-17.js +41 -41
  189. package/dist/icons/generated/shard-17.js.map +1 -1
  190. package/dist/icons/generated/shard-18.cjs +1 -1
  191. package/dist/icons/generated/shard-18.cjs.map +1 -1
  192. package/dist/icons/generated/shard-18.js +41 -41
  193. package/dist/icons/generated/shard-18.js.map +1 -1
  194. package/dist/icons/generated/shard-19.cjs +1 -1
  195. package/dist/icons/generated/shard-19.cjs.map +1 -1
  196. package/dist/icons/generated/shard-19.js +41 -41
  197. package/dist/icons/generated/shard-19.js.map +1 -1
  198. package/dist/icons/generated/shard-20.cjs +1 -1
  199. package/dist/icons/generated/shard-20.cjs.map +1 -1
  200. package/dist/icons/generated/shard-20.js +41 -41
  201. package/dist/icons/generated/shard-20.js.map +1 -1
  202. package/dist/icons/generated/shard-21.cjs +1 -1
  203. package/dist/icons/generated/shard-21.cjs.map +1 -1
  204. package/dist/icons/generated/shard-21.js +41 -41
  205. package/dist/icons/generated/shard-21.js.map +1 -1
  206. package/dist/icons/generated/shard-22.cjs +1 -1
  207. package/dist/icons/generated/shard-22.cjs.map +1 -1
  208. package/dist/icons/generated/shard-22.js +41 -41
  209. package/dist/icons/generated/shard-22.js.map +1 -1
  210. package/dist/icons/generated/shard-23.cjs +1 -1
  211. package/dist/icons/generated/shard-23.cjs.map +1 -1
  212. package/dist/icons/generated/shard-23.js +41 -41
  213. package/dist/icons/generated/shard-23.js.map +1 -1
  214. package/dist/icons/generated/shard-24.cjs +1 -1
  215. package/dist/icons/generated/shard-24.cjs.map +1 -1
  216. package/dist/icons/generated/shard-24.js +41 -41
  217. package/dist/icons/generated/shard-24.js.map +1 -1
  218. package/dist/icons/generated/shard-25.cjs +1 -1
  219. package/dist/icons/generated/shard-25.cjs.map +1 -1
  220. package/dist/icons/generated/shard-25.js +41 -41
  221. package/dist/icons/generated/shard-25.js.map +1 -1
  222. package/dist/icons/generated/shard-26.cjs +1 -1
  223. package/dist/icons/generated/shard-26.cjs.map +1 -1
  224. package/dist/icons/generated/shard-26.js +41 -41
  225. package/dist/icons/generated/shard-26.js.map +1 -1
  226. package/dist/icons/generated/shard-27.cjs +1 -1
  227. package/dist/icons/generated/shard-27.cjs.map +1 -1
  228. package/dist/icons/generated/shard-27.js +41 -41
  229. package/dist/icons/generated/shard-27.js.map +1 -1
  230. package/dist/icons/generated/shard-28.cjs +1 -1
  231. package/dist/icons/generated/shard-28.cjs.map +1 -1
  232. package/dist/icons/generated/shard-28.js +41 -41
  233. package/dist/icons/generated/shard-28.js.map +1 -1
  234. package/dist/icons/generated/shard-29.cjs +1 -1
  235. package/dist/icons/generated/shard-29.cjs.map +1 -1
  236. package/dist/icons/generated/shard-29.js +41 -41
  237. package/dist/icons/generated/shard-29.js.map +1 -1
  238. package/dist/icons/generated/shard-30.cjs +1 -1
  239. package/dist/icons/generated/shard-30.cjs.map +1 -1
  240. package/dist/icons/generated/shard-30.js +41 -41
  241. package/dist/icons/generated/shard-30.js.map +1 -1
  242. package/dist/icons/generated/shard-31.cjs +1 -1
  243. package/dist/icons/generated/shard-31.cjs.map +1 -1
  244. package/dist/icons/generated/shard-31.js +41 -41
  245. package/dist/icons/generated/shard-31.js.map +1 -1
  246. package/dist/icons/generated/shard-32.cjs +1 -1
  247. package/dist/icons/generated/shard-32.cjs.map +1 -1
  248. package/dist/icons/generated/shard-32.js +41 -41
  249. package/dist/icons/generated/shard-32.js.map +1 -1
  250. package/dist/icons/generated/shard-33.cjs +1 -1
  251. package/dist/icons/generated/shard-33.cjs.map +1 -1
  252. package/dist/icons/generated/shard-33.js +41 -41
  253. package/dist/icons/generated/shard-33.js.map +1 -1
  254. package/dist/icons/generated/shard-34.cjs +1 -1
  255. package/dist/icons/generated/shard-34.cjs.map +1 -1
  256. package/dist/icons/generated/shard-34.js +41 -41
  257. package/dist/icons/generated/shard-34.js.map +1 -1
  258. package/dist/icons/generated/shard-35.cjs +1 -1
  259. package/dist/icons/generated/shard-35.cjs.map +1 -1
  260. package/dist/icons/generated/shard-35.js +41 -41
  261. package/dist/icons/generated/shard-35.js.map +1 -1
  262. package/dist/icons/generated/shard-36.cjs +1 -1
  263. package/dist/icons/generated/shard-36.cjs.map +1 -1
  264. package/dist/icons/generated/shard-36.js +41 -41
  265. package/dist/icons/generated/shard-36.js.map +1 -1
  266. package/dist/icons/generated/shard-37.cjs +1 -1
  267. package/dist/icons/generated/shard-37.cjs.map +1 -1
  268. package/dist/icons/generated/shard-37.js +41 -41
  269. package/dist/icons/generated/shard-37.js.map +1 -1
  270. package/dist/icons/generated/shard-38.cjs +1 -1
  271. package/dist/icons/generated/shard-38.cjs.map +1 -1
  272. package/dist/icons/generated/shard-38.js +41 -41
  273. package/dist/icons/generated/shard-38.js.map +1 -1
  274. package/dist/icons/generated/shard-39.cjs +1 -1
  275. package/dist/icons/generated/shard-39.cjs.map +1 -1
  276. package/dist/icons/generated/shard-39.js +41 -41
  277. package/dist/icons/generated/shard-39.js.map +1 -1
  278. package/dist/icons/generated/shard-40.cjs +1 -1
  279. package/dist/icons/generated/shard-40.cjs.map +1 -1
  280. package/dist/icons/generated/shard-40.js +41 -41
  281. package/dist/icons/generated/shard-40.js.map +1 -1
  282. package/dist/icons/generated/shard-41.cjs +1 -1
  283. package/dist/icons/generated/shard-41.cjs.map +1 -1
  284. package/dist/icons/generated/shard-41.js +41 -41
  285. package/dist/icons/generated/shard-41.js.map +1 -1
  286. package/dist/icons/generated/shard-42.cjs +1 -1
  287. package/dist/icons/generated/shard-42.cjs.map +1 -1
  288. package/dist/icons/generated/shard-42.js +41 -41
  289. package/dist/icons/generated/shard-42.js.map +1 -1
  290. package/dist/icons/generated/shard-43.cjs +1 -1
  291. package/dist/icons/generated/shard-43.cjs.map +1 -1
  292. package/dist/icons/generated/shard-43.js +41 -41
  293. package/dist/icons/generated/shard-43.js.map +1 -1
  294. package/dist/icons/generated/shard-44.cjs +1 -1
  295. package/dist/icons/generated/shard-44.cjs.map +1 -1
  296. package/dist/icons/generated/shard-44.js +43 -10
  297. package/dist/icons/generated/shard-44.js.map +1 -1
  298. package/dist/icons/generated/shard-45.cjs +2 -0
  299. package/dist/icons/generated/shard-45.cjs.map +1 -0
  300. package/dist/icons/generated/shard-45.js +15 -0
  301. package/dist/icons/generated/shard-45.js.map +1 -0
  302. package/dist/icons/material-symbols.cjs +1 -1
  303. package/dist/icons/material-symbols.cjs.map +1 -1
  304. package/dist/icons/material-symbols.js +3 -3
  305. package/dist/icons/material-symbols.js.map +1 -1
  306. package/dist/icons.d.ts +4 -4
  307. package/dist/query/foreign-client-warning.cjs +1 -1
  308. package/dist/query/foreign-client-warning.cjs.map +1 -1
  309. package/dist/query/foreign-client-warning.js +1 -1
  310. package/dist/query/foreign-client-warning.js.map +1 -1
  311. package/dist/styles/Alert.css +1 -1
  312. package/dist/styles/Badge.css +1 -1
  313. package/dist/styles/BarList.css +3 -3
  314. package/dist/styles/BottomSheet.css +1 -1
  315. package/dist/styles/DataTable.css +1 -1
  316. package/dist/styles/Dropzone.css +1 -1
  317. package/dist/styles/ErrorText.css +1 -1
  318. package/dist/styles/Markdown.css +1 -1
  319. package/dist/styles/PasswordInput.css +1 -1
  320. package/dist/styles/SegmentedControl.css +1 -1
  321. package/dist/styles/Sidebar.css +2 -2
  322. package/dist/styles/StepperInput.css +1 -1
  323. package/dist/styles/Tag.css +1 -1
  324. package/dist/styles/Timeline.css +1 -1
  325. package/dist/styles/Toggle.css +1 -1
  326. package/dist/styles/ToggleGroup.css +1 -1
  327. package/dist/styles/advanced.css +2 -2
  328. package/dist/styles/core.css +4 -2
  329. package/dist/styles/data.css +6 -6
  330. package/dist/styles/feedback.css +3 -3
  331. package/dist/styles/forms.css +3 -3
  332. package/dist/styles/navigation.css +3 -3
  333. package/dist/styles/overlay.css +1 -1
  334. package/dist/styles/utility.css +1 -1
  335. package/dist/styles.css +1 -1
  336. package/dist/tempest-react-sdk.cjs +1 -1
  337. package/dist/tempest-react-sdk.d.ts +81 -7
  338. package/dist/tempest-react-sdk.js +347 -346
  339. package/dist/theme/create-theme.cjs +3 -3
  340. package/dist/theme/create-theme.cjs.map +1 -1
  341. package/dist/theme/create-theme.js +15 -14
  342. package/dist/theme/create-theme.js.map +1 -1
  343. package/dist/utils/dev-mode.cjs +1 -1
  344. package/dist/utils/dev-mode.cjs.map +1 -1
  345. package/dist/utils/dev-mode.js +7 -2
  346. package/dist/utils/dev-mode.js.map +1 -1
  347. package/dist/utils/json-frame.cjs +1 -1
  348. package/dist/utils/json-frame.cjs.map +1 -1
  349. package/dist/utils/json-frame.js +1 -1
  350. package/dist/utils/json-frame.js.map +1 -1
  351. package/dist/vision/core/providers.cjs +1 -1
  352. package/dist/vision/core/providers.cjs.map +1 -1
  353. package/dist/vision/core/providers.js +22 -3
  354. package/dist/vision/core/providers.js.map +1 -1
  355. package/dist/vision/core/session.cjs +1 -1
  356. package/dist/vision/core/session.cjs.map +1 -1
  357. package/dist/vision/core/session.js +22 -15
  358. package/dist/vision/core/session.js.map +1 -1
  359. package/dist/vision/fusion.cjs.map +1 -1
  360. package/dist/vision/fusion.js.map +1 -1
  361. package/dist/vision/index.cjs +1 -1
  362. package/dist/vision/index.cjs.map +1 -1
  363. package/dist/vision/index.js +17 -16
  364. package/dist/vision/index.js.map +1 -1
  365. package/dist/vision/normalization.cjs +2 -0
  366. package/dist/vision/normalization.cjs.map +1 -0
  367. package/dist/vision/normalization.js +49 -0
  368. package/dist/vision/normalization.js.map +1 -0
  369. package/dist/vision/results.cjs +1 -1
  370. package/dist/vision/results.cjs.map +1 -1
  371. package/dist/vision/results.js +8 -2
  372. package/dist/vision/results.js.map +1 -1
  373. package/dist/vision/tasks/classifier.cjs +1 -1
  374. package/dist/vision/tasks/classifier.cjs.map +1 -1
  375. package/dist/vision/tasks/classifier.js +36 -32
  376. package/dist/vision/tasks/classifier.js.map +1 -1
  377. package/dist/vision.cjs +1 -1
  378. package/dist/vision.d.ts +218 -13
  379. package/dist/vision.js +19 -18
  380. package/dist/vite/tempest-pwa-manifest.cjs +1 -1
  381. package/dist/vite/tempest-pwa-manifest.cjs.map +1 -1
  382. package/dist/vite/tempest-pwa-manifest.js +1 -1
  383. package/dist/vite/tempest-pwa-manifest.js.map +1 -1
  384. package/dist/webrtc/peer-link.cjs +1 -1
  385. package/dist/webrtc/peer-link.cjs.map +1 -1
  386. package/dist/webrtc/peer-link.js +1 -1
  387. package/dist/webrtc/peer-link.js.map +1 -1
  388. package/dist/webrtc/peer-mesh.cjs +1 -1
  389. package/dist/webrtc/peer-mesh.cjs.map +1 -1
  390. package/dist/webrtc/peer-mesh.js +1 -1
  391. package/dist/webrtc/peer-mesh.js.map +1 -1
  392. package/dist/ws/create-web-socket.cjs +1 -1
  393. package/dist/ws/create-web-socket.cjs.map +1 -1
  394. package/dist/ws/create-web-socket.js +1 -1
  395. package/dist/ws/create-web-socket.js.map +1 -1
  396. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"AIChat.cjs","names":[],"sources":["../../../src/components/AIChat/AIChat.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the widest surface in\n * the SDK on purpose: it is a whole chat page, and its 25 props are four groups —\n * the transcript (messages, votes, pending, showSystem), the turn actions an app has\n * to own (onSend, onStop, onRegenerate, onEditSubmit, onFeedback, onRetry,\n * onSendError), render slots (renderAvatar, renderContent, header, emptyState,\n * suggestions, composerActions, composerFooter) and composer settings (composerRef,\n * composerDisabled, maxRows, placeholder, locale, defaultReasoningOpen,\n * showLineNumbers). The parts already are separate components — AIChatTurn and\n * AIChatComposer are exported — and this is the assembly whose entire job is to\n * accept the union of their configuration.\n */\nimport { ArrowDown } from \"lucide-react\";\nimport {\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n type Ref,\n} from \"react\";\n\nimport { useAnnounce } from \"@/hooks/use-announce\";\nimport { cn } from \"@/utils/cn\";\n\nimport { EmptyState } from \"../EmptyState\";\nimport {\n aiChatStrings,\n isGenerating,\n lastAssistantId,\n tailSignature,\n visibleTurns,\n type AIChatMessage,\n type AIChatVote,\n} from \"./ai-chat-turns\";\nimport { AIChatComposer, type AIChatComposerHandle } from \"./AIChatComposer\";\nimport { AIChatTurn } from \"./AIChatTurn\";\nimport styles from \"./AIChat.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\" | \"onSubmit\";\n\nexport interface AIChatProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The transcript, **oldest first**. Never reordered by the component. */\n messages: readonly AIChatMessage[];\n /** Renders the composer when given. Receives the trimmed prompt. */\n onSend?: (text: string) => void | Promise<void>;\n /** Abort the turn in flight. Shows the stop button while generating. */\n onStop?: () => void;\n /** Ask again for the newest assistant turn. */\n onRegenerate?: (message: AIChatMessage) => void;\n /** Re-submit an edited user turn. The app decides what to drop after it. */\n onEditSubmit?: (message: AIChatMessage, text: string) => void | Promise<void>;\n /** Rating on an assistant turn. */\n onFeedback?: (message: AIChatMessage, vote: AIChatVote) => void;\n /** Retry a turn that carries an `error`. */\n onRetry?: (message: AIChatMessage) => void;\n /**\n * The request is out and nothing has arrived yet.\n *\n * Distinct from a turn with `streaming: true`: apps that only push a message\n * once the first token lands need somewhere to say \"we asked\", and without it the\n * screen is frozen for however long the model takes to start.\n */\n pending?: boolean;\n /** Prompts offered on an empty transcript. Clicking one sends it. */\n suggestions?: readonly string[];\n /** Avatar for a turn — an `<Avatar>`, an `<Icon>`, a logo. */\n renderAvatar?: (message: AIChatMessage) => ReactNode;\n /** Render a body yourself — a tool-call card, a chart, a citation list. */\n renderContent?: (message: AIChatMessage) => ReactNode;\n /** Ratings to show as pressed, by message id. Omit to keep them local. */\n votes?: Readonly<Record<string, AIChatVote>>;\n /** Rendered above the transcript, inside the panel. */\n header?: ReactNode;\n /** Shown when there are no turns and no suggestions. */\n emptyState?: ReactNode;\n /** Show `\"system\"` turns. Default `false`. */\n showSystem?: boolean;\n /** Reasoning blocks start expanded. Default `false`. */\n defaultReasoningOpen?: boolean;\n /** Show line numbers in fenced code. Default `false`. */\n showLineNumbers?: boolean;\n /** Locale for labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Placeholder for the composer. */\n placeholder?: string;\n /** Extra controls inside the composer, before the send button. */\n composerActions?: ReactNode;\n /**\n * Reach the composer imperatively — `focus()`, `getValue()`, `setValue()`.\n *\n * What makes dictation (or a slash-command menu, or \"edit and resend\") possible\n * without this component knowing anything about them: pair it with\n * `composerActions` and the button you put in the composer can write into the\n * field. Speech recognition is **not** wired in here on purpose — it would make\n * every consumer of `AIChat` pay for an API that streams audio to a third party.\n */\n composerRef?: Ref<AIChatComposerHandle>;\n /** Under the composer field — token count, model name, a disclaimer. */\n composerFooter?: ReactNode;\n /** Disable the composer — no credits, conversation archived, offline. */\n composerDisabled?: boolean;\n /** Largest height the composer grows to, in lines. Default 8. */\n maxRows?: number;\n /**\n * Called when `onSend` **or** `onEditSubmit` rejects. The draft stays in the field\n * either way.\n */\n onSendError?: (error: unknown) => void;\n}\n\n/** How close to the bottom still counts as \"reading the newest turn\", in pixels. */\nconst BOTTOM_SLACK = 48;\n\n/**\n * A conversation with a model: role-based turns, Markdown answers, a reasoning\n * block, a streaming caret, per-turn actions and a composer that turns into a stop\n * button while a turn is generating.\n *\n * This is the shape ChatGPT, Claude and DeepSeek converged on, and it is a different\n * component from {@link Chat}, not a variant of it. A human thread is addressed by\n * author and cares about delivery state; a model transcript is addressed by role,\n * has no delivery state at all, and needs three things a human thread never does —\n * partial output, reasoning separate from the answer, and re-asking.\n *\n * Presentational and controlled, like the rest of the SDK: it takes a list and emits\n * intent (`onSend`, `onStop`, `onRegenerate`, `onEditSubmit`, `onFeedback`). The\n * transport stays with the app, because \"how do I stream from my backend\" has a\n * different answer per provider — the SDK's `createEventStream` covers SSE, `fetch`\n * with a `ReadableStream` covers the rest, and either way the app owns the\n * `AbortController` it hands to `onStop`.\n *\n * @example\n * <AIChat\n * messages={turns}\n * pending={pending}\n * onSend={(text) => ask(text)}\n * onStop={() => controller.current?.abort()}\n * onRegenerate={(turn) => reask(turn)}\n * onFeedback={(turn, vote) => track(\"answer_rated\", { id: turn.id, vote })}\n * suggestions={[\"Resuma o último relatório\", \"Quais pedidos atrasaram?\"]}\n * />\n */\nexport function AIChat({\n messages,\n onSend,\n onStop,\n onRegenerate,\n onEditSubmit,\n onFeedback,\n onRetry,\n pending = false,\n suggestions = [],\n renderAvatar,\n renderContent,\n votes,\n header,\n emptyState,\n showSystem = false,\n defaultReasoningOpen = false,\n showLineNumbers = false,\n locale = \"pt-BR\",\n placeholder,\n composerActions,\n composerRef,\n composerFooter,\n composerDisabled,\n maxRows,\n onSendError,\n className,\n ...rest\n}: AIChatProps) {\n const strings = aiChatStrings(locale);\n const turns = visibleTurns({ messages, showSystem });\n const generating = pending || isGenerating(messages);\n const newestAssistant = lastAssistantId(messages);\n const signature = tailSignature(messages);\n\n const composer = useRef<AIChatComposerHandle | null>(null);\n const thread = useRef<HTMLDivElement | null>(null);\n const stuckToBottom = useRef(true);\n const wasGenerating = useRef(false);\n const [atBottom, setAtBottom] = useState(true);\n const announce = useAnnounce();\n\n /**\n * Remember whether the reader is at the bottom, before the next tokens land.\n *\n * A transcript that always scrolls to the newest text yanks somebody out of the\n * answer they were re-reading — and with a streaming answer that would happen\n * dozens of times per second. So the jump only happens when they were already at\n * the bottom. The slack covers a partially visible last line.\n */\n const trackPosition = (): void => {\n const node = thread.current;\n if (!node) return;\n const next = node.scrollHeight - node.scrollTop - node.clientHeight < BOTTOM_SLACK;\n stuckToBottom.current = next;\n setAtBottom(next);\n };\n\n const jumpToLatest = (): void => {\n const node = thread.current;\n if (!node) return;\n stuckToBottom.current = true;\n setAtBottom(true);\n node.scrollTop = node.scrollHeight;\n };\n\n useLayoutEffect(() => {\n const node = thread.current;\n if (!node || !stuckToBottom.current) return;\n node.scrollTop = node.scrollHeight;\n }, [signature, pending]);\n\n useEffect(() => {\n const node = thread.current;\n if (!node) return;\n node.scrollTop = node.scrollHeight;\n // Mount lands on the newest turn; from then on `trackPosition` decides.\n }, []);\n\n /**\n * Announce the start and the end of a generation, and nothing in between.\n *\n * The transcript is a `role=\"log\"` **without** `aria-live`: a live region over\n * streaming text makes a screen reader read the answer again on every token,\n * which is unusable. The two moments that matter are announced here instead —\n * through the shared `useAnnounce` region rather than a private one, so a page\n * holding a chat plus a table plus toasts still has exactly one live region per\n * politeness — and the finished answer is read from the log at the reader's own\n * pace.\n */\n useEffect(() => {\n if (generating) {\n announce(strings.generating);\n wasGenerating.current = true;\n return;\n }\n if (wasGenerating.current) {\n announce(strings.done);\n wasGenerating.current = false;\n }\n }, [generating, strings.generating, strings.done, announce]);\n\n const showSuggestions = turns.length === 0 && suggestions.length > 0 && onSend !== undefined;\n const detached = !atBottom && turns.length > 0;\n\n return (\n <div className={cn(styles.panel, className)} {...rest}>\n {header && <header className={styles.header}>{header}</header>}\n\n <div className={styles.threadWrapper}>\n <div\n ref={thread}\n className={styles.thread}\n onScroll={trackPosition}\n role=\"log\"\n tabIndex={0}\n aria-label={strings.thread}\n >\n {turns.length === 0 && !showSuggestions\n ? (emptyState ?? (\n <EmptyState title={strings.empty} description={strings.emptyHint} />\n ))\n : turns.map((message) => (\n <AIChatTurn\n key={message.id}\n message={message}\n locale={locale}\n canRegenerate={\n message.id === newestAssistant && message.streaming !== true\n }\n onRegenerate={onRegenerate}\n onFeedback={onFeedback}\n onEditSubmit={onEditSubmit}\n onEditError={onSendError}\n onRetry={onRetry}\n renderAvatar={renderAvatar}\n renderContent={renderContent}\n vote={votes?.[message.id]}\n defaultReasoningOpen={defaultReasoningOpen}\n showLineNumbers={showLineNumbers}\n />\n ))}\n\n {showSuggestions && (\n <div className={styles.suggestions}>\n <p className={styles.suggestionsTitle}>{strings.emptyHint}</p>\n {suggestions.map((suggestion) => (\n <button\n key={suggestion}\n type=\"button\"\n className={styles.suggestion}\n onClick={() => void onSend?.(suggestion)}\n >\n {suggestion}\n </button>\n ))}\n </div>\n )}\n\n {pending && !isGenerating(messages) && (\n <p className={styles.thinking}>\n <span className={styles.dots} aria-hidden=\"true\">\n <span />\n <span />\n <span />\n </span>\n {strings.thinking}\n </p>\n )}\n </div>\n\n {detached && (\n <button\n type=\"button\"\n className={styles.jump}\n onClick={jumpToLatest}\n aria-label={strings.jumpToLatest}\n title={strings.jumpToLatest}\n >\n <ArrowDown size={16} aria-hidden />\n </button>\n )}\n </div>\n\n {onSend && (\n <AIChatComposer\n ref={composerRef ?? composer}\n onSend={onSend}\n onStop={onStop}\n generating={generating}\n locale={locale}\n placeholder={placeholder}\n actions={composerActions}\n footer={composerFooter}\n disabled={composerDisabled}\n maxRows={maxRows}\n onError={onSendError}\n />\n )}\n </div>\n );\n}\n"],"mappings":"4UAkHA,IAAM,EAAe,GA+BrB,SAAgB,EAAO,CACnB,WACA,SACA,SACA,eACA,eACA,aACA,UACA,UAAU,GACV,cAAc,CAAC,EACf,eACA,gBACA,QACA,SACA,aACA,aAAa,GACb,uBAAuB,GACvB,kBAAkB,GAClB,SAAS,QACT,cACA,kBACA,cACA,iBACA,mBACA,UACA,cACA,YACA,GAAG,GACS,CACZ,IAAM,EAAU,EAAA,cAAc,CAAM,EAC9B,EAAQ,EAAA,aAAa,CAAE,WAAU,YAAW,CAAC,EAC7C,EAAa,GAAW,EAAA,aAAa,CAAQ,EAC7C,EAAkB,EAAA,gBAAgB,CAAQ,EAC1C,EAAY,EAAA,cAAc,CAAQ,EAElC,GAAA,EAAW,EAAA,OAAA,CAAoC,IAAI,EACnD,GAAA,EAAS,EAAA,OAAA,CAA8B,IAAI,EAC3C,GAAA,EAAgB,EAAA,OAAA,CAAO,EAAI,EAC3B,GAAA,EAAgB,EAAA,OAAA,CAAO,EAAK,EAC5B,CAAC,EAAU,IAAA,EAAe,EAAA,SAAA,CAAS,EAAI,EACvC,EAAW,EAAA,YAAY,EAUvB,MAA4B,CAC9B,IAAM,EAAO,EAAO,QACpB,GAAI,CAAC,EAAM,OACX,IAAM,EAAO,EAAK,aAAe,EAAK,UAAY,EAAK,aAAe,EACtE,EAAc,QAAU,EACxB,EAAY,CAAI,CACpB,EAEM,MAA2B,CAC7B,IAAM,EAAO,EAAO,QACf,IACL,EAAc,QAAU,GACxB,EAAY,EAAI,EAChB,EAAK,UAAY,EAAK,aAC1B,GAEA,EAAA,EAAA,gBAAA,KAAsB,CAClB,IAAM,EAAO,EAAO,QAChB,CAAC,GAAQ,CAAC,EAAc,UAC5B,EAAK,UAAY,EAAK,aAC1B,EAAG,CAAC,EAAW,CAAO,CAAC,GAEvB,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EAAO,EAAO,QACf,IACL,EAAK,UAAY,EAAK,aAE1B,EAAG,CAAC,CAAC,GAaL,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,EAAY,CACZ,EAAS,EAAQ,UAAU,EAC3B,EAAc,QAAU,GACxB,MACJ,CACA,AAEI,EAAc,WADd,EAAS,EAAQ,IAAI,EACG,GAEhC,EAAG,CAAC,EAAY,EAAQ,WAAY,EAAQ,KAAM,CAAQ,CAAC,EAE3D,IAAM,EAAkB,EAAM,SAAW,GAAK,EAAY,OAAS,GAAK,IAAW,IAAA,GAC7E,EAAW,CAAC,GAAY,EAAM,OAAS,EAE7C,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,GAAG,EAAA,QAAO,MAAO,CAAS,EAAG,GAAI,EAAjD,SAAA,CACK,IAAU,EAAA,EAAA,IAAA,CAAC,SAAD,CAAQ,UAAW,EAAA,QAAO,OAAS,SAAA,CAAe,CAAA,GAE7D,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,cAAvB,SAAA,EACI,EAAA,EAAA,KAAA,CAAC,MAAD,CACI,IAAK,EACL,UAAW,EAAA,QAAO,OAClB,SAAU,EACV,KAAK,MACL,SAAU,EACV,aAAY,EAAQ,OANxB,SAAA,CAQK,EAAM,SAAW,GAAK,CAAC,EACjB,IACG,EAAA,EAAA,IAAA,CAAC,EAAA,WAAD,CAAY,MAAO,EAAQ,MAAO,YAAa,EAAQ,SAAY,CAAA,EAEvE,EAAM,IAAK,IACP,EAAA,EAAA,IAAA,CAAC,EAAA,WAAD,CAEa,UACD,SACR,cACI,EAAQ,KAAO,GAAmB,EAAQ,YAAc,GAE9C,eACF,aACE,eACd,YAAa,EACJ,UACK,eACC,gBACf,KAAM,IAAQ,EAAQ,IACA,uBACL,iBACpB,EAhBQ,EAAQ,EAgBhB,CACJ,EAEN,IACG,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,YAAvB,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,IAAD,CAAG,UAAW,EAAA,QAAO,iBAAmB,SAAA,EAAQ,SAAa,CAAA,EAC5D,EAAY,IAAK,IACd,EAAA,EAAA,IAAA,CAAC,SAAD,CAEI,KAAK,SACL,UAAW,EAAA,QAAO,WAClB,YAAe,KAAK,IAAS,CAAU,EAEtC,SAAA,CACG,EANC,CAMD,CACX,CACA,IAGR,GAAW,CAAC,EAAA,aAAa,CAAQ,IAC9B,EAAA,EAAA,KAAA,CAAC,IAAD,CAAG,UAAW,EAAA,QAAO,SAArB,SAAA,EACI,EAAA,EAAA,KAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,KAAM,cAAY,OAA1C,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,OAAD,CAAO,CAAA,GACP,EAAA,EAAA,IAAA,CAAC,OAAD,CAAO,CAAA,GACP,EAAA,EAAA,IAAA,CAAC,OAAD,CAAO,CAAA,CACL,CACL,CAAA,EAAA,EAAQ,QACV,GAEN,CAEJ,CAAA,EAAA,IACG,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,KAClB,QAAS,EACT,aAAY,EAAQ,aACpB,MAAO,EAAQ,aAEf,UAAA,EAAA,EAAA,IAAA,CAAC,EAAA,UAAD,CAAW,KAAM,GAAI,cAAA,EAAa,CAAA,CAC9B,CAAA,CAEX,IAEJ,IACG,EAAA,EAAA,IAAA,CAAC,EAAA,eAAD,CACI,IAAK,GAAe,EACZ,SACA,SACI,aACJ,SACK,cACb,QAAS,EACT,OAAQ,EACR,SAAU,EACD,UACT,QAAS,CACZ,CAAA,CAEJ,GAEb"}
1
+ {"version":3,"file":"AIChat.cjs","names":[],"sources":["../../../src/components/AIChat/AIChat.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the widest surface in\n * the SDK on purpose: it is a whole chat page, and its 25 props are four groups —\n * the transcript (messages, votes, pending, showSystem), the turn actions an app has\n * to own (onSend, onStop, onRegenerate, onEditSubmit, onFeedback, onRetry,\n * onSendError), render slots (renderAvatar, renderContent, header, emptyState,\n * suggestions, composerActions, composerFooter) and composer settings (composerRef,\n * composerDisabled, maxRows, placeholder, locale, defaultReasoningOpen,\n * showLineNumbers). The parts already are separate components — AIChatTurn and\n * AIChatComposer are exported — and this is the assembly whose entire job is to\n * accept the union of their configuration.\n */\nimport { ArrowDown } from \"lucide-react\";\nimport {\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n type Ref,\n} from \"react\";\n\nimport { useAnnounce } from \"@/hooks/use-announce\";\nimport { cn } from \"@/utils/cn\";\n\nimport { EmptyState } from \"../EmptyState\";\nimport {\n aiChatStrings,\n isGenerating,\n lastAssistantId,\n tailSignature,\n visibleTurns,\n type AIChatMessage,\n type AIChatVote,\n} from \"./ai-chat-turns\";\nimport { AIChatComposer, type AIChatComposerHandle } from \"./AIChatComposer\";\nimport { AIChatTurn } from \"./AIChatTurn\";\nimport styles from \"./AIChat.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\" | \"onSubmit\";\n\nexport interface AIChatProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The transcript, **oldest first**. Never reordered by the component. */\n messages: readonly AIChatMessage[];\n /** Renders the composer when given. Receives the trimmed prompt. */\n onSend?: (text: string) => void | Promise<void>;\n /** Abort the turn in flight. Shows the stop button while generating. */\n onStop?: () => void;\n /** Ask again for the newest assistant turn. */\n onRegenerate?: (message: AIChatMessage) => void;\n /** Re-submit an edited user turn. The app decides what to drop after it. */\n onEditSubmit?: (message: AIChatMessage, text: string) => void | Promise<void>;\n /** Rating on an assistant turn. */\n onFeedback?: (message: AIChatMessage, vote: AIChatVote) => void;\n /** Retry a turn that carries an `error`. */\n onRetry?: (message: AIChatMessage) => void;\n /**\n * The request is out and nothing has arrived yet.\n *\n * Distinct from a turn with `streaming: true`: apps that only push a message\n * once the first token lands need somewhere to say \"we asked\", and without it the\n * screen is frozen for however long the model takes to start.\n */\n pending?: boolean;\n /** Prompts offered on an empty transcript. Clicking one sends it. */\n suggestions?: readonly string[];\n /** Avatar for a turn — an `<Avatar>`, an `<Icon>`, a logo. */\n renderAvatar?: (message: AIChatMessage) => ReactNode;\n /** Render a body yourself — a tool-call card, a chart, a citation list. */\n renderContent?: (message: AIChatMessage) => ReactNode;\n /** Ratings to show as pressed, by message id. Omit to keep them local. */\n votes?: Readonly<Record<string, AIChatVote>>;\n /** Rendered above the transcript, inside the panel. */\n header?: ReactNode;\n /** Shown when there are no turns and no suggestions. */\n emptyState?: ReactNode;\n /** Show `\"system\"` turns. Default `false`. */\n showSystem?: boolean;\n /** Reasoning blocks start expanded. Default `false`. */\n defaultReasoningOpen?: boolean;\n /** Show line numbers in fenced code. Default `false`. */\n showLineNumbers?: boolean;\n /** Locale for labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Placeholder for the composer. */\n placeholder?: string;\n /** Extra controls inside the composer, before the send button. */\n composerActions?: ReactNode;\n /**\n * Reach the composer imperatively — `focus()`, `getValue()`, `setValue()`.\n *\n * What makes dictation (or a slash-command menu, or \"edit and resend\") possible\n * without this component knowing anything about them: pair it with\n * `composerActions` and the button you put in the composer can write into the\n * field. Speech recognition is **not** wired in here on purpose — it would make\n * every consumer of `AIChat` pay for an API that streams audio to a third party.\n */\n composerRef?: Ref<AIChatComposerHandle>;\n /** Under the composer field — token count, model name, a disclaimer. */\n composerFooter?: ReactNode;\n /** Disable the composer — no credits, conversation archived, offline. */\n composerDisabled?: boolean;\n /** Largest height the composer grows to, in lines. Default 8. */\n maxRows?: number;\n /**\n * Called when `onSend` **or** `onEditSubmit` rejects. The draft stays in the field\n * either way.\n */\n onSendError?: (error: unknown) => void;\n}\n\n/** How close to the bottom still counts as \"reading the newest turn\", in pixels. */\nconst BOTTOM_SLACK = 48;\n\n/**\n * A conversation with a model: role-based turns, Markdown answers, a reasoning\n * block, a streaming caret, per-turn actions and a composer that turns into a stop\n * button while a turn is generating.\n *\n * This is the shape ChatGPT, Claude and DeepSeek converged on, and it is a different\n * component from {@link Chat}, not a variant of it. A human thread is addressed by\n * author and cares about delivery state; a model transcript is addressed by role,\n * has no delivery state at all, and needs three things a human thread never does —\n * partial output, reasoning separate from the answer, and re-asking.\n *\n * Presentational and controlled, like the rest of the SDK: it takes a list and emits\n * intent (`onSend`, `onStop`, `onRegenerate`, `onEditSubmit`, `onFeedback`). The\n * transport stays with the app, because \"how do I stream from my backend\" has a\n * different answer per provider — the SDK's `createEventStream` covers SSE, `fetch`\n * with a `ReadableStream` covers the rest, and either way the app owns the\n * `AbortController` it hands to `onStop`.\n *\n * @example\n * <AIChat\n * messages={turns}\n * pending={pending}\n * onSend={(text) => ask(text)}\n * onStop={() => controller.current?.abort()}\n * onRegenerate={(turn) => reask(turn)}\n * onFeedback={(turn, vote) => track(\"answer_rated\", { id: turn.id, vote })}\n * suggestions={[\"Resuma o último relatório\", \"Quais pedidos atrasaram?\"]}\n * />\n */\nexport function AIChat({\n messages,\n onSend,\n onStop,\n onRegenerate,\n onEditSubmit,\n onFeedback,\n onRetry,\n pending = false,\n suggestions = [],\n renderAvatar,\n renderContent,\n votes,\n header,\n emptyState,\n showSystem = false,\n defaultReasoningOpen = false,\n showLineNumbers = false,\n locale = \"pt-BR\",\n placeholder,\n composerActions,\n composerRef,\n composerFooter,\n composerDisabled,\n maxRows,\n onSendError,\n className,\n ...rest\n}: AIChatProps) {\n const strings = aiChatStrings(locale);\n const turns = visibleTurns({ messages, showSystem });\n const generating = pending || isGenerating(messages);\n const newestAssistant = lastAssistantId(messages);\n const signature = tailSignature(messages);\n\n const composer = useRef<AIChatComposerHandle | null>(null);\n const thread = useRef<HTMLDivElement | null>(null);\n const stuckToBottom = useRef(true);\n const wasGenerating = useRef(false);\n const [atBottom, setAtBottom] = useState(true);\n const announce = useAnnounce();\n\n /**\n * Remember whether the reader is at the bottom, before the next tokens land.\n *\n * A transcript that always scrolls to the newest text yanks somebody out of the\n * answer they were re-reading — and with a streaming answer that would happen\n * dozens of times per second. So the jump only happens when they were already at\n * the bottom. The slack covers a partially visible last line.\n */\n const trackPosition = (): void => {\n const node = thread.current;\n if (!node) return;\n const next = node.scrollHeight - node.scrollTop - node.clientHeight < BOTTOM_SLACK;\n stuckToBottom.current = next;\n setAtBottom(next);\n };\n\n const jumpToLatest = (): void => {\n const node = thread.current;\n if (!node) return;\n stuckToBottom.current = true;\n setAtBottom(true);\n node.scrollTop = node.scrollHeight;\n };\n\n useLayoutEffect(() => {\n const node = thread.current;\n if (!node || !stuckToBottom.current) return;\n node.scrollTop = node.scrollHeight;\n }, [signature, pending]);\n\n useEffect(() => {\n const node = thread.current;\n if (!node) return;\n node.scrollTop = node.scrollHeight;\n // Mount lands on the newest turn; from then on `trackPosition` decides.\n }, []);\n\n /**\n * Announce the start and the end of a generation, and nothing in between.\n *\n * The transcript is a `role=\"log\"` **without** `aria-live`: a live region over\n * streaming text makes a screen reader read the answer again on every token,\n * which is unusable. The two moments that matter are announced here instead —\n * through the shared `useAnnounce` region rather than a private one, so a page\n * holding a chat plus a table plus toasts still has exactly one live region per\n * politeness — and the finished answer is read from the log at the reader's own\n * pace.\n */\n useEffect(() => {\n if (generating) {\n announce(strings.generating);\n wasGenerating.current = true;\n return;\n }\n if (wasGenerating.current) {\n announce(strings.done);\n wasGenerating.current = false;\n }\n }, [generating, strings.generating, strings.done, announce]);\n\n const showSuggestions = turns.length === 0 && suggestions.length > 0 && onSend !== undefined;\n const detached = !atBottom && turns.length > 0;\n\n return (\n <div className={cn(styles.panel, className)} {...rest}>\n {header && <header className={styles.header}>{header}</header>}\n\n <div className={styles.threadWrapper}>\n <div\n ref={thread}\n className={styles.thread}\n onScroll={trackPosition}\n role=\"log\"\n tabIndex={0}\n aria-label={strings.thread}\n >\n {turns.length === 0 && !showSuggestions\n ? (emptyState ?? (\n <EmptyState title={strings.empty} description={strings.emptyHint} />\n ))\n : turns.map((message) => (\n <AIChatTurn\n key={message.id}\n message={message}\n locale={locale}\n canRegenerate={\n message.id === newestAssistant && message.streaming !== true\n }\n onRegenerate={onRegenerate}\n onFeedback={onFeedback}\n onEditSubmit={onEditSubmit}\n onEditError={onSendError}\n onRetry={onRetry}\n renderAvatar={renderAvatar}\n renderContent={renderContent}\n vote={votes?.[message.id]}\n defaultReasoningOpen={defaultReasoningOpen}\n showLineNumbers={showLineNumbers}\n />\n ))}\n\n {showSuggestions && (\n <div className={styles.suggestions}>\n <p className={styles.suggestionsTitle}>{strings.emptyHint}</p>\n {suggestions.map((suggestion) => (\n <button\n key={suggestion}\n type=\"button\"\n className={styles.suggestion}\n onClick={() => void onSend?.(suggestion)}\n >\n {suggestion}\n </button>\n ))}\n </div>\n )}\n\n {pending && !isGenerating(messages) && (\n <p className={styles.thinking}>\n <span className={styles.dots} aria-hidden=\"true\">\n <span />\n <span />\n <span />\n </span>\n {strings.thinking}\n </p>\n )}\n </div>\n\n {detached && (\n <button\n type=\"button\"\n className={styles.jump}\n onClick={jumpToLatest}\n aria-label={strings.jumpToLatest}\n title={strings.jumpToLatest}\n >\n <ArrowDown size={16} aria-hidden />\n </button>\n )}\n </div>\n\n {onSend && (\n <AIChatComposer\n ref={composerRef ?? composer}\n onSend={onSend}\n onStop={onStop}\n generating={generating}\n locale={locale}\n placeholder={placeholder}\n actions={composerActions}\n footer={composerFooter}\n disabled={composerDisabled}\n maxRows={maxRows}\n onError={onSendError}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -25,7 +25,7 @@ function y({ messages: y, onSend: b, onStop: x, onRegenerate: ee, onEditSubmit:
25
25
  };
26
26
  f(() => {
27
27
  let e = J.current;
28
- !e || !Y.current || (e.scrollTop = e.scrollHeight);
28
+ e && Y.current && (e.scrollTop = e.scrollHeight);
29
29
  }, [K, C]), d(() => {
30
30
  let e = J.current;
31
31
  e && (e.scrollTop = e.scrollHeight);
@@ -1 +1 @@
1
- {"version":3,"file":"AIChat.js","names":[],"sources":["../../../src/components/AIChat/AIChat.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the widest surface in\n * the SDK on purpose: it is a whole chat page, and its 25 props are four groups —\n * the transcript (messages, votes, pending, showSystem), the turn actions an app has\n * to own (onSend, onStop, onRegenerate, onEditSubmit, onFeedback, onRetry,\n * onSendError), render slots (renderAvatar, renderContent, header, emptyState,\n * suggestions, composerActions, composerFooter) and composer settings (composerRef,\n * composerDisabled, maxRows, placeholder, locale, defaultReasoningOpen,\n * showLineNumbers). The parts already are separate components — AIChatTurn and\n * AIChatComposer are exported — and this is the assembly whose entire job is to\n * accept the union of their configuration.\n */\nimport { ArrowDown } from \"lucide-react\";\nimport {\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n type Ref,\n} from \"react\";\n\nimport { useAnnounce } from \"@/hooks/use-announce\";\nimport { cn } from \"@/utils/cn\";\n\nimport { EmptyState } from \"../EmptyState\";\nimport {\n aiChatStrings,\n isGenerating,\n lastAssistantId,\n tailSignature,\n visibleTurns,\n type AIChatMessage,\n type AIChatVote,\n} from \"./ai-chat-turns\";\nimport { AIChatComposer, type AIChatComposerHandle } from \"./AIChatComposer\";\nimport { AIChatTurn } from \"./AIChatTurn\";\nimport styles from \"./AIChat.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\" | \"onSubmit\";\n\nexport interface AIChatProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The transcript, **oldest first**. Never reordered by the component. */\n messages: readonly AIChatMessage[];\n /** Renders the composer when given. Receives the trimmed prompt. */\n onSend?: (text: string) => void | Promise<void>;\n /** Abort the turn in flight. Shows the stop button while generating. */\n onStop?: () => void;\n /** Ask again for the newest assistant turn. */\n onRegenerate?: (message: AIChatMessage) => void;\n /** Re-submit an edited user turn. The app decides what to drop after it. */\n onEditSubmit?: (message: AIChatMessage, text: string) => void | Promise<void>;\n /** Rating on an assistant turn. */\n onFeedback?: (message: AIChatMessage, vote: AIChatVote) => void;\n /** Retry a turn that carries an `error`. */\n onRetry?: (message: AIChatMessage) => void;\n /**\n * The request is out and nothing has arrived yet.\n *\n * Distinct from a turn with `streaming: true`: apps that only push a message\n * once the first token lands need somewhere to say \"we asked\", and without it the\n * screen is frozen for however long the model takes to start.\n */\n pending?: boolean;\n /** Prompts offered on an empty transcript. Clicking one sends it. */\n suggestions?: readonly string[];\n /** Avatar for a turn — an `<Avatar>`, an `<Icon>`, a logo. */\n renderAvatar?: (message: AIChatMessage) => ReactNode;\n /** Render a body yourself — a tool-call card, a chart, a citation list. */\n renderContent?: (message: AIChatMessage) => ReactNode;\n /** Ratings to show as pressed, by message id. Omit to keep them local. */\n votes?: Readonly<Record<string, AIChatVote>>;\n /** Rendered above the transcript, inside the panel. */\n header?: ReactNode;\n /** Shown when there are no turns and no suggestions. */\n emptyState?: ReactNode;\n /** Show `\"system\"` turns. Default `false`. */\n showSystem?: boolean;\n /** Reasoning blocks start expanded. Default `false`. */\n defaultReasoningOpen?: boolean;\n /** Show line numbers in fenced code. Default `false`. */\n showLineNumbers?: boolean;\n /** Locale for labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Placeholder for the composer. */\n placeholder?: string;\n /** Extra controls inside the composer, before the send button. */\n composerActions?: ReactNode;\n /**\n * Reach the composer imperatively — `focus()`, `getValue()`, `setValue()`.\n *\n * What makes dictation (or a slash-command menu, or \"edit and resend\") possible\n * without this component knowing anything about them: pair it with\n * `composerActions` and the button you put in the composer can write into the\n * field. Speech recognition is **not** wired in here on purpose — it would make\n * every consumer of `AIChat` pay for an API that streams audio to a third party.\n */\n composerRef?: Ref<AIChatComposerHandle>;\n /** Under the composer field — token count, model name, a disclaimer. */\n composerFooter?: ReactNode;\n /** Disable the composer — no credits, conversation archived, offline. */\n composerDisabled?: boolean;\n /** Largest height the composer grows to, in lines. Default 8. */\n maxRows?: number;\n /**\n * Called when `onSend` **or** `onEditSubmit` rejects. The draft stays in the field\n * either way.\n */\n onSendError?: (error: unknown) => void;\n}\n\n/** How close to the bottom still counts as \"reading the newest turn\", in pixels. */\nconst BOTTOM_SLACK = 48;\n\n/**\n * A conversation with a model: role-based turns, Markdown answers, a reasoning\n * block, a streaming caret, per-turn actions and a composer that turns into a stop\n * button while a turn is generating.\n *\n * This is the shape ChatGPT, Claude and DeepSeek converged on, and it is a different\n * component from {@link Chat}, not a variant of it. A human thread is addressed by\n * author and cares about delivery state; a model transcript is addressed by role,\n * has no delivery state at all, and needs three things a human thread never does —\n * partial output, reasoning separate from the answer, and re-asking.\n *\n * Presentational and controlled, like the rest of the SDK: it takes a list and emits\n * intent (`onSend`, `onStop`, `onRegenerate`, `onEditSubmit`, `onFeedback`). The\n * transport stays with the app, because \"how do I stream from my backend\" has a\n * different answer per provider — the SDK's `createEventStream` covers SSE, `fetch`\n * with a `ReadableStream` covers the rest, and either way the app owns the\n * `AbortController` it hands to `onStop`.\n *\n * @example\n * <AIChat\n * messages={turns}\n * pending={pending}\n * onSend={(text) => ask(text)}\n * onStop={() => controller.current?.abort()}\n * onRegenerate={(turn) => reask(turn)}\n * onFeedback={(turn, vote) => track(\"answer_rated\", { id: turn.id, vote })}\n * suggestions={[\"Resuma o último relatório\", \"Quais pedidos atrasaram?\"]}\n * />\n */\nexport function AIChat({\n messages,\n onSend,\n onStop,\n onRegenerate,\n onEditSubmit,\n onFeedback,\n onRetry,\n pending = false,\n suggestions = [],\n renderAvatar,\n renderContent,\n votes,\n header,\n emptyState,\n showSystem = false,\n defaultReasoningOpen = false,\n showLineNumbers = false,\n locale = \"pt-BR\",\n placeholder,\n composerActions,\n composerRef,\n composerFooter,\n composerDisabled,\n maxRows,\n onSendError,\n className,\n ...rest\n}: AIChatProps) {\n const strings = aiChatStrings(locale);\n const turns = visibleTurns({ messages, showSystem });\n const generating = pending || isGenerating(messages);\n const newestAssistant = lastAssistantId(messages);\n const signature = tailSignature(messages);\n\n const composer = useRef<AIChatComposerHandle | null>(null);\n const thread = useRef<HTMLDivElement | null>(null);\n const stuckToBottom = useRef(true);\n const wasGenerating = useRef(false);\n const [atBottom, setAtBottom] = useState(true);\n const announce = useAnnounce();\n\n /**\n * Remember whether the reader is at the bottom, before the next tokens land.\n *\n * A transcript that always scrolls to the newest text yanks somebody out of the\n * answer they were re-reading — and with a streaming answer that would happen\n * dozens of times per second. So the jump only happens when they were already at\n * the bottom. The slack covers a partially visible last line.\n */\n const trackPosition = (): void => {\n const node = thread.current;\n if (!node) return;\n const next = node.scrollHeight - node.scrollTop - node.clientHeight < BOTTOM_SLACK;\n stuckToBottom.current = next;\n setAtBottom(next);\n };\n\n const jumpToLatest = (): void => {\n const node = thread.current;\n if (!node) return;\n stuckToBottom.current = true;\n setAtBottom(true);\n node.scrollTop = node.scrollHeight;\n };\n\n useLayoutEffect(() => {\n const node = thread.current;\n if (!node || !stuckToBottom.current) return;\n node.scrollTop = node.scrollHeight;\n }, [signature, pending]);\n\n useEffect(() => {\n const node = thread.current;\n if (!node) return;\n node.scrollTop = node.scrollHeight;\n // Mount lands on the newest turn; from then on `trackPosition` decides.\n }, []);\n\n /**\n * Announce the start and the end of a generation, and nothing in between.\n *\n * The transcript is a `role=\"log\"` **without** `aria-live`: a live region over\n * streaming text makes a screen reader read the answer again on every token,\n * which is unusable. The two moments that matter are announced here instead —\n * through the shared `useAnnounce` region rather than a private one, so a page\n * holding a chat plus a table plus toasts still has exactly one live region per\n * politeness — and the finished answer is read from the log at the reader's own\n * pace.\n */\n useEffect(() => {\n if (generating) {\n announce(strings.generating);\n wasGenerating.current = true;\n return;\n }\n if (wasGenerating.current) {\n announce(strings.done);\n wasGenerating.current = false;\n }\n }, [generating, strings.generating, strings.done, announce]);\n\n const showSuggestions = turns.length === 0 && suggestions.length > 0 && onSend !== undefined;\n const detached = !atBottom && turns.length > 0;\n\n return (\n <div className={cn(styles.panel, className)} {...rest}>\n {header && <header className={styles.header}>{header}</header>}\n\n <div className={styles.threadWrapper}>\n <div\n ref={thread}\n className={styles.thread}\n onScroll={trackPosition}\n role=\"log\"\n tabIndex={0}\n aria-label={strings.thread}\n >\n {turns.length === 0 && !showSuggestions\n ? (emptyState ?? (\n <EmptyState title={strings.empty} description={strings.emptyHint} />\n ))\n : turns.map((message) => (\n <AIChatTurn\n key={message.id}\n message={message}\n locale={locale}\n canRegenerate={\n message.id === newestAssistant && message.streaming !== true\n }\n onRegenerate={onRegenerate}\n onFeedback={onFeedback}\n onEditSubmit={onEditSubmit}\n onEditError={onSendError}\n onRetry={onRetry}\n renderAvatar={renderAvatar}\n renderContent={renderContent}\n vote={votes?.[message.id]}\n defaultReasoningOpen={defaultReasoningOpen}\n showLineNumbers={showLineNumbers}\n />\n ))}\n\n {showSuggestions && (\n <div className={styles.suggestions}>\n <p className={styles.suggestionsTitle}>{strings.emptyHint}</p>\n {suggestions.map((suggestion) => (\n <button\n key={suggestion}\n type=\"button\"\n className={styles.suggestion}\n onClick={() => void onSend?.(suggestion)}\n >\n {suggestion}\n </button>\n ))}\n </div>\n )}\n\n {pending && !isGenerating(messages) && (\n <p className={styles.thinking}>\n <span className={styles.dots} aria-hidden=\"true\">\n <span />\n <span />\n <span />\n </span>\n {strings.thinking}\n </p>\n )}\n </div>\n\n {detached && (\n <button\n type=\"button\"\n className={styles.jump}\n onClick={jumpToLatest}\n aria-label={strings.jumpToLatest}\n title={strings.jumpToLatest}\n >\n <ArrowDown size={16} aria-hidden />\n </button>\n )}\n </div>\n\n {onSend && (\n <AIChatComposer\n ref={composerRef ?? composer}\n onSend={onSend}\n onStop={onStop}\n generating={generating}\n locale={locale}\n placeholder={placeholder}\n actions={composerActions}\n footer={composerFooter}\n disabled={composerDisabled}\n maxRows={maxRows}\n onError={onSendError}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AIChat.js","names":[],"sources":["../../../src/components/AIChat/AIChat.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the widest surface in\n * the SDK on purpose: it is a whole chat page, and its 25 props are four groups —\n * the transcript (messages, votes, pending, showSystem), the turn actions an app has\n * to own (onSend, onStop, onRegenerate, onEditSubmit, onFeedback, onRetry,\n * onSendError), render slots (renderAvatar, renderContent, header, emptyState,\n * suggestions, composerActions, composerFooter) and composer settings (composerRef,\n * composerDisabled, maxRows, placeholder, locale, defaultReasoningOpen,\n * showLineNumbers). The parts already are separate components — AIChatTurn and\n * AIChatComposer are exported — and this is the assembly whose entire job is to\n * accept the union of their configuration.\n */\nimport { ArrowDown } from \"lucide-react\";\nimport {\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n type Ref,\n} from \"react\";\n\nimport { useAnnounce } from \"@/hooks/use-announce\";\nimport { cn } from \"@/utils/cn\";\n\nimport { EmptyState } from \"../EmptyState\";\nimport {\n aiChatStrings,\n isGenerating,\n lastAssistantId,\n tailSignature,\n visibleTurns,\n type AIChatMessage,\n type AIChatVote,\n} from \"./ai-chat-turns\";\nimport { AIChatComposer, type AIChatComposerHandle } from \"./AIChatComposer\";\nimport { AIChatTurn } from \"./AIChatTurn\";\nimport styles from \"./AIChat.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\" | \"onSubmit\";\n\nexport interface AIChatProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The transcript, **oldest first**. Never reordered by the component. */\n messages: readonly AIChatMessage[];\n /** Renders the composer when given. Receives the trimmed prompt. */\n onSend?: (text: string) => void | Promise<void>;\n /** Abort the turn in flight. Shows the stop button while generating. */\n onStop?: () => void;\n /** Ask again for the newest assistant turn. */\n onRegenerate?: (message: AIChatMessage) => void;\n /** Re-submit an edited user turn. The app decides what to drop after it. */\n onEditSubmit?: (message: AIChatMessage, text: string) => void | Promise<void>;\n /** Rating on an assistant turn. */\n onFeedback?: (message: AIChatMessage, vote: AIChatVote) => void;\n /** Retry a turn that carries an `error`. */\n onRetry?: (message: AIChatMessage) => void;\n /**\n * The request is out and nothing has arrived yet.\n *\n * Distinct from a turn with `streaming: true`: apps that only push a message\n * once the first token lands need somewhere to say \"we asked\", and without it the\n * screen is frozen for however long the model takes to start.\n */\n pending?: boolean;\n /** Prompts offered on an empty transcript. Clicking one sends it. */\n suggestions?: readonly string[];\n /** Avatar for a turn — an `<Avatar>`, an `<Icon>`, a logo. */\n renderAvatar?: (message: AIChatMessage) => ReactNode;\n /** Render a body yourself — a tool-call card, a chart, a citation list. */\n renderContent?: (message: AIChatMessage) => ReactNode;\n /** Ratings to show as pressed, by message id. Omit to keep them local. */\n votes?: Readonly<Record<string, AIChatVote>>;\n /** Rendered above the transcript, inside the panel. */\n header?: ReactNode;\n /** Shown when there are no turns and no suggestions. */\n emptyState?: ReactNode;\n /** Show `\"system\"` turns. Default `false`. */\n showSystem?: boolean;\n /** Reasoning blocks start expanded. Default `false`. */\n defaultReasoningOpen?: boolean;\n /** Show line numbers in fenced code. Default `false`. */\n showLineNumbers?: boolean;\n /** Locale for labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Placeholder for the composer. */\n placeholder?: string;\n /** Extra controls inside the composer, before the send button. */\n composerActions?: ReactNode;\n /**\n * Reach the composer imperatively — `focus()`, `getValue()`, `setValue()`.\n *\n * What makes dictation (or a slash-command menu, or \"edit and resend\") possible\n * without this component knowing anything about them: pair it with\n * `composerActions` and the button you put in the composer can write into the\n * field. Speech recognition is **not** wired in here on purpose — it would make\n * every consumer of `AIChat` pay for an API that streams audio to a third party.\n */\n composerRef?: Ref<AIChatComposerHandle>;\n /** Under the composer field — token count, model name, a disclaimer. */\n composerFooter?: ReactNode;\n /** Disable the composer — no credits, conversation archived, offline. */\n composerDisabled?: boolean;\n /** Largest height the composer grows to, in lines. Default 8. */\n maxRows?: number;\n /**\n * Called when `onSend` **or** `onEditSubmit` rejects. The draft stays in the field\n * either way.\n */\n onSendError?: (error: unknown) => void;\n}\n\n/** How close to the bottom still counts as \"reading the newest turn\", in pixels. */\nconst BOTTOM_SLACK = 48;\n\n/**\n * A conversation with a model: role-based turns, Markdown answers, a reasoning\n * block, a streaming caret, per-turn actions and a composer that turns into a stop\n * button while a turn is generating.\n *\n * This is the shape ChatGPT, Claude and DeepSeek converged on, and it is a different\n * component from {@link Chat}, not a variant of it. A human thread is addressed by\n * author and cares about delivery state; a model transcript is addressed by role,\n * has no delivery state at all, and needs three things a human thread never does —\n * partial output, reasoning separate from the answer, and re-asking.\n *\n * Presentational and controlled, like the rest of the SDK: it takes a list and emits\n * intent (`onSend`, `onStop`, `onRegenerate`, `onEditSubmit`, `onFeedback`). The\n * transport stays with the app, because \"how do I stream from my backend\" has a\n * different answer per provider — the SDK's `createEventStream` covers SSE, `fetch`\n * with a `ReadableStream` covers the rest, and either way the app owns the\n * `AbortController` it hands to `onStop`.\n *\n * @example\n * <AIChat\n * messages={turns}\n * pending={pending}\n * onSend={(text) => ask(text)}\n * onStop={() => controller.current?.abort()}\n * onRegenerate={(turn) => reask(turn)}\n * onFeedback={(turn, vote) => track(\"answer_rated\", { id: turn.id, vote })}\n * suggestions={[\"Resuma o último relatório\", \"Quais pedidos atrasaram?\"]}\n * />\n */\nexport function AIChat({\n messages,\n onSend,\n onStop,\n onRegenerate,\n onEditSubmit,\n onFeedback,\n onRetry,\n pending = false,\n suggestions = [],\n renderAvatar,\n renderContent,\n votes,\n header,\n emptyState,\n showSystem = false,\n defaultReasoningOpen = false,\n showLineNumbers = false,\n locale = \"pt-BR\",\n placeholder,\n composerActions,\n composerRef,\n composerFooter,\n composerDisabled,\n maxRows,\n onSendError,\n className,\n ...rest\n}: AIChatProps) {\n const strings = aiChatStrings(locale);\n const turns = visibleTurns({ messages, showSystem });\n const generating = pending || isGenerating(messages);\n const newestAssistant = lastAssistantId(messages);\n const signature = tailSignature(messages);\n\n const composer = useRef<AIChatComposerHandle | null>(null);\n const thread = useRef<HTMLDivElement | null>(null);\n const stuckToBottom = useRef(true);\n const wasGenerating = useRef(false);\n const [atBottom, setAtBottom] = useState(true);\n const announce = useAnnounce();\n\n /**\n * Remember whether the reader is at the bottom, before the next tokens land.\n *\n * A transcript that always scrolls to the newest text yanks somebody out of the\n * answer they were re-reading — and with a streaming answer that would happen\n * dozens of times per second. So the jump only happens when they were already at\n * the bottom. The slack covers a partially visible last line.\n */\n const trackPosition = (): void => {\n const node = thread.current;\n if (!node) return;\n const next = node.scrollHeight - node.scrollTop - node.clientHeight < BOTTOM_SLACK;\n stuckToBottom.current = next;\n setAtBottom(next);\n };\n\n const jumpToLatest = (): void => {\n const node = thread.current;\n if (!node) return;\n stuckToBottom.current = true;\n setAtBottom(true);\n node.scrollTop = node.scrollHeight;\n };\n\n useLayoutEffect(() => {\n const node = thread.current;\n if (!node || !stuckToBottom.current) return;\n node.scrollTop = node.scrollHeight;\n }, [signature, pending]);\n\n useEffect(() => {\n const node = thread.current;\n if (!node) return;\n node.scrollTop = node.scrollHeight;\n // Mount lands on the newest turn; from then on `trackPosition` decides.\n }, []);\n\n /**\n * Announce the start and the end of a generation, and nothing in between.\n *\n * The transcript is a `role=\"log\"` **without** `aria-live`: a live region over\n * streaming text makes a screen reader read the answer again on every token,\n * which is unusable. The two moments that matter are announced here instead —\n * through the shared `useAnnounce` region rather than a private one, so a page\n * holding a chat plus a table plus toasts still has exactly one live region per\n * politeness — and the finished answer is read from the log at the reader's own\n * pace.\n */\n useEffect(() => {\n if (generating) {\n announce(strings.generating);\n wasGenerating.current = true;\n return;\n }\n if (wasGenerating.current) {\n announce(strings.done);\n wasGenerating.current = false;\n }\n }, [generating, strings.generating, strings.done, announce]);\n\n const showSuggestions = turns.length === 0 && suggestions.length > 0 && onSend !== undefined;\n const detached = !atBottom && turns.length > 0;\n\n return (\n <div className={cn(styles.panel, className)} {...rest}>\n {header && <header className={styles.header}>{header}</header>}\n\n <div className={styles.threadWrapper}>\n <div\n ref={thread}\n className={styles.thread}\n onScroll={trackPosition}\n role=\"log\"\n tabIndex={0}\n aria-label={strings.thread}\n >\n {turns.length === 0 && !showSuggestions\n ? (emptyState ?? (\n <EmptyState title={strings.empty} description={strings.emptyHint} />\n ))\n : turns.map((message) => (\n <AIChatTurn\n key={message.id}\n message={message}\n locale={locale}\n canRegenerate={\n message.id === newestAssistant && message.streaming !== true\n }\n onRegenerate={onRegenerate}\n onFeedback={onFeedback}\n onEditSubmit={onEditSubmit}\n onEditError={onSendError}\n onRetry={onRetry}\n renderAvatar={renderAvatar}\n renderContent={renderContent}\n vote={votes?.[message.id]}\n defaultReasoningOpen={defaultReasoningOpen}\n showLineNumbers={showLineNumbers}\n />\n ))}\n\n {showSuggestions && (\n <div className={styles.suggestions}>\n <p className={styles.suggestionsTitle}>{strings.emptyHint}</p>\n {suggestions.map((suggestion) => (\n <button\n key={suggestion}\n type=\"button\"\n className={styles.suggestion}\n onClick={() => void onSend?.(suggestion)}\n >\n {suggestion}\n </button>\n ))}\n </div>\n )}\n\n {pending && !isGenerating(messages) && (\n <p className={styles.thinking}>\n <span className={styles.dots} aria-hidden=\"true\">\n <span />\n <span />\n <span />\n </span>\n {strings.thinking}\n </p>\n )}\n </div>\n\n {detached && (\n <button\n type=\"button\"\n className={styles.jump}\n onClick={jumpToLatest}\n aria-label={strings.jumpToLatest}\n title={strings.jumpToLatest}\n >\n <ArrowDown size={16} aria-hidden />\n </button>\n )}\n </div>\n\n {onSend && (\n <AIChatComposer\n ref={composerRef ?? composer}\n onSend={onSend}\n onStop={onStop}\n generating={generating}\n locale={locale}\n placeholder={placeholder}\n actions={composerActions}\n footer={composerFooter}\n disabled={composerDisabled}\n maxRows={maxRows}\n onError={onSendError}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- const e=require("../../utils/cn.cjs"),t=require("../VisuallyHidden/VisuallyHidden.cjs"),n=require("../../utils/numbers.cjs"),r=require("../CopyButton/CopyButton.cjs"),i=require("../Collapsible/Collapsible.cjs"),a=require("./ai-chat-turns.cjs"),o=require("./AIChat.module.cjs"),s=require("../Markdown/Markdown.cjs");let c=require("react"),l=require("react/jsx-runtime"),u=require("lucide-react");function d({message:d,locale:f=`pt-BR`,canRegenerate:p=!1,onRegenerate:m,onFeedback:h,onEditSubmit:g,onEditError:_,onRetry:v,renderAvatar:y,renderContent:b,vote:x,defaultReasoningOpen:S=!1,showLineNumbers:C=!1}){let w=a.aiChatStrings(f),{role:T,content:E,reasoning:D,streaming:O,error:k,model:A,createdAt:j,attachments:M}=d,[N,P]=(0,c.useState)(null),[F,I]=(0,c.useState)(!1),[L,R]=(0,c.useState)(E),[z,B]=(0,c.useState)(!1),V=x??N,H=T===`user`,U=(0,c.useMemo)(()=>(0,l.jsx)(s.Markdown,{source:E,showLineNumbers:C,className:o.default.markdownRoot}),[E,C]),W=b?b(d):H?(0,l.jsx)(`div`,{className:o.default.plain,children:E}):E===``?null:U,G=async()=>{let e=L.trim();if(!(!e||z||!g)){B(!0);try{await g(d,e),I(!1)}catch(e){_?.(e)}finally{B(!1)}}},K=e=>{V!==e&&(P(e),h?.(d,e))},q=!O&&!F,J=h!==void 0||p&&m!==void 0;return(0,l.jsxs)(`article`,{className:e.cn(o.default.turn,H&&o.default.userTurn,T===`system`&&o.default.systemTurn,O&&o.default.streamingTurn),"aria-busy":O||void 0,"data-role":T,children:[(0,l.jsxs)(`header`,{className:o.default.turnHeader,children:[y&&(0,l.jsx)(`span`,{className:o.default.avatar,"aria-hidden":`true`,children:y(d)}),(0,l.jsx)(`span`,{className:e.cn(o.default.role,H&&o.default.visuallyHiddenRole),children:a.roleLabel(T,w)}),A&&!H&&(0,l.jsx)(`span`,{className:o.default.model,children:A}),j!==void 0&&(0,l.jsx)(`time`,{className:o.default.time,dateTime:new Date(j).toISOString(),children:a.turnTime(j,f)})]}),M&&M.length>0&&(0,l.jsx)(`ul`,{className:o.default.attachments,children:M.map(e=>(0,l.jsx)(`li`,{className:o.default.attachment,children:e.url?(0,l.jsx)(`img`,{className:o.default.thumb,src:e.url,alt:e.name}):(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(`span`,{className:o.default.attachmentName,children:e.name}),(0,l.jsx)(`span`,{className:o.default.attachmentMeta,children:e.size===void 0?e.mimeType:n.formatBytes(e.size)})]})},e.id))}),D&&(0,l.jsx)(i.Collapsible,{className:o.default.reasoning,defaultOpen:S||O===!0&&E===``,trigger:(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(u.ChevronRight,{className:o.default.reasoningChevron,size:14,"aria-hidden":!0}),w.reasoning]}),children:(0,l.jsx)(`div`,{className:o.default.reasoningBody,children:D})}),F?(0,l.jsxs)(`div`,{className:o.default.editor,children:[(0,l.jsx)(t.VisuallyHidden,{children:(0,l.jsx)(`label`,{htmlFor:`${d.id}-edit`,children:w.editing})}),(0,l.jsx)(`textarea`,{id:`${d.id}-edit`,className:o.default.editField,value:L,rows:Math.min(L.split(`
1
+ const e=require("../../utils/cn.cjs"),t=require("../VisuallyHidden/VisuallyHidden.cjs"),n=require("../../utils/numbers.cjs"),r=require("../CopyButton/CopyButton.cjs"),i=require("../Collapsible/Collapsible.cjs"),a=require("./ai-chat-turns.cjs"),o=require("./AIChat.module.cjs"),s=require("../Markdown/Markdown.cjs");let c=require("react"),l=require("react/jsx-runtime"),u=require("lucide-react");function d({message:d,locale:f=`pt-BR`,canRegenerate:p=!1,onRegenerate:m,onFeedback:h,onEditSubmit:g,onEditError:_,onRetry:v,renderAvatar:y,renderContent:b,vote:x,defaultReasoningOpen:S=!1,showLineNumbers:C=!1}){let w=a.aiChatStrings(f),{role:T,content:E,reasoning:D,streaming:O,error:k,model:A,createdAt:j,attachments:M}=d,[N,P]=(0,c.useState)(null),[F,I]=(0,c.useState)(!1),[L,R]=(0,c.useState)(E),[z,B]=(0,c.useState)(!1),V=x??N,H=T===`user`,U=(0,c.useMemo)(()=>(0,l.jsx)(s.Markdown,{source:E,showLineNumbers:C,className:o.default.markdownRoot}),[E,C]),W=b?b(d):H?(0,l.jsx)(`div`,{className:o.default.plain,children:E}):E===``?null:U,G=async()=>{let e=L.trim();if(e&&!z&&g){B(!0);try{await g(d,e),I(!1)}catch(e){_?.(e)}finally{B(!1)}}},K=e=>{V!==e&&(P(e),h?.(d,e))},q=!O&&!F,J=h!==void 0||p&&m!==void 0;return(0,l.jsxs)(`article`,{className:e.cn(o.default.turn,H&&o.default.userTurn,T===`system`&&o.default.systemTurn,O&&o.default.streamingTurn),"aria-busy":O||void 0,"data-role":T,children:[(0,l.jsxs)(`header`,{className:o.default.turnHeader,children:[y&&(0,l.jsx)(`span`,{className:o.default.avatar,"aria-hidden":`true`,children:y(d)}),(0,l.jsx)(`span`,{className:e.cn(o.default.role,H&&o.default.visuallyHiddenRole),children:a.roleLabel(T,w)}),A&&!H&&(0,l.jsx)(`span`,{className:o.default.model,children:A}),j!==void 0&&(0,l.jsx)(`time`,{className:o.default.time,dateTime:new Date(j).toISOString(),children:a.turnTime(j,f)})]}),M&&M.length>0&&(0,l.jsx)(`ul`,{className:o.default.attachments,children:M.map(e=>(0,l.jsx)(`li`,{className:o.default.attachment,children:e.url?(0,l.jsx)(`img`,{className:o.default.thumb,src:e.url,alt:e.name}):(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(`span`,{className:o.default.attachmentName,children:e.name}),(0,l.jsx)(`span`,{className:o.default.attachmentMeta,children:e.size===void 0?e.mimeType:n.formatBytes(e.size)})]})},e.id))}),D&&(0,l.jsx)(i.Collapsible,{className:o.default.reasoning,defaultOpen:S||O===!0&&E===``,trigger:(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(u.ChevronRight,{className:o.default.reasoningChevron,size:14,"aria-hidden":!0}),w.reasoning]}),children:(0,l.jsx)(`div`,{className:o.default.reasoningBody,children:D})}),F?(0,l.jsxs)(`div`,{className:o.default.editor,children:[(0,l.jsx)(t.VisuallyHidden,{children:(0,l.jsx)(`label`,{htmlFor:`${d.id}-edit`,children:w.editing})}),(0,l.jsx)(`textarea`,{id:`${d.id}-edit`,className:o.default.editField,value:L,rows:Math.min(L.split(`
2
2
  `).length+1,10),onChange:e=>R(e.target.value)}),(0,l.jsxs)(`div`,{className:o.default.editActions,children:[(0,l.jsx)(`button`,{type:`button`,className:o.default.ghost,onClick:()=>{R(E),I(!1)},children:w.cancel}),(0,l.jsx)(`button`,{type:`button`,className:o.default.primary,disabled:z||L.trim()===``,onClick:()=>void G(),children:w.save})]})]}):(0,l.jsxs)(`div`,{className:e.cn(o.default.body,O&&o.default.streamingBody),children:[W,O===!0&&E===``&&(0,l.jsxs)(`span`,{className:o.default.dots,"aria-hidden":`true`,children:[(0,l.jsx)(`span`,{}),(0,l.jsx)(`span`,{}),(0,l.jsx)(`span`,{})]})]}),k&&(0,l.jsxs)(`p`,{className:o.default.error,role:`alert`,children:[(0,l.jsx)(`span`,{children:k}),v&&(0,l.jsx)(`button`,{type:`button`,className:o.default.retry,onClick:()=>v(d),children:w.retry})]}),q&&(0,l.jsxs)(`div`,{className:o.default.actions,"aria-label":w.turnActions,role:`group`,children:[(0,l.jsx)(r.CopyButton,{className:o.default.action,value:E,"aria-label":w.copy,title:w.copy,children:(0,l.jsx)(u.Copy,{size:14,"aria-hidden":!0})}),H&&g&&(0,l.jsx)(`button`,{type:`button`,className:o.default.action,"aria-label":w.edit,title:w.edit,onClick:()=>{R(E),I(!0)},children:(0,l.jsx)(u.Pencil,{size:14,"aria-hidden":!0})}),!H&&J&&(0,l.jsxs)(l.Fragment,{children:[p&&m&&(0,l.jsx)(`button`,{type:`button`,className:o.default.action,"aria-label":w.regenerate,title:w.regenerate,onClick:()=>m(d),children:(0,l.jsx)(u.RefreshCw,{size:14,"aria-hidden":!0})}),h&&(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(`button`,{type:`button`,className:e.cn(o.default.action,V===`up`&&o.default.actionActive),"aria-label":w.good,title:w.good,"aria-pressed":V===`up`,onClick:()=>K(`up`),children:(0,l.jsx)(u.ThumbsUp,{size:14,"aria-hidden":!0})}),(0,l.jsx)(`button`,{type:`button`,className:e.cn(o.default.action,V===`down`&&o.default.actionActive),"aria-label":w.bad,title:w.bad,"aria-pressed":V===`down`,onClick:()=>K(`down`),children:(0,l.jsx)(u.ThumbsDown,{size:14,"aria-hidden":!0})})]})]})]})]})}exports.AIChatTurn=d;
3
3
  //# sourceMappingURL=AIChatTurn.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"AIChatTurn.cjs","names":[],"sources":["../../../src/components/AIChat/AIChatTurn.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — one turn renders six\n * things that each have their own state: Markdown body, collapsible reasoning,\n * attachments, streaming caret, inline edit form and the action row. The 13 props\n * are the callbacks those actions need plus the render slots, and the body is long\n * because the edit form and the action row share the turn's message and its pending\n * state.\n */\nimport { ChevronRight, Copy, Pencil, RefreshCw, ThumbsDown, ThumbsUp } from \"lucide-react\";\nimport { useMemo, useState, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\nimport { formatBytes } from \"@/utils/numbers\";\n\nimport { Collapsible } from \"../Collapsible\";\nimport { CopyButton } from \"../CopyButton\";\nimport { Markdown } from \"../Markdown\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport {\n aiChatStrings,\n roleLabel,\n turnTime,\n type AIChatMessage,\n type AIChatVote,\n} from \"./ai-chat-turns\";\nimport styles from \"./AIChat.module.css\";\n\nexport interface AIChatTurnProps {\n /** The turn to render. */\n message: AIChatMessage;\n /** Locale for the labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /**\n * Offer the regenerate control.\n *\n * Only the newest assistant turn should get it — re-asking an older one throws\n * away every turn after it, which is a different operation and needs its own\n * confirmation.\n */\n canRegenerate?: boolean;\n onRegenerate?: (message: AIChatMessage) => void;\n onFeedback?: (message: AIChatMessage, vote: AIChatVote) => void;\n /** Enables the edit control on a user turn. Receives the edited prompt. */\n onEditSubmit?: (message: AIChatMessage, text: string) => void | Promise<void>;\n /**\n * Called when `onEditSubmit` rejects. The editor stays open with the draft either\n * way.\n *\n * Without it the rejection is swallowed after the draft is preserved: re-throwing\n * out of a click handler surfaces as an unhandled promise rejection, which is\n * console noise for the developer and nothing the user can act on.\n */\n onEditError?: (error: unknown) => void;\n /** Enables the retry control on a turn that carries an `error`. */\n onRetry?: (message: AIChatMessage) => void;\n renderAvatar?: (message: AIChatMessage) => ReactNode;\n /** Render the body yourself — a tool-call card, a chart, a citation list. */\n renderContent?: (message: AIChatMessage) => ReactNode;\n /**\n * Rating to show as pressed.\n *\n * Pass it to keep votes in app state (persisted across a reload); leave it out\n * and the pressed state is kept locally, which is enough for a fire-and-forget\n * `onFeedback`.\n */\n vote?: AIChatVote;\n /** Reasoning blocks start expanded. Default `false`. */\n defaultReasoningOpen?: boolean;\n /** Show line numbers in fenced code. Default `false`. */\n showLineNumbers?: boolean;\n}\n\n/**\n * One turn of a conversation with a model.\n *\n * Exported for apps that build their own transcript layout (a split view, a diff of\n * two answers) but still want the SDK's turn: Markdown body, reasoning block,\n * attachments, streaming caret, error state and the action row.\n *\n * @example\n * <AIChatTurn message={turn} canRegenerate onRegenerate={(m) => reask(m)} />\n */\nexport function AIChatTurn({\n message,\n locale = \"pt-BR\",\n canRegenerate = false,\n onRegenerate,\n onFeedback,\n onEditSubmit,\n onEditError,\n onRetry,\n renderAvatar,\n renderContent,\n vote,\n defaultReasoningOpen = false,\n showLineNumbers = false,\n}: AIChatTurnProps) {\n const strings = aiChatStrings(locale);\n const { role, content, reasoning, streaming, error, model, createdAt, attachments } = message;\n const [localVote, setLocalVote] = useState<AIChatVote | null>(null);\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(content);\n const [saving, setSaving] = useState(false);\n\n const currentVote = vote ?? localVote;\n const isUser = role === \"user\";\n\n /**\n * Memoised so a streaming answer re-parses **only the turn that is growing**.\n *\n * `Markdown` parses in its own render, and React skips re-rendering a child whose\n * element is referentially identical to the previous one. Holding the element\n * across renders is therefore what keeps a fifty-turn transcript from re-parsing\n * every finished answer on every token of the newest one.\n */\n const markdown = useMemo(\n () => (\n <Markdown\n source={content}\n showLineNumbers={showLineNumbers}\n className={styles.markdownRoot}\n />\n ),\n [content, showLineNumbers],\n );\n\n /**\n * Body of the turn.\n *\n * A user turn is plain text with newlines preserved, an assistant turn is\n * Markdown. A model emits Markdown by contract; a person typing `2 * 3 * 4` did\n * not mean to open an emphasis span.\n */\n const body = ((): ReactNode => {\n if (renderContent) return renderContent(message);\n if (isUser) return <div className={styles.plain}>{content}</div>;\n if (content === \"\") return null;\n return markdown;\n })();\n\n const submitEdit = async (): Promise<void> => {\n const text = draft.trim();\n if (!text || saving || !onEditSubmit) return;\n setSaving(true);\n try {\n await onEditSubmit(message, text);\n setEditing(false);\n } catch (error) {\n onEditError?.(error);\n } finally {\n setSaving(false);\n }\n };\n\n const rate = (next: AIChatVote): void => {\n if (currentVote === next) return;\n setLocalVote(next);\n onFeedback?.(message, next);\n };\n\n const showActions = !streaming && !editing;\n const hasAssistantActions =\n onFeedback !== undefined || (canRegenerate && onRegenerate !== undefined);\n\n return (\n <article\n className={cn(\n styles.turn,\n isUser && styles.userTurn,\n role === \"system\" && styles.systemTurn,\n streaming && styles.streamingTurn,\n )}\n aria-busy={streaming || undefined}\n data-role={role}\n >\n <header className={styles.turnHeader}>\n {renderAvatar && (\n <span className={styles.avatar} aria-hidden=\"true\">\n {renderAvatar(message)}\n </span>\n )}\n <span className={cn(styles.role, isUser && styles.visuallyHiddenRole)}>\n {roleLabel(role, strings)}\n </span>\n {model && !isUser && <span className={styles.model}>{model}</span>}\n {createdAt !== undefined && (\n <time className={styles.time} dateTime={new Date(createdAt).toISOString()}>\n {turnTime(createdAt, locale)}\n </time>\n )}\n </header>\n\n {attachments && attachments.length > 0 && (\n <ul className={styles.attachments}>\n {attachments.map((attachment) => (\n <li key={attachment.id} className={styles.attachment}>\n {attachment.url ? (\n <img\n className={styles.thumb}\n src={attachment.url}\n alt={attachment.name}\n />\n ) : (\n <>\n <span className={styles.attachmentName}>{attachment.name}</span>\n <span className={styles.attachmentMeta}>\n {attachment.size !== undefined\n ? formatBytes(attachment.size)\n : attachment.mimeType}\n </span>\n </>\n )}\n </li>\n ))}\n </ul>\n )}\n\n {reasoning && (\n <Collapsible\n className={styles.reasoning}\n defaultOpen={defaultReasoningOpen || (streaming === true && content === \"\")}\n trigger={\n <>\n <ChevronRight\n className={styles.reasoningChevron}\n size={14}\n aria-hidden\n />\n {strings.reasoning}\n </>\n }\n >\n <div className={styles.reasoningBody}>{reasoning}</div>\n </Collapsible>\n )}\n\n {editing ? (\n <div className={styles.editor}>\n <VisuallyHidden>\n <label htmlFor={`${message.id}-edit`}>{strings.editing}</label>\n </VisuallyHidden>\n <textarea\n id={`${message.id}-edit`}\n className={styles.editField}\n value={draft}\n rows={Math.min(draft.split(\"\\n\").length + 1, 10)}\n onChange={(event) => setDraft(event.target.value)}\n />\n <div className={styles.editActions}>\n <button\n type=\"button\"\n className={styles.ghost}\n onClick={() => {\n setDraft(content);\n setEditing(false);\n }}\n >\n {strings.cancel}\n </button>\n <button\n type=\"button\"\n className={styles.primary}\n disabled={saving || draft.trim() === \"\"}\n onClick={() => void submitEdit()}\n >\n {strings.save}\n </button>\n </div>\n </div>\n ) : (\n <div className={cn(styles.body, streaming && styles.streamingBody)}>\n {body}\n {streaming === true && content === \"\" && (\n <span className={styles.dots} aria-hidden=\"true\">\n <span />\n <span />\n <span />\n </span>\n )}\n </div>\n )}\n\n {error && (\n <p className={styles.error} role=\"alert\">\n <span>{error}</span>\n {onRetry && (\n <button\n type=\"button\"\n className={styles.retry}\n onClick={() => onRetry(message)}\n >\n {strings.retry}\n </button>\n )}\n </p>\n )}\n\n {showActions && (\n <div className={styles.actions} aria-label={strings.turnActions} role=\"group\">\n <CopyButton\n className={styles.action}\n value={content}\n aria-label={strings.copy}\n title={strings.copy}\n >\n <Copy size={14} aria-hidden />\n </CopyButton>\n\n {isUser && onEditSubmit && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.edit}\n title={strings.edit}\n onClick={() => {\n setDraft(content);\n setEditing(true);\n }}\n >\n <Pencil size={14} aria-hidden />\n </button>\n )}\n\n {!isUser && hasAssistantActions && (\n <>\n {canRegenerate && onRegenerate && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.regenerate}\n title={strings.regenerate}\n onClick={() => onRegenerate(message)}\n >\n <RefreshCw size={14} aria-hidden />\n </button>\n )}\n {onFeedback && (\n <>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"up\" && styles.actionActive,\n )}\n aria-label={strings.good}\n title={strings.good}\n aria-pressed={currentVote === \"up\"}\n onClick={() => rate(\"up\")}\n >\n <ThumbsUp size={14} aria-hidden />\n </button>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"down\" && styles.actionActive,\n )}\n aria-label={strings.bad}\n title={strings.bad}\n aria-pressed={currentVote === \"down\"}\n onClick={() => rate(\"down\")}\n >\n <ThumbsDown size={14} aria-hidden />\n </button>\n </>\n )}\n </>\n )}\n </div>\n )}\n </article>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AIChatTurn.cjs","names":[],"sources":["../../../src/components/AIChat/AIChatTurn.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — one turn renders six\n * things that each have their own state: Markdown body, collapsible reasoning,\n * attachments, streaming caret, inline edit form and the action row. The 13 props\n * are the callbacks those actions need plus the render slots, and the body is long\n * because the edit form and the action row share the turn's message and its pending\n * state.\n */\nimport { ChevronRight, Copy, Pencil, RefreshCw, ThumbsDown, ThumbsUp } from \"lucide-react\";\nimport { useMemo, useState, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\nimport { formatBytes } from \"@/utils/numbers\";\n\nimport { Collapsible } from \"../Collapsible\";\nimport { CopyButton } from \"../CopyButton\";\nimport { Markdown } from \"../Markdown\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport {\n aiChatStrings,\n roleLabel,\n turnTime,\n type AIChatMessage,\n type AIChatVote,\n} from \"./ai-chat-turns\";\nimport styles from \"./AIChat.module.css\";\n\nexport interface AIChatTurnProps {\n /** The turn to render. */\n message: AIChatMessage;\n /** Locale for the labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /**\n * Offer the regenerate control.\n *\n * Only the newest assistant turn should get it — re-asking an older one throws\n * away every turn after it, which is a different operation and needs its own\n * confirmation.\n */\n canRegenerate?: boolean;\n onRegenerate?: (message: AIChatMessage) => void;\n onFeedback?: (message: AIChatMessage, vote: AIChatVote) => void;\n /** Enables the edit control on a user turn. Receives the edited prompt. */\n onEditSubmit?: (message: AIChatMessage, text: string) => void | Promise<void>;\n /**\n * Called when `onEditSubmit` rejects. The editor stays open with the draft either\n * way.\n *\n * Without it the rejection is swallowed after the draft is preserved: re-throwing\n * out of a click handler surfaces as an unhandled promise rejection, which is\n * console noise for the developer and nothing the user can act on.\n */\n onEditError?: (error: unknown) => void;\n /** Enables the retry control on a turn that carries an `error`. */\n onRetry?: (message: AIChatMessage) => void;\n renderAvatar?: (message: AIChatMessage) => ReactNode;\n /** Render the body yourself — a tool-call card, a chart, a citation list. */\n renderContent?: (message: AIChatMessage) => ReactNode;\n /**\n * Rating to show as pressed.\n *\n * Pass it to keep votes in app state (persisted across a reload); leave it out\n * and the pressed state is kept locally, which is enough for a fire-and-forget\n * `onFeedback`.\n */\n vote?: AIChatVote;\n /** Reasoning blocks start expanded. Default `false`. */\n defaultReasoningOpen?: boolean;\n /** Show line numbers in fenced code. Default `false`. */\n showLineNumbers?: boolean;\n}\n\n/**\n * One turn of a conversation with a model.\n *\n * Exported for apps that build their own transcript layout (a split view, a diff of\n * two answers) but still want the SDK's turn: Markdown body, reasoning block,\n * attachments, streaming caret, error state and the action row.\n *\n * @example\n * <AIChatTurn message={turn} canRegenerate onRegenerate={(m) => reask(m)} />\n */\nexport function AIChatTurn({\n message,\n locale = \"pt-BR\",\n canRegenerate = false,\n onRegenerate,\n onFeedback,\n onEditSubmit,\n onEditError,\n onRetry,\n renderAvatar,\n renderContent,\n vote,\n defaultReasoningOpen = false,\n showLineNumbers = false,\n}: AIChatTurnProps) {\n const strings = aiChatStrings(locale);\n const { role, content, reasoning, streaming, error, model, createdAt, attachments } = message;\n const [localVote, setLocalVote] = useState<AIChatVote | null>(null);\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(content);\n const [saving, setSaving] = useState(false);\n\n const currentVote = vote ?? localVote;\n const isUser = role === \"user\";\n\n /**\n * Memoised so a streaming answer re-parses **only the turn that is growing**.\n *\n * `Markdown` parses in its own render, and React skips re-rendering a child whose\n * element is referentially identical to the previous one. Holding the element\n * across renders is therefore what keeps a fifty-turn transcript from re-parsing\n * every finished answer on every token of the newest one.\n */\n const markdown = useMemo(\n () => (\n <Markdown\n source={content}\n showLineNumbers={showLineNumbers}\n className={styles.markdownRoot}\n />\n ),\n [content, showLineNumbers],\n );\n\n /**\n * Body of the turn.\n *\n * A user turn is plain text with newlines preserved, an assistant turn is\n * Markdown. A model emits Markdown by contract; a person typing `2 * 3 * 4` did\n * not mean to open an emphasis span.\n */\n const body = ((): ReactNode => {\n if (renderContent) return renderContent(message);\n if (isUser) return <div className={styles.plain}>{content}</div>;\n if (content === \"\") return null;\n return markdown;\n })();\n\n const submitEdit = async (): Promise<void> => {\n const text = draft.trim();\n if (!text || saving || !onEditSubmit) return;\n setSaving(true);\n try {\n await onEditSubmit(message, text);\n setEditing(false);\n } catch (error) {\n onEditError?.(error);\n } finally {\n setSaving(false);\n }\n };\n\n const rate = (next: AIChatVote): void => {\n if (currentVote === next) return;\n setLocalVote(next);\n onFeedback?.(message, next);\n };\n\n const showActions = !streaming && !editing;\n const hasAssistantActions =\n onFeedback !== undefined || (canRegenerate && onRegenerate !== undefined);\n\n return (\n <article\n className={cn(\n styles.turn,\n isUser && styles.userTurn,\n role === \"system\" && styles.systemTurn,\n streaming && styles.streamingTurn,\n )}\n aria-busy={streaming || undefined}\n data-role={role}\n >\n <header className={styles.turnHeader}>\n {renderAvatar && (\n <span className={styles.avatar} aria-hidden=\"true\">\n {renderAvatar(message)}\n </span>\n )}\n <span className={cn(styles.role, isUser && styles.visuallyHiddenRole)}>\n {roleLabel(role, strings)}\n </span>\n {model && !isUser && <span className={styles.model}>{model}</span>}\n {createdAt !== undefined && (\n <time className={styles.time} dateTime={new Date(createdAt).toISOString()}>\n {turnTime(createdAt, locale)}\n </time>\n )}\n </header>\n\n {attachments && attachments.length > 0 && (\n <ul className={styles.attachments}>\n {attachments.map((attachment) => (\n <li key={attachment.id} className={styles.attachment}>\n {attachment.url ? (\n <img\n className={styles.thumb}\n src={attachment.url}\n alt={attachment.name}\n />\n ) : (\n <>\n <span className={styles.attachmentName}>{attachment.name}</span>\n <span className={styles.attachmentMeta}>\n {attachment.size !== undefined\n ? formatBytes(attachment.size)\n : attachment.mimeType}\n </span>\n </>\n )}\n </li>\n ))}\n </ul>\n )}\n\n {reasoning && (\n <Collapsible\n className={styles.reasoning}\n defaultOpen={defaultReasoningOpen || (streaming === true && content === \"\")}\n trigger={\n <>\n <ChevronRight\n className={styles.reasoningChevron}\n size={14}\n aria-hidden\n />\n {strings.reasoning}\n </>\n }\n >\n <div className={styles.reasoningBody}>{reasoning}</div>\n </Collapsible>\n )}\n\n {editing ? (\n <div className={styles.editor}>\n <VisuallyHidden>\n <label htmlFor={`${message.id}-edit`}>{strings.editing}</label>\n </VisuallyHidden>\n <textarea\n id={`${message.id}-edit`}\n className={styles.editField}\n value={draft}\n rows={Math.min(draft.split(\"\\n\").length + 1, 10)}\n onChange={(event) => setDraft(event.target.value)}\n />\n <div className={styles.editActions}>\n <button\n type=\"button\"\n className={styles.ghost}\n onClick={() => {\n setDraft(content);\n setEditing(false);\n }}\n >\n {strings.cancel}\n </button>\n <button\n type=\"button\"\n className={styles.primary}\n disabled={saving || draft.trim() === \"\"}\n onClick={() => void submitEdit()}\n >\n {strings.save}\n </button>\n </div>\n </div>\n ) : (\n <div className={cn(styles.body, streaming && styles.streamingBody)}>\n {body}\n {streaming === true && content === \"\" && (\n <span className={styles.dots} aria-hidden=\"true\">\n <span />\n <span />\n <span />\n </span>\n )}\n </div>\n )}\n\n {error && (\n <p className={styles.error} role=\"alert\">\n <span>{error}</span>\n {onRetry && (\n <button\n type=\"button\"\n className={styles.retry}\n onClick={() => onRetry(message)}\n >\n {strings.retry}\n </button>\n )}\n </p>\n )}\n\n {showActions && (\n <div className={styles.actions} aria-label={strings.turnActions} role=\"group\">\n <CopyButton\n className={styles.action}\n value={content}\n aria-label={strings.copy}\n title={strings.copy}\n >\n <Copy size={14} aria-hidden />\n </CopyButton>\n\n {isUser && onEditSubmit && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.edit}\n title={strings.edit}\n onClick={() => {\n setDraft(content);\n setEditing(true);\n }}\n >\n <Pencil size={14} aria-hidden />\n </button>\n )}\n\n {!isUser && hasAssistantActions && (\n <>\n {canRegenerate && onRegenerate && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.regenerate}\n title={strings.regenerate}\n onClick={() => onRegenerate(message)}\n >\n <RefreshCw size={14} aria-hidden />\n </button>\n )}\n {onFeedback && (\n <>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"up\" && styles.actionActive,\n )}\n aria-label={strings.good}\n title={strings.good}\n aria-pressed={currentVote === \"up\"}\n onClick={() => rate(\"up\")}\n >\n <ThumbsUp size={14} aria-hidden />\n </button>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"down\" && styles.actionActive,\n )}\n aria-label={strings.bad}\n title={strings.bad}\n aria-pressed={currentVote === \"down\"}\n onClick={() => rate(\"down\")}\n >\n <ThumbsDown size={14} aria-hidden />\n </button>\n </>\n )}\n </>\n )}\n </div>\n )}\n </article>\n );\n}\n"],"mappings":"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"}
@@ -20,7 +20,7 @@ function b({ message: b, locale: x = "pt-BR", canRegenerate: S = !1, onRegenerat
20
20
  children: F
21
21
  }) : F === "" ? null : Q, ne = async () => {
22
22
  let e = K.trim();
23
- if (!(!e || J || !T)) {
23
+ if (e && !J && T) {
24
24
  Y(!0);
25
25
  try {
26
26
  await T(b, e), G(!1);
@@ -1 +1 @@
1
- {"version":3,"file":"AIChatTurn.js","names":[],"sources":["../../../src/components/AIChat/AIChatTurn.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — one turn renders six\n * things that each have their own state: Markdown body, collapsible reasoning,\n * attachments, streaming caret, inline edit form and the action row. The 13 props\n * are the callbacks those actions need plus the render slots, and the body is long\n * because the edit form and the action row share the turn's message and its pending\n * state.\n */\nimport { ChevronRight, Copy, Pencil, RefreshCw, ThumbsDown, ThumbsUp } from \"lucide-react\";\nimport { useMemo, useState, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\nimport { formatBytes } from \"@/utils/numbers\";\n\nimport { Collapsible } from \"../Collapsible\";\nimport { CopyButton } from \"../CopyButton\";\nimport { Markdown } from \"../Markdown\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport {\n aiChatStrings,\n roleLabel,\n turnTime,\n type AIChatMessage,\n type AIChatVote,\n} from \"./ai-chat-turns\";\nimport styles from \"./AIChat.module.css\";\n\nexport interface AIChatTurnProps {\n /** The turn to render. */\n message: AIChatMessage;\n /** Locale for the labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /**\n * Offer the regenerate control.\n *\n * Only the newest assistant turn should get it — re-asking an older one throws\n * away every turn after it, which is a different operation and needs its own\n * confirmation.\n */\n canRegenerate?: boolean;\n onRegenerate?: (message: AIChatMessage) => void;\n onFeedback?: (message: AIChatMessage, vote: AIChatVote) => void;\n /** Enables the edit control on a user turn. Receives the edited prompt. */\n onEditSubmit?: (message: AIChatMessage, text: string) => void | Promise<void>;\n /**\n * Called when `onEditSubmit` rejects. The editor stays open with the draft either\n * way.\n *\n * Without it the rejection is swallowed after the draft is preserved: re-throwing\n * out of a click handler surfaces as an unhandled promise rejection, which is\n * console noise for the developer and nothing the user can act on.\n */\n onEditError?: (error: unknown) => void;\n /** Enables the retry control on a turn that carries an `error`. */\n onRetry?: (message: AIChatMessage) => void;\n renderAvatar?: (message: AIChatMessage) => ReactNode;\n /** Render the body yourself — a tool-call card, a chart, a citation list. */\n renderContent?: (message: AIChatMessage) => ReactNode;\n /**\n * Rating to show as pressed.\n *\n * Pass it to keep votes in app state (persisted across a reload); leave it out\n * and the pressed state is kept locally, which is enough for a fire-and-forget\n * `onFeedback`.\n */\n vote?: AIChatVote;\n /** Reasoning blocks start expanded. Default `false`. */\n defaultReasoningOpen?: boolean;\n /** Show line numbers in fenced code. Default `false`. */\n showLineNumbers?: boolean;\n}\n\n/**\n * One turn of a conversation with a model.\n *\n * Exported for apps that build their own transcript layout (a split view, a diff of\n * two answers) but still want the SDK's turn: Markdown body, reasoning block,\n * attachments, streaming caret, error state and the action row.\n *\n * @example\n * <AIChatTurn message={turn} canRegenerate onRegenerate={(m) => reask(m)} />\n */\nexport function AIChatTurn({\n message,\n locale = \"pt-BR\",\n canRegenerate = false,\n onRegenerate,\n onFeedback,\n onEditSubmit,\n onEditError,\n onRetry,\n renderAvatar,\n renderContent,\n vote,\n defaultReasoningOpen = false,\n showLineNumbers = false,\n}: AIChatTurnProps) {\n const strings = aiChatStrings(locale);\n const { role, content, reasoning, streaming, error, model, createdAt, attachments } = message;\n const [localVote, setLocalVote] = useState<AIChatVote | null>(null);\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(content);\n const [saving, setSaving] = useState(false);\n\n const currentVote = vote ?? localVote;\n const isUser = role === \"user\";\n\n /**\n * Memoised so a streaming answer re-parses **only the turn that is growing**.\n *\n * `Markdown` parses in its own render, and React skips re-rendering a child whose\n * element is referentially identical to the previous one. Holding the element\n * across renders is therefore what keeps a fifty-turn transcript from re-parsing\n * every finished answer on every token of the newest one.\n */\n const markdown = useMemo(\n () => (\n <Markdown\n source={content}\n showLineNumbers={showLineNumbers}\n className={styles.markdownRoot}\n />\n ),\n [content, showLineNumbers],\n );\n\n /**\n * Body of the turn.\n *\n * A user turn is plain text with newlines preserved, an assistant turn is\n * Markdown. A model emits Markdown by contract; a person typing `2 * 3 * 4` did\n * not mean to open an emphasis span.\n */\n const body = ((): ReactNode => {\n if (renderContent) return renderContent(message);\n if (isUser) return <div className={styles.plain}>{content}</div>;\n if (content === \"\") return null;\n return markdown;\n })();\n\n const submitEdit = async (): Promise<void> => {\n const text = draft.trim();\n if (!text || saving || !onEditSubmit) return;\n setSaving(true);\n try {\n await onEditSubmit(message, text);\n setEditing(false);\n } catch (error) {\n onEditError?.(error);\n } finally {\n setSaving(false);\n }\n };\n\n const rate = (next: AIChatVote): void => {\n if (currentVote === next) return;\n setLocalVote(next);\n onFeedback?.(message, next);\n };\n\n const showActions = !streaming && !editing;\n const hasAssistantActions =\n onFeedback !== undefined || (canRegenerate && onRegenerate !== undefined);\n\n return (\n <article\n className={cn(\n styles.turn,\n isUser && styles.userTurn,\n role === \"system\" && styles.systemTurn,\n streaming && styles.streamingTurn,\n )}\n aria-busy={streaming || undefined}\n data-role={role}\n >\n <header className={styles.turnHeader}>\n {renderAvatar && (\n <span className={styles.avatar} aria-hidden=\"true\">\n {renderAvatar(message)}\n </span>\n )}\n <span className={cn(styles.role, isUser && styles.visuallyHiddenRole)}>\n {roleLabel(role, strings)}\n </span>\n {model && !isUser && <span className={styles.model}>{model}</span>}\n {createdAt !== undefined && (\n <time className={styles.time} dateTime={new Date(createdAt).toISOString()}>\n {turnTime(createdAt, locale)}\n </time>\n )}\n </header>\n\n {attachments && attachments.length > 0 && (\n <ul className={styles.attachments}>\n {attachments.map((attachment) => (\n <li key={attachment.id} className={styles.attachment}>\n {attachment.url ? (\n <img\n className={styles.thumb}\n src={attachment.url}\n alt={attachment.name}\n />\n ) : (\n <>\n <span className={styles.attachmentName}>{attachment.name}</span>\n <span className={styles.attachmentMeta}>\n {attachment.size !== undefined\n ? formatBytes(attachment.size)\n : attachment.mimeType}\n </span>\n </>\n )}\n </li>\n ))}\n </ul>\n )}\n\n {reasoning && (\n <Collapsible\n className={styles.reasoning}\n defaultOpen={defaultReasoningOpen || (streaming === true && content === \"\")}\n trigger={\n <>\n <ChevronRight\n className={styles.reasoningChevron}\n size={14}\n aria-hidden\n />\n {strings.reasoning}\n </>\n }\n >\n <div className={styles.reasoningBody}>{reasoning}</div>\n </Collapsible>\n )}\n\n {editing ? (\n <div className={styles.editor}>\n <VisuallyHidden>\n <label htmlFor={`${message.id}-edit`}>{strings.editing}</label>\n </VisuallyHidden>\n <textarea\n id={`${message.id}-edit`}\n className={styles.editField}\n value={draft}\n rows={Math.min(draft.split(\"\\n\").length + 1, 10)}\n onChange={(event) => setDraft(event.target.value)}\n />\n <div className={styles.editActions}>\n <button\n type=\"button\"\n className={styles.ghost}\n onClick={() => {\n setDraft(content);\n setEditing(false);\n }}\n >\n {strings.cancel}\n </button>\n <button\n type=\"button\"\n className={styles.primary}\n disabled={saving || draft.trim() === \"\"}\n onClick={() => void submitEdit()}\n >\n {strings.save}\n </button>\n </div>\n </div>\n ) : (\n <div className={cn(styles.body, streaming && styles.streamingBody)}>\n {body}\n {streaming === true && content === \"\" && (\n <span className={styles.dots} aria-hidden=\"true\">\n <span />\n <span />\n <span />\n </span>\n )}\n </div>\n )}\n\n {error && (\n <p className={styles.error} role=\"alert\">\n <span>{error}</span>\n {onRetry && (\n <button\n type=\"button\"\n className={styles.retry}\n onClick={() => onRetry(message)}\n >\n {strings.retry}\n </button>\n )}\n </p>\n )}\n\n {showActions && (\n <div className={styles.actions} aria-label={strings.turnActions} role=\"group\">\n <CopyButton\n className={styles.action}\n value={content}\n aria-label={strings.copy}\n title={strings.copy}\n >\n <Copy size={14} aria-hidden />\n </CopyButton>\n\n {isUser && onEditSubmit && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.edit}\n title={strings.edit}\n onClick={() => {\n setDraft(content);\n setEditing(true);\n }}\n >\n <Pencil size={14} aria-hidden />\n </button>\n )}\n\n {!isUser && hasAssistantActions && (\n <>\n {canRegenerate && onRegenerate && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.regenerate}\n title={strings.regenerate}\n onClick={() => onRegenerate(message)}\n >\n <RefreshCw size={14} aria-hidden />\n </button>\n )}\n {onFeedback && (\n <>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"up\" && styles.actionActive,\n )}\n aria-label={strings.good}\n title={strings.good}\n aria-pressed={currentVote === \"up\"}\n onClick={() => rate(\"up\")}\n >\n <ThumbsUp size={14} aria-hidden />\n </button>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"down\" && styles.actionActive,\n )}\n aria-label={strings.bad}\n title={strings.bad}\n aria-pressed={currentVote === \"down\"}\n onClick={() => rate(\"down\")}\n >\n <ThumbsDown size={14} aria-hidden />\n </button>\n </>\n )}\n </>\n )}\n </div>\n )}\n </article>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AIChatTurn.js","names":[],"sources":["../../../src/components/AIChat/AIChatTurn.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — one turn renders six\n * things that each have their own state: Markdown body, collapsible reasoning,\n * attachments, streaming caret, inline edit form and the action row. The 13 props\n * are the callbacks those actions need plus the render slots, and the body is long\n * because the edit form and the action row share the turn's message and its pending\n * state.\n */\nimport { ChevronRight, Copy, Pencil, RefreshCw, ThumbsDown, ThumbsUp } from \"lucide-react\";\nimport { useMemo, useState, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\nimport { formatBytes } from \"@/utils/numbers\";\n\nimport { Collapsible } from \"../Collapsible\";\nimport { CopyButton } from \"../CopyButton\";\nimport { Markdown } from \"../Markdown\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport {\n aiChatStrings,\n roleLabel,\n turnTime,\n type AIChatMessage,\n type AIChatVote,\n} from \"./ai-chat-turns\";\nimport styles from \"./AIChat.module.css\";\n\nexport interface AIChatTurnProps {\n /** The turn to render. */\n message: AIChatMessage;\n /** Locale for the labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /**\n * Offer the regenerate control.\n *\n * Only the newest assistant turn should get it — re-asking an older one throws\n * away every turn after it, which is a different operation and needs its own\n * confirmation.\n */\n canRegenerate?: boolean;\n onRegenerate?: (message: AIChatMessage) => void;\n onFeedback?: (message: AIChatMessage, vote: AIChatVote) => void;\n /** Enables the edit control on a user turn. Receives the edited prompt. */\n onEditSubmit?: (message: AIChatMessage, text: string) => void | Promise<void>;\n /**\n * Called when `onEditSubmit` rejects. The editor stays open with the draft either\n * way.\n *\n * Without it the rejection is swallowed after the draft is preserved: re-throwing\n * out of a click handler surfaces as an unhandled promise rejection, which is\n * console noise for the developer and nothing the user can act on.\n */\n onEditError?: (error: unknown) => void;\n /** Enables the retry control on a turn that carries an `error`. */\n onRetry?: (message: AIChatMessage) => void;\n renderAvatar?: (message: AIChatMessage) => ReactNode;\n /** Render the body yourself — a tool-call card, a chart, a citation list. */\n renderContent?: (message: AIChatMessage) => ReactNode;\n /**\n * Rating to show as pressed.\n *\n * Pass it to keep votes in app state (persisted across a reload); leave it out\n * and the pressed state is kept locally, which is enough for a fire-and-forget\n * `onFeedback`.\n */\n vote?: AIChatVote;\n /** Reasoning blocks start expanded. Default `false`. */\n defaultReasoningOpen?: boolean;\n /** Show line numbers in fenced code. Default `false`. */\n showLineNumbers?: boolean;\n}\n\n/**\n * One turn of a conversation with a model.\n *\n * Exported for apps that build their own transcript layout (a split view, a diff of\n * two answers) but still want the SDK's turn: Markdown body, reasoning block,\n * attachments, streaming caret, error state and the action row.\n *\n * @example\n * <AIChatTurn message={turn} canRegenerate onRegenerate={(m) => reask(m)} />\n */\nexport function AIChatTurn({\n message,\n locale = \"pt-BR\",\n canRegenerate = false,\n onRegenerate,\n onFeedback,\n onEditSubmit,\n onEditError,\n onRetry,\n renderAvatar,\n renderContent,\n vote,\n defaultReasoningOpen = false,\n showLineNumbers = false,\n}: AIChatTurnProps) {\n const strings = aiChatStrings(locale);\n const { role, content, reasoning, streaming, error, model, createdAt, attachments } = message;\n const [localVote, setLocalVote] = useState<AIChatVote | null>(null);\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(content);\n const [saving, setSaving] = useState(false);\n\n const currentVote = vote ?? localVote;\n const isUser = role === \"user\";\n\n /**\n * Memoised so a streaming answer re-parses **only the turn that is growing**.\n *\n * `Markdown` parses in its own render, and React skips re-rendering a child whose\n * element is referentially identical to the previous one. Holding the element\n * across renders is therefore what keeps a fifty-turn transcript from re-parsing\n * every finished answer on every token of the newest one.\n */\n const markdown = useMemo(\n () => (\n <Markdown\n source={content}\n showLineNumbers={showLineNumbers}\n className={styles.markdownRoot}\n />\n ),\n [content, showLineNumbers],\n );\n\n /**\n * Body of the turn.\n *\n * A user turn is plain text with newlines preserved, an assistant turn is\n * Markdown. A model emits Markdown by contract; a person typing `2 * 3 * 4` did\n * not mean to open an emphasis span.\n */\n const body = ((): ReactNode => {\n if (renderContent) return renderContent(message);\n if (isUser) return <div className={styles.plain}>{content}</div>;\n if (content === \"\") return null;\n return markdown;\n })();\n\n const submitEdit = async (): Promise<void> => {\n const text = draft.trim();\n if (!text || saving || !onEditSubmit) return;\n setSaving(true);\n try {\n await onEditSubmit(message, text);\n setEditing(false);\n } catch (error) {\n onEditError?.(error);\n } finally {\n setSaving(false);\n }\n };\n\n const rate = (next: AIChatVote): void => {\n if (currentVote === next) return;\n setLocalVote(next);\n onFeedback?.(message, next);\n };\n\n const showActions = !streaming && !editing;\n const hasAssistantActions =\n onFeedback !== undefined || (canRegenerate && onRegenerate !== undefined);\n\n return (\n <article\n className={cn(\n styles.turn,\n isUser && styles.userTurn,\n role === \"system\" && styles.systemTurn,\n streaming && styles.streamingTurn,\n )}\n aria-busy={streaming || undefined}\n data-role={role}\n >\n <header className={styles.turnHeader}>\n {renderAvatar && (\n <span className={styles.avatar} aria-hidden=\"true\">\n {renderAvatar(message)}\n </span>\n )}\n <span className={cn(styles.role, isUser && styles.visuallyHiddenRole)}>\n {roleLabel(role, strings)}\n </span>\n {model && !isUser && <span className={styles.model}>{model}</span>}\n {createdAt !== undefined && (\n <time className={styles.time} dateTime={new Date(createdAt).toISOString()}>\n {turnTime(createdAt, locale)}\n </time>\n )}\n </header>\n\n {attachments && attachments.length > 0 && (\n <ul className={styles.attachments}>\n {attachments.map((attachment) => (\n <li key={attachment.id} className={styles.attachment}>\n {attachment.url ? (\n <img\n className={styles.thumb}\n src={attachment.url}\n alt={attachment.name}\n />\n ) : (\n <>\n <span className={styles.attachmentName}>{attachment.name}</span>\n <span className={styles.attachmentMeta}>\n {attachment.size !== undefined\n ? formatBytes(attachment.size)\n : attachment.mimeType}\n </span>\n </>\n )}\n </li>\n ))}\n </ul>\n )}\n\n {reasoning && (\n <Collapsible\n className={styles.reasoning}\n defaultOpen={defaultReasoningOpen || (streaming === true && content === \"\")}\n trigger={\n <>\n <ChevronRight\n className={styles.reasoningChevron}\n size={14}\n aria-hidden\n />\n {strings.reasoning}\n </>\n }\n >\n <div className={styles.reasoningBody}>{reasoning}</div>\n </Collapsible>\n )}\n\n {editing ? (\n <div className={styles.editor}>\n <VisuallyHidden>\n <label htmlFor={`${message.id}-edit`}>{strings.editing}</label>\n </VisuallyHidden>\n <textarea\n id={`${message.id}-edit`}\n className={styles.editField}\n value={draft}\n rows={Math.min(draft.split(\"\\n\").length + 1, 10)}\n onChange={(event) => setDraft(event.target.value)}\n />\n <div className={styles.editActions}>\n <button\n type=\"button\"\n className={styles.ghost}\n onClick={() => {\n setDraft(content);\n setEditing(false);\n }}\n >\n {strings.cancel}\n </button>\n <button\n type=\"button\"\n className={styles.primary}\n disabled={saving || draft.trim() === \"\"}\n onClick={() => void submitEdit()}\n >\n {strings.save}\n </button>\n </div>\n </div>\n ) : (\n <div className={cn(styles.body, streaming && styles.streamingBody)}>\n {body}\n {streaming === true && content === \"\" && (\n <span className={styles.dots} aria-hidden=\"true\">\n <span />\n <span />\n <span />\n </span>\n )}\n </div>\n )}\n\n {error && (\n <p className={styles.error} role=\"alert\">\n <span>{error}</span>\n {onRetry && (\n <button\n type=\"button\"\n className={styles.retry}\n onClick={() => onRetry(message)}\n >\n {strings.retry}\n </button>\n )}\n </p>\n )}\n\n {showActions && (\n <div className={styles.actions} aria-label={strings.turnActions} role=\"group\">\n <CopyButton\n className={styles.action}\n value={content}\n aria-label={strings.copy}\n title={strings.copy}\n >\n <Copy size={14} aria-hidden />\n </CopyButton>\n\n {isUser && onEditSubmit && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.edit}\n title={strings.edit}\n onClick={() => {\n setDraft(content);\n setEditing(true);\n }}\n >\n <Pencil size={14} aria-hidden />\n </button>\n )}\n\n {!isUser && hasAssistantActions && (\n <>\n {canRegenerate && onRegenerate && (\n <button\n type=\"button\"\n className={styles.action}\n aria-label={strings.regenerate}\n title={strings.regenerate}\n onClick={() => onRegenerate(message)}\n >\n <RefreshCw size={14} aria-hidden />\n </button>\n )}\n {onFeedback && (\n <>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"up\" && styles.actionActive,\n )}\n aria-label={strings.good}\n title={strings.good}\n aria-pressed={currentVote === \"up\"}\n onClick={() => rate(\"up\")}\n >\n <ThumbsUp size={14} aria-hidden />\n </button>\n <button\n type=\"button\"\n className={cn(\n styles.action,\n currentVote === \"down\" && styles.actionActive,\n )}\n aria-label={strings.bad}\n title={strings.bad}\n aria-pressed={currentVote === \"down\"}\n onClick={() => rate(\"down\")}\n >\n <ThumbsDown size={14} aria-hidden />\n </button>\n </>\n )}\n </>\n )}\n </div>\n )}\n </article>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"Alert.module.cjs","names":[],"sources":["../../../src/components/Alert/Alert.module.css"],"sourcesContent":[".alert {\n display: flex;\n align-items: flex-start;\n gap: var(--tempest-space-3);\n padding: var(--tempest-space-3) var(--tempest-space-4);\n border: 1px solid transparent;\n border-radius: var(--tempest-radius-lg);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n line-height: var(--tempest-leading-snug);\n color: var(--tempest-text);\n width: 100%;\n}\n\n.content {\n flex: 1;\n min-width: 0;\n}\n\n.title {\n margin: 0;\n font-size: var(--tempest-text-base);\n font-weight: var(--tempest-weight-semibold);\n line-height: var(--tempest-leading-snug);\n}\n\n.description {\n margin: 2px 0 0 0;\n font-size: var(--tempest-text-sm);\n line-height: var(--tempest-leading-snug);\n color: var(--tempest-text-muted);\n}\n\n.icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 20px;\n height: 20px;\n margin-top: 1px;\n}\n\n.close {\n margin-left: auto;\n background: none;\n border: none;\n color: inherit;\n opacity: 0.7;\n padding: 4px;\n border-radius: var(--tempest-radius-sm);\n display: flex;\n align-items: center;\n transition:\n var(--tempest-transition-color),\n opacity var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.close:hover {\n opacity: 1;\n background-color: rgba(15, 23, 42, 0.06);\n}\n\n.close:focus-visible {\n outline: var(--tempest-focus-ring-width) solid var(--tempest-focus-ring-color);\n outline-offset: var(--tempest-focus-ring-offset);\n}\n\n/* ============================================================\n * Tone × Appearance matrix\n * ============================================================ */\n\n/* neutral */\n.neutral {\n background-color: var(--tempest-surface);\n color: var(--tempest-text);\n border-color: var(--tempest-border);\n}\n.neutral.solid {\n background-color: var(--tempest-gray-700);\n color: #ffffff;\n border-color: transparent;\n}\n.neutral.outline {\n background-color: transparent;\n border-color: var(--tempest-border-strong);\n}\n\n/* info */\n.info {\n background-color: var(--tempest-info-bg);\n color: var(--tempest-info-fg);\n border-color: var(--tempest-info-border);\n}\n.info .description {\n color: var(--tempest-info-fg);\n opacity: 0.85;\n}\n.info.solid {\n background-color: var(--tempest-info-solid);\n color: var(--tempest-info-on-solid);\n border-color: transparent;\n}\n.info.solid .description {\n color: rgba(255, 255, 255, 0.9);\n}\n.info.outline {\n background-color: transparent;\n color: var(--tempest-info);\n border-color: var(--tempest-info);\n}\n\n/* success */\n.success {\n background-color: var(--tempest-success-bg);\n color: var(--tempest-success-fg);\n border-color: var(--tempest-success-border);\n}\n.success .description {\n color: var(--tempest-success-fg);\n opacity: 0.85;\n}\n.success.solid {\n background-color: var(--tempest-success-solid);\n color: var(--tempest-success-on-solid);\n border-color: transparent;\n}\n.success.solid .description {\n color: rgba(255, 255, 255, 0.9);\n}\n.success.outline {\n background-color: transparent;\n color: var(--tempest-success);\n border-color: var(--tempest-success);\n}\n\n/* warning */\n.warning {\n background-color: var(--tempest-warning-bg);\n color: var(--tempest-warning-fg);\n border-color: var(--tempest-warning-border);\n}\n.warning .description {\n color: var(--tempest-warning-fg);\n opacity: 0.85;\n}\n.warning.solid {\n background-color: var(--tempest-warning-solid);\n color: var(--tempest-warning-on-solid);\n border-color: transparent;\n}\n.warning.solid .description {\n color: rgba(255, 255, 255, 0.9);\n}\n.warning.outline {\n background-color: transparent;\n color: var(--tempest-warning);\n border-color: var(--tempest-warning);\n}\n\n/* danger */\n.danger {\n background-color: var(--tempest-danger-bg);\n color: var(--tempest-danger-fg);\n border-color: var(--tempest-danger-border);\n}\n.danger .description {\n color: var(--tempest-danger-fg);\n opacity: 0.85;\n}\n.danger.solid {\n background-color: var(--tempest-danger-solid);\n color: var(--tempest-danger-on-solid);\n border-color: transparent;\n}\n.danger.solid .description {\n color: rgba(255, 255, 255, 0.9);\n}\n.danger.outline {\n background-color: transparent;\n color: var(--tempest-danger);\n border-color: var(--tempest-danger);\n}\n"],"mappings":""}
1
+ {"version":3,"file":"Alert.module.cjs","names":[],"sources":["../../../src/components/Alert/Alert.module.css"],"sourcesContent":[".alert {\n display: flex;\n align-items: flex-start;\n gap: var(--tempest-space-3);\n padding: var(--tempest-space-3) var(--tempest-space-4);\n border: 1px solid transparent;\n border-radius: var(--tempest-radius-lg);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n line-height: var(--tempest-leading-snug);\n color: var(--tempest-text);\n width: 100%;\n}\n\n.content {\n flex: 1;\n min-width: 0;\n}\n\n.title {\n margin: 0;\n font-size: var(--tempest-text-base);\n font-weight: var(--tempest-weight-semibold);\n line-height: var(--tempest-leading-snug);\n}\n\n.description {\n margin: 2px 0 0 0;\n font-size: var(--tempest-text-sm);\n line-height: var(--tempest-leading-snug);\n color: var(--tempest-text-muted);\n}\n\n.icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 20px;\n height: 20px;\n margin-top: 1px;\n}\n\n.close {\n margin-left: auto;\n background: none;\n border: none;\n color: inherit;\n opacity: 0.7;\n padding: 4px;\n border-radius: var(--tempest-radius-sm);\n display: flex;\n align-items: center;\n transition:\n var(--tempest-transition-color),\n opacity var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.close:hover {\n opacity: 1;\n background-color: rgba(15, 23, 42, 0.06);\n}\n\n.close:focus-visible {\n outline: var(--tempest-focus-ring-width) solid var(--tempest-focus-ring-color);\n outline-offset: var(--tempest-focus-ring-offset);\n}\n\n/* ============================================================\n * Tone × Appearance matrix\n * ============================================================ */\n\n/* neutral */\n.neutral {\n background-color: var(--tempest-surface);\n color: var(--tempest-text);\n border-color: var(--tempest-border);\n}\n.neutral.solid {\n background-color: var(--tempest-gray-700);\n color: var(--tempest-neutral-on-solid);\n border-color: transparent;\n}\n.neutral.outline {\n background-color: transparent;\n border-color: var(--tempest-border-strong);\n}\n\n/* info */\n.info {\n background-color: var(--tempest-info-bg);\n color: var(--tempest-info-fg);\n border-color: var(--tempest-info-border);\n}\n.info .description {\n color: var(--tempest-info-fg);\n opacity: 0.85;\n}\n.info.solid {\n background-color: var(--tempest-info-solid);\n color: var(--tempest-info-on-solid);\n border-color: transparent;\n}\n.info.solid .description {\n color: rgba(255, 255, 255, 0.9);\n}\n.info.outline {\n background-color: transparent;\n color: var(--tempest-info);\n border-color: var(--tempest-info);\n}\n\n/* success */\n.success {\n background-color: var(--tempest-success-bg);\n color: var(--tempest-success-fg);\n border-color: var(--tempest-success-border);\n}\n.success .description {\n color: var(--tempest-success-fg);\n opacity: 0.85;\n}\n.success.solid {\n background-color: var(--tempest-success-solid);\n color: var(--tempest-success-on-solid);\n border-color: transparent;\n}\n.success.solid .description {\n color: rgba(255, 255, 255, 0.9);\n}\n.success.outline {\n background-color: transparent;\n color: var(--tempest-success);\n border-color: var(--tempest-success);\n}\n\n/* warning */\n.warning {\n background-color: var(--tempest-warning-bg);\n color: var(--tempest-warning-fg);\n border-color: var(--tempest-warning-border);\n}\n.warning .description {\n color: var(--tempest-warning-fg);\n opacity: 0.85;\n}\n.warning.solid {\n background-color: var(--tempest-warning-solid);\n color: var(--tempest-warning-on-solid);\n border-color: transparent;\n}\n.warning.solid .description {\n color: rgba(255, 255, 255, 0.9);\n}\n.warning.outline {\n background-color: transparent;\n color: var(--tempest-warning);\n border-color: var(--tempest-warning);\n}\n\n/* danger */\n.danger {\n background-color: var(--tempest-danger-bg);\n color: var(--tempest-danger-fg);\n border-color: var(--tempest-danger-border);\n}\n.danger .description {\n color: var(--tempest-danger-fg);\n opacity: 0.85;\n}\n.danger.solid {\n background-color: var(--tempest-danger-solid);\n color: var(--tempest-danger-on-solid);\n border-color: transparent;\n}\n.danger.solid .description {\n color: rgba(255, 255, 255, 0.9);\n}\n.danger.outline {\n background-color: transparent;\n color: var(--tempest-danger);\n border-color: var(--tempest-danger);\n}\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"Alert.module.js","names":[],"sources":["../../../src/components/Alert/Alert.module.css"],"sourcesContent":[".alert {\n display: flex;\n align-items: flex-start;\n gap: var(--tempest-space-3);\n padding: var(--tempest-space-3) var(--tempest-space-4);\n border: 1px solid transparent;\n border-radius: var(--tempest-radius-lg);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n line-height: var(--tempest-leading-snug);\n color: var(--tempest-text);\n width: 100%;\n}\n\n.content {\n flex: 1;\n min-width: 0;\n}\n\n.title {\n margin: 0;\n font-size: var(--tempest-text-base);\n font-weight: var(--tempest-weight-semibold);\n line-height: var(--tempest-leading-snug);\n}\n\n.description {\n margin: 2px 0 0 0;\n font-size: var(--tempest-text-sm);\n line-height: var(--tempest-leading-snug);\n color: var(--tempest-text-muted);\n}\n\n.icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 20px;\n height: 20px;\n margin-top: 1px;\n}\n\n.close {\n margin-left: auto;\n background: none;\n border: none;\n color: inherit;\n opacity: 0.7;\n padding: 4px;\n border-radius: var(--tempest-radius-sm);\n display: flex;\n align-items: center;\n transition:\n var(--tempest-transition-color),\n opacity var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.close:hover {\n opacity: 1;\n background-color: rgba(15, 23, 42, 0.06);\n}\n\n.close:focus-visible {\n outline: var(--tempest-focus-ring-width) solid var(--tempest-focus-ring-color);\n outline-offset: var(--tempest-focus-ring-offset);\n}\n\n/* ============================================================\n * Tone × Appearance matrix\n * ============================================================ */\n\n/* neutral */\n.neutral {\n background-color: var(--tempest-surface);\n color: var(--tempest-text);\n border-color: var(--tempest-border);\n}\n.neutral.solid {\n background-color: var(--tempest-gray-700);\n color: #ffffff;\n border-color: transparent;\n}\n.neutral.outline {\n background-color: transparent;\n border-color: var(--tempest-border-strong);\n}\n\n/* info */\n.info {\n background-color: var(--tempest-info-bg);\n color: var(--tempest-info-fg);\n border-color: var(--tempest-info-border);\n}\n.info .description {\n color: var(--tempest-info-fg);\n opacity: 0.85;\n}\n.info.solid {\n background-color: var(--tempest-info-solid);\n color: var(--tempest-info-on-solid);\n border-color: transparent;\n}\n.info.solid .description {\n color: rgba(255, 255, 255, 0.9);\n}\n.info.outline {\n background-color: transparent;\n color: var(--tempest-info);\n border-color: var(--tempest-info);\n}\n\n/* success */\n.success {\n background-color: var(--tempest-success-bg);\n color: var(--tempest-success-fg);\n border-color: var(--tempest-success-border);\n}\n.success .description {\n color: var(--tempest-success-fg);\n opacity: 0.85;\n}\n.success.solid {\n background-color: var(--tempest-success-solid);\n color: var(--tempest-success-on-solid);\n border-color: transparent;\n}\n.success.solid .description {\n color: rgba(255, 255, 255, 0.9);\n}\n.success.outline {\n background-color: transparent;\n color: var(--tempest-success);\n border-color: var(--tempest-success);\n}\n\n/* warning */\n.warning {\n background-color: var(--tempest-warning-bg);\n color: var(--tempest-warning-fg);\n border-color: var(--tempest-warning-border);\n}\n.warning .description {\n color: var(--tempest-warning-fg);\n opacity: 0.85;\n}\n.warning.solid {\n background-color: var(--tempest-warning-solid);\n color: var(--tempest-warning-on-solid);\n border-color: transparent;\n}\n.warning.solid .description {\n color: rgba(255, 255, 255, 0.9);\n}\n.warning.outline {\n background-color: transparent;\n color: var(--tempest-warning);\n border-color: var(--tempest-warning);\n}\n\n/* danger */\n.danger {\n background-color: var(--tempest-danger-bg);\n color: var(--tempest-danger-fg);\n border-color: var(--tempest-danger-border);\n}\n.danger .description {\n color: var(--tempest-danger-fg);\n opacity: 0.85;\n}\n.danger.solid {\n background-color: var(--tempest-danger-solid);\n color: var(--tempest-danger-on-solid);\n border-color: transparent;\n}\n.danger.solid .description {\n color: rgba(255, 255, 255, 0.9);\n}\n.danger.outline {\n background-color: transparent;\n color: var(--tempest-danger);\n border-color: var(--tempest-danger);\n}\n"],"mappings":""}
1
+ {"version":3,"file":"Alert.module.js","names":[],"sources":["../../../src/components/Alert/Alert.module.css"],"sourcesContent":[".alert {\n display: flex;\n align-items: flex-start;\n gap: var(--tempest-space-3);\n padding: var(--tempest-space-3) var(--tempest-space-4);\n border: 1px solid transparent;\n border-radius: var(--tempest-radius-lg);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n line-height: var(--tempest-leading-snug);\n color: var(--tempest-text);\n width: 100%;\n}\n\n.content {\n flex: 1;\n min-width: 0;\n}\n\n.title {\n margin: 0;\n font-size: var(--tempest-text-base);\n font-weight: var(--tempest-weight-semibold);\n line-height: var(--tempest-leading-snug);\n}\n\n.description {\n margin: 2px 0 0 0;\n font-size: var(--tempest-text-sm);\n line-height: var(--tempest-leading-snug);\n color: var(--tempest-text-muted);\n}\n\n.icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 20px;\n height: 20px;\n margin-top: 1px;\n}\n\n.close {\n margin-left: auto;\n background: none;\n border: none;\n color: inherit;\n opacity: 0.7;\n padding: 4px;\n border-radius: var(--tempest-radius-sm);\n display: flex;\n align-items: center;\n transition:\n var(--tempest-transition-color),\n opacity var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.close:hover {\n opacity: 1;\n background-color: rgba(15, 23, 42, 0.06);\n}\n\n.close:focus-visible {\n outline: var(--tempest-focus-ring-width) solid var(--tempest-focus-ring-color);\n outline-offset: var(--tempest-focus-ring-offset);\n}\n\n/* ============================================================\n * Tone × Appearance matrix\n * ============================================================ */\n\n/* neutral */\n.neutral {\n background-color: var(--tempest-surface);\n color: var(--tempest-text);\n border-color: var(--tempest-border);\n}\n.neutral.solid {\n background-color: var(--tempest-gray-700);\n color: var(--tempest-neutral-on-solid);\n border-color: transparent;\n}\n.neutral.outline {\n background-color: transparent;\n border-color: var(--tempest-border-strong);\n}\n\n/* info */\n.info {\n background-color: var(--tempest-info-bg);\n color: var(--tempest-info-fg);\n border-color: var(--tempest-info-border);\n}\n.info .description {\n color: var(--tempest-info-fg);\n opacity: 0.85;\n}\n.info.solid {\n background-color: var(--tempest-info-solid);\n color: var(--tempest-info-on-solid);\n border-color: transparent;\n}\n.info.solid .description {\n color: rgba(255, 255, 255, 0.9);\n}\n.info.outline {\n background-color: transparent;\n color: var(--tempest-info);\n border-color: var(--tempest-info);\n}\n\n/* success */\n.success {\n background-color: var(--tempest-success-bg);\n color: var(--tempest-success-fg);\n border-color: var(--tempest-success-border);\n}\n.success .description {\n color: var(--tempest-success-fg);\n opacity: 0.85;\n}\n.success.solid {\n background-color: var(--tempest-success-solid);\n color: var(--tempest-success-on-solid);\n border-color: transparent;\n}\n.success.solid .description {\n color: rgba(255, 255, 255, 0.9);\n}\n.success.outline {\n background-color: transparent;\n color: var(--tempest-success);\n border-color: var(--tempest-success);\n}\n\n/* warning */\n.warning {\n background-color: var(--tempest-warning-bg);\n color: var(--tempest-warning-fg);\n border-color: var(--tempest-warning-border);\n}\n.warning .description {\n color: var(--tempest-warning-fg);\n opacity: 0.85;\n}\n.warning.solid {\n background-color: var(--tempest-warning-solid);\n color: var(--tempest-warning-on-solid);\n border-color: transparent;\n}\n.warning.solid .description {\n color: rgba(255, 255, 255, 0.9);\n}\n.warning.outline {\n background-color: transparent;\n color: var(--tempest-warning);\n border-color: var(--tempest-warning);\n}\n\n/* danger */\n.danger {\n background-color: var(--tempest-danger-bg);\n color: var(--tempest-danger-fg);\n border-color: var(--tempest-danger-border);\n}\n.danger .description {\n color: var(--tempest-danger-fg);\n opacity: 0.85;\n}\n.danger.solid {\n background-color: var(--tempest-danger-solid);\n color: var(--tempest-danger-on-solid);\n border-color: transparent;\n}\n.danger.solid .description {\n color: rgba(255, 255, 255, 0.9);\n}\n.danger.outline {\n background-color: transparent;\n color: var(--tempest-danger);\n border-color: var(--tempest-danger);\n}\n"],"mappings":""}
@@ -1,2 +1,2 @@
1
- const e=require("../../utils/dev-mode.cjs");let t=require("react");var n="[tempest-react-sdk] <AppBar sticky /> will not stick: `document.body` has `overflow-x: hidden`, which forces its computed `overflow-y` to `auto` and makes the body a scroll container. The bar then pins to the body's scrollport instead of the viewport and scrolls off the top with the content — most visibly on Chrome Android. Fix: `html, body { overflow-x: clip }`, on both elements, since neither clamps alone. Pass `sticky={false}` if the bar is meant to scroll away.",r=!1;function i(i){(0,t.useEffect)(()=>{!i||r||!e.isDevBuild()||typeof document>`u`||!document.body||getComputedStyle(document.body).overflowX===`hidden`&&(r=!0,console.warn(n))},[i])}exports.useStickyBodyWarning=i;
1
+ const e=require("../../utils/dev-mode.cjs");let t=require("react");var n="[tempest-react-sdk] <AppBar sticky /> will not stick: `document.body` has `overflow-x: hidden`, which forces its computed `overflow-y` to `auto` and makes the body a scroll container. The bar then pins to the body's scrollport instead of the viewport and scrolls off the top with the content — most visibly on Chrome Android. Fix: `html, body { overflow-x: clip }`, on both elements, since neither clamps alone. Pass `sticky={false}` if the bar is meant to scroll away.",r=!1;function i(i){(0,t.useEffect)(()=>{i&&!r&&e.isDevBuild()&&(typeof document>`u`||!document.body||getComputedStyle(document.body).overflowX===`hidden`&&(r=!0,console.warn(n)))},[i])}exports.useStickyBodyWarning=i;
2
2
  //# sourceMappingURL=use-sticky-body-warning.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-sticky-body-warning.cjs","names":[],"sources":["../../../src/components/AppBar/use-sticky-body-warning.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\nimport { isDevBuild } from \"../../utils/dev-mode\";\n\n/**\n * The one line of the app's own CSS that silently defeats `sticky`.\n *\n * `body { overflow-x: hidden }` is how most apps stop a too-wide child from\n * panning the page sideways. It also, per CSS Overflow 3, forces the computed\n * `overflow-y` to `auto` — a value the author never wrote — which makes the\n * body a **scroll container**. Every sticky descendant then pins to the body's\n * scrollport instead of the viewport, and that scrollport travels with the\n * document: measured in Chromium at 390px with a long page, the bar sat at\n * `top: -900px` after scrolling 900px, i.e. off screen.\n *\n * Nothing about that reads as a CSS problem from the app's side. The bar still\n * reports `position: sticky` in DevTools, the markup is unchanged, and on\n * desktop the screens usually fit without scrolling so the bug never shows. It\n * surfaces on Chrome Android, where the collapsing URL bar makes every screen a\n * scrolling screen — and it gets reported as \"the app bar is broken\", because\n * that is what the user sees.\n *\n * Hence this warning: the SDK cannot fix it from inside the component (a sticky\n * element has no way to opt out of its scroll container, and a rule the SDK\n * ships in its reset loses to the app's own stylesheet, which is imported\n * after), but it can name the cause in one console line.\n *\n * `clip` is the fix because it clamps the same horizontal overflow without\n * creating a scroll container. It has to be on `html` **and** `body` — with it\n * on only one of them the document still panned to `x: 500` in the same\n * measurement.\n */\nconst MESSAGE =\n \"[tempest-react-sdk] <AppBar sticky /> will not stick: `document.body` has \" +\n \"`overflow-x: hidden`, which forces its computed `overflow-y` to `auto` and makes the \" +\n \"body a scroll container. The bar then pins to the body's scrollport instead of the \" +\n \"viewport and scrolls off the top with the content — most visibly on Chrome Android. \" +\n \"Fix: `html, body { overflow-x: clip }`, on both elements, since neither clamps alone. \" +\n \"Pass `sticky={false}` if the bar is meant to scroll away.\";\n\n/** Set once the warning has been printed, so re-renders do not bury the console. */\nlet warned = false;\n\n/**\n * Reset the warn-once latch. Test-only — the latch is module state, and a suite\n * that renders more than one bar would otherwise see the warning only once.\n */\nexport function resetStickyBodyWarning(): void {\n warned = false;\n}\n\n/**\n * Warn once, in development, when the page's own CSS will keep a sticky bar\n * from sticking.\n *\n * Reads the body's `overflow-x` rather than walking the ancestors, because that\n * single declaration is the whole failure mode: an app that genuinely wants the\n * body to scroll writes `overflow-y: auto` itself, and a sticky bar inside a\n * scroll container the app chose on purpose behaves exactly as intended. What\n * is never intentional is acquiring that scroll container as a side effect of\n * clamping the other axis.\n *\n * @param enabled - Whether the bar is actually sticky. A bar rendered with\n * `sticky={false}` has nothing to lose and stays quiet.\n */\nexport function useStickyBodyWarning(enabled: boolean): void {\n useEffect(() => {\n if (!enabled || warned || !isDevBuild()) return;\n if (typeof document === \"undefined\" || !document.body) return;\n if (getComputedStyle(document.body).overflowX !== \"hidden\") return;\n warned = true;\n console.warn(MESSAGE);\n }, [enabled]);\n}\n"],"mappings":"mEAgCA,IAAM,EACF,wdAQA,EAAS,GAwBb,SAAgB,EAAqB,EAAwB,EACzD,EAAA,EAAA,UAAA,KAAgB,CACR,CAAC,GAAW,GAAU,CAAC,EAAA,WAAW,GAClC,OAAO,SAAa,KAAe,CAAC,SAAS,MAC7C,iBAAiB,SAAS,IAAI,CAAC,CAAC,YAAc,WAClD,EAAS,GACT,QAAQ,KAAK,CAAO,EACxB,EAAG,CAAC,CAAO,CAAC,CAChB"}
1
+ {"version":3,"file":"use-sticky-body-warning.cjs","names":[],"sources":["../../../src/components/AppBar/use-sticky-body-warning.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\nimport { isDevBuild } from \"../../utils/dev-mode\";\n\n/**\n * The one line of the app's own CSS that silently defeats `sticky`.\n *\n * `body { overflow-x: hidden }` is how most apps stop a too-wide child from\n * panning the page sideways. It also, per CSS Overflow 3, forces the computed\n * `overflow-y` to `auto` — a value the author never wrote — which makes the\n * body a **scroll container**. Every sticky descendant then pins to the body's\n * scrollport instead of the viewport, and that scrollport travels with the\n * document: measured in Chromium at 390px with a long page, the bar sat at\n * `top: -900px` after scrolling 900px, i.e. off screen.\n *\n * Nothing about that reads as a CSS problem from the app's side. The bar still\n * reports `position: sticky` in DevTools, the markup is unchanged, and on\n * desktop the screens usually fit without scrolling so the bug never shows. It\n * surfaces on Chrome Android, where the collapsing URL bar makes every screen a\n * scrolling screen — and it gets reported as \"the app bar is broken\", because\n * that is what the user sees.\n *\n * Hence this warning: the SDK cannot fix it from inside the component (a sticky\n * element has no way to opt out of its scroll container, and a rule the SDK\n * ships in its reset loses to the app's own stylesheet, which is imported\n * after), but it can name the cause in one console line.\n *\n * `clip` is the fix because it clamps the same horizontal overflow without\n * creating a scroll container. It has to be on `html` **and** `body` — with it\n * on only one of them the document still panned to `x: 500` in the same\n * measurement.\n */\nconst MESSAGE =\n \"[tempest-react-sdk] <AppBar sticky /> will not stick: `document.body` has \" +\n \"`overflow-x: hidden`, which forces its computed `overflow-y` to `auto` and makes the \" +\n \"body a scroll container. The bar then pins to the body's scrollport instead of the \" +\n \"viewport and scrolls off the top with the content — most visibly on Chrome Android. \" +\n \"Fix: `html, body { overflow-x: clip }`, on both elements, since neither clamps alone. \" +\n \"Pass `sticky={false}` if the bar is meant to scroll away.\";\n\n/** Set once the warning has been printed, so re-renders do not bury the console. */\nlet warned = false;\n\n/**\n * Reset the warn-once latch. Test-only — the latch is module state, and a suite\n * that renders more than one bar would otherwise see the warning only once.\n */\nexport function resetStickyBodyWarning(): void {\n warned = false;\n}\n\n/**\n * Warn once, in development, when the page's own CSS will keep a sticky bar\n * from sticking.\n *\n * Reads the body's `overflow-x` rather than walking the ancestors, because that\n * single declaration is the whole failure mode: an app that genuinely wants the\n * body to scroll writes `overflow-y: auto` itself, and a sticky bar inside a\n * scroll container the app chose on purpose behaves exactly as intended. What\n * is never intentional is acquiring that scroll container as a side effect of\n * clamping the other axis.\n *\n * @param enabled - Whether the bar is actually sticky. A bar rendered with\n * `sticky={false}` has nothing to lose and stays quiet.\n */\nexport function useStickyBodyWarning(enabled: boolean): void {\n useEffect(() => {\n if (!enabled || warned || !isDevBuild()) return;\n if (typeof document === \"undefined\" || !document.body) return;\n if (getComputedStyle(document.body).overflowX !== \"hidden\") return;\n warned = true;\n console.warn(MESSAGE);\n }, [enabled]);\n}\n"],"mappings":"mEAgCA,IAAM,EACF,wdAQA,EAAS,GAwBb,SAAgB,EAAqB,EAAwB,EACzD,EAAA,EAAA,UAAA,KAAgB,CACP,GAAW,IAAW,EAAA,WAAW,IAClC,OAAO,SAAa,KAAe,CAAC,SAAS,MAC7C,iBAAiB,SAAS,IAAI,CAAC,CAAC,YAAc,WAClD,EAAS,GACT,QAAQ,KAAK,CAAO,GACxB,EAAG,CAAC,CAAO,CAAC,CAChB"}
@@ -4,7 +4,7 @@ import { useEffect as t } from "react";
4
4
  var n = "[tempest-react-sdk] <AppBar sticky /> will not stick: `document.body` has `overflow-x: hidden`, which forces its computed `overflow-y` to `auto` and makes the body a scroll container. The bar then pins to the body's scrollport instead of the viewport and scrolls off the top with the content — most visibly on Chrome Android. Fix: `html, body { overflow-x: clip }`, on both elements, since neither clamps alone. Pass `sticky={false}` if the bar is meant to scroll away.", r = !1;
5
5
  function i(i) {
6
6
  t(() => {
7
- !i || r || !e() || typeof document > "u" || !document.body || getComputedStyle(document.body).overflowX === "hidden" && (r = !0, console.warn(n));
7
+ i && !r && e() && (typeof document > "u" || !document.body || getComputedStyle(document.body).overflowX === "hidden" && (r = !0, console.warn(n)));
8
8
  }, [i]);
9
9
  }
10
10
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"use-sticky-body-warning.js","names":[],"sources":["../../../src/components/AppBar/use-sticky-body-warning.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\nimport { isDevBuild } from \"../../utils/dev-mode\";\n\n/**\n * The one line of the app's own CSS that silently defeats `sticky`.\n *\n * `body { overflow-x: hidden }` is how most apps stop a too-wide child from\n * panning the page sideways. It also, per CSS Overflow 3, forces the computed\n * `overflow-y` to `auto` — a value the author never wrote — which makes the\n * body a **scroll container**. Every sticky descendant then pins to the body's\n * scrollport instead of the viewport, and that scrollport travels with the\n * document: measured in Chromium at 390px with a long page, the bar sat at\n * `top: -900px` after scrolling 900px, i.e. off screen.\n *\n * Nothing about that reads as a CSS problem from the app's side. The bar still\n * reports `position: sticky` in DevTools, the markup is unchanged, and on\n * desktop the screens usually fit without scrolling so the bug never shows. It\n * surfaces on Chrome Android, where the collapsing URL bar makes every screen a\n * scrolling screen — and it gets reported as \"the app bar is broken\", because\n * that is what the user sees.\n *\n * Hence this warning: the SDK cannot fix it from inside the component (a sticky\n * element has no way to opt out of its scroll container, and a rule the SDK\n * ships in its reset loses to the app's own stylesheet, which is imported\n * after), but it can name the cause in one console line.\n *\n * `clip` is the fix because it clamps the same horizontal overflow without\n * creating a scroll container. It has to be on `html` **and** `body` — with it\n * on only one of them the document still panned to `x: 500` in the same\n * measurement.\n */\nconst MESSAGE =\n \"[tempest-react-sdk] <AppBar sticky /> will not stick: `document.body` has \" +\n \"`overflow-x: hidden`, which forces its computed `overflow-y` to `auto` and makes the \" +\n \"body a scroll container. The bar then pins to the body's scrollport instead of the \" +\n \"viewport and scrolls off the top with the content — most visibly on Chrome Android. \" +\n \"Fix: `html, body { overflow-x: clip }`, on both elements, since neither clamps alone. \" +\n \"Pass `sticky={false}` if the bar is meant to scroll away.\";\n\n/** Set once the warning has been printed, so re-renders do not bury the console. */\nlet warned = false;\n\n/**\n * Reset the warn-once latch. Test-only — the latch is module state, and a suite\n * that renders more than one bar would otherwise see the warning only once.\n */\nexport function resetStickyBodyWarning(): void {\n warned = false;\n}\n\n/**\n * Warn once, in development, when the page's own CSS will keep a sticky bar\n * from sticking.\n *\n * Reads the body's `overflow-x` rather than walking the ancestors, because that\n * single declaration is the whole failure mode: an app that genuinely wants the\n * body to scroll writes `overflow-y: auto` itself, and a sticky bar inside a\n * scroll container the app chose on purpose behaves exactly as intended. What\n * is never intentional is acquiring that scroll container as a side effect of\n * clamping the other axis.\n *\n * @param enabled - Whether the bar is actually sticky. A bar rendered with\n * `sticky={false}` has nothing to lose and stays quiet.\n */\nexport function useStickyBodyWarning(enabled: boolean): void {\n useEffect(() => {\n if (!enabled || warned || !isDevBuild()) return;\n if (typeof document === \"undefined\" || !document.body) return;\n if (getComputedStyle(document.body).overflowX !== \"hidden\") return;\n warned = true;\n console.warn(MESSAGE);\n }, [enabled]);\n}\n"],"mappings":";;;AAgCA,IAAM,IACF,ydAQA,IAAS;AAwBb,SAAgB,EAAqB,GAAwB;CACzD,QAAgB;EACR,CAAC,KAAW,KAAU,CAAC,EAAW,KAClC,OAAO,WAAa,OAAe,CAAC,SAAS,QAC7C,iBAAiB,SAAS,IAAI,CAAC,CAAC,cAAc,aAClD,IAAS,IACT,QAAQ,KAAK,CAAO;CACxB,GAAG,CAAC,CAAO,CAAC;AAChB"}
1
+ {"version":3,"file":"use-sticky-body-warning.js","names":[],"sources":["../../../src/components/AppBar/use-sticky-body-warning.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\nimport { isDevBuild } from \"../../utils/dev-mode\";\n\n/**\n * The one line of the app's own CSS that silently defeats `sticky`.\n *\n * `body { overflow-x: hidden }` is how most apps stop a too-wide child from\n * panning the page sideways. It also, per CSS Overflow 3, forces the computed\n * `overflow-y` to `auto` — a value the author never wrote — which makes the\n * body a **scroll container**. Every sticky descendant then pins to the body's\n * scrollport instead of the viewport, and that scrollport travels with the\n * document: measured in Chromium at 390px with a long page, the bar sat at\n * `top: -900px` after scrolling 900px, i.e. off screen.\n *\n * Nothing about that reads as a CSS problem from the app's side. The bar still\n * reports `position: sticky` in DevTools, the markup is unchanged, and on\n * desktop the screens usually fit without scrolling so the bug never shows. It\n * surfaces on Chrome Android, where the collapsing URL bar makes every screen a\n * scrolling screen — and it gets reported as \"the app bar is broken\", because\n * that is what the user sees.\n *\n * Hence this warning: the SDK cannot fix it from inside the component (a sticky\n * element has no way to opt out of its scroll container, and a rule the SDK\n * ships in its reset loses to the app's own stylesheet, which is imported\n * after), but it can name the cause in one console line.\n *\n * `clip` is the fix because it clamps the same horizontal overflow without\n * creating a scroll container. It has to be on `html` **and** `body` — with it\n * on only one of them the document still panned to `x: 500` in the same\n * measurement.\n */\nconst MESSAGE =\n \"[tempest-react-sdk] <AppBar sticky /> will not stick: `document.body` has \" +\n \"`overflow-x: hidden`, which forces its computed `overflow-y` to `auto` and makes the \" +\n \"body a scroll container. The bar then pins to the body's scrollport instead of the \" +\n \"viewport and scrolls off the top with the content — most visibly on Chrome Android. \" +\n \"Fix: `html, body { overflow-x: clip }`, on both elements, since neither clamps alone. \" +\n \"Pass `sticky={false}` if the bar is meant to scroll away.\";\n\n/** Set once the warning has been printed, so re-renders do not bury the console. */\nlet warned = false;\n\n/**\n * Reset the warn-once latch. Test-only — the latch is module state, and a suite\n * that renders more than one bar would otherwise see the warning only once.\n */\nexport function resetStickyBodyWarning(): void {\n warned = false;\n}\n\n/**\n * Warn once, in development, when the page's own CSS will keep a sticky bar\n * from sticking.\n *\n * Reads the body's `overflow-x` rather than walking the ancestors, because that\n * single declaration is the whole failure mode: an app that genuinely wants the\n * body to scroll writes `overflow-y: auto` itself, and a sticky bar inside a\n * scroll container the app chose on purpose behaves exactly as intended. What\n * is never intentional is acquiring that scroll container as a side effect of\n * clamping the other axis.\n *\n * @param enabled - Whether the bar is actually sticky. A bar rendered with\n * `sticky={false}` has nothing to lose and stays quiet.\n */\nexport function useStickyBodyWarning(enabled: boolean): void {\n useEffect(() => {\n if (!enabled || warned || !isDevBuild()) return;\n if (typeof document === \"undefined\" || !document.body) return;\n if (getComputedStyle(document.body).overflowX !== \"hidden\") return;\n warned = true;\n console.warn(MESSAGE);\n }, [enabled]);\n}\n"],"mappings":";;;AAgCA,IAAM,IACF,ydAQA,IAAS;AAwBb,SAAgB,EAAqB,GAAwB;CACzD,QAAgB;EACR,AAAC,KAAW,MAAW,EAAW,MAClC,OAAO,WAAa,OAAe,CAAC,SAAS,QAC7C,iBAAiB,SAAS,IAAI,CAAC,CAAC,cAAc,aAClD,IAAS,IACT,QAAQ,KAAK,CAAO;CACxB,GAAG,CAAC,CAAO,CAAC;AAChB"}
@@ -1,2 +1,2 @@
1
- const e=require("../../utils/cn.cjs"),t=require("../../hooks/use-object-url.cjs"),n=require("../../audio/duration.cjs"),r=require("../../audio/audio-output.cjs"),i=require("./AudioPlayer.module.cjs");let a=require("react"),o=require("react/jsx-runtime"),s=require("lucide-react");var c={"pt-BR":{play:`Tocar`,pause:`Pausar`,seek:`Posição`},en:{play:`Play`,pause:`Pause`,seek:`Seek`}};function l({src:l,durationMs:u,sinkId:d,autoPlay:f=!1,loop:p=!1,locale:m=`pt-BR`,actions:h,onEnded:g,onError:_,disabled:v=!1,className:y,...b}){let x=c[m],S=(0,a.useRef)(null),[C,w]=(0,a.useState)(!1),[T,E]=(0,a.useState)(0),[D,O]=(0,a.useState)(1/0),k=(0,a.useRef)(!1),A=t.useObjectUrl(typeof l==`string`?null:l),j=typeof l==`string`?l:A,M=Number.isFinite(u)?u:D,N=Number.isFinite(M)&&M>0;(0,a.useEffect)(()=>{E(0),w(!1),O(1/0),k.current=!1},[j]),(0,a.useEffect)(()=>{d!==void 0&&r.setAudioOutput(S.current,d)},[d,j]);let P=()=>{let e=S.current;if(!e)return;if(Number.isFinite(e.duration)){O(e.duration*1e3);return}if(k.current||Number.isFinite(u))return;k.current=!0;let t=()=>{e.removeEventListener(`timeupdate`,t),Number.isFinite(e.duration)&&O(e.duration*1e3),e.currentTime=0};e.addEventListener(`timeupdate`,t),e.currentTime=1e101},F=()=>{let e=S.current;if(!(!e||!j)){if(e.paused){e.play().then(()=>w(!0)).catch(e=>_?.(e));return}e.pause(),w(!1)}},I=e=>{let t=S.current;!t||!N||(t.currentTime=e/1e3,E(e))};return(0,o.jsxs)(`div`,{className:e.cn(i.default.player,y),...b,children:[(0,o.jsx)(`audio`,{ref:S,src:j??void 0,loop:p,autoPlay:f,preload:`metadata`,onLoadedMetadata:P,onDurationChange:P,onTimeUpdate:()=>{let e=S.current;e&&E(e.currentTime*1e3)},onPlay:()=>w(!0),onPause:()=>w(!1),onEnded:()=>{w(!1),E(0),g?.()},onError:e=>_?.(e)}),(0,o.jsx)(`button`,{type:`button`,className:i.default.transport,onClick:F,disabled:v||!j,"aria-label":C?x.pause:x.play,title:C?x.pause:x.play,children:C?(0,o.jsx)(s.Pause,{size:16,"aria-hidden":!0}):(0,o.jsx)(s.Play,{size:16,"aria-hidden":!0})}),(0,o.jsx)(`input`,{type:`range`,className:i.default.seek,min:0,max:N?Math.round(M):0,step:100,value:Math.min(Math.round(T),N?Math.round(M):0),onChange:e=>I(Number(e.target.value)),disabled:v||!N,"aria-label":x.seek,"aria-valuetext":`${n.formatDuration(T)} / ${n.formatDuration(M)}`}),(0,o.jsxs)(`span`,{className:i.default.time,children:[(0,o.jsx)(`span`,{children:n.formatDuration(T)}),(0,o.jsx)(`span`,{"aria-hidden":`true`,children:`/`}),(0,o.jsx)(`span`,{children:n.formatDuration(M)})]}),h&&(0,o.jsx)(`span`,{className:i.default.actions,children:h})]})}exports.AudioPlayer=l;
1
+ const e=require("../../utils/cn.cjs"),t=require("../../hooks/use-object-url.cjs"),n=require("../../audio/duration.cjs"),r=require("../../audio/audio-output.cjs"),i=require("./AudioPlayer.module.cjs");let a=require("react"),o=require("react/jsx-runtime"),s=require("lucide-react");var c={"pt-BR":{play:`Tocar`,pause:`Pausar`,seek:`Posição`},en:{play:`Play`,pause:`Pause`,seek:`Seek`}};function l({src:l,durationMs:u,sinkId:d,autoPlay:f=!1,loop:p=!1,locale:m=`pt-BR`,actions:h,onEnded:g,onError:_,disabled:v=!1,className:y,...b}){let x=c[m],S=(0,a.useRef)(null),[C,w]=(0,a.useState)(!1),[T,E]=(0,a.useState)(0),[D,O]=(0,a.useState)(1/0),k=(0,a.useRef)(!1),A=t.useObjectUrl(typeof l==`string`?null:l),j=typeof l==`string`?l:A,M=Number.isFinite(u)?u:D,N=Number.isFinite(M)&&M>0;(0,a.useEffect)(()=>{E(0),w(!1),O(1/0),k.current=!1},[j]),(0,a.useEffect)(()=>{d!==void 0&&r.setAudioOutput(S.current,d)},[d,j]);let P=()=>{let e=S.current;if(!e)return;if(Number.isFinite(e.duration)){O(e.duration*1e3);return}if(k.current||Number.isFinite(u))return;k.current=!0;let t=()=>{e.removeEventListener(`timeupdate`,t),Number.isFinite(e.duration)&&O(e.duration*1e3),e.currentTime=0};e.addEventListener(`timeupdate`,t),e.currentTime=1e101},F=()=>{let e=S.current;if(e&&j){if(e.paused){e.play().then(()=>w(!0)).catch(e=>_?.(e));return}e.pause(),w(!1)}},I=e=>{let t=S.current;t&&N&&(t.currentTime=e/1e3,E(e))};return(0,o.jsxs)(`div`,{className:e.cn(i.default.player,y),...b,children:[(0,o.jsx)(`audio`,{ref:S,src:j??void 0,loop:p,autoPlay:f,preload:`metadata`,onLoadedMetadata:P,onDurationChange:P,onTimeUpdate:()=>{let e=S.current;e&&E(e.currentTime*1e3)},onPlay:()=>w(!0),onPause:()=>w(!1),onEnded:()=>{w(!1),E(0),g?.()},onError:e=>_?.(e)}),(0,o.jsx)(`button`,{type:`button`,className:i.default.transport,onClick:F,disabled:v||!j,"aria-label":C?x.pause:x.play,title:C?x.pause:x.play,children:C?(0,o.jsx)(s.Pause,{size:16,"aria-hidden":!0}):(0,o.jsx)(s.Play,{size:16,"aria-hidden":!0})}),(0,o.jsx)(`input`,{type:`range`,className:i.default.seek,min:0,max:N?Math.round(M):0,step:100,value:Math.min(Math.round(T),N?Math.round(M):0),onChange:e=>I(Number(e.target.value)),disabled:v||!N,"aria-label":x.seek,"aria-valuetext":`${n.formatDuration(T)} / ${n.formatDuration(M)}`}),(0,o.jsxs)(`span`,{className:i.default.time,children:[(0,o.jsx)(`span`,{children:n.formatDuration(T)}),(0,o.jsx)(`span`,{"aria-hidden":`true`,children:`/`}),(0,o.jsx)(`span`,{children:n.formatDuration(M)})]}),h&&(0,o.jsx)(`span`,{className:i.default.actions,children:h})]})}exports.AudioPlayer=l;
2
2
  //# sourceMappingURL=AudioPlayer.cjs.map