@elizaos/client 1.5.5-alpha.10
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/LICENSE +21 -0
- package/README.md +350 -0
- package/dist/assets/empty-module-CLMscLYw.js +1 -0
- package/dist/assets/main-BBZ_3lkn.css +5999 -0
- package/dist/assets/main-C5zNUkXH.js +7 -0
- package/dist/assets/main-Dz64ENQg.js +614 -0
- package/dist/assets/react-vendor-DM5m98rr.js +545 -0
- package/dist/assets/ui-vendor-BQCqNqg0.js +1 -0
- package/dist/elizaos-avatar.png +0 -0
- package/dist/elizaos-icon.png +0 -0
- package/dist/elizaos-logo-light.png +0 -0
- package/dist/elizaos.webp +0 -0
- package/dist/favicon.ico +0 -0
- package/dist/images/agents/agent1.png +0 -0
- package/dist/images/agents/agent2.png +0 -0
- package/dist/images/agents/agent3.png +0 -0
- package/dist/images/agents/agent4.png +0 -0
- package/dist/images/agents/agent5.png +0 -0
- package/dist/index.html +14 -0
- package/index.html +24 -0
- package/package.json +159 -0
- package/postcss.config.js +3 -0
- package/public/elizaos-avatar.png +0 -0
- package/public/elizaos-icon.png +0 -0
- package/public/elizaos-logo-light.png +0 -0
- package/public/elizaos.webp +0 -0
- package/public/favicon.ico +0 -0
- package/public/images/agents/agent1.png +0 -0
- package/public/images/agents/agent2.png +0 -0
- package/public/images/agents/agent3.png +0 -0
- package/public/images/agents/agent4.png +0 -0
- package/public/images/agents/agent5.png +0 -0
- package/src/App.tsx +222 -0
- package/src/components/AgentDetailsPanel.tsx +147 -0
- package/src/components/ChatInputArea.tsx +196 -0
- package/src/components/ChatMessageListComponent.tsx +139 -0
- package/src/components/actionTool.tsx +186 -0
- package/src/components/add-agent-card.tsx +77 -0
- package/src/components/agent-action-viewer.tsx +816 -0
- package/src/components/agent-avatar-stack.tsx +121 -0
- package/src/components/agent-card.cy.tsx +259 -0
- package/src/components/agent-card.tsx +177 -0
- package/src/components/agent-creator.tsx +142 -0
- package/src/components/agent-log-viewer.tsx +645 -0
- package/src/components/agent-memory-edit-overlay.tsx +461 -0
- package/src/components/agent-memory-viewer.tsx +504 -0
- package/src/components/agent-settings.tsx +270 -0
- package/src/components/agent-sidebar.tsx +178 -0
- package/src/components/api-key-dialog.tsx +113 -0
- package/src/components/app-sidebar.tsx +685 -0
- package/src/components/array-input.tsx +116 -0
- package/src/components/audio-recorder.tsx +292 -0
- package/src/components/avatar-panel.tsx +141 -0
- package/src/components/character-form.tsx +1138 -0
- package/src/components/chat.tsx +1813 -0
- package/src/components/combobox.tsx +187 -0
- package/src/components/confirmation-dialog.tsx +59 -0
- package/src/components/connection-error-banner.tsx +101 -0
- package/src/components/connection-status.cy.tsx +73 -0
- package/src/components/connection-status.tsx +155 -0
- package/src/components/copy-button.tsx +35 -0
- package/src/components/delete-button.tsx +24 -0
- package/src/components/env-settings.tsx +261 -0
- package/src/components/group-card.tsx +160 -0
- package/src/components/group-panel.tsx +543 -0
- package/src/components/input-copy.tsx +21 -0
- package/src/components/logs-page.tsx +41 -0
- package/src/components/media-content.tsx +385 -0
- package/src/components/memory-graph.tsx +170 -0
- package/src/components/missing-secrets-dialog.tsx +72 -0
- package/src/components/onboarding-tour.tsx +247 -0
- package/src/components/page-title.tsx +8 -0
- package/src/components/plugins-panel.tsx +383 -0
- package/src/components/profile-card.tsx +66 -0
- package/src/components/profile-overlay.tsx +283 -0
- package/src/components/retry-button.tsx +28 -0
- package/src/components/secret-panel.tsx +1505 -0
- package/src/components/server-management.tsx +264 -0
- package/src/components/split-button.tsx +148 -0
- package/src/components/stop-agent-button.tsx +99 -0
- package/src/components/ui/alert-dialog.cy.tsx +333 -0
- package/src/components/ui/alert-dialog.tsx +115 -0
- package/src/components/ui/alert.tsx +49 -0
- package/src/components/ui/avatar.cy.tsx +180 -0
- package/src/components/ui/avatar.tsx +57 -0
- package/src/components/ui/badge.cy.tsx +146 -0
- package/src/components/ui/badge.tsx +43 -0
- package/src/components/ui/button.cy.tsx +177 -0
- package/src/components/ui/button.tsx +56 -0
- package/src/components/ui/card.cy.tsx +160 -0
- package/src/components/ui/card.tsx +73 -0
- package/src/components/ui/chat/animated-markdown.tsx +59 -0
- package/src/components/ui/chat/chat-bubble.tsx +178 -0
- package/src/components/ui/chat/chat-container.tsx +51 -0
- package/src/components/ui/chat/chat-input.cy.tsx +169 -0
- package/src/components/ui/chat/chat-input.tsx +47 -0
- package/src/components/ui/chat/chat-message-list.tsx +61 -0
- package/src/components/ui/chat/chat-tts-button.tsx +199 -0
- package/src/components/ui/chat/code-block.tsx +79 -0
- package/src/components/ui/chat/expandable-chat.tsx +131 -0
- package/src/components/ui/chat/hooks/useAutoScroll.ts +86 -0
- package/src/components/ui/chat/markdown.tsx +209 -0
- package/src/components/ui/chat/message-loading.tsx +48 -0
- package/src/components/ui/checkbox.cy.tsx +170 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/collapsible.cy.tsx +283 -0
- package/src/components/ui/collapsible.tsx +9 -0
- package/src/components/ui/command.cy.tsx +313 -0
- package/src/components/ui/command.tsx +143 -0
- package/src/components/ui/dialog.cy.tsx +279 -0
- package/src/components/ui/dialog.tsx +104 -0
- package/src/components/ui/dropdown-menu.cy.tsx +273 -0
- package/src/components/ui/dropdown-menu.tsx +281 -0
- package/src/components/ui/input.cy.tsx +82 -0
- package/src/components/ui/input.tsx +27 -0
- package/src/components/ui/label.cy.tsx +157 -0
- package/src/components/ui/label.tsx +19 -0
- package/src/components/ui/resizable.tsx +42 -0
- package/src/components/ui/scroll-area.cy.tsx +242 -0
- package/src/components/ui/scroll-area.tsx +46 -0
- package/src/components/ui/select.cy.tsx +277 -0
- package/src/components/ui/select.tsx +155 -0
- package/src/components/ui/separator.cy.tsx +145 -0
- package/src/components/ui/separator.tsx +29 -0
- package/src/components/ui/sheet.cy.tsx +324 -0
- package/src/components/ui/sheet.tsx +119 -0
- package/src/components/ui/sidebar.tsx +734 -0
- package/src/components/ui/skeleton.cy.tsx +149 -0
- package/src/components/ui/skeleton.tsx +17 -0
- package/src/components/ui/split-button.cy.tsx +274 -0
- package/src/components/ui/split-button.tsx +112 -0
- package/src/components/ui/switch.tsx +28 -0
- package/src/components/ui/tabs.cy.tsx +271 -0
- package/src/components/ui/tabs.tsx +53 -0
- package/src/components/ui/textarea.cy.tsx +136 -0
- package/src/components/ui/textarea.tsx +26 -0
- package/src/components/ui/toast.cy.tsx +209 -0
- package/src/components/ui/toast.tsx +126 -0
- package/src/components/ui/toaster.tsx +29 -0
- package/src/components/ui/tooltip.cy.tsx +244 -0
- package/src/components/ui/tooltip.tsx +30 -0
- package/src/config/agent-templates.ts +349 -0
- package/src/config/voice-models.ts +181 -0
- package/src/constants.ts +23 -0
- package/src/context/AuthContext.tsx +44 -0
- package/src/context/ConnectionContext.tsx +194 -0
- package/src/entry.tsx +9 -0
- package/src/hooks/__tests__/use-agent-tab-state.test.ts +137 -0
- package/src/hooks/__tests__/use-agent-update.test.tsx +250 -0
- package/src/hooks/__tests__/use-character-convert.test.ts +102 -0
- package/src/hooks/__tests__/use-panel-width-state.test.ts +243 -0
- package/src/hooks/__tests__/use-sidebar-state.test.ts +117 -0
- package/src/hooks/use-agent-management.ts +130 -0
- package/src/hooks/use-agent-tab-state.ts +74 -0
- package/src/hooks/use-agent-update.ts +469 -0
- package/src/hooks/use-character-convert.ts +138 -0
- package/src/hooks/use-confirmation.ts +55 -0
- package/src/hooks/use-delete-agent.ts +123 -0
- package/src/hooks/use-dm-channels.ts +198 -0
- package/src/hooks/use-elevenlabs-voices.ts +83 -0
- package/src/hooks/use-file-upload.ts +224 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/hooks/use-onboarding.tsx +49 -0
- package/src/hooks/use-panel-width-state.ts +147 -0
- package/src/hooks/use-partial-update.ts +288 -0
- package/src/hooks/use-plugin-details.ts +462 -0
- package/src/hooks/use-plugins.ts +119 -0
- package/src/hooks/use-query-hooks.ts +1263 -0
- package/src/hooks/use-server-agents.ts +62 -0
- package/src/hooks/use-server-version.tsx +47 -0
- package/src/hooks/use-sidebar-state.ts +50 -0
- package/src/hooks/use-socket-chat.ts +264 -0
- package/src/hooks/use-toast.ts +260 -0
- package/src/hooks/use-version.tsx +64 -0
- package/src/index.css +146 -0
- package/src/lib/api-client-config.ts +53 -0
- package/src/lib/api-type-mappers.ts +196 -0
- package/src/lib/export-utils.ts +123 -0
- package/src/lib/logger.ts +19 -0
- package/src/lib/media-utils.ts +170 -0
- package/src/lib/pca.test.ts +17 -0
- package/src/lib/pca.ts +52 -0
- package/src/lib/socketio-manager.ts +664 -0
- package/src/lib/utils.ts +168 -0
- package/src/main.tsx +16 -0
- package/src/mocks/empty-module.ts +12 -0
- package/src/mocks/node-module.ts +57 -0
- package/src/polyfills.ts +37 -0
- package/src/routes/agent-detail.tsx +30 -0
- package/src/routes/agent-list.tsx +27 -0
- package/src/routes/agent-settings.tsx +48 -0
- package/src/routes/character-detail.tsx +52 -0
- package/src/routes/character-form.tsx +79 -0
- package/src/routes/character-list.tsx +38 -0
- package/src/routes/chat.tsx +128 -0
- package/src/routes/createAgent.tsx +13 -0
- package/src/routes/group-new.tsx +50 -0
- package/src/routes/group.tsx +29 -0
- package/src/routes/home.tsx +218 -0
- package/src/routes/not-found.tsx +71 -0
- package/src/test/setup.ts +154 -0
- package/src/types/crypto-browserify.d.ts +4 -0
- package/src/types/index.ts +13 -0
- package/src/types/rooms.ts +8 -0
- package/src/types.ts +84 -0
- package/src/vite-env.d.ts +40 -0
- package/tailwind.config.ts +90 -0
- package/tsconfig.json +10 -0
- package/vite.config.ts +102 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
import { ChannelType, validateUuid } from '@elizaos/core';
|
|
2
|
+
import { Separator } from '@/components/ui/separator';
|
|
3
|
+
import { GROUP_CHAT_SOURCE } from '@/constants';
|
|
4
|
+
import { useAgentsWithDetails, useChannels } from '@/hooks/use-query-hooks';
|
|
5
|
+
import { createElizaClient } from '@/lib/api-client-config';
|
|
6
|
+
import { type Agent, AgentStatus, type UUID } from '@elizaos/core';
|
|
7
|
+
import { useQueryClient, useQuery, useMutation, type UseQueryResult } from '@tanstack/react-query';
|
|
8
|
+
import { Loader2, Trash, X } from 'lucide-react';
|
|
9
|
+
import { useEffect, useState, useMemo, useCallback, useRef } from 'react';
|
|
10
|
+
import { useNavigate } from 'react-router';
|
|
11
|
+
import MultiSelectCombobox from './combobox';
|
|
12
|
+
import { Button } from './ui/button';
|
|
13
|
+
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from './ui/card';
|
|
14
|
+
import { Input } from './ui/input';
|
|
15
|
+
import { useToast } from '@/hooks/use-toast';
|
|
16
|
+
import clientLogger from '@/lib/logger';
|
|
17
|
+
import { useConfirmation } from '@/hooks/use-confirmation';
|
|
18
|
+
import ConfirmationDialog from './confirmation-dialog';
|
|
19
|
+
|
|
20
|
+
const DEFAULT_SERVER_ID = '00000000-0000-0000-0000-000000000000' as UUID;
|
|
21
|
+
|
|
22
|
+
// This Option type must precisely match what MultiSelectCombobox.tsx expects for its props.
|
|
23
|
+
// Based on MultiSelectCombobox.tsx: { icon: string; label: string; id?: string; }
|
|
24
|
+
interface ComboboxOption {
|
|
25
|
+
icon: string;
|
|
26
|
+
label: string;
|
|
27
|
+
id?: string; // We will always provide agent.id here, which is UUID (string)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface GroupPanelProps {
|
|
31
|
+
onClose: () => void;
|
|
32
|
+
channelId?: UUID;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface ChannelParticipantsResponse {
|
|
36
|
+
success: boolean;
|
|
37
|
+
data?: UUID[];
|
|
38
|
+
error?: { message?: string; code?: number | string };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type SelectableAgent = Agent & { id: UUID; name: string };
|
|
42
|
+
|
|
43
|
+
function isAgentSelectable(agent: Partial<Agent>): agent is SelectableAgent {
|
|
44
|
+
return (
|
|
45
|
+
!!agent.id &&
|
|
46
|
+
!!validateUuid(agent.id) &&
|
|
47
|
+
typeof agent.name === 'string' &&
|
|
48
|
+
agent.name.trim() !== ''
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default function GroupPanel({ onClose, channelId }: GroupPanelProps) {
|
|
53
|
+
const [chatName, setChatName] = useState('');
|
|
54
|
+
const [selectedAgents, setSelectedAgents] = useState<SelectableAgent[]>([]);
|
|
55
|
+
const [initialChatName, setInitialChatName] = useState('');
|
|
56
|
+
const [initialSelectedAgentIds, setInitialSelectedAgentIds] = useState<UUID[]>([]);
|
|
57
|
+
const serverId = DEFAULT_SERVER_ID;
|
|
58
|
+
const initializedRef = useRef(false);
|
|
59
|
+
const lastChannelIdRef = useRef(channelId);
|
|
60
|
+
const agentsInitializedRef = useRef(false);
|
|
61
|
+
|
|
62
|
+
const { data: channelsData } = useChannels(channelId ? serverId : undefined, {
|
|
63
|
+
enabled: !!channelId,
|
|
64
|
+
});
|
|
65
|
+
const {
|
|
66
|
+
data: agentsData,
|
|
67
|
+
isLoading: isLoadingAgents,
|
|
68
|
+
isError: isErrorAgents,
|
|
69
|
+
} = useAgentsWithDetails();
|
|
70
|
+
|
|
71
|
+
const allAvailableSelectableAgents = useMemo(() => {
|
|
72
|
+
return (agentsData?.agents || []).filter(isAgentSelectable);
|
|
73
|
+
}, [agentsData]);
|
|
74
|
+
|
|
75
|
+
const queryClient = useQueryClient();
|
|
76
|
+
const navigate = useNavigate();
|
|
77
|
+
const { toast } = useToast();
|
|
78
|
+
|
|
79
|
+
const { confirm, isOpen: confirmOpen, onOpenChange, onConfirm, options } = useConfirmation();
|
|
80
|
+
|
|
81
|
+
// Force fetch participants immediately when component mounts with channelId
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (channelId) {
|
|
84
|
+
queryClient.invalidateQueries({ queryKey: ['channelParticipants', channelId] });
|
|
85
|
+
queryClient.refetchQueries({ queryKey: ['channelParticipants', channelId] });
|
|
86
|
+
}
|
|
87
|
+
}, [channelId, queryClient]);
|
|
88
|
+
|
|
89
|
+
// Create group mutation
|
|
90
|
+
const createGroupMutation = useMutation({
|
|
91
|
+
mutationFn: async ({ name, participantIds }: { name: string; participantIds: UUID[] }) => {
|
|
92
|
+
const elizaClient = createElizaClient();
|
|
93
|
+
return await elizaClient.messaging.createGroupChannel({
|
|
94
|
+
name,
|
|
95
|
+
participantIds: participantIds,
|
|
96
|
+
metadata: {
|
|
97
|
+
type: ChannelType.GROUP,
|
|
98
|
+
server_id: serverId,
|
|
99
|
+
source: GROUP_CHAT_SOURCE,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
onSuccess: (response) => {
|
|
104
|
+
if (response) {
|
|
105
|
+
toast({ title: 'Success', description: 'Group created successfully.' });
|
|
106
|
+
queryClient.invalidateQueries({ queryKey: ['channels', serverId] });
|
|
107
|
+
queryClient.invalidateQueries({ queryKey: ['channels'] });
|
|
108
|
+
onClose();
|
|
109
|
+
setTimeout(() => {
|
|
110
|
+
navigate(`/group/${response.id}?serverId=${serverId}`);
|
|
111
|
+
}, 100);
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
onError: (error) => {
|
|
115
|
+
clientLogger.error('Failed to create group', error);
|
|
116
|
+
const errorMsg = error instanceof Error ? error.message : 'Failed to create group.';
|
|
117
|
+
toast({ title: 'Error', description: errorMsg, variant: 'destructive' });
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Update group mutation
|
|
122
|
+
const updateGroupMutation = useMutation({
|
|
123
|
+
mutationFn: async ({ name, participantIds }: { name: string; participantIds: UUID[] }) => {
|
|
124
|
+
if (!channelId) throw new Error('Channel ID is required for update');
|
|
125
|
+
const elizaClient = createElizaClient();
|
|
126
|
+
return await elizaClient.messaging.updateChannel(channelId, {
|
|
127
|
+
name,
|
|
128
|
+
participantCentralUserIds: participantIds,
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
onSuccess: () => {
|
|
132
|
+
toast({ title: 'Group Updated', description: 'Group details updated successfully.' });
|
|
133
|
+
queryClient.invalidateQueries({ queryKey: ['channels', serverId] });
|
|
134
|
+
queryClient.invalidateQueries({ queryKey: ['channels'] });
|
|
135
|
+
onClose();
|
|
136
|
+
setTimeout(() => {
|
|
137
|
+
navigate(`/group/${channelId}?serverId=${serverId}`);
|
|
138
|
+
}, 100);
|
|
139
|
+
},
|
|
140
|
+
onError: (error) => {
|
|
141
|
+
clientLogger.error('Failed to update group', error);
|
|
142
|
+
console.error('Group update error details:', error);
|
|
143
|
+
const errorMsg = error instanceof Error ? error.message : 'Failed to update group.';
|
|
144
|
+
toast({ title: 'Error', description: errorMsg, variant: 'destructive' });
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Delete group mutation
|
|
149
|
+
const deleteGroupMutation = useMutation({
|
|
150
|
+
mutationFn: async () => {
|
|
151
|
+
if (!channelId) throw new Error('Channel ID is required for delete');
|
|
152
|
+
const elizaClient = createElizaClient();
|
|
153
|
+
return await elizaClient.messaging.deleteChannel(channelId);
|
|
154
|
+
},
|
|
155
|
+
onSuccess: () => {
|
|
156
|
+
toast({ title: 'Group Deleted', description: 'The group has been successfully deleted.' });
|
|
157
|
+
queryClient.invalidateQueries({ queryKey: ['channels', serverId] });
|
|
158
|
+
queryClient.invalidateQueries({ queryKey: ['channels'] });
|
|
159
|
+
navigate('/');
|
|
160
|
+
onClose();
|
|
161
|
+
},
|
|
162
|
+
onError: (error) => {
|
|
163
|
+
clientLogger.error('Failed to delete channel', error);
|
|
164
|
+
const errorMsg = error instanceof Error ? error.message : 'Could not delete group.';
|
|
165
|
+
if (typeof error === 'object' && error !== null && (error as any).statusCode === 404) {
|
|
166
|
+
toast({
|
|
167
|
+
title: 'Error Deleting Group',
|
|
168
|
+
description: 'Delete operation not found on server.',
|
|
169
|
+
variant: 'destructive',
|
|
170
|
+
});
|
|
171
|
+
} else {
|
|
172
|
+
toast({ title: 'Error Deleting Group', description: errorMsg, variant: 'destructive' });
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const {
|
|
178
|
+
data: channelParticipantsApiResponse,
|
|
179
|
+
isLoading: isLoadingChannelParticipants,
|
|
180
|
+
isError: isErrorChannelParticipants,
|
|
181
|
+
error: errorChannelParticipants,
|
|
182
|
+
}: UseQueryResult<ChannelParticipantsResponse, Error> = useQuery({
|
|
183
|
+
queryKey: ['channelParticipants', channelId],
|
|
184
|
+
queryFn: async () => {
|
|
185
|
+
if (!channelId) return { success: true, data: [] };
|
|
186
|
+
try {
|
|
187
|
+
const elizaClient = createElizaClient();
|
|
188
|
+
const result = await elizaClient.messaging.getChannelParticipants(channelId);
|
|
189
|
+
|
|
190
|
+
// Handle different possible response formats
|
|
191
|
+
let participants = [];
|
|
192
|
+
if (result && Array.isArray(result.participants)) {
|
|
193
|
+
participants = result.participants.map((participant) => participant.userId);
|
|
194
|
+
} else if (result && Array.isArray(result)) {
|
|
195
|
+
// If result is directly an array
|
|
196
|
+
participants = result.map(
|
|
197
|
+
(participant) => participant.userId || participant.id || participant
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return { success: true, data: participants };
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error('[GroupPanel] Error fetching channel participants:', error);
|
|
204
|
+
return {
|
|
205
|
+
success: false,
|
|
206
|
+
error: { message: error instanceof Error ? error.message : 'Unknown error' },
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
enabled: !!channelId,
|
|
211
|
+
retry: false,
|
|
212
|
+
refetchOnMount: true,
|
|
213
|
+
refetchOnWindowFocus: false,
|
|
214
|
+
staleTime: 0, // Always fetch fresh data
|
|
215
|
+
gcTime: 0, // Don't cache results
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Separate effect for initializing chat name when channel loads
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
if (channelId && channelsData?.data?.channels) {
|
|
221
|
+
const channelDetails = channelsData.data.channels.find((ch) => ch.id === channelId);
|
|
222
|
+
if (!initializedRef.current || lastChannelIdRef.current !== channelId) {
|
|
223
|
+
const initialName = channelDetails?.name || '';
|
|
224
|
+
setChatName(initialName);
|
|
225
|
+
setInitialChatName(initialName);
|
|
226
|
+
initializedRef.current = true;
|
|
227
|
+
lastChannelIdRef.current = channelId;
|
|
228
|
+
agentsInitializedRef.current = false; // Reset agents initialization for new channel
|
|
229
|
+
}
|
|
230
|
+
} else if (!channelId) {
|
|
231
|
+
// Reset for create mode
|
|
232
|
+
initializedRef.current = false;
|
|
233
|
+
setChatName('');
|
|
234
|
+
setSelectedAgents([]);
|
|
235
|
+
setInitialChatName('');
|
|
236
|
+
setInitialSelectedAgentIds([]);
|
|
237
|
+
}
|
|
238
|
+
}, [channelId, channelsData]);
|
|
239
|
+
|
|
240
|
+
// Separate effect for handling participants
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
if (isLoadingAgents) return;
|
|
243
|
+
if (channelId && isLoadingChannelParticipants) return;
|
|
244
|
+
if (!channelId) {
|
|
245
|
+
// Reset for create mode
|
|
246
|
+
agentsInitializedRef.current = false;
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Only initialize once per channel
|
|
251
|
+
if (agentsInitializedRef.current && lastChannelIdRef.current === channelId) return;
|
|
252
|
+
|
|
253
|
+
if (isErrorChannelParticipants) {
|
|
254
|
+
toast({
|
|
255
|
+
title: 'Error',
|
|
256
|
+
description: `Could not load group participants: ${errorChannelParticipants?.message || 'Unknown error'}`,
|
|
257
|
+
variant: 'destructive',
|
|
258
|
+
});
|
|
259
|
+
setSelectedAgents([]);
|
|
260
|
+
setInitialSelectedAgentIds([]);
|
|
261
|
+
agentsInitializedRef.current = true;
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (channelParticipantsApiResponse?.success && channelParticipantsApiResponse.data) {
|
|
266
|
+
const participantIds = channelParticipantsApiResponse.data;
|
|
267
|
+
const newSelected = allAvailableSelectableAgents.filter((agent) =>
|
|
268
|
+
participantIds.includes(agent.id)
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
setSelectedAgents(newSelected);
|
|
272
|
+
setInitialSelectedAgentIds(newSelected.map((a) => a.id));
|
|
273
|
+
agentsInitializedRef.current = true;
|
|
274
|
+
} else if (channelParticipantsApiResponse && !channelParticipantsApiResponse.success) {
|
|
275
|
+
toast({
|
|
276
|
+
title: 'Error',
|
|
277
|
+
description: `Could not load group participants: ${channelParticipantsApiResponse.error?.message || 'Server error'}`,
|
|
278
|
+
variant: 'destructive',
|
|
279
|
+
});
|
|
280
|
+
setSelectedAgents([]);
|
|
281
|
+
setInitialSelectedAgentIds([]);
|
|
282
|
+
agentsInitializedRef.current = true;
|
|
283
|
+
} else {
|
|
284
|
+
setSelectedAgents([]);
|
|
285
|
+
setInitialSelectedAgentIds([]);
|
|
286
|
+
agentsInitializedRef.current = true;
|
|
287
|
+
}
|
|
288
|
+
}, [
|
|
289
|
+
channelId,
|
|
290
|
+
isLoadingAgents,
|
|
291
|
+
isLoadingChannelParticipants,
|
|
292
|
+
channelParticipantsApiResponse,
|
|
293
|
+
allAvailableSelectableAgents,
|
|
294
|
+
isErrorChannelParticipants,
|
|
295
|
+
errorChannelParticipants,
|
|
296
|
+
toast,
|
|
297
|
+
]);
|
|
298
|
+
|
|
299
|
+
const comboboxOptions: ComboboxOption[] = useMemo(() => {
|
|
300
|
+
if (isLoadingAgents || isErrorAgents) return [];
|
|
301
|
+
return allAvailableSelectableAgents.map((agent) => ({
|
|
302
|
+
id: agent.id,
|
|
303
|
+
label: `${agent.name}${agent.status === AgentStatus.INACTIVE ? ' (Inactive)' : ''}`,
|
|
304
|
+
icon: typeof agent.settings?.avatar === 'string' ? agent.settings.avatar : '', // Ensure icon is always a string
|
|
305
|
+
}));
|
|
306
|
+
}, [allAvailableSelectableAgents, isLoadingAgents, isErrorAgents]);
|
|
307
|
+
|
|
308
|
+
const STABLE_EMPTY_COMBOBOX_OPTIONS_ARRAY = useMemo(() => [], []);
|
|
309
|
+
|
|
310
|
+
const initialSelectedComboboxOptions: ComboboxOption[] = useMemo(() => {
|
|
311
|
+
if (isLoadingAgents) return STABLE_EMPTY_COMBOBOX_OPTIONS_ARRAY;
|
|
312
|
+
if (!channelId) return STABLE_EMPTY_COMBOBOX_OPTIONS_ARRAY; // Create mode
|
|
313
|
+
|
|
314
|
+
// In edit mode, wait for agents to be initialized before determining selection
|
|
315
|
+
if (channelId && !agentsInitializedRef.current) return STABLE_EMPTY_COMBOBOX_OPTIONS_ARRAY;
|
|
316
|
+
|
|
317
|
+
const options = selectedAgents.map((agent) => ({
|
|
318
|
+
id: agent.id,
|
|
319
|
+
label: `${agent.name}${agent.status === AgentStatus.INACTIVE ? ' (Inactive)' : ''}`,
|
|
320
|
+
icon: typeof agent.settings?.avatar === 'string' ? agent.settings.avatar : '',
|
|
321
|
+
}));
|
|
322
|
+
|
|
323
|
+
return options;
|
|
324
|
+
}, [channelId, selectedAgents, isLoadingAgents, STABLE_EMPTY_COMBOBOX_OPTIONS_ARRAY]);
|
|
325
|
+
|
|
326
|
+
const handleSelectAgents = useCallback(
|
|
327
|
+
(selectedOptions: ComboboxOption[]) => {
|
|
328
|
+
const newSelectedAgentObjects = allAvailableSelectableAgents.filter((agent) =>
|
|
329
|
+
selectedOptions.some((option) => option.id === agent.id)
|
|
330
|
+
);
|
|
331
|
+
setSelectedAgents(newSelectedAgentObjects);
|
|
332
|
+
},
|
|
333
|
+
[allAvailableSelectableAgents]
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
const handleDeleteGroup = useCallback(async () => {
|
|
337
|
+
if (!channelId) return;
|
|
338
|
+
const channel = channelsData?.data?.channels.find((ch) => ch.id === channelId);
|
|
339
|
+
confirm(
|
|
340
|
+
{
|
|
341
|
+
title: 'Delete Group',
|
|
342
|
+
description: `Are you sure you want to permanently delete the group chat "${channel?.name || chatName || 'this group'}"? This action cannot be undone.`,
|
|
343
|
+
confirmText: 'Delete',
|
|
344
|
+
variant: 'destructive',
|
|
345
|
+
},
|
|
346
|
+
() => {
|
|
347
|
+
deleteGroupMutation.mutate();
|
|
348
|
+
}
|
|
349
|
+
);
|
|
350
|
+
}, [channelId, chatName, channelsData, deleteGroupMutation]);
|
|
351
|
+
|
|
352
|
+
// Check if form has changed
|
|
353
|
+
const hasFormChanged = useMemo(() => {
|
|
354
|
+
if (!channelId) return true; // Always allow creation
|
|
355
|
+
|
|
356
|
+
const nameChanged = chatName.trim() !== initialChatName.trim();
|
|
357
|
+
const currentAgentIds = selectedAgents.map((a) => a.id).sort();
|
|
358
|
+
const initialAgentIds = initialSelectedAgentIds.sort();
|
|
359
|
+
const agentsChanged = JSON.stringify(currentAgentIds) !== JSON.stringify(initialAgentIds);
|
|
360
|
+
|
|
361
|
+
return nameChanged || agentsChanged;
|
|
362
|
+
}, [channelId, chatName, initialChatName, selectedAgents, initialSelectedAgentIds]);
|
|
363
|
+
|
|
364
|
+
const handleCreateOrUpdateGroup = useCallback(async () => {
|
|
365
|
+
// For create mode, require at least one agent
|
|
366
|
+
if (!channelId && selectedAgents.length === 0) {
|
|
367
|
+
toast({
|
|
368
|
+
title: 'Validation Error',
|
|
369
|
+
description: 'Please select at least one agent for the group.',
|
|
370
|
+
variant: 'destructive',
|
|
371
|
+
});
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const proceed = () => {
|
|
376
|
+
const participantIds = selectedAgents.map((agent) => agent.id);
|
|
377
|
+
const finalName =
|
|
378
|
+
chatName.trim() ||
|
|
379
|
+
(selectedAgents.length > 0
|
|
380
|
+
? selectedAgents.map((agent) => agent.name).join(', ')
|
|
381
|
+
: 'Empty Group');
|
|
382
|
+
|
|
383
|
+
if (!channelId) {
|
|
384
|
+
createGroupMutation.mutate({ name: finalName, participantIds });
|
|
385
|
+
} else {
|
|
386
|
+
updateGroupMutation.mutate({ name: finalName, participantIds });
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
// For edit mode, warn if removing all agents but allow it
|
|
391
|
+
if (channelId && selectedAgents.length === 0) {
|
|
392
|
+
confirm(
|
|
393
|
+
{
|
|
394
|
+
title: 'Remove All Agents?',
|
|
395
|
+
description:
|
|
396
|
+
'Are you sure you want to remove all agents from this group? This will leave the group with no participants.',
|
|
397
|
+
confirmText: 'Remove All',
|
|
398
|
+
variant: 'destructive',
|
|
399
|
+
},
|
|
400
|
+
() => {
|
|
401
|
+
proceed();
|
|
402
|
+
}
|
|
403
|
+
);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
proceed();
|
|
408
|
+
}, [channelId, chatName, selectedAgents, createGroupMutation, updateGroupMutation, toast]);
|
|
409
|
+
|
|
410
|
+
// Use the exact same logic as "unsaved changes" detection for update button
|
|
411
|
+
const isSubmitDisabled = channelId
|
|
412
|
+
? // Edit mode - disable if no changes OR loading (allow agent removal)
|
|
413
|
+
!hasFormChanged ||
|
|
414
|
+
createGroupMutation.isPending ||
|
|
415
|
+
updateGroupMutation.isPending ||
|
|
416
|
+
deleteGroupMutation.isPending
|
|
417
|
+
: // Create mode - disable if no agents OR loading
|
|
418
|
+
selectedAgents.length === 0 ||
|
|
419
|
+
createGroupMutation.isPending ||
|
|
420
|
+
updateGroupMutation.isPending ||
|
|
421
|
+
deleteGroupMutation.isPending;
|
|
422
|
+
|
|
423
|
+
return (
|
|
424
|
+
<>
|
|
425
|
+
<div
|
|
426
|
+
className="fixed inset-0 bg-black bg-opacity-80 flex items-center justify-center z-50"
|
|
427
|
+
onClick={onClose}
|
|
428
|
+
>
|
|
429
|
+
<Card className="w-[80%] max-w-2xl" onClick={(e) => e.stopPropagation()}>
|
|
430
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0">
|
|
431
|
+
<CardTitle className="text-xl font-semibold">
|
|
432
|
+
{channelId ? 'Edit Group Chat' : 'Create Group Chat'}
|
|
433
|
+
</CardTitle>
|
|
434
|
+
<Button variant="ghost" size="sm" onClick={onClose}>
|
|
435
|
+
<X className="h-4 w-4" />
|
|
436
|
+
</Button>
|
|
437
|
+
</CardHeader>
|
|
438
|
+
|
|
439
|
+
<Separator />
|
|
440
|
+
|
|
441
|
+
<CardContent className="pt-4">
|
|
442
|
+
<div className="flex flex-col gap-4 w-full">
|
|
443
|
+
<div className="flex flex-col gap-2 w-full">
|
|
444
|
+
<label htmlFor="chat-name" className="text-sm font-medium">
|
|
445
|
+
Chat Name (Optional)
|
|
446
|
+
</label>
|
|
447
|
+
<Input
|
|
448
|
+
id="chat-name"
|
|
449
|
+
value={chatName}
|
|
450
|
+
onChange={(e) => setChatName(e.target.value)}
|
|
451
|
+
className="w-full bg-background text-foreground"
|
|
452
|
+
placeholder="Leave blank to auto-generate from participants"
|
|
453
|
+
disabled={
|
|
454
|
+
createGroupMutation.isPending ||
|
|
455
|
+
updateGroupMutation.isPending ||
|
|
456
|
+
deleteGroupMutation.isPending
|
|
457
|
+
}
|
|
458
|
+
/>
|
|
459
|
+
</div>
|
|
460
|
+
|
|
461
|
+
<div className="flex flex-col gap-2 w-full">
|
|
462
|
+
<label htmlFor="invite-agents" className="text-sm font-medium">
|
|
463
|
+
Select Agents{' '}
|
|
464
|
+
{!channelId && <span className="text-muted-foreground">(Required)</span>}
|
|
465
|
+
</label>
|
|
466
|
+
{isLoadingAgents ? (
|
|
467
|
+
<div className="flex items-center justify-center p-4">
|
|
468
|
+
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
469
|
+
<span className="ml-2 text-muted-foreground">Loading agents...</span>
|
|
470
|
+
</div>
|
|
471
|
+
) : isErrorAgents ? (
|
|
472
|
+
<div className="flex items-center justify-center p-4 text-red-500">
|
|
473
|
+
Error loading agents. Please try again later.
|
|
474
|
+
</div>
|
|
475
|
+
) : (
|
|
476
|
+
<MultiSelectCombobox
|
|
477
|
+
options={comboboxOptions}
|
|
478
|
+
onSelect={handleSelectAgents}
|
|
479
|
+
className="w-full"
|
|
480
|
+
initialSelected={initialSelectedComboboxOptions}
|
|
481
|
+
key={`group-panel-combobox-${channelId || 'create'}-${allAvailableSelectableAgents.length}`}
|
|
482
|
+
/>
|
|
483
|
+
)}
|
|
484
|
+
{selectedAgents.length > 0 && (
|
|
485
|
+
<p className="text-sm text-muted-foreground">
|
|
486
|
+
{selectedAgents.length} agent{selectedAgents.length > 1 ? 's' : ''} selected
|
|
487
|
+
</p>
|
|
488
|
+
)}
|
|
489
|
+
{channelId && hasFormChanged && (
|
|
490
|
+
<div className="text-sm text-blue-500 mt-1">You have unsaved changes</div>
|
|
491
|
+
)}
|
|
492
|
+
</div>
|
|
493
|
+
</div>
|
|
494
|
+
</CardContent>
|
|
495
|
+
|
|
496
|
+
<CardFooter className="flex justify-between pt-4">
|
|
497
|
+
{channelId && (
|
|
498
|
+
<Button
|
|
499
|
+
variant="destructive"
|
|
500
|
+
onClick={handleDeleteGroup}
|
|
501
|
+
disabled={
|
|
502
|
+
deleteGroupMutation.isPending ||
|
|
503
|
+
createGroupMutation.isPending ||
|
|
504
|
+
updateGroupMutation.isPending ||
|
|
505
|
+
isLoadingAgents
|
|
506
|
+
}
|
|
507
|
+
>
|
|
508
|
+
{deleteGroupMutation.isPending ? (
|
|
509
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
510
|
+
) : (
|
|
511
|
+
<Trash className="mr-2 h-4 w-4" />
|
|
512
|
+
)}
|
|
513
|
+
Delete Group
|
|
514
|
+
</Button>
|
|
515
|
+
)}
|
|
516
|
+
|
|
517
|
+
<Button
|
|
518
|
+
variant="default"
|
|
519
|
+
className={channelId ? '' : 'w-full'}
|
|
520
|
+
onClick={handleCreateOrUpdateGroup}
|
|
521
|
+
disabled={isSubmitDisabled}
|
|
522
|
+
>
|
|
523
|
+
{(createGroupMutation.isPending || updateGroupMutation.isPending) && (
|
|
524
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
525
|
+
)}
|
|
526
|
+
{channelId ? 'Update Group' : 'Create Group'}
|
|
527
|
+
</Button>
|
|
528
|
+
</CardFooter>
|
|
529
|
+
</Card>
|
|
530
|
+
</div>
|
|
531
|
+
<ConfirmationDialog
|
|
532
|
+
open={confirmOpen}
|
|
533
|
+
onOpenChange={onOpenChange}
|
|
534
|
+
title={options?.title || ''}
|
|
535
|
+
description={options?.description || ''}
|
|
536
|
+
confirmText={options?.confirmText}
|
|
537
|
+
cancelText={options?.cancelText}
|
|
538
|
+
variant={options?.variant}
|
|
539
|
+
onConfirm={onConfirm}
|
|
540
|
+
/>
|
|
541
|
+
</>
|
|
542
|
+
);
|
|
543
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Input } from './ui/input';
|
|
2
|
+
import { Label } from './ui/label';
|
|
3
|
+
|
|
4
|
+
export default function InputCopy({
|
|
5
|
+
title,
|
|
6
|
+
name,
|
|
7
|
+
value,
|
|
8
|
+
onChange,
|
|
9
|
+
}: {
|
|
10
|
+
title: string;
|
|
11
|
+
name: string;
|
|
12
|
+
value: string | number | undefined;
|
|
13
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
14
|
+
}) {
|
|
15
|
+
return (
|
|
16
|
+
<div className="space-y-2">
|
|
17
|
+
<Label>{title}</Label>
|
|
18
|
+
<Input name={name} value={value} onChange={onChange} />
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { AgentLogViewer } from './agent-log-viewer';
|
|
3
|
+
import { Button } from './ui/button';
|
|
4
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
|
|
5
|
+
import { Label } from './ui/label';
|
|
6
|
+
|
|
7
|
+
export default function LogsPage() {
|
|
8
|
+
const [logLevel, setLogLevel] = useState('all');
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div className="container mx-auto p-6 h-full flex flex-col">
|
|
12
|
+
<div className="flex items-center justify-between mb-6">
|
|
13
|
+
<h1 className="text-2xl font-bold">System Logs</h1>
|
|
14
|
+
<div className="flex items-center gap-4">
|
|
15
|
+
<div className="flex items-center gap-2">
|
|
16
|
+
<Label htmlFor="log-level">Log Level</Label>
|
|
17
|
+
<Select value={logLevel} onValueChange={setLogLevel}>
|
|
18
|
+
<SelectTrigger id="log-level" className="w-32">
|
|
19
|
+
<SelectValue placeholder="Select level" />
|
|
20
|
+
</SelectTrigger>
|
|
21
|
+
<SelectContent>
|
|
22
|
+
<SelectItem value="all">All</SelectItem>
|
|
23
|
+
<SelectItem value="info">Info</SelectItem>
|
|
24
|
+
<SelectItem value="warn">Warning</SelectItem>
|
|
25
|
+
<SelectItem value="error">Error</SelectItem>
|
|
26
|
+
<SelectItem value="debug">Debug</SelectItem>
|
|
27
|
+
</SelectContent>
|
|
28
|
+
</Select>
|
|
29
|
+
</div>
|
|
30
|
+
<Button variant="outline" onClick={() => window.location.reload()}>
|
|
31
|
+
Refresh
|
|
32
|
+
</Button>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div className="flex-1 border rounded-lg overflow-hidden">
|
|
37
|
+
<AgentLogViewer level={logLevel as any} />
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|