@adecore/agents-react 0.39.0 → 0.40.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/dist/agents/AgentModelLabel.js +2 -1
- 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/GoalDock.js +2 -1
- package/dist/chat/ui/LimitState.js +4 -6
- 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.js +216 -253
- 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/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/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/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/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 +27 -27
- package/src/agents/AccountDot.tsx +1 -0
- package/src/agents/AgentIcon.tsx +2 -0
- package/src/agents/AgentModelLabel.tsx +4 -6
- 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 +15 -0
- 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 +26 -0
- package/src/chat/ui/PromptComposer.tsx +2 -0
- package/src/chat/ui/PromptEditor.tsx +6 -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 +119 -0
- 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 +5 -0
- 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 +39 -0
- 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 +1 -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 +2 -0
- 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 +34 -14
- 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 +4 -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 +6 -4
- package/src/host.ts +2 -0
- package/src/index.ts +3 -0
- package/src/lazy.tsx +9 -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/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/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 />}
|
|
@@ -210,24 +213,31 @@ export function Timeline({
|
|
|
210
213
|
const cursor = useChatRow(chatId, (row) => row?.history?.cursor ?? null);
|
|
211
214
|
const scope = useChatScope();
|
|
212
215
|
const { id: scopeId, keyOf } = scope;
|
|
216
|
+
|
|
213
217
|
// Read when the menu opens rather than subscribed to, for the same reason the rows use the structure.
|
|
214
218
|
const fullItems = () => useChats.getState().byKey[keyOf(chatId)]?.items;
|
|
219
|
+
|
|
215
220
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
216
221
|
const threadRef = useRef<HTMLDivElement>(null);
|
|
217
222
|
const contentRef = useRef<HTMLDivElement>(null);
|
|
218
223
|
const quoteTakerRef = useRef<QuoteTaker | null>(null);
|
|
224
|
+
|
|
219
225
|
const registerQuoteTaker = useCallback((take: QuoteTaker) => {
|
|
220
226
|
quoteTakerRef.current = take;
|
|
227
|
+
|
|
221
228
|
return () => {
|
|
222
229
|
if (quoteTakerRef.current === take) {
|
|
223
230
|
quoteTakerRef.current = null;
|
|
224
231
|
}
|
|
225
232
|
};
|
|
226
233
|
}, []);
|
|
234
|
+
|
|
227
235
|
const followRef = useRef(true);
|
|
236
|
+
|
|
228
237
|
const stopFollowing = useCallback(() => {
|
|
229
238
|
followRef.current = false;
|
|
230
239
|
}, []);
|
|
240
|
+
|
|
231
241
|
const groups = useToggleSet(stopFollowing);
|
|
232
242
|
const turns = useToggleSet(stopFollowing);
|
|
233
243
|
const subagents = useToggleSet(stopFollowing);
|
|
@@ -247,8 +257,10 @@ export function Timeline({
|
|
|
247
257
|
if (cursor === null || loadingEarlierRef.current) {
|
|
248
258
|
return;
|
|
249
259
|
}
|
|
260
|
+
|
|
250
261
|
loadingEarlierRef.current = true;
|
|
251
262
|
setLoadingEarlier(true);
|
|
263
|
+
|
|
252
264
|
// A page that failed is asked for again on the next scroll; the thread keeps what it has.
|
|
253
265
|
void scope.chats
|
|
254
266
|
.loadEarlier(chatId, cursor)
|
|
@@ -263,6 +275,7 @@ export function Timeline({
|
|
|
263
275
|
if (!order || !items) {
|
|
264
276
|
return [];
|
|
265
277
|
}
|
|
278
|
+
|
|
266
279
|
return deriveTimelineRows(
|
|
267
280
|
order.map((id) => items[id]!),
|
|
268
281
|
{
|
|
@@ -275,14 +288,17 @@ export function Timeline({
|
|
|
275
288
|
}
|
|
276
289
|
);
|
|
277
290
|
}, [order, items, groups.ids, turns.ids, subagents.ids, activeTurnId, forkedTurns, host.commandLooks]);
|
|
291
|
+
|
|
278
292
|
const cards = useHostThreadCards(scopeId, chatId);
|
|
279
293
|
const drawsVisuals = host.visuals !== null;
|
|
280
294
|
// Where the part of the thread this client holds begins, while there is a page before it.
|
|
281
295
|
const heldFrom = cursor === null ? null : (items?.[order?.[0] ?? '']?.createdAt ?? null);
|
|
296
|
+
|
|
282
297
|
const rows = useMemo(
|
|
283
298
|
() => withTimedRows(threadRows, timedRowsOf(cards, drawsVisuals ? (visuals ?? []) : null, heldFrom)),
|
|
284
299
|
[threadRows, cards, drawsVisuals, visuals, heldFrom]
|
|
285
300
|
);
|
|
301
|
+
|
|
286
302
|
const welcomes = useHostWelcome(chatId);
|
|
287
303
|
|
|
288
304
|
const loadingHistory = historyPending && ((info !== null && (info.usage.turns > 0 || info.activeTurnId !== null)) || (order?.length ?? 0) > 0);
|
|
@@ -297,6 +313,7 @@ export function Timeline({
|
|
|
297
313
|
// A turn a jump is on its way to, which waits for the page its messages are on to come in.
|
|
298
314
|
const [revealing, setRevealing] = useState<{ turnId: string; target: TurnTarget } | null>(null);
|
|
299
315
|
const flash = useTimelineFlash((s) => s.target);
|
|
316
|
+
|
|
300
317
|
const revealBlock = useCallback(
|
|
301
318
|
(block: UiReplyTarget) => {
|
|
302
319
|
setLanding(null);
|
|
@@ -304,6 +321,7 @@ export function Timeline({
|
|
|
304
321
|
},
|
|
305
322
|
[keyOf, chatId]
|
|
306
323
|
);
|
|
324
|
+
|
|
307
325
|
const uiNavigation = useMemo(
|
|
308
326
|
() => ({
|
|
309
327
|
chatId,
|
|
@@ -316,31 +334,40 @@ export function Timeline({
|
|
|
316
334
|
// Whether the thread's text clears the strip depends on the width of the view, which only the thread's own frame tells.
|
|
317
335
|
useLayoutEffect(() => {
|
|
318
336
|
const element = frameRef.current;
|
|
337
|
+
|
|
319
338
|
if (element === null || typeof ResizeObserver === 'undefined') {
|
|
320
339
|
return;
|
|
321
340
|
}
|
|
341
|
+
|
|
322
342
|
const column = element.closest('.chat-column') !== null;
|
|
343
|
+
|
|
323
344
|
const measure = (): void =>
|
|
324
345
|
setFrame((current) =>
|
|
325
346
|
current.width === element.offsetWidth && current.height === element.offsetHeight && current.column === column
|
|
326
347
|
? current
|
|
327
348
|
: { width: element.offsetWidth, height: element.offsetHeight, column }
|
|
328
349
|
);
|
|
350
|
+
|
|
329
351
|
const observer = new ResizeObserver(measure);
|
|
330
352
|
observer.observe(element);
|
|
331
353
|
measure();
|
|
354
|
+
|
|
332
355
|
return () => observer.disconnect();
|
|
333
356
|
}, []);
|
|
334
357
|
|
|
335
358
|
useLayoutEffect(() => {
|
|
336
359
|
const element = composerRef.current;
|
|
360
|
+
|
|
337
361
|
if (element === null) {
|
|
338
362
|
return;
|
|
339
363
|
}
|
|
364
|
+
|
|
340
365
|
const measure = () => setComposerHeight(element.offsetHeight);
|
|
366
|
+
|
|
341
367
|
const observer = new ResizeObserver(measure);
|
|
342
368
|
observer.observe(element);
|
|
343
369
|
measure();
|
|
370
|
+
|
|
344
371
|
return () => observer.disconnect();
|
|
345
372
|
}, []);
|
|
346
373
|
|
|
@@ -382,10 +409,13 @@ export function Timeline({
|
|
|
382
409
|
// Include the end before the first pin, so its actual heights are known before revealing it.
|
|
383
410
|
rangeExtractor: (range) => {
|
|
384
411
|
const indexes = defaultRangeExtractor(range);
|
|
412
|
+
|
|
385
413
|
if (!initialLayout) {
|
|
386
414
|
return indexes;
|
|
387
415
|
}
|
|
416
|
+
|
|
388
417
|
const tail = Math.max(8, Math.ceil(frame.height / ESTIMATED_ROW_PX));
|
|
418
|
+
|
|
389
419
|
return [...new Set([...indexes, ...Array.from({ length: Math.min(tail, range.count) }, (_, index) => range.count - 1 - index)])].sort(
|
|
390
420
|
(left, right) => left - right
|
|
391
421
|
);
|
|
@@ -402,36 +432,47 @@ export function Timeline({
|
|
|
402
432
|
*/
|
|
403
433
|
const totalSize = virtualizer.getTotalSize();
|
|
404
434
|
const virtualRows = virtualizer.getVirtualItems();
|
|
435
|
+
|
|
405
436
|
useLayoutEffect(() => {
|
|
406
437
|
if (!initialLayout || empty || loadingHistory || frame.height === 0) {
|
|
407
438
|
return;
|
|
408
439
|
}
|
|
440
|
+
|
|
409
441
|
// Ref measurements can change offsets after render. Commit those offsets before the first paint.
|
|
410
442
|
const measured = virtualizer.getVirtualItems();
|
|
443
|
+
|
|
411
444
|
if (
|
|
412
445
|
virtualRows.length !== measured.length ||
|
|
413
446
|
virtualRows.some((row, index) => row.key !== measured[index]?.key || row.start !== measured[index]?.start || row.size !== measured[index]?.size)
|
|
414
447
|
) {
|
|
415
448
|
settleLayout();
|
|
449
|
+
|
|
416
450
|
return;
|
|
417
451
|
}
|
|
452
|
+
|
|
418
453
|
if (measured.at(-1)?.index === rows.length - 1) {
|
|
419
454
|
setMeasuredThread(keyOf(chatId));
|
|
420
455
|
}
|
|
421
456
|
}, [initialLayout, empty, loadingHistory, frame.height, virtualRows, virtualizer, rows.length, keyOf, chatId]);
|
|
457
|
+
|
|
422
458
|
useLayoutEffect(() => {
|
|
423
459
|
const thread = threadRef.current;
|
|
460
|
+
|
|
424
461
|
if (onContentHeight === undefined || initialLayout || empty || loadingHistory || thread === null) {
|
|
425
462
|
return;
|
|
426
463
|
}
|
|
464
|
+
|
|
427
465
|
const style = getComputedStyle(thread);
|
|
428
466
|
onContentHeight(totalSize + (Number.parseFloat(style.paddingTop) || 0) + (Number.parseFloat(style.paddingBottom) || 0));
|
|
429
467
|
}, [onContentHeight, initialLayout, empty, loadingHistory, totalSize]);
|
|
468
|
+
|
|
430
469
|
useLayoutEffect(() => {
|
|
431
470
|
const element = scrollRef.current;
|
|
471
|
+
|
|
432
472
|
if (!followRef.current || element === null) {
|
|
433
473
|
return;
|
|
434
474
|
}
|
|
475
|
+
|
|
435
476
|
// The composer is in the scroll flow, so its measured height is part of this end position.
|
|
436
477
|
element.scrollTop = element.scrollHeight;
|
|
437
478
|
scrollGeometry.current = { top: element.scrollTop, height: element.scrollHeight, viewport: element.clientHeight };
|
|
@@ -443,16 +484,21 @@ export function Timeline({
|
|
|
443
484
|
const head = order?.[0];
|
|
444
485
|
const moved = head !== headRef.current;
|
|
445
486
|
headRef.current = head;
|
|
487
|
+
|
|
446
488
|
if (element === null) {
|
|
447
489
|
return;
|
|
448
490
|
}
|
|
491
|
+
|
|
449
492
|
const anchor = anchorRef.current;
|
|
450
493
|
const index = moved && anchor !== null && !followRef.current ? rows.findIndex((row) => row.id === anchor.id) : -1;
|
|
494
|
+
|
|
451
495
|
if (anchor !== null && index !== -1) {
|
|
452
496
|
virtualizer.getTotalSize();
|
|
453
497
|
element.scrollTop = virtualizer.measurementsCache[index]!.start - anchor.offset;
|
|
498
|
+
|
|
454
499
|
return;
|
|
455
500
|
}
|
|
501
|
+
|
|
456
502
|
const row = virtualizer.getVirtualItemForOffset(element.scrollTop);
|
|
457
503
|
anchorRef.current = row === undefined ? null : { id: String(row.key), offset: row.start - element.scrollTop };
|
|
458
504
|
});
|
|
@@ -460,6 +506,7 @@ export function Timeline({
|
|
|
460
506
|
// Close to the top, the page before goes in; again after each page that left too little above the reader.
|
|
461
507
|
useEffect(() => {
|
|
462
508
|
const element = scrollRef.current;
|
|
509
|
+
|
|
463
510
|
if (element !== null && wantsEarlier(element)) {
|
|
464
511
|
loadEarlier();
|
|
465
512
|
}
|
|
@@ -474,13 +521,16 @@ export function Timeline({
|
|
|
474
521
|
|
|
475
522
|
// The virtualizer materializes measurements on access; binary searches need only a few message starts.
|
|
476
523
|
const measurements = virtualizer.measurementsCache;
|
|
524
|
+
|
|
477
525
|
const starts = {
|
|
478
526
|
length: ticks.length,
|
|
479
527
|
at: (index: number): number | undefined => {
|
|
480
528
|
const tick = ticks.at(index);
|
|
529
|
+
|
|
481
530
|
return tick === undefined ? undefined : (measurements[tick.rowIndex]?.start ?? tick.rowIndex * ESTIMATED_ROW_PX);
|
|
482
531
|
}
|
|
483
532
|
};
|
|
533
|
+
|
|
484
534
|
const scrollTop = virtualizer.scrollOffset ?? 0;
|
|
485
535
|
const viewport = virtualizer.scrollRect?.height ?? 0;
|
|
486
536
|
const visibleHeight = Math.max(0, viewport - coveredHeight);
|
|
@@ -494,12 +544,16 @@ export function Timeline({
|
|
|
494
544
|
const [, redrawStrip] = useReducer((count: number) => count + 1, 0);
|
|
495
545
|
const stripRef = useRef({ starts, inView });
|
|
496
546
|
stripRef.current = { starts, inView };
|
|
547
|
+
|
|
497
548
|
const followStrip = (element: HTMLElement): void => {
|
|
498
549
|
const { starts: current, inView: shown } = stripRef.current;
|
|
550
|
+
|
|
499
551
|
if (shown === null) {
|
|
500
552
|
return;
|
|
501
553
|
}
|
|
554
|
+
|
|
502
555
|
const next = messagesInView({ starts: current, scrollTop: element.scrollTop, visibleHeight: visibleHeightRef.current });
|
|
556
|
+
|
|
503
557
|
if (next?.first !== shown.first || next?.last !== shown.last) {
|
|
504
558
|
redrawStrip();
|
|
505
559
|
}
|
|
@@ -518,6 +572,7 @@ export function Timeline({
|
|
|
518
572
|
order,
|
|
519
573
|
firstRowInView: () => {
|
|
520
574
|
const top = scrollRef.current?.scrollTop ?? 0;
|
|
575
|
+
|
|
521
576
|
return virtualizer.getVirtualItems().find((virtualRow) => virtualRow.end > top)?.index ?? 0;
|
|
522
577
|
},
|
|
523
578
|
openTurn: turns.add,
|
|
@@ -527,6 +582,7 @@ export function Timeline({
|
|
|
527
582
|
scrollToRow: (index) => {
|
|
528
583
|
const top = scrollRef.current?.scrollTop ?? 0;
|
|
529
584
|
const bottom = top + visibleHeightRef.current;
|
|
585
|
+
|
|
530
586
|
if (!virtualizer.getVirtualItems().some((virtualRow) => virtualRow.index === index && virtualRow.end > top && virtualRow.start < bottom)) {
|
|
531
587
|
virtualizer.scrollToIndex(index, { align: 'center' });
|
|
532
588
|
}
|
|
@@ -536,27 +592,34 @@ export function Timeline({
|
|
|
536
592
|
}),
|
|
537
593
|
[chatId, onMainAgent, rows, items, order, virtualizer, turns.add, groups.add, subagents.add, stopFollowing]
|
|
538
594
|
);
|
|
595
|
+
|
|
539
596
|
const chatFind = useHostTimelineFind(findOptions);
|
|
540
597
|
const searching = chatFind.open;
|
|
598
|
+
|
|
541
599
|
// A find covers the whole thread, so every page before this one comes in while it is open.
|
|
542
600
|
useEffect(() => {
|
|
543
601
|
if (searching) {
|
|
544
602
|
loadEarlier();
|
|
545
603
|
}
|
|
546
604
|
}, [searching, loadEarlier]);
|
|
605
|
+
|
|
547
606
|
const found = useMemo(() => (searching ? ticksWithHits(ticks, chatFind.hitRows) : []), [searching, ticks, chatFind.hitRows]);
|
|
548
607
|
const foundCurrent = chatFind.currentRow === null ? null : tickOfRow(ticks, chatFind.currentRow);
|
|
549
608
|
|
|
550
609
|
const jumpTo = (index: number): void => {
|
|
551
610
|
const tick = ticks[index];
|
|
611
|
+
|
|
552
612
|
if (!tick) {
|
|
553
613
|
return;
|
|
554
614
|
}
|
|
615
|
+
|
|
555
616
|
if (tick.bookmark !== null) {
|
|
556
617
|
setLanding(null);
|
|
557
618
|
setSeeking({ itemId: tick.bookmark.itemId, key: keyOf(chatId) });
|
|
619
|
+
|
|
558
620
|
return;
|
|
559
621
|
}
|
|
622
|
+
|
|
560
623
|
showRowAtTop(virtualizer, followRef, tick.rowIndex);
|
|
561
624
|
};
|
|
562
625
|
|
|
@@ -564,65 +627,89 @@ export function Timeline({
|
|
|
564
627
|
if (seeking === null) {
|
|
565
628
|
return;
|
|
566
629
|
}
|
|
630
|
+
|
|
567
631
|
if (seeking.key !== keyOf(chatId)) {
|
|
568
632
|
setSeeking(null);
|
|
633
|
+
|
|
569
634
|
return;
|
|
570
635
|
}
|
|
636
|
+
|
|
571
637
|
if (loadingHistory) {
|
|
572
638
|
return;
|
|
573
639
|
}
|
|
640
|
+
|
|
574
641
|
if (seeking.block) {
|
|
575
642
|
const jump = uiReplyJump(seeking.block, rows, items, turns.ids, cursor !== null);
|
|
643
|
+
|
|
576
644
|
if (jump.kind === 'earlier') {
|
|
577
645
|
loadEarlier();
|
|
578
646
|
} else if (jump.kind === 'turn') {
|
|
579
647
|
turns.add(jump.turnId);
|
|
580
648
|
} else {
|
|
581
649
|
setSeeking(null);
|
|
650
|
+
|
|
582
651
|
if (jump.kind === 'row') {
|
|
583
652
|
showRowAtTop(virtualizer, followRef, jump.index);
|
|
584
653
|
setLanding({ target: seeking.block, key: seeking.key });
|
|
585
654
|
}
|
|
586
655
|
}
|
|
656
|
+
|
|
587
657
|
return;
|
|
588
658
|
}
|
|
659
|
+
|
|
589
660
|
const index = rows.findIndex((row) => row.id === seeking.itemId);
|
|
661
|
+
|
|
590
662
|
if (index !== -1) {
|
|
591
663
|
setSeeking(null);
|
|
592
664
|
showRowAtTop(virtualizer, followRef, index);
|
|
665
|
+
|
|
593
666
|
if (seeking.options?.flash) {
|
|
594
667
|
useTimelineFlash.getState().flash(seeking.key, seeking.itemId, false);
|
|
595
668
|
}
|
|
669
|
+
|
|
596
670
|
return;
|
|
597
671
|
}
|
|
672
|
+
|
|
598
673
|
// The thread has not loaded yet, so nothing says the item is missing; the effect below bounds the wait.
|
|
599
674
|
if (items === undefined) {
|
|
600
675
|
return;
|
|
601
676
|
}
|
|
677
|
+
|
|
602
678
|
const item = items[seeking.itemId];
|
|
679
|
+
|
|
603
680
|
if (item === undefined && cursor !== null) {
|
|
604
681
|
loadEarlier();
|
|
682
|
+
|
|
605
683
|
return;
|
|
606
684
|
}
|
|
685
|
+
|
|
607
686
|
const turnId = item?.turnId ?? null;
|
|
687
|
+
|
|
608
688
|
if (turnId !== null && !turns.ids.has(turnId)) {
|
|
609
689
|
turns.add(turnId);
|
|
690
|
+
|
|
610
691
|
return;
|
|
611
692
|
}
|
|
693
|
+
|
|
612
694
|
setSeeking(null);
|
|
613
695
|
seeking.options?.onMissing?.();
|
|
614
696
|
}, [seeking, rows, items, turns, virtualizer, cursor, loadEarlier, keyOf, chatId, loadingHistory]);
|
|
697
|
+
|
|
615
698
|
const waitingForThread = seeking !== null && (items === undefined || loadingHistory);
|
|
699
|
+
|
|
616
700
|
useEffect(() => {
|
|
617
701
|
if (!waitingForThread) {
|
|
618
702
|
return;
|
|
619
703
|
}
|
|
704
|
+
|
|
620
705
|
const timer = window.setTimeout(() => {
|
|
621
706
|
setSeeking(null);
|
|
622
707
|
seeking?.options?.onMissing?.();
|
|
623
708
|
}, JUMP_THREAD_WAIT_MS);
|
|
709
|
+
|
|
624
710
|
return () => window.clearTimeout(timer);
|
|
625
711
|
}, [waitingForThread, seeking]);
|
|
712
|
+
|
|
626
713
|
useEffect(
|
|
627
714
|
() =>
|
|
628
715
|
registerItemJumper(keyOf(chatId), (itemId, options) => {
|
|
@@ -631,14 +718,18 @@ export function Timeline({
|
|
|
631
718
|
}),
|
|
632
719
|
[keyOf, chatId]
|
|
633
720
|
);
|
|
721
|
+
|
|
634
722
|
useLayoutEffect(() => {
|
|
635
723
|
if (!landing || landing.key !== keyOf(chatId)) {
|
|
636
724
|
return;
|
|
637
725
|
}
|
|
726
|
+
|
|
638
727
|
let frame = 0;
|
|
639
728
|
let attempts = 0;
|
|
729
|
+
|
|
640
730
|
const reveal = () => {
|
|
641
731
|
const scroller = scrollRef.current;
|
|
732
|
+
|
|
642
733
|
if (scroller && revealUiReplyBlock(scroller, landing.target)) {
|
|
643
734
|
useTimelineFlash.getState().flash(keyOf(chatId), landing.target.itemId, false, landing.target);
|
|
644
735
|
setLanding(null);
|
|
@@ -649,7 +740,9 @@ export function Timeline({
|
|
|
649
740
|
setLanding(null);
|
|
650
741
|
}
|
|
651
742
|
};
|
|
743
|
+
|
|
652
744
|
frame = requestAnimationFrame(reveal);
|
|
745
|
+
|
|
653
746
|
return () => cancelAnimationFrame(frame);
|
|
654
747
|
}, [landing, keyOf, chatId]);
|
|
655
748
|
|
|
@@ -657,28 +750,36 @@ export function Timeline({
|
|
|
657
750
|
if (revealing === null) {
|
|
658
751
|
return;
|
|
659
752
|
}
|
|
753
|
+
|
|
660
754
|
const { turnId, target } = revealing;
|
|
661
755
|
const changes = target === 'changes' ? rows.findIndex((row) => row.kind === 'changed-files' && row.turnId === turnId) : -1;
|
|
662
756
|
const opening = order?.map((id) => items?.[id]).find((item) => item?.kind === 'user' && item.turnId === turnId);
|
|
663
757
|
const found = changes !== -1 ? changes : rows.findIndex((row) => row.id === opening?.id || row.id === `start-${turnId}` || row.id === `fold-${turnId}`);
|
|
758
|
+
|
|
664
759
|
if (found !== -1) {
|
|
665
760
|
setRevealing(null);
|
|
666
761
|
showRowAtTop(virtualizer, followRef, found);
|
|
667
762
|
useTimelineFlash.getState().flash(keyOf(chatId), rows[found]!.id, changes !== -1);
|
|
763
|
+
|
|
668
764
|
return;
|
|
669
765
|
}
|
|
766
|
+
|
|
670
767
|
// The turn is before the page this thread holds; a thread that holds it all has no such turn.
|
|
671
768
|
if (cursor === null) {
|
|
672
769
|
setRevealing(null);
|
|
770
|
+
|
|
673
771
|
return;
|
|
674
772
|
}
|
|
773
|
+
|
|
675
774
|
loadEarlier();
|
|
676
775
|
}, [revealing, rows, order, items, virtualizer, cursor, loadEarlier, keyOf, chatId]);
|
|
776
|
+
|
|
677
777
|
useEffect(() => registerTurnJumper(keyOf(chatId), (turnId, target) => setRevealing({ turnId, target })), [keyOf, chatId]);
|
|
678
778
|
// The strip is memoized and draws on every scroll, so it gets one callback for its life.
|
|
679
779
|
const jumpRef = useRef(jumpTo);
|
|
680
780
|
jumpRef.current = jumpTo;
|
|
681
781
|
const pick = useCallback((index: number) => jumpRef.current(index), []);
|
|
782
|
+
|
|
682
783
|
// Read when the card asks rather than on every render, so the strip's memo holds while the thread streams.
|
|
683
784
|
const cardChat = useMemo<CardChat>(
|
|
684
785
|
() => ({
|
|
@@ -687,7 +788,9 @@ export function Timeline({
|
|
|
687
788
|
if (fork === null) {
|
|
688
789
|
return false;
|
|
689
790
|
}
|
|
791
|
+
|
|
690
792
|
const row = useChats.getState().byKey[keyOf(chatId)];
|
|
793
|
+
|
|
691
794
|
return forkRefusal(row?.info ?? null, row?.structure[turnId]) === null;
|
|
692
795
|
},
|
|
693
796
|
fork: (turnId) => fork?.(chatId, turnId)
|
|
@@ -696,40 +799,51 @@ export function Timeline({
|
|
|
696
799
|
);
|
|
697
800
|
|
|
698
801
|
const stepRef = useRef<(direction: -1 | 1) => boolean>(() => false);
|
|
802
|
+
|
|
699
803
|
stepRef.current = (direction) => {
|
|
700
804
|
if (!onMainAgent) {
|
|
701
805
|
return false;
|
|
702
806
|
}
|
|
807
|
+
|
|
703
808
|
const index = stepMessage(starts, (i) => ticks[i]!.kind === 'person', scrollTop, direction);
|
|
809
|
+
|
|
704
810
|
if (index === null) {
|
|
705
811
|
return false;
|
|
706
812
|
}
|
|
813
|
+
|
|
707
814
|
jumpTo(index);
|
|
815
|
+
|
|
708
816
|
return true;
|
|
709
817
|
};
|
|
818
|
+
|
|
710
819
|
useEffect(() => registerMessageStepper(keyOf(chatId), (direction) => stepRef.current(direction)), [keyOf, chatId]);
|
|
711
820
|
|
|
712
821
|
// Read through a ref, so the rows keep one callback for their life and their memo holds.
|
|
713
822
|
const opensRef = useRef({ rows, show });
|
|
714
823
|
opensRef.current = { rows, show };
|
|
824
|
+
|
|
715
825
|
/* The whole of what a sub-agent did, in the thread's place. Its row only keeps the beginning. */
|
|
716
826
|
const openConversation = useCallback((step: SubagentStep): void => {
|
|
717
827
|
opensRef.current.show(openFromMain(step));
|
|
718
828
|
}, []);
|
|
719
829
|
|
|
720
830
|
const addSubagent = subagents.add;
|
|
831
|
+
|
|
721
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. */
|
|
722
833
|
const openSubagent = useCallback(
|
|
723
834
|
(toolUseId: string): void => {
|
|
724
835
|
const found = findSubagentBranch(opensRef.current.rows, toolUseId);
|
|
836
|
+
|
|
725
837
|
if (!found) {
|
|
726
838
|
return;
|
|
727
839
|
}
|
|
840
|
+
|
|
728
841
|
addSubagent(found.branch.id);
|
|
729
842
|
showRowAtTop(virtualizer, followRef, found.index);
|
|
730
843
|
},
|
|
731
844
|
[addSubagent, virtualizer]
|
|
732
845
|
);
|
|
846
|
+
|
|
733
847
|
const flashedRow = flash !== null && flash.key === keyOf(chatId) && !flash.block ? flash.rowId : null;
|
|
734
848
|
|
|
735
849
|
return (
|
|
@@ -758,16 +872,20 @@ export function Timeline({
|
|
|
758
872
|
const previous = scrollGeometry.current;
|
|
759
873
|
const layoutChanged = previous.height !== el.scrollHeight || previous.viewport !== el.clientHeight;
|
|
760
874
|
const atEnd = el.scrollHeight - el.scrollTop - el.clientHeight < FOLLOW_THRESHOLD_PX;
|
|
875
|
+
|
|
761
876
|
if (!layoutChanged && el.scrollTop < previous.top) {
|
|
762
877
|
followRef.current = false;
|
|
763
878
|
} else if (atEnd) {
|
|
764
879
|
followRef.current = true;
|
|
765
880
|
}
|
|
881
|
+
|
|
766
882
|
scrollGeometry.current = { top: el.scrollTop, height: el.scrollHeight, viewport: el.clientHeight };
|
|
767
883
|
setTimelineAtEnd(chatId, followRef.current);
|
|
884
|
+
|
|
768
885
|
if (wantsEarlier(el)) {
|
|
769
886
|
loadEarlier();
|
|
770
887
|
}
|
|
888
|
+
|
|
771
889
|
followStrip(el);
|
|
772
890
|
}}
|
|
773
891
|
>
|
|
@@ -803,6 +921,7 @@ export function Timeline({
|
|
|
803
921
|
<FindRevealContext.Provider value={chatFind.reveal}>
|
|
804
922
|
{virtualRows.map((virtualRow) => {
|
|
805
923
|
const row = rows[virtualRow.index]!;
|
|
924
|
+
|
|
806
925
|
return (
|
|
807
926
|
<ThreadRow
|
|
808
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}
|