@alpaca-editor/core 1.0.4135 → 1.0.4141
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/index.d.ts +2 -0
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/ui/select.d.ts +2 -1
- package/dist/components/ui/select.js +2 -2
- package/dist/components/ui/select.js.map +1 -1
- package/dist/components/ui/tabs.d.ts +17 -0
- package/dist/components/ui/tabs.js +27 -0
- package/dist/components/ui/tabs.js.map +1 -0
- package/dist/config/config.js +7 -1
- package/dist/config/config.js.map +1 -1
- package/dist/editor/FieldListField.js +3 -4
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/ImageEditButton.d.ts +2 -1
- package/dist/editor/ImageEditButton.js +4 -4
- package/dist/editor/ImageEditButton.js.map +1 -1
- package/dist/editor/PictureEditor.js +42 -1
- package/dist/editor/PictureEditor.js.map +1 -1
- package/dist/editor/Terminal.js +1 -1
- package/dist/editor/Terminal.js.map +1 -1
- package/dist/editor/Titlebar.js +0 -1
- package/dist/editor/Titlebar.js.map +1 -1
- package/dist/editor/ai/AgentCostDisplay.d.ts +3 -1
- package/dist/editor/ai/AgentCostDisplay.js +26 -2
- package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
- package/dist/editor/ai/AgentStatusBadge.d.ts +26 -0
- package/dist/editor/ai/AgentStatusBadge.js +110 -0
- package/dist/editor/ai/AgentStatusBadge.js.map +1 -0
- package/dist/editor/ai/AgentTerminal.js +289 -198
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/Agents.d.ts +2 -2
- package/dist/editor/ai/Agents.js +115 -19
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.js +259 -45
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/ContextInfoBar.js +124 -113
- package/dist/editor/ai/ContextInfoBar.js.map +1 -1
- package/dist/editor/ai/ToolCallDisplay.d.ts +1 -0
- package/dist/editor/ai/ToolCallDisplay.js +70 -58
- package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
- package/dist/editor/ai/useAgentStatus.d.ts +13 -0
- package/dist/editor/ai/useAgentStatus.js +101 -0
- package/dist/editor/ai/useAgentStatus.js.map +1 -0
- package/dist/editor/client/EditorShell.js +23 -8
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/itemsRepository.js.map +1 -1
- package/dist/editor/commands/localizeItem/LocalizeItemDialog.js +5 -5
- package/dist/editor/commands/localizeItem/LocalizeItemDialog.js.map +1 -1
- package/dist/editor/control-center/About.js +1 -1
- package/dist/editor/control-center/About.js.map +1 -1
- package/dist/editor/control-center/AllAgentsPanel.d.ts +5 -0
- package/dist/editor/control-center/AllAgentsPanel.js +126 -0
- package/dist/editor/control-center/AllAgentsPanel.js.map +1 -0
- package/dist/editor/control-center/WebSocketMessages.js +1 -0
- package/dist/editor/control-center/WebSocketMessages.js.map +1 -1
- package/dist/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.js +42 -7
- package/dist/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.js.map +1 -1
- package/dist/editor/media-selector/AiImageSearch.d.ts +1 -1
- package/dist/editor/media-selector/AiImageSearch.js +162 -103
- package/dist/editor/media-selector/AiImageSearch.js.map +1 -1
- package/dist/editor/media-selector/TreeSelector.js +20 -4
- package/dist/editor/media-selector/TreeSelector.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js +5 -2
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.d.ts +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +7 -5
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
- package/dist/editor/page-viewer/DeviceToolbar.js +2 -2
- package/dist/editor/page-viewer/DeviceToolbar.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +18 -11
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +53 -48
- package/dist/editor/services/agentService.js +137 -79
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/services/aiService.d.ts +1 -1
- package/dist/editor/services/editService.js +1 -0
- package/dist/editor/services/editService.js.map +1 -1
- package/dist/editor/sidebar/GraphQL.js +20 -7
- package/dist/editor/sidebar/GraphQL.js.map +1 -1
- package/dist/editor/sidebar/SEOInfo.js +1 -2
- package/dist/editor/sidebar/SEOInfo.js.map +1 -1
- package/dist/editor/sidebar/Translations.js +10 -7
- package/dist/editor/sidebar/Translations.js.map +1 -1
- package/dist/editor/ui/ItemNameDialogNew.js +1 -1
- package/dist/editor/ui/ItemSearch.js +10 -4
- package/dist/editor/ui/ItemSearch.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/page-wizard/steps/CollectStep.js +2 -2
- package/dist/page-wizard/steps/CollectStep.js.map +1 -1
- package/dist/page-wizard/steps/FieldEditor.js +2 -2
- package/dist/page-wizard/steps/FieldEditor.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/splash-screen/NewPage.js +2 -2
- package/dist/splash-screen/NewPage.js.map +1 -1
- package/dist/splash-screen/RecentPages.js +1 -1
- package/dist/splash-screen/RecentPages.js.map +1 -1
- package/dist/styles.css +167 -15
- package/dist/tour/Tour.js +15 -11
- package/dist/tour/Tour.js.map +1 -1
- package/package.json +1 -1
- package/src/components/index.ts +2 -0
- package/src/components/ui/select.tsx +3 -0
- package/src/components/ui/tabs.tsx +87 -0
- package/src/config/config.tsx +7 -1
- package/src/editor/FieldListField.tsx +13 -13
- package/src/editor/ImageEditButton.tsx +5 -3
- package/src/editor/PictureEditor.tsx +48 -1
- package/src/editor/Terminal.tsx +1 -1
- package/src/editor/Titlebar.tsx +0 -1
- package/src/editor/ai/AgentCostDisplay.tsx +57 -1
- package/src/editor/ai/AgentStatusBadge.tsx +144 -0
- package/src/editor/ai/AgentTerminal.tsx +345 -219
- package/src/editor/ai/Agents.tsx +179 -30
- package/src/editor/ai/AiResponseMessage.tsx +411 -114
- package/src/editor/ai/ContextInfoBar.tsx +134 -131
- package/src/editor/ai/ToolCallDisplay.tsx +217 -176
- package/src/editor/ai/useAgentStatus.ts +123 -0
- package/src/editor/client/EditorShell.tsx +34 -8
- package/src/editor/client/itemsRepository.ts +1 -2
- package/src/editor/commands/localizeItem/LocalizeItemDialog.tsx +5 -5
- package/src/editor/control-center/About.tsx +0 -14
- package/src/editor/control-center/AllAgentsPanel.tsx +300 -0
- package/src/editor/control-center/WebSocketMessages.tsx +1 -0
- package/src/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.tsx +49 -8
- package/src/editor/media-selector/AiImageSearch.tsx +162 -172
- package/src/editor/media-selector/TreeSelector.tsx +137 -116
- package/src/editor/menubar/toolbar-sections/UtilityControls.tsx +9 -1
- package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +7 -4
- package/src/editor/page-viewer/DeviceToolbar.tsx +15 -11
- package/src/editor/page-viewer/PageViewerFrame.tsx +20 -14
- package/src/editor/services/agentService.ts +217 -129
- package/src/editor/services/aiService.ts +2 -2
- package/src/editor/services/editService.ts +1 -0
- package/src/editor/sidebar/GraphQL.tsx +143 -117
- package/src/editor/sidebar/SEOInfo.tsx +1 -2
- package/src/editor/sidebar/Translations.tsx +14 -12
- package/src/editor/ui/ItemNameDialogNew.tsx +1 -1
- package/src/editor/ui/ItemSearch.tsx +11 -4
- package/src/editor/ui/SimpleTabs.tsx +1 -1
- package/src/index.ts +6 -0
- package/src/page-wizard/steps/CollectStep.tsx +2 -2
- package/src/page-wizard/steps/FieldEditor.tsx +13 -15
- package/src/revision.ts +2 -2
- package/src/splash-screen/NewPage.tsx +2 -2
- package/src/splash-screen/RecentPages.tsx +1 -1
- package/src/tour/Tour.tsx +61 -48
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from "react";
|
|
2
|
+
import { Agent, getActiveAgents } from "../services/agentService";
|
|
3
|
+
import { useEditContext } from "../client/editContext";
|
|
4
|
+
|
|
5
|
+
export interface AgentStatusSummary {
|
|
6
|
+
total: number;
|
|
7
|
+
running: number;
|
|
8
|
+
waitingForApproval: number;
|
|
9
|
+
hasActivity: boolean;
|
|
10
|
+
agents: Agent[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Hook to track the status of all active agents
|
|
15
|
+
* Uses WebSocket messages for real-time updates instead of polling
|
|
16
|
+
*/
|
|
17
|
+
export function useAgentStatus(): AgentStatusSummary {
|
|
18
|
+
const [agents, setAgents] = useState<Agent[]>([]);
|
|
19
|
+
const editContext = useEditContext();
|
|
20
|
+
|
|
21
|
+
const fetchAgentStatus = useCallback(async () => {
|
|
22
|
+
try {
|
|
23
|
+
const activeAgents = await getActiveAgents();
|
|
24
|
+
// Filter out completed, error, and closed agents to match WebSocket behavior
|
|
25
|
+
// Note: Backend sends status as both enum numbers (0-5) and strings
|
|
26
|
+
// Enum: New=0, Running=1, WaitingForApproval=2, Completed=3, Error=4, Closed=5
|
|
27
|
+
const filteredAgents = (activeAgents || []).filter(
|
|
28
|
+
(agent) => {
|
|
29
|
+
const status = agent.status;
|
|
30
|
+
// Handle numeric status (from initial fetch)
|
|
31
|
+
if (typeof status === "number") {
|
|
32
|
+
return status !== 3 && status !== 4 && status !== 5; // Not Completed, Error, or Closed
|
|
33
|
+
}
|
|
34
|
+
// Handle string status (from WebSocket updates)
|
|
35
|
+
return status !== "completed" && status !== "error" && status !== "closed";
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
setAgents(filteredAgents);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error("Failed to fetch agent status:", error);
|
|
41
|
+
// Don't update agents on error to keep showing last known state
|
|
42
|
+
}
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
// Initial fetch to populate state
|
|
47
|
+
fetchAgentStatus();
|
|
48
|
+
}, [fetchAgentStatus]);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!editContext?.addSocketMessageListener) return;
|
|
52
|
+
|
|
53
|
+
// Subscribe to WebSocket messages for agent status updates
|
|
54
|
+
const handleMessage = (message: { type: string; payload: any }) => {
|
|
55
|
+
if (message.type === "agent-status-changed") {
|
|
56
|
+
// Update agent status in real-time from WebSocket message
|
|
57
|
+
const { agentId, status, name } = message.payload || {};
|
|
58
|
+
if (!agentId) return;
|
|
59
|
+
|
|
60
|
+
setAgents((prevAgents) => {
|
|
61
|
+
// Check if agent exists in current list
|
|
62
|
+
const existingIndex = prevAgents.findIndex((a) => a.id === agentId);
|
|
63
|
+
|
|
64
|
+
if (existingIndex !== -1) {
|
|
65
|
+
// Update existing agent status
|
|
66
|
+
const updatedAgents = [...prevAgents];
|
|
67
|
+
const existingAgent = updatedAgents[existingIndex]!;
|
|
68
|
+
updatedAgents[existingIndex] = {
|
|
69
|
+
id: existingAgent.id,
|
|
70
|
+
name: existingAgent.name,
|
|
71
|
+
userId: existingAgent.userId,
|
|
72
|
+
updatedDate: existingAgent.updatedDate,
|
|
73
|
+
status,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Remove agent from list if status is completed, error, or closed
|
|
77
|
+
if (status === "completed" || status === "error" || status === "closed") {
|
|
78
|
+
return updatedAgents.filter((a) => a.id !== agentId);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return updatedAgents;
|
|
82
|
+
} else if (status === "running" || status === "waitingForApproval") {
|
|
83
|
+
// Add new agent to the list if it's in an active state
|
|
84
|
+
const newAgent: Agent = {
|
|
85
|
+
id: agentId,
|
|
86
|
+
name: name || "Agent",
|
|
87
|
+
userId: "",
|
|
88
|
+
status,
|
|
89
|
+
updatedDate: new Date().toISOString(),
|
|
90
|
+
};
|
|
91
|
+
return [...prevAgents, newAgent];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return prevAgents;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Register the listener and get cleanup function
|
|
100
|
+
const unsubscribe = editContext.addSocketMessageListener(handleMessage);
|
|
101
|
+
|
|
102
|
+
// Cleanup
|
|
103
|
+
return () => {
|
|
104
|
+
unsubscribe();
|
|
105
|
+
};
|
|
106
|
+
}, [editContext]);
|
|
107
|
+
|
|
108
|
+
// Calculate summary
|
|
109
|
+
// Handle both numeric (1) and string ("running") status values
|
|
110
|
+
const running = agents.filter((a) => a.status === "running" || a.status === 1).length;
|
|
111
|
+
const waitingForApproval = agents.filter(
|
|
112
|
+
(a) => a.status === "waitingForApproval" || a.status === 2,
|
|
113
|
+
).length;
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
total: agents.length,
|
|
117
|
+
running,
|
|
118
|
+
waitingForApproval,
|
|
119
|
+
hasActivity: agents.length > 0,
|
|
120
|
+
agents,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
@@ -857,12 +857,12 @@ export function EditorShell({
|
|
|
857
857
|
const item = me?.item;
|
|
858
858
|
const acknowledged =
|
|
859
859
|
!!item &&
|
|
860
|
-
item.id ===
|
|
861
|
-
item.language ===
|
|
862
|
-
item.version ===
|
|
860
|
+
item.id === contentEditorItem?.id &&
|
|
861
|
+
item.language === contentEditorItem?.language &&
|
|
862
|
+
item.version === contentEditorItem?.version;
|
|
863
863
|
if (acknowledged) setIsClientInfoAcknowledged(true);
|
|
864
864
|
} catch {}
|
|
865
|
-
}, [activeSessions,
|
|
865
|
+
}, [activeSessions, contentEditorItem, sessionId]);
|
|
866
866
|
|
|
867
867
|
const loadComments = useCallback(async () => {
|
|
868
868
|
if (!currentItemDescriptor) return;
|
|
@@ -1834,6 +1834,8 @@ export function EditorShell({
|
|
|
1834
1834
|
});
|
|
1835
1835
|
}
|
|
1836
1836
|
|
|
1837
|
+
console.log("Field action before setActiveFieldActions");
|
|
1838
|
+
|
|
1837
1839
|
if (actionButton.action) {
|
|
1838
1840
|
setActiveFieldActions((prevFieldActions) => {
|
|
1839
1841
|
const isSameField = (x: FieldAction) =>
|
|
@@ -1860,6 +1862,9 @@ export function EditorShell({
|
|
|
1860
1862
|
mergedOp,
|
|
1861
1863
|
];
|
|
1862
1864
|
});
|
|
1865
|
+
|
|
1866
|
+
console.log("Field action after setActiveFieldActions");
|
|
1867
|
+
|
|
1863
1868
|
await executeFieldServerAction(
|
|
1864
1869
|
fieldDescriptor.item,
|
|
1865
1870
|
fieldDescriptor.fieldId,
|
|
@@ -1869,18 +1874,39 @@ export function EditorShell({
|
|
|
1869
1874
|
selectedRange?.text || "",
|
|
1870
1875
|
parameters || {},
|
|
1871
1876
|
(data: any) => {
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1877
|
+
console.log("Field action progress", data);
|
|
1878
|
+
// Update progress message if available
|
|
1879
|
+
if (data?.responseText) {
|
|
1880
|
+
op.message = data.responseText;
|
|
1881
|
+
setActiveFieldActions((prevFieldActions) => [
|
|
1882
|
+
...prevFieldActions,
|
|
1883
|
+
]);
|
|
1884
|
+
}
|
|
1876
1885
|
},
|
|
1877
1886
|
);
|
|
1878
1887
|
|
|
1888
|
+
console.log("Field action completed", op);
|
|
1889
|
+
|
|
1879
1890
|
itemsRepository.refreshItems([fieldDescriptor.item]);
|
|
1880
1891
|
|
|
1881
1892
|
// Only mark as success if no websocket error has been reported
|
|
1882
1893
|
if (op.state === "running") {
|
|
1883
1894
|
op.state = "success";
|
|
1895
|
+
// Auto-dismiss successful actions after a brief delay
|
|
1896
|
+
setTimeout(() => {
|
|
1897
|
+
setActiveFieldActions((prevFieldActions) =>
|
|
1898
|
+
prevFieldActions.filter(
|
|
1899
|
+
(x) =>
|
|
1900
|
+
!(
|
|
1901
|
+
x.field.fieldId === fieldDescriptor.fieldId &&
|
|
1902
|
+
x.field.item.id === fieldDescriptor.item.id &&
|
|
1903
|
+
x.field.item.language === fieldDescriptor.item.language &&
|
|
1904
|
+
x.field.item.version === fieldDescriptor.item.version &&
|
|
1905
|
+
x.state === "success"
|
|
1906
|
+
),
|
|
1907
|
+
),
|
|
1908
|
+
);
|
|
1909
|
+
}, 500);
|
|
1884
1910
|
}
|
|
1885
1911
|
requestRefresh("immediate");
|
|
1886
1912
|
}
|
|
@@ -408,7 +408,7 @@ export function useItemsRepository(
|
|
|
408
408
|
timestamp: Date.now(),
|
|
409
409
|
saveSequence: currentSaveSequence,
|
|
410
410
|
};
|
|
411
|
-
|
|
411
|
+
|
|
412
412
|
fieldModificationStore.updateField(updatedField);
|
|
413
413
|
|
|
414
414
|
addRecentEdit({ ...field, timestamp: Date.now(), user });
|
|
@@ -523,7 +523,6 @@ export function useItemsRepository(
|
|
|
523
523
|
const prevFields = fieldModificationStore.getAllFields();
|
|
524
524
|
const newFields = updateModifiedFields(prevFields);
|
|
525
525
|
|
|
526
|
-
|
|
527
526
|
fieldModificationStore.updateFields(newFields);
|
|
528
527
|
setRevision((prev) => prev + 1);
|
|
529
528
|
}
|
|
@@ -11,7 +11,7 @@ import { DialogProps } from "../../client/editContext";
|
|
|
11
11
|
import { getLanguagesAndVersions } from "../../services/contentService";
|
|
12
12
|
import { FullItem, ItemDescriptor } from "../../pageModel";
|
|
13
13
|
import { LanguageVersions } from "../../../types";
|
|
14
|
-
import { Checkbox } from "
|
|
14
|
+
import { Checkbox } from "../../../components/ui/checkbox";
|
|
15
15
|
|
|
16
16
|
import { EditContextType } from "../../client/editContext";
|
|
17
17
|
import { Spinner } from "../../ui/Spinner";
|
|
@@ -150,12 +150,12 @@ export function LocalizeItemDialog(
|
|
|
150
150
|
checked={allLanguages.every(
|
|
151
151
|
(language) => languageSelection[language.code],
|
|
152
152
|
)}
|
|
153
|
-
|
|
153
|
+
onCheckedChange={(checked) => {
|
|
154
154
|
setLanguageSelection(
|
|
155
155
|
Object.fromEntries(
|
|
156
156
|
Array.from(languageData?.keys() || []).map((language) => [
|
|
157
157
|
language,
|
|
158
|
-
|
|
158
|
+
Boolean(checked),
|
|
159
159
|
]),
|
|
160
160
|
),
|
|
161
161
|
);
|
|
@@ -172,10 +172,10 @@ export function LocalizeItemDialog(
|
|
|
172
172
|
>
|
|
173
173
|
<Checkbox
|
|
174
174
|
checked={languageSelection[language.code] || false}
|
|
175
|
-
|
|
175
|
+
onCheckedChange={(checked) => {
|
|
176
176
|
setLanguageSelection((prev) => ({
|
|
177
177
|
...prev,
|
|
178
|
-
[language.code]:
|
|
178
|
+
[language.code]: Boolean(checked),
|
|
179
179
|
}));
|
|
180
180
|
}}
|
|
181
181
|
/>
|
|
@@ -138,20 +138,6 @@ export function About() {
|
|
|
138
138
|
designs{" "}
|
|
139
139
|
</p>{" "}
|
|
140
140
|
</div>{" "}
|
|
141
|
-
<div className="rounded-lg bg-gray-50 p-3">
|
|
142
|
-
{" "}
|
|
143
|
-
<div className="mb-2 flex items-center justify-between">
|
|
144
|
-
{" "}
|
|
145
|
-
<h4 className="text-sm font-medium text-gray-700">
|
|
146
|
-
PrimeReact
|
|
147
|
-
</h4>{" "}
|
|
148
|
-
<span className="text-xs text-gray-500">MIT License</span>{" "}
|
|
149
|
-
</div>{" "}
|
|
150
|
-
<p className="text-xs text-gray-600">
|
|
151
|
-
{" "}
|
|
152
|
-
React UI Component Library with rich set of components{" "}
|
|
153
|
-
</p>{" "}
|
|
154
|
-
</div>{" "}
|
|
155
141
|
<div className="rounded-lg bg-gray-50 p-3">
|
|
156
142
|
{" "}
|
|
157
143
|
<div className="mb-2 flex items-center justify-between">
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import {
|
|
3
|
+
AgentDetails,
|
|
4
|
+
getAllAgents,
|
|
5
|
+
closeAgent,
|
|
6
|
+
deleteAgent,
|
|
7
|
+
} from "../services/agentService";
|
|
8
|
+
import { getAgentStatusConfig } from "../ai/AgentStatusBadge";
|
|
9
|
+
import { cn } from "../../lib/utils";
|
|
10
|
+
import { Trash, X, RefreshCw } from "lucide-react";
|
|
11
|
+
import { SimpleIconButton } from "../ui/SimpleIconButton";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Control Center panel that displays all non-closed agents across all users
|
|
15
|
+
* Admin-only feature for monitoring and managing agents
|
|
16
|
+
*/
|
|
17
|
+
export function AllAgentsPanel() {
|
|
18
|
+
const [agents, setAgents] = useState<AgentDetails[]>([]);
|
|
19
|
+
const [loading, setLoading] = useState(true);
|
|
20
|
+
const [error, setError] = useState<string | null>(null);
|
|
21
|
+
const [refreshing, setRefreshing] = useState(false);
|
|
22
|
+
const [searchTerm, setSearchTerm] = useState("");
|
|
23
|
+
|
|
24
|
+
const loadAgents = async () => {
|
|
25
|
+
try {
|
|
26
|
+
setError(null);
|
|
27
|
+
const result = await getAllAgents(1000);
|
|
28
|
+
setAgents(result);
|
|
29
|
+
} catch (err: any) {
|
|
30
|
+
console.error("Failed to load agents:", err);
|
|
31
|
+
setError(
|
|
32
|
+
err?.message ||
|
|
33
|
+
"Failed to load agents. Check if you have admin permissions.",
|
|
34
|
+
);
|
|
35
|
+
} finally {
|
|
36
|
+
setLoading(false);
|
|
37
|
+
setRefreshing(false);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
loadAgents();
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
const handleRefresh = async () => {
|
|
46
|
+
setRefreshing(true);
|
|
47
|
+
await loadAgents();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handleCloseAgent = async (agentId: string, agentName: string) => {
|
|
51
|
+
if (
|
|
52
|
+
!confirm(
|
|
53
|
+
`Are you sure you want to close agent "${agentName}"? This will stop its execution if running.`,
|
|
54
|
+
)
|
|
55
|
+
) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
await closeAgent(agentId);
|
|
61
|
+
// Remove from list or reload
|
|
62
|
+
setAgents((prev) => prev.filter((a) => a.id !== agentId));
|
|
63
|
+
} catch (err: any) {
|
|
64
|
+
console.error("Failed to close agent:", err);
|
|
65
|
+
alert(`Failed to close agent: ${err?.message || "Unknown error"}`);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const handleDeleteAgent = async (
|
|
70
|
+
agentId: string,
|
|
71
|
+
agentName: string,
|
|
72
|
+
event: React.MouseEvent,
|
|
73
|
+
) => {
|
|
74
|
+
event.stopPropagation();
|
|
75
|
+
|
|
76
|
+
if (
|
|
77
|
+
!confirm(
|
|
78
|
+
`Are you sure you want to permanently delete agent "${agentName}"? This action cannot be undone.`,
|
|
79
|
+
)
|
|
80
|
+
) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
await deleteAgent(agentId);
|
|
86
|
+
setAgents((prev) => prev.filter((a) => a.id !== agentId));
|
|
87
|
+
} catch (err: any) {
|
|
88
|
+
console.error("Failed to delete agent:", err);
|
|
89
|
+
alert(`Failed to delete agent: ${err?.message || "Unknown error"}`);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const formatDateTime = (dateString: string) => {
|
|
94
|
+
try {
|
|
95
|
+
const date = new Date(dateString);
|
|
96
|
+
return date.toLocaleString();
|
|
97
|
+
} catch {
|
|
98
|
+
return dateString;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const filteredAgents = agents.filter((agent) => {
|
|
103
|
+
if (!searchTerm.trim()) return true;
|
|
104
|
+
const search = searchTerm.toLowerCase();
|
|
105
|
+
return (
|
|
106
|
+
agent.name.toLowerCase().includes(search) ||
|
|
107
|
+
agent.userId?.toLowerCase().includes(search) ||
|
|
108
|
+
agent.id.toLowerCase().includes(search)
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Group agents by status
|
|
113
|
+
const groupedAgents = filteredAgents.reduce(
|
|
114
|
+
(acc, agent) => {
|
|
115
|
+
const statusConfig = getAgentStatusConfig(agent);
|
|
116
|
+
const statusLabel = statusConfig.label.replace("Status: ", "");
|
|
117
|
+
if (!acc[statusLabel]) {
|
|
118
|
+
acc[statusLabel] = [];
|
|
119
|
+
}
|
|
120
|
+
acc[statusLabel].push(agent);
|
|
121
|
+
return acc;
|
|
122
|
+
},
|
|
123
|
+
{} as Record<string, AgentDetails[]>,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Sort groups by priority
|
|
127
|
+
const statusOrder = [
|
|
128
|
+
"Waiting for Approval",
|
|
129
|
+
"Running",
|
|
130
|
+
"Error",
|
|
131
|
+
"New",
|
|
132
|
+
"Completed",
|
|
133
|
+
];
|
|
134
|
+
const sortedGroups = Object.entries(groupedAgents).sort(([a], [b]) => {
|
|
135
|
+
const indexA = statusOrder.indexOf(a);
|
|
136
|
+
const indexB = statusOrder.indexOf(b);
|
|
137
|
+
if (indexA === -1) return 1;
|
|
138
|
+
if (indexB === -1) return -1;
|
|
139
|
+
return indexA - indexB;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (loading) {
|
|
143
|
+
return (
|
|
144
|
+
<div className="flex h-full items-center justify-center">
|
|
145
|
+
<div className="text-sm text-gray-500">Loading agents...</div>
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (error) {
|
|
151
|
+
return (
|
|
152
|
+
<div className="flex h-full items-center justify-center">
|
|
153
|
+
<div className="max-w-md text-center">
|
|
154
|
+
<div className="mb-4 text-sm text-red-600">{error}</div>
|
|
155
|
+
<button
|
|
156
|
+
onClick={handleRefresh}
|
|
157
|
+
className="rounded bg-blue-500 px-4 py-2 text-xs text-white hover:bg-blue-600"
|
|
158
|
+
>
|
|
159
|
+
Retry
|
|
160
|
+
</button>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<div className="flex h-full flex-col">
|
|
168
|
+
{/* Header */}
|
|
169
|
+
<div className="border-b border-gray-200 bg-white p-4">
|
|
170
|
+
<div className="mb-3 flex items-center justify-between">
|
|
171
|
+
<div>
|
|
172
|
+
<h2 className="text-lg font-semibold text-gray-900">All Agents</h2>
|
|
173
|
+
<p className="text-xs text-gray-500">
|
|
174
|
+
Showing {filteredAgents.length} non-closed agent
|
|
175
|
+
{filteredAgents.length !== 1 ? "s" : ""} across all users
|
|
176
|
+
</p>
|
|
177
|
+
</div>
|
|
178
|
+
<SimpleIconButton
|
|
179
|
+
onClick={handleRefresh}
|
|
180
|
+
icon={
|
|
181
|
+
<RefreshCw
|
|
182
|
+
className={cn("size-4", refreshing && "animate-spin")}
|
|
183
|
+
strokeWidth={1.5}
|
|
184
|
+
/>
|
|
185
|
+
}
|
|
186
|
+
label="Refresh"
|
|
187
|
+
disabled={refreshing}
|
|
188
|
+
className="text-gray-600 hover:text-gray-800"
|
|
189
|
+
/>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
{/* Search */}
|
|
193
|
+
<input
|
|
194
|
+
type="text"
|
|
195
|
+
placeholder="Search by name, user, or agent ID..."
|
|
196
|
+
value={searchTerm}
|
|
197
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
198
|
+
className="w-full rounded border border-gray-200 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none"
|
|
199
|
+
/>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
{/* Agents List */}
|
|
203
|
+
<div className="flex-1 overflow-auto">
|
|
204
|
+
{filteredAgents.length === 0 ? (
|
|
205
|
+
<div className="flex h-full items-center justify-center text-sm text-gray-500">
|
|
206
|
+
{searchTerm.trim()
|
|
207
|
+
? "No agents match your search"
|
|
208
|
+
: "No non-closed agents found"}
|
|
209
|
+
</div>
|
|
210
|
+
) : (
|
|
211
|
+
<div className="space-y-6 p-4">
|
|
212
|
+
{sortedGroups.map(([statusLabel, groupAgents]) => (
|
|
213
|
+
<div key={statusLabel}>
|
|
214
|
+
<h3 className="mb-2 text-xs font-semibold tracking-wide text-gray-500 uppercase">
|
|
215
|
+
{statusLabel} ({groupAgents.length})
|
|
216
|
+
</h3>
|
|
217
|
+
<div className="space-y-2">
|
|
218
|
+
{groupAgents.map((agent) => {
|
|
219
|
+
const statusConfig = getAgentStatusConfig(agent);
|
|
220
|
+
return (
|
|
221
|
+
<div
|
|
222
|
+
key={agent.id}
|
|
223
|
+
className="rounded-lg border border-gray-200 bg-white p-3 shadow-sm transition-shadow hover:shadow-md"
|
|
224
|
+
>
|
|
225
|
+
<div className="flex items-start justify-between">
|
|
226
|
+
<div className="min-w-0 flex-1">
|
|
227
|
+
{/* Agent Name & Status */}
|
|
228
|
+
<div className="mb-1 flex items-center gap-2">
|
|
229
|
+
<div
|
|
230
|
+
className={cn(
|
|
231
|
+
"h-2 w-2 flex-shrink-0 rounded-full",
|
|
232
|
+
statusConfig.color,
|
|
233
|
+
statusConfig.shouldPulse && "animate-pulse",
|
|
234
|
+
)}
|
|
235
|
+
title={statusConfig.label}
|
|
236
|
+
/>
|
|
237
|
+
<h4 className="truncate font-medium text-gray-900">
|
|
238
|
+
{agent.name}
|
|
239
|
+
</h4>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
{/* Agent Details */}
|
|
243
|
+
<div className="space-y-1 text-xs text-gray-600">
|
|
244
|
+
<div className="flex items-center gap-2">
|
|
245
|
+
<span className="font-medium">User:</span>
|
|
246
|
+
<span>{agent.userId || "Unknown"}</span>
|
|
247
|
+
</div>
|
|
248
|
+
<div className="flex items-center gap-2">
|
|
249
|
+
<span className="font-medium">ID:</span>
|
|
250
|
+
<span className="font-mono text-xs">
|
|
251
|
+
{agent.id}
|
|
252
|
+
</span>
|
|
253
|
+
</div>
|
|
254
|
+
<div className="flex items-center gap-2">
|
|
255
|
+
<span className="font-medium">Updated:</span>
|
|
256
|
+
<span>{formatDateTime(agent.updatedDate)}</span>
|
|
257
|
+
</div>
|
|
258
|
+
{agent.totalCost > 0 && (
|
|
259
|
+
<div className="flex items-center gap-2">
|
|
260
|
+
<span className="font-medium">Cost:</span>
|
|
261
|
+
<span>${agent.totalCost.toFixed(4)}</span>
|
|
262
|
+
</div>
|
|
263
|
+
)}
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
{/* Actions */}
|
|
268
|
+
<div className="ml-3 flex gap-1">
|
|
269
|
+
<SimpleIconButton
|
|
270
|
+
onClick={() =>
|
|
271
|
+
handleCloseAgent(agent.id, agent.name)
|
|
272
|
+
}
|
|
273
|
+
icon={<X className="size-3" strokeWidth={1.5} />}
|
|
274
|
+
label="Close Agent"
|
|
275
|
+
className="text-gray-600 opacity-60 hover:text-gray-800 hover:opacity-100"
|
|
276
|
+
/>
|
|
277
|
+
<SimpleIconButton
|
|
278
|
+
onClick={(e) =>
|
|
279
|
+
handleDeleteAgent(agent.id, agent.name, e)
|
|
280
|
+
}
|
|
281
|
+
icon={
|
|
282
|
+
<Trash className="size-3" strokeWidth={1.5} />
|
|
283
|
+
}
|
|
284
|
+
label="Delete Agent"
|
|
285
|
+
className="text-red-600 opacity-60 hover:text-red-700 hover:opacity-100"
|
|
286
|
+
/>
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
);
|
|
291
|
+
})}
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
))}
|
|
295
|
+
</div>
|
|
296
|
+
)}
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
@@ -53,6 +53,7 @@ export function WebSocketMessages() {
|
|
|
53
53
|
"suggested-edit-updated": "bg-indigo-100 text-indigo-800",
|
|
54
54
|
"suggested-edit-deleted": "bg-red-100 text-red-800",
|
|
55
55
|
"update-quota": "bg-gray-100 text-gray-800",
|
|
56
|
+
"agent-status-changed": "bg-violet-100 text-violet-800",
|
|
56
57
|
};
|
|
57
58
|
return colors[type] || "bg-gray-100 text-gray-800";
|
|
58
59
|
};
|