@alpaca-editor/core 1.0.4049 → 1.0.4053

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.
Files changed (69) hide show
  1. package/dist/components/ui/textarea.js +1 -1
  2. package/dist/components/ui/textarea.js.map +1 -1
  3. package/dist/editor/Terminal.js +3 -3
  4. package/dist/editor/Terminal.js.map +1 -1
  5. package/dist/editor/ai/AgentCostDisplay.js +2 -2
  6. package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
  7. package/dist/editor/ai/AgentHistory.d.ts +4 -4
  8. package/dist/editor/ai/AgentHistory.js +1 -1
  9. package/dist/editor/ai/AgentHistory.js.map +1 -1
  10. package/dist/editor/ai/AgentTerminal.d.ts +4 -0
  11. package/dist/editor/ai/AgentTerminal.js +753 -0
  12. package/dist/editor/ai/AgentTerminal.js.map +1 -0
  13. package/dist/editor/ai/Agents.d.ts +1 -3
  14. package/dist/editor/ai/Agents.js +213 -353
  15. package/dist/editor/ai/Agents.js.map +1 -1
  16. package/dist/editor/ai/AiPromptPopover.js +2 -2
  17. package/dist/editor/ai/AiPromptPopover.js.map +1 -1
  18. package/dist/editor/ai/AiResponseMessage.d.ts +0 -1
  19. package/dist/editor/ai/AiResponseMessage.js +23 -143
  20. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  21. package/dist/editor/ai/AiTerminal.d.ts +5 -23
  22. package/dist/editor/ai/AiTerminal.js +81 -824
  23. package/dist/editor/ai/AiTerminal.js.map +1 -1
  24. package/dist/editor/ai/DancingDots.d.ts +1 -0
  25. package/dist/editor/ai/DancingDots.js +6 -0
  26. package/dist/editor/ai/DancingDots.js.map +1 -0
  27. package/dist/editor/ai/ToolCallDisplay.d.ts +37 -0
  28. package/dist/editor/ai/ToolCallDisplay.js +154 -0
  29. package/dist/editor/ai/ToolCallDisplay.js.map +1 -0
  30. package/dist/editor/client/EditorClient.js +5 -1
  31. package/dist/editor/client/EditorClient.js.map +1 -1
  32. package/dist/editor/services/agentService.d.ts +23 -30
  33. package/dist/editor/services/agentService.js +62 -124
  34. package/dist/editor/services/agentService.js.map +1 -1
  35. package/dist/editor/sidebar/GraphQL.js +1 -0
  36. package/dist/editor/sidebar/GraphQL.js.map +1 -1
  37. package/dist/editor/sidebar/ViewSelector.js +8 -6
  38. package/dist/editor/sidebar/ViewSelector.js.map +1 -1
  39. package/dist/editor/ui/Section.js +4 -3
  40. package/dist/editor/ui/Section.js.map +1 -1
  41. package/dist/editor/utils.d.ts +4 -0
  42. package/dist/editor/utils.js +23 -0
  43. package/dist/editor/utils.js.map +1 -1
  44. package/dist/revision.d.ts +2 -2
  45. package/dist/revision.js +2 -2
  46. package/dist/styles.css +18 -33
  47. package/package.json +1 -1
  48. package/src/components/ui/textarea.tsx +1 -1
  49. package/src/editor/Terminal.tsx +4 -4
  50. package/src/editor/ai/AgentCostDisplay.tsx +7 -11
  51. package/src/editor/ai/AgentHistory.tsx +7 -9
  52. package/src/editor/ai/AgentTerminal.tsx +1094 -0
  53. package/src/editor/ai/Agents.tsx +340 -477
  54. package/src/editor/ai/AiPromptPopover.tsx +2 -2
  55. package/src/editor/ai/AiResponseMessage.tsx +85 -366
  56. package/src/editor/ai/AiTerminal.tsx +142 -1213
  57. package/src/editor/ai/DancingDots.tsx +14 -0
  58. package/src/editor/ai/ToolCallDisplay.tsx +363 -0
  59. package/src/editor/client/EditorClient.tsx +6 -1
  60. package/src/editor/services/agentService.ts +89 -162
  61. package/src/editor/sidebar/GraphQL.tsx +1 -0
  62. package/src/editor/sidebar/ViewSelector.tsx +82 -57
  63. package/src/editor/ui/Section.tsx +4 -3
  64. package/src/editor/utils.ts +29 -0
  65. package/src/revision.ts +2 -2
  66. package/dist/editor/ai/EditorAiTerminal.d.ts +0 -6
  67. package/dist/editor/ai/EditorAiTerminal.js +0 -7
  68. package/dist/editor/ai/EditorAiTerminal.js.map +0 -1
  69. package/src/editor/ai/EditorAiTerminal.tsx +0 -23
@@ -0,0 +1,753 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState, useRef, useCallback } from "react";
3
+ import { Send, Bot, AlertCircle, Loader2, User } from "lucide-react";
4
+ import { DancingDots } from "./DancingDots";
5
+ import { getAgent, startAgent, connectToAgentStream, } from "../services/agentService";
6
+ import { useEditContext } from "../client/editContext";
7
+ import { Textarea } from "../../components/ui/textarea";
8
+ import { Button } from "../../components/ui/button";
9
+ import { AiResponseMessage } from "./AiResponseMessage";
10
+ import { AgentCostDisplay } from "./AgentCostDisplay";
11
+ // Simple user message component
12
+ const UserMessage = ({ message }) => {
13
+ 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-sm font-medium text-gray-900", children: "You" }), message.createdDate && (_jsx("span", { className: "text-xs text-gray-400", children: new Date(message.createdDate).toLocaleTimeString() }))] }), _jsx("div", { className: "prose prose-sm max-w-none text-sm text-gray-700 select-text", children: message.content })] })] }));
14
+ };
15
+ const groupConsecutiveMessages = (agentMessages) => {
16
+ // Work directly with the messages array - streaming messages are identified by their properties
17
+ const allMessages = agentMessages;
18
+ const groups = [];
19
+ let currentAssistantGroup = [];
20
+ for (const message of allMessages) {
21
+ if (message.role === "user") {
22
+ // Finish any current assistant group
23
+ if (currentAssistantGroup.length > 0) {
24
+ groups.push({
25
+ type: "assistant-group",
26
+ messages: currentAssistantGroup,
27
+ });
28
+ currentAssistantGroup = [];
29
+ }
30
+ // Add user message
31
+ groups.push({ type: "user", messages: [message] });
32
+ }
33
+ else if (message.role === "assistant") {
34
+ // Add to current assistant group
35
+ currentAssistantGroup.push(message);
36
+ }
37
+ // Skip tool messages as they're handled within assistant messages
38
+ }
39
+ // Add any remaining assistant group
40
+ if (currentAssistantGroup.length > 0) {
41
+ groups.push({ type: "assistant-group", messages: currentAssistantGroup });
42
+ }
43
+ return groups;
44
+ };
45
+ // Calculate total token usage and cost data from agent messages
46
+ const calculateTotalTokens = (messages) => {
47
+ const totals = messages.reduce((acc, message) => {
48
+ return {
49
+ input: acc.input + (message.inputTokens || 0),
50
+ output: acc.output + (message.outputTokens || 0),
51
+ cached: acc.cached + (message.cachedInputTokens || 0),
52
+ inputCost: acc.inputCost + (message.inputTokenCost || 0),
53
+ outputCost: acc.outputCost + (message.outputTokenCost || 0),
54
+ cachedCost: acc.cachedCost + (message.cachedInputTokenCost || 0),
55
+ totalCost: acc.totalCost + (message.totalCost || 0),
56
+ };
57
+ }, {
58
+ input: 0,
59
+ output: 0,
60
+ cached: 0,
61
+ inputCost: 0,
62
+ outputCost: 0,
63
+ cachedCost: 0,
64
+ totalCost: 0,
65
+ });
66
+ return totals;
67
+ };
68
+ // Convert agent messages to AI terminal format for a response group
69
+ const convertAgentMessagesToAiFormat = (agentMessages) => {
70
+ return agentMessages.map((agentMessage) => {
71
+ const message = {
72
+ id: agentMessage.id,
73
+ content: agentMessage.content,
74
+ formattedContent: agentMessage.content
75
+ ?.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
76
+ ?.replace(/\n/g, "<br/>"),
77
+ name: agentMessage.name,
78
+ role: agentMessage.role,
79
+ tool_calls: agentMessage.toolCalls
80
+ ? agentMessage.toolCalls.map((toolCall) => ({
81
+ id: toolCall.toolCallId,
82
+ displayName: toolCall.functionName,
83
+ function: {
84
+ name: toolCall.functionName,
85
+ arguments: toolCall.functionArguments,
86
+ result: toolCall.functionResult,
87
+ error: toolCall.functionError,
88
+ },
89
+ }))
90
+ : [],
91
+ };
92
+ if (agentMessage.toolCallId) {
93
+ message.tool_call_id = agentMessage.toolCallId;
94
+ }
95
+ return message;
96
+ });
97
+ };
98
+ // interface AgentTerminalProps {
99
+ // agentStub: Agent;
100
+ // }
101
+ export function AgentTerminal({ agentStub }) {
102
+ const editContext = useEditContext();
103
+ const [agent, setAgent] = useState(undefined);
104
+ const [messages, setMessages] = useState([]);
105
+ const [prompt, setPrompt] = useState("");
106
+ const [isLoading, setIsLoading] = useState(false);
107
+ const [isConnecting, setIsConnecting] = useState(false);
108
+ const [isSubmitting, setIsSubmitting] = useState(false);
109
+ const [isWaitingForResponse, setIsWaitingForResponse] = useState(false);
110
+ const [promptHistory, setPromptHistory] = useState(() => {
111
+ if (typeof window !== "undefined") {
112
+ return JSON.parse(localStorage.getItem("editor.agent.promptHistory") || "[]");
113
+ }
114
+ return [];
115
+ });
116
+ const [currentHistoryIndex, setCurrentHistoryIndex] = useState(-1);
117
+ useEffect(() => {
118
+ localStorage.setItem("editor.agent.promptHistory", JSON.stringify(promptHistory));
119
+ }, [promptHistory]);
120
+ useEffect(() => {
121
+ // Keep messagesRef synchronized with messages state
122
+ messagesRef.current = messages;
123
+ }, [messages]);
124
+ const [error, setError] = useState(null);
125
+ // Flag to track when we should create a new message
126
+ const shouldCreateNewMessage = useRef(false);
127
+ // Keep a ref to the current messages for immediate access
128
+ const messagesRef = useRef([]);
129
+ const abortControllerRef = useRef(null);
130
+ const messagesEndRef = useRef(null);
131
+ const textareaRef = useRef(null);
132
+ const messagesContainerRef = useRef(null);
133
+ const [shouldAutoScroll, setShouldAutoScroll] = useState(true);
134
+ // Auto-scroll to bottom when new messages arrive
135
+ const scrollToBottom = useCallback(() => {
136
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
137
+ }, []);
138
+ // Check if user is at the bottom of the scroll container
139
+ const isAtBottom = useCallback(() => {
140
+ const container = messagesContainerRef.current;
141
+ if (!container)
142
+ return true;
143
+ const threshold = 100; // pixels from bottom to consider "at bottom"
144
+ return (container.scrollHeight - container.scrollTop - container.clientHeight <
145
+ threshold);
146
+ }, []);
147
+ // Handle scroll events to detect manual scrolling
148
+ const handleScroll = useCallback(() => {
149
+ const atBottom = isAtBottom();
150
+ if (atBottom !== shouldAutoScroll) {
151
+ setShouldAutoScroll(atBottom);
152
+ }
153
+ }, [isAtBottom, shouldAutoScroll]);
154
+ // Shared stream message handlers with messageId support
155
+ const createNewStreamMessage = useCallback((messageId, agentData) => {
156
+ const currentAgent = agentData || agent;
157
+ if (!currentAgent) {
158
+ console.error("❌ createNewStreamMessage: No agent available", {
159
+ messageId,
160
+ agentData: !!agentData,
161
+ agent: !!agent,
162
+ });
163
+ throw new Error("No agent available");
164
+ }
165
+ console.log("✅ Creating new stream message with agent:", currentAgent.id);
166
+ return {
167
+ id: messageId,
168
+ agentId: currentAgent.id,
169
+ messageIndex: -1,
170
+ role: "assistant",
171
+ content: "",
172
+ name: "agent",
173
+ messageType: "streaming",
174
+ isCompleted: false,
175
+ model: currentAgent.model || "",
176
+ tokensUsed: 0,
177
+ inputTokens: 0,
178
+ outputTokens: 0,
179
+ cachedInputTokens: 0,
180
+ inputTokenCost: 0,
181
+ outputTokenCost: 0,
182
+ cachedInputTokenCost: 0,
183
+ totalCost: 0,
184
+ currency: currentAgent.currency || "USD",
185
+ createdDate: new Date().toISOString(),
186
+ toolCalls: [],
187
+ };
188
+ }, [agent]);
189
+ const handleContentChunk = useCallback((message, agentData) => {
190
+ const messageId = message.data?.messageId;
191
+ if (!messageId) {
192
+ console.error("No messageId found in message data!");
193
+ return;
194
+ }
195
+ // Clear waiting state when first content chunk arrives
196
+ setIsWaitingForResponse(false);
197
+ // Always call setMessages and handle all logic in the callback with latest messages
198
+ setMessages((prev) => {
199
+ // Find existing message by messageId in the latest messages
200
+ const existingMessageIndex = prev.findIndex((msg) => msg.id === messageId);
201
+ if (existingMessageIndex === -1) {
202
+ // Message doesn't exist - create new streaming message
203
+ const newStreamMessage = createNewStreamMessage(messageId, agentData);
204
+ // Set the content for the new message
205
+ const updatedNewMessage = { ...newStreamMessage };
206
+ if (!message.data.isIncremental) {
207
+ updatedNewMessage.content = message.data?.deltaContent || "";
208
+ }
209
+ else {
210
+ updatedNewMessage.content = message.data?.deltaContent || "";
211
+ }
212
+ const updated = [...prev, updatedNewMessage];
213
+ messagesRef.current = updated;
214
+ return updated;
215
+ }
216
+ else {
217
+ // Message exists - update it
218
+ const existingMessage = prev[existingMessageIndex];
219
+ if (!existingMessage)
220
+ return prev;
221
+ // Check if existing content is already longer than what we're trying to stream
222
+ const currentContentLength = existingMessage.content?.length || 0;
223
+ const totalContentLength = message.data?.totalContentLength || 0;
224
+ if (currentContentLength >= totalContentLength &&
225
+ totalContentLength > 0) {
226
+ return prev;
227
+ }
228
+ const updatedMessage = { ...existingMessage };
229
+ if (!message.data.isIncremental) {
230
+ updatedMessage.content = message.data?.deltaContent || "";
231
+ }
232
+ else {
233
+ updatedMessage.content =
234
+ existingMessage.content + (message.data?.deltaContent || "");
235
+ }
236
+ const updated = prev.map((msg, index) => index === existingMessageIndex ? updatedMessage : msg);
237
+ messagesRef.current = updated;
238
+ return updated;
239
+ }
240
+ });
241
+ }, [createNewStreamMessage]);
242
+ const handleToolCall = useCallback((message, agentData) => {
243
+ const toolCallId = message.data?.toolCallId || message.data?.id || crypto.randomUUID();
244
+ const toolCallMessageId = message.data?.messageId;
245
+ // Find or create the target message for this tool call
246
+ if (toolCallMessageId) {
247
+ const currentMessages = messagesRef.current;
248
+ const existingMessageIndex = currentMessages.findIndex((msg) => msg.id === toolCallMessageId);
249
+ if (existingMessageIndex === -1) {
250
+ // Double-check with current ref to prevent race conditions
251
+ const currentMessages = messagesRef.current;
252
+ const existsInRef = currentMessages.find((msg) => msg.id === toolCallMessageId);
253
+ if (!existsInRef) {
254
+ // Create new message for this tool call
255
+ const newStreamMessage = createNewStreamMessage(toolCallMessageId, agentData);
256
+ setMessages((prev) => {
257
+ // Final check before adding to prevent duplicates
258
+ const finalCheck = prev.find((msg) => msg.id === toolCallMessageId);
259
+ if (finalCheck) {
260
+ console.log("#!# ⚠️ Tool call message already exists in state, skipping:", toolCallMessageId);
261
+ return prev;
262
+ }
263
+ const updated = [...prev, newStreamMessage];
264
+ messagesRef.current = updated;
265
+ return updated;
266
+ });
267
+ }
268
+ }
269
+ else {
270
+ console.log("📋 Adding tool call to existing message:", toolCallMessageId);
271
+ }
272
+ }
273
+ // Add tool call to the message in the array
274
+ if (toolCallMessageId && message.data && toolCallId) {
275
+ const functionName = message.data.functionName ||
276
+ message.data.name ||
277
+ message.data.function?.name ||
278
+ "unknown";
279
+ const toolCall = {
280
+ id: toolCallId,
281
+ messageId: toolCallMessageId,
282
+ toolCallId: toolCallId,
283
+ functionName: functionName,
284
+ functionArguments: message.data.functionArguments ||
285
+ message.data.arguments ||
286
+ JSON.stringify(message.data.function?.arguments || {}),
287
+ functionResult: message.data.functionResult || message.data.result || "",
288
+ functionError: message.data.functionError || message.data.error || "",
289
+ isCompleted: false,
290
+ responseTimeMs: message.data.responseTimeMs,
291
+ createdDate: new Date().toISOString(),
292
+ };
293
+ // Check for duplicates using the current messages ref
294
+ const currentMessages = messagesRef.current;
295
+ const targetMessage = currentMessages.find((msg) => msg.id === toolCallMessageId);
296
+ if (targetMessage) {
297
+ const existingToolCalls = targetMessage.toolCalls || [];
298
+ const existingToolCallIndex = existingToolCalls.findIndex((tc) => tc.toolCallId === toolCallId);
299
+ if (existingToolCallIndex !== -1) {
300
+ return; // Skip adding duplicate
301
+ }
302
+ }
303
+ setMessages((prev) => {
304
+ const updated = prev.map((msg) => {
305
+ if (msg.id !== toolCallMessageId)
306
+ return msg;
307
+ const existingToolCalls = msg.toolCalls || [];
308
+ return { ...msg, toolCalls: [...existingToolCalls, toolCall] };
309
+ });
310
+ messagesRef.current = updated;
311
+ return updated;
312
+ });
313
+ }
314
+ }, [createNewStreamMessage]);
315
+ const handleToolResult = useCallback((message, agentData) => {
316
+ const resultToolCallId = message.data?.toolCallId || message.data?.id || crypto.randomUUID();
317
+ const resultMessageId = message.data?.messageId;
318
+ // Update tool result directly in the messages array
319
+ if (!resultMessageId) {
320
+ console.warn("⚠️ No messageId provided for tool result");
321
+ return;
322
+ }
323
+ console.log("📋 Updating tool result in message:", resultMessageId);
324
+ // Update the message with tool result
325
+ setMessages((prev) => {
326
+ const updated = prev.map((msg) => {
327
+ if (msg.id !== resultMessageId)
328
+ return msg;
329
+ const updatedMessage = { ...msg };
330
+ if (!updatedMessage.toolCalls) {
331
+ updatedMessage.toolCalls = [];
332
+ }
333
+ // Find and update the tool call with the result
334
+ const toolCallIndex = updatedMessage.toolCalls.findIndex((tc) => tc.toolCallId === resultToolCallId);
335
+ if (toolCallIndex >= 0) {
336
+ const existingToolCall = updatedMessage.toolCalls[toolCallIndex];
337
+ if (existingToolCall && message.data) {
338
+ const updatedToolCalls = [...updatedMessage.toolCalls];
339
+ const toolCall = {
340
+ id: existingToolCall.id,
341
+ messageId: existingToolCall.messageId,
342
+ toolCallId: existingToolCall.toolCallId,
343
+ functionName: existingToolCall.functionName,
344
+ functionArguments: existingToolCall.functionArguments,
345
+ functionResult: message.data.functionResult || message.data.result || "",
346
+ functionError: message.data.functionError || message.data.error || "",
347
+ isCompleted: true,
348
+ responseTimeMs: message.data.responseTimeMs,
349
+ createdDate: existingToolCall.createdDate,
350
+ };
351
+ updatedToolCalls[toolCallIndex] = toolCall;
352
+ updatedMessage.toolCalls = updatedToolCalls;
353
+ }
354
+ // Check if all tool calls in message are completed
355
+ const allToolCallsCompleted = updatedMessage.toolCalls.every((tc) => tc.isCompleted);
356
+ if (allToolCallsCompleted) {
357
+ shouldCreateNewMessage.current = true;
358
+ }
359
+ }
360
+ else if (message.data && resultToolCallId && resultMessageId) {
361
+ // Create new tool call if it doesn't exist
362
+ const functionName = message.data.functionName ||
363
+ message.data.name ||
364
+ message.data.function?.name ||
365
+ "unknown";
366
+ const toolCall = {
367
+ id: resultToolCallId,
368
+ messageId: resultMessageId,
369
+ toolCallId: resultToolCallId,
370
+ functionName: functionName,
371
+ functionArguments: message.data.functionArguments ||
372
+ message.data.arguments ||
373
+ JSON.stringify(message.data.function?.arguments || {}),
374
+ functionResult: message.data.functionResult || message.data.result || "",
375
+ functionError: message.data.functionError || message.data.error || "",
376
+ isCompleted: true,
377
+ responseTimeMs: message.data.responseTimeMs,
378
+ createdDate: new Date().toISOString(),
379
+ };
380
+ updatedMessage.toolCalls = [...updatedMessage.toolCalls, toolCall];
381
+ }
382
+ console.log("🔧 Updated tool calls count:", updatedMessage.toolCalls.length);
383
+ return updatedMessage;
384
+ });
385
+ messagesRef.current = updated;
386
+ return updated;
387
+ });
388
+ }, []);
389
+ // Connect to agent stream for real-time updates
390
+ const connectToStream = useCallback(async (agentData) => {
391
+ const currentAgent = agentData || agent;
392
+ if (!currentAgent)
393
+ return;
394
+ // Cancel any existing connection
395
+ if (abortControllerRef.current) {
396
+ abortControllerRef.current.abort();
397
+ }
398
+ const abortController = new AbortController();
399
+ abortControllerRef.current = abortController;
400
+ try {
401
+ setIsConnecting(true);
402
+ console.log("🔌 connectToStream: Starting stream connection");
403
+ await connectToAgentStream(currentAgent.id, (message) => {
404
+ switch (message.type) {
405
+ case "contentChunk":
406
+ handleContentChunk(message, currentAgent);
407
+ break;
408
+ case "toolCall":
409
+ handleToolCall(message, currentAgent);
410
+ break;
411
+ case "toolResult":
412
+ handleToolResult(message, currentAgent);
413
+ break;
414
+ case "statusUpdate":
415
+ break;
416
+ case "completed":
417
+ const completedMessageId = message.data?.messageId;
418
+ console.log("💾 Stream completed for message:", completedMessageId, "messages count:", messages.length);
419
+ // Mark the specific message as completed by messageId
420
+ if (completedMessageId) {
421
+ setMessages((prev) => prev.map((msg) => {
422
+ if (msg.id === completedMessageId) {
423
+ const updatedMessage = {
424
+ ...msg,
425
+ isCompleted: true,
426
+ messageType: "completed",
427
+ };
428
+ // Update token usage data if provided in the completed event
429
+ if (message.data) {
430
+ const data = message.data;
431
+ if (data.numInputTokens !== undefined) {
432
+ updatedMessage.inputTokens = data.numInputTokens;
433
+ }
434
+ if (data.numOutputTokens !== undefined) {
435
+ updatedMessage.outputTokens = data.numOutputTokens;
436
+ }
437
+ if (data.numCachedTokens !== undefined) {
438
+ updatedMessage.cachedInputTokens =
439
+ data.numCachedTokens;
440
+ }
441
+ // Update total tokens used
442
+ updatedMessage.tokensUsed =
443
+ (updatedMessage.inputTokens || 0) +
444
+ (updatedMessage.outputTokens || 0);
445
+ // Handle content that might only be sent in the completed event
446
+ if (data.deltaContent && data.deltaContent.trim()) {
447
+ if (!data.isIncremental) {
448
+ // Non-incremental: replace the entire content
449
+ updatedMessage.content = data.deltaContent;
450
+ }
451
+ else {
452
+ // Incremental: append to existing content
453
+ updatedMessage.content =
454
+ (updatedMessage.content || "") +
455
+ data.deltaContent;
456
+ }
457
+ }
458
+ }
459
+ return updatedMessage;
460
+ }
461
+ return msg;
462
+ }));
463
+ }
464
+ else {
465
+ // Fallback: Mark any streaming messages as completed (old behavior)
466
+ console.warn("⚠️ No messageId in completed event, falling back to marking all streaming messages as completed");
467
+ setMessages((prev) => prev.map((msg) => !msg.isCompleted && msg.messageType === "streaming"
468
+ ? {
469
+ ...msg,
470
+ isCompleted: true,
471
+ messageType: "completed",
472
+ }
473
+ : msg));
474
+ }
475
+ shouldCreateNewMessage.current = false;
476
+ break;
477
+ case "error":
478
+ console.error("❌ Stream error:", message.error);
479
+ setError(message.error || "Stream error occurred");
480
+ setIsWaitingForResponse(false);
481
+ shouldCreateNewMessage.current = false;
482
+ break;
483
+ default:
484
+ console.warn("❓ Unhandled message type:", {
485
+ type: message.type,
486
+ typeOf: typeof message.type,
487
+ length: message.type?.length,
488
+ charCodes: message.type
489
+ ?.split("")
490
+ .map((c) => c.charCodeAt(0)),
491
+ message: message,
492
+ });
493
+ break;
494
+ }
495
+ }, abortController.signal);
496
+ }
497
+ catch (err) {
498
+ if (!abortController.signal.aborted) {
499
+ console.error("Stream connection failed:", err);
500
+ setError("Failed to connect to agent stream");
501
+ }
502
+ }
503
+ finally {
504
+ setIsConnecting(false);
505
+ }
506
+ }, [agent?.id, handleContentChunk, handleToolCall, handleToolResult]);
507
+ // Load agent data and messages
508
+ const loadAgent = useCallback(async () => {
509
+ try {
510
+ if (agentStub.status === "new") {
511
+ console.log("✅ Setting up new agent");
512
+ setAgent({
513
+ ...agentStub,
514
+ messages: [],
515
+ status: "new",
516
+ updatedDate: new Date().toISOString(),
517
+ createdDate: new Date().toISOString(),
518
+ id: agentStub.id,
519
+ name: agentStub.name,
520
+ model: "",
521
+ currency: "USD",
522
+ itemId: "",
523
+ itemPath: "",
524
+ language: "",
525
+ version: 0,
526
+ profileId: "",
527
+ profileName: "",
528
+ totalTokensUsed: 0,
529
+ totalInputTokens: 0,
530
+ totalOutputTokens: 0,
531
+ totalCachedInputTokens: 0,
532
+ totalInputTokenCost: 0,
533
+ totalOutputTokenCost: 0,
534
+ totalCachedInputTokenCost: 0,
535
+ totalCost: 0,
536
+ messageCount: 0,
537
+ });
538
+ setMessages([]);
539
+ setError(null);
540
+ setIsLoading(false);
541
+ return;
542
+ }
543
+ setIsLoading(true);
544
+ setError(null);
545
+ const agentData = await getAgent(agentStub.id);
546
+ setAgent(agentData);
547
+ setMessages(agentData.messages || []);
548
+ // Connect to stream if agent is running (handle both string and numeric status)
549
+ const isRunning = agentData.status === "running" || agentData.status === 1;
550
+ if (isRunning) {
551
+ console.log("🚀 Agent is running, connecting to stream...", {
552
+ agentId: agentData.id,
553
+ status: agentData.status,
554
+ messageCount: agentData.messages?.length || 0,
555
+ });
556
+ // Use setTimeout to ensure state updates are processed first
557
+ setTimeout(async () => {
558
+ // Check if we're already connecting to avoid duplicate connections
559
+ if (abortControllerRef.current) {
560
+ console.log("⚠️ loadAgent: Already connected to stream, skipping duplicate connection");
561
+ return;
562
+ }
563
+ // Reset streaming state for reconnection
564
+ shouldCreateNewMessage.current = false;
565
+ // Use the existing connectToStream function with the loaded agent data
566
+ await connectToStream(agentData);
567
+ }, 100);
568
+ }
569
+ else {
570
+ console.log("⏸️ Agent is not running, status:", agentData.status, "(0=new, 1=running, 2=closed, 3=cancelled)");
571
+ }
572
+ }
573
+ catch (err) {
574
+ console.error("❌ Failed to load agent:", err);
575
+ // For new agents that don't exist yet, this is expected
576
+ if (err?.message?.includes("404") ||
577
+ err?.message?.includes("not found")) {
578
+ console.log("ℹ️ Agent does not exist, treating as new");
579
+ setAgent(undefined);
580
+ setMessages([]);
581
+ setError(null);
582
+ }
583
+ else {
584
+ console.error("❌ Load error:", err.message);
585
+ setError("Failed to load agent data");
586
+ }
587
+ }
588
+ finally {
589
+ setIsLoading(false);
590
+ }
591
+ }, [agentStub.id]);
592
+ // Initial load
593
+ useEffect(() => {
594
+ loadAgent();
595
+ }, [loadAgent]);
596
+ // Cleanup stream connection when component unmounts or agent changes
597
+ useEffect(() => {
598
+ return () => {
599
+ if (abortControllerRef.current) {
600
+ console.log("Cleaning up stream connection");
601
+ abortControllerRef.current.abort();
602
+ }
603
+ };
604
+ }, [agent?.id]);
605
+ // Auto-scroll when messages change (only if user hasn't manually scrolled up)
606
+ useEffect(() => {
607
+ if (shouldAutoScroll) {
608
+ scrollToBottom();
609
+ }
610
+ }, [messages, scrollToBottom, shouldAutoScroll]);
611
+ const handleSubmit = async () => {
612
+ if (!prompt.trim() || isSubmitting || !editContext)
613
+ return;
614
+ try {
615
+ setIsSubmitting(true);
616
+ setError(null);
617
+ const agentId = agent?.id;
618
+ if (!agentId)
619
+ return;
620
+ // Add user message to local state immediately for better UX
621
+ const userMessage = {
622
+ id: `user-${Date.now()}`,
623
+ agentId,
624
+ messageIndex: messages.length,
625
+ role: "user",
626
+ content: prompt.trim(),
627
+ name: "user",
628
+ messageType: "user",
629
+ isCompleted: true,
630
+ model: "",
631
+ tokensUsed: 0,
632
+ inputTokens: 0,
633
+ outputTokens: 0,
634
+ cachedInputTokens: 0,
635
+ inputTokenCost: 0,
636
+ outputTokenCost: 0,
637
+ cachedInputTokenCost: 0,
638
+ totalCost: 0,
639
+ currency: "USD",
640
+ createdDate: new Date().toISOString(),
641
+ };
642
+ setMessages((prev) => [...prev, userMessage]);
643
+ const request = {
644
+ agentId: agent.id,
645
+ message: prompt.trim(),
646
+ sessionId: editContext.sessionId,
647
+ profileId: "default", // TODO: Get from context or settings
648
+ itemid: editContext.currentItemDescriptor?.id || "",
649
+ language: editContext.currentItemDescriptor?.language || "en",
650
+ version: editContext.currentItemDescriptor?.version || 1,
651
+ };
652
+ console.log("Starting agent:", request);
653
+ // Set waiting state to show dancing dots immediately
654
+ setIsWaitingForResponse(true);
655
+ // Re-enable auto-scroll when user submits a new message
656
+ setShouldAutoScroll(true);
657
+ // No need to create a temporary message - the stream will create messages as needed
658
+ await startAgent(request);
659
+ // Save prompt to history
660
+ if (prompt.trim()) {
661
+ setPromptHistory((prev) => [
662
+ prompt.trim(),
663
+ ...prev.filter((p) => p !== prompt.trim()).slice(0, 9),
664
+ ]);
665
+ setCurrentHistoryIndex(-1);
666
+ }
667
+ setPrompt("");
668
+ // Connect to stream immediately to start receiving updates
669
+ await connectToStream();
670
+ }
671
+ catch (err) {
672
+ console.error("Failed to submit prompt:", err);
673
+ setError("Failed to submit prompt");
674
+ setIsWaitingForResponse(false);
675
+ // Remove the optimistically added user message on error
676
+ setMessages((prev) => prev.slice(0, -1));
677
+ }
678
+ finally {
679
+ setIsSubmitting(false);
680
+ }
681
+ };
682
+ const handleKeyPress = (e) => {
683
+ if (e.key === "Enter" && !e.ctrlKey && !e.metaKey) {
684
+ e.preventDefault();
685
+ handleSubmit();
686
+ }
687
+ if (e.key === "ArrowUp") {
688
+ // Only navigate history if prompt is empty or we're already navigating history
689
+ const canNavigateHistory = prompt.trim().length === 0 || currentHistoryIndex !== -1;
690
+ if (canNavigateHistory) {
691
+ e.preventDefault();
692
+ if (promptHistory.length > 0) {
693
+ const newIndex = currentHistoryIndex < promptHistory.length - 1
694
+ ? currentHistoryIndex + 1
695
+ : currentHistoryIndex;
696
+ setCurrentHistoryIndex(newIndex);
697
+ const historicalPrompt = promptHistory[newIndex];
698
+ if (textareaRef.current && historicalPrompt) {
699
+ setPrompt(historicalPrompt);
700
+ setTimeout(() => {
701
+ if (textareaRef.current) {
702
+ textareaRef.current.selectionStart = historicalPrompt.length;
703
+ textareaRef.current.selectionEnd = historicalPrompt.length;
704
+ }
705
+ }, 0);
706
+ }
707
+ }
708
+ }
709
+ }
710
+ if (e.key === "ArrowDown" && currentHistoryIndex >= 0) {
711
+ e.preventDefault();
712
+ const newIndex = currentHistoryIndex - 1;
713
+ setCurrentHistoryIndex(newIndex);
714
+ const historicalPrompt = newIndex >= 0 ? promptHistory[newIndex] || "" : "";
715
+ setPrompt(historicalPrompt);
716
+ if (textareaRef.current) {
717
+ setTimeout(() => {
718
+ if (textareaRef.current) {
719
+ textareaRef.current.selectionStart = historicalPrompt.length;
720
+ textareaRef.current.selectionEnd = historicalPrompt.length;
721
+ }
722
+ }, 0);
723
+ }
724
+ }
725
+ };
726
+ if (isLoading) {
727
+ return (_jsx("div", { className: "flex h-full items-center justify-center", children: _jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-500", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin", strokeWidth: 1 }), "Loading agent..."] }) }));
728
+ }
729
+ // Calculate total token usage for cost display
730
+ const totalTokens = calculateTotalTokens(messages);
731
+ return (_jsxs("div", { className: "flex h-full flex-col", children: [_jsxs("div", { ref: messagesContainerRef, className: "flex-1 overflow-y-auto", onScroll: handleScroll, children: [error && (_jsx("div", { className: "m-4 rounded-lg border-l-4 border-red-500 bg-red-50 p-3", children: _jsxs("div", { className: "flex items-start", children: [_jsx(AlertCircle, { className: "mt-0.5 h-5 w-5 text-red-400", strokeWidth: 1 }), _jsxs("div", { className: "ml-3", children: [_jsx("p", { className: "text-sm font-medium text-red-800", children: "Error" }), _jsx("p", { className: "mt-1 text-sm text-red-700", children: error })] })] }) })), messages.length === 0 && !error && (_jsx("div", { className: "flex h-full items-center justify-center p-8", children: _jsxs("div", { className: "text-center", children: [_jsx(Bot, { className: "mx-auto mb-4 h-12 w-12 text-gray-400", strokeWidth: 1 }), _jsx("h3", { className: "mb-2 text-lg font-medium text-gray-900", children: "Start a conversation" }), _jsx("p", { className: "mb-4 text-sm text-gray-500", children: "Send a message to begin working with your AI agent." }), _jsx("div", { className: "text-xs text-gray-400", children: "Your agent can help with content editing, research, and automation tasks." })] }) })), _jsxs("div", { className: "space-y-0 divide-y divide-gray-100 select-text", children: [groupConsecutiveMessages(messages).map((group, groupIndex) => {
732
+ if (group.type === "user" && group.messages[0]) {
733
+ // Render user message
734
+ return (_jsx(UserMessage, { message: group.messages[0] }, groupIndex));
735
+ }
736
+ else {
737
+ // Render bundled assistant messages
738
+ // Check if this group contains any streaming message
739
+ const hasStreamingMessage = group.messages.some((msg) => !msg.isCompleted && msg.messageType === "streaming");
740
+ const convertedMessages = convertAgentMessagesToAiFormat(group.messages);
741
+ return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isSubmitting && !isConnecting, editOperations: [], error: error || undefined }, groupIndex));
742
+ }
743
+ }), (isWaitingForResponse ||
744
+ groupConsecutiveMessages(messages).some((group) => group.type === "assistant-group" &&
745
+ group.messages.some((msg) => !msg.isCompleted && msg.messageType === "streaming"))) && _jsx(DancingDots, {})] }), _jsx("div", { ref: messagesEndRef })] }), totalTokens.totalCost > 0 && (_jsx("div", { className: "border-t border-gray-100 px-4 py-2", children: _jsx(AgentCostDisplay, { totalTokens: totalTokens, className: "flex justify-end" }) })), _jsx("div", { className: "border-t border-gray-200 p-4", children: _jsxs("div", { className: "flex items-stretch gap-2", children: [_jsx(Textarea, { ref: textareaRef, value: prompt, onChange: (e) => {
746
+ setPrompt(e.target.value);
747
+ // Reset history index when user starts typing
748
+ if (currentHistoryIndex !== -1) {
749
+ setCurrentHistoryIndex(-1);
750
+ }
751
+ }, onKeyDown: handleKeyPress, placeholder: "Type your message... (Enter to send, Ctrl+Enter for new line)", className: "resize-nones min-h-[80px] flex-1 text-xs", disabled: isSubmitting }), _jsx(Button, { onClick: handleSubmit, disabled: !prompt.trim() || isSubmitting, size: "sm", className: "self-end", children: isSubmitting ? (_jsx(Loader2, { className: "h-4 w-4 animate-spin", strokeWidth: 1 })) : (_jsx(Send, { className: "h-4 w-4", strokeWidth: 1 })) })] }) })] }));
752
+ }
753
+ //# sourceMappingURL=AgentTerminal.js.map