@alpaca-editor/core 1.0.4049 → 1.0.4052
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/components/ui/textarea.js +1 -1
- package/dist/components/ui/textarea.js.map +1 -1
- package/dist/editor/Terminal.js +3 -3
- package/dist/editor/Terminal.js.map +1 -1
- package/dist/editor/ai/AgentCostDisplay.js +2 -2
- package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
- package/dist/editor/ai/AgentHistory.d.ts +4 -4
- package/dist/editor/ai/AgentHistory.js +1 -1
- package/dist/editor/ai/AgentHistory.js.map +1 -1
- package/dist/editor/ai/AgentTerminal.d.ts +4 -0
- package/dist/editor/ai/AgentTerminal.js +753 -0
- package/dist/editor/ai/AgentTerminal.js.map +1 -0
- package/dist/editor/ai/Agents.d.ts +1 -3
- package/dist/editor/ai/Agents.js +213 -353
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/AiPromptPopover.js +2 -2
- package/dist/editor/ai/AiPromptPopover.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.d.ts +0 -1
- package/dist/editor/ai/AiResponseMessage.js +23 -143
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/AiTerminal.d.ts +5 -23
- package/dist/editor/ai/AiTerminal.js +81 -824
- package/dist/editor/ai/AiTerminal.js.map +1 -1
- package/dist/editor/ai/DancingDots.d.ts +1 -0
- package/dist/editor/ai/DancingDots.js +6 -0
- package/dist/editor/ai/DancingDots.js.map +1 -0
- package/dist/editor/ai/ToolCallDisplay.d.ts +37 -0
- package/dist/editor/ai/ToolCallDisplay.js +154 -0
- package/dist/editor/ai/ToolCallDisplay.js.map +1 -0
- package/dist/editor/client/EditorClient.js +5 -1
- package/dist/editor/client/EditorClient.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +23 -30
- package/dist/editor/services/agentService.js +62 -124
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/sidebar/GraphQL.js +1 -0
- package/dist/editor/sidebar/GraphQL.js.map +1 -1
- package/dist/editor/sidebar/ViewSelector.js +8 -6
- package/dist/editor/sidebar/ViewSelector.js.map +1 -1
- package/dist/editor/ui/Section.js +4 -3
- package/dist/editor/ui/Section.js.map +1 -1
- package/dist/editor/utils.d.ts +4 -0
- package/dist/editor/utils.js +23 -0
- package/dist/editor/utils.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/styles.css +18 -33
- package/package.json +1 -1
- package/src/components/ui/textarea.tsx +1 -1
- package/src/editor/Terminal.tsx +4 -4
- package/src/editor/ai/AgentCostDisplay.tsx +7 -11
- package/src/editor/ai/AgentHistory.tsx +7 -9
- package/src/editor/ai/AgentTerminal.tsx +1094 -0
- package/src/editor/ai/Agents.tsx +340 -477
- package/src/editor/ai/AiPromptPopover.tsx +2 -2
- package/src/editor/ai/AiResponseMessage.tsx +85 -366
- package/src/editor/ai/AiTerminal.tsx +142 -1213
- package/src/editor/ai/DancingDots.tsx +14 -0
- package/src/editor/ai/ToolCallDisplay.tsx +363 -0
- package/src/editor/client/EditorClient.tsx +6 -1
- package/src/editor/services/agentService.ts +89 -162
- package/src/editor/sidebar/GraphQL.tsx +1 -0
- package/src/editor/sidebar/ViewSelector.tsx +82 -57
- package/src/editor/ui/Section.tsx +4 -3
- package/src/editor/utils.ts +29 -0
- package/src/revision.ts +2 -2
- package/dist/editor/ai/EditorAiTerminal.d.ts +0 -6
- package/dist/editor/ai/EditorAiTerminal.js +0 -7
- package/dist/editor/ai/EditorAiTerminal.js.map +0 -1
- 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
|