@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,1263 @@
1
+ import { GROUP_CHAT_SOURCE, USER_NAME } from '@/constants';
2
+ // Direct error handling without bridge layer
3
+ import { createElizaClient } from '@/lib/api-client-config';
4
+ import type {
5
+ Agent,
6
+ Content,
7
+ Memory,
8
+ UUID,
9
+ Memory as CoreMemory,
10
+ AgentStatus,
11
+ } from '@elizaos/core';
12
+ import {
13
+ useQuery,
14
+ useMutation,
15
+ useQueryClient,
16
+ useQueries,
17
+ UseQueryResult,
18
+ type DefinedUseQueryResult,
19
+ type UndefinedInitialDataOptions,
20
+ type UseQueryOptions,
21
+ } from '@tanstack/react-query';
22
+ import { useState, useEffect, useCallback, useMemo } from 'react';
23
+ import { useToast } from './use-toast';
24
+ import { getEntityId, randomUUID, moment } from '@/lib/utils';
25
+ import type {
26
+ ServerMessage,
27
+ AgentWithStatus,
28
+ MessageChannel as ClientMessageChannel,
29
+ MessageServer as ClientMessageServer,
30
+ } from '@/types';
31
+ import clientLogger from '@/lib/logger';
32
+ import { useNavigate } from 'react-router-dom';
33
+ import {
34
+ mapApiAgentToClient,
35
+ mapApiChannelToClient,
36
+ mapApiServerToClient,
37
+ mapApiServersToClient,
38
+ mapApiChannelsToClient,
39
+ mapApiMessageToUi,
40
+ mapApiLogToClient,
41
+ mapApiMemoryToClient,
42
+ mapEnumToApiStatus,
43
+ apiDateToTimestamp,
44
+ type AgentLog,
45
+ } from '@/lib/api-type-mappers';
46
+
47
+ // Create ElizaClient instance for direct API calls
48
+ const elizaClient = createElizaClient();
49
+
50
+ /**
51
+ * Represents content with additional user information.
52
+ * @typedef {Object} ContentWithUser
53
+ * @property {string} name - The name of the user.
54
+ * @property {number} createdAt - The timestamp when the content was created.
55
+ * @property {boolean} [isLoading] - Optional flag indicating if the content is currently loading.
56
+ * @property {string} [worldId] - Optional ID of the world associated with the content.
57
+ * @property {string} [id] - Optional ID field.
58
+ */
59
+ type ContentWithUser = Content & {
60
+ name: string;
61
+ createdAt: number;
62
+ isLoading?: boolean;
63
+ worldId?: UUID;
64
+ id?: UUID; // Add optional ID field
65
+ };
66
+
67
+ // AgentLog type is now imported from api-type-mappers
68
+
69
+ // Constants for stale times
70
+ export const STALE_TIMES = {
71
+ FREQUENT: 30000, // 30 seconds - for data that changes often
72
+ STANDARD: 120000, // 2 minutes - default
73
+ RARE: 600000, // 10 minutes - for rarely changing data
74
+ NEVER: Number.POSITIVE_INFINITY, // Only refetch on explicit invalidation
75
+ };
76
+
77
+ // Network Information API interface
78
+ /**
79
+ * Interface for representing network information.
80
+ *
81
+ * @property {("slow-2g" | "2g" | "3g" | "4g" | "unknown")} effectiveType - The effective network type.
82
+ * @property {boolean} saveData - Indicates if data saver mode is enabled.
83
+ * @property {unknown} [key] - Additional properties with unknown value types.
84
+ */
85
+
86
+ interface NetworkInformation {
87
+ effectiveType: 'slow-2g' | '2g' | '3g' | '4g' | 'unknown';
88
+ saveData: boolean;
89
+ [key: string]: unknown;
90
+ }
91
+
92
+ // Network status detection for smart polling
93
+ /**
94
+ * A custom React hook that returns the network status information.
95
+ * Utilizes the Network Information API if available.
96
+ * @returns {{
97
+ * isOffline: boolean,
98
+ * effectiveType: string,
99
+ * saveData: boolean
100
+ * }} The network status information including whether the user is offline, the effective connection type, and if data-saving mode is enabled.
101
+ */
102
+ const useNetworkStatus = () => {
103
+ // Get navigator.connection if available (Network Information API)
104
+ const connection =
105
+ typeof navigator !== 'undefined' && 'connection' in navigator
106
+ ? (navigator as Navigator & { connection: NetworkInformation }).connection
107
+ : null;
108
+
109
+ // Return the effective connection type or a default value
110
+ return {
111
+ isOffline: typeof navigator !== 'undefined' && !navigator.onLine,
112
+ effectiveType: connection?.effectiveType || 'unknown',
113
+ saveData: connection?.saveData || false,
114
+ };
115
+ };
116
+
117
+ // Hook for fetching agents with smart polling
118
+ /**
119
+ * Fetches a list of agents from the server with polling and network-aware intervals.
120
+ *
121
+ * @param options - Optional configuration to override default query behavior.
122
+ * @returns A React Query object containing the agents data and query state.
123
+ *
124
+ * @remark Polling frequency adapts to network conditions, using less frequent polling when offline or on slow connections.
125
+ */
126
+ export function useAgents(options = {}) {
127
+ const network = useNetworkStatus();
128
+
129
+ return useQuery<{ data: { agents: Partial<AgentWithStatus>[] } }>({
130
+ queryKey: ['agents'],
131
+ queryFn: async () => {
132
+ const result = await elizaClient.agents.listAgents();
133
+ // Map the API agents to client format
134
+ const mappedAgents = result.agents.map((agent) => mapApiAgentToClient(agent));
135
+ return { data: { agents: mappedAgents } };
136
+ },
137
+ staleTime: STALE_TIMES.FREQUENT, // Use shorter stale time for real-time data
138
+ // Use more frequent polling for real-time updates
139
+ refetchInterval: !network.isOffline ? STALE_TIMES.FREQUENT : false,
140
+ // Disable polling when the tab is not active
141
+ refetchIntervalInBackground: false,
142
+ // Configure based on network conditions
143
+ ...(!network.isOffline &&
144
+ network.effectiveType === 'slow-2g' && {
145
+ refetchInterval: STALE_TIMES.STANDARD, // Poll less frequently on slow connections
146
+ }),
147
+ // Allow overriding any options
148
+ ...options,
149
+ });
150
+ }
151
+
152
+ // Hook for fetching a specific agent with smart polling
153
+ /**
154
+ * Custom hook to fetch agent data based on the provided agentId.
155
+ * @param {UUID | undefined | null} agentId - The ID of the agent to fetch data for.
156
+ * @param {Object} options - Additional options to configure the query.
157
+ * @returns {QueryResult} The result of the query containing agent data.
158
+ */
159
+ export function useAgent(agentId: UUID | undefined | null, options = {}) {
160
+ const network = useNetworkStatus();
161
+
162
+ return useQuery<{ data: AgentWithStatus }>({
163
+ queryKey: ['agent', agentId],
164
+ queryFn: async () => {
165
+ if (!agentId) throw new Error('Agent ID is required');
166
+ const result = await elizaClient.agents.getAgent(agentId);
167
+ return { data: mapApiAgentToClient(result) };
168
+ },
169
+ staleTime: STALE_TIMES.FREQUENT, // Use shorter stale time for real-time data
170
+ enabled: Boolean(agentId),
171
+ // Use more frequent polling for real-time updates
172
+ refetchInterval: !network.isOffline && Boolean(agentId) ? STALE_TIMES.FREQUENT : false,
173
+ // Disable polling when the tab is not active
174
+ refetchIntervalInBackground: false,
175
+ // Configure based on network conditions
176
+ ...(!network.isOffline &&
177
+ network.effectiveType === 'slow-2g' && {
178
+ refetchInterval: STALE_TIMES.STANDARD, // Poll less frequently on slow connections
179
+ }),
180
+ // Allow overriding any options
181
+ ...options,
182
+ });
183
+ }
184
+
185
+ // Hook for starting an agent with optimistic updates
186
+ /**
187
+ * Custom hook to start an agent by calling the API with the provided agent ID.
188
+ *
189
+ * @returns {MutationFunction<UUID, unknown>} The useMutation hook for starting an agent.
190
+ */
191
+ export function useStartAgent() {
192
+ const queryClient = useQueryClient();
193
+ const { toast } = useToast();
194
+
195
+ return useMutation<{ data: { id: UUID; name: string; status: string } }, Error, UUID>({
196
+ mutationFn: async (agentId: UUID) => {
197
+ try {
198
+ const result = await elizaClient.agents.startAgent(agentId);
199
+ return { data: { id: agentId, name: 'Agent', status: result.status } };
200
+ } catch (error) {
201
+ // Use the centralized error handler, but preserve specific agent logic
202
+ if (error instanceof Error) {
203
+ if (error.message.includes('already running')) {
204
+ throw new Error('Agent is already running.');
205
+ }
206
+ }
207
+ throw error;
208
+ }
209
+ },
210
+ onMutate: async (_agentId) => {
211
+ // Optimistically update UI to show agent is starting
212
+ toast({
213
+ title: 'Starting Agent',
214
+ description: 'Initializing agent...',
215
+ });
216
+
217
+ // Return context for potential rollback
218
+ return {};
219
+ },
220
+ onSuccess: (response, agentId) => {
221
+ queryClient.invalidateQueries({ queryKey: ['agents'] });
222
+ queryClient.invalidateQueries({ queryKey: ['agent', agentId] });
223
+
224
+ toast({
225
+ title: 'Agent Started',
226
+ description: `${response?.data?.name || 'Agent'} is now running`,
227
+ });
228
+ },
229
+ onError: (error) => {
230
+ // Handle specific error cases
231
+ const errorMessage = error instanceof Error ? error.message : 'Failed to start agent';
232
+
233
+ toast({
234
+ title: 'Error Starting Agent',
235
+ description: `${errorMessage}. Please try again.`,
236
+ variant: 'destructive',
237
+ });
238
+ },
239
+ });
240
+ }
241
+
242
+ // Hook for stopping an agent with optimistic updates
243
+ /**
244
+ * Custom hook to stop an agent by calling the API and updating the UI optimistically.
245
+ *
246
+ * @returns {UseMutationResult} - Object containing the mutation function and its handlers.
247
+ */
248
+ export function useStopAgent() {
249
+ const queryClient = useQueryClient();
250
+ const { toast } = useToast();
251
+
252
+ return useMutation<{ data: { message: string } }, Error, UUID>({
253
+ mutationFn: async (agentId: UUID) => {
254
+ const result = await elizaClient.agents.stopAgent(agentId);
255
+ return { data: { message: `Agent ${result.status}` } };
256
+ },
257
+ onMutate: async (agentId) => {
258
+ // Optimistically update the UI
259
+ // Get the agent data from the cache
260
+ const agent = queryClient.getQueryData<Agent>(['agent', agentId]);
261
+
262
+ if (agent) {
263
+ toast({
264
+ title: 'Stopping Agent',
265
+ description: `Stopping ${agent.name}...`,
266
+ });
267
+ }
268
+ },
269
+ onSuccess: (response, agentId) => {
270
+ // Immediately invalidate the queries for fresh data
271
+ queryClient.invalidateQueries({ queryKey: ['agents'] });
272
+ queryClient.invalidateQueries({ queryKey: ['agent', agentId] });
273
+
274
+ toast({
275
+ title: 'Agent Stopped',
276
+ description: response?.data?.message || 'The agent has been successfully stopped',
277
+ });
278
+ },
279
+ onError: (error, agentId) => {
280
+ // Force invalidate on error
281
+ queryClient.invalidateQueries({ queryKey: ['agents'] });
282
+ queryClient.invalidateQueries({ queryKey: ['agent', agentId] });
283
+
284
+ toast({
285
+ title: 'Error',
286
+ description: error instanceof Error ? error.message : 'Failed to stop agent',
287
+ variant: 'destructive',
288
+ });
289
+ },
290
+ });
291
+ }
292
+
293
+ // Type for UI message list items
294
+ export type UiMessage = Content & {
295
+ id: UUID; // Message ID
296
+ name: string; // Display name of sender (USER_NAME or agent name)
297
+ senderId: UUID; // Central ID of the sender
298
+ isAgent: boolean;
299
+ createdAt: number; // Timestamp ms
300
+ isLoading?: boolean;
301
+ channelId: UUID; // Central Channel ID
302
+ serverId?: UUID; // Server ID (optional in some contexts, but good for full context)
303
+ prompt?: string; // The LLM prompt used to generate this message (for agents)
304
+ // attachments and other Content props are inherited
305
+ };
306
+
307
+ /**
308
+ * Custom hook to manage fetching and loading messages for a specific channel.
309
+ * @param {UUID | undefined} channelId - The GLOBAL ID of the channel.
310
+ * @returns {{...
311
+ }} An object containing messages data, loading states, etc.
312
+ */
313
+ export function useChannelMessages(
314
+ channelId: UUID | undefined, // Changed from UUID | null
315
+ initialServerId?: UUID | undefined // Changed from UUID (optional was already undefined)
316
+ ): {
317
+ data: UiMessage[] | undefined;
318
+ isLoading: boolean;
319
+ isError: boolean;
320
+ error: unknown;
321
+ fetchNextPage: () => Promise<void>; // Simplified pagination trigger
322
+ hasNextPage: boolean;
323
+ isFetchingNextPage: boolean;
324
+ addMessage: (newMessage: UiMessage) => void;
325
+ updateMessage: (messageId: UUID, updates: Partial<UiMessage>) => void;
326
+ removeMessage: (messageId: UUID) => void;
327
+ clearMessages: () => void;
328
+ } {
329
+ const currentClientCentralId = getEntityId(); // Central ID of the currently logged-in user
330
+
331
+ // Using a more manual approach for pagination with getChannelMessages
332
+ const [messages, setMessages] = useState<UiMessage[]>([]);
333
+ const [oldestMessageTimestamp, setOldestMessageTimestamp] = useState<number | null>(null);
334
+ const [hasMoreMessages, setHasMoreMessages] = useState<boolean>(true);
335
+ const [internalIsLoading, setInternalIsLoading] = useState<boolean>(true); // Start true
336
+ const [internalIsError, setInternalIsError] = useState<boolean>(false);
337
+ const [internalError, setInternalError] = useState<unknown>(null);
338
+ const [isFetchingMore, setIsFetchingMore] = useState<boolean>(false);
339
+
340
+ const transformServerMessageToUiMessage = useCallback(
341
+ (sm: ServerMessage, serverIdToUse?: UUID): UiMessage => {
342
+ const isAgent = sm.authorId !== currentClientCentralId;
343
+ let timestamp = Date.now(); // Default to now
344
+
345
+ if (typeof sm.createdAt === 'number') {
346
+ timestamp = sm.createdAt;
347
+ } else if (typeof sm.createdAt === 'string') {
348
+ const parsedTs = Date.parse(sm.createdAt); // Try direct parse
349
+ if (!isNaN(parsedTs)) {
350
+ timestamp = parsedTs;
351
+ } else {
352
+ // If direct parse fails, try moment (if available and robust)
353
+ // For now, log a warning if it's an unparsable string not handled by Date.parse
354
+ clientLogger.warn(
355
+ '[transformServerMessageToUiMessage] createdAt string was not directly parsable by Date.parse():',
356
+ sm.createdAt,
357
+ 'for message id:',
358
+ sm.id
359
+ );
360
+ // As a fallback, could try new Date(sm.createdAt).getTime(), but Date.parse is usually sufficient
361
+ // Defaulting to Date.now() if unparsable to avoid NaN
362
+ }
363
+ } else if (sm.createdAt) {
364
+ // If it's not a number or string, but exists (e.g. could be a Date object from some contexts)
365
+ // Attempt to convert. This is less likely if types are strict from server.
366
+ try {
367
+ const dateObjTimestamp = new Date(sm.createdAt as any).getTime();
368
+ if (!isNaN(dateObjTimestamp)) {
369
+ timestamp = dateObjTimestamp;
370
+ }
371
+ } catch (e) {
372
+ clientLogger.warn(
373
+ '[transformServerMessageToUiMessage] Could not process createdAt (unknown type):',
374
+ sm.createdAt,
375
+ 'for message:',
376
+ sm.id
377
+ );
378
+ }
379
+ }
380
+
381
+ return {
382
+ id: sm.id,
383
+ text: sm.content,
384
+ name: isAgent
385
+ ? sm.metadata?.agentName ||
386
+ sm.metadata?.authorDisplayName ||
387
+ sm.authorDisplayName ||
388
+ 'Agent'
389
+ : USER_NAME,
390
+ senderId: sm.authorId,
391
+ isAgent: isAgent,
392
+ createdAt: timestamp,
393
+ attachments: sm.metadata?.attachments as any[],
394
+ thought: isAgent ? sm.metadata?.thought : undefined,
395
+ actions: isAgent ? sm.metadata?.actions : undefined,
396
+ channelId: sm.channelId,
397
+ serverId: serverIdToUse || sm.metadata?.serverId || sm.serverId || initialServerId,
398
+ source: sm.sourceType,
399
+ isLoading: false,
400
+ prompt: isAgent ? sm.metadata?.prompt : undefined,
401
+ };
402
+ },
403
+ [currentClientCentralId, initialServerId]
404
+ );
405
+
406
+ const fetchMessages = useCallback(
407
+ async (beforeTimestamp?: number) => {
408
+ if (!channelId) {
409
+ setMessages([]);
410
+ setInternalIsLoading(false);
411
+ return;
412
+ }
413
+ if (!beforeTimestamp) {
414
+ setInternalIsLoading(true); // Full load
415
+ } else {
416
+ setIsFetchingMore(true);
417
+ }
418
+ setInternalIsError(false);
419
+ setInternalError(null);
420
+
421
+ try {
422
+ const response = await elizaClient.messaging.getChannelMessages(channelId, {
423
+ limit: 30,
424
+ before: beforeTimestamp ? new Date(beforeTimestamp).toISOString() : undefined,
425
+ });
426
+
427
+ const newUiMessages = response.messages.map((msg) =>
428
+ mapApiMessageToUi(msg, initialServerId || msg.metadata?.serverId)
429
+ );
430
+
431
+ setMessages((prev) => {
432
+ const combined = beforeTimestamp ? [...newUiMessages, ...prev] : newUiMessages;
433
+ const uniqueMessages = Array.from(
434
+ new Map(combined.map((item) => [item.id, item])).values()
435
+ );
436
+ return uniqueMessages.sort((a, b) => a.createdAt - b.createdAt);
437
+ });
438
+
439
+ if (newUiMessages.length > 0) {
440
+ const oldestFetched = Math.min(...newUiMessages.map((m) => m.createdAt));
441
+ if (!beforeTimestamp || oldestFetched < (oldestMessageTimestamp || Infinity)) {
442
+ setOldestMessageTimestamp(oldestFetched);
443
+ }
444
+ }
445
+ setHasMoreMessages(newUiMessages.length >= 30);
446
+ } catch (err) {
447
+ setInternalIsError(true);
448
+ setInternalError(err);
449
+ clientLogger.error(`Failed to fetch messages for channel ${channelId}:`, err);
450
+ } finally {
451
+ setInternalIsLoading(false);
452
+ setIsFetchingMore(false);
453
+ }
454
+ // eslint-disable-next-line react-hooks/exhaustive-deps
455
+ },
456
+ [channelId, transformServerMessageToUiMessage, initialServerId]
457
+ ); // Add initialServerId to deps
458
+
459
+ useEffect(() => {
460
+ // Initial fetch when channelId changes or becomes available
461
+ if (channelId) {
462
+ clientLogger.info(
463
+ `[useChannelMessages] ChannelId changed or became available: ${channelId}. Clearing messages and fetching initial set.`
464
+ );
465
+ setMessages([]); // Clear previous messages
466
+ setOldestMessageTimestamp(null);
467
+ setHasMoreMessages(true);
468
+ fetchMessages(); // This will set internalIsLoading to true
469
+ } else {
470
+ clientLogger.info('[useChannelMessages] ChannelId is undefined. Clearing messages.');
471
+ setMessages([]);
472
+ setOldestMessageTimestamp(null);
473
+ setHasMoreMessages(true);
474
+ setInternalIsLoading(false); // No channel, so not loading anything
475
+ }
476
+ // eslint-disable-next-line react-hooks/exhaustive-deps
477
+ }, [channelId, fetchMessages]); // fetchMessages is memoized with useCallback
478
+
479
+ const fetchNextPage = async () => {
480
+ if (hasMoreMessages && !isFetchingMore && oldestMessageTimestamp) {
481
+ await fetchMessages(oldestMessageTimestamp - 1); // -1 to avoid fetching the same last message
482
+ }
483
+ };
484
+
485
+ // Add method to manually add/update messages from external sources (e.g., WebSocket)
486
+ const addMessage = useCallback((newMessage: UiMessage) => {
487
+ setMessages((prev) => {
488
+ // Check if message already exists
489
+ const existingIndex = prev.findIndex((m) => m.id === newMessage.id);
490
+
491
+ if (existingIndex >= 0) {
492
+ // Update existing message
493
+ const updated = [...prev];
494
+ updated[existingIndex] = newMessage;
495
+ return updated.sort((a, b) => a.createdAt - b.createdAt);
496
+ } else {
497
+ // Add new message
498
+ return [...prev, newMessage].sort((a, b) => a.createdAt - b.createdAt);
499
+ }
500
+ });
501
+ }, []);
502
+
503
+ // Add method to update a message by ID
504
+ const updateMessage = useCallback((messageId: UUID, updates: Partial<UiMessage>) => {
505
+ setMessages((prev) => {
506
+ return prev.map((m) => {
507
+ if (m.id === messageId) {
508
+ return { ...m, ...updates };
509
+ }
510
+ return m;
511
+ });
512
+ });
513
+ }, []);
514
+
515
+ // Add method to remove a message by ID
516
+ const removeMessage = useCallback((messageId: UUID) => {
517
+ setMessages((prev) => prev.filter((m) => m.id !== messageId));
518
+ }, []);
519
+
520
+ // Add method to clear all messages
521
+ const clearMessages = useCallback(() => {
522
+ setMessages([]);
523
+ setOldestMessageTimestamp(null);
524
+ setHasMoreMessages(true);
525
+ }, []);
526
+
527
+ // This hook now manages its own state for messages
528
+ // To integrate with React Query for caching of initial load or background updates:
529
+ // One could use useInfiniteQuery, but given the manual state management already here for append/prepend,
530
+ // this simpler useState + manual fetch approach is retained from the original structure of useMessages.
531
+ // For full React Query benefits, `useInfiniteQuery` would be the way to go.
532
+
533
+ return {
534
+ data: messages,
535
+ isLoading: internalIsLoading && messages.length === 0, // True only on initial load
536
+ isError: internalIsError,
537
+ error: internalError,
538
+ fetchNextPage,
539
+ hasNextPage: hasMoreMessages,
540
+ isFetchingNextPage: isFetchingMore,
541
+ addMessage,
542
+ updateMessage,
543
+ removeMessage,
544
+ clearMessages,
545
+ };
546
+ }
547
+
548
+ export function useGroupChannelMessages(channelId: UUID | null, initialServerId?: UUID) {
549
+ // This hook now becomes an alias or a slightly specialized version of useChannelMessages
550
+ // if group-specific logic (like different source filtering) isn't handled here.
551
+ // For now, it can directly use useChannelMessages.
552
+ return useChannelMessages(channelId ?? undefined, initialServerId);
553
+ }
554
+
555
+ // Hook for fetching agent actions
556
+ /**
557
+ * Custom hook to fetch agent actions for a specific agent and room.
558
+ * @param {UUID} agentId - The ID of the agent.
559
+ * @param {UUID} roomId - The ID of the room.
560
+ * @param {string[]} excludeTypes - Optional array of types to exclude from results.
561
+ * @returns {QueryResult} The result of the query containing agent actions.
562
+ */
563
+ export function useAgentActions(agentId: UUID, roomId?: UUID, excludeTypes?: string[]) {
564
+ return useQuery({
565
+ queryKey: ['agentActions', agentId, roomId, excludeTypes],
566
+ queryFn: async () => {
567
+ const response = await elizaClient.agents.getAgentLogs(agentId, {
568
+ limit: 50,
569
+ });
570
+ // Map the API logs to client format
571
+ return response ? response.map(mapApiLogToClient) : [];
572
+ },
573
+ refetchInterval: 1000,
574
+ staleTime: 1000,
575
+ });
576
+ }
577
+
578
+ /**
579
+ * Hook to delete an agent log/action.
580
+ * @returns {UseMutationResult} - Object containing the mutation function and its handlers.
581
+ */
582
+ export function useDeleteLog() {
583
+ const queryClient = useQueryClient();
584
+ const { toast } = useToast();
585
+
586
+ return useMutation({
587
+ mutationFn: async ({ agentId, logId }: { agentId: UUID; logId: UUID }) => {
588
+ await elizaClient.agents.deleteAgentLog(agentId, logId);
589
+ return { agentId, logId };
590
+ },
591
+
592
+ onMutate: async ({ agentId, logId }) => {
593
+ // Optimistically update the UI by removing the log from the cache
594
+ const previousLogs = queryClient.getQueryData(['agentActions', agentId]);
595
+
596
+ // Update cache if we have the data
597
+ if (previousLogs) {
598
+ queryClient.setQueryData(['agentActions', agentId], (oldData: any) =>
599
+ oldData.filter((log: any) => log.id !== logId)
600
+ );
601
+ }
602
+
603
+ return { previousLogs, agentId, logId };
604
+ },
605
+
606
+ onSuccess: (_, { agentId }) => {
607
+ // Invalidate relevant queries to refetch the latest data
608
+ queryClient.invalidateQueries({ queryKey: ['agentActions', agentId] });
609
+
610
+ toast({
611
+ title: 'Log Deleted',
612
+ description: 'The log entry has been successfully removed',
613
+ });
614
+ },
615
+
616
+ onError: (error, { agentId }, context) => {
617
+ // Revert the optimistic update on error
618
+ if (context?.previousLogs) {
619
+ queryClient.setQueryData(['agentActions', agentId], context.previousLogs);
620
+ }
621
+
622
+ toast({
623
+ title: 'Error',
624
+ description: error instanceof Error ? error.message : 'Failed to delete log',
625
+ variant: 'destructive',
626
+ });
627
+
628
+ // Force invalidate on error to ensure data is fresh
629
+ queryClient.invalidateQueries({ queryKey: ['agentActions', agentId] });
630
+ },
631
+ });
632
+ }
633
+
634
+ /**
635
+ * Fetches memories for a specific agent, optionally filtered by channel
636
+ */
637
+ export function useAgentMemories(
638
+ agentId: UUID,
639
+ tableName?: string,
640
+ channelId?: UUID, // Changed from roomId to channelId
641
+ includeEmbedding = false
642
+ ) {
643
+ const queryKey = channelId
644
+ ? ['agents', agentId, 'channels', channelId, 'memories', tableName, includeEmbedding] // Updated query key
645
+ : ['agents', agentId, 'memories', tableName, includeEmbedding];
646
+
647
+ return useQuery({
648
+ queryKey,
649
+ queryFn: async () => {
650
+ const params: any = {
651
+ tableName,
652
+ includeEmbedding,
653
+ };
654
+ const result = channelId
655
+ ? await elizaClient.memory.getRoomMemories(agentId, channelId, params)
656
+ : await elizaClient.memory.getAgentMemories(agentId, params);
657
+ console.log('Agent memories result:', {
658
+ agentId,
659
+ tableName,
660
+ includeEmbedding,
661
+ channelId,
662
+ result,
663
+ dataLength: result.memories?.length,
664
+ firstMemory: result.memories?.[0],
665
+ hasEmbeddings: (result.memories || []).some((m: any) => m.embedding?.length > 0),
666
+ });
667
+ // Map the API memories to client format
668
+ const memories = result.memories || [];
669
+ return memories.map(mapApiMemoryToClient);
670
+ },
671
+ enabled: Boolean(agentId && tableName),
672
+ staleTime: 1000,
673
+ refetchInterval: 10 * 1000,
674
+ });
675
+ }
676
+
677
+ /**
678
+ * Provides a mutation hook to delete a specific memory entry for an agent.
679
+ *
680
+ * On success, invalidates related agent and room memory queries to ensure data consistency.
681
+ */
682
+ export function useDeleteMemory() {
683
+ const queryClient = useQueryClient();
684
+
685
+ return useMutation({
686
+ mutationFn: async ({ agentId, memoryId }: { agentId: UUID; memoryId: UUID }) => {
687
+ await elizaClient.memory.deleteMemory(agentId, memoryId);
688
+ return { agentId, memoryId };
689
+ },
690
+ onSuccess: (data) => {
691
+ // Invalidate relevant queries to trigger refetch
692
+ queryClient.invalidateQueries({
693
+ queryKey: ['agents', data.agentId, 'memories'],
694
+ });
695
+
696
+ // Also invalidate room-specific memories
697
+ queryClient.invalidateQueries({
698
+ queryKey: ['agents', data.agentId, 'rooms'],
699
+ predicate: (query) => query.queryKey.length > 3 && query.queryKey[4] === 'memories',
700
+ });
701
+ },
702
+ });
703
+ }
704
+
705
+ /**
706
+ * Hook for deleting all memories associated with a specific agent in a given room.
707
+ *
708
+ * @returns A mutation object for triggering the deletion and tracking its state.
709
+ */
710
+ export function useDeleteAllMemories() {
711
+ const queryClient = useQueryClient();
712
+
713
+ return useMutation({
714
+ mutationFn: async ({ agentId, roomId }: { agentId: UUID; roomId: UUID }) => {
715
+ await elizaClient.memory.clearRoomMemories(agentId, roomId);
716
+ return { agentId };
717
+ },
718
+ onSuccess: (data) => {
719
+ // Invalidate relevant queries to trigger refetch
720
+ queryClient.invalidateQueries({
721
+ queryKey: ['agents', data.agentId, 'memories'],
722
+ });
723
+ },
724
+ });
725
+ }
726
+
727
+ /**
728
+ * Updates a specific memory entry for an agent.
729
+ *
730
+ * Triggers cache invalidation for related agent and room memories, as well as messages, to ensure data consistency. Displays a toast notification on success or error.
731
+ *
732
+ * @returns A mutation object for updating an agent's memory entry.
733
+ */
734
+ export function useUpdateMemory() {
735
+ const queryClient = useQueryClient();
736
+ const { toast } = useToast();
737
+
738
+ return useMutation({
739
+ mutationFn: async ({
740
+ agentId,
741
+ memoryId,
742
+ memoryData,
743
+ }: {
744
+ agentId: UUID;
745
+ memoryId: UUID;
746
+ memoryData: Partial<Memory>;
747
+ }) => {
748
+ const result = await elizaClient.memory.updateMemory(agentId, memoryId, memoryData);
749
+ return { agentId, memoryId, result };
750
+ },
751
+
752
+ onSuccess: (data) => {
753
+ // Invalidate relevant queries to trigger refetch
754
+ queryClient.invalidateQueries({
755
+ queryKey: ['agents', data.agentId, 'memories'],
756
+ });
757
+
758
+ // Also invalidate room-specific memories if we have roomId in the memory data
759
+ if (data.result?.roomId) {
760
+ queryClient.invalidateQueries({
761
+ queryKey: ['agents', data.agentId, 'rooms', data.result.roomId, 'memories'],
762
+ });
763
+ } else {
764
+ // Otherwise invalidate all room memories for this agent
765
+ queryClient.invalidateQueries({
766
+ queryKey: ['agents', data.agentId, 'rooms'],
767
+ predicate: (query) => query.queryKey.length > 3 && query.queryKey[4] === 'memories',
768
+ });
769
+ }
770
+
771
+ // Also invalidate regular messages queries
772
+ if (data.result?.roomId) {
773
+ queryClient.invalidateQueries({
774
+ queryKey: ['messages', data.agentId, data.result.roomId],
775
+ });
776
+ }
777
+
778
+ toast({
779
+ title: 'Memory Updated',
780
+ description: 'The memory has been successfully updated',
781
+ });
782
+ },
783
+
784
+ onError: (error) => {
785
+ toast({
786
+ title: 'Error',
787
+ description: error instanceof Error ? error.message : 'Failed to update memory',
788
+ variant: 'destructive',
789
+ });
790
+ },
791
+ });
792
+ }
793
+
794
+ export function useDeleteGroupMemory() {
795
+ const queryClient = useQueryClient();
796
+
797
+ return useMutation({
798
+ mutationFn: async ({ serverId, memoryId }: { serverId: UUID; memoryId: UUID }) => {
799
+ await elizaClient.messaging.deleteMessage(serverId, memoryId);
800
+ return { serverId };
801
+ },
802
+ onSuccess: ({ serverId }) => {
803
+ queryClient.invalidateQueries({ queryKey: ['groupmessages', serverId] });
804
+ },
805
+ });
806
+ }
807
+
808
+ export function useClearGroupChat() {
809
+ const queryClient = useQueryClient();
810
+
811
+ return useMutation({
812
+ mutationFn: async (serverId: UUID) => {
813
+ await elizaClient.messaging.clearChannelHistory(serverId);
814
+ return { serverId };
815
+ },
816
+ onSuccess: ({ serverId }) => {
817
+ queryClient.invalidateQueries({ queryKey: ['groupmessages', serverId] });
818
+ },
819
+ });
820
+ }
821
+
822
+ // REMOVED: useRooms - Client should use channels, not rooms
823
+ // Rooms are an agent-only abstraction
824
+
825
+ // Hook for fetching agent panels (public GET routes)
826
+ /**
827
+ * Custom hook to fetch public GET routes (panels) for a specific agent.
828
+ * @param {UUID | undefined | null} agentId - The ID of the agent.
829
+ * @param {object} options - Optional TanStack Query options.
830
+ * @returns {QueryResult} The result of the query containing agent panels.
831
+ */
832
+ export type AgentPanel = {
833
+ id: string;
834
+ name: string;
835
+ url: string;
836
+ type: string;
837
+ };
838
+
839
+ export function useAgentPanels(agentId: UUID | undefined | null, options = {}) {
840
+ const network = useNetworkStatus();
841
+
842
+ return useQuery<{
843
+ success: boolean;
844
+ data: AgentPanel[];
845
+ error?: { code: string; message: string; details?: string };
846
+ }>({
847
+ queryKey: ['agentPanels', agentId],
848
+ queryFn: async () => {
849
+ if (!agentId) throw new Error('Agent ID required');
850
+ const result = await elizaClient.agents.getAgentPanels(agentId);
851
+ return { success: true, data: result.panels };
852
+ },
853
+ enabled: Boolean(agentId),
854
+ staleTime: STALE_TIMES.STANDARD, // Panels are unlikely to change very frequently
855
+ refetchInterval: !network.isOffline && Boolean(agentId) ? STALE_TIMES.RARE : false,
856
+ refetchIntervalInBackground: false,
857
+ ...(!network.isOffline &&
858
+ network.effectiveType === 'slow-2g' && {
859
+ refetchInterval: STALE_TIMES.NEVER, // Or even disable for slow connections
860
+ }),
861
+ ...options,
862
+ });
863
+ }
864
+
865
+ /**
866
+ * Custom hook that combines useAgents with individual useAgent calls for detailed data
867
+ * @returns {AgentsWithDetailsResult} Combined query results with both list and detailed data
868
+ */
869
+ interface AgentsWithDetailsResult {
870
+ data: {
871
+ agents: Agent[];
872
+ };
873
+ isLoading: boolean;
874
+ isError: boolean;
875
+ error: unknown;
876
+ }
877
+
878
+ /**
879
+ * Fetches a list of agents with detailed information for each agent in parallel.
880
+ *
881
+ * Combines the agent list from {@link useAgents} with individual agent detail queries using `useQueries`, aggregating loading and error states. Polling intervals adapt to network conditions.
882
+ *
883
+ * @returns An object containing detailed agent data, loading and error states, and any encountered error.
884
+ */
885
+ export function useAgentsWithDetails(): AgentsWithDetailsResult {
886
+ const network = useNetworkStatus();
887
+ const { data: agentsData, isLoading: isAgentsLoading } = useAgents();
888
+ const agentIds = agentsData?.data?.agents?.map((agent) => agent.id as UUID) || [];
889
+
890
+ // Use useQueries for parallel fetching
891
+ const agentQueries = useQueries<UseQueryResult<{ data: Agent }, Error>[]>({
892
+ queries: agentIds.map((id) => ({
893
+ queryKey: ['agent', id] as const,
894
+ queryFn: async () => {
895
+ const result = await elizaClient.agents.getAgent(id);
896
+ return { data: result };
897
+ },
898
+ staleTime: STALE_TIMES.FREQUENT,
899
+ enabled: Boolean(id),
900
+ refetchInterval: !network.isOffline && Boolean(id) ? STALE_TIMES.FREQUENT : false,
901
+ refetchIntervalInBackground: false,
902
+ ...(!network.isOffline &&
903
+ network.effectiveType === 'slow-2g' && {
904
+ refetchInterval: STALE_TIMES.STANDARD,
905
+ }),
906
+ })),
907
+ });
908
+
909
+ // Safely check loading and error states
910
+ const isLoading = isAgentsLoading || agentQueries.some((query) => query.isLoading);
911
+ const isError = agentQueries.some((query) => query.isError);
912
+ const error = agentQueries.find((query) => query.error)?.error;
913
+
914
+ // Safely collect agent details
915
+ const detailedAgents = agentQueries
916
+ .filter((query): query is UseQueryResult<{ data: Agent }, Error> & { data: { data: Agent } } =>
917
+ Boolean(query.data?.data)
918
+ )
919
+ .map((query) => query.data.data);
920
+
921
+ return {
922
+ data: {
923
+ agents: detailedAgents,
924
+ },
925
+ isLoading,
926
+ isError,
927
+ error,
928
+ };
929
+ }
930
+
931
+ // --- Hooks for Admin/Debug (Agent-Perspective Data) ---
932
+ export function useAgentInternalActions(
933
+ agentId: UUID | null,
934
+ agentPerspectiveRoomId?: UUID | null
935
+ ) {
936
+ return useQuery<AgentLog[], Error>({
937
+ queryKey: ['agentInternalActions', agentId, agentPerspectiveRoomId],
938
+ queryFn: async () => {
939
+ if (!agentId) return []; // Or throw error, depending on desired behavior for null agentId
940
+ const response = await elizaClient.agents.getAgentLogs(agentId, {
941
+ limit: 50,
942
+ });
943
+ // Map the API logs to client format
944
+ return response ? response.map(mapApiLogToClient) : [];
945
+ },
946
+ enabled: !!agentId, // Only enable if agentId is present
947
+ staleTime: STALE_TIMES.FREQUENT,
948
+ refetchInterval: 5000,
949
+ });
950
+ }
951
+
952
+ export function useDeleteAgentInternalLog() {
953
+ const queryClient = useQueryClient();
954
+ const { toast } = useToast();
955
+ return useMutation<void, Error, { agentId: UUID; logId: UUID }>({
956
+ mutationFn: async ({ agentId, logId }: { agentId: UUID; logId: UUID }) => {
957
+ await elizaClient.agents.deleteAgentLog(agentId, logId);
958
+ },
959
+ onSuccess: (_, { agentId }) => {
960
+ queryClient.invalidateQueries({ queryKey: ['agentInternalActions', agentId] });
961
+ queryClient.invalidateQueries({
962
+ queryKey: ['agentInternalActions', agentId, undefined],
963
+ exact: false,
964
+ });
965
+ toast({ title: 'Log Deleted', description: 'The agent log entry has been removed' });
966
+ },
967
+ onError: (error) => {
968
+ toast({
969
+ title: 'Error Deleting Log',
970
+ description: error instanceof Error ? error.message : 'Failed to delete agent log',
971
+ variant: 'destructive',
972
+ });
973
+ },
974
+ });
975
+ }
976
+
977
+ export function useAgentInternalMemories(
978
+ agentId: UUID | null,
979
+ agentPerspectiveRoomId: UUID | null,
980
+ tableName: string = 'messages',
981
+ includeEmbedding = false
982
+ ) {
983
+ return useQuery<CoreMemory[], Error>({
984
+ queryKey: [
985
+ 'agentInternalMemories',
986
+ agentId,
987
+ agentPerspectiveRoomId,
988
+ tableName,
989
+ includeEmbedding,
990
+ ],
991
+ queryFn: async () => {
992
+ if (!agentId || !agentPerspectiveRoomId) return Promise.resolve([]);
993
+ const response = await elizaClient.memory.getAgentInternalMemories(
994
+ agentId,
995
+ agentPerspectiveRoomId,
996
+ includeEmbedding
997
+ );
998
+ return response.data || [];
999
+ },
1000
+ enabled: !!agentId && !!agentPerspectiveRoomId,
1001
+ staleTime: STALE_TIMES.STANDARD,
1002
+ });
1003
+ }
1004
+
1005
+ export function useDeleteAgentInternalMemory() {
1006
+ const queryClient = useQueryClient();
1007
+ const { toast } = useToast();
1008
+ return useMutation<{ agentId: UUID; memoryId: UUID }, Error, { agentId: UUID; memoryId: UUID }>({
1009
+ mutationFn: async ({ agentId, memoryId }: { agentId: UUID; memoryId: UUID }) => {
1010
+ await elizaClient.memory.deleteAgentInternalMemory(agentId, memoryId);
1011
+ return { agentId, memoryId };
1012
+ },
1013
+ onSuccess: (_data, variables) => {
1014
+ toast({
1015
+ title: 'Memory Deleted',
1016
+ description: `Agent memory ${variables.memoryId} removed.`,
1017
+ });
1018
+ queryClient.invalidateQueries({ queryKey: ['agentInternalMemories', variables.agentId] });
1019
+ // More specific invalidation if needed:
1020
+ // queryClient.invalidateQueries({ queryKey: ['agentInternalMemories', variables.agentId, variables.memoryData?.roomId] });
1021
+ },
1022
+ onError: (error) => {
1023
+ toast({
1024
+ title: 'Error Deleting Memory',
1025
+ description: error instanceof Error ? error.message : 'Failed to delete agent memory',
1026
+ variant: 'destructive',
1027
+ });
1028
+ },
1029
+ });
1030
+ }
1031
+
1032
+ export function useDeleteAllAgentInternalMemories() {
1033
+ const queryClient = useQueryClient();
1034
+ const { toast } = useToast();
1035
+ return useMutation<
1036
+ { agentId: UUID; agentPerspectiveRoomId: UUID },
1037
+ Error,
1038
+ { agentId: UUID; agentPerspectiveRoomId: UUID }
1039
+ >({
1040
+ mutationFn: async ({ agentId, agentPerspectiveRoomId }) => {
1041
+ await elizaClient.memory.deleteAllAgentInternalMemories(agentId, agentPerspectiveRoomId);
1042
+ return { agentId, agentPerspectiveRoomId };
1043
+ },
1044
+ onSuccess: (_data, variables) => {
1045
+ toast({
1046
+ title: 'All Memories Deleted',
1047
+ description: `All memories for agent in room perspective ${variables.agentPerspectiveRoomId} cleared.`,
1048
+ });
1049
+ queryClient.invalidateQueries({
1050
+ queryKey: ['agentInternalMemories', variables.agentId, variables.agentPerspectiveRoomId],
1051
+ });
1052
+ },
1053
+ onError: (error) => {
1054
+ toast({
1055
+ title: 'Error Clearing Memories',
1056
+ description: error instanceof Error ? error.message : 'Failed to clear agent memories',
1057
+ variant: 'destructive',
1058
+ });
1059
+ },
1060
+ });
1061
+ }
1062
+
1063
+ export function useUpdateAgentInternalMemory() {
1064
+ const queryClient = useQueryClient();
1065
+ const { toast } = useToast();
1066
+ return useMutation<
1067
+ {
1068
+ agentId: UUID;
1069
+ memoryId: string;
1070
+ response: { success: boolean; data: { id: UUID; message: string } };
1071
+ },
1072
+ Error,
1073
+ { agentId: UUID; memoryId: UUID; memoryData: Partial<CoreMemory> }
1074
+ >({
1075
+ mutationFn: async ({ agentId, memoryId, memoryData }) => {
1076
+ const response = await elizaClient.memory.updateAgentInternalMemory(
1077
+ agentId,
1078
+ memoryId,
1079
+ memoryData
1080
+ );
1081
+ return { agentId, memoryId, response };
1082
+ },
1083
+ onSuccess: (_data, variables) => {
1084
+ toast({
1085
+ title: 'Memory Updated',
1086
+ description: `Agent memory ${variables.memoryId} updated.`,
1087
+ });
1088
+ queryClient.invalidateQueries({ queryKey: ['agentInternalMemories', variables.agentId] });
1089
+ },
1090
+ onError: (error) => {
1091
+ toast({
1092
+ title: 'Error Updating Memory',
1093
+ description: error instanceof Error ? error.message : 'Failed to update agent memory',
1094
+ variant: 'destructive',
1095
+ });
1096
+ },
1097
+ });
1098
+ }
1099
+
1100
+ // --- Hooks for Servers and Channels (GUI Navigation) ---
1101
+ export function useServers(options = {}) {
1102
+ const network = useNetworkStatus();
1103
+ return useQuery<{ data: { servers: ClientMessageServer[] } }>({
1104
+ queryKey: ['servers'],
1105
+ queryFn: async () => {
1106
+ const result = await elizaClient.messaging.listServers();
1107
+ return { data: { servers: mapApiServersToClient(result.servers) } };
1108
+ },
1109
+ staleTime: STALE_TIMES.RARE,
1110
+ refetchInterval: !network.isOffline ? STALE_TIMES.RARE : false,
1111
+ ...options,
1112
+ });
1113
+ }
1114
+
1115
+ export function useChannels(serverId: UUID | undefined, options = {}) {
1116
+ const network = useNetworkStatus();
1117
+ return useQuery<{ data: { channels: ClientMessageChannel[] } }>({
1118
+ queryKey: ['channels', serverId],
1119
+ queryFn: async () => {
1120
+ if (!serverId) return Promise.resolve({ data: { channels: [] } });
1121
+ const result = await elizaClient.messaging.getServerChannels(serverId);
1122
+ return { data: { channels: mapApiChannelsToClient(result.channels) } };
1123
+ },
1124
+ enabled: !!serverId,
1125
+ staleTime: STALE_TIMES.STANDARD,
1126
+ refetchInterval: !network.isOffline && !!serverId ? STALE_TIMES.STANDARD : false,
1127
+ ...options,
1128
+ });
1129
+ }
1130
+
1131
+ export function useChannelDetails(channelId: UUID | undefined, options = {}) {
1132
+ // Allow undefined
1133
+ const network = useNetworkStatus();
1134
+ return useQuery<{ success: boolean; data: ClientMessageChannel | null }>({
1135
+ queryKey: ['channelDetails', channelId],
1136
+ queryFn: async () => {
1137
+ if (!channelId) return Promise.resolve({ success: true, data: null });
1138
+ const result = await elizaClient.messaging.getChannelDetails(channelId);
1139
+ return { success: true, data: mapApiChannelToClient(result) };
1140
+ },
1141
+ enabled: !!channelId,
1142
+ staleTime: STALE_TIMES.STANDARD,
1143
+ refetchInterval: !network.isOffline && !!channelId ? STALE_TIMES.RARE : false,
1144
+ ...options,
1145
+ });
1146
+ }
1147
+
1148
+ export function useChannelParticipants(channelId: UUID | undefined, options = {}) {
1149
+ // Allow undefined
1150
+ const network = useNetworkStatus();
1151
+ return useQuery<{ success: boolean; data: UUID[] }>({
1152
+ queryKey: ['channelParticipants', channelId],
1153
+ queryFn: async () => {
1154
+ if (!channelId) return Promise.resolve({ success: true, data: [] });
1155
+ try {
1156
+ const result = await elizaClient.messaging.getChannelParticipants(channelId);
1157
+
1158
+ // Handle different possible response formats
1159
+ let participants = [];
1160
+ if (result && Array.isArray(result.participants)) {
1161
+ participants = result.participants.map((participant) => participant.userId);
1162
+ } else if (result && Array.isArray(result)) {
1163
+ // If result is directly an array
1164
+ participants = result.map(
1165
+ (participant) => participant.userId || participant.id || participant
1166
+ );
1167
+ }
1168
+ return { success: true, data: participants };
1169
+ } catch (error) {
1170
+ console.error('[useChannelParticipants] Error:', error);
1171
+ return { success: false, data: [] };
1172
+ }
1173
+ },
1174
+ enabled: !!channelId,
1175
+ staleTime: STALE_TIMES.STANDARD,
1176
+ refetchInterval: !network.isOffline && !!channelId ? STALE_TIMES.FREQUENT : false,
1177
+ ...options,
1178
+ });
1179
+ }
1180
+
1181
+ export function useDeleteChannelMessage() {
1182
+ const queryClient = useQueryClient();
1183
+ const { toast } = useToast();
1184
+ return useMutation<
1185
+ { channelId: UUID; messageId: UUID },
1186
+ Error,
1187
+ { channelId: UUID; messageId: UUID }
1188
+ >({
1189
+ mutationFn: async ({ channelId, messageId }) => {
1190
+ await elizaClient.messaging.deleteMessage(channelId, messageId);
1191
+ return { channelId, messageId };
1192
+ },
1193
+ onSuccess: (_data, variables) => {
1194
+ toast({
1195
+ title: 'Message Deleted',
1196
+ description: 'Message removed successfully.',
1197
+ });
1198
+ },
1199
+ onError: (error) => {
1200
+ toast({
1201
+ title: 'Error Deleting Message',
1202
+ description: error instanceof Error ? error.message : 'Failed to delete message',
1203
+ variant: 'destructive',
1204
+ });
1205
+ },
1206
+ });
1207
+ }
1208
+
1209
+ export function useClearChannelMessages() {
1210
+ const queryClient = useQueryClient();
1211
+ const { toast } = useToast();
1212
+ return useMutation<{ channelId: UUID }, Error, UUID>({
1213
+ mutationFn: async (channelId: UUID) => {
1214
+ await elizaClient.messaging.clearChannelHistory(channelId);
1215
+ return { channelId };
1216
+ },
1217
+ onSuccess: (_data, variables_channelId) => {
1218
+ toast({
1219
+ title: 'Channel Cleared',
1220
+ description: `All messages in channel ${variables_channelId} cleared.`,
1221
+ });
1222
+ queryClient.invalidateQueries({ queryKey: ['messages', variables_channelId] });
1223
+ queryClient.setQueryData(['messages', variables_channelId], () => []);
1224
+ },
1225
+ onError: (error) => {
1226
+ toast({
1227
+ title: 'Error Clearing Channel',
1228
+ description: error instanceof Error ? error.message : 'Failed to clear messages',
1229
+ variant: 'destructive',
1230
+ });
1231
+ },
1232
+ });
1233
+ }
1234
+
1235
+ export function useDeleteChannel() {
1236
+ const queryClient = useQueryClient();
1237
+ const { toast } = useToast();
1238
+ const navigate = useNavigate();
1239
+
1240
+ return useMutation<void, Error, { channelId: UUID; serverId: UUID }>({
1241
+ mutationFn: async ({ channelId }) => {
1242
+ await elizaClient.messaging.deleteChannel(channelId);
1243
+ },
1244
+ onSuccess: (_data, variables) => {
1245
+ toast({
1246
+ title: 'Group Deleted',
1247
+ description: 'The group has been successfully deleted.',
1248
+ });
1249
+ // Invalidate channel queries
1250
+ queryClient.invalidateQueries({ queryKey: ['channels', variables.serverId] });
1251
+ queryClient.invalidateQueries({ queryKey: ['channels'] });
1252
+ // Navigate back to home
1253
+ navigate('/');
1254
+ },
1255
+ onError: (error) => {
1256
+ toast({
1257
+ title: 'Error Deleting Group',
1258
+ description: error instanceof Error ? error.message : 'Failed to delete group',
1259
+ variant: 'destructive',
1260
+ });
1261
+ },
1262
+ });
1263
+ }