@elabs-ai/components-ai 4.2.0 → 5.1.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 +1 -1
- 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-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-GA7M34KU.js +236 -0
- package/dist/chunk-GA7M34KU.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-L4SU3QJV.js +174 -0
- package/dist/chunk-L4SU3QJV.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-QOBSEGCG.js +996 -0
- package/dist/chunk-QOBSEGCG.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-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-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 +366 -98
- package/dist/index.js +672 -13577
- 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 +11 -9
- package/schemas/a2ui-surface.v1.schema.json +5434 -0
- package/src/_chat-shell-rail.tsx +3 -1
- package/src/_flow-boundary.tsx +2 -2
- package/src/_persona-rive.tsx +27 -41
- 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 +1019 -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/agentic-workspace.stories.tsx +6 -45
- package/src/chat-shell.test.tsx +40 -10
- package/src/chat-shell.tsx +90 -44
- package/src/chat.stories.tsx +47 -42
- package/src/composer.stories.tsx +4 -5
- package/src/composer.test.tsx +45 -0
- package/src/composer.tsx +104 -21
- package/src/context-panel.stories.tsx +1 -1
- package/src/context-panel.tsx +1 -1
- package/src/conversation.stories.tsx +1 -0
- package/src/conversation.test.tsx +16 -0
- package/src/conversation.tsx +16 -2
- package/src/diff-view.stories.tsx +79 -0
- package/src/diff-view.tsx +3 -2
- package/src/file-tree.tsx +24 -17
- 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/message-compare.stories.tsx +40 -0
- package/src/message-compare.tsx +2 -1
- package/src/message-form.tsx +1 -1
- package/src/message-table.stories.tsx +24 -0
- package/src/message-table.tsx +1 -1
- package/src/package-info.tsx +12 -9
- package/src/reasoning.stories.tsx +27 -0
- package/src/reasoning.tsx +1 -1
- package/src/session-header.stories.tsx +35 -1
- package/src/session-header.tsx +1 -1
- package/src/sources.stories.tsx +30 -0
- package/src/sources.tsx +4 -1
- package/src/templates-ai-assistant.stories.tsx +151 -87
- package/src/tool.stories.tsx +39 -0
- package/src/tool.tsx +3 -3
- package/src/toolbar.tsx +0 -10
- 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
package/src/chat-shell.tsx
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import type { ReactNode } from "react";
|
|
4
|
+
import { useLayoutEffect, useRef } from "react";
|
|
4
5
|
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
5
6
|
|
|
6
7
|
export interface ChatShellProps {
|
|
7
8
|
/** Scrollable transcript — typically a <Conversation>. */
|
|
8
9
|
children: ReactNode;
|
|
9
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Floating composer — typically a <Composer> (the standard chat input). It
|
|
12
|
+
* floats, padded, over the bottom of the transcript, centred at
|
|
13
|
+
* `--chat-composer-column` (default `--container-3xl`) under a transcript
|
|
14
|
+
* column of `--chat-column` (default `--container-4xl`, read by
|
|
15
|
+
* `ConversationContent`); turns scroll up behind it and fade out.
|
|
16
|
+
*/
|
|
10
17
|
composer?: ReactNode;
|
|
11
18
|
/** Optional header row (title, model picker, actions). */
|
|
12
19
|
header?: ReactNode;
|
|
13
20
|
/** Optional right-side context rail (sources, files, memory). */
|
|
14
21
|
aside?: ReactNode;
|
|
15
22
|
/**
|
|
16
|
-
*
|
|
17
|
-
* - `"card"` (default) — a self-contained rounded, bordered chat card
|
|
18
|
-
* when the shell sits on a page among other
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* of a hard bordered bar. Use when the shell already lives inside a bounded
|
|
23
|
+
* Frame treatment (the transcript/composer layout is the same in both):
|
|
24
|
+
* - `"card"` (default) — a self-contained rounded, bordered chat card with a
|
|
25
|
+
* divider under the header. Use when the shell sits on a page among other
|
|
26
|
+
* content and needs its own frame.
|
|
27
|
+
* - `"bare"` — an immersive pane that fills its container with NO frame and
|
|
28
|
+
* no header divider. Use when the shell already lives inside a bounded
|
|
23
29
|
* region (an AppShell `SidebarInset`, a full-page workspace) so it doesn't
|
|
24
30
|
* draw a redundant second frame.
|
|
25
31
|
*/
|
|
@@ -28,14 +34,15 @@ export interface ChatShellProps {
|
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
/**
|
|
31
|
-
* Layout shell for an AI chat built from the AI Elements: a header, a
|
|
32
|
-
* transcript (give it a <Conversation>, which is
|
|
33
|
-
*
|
|
37
|
+
* Layout shell for an AI chat built from the AI Elements: a header, a
|
|
38
|
+
* full-height scrollable transcript (give it a <Conversation>, which is
|
|
39
|
+
* `flex-1`), a composer floating over the transcript's bottom edge (a
|
|
40
|
+
* <Composer>, the standard chat input), and an optional context rail. Fills
|
|
34
41
|
* its parent's height.
|
|
35
42
|
*
|
|
36
|
-
* Two
|
|
37
|
-
*
|
|
38
|
-
*
|
|
43
|
+
* Two frames (`variant`): a framed `"card"` for in-page use, and a frameless
|
|
44
|
+
* `"bare"` immersive pane for when the shell already sits inside a bounded
|
|
45
|
+
* region and a second frame would just read as boxy.
|
|
39
46
|
*/
|
|
40
47
|
export function ChatShell({
|
|
41
48
|
children,
|
|
@@ -46,50 +53,89 @@ export function ChatShell({
|
|
|
46
53
|
className,
|
|
47
54
|
}: ChatShellProps) {
|
|
48
55
|
const bare = variant === "bare";
|
|
56
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
57
|
+
const composerRef = useRef<HTMLDivElement>(null);
|
|
58
|
+
const hasComposer = composer != null;
|
|
59
|
+
|
|
60
|
+
// The composer floats OVER the transcript, so the transcript has to
|
|
61
|
+
// know how tall it is — `ConversationContent` pads its bottom by
|
|
62
|
+
// `--chat-composer-inset` (so the last turn scrolls clear of the composer)
|
|
63
|
+
// and `ConversationScrollButton` sits just above it. Written straight onto
|
|
64
|
+
// the root's style: no React state, no re-render per resize.
|
|
65
|
+
useLayoutEffect(() => {
|
|
66
|
+
const root = rootRef.current;
|
|
67
|
+
const node = composerRef.current;
|
|
68
|
+
if (!root) return;
|
|
69
|
+
if (!node) {
|
|
70
|
+
root.style.removeProperty("--chat-composer-inset");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const sync = () =>
|
|
74
|
+
root.style.setProperty("--chat-composer-inset", `${node.getBoundingClientRect().height}px`);
|
|
75
|
+
sync();
|
|
76
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
77
|
+
const observer = new ResizeObserver(sync);
|
|
78
|
+
observer.observe(node);
|
|
79
|
+
return () => observer.disconnect();
|
|
80
|
+
}, [hasComposer]);
|
|
81
|
+
|
|
49
82
|
return (
|
|
50
83
|
<div
|
|
84
|
+
ref={rootRef}
|
|
85
|
+
data-slot="chat-shell"
|
|
86
|
+
data-variant={variant}
|
|
51
87
|
className={cn(
|
|
52
|
-
|
|
88
|
+
// Reading columns: the transcript (`ConversationContent`) reads a
|
|
89
|
+
// little wider than the composer beneath it. Override per app, e.g.
|
|
90
|
+
// `className="[--chat-column:var(--container-5xl)]"`.
|
|
91
|
+
"flex h-full min-h-0 w-full overflow-hidden bg-background [--chat-column:var(--container-4xl)] [--chat-composer-column:var(--container-3xl)]",
|
|
53
92
|
bare ? null : "rounded-xl border",
|
|
54
93
|
className,
|
|
55
94
|
)}
|
|
56
95
|
>
|
|
57
96
|
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
|
58
97
|
{header ? (
|
|
59
|
-
<div className={cn("flex h-
|
|
98
|
+
<div className={cn("flex h-header shrink-0 items-center px-4", bare ? null : "border-b")}>
|
|
60
99
|
{header}
|
|
61
100
|
</div>
|
|
62
101
|
) : null}
|
|
63
102
|
|
|
64
|
-
{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
103
|
+
{/* The transcript runs the FULL height of the pane and the composer
|
|
104
|
+
floats over its bottom edge, so turns scroll up behind the composer
|
|
105
|
+
and dissolve under the top edge instead of stopping at a hard bar.
|
|
106
|
+
The scrims are token-driven (`from-background`) so they fade to the
|
|
107
|
+
shell's own ground in every theme, and inert (`pointer-events-none`
|
|
108
|
+
+ `aria-hidden`) so they never block scroll, steal a tap, or get
|
|
109
|
+
announced. */}
|
|
110
|
+
<div className="relative flex min-h-0 flex-1 flex-col">
|
|
111
|
+
{children}
|
|
112
|
+
<div
|
|
113
|
+
aria-hidden
|
|
114
|
+
className="pointer-events-none absolute inset-x-0 top-0 z-10 h-8 bg-gradient-to-b from-background to-transparent"
|
|
115
|
+
/>
|
|
116
|
+
<div
|
|
117
|
+
aria-hidden
|
|
118
|
+
data-slot="chat-shell-scrim"
|
|
119
|
+
// Solid under the composer, fading out a little above it.
|
|
120
|
+
className="pointer-events-none absolute inset-x-0 bottom-0 z-10 bg-gradient-to-t from-background from-50% to-transparent"
|
|
121
|
+
style={{ height: "calc(var(--chat-composer-inset, 0px) + 3rem)" }}
|
|
122
|
+
/>
|
|
123
|
+
{composer ? (
|
|
124
|
+
// The float layer passes pointer events through, so the
|
|
125
|
+
// transcript's scrollbar and the gutters beside the composer stay
|
|
126
|
+
// live; only the composer itself takes input. DOM order still puts
|
|
127
|
+
// it after the transcript (tab order follows reading order).
|
|
77
128
|
<div
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// divider) above the fade; card: the classic bordered footer.
|
|
89
|
-
<div className={cn("shrink-0", bare ? "relative z-20 px-4 pb-4" : "border-t p-3")}>
|
|
90
|
-
{composer}
|
|
91
|
-
</div>
|
|
92
|
-
) : null}
|
|
129
|
+
ref={composerRef}
|
|
130
|
+
data-slot="chat-shell-composer"
|
|
131
|
+
className="pointer-events-none absolute inset-x-0 bottom-0 z-20 px-4 pb-4"
|
|
132
|
+
>
|
|
133
|
+
<div className="pointer-events-auto mx-auto w-full max-w-(--chat-composer-column)">
|
|
134
|
+
{composer}
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
) : null}
|
|
138
|
+
</div>
|
|
93
139
|
</div>
|
|
94
140
|
{aside ? <div className="hidden w-80 shrink-0 border-s lg:block">{aside}</div> : null}
|
|
95
141
|
</div>
|
package/src/chat.stories.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
import { ChatShell } from "./chat-shell";
|
|
3
4
|
import { Conversation, ConversationContent, ConversationScrollButton } from "./conversation";
|
|
4
5
|
import { Message, MessageContent, MessageResponse } from "./message";
|
|
5
6
|
import { Composer } from "./composer";
|
|
@@ -45,45 +46,49 @@ function ChatExample() {
|
|
|
45
46
|
};
|
|
46
47
|
|
|
47
48
|
return (
|
|
48
|
-
<div className="mx-auto
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
49
|
+
<div className="mx-auto h-[640px] max-w-4xl overflow-hidden rounded-xl border bg-background">
|
|
50
|
+
<ChatShell
|
|
51
|
+
variant="bare"
|
|
52
|
+
composer={
|
|
53
|
+
<Composer
|
|
54
|
+
placeholder="Ask about deploys…"
|
|
55
|
+
status={status === "submitted" ? "Generating…" : "Awaiting your input"}
|
|
56
|
+
sendStatus={status}
|
|
57
|
+
onSubmit={(message) => {
|
|
58
|
+
const text = message.text?.trim();
|
|
59
|
+
if (text) send(text);
|
|
60
|
+
}}
|
|
61
|
+
/>
|
|
62
|
+
}
|
|
63
|
+
>
|
|
64
|
+
<Conversation className="flex-1">
|
|
65
|
+
<ConversationContent>
|
|
66
|
+
{messages.map((m) => (
|
|
67
|
+
<Message from={m.role} key={m.id}>
|
|
68
|
+
<MessageContent>
|
|
69
|
+
{m.reasoning ? (
|
|
70
|
+
<Reasoning>
|
|
71
|
+
<ReasoningTrigger />
|
|
72
|
+
<ReasoningContent>{m.reasoning}</ReasoningContent>
|
|
73
|
+
</Reasoning>
|
|
74
|
+
) : null}
|
|
75
|
+
<MessageResponse>{m.text}</MessageResponse>
|
|
76
|
+
{m.role === "assistant" ? (
|
|
77
|
+
<Sources>
|
|
78
|
+
<SourcesTrigger count={2} />
|
|
79
|
+
<SourcesContent>
|
|
80
|
+
<Source href="https://example.com" title="Deploy log — wk 23" />
|
|
81
|
+
<Source href="https://example.com" title="Billing runbook" />
|
|
82
|
+
</SourcesContent>
|
|
83
|
+
</Sources>
|
|
84
|
+
) : null}
|
|
85
|
+
</MessageContent>
|
|
86
|
+
</Message>
|
|
87
|
+
))}
|
|
88
|
+
</ConversationContent>
|
|
89
|
+
<ConversationScrollButton />
|
|
90
|
+
</Conversation>
|
|
91
|
+
</ChatShell>
|
|
87
92
|
</div>
|
|
88
93
|
);
|
|
89
94
|
}
|
|
@@ -100,9 +105,9 @@ const meta = {
|
|
|
100
105
|
"composed from the @elabs-ai/components-ai grammar: a scrolling " +
|
|
101
106
|
"[Conversation](?path=/story/ai-conversation--default) of " +
|
|
102
107
|
"[Messages](?path=/story/ai-message--presets) with reasoning and sources, plus a " +
|
|
103
|
-
"[Composer](?path=/story/ai-composer--default)
|
|
104
|
-
"[ChatShell](?path=/story/ai-chatshell--default)
|
|
105
|
-
"
|
|
108
|
+
"[Composer](?path=/story/ai-composer--default), laid out by " +
|
|
109
|
+
"[ChatShell](?path=/story/ai-chatshell--default): the transcript scrolls behind the " +
|
|
110
|
+
"floating composer and both sit in centred reading columns.",
|
|
106
111
|
},
|
|
107
112
|
},
|
|
108
113
|
},
|
package/src/composer.stories.tsx
CHANGED
|
@@ -24,9 +24,8 @@
|
|
|
24
24
|
* the three controls the `mode`, `effort` and `slashCommands` slots render.
|
|
25
25
|
* The fourth slot, `modelPicker`, takes a `ModelPicker` from
|
|
26
26
|
* `@elabs-ai/components-ui`, which is why it has no page of its own here.
|
|
27
|
-
* - **`WithMentionInput`** — the
|
|
28
|
-
*
|
|
29
|
-
* that page drops to `PromptInput` on purpose.
|
|
27
|
+
* - **`WithMentionInput`** — the `mentions` slot: an `@`-mention roster
|
|
28
|
+
* (`MentionInput` from `@elabs-ai/components-ui`) on the composer's field.
|
|
30
29
|
*
|
|
31
30
|
* `Terminal/TerminalComposer` is the console skin of this same family — same
|
|
32
31
|
* footer order, different surface.
|
|
@@ -147,8 +146,8 @@ const meta = {
|
|
|
147
146
|
"[PromptInputMode](?path=/docs/ai-composer-promptinputmode--docs), " +
|
|
148
147
|
"[PromptInputEffort](?path=/docs/ai-composer-promptinputeffort--docs) and " +
|
|
149
148
|
"[PromptInputSlash](?path=/docs/ai-composer-promptinputslash--docs) are the three controls the `mode`, `effort` and `slashCommands` slots render (the fourth slot, `modelPicker`, takes a `ModelPicker` from @elabs-ai/components-ui, which is why it has no page of its own); and " +
|
|
150
|
-
"[WithMentionInput](?path=/docs/ai-composer-withmentioninput--docs)
|
|
151
|
-
"The scaffold that puts this composer under a transcript is [Patterns/Blocks/AI Chat Shell](?path=/docs/patterns-blocks-ai-chat-shell--docs); the console skin of the same family is [Terminal/TerminalComposer](?path=/docs/terminal-terminalcomposer--docs).",
|
|
149
|
+
"[WithMentionInput](?path=/docs/ai-composer-withmentioninput--docs) shows the `mentions` slot — an @-mention roster on the composer's own field. " +
|
|
150
|
+
"The scaffold that puts this composer under a transcript is [Patterns/Blocks/AI and Terminal/AI Chat Shell](?path=/docs/patterns-blocks-ai-and-terminal-ai-chat-shell--docs); the console skin of the same family is [Terminal/TerminalComposer](?path=/docs/terminal-terminalcomposer--docs).",
|
|
152
151
|
},
|
|
153
152
|
},
|
|
154
153
|
},
|
package/src/composer.test.tsx
CHANGED
|
@@ -531,3 +531,48 @@ describe("Composer — shortcut hints change with a busy state (#107)", () => {
|
|
|
531
531
|
expect(shortcutRow).not.toContainElement(stopButton);
|
|
532
532
|
});
|
|
533
533
|
});
|
|
534
|
+
|
|
535
|
+
describe("Composer — mentions slot", () => {
|
|
536
|
+
const ROSTER = [
|
|
537
|
+
{ id: "u-ada", label: "Ada Lovelace" },
|
|
538
|
+
{ id: "u-grace", label: "Grace Hopper" },
|
|
539
|
+
];
|
|
540
|
+
|
|
541
|
+
it("opens the roster on @, inserts on Enter without submitting, then submits and resets", async () => {
|
|
542
|
+
const user = userEvent.setup();
|
|
543
|
+
const onSubmit = vi.fn();
|
|
544
|
+
const onValueChange = vi.fn();
|
|
545
|
+
render(
|
|
546
|
+
<Composer
|
|
547
|
+
onSubmit={onSubmit}
|
|
548
|
+
showAttach={false}
|
|
549
|
+
showVoice={false}
|
|
550
|
+
mentions={{ options: ROSTER, onValueChange }}
|
|
551
|
+
/>,
|
|
552
|
+
);
|
|
553
|
+
const field = screen.getByRole("textbox") as HTMLTextAreaElement;
|
|
554
|
+
// The composer's own field — what PromptInput's submit reads.
|
|
555
|
+
expect(field).toHaveAttribute("name", "message");
|
|
556
|
+
|
|
557
|
+
await user.click(field);
|
|
558
|
+
await user.keyboard("hi @ada");
|
|
559
|
+
expect(field).toHaveAttribute("data-state", "open");
|
|
560
|
+
await user.keyboard("{Enter}");
|
|
561
|
+
expect(field.value).toBe("hi @Ada Lovelace ");
|
|
562
|
+
expect(onSubmit).not.toHaveBeenCalled();
|
|
563
|
+
expect(onValueChange).toHaveBeenLastCalledWith(
|
|
564
|
+
expect.objectContaining({ mentions: [expect.objectContaining({ id: "u-ada" })] }),
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
await user.keyboard("{Enter}");
|
|
568
|
+
expect(onSubmit).toHaveBeenCalledTimes(1);
|
|
569
|
+
expect(onSubmit.mock.calls[0]![0].text.trim()).toBe("hi @Ada Lovelace");
|
|
570
|
+
expect(field.value).toBe("");
|
|
571
|
+
expect(onValueChange).toHaveBeenLastCalledWith({ text: "", mentions: [] });
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it("refuses slashCommands and mentions together at the type level", () => {
|
|
575
|
+
// @ts-expect-error — the field takes a slash palette OR a mention roster.
|
|
576
|
+
render(<Composer slashCommands={[]} mentions={{ options: ROSTER }} />);
|
|
577
|
+
});
|
|
578
|
+
});
|
package/src/composer.tsx
CHANGED
|
@@ -2,7 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useRef, useState, type ComponentProps, type ReactNode } from "react";
|
|
4
4
|
import { ArrowUp, Mic, Paperclip, Sparkles } from "lucide-react";
|
|
5
|
-
import {
|
|
5
|
+
import { useControllableState } from "@radix-ui/react-use-controllable-state";
|
|
6
|
+
import {
|
|
7
|
+
cn,
|
|
8
|
+
Kbd,
|
|
9
|
+
MentionInput,
|
|
10
|
+
MentionInputContent,
|
|
11
|
+
MentionInputEmpty,
|
|
12
|
+
MentionInputItem,
|
|
13
|
+
MentionInputList,
|
|
14
|
+
MentionInputTextarea,
|
|
15
|
+
useLocale,
|
|
16
|
+
type MentionOption,
|
|
17
|
+
type MentionValue,
|
|
18
|
+
} from "@elabs-ai/components-ui";
|
|
6
19
|
|
|
7
20
|
import {
|
|
8
21
|
PromptInput,
|
|
@@ -62,7 +75,29 @@ export interface ComposerShortcut {
|
|
|
62
75
|
label: string;
|
|
63
76
|
}
|
|
64
77
|
|
|
65
|
-
|
|
78
|
+
/**
|
|
79
|
+
* An `@`-mention roster for the composer field (`MentionInput` from
|
|
80
|
+
* `@elabs-ai/components-ui`). The field keeps its own submit contract; the
|
|
81
|
+
* mentions ride alongside the text in a `MentionValue`.
|
|
82
|
+
*
|
|
83
|
+
* Read the mentions on submit from `value` (controlled) or from
|
|
84
|
+
* `onValueChange`, and resolve them with `serializeMentions(value)`
|
|
85
|
+
* (`{ text, mentionedIds }`). `Composer` resets the value to empty after a
|
|
86
|
+
* submit it accepts — through `onValueChange`, so a controlled caller's state
|
|
87
|
+
* clears too.
|
|
88
|
+
*/
|
|
89
|
+
export interface ComposerMentions {
|
|
90
|
+
/** Who (or what) can be mentioned. */
|
|
91
|
+
options: MentionOption[];
|
|
92
|
+
/** Controlled value. Omit for an uncontrolled roster. */
|
|
93
|
+
value?: MentionValue;
|
|
94
|
+
/** Fires on every edit, pick and post-submit reset. */
|
|
95
|
+
onValueChange?: (value: MentionValue) => void;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const EMPTY_MENTION_VALUE: MentionValue = { text: "", mentions: [] };
|
|
99
|
+
|
|
100
|
+
export interface ComposerBaseProps {
|
|
66
101
|
/** Submit handler — receives the assembled message (text + attachments). */
|
|
67
102
|
onSubmit?: PromptInputProps["onSubmit"];
|
|
68
103
|
/** Textarea placeholder. Default `"Ask me anything…"`. */
|
|
@@ -96,20 +131,6 @@ export interface ComposerProps {
|
|
|
96
131
|
* and is entirely yours; `aria-label` names the scale.
|
|
97
132
|
*/
|
|
98
133
|
effort?: ComposerEffortProps;
|
|
99
|
-
/**
|
|
100
|
-
* Slash-command palette. When set (and non-empty) the textarea becomes a
|
|
101
|
-
* `PromptInputSlashTextarea` wrapped in a `PromptInputSlash`, so typing `/`
|
|
102
|
-
* at the start of a line opens a filtered command list — nothing else about
|
|
103
|
-
* the composer changes.
|
|
104
|
-
*
|
|
105
|
-
* Note this makes the textarea REACT-CONTROLLED (the palette needs to read
|
|
106
|
-
* and splice the text), which is why `Composer` clears its own copy of the
|
|
107
|
-
* text on submit: `PromptInput`'s `form.reset()` reaches the DOM node, not
|
|
108
|
-
* React state.
|
|
109
|
-
*/
|
|
110
|
-
slashCommands?: PromptInputSlashProps["commands"];
|
|
111
|
-
/** Fires with the chosen slash command (alongside the text splice). */
|
|
112
|
-
onSlashCommand?: PromptInputSlashProps["onSelect"];
|
|
113
134
|
/**
|
|
114
135
|
* Override the DEFAULT tool buttons (today: the attach button) — e.g. to add
|
|
115
136
|
* a web-search or connect-data control. Render `PromptInputButton`s /
|
|
@@ -185,6 +206,38 @@ export interface ComposerProps {
|
|
|
185
206
|
className?: string;
|
|
186
207
|
}
|
|
187
208
|
|
|
209
|
+
/**
|
|
210
|
+
* The field enhancements that each take over the textarea — a slash palette OR
|
|
211
|
+
* a mention roster, never both. The union makes the impossible combination a
|
|
212
|
+
* type error instead of a silent precedence rule.
|
|
213
|
+
*/
|
|
214
|
+
export type ComposerFieldProps =
|
|
215
|
+
| {
|
|
216
|
+
/**
|
|
217
|
+
* Slash-command palette. When set (and non-empty) the textarea becomes a
|
|
218
|
+
* `PromptInputSlashTextarea` wrapped in a `PromptInputSlash`, so typing `/`
|
|
219
|
+
* at the start of a line opens a filtered command list — nothing else about
|
|
220
|
+
* the composer changes.
|
|
221
|
+
*
|
|
222
|
+
* Note this makes the textarea REACT-CONTROLLED (the palette needs to read
|
|
223
|
+
* and splice the text), which is why `Composer` clears its own copy of the
|
|
224
|
+
* text on submit: `PromptInput`'s `form.reset()` reaches the DOM node, not
|
|
225
|
+
* React state.
|
|
226
|
+
*/
|
|
227
|
+
slashCommands?: PromptInputSlashProps["commands"];
|
|
228
|
+
/** Fires with the chosen slash command (alongside the text splice). */
|
|
229
|
+
onSlashCommand?: PromptInputSlashProps["onSelect"];
|
|
230
|
+
mentions?: never;
|
|
231
|
+
}
|
|
232
|
+
| {
|
|
233
|
+
/** `@`-mention roster on the field — see `ComposerMentions`. */
|
|
234
|
+
mentions?: ComposerMentions;
|
|
235
|
+
slashCommands?: never;
|
|
236
|
+
onSlashCommand?: never;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export type ComposerProps = ComposerBaseProps & ComposerFieldProps;
|
|
240
|
+
|
|
188
241
|
/**
|
|
189
242
|
* Composer — the standard brand-ui AI chat input.
|
|
190
243
|
*
|
|
@@ -202,9 +255,10 @@ export interface ComposerProps {
|
|
|
202
255
|
*
|
|
203
256
|
* **Composer is the chat input. Every control the `PromptInput` family ships
|
|
204
257
|
* is reachable from a `Composer` prop; drop to `PromptInput` only for a
|
|
205
|
-
* bespoke shell.** `modelPicker`, `mode`, `effort
|
|
206
|
-
*
|
|
207
|
-
* `PromptInputMode`, `PromptInputEffort
|
|
258
|
+
* bespoke shell.** `modelPicker`, `mode`, `effort`, `slashCommands` and
|
|
259
|
+
* `mentions` are the slots that make that true — they render `ModelPicker`,
|
|
260
|
+
* `PromptInputMode`, `PromptInputEffort`, `PromptInputSlash` and
|
|
261
|
+
* `MentionInput` respectively,
|
|
208
262
|
* in the footer order `attach · modelPicker · mode · effort │ voice · send`
|
|
209
263
|
* (the same left-to-right arrangement `TerminalComposer` uses, so the chat and
|
|
210
264
|
* console skins agree).
|
|
@@ -218,6 +272,7 @@ export function Composer({
|
|
|
218
272
|
effort,
|
|
219
273
|
slashCommands,
|
|
220
274
|
onSlashCommand,
|
|
275
|
+
mentions,
|
|
221
276
|
tools,
|
|
222
277
|
sendStatus,
|
|
223
278
|
onStop,
|
|
@@ -239,6 +294,12 @@ export function Composer({
|
|
|
239
294
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
240
295
|
const [text, setText] = useState("");
|
|
241
296
|
const hasSlash = slashCommands !== undefined && slashCommands.length > 0;
|
|
297
|
+
// Always called (hook order); only read when `mentions` is set.
|
|
298
|
+
const [mentionValue, setMentionValue] = useControllableState<MentionValue>({
|
|
299
|
+
prop: mentions?.value,
|
|
300
|
+
defaultProp: EMPTY_MENTION_VALUE,
|
|
301
|
+
onChange: mentions?.onValueChange,
|
|
302
|
+
});
|
|
242
303
|
|
|
243
304
|
// #107: "Composer shortcut hints change with a busy state" — derived from
|
|
244
305
|
// the existing canonical `sendStatus`, never a second boolean prop. The
|
|
@@ -258,9 +319,12 @@ export function Composer({
|
|
|
258
319
|
// keep rendering the sent text straight back. Clearing here is a no-op in
|
|
259
320
|
// the uncontrolled (non-slash) arrangement.
|
|
260
321
|
setText("");
|
|
261
|
-
|
|
322
|
+
const result = onSubmit?.(message, event);
|
|
323
|
+
// `form.reset()` cannot clear a value MentionInput controls either.
|
|
324
|
+
if (mentions) setMentionValue(EMPTY_MENTION_VALUE);
|
|
325
|
+
return result;
|
|
262
326
|
},
|
|
263
|
-
[onSubmit],
|
|
327
|
+
[onSubmit, mentions, setMentionValue],
|
|
264
328
|
);
|
|
265
329
|
|
|
266
330
|
const resolvedPlaceholder = placeholder ?? t("ai.composer.placeholder");
|
|
@@ -305,6 +369,25 @@ export function Composer({
|
|
|
305
369
|
placeholder={resolvedPlaceholder}
|
|
306
370
|
/>
|
|
307
371
|
</PromptInputSlash>
|
|
372
|
+
) : mentions ? (
|
|
373
|
+
<MentionInput
|
|
374
|
+
options={mentions.options}
|
|
375
|
+
value={mentionValue}
|
|
376
|
+
onValueChange={setMentionValue}
|
|
377
|
+
>
|
|
378
|
+
{/* `asChild` lends the mention behaviour to the composer's own
|
|
379
|
+
textarea, so `name="message"` — what PromptInput's submit
|
|
380
|
+
reads — survives. */}
|
|
381
|
+
<MentionInputTextarea asChild>
|
|
382
|
+
<PromptInputTextarea placeholder={resolvedPlaceholder} />
|
|
383
|
+
</MentionInputTextarea>
|
|
384
|
+
<MentionInputContent>
|
|
385
|
+
<MentionInputList>
|
|
386
|
+
{(option) => <MentionInputItem key={option.id} option={option} />}
|
|
387
|
+
</MentionInputList>
|
|
388
|
+
<MentionInputEmpty />
|
|
389
|
+
</MentionInputContent>
|
|
390
|
+
</MentionInput>
|
|
308
391
|
) : (
|
|
309
392
|
<PromptInputTextarea placeholder={resolvedPlaceholder} />
|
|
310
393
|
)}
|
|
@@ -102,7 +102,7 @@ function WorkspaceDemo(providerProps: Omit<ContextPanelProviderProps, "children"
|
|
|
102
102
|
<ContextPanelProvider {...providerProps}>
|
|
103
103
|
<div className="flex h-[520px] w-full overflow-hidden rounded-xl border bg-background">
|
|
104
104
|
<div className="flex min-w-0 flex-1 flex-col">
|
|
105
|
-
<header className="flex h-
|
|
105
|
+
<header className="flex h-header shrink-0 items-center justify-between border-b px-4">
|
|
106
106
|
<span className="text-subtitle">Agentic workspace</span>
|
|
107
107
|
<ContextPanelTrigger />
|
|
108
108
|
</header>
|
package/src/context-panel.tsx
CHANGED
|
@@ -469,7 +469,7 @@ export const ContextPanelHeader = forwardRef<HTMLDivElement, ContextPanelHeaderP
|
|
|
469
469
|
<div
|
|
470
470
|
ref={ref}
|
|
471
471
|
data-slot="context-panel-header"
|
|
472
|
-
className={cn("flex h-
|
|
472
|
+
className={cn("flex h-header shrink-0 items-center gap-2 border-b px-3", className)}
|
|
473
473
|
{...props}
|
|
474
474
|
>
|
|
475
475
|
{isDetail ? (
|
|
@@ -45,6 +45,22 @@ describe("Conversation — transcript region", () => {
|
|
|
45
45
|
expect(screen.getByRole("log")).toBeInTheDocument();
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
+
it("lets the content grow past the viewport so its composer inset stays scrollable", () => {
|
|
49
|
+
render(
|
|
50
|
+
<Conversation>
|
|
51
|
+
<ConversationContent>
|
|
52
|
+
<p>turn</p>
|
|
53
|
+
</ConversationContent>
|
|
54
|
+
</Conversation>,
|
|
55
|
+
);
|
|
56
|
+
const content = screen.getByText("turn").parentElement!;
|
|
57
|
+
// `min-h-0` clamps the content to the viewport height: the turns then
|
|
58
|
+
// overflow it and the bottom padding (the floating-composer inset) is
|
|
59
|
+
// left behind, so the last turn scrolls under the composer.
|
|
60
|
+
expect(content).not.toHaveClass("min-h-0");
|
|
61
|
+
expect(content).toHaveClass("shrink-0", "flex-1");
|
|
62
|
+
});
|
|
63
|
+
|
|
48
64
|
it("renders its children inside the log, in order", () => {
|
|
49
65
|
render(
|
|
50
66
|
<Conversation>
|
package/src/conversation.tsx
CHANGED
|
@@ -48,7 +48,19 @@ export const ConversationContent = ({
|
|
|
48
48
|
// can't resolve against a block-level parent whose own height is
|
|
49
49
|
// `min-height`-only (the #72 empty-state-pinned-to-the-top bug).
|
|
50
50
|
scrollClassName={cn("flex flex-col", scrollClassName)}
|
|
51
|
-
|
|
51
|
+
// Inside a `ChatShell variant="bare"` the composer floats over the
|
|
52
|
+
// transcript: the shell publishes `--chat-composer-inset` (its measured
|
|
53
|
+
// height) so the last turn scrolls clear of it, and `--chat-column` so
|
|
54
|
+
// turns share the composer's reading column. Outside such a shell both
|
|
55
|
+
// variables are unset: the inset falls back to 0 and `max-width` to none.
|
|
56
|
+
// `shrink-0`, never `min-h-0`: the content div must grow with its turns.
|
|
57
|
+
// Clamped to the viewport height, the turns overflow it and its bottom
|
|
58
|
+
// padding stays behind at the viewport's edge, so the last turn ends up
|
|
59
|
+
// under the floating composer instead of above it.
|
|
60
|
+
className={cn(
|
|
61
|
+
"mx-auto flex w-full max-w-(--chat-column) flex-1 shrink-0 flex-col gap-8 p-4 pb-[calc(var(--chat-composer-inset,0px)+--spacing(4))]",
|
|
62
|
+
className,
|
|
63
|
+
)}
|
|
52
64
|
{...props}
|
|
53
65
|
/>
|
|
54
66
|
);
|
|
@@ -113,7 +125,9 @@ export const ConversationScrollButton = ({
|
|
|
113
125
|
// the quieter `hover:bg-muted` (in place of `outline`'s default
|
|
114
126
|
// `hover:bg-accent`) now applies in every theme, not only the two
|
|
115
127
|
// shipped ones.
|
|
116
|
-
|
|
128
|
+
// `--chat-composer-inset`: lift the button clear of a floating
|
|
129
|
+
// `ChatShell variant="bare"` composer (0 anywhere else).
|
|
130
|
+
"absolute bottom-[calc(var(--chat-composer-inset,0px)+--spacing(4))] left-[50%] z-30 translate-x-[-50%] rounded-full bg-background hover:bg-muted",
|
|
117
131
|
className,
|
|
118
132
|
)}
|
|
119
133
|
aria-label={t("ai.turnStatus.scrollToBottom")}
|