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