@signalflare-ai/ui 1.1.0 → 1.3.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 +102 -5
- package/README.md +1 -1
- package/ai/component-registry.json +531 -79
- package/ai/component-registry.md +4067 -6
- package/ai/schemas.ts +6 -1
- package/dist/.build-complete +1 -1
- package/dist/ai/schemas.d.ts +76 -58
- package/dist/ai/schemas.d.ts.map +1 -1
- package/dist/{ai-actions-DSVeQn4e.js → ai-actions-CBfz5XEf.js} +4 -4
- package/dist/{ai-actions-DSVeQn4e.js.map → ai-actions-CBfz5XEf.js.map} +1 -1
- package/dist/{ai-agent-card-BXHwhWAU.js → ai-agent-card-CByAUe0q.js} +3 -3
- package/dist/ai-agent-card-CByAUe0q.js.map +1 -0
- package/dist/{ai-approval-aa0qvjFN.js → ai-approval-Ci8N70a7.js} +4 -3
- package/dist/{ai-approval-aa0qvjFN.js.map → ai-approval-Ci8N70a7.js.map} +1 -1
- package/dist/{ai-code-block-BgtIxtZZ.js → ai-code-block-P9TJHvaC.js} +37 -39
- package/dist/ai-code-block-P9TJHvaC.js.map +1 -0
- package/dist/ai-conversation-Qslfdi1t.js +228 -0
- package/dist/ai-conversation-Qslfdi1t.js.map +1 -0
- package/dist/{ai-info-banner-uFxHHwBA.js → ai-info-banner-B_9vtGK3.js} +8 -4
- package/dist/ai-info-banner-B_9vtGK3.js.map +1 -0
- package/dist/{ai-message-BjnFznXy.js → ai-message-Ci3gwM7G.js} +29 -10
- package/dist/ai-message-Ci3gwM7G.js.map +1 -0
- package/dist/{ai-mission-header-08__gULL.js → ai-mission-header-CaBc19-t.js} +2 -2
- package/dist/{ai-mission-header-08__gULL.js.map → ai-mission-header-CaBc19-t.js.map} +1 -1
- package/dist/{ai-part-group-DBtgTgAn.js → ai-part-group-Dx1Mr92B.js} +5 -4
- package/dist/ai-part-group-Dx1Mr92B.js.map +1 -0
- package/dist/{ai-prompt-input-CuluUzpf.js → ai-prompt-input-Bm4XoSj2.js} +44 -55
- package/dist/ai-prompt-input-Bm4XoSj2.js.map +1 -0
- package/dist/{ai-question-CHHoDJMg.js → ai-question-OyJovxGe.js} +4 -3
- package/dist/{ai-question-CHHoDJMg.js.map → ai-question-OyJovxGe.js.map} +1 -1
- package/dist/{ai-reasoning-CnL6ZSr5.js → ai-reasoning-BLfBXx3F.js} +9 -5
- package/dist/ai-reasoning-BLfBXx3F.js.map +1 -0
- package/dist/{ai-response-BEUg3xvd.js → ai-response-hbVCZJmo.js} +9 -4
- package/dist/ai-response-hbVCZJmo.js.map +1 -0
- package/dist/{ai-shimmer-By5_L05p.js → ai-shimmer-BamNMNK3.js} +2 -2
- package/dist/{ai-shimmer-By5_L05p.js.map → ai-shimmer-BamNMNK3.js.map} +1 -1
- package/dist/{ai-status-badge-BGYGWYF6.js → ai-status-badge-BZLczdkI.js} +2 -2
- package/dist/{ai-status-badge-BGYGWYF6.js.map → ai-status-badge-BZLczdkI.js.map} +1 -1
- package/dist/{ai-streaming-text-CMfoThV0.js → ai-streaming-text-DgYu64UH.js} +44 -16
- package/dist/ai-streaming-text-DgYu64UH.js.map +1 -0
- package/dist/{ai-subagent-DcPRqkAA.js → ai-subagent-p97AI1h9.js} +14 -6
- package/dist/ai-subagent-p97AI1h9.js.map +1 -0
- package/dist/{ai-suggestion-MgeCg5Ar.js → ai-suggestion-Bj6vF7CT.js} +3 -3
- package/dist/{ai-suggestion-MgeCg5Ar.js.map → ai-suggestion-Bj6vF7CT.js.map} +1 -1
- package/dist/{ai-task-list-Da9zIm00.js → ai-task-list-C_UQYpk9.js} +15 -6
- package/dist/ai-task-list-C_UQYpk9.js.map +1 -0
- package/dist/{ai-timeline-Cwu045IR.js → ai-timeline-CePL1LOU.js} +3 -3
- package/dist/ai-timeline-CePL1LOU.js.map +1 -0
- package/dist/{ai-tool-Cn1O4xjP.js → ai-tool-CfRcwmHT.js} +35 -16
- package/dist/ai-tool-CfRcwmHT.js.map +1 -0
- package/dist/{ai-usage-bar-DjS12DMp.js → ai-usage-bar-45pVRCGA.js} +2 -2
- package/dist/{ai-usage-bar-DjS12DMp.js.map → ai-usage-bar-45pVRCGA.js.map} +1 -1
- package/dist/{badge-D_eaA6wv.js → badge-Beb-6uut.js} +5 -5
- package/dist/{badge-D_eaA6wv.js.map → badge-Beb-6uut.js.map} +1 -1
- package/dist/{banner-B_6oBrsu.js → banner-CCEksxPg.js} +8 -3
- package/dist/banner-CCEksxPg.js.map +1 -0
- package/dist/{breadcrumbs-BlmeYfgq.js → breadcrumbs-HiTmgaZ4.js} +5 -5
- package/dist/{breadcrumbs-BlmeYfgq.js.map → breadcrumbs-HiTmgaZ4.js.map} +1 -1
- package/dist/{button-De0267YU.js → button-BHOgXJRU.js} +4 -4
- package/dist/{button-De0267YU.js.map → button-BHOgXJRU.js.map} +1 -1
- package/dist/catalog.js +1 -1
- package/dist/catalog.js.map +1 -1
- package/dist/{chart-BK3sVPnD.js → chart-B9FfZdKs.js} +7 -7
- package/dist/chart-B9FfZdKs.js.map +1 -0
- package/dist/{checkbox-DYhUmZNw.js → checkbox-Cy_OCyay.js} +3 -3
- package/dist/{checkbox-DYhUmZNw.js.map → checkbox-Cy_OCyay.js.map} +1 -1
- package/dist/{clipboard-text-ssybngLw.js → clipboard-text-CKSvNp9L.js} +6 -5
- package/dist/clipboard-text-CKSvNp9L.js.map +1 -0
- package/dist/{cn-YROP2_ox.js → cn-CmAOpn49.js} +2 -2
- package/dist/{cn-YROP2_ox.js.map → cn-CmAOpn49.js.map} +1 -1
- package/dist/{code-Cx-QSoOT.js → code-JsQz-0G_.js} +4 -4
- package/dist/{code-Cx-QSoOT.js.map → code-JsQz-0G_.js.map} +1 -1
- package/dist/{collapsible-DWsXeXmS.js → collapsible-1kOZ-89L.js} +2 -2
- package/dist/{collapsible-DWsXeXmS.js.map → collapsible-1kOZ-89L.js.map} +1 -1
- package/dist/{combobox-C0iW6a0r.js → combobox-CQwDmqgA.js} +4 -4
- package/dist/{combobox-C0iW6a0r.js.map → combobox-CQwDmqgA.js.map} +1 -1
- package/dist/command-line/cli.js +3 -3
- package/dist/{command-palette-DGzioeki.js → command-palette-Bkuv3e6o.js} +20 -5
- package/dist/command-palette-Bkuv3e6o.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 +2 -2
- 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 +1 -1
- package/dist/components/ai-prompt-input.js +1 -1
- 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 +2 -2
- 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/badge.js +1 -1
- package/dist/components/banner.js +1 -1
- package/dist/components/breadcrumbs.js +1 -1
- package/dist/components/button.js +1 -1
- package/dist/components/chart.js +2 -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/collapsible.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/date-range-picker.js +1 -1
- package/dist/components/dialog.js +1 -1
- package/dist/components/dropdown.js +1 -1
- package/dist/components/empty.js +1 -1
- package/dist/components/field.js +1 -1
- package/dist/components/filters.js +1 -1
- package/dist/components/flow.js +1 -1
- package/dist/components/grid.js +1 -1
- package/dist/components/input.js +2 -2
- package/dist/components/label.js +1 -1
- package/dist/components/layer-card.js +1 -1
- package/dist/components/link.js +3 -3
- package/dist/components/link.js.map +1 -1
- package/dist/components/loader.js +2 -2
- package/dist/components/menubar.js +1 -1
- package/dist/components/meter.js +1 -1
- package/dist/components/pagination.js +1 -1
- package/dist/components/popover.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/signalflare-ai-logo.js +1 -1
- package/dist/components/sparkline.js +1 -1
- package/dist/components/stat-card.js +1 -1
- package/dist/components/surface.js +1 -1
- package/dist/components/switch.js +1 -1
- package/dist/components/table.js +1 -1
- package/dist/components/tabs.js +1 -1
- package/dist/components/text-roll.js +1 -1
- package/dist/components/text.js +1 -1
- package/dist/components/theme-toggle.js +1 -1
- package/dist/components/toast.js +1 -1
- package/dist/components/tooltip.js +1 -1
- package/dist/components/use-agent-harness.js +1 -1
- package/dist/{data-grid-CG76N_hK.js → data-grid-DDSFMHud.js} +136 -53
- package/dist/data-grid-DDSFMHud.js.map +1 -0
- package/dist/{date-picker-Dqg9L4xu.js → date-picker-O34AqG3f.js} +2 -2
- package/dist/{date-picker-Dqg9L4xu.js.map → date-picker-O34AqG3f.js.map} +1 -1
- package/dist/{date-range-picker-D75LLINc.js → date-range-picker-YKYvum_r.js} +29 -39
- package/dist/{date-range-picker-D75LLINc.js.map → date-range-picker-YKYvum_r.js.map} +1 -1
- package/dist/{dialog-CyHEQXEY.js → dialog-DYqu4aDO.js} +3 -3
- package/dist/{dialog-CyHEQXEY.js.map → dialog-DYqu4aDO.js.map} +1 -1
- package/dist/{dist-1-gcEL2L.js → dist-6AtBsaJE.js} +153 -47
- package/dist/dist-6AtBsaJE.js.map +1 -0
- package/dist/{dropdown-qnEYRFXZ.js → dropdown-XzbnRLYR.js} +15 -5
- package/dist/dropdown-XzbnRLYR.js.map +1 -0
- package/dist/{echart-DURZEyai.js → echart-DGBIVAv1.js} +23 -57
- package/dist/{echart-DURZEyai.js.map → echart-DGBIVAv1.js.map} +1 -1
- package/dist/{empty-D2TypIId.js → empty-C1tAkawe.js} +12 -7
- package/dist/empty-C1tAkawe.js.map +1 -0
- package/dist/{field-Y_UK1_Cg.js → field-DBpFzzBS.js} +3 -3
- package/dist/{field-Y_UK1_Cg.js.map → field-DBpFzzBS.js.map} +1 -1
- package/dist/{filters-Bw_U6ZTx.js → filters-SmEl93za.js} +10 -10
- package/dist/filters-SmEl93za.js.map +1 -0
- package/dist/{flow-BRsYUCJa.js → flow-BLzgbq1T.js} +6 -6
- package/dist/flow-BLzgbq1T.js.map +1 -0
- package/dist/genui.js +2 -2
- package/dist/genui.js.map +1 -1
- package/dist/{grid-qUAN9hFx.js → grid-CifjQL-5.js} +2 -2
- package/dist/{grid-qUAN9hFx.js.map → grid-CifjQL-5.js.map} +1 -1
- package/dist/{highlight-to-react-ClEfL81q.js → highlight-to-react-DN9dUCS2.js} +9 -15
- package/dist/highlight-to-react-DN9dUCS2.js.map +1 -0
- package/dist/index.js +72 -72
- package/dist/index.js.map +1 -1
- package/dist/{input-DddtBN-g.js → input-COmx2M_R.js} +5 -5
- package/dist/{input-DddtBN-g.js.map → input-COmx2M_R.js.map} +1 -1
- package/dist/{input-DXYUjGgD.js → input-GkfMQZC_.js} +3 -3
- package/dist/{input-DXYUjGgD.js.map → input-GkfMQZC_.js.map} +1 -1
- package/dist/{label-QtJxtJ4u.js → label-CiGZ464N.js} +3 -3
- package/dist/{label-QtJxtJ4u.js.map → label-CiGZ464N.js.map} +1 -1
- package/dist/{layer-card-BME0eljh.js → layer-card-8l8GuLQr.js} +2 -2
- package/dist/{layer-card-BME0eljh.js.map → layer-card-8l8GuLQr.js.map} +1 -1
- package/dist/layout-CWBE0qwx.js +6207 -0
- package/dist/layout-CWBE0qwx.js.map +1 -0
- package/dist/{link-provider-BUZKXaNE.js → link-provider-BSn8YJon.js} +2 -2
- package/dist/link-provider-BSn8YJon.js.map +1 -0
- package/dist/{loader-DAcc-Uag.js → loader-BEMz8pJO.js} +1 -1
- package/dist/{loader-DAcc-Uag.js.map → loader-BEMz8pJO.js.map} +1 -1
- package/dist/measured-text-CXkdw9Yr.js +305 -0
- package/dist/measured-text-CXkdw9Yr.js.map +1 -0
- package/dist/{menubar-C8NzAjfd.js → menubar-CoOr4ocj.js} +3 -3
- package/dist/{menubar-C8NzAjfd.js.map → menubar-CoOr4ocj.js.map} +1 -1
- package/dist/{meter-CpmTenEr.js → meter-Pf_VOl59.js} +2 -2
- package/dist/{meter-CpmTenEr.js.map → meter-Pf_VOl59.js.map} +1 -1
- package/dist/{pagination-BVqdlONY.js → pagination-DSY279Ta.js} +2 -2
- package/dist/{pagination-BVqdlONY.js.map → pagination-DSY279Ta.js.map} +1 -1
- package/dist/{popover-BRQZ2b6z.js → popover-BY-e9co1.js} +2 -2
- package/dist/{popover-BRQZ2b6z.js.map → popover-BY-e9co1.js.map} +1 -1
- package/dist/{radio-BNSwOt3B.js → radio-DZwL13j0.js} +2 -2
- package/dist/{radio-BNSwOt3B.js.map → radio-DZwL13j0.js.map} +1 -1
- package/dist/{select-1w2aebGQ.js → select-BFifYqHA.js} +6 -6
- package/dist/{select-1w2aebGQ.js.map → select-BFifYqHA.js.map} +1 -1
- package/dist/{sensitive-input-82Cez3vj.js → sensitive-input-DHLZcM73.js} +4 -4
- package/dist/{sensitive-input-82Cez3vj.js.map → sensitive-input-DHLZcM73.js.map} +1 -1
- package/dist/{sidebar-CAsCmSpM.js → sidebar-odGsdvG4.js} +6 -7
- package/dist/sidebar-odGsdvG4.js.map +1 -0
- package/dist/{signalflare-ai-logo-DDhxMJD6.js → signalflare-ai-logo-CNaDT_w8.js} +2 -2
- package/dist/{signalflare-ai-logo-DDhxMJD6.js.map → signalflare-ai-logo-CNaDT_w8.js.map} +1 -1
- package/dist/{skeleton-line-Do3UmGk9.js → skeleton-line-CxxYVTO2.js} +2 -2
- package/dist/{skeleton-line-Do3UmGk9.js.map → skeleton-line-CxxYVTO2.js.map} +1 -1
- package/dist/{sparkline-DdbeM4Ai.js → sparkline-BQ-4j2W2.js} +2 -2
- package/dist/{sparkline-DdbeM4Ai.js.map → sparkline-BQ-4j2W2.js.map} +1 -1
- package/dist/src/blocks/agent-harness/agent-harness.d.ts.map +1 -1
- package/dist/src/blocks/agent-harness/agent-harness.tsx +40 -16
- package/dist/src/blocks/commander/commander.tsx +15 -15
- package/dist/src/blocks/map-block/map-block.d.ts.map +1 -1
- package/dist/src/blocks/map-block/map-block.tsx +11 -7
- package/dist/src/components/ai-approval/ai-approval.d.ts.map +1 -1
- package/dist/src/components/ai-code-block/ai-code-block.d.ts +14 -13
- package/dist/src/components/ai-code-block/ai-code-block.d.ts.map +1 -1
- package/dist/src/components/ai-conversation/ai-conversation.d.ts +69 -37
- package/dist/src/components/ai-conversation/ai-conversation.d.ts.map +1 -1
- package/dist/src/components/ai-conversation/index.d.ts +2 -1
- package/dist/src/components/ai-conversation/index.d.ts.map +1 -1
- package/dist/src/components/ai-conversation/measurement-constants.d.ts +30 -0
- package/dist/src/components/ai-conversation/measurement-constants.d.ts.map +1 -0
- package/dist/src/components/ai-info-banner/ai-info-banner.d.ts.map +1 -1
- package/dist/src/components/ai-message/ai-message.d.ts +3 -0
- package/dist/src/components/ai-message/ai-message.d.ts.map +1 -1
- package/dist/src/components/ai-part-group/ai-part-group.d.ts.map +1 -1
- package/dist/src/components/ai-prompt-input/ai-prompt-input.d.ts +13 -3
- 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.map +1 -1
- package/dist/src/components/ai-prompt-input/index.d.ts +1 -1
- package/dist/src/components/ai-prompt-input/index.d.ts.map +1 -1
- package/dist/src/components/ai-prompt-input/types.d.ts.map +1 -1
- package/dist/src/components/ai-question/ai-question.d.ts.map +1 -1
- package/dist/src/components/ai-reasoning/ai-reasoning.d.ts.map +1 -1
- package/dist/src/components/ai-response/ai-response.d.ts +12 -1
- package/dist/src/components/ai-response/ai-response.d.ts.map +1 -1
- package/dist/src/components/ai-streaming-text/ai-streaming-text.d.ts +27 -0
- package/dist/src/components/ai-streaming-text/ai-streaming-text.d.ts.map +1 -1
- package/dist/src/components/ai-streaming-text/index.d.ts +1 -1
- package/dist/src/components/ai-streaming-text/index.d.ts.map +1 -1
- package/dist/src/components/ai-subagent/ai-subagent.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.map +1 -1
- package/dist/src/components/banner/banner.d.ts.map +1 -1
- package/dist/src/components/chart/echart.d.ts.map +1 -1
- package/dist/src/components/clipboard-text/clipboard-text.d.ts.map +1 -1
- package/dist/src/components/data-grid/data-grid.d.ts +2 -1
- package/dist/src/components/data-grid/data-grid.d.ts.map +1 -1
- package/dist/src/components/data-grid/features.d.ts +20 -0
- package/dist/src/components/data-grid/features.d.ts.map +1 -0
- package/dist/src/components/data-grid/types.d.ts +38 -7
- package/dist/src/components/data-grid/types.d.ts.map +1 -1
- package/dist/src/components/empty/empty.d.ts.map +1 -1
- package/dist/src/components/filters/filters.d.ts.map +1 -1
- package/dist/src/components/flow/use-children.d.ts +1 -1
- package/dist/src/components/link/link.d.ts.map +1 -1
- package/dist/src/components/sidebar/sidebar.d.ts +1 -1
- package/dist/src/components/signalflare-ai-logo/signalflare-ai-logo.d.ts.map +1 -1
- package/dist/src/components/stat-card/stat-card.d.ts +5 -0
- package/dist/src/components/stat-card/stat-card.d.ts.map +1 -1
- package/dist/src/components/text/text.d.ts +36 -1
- package/dist/src/components/text/text.d.ts.map +1 -1
- package/dist/src/components/text-roll/text-roll.d.ts.map +1 -1
- package/dist/src/components/theme-toggle/theme-toggle.d.ts.map +1 -1
- package/dist/src/components/toast/toast.d.ts.map +1 -1
- package/dist/src/components/tooltip/tooltip.d.ts.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/utils/highlight-to-react.d.ts.map +1 -1
- package/dist/src/utils/index.d.ts +2 -0
- package/dist/src/utils/index.d.ts.map +1 -1
- package/dist/src/utils/measured-text.d.ts +40 -0
- package/dist/src/utils/measured-text.d.ts.map +1 -0
- package/dist/src/utils/use-measured-text.d.ts +59 -0
- package/dist/src/utils/use-measured-text.d.ts.map +1 -0
- package/dist/{stat-card-CEZscNh8.js → stat-card-Bspk4XFr.js} +30 -12
- package/dist/stat-card-Bspk4XFr.js.map +1 -0
- package/dist/styles/sf-binding.css +1 -1
- package/dist/styles/sf-standalone.css +2 -2
- package/dist/styles/shadcn.css +1 -1
- package/dist/styles/theme-minimal.css +9 -9
- package/dist/styles/theme-sf.css +14 -20
- package/dist/styles/theme-vesper.css +91 -0
- package/dist/{surface-BduI7Ehl.js → surface-CWdSFVUx.js} +3 -3
- package/dist/{surface-BduI7Ehl.js.map → surface-CWdSFVUx.js.map} +1 -1
- package/dist/{switch-CzZBRBL7.js → switch-TA4cByCJ.js} +5 -5
- package/dist/switch-TA4cByCJ.js.map +1 -0
- package/dist/{table-Rv4JMy0B.js → table-BM8JBGBs.js} +3 -3
- package/dist/{table-Rv4JMy0B.js.map → table-BM8JBGBs.js.map} +1 -1
- package/dist/{tabs-1cHrYoel.js → tabs-bnH2vGLv.js} +2 -2
- package/dist/{tabs-1cHrYoel.js.map → tabs-bnH2vGLv.js.map} +1 -1
- package/dist/{text-KJmGkwnf.js → text-iQ0YUFNg.js} +27 -6
- package/dist/text-iQ0YUFNg.js.map +1 -0
- package/dist/{text-roll-BZ3I1umc.js → text-roll-C3U2jd2u.js} +5 -2
- package/dist/text-roll-C3U2jd2u.js.map +1 -0
- package/dist/{theme-toggle-Bhu681D7.js → theme-toggle-BTVxD-fD.js} +10 -9
- package/dist/theme-toggle-BTVxD-fD.js.map +1 -0
- package/dist/{toast-Nw28a5Cx.js → toast-CgZVaAkw.js} +3 -3
- package/dist/{toast-Nw28a5Cx.js.map → toast-CgZVaAkw.js.map} +1 -1
- package/dist/{tooltip-Cb7QW-7H.js → tooltip-uobk6Oh-.js} +9 -3
- package/dist/tooltip-uobk6Oh-.js.map +1 -0
- package/dist/{use-agent-harness-BMyF8pTq.js → use-agent-harness-Dl8w6X5O.js} +3 -3
- package/dist/{use-agent-harness-BMyF8pTq.js.map → use-agent-harness-Dl8w6X5O.js.map} +1 -1
- package/dist/utils.js +4 -3
- package/package.json +27 -24
- package/scripts/component-registry/discovery.ts +11 -10
- package/scripts/component-registry/example-cleanup.ts +8 -8
- package/scripts/component-registry/index.ts +6 -6
- package/scripts/component-registry/schema-generator.ts +1 -1
- package/scripts/component-registry/sub-components.ts +35 -23
- package/scripts/component-registry/utils.ts +11 -11
- package/scripts/component-registry/variant-parser.ts +17 -15
- package/scripts/convert-demos-to-stories.ts +5 -5
- package/scripts/css-build.ts +1 -1
- package/scripts/theme-generator/config.ts +28 -146
- package/scripts/theme-generator/generate-css.ts +1 -2
- package/scripts/theme-generator/index.ts +0 -1
- package/scripts/theme-generator/migrate.ts +3 -3
- package/dist/ai-agent-card-BXHwhWAU.js.map +0 -1
- package/dist/ai-code-block-BgtIxtZZ.js.map +0 -1
- package/dist/ai-conversation-CArP7C8K.js +0 -184
- package/dist/ai-conversation-CArP7C8K.js.map +0 -1
- package/dist/ai-info-banner-uFxHHwBA.js.map +0 -1
- package/dist/ai-message-BjnFznXy.js.map +0 -1
- package/dist/ai-part-group-DBtgTgAn.js.map +0 -1
- package/dist/ai-prompt-input-CuluUzpf.js.map +0 -1
- package/dist/ai-reasoning-CnL6ZSr5.js.map +0 -1
- package/dist/ai-response-BEUg3xvd.js.map +0 -1
- package/dist/ai-streaming-text-CMfoThV0.js.map +0 -1
- package/dist/ai-subagent-DcPRqkAA.js.map +0 -1
- package/dist/ai-task-list-Da9zIm00.js.map +0 -1
- package/dist/ai-timeline-Cwu045IR.js.map +0 -1
- package/dist/ai-tool-Cn1O4xjP.js.map +0 -1
- package/dist/banner-B_6oBrsu.js.map +0 -1
- package/dist/chart-BK3sVPnD.js.map +0 -1
- package/dist/clipboard-text-ssybngLw.js.map +0 -1
- package/dist/command-palette-DGzioeki.js.map +0 -1
- package/dist/data-grid-CG76N_hK.js.map +0 -1
- package/dist/dist-1-gcEL2L.js.map +0 -1
- package/dist/dropdown-qnEYRFXZ.js.map +0 -1
- package/dist/empty-D2TypIId.js.map +0 -1
- package/dist/filters-Bw_U6ZTx.js.map +0 -1
- package/dist/flow-BRsYUCJa.js.map +0 -1
- package/dist/highlight-to-react-ClEfL81q.js.map +0 -1
- package/dist/link-provider-BUZKXaNE.js.map +0 -1
- package/dist/sidebar-CAsCmSpM.js.map +0 -1
- package/dist/stat-card-CEZscNh8.js.map +0 -1
- package/dist/styles/theme-blue-tint.css +0 -98
- package/dist/switch-CzZBRBL7.js.map +0 -1
- package/dist/text-KJmGkwnf.js.map +0 -1
- package/dist/text-roll-BZ3I1umc.js.map +0 -1
- package/dist/theme-toggle-Bhu681D7.js.map +0 -1
- package/dist/tooltip-Cb7QW-7H.js.map +0 -1
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { t as cn } from "./cn-YROP2_ox.js";
|
|
3
|
-
import { t as Button } from "./button-De0267YU.js";
|
|
4
|
-
import { forwardRef, useCallback, useEffect, useRef, useState } from "react";
|
|
5
|
-
import { jsx } from "react/jsx-runtime";
|
|
6
|
-
import { ArrowDownIcon } from "@phosphor-icons/react";
|
|
7
|
-
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
8
|
-
//#region src/components/ai-conversation/ai-conversation.tsx
|
|
9
|
-
var SF_AI_CONVERSATION_VARIANTS = {};
|
|
10
|
-
var SF_AI_CONVERSATION_DEFAULT_VARIANTS = {};
|
|
11
|
-
/**
|
|
12
|
-
* Outer scroll container for a conversation. Sticks to the bottom as new
|
|
13
|
-
* messages are added. Pair with `AiConversationContent` and optionally
|
|
14
|
-
* `AiConversationScrollButton`.
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```tsx
|
|
18
|
-
* <AiConversation ref={scrollRef}>
|
|
19
|
-
* <AiConversationContent>
|
|
20
|
-
* {messages.map(m => <AiMessage key={m.id} from={m.role}>...</AiMessage>)}
|
|
21
|
-
* </AiConversationContent>
|
|
22
|
-
* <AiConversationScrollButton scrollRef={scrollRef} />
|
|
23
|
-
* </AiConversation>
|
|
24
|
-
* ```
|
|
25
|
-
*/
|
|
26
|
-
var AiConversation = forwardRef(({ className, children, ...props }, ref) => {
|
|
27
|
-
return /* @__PURE__ */ jsx("div", {
|
|
28
|
-
ref,
|
|
29
|
-
className: cn("relative flex-1 overflow-y-auto", className),
|
|
30
|
-
role: "log",
|
|
31
|
-
"aria-live": "polite",
|
|
32
|
-
"aria-label": "Conversation",
|
|
33
|
-
...props,
|
|
34
|
-
children
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
AiConversation.displayName = "AiConversation";
|
|
38
|
-
/**
|
|
39
|
-
* Inner content wrapper with padding. Place message components inside here.
|
|
40
|
-
*/
|
|
41
|
-
function AiConversationContent({ className, children, ...props }) {
|
|
42
|
-
return /* @__PURE__ */ jsx("div", {
|
|
43
|
-
className: cn("flex flex-col gap-0 overscroll-y-none p-4", className),
|
|
44
|
-
...props,
|
|
45
|
-
children
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Floating "scroll to bottom" button. Appears when the user has scrolled up
|
|
50
|
-
* more than `threshold` pixels from the bottom.
|
|
51
|
-
*/
|
|
52
|
-
function AiConversationScrollButton({ scrollRef, threshold = 100, className, onClick, ...props }) {
|
|
53
|
-
const [show, setShow] = useState(false);
|
|
54
|
-
useEffect(() => {
|
|
55
|
-
const el = scrollRef?.current;
|
|
56
|
-
if (!el) return;
|
|
57
|
-
const check = () => {
|
|
58
|
-
setShow(el.scrollHeight - el.scrollTop - el.clientHeight > threshold);
|
|
59
|
-
};
|
|
60
|
-
check();
|
|
61
|
-
el.addEventListener("scroll", check, { passive: true });
|
|
62
|
-
return () => el.removeEventListener("scroll", check);
|
|
63
|
-
}, [scrollRef, threshold]);
|
|
64
|
-
const handleClick = useCallback((e) => {
|
|
65
|
-
scrollRef?.current?.scrollTo({
|
|
66
|
-
top: scrollRef.current.scrollHeight,
|
|
67
|
-
behavior: "smooth"
|
|
68
|
-
});
|
|
69
|
-
onClick?.(e);
|
|
70
|
-
}, [scrollRef, onClick]);
|
|
71
|
-
if (scrollRef && !show) return null;
|
|
72
|
-
return /* @__PURE__ */ jsx(Button, {
|
|
73
|
-
"aria-label": "Scroll to bottom",
|
|
74
|
-
className: cn("absolute bottom-4 left-1/2 -translate-x-1/2 rounded-full shadow-md", className),
|
|
75
|
-
onClick: handleClick,
|
|
76
|
-
size: "sm",
|
|
77
|
-
variant: "secondary",
|
|
78
|
-
...props,
|
|
79
|
-
children: /* @__PURE__ */ jsx(ArrowDownIcon, { className: "size-4" })
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* `useConversationVirtualizer` — virtualizes a conversation list with
|
|
84
|
-
* dynamic item heights and automatic stick-to-bottom behavior.
|
|
85
|
-
*
|
|
86
|
-
* Built on `@tanstack/react-virtual`. Items are measured dynamically via
|
|
87
|
-
* `ResizeObserver`, so tool calls expanding, subagents collapsing, and
|
|
88
|
-
* streaming text growing all work correctly.
|
|
89
|
-
*
|
|
90
|
-
* **Stick-to-bottom**: When the user is scrolled to (or near) the bottom,
|
|
91
|
-
* new items and item resizes automatically keep the viewport pinned to the
|
|
92
|
-
* bottom. When the user scrolls up, stick-to-bottom disengages until they
|
|
93
|
-
* scroll back down.
|
|
94
|
-
*
|
|
95
|
-
* @example
|
|
96
|
-
* ```tsx
|
|
97
|
-
* const { virtualItems, totalSize, measureRef, isAtBottom, scrollToBottom } =
|
|
98
|
-
* useConversationVirtualizer({ scrollRef, items });
|
|
99
|
-
*
|
|
100
|
-
* return (
|
|
101
|
-
* <AiConversation ref={scrollRef}>
|
|
102
|
-
* <div style={{ height: totalSize, position: "relative" }}>
|
|
103
|
-
* {virtualItems.map((vi) => {
|
|
104
|
-
* const item = items[vi.index];
|
|
105
|
-
* return (
|
|
106
|
-
* <div key={item.key} ref={measureRef} data-index={vi.index}
|
|
107
|
-
* style={{ position: "absolute", top: vi.start, width: "100%" }}>
|
|
108
|
-
* {item.render()}
|
|
109
|
-
* </div>
|
|
110
|
-
* );
|
|
111
|
-
* })}
|
|
112
|
-
* </div>
|
|
113
|
-
* {!isAtBottom && <button onClick={() => scrollToBottom()}>↓</button>}
|
|
114
|
-
* </AiConversation>
|
|
115
|
-
* );
|
|
116
|
-
* ```
|
|
117
|
-
*/
|
|
118
|
-
function useConversationVirtualizer({ scrollRef, items, estimateSize = 80, overscan = 8, stickThreshold = 40 }) {
|
|
119
|
-
const [isAtBottom, setIsAtBottom] = useState(true);
|
|
120
|
-
const isAtBottomRef = useRef(true);
|
|
121
|
-
const prevCountRef = useRef(items.length);
|
|
122
|
-
useEffect(() => {
|
|
123
|
-
const el = scrollRef.current;
|
|
124
|
-
if (!el) return;
|
|
125
|
-
const check = () => {
|
|
126
|
-
const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight <= stickThreshold;
|
|
127
|
-
isAtBottomRef.current = atBottom;
|
|
128
|
-
setIsAtBottom(atBottom);
|
|
129
|
-
};
|
|
130
|
-
check();
|
|
131
|
-
el.addEventListener("scroll", check, { passive: true });
|
|
132
|
-
return () => el.removeEventListener("scroll", check);
|
|
133
|
-
}, [scrollRef, stickThreshold]);
|
|
134
|
-
const prevTotalSizeRef = useRef(0);
|
|
135
|
-
const virtualizer = useVirtualizer({
|
|
136
|
-
count: items.length,
|
|
137
|
-
getScrollElement: () => scrollRef.current,
|
|
138
|
-
estimateSize: () => estimateSize,
|
|
139
|
-
overscan,
|
|
140
|
-
getItemKey: (index) => items[index]?.key ?? index
|
|
141
|
-
});
|
|
142
|
-
virtualizer.shouldAdjustScrollPositionOnItemSizeChange = () => isAtBottomRef.current;
|
|
143
|
-
const virtualItems = virtualizer.getVirtualItems();
|
|
144
|
-
const totalSize = virtualizer.getTotalSize();
|
|
145
|
-
useEffect(() => {
|
|
146
|
-
const count = items.length;
|
|
147
|
-
if (count > prevCountRef.current && isAtBottomRef.current) requestAnimationFrame(() => {
|
|
148
|
-
virtualizer.scrollToIndex(count - 1, {
|
|
149
|
-
align: "end",
|
|
150
|
-
behavior: "smooth"
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
prevCountRef.current = count;
|
|
154
|
-
}, [items.length, virtualizer]);
|
|
155
|
-
useEffect(() => {
|
|
156
|
-
const prev = prevTotalSizeRef.current;
|
|
157
|
-
prevTotalSizeRef.current = totalSize;
|
|
158
|
-
if (!isAtBottomRef.current || totalSize <= prev) return;
|
|
159
|
-
const el = scrollRef.current;
|
|
160
|
-
if (!el) return;
|
|
161
|
-
const dist = el.scrollHeight - el.scrollTop - el.clientHeight;
|
|
162
|
-
if (dist > 1 && dist < 300) el.scrollTop = el.scrollHeight - el.clientHeight;
|
|
163
|
-
}, [totalSize, scrollRef]);
|
|
164
|
-
return {
|
|
165
|
-
virtualizer,
|
|
166
|
-
virtualItems,
|
|
167
|
-
totalSize,
|
|
168
|
-
isAtBottom,
|
|
169
|
-
scrollToBottom: useCallback((behavior = "smooth") => {
|
|
170
|
-
if (items.length === 0) return;
|
|
171
|
-
virtualizer.scrollToIndex(items.length - 1, {
|
|
172
|
-
align: "end",
|
|
173
|
-
behavior
|
|
174
|
-
});
|
|
175
|
-
}, [virtualizer, items.length]),
|
|
176
|
-
measureRef: useCallback((node) => {
|
|
177
|
-
if (node) virtualizer.measureElement(node);
|
|
178
|
-
}, [virtualizer])
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
//#endregion
|
|
182
|
-
export { SF_AI_CONVERSATION_VARIANTS as a, SF_AI_CONVERSATION_DEFAULT_VARIANTS as i, AiConversationContent as n, useConversationVirtualizer as o, AiConversationScrollButton as r, AiConversation as t };
|
|
183
|
-
|
|
184
|
-
//# sourceMappingURL=ai-conversation-CArP7C8K.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ai-conversation-CArP7C8K.js","names":[],"sources":["../src/components/ai-conversation/ai-conversation.tsx"],"sourcesContent":["\"use client\";\n\nimport { ArrowDownIcon } from \"@phosphor-icons/react\";\nimport { useVirtualizer } from \"@tanstack/react-virtual\";\nimport type { Virtualizer, VirtualItem } from \"@tanstack/react-virtual\";\nimport type { ComponentProps, ReactNode, RefObject } from \"react\";\nimport { forwardRef, useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"../button\";\n\n// ─── Variants ────────────────────────────────────────────────────────────────\n\nexport const SF_AI_CONVERSATION_VARIANTS = {} as const;\nexport const SF_AI_CONVERSATION_DEFAULT_VARIANTS = {} as const;\n\n// ─── AiConversation ──────────────────────────────────────────────────────────\n\nexport type AiConversationProps = ComponentProps<\"div\">;\n\n/**\n * Outer scroll container for a conversation. Sticks to the bottom as new\n * messages are added. Pair with `AiConversationContent` and optionally\n * `AiConversationScrollButton`.\n *\n * @example\n * ```tsx\n * <AiConversation ref={scrollRef}>\n * <AiConversationContent>\n * {messages.map(m => <AiMessage key={m.id} from={m.role}>...</AiMessage>)}\n * </AiConversationContent>\n * <AiConversationScrollButton scrollRef={scrollRef} />\n * </AiConversation>\n * ```\n */\nexport const AiConversation = forwardRef<HTMLDivElement, AiConversationProps>(\n ({ className, children, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\"relative flex-1 overflow-y-auto\", className)}\n role=\"log\"\n aria-live=\"polite\"\n aria-label=\"Conversation\"\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nAiConversation.displayName = \"AiConversation\";\n\n// ─── AiConversationContent ───────────────────────────────────────────────────\n\nexport type AiConversationContentProps = ComponentProps<\"div\">;\n\n/**\n * Inner content wrapper with padding. Place message components inside here.\n */\nexport function AiConversationContent({\n className,\n children,\n ...props\n}: AiConversationContentProps) {\n return (\n <div\n className={cn(\"flex flex-col gap-0 overscroll-y-none p-4\", className)}\n {...props}\n >\n {children}\n </div>\n );\n}\n\n// ─── AiConversationScrollButton ──────────────────────────────────────────────\n\nexport type AiConversationScrollButtonProps = ComponentProps<typeof Button> & {\n /**\n * Ref to the scroll container (the `AiConversation` element).\n * When omitted, the button is always visible.\n */\n scrollRef?: RefObject<HTMLElement | null>;\n /** Scroll threshold in pixels from bottom to show the button. @default 100 */\n threshold?: number;\n};\n\n/**\n * Floating \"scroll to bottom\" button. Appears when the user has scrolled up\n * more than `threshold` pixels from the bottom.\n */\nexport function AiConversationScrollButton({\n scrollRef,\n threshold = 100,\n className,\n onClick,\n ...props\n}: AiConversationScrollButtonProps) {\n const [show, setShow] = useState(false);\n\n useEffect(() => {\n const el = scrollRef?.current;\n if (!el) return;\n\n const check = () => {\n const distFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;\n setShow(distFromBottom > threshold);\n };\n\n check();\n el.addEventListener(\"scroll\", check, { passive: true });\n return () => el.removeEventListener(\"scroll\", check);\n }, [scrollRef, threshold]);\n\n const handleClick = useCallback(\n (e: React.MouseEvent<HTMLButtonElement>) => {\n scrollRef?.current?.scrollTo({\n top: scrollRef.current.scrollHeight,\n behavior: \"smooth\",\n });\n onClick?.(e);\n },\n [scrollRef, onClick]\n );\n\n if (scrollRef && !show) return null;\n\n return (\n <Button\n aria-label=\"Scroll to bottom\"\n className={cn(\n \"absolute bottom-4 left-1/2 -translate-x-1/2 rounded-full shadow-md\",\n className\n )}\n onClick={handleClick}\n size=\"sm\"\n variant=\"secondary\"\n {...props}\n >\n <ArrowDownIcon className=\"size-4\" />\n </Button>\n );\n}\n\n// ─── Virtualized Conversation ────────────────────────────────────────────────\n\n/** A single item in the virtualized conversation list. */\nexport interface ConversationItem {\n /** Unique key for this item. */\n key: string;\n /**\n * Render the item content. The virtualizer handles measurement and\n * positioning on the wrapper — just return the content.\n */\n render: () => ReactNode;\n}\n\nexport interface UseConversationVirtualizerOptions {\n /** Ref to the scroll container element. */\n scrollRef: RefObject<HTMLElement | null>;\n /** The conversation items to virtualize. */\n items: ConversationItem[];\n /**\n * Estimated height of a single item in pixels.\n * Used before measurement. Doesn't need to be exact.\n * @default 80\n */\n estimateSize?: number;\n /**\n * Number of items to render beyond the visible area.\n * @default 8\n */\n overscan?: number;\n /**\n * Pixel distance from bottom to consider \"at bottom\" for stick-to-bottom.\n * @default 40\n */\n stickThreshold?: number;\n}\n\nexport interface UseConversationVirtualizerReturn {\n /** The TanStack virtualizer instance. */\n virtualizer: Virtualizer<HTMLElement, Element>;\n /** The virtual items to render. */\n virtualItems: VirtualItem[];\n /** Total measured/estimated height of all items. */\n totalSize: number;\n /** Whether the user is currently at the bottom of the conversation. */\n isAtBottom: boolean;\n /** Smoothly scroll to the bottom. */\n scrollToBottom: (behavior?: ScrollBehavior) => void;\n /** Get the measurement ref for a virtual item by index. */\n measureRef: (node: HTMLElement | null) => void;\n}\n\n/**\n * `useConversationVirtualizer` — virtualizes a conversation list with\n * dynamic item heights and automatic stick-to-bottom behavior.\n *\n * Built on `@tanstack/react-virtual`. Items are measured dynamically via\n * `ResizeObserver`, so tool calls expanding, subagents collapsing, and\n * streaming text growing all work correctly.\n *\n * **Stick-to-bottom**: When the user is scrolled to (or near) the bottom,\n * new items and item resizes automatically keep the viewport pinned to the\n * bottom. When the user scrolls up, stick-to-bottom disengages until they\n * scroll back down.\n *\n * @example\n * ```tsx\n * const { virtualItems, totalSize, measureRef, isAtBottom, scrollToBottom } =\n * useConversationVirtualizer({ scrollRef, items });\n *\n * return (\n * <AiConversation ref={scrollRef}>\n * <div style={{ height: totalSize, position: \"relative\" }}>\n * {virtualItems.map((vi) => {\n * const item = items[vi.index];\n * return (\n * <div key={item.key} ref={measureRef} data-index={vi.index}\n * style={{ position: \"absolute\", top: vi.start, width: \"100%\" }}>\n * {item.render()}\n * </div>\n * );\n * })}\n * </div>\n * {!isAtBottom && <button onClick={() => scrollToBottom()}>↓</button>}\n * </AiConversation>\n * );\n * ```\n */\nexport function useConversationVirtualizer({\n scrollRef,\n items,\n estimateSize = 80,\n overscan = 8,\n stickThreshold = 40,\n}: UseConversationVirtualizerOptions): UseConversationVirtualizerReturn {\n const [isAtBottom, setIsAtBottom] = useState(true);\n const isAtBottomRef = useRef(true);\n const prevCountRef = useRef(items.length);\n\n // Track whether user is at the bottom\n useEffect(() => {\n const el = scrollRef.current;\n if (!el) return;\n\n const check = () => {\n const dist = el.scrollHeight - el.scrollTop - el.clientHeight;\n const atBottom = dist <= stickThreshold;\n isAtBottomRef.current = atBottom;\n setIsAtBottom(atBottom);\n };\n\n check();\n el.addEventListener(\"scroll\", check, { passive: true });\n return () => el.removeEventListener(\"scroll\", check);\n }, [scrollRef, stickThreshold]);\n\n // Track the previous totalSize so we can detect growth from item resizes\n const prevTotalSizeRef = useRef(0);\n\n const virtualizer = useVirtualizer({\n count: items.length,\n getScrollElement: () => scrollRef.current,\n estimateSize: () => estimateSize,\n overscan,\n getItemKey: (index) => items[index]?.key ?? index,\n });\n\n // When items resize (streaming text, collapsible expand) and we're at\n // bottom, adjust scroll so the bottom stays pinned.\n virtualizer.shouldAdjustScrollPositionOnItemSizeChange = () =>\n isAtBottomRef.current;\n\n const virtualItems = virtualizer.getVirtualItems();\n const totalSize = virtualizer.getTotalSize();\n\n // When new items are added and we're at bottom, scroll to the end\n useEffect(() => {\n const count = items.length;\n if (count > prevCountRef.current && isAtBottomRef.current) {\n requestAnimationFrame(() => {\n virtualizer.scrollToIndex(count - 1, {\n align: \"end\",\n behavior: \"smooth\",\n });\n });\n }\n prevCountRef.current = count;\n }, [items.length, virtualizer]);\n\n // When totalSize grows (item content expanded, streaming text grew)\n // and we're stuck to bottom, smoothly follow by scrolling down.\n // This complements shouldAdjustScrollPositionOnItemSizeChange which\n // handles the instantaneous adjustment — this handles the smooth follow.\n useEffect(() => {\n const prev = prevTotalSizeRef.current;\n prevTotalSizeRef.current = totalSize;\n\n if (!isAtBottomRef.current || totalSize <= prev) return;\n\n const el = scrollRef.current;\n if (!el) return;\n\n // Only nudge if we're slightly off — the shouldAdjust callback handles\n // most cases, but measurement batching can leave a small gap.\n const dist = el.scrollHeight - el.scrollTop - el.clientHeight;\n if (dist > 1 && dist < 300) {\n el.scrollTop = el.scrollHeight - el.clientHeight;\n }\n }, [totalSize, scrollRef]);\n\n const scrollToBottom = useCallback(\n (behavior: ScrollBehavior = \"smooth\") => {\n if (items.length === 0) return;\n virtualizer.scrollToIndex(items.length - 1, { align: \"end\", behavior });\n },\n [virtualizer, items.length]\n );\n\n const measureRef = useCallback(\n (node: HTMLElement | null) => {\n if (node) virtualizer.measureElement(node);\n },\n [virtualizer]\n );\n\n return {\n virtualizer,\n virtualItems,\n totalSize,\n isAtBottom,\n scrollToBottom,\n measureRef,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ai-info-banner-uFxHHwBA.js","names":[],"sources":["../src/components/ai-info-banner/ai-info-banner.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n InfoIcon,\n WarningCircleIcon,\n ArrowsClockwiseIcon,\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_INFO_BANNER_VARIANTS = {\n level: {\n info: { classes: \"\", description: \"Informational notice\" },\n error: { classes: \"\", description: \"Error notice\" },\n change: {\n classes: \"\",\n description: \"State change notice (model/mode switch)\",\n },\n },\n} as const;\n\nexport const SF_AI_INFO_BANNER_DEFAULT_VARIANTS = {\n level: \"info\",\n} as const;\n\nexport type SFAiInfoBannerLevel = keyof typeof SF_AI_INFO_BANNER_VARIANTS.level;\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type AiInfoBannerProps = HTMLAttributes<HTMLDivElement> & {\n /** Banner level controls icon and color treatment. */\n level?: SFAiInfoBannerLevel;\n /** Custom icon override. */\n icon?: ElementType;\n /** Content to display. */\n children: ReactNode;\n};\n\n// ─── Level config ─────────────────────────────────────────────────────────────\n\nconst LEVEL_CONFIG: Record<\n SFAiInfoBannerLevel,\n { icon: ElementType; iconClassName: string; borderClassName: string }\n> = {\n info: {\n icon: InfoIcon,\n iconClassName: \"text-sf-info\",\n borderClassName: \"border-sf-info/20\",\n },\n error: {\n icon: WarningCircleIcon,\n iconClassName: \"text-sf-danger\",\n borderClassName: \"border-sf-danger/20\",\n },\n change: {\n icon: ArrowsClockwiseIcon,\n iconClassName: \"text-sf-subtle\",\n borderClassName: \"border-sf-line\",\n },\n};\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * Inline conversation banner for system notices. Renders as a compact,\n * horizontally centered divider-like notice rather than a chat bubble.\n *\n * Maps to harness events: `error`, `info`, `mode_changed`, `model_changed`.\n *\n * @example\n * ```tsx\n * <AiInfoBanner level=\"error\">\n * Connection lost. Retrying in 3s…\n * </AiInfoBanner>\n *\n * <AiInfoBanner level=\"change\">\n * Switched to claude-opus-4-6\n * </AiInfoBanner>\n *\n * <AiInfoBanner level=\"info\">\n * Follow-up queued — will send after current response\n * </AiInfoBanner>\n * ```\n */\nexport function AiInfoBanner({\n level = \"info\",\n icon,\n children,\n className,\n ...props\n}: AiInfoBannerProps) {\n const config = LEVEL_CONFIG[level];\n const IconComponent = icon ?? config.icon;\n\n return (\n <div\n className={cn(\n \"flex items-center justify-center gap-2 border-y px-4 py-1.5\",\n config.borderClassName,\n className\n )}\n role={level === \"error\" ? \"alert\" : \"status\"}\n {...props}\n >\n <IconComponent\n className={cn(\"size-3.5 shrink-0\", config.iconClassName)}\n />\n <span className=\"text-xs text-sf-subtle\">{children}</span>\n </div>\n );\n}\n\nAiInfoBanner.displayName = \"AiInfoBanner\";\n"],"mappings":";;;;;AAaA,IAAa,6BAA6B,EACxC,OAAO;CACL,MAAM;EAAE,SAAS;EAAI,aAAa;EAAwB;CAC1D,OAAO;EAAE,SAAS;EAAI,aAAa;EAAgB;CACnD,QAAQ;EACN,SAAS;EACT,aAAa;EACd;CACF,EACF;AAED,IAAa,qCAAqC,EAChD,OAAO,QACR;AAiBD,IAAM,eAGF;CACF,MAAM;EACJ,MAAM;EACN,eAAe;EACf,iBAAiB;EAClB;CACD,OAAO;EACL,MAAM;EACN,eAAe;EACf,iBAAiB;EAClB;CACD,QAAQ;EACN,MAAM;EACN,eAAe;EACf,iBAAiB;EAClB;CACF;;;;;;;;;;;;;;;;;;;;;;AAyBD,SAAgB,aAAa,EAC3B,QAAQ,QACR,MACA,UACA,WACA,GAAG,SACiB;CACpB,MAAM,SAAS,aAAa;CAC5B,MAAM,gBAAgB,QAAQ,OAAO;AAErC,QACE,qBAAC,OAAD;EACE,WAAW,GACT,+DACA,OAAO,iBACP,UACD;EACD,MAAM,UAAU,UAAU,UAAU;EACpC,GAAI;YAPN,CASE,oBAAC,eAAD,EACE,WAAW,GAAG,qBAAqB,OAAO,cAAc,EACxD,CAAA,EACF,oBAAC,QAAD;GAAM,WAAU;GAA0B;GAAgB,CAAA,CACtD;;;AAIV,aAAa,cAAc"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ai-message-BjnFznXy.js","names":[],"sources":["../src/components/ai-message/ai-message.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n FileIcon,\n FileTextIcon,\n ImageIcon,\n CaretLeftIcon,\n CaretRightIcon,\n XIcon,\n} from \"@phosphor-icons/react\";\nimport type {\n ComponentProps,\n HTMLAttributes,\n ReactElement,\n ReactNode,\n} from \"react\";\nimport {\n createContext,\n memo,\n useCallback,\n useContext,\n useEffect,\n useState,\n} from \"react\";\n\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"../button\";\nimport { Tooltip } from \"../tooltip\";\n\n// ─── Variants ────────────────────────────────────────────────────────────────\n\nexport const SF_AI_MESSAGE_VARIANTS = {\n from: {\n user: {\n classes: \"ml-auto w-full max-w-[70%] justify-end is-user\",\n description: \"User message — right-aligned with max width\",\n },\n assistant: {\n classes: \"w-full is-assistant\",\n description: \"Assistant message — full width left-aligned\",\n },\n system: {\n classes: \"w-full is-system\",\n description: \"System message — full width\",\n },\n },\n} as const;\n\nexport const SF_AI_MESSAGE_DEFAULT_VARIANTS = {\n from: \"assistant\",\n} as const;\n\nexport type SFAiMessageFrom = keyof typeof SF_AI_MESSAGE_VARIANTS.from;\n\n// ─── AiMessage ───────────────────────────────────────────────────────────────\n\nexport type AiMessageProps = HTMLAttributes<HTMLDivElement> & {\n /** Message sender role — controls layout and group class. */\n from: SFAiMessageFrom;\n};\n\n/**\n * Root message container. Sets layout, group class, and role context.\n *\n * @example\n * ```tsx\n * <AiMessage from=\"user\">\n * <AiMessageContent>Hello!</AiMessageContent>\n * </AiMessage>\n * <AiMessage from=\"assistant\">\n * <AiMessageContent><AiResponse>{text}</AiResponse></AiMessageContent>\n * <AiMessageToolbar>\n * <AiActions><AiAction tooltip=\"Copy\">...</AiAction></AiActions>\n * </AiMessageToolbar>\n * </AiMessage>\n * ```\n */\nexport function AiMessage({ className, from, ...props }: AiMessageProps) {\n return (\n <div\n className={cn(\n \"group flex flex-col gap-2 p-4\",\n SF_AI_MESSAGE_VARIANTS.from[from].classes,\n className\n )}\n {...props}\n />\n );\n}\n\n// ─── AiMessageContent ────────────────────────────────────────────────────────\n\nexport type AiMessageContentProps = HTMLAttributes<HTMLDivElement>;\n\n/**\n * Content bubble. Applies user/assistant bubble styles via group selectors.\n */\nexport function AiMessageContent({\n children,\n className,\n ...props\n}: AiMessageContentProps) {\n return (\n <div\n className={cn(\n \"flex w-fit max-w-full flex-col gap-2 overflow-hidden text-sm\",\n // User bubble\n \"group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-sf-control group-[.is-user]:px-4 group-[.is-user]:py-3\",\n // Assistant text\n \"group-[.is-assistant]:text-sf-default\",\n className\n )}\n {...props}\n >\n {children}\n </div>\n );\n}\n\n// ─── AiMessageToolbar ────────────────────────────────────────────────────────\n\nexport type AiMessageToolbarProps = ComponentProps<\"div\">;\n\n/**\n * Bottom toolbar row for action buttons.\n */\nexport function AiMessageToolbar({\n className,\n children,\n ...props\n}: AiMessageToolbarProps) {\n return (\n <div\n className={cn(\n \"flex w-full items-center justify-between gap-4\",\n className\n )}\n {...props}\n >\n {children}\n </div>\n );\n}\n\n// ─── AiMessageActions ────────────────────────────────────────────────────────\n\nexport type AiMessageActionsProps = ComponentProps<\"div\">;\n\n/** Flex row of message action buttons. */\nexport function AiMessageActions({\n className,\n children,\n ...props\n}: AiMessageActionsProps) {\n return (\n <div className={cn(\"flex items-center gap-1\", className)} {...props}>\n {children}\n </div>\n );\n}\n\n// ─── AiMessageAction ─────────────────────────────────────────────────────────\n\nexport type AiMessageActionProps = ComponentProps<typeof Button> & {\n /** Tooltip text and accessible label. */\n tooltip?: string;\n /** Override accessible label separately from tooltip. */\n label?: string;\n};\n\n/** Single icon action button with optional tooltip. */\nexport function AiMessageAction({\n tooltip,\n label,\n children,\n className,\n variant = \"ghost\",\n size = \"sm\",\n ...props\n}: AiMessageActionProps) {\n const button = (\n <Button\n className={cn(\"text-sf-subtle hover:text-sf-default\", className)}\n size={size}\n variant={variant}\n {...props}\n >\n {children}\n {(label ?? tooltip) && (\n <span className=\"sr-only\">{label ?? tooltip}</span>\n )}\n </Button>\n );\n\n if (tooltip) {\n return <Tooltip content={tooltip}>{button}</Tooltip>;\n }\n return button;\n}\n\n// ─── Branch context ───────────────────────────────────────────────────────────\n\ninterface AiMessageBranchContextValue {\n currentBranch: number;\n totalBranches: number;\n goToPrevious: () => void;\n goToNext: () => void;\n branches: ReactElement[];\n setBranches: (branches: ReactElement[]) => void;\n}\n\nconst AiMessageBranchContext =\n createContext<AiMessageBranchContextValue | null>(null);\n\nfunction useAiMessageBranch() {\n const ctx = useContext(AiMessageBranchContext);\n if (!ctx)\n throw new Error(\n \"AiMessageBranch sub-components must be used within <AiMessageBranch>\"\n );\n return ctx;\n}\n\n// ─── AiMessageBranch ─────────────────────────────────────────────────────────\n\nexport type AiMessageBranchProps = HTMLAttributes<HTMLDivElement> & {\n defaultBranch?: number;\n onBranchChange?: (index: number) => void;\n};\n\n/**\n * Multi-branch message container. Wraps multiple alternative responses and\n * provides prev/next navigation.\n */\nexport function AiMessageBranch({\n defaultBranch = 0,\n onBranchChange,\n className,\n ...props\n}: AiMessageBranchProps) {\n const [currentBranch, setCurrentBranch] = useState(defaultBranch);\n const [branches, setBranches] = useState<ReactElement[]>([]);\n\n const handleChange = useCallback(\n (next: number) => {\n setCurrentBranch(next);\n onBranchChange?.(next);\n },\n [onBranchChange]\n );\n\n const goToPrevious = useCallback(() => {\n handleChange(currentBranch > 0 ? currentBranch - 1 : branches.length - 1);\n }, [currentBranch, branches.length, handleChange]);\n\n const goToNext = useCallback(() => {\n handleChange(currentBranch < branches.length - 1 ? currentBranch + 1 : 0);\n }, [currentBranch, branches.length, handleChange]);\n\n return (\n <AiMessageBranchContext.Provider\n value={{\n currentBranch,\n totalBranches: branches.length,\n goToPrevious,\n goToNext,\n branches,\n setBranches,\n }}\n >\n <div className={cn(\"grid w-full gap-2\", className)} {...props} />\n </AiMessageBranchContext.Provider>\n );\n}\n\n// ─── AiMessageBranchContent ──────────────────────────────────────────────────\n\nexport type AiMessageBranchContentProps = HTMLAttributes<HTMLDivElement>;\n\n/** Renders only the active branch child. */\nexport function AiMessageBranchContent({\n children,\n ...props\n}: AiMessageBranchContentProps) {\n const { currentBranch, setBranches, branches } = useAiMessageBranch();\n const arr = Array.isArray(children) ? children : [children];\n\n useEffect(() => {\n if (branches.length !== arr.length) setBranches(arr as ReactElement[]);\n }, [arr, branches, setBranches]);\n\n return arr.map((branch, i) => (\n <div\n className={cn(\n \"grid gap-2 overflow-hidden\",\n i !== currentBranch && \"hidden\"\n )}\n key={(branch as ReactElement)?.key ?? i}\n {...props}\n >\n {branch}\n </div>\n ));\n}\n\n// ─── AiMessageBranchSelector ─────────────────────────────────────────────────\n\nexport type AiMessageBranchSelectorProps = ComponentProps<\"div\">;\n\n/** Prev/next navigator for branches. Hidden when there's only one branch. */\nexport function AiMessageBranchSelector({\n className,\n ...props\n}: AiMessageBranchSelectorProps) {\n const { totalBranches, currentBranch, goToPrevious, goToNext } =\n useAiMessageBranch();\n\n if (totalBranches <= 1) return null;\n\n return (\n <div className={cn(\"flex items-center gap-0.5\", className)} {...props}>\n <Button\n aria-label=\"Previous branch\"\n disabled={totalBranches <= 1}\n onClick={goToPrevious}\n size=\"sm\"\n variant=\"ghost\"\n >\n <CaretLeftIcon className=\"size-3\" />\n </Button>\n <span className=\"tabular-nums text-sf-subtle text-xs\">\n {currentBranch + 1} / {totalBranches}\n </span>\n <Button\n aria-label=\"Next branch\"\n disabled={totalBranches <= 1}\n onClick={goToNext}\n size=\"sm\"\n variant=\"ghost\"\n >\n <CaretRightIcon className=\"size-3\" />\n </Button>\n </div>\n );\n}\n\n// ─── Attachment helpers ───────────────────────────────────────────────────────\n\nconst IMAGE_EXTS = new Set([\"jpg\", \"jpeg\", \"png\", \"gif\", \"webp\", \"svg\", \"ico\"]);\nconst DOC_EXTS = new Set([\"pdf\", \"doc\", \"docx\", \"txt\", \"md\", \"rtf\"]);\nconst EXT_RE = /\\.[^/.]+$/;\n\nfunction fileIcon(filename: string): ReactNode {\n const ext = filename.split(\".\").pop()?.toLowerCase() ?? \"\";\n if (IMAGE_EXTS.has(ext)) return <ImageIcon className=\"size-3.5\" />;\n if (DOC_EXTS.has(ext)) return <FileTextIcon className=\"size-3.5\" />;\n return <FileIcon className=\"size-3.5\" />;\n}\n\n// ─── AiMessageAttachments ────────────────────────────────────────────────────\n\nexport type AiMessageAttachmentsProps = ComponentProps<\"div\">;\n\n/** Wrapping flex container for attachment chips. */\nexport function AiMessageAttachments({\n children,\n className,\n ...props\n}: AiMessageAttachmentsProps) {\n if (!children) return null;\n return (\n <div\n className={cn(\n \"ml-auto flex w-fit flex-wrap items-start gap-2\",\n className\n )}\n {...props}\n >\n {children}\n </div>\n );\n}\n\n// ─── AiMessageAttachment ─────────────────────────────────────────────────────\n\nexport type AiMessageAttachmentProps = HTMLAttributes<HTMLDivElement> & {\n /** Filename for display and icon selection. */\n filename: string;\n /** Object URL or data URL for images. */\n url?: string;\n /** MIME type — used to detect images. */\n mediaType?: string;\n /** Called when the remove button is clicked. */\n onRemove?: () => void;\n};\n\n/**\n * Single file attachment chip. Shows image thumbnail or file badge.\n */\nexport const AiMessageAttachment = memo(\n ({\n filename,\n url,\n mediaType,\n onRemove,\n className,\n ...props\n }: AiMessageAttachmentProps) => {\n const isImage = mediaType?.startsWith(\"image/\") && url;\n\n if (isImage) {\n return (\n <div\n className={cn(\n \"group relative size-24 overflow-hidden rounded-lg\",\n \"animate-in fade-in-0 slide-in-from-bottom-2 duration-200\",\n className\n )}\n {...props}\n >\n <img\n alt={filename || \"attachment\"}\n className=\"size-full object-cover\"\n src={url}\n />\n {onRemove && (\n <Button\n aria-label=\"Remove attachment\"\n className=\"absolute top-2 right-2 size-6 rounded-full bg-sf-overlay/80 p-0 opacity-0 backdrop-blur-sm transition-opacity group-hover:opacity-100\"\n onClick={(e) => {\n e.stopPropagation();\n onRemove();\n }}\n size=\"sm\"\n type=\"button\"\n variant=\"ghost\"\n >\n <XIcon className=\"size-3\" />\n </Button>\n )}\n </div>\n );\n }\n\n const ext = filename.split(\".\").pop()?.toUpperCase() ?? \"FILE\";\n const name = filename.replace(EXT_RE, \"\");\n\n return (\n <div\n className={cn(\n \"group relative inline-flex items-center gap-2 rounded-lg bg-sf-tint/50 px-3 py-2 transition-colors hover:bg-sf-tint\",\n \"animate-in fade-in-0 slide-in-from-bottom-2 duration-200\",\n className\n )}\n {...props}\n >\n <div className=\"flex size-8 shrink-0 items-center justify-center rounded-md bg-sf-control\">\n {fileIcon(filename)}\n </div>\n <div className=\"flex flex-col\">\n <span className=\"max-w-[200px] truncate font-medium text-sf-default text-sm\">\n {name || \"File\"}\n </span>\n <span className=\"w-fit rounded bg-sf-tint px-1.5 py-0.5 font-semibold text-sf-subtle text-[10px] uppercase\">\n {ext}\n </span>\n </div>\n {onRemove && (\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={(e) => {\n e.stopPropagation();\n onRemove();\n }}\n size=\"sm\"\n type=\"button\"\n variant=\"ghost\"\n >\n <XIcon className=\"size-3\" />\n </Button>\n )}\n </div>\n );\n }\n);\n\nAiMessageAttachment.displayName = \"AiMessageAttachment\";\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
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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"}
|