@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,424 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
5
|
+
import clsx from 'clsx';
|
|
6
|
+
import { LoaderCircle } from 'lucide-react';
|
|
7
|
+
import { useForkedTurns } from '../forks.js';
|
|
8
|
+
import { bookmarkRows } from '../logic/bookmarks.js';
|
|
9
|
+
import { deriveTimelineRows, findSubagentBranch } from '../logic/timeline.js';
|
|
10
|
+
import { withThreadCards } from '../logic/thread-cards.js';
|
|
11
|
+
import { openFromMain, useSubagentTrail } from '../subagent-view.js';
|
|
12
|
+
import { registerItemJumper, registerMessageStepper, registerTimeline, registerTurnJumper, setTimelineAtEnd, wantsEarlier } from '../timeline-scroll.js';
|
|
13
|
+
import { useTimelineFlash } from '../timeline-flash.js';
|
|
14
|
+
import { SCRUBBER_MIN_TICKS, STRIP_INSET_PX, STRIP_WIDTH_PX, messagesInView, stepMessage, threadPaddingLeft, tickOfRow, ticksOf, ticksWithHits } from '../logic/scrubber.js';
|
|
15
|
+
import { EMPTY_TARGET, readTimelineTarget, withCurrentText } from '../logic/timeline-target.js';
|
|
16
|
+
import { forkRefusal } from '../logic/fork.js';
|
|
17
|
+
import { FindRevealContext } from './find-reveal.js';
|
|
18
|
+
import { BookmarkMarker } from './BookmarkMarker.js';
|
|
19
|
+
import { MessageActions } from './MessageActions.js';
|
|
20
|
+
import { Scrubber } from './Scrubber.js';
|
|
21
|
+
import { TimelineMenuPopup } from './TimelineMenu.js';
|
|
22
|
+
import { FOLLOW_THRESHOLD_PX, replyHeader, rowRhythm } from './rows/row-rhythm.js';
|
|
23
|
+
import { QuoteButton } from './QuoteButton.js';
|
|
24
|
+
import { QuoteTakerContext } from './quote-selection.js';
|
|
25
|
+
import { useToggleSet } from './useToggleSet.js';
|
|
26
|
+
import { WelcomeGreeting } from './Welcome.js';
|
|
27
|
+
import { Row } from './rows/Rows.js';
|
|
28
|
+
import { ReplyHeader } from './rows/MessageRows.js';
|
|
29
|
+
import { useChatRow, useChats } from '../../state/chats.js';
|
|
30
|
+
import { chatHost } from '../../host.js';
|
|
31
|
+
import { useChatScope } from '../../scope.js';
|
|
32
|
+
import { FileLinkContext } from './file-links.js';
|
|
33
|
+
import { AgentIcon } from '../../agents/AgentIcon.js';
|
|
34
|
+
import { useModelName } from '../../agents/model-name.js';
|
|
35
|
+
import { ContextMenu, EmptyState, ErrorBoundary, Icon, SectionLabel } from '@adecore/ui';
|
|
36
|
+
const ESTIMATED_ROW_PX = 56;
|
|
37
|
+
/*
|
|
38
|
+
* A thread before its first message: which model answers, what the lines into this node let it read,
|
|
39
|
+
* and the folder it works in, so a first question can lean on all three.
|
|
40
|
+
*/
|
|
41
|
+
function EmptyThread({ chatId }) {
|
|
42
|
+
const { t } = useTranslation('agent-chat');
|
|
43
|
+
const info = useChatRow(chatId, (row) => row?.info ?? null);
|
|
44
|
+
const model = useModelName(info?.provider, info?.selection.model ?? '');
|
|
45
|
+
const sources = chatHost().useContextSources(chatId);
|
|
46
|
+
const hidesFolder = chatHost().useHidesFolder(chatId);
|
|
47
|
+
return (_jsx("div", { className: "flex grow items-center justify-center", children: _jsxs("div", { className: "flex max-w-sm flex-col items-center gap-3 px-6 py-8 text-center", children: [_jsx(EmptyState, { icon: info ? _jsx(AgentIcon, { kind: info.provider }) : undefined, className: "p-0", children: info ? t('timeline.empty.ready', { model }) : t('timeline.empty.ask') }), sources.length > 0 && (_jsxs("div", { className: "flex max-w-full flex-col items-center gap-1", children: [_jsx(SectionLabel, { children: t('timeline.empty.canRead') }), _jsx("span", { className: "max-w-full text-xs text-text-muted", children: sources.map((source) => source.title).join(', ') })] })), info?.cwd && !hidesFolder && (_jsxs("div", { className: "flex max-w-full flex-col items-center gap-1", children: [_jsx(SectionLabel, { children: t('timeline.empty.worksIn') }), _jsx("span", { className: "max-w-full font-mono text-xs break-all text-text-muted", children: info.cwd })] }))] }) }));
|
|
48
|
+
}
|
|
49
|
+
/*
|
|
50
|
+
* A row of the thread behind the chapter line of its bookmark. A message, the only row that takes a
|
|
51
|
+
* bookmark, gets its actions under it; every other row carries a bookmark only for a reply it folds.
|
|
52
|
+
*/
|
|
53
|
+
function MarkableRow({ row, chatId, bookmark, children }) {
|
|
54
|
+
if (row.kind !== 'user' && row.kind !== 'assistant') {
|
|
55
|
+
return (_jsxs(_Fragment, { children: [bookmark !== null && _jsx(BookmarkMarker, { chatId: chatId, itemId: bookmark.itemId, bookmark: bookmark }), children] }));
|
|
56
|
+
}
|
|
57
|
+
return (_jsxs(_Fragment, { children: [_jsx(BookmarkMarker, { chatId: chatId, itemId: row.id, bookmark: bookmark }), _jsxs("div", { className: "group/message", children: [children, _jsx(MessageActions, { chatId: chatId, row: row })] })] }));
|
|
58
|
+
}
|
|
59
|
+
/*
|
|
60
|
+
* `overlay` stands in the thread's place and ends where the composer starts, so the thread keeps
|
|
61
|
+
* where it was underneath and the composer stays in reach for the prompts it answers.
|
|
62
|
+
*/
|
|
63
|
+
export function Timeline({ chatId, composer, overlay }) {
|
|
64
|
+
const { t } = useTranslation('agent-chat');
|
|
65
|
+
const order = useChatRow(chatId, (row) => row?.order);
|
|
66
|
+
// The structure, not the items. A delta growing a reply must not derive every row again.
|
|
67
|
+
const items = useChatRow(chatId, (row) => row?.structure);
|
|
68
|
+
const activeTurnId = useChatRow(chatId, (row) => row?.info.activeTurnId ?? null);
|
|
69
|
+
const bookmarks = useChatRow(chatId, (row) => row?.bookmarks);
|
|
70
|
+
const forkedTurns = useForkedTurns(chatId);
|
|
71
|
+
const info = useChatRow(chatId, (row) => row?.info ?? null);
|
|
72
|
+
const cursor = useChatRow(chatId, (row) => row?.history?.cursor ?? null);
|
|
73
|
+
const scope = useChatScope();
|
|
74
|
+
const { id: scopeId, keyOf } = scope;
|
|
75
|
+
// Read when the menu opens rather than subscribed to, for the same reason the rows use the structure.
|
|
76
|
+
const fullItems = () => useChats.getState().byKey[keyOf(chatId)]?.items;
|
|
77
|
+
const scrollRef = useRef(null);
|
|
78
|
+
const threadRef = useRef(null);
|
|
79
|
+
const contentRef = useRef(null);
|
|
80
|
+
const quoteTakerRef = useRef(null);
|
|
81
|
+
const registerQuoteTaker = useCallback((take) => {
|
|
82
|
+
quoteTakerRef.current = take;
|
|
83
|
+
return () => {
|
|
84
|
+
if (quoteTakerRef.current === take) {
|
|
85
|
+
quoteTakerRef.current = null;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}, []);
|
|
89
|
+
const followRef = useRef(true);
|
|
90
|
+
const stopFollowing = useCallback(() => {
|
|
91
|
+
followRef.current = false;
|
|
92
|
+
}, []);
|
|
93
|
+
const groups = useToggleSet(stopFollowing);
|
|
94
|
+
const turns = useToggleSet(stopFollowing);
|
|
95
|
+
const subagents = useToggleSet(stopFollowing);
|
|
96
|
+
const scrollGeometry = useRef({ top: 0, height: 0, viewport: 0 });
|
|
97
|
+
const [target, setTarget] = useState(EMPTY_TARGET);
|
|
98
|
+
const { trail, show } = useSubagentTrail(chatId);
|
|
99
|
+
const frameRef = useRef(null);
|
|
100
|
+
const composerRef = useRef(null);
|
|
101
|
+
const [composerHeight, setComposerHeight] = useState(0);
|
|
102
|
+
const [frame, setFrame] = useState({ width: 0, height: 0, column: false });
|
|
103
|
+
const [loadingEarlier, setLoadingEarlier] = useState(false);
|
|
104
|
+
const loadingEarlierRef = useRef(false);
|
|
105
|
+
// The row being read and where it stood, taken after every render, so a page going in above puts it back.
|
|
106
|
+
const anchorRef = useRef(null);
|
|
107
|
+
const headRef = useRef(undefined);
|
|
108
|
+
const loadEarlier = useCallback(() => {
|
|
109
|
+
if (cursor === null || loadingEarlierRef.current) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
loadingEarlierRef.current = true;
|
|
113
|
+
setLoadingEarlier(true);
|
|
114
|
+
// A page that failed is asked for again on the next scroll; the thread keeps what it has.
|
|
115
|
+
void scope.chats
|
|
116
|
+
.loadEarlier(chatId, cursor)
|
|
117
|
+
.catch(() => undefined)
|
|
118
|
+
.finally(() => {
|
|
119
|
+
loadingEarlierRef.current = false;
|
|
120
|
+
setLoadingEarlier(false);
|
|
121
|
+
});
|
|
122
|
+
}, [scope, chatId, cursor]);
|
|
123
|
+
const threadRows = useMemo(() => {
|
|
124
|
+
if (!order || !items) {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
return deriveTimelineRows(order.map((id) => items[id]), { expandedGroups: groups.ids, expandedTurns: turns.ids, expandedSubagents: subagents.ids, activeTurnId, forkedTurns });
|
|
128
|
+
}, [order, items, groups.ids, turns.ids, subagents.ids, activeTurnId, forkedTurns]);
|
|
129
|
+
const cards = chatHost().useThreadCards(scopeId, chatId);
|
|
130
|
+
const rows = useMemo(() => withThreadCards(threadRows, cards), [threadRows, cards]);
|
|
131
|
+
const welcomes = chatHost().useWelcome(chatId);
|
|
132
|
+
const empty = rows.length === 0;
|
|
133
|
+
// A chat that took a turn before is one whose thread has not come in yet, not one to greet.
|
|
134
|
+
const welcome = welcomes && empty && (info === null || (info.usage.turns === 0 && info.activeTurnId === null));
|
|
135
|
+
const marks = useMemo(() => bookmarkRows(rows, bookmarks ?? [], items ?? {}), [rows, bookmarks, items]);
|
|
136
|
+
const ticks = useMemo(() => ticksOf(rows, marks), [rows, marks]);
|
|
137
|
+
// A message a jump is on its way to; it may first have to open the turn its reply folded into.
|
|
138
|
+
const [seeking, setSeeking] = useState(null);
|
|
139
|
+
// A turn a jump is on its way to, which waits for the page its messages are on to come in.
|
|
140
|
+
const [revealing, setRevealing] = useState(null);
|
|
141
|
+
const flash = useTimelineFlash((s) => s.target);
|
|
142
|
+
// Whether the thread's text clears the strip depends on the width of the view, which only the thread's own frame tells.
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
const element = frameRef.current;
|
|
145
|
+
if (element === null || typeof ResizeObserver === 'undefined') {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const column = element.closest('.chat-column') !== null;
|
|
149
|
+
const measure = () => setFrame((current) => current.width === element.offsetWidth && current.height === element.offsetHeight && current.column === column
|
|
150
|
+
? current
|
|
151
|
+
: { width: element.offsetWidth, height: element.offsetHeight, column });
|
|
152
|
+
const observer = new ResizeObserver(measure);
|
|
153
|
+
observer.observe(element);
|
|
154
|
+
measure();
|
|
155
|
+
return () => observer.disconnect();
|
|
156
|
+
}, []);
|
|
157
|
+
useLayoutEffect(() => {
|
|
158
|
+
const element = composerRef.current;
|
|
159
|
+
if (element === null) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const measure = () => setComposerHeight(element.offsetHeight);
|
|
163
|
+
const observer = new ResizeObserver(measure);
|
|
164
|
+
observer.observe(element);
|
|
165
|
+
measure();
|
|
166
|
+
return () => observer.disconnect();
|
|
167
|
+
}, []);
|
|
168
|
+
// Oversized prompts scroll in full instead of sticking with their top outside the viewport.
|
|
169
|
+
const stickyComposer = !welcome && composerHeight < frame.height - FOLLOW_THRESHOLD_PX;
|
|
170
|
+
const coveredHeight = stickyComposer ? composerHeight : 0;
|
|
171
|
+
const coveredHeightRef = useRef(coveredHeight);
|
|
172
|
+
coveredHeightRef.current = coveredHeight;
|
|
173
|
+
// The composer pages through the thread with PageUp and PageDown; this is the element it moves.
|
|
174
|
+
useEffect(() => registerTimeline(chatId, scrollRef.current, (enabled) => {
|
|
175
|
+
followRef.current = enabled;
|
|
176
|
+
}, () => coveredHeightRef.current), [chatId]);
|
|
177
|
+
// The client does not run the React Compiler, so its memoization rule has nothing to break here.
|
|
178
|
+
// oxlint-disable-next-line react/incompatible-library
|
|
179
|
+
const virtualizer = useVirtualizer({
|
|
180
|
+
count: rows.length,
|
|
181
|
+
getScrollElement: () => scrollRef.current,
|
|
182
|
+
estimateSize: () => ESTIMATED_ROW_PX,
|
|
183
|
+
getItemKey: (index) => rows[index].id,
|
|
184
|
+
overscan: 8,
|
|
185
|
+
scrollPaddingEnd: coveredHeight
|
|
186
|
+
});
|
|
187
|
+
// A change in what the last row says (a streaming delta) must also pull the view down.
|
|
188
|
+
const lastRow = rows[rows.length - 1];
|
|
189
|
+
const lastReplyLength = useChatRow(chatId, (row) => {
|
|
190
|
+
const item = lastRow?.kind === 'assistant' ? row?.items[lastRow.id] : undefined;
|
|
191
|
+
return item?.kind === 'assistant' ? item.text.length : null;
|
|
192
|
+
});
|
|
193
|
+
const tail = lastReplyLength ?? rows.length;
|
|
194
|
+
/*
|
|
195
|
+
* The rows are measured as they render, so the end of the list moves while it is drawn: a diff
|
|
196
|
+
* that highlights, an image that loads, a tool row that grows a line of output. `scrollToIndex`
|
|
197
|
+
* aims at where the row was when it was asked, which is what left the thread short of the end.
|
|
198
|
+
* The scroller's own bottom is a fact rather than an estimate, and the total size changing is
|
|
199
|
+
* what says a row was measured again, so this runs for every one of those.
|
|
200
|
+
*/
|
|
201
|
+
const totalSize = virtualizer.getTotalSize();
|
|
202
|
+
useLayoutEffect(() => {
|
|
203
|
+
const element = scrollRef.current;
|
|
204
|
+
if (!followRef.current || element === null) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
// The composer is in the scroll flow, so its measured height is part of this end position.
|
|
208
|
+
element.scrollTop = element.scrollHeight;
|
|
209
|
+
scrollGeometry.current = { top: element.scrollTop, height: element.scrollHeight, viewport: element.clientHeight };
|
|
210
|
+
}, [rows.length, tail, totalSize, composerHeight, frame.height]);
|
|
211
|
+
// A page that went in above moves every row down by what it measures; the reader stays on the row they were on.
|
|
212
|
+
useLayoutEffect(() => {
|
|
213
|
+
const element = scrollRef.current;
|
|
214
|
+
const head = order?.[0];
|
|
215
|
+
const moved = head !== headRef.current;
|
|
216
|
+
headRef.current = head;
|
|
217
|
+
if (element === null) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const anchor = anchorRef.current;
|
|
221
|
+
const index = moved && anchor !== null && !followRef.current ? rows.findIndex((row) => row.id === anchor.id) : -1;
|
|
222
|
+
if (anchor !== null && index !== -1) {
|
|
223
|
+
// The total measures again what this commit's rows reported; the cache alone lags behind them.
|
|
224
|
+
virtualizer.getTotalSize();
|
|
225
|
+
element.scrollTop = virtualizer.measurementsCache[index].start - anchor.offset;
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const row = virtualizer.getVirtualItemForOffset(element.scrollTop);
|
|
229
|
+
anchorRef.current = row === undefined ? null : { id: String(row.key), offset: row.start - element.scrollTop };
|
|
230
|
+
});
|
|
231
|
+
// Close to the top, the page before goes in; again after each page that left too little above the reader.
|
|
232
|
+
useEffect(() => {
|
|
233
|
+
const element = scrollRef.current;
|
|
234
|
+
if (element !== null && wantsEarlier(element)) {
|
|
235
|
+
loadEarlier();
|
|
236
|
+
}
|
|
237
|
+
}, [rows, loadEarlier, frame.height]);
|
|
238
|
+
// A sub-agent in the thread's place hides the thread, and the strip and the keyboard go with it.
|
|
239
|
+
const onMainAgent = trail.length === 0;
|
|
240
|
+
// A node on a canvas has no strip. It is too narrow to give up a column of its width.
|
|
241
|
+
const showsScrubber = frame.column && onMainAgent && ticks.length >= SCRUBBER_MIN_TICKS;
|
|
242
|
+
const paddingLeft = threadPaddingLeft(frame.width, showsScrubber);
|
|
243
|
+
/*
|
|
244
|
+
* Where each message starts, from the virtualizer's measurements (an estimate for a row it never
|
|
245
|
+
* drew), so reading the scroll position costs no layout. The virtualizer renders this component on
|
|
246
|
+
* every scroll already.
|
|
247
|
+
*/
|
|
248
|
+
const measurements = virtualizer.measurementsCache;
|
|
249
|
+
const starts = ticks.map((tick) => measurements[tick.rowIndex]?.start ?? tick.rowIndex * ESTIMATED_ROW_PX);
|
|
250
|
+
const scrollTop = virtualizer.scrollOffset ?? 0;
|
|
251
|
+
const viewport = virtualizer.scrollRect?.height ?? 0;
|
|
252
|
+
const visibleHeight = Math.max(0, viewport - coveredHeight);
|
|
253
|
+
const inView = showsScrubber ? messagesInView({ starts, scrollTop, visibleHeight }) : null;
|
|
254
|
+
const chatFind = chatHost().useTimelineFind({
|
|
255
|
+
chatId,
|
|
256
|
+
frame: frameRef,
|
|
257
|
+
thread: threadRef,
|
|
258
|
+
scroller: scrollRef,
|
|
259
|
+
enabled: onMainAgent,
|
|
260
|
+
rows,
|
|
261
|
+
structure: items,
|
|
262
|
+
order,
|
|
263
|
+
firstRowInView: () => {
|
|
264
|
+
const top = scrollRef.current?.scrollTop ?? 0;
|
|
265
|
+
return virtualizer.getVirtualItems().find((virtualRow) => virtualRow.end > top)?.index ?? 0;
|
|
266
|
+
},
|
|
267
|
+
openTurn: turns.add,
|
|
268
|
+
openGroup: groups.add,
|
|
269
|
+
openSubagent: subagents.add,
|
|
270
|
+
// A row already on screen stays put; the find scrolls to the match itself once it is drawn.
|
|
271
|
+
scrollToRow: (index) => {
|
|
272
|
+
const top = scrollRef.current?.scrollTop ?? 0;
|
|
273
|
+
const bottom = top + visibleHeight;
|
|
274
|
+
if (!virtualizer.getVirtualItems().some((virtualRow) => virtualRow.index === index && virtualRow.end > top && virtualRow.start < bottom)) {
|
|
275
|
+
virtualizer.scrollToIndex(index, { align: 'center' });
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
coveredHeight: () => coveredHeightRef.current,
|
|
279
|
+
stopFollowing
|
|
280
|
+
});
|
|
281
|
+
const searching = chatFind.open;
|
|
282
|
+
// A find covers the whole thread, so every page before this one comes in while it is open.
|
|
283
|
+
useEffect(() => {
|
|
284
|
+
if (searching) {
|
|
285
|
+
loadEarlier();
|
|
286
|
+
}
|
|
287
|
+
}, [searching, loadEarlier]);
|
|
288
|
+
const found = useMemo(() => (searching ? ticksWithHits(ticks, chatFind.hitRows) : []), [searching, ticks, chatFind.hitRows]);
|
|
289
|
+
const foundCurrent = chatFind.currentRow === null ? null : tickOfRow(ticks, chatFind.currentRow);
|
|
290
|
+
const jumpTo = (index) => {
|
|
291
|
+
const tick = ticks[index];
|
|
292
|
+
if (!tick) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (tick.bookmark !== null) {
|
|
296
|
+
setSeeking(tick.bookmark.itemId);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
followRef.current = false;
|
|
300
|
+
virtualizer.scrollToIndex(tick.rowIndex, { align: 'start' });
|
|
301
|
+
};
|
|
302
|
+
useLayoutEffect(() => {
|
|
303
|
+
if (seeking === null) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const index = rows.findIndex((row) => row.id === seeking);
|
|
307
|
+
if (index !== -1) {
|
|
308
|
+
setSeeking(null);
|
|
309
|
+
followRef.current = false;
|
|
310
|
+
virtualizer.scrollToIndex(index, { align: 'start' });
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const item = items?.[seeking];
|
|
314
|
+
if (item === undefined && cursor !== null) {
|
|
315
|
+
loadEarlier();
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const turnId = item?.turnId ?? null;
|
|
319
|
+
if (turnId !== null && !turns.ids.has(turnId)) {
|
|
320
|
+
turns.add(turnId);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
setSeeking(null);
|
|
324
|
+
}, [seeking, rows, items, turns, virtualizer, cursor, loadEarlier]);
|
|
325
|
+
useEffect(() => registerItemJumper(keyOf(chatId), setSeeking), [keyOf, chatId]);
|
|
326
|
+
useLayoutEffect(() => {
|
|
327
|
+
if (revealing === null) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const { turnId, target } = revealing;
|
|
331
|
+
const changes = target === 'changes' ? rows.findIndex((row) => row.kind === 'changed-files' && row.turnId === turnId) : -1;
|
|
332
|
+
const opening = order?.map((id) => items?.[id]).find((item) => item?.kind === 'user' && item.turnId === turnId);
|
|
333
|
+
const found = changes !== -1 ? changes : rows.findIndex((row) => row.id === opening?.id || row.id === `start-${turnId}` || row.id === `fold-${turnId}`);
|
|
334
|
+
if (found !== -1) {
|
|
335
|
+
setRevealing(null);
|
|
336
|
+
followRef.current = false;
|
|
337
|
+
virtualizer.scrollToIndex(found, { align: 'start' });
|
|
338
|
+
useTimelineFlash.getState().flash(keyOf(chatId), rows[found].id, changes !== -1);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
// The turn is before the page this thread holds; a thread that holds it all has no such turn.
|
|
342
|
+
if (cursor === null) {
|
|
343
|
+
setRevealing(null);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
loadEarlier();
|
|
347
|
+
}, [revealing, rows, order, items, virtualizer, cursor, loadEarlier, keyOf, chatId]);
|
|
348
|
+
useEffect(() => registerTurnJumper(keyOf(chatId), (turnId, target) => setRevealing({ turnId, target })), [keyOf, chatId]);
|
|
349
|
+
// The strip is memoized and draws on every scroll, so it gets one callback for its life.
|
|
350
|
+
const jumpRef = useRef(jumpTo);
|
|
351
|
+
jumpRef.current = jumpTo;
|
|
352
|
+
const pick = useCallback((index) => jumpRef.current(index), []);
|
|
353
|
+
// Read when the card asks rather than on every render, so the strip's memo holds while the thread streams.
|
|
354
|
+
const cardChat = useMemo(() => ({
|
|
355
|
+
chatId,
|
|
356
|
+
canFork: (turnId) => {
|
|
357
|
+
if (chatHost().fork === null) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
const row = useChats.getState().byKey[keyOf(chatId)];
|
|
361
|
+
return forkRefusal(row?.info ?? null, row?.structure[turnId]) === null;
|
|
362
|
+
},
|
|
363
|
+
fork: (turnId) => chatHost().fork?.(chatId, turnId)
|
|
364
|
+
}), [keyOf, chatId]);
|
|
365
|
+
const stepRef = useRef(() => false);
|
|
366
|
+
stepRef.current = (direction) => {
|
|
367
|
+
if (!onMainAgent) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
const index = stepMessage(starts, (i) => ticks[i].kind === 'person', scrollTop, direction);
|
|
371
|
+
if (index === null) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
jumpTo(index);
|
|
375
|
+
return true;
|
|
376
|
+
};
|
|
377
|
+
useEffect(() => registerMessageStepper(keyOf(chatId), (direction) => stepRef.current(direction)), [keyOf, chatId]);
|
|
378
|
+
/* The whole of what a sub-agent did, in the thread's place. Its row only keeps the beginning. */
|
|
379
|
+
const openConversation = (step) => {
|
|
380
|
+
show(openFromMain(step));
|
|
381
|
+
};
|
|
382
|
+
/* The header of a turn a sub-agent woke. It points at the row that agent worked in, or the one it hangs under. */
|
|
383
|
+
const openSubagent = (toolUseId) => {
|
|
384
|
+
const found = findSubagentBranch(rows, toolUseId);
|
|
385
|
+
if (!found) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
subagents.add(found.branch.id);
|
|
389
|
+
followRef.current = false;
|
|
390
|
+
virtualizer.scrollToIndex(found.index, { align: 'start' });
|
|
391
|
+
};
|
|
392
|
+
return (_jsx(FileLinkContext.Provider, { value: info?.cwd ?? null, children: _jsxs("div", { ref: frameRef, className: "relative flex min-h-0 grow flex-col", children: [searching && chatFind.bar, _jsxs(ContextMenu.Root, { children: [_jsx("div", { ref: scrollRef,
|
|
393
|
+
// No CSS scroll-padding here: Chromium counts the sticky composer's caret as hidden behind
|
|
394
|
+
// it and scrolls the thread toward the end on every keystroke.
|
|
395
|
+
className: "chat-scroll min-h-0 grow overflow-auto",
|
|
396
|
+
// The thread keeps its reader in place itself when a page goes in above.
|
|
397
|
+
style: { overflowAnchor: 'none' }, onScrollCapture: (e) => {
|
|
398
|
+
const el = e.currentTarget;
|
|
399
|
+
// Capture the reader's movement before virtualization remeasures rows and shifts the scroll offset.
|
|
400
|
+
const previous = scrollGeometry.current;
|
|
401
|
+
const layoutChanged = previous.height !== el.scrollHeight || previous.viewport !== el.clientHeight;
|
|
402
|
+
const atEnd = el.scrollHeight - el.scrollTop - el.clientHeight < FOLLOW_THRESHOLD_PX;
|
|
403
|
+
if (!layoutChanged && el.scrollTop < previous.top) {
|
|
404
|
+
followRef.current = false;
|
|
405
|
+
}
|
|
406
|
+
else if (atEnd) {
|
|
407
|
+
followRef.current = true;
|
|
408
|
+
}
|
|
409
|
+
scrollGeometry.current = { top: el.scrollTop, height: el.scrollHeight, viewport: el.clientHeight };
|
|
410
|
+
setTimelineAtEnd(chatId, followRef.current);
|
|
411
|
+
if (wantsEarlier(el)) {
|
|
412
|
+
loadEarlier();
|
|
413
|
+
}
|
|
414
|
+
}, children: _jsxs("div", { ref: contentRef, className: clsx('relative flex min-h-full flex-col', welcome && 'justify-center'), children: [_jsx(ContextMenu.Trigger, { ref: threadRef, className: clsx('chat-thread flex flex-col px-4 pt-4', !welcome && 'grow'), style: { paddingLeft }, inert: overlay ? true : undefined, onContextMenu: (e) => setTarget(readTimelineTarget(e.target, threadRef.current, withCurrentText(rows, fullItems()))), children: welcome ? (_jsx(WelcomeGreeting, { chatId: chatId })) : empty ? (_jsx(EmptyThread, { chatId: chatId })) : (_jsx("div", { className: "chat-column-content relative w-full shrink-0", style: { height: virtualizer.getTotalSize() }, children: virtualizer.getVirtualItems().map((virtualRow) => {
|
|
415
|
+
const row = rows[virtualRow.index];
|
|
416
|
+
// Put turn gaps inside the measured row so the virtualizer includes them.
|
|
417
|
+
const question = row.kind === 'user';
|
|
418
|
+
const previous = virtualRow.index > 0 ? rows[virtualRow.index - 1] : null;
|
|
419
|
+
const header = replyHeader(row, previous);
|
|
420
|
+
return (_jsxs("div", { "data-index": virtualRow.index, "data-item-id": row.id, ref: virtualizer.measureElement, className: clsx('absolute left-0 top-0 w-full', rowRhythm(row, previous), question && virtualRow.index > 0 && 'pt-(--chat-turn-gap)'), style: { transform: `translateY(${virtualRow.start}px)` }, children: [flash !== null && flash.key === keyOf(chatId) && flash.rowId === row.id && (_jsx("span", { className: "chat-flash pointer-events-none absolute inset-0 rounded-lg", "aria-hidden": true }, flash.nonce)), header !== null && _jsx(ReplyHeader, { chatId: chatId, at: header.at }), _jsx(FindRevealContext.Provider, { value: chatFind.reveal, children: _jsx(MarkableRow, { row: row, chatId: chatId, bookmark: marks.get(row.id) ?? null, children: _jsx(Row, { row: row, chatId: chatId, toggleGroup: groups.toggle, toggleTurn: turns.toggle, toggleSubagent: subagents.toggle, openSubagent: openSubagent, openConversation: openConversation }) }) })] }, row.id));
|
|
421
|
+
}) })) }), _jsx(QuoteButton, { thread: threadRef, frame: contentRef, taker: quoteTakerRef }), _jsx("div", { ref: composerRef,
|
|
422
|
+
// The card is translucent so the thread shows through it, but the pb-3 strip under it must stay opaque.
|
|
423
|
+
className: clsx('relative z-10 shrink-0', composer && 'px-3 pb-3 pt-3 bg-[linear-gradient(to_bottom,transparent_calc(100%-12px),var(--surface)_calc(100%-12px))]'), style: { position: stickyComposer ? 'sticky' : 'relative', bottom: 0 }, children: _jsx(QuoteTakerContext.Provider, { value: registerQuoteTaker, children: composer }) })] }) }), _jsx(TimelineMenuPopup, { target: target, thread: threadRef, chatId: onMainAgent ? chatId : null })] }), loadingEarlier && !overlay && (_jsxs("div", { className: "pointer-events-none absolute inset-x-0 top-3 z-10 flex justify-center", role: "status", children: [_jsx(Icon, { icon: LoaderCircle, size: 16, className: "animate-spin text-text-faint" }), _jsx("span", { className: "sr-only", children: t('timeline.loadingEarlier') })] })), showsScrubber && !overlay && (_jsx("div", { className: "absolute top-4", style: { left: STRIP_INSET_PX, width: STRIP_WIDTH_PX, bottom: coveredHeight }, children: _jsx(ErrorBoundary, { label: t('timeline.stripFailed'), resetKeys: [ticks.length], children: _jsx(Scrubber, { ticks: ticks, firstInView: inView?.first ?? null, lastInView: inView?.last ?? null, onPick: pick, chat: cardChat, found: found, foundCurrent: foundCurrent }) }) })), overlay && (_jsx("div", { className: "absolute inset-0 z-5 flex flex-col bg-surface", style: { paddingBottom: coveredHeight }, children: overlay }))] }) }));
|
|
424
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
import type { TimelineTarget } from '../logic/timeline-target.js';
|
|
3
|
+
export declare function TimelineMenuPopup({ target, thread, chatId }: {
|
|
4
|
+
target: TimelineTarget;
|
|
5
|
+
thread: RefObject<HTMLDivElement | null>;
|
|
6
|
+
chatId?: string | null;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
8
|
+
export declare function BookmarkMenuItems({ chatId, itemId, onName }: {
|
|
9
|
+
chatId: string;
|
|
10
|
+
itemId: string;
|
|
11
|
+
onName?: () => void;
|
|
12
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { BookmarkPlus, BookmarkX, Braces, Copy, Eye, FileText, GitFork, MessageSquare, Pencil, Scan } from 'lucide-react';
|
|
4
|
+
import { placeBookmark, removeBookmark, useBookmarkNaming } from '../bookmarks.js';
|
|
5
|
+
import { forkRefusal, turnIdOfRow } from '../logic/fork.js';
|
|
6
|
+
import { markdownOf, messageTextOf } from '../logic/timeline-copy.js';
|
|
7
|
+
import { chatHost } from '../../host.js';
|
|
8
|
+
import { useChatScope } from '../../scope.js';
|
|
9
|
+
import { useChatRow } from '../../state/chats.js';
|
|
10
|
+
import { openFileLink, useFileLinkCwd } from './file-links.js';
|
|
11
|
+
import { ContextMenu, copyText, DisabledReason, EDIT_SHORTCUTS, Icon, Kbd, selectAllWithin } from '@adecore/ui';
|
|
12
|
+
/*
|
|
13
|
+
* The menu behind a right-click in a thread. Copy is the reason it exists. Everything in a thread
|
|
14
|
+
* is text a person may want out of it, and a whole message, a code block or the markdown an answer
|
|
15
|
+
* was written in are each a different amount of that.
|
|
16
|
+
*/
|
|
17
|
+
export function TimelineMenuPopup({ target, thread, chatId = null }) {
|
|
18
|
+
const { t } = useTranslation('agent-chat');
|
|
19
|
+
const message = target.row === null ? null : messageTextOf(target.row);
|
|
20
|
+
const turnId = chatId === null || target.row === null ? null : turnIdOfRow(target.row);
|
|
21
|
+
const forkBlocked = useChatRow(chatId ?? '', (row) => (turnId === null ? null : forkRefusal(row?.info ?? null, row?.structure[turnId])));
|
|
22
|
+
const markdown = target.row === null ? null : markdownOf(target.row);
|
|
23
|
+
const markable = chatId !== null && (target.row?.kind === 'user' || target.row?.kind === 'assistant') ? target.row.id : null;
|
|
24
|
+
// A thread outside a chat (a sub-agent's transcript) has no cwd of its own; the app answers for it then.
|
|
25
|
+
const cwd = useFileLinkCwd();
|
|
26
|
+
const { fork, fileLinks } = chatHost();
|
|
27
|
+
const openInPreview = () => {
|
|
28
|
+
if (target.path !== null) {
|
|
29
|
+
openFileLink(cwd, { path: target.path, ...(target.line === null ? {} : { line: target.line }), directory: false });
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
return (_jsxs(ContextMenu.Popup, { children: [_jsxs(ContextMenu.Item, { disabled: target.selection === '', onClick: () => copyText(target.selection), children: [_jsx(Icon, { icon: Copy, size: 14 }), " ", t('common.action.copy'), " ", _jsx(Kbd, { shortcut: EDIT_SHORTCUTS.copy })] }), message !== null && (_jsxs(ContextMenu.Item, { onClick: () => copyText(message), children: [_jsx(Icon, { icon: MessageSquare, size: 14 }), " ", t('timeline.menu.copyMessage')] })), target.code !== null && (_jsxs(ContextMenu.Item, { onClick: () => copyText(target.code ?? ''), children: [_jsx(Icon, { icon: Braces, size: 14 }), " ", t('timeline.menu.copyCode')] })), markdown !== null && (_jsxs(ContextMenu.Item, { onClick: () => copyText(markdown), children: [_jsx(Icon, { icon: FileText, size: 14 }), " ", t('timeline.menu.copyMarkdown')] })), chatId !== null && turnId !== null && fork !== null && (_jsx(DisabledReason, { reason: forkBlocked, children: _jsxs(ContextMenu.Item, { disabled: forkBlocked !== null, onClick: () => fork(chatId, turnId), children: [_jsx(Icon, { icon: GitFork, size: 14 }), " ", t('timeline.menu.forkFromHere')] }) })), chatId !== null && markable !== null && _jsx(BookmarkMenuItems, { chatId: chatId, itemId: markable }), _jsx(ContextMenu.Separator, {}), _jsxs(ContextMenu.Item, { onClick: () => selectAllWithin(thread.current), children: [_jsx(Icon, { icon: Scan, size: 14 }), " ", t('common.action.selectAll')] }), target.path !== null && fileLinks !== null && (_jsxs(_Fragment, { children: [_jsx(ContextMenu.Separator, {}), _jsxs(ContextMenu.Item, { onClick: openInPreview, children: [_jsx(Icon, { icon: Eye, size: 14 }), " ", t('timeline.menu.openInPreview')] })] }))] }));
|
|
33
|
+
}
|
|
34
|
+
/*
|
|
35
|
+
* Place a bookmark on a message, or rename and remove the one it has: the same rows from the thread
|
|
36
|
+
* and from the strip beside it. `onName` runs when a row opens the name field, which sits on the
|
|
37
|
+
* bookmark's line over the message and so has to be on screen; the strip scrolls there, the thread
|
|
38
|
+
* was clicked on it.
|
|
39
|
+
*/
|
|
40
|
+
export function BookmarkMenuItems({ chatId, itemId, onName }) {
|
|
41
|
+
const { t } = useTranslation('agent-chat');
|
|
42
|
+
const scope = useChatScope();
|
|
43
|
+
const bookmark = useChatRow(chatId, (row) => row?.bookmarks?.find((candidate) => candidate.itemId === itemId) ?? null);
|
|
44
|
+
if (bookmark === null) {
|
|
45
|
+
return (_jsxs(ContextMenu.Item, { onClick: () => {
|
|
46
|
+
void placeBookmark(scope, chatId, itemId);
|
|
47
|
+
onName?.();
|
|
48
|
+
}, children: [_jsx(Icon, { icon: BookmarkPlus, size: 14 }), " ", t('bookmarks.add')] }));
|
|
49
|
+
}
|
|
50
|
+
return (_jsxs(_Fragment, { children: [_jsxs(ContextMenu.Item, { onClick: () => {
|
|
51
|
+
useBookmarkNaming.getState().open(scope.keyOf(chatId), bookmark.itemId);
|
|
52
|
+
onName?.();
|
|
53
|
+
}, children: [_jsx(Icon, { icon: Pencil, size: 14 }), " ", t('bookmarks.rename')] }), _jsxs(ContextMenu.Item, { onClick: () => void removeBookmark(scope, chatId, bookmark), children: [_jsx(Icon, { icon: BookmarkX, size: 14 }), " ", t('bookmarks.remove')] })] }));
|
|
54
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ChatFileChange } from '@adecore/agent-contracts';
|
|
2
|
+
import { type DiffContents } from './full-diff.js';
|
|
3
|
+
interface UnifiedDiffProps {
|
|
4
|
+
change: ChatFileChange;
|
|
5
|
+
overflow?: 'wrap' | 'scroll';
|
|
6
|
+
diffStyle?: 'unified' | 'split';
|
|
7
|
+
fill?: boolean;
|
|
8
|
+
contents?: DiffContents;
|
|
9
|
+
}
|
|
10
|
+
export default function UnifiedDiff({ change, overflow, diffStyle, fill, contents }: UnifiedDiffProps): import("react").JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { FileDiff, PatchDiff } from '@pierre/diffs/react';
|
|
5
|
+
import { chatHost } from '../../host.js';
|
|
6
|
+
import { useDiffTheme } from './diff-theme.js';
|
|
7
|
+
import { fullFileDiff } from './full-diff.js';
|
|
8
|
+
/*
|
|
9
|
+
* A patch the CLI reported itself (Codex writes unified diffs). The renderer needs a file header
|
|
10
|
+
* to know what it is looking at, and a CLI that only sends hunks gets one from the path we know.
|
|
11
|
+
* A diff without hunks at all is shown as its own lines, so nothing is ever swallowed.
|
|
12
|
+
*/
|
|
13
|
+
function asPatch(change) {
|
|
14
|
+
if (!/^@@/m.test(change.diff)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
if (/^\+\+\+ /m.test(change.diff)) {
|
|
18
|
+
return change.diff;
|
|
19
|
+
}
|
|
20
|
+
const from = change.kind === 'add' ? '/dev/null' : `a/${change.path}`;
|
|
21
|
+
const to = change.kind === 'delete' ? '/dev/null' : `b/${change.path}`;
|
|
22
|
+
return `--- ${from}\n+++ ${to}\n${change.diff}`;
|
|
23
|
+
}
|
|
24
|
+
/* Lets a diff with a view of its own run to the bottom of it, so the scrollbar of a short one sits
|
|
25
|
+
there instead of under its last line; the lines themselves keep their height. */
|
|
26
|
+
const FILL_CSS = `
|
|
27
|
+
:host { display: flex; flex-direction: column; flex-grow: 1; }
|
|
28
|
+
pre { flex-grow: 1; align-content: start; }
|
|
29
|
+
pre[data-diff-type="single"] { display: flex; flex-direction: column; }
|
|
30
|
+
pre[data-diff-type="split"][data-overflow="scroll"]:not([data-dehydrated]) { align-content: stretch; }
|
|
31
|
+
pre > [data-code] { flex-grow: 1; align-self: stretch; align-content: start; }
|
|
32
|
+
`;
|
|
33
|
+
function lineClass(line) {
|
|
34
|
+
if (line.startsWith('+')) {
|
|
35
|
+
return 'text-term-green';
|
|
36
|
+
}
|
|
37
|
+
if (line.startsWith('-')) {
|
|
38
|
+
return 'text-term-red';
|
|
39
|
+
}
|
|
40
|
+
return 'text-text-muted';
|
|
41
|
+
}
|
|
42
|
+
export default function UnifiedDiff({ change, overflow = 'wrap', diffStyle = 'unified', fill = false, contents }) {
|
|
43
|
+
const resolved = chatHost().code.useMode();
|
|
44
|
+
const theme = useDiffTheme();
|
|
45
|
+
const patch = useMemo(() => asPatch(change), [change]);
|
|
46
|
+
const oldText = contents?.old;
|
|
47
|
+
const newText = contents?.new;
|
|
48
|
+
const full = useMemo(() => (patch === null || oldText === undefined || newText === undefined ? null : fullFileDiff(patch, change.path, { old: oldText, new: newText })), [patch, change.path, oldText, newText]);
|
|
49
|
+
const unfolds = full !== null;
|
|
50
|
+
const options = useMemo(() => ({
|
|
51
|
+
theme,
|
|
52
|
+
themeType: resolved,
|
|
53
|
+
disableFileHeader: true,
|
|
54
|
+
diffStyle,
|
|
55
|
+
overflow,
|
|
56
|
+
hunkSeparators: unfolds ? 'line-info' : 'simple',
|
|
57
|
+
unsafeCSS: fill ? FILL_CSS : undefined
|
|
58
|
+
}), [diffStyle, overflow, theme, resolved, fill, unfolds]);
|
|
59
|
+
if (patch === null) {
|
|
60
|
+
return (_jsx("pre", { className: "max-h-64 overflow-auto px-3 py-2 font-mono text-code select-text", children: change.diff
|
|
61
|
+
.replace(/\n$/, '')
|
|
62
|
+
.split('\n')
|
|
63
|
+
.map((line, index) => (_jsx("div", { className: clsx(lineClass(line), 'whitespace-pre-wrap'), children: line }, index))) }));
|
|
64
|
+
}
|
|
65
|
+
return (_jsx("div", { className: clsx('chat-diff select-text', fill && 'flex grow flex-col'), children: full === null ? _jsx(PatchDiff, { patch: patch, options: options }) : _jsx(FileDiff, { fileDiff: full, options: options }) }));
|
|
66
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useUploadThumbnail } from '../thumbnails.js';
|
|
3
|
+
const BOX = 'h-14 w-14 rounded-lg border border-border';
|
|
4
|
+
/* An image the composer holds, drawn from its thumbnail; an empty box of the same size stands in while that is made. */
|
|
5
|
+
export function UploadThumb({ upload }) {
|
|
6
|
+
const url = useUploadThumbnail(upload);
|
|
7
|
+
if (url === null) {
|
|
8
|
+
return _jsx("span", { role: "img", "aria-label": upload.name, className: `block bg-surface-sunken ${BOX}` });
|
|
9
|
+
}
|
|
10
|
+
return _jsx("img", { src: url, alt: upload.name, className: `object-cover ${BOX}` });
|
|
11
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { AgentIcon } from '../../agents/AgentIcon.js';
|
|
5
|
+
import { useChatRow } from '../../state/chats.js';
|
|
6
|
+
import { dayPartOf } from '../logic/welcome.js';
|
|
7
|
+
function localClock() {
|
|
8
|
+
return new Date();
|
|
9
|
+
}
|
|
10
|
+
/* What stands over the composer of a chat nobody wrote in yet. The part of the day is read once, when the chat opens. */
|
|
11
|
+
export function WelcomeGreeting({ chatId, now = localClock }) {
|
|
12
|
+
const { t } = useTranslation('agent-chat');
|
|
13
|
+
const provider = useChatRow(chatId, (row) => row?.info.provider ?? null);
|
|
14
|
+
const [part] = useState(() => dayPartOf(now()));
|
|
15
|
+
return (_jsxs("div", { className: "flex items-center justify-center gap-3 px-3 pb-2 text-text", children: [provider && _jsx(AgentIcon, { kind: provider, size: 32, className: "shrink-0" }), _jsx("h2", { className: "text-4xl font-semibold tracking-tight text-balance", children: t(`timeline.welcome.${part}`) })] }));
|
|
16
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const MENTION_TONE = "bg-accent-soft text-accent shadow-[inset_0_0_0_1px_color-mix(in_srgb,var(--accent)_25%,transparent)]";
|
|
2
|
+
export declare const SKILL_TONE = "bg-skill-soft text-skill shadow-[inset_0_0_0_1px_color-mix(in_srgb,var(--skill)_25%,transparent)]";
|
|
3
|
+
export declare const CHIP_IN_EDITOR = "box-decoration-clone rounded-md px-1 py-0.5";
|
|
4
|
+
export declare const CHIP_IN_MESSAGE = "inline-flex h-5 max-w-full items-center gap-1 rounded-md px-1.5 align-[-3px] text-xs/none font-medium";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* An `@file` or `$skill` token drawn as a pill: the tone says which of the two it is, the shape
|
|
2
|
+
says where it is drawn. */
|
|
3
|
+
export const MENTION_TONE = 'bg-accent-soft text-accent shadow-[inset_0_0_0_1px_color-mix(in_srgb,var(--accent)_25%,transparent)]';
|
|
4
|
+
/* A skill is the same token shape as a file mention, in its own hue. */
|
|
5
|
+
export const SKILL_TONE = 'bg-skill-soft text-skill shadow-[inset_0_0_0_1px_color-mix(in_srgb,var(--skill)_25%,transparent)]';
|
|
6
|
+
/* The pill in the composer's editor, around the token's own characters: the editor places the caret
|
|
7
|
+
from what it drew, so padding is safe, and a chip that wraps keeps it on every fragment. */
|
|
8
|
+
export const CHIP_IN_EDITOR = 'box-decoration-clone rounded-md px-1 py-0.5';
|
|
9
|
+
/* The same pill in the transcript: a real inline box a shade smaller than the text around it, with
|
|
10
|
+
the icon in place of the sigil. */
|
|
11
|
+
export const CHIP_IN_MESSAGE = 'inline-flex h-5 max-w-full items-center gap-1 rounded-md px-1.5 align-[-3px] text-xs/none font-medium';
|