@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.
Files changed (149) hide show
  1. package/dist/components/index.d.ts +2 -0
  2. package/dist/components/index.js +1 -0
  3. package/dist/components/index.js.map +1 -1
  4. package/dist/components/ui/select.d.ts +2 -1
  5. package/dist/components/ui/select.js +2 -2
  6. package/dist/components/ui/select.js.map +1 -1
  7. package/dist/components/ui/tabs.d.ts +17 -0
  8. package/dist/components/ui/tabs.js +27 -0
  9. package/dist/components/ui/tabs.js.map +1 -0
  10. package/dist/config/config.js +7 -1
  11. package/dist/config/config.js.map +1 -1
  12. package/dist/editor/FieldListField.js +3 -4
  13. package/dist/editor/FieldListField.js.map +1 -1
  14. package/dist/editor/ImageEditButton.d.ts +2 -1
  15. package/dist/editor/ImageEditButton.js +4 -4
  16. package/dist/editor/ImageEditButton.js.map +1 -1
  17. package/dist/editor/PictureEditor.js +42 -1
  18. package/dist/editor/PictureEditor.js.map +1 -1
  19. package/dist/editor/Terminal.js +1 -1
  20. package/dist/editor/Terminal.js.map +1 -1
  21. package/dist/editor/Titlebar.js +0 -1
  22. package/dist/editor/Titlebar.js.map +1 -1
  23. package/dist/editor/ai/AgentCostDisplay.d.ts +3 -1
  24. package/dist/editor/ai/AgentCostDisplay.js +26 -2
  25. package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
  26. package/dist/editor/ai/AgentStatusBadge.d.ts +26 -0
  27. package/dist/editor/ai/AgentStatusBadge.js +110 -0
  28. package/dist/editor/ai/AgentStatusBadge.js.map +1 -0
  29. package/dist/editor/ai/AgentTerminal.js +289 -198
  30. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  31. package/dist/editor/ai/Agents.d.ts +2 -2
  32. package/dist/editor/ai/Agents.js +115 -19
  33. package/dist/editor/ai/Agents.js.map +1 -1
  34. package/dist/editor/ai/AiResponseMessage.js +259 -45
  35. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  36. package/dist/editor/ai/ContextInfoBar.js +124 -113
  37. package/dist/editor/ai/ContextInfoBar.js.map +1 -1
  38. package/dist/editor/ai/ToolCallDisplay.d.ts +1 -0
  39. package/dist/editor/ai/ToolCallDisplay.js +70 -58
  40. package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
  41. package/dist/editor/ai/useAgentStatus.d.ts +13 -0
  42. package/dist/editor/ai/useAgentStatus.js +101 -0
  43. package/dist/editor/ai/useAgentStatus.js.map +1 -0
  44. package/dist/editor/client/EditorShell.js +23 -8
  45. package/dist/editor/client/EditorShell.js.map +1 -1
  46. package/dist/editor/client/itemsRepository.js.map +1 -1
  47. package/dist/editor/commands/localizeItem/LocalizeItemDialog.js +5 -5
  48. package/dist/editor/commands/localizeItem/LocalizeItemDialog.js.map +1 -1
  49. package/dist/editor/control-center/About.js +1 -1
  50. package/dist/editor/control-center/About.js.map +1 -1
  51. package/dist/editor/control-center/AllAgentsPanel.d.ts +5 -0
  52. package/dist/editor/control-center/AllAgentsPanel.js +126 -0
  53. package/dist/editor/control-center/AllAgentsPanel.js.map +1 -0
  54. package/dist/editor/control-center/WebSocketMessages.js +1 -0
  55. package/dist/editor/control-center/WebSocketMessages.js.map +1 -1
  56. package/dist/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.js +42 -7
  57. package/dist/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.js.map +1 -1
  58. package/dist/editor/media-selector/AiImageSearch.d.ts +1 -1
  59. package/dist/editor/media-selector/AiImageSearch.js +162 -103
  60. package/dist/editor/media-selector/AiImageSearch.js.map +1 -1
  61. package/dist/editor/media-selector/TreeSelector.js +20 -4
  62. package/dist/editor/media-selector/TreeSelector.js.map +1 -1
  63. package/dist/editor/menubar/toolbar-sections/UtilityControls.js +5 -2
  64. package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
  65. package/dist/editor/page-editor-chrome/PlaceholderDropZone.d.ts +1 -1
  66. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +7 -5
  67. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
  68. package/dist/editor/page-viewer/DeviceToolbar.js +2 -2
  69. package/dist/editor/page-viewer/DeviceToolbar.js.map +1 -1
  70. package/dist/editor/page-viewer/PageViewerFrame.js +18 -11
  71. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  72. package/dist/editor/services/agentService.d.ts +53 -48
  73. package/dist/editor/services/agentService.js +137 -79
  74. package/dist/editor/services/agentService.js.map +1 -1
  75. package/dist/editor/services/aiService.d.ts +1 -1
  76. package/dist/editor/services/editService.js +1 -0
  77. package/dist/editor/services/editService.js.map +1 -1
  78. package/dist/editor/sidebar/GraphQL.js +20 -7
  79. package/dist/editor/sidebar/GraphQL.js.map +1 -1
  80. package/dist/editor/sidebar/SEOInfo.js +1 -2
  81. package/dist/editor/sidebar/SEOInfo.js.map +1 -1
  82. package/dist/editor/sidebar/Translations.js +10 -7
  83. package/dist/editor/sidebar/Translations.js.map +1 -1
  84. package/dist/editor/ui/ItemNameDialogNew.js +1 -1
  85. package/dist/editor/ui/ItemSearch.js +10 -4
  86. package/dist/editor/ui/ItemSearch.js.map +1 -1
  87. package/dist/index.d.ts +5 -1
  88. package/dist/index.js +4 -1
  89. package/dist/index.js.map +1 -1
  90. package/dist/page-wizard/steps/CollectStep.js +2 -2
  91. package/dist/page-wizard/steps/CollectStep.js.map +1 -1
  92. package/dist/page-wizard/steps/FieldEditor.js +2 -2
  93. package/dist/page-wizard/steps/FieldEditor.js.map +1 -1
  94. package/dist/revision.d.ts +2 -2
  95. package/dist/revision.js +2 -2
  96. package/dist/splash-screen/NewPage.js +2 -2
  97. package/dist/splash-screen/NewPage.js.map +1 -1
  98. package/dist/splash-screen/RecentPages.js +1 -1
  99. package/dist/splash-screen/RecentPages.js.map +1 -1
  100. package/dist/styles.css +167 -15
  101. package/dist/tour/Tour.js +15 -11
  102. package/dist/tour/Tour.js.map +1 -1
  103. package/package.json +1 -1
  104. package/src/components/index.ts +2 -0
  105. package/src/components/ui/select.tsx +3 -0
  106. package/src/components/ui/tabs.tsx +87 -0
  107. package/src/config/config.tsx +7 -1
  108. package/src/editor/FieldListField.tsx +13 -13
  109. package/src/editor/ImageEditButton.tsx +5 -3
  110. package/src/editor/PictureEditor.tsx +48 -1
  111. package/src/editor/Terminal.tsx +1 -1
  112. package/src/editor/Titlebar.tsx +0 -1
  113. package/src/editor/ai/AgentCostDisplay.tsx +57 -1
  114. package/src/editor/ai/AgentStatusBadge.tsx +144 -0
  115. package/src/editor/ai/AgentTerminal.tsx +345 -219
  116. package/src/editor/ai/Agents.tsx +179 -30
  117. package/src/editor/ai/AiResponseMessage.tsx +411 -114
  118. package/src/editor/ai/ContextInfoBar.tsx +134 -131
  119. package/src/editor/ai/ToolCallDisplay.tsx +217 -176
  120. package/src/editor/ai/useAgentStatus.ts +123 -0
  121. package/src/editor/client/EditorShell.tsx +34 -8
  122. package/src/editor/client/itemsRepository.ts +1 -2
  123. package/src/editor/commands/localizeItem/LocalizeItemDialog.tsx +5 -5
  124. package/src/editor/control-center/About.tsx +0 -14
  125. package/src/editor/control-center/AllAgentsPanel.tsx +300 -0
  126. package/src/editor/control-center/WebSocketMessages.tsx +1 -0
  127. package/src/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.tsx +49 -8
  128. package/src/editor/media-selector/AiImageSearch.tsx +162 -172
  129. package/src/editor/media-selector/TreeSelector.tsx +137 -116
  130. package/src/editor/menubar/toolbar-sections/UtilityControls.tsx +9 -1
  131. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +7 -4
  132. package/src/editor/page-viewer/DeviceToolbar.tsx +15 -11
  133. package/src/editor/page-viewer/PageViewerFrame.tsx +20 -14
  134. package/src/editor/services/agentService.ts +217 -129
  135. package/src/editor/services/aiService.ts +2 -2
  136. package/src/editor/services/editService.ts +1 -0
  137. package/src/editor/sidebar/GraphQL.tsx +143 -117
  138. package/src/editor/sidebar/SEOInfo.tsx +1 -2
  139. package/src/editor/sidebar/Translations.tsx +14 -12
  140. package/src/editor/ui/ItemNameDialogNew.tsx +1 -1
  141. package/src/editor/ui/ItemSearch.tsx +11 -4
  142. package/src/editor/ui/SimpleTabs.tsx +1 -1
  143. package/src/index.ts +6 -0
  144. package/src/page-wizard/steps/CollectStep.tsx +2 -2
  145. package/src/page-wizard/steps/FieldEditor.tsx +13 -15
  146. package/src/revision.ts +2 -2
  147. package/src/splash-screen/NewPage.tsx +2 -2
  148. package/src/splash-screen/RecentPages.tsx +1 -1
  149. 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 === currentItemDescriptor.id &&
861
- item.language === currentItemDescriptor.language &&
862
- item.version === currentItemDescriptor.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, currentItemDescriptor, sessionId]);
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
- op.message = data.responseText;
1873
- setActiveFieldActions((prevFieldActions) => [
1874
- ...prevFieldActions,
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 "primereact/checkbox";
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
- onChange={(e) => {
153
+ onCheckedChange={(checked) => {
154
154
  setLanguageSelection(
155
155
  Object.fromEntries(
156
156
  Array.from(languageData?.keys() || []).map((language) => [
157
157
  language,
158
- e.checked || false,
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
- onChange={(e) => {
175
+ onCheckedChange={(checked) => {
176
176
  setLanguageSelection((prev) => ({
177
177
  ...prev,
178
- [language.code]: e.checked || false,
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
  };