@elabs-ai/components-ai 4.1.0 → 5.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/README.md +3 -3
- package/dist/{_audio-player-media-chrome-T3XVXWRZ.js → _audio-player-media-chrome.js} +1 -1
- package/dist/_code-block-theme.js +12 -0
- package/dist/_code-block-theme.js.map +1 -0
- package/dist/{_flow-boundary-SHNWLQG5.js → _flow-boundary.js} +1 -1
- package/dist/_flow-boundary.js.map +1 -0
- package/dist/_flow-lazy.js +8 -0
- package/dist/_flow-lazy.js.map +1 -0
- package/dist/_lazy-boundary-conformance.js +2 -0
- package/dist/_lazy-boundary-conformance.js.map +1 -0
- package/dist/_lazy-cjk.js +13 -0
- package/dist/_lazy-cjk.js.map +1 -0
- package/dist/_lazy-engine-boundary.js +9 -0
- package/dist/_lazy-engine-boundary.js.map +1 -0
- package/dist/_lazy-math.js +13 -0
- package/dist/_lazy-math.js.map +1 -0
- package/dist/_lazy-mermaid.js +12 -0
- package/dist/_lazy-mermaid.js.map +1 -0
- package/dist/_mermaid-error-panel.js +9 -0
- package/dist/_mermaid-error-panel.js.map +1 -0
- package/dist/{_persona-rive-JEG44YHX.js → _persona-rive.js} +19 -24
- package/dist/_persona-rive.js.map +1 -0
- package/dist/_streamdown-i18n.js +21 -0
- package/dist/_streamdown-i18n.js.map +1 -0
- package/dist/_streamdown-safety.js +10 -0
- package/dist/_streamdown-safety.js.map +1 -0
- package/dist/_theme-scope-store.js +13 -0
- package/dist/_theme-scope-store.js.map +1 -0
- package/dist/a2ui/a2ui-surface.js +17 -0
- package/dist/a2ui/a2ui-surface.js.map +1 -0
- package/dist/a2ui/core/catalog.generated.js +10 -0
- package/dist/a2ui/core/catalog.generated.js.map +1 -0
- package/dist/a2ui/core/complete-json.js +10 -0
- package/dist/a2ui/core/complete-json.js.map +1 -0
- package/dist/a2ui/core/index.js +37 -0
- package/dist/a2ui/core/index.js.map +1 -0
- package/dist/a2ui/core/schema.js +11 -0
- package/dist/a2ui/core/schema.js.map +1 -0
- package/dist/a2ui/core/spec.js +10 -0
- package/dist/a2ui/core/spec.js.map +1 -0
- package/dist/a2ui/core/validate.js +16 -0
- package/dist/a2ui/core/validate.js.map +1 -0
- package/dist/a2ui/index.js +54 -0
- package/dist/a2ui/index.js.map +1 -0
- package/dist/a2ui/ui-catalog.js +18 -0
- package/dist/a2ui/ui-catalog.js.map +1 -0
- package/dist/agent-event.js +10 -0
- package/dist/agent-event.js.map +1 -0
- package/dist/agent-timeline.js +11 -0
- package/dist/agent-timeline.js.map +1 -0
- package/dist/agent.js +25 -0
- package/dist/agent.js.map +1 -0
- package/dist/artifact.js +23 -0
- package/dist/artifact.js.map +1 -0
- package/dist/asset-preview.js +22 -0
- package/dist/asset-preview.js.map +1 -0
- package/dist/attachments.js +33 -0
- package/dist/attachments.js.map +1 -0
- package/dist/audio-player.js +30 -0
- package/dist/audio-player.js.map +1 -0
- package/dist/audio-visualizer.js +9 -0
- package/dist/audio-visualizer.js.map +1 -0
- package/dist/canvas.js +10 -0
- package/dist/canvas.js.map +1 -0
- package/dist/chain-of-thought.js +22 -0
- package/dist/chain-of-thought.js.map +1 -0
- package/dist/chat-greeting.js +8 -0
- package/dist/chat-greeting.js.map +1 -0
- package/dist/chat-shell.js +9 -0
- package/dist/chat-shell.js.map +1 -0
- package/dist/checkpoint.js +13 -0
- package/dist/checkpoint.js.map +1 -0
- package/dist/chunk-2XOIG5TG.js +72 -0
- package/dist/chunk-2XOIG5TG.js.map +1 -0
- package/dist/chunk-34CQ2VHM.js +172 -0
- package/dist/chunk-34CQ2VHM.js.map +1 -0
- package/dist/chunk-3TDHPXZZ.js +60 -0
- package/dist/chunk-3TDHPXZZ.js.map +1 -0
- package/dist/chunk-3XNPMDZY.js +478 -0
- package/dist/chunk-3XNPMDZY.js.map +1 -0
- package/dist/chunk-3YPT3V7L.js +56 -0
- package/dist/chunk-3YPT3V7L.js.map +1 -0
- package/dist/chunk-475UEU2Q.js +87 -0
- package/dist/chunk-475UEU2Q.js.map +1 -0
- package/dist/chunk-4CBTII2I.js +95 -0
- package/dist/chunk-4CBTII2I.js.map +1 -0
- package/dist/chunk-4XRNIZMG.js +22 -0
- package/dist/chunk-4XRNIZMG.js.map +1 -0
- package/dist/chunk-5NNWUKK5.js +2 -0
- package/dist/chunk-5NNWUKK5.js.map +1 -0
- package/dist/chunk-5UOMU27Z.js +164 -0
- package/dist/chunk-5UOMU27Z.js.map +1 -0
- package/dist/chunk-5VGYZ62I.js +264 -0
- package/dist/chunk-5VGYZ62I.js.map +1 -0
- package/dist/chunk-6IQPH2OS.js +21 -0
- package/dist/chunk-6IQPH2OS.js.map +1 -0
- package/dist/chunk-6PB2MXCI.js +482 -0
- package/dist/chunk-6PB2MXCI.js.map +1 -0
- package/dist/chunk-7FD5CZEU.js +215 -0
- package/dist/chunk-7FD5CZEU.js.map +1 -0
- package/dist/chunk-7RL6KZDF.js +266 -0
- package/dist/chunk-7RL6KZDF.js.map +1 -0
- package/dist/chunk-ADOYAGGK.js +482 -0
- package/dist/chunk-ADOYAGGK.js.map +1 -0
- package/dist/chunk-AXMNJMMH.js +984 -0
- package/dist/chunk-AXMNJMMH.js.map +1 -0
- package/dist/chunk-BA3AEMOU.js +21 -0
- package/dist/chunk-BA3AEMOU.js.map +1 -0
- package/dist/chunk-BBHFOYML.js +336 -0
- package/dist/chunk-BBHFOYML.js.map +1 -0
- package/dist/chunk-BHUVISIY.js +221 -0
- package/dist/chunk-BHUVISIY.js.map +1 -0
- package/dist/chunk-BM2LWERA.js +100 -0
- package/dist/chunk-BM2LWERA.js.map +1 -0
- package/dist/chunk-CMOYXNPJ.js +211 -0
- package/dist/chunk-CMOYXNPJ.js.map +1 -0
- package/dist/chunk-CZP3RHC4.js +74 -0
- package/dist/chunk-CZP3RHC4.js.map +1 -0
- package/dist/chunk-DDG5JHQT.js +112 -0
- package/dist/chunk-DDG5JHQT.js.map +1 -0
- package/dist/chunk-DJODXO7I.js +38 -0
- package/dist/chunk-DJODXO7I.js.map +1 -0
- package/dist/chunk-DWJDJLPU.js +192 -0
- package/dist/chunk-DWJDJLPU.js.map +1 -0
- package/dist/chunk-DZW6LM22.js +45 -0
- package/dist/chunk-DZW6LM22.js.map +1 -0
- package/dist/chunk-ELGCZLZF.js +305 -0
- package/dist/chunk-ELGCZLZF.js.map +1 -0
- package/dist/chunk-EV3U6ZZB.js +209 -0
- package/dist/chunk-EV3U6ZZB.js.map +1 -0
- package/dist/chunk-EY7LVUKQ.js +87 -0
- package/dist/chunk-EY7LVUKQ.js.map +1 -0
- package/dist/chunk-F4C74F7M.js +230 -0
- package/dist/chunk-F4C74F7M.js.map +1 -0
- package/dist/chunk-FERYDFZA.js +108 -0
- package/dist/chunk-FERYDFZA.js.map +1 -0
- package/dist/chunk-FUIYG2DS.js +166 -0
- package/dist/chunk-FUIYG2DS.js.map +1 -0
- package/dist/chunk-GKYZT3LA.js +136 -0
- package/dist/chunk-GKYZT3LA.js.map +1 -0
- package/dist/chunk-H42LRUQ4.js +102 -0
- package/dist/chunk-H42LRUQ4.js.map +1 -0
- package/dist/chunk-HVXM2G5F.js +222 -0
- package/dist/chunk-HVXM2G5F.js.map +1 -0
- package/dist/chunk-I4RDAGVY.js +53 -0
- package/dist/chunk-I4RDAGVY.js.map +1 -0
- package/dist/chunk-IQJG4IDX.js +208 -0
- package/dist/chunk-IQJG4IDX.js.map +1 -0
- package/dist/chunk-IX2PXG4Y.js +144 -0
- package/dist/chunk-IX2PXG4Y.js.map +1 -0
- package/dist/chunk-IXICSOOP.js +287 -0
- package/dist/chunk-IXICSOOP.js.map +1 -0
- package/dist/chunk-JY5HSN4D.js +84 -0
- package/dist/chunk-JY5HSN4D.js.map +1 -0
- package/dist/chunk-LMNSBOBG.js +19 -0
- package/dist/chunk-LMNSBOBG.js.map +1 -0
- package/dist/chunk-MBI7D6IL.js +42 -0
- package/dist/chunk-MBI7D6IL.js.map +1 -0
- package/dist/chunk-MLFMYCPH.js +192 -0
- package/dist/chunk-MLFMYCPH.js.map +1 -0
- package/dist/chunk-MOK66734.js +33 -0
- package/dist/chunk-MOK66734.js.map +1 -0
- package/dist/chunk-MXTAWPYM.js +196 -0
- package/dist/chunk-MXTAWPYM.js.map +1 -0
- package/dist/chunk-NCL2UZKI.js +96 -0
- package/dist/chunk-NCL2UZKI.js.map +1 -0
- package/dist/chunk-NET4YLRY.js +107 -0
- package/dist/chunk-NET4YLRY.js.map +1 -0
- package/dist/chunk-NIG67CFM.js +329 -0
- package/dist/chunk-NIG67CFM.js.map +1 -0
- package/dist/chunk-NMVNNO3L.js +10 -0
- package/dist/chunk-NMVNNO3L.js.map +1 -0
- package/dist/chunk-NP7VID4K.js +2 -0
- package/dist/chunk-NP7VID4K.js.map +1 -0
- package/dist/chunk-NPUCMAG2.js +382 -0
- package/dist/chunk-NPUCMAG2.js.map +1 -0
- package/dist/chunk-NSCLRHES.js +102 -0
- package/dist/chunk-NSCLRHES.js.map +1 -0
- package/dist/chunk-OEUXHVFW.js +86 -0
- package/dist/chunk-OEUXHVFW.js.map +1 -0
- package/dist/chunk-ONBHFIMZ.js +137 -0
- package/dist/chunk-ONBHFIMZ.js.map +1 -0
- package/dist/chunk-OPH2HHL2.js +228 -0
- package/dist/chunk-OPH2HHL2.js.map +1 -0
- package/dist/chunk-OQERHIQP.js +287 -0
- package/dist/chunk-OQERHIQP.js.map +1 -0
- package/dist/chunk-P6YJAZFG.js +103 -0
- package/dist/chunk-P6YJAZFG.js.map +1 -0
- package/dist/chunk-PG7ERDOD.js +65 -0
- package/dist/chunk-PG7ERDOD.js.map +1 -0
- package/dist/chunk-PIB5JBMA.js +90 -0
- package/dist/chunk-PIB5JBMA.js.map +1 -0
- package/dist/chunk-PQ5BPIWQ.js +22 -0
- package/dist/chunk-PQ5BPIWQ.js.map +1 -0
- package/dist/chunk-PQBF6ZIQ.js +55 -0
- package/dist/chunk-PQBF6ZIQ.js.map +1 -0
- package/dist/chunk-PTWCTSZL.js +173 -0
- package/dist/chunk-PTWCTSZL.js.map +1 -0
- package/dist/chunk-QJUNPVOS.js +101 -0
- package/dist/chunk-QJUNPVOS.js.map +1 -0
- package/dist/chunk-QUDZOTRV.js +43 -0
- package/dist/chunk-QUDZOTRV.js.map +1 -0
- package/dist/chunk-RB7G65JC.js +213 -0
- package/dist/chunk-RB7G65JC.js.map +1 -0
- package/dist/chunk-RPHAOR7P.js +45 -0
- package/dist/chunk-RPHAOR7P.js.map +1 -0
- package/dist/chunk-RTCSH2AN.js +331 -0
- package/dist/chunk-RTCSH2AN.js.map +1 -0
- package/dist/chunk-S3ISPW3A.js +28 -0
- package/dist/chunk-S3ISPW3A.js.map +1 -0
- package/dist/chunk-SEVN656N.js +36 -0
- package/dist/chunk-SEVN656N.js.map +1 -0
- package/dist/chunk-SKVG672B.js +64 -0
- package/dist/chunk-SKVG672B.js.map +1 -0
- package/dist/chunk-SPQIU7GA.js +236 -0
- package/dist/chunk-SPQIU7GA.js.map +1 -0
- package/dist/chunk-SSGJ6MEM.js +1076 -0
- package/dist/chunk-SSGJ6MEM.js.map +1 -0
- package/dist/chunk-SSKZ2AED.js +118 -0
- package/dist/chunk-SSKZ2AED.js.map +1 -0
- package/dist/chunk-T56JFGXI.js +561 -0
- package/dist/chunk-T56JFGXI.js.map +1 -0
- package/dist/chunk-TCB6O74A.js +137 -0
- package/dist/chunk-TCB6O74A.js.map +1 -0
- package/dist/chunk-TTEYPO6X.js +57 -0
- package/dist/chunk-TTEYPO6X.js.map +1 -0
- package/dist/chunk-TWPHAJNM.js +462 -0
- package/dist/chunk-TWPHAJNM.js.map +1 -0
- package/dist/chunk-TXJJLP4O.js +336 -0
- package/dist/chunk-TXJJLP4O.js.map +1 -0
- package/dist/chunk-TZ6TB7VM.js +245 -0
- package/dist/chunk-TZ6TB7VM.js.map +1 -0
- package/dist/chunk-UBLKJJ7K.js +278 -0
- package/dist/chunk-UBLKJJ7K.js.map +1 -0
- package/dist/chunk-UCJ6IUFS.js +52 -0
- package/dist/chunk-UCJ6IUFS.js.map +1 -0
- package/dist/chunk-UCKFJ3QF.js +197 -0
- package/dist/chunk-UCKFJ3QF.js.map +1 -0
- package/dist/chunk-UPXSLILN.js +20 -0
- package/dist/chunk-UPXSLILN.js.map +1 -0
- package/dist/chunk-V4DORGWJ.js +92 -0
- package/dist/chunk-V4DORGWJ.js.map +1 -0
- package/dist/chunk-V6E5DYZD.js +80 -0
- package/dist/chunk-V6E5DYZD.js.map +1 -0
- package/dist/chunk-VLLYKF3P.js +96 -0
- package/dist/chunk-VLLYKF3P.js.map +1 -0
- package/dist/chunk-VLNBTOH6.js +43 -0
- package/dist/chunk-VLNBTOH6.js.map +1 -0
- package/dist/chunk-WMDSMSPL.js +303 -0
- package/dist/chunk-WMDSMSPL.js.map +1 -0
- package/dist/chunk-WU5BLGTF.js +149 -0
- package/dist/chunk-WU5BLGTF.js.map +1 -0
- package/dist/chunk-WYTZVSGK.js +245 -0
- package/dist/chunk-WYTZVSGK.js.map +1 -0
- package/dist/chunk-X47QMM6K.js +187 -0
- package/dist/chunk-X47QMM6K.js.map +1 -0
- package/dist/chunk-XNBKO32Y.js +174 -0
- package/dist/chunk-XNBKO32Y.js.map +1 -0
- package/dist/chunk-YHUSW7D2.js +40 -0
- package/dist/chunk-YHUSW7D2.js.map +1 -0
- package/dist/chunk-YSKD7UXX.js +126 -0
- package/dist/chunk-YSKD7UXX.js.map +1 -0
- package/dist/chunk-YYRDGWOU.js +72 -0
- package/dist/chunk-YYRDGWOU.js.map +1 -0
- package/dist/chunk-ZIGZMWM7.js +19 -0
- package/dist/chunk-ZIGZMWM7.js.map +1 -0
- package/dist/chunk-ZKRTUKNP.js +40 -0
- package/dist/chunk-ZKRTUKNP.js.map +1 -0
- package/dist/code-block.js +38 -0
- package/dist/code-block.js.map +1 -0
- package/dist/commit.js +51 -0
- package/dist/commit.js.map +1 -0
- package/dist/composer.js +15 -0
- package/dist/composer.js.map +1 -0
- package/dist/confirmation.js +53 -0
- package/dist/confirmation.js.map +1 -0
- package/dist/connection.js +8 -0
- package/dist/connection.js.map +1 -0
- package/dist/context-panel.js +25 -0
- package/dist/context-panel.js.map +1 -0
- package/dist/controls.js +10 -0
- package/dist/controls.js.map +1 -0
- package/dist/conversation.js +19 -0
- package/dist/conversation.js.map +1 -0
- package/dist/diff-view.js +15 -0
- package/dist/diff-view.js.map +1 -0
- package/dist/edge.js +10 -0
- package/dist/edge.js.map +1 -0
- package/dist/environment-variables.js +29 -0
- package/dist/environment-variables.js.map +1 -0
- package/dist/file-tree.js +23 -0
- package/dist/file-tree.js.map +1 -0
- package/dist/gallery.js +9 -0
- package/dist/gallery.js.map +1 -0
- package/dist/grouped-parts.js +21 -0
- package/dist/grouped-parts.js.map +1 -0
- package/dist/image.js +9 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +403 -108
- package/dist/index.js +671 -13205
- package/dist/index.js.map +1 -1
- package/dist/inline-citation.js +39 -0
- package/dist/inline-citation.js.map +1 -0
- package/dist/jsx-preview.js +17 -0
- package/dist/jsx-preview.js.map +1 -0
- package/dist/markdown-view.js +17 -0
- package/dist/markdown-view.js.map +1 -0
- package/dist/message-compare.js +12 -0
- package/dist/message-compare.js.map +1 -0
- package/dist/message-edit.js +19 -0
- package/dist/message-edit.js.map +1 -0
- package/dist/message-feedback.js +11 -0
- package/dist/message-feedback.js.map +1 -0
- package/dist/message-form-spec.js +38 -0
- package/dist/message-form-spec.js.map +1 -0
- package/dist/message-form.js +26 -0
- package/dist/message-form.js.map +1 -0
- package/dist/message-table-spec.js +26 -0
- package/dist/message-table-spec.js.map +1 -0
- package/dist/message-table.js +12 -0
- package/dist/message-table.js.map +1 -0
- package/dist/message.js +51 -0
- package/dist/message.js.map +1 -0
- package/dist/mic-selector.js +27 -0
- package/dist/mic-selector.js.map +1 -0
- package/dist/model-provider-logo.js +13 -0
- package/dist/model-provider-logo.js.map +1 -0
- package/dist/motion-config.js +9 -0
- package/dist/motion-config.js.map +1 -0
- package/dist/node.js +22 -0
- package/dist/node.js.map +1 -0
- package/dist/open-in-chat.js +31 -0
- package/dist/open-in-chat.js.map +1 -0
- package/dist/package-info.js +25 -0
- package/dist/package-info.js.map +1 -0
- package/dist/panel.js +10 -0
- package/dist/panel.js.map +1 -0
- package/dist/part-groups.js +18 -0
- package/dist/part-groups.js.map +1 -0
- package/dist/permission-mode-select.js +9 -0
- package/dist/permission-mode-select.js.map +1 -0
- package/dist/persona-sources.js +8 -0
- package/dist/persona-sources.js.map +1 -0
- package/dist/persona.js +14 -0
- package/dist/persona.js.map +1 -0
- package/dist/plan.js +32 -0
- package/dist/plan.js.map +1 -0
- package/dist/prompt-input-effort.js +9 -0
- package/dist/prompt-input-effort.js.map +1 -0
- package/dist/prompt-input-mode.js +10 -0
- package/dist/prompt-input-mode.js.map +1 -0
- package/dist/prompt-input-slash.js +12 -0
- package/dist/prompt-input-slash.js.map +1 -0
- package/dist/prompt-input.js +89 -0
- package/dist/prompt-input.js.map +1 -0
- package/dist/queue.js +37 -0
- package/dist/queue.js.map +1 -0
- package/dist/reasoning.js +23 -0
- package/dist/reasoning.js.map +1 -0
- package/dist/sandbox.js +28 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/schema-display.js +33 -0
- package/dist/schema-display.js.map +1 -0
- package/dist/selection-toolbar.js +11 -0
- package/dist/selection-toolbar.js.map +1 -0
- package/dist/session-header.js +9 -0
- package/dist/session-header.js.map +1 -0
- package/dist/session-status-bar.js +9 -0
- package/dist/session-status-bar.js.map +1 -0
- package/dist/shimmer.js +9 -0
- package/dist/shimmer.js.map +1 -0
- package/dist/snippet.js +17 -0
- package/dist/snippet.js.map +1 -0
- package/dist/sources.js +17 -0
- package/dist/sources.js.map +1 -0
- package/dist/speech-input.js +9 -0
- package/dist/speech-input.js.map +1 -0
- package/dist/stack-trace.js +27 -0
- package/dist/stack-trace.js.map +1 -0
- package/dist/suggestion.js +16 -0
- package/dist/suggestion.js.map +1 -0
- package/dist/task.js +18 -0
- package/dist/task.js.map +1 -0
- package/dist/test-results.js +43 -0
- package/dist/test-results.js.map +1 -0
- package/dist/token-usage.js +27 -0
- package/dist/token-usage.js.map +1 -0
- package/dist/tool-result-card.js +9 -0
- package/dist/tool-result-card.js.map +1 -0
- package/dist/tool.js +27 -0
- package/dist/tool.js.map +1 -0
- package/dist/toolbar.js +10 -0
- package/dist/toolbar.js.map +1 -0
- package/dist/transcription.js +11 -0
- package/dist/transcription.js.map +1 -0
- package/dist/turn-status.js +9 -0
- package/dist/turn-status.js.map +1 -0
- package/dist/use-audio-level.js +9 -0
- package/dist/use-audio-level.js.map +1 -0
- package/dist/voice-selector.js +47 -0
- package/dist/voice-selector.js.map +1 -0
- package/dist/web-preview.js +19 -0
- package/dist/web-preview.js.map +1 -0
- package/package.json +12 -10
- package/schemas/a2ui-surface.v1.schema.json +5425 -0
- package/src/__contract__/audio-visualizer.contract.test.tsx +49 -0
- package/src/__contract__/chat-shell.contract.test.tsx +49 -0
- package/src/__contract__/grouped-parts.contract.test.tsx +49 -0
- package/src/__contract__/image.contract.test.tsx +49 -0
- package/src/__contract__/markdown-view.contract.test.tsx +49 -0
- package/src/__contract__/message-feedback.contract.test.tsx +49 -0
- package/src/__contract__/message-form.contract.test.tsx +49 -0
- package/src/__contract__/message-table.contract.test.tsx +49 -0
- package/src/__contract__/model-provider-logo.contract.test.tsx +49 -0
- package/src/__contract__/persona.contract.test.tsx +49 -0
- package/src/__contract__/prompt-input-effort.contract.test.tsx +49 -0
- package/src/__contract__/prompt-input-mode.contract.test.tsx +49 -0
- package/src/_chat-shell-rail.tsx +4 -2
- package/src/_flow-boundary.tsx +2 -2
- package/src/_lazy-cjk.test.ts +43 -0
- package/src/_lazy-cjk.ts +73 -0
- package/src/_lazy-math.test.ts +63 -0
- package/src/_lazy-math.ts +90 -0
- package/src/_persona-rive.tsx +27 -41
- package/src/_streamdown-i18n.ts +73 -21
- package/src/_streamdown-safety.ts +2 -2
- package/src/_theme-scope-store.test.ts +83 -0
- package/src/_theme-scope-store.ts +103 -0
- package/src/a2ui/a2ui-surface.stories.tsx +662 -0
- package/src/a2ui/a2ui-surface.test.tsx +201 -0
- package/src/a2ui/a2ui-surface.tsx +354 -0
- package/src/a2ui/catalog.source.json +772 -0
- package/src/a2ui/core/a2ui-core.test.ts +178 -0
- package/src/a2ui/core/catalog.generated.ts +1006 -0
- package/src/a2ui/core/complete-json.ts +81 -0
- package/src/a2ui/core/index.ts +10 -0
- package/src/a2ui/core/schema.ts +111 -0
- package/src/a2ui/core/spec.ts +148 -0
- package/src/a2ui/core/validate.ts +266 -0
- package/src/a2ui/index.ts +7 -0
- package/src/a2ui/ui-catalog.tsx +225 -0
- package/src/a2ui/wave1-chart-spec.test.ts +87 -0
- package/src/a2ui/wave2-chart-spec.test.ts +108 -0
- package/src/agent-event.tsx +1 -1
- package/src/agent.tsx +18 -13
- package/src/agentic-workspace.stories.tsx +6 -45
- package/src/artifact.tsx +2 -2
- package/src/asset-preview.test.tsx +40 -0
- package/src/asset-preview.tsx +83 -12
- package/src/attachments.tsx +7 -4
- package/src/chat-shell.test.tsx +40 -10
- package/src/chat-shell.tsx +90 -44
- package/src/chat.stories.tsx +47 -42
- package/src/code-block.test.tsx +100 -1
- package/src/code-block.tsx +166 -103
- package/src/commit.tsx +30 -41
- package/src/composer.stories.tsx +4 -5
- package/src/composer.test.tsx +45 -0
- package/src/composer.tsx +104 -21
- package/src/confirmation.tsx +1 -1
- package/src/context-panel.stories.tsx +1 -1
- package/src/context-panel.tsx +2 -2
- package/src/conversation.stories.tsx +24 -0
- package/src/conversation.test.tsx +69 -0
- package/src/conversation.tsx +47 -8
- package/src/diff-view.stories.tsx +79 -0
- package/src/diff-view.test.tsx +52 -2
- package/src/diff-view.tsx +92 -36
- package/src/environment-variables.tsx +20 -37
- package/src/file-tree.test.tsx +21 -0
- package/src/file-tree.tsx +33 -16
- package/src/gallery.stories.tsx +42 -0
- package/src/gallery.tsx +2 -2
- package/src/grouped-parts.stories.tsx +23 -0
- package/src/grouped-parts.tsx +1 -1
- package/src/index.ts +1 -0
- package/src/inline-citation.tsx +5 -5
- package/src/jsx-preview.tsx +151 -43
- package/src/markdown-view.test.tsx +7 -3
- package/src/markdown-view.tsx +8 -1
- package/src/message-compare.stories.tsx +40 -0
- package/src/message-compare.tsx +2 -1
- package/src/message-form.stories.tsx +36 -3
- package/src/message-form.test.tsx +8 -2
- package/src/message-form.tsx +16 -8
- package/src/message-table.stories.tsx +26 -2
- package/src/message-table.test.tsx +7 -0
- package/src/message-table.tsx +9 -5
- package/src/message.test.tsx +73 -3
- package/src/message.tsx +36 -12
- package/src/model-provider-logo.test.tsx +57 -3
- package/src/model-provider-logo.tsx +45 -11
- package/src/open-in-chat.tsx +50 -29
- package/src/package-info.tsx +23 -20
- package/src/prompt-input-slash.stories.tsx +1 -1
- package/src/prompt-input.test.tsx +67 -1
- package/src/prompt-input.tsx +42 -4
- package/src/queue.tsx +4 -4
- package/src/reasoning.stories.tsx +27 -0
- package/src/reasoning.tsx +17 -6
- package/src/sandbox.tsx +3 -3
- package/src/schema-display.test.tsx +56 -0
- package/src/schema-display.tsx +68 -37
- package/src/session-header.stories.tsx +35 -1
- package/src/session-header.tsx +1 -1
- package/src/snippet.test.tsx +6 -2
- package/src/snippet.tsx +14 -34
- package/src/sources.stories.tsx +30 -0
- package/src/sources.tsx +4 -1
- package/src/speech-input.test.tsx +109 -0
- package/src/speech-input.tsx +31 -3
- package/src/stack-trace.tsx +20 -36
- package/src/streamdown-i18n.test.tsx +23 -0
- package/src/templates-ai-assistant.stories.tsx +151 -87
- package/src/test-results.tsx +47 -27
- package/src/token-usage.tsx +6 -6
- package/src/tool.stories.tsx +39 -0
- package/src/tool.test.tsx +65 -0
- package/src/tool.tsx +61 -25
- package/src/toolbar.tsx +0 -10
- package/src/transcription.tsx +1 -1
- package/src/voice-selector.tsx +5 -5
- package/src/web-preview.test.tsx +51 -1
- package/src/web-preview.tsx +39 -8
- package/dist/_flow-boundary-SHNWLQG5.js.map +0 -1
- package/dist/_persona-rive-JEG44YHX.js.map +0 -1
- /package/dist/{_audio-player-media-chrome-T3XVXWRZ.js.map → _audio-player-media-chrome.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/plan.tsx"],"sourcesContent":["\"use client\";\n\nimport { Button } from \"@elabs-ai/components-ui\";\nimport {\n Card,\n CardAction,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@elabs-ai/components-ui\";\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from \"@elabs-ai/components-ui\";\nimport { StatusBadge, type StatusTone } from \"@elabs-ai/components-ui\";\nimport { useLocale } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { CheckCircle2, ChevronsUpDownIcon, Clock, PencilLine, type LucideIcon } from \"lucide-react\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { createContext, useContext, useId, useMemo } from \"react\";\n\nimport { Shimmer } from \"./shimmer\";\n\n/**\n * The plan's decision status (#108). Additive: a `Plan` rendered with no\n * `status` behaves exactly as before (no ARIA region, no rail, no status\n * line) — every prop below this point is opt-in.\n */\nexport type PlanStatus = \"streaming\" | \"awaiting\" | \"approved\" | \"changes-requested\";\n\ninterface PlanContextValue {\n isStreaming: boolean;\n /** `undefined` when the plan is rendered via the legacy `isStreaming`-only API. */\n status?: PlanStatus;\n /** Wired to `PlanTitle` so the awaiting region can be labelled by it. */\n titleId?: string;\n}\n\nconst PlanContext = createContext<PlanContextValue | null>(null);\n\nconst usePlan = () => {\n const context = useContext(PlanContext);\n if (!context) {\n throw new Error(\"Plan components must be used within Plan\");\n }\n return context;\n};\n\nexport type PlanProps = ComponentProps<typeof Collapsible> & {\n isStreaming?: boolean;\n /**\n * The plan's decision status. Omit it to keep today's display-only\n * behaviour. When both `status` and `isStreaming` are given, `status`\n * wins (a `status=\"approved\"` plan never shimmers, even if `isStreaming`\n * is still `true`).\n */\n status?: PlanStatus;\n};\n\nexport const Plan = ({\n className,\n isStreaming = false,\n status,\n role,\n children,\n ...props\n}: PlanProps) => {\n const titleId = useId();\n const hasStatus = status !== undefined;\n const resolvedIsStreaming = hasStatus ? status === \"streaming\" : isStreaming;\n\n // A pending decision containing focusable controls is a labelled region,\n // NOT an assertive live region; a settled outcome is `role=\"alert\"` — the\n // same convention `Confirmation` already proves (packages/ai/src/confirmation.tsx:100-101).\n const isPending = status === \"awaiting\";\n const isSettled = status === \"approved\" || status === \"changes-requested\";\n const resolvedRole = hasStatus\n ? isPending\n ? \"group\"\n : isSettled\n ? \"alert\"\n : undefined\n : undefined;\n\n // Quiet structural rail per outcome — the outcome itself is always ALSO\n // carried in text + glyph via `PlanStatusLine` (WCAG 1.4.1), the rail is a\n // redundant, hue-independent cue on top of it.\n const railClass = !hasStatus\n ? undefined\n : status === \"approved\"\n ? \"border-s-4 border-s-success\"\n : status === \"changes-requested\"\n ? \"border-s-4 border-s-warning\"\n : isPending\n ? \"border-s-4 border-s-border-strong\"\n : undefined;\n\n const contextValue = useMemo<PlanContextValue>(\n () => ({\n isStreaming: resolvedIsStreaming,\n status: hasStatus ? status : undefined,\n titleId: hasStatus ? titleId : undefined,\n }),\n [resolvedIsStreaming, hasStatus, status, titleId],\n );\n\n return (\n <PlanContext.Provider value={contextValue}>\n <Collapsible asChild data-slot=\"plan\" {...props}>\n <Card\n role={role ?? resolvedRole}\n aria-labelledby={isPending ? titleId : undefined}\n className={cn(\"shadow-none\", railClass, className)}\n >\n {children}\n </Card>\n </Collapsible>\n </PlanContext.Provider>\n );\n};\n\nexport type PlanHeaderProps = ComponentProps<typeof CardHeader>;\n\nexport const PlanHeader = ({ className, ...props }: PlanHeaderProps) => (\n <CardHeader\n className={cn(\"flex items-start justify-between\", className)}\n data-slot=\"plan-header\"\n {...props}\n />\n);\n\nexport type PlanTitleProps = Omit<ComponentProps<typeof CardTitle>, \"children\"> & {\n children: string;\n};\n\nexport const PlanTitle = ({ id, children, ...props }: PlanTitleProps) => {\n const { isStreaming, titleId } = usePlan();\n\n return (\n <CardTitle id={id ?? titleId} data-slot=\"plan-title\" {...props}>\n {isStreaming ? <Shimmer>{children}</Shimmer> : children}\n </CardTitle>\n );\n};\n\nexport type PlanDescriptionProps = Omit<ComponentProps<typeof CardDescription>, \"children\"> & {\n children: string;\n};\n\nexport const PlanDescription = ({ className, children, ...props }: PlanDescriptionProps) => {\n const { isStreaming } = usePlan();\n\n return (\n <CardDescription\n className={cn(\"text-balance\", className)}\n data-slot=\"plan-description\"\n {...props}\n >\n {isStreaming ? <Shimmer>{children}</Shimmer> : children}\n </CardDescription>\n );\n};\n\nexport type PlanActionProps = ComponentProps<typeof CardAction>;\n\n/** The header's layout slot (e.g. a menu trigger) — unrelated to the decision actions below. */\nexport const PlanAction = (props: PlanActionProps) => (\n <CardAction data-slot=\"plan-action\" {...props} />\n);\n\nexport type PlanContentProps = ComponentProps<typeof CardContent>;\n\nexport const PlanContent = ({ className, ...props }: PlanContentProps) => (\n <CollapsibleContent asChild>\n <CardContent\n className={cn(\n \"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:slide-in-from-top-2 data-[state=closed]:slide-out-to-top-2 data-[state=closed]:fade-out-0 data-[state=open]:[--tw-ease:var(--ease-entrance)] data-[state=closed]:[--tw-ease:var(--ease-exit)] outline-none\",\n className,\n )}\n data-slot=\"plan-content\"\n {...props}\n />\n </CollapsibleContent>\n);\n\nexport type PlanFooterProps = ComponentProps<\"div\">;\n\nexport const PlanFooter = (props: PlanFooterProps) => (\n <CardFooter data-slot=\"plan-footer\" {...props} />\n);\n\nexport type PlanTriggerProps = ComponentProps<typeof CollapsibleTrigger>;\n\nexport const PlanTrigger = ({ className, ...props }: PlanTriggerProps) => {\n const { t } = useLocale();\n return (\n <CollapsibleTrigger asChild>\n <Button\n className={cn(\"size-8\", className)}\n data-slot=\"plan-trigger\"\n size=\"icon\"\n variant=\"ghost\"\n {...props}\n >\n <ChevronsUpDownIcon className=\"size-4\" />\n <span className=\"sr-only\">{t(\"ai.plan.togglePlan\")}</span>\n </Button>\n </CollapsibleTrigger>\n );\n};\n\ntype PlanOutcomeStatus = Exclude<PlanStatus, \"streaming\">;\n\n/**\n * Default label/tone/glyph per non-streaming status (#108). Every entry is a\n * CALM tone (the `StatusBadge` out-of-vocabulary escape hatch, #363) — the\n * decision is carried in text + glyph, never by rail colour alone (WCAG 1.4.1).\n */\nconst PLAN_STATUS_CONTENT: Record<\n PlanOutcomeStatus,\n { label: string; tone: StatusTone; icon: LucideIcon }\n> = {\n awaiting: { label: \"Waiting on plan approval\", tone: \"warning\", icon: Clock },\n approved: { label: \"Approved\", tone: \"success\", icon: CheckCircle2 },\n \"changes-requested\": { label: \"Changes requested\", tone: \"warning\", icon: PencilLine },\n};\n\nexport type PlanStatusLineProps = Omit<ComponentProps<typeof StatusBadge>, \"status\"> & {\n children?: ReactNode;\n};\n\n/**\n * The decision status line (#108) — text + glyph, never rail colour alone.\n * Renders nothing while `status` is unset or `\"streaming\"` (nothing has been\n * decided yet); shows the outcome once the plan is awaiting a decision or\n * settled.\n */\nexport const PlanStatusLine = ({ className, children, ...props }: PlanStatusLineProps) => {\n const { status } = usePlan();\n\n if (!status || status === \"streaming\") {\n return null;\n }\n\n const { label, tone, icon } = PLAN_STATUS_CONTENT[status];\n\n return (\n <StatusBadge\n data-slot=\"plan-status-line\"\n className={cn(\"self-start\", className)}\n status={{ label, tone, icon }}\n {...props}\n >\n {children}\n </StatusBadge>\n );\n};\n\nexport type PlanDecisionActionProps = ComponentProps<typeof Button>;\n\n/**\n * Shared decision-button primitive. Renders only while `status === \"awaiting\"`\n * — mirrors the gating `ConfirmationActions` uses (packages/ai/src/confirmation.tsx)\n * — so a settled or still-streaming plan never shows stale controls.\n */\nconst PlanDecisionAction = ({ className, ...props }: PlanDecisionActionProps) => {\n const { status } = usePlan();\n\n if (status !== \"awaiting\") {\n return null;\n }\n\n return <Button className={cn(\"h-8 px-3 text-body\", className)} type=\"button\" {...props} />;\n};\n\nexport type PlanApproveProps = PlanDecisionActionProps;\n\n/**\n * The proceed action — presets the filled primary variant so the primary path\n * can't drift to a quieter variant (same reasoning as `ConfirmationApprove`).\n */\nexport const PlanApprove = (props: PlanApproveProps) => (\n <PlanDecisionAction data-slot=\"plan-approve\" variant=\"default\" {...props} />\n);\n\nexport type PlanRequestChangesProps = PlanDecisionActionProps;\n\n/** Sends the plan back for revision — a quieter, non-primary action. */\nexport const PlanRequestChanges = (props: PlanRequestChangesProps) => (\n <PlanDecisionAction data-slot=\"plan-request-changes\" variant=\"outline\" {...props} />\n);\n\nexport type PlanCommentProps = PlanDecisionActionProps;\n\n/** Leaves feedback without settling the decision — the quietest action. */\nexport const PlanComment = (props: PlanCommentProps) => (\n <PlanDecisionAction data-slot=\"plan-comment\" variant=\"ghost\" {...props} />\n);\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/selection-toolbar.tsx
|
|
4
|
+
import {
|
|
5
|
+
forwardRef,
|
|
6
|
+
useCallback,
|
|
7
|
+
useEffect,
|
|
8
|
+
useRef,
|
|
9
|
+
useState
|
|
10
|
+
} from "react";
|
|
11
|
+
import { Quote } from "lucide-react";
|
|
12
|
+
import { Button, Popover, PopoverAnchor, PopoverContent } from "@elabs-ai/components-ui";
|
|
13
|
+
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
14
|
+
import { useLocale } from "@elabs-ai/components-ui";
|
|
15
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
16
|
+
function isEditableNode(node) {
|
|
17
|
+
let el = node;
|
|
18
|
+
while (el) {
|
|
19
|
+
if (el instanceof HTMLElement) {
|
|
20
|
+
const tag = el.tagName;
|
|
21
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || el.isContentEditable) return true;
|
|
22
|
+
}
|
|
23
|
+
el = el.parentNode;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
function readSelectionWithin(container) {
|
|
28
|
+
if (!container || typeof window === "undefined") return null;
|
|
29
|
+
const selection = window.getSelection();
|
|
30
|
+
if (!selection || selection.isCollapsed || selection.rangeCount === 0) return null;
|
|
31
|
+
const text = selection.toString().trim();
|
|
32
|
+
if (text.length === 0) return null;
|
|
33
|
+
const { anchorNode, focusNode } = selection;
|
|
34
|
+
if (!anchorNode || !focusNode) return null;
|
|
35
|
+
if (!container.contains(anchorNode) || !container.contains(focusNode)) return null;
|
|
36
|
+
if (isEditableNode(anchorNode)) return null;
|
|
37
|
+
const rect = selection.getRangeAt(0).getBoundingClientRect();
|
|
38
|
+
if (rect.width <= 0 && rect.height <= 0) return null;
|
|
39
|
+
return {
|
|
40
|
+
text,
|
|
41
|
+
rect: { top: rect.top, left: rect.left, width: rect.width, height: rect.height }
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
var SelectionToolbar = forwardRef(
|
|
45
|
+
function SelectionToolbar2({ onQuote, quoteLabel = "Quote", actions, disabled = false, className, children, ...props }, ref) {
|
|
46
|
+
const { t } = useLocale();
|
|
47
|
+
const containerRef = useRef(null);
|
|
48
|
+
const [snapshot, setSnapshot] = useState(null);
|
|
49
|
+
const open = snapshot !== null;
|
|
50
|
+
const setContainer = useCallback(
|
|
51
|
+
(node) => {
|
|
52
|
+
containerRef.current = node;
|
|
53
|
+
if (typeof ref === "function") ref(node);
|
|
54
|
+
else if (ref) ref.current = node;
|
|
55
|
+
},
|
|
56
|
+
[ref]
|
|
57
|
+
);
|
|
58
|
+
const refresh = useCallback(() => {
|
|
59
|
+
if (disabled) return;
|
|
60
|
+
setSnapshot(readSelectionWithin(containerRef.current));
|
|
61
|
+
}, [disabled]);
|
|
62
|
+
const dismiss = useCallback(() => setSnapshot(null), []);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (disabled || typeof document === "undefined") return;
|
|
65
|
+
const onSelectionChange = () => {
|
|
66
|
+
const sel = window.getSelection();
|
|
67
|
+
if (!sel || sel.isCollapsed || sel.toString().trim().length === 0) dismiss();
|
|
68
|
+
};
|
|
69
|
+
const onScroll = () => dismiss();
|
|
70
|
+
const onKeyDown = (e) => {
|
|
71
|
+
if (e.key === "Escape") dismiss();
|
|
72
|
+
};
|
|
73
|
+
document.addEventListener("selectionchange", onSelectionChange);
|
|
74
|
+
window.addEventListener("scroll", onScroll, true);
|
|
75
|
+
document.addEventListener("keydown", onKeyDown);
|
|
76
|
+
return () => {
|
|
77
|
+
document.removeEventListener("selectionchange", onSelectionChange);
|
|
78
|
+
window.removeEventListener("scroll", onScroll, true);
|
|
79
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
80
|
+
};
|
|
81
|
+
}, [disabled, dismiss]);
|
|
82
|
+
const handleQuote = () => {
|
|
83
|
+
if (snapshot) onQuote?.(snapshot.text);
|
|
84
|
+
dismiss();
|
|
85
|
+
};
|
|
86
|
+
const rect = snapshot?.rect;
|
|
87
|
+
return /* @__PURE__ */ jsxs(
|
|
88
|
+
"div",
|
|
89
|
+
{
|
|
90
|
+
ref: setContainer,
|
|
91
|
+
className: cn("relative", className),
|
|
92
|
+
onPointerUp: refresh,
|
|
93
|
+
onKeyUp: refresh,
|
|
94
|
+
...props,
|
|
95
|
+
children: [
|
|
96
|
+
children,
|
|
97
|
+
/* @__PURE__ */ jsxs(Popover, { open, onOpenChange: (next) => next ? void 0 : dismiss(), children: [
|
|
98
|
+
/* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
99
|
+
"span",
|
|
100
|
+
{
|
|
101
|
+
"aria-hidden": "true",
|
|
102
|
+
className: "pointer-events-none fixed",
|
|
103
|
+
style: rect ? { top: rect.top, left: rect.left, width: rect.width, height: rect.height } : { top: 0, left: 0, width: 0, height: 0 }
|
|
104
|
+
}
|
|
105
|
+
) }),
|
|
106
|
+
/* @__PURE__ */ jsxs(
|
|
107
|
+
PopoverContent,
|
|
108
|
+
{
|
|
109
|
+
side: "top",
|
|
110
|
+
align: "center",
|
|
111
|
+
sideOffset: 8,
|
|
112
|
+
onCloseAutoFocus: (e) => e.preventDefault(),
|
|
113
|
+
className: "flex w-auto items-center gap-1 p-1",
|
|
114
|
+
role: "toolbar",
|
|
115
|
+
"aria-label": t("ai.selectionToolbar.label"),
|
|
116
|
+
children: [
|
|
117
|
+
/* @__PURE__ */ jsxs(Button, { size: "sm", variant: "ghost", onClick: handleQuote, children: [
|
|
118
|
+
/* @__PURE__ */ jsx(Quote, { "aria-hidden": "true", className: "size-4" }),
|
|
119
|
+
quoteLabel
|
|
120
|
+
] }),
|
|
121
|
+
actions
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
] })
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
export {
|
|
133
|
+
readSelectionWithin,
|
|
134
|
+
SelectionToolbar
|
|
135
|
+
};
|
|
136
|
+
//# sourceMappingURL=chunk-GKYZT3LA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/selection-toolbar.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * SelectionToolbar — a floating toolbar over selected transcript text.\n *\n * Wrap a transcript region; when the user selects text inside it, a small\n * toolbar floats above the selection with a single default action: Quote.\n * `onQuote(selectedText)` fires — the consumer prepends a `>`-prefixed\n * blockquote into the composer. Positioned with the `@elabs-ai/components-ui` Popover\n * primitives (a virtual anchor at the selection rect); dismisses when the\n * selection collapses. Presentational only.\n */\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { Quote } from \"lucide-react\";\nimport { Button, Popover, PopoverAnchor, PopoverContent } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { useLocale } from \"@elabs-ai/components-ui\";\n\n/** A captured selection snapshot: the trimmed text + its viewport rect. */\nexport interface SelectionSnapshot {\n text: string;\n rect: { top: number; left: number; width: number; height: number };\n}\n\nfunction isEditableNode(node: Node | null): boolean {\n let el: Node | null = node;\n while (el) {\n if (el instanceof HTMLElement) {\n const tag = el.tagName;\n if (tag === \"INPUT\" || tag === \"TEXTAREA\" || el.isContentEditable) return true;\n }\n el = el.parentNode;\n }\n return false;\n}\n\n/**\n * Read the current selection if it is a non-empty range fully inside\n * `container` (and not inside a form field / editor). Returns `null` otherwise.\n * Exported for testing + custom integrations.\n */\nexport function readSelectionWithin(container: HTMLElement | null): SelectionSnapshot | null {\n if (!container || typeof window === \"undefined\") return null;\n const selection = window.getSelection();\n if (!selection || selection.isCollapsed || selection.rangeCount === 0) return null;\n\n const text = selection.toString().trim();\n if (text.length === 0) return null;\n\n const { anchorNode, focusNode } = selection;\n if (!anchorNode || !focusNode) return null;\n if (!container.contains(anchorNode) || !container.contains(focusNode)) return null;\n if (isEditableNode(anchorNode)) return null;\n\n const rect = selection.getRangeAt(0).getBoundingClientRect();\n if (rect.width <= 0 && rect.height <= 0) return null;\n\n return {\n text,\n rect: { top: rect.top, left: rect.left, width: rect.width, height: rect.height },\n };\n}\n\nexport interface SelectionToolbarProps extends HTMLAttributes<HTMLDivElement> {\n /** Called with the selected text when the user clicks Quote. */\n onQuote?: (selectedText: string) => void;\n /** Quote button label. @default \"Quote\" */\n quoteLabel?: string;\n /** Extra toolbar controls rendered after the Quote button. */\n actions?: ReactNode;\n /** Disable selection detection entirely. */\n disabled?: boolean;\n /** The transcript region to watch for selections. */\n children: ReactNode;\n}\n\n/**\n * Wraps a region and shows a floating Quote toolbar over selected text.\n * The wrapper renders inline (`display: contents` is avoided — it's a real\n * relative box) so the anchor + portal position correctly.\n */\nexport const SelectionToolbar = forwardRef<HTMLDivElement, SelectionToolbarProps>(\n function SelectionToolbar(\n { onQuote, quoteLabel = \"Quote\", actions, disabled = false, className, children, ...props },\n ref,\n ) {\n const { t } = useLocale();\n const containerRef = useRef<HTMLDivElement | null>(null);\n const [snapshot, setSnapshot] = useState<SelectionSnapshot | null>(null);\n const open = snapshot !== null;\n\n const setContainer = useCallback(\n (node: HTMLDivElement | null) => {\n containerRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) ref.current = node;\n },\n [ref],\n );\n\n const refresh = useCallback(() => {\n if (disabled) return;\n setSnapshot(readSelectionWithin(containerRef.current));\n }, [disabled]);\n\n const dismiss = useCallback(() => setSnapshot(null), []);\n\n // Open after the user finishes selecting; close when the selection collapses.\n useEffect(() => {\n if (disabled || typeof document === \"undefined\") return;\n const onSelectionChange = () => {\n const sel = window.getSelection();\n if (!sel || sel.isCollapsed || sel.toString().trim().length === 0) dismiss();\n };\n const onScroll = () => dismiss();\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") dismiss();\n };\n document.addEventListener(\"selectionchange\", onSelectionChange);\n window.addEventListener(\"scroll\", onScroll, true);\n document.addEventListener(\"keydown\", onKeyDown);\n return () => {\n document.removeEventListener(\"selectionchange\", onSelectionChange);\n window.removeEventListener(\"scroll\", onScroll, true);\n document.removeEventListener(\"keydown\", onKeyDown);\n };\n }, [disabled, dismiss]);\n\n const handleQuote = () => {\n if (snapshot) onQuote?.(snapshot.text);\n dismiss();\n };\n\n const rect = snapshot?.rect;\n\n return (\n <div\n ref={setContainer}\n className={cn(\"relative\", className)}\n onPointerUp={refresh}\n onKeyUp={refresh}\n {...props}\n >\n {children}\n\n <Popover open={open} onOpenChange={(next) => (next ? undefined : dismiss())}>\n {/* Virtual anchor positioned at the selection rect (viewport coords). */}\n <PopoverAnchor asChild>\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none fixed\"\n style={\n rect\n ? { top: rect.top, left: rect.left, width: rect.width, height: rect.height }\n : { top: 0, left: 0, width: 0, height: 0 }\n }\n />\n </PopoverAnchor>\n <PopoverContent\n side=\"top\"\n align=\"center\"\n sideOffset={8}\n // On open, let focus move to the Quote button so the action is\n // keyboard-reachable (a11y: mouse-operable ⇒ keyboard-operable). The\n // captured snapshot text drives the quote, so a grayed selection is\n // fine. On close, DON'T return focus to the anchor (it's an\n // aria-hidden virtual span) — prevent that jump.\n onCloseAutoFocus={(e) => e.preventDefault()}\n className=\"flex w-auto items-center gap-1 p-1\"\n role=\"toolbar\"\n aria-label={t(\"ai.selectionToolbar.label\")}\n >\n <Button size=\"sm\" variant=\"ghost\" onClick={handleQuote}>\n <Quote aria-hidden=\"true\" className=\"size-4\" />\n {quoteLabel}\n </Button>\n {actions}\n </PopoverContent>\n </Popover>\n </div>\n );\n },\n);\n"],"mappings":"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","names":["SelectionToolbar"]}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
PromptInputButton
|
|
4
|
+
} from "./chunk-SSGJ6MEM.js";
|
|
5
|
+
|
|
6
|
+
// src/prompt-input-mode.tsx
|
|
7
|
+
import { forwardRef, useId } from "react";
|
|
8
|
+
import { useControllableState } from "@radix-ui/react-use-controllable-state";
|
|
9
|
+
import { ChevronDown } from "lucide-react";
|
|
10
|
+
import {
|
|
11
|
+
DropdownMenu,
|
|
12
|
+
DropdownMenuContent,
|
|
13
|
+
DropdownMenuRadioGroup,
|
|
14
|
+
DropdownMenuRadioItem,
|
|
15
|
+
DropdownMenuTrigger,
|
|
16
|
+
Kbd,
|
|
17
|
+
cn
|
|
18
|
+
} from "@elabs-ai/components-ui";
|
|
19
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
20
|
+
var PromptInputMode = forwardRef(
|
|
21
|
+
function PromptInputMode2({ modes, value, onValueChange, className, "aria-label": ariaLabel, ...props }, ref) {
|
|
22
|
+
const [selected, setSelected] = useControllableState({
|
|
23
|
+
defaultProp: modes[0]?.id ?? "",
|
|
24
|
+
onChange: onValueChange,
|
|
25
|
+
prop: value
|
|
26
|
+
});
|
|
27
|
+
const handleChange = (next) => {
|
|
28
|
+
if (!next) return;
|
|
29
|
+
setSelected(next);
|
|
30
|
+
};
|
|
31
|
+
const current = modes.find((mode) => mode.id === selected) ?? modes[0];
|
|
32
|
+
const descriptionIdBase = useId();
|
|
33
|
+
return /* @__PURE__ */ jsx(
|
|
34
|
+
"div",
|
|
35
|
+
{
|
|
36
|
+
ref,
|
|
37
|
+
"data-slot": "prompt-input-mode",
|
|
38
|
+
className: cn("inline-flex", className),
|
|
39
|
+
...props,
|
|
40
|
+
children: /* @__PURE__ */ jsxs(DropdownMenu, { modal: false, children: [
|
|
41
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
42
|
+
PromptInputButton,
|
|
43
|
+
{
|
|
44
|
+
variant: "ghost",
|
|
45
|
+
className: "gap-1.5 rounded-full",
|
|
46
|
+
disabled: modes.length === 0,
|
|
47
|
+
"aria-label": ariaLabel && current ? `${ariaLabel}: ${current.label}` : ariaLabel ?? void 0,
|
|
48
|
+
"data-slot": "prompt-input-mode-trigger",
|
|
49
|
+
children: [
|
|
50
|
+
current?.icon,
|
|
51
|
+
/* @__PURE__ */ jsx("span", { children: current?.label }),
|
|
52
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: "size-3.5 opacity-60", "aria-hidden": "true" })
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
) }),
|
|
56
|
+
/* @__PURE__ */ jsx(DropdownMenuContent, { align: "start", "data-slot": "prompt-input-mode-content", className: "w-72", children: /* @__PURE__ */ jsx(DropdownMenuRadioGroup, { value: selected, onValueChange: handleChange, children: modes.map((mode, index) => {
|
|
57
|
+
const descriptionId = mode.description === void 0 ? void 0 : `${descriptionIdBase}-description-${index}`;
|
|
58
|
+
return /* @__PURE__ */ jsxs(
|
|
59
|
+
DropdownMenuRadioItem,
|
|
60
|
+
{
|
|
61
|
+
value: mode.id,
|
|
62
|
+
"data-slot": "prompt-input-mode-item",
|
|
63
|
+
className: "gap-2",
|
|
64
|
+
"aria-describedby": descriptionId,
|
|
65
|
+
children: [
|
|
66
|
+
mode.icon,
|
|
67
|
+
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
68
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-body", children: mode.label }),
|
|
69
|
+
mode.description === void 0 ? null : (
|
|
70
|
+
// Visible, but excluded from the item's accessible
|
|
71
|
+
// name (aria-hidden) so the name stays exactly the
|
|
72
|
+
// label; still exposed as the accessible DESCRIPTION
|
|
73
|
+
// via aria-describedby above — a hidden node an
|
|
74
|
+
// aria-describedby reference points to is still read
|
|
75
|
+
// for the description computation.
|
|
76
|
+
/* @__PURE__ */ jsx(
|
|
77
|
+
"span",
|
|
78
|
+
{
|
|
79
|
+
id: descriptionId,
|
|
80
|
+
"aria-hidden": "true",
|
|
81
|
+
className: "truncate text-meta text-muted-foreground",
|
|
82
|
+
children: mode.description
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
] }),
|
|
87
|
+
mode.keyHint === void 0 ? null : /* @__PURE__ */ jsx(Kbd, { className: "shrink-0", "aria-hidden": "true", children: mode.keyHint })
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
mode.id
|
|
91
|
+
);
|
|
92
|
+
}) }) })
|
|
93
|
+
] })
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
export {
|
|
100
|
+
PromptInputMode
|
|
101
|
+
};
|
|
102
|
+
//# sourceMappingURL=chunk-H42LRUQ4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/prompt-input-mode.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef, useId, type HTMLAttributes } from \"react\";\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport { ChevronDown } from \"lucide-react\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuTrigger,\n Kbd,\n cn,\n type OperatingMode,\n} from \"@elabs-ai/components-ui\";\n\nimport { PromptInputButton } from \"./prompt-input\";\n\n// `OperatingMode` moved to `@elabs-ai/components-ui` (`lib/operating-mode.ts`)\n// — the terminal CLI look-alike family's own composer chips (issue #117) reuse\n// the same vocabulary shape, and `@elabs-ai/components-ai`/\n// `@elabs-ai/components-terminal` are layer-2 DAG siblings that may not import\n// each other (T0; see\n// docs/decisions/2026-09-01-brainless-adoption-architecture.md § 4). Imported\n// above; NOT re-exported from here — `@elabs-ai/components-ai` never\n// re-exports from `@elabs-ai/components-ui`. A consumer imports\n// `OperatingMode` from `@elabs-ai/components-ui`.\n\nexport type PromptInputModeProps = Omit<HTMLAttributes<HTMLDivElement>, \"onChange\"> & {\n /** The app's own operating modes, low to high ceremony or however it orders them. */\n modes: OperatingMode[];\n /** Controlled selected mode id. Omit for the uncontrolled default (`modes[0]`). */\n value?: string;\n /** Fires with the chosen mode's id. */\n onValueChange?: (id: string) => void;\n /**\n * Accessible name for the trigger control, read alongside the current\n * mode's label. Optional — the trigger already carries the current mode's\n * visible text as its name, so this only adds context (e.g. \"Operating\n * mode: Auto\" instead of just \"Auto\").\n */\n \"aria-label\"?: string;\n};\n\n/**\n * PromptInputMode — a composer control for an app-defined operating mode\n * (how autonomously the agent may act on the next turn).\n *\n * A `DropdownMenu` of `modes`, radio-selected so exactly one is active at a\n * time; the trigger shows the current mode's icon + label. Ships no mode\n * vocabulary — `modes` is entirely prop-driven, so `auto`/`plan`/whatever a\n * given agent calls its modes lives in the consuming app, not here.\n *\n * Built on Radix (`DropdownMenu` + `DropdownMenuRadioGroup`/`RadioItem` from\n * `@elabs-ai/components-ui`) for keyboard navigation, roving focus and a\n * `menuitemradio` role whose `aria-checked` announces the current selection —\n * no hand-rolled key handling.\n */\nexport const PromptInputMode = forwardRef<HTMLDivElement, PromptInputModeProps>(\n function PromptInputMode(\n { modes, value, onValueChange, className, \"aria-label\": ariaLabel, ...props },\n ref,\n ) {\n const [selected, setSelected] = useControllableState<string>({\n defaultProp: modes[0]?.id ?? \"\",\n onChange: onValueChange,\n prop: value,\n });\n\n const handleChange = (next: string) => {\n // Radix would otherwise allow an empty-string commit if a caller wires\n // a deselectable radio group elsewhere — a mode picker always has an\n // active mode, so an empty commit is ignored.\n if (!next) return;\n setSelected(next);\n };\n\n const current = modes.find((mode) => mode.id === selected) ?? modes[0];\n\n // `OperatingMode.id` is an unconstrained, consumer-owned string — it may\n // contain whitespace or other characters that are invalid inside a\n // single `aria-describedby` token (a space-separated ID list splits on\n // whitespace). Derive a per-instance, DOM-safe id from `useId()` and the\n // mode's index instead of embedding the raw value.\n const descriptionIdBase = useId();\n\n return (\n <div\n ref={ref}\n data-slot=\"prompt-input-mode\"\n className={cn(\"inline-flex\", className)}\n {...props}\n >\n {/*\n * `modal={false}` is load-bearing, not a preference. Radix's modal\n * dropdown marks the rest of the document `aria-hidden` while the menu\n * is open — including this component's own trigger — which axe flags as\n * `aria-hidden-focus` (\"ARIA hidden element must not be focusable\")\n * and which drops the trigger out of the accessibility tree entirely.\n * A composer toolbar chip has no business hiding the page or locking\n * its scroll, so the non-modal layer is both the accessible answer and\n * the semantically correct one.\n */}\n <DropdownMenu modal={false}>\n <DropdownMenuTrigger asChild>\n <PromptInputButton\n variant=\"ghost\"\n className=\"gap-1.5 rounded-full\"\n disabled={modes.length === 0}\n aria-label={\n ariaLabel && current ? `${ariaLabel}: ${current.label}` : (ariaLabel ?? undefined)\n }\n data-slot=\"prompt-input-mode-trigger\"\n >\n {current?.icon}\n <span>{current?.label}</span>\n <ChevronDown className=\"size-3.5 opacity-60\" aria-hidden=\"true\" />\n </PromptInputButton>\n </DropdownMenuTrigger>\n\n <DropdownMenuContent align=\"start\" data-slot=\"prompt-input-mode-content\" className=\"w-72\">\n <DropdownMenuRadioGroup value={selected} onValueChange={handleChange}>\n {modes.map((mode, index) => {\n const descriptionId =\n mode.description === undefined\n ? undefined\n : `${descriptionIdBase}-description-${index}`;\n return (\n <DropdownMenuRadioItem\n key={mode.id}\n value={mode.id}\n data-slot=\"prompt-input-mode-item\"\n className=\"gap-2\"\n aria-describedby={descriptionId}\n >\n {mode.icon}\n <span className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"truncate text-body\">{mode.label}</span>\n {mode.description === undefined ? null : (\n // Visible, but excluded from the item's accessible\n // name (aria-hidden) so the name stays exactly the\n // label; still exposed as the accessible DESCRIPTION\n // via aria-describedby above — a hidden node an\n // aria-describedby reference points to is still read\n // for the description computation.\n <span\n id={descriptionId}\n aria-hidden=\"true\"\n className=\"truncate text-meta text-muted-foreground\"\n >\n {mode.description}\n </span>\n )}\n </span>\n {mode.keyHint === undefined ? null : (\n <Kbd className=\"shrink-0\" aria-hidden=\"true\">\n {mode.keyHint}\n </Kbd>\n )}\n </DropdownMenuRadioItem>\n );\n })}\n </DropdownMenuRadioGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n );\n },\n);\n"],"mappings":";;;;;;AAEA,SAAS,YAAY,aAAkC;AACvD,SAAS,4BAA4B;AACrC,SAAS,mBAAmB;AAC5B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AA2FK,SAUE,KAVF;AA/CL,IAAM,kBAAkB;AAAA,EAC7B,SAASA,iBACP,EAAE,OAAO,OAAO,eAAe,WAAW,cAAc,WAAW,GAAG,MAAM,GAC5E,KACA;AACA,UAAM,CAAC,UAAU,WAAW,IAAI,qBAA6B;AAAA,MAC3D,aAAa,MAAM,CAAC,GAAG,MAAM;AAAA,MAC7B,UAAU;AAAA,MACV,MAAM;AAAA,IACR,CAAC;AAED,UAAM,eAAe,CAAC,SAAiB;AAIrC,UAAI,CAAC,KAAM;AACX,kBAAY,IAAI;AAAA,IAClB;AAEA,UAAM,UAAU,MAAM,KAAK,CAAC,SAAS,KAAK,OAAO,QAAQ,KAAK,MAAM,CAAC;AAOrE,UAAM,oBAAoB,MAAM;AAEhC,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,aAAU;AAAA,QACV,WAAW,GAAG,eAAe,SAAS;AAAA,QACrC,GAAG;AAAA,QAYJ,+BAAC,gBAAa,OAAO,OACnB;AAAA,8BAAC,uBAAoB,SAAO,MAC1B;AAAA,YAAC;AAAA;AAAA,cACC,SAAQ;AAAA,cACR,WAAU;AAAA,cACV,UAAU,MAAM,WAAW;AAAA,cAC3B,cACE,aAAa,UAAU,GAAG,SAAS,KAAK,QAAQ,KAAK,KAAM,aAAa;AAAA,cAE1E,aAAU;AAAA,cAET;AAAA,yBAAS;AAAA,gBACV,oBAAC,UAAM,mBAAS,OAAM;AAAA,gBACtB,oBAAC,eAAY,WAAU,uBAAsB,eAAY,QAAO;AAAA;AAAA;AAAA,UAClE,GACF;AAAA,UAEA,oBAAC,uBAAoB,OAAM,SAAQ,aAAU,6BAA4B,WAAU,QACjF,8BAAC,0BAAuB,OAAO,UAAU,eAAe,cACrD,gBAAM,IAAI,CAAC,MAAM,UAAU;AAC1B,kBAAM,gBACJ,KAAK,gBAAgB,SACjB,SACA,GAAG,iBAAiB,gBAAgB,KAAK;AAC/C,mBACE;AAAA,cAAC;AAAA;AAAA,gBAEC,OAAO,KAAK;AAAA,gBACZ,aAAU;AAAA,gBACV,WAAU;AAAA,gBACV,oBAAkB;AAAA,gBAEjB;AAAA,uBAAK;AAAA,kBACN,qBAAC,UAAK,WAAU,gCACd;AAAA,wCAAC,UAAK,WAAU,sBAAsB,eAAK,OAAM;AAAA,oBAChD,KAAK,gBAAgB,SAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAOhC;AAAA,wBAAC;AAAA;AAAA,0BACC,IAAI;AAAA,0BACJ,eAAY;AAAA,0BACZ,WAAU;AAAA,0BAET,eAAK;AAAA;AAAA,sBACR;AAAA;AAAA,qBAEJ;AAAA,kBACC,KAAK,YAAY,SAAY,OAC5B,oBAAC,OAAI,WAAU,YAAW,eAAY,QACnC,eAAK,SACR;AAAA;AAAA;AAAA,cA5BG,KAAK;AAAA,YA8BZ;AAAA,UAEJ,CAAC,GACH,GACF;AAAA,WACF;AAAA;AAAA,IACF;AAAA,EAEJ;AACF;","names":["PromptInputMode"]}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/message-form-spec.ts
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
var enumOptionSchema = z.union([
|
|
6
|
+
z.string(),
|
|
7
|
+
z.object({ const: z.string(), title: z.string().optional() })
|
|
8
|
+
]);
|
|
9
|
+
function optionValue(option) {
|
|
10
|
+
return typeof option === "string" ? option : option.const;
|
|
11
|
+
}
|
|
12
|
+
function optionLabel(option) {
|
|
13
|
+
return typeof option === "string" ? option : option.title ?? option.const;
|
|
14
|
+
}
|
|
15
|
+
var baseField = {
|
|
16
|
+
/** Value key in the emitted form state (the `values` object key). */
|
|
17
|
+
name: z.string(),
|
|
18
|
+
/** Human label. Falls back to a humanized `name` when omitted. */
|
|
19
|
+
label: z.string().optional(),
|
|
20
|
+
/** Helper text shown under the control. */
|
|
21
|
+
description: z.string().optional(),
|
|
22
|
+
/** Whether a value is required before the form can submit. */
|
|
23
|
+
required: z.boolean().optional()
|
|
24
|
+
};
|
|
25
|
+
var stringFieldSchema = z.object({
|
|
26
|
+
type: z.literal("string"),
|
|
27
|
+
...baseField,
|
|
28
|
+
default: z.string().optional(),
|
|
29
|
+
format: z.enum(["email", "uri", "date", "date-time"]).optional(),
|
|
30
|
+
minLength: z.number().int().nonnegative().optional(),
|
|
31
|
+
maxLength: z.number().int().nonnegative().optional(),
|
|
32
|
+
/** A serialized RegExp source string (client-enforced when present). */
|
|
33
|
+
pattern: z.string().optional(),
|
|
34
|
+
/** Render a textarea instead of a single-line input. */
|
|
35
|
+
multiline: z.boolean().optional()
|
|
36
|
+
});
|
|
37
|
+
var numberFieldSchema = z.object({
|
|
38
|
+
type: z.literal("number"),
|
|
39
|
+
...baseField,
|
|
40
|
+
default: z.number().optional(),
|
|
41
|
+
min: z.number().optional(),
|
|
42
|
+
max: z.number().optional()
|
|
43
|
+
});
|
|
44
|
+
var integerFieldSchema = z.object({
|
|
45
|
+
type: z.literal("integer"),
|
|
46
|
+
...baseField,
|
|
47
|
+
default: z.number().int().optional(),
|
|
48
|
+
min: z.number().optional(),
|
|
49
|
+
max: z.number().optional()
|
|
50
|
+
});
|
|
51
|
+
var booleanFieldSchema = z.object({
|
|
52
|
+
type: z.literal("boolean"),
|
|
53
|
+
...baseField,
|
|
54
|
+
default: z.boolean().optional()
|
|
55
|
+
});
|
|
56
|
+
var enumFieldSchema = z.object({
|
|
57
|
+
type: z.literal("enum"),
|
|
58
|
+
...baseField,
|
|
59
|
+
options: z.array(enumOptionSchema),
|
|
60
|
+
default: z.string().optional()
|
|
61
|
+
});
|
|
62
|
+
var multiEnumFieldSchema = z.object({
|
|
63
|
+
type: z.literal("multi-enum"),
|
|
64
|
+
...baseField,
|
|
65
|
+
options: z.array(enumOptionSchema),
|
|
66
|
+
default: z.array(z.string()).optional()
|
|
67
|
+
});
|
|
68
|
+
var fieldSpecSchema = z.discriminatedUnion("type", [
|
|
69
|
+
stringFieldSchema,
|
|
70
|
+
numberFieldSchema,
|
|
71
|
+
integerFieldSchema,
|
|
72
|
+
booleanFieldSchema,
|
|
73
|
+
enumFieldSchema,
|
|
74
|
+
multiEnumFieldSchema
|
|
75
|
+
]);
|
|
76
|
+
var formSpecSchema = z.object({
|
|
77
|
+
/** Stable identifier echoed back in the submit payload. */
|
|
78
|
+
formName: z.string(),
|
|
79
|
+
/** Heading rendered above the fields (also the accessible form name). */
|
|
80
|
+
title: z.string().optional(),
|
|
81
|
+
/** Supplemental description under the title. */
|
|
82
|
+
description: z.string().optional(),
|
|
83
|
+
/** The ordered fields to render. */
|
|
84
|
+
fields: z.array(fieldSpecSchema),
|
|
85
|
+
/** Submit button label. @default "Submit" */
|
|
86
|
+
submitLabel: z.string().optional()
|
|
87
|
+
});
|
|
88
|
+
function normalizeFormSpec(spec) {
|
|
89
|
+
if (spec === null || typeof spec !== "object" || Array.isArray(spec)) {
|
|
90
|
+
return { ok: false, reason: "Form specification is missing or malformed." };
|
|
91
|
+
}
|
|
92
|
+
const raw = spec;
|
|
93
|
+
const rawFields = Array.isArray(raw.fields) ? raw.fields : [];
|
|
94
|
+
const fields = [];
|
|
95
|
+
const seen = /* @__PURE__ */ new Set();
|
|
96
|
+
for (const candidate of rawFields) {
|
|
97
|
+
const parsed = fieldSpecSchema.safeParse(candidate);
|
|
98
|
+
if (!parsed.success) continue;
|
|
99
|
+
if (seen.has(parsed.data.name)) continue;
|
|
100
|
+
seen.add(parsed.data.name);
|
|
101
|
+
fields.push(parsed.data);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
ok: true,
|
|
105
|
+
spec: {
|
|
106
|
+
formName: typeof raw.formName === "string" ? raw.formName : "",
|
|
107
|
+
title: typeof raw.title === "string" ? raw.title : void 0,
|
|
108
|
+
description: typeof raw.description === "string" ? raw.description : void 0,
|
|
109
|
+
submitLabel: typeof raw.submitLabel === "string" ? raw.submitLabel : void 0,
|
|
110
|
+
fields
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function humanize(name) {
|
|
115
|
+
const spaced = name.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim();
|
|
116
|
+
if (!spaced) return name;
|
|
117
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
118
|
+
}
|
|
119
|
+
function fieldLabel(field) {
|
|
120
|
+
return field.label && field.label.length > 0 ? field.label : humanize(field.name);
|
|
121
|
+
}
|
|
122
|
+
function initialFormValues(fields) {
|
|
123
|
+
const values = {};
|
|
124
|
+
for (const field of fields) {
|
|
125
|
+
if (field.default !== void 0) {
|
|
126
|
+
values[field.name] = field.default;
|
|
127
|
+
} else if (field.type === "boolean") {
|
|
128
|
+
values[field.name] = false;
|
|
129
|
+
} else if (field.type === "multi-enum") {
|
|
130
|
+
values[field.name] = [];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return values;
|
|
134
|
+
}
|
|
135
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
136
|
+
function isEmptyValue(value) {
|
|
137
|
+
return value === void 0 || value === "" || Array.isArray(value) && value.length === 0;
|
|
138
|
+
}
|
|
139
|
+
function isValidUrl(value) {
|
|
140
|
+
try {
|
|
141
|
+
void new URL(value);
|
|
142
|
+
return true;
|
|
143
|
+
} catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function validateField(field, value) {
|
|
148
|
+
const empty = isEmptyValue(value);
|
|
149
|
+
if (field.required) {
|
|
150
|
+
if (field.type === "boolean") {
|
|
151
|
+
if (value !== true) return "This field is required.";
|
|
152
|
+
} else if (empty) {
|
|
153
|
+
return "This field is required.";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (empty) return null;
|
|
157
|
+
switch (field.type) {
|
|
158
|
+
case "string": {
|
|
159
|
+
const text = String(value);
|
|
160
|
+
if (field.minLength !== void 0 && text.length < field.minLength) {
|
|
161
|
+
return `Must be at least ${field.minLength} character${field.minLength === 1 ? "" : "s"}.`;
|
|
162
|
+
}
|
|
163
|
+
if (field.maxLength !== void 0 && text.length > field.maxLength) {
|
|
164
|
+
return `Must be at most ${field.maxLength} character${field.maxLength === 1 ? "" : "s"}.`;
|
|
165
|
+
}
|
|
166
|
+
if (field.format === "email" && !EMAIL_RE.test(text)) {
|
|
167
|
+
return "Enter a valid email address.";
|
|
168
|
+
}
|
|
169
|
+
if (field.format === "uri" && !isValidUrl(text)) {
|
|
170
|
+
return "Enter a valid URL.";
|
|
171
|
+
}
|
|
172
|
+
if (field.pattern) {
|
|
173
|
+
let re = null;
|
|
174
|
+
try {
|
|
175
|
+
re = new RegExp(field.pattern);
|
|
176
|
+
} catch {
|
|
177
|
+
re = null;
|
|
178
|
+
}
|
|
179
|
+
if (re && !re.test(text)) return "Value doesn't match the required format.";
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
case "number":
|
|
184
|
+
case "integer": {
|
|
185
|
+
const num = typeof value === "number" ? value : Number(value);
|
|
186
|
+
if (Number.isNaN(num)) return "Enter a number.";
|
|
187
|
+
if (field.type === "integer" && !Number.isInteger(num)) return "Enter a whole number.";
|
|
188
|
+
if (field.min !== void 0 && num < field.min) return `Must be at least ${field.min}.`;
|
|
189
|
+
if (field.max !== void 0 && num > field.max) return `Must be at most ${field.max}.`;
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
default:
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function validateForm(fields, values) {
|
|
197
|
+
const errors = {};
|
|
198
|
+
for (const field of fields) {
|
|
199
|
+
errors[field.name] = validateField(field, values[field.name]);
|
|
200
|
+
}
|
|
201
|
+
return errors;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export {
|
|
205
|
+
enumOptionSchema,
|
|
206
|
+
optionValue,
|
|
207
|
+
optionLabel,
|
|
208
|
+
stringFieldSchema,
|
|
209
|
+
numberFieldSchema,
|
|
210
|
+
integerFieldSchema,
|
|
211
|
+
booleanFieldSchema,
|
|
212
|
+
enumFieldSchema,
|
|
213
|
+
multiEnumFieldSchema,
|
|
214
|
+
fieldSpecSchema,
|
|
215
|
+
formSpecSchema,
|
|
216
|
+
normalizeFormSpec,
|
|
217
|
+
fieldLabel,
|
|
218
|
+
initialFormValues,
|
|
219
|
+
validateField,
|
|
220
|
+
validateForm
|
|
221
|
+
};
|
|
222
|
+
//# sourceMappingURL=chunk-HVXM2G5F.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/message-form-spec.ts"],"sourcesContent":["/**\n * message-form-spec.ts — Serializable FormSpec for MessageForm.\n *\n * These are pure data types + zod schemas with no React dependency. The shape is\n * what an LLM tool-call emits when it wants the user to fill in a form inside a\n * chat message. It is deliberately compatible with MCP elicitation's\n * `requestedSchema` (spec 2025-11-25): flat primitive fields (string / number /\n * integer / boolean / single- and multi-select enum), each with a label,\n * optional description, `required`, and a `default`.\n *\n * The exported zod schemas are the source of truth downstream catalogs compile\n * prompts + validators from. Field types are intentionally limited to the safe\n * set — there is NO file input and NO password/credential type/format, so a spec\n * that asks for one fails validation (rejected, never rendered).\n */\n\nimport { z } from \"zod\";\n\n// ---------------------------------------------------------------------------\n// Enum options\n// ---------------------------------------------------------------------------\n\n/** A single enum choice: a plain value, or a `{ const, title }` pair. */\nexport const enumOptionSchema = z.union([\n z.string(),\n z.object({ const: z.string(), title: z.string().optional() }),\n]);\nexport type EnumOption = z.infer<typeof enumOptionSchema>;\n\n/** The submitted value of an enum option. */\nexport function optionValue(option: EnumOption): string {\n return typeof option === \"string\" ? option : option.const;\n}\n\n/** The display label of an enum option (falls back to its value). */\nexport function optionLabel(option: EnumOption): string {\n return typeof option === \"string\" ? option : (option.title ?? option.const);\n}\n\n// ---------------------------------------------------------------------------\n// Fields (a discriminated union on `type`)\n// ---------------------------------------------------------------------------\n\n/** Fields shared by every field type. `name` is the key in the emitted values. */\nconst baseField = {\n /** Value key in the emitted form state (the `values` object key). */\n name: z.string(),\n /** Human label. Falls back to a humanized `name` when omitted. */\n label: z.string().optional(),\n /** Helper text shown under the control. */\n description: z.string().optional(),\n /** Whether a value is required before the form can submit. */\n required: z.boolean().optional(),\n};\n\n/**\n * A single-line or multi-line text field.\n * `format` narrows the input type + validation. Note there is NO `\"password\"`\n * format — credential capture is deliberately unsupported.\n */\nexport const stringFieldSchema = z.object({\n type: z.literal(\"string\"),\n ...baseField,\n default: z.string().optional(),\n format: z.enum([\"email\", \"uri\", \"date\", \"date-time\"]).optional(),\n minLength: z.number().int().nonnegative().optional(),\n maxLength: z.number().int().nonnegative().optional(),\n /** A serialized RegExp source string (client-enforced when present). */\n pattern: z.string().optional(),\n /** Render a textarea instead of a single-line input. */\n multiline: z.boolean().optional(),\n});\nexport type StringFieldSpec = z.infer<typeof stringFieldSchema>;\n\n/** A floating-point number field. */\nexport const numberFieldSchema = z.object({\n type: z.literal(\"number\"),\n ...baseField,\n default: z.number().optional(),\n min: z.number().optional(),\n max: z.number().optional(),\n});\nexport type NumberFieldSpec = z.infer<typeof numberFieldSchema>;\n\n/** A whole-number field. */\nexport const integerFieldSchema = z.object({\n type: z.literal(\"integer\"),\n ...baseField,\n default: z.number().int().optional(),\n min: z.number().optional(),\n max: z.number().optional(),\n});\nexport type IntegerFieldSpec = z.infer<typeof integerFieldSchema>;\n\n/** A boolean checkbox field. */\nexport const booleanFieldSchema = z.object({\n type: z.literal(\"boolean\"),\n ...baseField,\n default: z.boolean().optional(),\n});\nexport type BooleanFieldSpec = z.infer<typeof booleanFieldSchema>;\n\n/** A single-select enum (one value from `options`). */\nexport const enumFieldSchema = z.object({\n type: z.literal(\"enum\"),\n ...baseField,\n options: z.array(enumOptionSchema),\n default: z.string().optional(),\n});\nexport type EnumFieldSpec = z.infer<typeof enumFieldSchema>;\n\n/** A multi-select enum (zero or more values from `options`). */\nexport const multiEnumFieldSchema = z.object({\n type: z.literal(\"multi-enum\"),\n ...baseField,\n options: z.array(enumOptionSchema),\n default: z.array(z.string()).optional(),\n});\nexport type MultiEnumFieldSpec = z.infer<typeof multiEnumFieldSchema>;\n\n/** Any one field in a FormSpec. */\nexport const fieldSpecSchema = z.discriminatedUnion(\"type\", [\n stringFieldSchema,\n numberFieldSchema,\n integerFieldSchema,\n booleanFieldSchema,\n enumFieldSchema,\n multiEnumFieldSchema,\n]);\nexport type FieldSpec = z.infer<typeof fieldSpecSchema>;\n\n// ---------------------------------------------------------------------------\n// FormSpec\n// ---------------------------------------------------------------------------\n\n/**\n * The serializable form specification produced by an LLM tool-call.\n * MessageForm reads this, renders the fields, and emits `{ formName, values }`.\n *\n * Export target for downstream catalogs: compile the prompt + a validator from\n * `formSpecSchema`.\n */\nexport const formSpecSchema = z.object({\n /** Stable identifier echoed back in the submit payload. */\n formName: z.string(),\n /** Heading rendered above the fields (also the accessible form name). */\n title: z.string().optional(),\n /** Supplemental description under the title. */\n description: z.string().optional(),\n /** The ordered fields to render. */\n fields: z.array(fieldSpecSchema),\n /** Submit button label. @default \"Submit\" */\n submitLabel: z.string().optional(),\n});\nexport type FormSpec = z.infer<typeof formSpecSchema>;\n\n// ---------------------------------------------------------------------------\n// Values\n// ---------------------------------------------------------------------------\n\n/** A single field's value in the form state. */\nexport type FormValue = string | number | boolean | string[] | undefined;\n\n/** The `values` object keyed by field `name`. */\nexport type FormValues = Record<string, FormValue>;\n\n/** The payload passed to `onSubmit`. */\nexport interface FormSubmitState {\n /** Echoes `spec.formName`. */\n formName: string;\n /** The current field values keyed by field name. */\n values: FormValues;\n}\n\n// ---------------------------------------------------------------------------\n// Normalization (lenient — streaming-tolerant, never throws)\n// ---------------------------------------------------------------------------\n\n/** A validated, render-ready FormSpec. */\nexport interface NormalizedFormSpec {\n formName: string;\n title?: string;\n description?: string;\n submitLabel?: string;\n fields: FieldSpec[];\n}\n\n/**\n * Lenient parse for the render path. Mirrors AutoChart's guard philosophy:\n * NEVER throws. Individually invalid or half-streamed fields are dropped (not\n * fatal), so a partial spec still renders progressively. Returns `{ ok: false }`\n * only when the whole spec is unusable (not an object) — the caller then shows\n * `MessageFormFallback`.\n */\nexport function normalizeFormSpec(\n spec: unknown,\n): { ok: true; spec: NormalizedFormSpec } | { ok: false; reason: string } {\n if (spec === null || typeof spec !== \"object\" || Array.isArray(spec)) {\n return { ok: false, reason: \"Form specification is missing or malformed.\" };\n }\n const raw = spec as Record<string, unknown>;\n const rawFields = Array.isArray(raw.fields) ? raw.fields : [];\n\n const fields: FieldSpec[] = [];\n const seen = new Set<string>();\n for (const candidate of rawFields) {\n const parsed = fieldSpecSchema.safeParse(candidate);\n if (!parsed.success) continue; // drop invalid / still-streaming field\n if (seen.has(parsed.data.name)) continue; // de-dupe by name (last-writer avoided)\n seen.add(parsed.data.name);\n fields.push(parsed.data);\n }\n\n return {\n ok: true,\n spec: {\n formName: typeof raw.formName === \"string\" ? raw.formName : \"\",\n title: typeof raw.title === \"string\" ? raw.title : undefined,\n description: typeof raw.description === \"string\" ? raw.description : undefined,\n submitLabel: typeof raw.submitLabel === \"string\" ? raw.submitLabel : undefined,\n fields,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Labels + initial values\n// ---------------------------------------------------------------------------\n\n/** Turn a field name into a human label (\"first_name\" / \"firstName\" → \"First name\"). */\nfunction humanize(name: string): string {\n const spaced = name\n .replace(/[_-]+/g, \" \")\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .trim();\n if (!spaced) return name;\n return spaced.charAt(0).toUpperCase() + spaced.slice(1);\n}\n\n/** The label to show for a field (explicit `label`, else a humanized `name`). */\nexport function fieldLabel(field: FieldSpec): string {\n return field.label && field.label.length > 0 ? field.label : humanize(field.name);\n}\n\n/** Seed the initial values from each field's `default` (+ sensible empties). */\nexport function initialFormValues(fields: FieldSpec[]): FormValues {\n const values: FormValues = {};\n for (const field of fields) {\n if (field.default !== undefined) {\n values[field.name] = field.default;\n } else if (field.type === \"boolean\") {\n values[field.name] = false;\n } else if (field.type === \"multi-enum\") {\n values[field.name] = [];\n }\n }\n return values;\n}\n\n// ---------------------------------------------------------------------------\n// Client-side validation (the declarative vocabulary)\n// ---------------------------------------------------------------------------\n\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\nfunction isEmptyValue(value: FormValue): boolean {\n return value === undefined || value === \"\" || (Array.isArray(value) && value.length === 0);\n}\n\nfunction isValidUrl(value: string): boolean {\n try {\n void new URL(value);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Validate one field's value against its declarative constraints.\n * Returns a short human error message, or `null` when the value is valid.\n * The vocabulary: required · email · url · min · max · minLength · maxLength ·\n * pattern · numeric (integer whole-number).\n */\nexport function validateField(field: FieldSpec, value: FormValue): string | null {\n const empty = isEmptyValue(value);\n\n if (field.required) {\n // A required boolean must be affirmatively true (e.g. \"accept terms\");\n // `false` is otherwise a valid, non-empty value.\n if (field.type === \"boolean\") {\n if (value !== true) return \"This field is required.\";\n } else if (empty) {\n return \"This field is required.\";\n }\n }\n if (empty) return null; // optional + empty → valid, skip further checks\n\n switch (field.type) {\n case \"string\": {\n const text = String(value);\n if (field.minLength !== undefined && text.length < field.minLength) {\n return `Must be at least ${field.minLength} character${field.minLength === 1 ? \"\" : \"s\"}.`;\n }\n if (field.maxLength !== undefined && text.length > field.maxLength) {\n return `Must be at most ${field.maxLength} character${field.maxLength === 1 ? \"\" : \"s\"}.`;\n }\n if (field.format === \"email\" && !EMAIL_RE.test(text)) {\n return \"Enter a valid email address.\";\n }\n if (field.format === \"uri\" && !isValidUrl(text)) {\n return \"Enter a valid URL.\";\n }\n if (field.pattern) {\n let re: RegExp | null = null;\n try {\n re = new RegExp(field.pattern);\n } catch {\n re = null; // an invalid pattern from the model must not break validation\n }\n if (re && !re.test(text)) return \"Value doesn't match the required format.\";\n }\n return null;\n }\n case \"number\":\n case \"integer\": {\n const num = typeof value === \"number\" ? value : Number(value);\n if (Number.isNaN(num)) return \"Enter a number.\";\n if (field.type === \"integer\" && !Number.isInteger(num)) return \"Enter a whole number.\";\n if (field.min !== undefined && num < field.min) return `Must be at least ${field.min}.`;\n if (field.max !== undefined && num > field.max) return `Must be at most ${field.max}.`;\n return null;\n }\n default:\n return null;\n }\n}\n\n/** Validate every field; returns a `{ [name]: error | null }` map. */\nexport function validateForm(\n fields: FieldSpec[],\n values: FormValues,\n): Record<string, string | null> {\n const errors: Record<string, string | null> = {};\n for (const field of fields) {\n errors[field.name] = validateField(field, values[field.name]);\n }\n return errors;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
AgentStep,
|
|
4
|
+
AgentTimeline
|
|
5
|
+
} from "./chunk-UCJ6IUFS.js";
|
|
6
|
+
|
|
7
|
+
// src/task.tsx
|
|
8
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@elabs-ai/components-ui";
|
|
9
|
+
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
10
|
+
import { ChevronDownIcon } from "lucide-react";
|
|
11
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
+
var TaskItemFile = ({ children, className, ...props }) => /* @__PURE__ */ jsx(
|
|
13
|
+
"span",
|
|
14
|
+
{
|
|
15
|
+
className: cn(
|
|
16
|
+
"inline-flex items-center gap-1 rounded-md bg-secondary px-1.5 py-0.5 text-meta text-secondary-foreground",
|
|
17
|
+
className
|
|
18
|
+
),
|
|
19
|
+
...props,
|
|
20
|
+
children
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
var TaskItem = ({
|
|
24
|
+
children,
|
|
25
|
+
status = "complete",
|
|
26
|
+
hideBadge = true,
|
|
27
|
+
...props
|
|
28
|
+
}) => /* @__PURE__ */ jsx(AgentStep, { hideBadge, name: children, status, ...props });
|
|
29
|
+
var Task = ({ className, ...props }) => /* @__PURE__ */ jsx(Collapsible, { className: cn(className), ...props });
|
|
30
|
+
var TaskTrigger = ({ children, className, title, ...props }) => /* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, className: cn("group", className), ...props, children: children ?? /* @__PURE__ */ jsxs("div", { className: "flex w-full cursor-pointer items-center gap-2 text-body text-muted-foreground transition-colors hover:text-foreground", children: [
|
|
31
|
+
/* @__PURE__ */ jsx("p", { children: title }),
|
|
32
|
+
/* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-4 transition-transform group-data-[state=open]:rotate-180" })
|
|
33
|
+
] }) });
|
|
34
|
+
var TaskContent = ({ children, className, ...props }) => /* @__PURE__ */ jsx(
|
|
35
|
+
CollapsibleContent,
|
|
36
|
+
{
|
|
37
|
+
className: cn(
|
|
38
|
+
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in data-[state=open]:[--tw-ease:var(--ease-entrance)] data-[state=closed]:[--tw-ease:var(--ease-exit)]",
|
|
39
|
+
className
|
|
40
|
+
),
|
|
41
|
+
...props,
|
|
42
|
+
children: /* @__PURE__ */ jsx(AgentTimeline, { className: "mt-4", children })
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
export {
|
|
47
|
+
TaskItemFile,
|
|
48
|
+
TaskItem,
|
|
49
|
+
Task,
|
|
50
|
+
TaskTrigger,
|
|
51
|
+
TaskContent
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=chunk-I4RDAGVY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/task.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Task — a collapsed \"what got done\" run summary (#192, research 10 §B.6).\n *\n * The body rides the canonical execution-trace rail (`AgentTimeline` /\n * `AgentStep`, research 10 §B.3) instead of the pre-#192 hand-rolled\n * `border-s-2` content rail. Collapsed by default — in a settled transcript\n * only the focal surfaces (the produced artifact + the final answer) stay\n * open, not every trace.\n */\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { ChevronDownIcon } from \"lucide-react\";\nimport type { ComponentProps, ReactNode } from \"react\";\n\nimport { AgentStep, AgentTimeline, type AgentStepProps } from \"./agent-timeline\";\n\nexport type TaskItemFileProps = ComponentProps<\"span\">;\n\n/**\n * A quiet inline file chip. Unbordered since #192 (the fill is the gesture;\n * a border on a filled chip is the redundant-border anti-pattern). For a\n * navigable list of produced files, prefer the asset-tree idiom (research 09).\n */\nexport const TaskItemFile = ({ children, className, ...props }: TaskItemFileProps) => (\n <span\n className={cn(\n \"inline-flex items-center gap-1 rounded-md bg-secondary px-1.5 py-0.5 text-meta text-secondary-foreground\",\n className,\n )}\n {...props}\n >\n {children}\n </span>\n);\n\nexport type TaskItemProps = Omit<AgentStepProps, \"name\" | \"children\"> & {\n children?: ReactNode;\n};\n\n/**\n * One line of the run summary — an `AgentStep` on the canonical rail.\n * Defaults to `status=\"complete\"` (the task reports finished work) with the\n * badge hidden (the rail node carries the status; a badge per log line would\n * shout).\n */\nexport const TaskItem = ({\n children,\n status = \"complete\",\n hideBadge = true,\n ...props\n}: TaskItemProps) => <AgentStep hideBadge={hideBadge} name={children} status={status} {...props} />;\n\nexport type TaskProps = ComponentProps<typeof Collapsible>;\n\n/** Collapsed by default since #192 (`defaultOpen` flipped `true`→`false`). */\nexport const Task = ({ className, ...props }: TaskProps) => (\n <Collapsible className={cn(className)} {...props} />\n);\n\nexport type TaskTriggerProps = ComponentProps<typeof CollapsibleTrigger> & {\n title: string;\n};\n\nexport const TaskTrigger = ({ children, className, title, ...props }: TaskTriggerProps) => (\n <CollapsibleTrigger asChild className={cn(\"group\", className)} {...props}>\n {children ?? (\n <div className=\"flex w-full cursor-pointer items-center gap-2 text-body text-muted-foreground transition-colors hover:text-foreground\">\n <p>{title}</p>\n <ChevronDownIcon className=\"size-4 transition-transform group-data-[state=open]:rotate-180\" />\n </div>\n )}\n </CollapsibleTrigger>\n);\n\nexport type TaskContentProps = ComponentProps<typeof CollapsibleContent>;\n\nexport const TaskContent = ({ children, className, ...props }: TaskContentProps) => (\n <CollapsibleContent\n className={cn(\n \"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in data-[state=open]:[--tw-ease:var(--ease-entrance)] data-[state=closed]:[--tw-ease:var(--ease-exit)]\",\n className,\n )}\n {...props}\n >\n <AgentTimeline className=\"mt-4\">{children}</AgentTimeline>\n </CollapsibleContent>\n);\n"],"mappings":";;;;;;;AAWA,SAAS,aAAa,oBAAoB,0BAA0B;AACpE,SAAS,UAAU;AACnB,SAAS,uBAAuB;AAa9B,cA0CI,YA1CJ;AADK,IAAM,eAAe,CAAC,EAAE,UAAU,WAAW,GAAG,MAAM,MAC3D;AAAA,EAAC;AAAA;AAAA,IACC,WAAW;AAAA,MACT;AAAA,MACA;AAAA,IACF;AAAA,IACC,GAAG;AAAA,IAEH;AAAA;AACH;AAaK,IAAM,WAAW,CAAC;AAAA,EACvB;AAAA,EACA,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,GAAG;AACL,MAAqB,oBAAC,aAAU,WAAsB,MAAM,UAAU,QAAiB,GAAG,OAAO;AAK1F,IAAM,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,MACzC,oBAAC,eAAY,WAAW,GAAG,SAAS,GAAI,GAAG,OAAO;AAO7C,IAAM,cAAc,CAAC,EAAE,UAAU,WAAW,OAAO,GAAG,MAAM,MACjE,oBAAC,sBAAmB,SAAO,MAAC,WAAW,GAAG,SAAS,SAAS,GAAI,GAAG,OAChE,sBACC,qBAAC,SAAI,WAAU,yHACb;AAAA,sBAAC,OAAG,iBAAM;AAAA,EACV,oBAAC,mBAAgB,WAAU,kEAAiE;AAAA,GAC9F,GAEJ;AAKK,IAAM,cAAc,CAAC,EAAE,UAAU,WAAW,GAAG,MAAM,MAC1D;AAAA,EAAC;AAAA;AAAA,IACC,WAAW;AAAA,MACT;AAAA,MACA;AAAA,IACF;AAAA,IACC,GAAG;AAAA,IAEJ,8BAAC,iBAAc,WAAU,QAAQ,UAAS;AAAA;AAC5C;","names":[]}
|