@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,62 @@
|
|
|
1
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
2
|
+
import { createElizaClient } from '@/lib/api-client-config';
|
|
3
|
+
import { useToast } from '@/hooks/use-toast';
|
|
4
|
+
import type { UUID } from '@elizaos/core';
|
|
5
|
+
|
|
6
|
+
export function useAddAgentToServer() {
|
|
7
|
+
const queryClient = useQueryClient();
|
|
8
|
+
const { toast } = useToast();
|
|
9
|
+
|
|
10
|
+
return useMutation({
|
|
11
|
+
mutationFn: async ({ serverId, agentId }: { serverId: UUID; agentId: UUID }) => {
|
|
12
|
+
const elizaClient = createElizaClient();
|
|
13
|
+
return await elizaClient.agents.addAgentToServer(serverId, agentId);
|
|
14
|
+
},
|
|
15
|
+
onSuccess: (_data, variables) => {
|
|
16
|
+
// Invalidate server agents query
|
|
17
|
+
queryClient.invalidateQueries({ queryKey: ['serverAgents', variables.serverId] });
|
|
18
|
+
queryClient.invalidateQueries({ queryKey: ['agentServers', variables.agentId] });
|
|
19
|
+
|
|
20
|
+
toast({
|
|
21
|
+
title: 'Agent Added',
|
|
22
|
+
description: 'Agent has been successfully added to the server',
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
onError: (error) => {
|
|
26
|
+
toast({
|
|
27
|
+
title: 'Error',
|
|
28
|
+
description: error instanceof Error ? error.message : 'Failed to add agent to server',
|
|
29
|
+
variant: 'destructive',
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useRemoveAgentFromServer() {
|
|
36
|
+
const queryClient = useQueryClient();
|
|
37
|
+
const { toast } = useToast();
|
|
38
|
+
|
|
39
|
+
return useMutation({
|
|
40
|
+
mutationFn: async ({ serverId, agentId }: { serverId: UUID; agentId: UUID }) => {
|
|
41
|
+
const elizaClient = createElizaClient();
|
|
42
|
+
return await elizaClient.agents.removeAgentFromServer(serverId, agentId);
|
|
43
|
+
},
|
|
44
|
+
onSuccess: (_data, variables) => {
|
|
45
|
+
// Invalidate server agents query
|
|
46
|
+
queryClient.invalidateQueries({ queryKey: ['serverAgents', variables.serverId] });
|
|
47
|
+
queryClient.invalidateQueries({ queryKey: ['agentServers', variables.agentId] });
|
|
48
|
+
|
|
49
|
+
toast({
|
|
50
|
+
title: 'Agent Removed',
|
|
51
|
+
description: 'Agent has been successfully removed from the server',
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
onError: (error) => {
|
|
55
|
+
toast({
|
|
56
|
+
title: 'Error',
|
|
57
|
+
description: error instanceof Error ? error.message : 'Failed to remove agent from server',
|
|
58
|
+
variant: 'destructive',
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useQuery } from '@tanstack/react-query';
|
|
2
|
+
import clientLogger from '../lib/logger';
|
|
3
|
+
|
|
4
|
+
export interface ServerVersionInfo {
|
|
5
|
+
version: string;
|
|
6
|
+
source: string;
|
|
7
|
+
timestamp: string;
|
|
8
|
+
environment: string;
|
|
9
|
+
uptime: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Hook to fetch server version information from the API
|
|
14
|
+
*/
|
|
15
|
+
export function useServerVersion() {
|
|
16
|
+
return useQuery<ServerVersionInfo>({
|
|
17
|
+
queryKey: ['server-version'],
|
|
18
|
+
queryFn: async () => {
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch('/api/system/version');
|
|
21
|
+
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Failed to fetch server version: ${response.status} ${response.statusText}`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const data = await response.json();
|
|
29
|
+
return data;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
clientLogger.error('Error fetching server version:', error);
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
staleTime: 5 * 60 * 1000, // Cache for 5 minutes
|
|
36
|
+
retry: 3,
|
|
37
|
+
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Hook that returns just the version string for backwards compatibility
|
|
43
|
+
*/
|
|
44
|
+
export function useServerVersionString() {
|
|
45
|
+
const { data } = useServerVersion();
|
|
46
|
+
return data?.version || '0.0.0-loading';
|
|
47
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import clientLogger from '@/lib/logger';
|
|
3
|
+
|
|
4
|
+
// Key for storing sidebar visibility state in localStorage
|
|
5
|
+
const SIDEBAR_STATE_KEY = 'eliza-agent-sidebar-visible';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Custom hook to manage agent sidebar visibility state with localStorage persistence
|
|
9
|
+
* Remembers whether the sidebar should be open or closed across page refreshes
|
|
10
|
+
*/
|
|
11
|
+
export function useSidebarState() {
|
|
12
|
+
const [isVisible, setIsVisible] = useState<boolean>(false);
|
|
13
|
+
|
|
14
|
+
// Load sidebar state from localStorage on mount
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
try {
|
|
17
|
+
const stored = localStorage.getItem(SIDEBAR_STATE_KEY);
|
|
18
|
+
if (stored !== null) {
|
|
19
|
+
const parsedState = JSON.parse(stored);
|
|
20
|
+
setIsVisible(parsedState);
|
|
21
|
+
}
|
|
22
|
+
} catch (error) {
|
|
23
|
+
clientLogger.error('Error reading sidebar state from localStorage:', error);
|
|
24
|
+
// Default to false if there's an error
|
|
25
|
+
setIsVisible(false);
|
|
26
|
+
}
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
// Update sidebar state and persist to localStorage
|
|
30
|
+
const setSidebarVisible = useCallback((visible: boolean) => {
|
|
31
|
+
setIsVisible(visible);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
localStorage.setItem(SIDEBAR_STATE_KEY, JSON.stringify(visible));
|
|
35
|
+
} catch (error) {
|
|
36
|
+
clientLogger.error('Error saving sidebar state to localStorage:', error);
|
|
37
|
+
}
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
// Toggle function for convenience
|
|
41
|
+
const toggleSidebar = useCallback(() => {
|
|
42
|
+
setSidebarVisible(!isVisible);
|
|
43
|
+
}, [isVisible, setSidebarVisible]);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
isVisible,
|
|
47
|
+
setSidebarVisible,
|
|
48
|
+
toggleSidebar,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { useEffect, useRef, useCallback } from 'react';
|
|
2
|
+
import { SocketIOManager } from '@/lib/socketio-manager';
|
|
3
|
+
import type {
|
|
4
|
+
MessageBroadcastData,
|
|
5
|
+
MessageCompleteData,
|
|
6
|
+
ControlMessageData,
|
|
7
|
+
MessageDeletedData,
|
|
8
|
+
ChannelClearedData,
|
|
9
|
+
ChannelDeletedData,
|
|
10
|
+
} from '@/lib/socketio-manager';
|
|
11
|
+
import { UUID, Agent, ChannelType } from '@elizaos/core';
|
|
12
|
+
import type { UiMessage } from './use-query-hooks';
|
|
13
|
+
import { randomUUID } from '@/lib/utils';
|
|
14
|
+
import clientLogger from '@/lib/logger';
|
|
15
|
+
|
|
16
|
+
interface UseSocketChatProps {
|
|
17
|
+
channelId: UUID | undefined;
|
|
18
|
+
currentUserId: string;
|
|
19
|
+
contextId: UUID; // agentId for DM, channelId for GROUP
|
|
20
|
+
chatType: ChannelType.DM | ChannelType.GROUP;
|
|
21
|
+
allAgents: Agent[];
|
|
22
|
+
messages: UiMessage[];
|
|
23
|
+
onAddMessage: (message: UiMessage) => void;
|
|
24
|
+
onUpdateMessage: (messageId: string, updates: Partial<UiMessage>) => void;
|
|
25
|
+
onDeleteMessage: (messageId: string) => void;
|
|
26
|
+
onClearMessages: () => void;
|
|
27
|
+
onInputDisabledChange: (disabled: boolean) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function useSocketChat({
|
|
31
|
+
channelId,
|
|
32
|
+
currentUserId,
|
|
33
|
+
contextId,
|
|
34
|
+
chatType,
|
|
35
|
+
allAgents,
|
|
36
|
+
messages,
|
|
37
|
+
onAddMessage,
|
|
38
|
+
onUpdateMessage,
|
|
39
|
+
onDeleteMessage,
|
|
40
|
+
onClearMessages,
|
|
41
|
+
onInputDisabledChange,
|
|
42
|
+
}: UseSocketChatProps) {
|
|
43
|
+
const socketIOManager = SocketIOManager.getInstance();
|
|
44
|
+
const animatedMessageIdRef = useRef<string | null>(null);
|
|
45
|
+
const joinedChannelRef = useRef<string | null>(null); // Ref to track joined channel
|
|
46
|
+
|
|
47
|
+
const sendMessage = useCallback(
|
|
48
|
+
async (
|
|
49
|
+
text: string,
|
|
50
|
+
serverId: UUID,
|
|
51
|
+
source: string,
|
|
52
|
+
attachments?: any[],
|
|
53
|
+
tempMessageId?: string,
|
|
54
|
+
metadata?: Record<string, any>,
|
|
55
|
+
overrideChannelId?: UUID
|
|
56
|
+
) => {
|
|
57
|
+
const channelIdToUse = overrideChannelId || channelId;
|
|
58
|
+
if (!channelIdToUse) {
|
|
59
|
+
clientLogger.error('[useSocketChat] Cannot send message: no channel ID available');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Add metadata for DM channels
|
|
64
|
+
const messageMetadata = {
|
|
65
|
+
...metadata,
|
|
66
|
+
channelType: chatType,
|
|
67
|
+
...(chatType === ChannelType.DM && {
|
|
68
|
+
isDm: true,
|
|
69
|
+
targetUserId: contextId, // The agent ID for DM channels
|
|
70
|
+
}),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
await socketIOManager.sendMessage(
|
|
74
|
+
text,
|
|
75
|
+
channelIdToUse,
|
|
76
|
+
serverId,
|
|
77
|
+
source,
|
|
78
|
+
attachments,
|
|
79
|
+
tempMessageId,
|
|
80
|
+
messageMetadata
|
|
81
|
+
);
|
|
82
|
+
},
|
|
83
|
+
[channelId, socketIOManager, chatType, contextId]
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (!channelId || !currentUserId) {
|
|
88
|
+
// If channelId becomes undefined (e.g., navigating away), ensure we reset the ref
|
|
89
|
+
if (joinedChannelRef.current) {
|
|
90
|
+
clientLogger.info(
|
|
91
|
+
`[useSocketChat] useEffect: channelId is now null/undefined, resetting joinedChannelRef from ${joinedChannelRef.current}`
|
|
92
|
+
);
|
|
93
|
+
joinedChannelRef.current = null;
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
socketIOManager.initialize(currentUserId); // Initialize on user context
|
|
99
|
+
|
|
100
|
+
// Only join if this specific channelId hasn't been joined by this hook instance yet,
|
|
101
|
+
// or if the channelId has changed.
|
|
102
|
+
if (channelId !== joinedChannelRef.current) {
|
|
103
|
+
clientLogger.info(
|
|
104
|
+
`[useSocketChat] useEffect: Joining channel ${channelId}. Previous joinedChannelRef: ${joinedChannelRef.current}`
|
|
105
|
+
);
|
|
106
|
+
socketIOManager.joinChannel(channelId);
|
|
107
|
+
joinedChannelRef.current = channelId; // Mark this channelId as joined by this instance
|
|
108
|
+
} else {
|
|
109
|
+
clientLogger.info(
|
|
110
|
+
`[useSocketChat] useEffect: Channel ${channelId} already marked as joined by this instance. Skipping joinChannel call.`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const handleMessageBroadcasting = (data: MessageBroadcastData) => {
|
|
115
|
+
clientLogger.info(
|
|
116
|
+
'[useSocketChat] Received raw messageBroadcast data:',
|
|
117
|
+
JSON.stringify(data)
|
|
118
|
+
);
|
|
119
|
+
const msgChannelId = data.channelId || data.roomId;
|
|
120
|
+
if (msgChannelId !== channelId) return;
|
|
121
|
+
const isCurrentUser = data.senderId === currentUserId;
|
|
122
|
+
|
|
123
|
+
// Unified message handling for both DM and GROUP
|
|
124
|
+
const isTargetAgent =
|
|
125
|
+
chatType === ChannelType.DM
|
|
126
|
+
? data.senderId === contextId
|
|
127
|
+
: allAgents.some((agent) => agent.id === data.senderId);
|
|
128
|
+
|
|
129
|
+
if (!isCurrentUser && isTargetAgent) onInputDisabledChange(false);
|
|
130
|
+
|
|
131
|
+
const clientMessageId = (data as any).clientMessageId;
|
|
132
|
+
if (clientMessageId && isCurrentUser) {
|
|
133
|
+
// Update optimistic message with server response
|
|
134
|
+
onUpdateMessage(clientMessageId, {
|
|
135
|
+
id: data.id || randomUUID(),
|
|
136
|
+
isLoading: false,
|
|
137
|
+
createdAt:
|
|
138
|
+
typeof data.createdAt === 'number' ? data.createdAt : Date.parse(data.createdAt),
|
|
139
|
+
text: data.text,
|
|
140
|
+
attachments: data.attachments,
|
|
141
|
+
isAgent: false,
|
|
142
|
+
});
|
|
143
|
+
} else {
|
|
144
|
+
// Add new message from other participants
|
|
145
|
+
const newUiMsg: UiMessage = {
|
|
146
|
+
id: data.id || randomUUID(),
|
|
147
|
+
text: data.text,
|
|
148
|
+
name: data.senderName,
|
|
149
|
+
senderId: data.senderId as UUID,
|
|
150
|
+
isAgent: isTargetAgent,
|
|
151
|
+
createdAt:
|
|
152
|
+
typeof data.createdAt === 'number' ? data.createdAt : Date.parse(data.createdAt),
|
|
153
|
+
channelId: (data.channelId || data.roomId) as UUID,
|
|
154
|
+
serverId: data.serverId as UUID | undefined,
|
|
155
|
+
source: data.source,
|
|
156
|
+
attachments: data.attachments,
|
|
157
|
+
thought: data.thought,
|
|
158
|
+
actions: data.actions,
|
|
159
|
+
isLoading: false,
|
|
160
|
+
prompt: data.prompt,
|
|
161
|
+
rawMessage: data.rawMessage,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Check if message already exists
|
|
165
|
+
const messageExists = messages.some((m) => m.id === data.id);
|
|
166
|
+
if (!messageExists) {
|
|
167
|
+
clientLogger.info('[useSocketChat] Adding new UiMessage:', JSON.stringify(newUiMsg));
|
|
168
|
+
onAddMessage(newUiMsg);
|
|
169
|
+
|
|
170
|
+
if (isTargetAgent && newUiMsg.id) {
|
|
171
|
+
animatedMessageIdRef.current = newUiMsg.id;
|
|
172
|
+
} else {
|
|
173
|
+
animatedMessageIdRef.current = null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const handleMessageComplete = (data: MessageCompleteData) => {
|
|
180
|
+
const completeChannelId = data.channelId || data.roomId;
|
|
181
|
+
if (completeChannelId === channelId) onInputDisabledChange(false);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const handleControlMessage = (data: ControlMessageData) => {
|
|
185
|
+
const ctrlChannelId = data.channelId || data.roomId;
|
|
186
|
+
if (ctrlChannelId === channelId) {
|
|
187
|
+
if (data.action === 'disable_input') onInputDisabledChange(true);
|
|
188
|
+
else if (data.action === 'enable_input') onInputDisabledChange(false);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const handleMessageDeleted = (data: MessageDeletedData) => {
|
|
193
|
+
const deletedChannelId = data.channelId || data.roomId;
|
|
194
|
+
if (deletedChannelId === channelId && data.messageId) {
|
|
195
|
+
onDeleteMessage(data.messageId);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const handleChannelCleared = (data: ChannelClearedData) => {
|
|
200
|
+
const clearedChannelId = data.channelId || data.roomId;
|
|
201
|
+
if (clearedChannelId === channelId) {
|
|
202
|
+
onClearMessages();
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const handleChannelDeleted = (data: ChannelDeletedData) => {
|
|
207
|
+
const deletedChannelId = data.channelId || data.roomId;
|
|
208
|
+
if (deletedChannelId === channelId) {
|
|
209
|
+
onClearMessages();
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const msgSub = socketIOManager.evtMessageBroadcast.attach(
|
|
214
|
+
(d: MessageBroadcastData) => (d.channelId || d.roomId) === channelId,
|
|
215
|
+
handleMessageBroadcasting
|
|
216
|
+
);
|
|
217
|
+
const completeSub = socketIOManager.evtMessageComplete.attach(
|
|
218
|
+
(d: MessageCompleteData) => (d.channelId || d.roomId) === channelId,
|
|
219
|
+
handleMessageComplete
|
|
220
|
+
);
|
|
221
|
+
const controlSub = socketIOManager.evtControlMessage.attach(
|
|
222
|
+
(d: ControlMessageData) => (d.channelId || d.roomId) === channelId,
|
|
223
|
+
handleControlMessage
|
|
224
|
+
);
|
|
225
|
+
const deleteSub = socketIOManager.evtMessageDeleted.attach(
|
|
226
|
+
(d: MessageDeletedData) => (d.channelId || d.roomId) === channelId,
|
|
227
|
+
handleMessageDeleted
|
|
228
|
+
);
|
|
229
|
+
const clearSub = socketIOManager.evtChannelCleared.attach(
|
|
230
|
+
(d: ChannelClearedData) => (d.channelId || d.roomId) === channelId,
|
|
231
|
+
handleChannelCleared
|
|
232
|
+
);
|
|
233
|
+
const deletedSub = socketIOManager.evtChannelDeleted.attach(
|
|
234
|
+
(d: ChannelDeletedData) => (d.channelId || d.roomId) === channelId,
|
|
235
|
+
handleChannelDeleted
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
return () => {
|
|
239
|
+
if (channelId) {
|
|
240
|
+
clientLogger.info(
|
|
241
|
+
`[useSocketChat] useEffect cleanup: Leaving channel ${channelId}. Current joinedChannelRef: ${joinedChannelRef.current}`
|
|
242
|
+
);
|
|
243
|
+
socketIOManager.leaveChannel(channelId);
|
|
244
|
+
// Reset ref when component unmounts or channelId changes leading to cleanup
|
|
245
|
+
if (channelId === joinedChannelRef.current) {
|
|
246
|
+
joinedChannelRef.current = null;
|
|
247
|
+
clientLogger.info(
|
|
248
|
+
`[useSocketChat] useEffect cleanup: Reset joinedChannelRef for ${channelId}`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
detachSubscriptions([msgSub, completeSub, controlSub, deleteSub, clearSub, deletedSub]);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
function detachSubscriptions(subscriptions: Array<{ detach: () => void } | undefined>) {
|
|
256
|
+
subscriptions.forEach((sub) => sub?.detach());
|
|
257
|
+
}
|
|
258
|
+
}, [channelId, currentUserId, socketIOManager]);
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
sendMessage,
|
|
262
|
+
animatedMessageId: animatedMessageIdRef.current,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { ToastActionElement, ToastProps } from '@/components/ui/toast';
|
|
4
|
+
|
|
5
|
+
const TOAST_LIMIT = 1;
|
|
6
|
+
const TOAST_REMOVE_DELAY = 3000; // 3 seconds auto-dismiss
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Represents a toast object with additional properties.
|
|
10
|
+
* @typedef {Object} ToasterToast
|
|
11
|
+
* @property {string} id - The unique identifier of the toast.
|
|
12
|
+
* @property {React.ReactNode} [title] - The title displayed in the toast.
|
|
13
|
+
* @property {React.ReactNode} [description] - The description displayed in the toast.
|
|
14
|
+
* @property {ToastActionElement} [action] - The action element displayed in the toast.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
type ToasterToast = ToastProps & {
|
|
18
|
+
id: string;
|
|
19
|
+
title?: React.ReactNode;
|
|
20
|
+
description?: React.ReactNode;
|
|
21
|
+
action?: ToastActionElement;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const actionTypes = {
|
|
25
|
+
ADD_TOAST: 'ADD_TOAST',
|
|
26
|
+
UPDATE_TOAST: 'UPDATE_TOAST',
|
|
27
|
+
DISMISS_TOAST: 'DISMISS_TOAST',
|
|
28
|
+
REMOVE_TOAST: 'REMOVE_TOAST',
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Variable to hold the count value.
|
|
33
|
+
*/
|
|
34
|
+
let count = 0;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Generates a unique ID string each time it is called.
|
|
38
|
+
*
|
|
39
|
+
* @returns {string} The generated ID string.
|
|
40
|
+
*/
|
|
41
|
+
function genId() {
|
|
42
|
+
count = (count + 1) % Number.MAX_SAFE_INTEGER;
|
|
43
|
+
return count.toString();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Define a type ActionType that is based on the values of the actionTypes object.
|
|
48
|
+
*/
|
|
49
|
+
type ActionType = typeof actionTypes;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Represents different types of actions that can be dispatched to
|
|
53
|
+
* manipulate the state of a toaster toast.
|
|
54
|
+
* @typedef {Object} Action
|
|
55
|
+
* @property {ActionType["ADD_TOAST"]} type - The type of action to add a new toast.
|
|
56
|
+
* @property {ToasterToast} toast - The toast to add.
|
|
57
|
+
* @property {ActionType["UPDATE_TOAST"]} type - The type of action to update an existing toast.
|
|
58
|
+
* @property {Partial<ToasterToast>} toast - The updated fields of the toast.
|
|
59
|
+
* @property {ActionType["DISMISS_TOAST"]} type - The type of action to dismiss a toast.
|
|
60
|
+
* @property {ToasterToast["id"]} [toastId] - The ID of the toast to dismiss.
|
|
61
|
+
* @property {ActionType["REMOVE_TOAST"]} type - The type of action to remove a toast.
|
|
62
|
+
* @property {ToasterToast["id"]} [toastId] - The ID of the toast to remove.
|
|
63
|
+
*/
|
|
64
|
+
type Action =
|
|
65
|
+
| {
|
|
66
|
+
type: ActionType['ADD_TOAST'];
|
|
67
|
+
toast: ToasterToast;
|
|
68
|
+
}
|
|
69
|
+
| {
|
|
70
|
+
type: ActionType['UPDATE_TOAST'];
|
|
71
|
+
toast: Partial<ToasterToast>;
|
|
72
|
+
}
|
|
73
|
+
| {
|
|
74
|
+
type: ActionType['DISMISS_TOAST'];
|
|
75
|
+
toastId?: ToasterToast['id'];
|
|
76
|
+
}
|
|
77
|
+
| {
|
|
78
|
+
type: ActionType['REMOVE_TOAST'];
|
|
79
|
+
toastId?: ToasterToast['id'];
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Interface representing the state object with an array of toasts.
|
|
84
|
+
*/
|
|
85
|
+
interface State {
|
|
86
|
+
toasts: ToasterToast[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Adds a toast to the removal queue with a specified toast ID.
|
|
93
|
+
* If the toast ID already exists in the queue, it will not be added again.
|
|
94
|
+
* Once the timeout period specified by TOAST_REMOVE_DELAY has elapsed, the toast will be removed from the queue.
|
|
95
|
+
*
|
|
96
|
+
* @param {string} toastId - The unique identifier for the toast to be added to the removal queue
|
|
97
|
+
*/
|
|
98
|
+
const addToRemoveQueue = (toastId: string) => {
|
|
99
|
+
if (toastTimeouts.has(toastId)) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const timeout = setTimeout(() => {
|
|
104
|
+
toastTimeouts.delete(toastId);
|
|
105
|
+
dispatch({
|
|
106
|
+
type: 'REMOVE_TOAST',
|
|
107
|
+
toastId: toastId,
|
|
108
|
+
});
|
|
109
|
+
}, TOAST_REMOVE_DELAY);
|
|
110
|
+
|
|
111
|
+
toastTimeouts.set(toastId, timeout);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Reducer function to handle various actions on the state related to toasts.
|
|
116
|
+
* @param {State} state - The current state of the application.
|
|
117
|
+
* @param {Action} action - The action to be performed on the state.
|
|
118
|
+
* @returns {State} - The updated state after performing the action.
|
|
119
|
+
*/
|
|
120
|
+
export const reducer = (state: State, action: Action): State => {
|
|
121
|
+
switch (action.type) {
|
|
122
|
+
case 'ADD_TOAST':
|
|
123
|
+
return {
|
|
124
|
+
...state,
|
|
125
|
+
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
case 'UPDATE_TOAST':
|
|
129
|
+
return {
|
|
130
|
+
...state,
|
|
131
|
+
toasts: state.toasts.map((t) => (t.id === action.toast.id ? { ...t, ...action.toast } : t)),
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
case 'DISMISS_TOAST': {
|
|
135
|
+
const { toastId } = action;
|
|
136
|
+
|
|
137
|
+
// ! Side effects ! - This could be extracted into a dismissToast() action,
|
|
138
|
+
// but I'll keep it here for simplicity
|
|
139
|
+
if (toastId) {
|
|
140
|
+
addToRemoveQueue(toastId);
|
|
141
|
+
} else {
|
|
142
|
+
for (const toast of state.toasts) {
|
|
143
|
+
addToRemoveQueue(toast.id);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
...state,
|
|
149
|
+
toasts: state.toasts.map((t) =>
|
|
150
|
+
t.id === toastId || toastId === undefined
|
|
151
|
+
? {
|
|
152
|
+
...t,
|
|
153
|
+
open: false,
|
|
154
|
+
}
|
|
155
|
+
: t
|
|
156
|
+
),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
case 'REMOVE_TOAST':
|
|
160
|
+
if (action.toastId === undefined) {
|
|
161
|
+
return {
|
|
162
|
+
...state,
|
|
163
|
+
toasts: [],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
...state,
|
|
168
|
+
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const listeners: Array<(state: State) => void> = [];
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Defines a variable to store the memory state, initialized with an empty array for toasts.
|
|
177
|
+
*/
|
|
178
|
+
let memoryState: State = { toasts: [] };
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Dispatches an action by passing it to the reducer function and then
|
|
182
|
+
* notifies all registered listeners with the updated memory state.
|
|
183
|
+
*
|
|
184
|
+
* @param {Action} action The action to dispatch
|
|
185
|
+
*/
|
|
186
|
+
function dispatch(action: Action) {
|
|
187
|
+
memoryState = reducer(memoryState, action);
|
|
188
|
+
for (const listener of listeners) {
|
|
189
|
+
listener(memoryState);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Represents a Toast object without the "id" property.
|
|
195
|
+
*/
|
|
196
|
+
type Toast = Omit<ToasterToast, 'id'>;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Creates a new toast message with the given properties.
|
|
200
|
+
* @param {Toast} props - The props for the toast message.
|
|
201
|
+
* @returns {Object} An object containing the id of the toast, a function to dismiss the toast, and a function to update the toast.
|
|
202
|
+
*/
|
|
203
|
+
function toast({ ...props }: Toast) {
|
|
204
|
+
const id = genId();
|
|
205
|
+
|
|
206
|
+
const update = (props: ToasterToast) =>
|
|
207
|
+
dispatch({
|
|
208
|
+
type: 'UPDATE_TOAST',
|
|
209
|
+
toast: { ...props, id },
|
|
210
|
+
});
|
|
211
|
+
const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id });
|
|
212
|
+
|
|
213
|
+
dispatch({
|
|
214
|
+
type: 'ADD_TOAST',
|
|
215
|
+
toast: {
|
|
216
|
+
...props,
|
|
217
|
+
id,
|
|
218
|
+
open: true,
|
|
219
|
+
onOpenChange: (open) => {
|
|
220
|
+
if (!open) dismiss();
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
id: id,
|
|
227
|
+
dismiss,
|
|
228
|
+
update,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Custom hook for managing toast messages.
|
|
234
|
+
*
|
|
235
|
+
* @returns {{
|
|
236
|
+
* showToast: Function,
|
|
237
|
+
* dismiss: Function,
|
|
238
|
+
* }}
|
|
239
|
+
*/
|
|
240
|
+
function useToast() {
|
|
241
|
+
const [state, setState] = React.useState<State>(memoryState);
|
|
242
|
+
|
|
243
|
+
React.useEffect(() => {
|
|
244
|
+
listeners.push(setState);
|
|
245
|
+
return () => {
|
|
246
|
+
const index = listeners.indexOf(setState);
|
|
247
|
+
if (index > -1) {
|
|
248
|
+
listeners.splice(index, 1);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}, []);
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
...state,
|
|
255
|
+
toast,
|
|
256
|
+
dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export { useToast, toast };
|