@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
|
@@ -54,6 +54,7 @@ export function VisualFrame({ chatId, visual, fill = false, className }: VisualF
|
|
|
54
54
|
const scope = useChatScope();
|
|
55
55
|
// The frame opens again when its page changes, so the reader keeps its identity while the visual does.
|
|
56
56
|
const page = useCallback(() => attachments.read(scope.id, chatId, visual.id).then((blob) => blob.text()), [attachments, scope.id, chatId, visual.id]);
|
|
57
|
+
|
|
57
58
|
return (
|
|
58
59
|
<PageFrame
|
|
59
60
|
page={page}
|
|
@@ -91,10 +92,12 @@ function PageFrame({ page, title, maxHeight, fill = false, className, initialHei
|
|
|
91
92
|
const controllerRef = useRef<VisualViewportController | null>(null);
|
|
92
93
|
const [viewportReady, setViewportReady] = useState(false);
|
|
93
94
|
const [measure, width] = useMeasuredWidth();
|
|
95
|
+
|
|
94
96
|
const setBox = (node: HTMLDivElement | null) => {
|
|
95
97
|
boxRef.current = node;
|
|
96
98
|
measure(node);
|
|
97
99
|
};
|
|
100
|
+
|
|
98
101
|
const theme = useVisualTheme(boxRef);
|
|
99
102
|
const widthRef = useRef(width);
|
|
100
103
|
const onHeightRef = useRef(onHeight);
|
|
@@ -104,9 +107,11 @@ function PageFrame({ page, title, maxHeight, fill = false, className, initialHei
|
|
|
104
107
|
const [failed, setFailed] = useState(false);
|
|
105
108
|
// The frame keeps the address it opened with: a later theme goes over the bridge, since a new address would load the page again.
|
|
106
109
|
const [opening, setOpening] = useState<{ src: string; theme: VisualTheme } | null>(null);
|
|
110
|
+
|
|
107
111
|
if (opening === null && frameUrl !== null && theme !== null && width > 0) {
|
|
108
112
|
setOpening({ src: frameUrl + visualThemeFragment(theme), theme });
|
|
109
113
|
}
|
|
114
|
+
|
|
110
115
|
const height = fill || width === 0 ? undefined : (reported ?? initialHeight(width));
|
|
111
116
|
|
|
112
117
|
useLayoutEffect(() => {
|
|
@@ -116,9 +121,11 @@ function PageFrame({ page, title, maxHeight, fill = false, className, initialHei
|
|
|
116
121
|
|
|
117
122
|
useLayoutEffect(() => {
|
|
118
123
|
const frame = frameRef.current;
|
|
124
|
+
|
|
119
125
|
if (opening === null || frame === null) {
|
|
120
126
|
return;
|
|
121
127
|
}
|
|
128
|
+
|
|
122
129
|
const bridge = new VisualBridge({
|
|
123
130
|
frame: () => frame.contentWindow,
|
|
124
131
|
visual: { maxHeight },
|
|
@@ -130,6 +137,7 @@ function PageFrame({ page, title, maxHeight, fill = false, className, initialHei
|
|
|
130
137
|
if (fill) {
|
|
131
138
|
return;
|
|
132
139
|
}
|
|
140
|
+
|
|
133
141
|
setReported(next);
|
|
134
142
|
onHeightRef.current?.(widthRef.current, next);
|
|
135
143
|
},
|
|
@@ -142,10 +150,14 @@ function PageFrame({ page, title, maxHeight, fill = false, className, initialHei
|
|
|
142
150
|
onAppearance: setDrawn,
|
|
143
151
|
onFailure: () => setFailed(true)
|
|
144
152
|
});
|
|
153
|
+
|
|
145
154
|
bridgeRef.current = bridge;
|
|
146
155
|
const view = frame.ownerDocument.defaultView ?? window;
|
|
156
|
+
|
|
147
157
|
const listen = (event: MessageEvent): void => bridge.receive(event);
|
|
158
|
+
|
|
148
159
|
view.addEventListener('message', listen);
|
|
160
|
+
|
|
149
161
|
return () => {
|
|
150
162
|
view.removeEventListener('message', listen);
|
|
151
163
|
bridge.dispose();
|
|
@@ -158,11 +170,14 @@ function PageFrame({ page, title, maxHeight, fill = false, className, initialHei
|
|
|
158
170
|
const viewport = viewportRef.current;
|
|
159
171
|
const bridge = bridgeRef.current;
|
|
160
172
|
const scroller = box?.closest<HTMLElement>('.chat-scroll');
|
|
173
|
+
|
|
161
174
|
if (fill || !viewportReady || !box || !viewport || !bridge || !scroller) {
|
|
162
175
|
return;
|
|
163
176
|
}
|
|
177
|
+
|
|
164
178
|
const controller = new VisualViewportController(box, viewport, scroller, bridge);
|
|
165
179
|
controllerRef.current = controller;
|
|
180
|
+
|
|
166
181
|
return () => {
|
|
167
182
|
controller.dispose();
|
|
168
183
|
controllerRef.current = null;
|
package/src/chat/ui/Welcome.tsx
CHANGED
|
@@ -13,6 +13,7 @@ export function WelcomeGreeting({ chatId, now = localClock }: { chatId: string;
|
|
|
13
13
|
const { t } = useTranslation('agent-chat');
|
|
14
14
|
const provider = useChatRow(chatId, (row) => row?.info.provider ?? null);
|
|
15
15
|
const [part] = useState(() => dayPartOf(now()));
|
|
16
|
+
|
|
16
17
|
return (
|
|
17
18
|
<div className="flex items-center justify-center gap-3 px-3 pb-2 text-text">
|
|
18
19
|
{provider && <AgentIcon kind={provider} size={32} className="shrink-0" />}
|
|
@@ -22,7 +22,9 @@ export function messageClipboardChips(
|
|
|
22
22
|
if (item.kind === 'assistant') {
|
|
23
23
|
return clipboardChipsOf(text, tokenizeNamedReferences(text, lookup.items, lookup.referenced));
|
|
24
24
|
}
|
|
25
|
+
|
|
25
26
|
const tokens = referenceTokens([...(item.chats ?? []), ...(item.references ?? [])], lookup.items);
|
|
27
|
+
|
|
26
28
|
return clipboardChipsOf(text, tokenizeChips(text, item.mentions ?? [], item.skills ?? [], tokens));
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -31,15 +33,18 @@ export function rowClipboardChips(row: TimelineRow, text: string, lookup: Pick<R
|
|
|
31
33
|
if (row.kind === 'user' || row.kind === 'assistant') {
|
|
32
34
|
return messageClipboardChips(row.item, text, lookup);
|
|
33
35
|
}
|
|
36
|
+
|
|
34
37
|
return clipboardChipsOf(text, []);
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
/* What a chip the transcript drew stands for, read back from the attributes `TokenChip` writes. */
|
|
38
41
|
function segmentOf(chip: Element): ChipSegment | null {
|
|
39
42
|
const value = chip.getAttribute('data-chip-value');
|
|
43
|
+
|
|
40
44
|
if (value === null) {
|
|
41
45
|
return null;
|
|
42
46
|
}
|
|
47
|
+
|
|
43
48
|
switch (chip.getAttribute('data-chip')) {
|
|
44
49
|
case 'mention':
|
|
45
50
|
return { kind: 'mention', path: value };
|
|
@@ -47,6 +52,7 @@ function segmentOf(chip: Element): ChipSegment | null {
|
|
|
47
52
|
return { kind: 'skill', name: value };
|
|
48
53
|
case 'reference': {
|
|
49
54
|
const name = chip.getAttribute('data-chip-name');
|
|
55
|
+
|
|
50
56
|
return name === null || name === '' ? null : { kind: 'reference', id: value, name };
|
|
51
57
|
}
|
|
52
58
|
default:
|
|
@@ -57,31 +63,39 @@ function segmentOf(chip: Element): ChipSegment | null {
|
|
|
57
63
|
/* The chips a selection holds whole, beside the text it copies; a chip cut in half is only text. */
|
|
58
64
|
export function selectionChips(selection: Selection): ClipboardChips {
|
|
59
65
|
const segments: ChipSegment[] = [];
|
|
66
|
+
|
|
60
67
|
for (let i = 0; i < selection.rangeCount; i++) {
|
|
61
68
|
const container = selection.getRangeAt(i).commonAncestorContainer;
|
|
62
69
|
const root = container instanceof Element ? container : container.parentElement;
|
|
63
70
|
const own = root?.closest('[data-chip]');
|
|
64
71
|
const candidates = own ? [own] : [...(root?.querySelectorAll('[data-chip]') ?? [])];
|
|
72
|
+
|
|
65
73
|
for (const chip of candidates) {
|
|
66
74
|
const segment = selection.containsNode(chip, false) ? segmentOf(chip) : null;
|
|
75
|
+
|
|
67
76
|
if (segment !== null) {
|
|
68
77
|
segments.push(segment);
|
|
69
78
|
}
|
|
70
79
|
}
|
|
71
80
|
}
|
|
81
|
+
|
|
72
82
|
return clipboardChipsOf(selection.toString(), segments);
|
|
73
83
|
}
|
|
74
84
|
|
|
75
85
|
/* A copy of a selection in the thread, which carries the chips in it as well when it holds any. */
|
|
76
86
|
export function copySelectionWithChips(event: ClipboardEvent): void {
|
|
77
87
|
const selection = document.getSelection();
|
|
88
|
+
|
|
78
89
|
if (event.defaultPrevented || event.clipboardData === null || selection === null || selection.isCollapsed) {
|
|
79
90
|
return;
|
|
80
91
|
}
|
|
92
|
+
|
|
81
93
|
const chips = selectionChips(selection);
|
|
94
|
+
|
|
82
95
|
if (!hasClipboardChips(chips)) {
|
|
83
96
|
return;
|
|
84
97
|
}
|
|
98
|
+
|
|
85
99
|
writeClipboardChips(event.clipboardData, chips);
|
|
86
100
|
event.preventDefault();
|
|
87
101
|
}
|
|
@@ -93,9 +107,12 @@ export function copySelectionWithChips(event: ClipboardEvent): void {
|
|
|
93
107
|
export function copyWithChips(chips: ClipboardChips): void {
|
|
94
108
|
if (!hasClipboardChips(chips) || typeof document.execCommand !== 'function') {
|
|
95
109
|
void copyText(chips.text);
|
|
110
|
+
|
|
96
111
|
return;
|
|
97
112
|
}
|
|
113
|
+
|
|
98
114
|
let written = false;
|
|
115
|
+
|
|
99
116
|
const write = (event: ClipboardEvent): void => {
|
|
100
117
|
if (event.clipboardData !== null) {
|
|
101
118
|
writeClipboardChips(event.clipboardData, chips);
|
|
@@ -103,12 +120,15 @@ export function copyWithChips(chips: ClipboardChips): void {
|
|
|
103
120
|
written = true;
|
|
104
121
|
}
|
|
105
122
|
};
|
|
123
|
+
|
|
106
124
|
document.addEventListener('copy', write, true);
|
|
125
|
+
|
|
107
126
|
try {
|
|
108
127
|
document.execCommand('copy');
|
|
109
128
|
} finally {
|
|
110
129
|
document.removeEventListener('copy', write, true);
|
|
111
130
|
}
|
|
131
|
+
|
|
112
132
|
if (!written) {
|
|
113
133
|
void copyText(chips.text);
|
|
114
134
|
}
|
|
@@ -22,25 +22,32 @@ export class TokenCache<State> {
|
|
|
22
22
|
if (state !== undefined) {
|
|
23
23
|
return tokenize(code, state);
|
|
24
24
|
}
|
|
25
|
+
|
|
25
26
|
const key = `${namespace}\0${code}`;
|
|
26
27
|
const known = this.entries.get(key);
|
|
28
|
+
|
|
27
29
|
if (known !== undefined) {
|
|
28
30
|
this.entries.delete(key);
|
|
29
31
|
this.entries.set(key, known);
|
|
32
|
+
|
|
30
33
|
return known.result;
|
|
31
34
|
}
|
|
35
|
+
|
|
32
36
|
const result = tokenize(code, state);
|
|
33
37
|
// Account for tokens too, so dense code cannot fill the cache with tiny strings.
|
|
34
38
|
const cost = key.length * 2 + result.lines.reduce((sum, line) => sum + line.length * 64, 0);
|
|
39
|
+
|
|
35
40
|
if (cost <= TOKEN_CACHE_BUDGET) {
|
|
36
41
|
while (this.entries.size > 0 && (this.size + cost > TOKEN_CACHE_BUDGET || this.entries.size >= TOKEN_CACHE_ENTRIES)) {
|
|
37
42
|
const oldest = this.entries.keys().next().value!;
|
|
38
43
|
this.size -= this.entries.get(oldest)!.cost;
|
|
39
44
|
this.entries.delete(oldest);
|
|
40
45
|
}
|
|
46
|
+
|
|
41
47
|
this.entries.set(key, { result, cost });
|
|
42
48
|
this.size += cost;
|
|
43
49
|
}
|
|
50
|
+
|
|
44
51
|
return result;
|
|
45
52
|
};
|
|
46
53
|
}
|
|
@@ -68,24 +75,31 @@ export class IncrementalLines<State> {
|
|
|
68
75
|
// A carriage return splits lines in ways a cut at newlines does not see, so that code starts over every time.
|
|
69
76
|
if (code.includes('\r')) {
|
|
70
77
|
this.reset();
|
|
78
|
+
|
|
71
79
|
return this.tokenize(code, undefined).lines;
|
|
72
80
|
}
|
|
81
|
+
|
|
73
82
|
const end = complete ? code.length : code.lastIndexOf('\n') + 1;
|
|
74
83
|
const stable = code.slice(0, end);
|
|
75
84
|
const continues = (this.source === '' || this.source.endsWith('\n')) && stable.startsWith(this.source);
|
|
85
|
+
|
|
76
86
|
if (!continues) {
|
|
77
87
|
this.reset();
|
|
78
88
|
}
|
|
89
|
+
|
|
79
90
|
const added = stable.slice(this.source.length);
|
|
91
|
+
|
|
80
92
|
if (added !== '') {
|
|
81
93
|
const result = this.tokenize(added.endsWith('\n') ? added.slice(0, -1) : added, this.state);
|
|
82
94
|
this.lines = [...this.lines, ...result.lines];
|
|
83
95
|
this.state = result.state;
|
|
84
96
|
this.source = stable;
|
|
85
97
|
}
|
|
98
|
+
|
|
86
99
|
if (complete) {
|
|
87
100
|
return this.lines;
|
|
88
101
|
}
|
|
102
|
+
|
|
89
103
|
return [...this.lines, ...this.tokenize(code.slice(end), this.state).lines];
|
|
90
104
|
}
|
|
91
105
|
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { useRef, type Ref } from 'react';
|
|
1
|
+
import { useRef, useState, type Ref } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { ArrowUp, ChevronDown, Play, Plus, Square } from 'lucide-react';
|
|
4
5
|
import type { ChatInfo } from '@adecore/agent-contracts';
|
|
5
|
-
import { Button, ErrorBoundary, Icon, Tooltip, type Shortcut } from '@adecore/ui';
|
|
6
|
-
import { useChatHost } from '../../../host';
|
|
6
|
+
import { Button, ErrorBoundary, Icon, Menu, Tooltip, type Shortcut } from '@adecore/ui';
|
|
7
|
+
import { useChatHost, type ComposerSendAction, type ComposerSendDraft } from '../../../host';
|
|
7
8
|
import { useChatScope } from '../../../scope';
|
|
8
9
|
import { useProviders } from '../../../state/providers';
|
|
9
10
|
import { useAccountChoice } from '../../account-choice';
|
|
10
11
|
import type { StashedPrompt } from '../../stash';
|
|
11
12
|
import { composerStopLabel } from '../../subagent-list';
|
|
13
|
+
import { HostMenuItems } from '../HostMenuItems';
|
|
12
14
|
import { StashPicker } from '../Pickers';
|
|
13
15
|
import { RunSettings } from '../RunSettings';
|
|
14
16
|
import type { ChatControls } from './use-chat-controls';
|
|
@@ -22,6 +24,68 @@ interface SendProps {
|
|
|
22
24
|
onSend(alternate: boolean): void;
|
|
23
25
|
/* Set when a person stopped the last turn and the box is empty: Continue stands where the send button would. */
|
|
24
26
|
onContinue: (() => void) | null;
|
|
27
|
+
/* The host's own, behind a chevron on the send button; empty draws the send button alone. */
|
|
28
|
+
actions: readonly ComposerSendAction[];
|
|
29
|
+
draft: ComposerSendDraft;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface SendButtonProps {
|
|
33
|
+
send: SendProps;
|
|
34
|
+
/* The chevron only follows whether the box takes input: an action may want an empty draft, such as to reuse the last message. */
|
|
35
|
+
writable: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* The send button, split with a chevron that opens the host's send actions when it has any. */
|
|
39
|
+
function SendButton({ send, writable }: SendButtonProps) {
|
|
40
|
+
const { t } = useTranslation('agent-chat');
|
|
41
|
+
const [box, setBox] = useState<HTMLDivElement | null>(null);
|
|
42
|
+
const split = send.actions.length > 0;
|
|
43
|
+
|
|
44
|
+
const button = (
|
|
45
|
+
<Tooltip label={send.label} kbd={send.kbd} name>
|
|
46
|
+
<button
|
|
47
|
+
className={clsx(
|
|
48
|
+
'flex h-9 w-9 shrink-0 items-center justify-center bg-accent text-accent-text hover:brightness-90 disabled:opacity-50 disabled:hover:brightness-100',
|
|
49
|
+
split ? 'focus-visible:-outline-offset-2' : 'rounded-full'
|
|
50
|
+
)}
|
|
51
|
+
disabled={send.disabled}
|
|
52
|
+
onClick={(event) => send.onSend(event.altKey)}
|
|
53
|
+
>
|
|
54
|
+
<Icon icon={ArrowUp} size={16} />
|
|
55
|
+
</button>
|
|
56
|
+
</Tooltip>
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (!split) {
|
|
60
|
+
return button;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const { draft } = send;
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
// One round shape that clips both halves, the way a split button of the library draws its frame.
|
|
67
|
+
<div ref={setBox} className="flex h-9 shrink-0 overflow-clip rounded-full">
|
|
68
|
+
{button}
|
|
69
|
+
<Menu.Root>
|
|
70
|
+
<Tooltip label={t('composer.sendActions')} name>
|
|
71
|
+
<Menu.Trigger
|
|
72
|
+
render={
|
|
73
|
+
<button
|
|
74
|
+
type="button"
|
|
75
|
+
className='relative flex h-9 w-6 shrink-0 items-center justify-center bg-accent text-accent-text before:absolute before:inset-y-0 before:left-0 before:my-auto before:h-4 before:w-px before:bg-current/30 before:content-[""] hover:brightness-90 focus-visible:-outline-offset-2 disabled:opacity-50 disabled:hover:brightness-100'
|
|
76
|
+
/>
|
|
77
|
+
}
|
|
78
|
+
disabled={!writable}
|
|
79
|
+
>
|
|
80
|
+
<Icon icon={ChevronDown} size={14} />
|
|
81
|
+
</Menu.Trigger>
|
|
82
|
+
</Tooltip>
|
|
83
|
+
<Menu.Popup anchor={box} side="top" sideOffset={8} align="end" className="min-w-48">
|
|
84
|
+
<HostMenuItems actions={send.actions.map((action) => ({ ...action, run: () => action.run(draft) }))} />
|
|
85
|
+
</Menu.Popup>
|
|
86
|
+
</Menu.Root>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
25
89
|
}
|
|
26
90
|
|
|
27
91
|
interface ComposerControlsProps {
|
|
@@ -62,6 +126,7 @@ export function ComposerControls({
|
|
|
62
126
|
const model = providers.find((entry) => entry.kind === info.provider)?.models.find((entry) => entry.slug === info.selection.model);
|
|
63
127
|
// The CLI announces its session on the first message, so anything before that is still a blank chat.
|
|
64
128
|
const started = info.agentSessionId !== null || info.usage.turns > 0;
|
|
129
|
+
|
|
65
130
|
// A chat that has started keeps its provider (the CLI holds the thread), and so does one opened for a
|
|
66
131
|
// named CLI; its models stay switchable, since the CLI takes a new one on the restart the next send does.
|
|
67
132
|
const pickable =
|
|
@@ -140,17 +205,7 @@ export function ComposerControls({
|
|
|
140
205
|
</Button>
|
|
141
206
|
</Tooltip>
|
|
142
207
|
)}
|
|
143
|
-
{send.onContinue === null && send.visible &&
|
|
144
|
-
<Tooltip label={send.label} kbd={send.kbd} name>
|
|
145
|
-
<button
|
|
146
|
-
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-accent text-accent-text hover:brightness-90 disabled:opacity-50 disabled:hover:brightness-100"
|
|
147
|
-
disabled={send.disabled}
|
|
148
|
-
onClick={(event) => send.onSend(event.altKey)}
|
|
149
|
-
>
|
|
150
|
-
<Icon icon={ArrowUp} size={16} />
|
|
151
|
-
</button>
|
|
152
|
-
</Tooltip>
|
|
153
|
-
)}
|
|
208
|
+
{send.onContinue === null && send.visible && <SendButton send={send} writable={writable} />}
|
|
154
209
|
</div>
|
|
155
210
|
</div>
|
|
156
211
|
);
|
|
@@ -9,6 +9,7 @@ import { optionIdOf } from './menu-aria';
|
|
|
9
9
|
|
|
10
10
|
function splitPath(path: string): { name: string; dir: string } {
|
|
11
11
|
const slash = path.lastIndexOf('/');
|
|
12
|
+
|
|
12
13
|
return slash < 0 ? { name: path, dir: '' } : { name: path.slice(slash + 1), dir: path.slice(0, slash) };
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -41,6 +42,7 @@ function MenuOption({ menus, index, className, onPick, children }: OptionProps)
|
|
|
41
42
|
|
|
42
43
|
function CommandMenu({ menus }: { menus: Menus }) {
|
|
43
44
|
const { t } = useTranslation('agent-chat');
|
|
45
|
+
|
|
44
46
|
return (
|
|
45
47
|
<div id={menus.listId} role="listbox" aria-label={t('composer.menu.commands')} className="border-b border-border px-1.5 py-1.5">
|
|
46
48
|
{menus.commands.map((command, index) => (
|
|
@@ -56,6 +58,7 @@ function CommandMenu({ menus }: { menus: Menus }) {
|
|
|
56
58
|
|
|
57
59
|
function SkillMenu({ menus }: { menus: Menus }) {
|
|
58
60
|
const { t } = useTranslation('agent-chat');
|
|
61
|
+
|
|
59
62
|
return (
|
|
60
63
|
<div id={menus.listId} role="listbox" aria-label={t('composer.menu.skills')} className="border-b border-border px-1.5 py-1.5">
|
|
61
64
|
{menus.skillMatches.map((skill, index) => (
|
|
@@ -76,6 +79,7 @@ function MentionMenu({ menus, query }: { menus: Menus; query: string }) {
|
|
|
76
79
|
const { listId, files, mentionCount } = menus;
|
|
77
80
|
const filesLabelId = `${listId}-files`;
|
|
78
81
|
const filesLabelShown = query === '';
|
|
82
|
+
|
|
79
83
|
return (
|
|
80
84
|
<div className="border-b border-border px-1.5 py-1.5">
|
|
81
85
|
{mentionCount > 0 && (
|
|
@@ -83,6 +87,7 @@ function MentionMenu({ menus, query }: { menus: Menus; query: string }) {
|
|
|
83
87
|
{filesLabelShown && <Menu.Label id={filesLabelId}>{t('composer.mentions.here')}</Menu.Label>}
|
|
84
88
|
{files.map((path, index) => {
|
|
85
89
|
const { name, dir } = splitPath(path);
|
|
90
|
+
|
|
86
91
|
return (
|
|
87
92
|
<MenuOption key={path} menus={menus} index={index} className="items-center" onPick={() => menus.chooseMention(path)}>
|
|
88
93
|
<FileIcon path={path} size={14} />
|
|
@@ -104,9 +109,11 @@ function MentionMenu({ menus, query }: { menus: Menus; query: string }) {
|
|
|
104
109
|
function ReferenceMenu({ menus }: { menus: Menus }) {
|
|
105
110
|
const { t } = useTranslation('agent-chat');
|
|
106
111
|
const { listId, referenceMatches } = menus;
|
|
112
|
+
|
|
107
113
|
const groups = (['canvas', 'views'] as const)
|
|
108
114
|
.map((group) => ({ group, items: referenceMatches.filter((item) => item.group === group) }))
|
|
109
115
|
.filter((entry) => entry.items.length > 0);
|
|
116
|
+
|
|
110
117
|
return (
|
|
111
118
|
<div className="border-b border-border px-1.5 py-1.5">
|
|
112
119
|
{referenceMatches.length > 0 ? (
|
|
@@ -116,6 +123,7 @@ function ReferenceMenu({ menus }: { menus: Menus }) {
|
|
|
116
123
|
<Menu.Label id={`${listId}-${group}`}>{t(`composer.references.${group}`)}</Menu.Label>
|
|
117
124
|
{items.map((item) => {
|
|
118
125
|
const index = referenceMatches.indexOf(item);
|
|
126
|
+
|
|
119
127
|
return (
|
|
120
128
|
<MenuOption key={item.id} menus={menus} index={index} className="items-center" onPick={() => menus.chooseReference(item)}>
|
|
121
129
|
<Icon icon={REFERENCE_ICONS[item.kind]} size={14} className="shrink-0 text-text-faint" />
|
|
@@ -141,14 +149,18 @@ export function ComposerMenu({ menus }: { menus: Menus }) {
|
|
|
141
149
|
if (menus.commandMenuOpen) {
|
|
142
150
|
return <CommandMenu menus={menus} />;
|
|
143
151
|
}
|
|
152
|
+
|
|
144
153
|
if (menus.skillMenuOpen) {
|
|
145
154
|
return <SkillMenu menus={menus} />;
|
|
146
155
|
}
|
|
156
|
+
|
|
147
157
|
if (menus.mentionMenuOpen && menus.mention !== null) {
|
|
148
158
|
return <MentionMenu menus={menus} query={menus.mention.query} />;
|
|
149
159
|
}
|
|
160
|
+
|
|
150
161
|
if (menus.referenceMenuOpen) {
|
|
151
162
|
return <ReferenceMenu menus={menus} />;
|
|
152
163
|
}
|
|
164
|
+
|
|
153
165
|
return null;
|
|
154
166
|
}
|
|
@@ -22,6 +22,7 @@ interface DraftQuoteProps {
|
|
|
22
22
|
export function DraftQuote({ chatId, quote, source, onRemove, onSourceGone }: DraftQuoteProps) {
|
|
23
23
|
const { t } = useTranslation('agent-chat');
|
|
24
24
|
const scope = useChatScope();
|
|
25
|
+
|
|
25
26
|
return (
|
|
26
27
|
<div className={`flex items-center gap-2 ${ROW}`}>
|
|
27
28
|
{source === null ? (
|
|
@@ -45,6 +46,7 @@ export function DraftQuote({ chatId, quote, source, onRemove, onSourceGone }: Dr
|
|
|
45
46
|
/* The files the message carries: an image as a thumbnail, anything else by its name and size. */
|
|
46
47
|
export function DraftAttachments({ attachments, onRemove }: { attachments: readonly ChatAttachmentUpload[]; onRemove(index: number): void }) {
|
|
47
48
|
const { t } = useTranslation('agent-chat');
|
|
49
|
+
|
|
48
50
|
return (
|
|
49
51
|
<div className={`flex flex-wrap gap-2 ${ROW}`}>
|
|
50
52
|
{attachments.map((attachment, index) => (
|
|
@@ -18,6 +18,7 @@ interface QueuedMessagesProps {
|
|
|
18
18
|
export function QueuedMessages({ chatId, queue, paused, takingBack, onEdit }: QueuedMessagesProps) {
|
|
19
19
|
const { t } = useTranslation('agent-chat');
|
|
20
20
|
const actions = useChatActions();
|
|
21
|
+
|
|
21
22
|
return (
|
|
22
23
|
<div className="flex flex-col gap-1 border-b border-border px-2 py-1.5">
|
|
23
24
|
{paused && <p className="px-1.5 text-xs text-text-muted">{t('composer.queue.paused')}</p>}
|
|
@@ -26,8 +27,11 @@ export function QueuedMessages({ chatId, queue, paused, takingBack, onEdit }: Qu
|
|
|
26
27
|
scrollTimelineToEnd(chatId, 'instant');
|
|
27
28
|
void actions.sendNow(chatId, message.id).catch(() => undefined);
|
|
28
29
|
};
|
|
30
|
+
|
|
29
31
|
const unqueue = (): void => void actions.unqueue(chatId, message.id).catch(() => undefined);
|
|
32
|
+
|
|
30
33
|
const edit = (): void => onEdit(message);
|
|
34
|
+
|
|
31
35
|
return (
|
|
32
36
|
<ContextMenu.Root key={message.id}>
|
|
33
37
|
<ContextMenu.Trigger className="group/queued flex items-center gap-2 rounded-md px-1.5 py-1 text-xs text-text-muted">
|
|
@@ -32,6 +32,7 @@ export function pastedChipsIn(text: string, from: number, to: number, code: read
|
|
|
32
32
|
const outside = [...code, { from: 0, to: from }, { from: to, to: text.length }];
|
|
33
33
|
const skills = chipRanges(text, [], [...choices.skills], outside);
|
|
34
34
|
const references = namedReferenceRanges(text, choices.referables, choices.referenced, outside);
|
|
35
|
+
|
|
35
36
|
return clipboardChipsOf(
|
|
36
37
|
text.slice(from, to),
|
|
37
38
|
[...skills, ...references].map((range) =>
|
|
@@ -51,9 +52,12 @@ export function withPastedChips(draft: ChatDraft, chips: ClipboardChips, choices
|
|
|
51
52
|
...ids,
|
|
52
53
|
...more.filter((id, index) => !ids.includes(id) && more.indexOf(id) === index)
|
|
53
54
|
];
|
|
55
|
+
|
|
54
56
|
const isChat = (token: ReferenceToken): boolean => choices.referables.some((item) => item.id === token.id && item.kind === 'chat');
|
|
57
|
+
|
|
55
58
|
const chats = chips.references.filter(isChat);
|
|
56
59
|
const references = chips.references.filter((token) => !isChat(token));
|
|
60
|
+
|
|
57
61
|
return {
|
|
58
62
|
...draft,
|
|
59
63
|
mentions: choices.mentions ? add(draft.mentions, chips.mentions) : draft.mentions,
|
|
@@ -80,20 +84,26 @@ export function withPastedChips(draft: ChatDraft, chips: ClipboardChips, choices
|
|
|
80
84
|
export function pasteWithChips(view: EditorView, clip: string, copied: ClipboardChips | null, choices: PasteChoices): ClipboardChips | null {
|
|
81
85
|
if (copied !== null && hasClipboardChips(copied)) {
|
|
82
86
|
view.dispatch(view.state.replaceSelection(copied.text), { scrollIntoView: true, userEvent: 'input.paste' });
|
|
87
|
+
|
|
83
88
|
return copied;
|
|
84
89
|
}
|
|
90
|
+
|
|
85
91
|
if (!mayHoldChips(clip)) {
|
|
86
92
|
return null;
|
|
87
93
|
}
|
|
94
|
+
|
|
88
95
|
const from = view.state.selection.main.from;
|
|
89
96
|
const inserted = view.state.replaceSelection(clip);
|
|
90
97
|
const state = view.state.update(inserted).state;
|
|
91
98
|
const to = state.selection.main.head;
|
|
92
99
|
const found = pastedChipsIn(state.doc.toString(), from, to, codeRanges(state), choices);
|
|
100
|
+
|
|
93
101
|
if (!hasClipboardChips(found)) {
|
|
94
102
|
return null;
|
|
95
103
|
}
|
|
104
|
+
|
|
96
105
|
view.dispatch(inserted, { scrollIntoView: true, userEvent: 'input.paste' });
|
|
106
|
+
|
|
97
107
|
return found;
|
|
98
108
|
}
|
|
99
109
|
|
|
@@ -105,18 +115,24 @@ export function copyWithChipsFromEditor(
|
|
|
105
115
|
references: readonly ReferenceToken[]
|
|
106
116
|
): boolean {
|
|
107
117
|
const { from, to } = view.state.selection.main;
|
|
118
|
+
|
|
108
119
|
if (from === to || event.clipboardData === null) {
|
|
109
120
|
return false;
|
|
110
121
|
}
|
|
122
|
+
|
|
111
123
|
const text = view.state.sliceDoc(from, to);
|
|
112
124
|
const chips = clipboardChipsOf(text, tokenizeChips(text, draft.mentions, draft.skills, references));
|
|
125
|
+
|
|
113
126
|
if (!hasClipboardChips(chips)) {
|
|
114
127
|
return false;
|
|
115
128
|
}
|
|
129
|
+
|
|
116
130
|
writeClipboardChips(event.clipboardData, chips);
|
|
117
131
|
event.preventDefault();
|
|
132
|
+
|
|
118
133
|
if (event.type === 'cut') {
|
|
119
134
|
view.dispatch({ changes: { from, to }, scrollIntoView: true, userEvent: 'delete.cut' });
|
|
120
135
|
}
|
|
136
|
+
|
|
121
137
|
return true;
|
|
122
138
|
}
|
|
@@ -47,6 +47,7 @@ class ReferenceIcon extends WidgetType {
|
|
|
47
47
|
const root = createRoot(holder);
|
|
48
48
|
root.render(createElement(Icon, { icon: REFERENCE_ICONS[this.kind], size: 12 }));
|
|
49
49
|
roots.set(holder, root);
|
|
50
|
+
|
|
50
51
|
return holder;
|
|
51
52
|
}
|
|
52
53
|
|
|
@@ -62,18 +63,23 @@ function chipMarks(state: EditorState): DecorationSet {
|
|
|
62
63
|
const { mentions, skills, references = [] } = state.facet(choices);
|
|
63
64
|
const kinds = new Map(references.map((token) => [token.id, token.kind]));
|
|
64
65
|
const marks: Range<Decoration>[] = [];
|
|
66
|
+
|
|
65
67
|
// `@a.ts` in backticks is code, not a mention.
|
|
66
68
|
for (const range of chipRanges(state.doc.toString(), mentions, skills, codeRanges(state), references)) {
|
|
67
69
|
if (range.kind !== 'reference') {
|
|
68
70
|
marks.push((range.kind === 'skill' ? skillMark : mentionMark).range(range.from, range.to));
|
|
71
|
+
|
|
69
72
|
continue;
|
|
70
73
|
}
|
|
74
|
+
|
|
71
75
|
marks.push(referenceMark.range(range.from, range.to));
|
|
72
76
|
const kind = kinds.get(range.value);
|
|
77
|
+
|
|
73
78
|
if (kind !== undefined && kind !== null) {
|
|
74
79
|
marks.push(Decoration.replace({ widget: new ReferenceIcon(kind) }).range(range.from, range.from + 1));
|
|
75
80
|
}
|
|
76
81
|
}
|
|
82
|
+
|
|
77
83
|
return Decoration.set(marks, true);
|
|
78
84
|
}
|
|
79
85
|
|
|
@@ -33,17 +33,21 @@ export interface CodeRange {
|
|
|
33
33
|
/* The code spans and fenced blocks between `from` and `to`, as far as the parser has got. */
|
|
34
34
|
export function codeRanges(state: EditorState, from = 0, to = state.doc.length): CodeRange[] {
|
|
35
35
|
const found: CodeRange[] = [];
|
|
36
|
+
|
|
36
37
|
syntaxTree(state).iterate({
|
|
37
38
|
from,
|
|
38
39
|
to,
|
|
39
40
|
enter: (node) => {
|
|
40
41
|
if (node.name === 'InlineCode' || node.name === 'FencedCode') {
|
|
41
42
|
found.push({ from: node.from, to: node.to, kind: node.name === 'InlineCode' ? 'inline' : 'fence' });
|
|
43
|
+
|
|
42
44
|
return false;
|
|
43
45
|
}
|
|
46
|
+
|
|
44
47
|
return undefined;
|
|
45
48
|
}
|
|
46
49
|
});
|
|
50
|
+
|
|
47
51
|
return found;
|
|
48
52
|
}
|
|
49
53
|
|
|
@@ -61,16 +65,20 @@ function codeDecorations(view: EditorView): DecorationSet {
|
|
|
61
65
|
const { state } = view;
|
|
62
66
|
const decorations: Array<Range<Decoration>> = [];
|
|
63
67
|
const fenceLines = new Set<number>();
|
|
68
|
+
|
|
64
69
|
for (const visible of view.visibleRanges) {
|
|
65
70
|
for (const range of codeRanges(state, visible.from, visible.to)) {
|
|
66
71
|
if (range.kind === 'inline') {
|
|
67
72
|
decorations.push(inlineCodeMark.range(range.from, range.to));
|
|
73
|
+
|
|
68
74
|
continue;
|
|
69
75
|
}
|
|
76
|
+
|
|
70
77
|
const first = state.doc.lineAt(range.from).number;
|
|
71
78
|
const last = state.doc.lineAt(range.to).number;
|
|
72
79
|
const start = state.doc.lineAt(Math.max(range.from, visible.from)).number;
|
|
73
80
|
const end = state.doc.lineAt(Math.min(range.to, visible.to)).number;
|
|
81
|
+
|
|
74
82
|
for (let number = start; number <= end; number++) {
|
|
75
83
|
if (!fenceLines.has(number)) {
|
|
76
84
|
fenceLines.add(number);
|
|
@@ -79,6 +87,7 @@ function codeDecorations(view: EditorView): DecorationSet {
|
|
|
79
87
|
}
|
|
80
88
|
}
|
|
81
89
|
}
|
|
90
|
+
|
|
82
91
|
return Decoration.set(decorations, true);
|
|
83
92
|
}
|
|
84
93
|
|
|
@@ -12,11 +12,13 @@ export function insertAtSelection(state: EditorState, text: string): Transaction
|
|
|
12
12
|
if (text === '') {
|
|
13
13
|
return {};
|
|
14
14
|
}
|
|
15
|
+
|
|
15
16
|
const { from, to } = state.selection.main;
|
|
16
17
|
const before = state.doc.sliceString(Math.max(0, from - 1), from);
|
|
17
18
|
const after = state.doc.sliceString(to, to + 1);
|
|
18
19
|
const lead = before !== '' && !isSpace(before) && !isSpace(text) ? ' ' : '';
|
|
19
20
|
const trail = (after === '' || !isSpace(after)) && !/\s$/.test(text) ? ' ' : '';
|
|
20
21
|
const insert = `${lead}${text}${trail}`;
|
|
22
|
+
|
|
21
23
|
return { changes: { from, to, insert }, selection: { anchor: from + insert.length }, scrollIntoView: true, userEvent: 'input' };
|
|
22
24
|
}
|