@keboola/design 13.2.0 → 15.0.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 (321) hide show
  1. package/AGENTS.md +16 -24
  2. package/CHANGELOG.md +194 -0
  3. package/COMPONENT-API.md +109 -105
  4. package/dist/{AIKit-C5FbKjwa.js → AIKit-CaAW4GLx.js} +2216 -307
  5. package/dist/AIKit-CaAW4GLx.js.map +1 -0
  6. package/dist/{Alert-DuAGeYyM.js → Alert-Chz6KZ7e.js} +11 -20
  7. package/dist/Alert-Chz6KZ7e.js.map +1 -0
  8. package/dist/{AlertModal-BDle9ZVQ.js → AlertModal-D4AtpvLS.js} +15 -35
  9. package/dist/AlertModal-D4AtpvLS.js.map +1 -0
  10. package/dist/{AppShell-B03fj1j7.js → AppShell-C8V1KRr_.js} +2 -2
  11. package/dist/{AppShell-B03fj1j7.js.map → AppShell-C8V1KRr_.js.map} +1 -1
  12. package/dist/{AutoSaveTextarea-jiqNeeE4.js → AutoSaveTextarea-BbMhC5Aq.js} +3 -3
  13. package/dist/AutoSaveTextarea-BbMhC5Aq.js.map +1 -0
  14. package/dist/{Badge-Ba3epgcI.js → Badge-5GHIsgRS.js} +14 -3
  15. package/dist/Badge-5GHIsgRS.js.map +1 -0
  16. package/dist/{BrandEditor-Dq168RJc.js → BrandEditor-CTGtQUFZ.js} +8 -10
  17. package/dist/BrandEditor-CTGtQUFZ.js.map +1 -0
  18. package/dist/{BrandLogo-BfITD0im.js → BrandLogo-DkESE19K.js} +2 -2
  19. package/dist/{BrandLogo-BfITD0im.js.map → BrandLogo-DkESE19K.js.map} +1 -1
  20. package/dist/{Breadcrumb-CUyYu5Bi.js → Breadcrumb-B2YSNON4.js} +55 -13
  21. package/dist/Breadcrumb-B2YSNON4.js.map +1 -0
  22. package/dist/{Button-D2JqBAq7.js → Button-BNkyYU8u.js} +9 -185
  23. package/dist/Button-BNkyYU8u.js.map +1 -0
  24. package/dist/{Card-Bpc_ZlL4.js → Card-DxjGiqH8.js} +11 -18
  25. package/dist/Card-DxjGiqH8.js.map +1 -0
  26. package/dist/{Checkbox-D9rafoKh.js → Checkbox-CHDDJyAA.js} +20 -45
  27. package/dist/Checkbox-CHDDJyAA.js.map +1 -0
  28. package/dist/{Clipboard-BHWUodAx.js → Clipboard-DlwVZS1P.js} +2 -2
  29. package/dist/{Clipboard-BHWUodAx.js.map → Clipboard-DlwVZS1P.js.map} +1 -1
  30. package/dist/{ClipboardButton-Urg-kZbh.js → ClipboardButton-B1Hr_n_Z.js} +3 -3
  31. package/dist/{ClipboardButton-Urg-kZbh.js.map → ClipboardButton-B1Hr_n_Z.js.map} +1 -1
  32. package/dist/{CodeEditor-Y0iE-SwV.js → CodeEditor-DVSPdfdo.js} +24 -31
  33. package/dist/CodeEditor-DVSPdfdo.js.map +1 -0
  34. package/dist/{ColorInput-D1nC0X6i.js → ColorInput-B_eZIu39.js} +5 -7
  35. package/dist/ColorInput-B_eZIu39.js.map +1 -0
  36. package/dist/{ColorModeSwitch-Cxee8jnB.js → ColorModeSwitch-B6UEzG0H.js} +3 -3
  37. package/dist/{ColorModeSwitch-Cxee8jnB.js.map → ColorModeSwitch-B6UEzG0H.js.map} +1 -1
  38. package/dist/{Combobox-B1Kjoy5J.js → Combobox-CaKqgstG.js} +250 -56
  39. package/dist/Combobox-CaKqgstG.js.map +1 -0
  40. package/dist/{Command-BuOA3V18.js → Command-DhXmqFLT.js} +51 -33
  41. package/dist/Command-DhXmqFLT.js.map +1 -0
  42. package/dist/{CopyField-DsOVtV2Q.js → CopyField-D0Pg4Z0R.js} +2 -2
  43. package/dist/{CopyField-DsOVtV2Q.js.map → CopyField-D0Pg4Z0R.js.map} +1 -1
  44. package/dist/{DateInput-BlKkequE.js → DateInput-BgbMDJkm.js} +5 -7
  45. package/dist/DateInput-BgbMDJkm.js.map +1 -0
  46. package/dist/{DatePicker-CgHAu9KX.js → DatePicker-BXpDRo-R.js} +2 -2
  47. package/dist/{DatePicker-CgHAu9KX.js.map → DatePicker-BXpDRo-R.js.map} +1 -1
  48. package/dist/{DateRangeInput-DOxJHw8V.js → DateRangeInput-BW0-_jV0.js} +5 -7
  49. package/dist/DateRangeInput-BW0-_jV0.js.map +1 -0
  50. package/dist/{DateRangePicker-CXpXPMoh.js → DateRangePicker-C4-DoWac.js} +7 -12
  51. package/dist/DateRangePicker-C4-DoWac.js.map +1 -0
  52. package/dist/{DropdownMenu-DtRYcKEx.js → DropdownMenu-CcVbZ_5u.js} +35 -64
  53. package/dist/DropdownMenu-CcVbZ_5u.js.map +1 -0
  54. package/dist/{FieldItem-Cf1E3rfQ.js → FieldItem-BHUqgSO1.js} +3 -3
  55. package/dist/{FieldItem-Cf1E3rfQ.js.map → FieldItem-BHUqgSO1.js.map} +1 -1
  56. package/dist/{FileUpload-D6vPG3WB.js → FileUpload-BYFONpEl.js} +3 -3
  57. package/dist/FileUpload-BYFONpEl.js.map +1 -0
  58. package/dist/{Form-CuaAn-lR.js → Form-CAofWyMI.js} +8 -8
  59. package/dist/{Form-CuaAn-lR.js.map → Form-CAofWyMI.js.map} +1 -1
  60. package/dist/{FormGroup-D4O96YPm.js → FormGroup-V5kIgQRN.js} +3 -3
  61. package/dist/{FormGroup-D4O96YPm.js.map → FormGroup-V5kIgQRN.js.map} +1 -1
  62. package/dist/{FullScreenModal-CUuAakh3.js → FullScreenModal-DAvvlGOz.js} +2 -5
  63. package/dist/FullScreenModal-DAvvlGOz.js.map +1 -0
  64. package/dist/HoverCard-Cv5rmvJq.js +26 -0
  65. package/dist/HoverCard-Cv5rmvJq.js.map +1 -0
  66. package/dist/{InlineEdit-DSMIEUUa.js → InlineEdit-D4P0SvCB.js} +5 -9
  67. package/dist/InlineEdit-D4P0SvCB.js.map +1 -0
  68. package/dist/{InputGroup-Dm014ruw.js → InputGroup-CZv_7QlM.js} +39 -146
  69. package/dist/InputGroup-CZv_7QlM.js.map +1 -0
  70. package/dist/{Kbd-Du93F3o4.js → Kbd-CflKthfu.js} +5 -14
  71. package/dist/Kbd-CflKthfu.js.map +1 -0
  72. package/dist/MermaidDiagram-Te-5gOCU.js +199 -0
  73. package/dist/MermaidDiagram-Te-5gOCU.js.map +1 -0
  74. package/dist/{Modal-DQIauzar.js → Modal-CbUz-5YH.js} +19 -37
  75. package/dist/Modal-CbUz-5YH.js.map +1 -0
  76. package/dist/{ModalTitle-CTECVgL8.js → ModalTitle-cy0Ht9VY.js} +2 -2
  77. package/dist/{ModalTitle-CTECVgL8.js.map → ModalTitle-cy0Ht9VY.js.map} +1 -1
  78. package/dist/{MultiActionButtonGroup-xKqiucAN.js → MultiActionButtonGroup-Dzig87s1.js} +3 -3
  79. package/dist/{MultiActionButtonGroup-xKqiucAN.js.map → MultiActionButtonGroup-Dzig87s1.js.map} +1 -1
  80. package/dist/{Navbar-CtVYcRFn.js → Navbar-DHUS7XiU.js} +2 -2
  81. package/dist/{Navbar-CtVYcRFn.js.map → Navbar-DHUS7XiU.js.map} +1 -1
  82. package/dist/OtpInput-CY8yCaJk.js.map +1 -1
  83. package/dist/{PageToolbar-B_e7HGva.js → PageToolbar-DJ5tdYla.js} +4 -4
  84. package/dist/{PageToolbar-B_e7HGva.js.map → PageToolbar-DJ5tdYla.js.map} +1 -1
  85. package/dist/{Pagination-DDp6PRqf.js → Pagination-D27x5KmH.js} +2 -2
  86. package/dist/{Pagination-DDp6PRqf.js.map → Pagination-D27x5KmH.js.map} +1 -1
  87. package/dist/{Popconfirm-BgV6a0sl.js → Popconfirm-DwLCYNaF.js} +3 -3
  88. package/dist/{Popconfirm-BgV6a0sl.js.map → Popconfirm-DwLCYNaF.js.map} +1 -1
  89. package/dist/{Popover-BU7nGOv-.js → Popover-DtUDwUVO.js} +24 -37
  90. package/dist/Popover-DtUDwUVO.js.map +1 -0
  91. package/dist/{RadioGroup-C2vk5acr.js → RadioGroup-B0TwHjBb.js} +9 -13
  92. package/dist/RadioGroup-B0TwHjBb.js.map +1 -0
  93. package/dist/{Search-CVrv3hqf.js → Search-vyZwGKMU.js} +3 -3
  94. package/dist/{Search-CVrv3hqf.js.map → Search-vyZwGKMU.js.map} +1 -1
  95. package/dist/{SearchInput-BnU6YHgT.js → SearchInput-DyYXyw3u.js} +4 -6
  96. package/dist/SearchInput-DyYXyw3u.js.map +1 -0
  97. package/dist/{SectionNav-XlhvXXdF.js → SectionNav-tLK3iF6c.js} +5 -5
  98. package/dist/{SectionNav-XlhvXXdF.js.map → SectionNav-tLK3iF6c.js.map} +1 -1
  99. package/dist/{Select-CjQgU_6K.js → Select-B22hB_-w.js} +11 -25
  100. package/dist/Select-B22hB_-w.js.map +1 -0
  101. package/dist/{Sheet-utw4_rUa.js → Sheet-0TWKTqid.js} +12 -15
  102. package/dist/Sheet-0TWKTqid.js.map +1 -0
  103. package/dist/{Sidebar-CKaxOPwY.js → Sidebar-C5hdANBU.js} +11 -11
  104. package/dist/Sidebar-C5hdANBU.js.map +1 -0
  105. package/dist/{Slider-Rcc2qVct.js → Slider-DrcsM8_G.js} +4 -4
  106. package/dist/Slider-DrcsM8_G.js.map +1 -0
  107. package/dist/{Stepper-BBPembeK.js → Stepper-CQrUPVFL.js} +2 -2
  108. package/dist/Stepper-CQrUPVFL.js.map +1 -0
  109. package/dist/Switch-DccpSpzC.js +66 -0
  110. package/dist/Switch-DccpSpzC.js.map +1 -0
  111. package/dist/{Table-CgSC-fSo.js → Table-CilEvpMq.js} +62 -3
  112. package/dist/Table-CilEvpMq.js.map +1 -0
  113. package/dist/{Tabs-C0uvkPHc.js → Tabs-Ck4uWf8U.js} +2 -2
  114. package/dist/{Tabs-C0uvkPHc.js.map → Tabs-Ck4uWf8U.js.map} +1 -1
  115. package/dist/{TextInput-B5TDDOty.js → TextInput-CR0_USaY.js} +4 -7
  116. package/dist/TextInput-CR0_USaY.js.map +1 -0
  117. package/dist/{ThemeProvider-Dt9b67sW.js → ThemeProvider-BTK7AL-T.js} +21 -8
  118. package/dist/ThemeProvider-BTK7AL-T.js.map +1 -0
  119. package/dist/{TimePicker-DyyV3nw8.js → TimePicker-CoRpiEM3.js} +2 -2
  120. package/dist/{TimePicker-DyyV3nw8.js.map → TimePicker-CoRpiEM3.js.map} +1 -1
  121. package/dist/{Toast-BGnUL4jV.js → Toast-937n7n8i.js} +2 -2
  122. package/dist/{Toast-BGnUL4jV.js.map → Toast-937n7n8i.js.map} +1 -1
  123. package/dist/ToggleAllTooltip-DEjbWFoj.js +37 -0
  124. package/dist/ToggleAllTooltip-DEjbWFoj.js.map +1 -0
  125. package/dist/{ZoomableSvg-D8sXKpoV.js → ZoomableSvg-DnFXo2L4.js} +85 -81
  126. package/dist/ZoomableSvg-DnFXo2L4.js.map +1 -0
  127. package/dist/editors.js +4 -4
  128. package/dist/flint.js +10 -45
  129. package/dist/flint.js.map +1 -1
  130. package/dist/highlightCode-D354tCXO.js +81 -0
  131. package/dist/highlightCode-D354tCXO.js.map +1 -0
  132. package/dist/index.d.ts +843 -443
  133. package/dist/index.js +62 -63
  134. package/dist/internal/blocks/AIKit.js +2 -2
  135. package/dist/internal/blocks/AppShell.js +1 -1
  136. package/dist/internal/blocks/AuthLayout.js +1 -1
  137. package/dist/internal/blocks/AutoSaveTextarea.js +1 -1
  138. package/dist/internal/blocks/BrandEditor.js +1 -1
  139. package/dist/internal/blocks/ChartPanel.js +1 -1
  140. package/dist/internal/blocks/ChipEditor.js +5 -9
  141. package/dist/internal/blocks/ChipEditor.js.map +1 -1
  142. package/dist/internal/blocks/ClipboardButton.js +1 -1
  143. package/dist/internal/blocks/ColorInput.js +1 -1
  144. package/dist/internal/blocks/ColorModeSwitch.js +1 -1
  145. package/dist/internal/blocks/Command.js +1 -1
  146. package/dist/internal/blocks/CopyField.js +1 -1
  147. package/dist/internal/blocks/DateInput.js +1 -1
  148. package/dist/internal/blocks/DateRangeInput.js +1 -1
  149. package/dist/internal/blocks/DiagramShared/DiagramSkeleton.js.map +1 -1
  150. package/dist/internal/blocks/EmptyConfigurationActionCard.js +1 -1
  151. package/dist/internal/blocks/ErrorState.js +18 -29
  152. package/dist/internal/blocks/ErrorState.js.map +1 -1
  153. package/dist/internal/blocks/ExportMenu.js +3 -3
  154. package/dist/internal/blocks/ExportMenu.js.map +1 -1
  155. package/dist/internal/blocks/Form/connectors/ComboboxField.js +2 -2
  156. package/dist/internal/blocks/Form/connectors/MultiComboboxField.js +1 -1
  157. package/dist/internal/blocks/Form.js +1 -1
  158. package/dist/internal/blocks/FormGroup.js +1 -1
  159. package/dist/internal/blocks/InlineEdit.js +1 -1
  160. package/dist/internal/blocks/MultiActionButtonGroup.js +1 -1
  161. package/dist/internal/blocks/MultiCombobox.js +202 -70
  162. package/dist/internal/blocks/MultiCombobox.js.map +1 -1
  163. package/dist/internal/blocks/NumberInput.js +2 -4
  164. package/dist/internal/blocks/NumberInput.js.map +1 -1
  165. package/dist/internal/blocks/PageToolbar.js +1 -1
  166. package/dist/internal/blocks/PasswordInput.js +3 -5
  167. package/dist/internal/blocks/PasswordInput.js.map +1 -1
  168. package/dist/internal/blocks/SearchInput.js +1 -1
  169. package/dist/internal/blocks/SearchInputWithNavigation.js +6 -7
  170. package/dist/internal/blocks/SearchInputWithNavigation.js.map +1 -1
  171. package/dist/internal/blocks/SectionNav.js +1 -1
  172. package/dist/internal/blocks/SplitButton.js +2 -2
  173. package/dist/internal/blocks/StatCard.js +1 -1
  174. package/dist/internal/blocks/TextInput.js +1 -1
  175. package/dist/internal/blocks/Textarea.js +3 -8
  176. package/dist/internal/blocks/Textarea.js.map +1 -1
  177. package/dist/internal/blocks/TimeInput.js +4 -6
  178. package/dist/internal/blocks/TimeInput.js.map +1 -1
  179. package/dist/internal/components/Alert.js +1 -1
  180. package/dist/internal/components/AlertModal.js +1 -1
  181. package/dist/internal/components/Avatar.js +1 -1
  182. package/dist/internal/components/Badge.js +1 -1
  183. package/dist/internal/components/BrandLogo.js +1 -1
  184. package/dist/internal/components/Breadcrumb.js +1 -1
  185. package/dist/internal/components/Button.js +2 -2
  186. package/dist/internal/components/ButtonGroup.js +1 -1
  187. package/dist/internal/components/ButtonGroup.js.map +1 -1
  188. package/dist/internal/components/Card.js +1 -1
  189. package/dist/internal/components/Checkbox.js +1 -1
  190. package/dist/internal/components/Clipboard.js +1 -1
  191. package/dist/internal/components/CollapsiblePanel.js +1 -1
  192. package/dist/internal/components/Combobox.js +1 -1
  193. package/dist/internal/components/DatePicker.js +1 -1
  194. package/dist/internal/components/DateRangePicker.js +1 -1
  195. package/dist/internal/components/DropdownMenu.js +1 -1
  196. package/dist/internal/components/FileUpload.js +1 -1
  197. package/dist/internal/components/FullScreenModal.js +1 -1
  198. package/dist/internal/components/HoverCard.js +1 -1
  199. package/dist/internal/components/InputGroup.js +1 -1
  200. package/dist/internal/components/Kbd.js +1 -1
  201. package/dist/internal/components/Modal.js +1 -1
  202. package/dist/internal/components/Navbar.js +1 -1
  203. package/dist/internal/components/Pagination.js +1 -1
  204. package/dist/internal/components/Popconfirm.js +1 -1
  205. package/dist/internal/components/Popover.js +1 -1
  206. package/dist/internal/components/RadioGroup.js +1 -1
  207. package/dist/internal/components/Search.js +1 -1
  208. package/dist/internal/components/Select.js +1 -1
  209. package/dist/internal/components/Sheet.js +1 -1
  210. package/dist/internal/components/Sidebar.js +1 -1
  211. package/dist/internal/components/Skeleton/Skeleton.js +2 -2
  212. package/dist/internal/components/Skeleton/Skeleton.js.map +1 -1
  213. package/dist/internal/components/Slider.js +1 -1
  214. package/dist/internal/components/Stepper.js +1 -1
  215. package/dist/internal/components/Switch.js +1 -1
  216. package/dist/internal/components/Table.js +1 -1
  217. package/dist/internal/components/Tabs.js +1 -1
  218. package/dist/internal/components/ThemeProvider.js +3 -4
  219. package/dist/internal/components/TimePicker.js +1 -1
  220. package/dist/internal/components/Toast.js +2 -2
  221. package/dist/internal/components/Toggle.js +13 -3
  222. package/dist/internal/components/Toggle.js.map +1 -1
  223. package/dist/internal/components/Tooltip.js +1 -1
  224. package/dist/internal/components/Truncated.js +1 -1
  225. package/dist/internal/components/ZoomableSvg.js +1 -1
  226. package/dist/internal/utils.js +1 -1
  227. package/dist/markdown-editor.js +16 -18
  228. package/dist/markdown-editor.js.map +1 -1
  229. package/dist/mermaid.d.ts +1 -2
  230. package/dist/mermaid.js +1 -1
  231. package/dist/rjsf.js +121 -43
  232. package/dist/rjsf.js.map +1 -1
  233. package/dist/styles-DxazJ8Mz.js +81 -0
  234. package/dist/styles-DxazJ8Mz.js.map +1 -0
  235. package/dist/styles-T8xwr0sR.js +73 -0
  236. package/dist/styles-T8xwr0sR.js.map +1 -0
  237. package/dist/styles.css +1 -1
  238. package/dist/svgCache-Dhmbe-7i.js +26 -0
  239. package/dist/svgCache-Dhmbe-7i.js.map +1 -0
  240. package/dist/svgTransforms-Hnvev3Vs.js +38 -0
  241. package/dist/svgTransforms-Hnvev3Vs.js.map +1 -0
  242. package/dist/testing.d.ts +0 -2
  243. package/dist/testing.js +3 -10
  244. package/dist/testing.js.map +1 -1
  245. package/dist/{useColorSchemeState-Cw4MCXUs.js → themeScopeContext-CEVXbYvZ.js} +43 -15
  246. package/dist/themeScopeContext-CEVXbYvZ.js.map +1 -0
  247. package/dist/{useThemeAttributes-B199Regy.js → themeTokens-DCeb62ap.js} +22 -4
  248. package/dist/themeTokens-DCeb62ap.js.map +1 -0
  249. package/dist/{toast-D6Z6MOB5.js → toast-Y3cTMBW8.js} +7 -10
  250. package/dist/toast-Y3cTMBW8.js.map +1 -0
  251. package/dist/{utils-p6tjltbX.js → utils-DwC9u0tM.js} +39 -49
  252. package/dist/utils-DwC9u0tM.js.map +1 -0
  253. package/dist/vega.js +2 -2
  254. package/dist/vegaTheme-ISwGXvof.js +49 -0
  255. package/dist/vegaTheme-ISwGXvof.js.map +1 -0
  256. package/dist/{vegaTheme-VQMWTlBw.js → vegaTokens-DCkvpIVE.js} +36 -44
  257. package/dist/vegaTokens-DCkvpIVE.js.map +1 -0
  258. package/package.json +3 -4
  259. package/src/styles.css +4 -39
  260. package/src/theme.css +42 -0
  261. package/dist/AIKit-C5FbKjwa.js.map +0 -1
  262. package/dist/Alert-DuAGeYyM.js.map +0 -1
  263. package/dist/AlertModal-BDle9ZVQ.js.map +0 -1
  264. package/dist/AutoSaveTextarea-jiqNeeE4.js.map +0 -1
  265. package/dist/Badge-Ba3epgcI.js.map +0 -1
  266. package/dist/BrandEditor-Dq168RJc.js.map +0 -1
  267. package/dist/Breadcrumb-CUyYu5Bi.js.map +0 -1
  268. package/dist/Button-D2JqBAq7.js.map +0 -1
  269. package/dist/Card-Bpc_ZlL4.js.map +0 -1
  270. package/dist/Checkbox-D9rafoKh.js.map +0 -1
  271. package/dist/CodeEditor-Y0iE-SwV.js.map +0 -1
  272. package/dist/ColorInput-D1nC0X6i.js.map +0 -1
  273. package/dist/Combobox-B1Kjoy5J.js.map +0 -1
  274. package/dist/Command-BuOA3V18.js.map +0 -1
  275. package/dist/DateInput-BlKkequE.js.map +0 -1
  276. package/dist/DateRangeInput-DOxJHw8V.js.map +0 -1
  277. package/dist/DateRangePicker-CXpXPMoh.js.map +0 -1
  278. package/dist/DiagramSource-jTwmmi9g.js +0 -11
  279. package/dist/DiagramSource-jTwmmi9g.js.map +0 -1
  280. package/dist/DropdownMenu-DtRYcKEx.js.map +0 -1
  281. package/dist/FileUpload-D6vPG3WB.js.map +0 -1
  282. package/dist/FullScreenModal-CUuAakh3.js.map +0 -1
  283. package/dist/HoverCard-Cy6nhg4u.js +0 -31
  284. package/dist/HoverCard-Cy6nhg4u.js.map +0 -1
  285. package/dist/InlineEdit-DSMIEUUa.js.map +0 -1
  286. package/dist/InputGroup-Dm014ruw.js.map +0 -1
  287. package/dist/Kbd-Du93F3o4.js.map +0 -1
  288. package/dist/MermaidDiagram-D2ThT8Q-.js +0 -302
  289. package/dist/MermaidDiagram-D2ThT8Q-.js.map +0 -1
  290. package/dist/Modal-DQIauzar.js.map +0 -1
  291. package/dist/Popover-BU7nGOv-.js.map +0 -1
  292. package/dist/RadioGroup-C2vk5acr.js.map +0 -1
  293. package/dist/SearchInput-BnU6YHgT.js.map +0 -1
  294. package/dist/Select-CjQgU_6K.js.map +0 -1
  295. package/dist/Sheet-utw4_rUa.js.map +0 -1
  296. package/dist/Sidebar-CKaxOPwY.js.map +0 -1
  297. package/dist/Slider-Rcc2qVct.js.map +0 -1
  298. package/dist/Stepper-BBPembeK.js.map +0 -1
  299. package/dist/Switch-Dw4tvQls.js +0 -201
  300. package/dist/Switch-Dw4tvQls.js.map +0 -1
  301. package/dist/Table-CgSC-fSo.js.map +0 -1
  302. package/dist/TextInput-B5TDDOty.js.map +0 -1
  303. package/dist/ThemeProvider-Dt9b67sW.js.map +0 -1
  304. package/dist/ZoomableSvg-D8sXKpoV.js.map +0 -1
  305. package/dist/context-U293r3v-.js +0 -29
  306. package/dist/context-U293r3v-.js.map +0 -1
  307. package/dist/defaults-i_xG2F_1.js +0 -32
  308. package/dist/defaults-i_xG2F_1.js.map +0 -1
  309. package/dist/styles-BDA5rQz3.js +0 -127
  310. package/dist/styles-BDA5rQz3.js.map +0 -1
  311. package/dist/styles-YQdM7G8c.js +0 -159
  312. package/dist/styles-YQdM7G8c.js.map +0 -1
  313. package/dist/svgTransforms-Dw2hRA-i.js +0 -51
  314. package/dist/svgTransforms-Dw2hRA-i.js.map +0 -1
  315. package/dist/themeTokens-7DEab0kp.js +0 -31
  316. package/dist/themeTokens-7DEab0kp.js.map +0 -1
  317. package/dist/toast-D6Z6MOB5.js.map +0 -1
  318. package/dist/useColorSchemeState-Cw4MCXUs.js.map +0 -1
  319. package/dist/useThemeAttributes-B199Regy.js.map +0 -1
  320. package/dist/utils-p6tjltbX.js.map +0 -1
  321. package/dist/vegaTheme-VQMWTlBw.js.map +0 -1
@@ -1,29 +1,68 @@
1
1
  import { cn } from "./classNames-DcGnTh2N.js";
2
- import { Tooltip, TooltipProvider } from "./utils-p6tjltbX.js";
3
- import { AlertCircle, ArrowDown, ArrowLeft, ArrowRight, ArrowUp, Ban, BarChart3, Bell, Bug, Calendar, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Circle, CircleAlert, CircleCheck, CircleHelp, CirclePause, CirclePlay, CirclePlus, CircleStop, CircleX, Clock, Cog, Copy, Database, Download, Ellipsis, ExternalLink, Eye, File, FilePen, FileText, Flag, Folder, Gauge, GitBranch, GitMerge, GitPullRequest, Globe, Hand, History, Hourglass, Info, Key, Layers, LineChart, Link, List, ListChecks, Loader2, Lock, Mail, Maximize2, Minimize2, Minus, Pencil, Play, Plus, RefreshCw, Rocket, RotateCcw, RotateCw, Save, Search, Send, Settings, Sparkles, Star, Table, ThumbsDown, ThumbsUp, Trash2, TriangleAlert, Upload, User, Users, WandSparkles, Warehouse, Workflow, X, XCircle, Zap } from "./icons-ZIeZXXS7.js";
4
- import { Button } from "./Button-D2JqBAq7.js";
2
+ import { Tooltip, TooltipProvider, isMac } from "./utils-DwC9u0tM.js";
3
+ import { AlertCircle, ArrowDown, ArrowLeft, ArrowRight, ArrowUp, Ban, BarChart3, Bell, BookOpen, Bookmark, Box, Bug, Calendar, ChartBar, ChartPie, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Circle, CircleAlert, CircleCheck, CircleHelp, CirclePause, CirclePlay, CirclePlus, CircleStop, CircleX, Clock, Cloud, Cog, Copy, Database, Download, Ellipsis, ExternalLink, Eye, File, FilePen, FileText, Filter, Flag, Folder, Gauge, GitBranch, GitMerge, GitPullRequest, Globe, Hand, HardDrive, History, Hourglass, Info, Key, Layers, Lightbulb, LineChart, Link, List, ListChecks, Loader2, LoaderCircle, Lock, Mail, Maximize2, Minimize2, Minus, Package, Pause, Pencil, Pin, Play, Plus, Quote, RefreshCw, Rocket, RotateCcw, RotateCw, Save, Search, Send, Server, Settings, Shield, Sparkles, Star, Table, ThumbsDown, ThumbsUp, Trash2, TrendingDown, TrendingUp, TriangleAlert, Upload, User, Users, WandSparkles, Warehouse, Workflow, Wrench, X, XCircle, Zap } from "./icons-ZIeZXXS7.js";
4
+ import { Button } from "./Button-BNkyYU8u.js";
5
5
  import { ButtonGroup } from "./internal/components/ButtonGroup.js";
6
- import { ClipboardButton } from "./ClipboardButton-Urg-kZbh.js";
7
- import { FullScreenModal, FullScreenModalBody, FullScreenModalHeader } from "./FullScreenModal-CUuAakh3.js";
6
+ import { ClipboardButton } from "./ClipboardButton-B1Hr_n_Z.js";
7
+ import { FullScreenModal, FullScreenModalBody, FullScreenModalHeader } from "./FullScreenModal-DAvvlGOz.js";
8
8
  import { Truncated } from "./internal/components/Truncated.js";
9
- import { HoverCard } from "./HoverCard-Cy6nhg4u.js";
10
- import { Command } from "./Command-BuOA3V18.js";
11
- import { TextInput } from "./TextInput-B5TDDOty.js";
12
- import { Badge } from "./Badge-Ba3epgcI.js";
9
+ import { useStableCallback } from "./internal/hooks/useStableCallback.js";
10
+ import { HoverCard } from "./HoverCard-Cv5rmvJq.js";
11
+ import { Command } from "./Command-DhXmqFLT.js";
12
+ import { TextInput } from "./TextInput-CR0_USaY.js";
13
+ import { Badge } from "./Badge-5GHIsgRS.js";
13
14
  import { Textarea } from "./internal/blocks/Textarea.js";
14
- import { Kbd, keys } from "./Kbd-Du93F3o4.js";
15
- import { Table as Table$1 } from "./Table-CgSC-fSo.js";
15
+ import { Kbd, keys } from "./Kbd-CflKthfu.js";
16
+ import { Collapsible } from "./Collapsible-DD4xTL75.js";
17
+ import { Alert } from "./Alert-Chz6KZ7e.js";
18
+ import { Table as Table$1, tableAriaSort } from "./Table-CilEvpMq.js";
16
19
  import { DiagramSkeleton } from "./internal/blocks/DiagramShared/DiagramSkeleton.js";
17
- import { DiagramSource } from "./DiagramSource-jTwmmi9g.js";
20
+ import { DiagramSource, makeResponsive } from "./svgTransforms-Hnvev3Vs.js";
21
+ import { ZoomableSvg, toSvgFileContent } from "./ZoomableSvg-DnFXo2L4.js";
18
22
  import { Progress } from "./internal/components/Progress.js";
19
23
  import { digitFromKeyCode, useAltDigitHotkey } from "./internal/hooks/useAltDigitHotkey.js";
20
- import { createContext, isValidElement, use, useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
24
+ import { createContext, isValidElement, use, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
21
25
  import { Slot } from "@radix-ui/react-slot";
22
26
  import { cva } from "class-variance-authority";
23
27
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
24
28
  import TextareaAutosize from "react-textarea-autosize";
25
29
  import ReactMarkdown from "react-markdown";
26
30
  import remarkGfm from "remark-gfm";
31
+ import DOMPurify from "dompurify";
32
+ //#region src/blocks/AIKit/AnswerCommentInput.tsx
33
+ const MAX_COMMENT_LENGTH = 2e3;
34
+ /**
35
+ * One-line follow-up to a thumbs-down.
36
+ *
37
+ * - Submits on Enter or blur, trimmed, and only when the text differs from the last one sent.
38
+ * - Never submits an empty comment.
39
+ */
40
+ const AnswerCommentInput = ({ placeholder, onSubmit }) => {
41
+ const [text, setText] = useState("");
42
+ const lastSubmittedRef = useRef("");
43
+ const submit = () => {
44
+ const comment = text.trim();
45
+ if (!comment || comment === lastSubmittedRef.current) return;
46
+ lastSubmittedRef.current = comment;
47
+ onSubmit(comment);
48
+ };
49
+ const handleKeyDown = (event) => {
50
+ if (event.key !== "Enter") return;
51
+ event.preventDefault();
52
+ submit();
53
+ };
54
+ return /* @__PURE__ */ jsx(TextInput, {
55
+ value: text,
56
+ onValueChange: setText,
57
+ onKeyDown: handleKeyDown,
58
+ onBlur: submit,
59
+ maxLength: MAX_COMMENT_LENGTH,
60
+ placeholder,
61
+ "aria-label": placeholder,
62
+ className: "tw:w-64 tw:max-w-full tw:[&_input]:text-ellipsis"
63
+ });
64
+ };
65
+ //#endregion
27
66
  //#region src/blocks/AIKit/AnswerActions.tsx
28
67
  const VOTE_BUTTONS = [{
29
68
  vote: "up",
@@ -54,7 +93,7 @@ const SIZES = {
54
93
  * - A vote can be changed but not withdrawn; pressing the pressed one re-sends it.
55
94
  * - The caller owns the recorded vote and the write.
56
95
  */
57
- const AnswerActions = ({ copyText, vote, onVote, onCopy, actions, labels = DEFAULT_LABELS, confirmation, size = "small", layout = "inline" }) => {
96
+ const AnswerActions = ({ copyText, vote, onVote, onCopy, actions, labels = DEFAULT_LABELS, confirmation, size = "small", layout = "inline", onDownComment, commentPlaceholder = "What was wrong? (optional)", onRegenerate, regenerateLabel = "Ask again" }) => {
58
97
  const { button, gap } = SIZES[size];
59
98
  const votes = VOTE_BUTTONS.map(({ vote: candidate, icon }) => {
60
99
  const isRecorded = vote === candidate;
@@ -81,32 +120,176 @@ const AnswerActions = ({ copyText, vote, onVote, onCopy, actions, labels = DEFAU
81
120
  variant: "invisible",
82
121
  onClick: onCopy
83
122
  }) : null;
123
+ const regenerate = onRegenerate ? /* @__PURE__ */ jsx(Tooltip, {
124
+ tooltip: regenerateLabel,
125
+ placement: "top",
126
+ triggerAsChild: true,
127
+ children: /* @__PURE__ */ jsx(Button, {
128
+ size: button,
129
+ variant: "invisible",
130
+ "aria-label": regenerateLabel,
131
+ onClick: onRegenerate,
132
+ children: /* @__PURE__ */ jsx(RotateCcw, { className: "tw:size-4" })
133
+ })
134
+ }) : null;
135
+ const comment = onDownComment && vote === "down" ? /* @__PURE__ */ jsx(AnswerCommentInput, {
136
+ placeholder: commentPlaceholder,
137
+ onSubmit: onDownComment
138
+ }) : null;
84
139
  const said = confirmation ? /* @__PURE__ */ jsx("span", {
85
140
  "aria-live": "polite",
86
141
  className: "tw:ml-1 tw:text-xs tw:font-medium tw:text-foreground tw:empty:hidden tw:animate-in tw:fade-in tw:slide-in-from-left-1 tw:duration-300",
87
142
  children: vote ? confirmation[vote] : ""
88
143
  }) : null;
89
144
  if (layout === "split") return /* @__PURE__ */ jsxs("div", {
90
- className: "tw:flex tw:w-full tw:items-center tw:justify-between tw:gap-2",
91
- children: [/* @__PURE__ */ jsxs(ButtonGroup, {
92
- space: gap,
93
- "aria-label": "Answer actions",
94
- children: [votes, said]
95
- }), /* @__PURE__ */ jsxs(ButtonGroup, {
96
- space: gap,
97
- children: [actions, copy]
98
- })]
145
+ className: "tw:flex tw:w-full tw:flex-wrap tw:items-center tw:justify-between tw:gap-2",
146
+ children: [
147
+ /* @__PURE__ */ jsxs(ButtonGroup, {
148
+ space: gap,
149
+ "aria-label": "Answer actions",
150
+ children: [votes, said]
151
+ }),
152
+ comment,
153
+ /* @__PURE__ */ jsxs(ButtonGroup, {
154
+ space: gap,
155
+ children: [
156
+ actions,
157
+ regenerate,
158
+ copy
159
+ ]
160
+ })
161
+ ]
99
162
  });
100
- return /* @__PURE__ */ jsxs(ButtonGroup, {
163
+ const group = /* @__PURE__ */ jsxs(ButtonGroup, {
101
164
  space: gap,
102
165
  "aria-label": "Answer actions",
103
166
  children: [
104
167
  copy,
105
168
  votes,
169
+ regenerate,
106
170
  actions,
107
171
  said
108
172
  ]
109
173
  });
174
+ if (!comment) return group;
175
+ return /* @__PURE__ */ jsxs("div", {
176
+ className: "tw:flex tw:flex-wrap tw:items-center tw:gap-2",
177
+ children: [group, comment]
178
+ });
179
+ };
180
+ //#endregion
181
+ //#region src/blocks/AIKit/SourceChip.tsx
182
+ const STATE_CLASSES = {
183
+ verified: "tw:border-solid tw:border-success/30 tw:bg-success/10 tw:text-foreground",
184
+ unverified: "tw:border-dashed tw:border-warning/60 tw:bg-warning/10 tw:text-foreground",
185
+ neutral: "tw:border-solid tw:border-border tw:bg-muted tw:text-foreground"
186
+ };
187
+ const STATE_WORD = {
188
+ verified: "verified",
189
+ unverified: "unverified"
190
+ };
191
+ const accessibleName = ({ kind, label, state = "neutral" }) => [[kind, label].filter(Boolean).join(" "), STATE_WORD[state]].filter(Boolean).join(", ");
192
+ /**
193
+ * One cited source in an answer's provenance row.
194
+ *
195
+ * - The verdict rides the border and the accessible name, not the visible text.
196
+ * - A link when it has `href`, a button when it only has `onClick`, a plain label otherwise.
197
+ * - A plain left click goes to `onClick`; modified clicks keep the link's own behaviour.
198
+ */
199
+ const SourceChip = (props) => {
200
+ const { label, state = "neutral", icon, href, external, onClick, title, className } = props;
201
+ const classes = cn("tailwindcss-preflight tw:inline-flex tw:h-6 tw:max-w-64 tw:min-w-0 tw:items-center tw:gap-1 tw:rounded-md tw:border tw:px-2 tw:text-xs tw:no-underline", STATE_CLASSES[state], (href || onClick) && "tw:cursor-pointer tw:hover:bg-hover tw:focus-visible:outline-none tw:focus-visible:ring-2 tw:focus-visible:ring-ring", className);
202
+ const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [icon && /* @__PURE__ */ jsx("span", {
203
+ "aria-hidden": true,
204
+ className: "tw:inline-flex tw:shrink-0 tw:[&_svg]:size-3.5",
205
+ children: icon
206
+ }), /* @__PURE__ */ jsx("span", {
207
+ className: "tw:min-w-0 tw:truncate",
208
+ children: label
209
+ })] });
210
+ const common = {
211
+ "aria-label": accessibleName(props),
212
+ title,
213
+ "data-state": state
214
+ };
215
+ if (href) return /* @__PURE__ */ jsx("a", {
216
+ ...common,
217
+ href,
218
+ target: external ? "_blank" : void 0,
219
+ rel: external ? "noopener noreferrer" : void 0,
220
+ className: classes,
221
+ onClick: (event) => {
222
+ const isModified = event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
223
+ if (!onClick || isModified || event.button !== 0) return;
224
+ event.preventDefault();
225
+ onClick();
226
+ },
227
+ children: content
228
+ });
229
+ if (onClick) return /* @__PURE__ */ jsx("button", {
230
+ ...common,
231
+ type: "button",
232
+ className: cn(classes, "tw:bg-clip-padding"),
233
+ onClick,
234
+ children: content
235
+ });
236
+ return /* @__PURE__ */ jsx("span", {
237
+ ...common,
238
+ className: classes,
239
+ children: content
240
+ });
241
+ };
242
+ //#endregion
243
+ //#region src/blocks/AIKit/AnswerSources.tsx
244
+ const ANSWER_SOURCES_VISIBLE = 6;
245
+ /**
246
+ * The provenance row under an answer: where its figures came from.
247
+ *
248
+ * - Says "N unverified" once, instead of on every chip, so checked sources can look calm.
249
+ * - Collapses past `visibleCount` behind a "+N more" toggle.
250
+ * - Empty `sources` states that none were declared, which is itself information.
251
+ */
252
+ const AnswerSources = ({ sources, label = "Sources", emptyLabel = "none declared", visibleCount = 6, footnote, className }) => {
253
+ const [isExpanded, setIsExpanded] = useState(false);
254
+ const isCollapsible = sources.length > visibleCount;
255
+ const shown = isCollapsible && !isExpanded ? sources.slice(0, visibleCount) : sources;
256
+ const unverifiedCount = sources.filter((source) => source.state === "unverified").length;
257
+ return /* @__PURE__ */ jsxs("div", {
258
+ className: cn("tailwindcss-preflight tw:flex tw:flex-col tw:gap-1 tw:text-xs", className),
259
+ children: [/* @__PURE__ */ jsxs("div", {
260
+ className: "tw:flex tw:flex-wrap tw:items-center tw:gap-1.5",
261
+ children: [
262
+ /* @__PURE__ */ jsx("span", {
263
+ className: "tw:font-medium tw:text-muted-foreground",
264
+ children: label
265
+ }),
266
+ sources.length === 0 && /* @__PURE__ */ jsx("span", {
267
+ className: "tw:text-muted-foreground",
268
+ children: emptyLabel
269
+ }),
270
+ shown.map(({ id, ...chip }) => /* @__PURE__ */ jsx(SourceChip, { ...chip }, id)),
271
+ isCollapsible && /* @__PURE__ */ jsx("button", {
272
+ type: "button",
273
+ "aria-expanded": isExpanded,
274
+ onClick: () => setIsExpanded((current) => !current),
275
+ className: "tw:cursor-pointer tw:border-0 tw:bg-transparent tw:p-0 tw:text-xs tw:text-muted-foreground tw:underline tw:hover:text-foreground",
276
+ children: isExpanded ? "Show fewer" : `+${sources.length - visibleCount} more`
277
+ }),
278
+ unverifiedCount > 0 && /* @__PURE__ */ jsxs("span", {
279
+ className: "tw:inline-flex tw:items-center tw:gap-1 tw:text-warning",
280
+ title: "No tool call in this turn touched these: the answer named them, nothing ran on them.",
281
+ children: [
282
+ /* @__PURE__ */ jsx(TriangleAlert, {
283
+ "aria-hidden": true,
284
+ className: "tw:size-3.5"
285
+ }),
286
+ unverifiedCount,
287
+ " unverified"
288
+ ]
289
+ })
290
+ ]
291
+ }), footnote]
292
+ });
110
293
  };
111
294
  //#endregion
112
295
  //#region src/blocks/AIKit/CollapsibleBody.tsx
@@ -185,23 +368,55 @@ const renderVariantIcon = (variant) => {
185
368
  if (variant === "dismissed") return /* @__PURE__ */ jsx(XCircle, { className: "tw:size-3.5 tw:text-muted-foreground/50" });
186
369
  return /* @__PURE__ */ jsx(CircleCheck, { className: "tw:size-3.5 tw:text-muted-foreground/50" });
187
370
  };
188
- const InlineStepLabel = ({ variant, children }) => /* @__PURE__ */ jsxs("div", {
189
- className: "tw:flex tw:min-w-0 tw:items-center tw:gap-1",
190
- children: [/* @__PURE__ */ jsx("div", {
191
- className: "tw:relative tw:flex tw:size-5 tw:shrink-0 tw:items-center tw:justify-center tw:[background:var(--inline-step-surface,transparent)]",
192
- children: renderVariantIcon(variant)
193
- }), /* @__PURE__ */ jsx("div", {
194
- className: "tw:flex tw:min-w-0 tw:flex-wrap tw:items-center tw:gap-x-1.5 tw:gap-y-1 tw:text-xs tw:font-medium tw:text-muted-foreground",
195
- children
196
- })]
197
- });
371
+ const InlineStepLabel = ({ variant, children, meta, detail, defaultExpanded = false }) => {
372
+ const [isExpanded, setIsExpanded] = useState(defaultExpanded);
373
+ const row = /* @__PURE__ */ jsxs("div", {
374
+ className: "tw:flex tw:min-w-0 tw:items-center tw:gap-1",
375
+ children: [/* @__PURE__ */ jsx("div", {
376
+ className: "tw:relative tw:flex tw:size-5 tw:shrink-0 tw:items-center tw:justify-center tw:[background:var(--inline-step-surface,transparent)]",
377
+ children: renderVariantIcon(variant)
378
+ }), /* @__PURE__ */ jsxs("div", {
379
+ className: "tw:flex tw:min-w-0 tw:flex-wrap tw:items-center tw:gap-x-1.5 tw:gap-y-1 tw:text-xs tw:font-medium tw:text-muted-foreground",
380
+ children: [
381
+ children,
382
+ meta && /* @__PURE__ */ jsx("span", {
383
+ className: "tw:font-normal tw:text-muted-foreground/70",
384
+ children: meta
385
+ }),
386
+ detail && /* @__PURE__ */ jsx(Button, {
387
+ variant: "inline",
388
+ size: "small",
389
+ className: "tw:px-0",
390
+ "aria-expanded": isExpanded,
391
+ onClick: () => setIsExpanded((value) => !value),
392
+ children: isExpanded ? "Hide details" : "Details"
393
+ })
394
+ ]
395
+ })]
396
+ });
397
+ if (!detail) return row;
398
+ return /* @__PURE__ */ jsxs("div", {
399
+ className: "tw:flex tw:min-w-0 tw:flex-col tw:gap-1",
400
+ children: [row, isExpanded && /* @__PURE__ */ jsx("div", {
401
+ className: "tw:min-w-0 tw:pl-6 tw:text-xs",
402
+ children: detail
403
+ })]
404
+ });
405
+ };
198
406
  //#endregion
199
407
  //#region src/blocks/AIKit/AnswerSummary.tsx
200
408
  const SECTION_LABELS = {
201
409
  success: "Answered questions",
202
- dismissed: "Dismissed questions"
410
+ dismissed: "Dismissed questions",
411
+ expired: "Expired questions"
203
412
  };
204
- const AnswerSummary = ({ items, variant = "success", noAnswerLabel = "No answer", onExpandToggle }) => {
413
+ const STEP_VARIANT = {
414
+ success: "success",
415
+ dismissed: "dismissed",
416
+ expired: "dismissed"
417
+ };
418
+ const DEFAULT_STATUS_LABEL = { expired: "Expired" };
419
+ const AnswerSummary = ({ items, variant = "success", statusLabel = DEFAULT_STATUS_LABEL[variant], statusMessage, noAnswerLabel = "No answer", onExpandToggle }) => {
205
420
  const [isExpanded, setIsExpanded] = useState(false);
206
421
  const [isClosing, setIsClosing] = useState(false);
207
422
  const toggle = () => {
@@ -212,42 +427,144 @@ const AnswerSummary = ({ items, variant = "success", noAnswerLabel = "No answer"
212
427
  };
213
428
  const count = items.length;
214
429
  const [firstItem] = items;
215
- const label = count === 1 && firstItem ? firstItem.question : `${count} questions`;
430
+ const questionLabel = count === 1 && firstItem ? firstItem.question : `${count} questions`;
431
+ const label = statusLabel ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
432
+ questionLabel,
433
+ " ",
434
+ /* @__PURE__ */ jsx("span", {
435
+ className: "tw:font-normal",
436
+ children: statusLabel
437
+ })
438
+ ] }) : questionLabel;
439
+ const stepVariant = STEP_VARIANT[variant];
216
440
  return /* @__PURE__ */ jsxs("section", {
217
441
  className: "tw:mt-4 tw:flex tw:flex-col",
218
442
  "aria-label": SECTION_LABELS[variant],
219
443
  children: [/* @__PURE__ */ jsx(CollapsibleStepHeader, {
220
444
  isExpanded,
221
445
  onToggle: toggle,
222
- variant,
446
+ variant: stepVariant,
223
447
  label
224
448
  }), /* @__PURE__ */ jsx(CollapsibleBody, {
225
449
  isExpanded,
226
450
  isClosing,
227
451
  onClosed: () => setIsClosing(false),
228
- children: /* @__PURE__ */ jsx("div", {
452
+ children: /* @__PURE__ */ jsxs("div", {
229
453
  className: "tw:flex tw:flex-col tw:gap-4 tw:pt-4 tw:pl-5",
230
- children: items.map(({ question, answer }, index) => /* @__PURE__ */ jsxs("div", {
454
+ children: [statusMessage != null && /* @__PURE__ */ jsx("p", {
455
+ className: "tw:m-0 tw:text-xs tw:text-muted-foreground",
456
+ children: statusMessage
457
+ }), items.map(({ question, answer }, index) => /* @__PURE__ */ jsxs("div", {
231
458
  className: "tw:flex tw:flex-col tw:gap-1",
232
459
  children: [/* @__PURE__ */ jsx(InlineStepLabel, {
233
- variant,
460
+ variant: stepVariant,
234
461
  children: question
235
462
  }), /* @__PURE__ */ jsx(InlineStepDetail, { children: answer && answer.length > 0 ? answer : noAnswerLabel })]
236
- }, `${index}-${question}`))
463
+ }, `${index}-${question}`))]
237
464
  })
238
465
  })]
239
466
  });
240
467
  };
241
468
  //#endregion
242
- //#region src/blocks/AIKit/ChatInlineIcon.tsx
469
+ //#region src/blocks/AIKit/AttachmentChip.tsx
470
+ const Lead = ({ status, thumbnailUrl }) => {
471
+ if (status === "uploading") return /* @__PURE__ */ jsx(LoaderCircle, {
472
+ "aria-hidden": true,
473
+ className: "tw:size-4 tw:shrink-0 tw:animate-spin"
474
+ });
475
+ if (status === "error") return /* @__PURE__ */ jsx(CircleAlert, {
476
+ "aria-hidden": true,
477
+ className: "tw:size-4 tw:shrink-0 tw:text-destructive"
478
+ });
479
+ if (thumbnailUrl) return /* @__PURE__ */ jsx("img", {
480
+ src: thumbnailUrl,
481
+ alt: "",
482
+ className: "tw:size-4 tw:shrink-0 tw:rounded-sm tw:object-cover"
483
+ });
484
+ return /* @__PURE__ */ jsx(FileText, {
485
+ "aria-hidden": true,
486
+ className: "tw:size-4 tw:shrink-0"
487
+ });
488
+ };
243
489
  /**
244
- * The Lucide component each `icon:<name>` token draws.
490
+ * A file attached to a message being composed.
245
491
  *
246
- * - Names are FontAwesome-solid ones, published as `CHAT_INLINE_ICON_NAMES`.
247
- * - The `satisfies` is the drift guard: a name the prompt teaches and this map lacks fails to compile.
248
- * - Read through `Object.hasOwn`: `icon:constructor` would otherwise hit `Object.prototype`.
492
+ * - Capped width with a truncating name, so a long filename cannot push the text away.
493
+ * - `error` explains itself in a tooltip; the remove control stays available in every status.
249
494
  */
495
+ const AttachmentChip = ({ name, thumbnailUrl, status, error, onRemove, trailing, className }) => {
496
+ const chip = /* @__PURE__ */ jsxs("span", {
497
+ "data-status": status,
498
+ className: cn("tailwindcss-preflight tw:inline-flex tw:h-6 tw:min-w-0 tw:max-w-48 tw:items-center tw:gap-1 tw:rounded-md tw:bg-muted tw:px-2 tw:text-xs tw:text-muted-foreground", status === "error" && "tw:text-destructive", className),
499
+ children: [
500
+ /* @__PURE__ */ jsx(Lead, {
501
+ status,
502
+ thumbnailUrl
503
+ }),
504
+ /* @__PURE__ */ jsx("span", {
505
+ className: "tw:min-w-0 tw:truncate",
506
+ children: name
507
+ }),
508
+ trailing,
509
+ /* @__PURE__ */ jsx("button", {
510
+ type: "button",
511
+ "aria-label": `Remove ${name}`,
512
+ onClick: onRemove,
513
+ className: "tw:-mr-1 tw:shrink-0 tw:cursor-pointer tw:rounded-sm tw:border-0 tw:bg-transparent tw:p-0 tw:text-muted-foreground tw:hover:text-foreground tw:focus-visible:outline-none tw:focus-visible:ring-2 tw:focus-visible:ring-ring",
514
+ children: /* @__PURE__ */ jsx(X, { className: "tw:size-3.5" })
515
+ })
516
+ ]
517
+ });
518
+ if (status !== "error" || !error) return chip;
519
+ return /* @__PURE__ */ jsx(Tooltip, {
520
+ tooltip: error,
521
+ placement: "top",
522
+ triggerAsChild: true,
523
+ children: chip
524
+ });
525
+ };
526
+ //#endregion
527
+ //#region src/blocks/AIKit/ChatInlineIcon.tsx
528
+ /** Every drawable name; read through `Object.hasOwn`, or `icon:constructor` hits `Object.prototype`. */
250
529
  const ICON_REGISTRY = {
530
+ "book-open": BookOpen,
531
+ bookmark: Bookmark,
532
+ box: Box,
533
+ "chart-bar": ChartBar,
534
+ "chart-pie": ChartPie,
535
+ "circle-alert": CircleAlert,
536
+ "circle-help": CircleHelp,
537
+ "circle-x": CircleX,
538
+ cloud: Cloud,
539
+ "external-link": ExternalLink,
540
+ "file-text": FileText,
541
+ filter: Filter,
542
+ "git-branch": GitBranch,
543
+ "hard-drive": HardDrive,
544
+ history: History,
545
+ hourglass: Hourglass,
546
+ info: Info,
547
+ layers: Layers,
548
+ lightbulb: Lightbulb,
549
+ list: List,
550
+ "list-checks": ListChecks,
551
+ mail: Mail,
552
+ package: Package,
553
+ pause: Pause,
554
+ pencil: Pencil,
555
+ pin: Pin,
556
+ "refresh-cw": RefreshCw,
557
+ server: Server,
558
+ settings: Settings,
559
+ shield: Shield,
560
+ "trash-2": Trash2,
561
+ "trending-down": TrendingDown,
562
+ "trending-up": TrendingUp,
563
+ "triangle-alert": TriangleAlert,
564
+ users: Users,
565
+ workflow: Workflow,
566
+ wrench: Wrench,
567
+ x: X,
251
568
  "alert-circle": AlertCircle,
252
569
  "arrow-down": ArrowDown,
253
570
  "arrow-left": ArrowLeft,
@@ -390,25 +707,152 @@ const ChatInlineIcon = ({ name }) => {
390
707
  });
391
708
  };
392
709
  //#endregion
710
+ //#region src/blocks/AIKit/useCountdown.ts
711
+ const TICK_MS = 1e3;
712
+ /** Whole seconds left of a countdown that starts at `seconds`; `undefined` when there is none. */
713
+ const useCountdown = (seconds) => {
714
+ const [ticks, setTicks] = useState({
715
+ start: seconds,
716
+ elapsed: 0
717
+ });
718
+ useEffect(() => {
719
+ if (seconds === void 0 || seconds <= 0) return void 0;
720
+ const interval = setInterval(() => {
721
+ setTicks((current) => ({
722
+ start: seconds,
723
+ elapsed: (current.start === seconds ? current.elapsed : 0) + 1
724
+ }));
725
+ }, TICK_MS);
726
+ return () => clearInterval(interval);
727
+ }, [seconds]);
728
+ if (seconds === void 0) return void 0;
729
+ const elapsed = ticks.start === seconds ? ticks.elapsed : 0;
730
+ return Math.max(seconds - elapsed, 0);
731
+ };
732
+ //#endregion
733
+ //#region src/blocks/AIKit/ChatNotice.tsx
734
+ const ALERT_VARIANT = {
735
+ error: "error",
736
+ warn: "warning",
737
+ info: "info"
738
+ };
739
+ const countdownLabel = (remaining) => remaining > 0 ? `Try again in ${remaining}s.` : "You can try again now.";
740
+ /**
741
+ * A notice row in a chat transcript.
742
+ *
743
+ * - Thin wrapper over `Alert`; `warn` maps to the warning variant.
744
+ * - `details` stays collapsed so a stack trace never crowds the message.
745
+ * - `retryAfter` appends a live countdown; `action` is always rendered, so the host decides whether to gate it.
746
+ */
747
+ const ChatNotice = ({ tone, children, action, details, retryAfter, className }) => {
748
+ const remaining = useCountdown(retryAfter);
749
+ return /* @__PURE__ */ jsx(Alert, {
750
+ variant: ALERT_VARIANT[tone],
751
+ role: "status",
752
+ className,
753
+ children: /* @__PURE__ */ jsxs("div", {
754
+ className: "tw:flex tw:flex-wrap tw:items-start tw:gap-x-3 tw:gap-y-2",
755
+ children: [/* @__PURE__ */ jsxs("div", {
756
+ className: "tw:min-w-0 tw:flex-1 tw:basis-40",
757
+ children: [
758
+ /* @__PURE__ */ jsx("span", { children }),
759
+ remaining !== void 0 && /* @__PURE__ */ jsx("span", {
760
+ className: "tw:ml-1 tw:text-muted-foreground",
761
+ "data-testid": "chat-notice-countdown",
762
+ children: countdownLabel(remaining)
763
+ }),
764
+ details && /* @__PURE__ */ jsxs(Collapsible, {
765
+ className: "tw:mt-1",
766
+ children: [/* @__PURE__ */ jsxs(Collapsible.Trigger, {
767
+ className: "tailwindcss-preflight tw:group tw:flex tw:cursor-pointer tw:items-center tw:gap-1 tw:border-0 tw:bg-transparent tw:p-0 tw:text-xs tw:text-muted-foreground tw:hover:text-foreground",
768
+ children: ["Technical details", /* @__PURE__ */ jsx(ChevronDown, { className: "tw:size-3 tw:transition-transform tw:group-data-[state=open]:rotate-180" })]
769
+ }), /* @__PURE__ */ jsx(Collapsible.Content, { children: /* @__PURE__ */ jsx("code", {
770
+ className: "tw:mt-1 tw:block tw:whitespace-pre-wrap tw:break-words tw:text-xs",
771
+ children: details
772
+ }) })]
773
+ })
774
+ ]
775
+ }), action && /* @__PURE__ */ jsx("div", {
776
+ className: "tw:ml-auto tw:flex tw:shrink-0 tw:gap-2",
777
+ children: action
778
+ })]
779
+ })
780
+ });
781
+ };
782
+ //#endregion
783
+ //#region src/blocks/AIKit/ContextChip.tsx
784
+ const CONTEXT_CHIP_EXPANDABLE_LENGTH = 120;
785
+ /**
786
+ * A quote or reference attached to a message being composed.
787
+ *
788
+ * - The excerpt is clamped to two lines; "Show all" appears once it is long enough to be cut.
789
+ * - Images sit under the excerpt as small thumbnails.
790
+ */
791
+ const ContextChip = ({ label, excerpt, images = [], onRemove, className }) => {
792
+ const [isExpanded, setIsExpanded] = useState(false);
793
+ const isExpandable = excerpt.length > 120;
794
+ return /* @__PURE__ */ jsxs("div", {
795
+ className: cn("tailwindcss-preflight tw:flex tw:w-full tw:min-w-0 tw:flex-col tw:gap-1 tw:rounded-md tw:border-0 tw:border-l-2 tw:border-solid tw:border-border tw:bg-muted tw:px-3 tw:py-2 tw:text-xs", className),
796
+ children: [
797
+ /* @__PURE__ */ jsxs("div", {
798
+ className: "tw:flex tw:items-center tw:gap-1 tw:text-muted-foreground",
799
+ children: [
800
+ /* @__PURE__ */ jsx(Quote, {
801
+ "aria-hidden": true,
802
+ className: "tw:size-3.5 tw:shrink-0"
803
+ }),
804
+ /* @__PURE__ */ jsx("span", {
805
+ className: "tw:min-w-0 tw:flex-1 tw:truncate tw:font-medium",
806
+ children: label
807
+ }),
808
+ /* @__PURE__ */ jsx("button", {
809
+ type: "button",
810
+ "aria-label": `Remove ${label}`,
811
+ onClick: onRemove,
812
+ className: "tw:shrink-0 tw:cursor-pointer tw:rounded-sm tw:border-0 tw:bg-transparent tw:p-0 tw:text-muted-foreground tw:hover:text-foreground tw:focus-visible:outline-none tw:focus-visible:ring-2 tw:focus-visible:ring-ring",
813
+ children: /* @__PURE__ */ jsx(X, { className: "tw:size-3.5" })
814
+ })
815
+ ]
816
+ }),
817
+ /* @__PURE__ */ jsx("p", {
818
+ className: cn("tw:m-0 tw:whitespace-pre-wrap tw:break-words tw:text-foreground", !isExpanded && "tw:line-clamp-2"),
819
+ children: excerpt
820
+ }),
821
+ isExpandable && /* @__PURE__ */ jsx("button", {
822
+ type: "button",
823
+ "aria-expanded": isExpanded,
824
+ onClick: () => setIsExpanded((current) => !current),
825
+ className: "tw:w-fit tw:cursor-pointer tw:border-0 tw:bg-transparent tw:p-0 tw:text-xs tw:text-muted-foreground tw:underline tw:hover:text-foreground",
826
+ children: isExpanded ? "Show less" : "Show all"
827
+ }),
828
+ images.length > 0 && /* @__PURE__ */ jsx("div", {
829
+ className: "tw:flex tw:flex-wrap tw:gap-1",
830
+ children: images.map((image) => /* @__PURE__ */ jsx("img", {
831
+ src: image.src,
832
+ alt: image.alt,
833
+ className: "tw:size-10 tw:rounded-sm tw:border tw:border-border tw:object-cover"
834
+ }, image.src))
835
+ })
836
+ ]
837
+ });
838
+ };
839
+ //#endregion
393
840
  //#region src/blocks/AIKit/FileDownloadChip.tsx
394
841
  /**
395
842
  * A file the agent wrote, offered inside its own answer.
396
843
  *
397
- * - The whole chip is the control: a small icon beside a truncating filename is hard to hit.
844
+ * - By default the whole chip is the control: a small icon beside a truncating filename is hard to hit.
845
+ * - With `onPreview` or `actions` the chip splits: the name previews (or downloads, without
846
+ * `onPreview`), the download sits beside it, and `actions` follow.
398
847
  * - Presentation only — the host fetches and saves, through its own auth.
399
848
  */
400
- const FileDownloadChip = ({ fileName, filePath, size, isPending = false, onDownload, className }) => /* @__PURE__ */ jsxs(Button, {
401
- variant: "secondary",
402
- size: "small",
403
- isLoading: isPending,
404
- "aria-disabled": isPending,
405
- "aria-label": `Download ${filePath ?? fileName}`,
406
- title: filePath,
407
- onClick: () => {
849
+ const FileDownloadChip = ({ fileName, filePath, size, isPending = false, onDownload, onPreview, actions, className }) => {
850
+ const isSplit = onPreview !== void 0 || actions !== void 0;
851
+ const target = filePath ?? fileName;
852
+ const handleDownload = () => {
408
853
  if (!isPending) onDownload();
409
- },
410
- className: cn("tw:max-w-full tw:justify-start tw:gap-2 tw:font-normal", className),
411
- children: [
854
+ };
855
+ const nameAndSize = /* @__PURE__ */ jsxs(Fragment$1, { children: [
412
856
  /* @__PURE__ */ jsx(FileText, { className: "tw:size-4 tw:shrink-0 tw:text-muted-foreground" }),
413
857
  /* @__PURE__ */ jsx("span", {
414
858
  className: "tw:truncate",
@@ -417,10 +861,148 @@ const FileDownloadChip = ({ fileName, filePath, size, isPending = false, onDownl
417
861
  size !== void 0 && /* @__PURE__ */ jsx("span", {
418
862
  className: "tw:shrink-0 tw:text-xs tw:text-muted-foreground",
419
863
  children: size
420
- }),
421
- !isPending && /* @__PURE__ */ jsx(Download, { className: "tw:size-4 tw:shrink-0" })
422
- ]
423
- });
864
+ })
865
+ ] });
866
+ if (!isSplit) return /* @__PURE__ */ jsxs(Button, {
867
+ variant: "secondary",
868
+ size: "small",
869
+ isLoading: isPending,
870
+ "aria-disabled": isPending,
871
+ "aria-label": `Download ${target}`,
872
+ title: filePath,
873
+ onClick: handleDownload,
874
+ className: cn("tw:max-w-full tw:justify-start tw:gap-2 tw:font-normal", className),
875
+ children: [nameAndSize, !isPending && /* @__PURE__ */ jsx(Download, { className: "tw:size-4 tw:shrink-0" })]
876
+ });
877
+ return /* @__PURE__ */ jsxs(ButtonGroup, {
878
+ space: "compact",
879
+ className: cn("tw:max-w-full", className),
880
+ children: [
881
+ onPreview ? /* @__PURE__ */ jsx(Button, {
882
+ variant: "secondary",
883
+ size: "small",
884
+ "aria-label": `Preview ${target}`,
885
+ title: filePath,
886
+ onClick: onPreview,
887
+ className: "tw:min-w-0 tw:!shrink tw:justify-start tw:gap-2 tw:overflow-hidden tw:font-normal",
888
+ children: nameAndSize
889
+ }) : null,
890
+ /* @__PURE__ */ jsxs(Button, {
891
+ variant: "secondary",
892
+ size: onPreview ? "icon-sm" : "small",
893
+ isLoading: isPending,
894
+ "aria-disabled": isPending,
895
+ "aria-label": `Download ${target}`,
896
+ title: onPreview ? void 0 : filePath,
897
+ onClick: handleDownload,
898
+ className: cn(onPreview ? "tw:shrink-0" : "tw:min-w-0 tw:!shrink tw:justify-start tw:gap-2 tw:overflow-hidden tw:font-normal"),
899
+ children: [onPreview ? !isPending && /* @__PURE__ */ jsx(Download, { className: "tw:size-4" }) : nameAndSize, !onPreview && !isPending && /* @__PURE__ */ jsx(Download, { className: "tw:size-4 tw:shrink-0" })]
900
+ }),
901
+ actions
902
+ ]
903
+ });
904
+ };
905
+ //#endregion
906
+ //#region src/blocks/AIKit/FileGalleryTile.tsx
907
+ const VISIBLE_MARGIN = "200px";
908
+ /**
909
+ * One image in a `FileGallery`.
910
+ *
911
+ * - Reports itself visible once, 200px before it scrolls in; without an `IntersectionObserver`
912
+ * (jsdom) it reports on mount.
913
+ * - A thumbnail that fails to decode counts as failed, and a press retries instead of opening.
914
+ */
915
+ const FileGalleryTile = ({ image, onOpen, onRetry, onVisible }) => {
916
+ const [tile, setTile] = useState(null);
917
+ const [isBroken, setIsBroken] = useState(false);
918
+ const isFailed = image.isFailed === true || isBroken;
919
+ const isLoading = !isFailed && image.src === void 0;
920
+ const hasVisibleHandler = onVisible !== void 0;
921
+ const reportVisible = useStableCallback(() => onVisible?.(image));
922
+ useEffect(() => {
923
+ if (!tile || !hasVisibleHandler) return;
924
+ if (typeof IntersectionObserver === "undefined") {
925
+ reportVisible();
926
+ return;
927
+ }
928
+ const observer = new IntersectionObserver((entries) => {
929
+ if (!entries.some((entry) => entry.isIntersecting)) return;
930
+ observer.disconnect();
931
+ reportVisible();
932
+ }, { rootMargin: VISIBLE_MARGIN });
933
+ observer.observe(tile);
934
+ return () => observer.disconnect();
935
+ }, [
936
+ tile,
937
+ hasVisibleHandler,
938
+ reportVisible
939
+ ]);
940
+ const handleClick = () => {
941
+ if (!isFailed) return onOpen(image);
942
+ setIsBroken(false);
943
+ onRetry?.(image);
944
+ };
945
+ return /* @__PURE__ */ jsxs(Button, {
946
+ ref: setTile,
947
+ variant: "invisible",
948
+ "aria-label": isFailed ? `Retry preview of ${image.name}` : image.name,
949
+ onClick: handleClick,
950
+ className: cn("tw:relative tw:aspect-square tw:!h-auto tw:!max-h-none tw:!min-h-0 tw:!w-full tw:overflow-hidden tw:rounded-md tw:border tw:border-border tw:bg-muted tw:p-0", isFailed && "tw:text-xs tw:text-muted-foreground"),
951
+ children: [
952
+ isLoading && /* @__PURE__ */ jsx("span", {
953
+ "aria-hidden": true,
954
+ className: "tw:absolute tw:inset-0 tw:animate-pulse tw:bg-muted-foreground/10"
955
+ }),
956
+ isFailed && /* @__PURE__ */ jsx("span", {
957
+ className: "tw:px-2 tw:text-center tw:whitespace-normal",
958
+ children: "Preview unavailable — retry"
959
+ }),
960
+ !isFailed && image.src !== void 0 && /* @__PURE__ */ jsx("img", {
961
+ src: image.src,
962
+ alt: image.name,
963
+ loading: "lazy",
964
+ decoding: "async",
965
+ onError: () => setIsBroken(true),
966
+ className: "tw:size-full tw:object-cover"
967
+ })
968
+ ]
969
+ });
970
+ };
971
+ //#endregion
972
+ //#region src/blocks/AIKit/FileGallery.tsx
973
+ const DEFAULT_MAX_TILES = 12;
974
+ /**
975
+ * Image files the agent wrote, as a grid of thumbnails.
976
+ *
977
+ * - Presentation only: the host owns thumbnail URLs, their loading and their auth.
978
+ * - Tiles ask for their thumbnail as they near the viewport, so a long gallery loads on scroll.
979
+ * - Draws `maxTiles`, then a "+N more" button that reveals the rest.
980
+ */
981
+ const FileGallery = ({ images, onOpen, onRetry, onImageVisible, maxTiles = DEFAULT_MAX_TILES, className }) => {
982
+ const [isShowingAll, setIsShowingAll] = useState(false);
983
+ const shown = isShowingAll ? images : images.slice(0, maxTiles);
984
+ const hiddenCount = images.length - shown.length;
985
+ if (images.length === 0) return null;
986
+ return /* @__PURE__ */ jsxs("div", {
987
+ role: "group",
988
+ "aria-label": "Images",
989
+ className: className ?? "tw:grid tw:grid-cols-[repeat(auto-fill,minmax(6rem,1fr))] tw:gap-2",
990
+ children: [shown.map((image) => /* @__PURE__ */ jsx(FileGalleryTile, {
991
+ image,
992
+ onOpen,
993
+ onRetry,
994
+ onVisible: onImageVisible
995
+ }, image.id)), hiddenCount > 0 && /* @__PURE__ */ jsxs(Button, {
996
+ variant: "secondary",
997
+ onClick: () => setIsShowingAll(true),
998
+ children: [
999
+ "+",
1000
+ hiddenCount,
1001
+ " more"
1002
+ ]
1003
+ })]
1004
+ });
1005
+ };
424
1006
  //#endregion
425
1007
  //#region src/blocks/AIKit/InlineStepGroup.tsx
426
1008
  /** The surface colour, as the custom property the step icons mask against. */
@@ -433,7 +1015,7 @@ const surfaceStyle = (surface) => ({ "--inline-step-surface": surface });
433
1015
  * - Animates `grid-template-rows`; children stay mounted while closed.
434
1016
  * - `role="group"`, not a `region` landmark: dozens per transcript would drown real landmarks.
435
1017
  */
436
- const InlineStepGroup = ({ label, variant = "success", isExpanded, onToggle, surface = "rgb(var(--background))", "aria-label": ariaLabel, "data-testid": testId, className, children }) => /* @__PURE__ */ jsxs("section", {
1018
+ const InlineStepGroup = ({ label, variant = "success", isExpanded, onToggle, surface = "rgb(var(--background))", "aria-label": ariaLabel, "data-testid": testId, className, footer, children }) => /* @__PURE__ */ jsxs("section", {
437
1019
  role: "group",
438
1020
  className: cn("tw:flex tw:flex-col", className),
439
1021
  "aria-label": ariaLabel,
@@ -452,15 +1034,42 @@ const InlineStepGroup = ({ label, variant = "success", isExpanded, onToggle, sur
452
1034
  children: /* @__PURE__ */ jsxs("div", {
453
1035
  style: surfaceStyle(surface),
454
1036
  className: "tw:relative tw:flex tw:flex-col tw:gap-4 tw:pt-4 tw:pl-5",
455
- children: [/* @__PURE__ */ jsx("span", {
456
- "aria-hidden": "true",
457
- className: "tw:pointer-events-none tw:absolute tw:top-4 tw:bottom-0 tw:left-[30px] tw:w-px tw:bg-border"
458
- }), children]
1037
+ children: [
1038
+ /* @__PURE__ */ jsx("span", {
1039
+ "aria-hidden": "true",
1040
+ className: "tw:pointer-events-none tw:absolute tw:top-4 tw:bottom-0 tw:left-[30px] tw:w-px tw:bg-border"
1041
+ }),
1042
+ children,
1043
+ footer && /* @__PURE__ */ jsx("div", {
1044
+ className: "tw:relative tw:pl-6 tw:[background:var(--inline-step-surface,transparent)]",
1045
+ children: footer
1046
+ })
1047
+ ]
459
1048
  })
460
1049
  })
461
1050
  })]
462
1051
  });
463
1052
  //#endregion
1053
+ //#region src/blocks/AIKit/JumpToLatest.tsx
1054
+ /**
1055
+ * Scrolls a transcript back to its newest message.
1056
+ *
1057
+ * - Renders nothing while `visible` is false.
1058
+ * - Positioning is the host's: pass `className` to pin it over the list.
1059
+ */
1060
+ const JumpToLatest = ({ visible, working = false, onClick, label, className }) => {
1061
+ if (!visible) return null;
1062
+ return /* @__PURE__ */ jsxs(Button, {
1063
+ variant: "outline",
1064
+ size: label ? "small" : "icon-sm",
1065
+ "aria-label": label ?? "Scroll to bottom",
1066
+ "data-working": working,
1067
+ onClick,
1068
+ className: cn("tw:rounded-full tw:shadow-md", working && "tw:animate-pulse tw:motion-reduce:animate-none", className),
1069
+ children: [/* @__PURE__ */ jsx(ArrowDown, { className: "tw:size-4" }), label]
1070
+ });
1071
+ };
1072
+ //#endregion
464
1073
  //#region src/blocks/AIKit/DiagramBlock.tsx
465
1074
  /**
466
1075
  * One diagram block: a skeleton while its chunk loads, the fence source plus a retry if it fails.
@@ -470,7 +1079,7 @@ const InlineStepGroup = ({ label, variant = "success", isExpanded, onToggle, sur
470
1079
  * - A new `load` resets the block, or a dialect switch keeps the previous renderer.
471
1080
  * - Functions in state go through the setter form, or React calls them.
472
1081
  */
473
- const DiagramBlock = ({ load, code }) => {
1082
+ const DiagramBlock = ({ load, code, renderActions }) => {
474
1083
  const [Diagram, setDiagram] = useState(null);
475
1084
  const [failed, setFailed] = useState(false);
476
1085
  const [attempt, setAttempt] = useState(0);
@@ -491,7 +1100,10 @@ const DiagramBlock = ({ load, code }) => {
491
1100
  cancelled = true;
492
1101
  };
493
1102
  }, [load, attempt]);
494
- if (Diagram) return /* @__PURE__ */ jsx(Diagram, { code });
1103
+ if (Diagram) return /* @__PURE__ */ jsx(Diagram, {
1104
+ code,
1105
+ renderActions
1106
+ });
495
1107
  if (failed) return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(DiagramSource, { code }), /* @__PURE__ */ jsx(Button, {
496
1108
  variant: "inline",
497
1109
  size: "small",
@@ -526,6 +1138,622 @@ const diagramChunk = (name, load) => {
526
1138
  };
527
1139
  };
528
1140
  //#endregion
1141
+ //#region src/blocks/AIKit/downloadSvgFile.ts
1142
+ /** How long the object URL outlives the click; the download has started by then. */
1143
+ const REVOKE_DELAY_MS = 1e3;
1144
+ /** Saves sanitized SVG markup as a standalone `.svg` file. */
1145
+ const downloadSvgFile = (svg, filename = "diagram.svg") => {
1146
+ const blob = new Blob([toSvgFileContent(svg)], { type: "image/svg+xml;charset=utf-8" });
1147
+ const url = URL.createObjectURL(blob);
1148
+ const anchor = document.createElement("a");
1149
+ anchor.href = url;
1150
+ anchor.download = filename;
1151
+ document.body.append(anchor);
1152
+ anchor.click();
1153
+ anchor.remove();
1154
+ setTimeout(() => URL.revokeObjectURL(url), REVOKE_DELAY_MS);
1155
+ };
1156
+ //#endregion
1157
+ //#region src/blocks/AIKit/DiagramDownloadButton.tsx
1158
+ /** A diagram toolbar action that saves the drawn diagram as an SVG file. */
1159
+ const DiagramDownloadButton = ({ safeSvg }) => /* @__PURE__ */ jsx(Tooltip, {
1160
+ tooltip: "Download SVG",
1161
+ placement: "top",
1162
+ triggerAsChild: true,
1163
+ children: /* @__PURE__ */ jsx(Button, {
1164
+ size: "icon-sm",
1165
+ variant: "outline",
1166
+ "aria-label": "Download SVG",
1167
+ onClick: () => downloadSvgFile(safeSvg),
1168
+ children: /* @__PURE__ */ jsx(Download, { className: "tw:size-4" })
1169
+ })
1170
+ });
1171
+ //#endregion
1172
+ //#region src/blocks/AIKit/highlightChunk.ts
1173
+ let pending = null;
1174
+ /**
1175
+ * The syntax highlighter, loaded on first use and shared between blocks.
1176
+ *
1177
+ * - The parsers are heavy, so they stay out of the main chunk.
1178
+ * - A failed import is logged and resolves `null`; the next block retries.
1179
+ */
1180
+ const loadHighlighter = () => {
1181
+ pending ??= import("./highlightCode-D354tCXO.js").then((module) => module.highlightCode, (error) => {
1182
+ console.error("[AIKit] failed to load the syntax highlighter chunk", error);
1183
+ pending = null;
1184
+ return null;
1185
+ });
1186
+ return pending;
1187
+ };
1188
+ //#endregion
1189
+ //#region src/blocks/AIKit/HighlightedCode.tsx
1190
+ /**
1191
+ * A fenced block's `<code>`, coloured once the highlighter chunk loads.
1192
+ *
1193
+ * - Functions in state go through the setter form, or React calls them.
1194
+ */
1195
+ const HighlightedCode = ({ code, grammar, className, children }) => {
1196
+ const [highlight, setHighlight] = useState(null);
1197
+ useEffect(() => {
1198
+ let cancelled = false;
1199
+ loadHighlighter().then((loaded) => {
1200
+ if (!cancelled && loaded) setHighlight(() => loaded);
1201
+ });
1202
+ return () => {
1203
+ cancelled = true;
1204
+ };
1205
+ }, []);
1206
+ const tokens = useMemo(() => highlight?.(code, grammar), [
1207
+ highlight,
1208
+ code,
1209
+ grammar
1210
+ ]);
1211
+ if (!tokens) return children;
1212
+ return /* @__PURE__ */ jsx("code", {
1213
+ className,
1214
+ children: tokens.map((token, index) => token.className ? /* @__PURE__ */ jsx("span", {
1215
+ className: token.className,
1216
+ children: token.text
1217
+ }, index) : token.text)
1218
+ });
1219
+ };
1220
+ //#endregion
1221
+ //#region src/blocks/AIKit/highlightLanguages.ts
1222
+ /**
1223
+ * Fence language → the grammar that highlights it.
1224
+ *
1225
+ * - Read via `Object.hasOwn`: ```constructor would otherwise hit `Object.prototype`.
1226
+ */
1227
+ const HIGHLIGHT_GRAMMARS = {
1228
+ sql: "sql",
1229
+ snowflake: "sql",
1230
+ sfsql: "sql",
1231
+ python: "python",
1232
+ py: "python",
1233
+ js: "javascript",
1234
+ javascript: "javascript",
1235
+ jsx: "jsx",
1236
+ ts: "typescript",
1237
+ typescript: "typescript",
1238
+ tsx: "tsx",
1239
+ json: "json",
1240
+ sh: "shell",
1241
+ bash: "shell",
1242
+ shell: "shell",
1243
+ zsh: "shell",
1244
+ yaml: "yaml",
1245
+ yml: "yaml"
1246
+ };
1247
+ /** The grammar for a fence language, or `undefined` when the block stays plain. */
1248
+ const getHighlightGrammar = (language) => {
1249
+ const key = language?.toLowerCase();
1250
+ return key !== void 0 && Object.hasOwn(HIGHLIGHT_GRAMMARS, key) ? HIGHLIGHT_GRAMMARS[key] : void 0;
1251
+ };
1252
+ //#endregion
1253
+ //#region src/blocks/AIKit/markdownOptions.ts
1254
+ const MarkdownOptionsContext = createContext({
1255
+ highlight: false,
1256
+ sortable: false,
1257
+ renderDiagramActions: void 0
1258
+ });
1259
+ //#endregion
1260
+ //#region src/blocks/AIKit/tableSort.ts
1261
+ /** Characters a number cell carries for the reader: grouping, currency, percent, sign, spaces. */
1262
+ const NUMBER_NOISE = /[\s,_%$€£¥+]/g;
1263
+ /** A trailing magnitude: `142k`, `1.2M`, `3B`. */
1264
+ const MAGNITUDE = /^(.*?)([kmb])$/i;
1265
+ const MAGNITUDES = {
1266
+ k: 1e3,
1267
+ m: 1e6,
1268
+ b: 1e9
1269
+ };
1270
+ /** A cell's number, or `null` when it is not one: `1,234`, `−5.1%`, `€142` and `1.2M` all read. */
1271
+ const parseSortNumber = (text) => {
1272
+ const cleaned = text.replace(NUMBER_NOISE, "").replace(/^\u2212/, "-");
1273
+ const [, digits = cleaned, suffix = ""] = MAGNITUDE.exec(cleaned) ?? [];
1274
+ if (digits === "" || /[^\d.eE-]/.test(digits)) return null;
1275
+ const value = Number(digits) * (MAGNITUDES[suffix.toLowerCase()] ?? 1);
1276
+ return Number.isFinite(value) ? value : null;
1277
+ };
1278
+ /** Cells a model writes for "no value"; they sort with the empty ones. */
1279
+ const PLACEHOLDER = /^(?:n\/?a|-+|[\u2013\u2014\u2026?]|none|null)$/i;
1280
+ const isBlank = (key) => key === "" || PLACEHOLDER.test(key);
1281
+ const collator = new Intl.Collator(void 0, {
1282
+ numeric: true,
1283
+ sensitivity: "base"
1284
+ });
1285
+ /**
1286
+ * Row indices in the order one column sorts them.
1287
+ *
1288
+ * - Numeric when every non-empty cell parses as a number; otherwise locale-aware text.
1289
+ * - Empty and placeholder cells (`n/a`, `—`) go last in both directions; ties keep source order.
1290
+ */
1291
+ const sortRowOrder = (cells, direction) => {
1292
+ const keys = cells.map((cell) => cell.trim());
1293
+ const numbers = keys.map(parseSortNumber);
1294
+ const filled = keys.flatMap((key, index) => isBlank(key) ? [] : [index]);
1295
+ const empty = keys.flatMap((key, index) => isBlank(key) ? [index] : []);
1296
+ const numeric = filled.every((index) => numbers[index] !== null);
1297
+ const compare = (a, b) => numeric ? (numbers[a] ?? 0) - (numbers[b] ?? 0) : collator.compare(keys[a] ?? "", keys[b] ?? "");
1298
+ const sorted = filled.toSorted(compare);
1299
+ return [...direction === "ascending" ? sorted : sorted.toReversed(), ...empty];
1300
+ };
1301
+ /** The sort after a header press: a new column sorts ascending, the same one flips. */
1302
+ const nextSort = (current, column) => current?.column === column && current.direction === "ascending" ? {
1303
+ column,
1304
+ direction: "descending"
1305
+ } : {
1306
+ column,
1307
+ direction: "ascending"
1308
+ };
1309
+ const getHastText = (node) => {
1310
+ if (node.type === "text") return node.value;
1311
+ if (node.type === "element") return node.children.map(getHastText).join("");
1312
+ return "";
1313
+ };
1314
+ /** A row's cells, read off its hast node; `[]` without one. */
1315
+ const getRowCells = (row) => row?.children.filter((child) => child.type === "element" && /^t[dh]$/.test(child.tagName)) ?? [];
1316
+ /** The text of a row's cell at `column`; `''` when the row is short. */
1317
+ const getRowCellText = (row, column) => {
1318
+ const cell = getRowCells(row)[column];
1319
+ return cell ? getHastText(cell) : "";
1320
+ };
1321
+ //#endregion
1322
+ //#region src/blocks/AIKit/tableSortContext.ts
1323
+ /** The sort of the table being rendered; `null` outside a sortable table. */
1324
+ const TableSortContext = createContext(null);
1325
+ /** The hast cells of the row being rendered; a cell finds its column by identity. */
1326
+ const RowCellsContext = createContext([]);
1327
+ //#endregion
1328
+ //#region src/blocks/AIKit/TableSortProvider.tsx
1329
+ /** Holds one table's sort; the `Markdown` header, row and body renderers read it. */
1330
+ const TableSortProvider = ({ children }) => {
1331
+ const [sort, setSort] = useState(null);
1332
+ const value = useMemo(() => ({
1333
+ sort,
1334
+ toggle: (column) => setSort((current) => nextSort(current, column))
1335
+ }), [sort]);
1336
+ return /* @__PURE__ */ jsx(TableSortContext, {
1337
+ value,
1338
+ children
1339
+ });
1340
+ };
1341
+ //#endregion
1342
+ //#region src/blocks/AIKit/MarkdownGrid.tsx
1343
+ /** The `table` renderer; a sortable block gives each table its own sort. */
1344
+ const MarkdownGrid = (props) => {
1345
+ const { className, node: _node, ...rest } = props;
1346
+ const { sortable } = use(MarkdownOptionsContext);
1347
+ const grid = /* @__PURE__ */ jsx("div", {
1348
+ className: "tw:not-prose tw:my-4",
1349
+ children: /* @__PURE__ */ jsx(Table$1, {
1350
+ bordered: true,
1351
+ className: cn("tw:!mb-0", className),
1352
+ ...rest
1353
+ })
1354
+ });
1355
+ return sortable ? /* @__PURE__ */ jsx(TableSortProvider, { children: grid }) : grid;
1356
+ };
1357
+ //#endregion
1358
+ //#region src/blocks/AIKit/MarkdownTableBody.tsx
1359
+ /**
1360
+ * The body of an answer's table, in the order its sorted column puts the rows.
1361
+ *
1362
+ * - react-markdown hands rows over as a keyed array, each with its hast `node`.
1363
+ * - Rows keep their keys, so a sort moves them rather than remounting them.
1364
+ */
1365
+ const MarkdownTableBody = (props) => {
1366
+ const { children, node: _node, ...rest } = props;
1367
+ const sort = use(TableSortContext)?.sort ?? null;
1368
+ if (sort === null || !Array.isArray(children)) return /* @__PURE__ */ jsx(Table$1.Body, {
1369
+ ...rest,
1370
+ children
1371
+ });
1372
+ const rows = children.filter(isValidElement);
1373
+ const order = sortRowOrder(rows.map((row) => getRowCellText(row.props.node, sort.column)), sort.direction);
1374
+ return /* @__PURE__ */ jsx(Table$1.Body, {
1375
+ ...rest,
1376
+ children: order.map((index) => rows[index])
1377
+ });
1378
+ };
1379
+ //#endregion
1380
+ //#region src/blocks/AIKit/MarkdownTableHead.tsx
1381
+ /**
1382
+ * A header cell in an answer's table; a sort control inside a sortable table.
1383
+ *
1384
+ * - The button sorts on click, Enter and Space; `aria-sort` sits on the cell.
1385
+ */
1386
+ const MarkdownTableHead = (props) => {
1387
+ const { children, node, ...rest } = props;
1388
+ const sorting = use(TableSortContext);
1389
+ const column = node ? use(RowCellsContext).indexOf(node) : -1;
1390
+ if (sorting === null || column === -1) return /* @__PURE__ */ jsx(Table$1.Head, {
1391
+ ...rest,
1392
+ children
1393
+ });
1394
+ const { sort, toggle } = sorting;
1395
+ const sorted = sort?.column !== column ? false : sort.direction === "ascending" ? "asc" : "desc";
1396
+ return /* @__PURE__ */ jsx(Table$1.Head, {
1397
+ "aria-sort": tableAriaSort(sorted),
1398
+ ...rest,
1399
+ children: /* @__PURE__ */ jsx(Table$1.SortHeader, {
1400
+ sorted,
1401
+ onClick: () => toggle(column),
1402
+ children
1403
+ })
1404
+ });
1405
+ };
1406
+ //#endregion
1407
+ //#region src/blocks/AIKit/MarkdownTableRow.tsx
1408
+ /** A row in an answer's table; inside a sortable table its cells can find their column. */
1409
+ const MarkdownTableRow = (props) => {
1410
+ const { node, ...rest } = props;
1411
+ const sorting = use(TableSortContext);
1412
+ const row = /* @__PURE__ */ jsx(Table$1.Row, { ...rest });
1413
+ return sorting === null ? row : /* @__PURE__ */ jsx(RowCellsContext, {
1414
+ value: getRowCells(node),
1415
+ children: row
1416
+ });
1417
+ };
1418
+ //#endregion
1419
+ //#region src/blocks/AIKit/svgFence.ts
1420
+ /** Fence languages whose body may be a single SVG document. */
1421
+ const SVG_FENCE_LANGUAGES = [
1422
+ "svg",
1423
+ "xml",
1424
+ "html"
1425
+ ];
1426
+ /** A root start tag: `<svg` then whitespace, `/` or `>` — not `<svg-chart`. */
1427
+ const SVG_ROOT = /^<svg[\s/>]/i;
1428
+ /** An end tag for `svg` as the HTML parser sees one. */
1429
+ const SVG_END_TAG = /<\/svg[\s/>]/iy;
1430
+ /** The only spelling that closes a chart: `</svg>`, optional whitespace before `>`. */
1431
+ const SVG_CLOSE = /<\/svg\s*>/iy;
1432
+ const COMMENT_END = /--!?>/g;
1433
+ /**
1434
+ * Where the HTML parser ends the comment, CDATA or other `<!…>` / `<?…>` markup opening at `lt`.
1435
+ *
1436
+ * - The index just past it, `-1` while it is still open, `null` when none opens there.
1437
+ */
1438
+ const skipMarkup = (text, lt) => {
1439
+ if (text.startsWith("<!--", lt)) {
1440
+ const body = lt + 4;
1441
+ if (text.startsWith(">", body)) return body + 1;
1442
+ if (text.startsWith("->", body)) return body + 2;
1443
+ COMMENT_END.lastIndex = body;
1444
+ const end = COMMENT_END.exec(text);
1445
+ return end ? end.index + end[0].length : -1;
1446
+ }
1447
+ if (text.startsWith("<![CDATA[", lt)) {
1448
+ const end = text.indexOf("]]>", lt + 9);
1449
+ return end < 0 ? -1 : end + 3;
1450
+ }
1451
+ if (text.startsWith("<!", lt) || text.startsWith("<?", lt)) {
1452
+ const end = text.indexOf(">", lt + 2);
1453
+ return end < 0 ? -1 : end + 1;
1454
+ }
1455
+ return null;
1456
+ };
1457
+ /**
1458
+ * Index just past the first `</svg>` at or after `from` outside skipped markup, or `-1`.
1459
+ *
1460
+ * - Any other spelling of that first end tag (`</svg/>`, junk attributes) also gives `-1`.
1461
+ * - One pass over the `<`s, never a backtracking regex: it runs on every streamed chunk.
1462
+ */
1463
+ const svgEndTagEnd = (text, from) => {
1464
+ let at = from;
1465
+ for (;;) {
1466
+ const lt = text.indexOf("<", at);
1467
+ if (lt < 0) return -1;
1468
+ const skipped = skipMarkup(text, lt);
1469
+ if (skipped === -1) return -1;
1470
+ if (skipped !== null) {
1471
+ at = skipped;
1472
+ continue;
1473
+ }
1474
+ SVG_END_TAG.lastIndex = lt;
1475
+ if (SVG_END_TAG.test(text)) {
1476
+ SVG_CLOSE.lastIndex = lt;
1477
+ return SVG_CLOSE.test(text) ? SVG_CLOSE.lastIndex : -1;
1478
+ }
1479
+ at = lt + 1;
1480
+ }
1481
+ };
1482
+ /**
1483
+ * The SVG document a fenced block holds, or `null` when it holds anything else.
1484
+ *
1485
+ * - Only `svg`, `xml` and `html` fences; an XML prolog, doctype or comments may lead.
1486
+ * - Exactly one document: a second one, prose around it or one still streaming stays code.
1487
+ * - The scan mirrors the HTML parser; whatever it accepts is still sanitized.
1488
+ */
1489
+ const svgFenceDocument = (language, body) => {
1490
+ if (language === void 0 || !SVG_FENCE_LANGUAGES.includes(language.toLowerCase())) return null;
1491
+ const text = body.trim();
1492
+ let at = 0;
1493
+ for (;;) {
1494
+ while (at < text.length && /\s/.test(text.charAt(at))) at++;
1495
+ const skipped = skipMarkup(text, at);
1496
+ if (skipped === null) break;
1497
+ if (skipped === -1) return null;
1498
+ at = skipped;
1499
+ }
1500
+ if (!SVG_ROOT.test(text.slice(at, at + 5))) return null;
1501
+ return svgEndTagEnd(text, at) === text.length ? text.slice(at) : null;
1502
+ };
1503
+ //#endregion
1504
+ //#region src/blocks/AIKit/scopeSvgReferences.ts
1505
+ /** More cloned nodes than any honest chart needs; matplotlib's glyphs stay well under it. */
1506
+ const MAX_CLONED_NODES = 2e4;
1507
+ const LINK_ATTRIBUTES = ["href", "xlink:href"];
1508
+ /**
1509
+ * The only id shape a reference may name.
1510
+ *
1511
+ * - No `%`, `\\` or quotes: the browser decodes those where this check would not.
1512
+ */
1513
+ const STRICT_ID = /^[A-Za-z_][\w.-]*$/;
1514
+ /** A `url(#id)` paint, clip, mask, filter or marker reference, in its one accepted spelling. */
1515
+ const URL_REFERENCE = /url\(\s*(['"]?)#([A-Za-z_][\w.-]*)\1\s*\)/gi;
1516
+ /** Anything the browser could read as a `url(` or a CSS escape. */
1517
+ const ANY_URL = /url\s*\(|\\/gi;
1518
+ /**
1519
+ * The ids an attribute value points at; `null` when it cannot be read safely.
1520
+ *
1521
+ * - Fails closed: a `url(` or escape the strict spelling does not account for is unreadable.
1522
+ */
1523
+ const referencesIn = (name, value) => {
1524
+ if (LINK_ATTRIBUTES.includes(name)) {
1525
+ const id = value.trim().replace(/^#/, "");
1526
+ return value.trim().startsWith("#") && STRICT_ID.test(id) ? [id] : null;
1527
+ }
1528
+ const strict = [...value.matchAll(URL_REFERENCE)].map((match) => match[2] ?? "");
1529
+ return (value.match(ANY_URL)?.length ?? 0) === strict.length ? strict : null;
1530
+ };
1531
+ /** The ids an element's own attributes point at, by attribute name; `null` marks an unreadable one. */
1532
+ const ownReferences = (element) => {
1533
+ const references = /* @__PURE__ */ new Map();
1534
+ for (const { name, value } of element.attributes) {
1535
+ const ids = referencesIn(name, value);
1536
+ if (ids === null || ids.length > 0) references.set(name, ids);
1537
+ }
1538
+ return references;
1539
+ };
1540
+ /** Drops a reference that failed the check: a `<use>` goes whole, anything else loses the attribute. */
1541
+ const dropReference = (element, attribute) => {
1542
+ if (element.localName === "use" && LINK_ATTRIBUTES.includes(attribute)) element.remove();
1543
+ else element.removeAttribute(attribute);
1544
+ };
1545
+ let fenceCount = 0;
1546
+ const nextPrefix = () => {
1547
+ fenceCount += 1;
1548
+ return `svgf${fenceCount.toString(36)}${Math.random().toString(36).slice(2, 8)}-`;
1549
+ };
1550
+ /**
1551
+ * Confines a sanitized fence's internal references to the fence itself, at linear cost.
1552
+ *
1553
+ * 1. An id outside `STRICT_ID` is removed; a repeated one keeps its first element, as the browser does.
1554
+ * 2. A reference survives only if readable and aimed at an in-fence, reference-free subtree.
1555
+ * 3. Each `<use>` costs its target's node count; past `MAX_CLONED_NODES` the rest go.
1556
+ * 4. Every id and reference gets a per-fence prefix, so nothing resolves into the host page.
1557
+ */
1558
+ const scopeSvgReferences = (root) => {
1559
+ const elements = [...root.querySelectorAll("*")];
1560
+ const byId = /* @__PURE__ */ new Map();
1561
+ for (const element of elements) {
1562
+ if (!element.hasAttribute("id")) continue;
1563
+ if (!STRICT_ID.test(element.id) || byId.has(element.id)) element.removeAttribute("id");
1564
+ else byId.set(element.id, element);
1565
+ }
1566
+ const references = new Map(elements.map((element) => [element, ownReferences(element)]));
1567
+ const referencing = /* @__PURE__ */ new Set();
1568
+ for (const [element, refs] of references) {
1569
+ if (refs.size === 0) continue;
1570
+ for (let node = element; node; node = node.parentElement) referencing.add(node);
1571
+ }
1572
+ for (const [element, refs] of references) for (const [attribute, ids] of refs) if (ids === null || ids.some((id) => {
1573
+ const target = byId.get(id);
1574
+ return !target || referencing.has(target);
1575
+ })) dropReference(element, attribute);
1576
+ const targetSize = /* @__PURE__ */ new Map();
1577
+ let cloned = 0;
1578
+ for (const use of root.querySelectorAll("use")) {
1579
+ const href = use.getAttribute("href") ?? use.getAttribute("xlink:href") ?? "";
1580
+ const target = byId.get(href.trim().slice(1));
1581
+ if (!target) continue;
1582
+ let size = targetSize.get(target);
1583
+ if (size === void 0) {
1584
+ size = target.getElementsByTagName("*").length + 1;
1585
+ targetSize.set(target, size);
1586
+ }
1587
+ cloned += size;
1588
+ if (cloned > MAX_CLONED_NODES) use.remove();
1589
+ }
1590
+ const prefix = nextPrefix();
1591
+ for (const element of root.querySelectorAll("*")) {
1592
+ if (element.id) element.id = `${prefix}${element.id}`;
1593
+ for (const { name, value } of [...element.attributes]) {
1594
+ const scoped = LINK_ATTRIBUTES.includes(name) ? value.trim().replace(/^#/, `#${prefix}`) : value.replace(URL_REFERENCE, (_match, quote, id) => {
1595
+ return `url(${quote}#${prefix}${id}${quote})`;
1596
+ });
1597
+ if (scoped !== value) element.setAttribute(name, scoped);
1598
+ }
1599
+ }
1600
+ };
1601
+ //#endregion
1602
+ //#region src/blocks/AIKit/filterSvgStyle.ts
1603
+ /** The CSS properties a fenced chart may set inline: nothing that loads a URL or leaves the box. */
1604
+ const SAFE_PROPERTIES = /* @__PURE__ */ new Set([
1605
+ "alignment-baseline",
1606
+ "baseline-shift",
1607
+ "clip-path",
1608
+ "clip-rule",
1609
+ "color",
1610
+ "display",
1611
+ "dominant-baseline",
1612
+ "fill",
1613
+ "fill-opacity",
1614
+ "fill-rule",
1615
+ "filter",
1616
+ "flood-color",
1617
+ "flood-opacity",
1618
+ "font",
1619
+ "font-family",
1620
+ "font-size",
1621
+ "font-style",
1622
+ "font-weight",
1623
+ "letter-spacing",
1624
+ "lighting-color",
1625
+ "marker",
1626
+ "marker-end",
1627
+ "marker-mid",
1628
+ "marker-start",
1629
+ "mask",
1630
+ "opacity",
1631
+ "paint-order",
1632
+ "shape-rendering",
1633
+ "stop-color",
1634
+ "stop-opacity",
1635
+ "stroke",
1636
+ "stroke-dasharray",
1637
+ "stroke-dashoffset",
1638
+ "stroke-linecap",
1639
+ "stroke-linejoin",
1640
+ "stroke-miterlimit",
1641
+ "stroke-opacity",
1642
+ "stroke-width",
1643
+ "text-anchor",
1644
+ "text-decoration",
1645
+ "text-rendering",
1646
+ "vector-effect",
1647
+ "visibility",
1648
+ "word-spacing"
1649
+ ]);
1650
+ /**
1651
+ * The only functions a value may call: colours, and a same-document reference.
1652
+ *
1653
+ * - Colour arguments take words, numbers and `/`, never a nested `(`.
1654
+ */
1655
+ const SAFE_FUNCTION = new RegExp(String.raw`(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch)\([\w\s.,%+/-]*\)|${URL_REFERENCE.source}`, "gi");
1656
+ /** What is left of a value once its safe functions are gone: words, numbers, colours, quotes. */
1657
+ const SAFE_REMAINDER = /^[\w\s#.,%'"+/-]*$/;
1658
+ const isSafeDeclaration = (property, value) => SAFE_PROPERTIES.has(property) && SAFE_REMAINDER.test(value.replace(SAFE_FUNCTION, ""));
1659
+ /**
1660
+ * A `style` attribute reduced to the declarations a static chart needs.
1661
+ *
1662
+ * - Allowlist, not denylist: `image-set()`, escapes and positioning never pass.
1663
+ * - A declaration goes whole; `''` when none survive.
1664
+ */
1665
+ const filterSvgStyle = (style) => style.split(";").flatMap((declaration) => {
1666
+ const colon = declaration.indexOf(":");
1667
+ if (colon < 0) return [];
1668
+ const property = declaration.slice(0, colon).trim().toLowerCase();
1669
+ const value = declaration.slice(colon + 1).trim();
1670
+ return isSafeDeclaration(property, value) ? [`${property}: ${value}`] : [];
1671
+ }).join("; ");
1672
+ //#endregion
1673
+ //#region src/blocks/AIKit/sanitizeSvgFence.ts
1674
+ /**
1675
+ * Elements a fenced chart never keeps.
1676
+ *
1677
+ * - SMIL (`animate*`, `set`) sets attributes at runtime, past every attribute check.
1678
+ * - `foreignObject` carries HTML; a `style` element would restyle the whole page.
1679
+ */
1680
+ const FORBIDDEN_TAGS = [
1681
+ "script",
1682
+ "foreignObject",
1683
+ "animate",
1684
+ "animateMotion",
1685
+ "animateTransform",
1686
+ "set",
1687
+ "style"
1688
+ ];
1689
+ const URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "xlink:href"]);
1690
+ let purifier = null;
1691
+ /** A DOMPurify instance of its own, so these hooks never reach `ZoomableSvg` or other callers. */
1692
+ const getPurifier = () => {
1693
+ if (purifier) return purifier;
1694
+ const instance = DOMPurify(window);
1695
+ instance.addHook("uponSanitizeAttribute", (_node, data) => {
1696
+ const name = data.attrName.toLowerCase();
1697
+ if (URL_ATTRIBUTES.has(name) && !data.attrValue.trim().startsWith("#")) data.keepAttr = false;
1698
+ if (name === "style") {
1699
+ data.attrValue = filterSvgStyle(data.attrValue);
1700
+ if (!data.attrValue) data.keepAttr = false;
1701
+ }
1702
+ });
1703
+ purifier = instance;
1704
+ return instance;
1705
+ };
1706
+ /**
1707
+ * A model-written SVG document reduced to static, self-contained drawing.
1708
+ *
1709
+ * - SVG profile only: scripts, event handlers and HTML go.
1710
+ * - `href`s survive only as same-document `#` references, so nothing loads from elsewhere.
1711
+ * - `use` is allowed back in for that reason (matplotlib draws every glyph with it).
1712
+ * - References are then confined to the fence; see `scopeSvgReferences`.
1713
+ */
1714
+ const sanitizeSvgFence = (svg) => {
1715
+ const template = document.createElement("template");
1716
+ template.innerHTML = getPurifier().sanitize(svg, {
1717
+ USE_PROFILES: {
1718
+ svg: true,
1719
+ svgFilters: true
1720
+ },
1721
+ ADD_TAGS: ["use"],
1722
+ FORBID_TAGS: FORBIDDEN_TAGS
1723
+ });
1724
+ scopeSvgReferences(template.content);
1725
+ return template.innerHTML;
1726
+ };
1727
+ //#endregion
1728
+ //#region src/blocks/AIKit/SvgFencesContext.ts
1729
+ /** Whether the `Markdown.Block` being rendered draws ```svg fences; off by default. */
1730
+ const SvgFencesContext = createContext(false);
1731
+ //#endregion
1732
+ //#region src/blocks/AIKit/SvgFenceChart.tsx
1733
+ /** A fenced SVG chart, sanitized and drawn responsive, in a block that opted in. */
1734
+ const SvgFenceChart = ({ svg, source, children }) => {
1735
+ const allowed = use(SvgFencesContext);
1736
+ const html = useMemo(() => allowed ? makeResponsive(sanitizeSvgFence(svg)) : "", [allowed, svg]);
1737
+ if (!allowed || !html) return children;
1738
+ return /* @__PURE__ */ jsx(ZoomableSvg, {
1739
+ html,
1740
+ copyText: source,
1741
+ className: "tw:my-3",
1742
+ allowUse: true
1743
+ });
1744
+ };
1745
+ /**
1746
+ * The fence languages `Markdown` draws instead of printing.
1747
+ *
1748
+ * - Streaming callers hold an open fence back as plain code, or every keystroke fetches the chunk.
1749
+ * - Frozen: it is shared across every app in the bundle.
1750
+ */
1751
+ const DIAGRAM_LANGUAGES = Object.freeze([...[
1752
+ "mermaid",
1753
+ "flint",
1754
+ "flint-chart"
1755
+ ]]);
1756
+ //#endregion
529
1757
  //#region src/blocks/AIKit/Markdown.tsx
530
1758
  const loadMermaid = diagramChunk("MermaidDiagram", () => import("@keboola/design/mermaid").then((module) => module.MermaidDiagram));
531
1759
  const loadFlint = diagramChunk("FlintChart", () => import("@keboola/design/flint").then((module) => module.FlintChart));
@@ -562,13 +1790,6 @@ const DIAGRAM_CHUNKS = {
562
1790
  "flint-chart": loadFlint
563
1791
  };
564
1792
  /**
565
- * The fence languages this renderer draws instead of printing.
566
- *
567
- * - Streaming callers hold an open fence back as plain code, or every keystroke fetches the chunk.
568
- * - Frozen: it is shared across every app in the bundle.
569
- */
570
- const DIAGRAM_LANGUAGES = Object.freeze(Object.keys(DIAGRAM_CHUNKS));
571
- /**
572
1793
  * Whether the `code` being rendered sits inside a fenced block.
573
1794
  *
574
1795
  * - Set by `pre`: a fence without info string has no `language-…` class, like inline code.
@@ -590,39 +1811,61 @@ const MarkdownCode = ({ children, className, ...props }) => {
590
1811
  children
591
1812
  });
592
1813
  };
593
- const baseComponents = {
594
- pre({ children, ...props }) {
595
- const child = Array.isArray(children) ? children[0] : children;
596
- const code = getText(child).replace(/\n$/, "");
597
- const language = getLanguage(child);
598
- const load = language !== void 0 && Object.hasOwn(DIAGRAM_CHUNKS, language) ? DIAGRAM_CHUNKS[language] : void 0;
599
- if (load) {
600
- if (!code.trim()) return null;
601
- return /* @__PURE__ */ jsx(DiagramBlock, {
602
- load,
603
- code
604
- });
605
- }
606
- const { node: _node, ...rest } = props;
607
- return /* @__PURE__ */ jsxs("div", {
608
- className: "tw:group tw:relative",
609
- children: [/* @__PURE__ */ jsx("span", {
610
- className: "tw:absolute tw:right-2 tw:top-2 tw:opacity-0 tw:transition-opacity tw:group-focus-within:opacity-100 tw:group-hover:opacity-100",
611
- children: /* @__PURE__ */ jsx(ClipboardButton, {
612
- text: code,
613
- tooltip: "Copy",
614
- size: "icon-xs",
615
- variant: "outline"
616
- })
617
- }), /* @__PURE__ */ jsx("pre", {
618
- ...rest,
619
- children: /* @__PURE__ */ jsx(CodeBlockContext, {
620
- value: true,
1814
+ /**
1815
+ * The `pre` renderer: a diagram fence draws, any other fence prints with a copy button.
1816
+ *
1817
+ * - Diagrams replace the whole `<pre>`; a `<div>` inside `<pre><code>` is invalid HTML.
1818
+ * - A `highlight` block colours a fence whose language has a grammar, once the chunk loads.
1819
+ */
1820
+ const MarkdownPre = (props) => {
1821
+ const { children, node: _node, ...rest } = props;
1822
+ const { highlight, renderDiagramActions } = use(MarkdownOptionsContext);
1823
+ const child = Array.isArray(children) ? children[0] : children;
1824
+ const code = getText(child).replace(/\n$/, "");
1825
+ const language = getLanguage(child);
1826
+ const load = language !== void 0 && Object.hasOwn(DIAGRAM_CHUNKS, language) ? DIAGRAM_CHUNKS[language] : void 0;
1827
+ if (load) {
1828
+ if (!code.trim()) return null;
1829
+ return /* @__PURE__ */ jsx(DiagramBlock, {
1830
+ load,
1831
+ code,
1832
+ renderActions: renderDiagramActions
1833
+ });
1834
+ }
1835
+ const grammar = highlight ? getHighlightGrammar(language) : void 0;
1836
+ const block = /* @__PURE__ */ jsxs("div", {
1837
+ className: "tw:group tw:relative",
1838
+ children: [/* @__PURE__ */ jsx("span", {
1839
+ className: "tw:absolute tw:right-2 tw:top-2 tw:opacity-0 tw:transition-opacity tw:group-focus-within:opacity-100 tw:group-hover:opacity-100",
1840
+ children: /* @__PURE__ */ jsx(ClipboardButton, {
1841
+ text: code,
1842
+ tooltip: "Copy",
1843
+ size: "icon-xs",
1844
+ variant: "outline"
1845
+ })
1846
+ }), /* @__PURE__ */ jsx("pre", {
1847
+ ...rest,
1848
+ children: /* @__PURE__ */ jsx(CodeBlockContext, {
1849
+ value: true,
1850
+ children: grammar === void 0 ? children : /* @__PURE__ */ jsx(HighlightedCode, {
1851
+ code,
1852
+ grammar,
1853
+ className: `language-${language}`,
621
1854
  children
622
1855
  })
623
- })]
624
- });
625
- },
1856
+ })
1857
+ })]
1858
+ });
1859
+ const svg = svgFenceDocument(language, code);
1860
+ if (svg === null) return block;
1861
+ return /* @__PURE__ */ jsx(SvgFenceChart, {
1862
+ svg,
1863
+ source: `\`\`\`${language}\n${code}\n\`\`\``,
1864
+ children: block
1865
+ });
1866
+ };
1867
+ const baseComponents = {
1868
+ pre: MarkdownPre,
626
1869
  code: MarkdownCode,
627
1870
  a({ children, href, ...props }) {
628
1871
  const { node: _node, ...rest } = props;
@@ -637,33 +1880,14 @@ const baseComponents = {
637
1880
  children
638
1881
  });
639
1882
  },
640
- table({ className, ...props }) {
641
- const { node: _node, ...rest } = props;
642
- return /* @__PURE__ */ jsx("div", {
643
- className: "tw:not-prose tw:my-4",
644
- children: /* @__PURE__ */ jsx(Table$1, {
645
- bordered: true,
646
- className: cn("tw:!mb-0", className),
647
- ...rest
648
- })
649
- });
650
- },
1883
+ table: MarkdownGrid,
651
1884
  thead(props) {
652
1885
  const { node: _node, ...rest } = props;
653
1886
  return /* @__PURE__ */ jsx(Table$1.Header, { ...rest });
654
1887
  },
655
- tbody(props) {
656
- const { node: _node, ...rest } = props;
657
- return /* @__PURE__ */ jsx(Table$1.Body, { ...rest });
658
- },
659
- tr(props) {
660
- const { node: _node, ...rest } = props;
661
- return /* @__PURE__ */ jsx(Table$1.Row, { ...rest });
662
- },
663
- th(props) {
664
- const { node: _node, ...rest } = props;
665
- return /* @__PURE__ */ jsx(Table$1.Head, { ...rest });
666
- },
1888
+ tbody: MarkdownTableBody,
1889
+ tr: MarkdownTableRow,
1890
+ th: MarkdownTableHead,
667
1891
  td(props) {
668
1892
  const { node: _node, ...rest } = props;
669
1893
  return /* @__PURE__ */ jsx(Table$1.Cell, { ...rest });
@@ -681,15 +1905,38 @@ const MarkdownRoot = ({ children, className, rhythm = "answer" }) => /* @__PURE_
681
1905
  className: cn(PROSE_CLASS[rhythm], className),
682
1906
  children
683
1907
  });
1908
+ /** The diagram toolbar a block asks for: the caller's actions, then the built-in download. */
1909
+ const composeDiagramActions = (diagramActions, diagramDownload) => {
1910
+ if (!diagramDownload) return diagramActions;
1911
+ return (safeSvg) => /* @__PURE__ */ jsxs(Fragment$1, { children: [diagramActions?.(safeSvg), /* @__PURE__ */ jsx(DiagramDownloadButton, { safeSvg })] });
1912
+ };
684
1913
  /** One parsed piece of an answer, with no surface of its own; memoise at the call site. */
685
- const MarkdownBlock = ({ children, remarkPlugins, components: componentOverrides }) => /* @__PURE__ */ jsx(ReactMarkdown, {
686
- remarkPlugins: remarkPlugins === void 0 ? baseRemarkPlugins : [...baseRemarkPlugins, ...remarkPlugins],
687
- components: componentOverrides === void 0 ? baseComponents : {
688
- ...baseComponents,
689
- ...componentOverrides
690
- },
691
- children
692
- });
1914
+ const MarkdownBlock = ({ children, remarkPlugins, components: componentOverrides, highlight = false, sortable = false, diagramActions, diagramDownload = false, allowSvgFences = false }) => {
1915
+ const options = useMemo(() => ({
1916
+ highlight,
1917
+ sortable,
1918
+ renderDiagramActions: composeDiagramActions(diagramActions, diagramDownload)
1919
+ }), [
1920
+ highlight,
1921
+ sortable,
1922
+ diagramActions,
1923
+ diagramDownload
1924
+ ]);
1925
+ return /* @__PURE__ */ jsx(MarkdownOptionsContext, {
1926
+ value: options,
1927
+ children: /* @__PURE__ */ jsx(SvgFencesContext, {
1928
+ value: allowSvgFences,
1929
+ children: /* @__PURE__ */ jsx(ReactMarkdown, {
1930
+ remarkPlugins: remarkPlugins === void 0 ? baseRemarkPlugins : [...baseRemarkPlugins, ...remarkPlugins],
1931
+ components: componentOverrides === void 0 ? baseComponents : {
1932
+ ...baseComponents,
1933
+ ...componentOverrides
1934
+ },
1935
+ children
1936
+ })
1937
+ })
1938
+ });
1939
+ };
693
1940
  /**
694
1941
  * Read-only GFM Markdown renderer with design-token typography.
695
1942
  *
@@ -725,7 +1972,7 @@ const FADE_SURFACE = {
725
1972
  * `baseline-widely-available` build floor lacks, and must sit on `Table`'s own container.
726
1973
  * - Past `EXPAND_RATIO` the expand action shows without hover.
727
1974
  */
728
- const MarkdownTable = ({ children, actions, onExpand, surface = "card", className }) => {
1975
+ const MarkdownTable = ({ children, actions, onExpand, surface = "card", sortable = false, className }) => {
729
1976
  const frameRef = useRef(null);
730
1977
  const [overflow, setOverflow] = useState({
731
1978
  scrollable: false,
@@ -775,15 +2022,104 @@ const MarkdownTable = ({ children, actions, onExpand, surface = "card", classNam
775
2022
  }), /* @__PURE__ */ jsxs("div", {
776
2023
  ref: frameRef,
777
2024
  className: "tw:relative",
778
- children: [children, overflow.scrollable && !overflow.atEnd && /* @__PURE__ */ jsx("span", {
2025
+ children: [sortable ? /* @__PURE__ */ jsx(TableSortProvider, { children }) : children, overflow.scrollable && !overflow.atEnd && /* @__PURE__ */ jsx("span", {
2026
+ "aria-hidden": true,
2027
+ "data-testid": "markdown-table-fade",
2028
+ className: cn("tw:pointer-events-none tw:absolute tw:inset-y-px tw:right-px tw:w-12 tw:rounded-r-[10px] tw:bg-gradient-to-l tw:to-transparent", FADE_SURFACE[surface])
2029
+ })]
2030
+ })]
2031
+ });
2032
+ };
2033
+ //#endregion
2034
+ //#region src/blocks/AIKit/CollapsedMessageBody.tsx
2035
+ /**
2036
+ * Clips tall content at `maxHeight` behind a fade, with a Show more / Show less toggle.
2037
+ *
2038
+ * - The toggle and fade appear only while the content is taller than `maxHeight`.
2039
+ * - Height is measured with a `ResizeObserver`; without one (jsdom) nothing is clipped.
2040
+ */
2041
+ const CollapsedMessageBody = ({ maxHeight, fadeClassName, children }) => {
2042
+ const contentRef = useRef(null);
2043
+ const [isOverflowing, setIsOverflowing] = useState(false);
2044
+ const [isExpanded, setIsExpanded] = useState(false);
2045
+ useLayoutEffect(() => {
2046
+ const node = contentRef.current;
2047
+ if (!node || typeof ResizeObserver === "undefined") return;
2048
+ const observer = new ResizeObserver(() => setIsOverflowing(node.offsetHeight > maxHeight));
2049
+ observer.observe(node);
2050
+ return () => observer.disconnect();
2051
+ }, [maxHeight]);
2052
+ const isClipped = isOverflowing && !isExpanded;
2053
+ return /* @__PURE__ */ jsxs("div", {
2054
+ className: "tw:flex tw:flex-col tw:items-start tw:gap-1",
2055
+ children: [/* @__PURE__ */ jsxs("div", {
2056
+ className: "tw:relative tw:w-full tw:overflow-hidden",
2057
+ style: isClipped ? { maxHeight } : void 0,
2058
+ children: [/* @__PURE__ */ jsx("div", {
2059
+ ref: contentRef,
2060
+ children
2061
+ }), isClipped && /* @__PURE__ */ jsx("div", {
779
2062
  "aria-hidden": true,
780
- "data-testid": "markdown-table-fade",
781
- className: cn("tw:pointer-events-none tw:absolute tw:inset-y-px tw:right-px tw:w-12 tw:rounded-r-[10px] tw:bg-gradient-to-l tw:to-transparent", FADE_SURFACE[surface])
2063
+ className: cn("tw:pointer-events-none tw:absolute tw:inset-x-0 tw:bottom-0 tw:h-12 tw:bg-gradient-to-t tw:to-transparent", fadeClassName)
782
2064
  })]
2065
+ }), isOverflowing && /* @__PURE__ */ jsx(Button, {
2066
+ variant: "inline",
2067
+ size: "small",
2068
+ className: "tw:px-0",
2069
+ "aria-expanded": isExpanded,
2070
+ onClick: () => setIsExpanded((v) => !v),
2071
+ children: isExpanded ? "Show less" : "Show more"
2072
+ })]
2073
+ });
2074
+ };
2075
+ //#endregion
2076
+ //#region src/blocks/AIKit/MessageDeliveryStatus.tsx
2077
+ /**
2078
+ * Where a sent message stands, until it is delivered.
2079
+ *
2080
+ * - `sending` is announced politely; `failed` is an alert with a Retry button when `onRetry` is set.
2081
+ */
2082
+ const MessageDeliveryStatus = ({ status, onRetry }) => {
2083
+ if (status === "sending") return /* @__PURE__ */ jsx("span", {
2084
+ role: "status",
2085
+ className: "tw:text-xs tw:text-muted-foreground",
2086
+ children: "Sending…"
2087
+ });
2088
+ return /* @__PURE__ */ jsxs("span", {
2089
+ role: "alert",
2090
+ className: "tw:inline-flex tw:items-center tw:gap-2 tw:text-xs tw:text-destructive",
2091
+ children: ["Failed to send", onRetry && /* @__PURE__ */ jsx(Button, {
2092
+ variant: "inline",
2093
+ size: "small",
2094
+ className: "tw:px-0",
2095
+ onClick: onRetry,
2096
+ children: "Retry"
783
2097
  })]
784
2098
  });
785
2099
  };
786
2100
  //#endregion
2101
+ //#region src/blocks/AIKit/MessageTimestamp.tsx
2102
+ const TIME_FORMAT = {
2103
+ hour: "2-digit",
2104
+ minute: "2-digit"
2105
+ };
2106
+ /**
2107
+ * The time a message was sent.
2108
+ *
2109
+ * - Shows the clock time, with the full locale date and time as the tooltip.
2110
+ * - Draws nothing for a value that is not a date.
2111
+ */
2112
+ const MessageTimestamp = ({ createdAt }) => {
2113
+ const date = new Date(createdAt);
2114
+ if (Number.isNaN(date.getTime())) return null;
2115
+ return /* @__PURE__ */ jsx("time", {
2116
+ dateTime: date.toISOString(),
2117
+ title: date.toLocaleString(),
2118
+ className: "tw:text-xs tw:text-muted-foreground",
2119
+ children: date.toLocaleTimeString([], TIME_FORMAT)
2120
+ });
2121
+ };
2122
+ //#endregion
787
2123
  //#region src/blocks/AIKit/MessageBubble.tsx
788
2124
  const containerClasses = cva([
789
2125
  "tw:flex",
@@ -838,49 +2174,104 @@ const contentClasses = cva([
838
2174
  } },
839
2175
  defaultVariants: { variant: "assistant" }
840
2176
  });
841
- const MessageBubble = ({ variant, children, actions, createdAt }) => {
842
- return /* @__PURE__ */ jsx("div", {
2177
+ const MessageBubble = ({ variant, children, actions, createdAt, timestamp = "tooltip", quote, status, onRetry, author, collapseAfterPx, avatar }) => {
2178
+ const isTimestampVisible = timestamp === "visible" && createdAt !== void 0;
2179
+ const isSending = variant === "user" && status === "sending";
2180
+ const deliveryStatus = variant === "user" && status ? /* @__PURE__ */ jsx(MessageDeliveryStatus, {
2181
+ status,
2182
+ onRetry
2183
+ }) : null;
2184
+ const body = collapseAfterPx === void 0 ? children : /* @__PURE__ */ jsx(CollapsedMessageBody, {
2185
+ maxHeight: collapseAfterPx,
2186
+ fadeClassName: variant === "user" ? "tw:from-muted" : "tw:from-background",
2187
+ children
2188
+ });
2189
+ const hasActionsRow = isTimestampVisible || deliveryStatus !== null;
2190
+ return /* @__PURE__ */ jsxs("div", {
843
2191
  className: containerClasses({ variant }),
844
2192
  "data-testid": `chat-message-${variant}`,
845
- children: /* @__PURE__ */ jsxs("div", {
846
- className: stackClasses({ variant }),
847
- children: [/* @__PURE__ */ jsx("div", {
848
- className: contentClasses({ variant }),
849
- title: variant === "user" ? createdAt : void 0,
850
- children
851
- }), actions]
2193
+ children: [
2194
+ variant === "assistant" && avatar,
2195
+ /* @__PURE__ */ jsxs("div", {
2196
+ className: stackClasses({ variant }),
2197
+ children: [
2198
+ author && /* @__PURE__ */ jsx("div", {
2199
+ className: "tw:text-xs tw:font-medium tw:text-muted-foreground",
2200
+ children: author
2201
+ }),
2202
+ /* @__PURE__ */ jsxs("div", {
2203
+ className: cn(contentClasses({ variant }), isSending && "tw:opacity-70"),
2204
+ title: variant === "user" && timestamp === "tooltip" ? createdAt : void 0,
2205
+ children: [quote && /* @__PURE__ */ jsx("blockquote", {
2206
+ className: "tw:m-0 tw:border-l-2 tw:border-border tw:pl-2 tw:text-sm tw:text-muted-foreground",
2207
+ children: quote
2208
+ }), body]
2209
+ }),
2210
+ hasActionsRow ? /* @__PURE__ */ jsxs("div", {
2211
+ className: "tw:flex tw:items-center tw:gap-2",
2212
+ children: [
2213
+ isTimestampVisible && /* @__PURE__ */ jsx(MessageTimestamp, { createdAt }),
2214
+ deliveryStatus,
2215
+ actions
2216
+ ]
2217
+ }) : actions
2218
+ ]
2219
+ }),
2220
+ variant === "user" && avatar
2221
+ ]
2222
+ });
2223
+ };
2224
+ //#endregion
2225
+ //#region src/blocks/AIKit/ModeChip.tsx
2226
+ /**
2227
+ * A removable pill naming a mode the composer is in.
2228
+ *
2229
+ * - The whole chip turns the mode off, not just the ×.
2230
+ * - Collapsed via a `0fr` / `1fr` grid column and taken out of the tab order while hidden.
2231
+ */
2232
+ const ModeChip = ({ children, visible = true, tooltip, removeLabel, color = "amber", onRemove, className }) => {
2233
+ const button = /* @__PURE__ */ jsx("button", {
2234
+ type: "button",
2235
+ onClick: onRemove,
2236
+ tabIndex: visible ? void 0 : -1,
2237
+ "aria-label": removeLabel,
2238
+ className: "tailwindcss-preflight tw:cursor-pointer tw:border-0 tw:bg-transparent tw:p-0",
2239
+ children: /* @__PURE__ */ jsx(Badge, {
2240
+ variant: "pill",
2241
+ color,
2242
+ suffix: /* @__PURE__ */ jsx(X, { className: "tw:size-3" }),
2243
+ children
2244
+ })
2245
+ });
2246
+ return /* @__PURE__ */ jsx("div", {
2247
+ "aria-hidden": !visible,
2248
+ className: cn("tw:grid tw:transition-all tw:duration-200 tw:ease-out tw:motion-reduce:transition-none", visible ? "tw:mr-1 tw:grid-cols-[1fr] tw:opacity-100" : "tw:mr-0 tw:grid-cols-[0fr] tw:opacity-0", className),
2249
+ children: /* @__PURE__ */ jsx("div", {
2250
+ className: "tw:overflow-hidden",
2251
+ children: tooltip ? /* @__PURE__ */ jsx(Tooltip, {
2252
+ placement: "top",
2253
+ tooltip,
2254
+ triggerAsChild: true,
2255
+ children: button
2256
+ }) : button
852
2257
  })
853
2258
  });
854
2259
  };
855
2260
  //#endregion
856
2261
  //#region src/blocks/AIKit/PlanApproval.tsx
857
2262
  const defaultStepCountLabel = (count) => `${count} step${count === 1 ? "" : "s"}`;
858
- const StatusPill = ({ status, pendingLabel, approvedLabel, dismissedLabel, changesRequestedLabel }) => {
859
- if (status === "approved") return /* @__PURE__ */ jsx(Badge, {
860
- color: "green",
861
- variant: "pill",
862
- className: "tw:normal-case tw:tracking-normal",
863
- children: approvedLabel
864
- });
865
- if (status === "dismissed") return /* @__PURE__ */ jsx(Badge, {
866
- color: "gray",
867
- variant: "pill",
868
- className: "tw:normal-case tw:tracking-normal",
869
- children: dismissedLabel
870
- });
871
- if (status === "changes-requested") return /* @__PURE__ */ jsx(Badge, {
872
- color: "blue",
873
- variant: "pill",
874
- className: "tw:normal-case tw:tracking-normal",
875
- children: changesRequestedLabel
876
- });
877
- return /* @__PURE__ */ jsx(Badge, {
878
- color: "green",
879
- variant: "pill",
880
- className: "tw:normal-case tw:tracking-normal",
881
- children: pendingLabel
882
- });
2263
+ const STATUS_PILL_COLOR = {
2264
+ pending: "green",
2265
+ approved: "green",
2266
+ dismissed: "gray",
2267
+ "changes-requested": "blue",
2268
+ expired: "orange"
883
2269
  };
2270
+ const StatusPill = ({ status, label }) => /* @__PURE__ */ jsx(Badge, {
2271
+ color: STATUS_PILL_COLOR[status],
2272
+ variant: "pill",
2273
+ children: label
2274
+ });
884
2275
  const ExpandToggle = ({ isExpanded, onToggle, showMoreLabel, showLessLabel }) => /* @__PURE__ */ jsxs("button", {
885
2276
  type: "button",
886
2277
  onClick: onToggle,
@@ -958,12 +2349,19 @@ const PlanActions = ({ isRequestingChanges, isSubmitting, onApprove, onRequestCh
958
2349
  children: approveLabel
959
2350
  }))]
960
2351
  });
961
- const PlanApproval = ({ status, title, summary, stepCount, planBody, defaultExpanded = false, isExpanded: isExpandedProp, onExpandToggle, onApprove, onRequestChanges, onClose, isSubmitting = false, approveTooltip, pendingPillLabel = "Ready for approval", approvedPillLabel = "Approved", dismissedPillLabel = "Dismissed", changesRequestedPillLabel = "Changes Requested", approveLabel = "Approve", requestChangesLabel = "Request Changes", cancelLabel = "Cancel", showMoreLabel = "Show more", showLessLabel = "Show less", closeLabel = "Dismiss plan", fullScreenLabel = "View full screen", exitFullScreenLabel = "Exit full screen", changeRequestPlaceholder = "What would you like to change in the plan?", changeRequestSubmitLabel = "Send change request", stepCountLabel = defaultStepCountLabel, className }) => {
2352
+ const PlanApproval = ({ status, statusLabel, statusMessage, title, summary, stepCount, planBody, defaultExpanded = false, isExpanded: isExpandedProp, onExpandToggle, onApprove, onRequestChanges, onClose, isSubmitting = false, approveTooltip, pendingPillLabel = "Ready for approval", approvedPillLabel = "Approved", dismissedPillLabel = "Dismissed", changesRequestedPillLabel = "Changes Requested", approveLabel = "Approve", requestChangesLabel = "Request Changes", cancelLabel = "Cancel", showMoreLabel = "Show more", showLessLabel = "Show less", closeLabel = "Dismiss plan", fullScreenLabel = "View full screen", exitFullScreenLabel = "Exit full screen", changeRequestPlaceholder = "What would you like to change in the plan?", changeRequestSubmitLabel = "Send change request", stepCountLabel = defaultStepCountLabel, className }) => {
962
2353
  const [uncontrolledExpanded, setUncontrolledExpanded] = useState(defaultExpanded);
963
2354
  const isExpanded = isExpandedProp ?? uncontrolledExpanded;
964
2355
  const [isRequestingChanges, setIsRequestingChanges] = useState(false);
965
2356
  const [changeRequest, setChangeRequest] = useState("");
966
2357
  const [isFullScreen, setIsFullScreen] = useState(false);
2358
+ const pillLabel = statusLabel ?? {
2359
+ pending: pendingPillLabel,
2360
+ approved: approvedPillLabel,
2361
+ dismissed: dismissedPillLabel,
2362
+ "changes-requested": changesRequestedPillLabel,
2363
+ expired: "Expired"
2364
+ }[status];
967
2365
  const isPending = status === "pending";
968
2366
  const showActions = isPending && (onApprove || onRequestChanges);
969
2367
  const showClose = isPending && Boolean(onClose);
@@ -1030,22 +2428,27 @@ const PlanApproval = ({ status, title, summary, stepCount, planBody, defaultExpa
1030
2428
  className: "tw:flex tw:items-start tw:gap-2 tw:pl-4 tw:pr-2 tw:pt-4",
1031
2429
  children: [/* @__PURE__ */ jsxs("div", {
1032
2430
  className: "tw:flex tw:min-w-0 tw:flex-1 tw:flex-col tw:gap-2 tw:pb-4",
1033
- children: [/* @__PURE__ */ jsxs("div", {
1034
- className: "tw:flex tw:items-center tw:gap-3",
1035
- children: [/* @__PURE__ */ jsx(StatusPill, {
1036
- status,
1037
- pendingLabel: pendingPillLabel,
1038
- approvedLabel: approvedPillLabel,
1039
- dismissedLabel: dismissedPillLabel,
1040
- changesRequestedLabel: changesRequestedPillLabel
1041
- }), stepCount != null && stepCount > 0 && /* @__PURE__ */ jsx("span", {
1042
- className: "tw:text-xs tw:leading-4 tw:text-muted-foreground",
1043
- children: stepCountLabel(stepCount)
1044
- })]
1045
- }), /* @__PURE__ */ jsx("p", {
1046
- className: "tw:m-0 tw:text-base tw:font-semibold tw:leading-6",
1047
- children: title
1048
- })]
2431
+ children: [
2432
+ /* @__PURE__ */ jsxs("div", {
2433
+ className: "tw:flex tw:items-center tw:gap-3",
2434
+ children: [/* @__PURE__ */ jsx(StatusPill, {
2435
+ status,
2436
+ label: pillLabel
2437
+ }), stepCount != null && stepCount > 0 && /* @__PURE__ */ jsx("span", {
2438
+ className: "tw:text-xs tw:leading-4 tw:text-muted-foreground",
2439
+ children: stepCountLabel(stepCount)
2440
+ })]
2441
+ }),
2442
+ /* @__PURE__ */ jsx("p", {
2443
+ className: "tw:m-0 tw:text-base tw:font-semibold tw:leading-6",
2444
+ children: title
2445
+ }),
2446
+ statusMessage != null && /* @__PURE__ */ jsx("p", {
2447
+ className: "tw:m-0 tw:text-sm tw:text-muted-foreground",
2448
+ "data-testid": "plan-status-message",
2449
+ children: statusMessage
2450
+ })
2451
+ ]
1049
2452
  }), /* @__PURE__ */ jsxs("div", {
1050
2453
  className: "tw:flex tw:items-center tw:gap-2",
1051
2454
  children: [canShowPlanBody && !isFullScreen && /* @__PURE__ */ jsx(Tooltip, {
@@ -1105,10 +2508,7 @@ const PlanApproval = ({ status, title, summary, stepCount, planBody, defaultExpa
1105
2508
  className: "tw:flex tw:min-w-0 tw:items-center tw:gap-3",
1106
2509
  children: [/* @__PURE__ */ jsx(StatusPill, {
1107
2510
  status,
1108
- pendingLabel: pendingPillLabel,
1109
- approvedLabel: approvedPillLabel,
1110
- dismissedLabel: dismissedPillLabel,
1111
- changesRequestedLabel: changesRequestedPillLabel
2511
+ label: pillLabel
1112
2512
  }), /* @__PURE__ */ jsx("span", {
1113
2513
  className: "tw:truncate",
1114
2514
  children: title
@@ -1294,7 +2694,7 @@ const clipBand = (node) => {
1294
2694
  bottom
1295
2695
  };
1296
2696
  };
1297
- const PromptInputCommands = ({ items, open, onSelect, filterValue, emptyText = "No commands found", panel, groupAction, groupHeading, children }) => {
2697
+ const PromptInputCommands = ({ items, open, onSelect, filterValue, tabPicks = false, emptyText = "No commands found", panel, groupAction, groupHeading, children }) => {
1298
2698
  const filtered = matchesFilter(items, filterValue);
1299
2699
  const [highlighted, setHighlighted] = useState();
1300
2700
  const [wasOpen, setWasOpen] = useState(open);
@@ -1346,6 +2746,15 @@ const PromptInputCommands = ({ items, open, onSelect, filterValue, emptyText = "
1346
2746
  acc[item.group] = group;
1347
2747
  return acc;
1348
2748
  }, {});
2749
+ const handleKeyDown = (event) => {
2750
+ if (!tabPicks || event.key !== "Tab" || event.shiftKey || !open || panel) return;
2751
+ if (!(event.target instanceof HTMLTextAreaElement)) return;
2752
+ if (event.defaultPrevented || event.nativeEvent.isComposing) return;
2753
+ const picked = filtered.find((item) => item.value === highlightedValue);
2754
+ if (!picked) return;
2755
+ event.preventDefault();
2756
+ onSelect(picked);
2757
+ };
1349
2758
  const menuStyle = { "--prompt-menu-max-h": `${maxHeight}px` };
1350
2759
  return /* @__PURE__ */ jsxs(Command, {
1351
2760
  ref: attachRoot,
@@ -1354,6 +2763,7 @@ const PromptInputCommands = ({ items, open, onSelect, filterValue, emptyText = "
1354
2763
  shouldFilter: false,
1355
2764
  value: highlightedValue ?? "",
1356
2765
  onValueChange: setHighlighted,
2766
+ onKeyDown: handleKeyDown,
1357
2767
  children: [open && /* @__PURE__ */ jsx("div", {
1358
2768
  className: cn("tw:absolute tw:left-0 tw:z-50 tw:w-full tw:max-w-lg", below ? "tw:top-full tw:mt-1" : "tw:bottom-full tw:mb-1", "tw:rounded-[14px] tw:border tw:border-border tw:bg-popover tw:text-popover-foreground tw:shadow-lg", "tw:p-1"),
1359
2769
  style: menuStyle,
@@ -1364,7 +2774,7 @@ const PromptInputCommands = ({ items, open, onSelect, filterValue, emptyText = "
1364
2774
  className: "tw:py-3 tw:pl-4 tw:text-center tw:text-sm tw:text-muted-foreground",
1365
2775
  children: emptyText
1366
2776
  }) : /* @__PURE__ */ jsx(Command.List, {
1367
- className: "tw:group-data-[variant=argo]/command:px-1 tw:group-data-[variant=argo]/command:pb-1",
2777
+ className: "tw:px-1 tw:pb-1",
1368
2778
  viewPortClassName: "tw:max-h-[var(--prompt-menu-max-h)] tw:[&>div]:!block",
1369
2779
  children: Object.entries(groups).map(([groupName, groupItems], index) => /* @__PURE__ */ jsx(Command.Group, {
1370
2780
  heading: groupName ? /* @__PURE__ */ jsxs("span", {
@@ -1455,6 +2865,68 @@ const PromptInputSubmit = ({ ref, disabled, status = "ready" }) => {
1455
2865
  })] });
1456
2866
  };
1457
2867
  //#endregion
2868
+ //#region src/blocks/AIKit/usePromptHistory.ts
2869
+ /** The prompts ↑ steps through: blanks dropped, a prompt sent twice in a row kept once. */
2870
+ const toRecallStack = (history) => history.reduce((stack, prompt) => {
2871
+ if (prompt.trim() && stack.at(-1) !== prompt) stack.push(prompt);
2872
+ return stack;
2873
+ }, []);
2874
+ const isPlainArrow = (event) => (event.key === "ArrowUp" || event.key === "ArrowDown") && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey && !event.nativeEvent.isComposing;
2875
+ /** Whether the caret sits on the text's first line (↑) or last line (↓), where a textarea has nowhere to move it. */
2876
+ const isOnEdgeLine = ({ key, currentTarget }) => {
2877
+ const { value, selectionStart, selectionEnd } = currentTarget;
2878
+ return key === "ArrowUp" ? !value.slice(0, selectionStart).includes("\n") : !value.slice(selectionEnd).includes("\n");
2879
+ };
2880
+ /**
2881
+ * Shell-style recall of the prompts already sent, on ↑ and ↓.
2882
+ *
2883
+ * - ↑ starts from the caret at the very start of the text.
2884
+ * - While browsing, ↑ and ↓ step only from the first and last line; inside a multi-line prompt they move the caret.
2885
+ * - ↓ past the newest prompt gives back the draft typed before browsing began.
2886
+ * - Browsing ends on any edit, or once the value is no longer the recalled prompt.
2887
+ */
2888
+ const usePromptHistory = ({ history, value, onRecall }) => {
2889
+ const stack = useMemo(() => toRecallStack(history ?? []), [history]);
2890
+ const browsingRef = useRef(null);
2891
+ const stop = () => {
2892
+ browsingRef.current = null;
2893
+ };
2894
+ const recall = (event, next, text) => {
2895
+ browsingRef.current = next;
2896
+ event.preventDefault();
2897
+ onRecall(text);
2898
+ return true;
2899
+ };
2900
+ const handleKeyDown = (event) => {
2901
+ if (stack.length === 0 || !isPlainArrow(event)) return false;
2902
+ const current = browsingRef.current;
2903
+ const browsing = current && stack[current.position] === value ? current : null;
2904
+ if (event.key === "ArrowUp") {
2905
+ const { selectionStart, selectionEnd } = event.currentTarget;
2906
+ if (browsing ? !isOnEdgeLine(event) : !(selectionStart === 0 && selectionEnd === 0)) return false;
2907
+ const position = (browsing?.position ?? stack.length) - 1;
2908
+ const older = stack[position];
2909
+ if (older === void 0) return false;
2910
+ return recall(event, {
2911
+ position,
2912
+ draft: browsing?.draft ?? value
2913
+ }, older);
2914
+ }
2915
+ if (!browsing || !isOnEdgeLine(event)) return false;
2916
+ const position = browsing.position + 1;
2917
+ const newer = stack[position];
2918
+ if (newer === void 0) return recall(event, null, browsing.draft);
2919
+ return recall(event, {
2920
+ ...browsing,
2921
+ position
2922
+ }, newer);
2923
+ };
2924
+ return {
2925
+ handleKeyDown,
2926
+ stop
2927
+ };
2928
+ };
2929
+ //#endregion
1458
2930
  //#region src/blocks/AIKit/PromptInput.tsx
1459
2931
  const CMDK_HIJACKED_KEYS = /* @__PURE__ */ new Set([
1460
2932
  "ArrowUp",
@@ -1544,9 +3016,11 @@ const wrapperVariants = cva(null, {
1544
3016
  inline: ""
1545
3017
  } }
1546
3018
  });
1547
- const PromptInput = ({ onSend, onAbort, onContinue, prefix, variant, suffix, attachments, className, disabled, disableSubmit, value = "", onValueChange, status = "ready", maxRows = 6, minRows, commands, commandsGroupAction, panel, mentions, onMentionSelect, mentionsEmptyText = "Nothing to attach", mentionsGroupHeading, mentionsMultiple = false, autoFocus, textareaRef: publishedRef, ...textareaProps }) => {
3019
+ const PromptInput = ({ onSend, onAbort, onContinue, prefix, variant, suffix, attachments, className, disabled, disableSubmit, busy = false, blurOnEscape = false, onKeyDown, onPasteFiles, canSubmitEmpty = false, allowSendWhileBusy = false, canAbort = true, value = "", onValueChange, status = "ready", maxRows = 6, minRows, commands, commandsEmptyText = "No commands found", commandsLoading = false, slashTrigger = "value", commandsGroupAction, panel, mentions, onMentionSelect, mentionsEmptyText = "Nothing to attach", mentionsGroupHeading, mentionsMultiple = false, history, autoFocus, textareaRef: publishedRef, ...textareaProps }) => {
1548
3020
  const defaultMinRows = variant === "inline" ? 1 : 2;
1549
- const isDisabled = (disabled || disableSubmit || !value.trim()) && status !== "streaming" && status !== "interrupted";
3021
+ const canSendWhileBusy = allowSendWhileBusy && (status === "streaming" || status === "submitted") && !busy && !disabled && !disableSubmit && value.trim() !== "";
3022
+ const showSeparateStop = allowSendWhileBusy && canAbort && (canSendWhileBusy || status === "submitted");
3023
+ const isDisabled = busy || !canSendWhileBusy && (disabled || disableSubmit || !canSubmitEmpty && !value.trim()) && status !== "streaming" && status !== "interrupted";
1550
3024
  const textareaId = useId();
1551
3025
  const textareaRef = useRef(null);
1552
3026
  const setTextareaNode = useCallback((node) => {
@@ -1565,20 +3039,37 @@ const PromptInput = ({ onSend, onAbort, onContinue, prefix, variant, suffix, att
1565
3039
  useEffect(() => {
1566
3040
  if (autoFocusRef.current) textareaRef.current?.focus({ preventScroll: true });
1567
3041
  }, []);
3042
+ const [typedSlashValue, setTypedSlashValue] = useState(null);
1568
3043
  if (dismissedValue !== null && value !== dismissedValue) setDismissedValue(null);
1569
- const hasCommands = !!commands && commands.length > 0;
3044
+ const hasCommands = commands !== void 0;
1570
3045
  const panelOpen = panel !== void 0;
1571
3046
  const hasMentions = mentions !== void 0;
1572
3047
  const hasMenus = hasCommands || hasMentions;
1573
- const slashToken = hasCommands && !panelOpen && value !== dismissedValue ? findSlashToken(value, Math.min(caret, value.length)) : null;
3048
+ const slashToken = hasCommands && !panelOpen && value !== dismissedValue && (slashTrigger === "value" || typedSlashValue === value) ? findSlashToken(value, Math.min(caret, value.length)) : null;
1574
3049
  const commandsOpen = slashToken !== null;
1575
3050
  const mentionToken = hasMentions && !commandsOpen && !panelOpen && value !== dismissedValue ? findMentionToken(value, Math.min(caret, value.length)) : null;
1576
3051
  const mentionsOpen = mentionToken !== null;
1577
3052
  const menuOpen = commandsOpen || mentionsOpen || panelOpen;
1578
3053
  const menuItems = commandsOpen ? commands ?? [] : mentions ?? [];
1579
3054
  const menuFilter = commandsOpen ? slashToken.query : mentionToken?.query ?? "";
3055
+ const promptHistory = usePromptHistory({
3056
+ history,
3057
+ value,
3058
+ onRecall: (text) => {
3059
+ onValueChange?.(text);
3060
+ moveCaret(text.length, text);
3061
+ setDismissedValue(text);
3062
+ requestAnimationFrame(() => {
3063
+ textareaRef.current?.setSelectionRange(text.length, text.length);
3064
+ });
3065
+ }
3066
+ });
1580
3067
  const handleSubmit = () => {
1581
3068
  if (isDisabled) return;
3069
+ if (canSendWhileBusy) {
3070
+ onSend?.(value);
3071
+ return;
3072
+ }
1582
3073
  if (status === "streaming") onAbort?.();
1583
3074
  if (status === "interrupted") onContinue?.();
1584
3075
  if (status === "ready") onSend?.(value);
@@ -1588,6 +3079,11 @@ const PromptInput = ({ onSend, onAbort, onContinue, prefix, variant, suffix, att
1588
3079
  handleSubmit();
1589
3080
  };
1590
3081
  const handleOnChange = (event) => {
3082
+ promptHistory.stop();
3083
+ const inputType = event.nativeEvent instanceof InputEvent ? event.nativeEvent.inputType : "";
3084
+ const isTyping = inputType === "insertText" || inputType.startsWith("delete");
3085
+ const startsSession = inputType === "insertText" && value === "" && event.target.value === "/";
3086
+ setTypedSlashValue(startsSession || isTyping && typedSlashValue === value ? event.target.value : null);
1591
3087
  moveCaret(event.target.selectionStart ?? event.target.value.length, event.target.value);
1592
3088
  onValueChange?.(event.target.value);
1593
3089
  };
@@ -1641,7 +3137,14 @@ const PromptInput = ({ onSend, onAbort, onContinue, prefix, variant, suffix, att
1641
3137
  }
1642
3138
  setDismissedValue(value);
1643
3139
  };
1644
- const handleOnKeyDown = (event) => {
3140
+ const handleOnPaste = (event) => {
3141
+ if (!onPasteFiles) return;
3142
+ const files = Array.from(event.clipboardData.files);
3143
+ if (files.length === 0) return;
3144
+ onPasteFiles(files);
3145
+ if (!event.clipboardData.getData("text/plain")) event.preventDefault();
3146
+ };
3147
+ const handleOnComposerKeyDown = (event) => {
1645
3148
  if (menuOpen) {
1646
3149
  if (event.key === "Escape") {
1647
3150
  event.preventDefault();
@@ -1653,11 +3156,18 @@ const PromptInput = ({ onSend, onAbort, onContinue, prefix, variant, suffix, att
1653
3156
  if (event.key === "ArrowUp" || event.key === "ArrowDown") return;
1654
3157
  if (event.key === "Enter" && matchesFilter(menuItems, menuFilter).length > 0) return;
1655
3158
  } else if (hasMenus && isCmdkNavigation(event)) event.stopPropagation();
3159
+ if (promptHistory.handleKeyDown(event)) return;
1656
3160
  if (event.key === "Enter" && !event.shiftKey && !event.ctrlKey && !event.metaKey) {
1657
3161
  event.preventDefault();
1658
- if (status !== "streaming") handleSubmit();
3162
+ if (status !== "streaming" || canSendWhileBusy) handleSubmit();
1659
3163
  }
1660
3164
  };
3165
+ const handleOnKeyDown = (event) => {
3166
+ handleOnComposerKeyDown(event);
3167
+ if (event.defaultPrevented) return;
3168
+ if (blurOnEscape && event.key === "Escape") event.currentTarget.blur();
3169
+ onKeyDown?.(event);
3170
+ };
1661
3171
  const handleOnFormKeyDown = (event) => {
1662
3172
  if (hasMenus && event.target !== textareaRef.current && isCmdkNavigation(event)) event.stopPropagation();
1663
3173
  };
@@ -1710,15 +3220,37 @@ const PromptInput = ({ onSend, onAbort, onContinue, prefix, variant, suffix, att
1710
3220
  onChange: handleOnChange,
1711
3221
  onSelect: handleOnSelect,
1712
3222
  onKeyDown: handleOnKeyDown,
1713
- className: cn("tw:resize-none", "tw:outline-hidden", "tw:min-w-32 tw:flex-1", variant === "inline" ? "tw:@max-[480px]/composer:pl-2" : "tw:pl-2 tw:pr-0", "tw:bg-transparent", disabled ? "tw:text-muted-foreground" : "tw:text-foreground", "tw:text-sm", "tw:font-sans", "tw:transition-[width]", "tw:delay-75", "tw:ease-linear", "tw:overflow-auto")
3223
+ onPaste: handleOnPaste,
3224
+ readOnly: busy,
3225
+ "aria-busy": busy || void 0,
3226
+ className: cn("tw:resize-none", "tw:outline-hidden", "tw:min-w-32 tw:flex-1", variant === "inline" ? "tw:@max-[480px]/composer:pl-2" : "tw:pl-2 tw:pr-0", "tw:bg-transparent", disabled || busy ? "tw:text-muted-foreground" : "tw:text-foreground", "tw:text-sm", "tw:font-sans", "tw:transition-[width]", "tw:delay-75", "tw:ease-linear", "tw:overflow-auto")
1714
3227
  })
1715
3228
  }),
1716
3229
  /* @__PURE__ */ jsxs("div", {
1717
3230
  className: cn("tw:flex tw:items-center tw:gap-1", "tw:[&>*:last-child]:ml-1", "tw:shrink-0", variant === "inline" && "tw:pr-2.5 tw:@max-[480px]/composer:grow tw:@max-[480px]/composer:pr-0"),
1718
- children: [suffix, /* @__PURE__ */ jsx(PromptInputSubmit, {
1719
- disabled: isDisabled,
1720
- status
1721
- })]
3231
+ children: [
3232
+ suffix,
3233
+ showSeparateStop && /* @__PURE__ */ jsxs(Tooltip.Root, { children: [/* @__PURE__ */ jsx(Tooltip.Trigger, {
3234
+ asChild: true,
3235
+ children: /* @__PURE__ */ jsx(PromptInputAction, { children: /* @__PURE__ */ jsx(Button, {
3236
+ type: "button",
3237
+ onClick: () => onAbort?.(),
3238
+ "aria-label": "Stop",
3239
+ size: "icon-sm",
3240
+ variant: "outline",
3241
+ className: "tw:flex tw:items-center tw:rounded-full",
3242
+ children: /* @__PURE__ */ jsx(CirclePause, { className: "tw:size-4" })
3243
+ }) })
3244
+ }), /* @__PURE__ */ jsx(Tooltip.Content, {
3245
+ placement: "top",
3246
+ variant: "action",
3247
+ children: "Stop"
3248
+ })] }),
3249
+ /* @__PURE__ */ jsx(PromptInputSubmit, {
3250
+ disabled: isDisabled,
3251
+ status: canSendWhileBusy ? "ready" : status
3252
+ })
3253
+ ]
1722
3254
  })
1723
3255
  ]
1724
3256
  })
@@ -1730,7 +3262,8 @@ const PromptInput = ({ onSend, onAbort, onContinue, prefix, variant, suffix, att
1730
3262
  open: menuOpen,
1731
3263
  onSelect: handleMenuSelect,
1732
3264
  filterValue: menuFilter,
1733
- emptyText: mentionsOpen ? mentionsEmptyText : "No commands found",
3265
+ tabPicks: commandsOpen,
3266
+ emptyText: mentionsOpen ? mentionsEmptyText : commandsLoading ? "Loading commands…" : commandsEmptyText,
1734
3267
  panel: panel?.content,
1735
3268
  groupAction: commandsOpen ? commandsGroupAction : void 0,
1736
3269
  groupHeading: mentionsOpen ? mentionsGroupHeading : void 0,
@@ -1741,7 +3274,7 @@ PromptInput.Action = PromptInputAction;
1741
3274
  PromptInput.MenuTrigger = PromptInputMenuTrigger;
1742
3275
  //#endregion
1743
3276
  //#region src/blocks/AIKit/Question.tsx
1744
- const isEditableTarget = (target) => target instanceof HTMLElement && (target.isContentEditable || [
3277
+ const isEditableTarget$1 = (target) => target instanceof HTMLElement && (target.isContentEditable || [
1745
3278
  "INPUT",
1746
3279
  "TEXTAREA",
1747
3280
  "SELECT"
@@ -1752,6 +3285,11 @@ const isFieldWithText = (element) => {
1752
3285
  };
1753
3286
  const hasCommandModifier = (e) => e.altKey || e.ctrlKey || e.metaKey;
1754
3287
  const SEND_KEY_CLASS = "tw:!bg-white/20 tw:!text-primary-foreground";
3288
+ const DEFAULT_SKIP_TOOLTIP = /* @__PURE__ */ jsx(Kbd, {
3289
+ variant: "primary",
3290
+ casing: "as-written",
3291
+ children: "Space"
3292
+ });
1755
3293
  const keyRange = (count) => count > 1 ? `1–${count}` : "1";
1756
3294
  /** An option's key, or a blank slot past the ninth; a picked option's turns into a check. */
1757
3295
  const OptionKey = ({ digit, checked }) => digit === null && !checked ? /* @__PURE__ */ jsx("span", {
@@ -1770,7 +3308,7 @@ const handleKeyDown = (callback) => (e) => {
1770
3308
  callback();
1771
3309
  }
1772
3310
  };
1773
- const Question = ({ question, header, options, multiSelect = true, selectedOptionIds = [], onSelectionChange, customAnswer = "", onCustomAnswerChange, onCustomAnswerConfirm, showCustomAnswer = true, customAnswerPlaceholder = "Type your answer…", showSkip = true, onSkip, onClose, onNext, submitTooltip, onPrevious, currentStep, totalSteps, renderPreview, autoFocus = false, enableShortcuts = false, className }) => {
3311
+ const Question = ({ question, header, options, multiSelect = true, selectedOptionIds = [], onSelectionChange, customAnswer = "", onCustomAnswerChange, onCustomAnswerConfirm, showCustomAnswer = true, customAnswerPlaceholder = "Type your answer…", customAnswerMaxLength, showSkip = true, onSkip, skipTooltip = DEFAULT_SKIP_TOOLTIP, onClose, onNext, submitTooltip, isSubmitDisabled = false, isSubmitting = false, error, onPrevious, currentStep, totalSteps, renderPreview, autoFocus = false, enableShortcuts = false, className }) => {
1774
3312
  const [isCustomExpanded, setIsCustomExpanded] = useState(false);
1775
3313
  const [activeRow, setActiveRow] = useState(0);
1776
3314
  const [pendingId, setPendingId] = useState(null);
@@ -1784,6 +3322,7 @@ const Question = ({ question, header, options, multiSelect = true, selectedOptio
1784
3322
  const chosenId = multiSelect ? null : pendingId ?? selectedOptionIds[0] ?? null;
1785
3323
  const showSubmitButton = multiSelect || isCustomSelected || chosenId !== null;
1786
3324
  const showFooter = showStepControls || showSkipButton || showSubmitButton;
3325
+ const canSend = !isSubmitDisabled && !isSubmitting;
1787
3326
  const progressValue = hasSteps ? currentStep / totalSteps * 100 : 0;
1788
3327
  const isFirstStep = currentStep === 1;
1789
3328
  const isLastStep = hasSteps && currentStep === totalSteps;
@@ -1834,7 +3373,7 @@ const Question = ({ question, header, options, multiSelect = true, selectedOptio
1834
3373
  */
1835
3374
  const handleCardKeyDown = (e) => {
1836
3375
  if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && !e.altKey && !e.repeat) {
1837
- if (!showSubmitButton) return;
3376
+ if (!showSubmitButton || !canSend) return;
1838
3377
  send();
1839
3378
  e.preventDefault();
1840
3379
  e.stopPropagation();
@@ -1845,7 +3384,7 @@ const Question = ({ question, header, options, multiSelect = true, selectedOptio
1845
3384
  focusRow(options.length);
1846
3385
  return;
1847
3386
  }
1848
- if (e.defaultPrevented || hasCommandModifier(e) || isEditableTarget(e.target)) return;
3387
+ if (e.defaultPrevented || hasCommandModifier(e) || isEditableTarget$1(e.target)) return;
1849
3388
  if ((e.key === "ArrowDown" || e.key === "ArrowUp") && rowCount > 0) {
1850
3389
  e.preventDefault();
1851
3390
  const delta = e.key === "ArrowDown" ? 1 : -1;
@@ -1871,7 +3410,7 @@ const Question = ({ question, header, options, multiSelect = true, selectedOptio
1871
3410
  if (e.key === " " && e.target === e.currentTarget) {
1872
3411
  e.preventDefault();
1873
3412
  e.stopPropagation();
1874
- if (showSkipButton) onSkip?.();
3413
+ if (showSkipButton && !isSubmitting) onSkip?.();
1875
3414
  return;
1876
3415
  }
1877
3416
  if (showCustomAnswer && digit === null && e.key.length === 1 && e.key !== " ") {
@@ -1912,6 +3451,8 @@ const Question = ({ question, header, options, multiSelect = true, selectedOptio
1912
3451
  variant: "primary",
1913
3452
  size: "small",
1914
3453
  onClick: send,
3454
+ disabled: !canSend,
3455
+ isLoading: isSubmitting,
1915
3456
  "data-testid": "question-next-button",
1916
3457
  children: [isLastStep ? "Submit" : "Next", /* @__PURE__ */ jsx(Kbd, {
1917
3458
  "aria-hidden": true,
@@ -1920,6 +3461,15 @@ const Question = ({ question, header, options, multiSelect = true, selectedOptio
1920
3461
  children: `${keys.mod.symbol} Enter`
1921
3462
  })]
1922
3463
  });
3464
+ const skipButton = /* @__PURE__ */ jsx(Button, {
3465
+ variant: "outline",
3466
+ size: "small",
3467
+ onClick: onSkip,
3468
+ disabled: isSubmitting,
3469
+ "aria-keyshortcuts": "Space",
3470
+ "data-testid": "question-skip-button",
3471
+ children: "Skip"
3472
+ });
1923
3473
  return /* @__PURE__ */ jsxs("div", {
1924
3474
  ref: rootRef,
1925
3475
  role: "group",
@@ -1986,7 +3536,15 @@ const Question = ({ question, header, options, multiSelect = true, selectedOptio
1986
3536
  className: "tw:flex tw:min-w-0 tw:flex-1 tw:items-start tw:gap-3",
1987
3537
  children: [/* @__PURE__ */ jsxs("div", {
1988
3538
  className: "tw:flex tw:flex-col tw:gap-1.5",
1989
- children: [/* @__PURE__ */ jsx("span", {
3539
+ children: [option.badge ? /* @__PURE__ */ jsxs("span", {
3540
+ className: "tw:flex tw:flex-wrap tw:items-center tw:gap-x-2 tw:gap-y-1 tw:text-sm tw:font-medium tw:leading-none",
3541
+ children: [option.label, /* @__PURE__ */ jsx(Badge, {
3542
+ variant: "pill",
3543
+ color: "green-inverse",
3544
+ "data-testid": "question-option-badge",
3545
+ children: option.badge
3546
+ })]
3547
+ }) : /* @__PURE__ */ jsx("span", {
1990
3548
  className: "tw:text-sm tw:font-medium tw:leading-none",
1991
3549
  children: option.label
1992
3550
  }), option.description && /* @__PURE__ */ jsx("span", {
@@ -2036,7 +3594,7 @@ const Question = ({ question, header, options, multiSelect = true, selectedOptio
2036
3594
  }
2037
3595
  } : void 0,
2038
3596
  placeholder: customAnswerPlaceholder,
2039
- sizing: "small",
3597
+ maxLength: customAnswerMaxLength,
2040
3598
  testId: "question-custom"
2041
3599
  })]
2042
3600
  })]
@@ -2067,6 +3625,12 @@ const Question = ({ question, header, options, multiSelect = true, selectedOptio
2067
3625
  })
2068
3626
  ]
2069
3627
  }),
3628
+ error != null && /* @__PURE__ */ jsx("div", {
3629
+ role: "alert",
3630
+ "data-testid": "question-error",
3631
+ className: "tw:mx-4 tw:-mt-1 tw:rounded-md tw:bg-destructive/10 tw:px-3 tw:py-2 tw:text-sm tw:text-destructive",
3632
+ children: error
3633
+ }),
2070
3634
  /* @__PURE__ */ jsxs("div", {
2071
3635
  className: "tw:flex tw:flex-col",
2072
3636
  children: [showStepControls && /* @__PURE__ */ jsx(Progress, {
@@ -2105,23 +3669,12 @@ const Question = ({ question, header, options, multiSelect = true, selectedOptio
2105
3669
  })
2106
3670
  ]
2107
3671
  }),
2108
- showSkipButton && /* @__PURE__ */ jsx(Tooltip, {
2109
- tooltip: /* @__PURE__ */ jsx(Kbd, {
2110
- variant: "primary",
2111
- casing: "as-written",
2112
- children: "Space"
2113
- }),
3672
+ showSkipButton && (skipTooltip == null ? skipButton : /* @__PURE__ */ jsx(Tooltip, {
3673
+ tooltip: skipTooltip,
2114
3674
  placement: "top",
2115
3675
  triggerAsChild: true,
2116
- children: /* @__PURE__ */ jsx(Button, {
2117
- variant: "outline",
2118
- size: "small",
2119
- onClick: onSkip,
2120
- "aria-keyshortcuts": "Space",
2121
- "data-testid": "question-skip-button",
2122
- children: "Skip"
2123
- })
2124
- }),
3676
+ children: skipButton
3677
+ })),
2125
3678
  showSubmitButton && (submitTooltip ? /* @__PURE__ */ jsx(Tooltip, {
2126
3679
  tooltip: submitTooltip,
2127
3680
  placement: "top",
@@ -2179,11 +3732,164 @@ const QuestionBlock = ({ steps, showSkip = true, onSubmit, onClose, renderPrevie
2179
3732
  }, currentStep);
2180
3733
  };
2181
3734
  //#endregion
3735
+ //#region src/blocks/AIKit/streamingMarkdown.ts
3736
+ /** A fence delimiter line: up to three spaces, then three or more backticks or tildes. */
3737
+ const FENCE_LINE = /^ {0,3}(`{3,}|~{3,})/;
3738
+ /** A list-item marker: a bullet or a `1.`/`1)` ordinal, then a space or the line end. */
3739
+ const LIST_MARKER = /^ {0,3}(?:[-*+]|\d{1,9}[.)])(?:\s|$)/;
3740
+ /** A line at column four continues whatever block sits above a blank line. */
3741
+ const INDENTED = /^(?: {4}| {0,3}\t)/;
3742
+ /** A line that continues an open list: one to three spaces, or another marker. */
3743
+ const CONTINUES_LIST = /^(?: {1,3}|(?:[-*+]|\d{1,9}[.)])(?:\s|$))/;
3744
+ /** A line that can only be a GFM table row: it opens with a pipe. */
3745
+ const TABLE_ROW = /^ {0,3}\|/;
3746
+ /** A delimiter row still streaming: pipes, dashes, colons and spaces only. */
3747
+ const TABLE_DELIMITER_PARTIAL = /^ {0,3}\|?[ :|-]*$/;
3748
+ /** Past this length a lone pipe line with no second pipe is prose, not a header. */
3749
+ const TABLE_HEAD_FIRST_CELL_MAX = 80;
3750
+ /** Whether `line` closes a fence opened with `marker`: same character, at least as long, nothing after. */
3751
+ const closesFence = (line, marker) => {
3752
+ const match = FENCE_LINE.exec(line);
3753
+ return !!match?.[1] && match[1][0] === marker[0] && match[1].length >= marker.length && !line.slice(match[0].length).trim();
3754
+ };
3755
+ /**
3756
+ * Every offset where all blocks before it are closed.
3757
+ *
3758
+ * - A cut is the end of a blank line outside a fence whose next line opens a fresh block.
3759
+ * - The last line is never classified: it is still streaming.
3760
+ * - An indented line, or a list continuation while a list is open, defers the cut.
3761
+ */
3762
+ const sealableBoundaries = (text) => {
3763
+ const cuts = [];
3764
+ let pos = 0;
3765
+ let fence = null;
3766
+ let pendingBlank = -1;
3767
+ let listOpen = false;
3768
+ const cutAtBlank = () => {
3769
+ if (pendingBlank > 0) cuts.push(pendingBlank);
3770
+ listOpen = false;
3771
+ };
3772
+ while (pos < text.length) {
3773
+ const nl = text.indexOf("\n", pos);
3774
+ if (nl === -1) break;
3775
+ const line = text.slice(pos, nl);
3776
+ pos = nl + 1;
3777
+ if (fence !== null) {
3778
+ if (closesFence(line, fence)) fence = null;
3779
+ continue;
3780
+ }
3781
+ const continuesList = listOpen && CONTINUES_LIST.test(line);
3782
+ const opener = FENCE_LINE.exec(line)?.[1];
3783
+ if (opener !== void 0) {
3784
+ fence = opener;
3785
+ if (pendingBlank !== -1 && !continuesList) cutAtBlank();
3786
+ pendingBlank = -1;
3787
+ } else if (!line.trim()) pendingBlank = pos;
3788
+ else {
3789
+ const continues = INDENTED.test(line) || continuesList;
3790
+ if (pendingBlank !== -1 && !continues) cutAtBlank();
3791
+ pendingBlank = -1;
3792
+ if (LIST_MARKER.test(line)) listOpen = true;
3793
+ }
3794
+ }
3795
+ return cuts;
3796
+ };
3797
+ /**
3798
+ * A streaming answer split into blocks that can no longer change and the tail that still can.
3799
+ *
3800
+ * - Render each `sealed` block in its own memoised `Markdown.Block`, keyed by index; only `tail` re-parses.
3801
+ * - A sealed block's text never changes as more text arrives.
3802
+ * - Reference links and footnotes defined in a later block resolve only once the full answer renders.
3803
+ */
3804
+ const splitSealedBlocks = (text) => {
3805
+ const sealed = [];
3806
+ let from = 0;
3807
+ for (const cut of sealableBoundaries(text)) {
3808
+ sealed.push(text.slice(from, cut));
3809
+ from = cut;
3810
+ }
3811
+ return {
3812
+ sealed,
3813
+ tail: text.slice(from)
3814
+ };
3815
+ };
3816
+ /**
3817
+ * The fence still open at the end of `text`, if any.
3818
+ *
3819
+ * - `language` is `null` while the info string is still streaming (no newline yet).
3820
+ */
3821
+ const findOpenFence = (text) => {
3822
+ let open = null;
3823
+ let pos = 0;
3824
+ for (;;) {
3825
+ const nl = text.indexOf("\n", pos);
3826
+ const line = text.slice(pos, nl === -1 ? text.length : nl);
3827
+ if (open !== null) {
3828
+ if (nl !== -1 && closesFence(line, open.marker)) open = null;
3829
+ } else {
3830
+ const match = FENCE_LINE.exec(line);
3831
+ if (match?.[1]) {
3832
+ const info = line.slice(match[0].length).trim().split(/\s+/)[0] ?? "";
3833
+ open = {
3834
+ start: pos,
3835
+ marker: match[1],
3836
+ language: nl === -1 ? null : info.toLowerCase()
3837
+ };
3838
+ }
3839
+ }
3840
+ if (nl === -1) break;
3841
+ pos = nl + 1;
3842
+ }
3843
+ return open && {
3844
+ start: open.start,
3845
+ language: open.language
3846
+ };
3847
+ };
3848
+ /**
3849
+ * Drops a table header still waiting for its delimiter row.
3850
+ *
3851
+ * - Until the delimiter row ends, GFM renders the header as a paragraph of raw pipes.
3852
+ * - Two shapes are withheld at the end: a lone pipe line, or that line plus a partial delimiter.
3853
+ * - A data row is never withheld: it has table rows above it.
3854
+ */
3855
+ const withholdTableHead = (text) => {
3856
+ const lastNl = text.lastIndexOf("\n");
3857
+ const last = text.slice(lastNl + 1);
3858
+ const prevNl = lastNl === -1 ? -1 : text.lastIndexOf("\n", lastNl - 1);
3859
+ const prev = lastNl === -1 ? null : text.slice(prevNl + 1, lastNl);
3860
+ const prevIsRow = prev !== null && TABLE_ROW.test(prev);
3861
+ if (TABLE_ROW.test(last) && !prevIsRow) {
3862
+ const firstPipe = last.indexOf("|");
3863
+ return last.indexOf("|", firstPipe + 1) === -1 && last.length > TABLE_HEAD_FIRST_CELL_MAX ? text : text.slice(0, lastNl + 1);
3864
+ }
3865
+ if (prevIsRow && TABLE_DELIMITER_PARTIAL.test(last)) {
3866
+ const prev2Nl = prevNl === -1 ? -1 : text.lastIndexOf("\n", prevNl - 1);
3867
+ const prev2 = prevNl === -1 ? null : text.slice(prev2Nl + 1, prevNl);
3868
+ if (prev2 === null || !TABLE_ROW.test(prev2)) return text.slice(0, prevNl + 1);
3869
+ }
3870
+ return text;
3871
+ };
3872
+ /**
3873
+ * The part of a streaming answer that is safe to paint now.
3874
+ *
3875
+ * - An open fence whose language is still being typed is always held back.
3876
+ * - An open fence in a `diagram` language is held back, or every chunk redraws the chart.
3877
+ * - Any other open fence streams as a growing code block.
3878
+ */
3879
+ const withholdOpenTail = (text, { diagram = false, tableHead = false } = {}) => {
3880
+ const fence = findOpenFence(text);
3881
+ const diagramLanguages = diagram === true ? DIAGRAM_LANGUAGES : diagram === false ? [] : diagram;
3882
+ const visible = fence === null ? text : fence.language === null || diagramLanguages.includes(fence.language) ? text.slice(0, fence.start) : null;
3883
+ if (visible === null) return text;
3884
+ return tableHead ? withholdTableHead(visible) : visible;
3885
+ };
3886
+ //#endregion
2182
3887
  //#region src/blocks/AIKit/SuggestedPrompt.tsx
2183
- const SuggestedPrompt = ({ label, disabled, onClick, icon, variant = "row", className }) => {
3888
+ const SuggestedPrompt = ({ label, disabled, onClick, icon, description, trailing, "aria-label": ariaLabel, variant = "row", className }) => {
2184
3889
  return /* @__PURE__ */ jsxs("button", {
2185
3890
  type: "button",
2186
3891
  disabled,
3892
+ "aria-label": ariaLabel,
2187
3893
  "data-variant": variant,
2188
3894
  className: cn("tailwindcss-preflight tw:flex tw:items-center tw:justify-start tw:gap-2.5 tw:whitespace-nowrap", "tw:text-left tw:text-sm tw:text-foreground tw:transition-colors", "tw:focus-visible:outline-none tw:focus-visible:ring-2 tw:focus-visible:ring-ring tw:focus-visible:ring-offset-1", "tw:disabled:cursor-not-allowed tw:disabled:opacity-disabled", variant === "bubble" ? [
2189
3895
  "tw:w-fit tw:max-w-[min(85%,40rem)] tw:rounded-xl tw:px-3 tw:py-2",
@@ -2191,10 +3897,23 @@ const SuggestedPrompt = ({ label, disabled, onClick, icon, variant = "row", clas
2191
3897
  "tw:not-disabled:hover:bg-hover tw:focus-visible:bg-hover"
2192
3898
  ] : "tw:rounded-lg tw:px-3 tw:py-2 tw:font-medium tw:not-disabled:hover:bg-hover", className),
2193
3899
  onClick,
2194
- children: [icon ?? /* @__PURE__ */ jsx(Sparkles, { className: "tw:size-4 tw:shrink-0 tw:basis-4 tw:[&_*]:stroke-aigradient-600" }), /* @__PURE__ */ jsx("div", {
2195
- className: "tw:whitespace-normal tw:text-left",
2196
- children: label
2197
- })]
3900
+ children: [
3901
+ icon ?? /* @__PURE__ */ jsx(Sparkles, { className: "tw:size-4 tw:shrink-0 tw:basis-4 tw:[&_*]:stroke-aigradient-600" }),
3902
+ description === void 0 ? /* @__PURE__ */ jsx("div", {
3903
+ className: "tw:whitespace-normal tw:text-left",
3904
+ children: label
3905
+ }) : /* @__PURE__ */ jsxs("div", {
3906
+ className: "tw:flex tw:min-w-0 tw:flex-1 tw:flex-col tw:gap-0.5 tw:whitespace-normal tw:text-left",
3907
+ children: [/* @__PURE__ */ jsx("span", { children: label }), /* @__PURE__ */ jsx("span", {
3908
+ className: "tw:text-xs tw:font-normal tw:text-muted-foreground",
3909
+ children: description
3910
+ })]
3911
+ }),
3912
+ trailing !== void 0 && /* @__PURE__ */ jsx("span", {
3913
+ className: "tw:ml-auto tw:shrink-0",
3914
+ children: trailing
3915
+ })
3916
+ ]
2198
3917
  });
2199
3918
  };
2200
3919
  //#endregion
@@ -2223,7 +3942,128 @@ const ThinkingIndicator = ({ label = "Thinking..." }) => {
2223
3942
  });
2224
3943
  };
2225
3944
  //#endregion
2226
- //#region src/blocks/AIKit/ToolCall/ToolCallApproval.tsx
3945
+ //#region src/blocks/AIKit/ToolCall/ToolCallApprovalActions.tsx
3946
+ const ShortcutHint = ({ shortcutKey }) => /* @__PURE__ */ jsxs(Kbd.Group, {
3947
+ separator: "+",
3948
+ children: [shortcutKey === "mod-enter" && /* @__PURE__ */ jsx(Kbd, {
3949
+ variant: "primary",
3950
+ children: keys.mod.symbol
3951
+ }), /* @__PURE__ */ jsx(Kbd, {
3952
+ variant: "primary",
3953
+ children: keys.enter.symbol
3954
+ })]
3955
+ });
3956
+ const BUTTON_CLASS = "tw:shrink-0 tw:whitespace-nowrap";
3957
+ const ToolCallApprovalActions = ({ onApprove, onReject, extraAction, approveLabel, rejectLabel, approveVariant, pendingAction, isDisabled, shortcutKey }) => {
3958
+ const isLocked = isDisabled || pendingAction != null;
3959
+ const approveButton = onApprove && /* @__PURE__ */ jsx(Button, {
3960
+ "data-testid": "tool-approve-button",
3961
+ variant: approveVariant,
3962
+ size: "small",
3963
+ className: BUTTON_CLASS,
3964
+ onClick: onApprove,
3965
+ disabled: isLocked,
3966
+ isLoading: pendingAction === "approve",
3967
+ children: approveLabel
3968
+ });
3969
+ return /* @__PURE__ */ jsxs("div", {
3970
+ className: "tw:flex tw:items-center tw:justify-end tw:gap-2 tw:overflow-x-auto",
3971
+ children: [
3972
+ onReject && /* @__PURE__ */ jsx(Button, {
3973
+ "data-testid": "tool-decline-button",
3974
+ variant: "outline",
3975
+ size: "small",
3976
+ className: BUTTON_CLASS,
3977
+ onClick: onReject,
3978
+ disabled: isLocked,
3979
+ isLoading: pendingAction === "reject",
3980
+ children: rejectLabel
3981
+ }),
3982
+ extraAction && /* @__PURE__ */ jsx(Button, {
3983
+ "data-testid": "tool-extra-action-button",
3984
+ variant: "outline",
3985
+ size: "small",
3986
+ className: BUTTON_CLASS,
3987
+ onClick: extraAction.onClick,
3988
+ disabled: isLocked,
3989
+ isLoading: pendingAction === "extra",
3990
+ children: extraAction.label
3991
+ }),
3992
+ approveButton && shortcutKey !== "none" ? /* @__PURE__ */ jsx(Tooltip, {
3993
+ tooltip: /* @__PURE__ */ jsx(ShortcutHint, { shortcutKey }),
3994
+ placement: "top",
3995
+ triggerAsChild: true,
3996
+ children: approveButton
3997
+ }) : approveButton
3998
+ ]
3999
+ });
4000
+ };
4001
+ //#endregion
4002
+ //#region src/blocks/AIKit/ToolCall/ToolCallApprovalNotice.tsx
4003
+ const TONE = {
4004
+ warning: {
4005
+ icon: TriangleAlert,
4006
+ className: "tw:bg-warning/10 tw:text-warning"
4007
+ },
4008
+ destructive: {
4009
+ icon: CircleAlert,
4010
+ className: "tw:bg-destructive/10 tw:text-destructive"
4011
+ },
4012
+ info: {
4013
+ icon: Info,
4014
+ className: "tw:bg-info/10 tw:text-info"
4015
+ }
4016
+ };
4017
+ const ToolCallApprovalNotice = ({ tone, content, icon }) => {
4018
+ const Icon = icon ?? TONE[tone].icon;
4019
+ return /* @__PURE__ */ jsxs("p", {
4020
+ "data-testid": "tool-call-approval-notice",
4021
+ "data-tone": tone,
4022
+ className: cn("tw:m-0 tw:flex tw:items-start tw:gap-2 tw:rounded-md tw:px-2.5 tw:py-1.5 tw:text-sm", TONE[tone].className),
4023
+ children: [/* @__PURE__ */ jsx(Icon, {
4024
+ className: "tw:mt-0.5 tw:size-4 tw:shrink-0",
4025
+ "aria-hidden": true
4026
+ }), /* @__PURE__ */ jsx("span", {
4027
+ className: "tw:min-w-0 tw:text-foreground",
4028
+ children: content
4029
+ })]
4030
+ });
4031
+ };
4032
+ //#endregion
4033
+ //#region src/blocks/AIKit/ToolCall/ToolCallApprovalRecord.tsx
4034
+ const ToolCallApprovalRecord = ({ status, label, subject, resolvedBy, resolvedAt, className }) => {
4035
+ const isApproved = status === "approved";
4036
+ const Icon = isApproved ? CircleCheck : CircleX;
4037
+ return /* @__PURE__ */ jsxs("p", {
4038
+ "data-testid": "tool-call-approval",
4039
+ "data-status": status,
4040
+ "data-density": "record",
4041
+ className: cn("tw:m-0 tw:flex tw:min-w-0 tw:items-center tw:gap-1.5 tw:text-xs tw:text-muted-foreground", className),
4042
+ children: [/* @__PURE__ */ jsx(Icon, {
4043
+ "aria-hidden": true,
4044
+ className: cn("tw:size-3.5 tw:shrink-0", isApproved ? "tw:text-success" : "tw:opacity-70")
4045
+ }), /* @__PURE__ */ jsxs("span", {
4046
+ className: "tw:flex tw:min-w-0 tw:flex-wrap tw:items-baseline tw:gap-x-1.5",
4047
+ children: [
4048
+ /* @__PURE__ */ jsx("span", {
4049
+ className: "tw:font-medium tw:text-foreground",
4050
+ children: label
4051
+ }),
4052
+ subject != null && /* @__PURE__ */ jsx("span", {
4053
+ className: "tw:min-w-0 tw:truncate",
4054
+ children: subject
4055
+ }),
4056
+ resolvedBy != null && /* @__PURE__ */ jsx("span", { children: resolvedBy }),
4057
+ resolvedAt && /* @__PURE__ */ jsx("time", {
4058
+ dateTime: resolvedAt.toISOString(),
4059
+ children: resolvedAt.toLocaleString()
4060
+ })
4061
+ ]
4062
+ })]
4063
+ });
4064
+ };
4065
+ //#endregion
4066
+ //#region src/blocks/AIKit/ToolCall/toolCallApprovalStatus.ts
2227
4067
  const STATUS_BADGE = {
2228
4068
  pending: {
2229
4069
  label: "Needs approval",
@@ -2239,86 +4079,155 @@ const STATUS_BADGE = {
2239
4079
  },
2240
4080
  expired: {
2241
4081
  label: "Expired",
4082
+ color: "orange"
4083
+ },
4084
+ "timed-out": {
4085
+ label: "Timed out",
4086
+ color: "orange"
4087
+ },
4088
+ cancelled: {
4089
+ label: "Cancelled",
4090
+ color: "gray"
4091
+ },
4092
+ unattended: {
4093
+ label: "Unattended",
2242
4094
  color: "gray"
2243
4095
  }
2244
4096
  };
4097
+ //#endregion
4098
+ //#region src/blocks/AIKit/ToolCall/useApprovalShortcut.ts
4099
+ const isEditableTarget = (target) => target instanceof HTMLElement && (target.isContentEditable || target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT");
4100
+ const isModPressed = (event) => isMac ? event.metaKey : event.ctrlKey;
4101
+ const matchesShortcut = (event, shortcutKey) => {
4102
+ if (event.key !== "Enter" || event.shiftKey || event.altKey) return false;
4103
+ if (shortcutKey === "mod-enter") return isModPressed(event);
4104
+ return !event.metaKey && !event.ctrlKey && !isEditableTarget(event.target);
4105
+ };
2245
4106
  /**
2246
- * Per-tool-call approve/reject frame — the granular sibling of `PlanApproval`
2247
- * (which approves a whole plan). Chrome + status + actions only: the app
2248
- * renders the call detail as `children` and owns the SDK/permission wiring.
4107
+ * Approves on `shortcutKey` from anywhere on the page while `isEnabled`.
4108
+ *
4109
+ * - `enter` skips keystrokes typed into a field; `mod-enter` fires from one too.
4110
+ * - Ignores key-repeat, IME composition and an event another handler already took.
2249
4111
  */
2250
- const ToolCallApproval = ({ toolName, title, status, onApprove, onReject, children, actions, isSubmitting = false, enableShortcuts = false, approveLabel = "Approve", rejectLabel = "Reject", className }) => {
2251
- const isPending = status === "pending";
2252
- const hasCustomActions = actions != null;
2253
- const showActions = isPending && (isPending && !hasCustomActions && Boolean(onApprove || onReject) || hasCustomActions);
2254
- const badge = STATUS_BADGE[status];
4112
+ const useApprovalShortcut = ({ shortcutKey, isEnabled, onApprove }) => {
2255
4113
  useEffect(() => {
2256
- if (!enableShortcuts || !isPending || isSubmitting) return;
2257
- const isEditableTarget = (target) => {
2258
- if (!(target instanceof HTMLElement)) return false;
2259
- return target.isContentEditable || target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT";
2260
- };
4114
+ if (shortcutKey === "none" || !isEnabled || !onApprove) return;
2261
4115
  const handleKeyDown = (event) => {
2262
- if (event.repeat || event.defaultPrevented || isEditableTarget(event.target)) return;
2263
- if (event.key === "Enter" && !event.shiftKey && !event.ctrlKey && !event.metaKey && onApprove) {
2264
- event.preventDefault();
2265
- onApprove();
2266
- } else if (event.key === "Escape" && onReject) {
2267
- event.preventDefault();
2268
- onReject();
2269
- }
4116
+ if (event.repeat || event.isComposing || event.defaultPrevented) return;
4117
+ if (!matchesShortcut(event, shortcutKey)) return;
4118
+ event.preventDefault();
4119
+ onApprove();
2270
4120
  };
2271
4121
  window.addEventListener("keydown", handleKeyDown);
2272
4122
  return () => window.removeEventListener("keydown", handleKeyDown);
2273
4123
  }, [
2274
- enableShortcuts,
2275
- isPending,
2276
- isSubmitting,
2277
- onApprove,
2278
- onReject
4124
+ shortcutKey,
4125
+ isEnabled,
4126
+ onApprove
2279
4127
  ]);
4128
+ };
4129
+ //#endregion
4130
+ //#region src/blocks/AIKit/ToolCall/ToolCallApproval.tsx
4131
+ /**
4132
+ * Per-tool-call approve/reject frame — the granular sibling of `PlanApproval`.
4133
+ *
4134
+ * - Chrome, status and actions only: the app renders the call detail and owns the permission wiring.
4135
+ * - Actions render only while `pending`.
4136
+ */
4137
+ const ToolCallApproval = ({ status, statusLabel, statusMessage, density = "card", resolvedBy, resolvedAt, toolName, eyebrow, title, children, notices = [], aside, error, onApprove, onReject, extraAction, approveLabel = "Approve", rejectLabel = "Reject", approveVariant = "primary", pendingAction = null, isDisabled = false, shortcutKey = "none", isShortcutHintOnly = false, className }) => {
4138
+ const isPending = status === "pending";
4139
+ const badge = STATUS_BADGE[status];
4140
+ const label = statusLabel ?? badge.label;
4141
+ const showActions = isPending && Boolean(onApprove || onReject || extraAction);
4142
+ useApprovalShortcut({
4143
+ shortcutKey: isShortcutHintOnly ? "none" : shortcutKey,
4144
+ isEnabled: isPending && !isDisabled && pendingAction == null,
4145
+ onApprove
4146
+ });
4147
+ if (density === "record" && !isPending) return /* @__PURE__ */ jsx(ToolCallApprovalRecord, {
4148
+ status,
4149
+ label,
4150
+ subject: title ?? toolName,
4151
+ resolvedBy,
4152
+ resolvedAt,
4153
+ className
4154
+ });
4155
+ const showBadge = !isPending || eyebrow == null;
4156
+ const hasHeadline = showBadge || toolName != null;
2280
4157
  return /* @__PURE__ */ jsxs("section", {
2281
4158
  "data-testid": "tool-call-approval",
2282
4159
  "data-status": status,
2283
- "aria-label": typeof title === "string" ? title : toolName,
2284
- className: cn("tw:flex tw:w-full tw:max-w-[800px] tw:flex-col tw:gap-3 tw:overflow-hidden tw:rounded-xl tw:border tw:border-solid tw:border-border tw:bg-muted tw:p-4", className),
2285
- children: [
2286
- /* @__PURE__ */ jsxs("div", {
2287
- className: "tw:flex tw:items-center tw:gap-2",
2288
- children: [/* @__PURE__ */ jsx(Badge, {
2289
- color: badge.color,
2290
- variant: "pill",
2291
- className: "tw:normal-case tw:tracking-normal",
2292
- children: badge.label
2293
- }), /* @__PURE__ */ jsx("code", {
2294
- className: "tw:rounded tw:bg-background tw:px-1.5 tw:py-0.5 tw:font-mono tw:text-xs tw:text-foreground",
2295
- children: toolName
2296
- })]
2297
- }),
2298
- title != null && /* @__PURE__ */ jsx("p", {
2299
- className: "tw:m-0 tw:text-sm tw:font-medium tw:text-foreground",
2300
- children: title
2301
- }),
2302
- children,
2303
- showActions && /* @__PURE__ */ jsx("div", {
2304
- className: "tw:flex tw:items-center tw:justify-end tw:gap-2 tw:overflow-x-auto",
2305
- children: actions ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [onReject && /* @__PURE__ */ jsx(Button, {
2306
- variant: "outline",
2307
- size: "small",
2308
- onClick: onReject,
2309
- disabled: isSubmitting,
2310
- className: "tw:shrink-0 tw:whitespace-nowrap",
2311
- children: rejectLabel
2312
- }), onApprove && /* @__PURE__ */ jsx(Button, {
2313
- variant: "primary",
2314
- size: "small",
2315
- onClick: onApprove,
2316
- disabled: isSubmitting,
2317
- className: "tw:shrink-0 tw:whitespace-nowrap",
2318
- children: approveLabel
2319
- })] })
2320
- })
2321
- ]
4160
+ "data-density": "card",
4161
+ "aria-label": typeof title === "string" ? title : toolName ?? label,
4162
+ className: cn("tw:flex tw:w-full tw:max-w-[800px] tw:flex-col tw:overflow-hidden tw:rounded-lg tw:border tw:border-solid tw:border-border tw:bg-card tw:text-card-foreground", className),
4163
+ children: [eyebrow, /* @__PURE__ */ jsxs("div", {
4164
+ className: "tw:flex tw:flex-col tw:gap-3 tw:p-3",
4165
+ children: [
4166
+ (hasHeadline || title != null || !isPending && statusMessage != null) && /* @__PURE__ */ jsxs("div", {
4167
+ className: "tw:flex tw:flex-col tw:gap-1.5",
4168
+ children: [
4169
+ hasHeadline && /* @__PURE__ */ jsxs("div", {
4170
+ className: "tw:flex tw:flex-wrap tw:items-center tw:gap-2",
4171
+ children: [showBadge && /* @__PURE__ */ jsx(Badge, {
4172
+ color: badge.color,
4173
+ variant: "pill",
4174
+ children: label
4175
+ }), toolName != null && /* @__PURE__ */ jsx("code", {
4176
+ className: "tw:rounded tw:bg-muted tw:px-1.5 tw:py-0.5 tw:font-mono tw:text-xs tw:text-foreground",
4177
+ children: toolName
4178
+ })]
4179
+ }),
4180
+ title != null && /* @__PURE__ */ jsx("p", {
4181
+ className: "tw:m-0 tw:text-sm tw:font-medium tw:text-foreground",
4182
+ children: title
4183
+ }),
4184
+ !isPending && statusMessage != null && /* @__PURE__ */ jsx("p", {
4185
+ "data-testid": "tool-call-approval-status-message",
4186
+ className: "tw:m-0 tw:text-sm tw:text-muted-foreground",
4187
+ children: statusMessage
4188
+ }),
4189
+ (resolvedBy != null || resolvedAt) && !isPending && /* @__PURE__ */ jsxs("p", {
4190
+ className: "tw:m-0 tw:flex tw:flex-wrap tw:gap-x-1.5 tw:text-xs tw:text-muted-foreground",
4191
+ children: [resolvedBy != null && /* @__PURE__ */ jsx("span", { children: resolvedBy }), resolvedAt && /* @__PURE__ */ jsx("time", {
4192
+ dateTime: resolvedAt.toISOString(),
4193
+ children: resolvedAt.toLocaleString()
4194
+ })]
4195
+ })
4196
+ ]
4197
+ }),
4198
+ notices.map((notice, index) => /* @__PURE__ */ jsx(ToolCallApprovalNotice, { ...notice }, index)),
4199
+ (children != null || aside != null) && /* @__PURE__ */ jsx("div", {
4200
+ className: "tw:@container",
4201
+ children: /* @__PURE__ */ jsxs("div", {
4202
+ className: "tw:flex tw:flex-col tw:gap-3 tw:@xl:flex-row",
4203
+ children: [children != null && /* @__PURE__ */ jsx("div", {
4204
+ className: "tw:min-w-0 tw:flex-1",
4205
+ children
4206
+ }), aside != null && /* @__PURE__ */ jsx("div", {
4207
+ "data-testid": "tool-call-approval-aside",
4208
+ className: "tw:min-w-0 tw:@xl:w-1/2 tw:@xl:shrink-0",
4209
+ children: aside
4210
+ })]
4211
+ })
4212
+ }),
4213
+ error != null && /* @__PURE__ */ jsx("div", {
4214
+ role: "alert",
4215
+ className: "tw:rounded-md tw:bg-destructive/10 tw:p-2 tw:text-sm tw:text-destructive tw:whitespace-pre-wrap tw:break-words",
4216
+ children: error
4217
+ }),
4218
+ showActions && /* @__PURE__ */ jsx(ToolCallApprovalActions, {
4219
+ onApprove,
4220
+ onReject,
4221
+ extraAction,
4222
+ approveLabel,
4223
+ rejectLabel,
4224
+ approveVariant,
4225
+ pendingAction,
4226
+ isDisabled,
4227
+ shortcutKey
4228
+ })
4229
+ ]
4230
+ })]
2322
4231
  });
2323
4232
  };
2324
4233
  //#endregion
@@ -2527,6 +4436,6 @@ const UsageStats = ({ value, alert = false, children, className, ariaLabel, alig
2527
4436
  });
2528
4437
  };
2529
4438
  //#endregion
2530
- export { AnswerActions, AnswerSummary, ChatInlineIcon, CollapsibleStepHeader, DIAGRAM_LANGUAGES, FileDownloadChip, InlineStepDetail, InlineStepGroup, InlineStepLabel, Markdown, MarkdownTable, MessageBubble, PROMPT_INPUT_CONTAINER, PlanApproval, PlanRewritingIndicator, PromptInput, Question, QuestionBlock, SuggestedPrompt, ThinkingIndicator, ToolCallApproval, ToolCallChip, ToolCallStatusHeader, UsageBar, UsageStats, UsageStatsRing, clampPercentage, findMentionToken, findSlashToken, formatPercentage, getChatInlineIconName };
4439
+ export { ANSWER_SOURCES_VISIBLE, AnswerActions, AnswerSources, AnswerSummary, AttachmentChip, CONTEXT_CHIP_EXPANDABLE_LENGTH, ChatInlineIcon, ChatNotice, CollapsibleStepHeader, ContextChip, DIAGRAM_LANGUAGES, FileDownloadChip, FileGallery, InlineStepDetail, InlineStepGroup, InlineStepLabel, JumpToLatest, Markdown, MarkdownTable, MessageBubble, ModeChip, PROMPT_INPUT_CONTAINER, PlanApproval, PlanRewritingIndicator, PromptInput, Question, QuestionBlock, SourceChip, SuggestedPrompt, ThinkingIndicator, ToolCallApproval, ToolCallChip, ToolCallStatusHeader, UsageBar, UsageStats, UsageStatsRing, clampPercentage, findMentionToken, findSlashToken, formatPercentage, getChatInlineIconName, splitSealedBlocks, withholdOpenTail };
2531
4440
 
2532
- //# sourceMappingURL=AIKit-C5FbKjwa.js.map
4441
+ //# sourceMappingURL=AIKit-CaAW4GLx.js.map