@elabs-ai/components-ai 4.2.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 +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-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 +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 +5425 -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 +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/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
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { A2uiSurface } from "./a2ui-surface";
|
|
5
|
+
import { A2UI_CATALOG_SCHEMA } from "./core/catalog.generated";
|
|
6
|
+
import type { A2uiSurfaceSpec } from "./core/spec";
|
|
7
|
+
import { UI_CATALOG_BINDINGS, createA2uiCatalog, defineA2uiType, uiCatalog } from "./ui-catalog";
|
|
8
|
+
|
|
9
|
+
const ORDER: A2uiSurfaceSpec = {
|
|
10
|
+
a2ui: "1",
|
|
11
|
+
title: "Order 4711",
|
|
12
|
+
root: {
|
|
13
|
+
type: "Card",
|
|
14
|
+
id: "order",
|
|
15
|
+
children: [
|
|
16
|
+
{
|
|
17
|
+
type: "CardHeader",
|
|
18
|
+
children: [
|
|
19
|
+
{ type: "CardTitle", children: ["Order 4711"] },
|
|
20
|
+
{ type: "CardDescription", children: ["Placed today"] },
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
type: "CardContent",
|
|
25
|
+
children: [
|
|
26
|
+
{
|
|
27
|
+
type: "Grid",
|
|
28
|
+
props: { columns: 2 },
|
|
29
|
+
children: [
|
|
30
|
+
{ type: "MetricCard", props: { label: "Total", value: "€ 1,240" } },
|
|
31
|
+
{ type: "StatusBadge", props: { status: "running" } },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: "SectionHeader",
|
|
36
|
+
props: { title: "Lines", actions: { type: "Badge", children: ["3 items"] } },
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
type: "CardFooter",
|
|
42
|
+
children: [
|
|
43
|
+
{
|
|
44
|
+
type: "Button",
|
|
45
|
+
props: { variant: "default" },
|
|
46
|
+
on: { click: { name: "approve", payload: { id: 4711 } } },
|
|
47
|
+
children: ["Approve"],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
type: "Input",
|
|
51
|
+
props: { "aria-label": "Note", defaultValue: "" },
|
|
52
|
+
on: { change: { name: "note" } },
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
describe("A2uiSurface", () => {
|
|
61
|
+
it("renders a valid surface with the real components and forwards actions", () => {
|
|
62
|
+
const onAction = vi.fn();
|
|
63
|
+
render(<A2uiSurface surface={ORDER} onAction={onAction} />);
|
|
64
|
+
const region = screen.getByRole("group", { name: "Order 4711" });
|
|
65
|
+
expect(region).toHaveAttribute("data-status", "ready");
|
|
66
|
+
expect(
|
|
67
|
+
screen.getByText("Order 4711", { selector: "[data-slot='card-title']" }),
|
|
68
|
+
).toBeInTheDocument();
|
|
69
|
+
expect(screen.getByText("€ 1,240")).toBeInTheDocument();
|
|
70
|
+
expect(screen.getByText("3 items")).toBeInTheDocument(); // a node inside a `node` prop
|
|
71
|
+
expect(region.querySelector("[data-a2ui-id='order']")).toHaveAttribute(
|
|
72
|
+
"data-a2ui-type",
|
|
73
|
+
"Card",
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
fireEvent.click(screen.getByRole("button", { name: "Approve" }));
|
|
77
|
+
expect(onAction).toHaveBeenCalledWith(
|
|
78
|
+
{ name: "approve", payload: { id: 4711 } },
|
|
79
|
+
expect.objectContaining({ event: "click", path: "root.children[2].children[0]" }),
|
|
80
|
+
);
|
|
81
|
+
fireEvent.change(screen.getByRole("textbox", { name: "Note" }), { target: { value: "ok" } });
|
|
82
|
+
expect(onAction).toHaveBeenLastCalledWith(
|
|
83
|
+
{ name: "note" },
|
|
84
|
+
expect.objectContaining({ event: "change", value: "ok" }),
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("settled + invalid → role=alert with every problem, onError once; nothing drawn", () => {
|
|
89
|
+
const onError = vi.fn();
|
|
90
|
+
const { rerender } = render(
|
|
91
|
+
<A2uiSurface
|
|
92
|
+
surface={{
|
|
93
|
+
a2ui: "1",
|
|
94
|
+
root: {
|
|
95
|
+
type: "Stack",
|
|
96
|
+
children: [{ type: "Sparkle" }, { type: "Button", props: { variant: "loud" } }],
|
|
97
|
+
},
|
|
98
|
+
}}
|
|
99
|
+
onError={onError}
|
|
100
|
+
/>,
|
|
101
|
+
);
|
|
102
|
+
const alert = screen.getByRole("alert");
|
|
103
|
+
expect(alert).toHaveTextContent("root.children[0].type");
|
|
104
|
+
expect(alert).toHaveTextContent("root.children[1].props.variant");
|
|
105
|
+
expect(screen.queryByRole("button")).toBeNull();
|
|
106
|
+
expect(onError).toHaveBeenCalledTimes(1);
|
|
107
|
+
expect(onError.mock.calls[0]![0]).toHaveLength(2);
|
|
108
|
+
rerender(
|
|
109
|
+
<A2uiSurface
|
|
110
|
+
surface={{
|
|
111
|
+
a2ui: "1",
|
|
112
|
+
root: {
|
|
113
|
+
type: "Stack",
|
|
114
|
+
children: [{ type: "Sparkle" }, { type: "Button", props: { variant: "loud" } }],
|
|
115
|
+
},
|
|
116
|
+
}}
|
|
117
|
+
onError={onError}
|
|
118
|
+
/>,
|
|
119
|
+
);
|
|
120
|
+
expect(onError).toHaveBeenCalledTimes(1);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("streams: a JSON prefix builds up, invalid-yet nodes are pruned, no alert", () => {
|
|
124
|
+
const full = JSON.stringify({
|
|
125
|
+
a2ui: "1",
|
|
126
|
+
root: {
|
|
127
|
+
type: "Stack",
|
|
128
|
+
children: [
|
|
129
|
+
{ type: "Heading", props: { level: 2 }, children: ["Revenue"] },
|
|
130
|
+
{ type: "Text", children: ["Up 12% quarter over quarter."] },
|
|
131
|
+
{ type: "Button", props: { variant: "outline" }, children: ["Details"] },
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
const cut = full.indexOf('"Up 12%') + 6;
|
|
136
|
+
const { rerender, container } = render(
|
|
137
|
+
<A2uiSurface surface={full.slice(0, cut)} isStreaming />,
|
|
138
|
+
);
|
|
139
|
+
expect(screen.getByRole("heading", { name: "Revenue" })).toBeInTheDocument();
|
|
140
|
+
expect(screen.getByText("Up 12")).toBeInTheDocument(); // the half-arrived text node
|
|
141
|
+
expect(screen.queryByRole("button")).toBeNull();
|
|
142
|
+
expect(screen.queryByRole("alert")).toBeNull();
|
|
143
|
+
expect(container.querySelector("[data-slot='a2ui-surface']")).toHaveAttribute(
|
|
144
|
+
"data-status",
|
|
145
|
+
"pending",
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
// A prefix that ends inside the Button's props — the Button is pruned, siblings stay.
|
|
149
|
+
const cut2 = full.indexOf('"variant":"out') + 12;
|
|
150
|
+
rerender(<A2uiSurface surface={full.slice(0, cut2)} isStreaming />);
|
|
151
|
+
expect(screen.getByRole("heading", { name: "Revenue" })).toBeInTheDocument();
|
|
152
|
+
expect(screen.queryByRole("button")).toBeNull();
|
|
153
|
+
|
|
154
|
+
rerender(<A2uiSurface surface={full} isStreaming={false} />);
|
|
155
|
+
expect(screen.getByRole("button", { name: "Details" })).toBeInTheDocument();
|
|
156
|
+
expect(container.querySelector("[data-slot='a2ui-surface']")).toHaveAttribute(
|
|
157
|
+
"data-status",
|
|
158
|
+
"ready",
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("loading → skeleton with a polite live region; empty → idle, nothing", () => {
|
|
163
|
+
const { container, rerender } = render(<A2uiSurface surface={null} loading />);
|
|
164
|
+
expect(screen.getByRole("status")).toHaveTextContent("Loading surface…");
|
|
165
|
+
rerender(<A2uiSurface surface={null} />);
|
|
166
|
+
expect(screen.queryByRole("status")).toBeNull();
|
|
167
|
+
expect(container.querySelector("[data-slot='a2ui-surface']")).toHaveAttribute(
|
|
168
|
+
"data-status",
|
|
169
|
+
"idle",
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("an app extends the catalog with its own type", () => {
|
|
174
|
+
const Sparkle = ({ level }: { level: string }) => <output>sparkle:{level}</output>;
|
|
175
|
+
const catalog = createA2uiCatalog(
|
|
176
|
+
{ ...UI_CATALOG_BINDINGS, Sparkle },
|
|
177
|
+
{
|
|
178
|
+
...A2UI_CATALOG_SCHEMA,
|
|
179
|
+
Sparkle: defineA2uiType({
|
|
180
|
+
props: { level: { type: "string", enum: ["lo", "hi"], required: true } },
|
|
181
|
+
}),
|
|
182
|
+
},
|
|
183
|
+
);
|
|
184
|
+
render(
|
|
185
|
+
<A2uiSurface
|
|
186
|
+
catalog={catalog}
|
|
187
|
+
surface={{ a2ui: "1", root: { type: "Sparkle", props: { level: "hi" } } }}
|
|
188
|
+
/>,
|
|
189
|
+
);
|
|
190
|
+
expect(screen.getByText("sparkle:hi")).toBeInTheDocument();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("the shipped catalog binds exactly the generated schema's non-builtin types", () => {
|
|
194
|
+
const schemaTypes = Object.entries(A2UI_CATALOG_SCHEMA)
|
|
195
|
+
.filter(([, s]) => !s.builtin)
|
|
196
|
+
.map(([t]) => t)
|
|
197
|
+
.sort();
|
|
198
|
+
expect(Object.keys(UI_CATALOG_BINDINGS).sort()).toEqual(schemaTypes);
|
|
199
|
+
expect(Object.keys(uiCatalog).sort()).toEqual(Object.keys(A2UI_CATALOG_SCHEMA).sort());
|
|
200
|
+
});
|
|
201
|
+
});
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A2uiSurface — render an agent-DESIGNED screen from data (D2's safe generative
|
|
5
|
+
* path). The agent emits an `A2uiSurfaceSpec` (JSON: a tree of catalog nodes);
|
|
6
|
+
* this component validates it against the catalog and draws it with the real
|
|
7
|
+
* components — never `eval`, never arbitrary tags, never agent styling.
|
|
8
|
+
*
|
|
9
|
+
* Readiness follows the house model (loading-states): `loading` → skeleton;
|
|
10
|
+
* `isStreaming` (or a string surface that is still incomplete JSON) → build up,
|
|
11
|
+
* pruning only the nodes that do not validate YET and suppressing errors; a
|
|
12
|
+
* settled surface that fails validation → `role="alert"` with every problem and
|
|
13
|
+
* `onError`. Interaction is data too: a node's `on.<event>` names a host action
|
|
14
|
+
* that arrives at `onAction` — the app decides what "approve" means (D5).
|
|
15
|
+
*/
|
|
16
|
+
import { Skeleton, useLocale } from "@elabs-ai/components-ui";
|
|
17
|
+
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
18
|
+
import { AlertCircle } from "lucide-react";
|
|
19
|
+
import type { ComponentProps, ReactNode, SyntheticEvent } from "react";
|
|
20
|
+
import { createElement, forwardRef, memo, useEffect, useMemo, useRef, useState } from "react";
|
|
21
|
+
|
|
22
|
+
import { completeJson, parseSurfaceJson } from "./core/complete-json";
|
|
23
|
+
import type {
|
|
24
|
+
A2uiAction,
|
|
25
|
+
A2uiElement,
|
|
26
|
+
A2uiError,
|
|
27
|
+
A2uiNode,
|
|
28
|
+
A2uiPropValue,
|
|
29
|
+
A2uiSurfaceSpec,
|
|
30
|
+
} from "./core/spec";
|
|
31
|
+
import { invalidNodePaths, isA2uiElement, validateA2uiSurface } from "./core/validate";
|
|
32
|
+
import { catalogSchema, uiCatalog, type A2uiCatalog } from "./ui-catalog";
|
|
33
|
+
|
|
34
|
+
export type A2uiSurfaceStatus = "idle" | "pending" | "ready" | "error";
|
|
35
|
+
|
|
36
|
+
/** What `onAction` receives besides the action the agent named. */
|
|
37
|
+
export interface A2uiActionContext {
|
|
38
|
+
/** The catalog event that fired (`click`, `change`, …). */
|
|
39
|
+
event: string;
|
|
40
|
+
/** The control's value for `change` events (text, checked, selected value…). */
|
|
41
|
+
value?: unknown;
|
|
42
|
+
/** The node that carried the binding, and its path in the spec. */
|
|
43
|
+
node: A2uiElement;
|
|
44
|
+
path: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type A2uiActionHandler = (action: A2uiAction, context: A2uiActionContext) => void;
|
|
48
|
+
|
|
49
|
+
export type A2uiSurfaceProps = Omit<ComponentProps<"div">, "children" | "onError"> & {
|
|
50
|
+
/** The agent's surface: a parsed spec, or its JSON text (may be a streaming prefix). */
|
|
51
|
+
surface: A2uiSurfaceSpec | string | null | undefined;
|
|
52
|
+
/** Which types render, and how. Defaults to the shipped `uiCatalog`. */
|
|
53
|
+
catalog?: A2uiCatalog;
|
|
54
|
+
/** Receives every `on.<event>` binding the user triggers. */
|
|
55
|
+
onAction?: A2uiActionHandler;
|
|
56
|
+
/** Content is arriving incrementally — build up, suppress transient errors. */
|
|
57
|
+
isStreaming?: boolean;
|
|
58
|
+
/** No surface yet (fetch-then-show) — forces the skeleton. */
|
|
59
|
+
loading?: boolean;
|
|
60
|
+
/** Fires once per settled, invalid surface with the validation errors. */
|
|
61
|
+
onError?: (errors: A2uiError[]) => void;
|
|
62
|
+
/** Replace the default skeleton (shown before the first paint while pending). */
|
|
63
|
+
skeleton?: ReactNode;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Builtins — the two layout nodes the surface draws itself, on tokens only.
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
const GAP: Record<string, string> = {
|
|
70
|
+
none: "gap-0",
|
|
71
|
+
xs: "gap-1",
|
|
72
|
+
sm: "gap-2",
|
|
73
|
+
md: "gap-4",
|
|
74
|
+
lg: "gap-6",
|
|
75
|
+
};
|
|
76
|
+
const ALIGN: Record<string, string> = {
|
|
77
|
+
start: "items-start",
|
|
78
|
+
center: "items-center",
|
|
79
|
+
end: "items-end",
|
|
80
|
+
stretch: "items-stretch",
|
|
81
|
+
};
|
|
82
|
+
const JUSTIFY: Record<string, string> = {
|
|
83
|
+
start: "justify-start",
|
|
84
|
+
center: "justify-center",
|
|
85
|
+
end: "justify-end",
|
|
86
|
+
between: "justify-between",
|
|
87
|
+
};
|
|
88
|
+
const COLUMNS: Record<number, string> = {
|
|
89
|
+
1: "grid-cols-1",
|
|
90
|
+
2: "grid-cols-1 sm:grid-cols-2",
|
|
91
|
+
3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
|
|
92
|
+
4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4",
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
function builtinClass(type: string, props: Record<string, unknown>): string {
|
|
96
|
+
const gap = GAP[String(props.gap ?? "md")] ?? GAP.md;
|
|
97
|
+
if (type === "Grid") {
|
|
98
|
+
return cn("grid", COLUMNS[Number(props.columns ?? 2)] ?? COLUMNS[2], gap);
|
|
99
|
+
}
|
|
100
|
+
return cn(
|
|
101
|
+
"flex min-w-0",
|
|
102
|
+
props.direction === "row" ? "flex-row" : "flex-col",
|
|
103
|
+
props.wrap ? "flex-wrap" : "",
|
|
104
|
+
ALIGN[String(props.align ?? "stretch")],
|
|
105
|
+
JUSTIFY[String(props.justify ?? "start")],
|
|
106
|
+
gap,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// Tree rendering
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
interface RenderCtx {
|
|
114
|
+
catalog: A2uiCatalog;
|
|
115
|
+
invalid: Set<string>;
|
|
116
|
+
onAction?: A2uiActionHandler;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const isEvent = (v: unknown): v is SyntheticEvent =>
|
|
120
|
+
typeof v === "object" && v !== null && "nativeEvent" in v && "target" in v;
|
|
121
|
+
|
|
122
|
+
/** The value a `change`-style handler carries: the control's value, or the handler's first arg. */
|
|
123
|
+
function eventValue(args: unknown[]): unknown {
|
|
124
|
+
const first = args[0];
|
|
125
|
+
if (isEvent(first)) {
|
|
126
|
+
const target = first.target as HTMLInputElement | null;
|
|
127
|
+
if (target && typeof target.type === "string" && target.type === "checkbox")
|
|
128
|
+
return target.checked;
|
|
129
|
+
return target && "value" in target ? target.value : undefined;
|
|
130
|
+
}
|
|
131
|
+
return first;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function renderPropValue(value: A2uiPropValue, path: string, ctx: RenderCtx): unknown {
|
|
135
|
+
if (Array.isArray(value) && value.some(isA2uiElement)) {
|
|
136
|
+
return value.map((item, i) => renderNode(item as A2uiNode, `${path}[${i}]`, ctx));
|
|
137
|
+
}
|
|
138
|
+
if (isA2uiElement(value)) return renderNode(value, path, ctx);
|
|
139
|
+
return value;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function renderNode(node: A2uiNode, path: string, ctx: RenderCtx): ReactNode {
|
|
143
|
+
if (typeof node === "string" || typeof node === "number") return node;
|
|
144
|
+
if (ctx.invalid.has(path)) return null;
|
|
145
|
+
const entry = ctx.catalog[node.type];
|
|
146
|
+
if (!entry) return null;
|
|
147
|
+
const props: Record<string, unknown> = {};
|
|
148
|
+
for (const [name, value] of Object.entries(node.props ?? {})) {
|
|
149
|
+
const schema = entry.schema.props[name];
|
|
150
|
+
props[name] =
|
|
151
|
+
schema?.type === "node" ? renderPropValue(value, `${path}.props.${name}`, ctx) : value;
|
|
152
|
+
}
|
|
153
|
+
for (const [event, action] of Object.entries(node.on ?? {})) {
|
|
154
|
+
const handlerName = entry.schema.events[event];
|
|
155
|
+
if (!handlerName) continue;
|
|
156
|
+
props[handlerName] = (...args: unknown[]) => {
|
|
157
|
+
// A control's value travels only with `change`-style events; a click
|
|
158
|
+
// carries no value (a button's `target.value` is an empty string).
|
|
159
|
+
const value = event === "click" ? undefined : eventValue(args);
|
|
160
|
+
ctx.onAction?.(action, { event, value, node, path });
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const children = entry.schema.children
|
|
164
|
+
? (node.children ?? []).map((child, i) => renderNode(child, `${path}.children[${i}]`, ctx))
|
|
165
|
+
: undefined;
|
|
166
|
+
const key = path;
|
|
167
|
+
const dataAttrs = {
|
|
168
|
+
"data-a2ui-type": node.type,
|
|
169
|
+
...(node.id ? { "data-a2ui-id": node.id } : {}),
|
|
170
|
+
};
|
|
171
|
+
if (entry.schema.builtin) {
|
|
172
|
+
return createElement(
|
|
173
|
+
"div",
|
|
174
|
+
{ key, className: builtinClass(node.type, props), ...dataAttrs },
|
|
175
|
+
...(children ?? []),
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
return createElement(
|
|
179
|
+
entry.component as never,
|
|
180
|
+
{ key, ...props, ...dataAttrs },
|
|
181
|
+
...(children ?? []),
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** A memoised subtree so a streaming parent re-render does not redraw settled siblings. */
|
|
186
|
+
const SurfaceTree = memo(function SurfaceTree({
|
|
187
|
+
spec,
|
|
188
|
+
catalog,
|
|
189
|
+
invalid,
|
|
190
|
+
onAction,
|
|
191
|
+
}: {
|
|
192
|
+
spec: A2uiSurfaceSpec;
|
|
193
|
+
catalog: A2uiCatalog;
|
|
194
|
+
invalid: Set<string>;
|
|
195
|
+
onAction?: A2uiActionHandler;
|
|
196
|
+
}) {
|
|
197
|
+
return <>{renderNode(spec.root, "root", { catalog, invalid, onAction })}</>;
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// The surface
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
export const A2uiSurface = forwardRef<HTMLDivElement, A2uiSurfaceProps>(function A2uiSurface(
|
|
204
|
+
{
|
|
205
|
+
surface,
|
|
206
|
+
catalog = uiCatalog,
|
|
207
|
+
onAction,
|
|
208
|
+
isStreaming = false,
|
|
209
|
+
loading = false,
|
|
210
|
+
onError,
|
|
211
|
+
skeleton,
|
|
212
|
+
className,
|
|
213
|
+
...props
|
|
214
|
+
},
|
|
215
|
+
ref,
|
|
216
|
+
) {
|
|
217
|
+
const { t } = useLocale();
|
|
218
|
+
const schema = useMemo(() => catalogSchema(catalog), [catalog]);
|
|
219
|
+
|
|
220
|
+
// Parse (completing a streaming prefix), then validate. A string that is not
|
|
221
|
+
// yet valid JSON even after completion is "incomplete", not invalid.
|
|
222
|
+
const parsed = useMemo(() => {
|
|
223
|
+
if (loading || surface == null) return { value: null, incomplete: false };
|
|
224
|
+
if (typeof surface !== "string") return { value: surface, incomplete: false };
|
|
225
|
+
const complete = parseSurfaceJson(surface, false);
|
|
226
|
+
if (complete !== null) return { value: complete, incomplete: false };
|
|
227
|
+
const partial = parseSurfaceJson(surface, true);
|
|
228
|
+
return { value: partial, incomplete: surface.trim() !== "" };
|
|
229
|
+
}, [surface, loading]);
|
|
230
|
+
|
|
231
|
+
const validation = useMemo(
|
|
232
|
+
() => (parsed.value === null ? null : validateA2uiSurface(parsed.value, schema)),
|
|
233
|
+
[parsed.value, schema],
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const pending = loading || isStreaming || parsed.incomplete;
|
|
237
|
+
const empty = surface == null || (typeof surface === "string" && surface.trim() === "");
|
|
238
|
+
|
|
239
|
+
// While pending, prune the failing subtrees and draw the rest; a settled
|
|
240
|
+
// surface is all-or-nothing.
|
|
241
|
+
const invalid = useMemo(
|
|
242
|
+
() =>
|
|
243
|
+
validation && !validation.ok && pending
|
|
244
|
+
? invalidNodePaths(validation.errors)
|
|
245
|
+
: new Set<string>(),
|
|
246
|
+
[validation, pending],
|
|
247
|
+
);
|
|
248
|
+
const drawable =
|
|
249
|
+
validation?.spec && (validation.ok || (pending && !invalid.has("root")))
|
|
250
|
+
? validation.spec
|
|
251
|
+
: null;
|
|
252
|
+
|
|
253
|
+
// The last tree that painted, so an unparsable streaming chunk (mid-token) or
|
|
254
|
+
// a momentarily root-invalid prefix keeps the previous frame on screen.
|
|
255
|
+
const lastGoodRef = useRef<A2uiSurfaceSpec | null>(null);
|
|
256
|
+
if (drawable) lastGoodRef.current = drawable;
|
|
257
|
+
const shown = drawable ?? (pending ? lastGoodRef.current : null);
|
|
258
|
+
const [hasRendered, setHasRendered] = useState(false);
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
if (shown && !hasRendered) setHasRendered(true);
|
|
261
|
+
}, [shown, hasRendered]);
|
|
262
|
+
|
|
263
|
+
const status: A2uiSurfaceStatus = loading
|
|
264
|
+
? "pending"
|
|
265
|
+
: empty
|
|
266
|
+
? hasRendered
|
|
267
|
+
? "ready"
|
|
268
|
+
: "idle"
|
|
269
|
+
: pending
|
|
270
|
+
? "pending"
|
|
271
|
+
: validation && !validation.ok
|
|
272
|
+
? "error"
|
|
273
|
+
: "ready";
|
|
274
|
+
|
|
275
|
+
// Terminal errors only — once per settled, invalid surface.
|
|
276
|
+
// Keyed by the error LIST, not the input's identity, so a parent re-render
|
|
277
|
+
// that passes an equal object literal does not report the same failure twice.
|
|
278
|
+
const reportedRef = useRef<string | null>(null);
|
|
279
|
+
useEffect(() => {
|
|
280
|
+
if (status !== "error" || !validation || validation.ok) return;
|
|
281
|
+
const key = JSON.stringify(validation.errors);
|
|
282
|
+
if (reportedRef.current === key) return;
|
|
283
|
+
reportedRef.current = key;
|
|
284
|
+
onError?.(validation.errors);
|
|
285
|
+
}, [status, validation, onError]);
|
|
286
|
+
|
|
287
|
+
const showSkeleton = status === "pending" && !shown;
|
|
288
|
+
const title = shown?.title;
|
|
289
|
+
|
|
290
|
+
return (
|
|
291
|
+
<div
|
|
292
|
+
ref={ref}
|
|
293
|
+
data-slot="a2ui-surface"
|
|
294
|
+
data-status={status}
|
|
295
|
+
className={cn("relative min-w-0", className)}
|
|
296
|
+
{...(title ? { role: "group", "aria-label": title } : {})}
|
|
297
|
+
{...props}
|
|
298
|
+
>
|
|
299
|
+
{showSkeleton ? (
|
|
300
|
+
<div
|
|
301
|
+
data-slot="a2ui-surface-skeleton"
|
|
302
|
+
role="status"
|
|
303
|
+
aria-live="polite"
|
|
304
|
+
className="space-y-2"
|
|
305
|
+
>
|
|
306
|
+
<span className="sr-only">{t("ai.a2ui.loading")}</span>
|
|
307
|
+
{skeleton ?? (
|
|
308
|
+
<>
|
|
309
|
+
<Skeleton className="h-6 w-1/3" />
|
|
310
|
+
<div className="grid grid-cols-3 gap-2">
|
|
311
|
+
<Skeleton className="h-16" />
|
|
312
|
+
<Skeleton className="h-16" />
|
|
313
|
+
<Skeleton className="h-16" />
|
|
314
|
+
</div>
|
|
315
|
+
<Skeleton className="h-24 w-full" />
|
|
316
|
+
</>
|
|
317
|
+
)}
|
|
318
|
+
</div>
|
|
319
|
+
) : null}
|
|
320
|
+
{shown ? (
|
|
321
|
+
<SurfaceTree spec={shown} catalog={catalog} invalid={invalid} onAction={onAction} />
|
|
322
|
+
) : null}
|
|
323
|
+
{status === "error" && validation && !validation.ok ? (
|
|
324
|
+
<div
|
|
325
|
+
data-slot="a2ui-surface-error"
|
|
326
|
+
role="alert"
|
|
327
|
+
// The status wash is the one separation gesture (surface-separation);
|
|
328
|
+
// `-text` is the AA ink rung on a wash (status-rung).
|
|
329
|
+
className="flex gap-2 rounded-md bg-destructive/10 p-3 text-body text-destructive-text"
|
|
330
|
+
>
|
|
331
|
+
<AlertCircle className="mt-0.5 size-4 shrink-0" aria-hidden="true" />
|
|
332
|
+
<div className="min-w-0 space-y-1">
|
|
333
|
+
<p className="font-medium">{t("ai.a2ui.invalid")}</p>
|
|
334
|
+
<ul className="space-y-0.5 text-caption">
|
|
335
|
+
{validation.errors.slice(0, 8).map((e) => (
|
|
336
|
+
<li key={`${e.path}:${e.code}`} className="break-words">
|
|
337
|
+
<code className="text-code">{e.path || "root"}</code> — {e.message}
|
|
338
|
+
</li>
|
|
339
|
+
))}
|
|
340
|
+
{validation.errors.length > 8 ? (
|
|
341
|
+
<li>{t("ai.a2ui.moreErrors", { count: validation.errors.length - 8 })}</li>
|
|
342
|
+
) : null}
|
|
343
|
+
</ul>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
) : null}
|
|
347
|
+
</div>
|
|
348
|
+
);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
A2uiSurface.displayName = "A2uiSurface";
|
|
352
|
+
|
|
353
|
+
/** Re-exported so a host can pre-complete a streaming prefix itself (e.g. for a raw-JSON view). */
|
|
354
|
+
export { completeJson };
|