@alpaca-editor/core 1.0.4033 → 1.0.4037

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 (187) hide show
  1. package/dist/components/index.d.ts +1 -0
  2. package/dist/components/index.js +1 -0
  3. package/dist/components/index.js.map +1 -1
  4. package/dist/{editor/menubar → components/ui}/LanguageSelector.d.ts +1 -1
  5. package/dist/{editor/menubar → components/ui}/LanguageSelector.js +8 -8
  6. package/dist/components/ui/LanguageSelector.js.map +1 -0
  7. package/dist/components/ui/button.d.ts +1 -1
  8. package/dist/components/ui/dropdown-menu.d.ts +1 -1
  9. package/dist/components/ui/dropdown-menu.js +2 -2
  10. package/dist/components/ui/dropdown-menu.js.map +1 -1
  11. package/dist/components/ui/sonner.js +3 -1
  12. package/dist/components/ui/sonner.js.map +1 -1
  13. package/dist/config/config.js +5 -5
  14. package/dist/config/config.js.map +1 -1
  15. package/dist/editor/ContentTree.d.ts +2 -1
  16. package/dist/editor/ContentTree.js +33 -9
  17. package/dist/editor/ContentTree.js.map +1 -1
  18. package/dist/editor/PictureEditor.js +2 -2
  19. package/dist/editor/PictureEditor.js.map +1 -1
  20. package/dist/editor/ScrollingContentTree.d.ts +2 -1
  21. package/dist/editor/ScrollingContentTree.js +2 -2
  22. package/dist/editor/ScrollingContentTree.js.map +1 -1
  23. package/dist/editor/Terminal.d.ts +2 -0
  24. package/dist/editor/Terminal.js +2 -2
  25. package/dist/editor/Terminal.js.map +1 -1
  26. package/dist/editor/ai/AgentHistory.d.ts +11 -0
  27. package/dist/editor/ai/AgentHistory.js +12 -0
  28. package/dist/editor/ai/AgentHistory.js.map +1 -0
  29. package/dist/editor/ai/Agents.js +187 -24
  30. package/dist/editor/ai/Agents.js.map +1 -1
  31. package/dist/editor/ai/AiResponseMessage.d.ts +2 -1
  32. package/dist/editor/ai/AiResponseMessage.js +6 -6
  33. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  34. package/dist/editor/ai/AiTerminal.d.ts +1 -0
  35. package/dist/editor/ai/AiTerminal.js +330 -43
  36. package/dist/editor/ai/AiTerminal.js.map +1 -1
  37. package/dist/editor/client/itemsRepository.js +19 -7
  38. package/dist/editor/client/itemsRepository.js.map +1 -1
  39. package/dist/editor/field-types/InternalLinkFieldEditor.js +48 -1
  40. package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
  41. package/dist/editor/field-types/richtext/contextMenuFactory.js +1 -1
  42. package/dist/editor/field-types/richtext/contextMenuFactory.js.map +1 -1
  43. package/dist/editor/menubar/ItemLanguageVersion.js +1 -1
  44. package/dist/editor/menubar/ItemLanguageVersion.js.map +1 -1
  45. package/dist/editor/menubar/PageSelector.js +1 -1
  46. package/dist/editor/menubar/PageSelector.js.map +1 -1
  47. package/dist/editor/page-editor-chrome/FieldActionIndicator.js +1 -1
  48. package/dist/editor/page-editor-chrome/FieldActionIndicator.js.map +1 -1
  49. package/dist/editor/page-editor-chrome/FieldEditedIndicator.js +2 -2
  50. package/dist/editor/page-editor-chrome/FieldEditedIndicator.js.map +1 -1
  51. package/dist/editor/page-editor-chrome/FrameMenu.js +2 -2
  52. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  53. package/dist/editor/page-editor-chrome/InlineEditor.js +9 -9
  54. package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
  55. package/dist/editor/page-editor-chrome/LockedFieldIndicator.js +2 -2
  56. package/dist/editor/page-editor-chrome/LockedFieldIndicator.js.map +1 -1
  57. package/dist/editor/page-editor-chrome/PageEditorChrome.js +1 -1
  58. package/dist/editor/page-editor-chrome/PageEditorChrome.js.map +1 -1
  59. package/dist/editor/page-editor-chrome/PictureEditorOverlay.js +1 -1
  60. package/dist/editor/page-editor-chrome/PictureEditorOverlay.js.map +1 -1
  61. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +1 -1
  62. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +2 -2
  63. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js.map +1 -1
  64. package/dist/editor/page-editor-chrome/useInlineAICompletion.js +12 -12
  65. package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -1
  66. package/dist/editor/page-viewer/EditorForm.js +1 -1
  67. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  68. package/dist/editor/page-viewer/MiniMap.js +10 -11
  69. package/dist/editor/page-viewer/MiniMap.js.map +1 -1
  70. package/dist/editor/page-viewer/PageViewer.js +2 -2
  71. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  72. package/dist/editor/page-viewer/PageViewerFrame.js +4 -4
  73. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  74. package/dist/editor/page-viewer/pageViewContext.d.ts +2 -2
  75. package/dist/editor/page-viewer/pageViewContext.js +11 -14
  76. package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
  77. package/dist/editor/services/agentService.d.ts +21 -1
  78. package/dist/editor/services/agentService.js +101 -0
  79. package/dist/editor/services/agentService.js.map +1 -1
  80. package/dist/editor/services/aiService.d.ts +1 -1
  81. package/dist/editor/services/aiService.js +1 -2
  82. package/dist/editor/services/aiService.js.map +1 -1
  83. package/dist/editor/sidebar/GraphQL.js +5 -6
  84. package/dist/editor/sidebar/GraphQL.js.map +1 -1
  85. package/dist/editor/sidebar/MainContentTree.d.ts +2 -1
  86. package/dist/editor/sidebar/MainContentTree.js +9 -5
  87. package/dist/editor/sidebar/MainContentTree.js.map +1 -1
  88. package/dist/editor/ui/Icons.d.ts +5 -0
  89. package/dist/editor/ui/Icons.js +3 -0
  90. package/dist/editor/ui/Icons.js.map +1 -1
  91. package/dist/editor/ui/ItemNameDialogNew.js +26 -14
  92. package/dist/editor/ui/ItemNameDialogNew.js.map +1 -1
  93. package/dist/editor/ui/PerfectTree.js +54 -2
  94. package/dist/editor/ui/PerfectTree.js.map +1 -1
  95. package/dist/editor/views/CompareView.js +3 -3
  96. package/dist/editor/views/CompareView.js.map +1 -1
  97. package/dist/editor/views/EditView.js +1 -1
  98. package/dist/editor/views/EditView.js.map +1 -1
  99. package/dist/editor/views/ItemEditor.js +1 -1
  100. package/dist/editor/views/ItemEditor.js.map +1 -1
  101. package/dist/index.d.ts +3 -4
  102. package/dist/index.js +3 -4
  103. package/dist/index.js.map +1 -1
  104. package/dist/page-wizard/steps/ContentStep.js +5 -5
  105. package/dist/page-wizard/steps/ContentStep.js.map +1 -1
  106. package/dist/page-wizard/steps/FindItemsStep.js +1 -1
  107. package/dist/page-wizard/steps/ImagesStep.js +1 -1
  108. package/dist/page-wizard/steps/ImagesStep.js.map +1 -1
  109. package/dist/page-wizard/steps/LayoutStep.js +1 -1
  110. package/dist/page-wizard/steps/LayoutStep.js.map +1 -1
  111. package/dist/page-wizard/steps/MetaDataStep.js +1 -1
  112. package/dist/page-wizard/steps/MetaDataStep.js.map +1 -1
  113. package/dist/page-wizard/steps/SelectStep.js +1 -1
  114. package/dist/page-wizard/steps/StructureStep.js +28 -11
  115. package/dist/page-wizard/steps/StructureStep.js.map +1 -1
  116. package/dist/page-wizard/steps/TranslateStep.d.ts +1 -0
  117. package/dist/page-wizard/steps/TranslateStep.js +67 -73
  118. package/dist/page-wizard/steps/TranslateStep.js.map +1 -1
  119. package/dist/revision.d.ts +2 -2
  120. package/dist/revision.js +2 -2
  121. package/dist/splash-screen/NewPage.js +1 -1
  122. package/dist/splash-screen/NewPage.js.map +1 -1
  123. package/dist/splash-screen/OpenPage.js +1 -1
  124. package/dist/splash-screen/OpenPage.js.map +1 -1
  125. package/dist/styles.css +71 -8
  126. package/package.json +1 -1
  127. package/src/components/index.ts +1 -0
  128. package/src/{editor/menubar → components/ui}/LanguageSelector.tsx +12 -12
  129. package/src/components/ui/dropdown-menu.tsx +3 -1
  130. package/src/components/ui/sonner.tsx +5 -1
  131. package/src/config/config.tsx +4 -3
  132. package/src/editor/ContentTree.tsx +41 -12
  133. package/src/editor/PictureEditor.tsx +2 -2
  134. package/src/editor/ScrollingContentTree.tsx +3 -0
  135. package/src/editor/Terminal.tsx +16 -7
  136. package/src/editor/ai/AgentHistory.tsx +85 -0
  137. package/src/editor/ai/Agents.tsx +256 -88
  138. package/src/editor/ai/AiResponseMessage.tsx +25 -11
  139. package/src/editor/ai/AiTerminal.tsx +571 -73
  140. package/src/editor/client/itemsRepository.ts +29 -12
  141. package/src/editor/field-types/InternalLinkFieldEditor.tsx +52 -1
  142. package/src/editor/field-types/richtext/components/SimpleRichTextEditor.css +64 -0
  143. package/src/editor/field-types/richtext/contextMenuFactory.tsx +7 -8
  144. package/src/editor/menubar/ItemLanguageVersion.tsx +1 -1
  145. package/src/editor/menubar/PageSelector.tsx +1 -0
  146. package/src/editor/page-editor-chrome/FieldActionIndicator.tsx +1 -1
  147. package/src/editor/page-editor-chrome/FieldEditedIndicator.tsx +3 -3
  148. package/src/editor/page-editor-chrome/FrameMenu.tsx +2 -2
  149. package/src/editor/page-editor-chrome/InlineEditor.tsx +9 -12
  150. package/src/editor/page-editor-chrome/LockedFieldIndicator.tsx +3 -3
  151. package/src/editor/page-editor-chrome/PageEditorChrome.tsx +3 -3
  152. package/src/editor/page-editor-chrome/PictureEditorOverlay.tsx +1 -1
  153. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +1 -1
  154. package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +2 -2
  155. package/src/editor/page-editor-chrome/useInlineAICompletion.tsx +12 -18
  156. package/src/editor/page-viewer/EditorForm.tsx +1 -1
  157. package/src/editor/page-viewer/MiniMap.tsx +10 -11
  158. package/src/editor/page-viewer/PageViewer.tsx +8 -3
  159. package/src/editor/page-viewer/PageViewerFrame.tsx +4 -4
  160. package/src/editor/page-viewer/pageViewContext.ts +71 -66
  161. package/src/editor/services/agentService.ts +129 -1
  162. package/src/editor/services/aiService.ts +2 -4
  163. package/src/editor/sidebar/GraphQL.tsx +16 -15
  164. package/src/editor/sidebar/MainContentTree.tsx +12 -4
  165. package/src/editor/ui/Icons.tsx +35 -0
  166. package/src/editor/ui/ItemNameDialogNew.tsx +29 -13
  167. package/src/editor/ui/PerfectTree.tsx +70 -4
  168. package/src/editor/views/CompareView.tsx +3 -3
  169. package/src/editor/views/EditView.tsx +1 -1
  170. package/src/editor/views/ItemEditor.tsx +1 -1
  171. package/src/index.ts +10 -4
  172. package/src/page-wizard/steps/ContentStep.tsx +5 -5
  173. package/src/page-wizard/steps/FindItemsStep.tsx +1 -1
  174. package/src/page-wizard/steps/ImagesStep.tsx +1 -1
  175. package/src/page-wizard/steps/LayoutStep.tsx +1 -1
  176. package/src/page-wizard/steps/MetaDataStep.tsx +1 -1
  177. package/src/page-wizard/steps/SelectStep.tsx +1 -1
  178. package/src/page-wizard/steps/StructureStep.tsx +41 -17
  179. package/src/page-wizard/steps/TranslateStep.tsx +326 -222
  180. package/src/revision.ts +2 -2
  181. package/src/splash-screen/NewPage.tsx +1 -0
  182. package/src/splash-screen/OpenPage.tsx +1 -0
  183. package/dist/components/SimpleLanguageSelector.d.ts +0 -10
  184. package/dist/components/SimpleLanguageSelector.js +0 -59
  185. package/dist/components/SimpleLanguageSelector.js.map +0 -1
  186. package/dist/editor/menubar/LanguageSelector.js.map +0 -1
  187. package/src/components/SimpleLanguageSelector.tsx +0 -113
@@ -6,7 +6,13 @@ import { TerminalService } from "primereact/terminalservice";
6
6
 
7
7
  import { Terminal } from "../Terminal";
8
8
  import { useEditContext } from "../client/editContext";
9
- import { Dropdown } from "primereact/dropdown";
9
+ import {
10
+ DropdownMenu,
11
+ DropdownMenuContent,
12
+ DropdownMenuItem,
13
+ DropdownMenuTrigger,
14
+ } from "../../components/ui/dropdown-menu";
15
+ import { ChevronDown } from "lucide-react";
10
16
 
11
17
  import { WizardIcon } from "../ui/Icons";
12
18
  import { AiResponseMessage } from "./AiResponseMessage";
@@ -20,14 +26,26 @@ import {
20
26
  getAgents,
21
27
  getChatHistory,
22
28
  cancelAgent,
29
+ checkAgentState,
30
+ convertAgentMessagesToTerminalFormat,
23
31
  AgentStreamMessageType,
24
32
  StartAgentRequest,
25
33
  AgentStreamMessage,
26
34
  } from "../services/agentService";
27
35
  import { EditOperation } from "../../types";
28
36
  import { SimpleIconButton } from "../ui/SimpleIconButton";
29
- import { Settings } from "lucide-react";
37
+ import { Settings, Square } from "lucide-react";
30
38
  import { AgentCostDisplay } from "./AgentCostDisplay";
39
+ import {
40
+ Tooltip,
41
+ TooltipContent,
42
+ TooltipTrigger,
43
+ } from "../../components/ui/tooltip";
44
+ import {
45
+ Popover,
46
+ PopoverContent,
47
+ PopoverTrigger,
48
+ } from "../../components/ui/popover";
31
49
 
32
50
  type Response = {
33
51
  messages: Message[];
@@ -37,6 +55,7 @@ type Response = {
37
55
  numCachedTokens: number;
38
56
  state: string;
39
57
  agentName?: string; // Updated agent name (only sent when the agent name has been updated)
58
+ error?: string; // Error message when agent execution fails
40
59
  // Cost information from backend
41
60
  totalCost?: number;
42
61
  totalInputTokenCost?: number;
@@ -137,6 +156,22 @@ export function AiTerminal({
137
156
  );
138
157
  const [showSettings, setShowSettings] = useState(false);
139
158
  const settingsRef = useRef<HTMLDivElement>(null);
159
+ const [isRunning, setIsRunning] = useState(false);
160
+ const abortControllerRef = useRef<AbortController | null>(null);
161
+ const [terminalError, setTerminalError] = useState<string | null>(null);
162
+
163
+ // Debug function - expose globally for testing
164
+ useEffect(() => {
165
+ (window as any).testAiTerminalError = (
166
+ errorMsg: string = "Test error message",
167
+ ) => {
168
+ console.log("Manually triggering error:", errorMsg);
169
+ setTerminalError(errorMsg);
170
+ };
171
+ return () => {
172
+ delete (window as any).testAiTerminalError;
173
+ };
174
+ }, []);
140
175
 
141
176
  useEffect(() => {
142
177
  if (options?.initialPrompt && !initialPromptExecuted && model) {
@@ -164,8 +199,271 @@ export function AiTerminal({
164
199
  fetchProfiles();
165
200
  }, [editContext?.currentItemDescriptor]);
166
201
 
202
+ // Agent reconnection logic
203
+ useEffect(() => {
204
+ async function checkAndReconnectAgent() {
205
+ if (!editContext || !agentId) return;
206
+
207
+ const context = createAiContext({ editContext });
208
+
209
+ try {
210
+ console.log("Checking agent state for reconnection:", agentId);
211
+ const agentState = await checkAgentState(agentId, context);
212
+
213
+ if (agentState.exists && agentState.agent) {
214
+ console.log(
215
+ "Found existing agent, restoring state:",
216
+ agentState.agent,
217
+ );
218
+
219
+ const agent = agentState.agent;
220
+
221
+ // Convert and set persisted messages if not already set via options
222
+ if (
223
+ agent.messages &&
224
+ (!options?.initialMessages || options.initialMessages.length === 0)
225
+ ) {
226
+ const terminalMessages = convertAgentMessagesToTerminalFormat(
227
+ agent.messages,
228
+ );
229
+ console.log("Restoring messages:", terminalMessages);
230
+ setMessages(terminalMessages);
231
+ setResponseMessages(terminalMessages);
232
+ }
233
+
234
+ // Set cost information if available
235
+ if (agent.totalCost !== undefined) {
236
+ setResponse(
237
+ (prev) =>
238
+ ({
239
+ ...prev,
240
+ totalCost: agent.totalCost,
241
+ totalInputTokenCost: agent.totalInputTokenCost,
242
+ totalOutputTokenCost: agent.totalOutputTokenCost,
243
+ totalCachedTokenCost: agent.totalCachedInputTokenCost,
244
+ totalInputTokens: agent.totalInputTokens,
245
+ totalOutputTokens: agent.totalOutputTokens,
246
+ totalCachedTokens: agent.totalCachedInputTokens,
247
+ }) as Response,
248
+ );
249
+ }
250
+
251
+ // If agent is still running, reconnect to the stream
252
+ if (agentState.isRunning) {
253
+ console.log("Agent is still running, reconnecting to stream");
254
+ await reconnectToRunningAgent(agentId, context);
255
+ } else {
256
+ console.log(
257
+ "Agent completed while disconnected, showing final state",
258
+ );
259
+ }
260
+ } else {
261
+ console.log("No existing agent found or agent doesn't exist");
262
+ }
263
+ } catch (error) {
264
+ console.error("Failed to check agent state:", error);
265
+ // Don't show error to user for reconnection failures - just continue as normal
266
+ }
267
+ }
268
+
269
+ // Only try to reconnect if we have an agentId and haven't already initialized with messages
270
+ if (agentId && editContext) {
271
+ checkAndReconnectAgent();
272
+ }
273
+ }, [agentId]); // Only run when agentId changes
274
+
275
+ // Function to reconnect to a running agent stream
276
+ async function reconnectToRunningAgent(agentId: string, context: AiContext) {
277
+ try {
278
+ const abortController = new AbortController();
279
+ abortControllerRef.current = abortController;
280
+ setIsRunning(true);
281
+
282
+ let accumulatedResponse: any = {
283
+ messages: [...messages],
284
+ editOperations: [],
285
+ numInputTokens: 0,
286
+ numOutputTokens: 0,
287
+ numCachedTokens: 0,
288
+ state: "running",
289
+ };
290
+ let completionProcessed = false;
291
+
292
+ console.log("Reconnecting to agent stream:", agentId);
293
+ await connectToAgentStream(
294
+ agentId,
295
+ context,
296
+ (streamMessage) => {
297
+ console.log(
298
+ "Reconnected stream message:",
299
+ streamMessage.type,
300
+ streamMessage,
301
+ );
302
+
303
+ // Use the same stream message handling logic as the original startAgent function
304
+ // (This is the same logic that was already in the component)
305
+ handleStreamMessage(
306
+ streamMessage,
307
+ accumulatedResponse,
308
+ completionProcessed,
309
+ context,
310
+ );
311
+ },
312
+ abortController.signal,
313
+ );
314
+ } catch (error) {
315
+ console.error("Failed to reconnect to agent stream:", error);
316
+ setIsRunning(false);
317
+ setTerminalError(
318
+ `Failed to reconnect to agent: ${error instanceof Error ? error.message : "Unknown error"}`,
319
+ );
320
+ }
321
+ }
322
+
323
+ // Extract stream message handling logic into a separate function for reuse
324
+ function handleStreamMessage(
325
+ streamMessage: AgentStreamMessage,
326
+ accumulatedResponse: any,
327
+ completionProcessed: boolean,
328
+ context: AiContext,
329
+ ) {
330
+ // Safety check for valid stream message
331
+ if (!streamMessage || !streamMessage.type) {
332
+ console.error("Invalid stream message received:", streamMessage);
333
+ return;
334
+ }
335
+
336
+ console.log(
337
+ "Processing stream message:",
338
+ streamMessage.type,
339
+ streamMessage,
340
+ );
341
+
342
+ // Handle different message types (this logic was already in the component)
343
+ switch (streamMessage.type) {
344
+ case AgentStreamMessageType.StatusUpdate:
345
+ break;
346
+
347
+ case AgentStreamMessageType.ContentChunk:
348
+ if (streamMessage.data && typeof streamMessage.data === "object") {
349
+ if (
350
+ streamMessage.data.isIncremental &&
351
+ streamMessage.data.deltaContent
352
+ ) {
353
+ // Handle incremental content updates
354
+ const updatedMessages = [...(accumulatedResponse.messages || [])];
355
+ let lastAssistantMessage = null;
356
+ let lastAssistantIndex = -1;
357
+
358
+ // Find the last assistant message
359
+ for (let i = updatedMessages.length - 1; i >= 0; i--) {
360
+ if (updatedMessages[i].role === "assistant") {
361
+ lastAssistantMessage = updatedMessages[i];
362
+ lastAssistantIndex = i;
363
+ break;
364
+ }
365
+ }
366
+
367
+ // If no assistant message exists, or the last one has tool calls, create a new one
368
+ if (
369
+ !lastAssistantMessage ||
370
+ (lastAssistantMessage.tool_calls &&
371
+ lastAssistantMessage.tool_calls.length > 0)
372
+ ) {
373
+ lastAssistantMessage = {
374
+ id: crypto.randomUUID(),
375
+ role: "assistant",
376
+ name: "assistant",
377
+ content: "",
378
+ tool_calls: [],
379
+ };
380
+ updatedMessages.push(lastAssistantMessage);
381
+ lastAssistantIndex = updatedMessages.length - 1;
382
+ }
383
+
384
+ // Append the delta content to build up the full message
385
+ const currentContent = lastAssistantMessage.content || "";
386
+ const newContent = currentContent + streamMessage.data.deltaContent;
387
+
388
+ // Update the message with accumulated content
389
+ updatedMessages[lastAssistantIndex] = {
390
+ ...lastAssistantMessage,
391
+ content: newContent,
392
+ };
393
+
394
+ // Update accumulated response with incremental content
395
+ accumulatedResponse = {
396
+ ...accumulatedResponse,
397
+ ...streamMessage.data,
398
+ messages: updatedMessages,
399
+ editOperations:
400
+ streamMessage.data.editOperations ||
401
+ accumulatedResponse.editOperations,
402
+ };
403
+ } else {
404
+ // Non-incremental update - use as provided
405
+ accumulatedResponse = {
406
+ ...accumulatedResponse,
407
+ ...streamMessage.data,
408
+ editOperations:
409
+ streamMessage.data.editOperations ||
410
+ accumulatedResponse.editOperations,
411
+ };
412
+ }
413
+
414
+ // Always trigger UI update for content chunks
415
+ handleResponse(accumulatedResponse, () => {}, false);
416
+ }
417
+ break;
418
+
419
+ case AgentStreamMessageType.ToolCall:
420
+ // Handle tool calls - simplified for reconnection
421
+ if (streamMessage.data && typeof streamMessage.data === "object") {
422
+ console.log(
423
+ "Tool call received during reconnection:",
424
+ streamMessage.data,
425
+ );
426
+ // For reconnection, we mainly need to update the UI
427
+ // The detailed tool call logic can be handled the same as in the original
428
+ }
429
+ break;
430
+
431
+ case AgentStreamMessageType.ToolResult:
432
+ // Handle tool results - simplified for reconnection
433
+ if (streamMessage.data && typeof streamMessage.data === "object") {
434
+ console.log(
435
+ "Tool result received during reconnection:",
436
+ streamMessage.data,
437
+ );
438
+ // For reconnection, we mainly need to update the UI
439
+ // The detailed tool result logic can be handled the same as in the original
440
+ }
441
+ break;
442
+
443
+ case AgentStreamMessageType.Completed:
444
+ if (!completionProcessed) {
445
+ completionProcessed = true;
446
+ console.log("Agent execution completed");
447
+ setIsRunning(false);
448
+ if (streamMessage.data) {
449
+ handleResponse(streamMessage.data, () => {}, true);
450
+ }
451
+ }
452
+ break;
453
+
454
+ case AgentStreamMessageType.Error:
455
+ console.error("Agent execution error:", streamMessage);
456
+ setIsRunning(false);
457
+ setTerminalError(streamMessage.error || "Agent execution failed");
458
+ break;
459
+
460
+ default:
461
+ console.warn("Unknown stream message type:", streamMessage.type);
462
+ }
463
+ }
464
+
167
465
  useEffect(() => {
168
- if (activeProfile?.defaultModel) setModel(activeProfile.defaultModel);
466
+ if (activeProfile?.defaultModelId) setModel(activeProfile.defaultModelId);
169
467
  }, [activeProfile]);
170
468
 
171
469
  const messagesRef = useRef(messages);
@@ -297,18 +595,38 @@ export function AiTerminal({
297
595
  // Handle click outside for settings popover
298
596
  useEffect(() => {
299
597
  function handleClickOutside(event: MouseEvent) {
300
- if (
301
- settingsRef.current &&
302
- !settingsRef.current.contains(event.target as Node)
303
- ) {
304
- setShowSettings(false);
598
+ const target = event.target as Element;
599
+
600
+ // Check if the click is inside the settings popover
601
+ if (settingsRef.current && settingsRef.current.contains(target)) {
602
+ return;
305
603
  }
604
+
605
+ // Check if the click is inside any dropdown menu content
606
+ // Radix UI dropdown menus can have various selectors
607
+ const isInDropdown = target.closest(
608
+ [
609
+ "[data-radix-popper-content-wrapper]",
610
+ '[data-slot="dropdown-menu-content"]',
611
+ "[data-radix-dropdown-menu-content]",
612
+ '[role="menu"]',
613
+ ".dropdown-menu", // fallback for any custom dropdown classes
614
+ ].join(", "),
615
+ );
616
+
617
+ if (isInDropdown) {
618
+ return;
619
+ }
620
+
621
+ // If we get here, it's a click outside both the settings and any dropdown
622
+ setShowSettings(false);
306
623
  }
307
624
 
308
625
  if (showSettings) {
309
- document.addEventListener("click", handleClickOutside);
626
+ // Use mousedown instead of click for better timing
627
+ document.addEventListener("mousedown", handleClickOutside);
310
628
  return () => {
311
- document.removeEventListener("click", handleClickOutside);
629
+ document.removeEventListener("mousedown", handleClickOutside);
312
630
  };
313
631
  }
314
632
  }, [showSettings]);
@@ -342,6 +660,7 @@ export function AiTerminal({
342
660
  messages={formattedMessages}
343
661
  editOperations={response.editOperations}
344
662
  finished={isFinished}
663
+ error={response.error}
345
664
  />,
346
665
  isFinished,
347
666
  );
@@ -352,6 +671,7 @@ export function AiTerminal({
352
671
  messages={messagesRef.current}
353
672
  editOperations={response.editOperations}
354
673
  finished={isFinished}
674
+ error={response.error}
355
675
  />,
356
676
  isFinished,
357
677
  );
@@ -416,10 +736,11 @@ export function AiTerminal({
416
736
  };
417
737
 
418
738
  const startResponse = await startAgent(startRequest, context);
419
- console.log("Agent started:", startResponse);
420
739
 
421
740
  // Step 2: Connect to the agent stream for real-time updates
422
741
  const abortController = new AbortController();
742
+ abortControllerRef.current = abortController;
743
+ setIsRunning(true);
423
744
  let accumulatedResponse: any = {
424
745
  messages: [...messages],
425
746
  editOperations: [],
@@ -442,6 +763,18 @@ export function AiTerminal({
442
763
  // new Date().toISOString(),
443
764
  // );
444
765
 
766
+ // Safety check for valid stream message
767
+ if (!streamMessage || !streamMessage.type) {
768
+ console.error("Invalid stream message received:", streamMessage);
769
+ return;
770
+ }
771
+
772
+ console.log(
773
+ "Processing stream message:",
774
+ streamMessage.type,
775
+ streamMessage,
776
+ );
777
+
445
778
  // Message types are now normalized in agentService.ts
446
779
  switch (streamMessage.type) {
447
780
  case AgentStreamMessageType.StatusUpdate:
@@ -449,7 +782,10 @@ export function AiTerminal({
449
782
  break;
450
783
 
451
784
  case AgentStreamMessageType.ContentChunk:
452
- if (streamMessage.data) {
785
+ if (
786
+ streamMessage.data &&
787
+ typeof streamMessage.data === "object"
788
+ ) {
453
789
  // Handle incremental content updates
454
790
  if (
455
791
  streamMessage.data.isIncremental &&
@@ -526,7 +862,10 @@ export function AiTerminal({
526
862
 
527
863
  case AgentStreamMessageType.ToolCall:
528
864
  // console.log("Tool call received:", streamMessage.data);
529
- if (streamMessage.data) {
865
+ if (
866
+ streamMessage.data &&
867
+ typeof streamMessage.data === "object"
868
+ ) {
530
869
  // Find or create the assistant message that contains this tool call
531
870
  const updatedMessages = [
532
871
  ...(accumulatedResponse.messages || []),
@@ -606,12 +945,16 @@ export function AiTerminal({
606
945
 
607
946
  case AgentStreamMessageType.ToolResult:
608
947
  console.log("Tool result received:", streamMessage.data);
609
- if (streamMessage.data) {
948
+ if (
949
+ streamMessage.data &&
950
+ typeof streamMessage.data === "object"
951
+ ) {
610
952
  // Find the assistant message and tool call to update with the result
611
953
  const updatedMessages = [
612
954
  ...(accumulatedResponse.messages || []),
613
955
  ];
614
956
 
957
+ let messageUpdated = false;
615
958
  // Find the assistant message with the matching tool call ID
616
959
  for (let i = updatedMessages.length - 1; i >= 0; i--) {
617
960
  if (
@@ -639,6 +982,7 @@ export function AiTerminal({
639
982
  ...updatedMessages[i],
640
983
  tool_calls: updatedToolCalls,
641
984
  };
985
+ messageUpdated = true;
642
986
  break;
643
987
  }
644
988
  }
@@ -656,12 +1000,15 @@ export function AiTerminal({
656
1000
  break;
657
1001
 
658
1002
  case AgentStreamMessageType.Completed:
659
- if (streamMessage.data) {
1003
+ if (
1004
+ streamMessage.data &&
1005
+ typeof streamMessage.data === "object"
1006
+ ) {
660
1007
  // Final completion data
661
1008
  const finalResponse = {
662
1009
  messages:
663
- streamMessage.data.messages ||
664
- accumulatedResponse.messages,
1010
+ accumulatedResponse.messages ||
1011
+ streamMessage.data.messages,
665
1012
  editOperations:
666
1013
  streamMessage.data.editOperations ||
667
1014
  accumulatedResponse.editOperations,
@@ -676,6 +1023,8 @@ export function AiTerminal({
676
1023
  accumulatedResponse.numCachedTokens,
677
1024
  state: "completed",
678
1025
  agentName: streamMessage.data.agentName,
1026
+ error:
1027
+ streamMessage.data.error || accumulatedResponse.error,
679
1028
  // Add cost fields from backend (try both tokenUsage object and direct properties)
680
1029
  totalCost:
681
1030
  streamMessage.data.totalCost ||
@@ -731,6 +1080,7 @@ export function AiTerminal({
731
1080
  messages={formattedMessages}
732
1081
  editOperations={finalResponse.editOperations}
733
1082
  finished={true}
1083
+ error={finalResponse.error}
734
1084
  />,
735
1085
  true,
736
1086
  );
@@ -746,15 +1096,41 @@ export function AiTerminal({
746
1096
  break;
747
1097
 
748
1098
  case AgentStreamMessageType.Error:
749
- console.error("Agent execution error:", streamMessage.error);
1099
+ const errorMessage =
1100
+ streamMessage.error || "Unknown agent error";
1101
+ console.error("Agent execution error:", errorMessage);
1102
+ console.log("Displaying error in terminal:", errorMessage);
1103
+ setTerminalError(errorMessage);
1104
+
750
1105
  const errorResponse = {
751
1106
  ...accumulatedResponse,
752
1107
  state: "error",
753
- error: streamMessage.error,
1108
+ error: errorMessage,
754
1109
  };
755
1110
  setResponse(errorResponse);
1111
+
1112
+ // Display the error in the terminal
1113
+ console.log("Calling handleResponse with error");
756
1114
  handleResponse(errorResponse, callback, true);
757
- break;
1115
+
1116
+ // Also try using TerminalService directly as fallback
1117
+ console.log("Also trying TerminalService.emit");
1118
+ TerminalService.emit("response", {
1119
+ text: (
1120
+ <AiResponseMessage
1121
+ messages={[]}
1122
+ editOperations={[]}
1123
+ finished={true}
1124
+ error={errorMessage}
1125
+ />
1126
+ ),
1127
+ type: "response",
1128
+ });
1129
+
1130
+ console.log("Error callback completed");
1131
+ setIsRunning(false);
1132
+ abortControllerRef.current = null;
1133
+ return; // Exit the stream processing
758
1134
 
759
1135
  default:
760
1136
  console.log(
@@ -777,24 +1153,53 @@ export function AiTerminal({
777
1153
  setResponse(finalResponse);
778
1154
  handleResponse(finalResponse, callback, true);
779
1155
  }
1156
+ setIsRunning(false);
1157
+ abortControllerRef.current = null;
780
1158
  } catch (streamError) {
781
1159
  console.error("Stream connection error:", streamError);
782
- const errorMessage =
783
- streamError instanceof Error
784
- ? streamError.message
785
- : "Unknown stream error";
786
- const errorResponse = {
787
- ...accumulatedResponse,
788
- state: "error",
789
- error: errorMessage,
790
- };
791
- setResponse(errorResponse);
792
- handleResponse(errorResponse, callback, true);
1160
+ const isAbort =
1161
+ (streamError as any)?.name === "AbortError" ||
1162
+ (streamError as any)?.message?.toLowerCase?.().includes("abort");
1163
+ if (isAbort) {
1164
+ const cancelledResponse = {
1165
+ ...accumulatedResponse,
1166
+ state: "cancelled",
1167
+ };
1168
+ setResponse(cancelledResponse);
1169
+ handleResponse(cancelledResponse, callback, true);
1170
+ } else {
1171
+ const errorMessage =
1172
+ streamError instanceof Error
1173
+ ? streamError.message
1174
+ : "Unknown stream error";
1175
+
1176
+ const errorResponse = {
1177
+ ...accumulatedResponse,
1178
+ state: "error",
1179
+ error: errorMessage,
1180
+ };
1181
+ setResponse(errorResponse);
1182
+
1183
+ // Display the error in the terminal
1184
+ callback(
1185
+ <AiResponseMessage
1186
+ messages={accumulatedResponse.messages || []}
1187
+ editOperations={accumulatedResponse.editOperations || []}
1188
+ finished={true}
1189
+ error={errorMessage}
1190
+ />,
1191
+ true,
1192
+ );
1193
+ }
1194
+ setIsRunning(false);
1195
+ abortControllerRef.current = null;
793
1196
  }
794
1197
  } catch (error) {
795
1198
  console.error("Error starting agent:", error);
796
1199
  const errorMessage =
797
1200
  error instanceof Error ? error.message : "Unknown error";
1201
+
1202
+ // Display error in the terminal
798
1203
  const errorResponse = {
799
1204
  messages: [...messages],
800
1205
  editOperations: [],
@@ -805,10 +1210,36 @@ export function AiTerminal({
805
1210
  error: errorMessage,
806
1211
  };
807
1212
  setResponse(errorResponse);
808
- handleResponse(errorResponse, callback, true);
1213
+
1214
+ // Show the error in the terminal
1215
+ callback(
1216
+ <AiResponseMessage
1217
+ messages={[]}
1218
+ editOperations={[]}
1219
+ finished={true}
1220
+ error={errorMessage}
1221
+ />,
1222
+ true,
1223
+ );
1224
+
1225
+ setIsRunning(false);
1226
+ abortControllerRef.current = null;
809
1227
  }
810
1228
  }
811
1229
 
1230
+ // Stop button handler to cancel running agent and abort stream
1231
+ const handleStop = async () => {
1232
+ try {
1233
+ if (!isRunning) return;
1234
+ const context = createAiContext({ editContext });
1235
+ await cancelAgent(agentId, context);
1236
+ } catch (e) {
1237
+ // Ignore cancel errors; still abort client-side stream
1238
+ } finally {
1239
+ abortControllerRef.current?.abort();
1240
+ }
1241
+ };
1242
+
812
1243
  useEffect(() => {
813
1244
  TerminalService.on("command", commandHandler);
814
1245
 
@@ -832,6 +1263,7 @@ export function AiTerminal({
832
1263
  setResponseMessages([]);
833
1264
  setResponse(undefined);
834
1265
  setInitialTerminalMessages(undefined);
1266
+ setTerminalError(null); // Clear any terminal errors
835
1267
  // Generate a new agentId when clearing the terminal to start fresh
836
1268
  setAgentId(crypto.randomUUID());
837
1269
  }}
@@ -873,16 +1305,51 @@ export function AiTerminal({
873
1305
  }
874
1306
  prompt={prompt}
875
1307
  setPrompt={setPrompt}
1308
+ submitOverride={
1309
+ isRunning ? (
1310
+ <SimpleIconButton
1311
+ icon={<Square size={16} strokeWidth={1} />}
1312
+ label="Stop"
1313
+ onClick={handleStop}
1314
+ disabled={!isRunning}
1315
+ />
1316
+ ) : undefined
1317
+ }
876
1318
  statusbar=<div className="flex flex-1 items-center justify-between gap-1">
877
- <a
878
- className="ml-1 flex cursor-pointer items-center gap-1 text-xs"
879
- onClick={() => {
880
- setShowPredefined(!showPredefined);
881
- }}
882
- >
883
- <WizardIcon className="h-5 w-5" />
884
- Predefined prompts
885
- </a>
1319
+ <Popover open={showPredefined} onOpenChange={setShowPredefined}>
1320
+ <Tooltip delayDuration={500}>
1321
+ <TooltipTrigger asChild>
1322
+ <PopoverTrigger asChild>
1323
+ <a className="ml-1 flex cursor-pointer items-center gap-1 text-xs">
1324
+ <WizardIcon className="text-theme-secondary h-5 w-5" />
1325
+ </a>
1326
+ </PopoverTrigger>
1327
+ </TooltipTrigger>
1328
+ <TooltipContent>Predefined prompts</TooltipContent>
1329
+ </Tooltip>
1330
+ <PopoverContent className="w-96 p-2" align="start" side="top">
1331
+ {activeProfile?.prompts?.length ? (
1332
+ <div className="flex max-h-64 flex-col gap-1 overflow-y-auto">
1333
+ {activeProfile.prompts.map((p, index) => (
1334
+ <div
1335
+ key={index}
1336
+ className="mb-1 cursor-pointer rounded-lg border border-gray-200 p-1.5 text-xs text-gray-700 hover:bg-gray-50"
1337
+ onClick={() => {
1338
+ setPrompt(p.prompt);
1339
+ setShowPredefined(false);
1340
+ }}
1341
+ >
1342
+ {p.title}
1343
+ </div>
1344
+ ))}
1345
+ </div>
1346
+ ) : (
1347
+ <div className="text-xs text-gray-500">
1348
+ No predefined prompts available
1349
+ </div>
1350
+ )}
1351
+ </PopoverContent>
1352
+ </Popover>
886
1353
  {editContext.selection?.length > 0 && (
887
1354
  <div className="mr-2 flex items-center text-xs text-red-400">
888
1355
  {editContext.selection.length} items selected
@@ -895,23 +1362,6 @@ export function AiTerminal({
895
1362
  />
896
1363
  </div>
897
1364
  )}
898
- {showPredefined && (
899
- <div className="absolute right-0 bottom-8 left-0 flex flex-col gap-1 overflow-y-auto bg-white p-3 pb-1 text-sm">
900
- {activeProfile &&
901
- activeProfile.prompts.map((p, index) => (
902
- <div
903
- key={index}
904
- className="mb-1 cursor-pointer rounded-lg border border-gray-200 p-1.5 text-xs text-gray-700"
905
- onClick={() => {
906
- setPrompt(p.prompt);
907
- setShowPredefined(false);
908
- }}
909
- >
910
- {p.title}
911
- </div>
912
- ))}
913
- </div>
914
- )}
915
1365
  </div>
916
1366
  toolbar=<div className="flex items-stretch gap-1">
917
1367
  <div className="relative" ref={settingsRef}>
@@ -926,27 +1376,49 @@ export function AiTerminal({
926
1376
  <label className="text-xs font-medium text-gray-700">
927
1377
  Profile
928
1378
  </label>
929
- <Dropdown
930
- className="text-sm"
931
- value={activeProfile}
932
- onChange={(e) => setActiveProfile(e.value)}
933
- optionLabel="name"
934
- options={profiles}
935
- />
1379
+ <DropdownMenu>
1380
+ <DropdownMenuTrigger className="flex w-full items-center justify-between rounded-md border border-gray-300 bg-white px-3 py-2 text-xs hover:bg-gray-50 focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none">
1381
+ <span>{activeProfile?.name || "Select profile"}</span>
1382
+ <ChevronDown size={16} strokeWidth={1} />
1383
+ </DropdownMenuTrigger>
1384
+ <DropdownMenuContent className="min-w-[200px]">
1385
+ {profiles.map((profile) => (
1386
+ <DropdownMenuItem
1387
+ key={profile.id}
1388
+ onClick={() => setActiveProfile(profile)}
1389
+ className="cursor-pointer"
1390
+ >
1391
+ {profile.name}
1392
+ </DropdownMenuItem>
1393
+ ))}
1394
+ </DropdownMenuContent>
1395
+ </DropdownMenu>
936
1396
  </div>
937
1397
  {activeProfile && (
938
1398
  <div className="flex flex-col gap-1">
939
1399
  <label className="text-xs font-medium text-gray-700">
940
1400
  Model
941
1401
  </label>
942
- <Dropdown
943
- className="text-sm"
944
- value={model}
945
- onChange={(e) => setModel(e.value)}
946
- options={activeProfile.models}
947
- optionLabel="name"
948
- optionValue="id"
949
- />
1402
+ <DropdownMenu>
1403
+ <DropdownMenuTrigger className="flex w-full items-center justify-between rounded-md border border-gray-300 bg-white px-3 py-2 text-xs hover:bg-gray-50 focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none">
1404
+ <span>
1405
+ {activeProfile.models.find((m) => m.id === model)
1406
+ ?.name || "Select model"}
1407
+ </span>
1408
+ <ChevronDown size={16} strokeWidth={1} />
1409
+ </DropdownMenuTrigger>
1410
+ <DropdownMenuContent className="min-w-[200px]">
1411
+ {activeProfile.models.map((modelOption) => (
1412
+ <DropdownMenuItem
1413
+ key={modelOption.id}
1414
+ onClick={() => setModel(modelOption.id)}
1415
+ className="cursor-pointer"
1416
+ >
1417
+ {modelOption.name}
1418
+ </DropdownMenuItem>
1419
+ ))}
1420
+ </DropdownMenuContent>
1421
+ </DropdownMenu>
950
1422
  </div>
951
1423
  )}
952
1424
  </div>
@@ -959,6 +1431,32 @@ export function AiTerminal({
959
1431
  }}
960
1432
  />
961
1433
  </div>
1434
+ {/* Display terminal errors prominently */}
1435
+ {terminalError && (
1436
+ <div className="bg-opacity-95 absolute inset-0 z-10 flex items-center justify-center bg-white">
1437
+ <div className="mx-4 max-w-md rounded-lg border-l-4 border-red-500 bg-red-50 p-4 shadow-md">
1438
+ <div className="flex items-start">
1439
+ <div className="flex-shrink-0">
1440
+ <div className="h-5 w-5 text-red-400">⚠️</div>
1441
+ </div>
1442
+ <div className="ml-3 flex-1">
1443
+ <h3 className="text-sm font-medium text-red-800">
1444
+ Agent Error
1445
+ </h3>
1446
+ <p className="mt-1 text-sm text-red-700">{terminalError}</p>
1447
+ <div className="mt-3">
1448
+ <button
1449
+ onClick={() => setTerminalError(null)}
1450
+ className="rounded bg-red-100 px-3 py-1 text-xs font-medium text-red-800 hover:bg-red-200"
1451
+ >
1452
+ Dismiss
1453
+ </button>
1454
+ </div>
1455
+ </div>
1456
+ </div>
1457
+ </div>
1458
+ </div>
1459
+ )}
962
1460
  {activeProfile?.errorMessage && (
963
1461
  <div className="absolute inset-0 grid items-center justify-center p-2 text-sm text-red-500">
964
1462
  {activeProfile?.errorMessage}