@adecore/agents-react 0.23.1 → 0.24.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/README.md +14 -11
- package/dist/agents/AccountDot.js +3 -1
- package/dist/agents/accounts.d.ts +3 -2
- package/dist/agents/accounts.js +5 -6
- package/dist/chat/actions.d.ts +2 -2
- package/dist/chat/actions.js +8 -5
- package/dist/chat/bookmarks.d.ts +5 -3
- package/dist/chat/bookmarks.js +13 -14
- package/dist/chat/chat-client.d.ts +1 -0
- package/dist/chat/chat-client.js +7 -0
- package/dist/chat/composer-send.d.ts +3 -0
- package/dist/chat/composer-send.js +22 -0
- package/dist/chat/forks.js +22 -5
- package/dist/chat/index.d.ts +31 -0
- package/dist/chat/index.js +28 -0
- package/dist/chat/logic/embedded-code.d.ts +8 -0
- package/dist/chat/logic/embedded-code.js +371 -0
- package/dist/chat/logic/fork.js +1 -0
- package/dist/chat/logic/goal.d.ts +6 -0
- package/dist/chat/logic/goal.js +26 -0
- package/dist/chat/logic/scrubber.d.ts +1 -1
- package/dist/chat/logic/scrubber.js +6 -5
- package/dist/chat/logic/thought.d.ts +3 -0
- package/dist/chat/logic/thought.js +35 -0
- package/dist/chat/logic/thread-cards.js +2 -0
- package/dist/chat/logic/timeline.d.ts +5 -0
- package/dist/chat/logic/timeline.js +22 -1
- package/dist/chat/logic/tool-catalog.d.ts +1 -0
- package/dist/chat/logic/tool-catalog.js +3 -1
- package/dist/chat/logic/tools.d.ts +14 -0
- package/dist/chat/logic/tools.js +57 -0
- package/dist/chat/preferences.d.ts +6 -0
- package/dist/chat/preferences.js +19 -3
- package/dist/chat/reading-width.d.ts +9 -0
- package/dist/chat/reading-width.js +27 -0
- package/dist/chat/send-key.d.ts +10 -0
- package/dist/chat/send-key.js +24 -0
- package/dist/chat/ui/BookmarkMarker.js +4 -2
- package/dist/chat/ui/BookmarkSubmenu.js +4 -3
- package/dist/chat/ui/ChatActivity.js +14 -10
- package/dist/chat/ui/ChatReferenceChip.js +3 -4
- package/dist/chat/ui/CodeBlock.d.ts +2 -1
- package/dist/chat/ui/CodeBlock.js +14 -96
- package/dist/chat/ui/Composer.d.ts +1 -1
- package/dist/chat/ui/Composer.js +117 -906
- package/dist/chat/ui/ComposerInput.js +5 -4
- package/dist/chat/ui/EditDiff.js +3 -2
- package/dist/chat/ui/GoalDock.d.ts +9 -0
- package/dist/chat/ui/GoalDock.js +63 -0
- package/dist/chat/ui/LimitState.js +5 -3
- package/dist/chat/ui/Markdown.d.ts +2 -1
- package/dist/chat/ui/Markdown.js +70 -31
- package/dist/chat/ui/MermaidBlock.d.ts +3 -0
- package/dist/chat/ui/MermaidBlock.js +73 -0
- package/dist/chat/ui/MessageActions.js +12 -7
- package/dist/chat/ui/Pickers.js +6 -5
- package/dist/chat/ui/PromptComposer.js +4 -3
- package/dist/chat/ui/RowTime.d.ts +3 -0
- package/dist/chat/ui/RowTime.js +12 -0
- package/dist/chat/ui/RunSettings.js +5 -4
- package/dist/chat/ui/ShellCommand.d.ts +3 -0
- package/dist/chat/ui/ShellCommand.js +110 -0
- package/dist/chat/ui/SubagentInfo.js +4 -3
- package/dist/chat/ui/SubagentStopButton.js +4 -3
- package/dist/chat/ui/SubagentTimeline.js +4 -1
- package/dist/chat/ui/Timeline.js +70 -40
- package/dist/chat/ui/TimelineMenu.d.ts +2 -1
- package/dist/chat/ui/TimelineMenu.js +18 -8
- package/dist/chat/ui/UnifiedDiff.js +3 -2
- package/dist/chat/ui/VisualFrame.js +6 -7
- package/dist/chat/ui/code-theme.d.ts +2 -1
- package/dist/chat/ui/code-theme.js +4 -4
- package/dist/chat/ui/composer/ComposerControls.d.ts +27 -0
- package/dist/chat/ui/composer/ComposerControls.js +34 -0
- package/dist/chat/ui/composer/ComposerMenu.d.ts +4 -0
- package/dist/chat/ui/composer/ComposerMenu.js +46 -0
- package/dist/chat/ui/composer/DraftParts.d.ts +18 -0
- package/dist/chat/ui/composer/DraftParts.js +25 -0
- package/dist/chat/ui/composer/QueuedMessages.d.ts +10 -0
- package/dist/chat/ui/composer/QueuedMessages.js +20 -0
- package/dist/chat/ui/composer/key-handler.d.ts +20 -0
- package/dist/chat/ui/composer/key-handler.js +150 -0
- package/dist/chat/ui/composer/keys.d.ts +7 -2
- package/dist/chat/ui/composer/keys.js +19 -9
- package/dist/chat/ui/composer/placeholder.d.ts +9 -0
- package/dist/chat/ui/composer/placeholder.js +38 -0
- package/dist/chat/ui/composer/use-chat-controls.d.ts +28 -0
- package/dist/chat/ui/composer/use-chat-controls.js +112 -0
- package/dist/chat/ui/composer/use-compaction-offer.d.ts +2 -0
- package/dist/chat/ui/composer/use-compaction-offer.js +23 -0
- package/dist/chat/ui/composer/use-composer-draft.d.ts +14 -0
- package/dist/chat/ui/composer/use-composer-draft.js +39 -0
- package/dist/chat/ui/composer/use-composer-menus.d.ts +52 -0
- package/dist/chat/ui/composer/use-composer-menus.js +232 -0
- package/dist/chat/ui/composer/use-composer-submit.d.ts +32 -0
- package/dist/chat/ui/composer/use-composer-submit.js +113 -0
- package/dist/chat/ui/composer/use-draft-files.d.ts +27 -0
- package/dist/chat/ui/composer/use-draft-files.js +111 -0
- package/dist/chat/ui/composer/use-notice.d.ts +6 -0
- package/dist/chat/ui/composer/use-notice.js +18 -0
- package/dist/chat/ui/composer/use-pending-requests.d.ts +2 -0
- package/dist/chat/ui/composer/use-pending-requests.js +22 -0
- package/dist/chat/ui/composer/use-prompt-history.d.ts +8 -0
- package/dist/chat/ui/composer/use-prompt-history.js +57 -0
- package/dist/chat/ui/composer/use-prompt-hold.d.ts +6 -0
- package/dist/chat/ui/composer/use-prompt-hold.js +48 -0
- package/dist/chat/ui/composer/use-take-back.d.ts +13 -0
- package/dist/chat/ui/composer/use-take-back.js +82 -0
- package/dist/chat/ui/diff/EditorEditDiff.js +4 -3
- package/dist/chat/ui/diff/LegacyDiffPool.js +3 -2
- package/dist/chat/ui/diff/LegacyEditDiff.js +3 -2
- package/dist/chat/ui/diff/LegacyUnifiedDiff.js +3 -2
- package/dist/chat/ui/diff/syntax-runtime.d.ts +2 -1
- package/dist/chat/ui/diff/syntax-runtime.js +9 -5
- package/dist/chat/ui/diff-syntax.d.ts +2 -2
- package/dist/chat/ui/diff-syntax.js +1 -2
- package/dist/chat/ui/diff-theme.js +10 -9
- package/dist/chat/ui/file-links.d.ts +1 -1
- package/dist/chat/ui/file-links.js +7 -5
- package/dist/chat/ui/highlighter.d.ts +27 -0
- package/dist/chat/ui/highlighter.js +151 -0
- package/dist/chat/ui/index.d.ts +67 -0
- package/dist/chat/ui/index.js +64 -0
- package/dist/chat/ui/intelligent-ui/UiReply.js +3 -2
- package/dist/chat/ui/intelligent-ui/renderers/content.js +3 -2
- package/dist/chat/ui/intelligent-ui/use-ui-links.js +2 -2
- package/dist/chat/ui/intelligent-ui/use-ui-queries.js +4 -3
- package/dist/chat/ui/markdown-blocks.d.ts +8 -0
- package/dist/chat/ui/markdown-blocks.js +260 -12
- package/dist/chat/ui/mermaid.d.ts +23 -0
- package/dist/chat/ui/mermaid.js +93 -0
- package/dist/chat/ui/rows/ForksRow.js +5 -3
- package/dist/chat/ui/rows/GeneratedImageRow.js +6 -4
- package/dist/chat/ui/rows/MessageRows.d.ts +4 -0
- package/dist/chat/ui/rows/MessageRows.js +36 -13
- package/dist/chat/ui/rows/Rows.js +3 -1
- package/dist/chat/ui/rows/SubagentRow.js +3 -2
- package/dist/chat/ui/rows/VisualRow.js +3 -2
- package/dist/chat/ui/rows/WorkRows.d.ts +6 -3
- package/dist/chat/ui/rows/WorkRows.js +57 -20
- package/dist/chat/ui/rows/WorkflowRow.js +3 -2
- package/dist/chat/ui/timestamp-format.d.ts +1 -0
- package/dist/chat/ui/timestamp-format.js +2 -0
- package/dist/chat/ui/use-diff-source-tokens.js +6 -3
- package/dist/chat/ui/visual-theme.js +3 -2
- package/dist/host.d.ts +9 -6
- package/dist/host.js +19 -8
- package/dist/index.d.ts +8 -0
- package/dist/index.js +9 -0
- package/dist/locales/en/agent-chat.d.ts +658 -0
- package/dist/locales/en/agent-chat.js +657 -0
- package/dist/locales/en/agent-prompts.d.ts +44 -0
- package/dist/locales/en/agent-prompts.js +43 -0
- package/dist/locales/en/agent-providers.d.ts +132 -0
- package/dist/locales/en/agent-providers.js +131 -0
- package/dist/locales/en/agent-usage.d.ts +132 -0
- package/dist/locales/en/agent-usage.js +131 -0
- package/dist/locales/nl/agent-chat.d.ts +658 -0
- package/dist/locales/nl/agent-chat.js +657 -0
- package/dist/locales/nl/agent-prompts.d.ts +44 -0
- package/dist/locales/nl/agent-prompts.js +43 -0
- package/dist/locales/nl/agent-providers.d.ts +132 -0
- package/dist/locales/nl/agent-providers.js +131 -0
- package/dist/locales/nl/agent-usage.d.ts +132 -0
- package/dist/locales/nl/agent-usage.js +131 -0
- package/dist/locales.js +9 -8
- package/dist/prompts/index.d.ts +11 -0
- package/dist/prompts/index.js +12 -0
- package/dist/prompts/logic/subjects.d.ts +2 -1
- package/dist/prompts/logic/subjects.js +6 -4
- package/dist/prompts/ui/ApprovalBody.js +6 -4
- package/dist/prompts/ui/PromptCard.js +5 -4
- package/dist/prompts/ui/PromptView.js +3 -2
- package/dist/prompts/ui/QuestionBody.js +3 -2
- package/dist/providers/AccountColors.js +2 -2
- package/dist/providers/AccountDetail.js +7 -6
- package/dist/providers/AccountVariables.js +3 -1
- package/dist/providers/AddAccountForm.js +3 -2
- package/dist/providers/ProvidersPane.js +4 -3
- package/dist/providers/account-actions.d.ts +4 -3
- package/dist/providers/account-actions.js +6 -7
- package/dist/providers/index.d.ts +16 -0
- package/dist/providers/index.js +17 -0
- package/dist/providers/provider-abilities.d.ts +2 -1
- package/dist/providers/provider-abilities.js +4 -0
- package/dist/state/chats.js +4 -2
- package/dist/state/index.d.ts +4 -0
- package/dist/state/index.js +5 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +2 -0
- package/dist/{prompts/logic/prompts.fixtures.d.ts → testing/prompts.d.ts} +1 -1
- package/dist/theme.css +13 -3
- package/dist/usage/UsageBreakdown.js +3 -2
- package/dist/usage/UsageLimits.js +4 -3
- package/dist/usage/UsagePage.js +3 -2
- package/dist/usage/index.d.ts +15 -0
- package/dist/usage/index.js +15 -0
- package/package.json +53 -1031
- package/src/agents/AccountDot.tsx +3 -1
- package/src/agents/accounts.ts +6 -6
- package/src/chat/actions.ts +8 -5
- package/src/chat/bookmarks.ts +23 -14
- package/src/chat/chat-client.ts +8 -0
- package/src/chat/composer-send.ts +27 -0
- package/src/chat/forks.ts +27 -6
- package/src/chat/index.ts +98 -0
- package/src/chat/logic/embedded-code.ts +409 -0
- package/src/chat/logic/fork.ts +1 -0
- package/src/chat/logic/goal.ts +33 -0
- package/src/chat/logic/scrubber.ts +6 -5
- package/src/chat/logic/thought.ts +40 -0
- package/src/chat/logic/thread-cards.ts +2 -0
- package/src/chat/logic/timeline.ts +25 -1
- package/src/chat/logic/tool-catalog.ts +5 -1
- package/src/chat/logic/tools.ts +76 -0
- package/src/chat/preferences.ts +26 -3
- package/src/chat/reading-width.ts +39 -0
- package/src/chat/send-key.ts +38 -0
- package/src/chat/ui/BookmarkMarker.tsx +4 -2
- package/src/chat/ui/BookmarkSubmenu.tsx +4 -3
- package/src/chat/ui/ChatActivity.tsx +14 -10
- package/src/chat/ui/ChatReferenceChip.tsx +3 -4
- package/src/chat/ui/CodeBlock.tsx +33 -124
- package/src/chat/ui/Composer.tsx +162 -1324
- package/src/chat/ui/ComposerInput.tsx +5 -4
- package/src/chat/ui/EditDiff.tsx +3 -2
- package/src/chat/ui/GoalDock.tsx +104 -0
- package/src/chat/ui/LimitState.tsx +5 -3
- package/src/chat/ui/Markdown.tsx +113 -39
- package/src/chat/ui/MermaidBlock.tsx +119 -0
- package/src/chat/ui/MessageActions.tsx +13 -8
- package/src/chat/ui/Pickers.tsx +8 -7
- package/src/chat/ui/PromptComposer.tsx +4 -3
- package/src/chat/ui/RowTime.tsx +21 -0
- package/src/chat/ui/RunSettings.tsx +9 -4
- package/src/chat/ui/ShellCommand.tsx +134 -0
- package/src/chat/ui/SubagentInfo.tsx +4 -3
- package/src/chat/ui/SubagentStopButton.tsx +4 -3
- package/src/chat/ui/SubagentTimeline.tsx +5 -1
- package/src/chat/ui/Timeline.tsx +167 -105
- package/src/chat/ui/TimelineMenu.tsx +42 -7
- package/src/chat/ui/UnifiedDiff.tsx +3 -2
- package/src/chat/ui/VisualFrame.tsx +6 -7
- package/src/chat/ui/code-theme.ts +4 -4
- package/src/chat/ui/composer/ComposerControls.tsx +147 -0
- package/src/chat/ui/composer/ComposerMenu.tsx +138 -0
- package/src/chat/ui/composer/DraftParts.tsx +86 -0
- package/src/chat/ui/composer/QueuedMessages.tsx +64 -0
- package/src/chat/ui/composer/key-handler.ts +174 -0
- package/src/chat/ui/composer/keys.ts +32 -10
- package/src/chat/ui/composer/placeholder.ts +48 -0
- package/src/chat/ui/composer/use-chat-controls.ts +134 -0
- package/src/chat/ui/composer/use-compaction-offer.ts +25 -0
- package/src/chat/ui/composer/use-composer-draft.ts +71 -0
- package/src/chat/ui/composer/use-composer-menus.ts +289 -0
- package/src/chat/ui/composer/use-composer-submit.ts +163 -0
- package/src/chat/ui/composer/use-draft-files.ts +135 -0
- package/src/chat/ui/composer/use-notice.ts +28 -0
- package/src/chat/ui/composer/use-pending-requests.ts +23 -0
- package/src/chat/ui/composer/use-prompt-history.ts +76 -0
- package/src/chat/ui/composer/use-prompt-hold.ts +58 -0
- package/src/chat/ui/composer/use-take-back.ts +91 -0
- package/src/chat/ui/diff/DiffFallbackBoundary.tsx +2 -2
- package/src/chat/ui/diff/EditorEditDiff.tsx +4 -3
- package/src/chat/ui/diff/LegacyDiffPool.tsx +3 -2
- package/src/chat/ui/diff/LegacyEditDiff.tsx +3 -2
- package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +3 -2
- package/src/chat/ui/diff/syntax-runtime.ts +10 -5
- package/src/chat/ui/diff-syntax.ts +2 -2
- package/src/chat/ui/diff-theme.ts +10 -9
- package/src/chat/ui/file-links.ts +7 -5
- package/src/chat/ui/highlighter.ts +185 -0
- package/src/chat/ui/index.ts +109 -0
- package/src/chat/ui/intelligent-ui/UiReply.tsx +5 -4
- package/src/chat/ui/intelligent-ui/renderers/content.tsx +3 -2
- package/src/chat/ui/intelligent-ui/use-ui-links.ts +2 -2
- package/src/chat/ui/intelligent-ui/use-ui-queries.ts +4 -3
- package/src/chat/ui/markdown-blocks.ts +300 -12
- package/src/chat/ui/mermaid.ts +118 -0
- package/src/chat/ui/rows/ForksRow.tsx +5 -3
- package/src/chat/ui/rows/GeneratedImageRow.tsx +6 -4
- package/src/chat/ui/rows/MessageRows.tsx +73 -18
- package/src/chat/ui/rows/Rows.tsx +14 -1
- package/src/chat/ui/rows/SubagentRow.tsx +3 -2
- package/src/chat/ui/rows/VisualRow.tsx +3 -2
- package/src/chat/ui/rows/WorkRows.tsx +148 -47
- package/src/chat/ui/rows/WorkflowRow.tsx +3 -2
- package/src/chat/ui/timestamp-format.ts +2 -0
- package/src/chat/ui/use-diff-source-tokens.ts +10 -4
- package/src/chat/ui/visual-theme.ts +3 -2
- package/src/host.ts +31 -15
- package/src/index.ts +49 -0
- package/src/locales/en/agent-chat.ts +657 -0
- package/src/locales/en/agent-prompts.ts +43 -0
- package/src/locales/en/agent-providers.ts +131 -0
- package/src/locales/en/agent-usage.ts +131 -0
- package/src/locales/nl/agent-chat.ts +657 -0
- package/src/locales/nl/agent-prompts.ts +43 -0
- package/src/locales/nl/agent-providers.ts +131 -0
- package/src/locales/nl/agent-usage.ts +132 -0
- package/src/locales.ts +9 -8
- package/src/prompts/index.ts +35 -0
- package/src/prompts/logic/subjects.ts +12 -4
- package/src/prompts/ui/ApprovalBody.tsx +6 -4
- package/src/prompts/ui/PromptCard.tsx +11 -4
- package/src/prompts/ui/PromptView.tsx +3 -2
- package/src/prompts/ui/QuestionBody.tsx +3 -2
- package/src/providers/AccountColors.tsx +2 -2
- package/src/providers/AccountDetail.tsx +13 -7
- package/src/providers/AccountVariables.tsx +3 -1
- package/src/providers/AddAccountForm.tsx +3 -2
- package/src/providers/ProvidersPane.tsx +4 -3
- package/src/providers/account-actions.ts +7 -7
- package/src/providers/index.ts +17 -0
- package/src/providers/provider-abilities.ts +6 -1
- package/src/state/chats.ts +4 -2
- package/src/state/index.ts +39 -0
- package/src/testing/index.ts +2 -0
- package/src/{prompts/logic/prompts.fixtures.ts → testing/prompts.ts} +1 -1
- package/src/theme.css +13 -3
- package/src/usage/UsageBreakdown.tsx +3 -2
- package/src/usage/UsageLimits.tsx +4 -3
- package/src/usage/UsagePage.tsx +3 -2
- package/src/usage/index.ts +16 -0
- package/dist/locales/en/agent-chat.json +0 -597
- package/dist/locales/en/agent-prompts.json +0 -43
- package/dist/locales/en/agent-providers.json +0 -131
- package/dist/locales/en/agent-usage.json +0 -131
- package/dist/locales/nl/agent-chat.json +0 -597
- package/dist/locales/nl/agent-prompts.json +0 -43
- package/dist/locales/nl/agent-providers.json +0 -131
- package/dist/locales/nl/agent-usage.json +0 -131
- package/src/locales/en/agent-chat.json +0 -597
- package/src/locales/en/agent-prompts.json +0 -43
- package/src/locales/en/agent-providers.json +0 -131
- package/src/locales/en/agent-usage.json +0 -131
- package/src/locales/nl/agent-chat.json +0 -597
- package/src/locales/nl/agent-prompts.json +0 -43
- package/src/locales/nl/agent-providers.json +0 -131
- package/src/locales/nl/agent-usage.json +0 -131
- /package/dist/{prompts/logic/prompts.fixtures.js → testing/prompts.js} +0 -0
|
@@ -10,8 +10,8 @@ import { modelName, shortModelName } from '../../agents/model-name.js';
|
|
|
10
10
|
import { CONTEXT_OPTION, contextFraction, contextSegments, orderOptions } from '../logic/context-usage.js';
|
|
11
11
|
import { RUNTIME_MODES } from '../runtime-modes.js';
|
|
12
12
|
import { formatClock, formatWeekdayClock, isSameDay, formatPercent, formatTokens } from '@adecore/ui/format';
|
|
13
|
-
import { Icon, Menu, Tooltip, useNow } from '@adecore/ui';
|
|
14
|
-
import {
|
|
13
|
+
import { Icon, Menu, Pill, Tooltip, useNow } from '@adecore/ui';
|
|
14
|
+
import { useChatHost } from '../../host.js';
|
|
15
15
|
import { useUsageLimits } from '../../usage/limits.js';
|
|
16
16
|
const MINUTE_MS = 60_000;
|
|
17
17
|
const MODE_ICONS = {
|
|
@@ -87,7 +87,7 @@ export function RunSettings({ disabled = false, providers, provider, selection,
|
|
|
87
87
|
const slash = value.indexOf('/');
|
|
88
88
|
onModel(value.slice(0, slash), value.slice(slash + 1));
|
|
89
89
|
};
|
|
90
|
-
const modelGroups = (groups) => groups.map(({ entry, models }) => (_jsxs(Menu.Group, { children: [grouped && (_jsxs(Menu.GroupLabel, { className: "flex items-center gap-1.5", children: [_jsx(AgentIcon, { kind: entry.kind, size: 12 }), entry.name] })), models.map((row) => (_jsxs(Menu.RadioItem, { value: modelValue(entry.kind, row.slug), closeOnClick: true, children: [_jsx(AgentIcon, { kind: entry.kind, size: 14, className: "shrink-0" }), _jsx("span", { className: "min-w-0 truncate", children: row.name }), row.badge && _jsx(
|
|
90
|
+
const modelGroups = (groups) => groups.map(({ entry, models }) => (_jsxs(Menu.Group, { children: [grouped && (_jsxs(Menu.GroupLabel, { className: "flex items-center gap-1.5", children: [_jsx(AgentIcon, { kind: entry.kind, size: 12 }), entry.name] })), models.map((row) => (_jsxs(Menu.RadioItem, { value: modelValue(entry.kind, row.slug), closeOnClick: true, children: [_jsx(AgentIcon, { kind: entry.kind, size: 14, className: "shrink-0" }), _jsx("span", { className: "min-w-0 truncate", children: row.name }), row.badge && (_jsx(Pill, { tone: "accent", shape: "tag", size: "sm", children: row.badge.charAt(0).toLocaleUpperCase() + row.badge.slice(1) }))] }, row.slug)))] }, entry.kind)));
|
|
91
91
|
return (_jsxs(Menu.Root, { open: open, onOpenChange: onOpenChange, children: [_jsx(Tooltip, { label: permissionHint ?? t('pickers.settings.title'), kbd: "/model", children: _jsxs(Menu.Trigger, { disabled: disabled, className: clsx('flex h-8 min-w-0 items-center gap-2 rounded-full border pr-3 pl-2.5 text-xs whitespace-nowrap text-text-muted @max-xl/composer:gap-1.5 @max-sm/composer:pr-2.5', open ? 'border-accent bg-accent-soft' : 'border-border hover:bg-surface-hover'), children: [_jsx(AgentIcon, { kind: provider, size: 14, className: "shrink-0" }), _jsxs("span", { className: "flex min-w-0 items-center gap-2 overflow-hidden @max-xl/composer:gap-1.5", children: [_jsx("span", { className: "truncate font-medium text-text @max-xl/composer:hidden", children: current }), _jsx("span", { className: "hidden truncate font-medium text-text @max-xl/composer:inline", children: short }), account !== null && (_jsxs("span", { className: "flex min-w-0 items-center gap-2 @max-xl/composer:gap-1.5", children: [_jsx("span", { className: "shrink-0 text-text-faint", children: "\u00B7" }), _jsx(AccountDot, { color: account.choice.current?.account.color }), _jsx("span", { className: "truncate @max-xl/composer:hidden", children: account.choice.current === null ? account.choice.currentId : account.choice.nameOf(account.choice.current) })] })), summary.map((entry) => (_jsxs("span", { className: clsx('flex shrink-0 items-center gap-2 @max-xl/composer:gap-1.5', !entry.ring && '@max-xl/composer:hidden'), children: [_jsx("span", { className: "text-text-faint", children: "\u00B7" }), entry.ring && _jsx(ContextRing, { usage: usage }), _jsx("span", { className: clsx(entry.ring && '@max-sm/composer:hidden'), children: entry.label })] }, entry.id))), _jsx("span", { className: "shrink-0 text-text-faint", children: "\u00B7" }), _jsxs("span", { className: clsx('flex shrink-0 items-center gap-1', fullAccess && 'text-status-needs-you'), children: [_jsx(Icon, { icon: MODE_ICONS[shownMode], size: 12, className: "shrink-0" }), _jsx("span", { children: effectiveRuntimeMode === undefined && permissionMode !== undefined ? permissionMode : t(`modes.${shownMode}.short`) }), permissionFallback && _jsx(Icon, { icon: TriangleAlert, size: 12, className: "shrink-0 text-status-needs-you" })] })] }), _jsx(Icon, { icon: open ? ChevronUp : ChevronDown, size: 12, className: "shrink-0 @max-sm/composer:hidden" })] }) }), _jsxs(Menu.Popup, { side: "top", sideOffset: 8, align: "start", className: "w-80", children: [providers.length === 0 && _jsx("div", { className: "px-2.5 py-3 text-xs text-text-faint", children: t('pickers.model.noProvider') }), _jsx(Menu.RadioGroup, { value: chosenModel, onValueChange: chooseModel, children: modelGroups(live) }), legacy.length > 0 && (_jsxs(Menu.SubmenuRoot, { children: [_jsxs(Menu.SubmenuTrigger, { className: "text-text-muted", children: [_jsx("span", { className: "min-w-0 grow truncate", children: t('pickers.model.legacyGroup', { count: legacy.reduce((count, group) => count + group.models.length, 0) }) }), chosenLegacy && _jsx(Menu.Hint, { className: "truncate", children: chosenLegacy.name })] }), _jsx(Menu.Popup, { className: "min-w-56", children: _jsx(Menu.RadioGroup, { value: chosenModel, onValueChange: chooseModel, children: modelGroups(legacy) }) })] })), _jsx(Menu.Separator, {}), account !== null && _jsx(AccountSubmenu, { provider: provider, account: account }), _jsxs(Menu.SubmenuRoot, { children: [_jsxs(Menu.SubmenuTrigger, { className: "text-text-muted", children: [_jsx(Icon, { icon: MODE_ICONS[runtimeMode], size: 14, className: clsx('shrink-0', fullAccess ? 'text-status-needs-you' : 'text-text-faint') }), _jsx("span", { className: "min-w-0 grow truncate", children: t('pickers.mode.label') }), _jsx(Menu.Hint, { className: "truncate", children: t(`modes.${runtimeMode}.label`) })] }), _jsxs(Menu.Popup, { className: "w-72", children: [permissionHint !== null && _jsx("p", { className: "px-2.5 py-2 text-xs text-text-muted", children: permissionHint }), _jsx(Menu.RadioGroup, { value: runtimeMode, onValueChange: (mode) => onMode(mode), children: RUNTIME_MODES.map((mode) => (_jsxs(Menu.RadioItem, { value: mode, className: "items-start", children: [_jsx("span", { className: "grid h-lh w-4 shrink-0 place-items-center", children: _jsx(Icon, { icon: MODE_ICONS[mode], size: 14, className: mode === 'full-access' ? 'text-status-needs-you' : 'text-text-faint' }) }), _jsxs("span", { className: "flex min-w-0 grow flex-col", children: [t(`modes.${mode}.label`), _jsx("span", { className: "text-xs text-text-faint", children: t(`modes.${mode}.hint`) })] })] }, mode))) })] })] }), options.map((option) => (_jsx(OptionRow, { option: option, selection: selection, onChange: (value) => onOption(option.id, value) }, option.id))), (hasContextOption || standaloneContext) && (_jsxs(_Fragment, { children: [_jsx(Menu.Separator, {}), _jsx(ContextUsage, { usage: usage, disabled: compactDisabled, onCompact: onCompact })] }))] })] }));
|
|
92
92
|
}
|
|
93
93
|
/* Red where a window is nearly spent, amber where it is worth knowing, as the usage bars have it. */
|
|
@@ -104,9 +104,10 @@ function sessionTone(used) {
|
|
|
104
104
|
* the conversation goes along to those. A switch applies from the next turn.
|
|
105
105
|
*/
|
|
106
106
|
function AccountSubmenu({ provider, account }) {
|
|
107
|
+
const host = useChatHost();
|
|
107
108
|
const { t } = useTranslation('agent-chat');
|
|
108
109
|
const { choice, started, onPick } = account;
|
|
109
|
-
return (_jsxs(Menu.SubmenuRoot, { children: [_jsxs(Menu.SubmenuTrigger, { className: "text-text-muted", children: [_jsx("span", { className: "grid w-3.5 shrink-0 place-items-center", children: _jsx(AccountDot, { color: choice.current?.account.color }) }), _jsx("span", { className: "min-w-0 grow truncate", children: t('pickers.account.label') }), _jsx(Menu.Hint, { className: "truncate", children: choice.current === null ? choice.currentId : choice.nameOf(choice.current) })] }), _jsxs(Menu.Popup, { className: "min-w-64", children: [_jsxs(Menu.Group, { children: [_jsxs(Menu.GroupLabel, { className: "flex items-center gap-1.5", children: [_jsx(AgentIcon, { kind: provider, size: 12 }), choice.providerName] }), _jsx(Menu.RadioGroup, { value: choice.currentId, onValueChange: (id) => onPick(id), children: _jsx(AccountRows, { choice: choice, locked: (id) => started && !canContinueOn(choice.accounts, provider, choice.currentId, id) }) })] }), _jsx(Menu.Separator, {}), _jsxs(Menu.Item, { className: "text-text-muted", onClick: () =>
|
|
110
|
+
return (_jsxs(Menu.SubmenuRoot, { children: [_jsxs(Menu.SubmenuTrigger, { className: "text-text-muted", children: [_jsx("span", { className: "grid w-3.5 shrink-0 place-items-center", children: _jsx(AccountDot, { color: choice.current?.account.color }) }), _jsx("span", { className: "min-w-0 grow truncate", children: t('pickers.account.label') }), _jsx(Menu.Hint, { className: "truncate", children: choice.current === null ? choice.currentId : choice.nameOf(choice.current) })] }), _jsxs(Menu.Popup, { className: "min-w-64", children: [_jsxs(Menu.Group, { children: [_jsxs(Menu.GroupLabel, { className: "flex items-center gap-1.5", children: [_jsx(AgentIcon, { kind: provider, size: 12 }), choice.providerName] }), _jsx(Menu.RadioGroup, { value: choice.currentId, onValueChange: (id) => onPick(id), children: _jsx(AccountRows, { choice: choice, locked: (id) => started && !canContinueOn(choice.accounts, provider, choice.currentId, id) }) })] }), _jsx(Menu.Separator, {}), _jsxs(Menu.Item, { className: "text-text-muted", onClick: () => host.openSettings('providers'), children: [_jsx(Icon, { icon: Settings, size: 14, className: "shrink-0 text-text-faint" }), t('pickers.account.manage')] })] })] }));
|
|
110
111
|
}
|
|
111
112
|
/* Mounted only while the submenu is open, since asking for the plan windows starts a CLI per account. */
|
|
112
113
|
function AccountRows({ choice, locked }) {
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { EMBEDDED_CODE_LIMIT, SHELL_LANGUAGE, embeddedCode } from '../logic/embedded-code.js';
|
|
4
|
+
import { useChatHost } from '../../host.js';
|
|
5
|
+
import { useCodeTheme } from './code-theme.js';
|
|
6
|
+
import { tokenStyle, useCodeHighlighter } from './highlighter.js';
|
|
7
|
+
// Rows redraw often and a thread holds many commands, so each is tokenized once per theme.
|
|
8
|
+
const CACHE_LIMIT = 300;
|
|
9
|
+
const cache = new Map();
|
|
10
|
+
/* The runs of `code` in one language, placed at `offset`; a run in the theme's own foreground takes the color around it. */
|
|
11
|
+
function runsOf(highlighter, code, language, theme, offset) {
|
|
12
|
+
const fg = highlighter.getTheme(theme).fg.toLowerCase();
|
|
13
|
+
const { tokens } = highlighter.codeToTokens(code, { lang: language, theme });
|
|
14
|
+
return tokens.flat().map((token) => ({
|
|
15
|
+
start: offset + token.offset,
|
|
16
|
+
end: offset + token.offset + token.content.length,
|
|
17
|
+
content: token.content,
|
|
18
|
+
color: token.color?.toLowerCase() === fg ? undefined : token.color,
|
|
19
|
+
fontStyle: token.fontStyle
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
/* `base` with `[start, end)` cut out of it and `inner` in its place. */
|
|
23
|
+
function overlay(base, start, end, inner) {
|
|
24
|
+
const kept = [];
|
|
25
|
+
for (const run of base) {
|
|
26
|
+
if (run.end <= start || run.start >= end) {
|
|
27
|
+
kept.push(run);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (run.start < start) {
|
|
31
|
+
kept.push({ ...run, end: start, content: run.content.slice(0, start - run.start) });
|
|
32
|
+
}
|
|
33
|
+
if (run.end > end) {
|
|
34
|
+
kept.push({ ...run, start: end, content: run.content.slice(end - run.start) });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return [...kept, ...inner].sort((left, right) => left.start - right.start);
|
|
38
|
+
}
|
|
39
|
+
function languagesOf(command) {
|
|
40
|
+
return new Set([SHELL_LANGUAGE, ...embeddedCode(command).map(({ language }) => language)]);
|
|
41
|
+
}
|
|
42
|
+
/* The colored runs of a command, or null while a grammar or the theme is still on its way. */
|
|
43
|
+
function highlight(command, theme, code) {
|
|
44
|
+
const key = `${theme}\n${command}`;
|
|
45
|
+
const known = cache.get(key);
|
|
46
|
+
if (known !== undefined) {
|
|
47
|
+
// Touched entries move to the end, so the oldest falls out first.
|
|
48
|
+
cache.delete(key);
|
|
49
|
+
cache.set(key, known);
|
|
50
|
+
return known;
|
|
51
|
+
}
|
|
52
|
+
const embedded = embeddedCode(command);
|
|
53
|
+
const highlighter = code.ready([SHELL_LANGUAGE, ...embedded.map(({ language }) => language)], theme);
|
|
54
|
+
if (highlighter === null) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
let runs = runsOf(highlighter, command, code.language(SHELL_LANGUAGE), theme, 0);
|
|
58
|
+
for (const part of embedded) {
|
|
59
|
+
runs = overlay(runs, part.start, part.end, runsOf(highlighter, command.slice(part.start, part.end), code.language(part.language), theme, part.start));
|
|
60
|
+
}
|
|
61
|
+
cache.set(key, runs);
|
|
62
|
+
if (cache.size > CACHE_LIMIT) {
|
|
63
|
+
cache.delete(cache.keys().next().value);
|
|
64
|
+
}
|
|
65
|
+
return runs;
|
|
66
|
+
}
|
|
67
|
+
function draw(command, runs) {
|
|
68
|
+
const parts = [];
|
|
69
|
+
let at = 0;
|
|
70
|
+
for (const run of runs) {
|
|
71
|
+
// Shiki leaves the line breaks out of its tokens; they come from the command itself.
|
|
72
|
+
if (run.start > at) {
|
|
73
|
+
parts.push(command.slice(at, run.start));
|
|
74
|
+
}
|
|
75
|
+
const style = tokenStyle(run);
|
|
76
|
+
parts.push(style === undefined ? (run.content) : (_jsx("span", { style: style, children: run.content }, run.start)));
|
|
77
|
+
at = run.end;
|
|
78
|
+
}
|
|
79
|
+
if (at < command.length) {
|
|
80
|
+
parts.push(command.slice(at));
|
|
81
|
+
}
|
|
82
|
+
return parts;
|
|
83
|
+
}
|
|
84
|
+
/*
|
|
85
|
+
* A shell command in the colors of the code theme, with the code inside it (a `bash -lc` script, a
|
|
86
|
+
* `python3 -c` argument, a heredoc) in its own language. Plain text until the grammars are in, which
|
|
87
|
+
* changes no size; a command past the limit stays plain.
|
|
88
|
+
*/
|
|
89
|
+
export function ShellCommand({ command }) {
|
|
90
|
+
const theme = useCodeTheme();
|
|
91
|
+
const { custom } = useChatHost().code;
|
|
92
|
+
const highlighter = useCodeHighlighter();
|
|
93
|
+
// Bumped when a load settles, so the cache is asked again.
|
|
94
|
+
const [, setLoads] = useState(0);
|
|
95
|
+
const tooLong = command.length > EMBEDDED_CODE_LIMIT;
|
|
96
|
+
const runs = tooLong ? null : highlight(command, theme, highlighter);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (runs !== null || tooLong) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
let cancelled = false;
|
|
102
|
+
Promise.all([highlighter.loadTheme(theme, custom), ...[...languagesOf(command)].map((lang) => highlighter.loadLanguage(lang))])
|
|
103
|
+
.then(() => !cancelled && setLoads((count) => count + 1))
|
|
104
|
+
.catch(() => undefined);
|
|
105
|
+
return () => {
|
|
106
|
+
cancelled = true;
|
|
107
|
+
};
|
|
108
|
+
}, [highlighter, command, theme, custom, runs, tooLong]);
|
|
109
|
+
return _jsx(_Fragment, { children: runs === null ? command : draw(command, runs) });
|
|
110
|
+
}
|
|
@@ -8,21 +8,22 @@ import { SubagentActions, SubagentBreadcrumb } from './SubagentControls.js';
|
|
|
8
8
|
import { useNow } from '@adecore/ui';
|
|
9
9
|
import { useModelName } from '../../agents/model-name.js';
|
|
10
10
|
import { useChatRow } from '../../state/chats.js';
|
|
11
|
-
import {
|
|
11
|
+
import { useChatHost } from '../../host.js';
|
|
12
12
|
import { useChatScope } from '../../scope.js';
|
|
13
13
|
/*
|
|
14
14
|
* What a sub-agent is, over its conversation: the way down to it, its state and time, the model it runs on
|
|
15
15
|
* and what it spent, and the way out. Only the way down and out for a conversation whose row the thread lost.
|
|
16
16
|
*/
|
|
17
17
|
export function SubagentInfo({ chatId, toolUseId }) {
|
|
18
|
+
const host = useChatHost();
|
|
18
19
|
const { t } = useTranslation('agent-chat');
|
|
19
20
|
const { id } = useChatScope();
|
|
20
21
|
const item = useSubagentItem(chatId, toolUseId);
|
|
21
22
|
const provider = useChatRow(chatId, (row) => row?.info.provider);
|
|
22
|
-
const task =
|
|
23
|
+
const task = host.tasks.useTask(id, item === null ? null : taskIdOf(item));
|
|
23
24
|
const modelName = useModelName(provider, item?.model ?? '');
|
|
24
25
|
const now = useNow(1000, item?.status === 'running');
|
|
25
26
|
const word = item === null ? null : statusWordOf(item, task);
|
|
26
27
|
const facts = item === null ? [] : subagentFacts(item, item.model === undefined ? null : modelName);
|
|
27
|
-
return (_jsx("section", { "aria-label": t('subagents.info.label'), className: "shrink-0 border-b border-border
|
|
28
|
+
return (_jsx("section", { "aria-label": t('subagents.info.label'), className: "shrink-0 border-b border-border py-1.5 pr-1.5 pl-4", children: _jsxs("div", { className: "flex min-w-0 items-center gap-2 text-xs text-text-faint", children: [_jsx(SubagentBreadcrumb, { chatId: chatId, className: "max-w-1/3 shrink-0 font-medium" }), item !== null && word !== null && (_jsxs(_Fragment, { children: [_jsx(StatusIcon, { word: word }), _jsx("span", { className: "sr-only", children: t(`common.status.${word}`) }), _jsx("span", { className: "shrink-0 text-text-muted tabular-nums", children: entryTimeOf(item, task, now) })] })), facts.map((fact) => (_jsxs(Fragment, { children: [_jsx("span", { "aria-hidden": "true", children: "\u00B7" }), _jsx("span", { className: "min-w-0 truncate", children: fact })] }, fact))), _jsx("span", { className: "grow" }), _jsx(SubagentActions, { chatId: chatId })] }) }));
|
|
28
29
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { Square } from 'lucide-react';
|
|
4
|
-
import {
|
|
4
|
+
import { useChatHost } from '../../host.js';
|
|
5
5
|
import { useChatScope } from '../../scope.js';
|
|
6
6
|
import { useChatRow } from '../../state/chats.js';
|
|
7
7
|
import { useChatActions } from '../actions.js';
|
|
@@ -9,6 +9,7 @@ import { stopLabel, stopOf, subagentTitle } from '../subagent-list.js';
|
|
|
9
9
|
import { IconButton } from '@adecore/ui';
|
|
10
10
|
/* Stops one active sub-agent of a chat, or says nothing when stopping it is not on offer. */
|
|
11
11
|
export function SubagentStopButton({ chatId, item, size = 'xs', className }) {
|
|
12
|
+
const host = useChatHost();
|
|
12
13
|
const { t } = useTranslation('agent-chat');
|
|
13
14
|
const { id } = useChatScope();
|
|
14
15
|
const actions = useChatActions();
|
|
@@ -21,12 +22,12 @@ export function SubagentStopButton({ chatId, item, size = 'xs', className }) {
|
|
|
21
22
|
void actions.stopSubagent(chatId, item.toolUseId).catch((e) => {
|
|
22
23
|
const stale = e instanceof Error && 'code' in e && e.code === 'subagent-stale';
|
|
23
24
|
const description = stale ? t('subagents.stale') : e instanceof Error ? e.message : t('subagents.noAnswer');
|
|
24
|
-
|
|
25
|
+
host.notify({ kind: 'error', title: t('subagents.stopFailed'), description });
|
|
25
26
|
});
|
|
26
27
|
};
|
|
27
28
|
const run = () => {
|
|
28
29
|
if (stop === 'task' && item.childId !== undefined) {
|
|
29
|
-
|
|
30
|
+
host.confirm.stopTask(id, item.childId, subagentTitle(item), send);
|
|
30
31
|
return;
|
|
31
32
|
}
|
|
32
33
|
send();
|
|
@@ -18,6 +18,8 @@ import { FOLLOW_THRESHOLD_PX, rowRhythm } from './rows/row-rhythm.js';
|
|
|
18
18
|
import { useToggleSet } from './useToggleSet.js';
|
|
19
19
|
import { FileLinkContext } from './file-links.js';
|
|
20
20
|
import { useChatRow } from '../../state/chats.js';
|
|
21
|
+
import { useChatPreferences } from '../preferences.js';
|
|
22
|
+
import { readingWidthStyle } from '../reading-width.js';
|
|
21
23
|
import { useChatScope } from '../../scope.js';
|
|
22
24
|
import { EmptyState, ContextMenu, Icon } from '@adecore/ui';
|
|
23
25
|
const NO_TURNS = new Set();
|
|
@@ -95,10 +97,11 @@ export function SubagentTimeline({ chatId, toolUseId }) {
|
|
|
95
97
|
loadEarlier();
|
|
96
98
|
}
|
|
97
99
|
}, [rows, state.status, loadEarlier]);
|
|
100
|
+
const readingWidth = useChatPreferences((preferences) => preferences.readingWidth);
|
|
98
101
|
const openChild = (step) => {
|
|
99
102
|
show(openBelow(trail, step));
|
|
100
103
|
};
|
|
101
|
-
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsx(SubagentInfo, { chatId: chatId, toolUseId: toolUseId }), state.status === 'loading' && _jsx("div", { className: "chat-column-content px-4 pt-4 text-xs text-text-faint", children: t('subagents.loading') }), state.status === 'failed' && (_jsx(EmptyState, { icon: Bot, children: state.unsupported ? t('subagents.unsupported') : (state.error ?? t('subagents.unreadable')) })), state.status === 'ready' && (_jsx(FileLinkContext.Provider, { value: state.context?.cwd ?? cwd, children: _jsx(ReplyContext.Provider, { value: state.context ?? { provider: state.source === 'codex-thread' ? 'codex' : 'claude' }, children: _jsx("div", { ref: scrollRef, "data-file-cwd": state.context?.cwd ?? cwd ?? '', "data-file-scope-id": scope.id, className: "chat-thread relative min-h-0 grow overflow-auto px-4 pt-4 pb-3",
|
|
104
|
+
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", style: readingWidthStyle(readingWidth), children: [_jsx(SubagentInfo, { chatId: chatId, toolUseId: toolUseId }), state.status === 'loading' && _jsx("div", { className: "chat-column-content px-4 pt-4 text-xs text-text-faint", children: t('subagents.loading') }), state.status === 'failed' && (_jsx(EmptyState, { icon: Bot, children: state.unsupported ? t('subagents.unsupported') : (state.error ?? t('subagents.unreadable')) })), state.status === 'ready' && (_jsx(FileLinkContext.Provider, { value: state.context?.cwd ?? cwd, children: _jsx(ReplyContext.Provider, { value: state.context ?? { provider: state.source === 'codex-thread' ? 'codex' : 'claude' }, children: _jsx("div", { ref: scrollRef, "data-file-cwd": state.context?.cwd ?? cwd ?? '', "data-file-scope-id": scope.id, className: "chat-thread relative min-h-0 grow overflow-auto px-4 pt-4 pb-3",
|
|
102
105
|
// The thread keeps its reader in place itself when a page goes in above.
|
|
103
106
|
style: { overflowAnchor: 'none' }, onScroll: (event) => {
|
|
104
107
|
const element = event.currentTarget;
|
package/dist/chat/ui/Timeline.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
5
5
|
import clsx from 'clsx';
|
|
@@ -11,6 +11,8 @@ import { timedRowsOf, withTimedRows } from '../logic/thread-cards.js';
|
|
|
11
11
|
import { openFromMain, useSubagentTrail } from '../subagent-view.js';
|
|
12
12
|
import { registerItemJumper, registerMessageStepper, registerTimeline, registerTurnJumper, setTimelineAtEnd, wantsEarlier } from '../timeline-scroll.js';
|
|
13
13
|
import { useTimelineFlash } from '../timeline-flash.js';
|
|
14
|
+
import { useChatPreferences } from '../preferences.js';
|
|
15
|
+
import { readingMeasure, readingWidthStyle } from '../reading-width.js';
|
|
14
16
|
import { SCRUBBER_MIN_TICKS, STRIP_INSET_PX, STRIP_WIDTH_PX, messagesInView, stepMessage, threadPaddingLeft, STRIP_CLEARANCE_PX, tickOfRow, ticksOf, ticksWithHits } from '../logic/scrubber.js';
|
|
15
17
|
import { EMPTY_TARGET, readTimelineTarget, revealUiReplyBlock, uiReplyJump, withCurrentText } from '../logic/timeline-target.js';
|
|
16
18
|
import { forkRefusal } from '../logic/fork.js';
|
|
@@ -30,7 +32,7 @@ import { Row } from './rows/Rows.js';
|
|
|
30
32
|
import { RowContainer } from './rows/RowContainer.js';
|
|
31
33
|
import { ReplyHeader } from './rows/MessageRows.js';
|
|
32
34
|
import { useChatRow, useChats } from '../../state/chats.js';
|
|
33
|
-
import {
|
|
35
|
+
import { useChatHost } from '../../host.js';
|
|
34
36
|
import { useChatScope } from '../../scope.js';
|
|
35
37
|
import { FileLinkContext } from './file-links.js';
|
|
36
38
|
import { AgentIcon } from '../../agents/AgentIcon.js';
|
|
@@ -49,11 +51,12 @@ function showRowAtTop(virtualizer, followRef, index) {
|
|
|
49
51
|
* and the folder it works in, so a first question can lean on all three.
|
|
50
52
|
*/
|
|
51
53
|
function EmptyThread({ chatId }) {
|
|
54
|
+
const host = useChatHost();
|
|
52
55
|
const { t } = useTranslation('agent-chat');
|
|
53
56
|
const info = useChatRow(chatId, (row) => row?.info ?? null);
|
|
54
57
|
const model = useModelName(info?.provider, info?.selection.model ?? '');
|
|
55
|
-
const sources =
|
|
56
|
-
const hidesFolder =
|
|
58
|
+
const sources = host.useContextSources(chatId);
|
|
59
|
+
const hidesFolder = host.useHidesFolder(chatId);
|
|
57
60
|
return (_jsx("div", { className: "flex grow items-center justify-center", children: _jsxs("div", { className: "flex max-w-sm flex-col items-center gap-3 px-6 py-8 text-center", children: [_jsx(EmptyState, { icon: info ? _jsx(AgentIcon, { kind: info.provider }) : undefined, className: "p-0", children: info ? t('timeline.empty.ready', { model }) : t('timeline.empty.ask') }), sources.length > 0 && (_jsxs("div", { className: "flex max-w-full flex-col items-center gap-1", children: [_jsx(SectionLabel, { children: t('timeline.empty.canRead') }), _jsx("span", { className: "max-w-full text-xs text-text-muted", children: sources.map((source) => source.title).join(', ') })] })), info?.cwd && !hidesFolder && (_jsxs("div", { className: "flex max-w-full flex-col items-center gap-1", children: [_jsx(SectionLabel, { children: t('timeline.empty.worksIn') }), _jsx("span", { className: "max-w-full font-mono text-xs break-all text-text-muted", children: info.cwd })] }))] }) }));
|
|
58
61
|
}
|
|
59
62
|
/*
|
|
@@ -66,11 +69,18 @@ function MarkableRow({ row, chatId, bookmark, children }) {
|
|
|
66
69
|
}
|
|
67
70
|
return (_jsxs(_Fragment, { children: [_jsx(BookmarkMarker, { chatId: chatId, itemId: row.id, bookmark: bookmark }), _jsxs("div", { className: "group/message", children: [children, _jsx(MessageActions, { chatId: chatId, row: row })] })] }));
|
|
68
71
|
}
|
|
72
|
+
/* Memoized with stable props, so a streamed word or a scroll draws only the rows it changed. */
|
|
73
|
+
const ThreadRow = memo(function ThreadRow({ row, previous, index, top, measure, chatId, flashNonce, bookmark, ...actions }) {
|
|
74
|
+
const header = replyHeader(row, previous);
|
|
75
|
+
return (_jsxs(RowContainer, { row: row, previous: previous, index: index, top: top, ref: measure, children: [flashNonce !== null && _jsx("span", { className: "chat-flash pointer-events-none absolute inset-0 rounded-lg", "aria-hidden": true }, flashNonce), header !== null && (_jsx("div", { className: "chat-column-content w-full", children: _jsx(ReplyHeader, { chatId: chatId, at: header.at }) })), _jsx(MarkableRow, { row: row, chatId: chatId, bookmark: bookmark, children: _jsx(Row, { row: row, chatId: chatId, ...actions }) })] }));
|
|
76
|
+
});
|
|
69
77
|
/*
|
|
70
78
|
* `overlay` stands in the thread's place and ends where the composer starts, so the thread keeps
|
|
71
79
|
* where it was underneath and the composer stays in reach for the prompts it answers.
|
|
72
80
|
*/
|
|
73
81
|
export function Timeline({ chatId, composer, overlay }) {
|
|
82
|
+
const host = useChatHost();
|
|
83
|
+
const { fork } = host;
|
|
74
84
|
const { t } = useTranslation('agent-chat');
|
|
75
85
|
const order = useChatRow(chatId, (row) => row?.order);
|
|
76
86
|
// The structure, not the items. A delta growing a reply must not derive every row again.
|
|
@@ -137,12 +147,12 @@ export function Timeline({ chatId, composer, overlay }) {
|
|
|
137
147
|
}
|
|
138
148
|
return deriveTimelineRows(order.map((id) => items[id]), { expandedGroups: groups.ids, expandedTurns: turns.ids, expandedSubagents: subagents.ids, activeTurnId, forkedTurns });
|
|
139
149
|
}, [order, items, groups.ids, turns.ids, subagents.ids, activeTurnId, forkedTurns]);
|
|
140
|
-
const cards =
|
|
141
|
-
const drawsVisuals =
|
|
150
|
+
const cards = host.useThreadCards(scopeId, chatId);
|
|
151
|
+
const drawsVisuals = host.visuals !== null;
|
|
142
152
|
// Where the part of the thread this client holds begins, while there is a page before it.
|
|
143
153
|
const heldFrom = cursor === null ? null : (items?.[order?.[0] ?? '']?.createdAt ?? null);
|
|
144
154
|
const rows = useMemo(() => withTimedRows(threadRows, timedRowsOf(cards, drawsVisuals ? (visuals ?? []) : null, heldFrom)), [threadRows, cards, drawsVisuals, visuals, heldFrom]);
|
|
145
|
-
const welcomes =
|
|
155
|
+
const welcomes = host.useWelcome(chatId);
|
|
146
156
|
const empty = rows.length === 0;
|
|
147
157
|
// A chat that took a turn before is one whose thread has not come in yet, not one to greet.
|
|
148
158
|
const welcome = welcomes && empty && (info === null || (info.usage.turns === 0 && info.activeTurnId === null));
|
|
@@ -208,17 +218,11 @@ export function Timeline({ chatId, composer, overlay }) {
|
|
|
208
218
|
overscan: 8,
|
|
209
219
|
scrollPaddingEnd: coveredHeight
|
|
210
220
|
});
|
|
211
|
-
// A change in what the last row says (a streaming delta) must also pull the view down.
|
|
212
|
-
const lastRow = rows[rows.length - 1];
|
|
213
|
-
const lastReplyLength = useChatRow(chatId, (row) => {
|
|
214
|
-
const item = lastRow?.kind === 'assistant' ? row?.items[lastRow.id] : undefined;
|
|
215
|
-
return item?.kind === 'assistant' ? item.text.length : null;
|
|
216
|
-
});
|
|
217
|
-
const tail = lastReplyLength ?? rows.length;
|
|
218
221
|
/*
|
|
219
|
-
* Rows are measured as they render, so the end moves while it is drawn (a diff that
|
|
220
|
-
* image that loads). `scrollToIndex` aims at where a row was when asked and fell
|
|
221
|
-
* scroller's own bottom is a fact, and a new total size says a row was measured again.
|
|
222
|
+
* Rows are measured as they render, so the end moves while it is drawn (a streamed line, a diff that
|
|
223
|
+
* highlights, an image that loads). `scrollToIndex` aims at where a row was when asked and fell
|
|
224
|
+
* short; the scroller's own bottom is a fact, and a new total size says a row was measured again.
|
|
225
|
+
* A word that does not add a line moves nothing, so the thread does not listen to the words.
|
|
222
226
|
*/
|
|
223
227
|
const totalSize = virtualizer.getTotalSize();
|
|
224
228
|
useLayoutEffect(() => {
|
|
@@ -229,7 +233,7 @@ export function Timeline({ chatId, composer, overlay }) {
|
|
|
229
233
|
// The composer is in the scroll flow, so its measured height is part of this end position.
|
|
230
234
|
element.scrollTop = element.scrollHeight;
|
|
231
235
|
scrollGeometry.current = { top: element.scrollTop, height: element.scrollHeight, viewport: element.clientHeight };
|
|
232
|
-
}, [rows.length,
|
|
236
|
+
}, [rows.length, totalSize, composerHeight, frame.height]);
|
|
233
237
|
// A page that went in above moves every row down by what it measures; the reader stays on the row they were on.
|
|
234
238
|
useLayoutEffect(() => {
|
|
235
239
|
const element = scrollRef.current;
|
|
@@ -261,7 +265,8 @@ export function Timeline({ chatId, composer, overlay }) {
|
|
|
261
265
|
const onMainAgent = trail.length === 0;
|
|
262
266
|
// A node on a canvas has no strip. It is too narrow to give up a column of its width.
|
|
263
267
|
const showsScrubber = frame.column && onMainAgent && ticks.length >= SCRUBBER_MIN_TICKS;
|
|
264
|
-
const
|
|
268
|
+
const readingWidth = useChatPreferences((preferences) => preferences.readingWidth);
|
|
269
|
+
const paddingLeft = threadPaddingLeft(frame.width, showsScrubber, readingMeasure(readingWidth));
|
|
265
270
|
/*
|
|
266
271
|
* Where each message starts, from the virtualizer's measurements (an estimate for a row it never
|
|
267
272
|
* drew), so reading the scroll position costs no layout.
|
|
@@ -271,8 +276,28 @@ export function Timeline({ chatId, composer, overlay }) {
|
|
|
271
276
|
const scrollTop = virtualizer.scrollOffset ?? 0;
|
|
272
277
|
const viewport = virtualizer.scrollRect?.height ?? 0;
|
|
273
278
|
const visibleHeight = Math.max(0, viewport - coveredHeight);
|
|
279
|
+
const visibleHeightRef = useRef(visibleHeight);
|
|
280
|
+
visibleHeightRef.current = visibleHeight;
|
|
274
281
|
const inView = showsScrubber ? messagesInView({ starts, scrollTop, visibleHeight }) : null;
|
|
275
|
-
|
|
282
|
+
/*
|
|
283
|
+
* The virtualizer draws again only when its range of rows changes, which a scroll inside one long
|
|
284
|
+
* row never does; the scroll itself redraws the strip once the messages in view are others.
|
|
285
|
+
*/
|
|
286
|
+
const [, redrawStrip] = useReducer((count) => count + 1, 0);
|
|
287
|
+
const stripRef = useRef({ starts, inView });
|
|
288
|
+
stripRef.current = { starts, inView };
|
|
289
|
+
const followStrip = (element) => {
|
|
290
|
+
const { starts: current, inView: shown } = stripRef.current;
|
|
291
|
+
if (shown === null) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const next = messagesInView({ starts: current, scrollTop: element.scrollTop, visibleHeight: visibleHeightRef.current });
|
|
295
|
+
if (next?.first !== shown.first || next?.last !== shown.last) {
|
|
296
|
+
redrawStrip();
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
// One object while what it holds stays, so a find that keys an effect on it does not run it for every scroll.
|
|
300
|
+
const findOptions = useMemo(() => ({
|
|
276
301
|
chatId,
|
|
277
302
|
frame: frameRef,
|
|
278
303
|
thread: threadRef,
|
|
@@ -291,14 +316,15 @@ export function Timeline({ chatId, composer, overlay }) {
|
|
|
291
316
|
// A row already on screen stays put; the find scrolls to the match itself once it is drawn.
|
|
292
317
|
scrollToRow: (index) => {
|
|
293
318
|
const top = scrollRef.current?.scrollTop ?? 0;
|
|
294
|
-
const bottom = top +
|
|
319
|
+
const bottom = top + visibleHeightRef.current;
|
|
295
320
|
if (!virtualizer.getVirtualItems().some((virtualRow) => virtualRow.index === index && virtualRow.end > top && virtualRow.start < bottom)) {
|
|
296
321
|
virtualizer.scrollToIndex(index, { align: 'center' });
|
|
297
322
|
}
|
|
298
323
|
},
|
|
299
324
|
coveredHeight: () => coveredHeightRef.current,
|
|
300
325
|
stopFollowing
|
|
301
|
-
});
|
|
326
|
+
}), [chatId, onMainAgent, rows, items, order, virtualizer, turns.add, groups.add, subagents.add, stopFollowing]);
|
|
327
|
+
const chatFind = host.useTimelineFind(findOptions);
|
|
302
328
|
const searching = chatFind.open;
|
|
303
329
|
// A find covers the whole thread, so every page before this one comes in while it is open.
|
|
304
330
|
useEffect(() => {
|
|
@@ -439,14 +465,14 @@ export function Timeline({ chatId, composer, overlay }) {
|
|
|
439
465
|
const cardChat = useMemo(() => ({
|
|
440
466
|
chatId,
|
|
441
467
|
canFork: (turnId) => {
|
|
442
|
-
if (
|
|
468
|
+
if (fork === null) {
|
|
443
469
|
return false;
|
|
444
470
|
}
|
|
445
471
|
const row = useChats.getState().byKey[keyOf(chatId)];
|
|
446
472
|
return forkRefusal(row?.info ?? null, row?.structure[turnId]) === null;
|
|
447
473
|
},
|
|
448
|
-
fork: (turnId) =>
|
|
449
|
-
}), [keyOf, chatId]);
|
|
474
|
+
fork: (turnId) => fork?.(chatId, turnId)
|
|
475
|
+
}), [keyOf, chatId, fork]);
|
|
450
476
|
const stepRef = useRef(() => false);
|
|
451
477
|
stepRef.current = (direction) => {
|
|
452
478
|
if (!onMainAgent) {
|
|
@@ -460,20 +486,25 @@ export function Timeline({ chatId, composer, overlay }) {
|
|
|
460
486
|
return true;
|
|
461
487
|
};
|
|
462
488
|
useEffect(() => registerMessageStepper(keyOf(chatId), (direction) => stepRef.current(direction)), [keyOf, chatId]);
|
|
489
|
+
// Read through a ref, so the rows keep one callback for their life and their memo holds.
|
|
490
|
+
const opensRef = useRef({ rows, show });
|
|
491
|
+
opensRef.current = { rows, show };
|
|
463
492
|
/* The whole of what a sub-agent did, in the thread's place. Its row only keeps the beginning. */
|
|
464
|
-
const openConversation = (step) => {
|
|
465
|
-
show(openFromMain(step));
|
|
466
|
-
};
|
|
493
|
+
const openConversation = useCallback((step) => {
|
|
494
|
+
opensRef.current.show(openFromMain(step));
|
|
495
|
+
}, []);
|
|
496
|
+
const addSubagent = subagents.add;
|
|
467
497
|
/* The header of a turn a sub-agent woke. It points at the row that agent worked in, or the one it hangs under. */
|
|
468
|
-
const openSubagent = (toolUseId) => {
|
|
469
|
-
const found = findSubagentBranch(rows, toolUseId);
|
|
498
|
+
const openSubagent = useCallback((toolUseId) => {
|
|
499
|
+
const found = findSubagentBranch(opensRef.current.rows, toolUseId);
|
|
470
500
|
if (!found) {
|
|
471
501
|
return;
|
|
472
502
|
}
|
|
473
|
-
|
|
503
|
+
addSubagent(found.branch.id);
|
|
474
504
|
showRowAtTop(virtualizer, followRef, found.index);
|
|
475
|
-
};
|
|
476
|
-
|
|
505
|
+
}, [addSubagent, virtualizer]);
|
|
506
|
+
const flashedRow = flash !== null && flash.key === keyOf(chatId) && !flash.block ? flash.rowId : null;
|
|
507
|
+
return (_jsx(FileLinkContext.Provider, { value: info?.cwd ?? null, children: _jsxs("div", { ref: frameRef, className: "relative flex min-h-0 grow flex-col", style: readingWidthStyle(readingWidth), "data-file-cwd": info?.cwd ?? '', "data-file-scope-id": scope.id, children: [searching && chatFind.bar, _jsxs(ContextMenu.Root, { children: [_jsx("div", { ref: scrollRef,
|
|
477
508
|
// No CSS scroll-padding here: Chromium counts the sticky composer's caret as hidden behind
|
|
478
509
|
// it and scrolls the thread toward the end on every keystroke.
|
|
479
510
|
className: "chat-scroll min-h-0 grow overflow-auto",
|
|
@@ -495,15 +526,14 @@ export function Timeline({ chatId, composer, overlay }) {
|
|
|
495
526
|
if (wantsEarlier(el)) {
|
|
496
527
|
loadEarlier();
|
|
497
528
|
}
|
|
529
|
+
followStrip(el);
|
|
498
530
|
}, children: _jsxs("div", { ref: contentRef, className: clsx('relative flex min-h-full flex-col', welcome && 'justify-center'), children: [_jsx(ContextMenu.Trigger, { ref: threadRef, className: clsx('chat-thread flex flex-col px-4 pt-4', !welcome && 'grow'), style: {
|
|
499
531
|
paddingLeft,
|
|
500
532
|
'--chat-wide-inset': `${2 * (showsScrubber ? STRIP_CLEARANCE_PX : paddingLeft) - paddingLeft}px`
|
|
501
|
-
}, inert: overlay ? true : undefined, onContextMenu: (e) => setTarget(readTimelineTarget(e.target, threadRef.current, withCurrentText(rows, fullItems()))), children: welcome ? (_jsx(WelcomeGreeting, { chatId: chatId })) : empty ? (_jsx(EmptyThread, { chatId: chatId })) : (_jsx("div", { className: "relative w-full shrink-0", style: { height: virtualizer.getTotalSize() }, children: virtualizer.getVirtualItems().map((virtualRow) => {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
return (_jsxs(RowContainer, { row: row, previous: previous, index: virtualRow.index, top: virtualRow.start, ref: virtualizer.measureElement, children: [flash !== null && flash.key === keyOf(chatId) && flash.rowId === row.id && !flash.block && (_jsx("span", { className: "chat-flash pointer-events-none absolute inset-0 rounded-lg", "aria-hidden": true }, flash.nonce)), header !== null && (_jsx("div", { className: "chat-column-content w-full", children: _jsx(ReplyHeader, { chatId: chatId, at: header.at }) })), _jsx(UiReplyNavigationContext.Provider, { value: uiNavigation, children: _jsx(FindRevealContext.Provider, { value: chatFind.reveal, children: _jsx(MarkableRow, { row: row, chatId: chatId, bookmark: marks.get(row.id) ?? null, children: _jsx(Row, { row: row, chatId: chatId, toggleGroup: groups.toggle, toggleTurn: turns.toggle, toggleSubagent: subagents.toggle, openSubagent: openSubagent, openConversation: openConversation }) }) }) })] }, row.id));
|
|
506
|
-
}) })) }), _jsx(QuoteButton, { thread: threadRef, frame: contentRef, taker: quoteTakerRef }), _jsx("div", { ref: composerRef,
|
|
533
|
+
}, inert: overlay ? true : undefined, onContextMenu: (e) => setTarget(readTimelineTarget(e.target, threadRef.current, withCurrentText(rows, fullItems()))), children: welcome ? (_jsx(WelcomeGreeting, { chatId: chatId })) : empty ? (_jsx(EmptyThread, { chatId: chatId })) : (_jsx("div", { className: "relative w-full shrink-0", style: { height: virtualizer.getTotalSize() }, children: _jsx(UiReplyNavigationContext.Provider, { value: uiNavigation, children: _jsx(FindRevealContext.Provider, { value: chatFind.reveal, children: virtualizer.getVirtualItems().map((virtualRow) => {
|
|
534
|
+
const row = rows[virtualRow.index];
|
|
535
|
+
return (_jsx(ThreadRow, { row: row, previous: virtualRow.index > 0 ? rows[virtualRow.index - 1] : null, index: virtualRow.index, top: virtualRow.start, measure: virtualizer.measureElement, chatId: chatId, flashNonce: flash !== null && flashedRow === row.id ? flash.nonce : null, bookmark: marks.get(row.id) ?? null, toggleGroup: groups.toggle, toggleTurn: turns.toggle, toggleSubagent: subagents.toggle, openSubagent: openSubagent, openConversation: openConversation }, row.id));
|
|
536
|
+
}) }) }) })) }), _jsx(QuoteButton, { thread: threadRef, frame: contentRef, taker: quoteTakerRef }), _jsx("div", { ref: composerRef,
|
|
507
537
|
// The card is translucent so the thread shows through it, but the pb-3 strip under it must stay opaque.
|
|
508
|
-
className: clsx('relative z-10 shrink-0', composer && 'px-3 pb-3 pt-3 bg-[linear-gradient(to_bottom,transparent_calc(100%-12px),var(--surface)_calc(100%-12px))]'), style: { position: stickyComposer ? 'sticky' : 'relative', bottom: 0 }, children: _jsx(QuoteTakerContext.Provider, { value: registerQuoteTaker, children: composer }) })] }) }), _jsx(TimelineMenuPopup, { target: target, thread: threadRef, chatId: onMainAgent ? chatId : null })] }), drawsVisuals && _jsx(VisualDialogs, { chatId: chatId }), loadingEarlier && !overlay && (_jsxs("div", { className: "pointer-events-none absolute inset-x-0 top-3 z-10 flex justify-center", role: "status", children: [_jsx(Icon, { icon: LoaderCircle, size: 16, className: "animate-spin text-text-faint" }), _jsx("span", { className: "sr-only", children: t('timeline.loadingEarlier') })] })), showsScrubber && !overlay && (_jsx("div", { className: "absolute top-4", style: { left: STRIP_INSET_PX, width: STRIP_WIDTH_PX, bottom: coveredHeight }, children: _jsx(ErrorBoundary, { label: t('timeline.stripFailed'), resetKeys: [ticks.length], children: _jsx(Scrubber, { ticks: ticks, firstInView: inView?.first ?? null, lastInView: inView?.last ?? null, onPick: pick, chat: cardChat, found: found, foundCurrent: foundCurrent }) }) })), overlay && (_jsx("div", { className: "absolute inset-0 z-5 flex flex-col bg-surface", style: { paddingBottom: coveredHeight }, children: overlay }))] }) }));
|
|
538
|
+
className: clsx('relative z-10 shrink-0', composer && 'px-3 pb-3 pt-3 bg-[linear-gradient(to_bottom,transparent_calc(100%-12px),var(--surface)_calc(100%-12px))]'), style: { position: stickyComposer ? 'sticky' : 'relative', bottom: 0 }, children: _jsx(QuoteTakerContext.Provider, { value: registerQuoteTaker, children: composer }) })] }) }), _jsx(TimelineMenuPopup, { target: target, thread: threadRef, chatId: onMainAgent ? chatId : null, column: frame.column })] }), drawsVisuals && _jsx(VisualDialogs, { chatId: chatId }), loadingEarlier && !overlay && (_jsxs("div", { className: "pointer-events-none absolute inset-x-0 top-3 z-10 flex justify-center", role: "status", children: [_jsx(Icon, { icon: LoaderCircle, size: 16, className: "animate-spin text-text-faint" }), _jsx("span", { className: "sr-only", children: t('timeline.loadingEarlier') })] })), showsScrubber && !overlay && (_jsx("div", { className: "absolute top-4", style: { left: STRIP_INSET_PX, width: STRIP_WIDTH_PX, bottom: coveredHeight }, children: _jsx(ErrorBoundary, { label: t('timeline.stripFailed'), resetKeys: [ticks.length], children: _jsx(Scrubber, { ticks: ticks, firstInView: inView?.first ?? null, lastInView: inView?.last ?? null, onPick: pick, chat: cardChat, found: found, foundCurrent: foundCurrent }) }) })), overlay && (_jsx("div", { className: "absolute inset-0 z-5 flex flex-col bg-surface", style: { paddingBottom: coveredHeight }, children: overlay }))] }) }));
|
|
509
539
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { RefObject } from 'react';
|
|
2
2
|
import type { TimelineTarget } from '../logic/timeline-target.js';
|
|
3
|
-
export declare function TimelineMenuPopup({ target, thread, chatId }: {
|
|
3
|
+
export declare function TimelineMenuPopup({ target, thread, chatId, column }: {
|
|
4
4
|
target: TimelineTarget;
|
|
5
5
|
thread: RefObject<HTMLDivElement | null>;
|
|
6
6
|
chatId?: string | null;
|
|
7
|
+
column?: boolean;
|
|
7
8
|
}): import("react").JSX.Element;
|
|
8
9
|
export declare function BookmarkMenuItems({ chatId, itemId, onName }: {
|
|
9
10
|
chatId: string;
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import { BookmarkPlus, BookmarkX, Braces, Copy, Eye, FileText, GitFork, MessageSquare, Pencil, Scan } from 'lucide-react';
|
|
3
|
+
import { BookmarkPlus, BookmarkX, Braces, Copy, Eye, FileText, GitFork, MessageSquare, MoveHorizontal, Pencil, Scan } from 'lucide-react';
|
|
4
4
|
import { placeBookmark, removeBookmark, useBookmarkNaming } from '../bookmarks.js';
|
|
5
|
+
import { rememberReadingWidth, useChatPreferences } from '../preferences.js';
|
|
6
|
+
import { CHAT_READING_WIDTHS, readingWidthLabel } from '../reading-width.js';
|
|
5
7
|
import { forkRefusal, turnIdOfRow } from '../logic/fork.js';
|
|
6
8
|
import { markdownOf, messageTextOf } from '../logic/timeline-copy.js';
|
|
7
|
-
import {
|
|
9
|
+
import { useChatHost } from '../../host.js';
|
|
8
10
|
import { useChatScope } from '../../scope.js';
|
|
9
11
|
import { useChatRow } from '../../state/chats.js';
|
|
10
|
-
import {
|
|
12
|
+
import { useFileLinkCwd, useFileLinkScopeId, useOpenFileLink } from './file-links.js';
|
|
11
13
|
import { ContextMenu, copyText, DisabledReason, EDIT_SHORTCUTS, Icon, Kbd, selectAllWithin } from '@adecore/ui';
|
|
12
14
|
/* The menu behind a right-click in a thread, mostly for copying: a whole message, a code block or an answer's markdown. */
|
|
13
|
-
export function TimelineMenuPopup({ target, thread, chatId = null }) {
|
|
15
|
+
export function TimelineMenuPopup({ target, thread, chatId = null, column = false }) {
|
|
14
16
|
const { t } = useTranslation('agent-chat');
|
|
15
17
|
const message = target.row === null ? null : messageTextOf(target.row);
|
|
16
18
|
const turnId = chatId === null || target.row === null ? null : turnIdOfRow(target.row);
|
|
@@ -20,7 +22,8 @@ export function TimelineMenuPopup({ target, thread, chatId = null }) {
|
|
|
20
22
|
// A thread outside a chat (a sub-agent's transcript) has no cwd of its own; the app answers for it then.
|
|
21
23
|
const cwd = useFileLinkCwd();
|
|
22
24
|
const scopeId = useFileLinkScopeId();
|
|
23
|
-
const { fork, fileLinks } =
|
|
25
|
+
const { fork, fileLinks } = useChatHost();
|
|
26
|
+
const openFileLink = useOpenFileLink();
|
|
24
27
|
const openInPreview = () => {
|
|
25
28
|
if (target.file != null) {
|
|
26
29
|
openFileLink(target.file.cwd, target.file.ref, target.file.scopeId);
|
|
@@ -29,7 +32,7 @@ export function TimelineMenuPopup({ target, thread, chatId = null }) {
|
|
|
29
32
|
openFileLink(cwd, { path: target.path, ...(target.line === null ? {} : { line: target.line }), directory: false }, scopeId);
|
|
30
33
|
}
|
|
31
34
|
};
|
|
32
|
-
return (_jsxs(ContextMenu.Popup, { children: [_jsxs(ContextMenu.Item, { disabled: target.selection === '', onClick: () => copyText(target.selection), children: [_jsx(Icon, { icon: Copy, size: 14 }), " ", t('common.action.copy'), " ", _jsx(Kbd, { shortcut: EDIT_SHORTCUTS.copy })] }), message !== null && (_jsxs(ContextMenu.Item, { onClick: () => copyText(message), children: [_jsx(Icon, { icon: MessageSquare, size: 14 }), " ", t('timeline.menu.copyMessage')] })), target.code !== null && (_jsxs(ContextMenu.Item, { onClick: () => copyText(target.code ?? ''), children: [_jsx(Icon, { icon: Braces, size: 14 }), " ", t('timeline.menu.copyCode')] })), markdown !== null && (_jsxs(ContextMenu.Item, { onClick: () => copyText(markdown), children: [_jsx(Icon, { icon: FileText, size: 14 }), " ", t('timeline.menu.copyMarkdown')] })), chatId !== null && turnId !== null && fork !== null && (_jsx(DisabledReason, { reason: forkBlocked, children: _jsxs(ContextMenu.Item, { disabled: forkBlocked !== null, onClick: () => fork(chatId, turnId), children: [_jsx(Icon, { icon: GitFork, size: 14 }), " ", t('timeline.menu.forkFromHere')] }) })), chatId !== null && markable !== null && _jsx(BookmarkMenuItems, { chatId: chatId, itemId: markable }), _jsx(ContextMenu.Separator, {}), _jsxs(ContextMenu.Item, { onClick: () => selectAllWithin(thread.current), children: [_jsx(Icon, { icon: Scan, size: 14 }), " ", t('common.action.selectAll')] }), target.path !== null && fileLinks !== null && (_jsxs(_Fragment, { children: [_jsx(ContextMenu.Separator, {}), _jsxs(ContextMenu.Item, { onClick: openInPreview, children: [_jsx(Icon, { icon: Eye, size: 14 }), " ", t('timeline.menu.openInPreview')] })] }))] }));
|
|
35
|
+
return (_jsxs(ContextMenu.Popup, { children: [_jsxs(ContextMenu.Item, { disabled: target.selection === '', onClick: () => copyText(target.selection), children: [_jsx(Icon, { icon: Copy, size: 14 }), " ", t('common.action.copy'), " ", _jsx(Kbd, { shortcut: EDIT_SHORTCUTS.copy })] }), message !== null && (_jsxs(ContextMenu.Item, { onClick: () => copyText(message), children: [_jsx(Icon, { icon: MessageSquare, size: 14 }), " ", t('timeline.menu.copyMessage')] })), target.code !== null && (_jsxs(ContextMenu.Item, { onClick: () => copyText(target.code ?? ''), children: [_jsx(Icon, { icon: Braces, size: 14 }), " ", t('timeline.menu.copyCode')] })), markdown !== null && (_jsxs(ContextMenu.Item, { onClick: () => copyText(markdown), children: [_jsx(Icon, { icon: FileText, size: 14 }), " ", t('timeline.menu.copyMarkdown')] })), chatId !== null && turnId !== null && fork !== null && (_jsx(DisabledReason, { reason: forkBlocked, children: _jsxs(ContextMenu.Item, { disabled: forkBlocked !== null, onClick: () => fork(chatId, turnId), children: [_jsx(Icon, { icon: GitFork, size: 14 }), " ", t('timeline.menu.forkFromHere')] }) })), chatId !== null && markable !== null && _jsx(BookmarkMenuItems, { chatId: chatId, itemId: markable }), _jsx(ContextMenu.Separator, {}), _jsxs(ContextMenu.Item, { onClick: () => selectAllWithin(thread.current), children: [_jsx(Icon, { icon: Scan, size: 14 }), " ", t('common.action.selectAll')] }), target.path !== null && fileLinks !== null && (_jsxs(_Fragment, { children: [_jsx(ContextMenu.Separator, {}), _jsxs(ContextMenu.Item, { onClick: openInPreview, children: [_jsx(Icon, { icon: Eye, size: 14 }), " ", t('timeline.menu.openInPreview')] })] })), column && (_jsxs(_Fragment, { children: [_jsx(ContextMenu.Separator, {}), _jsx(ReadingWidthSubmenu, {})] }))] }));
|
|
33
36
|
}
|
|
34
37
|
/*
|
|
35
38
|
* Place a bookmark on a message, or rename and remove the one it has, from the thread and from the strip.
|
|
@@ -38,15 +41,22 @@ export function TimelineMenuPopup({ target, thread, chatId = null }) {
|
|
|
38
41
|
export function BookmarkMenuItems({ chatId, itemId, onName }) {
|
|
39
42
|
const { t } = useTranslation('agent-chat');
|
|
40
43
|
const scope = useChatScope();
|
|
44
|
+
const host = useChatHost();
|
|
41
45
|
const bookmark = useChatRow(chatId, (row) => row?.bookmarks?.find((candidate) => candidate.itemId === itemId) ?? null);
|
|
42
46
|
if (bookmark === null) {
|
|
43
47
|
return (_jsxs(ContextMenu.Item, { onClick: () => {
|
|
44
|
-
void placeBookmark(scope, chatId, itemId);
|
|
48
|
+
void placeBookmark(scope, host, chatId, itemId);
|
|
45
49
|
onName?.();
|
|
46
50
|
}, children: [_jsx(Icon, { icon: BookmarkPlus, size: 14 }), " ", t('bookmarks.add')] }));
|
|
47
51
|
}
|
|
48
52
|
return (_jsxs(_Fragment, { children: [_jsxs(ContextMenu.Item, { onClick: () => {
|
|
49
53
|
useBookmarkNaming.getState().open(scope.keyOf(chatId), bookmark.itemId);
|
|
50
54
|
onName?.();
|
|
51
|
-
}, children: [_jsx(Icon, { icon: Pencil, size: 14 }), " ", t('bookmarks.rename')] }), _jsxs(ContextMenu.Item, { onClick: () => void removeBookmark(scope, chatId, bookmark), children: [_jsx(Icon, { icon: BookmarkX, size: 14 }), " ", t('bookmarks.remove')] })] }));
|
|
55
|
+
}, children: [_jsx(Icon, { icon: Pencil, size: 14 }), " ", t('bookmarks.rename')] }), _jsxs(ContextMenu.Item, { onClick: () => void removeBookmark(scope, host, chatId, bookmark), children: [_jsx(Icon, { icon: BookmarkX, size: 14 }), " ", t('bookmarks.remove')] })] }));
|
|
56
|
+
}
|
|
57
|
+
/* The person's reading width for every chat of its own on this client. */
|
|
58
|
+
function ReadingWidthSubmenu() {
|
|
59
|
+
const { t } = useTranslation('agent-chat');
|
|
60
|
+
const readingWidth = useChatPreferences((preferences) => preferences.readingWidth);
|
|
61
|
+
return (_jsxs(ContextMenu.SubmenuRoot, { children: [_jsxs(ContextMenu.SubmenuTrigger, { children: [_jsx(Icon, { icon: MoveHorizontal, size: 14 }), " ", t('readingWidth.label')] }), _jsx(ContextMenu.Popup, { className: "min-w-40", children: _jsx(ContextMenu.RadioGroup, { value: readingWidth, onValueChange: (width) => rememberReadingWidth(width), children: CHAT_READING_WIDTHS.map((width) => (_jsx(ContextMenu.RadioItem, { value: width, children: readingWidthLabel(width) }, width))) }) })] }));
|
|
52
62
|
}
|
|
@@ -7,13 +7,14 @@ import { DiffOmitted } from './diff/DiffOmitted.js';
|
|
|
7
7
|
import { DiffUnavailable } from './diff/DiffUnavailable.js';
|
|
8
8
|
import { RawDiff } from './diff/RawDiff.js';
|
|
9
9
|
import { DiffFallbackBoundary } from './diff/DiffFallbackBoundary.js';
|
|
10
|
-
import {
|
|
10
|
+
import { useChatHost } from '../../host.js';
|
|
11
11
|
const LegacyUnifiedDiff = lazyNamed(() => import('./diff/LegacyUnifiedDiff.js'), 'default', { prefetch: false });
|
|
12
12
|
const EditorPatchDiff = lazyNamed(() => import('./diff/EditorPatchDiff.js'), 'default');
|
|
13
13
|
export default function UnifiedDiff(props) {
|
|
14
|
+
const host = useChatHost();
|
|
14
15
|
const { t } = useTranslation('agent-chat');
|
|
15
16
|
const { change, contents, allowEditor = true, ignoreWhitespace = false, overflow = 'wrap', diffStyle = 'unified', fill = false, maxHeight } = props;
|
|
16
|
-
const loadEngine =
|
|
17
|
+
const loadEngine = host.code.editor;
|
|
17
18
|
const oldText = contents?.old ?? '';
|
|
18
19
|
const newText = contents?.new ?? '';
|
|
19
20
|
const sourceAllowed = useMemo(() => diffSourceBudget(oldText, newText) && patchSourceBudget(change.diff), [oldText, newText, change.diff]);
|