@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,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
|
+
}
|