@adea-ai/ui 0.140.0 → 0.142.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/dist/components/composites/appearance-editor/appearance-editor.js +2 -2
- package/dist/components/composites/appearance-editor/appearance-editor.js.map +1 -1
- package/dist/components/composites/catalog-browser/catalog-browser-group.js +1 -1
- package/dist/components/composites/chain-of-thought/chain-of-thought.d.ts +56 -0
- package/dist/components/composites/chain-of-thought/chain-of-thought.d.ts.map +1 -0
- package/dist/components/composites/chain-of-thought/chain-of-thought.js +117 -0
- package/dist/components/composites/chain-of-thought/chain-of-thought.js.map +1 -0
- package/dist/components/composites/chain-of-thought/index.d.ts +2 -0
- package/dist/components/composites/chain-of-thought/index.d.ts.map +1 -0
- package/dist/components/composites/chain-of-thought/index.js +2 -0
- package/dist/components/composites/chat/chat.d.ts +67 -0
- package/dist/components/composites/chat/chat.d.ts.map +1 -0
- package/dist/components/composites/chat/chat.js +179 -0
- package/dist/components/composites/chat/chat.js.map +1 -0
- package/dist/components/composites/chat/index.d.ts +2 -0
- package/dist/components/composites/chat/index.d.ts.map +1 -0
- package/dist/components/composites/chat/index.js +2 -0
- package/dist/components/composites/checkpoint/checkpoint-model.d.ts +34 -0
- package/dist/components/composites/checkpoint/checkpoint-model.d.ts.map +1 -0
- package/dist/components/composites/checkpoint/checkpoint-model.js +48 -0
- package/dist/components/composites/checkpoint/checkpoint-model.js.map +1 -0
- package/dist/components/composites/checkpoint/checkpoint.d.ts +48 -0
- package/dist/components/composites/checkpoint/checkpoint.d.ts.map +1 -0
- package/dist/components/composites/checkpoint/checkpoint.js +160 -0
- package/dist/components/composites/checkpoint/checkpoint.js.map +1 -0
- package/dist/components/composites/checkpoint/index.d.ts +3 -0
- package/dist/components/composites/checkpoint/index.d.ts.map +1 -0
- package/dist/components/composites/checkpoint/index.js +3 -0
- package/dist/components/composites/confirmation/confirmation-model.d.ts +45 -0
- package/dist/components/composites/confirmation/confirmation-model.d.ts.map +1 -0
- package/dist/components/composites/confirmation/confirmation-model.js +51 -0
- package/dist/components/composites/confirmation/confirmation-model.js.map +1 -0
- package/dist/components/composites/confirmation/confirmation.d.ts +72 -0
- package/dist/components/composites/confirmation/confirmation.d.ts.map +1 -0
- package/dist/components/composites/confirmation/confirmation.js +300 -0
- package/dist/components/composites/confirmation/confirmation.js.map +1 -0
- package/dist/components/composites/confirmation/index.d.ts +3 -0
- package/dist/components/composites/confirmation/index.d.ts.map +1 -0
- package/dist/components/composites/confirmation/index.js +3 -0
- package/dist/components/composites/context/context-model.d.ts +69 -0
- package/dist/components/composites/context/context-model.d.ts.map +1 -0
- package/dist/components/composites/context/context-model.js +122 -0
- package/dist/components/composites/context/context-model.js.map +1 -0
- package/dist/components/composites/context/context.d.ts +38 -0
- package/dist/components/composites/context/context.d.ts.map +1 -0
- package/dist/components/composites/context/context.js +234 -0
- package/dist/components/composites/context/context.js.map +1 -0
- package/dist/components/composites/context/index.d.ts +3 -0
- package/dist/components/composites/context/index.d.ts.map +1 -0
- package/dist/components/composites/context/index.js +3 -0
- package/dist/components/composites/file-tree/build-file-tree.d.ts +138 -0
- package/dist/components/composites/file-tree/build-file-tree.d.ts.map +1 -0
- package/dist/components/composites/file-tree/build-file-tree.js +325 -0
- package/dist/components/composites/file-tree/build-file-tree.js.map +1 -0
- package/dist/components/composites/file-tree/file-tree.d.ts +74 -0
- package/dist/components/composites/file-tree/file-tree.d.ts.map +1 -0
- package/dist/components/composites/file-tree/file-tree.js +389 -0
- package/dist/components/composites/file-tree/file-tree.js.map +1 -0
- package/dist/components/composites/file-tree/index.d.ts +3 -0
- package/dist/components/composites/file-tree/index.d.ts.map +1 -0
- package/dist/components/composites/file-tree/index.js +3 -0
- package/dist/components/composites/inline-citation/citation.d.ts +37 -0
- package/dist/components/composites/inline-citation/citation.d.ts.map +1 -0
- package/dist/components/composites/inline-citation/citation.js +52 -0
- package/dist/components/composites/inline-citation/citation.js.map +1 -0
- package/dist/components/composites/inline-citation/index.d.ts +3 -0
- package/dist/components/composites/inline-citation/index.d.ts.map +1 -0
- package/dist/components/composites/inline-citation/index.js +3 -0
- package/dist/components/composites/inline-citation/inline-citation.d.ts +64 -0
- package/dist/components/composites/inline-citation/inline-citation.d.ts.map +1 -0
- package/dist/components/composites/inline-citation/inline-citation.js +272 -0
- package/dist/components/composites/inline-citation/inline-citation.js.map +1 -0
- package/dist/components/composites/message-scroller/index.d.ts +3 -0
- package/dist/components/composites/message-scroller/index.d.ts.map +1 -0
- package/dist/components/composites/message-scroller/index.js +3 -0
- package/dist/components/composites/message-scroller/message-scroller.d.ts +55 -0
- package/dist/components/composites/message-scroller/message-scroller.d.ts.map +1 -0
- package/dist/components/composites/message-scroller/message-scroller.js +201 -0
- package/dist/components/composites/message-scroller/message-scroller.js.map +1 -0
- package/dist/components/composites/message-scroller/scroll-anchor.d.ts +66 -0
- package/dist/components/composites/message-scroller/scroll-anchor.d.ts.map +1 -0
- package/dist/components/composites/message-scroller/scroll-anchor.js +122 -0
- package/dist/components/composites/message-scroller/scroll-anchor.js.map +1 -0
- package/dist/components/composites/model-selector/index.d.ts +3 -0
- package/dist/components/composites/model-selector/index.d.ts.map +1 -0
- package/dist/components/composites/model-selector/index.js +3 -0
- package/dist/components/composites/model-selector/model-selector.d.ts +55 -0
- package/dist/components/composites/model-selector/model-selector.d.ts.map +1 -0
- package/dist/components/composites/model-selector/model-selector.js +490 -0
- package/dist/components/composites/model-selector/model-selector.js.map +1 -0
- package/dist/components/composites/model-selector/models.d.ts +51 -0
- package/dist/components/composites/model-selector/models.d.ts.map +1 -0
- package/dist/components/composites/model-selector/models.js +71 -0
- package/dist/components/composites/model-selector/models.js.map +1 -0
- package/dist/components/composites/plan/index.d.ts +2 -0
- package/dist/components/composites/plan/index.d.ts.map +1 -0
- package/dist/components/composites/plan/index.js +2 -0
- package/dist/components/composites/plan/plan.d.ts +61 -0
- package/dist/components/composites/plan/plan.d.ts.map +1 -0
- package/dist/components/composites/plan/plan.js +130 -0
- package/dist/components/composites/plan/plan.js.map +1 -0
- package/dist/components/composites/queue/index.d.ts +2 -0
- package/dist/components/composites/queue/index.d.ts.map +1 -0
- package/dist/components/composites/queue/index.js +2 -0
- package/dist/components/composites/queue/queue.d.ts +77 -0
- package/dist/components/composites/queue/queue.d.ts.map +1 -0
- package/dist/components/composites/queue/queue.js +271 -0
- package/dist/components/composites/queue/queue.js.map +1 -0
- package/dist/components/composites/reasoning/index.d.ts +2 -0
- package/dist/components/composites/reasoning/index.d.ts.map +1 -0
- package/dist/components/composites/reasoning/index.js +2 -0
- package/dist/components/composites/reasoning/reasoning.d.ts +62 -0
- package/dist/components/composites/reasoning/reasoning.d.ts.map +1 -0
- package/dist/components/composites/reasoning/reasoning.js +156 -0
- package/dist/components/composites/reasoning/reasoning.js.map +1 -0
- package/dist/components/composites/sources/index.d.ts +3 -0
- package/dist/components/composites/sources/index.d.ts.map +1 -0
- package/dist/components/composites/sources/index.js +3 -0
- package/dist/components/composites/sources/source.d.ts +46 -0
- package/dist/components/composites/sources/source.d.ts.map +1 -0
- package/dist/components/composites/sources/source.js +74 -0
- package/dist/components/composites/sources/source.js.map +1 -0
- package/dist/components/composites/sources/sources.d.ts +45 -0
- package/dist/components/composites/sources/sources.d.ts.map +1 -0
- package/dist/components/composites/sources/sources.js +175 -0
- package/dist/components/composites/sources/sources.js.map +1 -0
- package/dist/components/composites/stack-trace/index.d.ts +3 -0
- package/dist/components/composites/stack-trace/index.d.ts.map +1 -0
- package/dist/components/composites/stack-trace/index.js +3 -0
- package/dist/components/composites/stack-trace/parse-stack-trace.d.ts +118 -0
- package/dist/components/composites/stack-trace/parse-stack-trace.d.ts.map +1 -0
- package/dist/components/composites/stack-trace/parse-stack-trace.js +376 -0
- package/dist/components/composites/stack-trace/parse-stack-trace.js.map +1 -0
- package/dist/components/composites/stack-trace/stack-trace.d.ts +63 -0
- package/dist/components/composites/stack-trace/stack-trace.d.ts.map +1 -0
- package/dist/components/composites/stack-trace/stack-trace.js +491 -0
- package/dist/components/composites/stack-trace/stack-trace.js.map +1 -0
- package/dist/components/composites/subagent/index.d.ts +2 -0
- package/dist/components/composites/subagent/index.d.ts.map +1 -0
- package/dist/components/composites/subagent/index.js +2 -0
- package/dist/components/composites/subagent/subagent.d.ts +55 -0
- package/dist/components/composites/subagent/subagent.d.ts.map +1 -0
- package/dist/components/composites/subagent/subagent.js +138 -0
- package/dist/components/composites/subagent/subagent.js.map +1 -0
- package/dist/components/composites/suggestion/index.d.ts +2 -0
- package/dist/components/composites/suggestion/index.d.ts.map +1 -0
- package/dist/components/composites/suggestion/index.js +2 -0
- package/dist/components/composites/suggestion/suggestion.d.ts +37 -0
- package/dist/components/composites/suggestion/suggestion.d.ts.map +1 -0
- package/dist/components/composites/suggestion/suggestion.js +100 -0
- package/dist/components/composites/suggestion/suggestion.js.map +1 -0
- package/dist/components/composites/task/index.d.ts +2 -0
- package/dist/components/composites/task/index.d.ts.map +1 -0
- package/dist/components/composites/task/index.js +2 -0
- package/dist/components/composites/task/task.d.ts +96 -0
- package/dist/components/composites/task/task.d.ts.map +1 -0
- package/dist/components/composites/task/task.js +311 -0
- package/dist/components/composites/task/task.js.map +1 -0
- package/dist/components/composites/terminal/ansi.d.ts +101 -0
- package/dist/components/composites/terminal/ansi.d.ts.map +1 -0
- package/dist/components/composites/terminal/ansi.js +544 -0
- package/dist/components/composites/terminal/ansi.js.map +1 -0
- package/dist/components/composites/terminal/index.d.ts +4 -0
- package/dist/components/composites/terminal/index.d.ts.map +1 -0
- package/dist/components/composites/terminal/index.js +4 -0
- package/dist/components/composites/terminal/terminal-model.d.ts +63 -0
- package/dist/components/composites/terminal/terminal-model.d.ts.map +1 -0
- package/dist/components/composites/terminal/terminal-model.js +69 -0
- package/dist/components/composites/terminal/terminal-model.js.map +1 -0
- package/dist/components/composites/terminal/terminal.d.ts +88 -0
- package/dist/components/composites/terminal/terminal.d.ts.map +1 -0
- package/dist/components/composites/terminal/terminal.js +500 -0
- package/dist/components/composites/terminal/terminal.js.map +1 -0
- package/dist/components/composites/test-results/index.d.ts +3 -0
- package/dist/components/composites/test-results/index.d.ts.map +1 -0
- package/dist/components/composites/test-results/index.js +3 -0
- package/dist/components/composites/test-results/test-results-model.d.ts +154 -0
- package/dist/components/composites/test-results/test-results-model.d.ts.map +1 -0
- package/dist/components/composites/test-results/test-results-model.js +252 -0
- package/dist/components/composites/test-results/test-results-model.js.map +1 -0
- package/dist/components/composites/test-results/test-results.d.ts +60 -0
- package/dist/components/composites/test-results/test-results.d.ts.map +1 -0
- package/dist/components/composites/test-results/test-results.js +631 -0
- package/dist/components/composites/test-results/test-results.js.map +1 -0
- package/dist/components/composites/tool/format-tool-value.d.ts +37 -0
- package/dist/components/composites/tool/format-tool-value.d.ts.map +1 -0
- package/dist/components/composites/tool/format-tool-value.js +135 -0
- package/dist/components/composites/tool/format-tool-value.js.map +1 -0
- package/dist/components/composites/tool/index.d.ts +3 -0
- package/dist/components/composites/tool/index.d.ts.map +1 -0
- package/dist/components/composites/tool/index.js +3 -0
- package/dist/components/composites/tool/tool.d.ts +69 -0
- package/dist/components/composites/tool/tool.d.ts.map +1 -0
- package/dist/components/composites/tool/tool.js +214 -0
- package/dist/components/composites/tool/tool.js.map +1 -0
- package/dist/components/composites/update-dialog/update-dialog.js +1 -1
- package/dist/components/conversation/conversation-surface.d.ts +7 -0
- package/dist/components/conversation/conversation-surface.d.ts.map +1 -1
- package/dist/components/conversation/conversation-surface.js +2 -0
- package/dist/components/conversation/conversation-surface.js.map +1 -1
- package/dist/components/conversation/scroll-follow-core.d.ts +24 -0
- package/dist/components/conversation/scroll-follow-core.d.ts.map +1 -1
- package/dist/components/conversation/scroll-follow-core.js +23 -1
- package/dist/components/conversation/scroll-follow-core.js.map +1 -1
- package/dist/components/conversation/scroll-follow.d.ts +3 -0
- package/dist/components/conversation/scroll-follow.d.ts.map +1 -1
- package/dist/components/conversation/scroll-follow.js +80 -1
- package/dist/components/conversation/scroll-follow.js.map +1 -1
- package/dist/components/ui/attachment/attachment.d.ts +58 -0
- package/dist/components/ui/attachment/attachment.d.ts.map +1 -0
- package/dist/components/ui/attachment/attachment.js +279 -0
- package/dist/components/ui/attachment/attachment.js.map +1 -0
- package/dist/components/ui/attachment/index.d.ts +2 -0
- package/dist/components/ui/attachment/index.d.ts.map +1 -0
- package/dist/components/ui/attachment/index.js +2 -0
- package/dist/components/ui/badge/badge.d.ts +1 -1
- package/dist/components/ui/bubble/bubble.d.ts +33 -0
- package/dist/components/ui/bubble/bubble.d.ts.map +1 -0
- package/dist/components/ui/bubble/bubble.js +95 -0
- package/dist/components/ui/bubble/bubble.js.map +1 -0
- package/dist/components/ui/bubble/index.d.ts +2 -0
- package/dist/components/ui/bubble/index.d.ts.map +1 -0
- package/dist/components/ui/bubble/index.js +2 -0
- package/dist/components/ui/button/button.d.ts +1 -1
- package/dist/components/ui/code-block/code-block.d.ts +56 -3
- package/dist/components/ui/code-block/code-block.d.ts.map +1 -1
- package/dist/components/ui/code-block/code-block.js +231 -50
- package/dist/components/ui/code-block/code-block.js.map +1 -1
- package/dist/components/ui/code-block/index.d.ts +1 -1
- package/dist/components/ui/code-block/index.d.ts.map +1 -1
- package/dist/components/ui/code-block/index.js +2 -2
- package/dist/components/ui/field/number-field.d.ts +21 -4
- package/dist/components/ui/field/number-field.d.ts.map +1 -1
- package/dist/components/ui/field/number-field.js +22 -11
- package/dist/components/ui/field/number-field.js.map +1 -1
- package/dist/components/ui/marker/index.d.ts +2 -0
- package/dist/components/ui/marker/index.d.ts.map +1 -0
- package/dist/components/ui/marker/index.js +2 -0
- package/dist/components/ui/marker/marker.d.ts +35 -0
- package/dist/components/ui/marker/marker.d.ts.map +1 -0
- package/dist/components/ui/marker/marker.js +123 -0
- package/dist/components/ui/marker/marker.js.map +1 -0
- package/dist/components/ui/message/cite-ranges.d.ts +42 -0
- package/dist/components/ui/message/cite-ranges.d.ts.map +1 -0
- package/dist/components/ui/message/cite-ranges.js +105 -0
- package/dist/components/ui/message/cite-ranges.js.map +1 -0
- package/dist/components/ui/message/index.d.ts +4 -0
- package/dist/components/ui/message/index.d.ts.map +1 -0
- package/dist/components/ui/message/index.js +4 -0
- package/dist/components/ui/message/message.d.ts +95 -0
- package/dist/components/ui/message/message.d.ts.map +1 -0
- package/dist/components/ui/message/message.js +443 -0
- package/dist/components/ui/message/message.js.map +1 -0
- package/dist/components/ui/message/parse-message-text.d.ts +93 -0
- package/dist/components/ui/message/parse-message-text.d.ts.map +1 -0
- package/dist/components/ui/message/parse-message-text.js +365 -0
- package/dist/components/ui/message/parse-message-text.js.map +1 -0
- package/dist/components/ui/questionnaire/index.d.ts +2 -0
- package/dist/components/ui/questionnaire/index.d.ts.map +1 -0
- package/dist/components/ui/questionnaire/index.js +2 -0
- package/dist/components/ui/questionnaire/questionnaire.d.ts +83 -0
- package/dist/components/ui/questionnaire/questionnaire.d.ts.map +1 -0
- package/dist/components/ui/questionnaire/questionnaire.js +470 -0
- package/dist/components/ui/questionnaire/questionnaire.js.map +1 -0
- package/dist/components/ui/status-chip/status-chip.d.ts +1 -1
- package/dist/components/ui/switch/switch.js +11 -11
- package/dist/components/ui/switch/switch.js.map +1 -1
- package/dist/components/ui/trace/index.d.ts +2 -0
- package/dist/components/ui/trace/index.d.ts.map +1 -0
- package/dist/components/ui/trace/index.js +2 -0
- package/dist/components/ui/trace/trace.d.ts +72 -0
- package/dist/components/ui/trace/trace.d.ts.map +1 -0
- package/dist/components/ui/trace/trace.js +340 -0
- package/dist/components/ui/trace/trace.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -5
- package/dist/r/attachment.json +25 -0
- package/dist/r/bubble.json +22 -0
- package/dist/r/chain-of-thought.json +26 -0
- package/dist/r/chat.json +24 -0
- package/dist/r/checkpoint.json +35 -0
- package/dist/r/confirmation.json +34 -0
- package/dist/r/context.json +33 -0
- package/dist/r/file-tree.json +32 -0
- package/dist/r/inline-citation.json +35 -0
- package/dist/r/marker.json +22 -0
- package/dist/r/message-scroller.json +31 -0
- package/dist/r/message.json +33 -0
- package/dist/r/model-selector.json +34 -0
- package/dist/r/plan.json +26 -0
- package/dist/r/questionnaire.json +28 -0
- package/dist/r/queue.json +26 -0
- package/dist/r/reasoning.json +26 -0
- package/dist/r/registry.json +811 -52
- package/dist/r/sources.json +32 -0
- package/dist/r/src/components/composites/chain-of-thought/chain-of-thought.tsx +141 -0
- package/dist/r/src/components/composites/chain-of-thought/index.ts +11 -0
- package/dist/r/src/components/composites/chat/chat.tsx +190 -0
- package/dist/r/src/components/composites/chat/index.ts +11 -0
- package/dist/r/src/components/composites/checkpoint/checkpoint-model.ts +71 -0
- package/dist/r/src/components/composites/checkpoint/checkpoint.tsx +214 -0
- package/dist/r/src/components/composites/checkpoint/index.ts +8 -0
- package/dist/r/src/components/composites/confirmation/confirmation-model.ts +83 -0
- package/dist/r/src/components/composites/confirmation/confirmation.tsx +311 -0
- package/dist/r/src/components/composites/confirmation/index.ts +9 -0
- package/dist/r/src/components/composites/context/context-model.ts +168 -0
- package/dist/r/src/components/composites/context/context.tsx +187 -0
- package/dist/r/src/components/composites/context/index.ts +18 -0
- package/dist/r/src/components/composites/file-tree/build-file-tree.ts +456 -0
- package/dist/r/src/components/composites/file-tree/file-tree.tsx +405 -0
- package/dist/r/src/components/composites/file-tree/index.ts +25 -0
- package/dist/r/src/components/composites/inline-citation/citation.ts +78 -0
- package/dist/r/src/components/composites/inline-citation/index.ts +9 -0
- package/dist/r/src/components/composites/inline-citation/inline-citation.tsx +285 -0
- package/dist/r/src/components/composites/message-scroller/index.ts +2 -0
- package/dist/r/src/components/composites/message-scroller/message-scroller.tsx +301 -0
- package/dist/r/src/components/composites/message-scroller/scroll-anchor.ts +170 -0
- package/dist/r/src/components/composites/model-selector/index.ts +14 -0
- package/dist/r/src/components/composites/model-selector/model-selector.tsx +420 -0
- package/dist/r/src/components/composites/model-selector/models.ts +103 -0
- package/dist/r/src/components/composites/plan/index.ts +1 -0
- package/dist/r/src/components/composites/plan/plan.tsx +155 -0
- package/dist/r/src/components/composites/queue/index.ts +10 -0
- package/dist/r/src/components/composites/queue/queue.tsx +264 -0
- package/dist/r/src/components/composites/reasoning/index.ts +1 -0
- package/dist/r/src/components/composites/reasoning/reasoning.tsx +176 -0
- package/dist/r/src/components/composites/sources/index.ts +11 -0
- package/dist/r/src/components/composites/sources/source.ts +116 -0
- package/dist/r/src/components/composites/sources/sources.tsx +163 -0
- package/dist/r/src/components/composites/stack-trace/index.ts +19 -0
- package/dist/r/src/components/composites/stack-trace/parse-stack-trace.ts +507 -0
- package/dist/r/src/components/composites/stack-trace/stack-trace.tsx +395 -0
- package/dist/r/src/components/composites/subagent/index.ts +1 -0
- package/dist/r/src/components/composites/subagent/subagent.tsx +159 -0
- package/dist/r/src/components/composites/suggestion/index.ts +1 -0
- package/dist/r/src/components/composites/suggestion/suggestion.tsx +140 -0
- package/dist/r/src/components/composites/task/index.ts +14 -0
- package/dist/r/src/components/composites/task/task.tsx +317 -0
- package/dist/r/src/components/composites/terminal/ansi.ts +674 -0
- package/dist/r/src/components/composites/terminal/index.ts +20 -0
- package/dist/r/src/components/composites/terminal/terminal-model.ts +111 -0
- package/dist/r/src/components/composites/terminal/terminal.tsx +574 -0
- package/dist/r/src/components/composites/test-results/index.ts +20 -0
- package/dist/r/src/components/composites/test-results/test-results-model.ts +368 -0
- package/dist/r/src/components/composites/test-results/test-results.tsx +586 -0
- package/dist/r/src/components/composites/tool/format-tool-value.ts +221 -0
- package/dist/r/src/components/composites/tool/index.ts +2 -0
- package/dist/r/src/components/composites/tool/tool.tsx +228 -0
- package/dist/r/src/components/conversation/conversation-surface.tsx +9 -0
- package/dist/r/src/components/conversation/scroll-follow-core.ts +31 -0
- package/dist/r/src/components/conversation/scroll-follow.ts +92 -0
- package/dist/r/src/components/ui/attachment/attachment.tsx +282 -0
- package/dist/r/src/components/ui/attachment/index.ts +10 -0
- package/dist/r/src/components/ui/bubble/bubble.tsx +86 -0
- package/dist/r/src/components/ui/bubble/index.ts +1 -0
- package/dist/r/src/components/ui/code-block/code-block.tsx +256 -26
- package/dist/r/src/components/ui/code-block/index.ts +9 -1
- package/dist/r/src/components/ui/field/number-field.tsx +51 -14
- package/dist/r/src/components/ui/marker/index.ts +1 -0
- package/dist/r/src/components/ui/marker/marker.tsx +90 -0
- package/dist/r/src/components/ui/message/cite-ranges.ts +144 -0
- package/dist/r/src/components/ui/message/index.ts +23 -0
- package/dist/r/src/components/ui/message/message.tsx +434 -0
- package/dist/r/src/components/ui/message/parse-message-text.ts +450 -0
- package/dist/r/src/components/ui/questionnaire/index.ts +10 -0
- package/dist/r/src/components/ui/questionnaire/questionnaire.tsx +446 -0
- package/dist/r/src/components/ui/trace/index.ts +9 -0
- package/dist/r/src/components/ui/trace/trace.tsx +334 -0
- package/dist/r/src/styles/base.css +34 -0
- package/dist/r/stack-trace.json +32 -0
- package/dist/r/subagent.json +27 -0
- package/dist/r/suggestion.json +24 -0
- package/dist/r/task.json +28 -0
- package/dist/r/terminal.json +35 -0
- package/dist/r/test-results.json +33 -0
- package/dist/r/tool.json +31 -0
- package/dist/r/trace.json +25 -0
- package/package.json +6 -3
- package/registry.json +811 -52
- package/src/components/composites/chain-of-thought/chain-of-thought.tsx +141 -0
- package/src/components/composites/chain-of-thought/index.ts +11 -0
- package/src/components/composites/chat/chat.tsx +190 -0
- package/src/components/composites/chat/index.ts +11 -0
- package/src/components/composites/checkpoint/checkpoint-model.ts +71 -0
- package/src/components/composites/checkpoint/checkpoint.tsx +214 -0
- package/src/components/composites/checkpoint/index.ts +8 -0
- package/src/components/composites/confirmation/confirmation-model.ts +83 -0
- package/src/components/composites/confirmation/confirmation.tsx +311 -0
- package/src/components/composites/confirmation/index.ts +9 -0
- package/src/components/composites/context/context-model.ts +168 -0
- package/src/components/composites/context/context.tsx +187 -0
- package/src/components/composites/context/index.ts +18 -0
- package/src/components/composites/file-tree/build-file-tree.ts +456 -0
- package/src/components/composites/file-tree/file-tree.tsx +405 -0
- package/src/components/composites/file-tree/index.ts +25 -0
- package/src/components/composites/inline-citation/citation.ts +78 -0
- package/src/components/composites/inline-citation/index.ts +9 -0
- package/src/components/composites/inline-citation/inline-citation.tsx +285 -0
- package/src/components/composites/message-scroller/index.ts +2 -0
- package/src/components/composites/message-scroller/message-scroller.tsx +301 -0
- package/src/components/composites/message-scroller/scroll-anchor.ts +170 -0
- package/src/components/composites/model-selector/index.ts +14 -0
- package/src/components/composites/model-selector/model-selector.tsx +420 -0
- package/src/components/composites/model-selector/models.ts +103 -0
- package/src/components/composites/plan/index.ts +1 -0
- package/src/components/composites/plan/plan.tsx +155 -0
- package/src/components/composites/queue/index.ts +10 -0
- package/src/components/composites/queue/queue.tsx +264 -0
- package/src/components/composites/reasoning/index.ts +1 -0
- package/src/components/composites/reasoning/reasoning.tsx +176 -0
- package/src/components/composites/sources/index.ts +11 -0
- package/src/components/composites/sources/source.ts +116 -0
- package/src/components/composites/sources/sources.tsx +163 -0
- package/src/components/composites/stack-trace/index.ts +19 -0
- package/src/components/composites/stack-trace/parse-stack-trace.ts +507 -0
- package/src/components/composites/stack-trace/stack-trace.tsx +395 -0
- package/src/components/composites/subagent/index.ts +1 -0
- package/src/components/composites/subagent/subagent.tsx +159 -0
- package/src/components/composites/suggestion/index.ts +1 -0
- package/src/components/composites/suggestion/suggestion.tsx +140 -0
- package/src/components/composites/task/index.ts +14 -0
- package/src/components/composites/task/task.tsx +317 -0
- package/src/components/composites/terminal/ansi.ts +674 -0
- package/src/components/composites/terminal/index.ts +20 -0
- package/src/components/composites/terminal/terminal-model.ts +111 -0
- package/src/components/composites/terminal/terminal.tsx +574 -0
- package/src/components/composites/test-results/index.ts +20 -0
- package/src/components/composites/test-results/test-results-model.ts +368 -0
- package/src/components/composites/test-results/test-results.tsx +586 -0
- package/src/components/composites/tool/format-tool-value.ts +221 -0
- package/src/components/composites/tool/index.ts +2 -0
- package/src/components/composites/tool/tool.tsx +228 -0
- package/src/components/conversation/conversation-surface.tsx +9 -0
- package/src/components/conversation/scroll-follow-core.ts +31 -0
- package/src/components/conversation/scroll-follow.ts +92 -0
- package/src/components/ui/attachment/attachment.tsx +282 -0
- package/src/components/ui/attachment/index.ts +10 -0
- package/src/components/ui/bubble/bubble.tsx +86 -0
- package/src/components/ui/bubble/index.ts +1 -0
- package/src/components/ui/code-block/code-block.tsx +256 -26
- package/src/components/ui/code-block/index.ts +9 -1
- package/src/components/ui/field/number-field.tsx +51 -14
- package/src/components/ui/marker/index.ts +1 -0
- package/src/components/ui/marker/marker.tsx +90 -0
- package/src/components/ui/message/cite-ranges.ts +144 -0
- package/src/components/ui/message/index.ts +23 -0
- package/src/components/ui/message/message.tsx +434 -0
- package/src/components/ui/message/parse-message-text.ts +450 -0
- package/src/components/ui/questionnaire/index.ts +10 -0
- package/src/components/ui/questionnaire/questionnaire.tsx +446 -0
- package/src/components/ui/trace/index.ts +9 -0
- package/src/components/ui/trace/trace.tsx +334 -0
- package/src/index.ts +26 -0
- package/src/styles/base.css +34 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { ChevronLeft, ChevronRight } from 'lucide-solid'
|
|
2
|
+
import { createSignal, onCleanup, Show, splitProps } from 'solid-js'
|
|
3
|
+
import { cn } from '../../../lib/utils'
|
|
4
|
+
import { ActionButton } from '../action-button'
|
|
5
|
+
import { Button } from '../../ui/button'
|
|
6
|
+
import type { MessageCitations } from '../../ui/message'
|
|
7
|
+
import { Popover, PopoverContent, PopoverTitle, PopoverTrigger } from '../../ui/popover'
|
|
8
|
+
import { TextLink } from '../../ui/text-link'
|
|
9
|
+
import {
|
|
10
|
+
SOURCE_SNIPPET_LIMIT,
|
|
11
|
+
boundText,
|
|
12
|
+
safeSourceUrl,
|
|
13
|
+
sourceHost,
|
|
14
|
+
sourceTitle,
|
|
15
|
+
type Source,
|
|
16
|
+
} from '../sources/source'
|
|
17
|
+
import {
|
|
18
|
+
citationLabel,
|
|
19
|
+
citationName,
|
|
20
|
+
nextCitationMode,
|
|
21
|
+
wrapIndex,
|
|
22
|
+
type CitationEvent,
|
|
23
|
+
type CitationMode,
|
|
24
|
+
} from './citation'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* InlineCitation.
|
|
28
|
+
*
|
|
29
|
+
* A small badge inside a sentence — "1", or a host — that opens the source(s)
|
|
30
|
+
* behind the claim: title, host, address, snippet, and Previous/Next when several
|
|
31
|
+
* sources share one marker. It is an inline element for prose such as
|
|
32
|
+
* `MessageText`: it never breaks across lines, stays on the text baseline, and
|
|
33
|
+
* adds no block of its own. For the list of everything a reply used, reach for
|
|
34
|
+
* `Sources`; the two number sources the same way.
|
|
35
|
+
*
|
|
36
|
+
* ## One popover, two ways to open it
|
|
37
|
+
*
|
|
38
|
+
* A hover card would exclude everyone who is not holding a mouse, so this is a
|
|
39
|
+
* `Popover` that a pointer can also *peek* at:
|
|
40
|
+
*
|
|
41
|
+
* - **Hover or keyboard focus peeks.** The popover opens without taking focus
|
|
42
|
+
* and closes when the pointer or focus leaves. Touch has no hover, so a touch
|
|
43
|
+
* never peeks.
|
|
44
|
+
* - **Click, tap, Enter or Space pins.** Focus moves into the popover so its
|
|
45
|
+
* link and Previous/Next are reachable; it stays until Escape, an outside press,
|
|
46
|
+
* or the badge is pressed again, and focus returns to the badge. Pressing a badge
|
|
47
|
+
* that is only peeking pins it rather than toggling it shut — the transitions
|
|
48
|
+
* are the pure `nextCitationMode`.
|
|
49
|
+
*
|
|
50
|
+
* Kobalte's trigger toggles on every press, so `open` is controlled and its own
|
|
51
|
+
* close request is ignored for the press that we already decided (`pressing`).
|
|
52
|
+
* Without that, hover-then-click, the commonest pointer path, would close the card.
|
|
53
|
+
*
|
|
54
|
+
* ## It makes no claim without sources
|
|
55
|
+
*
|
|
56
|
+
* No sources renders nothing: a marker that points nowhere is not a citation.
|
|
57
|
+
* The address is a link only when `safeSourceUrl` allows it; the host comes from
|
|
58
|
+
* the parsed URL; titles and snippets are clipped, cleaned of direction
|
|
59
|
+
* overrides and rendered as text. The badge's accessible name carries the title
|
|
60
|
+
* and host (the visible label is only a number or a host), so a reader hears
|
|
61
|
+
* what is cited without opening anything.
|
|
62
|
+
*/
|
|
63
|
+
export type InlineCitationProps = {
|
|
64
|
+
/** One source, or several that support the same claim. */
|
|
65
|
+
sources: readonly Source[]
|
|
66
|
+
/**
|
|
67
|
+
* The 1-based number to show, matching the source's position in `Sources`.
|
|
68
|
+
* Omit it to show the host instead; a number is never guessed.
|
|
69
|
+
*/
|
|
70
|
+
index?: number
|
|
71
|
+
/** Milliseconds a pointer rests before the peek opens. */
|
|
72
|
+
hoverDelay?: number
|
|
73
|
+
class?: string
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const LEAVE_DELAY = 200
|
|
77
|
+
|
|
78
|
+
export function InlineCitation(props: InlineCitationProps) {
|
|
79
|
+
const [local] = splitProps(props, ['sources', 'index', 'hoverDelay', 'class'])
|
|
80
|
+
const [mode, setMode] = createSignal<CitationMode>('closed')
|
|
81
|
+
const [position, setPosition] = createSignal(0)
|
|
82
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
83
|
+
let content: HTMLElement | undefined
|
|
84
|
+
let pressing = false
|
|
85
|
+
// Focus handed back to the badge as the popover closes is not a request to
|
|
86
|
+
// peek again; without this, Escape from a pinned card reopens it as a preview.
|
|
87
|
+
let restoringFocus = false
|
|
88
|
+
|
|
89
|
+
const count = () => local.sources.length
|
|
90
|
+
const active = () => local.sources[wrapIndex(position(), count())]
|
|
91
|
+
const clear = () => {
|
|
92
|
+
if (timer !== undefined) clearTimeout(timer)
|
|
93
|
+
timer = undefined
|
|
94
|
+
}
|
|
95
|
+
onCleanup(clear)
|
|
96
|
+
|
|
97
|
+
const dispatch = (event: CitationEvent) => setMode((current) => nextCitationMode(current, event))
|
|
98
|
+
const schedule = (event: CitationEvent, delay: number) => {
|
|
99
|
+
clear()
|
|
100
|
+
timer = setTimeout(() => dispatch(event), delay)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const onPointerEnter = (event: PointerEvent) => {
|
|
104
|
+
if (event.pointerType === 'touch') return
|
|
105
|
+
schedule('enter', local.hoverDelay ?? 150)
|
|
106
|
+
}
|
|
107
|
+
const onPointerLeave = (event: PointerEvent) => {
|
|
108
|
+
if (event.pointerType === 'touch') return
|
|
109
|
+
schedule('leave', LEAVE_DELAY)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const onClick = () => {
|
|
113
|
+
clear()
|
|
114
|
+
const wasPeeking = mode() === 'peek'
|
|
115
|
+
dispatch('press')
|
|
116
|
+
// Kobalte toggles right after this handler; its request is not a dismissal.
|
|
117
|
+
pressing = true
|
|
118
|
+
queueMicrotask(() => {
|
|
119
|
+
pressing = false
|
|
120
|
+
})
|
|
121
|
+
// The peek never took focus; a deliberate press must hand it over.
|
|
122
|
+
if (wasPeeking) queueMicrotask(() => content?.focus())
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<Show when={count() > 0}>
|
|
127
|
+
<Popover
|
|
128
|
+
open={mode() !== 'closed'}
|
|
129
|
+
onOpenChange={(next) => {
|
|
130
|
+
if (!next && !pressing) dispatch('dismiss')
|
|
131
|
+
}}
|
|
132
|
+
placement="bottom"
|
|
133
|
+
gutter={6}
|
|
134
|
+
>
|
|
135
|
+
<PopoverTrigger
|
|
136
|
+
as={Button}
|
|
137
|
+
variant="subtle"
|
|
138
|
+
size="2xs"
|
|
139
|
+
touchTarget="comfortable"
|
|
140
|
+
data-slot="inline-citation"
|
|
141
|
+
aria-label={citationName(local.sources, local.index)}
|
|
142
|
+
class={cn('mx-0.5 align-baseline tabular-nums', local.class)}
|
|
143
|
+
onClick={onClick}
|
|
144
|
+
onPointerEnter={onPointerEnter}
|
|
145
|
+
onPointerLeave={onPointerLeave}
|
|
146
|
+
onFocus={(event: FocusEvent & { currentTarget: HTMLElement }) => {
|
|
147
|
+
// Mouse focus follows the press; only keyboard focus peeks.
|
|
148
|
+
if (restoringFocus) return
|
|
149
|
+
if (event.currentTarget.matches(':focus-visible')) dispatch('focus')
|
|
150
|
+
}}
|
|
151
|
+
onBlur={() => dispatch('blur')}
|
|
152
|
+
>
|
|
153
|
+
<span aria-hidden="true" class="max-w-32 truncate">
|
|
154
|
+
{citationLabel(local.index, local.sources[0])}
|
|
155
|
+
</span>
|
|
156
|
+
<Show when={count() > 1}>
|
|
157
|
+
<span aria-hidden="true">+{count() - 1}</span>
|
|
158
|
+
</Show>
|
|
159
|
+
</PopoverTrigger>
|
|
160
|
+
<PopoverContent
|
|
161
|
+
ref={(element: HTMLElement) => (content = element)}
|
|
162
|
+
class="w-72 max-w-full"
|
|
163
|
+
onOpenAutoFocus={(event: Event) => {
|
|
164
|
+
if (mode() === 'peek') event.preventDefault()
|
|
165
|
+
}}
|
|
166
|
+
onCloseAutoFocus={() => {
|
|
167
|
+
// The focus move that follows is synchronous; the microtask ends the window.
|
|
168
|
+
restoringFocus = true
|
|
169
|
+
queueMicrotask(() => {
|
|
170
|
+
restoringFocus = false
|
|
171
|
+
})
|
|
172
|
+
}}
|
|
173
|
+
onPointerEnter={(event: PointerEvent) => {
|
|
174
|
+
if (event.pointerType !== 'touch') clear()
|
|
175
|
+
}}
|
|
176
|
+
onPointerLeave={onPointerLeave}
|
|
177
|
+
>
|
|
178
|
+
<Show when={active()}>
|
|
179
|
+
{(source) => (
|
|
180
|
+
<div class="flex min-w-0 flex-col gap-1.5 px-2 py-1.5">
|
|
181
|
+
<Show when={count() > 1}>
|
|
182
|
+
<div class="flex items-center justify-between gap-2">
|
|
183
|
+
<span
|
|
184
|
+
role="status"
|
|
185
|
+
aria-live="polite"
|
|
186
|
+
class="text-xs text-muted-foreground tabular-nums"
|
|
187
|
+
>
|
|
188
|
+
Source {wrapIndex(position(), count()) + 1} of {count()}
|
|
189
|
+
</span>
|
|
190
|
+
<span class="flex items-center gap-0.5">
|
|
191
|
+
<ActionButton
|
|
192
|
+
variant="ghost"
|
|
193
|
+
size="icon-xs"
|
|
194
|
+
aria-label="Previous source"
|
|
195
|
+
tooltip="Previous source"
|
|
196
|
+
onClick={() => setPosition((value) => wrapIndex(value - 1, count()))}
|
|
197
|
+
>
|
|
198
|
+
<ChevronLeft />
|
|
199
|
+
</ActionButton>
|
|
200
|
+
<ActionButton
|
|
201
|
+
variant="ghost"
|
|
202
|
+
size="icon-xs"
|
|
203
|
+
aria-label="Next source"
|
|
204
|
+
tooltip="Next source"
|
|
205
|
+
onClick={() => setPosition((value) => wrapIndex(value + 1, count()))}
|
|
206
|
+
>
|
|
207
|
+
<ChevronRight />
|
|
208
|
+
</ActionButton>
|
|
209
|
+
</span>
|
|
210
|
+
</div>
|
|
211
|
+
</Show>
|
|
212
|
+
<PopoverTitle class="p-0 [overflow-wrap:anywhere]">
|
|
213
|
+
{sourceTitle(source())}
|
|
214
|
+
</PopoverTitle>
|
|
215
|
+
<CitationDetails source={source()} />
|
|
216
|
+
</div>
|
|
217
|
+
)}
|
|
218
|
+
</Show>
|
|
219
|
+
</PopoverContent>
|
|
220
|
+
</Popover>
|
|
221
|
+
</Show>
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Citation markers for `MessageText`, from the sources a reply really used.
|
|
227
|
+
*
|
|
228
|
+
* `[^2]` in the text becomes the second source's badge; `[^1,3]` one badge for
|
|
229
|
+
* sources 1 and 3. A number beyond the list leaves the marker as written, and an
|
|
230
|
+
* empty list makes every marker literal, so text can never cite a source the host
|
|
231
|
+
* did not supply.
|
|
232
|
+
*
|
|
233
|
+
* <MessageText text={reply} citations={messageCitations(sources)} />
|
|
234
|
+
*/
|
|
235
|
+
export function messageCitations(sources: readonly Source[]): MessageCitations {
|
|
236
|
+
return {
|
|
237
|
+
count: sources.length,
|
|
238
|
+
render: (numbers) => (
|
|
239
|
+
<InlineCitation
|
|
240
|
+
index={numbers[0]}
|
|
241
|
+
sources={numbers.flatMap((number) => {
|
|
242
|
+
const source = sources[number - 1]
|
|
243
|
+
return source ? [source] : []
|
|
244
|
+
})}
|
|
245
|
+
/>
|
|
246
|
+
),
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function CitationDetails(props: { source: Source }) {
|
|
251
|
+
const href = () => safeSourceUrl(props.source.url)
|
|
252
|
+
const host = () => sourceHost(props.source.url)
|
|
253
|
+
const snippet = () =>
|
|
254
|
+
props.source.snippet ? boundText(props.source.snippet, SOURCE_SNIPPET_LIMIT) : ''
|
|
255
|
+
const withheld = () => props.source.url !== undefined && props.source.url !== '' && !href()
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
<>
|
|
259
|
+
<Show when={host()}>
|
|
260
|
+
<p class="m-0 text-xs text-muted-foreground [overflow-wrap:anywhere]">{host()}</p>
|
|
261
|
+
</Show>
|
|
262
|
+
<Show when={href()}>
|
|
263
|
+
{(url) => (
|
|
264
|
+
<TextLink
|
|
265
|
+
href={url()}
|
|
266
|
+
target="_blank"
|
|
267
|
+
rel="noreferrer noopener"
|
|
268
|
+
class="w-fit max-w-full text-xs [overflow-wrap:anywhere]"
|
|
269
|
+
>
|
|
270
|
+
{boundText(url(), 96)}
|
|
271
|
+
<span class="visually-hidden"> (opens in a new tab)</span>
|
|
272
|
+
</TextLink>
|
|
273
|
+
)}
|
|
274
|
+
</Show>
|
|
275
|
+
<Show when={withheld()}>
|
|
276
|
+
<p class="m-0 text-xs text-muted-foreground">Address not linked</p>
|
|
277
|
+
</Show>
|
|
278
|
+
<Show when={snippet()}>
|
|
279
|
+
<p class="m-0 line-clamp-6 text-xs text-muted-foreground [overflow-wrap:anywhere]">
|
|
280
|
+
{snippet()}
|
|
281
|
+
</p>
|
|
282
|
+
</Show>
|
|
283
|
+
</>
|
|
284
|
+
)
|
|
285
|
+
}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { History, LoaderCircle } from 'lucide-solid'
|
|
2
|
+
import { createEffect, onCleanup, Show, splitProps, createSignal, type JSX } from 'solid-js'
|
|
3
|
+
import { cn } from '../../../lib/utils'
|
|
4
|
+
import { Button } from '../../ui/button'
|
|
5
|
+
import {
|
|
6
|
+
ConversationSurface,
|
|
7
|
+
type ConversationSurfaceProps,
|
|
8
|
+
} from '../../conversation/conversation-surface'
|
|
9
|
+
import { anchorIds, captureAnchor, restoreAnchor, type ScrollAnchor } from './scroll-anchor'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* MessageScroller.
|
|
13
|
+
*
|
|
14
|
+
* The thread. It owns the three scroll behaviours a chat gets wrong when each
|
|
15
|
+
* is bolted on separately, and keeps them from fighting:
|
|
16
|
+
*
|
|
17
|
+
* 1. **Follow the reply only while the reader is at the bottom.** New and
|
|
18
|
+
* streaming content scrolls into view when the reader is already at the end,
|
|
19
|
+
* and never otherwise. Scrolling up parks the view; the "Jump to latest"
|
|
20
|
+
* control brings it back. (This is `ConversationSurface`'s follow contract,
|
|
21
|
+
* which this component wraps rather than reimplements.)
|
|
22
|
+
* 2. **Load history without moving what the reader is looking at.** When the
|
|
23
|
+
* reader reaches the top, `onLoadOlder` is asked for earlier turns. Turns
|
|
24
|
+
* inserted *above* the viewport would normally push the visible content down
|
|
25
|
+
* by exactly their height; here the first visible turn (found by
|
|
26
|
+
* `data-anchor-id`, which `Message` sets from `anchorId`) is measured before
|
|
27
|
+
* the load and put back to the same offset after it, on every layout change
|
|
28
|
+
* until the load settles — so images and code blocks that size themselves
|
|
29
|
+
* late do not jolt the view either. A "Load earlier" button is always present
|
|
30
|
+
* while there is more, because the scroll trigger alone is unreachable to a
|
|
31
|
+
* keyboard or switch user and fires nothing in a short pane.
|
|
32
|
+
* 3. **Announce without flooding.** The scroller is a polite log, and a turn that
|
|
33
|
+
* is still arriving marks itself `aria-busy`, so a screen reader waits for the
|
|
34
|
+
* settled turn instead of reading every token. `announcement` is a separate
|
|
35
|
+
* status region for the two moments that matter: a response starting and a
|
|
36
|
+
* response finishing.
|
|
37
|
+
*
|
|
38
|
+
* What it does not own: which turns exist, their identity, or what "older"
|
|
39
|
+
* means. The host passes children and a loader; a session switch is
|
|
40
|
+
* `resetKey`, which re-arms follow and discards any in-flight anchor.
|
|
41
|
+
*/
|
|
42
|
+
export type MessageScrollerProps = Omit<ConversationSurfaceProps, 'header' | 'empty'> & {
|
|
43
|
+
/** There are earlier turns the host can load. */
|
|
44
|
+
hasOlder?: boolean
|
|
45
|
+
/** The host is loading earlier turns right now. */
|
|
46
|
+
loadingOlder?: boolean
|
|
47
|
+
/**
|
|
48
|
+
* Load earlier turns. The scroller preserves the reader's position across the
|
|
49
|
+
* resulting insert whether the host resolves before or after rendering them.
|
|
50
|
+
*/
|
|
51
|
+
onLoadOlder?: () => void | Promise<void>
|
|
52
|
+
/** Words for the loader row; override for another language. */
|
|
53
|
+
olderLabel?: string
|
|
54
|
+
loadingOlderLabel?: string
|
|
55
|
+
/** Rendered above the first turn, below the history control. */
|
|
56
|
+
header?: JSX.Element
|
|
57
|
+
/** Rendered when there are no turns. */
|
|
58
|
+
empty?: JSX.Element
|
|
59
|
+
/** A short sentence for the live status region ("Assistant is responding"). */
|
|
60
|
+
announcement?: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** How far from the top counts as "reached the top", in px. */
|
|
64
|
+
const TOP_MARGIN = 160
|
|
65
|
+
|
|
66
|
+
/** How long a host that still reports `loadingOlder` is waited for, in frames (~10 s). */
|
|
67
|
+
const MAX_HOST_WAIT_FRAMES = 600
|
|
68
|
+
|
|
69
|
+
export function MessageScroller(props: MessageScrollerProps) {
|
|
70
|
+
const [local, rest] = splitProps(props, [
|
|
71
|
+
'class',
|
|
72
|
+
'hasOlder',
|
|
73
|
+
'loadingOlder',
|
|
74
|
+
'onLoadOlder',
|
|
75
|
+
'olderLabel',
|
|
76
|
+
'loadingOlderLabel',
|
|
77
|
+
'header',
|
|
78
|
+
'empty',
|
|
79
|
+
'announcement',
|
|
80
|
+
'ref',
|
|
81
|
+
'resetKey',
|
|
82
|
+
'children',
|
|
83
|
+
])
|
|
84
|
+
let scroller: HTMLDivElement | undefined
|
|
85
|
+
let holdFollow: (() => () => void) | undefined
|
|
86
|
+
let epoch = 0
|
|
87
|
+
const [inFlight, setInFlight] = createSignal(false)
|
|
88
|
+
|
|
89
|
+
// While a load holds the reader's place, the browser's own scroll anchoring is
|
|
90
|
+
// switched off: it moves the offset by the same insert, and the two corrections
|
|
91
|
+
// would be added together.
|
|
92
|
+
let nativeAnchoring: string | undefined
|
|
93
|
+
const holdNativeAnchoring = () => {
|
|
94
|
+
if (!scroller || nativeAnchoring !== undefined) return
|
|
95
|
+
nativeAnchoring = scroller.style.overflowAnchor
|
|
96
|
+
scroller.style.overflowAnchor = 'none'
|
|
97
|
+
}
|
|
98
|
+
const releaseNativeAnchoring = () => {
|
|
99
|
+
if (scroller && nativeAnchoring !== undefined) scroller.style.overflowAnchor = nativeAnchoring
|
|
100
|
+
nativeAnchoring = undefined
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// A session switch invalidates a load that was started for the previous one.
|
|
104
|
+
createEffect(() => {
|
|
105
|
+
void local.resetKey
|
|
106
|
+
epoch += 1
|
|
107
|
+
setInFlight(false)
|
|
108
|
+
stopWatching()
|
|
109
|
+
releaseNativeAnchoring()
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const settle = (
|
|
113
|
+
anchor: ScrollAnchor | undefined,
|
|
114
|
+
run: number,
|
|
115
|
+
heightBefore: number,
|
|
116
|
+
restoreFocus: boolean,
|
|
117
|
+
releaseHold?: () => void
|
|
118
|
+
) => {
|
|
119
|
+
const finish = () => {
|
|
120
|
+
if (run !== epoch) return releaseHold?.()
|
|
121
|
+
if (scroller) restoreAnchor(scroller, anchor)
|
|
122
|
+
stopWatching()
|
|
123
|
+
releaseNativeAnchoring()
|
|
124
|
+
setInFlight(false)
|
|
125
|
+
// The control that started the load was replaced by the loading line, which
|
|
126
|
+
// takes focus with it. Put it back on the control (or the thread), so a
|
|
127
|
+
// keyboard user is not dropped onto the page.
|
|
128
|
+
if (restoreFocus && scroller && document.activeElement === document.body) {
|
|
129
|
+
const again = scroller.querySelector<HTMLElement>(
|
|
130
|
+
'[data-slot="message-scroller-older"] button'
|
|
131
|
+
)
|
|
132
|
+
;(again ?? scroller).focus({ preventScroll: true })
|
|
133
|
+
}
|
|
134
|
+
// Still at the top after a load that added content: keep going, so a
|
|
135
|
+
// pane taller than one page of history fills without a second scroll.
|
|
136
|
+
if (
|
|
137
|
+
scroller &&
|
|
138
|
+
local.hasOlder &&
|
|
139
|
+
scroller.scrollTop < TOP_MARGIN &&
|
|
140
|
+
scroller.scrollHeight > heightBefore
|
|
141
|
+
)
|
|
142
|
+
void load()
|
|
143
|
+
releaseHold?.()
|
|
144
|
+
}
|
|
145
|
+
// Two frames: the first lets the host's insert commit, the second lets
|
|
146
|
+
// late-sizing content (images, highlighted code) report its height. A host
|
|
147
|
+
// that says it is still loading (`loadingOlder`) after its promise resolved
|
|
148
|
+
// is not finished either: its turns will land later, and the anchor must
|
|
149
|
+
// still be held when they do.
|
|
150
|
+
const wait = (waited: number) =>
|
|
151
|
+
requestAnimationFrame(() => {
|
|
152
|
+
if (run !== epoch) return releaseHold?.()
|
|
153
|
+
if (local.loadingOlder && waited < MAX_HOST_WAIT_FRAMES) return void wait(waited + 1)
|
|
154
|
+
finish()
|
|
155
|
+
})
|
|
156
|
+
requestAnimationFrame(() => wait(0))
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let observer: MutationObserver | undefined
|
|
160
|
+
let resizer: ResizeObserver | undefined
|
|
161
|
+
let unfollow: (() => void) | undefined
|
|
162
|
+
const stopWatching = () => {
|
|
163
|
+
observer?.disconnect()
|
|
164
|
+
resizer?.disconnect()
|
|
165
|
+
unfollow?.()
|
|
166
|
+
unfollow = undefined
|
|
167
|
+
}
|
|
168
|
+
const load = async (fromControl = false) => {
|
|
169
|
+
if (!scroller || !local.onLoadOlder || !local.hasOlder || local.loadingOlder || inFlight())
|
|
170
|
+
return
|
|
171
|
+
const run = epoch
|
|
172
|
+
const anchor = captureAnchor(scroller)
|
|
173
|
+
anchor.known = anchorIds(scroller)
|
|
174
|
+
const heightBefore = scroller.scrollHeight
|
|
175
|
+
const restoreFocus =
|
|
176
|
+
fromControl && document.activeElement?.closest('[data-slot="message-scroller-older"]') != null
|
|
177
|
+
setInFlight(true)
|
|
178
|
+
holdNativeAnchoring()
|
|
179
|
+
// Every layout change while the load is open re-pins the anchor *before
|
|
180
|
+
// paint*, which is what makes the insert invisible.
|
|
181
|
+
stopWatching()
|
|
182
|
+
observer = new MutationObserver(() => {
|
|
183
|
+
if (run === epoch && scroller) restoreAnchor(scroller, anchor)
|
|
184
|
+
})
|
|
185
|
+
observer.observe(scroller, { childList: true, subtree: true })
|
|
186
|
+
// Turns that size themselves late (`content-visibility`, images, highlighted code)
|
|
187
|
+
// move the anchor without adding or removing a node, and with the browser's own
|
|
188
|
+
// anchoring held off nothing else would carry the reader through that.
|
|
189
|
+
const content = scroller.querySelector('[data-slot="conversation-content"]')
|
|
190
|
+
if (content && typeof ResizeObserver !== 'undefined') {
|
|
191
|
+
resizer = new ResizeObserver(() => {
|
|
192
|
+
if (run === epoch && scroller) restoreAnchor(scroller, anchor)
|
|
193
|
+
})
|
|
194
|
+
resizer.observe(content)
|
|
195
|
+
}
|
|
196
|
+
// The reader may carry on scrolling while the host answers. A restore re-takes the
|
|
197
|
+
// anchor on the turn they have come to, whichever of the observers or this event
|
|
198
|
+
// reaches it first.
|
|
199
|
+
const follow = () => {
|
|
200
|
+
if (run === epoch && scroller) restoreAnchor(scroller, anchor)
|
|
201
|
+
}
|
|
202
|
+
scroller.addEventListener('scroll', follow, { passive: true })
|
|
203
|
+
const watched = scroller
|
|
204
|
+
unfollow = () => watched.removeEventListener('scroll', follow)
|
|
205
|
+
try {
|
|
206
|
+
await local.onLoadOlder()
|
|
207
|
+
} catch {
|
|
208
|
+
// The host reports its own failure; the control stays so the reader can retry.
|
|
209
|
+
}
|
|
210
|
+
// A load that outlived its conversation inserts into a thread whose reader it knows
|
|
211
|
+
// nothing about, and the anchor it held is gone. What it does to the layout (rows
|
|
212
|
+
// taking their height, a clamp, the insert itself) is not the reader scrolling, so the
|
|
213
|
+
// follow position is held until the two frames the insert needs have passed.
|
|
214
|
+
settle(anchor, run, heightBefore, restoreFocus, run !== epoch ? holdFollow?.() : undefined)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
onCleanup(() => {
|
|
218
|
+
epoch += 1
|
|
219
|
+
stopWatching()
|
|
220
|
+
releaseNativeAnchoring()
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
const watchTop = (element: HTMLDivElement) => {
|
|
224
|
+
if (typeof IntersectionObserver === 'undefined') return
|
|
225
|
+
const io = new IntersectionObserver(
|
|
226
|
+
(entries) => {
|
|
227
|
+
if (entries.some((entry) => entry.isIntersecting)) void load()
|
|
228
|
+
},
|
|
229
|
+
{ root: scroller ?? null, rootMargin: `${TOP_MARGIN}px 0px 0px 0px` }
|
|
230
|
+
)
|
|
231
|
+
io.observe(element)
|
|
232
|
+
onCleanup(() => io.disconnect())
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<div
|
|
237
|
+
class={cn('relative flex min-h-0 flex-1 flex-col', local.class)}
|
|
238
|
+
data-slot="message-scroller"
|
|
239
|
+
>
|
|
240
|
+
<ConversationSurface
|
|
241
|
+
role="log"
|
|
242
|
+
aria-live="polite"
|
|
243
|
+
aria-relevant="additions"
|
|
244
|
+
resetKey={local.resetKey}
|
|
245
|
+
onFollowHold={(hold) => {
|
|
246
|
+
holdFollow = hold
|
|
247
|
+
}}
|
|
248
|
+
{...rest}
|
|
249
|
+
ref={(element) => {
|
|
250
|
+
scroller = element
|
|
251
|
+
if (typeof local.ref === 'function') local.ref(element)
|
|
252
|
+
}}
|
|
253
|
+
class="flex-1"
|
|
254
|
+
header={
|
|
255
|
+
<>
|
|
256
|
+
<Show when={local.hasOlder || local.loadingOlder}>
|
|
257
|
+
<div
|
|
258
|
+
ref={watchTop}
|
|
259
|
+
data-slot="message-scroller-older"
|
|
260
|
+
class="flex justify-center py-2"
|
|
261
|
+
>
|
|
262
|
+
<Show
|
|
263
|
+
when={local.loadingOlder || inFlight()}
|
|
264
|
+
fallback={
|
|
265
|
+
<Button
|
|
266
|
+
type="button"
|
|
267
|
+
variant="ghost"
|
|
268
|
+
size="sm"
|
|
269
|
+
class="gap-1.5 text-muted-foreground"
|
|
270
|
+
onClick={() => void load(true)}
|
|
271
|
+
>
|
|
272
|
+
<History aria-hidden="true" />
|
|
273
|
+
{local.olderLabel ?? 'Load earlier messages'}
|
|
274
|
+
</Button>
|
|
275
|
+
}
|
|
276
|
+
>
|
|
277
|
+
<p
|
|
278
|
+
role="status"
|
|
279
|
+
// The button's own height: swapping one for the other must not move
|
|
280
|
+
// the turns below it by even a pixel while the anchor is being held.
|
|
281
|
+
class="flex h-control-sm items-center gap-1.5 text-xs text-muted-foreground"
|
|
282
|
+
>
|
|
283
|
+
<LoaderCircle aria-hidden="true" class="size-3.5 motion-safe:animate-spin" />
|
|
284
|
+
{local.loadingOlderLabel ?? 'Loading earlier messages…'}
|
|
285
|
+
</p>
|
|
286
|
+
</Show>
|
|
287
|
+
</div>
|
|
288
|
+
</Show>
|
|
289
|
+
{local.header}
|
|
290
|
+
</>
|
|
291
|
+
}
|
|
292
|
+
empty={local.empty}
|
|
293
|
+
>
|
|
294
|
+
{local.children}
|
|
295
|
+
</ConversationSurface>
|
|
296
|
+
<p role="status" class="sr-only">
|
|
297
|
+
{local.announcement}
|
|
298
|
+
</p>
|
|
299
|
+
</div>
|
|
300
|
+
)
|
|
301
|
+
}
|