@adecore/agents-react 0.20.1 → 0.21.1
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/chat/logic/generated-image.d.ts +27 -0
- package/dist/chat/logic/generated-image.js +32 -0
- package/dist/chat/logic/timeline-target.d.ts +13 -1
- package/dist/chat/logic/timeline-target.js +26 -0
- package/dist/chat/logic/timeline.js +16 -10
- package/dist/chat/subagent-conversation.d.ts +2 -1
- package/dist/chat/subagent-conversation.js +7 -1
- package/dist/chat/timeline-flash.d.ts +3 -1
- package/dist/chat/timeline-flash.js +2 -2
- package/dist/chat/ui/ImageView.d.ts +8 -2
- package/dist/chat/ui/ImageView.js +12 -6
- package/dist/chat/ui/SubagentTimeline.js +6 -2
- package/dist/chat/ui/Timeline.js +67 -7
- package/dist/chat/ui/intelligent-ui/UiBlockFrame.d.ts +23 -0
- package/dist/chat/ui/intelligent-ui/UiBlockFrame.js +103 -0
- package/dist/chat/ui/intelligent-ui/UiFallbackPart.d.ts +10 -0
- package/dist/chat/ui/intelligent-ui/UiFallbackPart.js +11 -0
- package/dist/chat/ui/intelligent-ui/UiReply.d.ts +15 -0
- package/dist/chat/ui/intelligent-ui/UiReply.js +329 -0
- package/dist/chat/ui/intelligent-ui/block-local.d.ts +11 -0
- package/dist/chat/ui/intelligent-ui/block-local.js +42 -0
- package/dist/chat/ui/intelligent-ui/chart-data.d.ts +16 -0
- package/dist/chat/ui/intelligent-ui/chart-data.js +46 -0
- package/dist/chat/ui/intelligent-ui/node-text.d.ts +15 -0
- package/dist/chat/ui/intelligent-ui/node-text.js +106 -0
- package/dist/chat/ui/intelligent-ui/registry.d.ts +2 -0
- package/dist/chat/ui/intelligent-ui/registry.js +51 -0
- package/dist/chat/ui/intelligent-ui/render-context.d.ts +53 -0
- package/dist/chat/ui/intelligent-ui/render-context.js +3 -0
- package/dist/chat/ui/intelligent-ui/renderers/chart.d.ts +3 -0
- package/dist/chat/ui/intelligent-ui/renderers/chart.js +123 -0
- package/dist/chat/ui/intelligent-ui/renderers/choices.d.ts +4 -0
- package/dist/chat/ui/intelligent-ui/renderers/choices.js +45 -0
- package/dist/chat/ui/intelligent-ui/renderers/content.d.ts +14 -0
- package/dist/chat/ui/intelligent-ui/renderers/content.js +59 -0
- package/dist/chat/ui/intelligent-ui/renderers/data.d.ts +7 -0
- package/dist/chat/ui/intelligent-ui/renderers/data.js +80 -0
- package/dist/chat/ui/intelligent-ui/renderers/inputs.d.ts +8 -0
- package/dist/chat/ui/intelligent-ui/renderers/inputs.js +72 -0
- package/dist/chat/ui/intelligent-ui/renderers/links.d.ts +10 -0
- package/dist/chat/ui/intelligent-ui/renderers/links.js +87 -0
- package/dist/chat/ui/intelligent-ui/renderers/structure.d.ts +6 -0
- package/dist/chat/ui/intelligent-ui/renderers/structure.js +32 -0
- package/dist/chat/ui/intelligent-ui/renderers/text.d.ts +8 -0
- package/dist/chat/ui/intelligent-ui/renderers/text.js +61 -0
- package/dist/chat/ui/intelligent-ui/reply-parts.d.ts +9 -0
- package/dist/chat/ui/intelligent-ui/reply-parts.js +18 -0
- package/dist/chat/ui/intelligent-ui/table-data.d.ts +31 -0
- package/dist/chat/ui/intelligent-ui/table-data.js +79 -0
- package/dist/chat/ui/intelligent-ui/tones.d.ts +7 -0
- package/dist/chat/ui/intelligent-ui/tones.js +31 -0
- package/dist/chat/ui/intelligent-ui/use-later.d.ts +1 -0
- package/dist/chat/ui/intelligent-ui/use-later.js +16 -0
- package/dist/chat/ui/intelligent-ui/use-ui-links.d.ts +9 -0
- package/dist/chat/ui/intelligent-ui/use-ui-links.js +126 -0
- package/dist/chat/ui/intelligent-ui/use-ui-queries.d.ts +21 -0
- package/dist/chat/ui/intelligent-ui/use-ui-queries.js +272 -0
- package/dist/chat/ui/reply-context.d.ts +8 -0
- package/dist/chat/ui/reply-context.js +1 -0
- package/dist/chat/ui/rows/GeneratedImageRow.d.ts +5 -0
- package/dist/chat/ui/rows/GeneratedImageRow.js +121 -0
- package/dist/chat/ui/rows/MessageRows.js +21 -4
- package/dist/chat/ui/rows/Rows.js +4 -2
- package/dist/chat/ui/rows/SubagentRow.js +5 -0
- package/dist/chat/ui/rows/WorkRows.d.ts +4 -1
- package/dist/chat/ui/rows/WorkRows.js +4 -3
- package/dist/host.d.ts +11 -1
- package/dist/locales/en/agent-chat.json +101 -0
- package/dist/locales/nl/agent-chat.json +101 -0
- package/dist/state/chats.js +13 -0
- package/dist/theme.css +19 -0
- package/package.json +128 -7
- package/src/chat/logic/generated-image.ts +48 -0
- package/src/chat/logic/timeline-target.ts +41 -1
- package/src/chat/logic/timeline.ts +19 -10
- package/src/chat/subagent-conversation.ts +13 -2
- package/src/chat/timeline-flash.ts +5 -3
- package/src/chat/ui/ImageView.tsx +51 -8
- package/src/chat/ui/SubagentTimeline.tsx +24 -1
- package/src/chat/ui/Timeline.tsx +97 -21
- package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +219 -0
- package/src/chat/ui/intelligent-ui/UiFallbackPart.tsx +21 -0
- package/src/chat/ui/intelligent-ui/UiReply.tsx +541 -0
- package/src/chat/ui/intelligent-ui/block-local.ts +51 -0
- package/src/chat/ui/intelligent-ui/chart-data.ts +67 -0
- package/src/chat/ui/intelligent-ui/node-text.ts +119 -0
- package/src/chat/ui/intelligent-ui/registry.ts +53 -0
- package/src/chat/ui/intelligent-ui/render-context.ts +84 -0
- package/src/chat/ui/intelligent-ui/renderers/chart.tsx +319 -0
- package/src/chat/ui/intelligent-ui/renderers/choices.tsx +102 -0
- package/src/chat/ui/intelligent-ui/renderers/content.tsx +140 -0
- package/src/chat/ui/intelligent-ui/renderers/data.tsx +181 -0
- package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +144 -0
- package/src/chat/ui/intelligent-ui/renderers/links.tsx +158 -0
- package/src/chat/ui/intelligent-ui/renderers/structure.tsx +84 -0
- package/src/chat/ui/intelligent-ui/renderers/text.tsx +124 -0
- package/src/chat/ui/intelligent-ui/reply-parts.ts +22 -0
- package/src/chat/ui/intelligent-ui/table-data.ts +104 -0
- package/src/chat/ui/intelligent-ui/tones.ts +37 -0
- package/src/chat/ui/intelligent-ui/use-later.ts +17 -0
- package/src/chat/ui/intelligent-ui/use-ui-links.ts +143 -0
- package/src/chat/ui/intelligent-ui/use-ui-queries.ts +308 -0
- package/src/chat/ui/reply-context.ts +7 -0
- package/src/chat/ui/rows/GeneratedImageRow.tsx +244 -0
- package/src/chat/ui/rows/MessageRows.tsx +60 -4
- package/src/chat/ui/rows/Rows.tsx +4 -2
- package/src/chat/ui/rows/SubagentRow.tsx +5 -0
- package/src/chat/ui/rows/WorkRows.tsx +20 -5
- package/src/host.ts +27 -1
- package/src/locales/en/agent-chat.json +101 -0
- package/src/locales/nl/agent-chat.json +101 -0
- package/src/state/chats.ts +13 -0
- package/src/theme.css +19 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { uiInputValues, UI_CATALOG_VERSION, UI_HOST_LIMITS, UiState } from '@adecore/intelligent-ui';
|
|
3
|
+
import { chatHost } from '../../../host.js';
|
|
4
|
+
const NO_READINGS = {};
|
|
5
|
+
// A changed input reads once it has rested this long, so dragging a slider reads its last value only.
|
|
6
|
+
const INPUT_SETTLE_MS = 300;
|
|
7
|
+
/* The reading that holds for these inputs: this view's own read of them, or the frozen first one for the inputs the block starts with. */
|
|
8
|
+
function successfulReading(local, initial, identity, signature, defaultInputs) {
|
|
9
|
+
if (local?.identity === identity && local.successful?.inputs === signature) {
|
|
10
|
+
return local.successful.reading;
|
|
11
|
+
}
|
|
12
|
+
return signature === defaultInputs && initial?.state === 'fresh' ? initial : undefined;
|
|
13
|
+
}
|
|
14
|
+
function settleDeadline() {
|
|
15
|
+
return Date.now() + INPUT_SETTLE_MS;
|
|
16
|
+
}
|
|
17
|
+
function documentVisible() {
|
|
18
|
+
return document.visibilityState !== 'hidden';
|
|
19
|
+
}
|
|
20
|
+
export function useUiQueries(block, state, context, element, frozen) {
|
|
21
|
+
const [readings, setReadings] = useState({});
|
|
22
|
+
const [busyInput, setBusyInput] = useState(null);
|
|
23
|
+
const lastRead = useRef({ inputKey: '', at: 0 });
|
|
24
|
+
const names = Object.keys(block.queries);
|
|
25
|
+
const inputs = JSON.stringify(uiInputValues(block, state));
|
|
26
|
+
const identity = JSON.stringify([context.scopeId, context.chatId, context.itemId, block.id, block.revision]);
|
|
27
|
+
const inputKey = JSON.stringify([identity, inputs]);
|
|
28
|
+
const reading = busyInput === inputKey;
|
|
29
|
+
const defaultInputs = useMemo(() => JSON.stringify(uiInputValues(block, new UiState(block))), [block]);
|
|
30
|
+
const initial = frozen && frozen.revision === block.revision ? frozen.readings : NO_READINGS;
|
|
31
|
+
const successfulFor = (name, signature) => successfulReading(readings[name], initial[name], identity, signature, defaultInputs);
|
|
32
|
+
const readsFor = (values) => {
|
|
33
|
+
const signature = JSON.stringify(values);
|
|
34
|
+
const reads = {};
|
|
35
|
+
for (const name of names) {
|
|
36
|
+
const local = readings[name];
|
|
37
|
+
const successful = successfulFor(name, signature);
|
|
38
|
+
if (!successful?.readId || (local?.identity === identity && local.reading.state === 'refused')) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
reads[name] = successful.readId;
|
|
42
|
+
}
|
|
43
|
+
return reads;
|
|
44
|
+
};
|
|
45
|
+
const reads = readsFor(JSON.parse(inputs));
|
|
46
|
+
const ready = reads !== null;
|
|
47
|
+
const query = chatHost().intelligentUi?.query;
|
|
48
|
+
const subscribe = chatHost().intelligentUi?.subscribe;
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
const scope = state.scope();
|
|
51
|
+
for (const name of Object.keys(block.queries)) {
|
|
52
|
+
const successful = successfulReading(readings[name], initial[name], identity, inputs, defaultInputs);
|
|
53
|
+
if (successful && JSON.stringify(scope[name]) !== JSON.stringify(successful.value)) {
|
|
54
|
+
state.setQuery(name, successful.value, block);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}, [state, block, readings, identity, inputs, defaultInputs, initial]);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (!query ||
|
|
60
|
+
!element ||
|
|
61
|
+
!block.complete ||
|
|
62
|
+
block.catalogVersion !== UI_CATALOG_VERSION ||
|
|
63
|
+
!block.revision ||
|
|
64
|
+
!Object.keys(block.queries).length ||
|
|
65
|
+
context.phase !== 'final') {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
let stopped = false;
|
|
69
|
+
let visible = typeof IntersectionObserver === 'undefined';
|
|
70
|
+
let busy = false;
|
|
71
|
+
let timer = null;
|
|
72
|
+
// The first read of this block reads at once; a changed input waits until it rests.
|
|
73
|
+
const changed = lastRead.current.inputKey !== '' && lastRead.current.inputKey !== inputKey;
|
|
74
|
+
const settledAt = changed ? settleDeadline() : 0;
|
|
75
|
+
const refresh = async () => {
|
|
76
|
+
if (stopped || !visible || !documentVisible() || busy) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const wait = Math.max(settledAt - Date.now(), lastRead.current.inputKey === inputKey ? UI_HOST_LIMITS.refreshMilliseconds - (Date.now() - lastRead.current.at) : 0);
|
|
80
|
+
if (wait > 0) {
|
|
81
|
+
if (timer === null) {
|
|
82
|
+
if (!visible || !documentVisible()) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
timer = setTimeout(() => {
|
|
86
|
+
timer = null;
|
|
87
|
+
void refresh();
|
|
88
|
+
}, wait);
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
busy = true;
|
|
93
|
+
lastRead.current = { inputKey, at: Date.now() };
|
|
94
|
+
setBusyInput(inputKey);
|
|
95
|
+
for (const name of Object.keys(block.queries).slice(0, UI_HOST_LIMITS.queries)) {
|
|
96
|
+
if (!visible || !documentVisible()) {
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const reading = await query(context.scopeId, {
|
|
101
|
+
chatId: context.chatId,
|
|
102
|
+
itemId: context.itemId,
|
|
103
|
+
blockId: block.id,
|
|
104
|
+
revision: block.revision,
|
|
105
|
+
query: name,
|
|
106
|
+
values: JSON.parse(inputs)
|
|
107
|
+
});
|
|
108
|
+
if (stopped) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (reading.state === 'fresh') {
|
|
112
|
+
state.setQuery(name, reading.value, block);
|
|
113
|
+
}
|
|
114
|
+
setReadings((previous) => ({
|
|
115
|
+
...previous,
|
|
116
|
+
[name]: {
|
|
117
|
+
identity,
|
|
118
|
+
reading,
|
|
119
|
+
successful: reading.state === 'fresh' ? { inputs, reading } : previous[name]?.identity === identity ? previous[name].successful : undefined
|
|
120
|
+
}
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (stopped) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
setReadings((previous) => ({
|
|
128
|
+
...previous,
|
|
129
|
+
[name]: {
|
|
130
|
+
identity,
|
|
131
|
+
reading: { state: 'failed', readAt: Date.now(), code: 'unreadable', reason: error instanceof Error ? error.message : undefined },
|
|
132
|
+
successful: previous[name]?.identity === identity ? previous[name].successful : undefined
|
|
133
|
+
}
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
busy = false;
|
|
138
|
+
if (stopped) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
setBusyInput(null);
|
|
142
|
+
timer = setTimeout(() => {
|
|
143
|
+
timer = null;
|
|
144
|
+
void refresh();
|
|
145
|
+
}, UI_HOST_LIMITS.refreshMilliseconds);
|
|
146
|
+
};
|
|
147
|
+
const observer = typeof IntersectionObserver === 'undefined'
|
|
148
|
+
? null
|
|
149
|
+
: new IntersectionObserver((entries) => {
|
|
150
|
+
visible = entries.some((entry) => entry.isIntersecting);
|
|
151
|
+
if (visible) {
|
|
152
|
+
void refresh();
|
|
153
|
+
}
|
|
154
|
+
else if (timer !== null) {
|
|
155
|
+
clearTimeout(timer);
|
|
156
|
+
timer = null;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
if (element) {
|
|
160
|
+
observer?.observe(element);
|
|
161
|
+
}
|
|
162
|
+
const focus = () => {
|
|
163
|
+
void refresh();
|
|
164
|
+
};
|
|
165
|
+
document.addEventListener('visibilitychange', focus);
|
|
166
|
+
const unsubscribe = subscribe?.(context.scopeId, context.chatId, focus);
|
|
167
|
+
void refresh();
|
|
168
|
+
return () => {
|
|
169
|
+
stopped = true;
|
|
170
|
+
// A read cut short by this cleanup never reaches its own reset, which would leave the choices closed.
|
|
171
|
+
setBusyInput((current) => (current === inputKey ? null : current));
|
|
172
|
+
observer?.disconnect();
|
|
173
|
+
unsubscribe?.();
|
|
174
|
+
document.removeEventListener('visibilitychange', focus);
|
|
175
|
+
if (timer !== null) {
|
|
176
|
+
clearTimeout(timer);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}, [block, state, context.scopeId, context.chatId, context.itemId, context.phase, inputs, query, subscribe, element, identity, inputKey]);
|
|
180
|
+
const currentReadings = Object.fromEntries(names.flatMap((name) => {
|
|
181
|
+
const local = readings[name];
|
|
182
|
+
const reading = local?.identity === identity ? local.reading : initial[name];
|
|
183
|
+
return reading ? [[name, reading]] : [];
|
|
184
|
+
}));
|
|
185
|
+
const failed = Object.entries(currentReadings).find(([, value]) => value.state !== 'fresh');
|
|
186
|
+
const live = names.length
|
|
187
|
+
? {
|
|
188
|
+
state: reading || (!ready && !failed) ? 'reading' : (failed?.[1].state ?? 'fresh'),
|
|
189
|
+
sources: names.map((name) => block.queries[name].source),
|
|
190
|
+
readAt: Object.values(currentReadings)
|
|
191
|
+
.filter((reading) => reading.state === 'fresh')
|
|
192
|
+
.reduce((at, value) => (at === null ? value.readAt : Math.min(at, value.readAt)), null),
|
|
193
|
+
source: failed ? block.queries[failed[0]]?.source : undefined,
|
|
194
|
+
reason: failed?.[1].reason,
|
|
195
|
+
code: failed?.[1].code
|
|
196
|
+
}
|
|
197
|
+
: undefined;
|
|
198
|
+
return { live, reads: reads ?? {}, reading, ready, readsFor, currentReadings };
|
|
199
|
+
}
|
|
200
|
+
export function useUiLiveValues(block, nodes, readings) {
|
|
201
|
+
const [committed, setCommitted] = useState({
|
|
202
|
+
token: '',
|
|
203
|
+
values: new Map(),
|
|
204
|
+
previous: new Map()
|
|
205
|
+
});
|
|
206
|
+
const dependencies = new Map();
|
|
207
|
+
const names = new Set(Object.keys(block.queries));
|
|
208
|
+
const references = (value, into) => {
|
|
209
|
+
if (!value || typeof value !== 'object') {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const expression = value;
|
|
213
|
+
if (expression.kind === 'reference' && names.has(String(expression.name))) {
|
|
214
|
+
into.add(String(expression.name));
|
|
215
|
+
}
|
|
216
|
+
for (const child of Object.values(expression)) {
|
|
217
|
+
if (Array.isArray(child)) {
|
|
218
|
+
child.forEach((item) => references(item, into));
|
|
219
|
+
}
|
|
220
|
+
else if (typeof child === 'object') {
|
|
221
|
+
references(child, into);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
const written = (nodes, inherited = new Set()) => {
|
|
226
|
+
for (const node of nodes) {
|
|
227
|
+
const related = new Set(inherited);
|
|
228
|
+
references(node.expressions, related);
|
|
229
|
+
dependencies.set(node.id, related);
|
|
230
|
+
written(node.children, node.type === 'Each' ? related : inherited);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
written(block.nodes);
|
|
234
|
+
const values = new Map();
|
|
235
|
+
const sources = new Map();
|
|
236
|
+
const collect = (nodes) => {
|
|
237
|
+
for (const node of nodes) {
|
|
238
|
+
const related = dependencies.get(node.sourceId ?? node.id);
|
|
239
|
+
if (related?.size && !node.error) {
|
|
240
|
+
values.set(node.id, node.props);
|
|
241
|
+
sources.set(node.id, related);
|
|
242
|
+
}
|
|
243
|
+
collect(node.children);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
collect(nodes);
|
|
247
|
+
const token = JSON.stringify([block.revision, Object.entries(readings).map(([name, reading]) => [name, reading.state, reading.readId, reading.readAt])]);
|
|
248
|
+
const previous = committed.token === token ? committed.previous : committed.values;
|
|
249
|
+
if (committed.token !== token) {
|
|
250
|
+
setCommitted({ token, values, previous });
|
|
251
|
+
}
|
|
252
|
+
if (!names.size) {
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
return (id, prop, path) => {
|
|
256
|
+
const related = sources.get(id);
|
|
257
|
+
if (!related) {
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
const get = (props) => {
|
|
261
|
+
const value = props?.[prop];
|
|
262
|
+
return path && Array.isArray(value) ? value[path[0]]?.[path[1]] : value;
|
|
263
|
+
};
|
|
264
|
+
const before = get(previous.get(id));
|
|
265
|
+
const value = get(values.get(id));
|
|
266
|
+
return {
|
|
267
|
+
previous: before,
|
|
268
|
+
changed: before !== undefined && JSON.stringify(before) !== JSON.stringify(value),
|
|
269
|
+
stale: [...related].some((name) => readings[name]?.state === 'failed' || readings[name]?.state === 'refused')
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
import type { UiReplyTarget } from '../logic/timeline-target.js';
|
|
1
2
|
import type { ChatInfo } from '@adecore/agent-contracts';
|
|
2
3
|
export declare const ReplyContext: import("react").Context<{
|
|
3
4
|
provider: ChatInfo['provider'];
|
|
4
5
|
chatId?: string;
|
|
5
6
|
} | null>;
|
|
7
|
+
export declare const UiReplyNavigationContext: import("react").Context<{
|
|
8
|
+
chatId: string;
|
|
9
|
+
reveal(target: UiReplyTarget): void;
|
|
10
|
+
flash: (UiReplyTarget & {
|
|
11
|
+
nonce: number;
|
|
12
|
+
}) | null;
|
|
13
|
+
} | null>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { Trans, useTranslation } from 'react-i18next';
|
|
5
|
+
import { Check, Copy, Ellipsis, ExternalLink, FolderDown, ImageIcon, Maximize2 } from 'lucide-react';
|
|
6
|
+
import { Button, ButtonGroup, copyText, ErrorBoundary, FileIcon, Icon, IconButton, Menu, Surface } from '@adecore/ui';
|
|
7
|
+
import { chatHost } from '../../../host.js';
|
|
8
|
+
import { useChatScope } from '../../../scope.js';
|
|
9
|
+
import { useCurrentItem } from '../../../state/chats.js';
|
|
10
|
+
import { formatBytes } from '../../attachments.js';
|
|
11
|
+
import { attachmentAspect, generatedImageView } from '../../logic/generated-image.js';
|
|
12
|
+
import { toolStartedAt } from '../../logic/tools.js';
|
|
13
|
+
import { CHIP_IN_MESSAGE, MENTION_TONE } from '../chips.js';
|
|
14
|
+
import { openFileLink, useFileLinkCwd } from '../file-links.js';
|
|
15
|
+
import { ImageThumb } from '../ImageView.js';
|
|
16
|
+
import { ROW_GUTTER } from '../icons.js';
|
|
17
|
+
import { RunningFor, WorkRow } from './WorkRows.js';
|
|
18
|
+
const SAVED_FLASH_MS = 2000;
|
|
19
|
+
// Where each image went, so a row the thread unmounts while scrolling still says so when it comes back.
|
|
20
|
+
const savedPaths = new Map();
|
|
21
|
+
/* True for `ms` after `start()`, for a check mark that says an action landed. */
|
|
22
|
+
function useFlash(ms) {
|
|
23
|
+
const [on, setOn] = useState(false);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!on) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const timer = setTimeout(() => setOn(false), ms);
|
|
29
|
+
return () => clearTimeout(timer);
|
|
30
|
+
}, [on, ms]);
|
|
31
|
+
return [on, () => setOn(true)];
|
|
32
|
+
}
|
|
33
|
+
function failureText(failure, t) {
|
|
34
|
+
switch (failure.kind) {
|
|
35
|
+
case 'provider':
|
|
36
|
+
return failure.message ?? undefined;
|
|
37
|
+
case 'empty':
|
|
38
|
+
return t('generatedImage.empty');
|
|
39
|
+
case 'not-image':
|
|
40
|
+
return t('generatedImage.notImage');
|
|
41
|
+
case 'too-large':
|
|
42
|
+
return t('generatedImage.tooLarge', { size: formatBytes(failure.limit) });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/* The line over the image, in the measures of a tool line but nothing to fold. */
|
|
46
|
+
function HeadLine({ label, detail, live, startedAt }) {
|
|
47
|
+
return (_jsxs("div", { className: "-mx-1 mb-0.5 flex h-7 items-center gap-2 px-1 text-xs text-text-muted", children: [_jsx("span", { className: clsx(ROW_GUTTER, live && 'text-accent'), children: _jsx(Icon, { icon: ImageIcon, size: 12 }) }), _jsx("span", { className: clsx('shrink-0', live && 'chat-live-text'), children: label }), detail && _jsx("span", { className: "min-w-0 truncate text-text-faint", children: detail }), _jsx("span", { className: "grow" }), startedAt !== undefined && _jsx(RunningFor, { startedAt: startedAt })] }));
|
|
48
|
+
}
|
|
49
|
+
/* The file a saved image became, which opens where the app opens a file link. */
|
|
50
|
+
function SavedFile({ path }) {
|
|
51
|
+
const cwd = useFileLinkCwd();
|
|
52
|
+
const { id } = useChatScope();
|
|
53
|
+
const chip = (_jsxs(_Fragment, { children: [_jsx(FileIcon, { path: path, size: 14 }), _jsx("span", { className: "truncate", children: path })] }));
|
|
54
|
+
if (chatHost().fileLinks === null) {
|
|
55
|
+
return _jsx("span", { className: clsx(CHIP_IN_MESSAGE, MENTION_TONE), children: chip });
|
|
56
|
+
}
|
|
57
|
+
return (_jsx("button", { type: "button", className: clsx(CHIP_IN_MESSAGE, MENTION_TONE, 'hover:brightness-95'), "data-file-path": path, onClick: () => openFileLink(cwd, { path, directory: false }, id), children: chip }));
|
|
58
|
+
}
|
|
59
|
+
function ReadyImage({ chatId, attachment, prompt, transparent }) {
|
|
60
|
+
const { t } = useTranslation('agent-chat');
|
|
61
|
+
const { id: scopeId } = useChatScope();
|
|
62
|
+
const { attachments } = chatHost();
|
|
63
|
+
const source = attachments.useUrl(scopeId, chatId, attachment.id);
|
|
64
|
+
const savedKey = `${scopeId}\n${chatId}\n${attachment.id}`;
|
|
65
|
+
const [lightbox, setLightbox] = useState(false);
|
|
66
|
+
const [saving, setSaving] = useState(false);
|
|
67
|
+
const [savedPath, setSavedPath] = useState(() => savedPaths.get(savedKey) ?? null);
|
|
68
|
+
const [savedFlash, flashSaved] = useFlash(SAVED_FLASH_MS);
|
|
69
|
+
const missing = source.failure !== null;
|
|
70
|
+
const aspect = attachmentAspect(attachment);
|
|
71
|
+
const { open, saveToProject } = attachments;
|
|
72
|
+
const detail = [
|
|
73
|
+
attachment.width !== undefined && attachment.height !== undefined
|
|
74
|
+
? t('generatedImage.dimensions', { width: attachment.width, height: attachment.height })
|
|
75
|
+
: null,
|
|
76
|
+
formatBytes(attachment.size),
|
|
77
|
+
transparent ? t('generatedImage.transparent') : null
|
|
78
|
+
]
|
|
79
|
+
.filter((part) => part !== null)
|
|
80
|
+
.join(' · ');
|
|
81
|
+
const openFile = open === undefined ? undefined : () => open(scopeId, chatId, attachment.id, attachment.name);
|
|
82
|
+
const save = saveToProject === undefined
|
|
83
|
+
? undefined
|
|
84
|
+
: async () => {
|
|
85
|
+
setSaving(true);
|
|
86
|
+
try {
|
|
87
|
+
const path = await saveToProject(scopeId, chatId, attachment.id, attachment.name);
|
|
88
|
+
if (path !== null) {
|
|
89
|
+
savedPaths.set(savedKey, path);
|
|
90
|
+
setSavedPath(path);
|
|
91
|
+
flashSaved();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// The host's dialog shows why; the row stays as it was.
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
setSaving(false);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const lightboxActions = (_jsxs(_Fragment, { children: [openFile !== undefined && _jsx(IconButton, { icon: ExternalLink, size: "sm", label: t('generatedImage.open'), onClick: openFile }), save !== undefined && _jsx(IconButton, { icon: FolderDown, size: "sm", label: t('generatedImage.save'), disabled: saving, onClick: () => void save() })] }));
|
|
102
|
+
return (_jsxs("div", { children: [_jsx(HeadLine, { label: t('generatedImage.label'), detail: detail }), _jsxs("div", { className: "flex max-w-90 flex-col gap-1.5", children: [_jsxs("div", { className: clsx('group/image relative', aspect === undefined && 'w-fit', transparent && source.url !== null && 'chat-checkerboard rounded-lg'), children: [_jsx(ImageThumb, { source: source, alt: attachment.name, aspect: aspect, className: aspect === undefined ? 'max-h-90 max-w-full object-contain' : undefined, actions: lightboxActions, open: lightbox, onOpenChange: setLightbox }), source.url !== null && (_jsx(Surface, { role: "toolbar", "aria-label": t('generatedImage.actions'), className: "absolute top-2 right-2 rounded-lg p-0.5 opacity-0 transition-opacity group-focus-within/image:opacity-100 group-hover/image:opacity-100 has-[[data-popup-open]]:opacity-100 pointer-coarse:opacity-100", children: _jsxs(ButtonGroup, { children: [_jsx(IconButton, { icon: Maximize2, size: "sm", label: t('generatedImage.openLarge'), onClick: () => setLightbox(true) }), prompt !== null && (_jsxs(Menu.Root, { children: [_jsx(IconButton, { icon: Ellipsis, size: "sm", label: t('generatedImage.more'), render: _jsx(Menu.Trigger, {}) }), _jsx(Menu.Popup, { align: "end", children: _jsxs(Menu.Item, { onClick: () => copyText(prompt), children: [_jsx(Icon, { icon: Copy, size: 14 }), " ", t('generatedImage.copyPrompt')] }) })] }))] }) }))] }), (openFile !== undefined || save !== undefined) && (_jsxs("div", { className: "flex items-center gap-1.5", children: [openFile !== undefined && (_jsx(Button, { size: "xs", variant: "secondary", disabled: missing, onClick: openFile, children: t('generatedImage.open') })), save !== undefined && (_jsx(Button, { size: "xs", variant: "secondary", disabled: missing || saving || savedFlash, onClick: () => void save(), children: savedFlash ? (_jsxs(_Fragment, { children: [_jsx(Icon, { icon: Check, size: 12 }), " ", t('generatedImage.saved')] })) : (t('generatedImage.save')) }))] })), savedPath !== null && !savedFlash && (_jsx("div", { className: "flex min-w-0 items-center gap-1.5 text-xs text-text-muted", children: _jsx(Trans, { t: t, i18nKey: "generatedImage.savedTo", values: { path: savedPath }, components: { file: _jsx(SavedFile, { path: savedPath }) } }) }))] })] }));
|
|
103
|
+
}
|
|
104
|
+
/*
|
|
105
|
+
* The row of an image generation. Not a fold like other tool calls: the image is the result a person
|
|
106
|
+
* came for, so it stays open, opens large in the lightbox and offers what the host lets a person do
|
|
107
|
+
* with it. A failure is an ordinary failed tool line, with the reason as its detail.
|
|
108
|
+
*/
|
|
109
|
+
export function GeneratedImageRow({ chatId, tool: derived }) {
|
|
110
|
+
const { t } = useTranslation('agent-chat');
|
|
111
|
+
const tool = useCurrentItem(chatId, derived);
|
|
112
|
+
const view = generatedImageView(tool);
|
|
113
|
+
switch (view.state) {
|
|
114
|
+
case 'generating':
|
|
115
|
+
return _jsx(HeadLine, { label: t('generatedImage.generating'), live: true, startedAt: toolStartedAt(tool) });
|
|
116
|
+
case 'failed':
|
|
117
|
+
return (_jsx(WorkRow, { tool: tool, icon: _jsx(Icon, { icon: ImageIcon, size: 12 }), label: t('generatedImage.failed'), detail: failureText(view.failure, t) ?? '', failed: true }));
|
|
118
|
+
case 'ready':
|
|
119
|
+
return (_jsx(ErrorBoundary, { label: t('generatedImage.renderFailed'), resetKeys: [view.attachment], compact: true, className: "relative rounded-lg", children: _jsx(ReadyImage, { chatId: chatId, attachment: view.attachment, prompt: view.prompt, transparent: view.transparent }) }));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -2,7 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { useContext, useMemo, useState } from 'react';
|
|
3
3
|
import clsx from 'clsx';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
|
-
import { Bot, Brain, Check, ChevronDown, CircleAlert, File, Info, MessageCircleQuestionMark, TriangleAlert, X } from 'lucide-react';
|
|
5
|
+
import { Bot, Brain, Check, ChevronDown, CircleAlert, File, Info, MessageCircleQuestionMark, Reply, TriangleAlert, X } from 'lucide-react';
|
|
6
6
|
import { fileBadge, formatBytes, isImageAttachment } from '../../attachments.js';
|
|
7
7
|
import { useChatRow, useCurrentItem } from '../../../state/chats.js';
|
|
8
8
|
import { chatHost } from '../../../host.js';
|
|
@@ -15,12 +15,13 @@ import { FadingWords } from '../FadingWords.js';
|
|
|
15
15
|
import { settledBlocksText } from '../markdown-blocks.js';
|
|
16
16
|
import { WHOLE_FADE_CLASS } from '../rehype-fade.js';
|
|
17
17
|
import { useRevealedText } from '../reveal.js';
|
|
18
|
-
import { formatAgo, formatElapsedShort, formatTokens } from '@adecore/ui/format';
|
|
18
|
+
import { formatAgo, formatElapsedShort, formatMoment, formatTokens } from '@adecore/ui/format';
|
|
19
19
|
import { Icon, Tooltip, useTickingText } from '@adecore/ui';
|
|
20
20
|
import { toolSummary } from '../../logic/tools.js';
|
|
21
21
|
import { ROW_GUTTER } from '../icons.js';
|
|
22
22
|
import { useOpenForFind } from '../find-reveal.js';
|
|
23
|
-
import { ReplyContext } from '../reply-context.js';
|
|
23
|
+
import { ReplyContext, UiReplyNavigationContext } from '../reply-context.js';
|
|
24
|
+
import { UiReply } from '../intelligent-ui/UiReply.js';
|
|
24
25
|
// A long prompt folds so the answer stays in view; the reader can open it.
|
|
25
26
|
const USER_FOLD_LINES = 8;
|
|
26
27
|
const USER_FOLD_CHARS = 600;
|
|
@@ -55,7 +56,20 @@ export function UserRow({ chatId, item }) {
|
|
|
55
56
|
const attachments = item.attachments ?? [];
|
|
56
57
|
const chats = item.chats ?? [];
|
|
57
58
|
useOpenForFind(item.id, 'text', setOpen);
|
|
58
|
-
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.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: _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')] }))] }))] }));
|
|
59
|
+
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: _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
|
+
}
|
|
61
|
+
/*
|
|
62
|
+
* The choice a message answers. The label stays out of the message, which holds exactly what the agent
|
|
63
|
+
* read, and out of a copied selection; a screen reader hears it between the heading and the message.
|
|
64
|
+
*/
|
|
65
|
+
function ChoiceLine({ chatId, choice }) {
|
|
66
|
+
const { t } = useTranslation('agent-chat');
|
|
67
|
+
const inherited = useContext(UiReplyNavigationContext);
|
|
68
|
+
const navigation = inherited?.chatId === chatId ? inherited : null;
|
|
69
|
+
const Tag = navigation ? 'button' : 'div';
|
|
70
|
+
return (_jsxs(Tag, { type: navigation ? 'button' : undefined, onClick: navigation ? () => navigation.reveal(choice) : undefined, className: clsx('mb-1 flex max-w-[80%] items-start gap-1.5 px-1 text-left text-xs text-text-muted select-none', navigation && 'rounded-md hover:text-text focus-visible:outline-2 focus-visible:outline-accent'), children: [_jsx(Icon, { icon: Reply, size: 12, className: "mt-1 shrink-0" }), _jsx("span", { className: "min-w-0 wrap-anywhere", children: choice.older
|
|
71
|
+
? t('blocks.choseOlder', { label: choice.label, time: formatMoment(choice.sourceAt) })
|
|
72
|
+
: t('blocks.chose', { label: choice.label }) })] }));
|
|
59
73
|
}
|
|
60
74
|
/*
|
|
61
75
|
* Who wrote a message, for a screen reader only: one heading per message is what lets VoiceOver's
|
|
@@ -114,6 +128,9 @@ export function AssistantRow({ chatId, item: derived }) {
|
|
|
114
128
|
const [sawWriting] = useState(item.streaming);
|
|
115
129
|
const live = mode === 'words' && item.streaming;
|
|
116
130
|
const reveal = useRevealedText(item.text, live);
|
|
131
|
+
if (item.ui?.length && !item.parentToolUseId && !(mode === 'whole' && item.streaming)) {
|
|
132
|
+
return (_jsxs("div", { "data-find-item": item.id, className: "-mx-1 px-1 pb-2", children: [_jsx(ReplyHeading, { chatId: chatId }), _jsx("div", { "data-find-field": "text", "data-quote-answer": true, className: "contents", children: _jsx(UiReply, { text: item.text, blocks: item.ui, answers: item.uiAnswers, queries: item.uiQueries, reply: reply, context: { scopeId: scope.id, chatId, itemId: item.id, phase: item.streaming ? 'streaming' : 'final' } }) })] }));
|
|
133
|
+
}
|
|
117
134
|
if (mode === 'blocks') {
|
|
118
135
|
const settled = item.streaming ? settledBlocksText(item.text) : item.text;
|
|
119
136
|
return (_jsxs("div", { "data-find-item": item.id, className: "-mx-1 px-1 pb-2", children: [_jsx(ReplyHeading, { chatId: chatId }), settled !== '' && (_jsx("div", { "data-find-field": "text", "data-quote-answer": true, className: "contents", children: _jsx(ReplyMarkdown, { text: settled, streaming: false, arriving: sawWriting, reply: reply }) })), item.streaming && _jsx(Writing, {})] }));
|
|
@@ -2,7 +2,9 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { ErrorBoundary } from '@adecore/ui';
|
|
4
4
|
import { AgentTurnRow, ApprovalHistoryRow, AssistantRow, CompactionRow, NoteRow, QuestionHistoryRow, ReportRow, ThinkingRow, UserRow } from './MessageRows.js';
|
|
5
|
+
import { isImageGeneration } from '../../logic/generated-image.js';
|
|
5
6
|
import { ForksRow } from './ForksRow.js';
|
|
7
|
+
import { GeneratedImageRow } from './GeneratedImageRow.js';
|
|
6
8
|
import { SubagentBranchRow } from './SubagentRow.js';
|
|
7
9
|
import { VisualRow } from './VisualRow.js';
|
|
8
10
|
import { WorkflowRow } from './WorkflowRow.js';
|
|
@@ -31,9 +33,9 @@ export function Row({ row, chatId, toggleGroup, toggleTurn, toggleSubagent, open
|
|
|
31
33
|
case 'thinking':
|
|
32
34
|
return _jsx(ThinkingRow, { chatId: chatId, item: row.item });
|
|
33
35
|
case 'work':
|
|
34
|
-
return _jsx(WorkRow, { tool: row.tool });
|
|
36
|
+
return isImageGeneration(row.tool) ? _jsx(GeneratedImageRow, { chatId: chatId, tool: row.tool }) : _jsx(WorkRow, { tool: row.tool });
|
|
35
37
|
case 'work-live':
|
|
36
|
-
return _jsx(WorkLiveRow, { chatId: chatId, tool: row.tool });
|
|
38
|
+
return isImageGeneration(row.tool) ? _jsx(GeneratedImageRow, { chatId: chatId, tool: row.tool }) : _jsx(WorkLiveRow, { chatId: chatId, tool: row.tool });
|
|
37
39
|
case 'workflow':
|
|
38
40
|
return _jsx(WorkflowRow, { chatId: chatId, tool: row.tool, workflow: row.workflow, onOpenAgent: openConversation });
|
|
39
41
|
case 'work-group':
|
|
@@ -8,6 +8,8 @@ import { Icon, useNow } from '@adecore/ui';
|
|
|
8
8
|
import { useOpenForFind } from '../find-reveal.js';
|
|
9
9
|
import { Markdown } from '../Markdown.js';
|
|
10
10
|
import { TaskRow } from './TaskRow.js';
|
|
11
|
+
import { isImageGeneration } from '../../logic/generated-image.js';
|
|
12
|
+
import { GeneratedImageRow } from './GeneratedImageRow.js';
|
|
11
13
|
import { RunningFor, ToggleLine, WorkLiveRow, WorkRow } from './WorkRows.js';
|
|
12
14
|
import { entryTimeOf, statusWordOf, taskIdOf } from '../../subagent-list.js';
|
|
13
15
|
import { useSubagentSupport } from '../../subagent-support.js';
|
|
@@ -24,6 +26,9 @@ const CHILDREN_MAX_PX = 320;
|
|
|
24
26
|
*/
|
|
25
27
|
function ChildRow({ chatId, item }) {
|
|
26
28
|
if (item.kind === 'tool') {
|
|
29
|
+
if (isImageGeneration(item)) {
|
|
30
|
+
return _jsx(GeneratedImageRow, { chatId: chatId, tool: item });
|
|
31
|
+
}
|
|
27
32
|
return item.state === 'running' ? _jsx(WorkLiveRow, { chatId: chatId, tool: item }) : _jsx(WorkRow, { tool: item });
|
|
28
33
|
}
|
|
29
34
|
if (item.kind === 'assistant') {
|
|
@@ -10,10 +10,13 @@ export declare function ToggleLine({ icon, label, detail, open, onToggle, failed
|
|
|
10
10
|
trailing?: React.ReactNode;
|
|
11
11
|
className?: string;
|
|
12
12
|
}): import("react").JSX.Element;
|
|
13
|
-
export declare function WorkRow({ tool, nested, detail }: {
|
|
13
|
+
export declare function WorkRow({ tool, nested, detail, icon, label, failed }: {
|
|
14
14
|
tool: ChatToolItem;
|
|
15
15
|
nested?: boolean;
|
|
16
16
|
detail?: string;
|
|
17
|
+
icon?: React.ReactNode;
|
|
18
|
+
label?: string;
|
|
19
|
+
failed?: boolean;
|
|
17
20
|
}): import("react").JSX.Element;
|
|
18
21
|
export declare function RunningFor({ startedAt }: {
|
|
19
22
|
startedAt: number;
|
|
@@ -37,13 +37,14 @@ function ToolBody({ tool }) {
|
|
|
37
37
|
return (_jsxs("div", { className: "mt-1.5 mb-2 ml-6 overflow-hidden rounded-md border border-border bg-surface-raised", children: [patches.length > 0 ? (_jsx(Suspense, { fallback: _jsx("div", { className: "px-3 py-2 text-xs text-text-faint", children: t('work.loadingDiff') }), children: patches.map((change, index) => (_jsx(UnifiedDiff, { change: change }, index))) })) : changes.length > 0 ? (_jsx(Suspense, { fallback: _jsx("div", { className: "px-3 py-2 text-xs text-text-faint", children: t('work.loadingDiff') }), children: changes.map((change, index) => (_jsx(EditDiff, { change: change }, index))) })) : (_jsx("pre", { className: "max-h-64 overflow-auto px-3 py-2 font-mono text-code text-text-muted select-text", children: clip(JSON.stringify(tool.input, null, 2) ?? '') })), tool.output !== null && tool.output !== '' && patches.length === 0 && (_jsx("pre", { "data-find-field": "output", className: clsx('max-h-64 overflow-auto whitespace-pre-wrap border-t border-border px-3 py-2 font-mono text-code select-text', tool.state === 'error' ? 'text-chat-removed' : 'text-chat-output'), children: _jsx(AnsiOutput, { text: tool.output, limit: OUTPUT_LIMIT }) }))] }));
|
|
38
38
|
}
|
|
39
39
|
/* One settled tool call: a line, and its input and output behind it. An image the call looked at
|
|
40
|
-
is drawn under the line, because a picture says more about that read than its path does.
|
|
41
|
-
|
|
40
|
+
is drawn under the line, because a picture says more about that read than its path does.
|
|
41
|
+
`icon`, `label` and `failed` let a row that knows the call better name it in its own words. */
|
|
42
|
+
export function WorkRow({ tool, nested, detail, icon, label, failed }) {
|
|
42
43
|
const [open, setOpen] = useState(false);
|
|
43
44
|
const image = tool.state === 'done' ? readImagePath(tool.name, tool.input) : null;
|
|
44
45
|
const { ReadImage } = chatHost();
|
|
45
46
|
useOpenForFind(tool.id, 'output', setOpen);
|
|
46
|
-
return (_jsxs("div", { "data-find-item": tool.id, className: nested ? 'ml-6' : undefined, children: [_jsx(ToggleLine, { icon: toolIcon(tool.name), label: tool.name, detail: detail ?? toolSummary(tool.name, tool.input), open: open, onToggle: () => setOpen((o) => !o), failed: tool.state === 'error' }), image !== null && ReadImage !== null && _jsx(ReadImage, { path: image }), open && _jsx(ToolBody, { tool: tool })] }));
|
|
47
|
+
return (_jsxs("div", { "data-find-item": tool.id, className: nested ? 'ml-6' : undefined, children: [_jsx(ToggleLine, { icon: icon ?? toolIcon(tool.name), label: label ?? tool.name, detail: detail ?? toolSummary(tool.name, tool.input), open: open, onToggle: () => setOpen((o) => !o), failed: failed ?? tool.state === 'error' }), image !== null && ReadImage !== null && _jsx(ReadImage, { path: image }), open && _jsx(ToolBody, { tool: tool })] }));
|
|
47
48
|
}
|
|
48
49
|
/* "running for 12s" next to a live call. */
|
|
49
50
|
export function RunningFor({ startedAt }) {
|
package/dist/host.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ComponentProps, ComponentType, ReactNode, RefObject } from 'react'
|
|
|
2
2
|
import type { Extension } from '@codemirror/state';
|
|
3
3
|
import type { EditorView } from '@codemirror/view';
|
|
4
4
|
import type { ThemeRegistration } from 'shiki';
|
|
5
|
-
import type { AgentKind, ChatCheckpointDiff, ChatConfigurePayload, ChatItem, ChatSubagentItem } from '@adecore/agent-contracts';
|
|
5
|
+
import type { AgentKind, ChatCheckpointDiff, ChatConfigurePayload, ChatItem, ChatSubagentItem, ChatUiChoicePayload, ChatUiQueryPayload, ChatUiQueryReading, ChatUiLinkPayload, ChatUiLinkReading } from '@adecore/agent-contracts';
|
|
6
6
|
import type { TimelineRow } from './chat/logic/timeline.js';
|
|
7
7
|
import type { FindReveal } from './chat/ui/find-reveal.js';
|
|
8
8
|
import type { PromptAction } from './prompts/logic/prompts.js';
|
|
@@ -126,6 +126,14 @@ export interface ReplyAuthor {
|
|
|
126
126
|
}
|
|
127
127
|
export interface ChatHost {
|
|
128
128
|
storage?: ChatStorageOptions;
|
|
129
|
+
intelligentUi?: {
|
|
130
|
+
subscribe?(scopeId: string, chatId: string, changed: () => void): () => void;
|
|
131
|
+
link?(scopeId: string, payload: ChatUiLinkPayload): Promise<ChatUiLinkReading>;
|
|
132
|
+
openLink?(scopeId: string, reading: ChatUiLinkReading): void;
|
|
133
|
+
openUrl?(scopeId: string, url: string): void;
|
|
134
|
+
query?(scopeId: string, payload: ChatUiQueryPayload): Promise<ChatUiQueryReading>;
|
|
135
|
+
sendChoice(scopeId: string, payload: ChatUiChoicePayload): Promise<'sent' | 'queued'>;
|
|
136
|
+
};
|
|
129
137
|
accents: {
|
|
130
138
|
all: readonly AccentChoice[];
|
|
131
139
|
featured: readonly string[];
|
|
@@ -140,6 +148,8 @@ export interface ChatHost {
|
|
|
140
148
|
attachments: {
|
|
141
149
|
useUrl(scopeId: string, chatId: string, attachmentId: string): ResourceUrl;
|
|
142
150
|
read(scopeId: string, chatId: string, attachmentId: string): Promise<Blob>;
|
|
151
|
+
open?(scopeId: string, chatId: string, attachmentId: string, suggestedName: string): void;
|
|
152
|
+
saveToProject?(scopeId: string, chatId: string, attachmentId: string, suggestedName: string): Promise<string | null>;
|
|
143
153
|
};
|
|
144
154
|
ReadImage: ComponentType<{
|
|
145
155
|
path: string;
|