@alpaca-editor/core 1.0.4172 → 1.0.4174

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 (170) hide show
  1. package/dist/agents-view/AgentsView.d.ts +5 -0
  2. package/dist/agents-view/AgentsView.js +213 -0
  3. package/dist/agents-view/AgentsView.js.map +1 -0
  4. package/dist/components/ui/context-menu.js +4 -4
  5. package/dist/components/ui/context-menu.js.map +1 -1
  6. package/dist/config/config.js +56 -1
  7. package/dist/config/config.js.map +1 -1
  8. package/dist/editor/ConfirmationDialog.js +2 -1
  9. package/dist/editor/ConfirmationDialog.js.map +1 -1
  10. package/dist/editor/ContentTree.d.ts +2 -1
  11. package/dist/editor/ContentTree.js +18 -3
  12. package/dist/editor/ContentTree.js.map +1 -1
  13. package/dist/editor/ContextMenu.js +1 -1
  14. package/dist/editor/ContextMenu.js.map +1 -1
  15. package/dist/editor/FieldList.js +7 -3
  16. package/dist/editor/FieldList.js.map +1 -1
  17. package/dist/editor/FieldListField.d.ts +3 -2
  18. package/dist/editor/FieldListField.js +4 -4
  19. package/dist/editor/FieldListField.js.map +1 -1
  20. package/dist/editor/FieldListFieldWithFallbacks.d.ts +2 -1
  21. package/dist/editor/FieldListFieldWithFallbacks.js +5 -2
  22. package/dist/editor/FieldListFieldWithFallbacks.js.map +1 -1
  23. package/dist/editor/MainLayout.js +1 -1
  24. package/dist/editor/QuickItemSwitcher.d.ts +9 -0
  25. package/dist/editor/QuickItemSwitcher.js +74 -0
  26. package/dist/editor/QuickItemSwitcher.js.map +1 -0
  27. package/dist/editor/ai/AgentCostDisplay.js +7 -11
  28. package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
  29. package/dist/editor/ai/AgentProfilesOverview.d.ts +9 -0
  30. package/dist/editor/ai/AgentProfilesOverview.js +16 -0
  31. package/dist/editor/ai/AgentProfilesOverview.js.map +1 -0
  32. package/dist/editor/ai/AgentTerminal.js +285 -748
  33. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  34. package/dist/editor/ai/Agents.js +112 -54
  35. package/dist/editor/ai/Agents.js.map +1 -1
  36. package/dist/editor/ai/AiResponseMessage.d.ts +2 -1
  37. package/dist/editor/ai/AiResponseMessage.js +4 -2
  38. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  39. package/dist/editor/ai/ContextInfoBar.js +17 -17
  40. package/dist/editor/ai/useAgentStatus.js +7 -0
  41. package/dist/editor/ai/useAgentStatus.js.map +1 -1
  42. package/dist/editor/client/EditorShell.js +230 -4
  43. package/dist/editor/client/EditorShell.js.map +1 -1
  44. package/dist/editor/client/hooks/useSocketMessageHandler.js +0 -12
  45. package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
  46. package/dist/editor/client/ui/EditorChrome.js +1 -1
  47. package/dist/editor/client/ui/EditorChrome.js.map +1 -1
  48. package/dist/editor/commands/itemCommands.js +1 -0
  49. package/dist/editor/commands/itemCommands.js.map +1 -1
  50. package/dist/editor/control-center/parhelia-setup/Overview.d.ts +1 -0
  51. package/dist/editor/control-center/parhelia-setup/Overview.js +91 -0
  52. package/dist/editor/control-center/parhelia-setup/Overview.js.map +1 -0
  53. package/dist/editor/field-types/AttachmentEditor.js +3 -6
  54. package/dist/editor/field-types/AttachmentEditor.js.map +1 -1
  55. package/dist/editor/field-types/DropLinkEditor.js +2 -2
  56. package/dist/editor/field-types/DropLinkEditor.js.map +1 -1
  57. package/dist/editor/field-types/DropListEditor.js +2 -1
  58. package/dist/editor/field-types/DropListEditor.js.map +1 -1
  59. package/dist/editor/field-types/InternalLinkFieldEditor.js +3 -3
  60. package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
  61. package/dist/editor/field-types/MultiLineText.d.ts +2 -1
  62. package/dist/editor/field-types/MultiLineText.js +2 -2
  63. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  64. package/dist/editor/field-types/RawEditor.d.ts +2 -1
  65. package/dist/editor/field-types/RawEditor.js +2 -2
  66. package/dist/editor/field-types/RawEditor.js.map +1 -1
  67. package/dist/editor/field-types/SingleLineText.d.ts +2 -1
  68. package/dist/editor/field-types/SingleLineText.js +2 -2
  69. package/dist/editor/field-types/SingleLineText.js.map +1 -1
  70. package/dist/editor/field-types/TreeListEditor.js +9 -7
  71. package/dist/editor/field-types/TreeListEditor.js.map +1 -1
  72. package/dist/editor/media-selector/MediaFolderBrowser.js +68 -7
  73. package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
  74. package/dist/editor/media-selector/TreeSelector.js +1 -1
  75. package/dist/editor/media-selector/TreeSelector.js.map +1 -1
  76. package/dist/editor/menubar/ActiveUsers.js +2 -2
  77. package/dist/editor/menubar/FavoritesControls.js +2 -2
  78. package/dist/editor/menubar/FavoritesControls.js.map +1 -1
  79. package/dist/editor/page-editor-chrome/FrameMenu.js +10 -1
  80. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  81. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js +14 -0
  82. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js.map +1 -1
  83. package/dist/editor/services/agentService.d.ts +21 -3
  84. package/dist/editor/services/agentService.js +24 -8
  85. package/dist/editor/services/agentService.js.map +1 -1
  86. package/dist/editor/services/aiService.d.ts +6 -1
  87. package/dist/editor/services/aiService.js +36 -5
  88. package/dist/editor/services/aiService.js.map +1 -1
  89. package/dist/editor/services/contentService.d.ts +1 -1
  90. package/dist/editor/services/contentService.js +4 -2
  91. package/dist/editor/services/contentService.js.map +1 -1
  92. package/dist/editor/services/editService.d.ts +5 -1
  93. package/dist/editor/services/editService.js +1 -1
  94. package/dist/editor/services/editService.js.map +1 -1
  95. package/dist/editor/services/setupService.d.ts +21 -0
  96. package/dist/editor/services/setupService.js +10 -0
  97. package/dist/editor/services/setupService.js.map +1 -0
  98. package/dist/editor/sidebar/ComponentTree.js +15 -1
  99. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  100. package/dist/editor/sidebar/SidebarView.js +1 -1
  101. package/dist/editor/sidebar/SidebarView.js.map +1 -1
  102. package/dist/editor/ui/ItemSearch.d.ts +1 -0
  103. package/dist/editor/ui/ItemSearch.js +2 -2
  104. package/dist/editor/ui/ItemSearch.js.map +1 -1
  105. package/dist/editor/ui/PerfectTree.d.ts +5 -1
  106. package/dist/editor/ui/PerfectTree.js +308 -29
  107. package/dist/editor/ui/PerfectTree.js.map +1 -1
  108. package/dist/editor/utils/keyboardNavigation.d.ts +2 -0
  109. package/dist/editor/utils/keyboardNavigation.js +80 -2
  110. package/dist/editor/utils/keyboardNavigation.js.map +1 -1
  111. package/dist/editor/views/SingleEditView.js +6 -4
  112. package/dist/editor/views/SingleEditView.js.map +1 -1
  113. package/dist/revision.d.ts +2 -2
  114. package/dist/revision.js +2 -2
  115. package/dist/splash-screen/SplashScreen.js +78 -4
  116. package/dist/splash-screen/SplashScreen.js.map +1 -1
  117. package/dist/styles.css +157 -23
  118. package/dist/types.d.ts +13 -0
  119. package/package.json +1 -1
  120. package/src/agents-view/AgentsView.tsx +431 -0
  121. package/src/components/ui/context-menu.tsx +4 -4
  122. package/src/config/config.tsx +61 -0
  123. package/src/editor/ConfirmationDialog.tsx +42 -10
  124. package/src/editor/ContentTree.tsx +20 -1
  125. package/src/editor/ContextMenu.tsx +4 -1
  126. package/src/editor/FieldList.tsx +10 -4
  127. package/src/editor/FieldListField.tsx +7 -0
  128. package/src/editor/FieldListFieldWithFallbacks.tsx +10 -0
  129. package/src/editor/MainLayout.tsx +1 -1
  130. package/src/editor/QuickItemSwitcher.tsx +217 -0
  131. package/src/editor/ai/AgentCostDisplay.tsx +59 -60
  132. package/src/editor/ai/AgentProfilesOverview.tsx +81 -0
  133. package/src/editor/ai/AgentTerminal.tsx +321 -775
  134. package/src/editor/ai/Agents.tsx +157 -91
  135. package/src/editor/ai/AiResponseMessage.tsx +12 -1
  136. package/src/editor/ai/ContextInfoBar.tsx +17 -17
  137. package/src/editor/ai/useAgentStatus.ts +6 -0
  138. package/src/editor/client/EditorShell.tsx +288 -3
  139. package/src/editor/client/hooks/useSocketMessageHandler.ts +0 -15
  140. package/src/editor/client/ui/EditorChrome.tsx +1 -1
  141. package/src/editor/commands/itemCommands.tsx +1 -0
  142. package/src/editor/control-center/parhelia-setup/Overview.tsx +184 -0
  143. package/src/editor/field-types/AttachmentEditor.tsx +13 -50
  144. package/src/editor/field-types/DropLinkEditor.tsx +2 -2
  145. package/src/editor/field-types/DropListEditor.tsx +2 -1
  146. package/src/editor/field-types/InternalLinkFieldEditor.tsx +3 -3
  147. package/src/editor/field-types/MultiLineText.tsx +3 -0
  148. package/src/editor/field-types/RawEditor.tsx +3 -0
  149. package/src/editor/field-types/SingleLineText.tsx +3 -0
  150. package/src/editor/field-types/TreeListEditor.tsx +32 -24
  151. package/src/editor/media-selector/MediaFolderBrowser.tsx +93 -9
  152. package/src/editor/media-selector/TreeSelector.tsx +1 -0
  153. package/src/editor/menubar/ActiveUsers.tsx +2 -2
  154. package/src/editor/menubar/FavoritesControls.tsx +5 -5
  155. package/src/editor/page-editor-chrome/FrameMenu.tsx +10 -1
  156. package/src/editor/page-viewer/pageModelSkeletonBuilder.ts +17 -0
  157. package/src/editor/services/agentService.ts +46 -11
  158. package/src/editor/services/aiService.ts +48 -7
  159. package/src/editor/services/contentService.ts +4 -1
  160. package/src/editor/services/editService.ts +8 -3
  161. package/src/editor/services/setupService.ts +35 -0
  162. package/src/editor/sidebar/ComponentTree.tsx +16 -1
  163. package/src/editor/sidebar/SidebarView.tsx +1 -1
  164. package/src/editor/ui/ItemSearch.tsx +3 -1
  165. package/src/editor/ui/PerfectTree.tsx +393 -42
  166. package/src/editor/utils/keyboardNavigation.ts +97 -1
  167. package/src/editor/views/SingleEditView.tsx +27 -13
  168. package/src/revision.ts +2 -2
  169. package/src/splash-screen/SplashScreen.tsx +134 -2
  170. package/src/types.ts +15 -0
@@ -0,0 +1,431 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import {
3
+ AgentDetails,
4
+ getActiveAgents,
5
+ getAgent,
6
+ closeAgent,
7
+ deleteAgent,
8
+ } from "../editor/services/agentService";
9
+ import { getAgentStatusConfig } from "../editor/ai/AgentStatusBadge";
10
+ import { cn } from "../lib/utils";
11
+ import { Trash, X, RefreshCw, Play } from "lucide-react";
12
+ import { SimpleIconButton } from "../editor/ui/SimpleIconButton";
13
+ import { useEditContext } from "../editor/client/editContext";
14
+ import { SecretAgentIcon } from "../editor/ui/Icons";
15
+
16
+ /**
17
+ * Agents overview view that displays all non-closed agents for the current user
18
+ * grouped by profile with interactive controls
19
+ */
20
+ export function AgentsView() {
21
+ const editContext = useEditContext();
22
+ const [agents, setAgents] = useState<AgentDetails[]>([]);
23
+ const [loading, setLoading] = useState(true);
24
+ const [error, setError] = useState<string | null>(null);
25
+ const [refreshing, setRefreshing] = useState(false);
26
+ const [searchTerm, setSearchTerm] = useState("");
27
+
28
+ const loadAgents = async () => {
29
+ try {
30
+ setError(null);
31
+ // Get basic agent list
32
+ const basicAgents = await getActiveAgents(1000);
33
+
34
+ // Fetch full details for each agent
35
+ const detailsPromises = basicAgents.map((agent) =>
36
+ getAgent(agent.id).catch(() => null),
37
+ );
38
+ const agentDetails = await Promise.all(detailsPromises);
39
+
40
+ // Filter out any failed fetches and set the agents
41
+ const validAgents = agentDetails.filter(
42
+ (a): a is AgentDetails => a !== null,
43
+ );
44
+ setAgents(validAgents);
45
+ } catch (err: any) {
46
+ console.error("Failed to load agents:", err);
47
+ setError(
48
+ err?.message || "Failed to load agents. Please try again later.",
49
+ );
50
+ } finally {
51
+ setLoading(false);
52
+ setRefreshing(false);
53
+ }
54
+ };
55
+
56
+ useEffect(() => {
57
+ loadAgents();
58
+ }, []);
59
+
60
+ // Subscribe to websocket messages for real-time agent updates
61
+ useEffect(() => {
62
+ if (!editContext?.addSocketMessageListener) return;
63
+
64
+ const unsubscribe = editContext.addSocketMessageListener(
65
+ (message: { type: string; payload: any }) => {
66
+ if (message.type === "agent:run:start") {
67
+ const { agentId, agentName } = message.payload;
68
+
69
+ if (!agentId || !agentName) {
70
+ console.warn(
71
+ "Invalid agent:run:start message payload:",
72
+ message.payload,
73
+ );
74
+ return;
75
+ }
76
+
77
+ setAgents((prevAgents) => {
78
+ // Check if agent already exists
79
+ const existingAgentIndex = prevAgents.findIndex(
80
+ (agent) => agent.id === agentId,
81
+ );
82
+
83
+ if (existingAgentIndex !== -1) {
84
+ // Update existing agent
85
+ const updatedAgents = [...prevAgents];
86
+ const existingAgent = updatedAgents[existingAgentIndex]!;
87
+ updatedAgents[existingAgentIndex] = {
88
+ ...existingAgent,
89
+ name: agentName,
90
+ status: "running" as const,
91
+ updatedDate: new Date().toISOString(),
92
+ };
93
+ return updatedAgents;
94
+ } else {
95
+ // Add new agent - reload to get full details
96
+ loadAgents();
97
+ return prevAgents;
98
+ }
99
+ });
100
+ } else if (message.type === "agent:status:changed") {
101
+ // Update agent status in real-time
102
+ const { agentId, status } = message.payload || {};
103
+ if (!agentId) return;
104
+
105
+ setAgents((prevAgents) => {
106
+ const existingIndex = prevAgents.findIndex((a) => a.id === agentId);
107
+
108
+ if (existingIndex !== -1) {
109
+ const updatedAgents = [...prevAgents];
110
+ const existingAgent = updatedAgents[existingIndex]!;
111
+ updatedAgents[existingIndex] = {
112
+ ...existingAgent,
113
+ status,
114
+ updatedDate: new Date().toISOString(),
115
+ };
116
+ return updatedAgents;
117
+ }
118
+
119
+ return prevAgents;
120
+ });
121
+ } else if (message.type === "agent:run:closed") {
122
+ // Remove agent from list when closed
123
+ const { agentId } = message.payload || {};
124
+ if (!agentId) return;
125
+
126
+ setAgents((prevAgents) => prevAgents.filter((a) => a.id !== agentId));
127
+ }
128
+ },
129
+ );
130
+
131
+ return unsubscribe;
132
+ }, [editContext?.addSocketMessageListener]);
133
+
134
+ const handleRefresh = async () => {
135
+ setRefreshing(true);
136
+ await loadAgents();
137
+ };
138
+
139
+ const handleResumeAgent = (agent: AgentDetails) => {
140
+ editContext?.switchView("page-editor");
141
+ editContext?.setShowAgentsPanel?.(true);
142
+ // Delay for panel mount, then dispatch event to open existing agent
143
+ setTimeout(() => {
144
+ window.dispatchEvent(
145
+ new CustomEvent("editor:openAgent", {
146
+ detail: { agentId: agent.id },
147
+ }),
148
+ );
149
+ }, 500);
150
+ };
151
+
152
+ const handleCloseAgent = async (agentId: string, agentName: string) => {
153
+ editContext?.confirm({
154
+ header: "Close Agent",
155
+ message: `Are you sure you want to close "${agentName}"? This will stop its execution if running.`,
156
+ acceptLabel: "Close Agent",
157
+ accept: async () => {
158
+ try {
159
+ await closeAgent(agentId);
160
+ setAgents((prev) => prev.filter((a) => a.id !== agentId));
161
+ } catch (err: any) {
162
+ console.error("Failed to close agent:", err);
163
+ editContext?.showToast(
164
+ `Failed to close agent: ${err?.message || "Unknown error"}`,
165
+ );
166
+ }
167
+ },
168
+ showCancel: true,
169
+ });
170
+ };
171
+
172
+ const handleDeleteAgent = async (
173
+ agentId: string,
174
+ agentName: string,
175
+ event: React.MouseEvent,
176
+ ) => {
177
+ event.stopPropagation();
178
+
179
+ editContext?.confirm({
180
+ header: "Delete Agent",
181
+ message: `Are you sure you want to permanently delete "${agentName}"? This action cannot be undone.`,
182
+ acceptLabel: "Delete Agent",
183
+ accept: async () => {
184
+ try {
185
+ await deleteAgent(agentId);
186
+ setAgents((prev) => prev.filter((a) => a.id !== agentId));
187
+ } catch (err: any) {
188
+ console.error("Failed to delete agent:", err);
189
+ editContext?.showToast(
190
+ `Failed to delete agent: ${err?.message || "Unknown error"}`,
191
+ );
192
+ }
193
+ },
194
+ showCancel: true,
195
+ });
196
+ };
197
+
198
+ const formatDateTime = (dateString: string) => {
199
+ try {
200
+ const date = new Date(dateString);
201
+ return date.toLocaleString();
202
+ } catch {
203
+ return dateString;
204
+ }
205
+ };
206
+
207
+ const filteredAgents = agents.filter((agent) => {
208
+ if (!searchTerm.trim()) return true;
209
+ const search = searchTerm.toLowerCase();
210
+ return (
211
+ agent.name.toLowerCase().includes(search) ||
212
+ agent.profileName?.toLowerCase().includes(search) ||
213
+ agent.id.toLowerCase().includes(search)
214
+ );
215
+ });
216
+
217
+ // Group agents by profile
218
+ const groupedAgents = filteredAgents.reduce(
219
+ (acc, agent) => {
220
+ const profileName = agent.profileName || "Unknown Profile";
221
+ if (!acc[profileName]) {
222
+ acc[profileName] = [];
223
+ }
224
+ acc[profileName].push(agent);
225
+ return acc;
226
+ },
227
+ {} as Record<string, AgentDetails[]>,
228
+ );
229
+
230
+ // Sort profile groups by name
231
+ const sortedProfileGroups = Object.entries(groupedAgents).sort(([a], [b]) =>
232
+ a.localeCompare(b),
233
+ );
234
+
235
+ if (loading) {
236
+ return (
237
+ <div className="flex h-full items-center justify-center">
238
+ <div className="text-sm text-gray-500">Loading agents...</div>
239
+ </div>
240
+ );
241
+ }
242
+
243
+ if (error) {
244
+ return (
245
+ <div className="flex h-full items-center justify-center">
246
+ <div className="max-w-md text-center">
247
+ <div className="mb-4 text-sm text-red-600">{error}</div>
248
+ <button
249
+ onClick={handleRefresh}
250
+ className="rounded bg-blue-500 px-4 py-2 text-xs text-white hover:bg-blue-600"
251
+ >
252
+ Retry
253
+ </button>
254
+ </div>
255
+ </div>
256
+ );
257
+ }
258
+
259
+ return (
260
+ <div className="flex h-full flex-col">
261
+ {/* Header */}
262
+ <div className="border-b border-gray-200 bg-white p-4">
263
+ <div className="mb-3 flex items-center justify-between">
264
+ <div>
265
+ <h2 className="text-lg font-semibold text-gray-900">My Agents</h2>
266
+ <p className="text-xs text-gray-500">
267
+ Showing {filteredAgents.length} active agent
268
+ {filteredAgents.length !== 1 ? "s" : ""}
269
+ </p>
270
+ </div>
271
+ <SimpleIconButton
272
+ onClick={handleRefresh}
273
+ icon={
274
+ <RefreshCw
275
+ className={cn("size-4", refreshing && "animate-spin")}
276
+ strokeWidth={1.5}
277
+ />
278
+ }
279
+ label="Refresh"
280
+ disabled={refreshing}
281
+ className="text-gray-600 hover:text-gray-800"
282
+ />
283
+ </div>
284
+
285
+ {/* Search */}
286
+ <input
287
+ type="text"
288
+ placeholder="Search by name, profile, or agent ID..."
289
+ value={searchTerm}
290
+ onChange={(e) => setSearchTerm(e.target.value)}
291
+ className="w-full rounded border border-gray-200 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none"
292
+ />
293
+ </div>
294
+
295
+ {/* Agents List */}
296
+ <div className="flex-1 overflow-auto">
297
+ {filteredAgents.length === 0 ? (
298
+ <div className="flex h-full items-center justify-center text-sm text-gray-500">
299
+ {searchTerm.trim()
300
+ ? "No agents match your search"
301
+ : "No active agents found"}
302
+ </div>
303
+ ) : (
304
+ <div className="space-y-6 p-4">
305
+ {sortedProfileGroups.map(([profileName, profileAgents]) => (
306
+ <div key={profileName}>
307
+ <h3 className="mb-2 text-xs font-semibold tracking-wide text-gray-500 uppercase">
308
+ {profileName} ({profileAgents.length})
309
+ </h3>
310
+ <div className="space-y-2">
311
+ {profileAgents.map((agent) => {
312
+ const statusConfig = getAgentStatusConfig(agent);
313
+ return (
314
+ <div
315
+ key={agent.id}
316
+ className="cursor-pointer rounded-lg border border-gray-200 bg-white p-3 shadow-sm transition-shadow hover:shadow-md"
317
+ onClick={() => {
318
+ // Only select the agent in the agents panel; do not switch views
319
+ window.dispatchEvent(
320
+ new CustomEvent("editor:selectAgent", {
321
+ detail: { agentId: agent.id },
322
+ }),
323
+ );
324
+ }}
325
+ >
326
+ <div className="flex items-start justify-between">
327
+ {/* Agent Icon */}
328
+ <div className="mr-3 flex-shrink-0">
329
+ {agent.profileSvgIcon ? (
330
+ <div
331
+ className="flex h-6 w-6 items-center justify-center text-gray-400 [&>svg]:h-full [&>svg]:w-full"
332
+ dangerouslySetInnerHTML={{
333
+ __html: agent.profileSvgIcon,
334
+ }}
335
+ />
336
+ ) : (
337
+ <SecretAgentIcon
338
+ size={24}
339
+ strokeWidth={1}
340
+ className="text-gray-400"
341
+ />
342
+ )}
343
+ </div>
344
+
345
+ <div className="min-w-0 flex-1">
346
+ {/* Agent Name & Status */}
347
+ <div className="mb-1 flex items-center gap-2">
348
+ <div
349
+ className={cn(
350
+ "h-2 w-2 flex-shrink-0 rounded-full",
351
+ statusConfig.color,
352
+ statusConfig.shouldPulse && "animate-pulse",
353
+ )}
354
+ title={statusConfig.label}
355
+ />
356
+ <h4 className="truncate font-medium text-gray-900">
357
+ {agent.name}
358
+ </h4>
359
+ </div>
360
+
361
+ {/* Agent Details */}
362
+ <div className="space-y-1 text-xs text-gray-600">
363
+ <div className="flex items-center gap-2">
364
+ <span className="font-medium">Updated:</span>
365
+ <span>{formatDateTime(agent.updatedDate)}</span>
366
+ </div>
367
+ {agent.messageCount > 0 && (
368
+ <div className="flex items-center gap-2">
369
+ <span className="font-medium">Messages:</span>
370
+ <span>{agent.messageCount}</span>
371
+ </div>
372
+ )}
373
+ {agent.totalCost > 0 && (
374
+ <div className="flex items-center gap-2">
375
+ <span className="font-medium">Cost:</span>
376
+ <span>${agent.totalCost.toFixed(4)}</span>
377
+ <span className="text-gray-400">
378
+ ({agent.totalTokensUsed.toLocaleString()}{" "}
379
+ tokens)
380
+ </span>
381
+ </div>
382
+ )}
383
+ </div>
384
+ </div>
385
+
386
+ {/* Actions */}
387
+ <div className="ml-3 flex gap-1">
388
+ <SimpleIconButton
389
+ onClick={(e) => {
390
+ e.stopPropagation();
391
+ handleResumeAgent(agent);
392
+ }}
393
+ icon={
394
+ <Play className="size-3" strokeWidth={1.5} />
395
+ }
396
+ label="Resume Agent"
397
+ className="text-blue-600 opacity-60 hover:text-blue-700 hover:opacity-100"
398
+ />
399
+ <SimpleIconButton
400
+ onClick={(e) => {
401
+ e.stopPropagation();
402
+ handleCloseAgent(agent.id, agent.name);
403
+ }}
404
+ icon={<X className="size-3" strokeWidth={1.5} />}
405
+ label="Close Agent"
406
+ className="text-gray-600 opacity-60 hover:text-gray-800 hover:opacity-100"
407
+ />
408
+ <SimpleIconButton
409
+ onClick={(e) =>
410
+ handleDeleteAgent(agent.id, agent.name, e)
411
+ }
412
+ icon={
413
+ <Trash className="size-3" strokeWidth={1.5} />
414
+ }
415
+ label="Delete Agent"
416
+ className="text-red-600 opacity-60 hover:text-red-700 hover:opacity-100"
417
+ />
418
+ </div>
419
+ </div>
420
+ </div>
421
+ );
422
+ })}
423
+ </div>
424
+ </div>
425
+ ))}
426
+ </div>
427
+ )}
428
+ </div>
429
+ </div>
430
+ );
431
+ }
@@ -326,7 +326,7 @@ function ContextMenuItem({
326
326
  data-variant={variant}
327
327
  data-disabled={disabled ? "" : undefined}
328
328
  className={cn(
329
- "hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive relative flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-xs outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
329
+ "hover:text-accent-foreground focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive relative flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-xs outline-hidden select-none hover:bg-[#f6eeff] focus:bg-[#f6eeff] data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
330
330
  className,
331
331
  )}
332
332
  onMouseDown={handleMouseDown}
@@ -361,7 +361,7 @@ function ContextMenuCheckboxItem({
361
361
  data-testid="context-menu-checkbox-item"
362
362
  data-disabled={(rest as any).disabled ? "" : undefined}
363
363
  className={cn(
364
- "hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground relative flex cursor-pointer items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-xs outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
364
+ "hover:text-accent-foreground focus:text-accent-foreground relative flex cursor-pointer items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-xs outline-hidden select-none hover:bg-[#f6eeff] focus:bg-[#f6eeff] data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
365
365
  className,
366
366
  )}
367
367
  onClick={handle}
@@ -410,7 +410,7 @@ function ContextMenuRadioItem({
410
410
  data-testid="context-menu-radio-item"
411
411
  data-disabled={(rest as any).disabled ? "" : undefined}
412
412
  className={cn(
413
- "hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground relative flex cursor-pointer items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-xs outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
413
+ "hover:text-accent-foreground focus:text-accent-foreground relative flex cursor-pointer items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-xs outline-hidden select-none hover:bg-[#f6eeff] focus:bg-[#f6eeff] data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
414
414
  className,
415
415
  )}
416
416
  onClick={handle}
@@ -539,7 +539,7 @@ function ContextMenuSubTrigger({
539
539
  data-inset={inset}
540
540
  data-disabled={disabled ? "" : undefined}
541
541
  className={cn(
542
- "hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-xs outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
542
+ "hover:text-accent-foreground focus:text-accent-foreground flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-xs outline-hidden select-none hover:bg-[#f6eeff] focus:bg-[#f6eeff] data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
543
543
  className,
544
544
  )}
545
545
  onMouseEnter={openSub}
@@ -47,6 +47,7 @@ import { Titlebar } from "../editor/Titlebar";
47
47
  import { NoWriteLanguageAccess } from "../editor/editor-warnings/NoLanguageWriteAccess";
48
48
  import { NoWorkflowWriteAccess } from "../editor/editor-warnings/NoWorkflowWriteAccess";
49
49
  import { SplashScreen } from "../splash-screen/SplashScreen";
50
+ import { AgentsView } from "../agents-view/AgentsView";
50
51
  import { createEditToolbar } from "../editor/menubar/ToolbarFactory";
51
52
 
52
53
  import { Validation } from "../editor/sidebar/Validation";
@@ -81,6 +82,7 @@ import { About } from "../editor/control-center/About";
81
82
  import { WebSocketMessages } from "../editor/control-center/WebSocketMessages";
82
83
  import { LatestFeedback } from "../editor/control-center/LatestFeedback";
83
84
  import { AllAgentsPanel } from "../editor/control-center/AllAgentsPanel";
85
+ import { ParheliaSetupOverview } from "../editor/control-center/parhelia-setup/Overview";
84
86
  import { DbSetupStep } from "../editor/control-center/setup-steps/DbSetupStep";
85
87
  import { SettingsSetupStep } from "../editor/control-center/setup-steps/SettingsSetupStep";
86
88
  import { AiSetupStep } from "../editor/control-center/setup-steps/AiSetupStep";
@@ -137,6 +139,8 @@ import {
137
139
  Video,
138
140
  X,
139
141
  Expand,
142
+ Download,
143
+ Bot,
140
144
  } from "lucide-react";
141
145
 
142
146
  import { Completions } from "../editor/sidebar/Completions";
@@ -360,6 +364,47 @@ const openImageMediaItemButton: ClientFieldButton = {
360
364
  description: "Opens the media item in the content editor",
361
365
  };
362
366
 
367
+ const openAttachmentButton: ClientFieldButton = {
368
+ label: "Open",
369
+ icon: (<ExternalLink strokeWidth={1} size={16} />) as React.ReactNode,
370
+ visible: ({ field }) => !!(field.value as any)?.src,
371
+ clientAction: ({ field }) => {
372
+ const src = (field.value as any)?.src;
373
+ if (src) {
374
+ window.open(src, "_blank", "noopener,noreferrer");
375
+ }
376
+ },
377
+ isGenerator: false,
378
+ id: "open-attachment",
379
+ description: "Opens the attachment in a new tab",
380
+ };
381
+
382
+ const downloadAttachmentButton: ClientFieldButton = {
383
+ label: "Download",
384
+ icon: (<Download strokeWidth={1} size={16} />) as React.ReactNode,
385
+ visible: ({ field }) => !!(field.value as any)?.src,
386
+ clientAction: ({ field }) => {
387
+ const src = (field.value as any)?.src;
388
+ const fileName = (field.value as any)?.fileName;
389
+ const mimeType = (field.value as any)?.mimeType;
390
+
391
+ if (src) {
392
+ const link = document.createElement("a");
393
+ link.href = src;
394
+ link.download = fileName || "";
395
+ if (mimeType) {
396
+ link.type = mimeType;
397
+ }
398
+ document.body.appendChild(link);
399
+ link.click();
400
+ document.body.removeChild(link);
401
+ }
402
+ },
403
+ isGenerator: false,
404
+ id: "download-attachment",
405
+ description: "Downloads the attachment file",
406
+ };
407
+
363
408
  const pageEditorRightSidebar = {
364
409
  title: "COMPONENT NAVIGATOR & HISTORY",
365
410
  panels: [
@@ -576,6 +621,7 @@ export const getConfiguration = (): EditorConfiguration => {
576
621
  },
577
622
  attachment: {
578
623
  editor: AttachmentEditor,
624
+ buttons: [openAttachmentButton, downloadAttachmentButton],
579
625
  },
580
626
  },
581
627
  editorWarnings: [
@@ -628,6 +674,12 @@ export const getConfiguration = (): EditorConfiguration => {
628
674
  icon: <Settings strokeWidth={1} />,
629
675
  content: <Setup />,
630
676
  },
677
+ {
678
+ id: "parhelia-setup",
679
+ title: "Parhelia Setup",
680
+ icon: <Settings strokeWidth={1} />,
681
+ content: <ParheliaSetupOverview />,
682
+ },
631
683
  {
632
684
  id: "info",
633
685
  title: "System Info",
@@ -707,6 +759,15 @@ export const getConfiguration = (): EditorConfiguration => {
707
759
  defaultCenterPanelView: <NewPage />,
708
760
  hideViewSelector: true,
709
761
  },
762
+ {
763
+ name: "agents-overview",
764
+ title: "My Agents",
765
+ icon: <Bot strokeWidth={1} />,
766
+
767
+ defaultCenterPanelView: <AgentsView />,
768
+ hideViewSelector: false,
769
+ menuBar: <></>,
770
+ },
710
771
  {
711
772
  name: "page-editor",
712
773
  title: "Edit",
@@ -7,6 +7,7 @@ import {
7
7
  DialogFooter,
8
8
  } from "../components/ui/dialog";
9
9
  import { Button } from "../components/ui/button";
10
+ import { AlertTriangle } from "lucide-react";
10
11
 
11
12
  export type ConfirmationDialogProps = {};
12
13
 
@@ -62,29 +63,60 @@ const ConfirmationDialog = forwardRef<
62
63
  return (
63
64
  <Dialog open={visible} onOpenChange={setVisible}>
64
65
  <DialogContent
65
- className="sm:max-w-[425px]"
66
+ className="gap-0 p-0 sm:max-w-[480px]"
66
67
  data-testid="confirmation-dialog"
67
68
  >
68
- <DialogHeader>
69
- <DialogTitle>{props?.header || "Confirm"}</DialogTitle>
69
+ <DialogHeader className="space-y-0 px-6 pt-3 pb-2">
70
+ <DialogTitle className="text-lg font-semibold">
71
+ {props?.header || "Confirmation"}
72
+ </DialogTitle>
70
73
  </DialogHeader>
71
- <div className="flex items-center gap-3 p-3">
72
- {props?.icon && <div className="text-2xl">{props.icon}</div>}
73
- <div className="flex-1">{props?.message}</div>
74
+ <div className="px-6 py-4">
75
+ <div className="flex gap-4">
76
+ {props?.icon ? (
77
+ <div className="flex-shrink-0 pt-0.5 text-amber-500">
78
+ {props.icon}
79
+ </div>
80
+ ) : (
81
+ <div className="flex-shrink-0 pt-0.5">
82
+ <AlertTriangle
83
+ className="h-5 w-5 text-amber-500"
84
+ strokeWidth={1.5}
85
+ />
86
+ </div>
87
+ )}
88
+ <div className="flex-1 space-y-2">
89
+ <div className="text-sm leading-relaxed text-gray-700">
90
+ {props?.message}
91
+ </div>
92
+ </div>
93
+ </div>
74
94
  </div>
75
- <DialogFooter className="gap-2 border-t p-2">
95
+ <DialogFooter className="gap-2 border-t bg-gray-50 px-6 py-3 sm:gap-2">
76
96
  {props?.showCancel && (
77
- <Button variant="ghost" onClick={handleCancel}>
97
+ <Button
98
+ variant="outline"
99
+ onClick={handleCancel}
100
+ className="min-w-[90px]"
101
+ >
78
102
  Cancel
79
103
  </Button>
80
104
  )}
81
105
  {props?.rejectLabel && (
82
- <Button variant="destructive" onClick={handleReject}>
106
+ <Button
107
+ variant="outline"
108
+ onClick={handleReject}
109
+ className="min-w-[90px]"
110
+ >
83
111
  {props.rejectLabel}
84
112
  </Button>
85
113
  )}
86
114
  {showAccept && (
87
- <Button onClick={handleAccept}>
115
+ <Button
116
+ onClick={handleAccept}
117
+ variant="destructive"
118
+ className="min-w-[90px]"
119
+ >
88
120
  {props?.acceptLabel || "Yes"}
89
121
  </Button>
90
122
  )}