@adecore/agents-react 0.39.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.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 +26 -26
- 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/Markdown.tsx
CHANGED
|
@@ -37,12 +37,15 @@ interface MarkdownNode {
|
|
|
37
37
|
/* The command a shell fence holds, read from the fence as written, so it keeps the indentation and line endings it has. */
|
|
38
38
|
function shellFence(source: string): ShellFence | null {
|
|
39
39
|
const opening = /^ {0,3}(`{3,}|~{3,})[ \t]*(sh|bash|zsh)[ \t]*\r?\n/.exec(source);
|
|
40
|
+
|
|
40
41
|
if (opening === null) {
|
|
41
42
|
return null;
|
|
42
43
|
}
|
|
44
|
+
|
|
43
45
|
const body = source.slice(opening[0].length);
|
|
44
46
|
const closing = /(?:^|\r?\n) {0,3}(`{3,}|~{3,})[ \t]*$/.exec(body);
|
|
45
47
|
const closed = closing !== null && closing[1]![0] === opening[1]![0] && closing[1]!.length >= opening[1]!.length;
|
|
48
|
+
|
|
46
49
|
return { language: opening[2] as ShellFence['language'], code: closed ? body.slice(0, closing.index) : body, closed };
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -53,15 +56,20 @@ function remarkShellCodeBlocks() {
|
|
|
53
56
|
if (node.type !== 'code') {
|
|
54
57
|
continue;
|
|
55
58
|
}
|
|
59
|
+
|
|
56
60
|
const start = node.position?.start.offset;
|
|
57
61
|
const end = node.position?.end.offset;
|
|
62
|
+
|
|
58
63
|
if (start === undefined || end === undefined) {
|
|
59
64
|
continue;
|
|
60
65
|
}
|
|
66
|
+
|
|
61
67
|
const shell = shellFence(String(file.value).slice(start, end));
|
|
68
|
+
|
|
62
69
|
if (shell === null) {
|
|
63
70
|
continue;
|
|
64
71
|
}
|
|
72
|
+
|
|
65
73
|
node.data = {
|
|
66
74
|
...node.data,
|
|
67
75
|
hProperties: {
|
|
@@ -80,7 +88,9 @@ function shellCodeBlockActions(reply: ShellCodeBlockReply | null, shell: ShellFe
|
|
|
80
88
|
if (reply === null || shell === null) {
|
|
81
89
|
return undefined;
|
|
82
90
|
}
|
|
91
|
+
|
|
83
92
|
const { scopeId, chatId, itemId } = reply;
|
|
93
|
+
|
|
84
94
|
return render?.({ scopeId, chatId, itemId, language: shell.language, code: shell.code, complete: !reply.streaming && shell.closed });
|
|
85
95
|
}
|
|
86
96
|
|
|
@@ -98,6 +108,7 @@ function FileLink({ target, className, children }: { target: FileRef; className?
|
|
|
98
108
|
const cwd = useFileLinkCwd();
|
|
99
109
|
const scopeId = useFileLinkScopeId();
|
|
100
110
|
const openFileLink = useOpenFileLink();
|
|
111
|
+
|
|
101
112
|
return (
|
|
102
113
|
// A button is not selectable on its own, and a thread is copied as often as it is clicked.
|
|
103
114
|
<button
|
|
@@ -120,9 +131,11 @@ function FileLink({ target, className, children }: { target: FileRef; className?
|
|
|
120
131
|
/* Inline code, which is a file reference often enough that it is worth asking every time. */
|
|
121
132
|
function InlineCode({ text }: { text: string }) {
|
|
122
133
|
const target = useFileLinkTarget(text);
|
|
134
|
+
|
|
123
135
|
if (target === null) {
|
|
124
136
|
return <code className={INLINE_CODE}>{text}</code>;
|
|
125
137
|
}
|
|
138
|
+
|
|
126
139
|
return (
|
|
127
140
|
// The chip sits on a code inside the button, since a button is always a box of its own and its
|
|
128
141
|
// background would fill the whole box once a long path wraps.
|
|
@@ -138,6 +151,7 @@ function InlineCode({ text }: { text: string }) {
|
|
|
138
151
|
*/
|
|
139
152
|
function hrefPath(href: string): string {
|
|
140
153
|
const bare = href.replace(/^file:\/\//, '');
|
|
154
|
+
|
|
141
155
|
try {
|
|
142
156
|
return decodeURI(bare);
|
|
143
157
|
} catch {
|
|
@@ -148,6 +162,7 @@ function hrefPath(href: string): string {
|
|
|
148
162
|
/* A markdown link, which is a file when its target names no scheme of its own. */
|
|
149
163
|
function MarkdownLink({ href, children }: { href?: string; children?: ReactNode }) {
|
|
150
164
|
const target = useFileLinkTarget(hrefPath(href ?? ''));
|
|
165
|
+
|
|
151
166
|
if (target !== null) {
|
|
152
167
|
return (
|
|
153
168
|
<FileLink target={target} className="text-accent underline underline-offset-2">
|
|
@@ -155,6 +170,7 @@ function MarkdownLink({ href, children }: { href?: string; children?: ReactNode
|
|
|
155
170
|
</FileLink>
|
|
156
171
|
);
|
|
157
172
|
}
|
|
173
|
+
|
|
158
174
|
return <ExternalLink href={href}>{children}</ExternalLink>;
|
|
159
175
|
}
|
|
160
176
|
|
|
@@ -174,13 +190,17 @@ function localImagePath(src: string, cwd: string | null): string | null {
|
|
|
174
190
|
if (src === '' || /^[a-z][a-z0-9+.-]*:/i.test(src)) {
|
|
175
191
|
return null;
|
|
176
192
|
}
|
|
193
|
+
|
|
177
194
|
const target = hrefPath(src);
|
|
195
|
+
|
|
178
196
|
if (target.startsWith('/')) {
|
|
179
197
|
return target;
|
|
180
198
|
}
|
|
199
|
+
|
|
181
200
|
if (cwd === null) {
|
|
182
201
|
return null;
|
|
183
202
|
}
|
|
203
|
+
|
|
184
204
|
return `${cwd.replace(/\/+$/, '')}/${target.replace(/^\.\//, '')}`;
|
|
185
205
|
}
|
|
186
206
|
|
|
@@ -189,9 +209,11 @@ function MarkdownImage({ src, alt }: { src?: string; alt?: string }) {
|
|
|
189
209
|
const { MarkdownImage: HostImage } = useChatHost();
|
|
190
210
|
const cwd = useFileLinkCwd();
|
|
191
211
|
const path = typeof src === 'string' ? localImagePath(src, cwd) : null;
|
|
212
|
+
|
|
192
213
|
if (HostImage === undefined || path === null) {
|
|
193
214
|
return <img src={src} alt={alt} />;
|
|
194
215
|
}
|
|
216
|
+
|
|
195
217
|
return <HostImage path={path} alt={alt ?? ''} />;
|
|
196
218
|
}
|
|
197
219
|
|
|
@@ -202,6 +224,7 @@ function PlainImage({ src, alt }: { src?: string; alt?: string }) {
|
|
|
202
224
|
/* A ```mermaid fence, which draws what it says. */
|
|
203
225
|
function Diagram({ code }: { code: string }) {
|
|
204
226
|
const { t } = useTranslation('agent-chat');
|
|
227
|
+
|
|
205
228
|
return (
|
|
206
229
|
<ErrorBoundary label={t('code.mermaid.crashed')} resetKeys={[code]} compact className="rounded-lg border border-border">
|
|
207
230
|
<MermaidBlock code={code} />
|
|
@@ -212,11 +235,14 @@ function Diagram({ code }: { code: string }) {
|
|
|
212
235
|
function MarkdownCode({ className, children, plain = false }: { className?: string; children?: ReactNode; plain?: boolean }) {
|
|
213
236
|
const block = useContext(BlockCodeContext);
|
|
214
237
|
const text = String(children ?? '');
|
|
238
|
+
|
|
215
239
|
if (block) {
|
|
216
240
|
const lang = languageOf(className);
|
|
217
241
|
const code = text.replace(/\n$/, '');
|
|
242
|
+
|
|
218
243
|
return lang === 'mermaid' ? <Diagram code={code} /> : <CodeBlock code={code} lang={lang} />;
|
|
219
244
|
}
|
|
245
|
+
|
|
220
246
|
return plain ? <code className={INLINE_CODE}>{text}</code> : <InlineCode text={text} />;
|
|
221
247
|
}
|
|
222
248
|
|
|
@@ -259,6 +285,7 @@ const REPLY_HEADING_OFFSET = 3;
|
|
|
259
285
|
|
|
260
286
|
function replyHeading(level: 1 | 2 | 3 | 4 | 5 | 6) {
|
|
261
287
|
const Tag = `h${level}` as const;
|
|
288
|
+
|
|
262
289
|
return function ReplyHeading({ children }: { children?: ReactNode }) {
|
|
263
290
|
return <Tag aria-level={Math.min(6, level + REPLY_HEADING_OFFSET)}>{children}</Tag>;
|
|
264
291
|
};
|
|
@@ -283,17 +310,22 @@ function ReplyCode({ className, children, node }: { className?: string; children
|
|
|
283
310
|
const properties = node?.properties;
|
|
284
311
|
const language = properties?.['data-shell-language'];
|
|
285
312
|
const code = properties?.['data-shell-code'];
|
|
313
|
+
|
|
286
314
|
const shell: ShellFence | null =
|
|
287
315
|
(language === 'sh' || language === 'bash' || language === 'zsh') && typeof code === 'string'
|
|
288
316
|
? { language, code, closed: properties?.['data-shell-closed'] === true }
|
|
289
317
|
: null;
|
|
318
|
+
|
|
290
319
|
const text = String(children ?? '');
|
|
320
|
+
|
|
291
321
|
if (block && languageOf(className) === 'mermaid') {
|
|
292
322
|
return <Diagram code={text.replace(/\n$/, '')} />;
|
|
293
323
|
}
|
|
324
|
+
|
|
294
325
|
if (block) {
|
|
295
326
|
return <CodeBlock code={text.replace(/\n$/, '')} lang={languageOf(className)} actions={shellCodeBlockActions(reply, shell, renderShellCodeBlock)} />;
|
|
296
327
|
}
|
|
328
|
+
|
|
297
329
|
return <InlineCode text={text} />;
|
|
298
330
|
}
|
|
299
331
|
|
|
@@ -302,6 +334,7 @@ function Chip({ kind, value }: { kind: string; value: string }) {
|
|
|
302
334
|
if (kind === 'reference') {
|
|
303
335
|
return <ReferenceChip id={value} />;
|
|
304
336
|
}
|
|
337
|
+
|
|
305
338
|
if (kind === 'skill') {
|
|
306
339
|
return (
|
|
307
340
|
<TokenChip
|
|
@@ -314,6 +347,7 @@ function Chip({ kind, value }: { kind: string; value: string }) {
|
|
|
314
347
|
/>
|
|
315
348
|
);
|
|
316
349
|
}
|
|
350
|
+
|
|
317
351
|
return (
|
|
318
352
|
// The path is what the thread's menu opens in the preview from here.
|
|
319
353
|
<TokenChip
|
|
@@ -336,6 +370,7 @@ function ChipSpan({ children, node: _node, 'data-chip': chip, 'data-value': valu
|
|
|
336
370
|
if (chip === undefined || value === undefined) {
|
|
337
371
|
return <span {...rest}>{children}</span>;
|
|
338
372
|
}
|
|
373
|
+
|
|
339
374
|
return <Chip kind={chip} value={value} />;
|
|
340
375
|
}
|
|
341
376
|
|
|
@@ -352,8 +387,10 @@ const ReplyBlock = memo(function ReplyBlock({ text, fade, open, reply }: { text:
|
|
|
352
387
|
const { items, referenced } = useContext(ReferenceLookupContext);
|
|
353
388
|
// Chips first, so the fade wraps the words around a chip and never cuts a name in two.
|
|
354
389
|
const named: NamedReferenceOptions = { items, referenced };
|
|
390
|
+
|
|
355
391
|
const rehypePlugins: Options['rehypePlugins'] =
|
|
356
392
|
items.length === 0 ? (fade ? FADE_PLUGINS : NO_PLUGINS) : [[rehypeNamedReferences, named], ...(fade ? FADE_PLUGINS : [])];
|
|
393
|
+
|
|
357
394
|
return (
|
|
358
395
|
// A context rather than a second set of components. A component that changed would mount the
|
|
359
396
|
// code block again the moment its fence closes, and it would lose the lines it already has.
|
|
@@ -389,6 +426,7 @@ const ReplyFence = memo(function ReplyFence({
|
|
|
389
426
|
// The class markdown gives the code, read back the way a parsed fence's is.
|
|
390
427
|
const lang = languageOf(`language-${info.split(/[ \t]/, 1)[0]}`);
|
|
391
428
|
const shell = reply === undefined ? null : shellFence(source);
|
|
429
|
+
|
|
392
430
|
return (
|
|
393
431
|
<CodeStreamingContext.Provider value={open}>
|
|
394
432
|
{lang === 'mermaid' ? (
|
|
@@ -418,6 +456,7 @@ export const ReplyMarkdown = memo(function ReplyMarkdown({
|
|
|
418
456
|
reply?: ShellCodeBlockReply;
|
|
419
457
|
}) {
|
|
420
458
|
const blocks = splitMarkdownBlocks(text);
|
|
459
|
+
|
|
421
460
|
return (
|
|
422
461
|
<div className="chat-markdown prose prose-sm max-w-none text-sm" data-arriving={arriving || undefined}>
|
|
423
462
|
{blocks.map((block, index) =>
|
|
@@ -455,6 +494,7 @@ export const MessageMarkdown = memo(function MessageMarkdown({
|
|
|
455
494
|
references?: readonly ReferenceToken[];
|
|
456
495
|
}) {
|
|
457
496
|
const rehypePlugins: [typeof rehypeChips, ChipOptions][] = [[rehypeChips, { mentions, skills, references }]];
|
|
497
|
+
|
|
458
498
|
return (
|
|
459
499
|
<div className="chat-markdown prose prose-sm max-w-none text-sm">
|
|
460
500
|
<ReactMarkdown remarkPlugins={MESSAGE_PLUGINS} rehypePlugins={rehypePlugins} components={replyComponents}>
|
|
@@ -484,6 +524,7 @@ export const Markdown = memo(function Markdown({
|
|
|
484
524
|
componentOverrides?: Components;
|
|
485
525
|
}) {
|
|
486
526
|
const renderComponents = { ...(fileLinks ? components : plainComponents), ...componentOverrides };
|
|
527
|
+
|
|
487
528
|
return (
|
|
488
529
|
<div className="chat-markdown prose prose-sm max-w-none text-sm" data-muted={muted || undefined}>
|
|
489
530
|
<ReactMarkdown remarkPlugins={breaks ? PLUGINS_WITH_BREAKS : PLUGINS} rehypePlugins={rehypePlugins} components={renderComponents}>
|
|
@@ -25,14 +25,17 @@ function resolveColor(element: HTMLElement, token: string, ground: string): stri
|
|
|
25
25
|
probeCanvas.height = 1;
|
|
26
26
|
const context = probeCanvas.getContext('2d', { willReadFrequently: true });
|
|
27
27
|
const value = getComputedStyle(element).getPropertyValue(token).trim();
|
|
28
|
+
|
|
28
29
|
if (context === null || value === '') {
|
|
29
30
|
return ground;
|
|
30
31
|
}
|
|
32
|
+
|
|
31
33
|
context.fillStyle = ground;
|
|
32
34
|
context.fillRect(0, 0, 1, 1);
|
|
33
35
|
context.fillStyle = value;
|
|
34
36
|
context.fillRect(0, 0, 1, 1);
|
|
35
37
|
const [red, green, blue] = context.getImageData(0, 0, 1, 1).data;
|
|
38
|
+
|
|
36
39
|
return `#${[red, green, blue].map((channel) => channel!.toString(16).padStart(2, '0')).join('')}`;
|
|
37
40
|
}
|
|
38
41
|
|
|
@@ -45,14 +48,17 @@ interface DiagramLook {
|
|
|
45
48
|
function diagramLook(element: HTMLElement): DiagramLook {
|
|
46
49
|
const style = getComputedStyle(element);
|
|
47
50
|
const ground = resolveColor(element, GROUND_TOKEN, '#808080');
|
|
51
|
+
|
|
48
52
|
const colors = Object.fromEntries(
|
|
49
53
|
Object.entries(DIAGRAM_TOKENS).map(([name, token]) => [name, token === GROUND_TOKEN ? ground : resolveColor(element, token, ground)])
|
|
50
54
|
);
|
|
55
|
+
|
|
51
56
|
const font = {
|
|
52
57
|
family: style.fontFamily,
|
|
53
58
|
mono: style.getPropertyValue('--font-mono').trim() || 'monospace',
|
|
54
59
|
size: Number.parseFloat(style.fontSize) || 14
|
|
55
60
|
};
|
|
61
|
+
|
|
56
62
|
return { colors, font };
|
|
57
63
|
}
|
|
58
64
|
|
|
@@ -64,6 +70,7 @@ function keepingSame(next: DiagramLook) {
|
|
|
64
70
|
async function layOut(id: string, source: string, font: DiagramFont): Promise<LaidOut> {
|
|
65
71
|
const { renderAsync } = await import('pele/lazy');
|
|
66
72
|
const { svg, width, height } = await renderAsync(source, renderOptions(font, id));
|
|
73
|
+
|
|
67
74
|
return { svg, width: Math.ceil(width), height: Math.ceil(height) };
|
|
68
75
|
}
|
|
69
76
|
|
|
@@ -84,6 +91,7 @@ export function MermaidBlock({ code }: { code: string }) {
|
|
|
84
91
|
|
|
85
92
|
useEffect(() => {
|
|
86
93
|
const element = frame.current;
|
|
94
|
+
|
|
87
95
|
if (element !== null) {
|
|
88
96
|
setLook(keepingSame(diagramLook(element)));
|
|
89
97
|
}
|
|
@@ -92,25 +100,31 @@ export function MermaidBlock({ code }: { code: string }) {
|
|
|
92
100
|
// An app may change its colors without turning light or dark, which only the root of the page tells.
|
|
93
101
|
useEffect(() => {
|
|
94
102
|
const element = frame.current;
|
|
103
|
+
|
|
95
104
|
if (element === null) {
|
|
96
105
|
return;
|
|
97
106
|
}
|
|
107
|
+
|
|
98
108
|
return watchTheme(element.ownerDocument, () => setLook(keepingSame(diagramLook(element))));
|
|
99
109
|
}, []);
|
|
100
110
|
|
|
101
111
|
const fontFamily = look?.font.family;
|
|
102
112
|
const fontMono = look?.font.mono;
|
|
103
113
|
const fontSize = look?.font.size;
|
|
114
|
+
|
|
104
115
|
useEffect(() => {
|
|
105
116
|
if (streaming || fontFamily === undefined || fontMono === undefined || fontSize === undefined) {
|
|
106
117
|
return;
|
|
107
118
|
}
|
|
119
|
+
|
|
108
120
|
let cancelled = false;
|
|
121
|
+
|
|
109
122
|
// A diagram already drawn stays until the next one is there, so it does not blink.
|
|
110
123
|
layOut(id, code, { family: fontFamily, mono: fontMono, size: fontSize }).then(
|
|
111
124
|
(image) => !cancelled && setDrawing({ state: 'drawn', image }),
|
|
112
125
|
() => !cancelled && setDrawing({ state: 'failed' })
|
|
113
126
|
);
|
|
127
|
+
|
|
114
128
|
return () => {
|
|
115
129
|
cancelled = true;
|
|
116
130
|
};
|
|
@@ -118,6 +132,7 @@ export function MermaidBlock({ code }: { code: string }) {
|
|
|
118
132
|
|
|
119
133
|
const image = drawing.state === 'drawn' && look !== null ? { ...drawing.image, svg: themedSvg(drawing.image.svg, look.colors) } : null;
|
|
120
134
|
const showsDiagram = !streaming && !source && drawing.state !== 'failed';
|
|
135
|
+
|
|
121
136
|
const toggle =
|
|
122
137
|
streaming || drawing.state === 'failed' ? undefined : (
|
|
123
138
|
<IconButton
|
|
@@ -127,6 +142,7 @@ export function MermaidBlock({ code }: { code: string }) {
|
|
|
127
142
|
onClick={() => setSource((shown) => !shown)}
|
|
128
143
|
/>
|
|
129
144
|
);
|
|
145
|
+
|
|
130
146
|
return (
|
|
131
147
|
// One tree in every state, so the code block keeps the lines it has when the fence closes.
|
|
132
148
|
<div ref={frame}>
|
|
@@ -31,14 +31,19 @@ export function MessageActions({ chatId, row }: { chatId: string; row: MessageRo
|
|
|
31
31
|
const bookmarks = useChatRow(chatId, (chat) => chat?.bookmarks);
|
|
32
32
|
const info = useChatRow(chatId, (chat) => chat?.info ?? null);
|
|
33
33
|
const turn = useChatRow(chatId, (chat) => (turnId === null ? undefined : chat?.structure[turnId]));
|
|
34
|
+
|
|
34
35
|
const streaming = useChatRow(chatId, (chat) => {
|
|
35
36
|
const item = chat?.items[row.id];
|
|
37
|
+
|
|
36
38
|
return item?.kind === 'assistant' && item.streaming;
|
|
37
39
|
});
|
|
40
|
+
|
|
38
41
|
const hasText = useChatRow(chatId, (chat) => {
|
|
39
42
|
const item = chat?.items[row.id];
|
|
43
|
+
|
|
40
44
|
return (item?.kind === 'user' || item?.kind === 'assistant') && /\S/.test(item.text);
|
|
41
45
|
});
|
|
46
|
+
|
|
42
47
|
const bookmarked = bookmarks?.some((candidate) => candidate.itemId === row.id) === true;
|
|
43
48
|
const canFork = turnId !== null && forkRefusal(info, turn) === null;
|
|
44
49
|
const [copied, setCopied] = useState(false);
|
|
@@ -48,7 +53,9 @@ export function MessageActions({ chatId, row }: { chatId: string; row: MessageRo
|
|
|
48
53
|
if (!copied) {
|
|
49
54
|
return;
|
|
50
55
|
}
|
|
56
|
+
|
|
51
57
|
const timer = setTimeout(() => setCopied(false), COPIED_MS);
|
|
58
|
+
|
|
52
59
|
return () => clearTimeout(timer);
|
|
53
60
|
}, [copied]);
|
|
54
61
|
|
|
@@ -57,6 +64,7 @@ export function MessageActions({ chatId, row }: { chatId: string; row: MessageRo
|
|
|
57
64
|
const item = useChats.getState().byKey[scope.keyOf(chatId)]?.items[row.id];
|
|
58
65
|
const current = item?.kind === row.kind ? ({ ...row, item } as MessageRow) : row;
|
|
59
66
|
const text = (plain ? null : markdownOf(current)) ?? messageTextOf(current);
|
|
67
|
+
|
|
60
68
|
if (text !== null) {
|
|
61
69
|
copyWithChips(messageClipboardChips(current.item, text, lookup));
|
|
62
70
|
setCopied(true);
|
|
@@ -6,6 +6,7 @@ import { Icon, Pill, Tooltip } from '@adecore/ui';
|
|
|
6
6
|
/* What a model row says beside the name: its badge, and a mark when the provider says the model can call tools. */
|
|
7
7
|
export function ModelMarks({ model }: { model: ModelInfo }) {
|
|
8
8
|
const { t } = useTranslation('agent-chat');
|
|
9
|
+
|
|
9
10
|
return (
|
|
10
11
|
<>
|
|
11
12
|
{model.badge && (
|
package/src/chat/ui/Pickers.tsx
CHANGED
|
@@ -77,19 +77,26 @@ export function ModelPicker({
|
|
|
77
77
|
const trimmed = query.trim().toLowerCase();
|
|
78
78
|
|
|
79
79
|
const favorites = useChatPreferences((preferences) => preferences.favoriteModels);
|
|
80
|
+
|
|
80
81
|
const starredHere = (row: PickerEntry | undefined): boolean => row?.kind === 'model' && favorites[row.provider.kind]?.includes(row.model.slug) === true;
|
|
82
|
+
|
|
81
83
|
const entries = pickerEntriesOf(inPickerOrder(providers), trimmed, legacyOpen, expanded, { provider, model: selection.model }, (kind) =>
|
|
82
84
|
favoriteModelsOf(favorites, kind)
|
|
83
85
|
);
|
|
86
|
+
|
|
84
87
|
const scope = useContext(ChatScopeContext);
|
|
85
88
|
const machine = useHostMachineLabel(scope?.id ?? '');
|
|
86
89
|
const reported = useReportedModels(provider);
|
|
90
|
+
|
|
87
91
|
const requirementOf = (entry: PickerEntry & { kind: 'model' }) => modelRequirementOf(entry.model, entry.provider, reported);
|
|
92
|
+
|
|
88
93
|
// A CLI that is too old blocks all its models; one way to its settings under the group says it once.
|
|
89
94
|
const kindBefore = (i: number): AgentKind | undefined => {
|
|
90
95
|
const before = entries[i - 1];
|
|
96
|
+
|
|
91
97
|
return before === undefined || before.kind === 'legacy' ? undefined : before.provider.kind;
|
|
92
98
|
};
|
|
99
|
+
|
|
93
100
|
const blockedKinds = new Set(entries.flatMap((entry) => (entry.kind === 'model' && requirementOf(entry) !== null ? [entry.provider.kind] : [])));
|
|
94
101
|
|
|
95
102
|
/* Closing forgets the search, so the next open (a click or `/model`) starts on the whole list. */
|
|
@@ -99,6 +106,7 @@ export function ModelPicker({
|
|
|
99
106
|
setLegacyOpen(false);
|
|
100
107
|
setExpanded(new Set());
|
|
101
108
|
}
|
|
109
|
+
|
|
102
110
|
setIndex(0);
|
|
103
111
|
onOpenChange(next);
|
|
104
112
|
};
|
|
@@ -112,26 +120,36 @@ export function ModelPicker({
|
|
|
112
120
|
if (!entry) {
|
|
113
121
|
return;
|
|
114
122
|
}
|
|
123
|
+
|
|
115
124
|
if (entry.kind === 'legacy') {
|
|
116
125
|
setLegacyOpen(true);
|
|
117
126
|
inputRef.current?.focus();
|
|
127
|
+
|
|
118
128
|
return;
|
|
119
129
|
}
|
|
130
|
+
|
|
120
131
|
if (entry.kind === 'all') {
|
|
121
132
|
const kind = entry.provider.kind;
|
|
133
|
+
|
|
122
134
|
setExpanded((now) => {
|
|
123
135
|
const next = new Set(now);
|
|
136
|
+
|
|
124
137
|
if (!next.delete(kind)) {
|
|
125
138
|
next.add(kind);
|
|
126
139
|
}
|
|
140
|
+
|
|
127
141
|
return next;
|
|
128
142
|
});
|
|
143
|
+
|
|
129
144
|
inputRef.current?.focus();
|
|
145
|
+
|
|
130
146
|
return;
|
|
131
147
|
}
|
|
148
|
+
|
|
132
149
|
if (requirementOf(entry) !== null) {
|
|
133
150
|
return;
|
|
134
151
|
}
|
|
152
|
+
|
|
135
153
|
onChange(entry.provider.kind, entry.model.slug);
|
|
136
154
|
setOpen(false);
|
|
137
155
|
};
|
|
@@ -193,6 +211,7 @@ export function ModelPicker({
|
|
|
193
211
|
if (entry.kind === 'all') {
|
|
194
212
|
const before = entries[i - 1];
|
|
195
213
|
const header = grouped && (before === undefined || before.kind === 'legacy' || before.provider.kind !== entry.provider.kind);
|
|
214
|
+
|
|
196
215
|
return (
|
|
197
216
|
<div key={`all-${entry.provider.kind}`}>
|
|
198
217
|
{separatesGroups(kindBefore(i), entry.provider.kind) && <Menu.Separator />}
|
|
@@ -215,6 +234,7 @@ export function ModelPicker({
|
|
|
215
234
|
</div>
|
|
216
235
|
);
|
|
217
236
|
}
|
|
237
|
+
|
|
218
238
|
if (entry.kind === 'legacy') {
|
|
219
239
|
return (
|
|
220
240
|
<button
|
|
@@ -229,11 +249,13 @@ export function ModelPicker({
|
|
|
229
249
|
</button>
|
|
230
250
|
);
|
|
231
251
|
}
|
|
252
|
+
|
|
232
253
|
const previous = entries[i - 1];
|
|
233
254
|
// A provider's rows start where the one before it ended; its row that opens the rest belongs to it.
|
|
234
255
|
const first = grouped && (previous === undefined || previous.kind === 'legacy' || previous.provider.kind !== entry.provider.kind);
|
|
235
256
|
// The makers read as groups only where the whole list is shown, so a short list is not cut by labels.
|
|
236
257
|
const vendorOpen = trimmed !== '' || expanded.has(entry.provider.kind);
|
|
258
|
+
|
|
237
259
|
// The starred rows lead their CLI without a maker's label; the makers start after them.
|
|
238
260
|
const vendorFirst =
|
|
239
261
|
vendorOpen &&
|
|
@@ -244,11 +266,13 @@ export function ModelPicker({
|
|
|
244
266
|
previous.provider.kind !== entry.provider.kind ||
|
|
245
267
|
starredHere(previous) ||
|
|
246
268
|
previous.model.vendor !== entry.model.vendor);
|
|
269
|
+
|
|
247
270
|
const requirement = requirementOf(entry);
|
|
248
271
|
const next = entries[i + 1];
|
|
249
272
|
const lastOfGroup = next === undefined || next.kind !== 'model' || next.provider.kind !== entry.provider.kind;
|
|
250
273
|
const chosen = entry.provider.kind === provider && entry.model.slug === selection.model;
|
|
251
274
|
const starred = starredHere(entry);
|
|
275
|
+
|
|
252
276
|
return (
|
|
253
277
|
<div key={`${entry.provider.kind}/${entry.model.slug}`}>
|
|
254
278
|
{separatesGroups(kindBefore(i), entry.provider.kind) && <Menu.Separator />}
|
|
@@ -325,9 +349,11 @@ export function ModelPicker({
|
|
|
325
349
|
export function StashPicker({ onRestore }: { onRestore(prompt: StashedPrompt): void }) {
|
|
326
350
|
const { t } = useTranslation('agent-chat');
|
|
327
351
|
const prompts = useStash((s) => s.prompts);
|
|
352
|
+
|
|
328
353
|
if (prompts.length === 0) {
|
|
329
354
|
return null;
|
|
330
355
|
}
|
|
356
|
+
|
|
331
357
|
return (
|
|
332
358
|
<Popover.Root>
|
|
333
359
|
<Tooltip label={t('pickers.stash.title')} kbd={STASH_SHORTCUT}>
|
|
@@ -82,6 +82,7 @@ export function PromptComposer({
|
|
|
82
82
|
if (hold?.releasedBy === 'blur' && heldKey.current === activeKey) {
|
|
83
83
|
return;
|
|
84
84
|
}
|
|
85
|
+
|
|
85
86
|
if (focused && expanded && !elsewhere && !ref.current?.querySelector('.prompt-card')?.contains(document.activeElement)) {
|
|
86
87
|
// Keys meant for the draft may still be coming, and on a button the next Space would answer the card.
|
|
87
88
|
if (hasDraft) {
|
|
@@ -114,6 +115,7 @@ export function PromptComposer({
|
|
|
114
115
|
onDraft={(draft) => session.setDraft(promptIdOf(active), draft)}
|
|
115
116
|
onAction={(action) => {
|
|
116
117
|
refocus.hold();
|
|
118
|
+
|
|
117
119
|
void session
|
|
118
120
|
.act(active, () => answerPrompt(active, action, actions, host))
|
|
119
121
|
.then((result) => {
|
|
@@ -76,23 +76,29 @@ export function PromptEditor({
|
|
|
76
76
|
referables,
|
|
77
77
|
label
|
|
78
78
|
});
|
|
79
|
+
|
|
79
80
|
const extensions = chipDecorations({ mentions: value.mentions, skills: value.skills, references: menus.referenceChips });
|
|
80
81
|
|
|
81
82
|
const onKeyDown = (e: KeyboardEvent): boolean => {
|
|
82
83
|
if (e.isComposing) {
|
|
83
84
|
return false;
|
|
84
85
|
}
|
|
86
|
+
|
|
85
87
|
// Escape closes the picker, and only that: the dialog around the field stays.
|
|
86
88
|
if (e.key === 'Escape' && menus.menuOpen) {
|
|
87
89
|
menus.close();
|
|
88
90
|
e.stopPropagation();
|
|
91
|
+
|
|
89
92
|
return true;
|
|
90
93
|
}
|
|
94
|
+
|
|
91
95
|
// Enter adds a line here rather than sending, so on a command it completes like Tab.
|
|
92
96
|
if (e.key === 'Enter' && menus.commandMenuOpen) {
|
|
93
97
|
menus.completeCommand(menus.commands[menus.activeIndex]!);
|
|
98
|
+
|
|
94
99
|
return true;
|
|
95
100
|
}
|
|
101
|
+
|
|
96
102
|
return pickInMenu(e, menus);
|
|
97
103
|
};
|
|
98
104
|
|
|
@@ -18,6 +18,7 @@ interface Offer {
|
|
|
18
18
|
/* The last line the range covers. A triple-click ends on an empty rect at the start of the next row. */
|
|
19
19
|
function lastLineOf(range: Range): DOMRect | null {
|
|
20
20
|
const rects = [...range.getClientRects()].filter((rect) => rect.width > 0);
|
|
21
|
+
|
|
21
22
|
return rects.at(-1) ?? null;
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -39,15 +40,19 @@ export function QuoteButton({
|
|
|
39
40
|
|
|
40
41
|
useEffect(() => {
|
|
41
42
|
let pressed = false;
|
|
43
|
+
|
|
42
44
|
const read = (): void => {
|
|
43
45
|
const selection = document.getSelection();
|
|
44
46
|
const quote = taker.current === null ? null : selectedAnswerQuote(selection, thread.current);
|
|
45
47
|
const box = frame.current;
|
|
46
48
|
const line = quote === null || selection === null ? null : lastLineOf(selection.getRangeAt(0));
|
|
49
|
+
|
|
47
50
|
if (quote === null || box === null || line === null) {
|
|
48
51
|
setOffer(null);
|
|
52
|
+
|
|
49
53
|
return;
|
|
50
54
|
}
|
|
55
|
+
|
|
51
56
|
const bounds = box.getBoundingClientRect();
|
|
52
57
|
// The canvas may zoom the cell; the button is placed in the frame's own pixels.
|
|
53
58
|
const zoom = box.offsetWidth > 0 ? bounds.width / box.offsetWidth : 1;
|
|
@@ -55,23 +60,30 @@ export function QuoteButton({
|
|
|
55
60
|
const y = (line.bottom - bounds.top) / zoom + GAP_PX;
|
|
56
61
|
setOffer({ quote: quote.text, itemId: quote.itemId, x: Math.round(x), y: Math.round(y) });
|
|
57
62
|
};
|
|
63
|
+
|
|
58
64
|
const press = (): void => {
|
|
59
65
|
pressed = true;
|
|
60
66
|
};
|
|
67
|
+
|
|
61
68
|
const release = (): void => {
|
|
62
69
|
pressed = false;
|
|
63
70
|
read();
|
|
64
71
|
};
|
|
72
|
+
|
|
65
73
|
const change = (): void => {
|
|
66
74
|
if (pressed) {
|
|
67
75
|
setOffer(null);
|
|
76
|
+
|
|
68
77
|
return;
|
|
69
78
|
}
|
|
79
|
+
|
|
70
80
|
read();
|
|
71
81
|
};
|
|
82
|
+
|
|
72
83
|
document.addEventListener('pointerdown', press, true);
|
|
73
84
|
document.addEventListener('pointerup', release, true);
|
|
74
85
|
document.addEventListener('selectionchange', change);
|
|
86
|
+
|
|
75
87
|
return () => {
|
|
76
88
|
document.removeEventListener('pointerdown', press, true);
|
|
77
89
|
document.removeEventListener('pointerup', release, true);
|
|
@@ -31,6 +31,7 @@ export function TokenChip({ kind, value, sigil, icon, label, tone, onOpen, class
|
|
|
31
31
|
onOpen();
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
|
+
|
|
34
35
|
return (
|
|
35
36
|
<span
|
|
36
37
|
{...rest}
|
|
@@ -55,11 +56,13 @@ export function ReferenceChip({ id }: { id: string }) {
|
|
|
55
56
|
const { t } = useTranslation('agent-chat');
|
|
56
57
|
const { items, open } = useContext(ReferenceLookupContext);
|
|
57
58
|
const item = items.find((candidate) => candidate.id === id);
|
|
59
|
+
|
|
58
60
|
if (item === undefined) {
|
|
59
61
|
return (
|
|
60
62
|
<TokenChip kind="gone" icon={<Icon icon={CircleOff} size={14} className={TOKEN_ICON} />} label={t('references.gone')} tone={REFERENCE_GONE_TONE} />
|
|
61
63
|
);
|
|
62
64
|
}
|
|
65
|
+
|
|
63
66
|
return (
|
|
64
67
|
<TokenChip
|
|
65
68
|
kind="reference"
|
|
@@ -20,6 +20,7 @@ interface ResumeCompactionDockProps {
|
|
|
20
20
|
export function ResumeCompactionDock({ tokens, reason = 'idle', onCompact, onDismiss }: ResumeCompactionDockProps) {
|
|
21
21
|
const { t } = useTranslation('agent-chat');
|
|
22
22
|
const cold = reason === 'cache-expired';
|
|
23
|
+
|
|
23
24
|
return (
|
|
24
25
|
<div className="flex items-center gap-2 border-b border-border px-3.5 py-2.5">
|
|
25
26
|
<Icon icon={Minimize2} size={16} className="shrink-0 text-text-faint" />
|