@adecore/agents-react 0.38.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/AgentModelLabel.d.ts +10 -0
- package/dist/agents/AgentModelLabel.js +98 -0
- package/dist/agents/model-name.js +2 -1
- package/dist/agents/provider-paths.d.ts +2 -1
- package/dist/agents/provider-paths.js +7 -3
- package/dist/chat/account-choice.js +57 -28
- package/dist/chat/bookmarks.js +2 -1
- package/dist/chat/drafts.d.ts +0 -1
- package/dist/chat/drafts.js +9 -28
- package/dist/chat/logic/picker-entries.js +2 -8
- package/dist/chat/logic/resume-compaction.js +2 -5
- package/dist/chat/logic/timeline-copy.js +2 -8
- package/dist/chat/logic/timeline.js +9 -10
- package/dist/chat/logic/workflow.js +2 -4
- package/dist/chat/subagent-conversation.js +2 -1
- package/dist/chat/ui/Composer.js +385 -360
- package/dist/chat/ui/GoalDock.js +2 -1
- package/dist/chat/ui/LimitState.js +4 -6
- package/dist/chat/ui/Pickers.d.ts +2 -1
- package/dist/chat/ui/Pickers.js +126 -123
- package/dist/chat/ui/PromptEditor.d.ts +19 -0
- package/dist/chat/ui/PromptEditor.js +115 -0
- package/dist/chat/ui/Timeline.d.ts +2 -1
- package/dist/chat/ui/Timeline.js +13 -1
- package/dist/chat/ui/composer/ComposerControls.d.ts +3 -0
- package/dist/chat/ui/composer/ComposerControls.js +235 -38
- package/dist/chat/ui/composer/key-handler.d.ts +1 -1
- package/dist/chat/ui/composer/key-handler.js +3 -11
- package/dist/chat/ui/composer/prompt-picks.d.ts +4 -0
- package/dist/chat/ui/composer/prompt-picks.js +14 -0
- package/dist/chat/ui/composer/use-composer-menus.d.ts +4 -49
- package/dist/chat/ui/composer/use-composer-menus.js +65 -519
- package/dist/chat/ui/composer/use-continuable-turn.js +0 -1
- package/dist/chat/ui/composer/use-notice.js +2 -1
- package/dist/chat/ui/composer/use-prompt-history.js +1 -1
- package/dist/chat/ui/composer/use-prompt-menus.d.ts +73 -0
- package/dist/chat/ui/composer/use-prompt-menus.js +499 -0
- package/dist/chat/ui/composer/use-take-back.js +2 -2
- package/dist/chat/ui/diff/ApprovalReview.js +1 -1
- package/dist/chat/ui/highlighter.d.ts +0 -6
- package/dist/chat/ui/highlighter.js +0 -12
- package/dist/chat/ui/icons.d.ts +0 -1
- package/dist/chat/ui/icons.js +0 -3
- package/dist/chat/ui/index.d.ts +3 -0
- package/dist/chat/ui/index.js +2 -0
- package/dist/chat/ui/rows/MessageRows.js +2 -5
- package/dist/chat/ui/rows/row-rhythm.js +2 -5
- package/dist/chat/ui/theme-watch.js +3 -7
- package/dist/host-hooks.d.ts +2 -1
- package/dist/host-hooks.js +6 -0
- package/dist/host.d.ts +17 -1
- package/dist/index.d.ts +1 -1
- package/dist/locales/en/agent-chat.d.ts +1 -0
- package/dist/locales/en/agent-chat.js +1 -0
- package/dist/locales/nl/agent-chat.d.ts +1 -0
- package/dist/locales/nl/agent-chat.js +1 -0
- package/dist/prompts/logic/usePromptSession.js +2 -1
- package/dist/providers/AddAccountForm.js +2 -1
- package/dist/providers/CliDetail.js +104 -130
- package/dist/providers/account-actions.js +6 -9
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/index.js +1 -0
- package/dist/state/chats.d.ts +1 -0
- package/dist/state/chats.js +6 -11
- package/dist/state/provider-accounts.d.ts +1 -1
- package/dist/state/provider-accounts.js +2 -8
- package/dist/theme.css +3 -0
- package/dist/transport.d.ts +2 -0
- package/dist/transport.js +14 -0
- package/dist/usage/ChartLogo.d.ts +5 -0
- package/dist/usage/ChartLogo.js +48 -0
- package/dist/usage/LimitsList.js +6 -16
- package/dist/usage/UsageBreakdown.js +17 -40
- package/dist/usage/UsageLimits.js +183 -211
- package/dist/usage/UsagePage.js +3 -17
- package/dist/usage/UsageSummary.js +4 -9
- package/dist/usage/limits.js +16 -31
- package/dist/usage/summary.d.ts +1 -1
- package/dist/usage/summary.js +3 -5
- package/package.json +26 -26
- package/src/agents/AccountDot.tsx +1 -0
- package/src/agents/AgentIcon.tsx +2 -0
- package/src/agents/AgentModelLabel.tsx +38 -0
- package/src/agents/ModelOptionControl.tsx +8 -0
- package/src/agents/account-limits.ts +3 -0
- package/src/agents/accounts.ts +12 -0
- package/src/agents/model-name.ts +13 -1
- package/src/agents/model-options.ts +2 -0
- package/src/agents/model-requirement.ts +4 -0
- package/src/agents/option-words.ts +2 -0
- package/src/agents/provider-paths.ts +8 -3
- package/src/agents/status-look.ts +1 -0
- package/src/chat/account-choice.ts +25 -19
- package/src/chat/actions.ts +5 -0
- package/src/chat/attachments.ts +8 -0
- package/src/chat/bookmarks.ts +13 -1
- package/src/chat/chat-client.ts +63 -0
- package/src/chat/chat-references.ts +3 -0
- package/src/chat/composer-effort.ts +5 -0
- package/src/chat/composer-send.ts +4 -0
- package/src/chat/drafts.ts +21 -17
- package/src/chat/forks.ts +4 -0
- package/src/chat/guards.ts +2 -0
- package/src/chat/index.ts +7 -0
- package/src/chat/logic/ansi.ts +47 -0
- package/src/chat/logic/bookmarks.ts +7 -0
- package/src/chat/logic/command-looks.ts +29 -0
- package/src/chat/logic/context-usage.ts +3 -0
- package/src/chat/logic/continue-after-stop.ts +5 -0
- package/src/chat/logic/effort.ts +4 -0
- package/src/chat/logic/embedded-code.ts +63 -0
- package/src/chat/logic/fork.ts +22 -0
- package/src/chat/logic/generated-image.ts +7 -0
- package/src/chat/logic/goal.ts +7 -0
- package/src/chat/logic/handback.ts +2 -0
- package/src/chat/logic/limit.ts +8 -0
- package/src/chat/logic/picker-entries.ts +16 -4
- package/src/chat/logic/resume-compaction.ts +22 -5
- package/src/chat/logic/scrubber.ts +38 -5
- package/src/chat/logic/thought.ts +5 -0
- package/src/chat/logic/thread-cards.ts +6 -0
- package/src/chat/logic/timeline-copy.ts +4 -9
- package/src/chat/logic/timeline-target.ts +15 -0
- package/src/chat/logic/timeline.ts +113 -10
- package/src/chat/logic/tools.ts +30 -0
- package/src/chat/logic/visual-height.ts +5 -0
- package/src/chat/logic/visual-viewport.ts +1 -0
- package/src/chat/logic/welcome.ts +3 -0
- package/src/chat/logic/workflow.ts +26 -7
- package/src/chat/mentions.ts +56 -0
- package/src/chat/pending-prompts.ts +16 -0
- package/src/chat/persisted-json.ts +15 -0
- package/src/chat/preferences.ts +16 -0
- package/src/chat/prompt-hold.ts +2 -0
- package/src/chat/quote.ts +7 -0
- package/src/chat/reading-width.ts +1 -0
- package/src/chat/recent-messages.ts +5 -0
- package/src/chat/resume-compaction-dismissals.ts +1 -0
- package/src/chat/stash.ts +12 -0
- package/src/chat/subagent-conversation.ts +24 -4
- package/src/chat/subagent-list.ts +14 -0
- package/src/chat/subagent-support.ts +1 -0
- package/src/chat/subagent-view.ts +9 -0
- package/src/chat/thumbnails.ts +14 -0
- package/src/chat/timeline-flash.ts +2 -0
- package/src/chat/timeline-scroll.ts +23 -0
- package/src/chat/ui/AccountPill.tsx +4 -0
- package/src/chat/ui/AnsiOutput.tsx +1 -0
- package/src/chat/ui/BookmarkMarker.tsx +5 -0
- package/src/chat/ui/BookmarkSubmenu.tsx +1 -0
- package/src/chat/ui/ChatActivity.tsx +13 -0
- package/src/chat/ui/CodeBlock.tsx +8 -0
- package/src/chat/ui/Composer.tsx +27 -3
- package/src/chat/ui/ComposerInput.tsx +22 -0
- package/src/chat/ui/DiffPool.tsx +3 -0
- package/src/chat/ui/EditDiff.tsx +5 -0
- package/src/chat/ui/GoalDock.tsx +13 -1
- package/src/chat/ui/ImageView.tsx +11 -0
- package/src/chat/ui/LimitState.tsx +23 -6
- package/src/chat/ui/Markdown.tsx +41 -0
- package/src/chat/ui/MermaidBlock.tsx +16 -0
- package/src/chat/ui/MessageActions.tsx +8 -0
- package/src/chat/ui/ModelMarks.tsx +1 -0
- package/src/chat/ui/Pickers.tsx +30 -1
- package/src/chat/ui/PromptComposer.tsx +2 -0
- package/src/chat/ui/PromptEditor.tsx +136 -0
- package/src/chat/ui/QuoteButton.tsx +12 -0
- package/src/chat/ui/ReferenceChip.tsx +3 -0
- package/src/chat/ui/ResumeCompactionDock.tsx +1 -0
- package/src/chat/ui/RowTime.tsx +1 -0
- package/src/chat/ui/RunSettings.tsx +38 -0
- package/src/chat/ui/Scrubber.tsx +14 -0
- package/src/chat/ui/SessionLine.tsx +1 -0
- package/src/chat/ui/ShellCommand.tsx +32 -0
- package/src/chat/ui/SubagentControls.tsx +7 -0
- package/src/chat/ui/SubagentInfo.tsx +1 -0
- package/src/chat/ui/SubagentStopButton.tsx +6 -0
- package/src/chat/ui/SubagentTimeline.tsx +10 -0
- package/src/chat/ui/Timeline.tsx +141 -1
- package/src/chat/ui/TimelineMenu.tsx +5 -0
- package/src/chat/ui/UnifiedDiff.tsx +6 -0
- package/src/chat/ui/UploadThumb.tsx +2 -0
- package/src/chat/ui/VisualDialogs.tsx +3 -0
- package/src/chat/ui/VisualFrame.tsx +15 -0
- package/src/chat/ui/Welcome.tsx +1 -0
- package/src/chat/ui/chip-clipboard.ts +20 -0
- package/src/chat/ui/code-lines.ts +14 -0
- package/src/chat/ui/code-theme.ts +1 -0
- package/src/chat/ui/composer/ComposerControls.tsx +70 -15
- package/src/chat/ui/composer/ComposerMenu.tsx +12 -0
- package/src/chat/ui/composer/DraftParts.tsx +2 -0
- package/src/chat/ui/composer/QueuedMessages.tsx +4 -0
- package/src/chat/ui/composer/chip-paste.ts +16 -0
- package/src/chat/ui/composer/chips.ts +6 -0
- package/src/chat/ui/composer/editor.ts +9 -0
- package/src/chat/ui/composer/insert.ts +2 -0
- package/src/chat/ui/composer/key-handler.ts +42 -9
- package/src/chat/ui/composer/keys.ts +23 -0
- package/src/chat/ui/composer/placeholder.ts +6 -0
- package/src/chat/ui/composer/prompt-picks.ts +18 -0
- package/src/chat/ui/composer/surface.ts +5 -0
- package/src/chat/ui/composer/use-chat-controls.ts +13 -0
- package/src/chat/ui/composer/use-compaction-offer.ts +3 -0
- package/src/chat/ui/composer/use-composer-menus.ts +20 -275
- package/src/chat/ui/composer/use-composer-submit.ts +27 -0
- package/src/chat/ui/composer/use-continuable-turn.ts +3 -5
- package/src/chat/ui/composer/use-draft-files.ts +24 -0
- package/src/chat/ui/composer/use-effort-stepper.ts +8 -0
- package/src/chat/ui/composer/use-notice.ts +4 -1
- package/src/chat/ui/composer/use-pending-requests.ts +3 -0
- package/src/chat/ui/composer/use-prompt-history.ts +20 -11
- package/src/chat/ui/composer/use-prompt-hold.ts +3 -0
- package/src/chat/ui/composer/use-prompt-menus.ts +361 -0
- package/src/chat/ui/composer/use-take-back.ts +20 -2
- package/src/chat/ui/diff/ApprovalReview.tsx +18 -15
- package/src/chat/ui/diff/DiffFallbackBoundary.tsx +1 -0
- package/src/chat/ui/diff/DiffOmitted.tsx +1 -0
- package/src/chat/ui/diff/DiffStack.tsx +2 -0
- package/src/chat/ui/diff/DiffUnavailable.tsx +1 -0
- package/src/chat/ui/diff/EditorEditDiff.tsx +13 -0
- package/src/chat/ui/diff/EditorPatchDiff.tsx +4 -0
- package/src/chat/ui/diff/LegacyDiffPool.tsx +2 -0
- package/src/chat/ui/diff/LegacyEditDiff.tsx +2 -0
- package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +8 -0
- package/src/chat/ui/diff/RawDiff.tsx +1 -0
- package/src/chat/ui/diff/ReviewDiff.tsx +1 -0
- package/src/chat/ui/diff/diff-source-budget.ts +8 -0
- package/src/chat/ui/diff/editor-edit-model.ts +4 -0
- package/src/chat/ui/diff/review-plan.ts +9 -0
- package/src/chat/ui/diff/review-settle.ts +1 -0
- package/src/chat/ui/diff/syntax-runtime.ts +52 -0
- package/src/chat/ui/diff/use-review-stack.ts +9 -0
- package/src/chat/ui/diff-syntax.ts +12 -0
- package/src/chat/ui/diff-theme.ts +2 -0
- package/src/chat/ui/file-links.ts +2 -0
- package/src/chat/ui/find-reveal.ts +1 -0
- package/src/chat/ui/full-diff.ts +1 -0
- package/src/chat/ui/highlighter.ts +29 -15
- package/src/chat/ui/icons.tsx +2 -4
- package/src/chat/ui/index.ts +7 -0
- package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +25 -0
- package/src/chat/ui/intelligent-ui/UiFallbackPart.tsx +1 -0
- package/src/chat/ui/intelligent-ui/UiReply.tsx +63 -0
- package/src/chat/ui/intelligent-ui/block-local.ts +5 -0
- package/src/chat/ui/intelligent-ui/chart-data.ts +15 -0
- package/src/chat/ui/intelligent-ui/node-text.ts +17 -0
- package/src/chat/ui/intelligent-ui/renderers/binding.ts +1 -0
- package/src/chat/ui/intelligent-ui/renderers/chart.tsx +31 -0
- package/src/chat/ui/intelligent-ui/renderers/choices.tsx +4 -0
- package/src/chat/ui/intelligent-ui/renderers/content.tsx +18 -0
- package/src/chat/ui/intelligent-ui/renderers/data.tsx +11 -0
- package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +13 -0
- package/src/chat/ui/intelligent-ui/renderers/links.tsx +9 -0
- package/src/chat/ui/intelligent-ui/renderers/map-slots.ts +15 -0
- package/src/chat/ui/intelligent-ui/renderers/map-style.ts +15 -0
- package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +48 -0
- package/src/chat/ui/intelligent-ui/renderers/map.tsx +20 -0
- package/src/chat/ui/intelligent-ui/renderers/place-point.ts +3 -0
- package/src/chat/ui/intelligent-ui/renderers/structure.tsx +3 -0
- package/src/chat/ui/intelligent-ui/renderers/text.tsx +8 -0
- package/src/chat/ui/intelligent-ui/reply-parts.ts +5 -0
- package/src/chat/ui/intelligent-ui/table-data.ts +17 -0
- package/src/chat/ui/intelligent-ui/use-in-view.ts +4 -0
- package/src/chat/ui/intelligent-ui/use-later.ts +4 -0
- package/src/chat/ui/intelligent-ui/use-truncated.ts +5 -0
- package/src/chat/ui/intelligent-ui/use-ui-links.ts +29 -0
- package/src/chat/ui/intelligent-ui/use-ui-queries.ts +60 -0
- package/src/chat/ui/markdown-blocks.ts +64 -0
- package/src/chat/ui/mermaid.ts +4 -0
- package/src/chat/ui/quote-selection.ts +10 -0
- package/src/chat/ui/rehype-chips.ts +5 -0
- package/src/chat/ui/rehype-fade.ts +2 -0
- package/src/chat/ui/remark-html-as-text.ts +5 -0
- package/src/chat/ui/reveal.ts +18 -0
- package/src/chat/ui/rows/ForksRow.tsx +5 -0
- package/src/chat/ui/rows/GeneratedImageRow.tsx +10 -0
- package/src/chat/ui/rows/MessageRows.tsx +29 -5
- package/src/chat/ui/rows/RowContainer.tsx +1 -0
- package/src/chat/ui/rows/Rows.tsx +2 -0
- package/src/chat/ui/rows/SubagentRow.tsx +13 -0
- package/src/chat/ui/rows/TaskRow.tsx +7 -0
- package/src/chat/ui/rows/VisualRow.tsx +4 -0
- package/src/chat/ui/rows/WorkRows.tsx +46 -0
- package/src/chat/ui/rows/WorkflowRow.tsx +10 -0
- package/src/chat/ui/rows/row-rhythm.ts +7 -5
- package/src/chat/ui/theme-watch.ts +8 -7
- package/src/chat/ui/thread-references.ts +7 -0
- package/src/chat/ui/use-diff-source-tokens.ts +8 -0
- package/src/chat/ui/useToggleSet.ts +3 -0
- package/src/chat/ui/visual-theme.ts +30 -0
- package/src/chat/visual-bridge.ts +23 -0
- package/src/chat/visual-viewport.ts +12 -0
- package/src/host-hooks.ts +15 -5
- package/src/host.ts +29 -1
- package/src/index.ts +5 -0
- package/src/lazy.tsx +9 -0
- package/src/locales/en/agent-chat.ts +1 -0
- package/src/locales/nl/agent-chat.ts +1 -0
- package/src/mounted-registry.ts +4 -0
- package/src/port-transport.ts +32 -0
- package/src/prompts/index.ts +2 -0
- package/src/prompts/logic/focus.ts +5 -0
- package/src/prompts/logic/keys.ts +12 -0
- package/src/prompts/logic/prompts.ts +8 -0
- package/src/prompts/logic/subjects.ts +8 -0
- package/src/prompts/logic/useFocusAfterAnswer.ts +2 -0
- package/src/prompts/logic/usePromptSession.ts +13 -1
- package/src/prompts/ui/ApprovalBody.tsx +2 -0
- package/src/prompts/ui/PromptActions.tsx +2 -0
- package/src/prompts/ui/PromptCard.tsx +6 -0
- package/src/prompts/ui/PromptView.tsx +6 -0
- package/src/prompts/ui/QuestionBody.tsx +11 -0
- package/src/providers/AccountColors.tsx +1 -0
- package/src/providers/AccountDetail.tsx +7 -0
- package/src/providers/AccountVariables.tsx +2 -0
- package/src/providers/AddAccountForm.tsx +5 -1
- package/src/providers/CliDetail.tsx +6 -5
- package/src/providers/InstallPage.tsx +11 -0
- package/src/providers/ProviderList.tsx +3 -0
- package/src/providers/ProvidersPane.tsx +18 -0
- package/src/providers/account-actions.ts +10 -8
- package/src/providers/index.ts +1 -0
- package/src/providers/install-steps.ts +3 -0
- package/src/providers/pane-target.ts +5 -0
- package/src/providers/provider-abilities.ts +1 -0
- package/src/providers/provider-groups.ts +6 -0
- package/src/providers/variables.ts +12 -0
- package/src/scope.ts +2 -0
- package/src/state/chats.ts +61 -14
- package/src/state/index.ts +2 -0
- package/src/state/provider-accounts.ts +7 -9
- package/src/state/providers.ts +1 -0
- package/src/state/usage.ts +6 -0
- package/src/storage.ts +18 -0
- package/src/testing/prompts.ts +5 -0
- package/src/theme.css +3 -0
- package/src/transport.ts +18 -0
- package/src/usage/ChartLogo.tsx +12 -0
- package/src/usage/LimitsList.tsx +7 -8
- package/src/usage/UsageBreakdown.tsx +11 -4
- package/src/usage/UsageChart.tsx +5 -0
- package/src/usage/UsageLimits.tsx +10 -7
- package/src/usage/UsageLimitsCard.tsx +2 -0
- package/src/usage/UsagePage.tsx +16 -19
- package/src/usage/UsageSummary.tsx +4 -5
- package/src/usage/UsageTiles.tsx +2 -0
- package/src/usage/format.ts +7 -0
- package/src/usage/limit-groups.ts +14 -0
- package/src/usage/limits.ts +11 -13
- package/src/usage/money.ts +1 -0
- package/src/usage/summary.ts +15 -7
|
@@ -18,6 +18,7 @@ import { useChatActions } from '../actions';
|
|
|
18
18
|
import { useUsageStore } from '../../state/usage';
|
|
19
19
|
import { SessionLine } from './SessionLine';
|
|
20
20
|
import { useHostResumeAtReset } from '../../host-hooks';
|
|
21
|
+
import { errorMessage } from '../../transport';
|
|
21
22
|
|
|
22
23
|
// A time a limit names reads as a clock today and with its day after that, so the words follow the minute.
|
|
23
24
|
const MINUTE_MS = 60_000;
|
|
@@ -32,10 +33,13 @@ const refreshedAt = new Map<string, number>();
|
|
|
32
33
|
*/
|
|
33
34
|
function readUnreadAccounts(scope: ChatScope): void {
|
|
34
35
|
const last = refreshedAt.get(scope.id);
|
|
36
|
+
|
|
35
37
|
if (last !== undefined && Date.now() - last < UNREAD_REFRESH_MS) {
|
|
36
38
|
return;
|
|
37
39
|
}
|
|
40
|
+
|
|
38
41
|
refreshedAt.set(scope.id, Date.now());
|
|
42
|
+
|
|
39
43
|
void scope.transport
|
|
40
44
|
.request('usage.refreshLimits', {})
|
|
41
45
|
.then((snapshot) => useUsageStore.getState().setLimits(scope.id, snapshot))
|
|
@@ -51,9 +55,11 @@ function ResumeAtResetToggle({ chatId, info }: { chatId: string; info: ChatInfo
|
|
|
51
55
|
const { id } = useChatScope();
|
|
52
56
|
const actions = useChatActions();
|
|
53
57
|
const machineAllows = useHostResumeAtReset(id);
|
|
58
|
+
|
|
54
59
|
const set = (on: boolean): void => {
|
|
55
60
|
void actions.configure(chatId, { resumeAtReset: on }).catch(() => undefined);
|
|
56
61
|
};
|
|
62
|
+
|
|
57
63
|
return (
|
|
58
64
|
<Tooltip label={machineAllows ? t('limit.toggle') : t('limit.machineOff')}>
|
|
59
65
|
{/* A disabled switch does not take the pointer, so the tooltip hangs on a wrapper. */}
|
|
@@ -65,11 +71,8 @@ function ResumeAtResetToggle({ chatId, info }: { chatId: string; info: ChatInfo
|
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
/*
|
|
68
|
-
* Another account of the chat's CLI
|
|
69
|
-
*
|
|
70
|
-
* work. Only ever offered, since going on there spends someone's other plan, and nothing is
|
|
71
|
-
* remembered: the next limit suggests the one with the most room again. An account that reads the
|
|
72
|
-
* chat's conversation takes it over; any other goes on in a fork, which then opens the way a fork does.
|
|
74
|
+
* Another account of the chat's CLI with room, only ever offered since it spends someone's other plan, and
|
|
75
|
+
* never remembered. One that reads the chat's conversation takes it over; any other goes on in a fork.
|
|
73
76
|
*/
|
|
74
77
|
function ContinueOn({ chatId, info, choice }: { chatId: string; info: ChatInfo; choice: AccountChoice }) {
|
|
75
78
|
const host = useChatHost();
|
|
@@ -88,24 +91,29 @@ function ContinueOn({ chatId, info, choice }: { chatId: string; info: ChatInfo;
|
|
|
88
91
|
}, [scope, unread]);
|
|
89
92
|
|
|
90
93
|
const target = targets[0];
|
|
94
|
+
|
|
91
95
|
if (target === undefined) {
|
|
92
96
|
return null;
|
|
93
97
|
}
|
|
98
|
+
|
|
94
99
|
const go = (entry: AccountEntry): void => {
|
|
95
100
|
setBusy(true);
|
|
101
|
+
|
|
96
102
|
actions
|
|
97
103
|
.continueOn(chatId, entry.id)
|
|
98
104
|
.catch((e: unknown) =>
|
|
99
105
|
host.notify({
|
|
100
106
|
kind: 'error',
|
|
101
107
|
title: t('limit.continueFailed', { account: choice.nameOf(entry) }),
|
|
102
|
-
description:
|
|
108
|
+
description: errorMessage(e)
|
|
103
109
|
})
|
|
104
110
|
)
|
|
105
111
|
.finally(() => setBusy(false));
|
|
106
112
|
};
|
|
113
|
+
|
|
107
114
|
const name = choice.nameOf(target);
|
|
108
115
|
const split = targets.length > 1;
|
|
116
|
+
|
|
109
117
|
const suggestion = (
|
|
110
118
|
<Tooltip label={t('limit.continueOnTip', { account: name })}>
|
|
111
119
|
<Button
|
|
@@ -122,9 +130,11 @@ function ContinueOn({ chatId, info, choice }: { chatId: string; info: ChatInfo;
|
|
|
122
130
|
</Button>
|
|
123
131
|
</Tooltip>
|
|
124
132
|
);
|
|
133
|
+
|
|
125
134
|
if (!split) {
|
|
126
135
|
return suggestion;
|
|
127
136
|
}
|
|
137
|
+
|
|
128
138
|
return (
|
|
129
139
|
<ButtonGroup className="shrink-0">
|
|
130
140
|
{suggestion}
|
|
@@ -160,10 +170,12 @@ function ContinueOnRows({
|
|
|
160
170
|
const { t } = useTranslation('agent-chat');
|
|
161
171
|
const limits = useUsageLimits();
|
|
162
172
|
const now = useNow(MINUTE_MS);
|
|
173
|
+
|
|
163
174
|
return targets.map((entry) => {
|
|
164
175
|
const name = choice.nameOf(entry);
|
|
165
176
|
const session = sessionWindow(limitsOfAccount(limits, entry.id));
|
|
166
177
|
const fork = !canContinueOn(choice.accounts, info.provider, info.account, entry.id);
|
|
178
|
+
|
|
167
179
|
return (
|
|
168
180
|
<Menu.Item
|
|
169
181
|
key={entry.id}
|
|
@@ -191,12 +203,15 @@ export function LimitDock({ chatId, info }: { chatId: string; info: ChatInfo })
|
|
|
191
203
|
const now = useNow(MINUTE_MS, info.limit !== undefined);
|
|
192
204
|
const choice = useAccountChoice(info.provider, info.account);
|
|
193
205
|
const view = limitView(info, now);
|
|
206
|
+
|
|
194
207
|
if (view === null) {
|
|
195
208
|
return null;
|
|
196
209
|
}
|
|
210
|
+
|
|
197
211
|
// With a choice of accounts the line names the one that ran into the limit.
|
|
198
212
|
const account = choice === null ? null : choice.current === null ? choice.currentId : choice.nameOf(choice.current);
|
|
199
213
|
const detail = account === null ? view.detail : view.detail === null ? account : `${account} · ${view.detail}`;
|
|
214
|
+
|
|
200
215
|
return (
|
|
201
216
|
<div className="flex items-center gap-2 border-b border-border px-3.5 py-2.5">
|
|
202
217
|
<Icon icon={Hourglass} size={16} className="shrink-0 text-text-faint" />
|
|
@@ -218,9 +233,11 @@ export function LimitPill({ chatId }: { chatId: string }) {
|
|
|
218
233
|
useFormatLocale();
|
|
219
234
|
const now = useNow(MINUTE_MS, info?.limit !== undefined);
|
|
220
235
|
const view = info === undefined ? null : limitView(info, now);
|
|
236
|
+
|
|
221
237
|
if (info === undefined || view === null) {
|
|
222
238
|
return null;
|
|
223
239
|
}
|
|
240
|
+
|
|
224
241
|
return (
|
|
225
242
|
<>
|
|
226
243
|
<Tooltip label={view.detail === null ? view.title : `${view.title}. ${view.detail}`}>
|
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
|
@@ -34,6 +34,8 @@ interface ModelPickerProps {
|
|
|
34
34
|
side?: 'top' | 'bottom';
|
|
35
35
|
/* `chip` names the provider beside the model on a secondary button, for a picker that sits in a card or a form. */
|
|
36
36
|
trigger?: 'plain' | 'chip';
|
|
37
|
+
/* The chip's height: `sm` in a card, `md` beside the fields of a form. */
|
|
38
|
+
chipSize?: 'sm' | 'md';
|
|
37
39
|
/* Where the focus goes when the popup closes; by default back to the trigger. */
|
|
38
40
|
finalFocus?: RefObject<HTMLElement | null>;
|
|
39
41
|
/* A note under the list, such as what picking an agent here does. */
|
|
@@ -55,6 +57,7 @@ export function ModelPicker({
|
|
|
55
57
|
kbd = '/model',
|
|
56
58
|
side = 'top',
|
|
57
59
|
trigger = 'plain',
|
|
60
|
+
chipSize = 'sm',
|
|
58
61
|
finalFocus,
|
|
59
62
|
footer
|
|
60
63
|
}: ModelPickerProps) {
|
|
@@ -74,19 +77,26 @@ export function ModelPicker({
|
|
|
74
77
|
const trimmed = query.trim().toLowerCase();
|
|
75
78
|
|
|
76
79
|
const favorites = useChatPreferences((preferences) => preferences.favoriteModels);
|
|
80
|
+
|
|
77
81
|
const starredHere = (row: PickerEntry | undefined): boolean => row?.kind === 'model' && favorites[row.provider.kind]?.includes(row.model.slug) === true;
|
|
82
|
+
|
|
78
83
|
const entries = pickerEntriesOf(inPickerOrder(providers), trimmed, legacyOpen, expanded, { provider, model: selection.model }, (kind) =>
|
|
79
84
|
favoriteModelsOf(favorites, kind)
|
|
80
85
|
);
|
|
86
|
+
|
|
81
87
|
const scope = useContext(ChatScopeContext);
|
|
82
88
|
const machine = useHostMachineLabel(scope?.id ?? '');
|
|
83
89
|
const reported = useReportedModels(provider);
|
|
90
|
+
|
|
84
91
|
const requirementOf = (entry: PickerEntry & { kind: 'model' }) => modelRequirementOf(entry.model, entry.provider, reported);
|
|
92
|
+
|
|
85
93
|
// A CLI that is too old blocks all its models; one way to its settings under the group says it once.
|
|
86
94
|
const kindBefore = (i: number): AgentKind | undefined => {
|
|
87
95
|
const before = entries[i - 1];
|
|
96
|
+
|
|
88
97
|
return before === undefined || before.kind === 'legacy' ? undefined : before.provider.kind;
|
|
89
98
|
};
|
|
99
|
+
|
|
90
100
|
const blockedKinds = new Set(entries.flatMap((entry) => (entry.kind === 'model' && requirementOf(entry) !== null ? [entry.provider.kind] : [])));
|
|
91
101
|
|
|
92
102
|
/* Closing forgets the search, so the next open (a click or `/model`) starts on the whole list. */
|
|
@@ -96,6 +106,7 @@ export function ModelPicker({
|
|
|
96
106
|
setLegacyOpen(false);
|
|
97
107
|
setExpanded(new Set());
|
|
98
108
|
}
|
|
109
|
+
|
|
99
110
|
setIndex(0);
|
|
100
111
|
onOpenChange(next);
|
|
101
112
|
};
|
|
@@ -109,26 +120,36 @@ export function ModelPicker({
|
|
|
109
120
|
if (!entry) {
|
|
110
121
|
return;
|
|
111
122
|
}
|
|
123
|
+
|
|
112
124
|
if (entry.kind === 'legacy') {
|
|
113
125
|
setLegacyOpen(true);
|
|
114
126
|
inputRef.current?.focus();
|
|
127
|
+
|
|
115
128
|
return;
|
|
116
129
|
}
|
|
130
|
+
|
|
117
131
|
if (entry.kind === 'all') {
|
|
118
132
|
const kind = entry.provider.kind;
|
|
133
|
+
|
|
119
134
|
setExpanded((now) => {
|
|
120
135
|
const next = new Set(now);
|
|
136
|
+
|
|
121
137
|
if (!next.delete(kind)) {
|
|
122
138
|
next.add(kind);
|
|
123
139
|
}
|
|
140
|
+
|
|
124
141
|
return next;
|
|
125
142
|
});
|
|
143
|
+
|
|
126
144
|
inputRef.current?.focus();
|
|
145
|
+
|
|
127
146
|
return;
|
|
128
147
|
}
|
|
148
|
+
|
|
129
149
|
if (requirementOf(entry) !== null) {
|
|
130
150
|
return;
|
|
131
151
|
}
|
|
152
|
+
|
|
132
153
|
onChange(entry.provider.kind, entry.model.slug);
|
|
133
154
|
setOpen(false);
|
|
134
155
|
};
|
|
@@ -150,7 +171,7 @@ export function ModelPicker({
|
|
|
150
171
|
<Popover.Root open={open} onOpenChange={setOpen}>
|
|
151
172
|
<Tooltip label={owner ? `${owner.name} · ${current}` : t('pickers.model.choose')} kbd={kbd ?? undefined}>
|
|
152
173
|
{trigger === 'chip' ? (
|
|
153
|
-
<Popover.Trigger render={<Button variant="secondary" size=
|
|
174
|
+
<Popover.Trigger render={<Button variant="secondary" size={chipSize} className="min-w-0 shrink gap-1.5" />}>
|
|
154
175
|
{chip.mark === 'logo' ? <ProviderLogo provider={provider as UsageProvider} size={12} /> : <AgentIcon kind={provider} size={12} />}
|
|
155
176
|
<span className="truncate">{chip.name}</span>
|
|
156
177
|
<span className="max-w-32 truncate text-text-muted">{chip.model}</span>
|
|
@@ -190,6 +211,7 @@ export function ModelPicker({
|
|
|
190
211
|
if (entry.kind === 'all') {
|
|
191
212
|
const before = entries[i - 1];
|
|
192
213
|
const header = grouped && (before === undefined || before.kind === 'legacy' || before.provider.kind !== entry.provider.kind);
|
|
214
|
+
|
|
193
215
|
return (
|
|
194
216
|
<div key={`all-${entry.provider.kind}`}>
|
|
195
217
|
{separatesGroups(kindBefore(i), entry.provider.kind) && <Menu.Separator />}
|
|
@@ -212,6 +234,7 @@ export function ModelPicker({
|
|
|
212
234
|
</div>
|
|
213
235
|
);
|
|
214
236
|
}
|
|
237
|
+
|
|
215
238
|
if (entry.kind === 'legacy') {
|
|
216
239
|
return (
|
|
217
240
|
<button
|
|
@@ -226,11 +249,13 @@ export function ModelPicker({
|
|
|
226
249
|
</button>
|
|
227
250
|
);
|
|
228
251
|
}
|
|
252
|
+
|
|
229
253
|
const previous = entries[i - 1];
|
|
230
254
|
// A provider's rows start where the one before it ended; its row that opens the rest belongs to it.
|
|
231
255
|
const first = grouped && (previous === undefined || previous.kind === 'legacy' || previous.provider.kind !== entry.provider.kind);
|
|
232
256
|
// The makers read as groups only where the whole list is shown, so a short list is not cut by labels.
|
|
233
257
|
const vendorOpen = trimmed !== '' || expanded.has(entry.provider.kind);
|
|
258
|
+
|
|
234
259
|
// The starred rows lead their CLI without a maker's label; the makers start after them.
|
|
235
260
|
const vendorFirst =
|
|
236
261
|
vendorOpen &&
|
|
@@ -241,11 +266,13 @@ export function ModelPicker({
|
|
|
241
266
|
previous.provider.kind !== entry.provider.kind ||
|
|
242
267
|
starredHere(previous) ||
|
|
243
268
|
previous.model.vendor !== entry.model.vendor);
|
|
269
|
+
|
|
244
270
|
const requirement = requirementOf(entry);
|
|
245
271
|
const next = entries[i + 1];
|
|
246
272
|
const lastOfGroup = next === undefined || next.kind !== 'model' || next.provider.kind !== entry.provider.kind;
|
|
247
273
|
const chosen = entry.provider.kind === provider && entry.model.slug === selection.model;
|
|
248
274
|
const starred = starredHere(entry);
|
|
275
|
+
|
|
249
276
|
return (
|
|
250
277
|
<div key={`${entry.provider.kind}/${entry.model.slug}`}>
|
|
251
278
|
{separatesGroups(kindBefore(i), entry.provider.kind) && <Menu.Separator />}
|
|
@@ -322,9 +349,11 @@ export function ModelPicker({
|
|
|
322
349
|
export function StashPicker({ onRestore }: { onRestore(prompt: StashedPrompt): void }) {
|
|
323
350
|
const { t } = useTranslation('agent-chat');
|
|
324
351
|
const prompts = useStash((s) => s.prompts);
|
|
352
|
+
|
|
325
353
|
if (prompts.length === 0) {
|
|
326
354
|
return null;
|
|
327
355
|
}
|
|
356
|
+
|
|
328
357
|
return (
|
|
329
358
|
<Popover.Root>
|
|
330
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) => {
|