@moldable-ai/ui 0.2.6 → 0.2.8
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/LICENSE +75 -71
- package/dist/components/chat/chat-input.d.ts +16 -3
- package/dist/components/chat/chat-input.d.ts.map +1 -1
- package/dist/components/chat/chat-input.js +25 -8
- package/dist/components/chat/chat-message.d.ts +11 -0
- package/dist/components/chat/chat-message.d.ts.map +1 -1
- package/dist/components/chat/chat-message.js +503 -158
- package/dist/components/chat/chat-panel.d.ts +26 -2
- package/dist/components/chat/chat-panel.d.ts.map +1 -1
- package/dist/components/chat/chat-panel.js +69 -33
- package/dist/components/chat/conversation-history.d.ts +6 -1
- package/dist/components/chat/conversation-history.d.ts.map +1 -1
- package/dist/components/chat/conversation-history.js +6 -6
- package/dist/components/chat/index.d.ts +2 -2
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +1 -1
- package/dist/components/chat/model-selector.d.ts +3 -1
- package/dist/components/chat/model-selector.d.ts.map +1 -1
- package/dist/components/chat/model-selector.js +2 -2
- package/dist/components/chat/reasoning-effort-selector.d.ts +4 -1
- package/dist/components/chat/reasoning-effort-selector.d.ts.map +1 -1
- package/dist/components/chat/reasoning-effort-selector.js +6 -6
- package/dist/components/chat/tool-handlers.d.ts.map +1 -1
- package/dist/components/chat/tool-handlers.js +62 -6
- package/dist/components/markdown.js +2 -2
- package/dist/components/ui/command.d.ts +2 -1
- package/dist/components/ui/command.d.ts.map +1 -1
- package/dist/components/ui/command.js +2 -2
- package/dist/components/ui/scroll-area.d.ts +1 -1
- package/dist/components/ui/scroll-area.d.ts.map +1 -1
- package/dist/components/ui/scroll-area.js +5 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/commands.d.ts +57 -0
- package/dist/lib/commands.d.ts.map +1 -1
- package/dist/lib/commands.js +74 -0
- package/package.json +2 -2
- package/src/styles/index.css +1 -1
|
@@ -1,58 +1,419 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ChevronDown, Image as ImageIcon } from 'lucide-react';
|
|
4
4
|
import { memo, useEffect, useMemo, useState } from 'react';
|
|
5
5
|
import { cn } from '../../lib/utils';
|
|
6
6
|
import { Markdown } from '../markdown';
|
|
7
7
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from '../ui/collapsible';
|
|
8
|
-
import { ThinkingTimeline, ThinkingTimelineMarker, } from './thinking-timeline';
|
|
9
8
|
import { ToolApproval, ToolApprovalHeader, ToolApprovalRejected, } from './tool-approval';
|
|
10
9
|
import { useToolApprovalResponse } from './tool-approval-context';
|
|
11
|
-
import { getToolHandler } from './tool-handlers';
|
|
10
|
+
import { getToolHandler, } from './tool-handlers';
|
|
12
11
|
import { useToolProgress } from './tool-progress-context';
|
|
13
12
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
14
13
|
const DEFAULT_ACTIONS_LABEL = 'Thinking';
|
|
15
14
|
// OpenRouter sends these for encrypted reasoning msgs
|
|
16
15
|
const stripRedacted = (text) => text.replace(/\[REDACTED\]/g, '').trim();
|
|
16
|
+
function emptyActivityCounts() {
|
|
17
|
+
return {
|
|
18
|
+
createdFileCount: 0,
|
|
19
|
+
editedFileCount: 0,
|
|
20
|
+
deletedFileCount: 0,
|
|
21
|
+
exploredFileCount: 0,
|
|
22
|
+
searchCount: 0,
|
|
23
|
+
listCount: 0,
|
|
24
|
+
commandCount: 0,
|
|
25
|
+
runningCommandCount: 0,
|
|
26
|
+
webSearchCount: 0,
|
|
27
|
+
apiCallCount: 0,
|
|
28
|
+
skillActionCount: 0,
|
|
29
|
+
appActionCount: 0,
|
|
30
|
+
toolCallCount: 0,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function incrementActivityCount(counts, key, amount = 1) {
|
|
34
|
+
counts[key] += amount;
|
|
35
|
+
}
|
|
36
|
+
function asRecord(value) {
|
|
37
|
+
return value && typeof value === 'object'
|
|
38
|
+
? value
|
|
39
|
+
: undefined;
|
|
40
|
+
}
|
|
41
|
+
function stringValue(value) {
|
|
42
|
+
return typeof value === 'string' && value.trim().length > 0
|
|
43
|
+
? value
|
|
44
|
+
: undefined;
|
|
45
|
+
}
|
|
46
|
+
function getString(record, keys) {
|
|
47
|
+
if (!record) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
for (const key of keys) {
|
|
51
|
+
const value = stringValue(record[key]);
|
|
52
|
+
if (value) {
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
function getCount(record, keys) {
|
|
59
|
+
if (!record) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
for (const key of keys) {
|
|
63
|
+
const value = record[key];
|
|
64
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
if (Array.isArray(value)) {
|
|
68
|
+
return value.length;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
function getFileName(path) {
|
|
74
|
+
const parts = path.split('/');
|
|
75
|
+
return parts[parts.length - 1] || path;
|
|
76
|
+
}
|
|
77
|
+
function getPathLabel(path) {
|
|
78
|
+
return path ? getFileName(path) : undefined;
|
|
79
|
+
}
|
|
80
|
+
function getToolPath(part) {
|
|
81
|
+
const args = asRecord(part.args);
|
|
82
|
+
const output = asRecord(part.output);
|
|
83
|
+
return (getString(output, ['path', 'filePath', 'targetPath']) ??
|
|
84
|
+
getString(args, ['path', 'filePath', 'targetPath']));
|
|
85
|
+
}
|
|
86
|
+
function getToolQuery(part) {
|
|
87
|
+
const args = asRecord(part.args);
|
|
88
|
+
const output = asRecord(part.output);
|
|
89
|
+
return (getString(args, ['query', 'pattern', 'glob', 'text']) ??
|
|
90
|
+
getString(output, ['query', 'pattern', 'glob']));
|
|
91
|
+
}
|
|
92
|
+
function getToolCommand(part, progress) {
|
|
93
|
+
const args = asRecord(part.args);
|
|
94
|
+
const output = asRecord(part.output);
|
|
95
|
+
return (progress?.command ??
|
|
96
|
+
getString(output, ['command']) ??
|
|
97
|
+
getString(args, ['command']));
|
|
98
|
+
}
|
|
99
|
+
function getToolTarget(part) {
|
|
100
|
+
const args = asRecord(part.args);
|
|
101
|
+
const output = asRecord(part.output);
|
|
102
|
+
return (getString(output, ['appName', 'appId', 'name', 'id']) ??
|
|
103
|
+
getString(args, ['appName', 'appId', 'name', 'id', 'repoName', 'skillName']));
|
|
104
|
+
}
|
|
105
|
+
function isToolLoading(part, progress) {
|
|
106
|
+
const isApprovalResponded = part.state === 'approval-responded' || part.state === 'output-denied';
|
|
107
|
+
const wasApproved = part.approval?.approved === true;
|
|
108
|
+
return (part.state === 'partial-call' ||
|
|
109
|
+
part.state === 'call' ||
|
|
110
|
+
part.state === 'pending' ||
|
|
111
|
+
progress?.status === 'running' ||
|
|
112
|
+
(isApprovalResponded && wasApproved && part.output === undefined));
|
|
113
|
+
}
|
|
114
|
+
function toolHasError(part) {
|
|
115
|
+
const output = asRecord(part.output);
|
|
116
|
+
if (!output) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
return (output.success === false ||
|
|
120
|
+
output.ok === false ||
|
|
121
|
+
(typeof output.exitCode === 'number' && output.exitCode !== 0) ||
|
|
122
|
+
Boolean(output.error));
|
|
123
|
+
}
|
|
124
|
+
function plural(count, singular, pluralForm = `${singular}s`) {
|
|
125
|
+
return `${count} ${count === 1 ? singular : pluralForm}`;
|
|
126
|
+
}
|
|
127
|
+
function sentenceCase(text) {
|
|
128
|
+
return text.length > 0 ? text.charAt(0).toUpperCase() + text.slice(1) : text;
|
|
129
|
+
}
|
|
130
|
+
function formatActivitySummary(counts, fallback, isStreaming) {
|
|
131
|
+
const segments = [];
|
|
132
|
+
const completedCommands = Math.max(0, counts.commandCount - counts.runningCommandCount);
|
|
133
|
+
const explorationDetails = [
|
|
134
|
+
counts.exploredFileCount > 0
|
|
135
|
+
? plural(counts.exploredFileCount, 'file')
|
|
136
|
+
: null,
|
|
137
|
+
counts.searchCount > 0
|
|
138
|
+
? plural(counts.searchCount, 'search', 'searches')
|
|
139
|
+
: null,
|
|
140
|
+
counts.listCount > 0
|
|
141
|
+
? plural(counts.listCount, 'directory', 'directories')
|
|
142
|
+
: null,
|
|
143
|
+
].filter(Boolean);
|
|
144
|
+
if (counts.createdFileCount > 0) {
|
|
145
|
+
segments.push(`created ${plural(counts.createdFileCount, 'file')}`);
|
|
146
|
+
}
|
|
147
|
+
if (counts.editedFileCount > 0) {
|
|
148
|
+
segments.push(`edited ${plural(counts.editedFileCount, 'file')}`);
|
|
149
|
+
}
|
|
150
|
+
if (counts.deletedFileCount > 0) {
|
|
151
|
+
segments.push(`deleted ${plural(counts.deletedFileCount, 'file')}`);
|
|
152
|
+
}
|
|
153
|
+
if (explorationDetails.length > 0) {
|
|
154
|
+
segments.push(`explored ${explorationDetails.join(', ')}`);
|
|
155
|
+
}
|
|
156
|
+
if (completedCommands > 0) {
|
|
157
|
+
segments.push(`ran ${plural(completedCommands, 'command')}`);
|
|
158
|
+
}
|
|
159
|
+
if (counts.runningCommandCount > 0) {
|
|
160
|
+
segments.push(`running ${plural(counts.runningCommandCount, 'command')}`);
|
|
161
|
+
}
|
|
162
|
+
if (counts.webSearchCount > 0) {
|
|
163
|
+
segments.push(`ran ${plural(counts.webSearchCount, 'web search', 'web searches')}`);
|
|
164
|
+
}
|
|
165
|
+
if (counts.apiCallCount > 0) {
|
|
166
|
+
segments.push(`called ${plural(counts.apiCallCount, 'app API', 'app APIs')}`);
|
|
167
|
+
}
|
|
168
|
+
if (counts.skillActionCount > 0) {
|
|
169
|
+
segments.push(`updated ${plural(counts.skillActionCount, 'skill setting')}`);
|
|
170
|
+
}
|
|
171
|
+
if (counts.appActionCount > 0) {
|
|
172
|
+
segments.push(`updated ${plural(counts.appActionCount, 'app')}`);
|
|
173
|
+
}
|
|
174
|
+
if (counts.toolCallCount > 0) {
|
|
175
|
+
segments.push(`used ${plural(counts.toolCallCount, 'tool')}`);
|
|
176
|
+
}
|
|
177
|
+
if (segments.length === 0) {
|
|
178
|
+
return isStreaming ? fallback : fallback || DEFAULT_ACTIONS_LABEL;
|
|
179
|
+
}
|
|
180
|
+
return sentenceCase(segments.join(', '));
|
|
181
|
+
}
|
|
182
|
+
function ActivityCode({ children, title, }) {
|
|
183
|
+
return (_jsx("code", { className: "text-foreground/80 inline max-w-full truncate align-baseline font-mono text-[0.95em]", title: title, children: children }));
|
|
184
|
+
}
|
|
185
|
+
function toolActivityText({ verb, path, detail, fallback, }) {
|
|
186
|
+
const label = path ?? detail;
|
|
187
|
+
return (_jsxs(_Fragment, { children: [_jsx("span", { children: verb }), label ? (_jsxs(_Fragment, { children: [' ', _jsx(ActivityCode, { title: path, children: label })] })) : fallback ? (_jsxs(_Fragment, { children: [" ", fallback] })) : null] }));
|
|
188
|
+
}
|
|
189
|
+
function summarizeToolActivity(part, toolHandler, progress, fallbackId) {
|
|
190
|
+
const counts = emptyActivityCounts();
|
|
191
|
+
const running = isToolLoading(part, progress);
|
|
192
|
+
const hasError = toolHasError(part);
|
|
193
|
+
const path = getToolPath(part);
|
|
194
|
+
const pathLabel = getPathLabel(path);
|
|
195
|
+
const query = getToolQuery(part);
|
|
196
|
+
const command = getToolCommand(part, progress);
|
|
197
|
+
const target = getToolTarget(part);
|
|
198
|
+
const output = asRecord(part.output);
|
|
199
|
+
const args = asRecord(part.args);
|
|
200
|
+
const entryId = part.toolCallId ?? fallbackId ?? `${part.toolName}-${part.state}`;
|
|
201
|
+
const loadingLabel = toolHandler.loadingLabel.replace(/\.\.\.$/, '');
|
|
202
|
+
let content = running ? loadingLabel : `Used ${part.toolName}`;
|
|
203
|
+
switch (part.toolName) {
|
|
204
|
+
case 'readFile':
|
|
205
|
+
case 'fileExists':
|
|
206
|
+
incrementActivityCount(counts, 'exploredFileCount');
|
|
207
|
+
content = toolActivityText({
|
|
208
|
+
verb: running
|
|
209
|
+
? 'Reading'
|
|
210
|
+
: part.toolName === 'fileExists'
|
|
211
|
+
? 'Checked'
|
|
212
|
+
: 'Read',
|
|
213
|
+
path: pathLabel,
|
|
214
|
+
fallback: 'file',
|
|
215
|
+
});
|
|
216
|
+
break;
|
|
217
|
+
case 'readToolOutput':
|
|
218
|
+
incrementActivityCount(counts, 'exploredFileCount');
|
|
219
|
+
content = running ? 'Reading tool output' : 'Read tool output';
|
|
220
|
+
break;
|
|
221
|
+
case 'listDirectory': {
|
|
222
|
+
incrementActivityCount(counts, 'listCount');
|
|
223
|
+
const itemCount = getCount(output, ['entries', 'items']);
|
|
224
|
+
content = (_jsxs(_Fragment, { children: [_jsx("span", { children: running ? 'Listing' : 'Listed' }), ' ', _jsx(ActivityCode, { title: path, children: pathLabel ?? 'directory' }), typeof itemCount === 'number' && (_jsxs("span", { children: [" (", plural(itemCount, 'item'), ")"] }))] }));
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
case 'grep':
|
|
228
|
+
case 'globFileSearch': {
|
|
229
|
+
incrementActivityCount(counts, 'searchCount');
|
|
230
|
+
const resultCount = getCount(output, ['matches', 'files', 'results']);
|
|
231
|
+
content = (_jsxs(_Fragment, { children: [_jsx("span", { children: running ? 'Searching' : 'Searched' }), query && (_jsxs(_Fragment, { children: [' ', "for ", _jsx(ActivityCode, { title: query, children: query })] })), typeof resultCount === 'number' && (_jsxs("span", { children: [" (", plural(resultCount, 'result'), ")"] }))] }));
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
case 'webSearch':
|
|
235
|
+
incrementActivityCount(counts, 'webSearchCount');
|
|
236
|
+
content = (_jsxs(_Fragment, { children: [_jsx("span", { children: running ? 'Searching web' : 'Searched web' }), query && (_jsxs(_Fragment, { children: [' ', "for ", _jsx(ActivityCode, { title: query, children: query })] }))] }));
|
|
237
|
+
break;
|
|
238
|
+
case 'executeBashCommand':
|
|
239
|
+
case 'runCommand':
|
|
240
|
+
incrementActivityCount(counts, 'commandCount');
|
|
241
|
+
if (running) {
|
|
242
|
+
incrementActivityCount(counts, 'runningCommandCount');
|
|
243
|
+
}
|
|
244
|
+
content = (_jsxs(_Fragment, { children: [_jsx("span", { children: running ? 'Running' : 'Ran' }), ' ', _jsx(ActivityCode, { title: command, children: command ?? 'command' })] }));
|
|
245
|
+
break;
|
|
246
|
+
case 'writeFile': {
|
|
247
|
+
const created = output?.created === true || args?.create === true;
|
|
248
|
+
incrementActivityCount(counts, created ? 'createdFileCount' : 'editedFileCount');
|
|
249
|
+
content = toolActivityText({
|
|
250
|
+
verb: running ? 'Writing' : created ? 'Created' : 'Wrote',
|
|
251
|
+
path: pathLabel,
|
|
252
|
+
fallback: 'file',
|
|
253
|
+
});
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
case 'editFile':
|
|
257
|
+
incrementActivityCount(counts, 'editedFileCount');
|
|
258
|
+
content = toolActivityText({
|
|
259
|
+
verb: running ? 'Editing' : 'Edited',
|
|
260
|
+
path: pathLabel,
|
|
261
|
+
fallback: 'file',
|
|
262
|
+
});
|
|
263
|
+
break;
|
|
264
|
+
case 'deleteFile':
|
|
265
|
+
incrementActivityCount(counts, 'deletedFileCount');
|
|
266
|
+
content = toolActivityText({
|
|
267
|
+
verb: running ? 'Deleting' : 'Deleted',
|
|
268
|
+
path: pathLabel,
|
|
269
|
+
fallback: 'file',
|
|
270
|
+
});
|
|
271
|
+
break;
|
|
272
|
+
case 'listMoldableAppApi':
|
|
273
|
+
case 'callMoldableAppApi':
|
|
274
|
+
incrementActivityCount(counts, 'apiCallCount');
|
|
275
|
+
content = (_jsxs(_Fragment, { children: [_jsx("span", { children: running
|
|
276
|
+
? part.toolName === 'listMoldableAppApi'
|
|
277
|
+
? 'Listing app APIs'
|
|
278
|
+
: 'Calling app API'
|
|
279
|
+
: part.toolName === 'listMoldableAppApi'
|
|
280
|
+
? 'Listed app APIs'
|
|
281
|
+
: 'Called app API' }), target && (_jsxs(_Fragment, { children: [' ', _jsx(ActivityCode, { title: target, children: target })] }))] }));
|
|
282
|
+
break;
|
|
283
|
+
case 'listSkillRepos':
|
|
284
|
+
case 'listAvailableSkills':
|
|
285
|
+
case 'syncSkills':
|
|
286
|
+
case 'addSkillRepo':
|
|
287
|
+
case 'updateSkillSelection':
|
|
288
|
+
case 'initSkillsConfig':
|
|
289
|
+
incrementActivityCount(counts, 'skillActionCount');
|
|
290
|
+
content = (_jsxs(_Fragment, { children: [_jsx("span", { children: running ? loadingLabel : 'Updated skills' }), target && (_jsxs(_Fragment, { children: [' ', _jsx(ActivityCode, { title: target, children: target })] }))] }));
|
|
291
|
+
break;
|
|
292
|
+
case 'scaffoldApp':
|
|
293
|
+
case 'getAppInfo':
|
|
294
|
+
case 'unregisterApp':
|
|
295
|
+
case 'deleteAppData':
|
|
296
|
+
case 'deleteApp':
|
|
297
|
+
incrementActivityCount(counts, 'appActionCount');
|
|
298
|
+
content = (_jsxs(_Fragment, { children: [_jsx("span", { children: running ? loadingLabel : 'Updated app' }), target && (_jsxs(_Fragment, { children: [' ', _jsx(ActivityCode, { title: target, children: target })] }))] }));
|
|
299
|
+
break;
|
|
300
|
+
default:
|
|
301
|
+
incrementActivityCount(counts, 'toolCallCount');
|
|
302
|
+
content = (_jsxs(_Fragment, { children: [_jsx("span", { children: running ? loadingLabel : 'Used tool' }), ' ', _jsx(ActivityCode, { children: part.toolName })] }));
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
if (hasError) {
|
|
306
|
+
content = (_jsxs(_Fragment, { children: [content, _jsx("span", { children: " failed" })] }));
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
entry: {
|
|
310
|
+
id: entryId,
|
|
311
|
+
content,
|
|
312
|
+
isRunning: running,
|
|
313
|
+
isError: hasError,
|
|
314
|
+
},
|
|
315
|
+
counts,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function formatWorkDuration(ms) {
|
|
319
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1000));
|
|
320
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
321
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
322
|
+
const seconds = totalSeconds % 60;
|
|
323
|
+
if (hours > 0) {
|
|
324
|
+
return `${hours}h ${minutes}m`;
|
|
325
|
+
}
|
|
326
|
+
if (minutes > 0) {
|
|
327
|
+
return `${minutes}m ${seconds}s`;
|
|
328
|
+
}
|
|
329
|
+
return `${seconds}s`;
|
|
330
|
+
}
|
|
331
|
+
function useNow(enabled) {
|
|
332
|
+
const [now, setNow] = useState(() => Date.now());
|
|
333
|
+
useEffect(() => {
|
|
334
|
+
if (!enabled) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const interval = window.setInterval(() => {
|
|
338
|
+
setNow(Date.now());
|
|
339
|
+
}, 1000);
|
|
340
|
+
return () => window.clearInterval(interval);
|
|
341
|
+
}, [enabled]);
|
|
342
|
+
return now;
|
|
343
|
+
}
|
|
344
|
+
function WorkSummary({ messageId, startedAtMs, completedAtMs, isWorking, children, }) {
|
|
345
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
346
|
+
const now = useNow(isWorking && Boolean(startedAtMs));
|
|
347
|
+
const endAtMs = completedAtMs ?? (isWorking ? now : undefined);
|
|
348
|
+
const elapsedMs = typeof startedAtMs === 'number' && typeof endAtMs === 'number'
|
|
349
|
+
? Math.max(0, endAtMs - startedAtMs)
|
|
350
|
+
: null;
|
|
351
|
+
const durationLabel = elapsedMs !== null && (elapsedMs >= 1000 || !isWorking)
|
|
352
|
+
? formatWorkDuration(elapsedMs)
|
|
353
|
+
: null;
|
|
354
|
+
const summaryLabel = isWorking
|
|
355
|
+
? durationLabel
|
|
356
|
+
? `Working for ${durationLabel}`
|
|
357
|
+
: 'Working'
|
|
358
|
+
: durationLabel
|
|
359
|
+
? `Worked for ${durationLabel}`
|
|
360
|
+
: 'Worked';
|
|
361
|
+
return (_jsxs(Collapsible, { open: isOpen, onOpenChange: setIsOpen, className: "w-full", "data-work-summary": messageId, children: [_jsxs("div", { className: "text-muted-foreground/75 text-[13px] leading-5", children: [_jsxs(CollapsibleTrigger, { className: cn('group inline-flex max-w-full cursor-pointer items-center gap-1 rounded-md border border-transparent text-left transition-colors', 'hover:bg-muted/30 hover:text-foreground/80 focus-visible:ring-ring focus-visible:outline-none focus-visible:ring-2'), "aria-label": `${summaryLabel} activity`, children: [_jsx("span", { className: "truncate", children: summaryLabel }), _jsx(ChevronDown, { className: cn('size-3 shrink-0 opacity-40 transition-transform duration-200 group-hover:opacity-70', isOpen ? 'rotate-0' : '-rotate-90') })] }), _jsx("div", { className: "border-border/60 mt-1 w-full border-t" })] }), _jsx(CollapsibleContent, { className: "overflow-hidden", children: _jsx("div", { className: "pt-4", children: children }) })] }));
|
|
362
|
+
}
|
|
17
363
|
function PureMessage({ message, isLast = false, isStreaming = false, }) {
|
|
18
364
|
// Get tool progress for streaming stdout/stderr
|
|
19
365
|
const toolProgress = useToolProgress();
|
|
20
366
|
// Get approval response handler
|
|
21
367
|
const onApprovalResponse = useToolApprovalResponse();
|
|
22
|
-
// Track open state for each
|
|
23
|
-
const [
|
|
24
|
-
// Process parts chronologically, grouping
|
|
25
|
-
//
|
|
368
|
+
// Track open state for each background activity group independently.
|
|
369
|
+
const [activityGroupStates, setActivityGroupStates] = useState(new Map());
|
|
370
|
+
// Process parts chronologically, grouping ordinary tool work and reasoning as
|
|
371
|
+
// low-emphasis background activity. Approval prompts remain prominent because
|
|
372
|
+
// they need direct user action.
|
|
26
373
|
const { contentItems, hasFinalAssistantText } = useMemo(() => {
|
|
27
374
|
const items = [];
|
|
28
375
|
const state = {
|
|
29
|
-
|
|
30
|
-
|
|
376
|
+
currentActivityGroup: null,
|
|
377
|
+
activityGroupIndex: 0,
|
|
31
378
|
hasText: false,
|
|
32
379
|
};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
380
|
+
const updateActivityTitle = (group) => {
|
|
381
|
+
group.title = formatActivitySummary(group.counts, group.title, group.isStreaming);
|
|
382
|
+
};
|
|
383
|
+
const addActivity = (group, entry, counts) => {
|
|
384
|
+
group.entries.push(entry);
|
|
385
|
+
group.isStreaming = group.isStreaming || Boolean(entry.isRunning);
|
|
386
|
+
if (counts) {
|
|
387
|
+
for (const key of Object.keys(counts)) {
|
|
388
|
+
group.counts[key] += counts[key];
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
updateActivityTitle(group);
|
|
392
|
+
};
|
|
393
|
+
// Helper to close current activity group
|
|
394
|
+
const closeActivityGroup = () => {
|
|
395
|
+
if (state.currentActivityGroup) {
|
|
36
396
|
items.push({
|
|
37
|
-
type: '
|
|
38
|
-
id: state.
|
|
39
|
-
|
|
397
|
+
type: 'activity',
|
|
398
|
+
id: state.currentActivityGroup.id,
|
|
399
|
+
activityGroup: state.currentActivityGroup,
|
|
40
400
|
});
|
|
41
|
-
state.
|
|
401
|
+
state.currentActivityGroup = null;
|
|
42
402
|
}
|
|
43
403
|
};
|
|
44
|
-
// Helper to ensure
|
|
45
|
-
const
|
|
46
|
-
if (!state.
|
|
47
|
-
state.
|
|
48
|
-
state.
|
|
49
|
-
id: `
|
|
50
|
-
|
|
404
|
+
// Helper to ensure activity group exists
|
|
405
|
+
const ensureActivityGroup = () => {
|
|
406
|
+
if (!state.currentActivityGroup) {
|
|
407
|
+
state.activityGroupIndex++;
|
|
408
|
+
state.currentActivityGroup = {
|
|
409
|
+
id: `activity-${message.id}-${state.activityGroupIndex}`,
|
|
410
|
+
entries: [],
|
|
411
|
+
counts: emptyActivityCounts(),
|
|
51
412
|
title: DEFAULT_ACTIONS_LABEL,
|
|
52
413
|
isStreaming: false,
|
|
53
414
|
};
|
|
54
415
|
}
|
|
55
|
-
return state.
|
|
416
|
+
return state.currentActivityGroup;
|
|
56
417
|
};
|
|
57
418
|
// If no parts, use content as text
|
|
58
419
|
const parts = message.parts ?? [];
|
|
@@ -74,85 +435,40 @@ function PureMessage({ message, isLast = false, isStreaming = false, }) {
|
|
|
74
435
|
// Handle tool calls
|
|
75
436
|
if (part.type === 'tool-invocation') {
|
|
76
437
|
const toolHandler = getToolHandler(part.toolName);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const wasRejected = part.approval?.approved === false;
|
|
86
|
-
const wasApproved = part.approval?.approved === true;
|
|
87
|
-
// Render inline tool
|
|
88
|
-
// Also treat approval-responded (when approved) as loading until output arrives
|
|
89
|
-
const isToolLoading = part.state === 'partial-call' ||
|
|
90
|
-
part.state === 'call' ||
|
|
91
|
-
part.state === 'pending' ||
|
|
92
|
-
(isApprovalResponded && wasApproved && part.output === undefined);
|
|
93
|
-
// Check for streaming progress (stdout/stderr from command execution)
|
|
94
|
-
const progress = part.toolCallId
|
|
95
|
-
? toolProgress[part.toolCallId]
|
|
96
|
-
: undefined;
|
|
97
|
-
const hasStreamingOutput = progress && (progress.stdout || progress.stderr);
|
|
98
|
-
let toolContent;
|
|
99
|
-
// Handle approval request state
|
|
100
|
-
if (isApprovalRequested &&
|
|
101
|
-
toolHandler.renderApproval &&
|
|
102
|
-
part.approval?.id &&
|
|
103
|
-
onApprovalResponse) {
|
|
104
|
-
toolContent = toolHandler.renderApproval({
|
|
105
|
-
approvalId: part.approval.id,
|
|
106
|
-
toolCallId: part.toolCallId || key,
|
|
107
|
-
args: part.args,
|
|
108
|
-
}, onApprovalResponse);
|
|
109
|
-
}
|
|
110
|
-
else if (isApprovalResponded && wasRejected) {
|
|
111
|
-
// Show rejection message
|
|
112
|
-
toolContent = (_jsx(ToolApproval, { state: "output-denied", approved: false, children: _jsx(ToolApprovalHeader, { children: _jsx(ToolApprovalRejected, { children: "Command execution was rejected" }) }) }));
|
|
113
|
-
}
|
|
114
|
-
else if (hasStreamingOutput && toolHandler.renderStreaming) {
|
|
115
|
-
// Show streaming output (live stdout/stderr)
|
|
116
|
-
toolContent = toolHandler.renderStreaming(part.args, progress);
|
|
117
|
-
}
|
|
118
|
-
else if (isToolLoading) {
|
|
119
|
-
// Show loading state (arguments streaming or waiting)
|
|
120
|
-
toolContent = toolHandler.renderLoading?.(part.args) || (_jsx("div", { className: "text-muted-foreground text-xs", children: toolHandler.loadingLabel }));
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
// Show final output
|
|
124
|
-
toolContent = toolHandler.renderOutput(part.output, part.toolCallId || key);
|
|
125
|
-
}
|
|
438
|
+
const isApprovalRequested = part.state === 'approval-requested';
|
|
439
|
+
const isApprovalResponded = part.state === 'approval-responded' || part.state === 'output-denied';
|
|
440
|
+
const wasRejected = part.approval?.approved === false;
|
|
441
|
+
if (isApprovalRequested &&
|
|
442
|
+
toolHandler.renderApproval &&
|
|
443
|
+
part.approval?.id &&
|
|
444
|
+
onApprovalResponse) {
|
|
445
|
+
closeActivityGroup();
|
|
126
446
|
items.push({
|
|
127
447
|
type: 'inline-tool',
|
|
128
448
|
id: key,
|
|
129
|
-
content:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const group = ensureThinkingGroup();
|
|
135
|
-
// Show loading state for tools
|
|
136
|
-
if (part.state === 'partial-call' ||
|
|
137
|
-
part.state === 'call' ||
|
|
138
|
-
part.state === 'pending') {
|
|
139
|
-
const toolLabel = toolHandler.loadingLabel;
|
|
140
|
-
group.title = toolLabel;
|
|
141
|
-
group.isStreaming = true;
|
|
142
|
-
group.timeline.push({
|
|
143
|
-
marker: ThinkingTimelineMarker.Loading,
|
|
144
|
-
content: (_jsx("div", { className: "px-2 py-1", children: _jsx("span", { className: "text-muted-foreground font-mono text-xs", children: toolLabel }) }, `${part.toolCallId || key}-loading`)),
|
|
449
|
+
content: toolHandler.renderApproval({
|
|
450
|
+
approvalId: part.approval.id,
|
|
451
|
+
toolCallId: part.toolCallId || key,
|
|
452
|
+
args: part.args,
|
|
453
|
+
}, onApprovalResponse),
|
|
145
454
|
});
|
|
146
455
|
continue;
|
|
147
456
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
457
|
+
if (isApprovalResponded && wasRejected) {
|
|
458
|
+
closeActivityGroup();
|
|
459
|
+
items.push({
|
|
460
|
+
type: 'inline-tool',
|
|
461
|
+
id: key,
|
|
462
|
+
content: (_jsx(ToolApproval, { state: "output-denied", approved: false, children: _jsx(ToolApprovalHeader, { children: _jsx(ToolApprovalRejected, { children: "Command execution was rejected" }) }) })),
|
|
153
463
|
});
|
|
154
464
|
continue;
|
|
155
465
|
}
|
|
466
|
+
const group = ensureActivityGroup();
|
|
467
|
+
const progress = part.toolCallId
|
|
468
|
+
? toolProgress[part.toolCallId]
|
|
469
|
+
: undefined;
|
|
470
|
+
const activity = summarizeToolActivity(part, toolHandler, progress, key);
|
|
471
|
+
addActivity(group, activity.entry, activity.counts);
|
|
156
472
|
continue;
|
|
157
473
|
}
|
|
158
474
|
if (part.type === 'reasoning') {
|
|
@@ -161,7 +477,7 @@ function PureMessage({ message, isLast = false, isStreaming = false, }) {
|
|
|
161
477
|
if (cleaned.length === 0) {
|
|
162
478
|
continue;
|
|
163
479
|
}
|
|
164
|
-
const group =
|
|
480
|
+
const group = ensureActivityGroup();
|
|
165
481
|
// Extract title from markdown **title** or use first line
|
|
166
482
|
const markdownTitleMatch = cleaned.match(/\*\*([^*]+)\*\*/);
|
|
167
483
|
const candidateTitle = markdownTitleMatch?.[1];
|
|
@@ -172,25 +488,34 @@ function PureMessage({ message, isLast = false, isStreaming = false, }) {
|
|
|
172
488
|
const body = candidateTitle
|
|
173
489
|
? trimmed.replace(/\*\*[^*]+\*\*\s*/, '')
|
|
174
490
|
: trimmed;
|
|
175
|
-
group
|
|
176
|
-
|
|
177
|
-
content:
|
|
491
|
+
addActivity(group, {
|
|
492
|
+
id: key,
|
|
493
|
+
content: _jsx("span", { className: "whitespace-pre-wrap", children: body }),
|
|
178
494
|
});
|
|
179
495
|
continue;
|
|
180
496
|
}
|
|
181
497
|
if (part.type === 'text') {
|
|
182
|
-
// Close current
|
|
183
|
-
|
|
498
|
+
// Close current background activity group before text.
|
|
499
|
+
closeActivityGroup();
|
|
184
500
|
state.hasText = true;
|
|
185
501
|
items.push({
|
|
186
502
|
type: 'text',
|
|
187
503
|
id: key,
|
|
188
504
|
textPart: part,
|
|
189
505
|
});
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
if (part.type === 'file') {
|
|
509
|
+
closeActivityGroup();
|
|
510
|
+
items.push({
|
|
511
|
+
type: 'file',
|
|
512
|
+
id: key,
|
|
513
|
+
filePart: part,
|
|
514
|
+
});
|
|
190
515
|
}
|
|
191
516
|
}
|
|
192
|
-
// Close any remaining
|
|
193
|
-
|
|
517
|
+
// Close any remaining activity group
|
|
518
|
+
closeActivityGroup();
|
|
194
519
|
return {
|
|
195
520
|
contentItems: items,
|
|
196
521
|
hasFinalAssistantText: state.hasText,
|
|
@@ -202,27 +527,27 @@ function PureMessage({ message, isLast = false, isStreaming = false, }) {
|
|
|
202
527
|
toolProgress,
|
|
203
528
|
onApprovalResponse,
|
|
204
529
|
]);
|
|
205
|
-
// Determine if last
|
|
206
|
-
const
|
|
207
|
-
.filter((item) => item.type === '
|
|
208
|
-
.map((item) => (item.type === '
|
|
530
|
+
// Determine if last activity group is streaming
|
|
531
|
+
const lastActivityGroup = contentItems
|
|
532
|
+
.filter((item) => item.type === 'activity')
|
|
533
|
+
.map((item) => (item.type === 'activity' ? item.activityGroup : null))
|
|
209
534
|
.filter((g) => g !== null)
|
|
210
535
|
.pop();
|
|
211
536
|
// Check if we're still streaming overall and waiting for text
|
|
212
537
|
const isStreamingOverall = isStreaming && isLast && !hasFinalAssistantText;
|
|
213
|
-
// For the last
|
|
538
|
+
// For the last activity group, also keep it open if we just finished streaming
|
|
214
539
|
// but text hasn't appeared yet (grace period for AI to start responding)
|
|
215
540
|
const shouldKeepLastGroupOpen = isLast && !hasFinalAssistantText && message.role === 'assistant';
|
|
216
|
-
// Auto-open/close
|
|
541
|
+
// Auto-open/close activity groups based on streaming state
|
|
217
542
|
useEffect(() => {
|
|
218
|
-
|
|
543
|
+
setActivityGroupStates((prevStates) => {
|
|
219
544
|
let hasChanges = false;
|
|
220
545
|
const nextStates = new Map(prevStates);
|
|
221
546
|
contentItems.forEach((item) => {
|
|
222
|
-
if (item.type === '
|
|
223
|
-
const groupId = item.
|
|
224
|
-
const group = item.
|
|
225
|
-
const isLastGroup =
|
|
547
|
+
if (item.type === 'activity' && item.activityGroup) {
|
|
548
|
+
const groupId = item.activityGroup.id;
|
|
549
|
+
const group = item.activityGroup;
|
|
550
|
+
const isLastGroup = lastActivityGroup?.id === groupId;
|
|
226
551
|
const currentState = nextStates.get(groupId);
|
|
227
552
|
const shouldBeOpen = group.isStreaming ||
|
|
228
553
|
(isLastGroup && isStreamingOverall) ||
|
|
@@ -253,53 +578,73 @@ function PureMessage({ message, isLast = false, isStreaming = false, }) {
|
|
|
253
578
|
}, [
|
|
254
579
|
contentItems,
|
|
255
580
|
isStreamingOverall,
|
|
256
|
-
|
|
581
|
+
lastActivityGroup,
|
|
257
582
|
shouldKeepLastGroupOpen,
|
|
258
583
|
]);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
584
|
+
const renderContentItem = (item) => {
|
|
585
|
+
if (item.type === 'activity' &&
|
|
586
|
+
item.activityGroup &&
|
|
587
|
+
message.role === 'assistant') {
|
|
588
|
+
const group = item.activityGroup;
|
|
589
|
+
const groupState = activityGroupStates.get(group.id);
|
|
590
|
+
const isLastGroup = lastActivityGroup?.id === group.id;
|
|
591
|
+
const isGroupStreaming = group.isStreaming || (isLastGroup && isStreamingOverall);
|
|
592
|
+
const shouldBeOpenByDefault = isGroupStreaming;
|
|
593
|
+
const isOpen = groupState?.isOpen ?? shouldBeOpenByDefault;
|
|
594
|
+
const titleForTrigger = group.title || DEFAULT_ACTIONS_LABEL;
|
|
595
|
+
return (_jsxs(Collapsible, { open: isOpen, onOpenChange: (open) => {
|
|
596
|
+
setActivityGroupStates((prev) => {
|
|
597
|
+
const next = new Map(prev);
|
|
598
|
+
const current = next.get(group.id) ?? {
|
|
599
|
+
isOpen: false,
|
|
600
|
+
userOpened: false,
|
|
601
|
+
};
|
|
602
|
+
next.set(group.id, {
|
|
603
|
+
...current,
|
|
604
|
+
isOpen: open,
|
|
605
|
+
userOpened: open ? true : current.userOpened,
|
|
606
|
+
});
|
|
607
|
+
return next;
|
|
608
|
+
});
|
|
609
|
+
}, className: "mb-1 w-full", "data-ai-reasoning": true, children: [_jsxs(CollapsibleTrigger, { className: cn('group flex max-w-full cursor-pointer items-center gap-1 py-0.5 text-[13px] font-normal leading-5 transition-colors', 'text-muted-foreground/75 hover:text-foreground/80 min-w-0'), children: [_jsx("span", { className: "flex-1 truncate", children: titleForTrigger }), _jsx(ChevronDown, { className: cn('size-3 shrink-0 opacity-0 transition-all duration-200 group-hover:opacity-70', isOpen && 'rotate-180 opacity-50') })] }), _jsx(CollapsibleContent, { className: "text-muted-foreground/75 min-w-0 overflow-hidden pb-1.5 pl-3 pt-0 text-[13px] leading-5", children: _jsx("div", { className: "max-h-28 space-y-0.5 overflow-y-auto pr-2", children: group.entries.map((entry) => (_jsx("div", { className: cn('min-w-0 overflow-hidden truncate', entry.isError && 'text-destructive'), children: entry.content }, entry.id))) }) })] }, item.id));
|
|
610
|
+
}
|
|
611
|
+
if (item.type === 'inline-tool') {
|
|
612
|
+
return (_jsx("div", { className: "w-full min-w-0", children: item.content }, item.id));
|
|
613
|
+
}
|
|
614
|
+
if (item.type === 'file' && item.filePart) {
|
|
615
|
+
const isImage = item.filePart.mediaType.startsWith('image/');
|
|
616
|
+
const label = item.filePart.filename || 'Attached image';
|
|
617
|
+
return (_jsxs("div", { className: cn('bg-secondary text-secondary-foreground flex w-full items-center gap-3 rounded-2xl px-3 py-2.5', message.role !== 'user' && 'bg-muted text-foreground'), children: [_jsx("div", { className: "bg-background flex size-12 shrink-0 items-center justify-center overflow-hidden rounded-lg border", children: isImage && item.filePart.previewUrl ? (_jsx("img", { src: item.filePart.previewUrl, alt: "", className: "size-full object-cover", draggable: false })) : (_jsx(ImageIcon, { className: "text-muted-foreground size-5" })) }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate text-sm font-medium", children: label }), _jsx("p", { className: "text-muted-foreground truncate text-xs", children: item.filePart.mediaType })] })] }, item.id));
|
|
618
|
+
}
|
|
619
|
+
if (item.type === 'text' && item.textPart) {
|
|
620
|
+
if (message.role === 'user') {
|
|
621
|
+
return (_jsx("div", { className: "flex w-full justify-end", children: _jsx("div", { className: "bg-foreground/5 text-foreground max-w-[77%] break-words rounded-2xl px-3 py-2 text-[13px] leading-5", children: _jsx("div", { className: "whitespace-pre-wrap break-words", children: item.textPart.text }) }) }, item.id));
|
|
622
|
+
}
|
|
623
|
+
return (_jsx("div", { className: cn('flex min-w-0 flex-col gap-4', {
|
|
624
|
+
'w-full': message.role === 'assistant',
|
|
625
|
+
}), children: _jsx(Markdown, { markdown: item.textPart.text, proseSize: "sm", className: cn('[&_.prose]:text-[13px] [&_.prose]:leading-[21px] [&_.prose]:text-current', '[&_.prose_pre]:bg-background/80 [&_.prose_code]:text-current', '[&_.prose_p:last-child]:mb-0 [&_.prose_p]:mb-2', '[&_.prose_ol:first-child]:mt-0 [&_.prose_ul:first-child]:mt-0', '[&_.prose>*:last-child]:mb-0') }) }, item.id));
|
|
626
|
+
}
|
|
627
|
+
return null;
|
|
628
|
+
};
|
|
629
|
+
const finalTextItemIndex = message.role === 'assistant'
|
|
630
|
+
? contentItems.reduce((lastIndex, item, index) => item.type === 'text' ? index : lastIndex, -1)
|
|
631
|
+
: -1;
|
|
632
|
+
const shouldCollapsePreAnswerWork = message.role === 'assistant' &&
|
|
633
|
+
hasFinalAssistantText &&
|
|
634
|
+
finalTextItemIndex > 0;
|
|
635
|
+
const preAnswerWorkItems = shouldCollapsePreAnswerWork
|
|
636
|
+
? contentItems.slice(0, finalTextItemIndex)
|
|
637
|
+
: [];
|
|
638
|
+
const finalAnswerItems = shouldCollapsePreAnswerWork
|
|
639
|
+
? contentItems.slice(finalTextItemIndex)
|
|
640
|
+
: contentItems;
|
|
641
|
+
const isTurnStillWorking = isLast &&
|
|
642
|
+
isStreaming &&
|
|
643
|
+
message.role === 'assistant' &&
|
|
644
|
+
!message.turn?.completedAtMs;
|
|
645
|
+
return (_jsx(AnimatePresence, { children: _jsx(motion.div, { "data-testid": `message-${message.role}`, className: "group/message w-full min-w-0 px-2", initial: { y: 5, opacity: 0 }, animate: { y: 0, opacity: 1 }, "data-role": message.role, children: _jsx("div", { className: "flex w-full min-w-0 items-start gap-4", children: _jsxs("div", { className: "mt-1 flex w-full min-w-0 flex-col gap-2", children: [shouldCollapsePreAnswerWork && (_jsx(WorkSummary, { messageId: message.id, startedAtMs: message.turn?.startedAtMs, completedAtMs: message.turn?.completedAtMs, isWorking: isTurnStillWorking, children: _jsx("div", { className: "flex min-w-0 flex-col gap-2", children: preAnswerWorkItems.map(renderContentItem) }) })), finalAnswerItems.map(renderContentItem)] }) }) }) }));
|
|
301
646
|
}
|
|
302
647
|
export const Message = memo(PureMessage);
|
|
303
648
|
export function ThinkingMessage() {
|
|
304
|
-
return (_jsx(motion.div, { "data-testid": "message-assistant-loading", className: "group/message w-full px-2", initial: { y: 5, opacity: 0 }, animate: { y: 0, opacity: 1, transition: { delay: 0.5 } }, "data-role": "assistant", children:
|
|
649
|
+
return (_jsx(motion.div, { "data-testid": "message-assistant-loading", className: "group/message w-full px-2", initial: { y: 5, opacity: 0 }, animate: { y: 0, opacity: 1, transition: { delay: 0.5 } }, "data-role": "assistant", children: _jsx("div", { className: "text-muted-foreground/75 px-2 text-[13px] leading-5", children: _jsx("span", { className: "animate-pulse select-none", children: "Thinking" }) }) }));
|
|
305
650
|
}
|