@alpaca-editor/core 1.0.4133 → 1.0.4134
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/editor/ai/AgentTerminal.js +286 -25
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/services/aiService.d.ts +2 -0
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/package.json +1 -1
- package/src/editor/ai/AgentTerminal.tsx +377 -77
- package/src/editor/services/aiService.ts +3 -0
- package/src/revision.ts +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React, { useEffect, useState, useRef, useCallback, useLayoutEffect, useMemo, } from "react";
|
|
3
3
|
import { Send, AlertCircle, Loader2, User, Wand2, Square, Mic, MicOff, ChevronDown, ChevronUp, ListTodo, } from "lucide-react";
|
|
4
4
|
import { DancingDots } from "./DancingDots";
|
|
@@ -18,6 +18,106 @@ import { Tooltip, TooltipTrigger, TooltipContent, } from "../../components/ui/to
|
|
|
18
18
|
const UserMessage = ({ message }) => {
|
|
19
19
|
return (_jsxs("div", { className: "flex gap-3 p-4", children: [_jsx("div", { className: "flex-shrink-0", children: _jsx(User, { className: "h-6 w-6 text-blue-600", strokeWidth: 1 }) }), _jsxs("div", { className: "min-w-0 flex-1 select-text", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [_jsx("span", { className: "text-xs font-medium text-gray-900", children: "You" }), message.createdDate && (_jsx("span", { className: "text-xs text-gray-400", children: formatTime(new Date(message.createdDate)) }))] }), _jsx("div", { className: "prose prose-sm max-w-none text-xs text-gray-700 select-text", children: message.content })] })] }));
|
|
20
20
|
};
|
|
21
|
+
// Helper to extract todos from potentially incomplete JSON during streaming
|
|
22
|
+
const extractPartialTodos = (jsonText) => {
|
|
23
|
+
// First try to parse complete JSON
|
|
24
|
+
try {
|
|
25
|
+
const parsed = JSON.parse(jsonText);
|
|
26
|
+
return Array.isArray(parsed) ? parsed : parsed?.items || [];
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
// If JSON is incomplete, try to extract whatever todo items we can find
|
|
30
|
+
const items = [];
|
|
31
|
+
// Look for individual todo objects in the partial JSON
|
|
32
|
+
// Match patterns like: { "text": "...", "done": false, "note": "..." }
|
|
33
|
+
// Handle various field orderings (text can be anywhere in the object)
|
|
34
|
+
const textPattern = /"text"\s*:\s*"([^"]+)"/g;
|
|
35
|
+
const textMatches = [];
|
|
36
|
+
let textMatch;
|
|
37
|
+
while ((textMatch = textPattern.exec(jsonText)) !== null) {
|
|
38
|
+
if (textMatch[1]) {
|
|
39
|
+
textMatches.push({
|
|
40
|
+
text: textMatch[1],
|
|
41
|
+
startIdx: textMatch.index,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// For each text field found, try to find the enclosing object
|
|
46
|
+
for (const { text, startIdx } of textMatches) {
|
|
47
|
+
// Find the opening brace before this text field
|
|
48
|
+
let openBrace = -1;
|
|
49
|
+
for (let i = startIdx - 1; i >= 0; i--) {
|
|
50
|
+
if (jsonText[i] === "{") {
|
|
51
|
+
openBrace = i;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
if (jsonText[i] === "}")
|
|
55
|
+
break; // Hit another object's end
|
|
56
|
+
}
|
|
57
|
+
if (openBrace === -1)
|
|
58
|
+
continue;
|
|
59
|
+
// Find the closing brace after this text field
|
|
60
|
+
let closeBrace = -1;
|
|
61
|
+
let depth = 0;
|
|
62
|
+
for (let i = openBrace; i < jsonText.length; i++) {
|
|
63
|
+
if (jsonText[i] === "{")
|
|
64
|
+
depth++;
|
|
65
|
+
if (jsonText[i] === "}") {
|
|
66
|
+
depth--;
|
|
67
|
+
if (depth === 0) {
|
|
68
|
+
closeBrace = i;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Extract the object and try to parse it
|
|
74
|
+
const objStr = closeBrace !== -1
|
|
75
|
+
? jsonText.substring(openBrace, closeBrace + 1)
|
|
76
|
+
: jsonText.substring(openBrace) + "}"; // Try to close incomplete object
|
|
77
|
+
try {
|
|
78
|
+
const obj = JSON.parse(objStr);
|
|
79
|
+
if (obj.text) {
|
|
80
|
+
items.push({
|
|
81
|
+
text: obj.text,
|
|
82
|
+
done: obj.done === true,
|
|
83
|
+
note: obj.note || undefined,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
// Skip malformed objects
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Also try to extract from partial objects at the end
|
|
92
|
+
// Look for the last opening brace and try to parse up to where we have valid content
|
|
93
|
+
const lines = jsonText.split("\n");
|
|
94
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
95
|
+
const partialJson = lines.slice(0, i + 1).join("\n");
|
|
96
|
+
// Try to close any open braces/brackets
|
|
97
|
+
let testJson = partialJson;
|
|
98
|
+
const openBraces = (testJson.match(/\{/g) || []).length;
|
|
99
|
+
const closeBraces = (testJson.match(/\}/g) || []).length;
|
|
100
|
+
const openBrackets = (testJson.match(/\[/g) || []).length;
|
|
101
|
+
const closeBrackets = (testJson.match(/\]/g) || []).length;
|
|
102
|
+
// Add missing closing characters
|
|
103
|
+
testJson += "]".repeat(openBrackets - closeBrackets);
|
|
104
|
+
testJson += "}".repeat(openBraces - closeBraces);
|
|
105
|
+
try {
|
|
106
|
+
const parsed = JSON.parse(testJson);
|
|
107
|
+
const partialItems = Array.isArray(parsed)
|
|
108
|
+
? parsed
|
|
109
|
+
: parsed?.items || [];
|
|
110
|
+
if (partialItems.length > items.length) {
|
|
111
|
+
return partialItems;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return items;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
21
121
|
const extractTodosFromMessages = (messages) => {
|
|
22
122
|
const todos = [];
|
|
23
123
|
const fencedTodoToken = "```todo_list";
|
|
@@ -48,13 +148,20 @@ const extractTodosFromMessages = (messages) => {
|
|
|
48
148
|
break;
|
|
49
149
|
try {
|
|
50
150
|
let jsonText = "";
|
|
151
|
+
let isComplete = true;
|
|
51
152
|
if (isFenced) {
|
|
52
153
|
const afterToken = todoStart + fencedTodoToken.length;
|
|
53
154
|
const closePos = content.indexOf("```", afterToken);
|
|
54
|
-
if (closePos === -1)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
155
|
+
if (closePos === -1) {
|
|
156
|
+
// Incomplete fenced block - extract what we have so far
|
|
157
|
+
jsonText = content.slice(afterToken).trim();
|
|
158
|
+
isComplete = false;
|
|
159
|
+
cursor = content.length; // Process till end
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
jsonText = content.slice(afterToken, closePos).trim();
|
|
163
|
+
cursor = closePos + 3;
|
|
164
|
+
}
|
|
58
165
|
}
|
|
59
166
|
else {
|
|
60
167
|
const afterToken = todoStart + plainTodoToken.length;
|
|
@@ -74,24 +181,54 @@ const extractTodosFromMessages = (messages) => {
|
|
|
74
181
|
}
|
|
75
182
|
}
|
|
76
183
|
}
|
|
77
|
-
if (braceEnd === -1)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
184
|
+
if (braceEnd === -1) {
|
|
185
|
+
// Incomplete JSON - extract what we have
|
|
186
|
+
jsonText = content.slice(braceStart).trim();
|
|
187
|
+
isComplete = false;
|
|
188
|
+
cursor = content.length;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
jsonText = content.slice(braceStart, braceEnd + 1).trim();
|
|
192
|
+
cursor = braceEnd + 1;
|
|
193
|
+
}
|
|
81
194
|
}
|
|
82
|
-
|
|
83
|
-
const todoItems =
|
|
84
|
-
|
|
195
|
+
// Use the partial extraction helper for incomplete JSON
|
|
196
|
+
const todoItems = isComplete
|
|
197
|
+
? (() => {
|
|
198
|
+
try {
|
|
199
|
+
const parsed = JSON.parse(jsonText);
|
|
200
|
+
return Array.isArray(parsed) ? parsed : parsed?.items || [];
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
})()
|
|
206
|
+
: extractPartialTodos(jsonText);
|
|
207
|
+
const title = (() => {
|
|
208
|
+
try {
|
|
209
|
+
const parsed = JSON.parse(jsonText);
|
|
210
|
+
return Array.isArray(parsed) ? undefined : parsed?.title;
|
|
211
|
+
}
|
|
212
|
+
catch (e) {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
})();
|
|
85
216
|
todoItems.forEach((item) => {
|
|
86
217
|
if (!item)
|
|
87
218
|
return;
|
|
88
|
-
const text = item.text ||
|
|
219
|
+
const text = item.text ||
|
|
220
|
+
item.content ||
|
|
221
|
+
item.label ||
|
|
222
|
+
String(item.task || item.title || "");
|
|
89
223
|
if (!text)
|
|
90
224
|
return;
|
|
91
225
|
todos.push({
|
|
92
226
|
id: item.id,
|
|
93
227
|
text,
|
|
94
|
-
done: !!(item.done ??
|
|
228
|
+
done: !!(item.done ??
|
|
229
|
+
item.completed ??
|
|
230
|
+
item.checked ??
|
|
231
|
+
item.status === "completed"),
|
|
95
232
|
note: item.note || item.description,
|
|
96
233
|
messageId: message.id,
|
|
97
234
|
sourceTitle: title,
|
|
@@ -107,25 +244,97 @@ const extractTodosFromMessages = (messages) => {
|
|
|
107
244
|
return todos;
|
|
108
245
|
};
|
|
109
246
|
// TodoListPanel component
|
|
110
|
-
const TodoListPanel = ({ messages }) => {
|
|
247
|
+
const TodoListPanel = ({ messages, agentMetadata, }) => {
|
|
111
248
|
const [isExpanded, setIsExpanded] = useState(true);
|
|
112
|
-
const todos = useMemo(() =>
|
|
113
|
-
|
|
249
|
+
const todos = useMemo(() => {
|
|
250
|
+
// First try to get todos from agent metadata (real-time updates)
|
|
251
|
+
const metadataTodos = (() => {
|
|
252
|
+
try {
|
|
253
|
+
const context = agentMetadata?.additionalData?.context;
|
|
254
|
+
const todoList = context?.todoList;
|
|
255
|
+
if (todoList?.items && Array.isArray(todoList.items)) {
|
|
256
|
+
return todoList.items
|
|
257
|
+
.map((item, idx) => ({
|
|
258
|
+
id: item.id || `metadata-${idx}`,
|
|
259
|
+
text: item.text ||
|
|
260
|
+
item.label ||
|
|
261
|
+
String(item.task || item.title || ""),
|
|
262
|
+
done: !!(item.done ?? item.completed ?? item.checked),
|
|
263
|
+
note: item.note || item.description,
|
|
264
|
+
messageId: undefined,
|
|
265
|
+
sourceTitle: todoList.title,
|
|
266
|
+
}))
|
|
267
|
+
.filter((item) => item.text);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
catch (e) {
|
|
271
|
+
// Fallback to extracting from messages
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
})();
|
|
275
|
+
// If we have metadata todos, use them; otherwise extract from messages
|
|
276
|
+
if (metadataTodos && metadataTodos.length > 0) {
|
|
277
|
+
return metadataTodos;
|
|
278
|
+
}
|
|
279
|
+
return extractTodosFromMessages(messages);
|
|
280
|
+
}, [messages, agentMetadata]);
|
|
281
|
+
// Check if there's an active streaming message with incomplete todo content
|
|
114
282
|
const isUpdating = useMemo(() => {
|
|
115
283
|
return messages.some((msg) => {
|
|
116
284
|
if (msg.role !== "assistant" || msg.isCompleted)
|
|
117
285
|
return false;
|
|
118
286
|
const content = msg.content || "";
|
|
119
|
-
|
|
287
|
+
// Check for incomplete fenced todo blocks
|
|
288
|
+
const fencedStart = content.indexOf("```todo_list");
|
|
289
|
+
if (fencedStart !== -1) {
|
|
290
|
+
const afterStart = fencedStart + "```todo_list".length;
|
|
291
|
+
const closePos = content.indexOf("```", afterStart);
|
|
292
|
+
if (closePos === -1) {
|
|
293
|
+
// Incomplete fenced block
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// Check for incomplete plain todo blocks
|
|
298
|
+
const plainStart = content.indexOf("todo_list");
|
|
299
|
+
if (plainStart !== -1 && plainStart !== fencedStart) {
|
|
300
|
+
const before = plainStart > 0 ? content[plainStart - 1] : "\n";
|
|
301
|
+
if (before === "\n" || before === "\r" || plainStart === 0) {
|
|
302
|
+
const braceStart = content.indexOf("{", plainStart);
|
|
303
|
+
if (braceStart !== -1) {
|
|
304
|
+
let depth = 0;
|
|
305
|
+
let braceEnd = -1;
|
|
306
|
+
for (let i = braceStart; i < content.length; i++) {
|
|
307
|
+
if (content[i] === "{")
|
|
308
|
+
depth++;
|
|
309
|
+
if (content[i] === "}") {
|
|
310
|
+
depth--;
|
|
311
|
+
if (depth === 0) {
|
|
312
|
+
braceEnd = i;
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (braceEnd === -1) {
|
|
318
|
+
// Incomplete plain block
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return false;
|
|
120
325
|
});
|
|
121
326
|
}, [messages]);
|
|
122
327
|
if (todos.length === 0 && !isUpdating)
|
|
123
328
|
return null;
|
|
124
329
|
const completedCount = todos.filter((t) => t.done).length;
|
|
125
330
|
const totalCount = todos.length;
|
|
126
|
-
return (_jsxs("div", { className: "border-t border-gray-200 bg-gray-50", children: [_jsxs("button", { onClick: () => setIsExpanded(!isExpanded), className: "flex w-full cursor-pointer items-center justify-between px-4 py-2 text-left transition-colors hover:bg-gray-100", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ListTodo, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 }), _jsx("span", { className: "text-xs font-medium text-gray-700", children: "Todo List" }), isUpdating ? (_jsxs("span", { className: "flex items-center gap-1 text-xs text-blue-600", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin", strokeWidth: 1 }), "Updating..."] })) : (_jsxs("span", { className: "text-xs text-gray-500", children: [completedCount, "/", totalCount, " completed"] }))] }), isExpanded ? (_jsx(ChevronUp, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 })) : (_jsx(ChevronDown, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 }))] }), isExpanded && (
|
|
127
|
-
|
|
128
|
-
|
|
331
|
+
return (_jsxs("div", { className: "border-t border-gray-200 bg-gray-50", children: [_jsxs("button", { onClick: () => setIsExpanded(!isExpanded), className: "flex w-full cursor-pointer items-center justify-between px-4 py-2 text-left transition-colors hover:bg-gray-100", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ListTodo, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 }), _jsx("span", { className: "text-xs font-medium text-gray-700", children: "Todo List" }), isUpdating ? (_jsxs("span", { className: "flex items-center gap-1 text-xs text-blue-600", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin", strokeWidth: 1 }), "Updating..."] })) : (_jsxs("span", { className: "text-xs text-gray-500", children: [completedCount, "/", totalCount, " completed"] }))] }), isExpanded ? (_jsx(ChevronUp, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 })) : (_jsx(ChevronDown, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 }))] }), isExpanded && (_jsxs("div", { className: "max-h-64 overflow-y-auto px-4 pb-3", children: [todos.length > 0 && (_jsx("div", { className: "space-y-1.5", children: todos.map((todo, idx) => (_jsxs("div", { className: "flex items-start gap-2 rounded bg-white p-2 text-xs", children: [_jsx("div", { className: "flex-shrink-0 pt-0.5", children: todo.done ? (_jsx("div", { className: "flex h-4 w-4 items-center justify-center rounded border-2 border-green-500 bg-green-500", children: _jsx("svg", { className: "h-3 w-3 text-white", fill: "none", strokeWidth: 2, stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }) })) : (_jsx("div", { className: "h-4 w-4 rounded border-2 border-gray-300" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: `${todo.done
|
|
332
|
+
? "text-gray-500 line-through"
|
|
333
|
+
: "text-gray-900"}`, children: todo.text }), todo.note && (_jsx("div", { className: "mt-0.5 text-xs text-gray-500", children: todo.note }))] })] }, todo.id || `${todo.messageId}-${idx}`))) })), isUpdating && (_jsxs("div", { className: `flex items-center gap-2 rounded px-3 py-2 text-xs ${todos.length > 0
|
|
334
|
+
? "mt-2 bg-blue-50 text-blue-700"
|
|
335
|
+
: "justify-center bg-white text-gray-500"}`, children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin", strokeWidth: 1 }), _jsx("span", { children: todos.length > 0
|
|
336
|
+
? "Updating todo list..."
|
|
337
|
+
: "Loading todo list..." })] }))] }))] }));
|
|
129
338
|
};
|
|
130
339
|
const groupConsecutiveMessages = (agentMessages) => {
|
|
131
340
|
// Work directly with the messages array - streaming messages are identified by their properties
|
|
@@ -222,7 +431,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
|
|
|
222
431
|
const [agent, setAgent] = useState(undefined);
|
|
223
432
|
const [messages, setMessages] = useState([]);
|
|
224
433
|
const [prompt, setPrompt] = useState("");
|
|
225
|
-
const [inputPlaceholder, setInputPlaceholder] = useState("Type your message... (Enter to send, Ctrl+Enter for new line)");
|
|
434
|
+
const [inputPlaceholder, setInputPlaceholder] = useState("Type your message... (Enter to send, Shift+Enter or Ctrl+Enter for new line)");
|
|
226
435
|
const [isLoading, setIsLoading] = useState(false);
|
|
227
436
|
const [isConnecting, setIsConnecting] = useState(false);
|
|
228
437
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
@@ -320,6 +529,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
|
|
|
320
529
|
}, [messages]);
|
|
321
530
|
const [error, setError] = useState(null);
|
|
322
531
|
const [costLimitExceeded, setCostLimitExceeded] = useState(null);
|
|
532
|
+
// Live running totals from backend status updates (tokenUsage)
|
|
533
|
+
const [liveTotals, setLiveTotals] = useState(null);
|
|
323
534
|
// Flag to track when we should create a new message
|
|
324
535
|
const shouldCreateNewMessage = useRef(false);
|
|
325
536
|
// Keep a ref to the current messages for immediate access
|
|
@@ -776,6 +987,25 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
|
|
|
776
987
|
case "statusUpdate":
|
|
777
988
|
try {
|
|
778
989
|
const kind = message?.data?.kind;
|
|
990
|
+
// Live token usage totals update from backend
|
|
991
|
+
if (kind === "tokenUsage") {
|
|
992
|
+
const totals = message?.data?.totals;
|
|
993
|
+
if (totals) {
|
|
994
|
+
setLiveTotals({
|
|
995
|
+
input: Number(totals.totalInputTokens) || 0,
|
|
996
|
+
output: Number(totals.totalOutputTokens) || 0,
|
|
997
|
+
cached: Number(totals.totalCachedInputTokens) || 0,
|
|
998
|
+
inputCost: Number(totals.totalInputTokenCost) || 0,
|
|
999
|
+
outputCost: Number(totals.totalOutputTokenCost) || 0,
|
|
1000
|
+
cachedCost: Number(totals.totalCachedInputTokenCost) || 0,
|
|
1001
|
+
totalCost: Number(totals.totalCost) || 0,
|
|
1002
|
+
currency: totals.currency,
|
|
1003
|
+
});
|
|
1004
|
+
// Force a re-render to update cost display immediately
|
|
1005
|
+
setMessages((prev) => [...prev]);
|
|
1006
|
+
}
|
|
1007
|
+
break;
|
|
1008
|
+
}
|
|
779
1009
|
if (kind === "toolApprovalsRequired") {
|
|
780
1010
|
const data = message.data || {};
|
|
781
1011
|
const msgId = data.messageId;
|
|
@@ -981,6 +1211,22 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
|
|
|
981
1211
|
updatedMessage.tokensUsed =
|
|
982
1212
|
(updatedMessage.inputTokens || 0) +
|
|
983
1213
|
(updatedMessage.outputTokens || 0);
|
|
1214
|
+
// Update cost data if provided in the completed event
|
|
1215
|
+
if (data.inputTokenCost !== undefined) {
|
|
1216
|
+
updatedMessage.inputTokenCost = data.inputTokenCost;
|
|
1217
|
+
}
|
|
1218
|
+
if (data.outputTokenCost !== undefined) {
|
|
1219
|
+
updatedMessage.outputTokenCost =
|
|
1220
|
+
data.outputTokenCost;
|
|
1221
|
+
}
|
|
1222
|
+
if (data.cachedInputTokenCost !== undefined ||
|
|
1223
|
+
data.cachedTokenCost !== undefined) {
|
|
1224
|
+
updatedMessage.cachedInputTokenCost =
|
|
1225
|
+
data.cachedInputTokenCost ?? data.cachedTokenCost;
|
|
1226
|
+
}
|
|
1227
|
+
if (data.totalCost !== undefined) {
|
|
1228
|
+
updatedMessage.totalCost = data.totalCost;
|
|
1229
|
+
}
|
|
984
1230
|
// Handle content that might only be sent in the completed event
|
|
985
1231
|
if (data.deltaContent && data.deltaContent.trim()) {
|
|
986
1232
|
if (!data.isIncremental) {
|
|
@@ -1639,7 +1885,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
|
|
|
1639
1885
|
}
|
|
1640
1886
|
};
|
|
1641
1887
|
const handleKeyPress = (e) => {
|
|
1642
|
-
|
|
1888
|
+
// Submit only on plain Enter (no Ctrl/Meta/Shift/Alt)
|
|
1889
|
+
if (e.key === "Enter" &&
|
|
1890
|
+
!e.ctrlKey &&
|
|
1891
|
+
!e.metaKey &&
|
|
1892
|
+
!e.shiftKey &&
|
|
1893
|
+
!e.altKey) {
|
|
1643
1894
|
e.preventDefault();
|
|
1644
1895
|
handleSubmit();
|
|
1645
1896
|
}
|
|
@@ -2315,7 +2566,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
|
|
|
2315
2566
|
sendQuickMessage(text);
|
|
2316
2567
|
} }, groupIndex));
|
|
2317
2568
|
}
|
|
2318
|
-
}) }), _jsx("div", { className: showDots ? "visible" : "invisible", children: _jsx(DancingDots, {}) }), _jsx("div", { ref: messagesEndRef })] }), renderContextInfoBar(), _jsx(TodoListPanel, { messages: messages }), _jsxs("div", { className: "border-t border-gray-200 p-4", children: [_jsx("div", { className: "flex items-stretch gap-2", children: _jsx(Textarea, { ref: textareaRef, value: prompt, onChange: (e) => {
|
|
2569
|
+
}) }), _jsx("div", { className: showDots ? "visible" : "invisible", children: _jsx(DancingDots, {}) }), _jsx("div", { ref: messagesEndRef })] }), renderContextInfoBar(), _jsx(TodoListPanel, { messages: messages, agentMetadata: agentMetadata }), _jsxs("div", { className: "border-t border-gray-200 p-4", children: [_jsx("div", { className: "flex items-stretch gap-2", children: _jsx(Textarea, { ref: textareaRef, value: prompt, onChange: (e) => {
|
|
2319
2570
|
setPrompt(e.target.value);
|
|
2320
2571
|
// Reset history index when user starts typing
|
|
2321
2572
|
if (currentHistoryIndex !== -1) {
|
|
@@ -2384,7 +2635,17 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
|
|
|
2384
2635
|
setShowPredefined(false);
|
|
2385
2636
|
if (textareaRef.current)
|
|
2386
2637
|
textareaRef.current.focus();
|
|
2387
|
-
}, children: p.title }, index))) }) })] })) : null] }), _jsxs("div", { className: "flex items-center gap-1 self-end", children: [isVoiceSupported ? (_jsx(Button, { onClick: toggleVoice, size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", title: isListening ? "Stop voice input" : "Start voice input", "aria-label": isListening ? "Stop voice input" : "Start voice input", "aria-pressed": isListening, children: isListening ? (_jsx(MicOff, { className: "size-3", strokeWidth: 1 })) : (_jsx(Mic, { className: "size-3", strokeWidth: 1 })) })) : null, _jsx(Button, { onClick: isExecuting ? handleStop : handleSubmit, disabled: !isExecuting && !prompt.trim(), size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", title: isExecuting ? "Stop" : "Send", "aria-label": isExecuting ? "Stop" : "Send", "data-testid": "agent-send-stop-button", "data-executing": isExecuting ? "true" : "false", children: isExecuting ? (_jsx(Square, { className: "size-3", strokeWidth: 1 })) : (_jsx(Send, { className: "size-3", strokeWidth: 1 })) })] })] }), _jsxs("div", { className: "mt-1 flex items-center gap-2 text-[10px] text-gray-500", children: [_jsx(AgentCostDisplay, { totalTokens:
|
|
2638
|
+
}, children: p.title }, index))) }) })] })) : null] }), _jsxs("div", { className: "flex items-center gap-1 self-end", children: [isVoiceSupported ? (_jsx(Button, { onClick: toggleVoice, size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", title: isListening ? "Stop voice input" : "Start voice input", "aria-label": isListening ? "Stop voice input" : "Start voice input", "aria-pressed": isListening, children: isListening ? (_jsx(MicOff, { className: "size-3", strokeWidth: 1 })) : (_jsx(Mic, { className: "size-3", strokeWidth: 1 })) })) : null, _jsx(Button, { onClick: isExecuting ? handleStop : handleSubmit, disabled: !isExecuting && !prompt.trim(), size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", title: isExecuting ? "Stop" : "Send", "aria-label": isExecuting ? "Stop" : "Send", "data-testid": "agent-send-stop-button", "data-executing": isExecuting ? "true" : "false", children: isExecuting ? (_jsx(Square, { className: "size-3", strokeWidth: 1 })) : (_jsx(Send, { className: "size-3", strokeWidth: 1 })) })] })] }), _jsxs("div", { className: "mt-1 flex items-center gap-2 text-[10px] text-gray-500", children: [_jsx(AgentCostDisplay, { totalTokens: liveTotals
|
|
2639
|
+
? {
|
|
2640
|
+
input: liveTotals.input,
|
|
2641
|
+
output: liveTotals.output,
|
|
2642
|
+
cached: liveTotals.cached,
|
|
2643
|
+
inputCost: liveTotals.inputCost,
|
|
2644
|
+
outputCost: liveTotals.outputCost,
|
|
2645
|
+
cachedCost: liveTotals.cachedCost,
|
|
2646
|
+
totalCost: liveTotals.totalCost,
|
|
2647
|
+
}
|
|
2648
|
+
: totalTokens }), (() => {
|
|
2388
2649
|
try {
|
|
2389
2650
|
const s = window.__agentContextWindowStatus;
|
|
2390
2651
|
if (!s || !s.contextWindowTokens)
|