@adecore/agents-react 0.38.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/AgentModelLabel.d.ts +10 -0
- package/dist/agents/AgentModelLabel.js +98 -0
- package/dist/agents/model-name.js +2 -1
- package/dist/agents/provider-paths.d.ts +2 -1
- package/dist/agents/provider-paths.js +7 -3
- package/dist/chat/account-choice.js +57 -28
- package/dist/chat/bookmarks.js +2 -1
- package/dist/chat/drafts.d.ts +0 -1
- package/dist/chat/drafts.js +9 -28
- package/dist/chat/logic/picker-entries.js +2 -8
- package/dist/chat/logic/resume-compaction.js +2 -5
- package/dist/chat/logic/timeline-copy.js +2 -8
- package/dist/chat/logic/timeline.js +9 -10
- package/dist/chat/logic/workflow.js +2 -4
- package/dist/chat/subagent-conversation.js +2 -1
- package/dist/chat/ui/Composer.js +385 -360
- package/dist/chat/ui/GoalDock.js +2 -1
- package/dist/chat/ui/LimitState.js +4 -6
- package/dist/chat/ui/Pickers.d.ts +2 -1
- package/dist/chat/ui/Pickers.js +126 -123
- package/dist/chat/ui/PromptEditor.d.ts +19 -0
- package/dist/chat/ui/PromptEditor.js +115 -0
- package/dist/chat/ui/Timeline.d.ts +2 -1
- package/dist/chat/ui/Timeline.js +13 -1
- package/dist/chat/ui/composer/ComposerControls.d.ts +3 -0
- package/dist/chat/ui/composer/ComposerControls.js +235 -38
- package/dist/chat/ui/composer/key-handler.d.ts +1 -1
- package/dist/chat/ui/composer/key-handler.js +3 -11
- package/dist/chat/ui/composer/prompt-picks.d.ts +4 -0
- package/dist/chat/ui/composer/prompt-picks.js +14 -0
- package/dist/chat/ui/composer/use-composer-menus.d.ts +4 -49
- package/dist/chat/ui/composer/use-composer-menus.js +65 -519
- package/dist/chat/ui/composer/use-continuable-turn.js +0 -1
- package/dist/chat/ui/composer/use-notice.js +2 -1
- package/dist/chat/ui/composer/use-prompt-history.js +1 -1
- package/dist/chat/ui/composer/use-prompt-menus.d.ts +73 -0
- package/dist/chat/ui/composer/use-prompt-menus.js +499 -0
- package/dist/chat/ui/composer/use-take-back.js +2 -2
- package/dist/chat/ui/diff/ApprovalReview.js +1 -1
- package/dist/chat/ui/highlighter.d.ts +0 -6
- package/dist/chat/ui/highlighter.js +0 -12
- package/dist/chat/ui/icons.d.ts +0 -1
- package/dist/chat/ui/icons.js +0 -3
- package/dist/chat/ui/index.d.ts +3 -0
- package/dist/chat/ui/index.js +2 -0
- package/dist/chat/ui/rows/MessageRows.js +2 -5
- package/dist/chat/ui/rows/row-rhythm.js +2 -5
- package/dist/chat/ui/theme-watch.js +3 -7
- package/dist/host-hooks.d.ts +2 -1
- package/dist/host-hooks.js +6 -0
- package/dist/host.d.ts +17 -1
- package/dist/index.d.ts +1 -1
- package/dist/locales/en/agent-chat.d.ts +1 -0
- package/dist/locales/en/agent-chat.js +1 -0
- package/dist/locales/nl/agent-chat.d.ts +1 -0
- package/dist/locales/nl/agent-chat.js +1 -0
- package/dist/prompts/logic/usePromptSession.js +2 -1
- package/dist/providers/AddAccountForm.js +2 -1
- package/dist/providers/CliDetail.js +104 -130
- package/dist/providers/account-actions.js +6 -9
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/index.js +1 -0
- package/dist/state/chats.d.ts +1 -0
- package/dist/state/chats.js +6 -11
- package/dist/state/provider-accounts.d.ts +1 -1
- package/dist/state/provider-accounts.js +2 -8
- package/dist/theme.css +3 -0
- package/dist/transport.d.ts +2 -0
- package/dist/transport.js +14 -0
- package/dist/usage/ChartLogo.d.ts +5 -0
- package/dist/usage/ChartLogo.js +48 -0
- package/dist/usage/LimitsList.js +6 -16
- package/dist/usage/UsageBreakdown.js +17 -40
- package/dist/usage/UsageLimits.js +183 -211
- package/dist/usage/UsagePage.js +3 -17
- package/dist/usage/UsageSummary.js +4 -9
- package/dist/usage/limits.js +16 -31
- package/dist/usage/summary.d.ts +1 -1
- package/dist/usage/summary.js +3 -5
- package/package.json +26 -26
- package/src/agents/AccountDot.tsx +1 -0
- package/src/agents/AgentIcon.tsx +2 -0
- package/src/agents/AgentModelLabel.tsx +38 -0
- package/src/agents/ModelOptionControl.tsx +8 -0
- package/src/agents/account-limits.ts +3 -0
- package/src/agents/accounts.ts +12 -0
- package/src/agents/model-name.ts +13 -1
- package/src/agents/model-options.ts +2 -0
- package/src/agents/model-requirement.ts +4 -0
- package/src/agents/option-words.ts +2 -0
- package/src/agents/provider-paths.ts +8 -3
- package/src/agents/status-look.ts +1 -0
- package/src/chat/account-choice.ts +25 -19
- package/src/chat/actions.ts +5 -0
- package/src/chat/attachments.ts +8 -0
- package/src/chat/bookmarks.ts +13 -1
- package/src/chat/chat-client.ts +63 -0
- package/src/chat/chat-references.ts +3 -0
- package/src/chat/composer-effort.ts +5 -0
- package/src/chat/composer-send.ts +4 -0
- package/src/chat/drafts.ts +21 -17
- package/src/chat/forks.ts +4 -0
- package/src/chat/guards.ts +2 -0
- package/src/chat/index.ts +7 -0
- package/src/chat/logic/ansi.ts +47 -0
- package/src/chat/logic/bookmarks.ts +7 -0
- package/src/chat/logic/command-looks.ts +29 -0
- package/src/chat/logic/context-usage.ts +3 -0
- package/src/chat/logic/continue-after-stop.ts +5 -0
- package/src/chat/logic/effort.ts +4 -0
- package/src/chat/logic/embedded-code.ts +63 -0
- package/src/chat/logic/fork.ts +22 -0
- package/src/chat/logic/generated-image.ts +7 -0
- package/src/chat/logic/goal.ts +7 -0
- package/src/chat/logic/handback.ts +2 -0
- package/src/chat/logic/limit.ts +8 -0
- package/src/chat/logic/picker-entries.ts +16 -4
- package/src/chat/logic/resume-compaction.ts +22 -5
- package/src/chat/logic/scrubber.ts +38 -5
- package/src/chat/logic/thought.ts +5 -0
- package/src/chat/logic/thread-cards.ts +6 -0
- package/src/chat/logic/timeline-copy.ts +4 -9
- package/src/chat/logic/timeline-target.ts +15 -0
- package/src/chat/logic/timeline.ts +113 -10
- package/src/chat/logic/tools.ts +30 -0
- package/src/chat/logic/visual-height.ts +5 -0
- package/src/chat/logic/visual-viewport.ts +1 -0
- package/src/chat/logic/welcome.ts +3 -0
- package/src/chat/logic/workflow.ts +26 -7
- package/src/chat/mentions.ts +56 -0
- package/src/chat/pending-prompts.ts +16 -0
- package/src/chat/persisted-json.ts +15 -0
- package/src/chat/preferences.ts +16 -0
- package/src/chat/prompt-hold.ts +2 -0
- package/src/chat/quote.ts +7 -0
- package/src/chat/reading-width.ts +1 -0
- package/src/chat/recent-messages.ts +5 -0
- package/src/chat/resume-compaction-dismissals.ts +1 -0
- package/src/chat/stash.ts +12 -0
- package/src/chat/subagent-conversation.ts +24 -4
- package/src/chat/subagent-list.ts +14 -0
- package/src/chat/subagent-support.ts +1 -0
- package/src/chat/subagent-view.ts +9 -0
- package/src/chat/thumbnails.ts +14 -0
- package/src/chat/timeline-flash.ts +2 -0
- package/src/chat/timeline-scroll.ts +23 -0
- package/src/chat/ui/AccountPill.tsx +4 -0
- package/src/chat/ui/AnsiOutput.tsx +1 -0
- package/src/chat/ui/BookmarkMarker.tsx +5 -0
- package/src/chat/ui/BookmarkSubmenu.tsx +1 -0
- package/src/chat/ui/ChatActivity.tsx +13 -0
- package/src/chat/ui/CodeBlock.tsx +8 -0
- package/src/chat/ui/Composer.tsx +27 -3
- package/src/chat/ui/ComposerInput.tsx +22 -0
- package/src/chat/ui/DiffPool.tsx +3 -0
- package/src/chat/ui/EditDiff.tsx +5 -0
- package/src/chat/ui/GoalDock.tsx +13 -1
- package/src/chat/ui/ImageView.tsx +11 -0
- package/src/chat/ui/LimitState.tsx +23 -6
- package/src/chat/ui/Markdown.tsx +41 -0
- package/src/chat/ui/MermaidBlock.tsx +16 -0
- package/src/chat/ui/MessageActions.tsx +8 -0
- package/src/chat/ui/ModelMarks.tsx +1 -0
- package/src/chat/ui/Pickers.tsx +30 -1
- package/src/chat/ui/PromptComposer.tsx +2 -0
- package/src/chat/ui/PromptEditor.tsx +136 -0
- package/src/chat/ui/QuoteButton.tsx +12 -0
- package/src/chat/ui/ReferenceChip.tsx +3 -0
- package/src/chat/ui/ResumeCompactionDock.tsx +1 -0
- package/src/chat/ui/RowTime.tsx +1 -0
- package/src/chat/ui/RunSettings.tsx +38 -0
- package/src/chat/ui/Scrubber.tsx +14 -0
- package/src/chat/ui/SessionLine.tsx +1 -0
- package/src/chat/ui/ShellCommand.tsx +32 -0
- package/src/chat/ui/SubagentControls.tsx +7 -0
- package/src/chat/ui/SubagentInfo.tsx +1 -0
- package/src/chat/ui/SubagentStopButton.tsx +6 -0
- package/src/chat/ui/SubagentTimeline.tsx +10 -0
- package/src/chat/ui/Timeline.tsx +141 -1
- package/src/chat/ui/TimelineMenu.tsx +5 -0
- package/src/chat/ui/UnifiedDiff.tsx +6 -0
- package/src/chat/ui/UploadThumb.tsx +2 -0
- package/src/chat/ui/VisualDialogs.tsx +3 -0
- package/src/chat/ui/VisualFrame.tsx +15 -0
- package/src/chat/ui/Welcome.tsx +1 -0
- package/src/chat/ui/chip-clipboard.ts +20 -0
- package/src/chat/ui/code-lines.ts +14 -0
- package/src/chat/ui/code-theme.ts +1 -0
- package/src/chat/ui/composer/ComposerControls.tsx +70 -15
- package/src/chat/ui/composer/ComposerMenu.tsx +12 -0
- package/src/chat/ui/composer/DraftParts.tsx +2 -0
- package/src/chat/ui/composer/QueuedMessages.tsx +4 -0
- package/src/chat/ui/composer/chip-paste.ts +16 -0
- package/src/chat/ui/composer/chips.ts +6 -0
- package/src/chat/ui/composer/editor.ts +9 -0
- package/src/chat/ui/composer/insert.ts +2 -0
- package/src/chat/ui/composer/key-handler.ts +42 -9
- package/src/chat/ui/composer/keys.ts +23 -0
- package/src/chat/ui/composer/placeholder.ts +6 -0
- package/src/chat/ui/composer/prompt-picks.ts +18 -0
- package/src/chat/ui/composer/surface.ts +5 -0
- package/src/chat/ui/composer/use-chat-controls.ts +13 -0
- package/src/chat/ui/composer/use-compaction-offer.ts +3 -0
- package/src/chat/ui/composer/use-composer-menus.ts +20 -275
- package/src/chat/ui/composer/use-composer-submit.ts +27 -0
- package/src/chat/ui/composer/use-continuable-turn.ts +3 -5
- package/src/chat/ui/composer/use-draft-files.ts +24 -0
- package/src/chat/ui/composer/use-effort-stepper.ts +8 -0
- package/src/chat/ui/composer/use-notice.ts +4 -1
- package/src/chat/ui/composer/use-pending-requests.ts +3 -0
- package/src/chat/ui/composer/use-prompt-history.ts +20 -11
- package/src/chat/ui/composer/use-prompt-hold.ts +3 -0
- package/src/chat/ui/composer/use-prompt-menus.ts +361 -0
- package/src/chat/ui/composer/use-take-back.ts +20 -2
- package/src/chat/ui/diff/ApprovalReview.tsx +18 -15
- package/src/chat/ui/diff/DiffFallbackBoundary.tsx +1 -0
- package/src/chat/ui/diff/DiffOmitted.tsx +1 -0
- package/src/chat/ui/diff/DiffStack.tsx +2 -0
- package/src/chat/ui/diff/DiffUnavailable.tsx +1 -0
- package/src/chat/ui/diff/EditorEditDiff.tsx +13 -0
- package/src/chat/ui/diff/EditorPatchDiff.tsx +4 -0
- package/src/chat/ui/diff/LegacyDiffPool.tsx +2 -0
- package/src/chat/ui/diff/LegacyEditDiff.tsx +2 -0
- package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +8 -0
- package/src/chat/ui/diff/RawDiff.tsx +1 -0
- package/src/chat/ui/diff/ReviewDiff.tsx +1 -0
- package/src/chat/ui/diff/diff-source-budget.ts +8 -0
- package/src/chat/ui/diff/editor-edit-model.ts +4 -0
- package/src/chat/ui/diff/review-plan.ts +9 -0
- package/src/chat/ui/diff/review-settle.ts +1 -0
- package/src/chat/ui/diff/syntax-runtime.ts +52 -0
- package/src/chat/ui/diff/use-review-stack.ts +9 -0
- package/src/chat/ui/diff-syntax.ts +12 -0
- package/src/chat/ui/diff-theme.ts +2 -0
- package/src/chat/ui/file-links.ts +2 -0
- package/src/chat/ui/find-reveal.ts +1 -0
- package/src/chat/ui/full-diff.ts +1 -0
- package/src/chat/ui/highlighter.ts +29 -15
- package/src/chat/ui/icons.tsx +2 -4
- package/src/chat/ui/index.ts +7 -0
- package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +25 -0
- package/src/chat/ui/intelligent-ui/UiFallbackPart.tsx +1 -0
- package/src/chat/ui/intelligent-ui/UiReply.tsx +63 -0
- package/src/chat/ui/intelligent-ui/block-local.ts +5 -0
- package/src/chat/ui/intelligent-ui/chart-data.ts +15 -0
- package/src/chat/ui/intelligent-ui/node-text.ts +17 -0
- package/src/chat/ui/intelligent-ui/renderers/binding.ts +1 -0
- package/src/chat/ui/intelligent-ui/renderers/chart.tsx +31 -0
- package/src/chat/ui/intelligent-ui/renderers/choices.tsx +4 -0
- package/src/chat/ui/intelligent-ui/renderers/content.tsx +18 -0
- package/src/chat/ui/intelligent-ui/renderers/data.tsx +11 -0
- package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +13 -0
- package/src/chat/ui/intelligent-ui/renderers/links.tsx +9 -0
- package/src/chat/ui/intelligent-ui/renderers/map-slots.ts +15 -0
- package/src/chat/ui/intelligent-ui/renderers/map-style.ts +15 -0
- package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +48 -0
- package/src/chat/ui/intelligent-ui/renderers/map.tsx +20 -0
- package/src/chat/ui/intelligent-ui/renderers/place-point.ts +3 -0
- package/src/chat/ui/intelligent-ui/renderers/structure.tsx +3 -0
- package/src/chat/ui/intelligent-ui/renderers/text.tsx +8 -0
- package/src/chat/ui/intelligent-ui/reply-parts.ts +5 -0
- package/src/chat/ui/intelligent-ui/table-data.ts +17 -0
- package/src/chat/ui/intelligent-ui/use-in-view.ts +4 -0
- package/src/chat/ui/intelligent-ui/use-later.ts +4 -0
- package/src/chat/ui/intelligent-ui/use-truncated.ts +5 -0
- package/src/chat/ui/intelligent-ui/use-ui-links.ts +29 -0
- package/src/chat/ui/intelligent-ui/use-ui-queries.ts +60 -0
- package/src/chat/ui/markdown-blocks.ts +64 -0
- package/src/chat/ui/mermaid.ts +4 -0
- package/src/chat/ui/quote-selection.ts +10 -0
- package/src/chat/ui/rehype-chips.ts +5 -0
- package/src/chat/ui/rehype-fade.ts +2 -0
- package/src/chat/ui/remark-html-as-text.ts +5 -0
- package/src/chat/ui/reveal.ts +18 -0
- package/src/chat/ui/rows/ForksRow.tsx +5 -0
- package/src/chat/ui/rows/GeneratedImageRow.tsx +10 -0
- package/src/chat/ui/rows/MessageRows.tsx +29 -5
- package/src/chat/ui/rows/RowContainer.tsx +1 -0
- package/src/chat/ui/rows/Rows.tsx +2 -0
- package/src/chat/ui/rows/SubagentRow.tsx +13 -0
- package/src/chat/ui/rows/TaskRow.tsx +7 -0
- package/src/chat/ui/rows/VisualRow.tsx +4 -0
- package/src/chat/ui/rows/WorkRows.tsx +46 -0
- package/src/chat/ui/rows/WorkflowRow.tsx +10 -0
- package/src/chat/ui/rows/row-rhythm.ts +7 -5
- package/src/chat/ui/theme-watch.ts +8 -7
- package/src/chat/ui/thread-references.ts +7 -0
- package/src/chat/ui/use-diff-source-tokens.ts +8 -0
- package/src/chat/ui/useToggleSet.ts +3 -0
- package/src/chat/ui/visual-theme.ts +30 -0
- package/src/chat/visual-bridge.ts +23 -0
- package/src/chat/visual-viewport.ts +12 -0
- package/src/host-hooks.ts +15 -5
- package/src/host.ts +29 -1
- package/src/index.ts +5 -0
- package/src/lazy.tsx +9 -0
- package/src/locales/en/agent-chat.ts +1 -0
- package/src/locales/nl/agent-chat.ts +1 -0
- package/src/mounted-registry.ts +4 -0
- package/src/port-transport.ts +32 -0
- package/src/prompts/index.ts +2 -0
- package/src/prompts/logic/focus.ts +5 -0
- package/src/prompts/logic/keys.ts +12 -0
- package/src/prompts/logic/prompts.ts +8 -0
- package/src/prompts/logic/subjects.ts +8 -0
- package/src/prompts/logic/useFocusAfterAnswer.ts +2 -0
- package/src/prompts/logic/usePromptSession.ts +13 -1
- package/src/prompts/ui/ApprovalBody.tsx +2 -0
- package/src/prompts/ui/PromptActions.tsx +2 -0
- package/src/prompts/ui/PromptCard.tsx +6 -0
- package/src/prompts/ui/PromptView.tsx +6 -0
- package/src/prompts/ui/QuestionBody.tsx +11 -0
- package/src/providers/AccountColors.tsx +1 -0
- package/src/providers/AccountDetail.tsx +7 -0
- package/src/providers/AccountVariables.tsx +2 -0
- package/src/providers/AddAccountForm.tsx +5 -1
- package/src/providers/CliDetail.tsx +6 -5
- package/src/providers/InstallPage.tsx +11 -0
- package/src/providers/ProviderList.tsx +3 -0
- package/src/providers/ProvidersPane.tsx +18 -0
- package/src/providers/account-actions.ts +10 -8
- package/src/providers/index.ts +1 -0
- package/src/providers/install-steps.ts +3 -0
- package/src/providers/pane-target.ts +5 -0
- package/src/providers/provider-abilities.ts +1 -0
- package/src/providers/provider-groups.ts +6 -0
- package/src/providers/variables.ts +12 -0
- package/src/scope.ts +2 -0
- package/src/state/chats.ts +61 -14
- package/src/state/index.ts +2 -0
- package/src/state/provider-accounts.ts +7 -9
- package/src/state/providers.ts +1 -0
- package/src/state/usage.ts +6 -0
- package/src/storage.ts +18 -0
- package/src/testing/prompts.ts +5 -0
- package/src/theme.css +3 -0
- package/src/transport.ts +18 -0
- package/src/usage/ChartLogo.tsx +12 -0
- package/src/usage/LimitsList.tsx +7 -8
- package/src/usage/UsageBreakdown.tsx +11 -4
- package/src/usage/UsageChart.tsx +5 -0
- package/src/usage/UsageLimits.tsx +10 -7
- package/src/usage/UsageLimitsCard.tsx +2 -0
- package/src/usage/UsagePage.tsx +16 -19
- package/src/usage/UsageSummary.tsx +4 -5
- package/src/usage/UsageTiles.tsx +2 -0
- package/src/usage/format.ts +7 -0
- package/src/usage/limit-groups.ts +14 -0
- package/src/usage/limits.ts +11 -13
- package/src/usage/money.ts +1 -0
- package/src/usage/summary.ts +15 -7
package/src/chat/ui/Timeline.tsx
CHANGED
|
@@ -101,6 +101,7 @@ function EmptyThread({ chatId }: { chatId: string }) {
|
|
|
101
101
|
const model = useModelName(info?.provider, info?.selection.model ?? '');
|
|
102
102
|
const sources = useHostContextSources(chatId);
|
|
103
103
|
const hidesFolder = useHostHidesFolder(chatId);
|
|
104
|
+
|
|
104
105
|
return (
|
|
105
106
|
<div className="flex grow items-center justify-center">
|
|
106
107
|
<div className="flex max-w-sm flex-col items-center gap-3 px-6 py-8 text-center">
|
|
@@ -137,6 +138,7 @@ function MarkableRow({ row, chatId, bookmark, children }: { row: TimelineRow; ch
|
|
|
137
138
|
</>
|
|
138
139
|
);
|
|
139
140
|
}
|
|
141
|
+
|
|
140
142
|
return (
|
|
141
143
|
<>
|
|
142
144
|
<BookmarkMarker chatId={chatId} itemId={row.id} bookmark={bookmark} />
|
|
@@ -162,6 +164,7 @@ interface ThreadRowProps extends Omit<RowProps, 'openConversation'> {
|
|
|
162
164
|
/* Memoized with stable props, so a streamed word or a scroll draws only the rows it changed. */
|
|
163
165
|
const ThreadRow = memo(function ThreadRow({ row, previous, index, top, measure, chatId, flashNonce, bookmark, ...actions }: ThreadRowProps) {
|
|
164
166
|
const header = replyHeader(row, previous);
|
|
167
|
+
|
|
165
168
|
return (
|
|
166
169
|
<RowContainer row={row} previous={previous} index={index} top={top} ref={measure}>
|
|
167
170
|
{flashNonce !== null && <span key={flashNonce} className="chat-flash pointer-events-none absolute inset-0 rounded-lg" aria-hidden />}
|
|
@@ -180,8 +183,21 @@ const ThreadRow = memo(function ThreadRow({ row, previous, index, top, measure,
|
|
|
180
183
|
/*
|
|
181
184
|
* `overlay` stands in the thread's place and ends where the composer starts, so the thread keeps
|
|
182
185
|
* where it was underneath and the composer stays in reach for the prompts it answers.
|
|
186
|
+
* `onContentHeight` hears what the thread's rows and its padding take, without the composer, once
|
|
187
|
+
* the rows in view are laid out at their real heights, for a host that sizes its frame to the thread.
|
|
188
|
+
* Rows out of view still count at an estimate, so a frame up to the viewport's height gets it exact.
|
|
183
189
|
*/
|
|
184
|
-
export function Timeline({
|
|
190
|
+
export function Timeline({
|
|
191
|
+
chatId,
|
|
192
|
+
composer,
|
|
193
|
+
overlay,
|
|
194
|
+
onContentHeight
|
|
195
|
+
}: {
|
|
196
|
+
chatId: string;
|
|
197
|
+
composer?: ReactNode;
|
|
198
|
+
overlay?: ReactNode;
|
|
199
|
+
onContentHeight?: (height: number) => void;
|
|
200
|
+
}) {
|
|
185
201
|
const host = useChatHost();
|
|
186
202
|
const { fork } = host;
|
|
187
203
|
const { t } = useTranslation('agent-chat');
|
|
@@ -197,24 +213,31 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
197
213
|
const cursor = useChatRow(chatId, (row) => row?.history?.cursor ?? null);
|
|
198
214
|
const scope = useChatScope();
|
|
199
215
|
const { id: scopeId, keyOf } = scope;
|
|
216
|
+
|
|
200
217
|
// Read when the menu opens rather than subscribed to, for the same reason the rows use the structure.
|
|
201
218
|
const fullItems = () => useChats.getState().byKey[keyOf(chatId)]?.items;
|
|
219
|
+
|
|
202
220
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
203
221
|
const threadRef = useRef<HTMLDivElement>(null);
|
|
204
222
|
const contentRef = useRef<HTMLDivElement>(null);
|
|
205
223
|
const quoteTakerRef = useRef<QuoteTaker | null>(null);
|
|
224
|
+
|
|
206
225
|
const registerQuoteTaker = useCallback((take: QuoteTaker) => {
|
|
207
226
|
quoteTakerRef.current = take;
|
|
227
|
+
|
|
208
228
|
return () => {
|
|
209
229
|
if (quoteTakerRef.current === take) {
|
|
210
230
|
quoteTakerRef.current = null;
|
|
211
231
|
}
|
|
212
232
|
};
|
|
213
233
|
}, []);
|
|
234
|
+
|
|
214
235
|
const followRef = useRef(true);
|
|
236
|
+
|
|
215
237
|
const stopFollowing = useCallback(() => {
|
|
216
238
|
followRef.current = false;
|
|
217
239
|
}, []);
|
|
240
|
+
|
|
218
241
|
const groups = useToggleSet(stopFollowing);
|
|
219
242
|
const turns = useToggleSet(stopFollowing);
|
|
220
243
|
const subagents = useToggleSet(stopFollowing);
|
|
@@ -234,8 +257,10 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
234
257
|
if (cursor === null || loadingEarlierRef.current) {
|
|
235
258
|
return;
|
|
236
259
|
}
|
|
260
|
+
|
|
237
261
|
loadingEarlierRef.current = true;
|
|
238
262
|
setLoadingEarlier(true);
|
|
263
|
+
|
|
239
264
|
// A page that failed is asked for again on the next scroll; the thread keeps what it has.
|
|
240
265
|
void scope.chats
|
|
241
266
|
.loadEarlier(chatId, cursor)
|
|
@@ -250,6 +275,7 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
250
275
|
if (!order || !items) {
|
|
251
276
|
return [];
|
|
252
277
|
}
|
|
278
|
+
|
|
253
279
|
return deriveTimelineRows(
|
|
254
280
|
order.map((id) => items[id]!),
|
|
255
281
|
{
|
|
@@ -262,14 +288,17 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
262
288
|
}
|
|
263
289
|
);
|
|
264
290
|
}, [order, items, groups.ids, turns.ids, subagents.ids, activeTurnId, forkedTurns, host.commandLooks]);
|
|
291
|
+
|
|
265
292
|
const cards = useHostThreadCards(scopeId, chatId);
|
|
266
293
|
const drawsVisuals = host.visuals !== null;
|
|
267
294
|
// Where the part of the thread this client holds begins, while there is a page before it.
|
|
268
295
|
const heldFrom = cursor === null ? null : (items?.[order?.[0] ?? '']?.createdAt ?? null);
|
|
296
|
+
|
|
269
297
|
const rows = useMemo(
|
|
270
298
|
() => withTimedRows(threadRows, timedRowsOf(cards, drawsVisuals ? (visuals ?? []) : null, heldFrom)),
|
|
271
299
|
[threadRows, cards, drawsVisuals, visuals, heldFrom]
|
|
272
300
|
);
|
|
301
|
+
|
|
273
302
|
const welcomes = useHostWelcome(chatId);
|
|
274
303
|
|
|
275
304
|
const loadingHistory = historyPending && ((info !== null && (info.usage.turns > 0 || info.activeTurnId !== null)) || (order?.length ?? 0) > 0);
|
|
@@ -284,6 +313,7 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
284
313
|
// A turn a jump is on its way to, which waits for the page its messages are on to come in.
|
|
285
314
|
const [revealing, setRevealing] = useState<{ turnId: string; target: TurnTarget } | null>(null);
|
|
286
315
|
const flash = useTimelineFlash((s) => s.target);
|
|
316
|
+
|
|
287
317
|
const revealBlock = useCallback(
|
|
288
318
|
(block: UiReplyTarget) => {
|
|
289
319
|
setLanding(null);
|
|
@@ -291,6 +321,7 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
291
321
|
},
|
|
292
322
|
[keyOf, chatId]
|
|
293
323
|
);
|
|
324
|
+
|
|
294
325
|
const uiNavigation = useMemo(
|
|
295
326
|
() => ({
|
|
296
327
|
chatId,
|
|
@@ -303,31 +334,40 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
303
334
|
// Whether the thread's text clears the strip depends on the width of the view, which only the thread's own frame tells.
|
|
304
335
|
useLayoutEffect(() => {
|
|
305
336
|
const element = frameRef.current;
|
|
337
|
+
|
|
306
338
|
if (element === null || typeof ResizeObserver === 'undefined') {
|
|
307
339
|
return;
|
|
308
340
|
}
|
|
341
|
+
|
|
309
342
|
const column = element.closest('.chat-column') !== null;
|
|
343
|
+
|
|
310
344
|
const measure = (): void =>
|
|
311
345
|
setFrame((current) =>
|
|
312
346
|
current.width === element.offsetWidth && current.height === element.offsetHeight && current.column === column
|
|
313
347
|
? current
|
|
314
348
|
: { width: element.offsetWidth, height: element.offsetHeight, column }
|
|
315
349
|
);
|
|
350
|
+
|
|
316
351
|
const observer = new ResizeObserver(measure);
|
|
317
352
|
observer.observe(element);
|
|
318
353
|
measure();
|
|
354
|
+
|
|
319
355
|
return () => observer.disconnect();
|
|
320
356
|
}, []);
|
|
321
357
|
|
|
322
358
|
useLayoutEffect(() => {
|
|
323
359
|
const element = composerRef.current;
|
|
360
|
+
|
|
324
361
|
if (element === null) {
|
|
325
362
|
return;
|
|
326
363
|
}
|
|
364
|
+
|
|
327
365
|
const measure = () => setComposerHeight(element.offsetHeight);
|
|
366
|
+
|
|
328
367
|
const observer = new ResizeObserver(measure);
|
|
329
368
|
observer.observe(element);
|
|
330
369
|
measure();
|
|
370
|
+
|
|
331
371
|
return () => observer.disconnect();
|
|
332
372
|
}, []);
|
|
333
373
|
|
|
@@ -369,10 +409,13 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
369
409
|
// Include the end before the first pin, so its actual heights are known before revealing it.
|
|
370
410
|
rangeExtractor: (range) => {
|
|
371
411
|
const indexes = defaultRangeExtractor(range);
|
|
412
|
+
|
|
372
413
|
if (!initialLayout) {
|
|
373
414
|
return indexes;
|
|
374
415
|
}
|
|
416
|
+
|
|
375
417
|
const tail = Math.max(8, Math.ceil(frame.height / ESTIMATED_ROW_PX));
|
|
418
|
+
|
|
376
419
|
return [...new Set([...indexes, ...Array.from({ length: Math.min(tail, range.count) }, (_, index) => range.count - 1 - index)])].sort(
|
|
377
420
|
(left, right) => left - right
|
|
378
421
|
);
|
|
@@ -389,28 +432,47 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
389
432
|
*/
|
|
390
433
|
const totalSize = virtualizer.getTotalSize();
|
|
391
434
|
const virtualRows = virtualizer.getVirtualItems();
|
|
435
|
+
|
|
392
436
|
useLayoutEffect(() => {
|
|
393
437
|
if (!initialLayout || empty || loadingHistory || frame.height === 0) {
|
|
394
438
|
return;
|
|
395
439
|
}
|
|
440
|
+
|
|
396
441
|
// Ref measurements can change offsets after render. Commit those offsets before the first paint.
|
|
397
442
|
const measured = virtualizer.getVirtualItems();
|
|
443
|
+
|
|
398
444
|
if (
|
|
399
445
|
virtualRows.length !== measured.length ||
|
|
400
446
|
virtualRows.some((row, index) => row.key !== measured[index]?.key || row.start !== measured[index]?.start || row.size !== measured[index]?.size)
|
|
401
447
|
) {
|
|
402
448
|
settleLayout();
|
|
449
|
+
|
|
403
450
|
return;
|
|
404
451
|
}
|
|
452
|
+
|
|
405
453
|
if (measured.at(-1)?.index === rows.length - 1) {
|
|
406
454
|
setMeasuredThread(keyOf(chatId));
|
|
407
455
|
}
|
|
408
456
|
}, [initialLayout, empty, loadingHistory, frame.height, virtualRows, virtualizer, rows.length, keyOf, chatId]);
|
|
457
|
+
|
|
458
|
+
useLayoutEffect(() => {
|
|
459
|
+
const thread = threadRef.current;
|
|
460
|
+
|
|
461
|
+
if (onContentHeight === undefined || initialLayout || empty || loadingHistory || thread === null) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const style = getComputedStyle(thread);
|
|
466
|
+
onContentHeight(totalSize + (Number.parseFloat(style.paddingTop) || 0) + (Number.parseFloat(style.paddingBottom) || 0));
|
|
467
|
+
}, [onContentHeight, initialLayout, empty, loadingHistory, totalSize]);
|
|
468
|
+
|
|
409
469
|
useLayoutEffect(() => {
|
|
410
470
|
const element = scrollRef.current;
|
|
471
|
+
|
|
411
472
|
if (!followRef.current || element === null) {
|
|
412
473
|
return;
|
|
413
474
|
}
|
|
475
|
+
|
|
414
476
|
// The composer is in the scroll flow, so its measured height is part of this end position.
|
|
415
477
|
element.scrollTop = element.scrollHeight;
|
|
416
478
|
scrollGeometry.current = { top: element.scrollTop, height: element.scrollHeight, viewport: element.clientHeight };
|
|
@@ -422,16 +484,21 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
422
484
|
const head = order?.[0];
|
|
423
485
|
const moved = head !== headRef.current;
|
|
424
486
|
headRef.current = head;
|
|
487
|
+
|
|
425
488
|
if (element === null) {
|
|
426
489
|
return;
|
|
427
490
|
}
|
|
491
|
+
|
|
428
492
|
const anchor = anchorRef.current;
|
|
429
493
|
const index = moved && anchor !== null && !followRef.current ? rows.findIndex((row) => row.id === anchor.id) : -1;
|
|
494
|
+
|
|
430
495
|
if (anchor !== null && index !== -1) {
|
|
431
496
|
virtualizer.getTotalSize();
|
|
432
497
|
element.scrollTop = virtualizer.measurementsCache[index]!.start - anchor.offset;
|
|
498
|
+
|
|
433
499
|
return;
|
|
434
500
|
}
|
|
501
|
+
|
|
435
502
|
const row = virtualizer.getVirtualItemForOffset(element.scrollTop);
|
|
436
503
|
anchorRef.current = row === undefined ? null : { id: String(row.key), offset: row.start - element.scrollTop };
|
|
437
504
|
});
|
|
@@ -439,6 +506,7 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
439
506
|
// Close to the top, the page before goes in; again after each page that left too little above the reader.
|
|
440
507
|
useEffect(() => {
|
|
441
508
|
const element = scrollRef.current;
|
|
509
|
+
|
|
442
510
|
if (element !== null && wantsEarlier(element)) {
|
|
443
511
|
loadEarlier();
|
|
444
512
|
}
|
|
@@ -453,13 +521,16 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
453
521
|
|
|
454
522
|
// The virtualizer materializes measurements on access; binary searches need only a few message starts.
|
|
455
523
|
const measurements = virtualizer.measurementsCache;
|
|
524
|
+
|
|
456
525
|
const starts = {
|
|
457
526
|
length: ticks.length,
|
|
458
527
|
at: (index: number): number | undefined => {
|
|
459
528
|
const tick = ticks.at(index);
|
|
529
|
+
|
|
460
530
|
return tick === undefined ? undefined : (measurements[tick.rowIndex]?.start ?? tick.rowIndex * ESTIMATED_ROW_PX);
|
|
461
531
|
}
|
|
462
532
|
};
|
|
533
|
+
|
|
463
534
|
const scrollTop = virtualizer.scrollOffset ?? 0;
|
|
464
535
|
const viewport = virtualizer.scrollRect?.height ?? 0;
|
|
465
536
|
const visibleHeight = Math.max(0, viewport - coveredHeight);
|
|
@@ -473,12 +544,16 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
473
544
|
const [, redrawStrip] = useReducer((count: number) => count + 1, 0);
|
|
474
545
|
const stripRef = useRef({ starts, inView });
|
|
475
546
|
stripRef.current = { starts, inView };
|
|
547
|
+
|
|
476
548
|
const followStrip = (element: HTMLElement): void => {
|
|
477
549
|
const { starts: current, inView: shown } = stripRef.current;
|
|
550
|
+
|
|
478
551
|
if (shown === null) {
|
|
479
552
|
return;
|
|
480
553
|
}
|
|
554
|
+
|
|
481
555
|
const next = messagesInView({ starts: current, scrollTop: element.scrollTop, visibleHeight: visibleHeightRef.current });
|
|
556
|
+
|
|
482
557
|
if (next?.first !== shown.first || next?.last !== shown.last) {
|
|
483
558
|
redrawStrip();
|
|
484
559
|
}
|
|
@@ -497,6 +572,7 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
497
572
|
order,
|
|
498
573
|
firstRowInView: () => {
|
|
499
574
|
const top = scrollRef.current?.scrollTop ?? 0;
|
|
575
|
+
|
|
500
576
|
return virtualizer.getVirtualItems().find((virtualRow) => virtualRow.end > top)?.index ?? 0;
|
|
501
577
|
},
|
|
502
578
|
openTurn: turns.add,
|
|
@@ -506,6 +582,7 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
506
582
|
scrollToRow: (index) => {
|
|
507
583
|
const top = scrollRef.current?.scrollTop ?? 0;
|
|
508
584
|
const bottom = top + visibleHeightRef.current;
|
|
585
|
+
|
|
509
586
|
if (!virtualizer.getVirtualItems().some((virtualRow) => virtualRow.index === index && virtualRow.end > top && virtualRow.start < bottom)) {
|
|
510
587
|
virtualizer.scrollToIndex(index, { align: 'center' });
|
|
511
588
|
}
|
|
@@ -515,27 +592,34 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
515
592
|
}),
|
|
516
593
|
[chatId, onMainAgent, rows, items, order, virtualizer, turns.add, groups.add, subagents.add, stopFollowing]
|
|
517
594
|
);
|
|
595
|
+
|
|
518
596
|
const chatFind = useHostTimelineFind(findOptions);
|
|
519
597
|
const searching = chatFind.open;
|
|
598
|
+
|
|
520
599
|
// A find covers the whole thread, so every page before this one comes in while it is open.
|
|
521
600
|
useEffect(() => {
|
|
522
601
|
if (searching) {
|
|
523
602
|
loadEarlier();
|
|
524
603
|
}
|
|
525
604
|
}, [searching, loadEarlier]);
|
|
605
|
+
|
|
526
606
|
const found = useMemo(() => (searching ? ticksWithHits(ticks, chatFind.hitRows) : []), [searching, ticks, chatFind.hitRows]);
|
|
527
607
|
const foundCurrent = chatFind.currentRow === null ? null : tickOfRow(ticks, chatFind.currentRow);
|
|
528
608
|
|
|
529
609
|
const jumpTo = (index: number): void => {
|
|
530
610
|
const tick = ticks[index];
|
|
611
|
+
|
|
531
612
|
if (!tick) {
|
|
532
613
|
return;
|
|
533
614
|
}
|
|
615
|
+
|
|
534
616
|
if (tick.bookmark !== null) {
|
|
535
617
|
setLanding(null);
|
|
536
618
|
setSeeking({ itemId: tick.bookmark.itemId, key: keyOf(chatId) });
|
|
619
|
+
|
|
537
620
|
return;
|
|
538
621
|
}
|
|
622
|
+
|
|
539
623
|
showRowAtTop(virtualizer, followRef, tick.rowIndex);
|
|
540
624
|
};
|
|
541
625
|
|
|
@@ -543,65 +627,89 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
543
627
|
if (seeking === null) {
|
|
544
628
|
return;
|
|
545
629
|
}
|
|
630
|
+
|
|
546
631
|
if (seeking.key !== keyOf(chatId)) {
|
|
547
632
|
setSeeking(null);
|
|
633
|
+
|
|
548
634
|
return;
|
|
549
635
|
}
|
|
636
|
+
|
|
550
637
|
if (loadingHistory) {
|
|
551
638
|
return;
|
|
552
639
|
}
|
|
640
|
+
|
|
553
641
|
if (seeking.block) {
|
|
554
642
|
const jump = uiReplyJump(seeking.block, rows, items, turns.ids, cursor !== null);
|
|
643
|
+
|
|
555
644
|
if (jump.kind === 'earlier') {
|
|
556
645
|
loadEarlier();
|
|
557
646
|
} else if (jump.kind === 'turn') {
|
|
558
647
|
turns.add(jump.turnId);
|
|
559
648
|
} else {
|
|
560
649
|
setSeeking(null);
|
|
650
|
+
|
|
561
651
|
if (jump.kind === 'row') {
|
|
562
652
|
showRowAtTop(virtualizer, followRef, jump.index);
|
|
563
653
|
setLanding({ target: seeking.block, key: seeking.key });
|
|
564
654
|
}
|
|
565
655
|
}
|
|
656
|
+
|
|
566
657
|
return;
|
|
567
658
|
}
|
|
659
|
+
|
|
568
660
|
const index = rows.findIndex((row) => row.id === seeking.itemId);
|
|
661
|
+
|
|
569
662
|
if (index !== -1) {
|
|
570
663
|
setSeeking(null);
|
|
571
664
|
showRowAtTop(virtualizer, followRef, index);
|
|
665
|
+
|
|
572
666
|
if (seeking.options?.flash) {
|
|
573
667
|
useTimelineFlash.getState().flash(seeking.key, seeking.itemId, false);
|
|
574
668
|
}
|
|
669
|
+
|
|
575
670
|
return;
|
|
576
671
|
}
|
|
672
|
+
|
|
577
673
|
// The thread has not loaded yet, so nothing says the item is missing; the effect below bounds the wait.
|
|
578
674
|
if (items === undefined) {
|
|
579
675
|
return;
|
|
580
676
|
}
|
|
677
|
+
|
|
581
678
|
const item = items[seeking.itemId];
|
|
679
|
+
|
|
582
680
|
if (item === undefined && cursor !== null) {
|
|
583
681
|
loadEarlier();
|
|
682
|
+
|
|
584
683
|
return;
|
|
585
684
|
}
|
|
685
|
+
|
|
586
686
|
const turnId = item?.turnId ?? null;
|
|
687
|
+
|
|
587
688
|
if (turnId !== null && !turns.ids.has(turnId)) {
|
|
588
689
|
turns.add(turnId);
|
|
690
|
+
|
|
589
691
|
return;
|
|
590
692
|
}
|
|
693
|
+
|
|
591
694
|
setSeeking(null);
|
|
592
695
|
seeking.options?.onMissing?.();
|
|
593
696
|
}, [seeking, rows, items, turns, virtualizer, cursor, loadEarlier, keyOf, chatId, loadingHistory]);
|
|
697
|
+
|
|
594
698
|
const waitingForThread = seeking !== null && (items === undefined || loadingHistory);
|
|
699
|
+
|
|
595
700
|
useEffect(() => {
|
|
596
701
|
if (!waitingForThread) {
|
|
597
702
|
return;
|
|
598
703
|
}
|
|
704
|
+
|
|
599
705
|
const timer = window.setTimeout(() => {
|
|
600
706
|
setSeeking(null);
|
|
601
707
|
seeking?.options?.onMissing?.();
|
|
602
708
|
}, JUMP_THREAD_WAIT_MS);
|
|
709
|
+
|
|
603
710
|
return () => window.clearTimeout(timer);
|
|
604
711
|
}, [waitingForThread, seeking]);
|
|
712
|
+
|
|
605
713
|
useEffect(
|
|
606
714
|
() =>
|
|
607
715
|
registerItemJumper(keyOf(chatId), (itemId, options) => {
|
|
@@ -610,14 +718,18 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
610
718
|
}),
|
|
611
719
|
[keyOf, chatId]
|
|
612
720
|
);
|
|
721
|
+
|
|
613
722
|
useLayoutEffect(() => {
|
|
614
723
|
if (!landing || landing.key !== keyOf(chatId)) {
|
|
615
724
|
return;
|
|
616
725
|
}
|
|
726
|
+
|
|
617
727
|
let frame = 0;
|
|
618
728
|
let attempts = 0;
|
|
729
|
+
|
|
619
730
|
const reveal = () => {
|
|
620
731
|
const scroller = scrollRef.current;
|
|
732
|
+
|
|
621
733
|
if (scroller && revealUiReplyBlock(scroller, landing.target)) {
|
|
622
734
|
useTimelineFlash.getState().flash(keyOf(chatId), landing.target.itemId, false, landing.target);
|
|
623
735
|
setLanding(null);
|
|
@@ -628,7 +740,9 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
628
740
|
setLanding(null);
|
|
629
741
|
}
|
|
630
742
|
};
|
|
743
|
+
|
|
631
744
|
frame = requestAnimationFrame(reveal);
|
|
745
|
+
|
|
632
746
|
return () => cancelAnimationFrame(frame);
|
|
633
747
|
}, [landing, keyOf, chatId]);
|
|
634
748
|
|
|
@@ -636,28 +750,36 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
636
750
|
if (revealing === null) {
|
|
637
751
|
return;
|
|
638
752
|
}
|
|
753
|
+
|
|
639
754
|
const { turnId, target } = revealing;
|
|
640
755
|
const changes = target === 'changes' ? rows.findIndex((row) => row.kind === 'changed-files' && row.turnId === turnId) : -1;
|
|
641
756
|
const opening = order?.map((id) => items?.[id]).find((item) => item?.kind === 'user' && item.turnId === turnId);
|
|
642
757
|
const found = changes !== -1 ? changes : rows.findIndex((row) => row.id === opening?.id || row.id === `start-${turnId}` || row.id === `fold-${turnId}`);
|
|
758
|
+
|
|
643
759
|
if (found !== -1) {
|
|
644
760
|
setRevealing(null);
|
|
645
761
|
showRowAtTop(virtualizer, followRef, found);
|
|
646
762
|
useTimelineFlash.getState().flash(keyOf(chatId), rows[found]!.id, changes !== -1);
|
|
763
|
+
|
|
647
764
|
return;
|
|
648
765
|
}
|
|
766
|
+
|
|
649
767
|
// The turn is before the page this thread holds; a thread that holds it all has no such turn.
|
|
650
768
|
if (cursor === null) {
|
|
651
769
|
setRevealing(null);
|
|
770
|
+
|
|
652
771
|
return;
|
|
653
772
|
}
|
|
773
|
+
|
|
654
774
|
loadEarlier();
|
|
655
775
|
}, [revealing, rows, order, items, virtualizer, cursor, loadEarlier, keyOf, chatId]);
|
|
776
|
+
|
|
656
777
|
useEffect(() => registerTurnJumper(keyOf(chatId), (turnId, target) => setRevealing({ turnId, target })), [keyOf, chatId]);
|
|
657
778
|
// The strip is memoized and draws on every scroll, so it gets one callback for its life.
|
|
658
779
|
const jumpRef = useRef(jumpTo);
|
|
659
780
|
jumpRef.current = jumpTo;
|
|
660
781
|
const pick = useCallback((index: number) => jumpRef.current(index), []);
|
|
782
|
+
|
|
661
783
|
// Read when the card asks rather than on every render, so the strip's memo holds while the thread streams.
|
|
662
784
|
const cardChat = useMemo<CardChat>(
|
|
663
785
|
() => ({
|
|
@@ -666,7 +788,9 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
666
788
|
if (fork === null) {
|
|
667
789
|
return false;
|
|
668
790
|
}
|
|
791
|
+
|
|
669
792
|
const row = useChats.getState().byKey[keyOf(chatId)];
|
|
793
|
+
|
|
670
794
|
return forkRefusal(row?.info ?? null, row?.structure[turnId]) === null;
|
|
671
795
|
},
|
|
672
796
|
fork: (turnId) => fork?.(chatId, turnId)
|
|
@@ -675,40 +799,51 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
675
799
|
);
|
|
676
800
|
|
|
677
801
|
const stepRef = useRef<(direction: -1 | 1) => boolean>(() => false);
|
|
802
|
+
|
|
678
803
|
stepRef.current = (direction) => {
|
|
679
804
|
if (!onMainAgent) {
|
|
680
805
|
return false;
|
|
681
806
|
}
|
|
807
|
+
|
|
682
808
|
const index = stepMessage(starts, (i) => ticks[i]!.kind === 'person', scrollTop, direction);
|
|
809
|
+
|
|
683
810
|
if (index === null) {
|
|
684
811
|
return false;
|
|
685
812
|
}
|
|
813
|
+
|
|
686
814
|
jumpTo(index);
|
|
815
|
+
|
|
687
816
|
return true;
|
|
688
817
|
};
|
|
818
|
+
|
|
689
819
|
useEffect(() => registerMessageStepper(keyOf(chatId), (direction) => stepRef.current(direction)), [keyOf, chatId]);
|
|
690
820
|
|
|
691
821
|
// Read through a ref, so the rows keep one callback for their life and their memo holds.
|
|
692
822
|
const opensRef = useRef({ rows, show });
|
|
693
823
|
opensRef.current = { rows, show };
|
|
824
|
+
|
|
694
825
|
/* The whole of what a sub-agent did, in the thread's place. Its row only keeps the beginning. */
|
|
695
826
|
const openConversation = useCallback((step: SubagentStep): void => {
|
|
696
827
|
opensRef.current.show(openFromMain(step));
|
|
697
828
|
}, []);
|
|
698
829
|
|
|
699
830
|
const addSubagent = subagents.add;
|
|
831
|
+
|
|
700
832
|
/* The header of a turn a sub-agent woke. It points at the row that agent worked in, or the one it hangs under. */
|
|
701
833
|
const openSubagent = useCallback(
|
|
702
834
|
(toolUseId: string): void => {
|
|
703
835
|
const found = findSubagentBranch(opensRef.current.rows, toolUseId);
|
|
836
|
+
|
|
704
837
|
if (!found) {
|
|
705
838
|
return;
|
|
706
839
|
}
|
|
840
|
+
|
|
707
841
|
addSubagent(found.branch.id);
|
|
708
842
|
showRowAtTop(virtualizer, followRef, found.index);
|
|
709
843
|
},
|
|
710
844
|
[addSubagent, virtualizer]
|
|
711
845
|
);
|
|
846
|
+
|
|
712
847
|
const flashedRow = flash !== null && flash.key === keyOf(chatId) && !flash.block ? flash.rowId : null;
|
|
713
848
|
|
|
714
849
|
return (
|
|
@@ -737,16 +872,20 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
737
872
|
const previous = scrollGeometry.current;
|
|
738
873
|
const layoutChanged = previous.height !== el.scrollHeight || previous.viewport !== el.clientHeight;
|
|
739
874
|
const atEnd = el.scrollHeight - el.scrollTop - el.clientHeight < FOLLOW_THRESHOLD_PX;
|
|
875
|
+
|
|
740
876
|
if (!layoutChanged && el.scrollTop < previous.top) {
|
|
741
877
|
followRef.current = false;
|
|
742
878
|
} else if (atEnd) {
|
|
743
879
|
followRef.current = true;
|
|
744
880
|
}
|
|
881
|
+
|
|
745
882
|
scrollGeometry.current = { top: el.scrollTop, height: el.scrollHeight, viewport: el.clientHeight };
|
|
746
883
|
setTimelineAtEnd(chatId, followRef.current);
|
|
884
|
+
|
|
747
885
|
if (wantsEarlier(el)) {
|
|
748
886
|
loadEarlier();
|
|
749
887
|
}
|
|
888
|
+
|
|
750
889
|
followStrip(el);
|
|
751
890
|
}}
|
|
752
891
|
>
|
|
@@ -782,6 +921,7 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
|
|
|
782
921
|
<FindRevealContext.Provider value={chatFind.reveal}>
|
|
783
922
|
{virtualRows.map((virtualRow) => {
|
|
784
923
|
const row = rows[virtualRow.index]!;
|
|
924
|
+
|
|
785
925
|
return (
|
|
786
926
|
<ThreadRow
|
|
787
927
|
key={row.id}
|
|
@@ -42,6 +42,7 @@ export function TimelineMenuPopup({
|
|
|
42
42
|
const lookup = useReferenceLookup(chatId ?? '');
|
|
43
43
|
const { fork, fileLinks } = useChatHost();
|
|
44
44
|
const openFileLink = useOpenFileLink();
|
|
45
|
+
|
|
45
46
|
const openInPreview = (): void => {
|
|
46
47
|
if (target.file != null) {
|
|
47
48
|
openFileLink(target.file.cwd, target.file.ref, target.file.scopeId);
|
|
@@ -49,6 +50,7 @@ export function TimelineMenuPopup({
|
|
|
49
50
|
openFileLink(cwd, { path: target.path, ...(target.line === null ? {} : { line: target.line }), directory: false }, scopeId);
|
|
50
51
|
}
|
|
51
52
|
};
|
|
53
|
+
|
|
52
54
|
return (
|
|
53
55
|
<ContextMenu.Popup>
|
|
54
56
|
<ContextMenu.Item disabled={target.selection === ''} onClick={() => copyText(target.selection)}>
|
|
@@ -108,6 +110,7 @@ export function BookmarkMenuItems({ chatId, itemId, onName }: { chatId: string;
|
|
|
108
110
|
const scope = useChatScope();
|
|
109
111
|
const host = useChatHost();
|
|
110
112
|
const bookmark = useChatRow(chatId, (row) => row?.bookmarks?.find((candidate) => candidate.itemId === itemId) ?? null);
|
|
113
|
+
|
|
111
114
|
if (bookmark === null) {
|
|
112
115
|
return (
|
|
113
116
|
<ContextMenu.Item
|
|
@@ -120,6 +123,7 @@ export function BookmarkMenuItems({ chatId, itemId, onName }: { chatId: string;
|
|
|
120
123
|
</ContextMenu.Item>
|
|
121
124
|
);
|
|
122
125
|
}
|
|
126
|
+
|
|
123
127
|
return (
|
|
124
128
|
<>
|
|
125
129
|
<ContextMenu.Item
|
|
@@ -141,6 +145,7 @@ export function BookmarkMenuItems({ chatId, itemId, onName }: { chatId: string;
|
|
|
141
145
|
function ReadingWidthSubmenu() {
|
|
142
146
|
const { t } = useTranslation('agent-chat');
|
|
143
147
|
const readingWidth = useChatPreferences((preferences) => preferences.readingWidth);
|
|
148
|
+
|
|
144
149
|
return (
|
|
145
150
|
<ContextMenu.SubmenuRoot>
|
|
146
151
|
<ContextMenu.SubmenuTrigger>
|
|
@@ -25,6 +25,7 @@ export default function UnifiedDiff(props: UnifiedDiffProps) {
|
|
|
25
25
|
const legacyAllowed = legacyMode && legacyDiffBudget(oldText, newText, change.diff);
|
|
26
26
|
const rawAllowed = hasHunks || legacyDiffBudget('', '', change.diff);
|
|
27
27
|
const omitted = <DiffOmitted path={change.path} />;
|
|
28
|
+
|
|
28
29
|
const fallback = legacyAllowed ? (
|
|
29
30
|
<DiffFallbackBoundary fallback={omitted} resetKeys={[change.path, change.diff, oldText, newText, diffStyle]}>
|
|
30
31
|
<LazyBoundary fallback={<div className="px-3 py-2 text-xs text-text-faint">{t('work.loadingDiff')}</div>}>
|
|
@@ -34,16 +35,21 @@ export default function UnifiedDiff(props: UnifiedDiffProps) {
|
|
|
34
35
|
) : (
|
|
35
36
|
omitted
|
|
36
37
|
);
|
|
38
|
+
|
|
37
39
|
if (!sourceAllowed) {
|
|
38
40
|
return omitted;
|
|
39
41
|
}
|
|
42
|
+
|
|
40
43
|
if (!hasHunks) {
|
|
41
44
|
return rawAllowed ? <RawDiff text={change.diff} overflow={overflow} fill={fill} maxHeight={maxHeight} /> : omitted;
|
|
42
45
|
}
|
|
46
|
+
|
|
43
47
|
if (legacyMode) {
|
|
44
48
|
return fallback;
|
|
45
49
|
}
|
|
50
|
+
|
|
46
51
|
const unavailable = <DiffUnavailable path={change.path} />;
|
|
52
|
+
|
|
47
53
|
return (
|
|
48
54
|
<DiffFallbackBoundary fallback={unavailable} resetKeys={[change.path, change.diff, oldText, newText, ignoreWhitespace, diffStyle, loadEngine]}>
|
|
49
55
|
<LazyBoundary fallback={<div className="px-3 py-2 text-xs text-text-faint">{t('work.loadingDiff')}</div>}>
|
|
@@ -6,8 +6,10 @@ const BOX = 'h-14 w-14 rounded-lg border border-border';
|
|
|
6
6
|
/* An image the composer holds, drawn from its thumbnail; an empty box of the same size stands in while that is made. */
|
|
7
7
|
export function UploadThumb({ upload }: { upload: ChatAttachmentUpload }) {
|
|
8
8
|
const url = useUploadThumbnail(upload);
|
|
9
|
+
|
|
9
10
|
if (url === null) {
|
|
10
11
|
return <span role="img" aria-label={upload.name} className={`block bg-surface-sunken ${BOX}`} />;
|
|
11
12
|
}
|
|
13
|
+
|
|
12
14
|
return <img src={url} alt={upload.name} className={`object-cover ${BOX}`} />;
|
|
13
15
|
}
|
|
@@ -20,9 +20,11 @@ export function VisualDialogs({ chatId }: { chatId: string }) {
|
|
|
20
20
|
const visual = useChatRow(chatId, (row) => (visualId === null ? null : (row?.visuals?.find((candidate) => candidate.id === visualId) ?? null)));
|
|
21
21
|
// The dialog fades out after it closes, and draws what it showed until then.
|
|
22
22
|
const [shown, setShown] = useState<ChatVisual | null>(visual);
|
|
23
|
+
|
|
23
24
|
if (visual !== null && visual !== shown) {
|
|
24
25
|
setShown(visual);
|
|
25
26
|
}
|
|
27
|
+
|
|
26
28
|
const close = (): void => useVisualDialog.getState().close();
|
|
27
29
|
|
|
28
30
|
return (
|
|
@@ -55,6 +57,7 @@ export function VisualDialogs({ chatId }: { chatId: string }) {
|
|
|
55
57
|
if (visualId !== null) {
|
|
56
58
|
await scope.chats.removeVisual(chatId, visualId);
|
|
57
59
|
}
|
|
60
|
+
|
|
58
61
|
close();
|
|
59
62
|
}}
|
|
60
63
|
onOpenChange={close}
|