@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
package/src/tool.stories.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { expect, userEvent } from "storybook/test";
|
|
2
3
|
import {
|
|
3
4
|
Tool,
|
|
4
5
|
ToolContent,
|
|
@@ -68,3 +69,41 @@ export const Streaming: Story = {
|
|
|
68
69
|
);
|
|
69
70
|
},
|
|
70
71
|
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Focus indicator on ToolDetails trigger (#313): Tab to the disclosure
|
|
75
|
+
* trigger and assert the compound indicator's OUTLINE layer. The prior
|
|
76
|
+
* version read `buttons[0]` — but `ToolHeader` (rendered before `ToolDetails`
|
|
77
|
+
* in the DOM) is ALSO a `CollapsibleTrigger` (`tool.tsx:98`, the outer
|
|
78
|
+
* `Tool` row's own disclosure) with no focus styling of its own, so it is
|
|
79
|
+
* `buttons[0]` and receives the first Tab, not the `ToolDetails` trigger
|
|
80
|
+
* this issue is about (`tool.tsx:152`). Query the actual trigger by its
|
|
81
|
+
* accessible name (`t("ai.tool.showTechnicalDetails")`) instead of by index.
|
|
82
|
+
*/
|
|
83
|
+
export const FocusIndicator: Story = {
|
|
84
|
+
render: () => (
|
|
85
|
+
// defaultOpen on the OUTER Tool — its ToolContent (and so ToolDetails'
|
|
86
|
+
// trigger) is hidden/unmounted when the outer Collapsible starts closed,
|
|
87
|
+
// as it does without this prop (see Default story). Without it the
|
|
88
|
+
// ToolDetails trigger this test targets isn't in the accessibility tree
|
|
89
|
+
// at all, and `getByRole` throws before ever reaching the focus check.
|
|
90
|
+
<Tool defaultOpen className="max-w-prose">
|
|
91
|
+
<ToolHeader type="tool-fetch" state="output-available" summary="Fetched data" />
|
|
92
|
+
<ToolContent>
|
|
93
|
+
<ToolDetails>
|
|
94
|
+
<ToolInput input={{ url: "https://example.com" }} />
|
|
95
|
+
<ToolOutput output={{ status: "ok" }} />
|
|
96
|
+
</ToolDetails>
|
|
97
|
+
</ToolContent>
|
|
98
|
+
</Tool>
|
|
99
|
+
),
|
|
100
|
+
play: async ({ canvas }) => {
|
|
101
|
+
const trigger = canvas.getByRole("button", { name: /show technical details/i });
|
|
102
|
+
trigger.focus();
|
|
103
|
+
await expect(trigger).toHaveFocus();
|
|
104
|
+
const focused = getComputedStyle(trigger);
|
|
105
|
+
await expect(focused.boxShadow).not.toBe("none");
|
|
106
|
+
await expect(focused.outlineStyle).toBe("solid");
|
|
107
|
+
await expect(parseFloat(focused.outlineWidth)).toBeGreaterThan(0);
|
|
108
|
+
},
|
|
109
|
+
};
|
package/src/tool.test.tsx
CHANGED
|
@@ -61,3 +61,68 @@ describe("ToolOutput isStreaming (#269, loading-states.md)", () => {
|
|
|
61
61
|
expect(container).toBeEmptyDOMElement();
|
|
62
62
|
});
|
|
63
63
|
});
|
|
64
|
+
|
|
65
|
+
// A tool that returns a count of 0, a boolean check of false, or an empty
|
|
66
|
+
// string is a REAL, defined result — only `undefined` means "no output yet".
|
|
67
|
+
// The old `!(output || errorText || isStreaming)` guard treated all three as
|
|
68
|
+
// "nothing to show" and rendered null outright.
|
|
69
|
+
describe("ToolOutput — defined-but-falsy output renders (review #1)", () => {
|
|
70
|
+
it("renders a numeric 0 result instead of returning null", () => {
|
|
71
|
+
render(<ToolOutput output={0} errorText={undefined} />);
|
|
72
|
+
expect(screen.getByText("Result")).toBeInTheDocument();
|
|
73
|
+
expect(screen.getByText("0")).toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("renders a boolean false result instead of returning null", () => {
|
|
77
|
+
const { container } = render(<ToolOutput output={false} errorText={undefined} />);
|
|
78
|
+
expect(screen.getByText("Result")).toBeInTheDocument();
|
|
79
|
+
expect(container.querySelector('[data-language="json"]')).not.toBeNull();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("renders an empty-string result (not the pending skeleton, not null)", () => {
|
|
83
|
+
const { container } = render(<ToolOutput output="" errorText={undefined} />);
|
|
84
|
+
expect(screen.getByText("Result")).toBeInTheDocument();
|
|
85
|
+
expect(container.querySelector('[role="status"]')).toBeNull();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("ToolOutput — string output renders as plain text, not forced JSON (review #1)", () => {
|
|
90
|
+
it("renders a plain-text (non-JSON) string result without the JSON highlighter", () => {
|
|
91
|
+
const { container } = render(
|
|
92
|
+
<ToolOutput output="just a plain sentence, not json" errorText={undefined} />,
|
|
93
|
+
);
|
|
94
|
+
expect(screen.getByText("just a plain sentence, not json")).toBeInTheDocument();
|
|
95
|
+
expect(container.querySelector('[data-language="text"]')).not.toBeNull();
|
|
96
|
+
expect(container.querySelector('[data-language="json"]')).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("still renders an object result through the JSON highlighter", () => {
|
|
100
|
+
const { container } = render(<ToolOutput output={{ ok: true }} errorText={undefined} />);
|
|
101
|
+
expect(container.querySelector('[data-language="json"]')).not.toBeNull();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("ToolOutput/ToolInput — JSON.stringify never crashes the render (review #1)", () => {
|
|
106
|
+
it("falls back gracefully for a circular output object instead of throwing", () => {
|
|
107
|
+
const circular: Record<string, unknown> = { name: "circular" };
|
|
108
|
+
circular.self = circular;
|
|
109
|
+
|
|
110
|
+
expect(() => render(<ToolOutput output={circular} errorText={undefined} />)).not.toThrow();
|
|
111
|
+
expect(screen.getByText("Result")).toBeInTheDocument();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("renders a BigInt field in the output instead of throwing", () => {
|
|
115
|
+
expect(() =>
|
|
116
|
+
render(<ToolOutput output={{ total: 10n }} errorText={undefined} />),
|
|
117
|
+
).not.toThrow();
|
|
118
|
+
expect(screen.getByText("Result")).toBeInTheDocument();
|
|
119
|
+
expect(screen.getByText(/10n/)).toBeInTheDocument();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("falls back gracefully for circular tool input instead of throwing", () => {
|
|
123
|
+
const circular: Record<string, unknown> = { query: "x" };
|
|
124
|
+
circular.self = circular;
|
|
125
|
+
|
|
126
|
+
expect(() => render(<ToolInput input={circular} />)).not.toThrow();
|
|
127
|
+
});
|
|
128
|
+
});
|
package/src/tool.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import type { DynamicToolUIPart, ToolUIPart } from "ai";
|
|
|
7
7
|
import { ChevronDownIcon, WrenchIcon } from "lucide-react";
|
|
8
8
|
import type { ComponentProps, ReactNode } from "react";
|
|
9
9
|
import { isValidElement } from "react";
|
|
10
|
+
import type { BundledLanguage } from "shiki";
|
|
10
11
|
|
|
11
12
|
import { CodeBlock } from "./code-block";
|
|
12
13
|
|
|
@@ -21,6 +22,19 @@ export const Tool = ({ className, ...props }: ToolProps) => (
|
|
|
21
22
|
|
|
22
23
|
export type ToolPart = ToolUIPart | DynamicToolUIPart;
|
|
23
24
|
|
|
25
|
+
/**
|
|
26
|
+
* `JSON.stringify` throws on a circular reference or a `BigInt` — both
|
|
27
|
+
* realistic shapes for tool input/output payloads a model produced. Falls
|
|
28
|
+
* back to a readable placeholder instead of crashing the message render.
|
|
29
|
+
*/
|
|
30
|
+
function safeJsonStringify(value: unknown): string {
|
|
31
|
+
try {
|
|
32
|
+
return JSON.stringify(value, (_key, v) => (typeof v === "bigint" ? `${v.toString()}n` : v), 2);
|
|
33
|
+
} catch {
|
|
34
|
+
return String(value);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
24
38
|
export type ToolHeaderProps = {
|
|
25
39
|
title?: string;
|
|
26
40
|
/**
|
|
@@ -123,23 +137,26 @@ export type ToolDetailsProps = ComponentProps<typeof Collapsible> & {
|
|
|
123
137
|
*/
|
|
124
138
|
export const ToolDetails = ({
|
|
125
139
|
className,
|
|
126
|
-
label
|
|
140
|
+
label,
|
|
127
141
|
defaultOpen = false,
|
|
128
142
|
children,
|
|
129
143
|
...props
|
|
130
|
-
}: ToolDetailsProps) =>
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
144
|
+
}: ToolDetailsProps) => {
|
|
145
|
+
const { t } = useLocale();
|
|
146
|
+
return (
|
|
147
|
+
<Collapsible
|
|
148
|
+
className={cn("group/tool-details not-prose", className)}
|
|
149
|
+
defaultOpen={defaultOpen}
|
|
150
|
+
{...props}
|
|
151
|
+
>
|
|
152
|
+
<CollapsibleTrigger className="flex items-center gap-1 rounded-sm text-meta text-muted-foreground transition-colors hover:text-foreground focus-ring">
|
|
153
|
+
<ChevronDownIcon className="size-3.5 transition-transform group-data-[state=open]/tool-details:rotate-180" />
|
|
154
|
+
{label ?? t("ai.tool.showTechnicalDetails")}
|
|
155
|
+
</CollapsibleTrigger>
|
|
156
|
+
<CollapsibleContent className="mt-3 space-y-4">{children}</CollapsibleContent>
|
|
157
|
+
</Collapsible>
|
|
158
|
+
);
|
|
159
|
+
};
|
|
143
160
|
|
|
144
161
|
export type ToolInputProps = ComponentProps<"div"> & {
|
|
145
162
|
input: ToolPart["input"];
|
|
@@ -149,11 +166,11 @@ export const ToolInput = ({ className, input, ...props }: ToolInputProps) => {
|
|
|
149
166
|
const { t } = useLocale();
|
|
150
167
|
return (
|
|
151
168
|
<div className={cn("space-y-2 overflow-hidden", className)} {...props}>
|
|
152
|
-
<h4 className="text-
|
|
169
|
+
<h4 className="text-eyebrow uppercase text-muted-foreground">
|
|
153
170
|
{t("ai.schemaDisplay.parameters")}
|
|
154
171
|
</h4>
|
|
155
172
|
<div className="rounded-md bg-muted/50">
|
|
156
|
-
<CodeBlock code={
|
|
173
|
+
<CodeBlock code={safeJsonStringify(input)} language="json" />
|
|
157
174
|
</div>
|
|
158
175
|
</div>
|
|
159
176
|
);
|
|
@@ -197,25 +214,44 @@ export const ToolOutput = ({
|
|
|
197
214
|
}: ToolOutputProps) => {
|
|
198
215
|
const { t } = useLocale();
|
|
199
216
|
|
|
200
|
-
|
|
217
|
+
// `output` is a defined-but-falsy result (`0`, `false`, `""`) for plenty of
|
|
218
|
+
// real tools (a count, a boolean check, an empty-string field) — only
|
|
219
|
+
// `undefined` means "no output (yet)".
|
|
220
|
+
const hasOutput = output !== undefined;
|
|
221
|
+
|
|
222
|
+
if (!(hasOutput || errorText || isStreaming)) {
|
|
201
223
|
return null;
|
|
202
224
|
}
|
|
203
225
|
|
|
204
226
|
const showError = !isStreaming && Boolean(errorText);
|
|
205
|
-
const pending = isStreaming && !
|
|
227
|
+
const pending = isStreaming && !hasOutput && !errorText;
|
|
206
228
|
|
|
207
|
-
let Output =
|
|
229
|
+
let Output: ReactNode = null;
|
|
208
230
|
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
231
|
+
if (hasOutput) {
|
|
232
|
+
if (isValidElement(output)) {
|
|
233
|
+
Output = output;
|
|
234
|
+
} else if (typeof output === "string") {
|
|
235
|
+
// A tool result is arbitrary text, not guaranteed JSON — forcing the
|
|
236
|
+
// JSON highlighter on it mis-colours ordinary strings. Shiki's own
|
|
237
|
+
// `BundledLanguage` union (grammar-backed languages) omits its
|
|
238
|
+
// hard-coded plain-text pseudo-languages (`isPlainLang`:
|
|
239
|
+
// "plaintext" | "txt" | "text" | "plain") — `createHighlighter`/
|
|
240
|
+
// `codeToTokens` accept and special-case them with no grammar load
|
|
241
|
+
// (verified: `getLoadedLanguages()` stays empty, no throw), so this
|
|
242
|
+
// is a type-only gap, not a runtime one.
|
|
243
|
+
Output = <CodeBlock code={output} language={"text" as BundledLanguage} />;
|
|
244
|
+
} else {
|
|
245
|
+
// Objects, arrays, numbers, booleans, null, bigint — all safe to
|
|
246
|
+
// stringify for display.
|
|
247
|
+
Output = <CodeBlock code={safeJsonStringify(output)} language="json" />;
|
|
248
|
+
}
|
|
213
249
|
}
|
|
214
250
|
|
|
215
251
|
return (
|
|
216
252
|
<div className={cn("space-y-2", className)} {...props}>
|
|
217
|
-
<h4 className="text-
|
|
218
|
-
{showError ? "
|
|
253
|
+
<h4 className="text-eyebrow uppercase text-muted-foreground">
|
|
254
|
+
{showError ? t("ai.tool.error") : t("ai.tool.result")}
|
|
219
255
|
</h4>
|
|
220
256
|
{pending ? (
|
|
221
257
|
<div className="space-y-2 rounded-md bg-muted/50 p-3" role="status" aria-live="polite">
|
package/src/toolbar.tsx
CHANGED
|
@@ -32,13 +32,3 @@ export const NodeToolbar = (props: NodeToolbarProps) => (
|
|
|
32
32
|
<NodeToolbarImpl data-slot="node-toolbar" {...props} />
|
|
33
33
|
</Suspense>
|
|
34
34
|
);
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* @deprecated Renamed to {@link NodeToolbar}. `Toolbar` is now the WAI-ARIA
|
|
38
|
-
* toolbar in `@elabs-ai/components-ui`. This alias will be removed in
|
|
39
|
-
* the next major.
|
|
40
|
-
*/
|
|
41
|
-
export const Toolbar = NodeToolbar;
|
|
42
|
-
|
|
43
|
-
/** @deprecated Renamed to {@link NodeToolbarProps}. */
|
|
44
|
-
export type ToolbarProps = NodeToolbarProps;
|
package/src/transcription.tsx
CHANGED
|
@@ -54,7 +54,7 @@ export const Transcription = ({
|
|
|
54
54
|
return (
|
|
55
55
|
<TranscriptionContext.Provider value={contextValue}>
|
|
56
56
|
<div
|
|
57
|
-
className={cn("flex flex-wrap gap-1 text-
|
|
57
|
+
className={cn("flex flex-wrap gap-1 text-body leading-relaxed", className)}
|
|
58
58
|
data-slot="transcription"
|
|
59
59
|
{...props}
|
|
60
60
|
>
|
package/src/voice-selector.tsx
CHANGED
|
@@ -197,7 +197,7 @@ export const VoiceSelectorGender = ({
|
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
return (
|
|
200
|
-
<span className={cn("text-muted-foreground text-
|
|
200
|
+
<span className={cn("text-muted-foreground text-meta", className)} {...props}>
|
|
201
201
|
{children ?? icon}
|
|
202
202
|
</span>
|
|
203
203
|
);
|
|
@@ -373,7 +373,7 @@ export const VoiceSelectorAccent = ({
|
|
|
373
373
|
}
|
|
374
374
|
|
|
375
375
|
return (
|
|
376
|
-
<span className={cn("text-muted-foreground text-
|
|
376
|
+
<span className={cn("text-muted-foreground text-meta", className)} {...props}>
|
|
377
377
|
{children ?? emoji}
|
|
378
378
|
</span>
|
|
379
379
|
);
|
|
@@ -382,7 +382,7 @@ export const VoiceSelectorAccent = ({
|
|
|
382
382
|
export type VoiceSelectorAgeProps = ComponentProps<"span">;
|
|
383
383
|
|
|
384
384
|
export const VoiceSelectorAge = ({ className, ...props }: VoiceSelectorAgeProps) => (
|
|
385
|
-
<span className={cn("text-muted-foreground text-
|
|
385
|
+
<span className={cn("text-muted-foreground text-meta tabular-nums", className)} {...props} />
|
|
386
386
|
);
|
|
387
387
|
|
|
388
388
|
export type VoiceSelectorNameProps = ComponentProps<"span">;
|
|
@@ -397,7 +397,7 @@ export const VoiceSelectorDescription = ({
|
|
|
397
397
|
className,
|
|
398
398
|
...props
|
|
399
399
|
}: VoiceSelectorDescriptionProps) => (
|
|
400
|
-
<span className={cn("text-muted-foreground text-
|
|
400
|
+
<span className={cn("text-muted-foreground text-meta", className)} {...props} />
|
|
401
401
|
);
|
|
402
402
|
|
|
403
403
|
export type VoiceSelectorAttributesProps = ComponentProps<"div">;
|
|
@@ -407,7 +407,7 @@ export const VoiceSelectorAttributes = ({
|
|
|
407
407
|
children,
|
|
408
408
|
...props
|
|
409
409
|
}: VoiceSelectorAttributesProps) => (
|
|
410
|
-
<div className={cn("flex items-center text-
|
|
410
|
+
<div className={cn("flex items-center text-meta", className)} {...props}>
|
|
411
411
|
{children}
|
|
412
412
|
</div>
|
|
413
413
|
);
|
package/src/web-preview.test.tsx
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
3
|
+
import { LocaleProvider } from "@elabs-ai/components-ui";
|
|
3
4
|
import {
|
|
4
5
|
WebPreview,
|
|
5
6
|
WebPreviewBody,
|
|
7
|
+
WebPreviewConsole,
|
|
6
8
|
WebPreviewNavigation,
|
|
7
9
|
WebPreviewNavigationButton,
|
|
8
10
|
WebPreviewUrl,
|
|
@@ -115,3 +117,51 @@ describe("WebPreviewNavigationButton accessible name (#386)", () => {
|
|
|
115
117
|
expect(screen.getByRole("button", { name: "Back" })).toBeInTheDocument();
|
|
116
118
|
});
|
|
117
119
|
});
|
|
120
|
+
|
|
121
|
+
// 1.3 — `allow-same-origin` combined with `allow-scripts` lets a same-origin
|
|
122
|
+
// previewed page break out of the sandbox entirely. It must be opt-in.
|
|
123
|
+
describe("WebPreviewBody sandbox (#1.3)", () => {
|
|
124
|
+
it("never includes allow-same-origin by default", () => {
|
|
125
|
+
const { container } = render(
|
|
126
|
+
<WebPreview>
|
|
127
|
+
<WebPreviewBody />
|
|
128
|
+
</WebPreview>,
|
|
129
|
+
);
|
|
130
|
+
const iframe = container.querySelector("iframe");
|
|
131
|
+
expect(iframe).not.toBeNull();
|
|
132
|
+
expect(iframe?.getAttribute("sandbox")).not.toContain("allow-same-origin");
|
|
133
|
+
// The other, non-origin-escaping permissions still apply.
|
|
134
|
+
expect(iframe?.getAttribute("sandbox")).toContain("allow-scripts");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("adds allow-same-origin only when explicitly opted in", () => {
|
|
138
|
+
const { container } = render(
|
|
139
|
+
<WebPreview>
|
|
140
|
+
<WebPreviewBody allowSameOrigin />
|
|
141
|
+
</WebPreview>,
|
|
142
|
+
);
|
|
143
|
+
const iframe = container.querySelector("iframe");
|
|
144
|
+
expect(iframe?.getAttribute("sandbox")).toContain("allow-same-origin");
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe("WebPreviewConsole timestamp formatting (#1.3)", () => {
|
|
149
|
+
it("formats the log timestamp through the active locale, not the JS-engine default", () => {
|
|
150
|
+
const timestamp = new Date("2024-03-04T15:30:00Z");
|
|
151
|
+
render(
|
|
152
|
+
<LocaleProvider locale="de-DE">
|
|
153
|
+
<WebPreview>
|
|
154
|
+
<WebPreviewConsole logs={[{ level: "log", message: "started", timestamp }]} />
|
|
155
|
+
</WebPreview>
|
|
156
|
+
</LocaleProvider>,
|
|
157
|
+
);
|
|
158
|
+
// The console body is collapsed by default — open it before asserting.
|
|
159
|
+
fireEvent.click(screen.getByRole("button", { name: "Console" }));
|
|
160
|
+
const expected = new Intl.DateTimeFormat("de-DE", {
|
|
161
|
+
hour: "2-digit",
|
|
162
|
+
minute: "2-digit",
|
|
163
|
+
second: "2-digit",
|
|
164
|
+
}).format(timestamp);
|
|
165
|
+
expect(screen.getByText(expected)).toBeInTheDocument();
|
|
166
|
+
});
|
|
167
|
+
});
|
package/src/web-preview.tsx
CHANGED
|
@@ -158,7 +158,7 @@ export const WebPreviewUrl = ({ value, onChange, onKeyDown, ...props }: WebPrevi
|
|
|
158
158
|
|
|
159
159
|
return (
|
|
160
160
|
<Input
|
|
161
|
-
className="h-8 flex-1 text-
|
|
161
|
+
className="h-8 flex-1 text-body"
|
|
162
162
|
onChange={onChange ?? handleChange}
|
|
163
163
|
onKeyDown={handleKeyDown}
|
|
164
164
|
placeholder={t("ai.webPreview.urlPlaceholder")}
|
|
@@ -180,20 +180,45 @@ export type WebPreviewBodyProps = Omit<ComponentProps<"iframe">, "loading"> & {
|
|
|
180
180
|
* @default false
|
|
181
181
|
*/
|
|
182
182
|
loading?: boolean | ReactNode;
|
|
183
|
+
/**
|
|
184
|
+
* Adds `allow-same-origin` to the iframe sandbox. **Opt-in, off by
|
|
185
|
+
* default**: combined with `allow-scripts`, a same-origin previewed page
|
|
186
|
+
* can script its way out of the sandbox restrictions entirely (its own
|
|
187
|
+
* `<iframe>` reads as unsandboxed once it shares the origin). Only enable
|
|
188
|
+
* this for content you trust to be same-origin-safe.
|
|
189
|
+
* @default false
|
|
190
|
+
*/
|
|
191
|
+
allowSameOrigin?: boolean;
|
|
183
192
|
};
|
|
184
193
|
|
|
185
|
-
export const WebPreviewBody = ({
|
|
194
|
+
export const WebPreviewBody = ({
|
|
195
|
+
className,
|
|
196
|
+
loading,
|
|
197
|
+
src,
|
|
198
|
+
allowSameOrigin = false,
|
|
199
|
+
...props
|
|
200
|
+
}: WebPreviewBodyProps) => {
|
|
186
201
|
const { url } = useWebPreview();
|
|
187
202
|
const { t } = useLocale();
|
|
188
203
|
|
|
204
|
+
const sandbox = [
|
|
205
|
+
"allow-scripts",
|
|
206
|
+
"allow-forms",
|
|
207
|
+
"allow-popups",
|
|
208
|
+
"allow-presentation",
|
|
209
|
+
allowSameOrigin && "allow-same-origin",
|
|
210
|
+
]
|
|
211
|
+
.filter(Boolean)
|
|
212
|
+
.join(" ");
|
|
213
|
+
|
|
189
214
|
return (
|
|
190
215
|
<div className="relative flex-1">
|
|
191
216
|
<iframe
|
|
192
217
|
className={cn("size-full", className)}
|
|
193
218
|
// oxlint-disable-next-line eslint-plugin-react(iframe-missing-sandbox)
|
|
194
|
-
sandbox=
|
|
219
|
+
sandbox={sandbox}
|
|
195
220
|
src={(src ?? url) || undefined}
|
|
196
|
-
title="
|
|
221
|
+
title={t("ai.webPreview.previewTitle")}
|
|
197
222
|
{...props}
|
|
198
223
|
/>
|
|
199
224
|
{loading === true ? (
|
|
@@ -228,11 +253,11 @@ export const WebPreviewConsole = ({
|
|
|
228
253
|
...props
|
|
229
254
|
}: WebPreviewConsoleProps) => {
|
|
230
255
|
const { consoleOpen, setConsoleOpen } = useWebPreview();
|
|
231
|
-
const { t } = useLocale();
|
|
256
|
+
const { t, formatDate } = useLocale();
|
|
232
257
|
|
|
233
258
|
return (
|
|
234
259
|
<Collapsible
|
|
235
|
-
className={cn("border-t bg-muted/50 font-mono text-
|
|
260
|
+
className={cn("border-t bg-muted/50 font-mono text-body", className)}
|
|
236
261
|
onOpenChange={setConsoleOpen}
|
|
237
262
|
open={consoleOpen}
|
|
238
263
|
{...props}
|
|
@@ -264,7 +289,7 @@ export const WebPreviewConsole = ({
|
|
|
264
289
|
logs.map((log) => (
|
|
265
290
|
<div
|
|
266
291
|
className={cn(
|
|
267
|
-
"text-
|
|
292
|
+
"text-meta",
|
|
268
293
|
// #124: console log lines are running text — ink rung, not
|
|
269
294
|
// the mark rung. `text-yellow-600` was also a raw, untokened
|
|
270
295
|
// Tailwind palette colour (styling-and-tokens.md); replaced
|
|
@@ -275,7 +300,13 @@ export const WebPreviewConsole = ({
|
|
|
275
300
|
)}
|
|
276
301
|
key={`${log.timestamp.getTime()}-${log.level}-${log.message}`}
|
|
277
302
|
>
|
|
278
|
-
<span className="text-muted-foreground">
|
|
303
|
+
<span className="text-muted-foreground">
|
|
304
|
+
{formatDate(log.timestamp, {
|
|
305
|
+
hour: "2-digit",
|
|
306
|
+
minute: "2-digit",
|
|
307
|
+
second: "2-digit",
|
|
308
|
+
})}
|
|
309
|
+
</span>{" "}
|
|
279
310
|
{log.message}
|
|
280
311
|
</div>
|
|
281
312
|
))
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/_flow-boundary.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * The React Flow half of `@elabs-ai/components-ai`'s in-chat agent\n * workspace graph — `Canvas`, `Controls`, `Edge`, `Node`, `Panel`, `Toolbar` —\n * split out so the engine can be `lazy()`-loaded.\n *\n * `@xyflow/react` declares no `sideEffects`, so the six static imports it used to\n * have (one per public module, plus `@xyflow/react/dist/style.css`) put the whole\n * engine into the entry chunk of every consumer, canvas rendered or not. **The\n * stylesheet import has to live here too**: a bare\n * `import \"@xyflow/react/dist/style.css\"` in `canvas.tsx` keeps the edge alive on\n * its own.\n *\n * This is ONE boundary for all six parts on purpose (see `_flow-lazy.ts`): six\n * boundaries would be six chunks all pulling the same engine. The public modules\n * keep their `import type { … } from \"@xyflow/react\"` lines (types erase) and\n * their prop types, and render a `lazy()` wrapper over the implementations here.\n * `connection.tsx` is type-only already and needs no boundary.\n *\n * See ADR 0019 and `pnpm heavy-deps:check`.\n *\n * @lazy-boundary This module must only ever be reached via `import()`. The gate\n * fails if anything imports it statically, which would put React Flow back in\n * the entry chunk and make the `lazy()` pointless.\n */\nimport { Card } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport type { EdgeProps, InternalNode, Node as FlowNode } from \"@xyflow/react\";\nimport {\n Background,\n BaseEdge,\n Controls as ControlsPrimitive,\n getBezierPath,\n getSimpleBezierPath,\n Handle,\n NodeToolbar,\n Panel as PanelPrimitive,\n Position,\n ReactFlow,\n useInternalNode,\n} from \"@xyflow/react\";\n\nimport \"@xyflow/react/dist/style.css\";\n\nimport type { CanvasProps } from \"./canvas\";\nimport type { ControlsProps } from \"./controls\";\nimport type { NodeProps } from \"./node\";\nimport type { PanelProps } from \"./panel\";\nimport type { ToolbarProps } from \"./toolbar\";\n\nconst deleteKeyCode = [\"Backspace\", \"Delete\"];\n\n/**\n * The React Flow attribution badge is hidden on both canvas surfaces (here and\n * `@elabs-ai/components-flow`'s `CanvasShell`). `@xyflow/react` is\n * MIT — the licence requires the notice in source copies, not a rendered badge —\n * and xyflow asks that the badge only be hidden under a React Flow Pro\n * subscription. Keeping it is therefore a product/commercial decision, and this\n * repo's is to hide it. A consumer can restore it per-canvas with\n * `proOptions={{ hideAttribution: false }}`, which still wins via `...props`.\n */\nconst proOptions = { hideAttribution: true };\n\nexport const Canvas = ({ children, ...props }: CanvasProps) => (\n <ReactFlow\n deleteKeyCode={deleteKeyCode}\n fitView\n panOnDrag={false}\n panOnScroll\n proOptions={proOptions}\n selectionOnDrag={true}\n zoomOnDoubleClick={false}\n {...props}\n >\n <Background bgColor=\"var(--sidebar)\" />\n {children}\n </ReactFlow>\n);\n\nexport const Controls = ({ className, ...props }: ControlsProps) => (\n <ControlsPrimitive\n className={cn(\n \"gap-px overflow-hidden rounded-md border bg-card p-1 shadow-none!\",\n \"[&>button]:rounded-md [&>button]:border-none! [&>button]:bg-transparent! [&>button]:hover:bg-secondary!\",\n className,\n )}\n {...props}\n />\n);\n\nexport const EdgeTemporary = ({\n id,\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition,\n targetPosition,\n}: EdgeProps) => {\n const [edgePath] = getSimpleBezierPath({\n sourcePosition,\n sourceX,\n sourceY,\n targetPosition,\n targetX,\n targetY,\n });\n\n return (\n <BaseEdge\n className=\"stroke-1 stroke-ring\"\n id={id}\n path={edgePath}\n style={{\n strokeDasharray: \"5, 5\",\n }}\n />\n );\n};\n\n/**\n * The point on `node` where an edge of `handleType` should attach, plus the side\n * it leaves from.\n *\n * Read from React Flow's **measured** `handleBounds` — the DOM box of the\n * painted dot — and taken at the box's OUTER edge, which is React Flow's own\n * convention (the anchor lands on the dot's rim, which keeps a `markerEnd`\n * visible).\n *\n * The two fallbacks matter as much as the happy path. `preferred` is only a\n * preference: `nodeTypes` is an open prop, so a consumer node may legitimately\n * put its handles on the top and bottom, and the shipped `Node`'s left/right\n * pair is a convenience rather than a constraint. And `handleBounds` is empty\n * until React Flow's first measurement pass, which on this canvas is a real\n * window because the engine arrives in a lazy chunk (ADR 0019). Both used to\n * resolve to `[0, 0]` — the CANVAS ORIGIN — so the edge was drawn hundreds of\n * pixels from either node (measured at 498.8px in the `VerticalHandles` story).\n * Falling back to any handle of the right type, and then to the node's own\n * border, keeps the line on the node in every case.\n */\nconst getHandleAnchor = (\n node: InternalNode<FlowNode>,\n handleType: \"source\" | \"target\",\n preferred: Position,\n) => {\n const bounds = node.internals.handleBounds?.[handleType] ?? [];\n const handle = bounds.find((h) => h.position === preferred) ?? bounds[0];\n const origin = node.internals.positionAbsolute;\n\n if (handle) {\n // Offset to the handle box's outer edge on its own side; the other axis is\n // centred.\n const offsetX =\n handle.position === Position.Left\n ? 0\n : handle.position === Position.Right\n ? handle.width\n : handle.width / 2;\n const offsetY =\n handle.position === Position.Top\n ? 0\n : handle.position === Position.Bottom\n ? handle.height\n : handle.height / 2;\n\n return {\n position: handle.position,\n x: origin.x + handle.x + offsetX,\n y: origin.y + handle.y + offsetY,\n };\n }\n\n // No measured handle at all — anchor on the node's own border midpoint.\n const width = node.measured.width ?? 0;\n const height = node.measured.height ?? 0;\n switch (preferred) {\n case Position.Left:\n return { position: preferred, x: origin.x, y: origin.y + height / 2 };\n case Position.Right:\n return { position: preferred, x: origin.x + width, y: origin.y + height / 2 };\n case Position.Top:\n return { position: preferred, x: origin.x + width / 2, y: origin.y };\n default:\n return { position: preferred, x: origin.x + width / 2, y: origin.y + height };\n }\n};\n\nconst getEdgeParams = (source: InternalNode<FlowNode>, target: InternalNode<FlowNode>) => {\n const from = getHandleAnchor(source, \"source\", Position.Right);\n const to = getHandleAnchor(target, \"target\", Position.Left);\n\n return {\n sourcePos: from.position,\n sx: from.x,\n sy: from.y,\n targetPos: to.position,\n tx: to.x,\n ty: to.y,\n };\n};\n\nexport const EdgeAnimated = ({ id, source, target, markerEnd, style }: EdgeProps) => {\n const sourceNode = useInternalNode(source);\n const targetNode = useInternalNode(target);\n\n if (!(sourceNode && targetNode)) {\n return null;\n }\n\n const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode);\n\n const [edgePath] = getBezierPath({\n sourcePosition: sourcePos,\n sourceX: sx,\n sourceY: sy,\n targetPosition: targetPos,\n targetX: tx,\n targetY: ty,\n });\n\n return (\n <>\n <BaseEdge id={id} markerEnd={markerEnd} path={edgePath} style={style} />\n <circle fill=\"var(--primary)\" r=\"4\">\n <animateMotion dur=\"2s\" path={edgePath} repeatCount=\"indefinite\" />\n </circle>\n </>\n );\n};\n\nexport const Node = ({ handles, className, ...props }: NodeProps) => (\n <Card\n className={cn(\"node-container relative size-full h-auto w-sm gap-0 rounded-md p-0\", className)}\n {...props}\n >\n {handles.target && <Handle position={Position.Left} type=\"target\" />}\n {handles.source && <Handle position={Position.Right} type=\"source\" />}\n {props.children}\n </Card>\n);\n\nexport const Panel = ({ className, ...props }: PanelProps) => (\n <PanelPrimitive\n className={cn(\"m-4 overflow-hidden rounded-md border bg-card p-1\", className)}\n {...props}\n />\n);\n\nexport const Toolbar = ({ className, ...props }: ToolbarProps) => (\n <NodeToolbar\n className={cn(\"flex items-center gap-1 rounded-sm border bg-background p-1.5\", className)}\n position={Position.Bottom}\n {...props}\n />\n);\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/_persona-rive.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * The Rive half of `Persona`, split out so it can be `lazy()`-loaded.\n *\n * `@rive-app/react-webgl2` declares no `sideEffects`, so a static import from\n * `persona.tsx` put the whole WebGL2 runtime (and its `rive.wasm`) into the entry\n * chunk of every `@elabs-ai/components-ai` consumer — including the vast majority that never\n * render a `Persona`. Keeping every Rive *value* import in this module, reached\n * only through `lazy(() => import(\"./_persona-rive\"))`, confines it to its own\n * chunk. `persona.tsx` may still `import type` from Rive (types erase).\n *\n * See ADR 0019 and `pnpm heavy-deps:check`.\n *\n * @lazy-boundary This module must only ever be reached via `import()`. The gate\n * fails if anything imports it statically, which would put Rive back in the\n * entry chunk and make the `lazy()` pointless.\n */\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { useReducedMotion } from \"@elabs-ai/components-tokens\";\nimport type { RiveParameters } from \"@rive-app/react-webgl2\";\n// `import type` from an optional peer is safe HERE ONLY because this module is\n// reached exclusively through `lazy(() => import(\"./_persona-rive\"))` — it is\n// never statically imported, so nothing in this file reaches the barrel's\n// `.d.ts` declaration graph. `persona.tsx`'s PUBLIC types must never do this\n// (issue #101) — see its module doc comment and `PersonaRiveEvent`/\n// `PersonaRiveEventCallback`, which this module imports below and proves\n// conformant against the real peer type at the bottom of this file.\n// A NAMED import (`import { useRive } from \"@rive-app/react-webgl2\"`) is a\n// static ESM binding a bundler must resolve at build time. Now that\n// `@rive-app/react-webgl2` is a genuinely optional peer (issue #33), a\n// consumer who has not installed it hits that resolution at the worst\n// possible time: Vite's own optional-peer-dependency handling swaps in a\n// build-time stub with no exports, and Rollup's static named-export check\n// then fails the WHOLE APP BUILD — not a runtime error `LazyEngineBoundary`\n// could ever see (confirmed against `fixtures/consumer-smoke`'s real Vite\n// build). A namespace import defers every one of these to a plain property\n// lookup, which Rollup does not statically validate, so the build always\n// succeeds; a genuinely missing peer instead resolves the destructured hooks\n// below to `undefined`, and the guard turns that into the render-phase throw\n// `LazyEngineBoundary` (see `persona.tsx`) already catches.\nimport * as RiveModule from \"@rive-app/react-webgl2\";\nimport type { ReactNode } from \"react\";\nimport { memo, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport type { AssertAssignable } from \"./_lazy-boundary-conformance\";\nimport type { PersonaRiveEventCallback } from \"./persona\";\nimport type { PersonaSource, PersonaState } from \"./persona-sources\";\n\nconst {\n useRive,\n useStateMachineInput,\n useViewModel,\n useViewModelInstance,\n useViewModelInstanceColor,\n} = RiveModule;\n\nif (!useRive) {\n throw new Error(\"Cannot find module '@rive-app/react-webgl2'\");\n}\n\n// Delays Rive initialization by one frame so that React Strict Mode's\n// immediate unmount cycle never creates a WebGL2 context. Only the\n// second (real) mount will initialise, avoiding context exhaustion.\nconst useStrictModeSafeInit = () => {\n const [ready, setReady] = useState(false);\n\n useEffect(() => {\n const id = requestAnimationFrame(() => setReady(true));\n return () => {\n cancelAnimationFrame(id);\n setReady(false);\n };\n }, []);\n\n return ready;\n};\n\n// The state machine name is always 'default' for Elements AI visuals\nconst stateMachine = \"default\";\n\nconst getCurrentTheme = (): \"light\" | \"dark\" => {\n if (typeof window !== \"undefined\") {\n if (document.documentElement.classList.contains(\"dark\")) {\n return \"dark\";\n }\n if (window.matchMedia?.(\"(prefers-color-scheme: dark)\").matches) {\n return \"dark\";\n }\n }\n return \"light\";\n};\n\nconst useTheme = (enabled: boolean) => {\n const [theme, setTheme] = useState<\"light\" | \"dark\">(getCurrentTheme);\n\n useEffect(() => {\n // Skip if not enabled (avoids unnecessary observers for non-dynamic-color variants)\n if (!enabled) {\n return;\n }\n\n // Watch for classList changes\n const observer = new MutationObserver(() => {\n setTheme(getCurrentTheme());\n });\n\n observer.observe(document.documentElement, {\n attributeFilter: [\"class\"],\n attributes: true,\n });\n\n // Watch for OS-level theme changes\n let mql: MediaQueryList | null = null;\n const handleMediaChange = () => {\n setTheme(getCurrentTheme());\n };\n\n if (window.matchMedia) {\n mql = window.matchMedia(\"(prefers-color-scheme: dark)\");\n mql.addEventListener(\"change\", handleMediaChange);\n }\n\n return () => {\n observer.disconnect();\n if (mql) {\n mql.removeEventListener(\"change\", handleMediaChange);\n }\n };\n }, [enabled]);\n\n return theme;\n};\n\ninterface PersonaWithModelProps {\n rive: ReturnType<typeof useRive>[\"rive\"];\n source: PersonaSource;\n children: ReactNode;\n}\n\nconst PersonaWithModel = memo(({ rive, source, children }: PersonaWithModelProps) => {\n const theme = useTheme(source.dynamicColor);\n const viewModel = useViewModel(rive, { useDefault: true });\n const viewModelInstance = useViewModelInstance(viewModel, {\n rive,\n useDefault: true,\n });\n const viewModelInstanceColor = useViewModelInstanceColor(\"color\", viewModelInstance);\n\n useEffect(() => {\n if (!(viewModelInstanceColor && source.dynamicColor)) {\n return;\n }\n\n const [r, g, b] = theme === \"dark\" ? [255, 255, 255] : [0, 0, 0];\n viewModelInstanceColor.setRgb(r, g, b);\n }, [viewModelInstanceColor, theme, source.dynamicColor]);\n\n return children;\n});\n\nPersonaWithModel.displayName = \"PersonaWithModel\";\n\nconst PersonaWithoutModel = memo(({ children }: { children: ReactNode }) => children);\n\nPersonaWithoutModel.displayName = \"PersonaWithoutModel\";\n\nexport interface PersonaRiveProps {\n className?: string;\n onLoad?: PersonaRiveEventCallback;\n onLoadError?: PersonaRiveEventCallback;\n onPause?: PersonaRiveEventCallback;\n onPlay?: PersonaRiveEventCallback;\n onReady?: () => void;\n onStop?: PersonaRiveEventCallback;\n source: PersonaSource;\n /** Resolved `.riv` URL — `Persona`'s `src` override, or the variant default. */\n src: string;\n state: PersonaState;\n}\n\nconst PersonaRive = ({\n className,\n onLoad,\n onLoadError,\n onPause,\n onPlay,\n onReady,\n onStop,\n source,\n src,\n state,\n}: PersonaRiveProps) => {\n // Stabilize callbacks to prevent useRive from reinitializing\n const callbacksRef = useRef({ onLoad, onLoadError, onPause, onPlay, onReady, onStop });\n\n useEffect(() => {\n callbacksRef.current = { onLoad, onLoadError, onPause, onPlay, onReady, onStop };\n }, [onLoad, onLoadError, onPause, onPlay, onReady, onStop]);\n\n const stableCallbacks = useMemo(\n () => ({\n onLoad: ((loadedRive) =>\n callbacksRef.current.onLoad?.(loadedRive)) as RiveParameters[\"onLoad\"],\n onLoadError: ((err) =>\n callbacksRef.current.onLoadError?.(err)) as RiveParameters[\"onLoadError\"],\n onPause: ((event) => callbacksRef.current.onPause?.(event)) as RiveParameters[\"onPause\"],\n onPlay: ((event) => callbacksRef.current.onPlay?.(event)) as RiveParameters[\"onPlay\"],\n onReady: () => callbacksRef.current.onReady?.(),\n onStop: ((event) => callbacksRef.current.onStop?.(event)) as RiveParameters[\"onStop\"],\n }),\n [],\n );\n\n // Delay initialisation by one frame to avoid creating (and leaking)\n // a WebGL2 context during React Strict Mode's first throw-away mount.\n const ready = useStrictModeSafeInit();\n const prefersReducedMotion = useReducedMotion();\n\n const { rive, RiveComponent } = useRive(\n ready\n ? {\n autoplay: !prefersReducedMotion,\n onLoad: stableCallbacks.onLoad,\n onLoadError: stableCallbacks.onLoadError,\n onPause: stableCallbacks.onPause,\n onPlay: stableCallbacks.onPlay,\n onRiveReady: stableCallbacks.onReady,\n onStop: stableCallbacks.onStop,\n src,\n stateMachines: stateMachine,\n }\n : null,\n );\n\n // `autoplay` only governs the INITIAL mount — honor a mid-session\n // reduced-motion preference change by pausing/resuming the running\n // instance. The state-machine inputs below keep updating either way, so\n // the artwork still shows the correct resting pose while paused.\n useEffect(() => {\n if (!rive) return;\n if (prefersReducedMotion) {\n rive.pause();\n } else {\n rive.play();\n }\n }, [rive, prefersReducedMotion]);\n\n const listeningInput = useStateMachineInput(rive, stateMachine, \"listening\");\n const thinkingInput = useStateMachineInput(rive, stateMachine, \"thinking\");\n const speakingInput = useStateMachineInput(rive, stateMachine, \"speaking\");\n const asleepInput = useStateMachineInput(rive, stateMachine, \"asleep\");\n\n // Rive state machine inputs are mutable objects that must be set via direct\n // property assignment — this is the intended Rive API, not a React anti-pattern.\n useEffect(() => {\n if (listeningInput) {\n listeningInput.value = state === \"listening\";\n }\n if (thinkingInput) {\n thinkingInput.value = state === \"thinking\";\n }\n if (speakingInput) {\n speakingInput.value = state === \"speaking\";\n }\n if (asleepInput) {\n asleepInput.value = state === \"asleep\";\n }\n }, [state, listeningInput, thinkingInput, speakingInput, asleepInput]);\n\n const Wrapper = source.hasModel ? PersonaWithModel : PersonaWithoutModel;\n\n return (\n <Wrapper rive={rive} source={source}>\n <RiveComponent className={cn(\"size-16 shrink-0\", className)} />\n </Wrapper>\n );\n};\n\nexport default PersonaRive;\n\n/**\n * Conformance assertion (issue #101): compile-time proof that the owned\n * `PersonaRiveEventCallback` (declared in `persona.tsx`, structurally free of\n * `@rive-app/react-webgl2`/`@rive-app/webgl2`) is still assignable to the REAL\n * `RiveParameters[\"onLoad\"]` — and, since `onLoad`/`onLoadError`/`onPause`/\n * `onPlay`/`onStop` all share Rive's one `EventCallback` type, this single\n * check covers all five. `AssertAssignable`'s type parameter is constrained\n * (`TOwned extends TReal`), so if a future Rive release changes `Event`'s\n * shape in a way `PersonaRiveEvent` no longer satisfies, this line fails to\n * typecheck — caught by `pnpm --filter @elabs-ai/components-ai typecheck`\n * locally, never shipped as a silent mismatch to a consumer.\n *\n * `AssertAssignable` itself is shared with `_audio-player-media-chrome.tsx`\n * via `_lazy-boundary-conformance.ts` — see that module's doc comment for\n * what this check can and cannot prove (it is one-directional: it cannot\n * catch the owned type being NARROWER than the real one, which is exactly\n * what `PersonaRiveEvent[\"data\"]` is — see the CHANGELOG's \"Breaking (types)\"\n * entry).\n */\nexport type _PersonaRiveEventCallbackConformance = AssertAssignable<\n PersonaRiveEventCallback,\n NonNullable<RiveParameters[\"onLoad\"]>\n>;\n"],"mappings":"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","names":[]}
|
/package/dist/{_audio-player-media-chrome-T3XVXWRZ.js.map → _audio-player-media-chrome.js.map}
RENAMED
|
File without changes
|