@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,385 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { Play, Volume2, FileText, ExternalLink, AlertCircle, Download } from 'lucide-react';
5
+ import { cn } from '@/lib/utils';
6
+
7
+ interface MediaContentProps {
8
+ url: string;
9
+ title?: string;
10
+ className?: string;
11
+ maxWidth?: number;
12
+ maxHeight?: number;
13
+ }
14
+
15
+ // Utility functions to detect media types
16
+ const getYouTubeId = (url: string): string | null => {
17
+ const patterns = [
18
+ /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
19
+ /youtube\.com\/watch\?.*v=([^&\n?#]+)/,
20
+ ];
21
+
22
+ for (const pattern of patterns) {
23
+ const match = url.match(pattern);
24
+ if (match) return match[1];
25
+ }
26
+ return null;
27
+ };
28
+
29
+ const getSpotifyId = (url: string): { type: string; id: string } | null => {
30
+ const match = url.match(
31
+ /spotify\.com\/(track|album|playlist|episode|show)\/([a-zA-Z0-9]+)(?:\?.*)?/
32
+ );
33
+ if (match) {
34
+ return { type: match[1], id: match[2] };
35
+ }
36
+ return null;
37
+ };
38
+
39
+ const isImageUrl = (url: string): boolean => {
40
+ return /\.(jpg|jpeg|png|gif|webp|svg|bmp|ico)(\?.*)?$/i.test(url);
41
+ };
42
+
43
+ const isVideoUrl = (url: string): boolean => {
44
+ return /\.(mp4|webm|ogg|mov|avi|wmv|flv|mkv)(\?.*)?$/i.test(url);
45
+ };
46
+
47
+ const isAudioUrl = (url: string): boolean => {
48
+ return /\.(mp3|wav|ogg|aac|flac|m4a|wma)(\?.*)?$/i.test(url);
49
+ };
50
+
51
+ const isPdfUrl = (url: string): boolean => {
52
+ return /\.pdf(\?.*)?$/i.test(url) || url.includes('pdf');
53
+ };
54
+
55
+ const isDocumentUrl = (url: string): boolean => {
56
+ return /\.(pdf|doc|docx|ppt|pptx|xls|xlsx|txt)(\?.*)?$/i.test(url);
57
+ };
58
+
59
+ export default function MediaContent({
60
+ url,
61
+ title,
62
+ className,
63
+ maxWidth = 600,
64
+ maxHeight = 400,
65
+ }: MediaContentProps) {
66
+ const [isLoading, setIsLoading] = useState(true);
67
+ const [hasError, setHasError] = useState(false);
68
+
69
+ const handleLoad = () => setIsLoading(false);
70
+ const handleError = () => {
71
+ setIsLoading(false);
72
+ setHasError(true);
73
+ };
74
+
75
+ // YouTube Video
76
+ const youtubeId = getYouTubeId(url);
77
+ if (youtubeId) {
78
+ return (
79
+ <div
80
+ className={cn('relative rounded-lg overflow-hidden bg-muted', className)}
81
+ style={{ maxWidth, maxHeight }}
82
+ >
83
+ {isLoading && (
84
+ <div className="absolute inset-0 flex items-center justify-center bg-muted">
85
+ <div className="flex items-center space-x-2 text-muted-foreground">
86
+ <Play className="w-6 h-6" />
87
+ <span>Loading video...</span>
88
+ </div>
89
+ </div>
90
+ )}
91
+ <iframe
92
+ src={`https://www.youtube.com/embed/${youtubeId}`}
93
+ title={title || 'YouTube video'}
94
+ className="w-full aspect-video"
95
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
96
+ allowFullScreen
97
+ onLoad={handleLoad}
98
+ onError={handleError}
99
+ />
100
+ </div>
101
+ );
102
+ }
103
+
104
+ // Spotify Content
105
+ const spotifyData = getSpotifyId(url);
106
+ if (spotifyData) {
107
+ const height = spotifyData.type === 'track' ? 152 : spotifyData.type === 'episode' ? 232 : 352;
108
+ return (
109
+ <div
110
+ className={cn('relative rounded-lg overflow-hidden bg-muted', className)}
111
+ style={{ maxWidth }}
112
+ >
113
+ {isLoading && (
114
+ <div className="absolute inset-0 flex items-center justify-center bg-muted">
115
+ <div className="flex items-center space-x-2 text-muted-foreground">
116
+ <Volume2 className="w-6 h-6" />
117
+ <span>Loading Spotify content...</span>
118
+ </div>
119
+ </div>
120
+ )}
121
+ <iframe
122
+ src={`https://open.spotify.com/embed/${spotifyData.type}/${spotifyData.id}?utm_source=generator`}
123
+ width="100%"
124
+ height={height}
125
+ frameBorder="0"
126
+ allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
127
+ loading="lazy"
128
+ title={title || 'Spotify content'}
129
+ onLoad={handleLoad}
130
+ onError={handleError}
131
+ />
132
+ </div>
133
+ );
134
+ }
135
+
136
+ // Direct Image
137
+ if (isImageUrl(url)) {
138
+ return (
139
+ <div
140
+ className={cn('relative rounded-lg overflow-hidden bg-muted', className)}
141
+ style={{ maxWidth, maxHeight }}
142
+ >
143
+ {isLoading && (
144
+ <div className="absolute inset-0 flex items-center justify-center bg-muted">
145
+ <div className="animate-pulse w-full h-32 bg-muted-foreground/20 rounded" />
146
+ </div>
147
+ )}
148
+ {hasError ? (
149
+ <div className="flex items-center justify-center p-4 text-muted-foreground">
150
+ <AlertCircle className="w-6 h-6 mr-2" />
151
+ <span>Failed to load image</span>
152
+ </div>
153
+ ) : (
154
+ <img
155
+ src={url || '/placeholder.svg'}
156
+ alt={title || 'Image'}
157
+ width={maxWidth}
158
+ height={maxHeight}
159
+ className="w-full h-auto object-contain"
160
+ onLoad={handleLoad}
161
+ onError={handleError}
162
+ />
163
+ )}
164
+ </div>
165
+ );
166
+ }
167
+
168
+ // Direct Video
169
+ if (isVideoUrl(url)) {
170
+ return (
171
+ <div
172
+ className={cn('relative rounded-lg overflow-hidden bg-muted', className)}
173
+ style={{ maxWidth, maxHeight }}
174
+ >
175
+ {hasError ? (
176
+ <div className="flex items-center justify-center p-4 text-muted-foreground">
177
+ <AlertCircle className="w-6 h-6 mr-2" />
178
+ <span>Failed to load video</span>
179
+ </div>
180
+ ) : (
181
+ <video
182
+ controls
183
+ preload="metadata"
184
+ className="w-full h-auto"
185
+ onLoadedData={handleLoad}
186
+ onError={handleError}
187
+ crossOrigin="anonymous"
188
+ >
189
+ <source src={url} />
190
+ Your browser does not support the video tag.
191
+ </video>
192
+ )}
193
+ </div>
194
+ );
195
+ }
196
+
197
+ // Direct Audio
198
+ if (isAudioUrl(url)) {
199
+ return (
200
+ <div className={cn('relative rounded-lg bg-card border p-4', className)} style={{ maxWidth }}>
201
+ <div className="flex items-center space-x-3 mb-3 overflow-hidden">
202
+ <Volume2 className="w-5 h-5 text-muted-foreground" />
203
+ <span className="text-sm text-primary font-medium truncate whitespace-nowrap overflow-hidden text-ellipsis max-w-44">
204
+ {title || 'Audio File'}
205
+ </span>
206
+ </div>
207
+ {hasError ? (
208
+ <div className="flex items-center text-muted-foreground">
209
+ <AlertCircle className="w-4 h-4 mr-2" />
210
+ <span className="text-sm">Failed to load audio</span>
211
+ </div>
212
+ ) : (
213
+ <audio
214
+ controls
215
+ className="w-full"
216
+ preload="metadata"
217
+ onLoadedData={handleLoad}
218
+ onError={handleError}
219
+ crossOrigin="anonymous"
220
+ >
221
+ <source src={url} />
222
+ Your browser does not support the audio tag.
223
+ </audio>
224
+ )}
225
+ </div>
226
+ );
227
+ }
228
+
229
+ // PDF Document
230
+ if (isPdfUrl(url)) {
231
+ return (
232
+ <div
233
+ className={cn('relative rounded-lg overflow-hidden bg-card border', className)}
234
+ style={{ maxWidth }}
235
+ >
236
+ <div className="flex items-center justify-between p-3 bg-muted/50 border-b gap-10">
237
+ <div className="flex items-center space-x-2 overflow-hidden">
238
+ <FileText className="w-5 h-5 text-muted-foreground" />
239
+ <span className="text-sm text-primary font-medium truncate whitespace-nowrap overflow-hidden text-ellipsis max-w-44">
240
+ {title || 'PDF Document'}
241
+ </span>
242
+ </div>
243
+ <div className="flex items-center space-x-2">
244
+ <a
245
+ href={`https://docs.google.com/viewer?url=${encodeURIComponent(url)}&embedded=true`}
246
+ target="_blank"
247
+ rel="noopener noreferrer"
248
+ className="flex items-center space-x-1 text-primary hover:text-primary/80 text-sm"
249
+ >
250
+ <ExternalLink className="w-4 h-4" />
251
+ <span>View</span>
252
+ </a>
253
+ <a
254
+ href={url}
255
+ download
256
+ className="flex items-center space-x-1 text-primary hover:text-primary/80 text-sm"
257
+ >
258
+ <Download className="w-4 h-4" />
259
+ <span>Download</span>
260
+ </a>
261
+ </div>
262
+ </div>
263
+
264
+ {/* Try multiple PDF viewing methods */}
265
+ <div className="relative" style={{ height: Math.min(maxHeight, 500) }}>
266
+ {hasError ? (
267
+ <div className="flex flex-col items-center justify-center p-8 text-muted-foreground h-full">
268
+ <AlertCircle className="w-8 h-8 mb-2" />
269
+ <span className="text-center mb-4">PDF preview not available</span>
270
+ <div className="flex space-x-2">
271
+ <a
272
+ href={`https://docs.google.com/viewer?url=${encodeURIComponent(url)}`}
273
+ target="_blank"
274
+ rel="noopener noreferrer"
275
+ className="px-3 py-2 bg-primary text-primary-foreground rounded text-sm hover:bg-primary/90"
276
+ >
277
+ Open in Google Viewer
278
+ </a>
279
+ <a
280
+ href={url}
281
+ target="_blank"
282
+ rel="noopener noreferrer"
283
+ className="px-3 py-2 bg-secondary text-secondary-foreground rounded text-sm hover:bg-secondary/90"
284
+ >
285
+ Open Original
286
+ </a>
287
+ </div>
288
+ </div>
289
+ ) : (
290
+ <>
291
+ {isLoading && (
292
+ <div className="absolute inset-0 flex items-center justify-center bg-muted">
293
+ <div className="flex items-center space-x-2 text-muted-foreground">
294
+ <FileText className="w-6 h-6" />
295
+ <span>Loading PDF...</span>
296
+ </div>
297
+ </div>
298
+ )}
299
+
300
+ {/* Try Google Docs Viewer first */}
301
+ <iframe
302
+ src={`https://docs.google.com/viewer?url=${encodeURIComponent(url)}&embedded=true`}
303
+ className="w-full h-full border-0"
304
+ title={title || 'PDF Document'}
305
+ onLoad={handleLoad}
306
+ onError={() => {
307
+ // Fallback to direct PDF if Google Viewer fails
308
+ const iframe = document.createElement('iframe');
309
+ iframe.src = `${url}#toolbar=0&navpanes=0&scrollbar=0`;
310
+ iframe.className = 'w-full h-full border-0';
311
+ iframe.title = title || 'PDF Document';
312
+ iframe.onload = handleLoad;
313
+ iframe.onerror = handleError;
314
+
315
+ const container = document.querySelector(`[data-pdf-container="${url}"]`);
316
+ if (container) {
317
+ container.innerHTML = '';
318
+ container.appendChild(iframe);
319
+ }
320
+ }}
321
+ />
322
+
323
+ <div data-pdf-container={url} className="hidden" />
324
+ </>
325
+ )}
326
+ </div>
327
+
328
+ {/* PDF Info Footer */}
329
+ <div className="p-2 bg-muted/50 border-t text-xs text-muted-foreground">
330
+ <span>PDF Document • Click "View" or "Download" if preview doesn't load</span>
331
+ </div>
332
+ </div>
333
+ );
334
+ }
335
+
336
+ // Other Documents
337
+ if (isDocumentUrl(url)) {
338
+ return (
339
+ <div className={cn('rounded-lg bg-card border p-4', className)} style={{ maxWidth }}>
340
+ <div className="flex items-center space-x-3">
341
+ <FileText className="w-8 h-8 text-muted-foreground" />
342
+ <div className="flex-1 overflow-hidden mr-6">
343
+ <p className="text-sm text-primary font-medium truncate whitespace-nowrap overflow-hidden text-ellipsis max-w-44">
344
+ {title || 'Document'}
345
+ </p>
346
+ <p className="text-xs text-muted-foreground">
347
+ {url.split('.').pop()?.toUpperCase()} file
348
+ </p>
349
+ </div>
350
+ <a
351
+ href={url}
352
+ target="_blank"
353
+ rel="noopener noreferrer"
354
+ className="flex items-center space-x-1 text-primary hover:text-primary/80 text-sm font-medium"
355
+ >
356
+ <ExternalLink className="w-4 h-4" />
357
+ <span>Open</span>
358
+ </a>
359
+ </div>
360
+ </div>
361
+ );
362
+ }
363
+
364
+ // Fallback for unknown URLs
365
+ return (
366
+ <div className={cn('rounded-lg bg-card border p-4', className)} style={{ maxWidth }}>
367
+ <div className="flex items-center space-x-3">
368
+ <ExternalLink className="w-8 h-8 text-muted-foreground" />
369
+ <div className="flex-1">
370
+ <p className="text-sm text-primary font-medium">{title || 'External Link'}</p>
371
+ <p className="text-xs text-muted-foreground truncate">{url}</p>
372
+ </div>
373
+ <a
374
+ href={url}
375
+ target="_blank"
376
+ rel="noopener noreferrer"
377
+ className="flex items-center space-x-1 text-primary hover:text-primary/80 text-sm font-medium"
378
+ >
379
+ <ExternalLink className="w-4 h-4" />
380
+ <span>Open</span>
381
+ </a>
382
+ </div>
383
+ </div>
384
+ );
385
+ }
@@ -0,0 +1,170 @@
1
+ import { useMemo, useRef, useEffect, useState } from 'react';
2
+ import ForceGraph2D from 'react-force-graph-2d';
3
+ import type { Memory } from '@elizaos/core';
4
+ import { computePca } from '@/lib/pca';
5
+
6
+ export default function MemoryGraph({
7
+ memories,
8
+ onSelect,
9
+ }: {
10
+ memories: Memory[];
11
+ onSelect: (m: Memory) => void;
12
+ }) {
13
+ const graphRef = useRef<HTMLDivElement>(null);
14
+ const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
15
+
16
+ useEffect(() => {
17
+ if (graphRef.current) {
18
+ setDimensions({
19
+ width: graphRef.current.offsetWidth,
20
+ height: graphRef.current.offsetHeight,
21
+ });
22
+ }
23
+ const handleResize = () => {
24
+ if (graphRef.current) {
25
+ setDimensions({
26
+ width: graphRef.current.offsetWidth,
27
+ height: graphRef.current.offsetHeight,
28
+ });
29
+ }
30
+ };
31
+ window.addEventListener('resize', handleResize);
32
+ return () => window.removeEventListener('resize', handleResize);
33
+ }, []);
34
+
35
+ const data = useMemo(() => {
36
+ console.log('Processing memories for graph:', memories.length);
37
+
38
+ // Filter memories with valid embeddings
39
+ const memoriesWithEmbeddings = memories.filter(
40
+ (m) => m.embedding && Array.isArray(m.embedding) && m.embedding.length > 0
41
+ );
42
+
43
+ console.log('Memories with embeddings:', memoriesWithEmbeddings.length);
44
+
45
+ if (memoriesWithEmbeddings.length === 0) {
46
+ return { nodes: [], links: [], hasEmbeddings: false };
47
+ }
48
+
49
+ const embeddings = memoriesWithEmbeddings.map((m) => m.embedding as number[]);
50
+
51
+ // Compute PCA coordinates
52
+ const coords = computePca(embeddings, 2);
53
+
54
+ // Scale coordinates to fit the graph area with some padding
55
+ const padding = 50;
56
+ const scale = Math.min(dimensions.width - 2 * padding, dimensions.height - 2 * padding) * 0.3;
57
+ const centerX = dimensions.width / 2;
58
+ const centerY = dimensions.height / 2;
59
+
60
+ const nodes = memoriesWithEmbeddings.map((m, i) => ({
61
+ id: m.id || String(i),
62
+ memory: m,
63
+ x: coords[i][0] * scale + centerX,
64
+ y: coords[i][1] * scale + centerY,
65
+ fx: coords[i][0] * scale + centerX, // Fixed position
66
+ fy: coords[i][1] * scale + centerY, // Fixed position
67
+ val: 8, // Slightly larger node size
68
+ color: m.entityId === m.agentId ? '#3b82f6' : '#10b981', // Blue for agent, green for user
69
+ }));
70
+
71
+ // Create links between temporally adjacent memories
72
+ const links: any[] = [];
73
+ const sortedMemories = [...memoriesWithEmbeddings].sort(
74
+ (a, b) => (a.createdAt || 0) - (b.createdAt || 0)
75
+ );
76
+
77
+ // Only create links if there are enough memories
78
+ if (sortedMemories.length > 1) {
79
+ for (let i = 0; i < sortedMemories.length - 1; i++) {
80
+ const sourceNode = nodes.find((n) => n.memory.id === sortedMemories[i].id);
81
+ const targetNode = nodes.find((n) => n.memory.id === sortedMemories[i + 1].id);
82
+
83
+ if (sourceNode && targetNode) {
84
+ links.push({
85
+ source: sourceNode.id,
86
+ target: targetNode.id,
87
+ value: 0.2,
88
+ });
89
+ }
90
+ }
91
+ }
92
+
93
+ return { nodes, links, hasEmbeddings: true };
94
+ }, [memories, dimensions]);
95
+
96
+ // Loading or empty state
97
+ if (!data.hasEmbeddings) {
98
+ return (
99
+ <div className="w-full h-full flex items-center justify-center">
100
+ <div className="text-center">
101
+ <p className="text-muted-foreground mb-2">No embeddings available for visualization</p>
102
+ <p className="text-sm text-muted-foreground">
103
+ Embeddings are required to display the memory graph
104
+ </p>
105
+ </div>
106
+ </div>
107
+ );
108
+ }
109
+
110
+ return (
111
+ <div ref={graphRef} className="w-full h-full bg-muted/10 rounded-lg relative">
112
+ {/* Legend */}
113
+ <div className="absolute top-4 left-4 bg-background/90 backdrop-blur-sm border rounded-lg p-3 z-10">
114
+ <h4 className="text-sm font-medium mb-2">Legend</h4>
115
+ <div className="space-y-1">
116
+ <div className="flex items-center gap-2">
117
+ <div className="w-3 h-3 rounded-full bg-blue-500"></div>
118
+ <span className="text-xs">Agent Messages</span>
119
+ </div>
120
+ <div className="flex items-center gap-2">
121
+ <div className="w-3 h-3 rounded-full bg-green-500"></div>
122
+ <span className="text-xs">User Messages</span>
123
+ </div>
124
+ </div>
125
+ <p className="text-xs text-muted-foreground mt-2">Click nodes to view details</p>
126
+ </div>
127
+
128
+ {dimensions.width > 0 && dimensions.height > 0 && data.nodes.length > 0 && (
129
+ <ForceGraph2D
130
+ width={dimensions.width}
131
+ height={dimensions.height}
132
+ graphData={data}
133
+ nodeLabel={(node: any) => {
134
+ const content = node.memory?.content;
135
+ const text = content?.text || '';
136
+ const truncated = text.length > 100 ? text.substring(0, 100) + '...' : text;
137
+ return truncated || node.memory?.id || 'Memory';
138
+ }}
139
+ onNodeClick={(node: any) => {
140
+ console.log('Node clicked:', node);
141
+ if (node.memory) {
142
+ onSelect(node.memory);
143
+ }
144
+ }}
145
+ nodeCanvasObject={(node: any, ctx, globalScale) => {
146
+ const label = node.memory?.entityId === node.memory?.agentId ? 'A' : 'U';
147
+ const fontSize = 12 / globalScale;
148
+ ctx.font = `${fontSize}px Sans-Serif`;
149
+ ctx.textAlign = 'center';
150
+ ctx.textBaseline = 'middle';
151
+
152
+ // Draw node circle
153
+ ctx.beginPath();
154
+ ctx.arc(node.x, node.y, node.val, 0, 2 * Math.PI, false);
155
+ ctx.fillStyle = node.color;
156
+ ctx.fill();
157
+
158
+ // Draw label
159
+ ctx.fillStyle = 'white';
160
+ ctx.fillText(label, node.x, node.y);
161
+ }}
162
+ linkColor={() => '#64748b'}
163
+ backgroundColor="#00000000"
164
+ enableZoomInteraction={true}
165
+ enableNodeDrag={false}
166
+ />
167
+ )}
168
+ </div>
169
+ );
170
+ }
@@ -0,0 +1,72 @@
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 { AlertCircle } from 'lucide-react';
12
+
13
+ interface MissingSecretsDialogProps {
14
+ open: boolean;
15
+ onOpenChange: (open: boolean) => void;
16
+ missingSecrets: Array<{
17
+ name: string;
18
+ plugin?: string;
19
+ description?: string;
20
+ }>;
21
+ onConfirm: () => void;
22
+ onCancel: () => void;
23
+ }
24
+
25
+ export function MissingSecretsDialog({
26
+ open,
27
+ onOpenChange,
28
+ missingSecrets,
29
+ onConfirm,
30
+ onCancel,
31
+ }: MissingSecretsDialogProps) {
32
+ return (
33
+ <AlertDialog open={open} onOpenChange={onOpenChange}>
34
+ <AlertDialogContent>
35
+ <AlertDialogHeader>
36
+ <AlertDialogTitle className="flex items-center gap-2">
37
+ <AlertCircle className="h-5 w-5 text-yellow-500" />
38
+ Missing Required Configuration
39
+ </AlertDialogTitle>
40
+ <AlertDialogDescription className="space-y-3">
41
+ <p>
42
+ The following required secrets are missing or empty. Your agent may not function
43
+ properly without these values.
44
+ </p>
45
+ <div className="bg-muted rounded-md p-3 space-y-2">
46
+ {missingSecrets.map((secret) => (
47
+ <div key={secret.name} className="text-sm">
48
+ <code className="font-mono font-semibold">{secret.name}</code>
49
+ {secret.plugin && (
50
+ <span className="text-muted-foreground ml-2">
51
+ (required by {secret.plugin})
52
+ </span>
53
+ )}
54
+ {secret.description && (
55
+ <div className="text-xs text-muted-foreground mt-1">{secret.description}</div>
56
+ )}
57
+ </div>
58
+ ))}
59
+ </div>
60
+ <p className="text-sm">Do you want to save anyway?</p>
61
+ </AlertDialogDescription>
62
+ </AlertDialogHeader>
63
+ <AlertDialogFooter>
64
+ <AlertDialogCancel onClick={onCancel}>Go Back</AlertDialogCancel>
65
+ <AlertDialogAction onClick={onConfirm} className="bg-yellow-600 hover:bg-yellow-700">
66
+ Save Anyway
67
+ </AlertDialogAction>
68
+ </AlertDialogFooter>
69
+ </AlertDialogContent>
70
+ </AlertDialog>
71
+ );
72
+ }