@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,128 @@
1
+ import ChatComponent from '@/components/chat';
2
+ import { Button } from '@/components/ui/button';
3
+ import { useAgentManagement } from '@/hooks/use-agent-management';
4
+ import { useAgent } from '@/hooks/use-query-hooks';
5
+ import clientLogger from '@/lib/logger';
6
+ import {
7
+ type Agent,
8
+ ChannelType,
9
+ AgentStatus as CoreAgentStatusEnum,
10
+ type UUID,
11
+ } from '@elizaos/core';
12
+ import { Loader2, Play, Settings } from 'lucide-react';
13
+ import { useEffect } from 'react';
14
+ import { useParams, useNavigate } from 'react-router';
15
+ import type { AgentWithStatus } from '../types';
16
+
17
+ /**
18
+ * Displays the agent chat interface with an optional details sidebar in a resizable layout.
19
+ *
20
+ * Renders the chat panel for a specific agent, and conditionally shows a sidebar with agent details based on user interaction. If no agent ID is present in the URL, displays a "No data." message.
21
+ */
22
+ export default function AgentRoute() {
23
+ // useParams will include agentId and optionally channelId for /chat/:agentId/:channelId routes
24
+ const { agentId, channelId } = useParams<{ agentId: UUID; channelId?: UUID }>();
25
+ const navigate = useNavigate();
26
+
27
+ useEffect(() => {
28
+ clientLogger.info('[AgentRoute] Component mounted/updated', { agentId, channelId });
29
+ return () => {
30
+ clientLogger.info('[AgentRoute] Component unmounted', { agentId, channelId });
31
+ };
32
+ }, [agentId, channelId]);
33
+
34
+ const { data: agentDataResponse, isLoading: isLoadingAgent } = useAgent(agentId);
35
+ const { startAgent, isAgentStarting } = useAgentManagement();
36
+
37
+ const agentFromHook: Agent | undefined = agentDataResponse?.data
38
+ ? ({
39
+ ...(agentDataResponse.data as AgentWithStatus),
40
+ status:
41
+ agentDataResponse.data.status === 'active'
42
+ ? CoreAgentStatusEnum.ACTIVE
43
+ : agentDataResponse.data.status === 'inactive'
44
+ ? CoreAgentStatusEnum.INACTIVE
45
+ : CoreAgentStatusEnum.INACTIVE,
46
+ username: agentDataResponse.data.username || agentDataResponse.data.name || 'Unknown',
47
+ bio: agentDataResponse.data.bio || '',
48
+ messageExamples: agentDataResponse.data.messageExamples || [],
49
+ postExamples: agentDataResponse.data.postExamples || [],
50
+ topics: agentDataResponse.data.topics || [],
51
+ adjectives: agentDataResponse.data.adjectives || [],
52
+ knowledge: agentDataResponse.data.knowledge || [],
53
+ plugins: agentDataResponse.data.plugins || [],
54
+ settings: agentDataResponse.data.settings || {},
55
+ secrets: (agentDataResponse.data as any).secrets || {},
56
+ style: agentDataResponse.data.style || {},
57
+ templates: agentDataResponse.data.templates || {},
58
+ enabled:
59
+ typeof agentDataResponse.data.enabled === 'boolean'
60
+ ? agentDataResponse.data.enabled
61
+ : true,
62
+ createdAt:
63
+ typeof agentDataResponse.data.createdAt === 'number'
64
+ ? agentDataResponse.data.createdAt
65
+ : Date.now(),
66
+ updatedAt:
67
+ typeof agentDataResponse.data.updatedAt === 'number'
68
+ ? agentDataResponse.data.updatedAt
69
+ : Date.now(),
70
+ } as Agent)
71
+ : undefined;
72
+
73
+ if (!agentId) return <div className="p-4">Agent ID not provided.</div>;
74
+ if (isLoadingAgent || !agentFromHook)
75
+ return (
76
+ <div className="p-4 flex items-center justify-center h-full">
77
+ <Loader2 className="h-8 w-8 animate-spin text-primary" />
78
+ </div>
79
+ );
80
+
81
+ const isActive = agentFromHook.status === CoreAgentStatusEnum.ACTIVE;
82
+ const isStarting = isAgentStarting(agentFromHook.id);
83
+
84
+ const handleStartAgent = () => {
85
+ if (agentFromHook) {
86
+ startAgent(agentFromHook);
87
+ }
88
+ };
89
+
90
+ if (!isActive) {
91
+ clientLogger.info('[AgentRoute] Agent is not active, rendering inactive state UI', {
92
+ agentName: agentFromHook?.name,
93
+ });
94
+ return (
95
+ <div className="flex flex-col items-center justify-center h-full w-full p-8 text-center">
96
+ <h2 className="text-2xl font-semibold mb-4">{agentFromHook.name} is not active.</h2>
97
+ <p className="text-muted-foreground mb-6">Press the button below to start this agent.</p>
98
+ <div className="flex gap-3">
99
+ <Button onClick={() => navigate(`/settings/${agentId}`)} variant="outline" size="lg">
100
+ <Settings className="h-5 w-5" />
101
+ </Button>
102
+ <Button onClick={handleStartAgent} disabled={isStarting} size="lg">
103
+ {isStarting ? (
104
+ <Loader2 className="mr-2 h-5 w-5 animate-spin" />
105
+ ) : (
106
+ <Play className="mr-2 h-5 w-5" />
107
+ )}
108
+ {isStarting ? 'Starting Agent...' : 'Start Agent'}
109
+ </Button>
110
+ </div>
111
+ </div>
112
+ );
113
+ }
114
+
115
+ clientLogger.info('[AgentRoute] Agent is active, rendering chat for DM', {
116
+ agentName: agentFromHook?.name,
117
+ dmChannelIdFromRoute: channelId,
118
+ });
119
+
120
+ return (
121
+ <ChatComponent
122
+ key={`${agentId}-${channelId || 'no-dm-channel'}`}
123
+ chatType={ChannelType.DM}
124
+ contextId={agentId}
125
+ initialDmChannelId={channelId}
126
+ />
127
+ );
128
+ }
@@ -0,0 +1,13 @@
1
+ import AgentCreator from '@/components/agent-creator';
2
+
3
+ export default function AgentCreatorRoute() {
4
+ return (
5
+ <div className="h-full w-full overflow-y-auto">
6
+ <div className="min-h-full flex w-full justify-center px-4 sm:px-6 py-4">
7
+ <div className="w-full max-w-4xl min-w-0">
8
+ <AgentCreator />
9
+ </div>
10
+ </div>
11
+ </div>
12
+ );
13
+ }
@@ -0,0 +1,50 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ // import { CreateGroupDialog } from '@/components/create-group-dialog'; // To be removed
4
+ import GroupPanel from '@/components/group-panel'; // Import GroupPanel
5
+ // import { useAgentsWithDetails, useServers } from '@/hooks/use-query-hooks'; // No longer needed if GroupPanel fetches its own agents
6
+ import type { UUID } from '@elizaos/core';
7
+
8
+ export default function GroupNew() {
9
+ const navigate = useNavigate();
10
+ // const [open, setOpen] = useState(true); // GroupPanel typically manages its own visibility or is used as a page component
11
+ // const { data: serversData } = useServers();
12
+ // const { data: agentsData, isLoading: isLoadingAgents } = useAgentsWithDetails(); // GroupPanel fetches its own agents
13
+ // const [selectedServerId, setSelectedServerId] = useState<UUID | null>(null); // GroupPanel will use DEFAULT_SERVER_ID
14
+
15
+ // useEffect(() => {
16
+ // // Use the first available server or create one if needed
17
+ // if (serversData?.data?.servers && serversData.data.servers.length > 0) {
18
+ // setSelectedServerId(serversData.data.servers[0].id);
19
+ // }
20
+ // }, [serversData]);
21
+
22
+ // const handleOpenChange = (open: boolean) => {
23
+ // setOpen(open);
24
+ // if (!open) {
25
+ // // Navigate back to home when dialog is closed
26
+ // navigate('/');
27
+ // }
28
+ // };
29
+
30
+ // if (!selectedServerId) { // GroupPanel handles server ID internally or gets it via props if needed for specific server contexts
31
+ // return (
32
+ // <div className="flex items-center justify-center h-screen">
33
+ // <p>Loading servers...</p>
34
+ // </div>
35
+ // );
36
+ // }
37
+
38
+ return (
39
+ // <CreateGroupDialog open={open} onOpenChange={handleOpenChange} serverId={selectedServerId} />
40
+ // Render GroupPanel directly as the route's content
41
+ // GroupPanel will handle its own logic for fetching serverId (default) or if it were to be passed.
42
+ <div className="pt-4 md:pt-8">
43
+ <GroupPanel
44
+ // agents={agents} // Removed prop
45
+ onClose={() => navigate(-1)} // Navigate back on close
46
+ // channelId is undefined, so it's in "create" mode
47
+ />
48
+ </div>
49
+ );
50
+ }
@@ -0,0 +1,29 @@
1
+ import ChatComponent from '@/components/chat';
2
+ import { ChannelType, validateUuid, type UUID } from '@elizaos/core';
3
+ import { useParams, useSearchParams } from 'react-router-dom';
4
+
5
+ export default function GroupRoute() {
6
+ const { channelId: channelIdFromPath } = useParams<{ channelId: string }>();
7
+ const [searchParams] = useSearchParams();
8
+ const serverIdFromQuery = searchParams.get('serverId');
9
+
10
+ const channelId = validateUuid(channelIdFromPath);
11
+ const serverId = validateUuid(serverIdFromQuery || '');
12
+
13
+ if (!channelId || !serverId) {
14
+ return (
15
+ <div className="flex flex-1 justify-center items-center">
16
+ <p>Missing channel or server information.</p>
17
+ </div>
18
+ );
19
+ }
20
+
21
+ return (
22
+ <ChatComponent
23
+ key={channelId}
24
+ chatType={ChannelType.GROUP}
25
+ contextId={channelId as UUID}
26
+ serverId={serverId as UUID}
27
+ />
28
+ );
29
+ }
@@ -0,0 +1,218 @@
1
+ import AgentCard from '@/components/agent-card';
2
+ import GroupCard from '@/components/group-card';
3
+ import GroupPanel from '@/components/group-panel';
4
+ import ProfileOverlay from '@/components/profile-overlay';
5
+ import { useAgentsWithDetails, useChannels, useServers } from '@/hooks/use-query-hooks';
6
+ import clientLogger from '@/lib/logger';
7
+ import { type Agent, type UUID, ChannelType as CoreChannelType, AgentStatus } from '@elizaos/core';
8
+ import type { MessageChannel, MessageServer } from '@/types';
9
+ import { Plus } from 'lucide-react';
10
+ import React, { useEffect, useMemo, useState } from 'react';
11
+ import { useNavigate } from 'react-router-dom';
12
+ import { Button } from '../components/ui/button';
13
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '../components/ui/tabs';
14
+ import { Separator } from '@/components/ui/separator';
15
+
16
+ /**
17
+ * Renders the main dashboard for managing agents and groups, providing interactive controls for viewing, starting, messaging, and configuring agents, as well as creating and editing groups.
18
+ *
19
+ * Displays lists of agents and groups with status indicators, action buttons, and overlays for detailed views and settings. Handles loading and error states, and supports navigation to chat and settings pages.
20
+ */
21
+ export default function Home() {
22
+ const { data: agentsData, isLoading, isError, error } = useAgentsWithDetails();
23
+ const navigate = useNavigate();
24
+
25
+ // Extract agents properly from the response
26
+ const agents = useMemo(() => agentsData?.agents || [], [agentsData]);
27
+ const activeAgentsCount = agents.filter((a) => a.status === AgentStatus.ACTIVE).length;
28
+
29
+ const { data: serversData } = useServers() as {
30
+ data: { data: { servers: MessageServer[] } } | undefined;
31
+ };
32
+ const servers = serversData?.data?.servers || [];
33
+
34
+ const [isOverlayOpen, setOverlayOpen] = useState(false);
35
+ const [isGroupPanelOpen, setIsGroupPanelOpen] = useState(false);
36
+ const [selectedAgent, setSelectedAgent] = useState<Partial<Agent> | null>(null);
37
+ const [selectedGroupId, setSelectedGroupId] = useState<UUID | null>(null);
38
+ const [activeTab, setActiveTab] = useState('agents');
39
+
40
+ const closeOverlay = () => {
41
+ setSelectedAgent(null);
42
+ setOverlayOpen(false);
43
+ };
44
+
45
+ const handleNavigateToDm = async (agent: Partial<Agent>, forceNew: boolean) => {
46
+ if (!agent.id) return;
47
+ // Navigate directly to agent chat - DM channel will be created automatically with default server
48
+ navigate(`/chat/${agent.id}`, { state: { forceNew } });
49
+ };
50
+
51
+ const handleCreateGroup = () => {
52
+ navigate('/group/new');
53
+ };
54
+
55
+ useEffect(() => {
56
+ clientLogger.info('[Home] Component mounted/re-rendered. Key might have changed.');
57
+ // You might want to trigger data re-fetching here if it's not automatic
58
+ // e.g., queryClient.invalidateQueries(['agents']);
59
+ }, []); // Empty dependency array means this runs on mount and when key changes
60
+
61
+ return (
62
+ <>
63
+ <div className="flex-1 w-full overflow-y-auto bg-background">
64
+ <div className="flex flex-col w-full h-full">
65
+ <Tabs
66
+ value={activeTab}
67
+ onValueChange={setActiveTab}
68
+ className="w-full h-full flex flex-col"
69
+ >
70
+ <div className="w-full">
71
+ <div className="w-full md:max-w-4xl mx-auto px-6 pt-6 pb-2">
72
+ <div className="flex justify-between items-center mb-3">
73
+ <TabsList className="h-auto p-0 bg-transparent border-0 border-b-0 gap-2 w-auto">
74
+ <TabsTrigger
75
+ value="agents"
76
+ className="relative rounded-full data-[state=active]:border-b-0 data-[state=active]:bg-white data-[state=active]:text-black data-[state=active]:font-bold cursor-pointer text-lg py-1"
77
+ >
78
+ Agents
79
+ <span
80
+ className={`
81
+ absolute -top-2.5 right-0 inline-flex items-center justify-center h-5 w-5 rounded-full bg-blue-600 text-white text-[8px] font-semibold border border-black
82
+ transition-all duration-300 ease-in-out
83
+ ${activeTab === 'agents' && activeAgentsCount > 0 ? 'opacity-100 scale-100' : 'opacity-0 scale-75 pointer-events-none'}
84
+ `}
85
+ >
86
+ {activeAgentsCount}
87
+ </span>
88
+ </TabsTrigger>
89
+ <TabsTrigger
90
+ value="groups"
91
+ className="rounded-full data-[state=active]:border-b-0 data-[state=active]:bg-white data-[state=active]:text-black data-[state=active]:font-bold cursor-pointer text-lg py-1"
92
+ >
93
+ Groups
94
+ </TabsTrigger>
95
+ </TabsList>
96
+ <Button
97
+ variant="ghost"
98
+ onClick={() => {
99
+ if (activeTab === 'agents') {
100
+ navigate('/create');
101
+ } else {
102
+ handleCreateGroup();
103
+ }
104
+ }}
105
+ className="create-agent-button cursor-pointer gap-1"
106
+ >
107
+ <Plus className="w-4 h-4" />
108
+ {activeTab === 'agents' ? 'Create New Agent' : 'Create New Group'}
109
+ </Button>
110
+ </div>
111
+ <Separator />
112
+ </div>
113
+ </div>
114
+
115
+ <TabsContent value="agents" className="flex-1 mt-0 bg-background">
116
+ <div className="flex flex-col gap-6 w-full md:max-w-4xl mx-auto px-6 py-2">
117
+ {isLoading && <div className="text-center py-8">Loading agents...</div>}
118
+
119
+ {isError && (
120
+ <div className="text-center py-8">
121
+ Error loading agents: {error instanceof Error ? error.message : 'Unknown error'}
122
+ </div>
123
+ )}
124
+
125
+ {agents.length === 0 && !isLoading && (
126
+ <div className="text-center py-8 flex flex-col items-center gap-4">
127
+ <p className="text-muted-foreground">
128
+ No agents currently running. Start a character to begin.
129
+ </p>
130
+ </div>
131
+ )}
132
+
133
+ {!isLoading && !isError && (
134
+ <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-2 gap-3 agents-section">
135
+ {agents
136
+ .sort((a, b) => {
137
+ // Sort by status - ACTIVE agents first
138
+ const aActive = a.status === AgentStatus.ACTIVE ? 1 : 0;
139
+ const bActive = b.status === AgentStatus.ACTIVE ? 1 : 0;
140
+ return bActive - aActive;
141
+ })
142
+ .map((agent) => {
143
+ return (
144
+ <AgentCard
145
+ key={agent.id}
146
+ agent={agent}
147
+ onChat={(forceNew) => handleNavigateToDm(agent as Agent, forceNew)}
148
+ />
149
+ );
150
+ })}
151
+ </div>
152
+ )}
153
+ </div>
154
+ </TabsContent>
155
+
156
+ <TabsContent value="groups" className="flex-1 mt-0 bg-background">
157
+ <div className="flex flex-col gap-6 w-full md:max-w-4xl mx-auto px-6 py-2">
158
+ {!isLoading && !isError && (
159
+ <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-2 gap-3 groups-section">
160
+ {servers.map((server: MessageServer) => (
161
+ <ServerChannels key={server.id} serverId={server.id} />
162
+ ))}
163
+ </div>
164
+ )}
165
+ </div>
166
+ </TabsContent>
167
+ </Tabs>
168
+ </div>
169
+ </div>
170
+
171
+ {selectedAgent?.id && (
172
+ <ProfileOverlay isOpen={isOverlayOpen} onClose={closeOverlay} agentId={selectedAgent.id} />
173
+ )}
174
+
175
+ {isGroupPanelOpen && (
176
+ <GroupPanel
177
+ onClose={() => {
178
+ setSelectedGroupId(null);
179
+ setIsGroupPanelOpen(false);
180
+ }}
181
+ channelId={selectedGroupId ?? undefined}
182
+ />
183
+ )}
184
+ </>
185
+ );
186
+ }
187
+
188
+ // Sub-component to fetch and display channels for a given server
189
+ const ServerChannels = React.memo(({ serverId }: { serverId: UUID }) => {
190
+ const { data: channelsData, isLoading: isLoadingChannels } = useChannels(serverId) as {
191
+ data: { data: { channels: MessageChannel[] } } | undefined;
192
+ isLoading: boolean;
193
+ };
194
+ const groupChannels = useMemo(
195
+ () =>
196
+ channelsData?.data?.channels?.filter(
197
+ (ch: MessageChannel) => ch.type === CoreChannelType.GROUP
198
+ ) || [],
199
+ [channelsData]
200
+ );
201
+
202
+ if (isLoadingChannels) return <p>Loading channels for server...</p>;
203
+ if (!groupChannels || groupChannels.length === 0)
204
+ return <p className="text-sm text-muted-foreground">No group channels in this server.</p>;
205
+
206
+ return (
207
+ <>
208
+ {groupChannels.map((channel: MessageChannel) => (
209
+ <GroupCard
210
+ key={channel.id}
211
+ group={{ ...channel, server_id: serverId } as MessageChannel & { server_id: UUID }} // Pass server_id for navigation context
212
+ />
213
+ ))}
214
+ </>
215
+ );
216
+ });
217
+
218
+ ServerChannels.displayName = 'ServerChannels';
@@ -0,0 +1,71 @@
1
+ import { AlertCircle, Home, ArrowLeft } from 'lucide-react';
2
+ import { Link, useLocation } from 'react-router-dom';
3
+ import { Button } from '@/components/ui/button';
4
+
5
+ export default function NotFound() {
6
+ const location = useLocation();
7
+ const path = location.pathname;
8
+
9
+ // Determine if this is likely an API endpoint that doesn't exist
10
+ const isLikelyApiEndpoint =
11
+ path.startsWith('/api/') ||
12
+ path.includes('/agents/') ||
13
+ path.includes('/memory/') ||
14
+ path.includes('/speech/');
15
+
16
+ return (
17
+ <div className="flex flex-col items-center justify-center min-h-[70vh] px-4 text-center">
18
+ <div className="flex items-center justify-center mb-8">
19
+ <div className="bg-red-900/20 h-24 w-24 rounded-full flex items-center justify-center">
20
+ <AlertCircle className="h-14 w-14 text-red-500" />
21
+ </div>
22
+ </div>
23
+
24
+ <h1 className="text-3xl font-bold mb-2">Page Not Found</h1>
25
+
26
+ <div className="max-w-lg">
27
+ {isLikelyApiEndpoint ? (
28
+ <>
29
+ <p className="text-lg mb-6 text-muted-foreground">
30
+ The endpoint <span className="font-mono text-red-400">{path}</span> does not exist.
31
+ </p>
32
+ <div className="bg-red-900/20 border border-red-800/30 rounded-md p-4 mb-8 text-left">
33
+ <h3 className="text-red-400 font-medium mb-2 flex items-center">
34
+ <AlertCircle className="h-4 w-4 mr-2" />
35
+ Endpoint Not Found
36
+ </h3>
37
+ <p className="text-sm text-red-300/80 mb-2">
38
+ The requested API endpoint does not exist on this server. Please check that:
39
+ </p>
40
+ <ul className="text-sm text-red-300/80 list-disc pl-5 space-y-1">
41
+ <li>The URL is spelled correctly</li>
42
+ <li>You're using the correct version of the API</li>
43
+ <li>The endpoint is available in this version of Eliza</li>
44
+ </ul>
45
+ </div>
46
+ </>
47
+ ) : (
48
+ <p className="text-lg mb-6 text-muted-foreground">
49
+ Sorry, the page you're looking for doesn't exist or has been moved.
50
+ </p>
51
+ )}
52
+
53
+ <div className="flex flex-col sm:flex-row gap-4 justify-center">
54
+ <Button asChild variant="default">
55
+ <Link to="/">
56
+ <Home className="h-4 w-4 mr-2" />
57
+ Go to Home
58
+ </Link>
59
+ </Button>
60
+
61
+ <Button asChild variant="outline">
62
+ <a onClick={() => window.history.back()}>
63
+ <ArrowLeft className="h-4 w-4 mr-2" />
64
+ Go Back
65
+ </a>
66
+ </Button>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ );
71
+ }
@@ -0,0 +1,154 @@
1
+ // Test setup file for Bun:test
2
+ import { GlobalRegistrator } from '@happy-dom/global-registrator';
3
+ import { afterEach, beforeEach, expect, mock } from 'bun:test';
4
+ import { cleanup } from '@testing-library/react';
5
+ import * as matchers from '@testing-library/jest-dom/matchers';
6
+
7
+ // Import React to access internal state
8
+ import React from 'react';
9
+
10
+ // Set up DOM environment with Happy DOM (recommended by Bun)
11
+ GlobalRegistrator.register();
12
+
13
+ // Extend expect with jest-dom matchers
14
+ expect.extend(matchers);
15
+
16
+ // Create a comprehensive localStorage mock
17
+ const createLocalStorageMock = () => {
18
+ const store: Record<string, string> = {};
19
+
20
+ return {
21
+ getItem: (key: string): string | null => {
22
+ return store[key] || null;
23
+ },
24
+ setItem: (key: string, value: string): void => {
25
+ store[key] = value;
26
+ },
27
+ removeItem: (key: string): void => {
28
+ delete store[key];
29
+ },
30
+ clear: (): void => {
31
+ Object.keys(store).forEach((key) => delete store[key]);
32
+ },
33
+ get length(): number {
34
+ return Object.keys(store).length;
35
+ },
36
+ key: (index: number): string | null => {
37
+ const keys = Object.keys(store);
38
+ return keys[index] || null;
39
+ },
40
+ };
41
+ };
42
+
43
+ // Set up fresh localStorage and sessionStorage for each test
44
+ beforeEach(() => {
45
+ // Create fresh storage instances for each test
46
+ Object.defineProperty(window, 'localStorage', {
47
+ value: createLocalStorageMock(),
48
+ writable: true,
49
+ configurable: true,
50
+ });
51
+
52
+ Object.defineProperty(window, 'sessionStorage', {
53
+ value: createLocalStorageMock(),
54
+ writable: true,
55
+ configurable: true,
56
+ });
57
+
58
+ // Reset window dimensions to default
59
+ Object.defineProperty(window, 'innerWidth', {
60
+ writable: true,
61
+ configurable: true,
62
+ value: 1500,
63
+ });
64
+
65
+ Object.defineProperty(window, 'innerHeight', {
66
+ writable: true,
67
+ configurable: true,
68
+ value: 900,
69
+ });
70
+ });
71
+
72
+ // Clean up after each test
73
+ afterEach(() => {
74
+ cleanup();
75
+
76
+ // Clear storage but don't reset the implementation
77
+ if (window.localStorage) {
78
+ window.localStorage.clear();
79
+ }
80
+ if (window.sessionStorage) {
81
+ window.sessionStorage.clear();
82
+ }
83
+
84
+ // Force React to clear any cached hook state
85
+ // This helps prevent cross-test contamination when running multiple test files
86
+ if ((React as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED) {
87
+ const internals = (React as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
88
+ if (internals.ReactCurrentDispatcher) {
89
+ internals.ReactCurrentDispatcher.current = null;
90
+ }
91
+ if (internals.ReactCurrentBatchConfig) {
92
+ internals.ReactCurrentBatchConfig.transition = null;
93
+ }
94
+ }
95
+ });
96
+
97
+ // React 19 specific setup - add missing APIs for testing-library compatibility
98
+ try {
99
+ const React = require('react');
100
+
101
+ // Add React.createRef polyfill for React 19 compatibility with testing-library
102
+ if (!React.createRef) {
103
+ React.createRef = function createRef() {
104
+ return { current: null };
105
+ };
106
+ }
107
+ } catch (e) {
108
+ console.warn('Failed to set up React internals:', e instanceof Error ? e.message : String(e));
109
+ }
110
+
111
+ // Mock window.matchMedia
112
+ Object.defineProperty(window, 'matchMedia', {
113
+ writable: true,
114
+ value: (query: string) => ({
115
+ matches: false,
116
+ media: query,
117
+ onchange: null,
118
+ addListener: () => {},
119
+ removeListener: () => {},
120
+ addEventListener: () => {},
121
+ removeEventListener: () => {},
122
+ dispatchEvent: () => {},
123
+ }),
124
+ });
125
+
126
+ // Mock IntersectionObserver
127
+ global.IntersectionObserver = class MockIntersectionObserver {
128
+ root = null;
129
+ rootMargin = '';
130
+ thresholds = [];
131
+
132
+ constructor() {}
133
+ observe() {}
134
+ disconnect() {}
135
+ unobserve() {}
136
+ takeRecords() {
137
+ return [];
138
+ }
139
+ } as any;
140
+
141
+ // Mock ResizeObserver
142
+ global.ResizeObserver = class MockResizeObserver {
143
+ constructor() {}
144
+ observe() {}
145
+ disconnect() {}
146
+ unobserve() {}
147
+ } as any;
148
+
149
+ // Mock scrollTo for window and elements
150
+ window.scrollTo = mock();
151
+ Element.prototype.scrollTo = mock();
152
+ Element.prototype.scrollIntoView = mock();
153
+
154
+ // Add any other global test setup here
@@ -0,0 +1,4 @@
1
+ declare module 'crypto-browserify' {
2
+ const crypto: any;
3
+ export default crypto;
4
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Interface representing an attachment.
3
+ *
4
+ * @interface
5
+ * @property {string} url - The URL of the attachment.
6
+ * @property {string} contentType - The content type of the attachment.
7
+ * @property {string} title - The title of the attachment.
8
+ */
9
+ export interface IAttachment {
10
+ url: string;
11
+ contentType: string;
12
+ title: string;
13
+ }