@alpaca-editor/core 1.0.4049 → 1.0.4053
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ui/textarea.js +1 -1
- package/dist/components/ui/textarea.js.map +1 -1
- package/dist/editor/Terminal.js +3 -3
- package/dist/editor/Terminal.js.map +1 -1
- package/dist/editor/ai/AgentCostDisplay.js +2 -2
- package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
- package/dist/editor/ai/AgentHistory.d.ts +4 -4
- package/dist/editor/ai/AgentHistory.js +1 -1
- package/dist/editor/ai/AgentHistory.js.map +1 -1
- package/dist/editor/ai/AgentTerminal.d.ts +4 -0
- package/dist/editor/ai/AgentTerminal.js +753 -0
- package/dist/editor/ai/AgentTerminal.js.map +1 -0
- package/dist/editor/ai/Agents.d.ts +1 -3
- package/dist/editor/ai/Agents.js +213 -353
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/AiPromptPopover.js +2 -2
- package/dist/editor/ai/AiPromptPopover.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.d.ts +0 -1
- package/dist/editor/ai/AiResponseMessage.js +23 -143
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/AiTerminal.d.ts +5 -23
- package/dist/editor/ai/AiTerminal.js +81 -824
- package/dist/editor/ai/AiTerminal.js.map +1 -1
- package/dist/editor/ai/DancingDots.d.ts +1 -0
- package/dist/editor/ai/DancingDots.js +6 -0
- package/dist/editor/ai/DancingDots.js.map +1 -0
- package/dist/editor/ai/ToolCallDisplay.d.ts +37 -0
- package/dist/editor/ai/ToolCallDisplay.js +154 -0
- package/dist/editor/ai/ToolCallDisplay.js.map +1 -0
- package/dist/editor/client/EditorClient.js +5 -1
- package/dist/editor/client/EditorClient.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +23 -30
- package/dist/editor/services/agentService.js +62 -124
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/sidebar/GraphQL.js +1 -0
- package/dist/editor/sidebar/GraphQL.js.map +1 -1
- package/dist/editor/sidebar/ViewSelector.js +8 -6
- package/dist/editor/sidebar/ViewSelector.js.map +1 -1
- package/dist/editor/ui/Section.js +4 -3
- package/dist/editor/ui/Section.js.map +1 -1
- package/dist/editor/utils.d.ts +4 -0
- package/dist/editor/utils.js +23 -0
- package/dist/editor/utils.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/styles.css +18 -33
- package/package.json +1 -1
- package/src/components/ui/textarea.tsx +1 -1
- package/src/editor/Terminal.tsx +4 -4
- package/src/editor/ai/AgentCostDisplay.tsx +7 -11
- package/src/editor/ai/AgentHistory.tsx +7 -9
- package/src/editor/ai/AgentTerminal.tsx +1094 -0
- package/src/editor/ai/Agents.tsx +340 -477
- package/src/editor/ai/AiPromptPopover.tsx +2 -2
- package/src/editor/ai/AiResponseMessage.tsx +85 -366
- package/src/editor/ai/AiTerminal.tsx +142 -1213
- package/src/editor/ai/DancingDots.tsx +14 -0
- package/src/editor/ai/ToolCallDisplay.tsx +363 -0
- package/src/editor/client/EditorClient.tsx +6 -1
- package/src/editor/services/agentService.ts +89 -162
- package/src/editor/sidebar/GraphQL.tsx +1 -0
- package/src/editor/sidebar/ViewSelector.tsx +82 -57
- package/src/editor/ui/Section.tsx +4 -3
- package/src/editor/utils.ts +29 -0
- package/src/revision.ts +2 -2
- package/dist/editor/ai/EditorAiTerminal.d.ts +0 -6
- package/dist/editor/ai/EditorAiTerminal.js +0 -7
- package/dist/editor/ai/EditorAiTerminal.js.map +0 -1
- package/src/editor/ai/EditorAiTerminal.tsx +0 -23
package/src/editor/ai/Agents.tsx
CHANGED
|
@@ -1,481 +1,320 @@
|
|
|
1
1
|
import React, { useState, useRef, useEffect } from "react";
|
|
2
|
-
|
|
3
|
-
import { EditorAiTerminal } from "./EditorAiTerminal";
|
|
4
|
-
import { AgentHistory } from "./AgentHistory";
|
|
2
|
+
|
|
5
3
|
import { SimpleIconButton } from "../ui/SimpleIconButton";
|
|
6
|
-
import { Plus, X,
|
|
4
|
+
import { Plus, X, History, MoreVertical, Trash } from "lucide-react";
|
|
7
5
|
import { cn } from "../../lib/utils";
|
|
6
|
+
|
|
8
7
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
Popover,
|
|
9
|
+
PopoverContent,
|
|
10
|
+
PopoverTrigger,
|
|
11
|
+
} from "../../components/ui/popover";
|
|
12
|
+
import {
|
|
13
|
+
Agent,
|
|
14
|
+
getActiveAgents,
|
|
15
|
+
getClosedAgents,
|
|
16
|
+
closeAgent as closeAgentService,
|
|
13
17
|
deleteAgent,
|
|
14
|
-
AgentChat,
|
|
15
|
-
AgentChatMessage,
|
|
16
18
|
} from "../services/agentService";
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
DropdownMenu,
|
|
20
|
-
DropdownMenuContent,
|
|
21
|
-
DropdownMenuItem,
|
|
22
|
-
DropdownMenuTrigger,
|
|
23
|
-
} from "../../components/ui/dropdown-menu";
|
|
24
|
-
|
|
19
|
+
import { AgentTerminal } from "./AgentTerminal";
|
|
25
20
|
import { useEditContext } from "../client/editContext";
|
|
26
|
-
import { AiContext } from "./AiTerminal";
|
|
27
|
-
|
|
28
|
-
interface TerminalInstance {
|
|
29
|
-
id: string;
|
|
30
|
-
title: string;
|
|
31
|
-
agentId?: string;
|
|
32
|
-
options?: AiTerminalOptions;
|
|
33
|
-
messages?: Message[];
|
|
34
|
-
}
|
|
35
21
|
|
|
36
|
-
function convertAgentMessagesToTerminalMessages(
|
|
37
|
-
|
|
38
|
-
): Message[] {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
22
|
+
// function convertAgentMessagesToTerminalMessages(
|
|
23
|
+
// agentMessages: AgentChatMessage[],
|
|
24
|
+
// ): 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
|
+
// ? [
|
|
32
|
+
// {
|
|
33
|
+
// id: msg.toolCallId || msg.id,
|
|
34
|
+
// displayName: msg.functionName,
|
|
35
|
+
// function: {
|
|
36
|
+
// name: msg.functionName,
|
|
37
|
+
// arguments: msg.functionArguments || "",
|
|
38
|
+
// },
|
|
39
|
+
// },
|
|
40
|
+
// ]
|
|
41
|
+
// : [],
|
|
42
|
+
// tool_call_id: msg.toolCallId,
|
|
43
|
+
// }));
|
|
44
|
+
// }
|
|
45
|
+
|
|
46
|
+
const ACTIVE_AGENT_STORAGE_KEY = "editor.activeAgentId";
|
|
47
|
+
|
|
48
|
+
export function Agents({ closeButton }: { closeButton?: React.ReactNode }) {
|
|
49
|
+
const [agents, setAgents] = useState<Agent[]>([]);
|
|
50
|
+
const [activeAgentId, setActiveAgentId] = useState<string | null>(null);
|
|
51
|
+
const [historyPopoverOpen, setHistoryPopoverOpen] = useState(false);
|
|
52
|
+
const [menuPopoverOpen, setMenuPopoverOpen] = useState(false);
|
|
53
|
+
const [loadingAgents, setLoadingAgents] = useState(false);
|
|
54
|
+
const [inactiveAgents, setInactiveAgents] = useState<Agent[]>([]);
|
|
62
55
|
|
|
63
|
-
|
|
64
|
-
// Ensure the date string is treated as UTC if it doesn't have timezone info
|
|
65
|
-
const normalizedDate =
|
|
66
|
-
dateString.includes("Z") ||
|
|
67
|
-
dateString.includes("+") ||
|
|
68
|
-
dateString.includes("-", 19)
|
|
69
|
-
? dateString
|
|
70
|
-
: dateString + "Z";
|
|
56
|
+
const editContext = useEditContext();
|
|
71
57
|
|
|
72
|
-
|
|
73
|
-
|
|
58
|
+
// Helper function to get the most recently updated agent
|
|
59
|
+
const getMostRecentAgent = (agentList: Agent[]): Agent | null => {
|
|
60
|
+
if (agentList.length === 0) return null;
|
|
61
|
+
return agentList.reduce((mostRecent, current) => {
|
|
62
|
+
return new Date(current.updatedDate) > new Date(mostRecent.updatedDate)
|
|
63
|
+
? current
|
|
64
|
+
: mostRecent;
|
|
65
|
+
});
|
|
66
|
+
};
|
|
74
67
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
closeButton?: React.ReactNode;
|
|
80
|
-
initialOptions?: AiTerminalOptions;
|
|
81
|
-
}) {
|
|
82
|
-
const [terminals, setTerminals] = useState<TerminalInstance[]>([]);
|
|
83
|
-
const [activeTerminalId, setActiveTerminalId] = useState<string | null>(null);
|
|
84
|
-
const [closedAgents, setClosedAgents] = useState<AgentChat[]>([]);
|
|
85
|
-
const [historyPopoverOpen, setHistoryPopoverOpen] = useState(false);
|
|
86
|
-
const [loadingAgents, setLoadingAgents] = useState(true);
|
|
68
|
+
// Helper function to set active agent and persist to localStorage
|
|
69
|
+
const setActiveAgentIdWithStorage = (agentId: string | null) => {
|
|
70
|
+
console.log("setActiveAgentIdWithStorage", agentId);
|
|
71
|
+
setActiveAgentId(agentId);
|
|
87
72
|
|
|
88
|
-
|
|
89
|
-
|
|
73
|
+
if (agentId) {
|
|
74
|
+
localStorage.setItem(ACTIVE_AGENT_STORAGE_KEY, agentId);
|
|
75
|
+
} else {
|
|
76
|
+
localStorage.removeItem(ACTIVE_AGENT_STORAGE_KEY);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
90
79
|
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
80
|
+
// Initialize with a default agent if none exist
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (agents.length === 0) {
|
|
83
|
+
const defaultAgent: Agent = {
|
|
84
|
+
status: "new",
|
|
85
|
+
id: crypto.randomUUID(),
|
|
86
|
+
name: `New Agent`,
|
|
87
|
+
updatedDate: new Date().toISOString(),
|
|
88
|
+
userId: "",
|
|
89
|
+
};
|
|
90
|
+
setAgents([defaultAgent]);
|
|
91
|
+
setActiveAgentId(defaultAgent.id);
|
|
92
|
+
}
|
|
93
|
+
}, [agents.length]);
|
|
99
94
|
|
|
100
95
|
// Load agents from backend on mount
|
|
101
96
|
useEffect(() => {
|
|
102
97
|
loadAgentsFromBackend();
|
|
103
98
|
}, []);
|
|
104
99
|
|
|
105
|
-
//
|
|
100
|
+
// Subscribe to websocket messages for agent-started events
|
|
106
101
|
useEffect(() => {
|
|
107
|
-
if (!editContext) return;
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
(message) => {
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
102
|
+
if (!editContext?.addSocketMessageListener) return;
|
|
103
|
+
|
|
104
|
+
const unsubscribe = editContext.addSocketMessageListener(
|
|
105
|
+
(message: { type: string; payload: any }) => {
|
|
106
|
+
if (message.type === "agent-started") {
|
|
107
|
+
const { agentId, agentName } = message.payload;
|
|
108
|
+
|
|
109
|
+
if (!agentId || !agentName) {
|
|
110
|
+
console.warn(
|
|
111
|
+
"Invalid agent-started message payload:",
|
|
112
|
+
message.payload,
|
|
113
|
+
);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
setAgents((prevAgents) => {
|
|
118
|
+
// Check if agent already exists
|
|
119
|
+
const existingAgentIndex = prevAgents.findIndex(
|
|
120
|
+
(agent) => agent.id === agentId,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
if (existingAgentIndex !== -1) {
|
|
124
|
+
// Update existing agent name
|
|
125
|
+
const updatedAgents = [...prevAgents];
|
|
126
|
+
const existingAgent = updatedAgents[existingAgentIndex]!;
|
|
127
|
+
updatedAgents[existingAgentIndex] = {
|
|
128
|
+
...existingAgent,
|
|
129
|
+
name: agentName,
|
|
130
|
+
status: "running" as const,
|
|
131
|
+
updatedDate: new Date().toISOString(),
|
|
132
|
+
};
|
|
133
|
+
return updatedAgents;
|
|
134
|
+
} else {
|
|
135
|
+
// Add new agent to the array
|
|
136
|
+
const newAgent: Agent = {
|
|
137
|
+
id: agentId,
|
|
138
|
+
name: agentName,
|
|
139
|
+
status: "running" as const,
|
|
140
|
+
userId: "", // Will be populated from backend if needed
|
|
141
|
+
updatedDate: new Date().toISOString(),
|
|
142
|
+
};
|
|
143
|
+
return [...prevAgents, newAgent];
|
|
144
|
+
}
|
|
145
|
+
});
|
|
117
146
|
}
|
|
118
147
|
},
|
|
119
148
|
);
|
|
120
149
|
|
|
121
|
-
return
|
|
122
|
-
}, [editContext]);
|
|
150
|
+
return unsubscribe;
|
|
151
|
+
}, [editContext?.addSocketMessageListener]);
|
|
123
152
|
|
|
124
153
|
const loadAgentsFromBackend = async () => {
|
|
125
154
|
try {
|
|
126
155
|
setLoadingAgents(true);
|
|
127
|
-
const context = createAgentContext();
|
|
128
156
|
|
|
129
|
-
// Load
|
|
130
|
-
const activeAgentsResult = await
|
|
157
|
+
// Load active agents
|
|
158
|
+
const activeAgentsResult = await getActiveAgents();
|
|
131
159
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
//
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
id: `agent-${agent.id}`,
|
|
146
|
-
title: agent.name || "New Agent",
|
|
147
|
-
agentId: agent.id,
|
|
148
|
-
options: {
|
|
149
|
-
...initialOptions,
|
|
150
|
-
// Pass cost information from loaded agent
|
|
151
|
-
totalCost: agentWithMessages?.totalCost,
|
|
152
|
-
totalInputTokenCost: agentWithMessages?.totalInputTokenCost,
|
|
153
|
-
totalOutputTokenCost: agentWithMessages?.totalOutputTokenCost,
|
|
154
|
-
totalCachedTokenCost:
|
|
155
|
-
agentWithMessages?.totalCachedInputTokenCost,
|
|
156
|
-
totalInputTokens: agentWithMessages?.totalInputTokens,
|
|
157
|
-
totalOutputTokens: agentWithMessages?.totalOutputTokens,
|
|
158
|
-
totalCachedTokens: agentWithMessages?.totalCachedInputTokens,
|
|
159
|
-
},
|
|
160
|
-
messages: agentWithMessages?.messages
|
|
161
|
-
? convertAgentMessagesToTerminalMessages(
|
|
162
|
-
agentWithMessages.messages,
|
|
163
|
-
)
|
|
164
|
-
: [],
|
|
165
|
-
};
|
|
166
|
-
}),
|
|
160
|
+
setAgents(activeAgentsResult);
|
|
161
|
+
|
|
162
|
+
// Determine which agent to select as active
|
|
163
|
+
let selectedAgentId: string | null = null;
|
|
164
|
+
|
|
165
|
+
if (activeAgentsResult.length > 0) {
|
|
166
|
+
// Try to restore the previously active agent from localStorage
|
|
167
|
+
const storedAgentId = localStorage.getItem(ACTIVE_AGENT_STORAGE_KEY);
|
|
168
|
+
|
|
169
|
+
const storedAgent = activeAgentsResult.find(
|
|
170
|
+
(agent) => agent.id === storedAgentId,
|
|
167
171
|
);
|
|
168
172
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
setTerminals(activeTerminals);
|
|
172
|
-
setActiveTerminalId(activeTerminals[0]!.id);
|
|
173
|
+
if (storedAgent) {
|
|
174
|
+
selectedAgentId = storedAgent.id;
|
|
173
175
|
} else {
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
options: initialOptions,
|
|
179
|
-
};
|
|
180
|
-
setTerminals([defaultTerminal]);
|
|
181
|
-
setActiveTerminalId(defaultTerminal.id);
|
|
182
|
-
nextTerminalNumber.current++;
|
|
176
|
+
// Fall back to the most recently updated agent
|
|
177
|
+
console.log("get most recent agent", activeAgentsResult);
|
|
178
|
+
const mostRecentAgent = getMostRecentAgent(activeAgentsResult);
|
|
179
|
+
selectedAgentId = mostRecentAgent?.id || null;
|
|
183
180
|
}
|
|
184
181
|
}
|
|
185
182
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
183
|
+
console.log("set selectedAgentId", selectedAgentId);
|
|
184
|
+
|
|
185
|
+
setActiveAgentIdWithStorage(selectedAgentId);
|
|
186
|
+
|
|
187
|
+
// Load closed agents for history
|
|
188
|
+
const closedAgentsResult = await getClosedAgents();
|
|
189
|
+
|
|
190
|
+
setInactiveAgents(closedAgentsResult);
|
|
189
191
|
} catch (error) {
|
|
190
192
|
console.error("Failed to load agents:", error);
|
|
191
|
-
// Create a default terminal on error
|
|
192
|
-
const defaultTerminal: TerminalInstance = {
|
|
193
|
-
id: `terminal-${nextTerminalNumber.current}`,
|
|
194
|
-
title: `Agent ${nextTerminalNumber.current}`,
|
|
195
|
-
options: initialOptions,
|
|
196
|
-
};
|
|
197
|
-
setTerminals([defaultTerminal]);
|
|
198
|
-
setActiveTerminalId(defaultTerminal.id);
|
|
199
|
-
nextTerminalNumber.current++;
|
|
200
193
|
} finally {
|
|
201
194
|
setLoadingAgents(false);
|
|
202
195
|
}
|
|
203
196
|
};
|
|
204
197
|
|
|
205
|
-
const loadAgentMessages = async (
|
|
206
|
-
|
|
207
|
-
): Promise<AgentChat | null> => {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
id:
|
|
223
|
-
|
|
224
|
-
|
|
198
|
+
// const loadAgentMessages = async (
|
|
199
|
+
// agentId: string,
|
|
200
|
+
// ): Promise<AgentChat | null> => {
|
|
201
|
+
// try {
|
|
202
|
+
// const result = await getAgent(agentId);
|
|
203
|
+
// if (result.type === "success" && result.data) {
|
|
204
|
+
// return result.data;
|
|
205
|
+
// }
|
|
206
|
+
// } catch (error) {
|
|
207
|
+
// console.error(`Failed to load messages for agent ${agentId}:`, error);
|
|
208
|
+
// }
|
|
209
|
+
// return null;
|
|
210
|
+
// };
|
|
211
|
+
|
|
212
|
+
const addAgent = () => {
|
|
213
|
+
const newAgent: Agent = {
|
|
214
|
+
status: "new",
|
|
215
|
+
id: crypto.randomUUID(),
|
|
216
|
+
name: `New Agent`,
|
|
217
|
+
updatedDate: new Date().toISOString(),
|
|
218
|
+
userId: "",
|
|
225
219
|
};
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
nextTerminalNumber.current++;
|
|
220
|
+
setAgents((prev) => [...prev, newAgent]);
|
|
221
|
+
setActiveAgentIdWithStorage(newAgent.id);
|
|
229
222
|
};
|
|
230
223
|
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
setActiveTerminalId(newTerminal.id);
|
|
239
|
-
nextTerminalNumber.current++;
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
const closeTerminal = async (terminalId: string) => {
|
|
243
|
-
const terminal = terminals.find((t) => t.id === terminalId);
|
|
244
|
-
if (!terminal) return;
|
|
245
|
-
|
|
246
|
-
// If this terminal has an associated agent, show confirmation
|
|
247
|
-
if (terminal.agentId) {
|
|
248
|
-
editContext?.confirm({
|
|
249
|
-
header: "Close Agent",
|
|
250
|
-
message:
|
|
251
|
-
"Are you sure you want to close this agent? This will abort any running execution and mark the agent as closed.",
|
|
252
|
-
acceptLabel: "Close Agent",
|
|
253
|
-
rejectLabel: "Cancel",
|
|
254
|
-
accept: () => performCloseTerminal(terminalId),
|
|
255
|
-
reject: () => {}, // Do nothing on reject
|
|
256
|
-
});
|
|
257
|
-
return;
|
|
224
|
+
const closeAgent = async (agentId: string) => {
|
|
225
|
+
try {
|
|
226
|
+
// Permanently close the agent in the backend
|
|
227
|
+
await closeAgentService(agentId);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.error("Failed to close agent:", error);
|
|
230
|
+
// Continue with UI cleanup even if backend call fails
|
|
258
231
|
}
|
|
259
232
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
const performCloseTerminal = async (terminalId: string) => {
|
|
265
|
-
const terminal = terminals.find((t) => t.id === terminalId);
|
|
266
|
-
|
|
267
|
-
try {
|
|
268
|
-
// If this terminal has an associated agent, close it in the backend
|
|
269
|
-
if (terminal?.agentId) {
|
|
270
|
-
const context = createAgentContext();
|
|
271
|
-
await closeAgent(terminal.agentId, context);
|
|
272
|
-
}
|
|
233
|
+
setAgents((prev) => {
|
|
234
|
+
const filtered = prev.filter((a) => a.id !== agentId);
|
|
273
235
|
|
|
274
|
-
//
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (filtered.length > 0) {
|
|
282
|
-
setActiveTerminalId(filtered[0]!.id);
|
|
283
|
-
} else {
|
|
284
|
-
// Create a new terminal if this was the last one
|
|
285
|
-
const newTerminal: TerminalInstance = {
|
|
286
|
-
id: `terminal-${nextTerminalNumber.current}`,
|
|
287
|
-
title: "New Agent",
|
|
288
|
-
options: initialOptions,
|
|
289
|
-
};
|
|
290
|
-
nextTerminalNumber.current++;
|
|
291
|
-
setActiveTerminalId(newTerminal.id);
|
|
292
|
-
return [newTerminal];
|
|
293
|
-
}
|
|
236
|
+
// If we're closing the active terminal, switch to the most recent remaining one or clear storage
|
|
237
|
+
if (activeAgentId === agentId) {
|
|
238
|
+
if (filtered.length > 0) {
|
|
239
|
+
const mostRecentAgent = getMostRecentAgent(filtered);
|
|
240
|
+
setActiveAgentIdWithStorage(mostRecentAgent?.id || null);
|
|
241
|
+
} else {
|
|
242
|
+
setActiveAgentIdWithStorage(null);
|
|
294
243
|
}
|
|
244
|
+
}
|
|
295
245
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
} catch (error) {
|
|
299
|
-
console.error("Failed to close agent:", error);
|
|
300
|
-
editContext?.showToast("Failed to close agent. Removing from UI.");
|
|
301
|
-
// Still remove the terminal from UI even if backend call fails
|
|
302
|
-
setTerminals((prev) => prev.filter((t) => t.id !== terminalId));
|
|
303
|
-
}
|
|
246
|
+
return filtered;
|
|
247
|
+
});
|
|
304
248
|
};
|
|
305
249
|
|
|
306
|
-
const
|
|
307
|
-
if (!
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
250
|
+
const closeOtherAgents = async () => {
|
|
251
|
+
if (!activeAgentId) return;
|
|
310
252
|
|
|
311
|
-
// Get all
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
// Show confirmation if any of the other terminals have agents
|
|
315
|
-
const hasAgents = otherTerminals.some((t) => t.agentId);
|
|
316
|
-
|
|
317
|
-
if (hasAgents) {
|
|
318
|
-
editContext?.confirm({
|
|
319
|
-
header: "Close Other Agents",
|
|
320
|
-
message: `Are you sure you want to close ${otherTerminals.length} other agent${otherTerminals.length > 1 ? "s" : ""}? This will abort any running executions and mark them as closed.`,
|
|
321
|
-
acceptLabel: "Close Others",
|
|
322
|
-
rejectLabel: "Cancel",
|
|
323
|
-
accept: async () => {
|
|
324
|
-
// Close all other terminals
|
|
325
|
-
for (const terminal of otherTerminals) {
|
|
326
|
-
try {
|
|
327
|
-
if (terminal.agentId) {
|
|
328
|
-
const context = createAgentContext();
|
|
329
|
-
await closeAgent(terminal.agentId, context);
|
|
330
|
-
}
|
|
331
|
-
} catch (error) {
|
|
332
|
-
console.error("Failed to close agent:", error);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
253
|
+
// Get agents to close (all except active)
|
|
254
|
+
const agentsToClose = agents.filter((a) => a.id !== activeAgentId);
|
|
335
255
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
343
|
-
},
|
|
344
|
-
reject: () => {}, // Do nothing on reject
|
|
345
|
-
});
|
|
346
|
-
} else {
|
|
347
|
-
// No agents, just close immediately
|
|
348
|
-
const activeTerminal = terminals.find((t) => t.id === activeTerminalId);
|
|
349
|
-
if (activeTerminal) {
|
|
350
|
-
setTerminals([activeTerminal]);
|
|
256
|
+
// Permanently close each agent in the backend
|
|
257
|
+
const closePromises = agentsToClose.map(async (agent) => {
|
|
258
|
+
try {
|
|
259
|
+
await closeAgentService(agent.id);
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.error(`Failed to close agent ${agent.id}:`, error);
|
|
351
262
|
}
|
|
352
|
-
}
|
|
353
|
-
};
|
|
263
|
+
});
|
|
354
264
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const hasAgents = terminals.some((t) => t.agentId);
|
|
358
|
-
|
|
359
|
-
if (hasAgents) {
|
|
360
|
-
editContext?.confirm({
|
|
361
|
-
header: "Close All Agents",
|
|
362
|
-
message: `Are you sure you want to close all ${terminals.length} agent${terminals.length > 1 ? "s" : ""}? This will abort any running executions and mark them as closed.`,
|
|
363
|
-
acceptLabel: "Close All",
|
|
364
|
-
rejectLabel: "Cancel",
|
|
365
|
-
accept: async () => {
|
|
366
|
-
// Close all terminals with agents
|
|
367
|
-
for (const terminal of terminals) {
|
|
368
|
-
try {
|
|
369
|
-
if (terminal.agentId) {
|
|
370
|
-
const context = createAgentContext();
|
|
371
|
-
await closeAgent(terminal.agentId, context);
|
|
372
|
-
}
|
|
373
|
-
} catch (error) {
|
|
374
|
-
console.error("Failed to close agent:", error);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
265
|
+
// Wait for all close operations to complete
|
|
266
|
+
await Promise.all(closePromises);
|
|
377
267
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
options: initialOptions,
|
|
383
|
-
};
|
|
384
|
-
setTerminals([defaultTerminal]);
|
|
385
|
-
setActiveTerminalId(defaultTerminal.id);
|
|
386
|
-
nextTerminalNumber.current++;
|
|
387
|
-
},
|
|
388
|
-
reject: () => {}, // Do nothing on reject
|
|
389
|
-
});
|
|
390
|
-
} else {
|
|
391
|
-
// No agents, just create a new default terminal
|
|
392
|
-
const defaultTerminal: TerminalInstance = {
|
|
393
|
-
id: `terminal-${nextTerminalNumber.current}`,
|
|
394
|
-
title: "New Agent",
|
|
395
|
-
options: initialOptions,
|
|
396
|
-
};
|
|
397
|
-
setTerminals([defaultTerminal]);
|
|
398
|
-
setActiveTerminalId(defaultTerminal.id);
|
|
399
|
-
nextTerminalNumber.current++;
|
|
400
|
-
}
|
|
268
|
+
setAgents((prev) => {
|
|
269
|
+
return prev.filter((a) => a.id === activeAgentId);
|
|
270
|
+
});
|
|
271
|
+
setMenuPopoverOpen(false);
|
|
401
272
|
};
|
|
402
273
|
|
|
403
|
-
const openAgentFromHistory = async (agent:
|
|
274
|
+
const openAgentFromHistory = async (agent: Agent) => {
|
|
404
275
|
// Check if this agent is already open as a terminal
|
|
405
|
-
const
|
|
276
|
+
const existingAgent = agents.find((a) => a.id === agent.id);
|
|
406
277
|
|
|
407
|
-
if (
|
|
408
|
-
// Switch to existing
|
|
409
|
-
|
|
278
|
+
if (existingAgent) {
|
|
279
|
+
// Switch to existing agent
|
|
280
|
+
setActiveAgentIdWithStorage(existingAgent.id);
|
|
410
281
|
setHistoryPopoverOpen(false);
|
|
411
282
|
return;
|
|
412
283
|
}
|
|
413
284
|
|
|
414
|
-
//
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
const newTerminal: TerminalInstance = {
|
|
419
|
-
id: `agent-${agent.id}`,
|
|
420
|
-
title: agent.name,
|
|
421
|
-
agentId: agent.id,
|
|
422
|
-
options: {
|
|
423
|
-
...initialOptions,
|
|
424
|
-
// Pass cost information from loaded agent
|
|
425
|
-
totalCost: agentWithMessages?.totalCost,
|
|
426
|
-
totalInputTokenCost: agentWithMessages?.totalInputTokenCost,
|
|
427
|
-
totalOutputTokenCost: agentWithMessages?.totalOutputTokenCost,
|
|
428
|
-
totalCachedTokenCost: agentWithMessages?.totalCachedInputTokenCost,
|
|
429
|
-
totalInputTokens: agentWithMessages?.totalInputTokens,
|
|
430
|
-
totalOutputTokens: agentWithMessages?.totalOutputTokens,
|
|
431
|
-
totalCachedTokens: agentWithMessages?.totalCachedInputTokens,
|
|
432
|
-
},
|
|
433
|
-
messages: agentWithMessages?.messages
|
|
434
|
-
? convertAgentMessagesToTerminalMessages(agentWithMessages.messages)
|
|
435
|
-
: [],
|
|
285
|
+
// Add the closed agent to the active agents list
|
|
286
|
+
const reopenedAgent: Agent = {
|
|
287
|
+
...agent,
|
|
288
|
+
// Keep the original status to allow AgentTerminal to load the full agent data
|
|
436
289
|
};
|
|
437
290
|
|
|
438
|
-
|
|
439
|
-
|
|
291
|
+
setAgents((prev) => [...prev, reopenedAgent]);
|
|
292
|
+
setActiveAgentIdWithStorage(reopenedAgent.id);
|
|
440
293
|
setHistoryPopoverOpen(false);
|
|
441
294
|
};
|
|
442
295
|
|
|
443
|
-
const deleteAgentFromHistory = async (
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
rejectLabel: "Cancel",
|
|
449
|
-
accept: async () => {
|
|
450
|
-
try {
|
|
451
|
-
const context = createAgentContext();
|
|
452
|
-
await deleteAgent(agent.id, context);
|
|
453
|
-
|
|
454
|
-
// Remove from closed agents list
|
|
455
|
-
setClosedAgents((prev) => prev.filter((a) => a.id !== agent.id));
|
|
456
|
-
|
|
457
|
-
// If this agent is currently open as a terminal, close it
|
|
458
|
-
const existingTerminal = terminals.find(
|
|
459
|
-
(t) => t.agentId === agent.id,
|
|
460
|
-
);
|
|
461
|
-
if (existingTerminal) {
|
|
462
|
-
performCloseTerminal(existingTerminal.id);
|
|
463
|
-
}
|
|
296
|
+
const deleteAgentFromHistory = async (
|
|
297
|
+
agentId: string,
|
|
298
|
+
event: React.MouseEvent,
|
|
299
|
+
) => {
|
|
300
|
+
event.stopPropagation(); // Prevent opening the agent when clicking delete
|
|
464
301
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
302
|
+
try {
|
|
303
|
+
// Permanently delete the agent from the backend
|
|
304
|
+
await deleteAgent(agentId);
|
|
305
|
+
|
|
306
|
+
// Remove from inactive agents list
|
|
307
|
+
setInactiveAgents((prev) => prev.filter((agent) => agent.id !== agentId));
|
|
308
|
+
} catch (error) {
|
|
309
|
+
console.error("Failed to delete agent:", error);
|
|
310
|
+
// You might want to show a user-facing error message here
|
|
311
|
+
}
|
|
473
312
|
};
|
|
474
313
|
|
|
475
314
|
if (loadingAgents) {
|
|
476
315
|
return (
|
|
477
316
|
<div className="flex h-full items-center justify-center">
|
|
478
|
-
<div className="text-
|
|
317
|
+
<div className="text-sm text-gray-500">Loading agents...</div>
|
|
479
318
|
</div>
|
|
480
319
|
);
|
|
481
320
|
}
|
|
@@ -484,81 +323,121 @@ export function Agents({
|
|
|
484
323
|
<div className="flex h-full flex-col">
|
|
485
324
|
{/* Tab Header */}
|
|
486
325
|
<div className="flex items-center border-b border-gray-200 bg-gray-50">
|
|
487
|
-
<div className="flex flex-1 overflow-x-auto
|
|
488
|
-
{
|
|
326
|
+
<div className="flex flex-1 overflow-x-auto">
|
|
327
|
+
{agents.map((agent) => (
|
|
489
328
|
<div
|
|
490
|
-
key={
|
|
329
|
+
key={agent.id}
|
|
491
330
|
className={cn(
|
|
492
331
|
"flex min-w-0 cursor-pointer items-center gap-1 border-r border-gray-200 px-3 py-2 text-xs",
|
|
493
|
-
|
|
332
|
+
activeAgentId === agent.id
|
|
494
333
|
? "border-b-white bg-white"
|
|
495
334
|
: "hover:bg-gray-100",
|
|
496
335
|
)}
|
|
497
|
-
onClick={() =>
|
|
336
|
+
onClick={() => setActiveAgentIdWithStorage(agent.id)}
|
|
498
337
|
>
|
|
499
|
-
<span className="truncate">{
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
e
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
338
|
+
<span className="truncate">{agent.name}</span>
|
|
339
|
+
{agents.length > 1 && (
|
|
340
|
+
<SimpleIconButton
|
|
341
|
+
onClick={(e) => {
|
|
342
|
+
e.stopPropagation();
|
|
343
|
+
closeAgent(agent.id);
|
|
344
|
+
}}
|
|
345
|
+
icon={<X className="size-2" strokeWidth={1} />}
|
|
346
|
+
label="Close"
|
|
347
|
+
className="ml-1 opacity-60 hover:opacity-100"
|
|
348
|
+
/>
|
|
349
|
+
)}
|
|
509
350
|
</div>
|
|
510
351
|
))}
|
|
511
352
|
</div>
|
|
512
353
|
|
|
513
|
-
{/*
|
|
514
|
-
<div className="flex items-center px-1">
|
|
515
|
-
<SimpleIconButton
|
|
516
|
-
onClick={addTerminal}
|
|
517
|
-
icon={<Plus className="size-4" />}
|
|
518
|
-
label="Add Agent"
|
|
519
|
-
className="text-gray-600 hover:text-gray-800"
|
|
520
|
-
/>
|
|
521
|
-
</div>
|
|
522
|
-
|
|
523
|
-
{/* Agent History */}
|
|
354
|
+
{/* History Popover */}
|
|
524
355
|
<div className="flex items-center px-1">
|
|
525
|
-
<
|
|
526
|
-
|
|
527
|
-
isOpen={historyPopoverOpen}
|
|
356
|
+
<Popover
|
|
357
|
+
open={historyPopoverOpen}
|
|
528
358
|
onOpenChange={setHistoryPopoverOpen}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
359
|
+
>
|
|
360
|
+
<PopoverTrigger asChild>
|
|
361
|
+
<SimpleIconButton
|
|
362
|
+
onClick={() => {}}
|
|
363
|
+
icon={<History className="size-4" strokeWidth={1} />}
|
|
364
|
+
label="Agent History"
|
|
365
|
+
className="text-gray-600 hover:text-gray-800"
|
|
366
|
+
/>
|
|
367
|
+
</PopoverTrigger>
|
|
368
|
+
<PopoverContent className="w-64 p-0" align="end">
|
|
369
|
+
<div className="border-b border-gray-100 px-3 py-2 text-xs font-medium text-gray-500">
|
|
370
|
+
Closed Agents
|
|
371
|
+
</div>
|
|
372
|
+
<div className="max-h-80 overflow-y-auto">
|
|
373
|
+
{inactiveAgents.length === 0 ? (
|
|
374
|
+
<div className="px-3 py-2 text-xs text-gray-500">
|
|
375
|
+
No closed agents found
|
|
376
|
+
</div>
|
|
377
|
+
) : (
|
|
378
|
+
inactiveAgents.map((agent) => (
|
|
379
|
+
<div
|
|
380
|
+
key={agent.id}
|
|
381
|
+
className="cursor-pointer border-b border-gray-50 px-3 py-2 text-xs hover:bg-gray-50"
|
|
382
|
+
onClick={() => openAgentFromHistory(agent)}
|
|
383
|
+
>
|
|
384
|
+
<div className="flex items-center justify-between">
|
|
385
|
+
<div className="min-w-0 flex-1">
|
|
386
|
+
<div className="truncate font-medium text-gray-900">
|
|
387
|
+
{agent.name}
|
|
388
|
+
</div>
|
|
389
|
+
<div className="text-xs text-gray-400">
|
|
390
|
+
{new Date(agent.updatedDate).toLocaleString()}
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
<SimpleIconButton
|
|
394
|
+
onClick={(e) => deleteAgentFromHistory(agent.id, e)}
|
|
395
|
+
icon={<Trash className="size-3" strokeWidth={1} />}
|
|
396
|
+
label="Delete Agent"
|
|
397
|
+
className="ml-2 text-red-600 opacity-60 hover:text-red-700 hover:opacity-100"
|
|
398
|
+
/>
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
))
|
|
402
|
+
)}
|
|
403
|
+
</div>
|
|
404
|
+
</PopoverContent>
|
|
405
|
+
</Popover>
|
|
533
406
|
</div>
|
|
534
407
|
|
|
535
|
-
{/*
|
|
408
|
+
{/* Menu Popover */}
|
|
536
409
|
<div className="flex items-center px-1">
|
|
537
|
-
<
|
|
538
|
-
<
|
|
410
|
+
<Popover open={menuPopoverOpen} onOpenChange={setMenuPopoverOpen}>
|
|
411
|
+
<PopoverTrigger asChild>
|
|
539
412
|
<SimpleIconButton
|
|
540
413
|
onClick={() => {}}
|
|
541
|
-
icon={<
|
|
542
|
-
label="
|
|
414
|
+
icon={<MoreVertical className="size-4" strokeWidth={1} />}
|
|
415
|
+
label="Menu"
|
|
543
416
|
className="text-gray-600 hover:text-gray-800"
|
|
544
417
|
/>
|
|
545
|
-
</
|
|
546
|
-
<
|
|
547
|
-
<
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
418
|
+
</PopoverTrigger>
|
|
419
|
+
<PopoverContent className="w-48 p-0" align="end">
|
|
420
|
+
<div className="py-1">
|
|
421
|
+
<button
|
|
422
|
+
onClick={closeOtherAgents}
|
|
423
|
+
disabled={agents.length <= 1}
|
|
424
|
+
className="w-full px-3 py-2 text-left text-xs hover:bg-gray-50 disabled:cursor-not-allowed disabled:text-gray-400"
|
|
425
|
+
>
|
|
426
|
+
Close Other
|
|
427
|
+
</button>
|
|
428
|
+
</div>
|
|
429
|
+
</PopoverContent>
|
|
430
|
+
</Popover>
|
|
431
|
+
</div>
|
|
432
|
+
|
|
433
|
+
{/* Add Terminal Button */}
|
|
434
|
+
<div className="flex items-center px-1">
|
|
435
|
+
<SimpleIconButton
|
|
436
|
+
onClick={addAgent}
|
|
437
|
+
icon={<Plus className="size-4" strokeWidth={1} />}
|
|
438
|
+
label="Add Terminal"
|
|
439
|
+
className="text-gray-600 hover:text-gray-800"
|
|
440
|
+
/>
|
|
562
441
|
</div>
|
|
563
442
|
|
|
564
443
|
{/* Main Close Button */}
|
|
@@ -567,33 +446,17 @@ export function Agents({
|
|
|
567
446
|
)}
|
|
568
447
|
</div>
|
|
569
448
|
|
|
570
|
-
{/*
|
|
449
|
+
{/* Agent Content */}
|
|
571
450
|
<div className="relative flex-1">
|
|
572
|
-
{
|
|
451
|
+
{agents.map((agent) => (
|
|
573
452
|
<div
|
|
574
|
-
key={
|
|
453
|
+
key={agent.id}
|
|
575
454
|
className={cn(
|
|
576
455
|
"absolute inset-0",
|
|
577
|
-
|
|
456
|
+
activeAgentId === agent.id ? "block" : "hidden",
|
|
578
457
|
)}
|
|
579
458
|
>
|
|
580
|
-
<
|
|
581
|
-
options={{
|
|
582
|
-
...terminal.options,
|
|
583
|
-
// Pass the pre-loaded messages to the terminal
|
|
584
|
-
initialMessages: terminal.messages,
|
|
585
|
-
// Pass the agentId to maintain continuity
|
|
586
|
-
agentId: terminal.agentId,
|
|
587
|
-
}}
|
|
588
|
-
onAgentNameUpdate={(name) => {
|
|
589
|
-
// Update the terminal title when agent name is updated
|
|
590
|
-
setTerminals((prev) =>
|
|
591
|
-
prev.map((t) =>
|
|
592
|
-
t.id === terminal.id ? { ...t, title: name } : t,
|
|
593
|
-
),
|
|
594
|
-
);
|
|
595
|
-
}}
|
|
596
|
-
/>
|
|
459
|
+
<AgentTerminal agentStub={agent} />
|
|
597
460
|
</div>
|
|
598
461
|
))}
|
|
599
462
|
</div>
|