@alpaca-editor/core 1.0.4184 → 1.0.4185

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 (43) hide show
  1. package/dist/agents-view/AgentCard.d.ts +12 -0
  2. package/dist/agents-view/AgentCard.js +30 -0
  3. package/dist/agents-view/AgentCard.js.map +1 -0
  4. package/dist/agents-view/AgentsView.d.ts +2 -2
  5. package/dist/agents-view/AgentsView.js +102 -85
  6. package/dist/agents-view/AgentsView.js.map +1 -1
  7. package/dist/agents-view/ProfileAgentsGroup.d.ts +17 -0
  8. package/dist/agents-view/ProfileAgentsGroup.js +13 -0
  9. package/dist/agents-view/ProfileAgentsGroup.js.map +1 -0
  10. package/dist/editor/ai/AgentProfilesOverview.js +1 -1
  11. package/dist/editor/ai/AgentProfilesOverview.js.map +1 -1
  12. package/dist/editor/commands/componentCommands.js +19 -1
  13. package/dist/editor/commands/componentCommands.js.map +1 -1
  14. package/dist/editor/page-editor-chrome/FrameMenu.js +7 -3
  15. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  16. package/dist/editor/page-viewer/PageViewerFrame.js +7 -28
  17. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  18. package/dist/editor/reviews/Comment.js +56 -4
  19. package/dist/editor/reviews/Comment.js.map +1 -1
  20. package/dist/editor/services/agentService.d.ts +19 -0
  21. package/dist/editor/services/agentService.js +39 -0
  22. package/dist/editor/services/agentService.js.map +1 -1
  23. package/dist/editor/services/serviceHelper.d.ts +1 -0
  24. package/dist/editor/services/serviceHelper.js +58 -4
  25. package/dist/editor/services/serviceHelper.js.map +1 -1
  26. package/dist/revision.d.ts +2 -2
  27. package/dist/revision.js +2 -2
  28. package/dist/styles.css +4 -6
  29. package/dist/tour/default-tour.js +24 -9
  30. package/dist/tour/default-tour.js.map +1 -1
  31. package/package.json +1 -1
  32. package/src/agents-view/AgentCard.tsx +162 -0
  33. package/src/agents-view/AgentsView.tsx +218 -253
  34. package/src/agents-view/ProfileAgentsGroup.tsx +123 -0
  35. package/src/editor/ai/AgentProfilesOverview.tsx +1 -2
  36. package/src/editor/commands/componentCommands.tsx +19 -1
  37. package/src/editor/page-editor-chrome/FrameMenu.tsx +9 -3
  38. package/src/editor/page-viewer/PageViewerFrame.tsx +7 -36
  39. package/src/editor/reviews/Comment.tsx +85 -4
  40. package/src/editor/services/agentService.ts +77 -0
  41. package/src/editor/services/serviceHelper.ts +92 -28
  42. package/src/revision.ts +2 -2
  43. package/src/tour/default-tour.tsx +63 -48
@@ -1,49 +1,40 @@
1
- import React, { useState, useEffect, useCallback, useRef } from "react";
1
+ import React, { useState, useEffect, useCallback } from "react";
2
2
  import {
3
3
  Agent,
4
- getActiveAgents,
5
- GetAgentsParams,
6
- getAgent,
4
+ getAgentsGrouped,
5
+ getClosedAgentsByProfile,
6
+ ProfileAgentsGroup as ProfileGroup,
7
7
  closeAgent,
8
8
  deleteAgent,
9
9
  } from "../editor/services/agentService";
10
- import { getAgentStatusConfig } from "../editor/ai/AgentStatusBadge";
11
10
  import { cn } from "../lib/utils";
12
- import {
13
- Trash,
14
- X,
15
- RefreshCw,
16
- Play,
17
- Search,
18
- Users,
19
- XCircle,
20
- } from "lucide-react";
11
+ import { RefreshCw, Search, XCircle } from "lucide-react";
21
12
  import { SimpleIconButton } from "../editor/ui/SimpleIconButton";
22
13
  import { useEditContext } from "../editor/client/editContext";
23
- import { SecretAgentIcon } from "../editor/ui/Icons";
14
+ import { AgentCard } from "./AgentCard";
15
+ import { ProfileAgentsGroup } from "./ProfileAgentsGroup";
24
16
 
25
17
  /**
26
- * Agents overview view that displays all agents for the current user
27
- * (including closed and shared agents) grouped by profile with interactive controls
18
+ * Agents overview view that displays active agents at the top
19
+ * and closed agents grouped by profile below with interactive controls
28
20
  */
29
21
  export function AgentsView() {
30
22
  const editContext = useEditContext();
31
- const [agents, setAgents] = useState<Agent[]>([]);
32
- const [totalCount, setTotalCount] = useState(0);
33
- const [hasMore, setHasMore] = useState(false);
23
+ const [activeAgents, setActiveAgents] = useState<Agent[]>([]);
24
+ const [profileGroups, setProfileGroups] = useState<ProfileGroup[]>([]);
25
+ const [expandedProfiles, setExpandedProfiles] = useState<Set<string>>(
26
+ new Set(),
27
+ );
28
+ const [loadingMoreForProfile, setLoadingMoreForProfile] = useState<
29
+ string | null
30
+ >(null);
34
31
  const [loading, setLoading] = useState(true);
35
- const [loadingMore, setLoadingMore] = useState(false);
32
+ const [initialLoad, setInitialLoad] = useState(true);
36
33
  const [error, setError] = useState<string | null>(null);
37
34
  const [refreshing, setRefreshing] = useState(false);
38
35
  const [searchTerm, setSearchTerm] = useState("");
39
36
  const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
40
37
  const [showOnlyMyAgents, setShowOnlyMyAgents] = useState(false);
41
- const agentsRef = useRef<Agent[]>([]);
42
-
43
- // Keep ref in sync with state
44
- useEffect(() => {
45
- agentsRef.current = agents;
46
- }, [agents]);
47
38
 
48
39
  // Debounce search term
49
40
  useEffect(() => {
@@ -54,53 +45,35 @@ export function AgentsView() {
54
45
  return () => clearTimeout(timer);
55
46
  }, [searchTerm]);
56
47
 
57
- const loadAgents = useCallback(
58
- async (reset: boolean = false) => {
59
- try {
60
- if (reset) {
61
- setLoading(true);
62
- setError(null);
63
- } else {
64
- setLoadingMore(true);
65
- }
48
+ const loadAgents = useCallback(async () => {
49
+ try {
50
+ setLoading(true);
51
+ setError(null);
66
52
 
67
- const skip = reset ? 0 : agentsRef.current.length;
68
- const params: GetAgentsParams = {
69
- skip,
70
- limit: 50,
71
- includeShared: !showOnlyMyAgents,
72
- includeOwned: true,
73
- searchTerm: debouncedSearchTerm || undefined,
74
- };
75
-
76
- const response = await getActiveAgents(params);
77
-
78
- if (reset) {
79
- setAgents(response.agents);
80
- } else {
81
- setAgents((prev) => [...prev, ...response.agents]);
82
- }
53
+ const response = await getAgentsGrouped(
54
+ debouncedSearchTerm || undefined,
55
+ !showOnlyMyAgents, // includeShared
56
+ true, // includeOwned
57
+ );
83
58
 
84
- setTotalCount(response.totalCount);
85
- setHasMore(response.hasMore);
86
- } catch (err: any) {
87
- console.error("Failed to load agents:", err);
88
- setError(
89
- err?.message || "Failed to load agents. Please try again later.",
90
- );
91
- } finally {
92
- setLoading(false);
93
- setLoadingMore(false);
94
- setRefreshing(false);
95
- }
96
- },
97
- [showOnlyMyAgents, debouncedSearchTerm],
98
- );
59
+ setActiveAgents(response.activeAgents);
60
+ setProfileGroups(response.closedAgentsByProfile);
61
+ } catch (err: any) {
62
+ console.error("Failed to load agents:", err);
63
+ setError(
64
+ err?.message || "Failed to load agents. Please try again later.",
65
+ );
66
+ } finally {
67
+ setLoading(false);
68
+ setRefreshing(false);
69
+ setInitialLoad(false);
70
+ }
71
+ }, [showOnlyMyAgents, debouncedSearchTerm]);
99
72
 
100
- // Initial load
73
+ // Initial load and reload when filters change
101
74
  useEffect(() => {
102
- loadAgents(true);
103
- }, [showOnlyMyAgents, debouncedSearchTerm]);
75
+ loadAgents();
76
+ }, [loadAgents]);
104
77
 
105
78
  // Subscribe to websocket messages for real-time agent updates
106
79
  useEffect(() => {
@@ -119,8 +92,8 @@ export function AgentsView() {
119
92
  return;
120
93
  }
121
94
 
122
- setAgents((prevAgents) => {
123
- // Check if agent already exists
95
+ // Update or add agent in active agents list
96
+ setActiveAgents((prevAgents) => {
124
97
  const existingAgentIndex = prevAgents.findIndex(
125
98
  (agent) => agent.id === agentId,
126
99
  );
@@ -137,7 +110,7 @@ export function AgentsView() {
137
110
  };
138
111
  return updatedAgents;
139
112
  } else {
140
- // Add new agent - reload to get full details
113
+ // Agent not found - reload to get full details
141
114
  loadAgents();
142
115
  return prevAgents;
143
116
  }
@@ -146,10 +119,12 @@ export function AgentsView() {
146
119
  const { agentId, agentName, agentDescription } =
147
120
  message.payload || {};
148
121
  if (!agentId) return;
149
- // Update the agent name and/or description in the list
150
- setAgents((prevAgents) => {
122
+
123
+ // Update agent in active agents list
124
+ setActiveAgents((prevAgents) => {
151
125
  const existingIndex = prevAgents.findIndex((a) => a.id === agentId);
152
126
  if (existingIndex === -1) return prevAgents;
127
+
153
128
  const updatedAgents = [...prevAgents];
154
129
  const existingAgent = updatedAgents[existingIndex]!;
155
130
  updatedAgents[existingIndex] = {
@@ -163,8 +138,8 @@ export function AgentsView() {
163
138
  return updatedAgents;
164
139
  });
165
140
  } else if (message.type === "agent:run:closed") {
166
- // Reload agents when one is closed - it should still appear in the list
167
- loadAgents(true);
141
+ // Reload agents when one is closed - it should move to closed section
142
+ loadAgents();
168
143
  }
169
144
  },
170
145
  );
@@ -174,12 +149,61 @@ export function AgentsView() {
174
149
 
175
150
  const handleRefresh = async () => {
176
151
  setRefreshing(true);
177
- await loadAgents(true);
152
+ await loadAgents();
153
+ };
154
+
155
+ const handleToggleProfile = (profileId: string | null) => {
156
+ const key = profileId || "null";
157
+ setExpandedProfiles((prev) => {
158
+ const newSet = new Set(prev);
159
+ if (newSet.has(key)) {
160
+ newSet.delete(key);
161
+ } else {
162
+ newSet.add(key);
163
+ }
164
+ return newSet;
165
+ });
178
166
  };
179
167
 
180
- const handleLoadMore = () => {
181
- if (!loadingMore && hasMore) {
182
- loadAgents(false);
168
+ const handleLoadMoreForProfile = async (profileId: string | null) => {
169
+ if (!profileId || loadingMoreForProfile) return;
170
+
171
+ try {
172
+ setLoadingMoreForProfile(profileId);
173
+
174
+ // Find the current profile group
175
+ const profileGroup = profileGroups.find((g) => g.profileId === profileId);
176
+ if (!profileGroup) return;
177
+
178
+ const currentCount = profileGroup.agents.length;
179
+
180
+ // Load more agents for this profile
181
+ const response = await getClosedAgentsByProfile(
182
+ profileId,
183
+ currentCount,
184
+ 10,
185
+ debouncedSearchTerm || undefined,
186
+ );
187
+
188
+ // Update the profile group with new agents
189
+ setProfileGroups((prevGroups) =>
190
+ prevGroups.map((group) => {
191
+ if (group.profileId === profileId) {
192
+ return {
193
+ ...group,
194
+ agents: [...group.agents, ...response.agents],
195
+ };
196
+ }
197
+ return group;
198
+ }),
199
+ );
200
+ } catch (err: any) {
201
+ console.error("Failed to load more agents:", err);
202
+ editContext?.showToast(
203
+ `Failed to load more agents: ${err?.message || "Unknown error"}`,
204
+ );
205
+ } finally {
206
+ setLoadingMoreForProfile(null);
183
207
  }
184
208
  };
185
209
 
@@ -204,7 +228,8 @@ export function AgentsView() {
204
228
  accept: async () => {
205
229
  try {
206
230
  await closeAgent(agentId);
207
- setAgents((prev) => prev.filter((a) => a.id !== agentId));
231
+ // Reload to move agent from active to closed section
232
+ loadAgents();
208
233
  } catch (err: any) {
209
234
  console.error("Failed to close agent:", err);
210
235
  editContext?.showToast(
@@ -230,7 +255,15 @@ export function AgentsView() {
230
255
  accept: async () => {
231
256
  try {
232
257
  await deleteAgent(agentId);
233
- setAgents((prev) => prev.filter((a) => a.id !== agentId));
258
+ // Remove from both active and closed lists
259
+ setActiveAgents((prev) => prev.filter((a) => a.id !== agentId));
260
+ setProfileGroups((prevGroups) =>
261
+ prevGroups.map((group) => ({
262
+ ...group,
263
+ agents: group.agents.filter((a) => a.id !== agentId),
264
+ totalClosedCount: Math.max(0, group.totalClosedCount - 1),
265
+ })),
266
+ );
234
267
  } catch (err: any) {
235
268
  console.error("Failed to delete agent:", err);
236
269
  editContext?.showToast(
@@ -242,6 +275,15 @@ export function AgentsView() {
242
275
  });
243
276
  };
244
277
 
278
+ const handleAgentClick = (agent: Agent) => {
279
+ // Only select the agent in the agents panel; do not switch views
280
+ window.dispatchEvent(
281
+ new CustomEvent("editor:selectAgent", {
282
+ detail: { agentId: agent.id },
283
+ }),
284
+ );
285
+ };
286
+
245
287
  const formatDateTime = (dateString: string) => {
246
288
  try {
247
289
  const date = new Date(dateString);
@@ -251,10 +293,8 @@ export function AgentsView() {
251
293
  }
252
294
  };
253
295
 
254
- // No client-side filtering needed - backend handles it
255
- // Just display the agents as returned from the API
256
-
257
- if (loading) {
296
+ // Show full-screen loading only on initial load
297
+ if (initialLoad && loading) {
258
298
  return (
259
299
  <div className="flex h-full items-center justify-center">
260
300
  <div className="text-sm text-gray-500">Loading agents...</div>
@@ -262,32 +302,23 @@ export function AgentsView() {
262
302
  );
263
303
  }
264
304
 
265
- if (error) {
266
- return (
267
- <div className="flex h-full items-center justify-center">
268
- <div className="max-w-md text-center">
269
- <div className="mb-4 text-sm text-red-600">{error}</div>
270
- <button
271
- onClick={handleRefresh}
272
- className="rounded bg-blue-500 px-4 py-2 text-xs text-white hover:bg-blue-600"
273
- >
274
- Retry
275
- </button>
276
- </div>
277
- </div>
278
- );
279
- }
305
+ const totalActiveCount = activeAgents.length;
306
+ const totalClosedCount = profileGroups.reduce(
307
+ (sum, group) => sum + group.totalClosedCount,
308
+ 0,
309
+ );
310
+ const totalCount = totalActiveCount + totalClosedCount;
280
311
 
281
312
  return (
282
- <div className="flex h-full flex-col">
313
+ <div className="absolute inset-0 flex flex-col">
283
314
  {/* Header */}
284
- <div className="border-b border-gray-200 bg-white p-4">
315
+ <div className="flex-shrink-0 border-b border-gray-200 bg-white p-4">
285
316
  <div className="mb-3 flex items-center justify-between">
286
317
  <div>
287
318
  <h2 className="text-lg font-semibold text-gray-900">Agents</h2>
288
319
  <p className="text-xs text-gray-500">
289
- Showing {agents.length} of {totalCount} agent
290
- {totalCount !== 1 ? "s" : ""}
320
+ {totalActiveCount} active, {totalClosedCount} closed ({totalCount}{" "}
321
+ total)
291
322
  </p>
292
323
  </div>
293
324
  <div className="flex items-center gap-2">
@@ -343,159 +374,93 @@ export function AgentsView() {
343
374
  </div>
344
375
  </div>
345
376
 
346
- {/* Agents List */}
347
- <div className="flex-1 overflow-auto">
348
- {agents.length === 0 ? (
377
+ {/* Content */}
378
+ <div className="relative flex-1 overflow-y-auto">
379
+ {/* Loading overlay - shows subtle indicator without hiding content */}
380
+ {loading && !initialLoad && (
381
+ <div className="absolute top-2 left-1/2 z-10 -translate-x-1/2 rounded-md bg-white px-3 py-1.5 shadow-md">
382
+ <div className="flex items-center gap-2 text-sm text-gray-600">
383
+ <RefreshCw className="size-3 animate-spin" strokeWidth={1} />
384
+ <span>Loading...</span>
385
+ </div>
386
+ </div>
387
+ )}
388
+
389
+ {error ? (
390
+ <div className="flex h-full items-center justify-center">
391
+ <div className="max-w-md text-center">
392
+ <div className="mb-4 text-sm text-red-600">{error}</div>
393
+ <button
394
+ onClick={handleRefresh}
395
+ className="rounded bg-blue-500 px-4 py-2 text-xs text-white hover:bg-blue-600"
396
+ >
397
+ Retry
398
+ </button>
399
+ </div>
400
+ </div>
401
+ ) : totalCount === 0 && !loading ? (
349
402
  <div className="flex h-full items-center justify-center text-sm text-gray-500">
350
403
  {searchTerm.trim()
351
404
  ? "No agents match your search"
352
405
  : "No agents found"}
353
406
  </div>
354
407
  ) : (
355
- <div className="space-y-2 p-4">
356
- {agents.map((agent) => {
357
- const statusConfig = getAgentStatusConfig(agent);
358
- return (
359
- <div
360
- key={agent.id}
361
- className="cursor-pointer rounded-lg border border-gray-200 bg-white p-3 shadow-sm transition-shadow hover:shadow-md"
362
- onClick={() => {
363
- // Only select the agent in the agents panel; do not switch views
364
- window.dispatchEvent(
365
- new CustomEvent("editor:selectAgent", {
366
- detail: { agentId: agent.id },
367
- }),
368
- );
369
- }}
370
- >
371
- <div className="flex items-start justify-between">
372
- {/* Agent Icon */}
373
- <div className="mr-3 flex-shrink-0">
374
- {agent.profileSvgIcon ? (
375
- <div
376
- className="flex h-6 w-6 items-center justify-center text-gray-400 [&>svg]:h-full [&>svg]:w-full"
377
- dangerouslySetInnerHTML={{
378
- __html: agent.profileSvgIcon,
379
- }}
380
- />
381
- ) : (
382
- <SecretAgentIcon
383
- size={24}
384
- strokeWidth={1}
385
- className="text-gray-400"
386
- />
387
- )}
388
- </div>
389
-
390
- <div className="min-w-0 flex-1">
391
- {/* Agent Name & Status */}
392
- <div className="mb-1 flex items-center gap-2">
393
- <div
394
- className={cn(
395
- "h-2 w-2 flex-shrink-0 rounded-full",
396
- statusConfig.color,
397
- statusConfig.shouldPulse && "animate-pulse",
398
- )}
399
- title={statusConfig.label}
400
- />
401
- <h4 className="truncate font-medium text-gray-900">
402
- {agent.name}
403
- </h4>
404
- {agent.isShared && (
405
- <div className="flex items-center gap-1 rounded bg-blue-50 px-2 py-0.5 text-xs text-blue-700">
406
- <Users className="size-3" strokeWidth={1} />
407
- <span>Shared</span>
408
- </div>
409
- )}
410
- </div>
411
-
412
- {/* Shared Agent Owner */}
413
- {agent.isShared && agent.ownerName && (
414
- <p className="mb-1 text-xs text-gray-500">
415
- Created by {agent.ownerName}
416
- </p>
417
- )}
418
-
419
- {/* Agent Description */}
420
- {agent.description && (
421
- <p className="mb-2 truncate text-xs text-gray-500">
422
- {agent.description}
423
- </p>
424
- )}
425
-
426
- {/* Agent Details */}
427
- <div className="space-y-1 text-xs text-gray-600">
428
- <div className="flex items-center gap-2">
429
- <span className="font-medium">Updated:</span>
430
- <span>{formatDateTime(agent.updatedDate)}</span>
431
- </div>
432
- {agent.messageCount !== undefined &&
433
- agent.messageCount > 0 && (
434
- <div className="flex items-center gap-2">
435
- <span className="font-medium">Messages:</span>
436
- <span>{agent.messageCount}</span>
437
- </div>
438
- )}
439
- {agent.totalCost !== undefined &&
440
- agent.totalCost > 0 && (
441
- <div className="flex items-center gap-2">
442
- <span className="font-medium">Cost:</span>
443
- <span>${agent.totalCost.toFixed(4)}</span>
444
- {agent.totalTokensUsed !== undefined && (
445
- <span className="text-gray-400">
446
- ({agent.totalTokensUsed.toLocaleString()}{" "}
447
- tokens)
448
- </span>
449
- )}
450
- </div>
451
- )}
452
- </div>
453
- </div>
454
-
455
- {/* Actions */}
456
- <div className="ml-3 flex gap-1">
457
- <SimpleIconButton
458
- onClick={(e) => {
459
- e.stopPropagation();
460
- handleResumeAgent(agent);
461
- }}
462
- icon={<Play className="size-3" strokeWidth={1.5} />}
463
- label="Resume Agent"
464
- className="text-blue-600 opacity-60 hover:text-blue-700 hover:opacity-100"
465
- />
466
- <SimpleIconButton
467
- onClick={(e) => {
468
- e.stopPropagation();
469
- handleCloseAgent(agent.id, agent.name);
470
- }}
471
- icon={<X className="size-3" strokeWidth={1.5} />}
472
- label="Close Agent"
473
- className="text-gray-600 opacity-60 hover:text-gray-800 hover:opacity-100"
474
- />
475
- <SimpleIconButton
476
- onClick={(e) =>
477
- handleDeleteAgent(agent.id, agent.name, e)
478
- }
479
- icon={<Trash className="size-3" strokeWidth={1.5} />}
480
- label="Delete Agent"
481
- className="text-red-600 opacity-60 hover:text-red-700 hover:opacity-100"
482
- />
483
- </div>
484
- </div>
485
- </div>
486
- );
487
- })}
488
-
489
- {/* Load More Button */}
490
- {hasMore && (
491
- <div className="flex justify-center pt-4">
492
- <button
493
- onClick={handleLoadMore}
494
- disabled={loadingMore}
495
- className="rounded-lg border border-gray-300 bg-white px-6 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50"
496
- >
497
- {loadingMore ? "Loading..." : "Load More"}
498
- </button>
408
+ <div className={cn("space-y-4 p-4", loading && "opacity-60")}>
409
+ {/* Active Agents Section */}
410
+ {activeAgents.length > 0 && (
411
+ <div className="space-y-2">
412
+ <h3 className="text-sm font-semibold text-gray-700">
413
+ Active Agents
414
+ </h3>
415
+ {activeAgents.map((agent) => (
416
+ <AgentCard
417
+ key={agent.id}
418
+ agent={agent}
419
+ onClick={handleAgentClick}
420
+ onResume={handleResumeAgent}
421
+ onClose={handleCloseAgent}
422
+ onDelete={handleDeleteAgent}
423
+ formatDateTime={formatDateTime}
424
+ />
425
+ ))}
426
+ </div>
427
+ )}
428
+
429
+ {/* Closed Agents by Profile Section */}
430
+ {profileGroups.length > 0 && (
431
+ <div className="space-y-2">
432
+ <h3 className="text-sm font-semibold text-gray-700">
433
+ Closed Agents by Profile
434
+ </h3>
435
+ {profileGroups.map((profileGroup) => {
436
+ const profileKey = profileGroup.profileId || "null";
437
+ const isExpanded = expandedProfiles.has(profileKey);
438
+ const isLoadingMore =
439
+ loadingMoreForProfile === profileGroup.profileId;
440
+ const hasMore =
441
+ profileGroup.agents.length < profileGroup.totalClosedCount;
442
+
443
+ return (
444
+ <ProfileAgentsGroup
445
+ key={profileKey}
446
+ profileGroup={profileGroup}
447
+ isExpanded={isExpanded}
448
+ isLoadingMore={isLoadingMore}
449
+ hasMore={hasMore}
450
+ onToggle={() =>
451
+ handleToggleProfile(profileGroup.profileId)
452
+ }
453
+ onLoadMore={() =>
454
+ handleLoadMoreForProfile(profileGroup.profileId)
455
+ }
456
+ onAgentClick={handleAgentClick}
457
+ onResumeAgent={handleResumeAgent}
458
+ onCloseAgent={handleCloseAgent}
459
+ onDeleteAgent={handleDeleteAgent}
460
+ formatDateTime={formatDateTime}
461
+ />
462
+ );
463
+ })}
499
464
  </div>
500
465
  )}
501
466
  </div>