@adecore/agents-react 0.21.1 → 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/agents/status-look.js +2 -3
- package/dist/chat/chat-client.d.ts +3 -2
- package/dist/chat/chat-client.js +4 -2
- package/dist/chat/drafts.d.ts +2 -1
- package/dist/chat/drafts.js +21 -17
- package/dist/chat/logic/ansi.js +5 -1
- package/dist/chat/logic/fork.js +8 -9
- package/dist/chat/logic/limit.js +17 -14
- package/dist/chat/logic/picker-entries.d.ts +10 -0
- package/dist/chat/logic/picker-entries.js +19 -0
- package/dist/chat/logic/resume-compaction.js +2 -11
- package/dist/chat/logic/scrubber.js +9 -18
- package/dist/chat/logic/timeline.d.ts +5 -0
- package/dist/chat/logic/timeline.js +25 -11
- package/dist/chat/logic/tool-catalog.js +3 -4
- package/dist/chat/mentions.js +5 -6
- 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/runtime-modes.js +2 -4
- package/dist/chat/stash.d.ts +8 -2
- package/dist/chat/stash.js +22 -8
- package/dist/chat/subagent-list.d.ts +7 -1
- package/dist/chat/subagent-list.js +13 -1
- package/dist/chat/thumbnails.js +3 -4
- package/dist/chat/timeline-scroll.d.ts +6 -2
- package/dist/chat/timeline-scroll.js +45 -46
- package/dist/chat/ui/BookmarkSubmenu.js +1 -5
- package/dist/chat/ui/ChatActivity.js +22 -8
- package/dist/chat/ui/CodeBlock.js +2 -3
- package/dist/chat/ui/Composer.js +160 -88
- package/dist/chat/ui/ComposerInput.d.ts +3 -1
- package/dist/chat/ui/ComposerInput.js +13 -4
- package/dist/chat/ui/Markdown.js +11 -11
- package/dist/chat/ui/Pickers.js +31 -27
- package/dist/chat/ui/PromptComposer.d.ts +8 -1
- package/dist/chat/ui/PromptComposer.js +18 -6
- package/dist/chat/ui/QuoteButton.js +4 -5
- package/dist/chat/ui/RunSettings.js +8 -2
- package/dist/chat/ui/Scrubber.js +2 -3
- package/dist/chat/ui/SubagentControls.d.ts +4 -2
- package/dist/chat/ui/SubagentControls.js +13 -11
- 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 +39 -21
- package/dist/chat/ui/TimelineMenu.js +3 -9
- package/dist/chat/ui/UnifiedDiff.js +3 -5
- package/dist/chat/ui/VisualFrame.js +3 -4
- package/dist/chat/ui/composer/keys.js +19 -11
- 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/intelligent-ui/render-context.d.ts +1 -1
- 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 +26 -8
- package/dist/chat/ui/rows/Rows.js +1 -1
- package/dist/chat/ui/rows/WorkRows.d.ts +7 -4
- package/dist/chat/ui/rows/WorkRows.js +22 -17
- package/dist/chat/visual-viewport.d.ts +1 -1
- package/dist/chat/visual-viewport.js +9 -8
- 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/port-transport.js +4 -8
- package/dist/state/chats.d.ts +2 -1
- package/dist/state/chats.js +29 -15
- package/dist/state/usage.js +44 -49
- package/dist/storage.js +7 -7
- package/dist/theme.css +6 -14
- package/dist/usage/LimitsList.js +1 -2
- package/dist/usage/UsageBreakdown.js +1 -3
- package/dist/usage/UsageDialog.js +2 -4
- package/dist/usage/format.js +6 -6
- package/dist/usage/money.js +1 -5
- package/dist/usage/summary.js +2 -4
- package/package.json +24 -4
- package/src/agents/model-requirement.ts +36 -0
- package/src/agents/status-look.ts +2 -3
- package/src/chat/chat-client.ts +7 -3
- package/src/chat/drafts.ts +33 -21
- package/src/chat/logic/ansi.ts +5 -1
- package/src/chat/logic/fork.ts +8 -9
- package/src/chat/logic/limit.ts +17 -16
- package/src/chat/logic/picker-entries.ts +25 -0
- package/src/chat/logic/resume-compaction.ts +2 -11
- package/src/chat/logic/scrubber.ts +13 -17
- package/src/chat/logic/timeline.ts +39 -13
- package/src/chat/logic/tool-catalog.ts +7 -6
- package/src/chat/mentions.ts +5 -6
- package/src/chat/prompt-hold.ts +27 -0
- package/src/chat/quote.ts +20 -0
- package/src/chat/runtime-modes.ts +2 -4
- package/src/chat/stash.ts +32 -8
- package/src/chat/subagent-list.ts +19 -5
- package/src/chat/thumbnails.ts +3 -4
- package/src/chat/timeline-scroll.ts +58 -53
- package/src/chat/ui/BookmarkSubmenu.tsx +1 -5
- package/src/chat/ui/ChatActivity.tsx +35 -9
- package/src/chat/ui/CodeBlock.tsx +2 -3
- package/src/chat/ui/Composer.tsx +263 -138
- package/src/chat/ui/ComposerInput.tsx +18 -3
- package/src/chat/ui/Markdown.tsx +13 -17
- package/src/chat/ui/Pickers.tsx +56 -39
- package/src/chat/ui/PromptComposer.tsx +37 -8
- package/src/chat/ui/QuoteButton.tsx +5 -5
- package/src/chat/ui/RunSettings.tsx +9 -2
- package/src/chat/ui/Scrubber.tsx +2 -3
- package/src/chat/ui/SubagentControls.tsx +23 -22
- package/src/chat/ui/SubagentInfo.tsx +24 -42
- package/src/chat/ui/SubagentStopButton.tsx +2 -1
- package/src/chat/ui/SubagentTimeline.tsx +1 -2
- package/src/chat/ui/Timeline.tsx +44 -24
- package/src/chat/ui/TimelineMenu.tsx +3 -9
- package/src/chat/ui/UnifiedDiff.tsx +3 -5
- package/src/chat/ui/VisualFrame.tsx +3 -4
- package/src/chat/ui/composer/keys.ts +21 -11
- package/src/chat/ui/composer/menu-aria.ts +29 -0
- package/src/chat/ui/intelligent-ui/render-context.ts +2 -2
- package/src/chat/ui/quote-selection.ts +6 -4
- package/src/chat/ui/rehype-chips.ts +1 -1
- package/src/chat/ui/rehype-fade.ts +1 -1
- package/src/chat/ui/remark-html-as-text.ts +1 -1
- package/src/chat/ui/rows/MessageRows.tsx +47 -9
- package/src/chat/ui/rows/Rows.tsx +3 -1
- package/src/chat/ui/rows/WorkRows.tsx +101 -70
- package/src/chat/visual-viewport.ts +10 -8
- package/src/host.ts +4 -3
- package/src/locales/en/agent-chat.json +30 -7
- package/src/locales/nl/agent-chat.json +30 -7
- package/src/port-transport.ts +4 -8
- package/src/state/chats.ts +42 -16
- package/src/state/usage.ts +49 -60
- package/src/storage.ts +7 -7
- package/src/theme.css +6 -14
- package/src/usage/LimitsList.tsx +2 -4
- package/src/usage/UsageBreakdown.tsx +2 -5
- package/src/usage/UsageDialog.tsx +2 -4
- package/src/usage/format.ts +6 -6
- package/src/usage/money.ts +1 -5
- package/src/usage/summary.ts +2 -4
package/dist/chat/stash.js
CHANGED
|
@@ -23,6 +23,16 @@ function attachments(value) {
|
|
|
23
23
|
size: typeof entry.size === 'number' ? entry.size : 0
|
|
24
24
|
}));
|
|
25
25
|
}
|
|
26
|
+
function stashedQuote(value) {
|
|
27
|
+
if (!isRecord(value) || typeof value.text !== 'string' || typeof value.chatId !== 'string' || typeof value.itemId !== 'string') {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
return { text: value.text, chatId: value.chatId, itemId: value.itemId };
|
|
31
|
+
}
|
|
32
|
+
function stashedQuoteField(value) {
|
|
33
|
+
const quote = stashedQuote(value);
|
|
34
|
+
return quote === undefined ? {} : { quote };
|
|
35
|
+
}
|
|
26
36
|
/* Only the fields we know today, so anything an older or a broken write left behind falls away here. */
|
|
27
37
|
export function parseStash(raw) {
|
|
28
38
|
if (raw === null) {
|
|
@@ -42,6 +52,7 @@ export function parseStash(raw) {
|
|
|
42
52
|
mentions: strings(entry.mentions),
|
|
43
53
|
skills: strings(entry.skills),
|
|
44
54
|
attachments: attachments(entry.attachments),
|
|
55
|
+
...stashedQuoteField(entry.quote),
|
|
45
56
|
createdAt: typeof entry.createdAt === 'number' ? entry.createdAt : 0
|
|
46
57
|
}))
|
|
47
58
|
.slice(0, STASH_LIMIT);
|
|
@@ -54,14 +65,19 @@ export function parseStash(raw) {
|
|
|
54
65
|
export function withStashed(prompts, entry) {
|
|
55
66
|
return [entry, ...prompts].slice(0, STASH_LIMIT);
|
|
56
67
|
}
|
|
57
|
-
/*
|
|
58
|
-
|
|
68
|
+
/*
|
|
69
|
+
* What a draft looks like on the shelf; null when there is nothing worth putting there. A quote that
|
|
70
|
+
* knows its reply stays apart, since it leads back to that reply in `chatId`; any other is folded into the text.
|
|
71
|
+
*/
|
|
72
|
+
export function stashedFrom(draft, id, now, chatId) {
|
|
59
73
|
if (isEmptyDraft(draft)) {
|
|
60
74
|
return null;
|
|
61
75
|
}
|
|
76
|
+
const kept = draft.quote !== '' && draft.quoteSource !== null;
|
|
62
77
|
return {
|
|
63
78
|
id,
|
|
64
|
-
text: withQuote(draft.quote, draft.text),
|
|
79
|
+
text: kept ? draft.text : withQuote(draft.quote, draft.text),
|
|
80
|
+
...(kept ? { quote: { text: draft.quote, chatId, itemId: draft.quoteSource } } : {}),
|
|
65
81
|
mentions: [...draft.mentions],
|
|
66
82
|
skills: [...draft.skills],
|
|
67
83
|
attachments: draft.attachments.map((attachment) => ({ name: attachment.name, mime: attachment.mime, size: uploadBytes(attachment) })),
|
|
@@ -69,17 +85,15 @@ export function stashedFrom(draft, id, now) {
|
|
|
69
85
|
};
|
|
70
86
|
}
|
|
71
87
|
const storage = persistedJson(storageKey, parseStash, [], () => chatStorageLegacyKeys('stash'));
|
|
72
|
-
/*
|
|
73
|
-
* Prompts put aside for later can move to another chat, so the shelf is shared across chats.
|
|
74
|
-
*/
|
|
88
|
+
/* Prompts put aside for later can move to another chat, so the shelf is shared across chats. */
|
|
75
89
|
export const useStash = createChatStore(() => ({ prompts: [] }), (store) => store.setState({ prompts: storage.read() }));
|
|
76
90
|
function write(prompts) {
|
|
77
91
|
useStash.setState({ prompts });
|
|
78
92
|
storage.write(prompts);
|
|
79
93
|
}
|
|
80
94
|
/* Puts a draft on the shelf; false when the composer held nothing to put there. */
|
|
81
|
-
export function stashDraft(draft) {
|
|
82
|
-
const entry = stashedFrom(draft, `stash-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, Date.now());
|
|
95
|
+
export function stashDraft(draft, chatId) {
|
|
96
|
+
const entry = stashedFrom(draft, `stash-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, Date.now(), chatId);
|
|
83
97
|
if (!entry) {
|
|
84
98
|
return false;
|
|
85
99
|
}
|
|
@@ -7,11 +7,17 @@ export declare function taskIdOf(item: ChatSubagentItem): string | null;
|
|
|
7
7
|
export declare function statusWordOf(item: ChatSubagentItem, task: SubagentTask | null): StatusWord;
|
|
8
8
|
export type TaskRowState = StatusWord | 'waiting';
|
|
9
9
|
export declare function taskRowStateOf(item: ChatSubagentItem, task: SubagentTask | null, child: ChatInfo | undefined): TaskRowState;
|
|
10
|
+
export declare function progressOf(states: readonly TaskRowState[]): {
|
|
11
|
+
done: number;
|
|
12
|
+
total: number;
|
|
13
|
+
waiting: number;
|
|
14
|
+
active: number;
|
|
15
|
+
};
|
|
10
16
|
export declare function flyoutSubagents(order: readonly string[], structure: Readonly<Record<string, ChatItem>>): ChatSubagentItem[];
|
|
11
17
|
export declare function summaryWordOf(words: readonly StatusWord[]): StatusWord;
|
|
12
18
|
export declare function badgeCountOf(words: readonly StatusWord[]): number;
|
|
13
19
|
export declare function entryTimeOf(item: ChatSubagentItem, task: SubagentTask | null, now: number): string;
|
|
14
|
-
export type SubagentStop = 'task' | 'mark';
|
|
20
|
+
export type SubagentStop = 'task' | 'native' | 'mark';
|
|
15
21
|
export declare function stopOf(item: ChatSubagentItem, turnRunning: boolean): SubagentStop | null;
|
|
16
22
|
export declare function stopLabel(stop: SubagentStop): string;
|
|
17
23
|
export type ComposerStop = 'turn' | 'turn-and-subagents';
|
|
@@ -36,6 +36,15 @@ export function taskRowStateOf(item, task, child) {
|
|
|
36
36
|
}
|
|
37
37
|
return word;
|
|
38
38
|
}
|
|
39
|
+
/* How far delegated work is: what settled as done, what still runs or waits, and how many of those wait on a person. */
|
|
40
|
+
export function progressOf(states) {
|
|
41
|
+
return {
|
|
42
|
+
done: states.filter((state) => state === 'done').length,
|
|
43
|
+
total: states.length,
|
|
44
|
+
waiting: states.filter((state) => state === 'waiting').length,
|
|
45
|
+
active: states.filter((state) => state === 'running' || state === 'paused' || state === 'waiting').length
|
|
46
|
+
};
|
|
47
|
+
}
|
|
39
48
|
/*
|
|
40
49
|
* What the flyout over the composer lists: every sub-agent still at work, and the ones that settled
|
|
41
50
|
* since the last message, so a finished batch stays until the next message is sent. That includes an
|
|
@@ -93,10 +102,13 @@ export function stopOf(item, turnRunning) {
|
|
|
93
102
|
if (item.origin === 'ruimte') {
|
|
94
103
|
return item.childId === undefined ? null : 'task';
|
|
95
104
|
}
|
|
105
|
+
if (item.native?.agentId) {
|
|
106
|
+
return 'native';
|
|
107
|
+
}
|
|
96
108
|
return turnRunning ? null : 'mark';
|
|
97
109
|
}
|
|
98
110
|
export function stopLabel(stop) {
|
|
99
|
-
return
|
|
111
|
+
return i18next.t(`agent-chat:subagents.stop.${stop}`);
|
|
100
112
|
}
|
|
101
113
|
export function composerStopOf(shiftKey) {
|
|
102
114
|
return shiftKey ? 'turn-and-subagents' : 'turn';
|
package/dist/chat/thumbnails.js
CHANGED
|
@@ -30,10 +30,9 @@ function dataUrlOf(blob) {
|
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
32
|
/*
|
|
33
|
-
* A still of the middle of an image, decoded once
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* without `OffscreenCanvas`) falls back to the image itself, which is what it was before.
|
|
33
|
+
* A still of the middle of an image, decoded once: a browser decodes a full data URL again on every
|
|
34
|
+
* paint, so a tall screenshot made typing stutter. An animated image gives its first frame; what
|
|
35
|
+
* cannot be decoded this way (an SVG, a browser without `OffscreenCanvas`) falls back to the image.
|
|
37
36
|
*/
|
|
38
37
|
async function makeThumbnail(upload) {
|
|
39
38
|
try {
|
|
@@ -11,8 +11,12 @@ export declare function scrollTimelineToEnd(chatId: string, behavior?: ScrollBeh
|
|
|
11
11
|
export declare function pageTimeline(chatId: string, direction: -1 | 1): boolean;
|
|
12
12
|
export declare function registerMessageStepper(key: string, step: (direction: -1 | 1) => boolean): () => void;
|
|
13
13
|
export declare function stepTimelineMessage(key: string, direction: -1 | 1): boolean;
|
|
14
|
-
export
|
|
15
|
-
|
|
14
|
+
export interface ItemJumpOptions {
|
|
15
|
+
flash?: boolean;
|
|
16
|
+
onMissing?(): void;
|
|
17
|
+
}
|
|
18
|
+
export declare function registerItemJumper(key: string, jump: (itemId: string, options: ItemJumpOptions) => void): () => void;
|
|
19
|
+
export declare function jumpToTimelineItem(key: string, itemId: string, options?: ItemJumpOptions): boolean;
|
|
16
20
|
export type TurnTarget = 'prompt' | 'changes';
|
|
17
21
|
type TurnJump = (turnId: string, target: TurnTarget) => void;
|
|
18
22
|
export declare function registerTurnJumper(key: string, jump: TurnJump): () => void;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// A page is a screen minus a little, so the line you were reading is still on it after the jump.
|
|
2
2
|
const PAGE_OVERLAP = 0.9;
|
|
3
|
+
// How long a jump asked before its thread registered stays up for that thread.
|
|
4
|
+
const PENDING_JUMP_TTL_MS = 10_000;
|
|
3
5
|
const scrollers = new Map();
|
|
6
|
+
/* The timeline owns following; the composer subscribes here to show its jump-to-end button. */
|
|
7
|
+
const ends = new Map();
|
|
8
|
+
const listeners = new Set();
|
|
4
9
|
/* The timeline hands its scroller over so the composer, which never sees it, can page through it. */
|
|
5
10
|
export function registerTimeline(chatId, element, follow, coveredHeight) {
|
|
6
11
|
if (element === null) {
|
|
@@ -14,9 +19,6 @@ export function registerTimeline(chatId, element, follow, coveredHeight) {
|
|
|
14
19
|
}
|
|
15
20
|
};
|
|
16
21
|
}
|
|
17
|
-
/* The timeline owns following; the composer subscribes here to show its jump-to-end button. */
|
|
18
|
-
const ends = new Map();
|
|
19
|
-
const listeners = new Set();
|
|
20
22
|
export function setTimelineAtEnd(chatId, atEnd) {
|
|
21
23
|
if (ends.get(chatId) === atEnd) {
|
|
22
24
|
return;
|
|
@@ -73,59 +75,56 @@ export function stepTimelineMessage(key, direction) {
|
|
|
73
75
|
return steppers.get(key)?.(direction) ?? false;
|
|
74
76
|
}
|
|
75
77
|
/*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
78
|
+
* Jumps asked from outside a thread (the chat's menu, a card that names a turn), keyed with the
|
|
79
|
+
* scope's `keyOf` like the steppers. A thread that is not on screen yet gets its jump when it
|
|
80
|
+
* registers, since the asker may have to show the chat first.
|
|
79
81
|
*/
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
82
|
+
function waitingJumps() {
|
|
83
|
+
const jumpers = new Map();
|
|
84
|
+
const waiting = new Map();
|
|
85
|
+
return {
|
|
86
|
+
register(key, jump) {
|
|
87
|
+
jumpers.set(key, jump);
|
|
88
|
+
const pending = waiting.get(key);
|
|
89
|
+
if (pending !== undefined) {
|
|
90
|
+
waiting.delete(key);
|
|
91
|
+
// A jump nobody picked up for long is not what the person wants any more.
|
|
92
|
+
if (Date.now() - pending.at <= PENDING_JUMP_TTL_MS) {
|
|
93
|
+
jump(pending.target);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return () => {
|
|
97
|
+
if (jumpers.get(key) === jump) {
|
|
98
|
+
jumpers.delete(key);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
jump(key, target) {
|
|
103
|
+
const jump = jumpers.get(key);
|
|
104
|
+
if (jump === undefined) {
|
|
105
|
+
waiting.set(key, { target, at: Date.now() });
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
jump(target);
|
|
109
|
+
return true;
|
|
92
110
|
}
|
|
93
111
|
};
|
|
94
112
|
}
|
|
113
|
+
const itemJumps = waitingJumps();
|
|
114
|
+
export function registerItemJumper(key, jump) {
|
|
115
|
+
return itemJumps.register(key, ({ itemId, options }) => jump(itemId, options));
|
|
116
|
+
}
|
|
95
117
|
/* False when that chat has no thread on screen; the jump then waits for the next one that registers. */
|
|
96
|
-
export function jumpToTimelineItem(key, itemId) {
|
|
97
|
-
|
|
98
|
-
if (jump === undefined) {
|
|
99
|
-
waitingJumps.set(key, itemId);
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
jump(itemId);
|
|
103
|
-
return true;
|
|
118
|
+
export function jumpToTimelineItem(key, itemId, options = {}) {
|
|
119
|
+
return itemJumps.jump(key, { itemId, options });
|
|
104
120
|
}
|
|
105
|
-
const
|
|
106
|
-
const waitingTurnJumps = new Map();
|
|
121
|
+
const turnJumps = waitingJumps();
|
|
107
122
|
export function registerTurnJumper(key, jump) {
|
|
108
|
-
|
|
109
|
-
const waiting = waitingTurnJumps.get(key);
|
|
110
|
-
if (waiting !== undefined) {
|
|
111
|
-
waitingTurnJumps.delete(key);
|
|
112
|
-
jump(waiting.turnId, waiting.target);
|
|
113
|
-
}
|
|
114
|
-
return () => {
|
|
115
|
-
if (turnJumpers.get(key) === jump) {
|
|
116
|
-
turnJumpers.delete(key);
|
|
117
|
-
}
|
|
118
|
-
};
|
|
123
|
+
return turnJumps.register(key, ({ turnId, target }) => jump(turnId, target));
|
|
119
124
|
}
|
|
120
125
|
/* False when that chat has no thread on screen; the jump then waits for the next one that registers. */
|
|
121
126
|
export function jumpToTimelineTurn(key, turnId, target) {
|
|
122
|
-
|
|
123
|
-
if (jump === undefined) {
|
|
124
|
-
waitingTurnJumps.set(key, { turnId, target });
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
jump(turnId, target);
|
|
128
|
-
return true;
|
|
127
|
+
return turnJumps.jump(key, { turnId, target });
|
|
129
128
|
}
|
|
130
129
|
/* How near the top, in screens, the page before is asked for, so it is there before a reader reaches the edge. */
|
|
131
130
|
const EARLIER_SCREENS = 1.5;
|
|
@@ -9,11 +9,7 @@ import { chatHost } from '../../host.js';
|
|
|
9
9
|
import { useChatScope } from '../../scope.js';
|
|
10
10
|
import { useChatRow } from '../../state/chats.js';
|
|
11
11
|
import { Icon, Menu } from '@adecore/ui';
|
|
12
|
-
/*
|
|
13
|
-
* "Bookmarks" in the menu of a chat node or a chat view: the chat's bookmarks in the order of the
|
|
14
|
-
* thread, each a jump to its message, with rename and remove on the row. A chat without bookmarks
|
|
15
|
-
* draws nothing.
|
|
16
|
-
*/
|
|
12
|
+
/* A chat's bookmarks in thread order, each a jump to its message with rename and remove; nothing without bookmarks. */
|
|
17
13
|
export function BookmarkSubmenu({ chatId }) {
|
|
18
14
|
const { t } = useTranslation('agent-chat');
|
|
19
15
|
const scope = useChatScope();
|
|
@@ -2,15 +2,15 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
3
|
import clsx from 'clsx';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
|
-
import { Activity, Square, SquareTerminal } from 'lucide-react';
|
|
5
|
+
import { Activity, MessageCircleQuestionMark, Square, SquareTerminal } from 'lucide-react';
|
|
6
6
|
import { backgroundCounts } from '../activity.js';
|
|
7
|
-
import { badgeCountOf, entryTimeOf, flyoutSubagents, statusWordOf, subagentTitle, summaryWordOf, taskIdOf } from '../subagent-list.js';
|
|
7
|
+
import { badgeCountOf, entryTimeOf, flyoutSubagents, progressOf, statusWordOf, subagentTitle, summaryWordOf, taskIdOf, taskRowStateOf } from '../subagent-list.js';
|
|
8
8
|
import { useSubagentSupport } from '../subagent-support.js';
|
|
9
9
|
import { canOpenSubagent, crumbOf, openFromMain, useSubagentTrail } from '../subagent-view.js';
|
|
10
10
|
import { SubagentStopButton } from './SubagentStopButton.js';
|
|
11
11
|
import { formatElapsedShort } from '@adecore/ui/format';
|
|
12
12
|
import { Icon, IconButton, Popover, Surface, useNow } from '@adecore/ui';
|
|
13
|
-
import { useChatRow } from '../../state/chats.js';
|
|
13
|
+
import { useChatRow, useChats } from '../../state/chats.js';
|
|
14
14
|
import { chatHost } from '../../host.js';
|
|
15
15
|
import { useChatScope } from '../../scope.js';
|
|
16
16
|
import { useChatActions } from '../actions.js';
|
|
@@ -24,8 +24,14 @@ export function ChatActivity({ chatId }) {
|
|
|
24
24
|
const order = useChatRow(chatId, (row) => row?.order) ?? NO_ITEMS;
|
|
25
25
|
const structure = useChatRow(chatId, (row) => row?.structure);
|
|
26
26
|
const subagents = useMemo(() => (structure === undefined ? [] : flyoutSubagents(order, structure)), [order, structure]);
|
|
27
|
-
const { id } = useChatScope();
|
|
27
|
+
const { id, keyOf } = useChatScope();
|
|
28
28
|
const tasks = chatHost().tasks.useTasks(id);
|
|
29
|
+
const statuses = useChats((s) => s.statusByKey);
|
|
30
|
+
const progress = useMemo(() => progressOf(subagents.map((item) => {
|
|
31
|
+
const taskId = taskIdOf(item);
|
|
32
|
+
const child = item.childId === undefined ? undefined : statuses[keyOf(item.childId)]?.info;
|
|
33
|
+
return taskRowStateOf(item, taskId === null ? null : (tasks?.[taskId] ?? null), child);
|
|
34
|
+
})), [subagents, tasks, statuses, keyOf]);
|
|
29
35
|
const words = useMemo(() => subagents.map((item) => {
|
|
30
36
|
const taskId = taskIdOf(item);
|
|
31
37
|
return statusWordOf(item, taskId === null ? null : (tasks?.[taskId] ?? null));
|
|
@@ -36,8 +42,14 @@ export function ChatActivity({ chatId }) {
|
|
|
36
42
|
}
|
|
37
43
|
const { shells, monitors } = backgroundCounts(background);
|
|
38
44
|
const Slot = chatHost().SubagentSlot;
|
|
45
|
+
const counting = progress.active > 0 && progress.total > 1;
|
|
46
|
+
const label = counting
|
|
47
|
+
? [t('activity.progress', { done: progress.done, total: progress.total }), progress.waiting > 0 && t('activity.needsYou', { count: progress.waiting })]
|
|
48
|
+
.filter(Boolean)
|
|
49
|
+
.join(', ')
|
|
50
|
+
: t('activity.subagents', { count: badgeCountOf(words) });
|
|
39
51
|
return (_jsxs("div", { className: "mb-3 flex flex-wrap items-center gap-2", children: [subagents.length > 0 &&
|
|
40
|
-
(Slot === null ? (_jsx(Flyout, { icon: _jsx(
|
|
52
|
+
(Slot === null ? (_jsx(Flyout, { icon: progress.waiting > 0 ? (_jsx(Icon, { icon: MessageCircleQuestionMark, size: 14, className: "shrink-0 text-status-needs-you" })) : (_jsx(StatusIcon, { word: summaryWordOf(words) })), label: label, heading: t('activity.subagents', { count: subagents.length }), children: _jsx(SubagentRows, { chatId: chatId, items: subagents }) })) : (_jsx(Slot, { chatId: chatId, items: subagents }))), background.length > 0 && (_jsx(Flyout, { icon: _jsx(Icon, { icon: shells > 0 ? SquareTerminal : Activity, size: 14 }), label: [shells > 0 && t('activity.shells', { count: shells }), monitors > 0 && t('activity.monitors', { count: monitors })]
|
|
41
53
|
.filter(Boolean)
|
|
42
54
|
.join(' · '), children: _jsx(BackgroundTaskRows, { chatId: chatId, tasks: background }) }))] }));
|
|
43
55
|
}
|
|
@@ -47,7 +59,7 @@ export function StatusIcon({ word }) {
|
|
|
47
59
|
}
|
|
48
60
|
/* A chip that opens a list over it, headed by what the chip says unless the list counts more than the chip does. */
|
|
49
61
|
function Flyout({ icon, label, heading = label, children }) {
|
|
50
|
-
return (_jsxs(Popover.Root, { children: [_jsxs(Surface, { render: _jsx(Popover.Trigger, {}), className: "pointer-events-auto inline-flex h-7.5 items-center gap-1.5 rounded-full px-3 text-xs text-text-muted hover:text-text", children: [icon, label] }), _jsxs(Popover.Popup, { side: "top", className: "w-80", children: [_jsx("div", { className: "px-2.5 pt-1.5 pb-1 text-xs text-text-faint", children: heading }), children] })] }));
|
|
62
|
+
return (_jsxs(Popover.Root, { children: [_jsxs(Surface, { render: _jsx(Popover.Trigger, {}), className: "pointer-events-auto inline-flex h-7.5 items-center gap-1.5 rounded-full px-3 text-xs text-text-muted tabular-nums hover:text-text", children: [icon, label] }), _jsxs(Popover.Popup, { side: "top", className: "w-80", children: [_jsx("div", { className: "px-2.5 pt-1.5 pb-1 text-xs text-text-faint", children: heading }), children] })] }));
|
|
51
63
|
}
|
|
52
64
|
/* One line of a flyout; with `onOpen` the line leads somewhere and closes the flyout on the way. */
|
|
53
65
|
function ActivityRow({ icon, title, detail, time, onOpen, children }) {
|
|
@@ -62,12 +74,14 @@ function SubagentRows({ chatId, items }) {
|
|
|
62
74
|
}
|
|
63
75
|
function SubagentEntry({ chatId, item, now }) {
|
|
64
76
|
const { t } = useTranslation('agent-chat');
|
|
65
|
-
const { id } = useChatScope();
|
|
77
|
+
const { id, keyOf } = useChatScope();
|
|
66
78
|
const refused = useSubagentSupport((s) => s.unsupported[id] === true);
|
|
67
79
|
const task = chatHost().tasks.useTask(id, taskIdOf(item));
|
|
68
80
|
const { show } = useSubagentTrail(chatId);
|
|
81
|
+
const child = useChats((s) => (item.childId === undefined ? undefined : s.statusByKey[keyOf(item.childId)]?.info));
|
|
69
82
|
const word = statusWordOf(item, task);
|
|
70
|
-
|
|
83
|
+
const state = taskRowStateOf(item, task, child);
|
|
84
|
+
return (_jsx(ActivityRow, { icon: _jsx(StatusIcon, { word: word }), title: _jsxs(_Fragment, { children: [subagentTitle(item), _jsxs("span", { className: "sr-only", children: [", ", state === 'waiting' ? t('rows.task.waiting') : t(`common.status.${word}`)] })] }), time: entryTimeOf(item, task, now), onOpen: canOpenSubagent(item, refused) ? () => show(openFromMain(crumbOf(item))) : undefined, children: _jsx(SubagentStopButton, { chatId: chatId, item: item, className: "mr-1.5" }) }));
|
|
71
85
|
}
|
|
72
86
|
/* Only drawn while the flyout is open, so the clock ticks for nobody otherwise. */
|
|
73
87
|
function BackgroundTaskRows({ chatId, tasks }) {
|
|
@@ -102,9 +102,8 @@ const CodeLine = memo(function CodeLine({ tokens }) {
|
|
|
102
102
|
});
|
|
103
103
|
/*
|
|
104
104
|
* A fenced block, highlighted a line at a time. Until shiki and the grammar are there it holds its
|
|
105
|
-
* place invisibly
|
|
106
|
-
*
|
|
107
|
-
* closes, so closing it does not draw it again.
|
|
105
|
+
* place invisibly, so bare code never turns colored. An open fence is tokenized as it grows, and
|
|
106
|
+
* closing it does not draw it again.
|
|
108
107
|
*/
|
|
109
108
|
export function CodeBlock({ code, lang, actions }) {
|
|
110
109
|
const { t } = useTranslation('agent-chat');
|