@adecore/agents-react 0.21.2 → 0.22.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/model-requirement.d.ts +6 -0
- package/dist/agents/model-requirement.js +26 -0
- package/dist/chat/chat-client.d.ts +2 -1
- package/dist/chat/chat-client.js +2 -0
- package/dist/chat/drafts.d.ts +2 -1
- package/dist/chat/drafts.js +15 -5
- package/dist/chat/logic/picker-entries.d.ts +10 -0
- package/dist/chat/logic/picker-entries.js +19 -0
- package/dist/chat/logic/timeline.d.ts +5 -0
- package/dist/chat/logic/timeline.js +10 -1
- package/dist/chat/prompt-hold.d.ts +8 -0
- package/dist/chat/prompt-hold.js +16 -0
- package/dist/chat/quote.d.ts +4 -0
- package/dist/chat/quote.js +19 -0
- package/dist/chat/stash.d.ts +8 -2
- package/dist/chat/stash.js +21 -5
- package/dist/chat/subagent-list.d.ts +7 -1
- package/dist/chat/subagent-list.js +13 -1
- package/dist/chat/timeline-scroll.d.ts +6 -2
- package/dist/chat/timeline-scroll.js +12 -7
- package/dist/chat/ui/ChatActivity.js +22 -8
- package/dist/chat/ui/Composer.js +103 -19
- package/dist/chat/ui/ComposerInput.d.ts +3 -1
- package/dist/chat/ui/ComposerInput.js +13 -4
- package/dist/chat/ui/Pickers.js +26 -18
- package/dist/chat/ui/PromptComposer.d.ts +8 -1
- package/dist/chat/ui/PromptComposer.js +18 -6
- package/dist/chat/ui/QuoteButton.js +2 -2
- package/dist/chat/ui/SubagentControls.d.ts +4 -2
- package/dist/chat/ui/SubagentControls.js +12 -8
- package/dist/chat/ui/SubagentInfo.d.ts +1 -1
- package/dist/chat/ui/SubagentInfo.js +9 -15
- package/dist/chat/ui/SubagentStopButton.js +2 -1
- package/dist/chat/ui/Timeline.js +24 -3
- package/dist/chat/ui/composer/menu-aria.d.ts +10 -0
- package/dist/chat/ui/composer/menu-aria.js +18 -0
- package/dist/chat/ui/quote-selection.d.ts +5 -2
- package/dist/chat/ui/quote-selection.js +3 -2
- package/dist/chat/ui/rows/MessageRows.js +24 -5
- package/dist/chat/ui/rows/Rows.js +1 -1
- package/dist/chat/ui/rows/WorkRows.d.ts +3 -1
- package/dist/chat/ui/rows/WorkRows.js +4 -4
- package/dist/host.d.ts +1 -0
- package/dist/host.js +1 -0
- package/dist/locales/en/agent-chat.json +30 -7
- package/dist/locales/nl/agent-chat.json +30 -7
- package/dist/state/chats.d.ts +2 -1
- package/dist/state/chats.js +19 -1
- package/package.json +24 -4
- package/src/agents/model-requirement.ts +36 -0
- package/src/chat/chat-client.ts +5 -0
- package/src/chat/drafts.ts +27 -7
- package/src/chat/logic/picker-entries.ts +25 -0
- package/src/chat/logic/timeline.ts +17 -2
- package/src/chat/prompt-hold.ts +27 -0
- package/src/chat/quote.ts +20 -0
- package/src/chat/stash.ts +31 -5
- package/src/chat/subagent-list.ts +19 -5
- package/src/chat/timeline-scroll.ts +24 -10
- package/src/chat/ui/ChatActivity.tsx +35 -9
- package/src/chat/ui/Composer.tsx +197 -52
- package/src/chat/ui/ComposerInput.tsx +18 -3
- package/src/chat/ui/Pickers.tsx +51 -30
- package/src/chat/ui/PromptComposer.tsx +37 -8
- package/src/chat/ui/QuoteButton.tsx +3 -2
- package/src/chat/ui/SubagentControls.tsx +22 -19
- package/src/chat/ui/SubagentInfo.tsx +24 -42
- package/src/chat/ui/SubagentStopButton.tsx +2 -1
- package/src/chat/ui/Timeline.tsx +26 -4
- package/src/chat/ui/composer/menu-aria.ts +29 -0
- package/src/chat/ui/quote-selection.ts +6 -4
- package/src/chat/ui/rows/MessageRows.tsx +44 -5
- package/src/chat/ui/rows/Rows.tsx +3 -1
- package/src/chat/ui/rows/WorkRows.tsx +15 -1
- package/src/host.ts +3 -0
- package/src/locales/en/agent-chat.json +30 -7
- package/src/locales/nl/agent-chat.json +30 -7
- package/src/state/chats.ts +31 -2
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Fragment
|
|
3
|
-
import clsx from 'clsx';
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment } from 'react';
|
|
4
3
|
import { useTranslation } from 'react-i18next';
|
|
5
|
-
import { ChevronDown } from 'lucide-react';
|
|
6
4
|
import { entryTimeOf, statusWordOf, subagentFacts, taskIdOf } from '../subagent-list.js';
|
|
7
5
|
import { useSubagentItem } from '../subagent-view.js';
|
|
8
6
|
import { StatusIcon } from './ChatActivity.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
7
|
+
import { SubagentActions, SubagentBreadcrumb } from './SubagentControls.js';
|
|
8
|
+
import { useNow } from '@adecore/ui';
|
|
11
9
|
import { useModelName } from '../../agents/model-name.js';
|
|
12
10
|
import { useChatRow } from '../../state/chats.js';
|
|
13
11
|
import { chatHost } from '../../host.js';
|
|
14
12
|
import { useChatScope } from '../../scope.js';
|
|
15
13
|
/*
|
|
16
|
-
* What a sub-agent is, over its conversation: its state and time, the model it runs on
|
|
17
|
-
* spent, and the
|
|
14
|
+
* What a sub-agent is, over its conversation: the way down to it, its state and time, the model it runs on
|
|
15
|
+
* and what it spent, and the way out. Only the way down and out for a conversation whose row the thread lost.
|
|
18
16
|
*/
|
|
19
17
|
export function SubagentInfo({ chatId, toolUseId }) {
|
|
20
18
|
const { t } = useTranslation('agent-chat');
|
|
@@ -24,11 +22,7 @@ export function SubagentInfo({ chatId, toolUseId }) {
|
|
|
24
22
|
const task = chatHost().tasks.useTask(id, item === null ? null : taskIdOf(item));
|
|
25
23
|
const modelName = useModelName(provider, item?.model ?? '');
|
|
26
24
|
const now = useNow(1000, item?.status === 'running');
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
const word = statusWordOf(item, task);
|
|
32
|
-
const facts = subagentFacts(item, item.model === undefined ? null : modelName);
|
|
33
|
-
return (_jsx("section", { "aria-label": t('subagents.info.label'), className: "shrink-0 border-b border-border px-4 py-2", children: _jsxs("div", { className: "chat-column-content flex flex-col gap-2", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2 text-xs text-text-faint", 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" }), item.prompt && (_jsxs("button", { type: "button", className: "flex shrink-0 items-center gap-1.5 text-text-muted hover:text-text", "aria-expanded": open, onClick: () => setOpen((current) => !current), children: [_jsx(Icon, { icon: ChevronDown, size: 12, className: clsx('transition-transform', open && 'rotate-180') }), open ? t('subagents.info.hideTask') : t('subagents.info.showTask')] }))] }), open && item.prompt && (_jsx("div", { className: "max-h-60 overflow-auto rounded-md border border-border bg-surface-raised px-3 py-2 text-xs select-text", children: _jsx(Markdown, { text: item.prompt }) }))] }) }));
|
|
25
|
+
const word = item === null ? null : statusWordOf(item, task);
|
|
26
|
+
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 px-4 py-1.5", 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 })] }) }));
|
|
34
28
|
}
|
|
@@ -19,7 +19,8 @@ export function SubagentStopButton({ chatId, item, size = 'xs', className }) {
|
|
|
19
19
|
}
|
|
20
20
|
const send = () => {
|
|
21
21
|
void actions.stopSubagent(chatId, item.toolUseId).catch((e) => {
|
|
22
|
-
const
|
|
22
|
+
const stale = e instanceof Error && 'code' in e && e.code === 'subagent-stale';
|
|
23
|
+
const description = stale ? t('subagents.stale') : e instanceof Error ? e.message : t('subagents.noAnswer');
|
|
23
24
|
chatHost().notify({ kind: 'error', title: t('subagents.stopFailed'), description });
|
|
24
25
|
});
|
|
25
26
|
};
|
package/dist/chat/ui/Timeline.js
CHANGED
|
@@ -37,6 +37,8 @@ import { AgentIcon } from '../../agents/AgentIcon.js';
|
|
|
37
37
|
import { useModelName } from '../../agents/model-name.js';
|
|
38
38
|
import { ContextMenu, EmptyState, ErrorBoundary, Icon, SectionLabel } from '@adecore/ui';
|
|
39
39
|
const ESTIMATED_ROW_PX = 56;
|
|
40
|
+
// How long a jump waits for a thread that is still loading before it calls the item missing.
|
|
41
|
+
const JUMP_THREAD_WAIT_MS = 8000;
|
|
40
42
|
/* A jump puts its row at the top and stops following the end, so the reader stays where they landed. */
|
|
41
43
|
function showRowAtTop(virtualizer, followRef, index) {
|
|
42
44
|
followRef.current = false;
|
|
@@ -347,9 +349,16 @@ export function Timeline({ chatId, composer, overlay }) {
|
|
|
347
349
|
if (index !== -1) {
|
|
348
350
|
setSeeking(null);
|
|
349
351
|
showRowAtTop(virtualizer, followRef, index);
|
|
352
|
+
if (seeking.options?.flash) {
|
|
353
|
+
useTimelineFlash.getState().flash(seeking.key, seeking.itemId, false);
|
|
354
|
+
}
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
// The thread has not loaded yet, so nothing says the item is missing; the effect below bounds the wait.
|
|
358
|
+
if (items === undefined) {
|
|
350
359
|
return;
|
|
351
360
|
}
|
|
352
|
-
const item = items
|
|
361
|
+
const item = items[seeking.itemId];
|
|
353
362
|
if (item === undefined && cursor !== null) {
|
|
354
363
|
loadEarlier();
|
|
355
364
|
return;
|
|
@@ -360,10 +369,22 @@ export function Timeline({ chatId, composer, overlay }) {
|
|
|
360
369
|
return;
|
|
361
370
|
}
|
|
362
371
|
setSeeking(null);
|
|
372
|
+
seeking.options?.onMissing?.();
|
|
363
373
|
}, [seeking, rows, items, turns, virtualizer, cursor, loadEarlier, keyOf, chatId]);
|
|
364
|
-
|
|
374
|
+
const waitingForThread = seeking !== null && items === undefined;
|
|
375
|
+
useEffect(() => {
|
|
376
|
+
if (!waitingForThread) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const timer = window.setTimeout(() => {
|
|
380
|
+
setSeeking(null);
|
|
381
|
+
seeking?.options?.onMissing?.();
|
|
382
|
+
}, JUMP_THREAD_WAIT_MS);
|
|
383
|
+
return () => window.clearTimeout(timer);
|
|
384
|
+
}, [waitingForThread, seeking]);
|
|
385
|
+
useEffect(() => registerItemJumper(keyOf(chatId), (itemId, options) => {
|
|
365
386
|
setLanding(null);
|
|
366
|
-
setSeeking({ itemId, key: keyOf(chatId) });
|
|
387
|
+
setSeeking({ itemId, key: keyOf(chatId), options });
|
|
367
388
|
}), [keyOf, chatId]);
|
|
368
389
|
useLayoutEffect(() => {
|
|
369
390
|
if (!landing || landing.key !== keyOf(chatId)) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface ComposerMenuAriaInput {
|
|
2
|
+
label: string;
|
|
3
|
+
listId: string;
|
|
4
|
+
open: boolean;
|
|
5
|
+
activeIndex: number;
|
|
6
|
+
count: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function clampMenuIndex(index: number, count: number): number;
|
|
9
|
+
export declare function optionIdOf(listId: string, index: number): string;
|
|
10
|
+
export declare function composerMenuAria({ label, listId, open, activeIndex, count }: ComposerMenuAriaInput): Record<string, string>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* The index that still points at an option once the list has shrunk under a stale one. */
|
|
2
|
+
export function clampMenuIndex(index, count) {
|
|
3
|
+
return Math.min(index, Math.max(count - 1, 0));
|
|
4
|
+
}
|
|
5
|
+
export function optionIdOf(listId, index) {
|
|
6
|
+
return `${listId}-${index}`;
|
|
7
|
+
}
|
|
8
|
+
/*
|
|
9
|
+
* The attributes the editor's content element takes while a suggestion menu stands beside it. The
|
|
10
|
+
* element keeps its `textbox` role; ARIA 1.2 lets it name the option the arrow keys are on.
|
|
11
|
+
*/
|
|
12
|
+
export function composerMenuAria({ label, listId, open, activeIndex, count }) {
|
|
13
|
+
return {
|
|
14
|
+
'aria-label': label,
|
|
15
|
+
'aria-autocomplete': 'list',
|
|
16
|
+
...(open && count > 0 ? { 'aria-controls': listId, 'aria-activedescendant': optionIdOf(listId, activeIndex) } : {})
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
export type QuoteTaker = (quote: string) => void;
|
|
1
|
+
export type QuoteTaker = (quote: string, itemId: string | null) => void;
|
|
2
2
|
export declare const QuoteTakerContext: import("react").Context<((take: QuoteTaker) => () => void) | null>;
|
|
3
|
-
export declare function selectedAnswerQuote(selection: Selection | null, thread: HTMLElement | null):
|
|
3
|
+
export declare function selectedAnswerQuote(selection: Selection | null, thread: HTMLElement | null): {
|
|
4
|
+
text: string;
|
|
5
|
+
itemId: string | null;
|
|
6
|
+
} | null;
|
|
@@ -28,7 +28,7 @@ function selectsOutside(range, answer) {
|
|
|
28
28
|
}
|
|
29
29
|
return false;
|
|
30
30
|
}
|
|
31
|
-
/* The quote the selection makes, when it lies in one answer of this thread. */
|
|
31
|
+
/* The quote the selection makes, when it lies in one answer of this thread, and the reply it was taken from. */
|
|
32
32
|
export function selectedAnswerQuote(selection, thread) {
|
|
33
33
|
if (thread === null || selection === null || selection.isCollapsed || selection.rangeCount === 0) {
|
|
34
34
|
return null;
|
|
@@ -42,5 +42,6 @@ export function selectedAnswerQuote(selection, thread) {
|
|
|
42
42
|
if (!inside && selectsOutside(range, answer)) {
|
|
43
43
|
return null;
|
|
44
44
|
}
|
|
45
|
-
|
|
45
|
+
const text = quoteOf(selection.toString());
|
|
46
|
+
return text === null ? null : { text, itemId: answer.closest('[data-find-item]')?.getAttribute('data-find-item') ?? null };
|
|
46
47
|
}
|
|
@@ -3,10 +3,13 @@ import { useContext, useMemo, useState } from 'react';
|
|
|
3
3
|
import clsx from 'clsx';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { Bot, Brain, Check, ChevronDown, CircleAlert, File, Info, MessageCircleQuestionMark, Reply, TriangleAlert, X } from 'lucide-react';
|
|
6
|
+
import { WAITING_NOTE_PREFIX } from '@adecore/agent-contracts';
|
|
6
7
|
import { fileBadge, formatBytes, isImageAttachment } from '../../attachments.js';
|
|
7
8
|
import { useChatRow, useCurrentItem } from '../../../state/chats.js';
|
|
8
9
|
import { chatHost } from '../../../host.js';
|
|
9
10
|
import { useChatScope } from '../../../scope.js';
|
|
11
|
+
import { splitQuote } from '../../quote.js';
|
|
12
|
+
import { jumpToTimelineItem } from '../../timeline-scroll.js';
|
|
10
13
|
import { useProviders } from '../../../state/providers.js';
|
|
11
14
|
import { ChatReferenceChip } from '../ChatReferenceChip.js';
|
|
12
15
|
import { ImageThumb } from '../ImageView.js';
|
|
@@ -55,8 +58,24 @@ export function UserRow({ chatId, item }) {
|
|
|
55
58
|
const long = item.text.split('\n').length > USER_FOLD_LINES || item.text.length > USER_FOLD_CHARS;
|
|
56
59
|
const attachments = item.attachments ?? [];
|
|
57
60
|
const chats = item.chats ?? [];
|
|
61
|
+
const [missing, setMissing] = useState(false);
|
|
62
|
+
const { keyOf } = useChatScope();
|
|
58
63
|
useOpenForFind(item.id, 'text', setOpen);
|
|
59
|
-
|
|
64
|
+
// A quote only leads somewhere when the message still opens with the blockquote it was sent with.
|
|
65
|
+
const quoted = item.quoteSource === undefined ? null : splitQuote(item.text);
|
|
66
|
+
const source = item.quoteSource?.itemId;
|
|
67
|
+
function goToSource() {
|
|
68
|
+
if (source !== undefined) {
|
|
69
|
+
setMissing(false);
|
|
70
|
+
jumpToTimelineItem(keyOf(chatId), source, { flash: true, onMissing: () => setMissing(true) });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return (_jsxs("div", { "data-find-item": item.id, className: "flex flex-col items-end", children: [_jsx(MessageHeading, { children: t('rows.user.heading') }), attachments.length > 0 && (_jsx("div", { className: "mb-1.5 flex max-w-[80%] flex-wrap justify-end gap-1.5", children: attachments.map((attachment) => isImageAttachment(attachment.mime) ? (_jsx(AttachmentThumb, { chatId: chatId, attachment: attachment }, attachment.id)) : (_jsx(AttachmentLink, { chatId: chatId, attachment: attachment }, attachment.id))) })), chats.length > 0 && (_jsx("div", { className: "mb-1.5 flex max-w-[80%] flex-wrap justify-end gap-1.5", children: chats.map((id) => (_jsx(ChatReferenceChip, { chatId: id }, id))) })), item.uiChoice !== undefined && _jsx(ChoiceLine, { chatId: chatId, choice: item.uiChoice }), item.text !== '' && (_jsxs("div", { className: "relative max-w-[80%] rounded-2xl bg-surface-active px-3.5 py-2.5 text-sm wrap-anywhere text-text select-text", children: [_jsx("div", { "data-find-field": "text", className: clsx(long && !open && FOLD), children: quoted !== null && source !== undefined ? (_jsxs(_Fragment, { children: [_jsx(Tooltip, { label: t('composer.quote.goToSource'), children: _jsx("div", { role: "button", tabIndex: 0, className: "mb-2 cursor-pointer rounded-sm border-l-2 border-border-strong pl-2.5 text-text-muted hover:bg-text/5 hover:text-text focus-visible:outline-2 focus-visible:outline-accent", onClick: goToSource, onKeyDown: (e) => {
|
|
74
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
goToSource();
|
|
77
|
+
}
|
|
78
|
+
}, children: _jsx(MessageMarkdown, { text: quoted.quote }) }) }), missing && _jsx("div", { className: "mb-2 text-xs text-text-faint", children: t('rows.user.quoteGone') }), quoted.rest !== '' && _jsx(MessageMarkdown, { text: quoted.rest, mentions: item.mentions, skills: item.skills })] })) : (_jsx(MessageMarkdown, { text: item.text, mentions: item.mentions, skills: item.skills })) }), long && (_jsxs("button", { className: "mt-1 flex items-center gap-1 text-xs text-text-muted hover:text-text", onClick: () => setOpen((o) => !o), children: [_jsx(Icon, { icon: ChevronDown, size: 12, className: clsx('transition-transform', open && 'rotate-180') }), ' ', open ? t('rows.user.showLess') : t('rows.user.showAll')] }))] }))] }));
|
|
60
79
|
}
|
|
61
80
|
/*
|
|
62
81
|
* The choice a message answers. The label stays out of the message, which holds exactly what the agent
|
|
@@ -174,16 +193,16 @@ export function NoteRow({ id, level, text, from }) {
|
|
|
174
193
|
const head = breakAt === -1 ? text : text.slice(0, breakAt);
|
|
175
194
|
const rest = breakAt === -1 ? '' : text.slice(breakAt + 1).trim();
|
|
176
195
|
useOpenForFind(id, 'output', setOpen);
|
|
177
|
-
return (_jsxs("div", { "data-find-item": id, className: clsx('-mx-1 mb-0.5 flex min-h-7 items-start gap-2 px-1 py-1 text-xs', level === 'error' ? 'text-status-error' : level === 'warning' ? 'text-status-needs-you' : 'text-text-faint'), children: [_jsx("span", { className: ROW_GUTTER, children: NOTE_ICON[level] }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-x-2", children: [_jsx("span", { "data-find-field": "summary", className: "select-text", children: head }), rest !== '' && (_jsx("button", { className: "text-text-muted hover:text-text", onClick: () => setOpen(!open), children: open ? t('rows.note.hide') : t('rows.note.show') })), from !== undefined && _jsx(OpenChatButton, { chatId: from })] }), open && rest !== '' && (_jsx("div", { "data-find-field": "output", className: "mt-1 text-text-muted select-text", children: _jsx(MessageMarkdown, { text: rest }) }))] })] }));
|
|
196
|
+
return (_jsxs("div", { "data-find-item": id, className: clsx('-mx-1 mb-0.5 flex min-h-7 items-start gap-2 px-1 py-1 text-xs', level === 'error' ? 'text-status-error' : level === 'warning' ? 'text-status-needs-you' : 'text-text-faint'), children: [_jsx("span", { className: ROW_GUTTER, children: NOTE_ICON[level] }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-x-2", children: [_jsx("span", { "data-find-field": "summary", className: "select-text", children: head }), rest !== '' && (_jsx("button", { className: "text-text-muted hover:text-text", onClick: () => setOpen(!open), children: open ? t('rows.note.hide') : t('rows.note.show') })), from !== undefined && _jsx(OpenChatButton, { chatId: from, label: id.startsWith(WAITING_NOTE_PREFIX) ? 'openWaiting' : 'openFork' })] }), open && rest !== '' && (_jsx("div", { "data-find-field": "output", className: "mt-1 text-text-muted select-text", children: _jsx(MessageMarkdown, { text: rest }) }))] })] }));
|
|
178
197
|
}
|
|
179
|
-
/* "Open fork" on a note a fork sent, while that
|
|
180
|
-
function OpenChatButton({ chatId }) {
|
|
198
|
+
/* "Open fork" on a note a fork sent, or "Open to answer" on one about a waiting child, while that chat is still in the project. */
|
|
199
|
+
function OpenChatButton({ chatId, label }) {
|
|
181
200
|
const { t } = useTranslation('agent-chat');
|
|
182
201
|
const place = chatHost().useChatPlace(chatId);
|
|
183
202
|
if (place.title === null) {
|
|
184
203
|
return null;
|
|
185
204
|
}
|
|
186
|
-
return (_jsx("button", { className: "text-text-muted hover:text-text", onClick: place.go, children: t(
|
|
205
|
+
return (_jsx("button", { className: "text-text-muted hover:text-text", onClick: place.go, children: t(`rows.note.${label}`) }));
|
|
187
206
|
}
|
|
188
207
|
/*
|
|
189
208
|
* The header of a turn the agent started itself, in place of a message of the person. What the
|
|
@@ -47,7 +47,7 @@ export function Row({ row, chatId, toggleGroup, toggleTurn, toggleSubagent, open
|
|
|
47
47
|
case 'forks':
|
|
48
48
|
return _jsx(ForksRow, { chatId: chatId, turnId: row.turnId });
|
|
49
49
|
case 'changed-files':
|
|
50
|
-
return _jsx(ChangedFilesRow, { chatId: chatId, turnId: row.turnId, tools: row.tools, diff: row.diff, checkpoint: row.checkpoint });
|
|
50
|
+
return (_jsx(ChangedFilesRow, { chatId: chatId, turnId: row.turnId, tools: row.tools, diff: row.diff, checkpoint: row.checkpoint, baseMoved: row.baseMoved }));
|
|
51
51
|
case 'approval':
|
|
52
52
|
return _jsx(ApprovalHistoryRow, { item: row.item });
|
|
53
53
|
case 'question':
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { ChatCheckpointDiff, ChatToolItem, ChatTurnItem } from '@adecore/agent-contracts';
|
|
3
|
+
import type { BaseMoved } from '../../logic/timeline.js';
|
|
3
4
|
export declare function ToggleLine({ icon, label, detail, open, onToggle, failed, live, trailing, className }: {
|
|
4
5
|
icon: ReactNode;
|
|
5
6
|
label: string;
|
|
@@ -39,10 +40,11 @@ export declare function TurnFoldRow({ turn, label, work, expanded, onToggle }: {
|
|
|
39
40
|
expanded: boolean;
|
|
40
41
|
onToggle(): void;
|
|
41
42
|
}): import("react").JSX.Element;
|
|
42
|
-
export declare function ChangedFilesRow({ tools, diff, checkpoint, chatId, turnId }: {
|
|
43
|
+
export declare function ChangedFilesRow({ tools, diff, checkpoint, baseMoved, chatId, turnId }: {
|
|
43
44
|
tools: ChatToolItem[];
|
|
44
45
|
diff: ChatCheckpointDiff | null;
|
|
45
46
|
checkpoint: boolean;
|
|
47
|
+
baseMoved?: BaseMoved | undefined;
|
|
46
48
|
chatId: string;
|
|
47
49
|
turnId: string;
|
|
48
50
|
}): import("react").JSX.Element;
|
|
@@ -91,7 +91,7 @@ function CheckpointFileBody({ file }) {
|
|
|
91
91
|
return (_jsx(DiffSuspense, { children: _jsx(UnifiedDiff, { change: file }) }));
|
|
92
92
|
}
|
|
93
93
|
/* The frame of a changed files card: a header that folds the list of files away. */
|
|
94
|
-
function ChangedFilesCard({ count, truncated, added, deleted, revealNonce = 0, children }) {
|
|
94
|
+
function ChangedFilesCard({ count, truncated, added, deleted, revealNonce = 0, baseMoved, children }) {
|
|
95
95
|
const { t } = useTranslation('agent-chat');
|
|
96
96
|
const [expanded, setExpanded] = useState(false);
|
|
97
97
|
// A jump opens the card, also one that mounted it by scrolling it on screen; the flash is gone by the next visit.
|
|
@@ -100,7 +100,7 @@ function ChangedFilesCard({ count, truncated, added, deleted, revealNonce = 0, c
|
|
|
100
100
|
setSeenReveal(revealNonce);
|
|
101
101
|
setExpanded(true);
|
|
102
102
|
}
|
|
103
|
-
return (_jsxs("div", { className: "mb-3 overflow-hidden rounded-lg border border-border bg-surface-raised", children: [_jsxs("button", { className: "flex w-full items-center gap-2 px-3 py-2 text-left text-xs text-text-muted hover:bg-surface-hover", "aria-expanded": expanded, onClick: () => setExpanded((value) => !value), children: [_jsx(Icon, { icon: FileDiff, size: 12, className: "shrink-0" }), _jsx("span", { className: "shrink-0 font-medium text-text", children: t('work.changedFiles', { count }) }), truncated && _jsx("span", { className: "min-w-0 truncate text-text-faint", children: t('work.andMore') }), _jsx("span", { className: "grow" }), added !== undefined && _jsxs("span", { className: "text-chat-added tabular-nums", children: ["+", added] }), deleted !== undefined && _jsxs("span", { className: "text-chat-removed tabular-nums", children: ["-", deleted] }), _jsx(Icon, { icon: ChevronRight, size: 12, className: clsx('shrink-0 text-text-faint transition-transform', expanded && 'rotate-90') })] }), expanded && children] }));
|
|
103
|
+
return (_jsxs("div", { className: "mb-3 overflow-hidden rounded-lg border border-border bg-surface-raised", children: [_jsxs("button", { className: "flex w-full items-center gap-2 px-3 py-2 text-left text-xs text-text-muted hover:bg-surface-hover", "aria-expanded": expanded, onClick: () => setExpanded((value) => !value), children: [_jsx(Icon, { icon: FileDiff, size: 12, className: "shrink-0" }), _jsx("span", { className: "shrink-0 font-medium text-text", children: baseMoved === undefined ? t('work.changedFiles', { count }) : t('work.folderChanges', { count }) }), truncated && _jsx("span", { className: "min-w-0 truncate text-text-faint", children: t('work.andMore') }), _jsx("span", { className: "grow" }), added !== undefined && _jsxs("span", { className: "text-chat-added tabular-nums", children: ["+", added] }), deleted !== undefined && _jsxs("span", { className: "text-chat-removed tabular-nums", children: ["-", deleted] }), _jsx(Icon, { icon: ChevronRight, size: 12, className: clsx('shrink-0 text-text-faint transition-transform', expanded && 'rotate-90') })] }), baseMoved !== undefined && (_jsx("div", { className: "pr-3 pb-2 pl-8 text-xs text-text-faint", children: t('work.baseMoved', { from: baseMoved.from.slice(0, 7), to: baseMoved.to.slice(0, 7) }) })), expanded && children] }));
|
|
104
104
|
}
|
|
105
105
|
/*
|
|
106
106
|
* The files a settled turn changed, as a card; each file opens its diff in place. The host's
|
|
@@ -108,7 +108,7 @@ function ChangedFilesCard({ count, truncated, added, deleted, revealNonce = 0, c
|
|
|
108
108
|
* unified diffs the CLI reported, then the before and after of its own edits. A turn that has a
|
|
109
109
|
* checkpoint but no stored diff asks the host for one, which is also how a running turn gets it.
|
|
110
110
|
*/
|
|
111
|
-
export function ChangedFilesRow({ tools, diff, checkpoint, chatId, turnId }) {
|
|
111
|
+
export function ChangedFilesRow({ tools, diff, checkpoint, baseMoved, chatId, turnId }) {
|
|
112
112
|
const [open, setOpen] = useState({});
|
|
113
113
|
const [fetched, setFetched] = useState(null);
|
|
114
114
|
const actions = useChatActions();
|
|
@@ -135,7 +135,7 @@ export function ChangedFilesRow({ tools, diff, checkpoint, chatId, turnId }) {
|
|
|
135
135
|
const revealNonce = flash !== null && flash.opens && flash.key === keyOf(chatId) && flash.rowId === `files-${turnId}` ? flash.nonce : 0;
|
|
136
136
|
if (checkpointDiff !== null && checkpointDiff.files.length > 0) {
|
|
137
137
|
const files = checkpointDiff.files;
|
|
138
|
-
return (_jsx(ChangedFilesCard, { revealNonce: revealNonce, count: files.length, truncated: checkpointDiff.truncated, added: files.reduce((sum, file) => sum + file.added, 0), deleted: files.reduce((sum, file) => sum + file.deleted, 0), children: files.map((file) => (_jsx(ChangedFileLine, { path: file.path, open: open[file.path] === true, onToggle: () => setOpen((o) => ({ ...o, [file.path]: !o[file.path] })), trailing: _jsxs(_Fragment, { children: [_jsxs("span", { className: "text-chat-added tabular-nums", children: ["+", file.added] }), _jsxs("span", { className: "text-chat-removed tabular-nums", children: ["-", file.deleted] })] }), children: _jsx(CheckpointFileBody, { file: file }) }, file.path))) }));
|
|
138
|
+
return (_jsx(ChangedFilesCard, { revealNonce: revealNonce, baseMoved: baseMoved, count: files.length, truncated: checkpointDiff.truncated, added: files.reduce((sum, file) => sum + file.added, 0), deleted: files.reduce((sum, file) => sum + file.deleted, 0), children: files.map((file) => (_jsx(ChangedFileLine, { path: file.path, open: open[file.path] === true, onToggle: () => setOpen((o) => ({ ...o, [file.path]: !o[file.path] })), trailing: _jsxs(_Fragment, { children: [_jsxs("span", { className: "text-chat-added tabular-nums", children: ["+", file.added] }), _jsxs("span", { className: "text-chat-removed tabular-nums", children: ["-", file.deleted] })] }), children: _jsx(CheckpointFileBody, { file: file }) }, file.path))) }));
|
|
139
139
|
}
|
|
140
140
|
return _jsx(ProviderChangedFiles, { tools: tools, open: open, setOpen: setOpen, revealNonce: revealNonce });
|
|
141
141
|
}
|
package/dist/host.d.ts
CHANGED
package/dist/host.js
CHANGED
|
@@ -91,7 +91,8 @@
|
|
|
91
91
|
"user": {
|
|
92
92
|
"heading": "You",
|
|
93
93
|
"showAll": "Show all",
|
|
94
|
-
"showLess": "Show less"
|
|
94
|
+
"showLess": "Show less",
|
|
95
|
+
"quoteGone": "The reply this quote came from is no longer in this chat."
|
|
95
96
|
},
|
|
96
97
|
"report": {
|
|
97
98
|
"heading": "Report"
|
|
@@ -107,7 +108,8 @@
|
|
|
107
108
|
"note": {
|
|
108
109
|
"hide": "Hide",
|
|
109
110
|
"show": "Show",
|
|
110
|
-
"openFork": "Open fork"
|
|
111
|
+
"openFork": "Open fork",
|
|
112
|
+
"openWaiting": "Open to answer"
|
|
111
113
|
},
|
|
112
114
|
"compaction": {
|
|
113
115
|
"plain": "Context compacted",
|
|
@@ -165,6 +167,9 @@
|
|
|
165
167
|
},
|
|
166
168
|
"changedFiles_one": "{{count}} changed file",
|
|
167
169
|
"changedFiles_other": "{{count}} changed files",
|
|
170
|
+
"folderChanges_one": "{{count}} change in the folder this turn",
|
|
171
|
+
"folderChanges_other": "{{count}} changes in the folder this turn",
|
|
172
|
+
"baseMoved": "The branch moved during this turn ({{from}} to {{to}}), so this includes what came in with it.",
|
|
168
173
|
"andMore": "and more, truncated",
|
|
169
174
|
"edits_one": "{{count}} edit",
|
|
170
175
|
"edits_other": "{{count}} edits",
|
|
@@ -249,6 +254,17 @@
|
|
|
249
254
|
},
|
|
250
255
|
"composer": {
|
|
251
256
|
"slotFailed": "This control failed to render",
|
|
257
|
+
"inputLabel": "Message",
|
|
258
|
+
"menu": {
|
|
259
|
+
"commands": "Commands",
|
|
260
|
+
"skills": "Skills",
|
|
261
|
+
"mentions": "Mentions"
|
|
262
|
+
},
|
|
263
|
+
"held": {
|
|
264
|
+
"question": "A question is waiting. It opens when you pause typing.",
|
|
265
|
+
"approval": "An approval is waiting. It opens when you pause typing.",
|
|
266
|
+
"show": "Show now"
|
|
267
|
+
},
|
|
252
268
|
"commands": {
|
|
253
269
|
"model": "Switch the model",
|
|
254
270
|
"compact": "Compact the context",
|
|
@@ -294,7 +310,8 @@
|
|
|
294
310
|
},
|
|
295
311
|
"quote": {
|
|
296
312
|
"add": "Quote",
|
|
297
|
-
"remove": "Remove quote"
|
|
313
|
+
"remove": "Remove quote",
|
|
314
|
+
"goToSource": "Go to the reply"
|
|
298
315
|
},
|
|
299
316
|
"removeAttachment": "Remove {{name}}",
|
|
300
317
|
"attach": "Attach files",
|
|
@@ -319,6 +336,9 @@
|
|
|
319
336
|
"monitors_one": "{{count}} monitor",
|
|
320
337
|
"monitors_other": "{{count}} monitors",
|
|
321
338
|
"pausedUntil": "paused until {{time}}",
|
|
339
|
+
"progress": "{{done}} of {{total}} sub-agents done",
|
|
340
|
+
"needsYou_one": "{{count}} needs you",
|
|
341
|
+
"needsYou_other": "{{count}} need you",
|
|
322
342
|
"took": {
|
|
323
343
|
"done": "done in {{duration}}",
|
|
324
344
|
"failed": "failed after {{duration}}",
|
|
@@ -332,8 +352,10 @@
|
|
|
332
352
|
"subagents": {
|
|
333
353
|
"stop": {
|
|
334
354
|
"task": "Stop this task",
|
|
355
|
+
"native": "Stop this sub-agent",
|
|
335
356
|
"mark": "Mark as stopped. The CLI cannot stop one sub-agent, so it may keep working until the chat's process ends."
|
|
336
357
|
},
|
|
358
|
+
"stale": "This sub-agent belongs to a process that has ended.",
|
|
337
359
|
"title": "Sub-agents",
|
|
338
360
|
"backToMain": "Back to the main agent",
|
|
339
361
|
"loading": "Loading the conversation…",
|
|
@@ -344,9 +366,7 @@
|
|
|
344
366
|
"stopFailed": "The sub-agent could not be stopped",
|
|
345
367
|
"info": {
|
|
346
368
|
"label": "About this sub-agent",
|
|
347
|
-
"tokens": "{{tokens}} tokens"
|
|
348
|
-
"showTask": "Show its task",
|
|
349
|
-
"hideTask": "Hide its task"
|
|
369
|
+
"tokens": "{{tokens}} tokens"
|
|
350
370
|
}
|
|
351
371
|
},
|
|
352
372
|
"modes": {
|
|
@@ -382,7 +402,10 @@
|
|
|
382
402
|
"noMatch": "No models match",
|
|
383
403
|
"legacyGroup_one": "Legacy models ({{count}})",
|
|
384
404
|
"legacyGroup_other": "Legacy models ({{count}})",
|
|
385
|
-
"legacy": "Legacy"
|
|
405
|
+
"legacy": "Legacy",
|
|
406
|
+
"requires": "Requires {{cli}} {{version}} or later on {{machine}}",
|
|
407
|
+
"requiresHere": "Requires {{cli}} {{version}} or later",
|
|
408
|
+
"providerSettings": "Provider settings"
|
|
386
409
|
},
|
|
387
410
|
"stash": {
|
|
388
411
|
"title": "Stashed prompts",
|
|
@@ -91,7 +91,8 @@
|
|
|
91
91
|
"user": {
|
|
92
92
|
"heading": "Jij",
|
|
93
93
|
"showAll": "Alles tonen",
|
|
94
|
-
"showLess": "Minder tonen"
|
|
94
|
+
"showLess": "Minder tonen",
|
|
95
|
+
"quoteGone": "Het antwoord waar dit citaat vandaan kwam, staat niet meer in deze chat."
|
|
95
96
|
},
|
|
96
97
|
"report": {
|
|
97
98
|
"heading": "Rapport"
|
|
@@ -107,7 +108,8 @@
|
|
|
107
108
|
"note": {
|
|
108
109
|
"hide": "Verbergen",
|
|
109
110
|
"show": "Tonen",
|
|
110
|
-
"openFork": "Fork openen"
|
|
111
|
+
"openFork": "Fork openen",
|
|
112
|
+
"openWaiting": "Openen om te antwoorden"
|
|
111
113
|
},
|
|
112
114
|
"compaction": {
|
|
113
115
|
"plain": "Context gecomprimeerd",
|
|
@@ -165,6 +167,9 @@
|
|
|
165
167
|
},
|
|
166
168
|
"changedFiles_one": "{{count}} gewijzigd bestand",
|
|
167
169
|
"changedFiles_other": "{{count}} gewijzigde bestanden",
|
|
170
|
+
"folderChanges_one": "{{count}} wijziging in de map tijdens deze beurt",
|
|
171
|
+
"folderChanges_other": "{{count}} wijzigingen in de map tijdens deze beurt",
|
|
172
|
+
"baseMoved": "De branch is tijdens deze beurt verplaatst ({{from}} naar {{to}}), dus dit omvat ook wat daarmee binnenkwam.",
|
|
168
173
|
"andMore": "en meer, afgekapt",
|
|
169
174
|
"edits_one": "{{count}} bewerking",
|
|
170
175
|
"edits_other": "{{count}} bewerkingen",
|
|
@@ -249,6 +254,17 @@
|
|
|
249
254
|
},
|
|
250
255
|
"composer": {
|
|
251
256
|
"slotFailed": "Dit onderdeel kon niet worden getekend",
|
|
257
|
+
"inputLabel": "Bericht",
|
|
258
|
+
"menu": {
|
|
259
|
+
"commands": "Commando's",
|
|
260
|
+
"skills": "Skills",
|
|
261
|
+
"mentions": "Vermeldingen"
|
|
262
|
+
},
|
|
263
|
+
"held": {
|
|
264
|
+
"question": "Er wacht een vraag. Die opent zodra je even stopt met typen.",
|
|
265
|
+
"approval": "Er wacht een goedkeuring. Die opent zodra je even stopt met typen.",
|
|
266
|
+
"show": "Nu tonen"
|
|
267
|
+
},
|
|
252
268
|
"commands": {
|
|
253
269
|
"model": "Wissel van model",
|
|
254
270
|
"compact": "Comprimeer de context",
|
|
@@ -294,7 +310,8 @@
|
|
|
294
310
|
},
|
|
295
311
|
"quote": {
|
|
296
312
|
"add": "Citeren",
|
|
297
|
-
"remove": "Citaat verwijderen"
|
|
313
|
+
"remove": "Citaat verwijderen",
|
|
314
|
+
"goToSource": "Naar het antwoord"
|
|
298
315
|
},
|
|
299
316
|
"removeAttachment": "{{name}} verwijderen",
|
|
300
317
|
"attach": "Bestanden toevoegen",
|
|
@@ -319,6 +336,9 @@
|
|
|
319
336
|
"monitors_one": "{{count}} monitor",
|
|
320
337
|
"monitors_other": "{{count}} monitors",
|
|
321
338
|
"pausedUntil": "gepauzeerd tot {{time}}",
|
|
339
|
+
"progress": "{{done}} van {{total}} sub-agents klaar",
|
|
340
|
+
"needsYou_one": "{{count}} vraagt aandacht",
|
|
341
|
+
"needsYou_other": "{{count}} vragen aandacht",
|
|
322
342
|
"took": {
|
|
323
343
|
"done": "klaar in {{duration}}",
|
|
324
344
|
"failed": "mislukt na {{duration}}",
|
|
@@ -332,8 +352,10 @@
|
|
|
332
352
|
"subagents": {
|
|
333
353
|
"stop": {
|
|
334
354
|
"task": "Deze taak stoppen",
|
|
355
|
+
"native": "Deze sub-agent stoppen",
|
|
335
356
|
"mark": "Markeren als gestopt. De CLI kan een losse sub-agent niet stoppen, dus die werkt mogelijk door tot het proces van de chat eindigt."
|
|
336
357
|
},
|
|
358
|
+
"stale": "Deze sub-agent hoort bij een proces dat al gestopt is.",
|
|
337
359
|
"title": "Sub-agents",
|
|
338
360
|
"backToMain": "Terug naar de main agent",
|
|
339
361
|
"loading": "Gesprek laden…",
|
|
@@ -344,9 +366,7 @@
|
|
|
344
366
|
"stopFailed": "De sub-agent kon niet worden gestopt",
|
|
345
367
|
"info": {
|
|
346
368
|
"label": "Over deze sub-agent",
|
|
347
|
-
"tokens": "{{tokens}} tokens"
|
|
348
|
-
"showTask": "Opdracht tonen",
|
|
349
|
-
"hideTask": "Opdracht verbergen"
|
|
369
|
+
"tokens": "{{tokens}} tokens"
|
|
350
370
|
}
|
|
351
371
|
},
|
|
352
372
|
"modes": {
|
|
@@ -382,7 +402,10 @@
|
|
|
382
402
|
"noMatch": "Geen modellen gevonden",
|
|
383
403
|
"legacyGroup_one": "Legacy-modellen ({{count}})",
|
|
384
404
|
"legacyGroup_other": "Legacy-modellen ({{count}})",
|
|
385
|
-
"legacy": "Legacy"
|
|
405
|
+
"legacy": "Legacy",
|
|
406
|
+
"requires": "Vereist {{cli}} {{version}} of nieuwer op {{machine}}",
|
|
407
|
+
"requiresHere": "Vereist {{cli}} {{version}} of nieuwer",
|
|
408
|
+
"providerSettings": "Providerinstellingen"
|
|
386
409
|
},
|
|
387
410
|
"stash": {
|
|
388
411
|
"title": "Stashed prompts",
|
package/dist/state/chats.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChatApprovalItem, ChatBookmark, ChatEvent, ChatHistoryResult, ChatInfo, ChatItem, ChatQuestionItem, ChatVisual } from '@adecore/agent-contracts';
|
|
1
|
+
import type { AgentKind, ChatApprovalItem, ChatBookmark, ChatEvent, ChatHistoryResult, ChatInfo, ChatItem, ChatQuestionItem, ChatVisual } from '@adecore/agent-contracts';
|
|
2
2
|
export interface ChatState {
|
|
3
3
|
info: ChatInfo;
|
|
4
4
|
items: Record<string, ChatItem>;
|
|
@@ -43,5 +43,6 @@ export declare function applyEvent(state: ChatState, event: ChatEvent): ChatStat
|
|
|
43
43
|
export declare const useChats: import("zustand").UseBoundStore<import("zustand").StoreApi<ChatsStore>>;
|
|
44
44
|
export declare function chatSink(keyOf: (chatId: string) => string): ChatSink;
|
|
45
45
|
export declare function useChatRow<T>(chatId: string, select: (row: ChatState | undefined) => T): T;
|
|
46
|
+
export declare function useReportedModels(provider: AgentKind): ReadonlySet<string>;
|
|
46
47
|
export declare function useCurrentItem<T extends ChatItem>(chatId: string, derived: T): T;
|
|
47
48
|
export {};
|
package/dist/state/chats.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { useContext, useMemo } from 'react';
|
|
1
2
|
import { create } from 'zustand';
|
|
2
|
-
import { useChatScope } from '../scope.js';
|
|
3
|
+
import { ChatScopeContext, useChatScope } from '../scope.js';
|
|
3
4
|
function isWaiting(item) {
|
|
4
5
|
return (item?.kind === 'approval' && item.decision === 'pending') || (item?.kind === 'question' && item.state === 'pending');
|
|
5
6
|
}
|
|
@@ -209,6 +210,23 @@ export function useChatRow(chatId, select) {
|
|
|
209
210
|
const { keyOf } = useChatScope();
|
|
210
211
|
return useChats((s) => select(s.byKey[keyOf(chatId)]));
|
|
211
212
|
}
|
|
213
|
+
/*
|
|
214
|
+
* The models the CLI of this provider reported running in the chats this client holds of the host.
|
|
215
|
+
* A model it ran is proof the CLI offers it, whatever the version says. Outside a scope there is none.
|
|
216
|
+
*/
|
|
217
|
+
export function useReportedModels(provider) {
|
|
218
|
+
const scope = useContext(ChatScopeContext);
|
|
219
|
+
const joined = useChats((s) => {
|
|
220
|
+
const names = new Set();
|
|
221
|
+
for (const [key, state] of Object.entries(s.statusByKey)) {
|
|
222
|
+
if (scope?.owns(key) === true && state.info.provider === provider && state.info.model !== null) {
|
|
223
|
+
names.add(state.info.model);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return [...names].sort().join('\n');
|
|
227
|
+
});
|
|
228
|
+
return useMemo(() => new Set(joined === '' ? [] : joined.split('\n')), [joined]);
|
|
229
|
+
}
|
|
212
230
|
/* The item as the thread holds it now, for a row derived from the structure, which a delta leaves alone. */
|
|
213
231
|
export function useCurrentItem(chatId, derived) {
|
|
214
232
|
return useChatRow(chatId, (row) => {
|