@signalflare-ai/ui 0.5.0 → 1.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/CHANGELOG.md +117 -1
- package/ai/USAGE.md +64 -0
- package/ai/component-registry.json +492 -618
- package/ai/component-registry.md +167 -85
- package/ai/schemas.ts +545 -102
- package/bin/sf.js +2 -3
- package/dist/.build-complete +1 -1
- package/dist/ai/schemas.d.ts +1659 -5532
- package/dist/ai/schemas.d.ts.map +1 -1
- package/dist/{ai-actions-DG1dhDMP.js → ai-actions-DSVeQn4e.js} +1 -1
- package/dist/{ai-actions-DG1dhDMP.js.map → ai-actions-DSVeQn4e.js.map} +1 -1
- package/dist/{ai-agent-card-BbtL4NII.js → ai-agent-card-BXHwhWAU.js} +1 -1
- package/dist/{ai-agent-card-BbtL4NII.js.map → ai-agent-card-BXHwhWAU.js.map} +1 -1
- package/dist/{ai-approval-Mb7-BY6i.js → ai-approval-aa0qvjFN.js} +1 -1
- package/dist/{ai-approval-Mb7-BY6i.js.map → ai-approval-aa0qvjFN.js.map} +1 -1
- package/dist/{ai-code-block-BI_z0UVR.js → ai-code-block-BgtIxtZZ.js} +1 -1
- package/dist/{ai-code-block-BI_z0UVR.js.map → ai-code-block-BgtIxtZZ.js.map} +1 -1
- package/dist/{ai-conversation-DYtExcrw.js → ai-conversation-CArP7C8K.js} +1 -1
- package/dist/{ai-conversation-DYtExcrw.js.map → ai-conversation-CArP7C8K.js.map} +1 -1
- package/dist/{ai-info-banner-BpzauUAY.js → ai-info-banner-uFxHHwBA.js} +1 -1
- package/dist/{ai-info-banner-BpzauUAY.js.map → ai-info-banner-uFxHHwBA.js.map} +1 -1
- package/dist/{ai-message-CV8SBoHM.js → ai-message-BjnFznXy.js} +1 -1
- package/dist/{ai-message-CV8SBoHM.js.map → ai-message-BjnFznXy.js.map} +1 -1
- package/dist/{ai-mission-header-ByYkJ6YP.js → ai-mission-header-08__gULL.js} +1 -1
- package/dist/{ai-mission-header-ByYkJ6YP.js.map → ai-mission-header-08__gULL.js.map} +1 -1
- package/dist/ai-part-group-DBtgTgAn.js +277 -0
- package/dist/ai-part-group-DBtgTgAn.js.map +1 -0
- package/dist/ai-prompt-input-Dy1LfxPk.js +1541 -0
- package/dist/ai-prompt-input-Dy1LfxPk.js.map +1 -0
- package/dist/{ai-question-Dp1g9k2o.js → ai-question-CHHoDJMg.js} +1 -1
- package/dist/{ai-question-Dp1g9k2o.js.map → ai-question-CHHoDJMg.js.map} +1 -1
- package/dist/{ai-reasoning-UAmNx_LD.js → ai-reasoning-CnL6ZSr5.js} +84 -5
- package/dist/ai-reasoning-CnL6ZSr5.js.map +1 -0
- package/dist/{ai-response-BWoVsNQG.js → ai-response-BEUg3xvd.js} +13 -16
- package/dist/ai-response-BEUg3xvd.js.map +1 -0
- package/dist/{ai-shimmer-BpOmfonu.js → ai-shimmer-By5_L05p.js} +1 -1
- package/dist/{ai-shimmer-BpOmfonu.js.map → ai-shimmer-By5_L05p.js.map} +1 -1
- package/dist/{ai-status-badge-WhbKVeqn.js → ai-status-badge-BGYGWYF6.js} +1 -1
- package/dist/{ai-status-badge-WhbKVeqn.js.map → ai-status-badge-BGYGWYF6.js.map} +1 -1
- package/dist/{ai-streaming-text-ClL7FwvD.js → ai-streaming-text-CMfoThV0.js} +1 -1
- package/dist/{ai-streaming-text-ClL7FwvD.js.map → ai-streaming-text-CMfoThV0.js.map} +1 -1
- package/dist/{ai-subagent-BruGN1UE.js → ai-subagent-DcPRqkAA.js} +1 -1
- package/dist/{ai-subagent-BruGN1UE.js.map → ai-subagent-DcPRqkAA.js.map} +1 -1
- package/dist/{ai-suggestion-CNsCZj5P.js → ai-suggestion-MgeCg5Ar.js} +1 -1
- package/dist/{ai-suggestion-CNsCZj5P.js.map → ai-suggestion-MgeCg5Ar.js.map} +1 -1
- package/dist/{ai-task-list-B9CpMDYN.js → ai-task-list-Da9zIm00.js} +9 -12
- package/dist/ai-task-list-Da9zIm00.js.map +1 -0
- package/dist/{ai-timeline-Bb5ntsr3.js → ai-timeline-Cwu045IR.js} +1 -1
- package/dist/ai-timeline-Cwu045IR.js.map +1 -0
- package/dist/{ai-tool-BGH8nQ_D.js → ai-tool-Cn1O4xjP.js} +168 -11
- package/dist/ai-tool-Cn1O4xjP.js.map +1 -0
- package/dist/{ai-usage-bar-BI-p-JBk.js → ai-usage-bar-DjS12DMp.js} +1 -1
- package/dist/{ai-usage-bar-BI-p-JBk.js.map → ai-usage-bar-DjS12DMp.js.map} +1 -1
- package/dist/catalog.js +3 -3
- package/dist/catalog.js.map +1 -1
- package/dist/{chart-Bes4MN3C.js → chart-BK3sVPnD.js} +442 -248
- package/dist/chart-BK3sVPnD.js.map +1 -0
- package/dist/{checkbox-CPX7lBaU.js → checkbox-DYhUmZNw.js} +4 -4
- package/dist/checkbox-DYhUmZNw.js.map +1 -0
- package/dist/{clipboard-text-92YeCybc.js → clipboard-text-ssybngLw.js} +2 -2
- package/dist/{clipboard-text-92YeCybc.js.map → clipboard-text-ssybngLw.js.map} +1 -1
- package/dist/{code-DE1Yy1Cu.js → code-Cx-QSoOT.js} +2 -2
- package/dist/{code-DE1Yy1Cu.js.map → code-Cx-QSoOT.js.map} +1 -1
- package/dist/{combobox-B0bLdsX8.js → combobox-C0iW6a0r.js} +2 -2
- package/dist/{combobox-B0bLdsX8.js.map → combobox-C0iW6a0r.js.map} +1 -1
- package/dist/command-line/cli.js +22 -27
- package/dist/{command-palette-CBTY8EiF.js → command-palette-DGzioeki.js} +2 -2
- package/dist/command-palette-DGzioeki.js.map +1 -0
- package/dist/components/ai-actions.js +1 -1
- package/dist/components/ai-agent-card.js +1 -1
- package/dist/components/ai-approval.js +1 -1
- package/dist/components/ai-code-block.js +1 -1
- package/dist/components/ai-conversation.js +1 -1
- package/dist/components/ai-info-banner.js +1 -1
- package/dist/components/ai-message.js +1 -1
- package/dist/components/ai-mission-header.js +1 -1
- package/dist/components/ai-part-group.js +3 -0
- package/dist/components/ai-prompt-input.js +2 -2
- package/dist/components/ai-question.js +1 -1
- package/dist/components/ai-reasoning.js +1 -1
- package/dist/components/ai-response.js +1 -1
- package/dist/components/ai-shimmer.js +1 -1
- package/dist/components/ai-status-badge.js +1 -1
- package/dist/components/ai-streaming-text.js +1 -1
- package/dist/components/ai-subagent.js +1 -1
- package/dist/components/ai-suggestion.js +1 -1
- package/dist/components/ai-task-list.js +1 -1
- package/dist/components/ai-timeline.js +1 -1
- package/dist/components/ai-tool.js +1 -1
- package/dist/components/ai-usage-bar.js +1 -1
- package/dist/components/chart.js +3 -2
- package/dist/components/checkbox.js +1 -1
- package/dist/components/clipboard-text.js +1 -1
- package/dist/components/code.js +1 -1
- package/dist/components/combobox.js +1 -1
- package/dist/components/command-palette.js +1 -1
- package/dist/components/data-grid.js +1 -1
- package/dist/components/date-picker.js +1 -1
- package/dist/components/dropdown.js +1 -1
- package/dist/components/filters.js +1 -1
- package/dist/components/input.js +2 -2
- package/dist/components/link.js +2 -2
- package/dist/components/link.js.map +1 -1
- package/dist/components/pagination.js +1 -1
- package/dist/components/radio.js +1 -1
- package/dist/components/select.js +1 -1
- package/dist/components/sensitive-input.js +1 -1
- package/dist/components/sidebar.js +1 -1
- package/dist/components/sparkline.js +3 -0
- package/dist/components/stat-card.js +3 -0
- package/dist/components/table.js +1 -1
- package/dist/components/text-roll.js +3 -0
- package/dist/components/theme-toggle.js +1 -1
- package/dist/components/use-agent-harness.js +1 -1
- package/dist/{data-grid-UJ9ja5cu.js → data-grid-CG76N_hK.js} +6 -6
- package/dist/data-grid-CG76N_hK.js.map +1 -0
- package/dist/{date-picker-ebekkC3R.js → date-picker-Dqg9L4xu.js} +2 -2
- package/dist/{date-picker-ebekkC3R.js.map → date-picker-Dqg9L4xu.js.map} +1 -1
- package/dist/{dist-BNlyONdD.js → dist-1-gcEL2L.js} +224 -135
- package/dist/dist-1-gcEL2L.js.map +1 -0
- package/dist/{dropdown-J5T4pHaR.js → dropdown-qnEYRFXZ.js} +2 -2
- package/dist/{dropdown-J5T4pHaR.js.map → dropdown-qnEYRFXZ.js.map} +1 -1
- package/dist/echart-DURZEyai.js +314 -0
- package/dist/echart-DURZEyai.js.map +1 -0
- package/dist/{filters-BdBogf7D.js → filters-Bw_U6ZTx.js} +5 -5
- package/dist/filters-Bw_U6ZTx.js.map +1 -0
- package/dist/flow-BRsYUCJa.js.map +1 -1
- package/dist/genui.js +2 -2
- package/dist/genui.js.map +1 -1
- package/dist/index.js +45 -42
- package/dist/index.js.map +1 -1
- package/dist/{input-BxQAnXki.js → input-DXYUjGgD.js} +2 -2
- package/dist/{input-BxQAnXki.js.map → input-DXYUjGgD.js.map} +1 -1
- package/dist/{input-Cn25I4o5.js → input-DddtBN-g.js} +2 -2
- package/dist/{input-Cn25I4o5.js.map → input-DddtBN-g.js.map} +1 -1
- package/dist/layer-card-BME0eljh.js.map +1 -1
- package/dist/{pagination-C_YqCy8l.js → pagination-BVqdlONY.js} +2 -2
- package/dist/{pagination-C_YqCy8l.js.map → pagination-BVqdlONY.js.map} +1 -1
- package/dist/primitives/otp-field.js +2 -0
- package/dist/primitives.js +1 -0
- package/dist/{radio-B7zg1wUI.js → radio-BNSwOt3B.js} +3 -3
- package/dist/radio-BNSwOt3B.js.map +1 -0
- package/dist/{select-9p721G00.js → select-1w2aebGQ.js} +2 -2
- package/dist/select-1w2aebGQ.js.map +1 -0
- package/dist/{sensitive-input-D5je2NLl.js → sensitive-input-82Cez3vj.js} +2 -2
- package/dist/{sensitive-input-D5je2NLl.js.map → sensitive-input-82Cez3vj.js.map} +1 -1
- package/dist/{sidebar-DOwBrq57.js → sidebar-CAsCmSpM.js} +3 -3
- package/dist/sidebar-CAsCmSpM.js.map +1 -0
- package/dist/sparkline-DdbeM4Ai.js +108 -0
- package/dist/sparkline-DdbeM4Ai.js.map +1 -0
- package/dist/src/blocks/agent-harness/agent-harness.d.ts +30 -9
- package/dist/src/blocks/agent-harness/agent-harness.d.ts.map +1 -1
- package/dist/src/blocks/agent-harness/agent-harness.stories.tsx +114 -0
- package/dist/src/blocks/agent-harness/agent-harness.tsx +144 -63
- package/dist/src/blocks/commander/commander.stories.tsx +31 -0
- package/dist/src/blocks/dashboard-grid/dashboard-grid.d.ts +48 -0
- package/dist/src/blocks/dashboard-grid/dashboard-grid.d.ts.map +1 -0
- package/dist/src/blocks/dashboard-grid/dashboard-grid.stories.tsx +19 -0
- package/dist/src/blocks/dashboard-grid/dashboard-grid.tsx +110 -0
- package/dist/src/blocks/dashboard-grid/index.d.ts +2 -0
- package/dist/src/blocks/dashboard-grid/index.d.ts.map +1 -0
- package/dist/src/blocks/delete-resource/delete-resource.stories.tsx +31 -0
- package/dist/src/blocks/map-block/map-block.stories.tsx +41 -0
- package/dist/src/blocks/metrics-overview/index.d.ts +2 -0
- package/dist/src/blocks/metrics-overview/index.d.ts.map +1 -0
- package/dist/src/blocks/metrics-overview/metrics-overview.d.ts +67 -0
- package/dist/src/blocks/metrics-overview/metrics-overview.d.ts.map +1 -0
- package/dist/src/blocks/metrics-overview/metrics-overview.stories.tsx +26 -0
- package/dist/src/blocks/metrics-overview/metrics-overview.tsx +139 -0
- package/dist/src/blocks/page-header/page-header.stories.tsx +56 -0
- package/dist/src/blocks/resource-list/resource-list.stories.tsx +31 -0
- package/dist/src/catalog/catalog.d.ts +1 -1
- package/dist/src/catalog/index.d.ts +1 -1
- package/dist/src/catalog/types.d.ts +1 -1
- package/dist/src/command-line/build-cli.d.ts +1 -1
- package/dist/src/command-line/cli.d.ts +1 -1
- package/dist/src/command-line/commands/add.d.ts +1 -1
- package/dist/src/command-line/commands/add.d.ts.map +1 -1
- package/dist/src/command-line/commands/ai.d.ts.map +1 -1
- package/dist/src/command-line/commands/blocks.d.ts +1 -1
- package/dist/src/command-line/commands/blocks.d.ts.map +1 -1
- package/dist/src/command-line/commands/doc.d.ts +1 -1
- package/dist/src/command-line/commands/doc.d.ts.map +1 -1
- package/dist/src/command-line/commands/ls.d.ts +1 -1
- package/dist/src/command-line/commands/ls.d.ts.map +1 -1
- package/dist/src/command-line/commands/migrate.d.ts +3 -3
- package/dist/src/command-line/commands/migrate.d.ts.map +1 -1
- package/dist/src/command-line/utils/config.d.ts +1 -1
- package/dist/src/command-line/utils/transformer.d.ts +1 -1
- package/dist/src/components/ai-part-group/ai-part-group.d.ts +134 -0
- package/dist/src/components/ai-part-group/ai-part-group.d.ts.map +1 -0
- package/dist/src/components/ai-part-group/index.d.ts +2 -0
- package/dist/src/components/ai-part-group/index.d.ts.map +1 -0
- package/dist/src/components/ai-prompt-input/ai-prompt-input.d.ts +196 -7
- package/dist/src/components/ai-prompt-input/ai-prompt-input.d.ts.map +1 -1
- package/dist/src/components/ai-prompt-input/controller.d.ts +41 -0
- package/dist/src/components/ai-prompt-input/controller.d.ts.map +1 -0
- package/dist/src/components/ai-prompt-input/index.d.ts +3 -1
- package/dist/src/components/ai-prompt-input/index.d.ts.map +1 -1
- package/dist/src/components/ai-prompt-input/types.d.ts +86 -0
- package/dist/src/components/ai-prompt-input/types.d.ts.map +1 -0
- package/dist/src/components/ai-reasoning/ai-reasoning.d.ts +17 -1
- package/dist/src/components/ai-reasoning/ai-reasoning.d.ts.map +1 -1
- package/dist/src/components/ai-response/ai-response.d.ts +1 -2
- package/dist/src/components/ai-response/ai-response.d.ts.map +1 -1
- package/dist/src/components/ai-task-list/ai-task-list.d.ts.map +1 -1
- package/dist/src/components/ai-tool/ai-tool.d.ts +17 -1
- package/dist/src/components/ai-tool/ai-tool.d.ts.map +1 -1
- package/dist/src/components/chart/area-chart.d.ts +69 -0
- package/dist/src/components/chart/area-chart.d.ts.map +1 -0
- package/dist/src/components/chart/bar-chart.d.ts +60 -0
- package/dist/src/components/chart/bar-chart.d.ts.map +1 -0
- package/dist/src/components/chart/color.d.ts +56 -0
- package/dist/src/components/chart/color.d.ts.map +1 -1
- package/dist/src/components/chart/echart.d.ts.map +1 -1
- package/dist/src/components/chart/index.d.ts +5 -0
- package/dist/src/components/chart/index.d.ts.map +1 -1
- package/dist/src/components/chart/pie-chart.d.ts +64 -0
- package/dist/src/components/chart/pie-chart.d.ts.map +1 -0
- package/dist/src/components/chart/scatter-chart.d.ts +57 -0
- package/dist/src/components/chart/scatter-chart.d.ts.map +1 -0
- package/dist/src/components/chart/stacked-bar-chart.d.ts +59 -0
- package/dist/src/components/chart/stacked-bar-chart.d.ts.map +1 -0
- package/dist/src/components/chart/timeseries-chart.d.ts.map +1 -1
- package/dist/src/components/code/code.d.ts +1 -1
- package/dist/src/components/command-palette/command-palette.d.ts +1 -1
- package/dist/src/components/data-grid/data-grid.d.ts.map +1 -1
- package/dist/src/components/data-grid/index.d.ts +1 -1
- package/dist/src/components/data-grid/index.d.ts.map +1 -1
- package/dist/src/components/data-grid/types.d.ts.map +1 -1
- package/dist/src/components/date-picker/date-picker.d.ts +2 -2
- package/dist/src/components/filters/filters.d.ts.map +1 -1
- package/dist/src/components/filters/helpers.d.ts.map +1 -1
- package/dist/src/components/filters/types.d.ts.map +1 -1
- package/dist/src/components/layer-card/layer-card.d.ts +1 -1
- package/dist/src/components/link/link.d.ts +2 -2
- package/dist/src/components/radio/radio.d.ts +1 -1
- package/dist/src/components/select/select.d.ts +1 -1
- package/dist/src/components/sidebar/sidebar.d.ts +2 -2
- package/dist/src/components/sparkline/index.d.ts +2 -0
- package/dist/src/components/sparkline/index.d.ts.map +1 -0
- package/dist/src/components/sparkline/sparkline.d.ts +52 -0
- package/dist/src/components/sparkline/sparkline.d.ts.map +1 -0
- package/dist/src/components/stat-card/index.d.ts +2 -0
- package/dist/src/components/{ai-loader → stat-card}/index.d.ts.map +1 -1
- package/dist/src/components/stat-card/stat-card.d.ts +80 -0
- package/dist/src/components/stat-card/stat-card.d.ts.map +1 -0
- package/dist/src/components/text-roll/index.d.ts +2 -0
- package/dist/src/components/text-roll/index.d.ts.map +1 -0
- package/dist/src/components/text-roll/text-roll.d.ts +49 -0
- package/dist/src/components/text-roll/text-roll.d.ts.map +1 -0
- package/dist/src/components/toast/toast.d.ts.map +1 -1
- package/dist/src/components/use-agent-harness/use-agent-harness.d.ts +2 -2
- package/dist/src/genui/genui.d.ts.map +1 -1
- package/dist/src/index.d.ts +5 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/primitives/index.d.ts +1 -0
- package/dist/src/primitives/index.d.ts.map +1 -1
- package/dist/src/primitives/otp-field.d.ts +13 -0
- package/dist/src/primitives/otp-field.d.ts.map +1 -0
- package/dist/src/registry/index.d.ts +1 -1
- package/dist/src/registry/types.d.ts +1 -1
- package/dist/stat-card-CEZscNh8.js +103 -0
- package/dist/stat-card-CEZscNh8.js.map +1 -0
- package/dist/styles/sf-binding.css +20 -62
- package/dist/styles/sf-standalone.css +2 -2
- package/dist/styles/shadcn.css +120 -0
- package/dist/styles/theme-blue-tint.css +98 -0
- package/dist/styles/theme-fedramp.css +3 -12
- package/dist/styles/theme-minimal.css +26 -104
- package/dist/styles/theme-sf.css +96 -114
- package/dist/{table-CIMx0Oq0.js → table-Rv4JMy0B.js} +2 -2
- package/dist/{table-CIMx0Oq0.js.map → table-Rv4JMy0B.js.map} +1 -1
- package/dist/text-roll-BZ3I1umc.js +79 -0
- package/dist/text-roll-BZ3I1umc.js.map +1 -0
- package/dist/{theme-toggle-Dpgnoj_Q.js → theme-toggle-Bhu681D7.js} +1 -1
- package/dist/{theme-toggle-Dpgnoj_Q.js.map → theme-toggle-Bhu681D7.js.map} +1 -1
- package/dist/{use-agent-harness-DZzcn96L.js → use-agent-harness-BMyF8pTq.js} +5 -5
- package/dist/use-agent-harness-BMyF8pTq.js.map +1 -0
- package/package.json +48 -19
- package/scripts/component-registry/discovery.ts +2 -2
- package/scripts/component-registry/index.ts +1 -1
- package/scripts/component-registry/props-filter.ts +1 -1
- package/scripts/component-registry/utils.ts +5 -5
- package/scripts/component-registry/variant-parser.ts +124 -19
- package/scripts/convert-demos-to-stories.ts +253 -0
- package/scripts/css-build.ts +4 -3
- package/scripts/generate-primitives.ts +11 -3
- package/scripts/theme-generator/config.ts +339 -71
- package/scripts/theme-generator/generate-css.ts +17 -3
- package/scripts/theme-generator/index.ts +3 -3
- package/scripts/theme-generator/migrate.ts +1 -1
- package/dist/ai-loader-Cr3eQkNS.js +0 -134
- package/dist/ai-loader-Cr3eQkNS.js.map +0 -1
- package/dist/ai-prompt-input-Bo1YuJly.js +0 -769
- package/dist/ai-prompt-input-Bo1YuJly.js.map +0 -1
- package/dist/ai-reasoning-UAmNx_LD.js.map +0 -1
- package/dist/ai-response-BWoVsNQG.js.map +0 -1
- package/dist/ai-task-list-B9CpMDYN.js.map +0 -1
- package/dist/ai-timeline-Bb5ntsr3.js.map +0 -1
- package/dist/ai-tool-BGH8nQ_D.js.map +0 -1
- package/dist/chart-Bes4MN3C.js.map +0 -1
- package/dist/checkbox-CPX7lBaU.js.map +0 -1
- package/dist/command-palette-CBTY8EiF.js.map +0 -1
- package/dist/components/ai-loader.js +0 -3
- package/dist/data-grid-UJ9ja5cu.js.map +0 -1
- package/dist/dist-BNlyONdD.js.map +0 -1
- package/dist/filters-BdBogf7D.js.map +0 -1
- package/dist/radio-B7zg1wUI.js.map +0 -1
- package/dist/select-9p721G00.js.map +0 -1
- package/dist/sidebar-DOwBrq57.js.map +0 -1
- package/dist/src/components/ai-loader/ai-loader.d.ts +0 -44
- package/dist/src/components/ai-loader/ai-loader.d.ts.map +0 -1
- package/dist/src/components/ai-loader/index.d.ts +0 -2
- package/dist/styles/theme-navigator.css +0 -137
- package/dist/use-agent-harness-DZzcn96L.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ai-prompt-input-Bo1YuJly.js","names":[],"sources":["../src/components/ai-prompt-input/ai-prompt-input.tsx"],"sourcesContent":["\"use client\";\n\nimport { Menu as DropdownMenuPrimitive } from \"@base-ui/react/menu\";\nimport {\n ArrowUpIcon,\n CaretDownIcon,\n FileIcon,\n FileTextIcon,\n ImageIcon,\n MicrophoneIcon,\n PaperclipIcon,\n PlusIcon,\n SpinnerGapIcon,\n SquareIcon,\n XIcon,\n} from \"@phosphor-icons/react\";\nimport type {\n ChangeEvent,\n ChangeEventHandler,\n ClipboardEventHandler,\n ComponentProps,\n FormEvent,\n FormEventHandler,\n HTMLAttributes,\n KeyboardEventHandler,\n PropsWithChildren,\n ReactNode,\n RefObject,\n} from \"react\";\nimport {\n Fragment,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"../button\";\nimport { InputGroup } from \"../input\";\nimport { Tooltip } from \"../tooltip\";\n\n// ─── Variants ────────────────────────────────────────────────────────────────\n\nexport const SF_AI_PROMPT_INPUT_VARIANTS = {\n status: {\n idle: { classes: \"\", description: \"Ready to accept input\" },\n submitted: {\n classes: \"\",\n description: \"Waiting for response (shows spinner)\",\n },\n streaming: { classes: \"\", description: \"Streaming response (shows stop)\" },\n error: { classes: \"\", description: \"Error state (shows X)\" },\n },\n} as const;\n\nexport const SF_AI_PROMPT_INPUT_DEFAULT_VARIANTS = {\n status: \"idle\",\n} as const;\n\nexport type SFAiPromptInputStatus =\n keyof typeof SF_AI_PROMPT_INPUT_VARIANTS.status;\n\n// ─── Attachment types ─────────────────────────────────────────────────────────\n\nexport type AttachmentFile = {\n id: string;\n url: string;\n mediaType: string;\n filename: string;\n};\n\nexport type AttachmentsContext = {\n files: AttachmentFile[];\n add: (files: File[] | FileList) => void;\n remove: (id: string) => void;\n clear: () => void;\n openFileDialog: () => void;\n fileInputRef: RefObject<HTMLInputElement | null>;\n};\n\n// ─── Provider Context & Types ─────────────────────────────────────────────────\n\nexport type TextInputContext = {\n value: string;\n setInput: (v: string) => void;\n clear: () => void;\n};\n\nexport type PromptInputController = {\n textInput: TextInputContext;\n attachments: AttachmentsContext;\n /** INTERNAL: Allows PromptInput to register its file input + \"open\" callback */\n __registerFileInput: (\n ref: RefObject<HTMLInputElement | null>,\n open: () => void\n ) => void;\n};\n\nconst PromptInputContext = createContext<PromptInputController | null>(null);\nconst ProviderAttachmentsContext = createContext<AttachmentsContext | null>(\n null\n);\n\nexport const usePromptInputController = () => {\n const ctx = useContext(PromptInputContext);\n if (!ctx) {\n throw new Error(\n \"Wrap your component inside <PromptInputProvider> to use usePromptInputController().\"\n );\n }\n return ctx;\n};\n\nconst useOptionalPromptInputController = () => useContext(PromptInputContext);\n\nexport const useProviderAttachments = () => {\n const ctx = useContext(ProviderAttachmentsContext);\n if (!ctx) {\n throw new Error(\n \"Wrap your component inside <PromptInputProvider> to use useProviderAttachments().\"\n );\n }\n return ctx;\n};\n\nconst useOptionalProviderAttachments = () =>\n useContext(ProviderAttachmentsContext);\n\n// ─── PromptInputProvider ──────────────────────────────────────────────────────\n\nexport type PromptInputProviderProps = PropsWithChildren<{\n initialInput?: string;\n}>;\n\n/**\n * Optional global provider that lifts PromptInput state outside of PromptInput.\n * If you don't use it, PromptInput stays fully self-managed.\n *\n * @example\n * ```tsx\n * <PromptInputProvider>\n * <PromptInput onSubmit={handleSubmit}>...</PromptInput>\n * </PromptInputProvider>\n * ```\n */\nexport function PromptInputProvider({\n initialInput = \"\",\n children,\n}: PromptInputProviderProps) {\n const [textInput, setTextInput] = useState(initialInput);\n const clearInput = useCallback(() => setTextInput(\"\"), []);\n\n const [attachmentItems, setAttachmentItems] = useState<AttachmentFile[]>([]);\n const fileInputRef = useRef<HTMLInputElement | null>(null);\n const openRef = useRef<() => void>(() => {});\n\n const add = useCallback((files: File[] | FileList) => {\n const incoming = Array.from(files);\n if (incoming.length === 0) return;\n setAttachmentItems((prev) =>\n prev.concat(\n incoming.map((file) => ({\n id: `${Date.now()}-${Math.random()}`,\n url: URL.createObjectURL(file),\n mediaType: file.type,\n filename: file.name,\n }))\n )\n );\n }, []);\n\n const remove = useCallback((id: string) => {\n setAttachmentItems((prev) => {\n const found = prev.find((f) => f.id === id);\n if (found?.url) URL.revokeObjectURL(found.url);\n return prev.filter((f) => f.id !== id);\n });\n }, []);\n\n const clear = useCallback(() => {\n setAttachmentItems((prev) => {\n for (const f of prev) if (f.url) URL.revokeObjectURL(f.url);\n return [];\n });\n }, []);\n\n const openFileDialog = useCallback(() => {\n openRef.current?.();\n }, []);\n\n const attachments = useMemo<AttachmentsContext>(\n () => ({\n files: attachmentItems,\n add,\n remove,\n clear,\n openFileDialog,\n fileInputRef,\n }),\n [attachmentItems, add, remove, clear, openFileDialog]\n );\n\n const __registerFileInput = useCallback(\n (ref: RefObject<HTMLInputElement | null>, open: () => void) => {\n fileInputRef.current = ref.current;\n openRef.current = open;\n },\n []\n );\n\n const controller = useMemo<PromptInputController>(\n () => ({\n textInput: {\n value: textInput,\n setInput: setTextInput,\n clear: clearInput,\n },\n attachments,\n __registerFileInput,\n }),\n [textInput, clearInput, attachments, __registerFileInput]\n );\n\n return (\n <PromptInputContext.Provider value={controller}>\n <ProviderAttachmentsContext.Provider value={attachments}>\n {children}\n </ProviderAttachmentsContext.Provider>\n </PromptInputContext.Provider>\n );\n}\n\n// ─── Local Attachments Context ────────────────────────────────────────────────\n\nconst LocalAttachmentsContext = createContext<AttachmentsContext | null>(null);\n\nexport const usePromptInputAttachments = () => {\n const provider = useOptionalProviderAttachments();\n const local = useContext(LocalAttachmentsContext);\n const context = provider ?? local;\n if (!context) {\n throw new Error(\n \"usePromptInputAttachments must be used within a PromptInput or PromptInputProvider\"\n );\n }\n return context;\n};\n\n// ─── Helpers (module-level) ───────────────────────────────────────────────────\n\n/** Convert a blob URL to a base64 data URL for serialization. */\nasync function convertBlobToDataUrl(url: string): Promise<string> {\n const response = await fetch(url);\n const blob = await response.blob();\n // FileReader is callback-only — no Promise-based API exists for this\n // oxlint-disable-next-line avoid-new\n return new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.addEventListener(\"loadend\", () => resolve(reader.result as string));\n reader.addEventListener(\"error\", () =>\n reject(new Error(\"FileReader error\"))\n );\n reader.readAsDataURL(blob);\n });\n}\n\nfunction makeAttachmentId() {\n return `${Date.now()}-${Math.random()}`;\n}\n\n// ─── PromptInputMessage ───────────────────────────────────────────────────────\n\nexport type PromptInputMessage = {\n text?: string;\n files?: AttachmentFile[];\n};\n\n// ─── PromptInput ─────────────────────────────────────────────────────────────\n\nexport type PromptInputProps = Omit<\n HTMLAttributes<HTMLFormElement>,\n \"onSubmit\" | \"onError\"\n> & {\n /** Accepted file types — e.g. `\"image/*\"`. Default: any. */\n accept?: string;\n /** Allow selecting multiple files. */\n multiple?: boolean;\n /** When true, accept drops anywhere on document. Default false. */\n globalDrop?: boolean;\n /** Maximum number of attached files. */\n maxFiles?: number;\n /** Maximum file size in bytes. */\n maxFileSize?: number;\n onError?: (err: {\n code: \"max_files\" | \"max_file_size\" | \"accept\";\n message: string;\n }) => void;\n onSubmit: (\n message: PromptInputMessage,\n event: FormEvent<HTMLFormElement>\n ) => void | Promise<void>;\n /**\n * Content rendered in the collapsible back layer (e.g. HITL approvals, task\n * lists). When provided the prompt input is wrapped in a LayerCard shell with\n * a header row containing a chevron toggle.\n * Use `PromptInputBackLayer` to compose structured content for this slot.\n */\n backLayer?: ReactNode;\n /**\n * Title shown in the back layer header row. Defaults to `\"Context\"`.\n */\n backLayerTitle?: string;\n /**\n * Controls whether the back layer is visible. Pair with `onBackLayerOpenChange`\n * for a controlled pattern.\n */\n backLayerOpen?: boolean;\n /** Called when the chevron toggle changes the back layer open state. */\n onBackLayerOpenChange?: (open: boolean) => void;\n /**\n * Automatically opens the back layer when this condition is true.\n * Useful for showing pending approvals/questions without manual toggle.\n */\n autoOpenBackLayerWhen?: boolean;\n};\n\n/**\n * Prompt input form. Can be self-managed or controlled via `PromptInputProvider`.\n *\n * @example\n * ```tsx\n * <PromptInput onSubmit={({ text, files }) => send(text, files)}>\n * <PromptInputAttachments>{(f) => <PromptInputAttachment data={f} />}</PromptInputAttachments>\n * <PromptInputTextarea placeholder=\"Ask anything…\" />\n * <PromptInputToolbar>\n * <PromptInputTools>\n * <PromptInputAttachButton />\n * </PromptInputTools>\n * <PromptInputSubmit />\n * </PromptInputToolbar>\n * </PromptInput>\n * ```\n */\nexport const PromptInput = ({\n className,\n accept,\n multiple,\n globalDrop,\n maxFiles,\n maxFileSize,\n onError,\n onSubmit,\n children,\n backLayer,\n backLayerTitle = \"Context\",\n backLayerOpen,\n onBackLayerOpenChange,\n autoOpenBackLayerWhen,\n ...props\n}: PromptInputProps) => {\n const hasBackLayer = backLayer !== undefined;\n const controller = useOptionalPromptInputController();\n const usingProvider = !!controller;\n\n const inputRef = useRef<HTMLInputElement | null>(null);\n const anchorRef = useRef<HTMLSpanElement>(null);\n const formRef = useRef<HTMLFormElement | null>(null);\n\n useEffect(() => {\n const root = anchorRef.current?.closest(\"form\");\n if (root instanceof HTMLFormElement) {\n formRef.current = root;\n }\n }, []);\n\n // Auto-open back layer when condition is true\n useEffect(() => {\n if (autoOpenBackLayerWhen) {\n onBackLayerOpenChange?.(true);\n }\n }, [autoOpenBackLayerWhen, onBackLayerOpenChange]);\n\n // Local attachment state (only when no provider)\n const [items, setItems] = useState<AttachmentFile[]>([]);\n const files = usingProvider ? controller.attachments.files : items;\n\n const openFileDialogLocal = useCallback(() => {\n inputRef.current?.click();\n }, []);\n\n const matchesAccept = useCallback(\n (f: File) => {\n if (!accept || accept.trim() === \"\") return true;\n if (accept.includes(\"image/*\")) return f.type.startsWith(\"image/\");\n return true;\n },\n [accept]\n );\n\n const addLocal = useCallback(\n (fileList: File[] | FileList) => {\n const incoming = Array.from(fileList);\n const accepted = incoming.filter((f) => matchesAccept(f));\n if (incoming.length && accepted.length === 0) {\n onError?.({\n code: \"accept\",\n message: \"No files match the accepted types.\",\n });\n return;\n }\n const withinSize = (f: File) =>\n maxFileSize ? f.size <= maxFileSize : true;\n const sized = accepted.filter(withinSize);\n if (accepted.length > 0 && sized.length === 0) {\n onError?.({\n code: \"max_file_size\",\n message: \"All files exceed the maximum size.\",\n });\n return;\n }\n setItems((prev) => {\n const capacity =\n typeof maxFiles === \"number\"\n ? Math.max(0, maxFiles - prev.length)\n : undefined;\n const capped =\n typeof capacity === \"number\" ? sized.slice(0, capacity) : sized;\n if (typeof capacity === \"number\" && sized.length > capacity) {\n onError?.({\n code: \"max_files\",\n message: \"Too many files. Some were not added.\",\n });\n }\n const next: AttachmentFile[] = capped.map((file) => ({\n id: makeAttachmentId(),\n url: URL.createObjectURL(file),\n mediaType: file.type,\n filename: file.name,\n }));\n return prev.concat(next);\n });\n },\n [matchesAccept, maxFiles, maxFileSize, onError]\n );\n\n const add = usingProvider\n ? (f: File[] | FileList) => controller.attachments.add(f)\n : addLocal;\n\n const remove = usingProvider\n ? (id: string) => controller.attachments.remove(id)\n : (id: string) =>\n setItems((prev) => {\n const found = prev.find((f) => f.id === id);\n if (found?.url) URL.revokeObjectURL(found.url);\n return prev.filter((f) => f.id !== id);\n });\n\n const clear = usingProvider\n ? () => controller.attachments.clear()\n : () =>\n setItems((prev) => {\n for (const f of prev) if (f.url) URL.revokeObjectURL(f.url);\n return [];\n });\n\n const openFileDialog = usingProvider\n ? () => controller.attachments.openFileDialog()\n : openFileDialogLocal;\n\n useEffect(() => {\n if (!usingProvider) return;\n controller.__registerFileInput(inputRef, () => inputRef.current?.click());\n }, [usingProvider, controller]);\n\n // File drag-drop on the form\n useEffect(() => {\n const form = formRef.current;\n if (!form) return;\n const onDragOver = (e: DragEvent) => {\n if (e.dataTransfer?.types?.includes(\"Files\")) e.preventDefault();\n };\n const onDrop = (e: DragEvent) => {\n if (e.dataTransfer?.types?.includes(\"Files\")) e.preventDefault();\n if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {\n add(e.dataTransfer.files);\n }\n };\n form.addEventListener(\"dragover\", onDragOver);\n form.addEventListener(\"drop\", onDrop);\n return () => {\n form.removeEventListener(\"dragover\", onDragOver);\n form.removeEventListener(\"drop\", onDrop);\n };\n }, [add]);\n\n useEffect(() => {\n if (!globalDrop) return;\n const onDragOver = (e: DragEvent) => {\n if (e.dataTransfer?.types?.includes(\"Files\")) e.preventDefault();\n };\n const onDrop = (e: DragEvent) => {\n if (e.dataTransfer?.types?.includes(\"Files\")) e.preventDefault();\n if (e.dataTransfer?.files && e.dataTransfer.files.length > 0)\n add(e.dataTransfer.files);\n };\n document.addEventListener(\"dragover\", onDragOver);\n document.addEventListener(\"drop\", onDrop);\n return () => {\n document.removeEventListener(\"dragover\", onDragOver);\n document.removeEventListener(\"drop\", onDrop);\n };\n }, [add, globalDrop]);\n\n // Cleanup blob URLs on unmount (local mode only)\n useEffect(() => {\n return () => {\n if (!usingProvider) {\n for (const f of files) if (f.url) URL.revokeObjectURL(f.url);\n }\n };\n }, [usingProvider, files]);\n\n const handleFileInputChange: ChangeEventHandler<HTMLInputElement> = (\n event\n ) => {\n if (event.currentTarget.files) add(event.currentTarget.files);\n };\n\n const ctx = useMemo<AttachmentsContext>(\n () => ({\n files,\n add,\n remove,\n clear,\n openFileDialog,\n fileInputRef: inputRef,\n }),\n [files, add, remove, clear, openFileDialog]\n );\n\n const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => {\n event.preventDefault();\n const form = event.currentTarget;\n const text = usingProvider\n ? controller.textInput.value\n : (() => {\n const formData = new FormData(form);\n return (formData.get(\"message\") as string) || \"\";\n })();\n\n if (!usingProvider) form.reset();\n\n const doSubmit = async () => {\n const convertedFiles = await Promise.all(\n files.map(async (item) => {\n if (item.url.startsWith(\"blob:\")) {\n return { ...item, url: await convertBlobToDataUrl(item.url) };\n }\n return item;\n })\n );\n\n try {\n await onSubmit({ text, files: convertedFiles }, event);\n clear();\n if (usingProvider) controller.textInput.clear();\n } catch {\n // Don't clear on error — user may want to retry\n }\n };\n\n doSubmit().catch(() => {});\n };\n\n const isBackLayerOpen = backLayerOpen ?? false;\n\n const inputShell = hasBackLayer ? (\n // LayerCard-style shell: elevated outer card with header + collapsible back\n // layer + primary layer housing the actual input.\n <div className=\"flex w-full flex-col overflow-hidden rounded-xl bg-sf-elevated ring ring-sf-line/50\">\n {/* Header row — always visible, contains title + chevron toggle */}\n <button\n aria-expanded={isBackLayerOpen}\n aria-label={isBackLayerOpen ? \"Collapse context\" : \"Expand context\"}\n className=\"flex w-full items-center justify-between gap-2 px-3 py-2 text-left\"\n onClick={() => onBackLayerOpenChange?.(!isBackLayerOpen)}\n type=\"button\"\n >\n <span className=\"text-sm font-medium text-sf-subtle uppercase tracking-wide select-none\">\n {backLayerTitle}\n </span>\n <CaretDownIcon\n className={cn(\n \"size-3.5 text-sf-subtle transition-transform duration-200\",\n isBackLayerOpen && \"rotate-180\"\n )}\n />\n </button>\n {/* Collapsible back layer content */}\n <PromptInputBackLayerPanel open={isBackLayerOpen}>\n {backLayer}\n </PromptInputBackLayerPanel>\n {/* Primary layer — the input itself, visually raised off the back layer */}\n <div className=\"flex flex-col overflow-hidden rounded-xl bg-sf-base ring ring-sf-line/50 focus-within:ring-sf-ring\">\n {children}\n </div>\n </div>\n ) : (\n <InputGroup>{children}</InputGroup>\n );\n\n const inner = (\n <>\n <span aria-hidden=\"true\" className=\"hidden\" ref={anchorRef} />\n <input\n accept={accept}\n aria-label=\"Upload files\"\n className=\"hidden\"\n multiple={multiple}\n onChange={handleFileInputChange}\n ref={inputRef}\n type=\"file\"\n />\n <form\n className={cn(\"w-full\", className)}\n onSubmit={handleSubmit}\n {...props}\n >\n {inputShell}\n </form>\n </>\n );\n\n return usingProvider ? (\n inner\n ) : (\n <LocalAttachmentsContext.Provider value={ctx}>\n {inner}\n </LocalAttachmentsContext.Provider>\n );\n};\n\n// ─── PromptInputBody ──────────────────────────────────────────────────────────\n\nexport type PromptInputBodyProps = HTMLAttributes<HTMLDivElement>;\n\nexport const PromptInputBody = ({\n className,\n ...props\n}: PromptInputBodyProps) => (\n <div className={cn(\"contents\", className)} {...props} />\n);\n\n// ─── PromptInputBackLayerPanel (internal) ─────────────────────────────────────\n// Grid-rows animation: collapses to 0fr when closed, expands to 1fr when open.\n\ntype PromptInputBackLayerPanelProps = PropsWithChildren<{\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n}>;\n\nfunction PromptInputBackLayerPanel({\n open,\n children,\n}: PromptInputBackLayerPanelProps) {\n const isOpen = open ?? false;\n\n return (\n <div\n aria-hidden={!isOpen}\n className=\"grid\"\n style={{\n gridTemplateRows: isOpen ? \"1fr\" : \"0fr\",\n opacity: isOpen ? 1 : 0,\n transition: \"grid-template-rows 200ms ease-out, opacity 200ms ease-out\",\n }}\n >\n {/* min-h-0 lets the grid row collapse to 0 */}\n <div className=\"min-h-0 overflow-hidden\">{children}</div>\n </div>\n );\n}\n\n// ─── PromptInputBackLayer (public) ────────────────────────────────────────────\n\nexport type PromptInputBackLayerProps = PropsWithChildren<{\n className?: string;\n}>;\n\n/**\n * Container for back-layer content (HITL approvals, task lists, agent status,\n * etc.). Pass instances of this as the `backLayer` prop on `PromptInput`.\n *\n * @example\n * ```tsx\n * <PromptInput\n * onSubmit={handleSubmit}\n * backLayer={<PromptInputBackLayer>...</PromptInputBackLayer>}\n * backLayerOpen={open}\n * onBackLayerOpenChange={setOpen}\n * >\n * <PromptInputTextarea />\n * <PromptInputToolbar>\n * <PromptInputTools />\n * <PromptInputSubmit />\n * </PromptInputToolbar>\n * </PromptInput>\n * ```\n */\nexport function PromptInputBackLayer({\n children,\n className,\n}: PromptInputBackLayerProps) {\n return (\n <div\n className={cn(\n \"flex flex-col gap-2 px-3 py-2.5 text-sm text-sf-default\",\n className\n )}\n >\n {children}\n </div>\n );\n}\n\n// ─── Textarea helpers (module-level) ──────────────────────────────────────────\n\nconst handleTextareaKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = (\n e\n) => {\n if (e.key === \"Enter\") {\n if (e.nativeEvent.isComposing) return;\n if (e.shiftKey) return;\n e.preventDefault();\n e.currentTarget.form?.requestSubmit();\n }\n};\n\n// ─── PromptInputTextarea ──────────────────────────────────────────────────────\n\nexport type PromptInputTextareaProps = ComponentProps<\"textarea\"> & {\n placeholder?: string;\n};\n\n/**\n * Auto-resizing textarea for the prompt. Submits on Enter (Shift+Enter for newline).\n * Pastes files into attachments.\n */\nexport const PromptInputTextarea = ({\n onChange,\n className,\n placeholder = \"What would you like to know?\",\n ...props\n}: PromptInputTextareaProps) => {\n const controller = useOptionalPromptInputController();\n const attachments = usePromptInputAttachments();\n\n const handlePaste: ClipboardEventHandler<HTMLTextAreaElement> = (event) => {\n const items = event.clipboardData?.items;\n if (!items) return;\n const pastedFiles: File[] = [];\n for (const item of items) {\n if (item.kind === \"file\") {\n const file = item.getAsFile();\n if (file) pastedFiles.push(file);\n }\n }\n if (pastedFiles.length > 0) {\n event.preventDefault();\n attachments.add(pastedFiles);\n }\n };\n\n const controlledProps = controller\n ? {\n value: controller.textInput.value,\n onChange: (e: ChangeEvent<HTMLTextAreaElement>) => {\n controller.textInput.setInput(e.currentTarget.value);\n onChange?.(e);\n },\n }\n : { onChange };\n\n return (\n <textarea\n className={cn(\n \"field-sizing-content max-h-48 min-h-16 w-full resize-none border-0 bg-transparent px-3 py-2 text-sm text-sf-default outline-none placeholder:text-sf-inactive\",\n className\n )}\n name=\"message\"\n onKeyDown={handleTextareaKeyDown}\n onPaste={handlePaste}\n placeholder={placeholder}\n rows={1}\n {...props}\n {...controlledProps}\n />\n );\n};\n\n// ─── PromptInputToolbar ───────────────────────────────────────────────────────\n\nexport type PromptInputToolbarProps = HTMLAttributes<HTMLDivElement>;\n\nexport const PromptInputToolbar = ({\n className,\n ...props\n}: PromptInputToolbarProps) => (\n <div\n className={cn(\n \"flex items-center justify-between gap-1 px-2 py-1.5\",\n className\n )}\n {...props}\n />\n);\n\n// ─── PromptInputTools ─────────────────────────────────────────────────────────\n\nexport type PromptInputToolsProps = HTMLAttributes<HTMLDivElement>;\n\nexport const PromptInputTools = ({\n className,\n ...props\n}: PromptInputToolsProps) => (\n <div className={cn(\"flex items-center gap-1\", className)} {...props} />\n);\n\n// ─── PromptInputButton ────────────────────────────────────────────────────────\n\nexport type PromptInputButtonProps = ComponentProps<typeof Button> & {\n \"aria-label\": string;\n};\n\nexport const PromptInputButton = ({\n variant = \"ghost\",\n size = \"sm\",\n className,\n ...props\n}: PromptInputButtonProps) => (\n <Button\n className={cn(\"text-sf-subtle hover:text-sf-default\", className)}\n size={size}\n type=\"button\"\n variant={variant}\n {...props}\n />\n);\n\n// ─── PromptInputSubmit ────────────────────────────────────────────────────────\n\nexport type PromptInputSubmitProps = ComponentProps<typeof Button> & {\n status?: SFAiPromptInputStatus;\n};\n\n/**\n * Submit button. Icon changes based on `status`:\n * - `idle` → send arrow\n * - `submitted` → spinner\n * - `streaming` → stop square\n * - `error` → X\n */\nexport const PromptInputSubmit = ({\n className,\n variant = \"primary\",\n size = \"sm\",\n status = \"idle\",\n children,\n ...props\n}: PromptInputSubmitProps) => {\n let Icon: ReactNode;\n if (status === \"submitted\") {\n Icon = <SpinnerGapIcon className=\"size-4 animate-spin\" />;\n } else if (status === \"streaming\") {\n Icon = <SquareIcon className=\"size-4\" />;\n } else if (status === \"error\") {\n Icon = <XIcon className=\"size-4\" />;\n } else {\n Icon = <ArrowUpIcon className=\"size-4\" />;\n }\n\n return (\n <Button\n aria-label=\"Submit\"\n className={cn(className)}\n size={size}\n type=\"submit\"\n variant={variant}\n {...props}\n >\n {children ?? Icon}\n </Button>\n );\n};\n\n// ─── PromptInputActionMenu ────────────────────────────────────────────────────\n\nconst DropdownMenu = DropdownMenuPrimitive;\n\nexport type PromptInputActionMenuProps = ComponentProps<\n typeof DropdownMenu.Root\n>;\n\nexport const PromptInputActionMenu = (props: PromptInputActionMenuProps) => (\n <DropdownMenu.Root {...props} />\n);\n\nexport type PromptInputActionMenuTriggerProps = Omit<\n PromptInputButtonProps,\n \"aria-label\"\n> & {\n \"aria-label\"?: string;\n};\n\nexport const PromptInputActionMenuTrigger = ({\n className,\n children,\n \"aria-label\": ariaLabel = \"More actions\",\n ...props\n}: PromptInputActionMenuTriggerProps) => (\n <DropdownMenu.Trigger\n render={\n <Button\n aria-label={ariaLabel}\n className={cn(\"text-sf-subtle hover:text-sf-default\", className)}\n size=\"sm\"\n type=\"button\"\n variant=\"ghost\"\n {...props}\n >\n {children ?? <PlusIcon className=\"size-4\" />}\n </Button>\n }\n />\n);\n\nexport type PromptInputActionMenuContentProps = ComponentProps<\n typeof DropdownMenu.Positioner\n> & { className?: string; children?: ReactNode };\n\nexport const PromptInputActionMenuContent = ({\n className,\n children,\n ...props\n}: PromptInputActionMenuContentProps) => (\n <DropdownMenu.Positioner className=\"z-50\" side=\"top\" align=\"start\" {...props}>\n <DropdownMenu.Popup\n className={cn(\n \"min-w-[160px] rounded-lg bg-sf-elevated p-1 shadow-md\",\n \"data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 transition-opacity duration-100\",\n className\n )}\n >\n {children}\n </DropdownMenu.Popup>\n </DropdownMenu.Positioner>\n);\n\nexport type PromptInputActionMenuItemProps = ComponentProps<\n typeof DropdownMenu.Item\n>;\n\nexport const PromptInputActionMenuItem = ({\n className,\n ...props\n}: PromptInputActionMenuItemProps) => (\n <DropdownMenu.Item\n className={cn(\n \"flex cursor-pointer select-none items-center gap-2 rounded-md px-2 py-1.5 text-sm text-sf-default outline-none\",\n \"data-highlighted:bg-sf-tint\",\n className\n )}\n {...props}\n />\n);\n\n// ─── PromptInputActionAddAttachments ─────────────────────────────────────────\n\nexport type PromptInputActionAddAttachmentsProps =\n PromptInputActionMenuItemProps & {\n label?: string;\n };\n\nexport const PromptInputActionAddAttachments = ({\n label = \"Add photos or files\",\n ...props\n}: PromptInputActionAddAttachmentsProps) => {\n const attachments = usePromptInputAttachments();\n const handleSelect = useCallback(() => {\n attachments.openFileDialog();\n }, [attachments]);\n return (\n <PromptInputActionMenuItem {...props} onSelect={handleSelect}>\n <ImageIcon className=\"size-4\" />\n {label}\n </PromptInputActionMenuItem>\n );\n};\n\n// ─── PromptInputModeSelector ───────────────────────────────────────────────────\n\nexport type PromptInputModeSelectorProps = {\n modes: { modeId: string; modeName: string }[];\n value?: string;\n onChange?: (modeId: string) => void;\n};\n\nexport const PromptInputModeSelector = ({\n modes,\n value,\n onChange,\n}: PromptInputModeSelectorProps) => {\n if (modes.length === 0) return null;\n\n const selectedMode = modes.find((m) => m.modeId === value);\n const displayName = selectedMode?.modeName ?? value ?? modes[0]?.modeName;\n\n return (\n <div\n className={cn(\"flex items-center gap-0.5 rounded-md bg-sf-tint p-0.5\")}\n role=\"group\"\n aria-label=\"Select mode\"\n >\n {modes.map((mode) => {\n const isSelected =\n mode.modeId === value || mode.modeName === displayName;\n return (\n <button\n key={mode.modeId}\n aria-pressed={isSelected}\n className={cn(\n \"cursor-pointer rounded-[4px] px-2 py-0.5 text-[11px] font-medium transition-colors\",\n isSelected\n ? \"bg-sf-elevated text-sf-default shadow-sm\"\n : \"text-sf-subtle hover:text-sf-default\"\n )}\n onClick={() => onChange?.(mode.modeId)}\n type=\"button\"\n >\n {mode.modeName}\n </button>\n );\n })}\n </div>\n );\n};\n\n// ─── Attachment display helpers ───────────────────────────────────────────────\n\nconst IMAGE_EXTS = new Set([\"jpg\", \"jpeg\", \"png\", \"gif\", \"webp\", \"svg\"]);\n\nfunction getFileIcon(filename: string): ReactNode {\n const ext = filename.split(\".\").pop()?.toLowerCase() ?? \"\";\n if (IMAGE_EXTS.has(ext)) return <ImageIcon className=\"size-3.5\" />;\n if ([\"pdf\", \"doc\", \"docx\", \"txt\", \"md\"].includes(ext))\n return <FileTextIcon className=\"size-3.5\" />;\n return <FileIcon className=\"size-3.5\" />;\n}\n\n// ─── PromptInputAttachment ────────────────────────────────────────────────────\n\nexport type PromptInputAttachmentProps = HTMLAttributes<HTMLDivElement> & {\n data: AttachmentFile;\n};\n\n/**\n * Single attachment chip for images or files. Includes a remove button.\n */\nexport function PromptInputAttachment({\n data,\n className,\n ...props\n}: PromptInputAttachmentProps) {\n const attachments = usePromptInputAttachments();\n const isImage = data.mediaType?.startsWith(\"image/\") && data.url;\n\n const handleRemove = useCallback(() => {\n attachments.remove(data.id);\n }, [attachments, data.id]);\n\n if (isImage) {\n return (\n <div\n className={cn(\n \"group relative size-14 overflow-hidden rounded-md\",\n className\n )}\n {...props}\n >\n <img\n alt={data.filename || \"attachment\"}\n className=\"size-full object-cover\"\n src={data.url}\n />\n <Button\n aria-label=\"Remove attachment\"\n className=\"absolute top-1 right-1 size-5 rounded-full bg-sf-overlay/80 p-0 opacity-0 backdrop-blur-sm transition-opacity group-hover:opacity-100\"\n onClick={handleRemove}\n size=\"sm\"\n type=\"button\"\n variant=\"ghost\"\n >\n <XIcon className=\"size-3\" />\n </Button>\n </div>\n );\n }\n\n const ext = data.filename.split(\".\").pop()?.toUpperCase() ?? \"FILE\";\n const name = data.filename.replace(/\\.[^/.]+$/, \"\");\n\n return (\n <div\n className={cn(\n \"group relative inline-flex h-8 items-center gap-2 rounded-md bg-sf-tint/50 px-2 text-sm\",\n className\n )}\n {...props}\n >\n <div className=\"flex size-5 shrink-0 items-center justify-center text-sf-subtle\">\n {getFileIcon(data.filename)}\n </div>\n <Tooltip content={data.filename}>\n <span className=\"max-w-[160px] truncate font-medium text-sf-default\">\n {name || \"File\"}\n </span>\n </Tooltip>\n <span className=\"rounded bg-sf-tint px-1 py-0.5 font-semibold text-sf-subtle text-[10px] uppercase\">\n {ext}\n </span>\n <Button\n aria-label=\"Remove attachment\"\n className=\"ml-0.5 size-4 shrink-0 rounded-full p-0 opacity-0 transition-opacity group-hover:opacity-100\"\n onClick={handleRemove}\n size=\"sm\"\n type=\"button\"\n variant=\"ghost\"\n >\n <XIcon className=\"size-3\" />\n </Button>\n </div>\n );\n}\n\n// ─── PromptInputAttachments ───────────────────────────────────────────────────\n\nexport type PromptInputAttachmentsProps = Omit<\n HTMLAttributes<HTMLDivElement>,\n \"children\"\n> & {\n children: (attachment: AttachmentFile) => ReactNode;\n};\n\n/**\n * Renders all attachments using a render prop. Hidden when no files.\n * Animates height as files are added/removed.\n */\nexport function PromptInputAttachments({\n className,\n children,\n ...props\n}: PromptInputAttachmentsProps) {\n const attachments = usePromptInputAttachments();\n const [height, setHeight] = useState(0);\n const contentRef = useRef<HTMLDivElement>(null);\n\n useLayoutEffect(() => {\n const el = contentRef.current;\n if (!el) return;\n const ro = new ResizeObserver(() => {\n setHeight(el.getBoundingClientRect().height);\n });\n ro.observe(el);\n setHeight(el.getBoundingClientRect().height);\n return () => ro.disconnect();\n }, []);\n\n // Re-measure when file count changes\n // biome-ignore lint/correctness/useExhaustiveDependencies: intentional re-measure on count change\n useLayoutEffect(() => {\n const el = contentRef.current;\n if (el) setHeight(el.getBoundingClientRect().height);\n }, [attachments.files.length]);\n\n if (attachments.files.length === 0) return null;\n\n return (\n <div\n aria-live=\"polite\"\n className={cn(\n \"overflow-hidden px-2 transition-[height] duration-200 ease-out\",\n className\n )}\n style={{ height: attachments.files.length ? height : 0 }}\n {...props}\n >\n <div className=\"flex flex-wrap gap-2 py-1\" ref={contentRef}>\n {attachments.files.map((file) => (\n <Fragment key={file.id}>{children(file)}</Fragment>\n ))}\n </div>\n </div>\n );\n}\n\n// ─── Speech recognition types ─────────────────────────────────────────────────\n\ntype SpeechRecognitionInstance = EventTarget & {\n continuous: boolean;\n interimResults: boolean;\n lang: string;\n start(): void;\n stop(): void;\n};\n\ntype SpeechRecognitionResultEvent = Event & {\n results: {\n readonly length: number;\n item(index: number): {\n isFinal: boolean;\n [i: number]: { transcript: string };\n };\n [index: number]: { isFinal: boolean; [i: number]: { transcript: string } };\n };\n};\n\ntype SpeechRecognitionConstructor = new () => SpeechRecognitionInstance;\n\ndeclare global {\n interface Window {\n SpeechRecognition?: SpeechRecognitionConstructor;\n webkitSpeechRecognition?: SpeechRecognitionConstructor;\n }\n}\n\n// ─── PromptInputSpeechButton ──────────────────────────────────────────────────\n\nexport type PromptInputSpeechButtonProps = Omit<\n PromptInputButtonProps,\n \"aria-label\"\n> & {\n \"aria-label\"?: string;\n textareaRef?: RefObject<HTMLTextAreaElement | null>;\n onTranscriptionChange?: (text: string) => void;\n};\n\n/**\n * Voice input button. Uses the Web Speech API (Chrome/Edge). Hidden on unsupported browsers.\n * Pulses while listening.\n */\nexport const PromptInputSpeechButton = ({\n className,\n textareaRef,\n onTranscriptionChange,\n \"aria-label\": ariaLabel = \"Voice input\",\n ...props\n}: PromptInputSpeechButtonProps) => {\n const [isListening, setIsListening] = useState(false);\n const [recognition, setRecognition] =\n useState<SpeechRecognitionInstance | null>(null);\n const recognitionRef = useRef<SpeechRecognitionInstance | null>(null);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const SR = window.SpeechRecognition ?? window.webkitSpeechRecognition;\n if (!SR) return;\n\n const sr = new SR() as SpeechRecognitionInstance & {\n continuous: boolean;\n interimResults: boolean;\n lang: string;\n };\n sr.continuous = true;\n sr.interimResults = true;\n sr.lang = \"en-US\";\n\n const handleStart = () => setIsListening(true);\n const handleEnd = () => setIsListening(false);\n const handleResult = (event: Event) => {\n const e = event as SpeechRecognitionResultEvent;\n let final = \"\";\n for (const result of Array.from(\n { length: e.results.length },\n (_, i) => e.results[i]\n )) {\n if (result?.isFinal && result[0]?.transcript) {\n final += result[0].transcript;\n }\n }\n if (final && textareaRef?.current) {\n const ta = textareaRef.current;\n const next = ta.value + (ta.value ? \" \" : \"\") + final;\n ta.value = next;\n ta.dispatchEvent(new Event(\"input\", { bubbles: true }));\n onTranscriptionChange?.(next);\n }\n };\n const handleError = (_event: Event) => setIsListening(false);\n\n sr.addEventListener(\"start\", handleStart);\n sr.addEventListener(\"end\", handleEnd);\n sr.addEventListener(\"result\", handleResult);\n sr.addEventListener(\"error\", handleError);\n\n recognitionRef.current = sr;\n setRecognition(sr);\n\n return () => {\n sr.removeEventListener(\"start\", handleStart);\n sr.removeEventListener(\"end\", handleEnd);\n sr.removeEventListener(\"result\", handleResult);\n sr.removeEventListener(\"error\", handleError);\n recognitionRef.current?.stop();\n };\n }, [textareaRef, onTranscriptionChange]);\n\n const handleToggleListening = useCallback(() => {\n if (!recognition) return;\n const sr = recognition as SpeechRecognitionInstance & {\n start(): void;\n stop(): void;\n };\n if (isListening) sr.stop();\n else sr.start();\n }, [recognition, isListening]);\n\n const button = (\n <Button\n aria-label={ariaLabel}\n className={cn(\n \"text-sf-subtle hover:text-sf-default transition-all\",\n isListening && \"animate-pulse text-sf-brand\",\n className\n )}\n disabled={!recognition}\n onClick={handleToggleListening}\n size=\"sm\"\n type=\"button\"\n variant=\"ghost\"\n {...props}\n >\n <MicrophoneIcon className=\"size-4\" />\n </Button>\n );\n\n if (!recognition) return null;\n\n return (\n <Tooltip content={isListening ? \"Stop listening\" : \"Voice input\"}>\n {button}\n </Tooltip>\n );\n};\n\n// ─── PromptInputAttachButton ──────────────────────────────────────────────────\n\nexport type PromptInputAttachButtonProps = Omit<\n PromptInputButtonProps,\n \"aria-label\"\n> & {\n \"aria-label\"?: string;\n};\n\n/**\n * Convenience button that opens the file dialog directly (no dropdown needed).\n */\nexport const PromptInputAttachButton = ({\n \"aria-label\": ariaLabel = \"Attach file\",\n className,\n ...props\n}: PromptInputAttachButtonProps) => {\n const attachments = usePromptInputAttachments();\n const handleClick = useCallback(() => {\n attachments.openFileDialog();\n }, [attachments]);\n return (\n <Tooltip content={ariaLabel}>\n <Button\n aria-label={ariaLabel}\n className={cn(\"text-sf-subtle hover:text-sf-default\", className)}\n onClick={handleClick}\n size=\"sm\"\n type=\"button\"\n variant=\"ghost\"\n {...props}\n >\n <PaperclipIcon className=\"size-4\" />\n </Button>\n </Tooltip>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ai-reasoning-UAmNx_LD.js","names":[],"sources":["../src/components/ai-reasoning/ai-reasoning.tsx"],"sourcesContent":["\"use client\";\n\nimport { Collapsible as BaseCollapsible } from \"@base-ui/react/collapsible\";\nimport {\n BrainIcon,\n CaretDownIcon,\n CaretRightIcon,\n SparkleIcon,\n SpinnerGapIcon,\n} from \"@phosphor-icons/react\";\nimport type { ComponentProps, ElementType } from \"react\";\nimport {\n memo,\n Suspense,\n lazy,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\nimport { cn } from \"../../utils/cn\";\nimport { AiStatusBadge } from \"../ai-status-badge\";\n\n// Lazy-load Streamdown for markdown rendering in reasoning content.\n// Mirrors the pattern from ai-response.tsx for SSR safety.\ntype StreamdownModule = {\n default: (props: { children: string; className?: string }) => React.ReactNode;\n};\n\nconst Streamdown = lazy((): Promise<StreamdownModule> => {\n if (typeof window === \"undefined\") {\n return Promise.resolve({\n default: ({ children }: { children: string }) => <span>{children}</span>,\n });\n }\n return import(\"streamdown\").then((sd) => ({\n default: (props: { children: string; className?: string }) => (\n <sd.Streamdown {...props} />\n ),\n }));\n});\n\n// ─── Variants ────────────────────────────────────────────────────────────────\n\nexport const SF_AI_REASONING_VARIANTS = {\n variant: {\n default: {\n classes: \"\",\n description:\n \"Expandable card with collapsible reasoning text and duration\",\n },\n inline: {\n classes: \"\",\n description: \"Compact single-line display with left accent border\",\n },\n minimal: {\n classes: \"\",\n description: \"Pill-shaped status badge\",\n },\n },\n} as const;\n\nexport const SF_AI_REASONING_DEFAULT_VARIANTS = {\n variant: \"default\",\n} as const;\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Display variant for reasoning rendering. */\nexport type AiReasoningVariant = keyof typeof SF_AI_REASONING_VARIANTS.variant;\n\n/**\n * Structured reasoning part — mirrors the shape of Vercel AI SDK's\n * `ReasoningUIPart` without requiring the `ai` package as a dependency.\n */\nexport interface AiReasoningPart {\n /** The reasoning text content. */\n text: string;\n}\n\nexport type AiReasoningProps = Omit<ComponentProps<\"div\">, \"part\"> & {\n /** Structured reasoning part data. */\n part: AiReasoningPart;\n /** Display variant. @default \"default\" */\n variant?: AiReasoningVariant;\n /** Whether reasoning is currently streaming. */\n isStreaming?: boolean;\n /** Duration in seconds (passed in or self-tracked when streaming). */\n duration?: number;\n /** Default expanded state. @default false */\n defaultExpanded?: boolean;\n /** Controlled open state. */\n open?: boolean;\n /** Called when open state changes. */\n onOpenChange?: (open: boolean) => void;\n /** Delay in ms before auto-closing after streaming ends. Set to 0 to disable. @default 1000 */\n autoCloseDelay?: number;\n /**\n * Custom icon override. Defaults to `SparkleIcon` (streaming) or `BrainIcon` (done).\n * Use to distinguish observation/reflection blocks from standard reasoning.\n */\n icon?: ElementType;\n /**\n * Custom label override. Defaults to `\"Thinking...\"` (streaming) or `\"Reasoning\"` (done).\n * Use to label observational memory blocks: `\"Observing...\"`, `\"Reflecting...\"`, etc.\n */\n label?: string;\n /**\n * Custom streaming label. When provided, used instead of `label` while `isStreaming` is true.\n * Falls back to `label`, then to the default `\"Thinking...\"`.\n */\n streamingLabel?: string;\n};\n\nexport type AiReasoningGroupProps = Omit<ComponentProps<\"div\">, \"part\"> & {\n /** Array of reasoning parts. */\n parts: AiReasoningPart[];\n /** Whether the last part is currently streaming. */\n isStreaming?: boolean;\n /** Total duration in seconds. */\n totalDuration?: number;\n /** Default expanded state. @default false */\n defaultExpanded?: boolean;\n};\n\n// ─── Utilities ───────────────────────────────────────────────────────────────\n\n/** Format seconds into a human-friendly duration string. */\nexport function formatDuration(seconds: number): string {\n if (seconds < 60) return `${seconds}s`;\n const mins = Math.floor(seconds / 60);\n const secs = seconds % 60;\n return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`;\n}\n\n/** Truncate reasoning text with an ellipsis. */\nexport function truncateReasoning(text: string, maxLength = 100): string {\n if (text.length <= maxLength) return text;\n return `${text.slice(0, maxLength).trim()}...`;\n}\n\n/** Extract the first **bold** text from markdown-style reasoning. */\nexport function extractFirstBoldText(text: string): string | null {\n const match = text.match(/\\*\\*([^*]+)\\*\\*/);\n return match?.[1]?.trim() ?? null;\n}\n\n// ─── AiReasoning ─────────────────────────────────────────────────────────────\n\n/**\n * Collapsible reasoning block. Supports three display variants:\n * - `\"default\"` — expandable card with auto-open on stream start\n * - `\"inline\"` — compact single-line with left accent border\n * - `\"minimal\"` — pill-shaped status badge\n *\n * @example\n * ```tsx\n * <AiReasoning\n * part={{ text: \"First I considered the user's intent...\" }}\n * isStreaming={false}\n * duration={3}\n * />\n * ```\n */\nexport const AiReasoning = memo(\n ({\n part,\n variant = \"default\",\n isStreaming = false,\n duration: durationProp,\n defaultExpanded = false,\n open: openProp,\n onOpenChange,\n autoCloseDelay = 1000,\n icon: iconProp,\n label: labelProp,\n streamingLabel: streamingLabelProp,\n className,\n ...props\n }: AiReasoningProps) => {\n const [internalOpen, setInternalOpen] = useState(defaultExpanded);\n const [hasAutoClosed, setHasAutoClosed] = useState(false);\n const [userOpened, setUserOpened] = useState(false);\n const [internalDuration, setInternalDuration] = useState(0);\n const startTimeRef = useRef<number | null>(null);\n\n const isControlled = openProp !== undefined;\n const isOpen = isControlled ? openProp : internalOpen;\n\n const rawSetIsOpen = isControlled ? onOpenChange : setInternalOpen;\n const setIsOpen = useCallback(\n (value: boolean) => {\n rawSetIsOpen?.(value);\n },\n [rawSetIsOpen]\n );\n\n const handleUserToggle = useCallback(\n (value: boolean) => {\n if (value) setUserOpened(true);\n setIsOpen(value);\n },\n [setIsOpen]\n );\n\n const displayDuration = durationProp ?? internalDuration;\n const reasoningText = part.text || \"\";\n\n // Resolve custom icon/label (with defaults)\n // When not streaming, try to extract a dynamic title from first **bold** text\n const extractedBoldTitle = !isStreaming\n ? extractFirstBoldText(reasoningText)\n : null;\n const dynamicLabel = extractedBoldTitle\n ? truncateReasoning(extractedBoldTitle, 40)\n : null;\n const displayLabel = isStreaming\n ? (streamingLabelProp ?? labelProp ?? \"Thinking...\")\n : (labelProp ?? dynamicLabel ?? \"Reasoning\");\n const ActiveIcon = iconProp ?? (isStreaming ? SparkleIcon : BrainIcon);\n const StreamIcon = iconProp ?? SparkleIcon;\n const DoneIcon = iconProp ?? BrainIcon;\n\n // Track duration while streaming\n useEffect(() => {\n if (isStreaming) {\n if (startTimeRef.current === null) {\n startTimeRef.current = Date.now();\n }\n const interval = setInterval(() => {\n if (startTimeRef.current) {\n setInternalDuration(\n Math.round((Date.now() - startTimeRef.current) / 1000)\n );\n }\n }, 1000);\n return () => clearInterval(interval);\n }\n if (startTimeRef.current !== null) {\n setInternalDuration(\n Math.round((Date.now() - startTimeRef.current) / 1000)\n );\n startTimeRef.current = null;\n }\n }, [isStreaming]);\n\n // Auto-open on stream start, auto-close when done (unless user opened)\n useEffect(() => {\n if (isStreaming && !isOpen) {\n setUserOpened(false);\n setIsOpen(true);\n return;\n }\n if (\n !isStreaming &&\n isOpen &&\n !defaultExpanded &&\n !hasAutoClosed &&\n !userOpened &&\n autoCloseDelay > 0\n ) {\n const timer = setTimeout(() => {\n setIsOpen(false);\n setHasAutoClosed(true);\n }, autoCloseDelay);\n return () => clearTimeout(timer);\n }\n }, [\n isStreaming,\n isOpen,\n defaultExpanded,\n hasAutoClosed,\n userOpened,\n autoCloseDelay,\n setIsOpen,\n ]);\n\n // ── Minimal ──────────────────────────────────────────────────────────────\n\n if (variant === \"minimal\") {\n return (\n <BaseCollapsible.Root open={isOpen} onOpenChange={handleUserToggle}>\n <div className={cn(\"my-1.5\", className)} {...props}>\n <BaseCollapsible.Trigger\n render={<button type=\"button\" className=\"cursor-pointer\" />}\n >\n <AiStatusBadge\n className=\"py-1\"\n icon={DoneIcon}\n label={displayLabel}\n status={isStreaming ? \"running\" : \"success\"}\n />\n </BaseCollapsible.Trigger>\n <BaseCollapsible.Panel className=\"overflow-hidden\">\n <div className=\"mx-2 mt-1 rounded-lg bg-sf-tint/50 px-3 py-2\">\n <Suspense>\n <Streamdown className=\"text-sf-subtle text-sm [&>*:first-child]:mt-0 [&>*:last-child]:mb-0\">\n {reasoningText}\n </Streamdown>\n </Suspense>\n </div>\n </BaseCollapsible.Panel>\n </div>\n </BaseCollapsible.Root>\n );\n }\n\n // ── Inline ───────────────────────────────────────────────────────────────\n\n if (variant === \"inline\") {\n return (\n <div\n className={cn(\n \"my-1.5 flex items-center gap-2 border-l-2 border-sf-line py-1 pl-2\",\n \"animate-in fade-in-0 slide-in-from-bottom-1 duration-200\",\n className\n )}\n {...props}\n >\n <ActiveIcon className=\"size-3.5 shrink-0 text-sf-subtle\" />\n <span className=\"text-sm text-sf-subtle\">{displayLabel}</span>\n {isStreaming ? (\n <SpinnerGapIcon className=\"size-3.5 animate-spin text-sf-subtle\" />\n ) : null}\n {!isStreaming && reasoningText ? (\n <span className=\"max-w-[300px] truncate text-xs text-sf-subtle/60\">\n — {truncateReasoning(reasoningText)}\n </span>\n ) : null}\n </div>\n );\n }\n\n // ── Default (compact expandable row) ─────────────────────────────────────\n\n return (\n <BaseCollapsible.Root open={isOpen} onOpenChange={handleUserToggle}>\n <div\n className={cn(\n \"my-0.5 flex w-full flex-col\",\n \"animate-in fade-in-0 duration-150\",\n className\n )}\n {...props}\n >\n {/* Trigger — content-width, left-justified */}\n <BaseCollapsible.Trigger\n render={\n <button\n type=\"button\"\n className=\"flex w-fit items-center gap-1.5 rounded px-1 py-0.5 text-left transition-colors hover:bg-sf-tint\"\n />\n }\n >\n {isStreaming ? (\n <StreamIcon className=\"size-3.5 shrink-0 animate-pulse text-sf-subtle\" />\n ) : (\n <DoneIcon className=\"size-3.5 shrink-0 text-sf-subtle\" />\n )}\n <span className=\"text-sm text-sf-subtle\">{displayLabel}</span>\n {isStreaming ? (\n <SpinnerGapIcon className=\"size-3 shrink-0 animate-spin text-sf-subtle\" />\n ) : null}\n {displayDuration > 0 && !isStreaming ? (\n <span className=\"text-xs tabular-nums text-sf-subtle/60\">\n {formatDuration(displayDuration)}\n </span>\n ) : null}\n {isOpen ? (\n <CaretDownIcon className=\"size-3 shrink-0 text-sf-subtle/60\" />\n ) : (\n <CaretRightIcon className=\"size-3 shrink-0 text-sf-subtle/60\" />\n )}\n </BaseCollapsible.Trigger>\n\n {/* Detail panel */}\n <BaseCollapsible.Panel className=\"overflow-hidden\">\n <div className=\"mt-1 rounded-lg bg-sf-tint/50 px-3 py-2\">\n <Suspense>\n <Streamdown className=\"text-sf-subtle text-sm [&>*:first-child]:mt-0 [&>*:last-child]:mb-0\">\n {reasoningText || \"*No reasoning content*\"}\n </Streamdown>\n </Suspense>\n </div>\n </BaseCollapsible.Panel>\n </div>\n </BaseCollapsible.Root>\n );\n }\n);\n\nAiReasoning.displayName = \"AiReasoning\";\n\n// ─── AiReasoningGroup ────────────────────────────────────────────────────────\n\n/**\n * Renders multiple reasoning steps. Shows a single `<AiReasoning>` directly\n * when only one part is provided. When multiple parts exist, renders a\n * collapsible group with step labels.\n *\n * @example\n * ```tsx\n * <AiReasoningGroup parts={reasoningParts} isStreaming={false} totalDuration={5} />\n * ```\n */\nexport function AiReasoningGroup({\n parts,\n isStreaming = false,\n totalDuration,\n defaultExpanded = false,\n className,\n ...props\n}: AiReasoningGroupProps) {\n const [isExpanded, setIsExpanded] = useState(defaultExpanded);\n\n // Single part — render directly\n if (parts.length === 1 && parts[0]) {\n const singlePart = parts[0];\n // Strip `part` from div props spread to avoid collision with HTML `part` attribute\n const { part: _part, ...restProps } = props as typeof props & {\n part?: unknown;\n };\n return (\n <AiReasoning\n part={singlePart}\n isStreaming={isStreaming}\n duration={totalDuration}\n className={className}\n {...restProps}\n />\n );\n }\n\n return (\n <BaseCollapsible.Root open={isExpanded} onOpenChange={setIsExpanded}>\n <div\n className={cn(\n \"my-0.5 flex w-full flex-col\",\n \"animate-in fade-in-0 duration-150\",\n className\n )}\n {...props}\n >\n <BaseCollapsible.Trigger\n render={\n <button\n type=\"button\"\n className=\"flex w-fit items-center gap-1.5 rounded px-1 py-0.5 text-left transition-colors hover:bg-sf-tint\"\n />\n }\n >\n {isStreaming ? (\n <SparkleIcon className=\"size-3.5 shrink-0 animate-pulse text-sf-subtle\" />\n ) : (\n <BrainIcon className=\"size-3.5 shrink-0 text-sf-subtle\" />\n )}\n <span className=\"text-sm text-sf-subtle\">\n {isStreaming ? \"Thinking...\" : `${parts.length} reasoning steps`}\n </span>\n {isStreaming ? (\n <SpinnerGapIcon className=\"size-3 shrink-0 animate-spin text-sf-subtle\" />\n ) : null}\n {totalDuration && totalDuration > 0 && !isStreaming ? (\n <span className=\"text-xs tabular-nums text-sf-subtle/60\">\n {formatDuration(totalDuration)}\n </span>\n ) : null}\n {isExpanded ? (\n <CaretDownIcon className=\"size-3 shrink-0 text-sf-subtle/60\" />\n ) : (\n <CaretRightIcon className=\"size-3 shrink-0 text-sf-subtle/60\" />\n )}\n </BaseCollapsible.Trigger>\n\n <BaseCollapsible.Panel className=\"overflow-hidden\">\n <div className=\"mt-1 space-y-2 rounded-lg bg-sf-tint/50 px-3 py-2\">\n {parts.map((partItem, index) => (\n <div key={index} className=\"text-sm text-sf-subtle\">\n {parts.length > 1 ? (\n <p className=\"mb-0.5 text-[10px] uppercase tracking-wider text-sf-subtle/50\">\n Step {index + 1}\n </p>\n ) : null}\n <Suspense>\n <Streamdown className=\"[&>*:first-child]:mt-0 [&>*:last-child]:mb-0\">\n {partItem.text || \"*No content*\"}\n </Streamdown>\n </Suspense>\n </div>\n ))}\n </div>\n </BaseCollapsible.Panel>\n </div>\n </BaseCollapsible.Root>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ai-response-BWoVsNQG.js","names":[],"sources":["../src/components/ai-response/ai-response.tsx"],"sourcesContent":["\"use client\";\n\nimport type React from \"react\";\nimport { memo, Suspense, lazy } from \"react\";\nimport type { StreamdownProps } from \"streamdown\";\n\nimport { cn } from \"../../utils/cn\";\n\n// ─── Variants ────────────────────────────────────────────────────────────────\n\nexport const SF_AI_RESPONSE_VARIANTS = {} as const;\nexport const SF_AI_RESPONSE_DEFAULT_VARIANTS = {} as const;\n\n// Lazy-load Streamdown + the code plugin.\n// Streamdown accesses `document` at module init time, which crashes Astro\n// static builds. We gate the import behind a browser check so it is never\n// evaluated in a Node / SSR context.\ntype StreamdownModule = {\n default: (props: StreamdownProps) => React.ReactNode;\n};\n\nconst Streamdown = lazy((): Promise<StreamdownModule> => {\n if (typeof window === \"undefined\") {\n // SSR: return a no-op placeholder that renders nothing\n return Promise.resolve({\n default: (_props: StreamdownProps) => null,\n });\n }\n return Promise.all([import(\"streamdown\"), import(\"@streamdown/code\")]).then(\n ([sd, codePlugin]) => {\n const plugins = { code: codePlugin.code };\n return {\n default: (props: StreamdownProps) => (\n <sd.Streamdown plugins={plugins} {...props} />\n ),\n };\n }\n );\n});\n\n// ─── Component ───────────────────────────────────────────────────────────────\n\nexport type AiResponseProps = StreamdownProps & {\n /** Additional CSS classes. */\n className?: string;\n};\n\n/**\n * Renders AI-generated markdown using Streamdown.\n * Supports streaming animation, syntax-highlighted code, tables, and more.\n *\n * Pass `isAnimating={true}` while streaming and `false` once complete to get\n * character-by-character reveal animation.\n *\n * @example\n * ```tsx\n * // Static\n * <AiResponse>{\"# Hello\\n\\nThis is **bold** and _italic_.\"}</AiResponse>\n *\n * // Streaming\n * <AiResponse isAnimating={isStreaming}>{streamedText}</AiResponse>\n * ```\n */\nexport const AiResponse = memo(\n ({ className, ...props }: AiResponseProps) => (\n <Suspense>\n <Streamdown\n className={cn(\n \"w-full text-sf-default text-sm\",\n \"[&>*:first-child]:mt-0 [&>*:last-child]:mb-0\",\n className\n )}\n {...props}\n />\n </Suspense>\n ),\n (prev, next) =>\n prev.children === next.children && prev.isAnimating === next.isAnimating\n);\n\nAiResponse.displayName = \"AiResponse\";\n"],"mappings":";;;;;AAUA,IAAa,0BAA0B,EAAE;AACzC,IAAa,kCAAkC,EAAE;AAUjD,IAAM,aAAa,WAAsC;AACvD,KAAI,OAAO,WAAW,YAEpB,QAAO,QAAQ,QAAQ,EACrB,UAAU,WAA4B,MACvC,CAAC;AAEJ,QAAO,QAAQ,IAAI,CAAC,OAAO,eAAe,OAAO,oBAAoB,CAAC,CAAC,MACpE,CAAC,IAAI,gBAAgB;EACpB,MAAM,UAAU,EAAE,MAAM,WAAW,MAAM;AACzC,SAAO,EACL,UAAU,UACR,oBAAC,GAAG,YAAJ;GAAwB;GAAS,GAAI;GAAS,CAAA,EAEjD;GAEJ;EACD;;;;;;;;;;;;;;;;;AAyBF,IAAa,aAAa,MACvB,EAAE,WAAW,GAAG,YACf,oBAAC,UAAD,EAAA,UACE,oBAAC,YAAD;CACE,WAAW,GACT,kCACA,gDACA,UACD;CACD,GAAI;CACJ,CAAA,EACO,CAAA,GAEZ,MAAM,SACL,KAAK,aAAa,KAAK,YAAY,KAAK,gBAAgB,KAAK,YAChE;AAED,WAAW,cAAc"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ai-task-list-B9CpMDYN.js","names":[],"sources":["../src/components/ai-task-list/ai-task-list.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n CheckCircleIcon,\n CircleDashedIcon,\n CircleNotchIcon,\n XCircleIcon,\n} from \"@phosphor-icons/react\";\nimport type { ElementType, HTMLAttributes, ReactNode } from \"react\";\n\nimport { cn } from \"../../utils/cn\";\n\n// ─── Variants ────────────────────────────────────────────────────────────────\n\nexport const SF_AI_TASK_LIST_VARIANTS = {} as const;\nexport const SF_AI_TASK_LIST_DEFAULT_VARIANTS = {} as const;\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type AiTaskStatus =\n | \"pending\"\n | \"in_progress\"\n | \"completed\"\n | \"cancelled\";\n\nexport type AiTaskPriority = \"high\" | \"medium\" | \"low\";\n\nexport type AiTaskItem = {\n /** Unique task identifier. */\n id: string;\n /** Task description. */\n content: string;\n /** Current status. */\n status: AiTaskStatus;\n /** Priority level. */\n priority?: AiTaskPriority;\n};\n\nexport type AiTaskListProps = Omit<HTMLAttributes<HTMLDivElement>, \"title\"> & {\n /** List of tasks. */\n tasks: AiTaskItem[];\n /** Optional title above the task list. */\n title?: string;\n /** Custom icon for the title area. */\n icon?: ElementType;\n /** Show a progress summary (e.g. \"3 / 7 done\"). Default: `true`. */\n showProgress?: boolean;\n /** Content rendered below the task list. */\n children?: ReactNode;\n};\n\nexport type AiTaskListItemProps = HTMLAttributes<HTMLDivElement> & {\n task: AiTaskItem;\n};\n\n// ─── Status config ────────────────────────────────────────────────────────────\n\nconst STATUS_CONFIG: Record<\n AiTaskStatus,\n { icon: ElementType; className: string; textClassName: string }\n> = {\n pending: {\n icon: CircleDashedIcon,\n className: \"text-sf-subtle/60\",\n textClassName: \"text-sf-subtle\",\n },\n in_progress: {\n icon: CircleNotchIcon,\n className: \"text-sf-brand animate-spin\",\n textClassName: \"text-sf-default\",\n },\n completed: {\n icon: CheckCircleIcon,\n className: \"text-sf-success/60\",\n textClassName: \"text-sf-subtle/60 line-through\",\n },\n cancelled: {\n icon: XCircleIcon,\n className: \"text-sf-subtle/40\",\n textClassName: \"text-sf-subtle/40 line-through\",\n },\n};\n\nconst PRIORITY_DOT: Record<AiTaskPriority, string> = {\n high: \"bg-sf-danger\",\n medium: \"bg-sf-warning\",\n low: \"bg-sf-subtle\",\n};\n\n// ─── AiTaskListItem ──────────────────────────────────────────────────────────\n\n/**\n * Single task item with status icon, content, and optional priority dot.\n */\nexport function AiTaskListItem({\n task,\n className,\n ...props\n}: AiTaskListItemProps) {\n const config = STATUS_CONFIG[task.status];\n const StatusIcon = config.icon;\n\n return (\n <div className={cn(\"flex items-start gap-2 py-1\", className)} {...props}>\n <StatusIcon\n className={cn(\"mt-0.5 size-3.5 shrink-0\", config.className)}\n />\n <span className={cn(\"min-w-0 grow text-sm\", config.textClassName)}>\n {task.content}\n </span>\n {task.priority && (\n <span\n className={cn(\n \"mt-2 size-1 shrink-0 rounded-full\",\n PRIORITY_DOT[task.priority]\n )}\n title={`${task.priority} priority`}\n />\n )}\n </div>\n );\n}\n\nAiTaskListItem.displayName = \"AiTaskListItem\";\n\n// ─── AiTaskList ──────────────────────────────────────────────────────────────\n\n/**\n * Task progress list. Renders structured tasks from the harness `task_write`\n * tool. Typically rendered inside the `PromptInputBackLayer` or inline in\n * conversation.\n *\n * Maps to harness event: `task_updated`.\n *\n * @example\n * ```tsx\n * <AiTaskList\n * title=\"Current tasks\"\n * tasks={[\n * { id: \"1\", content: \"Read config file\", status: \"completed\", priority: \"high\" },\n * { id: \"2\", content: \"Run migrations\", status: \"in_progress\", priority: \"high\" },\n * { id: \"3\", content: \"Seed database\", status: \"pending\", priority: \"medium\" },\n * ]}\n * />\n * ```\n */\nexport function AiTaskList({\n tasks,\n title,\n icon,\n showProgress = true,\n children,\n className,\n ...props\n}: AiTaskListProps) {\n const completed = tasks.filter((t) => t.status === \"completed\").length;\n const total = tasks.filter((t) => t.status !== \"cancelled\").length;\n\n return (\n <div className={cn(\"flex flex-col gap-1\", className)} {...props}>\n {/* Header */}\n {(title || showProgress) && (\n <div className=\"flex items-center justify-between gap-2 pb-0.5\">\n <div className=\"flex items-center gap-1.5\">\n {icon &&\n (() => {\n const Icon = icon;\n return <Icon className=\"size-3.5 text-sf-subtle\" />;\n })()}\n {title && <span className=\"text-sm text-sf-subtle\">{title}</span>}\n </div>\n {showProgress && total > 0 && (\n <span className=\"text-xs tabular-nums text-sf-subtle/60\">\n {completed}/{total}\n </span>\n )}\n </div>\n )}\n\n {/* Tasks */}\n <div className=\"flex flex-col\">\n {tasks.map((task) => (\n <AiTaskListItem key={task.id} task={task} />\n ))}\n </div>\n\n {/* Progress bar */}\n {showProgress && total > 0 && (\n <div className=\"mt-1 h-0.5 overflow-hidden rounded-full bg-sf-tint\">\n <div\n className=\"h-full rounded-full bg-sf-brand/60 transition-[width] duration-300 ease-out\"\n style={{ width: `${(completed / total) * 100}%` }}\n />\n </div>\n )}\n\n {children}\n </div>\n );\n}\n\nAiTaskList.displayName = \"AiTaskList\";\n"],"mappings":";;;;;AAcA,IAAa,2BAA2B,EAAE;AAC1C,IAAa,mCAAmC,EAAE;AA0ClD,IAAM,gBAGF;CACF,SAAS;EACP,MAAM;EACN,WAAW;EACX,eAAe;EAChB;CACD,aAAa;EACX,MAAM;EACN,WAAW;EACX,eAAe;EAChB;CACD,WAAW;EACT,MAAM;EACN,WAAW;EACX,eAAe;EAChB;CACD,WAAW;EACT,MAAM;EACN,WAAW;EACX,eAAe;EAChB;CACF;AAED,IAAM,eAA+C;CACnD,MAAM;CACN,QAAQ;CACR,KAAK;CACN;;;;AAOD,SAAgB,eAAe,EAC7B,MACA,WACA,GAAG,SACmB;CACtB,MAAM,SAAS,cAAc,KAAK;CAClC,MAAM,aAAa,OAAO;AAE1B,QACE,qBAAC,OAAD;EAAK,WAAW,GAAG,+BAA+B,UAAU;EAAE,GAAI;YAAlE;GACE,oBAAC,YAAD,EACE,WAAW,GAAG,4BAA4B,OAAO,UAAU,EAC3D,CAAA;GACF,oBAAC,QAAD;IAAM,WAAW,GAAG,wBAAwB,OAAO,cAAc;cAC9D,KAAK;IACD,CAAA;GACN,KAAK,YACJ,oBAAC,QAAD;IACE,WAAW,GACT,qCACA,aAAa,KAAK,UACnB;IACD,OAAO,GAAG,KAAK,SAAS;IACxB,CAAA;GAEA;;;AAIV,eAAe,cAAc;;;;;;;;;;;;;;;;;;;;AAuB7B,SAAgB,WAAW,EACzB,OACA,OACA,MACA,eAAe,MACf,UACA,WACA,GAAG,SACe;CAClB,MAAM,YAAY,MAAM,QAAQ,MAAM,EAAE,WAAW,YAAY,CAAC;CAChE,MAAM,QAAQ,MAAM,QAAQ,MAAM,EAAE,WAAW,YAAY,CAAC;AAE5D,QACE,qBAAC,OAAD;EAAK,WAAW,GAAG,uBAAuB,UAAU;EAAE,GAAI;YAA1D;IAEI,SAAS,iBACT,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,qBAAC,OAAD;KAAK,WAAU;eAAf,CACG,QAGU,oBADM,MACN,EAAM,WAAU,2BAA4B,CAAA,EAEtD,SAAS,oBAAC,QAAD;MAAM,WAAU;gBAA0B;MAAa,CAAA,CAC7D;QACL,gBAAgB,QAAQ,KACvB,qBAAC,QAAD;KAAM,WAAU;eAAhB;MACG;MAAU;MAAE;MACR;OAEL;;GAIR,oBAAC,OAAD;IAAK,WAAU;cACZ,MAAM,KAAK,SACV,oBAAC,gBAAD,EAAoC,MAAQ,EAAvB,KAAK,GAAkB,CAC5C;IACE,CAAA;GAGL,gBAAgB,QAAQ,KACvB,oBAAC,OAAD;IAAK,WAAU;cACb,oBAAC,OAAD;KACE,WAAU;KACV,OAAO,EAAE,OAAO,GAAI,YAAY,QAAS,IAAI,IAAI;KACjD,CAAA;IACE,CAAA;GAGP;GACG;;;AAIV,WAAW,cAAc"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ai-timeline-Bb5ntsr3.js","names":[],"sources":["../src/components/ai-timeline/ai-timeline.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n BrainIcon,\n CaretDownIcon,\n CheckCircleIcon,\n CircleDashedIcon,\n CodeIcon,\n LightningIcon,\n MagnifyingGlassIcon,\n RobotIcon,\n ShieldWarningIcon,\n SpinnerGapIcon,\n WrenchIcon,\n XCircleIcon,\n} from \"@phosphor-icons/react\";\nimport type { ComponentProps, ElementType, ReactNode } from \"react\";\nimport { forwardRef, useCallback, useMemo, useRef, useState } from \"react\";\n\nimport { cn } from \"../../utils/cn\";\n\n// ─── Variants ────────────────────────────────────────────────────────────────\n\nexport const SF_AI_TIMELINE_VARIANTS = {\n density: {\n comfortable: { classes: \"\", description: \"Taller lanes with more padding\" },\n compact: { classes: \"\", description: \"Tight lanes for dense views\" },\n },\n} as const;\n\nexport const SF_AI_TIMELINE_DEFAULT_VARIANTS = {\n density: \"comfortable\",\n} as const;\n\nexport type SFAiTimelineDensity = keyof typeof SF_AI_TIMELINE_VARIANTS.density;\n\n// ─── Block types ─────────────────────────────────────────────────────────────\n\n/** Visual type of a timeline block — controls color and icon. */\nexport type AiTimelineBlockType =\n | \"running\" // brand blue — agent actively generating\n | \"tool_call\" // amber — executing a tool\n | \"waiting\" // gray — idle/blocked\n | \"approval\" // purple — waiting for human approval\n | \"completed\" // green — successfully finished\n | \"error\"; // red — failed\n\n/** A colored segment on a lane representing a period of activity. */\nexport interface AiTimelineBlock {\n /** Unique ID for this block. */\n id: string;\n /** Visual type (controls color). */\n type: AiTimelineBlockType;\n /** Wall-clock ms when this activity started. */\n startMs: number;\n /**\n * Wall-clock ms when this activity ended.\n * Undefined means still active — the block extends to \"now\".\n */\n endMs?: number;\n /** Short label shown inside the block when space allows. */\n label?: string;\n /** Longer description shown in the expanded detail panel. */\n description?: string;\n /** Extra detail content shown when the block is expanded. */\n detail?: ReactNode;\n}\n\n// ─── Lane props ───────────────────────────────────────────────────────────────\n\nexport type AiTimelineLaneProps = Omit<ComponentProps<\"div\">, \"children\"> & {\n /** Lane label (agent name). */\n label: string;\n /** Agent type string for icon selection. */\n agentType?: string;\n /** Model ID shown as a subtle badge. */\n modelId?: string;\n /** Current lane status — controls the label icon. */\n status?: \"idle\" | \"running\" | \"completed\" | \"error\";\n /** Ordered activity blocks for this lane. */\n blocks: AiTimelineBlock[];\n /** Timeline origin (ms) — same as AiTimeline timeOrigin. */\n timeOrigin: number;\n /** Current \"now\" timestamp (ms) for live blocks. */\n nowMs: number;\n /** Pixels per millisecond (zoom level). */\n pxPerMs: number;\n /** Whether the lane is expanded to show block details. */\n defaultExpanded?: boolean;\n /** Callback when a block is clicked. */\n onBlockClick?: (block: AiTimelineBlock) => void;\n /** Custom icon override. */\n icon?: ElementType;\n /** Lane height variant. */\n density?: SFAiTimelineDensity;\n};\n\n// ─── Timeline props ───────────────────────────────────────────────────────────\n\nexport type AiTimelineProps = Omit<ComponentProps<\"div\">, \"children\"> & {\n /**\n * Wall-clock ms for the timeline origin (t=0).\n * Defaults to the startMs of the first block across all lanes.\n */\n timeOrigin?: number;\n /**\n * Pixels per second of elapsed time.\n * Controls horizontal zoom level.\n * @default 30\n */\n pixelsPerSecond?: number;\n /**\n * Whether to show a pulsing \"now\" marker at the live edge.\n * @default true\n */\n showNowMarker?: boolean;\n /**\n * Current timestamp in ms. Used to render the \"now\" marker and open-ended\n * block widths. Pass a stable `useState` value to prevent hydration shifts.\n * When omitted, `Date.now()` is called at render time (fine for live views\n * driven by a ticking parent, not suitable for SSR).\n */\n nowMs?: number;\n /** Lane density variant. @default \"comfortable\" */\n density?: SFAiTimelineDensity;\n /** Children should be `AiTimelineLane` components. */\n children?: ReactNode;\n};\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nconst AGENT_ICONS: Record<string, ElementType> = {\n explore: MagnifyingGlassIcon,\n search: MagnifyingGlassIcon,\n execute: CodeIcon,\n code: CodeIcon,\n plan: BrainIcon,\n think: BrainIcon,\n tool: WrenchIcon,\n build: WrenchIcon,\n main: LightningIcon,\n};\n\nfunction getAgentIcon(\n agentType?: string,\n customIcon?: ElementType\n): ElementType {\n if (customIcon) return customIcon;\n if (agentType) {\n const lower = agentType.toLowerCase();\n for (const [key, icon] of Object.entries(AGENT_ICONS)) {\n if (lower.includes(key)) return icon;\n }\n }\n return RobotIcon;\n}\n\nfunction getModelShort(modelId?: string) {\n if (!modelId) return \"\";\n return (\n modelId\n .split(\"/\")\n .pop()\n ?.replace(/^claude-/, \"\")\n .replace(/-\\d+$/, \"\") ?? \"\"\n );\n}\n\nfunction formatMs(ms: number) {\n const s = Math.round(ms / 100) / 10;\n if (s < 60) return `${s}s`;\n return `${Math.floor(s / 60)}m ${Math.round(s % 60)}s`;\n}\n\n// Block colors — all semantic SF tokens, no raw Tailwind colors\nconst BLOCK_COLORS: Record<AiTimelineBlockType, string> = {\n running: \"bg-sf-brand text-sf-inverse\",\n tool_call: \"bg-sf-warning text-sf-inverse\",\n waiting: \"bg-sf-fill text-sf-subtle\",\n approval: \"bg-sf-info text-sf-inverse\",\n completed: \"bg-sf-brand/60 text-sf-inverse\",\n error: \"bg-sf-danger text-sf-inverse\",\n};\n\n// Hover is a uniform subtle brightness lift — no per-type color shifting\nconst BLOCK_HOVER = \"hover:brightness-110 hover:shadow-sm\";\n\nfunction BlockIcon({ type }: { type: AiTimelineBlockType }) {\n switch (type) {\n case \"running\":\n return <SpinnerGapIcon size={8} className=\"animate-spin\" />;\n case \"tool_call\":\n return <WrenchIcon size={8} />;\n case \"approval\":\n return <ShieldWarningIcon size={8} />;\n case \"completed\":\n return <CheckCircleIcon size={8} />;\n case \"error\":\n return <XCircleIcon size={8} />;\n default:\n return <CircleDashedIcon size={8} />;\n }\n}\n\n// ─── AiTimelineBlock (rendered) ───────────────────────────────────────────────\n\nfunction TimelineBlockSegment({\n block,\n timeOrigin,\n nowMs,\n pxPerMs,\n laneHeight,\n onBlockClick,\n rowIndex = 0,\n rowCount = 1,\n}: {\n block: AiTimelineBlock;\n timeOrigin: number;\n nowMs: number;\n pxPerMs: number;\n laneHeight: number;\n onBlockClick?: (block: AiTimelineBlock) => void;\n rowIndex?: number;\n rowCount?: number;\n}) {\n const left = Math.max(0, (block.startMs - timeOrigin) * pxPerMs);\n const end = block.endMs ?? nowMs;\n const width = Math.max(4, (end - block.startMs) * pxPerMs);\n const durationMs = end - block.startMs;\n\n const MIN_LABEL_PX = 40;\n // Short blocks get sharp corners to avoid weird rounded look when overlaid\n const isShort = width < 20;\n const roundedClass = isShort\n ? \"rounded-none\"\n : width < 40\n ? \"rounded-sm\"\n : \"rounded-md\";\n\n // Calculate vertical position for overlapping blocks\n // Blocks that overlap in time stack vertically, centered within the lane\n const blockHeight = laneHeight - 8;\n const innerPadding = 2;\n const availableHeight = blockHeight - innerPadding * 2;\n const rowHeight = availableHeight / rowCount;\n const topOffset = innerPadding + rowIndex * rowHeight;\n\n return (\n <button\n type=\"button\"\n aria-label={block.label ?? block.type}\n title={block.description ?? block.label}\n onClick={onBlockClick ? () => onBlockClick(block) : undefined}\n className={cn(\n \"group absolute transition-[width] duration-150\",\n roundedClass,\n BLOCK_COLORS[block.type],\n onBlockClick ? BLOCK_HOVER : \"cursor-default\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sf-ring\",\n \"flex items-center gap-1 overflow-hidden px-1.5\"\n )}\n style={{\n left,\n width,\n height: rowHeight - 1,\n top: topOffset,\n }}\n >\n <BlockIcon type={block.type} />\n {width >= MIN_LABEL_PX && block.label && (\n <span className=\"truncate text-[9px] font-medium leading-none\">\n {block.label}\n </span>\n )}\n {width >= MIN_LABEL_PX && (\n <span className=\"ml-auto shrink-0 text-[8px] opacity-70\">\n {formatMs(durationMs)}\n </span>\n )}\n </button>\n );\n}\n\n/**\n * Calculate row assignments for blocks to avoid overlap.\n * Returns arrays of blocks grouped by row, where no blocks in a row overlap in time.\n */\nfunction computeBlockRows(blocks: AiTimelineBlock[]): AiTimelineBlock[][] {\n if (blocks.length === 0) return [];\n\n // Sort blocks by start time\n const sorted = [...blocks].sort((a, b) => a.startMs - b.startMs);\n const rows: AiTimelineBlock[][] = [];\n\n for (const block of sorted) {\n // Find the first row where this block doesn't overlap with any existing block\n let placed = false;\n for (const row of rows) {\n const overlaps = row.some(\n (existing) =>\n block.startMs < (existing.endMs ?? Infinity) &&\n (block.endMs ?? Infinity) > existing.startMs\n );\n if (!overlaps) {\n row.push(block);\n placed = true;\n break;\n }\n }\n if (!placed) {\n // Start a new row\n rows.push([block]);\n }\n }\n\n return rows;\n}\n\n// ─── AiTimelineLane ───────────────────────────────────────────────────────────\n\nconst LANE_HEIGHT_COMFORTABLE = 40;\nconst LANE_HEIGHT_COMPACT = 28;\nconst LABEL_WIDTH = 128;\n\n/**\n * `AiTimelineLane` — a single horizontal swim lane within an `AiTimeline`.\n *\n * Shows one agent's activity as colored blocks on a time axis. Blocks\n * represent periods of activity: running, tool calls, waiting, etc.\n *\n * Consumed by `AiTimeline` — pass lanes as children.\n */\nexport const AiTimelineLane = forwardRef<HTMLDivElement, AiTimelineLaneProps>(\n (\n {\n label,\n agentType,\n modelId,\n status = \"idle\",\n blocks,\n timeOrigin,\n nowMs,\n pxPerMs,\n defaultExpanded = false,\n onBlockClick,\n icon,\n density = \"comfortable\",\n className,\n ...props\n },\n ref\n ) => {\n const [expanded, setExpanded] = useState(defaultExpanded);\n const Icon = getAgentIcon(agentType, icon);\n const modelShort = getModelShort(modelId);\n\n // Compute block rows to handle overlapping blocks\n const blockRows = useMemo(() => computeBlockRows(blocks), [blocks]);\n const rowCount = blockRows.length;\n\n // Lane height scales with number of rows for overlapping blocks\n const baseLaneH =\n density === \"compact\" ? LANE_HEIGHT_COMPACT : LANE_HEIGHT_COMFORTABLE;\n const laneH = rowCount > 1 ? baseLaneH * rowCount : baseLaneH;\n\n // Total timeline width needed for this lane\n const maxEnd = useMemo(() => {\n if (!blocks.length) return timeOrigin;\n return Math.max(...blocks.map((b) => b.endMs ?? nowMs));\n }, [blocks, timeOrigin, nowMs]);\n\n const totalWidth = Math.max(0, (maxEnd - timeOrigin) * pxPerMs);\n\n return (\n <div\n ref={ref}\n className={cn(\"border-b border-sf-line last:border-b-0\", className)}\n {...props}\n >\n {/* Lane row */}\n <div className=\"flex\" style={{ height: laneH }}>\n {/* Label sidebar */}\n <div\n className=\"flex shrink-0 items-center gap-2 border-r border-sf-line bg-sf-base px-3\"\n style={{ width: LABEL_WIDTH }}\n >\n {/* Expand toggle (only when there are expandable blocks) */}\n {blocks.some((b) => b.detail) && (\n <button\n type=\"button\"\n aria-label={expanded ? \"Collapse lane\" : \"Expand lane\"}\n onClick={() => setExpanded((v) => !v)}\n className=\"shrink-0 text-sf-subtle hover:text-sf-default focus-visible:outline-none\"\n >\n <CaretDownIcon\n size={10}\n className={cn(\n \"transition-transform\",\n expanded && \"rotate-180\"\n )}\n />\n </button>\n )}\n\n {/* Agent icon */}\n <div\n className={cn(\n \"flex shrink-0 items-center justify-center rounded\",\n density === \"compact\" ? \"size-5\" : \"size-6\",\n status === \"running\"\n ? \"text-sf-brand\"\n : status === \"completed\"\n ? \"text-sf-success\"\n : status === \"error\"\n ? \"text-sf-danger\"\n : \"text-sf-subtle\"\n )}\n >\n {status === \"running\" ? (\n <SpinnerGapIcon\n size={density === \"compact\" ? 12 : 14}\n className=\"animate-spin\"\n />\n ) : (\n <Icon size={density === \"compact\" ? 12 : 14} />\n )}\n </div>\n\n {/* Name + model */}\n <div className=\"min-w-0 flex-1\">\n <div\n className={cn(\n \"truncate font-medium text-sf-default\",\n density === \"compact\" ? \"text-[10px]\" : \"text-xs\"\n )}\n >\n {label}\n </div>\n {modelShort && density === \"comfortable\" && (\n <div className=\"truncate font-mono text-[9px] text-sf-inactive\">\n {modelShort}\n </div>\n )}\n </div>\n </div>\n\n {/* Blocks area */}\n <div\n className=\"relative flex-1 overflow-hidden\"\n style={{ minWidth: totalWidth }}\n >\n {blockRows.map((row, rowIndex) =>\n row.map((block) => (\n <TimelineBlockSegment\n key={block.id}\n block={block}\n timeOrigin={timeOrigin}\n nowMs={nowMs}\n pxPerMs={pxPerMs}\n laneHeight={laneH}\n onBlockClick={onBlockClick}\n rowIndex={rowIndex}\n rowCount={rowCount}\n />\n ))\n )}\n </div>\n </div>\n\n {/* Expanded detail rows */}\n {expanded &&\n blocks\n .filter((b) => b.detail)\n .map((block) => (\n <div\n key={`detail-${block.id}`}\n className=\"ml-[128px] border-t border-sf-line/50 bg-sf-recessed px-3 py-2\"\n >\n <div className=\"mb-1 flex items-center gap-1.5\">\n <span\n className={cn(\n \"size-1.5 rounded-full\",\n BLOCK_COLORS[block.type].split(\" \")[0]\n )}\n />\n <span className=\"text-[10px] font-medium text-sf-strong\">\n {block.label ?? block.type}\n </span>\n {block.endMs && (\n <span className=\"text-[10px] text-sf-subtle\">\n {formatMs(block.endMs - block.startMs)}\n </span>\n )}\n </div>\n <div className=\"text-xs text-sf-subtle\">{block.detail}</div>\n </div>\n ))}\n </div>\n );\n }\n);\n\nAiTimelineLane.displayName = \"AiTimelineLane\";\n\n// ─── AiTimeline ───────────────────────────────────────────────────────────────\n\nconst TIME_AXIS_HEIGHT = 24;\n\n/** Format elapsed time for tick labels — shows seconds with 1 decimal when under 1 minute */\nfunction formatElapsed(ms: number): string {\n if (ms < 60_000) {\n const s = Math.round(ms / 100) / 10;\n return `${s.toFixed(s % 1 === 0 ? 0 : 1)}s`;\n }\n const m = Math.floor(ms / 60_000);\n const s = Math.round((ms % 60_000) / 1000);\n return s > 0 ? `${m}m ${s}s` : `${m}m`;\n}\n\n/** Generate time axis tick marks spaced appropriately for the zoom level. */\nfunction generateTicks(\n originMs: number,\n nowMs: number,\n pxPerMs: number,\n totalWidth: number\n): Array<{ label: string; sublabel?: string; x: number }> {\n const durationMs = nowMs - originMs;\n const tickIntervalMs = (() => {\n const pxPerSec = pxPerMs * 1000;\n if (pxPerSec >= 100) return 1000; // 1s ticks\n if (pxPerSec >= 30) return 5000; // 5s ticks\n if (pxPerSec >= 10) return 10_000; // 10s ticks\n if (pxPerSec >= 5) return 30_000; // 30s ticks\n return 60_000; // 1min ticks\n })();\n\n const ticks: Array<{ label: string; sublabel?: string; x: number }> = [];\n const firstTick = Math.ceil(originMs / tickIntervalMs) * tickIntervalMs;\n\n for (let t = firstTick; t <= originMs + durationMs; t += tickIntervalMs) {\n const elapsed = t - originMs;\n const x = elapsed * pxPerMs;\n if (x > totalWidth) break;\n const label = formatElapsed(elapsed);\n ticks.push({ label, x });\n }\n return ticks;\n}\n\n/**\n * `AiTimeline` — horizontal swim-lane timeline for commander-level orchestration.\n *\n * Displays multiple `AiTimelineLane` children on a shared time axis. Supports\n * live updating (a \"now\" marker follows the live edge), panning, and zoom via\n * `pixelsPerSecond`.\n *\n * @example\n * ```tsx\n * <AiTimeline\n * timeOrigin={state.missionStartedAt}\n * pixelsPerSecond={30}\n * showNowMarker\n * >\n * <AiTimelineLane\n * label=\"Main\"\n * blocks={mainLaneBlocks}\n * timeOrigin={timeOrigin}\n * nowMs={Date.now()}\n * pxPerMs={pxPerMs}\n * />\n * <AiTimelineLane label=\"Explore\" blocks={exploreBlocks} ... />\n * </AiTimeline>\n * ```\n */\nexport const AiTimeline = forwardRef<HTMLDivElement, AiTimelineProps>(\n (\n {\n timeOrigin,\n pixelsPerSecond = 30,\n showNowMarker = true,\n nowMs: nowMsProp,\n density: _density = SF_AI_TIMELINE_DEFAULT_VARIANTS.density,\n className,\n children,\n ...props\n },\n ref\n ) => {\n // Use the provided stable nowMs, or fall back to Date.now().\n // For SSR-safe demos, always pass a stable useState value as nowMs.\n const nowMs = nowMsProp ?? Date.now();\n const origin = timeOrigin ?? nowMs;\n const pxPerMs = pixelsPerSecond / 1000;\n const totalDurationMs = nowMs - origin;\n const totalWidth = Math.max(400, totalDurationMs * pxPerMs + 120);\n const nowX = totalDurationMs * pxPerMs;\n\n const ticks = useMemo(\n () => generateTicks(origin, nowMs, pxPerMs, totalWidth),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [origin, Math.floor(nowMs / 2000), pxPerMs, totalWidth]\n );\n\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // Scroll to keep the now-marker in view\n const scrollToNow = useCallback(() => {\n const el = scrollRef.current;\n if (!el) return;\n const targetScroll = LABEL_WIDTH + nowX - el.clientWidth * 0.85;\n el.scrollLeft = Math.max(0, targetScroll);\n }, [nowX]);\n\n return (\n <div\n ref={ref}\n className={cn(\n \"relative overflow-hidden rounded-lg border border-sf-line bg-sf-base\",\n className\n )}\n {...props}\n >\n {/* Scrollable horizontal container */}\n <div ref={scrollRef} className=\"overflow-x-auto\">\n <div style={{ minWidth: LABEL_WIDTH + totalWidth }}>\n {/* Time axis */}\n <div\n className=\"flex border-b border-sf-line\"\n style={{ height: TIME_AXIS_HEIGHT }}\n >\n {/* Label column spacer */}\n <div\n className=\"shrink-0 border-r border-sf-line bg-sf-elevated px-3\"\n style={{ width: LABEL_WIDTH }}\n >\n <span className=\"text-[10px] font-medium uppercase tracking-wider text-sf-subtle\">\n Time\n </span>\n </div>\n {/* Axis ticks */}\n <div\n className=\"relative flex-1 bg-sf-recessed\"\n style={{ minWidth: totalWidth }}\n >\n {ticks.map((tick) => (\n <div\n key={tick.x}\n className=\"absolute top-0 flex flex-col items-center\"\n style={{ left: tick.x }}\n >\n <div className=\"h-2 w-px bg-sf-line\" />\n <span className=\"mt-0.5 font-mono text-[9px] text-sf-inactive\">\n +{tick.label}\n </span>\n </div>\n ))}\n\n {/* Now marker line */}\n {showNowMarker && (\n <div\n className=\"absolute top-0 flex flex-col items-center\"\n style={{ left: nowX }}\n >\n <div className=\"h-2 w-px bg-sf-brand\" />\n <span className=\"mt-0.5 font-mono text-[9px] font-medium text-sf-brand\">\n now\n </span>\n </div>\n )}\n </div>\n </div>\n\n {/* Lanes — pass timeOrigin/nowMs/pxPerMs via context-like clone or expect them as props */}\n <div className=\"relative\">\n {/* Vertical grid lines at tick positions */}\n <div className=\"pointer-events-none absolute inset-0 ml-[128px]\">\n {ticks.map((tick) => (\n <div\n key={`grid-${tick.x}`}\n className=\"absolute top-0 h-full w-px bg-sf-line/50\"\n style={{ left: tick.x }}\n />\n ))}\n {/* Now marker vertical line */}\n {showNowMarker && (\n <div\n className=\"absolute top-0 h-full w-px bg-sf-brand/30\"\n style={{ left: nowX }}\n />\n )}\n </div>\n\n {children}\n </div>\n </div>\n </div>\n\n {/* Scroll-to-now button */}\n {showNowMarker && (\n <button\n type=\"button\"\n aria-label=\"Scroll to now\"\n onClick={scrollToNow}\n className={cn(\n \"absolute right-2 top-1 rounded px-1.5 py-0.5 text-[9px] font-medium\",\n \"bg-sf-brand/10 text-sf-brand hover:bg-sf-brand/20 transition-colors\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sf-ring\"\n )}\n >\n → now\n </button>\n )}\n </div>\n );\n }\n);\n\nAiTimeline.displayName = \"AiTimeline\";\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ai-tool-BGH8nQ_D.js","names":[],"sources":["../src/components/ai-tool/ai-tool.tsx"],"sourcesContent":["\"use client\";\n\nimport { Collapsible as BaseCollapsible } from \"@base-ui/react/collapsible\";\nimport {\n CaretDownIcon,\n CheckIcon,\n CircleIcon,\n CodeIcon,\n DatabaseIcon,\n FileTextIcon,\n GearIcon,\n GlobeIcon,\n ImageIcon,\n LightningIcon,\n MagnifyingGlassIcon,\n ChatCircleIcon,\n SpinnerGapIcon,\n TerminalIcon,\n WarningCircleIcon,\n XCircleIcon,\n ShieldWarningIcon,\n BrainIcon,\n} from \"@phosphor-icons/react\";\nimport type { ComponentProps, ElementType } from \"react\";\nimport { useState } from \"react\";\n\nimport { cn } from \"../../utils/cn\";\nimport { AiStatusBadge, type AiStatusBadgeStatus } from \"../ai-status-badge\";\nimport { Button } from \"../button\";\nimport { Tooltip } from \"../tooltip\";\n\n// ─── Variants ────────────────────────────────────────────────────────────────\n\nexport const SF_AI_TOOL_VARIANTS = {\n variant: {\n default: {\n classes: \"\",\n description: \"Expandable card with collapsible input/output sections\",\n },\n inline: {\n classes: \"\",\n description: \"Compact single-line display with left accent border\",\n },\n minimal: {\n classes: \"\",\n description: \"Pill-shaped status badge\",\n },\n },\n} as const;\n\nexport const SF_AI_TOOL_DEFAULT_VARIANTS = {\n variant: \"default\",\n} as const;\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Tool execution state — matches Vercel AI SDK ToolUIPart[\"state\"]. */\nexport type AiToolState =\n | \"input-streaming\"\n | \"input-available\"\n | \"approval-requested\"\n | \"approval-responded\"\n | \"output-available\"\n | \"output-error\"\n | \"output-denied\";\n\n/** Display variant for tool call rendering. */\nexport type AiToolCallVariant = keyof typeof SF_AI_TOOL_VARIANTS.variant;\n\n/** Approval state for tools requiring user confirmation. */\nexport type AiToolApprovalState =\n | \"awaiting\"\n | \"approved\"\n | \"declined\"\n | undefined;\n\n/**\n * Structured tool part — mirrors the shape of Vercel AI SDK's `ToolUIPart`\n * without requiring the `ai` package as a dependency.\n */\nexport interface AiToolPart {\n /** Tool call identifier. */\n toolCallId: string;\n /** Tool name or type identifier. */\n toolName: string;\n /** Current execution state. */\n state: AiToolState;\n /** Tool input parameters. */\n input?: unknown;\n /** Tool output result. */\n output?: unknown;\n /** Error text when state is \"output-error\". */\n errorText?: string;\n}\n\nexport type AiToolCallProps = Omit<ComponentProps<\"div\">, \"part\"> & {\n /** Structured tool part data. */\n part: AiToolPart;\n /** Display variant. @default \"default\" */\n variant?: AiToolCallVariant;\n /** Duration in milliseconds. */\n duration?: number;\n /** Summary text shown alongside the tool name. */\n summary?: string;\n /** Custom icon override. */\n icon?: ElementType;\n /** Approval state for tools requiring confirmation. */\n approvalState?: AiToolApprovalState;\n /** Called when user approves a tool call (only relevant when `approvalState=\"awaiting\"`). */\n onApprove?: () => void;\n /** Called when user rejects a tool call (only relevant when `approvalState=\"awaiting\"`). */\n onReject?: () => void;\n /** Default expanded state for the collapsible. @default false */\n defaultExpanded?: boolean;\n};\n\nexport type AiToolCallGroupProps = Omit<ComponentProps<\"div\">, \"part\"> & {\n /** Array of tool parts. */\n parts: AiToolPart[];\n /** Group label. */\n label?: string;\n /** Duration map keyed by toolCallId. */\n durations?: Record<string, number>;\n /** Summary map keyed by toolCallId. */\n summaries?: Record<string, string>;\n /** Approval states map keyed by toolCallId. */\n approvalStates?: Record<string, AiToolApprovalState>;\n /** Called when user approves a tool call. Receives the `toolCallId`. */\n onApprove?: (toolCallId: string) => void;\n /** Called when user rejects a tool call. Receives the `toolCallId`. */\n onReject?: (toolCallId: string) => void;\n /** Default expanded state. @default false */\n defaultExpanded?: boolean;\n};\n\nexport type AiToolCallTimelineProps = ComponentProps<\"div\"> & {\n /** Array of tool parts. */\n parts: AiToolPart[];\n /** Duration map keyed by toolCallId. */\n durations?: Record<string, number>;\n};\n\n// ─── Icon Map ────────────────────────────────────────────────────────────────\n\nconst TOOL_ICONS: Record<string, ElementType> = {\n generateSuggestions: BrainIcon,\n searchDatabase: DatabaseIcon,\n searchWeb: GlobeIcon,\n search: MagnifyingGlassIcon,\n readFile: FileTextIcon,\n writeFile: FileTextIcon,\n executeQuery: LightningIcon,\n analyzeContent: BrainIcon,\n generateImage: ImageIcon,\n chat: ChatCircleIcon,\n runCode: TerminalIcon,\n settings: GearIcon,\n code: CodeIcon,\n};\n\n// ─── Utilities ───────────────────────────────────────────────────────────────\n\n/** Extract the display-friendly tool name from a tool part. */\nexport function getToolDisplayName(part: AiToolPart): string {\n return part.toolName\n .replace(\"tool-\", \"\")\n .replace(\"Tool\", \"\")\n .replace(/([A-Z])/g, \" $1\")\n .replace(/_/g, \" \")\n .replace(/^./, (s) => s.toUpperCase())\n .trim();\n}\n\n/** Get the best-matching icon for a tool part. */\nexport function getToolIcon(part: AiToolPart): ElementType {\n if (TOOL_ICONS[part.toolName]) return TOOL_ICONS[part.toolName];\n\n const lower = part.toolName.toLowerCase();\n for (const [key, icon] of Object.entries(TOOL_ICONS)) {\n if (lower.includes(key.toLowerCase())) return icon;\n }\n\n return MagnifyingGlassIcon;\n}\n\n/** Whether the tool is still loading (input phase). */\nexport function isToolLoading(part: AiToolPart): boolean {\n return part.state === \"input-streaming\" || part.state === \"input-available\";\n}\n\n/** Whether the tool has produced a result. */\nexport function hasToolResult(part: AiToolPart): boolean {\n return part.state === \"output-available\";\n}\n\n/** Whether the output itself contains an error flag. */\nexport function hasToolOutputError(part: AiToolPart): boolean {\n if (part.state !== \"output-available\") return false;\n const output = part.output as Record<string, unknown> | undefined;\n return output?.error === true;\n}\n\n/** Whether the tool is in any error state. */\nexport function hasToolError(part: AiToolPart): boolean {\n return part.state === \"output-error\" || hasToolOutputError(part);\n}\n\n/** Extract error text from either error state or output.error. */\nexport function getToolErrorText(part: AiToolPart): string | undefined {\n if (part.state === \"output-error\") return part.errorText;\n if (hasToolOutputError(part)) {\n const output = part.output as Record<string, unknown>;\n return (\n (output.message as string) ||\n (output.error as string) ||\n \"Tool returned an error\"\n );\n }\n return undefined;\n}\n\n// ─── State Config ────────────────────────────────────────────────────────────\n\ninterface StateConfig {\n colorClass: string;\n bgClass: string;\n icon: ElementType;\n spin: boolean;\n label: string;\n}\n\nfunction getStateConfig(\n part: AiToolPart,\n approvalState?: AiToolApprovalState\n): StateConfig {\n if (approvalState === \"awaiting\") {\n return {\n colorClass: \"text-sf-warning\",\n bgClass: \"bg-sf-warning/10\",\n icon: ShieldWarningIcon,\n spin: false,\n label: \"Awaiting Approval\",\n };\n }\n\n if (approvalState === \"declined\") {\n return {\n colorClass: \"text-sf-danger\",\n bgClass: \"bg-sf-danger/10\",\n icon: XCircleIcon,\n spin: false,\n label: \"Declined\",\n };\n }\n\n if (hasToolError(part)) {\n return {\n colorClass: \"text-sf-danger\",\n bgClass: \"bg-sf-danger/10\",\n icon: WarningCircleIcon,\n spin: false,\n label: \"Failed\",\n };\n }\n\n switch (part.state) {\n case \"input-streaming\":\n return {\n colorClass: \"text-sf-brand\",\n bgClass: \"bg-sf-brand/10\",\n icon: CircleIcon,\n spin: false,\n label: \"Streaming\",\n };\n case \"input-available\":\n return {\n colorClass: \"text-sf-brand\",\n bgClass: \"bg-sf-brand/10\",\n icon: SpinnerGapIcon,\n spin: true,\n label: \"Running\",\n };\n case \"output-available\":\n return {\n colorClass: \"text-sf-success\",\n bgClass: \"bg-sf-tint\",\n icon: CheckIcon,\n spin: false,\n label: \"Complete\",\n };\n default:\n return {\n colorClass: \"text-sf-subtle\",\n bgClass: \"bg-sf-tint\",\n icon: CircleIcon,\n spin: false,\n label: \"Pending\",\n };\n }\n}\n\n// ─── AiToolCall ──────────────────────────────────────────────────────────────\n\n/**\n * Renders a single tool call. Supports three display variants:\n * - `\"default\"` — expandable card with collapsible input/output\n * - `\"inline\"` — compact single-line with left accent border\n * - `\"minimal\"` — pill-shaped status badge\n *\n * @example\n * ```tsx\n * <AiToolCall\n * part={{ toolCallId: \"1\", toolName: \"web_search\", state: \"output-available\", input: { q: \"ts\" }, output: \"...\" }}\n * />\n * ```\n */\nexport function AiToolCall({\n part,\n variant = \"default\",\n duration,\n summary,\n icon: CustomIcon,\n approvalState,\n onApprove,\n onReject,\n defaultExpanded = false,\n className,\n ...props\n}: AiToolCallProps) {\n const [isExpanded, setIsExpanded] = useState(defaultExpanded);\n\n const IconComponent = CustomIcon ?? getToolIcon(part);\n const displayName = getToolDisplayName(part);\n const config = getStateConfig(part, approvalState);\n const StatusIconEl = config.icon;\n const errorText = getToolErrorText(part);\n\n // ── Minimal ────────────────────────────────────────────────────────────────\n\n if (variant === \"minimal\") {\n const handleGetToolStatus = (): AiStatusBadgeStatus => {\n if (hasToolError(part)) return \"error\";\n if (approvalState === \"awaiting\") return \"idle\";\n if (isToolLoading(part)) return \"running\";\n if (hasToolResult(part)) return \"success\";\n return \"idle\";\n };\n\n return (\n <AiStatusBadge\n className={cn(\n \"my-1.5 py-1\",\n hasToolError(part) && \"bg-sf-danger/10\",\n className\n )}\n icon={IconComponent}\n label={displayName}\n status={handleGetToolStatus()}\n {...props}\n />\n );\n }\n\n // ── Inline ─────────────────────────────────────────────────────────────────\n\n if (variant === \"inline\") {\n return (\n <div\n className={cn(\n \"my-1 flex w-full items-center gap-2 py-1.5 pl-1\",\n \"animate-in fade-in-0 slide-in-from-bottom-1 duration-200\",\n className\n )}\n {...props}\n >\n <div className={cn(\"shrink-0 rounded-md p-1\", config.bgClass)}>\n <IconComponent className={cn(\"size-3.5\", config.colorClass)} />\n </div>\n <span\n className={cn(\n \"min-w-0 grow truncate text-sm\",\n hasToolError(part) ? \"text-sf-danger\" : \"text-sf-subtle\"\n )}\n >\n {displayName}\n </span>\n {/* Right-aligned meta group */}\n <div className=\"flex shrink-0 items-center gap-1.5\">\n {duration && hasToolResult(part) ? (\n <span className=\"text-xs tabular-nums text-sf-subtle\">\n {duration}ms\n </span>\n ) : null}\n {summary && !hasToolError(part) ? (\n <span className=\"max-w-[160px] truncate text-xs text-sf-subtle\">\n {summary}\n </span>\n ) : null}\n {errorText ? (\n <span\n className=\"max-w-[160px] truncate text-xs text-sf-danger\"\n title={errorText}\n >\n {errorText}\n </span>\n ) : null}\n {approvalState === \"awaiting\" && (onApprove || onReject) ? (\n <>\n {onReject && (\n <Button\n onClick={onReject}\n size=\"sm\"\n type=\"button\"\n variant=\"ghost\"\n >\n Reject\n </Button>\n )}\n {onApprove && (\n <Button\n onClick={onApprove}\n size=\"sm\"\n type=\"button\"\n variant=\"primary\"\n >\n Approve\n </Button>\n )}\n </>\n ) : (\n <StatusIconEl\n className={cn(\n \"size-3.5\",\n config.colorClass,\n config.spin && \"animate-spin\"\n )}\n />\n )}\n </div>\n </div>\n );\n }\n\n // ── Default (compact expandable row) ─────────────────────────────────────\n\n return (\n <BaseCollapsible.Root open={isExpanded} onOpenChange={setIsExpanded}>\n <div\n className={cn(\n \"my-0.5 flex w-full max-w-sm flex-col\",\n \"animate-in fade-in-0 duration-150\",\n className\n )}\n {...props}\n >\n {/* Trigger — content-width: name + meta + caret */}\n <BaseCollapsible.Trigger\n render={\n <button\n type=\"button\"\n className={cn(\n \"flex w-fit items-center gap-1.5 rounded px-1 py-0.5 text-left\",\n \"transition-colors hover:bg-sf-tint\"\n )}\n />\n }\n >\n <span className=\"min-w-0 truncate text-sm text-sf-subtle\">\n {displayName}\n </span>\n {/* Meta group — part of the content-width trigger */}\n <div className=\"flex shrink-0 items-center gap-1.5\">\n {duration && hasToolResult(part) && !isExpanded ? (\n <span className=\"text-xs tabular-nums text-sf-subtle\">\n {duration}ms\n </span>\n ) : null}\n {summary && !isExpanded && !errorText ? (\n <span className=\"max-w-[120px] truncate text-xs text-sf-subtle\">\n {summary}\n </span>\n ) : null}\n <StatusIconEl\n className={cn(\n \"size-3.5\",\n config.colorClass,\n config.spin && \"animate-spin\"\n )}\n />\n <CaretDownIcon\n className={cn(\n \"size-3 text-sf-subtle transition-transform duration-200\",\n !isExpanded && \"-rotate-90\"\n )}\n />\n </div>\n </BaseCollapsible.Trigger>\n\n {/* Detail panel */}\n <BaseCollapsible.Panel className=\"overflow-hidden\">\n <div className=\"mt-1 space-y-2 rounded-lg bg-sf-tint/50 px-3 py-2\">\n {/* Input */}\n {part.input &&\n typeof part.input === \"object\" &&\n Object.keys(part.input as object).length > 0 ? (\n <div>\n <p className=\"mb-1 text-[10px] uppercase tracking-wider text-sf-subtle\">\n Input\n </p>\n <pre className=\"overflow-x-auto rounded bg-sf-recessed p-1.5 font-mono text-xs text-sf-subtle\">\n {JSON.stringify(\n part.input as Record<string, unknown>,\n null,\n 2\n )}\n </pre>\n </div>\n ) : null}\n\n {/* Output */}\n {hasToolResult(part) &&\n part.output !== undefined &&\n !hasToolError(part) ? (\n <div>\n <p className=\"mb-1 text-[10px] uppercase tracking-wider text-sf-subtle\">\n Output\n </p>\n <pre className=\"max-h-28 overflow-x-auto overflow-y-auto rounded bg-sf-recessed p-1.5 font-mono text-xs text-sf-subtle\">\n {typeof part.output === \"string\"\n ? part.output\n : JSON.stringify(\n part.output as Record<string, unknown>,\n null,\n 2\n )}\n </pre>\n </div>\n ) : null}\n\n {/* Error */}\n {errorText ? (\n <div>\n <p className=\"mb-1 flex items-center gap-1 text-[10px] uppercase tracking-wider text-sf-danger\">\n <WarningCircleIcon className=\"size-3\" />\n Error\n </p>\n <pre className=\"overflow-x-auto whitespace-pre-wrap rounded bg-sf-danger/10 p-1.5 font-mono text-xs text-sf-danger\">\n {errorText}\n </pre>\n </div>\n ) : null}\n\n {/* Approval actions */}\n {approvalState === \"awaiting\" && (onApprove || onReject) && (\n <div className=\"flex items-center justify-end gap-2 border-t border-sf-line/20 pt-2\">\n {onReject && (\n <Button\n onClick={onReject}\n size=\"sm\"\n type=\"button\"\n variant=\"ghost\"\n >\n Reject\n </Button>\n )}\n {onApprove && (\n <Button\n onClick={onApprove}\n size=\"sm\"\n type=\"button\"\n variant=\"primary\"\n >\n Approve\n </Button>\n )}\n </div>\n )}\n\n {/* Tool call ID */}\n <p className=\"font-mono text-[10px] text-sf-subtle/50\">\n {part.toolCallId}\n </p>\n </div>\n </BaseCollapsible.Panel>\n </div>\n </BaseCollapsible.Root>\n );\n}\n\n// ─── AiToolCallGroup ─────────────────────────────────────────────────────────\n\n/**\n * Collapsible group of tool calls. Shows a summary header with progress\n * counter and stacked tool icons when collapsed. Renders a single tool\n * directly if only one part is provided.\n *\n * @example\n * ```tsx\n * <AiToolCallGroup parts={toolParts} label=\"3 tools ran\" />\n * ```\n */\nexport function AiToolCallGroup({\n parts,\n label,\n durations = {},\n summaries = {},\n approvalStates = {},\n onApprove,\n onReject,\n defaultExpanded = false,\n className,\n ...props\n}: AiToolCallGroupProps) {\n const [isExpanded, setIsExpanded] = useState(defaultExpanded);\n\n const completedCount = parts.filter(\n (p) => p.state === \"output-available\"\n ).length;\n const errorCount = parts.filter(hasToolError).length;\n const totalCount = parts.length;\n\n // Single item — render directly\n if (parts.length === 1 && parts[0]) {\n const singlePart = parts[0];\n // Strip `part` from div props spread to avoid collision with HTML `part` attribute\n const { part: _part, ...restProps } = props as typeof props & {\n part?: unknown;\n };\n return (\n <AiToolCall\n part={singlePart}\n duration={durations[singlePart.toolCallId]}\n summary={summaries[singlePart.toolCallId]}\n approvalState={approvalStates[singlePart.toolCallId]}\n onApprove={\n onApprove ? () => onApprove(singlePart.toolCallId) : undefined\n }\n onReject={onReject ? () => onReject(singlePart.toolCallId) : undefined}\n className={className}\n {...restProps}\n />\n );\n }\n\n return (\n <BaseCollapsible.Root open={isExpanded} onOpenChange={setIsExpanded}>\n <div\n className={cn(\n \"my-1.5 w-full overflow-hidden rounded-lg bg-sf-tint/50\",\n \"animate-in fade-in-0 slide-in-from-bottom-2 duration-200\",\n className\n )}\n {...props}\n >\n <BaseCollapsible.Trigger\n render={\n <button\n type=\"button\"\n className=\"flex w-fit items-center gap-3 px-3 py-2.5 text-left transition-colors hover:bg-sf-tint\"\n />\n }\n >\n <span className=\"text-sm font-medium text-sf-subtle\">\n {label ?? `${totalCount} tool calls`}\n </span>\n <span className=\"text-xs text-sf-subtle\">\n {completedCount}/{totalCount}\n {errorCount > 0 ? (\n <span className=\"ml-1 text-sf-danger\">({errorCount} errors)</span>\n ) : null}\n </span>\n\n <div className=\"flex items-center gap-2\">\n {/* Tool icon stack when collapsed */}\n {!isExpanded ? (\n <div className=\"flex -space-x-1\">\n {parts.slice(0, 5).map((p) => {\n const Icon = getToolIcon(p);\n return (\n <div\n key={p.toolCallId}\n className={cn(\n \"flex size-6 items-center justify-center rounded-full border-2 border-sf-elevated\",\n p.state === \"output-available\" &&\n !hasToolError(p) &&\n \"bg-sf-tint\",\n isToolLoading(p) && \"bg-sf-tint\",\n hasToolError(p) && \"bg-sf-danger/20\"\n )}\n >\n <Icon className=\"size-3\" />\n </div>\n );\n })}\n {parts.length > 5 ? (\n <div className=\"flex size-6 items-center justify-center rounded-full border-2 border-sf-elevated bg-sf-tint text-[10px] text-sf-subtle\">\n +{parts.length - 5}\n </div>\n ) : null}\n </div>\n ) : null}\n <CaretDownIcon\n className={cn(\n \"size-4 text-sf-subtle transition-transform duration-200\",\n !isExpanded && \"-rotate-90\"\n )}\n />\n </div>\n </BaseCollapsible.Trigger>\n\n <BaseCollapsible.Panel className=\"overflow-hidden\">\n <div className=\"space-y-2 border-t border-sf-line/20 px-3 pt-1 pb-3\">\n {parts.map((p) => (\n <AiToolCall\n key={p.toolCallId}\n part={p}\n variant=\"inline\"\n duration={durations[p.toolCallId]}\n summary={summaries[p.toolCallId]}\n approvalState={approvalStates[p.toolCallId]}\n onApprove={\n onApprove ? () => onApprove(p.toolCallId) : undefined\n }\n onReject={onReject ? () => onReject(p.toolCallId) : undefined}\n />\n ))}\n </div>\n </BaseCollapsible.Panel>\n </div>\n </BaseCollapsible.Root>\n );\n}\n\n// ─── AiToolCallTimeline ──────────────────────────────────────────────────────\n\n/**\n * Horizontal timeline view of tool calls. Each tool is shown as a small\n * icon pill with a tooltip showing its name, duration, and any errors.\n *\n * @example\n * ```tsx\n * <AiToolCallTimeline parts={toolParts} durations={{ id1: 230, id2: 540 }} />\n * ```\n */\nexport function AiToolCallTimeline({\n parts,\n durations = {},\n className,\n ...props\n}: AiToolCallTimelineProps) {\n return (\n <div\n className={cn(\"flex flex-wrap items-center gap-1 py-2\", className)}\n {...props}\n >\n {parts.map((part, index) => {\n const IconComponent = getToolIcon(part);\n const displayName = getToolDisplayName(part);\n const config = getStateConfig(part);\n const duration = durations[part.toolCallId];\n const errorText = getToolErrorText(part);\n\n const tooltipLines = [\n displayName,\n duration ? `${duration}ms` : null,\n errorText ?? null,\n ]\n .filter(Boolean)\n .join(\" · \");\n\n return (\n <div key={part.toolCallId} className=\"flex items-center\">\n <Tooltip content={tooltipLines}>\n <div\n className={cn(\n \"flex cursor-default items-center gap-1.5 rounded-md px-2 py-1 transition-all\",\n config.bgClass\n )}\n >\n <IconComponent className={cn(\"size-3.5\", config.colorClass)} />\n {part.state === \"input-streaming\" ? (\n <CircleIcon\n className={cn(\"size-3 animate-pulse\", config.colorClass)}\n />\n ) : null}\n {part.state === \"input-available\" ? (\n <SpinnerGapIcon\n className={cn(\"size-3 animate-spin\", config.colorClass)}\n />\n ) : null}\n {hasToolResult(part) && !hasToolError(part) ? (\n <CheckIcon className={cn(\"size-3\", config.colorClass)} />\n ) : null}\n {hasToolError(part) ? (\n <WarningCircleIcon\n className={cn(\"size-3\", config.colorClass)}\n />\n ) : null}\n </div>\n </Tooltip>\n\n {/* Connector line */}\n {index < parts.length - 1 ? (\n <div\n className={cn(\n \"mx-0.5 h-px w-3\",\n (() => {\n const nextPart = parts[index + 1];\n if (!nextPart) return \"bg-sf-line\";\n return hasToolResult(nextPart) || isToolLoading(nextPart)\n ? \"bg-sf-success/50\"\n : \"bg-sf-line\";\n })()\n )}\n />\n ) : null}\n </div>\n );\n })}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"chart-Bes4MN3C.js","names":[],"sources":["../src/components/chart/color.ts","../src/components/chart/echart.tsx","../src/components/chart/timeseries-chart.tsx","../src/components/chart/legend.tsx"],"sourcesContent":["/**\n * Categorical colors for light mode — used when assigning colors to data series\n * by index (e.g. the first series gets Blue, the second gets Violet, etc.).\n */\nenum ChartCategoricalLightColors {\n Blue = \"#086FFF\",\n Violet = \"#CF7EE9\",\n Cyan = \"#73CEE6\",\n Indigo = \"#5B5FEF\",\n LightBlue = \"#82B6FF\",\n Pink = \"#F5609F\",\n Indigo3 = \"#C2BDF3\",\n Violet2 = \"#8D1EB1\",\n Violet3 = \"#EBCAF6\",\n Indigo2 = \"#7366E4\",\n}\n\n/**\n * Categorical colors for dark mode — same hues as the light palette but with\n * `E6` alpha (90% opacity) appended to soften contrast on dark backgrounds.\n */\nenum ChartCategoricalDarkColors {\n Blue = \"#086FFFE6\",\n Violet = \"#CF7EE9E6\",\n Cyan = \"#73CEE6E6\",\n Indigo = \"#5B5FEFE6\",\n LightBlue = \"#82B6FFE6\",\n Pink = \"#F5609FE6\",\n Indigo3 = \"#C2BDF3E6\",\n Violet2 = \"#8D1EB1E6\",\n Violet3 = \"#EBCAF6E6\",\n Indigo2 = \"#7366E4E6\",\n}\n\n/**\n * Semantic colors for light mode — used to convey meaning (status, severity)\n * rather than just distinguishing series. Use via `ChartPalette.semantic()`.\n */\nenum ChartSemanticLightColors {\n Attention = \"#FC574A\",\n Warning = \"#F8A054\",\n Neutral = \"#82B6FF\",\n NeutralLight = \"#B9D6FF\",\n Disabled = \"#B6B6B6\",\n DisabledLight = \"#D9D9D9\",\n}\n\n/**\n * Semantic colors for dark mode — same meanings as the light palette but with\n * `E6` alpha (90% opacity) for dark backgrounds.\n */\nenum ChartSemanticDarkColors {\n Attention = \"#FC574AE6\",\n Warning = \"#F8A054E6\",\n Neutral = \"#82B6FFE6\",\n NeutralLight = \"#B9D6FFE6\",\n Disabled = \"#B6B6B6E6\",\n DisabledLight = \"#D9D9D9E6\",\n}\n\n/**\n * Ordered list of categorical colors for light mode, indexed by series position.\n * Used as the default ECharts color palette when `isDarkMode` is `false`.\n */\nexport const CHART_LIGHT_COLORS = [\n ChartCategoricalLightColors.Blue,\n ChartCategoricalLightColors.Violet,\n ChartCategoricalLightColors.Cyan,\n ChartCategoricalLightColors.Indigo,\n ChartCategoricalLightColors.LightBlue,\n ChartCategoricalLightColors.Pink,\n ChartCategoricalLightColors.Indigo3,\n ChartCategoricalLightColors.Violet2,\n ChartCategoricalLightColors.Violet3,\n ChartCategoricalLightColors.Indigo2,\n];\n\n/**\n * Ordered list of categorical colors for dark mode, indexed by series position.\n * Used as the default ECharts color palette when `isDarkMode` is `true`.\n */\nexport const CHART_DARK_COLORS = [\n ChartCategoricalDarkColors.Blue,\n ChartCategoricalDarkColors.Violet,\n ChartCategoricalDarkColors.Cyan,\n ChartCategoricalDarkColors.Indigo,\n ChartCategoricalDarkColors.LightBlue,\n ChartCategoricalDarkColors.Pink,\n ChartCategoricalDarkColors.Indigo3,\n ChartCategoricalDarkColors.Violet2,\n ChartCategoricalDarkColors.Violet3,\n ChartCategoricalDarkColors.Indigo2,\n];\n\n/**\n * Returns the hex color for a named semantic value (status, severity, etc.).\n *\n * @example\n * ```ts\n * ChartPalette.semantic(\"Attention\") // \"#FC574A\" (light)\n * ChartPalette.semantic(\"Warning\", true) // \"#F8A054E6\" (dark)\n * ```\n */\nfunction semantic(\n name:\n | \"Attention\"\n | \"Warning\"\n | \"Neutral\"\n | \"NeutralLight\"\n | \"Disabled\"\n | \"DisabledLight\",\n isDarkMode = false\n) {\n return isDarkMode\n ? ChartSemanticDarkColors[name]\n : ChartSemanticLightColors[name];\n}\n\n/**\n * Returns the categorical color for a given series index.\n * Wraps around via modulo when `index` exceeds the palette length (10 colors).\n *\n * @example\n * ```ts\n * ChartPalette.color(0) // Blue (light)\n * ChartPalette.color(0, true) // Blue with E6 alpha (dark)\n * ChartPalette.color(10) // wraps back to Blue\n * ```\n */\nfunction color(index: number, isDarkMode = false) {\n return isDarkMode\n ? CHART_DARK_COLORS[index % CHART_DARK_COLORS.length]\n : CHART_LIGHT_COLORS[index % CHART_LIGHT_COLORS.length];\n}\n\n/**\n * Utilities for resolving SignalFlare chart colors by semantic name or series index.\n * Both functions accept an `isDarkMode` flag and return the appropriate hex color.\n */\nexport const ChartPalette = { semantic, color };\n","import type { EChartsOption, SetOptionOpts } from \"echarts\";\nimport type * as echarts from \"echarts/core\";\nimport { forwardRef, useEffect, useRef } from \"react\";\n\nimport { cn } from \"../../utils\";\nimport { CHART_DARK_COLORS, CHART_LIGHT_COLORS } from \"./color\";\n\n/** Parameters passed to mouse event handlers on chart elements */\ntype EChartsMouseEventParams = {\n /** The type of component that triggered the event (e.g. \"series\", \"markPoint\") */\n componentType: string;\n /** Series type (e.g. \"line\", \"bar\") — present when componentType is \"series\" */\n seriesType?: string;\n /** Zero-based index of the series in the option.series array */\n seriesIndex?: number;\n /** Name of the series */\n seriesName?: string;\n /** Name of the data item */\n name?: string;\n /** Zero-based index of the data item within its series */\n dataIndex?: number;\n /** Raw data item value */\n data?: any;\n /** Sub-type of data (e.g. \"node\", \"edge\" for graph series) */\n dataType?: string;\n /** Numeric or array value of the data item */\n value?: number | any[];\n /** Resolved color of the series or data item */\n color?: string;\n};\n\n/**\n * ECharts event handlers that can be attached to a `Chart`.\n * Pass a subset via the `onEvents` prop; handlers are registered lazily and\n * cleaned up automatically when removed or when the chart is unmounted.\n */\nexport interface ChartEvents {\n // Mouse events — fired on chart elements (series, marks, etc.)\n click: (params: EChartsMouseEventParams) => void;\n dblclick: (params: EChartsMouseEventParams) => void;\n mousedown: (params: EChartsMouseEventParams) => void;\n mousemove: (params: EChartsMouseEventParams) => void;\n mouseup: (params: EChartsMouseEventParams) => void;\n mouseover: (params: EChartsMouseEventParams) => void;\n mouseout: (params: EChartsMouseEventParams) => void;\n /** Fired when the pointer leaves the chart canvas entirely */\n globalout: (params: any) => void;\n contextmenu: (params: any) => void;\n\n // Legend events\n /** Fired when any legend item's selected state changes */\n legendselectchanged: (params: {\n name: string;\n /** Map of series name → selected state for all legend items */\n selected: Record<string, boolean>;\n }) => void;\n legendselected: (params: any) => void;\n legendunselected: (params: any) => void;\n legendscroll: (params: any) => void;\n\n // Data zoom / timeline events\n datazoom: (params: any) => void;\n datarangeselected: (params: any) => void;\n timelinechanged: (params: any) => void;\n timelineplaychanged: (params: any) => void;\n\n // Toolbox events\n restore: (params: any) => void;\n dataviewchanged: (params: any) => void;\n magictypechanged: (params: any) => void;\n\n // Pie chart selection events\n pieselectchanged: (params: any) => void;\n pieselected: (params: any) => void;\n pieunselected: (params: any) => void;\n\n // Map / geo selection events\n mapselectchanged: (params: any) => void;\n mapselected: (params: any) => void;\n mapunselected: (params: any) => void;\n geoselectchanged: (params: any) => void;\n geoselected: (params: any) => void;\n geounselected: (params: any) => void;\n\n axisareaselected: (params: any) => void;\n\n // Brush / selection events\n brush: (params: any) => void;\n brushselected: (params: any) => void;\n /** Fired when the user finishes drawing a brush selection */\n brushend: (params: {\n areas: Array<{\n /** Coordinate range covered by the brush — interpretation depends on axis type */\n coordRange: any;\n brushType?: string;\n panelId?: string;\n range?: any;\n }>;\n }) => void;\n}\n\n/** Props for the low-level `Chart` wrapper around Apache ECharts */\nexport interface ChartProps {\n /**\n * The ECharts core instance imported by the consumer.\n * Passed in rather than imported directly so the consumer controls which\n * ECharts modules are bundled (tree-shaking).\n */\n echarts: typeof echarts;\n /** ECharts option object — passed through to `chart.setOption()` */\n options: EChartsOption;\n /**\n * Additional options passed as the second argument to `chart.setOption()`.\n * Defaults to `{ notMerge: false, lazyUpdate: true }`.\n */\n optionUpdateBehavior?: SetOptionOpts;\n /** Additional CSS classes applied to the chart container `<div>` */\n className?: string;\n /**\n * When `true`, initialises ECharts with its built-in dark theme.\n * Changing this value after mount destroys and re-creates the chart instance.\n */\n isDarkMode?: boolean;\n /** Height of the chart container in pixels. Defaults to `350`. */\n height?: number;\n /** Subset of ECharts events to listen for. Handlers are bound/unbound reactively. */\n onEvents?: Partial<ChartEvents>;\n}\n\n/**\n * Chart — a low-level wrapper around [Apache ECharts](https://echarts.apache.org).\n *\n * Manages the ECharts instance lifecycle (init, option updates, event binding,\n * resize observation, and disposal). Exposes the raw `echarts.ECharts` instance\n * via `ref` for imperative access when needed.\n *\n * Prefer `TimeseriesChart` for time-series data; use this component when you\n * need full control over the ECharts option object.\n *\n * @example\n * ```tsx\n * import * as echarts from \"echarts/core\";\n * import { BarChart } from \"echarts/charts\";\n * import { GridComponent } from \"echarts/components\";\n * import { CanvasRenderer } from \"echarts/renderers\";\n *\n * echarts.use([BarChart, GridComponent, CanvasRenderer]);\n *\n * <Chart\n * echarts={echarts}\n * options={{ xAxis: { data: [\"A\", \"B\"] }, yAxis: {}, series: [{ type: \"bar\", data: [1, 2] }] }}\n * />\n * ```\n */\nexport const Chart = forwardRef<echarts.ECharts, ChartProps>(function Chart(\n {\n echarts,\n options,\n optionUpdateBehavior,\n className,\n isDarkMode,\n height = 350,\n onEvents,\n }: ChartProps,\n ref\n) {\n // Ref to the container DOM node that ECharts renders into\n const elRef = useRef<HTMLDivElement | null>(null);\n // Ref to the active ECharts instance\n const chartRef = useRef<echarts.ECharts | null>(null);\n // Keeps the latest onEvents object without triggering re-binding on every render\n const handlersRef = useRef<Partial<ChartEvents>>({});\n // Stable wrapper functions per event name — avoids creating new closures on re-render\n const wrappersRef = useRef<Record<string, (params: any) => void>>({});\n // Tracks which event names are currently bound to the chart instance\n const boundEventsRef = useRef<Set<string>>(new Set());\n\n // Init and cleanup\n useEffect(() => {\n if (!elRef.current) return;\n\n const chart = echarts.init(\n elRef.current,\n isDarkMode\n ? \"dark\"\n : {\n color: isDarkMode ? CHART_DARK_COLORS : CHART_LIGHT_COLORS,\n }\n );\n chartRef.current = chart;\n\n if (typeof ref === \"function\") ref(chart);\n else if (ref) ref.current = chart;\n\n return () => {\n for (const event of boundEventsRef.current) {\n const wrapper = wrappersRef.current[event];\n if (wrapper) chart.off(event, wrapper);\n }\n boundEventsRef.current.clear();\n if (typeof ref === \"function\") ref(null);\n else if (ref) ref.current = null;\n chartRef.current = null;\n chart.dispose();\n };\n }, [elRef, isDarkMode]);\n\n // Update options\n useEffect(() => {\n const chart = chartRef.current;\n if (!chart) return;\n\n chart.setOption(options, {\n notMerge: false,\n lazyUpdate: true,\n ...optionUpdateBehavior,\n });\n }, [isDarkMode, optionUpdateBehavior, options]);\n\n // Keep handlersRef in sync so wrapper closures always call the latest handler\n // without needing to re-bind listeners on every render\n useEffect(() => {\n handlersRef.current = onEvents ?? {};\n }, [onEvents]);\n\n // Reactively bind and unbind event listeners when onEvents changes.\n // Uses stable wrapper functions (wrappersRef) so the same function reference\n // is passed to both chart.on() and chart.off(), which ECharts requires.\n useEffect(() => {\n const chart = chartRef.current;\n if (!chart) return;\n\n const nextBound = new Set<string>();\n\n for (const [event, handler] of Object.entries(onEvents ?? {})) {\n if (typeof handler !== \"function\") continue;\n nextBound.add(event);\n\n if (!wrappersRef.current[event]) {\n wrappersRef.current[event] = (params: any) => {\n const current = handlersRef.current as Record<\n string,\n ((p: any) => void) | undefined\n >;\n current[event]?.(params);\n };\n }\n\n if (!boundEventsRef.current.has(event)) {\n chart.on(event, wrappersRef.current[event]);\n }\n }\n\n for (const event of boundEventsRef.current) {\n if (nextBound.has(event)) continue;\n const wrapper = wrappersRef.current[event];\n if (wrapper) {\n chart.off(event, wrapper);\n }\n }\n\n boundEventsRef.current = nextBound;\n }, [echarts, isDarkMode, onEvents]);\n\n // Resize handling\n useEffect(() => {\n const chart = chartRef.current;\n const el = elRef.current;\n if (!chart || !el) return;\n\n // Flag to skip the very first trigger\n let isInitial = true;\n\n const ro = new ResizeObserver(() => {\n if (isInitial) {\n isInitial = false;\n return; // Skip the first resize to let the animation play\n }\n chart.resize();\n });\n\n ro.observe(el);\n\n return () => ro.disconnect();\n }, []);\n\n return (\n <div\n ref={elRef}\n className={cn(\"w-full\", className)}\n style={{ height }}\n tabIndex={options.aria?.enabled ? 0 : undefined}\n role={options.aria?.enabled ? \"img\" : undefined}\n />\n );\n});\n\nChart.displayName = \"Chart\";\n","import type { EChartsOption } from \"echarts\";\nimport type { LineSeriesOption, BarSeriesOption } from \"echarts/charts\";\nimport type * as echarts from \"echarts/core\";\nimport { useEffect, useMemo, useRef } from \"react\";\n\nimport { Chart, ChartEvents } from \"./echart\";\n\n/** A single data series rendered on a `TimeseriesChart` */\nexport interface TimeseriesData {\n /** Display name shown in tooltips and legends */\n name: string;\n /** Array of `[timestamp_ms, value]` tuples ordered by time */\n data: [number, number][];\n /** Hex color string used for this series' line, bars, and legend dot */\n color: string;\n}\n\n/** Props for `TimeseriesChart` */\nexport interface TimeseriesChartProps {\n /**\n * The ECharts core instance imported by the consumer.\n * Passed in rather than imported directly so the consumer controls which\n * ECharts modules are bundled (tree-shaking).\n */\n echarts: typeof echarts;\n /** Visual style of each series. Defaults to `\"line\"`. */\n type?: \"line\" | \"bar\";\n /** Array of time series data to display on the chart */\n data: TimeseriesData[];\n /** Label for the x-axis (time axis) */\n xAxisName?: string;\n /** Number of ticks to display on the x-axis */\n xAxisTickCount?: number;\n /**\n * Custom formatter for x-axis tick labels.\n * Receives the raw timestamp in milliseconds and returns a display string,\n * overriding ECharts' built-in time formatting.\n */\n xAxisTickFormat?: (value: number) => string;\n /**\n * Custom formatter for y-axis tick labels.\n * Receives the raw value and returns a display string.\n * When omitted, ECharts' built-in formatter is used.\n */\n yAxisTickFormat?: (value: number) => string;\n /**\n * @deprecated Use `tooltipValueFormat` instead. This prop formats tooltip\n * values, not y-axis tick labels. It will be removed in a future major version.\n */\n yAxisTickLabelFormat?: (value: number) => string;\n /** Label for the y-axis (value axis) */\n yAxisName?: string;\n /** Number of ticks to display on the y-axis */\n yAxisTickCount?: number;\n /**\n * Custom formatter for tooltip values.\n * Receives the raw y-value and returns a display string.\n * When omitted, the raw value is shown. Takes precedence over the\n * deprecated `yAxisTickLabelFormat` prop.\n */\n tooltipValueFormat?: (value: number) => string;\n /** Indicates incomplete data periods with optional before/after timestamps in ms */\n incomplete?: { before?: number; after?: number };\n /** Height of the chart in pixels. Defaults to `350`. */\n height?: number;\n /** Callback fired when user selects a time range via brush selection */\n onTimeRangeChange?: (from: number, to: number) => void;\n /** When `true`, switches the chart to ECharts' built-in dark theme */\n isDarkMode?: boolean;\n /**\n * When `true`, renders a vertical gradient fill beneath each line series.\n * The gradient fades from the series' color at the top to transparent at the bottom.\n * Has no effect when `type` is `\"bar\"`.\n */\n gradient?: boolean;\n /**\n * When `true`, hides the chart and displays an animated sine-wave skeleton\n * that oscillates back and forth to indicate that data is being fetched.\n */\n loading?: boolean;\n /**\n * Accessible description for screen readers. When provided, it is passed to\n * ECharts' `aria.label.description` and announced when the chart receives\n * focus. Consumers are responsible for writing a meaningful description —\n * see the W3C guidance on complex images for recommendations.\n *\n * @see https://www.w3.org/WAI/tutorials/images/complex/\n * @see https://echarts.apache.org/handbook/en/best-practices/aria/\n */\n ariaDescription?: string;\n}\n\n/**\n * TimeseriesChart — a time-series line or bar chart.\n *\n * Built on `Chart` (Apache ECharts) with opinionated defaults for time-series data:\n * a time-typed x-axis, dashed lines for incomplete data periods, brush-based\n * time range selection, and automatic tooltip deduplication.\n *\n * @example\n * ```tsx\n * import * as echarts from \"echarts/core\";\n * import { LineChart } from \"echarts/charts\";\n * import { GridComponent, TooltipComponent, BrushComponent, ToolboxComponent } from \"echarts/components\";\n * import { CanvasRenderer } from \"echarts/renderers\";\n *\n * echarts.use([LineChart, GridComponent, TooltipComponent, BrushComponent, ToolboxComponent, CanvasRenderer]);\n *\n * const [range, setRange] = useState<[number, number]>();\n *\n * <TimeseriesChart\n * echarts={echarts}\n * data={[{ name: \"Requests\", data: [[Date.now(), 42]], color: \"#086FFF\" }]}\n * xAxisName=\"Time\"\n * xAxisTickFormat={(ts) => new Date(ts).toLocaleTimeString()}\n * yAxisName=\"Count\"\n * yAxisTickFormat={(value) => `${value / 1000}k`}\n * tooltipValueFormat={(value) => `${value.toFixed(2)} req/s`}\n * onTimeRangeChange={(from, to) => setRange([from, to])}\n * />\n * ```\n */\nexport function TimeseriesChart({\n echarts,\n type = \"line\",\n data,\n xAxisName,\n xAxisTickCount,\n xAxisTickFormat,\n yAxisTickFormat,\n yAxisTickLabelFormat,\n yAxisName,\n yAxisTickCount,\n tooltipValueFormat,\n onTimeRangeChange,\n height = 350,\n incomplete,\n isDarkMode,\n gradient,\n loading,\n ariaDescription,\n}: TimeseriesChartProps) {\n const chartRef = useRef<echarts.ECharts | null>(null);\n const incompleteBefore = incomplete?.before;\n const incompleteAfter = incomplete?.after;\n\n const options = useMemo(() => {\n const transformSeries: Array<LineSeriesOption | BarSeriesOption> = [];\n\n const seriesType =\n type === \"bar\"\n ? ({ type: \"bar\", stack: \"total\" } as const)\n : ({ type: \"line\", showSymbol: false } as const);\n\n for (const s of data) {\n const incompleteBeforePoints =\n incompleteBefore && type === \"line\"\n ? s.data.filter((point) => point[0] <= incompleteBefore)\n : [];\n\n const incompleteAfterPoints =\n incompleteAfter && type === \"line\"\n ? s.data.filter((point) => point[0] >= incompleteAfter)\n : [];\n\n const completePoints =\n incompleteBeforePoints.length > 0 || incompleteAfterPoints.length > 0\n ? s.data.slice(\n Math.max(0, incompleteBeforePoints.length - 1),\n Math.max(0, s.data.length - incompleteAfterPoints.length + 1)\n )\n : s.data;\n\n // Main complete data series\n const areaStyle =\n gradient && type === \"line\"\n ? {\n color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [\n { offset: 0, color: colorWithOpacity(s.color, 0.4) },\n { offset: 1, color: colorWithOpacity(s.color, 0) },\n ]),\n }\n : undefined;\n\n transformSeries.push({\n data: completePoints,\n color: s.color,\n name: s.name,\n emphasis: { focus: \"series\" },\n ...(areaStyle ? { areaStyle } : {}),\n ...seriesType,\n });\n\n // Incomplete data series with dashed lines\n const incompleteSeriesConfig = {\n color: s.color,\n name: s.name,\n type: \"line\" as const,\n lineStyle: { type: \"dashed\" as const },\n showSymbol: false,\n emphasis: { focus: \"series\" as const },\n };\n\n if (incompleteBeforePoints.length > 0) {\n transformSeries.push({\n ...incompleteSeriesConfig,\n data: incompleteBeforePoints,\n });\n }\n\n if (incompleteAfterPoints.length > 0) {\n transformSeries.push({\n ...incompleteSeriesConfig,\n data: incompleteAfterPoints,\n });\n }\n }\n\n return {\n aria: {\n enabled: true,\n ...(ariaDescription && { label: { description: ariaDescription } }),\n },\n brush: {\n snapToData: true,\n xAxisIndex: \"all\" as const,\n brushType: \"lineX\" as const,\n brushMode: \"single\" as const,\n outOfBrush: {\n colorAlpha: 0.3,\n },\n brushStyle: {\n borderWidth: 1,\n color: \"rgba(120,140,180,0.3)\",\n borderColor: \"rgba(120,140,180,0.8)\",\n },\n },\n tooltip: {\n trigger: \"axis\" as const,\n axisPointer: { type: \"shadow\" as const },\n formatter: (params: any) => {\n const items = Array.isArray(params) ? params : [params];\n\n // Track seen series names to avoid duplicates in tooltip\n // This is needed because incomplete data series (dashed lines) and complete data series\n // can overlap at the same timestamp, causing duplicate entries in the tooltip\n const seenNames = new Set<string>();\n const filteredParams = items.filter((param: any) => {\n if (seenNames.has(param.seriesName)) return false;\n seenNames.add(param.seriesName);\n return true;\n });\n\n const first = filteredParams[0];\n const ts = first?.value?.[0] ?? first?.axisValue;\n const header =\n ts != null\n ? `<div style=\"font-weight:600;margin-bottom:4px;\">${formatTimestamp(ts)}</div>`\n : \"\";\n\n const rows = filteredParams\n .map((param: any) => {\n const value = param?.value?.[1];\n const formatFn = tooltipValueFormat ?? yAxisTickLabelFormat;\n return `${param.marker} ${param.seriesName}: <strong>${formatFn ? formatFn(value) : value}</strong>`;\n })\n .join(\"<br/>\");\n\n return `${header}${rows}`;\n },\n },\n backgroundColor: \"transparent\",\n toolbox: { show: false },\n xAxis: {\n name: xAxisName,\n nameLocation: \"middle\" as const,\n nameGap: 30,\n type: \"time\" as const,\n splitLine: {\n show: false,\n },\n axisLine: { show: false },\n splitNumber: xAxisTickCount ?? 5,\n ...(xAxisTickFormat && {\n axisLabel: {\n formatter: (value: number) => xAxisTickFormat(value),\n },\n }),\n },\n yAxis: {\n name: yAxisName,\n nameLocation: \"middle\" as const,\n nameGap: 40,\n type: \"value\" as const,\n axisTick: { show: true },\n axisLabel: {\n margin: 15,\n ...(yAxisTickFormat && {\n formatter: (value: number) => yAxisTickFormat(value),\n }),\n },\n splitLine: {\n show: true,\n lineStyle: { type: \"dashed\" as const, width: 1 },\n },\n splitNumber: yAxisTickCount,\n },\n grid: {\n left: yAxisName ? 30 : 24,\n right: 24,\n top: 24,\n bottom: xAxisName ? 30 : 24,\n },\n series: transformSeries,\n };\n }, [\n data,\n xAxisName,\n xAxisTickCount,\n xAxisTickFormat,\n yAxisTickFormat,\n yAxisTickLabelFormat,\n yAxisName,\n yAxisTickCount,\n tooltipValueFormat,\n incompleteBefore,\n incompleteAfter,\n type,\n gradient,\n echarts,\n ariaDescription,\n ]);\n\n const events = useMemo<Partial<ChartEvents>>(() => {\n if (!onTimeRangeChange) return {};\n\n return {\n brushend: (params) => {\n const range = params.areas[0].coordRange;\n onTimeRangeChange(range[0], range[1]);\n chartRef.current?.dispatchAction({ type: \"brush\", areas: [] });\n },\n };\n }, [onTimeRangeChange]);\n\n // Activate the lineX brush cursor when a time-range callback is provided,\n // and deactivate it on cleanup so the cursor resets when the prop is removed.\n const hasTimeRangeCallback = !!onTimeRangeChange;\n useEffect(() => {\n const chart = chartRef.current;\n if (chart && hasTimeRangeCallback) {\n chart.dispatchAction({\n type: \"takeGlobalCursor\",\n key: \"brush\",\n brushOption: {\n brushType: \"lineX\" as const,\n brushMode: \"single\" as const,\n },\n });\n\n return () => {\n chart.dispatchAction({\n type: \"takeGlobalCursor\",\n key: \"brush\",\n brushOption: {\n brushType: false,\n },\n });\n };\n }\n // `loading` controls whether <Chart> is mounted. When it flips to false,\n // chartRef.current becomes available and the brush cursor must be activated.\n // Without this dep, the effect won't re-run after Chart mounts.\n }, [chartRef, hasTimeRangeCallback, loading]);\n\n return (\n <div className=\"relative w-full\" style={{ height }}>\n {loading && <ChartWaveLoader height={height} isDarkMode={isDarkMode} />}\n {!loading && (\n <Chart\n echarts={echarts}\n ref={chartRef}\n options={options as EChartsOption}\n height={height}\n isDarkMode={isDarkMode}\n onEvents={events}\n />\n )}\n </div>\n );\n}\n\n/**\n * Animated sine-wave skeleton shown while `TimeseriesChart` is in `loading` state.\n * Renders multiple staggered wave paths that sweep continuously left-to-right,\n * mimicking the motion of live time-series data being drawn.\n */\nfunction ChartWaveLoader({\n height,\n isDarkMode,\n}: {\n height: number;\n isDarkMode?: boolean;\n}) {\n const mid = height / 2;\n const amp = Math.min(height * 0.12, 28);\n const period = 400;\n const steps = 120;\n\n const points: string[] = [];\n for (let i = 0; i <= steps; i++) {\n const x = -period + (i / steps) * period * 3;\n const y = mid + Math.sin((i / steps) * 2 * Math.PI * 3) * amp;\n points.push(`${i === 0 ? \"M\" : \"L\"}${x.toFixed(2)},${y.toFixed(2)}`);\n }\n const d = points.join(\" \");\n\n const strokeColor = isDarkMode ? \"rgba(255,255,255,0.5)\" : \"rgba(0,0,0,0.2)\";\n\n return (\n <div\n aria-hidden=\"true\"\n className=\"absolute inset-0 overflow-hidden\"\n style={{ height }}\n >\n <style>{`@keyframes sf-chart-wave{from{transform:translateX(0)}to{transform:translateX(${period}px)}}`}</style>\n <svg\n width=\"100%\"\n height={height}\n viewBox={`0 0 ${period} ${height}`}\n preserveAspectRatio=\"none\"\n className=\"w-full animate-pulse\"\n >\n <path\n d={d}\n fill=\"none\"\n stroke={strokeColor}\n strokeWidth=\"2\"\n style={{\n animation: `sf-chart-wave 2.4s linear infinite`,\n transformOrigin: \"0 0\",\n }}\n />\n </svg>\n </div>\n );\n}\n\n/**\n * Returns an `rgba(r, g, b, alpha)` string for any hex or rgb(a) color input,\n * replacing whatever opacity was already present with the given `alpha` (0–1).\n *\n * Handles:\n * - 6-digit hex: `#RRGGBB`\n * - 8-digit hex: `#RRGGBBAA` ← strips existing alpha\n * - 3-digit hex: `#RGB`\n * - `rgb(r, g, b)`\n * - `rgba(r, g, b, a)` ← replaces existing alpha\n */\nfunction colorWithOpacity(color: string, alpha: number): string {\n const a = Math.max(0, Math.min(1, alpha));\n\n // rgb / rgba\n const rgbMatch = color.match(\n /rgba?\\(\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*,\\s*([\\d.]+)/i\n );\n if (rgbMatch) {\n return `rgba(${rgbMatch[1]}, ${rgbMatch[2]}, ${rgbMatch[3]}, ${a})`;\n }\n\n // hex — strip leading #\n let hex = color.replace(/^#/, \"\");\n\n // expand 3-digit → 6-digit\n if (hex.length === 3) {\n hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];\n }\n\n // strip 8-digit alpha → keep only 6\n if (hex.length === 8) {\n hex = hex.slice(0, 6);\n }\n\n const r = Number.parseInt(hex.slice(0, 2), 16);\n const g = Number.parseInt(hex.slice(2, 4), 16);\n const b = Number.parseInt(hex.slice(4, 6), 16);\n\n return `rgba(${r}, ${g}, ${b}, ${a})`;\n}\n\n/** Zero-pads a number to two digits (e.g. `5` → `\"05\"`) */\nfunction pad(n: number) {\n return n.toString().padStart(2, \"0\");\n}\n\n/**\n * Formats a timestamp as `\"YYYY-MM-DD HH:mm:ss\"` for use in chart tooltips.\n * Accepts a Unix timestamp in milliseconds, an ISO date string, or a `Date` object.\n */\nfunction formatTimestamp(ts: number | string | Date): string {\n const d = new Date(ts);\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;\n}\n","import { cn } from \"../../utils\";\n\n/** Shared props for both legend item variants */\ninterface LegendItemProps {\n /** Series name shown as a label */\n name: string;\n /** Hex color string for the series indicator dot */\n color: string;\n /** Formatted value string to display */\n value: string;\n /** Optional unit label shown after the value (e.g. `\"ms\"`, `\"%\"`) */\n unit?: string;\n /** When `true`, renders the item at 50% opacity to indicate a deselected state */\n inactive?: boolean;\n}\n\n/**\n * Large legend item — stacked layout with a colored dot + series name on top\n * and a large value with an optional small unit below. Use for prominent\n * single-metric displays such as dashboard cards.\n */\nfunction LargeItem({ color, value, name, unit, inactive }: LegendItemProps) {\n return (\n <div className=\"inline-flex flex-col gap-2 min-w-42 py-2\">\n <div className=\"flex items-center gap-2\">\n <span\n className={cn(\"size-2 rounded-full inline-block\", {\n \"opacity-50\": inactive,\n })}\n style={{ backgroundColor: color }}\n />\n <span className={cn(\"text-xs\", { \"opacity-50\": inactive })}>\n {name}\n </span>\n </div>\n <div className=\"flex items-baseline gap-0.5\">\n <span\n className={cn(\"text-lg font-medium leading-none\", {\n \"opacity-50\": inactive,\n })}\n >\n {value}\n </span>\n {unit && (\n <span\n className={cn(\"text-xs text-sf-subtle leading-none\", {\n \"opacity-50\": inactive,\n })}\n >\n {unit}\n </span>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * Small legend item — inline layout with a colored dot, series name, and value\n * on a single row. Use for compact legends below or beside a chart.\n */\nfunction SmallItem({ color, value, name, inactive }: LegendItemProps) {\n return (\n <div className=\"inline-flex items-center gap-2\">\n <span\n className={cn(\"size-2 rounded-full inline-block\", {\n \"opacity-50\": inactive,\n })}\n style={{ backgroundColor: color }}\n />\n <span className={cn(\"text-xs\", { \"opacity-50\": inactive })}>{name}</span>\n <span className={cn(\"text-xs font-medium\", { \"opacity-50\": inactive })}>\n {value}\n </span>\n </div>\n );\n}\n\n/**\n * ChartLegend — pre-built legend item components for use alongside a chart.\n *\n * - `ChartLegend.SmallItem` — compact inline layout; suited for multi-series legends\n * - `ChartLegend.LargeItem` — stacked layout with a large value; suited for single-metric cards\n *\n * @example\n * ```tsx\n * <ChartLegend.SmallItem name=\"Requests\" color=\"#086FFF\" value=\"1,234\" />\n * <ChartLegend.LargeItem name=\"Latency\" color=\"#CF7EE9\" value=\"42\" unit=\"ms\" inactive />\n * ```\n */\nexport const ChartLegend = {\n SmallItem,\n LargeItem,\n};\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"checkbox-CPX7lBaU.js","names":[],"sources":["../src/components/checkbox/checkbox.tsx"],"sourcesContent":["import {\n Checkbox as BaseCheckbox,\n type CheckboxRootChangeEventDetails,\n} from \"@base-ui/react/checkbox\";\nimport { CheckboxGroup as BaseCheckboxGroup } from \"@base-ui/react/checkbox-group\";\nimport { Field as FieldBase } from \"@base-ui/react/field\";\nimport { Fieldset } from \"@base-ui/react/fieldset\";\nimport { CheckIcon, MinusIcon } from \"@phosphor-icons/react\";\nimport { forwardRef, createContext, useContext, type ReactNode } from \"react\";\n\nimport { cn } from \"../../utils/cn\";\nimport { Label } from \"../label\";\n\n/** Checkbox variant definitions mapping variant names to their Tailwind classes. */\nexport const SF_CHECKBOX_VARIANTS = {\n variant: {\n default: {\n classes: \"[&:focus-within>span]:ring-sf-ring [&:hover>span]:ring-sf-ring\",\n description: \"Default checkbox appearance\",\n },\n error: {\n classes: \"[&>span]:ring-sf-danger\",\n description: \"Error state for validation failures\",\n },\n },\n} as const;\n\nexport const SF_CHECKBOX_DEFAULT_VARIANTS = {\n variant: \"default\",\n} as const;\n\n// Derived types from SF_CHECKBOX_VARIANTS\nexport type SFCheckboxVariant = keyof typeof SF_CHECKBOX_VARIANTS.variant;\n\nexport interface SFCheckboxVariantsProps {\n /**\n * Visual variant.\n * - `\"default\"` — Standard checkbox appearance\n * - `\"error\"` — Error state for validation failures\n * @default \"default\"\n */\n variant?: SFCheckboxVariant;\n}\n\nexport function checkboxVariants({\n variant = SF_CHECKBOX_DEFAULT_VARIANTS.variant,\n}: SFCheckboxVariantsProps = {}) {\n return cn(SF_CHECKBOX_VARIANTS.variant[variant].classes);\n}\n\n// Legacy type alias for backwards compatibility\nexport type CheckboxVariant = SFCheckboxVariant;\n\n// Context for passing controlFirst from Group to Items\nconst CheckboxGroupContext = createContext<{ controlFirst: boolean }>({\n controlFirst: true,\n});\n\n/**\n * Single checkbox component props with accessibility guidance.\n *\n * **Accessible Name Required:** Checkbox should have one of:\n * 1. `label` prop (recommended) - built-in Field wrapper with horizontal layout\n * 2. `aria-label` - for checkboxes without visible label\n * 3. `aria-labelledby` - for custom label association\n *\n * **Note:** When used inside Checkbox.Group or Dropdown, label is optional (parent provides context).\n *\n * Missing accessible names will trigger console warnings in development (unless inside a group).\n *\n * @example\n * // Recommended: Built-in Field wrapper with label\n * <Checkbox label=\"Accept terms and conditions\" />\n *\n * @example\n * // Control-first layout (checkbox before label)\n * <Checkbox label=\"Remember me\" controlFirst={true} />\n *\n * @example\n * // Label-first layout (label before checkbox)\n * <Checkbox label=\"Enable notifications\" controlFirst={false} />\n *\n * @example\n * // Error variant (visual only, no error text for single checkboxes)\n * <Checkbox label=\"Required field\" variant=\"error\" />\n *\n * @example\n * // Without visible label (aria-label required)\n * <Checkbox aria-label=\"Select all items\" />\n *\n * @example\n * // Custom label association\n * <label id=\"terms-label\">I accept the terms</label>\n * <Checkbox aria-labelledby=\"terms-label\" />\n *\n * @example\n * // Inside Checkbox.Group (label optional)\n * <Checkbox.Group legend=\"Preferences\">\n * <Checkbox.Item value=\"email\" label=\"Email notifications\" />\n * <Checkbox.Item value=\"sms\" label=\"SMS notifications\" />\n * </Checkbox.Group>\n */\nexport type CheckboxProps = {\n /** Visual variant: \"default\" or \"error\" for validation failures (visual only, no error text) */\n variant?: CheckboxVariant;\n /** Label content for the checkbox (enables built-in Field wrapper) - can be a string or any React node */\n label?: ReactNode;\n /** Tooltip content to display next to the label via an info icon */\n labelTooltip?: ReactNode;\n /** When true (default), checkbox appears before label. When false, label appears before checkbox. */\n controlFirst?: boolean;\n /** Whether the checkbox is checked (controlled) */\n checked?: boolean;\n /** Whether the checkbox is in indeterminate state */\n indeterminate?: boolean;\n /** Whether the checkbox is disabled */\n disabled?: boolean;\n /** Callback when the checked state changes */\n onCheckedChange?: (checked: boolean) => void;\n /** @deprecated Use onCheckedChange instead */\n onValueChange?: (checked: boolean) => void;\n /** @deprecated Use onCheckedChange instead */\n onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;\n /** Click handler */\n onClick?: (event: React.MouseEvent) => void;\n /** Name for form submission */\n name?: string;\n /** Whether the field is required */\n required?: boolean;\n /** Additional class name */\n className?: string;\n /** Accessible label when no visible label is provided */\n \"aria-label\"?: string;\n /** ID of element that labels this checkbox */\n \"aria-labelledby\"?: string;\n};\n\n/**\n * Checkbox group component props (with built-in Fieldset and CheckboxGroup)\n *\n * Usage:\n * ```tsx\n * <Checkbox.Group\n * legend=\"Choose preferences\"\n * defaultValue={['email']}\n * error=\"You must select at least one option\"\n * >\n * <Checkbox.Item label=\"Email notifications\" value=\"email\" />\n * <Checkbox.Item label=\"SMS notifications\" value=\"sms\" />\n * </Checkbox.Group>\n * ```\n */\nexport interface CheckboxGroupProps {\n /** Legend text for the group */\n legend: string;\n /** Child Checkbox.Item components */\n children: ReactNode;\n /** Error message for the group (only appears in groups, not single checkboxes) */\n error?: string;\n /** Helper text for the group */\n description?: ReactNode;\n /** Values of checkboxes that should be initially checked (uncontrolled) */\n defaultValue?: string[];\n /** Values of checkboxes that should be checked (controlled) */\n value?: string[];\n /** Event handler called when checkbox values change */\n onValueChange?: (value: string[]) => void;\n /** All possible checkbox values (required for parent checkbox pattern) */\n allValues?: string[];\n /** Whether all checkboxes in the group are disabled */\n disabled?: boolean;\n /** When true (default), checkbox appears before label. When false, label appears before checkbox. */\n controlFirst?: boolean;\n /** Additional CSS classes */\n className?: string;\n}\n\n/**\n * Individual checkbox item within a group\n */\nexport type CheckboxItemProps = {\n /** Visual variant: \"default\" or \"error\" for validation failures */\n variant?: CheckboxVariant;\n /** Label text displayed next to checkbox */\n label: string;\n /** Value of the checkbox (required when used in Checkbox.Group) */\n value?: string;\n /** Additional CSS classes for the label wrapper */\n className?: string;\n checked?: boolean;\n indeterminate?: boolean;\n disabled?: boolean;\n /** Callback when the checked state changes */\n onCheckedChange?: (checked: boolean) => void;\n /** @deprecated Use onCheckedChange instead */\n onValueChange?: (checked: boolean) => void;\n name?: string;\n};\n\n// Single checkbox with built-in Field\nconst CheckboxBase = forwardRef<HTMLButtonElement, CheckboxProps>(\n (\n {\n className,\n checked,\n indeterminate,\n disabled,\n variant = \"default\",\n label,\n labelTooltip,\n controlFirst = true,\n onCheckedChange,\n onValueChange,\n onChange,\n required,\n name,\n ...props\n },\n ref\n ) => {\n // A11y enforcement: warn in dev if no accessible name provided\n if (process.env.NODE_ENV !== \"production\") {\n const hasLabel = Boolean(label);\n const hasAriaLabel = Boolean(props[\"aria-label\"]);\n const hasAriaLabelledBy = Boolean(props[\"aria-labelledby\"]);\n\n if (!hasLabel && !hasAriaLabel && !hasAriaLabelledBy) {\n console.warn(\n \"[SF Checkbox]: Checkbox must have an accessible name. Provide either:\\n\" +\n \" - label prop: <Checkbox label='Accept terms' />\\n\" +\n \" - aria-label: <Checkbox aria-label='Select item' />\\n\" +\n \" - aria-labelledby for custom label association\\n\" +\n \" Note: When used inside Checkbox.Group, label is optional\"\n );\n }\n }\n\n // Handle onCheckedChange (preferred) and deprecated onValueChange/onChange\n const handleCheckedChange = (\n newChecked: boolean,\n eventDetails: CheckboxRootChangeEventDetails\n ) => {\n onCheckedChange?.(newChecked);\n onValueChange?.(newChecked);\n if (onChange) {\n // Backwards compatibility: extend native event with target.checked\n // so existing code using `e.target.checked` continues to work\n const event = Object.assign(eventDetails.event, {\n target: { checked: newChecked },\n });\n onChange(event as never);\n }\n };\n\n const checkboxControl = (\n <BaseCheckbox.Root\n ref={ref}\n name={name}\n checked={checked}\n indeterminate={indeterminate}\n disabled={disabled}\n onCheckedChange={handleCheckedChange}\n className={cn(\n \"flex h-3.5 w-3.5 items-center justify-center rounded-sm border-0 bg-sf-base ring\",\n variant === \"error\" ? \"ring-sf-danger\" : \"ring-sf-line\",\n !disabled && \"hover:ring-sf-ring focus-visible:ring-sf-ring\",\n \"data-[checked]:bg-sf-contrast data-[checked]:ring-sf-contrast data-[indeterminate]:bg-sf-contrast data-[indeterminate]:ring-sf-contrast\",\n disabled && \"cursor-not-allowed opacity-50\",\n className\n )}\n {...props}\n >\n <BaseCheckbox.Indicator\n className=\"flex items-center justify-center text-sf-inverse\"\n render={(renderProps, state) => {\n const Icon = state.indeterminate ? MinusIcon : CheckIcon;\n return (\n <span {...renderProps}>\n {(state.checked || state.indeterminate) && (\n <Icon weight=\"bold\" size={10} />\n )}\n </span>\n );\n }}\n />\n </BaseCheckbox.Root>\n );\n\n // If no label provided, return bare checkbox (for use in other components like Dropdown)\n if (!label) {\n return checkboxControl;\n }\n\n // Use Field.Root + Field.Label enclosing pattern for proper a11y association\n // See: https://base-ui.com/react/components/field\n return (\n <FieldBase.Root className=\"inline-flex\">\n <FieldBase.Label\n className={cn(\n \"inline-flex items-center gap-2\",\n controlFirst ? \"flex-row\" : \"flex-row-reverse justify-end\",\n disabled ? \"cursor-not-allowed\" : \"cursor-pointer\"\n )}\n >\n {checkboxControl}\n <Label\n showOptional={required === false}\n tooltip={labelTooltip}\n asContent\n >\n {label}\n </Label>\n </FieldBase.Label>\n </FieldBase.Root>\n );\n }\n);\n\nCheckboxBase.displayName = \"Checkbox\";\n\n// Checkbox.Item for use within Checkbox.Group\nconst CheckboxItem = forwardRef<HTMLButtonElement, CheckboxItemProps>(\n (\n {\n className,\n checked,\n indeterminate,\n disabled,\n variant = \"default\",\n label,\n value,\n onCheckedChange,\n onValueChange,\n name,\n },\n ref\n ) => {\n const { controlFirst } = useContext(CheckboxGroupContext);\n\n // Handle onCheckedChange (preferred) and deprecated onValueChange\n const handleCheckedChange = (newChecked: boolean) => {\n onCheckedChange?.(newChecked);\n onValueChange?.(newChecked);\n };\n\n return (\n <label\n className={cn(\n \"relative inline-flex items-center gap-2\",\n // Control first (default): checkbox before label\n // Label first: label before checkbox using flex-row-reverse\n !controlFirst && \"flex-row-reverse justify-end\",\n disabled ? \"cursor-not-allowed opacity-50\" : \"cursor-pointer\",\n className\n )}\n >\n <BaseCheckbox.Root\n ref={ref}\n value={value}\n name={name}\n checked={checked}\n indeterminate={indeterminate}\n disabled={disabled}\n onCheckedChange={handleCheckedChange}\n className={cn(\n \"peer flex h-3.5 w-3.5 items-center justify-center rounded-sm border-0 bg-sf-base ring\",\n variant === \"error\" ? \"ring-sf-danger\" : \"ring-sf-line\",\n !disabled &&\n \"group-hover:ring-sf-ring hover:ring-sf-ring focus-visible:ring-sf-ring\",\n \"data-[checked]:bg-sf-contrast data-[checked]:ring-sf-contrast data-[indeterminate]:bg-sf-contrast data-[indeterminate]:ring-sf-contrast\"\n )}\n >\n <BaseCheckbox.Indicator\n className=\"flex items-center justify-center text-sf-inverse\"\n render={(props, state) => {\n const Icon = state.indeterminate ? MinusIcon : CheckIcon;\n return (\n <span {...props}>\n {(state.checked || state.indeterminate) && (\n <Icon weight=\"bold\" size={10} />\n )}\n </span>\n );\n }}\n />\n </BaseCheckbox.Root>\n <span className=\"text-base font-medium text-sf-default\">{label}</span>\n </label>\n );\n }\n);\n\nCheckboxItem.displayName = \"Checkbox.Item\";\n\n// Checkbox.Group with built-in Fieldset and CheckboxGroup\nfunction CheckboxGroup({\n legend,\n children,\n error,\n description,\n defaultValue,\n value,\n onValueChange,\n allValues,\n disabled,\n controlFirst = true,\n className,\n}: CheckboxGroupProps) {\n return (\n <CheckboxGroupContext.Provider value={{ controlFirst }}>\n <BaseCheckboxGroup\n defaultValue={defaultValue}\n value={value}\n onValueChange={onValueChange}\n allValues={allValues}\n disabled={disabled}\n >\n <Fieldset.Root\n className={cn(\n \"flex flex-col gap-4 rounded-lg border border-sf-line p-4\",\n className\n )}\n >\n <Fieldset.Legend className=\"text-lg font-medium text-sf-default\">\n {legend}\n </Fieldset.Legend>\n <div className=\"flex flex-col gap-2\">{children}</div>\n {error && <p className=\"text-sm text-sf-danger\">{error}</p>}\n {description && (\n <p className=\"text-sm text-sf-subtle\">{description}</p>\n )}\n </Fieldset.Root>\n </BaseCheckboxGroup>\n </CheckboxGroupContext.Provider>\n );\n}\n\n// Compound component\nexport const Checkbox = Object.assign(CheckboxBase, {\n Item: CheckboxItem,\n Group: CheckboxGroup,\n});\n\nCheckbox.displayName = \"Checkbox\";\n"],"mappings":"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"}
|