@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.
- package/AGENTS.md +16 -24
- package/CHANGELOG.md +194 -0
- package/COMPONENT-API.md +109 -105
- package/dist/{AIKit-C5FbKjwa.js → AIKit-CaAW4GLx.js} +2216 -307
- package/dist/AIKit-CaAW4GLx.js.map +1 -0
- package/dist/{Alert-DuAGeYyM.js → Alert-Chz6KZ7e.js} +11 -20
- package/dist/Alert-Chz6KZ7e.js.map +1 -0
- package/dist/{AlertModal-BDle9ZVQ.js → AlertModal-D4AtpvLS.js} +15 -35
- package/dist/AlertModal-D4AtpvLS.js.map +1 -0
- package/dist/{AppShell-B03fj1j7.js → AppShell-C8V1KRr_.js} +2 -2
- package/dist/{AppShell-B03fj1j7.js.map → AppShell-C8V1KRr_.js.map} +1 -1
- package/dist/{AutoSaveTextarea-jiqNeeE4.js → AutoSaveTextarea-BbMhC5Aq.js} +3 -3
- package/dist/AutoSaveTextarea-BbMhC5Aq.js.map +1 -0
- package/dist/{Badge-Ba3epgcI.js → Badge-5GHIsgRS.js} +14 -3
- package/dist/Badge-5GHIsgRS.js.map +1 -0
- package/dist/{BrandEditor-Dq168RJc.js → BrandEditor-CTGtQUFZ.js} +8 -10
- package/dist/BrandEditor-CTGtQUFZ.js.map +1 -0
- package/dist/{BrandLogo-BfITD0im.js → BrandLogo-DkESE19K.js} +2 -2
- package/dist/{BrandLogo-BfITD0im.js.map → BrandLogo-DkESE19K.js.map} +1 -1
- package/dist/{Breadcrumb-CUyYu5Bi.js → Breadcrumb-B2YSNON4.js} +55 -13
- package/dist/Breadcrumb-B2YSNON4.js.map +1 -0
- package/dist/{Button-D2JqBAq7.js → Button-BNkyYU8u.js} +9 -185
- package/dist/Button-BNkyYU8u.js.map +1 -0
- package/dist/{Card-Bpc_ZlL4.js → Card-DxjGiqH8.js} +11 -18
- package/dist/Card-DxjGiqH8.js.map +1 -0
- package/dist/{Checkbox-D9rafoKh.js → Checkbox-CHDDJyAA.js} +20 -45
- package/dist/Checkbox-CHDDJyAA.js.map +1 -0
- package/dist/{Clipboard-BHWUodAx.js → Clipboard-DlwVZS1P.js} +2 -2
- package/dist/{Clipboard-BHWUodAx.js.map → Clipboard-DlwVZS1P.js.map} +1 -1
- package/dist/{ClipboardButton-Urg-kZbh.js → ClipboardButton-B1Hr_n_Z.js} +3 -3
- package/dist/{ClipboardButton-Urg-kZbh.js.map → ClipboardButton-B1Hr_n_Z.js.map} +1 -1
- package/dist/{CodeEditor-Y0iE-SwV.js → CodeEditor-DVSPdfdo.js} +24 -31
- package/dist/CodeEditor-DVSPdfdo.js.map +1 -0
- package/dist/{ColorInput-D1nC0X6i.js → ColorInput-B_eZIu39.js} +5 -7
- package/dist/ColorInput-B_eZIu39.js.map +1 -0
- package/dist/{ColorModeSwitch-Cxee8jnB.js → ColorModeSwitch-B6UEzG0H.js} +3 -3
- package/dist/{ColorModeSwitch-Cxee8jnB.js.map → ColorModeSwitch-B6UEzG0H.js.map} +1 -1
- package/dist/{Combobox-B1Kjoy5J.js → Combobox-CaKqgstG.js} +250 -56
- package/dist/Combobox-CaKqgstG.js.map +1 -0
- package/dist/{Command-BuOA3V18.js → Command-DhXmqFLT.js} +51 -33
- package/dist/Command-DhXmqFLT.js.map +1 -0
- package/dist/{CopyField-DsOVtV2Q.js → CopyField-D0Pg4Z0R.js} +2 -2
- package/dist/{CopyField-DsOVtV2Q.js.map → CopyField-D0Pg4Z0R.js.map} +1 -1
- package/dist/{DateInput-BlKkequE.js → DateInput-BgbMDJkm.js} +5 -7
- package/dist/DateInput-BgbMDJkm.js.map +1 -0
- package/dist/{DatePicker-CgHAu9KX.js → DatePicker-BXpDRo-R.js} +2 -2
- package/dist/{DatePicker-CgHAu9KX.js.map → DatePicker-BXpDRo-R.js.map} +1 -1
- package/dist/{DateRangeInput-DOxJHw8V.js → DateRangeInput-BW0-_jV0.js} +5 -7
- package/dist/DateRangeInput-BW0-_jV0.js.map +1 -0
- package/dist/{DateRangePicker-CXpXPMoh.js → DateRangePicker-C4-DoWac.js} +7 -12
- package/dist/DateRangePicker-C4-DoWac.js.map +1 -0
- package/dist/{DropdownMenu-DtRYcKEx.js → DropdownMenu-CcVbZ_5u.js} +35 -64
- package/dist/DropdownMenu-CcVbZ_5u.js.map +1 -0
- package/dist/{FieldItem-Cf1E3rfQ.js → FieldItem-BHUqgSO1.js} +3 -3
- package/dist/{FieldItem-Cf1E3rfQ.js.map → FieldItem-BHUqgSO1.js.map} +1 -1
- package/dist/{FileUpload-D6vPG3WB.js → FileUpload-BYFONpEl.js} +3 -3
- package/dist/FileUpload-BYFONpEl.js.map +1 -0
- package/dist/{Form-CuaAn-lR.js → Form-CAofWyMI.js} +8 -8
- package/dist/{Form-CuaAn-lR.js.map → Form-CAofWyMI.js.map} +1 -1
- package/dist/{FormGroup-D4O96YPm.js → FormGroup-V5kIgQRN.js} +3 -3
- package/dist/{FormGroup-D4O96YPm.js.map → FormGroup-V5kIgQRN.js.map} +1 -1
- package/dist/{FullScreenModal-CUuAakh3.js → FullScreenModal-DAvvlGOz.js} +2 -5
- package/dist/FullScreenModal-DAvvlGOz.js.map +1 -0
- package/dist/HoverCard-Cv5rmvJq.js +26 -0
- package/dist/HoverCard-Cv5rmvJq.js.map +1 -0
- package/dist/{InlineEdit-DSMIEUUa.js → InlineEdit-D4P0SvCB.js} +5 -9
- package/dist/InlineEdit-D4P0SvCB.js.map +1 -0
- package/dist/{InputGroup-Dm014ruw.js → InputGroup-CZv_7QlM.js} +39 -146
- package/dist/InputGroup-CZv_7QlM.js.map +1 -0
- package/dist/{Kbd-Du93F3o4.js → Kbd-CflKthfu.js} +5 -14
- package/dist/Kbd-CflKthfu.js.map +1 -0
- package/dist/MermaidDiagram-Te-5gOCU.js +199 -0
- package/dist/MermaidDiagram-Te-5gOCU.js.map +1 -0
- package/dist/{Modal-DQIauzar.js → Modal-CbUz-5YH.js} +19 -37
- package/dist/Modal-CbUz-5YH.js.map +1 -0
- package/dist/{ModalTitle-CTECVgL8.js → ModalTitle-cy0Ht9VY.js} +2 -2
- package/dist/{ModalTitle-CTECVgL8.js.map → ModalTitle-cy0Ht9VY.js.map} +1 -1
- package/dist/{MultiActionButtonGroup-xKqiucAN.js → MultiActionButtonGroup-Dzig87s1.js} +3 -3
- package/dist/{MultiActionButtonGroup-xKqiucAN.js.map → MultiActionButtonGroup-Dzig87s1.js.map} +1 -1
- package/dist/{Navbar-CtVYcRFn.js → Navbar-DHUS7XiU.js} +2 -2
- package/dist/{Navbar-CtVYcRFn.js.map → Navbar-DHUS7XiU.js.map} +1 -1
- package/dist/OtpInput-CY8yCaJk.js.map +1 -1
- package/dist/{PageToolbar-B_e7HGva.js → PageToolbar-DJ5tdYla.js} +4 -4
- package/dist/{PageToolbar-B_e7HGva.js.map → PageToolbar-DJ5tdYla.js.map} +1 -1
- package/dist/{Pagination-DDp6PRqf.js → Pagination-D27x5KmH.js} +2 -2
- package/dist/{Pagination-DDp6PRqf.js.map → Pagination-D27x5KmH.js.map} +1 -1
- package/dist/{Popconfirm-BgV6a0sl.js → Popconfirm-DwLCYNaF.js} +3 -3
- package/dist/{Popconfirm-BgV6a0sl.js.map → Popconfirm-DwLCYNaF.js.map} +1 -1
- package/dist/{Popover-BU7nGOv-.js → Popover-DtUDwUVO.js} +24 -37
- package/dist/Popover-DtUDwUVO.js.map +1 -0
- package/dist/{RadioGroup-C2vk5acr.js → RadioGroup-B0TwHjBb.js} +9 -13
- package/dist/RadioGroup-B0TwHjBb.js.map +1 -0
- package/dist/{Search-CVrv3hqf.js → Search-vyZwGKMU.js} +3 -3
- package/dist/{Search-CVrv3hqf.js.map → Search-vyZwGKMU.js.map} +1 -1
- package/dist/{SearchInput-BnU6YHgT.js → SearchInput-DyYXyw3u.js} +4 -6
- package/dist/SearchInput-DyYXyw3u.js.map +1 -0
- package/dist/{SectionNav-XlhvXXdF.js → SectionNav-tLK3iF6c.js} +5 -5
- package/dist/{SectionNav-XlhvXXdF.js.map → SectionNav-tLK3iF6c.js.map} +1 -1
- package/dist/{Select-CjQgU_6K.js → Select-B22hB_-w.js} +11 -25
- package/dist/Select-B22hB_-w.js.map +1 -0
- package/dist/{Sheet-utw4_rUa.js → Sheet-0TWKTqid.js} +12 -15
- package/dist/Sheet-0TWKTqid.js.map +1 -0
- package/dist/{Sidebar-CKaxOPwY.js → Sidebar-C5hdANBU.js} +11 -11
- package/dist/Sidebar-C5hdANBU.js.map +1 -0
- package/dist/{Slider-Rcc2qVct.js → Slider-DrcsM8_G.js} +4 -4
- package/dist/Slider-DrcsM8_G.js.map +1 -0
- package/dist/{Stepper-BBPembeK.js → Stepper-CQrUPVFL.js} +2 -2
- package/dist/Stepper-CQrUPVFL.js.map +1 -0
- package/dist/Switch-DccpSpzC.js +66 -0
- package/dist/Switch-DccpSpzC.js.map +1 -0
- package/dist/{Table-CgSC-fSo.js → Table-CilEvpMq.js} +62 -3
- package/dist/Table-CilEvpMq.js.map +1 -0
- package/dist/{Tabs-C0uvkPHc.js → Tabs-Ck4uWf8U.js} +2 -2
- package/dist/{Tabs-C0uvkPHc.js.map → Tabs-Ck4uWf8U.js.map} +1 -1
- package/dist/{TextInput-B5TDDOty.js → TextInput-CR0_USaY.js} +4 -7
- package/dist/TextInput-CR0_USaY.js.map +1 -0
- package/dist/{ThemeProvider-Dt9b67sW.js → ThemeProvider-BTK7AL-T.js} +21 -8
- package/dist/ThemeProvider-BTK7AL-T.js.map +1 -0
- package/dist/{TimePicker-DyyV3nw8.js → TimePicker-CoRpiEM3.js} +2 -2
- package/dist/{TimePicker-DyyV3nw8.js.map → TimePicker-CoRpiEM3.js.map} +1 -1
- package/dist/{Toast-BGnUL4jV.js → Toast-937n7n8i.js} +2 -2
- package/dist/{Toast-BGnUL4jV.js.map → Toast-937n7n8i.js.map} +1 -1
- package/dist/ToggleAllTooltip-DEjbWFoj.js +37 -0
- package/dist/ToggleAllTooltip-DEjbWFoj.js.map +1 -0
- package/dist/{ZoomableSvg-D8sXKpoV.js → ZoomableSvg-DnFXo2L4.js} +85 -81
- package/dist/ZoomableSvg-DnFXo2L4.js.map +1 -0
- package/dist/editors.js +4 -4
- package/dist/flint.js +10 -45
- package/dist/flint.js.map +1 -1
- package/dist/highlightCode-D354tCXO.js +81 -0
- package/dist/highlightCode-D354tCXO.js.map +1 -0
- package/dist/index.d.ts +843 -443
- package/dist/index.js +62 -63
- package/dist/internal/blocks/AIKit.js +2 -2
- package/dist/internal/blocks/AppShell.js +1 -1
- package/dist/internal/blocks/AuthLayout.js +1 -1
- package/dist/internal/blocks/AutoSaveTextarea.js +1 -1
- package/dist/internal/blocks/BrandEditor.js +1 -1
- package/dist/internal/blocks/ChartPanel.js +1 -1
- package/dist/internal/blocks/ChipEditor.js +5 -9
- package/dist/internal/blocks/ChipEditor.js.map +1 -1
- package/dist/internal/blocks/ClipboardButton.js +1 -1
- package/dist/internal/blocks/ColorInput.js +1 -1
- package/dist/internal/blocks/ColorModeSwitch.js +1 -1
- package/dist/internal/blocks/Command.js +1 -1
- package/dist/internal/blocks/CopyField.js +1 -1
- package/dist/internal/blocks/DateInput.js +1 -1
- package/dist/internal/blocks/DateRangeInput.js +1 -1
- package/dist/internal/blocks/DiagramShared/DiagramSkeleton.js.map +1 -1
- package/dist/internal/blocks/EmptyConfigurationActionCard.js +1 -1
- package/dist/internal/blocks/ErrorState.js +18 -29
- package/dist/internal/blocks/ErrorState.js.map +1 -1
- package/dist/internal/blocks/ExportMenu.js +3 -3
- package/dist/internal/blocks/ExportMenu.js.map +1 -1
- package/dist/internal/blocks/Form/connectors/ComboboxField.js +2 -2
- package/dist/internal/blocks/Form/connectors/MultiComboboxField.js +1 -1
- package/dist/internal/blocks/Form.js +1 -1
- package/dist/internal/blocks/FormGroup.js +1 -1
- package/dist/internal/blocks/InlineEdit.js +1 -1
- package/dist/internal/blocks/MultiActionButtonGroup.js +1 -1
- package/dist/internal/blocks/MultiCombobox.js +202 -70
- package/dist/internal/blocks/MultiCombobox.js.map +1 -1
- package/dist/internal/blocks/NumberInput.js +2 -4
- package/dist/internal/blocks/NumberInput.js.map +1 -1
- package/dist/internal/blocks/PageToolbar.js +1 -1
- package/dist/internal/blocks/PasswordInput.js +3 -5
- package/dist/internal/blocks/PasswordInput.js.map +1 -1
- package/dist/internal/blocks/SearchInput.js +1 -1
- package/dist/internal/blocks/SearchInputWithNavigation.js +6 -7
- package/dist/internal/blocks/SearchInputWithNavigation.js.map +1 -1
- package/dist/internal/blocks/SectionNav.js +1 -1
- package/dist/internal/blocks/SplitButton.js +2 -2
- package/dist/internal/blocks/StatCard.js +1 -1
- package/dist/internal/blocks/TextInput.js +1 -1
- package/dist/internal/blocks/Textarea.js +3 -8
- package/dist/internal/blocks/Textarea.js.map +1 -1
- package/dist/internal/blocks/TimeInput.js +4 -6
- package/dist/internal/blocks/TimeInput.js.map +1 -1
- package/dist/internal/components/Alert.js +1 -1
- package/dist/internal/components/AlertModal.js +1 -1
- package/dist/internal/components/Avatar.js +1 -1
- package/dist/internal/components/Badge.js +1 -1
- package/dist/internal/components/BrandLogo.js +1 -1
- package/dist/internal/components/Breadcrumb.js +1 -1
- package/dist/internal/components/Button.js +2 -2
- package/dist/internal/components/ButtonGroup.js +1 -1
- package/dist/internal/components/ButtonGroup.js.map +1 -1
- package/dist/internal/components/Card.js +1 -1
- package/dist/internal/components/Checkbox.js +1 -1
- package/dist/internal/components/Clipboard.js +1 -1
- package/dist/internal/components/CollapsiblePanel.js +1 -1
- package/dist/internal/components/Combobox.js +1 -1
- package/dist/internal/components/DatePicker.js +1 -1
- package/dist/internal/components/DateRangePicker.js +1 -1
- package/dist/internal/components/DropdownMenu.js +1 -1
- package/dist/internal/components/FileUpload.js +1 -1
- package/dist/internal/components/FullScreenModal.js +1 -1
- package/dist/internal/components/HoverCard.js +1 -1
- package/dist/internal/components/InputGroup.js +1 -1
- package/dist/internal/components/Kbd.js +1 -1
- package/dist/internal/components/Modal.js +1 -1
- package/dist/internal/components/Navbar.js +1 -1
- package/dist/internal/components/Pagination.js +1 -1
- package/dist/internal/components/Popconfirm.js +1 -1
- package/dist/internal/components/Popover.js +1 -1
- package/dist/internal/components/RadioGroup.js +1 -1
- package/dist/internal/components/Search.js +1 -1
- package/dist/internal/components/Select.js +1 -1
- package/dist/internal/components/Sheet.js +1 -1
- package/dist/internal/components/Sidebar.js +1 -1
- package/dist/internal/components/Skeleton/Skeleton.js +2 -2
- package/dist/internal/components/Skeleton/Skeleton.js.map +1 -1
- package/dist/internal/components/Slider.js +1 -1
- package/dist/internal/components/Stepper.js +1 -1
- package/dist/internal/components/Switch.js +1 -1
- package/dist/internal/components/Table.js +1 -1
- package/dist/internal/components/Tabs.js +1 -1
- package/dist/internal/components/ThemeProvider.js +3 -4
- package/dist/internal/components/TimePicker.js +1 -1
- package/dist/internal/components/Toast.js +2 -2
- package/dist/internal/components/Toggle.js +13 -3
- package/dist/internal/components/Toggle.js.map +1 -1
- package/dist/internal/components/Tooltip.js +1 -1
- package/dist/internal/components/Truncated.js +1 -1
- package/dist/internal/components/ZoomableSvg.js +1 -1
- package/dist/internal/utils.js +1 -1
- package/dist/markdown-editor.js +16 -18
- package/dist/markdown-editor.js.map +1 -1
- package/dist/mermaid.d.ts +1 -2
- package/dist/mermaid.js +1 -1
- package/dist/rjsf.js +121 -43
- package/dist/rjsf.js.map +1 -1
- package/dist/styles-DxazJ8Mz.js +81 -0
- package/dist/styles-DxazJ8Mz.js.map +1 -0
- package/dist/styles-T8xwr0sR.js +73 -0
- package/dist/styles-T8xwr0sR.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/svgCache-Dhmbe-7i.js +26 -0
- package/dist/svgCache-Dhmbe-7i.js.map +1 -0
- package/dist/svgTransforms-Hnvev3Vs.js +38 -0
- package/dist/svgTransforms-Hnvev3Vs.js.map +1 -0
- package/dist/testing.d.ts +0 -2
- package/dist/testing.js +3 -10
- package/dist/testing.js.map +1 -1
- package/dist/{useColorSchemeState-Cw4MCXUs.js → themeScopeContext-CEVXbYvZ.js} +43 -15
- package/dist/themeScopeContext-CEVXbYvZ.js.map +1 -0
- package/dist/{useThemeAttributes-B199Regy.js → themeTokens-DCeb62ap.js} +22 -4
- package/dist/themeTokens-DCeb62ap.js.map +1 -0
- package/dist/{toast-D6Z6MOB5.js → toast-Y3cTMBW8.js} +7 -10
- package/dist/toast-Y3cTMBW8.js.map +1 -0
- package/dist/{utils-p6tjltbX.js → utils-DwC9u0tM.js} +39 -49
- package/dist/utils-DwC9u0tM.js.map +1 -0
- package/dist/vega.js +2 -2
- package/dist/vegaTheme-ISwGXvof.js +49 -0
- package/dist/vegaTheme-ISwGXvof.js.map +1 -0
- package/dist/{vegaTheme-VQMWTlBw.js → vegaTokens-DCkvpIVE.js} +36 -44
- package/dist/vegaTokens-DCkvpIVE.js.map +1 -0
- package/package.json +3 -4
- package/src/styles.css +4 -39
- package/src/theme.css +42 -0
- package/dist/AIKit-C5FbKjwa.js.map +0 -1
- package/dist/Alert-DuAGeYyM.js.map +0 -1
- package/dist/AlertModal-BDle9ZVQ.js.map +0 -1
- package/dist/AutoSaveTextarea-jiqNeeE4.js.map +0 -1
- package/dist/Badge-Ba3epgcI.js.map +0 -1
- package/dist/BrandEditor-Dq168RJc.js.map +0 -1
- package/dist/Breadcrumb-CUyYu5Bi.js.map +0 -1
- package/dist/Button-D2JqBAq7.js.map +0 -1
- package/dist/Card-Bpc_ZlL4.js.map +0 -1
- package/dist/Checkbox-D9rafoKh.js.map +0 -1
- package/dist/CodeEditor-Y0iE-SwV.js.map +0 -1
- package/dist/ColorInput-D1nC0X6i.js.map +0 -1
- package/dist/Combobox-B1Kjoy5J.js.map +0 -1
- package/dist/Command-BuOA3V18.js.map +0 -1
- package/dist/DateInput-BlKkequE.js.map +0 -1
- package/dist/DateRangeInput-DOxJHw8V.js.map +0 -1
- package/dist/DateRangePicker-CXpXPMoh.js.map +0 -1
- package/dist/DiagramSource-jTwmmi9g.js +0 -11
- package/dist/DiagramSource-jTwmmi9g.js.map +0 -1
- package/dist/DropdownMenu-DtRYcKEx.js.map +0 -1
- package/dist/FileUpload-D6vPG3WB.js.map +0 -1
- package/dist/FullScreenModal-CUuAakh3.js.map +0 -1
- package/dist/HoverCard-Cy6nhg4u.js +0 -31
- package/dist/HoverCard-Cy6nhg4u.js.map +0 -1
- package/dist/InlineEdit-DSMIEUUa.js.map +0 -1
- package/dist/InputGroup-Dm014ruw.js.map +0 -1
- package/dist/Kbd-Du93F3o4.js.map +0 -1
- package/dist/MermaidDiagram-D2ThT8Q-.js +0 -302
- package/dist/MermaidDiagram-D2ThT8Q-.js.map +0 -1
- package/dist/Modal-DQIauzar.js.map +0 -1
- package/dist/Popover-BU7nGOv-.js.map +0 -1
- package/dist/RadioGroup-C2vk5acr.js.map +0 -1
- package/dist/SearchInput-BnU6YHgT.js.map +0 -1
- package/dist/Select-CjQgU_6K.js.map +0 -1
- package/dist/Sheet-utw4_rUa.js.map +0 -1
- package/dist/Sidebar-CKaxOPwY.js.map +0 -1
- package/dist/Slider-Rcc2qVct.js.map +0 -1
- package/dist/Stepper-BBPembeK.js.map +0 -1
- package/dist/Switch-Dw4tvQls.js +0 -201
- package/dist/Switch-Dw4tvQls.js.map +0 -1
- package/dist/Table-CgSC-fSo.js.map +0 -1
- package/dist/TextInput-B5TDDOty.js.map +0 -1
- package/dist/ThemeProvider-Dt9b67sW.js.map +0 -1
- package/dist/ZoomableSvg-D8sXKpoV.js.map +0 -1
- package/dist/context-U293r3v-.js +0 -29
- package/dist/context-U293r3v-.js.map +0 -1
- package/dist/defaults-i_xG2F_1.js +0 -32
- package/dist/defaults-i_xG2F_1.js.map +0 -1
- package/dist/styles-BDA5rQz3.js +0 -127
- package/dist/styles-BDA5rQz3.js.map +0 -1
- package/dist/styles-YQdM7G8c.js +0 -159
- package/dist/styles-YQdM7G8c.js.map +0 -1
- package/dist/svgTransforms-Dw2hRA-i.js +0 -51
- package/dist/svgTransforms-Dw2hRA-i.js.map +0 -1
- package/dist/themeTokens-7DEab0kp.js +0 -31
- package/dist/themeTokens-7DEab0kp.js.map +0 -1
- package/dist/toast-D6Z6MOB5.js.map +0 -1
- package/dist/useColorSchemeState-Cw4MCXUs.js.map +0 -1
- package/dist/useThemeAttributes-B199Regy.js.map +0 -1
- package/dist/utils-p6tjltbX.js.map +0 -1
- 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-
|
|
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-
|
|
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-
|
|
7
|
-
import { FullScreenModal, FullScreenModalBody, FullScreenModalHeader } from "./FullScreenModal-
|
|
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 {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
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-
|
|
15
|
-
import {
|
|
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 "./
|
|
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: [
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
className: "tw:
|
|
192
|
-
children:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
|
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
|
|
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__ */
|
|
452
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
229
453
|
className: "tw:flex tw:flex-col tw:gap-4 tw:pt-4 tw:pl-5",
|
|
230
|
-
children:
|
|
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/
|
|
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
|
-
*
|
|
490
|
+
* A file attached to a message being composed.
|
|
245
491
|
*
|
|
246
|
-
* -
|
|
247
|
-
* -
|
|
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
|
-
* -
|
|
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 }) =>
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: [
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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, {
|
|
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
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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
|
|
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
|
|
656
|
-
|
|
657
|
-
|
|
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
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
},
|
|
691
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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:
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
className:
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
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
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
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: [
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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: [
|
|
1719
|
-
|
|
1720
|
-
|
|
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
|
-
|
|
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__ */
|
|
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
|
-
|
|
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:
|
|
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:
|
|
2117
|
-
|
|
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: [
|
|
2195
|
-
className: "tw:
|
|
2196
|
-
|
|
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/
|
|
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
|
-
*
|
|
2247
|
-
*
|
|
2248
|
-
*
|
|
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
|
|
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 (
|
|
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.
|
|
2263
|
-
if (
|
|
2264
|
-
|
|
2265
|
-
|
|
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
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
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
|
-
"
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
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-
|
|
4441
|
+
//# sourceMappingURL=AIKit-CaAW4GLx.js.map
|