@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/quote.ts
CHANGED
|
@@ -10,6 +10,7 @@ export function quoteOf(selected: string): string | null {
|
|
|
10
10
|
.join('\n')
|
|
11
11
|
.replace(/^\n+|\n+$/g, '')
|
|
12
12
|
.replace(/\n{3,}/g, '\n\n');
|
|
13
|
+
|
|
13
14
|
return text.trim() === '' ? null : text;
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -18,10 +19,12 @@ export function withQuote(quote: string, message: string): string {
|
|
|
18
19
|
if (quote === '') {
|
|
19
20
|
return message;
|
|
20
21
|
}
|
|
22
|
+
|
|
21
23
|
const block = quote
|
|
22
24
|
.split('\n')
|
|
23
25
|
.map((line) => (line === '' ? '>' : `> ${line}`))
|
|
24
26
|
.join('\n');
|
|
27
|
+
|
|
25
28
|
return message === '' ? block : `${block}\n\n${message}`;
|
|
26
29
|
}
|
|
27
30
|
|
|
@@ -32,15 +35,19 @@ export function withQuote(quote: string, message: string): string {
|
|
|
32
35
|
export function splitQuote(text: string): { quote: string; rest: string } | null {
|
|
33
36
|
const lines = text.split('\n');
|
|
34
37
|
let end = 0;
|
|
38
|
+
|
|
35
39
|
while (end < lines.length && (lines[end] === '>' || lines[end]!.startsWith('> '))) {
|
|
36
40
|
end += 1;
|
|
37
41
|
}
|
|
42
|
+
|
|
38
43
|
if (end === 0 || (end < lines.length && lines[end] !== '')) {
|
|
39
44
|
return null;
|
|
40
45
|
}
|
|
46
|
+
|
|
41
47
|
const quote = lines
|
|
42
48
|
.slice(0, end)
|
|
43
49
|
.map((line) => line.slice(2))
|
|
44
50
|
.join('\n');
|
|
51
|
+
|
|
45
52
|
return { quote, rest: lines.slice(end + 1).join('\n') };
|
|
46
53
|
}
|
|
@@ -31,6 +31,7 @@ const WIDE_MEASURE = 1200;
|
|
|
31
31
|
export function readingWidthStyle(width: ChatReadingWidth): CSSProperties {
|
|
32
32
|
const measure = MEASURES[width];
|
|
33
33
|
const bounded = Number.isFinite(measure);
|
|
34
|
+
|
|
34
35
|
return {
|
|
35
36
|
'--chat-measure': bounded ? `${measure}px` : '100%',
|
|
36
37
|
'--chat-wide-measure': bounded ? `${WIDE_MEASURE}px` : '100%'
|
|
@@ -30,18 +30,23 @@ export function recentMessages(eligible: readonly MessageItem[], limit: number):
|
|
|
30
30
|
const messages: RecentChatMessage[] = [];
|
|
31
31
|
let characters = 0;
|
|
32
32
|
let truncated = candidates.length < eligible.length;
|
|
33
|
+
|
|
33
34
|
for (const item of candidates.toReversed()) {
|
|
34
35
|
const message = clipped(item.text.trim());
|
|
35
36
|
const remaining = MAX_TOTAL_CHARACTERS - characters;
|
|
37
|
+
|
|
36
38
|
if (remaining <= 0) {
|
|
37
39
|
truncated = true;
|
|
40
|
+
|
|
38
41
|
break;
|
|
39
42
|
}
|
|
43
|
+
|
|
40
44
|
const text = message.text.length <= remaining ? message.text : `${message.text.slice(0, Math.max(0, remaining - 1))}…`;
|
|
41
45
|
messages.unshift({ role: item.kind, text, createdAt: item.createdAt });
|
|
42
46
|
characters += text.length;
|
|
43
47
|
truncated ||= message.truncated || text.length < message.text.length;
|
|
44
48
|
}
|
|
49
|
+
|
|
45
50
|
return { messages, truncated };
|
|
46
51
|
}
|
|
47
52
|
|
|
@@ -9,6 +9,7 @@ const useDismissals = create<{ byKey: Record<string, string> }>(() => ({ byKey:
|
|
|
9
9
|
|
|
10
10
|
export function useResumeCompactionDismissal(chatId: string): string | null {
|
|
11
11
|
const { keyOf } = useChatScope();
|
|
12
|
+
|
|
12
13
|
return useDismissals((s) => s.byKey[keyOf(chatId)] ?? null);
|
|
13
14
|
}
|
|
14
15
|
|
package/src/chat/stash.ts
CHANGED
|
@@ -50,6 +50,7 @@ function attachments(value: unknown): StashedAttachment[] {
|
|
|
50
50
|
if (!Array.isArray(value)) {
|
|
51
51
|
return [];
|
|
52
52
|
}
|
|
53
|
+
|
|
53
54
|
return value.filter(isRecord).map((entry) => ({
|
|
54
55
|
name: typeof entry.name === 'string' ? entry.name : '',
|
|
55
56
|
mime: typeof entry.mime === 'string' ? entry.mime : '',
|
|
@@ -61,11 +62,13 @@ function stashedQuote(value: unknown): StashedQuote | undefined {
|
|
|
61
62
|
if (!isRecord(value) || typeof value.text !== 'string' || typeof value.chatId !== 'string' || typeof value.itemId !== 'string') {
|
|
62
63
|
return undefined;
|
|
63
64
|
}
|
|
65
|
+
|
|
64
66
|
return { text: value.text, chatId: value.chatId, itemId: value.itemId };
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
function stashedQuoteField(value: unknown): { quote?: StashedQuote } {
|
|
68
70
|
const quote = stashedQuote(value);
|
|
71
|
+
|
|
69
72
|
return quote === undefined ? {} : { quote };
|
|
70
73
|
}
|
|
71
74
|
|
|
@@ -74,11 +77,14 @@ export function parseStash(raw: string | null): StashedPrompt[] {
|
|
|
74
77
|
if (raw === null) {
|
|
75
78
|
return [];
|
|
76
79
|
}
|
|
80
|
+
|
|
77
81
|
try {
|
|
78
82
|
const stored: unknown = JSON.parse(raw);
|
|
83
|
+
|
|
79
84
|
if (!Array.isArray(stored)) {
|
|
80
85
|
return [];
|
|
81
86
|
}
|
|
87
|
+
|
|
82
88
|
return stored
|
|
83
89
|
.filter(isRecord)
|
|
84
90
|
.filter((entry) => typeof entry.id === 'string' && typeof entry.text === 'string')
|
|
@@ -112,7 +118,9 @@ export function stashedFrom(draft: ChatDraft, id: string, now: number, chatId: s
|
|
|
112
118
|
if (isEmptyDraft(draft)) {
|
|
113
119
|
return null;
|
|
114
120
|
}
|
|
121
|
+
|
|
115
122
|
const kept = draft.quote !== '' && draft.quoteSource !== null;
|
|
123
|
+
|
|
116
124
|
return {
|
|
117
125
|
id,
|
|
118
126
|
text: kept ? draft.text : withQuote(draft.quote, draft.text),
|
|
@@ -136,6 +144,7 @@ export const STASH_CHATS_LIMIT = 16;
|
|
|
136
144
|
export function restorableChats(stashed: readonly string[], referable: ReadonlySet<string>, chatId: string): { chats: string[]; dropped: number } {
|
|
137
145
|
const others = stashed.filter((id) => id !== chatId);
|
|
138
146
|
const chats = others.filter((id) => referable.has(id));
|
|
147
|
+
|
|
139
148
|
return { chats: chats.slice(0, STASH_CHATS_LIMIT), dropped: others.length - chats.length };
|
|
140
149
|
}
|
|
141
150
|
|
|
@@ -155,10 +164,13 @@ function write(prompts: StashedPrompt[]): void {
|
|
|
155
164
|
/* Puts a draft on the shelf; false when the composer held nothing to put there. */
|
|
156
165
|
export function stashDraft(draft: ChatDraft, chatId: string): boolean {
|
|
157
166
|
const entry = stashedFrom(draft, `stash-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, Date.now(), chatId);
|
|
167
|
+
|
|
158
168
|
if (!entry) {
|
|
159
169
|
return false;
|
|
160
170
|
}
|
|
171
|
+
|
|
161
172
|
write(withStashed(useStash.getState().prompts, entry));
|
|
173
|
+
|
|
162
174
|
return true;
|
|
163
175
|
}
|
|
164
176
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import i18next from 'i18next';
|
|
2
2
|
import type { ChatApprovalItem, ChatItem, ChatQuestionItem, ChatSubagentResult } from '@adecore/agent-contracts';
|
|
3
|
+
import { isWaiting } from '../state/chats';
|
|
3
4
|
import { errorCode, isConnectionError, type ChatTransport } from '../transport';
|
|
4
5
|
|
|
5
6
|
// What the panel opens on: the newest end of the conversation, which is where an agent that works is.
|
|
@@ -46,26 +47,28 @@ export function mergeNewest(
|
|
|
46
47
|
pageCursor: string | null
|
|
47
48
|
): { items: ChatItem[]; cursor: string | null } {
|
|
48
49
|
const known = new Map(current.map((item, index) => [item.id, index]));
|
|
50
|
+
|
|
49
51
|
if (current.length > 0 && page.length > 0 && !page.some((item) => known.has(item.id))) {
|
|
50
52
|
return { items: page, cursor: pageCursor };
|
|
51
53
|
}
|
|
54
|
+
|
|
52
55
|
const items = [...current];
|
|
56
|
+
|
|
53
57
|
for (const item of page) {
|
|
54
58
|
const index = known.get(item.id);
|
|
59
|
+
|
|
55
60
|
if (index === undefined) {
|
|
56
61
|
items.push(item);
|
|
57
62
|
} else {
|
|
58
63
|
items[index] = item;
|
|
59
64
|
}
|
|
60
65
|
}
|
|
66
|
+
|
|
61
67
|
return { items, cursor: current.length === 0 ? pageCursor : currentCursor };
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
function pendingOf(items: readonly ChatItem[]): Array<ChatApprovalItem | ChatQuestionItem> {
|
|
65
|
-
return items.filter(
|
|
66
|
-
(item): item is ChatApprovalItem | ChatQuestionItem =>
|
|
67
|
-
(item.kind === 'approval' && item.decision === 'pending') || (item.kind === 'question' && item.state === 'pending')
|
|
68
|
-
);
|
|
71
|
+
return items.filter(isWaiting);
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
function messageOf(error: unknown): string {
|
|
@@ -115,6 +118,7 @@ export class SubagentConversation {
|
|
|
115
118
|
}
|
|
116
119
|
})
|
|
117
120
|
);
|
|
121
|
+
|
|
118
122
|
return this.enqueue(() => this.readNewest(true));
|
|
119
123
|
}
|
|
120
124
|
|
|
@@ -123,9 +127,12 @@ export class SubagentConversation {
|
|
|
123
127
|
if (this.refreshWaiting) {
|
|
124
128
|
return this.queue;
|
|
125
129
|
}
|
|
130
|
+
|
|
126
131
|
this.refreshWaiting = true;
|
|
132
|
+
|
|
127
133
|
return this.enqueue(() => {
|
|
128
134
|
this.refreshWaiting = false;
|
|
135
|
+
|
|
129
136
|
return this.readNewest(false);
|
|
130
137
|
});
|
|
131
138
|
}
|
|
@@ -134,10 +141,13 @@ export class SubagentConversation {
|
|
|
134
141
|
// A page on its way answers every call made meanwhile; a scroll asks for one on every frame.
|
|
135
142
|
this.earlier ??= this.enqueue(async () => {
|
|
136
143
|
const cursor = this.state.cursor;
|
|
144
|
+
|
|
137
145
|
if (cursor === null) {
|
|
138
146
|
return;
|
|
139
147
|
}
|
|
148
|
+
|
|
140
149
|
this.set({ loadingEarlier: true });
|
|
150
|
+
|
|
141
151
|
try {
|
|
142
152
|
const page = await this.ask({ cursor, limit: SUBAGENT_PAGE });
|
|
143
153
|
const items = [...page.items, ...this.state.items];
|
|
@@ -147,13 +157,16 @@ export class SubagentConversation {
|
|
|
147
157
|
// The record was written again under this panel; what it holds now is the only truth.
|
|
148
158
|
this.set({ items: [], cursor: null, loadingEarlier: false });
|
|
149
159
|
await this.readNewest(false);
|
|
160
|
+
|
|
150
161
|
return;
|
|
151
162
|
}
|
|
163
|
+
|
|
152
164
|
this.set({ loadingEarlier: false, error: messageOf(error) });
|
|
153
165
|
}
|
|
154
166
|
}).finally(() => {
|
|
155
167
|
this.earlier = null;
|
|
156
168
|
});
|
|
169
|
+
|
|
157
170
|
return this.earlier;
|
|
158
171
|
}
|
|
159
172
|
|
|
@@ -161,10 +174,13 @@ export class SubagentConversation {
|
|
|
161
174
|
if (this.disposed) {
|
|
162
175
|
return;
|
|
163
176
|
}
|
|
177
|
+
|
|
164
178
|
this.disposed = true;
|
|
179
|
+
|
|
165
180
|
for (const off of this.unsubscribe.splice(0)) {
|
|
166
181
|
off();
|
|
167
182
|
}
|
|
183
|
+
|
|
168
184
|
if (this.transport.status === 'open') {
|
|
169
185
|
void this.transport.request('chat.subagent', { chatId: this.chatId, toolUseId: this.toolUseId, limit: 1, watch: false }).catch(() => undefined);
|
|
170
186
|
}
|
|
@@ -174,6 +190,7 @@ export class SubagentConversation {
|
|
|
174
190
|
try {
|
|
175
191
|
const page = await this.ask({ limit: SUBAGENT_PAGE, ...(watch ? { watch: true } : {}) });
|
|
176
192
|
const merged = mergeNewest(this.state.items, this.state.cursor, page.items, page.history.cursor);
|
|
193
|
+
|
|
177
194
|
this.set({
|
|
178
195
|
status: 'ready',
|
|
179
196
|
items: merged.items,
|
|
@@ -189,6 +206,7 @@ export class SubagentConversation {
|
|
|
189
206
|
// What is on screen stays; the socket that comes back asks again.
|
|
190
207
|
return;
|
|
191
208
|
}
|
|
209
|
+
|
|
192
210
|
const unsupported = errorCode(error) === 'unknown-request';
|
|
193
211
|
this.set({ status: 'failed', error: messageOf(error), unsupported });
|
|
194
212
|
}
|
|
@@ -201,6 +219,7 @@ export class SubagentConversation {
|
|
|
201
219
|
private enqueue(work: () => Promise<void>): Promise<void> {
|
|
202
220
|
const next = this.queue.then(() => (this.disposed ? undefined : work()));
|
|
203
221
|
this.queue = next.catch(() => undefined);
|
|
222
|
+
|
|
204
223
|
return next;
|
|
205
224
|
}
|
|
206
225
|
|
|
@@ -208,6 +227,7 @@ export class SubagentConversation {
|
|
|
208
227
|
if (this.disposed) {
|
|
209
228
|
return;
|
|
210
229
|
}
|
|
230
|
+
|
|
211
231
|
this.state = { ...this.state, ...patch };
|
|
212
232
|
this.onChange(this.state);
|
|
213
233
|
}
|
|
@@ -31,9 +31,11 @@ export function statusWordOf(item: ChatSubagentItem, task: SubagentTask | null):
|
|
|
31
31
|
if (item.status === 'failed' && task?.status === 'cancelled') {
|
|
32
32
|
return 'cancelled';
|
|
33
33
|
}
|
|
34
|
+
|
|
34
35
|
if (item.status === 'running' && task?.status === 'open' && task.paused !== undefined) {
|
|
35
36
|
return 'paused';
|
|
36
37
|
}
|
|
38
|
+
|
|
37
39
|
return item.status;
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -42,9 +44,11 @@ export type TaskRowState = StatusWord | 'waiting';
|
|
|
42
44
|
|
|
43
45
|
export function taskRowStateOf(item: ChatSubagentItem, task: SubagentTask | null, child: ChatInfo | undefined): TaskRowState {
|
|
44
46
|
const word = statusWordOf(item, task);
|
|
47
|
+
|
|
45
48
|
if (word === 'running' && (child?.status === 'needs-you' || (child?.requests?.length ?? 0) > 0)) {
|
|
46
49
|
return 'waiting';
|
|
47
50
|
}
|
|
51
|
+
|
|
48
52
|
return word;
|
|
49
53
|
}
|
|
50
54
|
|
|
@@ -63,6 +67,7 @@ export function flyoutSubagents(order: readonly string[], structure: Readonly<Re
|
|
|
63
67
|
return activeSubagents(
|
|
64
68
|
order.flatMap((id) => {
|
|
65
69
|
const item = structure[id];
|
|
70
|
+
|
|
66
71
|
return item === undefined ? [] : [item];
|
|
67
72
|
})
|
|
68
73
|
);
|
|
@@ -76,6 +81,7 @@ export function summaryWordOf(words: readonly StatusWord[]): StatusWord {
|
|
|
76
81
|
/* The number on the badge: the ones still at work, a paused one included, and all of them once none is. */
|
|
77
82
|
export function badgeCountOf(words: readonly StatusWord[]): number {
|
|
78
83
|
const active = words.filter((word) => word === 'running' || word === 'paused').length;
|
|
84
|
+
|
|
79
85
|
return active > 0 ? active : words.length;
|
|
80
86
|
}
|
|
81
87
|
|
|
@@ -85,18 +91,23 @@ export function entryTimeOf(item: ChatSubagentItem, task: SubagentTask | null, n
|
|
|
85
91
|
const startedAt = task?.createdAt ?? item.startedAt;
|
|
86
92
|
const finishedAt = task === null ? item.finishedAt : (task.settledAt ?? item.finishedAt);
|
|
87
93
|
const word = statusWordOf(item, task);
|
|
94
|
+
|
|
88
95
|
if (word === 'paused') {
|
|
89
96
|
const until = task?.paused?.until;
|
|
97
|
+
|
|
90
98
|
return until === undefined
|
|
91
99
|
? i18next.t('agent-chat:common.status.paused')
|
|
92
100
|
: i18next.t('agent-chat:activity.pausedUntil', { time: formatMoment(until, now) });
|
|
93
101
|
}
|
|
102
|
+
|
|
94
103
|
if (item.status === 'running') {
|
|
95
104
|
return startedAt > 0 ? formatElapsedShort(now - startedAt) : '';
|
|
96
105
|
}
|
|
106
|
+
|
|
97
107
|
if (startedAt <= 0 || finishedAt === null || finishedAt < startedAt) {
|
|
98
108
|
return i18next.t(`agent-chat:common.status.${word}`);
|
|
99
109
|
}
|
|
110
|
+
|
|
100
111
|
return i18next.t(`agent-chat:activity.took.${word}`, { duration: formatElapsedShort(finishedAt - startedAt) });
|
|
101
112
|
}
|
|
102
113
|
|
|
@@ -112,12 +123,15 @@ export function stopOf(item: ChatSubagentItem, turnRunning: boolean): SubagentSt
|
|
|
112
123
|
if (item.status !== 'running') {
|
|
113
124
|
return null;
|
|
114
125
|
}
|
|
126
|
+
|
|
115
127
|
if (item.origin === 'ruimte') {
|
|
116
128
|
return item.childId === undefined ? null : 'task';
|
|
117
129
|
}
|
|
130
|
+
|
|
118
131
|
if (item.native?.agentId) {
|
|
119
132
|
return 'native';
|
|
120
133
|
}
|
|
134
|
+
|
|
121
135
|
return turnRunning ? null : 'mark';
|
|
122
136
|
}
|
|
123
137
|
|
|
@@ -77,6 +77,7 @@ export function subagentOf(structure: Readonly<Record<string, ChatItem>>, toolUs
|
|
|
77
77
|
export function openableSubagents(order: readonly string[], structure: Record<string, ChatItem>, machineRefused: boolean): ChatSubagentItem[] {
|
|
78
78
|
return order.flatMap((id) => {
|
|
79
79
|
const item = structure[id];
|
|
80
|
+
|
|
80
81
|
return item?.kind === 'subagent' && canOpenSubagent(item, machineRefused) ? [item] : [];
|
|
81
82
|
});
|
|
82
83
|
}
|
|
@@ -97,19 +98,24 @@ export const useSubagentView = create<SubagentViewStore>((set, get) => ({
|
|
|
97
98
|
trails: {},
|
|
98
99
|
show(key, trail) {
|
|
99
100
|
const trails = { ...get().trails };
|
|
101
|
+
|
|
100
102
|
if (trail.length === 0) {
|
|
101
103
|
delete trails[key];
|
|
102
104
|
} else {
|
|
103
105
|
trails[key] = trail;
|
|
104
106
|
}
|
|
107
|
+
|
|
105
108
|
set({ trails });
|
|
106
109
|
},
|
|
107
110
|
back(key) {
|
|
108
111
|
const trail = get().trails[key];
|
|
112
|
+
|
|
109
113
|
if (trail === undefined || trail.length === 0) {
|
|
110
114
|
return false;
|
|
111
115
|
}
|
|
116
|
+
|
|
112
117
|
get().show(key, stepBack(trail));
|
|
118
|
+
|
|
113
119
|
return true;
|
|
114
120
|
}
|
|
115
121
|
}));
|
|
@@ -118,6 +124,7 @@ export const useSubagentView = create<SubagentViewStore>((set, get) => ({
|
|
|
118
124
|
export function useSubagentTrail(chatId: string): { trail: SubagentTrail; show(trail: SubagentTrail): void } {
|
|
119
125
|
const key = useChatScope().keyOf(chatId);
|
|
120
126
|
const trail = useSubagentView((s) => s.trails[key] ?? MAIN_AGENT);
|
|
127
|
+
|
|
121
128
|
return { trail, show: (next) => useSubagentView.getState().show(key, next) };
|
|
122
129
|
}
|
|
123
130
|
|
|
@@ -127,10 +134,12 @@ export function useOpenableSubagents(chatId: string): ChatSubagentItem[] {
|
|
|
127
134
|
// The structure and not the items, so a delta growing a reply does not walk the thread again.
|
|
128
135
|
const order = useChatRow(chatId, (row) => row?.order);
|
|
129
136
|
const structure = useChatRow(chatId, (row) => row?.structure);
|
|
137
|
+
|
|
130
138
|
return structure === undefined ? [] : openableSubagents(order ?? NO_ITEMS, structure, refused);
|
|
131
139
|
}
|
|
132
140
|
|
|
133
141
|
export function useSubagentItem(chatId: string, toolUseId: string): ChatSubagentItem | null {
|
|
134
142
|
const structure = useChatRow(chatId, (row) => row?.structure);
|
|
143
|
+
|
|
135
144
|
return structure === undefined ? null : subagentOf(structure, toolUseId);
|
|
136
145
|
}
|
package/src/chat/thumbnails.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface ThumbnailCrop {
|
|
|
18
18
|
/* The square in the middle of an image that a thumbnail shows, the same part `object-cover` would. */
|
|
19
19
|
export function thumbnailCrop(width: number, height: number, max: number = THUMBNAIL_PX): ThumbnailCrop {
|
|
20
20
|
const size = Math.max(1, Math.min(width, height));
|
|
21
|
+
|
|
21
22
|
return {
|
|
22
23
|
sx: Math.max(0, Math.floor((width - size) / 2)),
|
|
23
24
|
sy: Math.max(0, Math.floor((height - size) / 2)),
|
|
@@ -29,9 +30,11 @@ export function thumbnailCrop(width: number, height: number, max: number = THUMB
|
|
|
29
30
|
function bytesOf(base64: string): Uint8Array<ArrayBuffer> {
|
|
30
31
|
const binary = atob(base64);
|
|
31
32
|
const bytes = new Uint8Array(new ArrayBuffer(binary.length));
|
|
33
|
+
|
|
32
34
|
for (let i = 0; i < binary.length; i++) {
|
|
33
35
|
bytes[i] = binary.charCodeAt(i);
|
|
34
36
|
}
|
|
37
|
+
|
|
35
38
|
return bytes;
|
|
36
39
|
}
|
|
37
40
|
|
|
@@ -55,12 +58,16 @@ async function makeThumbnail(upload: ChatAttachmentUpload): Promise<string> {
|
|
|
55
58
|
const crop = thumbnailCrop(bitmap.width, bitmap.height);
|
|
56
59
|
const canvas = new OffscreenCanvas(crop.out, crop.out);
|
|
57
60
|
const context = canvas.getContext('2d');
|
|
61
|
+
|
|
58
62
|
if (!context) {
|
|
59
63
|
bitmap.close();
|
|
64
|
+
|
|
60
65
|
return uploadPreviewUrl(upload);
|
|
61
66
|
}
|
|
67
|
+
|
|
62
68
|
context.drawImage(bitmap, crop.sx, crop.sy, crop.size, crop.size, 0, 0, crop.out, crop.out);
|
|
63
69
|
bitmap.close();
|
|
70
|
+
|
|
64
71
|
return await dataUrlOf(await canvas.convertToBlob({ type: 'image/png' }));
|
|
65
72
|
} catch {
|
|
66
73
|
return uploadPreviewUrl(upload);
|
|
@@ -73,13 +80,17 @@ const making = new WeakMap<ChatAttachmentUpload, Promise<string>>();
|
|
|
73
80
|
|
|
74
81
|
function thumbnailOf(upload: ChatAttachmentUpload): Promise<string> {
|
|
75
82
|
let promise = making.get(upload);
|
|
83
|
+
|
|
76
84
|
if (!promise) {
|
|
77
85
|
promise = makeThumbnail(upload).then((url) => {
|
|
78
86
|
made.set(upload, url);
|
|
87
|
+
|
|
79
88
|
return url;
|
|
80
89
|
});
|
|
90
|
+
|
|
81
91
|
making.set(upload, promise);
|
|
82
92
|
}
|
|
93
|
+
|
|
83
94
|
return promise;
|
|
84
95
|
}
|
|
85
96
|
|
|
@@ -92,12 +103,15 @@ export function useUploadThumbnail(upload: ChatAttachmentUpload): string | null
|
|
|
92
103
|
if (made.has(upload)) {
|
|
93
104
|
return;
|
|
94
105
|
}
|
|
106
|
+
|
|
95
107
|
let cancelled = false;
|
|
108
|
+
|
|
96
109
|
void thumbnailOf(upload).then((url) => {
|
|
97
110
|
if (!cancelled) {
|
|
98
111
|
setReady({ upload, url });
|
|
99
112
|
}
|
|
100
113
|
});
|
|
114
|
+
|
|
101
115
|
return () => {
|
|
102
116
|
cancelled = true;
|
|
103
117
|
};
|
|
@@ -25,11 +25,13 @@ export const useTimelineFlash = create<TimelineFlashStore>((set, get) => ({
|
|
|
25
25
|
flash(key, rowId, opens, block) {
|
|
26
26
|
const nonce = (get().target?.nonce ?? 0) + 1;
|
|
27
27
|
set({ target: { key, rowId, opens, nonce, ...(block ? { block } : {}) } });
|
|
28
|
+
|
|
28
29
|
const timer = setTimeout(() => {
|
|
29
30
|
if (get().target?.nonce === nonce) {
|
|
30
31
|
set({ target: null });
|
|
31
32
|
}
|
|
32
33
|
}, FLASH_MS);
|
|
34
|
+
|
|
33
35
|
(timer as { unref?: () => void }).unref?.();
|
|
34
36
|
}
|
|
35
37
|
}));
|
|
@@ -27,7 +27,9 @@ export function registerTimeline(
|
|
|
27
27
|
if (element === null) {
|
|
28
28
|
return () => undefined;
|
|
29
29
|
}
|
|
30
|
+
|
|
30
31
|
scrollers.set(chatId, { element, follow, coveredHeight });
|
|
32
|
+
|
|
31
33
|
return () => {
|
|
32
34
|
if (scrollers.get(chatId)?.element === element) {
|
|
33
35
|
scrollers.delete(chatId);
|
|
@@ -40,7 +42,9 @@ export function setTimelineAtEnd(chatId: string, atEnd: boolean): void {
|
|
|
40
42
|
if (ends.get(chatId) === atEnd) {
|
|
41
43
|
return;
|
|
42
44
|
}
|
|
45
|
+
|
|
43
46
|
ends.set(chatId, atEnd);
|
|
47
|
+
|
|
44
48
|
for (const listener of listeners) {
|
|
45
49
|
listener();
|
|
46
50
|
}
|
|
@@ -48,6 +52,7 @@ export function setTimelineAtEnd(chatId: string, atEnd: boolean): void {
|
|
|
48
52
|
|
|
49
53
|
export function subscribeTimelineEnd(listener: () => void): () => void {
|
|
50
54
|
listeners.add(listener);
|
|
55
|
+
|
|
51
56
|
return () => {
|
|
52
57
|
listeners.delete(listener);
|
|
53
58
|
};
|
|
@@ -60,6 +65,7 @@ export function timelineAtEnd(chatId: string): boolean {
|
|
|
60
65
|
|
|
61
66
|
export function scrollTimelineToEnd(chatId: string, behavior: ScrollBehavior = 'smooth'): void {
|
|
62
67
|
const scroller = scrollers.get(chatId);
|
|
68
|
+
|
|
63
69
|
if (scroller) {
|
|
64
70
|
scroller.follow(true);
|
|
65
71
|
scroller.element.scrollTo({ top: scroller.element.scrollHeight, behavior });
|
|
@@ -69,12 +75,15 @@ export function scrollTimelineToEnd(chatId: string, behavior: ScrollBehavior = '
|
|
|
69
75
|
/* Pages the thread of this chat up or down; false when it has no timeline on screen. */
|
|
70
76
|
export function pageTimeline(chatId: string, direction: -1 | 1): boolean {
|
|
71
77
|
const scroller = scrollers.get(chatId);
|
|
78
|
+
|
|
72
79
|
if (!scroller) {
|
|
73
80
|
return false;
|
|
74
81
|
}
|
|
82
|
+
|
|
75
83
|
scroller.follow(false);
|
|
76
84
|
const { element } = scroller;
|
|
77
85
|
element.scrollBy({ top: direction * Math.max(0, element.clientHeight - scroller.coveredHeight()) * PAGE_OVERLAP, behavior: 'smooth' });
|
|
86
|
+
|
|
78
87
|
return true;
|
|
79
88
|
}
|
|
80
89
|
|
|
@@ -87,6 +96,7 @@ const steppers = new Map<string, (direction: -1 | 1) => boolean>();
|
|
|
87
96
|
|
|
88
97
|
export function registerMessageStepper(key: string, step: (direction: -1 | 1) => boolean): () => void {
|
|
89
98
|
steppers.set(key, step);
|
|
99
|
+
|
|
90
100
|
return () => {
|
|
91
101
|
if (steppers.get(key) === step) {
|
|
92
102
|
steppers.delete(key);
|
|
@@ -107,17 +117,21 @@ export function stepTimelineMessage(key: string, direction: -1 | 1): boolean {
|
|
|
107
117
|
function waitingJumps<T>(): { register(key: string, jump: (target: T) => void): () => void; jump(key: string, target: T): boolean } {
|
|
108
118
|
const jumpers = new Map<string, (target: T) => void>();
|
|
109
119
|
const waiting = new Map<string, { target: T; at: number }>();
|
|
120
|
+
|
|
110
121
|
return {
|
|
111
122
|
register(key, jump) {
|
|
112
123
|
jumpers.set(key, jump);
|
|
113
124
|
const pending = waiting.get(key);
|
|
125
|
+
|
|
114
126
|
if (pending !== undefined) {
|
|
115
127
|
waiting.delete(key);
|
|
128
|
+
|
|
116
129
|
// A jump nobody picked up for long is not what the person wants any more.
|
|
117
130
|
if (Date.now() - pending.at <= PENDING_JUMP_TTL_MS) {
|
|
118
131
|
jump(pending.target);
|
|
119
132
|
}
|
|
120
133
|
}
|
|
134
|
+
|
|
121
135
|
return () => {
|
|
122
136
|
if (jumpers.get(key) === jump) {
|
|
123
137
|
jumpers.delete(key);
|
|
@@ -126,11 +140,15 @@ function waitingJumps<T>(): { register(key: string, jump: (target: T) => void):
|
|
|
126
140
|
},
|
|
127
141
|
jump(key, target) {
|
|
128
142
|
const jump = jumpers.get(key);
|
|
143
|
+
|
|
129
144
|
if (jump === undefined) {
|
|
130
145
|
waiting.set(key, { target, at: Date.now() });
|
|
146
|
+
|
|
131
147
|
return false;
|
|
132
148
|
}
|
|
149
|
+
|
|
133
150
|
jump(target);
|
|
151
|
+
|
|
134
152
|
return true;
|
|
135
153
|
}
|
|
136
154
|
};
|
|
@@ -159,6 +177,7 @@ export function jumpToTimelineItem(key: string, itemId: string, options: ItemJum
|
|
|
159
177
|
export type TurnTarget = 'prompt' | 'changes';
|
|
160
178
|
|
|
161
179
|
type TurnJump = (turnId: string, target: TurnTarget) => void;
|
|
180
|
+
|
|
162
181
|
const turnJumps = waitingJumps<{ turnId: string; target: TurnTarget }>();
|
|
163
182
|
|
|
164
183
|
export function registerTurnJumper(key: string, jump: TurnJump): () => void {
|
|
@@ -186,18 +205,22 @@ export interface ReadingAnchor {
|
|
|
186
205
|
/* The first row in view, found by the rows' `data-item-id`, for a thread that keeps every row in the document. */
|
|
187
206
|
export function firstRowInView(scroller: HTMLElement): ReadingAnchor | null {
|
|
188
207
|
const top = scroller.getBoundingClientRect().top;
|
|
208
|
+
|
|
189
209
|
for (const row of scroller.querySelectorAll<HTMLElement>('[data-item-id]')) {
|
|
190
210
|
const rect = row.getBoundingClientRect();
|
|
211
|
+
|
|
191
212
|
if (rect.bottom > top) {
|
|
192
213
|
return { id: row.dataset.itemId!, offset: rect.top - top };
|
|
193
214
|
}
|
|
194
215
|
}
|
|
216
|
+
|
|
195
217
|
return null;
|
|
196
218
|
}
|
|
197
219
|
|
|
198
220
|
/* Puts the anchored row back where it was; nothing moves when that row is gone. */
|
|
199
221
|
export function restoreAnchor(scroller: HTMLElement, anchor: ReadingAnchor): void {
|
|
200
222
|
const row = scroller.querySelector<HTMLElement>(`[data-item-id="${CSS.escape(anchor.id)}"]`);
|
|
223
|
+
|
|
201
224
|
if (row !== null) {
|
|
202
225
|
scroller.scrollTop += row.getBoundingClientRect().top - scroller.getBoundingClientRect().top - anchor.offset;
|
|
203
226
|
}
|
|
@@ -9,10 +9,13 @@ import { Pill, Tooltip } from '@adecore/ui';
|
|
|
9
9
|
function AccountPillOf({ provider, account }: { provider: AgentKind; account: string | undefined }) {
|
|
10
10
|
const { t } = useTranslation('agent-chat');
|
|
11
11
|
const choice = useAccountChoice(provider, account);
|
|
12
|
+
|
|
12
13
|
if (choice === null) {
|
|
13
14
|
return null;
|
|
14
15
|
}
|
|
16
|
+
|
|
15
17
|
const name = choice.current === null ? choice.currentId : choice.nameOf(choice.current);
|
|
18
|
+
|
|
16
19
|
return (
|
|
17
20
|
<Tooltip label={t('account.pill', { provider: choice.providerName, account: name })}>
|
|
18
21
|
<Pill icon={<AccountDot color={choice.current?.account.color} className="size-1.75" />}>
|
|
@@ -27,5 +30,6 @@ export function AccountPill({ chatId }: { chatId: string }) {
|
|
|
27
30
|
const { keyOf } = useChatScope();
|
|
28
31
|
const provider = useChats((s) => s.statusByKey[keyOf(chatId)]?.info.provider);
|
|
29
32
|
const account = useChats((s) => s.statusByKey[keyOf(chatId)]?.info.account);
|
|
33
|
+
|
|
30
34
|
return provider === undefined ? null : <AccountPillOf provider={provider} account={account} />;
|
|
31
35
|
}
|
|
@@ -4,6 +4,7 @@ import { parseAnsi } from '../logic/ansi';
|
|
|
4
4
|
export function AnsiOutput({ text, limit, tailLines }: { text: string; limit?: number; tailLines?: number }) {
|
|
5
5
|
const { t } = useTranslation('agent-chat');
|
|
6
6
|
const { tokens, omitted } = parseAnsi(text, { limit, tailLines });
|
|
7
|
+
|
|
7
8
|
return (
|
|
8
9
|
<>
|
|
9
10
|
{tokens.map((token, index) => (
|