@adecore/agents-react 0.0.1 → 0.17.0-beta.1
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/LICENSE +110 -0
- package/README.md +53 -0
- package/dist/agents/AccountDot.d.ts +4 -0
- package/dist/agents/AccountDot.js +8 -0
- package/dist/agents/AgentIcon.d.ts +6 -0
- package/dist/agents/AgentIcon.js +20 -0
- package/dist/agents/ModelOptionControl.d.ts +15 -0
- package/dist/agents/ModelOptionControl.js +33 -0
- package/dist/agents/ProviderLogo.d.ts +8 -0
- package/dist/agents/ProviderLogo.js +8 -0
- package/dist/agents/account-limits.d.ts +7 -0
- package/dist/agents/account-limits.js +40 -0
- package/dist/agents/accounts.d.ts +22 -0
- package/dist/agents/accounts.js +113 -0
- package/dist/agents/model-name.d.ts +12 -0
- package/dist/agents/model-name.js +56 -0
- package/dist/agents/model-options.d.ts +5 -0
- package/dist/agents/model-options.js +15 -0
- package/dist/agents/provider-paths.d.ts +2 -0
- package/dist/agents/provider-paths.js +8 -0
- package/dist/agents/status-look.d.ts +13 -0
- package/dist/agents/status-look.js +24 -0
- package/dist/chat/account-choice.d.ts +11 -0
- package/dist/chat/account-choice.js +30 -0
- package/dist/chat/actions.d.ts +6 -0
- package/dist/chat/actions.js +68 -0
- package/dist/chat/activity.d.ts +5 -0
- package/dist/chat/activity.js +6 -0
- package/dist/chat/attachments.d.ts +23 -0
- package/dist/chat/attachments.js +86 -0
- package/dist/chat/bookmarks.d.ts +14 -0
- package/dist/chat/bookmarks.js +73 -0
- package/dist/chat/chat-client.d.ts +55 -0
- package/dist/chat/chat-client.js +279 -0
- package/dist/chat/chat-references.d.ts +5 -0
- package/dist/chat/chat-references.js +5 -0
- package/dist/chat/drafts.d.ts +29 -0
- package/dist/chat/drafts.js +116 -0
- package/dist/chat/forks.d.ts +2 -0
- package/dist/chat/forks.js +28 -0
- package/dist/chat/guards.d.ts +12 -0
- package/dist/chat/guards.js +69 -0
- package/dist/chat/logic/ansi.d.ts +13 -0
- package/dist/chat/logic/ansi.js +185 -0
- package/dist/chat/logic/answers.d.ts +1 -0
- package/dist/chat/logic/answers.js +8 -0
- package/dist/chat/logic/bookmarks.d.ts +5 -0
- package/dist/chat/logic/bookmarks.js +33 -0
- package/dist/chat/logic/context-usage.d.ts +12 -0
- package/dist/chat/logic/context-usage.js +21 -0
- package/dist/chat/logic/fork.d.ts +45 -0
- package/dist/chat/logic/fork.js +148 -0
- package/dist/chat/logic/handback.d.ts +2 -0
- package/dist/chat/logic/handback.js +8 -0
- package/dist/chat/logic/json.d.ts +1 -0
- package/dist/chat/logic/json.js +4 -0
- package/dist/chat/logic/limit.d.ts +7 -0
- package/dist/chat/logic/limit.js +34 -0
- package/dist/chat/logic/resume-compaction.d.ts +14 -0
- package/dist/chat/logic/resume-compaction.js +47 -0
- package/dist/chat/logic/scrubber.d.ts +50 -0
- package/dist/chat/logic/scrubber.js +220 -0
- package/dist/chat/logic/thread-cards.d.ts +3 -0
- package/dist/chat/logic/thread-cards.js +59 -0
- package/dist/chat/logic/timeline-copy.d.ts +4 -0
- package/dist/chat/logic/timeline-copy.js +59 -0
- package/dist/chat/logic/timeline-target.d.ts +12 -0
- package/dist/chat/logic/timeline-target.js +34 -0
- package/dist/chat/logic/timeline.d.ts +115 -0
- package/dist/chat/logic/timeline.js +343 -0
- package/dist/chat/logic/tool-catalog.d.ts +10 -0
- package/dist/chat/logic/tool-catalog.js +44 -0
- package/dist/chat/logic/tools.d.ts +15 -0
- package/dist/chat/logic/tools.js +82 -0
- package/dist/chat/logic/welcome.d.ts +2 -0
- package/dist/chat/logic/welcome.js +10 -0
- package/dist/chat/logic/workflow.d.ts +14 -0
- package/dist/chat/logic/workflow.js +47 -0
- package/dist/chat/mentions.d.ts +65 -0
- package/dist/chat/mentions.js +184 -0
- package/dist/chat/persisted-json.d.ts +5 -0
- package/dist/chat/persisted-json.js +58 -0
- package/dist/chat/preferences.d.ts +27 -0
- package/dist/chat/preferences.js +139 -0
- package/dist/chat/quote.d.ts +2 -0
- package/dist/chat/quote.js +25 -0
- package/dist/chat/recent-messages.d.ts +17 -0
- package/dist/chat/recent-messages.js +36 -0
- package/dist/chat/resume-compaction-dismissals.d.ts +3 -0
- package/dist/chat/resume-compaction-dismissals.js +14 -0
- package/dist/chat/runtime-modes.d.ts +4 -0
- package/dist/chat/runtime-modes.js +15 -0
- package/dist/chat/stash.d.ts +24 -0
- package/dist/chat/stash.js +91 -0
- package/dist/chat/subagent-conversation.d.ts +41 -0
- package/dist/chat/subagent-conversation.js +167 -0
- package/dist/chat/subagent-list.d.ts +19 -0
- package/dist/chat/subagent-list.js +106 -0
- package/dist/chat/subagent-support.d.ts +6 -0
- package/dist/chat/subagent-support.js +14 -0
- package/dist/chat/subagent-view.d.ts +35 -0
- package/dist/chat/subagent-view.js +96 -0
- package/dist/chat/thumbnails.d.ts +10 -0
- package/dist/chat/thumbnails.js +89 -0
- package/dist/chat/timeline-flash.d.ts +12 -0
- package/dist/chat/timeline-flash.js +16 -0
- package/dist/chat/timeline-scroll.d.ts +27 -0
- package/dist/chat/timeline-scroll.js +152 -0
- package/dist/chat/ui/AccountPill.d.ts +3 -0
- package/dist/chat/ui/AccountPill.js +23 -0
- package/dist/chat/ui/AnsiOutput.d.ts +5 -0
- package/dist/chat/ui/AnsiOutput.js +9 -0
- package/dist/chat/ui/BookmarkMarker.d.ts +8 -0
- package/dist/chat/ui/BookmarkMarker.js +57 -0
- package/dist/chat/ui/BookmarkSubmenu.d.ts +3 -0
- package/dist/chat/ui/BookmarkSubmenu.js +33 -0
- package/dist/chat/ui/ChatActivity.d.ts +7 -0
- package/dist/chat/ui/ChatActivity.js +84 -0
- package/dist/chat/ui/ChatReferenceChip.d.ts +4 -0
- package/dist/chat/ui/ChatReferenceChip.js +16 -0
- package/dist/chat/ui/CodeBlock.d.ts +4 -0
- package/dist/chat/ui/CodeBlock.js +132 -0
- package/dist/chat/ui/Composer.d.ts +15 -0
- package/dist/chat/ui/Composer.js +856 -0
- package/dist/chat/ui/ComposerInput.d.ts +29 -0
- package/dist/chat/ui/ComposerInput.js +127 -0
- package/dist/chat/ui/DiffPool.d.ts +5 -0
- package/dist/chat/ui/DiffPool.js +26 -0
- package/dist/chat/ui/EditDiff.d.ts +4 -0
- package/dist/chat/ui/EditDiff.js +21 -0
- package/dist/chat/ui/FadingWords.d.ts +3 -0
- package/dist/chat/ui/FadingWords.js +5 -0
- package/dist/chat/ui/ImageView.d.ts +6 -0
- package/dist/chat/ui/ImageView.js +81 -0
- package/dist/chat/ui/LimitState.d.ts +8 -0
- package/dist/chat/ui/LimitState.js +112 -0
- package/dist/chat/ui/Markdown.d.ts +18 -0
- package/dist/chat/ui/Markdown.js +159 -0
- package/dist/chat/ui/MessageActions.d.ts +9 -0
- package/dist/chat/ui/MessageActions.js +53 -0
- package/dist/chat/ui/Pickers.d.ts +21 -0
- package/dist/chat/ui/Pickers.js +111 -0
- package/dist/chat/ui/PromptComposer.d.ts +13 -0
- package/dist/chat/ui/PromptComposer.js +56 -0
- package/dist/chat/ui/QuoteButton.d.ts +7 -0
- package/dist/chat/ui/QuoteButton.js +74 -0
- package/dist/chat/ui/ResumeCompactionDock.d.ts +5 -0
- package/dist/chat/ui/ResumeCompactionDock.js +14 -0
- package/dist/chat/ui/RunSettings.d.ts +28 -0
- package/dist/chat/ui/RunSettings.js +153 -0
- package/dist/chat/ui/Scrubber.d.ts +17 -0
- package/dist/chat/ui/Scrubber.js +118 -0
- package/dist/chat/ui/SubagentControls.d.ts +11 -0
- package/dist/chat/ui/SubagentControls.js +36 -0
- package/dist/chat/ui/SubagentInfo.d.ts +4 -0
- package/dist/chat/ui/SubagentInfo.js +34 -0
- package/dist/chat/ui/SubagentStopButton.d.ts +7 -0
- package/dist/chat/ui/SubagentStopButton.js +34 -0
- package/dist/chat/ui/SubagentTimeline.d.ts +4 -0
- package/dist/chat/ui/SubagentTimeline.js +107 -0
- package/dist/chat/ui/Timeline.d.ts +6 -0
- package/dist/chat/ui/Timeline.js +424 -0
- package/dist/chat/ui/TimelineMenu.d.ts +12 -0
- package/dist/chat/ui/TimelineMenu.js +54 -0
- package/dist/chat/ui/UnifiedDiff.d.ts +11 -0
- package/dist/chat/ui/UnifiedDiff.js +66 -0
- package/dist/chat/ui/UploadThumb.d.ts +4 -0
- package/dist/chat/ui/UploadThumb.js +11 -0
- package/dist/chat/ui/Welcome.d.ts +4 -0
- package/dist/chat/ui/Welcome.js +16 -0
- package/dist/chat/ui/chips.d.ts +4 -0
- package/dist/chat/ui/chips.js +11 -0
- package/dist/chat/ui/code-lines.d.ts +18 -0
- package/dist/chat/ui/code-lines.js +46 -0
- package/dist/chat/ui/code-streaming.d.ts +1 -0
- package/dist/chat/ui/code-streaming.js +3 -0
- package/dist/chat/ui/code-theme.d.ts +3 -0
- package/dist/chat/ui/code-theme.js +12 -0
- package/dist/chat/ui/composer/chips.d.ts +6 -0
- package/dist/chat/ui/composer/chips.js +31 -0
- package/dist/chat/ui/composer/editor.d.ts +11 -0
- package/dist/chat/ui/composer/editor.js +98 -0
- package/dist/chat/ui/composer/insert.d.ts +2 -0
- package/dist/chat/ui/composer/insert.js +19 -0
- package/dist/chat/ui/composer/keys.d.ts +30 -0
- package/dist/chat/ui/composer/keys.js +100 -0
- package/dist/chat/ui/diff-theme.d.ts +2 -0
- package/dist/chat/ui/diff-theme.js +20 -0
- package/dist/chat/ui/file-links.d.ts +5 -0
- package/dist/chat/ui/file-links.js +20 -0
- package/dist/chat/ui/find-reveal.d.ts +7 -0
- package/dist/chat/ui/find-reveal.js +13 -0
- package/dist/chat/ui/full-diff.d.ts +6 -0
- package/dist/chat/ui/full-diff.js +19 -0
- package/dist/chat/ui/icons.d.ts +4 -0
- package/dist/chat/ui/icons.js +20 -0
- package/dist/chat/ui/markdown-blocks.d.ts +6 -0
- package/dist/chat/ui/markdown-blocks.js +103 -0
- package/dist/chat/ui/quote-selection.d.ts +3 -0
- package/dist/chat/ui/quote-selection.js +46 -0
- package/dist/chat/ui/rehype-chips.d.ts +23 -0
- package/dist/chat/ui/rehype-chips.js +32 -0
- package/dist/chat/ui/rehype-fade.d.ts +23 -0
- package/dist/chat/ui/rehype-fade.js +36 -0
- package/dist/chat/ui/remark-html-as-text.d.ts +8 -0
- package/dist/chat/ui/remark-html-as-text.js +26 -0
- package/dist/chat/ui/reply-context.d.ts +5 -0
- package/dist/chat/ui/reply-context.js +2 -0
- package/dist/chat/ui/reveal.d.ts +13 -0
- package/dist/chat/ui/reveal.js +114 -0
- package/dist/chat/ui/rows/ForksRow.d.ts +4 -0
- package/dist/chat/ui/rows/ForksRow.js +38 -0
- package/dist/chat/ui/rows/MessageRows.d.ts +40 -0
- package/dist/chat/ui/rows/MessageRows.js +204 -0
- package/dist/chat/ui/rows/Rows.d.ts +12 -0
- package/dist/chat/ui/rows/Rows.js +61 -0
- package/dist/chat/ui/rows/SubagentRow.d.ts +17 -0
- package/dist/chat/ui/rows/SubagentRow.js +101 -0
- package/dist/chat/ui/rows/TaskRow.d.ts +8 -0
- package/dist/chat/ui/rows/TaskRow.js +63 -0
- package/dist/chat/ui/rows/WorkRows.d.ts +47 -0
- package/dist/chat/ui/rows/WorkRows.js +168 -0
- package/dist/chat/ui/rows/WorkflowRow.d.ts +8 -0
- package/dist/chat/ui/rows/WorkflowRow.js +48 -0
- package/dist/chat/ui/rows/row-rhythm.d.ts +6 -0
- package/dist/chat/ui/rows/row-rhythm.js +34 -0
- package/dist/chat/ui/useToggleSet.d.ts +6 -0
- package/dist/chat/ui/useToggleSet.js +25 -0
- package/dist/host.d.ts +195 -0
- package/dist/host.js +55 -0
- package/dist/lazy.d.ts +6 -0
- package/dist/lazy.js +57 -0
- package/dist/locales/en/agent-chat.json +446 -0
- package/dist/locales/en/agent-prompts.json +42 -0
- package/dist/locales/en/agent-providers.json +131 -0
- package/dist/locales/en/agent-usage.json +131 -0
- package/dist/locales/nl/agent-chat.json +446 -0
- package/dist/locales/nl/agent-prompts.json +42 -0
- package/dist/locales/nl/agent-providers.json +131 -0
- package/dist/locales/nl/agent-usage.json +131 -0
- package/dist/locales.d.ts +5 -0
- package/dist/locales.js +20 -0
- package/dist/mounted-registry.d.ts +7 -0
- package/dist/mounted-registry.js +39 -0
- package/dist/port-transport.d.ts +6 -0
- package/dist/port-transport.js +129 -0
- package/dist/prompts/logic/focus.d.ts +2 -0
- package/dist/prompts/logic/focus.js +23 -0
- package/dist/prompts/logic/keys.d.ts +42 -0
- package/dist/prompts/logic/keys.js +94 -0
- package/dist/prompts/logic/prompts.d.ts +34 -0
- package/dist/prompts/logic/prompts.fixtures.d.ts +5 -0
- package/dist/prompts/logic/prompts.fixtures.js +79 -0
- package/dist/prompts/logic/prompts.js +32 -0
- package/dist/prompts/logic/subjects.d.ts +50 -0
- package/dist/prompts/logic/subjects.js +63 -0
- package/dist/prompts/logic/useFocusAfterAnswer.d.ts +6 -0
- package/dist/prompts/logic/useFocusAfterAnswer.js +28 -0
- package/dist/prompts/logic/usePromptSession.d.ts +24 -0
- package/dist/prompts/logic/usePromptSession.js +87 -0
- package/dist/prompts/ui/ApprovalBody.d.ts +13 -0
- package/dist/prompts/ui/ApprovalBody.js +34 -0
- package/dist/prompts/ui/PromptActions.d.ts +27 -0
- package/dist/prompts/ui/PromptActions.js +18 -0
- package/dist/prompts/ui/PromptCard.d.ts +16 -0
- package/dist/prompts/ui/PromptCard.js +50 -0
- package/dist/prompts/ui/PromptView.d.ts +2 -0
- package/dist/prompts/ui/PromptView.js +47 -0
- package/dist/prompts/ui/QuestionBody.d.ts +9 -0
- package/dist/prompts/ui/QuestionBody.js +87 -0
- package/dist/providers/AccountColors.d.ts +5 -0
- package/dist/providers/AccountColors.js +8 -0
- package/dist/providers/AccountDetail.d.ts +12 -0
- package/dist/providers/AccountDetail.js +69 -0
- package/dist/providers/AccountVariables.d.ts +8 -0
- package/dist/providers/AccountVariables.js +43 -0
- package/dist/providers/AddAccountForm.d.ts +10 -0
- package/dist/providers/AddAccountForm.js +54 -0
- package/dist/providers/CliDetail.d.ts +12 -0
- package/dist/providers/CliDetail.js +54 -0
- package/dist/providers/ProvidersPane.d.ts +9 -0
- package/dist/providers/ProvidersPane.js +86 -0
- package/dist/providers/account-actions.d.ts +7 -0
- package/dist/providers/account-actions.js +57 -0
- package/dist/providers/parts.d.ts +9 -0
- package/dist/providers/parts.js +18 -0
- package/dist/providers/provider-abilities.d.ts +2 -0
- package/dist/providers/provider-abilities.js +7 -0
- package/dist/providers/variables.d.ts +13 -0
- package/dist/providers/variables.js +63 -0
- package/dist/scope.d.ts +11 -0
- package/dist/scope.js +10 -0
- package/dist/settings/sections.d.ts +13 -0
- package/dist/settings/sections.js +32 -0
- package/dist/state/chats.d.ts +44 -0
- package/dist/state/chats.js +199 -0
- package/dist/state/provider-accounts.d.ts +17 -0
- package/dist/state/provider-accounts.js +67 -0
- package/dist/state/providers.d.ts +17 -0
- package/dist/state/providers.js +33 -0
- package/dist/state/usage.d.ts +44 -0
- package/dist/state/usage.js +121 -0
- package/dist/storage.d.ts +20 -0
- package/dist/storage.js +89 -0
- package/dist/theme.css +196 -0
- package/dist/transport.d.ts +24 -0
- package/dist/transport.js +18 -0
- package/dist/ui/PlainTextarea.d.ts +2 -0
- package/dist/ui/PlainTextarea.js +5 -0
- package/dist/usage/LimitsList.d.ts +18 -0
- package/dist/usage/LimitsList.js +73 -0
- package/dist/usage/UsageBreakdown.d.ts +9 -0
- package/dist/usage/UsageBreakdown.js +67 -0
- package/dist/usage/UsageChart.d.ts +10 -0
- package/dist/usage/UsageChart.js +47 -0
- package/dist/usage/UsageDialog.d.ts +6 -0
- package/dist/usage/UsageDialog.js +12 -0
- package/dist/usage/UsageLimits.d.ts +1 -0
- package/dist/usage/UsageLimits.js +57 -0
- package/dist/usage/UsageLimitsCard.d.ts +4 -0
- package/dist/usage/UsageLimitsCard.js +26 -0
- package/dist/usage/UsagePage.d.ts +7 -0
- package/dist/usage/UsagePage.js +154 -0
- package/dist/usage/UsagePane.d.ts +3 -0
- package/dist/usage/UsagePane.js +21 -0
- package/dist/usage/UsageSummary.d.ts +12 -0
- package/dist/usage/UsageSummary.js +15 -0
- package/dist/usage/UsageTiles.d.ts +7 -0
- package/dist/usage/UsageTiles.js +19 -0
- package/dist/usage/format.d.ts +13 -0
- package/dist/usage/format.js +55 -0
- package/dist/usage/limit-groups.d.ts +22 -0
- package/dist/usage/limit-groups.js +86 -0
- package/dist/usage/limits.d.ts +4 -0
- package/dist/usage/limits.js +58 -0
- package/dist/usage/money.d.ts +1 -0
- package/dist/usage/money.js +13 -0
- package/dist/usage/summary.d.ts +34 -0
- package/dist/usage/summary.js +94 -0
- package/package.json +895 -3
- package/src/agents/AccountDot.tsx +14 -0
- package/src/agents/AgentIcon.tsx +26 -0
- package/src/agents/ModelOptionControl.tsx +68 -0
- package/src/agents/ProviderLogo.tsx +20 -0
- package/src/agents/account-limits.ts +61 -0
- package/src/agents/accounts.ts +137 -0
- package/src/agents/model-name.ts +70 -0
- package/src/agents/model-options.ts +21 -0
- package/src/agents/provider-paths.ts +10 -0
- package/src/agents/status-look.ts +43 -0
- package/src/chat/account-choice.ts +44 -0
- package/src/chat/actions.ts +72 -0
- package/src/chat/activity.ts +8 -0
- package/src/chat/attachments.ts +118 -0
- package/src/chat/bookmarks.ts +87 -0
- package/src/chat/chat-client.ts +345 -0
- package/src/chat/chat-references.ts +11 -0
- package/src/chat/drafts.ts +165 -0
- package/src/chat/forks.ts +33 -0
- package/src/chat/guards.ts +84 -0
- package/src/chat/logic/ansi.ts +206 -0
- package/src/chat/logic/answers.ts +8 -0
- package/src/chat/logic/bookmarks.ts +42 -0
- package/src/chat/logic/context-usage.ts +36 -0
- package/src/chat/logic/fork.ts +200 -0
- package/src/chat/logic/handback.ts +10 -0
- package/src/chat/logic/json.ts +4 -0
- package/src/chat/logic/limit.ts +46 -0
- package/src/chat/logic/resume-compaction.ts +71 -0
- package/src/chat/logic/scrubber.ts +289 -0
- package/src/chat/logic/thread-cards.ts +64 -0
- package/src/chat/logic/timeline-copy.ts +65 -0
- package/src/chat/logic/timeline-target.ts +52 -0
- package/src/chat/logic/timeline.ts +419 -0
- package/src/chat/logic/tool-catalog.ts +62 -0
- package/src/chat/logic/tools.ts +102 -0
- package/src/chat/logic/welcome.ts +13 -0
- package/src/chat/logic/workflow.ts +66 -0
- package/src/chat/mentions.ts +232 -0
- package/src/chat/persisted-json.ts +67 -0
- package/src/chat/preferences.ts +179 -0
- package/src/chat/quote.ts +26 -0
- package/src/chat/recent-messages.ts +59 -0
- package/src/chat/resume-compaction-dismissals.ts +17 -0
- package/src/chat/runtime-modes.ts +18 -0
- package/src/chat/stash.ts +122 -0
- package/src/chat/subagent-conversation.ts +203 -0
- package/src/chat/subagent-list.ts +141 -0
- package/src/chat/subagent-support.ts +21 -0
- package/src/chat/subagent-view.ts +137 -0
- package/src/chat/thumbnails.ts +108 -0
- package/src/chat/timeline-flash.ts +33 -0
- package/src/chat/timeline-scroll.ts +199 -0
- package/src/chat/ui/AccountPill.tsx +31 -0
- package/src/chat/ui/AnsiOutput.tsx +18 -0
- package/src/chat/ui/BookmarkMarker.tsx +108 -0
- package/src/chat/ui/BookmarkSubmenu.tsx +53 -0
- package/src/chat/ui/ChatActivity.tsx +198 -0
- package/src/chat/ui/ChatReferenceChip.tsx +28 -0
- package/src/chat/ui/CodeBlock.tsx +175 -0
- package/src/chat/ui/Composer.tsx +1326 -0
- package/src/chat/ui/ComposerInput.tsx +187 -0
- package/src/chat/ui/DiffPool.tsx +36 -0
- package/src/chat/ui/EditDiff.tsx +30 -0
- package/src/chat/ui/FadingWords.tsx +18 -0
- package/src/chat/ui/ImageView.tsx +159 -0
- package/src/chat/ui/LimitState.tsx +165 -0
- package/src/chat/ui/Markdown.tsx +258 -0
- package/src/chat/ui/MessageActions.tsx +88 -0
- package/src/chat/ui/Pickers.tsx +272 -0
- package/src/chat/ui/PromptComposer.tsx +119 -0
- package/src/chat/ui/QuoteButton.tsx +103 -0
- package/src/chat/ui/ResumeCompactionDock.tsx +28 -0
- package/src/chat/ui/RunSettings.tsx +546 -0
- package/src/chat/ui/Scrubber.tsx +265 -0
- package/src/chat/ui/SubagentControls.tsx +66 -0
- package/src/chat/ui/SubagentInfo.tsx +67 -0
- package/src/chat/ui/SubagentStopButton.tsx +45 -0
- package/src/chat/ui/SubagentTimeline.tsx +170 -0
- package/src/chat/ui/Timeline.tsx +628 -0
- package/src/chat/ui/TimelineMenu.tsx +124 -0
- package/src/chat/ui/UnifiedDiff.tsx +99 -0
- package/src/chat/ui/UploadThumb.tsx +13 -0
- package/src/chat/ui/Welcome.tsx +22 -0
- package/src/chat/ui/chips.ts +15 -0
- package/src/chat/ui/code-lines.ts +60 -0
- package/src/chat/ui/code-streaming.ts +4 -0
- package/src/chat/ui/code-theme.ts +15 -0
- package/src/chat/ui/composer/chips.ts +43 -0
- package/src/chat/ui/composer/editor.ts +118 -0
- package/src/chat/ui/composer/insert.ts +22 -0
- package/src/chat/ui/composer/keys.ts +144 -0
- package/src/chat/ui/diff-theme.ts +23 -0
- package/src/chat/ui/file-links.ts +24 -0
- package/src/chat/ui/find-reveal.ts +23 -0
- package/src/chat/ui/full-diff.ts +25 -0
- package/src/chat/ui/icons.tsx +24 -0
- package/src/chat/ui/markdown-blocks.ts +123 -0
- package/src/chat/ui/quote-selection.ts +53 -0
- package/src/chat/ui/rehype-chips.ts +62 -0
- package/src/chat/ui/rehype-fade.ts +64 -0
- package/src/chat/ui/remark-html-as-text.ts +36 -0
- package/src/chat/ui/reply-context.ts +4 -0
- package/src/chat/ui/reveal.ts +135 -0
- package/src/chat/ui/rows/ForksRow.tsx +79 -0
- package/src/chat/ui/rows/MessageRows.tsx +415 -0
- package/src/chat/ui/rows/Rows.tsx +81 -0
- package/src/chat/ui/rows/SubagentRow.tsx +209 -0
- package/src/chat/ui/rows/TaskRow.tsx +91 -0
- package/src/chat/ui/rows/WorkRows.tsx +445 -0
- package/src/chat/ui/rows/WorkflowRow.tsx +120 -0
- package/src/chat/ui/rows/row-rhythm.ts +37 -0
- package/src/chat/ui/useToggleSet.ts +39 -0
- package/src/host.ts +324 -0
- package/src/lazy.tsx +64 -0
- package/src/locales/en/agent-chat.json +446 -0
- package/src/locales/en/agent-prompts.json +42 -0
- package/src/locales/en/agent-providers.json +131 -0
- package/src/locales/en/agent-usage.json +131 -0
- package/src/locales/nl/agent-chat.json +446 -0
- package/src/locales/nl/agent-prompts.json +42 -0
- package/src/locales/nl/agent-providers.json +131 -0
- package/src/locales/nl/agent-usage.json +131 -0
- package/src/locales.ts +33 -0
- package/src/mounted-registry.ts +45 -0
- package/src/port-transport.ts +146 -0
- package/src/prompts/logic/focus.ts +25 -0
- package/src/prompts/logic/keys.ts +116 -0
- package/src/prompts/logic/prompts.fixtures.ts +84 -0
- package/src/prompts/logic/prompts.ts +58 -0
- package/src/prompts/logic/subjects.ts +122 -0
- package/src/prompts/logic/useFocusAfterAnswer.ts +38 -0
- package/src/prompts/logic/usePromptSession.ts +114 -0
- package/src/prompts/ui/ApprovalBody.tsx +109 -0
- package/src/prompts/ui/PromptActions.tsx +109 -0
- package/src/prompts/ui/PromptCard.tsx +101 -0
- package/src/prompts/ui/PromptView.tsx +111 -0
- package/src/prompts/ui/QuestionBody.tsx +174 -0
- package/src/providers/AccountColors.tsx +8 -0
- package/src/providers/AccountDetail.tsx +209 -0
- package/src/providers/AccountVariables.tsx +126 -0
- package/src/providers/AddAccountForm.tsx +121 -0
- package/src/providers/CliDetail.tsx +157 -0
- package/src/providers/ProvidersPane.tsx +236 -0
- package/src/providers/account-actions.ts +67 -0
- package/src/providers/parts.tsx +25 -0
- package/src/providers/provider-abilities.ts +9 -0
- package/src/providers/variables.ts +87 -0
- package/src/scope.ts +30 -0
- package/src/settings/sections.ts +47 -0
- package/src/state/chats.ts +279 -0
- package/src/state/provider-accounts.ts +87 -0
- package/src/state/providers.ts +50 -0
- package/src/state/usage.ts +196 -0
- package/src/storage.ts +113 -0
- package/src/theme.css +196 -0
- package/src/transport.ts +50 -0
- package/src/ui/PlainTextarea.tsx +6 -0
- package/src/usage/LimitsList.tsx +232 -0
- package/src/usage/UsageBreakdown.tsx +210 -0
- package/src/usage/UsageChart.tsx +113 -0
- package/src/usage/UsageDialog.tsx +20 -0
- package/src/usage/UsageLimits.tsx +145 -0
- package/src/usage/UsageLimitsCard.tsx +36 -0
- package/src/usage/UsagePage.tsx +276 -0
- package/src/usage/UsagePane.tsx +52 -0
- package/src/usage/UsageSummary.tsx +50 -0
- package/src/usage/UsageTiles.tsx +34 -0
- package/src/usage/format.ts +71 -0
- package/src/usage/limit-groups.ts +119 -0
- package/src/usage/limits.ts +71 -0
- package/src/usage/money.ts +14 -0
- package/src/usage/summary.ts +137 -0
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type ReactNode } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
4
|
+
import clsx from 'clsx';
|
|
5
|
+
import { LoaderCircle } from 'lucide-react';
|
|
6
|
+
import type { ChatBookmark } from '@adecore/agent-contracts';
|
|
7
|
+
import { useForkedTurns } from '../forks';
|
|
8
|
+
import { bookmarkRows } from '../logic/bookmarks';
|
|
9
|
+
import { deriveTimelineRows, findSubagentBranch, type TimelineRow } from '../logic/timeline';
|
|
10
|
+
import { withThreadCards } from '../logic/thread-cards';
|
|
11
|
+
import { openFromMain, useSubagentTrail, type SubagentStep } from '../subagent-view';
|
|
12
|
+
import {
|
|
13
|
+
registerItemJumper,
|
|
14
|
+
registerMessageStepper,
|
|
15
|
+
registerTimeline,
|
|
16
|
+
registerTurnJumper,
|
|
17
|
+
setTimelineAtEnd,
|
|
18
|
+
wantsEarlier,
|
|
19
|
+
type ReadingAnchor,
|
|
20
|
+
type TurnTarget
|
|
21
|
+
} from '../timeline-scroll';
|
|
22
|
+
import { useTimelineFlash } from '../timeline-flash';
|
|
23
|
+
import {
|
|
24
|
+
SCRUBBER_MIN_TICKS,
|
|
25
|
+
STRIP_INSET_PX,
|
|
26
|
+
STRIP_WIDTH_PX,
|
|
27
|
+
messagesInView,
|
|
28
|
+
stepMessage,
|
|
29
|
+
threadPaddingLeft,
|
|
30
|
+
tickOfRow,
|
|
31
|
+
ticksOf,
|
|
32
|
+
ticksWithHits
|
|
33
|
+
} from '../logic/scrubber';
|
|
34
|
+
import { EMPTY_TARGET, readTimelineTarget, withCurrentText, type TimelineTarget } from '../logic/timeline-target';
|
|
35
|
+
import { forkRefusal } from '../logic/fork';
|
|
36
|
+
import { FindRevealContext } from './find-reveal';
|
|
37
|
+
import { BookmarkMarker } from './BookmarkMarker';
|
|
38
|
+
import { MessageActions } from './MessageActions';
|
|
39
|
+
import { Scrubber, type CardChat } from './Scrubber';
|
|
40
|
+
import { TimelineMenuPopup } from './TimelineMenu';
|
|
41
|
+
import { FOLLOW_THRESHOLD_PX, replyHeader, rowRhythm } from './rows/row-rhythm';
|
|
42
|
+
import { QuoteButton } from './QuoteButton';
|
|
43
|
+
import { QuoteTakerContext, type QuoteTaker } from './quote-selection';
|
|
44
|
+
import { useToggleSet } from './useToggleSet';
|
|
45
|
+
import { WelcomeGreeting } from './Welcome';
|
|
46
|
+
import { Row } from './rows/Rows';
|
|
47
|
+
import { ReplyHeader } from './rows/MessageRows';
|
|
48
|
+
import { useChatRow, useChats } from '../../state/chats';
|
|
49
|
+
import { chatHost } from '../../host';
|
|
50
|
+
import { useChatScope } from '../../scope';
|
|
51
|
+
import { FileLinkContext } from './file-links';
|
|
52
|
+
import { AgentIcon } from '../../agents/AgentIcon';
|
|
53
|
+
import { useModelName } from '../../agents/model-name';
|
|
54
|
+
import { ContextMenu, EmptyState, ErrorBoundary, Icon, SectionLabel } from '@adecore/ui';
|
|
55
|
+
|
|
56
|
+
const ESTIMATED_ROW_PX = 56;
|
|
57
|
+
|
|
58
|
+
/*
|
|
59
|
+
* A thread before its first message: which model answers, what the lines into this node let it read,
|
|
60
|
+
* and the folder it works in, so a first question can lean on all three.
|
|
61
|
+
*/
|
|
62
|
+
function EmptyThread({ chatId }: { chatId: string }) {
|
|
63
|
+
const { t } = useTranslation('agent-chat');
|
|
64
|
+
const info = useChatRow(chatId, (row) => row?.info ?? null);
|
|
65
|
+
const model = useModelName(info?.provider, info?.selection.model ?? '');
|
|
66
|
+
const sources = chatHost().useContextSources(chatId);
|
|
67
|
+
const hidesFolder = chatHost().useHidesFolder(chatId);
|
|
68
|
+
return (
|
|
69
|
+
<div className="flex grow items-center justify-center">
|
|
70
|
+
<div className="flex max-w-sm flex-col items-center gap-3 px-6 py-8 text-center">
|
|
71
|
+
<EmptyState icon={info ? <AgentIcon kind={info.provider} /> : undefined} className="p-0">
|
|
72
|
+
{info ? t('timeline.empty.ready', { model }) : t('timeline.empty.ask')}
|
|
73
|
+
</EmptyState>
|
|
74
|
+
{sources.length > 0 && (
|
|
75
|
+
<div className="flex max-w-full flex-col items-center gap-1">
|
|
76
|
+
<SectionLabel>{t('timeline.empty.canRead')}</SectionLabel>
|
|
77
|
+
<span className="max-w-full text-xs text-text-muted">{sources.map((source) => source.title).join(', ')}</span>
|
|
78
|
+
</div>
|
|
79
|
+
)}
|
|
80
|
+
{info?.cwd && !hidesFolder && (
|
|
81
|
+
<div className="flex max-w-full flex-col items-center gap-1">
|
|
82
|
+
<SectionLabel>{t('timeline.empty.worksIn')}</SectionLabel>
|
|
83
|
+
<span className="max-w-full font-mono text-xs break-all text-text-muted">{info.cwd}</span>
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/*
|
|
92
|
+
* A row of the thread behind the chapter line of its bookmark. A message, the only row that takes a
|
|
93
|
+
* bookmark, gets its actions under it; every other row carries a bookmark only for a reply it folds.
|
|
94
|
+
*/
|
|
95
|
+
function MarkableRow({ row, chatId, bookmark, children }: { row: TimelineRow; chatId: string; bookmark: ChatBookmark | null; children: ReactNode }) {
|
|
96
|
+
if (row.kind !== 'user' && row.kind !== 'assistant') {
|
|
97
|
+
return (
|
|
98
|
+
<>
|
|
99
|
+
{bookmark !== null && <BookmarkMarker chatId={chatId} itemId={bookmark.itemId} bookmark={bookmark} />}
|
|
100
|
+
{children}
|
|
101
|
+
</>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return (
|
|
105
|
+
<>
|
|
106
|
+
<BookmarkMarker chatId={chatId} itemId={row.id} bookmark={bookmark} />
|
|
107
|
+
<div className="group/message">
|
|
108
|
+
{children}
|
|
109
|
+
<MessageActions chatId={chatId} row={row} />
|
|
110
|
+
</div>
|
|
111
|
+
</>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/*
|
|
116
|
+
* `overlay` stands in the thread's place and ends where the composer starts, so the thread keeps
|
|
117
|
+
* where it was underneath and the composer stays in reach for the prompts it answers.
|
|
118
|
+
*/
|
|
119
|
+
export function Timeline({ chatId, composer, overlay }: { chatId: string; composer?: ReactNode; overlay?: ReactNode }) {
|
|
120
|
+
const { t } = useTranslation('agent-chat');
|
|
121
|
+
const order = useChatRow(chatId, (row) => row?.order);
|
|
122
|
+
// The structure, not the items. A delta growing a reply must not derive every row again.
|
|
123
|
+
const items = useChatRow(chatId, (row) => row?.structure);
|
|
124
|
+
const activeTurnId = useChatRow(chatId, (row) => row?.info.activeTurnId ?? null);
|
|
125
|
+
const bookmarks = useChatRow(chatId, (row) => row?.bookmarks);
|
|
126
|
+
const forkedTurns = useForkedTurns(chatId);
|
|
127
|
+
const info = useChatRow(chatId, (row) => row?.info ?? null);
|
|
128
|
+
const cursor = useChatRow(chatId, (row) => row?.history?.cursor ?? null);
|
|
129
|
+
const scope = useChatScope();
|
|
130
|
+
const { id: scopeId, keyOf } = scope;
|
|
131
|
+
// Read when the menu opens rather than subscribed to, for the same reason the rows use the structure.
|
|
132
|
+
const fullItems = () => useChats.getState().byKey[keyOf(chatId)]?.items;
|
|
133
|
+
const scrollRef = useRef<HTMLDivElement>(null);
|
|
134
|
+
const threadRef = useRef<HTMLDivElement>(null);
|
|
135
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
136
|
+
const quoteTakerRef = useRef<QuoteTaker | null>(null);
|
|
137
|
+
const registerQuoteTaker = useCallback((take: QuoteTaker) => {
|
|
138
|
+
quoteTakerRef.current = take;
|
|
139
|
+
return () => {
|
|
140
|
+
if (quoteTakerRef.current === take) {
|
|
141
|
+
quoteTakerRef.current = null;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}, []);
|
|
145
|
+
const followRef = useRef(true);
|
|
146
|
+
const stopFollowing = useCallback(() => {
|
|
147
|
+
followRef.current = false;
|
|
148
|
+
}, []);
|
|
149
|
+
const groups = useToggleSet(stopFollowing);
|
|
150
|
+
const turns = useToggleSet(stopFollowing);
|
|
151
|
+
const subagents = useToggleSet(stopFollowing);
|
|
152
|
+
const scrollGeometry = useRef({ top: 0, height: 0, viewport: 0 });
|
|
153
|
+
const [target, setTarget] = useState<TimelineTarget>(EMPTY_TARGET);
|
|
154
|
+
const { trail, show } = useSubagentTrail(chatId);
|
|
155
|
+
const frameRef = useRef<HTMLDivElement>(null);
|
|
156
|
+
const composerRef = useRef<HTMLDivElement>(null);
|
|
157
|
+
const [composerHeight, setComposerHeight] = useState(0);
|
|
158
|
+
const [frame, setFrame] = useState({ width: 0, height: 0, column: false });
|
|
159
|
+
const [loadingEarlier, setLoadingEarlier] = useState(false);
|
|
160
|
+
const loadingEarlierRef = useRef(false);
|
|
161
|
+
// The row being read and where it stood, taken after every render, so a page going in above puts it back.
|
|
162
|
+
const anchorRef = useRef<ReadingAnchor | null>(null);
|
|
163
|
+
const headRef = useRef<string | undefined>(undefined);
|
|
164
|
+
|
|
165
|
+
const loadEarlier = useCallback(() => {
|
|
166
|
+
if (cursor === null || loadingEarlierRef.current) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
loadingEarlierRef.current = true;
|
|
170
|
+
setLoadingEarlier(true);
|
|
171
|
+
// A page that failed is asked for again on the next scroll; the thread keeps what it has.
|
|
172
|
+
void scope.chats
|
|
173
|
+
.loadEarlier(chatId, cursor)
|
|
174
|
+
.catch(() => undefined)
|
|
175
|
+
.finally(() => {
|
|
176
|
+
loadingEarlierRef.current = false;
|
|
177
|
+
setLoadingEarlier(false);
|
|
178
|
+
});
|
|
179
|
+
}, [scope, chatId, cursor]);
|
|
180
|
+
|
|
181
|
+
const threadRows = useMemo(() => {
|
|
182
|
+
if (!order || !items) {
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
return deriveTimelineRows(
|
|
186
|
+
order.map((id) => items[id]!),
|
|
187
|
+
{ expandedGroups: groups.ids, expandedTurns: turns.ids, expandedSubagents: subagents.ids, activeTurnId, forkedTurns }
|
|
188
|
+
);
|
|
189
|
+
}, [order, items, groups.ids, turns.ids, subagents.ids, activeTurnId, forkedTurns]);
|
|
190
|
+
const cards = chatHost().useThreadCards(scopeId, chatId);
|
|
191
|
+
const rows = useMemo(() => withThreadCards(threadRows, cards), [threadRows, cards]);
|
|
192
|
+
const welcomes = chatHost().useWelcome(chatId);
|
|
193
|
+
|
|
194
|
+
const empty = rows.length === 0;
|
|
195
|
+
// A chat that took a turn before is one whose thread has not come in yet, not one to greet.
|
|
196
|
+
const welcome = welcomes && empty && (info === null || (info.usage.turns === 0 && info.activeTurnId === null));
|
|
197
|
+
const marks = useMemo(() => bookmarkRows(rows, bookmarks ?? [], items ?? {}), [rows, bookmarks, items]);
|
|
198
|
+
const ticks = useMemo(() => ticksOf(rows, marks), [rows, marks]);
|
|
199
|
+
// A message a jump is on its way to; it may first have to open the turn its reply folded into.
|
|
200
|
+
const [seeking, setSeeking] = useState<string | null>(null);
|
|
201
|
+
// A turn a jump is on its way to, which waits for the page its messages are on to come in.
|
|
202
|
+
const [revealing, setRevealing] = useState<{ turnId: string; target: TurnTarget } | null>(null);
|
|
203
|
+
const flash = useTimelineFlash((s) => s.target);
|
|
204
|
+
|
|
205
|
+
// Whether the thread's text clears the strip depends on the width of the view, which only the thread's own frame tells.
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
const element = frameRef.current;
|
|
208
|
+
if (element === null || typeof ResizeObserver === 'undefined') {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const column = element.closest('.chat-column') !== null;
|
|
212
|
+
const measure = (): void =>
|
|
213
|
+
setFrame((current) =>
|
|
214
|
+
current.width === element.offsetWidth && current.height === element.offsetHeight && current.column === column
|
|
215
|
+
? current
|
|
216
|
+
: { width: element.offsetWidth, height: element.offsetHeight, column }
|
|
217
|
+
);
|
|
218
|
+
const observer = new ResizeObserver(measure);
|
|
219
|
+
observer.observe(element);
|
|
220
|
+
measure();
|
|
221
|
+
return () => observer.disconnect();
|
|
222
|
+
}, []);
|
|
223
|
+
|
|
224
|
+
useLayoutEffect(() => {
|
|
225
|
+
const element = composerRef.current;
|
|
226
|
+
if (element === null) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const measure = () => setComposerHeight(element.offsetHeight);
|
|
230
|
+
const observer = new ResizeObserver(measure);
|
|
231
|
+
observer.observe(element);
|
|
232
|
+
measure();
|
|
233
|
+
return () => observer.disconnect();
|
|
234
|
+
}, []);
|
|
235
|
+
|
|
236
|
+
// Oversized prompts scroll in full instead of sticking with their top outside the viewport.
|
|
237
|
+
const stickyComposer = !welcome && composerHeight < frame.height - FOLLOW_THRESHOLD_PX;
|
|
238
|
+
const coveredHeight = stickyComposer ? composerHeight : 0;
|
|
239
|
+
const coveredHeightRef = useRef(coveredHeight);
|
|
240
|
+
coveredHeightRef.current = coveredHeight;
|
|
241
|
+
|
|
242
|
+
// The composer pages through the thread with PageUp and PageDown; this is the element it moves.
|
|
243
|
+
useEffect(
|
|
244
|
+
() =>
|
|
245
|
+
registerTimeline(
|
|
246
|
+
chatId,
|
|
247
|
+
scrollRef.current,
|
|
248
|
+
(enabled) => {
|
|
249
|
+
followRef.current = enabled;
|
|
250
|
+
},
|
|
251
|
+
() => coveredHeightRef.current
|
|
252
|
+
),
|
|
253
|
+
[chatId]
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// The client does not run the React Compiler, so its memoization rule has nothing to break here.
|
|
257
|
+
// oxlint-disable-next-line react/incompatible-library
|
|
258
|
+
const virtualizer = useVirtualizer({
|
|
259
|
+
count: rows.length,
|
|
260
|
+
getScrollElement: () => scrollRef.current,
|
|
261
|
+
estimateSize: () => ESTIMATED_ROW_PX,
|
|
262
|
+
getItemKey: (index) => rows[index]!.id,
|
|
263
|
+
overscan: 8,
|
|
264
|
+
scrollPaddingEnd: coveredHeight
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// A change in what the last row says (a streaming delta) must also pull the view down.
|
|
268
|
+
const lastRow = rows[rows.length - 1];
|
|
269
|
+
const lastReplyLength = useChatRow(chatId, (row) => {
|
|
270
|
+
const item = lastRow?.kind === 'assistant' ? row?.items[lastRow.id] : undefined;
|
|
271
|
+
return item?.kind === 'assistant' ? item.text.length : null;
|
|
272
|
+
});
|
|
273
|
+
const tail = lastReplyLength ?? rows.length;
|
|
274
|
+
/*
|
|
275
|
+
* The rows are measured as they render, so the end of the list moves while it is drawn: a diff
|
|
276
|
+
* that highlights, an image that loads, a tool row that grows a line of output. `scrollToIndex`
|
|
277
|
+
* aims at where the row was when it was asked, which is what left the thread short of the end.
|
|
278
|
+
* The scroller's own bottom is a fact rather than an estimate, and the total size changing is
|
|
279
|
+
* what says a row was measured again, so this runs for every one of those.
|
|
280
|
+
*/
|
|
281
|
+
const totalSize = virtualizer.getTotalSize();
|
|
282
|
+
useLayoutEffect(() => {
|
|
283
|
+
const element = scrollRef.current;
|
|
284
|
+
if (!followRef.current || element === null) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
// The composer is in the scroll flow, so its measured height is part of this end position.
|
|
288
|
+
element.scrollTop = element.scrollHeight;
|
|
289
|
+
scrollGeometry.current = { top: element.scrollTop, height: element.scrollHeight, viewport: element.clientHeight };
|
|
290
|
+
}, [rows.length, tail, totalSize, composerHeight, frame.height]);
|
|
291
|
+
|
|
292
|
+
// A page that went in above moves every row down by what it measures; the reader stays on the row they were on.
|
|
293
|
+
useLayoutEffect(() => {
|
|
294
|
+
const element = scrollRef.current;
|
|
295
|
+
const head = order?.[0];
|
|
296
|
+
const moved = head !== headRef.current;
|
|
297
|
+
headRef.current = head;
|
|
298
|
+
if (element === null) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const anchor = anchorRef.current;
|
|
302
|
+
const index = moved && anchor !== null && !followRef.current ? rows.findIndex((row) => row.id === anchor.id) : -1;
|
|
303
|
+
if (anchor !== null && index !== -1) {
|
|
304
|
+
// The total measures again what this commit's rows reported; the cache alone lags behind them.
|
|
305
|
+
virtualizer.getTotalSize();
|
|
306
|
+
element.scrollTop = virtualizer.measurementsCache[index]!.start - anchor.offset;
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const row = virtualizer.getVirtualItemForOffset(element.scrollTop);
|
|
310
|
+
anchorRef.current = row === undefined ? null : { id: String(row.key), offset: row.start - element.scrollTop };
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Close to the top, the page before goes in; again after each page that left too little above the reader.
|
|
314
|
+
useEffect(() => {
|
|
315
|
+
const element = scrollRef.current;
|
|
316
|
+
if (element !== null && wantsEarlier(element)) {
|
|
317
|
+
loadEarlier();
|
|
318
|
+
}
|
|
319
|
+
}, [rows, loadEarlier, frame.height]);
|
|
320
|
+
|
|
321
|
+
// A sub-agent in the thread's place hides the thread, and the strip and the keyboard go with it.
|
|
322
|
+
const onMainAgent = trail.length === 0;
|
|
323
|
+
// A node on a canvas has no strip. It is too narrow to give up a column of its width.
|
|
324
|
+
const showsScrubber = frame.column && onMainAgent && ticks.length >= SCRUBBER_MIN_TICKS;
|
|
325
|
+
const paddingLeft = threadPaddingLeft(frame.width, showsScrubber);
|
|
326
|
+
|
|
327
|
+
/*
|
|
328
|
+
* Where each message starts, from the virtualizer's measurements (an estimate for a row it never
|
|
329
|
+
* drew), so reading the scroll position costs no layout. The virtualizer renders this component on
|
|
330
|
+
* every scroll already.
|
|
331
|
+
*/
|
|
332
|
+
const measurements = virtualizer.measurementsCache;
|
|
333
|
+
const starts = ticks.map((tick) => measurements[tick.rowIndex]?.start ?? tick.rowIndex * ESTIMATED_ROW_PX);
|
|
334
|
+
const scrollTop = virtualizer.scrollOffset ?? 0;
|
|
335
|
+
const viewport = virtualizer.scrollRect?.height ?? 0;
|
|
336
|
+
const visibleHeight = Math.max(0, viewport - coveredHeight);
|
|
337
|
+
const inView = showsScrubber ? messagesInView({ starts, scrollTop, visibleHeight }) : null;
|
|
338
|
+
|
|
339
|
+
const chatFind = chatHost().useTimelineFind({
|
|
340
|
+
chatId,
|
|
341
|
+
frame: frameRef,
|
|
342
|
+
thread: threadRef,
|
|
343
|
+
scroller: scrollRef,
|
|
344
|
+
enabled: onMainAgent,
|
|
345
|
+
rows,
|
|
346
|
+
structure: items,
|
|
347
|
+
order,
|
|
348
|
+
firstRowInView: () => {
|
|
349
|
+
const top = scrollRef.current?.scrollTop ?? 0;
|
|
350
|
+
return virtualizer.getVirtualItems().find((virtualRow) => virtualRow.end > top)?.index ?? 0;
|
|
351
|
+
},
|
|
352
|
+
openTurn: turns.add,
|
|
353
|
+
openGroup: groups.add,
|
|
354
|
+
openSubagent: subagents.add,
|
|
355
|
+
// A row already on screen stays put; the find scrolls to the match itself once it is drawn.
|
|
356
|
+
scrollToRow: (index) => {
|
|
357
|
+
const top = scrollRef.current?.scrollTop ?? 0;
|
|
358
|
+
const bottom = top + visibleHeight;
|
|
359
|
+
if (!virtualizer.getVirtualItems().some((virtualRow) => virtualRow.index === index && virtualRow.end > top && virtualRow.start < bottom)) {
|
|
360
|
+
virtualizer.scrollToIndex(index, { align: 'center' });
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
coveredHeight: () => coveredHeightRef.current,
|
|
364
|
+
stopFollowing
|
|
365
|
+
});
|
|
366
|
+
const searching = chatFind.open;
|
|
367
|
+
// A find covers the whole thread, so every page before this one comes in while it is open.
|
|
368
|
+
useEffect(() => {
|
|
369
|
+
if (searching) {
|
|
370
|
+
loadEarlier();
|
|
371
|
+
}
|
|
372
|
+
}, [searching, loadEarlier]);
|
|
373
|
+
const found = useMemo(() => (searching ? ticksWithHits(ticks, chatFind.hitRows) : []), [searching, ticks, chatFind.hitRows]);
|
|
374
|
+
const foundCurrent = chatFind.currentRow === null ? null : tickOfRow(ticks, chatFind.currentRow);
|
|
375
|
+
|
|
376
|
+
const jumpTo = (index: number): void => {
|
|
377
|
+
const tick = ticks[index];
|
|
378
|
+
if (!tick) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (tick.bookmark !== null) {
|
|
382
|
+
setSeeking(tick.bookmark.itemId);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
followRef.current = false;
|
|
386
|
+
virtualizer.scrollToIndex(tick.rowIndex, { align: 'start' });
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
useLayoutEffect(() => {
|
|
390
|
+
if (seeking === null) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const index = rows.findIndex((row) => row.id === seeking);
|
|
394
|
+
if (index !== -1) {
|
|
395
|
+
setSeeking(null);
|
|
396
|
+
followRef.current = false;
|
|
397
|
+
virtualizer.scrollToIndex(index, { align: 'start' });
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const item = items?.[seeking];
|
|
401
|
+
if (item === undefined && cursor !== null) {
|
|
402
|
+
loadEarlier();
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const turnId = item?.turnId ?? null;
|
|
406
|
+
if (turnId !== null && !turns.ids.has(turnId)) {
|
|
407
|
+
turns.add(turnId);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
setSeeking(null);
|
|
411
|
+
}, [seeking, rows, items, turns, virtualizer, cursor, loadEarlier]);
|
|
412
|
+
useEffect(() => registerItemJumper(keyOf(chatId), setSeeking), [keyOf, chatId]);
|
|
413
|
+
|
|
414
|
+
useLayoutEffect(() => {
|
|
415
|
+
if (revealing === null) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const { turnId, target } = revealing;
|
|
419
|
+
const changes = target === 'changes' ? rows.findIndex((row) => row.kind === 'changed-files' && row.turnId === turnId) : -1;
|
|
420
|
+
const opening = order?.map((id) => items?.[id]).find((item) => item?.kind === 'user' && item.turnId === turnId);
|
|
421
|
+
const found = changes !== -1 ? changes : rows.findIndex((row) => row.id === opening?.id || row.id === `start-${turnId}` || row.id === `fold-${turnId}`);
|
|
422
|
+
if (found !== -1) {
|
|
423
|
+
setRevealing(null);
|
|
424
|
+
followRef.current = false;
|
|
425
|
+
virtualizer.scrollToIndex(found, { align: 'start' });
|
|
426
|
+
useTimelineFlash.getState().flash(keyOf(chatId), rows[found]!.id, changes !== -1);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
// The turn is before the page this thread holds; a thread that holds it all has no such turn.
|
|
430
|
+
if (cursor === null) {
|
|
431
|
+
setRevealing(null);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
loadEarlier();
|
|
435
|
+
}, [revealing, rows, order, items, virtualizer, cursor, loadEarlier, keyOf, chatId]);
|
|
436
|
+
useEffect(() => registerTurnJumper(keyOf(chatId), (turnId, target) => setRevealing({ turnId, target })), [keyOf, chatId]);
|
|
437
|
+
// The strip is memoized and draws on every scroll, so it gets one callback for its life.
|
|
438
|
+
const jumpRef = useRef(jumpTo);
|
|
439
|
+
jumpRef.current = jumpTo;
|
|
440
|
+
const pick = useCallback((index: number) => jumpRef.current(index), []);
|
|
441
|
+
// Read when the card asks rather than on every render, so the strip's memo holds while the thread streams.
|
|
442
|
+
const cardChat = useMemo<CardChat>(
|
|
443
|
+
() => ({
|
|
444
|
+
chatId,
|
|
445
|
+
canFork: (turnId) => {
|
|
446
|
+
if (chatHost().fork === null) {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
const row = useChats.getState().byKey[keyOf(chatId)];
|
|
450
|
+
return forkRefusal(row?.info ?? null, row?.structure[turnId]) === null;
|
|
451
|
+
},
|
|
452
|
+
fork: (turnId) => chatHost().fork?.(chatId, turnId)
|
|
453
|
+
}),
|
|
454
|
+
[keyOf, chatId]
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
const stepRef = useRef<(direction: -1 | 1) => boolean>(() => false);
|
|
458
|
+
stepRef.current = (direction) => {
|
|
459
|
+
if (!onMainAgent) {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
const index = stepMessage(starts, (i) => ticks[i]!.kind === 'person', scrollTop, direction);
|
|
463
|
+
if (index === null) {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
jumpTo(index);
|
|
467
|
+
return true;
|
|
468
|
+
};
|
|
469
|
+
useEffect(() => registerMessageStepper(keyOf(chatId), (direction) => stepRef.current(direction)), [keyOf, chatId]);
|
|
470
|
+
|
|
471
|
+
/* The whole of what a sub-agent did, in the thread's place. Its row only keeps the beginning. */
|
|
472
|
+
const openConversation = (step: SubagentStep): void => {
|
|
473
|
+
show(openFromMain(step));
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
/* The header of a turn a sub-agent woke. It points at the row that agent worked in, or the one it hangs under. */
|
|
477
|
+
const openSubagent = (toolUseId: string): void => {
|
|
478
|
+
const found = findSubagentBranch(rows, toolUseId);
|
|
479
|
+
if (!found) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
subagents.add(found.branch.id);
|
|
483
|
+
followRef.current = false;
|
|
484
|
+
virtualizer.scrollToIndex(found.index, { align: 'start' });
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
return (
|
|
488
|
+
// A file an answer names is relative to the folder this chat runs in, which is a worktree as
|
|
489
|
+
// often as it is the project itself.
|
|
490
|
+
<FileLinkContext.Provider value={info?.cwd ?? null}>
|
|
491
|
+
<div ref={frameRef} className="relative flex min-h-0 grow flex-col">
|
|
492
|
+
{searching && chatFind.bar}
|
|
493
|
+
<ContextMenu.Root>
|
|
494
|
+
<div
|
|
495
|
+
ref={scrollRef}
|
|
496
|
+
// No CSS scroll-padding here: Chromium counts the sticky composer's caret as hidden behind
|
|
497
|
+
// it and scrolls the thread toward the end on every keystroke.
|
|
498
|
+
className="chat-scroll min-h-0 grow overflow-auto"
|
|
499
|
+
// The thread keeps its reader in place itself when a page goes in above.
|
|
500
|
+
style={{ overflowAnchor: 'none' }}
|
|
501
|
+
onScrollCapture={(e) => {
|
|
502
|
+
const el = e.currentTarget;
|
|
503
|
+
// Capture the reader's movement before virtualization remeasures rows and shifts the scroll offset.
|
|
504
|
+
const previous = scrollGeometry.current;
|
|
505
|
+
const layoutChanged = previous.height !== el.scrollHeight || previous.viewport !== el.clientHeight;
|
|
506
|
+
const atEnd = el.scrollHeight - el.scrollTop - el.clientHeight < FOLLOW_THRESHOLD_PX;
|
|
507
|
+
if (!layoutChanged && el.scrollTop < previous.top) {
|
|
508
|
+
followRef.current = false;
|
|
509
|
+
} else if (atEnd) {
|
|
510
|
+
followRef.current = true;
|
|
511
|
+
}
|
|
512
|
+
scrollGeometry.current = { top: el.scrollTop, height: el.scrollHeight, viewport: el.clientHeight };
|
|
513
|
+
setTimelineAtEnd(chatId, followRef.current);
|
|
514
|
+
if (wantsEarlier(el)) {
|
|
515
|
+
loadEarlier();
|
|
516
|
+
}
|
|
517
|
+
}}
|
|
518
|
+
>
|
|
519
|
+
{/* A welcome centers the greeting and the composer as one group. The composer keeps its
|
|
520
|
+
place in the tree, so the first message does not mount it again. */}
|
|
521
|
+
<div ref={contentRef} className={clsx('relative flex min-h-full flex-col', welcome && 'justify-center')}>
|
|
522
|
+
<ContextMenu.Trigger
|
|
523
|
+
ref={threadRef}
|
|
524
|
+
className={clsx('chat-thread flex flex-col px-4 pt-4', !welcome && 'grow')}
|
|
525
|
+
style={{ paddingLeft }}
|
|
526
|
+
inert={overlay ? true : undefined}
|
|
527
|
+
onContextMenu={(e) =>
|
|
528
|
+
setTarget(readTimelineTarget(e.target as HTMLElement, threadRef.current, withCurrentText(rows, fullItems())))
|
|
529
|
+
}
|
|
530
|
+
>
|
|
531
|
+
{welcome ? (
|
|
532
|
+
<WelcomeGreeting chatId={chatId} />
|
|
533
|
+
) : empty ? (
|
|
534
|
+
<EmptyThread chatId={chatId} />
|
|
535
|
+
) : (
|
|
536
|
+
<div className="chat-column-content relative w-full shrink-0" style={{ height: virtualizer.getTotalSize() }}>
|
|
537
|
+
{virtualizer.getVirtualItems().map((virtualRow) => {
|
|
538
|
+
const row = rows[virtualRow.index]!;
|
|
539
|
+
// Put turn gaps inside the measured row so the virtualizer includes them.
|
|
540
|
+
const question = row.kind === 'user';
|
|
541
|
+
const previous = virtualRow.index > 0 ? rows[virtualRow.index - 1]! : null;
|
|
542
|
+
const header = replyHeader(row, previous);
|
|
543
|
+
return (
|
|
544
|
+
<div
|
|
545
|
+
key={row.id}
|
|
546
|
+
data-index={virtualRow.index}
|
|
547
|
+
data-item-id={row.id}
|
|
548
|
+
ref={virtualizer.measureElement}
|
|
549
|
+
className={clsx(
|
|
550
|
+
'absolute left-0 top-0 w-full',
|
|
551
|
+
rowRhythm(row, previous),
|
|
552
|
+
question && virtualRow.index > 0 && 'pt-(--chat-turn-gap)'
|
|
553
|
+
)}
|
|
554
|
+
style={{ transform: `translateY(${virtualRow.start}px)` }}
|
|
555
|
+
>
|
|
556
|
+
{flash !== null && flash.key === keyOf(chatId) && flash.rowId === row.id && (
|
|
557
|
+
<span
|
|
558
|
+
key={flash.nonce}
|
|
559
|
+
className="chat-flash pointer-events-none absolute inset-0 rounded-lg"
|
|
560
|
+
aria-hidden
|
|
561
|
+
/>
|
|
562
|
+
)}
|
|
563
|
+
{header !== null && <ReplyHeader chatId={chatId} at={header.at} />}
|
|
564
|
+
<FindRevealContext.Provider value={chatFind.reveal}>
|
|
565
|
+
<MarkableRow row={row} chatId={chatId} bookmark={marks.get(row.id) ?? null}>
|
|
566
|
+
<Row
|
|
567
|
+
row={row}
|
|
568
|
+
chatId={chatId}
|
|
569
|
+
toggleGroup={groups.toggle}
|
|
570
|
+
toggleTurn={turns.toggle}
|
|
571
|
+
toggleSubagent={subagents.toggle}
|
|
572
|
+
openSubagent={openSubagent}
|
|
573
|
+
openConversation={openConversation}
|
|
574
|
+
/>
|
|
575
|
+
</MarkableRow>
|
|
576
|
+
</FindRevealContext.Provider>
|
|
577
|
+
</div>
|
|
578
|
+
);
|
|
579
|
+
})}
|
|
580
|
+
</div>
|
|
581
|
+
)}
|
|
582
|
+
</ContextMenu.Trigger>
|
|
583
|
+
<QuoteButton thread={threadRef} frame={contentRef} taker={quoteTakerRef} />
|
|
584
|
+
<div
|
|
585
|
+
ref={composerRef}
|
|
586
|
+
// The card is translucent so the thread shows through it, but the pb-3 strip under it must stay opaque.
|
|
587
|
+
className={clsx(
|
|
588
|
+
'relative z-10 shrink-0',
|
|
589
|
+
composer && 'px-3 pb-3 pt-3 bg-[linear-gradient(to_bottom,transparent_calc(100%-12px),var(--surface)_calc(100%-12px))]'
|
|
590
|
+
)}
|
|
591
|
+
style={{ position: stickyComposer ? 'sticky' : 'relative', bottom: 0 }}
|
|
592
|
+
>
|
|
593
|
+
<QuoteTakerContext.Provider value={registerQuoteTaker}>{composer}</QuoteTakerContext.Provider>
|
|
594
|
+
</div>
|
|
595
|
+
</div>
|
|
596
|
+
</div>
|
|
597
|
+
<TimelineMenuPopup target={target} thread={threadRef} chatId={onMainAgent ? chatId : null} />
|
|
598
|
+
</ContextMenu.Root>
|
|
599
|
+
{loadingEarlier && !overlay && (
|
|
600
|
+
<div className="pointer-events-none absolute inset-x-0 top-3 z-10 flex justify-center" role="status">
|
|
601
|
+
<Icon icon={LoaderCircle} size={16} className="animate-spin text-text-faint" />
|
|
602
|
+
<span className="sr-only">{t('timeline.loadingEarlier')}</span>
|
|
603
|
+
</div>
|
|
604
|
+
)}
|
|
605
|
+
{showsScrubber && !overlay && (
|
|
606
|
+
<div className="absolute top-4" style={{ left: STRIP_INSET_PX, width: STRIP_WIDTH_PX, bottom: coveredHeight }}>
|
|
607
|
+
<ErrorBoundary label={t('timeline.stripFailed')} resetKeys={[ticks.length]}>
|
|
608
|
+
<Scrubber
|
|
609
|
+
ticks={ticks}
|
|
610
|
+
firstInView={inView?.first ?? null}
|
|
611
|
+
lastInView={inView?.last ?? null}
|
|
612
|
+
onPick={pick}
|
|
613
|
+
chat={cardChat}
|
|
614
|
+
found={found}
|
|
615
|
+
foundCurrent={foundCurrent}
|
|
616
|
+
/>
|
|
617
|
+
</ErrorBoundary>
|
|
618
|
+
</div>
|
|
619
|
+
)}
|
|
620
|
+
{overlay && (
|
|
621
|
+
<div className="absolute inset-0 z-5 flex flex-col bg-surface" style={{ paddingBottom: coveredHeight }}>
|
|
622
|
+
{overlay}
|
|
623
|
+
</div>
|
|
624
|
+
)}
|
|
625
|
+
</div>
|
|
626
|
+
</FileLinkContext.Provider>
|
|
627
|
+
);
|
|
628
|
+
}
|