@elizaos/client 1.5.5-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +350 -0
- package/dist/assets/empty-module-CLMscLYw.js +1 -0
- package/dist/assets/main-BBZ_3lkn.css +5999 -0
- package/dist/assets/main-C5zNUkXH.js +7 -0
- package/dist/assets/main-Dz64ENQg.js +614 -0
- package/dist/assets/react-vendor-DM5m98rr.js +545 -0
- package/dist/assets/ui-vendor-BQCqNqg0.js +1 -0
- package/dist/elizaos-avatar.png +0 -0
- package/dist/elizaos-icon.png +0 -0
- package/dist/elizaos-logo-light.png +0 -0
- package/dist/elizaos.webp +0 -0
- package/dist/favicon.ico +0 -0
- package/dist/images/agents/agent1.png +0 -0
- package/dist/images/agents/agent2.png +0 -0
- package/dist/images/agents/agent3.png +0 -0
- package/dist/images/agents/agent4.png +0 -0
- package/dist/images/agents/agent5.png +0 -0
- package/dist/index.html +14 -0
- package/index.html +24 -0
- package/package.json +159 -0
- package/postcss.config.js +3 -0
- package/public/elizaos-avatar.png +0 -0
- package/public/elizaos-icon.png +0 -0
- package/public/elizaos-logo-light.png +0 -0
- package/public/elizaos.webp +0 -0
- package/public/favicon.ico +0 -0
- package/public/images/agents/agent1.png +0 -0
- package/public/images/agents/agent2.png +0 -0
- package/public/images/agents/agent3.png +0 -0
- package/public/images/agents/agent4.png +0 -0
- package/public/images/agents/agent5.png +0 -0
- package/src/App.tsx +222 -0
- package/src/components/AgentDetailsPanel.tsx +147 -0
- package/src/components/ChatInputArea.tsx +196 -0
- package/src/components/ChatMessageListComponent.tsx +139 -0
- package/src/components/actionTool.tsx +186 -0
- package/src/components/add-agent-card.tsx +77 -0
- package/src/components/agent-action-viewer.tsx +816 -0
- package/src/components/agent-avatar-stack.tsx +121 -0
- package/src/components/agent-card.cy.tsx +259 -0
- package/src/components/agent-card.tsx +177 -0
- package/src/components/agent-creator.tsx +142 -0
- package/src/components/agent-log-viewer.tsx +645 -0
- package/src/components/agent-memory-edit-overlay.tsx +461 -0
- package/src/components/agent-memory-viewer.tsx +504 -0
- package/src/components/agent-settings.tsx +270 -0
- package/src/components/agent-sidebar.tsx +178 -0
- package/src/components/api-key-dialog.tsx +113 -0
- package/src/components/app-sidebar.tsx +685 -0
- package/src/components/array-input.tsx +116 -0
- package/src/components/audio-recorder.tsx +292 -0
- package/src/components/avatar-panel.tsx +141 -0
- package/src/components/character-form.tsx +1138 -0
- package/src/components/chat.tsx +1813 -0
- package/src/components/combobox.tsx +187 -0
- package/src/components/confirmation-dialog.tsx +59 -0
- package/src/components/connection-error-banner.tsx +101 -0
- package/src/components/connection-status.cy.tsx +73 -0
- package/src/components/connection-status.tsx +155 -0
- package/src/components/copy-button.tsx +35 -0
- package/src/components/delete-button.tsx +24 -0
- package/src/components/env-settings.tsx +261 -0
- package/src/components/group-card.tsx +160 -0
- package/src/components/group-panel.tsx +543 -0
- package/src/components/input-copy.tsx +21 -0
- package/src/components/logs-page.tsx +41 -0
- package/src/components/media-content.tsx +385 -0
- package/src/components/memory-graph.tsx +170 -0
- package/src/components/missing-secrets-dialog.tsx +72 -0
- package/src/components/onboarding-tour.tsx +247 -0
- package/src/components/page-title.tsx +8 -0
- package/src/components/plugins-panel.tsx +383 -0
- package/src/components/profile-card.tsx +66 -0
- package/src/components/profile-overlay.tsx +283 -0
- package/src/components/retry-button.tsx +28 -0
- package/src/components/secret-panel.tsx +1505 -0
- package/src/components/server-management.tsx +264 -0
- package/src/components/split-button.tsx +148 -0
- package/src/components/stop-agent-button.tsx +99 -0
- package/src/components/ui/alert-dialog.cy.tsx +333 -0
- package/src/components/ui/alert-dialog.tsx +115 -0
- package/src/components/ui/alert.tsx +49 -0
- package/src/components/ui/avatar.cy.tsx +180 -0
- package/src/components/ui/avatar.tsx +57 -0
- package/src/components/ui/badge.cy.tsx +146 -0
- package/src/components/ui/badge.tsx +43 -0
- package/src/components/ui/button.cy.tsx +177 -0
- package/src/components/ui/button.tsx +56 -0
- package/src/components/ui/card.cy.tsx +160 -0
- package/src/components/ui/card.tsx +73 -0
- package/src/components/ui/chat/animated-markdown.tsx +59 -0
- package/src/components/ui/chat/chat-bubble.tsx +178 -0
- package/src/components/ui/chat/chat-container.tsx +51 -0
- package/src/components/ui/chat/chat-input.cy.tsx +169 -0
- package/src/components/ui/chat/chat-input.tsx +47 -0
- package/src/components/ui/chat/chat-message-list.tsx +61 -0
- package/src/components/ui/chat/chat-tts-button.tsx +199 -0
- package/src/components/ui/chat/code-block.tsx +79 -0
- package/src/components/ui/chat/expandable-chat.tsx +131 -0
- package/src/components/ui/chat/hooks/useAutoScroll.ts +86 -0
- package/src/components/ui/chat/markdown.tsx +209 -0
- package/src/components/ui/chat/message-loading.tsx +48 -0
- package/src/components/ui/checkbox.cy.tsx +170 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/collapsible.cy.tsx +283 -0
- package/src/components/ui/collapsible.tsx +9 -0
- package/src/components/ui/command.cy.tsx +313 -0
- package/src/components/ui/command.tsx +143 -0
- package/src/components/ui/dialog.cy.tsx +279 -0
- package/src/components/ui/dialog.tsx +104 -0
- package/src/components/ui/dropdown-menu.cy.tsx +273 -0
- package/src/components/ui/dropdown-menu.tsx +281 -0
- package/src/components/ui/input.cy.tsx +82 -0
- package/src/components/ui/input.tsx +27 -0
- package/src/components/ui/label.cy.tsx +157 -0
- package/src/components/ui/label.tsx +19 -0
- package/src/components/ui/resizable.tsx +42 -0
- package/src/components/ui/scroll-area.cy.tsx +242 -0
- package/src/components/ui/scroll-area.tsx +46 -0
- package/src/components/ui/select.cy.tsx +277 -0
- package/src/components/ui/select.tsx +155 -0
- package/src/components/ui/separator.cy.tsx +145 -0
- package/src/components/ui/separator.tsx +29 -0
- package/src/components/ui/sheet.cy.tsx +324 -0
- package/src/components/ui/sheet.tsx +119 -0
- package/src/components/ui/sidebar.tsx +734 -0
- package/src/components/ui/skeleton.cy.tsx +149 -0
- package/src/components/ui/skeleton.tsx +17 -0
- package/src/components/ui/split-button.cy.tsx +274 -0
- package/src/components/ui/split-button.tsx +112 -0
- package/src/components/ui/switch.tsx +28 -0
- package/src/components/ui/tabs.cy.tsx +271 -0
- package/src/components/ui/tabs.tsx +53 -0
- package/src/components/ui/textarea.cy.tsx +136 -0
- package/src/components/ui/textarea.tsx +26 -0
- package/src/components/ui/toast.cy.tsx +209 -0
- package/src/components/ui/toast.tsx +126 -0
- package/src/components/ui/toaster.tsx +29 -0
- package/src/components/ui/tooltip.cy.tsx +244 -0
- package/src/components/ui/tooltip.tsx +30 -0
- package/src/config/agent-templates.ts +349 -0
- package/src/config/voice-models.ts +181 -0
- package/src/constants.ts +23 -0
- package/src/context/AuthContext.tsx +44 -0
- package/src/context/ConnectionContext.tsx +194 -0
- package/src/entry.tsx +9 -0
- package/src/hooks/__tests__/use-agent-tab-state.test.ts +137 -0
- package/src/hooks/__tests__/use-agent-update.test.tsx +250 -0
- package/src/hooks/__tests__/use-character-convert.test.ts +102 -0
- package/src/hooks/__tests__/use-panel-width-state.test.ts +243 -0
- package/src/hooks/__tests__/use-sidebar-state.test.ts +117 -0
- package/src/hooks/use-agent-management.ts +130 -0
- package/src/hooks/use-agent-tab-state.ts +74 -0
- package/src/hooks/use-agent-update.ts +469 -0
- package/src/hooks/use-character-convert.ts +138 -0
- package/src/hooks/use-confirmation.ts +55 -0
- package/src/hooks/use-delete-agent.ts +123 -0
- package/src/hooks/use-dm-channels.ts +198 -0
- package/src/hooks/use-elevenlabs-voices.ts +83 -0
- package/src/hooks/use-file-upload.ts +224 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/hooks/use-onboarding.tsx +49 -0
- package/src/hooks/use-panel-width-state.ts +147 -0
- package/src/hooks/use-partial-update.ts +288 -0
- package/src/hooks/use-plugin-details.ts +462 -0
- package/src/hooks/use-plugins.ts +119 -0
- package/src/hooks/use-query-hooks.ts +1263 -0
- package/src/hooks/use-server-agents.ts +62 -0
- package/src/hooks/use-server-version.tsx +47 -0
- package/src/hooks/use-sidebar-state.ts +50 -0
- package/src/hooks/use-socket-chat.ts +264 -0
- package/src/hooks/use-toast.ts +260 -0
- package/src/hooks/use-version.tsx +64 -0
- package/src/index.css +146 -0
- package/src/lib/api-client-config.ts +53 -0
- package/src/lib/api-type-mappers.ts +196 -0
- package/src/lib/export-utils.ts +123 -0
- package/src/lib/logger.ts +19 -0
- package/src/lib/media-utils.ts +170 -0
- package/src/lib/pca.test.ts +17 -0
- package/src/lib/pca.ts +52 -0
- package/src/lib/socketio-manager.ts +664 -0
- package/src/lib/utils.ts +168 -0
- package/src/main.tsx +16 -0
- package/src/mocks/empty-module.ts +12 -0
- package/src/mocks/node-module.ts +57 -0
- package/src/polyfills.ts +37 -0
- package/src/routes/agent-detail.tsx +30 -0
- package/src/routes/agent-list.tsx +27 -0
- package/src/routes/agent-settings.tsx +48 -0
- package/src/routes/character-detail.tsx +52 -0
- package/src/routes/character-form.tsx +79 -0
- package/src/routes/character-list.tsx +38 -0
- package/src/routes/chat.tsx +128 -0
- package/src/routes/createAgent.tsx +13 -0
- package/src/routes/group-new.tsx +50 -0
- package/src/routes/group.tsx +29 -0
- package/src/routes/home.tsx +218 -0
- package/src/routes/not-found.tsx +71 -0
- package/src/test/setup.ts +154 -0
- package/src/types/crypto-browserify.d.ts +4 -0
- package/src/types/index.ts +13 -0
- package/src/types/rooms.ts +8 -0
- package/src/types.ts +84 -0
- package/src/vite-env.d.ts +40 -0
- package/tailwind.config.ts +90 -0
- package/tsconfig.json +10 -0
- package/vite.config.ts +102 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useContext,
|
|
4
|
+
useState,
|
|
5
|
+
ReactNode,
|
|
6
|
+
useEffect,
|
|
7
|
+
useRef,
|
|
8
|
+
useCallback,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import { useToast } from '@/hooks/use-toast';
|
|
11
|
+
import SocketIOManager from '@/lib/socketio-manager';
|
|
12
|
+
import { updateApiClientApiKey } from '@/lib/api-client-config';
|
|
13
|
+
// Eliza client refresh functionality removed (not needed with direct client)
|
|
14
|
+
|
|
15
|
+
export const connectionStatusActions = {
|
|
16
|
+
setUnauthorized: (message: string) => {
|
|
17
|
+
console.warn('setUnauthorized called before ConnectionContext is ready', message);
|
|
18
|
+
},
|
|
19
|
+
setOfflineStatus: (isOffline: boolean) => {
|
|
20
|
+
console.warn('setOfflineStatus called before ConnectionContext is ready', isOffline);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type ConnectionStatusType =
|
|
25
|
+
| 'loading'
|
|
26
|
+
| 'connected'
|
|
27
|
+
| 'reconnecting'
|
|
28
|
+
| 'error'
|
|
29
|
+
| 'unauthorized';
|
|
30
|
+
|
|
31
|
+
interface ConnectionContextType {
|
|
32
|
+
status: ConnectionStatusType;
|
|
33
|
+
error: string | null;
|
|
34
|
+
setUnauthorizedFromApi: (message: string) => void;
|
|
35
|
+
setOfflineStatusFromProvider: (isOffline: boolean) => void;
|
|
36
|
+
refreshApiClient: (newApiKey?: string | null) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ConnectionContext = createContext<ConnectionContextType | undefined>(undefined);
|
|
40
|
+
|
|
41
|
+
export const ConnectionProvider = ({ children }: { children: ReactNode }) => {
|
|
42
|
+
const { toast } = useToast();
|
|
43
|
+
const [status, setStatus] = useState<ConnectionStatusType>('loading');
|
|
44
|
+
const [error, setError] = useState<string | null>(null);
|
|
45
|
+
const isFirstConnect = useRef(true);
|
|
46
|
+
const socketManager = SocketIOManager.getInstance();
|
|
47
|
+
|
|
48
|
+
const setUnauthorizedFromApi = useCallback(
|
|
49
|
+
(message: string) => {
|
|
50
|
+
setStatus('unauthorized');
|
|
51
|
+
setError(message);
|
|
52
|
+
toast({
|
|
53
|
+
title: 'Authorization Required',
|
|
54
|
+
description: message || 'Please provide a valid API Key.',
|
|
55
|
+
variant: 'destructive',
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
[toast]
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const setOfflineStatusFromProvider = useCallback(
|
|
62
|
+
(isOffline: boolean) => {
|
|
63
|
+
if (isOffline) {
|
|
64
|
+
if (status !== 'error' && status !== 'unauthorized') {
|
|
65
|
+
setStatus('error');
|
|
66
|
+
setError('Network connection appears to be offline.');
|
|
67
|
+
toast({
|
|
68
|
+
title: 'Network Offline',
|
|
69
|
+
description: 'Please check your internet connection.',
|
|
70
|
+
variant: 'destructive',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
if (status === 'error' && error?.includes('offline')) {
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
[status, error, toast]
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const refreshApiClient = useCallback((newApiKey?: string | null) => {
|
|
82
|
+
try {
|
|
83
|
+
// Update localStorage if a new API key is provided
|
|
84
|
+
if (newApiKey !== undefined) {
|
|
85
|
+
updateApiClientApiKey(newApiKey);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Refresh the ElizaClient instance with new configuration
|
|
89
|
+
// Client refresh not needed with direct client pattern
|
|
90
|
+
|
|
91
|
+
console.log('API client refreshed with new configuration');
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error('Failed to refresh API client:', error);
|
|
94
|
+
}
|
|
95
|
+
}, []);
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
connectionStatusActions.setUnauthorized = setUnauthorizedFromApi;
|
|
99
|
+
connectionStatusActions.setOfflineStatus = setOfflineStatusFromProvider;
|
|
100
|
+
}, [setUnauthorizedFromApi, setOfflineStatusFromProvider]);
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
const onConnect = () => {
|
|
104
|
+
setStatus('connected');
|
|
105
|
+
setError(null);
|
|
106
|
+
if (connectionStatusActions.setOfflineStatus) {
|
|
107
|
+
connectionStatusActions.setOfflineStatus(false);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (isFirstConnect.current) {
|
|
111
|
+
isFirstConnect.current = false;
|
|
112
|
+
} else {
|
|
113
|
+
toast({
|
|
114
|
+
title: 'Connection Restored',
|
|
115
|
+
description: 'Successfully reconnected to the Eliza server.',
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const onDisconnect = (reason: string) => {
|
|
121
|
+
setStatus('error');
|
|
122
|
+
setError(`Connection lost: ${reason}`);
|
|
123
|
+
if (connectionStatusActions.setOfflineStatus) {
|
|
124
|
+
connectionStatusActions.setOfflineStatus(true);
|
|
125
|
+
}
|
|
126
|
+
toast({
|
|
127
|
+
title: 'Connection Lost',
|
|
128
|
+
description: 'Attempting to reconnect to the Eliza server…',
|
|
129
|
+
variant: 'destructive',
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const onReconnectAttempt = () => {
|
|
134
|
+
setStatus('reconnecting');
|
|
135
|
+
setError('Reconnecting...');
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const onConnectError = (err: Error) => {
|
|
139
|
+
setStatus('error');
|
|
140
|
+
setError(err.message);
|
|
141
|
+
if (connectionStatusActions.setOfflineStatus) {
|
|
142
|
+
connectionStatusActions.setOfflineStatus(true);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const onUnauthorized = (reason: string) => {
|
|
147
|
+
setStatus('unauthorized');
|
|
148
|
+
setError(`Unauthorized: ${reason}`);
|
|
149
|
+
toast({ title: 'Unauthorized', description: 'Please log in again.', variant: 'destructive' });
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
socketManager.on('connect', onConnect);
|
|
153
|
+
socketManager.on('disconnect', onDisconnect);
|
|
154
|
+
socketManager.on('reconnect', onConnect);
|
|
155
|
+
socketManager.on('reconnect_attempt', onReconnectAttempt);
|
|
156
|
+
socketManager.on('connect_error', onConnectError);
|
|
157
|
+
socketManager.on('unauthorized', onUnauthorized);
|
|
158
|
+
|
|
159
|
+
if (SocketIOManager.isConnected()) {
|
|
160
|
+
onConnect();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return () => {
|
|
164
|
+
socketManager.off('connect', onConnect);
|
|
165
|
+
socketManager.off('disconnect', onDisconnect);
|
|
166
|
+
socketManager.off('reconnect', onConnect);
|
|
167
|
+
socketManager.off('reconnect_attempt', onReconnectAttempt);
|
|
168
|
+
socketManager.off('connect_error', onConnectError);
|
|
169
|
+
socketManager.off('unauthorized', onUnauthorized);
|
|
170
|
+
};
|
|
171
|
+
}, [toast, socketManager, setOfflineStatusFromProvider]);
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<ConnectionContext.Provider
|
|
175
|
+
value={{
|
|
176
|
+
status,
|
|
177
|
+
error,
|
|
178
|
+
setUnauthorizedFromApi,
|
|
179
|
+
setOfflineStatusFromProvider,
|
|
180
|
+
refreshApiClient,
|
|
181
|
+
}}
|
|
182
|
+
>
|
|
183
|
+
{children}
|
|
184
|
+
</ConnectionContext.Provider>
|
|
185
|
+
);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export const useConnection = () => {
|
|
189
|
+
const ctx = useContext(ConnectionContext);
|
|
190
|
+
if (!ctx) {
|
|
191
|
+
throw new Error('useConnection must be inside ConnectionProvider');
|
|
192
|
+
}
|
|
193
|
+
return ctx;
|
|
194
|
+
};
|
package/src/entry.tsx
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { renderHook, act, waitFor } from '@testing-library/react';
|
|
2
|
+
import { mock, describe, it, expect, beforeEach } from 'bun:test';
|
|
3
|
+
import { useAgentTabState } from '../use-agent-tab-state';
|
|
4
|
+
|
|
5
|
+
// Mock clientLogger
|
|
6
|
+
mock.module('../../lib/logger', () => ({
|
|
7
|
+
default: {
|
|
8
|
+
error: mock(),
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
describe('useAgentTabState', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// Clear localStorage before each test
|
|
15
|
+
localStorage.clear();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should initialize with details tab when no agentId is provided', () => {
|
|
19
|
+
const { result } = renderHook(() => useAgentTabState(undefined));
|
|
20
|
+
|
|
21
|
+
expect(result.current.currentTab).toBe('details');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should initialize with details tab for new agent', () => {
|
|
25
|
+
const agentId = '550e8400-e29b-41d4-a716-446655440000';
|
|
26
|
+
localStorage.setItem('eliza-agent-tab-states', '{}');
|
|
27
|
+
|
|
28
|
+
const { result } = renderHook(() => useAgentTabState(agentId));
|
|
29
|
+
|
|
30
|
+
expect(result.current.currentTab).toBe('details');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should load saved tab state for existing agent', async () => {
|
|
34
|
+
const agentId = '550e8400-e29b-41d4-a716-446655440000';
|
|
35
|
+
const savedStates = {
|
|
36
|
+
'550e8400-e29b-41d4-a716-446655440000': 'memories',
|
|
37
|
+
};
|
|
38
|
+
localStorage.setItem('eliza-agent-tab-states', JSON.stringify(savedStates));
|
|
39
|
+
|
|
40
|
+
const { result } = renderHook(() => useAgentTabState(agentId));
|
|
41
|
+
|
|
42
|
+
await waitFor(() => {
|
|
43
|
+
expect(result.current.currentTab).toBe('memories');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should save tab state when changed', async () => {
|
|
48
|
+
const agentId = '550e8400-e29b-41d4-a716-446655440000';
|
|
49
|
+
localStorage.setItem('eliza-agent-tab-states', '{}');
|
|
50
|
+
|
|
51
|
+
const { result } = renderHook(() => useAgentTabState(agentId));
|
|
52
|
+
|
|
53
|
+
act(() => {
|
|
54
|
+
result.current.setTab('actions');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await waitFor(() => {
|
|
58
|
+
expect(result.current.currentTab).toBe('actions');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(localStorage.getItem('eliza-agent-tab-states')).toBe(
|
|
62
|
+
JSON.stringify({ '550e8400-e29b-41d4-a716-446655440000': 'actions' })
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should preserve other agent states when updating', () => {
|
|
67
|
+
const agentId = '550e8400-e29b-41d4-a716-446655440001';
|
|
68
|
+
const existingStates = {
|
|
69
|
+
'550e8400-e29b-41d4-a716-446655440000': 'memories',
|
|
70
|
+
};
|
|
71
|
+
localStorage.setItem('eliza-agent-tab-states', JSON.stringify(existingStates));
|
|
72
|
+
|
|
73
|
+
const { result } = renderHook(() => useAgentTabState(agentId));
|
|
74
|
+
|
|
75
|
+
act(() => {
|
|
76
|
+
result.current.setTab('logs');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(localStorage.getItem('eliza-agent-tab-states')).toBe(
|
|
80
|
+
JSON.stringify({
|
|
81
|
+
'550e8400-e29b-41d4-a716-446655440000': 'memories',
|
|
82
|
+
'550e8400-e29b-41d4-a716-446655440001': 'logs',
|
|
83
|
+
})
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should handle localStorage errors gracefully', () => {
|
|
88
|
+
const agentId = '550e8400-e29b-41d4-a716-446655440000';
|
|
89
|
+
const originalGetItem = localStorage.getItem;
|
|
90
|
+
localStorage.getItem = () => {
|
|
91
|
+
throw new Error('localStorage not available');
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const { result } = renderHook(() => useAgentTabState(agentId));
|
|
95
|
+
|
|
96
|
+
expect(result.current.currentTab).toBe('details');
|
|
97
|
+
|
|
98
|
+
// Restore original getItem
|
|
99
|
+
localStorage.getItem = originalGetItem;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should switch tab state when agentId changes', async () => {
|
|
103
|
+
const savedStates = {
|
|
104
|
+
'550e8400-e29b-41d4-a716-446655440000': 'memories',
|
|
105
|
+
'550e8400-e29b-41d4-a716-446655440001': 'actions',
|
|
106
|
+
};
|
|
107
|
+
localStorage.setItem('eliza-agent-tab-states', JSON.stringify(savedStates));
|
|
108
|
+
|
|
109
|
+
const { result, rerender } = renderHook(({ agentId }) => useAgentTabState(agentId), {
|
|
110
|
+
initialProps: { agentId: '550e8400-e29b-41d4-a716-446655440000' as any },
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
await waitFor(() => {
|
|
114
|
+
expect(result.current.currentTab).toBe('memories');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
rerender({ agentId: '550e8400-e29b-41d4-a716-446655440001' as any });
|
|
118
|
+
|
|
119
|
+
await waitFor(() => {
|
|
120
|
+
expect(result.current.currentTab).toBe('actions');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should not save to localStorage when no agentId is provided', async () => {
|
|
125
|
+
const { result } = renderHook(() => useAgentTabState(undefined));
|
|
126
|
+
|
|
127
|
+
act(() => {
|
|
128
|
+
result.current.setTab('actions');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
await waitFor(() => {
|
|
132
|
+
expect(result.current.currentTab).toBe('actions');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(localStorage.getItem('eliza-agent-tab-states')).toBe(null);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { useAgentUpdate } from '../use-agent-update';
|
|
2
|
+
import { describe, test, expect, mock, beforeEach, afterEach } from 'bun:test';
|
|
3
|
+
import { renderHook } from '@testing-library/react';
|
|
4
|
+
|
|
5
|
+
// Mock the usePartialUpdate hook instead of React core hooks
|
|
6
|
+
mock.module('../use-partial-update', () => ({
|
|
7
|
+
usePartialUpdate: (initialValue: any) => {
|
|
8
|
+
let currentValue = { ...initialValue };
|
|
9
|
+
|
|
10
|
+
const updateFieldMock = mock((path: string, value: any) => {
|
|
11
|
+
// Simple implementation to track updates
|
|
12
|
+
const pathParts = path.split('.');
|
|
13
|
+
|
|
14
|
+
if (pathParts.length === 1) {
|
|
15
|
+
currentValue[path] = value;
|
|
16
|
+
} else {
|
|
17
|
+
// Handle nested paths (simplified)
|
|
18
|
+
const [first, ...rest] = pathParts;
|
|
19
|
+
if (!currentValue[first]) currentValue[first] = {};
|
|
20
|
+
|
|
21
|
+
// Very simple implementation - doesn't handle complex nested paths
|
|
22
|
+
let target = currentValue[first];
|
|
23
|
+
for (let i = 0; i < rest.length - 1; i++) {
|
|
24
|
+
if (!target[rest[i]]) target[rest[i]] = {};
|
|
25
|
+
target = target[rest[i]];
|
|
26
|
+
}
|
|
27
|
+
target[rest[rest.length - 1]] = value;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const addArrayItemMock = mock((path: string, item: any) => {
|
|
32
|
+
const pathParts = path.split('.');
|
|
33
|
+
if (pathParts.length === 1) {
|
|
34
|
+
if (!Array.isArray(currentValue[path])) {
|
|
35
|
+
currentValue[path] = [];
|
|
36
|
+
}
|
|
37
|
+
currentValue[path].push(item);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const removeArrayItemMock = mock();
|
|
42
|
+
const resetMock = mock();
|
|
43
|
+
|
|
44
|
+
const updateSettingsMock = mock((settings: any) => {
|
|
45
|
+
currentValue.settings = { ...currentValue.settings, ...settings };
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
value: currentValue,
|
|
50
|
+
updateField: updateFieldMock,
|
|
51
|
+
addArrayItem: addArrayItemMock,
|
|
52
|
+
removeArrayItem: removeArrayItemMock,
|
|
53
|
+
reset: resetMock,
|
|
54
|
+
updateSettings: updateSettingsMock,
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
// Define a simplified Agent type for our tests
|
|
60
|
+
type MockAgent = {
|
|
61
|
+
name: string;
|
|
62
|
+
username: string;
|
|
63
|
+
system: string;
|
|
64
|
+
bio: string[];
|
|
65
|
+
topics: string[];
|
|
66
|
+
adjectives: string[];
|
|
67
|
+
plugins: string[];
|
|
68
|
+
style: {
|
|
69
|
+
all: string[];
|
|
70
|
+
chat: string[];
|
|
71
|
+
post: string[];
|
|
72
|
+
};
|
|
73
|
+
settings: {
|
|
74
|
+
avatar?: string;
|
|
75
|
+
voice?: {
|
|
76
|
+
model?: string;
|
|
77
|
+
settings?: Record<string, any>;
|
|
78
|
+
};
|
|
79
|
+
secrets?: Record<string, string>;
|
|
80
|
+
[key: string]: any;
|
|
81
|
+
};
|
|
82
|
+
[key: string]: any;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
describe('useAgentUpdate hook', () => {
|
|
86
|
+
beforeEach(() => {
|
|
87
|
+
// Clear all mocks before each test
|
|
88
|
+
mock.restore();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
afterEach(() => {
|
|
92
|
+
// Clean up after each test
|
|
93
|
+
mock.restore();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('importAgent should call the appropriate update functions for all template fields', () => {
|
|
97
|
+
// Create initial and template agents
|
|
98
|
+
const initialAgent: MockAgent = {
|
|
99
|
+
name: 'Initial Agent',
|
|
100
|
+
username: 'initial_agent',
|
|
101
|
+
system: 'Initial system prompt',
|
|
102
|
+
bio: ['Initial bio'],
|
|
103
|
+
topics: ['Initial topic'],
|
|
104
|
+
adjectives: ['Initial adjective'],
|
|
105
|
+
plugins: ['initial-plugin'],
|
|
106
|
+
style: {
|
|
107
|
+
all: ['Initial style all'],
|
|
108
|
+
chat: ['Initial style chat'],
|
|
109
|
+
post: ['Initial style post'],
|
|
110
|
+
},
|
|
111
|
+
settings: {
|
|
112
|
+
avatar: 'initial-avatar.png',
|
|
113
|
+
voice: {
|
|
114
|
+
model: 'initial-voice-model',
|
|
115
|
+
},
|
|
116
|
+
secrets: {
|
|
117
|
+
INITIAL_API_KEY: 'initial-key-value',
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const templateAgent: MockAgent = {
|
|
123
|
+
name: 'Template Agent',
|
|
124
|
+
username: 'template_agent',
|
|
125
|
+
system: 'Template system prompt',
|
|
126
|
+
bio: ['Template bio 1', 'Template bio 2'],
|
|
127
|
+
topics: ['Template topic 1', 'Template topic 2'],
|
|
128
|
+
adjectives: ['Template adjective 1', 'Template adjective 2'],
|
|
129
|
+
plugins: ['template-plugin-1', 'template-plugin-2'],
|
|
130
|
+
style: {
|
|
131
|
+
all: ['Template style all 1', 'Template style all 2'],
|
|
132
|
+
chat: ['Template style chat 1', 'Template style chat 2'],
|
|
133
|
+
post: ['Template style post 1', 'Template style post 2'],
|
|
134
|
+
},
|
|
135
|
+
settings: {
|
|
136
|
+
avatar: 'template-avatar.png',
|
|
137
|
+
voice: {
|
|
138
|
+
model: 'template-voice-model',
|
|
139
|
+
},
|
|
140
|
+
secrets: {
|
|
141
|
+
TEMPLATE_API_KEY: 'template-key-value',
|
|
142
|
+
},
|
|
143
|
+
newSetting: 'new-template-value',
|
|
144
|
+
},
|
|
145
|
+
extraField: 'extra-field-value',
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Use renderHook to properly test the React hook
|
|
149
|
+
const { result } = renderHook(() => useAgentUpdate(initialAgent as any));
|
|
150
|
+
|
|
151
|
+
// Get the necessary functions
|
|
152
|
+
const { updateField, updateSettings } = result.current;
|
|
153
|
+
|
|
154
|
+
// Call importAgent
|
|
155
|
+
result.current.importAgent(templateAgent as any);
|
|
156
|
+
|
|
157
|
+
// Verify that updateField or updateSettings was called for each field in the template
|
|
158
|
+
|
|
159
|
+
// Check basic scalar fields
|
|
160
|
+
expect(updateField).toHaveBeenCalledWith('name', templateAgent.name);
|
|
161
|
+
expect(updateField).toHaveBeenCalledWith('username', templateAgent.username);
|
|
162
|
+
expect(updateField).toHaveBeenCalledWith('system', templateAgent.system);
|
|
163
|
+
|
|
164
|
+
// Check array fields
|
|
165
|
+
expect(updateField).toHaveBeenCalledWith('bio', templateAgent.bio);
|
|
166
|
+
expect(updateField).toHaveBeenCalledWith('topics', templateAgent.topics);
|
|
167
|
+
expect(updateField).toHaveBeenCalledWith('adjectives', templateAgent.adjectives);
|
|
168
|
+
expect(updateField).toHaveBeenCalledWith('plugins', templateAgent.plugins);
|
|
169
|
+
|
|
170
|
+
// Check style fields
|
|
171
|
+
expect(updateField).toHaveBeenCalledWith('style', {
|
|
172
|
+
all: templateAgent.style.all,
|
|
173
|
+
chat: templateAgent.style.chat,
|
|
174
|
+
post: templateAgent.style.post,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Check settings
|
|
178
|
+
expect(updateSettings).toHaveBeenCalledWith(
|
|
179
|
+
expect.objectContaining({
|
|
180
|
+
...initialAgent.settings,
|
|
181
|
+
...templateAgent.settings,
|
|
182
|
+
})
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// Check extra fields
|
|
186
|
+
expect(updateField).toHaveBeenCalledWith('extraField', templateAgent.extraField);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('importAgent should handle custom fields and nested objects', () => {
|
|
190
|
+
// Create initial and template agents with complex nested structures
|
|
191
|
+
const initialAgent: MockAgent = {
|
|
192
|
+
name: 'Initial',
|
|
193
|
+
username: 'initial',
|
|
194
|
+
system: 'Initial',
|
|
195
|
+
bio: [],
|
|
196
|
+
topics: [],
|
|
197
|
+
adjectives: [],
|
|
198
|
+
plugins: [],
|
|
199
|
+
style: { all: [], chat: [], post: [] },
|
|
200
|
+
settings: {
|
|
201
|
+
complexObject: {
|
|
202
|
+
level1: {
|
|
203
|
+
value: 'initial',
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const templateAgent: MockAgent = {
|
|
210
|
+
name: 'Template',
|
|
211
|
+
username: 'template',
|
|
212
|
+
system: 'Template',
|
|
213
|
+
bio: [],
|
|
214
|
+
topics: [],
|
|
215
|
+
adjectives: [],
|
|
216
|
+
plugins: [],
|
|
217
|
+
style: { all: [], chat: [], post: [] },
|
|
218
|
+
settings: {
|
|
219
|
+
complexObject: {
|
|
220
|
+
level1: {
|
|
221
|
+
value: 'template',
|
|
222
|
+
newField: 'new',
|
|
223
|
+
},
|
|
224
|
+
level2: 'new level',
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
customField: 'custom value',
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// Use renderHook to properly test the React hook
|
|
231
|
+
const { result } = renderHook(() => useAgentUpdate(initialAgent as any));
|
|
232
|
+
|
|
233
|
+
// Get the necessary functions
|
|
234
|
+
const { updateField, updateSettings } = result.current;
|
|
235
|
+
|
|
236
|
+
// Call importAgent
|
|
237
|
+
result.current.importAgent(templateAgent as any);
|
|
238
|
+
|
|
239
|
+
// Verify updateSettings was called with the complex nested object
|
|
240
|
+
expect(updateSettings).toHaveBeenCalledWith(
|
|
241
|
+
expect.objectContaining({
|
|
242
|
+
...initialAgent.settings,
|
|
243
|
+
...templateAgent.settings,
|
|
244
|
+
})
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// Verify custom field was updated
|
|
248
|
+
expect(updateField).toHaveBeenCalledWith('customField', templateAgent.customField);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, it, expect, mock } from 'bun:test';
|
|
2
|
+
import { useConvertCharacter } from '../use-character-convert';
|
|
3
|
+
|
|
4
|
+
// Mock the usePlugins hook
|
|
5
|
+
const mockUsePlugins = mock();
|
|
6
|
+
|
|
7
|
+
mock.module('../use-plugins', () => ({
|
|
8
|
+
usePlugins: mockUsePlugins,
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
describe('useConvertCharacter', () => {
|
|
12
|
+
it('should only include plugins that exist in availablePlugins', () => {
|
|
13
|
+
// Set up mock return value
|
|
14
|
+
mockUsePlugins.mockImplementation(() => ({
|
|
15
|
+
data: ['@elizaos/plugin-sql', '@elizaos/plugin-bootstrap', '@elizaos/plugin-discord'],
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
const { convertCharacter } = useConvertCharacter();
|
|
19
|
+
|
|
20
|
+
const v1Character = {
|
|
21
|
+
name: 'Test Character',
|
|
22
|
+
clients: ['discord'],
|
|
23
|
+
modelProvider: 'google',
|
|
24
|
+
bio: ['Test bio'],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const result = convertCharacter(v1Character);
|
|
28
|
+
|
|
29
|
+
// Should only include plugins that exist in availablePlugins
|
|
30
|
+
expect(result.plugins).toEqual([
|
|
31
|
+
'@elizaos/plugin-bootstrap',
|
|
32
|
+
'@elizaos/plugin-discord',
|
|
33
|
+
'@elizaos/plugin-sql',
|
|
34
|
+
]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should not include non-existent plugins', () => {
|
|
38
|
+
// Set up mock return value with limited set
|
|
39
|
+
mockUsePlugins.mockImplementation(() => ({
|
|
40
|
+
data: ['@elizaos/plugin-sql'], // Only sql plugin available
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
const { convertCharacter } = useConvertCharacter();
|
|
44
|
+
|
|
45
|
+
const v1Character = {
|
|
46
|
+
name: 'Test Character',
|
|
47
|
+
clients: ['discord', 'slack'], // These plugins don't exist
|
|
48
|
+
modelProvider: 'openai', // This plugin doesn't exist
|
|
49
|
+
bio: ['Test bio'],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const result = convertCharacter(v1Character);
|
|
53
|
+
|
|
54
|
+
// Should only include plugins that actually exist
|
|
55
|
+
expect(result.plugins).toEqual(['@elizaos/plugin-sql']);
|
|
56
|
+
expect(result.plugins).not.toContain('@elizaos/plugin-discord');
|
|
57
|
+
expect(result.plugins).not.toContain('@elizaos/plugin-slack');
|
|
58
|
+
expect(result.plugins).not.toContain('@elizaos/plugin-openai');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should handle empty availablePlugins gracefully', () => {
|
|
62
|
+
// Set up mock return value with empty array
|
|
63
|
+
mockUsePlugins.mockImplementation(() => ({
|
|
64
|
+
data: [],
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
const { convertCharacter } = useConvertCharacter();
|
|
68
|
+
|
|
69
|
+
const v1Character = {
|
|
70
|
+
name: 'Test Character',
|
|
71
|
+
clients: ['discord'],
|
|
72
|
+
modelProvider: 'google',
|
|
73
|
+
bio: ['Test bio'],
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const result = convertCharacter(v1Character);
|
|
77
|
+
|
|
78
|
+
// Should return empty plugins array when no plugins are available
|
|
79
|
+
expect(result.plugins).toEqual([]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should handle mapped plugins correctly', () => {
|
|
83
|
+
// Set up mock return value including mapped ones
|
|
84
|
+
mockUsePlugins.mockImplementation(() => ({
|
|
85
|
+
data: ['@elizaos/plugin-google-genai', '@elizaos/plugin-sql'],
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
const { convertCharacter } = useConvertCharacter();
|
|
89
|
+
|
|
90
|
+
const v1Character = {
|
|
91
|
+
name: 'Test Character',
|
|
92
|
+
modelProvider: 'google', // This should map to @elizaos/plugin-google-genai
|
|
93
|
+
bio: ['Test bio'],
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const result = convertCharacter(v1Character);
|
|
97
|
+
|
|
98
|
+
// Should include the mapped plugin
|
|
99
|
+
expect(result.plugins).toContain('@elizaos/plugin-google-genai');
|
|
100
|
+
expect(result.plugins).toContain('@elizaos/plugin-sql');
|
|
101
|
+
});
|
|
102
|
+
});
|