@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,243 @@
|
|
|
1
|
+
import { renderHook, act, waitFor } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect, beforeEach, mock } from 'bun:test';
|
|
3
|
+
import { usePanelWidthState } from '../use-panel-width-state';
|
|
4
|
+
|
|
5
|
+
// Mock clientLogger
|
|
6
|
+
mock.module('../../lib/logger', () => ({
|
|
7
|
+
default: {
|
|
8
|
+
error: mock(),
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
describe('usePanelWidthState', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// Clear localStorage before each test
|
|
15
|
+
localStorage.clear();
|
|
16
|
+
|
|
17
|
+
// Reset window dimensions (above 1400 threshold)
|
|
18
|
+
Object.defineProperty(window, 'innerWidth', {
|
|
19
|
+
writable: true,
|
|
20
|
+
configurable: true,
|
|
21
|
+
value: 1500,
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should initialize with default values when localStorage is empty', () => {
|
|
26
|
+
const { result } = renderHook(() => usePanelWidthState());
|
|
27
|
+
|
|
28
|
+
expect(result.current.mainPanelSize).toBe(70);
|
|
29
|
+
expect(result.current.sidebarPanelSize).toBe(30);
|
|
30
|
+
expect(result.current.isFloatingMode).toBe(false);
|
|
31
|
+
expect(result.current.floatingThreshold).toBe(1400);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should load persisted values from localStorage on mount', async () => {
|
|
35
|
+
// Set up localStorage with saved values (use correct keys)
|
|
36
|
+
localStorage.setItem('eliza-main-panel-width', '65');
|
|
37
|
+
localStorage.setItem('eliza-sidebar-panel-width', '35');
|
|
38
|
+
|
|
39
|
+
const { result } = renderHook(() => usePanelWidthState());
|
|
40
|
+
|
|
41
|
+
await waitFor(() => {
|
|
42
|
+
expect(result.current.mainPanelSize).toBe(65);
|
|
43
|
+
expect(result.current.sidebarPanelSize).toBe(35);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should persist main panel size to localStorage when updated', async () => {
|
|
48
|
+
const { result } = renderHook(() => usePanelWidthState());
|
|
49
|
+
|
|
50
|
+
act(() => {
|
|
51
|
+
result.current.setMainPanelSize(75);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await waitFor(() => {
|
|
55
|
+
expect(result.current.mainPanelSize).toBe(75);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(localStorage.getItem('eliza-main-panel-width')).toBe('75');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should persist sidebar panel size to localStorage when updated', async () => {
|
|
62
|
+
const { result } = renderHook(() => usePanelWidthState());
|
|
63
|
+
|
|
64
|
+
act(() => {
|
|
65
|
+
result.current.setSidebarPanelSize(25);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await waitFor(() => {
|
|
69
|
+
expect(result.current.sidebarPanelSize).toBe(25);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(localStorage.getItem('eliza-sidebar-panel-width')).toBe('25');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should validate and clamp panel sizes within bounds', async () => {
|
|
76
|
+
const { result } = renderHook(() => usePanelWidthState());
|
|
77
|
+
|
|
78
|
+
// Test main panel size clamping
|
|
79
|
+
act(() => {
|
|
80
|
+
result.current.setMainPanelSize(5); // Below minimum (20)
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await waitFor(() => {
|
|
84
|
+
expect(result.current.mainPanelSize).toBe(20);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
act(() => {
|
|
88
|
+
result.current.setMainPanelSize(95); // Above maximum (80)
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
await waitFor(() => {
|
|
92
|
+
expect(result.current.mainPanelSize).toBe(80);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Test sidebar panel size clamping
|
|
96
|
+
act(() => {
|
|
97
|
+
result.current.setSidebarPanelSize(15); // Below minimum (20)
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await waitFor(() => {
|
|
101
|
+
expect(result.current.sidebarPanelSize).toBe(20);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
act(() => {
|
|
105
|
+
result.current.setSidebarPanelSize(85); // Above maximum (80)
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
await waitFor(() => {
|
|
109
|
+
expect(result.current.sidebarPanelSize).toBe(80);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should reset panel sizes to defaults and clear localStorage', () => {
|
|
114
|
+
// Set up some custom values first
|
|
115
|
+
localStorage.setItem('eliza-main-panel-width', '60');
|
|
116
|
+
localStorage.setItem('eliza-sidebar-panel-width', '40');
|
|
117
|
+
|
|
118
|
+
const { result } = renderHook(() => usePanelWidthState());
|
|
119
|
+
|
|
120
|
+
// Verify initial persisted values
|
|
121
|
+
expect(result.current.mainPanelSize).toBe(60);
|
|
122
|
+
expect(result.current.sidebarPanelSize).toBe(40);
|
|
123
|
+
|
|
124
|
+
// Reset to defaults
|
|
125
|
+
act(() => {
|
|
126
|
+
result.current.resetPanelSizes();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(result.current.mainPanelSize).toBe(70);
|
|
130
|
+
expect(result.current.sidebarPanelSize).toBe(30);
|
|
131
|
+
expect(localStorage.getItem('eliza-main-panel-width')).toBe(null);
|
|
132
|
+
expect(localStorage.getItem('eliza-sidebar-panel-width')).toBe(null);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should handle localStorage errors gracefully when reading', () => {
|
|
136
|
+
// Set up invalid values
|
|
137
|
+
localStorage.setItem('eliza-main-panel-width', 'invalid');
|
|
138
|
+
localStorage.setItem('eliza-sidebar-panel-width', 'invalid');
|
|
139
|
+
|
|
140
|
+
const { result } = renderHook(() => usePanelWidthState());
|
|
141
|
+
|
|
142
|
+
// Should fall back to defaults
|
|
143
|
+
expect(result.current.mainPanelSize).toBe(70);
|
|
144
|
+
expect(result.current.sidebarPanelSize).toBe(30);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should handle localStorage errors gracefully when writing', () => {
|
|
148
|
+
const { result } = renderHook(() => usePanelWidthState());
|
|
149
|
+
|
|
150
|
+
// Mock localStorage to throw an error on setItem
|
|
151
|
+
const originalSetItem = localStorage.setItem;
|
|
152
|
+
localStorage.setItem = () => {
|
|
153
|
+
throw new Error('localStorage save error');
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Should still update state even if localStorage fails
|
|
157
|
+
act(() => {
|
|
158
|
+
result.current.setMainPanelSize(65);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expect(result.current.mainPanelSize).toBe(65);
|
|
162
|
+
|
|
163
|
+
// Restore original setItem immediately
|
|
164
|
+
localStorage.setItem = originalSetItem;
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should handle invalid JSON values from localStorage', () => {
|
|
168
|
+
localStorage.setItem('eliza-main-panel-width', 'not-a-number');
|
|
169
|
+
localStorage.setItem('eliza-sidebar-panel-width', 'also-not-a-number');
|
|
170
|
+
|
|
171
|
+
const { result } = renderHook(() => usePanelWidthState());
|
|
172
|
+
|
|
173
|
+
// Should use defaults when values can't be parsed
|
|
174
|
+
expect(result.current.mainPanelSize).toBe(70);
|
|
175
|
+
expect(result.current.sidebarPanelSize).toBe(30);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should handle out-of-range values from localStorage', () => {
|
|
179
|
+
localStorage.setItem('eliza-main-panel-width', '150'); // Way too high (> 100)
|
|
180
|
+
localStorage.setItem('eliza-sidebar-panel-width', '0'); // Way too low (<= 0)
|
|
181
|
+
|
|
182
|
+
const { result } = renderHook(() => usePanelWidthState());
|
|
183
|
+
|
|
184
|
+
// Should fall back to defaults when values are out of valid loading range
|
|
185
|
+
expect(result.current.mainPanelSize).toBe(70); // Default value
|
|
186
|
+
expect(result.current.sidebarPanelSize).toBe(30); // Default value
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should detect floating mode based on window width', () => {
|
|
190
|
+
// Set window width below threshold
|
|
191
|
+
Object.defineProperty(window, 'innerWidth', {
|
|
192
|
+
writable: true,
|
|
193
|
+
configurable: true,
|
|
194
|
+
value: 1300,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const { result } = renderHook(() => usePanelWidthState());
|
|
198
|
+
|
|
199
|
+
expect(result.current.isFloatingMode).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should toggle floating mode manually', () => {
|
|
203
|
+
const { result } = renderHook(() => usePanelWidthState());
|
|
204
|
+
|
|
205
|
+
expect(result.current.isFloatingMode).toBe(false);
|
|
206
|
+
|
|
207
|
+
act(() => {
|
|
208
|
+
result.current.toggleFloatingMode();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(result.current.isFloatingMode).toBe(true);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should update floating threshold and persist to localStorage', () => {
|
|
215
|
+
const { result } = renderHook(() => usePanelWidthState());
|
|
216
|
+
|
|
217
|
+
act(() => {
|
|
218
|
+
result.current.setFloatingThreshold(900);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
expect(result.current.floatingThreshold).toBe(900);
|
|
222
|
+
expect(localStorage.getItem('eliza-floating-threshold')).toBe('900');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should respond to window resize events', () => {
|
|
226
|
+
const { result } = renderHook(() => usePanelWidthState());
|
|
227
|
+
|
|
228
|
+
// Initially not in floating mode (width 1500 > 1400)
|
|
229
|
+
expect(result.current.isFloatingMode).toBe(false);
|
|
230
|
+
|
|
231
|
+
act(() => {
|
|
232
|
+
// Simulate window resize to trigger floating mode
|
|
233
|
+
Object.defineProperty(window, 'innerWidth', {
|
|
234
|
+
writable: true,
|
|
235
|
+
configurable: true,
|
|
236
|
+
value: 1300,
|
|
237
|
+
});
|
|
238
|
+
window.dispatchEvent(new Event('resize'));
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
expect(result.current.isFloatingMode).toBe(true);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { renderHook, act, waitFor } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect, beforeEach, mock } from 'bun:test';
|
|
3
|
+
import { useSidebarState } from '../use-sidebar-state';
|
|
4
|
+
|
|
5
|
+
// Mock clientLogger
|
|
6
|
+
mock.module('../../lib/logger', () => ({
|
|
7
|
+
default: {
|
|
8
|
+
error: mock(),
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
describe('useSidebarState', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// Clear localStorage before each test
|
|
15
|
+
localStorage.clear();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should initialize with false when no stored state exists', () => {
|
|
19
|
+
const { result } = renderHook(() => useSidebarState());
|
|
20
|
+
|
|
21
|
+
expect(result.current.isVisible).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should load stored state when it exists', async () => {
|
|
25
|
+
localStorage.setItem('eliza-agent-sidebar-visible', 'true');
|
|
26
|
+
|
|
27
|
+
const { result } = renderHook(() => useSidebarState());
|
|
28
|
+
|
|
29
|
+
await waitFor(() => {
|
|
30
|
+
expect(result.current.isVisible).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should save state when setSidebarVisible is called', async () => {
|
|
35
|
+
const { result } = renderHook(() => useSidebarState());
|
|
36
|
+
|
|
37
|
+
act(() => {
|
|
38
|
+
result.current.setSidebarVisible(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await waitFor(() => {
|
|
42
|
+
expect(result.current.isVisible).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(localStorage.getItem('eliza-agent-sidebar-visible')).toBe('true');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should toggle state when toggleSidebar is called', async () => {
|
|
49
|
+
const { result } = renderHook(() => useSidebarState());
|
|
50
|
+
|
|
51
|
+
// Initially false
|
|
52
|
+
expect(result.current.isVisible).toBe(false);
|
|
53
|
+
|
|
54
|
+
// Toggle to true
|
|
55
|
+
act(() => {
|
|
56
|
+
result.current.toggleSidebar();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
await waitFor(() => {
|
|
60
|
+
expect(result.current.isVisible).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(localStorage.getItem('eliza-agent-sidebar-visible')).toBe('true');
|
|
64
|
+
|
|
65
|
+
// Toggle back to false
|
|
66
|
+
act(() => {
|
|
67
|
+
result.current.toggleSidebar();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await waitFor(() => {
|
|
71
|
+
expect(result.current.isVisible).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(localStorage.getItem('eliza-agent-sidebar-visible')).toBe('false');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should handle localStorage errors gracefully', () => {
|
|
78
|
+
// Set up invalid JSON
|
|
79
|
+
localStorage.setItem('eliza-agent-sidebar-visible', 'invalid-json');
|
|
80
|
+
|
|
81
|
+
const { result } = renderHook(() => useSidebarState());
|
|
82
|
+
|
|
83
|
+
// Should default to false when JSON parsing fails
|
|
84
|
+
expect(result.current.isVisible).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should handle localStorage save errors gracefully', async () => {
|
|
88
|
+
// Mock localStorage to throw an error on setItem
|
|
89
|
+
const originalSetItem = localStorage.setItem;
|
|
90
|
+
localStorage.setItem = () => {
|
|
91
|
+
throw new Error('localStorage save error');
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const { result } = renderHook(() => useSidebarState());
|
|
95
|
+
|
|
96
|
+
// Should still update state even if localStorage fails
|
|
97
|
+
act(() => {
|
|
98
|
+
result.current.setSidebarVisible(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
await waitFor(() => {
|
|
102
|
+
expect(result.current.isVisible).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Restore original setItem
|
|
106
|
+
localStorage.setItem = originalSetItem;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should handle invalid JSON in localStorage', () => {
|
|
110
|
+
localStorage.setItem('eliza-agent-sidebar-visible', 'invalid-json');
|
|
111
|
+
|
|
112
|
+
const { result } = renderHook(() => useSidebarState());
|
|
113
|
+
|
|
114
|
+
// Should default to false when JSON parsing fails
|
|
115
|
+
expect(result.current.isVisible).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { Agent, UUID } from '@elizaos/core';
|
|
2
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { useNavigate } from 'react-router-dom';
|
|
5
|
+
import { useStartAgent, useStopAgent } from './use-query-hooks';
|
|
6
|
+
import { useToast } from './use-toast';
|
|
7
|
+
// Direct error handling
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Custom hook for managing agents (starting, stopping, and tracking status)
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Custom hook for agent management.
|
|
14
|
+
* Allows starting and stopping agents with mutation operations.
|
|
15
|
+
* Provides functions to check if an agent is currently starting or stopping.
|
|
16
|
+
* @returns Object with functions for starting and stopping agents, checking agent status, and lists of agents in starting and stopping processes.
|
|
17
|
+
*/
|
|
18
|
+
export function useAgentManagement() {
|
|
19
|
+
const navigate = useNavigate();
|
|
20
|
+
const queryClient = useQueryClient();
|
|
21
|
+
const { toast } = useToast();
|
|
22
|
+
|
|
23
|
+
// Mutations for starting and stopping agents
|
|
24
|
+
const startAgentMutation = useStartAgent();
|
|
25
|
+
const stopAgentMutation = useStopAgent();
|
|
26
|
+
|
|
27
|
+
// Track agents that are currently in the process of starting or stopping
|
|
28
|
+
const [startingAgents, setStartingAgents] = useState<UUID[]>([]);
|
|
29
|
+
const [stoppingAgents, setStoppingAgents] = useState<UUID[]>([]);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Start an agent and navigate to its chat
|
|
33
|
+
*/
|
|
34
|
+
const startAgent = async (agent: Agent) => {
|
|
35
|
+
if (!agent.id) {
|
|
36
|
+
toast({
|
|
37
|
+
title: 'Error',
|
|
38
|
+
description: 'Agent ID is missing',
|
|
39
|
+
variant: 'destructive',
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const agentId = agent.id as UUID;
|
|
45
|
+
|
|
46
|
+
// Prevent starting if already in progress
|
|
47
|
+
if (startingAgents.includes(agentId)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// Add agent to starting list
|
|
53
|
+
setStartingAgents((prev) => [...prev, agentId]);
|
|
54
|
+
|
|
55
|
+
// Start the agent
|
|
56
|
+
await startAgentMutation.mutateAsync(agentId);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('Failed to start agent:', error);
|
|
59
|
+
// Let the mutation's onError handler show the appropriate toast
|
|
60
|
+
} finally {
|
|
61
|
+
// Remove agent from starting list regardless of success/failure
|
|
62
|
+
setStartingAgents((prev) => prev.filter((id) => id !== agentId));
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Stop an agent
|
|
68
|
+
*/
|
|
69
|
+
const stopAgent = async (agent: Agent) => {
|
|
70
|
+
if (!agent.id) {
|
|
71
|
+
toast({
|
|
72
|
+
title: 'Error',
|
|
73
|
+
description: 'Agent ID is missing',
|
|
74
|
+
variant: 'destructive',
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const agentId = agent.id as UUID;
|
|
80
|
+
|
|
81
|
+
// Prevent stopping if already in progress
|
|
82
|
+
if (stoppingAgents.includes(agentId)) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
// Add agent to stopping list
|
|
88
|
+
setStoppingAgents((prev) => [...prev, agentId]);
|
|
89
|
+
|
|
90
|
+
// Stop the agent
|
|
91
|
+
await stopAgentMutation.mutateAsync(agentId);
|
|
92
|
+
|
|
93
|
+
toast({
|
|
94
|
+
title: 'Agent Stopped',
|
|
95
|
+
description: `${agent.name} has been stopped`,
|
|
96
|
+
});
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error('Failed to stop agent:', error);
|
|
99
|
+
// Let the mutation's onError handler show the appropriate toast
|
|
100
|
+
} finally {
|
|
101
|
+
// Remove agent from stopping list regardless of success/failure
|
|
102
|
+
setStoppingAgents((prev) => prev.filter((id) => id !== agentId));
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check if an agent is currently starting
|
|
108
|
+
*/
|
|
109
|
+
const isAgentStarting = (agentId: UUID | undefined | null) => {
|
|
110
|
+
if (!agentId) return false;
|
|
111
|
+
return startingAgents.includes(agentId);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Check if an agent is currently stopping
|
|
116
|
+
*/
|
|
117
|
+
const isAgentStopping = (agentId: UUID | undefined | null) => {
|
|
118
|
+
if (!agentId) return false;
|
|
119
|
+
return stoppingAgents.includes(agentId);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
startAgent,
|
|
124
|
+
stopAgent,
|
|
125
|
+
isAgentStarting,
|
|
126
|
+
isAgentStopping,
|
|
127
|
+
startingAgents,
|
|
128
|
+
stoppingAgents,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import type { UUID } from '@elizaos/core';
|
|
3
|
+
import clientLogger from '@/lib/logger';
|
|
4
|
+
|
|
5
|
+
// Key for storing agent tab states in localStorage
|
|
6
|
+
const AGENT_TAB_STATE_KEY = 'eliza-agent-tab-states';
|
|
7
|
+
|
|
8
|
+
type TabValue = 'details' | 'actions' | 'logs' | 'memories' | string;
|
|
9
|
+
|
|
10
|
+
interface AgentTabStates {
|
|
11
|
+
[agentId: string]: TabValue;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Custom hook to manage agent sidebar tab state with localStorage persistence
|
|
16
|
+
* Each agent remembers its last selected tab when switching between agents
|
|
17
|
+
*/
|
|
18
|
+
export function useAgentTabState(agentId: UUID | undefined) {
|
|
19
|
+
const [currentTab, setCurrentTab] = useState<TabValue>('details');
|
|
20
|
+
|
|
21
|
+
// Load tab states from localStorage
|
|
22
|
+
const getStoredTabStates = useCallback((): AgentTabStates => {
|
|
23
|
+
try {
|
|
24
|
+
const stored = localStorage.getItem(AGENT_TAB_STATE_KEY);
|
|
25
|
+
return stored ? JSON.parse(stored) : {};
|
|
26
|
+
} catch (error) {
|
|
27
|
+
clientLogger.error('Error reading agent tab states from localStorage:', error);
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
// Save tab states to localStorage
|
|
33
|
+
const saveTabStates = useCallback((states: AgentTabStates) => {
|
|
34
|
+
try {
|
|
35
|
+
localStorage.setItem(AGENT_TAB_STATE_KEY, JSON.stringify(states));
|
|
36
|
+
} catch (error) {
|
|
37
|
+
clientLogger.error('Error saving agent tab states to localStorage:', error);
|
|
38
|
+
}
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
// Load the tab state for the current agent when agentId changes
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!agentId) {
|
|
44
|
+
setCurrentTab('details');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const storedStates = getStoredTabStates();
|
|
49
|
+
const agentTabState = storedStates[agentId] || 'details';
|
|
50
|
+
setCurrentTab(agentTabState);
|
|
51
|
+
}, [agentId, getStoredTabStates]);
|
|
52
|
+
|
|
53
|
+
// Update tab state and persist to localStorage
|
|
54
|
+
const updateTab = useCallback(
|
|
55
|
+
(newTab: TabValue) => {
|
|
56
|
+
setCurrentTab(newTab);
|
|
57
|
+
|
|
58
|
+
if (agentId) {
|
|
59
|
+
const storedStates = getStoredTabStates();
|
|
60
|
+
const updatedStates = {
|
|
61
|
+
...storedStates,
|
|
62
|
+
[agentId]: newTab,
|
|
63
|
+
};
|
|
64
|
+
saveTabStates(updatedStates);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
[agentId, getStoredTabStates, saveTabStates]
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
currentTab,
|
|
72
|
+
setTab: updateTab,
|
|
73
|
+
};
|
|
74
|
+
}
|