@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
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { t as cn } from "./cn-YROP2_ox.js";
|
|
3
|
+
import { d as hasToolResult, l as hasToolError } from "./ai-tool-Cn1O4xjP.js";
|
|
4
|
+
import { t as TextRoll } from "./text-roll-BZ3I1umc.js";
|
|
5
|
+
import { Children, isValidElement, useState } from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { CaretDownIcon, SpinnerGapIcon } from "@phosphor-icons/react";
|
|
8
|
+
import { Collapsible } from "@base-ui/react/collapsible";
|
|
9
|
+
//#region src/components/ai-part-group/ai-part-group.tsx
|
|
10
|
+
var snapshotCache = /* @__PURE__ */ new Map();
|
|
11
|
+
/**
|
|
12
|
+
* Reads the frozen snapshot for a group, or `undefined` if it hasn't completed
|
|
13
|
+
* yet. Useful for consumers driving custom layouts.
|
|
14
|
+
*/
|
|
15
|
+
function getAiPartGroupSnapshot(groupKey) {
|
|
16
|
+
return snapshotCache.get(groupKey);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Groups streaming ephemeral activity (tool calls + reasoning) into a single
|
|
20
|
+
* rolling window during streaming, then collapses to a frozen summary line
|
|
21
|
+
* once the parent message completes.
|
|
22
|
+
*
|
|
23
|
+
* Behavior:
|
|
24
|
+
* - **Streaming:** shows the most recent `max` (default 3) ephemeral children.
|
|
25
|
+
* Pinned rows (errors / user-expanded) sit above the live window.
|
|
26
|
+
* - **Completed:** writes a snapshot keyed by `groupKey`, unmounts ephemeral
|
|
27
|
+
* children entirely, and renders a static `▸ {title} · N steps` line.
|
|
28
|
+
* - **Re-mount after completion:** always renders the cached snapshot — no
|
|
29
|
+
* animations, no `useEffect` re-runs, no tool re-execution risk.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```tsx
|
|
33
|
+
* <AiPartGroup
|
|
34
|
+
* groupKey={message.id}
|
|
35
|
+
* complete={message.status === "complete"}
|
|
36
|
+
* title={message.reasoningSummary}
|
|
37
|
+
* max={3}
|
|
38
|
+
* >
|
|
39
|
+
* {message.parts.map((p) =>
|
|
40
|
+
* p.type === "reasoning"
|
|
41
|
+
* ? <AiReasoning key={p.id} variant="ephemeral" part={p} />
|
|
42
|
+
* : <AiToolCall key={p.toolCallId} variant="ephemeral" part={p} />
|
|
43
|
+
* )}
|
|
44
|
+
* </AiPartGroup>
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
function AiPartGroup({ groupKey, complete = false, max = 3, title, children, className, ...props }) {
|
|
48
|
+
const cached = snapshotCache.get(groupKey);
|
|
49
|
+
if (cached) return /* @__PURE__ */ jsx(TitledSummary, {
|
|
50
|
+
snapshot: cached,
|
|
51
|
+
className,
|
|
52
|
+
defaultOpen: false,
|
|
53
|
+
...props
|
|
54
|
+
});
|
|
55
|
+
if (complete) {
|
|
56
|
+
const snapshot = deriveSnapshot(children, title);
|
|
57
|
+
snapshotCache.set(groupKey, snapshot);
|
|
58
|
+
return /* @__PURE__ */ jsx(TitledSummary, {
|
|
59
|
+
snapshot,
|
|
60
|
+
className,
|
|
61
|
+
defaultOpen: false,
|
|
62
|
+
...props
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const allChildren = Children.toArray(children).filter(isValidElement);
|
|
66
|
+
const live = allChildren.slice(Math.max(0, allChildren.length - max));
|
|
67
|
+
const liveTitle = title?.trim() || deriveLiveTitle(allChildren);
|
|
68
|
+
const stepCount = allChildren.length;
|
|
69
|
+
return /* @__PURE__ */ jsx(LiveGroup, {
|
|
70
|
+
className,
|
|
71
|
+
liveRows: live,
|
|
72
|
+
stepCount,
|
|
73
|
+
title: liveTitle,
|
|
74
|
+
...props
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
AiPartGroup.displayName = "AiPartGroup";
|
|
78
|
+
function LiveGroup({ title, liveRows, stepCount, className, ...props }) {
|
|
79
|
+
const stepLabel = stepCount === 1 ? "step" : "steps";
|
|
80
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
81
|
+
className: cn("flex flex-col", className),
|
|
82
|
+
"data-streaming": "true",
|
|
83
|
+
...props,
|
|
84
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
85
|
+
className: cn("flex w-fit items-center gap-1.5 rounded px-1 py-0.5"),
|
|
86
|
+
children: [
|
|
87
|
+
/* @__PURE__ */ jsx(SpinnerGapIcon, { className: "size-3.5 shrink-0 animate-spin text-sf-subtle" }),
|
|
88
|
+
/* @__PURE__ */ jsx("span", {
|
|
89
|
+
className: "min-w-0 truncate text-sm text-sf-default",
|
|
90
|
+
children: /* @__PURE__ */ jsx(TextRoll, {
|
|
91
|
+
duration: .3,
|
|
92
|
+
getEnterDelay: (i) => i * .03,
|
|
93
|
+
getExitDelay: (i) => i * .03 + .12,
|
|
94
|
+
children: title
|
|
95
|
+
}, title)
|
|
96
|
+
}),
|
|
97
|
+
stepCount > 0 ? /* @__PURE__ */ jsxs("span", {
|
|
98
|
+
className: "text-xs tabular-nums text-sf-subtle/60",
|
|
99
|
+
children: [
|
|
100
|
+
stepCount,
|
|
101
|
+
" ",
|
|
102
|
+
stepLabel
|
|
103
|
+
]
|
|
104
|
+
}) : null,
|
|
105
|
+
/* @__PURE__ */ jsx(CaretDownIcon, { className: "size-3 shrink-0 text-sf-subtle/60" })
|
|
106
|
+
]
|
|
107
|
+
}), liveRows.length > 0 ? /* @__PURE__ */ jsx("div", {
|
|
108
|
+
className: "ml-[0.3125rem] flex flex-col gap-0.5 border-l border-sf-line/40 pl-3",
|
|
109
|
+
children: liveRows
|
|
110
|
+
}) : null]
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function TitledSummary({ snapshot, defaultOpen, className, ...props }) {
|
|
114
|
+
const [isOpen, setIsOpen] = useState(defaultOpen);
|
|
115
|
+
const stepLabel = snapshot.steps.length === 1 ? "step" : "steps";
|
|
116
|
+
return /* @__PURE__ */ jsx(Collapsible.Root, {
|
|
117
|
+
open: isOpen,
|
|
118
|
+
onOpenChange: setIsOpen,
|
|
119
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
120
|
+
className: cn("flex flex-col", className),
|
|
121
|
+
"data-streaming": "false",
|
|
122
|
+
...props,
|
|
123
|
+
children: [/* @__PURE__ */ jsxs(Collapsible.Trigger, {
|
|
124
|
+
render: /* @__PURE__ */ jsx("button", {
|
|
125
|
+
type: "button",
|
|
126
|
+
className: cn("flex w-fit items-center gap-1.5 rounded px-1 py-0.5 text-left", "transition-colors hover:bg-sf-tint")
|
|
127
|
+
}),
|
|
128
|
+
children: [
|
|
129
|
+
/* @__PURE__ */ jsx("span", {
|
|
130
|
+
"aria-hidden": true,
|
|
131
|
+
className: cn("size-1.5 shrink-0 rounded-full bg-current", snapshot.errorCount > 0 ? "text-sf-danger" : "text-sf-success")
|
|
132
|
+
}),
|
|
133
|
+
/* @__PURE__ */ jsx("span", {
|
|
134
|
+
className: "min-w-0 truncate text-sm text-sf-subtle",
|
|
135
|
+
children: snapshot.title
|
|
136
|
+
}),
|
|
137
|
+
/* @__PURE__ */ jsxs("span", {
|
|
138
|
+
className: "text-xs tabular-nums text-sf-subtle/60",
|
|
139
|
+
children: [
|
|
140
|
+
snapshot.steps.length,
|
|
141
|
+
" ",
|
|
142
|
+
stepLabel,
|
|
143
|
+
snapshot.errorCount > 0 ? ` · ${snapshot.errorCount} error` : ""
|
|
144
|
+
]
|
|
145
|
+
}),
|
|
146
|
+
/* @__PURE__ */ jsx(CaretDownIcon, { className: cn("size-3 shrink-0 text-sf-subtle/60 transition-transform duration-200", !isOpen && "-rotate-90") })
|
|
147
|
+
]
|
|
148
|
+
}), /* @__PURE__ */ jsx(Collapsible.Panel, {
|
|
149
|
+
className: "overflow-hidden",
|
|
150
|
+
children: /* @__PURE__ */ jsx("ul", {
|
|
151
|
+
className: "mt-1 ml-[0.3125rem] flex flex-col gap-0.5 border-l border-sf-line/40 pl-3",
|
|
152
|
+
children: snapshot.steps.map((step, i) => /* @__PURE__ */ jsxs("li", {
|
|
153
|
+
className: "flex items-center gap-1.5 text-xs text-sf-subtle",
|
|
154
|
+
children: [
|
|
155
|
+
/* @__PURE__ */ jsx("span", {
|
|
156
|
+
"aria-hidden": true,
|
|
157
|
+
className: cn("size-1.5 shrink-0 rounded-full bg-current", step.ok ? "text-sf-success" : "text-sf-danger")
|
|
158
|
+
}),
|
|
159
|
+
/* @__PURE__ */ jsx("span", {
|
|
160
|
+
className: "min-w-0 truncate",
|
|
161
|
+
children: step.name
|
|
162
|
+
}),
|
|
163
|
+
step.duration ? /* @__PURE__ */ jsxs("span", {
|
|
164
|
+
className: "tabular-nums text-sf-subtle/60",
|
|
165
|
+
children: [step.duration, "ms"]
|
|
166
|
+
}) : null
|
|
167
|
+
]
|
|
168
|
+
}, `${step.kind}-${i}-${step.name}`))
|
|
169
|
+
})
|
|
170
|
+
})]
|
|
171
|
+
})
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function deriveLiveTitle(elements) {
|
|
175
|
+
for (const el of elements) {
|
|
176
|
+
const part = el.props.part;
|
|
177
|
+
const text = part?.summary ?? part?.text;
|
|
178
|
+
if (text) return truncate(text, 60);
|
|
179
|
+
}
|
|
180
|
+
return "Working…";
|
|
181
|
+
}
|
|
182
|
+
function deriveSnapshot(children, explicitTitle) {
|
|
183
|
+
const elements = Children.toArray(children).filter(isValidElement);
|
|
184
|
+
const steps = [];
|
|
185
|
+
let errorCount = 0;
|
|
186
|
+
let totalDuration = 0;
|
|
187
|
+
let firstReasoningSnippet;
|
|
188
|
+
for (const el of elements) {
|
|
189
|
+
const kind = inferKind(el);
|
|
190
|
+
const part = el.props.part;
|
|
191
|
+
const duration = el.props.duration;
|
|
192
|
+
if (typeof duration === "number") totalDuration += duration;
|
|
193
|
+
if (kind === "tool") {
|
|
194
|
+
const toolPart = part;
|
|
195
|
+
const ok = toolPart ? !hasToolError(toolPart) : true;
|
|
196
|
+
const settled = toolPart ? hasToolResult(toolPart) : true;
|
|
197
|
+
if (!ok) errorCount += 1;
|
|
198
|
+
steps.push({
|
|
199
|
+
kind: "tool",
|
|
200
|
+
name: toolPart?.toolName ?? "tool",
|
|
201
|
+
ok: ok && settled,
|
|
202
|
+
duration
|
|
203
|
+
});
|
|
204
|
+
} else {
|
|
205
|
+
const text = part?.summary ?? part?.text;
|
|
206
|
+
if (!firstReasoningSnippet && text) firstReasoningSnippet = text;
|
|
207
|
+
steps.push({
|
|
208
|
+
kind: "reasoning",
|
|
209
|
+
name: text ? truncate(text, 60) : "Thinking",
|
|
210
|
+
ok: true,
|
|
211
|
+
duration
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
title: explicitTitle?.trim() || firstReasoningSnippet && truncate(firstReasoningSnippet, 60) || `${steps.length} ${steps.length === 1 ? "step" : "steps"}`,
|
|
217
|
+
steps,
|
|
218
|
+
errorCount,
|
|
219
|
+
totalDuration
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function inferKind(el) {
|
|
223
|
+
const part = el.props.part;
|
|
224
|
+
if (part && typeof part === "object") {
|
|
225
|
+
if ("toolName" in part || "toolCallId" in part) return "tool";
|
|
226
|
+
if ("text" in part || "summary" in part) return "reasoning";
|
|
227
|
+
}
|
|
228
|
+
if ((el.type?.displayName ?? el.type?.name ?? "").includes("Tool")) return "tool";
|
|
229
|
+
return "reasoning";
|
|
230
|
+
}
|
|
231
|
+
function truncate(s, n) {
|
|
232
|
+
if (s.length <= n) return s;
|
|
233
|
+
return `${s.slice(0, n - 1).trimEnd()}…`;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Split a message's parts into alternating `text` and `activity` chunks so each
|
|
237
|
+
* contiguous run of reasoning/tool parts can be rendered inside its own
|
|
238
|
+
* {@link AiPartGroup}, with narrative text rendered between them.
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```tsx
|
|
242
|
+
* {groupParts(message.parts).map((chunk, i) =>
|
|
243
|
+
* chunk.kind === "text" ? (
|
|
244
|
+
* <AiMessageContent key={i}>{joinText(chunk.parts)}</AiMessageContent>
|
|
245
|
+
* ) : (
|
|
246
|
+
* <AiPartGroup
|
|
247
|
+
* key={i}
|
|
248
|
+
* groupKey={`${message.id}:activity:${i}`}
|
|
249
|
+
* complete={!message.isStreaming}
|
|
250
|
+
* >
|
|
251
|
+
* {chunk.parts.map(renderActivityPart)}
|
|
252
|
+
* </AiPartGroup>
|
|
253
|
+
* )
|
|
254
|
+
* )}
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
function groupParts(parts, options = {}) {
|
|
258
|
+
const isActivity = options.isActivity ?? ((p) => {
|
|
259
|
+
const t = p.type;
|
|
260
|
+
return t === "reasoning" || t === "tool-call" || t === "tool-result" || t.startsWith("tool-");
|
|
261
|
+
});
|
|
262
|
+
const chunks = [];
|
|
263
|
+
for (const part of parts) {
|
|
264
|
+
const kind = isActivity(part) ? "activity" : "text";
|
|
265
|
+
const last = chunks.at(-1);
|
|
266
|
+
if (last && last.kind === kind) last.parts.push(part);
|
|
267
|
+
else chunks.push({
|
|
268
|
+
kind,
|
|
269
|
+
parts: [part]
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
return chunks;
|
|
273
|
+
}
|
|
274
|
+
//#endregion
|
|
275
|
+
export { getAiPartGroupSnapshot as n, groupParts as r, AiPartGroup as t };
|
|
276
|
+
|
|
277
|
+
//# sourceMappingURL=ai-part-group-DBtgTgAn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-part-group-DBtgTgAn.js","names":[],"sources":["../src/components/ai-part-group/ai-part-group.tsx"],"sourcesContent":["\"use client\";\n\nimport { Collapsible as BaseCollapsible } from \"@base-ui/react/collapsible\";\nimport { CaretDownIcon, SpinnerGapIcon } from \"@phosphor-icons/react\";\nimport type { ComponentProps, ReactElement, ReactNode } from \"react\";\nimport { Children, isValidElement, useState } from \"react\";\n\nimport { cn } from \"../../utils/cn\";\nimport { type AiToolPart, hasToolError, hasToolResult } from \"../ai-tool\";\nimport { TextRoll } from \"../text-roll\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\ntype FrozenStep = {\n kind: \"tool\" | \"reasoning\";\n name: string;\n ok: boolean;\n duration?: number;\n};\n\ntype FrozenSnapshot = {\n title: string;\n steps: FrozenStep[];\n errorCount: number;\n totalDuration: number;\n};\n\n// Module-level snapshot cache. Survives re-renders within the page session.\n// Keyed by `groupKey` — usually the parent message id.\nconst snapshotCache = new Map<string, FrozenSnapshot>();\n\n/** Test-only escape hatch for clearing the snapshot cache. */\nexport function _clearAiPartGroupCache() {\n snapshotCache.clear();\n}\n\n/**\n * Reads the frozen snapshot for a group, or `undefined` if it hasn't completed\n * yet. Useful for consumers driving custom layouts.\n */\nexport function getAiPartGroupSnapshot(\n groupKey: string\n): FrozenSnapshot | undefined {\n return snapshotCache.get(groupKey);\n}\n\n// ─── Component ───────────────────────────────────────────────────────────────\n\nexport type AiPartGroupProps = Omit<ComponentProps<\"div\">, \"title\"> & {\n /**\n * Stable identifier — usually the parent message id. Required so the group\n * can persist its frozen snapshot across re-mounts (scroll-back into a\n * virtualized list re-renders without restarting animations).\n */\n groupKey: string;\n /**\n * Set to `true` once the parent stream has finished. Triggers the group to\n * snapshot its current children and from then on render only the static\n * collapsed summary — ephemeral children never re-mount.\n */\n complete?: boolean;\n /**\n * Max number of live ephemeral rows visible at once during streaming.\n * Older rows fall off the top. Pinned rows (errors / user-expanded) render\n * above the live window and do not count toward this cap.\n * @default 3\n */\n max?: number;\n /**\n * Title shown in the collapsed summary line. Falls back to the first\n * reasoning text snippet, then to a step count.\n */\n title?: string;\n /** Child ephemeral rows (`AiToolCall variant=\"ephemeral\"`, `AiReasoning variant=\"ephemeral\"`). */\n children?: ReactNode;\n};\n\n/**\n * Groups streaming ephemeral activity (tool calls + reasoning) into a single\n * rolling window during streaming, then collapses to a frozen summary line\n * once the parent message completes.\n *\n * Behavior:\n * - **Streaming:** shows the most recent `max` (default 3) ephemeral children.\n * Pinned rows (errors / user-expanded) sit above the live window.\n * - **Completed:** writes a snapshot keyed by `groupKey`, unmounts ephemeral\n * children entirely, and renders a static `▸ {title} · N steps` line.\n * - **Re-mount after completion:** always renders the cached snapshot — no\n * animations, no `useEffect` re-runs, no tool re-execution risk.\n *\n * @example\n * ```tsx\n * <AiPartGroup\n * groupKey={message.id}\n * complete={message.status === \"complete\"}\n * title={message.reasoningSummary}\n * max={3}\n * >\n * {message.parts.map((p) =>\n * p.type === \"reasoning\"\n * ? <AiReasoning key={p.id} variant=\"ephemeral\" part={p} />\n * : <AiToolCall key={p.toolCallId} variant=\"ephemeral\" part={p} />\n * )}\n * </AiPartGroup>\n * ```\n */\nexport function AiPartGroup({\n groupKey,\n complete = false,\n max = 3,\n title,\n children,\n className,\n ...props\n}: AiPartGroupProps) {\n const cached = snapshotCache.get(groupKey);\n\n // Cache hit: always render the frozen snapshot, never re-mount ephemerals.\n // This is the scroll-back-into-an-old-message path.\n if (cached) {\n return (\n <TitledSummary\n snapshot={cached}\n className={className}\n defaultOpen={false}\n {...props}\n />\n );\n }\n\n // First-time completion: derive a snapshot, persist it, render frozen.\n if (complete) {\n const snapshot = deriveSnapshot(children, title);\n snapshotCache.set(groupKey, snapshot);\n return (\n <TitledSummary\n snapshot={snapshot}\n className={className}\n defaultOpen={false}\n {...props}\n />\n );\n }\n\n // Streaming: persistent title row + rolling window of the latest `max` children.\n // oxlint-disable-next-line no-react-children\n const allChildren = Children.toArray(children).filter(isValidElement);\n const live = allChildren.slice(Math.max(0, allChildren.length - max));\n const liveTitle = title?.trim() || deriveLiveTitle(allChildren);\n const stepCount = allChildren.length;\n\n return (\n <LiveGroup\n className={className}\n liveRows={live}\n stepCount={stepCount}\n title={liveTitle}\n {...props}\n />\n );\n}\n\nAiPartGroup.displayName = \"AiPartGroup\";\n\n// ─── Live group (streaming) ──────────────────────────────────────────────────\n\nfunction LiveGroup({\n title,\n liveRows,\n stepCount,\n className,\n ...props\n}: {\n title: string;\n liveRows: ReactElement[];\n stepCount: number;\n} & Omit<ComponentProps<\"div\">, \"title\">) {\n // During streaming the group is always open — the caret is purely decorative\n // so the frozen/live states share a visual footprint (no layout jump on settle).\n const stepLabel = stepCount === 1 ? \"step\" : \"steps\";\n\n return (\n <div\n className={cn(\"flex flex-col\", className)}\n data-streaming=\"true\"\n {...props}\n >\n <div\n className={cn(\"flex w-fit items-center gap-1.5 rounded px-1 py-0.5\")}\n >\n <SpinnerGapIcon className=\"size-3.5 shrink-0 animate-spin text-sf-subtle\" />\n <span className=\"min-w-0 truncate text-sm text-sf-default\">\n <TextRoll\n duration={0.3}\n getEnterDelay={(i) => i * 0.03}\n getExitDelay={(i) => i * 0.03 + 0.12}\n key={title}\n >\n {title}\n </TextRoll>\n </span>\n {stepCount > 0 ? (\n <span className=\"text-xs tabular-nums text-sf-subtle/60\">\n {stepCount} {stepLabel}\n </span>\n ) : null}\n <CaretDownIcon className=\"size-3 shrink-0 text-sf-subtle/60\" />\n </div>\n {liveRows.length > 0 ? (\n <div className=\"ml-[0.3125rem] flex flex-col gap-0.5 border-l border-sf-line/40 pl-3\">\n {liveRows}\n </div>\n ) : null}\n </div>\n );\n}\n\n// ─── Frozen summary (titled) ─────────────────────────────────────────────────\n\nfunction TitledSummary({\n snapshot,\n defaultOpen,\n className,\n ...props\n}: {\n snapshot: FrozenSnapshot;\n defaultOpen: boolean;\n} & Omit<ComponentProps<\"div\">, \"title\">) {\n const [isOpen, setIsOpen] = useState(defaultOpen);\n const stepLabel = snapshot.steps.length === 1 ? \"step\" : \"steps\";\n\n return (\n <BaseCollapsible.Root open={isOpen} onOpenChange={setIsOpen}>\n <div\n className={cn(\"flex flex-col\", className)}\n data-streaming=\"false\"\n {...props}\n >\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\n aria-hidden\n className={cn(\n \"size-1.5 shrink-0 rounded-full bg-current\",\n snapshot.errorCount > 0 ? \"text-sf-danger\" : \"text-sf-success\"\n )}\n />\n <span className=\"min-w-0 truncate text-sm text-sf-subtle\">\n {snapshot.title}\n </span>\n <span className=\"text-xs tabular-nums text-sf-subtle/60\">\n {snapshot.steps.length} {stepLabel}\n {snapshot.errorCount > 0 ? ` · ${snapshot.errorCount} error` : \"\"}\n </span>\n <CaretDownIcon\n className={cn(\n \"size-3 shrink-0 text-sf-subtle/60 transition-transform duration-200\",\n !isOpen && \"-rotate-90\"\n )}\n />\n </BaseCollapsible.Trigger>\n\n <BaseCollapsible.Panel className=\"overflow-hidden\">\n <ul className=\"mt-1 ml-[0.3125rem] flex flex-col gap-0.5 border-l border-sf-line/40 pl-3\">\n {snapshot.steps.map((step, i) => (\n <li\n className=\"flex items-center gap-1.5 text-xs text-sf-subtle\"\n key={`${step.kind}-${i}-${step.name}`}\n >\n <span\n aria-hidden\n className={cn(\n \"size-1.5 shrink-0 rounded-full bg-current\",\n step.ok ? \"text-sf-success\" : \"text-sf-danger\"\n )}\n />\n <span className=\"min-w-0 truncate\">{step.name}</span>\n {step.duration ? (\n <span className=\"tabular-nums text-sf-subtle/60\">\n {step.duration}ms\n </span>\n ) : null}\n </li>\n ))}\n </ul>\n </BaseCollapsible.Panel>\n </div>\n </BaseCollapsible.Root>\n );\n}\n\nfunction deriveLiveTitle(elements: ReactElement[]): string {\n for (const el of elements as ChildLike[]) {\n const part = el.props.part as\n | { text?: string; summary?: string }\n | undefined;\n const text = part?.summary ?? part?.text;\n if (text) return truncate(text, 60);\n }\n return \"Working…\";\n}\n\n// ─── Snapshot derivation ─────────────────────────────────────────────────────\n\ntype ChildLike = ReactElement<{\n part?: AiToolPart | { text?: string; summary?: string; name?: string };\n duration?: number;\n}>;\n\nfunction deriveSnapshot(\n children: ReactNode,\n explicitTitle: string | undefined\n): FrozenSnapshot {\n // oxlint-disable-next-line no-react-children\n const elements = Children.toArray(children).filter(\n isValidElement\n ) as ChildLike[];\n\n const steps: FrozenStep[] = [];\n let errorCount = 0;\n let totalDuration = 0;\n let firstReasoningSnippet: string | undefined;\n\n for (const el of elements) {\n const kind = inferKind(el);\n const part = el.props.part as\n | (AiToolPart & { text?: string; summary?: string; name?: string })\n | undefined;\n const duration = el.props.duration;\n if (typeof duration === \"number\") totalDuration += duration;\n\n if (kind === \"tool\") {\n const toolPart = part as AiToolPart | undefined;\n const ok = toolPart ? !hasToolError(toolPart) : true;\n const settled = toolPart ? hasToolResult(toolPart) : true;\n if (!ok) errorCount += 1;\n steps.push({\n kind: \"tool\",\n name: toolPart?.toolName ?? \"tool\",\n ok: ok && settled,\n duration,\n });\n } else {\n const text =\n (part?.summary as string | undefined) ??\n (part?.text as string | undefined);\n if (!firstReasoningSnippet && text) {\n firstReasoningSnippet = text;\n }\n steps.push({\n kind: \"reasoning\",\n name: text ? truncate(text, 60) : \"Thinking\",\n ok: true,\n duration,\n });\n }\n }\n\n const title =\n explicitTitle?.trim() ||\n (firstReasoningSnippet && truncate(firstReasoningSnippet, 60)) ||\n `${steps.length} ${steps.length === 1 ? \"step\" : \"steps\"}`;\n\n return { title, steps, errorCount, totalDuration };\n}\n\nfunction inferKind(el: ChildLike): \"tool\" | \"reasoning\" {\n // Best-effort: check common indicators. Tool parts have `toolName`/`toolCallId`.\n // Reasoning parts have `text`.\n const part = el.props.part as Record<string, unknown> | undefined;\n if (part && typeof part === \"object\") {\n if (\"toolName\" in part || \"toolCallId\" in part) return \"tool\";\n if (\"text\" in part || \"summary\" in part) return \"reasoning\";\n }\n // Fallback: inspect displayName.\n const displayName =\n (el.type as { displayName?: string; name?: string } | undefined)\n ?.displayName ??\n (el.type as { displayName?: string; name?: string } | undefined)?.name ??\n \"\";\n if (displayName.includes(\"Tool\")) return \"tool\";\n return \"reasoning\";\n}\n\nfunction truncate(s: string, n: number): string {\n if (s.length <= n) return s;\n return `${s.slice(0, n - 1).trimEnd()}…`;\n}\n\n// ─── groupParts ──────────────────────────────────────────────────────\n\n/**\n * A message part that is either narrative text or an \"activity\" (reasoning or\n * tool call). Consumers pass parts shaped like this — the helper does not read\n * any field other than `type`.\n */\nexport type GroupablePart =\n | { type: \"text\"; [key: string]: unknown }\n | { type: \"reasoning\"; [key: string]: unknown }\n | { type: \"tool-call\" | \"tool-result\" | string; [key: string]: unknown };\n\n/** Emitted by {@link groupParts}. */\nexport type PartChunk<TPart = GroupablePart> =\n | { kind: \"text\"; parts: TPart[] }\n | { kind: \"activity\"; parts: TPart[] };\n\n/**\n * Split a message's parts into alternating `text` and `activity` chunks so each\n * contiguous run of reasoning/tool parts can be rendered inside its own\n * {@link AiPartGroup}, with narrative text rendered between them.\n *\n * @example\n * ```tsx\n * {groupParts(message.parts).map((chunk, i) =>\n * chunk.kind === \"text\" ? (\n * <AiMessageContent key={i}>{joinText(chunk.parts)}</AiMessageContent>\n * ) : (\n * <AiPartGroup\n * key={i}\n * groupKey={`${message.id}:activity:${i}`}\n * complete={!message.isStreaming}\n * >\n * {chunk.parts.map(renderActivityPart)}\n * </AiPartGroup>\n * )\n * )}\n * ```\n */\nexport function groupParts<TPart extends { type: string }>(\n parts: readonly TPart[],\n options: {\n /** Which `type` values count as \"activity\". Defaults to reasoning + tool parts. */\n isActivity?: (part: TPart) => boolean;\n } = {}\n): PartChunk<TPart>[] {\n const isActivity =\n options.isActivity ??\n ((p: TPart) => {\n const t = p.type;\n return (\n t === \"reasoning\" ||\n t === \"tool-call\" ||\n t === \"tool-result\" ||\n t.startsWith(\"tool-\")\n );\n });\n\n const chunks: PartChunk<TPart>[] = [];\n for (const part of parts) {\n const kind: PartChunk<TPart>[\"kind\"] = isActivity(part)\n ? \"activity\"\n : \"text\";\n const last = chunks.at(-1);\n if (last && last.kind === kind) {\n last.parts.push(part);\n } else {\n chunks.push({ kind, parts: [part] });\n }\n }\n return chunks;\n}\n"],"mappings":"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"}
|