@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,247 @@
1
+ import { useEffect, useState } from 'react';
2
+ import Joyride, { CallBackProps, STATUS, ACTIONS } from 'react-joyride';
3
+ import { useOnboarding } from '@/hooks/use-onboarding';
4
+ import { useLocation } from 'react-router-dom';
5
+
6
+ export default function OnboardingTour() {
7
+ const { onboardingCompleted, completeOnboarding } = useOnboarding();
8
+ const [run, setRun] = useState(false);
9
+ const location = useLocation();
10
+ const [tourStep, setTourStep] = useState(0);
11
+
12
+ // Home page tour steps
13
+ const homeSteps = [
14
+ {
15
+ target: '.sidebar-logo',
16
+ content:
17
+ 'Welcome to ElizaOS! This is your gateway to creating and managing intelligent AI agents.',
18
+ disableBeacon: true,
19
+ title: 'Welcome to ElizaOS',
20
+ },
21
+ {
22
+ target: '.sidebar-create-button',
23
+ content:
24
+ 'This is your main creation button. Click here to create new agents or rooms quickly.',
25
+ title: 'Create Button',
26
+ },
27
+ {
28
+ target: '.sidebar-online-section, .sidebar-offline-section',
29
+ content:
30
+ 'Your agents are organized here. Online agents (green dot) are ready to chat, while offline agents need to be activated first.',
31
+ title: 'Your Agents',
32
+ },
33
+ {
34
+ target: '.sidebar-groups-section',
35
+ content:
36
+ 'Access your agent groups from here to see collaborative conversations between multiple agents.',
37
+ title: 'Groups Navigation',
38
+ },
39
+ {
40
+ target: '.sidebar-docs-button, .sidebar-logs-button, .sidebar-settings-button',
41
+ content: 'Access documentation, system logs, and global settings from these utility buttons.',
42
+ title: 'System Utilities',
43
+ },
44
+ {
45
+ target: '.sidebar-connection-status',
46
+ content: 'This indicator shows your connection status to the ElizaOS server.',
47
+ title: 'Connection Status',
48
+ },
49
+ {
50
+ target: '.agents-section',
51
+ content:
52
+ 'This is your main workspace showing all your agents. Active agents have a green dot, while inactive ones appear grayed out with an "Offline" label.',
53
+ title: 'Agent Dashboard',
54
+ },
55
+ {
56
+ target: '.message-button, .start-button',
57
+ content:
58
+ 'Click "Message" to chat with active agents, or "Start" to activate offline agents first.',
59
+ title: 'Agent Interaction',
60
+ },
61
+ {
62
+ target: '.agent-info-button, .agent-settings-button',
63
+ content: 'View agent details or configure settings using these buttons on each agent card.',
64
+ title: 'Agent Management',
65
+ },
66
+ {
67
+ target: '.groups-section',
68
+ content:
69
+ 'This section displays your agent groups for collaborative interactions between multiple agents.',
70
+ title: 'Agent Groups',
71
+ },
72
+ {
73
+ target: '.groups-create-button',
74
+ content:
75
+ 'Create a new group by clicking this button. Groups allow multiple agents to interact together.',
76
+ title: 'Create New Group',
77
+ },
78
+ {
79
+ target: '.create-agent-button',
80
+ content: "Let's create your first agent! Click this button to get started.",
81
+ title: 'Create Your First Agent',
82
+ spotlightClicks: true,
83
+ },
84
+ ];
85
+
86
+ const createAgentSteps = [
87
+ {
88
+ target: '.agent-form-name',
89
+ content: 'Give your agent a name that reflects its purpose or personality.',
90
+ title: 'Name Your Agent',
91
+ disableBeacon: true,
92
+ },
93
+ {
94
+ target: '.agent-form-system-prompt',
95
+ content:
96
+ "The system prompt defines your agent's behavior and capabilities. This is the most important field for shaping how your agent will respond.",
97
+ title: 'Define Agent Behavior',
98
+ },
99
+ {
100
+ target: '.tabs-list',
101
+ content:
102
+ 'Switch between these tabs to configure different aspects of your agent such as content, style, plugins, and appearance.',
103
+ title: 'Configuration Tabs',
104
+ },
105
+ {
106
+ target: '.agent-form-submit',
107
+ content: 'Click Save to create your agent and return to the dashboard.',
108
+ title: 'Save Your Agent',
109
+ spotlightClicks: true,
110
+ },
111
+ ];
112
+
113
+ // Select which steps to show based on current route
114
+ const steps =
115
+ location.pathname === '/' ? homeSteps : location.pathname === '/create' ? createAgentSteps : [];
116
+
117
+ // Start the tour when on the home page and not completed
118
+ useEffect(() => {
119
+ if (location.pathname === '/' && !onboardingCompleted && tourStep === 0) {
120
+ // Small delay to ensure DOM elements are loaded
121
+ const timer = setTimeout(() => {
122
+ setRun(true);
123
+ }, 1000);
124
+
125
+ return () => clearTimeout(timer);
126
+ } else if (location.pathname === '/create' && !onboardingCompleted && tourStep === 1) {
127
+ // Continue the tour on the create page
128
+ const timer = setTimeout(() => {
129
+ setRun(true);
130
+ }, 1000);
131
+
132
+ return () => clearTimeout(timer);
133
+ } else {
134
+ setRun(false);
135
+ }
136
+ }, [location.pathname, onboardingCompleted, tourStep]);
137
+
138
+ // Handle tour events
139
+ const handleJoyrideCallback = (data: CallBackProps) => {
140
+ const { status, index, type, action } = data;
141
+
142
+ // If we finished the home page tour, update the tour step
143
+ if (location.pathname === '/' && type === 'step:after' && index === homeSteps.length - 1) {
144
+ setTourStep(1);
145
+ setRun(false);
146
+ }
147
+
148
+ // Tour is finished or skipped
149
+ if (status === STATUS.FINISHED || status === STATUS.SKIPPED || action === ACTIONS.CLOSE) {
150
+ completeOnboarding();
151
+ setRun(false);
152
+ }
153
+ };
154
+
155
+ // Don't render if onboarding is completed or no steps
156
+ if (onboardingCompleted || steps.length === 0) {
157
+ return null;
158
+ }
159
+
160
+ return (
161
+ <Joyride
162
+ callback={handleJoyrideCallback}
163
+ continuous
164
+ run={run}
165
+ scrollToFirstStep
166
+ showProgress
167
+ showSkipButton
168
+ steps={steps}
169
+ floaterProps={{
170
+ disableAnimation: false,
171
+ styles: {
172
+ floater: {
173
+ filter: 'drop-shadow(0 10px 15px rgba(0, 0, 0, 0.25))',
174
+ },
175
+ },
176
+ }}
177
+ styles={{
178
+ options: {
179
+ primaryColor: '#3b82f6',
180
+ backgroundColor: '#1f2937',
181
+ textColor: '#f3f4f6',
182
+ overlayColor: 'rgba(0, 0, 0, 0.6)',
183
+ zIndex: 9999,
184
+ arrowColor: '#1f2937',
185
+ beaconSize: 36,
186
+ },
187
+ tooltip: {
188
+ fontSize: '14px',
189
+ padding: '16px',
190
+ borderRadius: '10px',
191
+ boxShadow: '0 10px 25px rgba(0, 0, 0, 0.3)',
192
+ },
193
+ tooltipContainer: {
194
+ textAlign: 'left',
195
+ },
196
+ tooltipTitle: {
197
+ fontSize: '16px',
198
+ fontWeight: '600',
199
+ marginBottom: '8px',
200
+ },
201
+ tooltipContent: {
202
+ padding: '5px 0',
203
+ fontSize: '14px',
204
+ lineHeight: '1.5',
205
+ },
206
+ buttonClose: {
207
+ color: '#9ca3af',
208
+ opacity: 0.8,
209
+ },
210
+ buttonBack: {
211
+ color: '#9ca3af',
212
+ fontSize: '14px',
213
+ fontWeight: '500',
214
+ padding: '8px 12px',
215
+ borderRadius: '6px',
216
+ marginRight: '8px',
217
+ transition: 'background-color 0.2s ease',
218
+ },
219
+ buttonNext: {
220
+ backgroundColor: '#3b82f6',
221
+ fontSize: '14px',
222
+ fontWeight: '500',
223
+ padding: '8px 16px',
224
+ borderRadius: '6px',
225
+ color: 'white',
226
+ border: 'none',
227
+ boxShadow: '0 4px 6px rgba(59, 130, 246, 0.3)',
228
+ transition: 'all 0.2s ease',
229
+ },
230
+ buttonSkip: {
231
+ color: '#9ca3af',
232
+ fontSize: '14px',
233
+ padding: '8px 12px',
234
+ borderRadius: '6px',
235
+ transition: 'background-color 0.2s ease',
236
+ },
237
+ spotlight: {
238
+ borderRadius: '8px',
239
+ backgroundColor: 'transparent',
240
+ },
241
+ overlay: {
242
+ backgroundColor: 'rgba(0, 0, 0, 0.7)',
243
+ },
244
+ }}
245
+ />
246
+ );
247
+ }
@@ -0,0 +1,8 @@
1
+ export default function PageTitle({ title, subtitle }: { title: string; subtitle?: string }) {
2
+ return (
3
+ <div className="space-y-0.5">
4
+ <h2 className="text-2xl font-bold tracking-tight">{title}</h2>
5
+ {subtitle ? <p className="text-muted-foreground">{subtitle}</p> : null}
6
+ </div>
7
+ );
8
+ }
@@ -0,0 +1,383 @@
1
+ import {
2
+ AlertDialog,
3
+ AlertDialogAction,
4
+ AlertDialogCancel,
5
+ AlertDialogContent,
6
+ AlertDialogDescription,
7
+ AlertDialogFooter,
8
+ AlertDialogHeader,
9
+ AlertDialogTitle,
10
+ } from '@/components/ui/alert-dialog';
11
+ import {
12
+ Dialog,
13
+ DialogContent,
14
+ DialogHeader,
15
+ DialogTitle,
16
+ DialogTrigger,
17
+ } from '@/components/ui/dialog';
18
+ import { Input } from '@/components/ui/input';
19
+ import { usePlugins } from '@/hooks/use-plugins';
20
+ import { useToast } from '@/hooks/use-toast';
21
+ import type { Agent } from '@elizaos/core';
22
+ import clsx from 'clsx';
23
+ import { CircleAlert } from 'lucide-react';
24
+ import { useMemo, useState } from 'react';
25
+ import {
26
+ // getAllRequiredPlugins,
27
+ getVoiceModelByValue,
28
+ providerPluginMap,
29
+ } from '../config/voice-models';
30
+ import { Button } from './ui/button';
31
+
32
+ interface PluginsPanelProps {
33
+ characterValue: Agent;
34
+ setCharacterValue: {
35
+ addPlugin?: (pluginId: string) => void;
36
+ removePlugin?: (index: number) => void;
37
+ setPlugins?: (plugins: string[]) => void;
38
+ updateField?: <T>(path: string, value: T) => void;
39
+ [key: string]: any;
40
+ };
41
+ initialPlugins?: string[];
42
+ }
43
+
44
+ // Define a type for the essential plugin information
45
+ type EssentialPluginInfo = {
46
+ title: string;
47
+ description: string;
48
+ };
49
+
50
+ // Map of essential plugins that require confirmation when removing
51
+ const ESSENTIAL_PLUGINS: Record<string, EssentialPluginInfo> = {
52
+ '@elizaos/plugin-sql': {
53
+ title: 'Essential Plugin: SQL',
54
+ description:
55
+ 'Provides memory and state storage. If removed, replace with an adapter plugin or your agent may lose conversation history and memory capabilities.',
56
+ },
57
+ '@elizaos/plugin-openai': {
58
+ title: 'Essential Plugin: OpenAI',
59
+ description:
60
+ 'Provides language model access. If removed, replace with another LLM plugin or your agent may fail to function properly.',
61
+ },
62
+ '@elizaos/plugin-bootstrap': {
63
+ title: 'Essential Plugin: Bootstrap',
64
+ description:
65
+ 'Provides default message processing, event handling, and attachment workflows for your agent. If removed, ensure you have a custom plugin handling these responsibilities, or your agent may not process events or respond to messages as expected.',
66
+ },
67
+ };
68
+
69
+ export default function PluginsPanel({
70
+ characterValue,
71
+ setCharacterValue,
72
+ initialPlugins,
73
+ }: PluginsPanelProps) {
74
+ const { data: plugins, error } = usePlugins();
75
+ const { toast } = useToast();
76
+ const [searchQuery, setSearchQuery] = useState('');
77
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
78
+ const [pendingRemoval, setPendingRemoval] = useState<string | null>(null);
79
+ const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false);
80
+
81
+ // Ensure we always have arrays and normalize plugin names
82
+ const safeCharacterPlugins = useMemo(() => {
83
+ if (!Array.isArray(characterValue?.plugins)) return [];
84
+ return characterValue.plugins;
85
+ }, [characterValue?.plugins]);
86
+
87
+ // Get plugin names from available plugins
88
+ const pluginNames = useMemo(() => {
89
+ const defaultPlugins = ['@elizaos/plugin-sql'];
90
+ if (!plugins) return defaultPlugins;
91
+ return [
92
+ ...defaultPlugins,
93
+ ...(Array.isArray(plugins) ? plugins : Object.keys(plugins)).filter(
94
+ (name) => !defaultPlugins.includes(name)
95
+ ),
96
+ ];
97
+ }, [plugins]);
98
+
99
+ // Check if the selected voice model requires specific plugins
100
+ const voiceModelPluginInfo = useMemo(() => {
101
+ const settings = characterValue?.settings;
102
+ if (!settings || typeof settings !== 'object' || Array.isArray(settings)) return null;
103
+
104
+ const voice = settings.voice;
105
+ if (!voice || typeof voice !== 'object' || Array.isArray(voice)) return null;
106
+
107
+ const voiceModelValue = voice.model;
108
+ if (!voiceModelValue || typeof voiceModelValue !== 'string') return null;
109
+
110
+ const voiceModel = getVoiceModelByValue(voiceModelValue);
111
+ if (!voiceModel) return null;
112
+
113
+ // Get required plugin from configuration
114
+ const requiredPlugin = providerPluginMap[voiceModel.provider];
115
+ const isPluginEnabled = safeCharacterPlugins.includes(requiredPlugin);
116
+
117
+ return {
118
+ provider: voiceModel.provider,
119
+ requiredPlugin,
120
+ isPluginEnabled,
121
+ };
122
+ }, [characterValue?.settings, safeCharacterPlugins]);
123
+
124
+ // Get all voice-related plugins that are currently enabled
125
+ // const enabledVoicePlugins = useMemo(() => {
126
+ // const voicePlugins = getAllRequiredPlugins();
127
+ // return safeCharacterPlugins.filter((plugin) => voicePlugins.includes(plugin));
128
+ // }, [safeCharacterPlugins]);
129
+
130
+ const hasChanged = useMemo(() => {
131
+ if (!initialPlugins) return false;
132
+ if (initialPlugins.length !== safeCharacterPlugins.length) return true;
133
+ return !initialPlugins?.every((plugin) => safeCharacterPlugins.includes(plugin));
134
+ }, [safeCharacterPlugins, initialPlugins]);
135
+
136
+ const filteredPlugins = useMemo(() => {
137
+ return pluginNames
138
+ .filter((plugin) => !safeCharacterPlugins.includes(plugin))
139
+ .filter((plugin) => plugin.toLowerCase().includes(searchQuery.toLowerCase()));
140
+ }, [pluginNames, safeCharacterPlugins, searchQuery]);
141
+
142
+ const handlePluginAdd = (plugin: string) => {
143
+ if (safeCharacterPlugins.includes(plugin)) return;
144
+
145
+ if (setCharacterValue.addPlugin) {
146
+ setCharacterValue.addPlugin(plugin);
147
+ } else if (setCharacterValue.updateField) {
148
+ const currentPlugins = Array.isArray(characterValue.plugins)
149
+ ? [...characterValue.plugins]
150
+ : [];
151
+ setCharacterValue.updateField('plugins', [...currentPlugins, plugin]);
152
+ }
153
+ };
154
+
155
+ const handlePluginRemove = (plugin: string) => {
156
+ // Check if it's an essential plugin that needs confirmation
157
+ if (Object.keys(ESSENTIAL_PLUGINS).includes(plugin)) {
158
+ setPendingRemoval(plugin);
159
+ setIsConfirmDialogOpen(true);
160
+ return;
161
+ }
162
+
163
+ // If not essential, proceed with removal immediately
164
+ removePlugin(plugin);
165
+ };
166
+
167
+ // Actual plugin removal after confirmation (if required)
168
+ const removePlugin = (plugin: string) => {
169
+ const index = safeCharacterPlugins.indexOf(plugin);
170
+ if (index !== -1) {
171
+ if (setCharacterValue.removePlugin) {
172
+ setCharacterValue.removePlugin(index);
173
+ } else if (setCharacterValue.updateField) {
174
+ const newPlugins = [...safeCharacterPlugins];
175
+ newPlugins.splice(index, 1);
176
+ setCharacterValue.updateField('plugins', newPlugins);
177
+ }
178
+ }
179
+ };
180
+
181
+ // Function to handle confirmation dialog acceptance
182
+ const handleConfirmRemoval = () => {
183
+ if (pendingRemoval) {
184
+ removePlugin(pendingRemoval);
185
+ setPendingRemoval(null);
186
+ }
187
+ setIsConfirmDialogOpen(false);
188
+ };
189
+
190
+ // Function to handle confirmation dialog cancellation
191
+ const handleCancelRemoval = () => {
192
+ setPendingRemoval(null);
193
+ setIsConfirmDialogOpen(false);
194
+ };
195
+
196
+ return (
197
+ <div className="space-y-6">
198
+ <div className="space-y-4">
199
+ <div>
200
+ <h3 className="text-lg font-semibold mb-4">Plugins</h3>
201
+ {error ? (
202
+ <p className="text-destructive">Failed to load plugins: {error.message}</p>
203
+ ) : (
204
+ <div className="space-y-4">
205
+ {/* Alert Dialog for Essential Plugin Removal */}
206
+ <AlertDialog open={isConfirmDialogOpen} onOpenChange={setIsConfirmDialogOpen}>
207
+ <AlertDialogContent>
208
+ <AlertDialogHeader>
209
+ <AlertDialogTitle>
210
+ {pendingRemoval && Object.keys(ESSENTIAL_PLUGINS).includes(pendingRemoval)
211
+ ? ESSENTIAL_PLUGINS[pendingRemoval].title
212
+ : 'Warning: Essential Plugin'}
213
+ </AlertDialogTitle>
214
+ <AlertDialogDescription>
215
+ {pendingRemoval && Object.keys(ESSENTIAL_PLUGINS).includes(pendingRemoval)
216
+ ? ESSENTIAL_PLUGINS[pendingRemoval].description
217
+ : 'This plugin provides essential functionality for your agent.'}
218
+ </AlertDialogDescription>
219
+ </AlertDialogHeader>
220
+ <AlertDialogFooter>
221
+ <AlertDialogCancel onClick={handleCancelRemoval}>Cancel</AlertDialogCancel>
222
+ <AlertDialogAction onClick={handleConfirmRemoval}>
223
+ Confirm Removal
224
+ </AlertDialogAction>
225
+ </AlertDialogFooter>
226
+ </AlertDialogContent>
227
+ </AlertDialog>
228
+
229
+ {voiceModelPluginInfo && (
230
+ <div className="rounded-md border p-4 mb-4 flex items-center gap-2">
231
+ <CircleAlert className="h-4 w-4 text-yellow-500" />
232
+ <p className="text-xs text-white">
233
+ {(() => {
234
+ switch (voiceModelPluginInfo.provider) {
235
+ case 'elevenlabs':
236
+ return 'ElevenLabs plugin is required for the selected voice model.';
237
+ case 'openai':
238
+ return 'OpenAI plugin is required for the selected voice model.';
239
+ case 'none':
240
+ return 'No voice plugin required for "No Voice" option.';
241
+ default:
242
+ return `${voiceModelPluginInfo.provider} plugin is required for the selected voice model.`;
243
+ }
244
+ })()}
245
+ </p>
246
+ {/*
247
+ Commented out for now — this warning doesn't make sense when using ElevenLabs voice model with OpenAI plugin.
248
+ */}
249
+ {/* {enabledVoicePlugins.length > 1 && (
250
+ <p className="text-xs text-amber-600 mt-2">
251
+ Multiple voice plugins detected. This may cause conflicts. Consider removing
252
+ unused voice plugins.
253
+ </p>
254
+ )} */}
255
+ </div>
256
+ )}
257
+ {safeCharacterPlugins.length > 0 && (
258
+ <div className="rounded-md bg-muted p-4">
259
+ <h4 className="text-sm font-medium mb-2">Currently Enabled</h4>
260
+ <div className="flex flex-wrap gap-2">
261
+ {[...safeCharacterPlugins]
262
+ .sort((a, b) => {
263
+ const aIsEssential = Object.keys(ESSENTIAL_PLUGINS).includes(a);
264
+ const bIsEssential = Object.keys(ESSENTIAL_PLUGINS).includes(b);
265
+ if (aIsEssential === bIsEssential) return 0;
266
+ return aIsEssential ? -1 : 1;
267
+ })
268
+ .map((plugin) => {
269
+ // Check if this plugin is required by the current voice model
270
+ const isRequiredByVoice = voiceModelPluginInfo?.requiredPlugin === plugin;
271
+ // Check if this is an essential plugin (SQL or OpenAI)
272
+ const isEssential = Object.keys(ESSENTIAL_PLUGINS).includes(plugin);
273
+
274
+ return (
275
+ <Button
276
+ type="button"
277
+ variant="ghost"
278
+ size="sm"
279
+ key={plugin}
280
+ className={`inline-flex items-center rounded-full ${
281
+ isEssential
282
+ ? 'bg-blue-800 text-blue-700 hover:bg-blue-600'
283
+ : 'bg-primary/10 text-primary hover:bg-primary/20'
284
+ } px-2.5 py-0.5 text-xs font-medium h-auto`}
285
+ title={
286
+ isRequiredByVoice
287
+ ? 'Required by voice model'
288
+ : isEssential
289
+ ? 'Essential plugin for agent functionality'
290
+ : ''
291
+ }
292
+ >
293
+ {isEssential && (
294
+ <span className="w-2 h-2 rounded-full bg-white inline-block"></span>
295
+ )}
296
+ <span className="text-white font-semibold">{plugin}</span>
297
+ <span
298
+ className={clsx(
299
+ 'ml-1 opacity-70 hover:opacity-100',
300
+ isEssential && 'text-white'
301
+ )}
302
+ onClick={() => {
303
+ // Don't allow removing if it's required by the voice model
304
+ if (isRequiredByVoice) {
305
+ toast({
306
+ title: "Can't Remove Plugin",
307
+ description:
308
+ 'This plugin is required by the selected voice model.',
309
+ variant: 'destructive',
310
+ });
311
+ return;
312
+ }
313
+ handlePluginRemove(plugin);
314
+ }}
315
+ >
316
+ ×
317
+ </span>
318
+ </Button>
319
+ );
320
+ })}
321
+ </div>
322
+ <div className="mt-3 text-xs text-muted-foreground">
323
+ <span className="inline-flex items-center">
324
+ <span className="w-2 h-2 rounded-full bg-blue-600 mr-1"></span>
325
+ Essential plugins provide core functionality
326
+ </span>
327
+ </div>
328
+ </div>
329
+ )}
330
+ <div className="space-y-2">
331
+ <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
332
+ <DialogTrigger asChild>
333
+ <Button variant="outline" className="w-full justify-start">
334
+ Search and add plugins...
335
+ </Button>
336
+ </DialogTrigger>
337
+ <DialogContent className="max-w-[400px]">
338
+ <DialogHeader>
339
+ <DialogTitle>Add Plugins</DialogTitle>
340
+ </DialogHeader>
341
+ <div className="space-y-4 py-4">
342
+ <div className="space-y-2">
343
+ <Input
344
+ type="search"
345
+ placeholder="Search plugins..."
346
+ value={searchQuery}
347
+ onChange={(e) => setSearchQuery(e.target.value)}
348
+ />
349
+ </div>
350
+ <div className="max-h-[300px] overflow-y-auto space-y-2">
351
+ {filteredPlugins.length === 0 ? (
352
+ <p className="text-sm text-muted-foreground">No plugins found.</p>
353
+ ) : (
354
+ filteredPlugins.map((plugin) => (
355
+ <Button
356
+ key={plugin}
357
+ variant="ghost"
358
+ className="w-full justify-start font-normal"
359
+ onClick={() => {
360
+ handlePluginAdd(plugin);
361
+ setSearchQuery('');
362
+ setIsDialogOpen(false);
363
+ }}
364
+ >
365
+ {plugin}
366
+ </Button>
367
+ ))
368
+ )}
369
+ </div>
370
+ </div>
371
+ </DialogContent>
372
+ </Dialog>
373
+ </div>
374
+ {hasChanged && (
375
+ <p className="text-xs text-yellow-500">Plugins configuration has been changed</p>
376
+ )}
377
+ </div>
378
+ )}
379
+ </div>
380
+ </div>
381
+ </div>
382
+ );
383
+ }