@alpaca-editor/core 1.0.4013 → 1.0.4014

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 (76) hide show
  1. package/dist/editor/Terminal.d.ts +6 -0
  2. package/dist/editor/Terminal.js +10 -4
  3. package/dist/editor/Terminal.js.map +1 -1
  4. package/dist/editor/ai/Agents.js +133 -13
  5. package/dist/editor/ai/Agents.js.map +1 -1
  6. package/dist/editor/ai/AiTerminal.d.ts +2 -1
  7. package/dist/editor/ai/AiTerminal.js +86 -139
  8. package/dist/editor/ai/AiTerminal.js.map +1 -1
  9. package/dist/editor/media-selector/AiImageSearch.js +2 -1
  10. package/dist/editor/media-selector/AiImageSearch.js.map +1 -1
  11. package/dist/editor/media-selector/AiImageSearchPrompt.js +2 -1
  12. package/dist/editor/media-selector/AiImageSearchPrompt.js.map +1 -1
  13. package/dist/editor/page-editor-chrome/useInlineAICompletion.js +4 -2
  14. package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -1
  15. package/dist/editor/services/agentService.d.ts +59 -0
  16. package/dist/editor/services/agentService.js +26 -0
  17. package/dist/editor/services/agentService.js.map +1 -0
  18. package/dist/editor/services/aiService.d.ts +22 -4
  19. package/dist/editor/services/aiService.js +131 -20
  20. package/dist/editor/services/aiService.js.map +1 -1
  21. package/dist/editor/services/contextService.js +6 -4
  22. package/dist/editor/services/contextService.js.map +1 -1
  23. package/dist/editor/sidebar/SidebarView.js +2 -5
  24. package/dist/editor/sidebar/SidebarView.js.map +1 -1
  25. package/dist/editor/sidebar/ViewSelector.js +1 -1
  26. package/dist/editor/sidebar/ViewSelector.js.map +1 -1
  27. package/dist/editor/ui/SimpleIconButton.d.ts +2 -2
  28. package/dist/editor/ui/SimpleIconButton.js +5 -3
  29. package/dist/editor/ui/SimpleIconButton.js.map +1 -1
  30. package/dist/editor/utils/jsonCleaner.d.ts +8 -0
  31. package/dist/editor/utils/jsonCleaner.js +76 -0
  32. package/dist/editor/utils/jsonCleaner.js.map +1 -0
  33. package/dist/editor/views/CompareView.js +2 -2
  34. package/dist/editor/views/CompareView.js.map +1 -1
  35. package/dist/page-wizard/steps/ContentStep.js +7 -2
  36. package/dist/page-wizard/steps/ContentStep.js.map +1 -1
  37. package/dist/page-wizard/steps/FindItemsStep.js +4 -1
  38. package/dist/page-wizard/steps/FindItemsStep.js.map +1 -1
  39. package/dist/page-wizard/steps/ImagesStep.js +13 -7
  40. package/dist/page-wizard/steps/ImagesStep.js.map +1 -1
  41. package/dist/page-wizard/steps/LayoutStep.d.ts +1 -1
  42. package/dist/page-wizard/steps/LayoutStep.js +22 -20
  43. package/dist/page-wizard/steps/LayoutStep.js.map +1 -1
  44. package/dist/page-wizard/steps/MetaDataStep.js +8 -15
  45. package/dist/page-wizard/steps/MetaDataStep.js.map +1 -1
  46. package/dist/page-wizard/steps/SelectStep.js +9 -4
  47. package/dist/page-wizard/steps/SelectStep.js.map +1 -1
  48. package/dist/page-wizard/steps/StructureStep.js +3 -1
  49. package/dist/page-wizard/steps/StructureStep.js.map +1 -1
  50. package/dist/revision.d.ts +2 -2
  51. package/dist/revision.js +2 -2
  52. package/dist/styles.css +10 -11
  53. package/package.json +1 -1
  54. package/src/editor/Terminal.tsx +12 -3
  55. package/src/editor/ai/Agents.tsx +212 -16
  56. package/src/editor/ai/AiTerminal.tsx +113 -173
  57. package/src/editor/ai/GhostWriter.tsx_ +3 -3
  58. package/src/editor/media-selector/AiImageSearch.tsx +2 -2
  59. package/src/editor/media-selector/AiImageSearchPrompt.tsx +2 -2
  60. package/src/editor/page-editor-chrome/useInlineAICompletion.tsx +5 -5
  61. package/src/editor/services/agentService.ts +83 -0
  62. package/src/editor/services/aiService.ts +176 -33
  63. package/src/editor/services/contextService.ts +5 -6
  64. package/src/editor/sidebar/SidebarView.tsx +10 -6
  65. package/src/editor/sidebar/ViewSelector.tsx +2 -2
  66. package/src/editor/ui/SimpleIconButton.tsx +20 -14
  67. package/src/editor/utils/jsonCleaner.ts +92 -0
  68. package/src/editor/views/CompareView.tsx +2 -2
  69. package/src/page-wizard/steps/ContentStep.tsx +10 -9
  70. package/src/page-wizard/steps/FindItemsStep.tsx +7 -5
  71. package/src/page-wizard/steps/ImagesStep.tsx +16 -11
  72. package/src/page-wizard/steps/LayoutStep.tsx +24 -28
  73. package/src/page-wizard/steps/MetaDataStep.tsx +11 -21
  74. package/src/page-wizard/steps/SelectStep.tsx +11 -8
  75. package/src/page-wizard/steps/StructureStep.tsx +4 -5
  76. package/src/revision.ts +2 -2
@@ -1,14 +1,42 @@
1
- import React, { useState, useRef } from "react";
1
+ import React, { useState, useRef, useEffect } from "react";
2
2
  import { AiTerminalOptions } from "./AiTerminal";
3
3
  import { EditorAiTerminal } from "./EditorAiTerminal";
4
4
  import { SimpleIconButton } from "../ui/SimpleIconButton";
5
- import { Plus, X } from "lucide-react";
5
+ import { Plus, X, History } from "lucide-react";
6
6
  import { cn } from "../../lib/utils";
7
+ import { getAgents, getAgent, getChatHistory, AgentChat, AgentChatMessage } from "../services/agentService";
8
+ import { Message } from "./AiTerminal";
9
+ import {
10
+ Popover,
11
+ PopoverContent,
12
+ PopoverTrigger,
13
+ } from "../../components/ui/popover";
14
+ import { Button } from "../../components/ui/button";
7
15
 
8
16
  interface TerminalInstance {
9
17
  id: string;
10
18
  title: string;
19
+ agentId?: string;
11
20
  options?: AiTerminalOptions;
21
+ messages?: Message[];
22
+ }
23
+
24
+ function convertAgentMessagesToTerminalMessages(agentMessages: AgentChatMessage[]): Message[] {
25
+ return agentMessages.map(msg => ({
26
+ id: msg.id,
27
+ content: msg.content,
28
+ name: msg.name,
29
+ role: msg.role,
30
+ tool_calls: msg.functionName ? [{
31
+ id: msg.toolCallId || msg.id,
32
+ displayName: msg.functionName,
33
+ function: {
34
+ name: msg.functionName,
35
+ arguments: msg.functionArguments || ""
36
+ }
37
+ }] : [],
38
+ tool_call_id: msg.toolCallId
39
+ }));
12
40
  }
13
41
 
14
42
  export function Agents({
@@ -18,16 +46,98 @@ export function Agents({
18
46
  closeButton?: React.ReactNode;
19
47
  initialOptions?: AiTerminalOptions;
20
48
  }) {
21
- const [terminals, setTerminals] = useState<TerminalInstance[]>([
22
- {
23
- id: "terminal-1",
24
- title: "Agent 1",
25
- options: initialOptions,
26
- },
27
- ]);
28
- const [activeTerminalId, setActiveTerminalId] =
29
- useState<string>("terminal-1");
30
- const nextTerminalNumber = useRef(2);
49
+ const [terminals, setTerminals] = useState<TerminalInstance[]>([]);
50
+ const [activeTerminalId, setActiveTerminalId] = useState<string | null>(null);
51
+ const [inactiveAgents, setInactiveAgents] = useState<AgentChat[]>([]);
52
+ const [historyPopoverOpen, setHistoryPopoverOpen] = useState(false);
53
+ const [loadingAgents, setLoadingAgents] = useState(true);
54
+ const nextTerminalNumber = useRef(1);
55
+
56
+ // Load agents from backend on mount
57
+ useEffect(() => {
58
+ loadAgentsFromBackend();
59
+ }, []);
60
+
61
+
62
+
63
+ const loadAgentsFromBackend = async () => {
64
+ try {
65
+ setLoadingAgents(true);
66
+
67
+ // Load active agents
68
+ const activeAgentsResult = await getAgents("active");
69
+
70
+ // Load inactive agents for history
71
+ const inactiveAgentsResult = await getChatHistory("completed", 20);
72
+
73
+ if (activeAgentsResult.type === "success" && activeAgentsResult.data) {
74
+ const activeAgents = activeAgentsResult.data;
75
+
76
+ // Create terminals for active agents
77
+ const activeTerminals: TerminalInstance[] = await Promise.all(
78
+ activeAgents.map(async (agent, index) => {
79
+ // Load messages for each active agent
80
+ const agentWithMessages = await loadAgentMessages(agent.id);
81
+
82
+ return {
83
+ id: `agent-${agent.id}`,
84
+ title: agent.name || `Agent ${index + 1}`,
85
+ agentId: agent.id,
86
+ options: initialOptions,
87
+ messages: agentWithMessages?.messages ? convertAgentMessagesToTerminalMessages(agentWithMessages.messages) : []
88
+ };
89
+ })
90
+ );
91
+
92
+ setTerminals(activeTerminals);
93
+
94
+ // Set the first active terminal as active, or create a new one if none exist
95
+ if (activeTerminals.length > 0) {
96
+ setActiveTerminalId(activeTerminals[0]!.id);
97
+ } else {
98
+ // Create a default terminal if no active agents
99
+ const defaultTerminal: TerminalInstance = {
100
+ id: `terminal-${nextTerminalNumber.current}`,
101
+ title: `Agent ${nextTerminalNumber.current}`,
102
+ options: initialOptions,
103
+ };
104
+ setTerminals([defaultTerminal]);
105
+ setActiveTerminalId(defaultTerminal.id);
106
+ nextTerminalNumber.current++;
107
+ }
108
+ }
109
+
110
+ if (inactiveAgentsResult.type === "success" && inactiveAgentsResult.data) {
111
+ setInactiveAgents(inactiveAgentsResult.data);
112
+ }
113
+
114
+ } catch (error) {
115
+ console.error("Failed to load agents:", error);
116
+ // Create a default terminal on error
117
+ const defaultTerminal: TerminalInstance = {
118
+ id: `terminal-${nextTerminalNumber.current}`,
119
+ title: `Agent ${nextTerminalNumber.current}`,
120
+ options: initialOptions,
121
+ };
122
+ setTerminals([defaultTerminal]);
123
+ setActiveTerminalId(defaultTerminal.id);
124
+ nextTerminalNumber.current++;
125
+ } finally {
126
+ setLoadingAgents(false);
127
+ }
128
+ };
129
+
130
+ const loadAgentMessages = async (agentId: string): Promise<AgentChat | null> => {
131
+ try {
132
+ const result = await getAgent(agentId);
133
+ if (result.type === "success" && result.data) {
134
+ return result.data;
135
+ }
136
+ } catch (error) {
137
+ console.error(`Failed to load messages for agent ${agentId}:`, error);
138
+ }
139
+ return null;
140
+ };
31
141
 
32
142
  const addTerminal = () => {
33
143
  const newTerminal: TerminalInstance = {
@@ -58,6 +168,42 @@ export function Agents({
58
168
  });
59
169
  };
60
170
 
171
+ const openAgentFromHistory = async (agent: AgentChat) => {
172
+ // Check if this agent is already open as a terminal
173
+ const existingTerminal = terminals.find(t => t.agentId === agent.id);
174
+
175
+ if (existingTerminal) {
176
+ // Switch to existing terminal
177
+ setActiveTerminalId(existingTerminal.id);
178
+ setHistoryPopoverOpen(false);
179
+ return;
180
+ }
181
+
182
+ // Load the agent with messages
183
+ const agentWithMessages = await loadAgentMessages(agent.id);
184
+
185
+ // Create new terminal for this agent
186
+ const newTerminal: TerminalInstance = {
187
+ id: `agent-${agent.id}`,
188
+ title: agent.name,
189
+ agentId: agent.id,
190
+ options: initialOptions,
191
+ messages: agentWithMessages?.messages ? convertAgentMessagesToTerminalMessages(agentWithMessages.messages) : []
192
+ };
193
+
194
+ setTerminals((prev) => [...prev, newTerminal]);
195
+ setActiveTerminalId(newTerminal.id);
196
+ setHistoryPopoverOpen(false);
197
+ };
198
+
199
+ if (loadingAgents) {
200
+ return (
201
+ <div className="flex h-full items-center justify-center">
202
+ <div className="text-sm text-gray-500">Loading agents...</div>
203
+ </div>
204
+ );
205
+ }
206
+
61
207
  return (
62
208
  <div className="flex h-full flex-col">
63
209
  {/* Tab Header */}
@@ -67,7 +213,7 @@ export function Agents({
67
213
  <div
68
214
  key={terminal.id}
69
215
  className={cn(
70
- "flex min-w-0 cursor-pointer items-center gap-1 border-r border-gray-200 px-3 py-2 text-sm",
216
+ "flex min-w-0 cursor-pointer items-center gap-1 border-r border-gray-200 px-3 py-2 text-xs",
71
217
  activeTerminalId === terminal.id
72
218
  ? "border-b-white bg-white"
73
219
  : "hover:bg-gray-100",
@@ -81,7 +227,7 @@ export function Agents({
81
227
  e.stopPropagation();
82
228
  closeTerminal(terminal.id);
83
229
  }}
84
- icon={<X className="size-3" />}
230
+ icon={<X className="size-2" />}
85
231
  label="Close"
86
232
  className="ml-1 opacity-60 hover:opacity-100"
87
233
  />
@@ -90,8 +236,52 @@ export function Agents({
90
236
  ))}
91
237
  </div>
92
238
 
239
+ {/* History Popover */}
240
+ <div className="flex items-center px-1">
241
+ <Popover open={historyPopoverOpen} onOpenChange={setHistoryPopoverOpen}>
242
+ <PopoverTrigger asChild>
243
+ <SimpleIconButton
244
+ onClick={() => {}}
245
+ icon={<History className="size-4" />}
246
+ label="Agent History"
247
+ className="text-gray-600 hover:text-gray-800"
248
+ />
249
+ </PopoverTrigger>
250
+ <PopoverContent className="w-64 p-0" align="end">
251
+ <div className="px-3 py-2 text-xs font-medium text-gray-500 border-b border-gray-100">
252
+ Agent History
253
+ </div>
254
+ <div className="max-h-80 overflow-y-auto">
255
+ {inactiveAgents.length === 0 ? (
256
+ <div className="px-3 py-2 text-xs text-gray-500">
257
+ No previous agents found
258
+ </div>
259
+ ) : (
260
+ inactiveAgents.map((agent) => (
261
+ <div
262
+ key={agent.id}
263
+ className="cursor-pointer px-3 py-2 text-xs hover:bg-gray-50 border-b border-gray-50"
264
+ onClick={() => openAgentFromHistory(agent)}
265
+ >
266
+ <div className="font-medium text-gray-900 truncate">
267
+ {agent.name}
268
+ </div>
269
+ <div className="text-gray-500 truncate">
270
+ {agent.profileName}
271
+ </div>
272
+ <div className="text-gray-400 text-xs">
273
+ {new Date(agent.updatedDate).toLocaleString()}
274
+ </div>
275
+ </div>
276
+ ))
277
+ )}
278
+ </div>
279
+ </PopoverContent>
280
+ </Popover>
281
+ </div>
282
+
93
283
  {/* Add Terminal Button */}
94
- <div className="flex items-center px-2">
284
+ <div className="flex items-center px-1">
95
285
  <SimpleIconButton
96
286
  onClick={addTerminal}
97
287
  icon={<Plus className="size-4" />}
@@ -116,7 +306,13 @@ export function Agents({
116
306
  activeTerminalId === terminal.id ? "block" : "hidden",
117
307
  )}
118
308
  >
119
- <EditorAiTerminal options={terminal.options} />
309
+ <EditorAiTerminal
310
+ options={{
311
+ ...terminal.options,
312
+ // Pass the pre-loaded messages to the terminal
313
+ initialMessages: terminal.messages
314
+ }}
315
+ />
120
316
  </div>
121
317
  ))}
122
318
  </div>
@@ -8,10 +8,13 @@ import { Terminal } from "../Terminal";
8
8
  import { useEditContext } from "../client/editContext";
9
9
  import { Dropdown } from "primereact/dropdown";
10
10
 
11
- import Cookies from "universal-cookie";
12
11
  import { WizardIcon } from "../ui/Icons";
13
12
  import { AiResponseMessage } from "./AiResponseMessage";
14
- import { AiProfile, loadAiProfiles } from "../services/aiService";
13
+ import {
14
+ AiProfile,
15
+ loadAiProfiles,
16
+ executePrompt,
17
+ } from "../services/aiService";
15
18
  import { EditOperation } from "../../types";
16
19
  import { SimpleIconButton } from "../ui/SimpleIconButton";
17
20
  import { Settings } from "lucide-react";
@@ -35,7 +38,7 @@ export type ToolCall = {
35
38
  };
36
39
 
37
40
  export type Message = {
38
- id: number;
41
+ id: string; // Changed from number to string for UUID support
39
42
  content: string;
40
43
  formattedContent?: string;
41
44
  name: string;
@@ -53,6 +56,7 @@ export type AiContext = {
53
56
  export type AiTerminalOptions = {
54
57
  initialPrompt?: string;
55
58
  hiddenSystemPrompt?: string;
59
+ initialMessages?: Message[];
56
60
  };
57
61
 
58
62
  export function AiTerminal({
@@ -71,7 +75,7 @@ export function AiTerminal({
71
75
 
72
76
  if (!editContext) return null;
73
77
 
74
- const [messages, setMessages] = useState<Message[]>([]);
78
+ const [messages, setMessages] = useState<Message[]>(options?.initialMessages || []);
75
79
  const [response, setResponse] = useState<Response>();
76
80
  const [model, setModel] = useState<string>();
77
81
  const [prompt, setPrompt] = useState("");
@@ -81,7 +85,7 @@ export function AiTerminal({
81
85
  const [agentId] = useState<string>(() => crypto.randomUUID());
82
86
  const selection = editContext.selection;
83
87
  const terminalRef = useRef<{ submit: () => void }>(null);
84
- const [responseMessages, setResponseMessages] = useState<Message[]>([]);
88
+ const [responseMessages, setResponseMessages] = useState<Message[]>(options?.initialMessages || []);
85
89
  const [showSettings, setShowSettings] = useState(false);
86
90
  const settingsRef = useRef<HTMLDivElement>(null);
87
91
 
@@ -120,6 +124,91 @@ export function AiTerminal({
120
124
  messagesRef.current = responseMessages;
121
125
  }, [responseMessages]);
122
126
 
127
+ // State for initial messages to pass to Terminal
128
+ type TerminalMessage = {text: React.ReactNode, type: "command" | "response"};
129
+ const [initialTerminalMessages, setInitialTerminalMessages] = useState<TerminalMessage[] | undefined>(undefined);
130
+
131
+ // Effect to set up initial messages for display
132
+ useEffect(() => {
133
+ if (options?.initialMessages && options.initialMessages.length > 0) {
134
+ console.log("AiTerminal: Loading initial messages", options.initialMessages);
135
+
136
+ // Format the initial messages for display
137
+ const formattedMessages = options.initialMessages.map((message) => {
138
+ const formattedContent = message.content
139
+ ?.trim()
140
+ ?.replaceAll("\n", "<br>")
141
+ ?.replace(/\*\*(.*?)\*\*/g, "<b>$1</b>") || "";
142
+
143
+ return {
144
+ ...message,
145
+ content: message.content || "",
146
+ formattedContent: formattedContent,
147
+ tool_calls: message.tool_calls || [],
148
+ };
149
+ });
150
+
151
+ console.log("AiTerminal: Formatted messages", formattedMessages);
152
+
153
+ // Update the internal message states
154
+ setMessages(formattedMessages);
155
+ setResponseMessages(formattedMessages);
156
+
157
+ // Create a response object for internal state
158
+ const initialResponse: Response = {
159
+ messages: formattedMessages,
160
+ editOperations: [],
161
+ numInputTokens: 0,
162
+ numOutputTokens: 0,
163
+ numCachedTokens: 0,
164
+ state: "loaded"
165
+ };
166
+
167
+ console.log("AiTerminal: Setting response", initialResponse);
168
+ setResponse(initialResponse);
169
+
170
+ // Create individual Terminal messages for each conversation message
171
+ const terminalMessages: TerminalMessage[] = [];
172
+
173
+ formattedMessages.forEach((message) => {
174
+ if (message.role === "user") {
175
+ // User messages appear as commands
176
+ terminalMessages.push({
177
+ type: "command",
178
+ text: message.content
179
+ });
180
+ } else if (message.role === "assistant") {
181
+ // Assistant messages appear as responses
182
+ terminalMessages.push({
183
+ type: "response",
184
+ text: (
185
+ <div
186
+ dangerouslySetInnerHTML={{
187
+ __html: message.formattedContent || message.content || "",
188
+ }}
189
+ />
190
+ )
191
+ });
192
+ }
193
+ // Add tool calls if present
194
+ if (message.tool_calls && message.tool_calls.length > 0) {
195
+ message.tool_calls.forEach((toolCall) => {
196
+ terminalMessages.push({
197
+ type: "response",
198
+ text: (
199
+ <div className="text-xs text-gray-400">
200
+ 🔧 {toolCall.displayName}
201
+ </div>
202
+ )
203
+ });
204
+ });
205
+ }
206
+ });
207
+
208
+ setInitialTerminalMessages(terminalMessages);
209
+ }
210
+ }, [options?.initialMessages]);
211
+
123
212
  // Handle click outside for settings popover
124
213
  useEffect(() => {
125
214
  function handleClickOutside(event: MouseEvent) {
@@ -150,13 +239,13 @@ export function AiTerminal({
150
239
  if (updatedMessages && Array.isArray(updatedMessages)) {
151
240
  const formattedMessages = updatedMessages.map((message) => {
152
241
  const formattedContent = message.content
153
- .trim()
154
- .replaceAll("\n", "<br>")
155
- ?.replace(/\*\*(.*?)\*\*/g, "<b>$1</b>");
242
+ ?.trim()
243
+ ?.replaceAll("\n", "<br>")
244
+ ?.replace(/\*\*(.*?)\*\*/g, "<b>$1</b>") || "";
156
245
 
157
246
  return {
158
247
  ...message,
159
- content: message.content,
248
+ content: message.content || "",
160
249
  formattedContent: formattedContent,
161
250
  tool_calls: message.tool_calls || [],
162
251
  };
@@ -192,7 +281,7 @@ export function AiTerminal({
192
281
  callback: (text: React.ReactNode, finished: boolean) => void,
193
282
  ) {
194
283
  const userMessage = {
195
- id: Date.now(), // Add unique id
284
+ id: crypto.randomUUID(), // Add unique id
196
285
  content: text,
197
286
  role: "user",
198
287
  name: "user",
@@ -214,7 +303,7 @@ export function AiTerminal({
214
303
  role: "system",
215
304
  name: "system",
216
305
  content: options.hiddenSystemPrompt,
217
- id: 0, // System message id
306
+ id: crypto.randomUUID(), // Use UUID instead of hardcoded 0
218
307
  toolCalls: [],
219
308
  },
220
309
  ]
@@ -223,72 +312,21 @@ export function AiTerminal({
223
312
  ];
224
313
 
225
314
  const response = await executePrompt(
226
- activeProfile.id,
227
315
  messages,
228
- selection,
229
- editContext!.sessionId,
230
- model,
231
- selectedText,
232
316
  context,
233
- agentId,
317
+ {
318
+ profileId: activeProfile.id,
319
+ selection,
320
+ selectedText,
321
+ model,
322
+ sessionId: editContext!.sessionId,
323
+ agentId,
324
+ addSelectedComponents: true,
325
+ },
326
+ undefined,
234
327
  (response: any) => {
235
328
  setResponse(response);
236
329
  handleResponse(response, callback, false);
237
- // if (response.editOperations.length) {
238
- // if (!editContext) return;
239
-
240
- // const newOps = response.editOperations.slice(lastOpIndex);
241
-
242
- // if (newOps.length === 0) return;
243
-
244
- // const isEditTextFieldOp = (op: EditOperation) => {
245
- // if (op.type !== "edit-field") return false;
246
- // const editFieldOp = op as EditFieldOperation;
247
- // return (
248
- // editFieldOp.fieldType &&
249
- // editFieldOp.fieldType.indexOf("text") !== -1
250
- // );
251
- // };
252
-
253
- // const isEditTextField = isEditTextFieldOp(newOps[newOps.length - 1]);
254
-
255
- // if (isEditTextField) lastOpIndex = response.editOperations.length - 1;
256
- // else lastOpIndex = response.editOperations.length;
257
-
258
- // newOps.forEach((op: EditOperation) => {
259
- // if (isEditTextFieldOp(op)) {
260
- // const editFieldOp = op as EditFieldOperation;
261
-
262
- // if (editFieldOp.itemId) {
263
- // const fieldDescriptor = {
264
- // item: {
265
- // ...editFieldOp.mainItem,
266
- // id: editFieldOp.itemId,
267
- // },
268
- // fieldId: editFieldOp.fieldId,
269
- // };
270
- // editContext.itemsRepository.updateFieldValue(
271
- // fieldDescriptor,
272
- // editFieldOp.user ?? { name: "unknown", ai: false },
273
- // false,
274
- // editFieldOp.value,
275
- // );
276
-
277
- // editContext.select([editFieldOp.itemId]);
278
- // editContext.setScrollIntoView(editFieldOp.itemId);
279
-
280
- // editContext?.setFocusedField(fieldDescriptor, false);
281
- // }
282
- // } else {
283
- // const addOp = op as AddComponentOperation;
284
- // if (op.type === "add-component" && addOp.componentId) {
285
- // const newComponentId = addOp.componentId;
286
- // editContext.select([newComponentId]);
287
- // editContext.setScrollIntoView(newComponentId);
288
- // }
289
- // }
290
- // });
291
- // }
292
330
  },
293
331
  );
294
332
 
@@ -320,7 +358,7 @@ export function AiTerminal({
320
358
 
321
359
  return (
322
360
  <div
323
- className="relative flex h-full flex-1 flex-col pl-1.5"
361
+ className="relative flex h-full flex-1 flex-col"
324
362
  data-testid="ai-terminal"
325
363
  >
326
364
  <div className="relative flex-1">
@@ -332,7 +370,9 @@ export function AiTerminal({
332
370
  setMessages([]);
333
371
  setResponseMessages([]);
334
372
  setResponse(undefined);
373
+ setInitialTerminalMessages(undefined);
335
374
  }}
375
+ initialMessages={initialTerminalMessages}
336
376
  infobar={
337
377
  response?.numInputTokens && (
338
378
  <div
@@ -448,103 +488,3 @@ export function AiTerminal({
448
488
  </div>
449
489
  );
450
490
  }
451
-
452
- async function executePrompt(
453
- profileId: string,
454
- messages: Message[],
455
- selection: string[],
456
- session: string | null,
457
- model: string | null,
458
- selectedText: string | null,
459
- context: AiContext,
460
- agentId: string,
461
- callback: (response: any) => void,
462
- ): Promise<Response | null> {
463
- const response = await fetch(context.endpoint, {
464
- method: "POST",
465
- body: JSON.stringify({
466
- ...context.promptData,
467
- profileId,
468
- messages,
469
- selection,
470
- addSelectedComponents: true,
471
- selectedText,
472
- model,
473
- sessionId: session,
474
- agentId,
475
- }),
476
- credentials: "include",
477
- headers: {
478
- "Content-Type": "application/json",
479
- Cookie: new Cookies().getAll(),
480
- },
481
- });
482
-
483
- if (!response.ok) {
484
- const text = await response.text();
485
- return {
486
- messages: [
487
- {
488
- content: "There was an error processing your request: " + text,
489
- id: 0,
490
- name: "assistant",
491
- role: "assistant",
492
- },
493
- ],
494
- editOperations: [],
495
- numInputTokens: 0,
496
- numOutputTokens: 0,
497
- numCachedTokens: 0,
498
- state: "error",
499
- };
500
- }
501
-
502
- if (!response?.body) return null;
503
-
504
- const reader = response.body.getReader();
505
- const decoder = new TextDecoder();
506
- let buffer = "";
507
-
508
- let result = null;
509
-
510
- while (true) {
511
- const { done, value } = await reader.read();
512
-
513
- if (done) {
514
- break;
515
- }
516
-
517
- buffer += decoder.decode(value, { stream: true }); // 'stream: true' ensures that any incomplete multi-byte characters aren't malformed.
518
-
519
- // Split the buffer by newline and keep the last partial line for the next iteration.
520
- const lines = buffer.split("\n");
521
-
522
- if (lines.length > 0) {
523
- buffer = lines.pop() || ""; // Incomplete line (if any) is kept for the next iteration.
524
-
525
- for (let line of lines) {
526
- if (line.trim() === "") continue; // Skip empty lines if any.
527
-
528
- try {
529
- const jsonData = JSON.parse(line);
530
- callback(jsonData);
531
- result = jsonData;
532
- } catch (e) {
533
- console.error("Error parsing line:" + line, e);
534
- }
535
- }
536
- }
537
- }
538
-
539
- // If there's any remaining content in the buffer after processing all chunks, try to process it.
540
- if (buffer.trim() !== "") {
541
- try {
542
- const jsonData = JSON.parse(buffer);
543
- result = jsonData;
544
- } catch (e) {
545
- console.error("Error parsing the final buffer content:", e);
546
- }
547
- }
548
-
549
- return result;
550
- }
@@ -267,16 +267,16 @@ export function GhostWriter() {
267
267
 
268
268
  role: "user",
269
269
  name: "user",
270
+ id: Date.now(),
270
271
  },
271
272
  ],
272
- editContext,
273
- createEditorAiContext,
273
+ { editContext, createAiContext: createEditorAiContext },
274
274
  {
275
275
  addAllContent: true,
276
276
  profile: "ghostwriter",
277
+ model: "gpt-4.1",
277
278
  },
278
279
  undefined,
279
- "gpt-4.1",
280
280
  handleAiResponse,
281
281
  );
282
282
 
@@ -61,10 +61,10 @@ export function AiImageSearch({
61
61
  "Reply with search terms only!",
62
62
  name: "user",
63
63
  role: "user",
64
+ id: crypto.randomUUID(), // Use proper UUID instead of Date.now()
64
65
  },
65
66
  ],
66
- editContext,
67
- createEditorAiContext,
67
+ { editContext, createAiContext: createEditorAiContext },
68
68
  { allowedFunctions: ["get-content"], addContextContent: true },
69
69
  { signal: abortController.signal },
70
70
  );