@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.
Files changed (209) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +350 -0
  3. package/dist/assets/empty-module-CLMscLYw.js +1 -0
  4. package/dist/assets/main-BBZ_3lkn.css +5999 -0
  5. package/dist/assets/main-C5zNUkXH.js +7 -0
  6. package/dist/assets/main-Dz64ENQg.js +614 -0
  7. package/dist/assets/react-vendor-DM5m98rr.js +545 -0
  8. package/dist/assets/ui-vendor-BQCqNqg0.js +1 -0
  9. package/dist/elizaos-avatar.png +0 -0
  10. package/dist/elizaos-icon.png +0 -0
  11. package/dist/elizaos-logo-light.png +0 -0
  12. package/dist/elizaos.webp +0 -0
  13. package/dist/favicon.ico +0 -0
  14. package/dist/images/agents/agent1.png +0 -0
  15. package/dist/images/agents/agent2.png +0 -0
  16. package/dist/images/agents/agent3.png +0 -0
  17. package/dist/images/agents/agent4.png +0 -0
  18. package/dist/images/agents/agent5.png +0 -0
  19. package/dist/index.html +14 -0
  20. package/index.html +24 -0
  21. package/package.json +159 -0
  22. package/postcss.config.js +3 -0
  23. package/public/elizaos-avatar.png +0 -0
  24. package/public/elizaos-icon.png +0 -0
  25. package/public/elizaos-logo-light.png +0 -0
  26. package/public/elizaos.webp +0 -0
  27. package/public/favicon.ico +0 -0
  28. package/public/images/agents/agent1.png +0 -0
  29. package/public/images/agents/agent2.png +0 -0
  30. package/public/images/agents/agent3.png +0 -0
  31. package/public/images/agents/agent4.png +0 -0
  32. package/public/images/agents/agent5.png +0 -0
  33. package/src/App.tsx +222 -0
  34. package/src/components/AgentDetailsPanel.tsx +147 -0
  35. package/src/components/ChatInputArea.tsx +196 -0
  36. package/src/components/ChatMessageListComponent.tsx +139 -0
  37. package/src/components/actionTool.tsx +186 -0
  38. package/src/components/add-agent-card.tsx +77 -0
  39. package/src/components/agent-action-viewer.tsx +816 -0
  40. package/src/components/agent-avatar-stack.tsx +121 -0
  41. package/src/components/agent-card.cy.tsx +259 -0
  42. package/src/components/agent-card.tsx +177 -0
  43. package/src/components/agent-creator.tsx +142 -0
  44. package/src/components/agent-log-viewer.tsx +645 -0
  45. package/src/components/agent-memory-edit-overlay.tsx +461 -0
  46. package/src/components/agent-memory-viewer.tsx +504 -0
  47. package/src/components/agent-settings.tsx +270 -0
  48. package/src/components/agent-sidebar.tsx +178 -0
  49. package/src/components/api-key-dialog.tsx +113 -0
  50. package/src/components/app-sidebar.tsx +685 -0
  51. package/src/components/array-input.tsx +116 -0
  52. package/src/components/audio-recorder.tsx +292 -0
  53. package/src/components/avatar-panel.tsx +141 -0
  54. package/src/components/character-form.tsx +1138 -0
  55. package/src/components/chat.tsx +1813 -0
  56. package/src/components/combobox.tsx +187 -0
  57. package/src/components/confirmation-dialog.tsx +59 -0
  58. package/src/components/connection-error-banner.tsx +101 -0
  59. package/src/components/connection-status.cy.tsx +73 -0
  60. package/src/components/connection-status.tsx +155 -0
  61. package/src/components/copy-button.tsx +35 -0
  62. package/src/components/delete-button.tsx +24 -0
  63. package/src/components/env-settings.tsx +261 -0
  64. package/src/components/group-card.tsx +160 -0
  65. package/src/components/group-panel.tsx +543 -0
  66. package/src/components/input-copy.tsx +21 -0
  67. package/src/components/logs-page.tsx +41 -0
  68. package/src/components/media-content.tsx +385 -0
  69. package/src/components/memory-graph.tsx +170 -0
  70. package/src/components/missing-secrets-dialog.tsx +72 -0
  71. package/src/components/onboarding-tour.tsx +247 -0
  72. package/src/components/page-title.tsx +8 -0
  73. package/src/components/plugins-panel.tsx +383 -0
  74. package/src/components/profile-card.tsx +66 -0
  75. package/src/components/profile-overlay.tsx +283 -0
  76. package/src/components/retry-button.tsx +28 -0
  77. package/src/components/secret-panel.tsx +1505 -0
  78. package/src/components/server-management.tsx +264 -0
  79. package/src/components/split-button.tsx +148 -0
  80. package/src/components/stop-agent-button.tsx +99 -0
  81. package/src/components/ui/alert-dialog.cy.tsx +333 -0
  82. package/src/components/ui/alert-dialog.tsx +115 -0
  83. package/src/components/ui/alert.tsx +49 -0
  84. package/src/components/ui/avatar.cy.tsx +180 -0
  85. package/src/components/ui/avatar.tsx +57 -0
  86. package/src/components/ui/badge.cy.tsx +146 -0
  87. package/src/components/ui/badge.tsx +43 -0
  88. package/src/components/ui/button.cy.tsx +177 -0
  89. package/src/components/ui/button.tsx +56 -0
  90. package/src/components/ui/card.cy.tsx +160 -0
  91. package/src/components/ui/card.tsx +73 -0
  92. package/src/components/ui/chat/animated-markdown.tsx +59 -0
  93. package/src/components/ui/chat/chat-bubble.tsx +178 -0
  94. package/src/components/ui/chat/chat-container.tsx +51 -0
  95. package/src/components/ui/chat/chat-input.cy.tsx +169 -0
  96. package/src/components/ui/chat/chat-input.tsx +47 -0
  97. package/src/components/ui/chat/chat-message-list.tsx +61 -0
  98. package/src/components/ui/chat/chat-tts-button.tsx +199 -0
  99. package/src/components/ui/chat/code-block.tsx +79 -0
  100. package/src/components/ui/chat/expandable-chat.tsx +131 -0
  101. package/src/components/ui/chat/hooks/useAutoScroll.ts +86 -0
  102. package/src/components/ui/chat/markdown.tsx +209 -0
  103. package/src/components/ui/chat/message-loading.tsx +48 -0
  104. package/src/components/ui/checkbox.cy.tsx +170 -0
  105. package/src/components/ui/checkbox.tsx +30 -0
  106. package/src/components/ui/collapsible.cy.tsx +283 -0
  107. package/src/components/ui/collapsible.tsx +9 -0
  108. package/src/components/ui/command.cy.tsx +313 -0
  109. package/src/components/ui/command.tsx +143 -0
  110. package/src/components/ui/dialog.cy.tsx +279 -0
  111. package/src/components/ui/dialog.tsx +104 -0
  112. package/src/components/ui/dropdown-menu.cy.tsx +273 -0
  113. package/src/components/ui/dropdown-menu.tsx +281 -0
  114. package/src/components/ui/input.cy.tsx +82 -0
  115. package/src/components/ui/input.tsx +27 -0
  116. package/src/components/ui/label.cy.tsx +157 -0
  117. package/src/components/ui/label.tsx +19 -0
  118. package/src/components/ui/resizable.tsx +42 -0
  119. package/src/components/ui/scroll-area.cy.tsx +242 -0
  120. package/src/components/ui/scroll-area.tsx +46 -0
  121. package/src/components/ui/select.cy.tsx +277 -0
  122. package/src/components/ui/select.tsx +155 -0
  123. package/src/components/ui/separator.cy.tsx +145 -0
  124. package/src/components/ui/separator.tsx +29 -0
  125. package/src/components/ui/sheet.cy.tsx +324 -0
  126. package/src/components/ui/sheet.tsx +119 -0
  127. package/src/components/ui/sidebar.tsx +734 -0
  128. package/src/components/ui/skeleton.cy.tsx +149 -0
  129. package/src/components/ui/skeleton.tsx +17 -0
  130. package/src/components/ui/split-button.cy.tsx +274 -0
  131. package/src/components/ui/split-button.tsx +112 -0
  132. package/src/components/ui/switch.tsx +28 -0
  133. package/src/components/ui/tabs.cy.tsx +271 -0
  134. package/src/components/ui/tabs.tsx +53 -0
  135. package/src/components/ui/textarea.cy.tsx +136 -0
  136. package/src/components/ui/textarea.tsx +26 -0
  137. package/src/components/ui/toast.cy.tsx +209 -0
  138. package/src/components/ui/toast.tsx +126 -0
  139. package/src/components/ui/toaster.tsx +29 -0
  140. package/src/components/ui/tooltip.cy.tsx +244 -0
  141. package/src/components/ui/tooltip.tsx +30 -0
  142. package/src/config/agent-templates.ts +349 -0
  143. package/src/config/voice-models.ts +181 -0
  144. package/src/constants.ts +23 -0
  145. package/src/context/AuthContext.tsx +44 -0
  146. package/src/context/ConnectionContext.tsx +194 -0
  147. package/src/entry.tsx +9 -0
  148. package/src/hooks/__tests__/use-agent-tab-state.test.ts +137 -0
  149. package/src/hooks/__tests__/use-agent-update.test.tsx +250 -0
  150. package/src/hooks/__tests__/use-character-convert.test.ts +102 -0
  151. package/src/hooks/__tests__/use-panel-width-state.test.ts +243 -0
  152. package/src/hooks/__tests__/use-sidebar-state.test.ts +117 -0
  153. package/src/hooks/use-agent-management.ts +130 -0
  154. package/src/hooks/use-agent-tab-state.ts +74 -0
  155. package/src/hooks/use-agent-update.ts +469 -0
  156. package/src/hooks/use-character-convert.ts +138 -0
  157. package/src/hooks/use-confirmation.ts +55 -0
  158. package/src/hooks/use-delete-agent.ts +123 -0
  159. package/src/hooks/use-dm-channels.ts +198 -0
  160. package/src/hooks/use-elevenlabs-voices.ts +83 -0
  161. package/src/hooks/use-file-upload.ts +224 -0
  162. package/src/hooks/use-mobile.tsx +19 -0
  163. package/src/hooks/use-onboarding.tsx +49 -0
  164. package/src/hooks/use-panel-width-state.ts +147 -0
  165. package/src/hooks/use-partial-update.ts +288 -0
  166. package/src/hooks/use-plugin-details.ts +462 -0
  167. package/src/hooks/use-plugins.ts +119 -0
  168. package/src/hooks/use-query-hooks.ts +1263 -0
  169. package/src/hooks/use-server-agents.ts +62 -0
  170. package/src/hooks/use-server-version.tsx +47 -0
  171. package/src/hooks/use-sidebar-state.ts +50 -0
  172. package/src/hooks/use-socket-chat.ts +264 -0
  173. package/src/hooks/use-toast.ts +260 -0
  174. package/src/hooks/use-version.tsx +64 -0
  175. package/src/index.css +146 -0
  176. package/src/lib/api-client-config.ts +53 -0
  177. package/src/lib/api-type-mappers.ts +196 -0
  178. package/src/lib/export-utils.ts +123 -0
  179. package/src/lib/logger.ts +19 -0
  180. package/src/lib/media-utils.ts +170 -0
  181. package/src/lib/pca.test.ts +17 -0
  182. package/src/lib/pca.ts +52 -0
  183. package/src/lib/socketio-manager.ts +664 -0
  184. package/src/lib/utils.ts +168 -0
  185. package/src/main.tsx +16 -0
  186. package/src/mocks/empty-module.ts +12 -0
  187. package/src/mocks/node-module.ts +57 -0
  188. package/src/polyfills.ts +37 -0
  189. package/src/routes/agent-detail.tsx +30 -0
  190. package/src/routes/agent-list.tsx +27 -0
  191. package/src/routes/agent-settings.tsx +48 -0
  192. package/src/routes/character-detail.tsx +52 -0
  193. package/src/routes/character-form.tsx +79 -0
  194. package/src/routes/character-list.tsx +38 -0
  195. package/src/routes/chat.tsx +128 -0
  196. package/src/routes/createAgent.tsx +13 -0
  197. package/src/routes/group-new.tsx +50 -0
  198. package/src/routes/group.tsx +29 -0
  199. package/src/routes/home.tsx +218 -0
  200. package/src/routes/not-found.tsx +71 -0
  201. package/src/test/setup.ts +154 -0
  202. package/src/types/crypto-browserify.d.ts +4 -0
  203. package/src/types/index.ts +13 -0
  204. package/src/types/rooms.ts +8 -0
  205. package/src/types.ts +84 -0
  206. package/src/vite-env.d.ts +40 -0
  207. package/tailwind.config.ts +90 -0
  208. package/tsconfig.json +10 -0
  209. 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 };