@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.
- package/dist/editor/Terminal.d.ts +6 -0
- package/dist/editor/Terminal.js +10 -4
- package/dist/editor/Terminal.js.map +1 -1
- package/dist/editor/ai/Agents.js +133 -13
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/AiTerminal.d.ts +2 -1
- package/dist/editor/ai/AiTerminal.js +86 -139
- package/dist/editor/ai/AiTerminal.js.map +1 -1
- package/dist/editor/media-selector/AiImageSearch.js +2 -1
- package/dist/editor/media-selector/AiImageSearch.js.map +1 -1
- package/dist/editor/media-selector/AiImageSearchPrompt.js +2 -1
- package/dist/editor/media-selector/AiImageSearchPrompt.js.map +1 -1
- package/dist/editor/page-editor-chrome/useInlineAICompletion.js +4 -2
- package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +59 -0
- package/dist/editor/services/agentService.js +26 -0
- package/dist/editor/services/agentService.js.map +1 -0
- package/dist/editor/services/aiService.d.ts +22 -4
- package/dist/editor/services/aiService.js +131 -20
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/editor/services/contextService.js +6 -4
- package/dist/editor/services/contextService.js.map +1 -1
- package/dist/editor/sidebar/SidebarView.js +2 -5
- package/dist/editor/sidebar/SidebarView.js.map +1 -1
- package/dist/editor/sidebar/ViewSelector.js +1 -1
- package/dist/editor/sidebar/ViewSelector.js.map +1 -1
- package/dist/editor/ui/SimpleIconButton.d.ts +2 -2
- package/dist/editor/ui/SimpleIconButton.js +5 -3
- package/dist/editor/ui/SimpleIconButton.js.map +1 -1
- package/dist/editor/utils/jsonCleaner.d.ts +8 -0
- package/dist/editor/utils/jsonCleaner.js +76 -0
- package/dist/editor/utils/jsonCleaner.js.map +1 -0
- package/dist/editor/views/CompareView.js +2 -2
- package/dist/editor/views/CompareView.js.map +1 -1
- package/dist/page-wizard/steps/ContentStep.js +7 -2
- package/dist/page-wizard/steps/ContentStep.js.map +1 -1
- package/dist/page-wizard/steps/FindItemsStep.js +4 -1
- package/dist/page-wizard/steps/FindItemsStep.js.map +1 -1
- package/dist/page-wizard/steps/ImagesStep.js +13 -7
- package/dist/page-wizard/steps/ImagesStep.js.map +1 -1
- package/dist/page-wizard/steps/LayoutStep.d.ts +1 -1
- package/dist/page-wizard/steps/LayoutStep.js +22 -20
- package/dist/page-wizard/steps/LayoutStep.js.map +1 -1
- package/dist/page-wizard/steps/MetaDataStep.js +8 -15
- package/dist/page-wizard/steps/MetaDataStep.js.map +1 -1
- package/dist/page-wizard/steps/SelectStep.js +9 -4
- package/dist/page-wizard/steps/SelectStep.js.map +1 -1
- package/dist/page-wizard/steps/StructureStep.js +3 -1
- package/dist/page-wizard/steps/StructureStep.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/styles.css +10 -11
- package/package.json +1 -1
- package/src/editor/Terminal.tsx +12 -3
- package/src/editor/ai/Agents.tsx +212 -16
- package/src/editor/ai/AiTerminal.tsx +113 -173
- package/src/editor/ai/GhostWriter.tsx_ +3 -3
- package/src/editor/media-selector/AiImageSearch.tsx +2 -2
- package/src/editor/media-selector/AiImageSearchPrompt.tsx +2 -2
- package/src/editor/page-editor-chrome/useInlineAICompletion.tsx +5 -5
- package/src/editor/services/agentService.ts +83 -0
- package/src/editor/services/aiService.ts +176 -33
- package/src/editor/services/contextService.ts +5 -6
- package/src/editor/sidebar/SidebarView.tsx +10 -6
- package/src/editor/sidebar/ViewSelector.tsx +2 -2
- package/src/editor/ui/SimpleIconButton.tsx +20 -14
- package/src/editor/utils/jsonCleaner.ts +92 -0
- package/src/editor/views/CompareView.tsx +2 -2
- package/src/page-wizard/steps/ContentStep.tsx +10 -9
- package/src/page-wizard/steps/FindItemsStep.tsx +7 -5
- package/src/page-wizard/steps/ImagesStep.tsx +16 -11
- package/src/page-wizard/steps/LayoutStep.tsx +24 -28
- package/src/page-wizard/steps/MetaDataStep.tsx +11 -21
- package/src/page-wizard/steps/SelectStep.tsx +11 -8
- package/src/page-wizard/steps/StructureStep.tsx +4 -5
- package/src/revision.ts +2 -2
package/src/editor/ai/Agents.tsx
CHANGED
|
@@ -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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
154
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
);
|