@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,816 @@
|
|
|
1
|
+
import type { UUID } from '@elizaos/core';
|
|
2
|
+
import type { AgentLog } from '@/lib/api-type-mappers';
|
|
3
|
+
import {
|
|
4
|
+
Bot,
|
|
5
|
+
Brain,
|
|
6
|
+
ImagePlusIcon,
|
|
7
|
+
Trash2,
|
|
8
|
+
LoaderIcon,
|
|
9
|
+
Search,
|
|
10
|
+
Clock,
|
|
11
|
+
Database,
|
|
12
|
+
Zap,
|
|
13
|
+
Activity,
|
|
14
|
+
FileText,
|
|
15
|
+
Copy,
|
|
16
|
+
ChevronDown,
|
|
17
|
+
ChevronUp,
|
|
18
|
+
AlertCircle,
|
|
19
|
+
} from 'lucide-react';
|
|
20
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
21
|
+
import { useAgentActions, useDeleteLog } from '@/hooks/use-query-hooks';
|
|
22
|
+
import { Button } from '@/components/ui/button';
|
|
23
|
+
import { Input } from '@/components/ui/input';
|
|
24
|
+
import {
|
|
25
|
+
Select,
|
|
26
|
+
SelectContent,
|
|
27
|
+
SelectItem,
|
|
28
|
+
SelectTrigger,
|
|
29
|
+
SelectValue,
|
|
30
|
+
} from '@/components/ui/select';
|
|
31
|
+
import { Separator } from '@/components/ui/separator';
|
|
32
|
+
import { Badge } from '@/components/ui/badge';
|
|
33
|
+
import { useConfirmation } from '@/hooks/use-confirmation';
|
|
34
|
+
import ConfirmationDialog from './confirmation-dialog';
|
|
35
|
+
|
|
36
|
+
// Constants
|
|
37
|
+
const ITEMS_PER_PAGE = 15;
|
|
38
|
+
|
|
39
|
+
// Enums
|
|
40
|
+
enum ActionType {
|
|
41
|
+
all = 'all',
|
|
42
|
+
llm = 'llm',
|
|
43
|
+
transcription = 'transcription',
|
|
44
|
+
image = 'image',
|
|
45
|
+
other = 'other',
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Types
|
|
49
|
+
type ActionCardProps = {
|
|
50
|
+
action: AgentLog;
|
|
51
|
+
onDelete?: (logId: string) => void;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type AgentActionViewerProps = {
|
|
55
|
+
agentId: UUID;
|
|
56
|
+
roomId?: UUID;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Helper functions
|
|
60
|
+
function getModelUsageType(modelType: string): string {
|
|
61
|
+
if (
|
|
62
|
+
(modelType.includes('TEXT') || modelType.includes('OBJECT')) &&
|
|
63
|
+
!modelType.includes('EMBEDDING') &&
|
|
64
|
+
!modelType.includes('TRANSCRIPTION')
|
|
65
|
+
) {
|
|
66
|
+
return 'LLM';
|
|
67
|
+
}
|
|
68
|
+
if (modelType.includes('EMBEDDING')) {
|
|
69
|
+
return 'Embedding';
|
|
70
|
+
}
|
|
71
|
+
if (modelType.includes('TRANSCRIPTION')) {
|
|
72
|
+
return 'Transcription';
|
|
73
|
+
}
|
|
74
|
+
if (modelType.includes('IMAGE')) {
|
|
75
|
+
return 'Image';
|
|
76
|
+
}
|
|
77
|
+
if (
|
|
78
|
+
!modelType.includes('TEXT') &&
|
|
79
|
+
!modelType.includes('IMAGE') &&
|
|
80
|
+
!modelType.includes('EMBEDDING') &&
|
|
81
|
+
!modelType.includes('TRANSCRIPTION')
|
|
82
|
+
) {
|
|
83
|
+
return 'Other';
|
|
84
|
+
}
|
|
85
|
+
return 'Unknown';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function formatDate(timestamp: number | undefined) {
|
|
89
|
+
if (!timestamp) return 'Unknown date';
|
|
90
|
+
const date = new Date(timestamp);
|
|
91
|
+
const now = new Date();
|
|
92
|
+
const diffInHours = (now.getTime() - date.getTime()) / (1000 * 60 * 60);
|
|
93
|
+
|
|
94
|
+
if (diffInHours < 1) {
|
|
95
|
+
const diffInMinutes = Math.floor((now.getTime() - date.getTime()) / (1000 * 60));
|
|
96
|
+
return `${diffInMinutes}m ago`;
|
|
97
|
+
} else if (diffInHours < 24) {
|
|
98
|
+
return `${Math.floor(diffInHours)}h ago`;
|
|
99
|
+
} else if (diffInHours < 168) {
|
|
100
|
+
return date.toLocaleDateString([], { weekday: 'short', hour: '2-digit', minute: '2-digit' });
|
|
101
|
+
} else {
|
|
102
|
+
return date.toLocaleDateString([], {
|
|
103
|
+
month: 'short',
|
|
104
|
+
day: 'numeric',
|
|
105
|
+
hour: '2-digit',
|
|
106
|
+
minute: '2-digit',
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function getModelIcon(modelType = '') {
|
|
112
|
+
if (modelType === 'ACTION') return Zap;
|
|
113
|
+
if (modelType.includes('TEXT_EMBEDDING')) return Brain;
|
|
114
|
+
if (modelType.includes('TRANSCRIPTION')) return FileText;
|
|
115
|
+
if (modelType.includes('TEXT') || modelType.includes('OBJECT')) return Bot;
|
|
116
|
+
if (modelType.includes('IMAGE')) return ImagePlusIcon;
|
|
117
|
+
return Activity;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function formatTokenUsage(usage: any) {
|
|
121
|
+
if (!usage) return null;
|
|
122
|
+
|
|
123
|
+
const { prompt_tokens, completion_tokens, total_tokens } = usage;
|
|
124
|
+
if (!total_tokens) return null;
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
prompt: prompt_tokens || 0,
|
|
128
|
+
completion: completion_tokens || 0,
|
|
129
|
+
total: total_tokens,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function truncateText(text: string, maxLength = 100) {
|
|
134
|
+
if (text.length <= maxLength) return text;
|
|
135
|
+
return text.substring(0, maxLength) + '...';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function copyToClipboard(text: string) {
|
|
139
|
+
navigator.clipboard.writeText(text).catch(console.error);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function groupActionsByDate(actions: AgentLog[]) {
|
|
143
|
+
const groups: Record<string, AgentLog[]> = {};
|
|
144
|
+
|
|
145
|
+
for (const action of actions) {
|
|
146
|
+
const timestamp = action.createdAt || action.timestamp || 0;
|
|
147
|
+
const date = new Date(timestamp);
|
|
148
|
+
const dateKey = date.toLocaleDateString();
|
|
149
|
+
|
|
150
|
+
if (!groups[dateKey]) {
|
|
151
|
+
groups[dateKey] = [];
|
|
152
|
+
}
|
|
153
|
+
groups[dateKey].push(action);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return groups;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Components
|
|
160
|
+
function ActionCard({ action, onDelete }: ActionCardProps) {
|
|
161
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
162
|
+
const [showFullParams, setShowFullParams] = useState(false);
|
|
163
|
+
const [showFullResponse, setShowFullResponse] = useState(false);
|
|
164
|
+
|
|
165
|
+
const modelType = action.body?.modelType || '';
|
|
166
|
+
const modelKey = action.body?.modelKey || '';
|
|
167
|
+
const isActionLog = action.type === 'action';
|
|
168
|
+
const actionName = action.body?.action || '';
|
|
169
|
+
const IconComponent = getModelIcon(isActionLog ? 'ACTION' : modelType);
|
|
170
|
+
const usageType = isActionLog ? 'Action' : getModelUsageType(modelType);
|
|
171
|
+
const tokenUsage = formatTokenUsage(action.body?.response?.usage || action.body?.usage);
|
|
172
|
+
const actionPrompts = action.body?.prompts;
|
|
173
|
+
|
|
174
|
+
const renderParams = () => {
|
|
175
|
+
const params = action.body?.params;
|
|
176
|
+
|
|
177
|
+
if (!params && !actionPrompts) return null;
|
|
178
|
+
|
|
179
|
+
if (modelType.includes('TRANSCRIPTION') && Array.isArray(params)) {
|
|
180
|
+
return (
|
|
181
|
+
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
182
|
+
<FileText className="h-3 w-3" />
|
|
183
|
+
<span>Audio input data</span>
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Extract prompt from params if present (for backward compatibility)
|
|
189
|
+
const { prompt, ...otherParams } = params || {};
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<div className="space-y-4">
|
|
193
|
+
{/* Display multiple prompts if this is an action with prompts */}
|
|
194
|
+
{actionPrompts && Array.isArray(actionPrompts) && actionPrompts.length > 0 && (
|
|
195
|
+
<div className="space-y-2">
|
|
196
|
+
<div className="flex items-center justify-between">
|
|
197
|
+
<span className="text-xs font-medium text-muted-foreground">
|
|
198
|
+
Prompts ({actionPrompts.length})
|
|
199
|
+
</span>
|
|
200
|
+
</div>
|
|
201
|
+
<div className="space-y-3">
|
|
202
|
+
{actionPrompts.map((promptData, index) => (
|
|
203
|
+
<div key={index} className="space-y-1">
|
|
204
|
+
<div className="flex items-center justify-between">
|
|
205
|
+
<span className="text-xs text-muted-foreground">
|
|
206
|
+
{promptData.modelType || 'Prompt'} #{index + 1}
|
|
207
|
+
</span>
|
|
208
|
+
<Button
|
|
209
|
+
variant="ghost"
|
|
210
|
+
size="sm"
|
|
211
|
+
onClick={() => copyToClipboard(promptData.prompt)}
|
|
212
|
+
className="h-5 px-1 text-xs"
|
|
213
|
+
title="Copy prompt"
|
|
214
|
+
>
|
|
215
|
+
<Copy className="h-3 w-3" />
|
|
216
|
+
</Button>
|
|
217
|
+
</div>
|
|
218
|
+
<div className="bg-muted/30 rounded-md p-2">
|
|
219
|
+
<pre className="text-xs font-mono whitespace-pre-wrap overflow-x-auto">
|
|
220
|
+
{promptData.prompt}
|
|
221
|
+
</pre>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
))}
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
)}
|
|
228
|
+
|
|
229
|
+
{/* Display single prompt from params (backward compatibility) */}
|
|
230
|
+
{!actionPrompts && prompt && (
|
|
231
|
+
<div className="space-y-2">
|
|
232
|
+
<div className="flex items-center justify-between">
|
|
233
|
+
<span className="text-xs font-medium text-muted-foreground">Prompt</span>
|
|
234
|
+
<Button
|
|
235
|
+
variant="ghost"
|
|
236
|
+
size="sm"
|
|
237
|
+
onClick={() => copyToClipboard(prompt)}
|
|
238
|
+
className="h-6 px-2 text-xs"
|
|
239
|
+
title="Copy prompt"
|
|
240
|
+
>
|
|
241
|
+
<Copy className="h-3 w-3 mr-1" />
|
|
242
|
+
Copy
|
|
243
|
+
</Button>
|
|
244
|
+
</div>
|
|
245
|
+
<div className="bg-muted/30 rounded-md p-3 relative">
|
|
246
|
+
<pre className="text-xs font-mono whitespace-pre-wrap overflow-x-auto">
|
|
247
|
+
{typeof prompt === 'string' ? prompt : JSON.stringify(prompt, null, 2)}
|
|
248
|
+
</pre>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
)}
|
|
252
|
+
|
|
253
|
+
{/* Display other parameters if any */}
|
|
254
|
+
{Object.keys(otherParams).length > 0 && (
|
|
255
|
+
<div className="space-y-2">
|
|
256
|
+
<div className="flex items-center justify-between">
|
|
257
|
+
<span className="text-xs font-medium text-muted-foreground">Other Parameters</span>
|
|
258
|
+
{(() => {
|
|
259
|
+
const paramsText = JSON.stringify(otherParams, null, 2);
|
|
260
|
+
const isLong = paramsText.length > 200;
|
|
261
|
+
return isLong ? (
|
|
262
|
+
<Button
|
|
263
|
+
variant="ghost"
|
|
264
|
+
size="sm"
|
|
265
|
+
onClick={() => setShowFullParams(!showFullParams)}
|
|
266
|
+
className="h-6 px-2 text-xs"
|
|
267
|
+
>
|
|
268
|
+
{showFullParams ? 'Show less' : 'Show more'}
|
|
269
|
+
</Button>
|
|
270
|
+
) : null;
|
|
271
|
+
})()}
|
|
272
|
+
</div>
|
|
273
|
+
<div className="bg-muted/30 rounded-md p-3 relative group">
|
|
274
|
+
<pre className="text-xs font-mono whitespace-pre-wrap overflow-x-auto">
|
|
275
|
+
{(() => {
|
|
276
|
+
const paramsText = JSON.stringify(otherParams, null, 2);
|
|
277
|
+
const isLong = paramsText.length > 200;
|
|
278
|
+
return showFullParams || !isLong ? paramsText : truncateText(paramsText, 200);
|
|
279
|
+
})()}
|
|
280
|
+
</pre>
|
|
281
|
+
<Button
|
|
282
|
+
variant="ghost"
|
|
283
|
+
size="sm"
|
|
284
|
+
onClick={() => copyToClipboard(JSON.stringify(otherParams, null, 2))}
|
|
285
|
+
className="absolute top-1 right-1 h-6 w-6 p-0 opacity-0 group-hover:opacity-100 transition-opacity"
|
|
286
|
+
title="Copy parameters"
|
|
287
|
+
>
|
|
288
|
+
<Copy className="h-3 w-3" />
|
|
289
|
+
</Button>
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
)}
|
|
293
|
+
</div>
|
|
294
|
+
);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const renderResponse = () => {
|
|
298
|
+
const response = action.body?.response;
|
|
299
|
+
if (!response) return null;
|
|
300
|
+
|
|
301
|
+
if (response === '[array]') {
|
|
302
|
+
return (
|
|
303
|
+
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
304
|
+
<Database className="h-3 w-3" />
|
|
305
|
+
<span>Array response data</span>
|
|
306
|
+
</div>
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const responseText =
|
|
311
|
+
typeof response === 'object' ? JSON.stringify(response, null, 2) : String(response);
|
|
312
|
+
const isLong = responseText.length > 300;
|
|
313
|
+
|
|
314
|
+
return (
|
|
315
|
+
<div className="space-y-2">
|
|
316
|
+
<div className="flex items-center justify-between">
|
|
317
|
+
<span className="text-xs font-medium text-muted-foreground">Response</span>
|
|
318
|
+
{isLong && (
|
|
319
|
+
<Button
|
|
320
|
+
variant="ghost"
|
|
321
|
+
size="sm"
|
|
322
|
+
onClick={() => setShowFullResponse(!showFullResponse)}
|
|
323
|
+
className="h-6 px-2 text-xs"
|
|
324
|
+
>
|
|
325
|
+
{showFullResponse ? 'Show less' : 'Show more'}
|
|
326
|
+
</Button>
|
|
327
|
+
)}
|
|
328
|
+
</div>
|
|
329
|
+
<div className="bg-muted/30 rounded-md p-3 relative group max-h-64 overflow-y-auto">
|
|
330
|
+
<pre className="text-xs font-mono whitespace-pre-wrap overflow-x-auto">
|
|
331
|
+
{showFullResponse || !isLong ? responseText : truncateText(responseText, 300)}
|
|
332
|
+
</pre>
|
|
333
|
+
<Button
|
|
334
|
+
variant="ghost"
|
|
335
|
+
size="sm"
|
|
336
|
+
onClick={() => copyToClipboard(responseText)}
|
|
337
|
+
className="absolute top-1 right-1 h-6 w-6 p-0 opacity-0 group-hover:opacity-100 transition-opacity"
|
|
338
|
+
title="Copy response"
|
|
339
|
+
>
|
|
340
|
+
<Copy className="h-3 w-3" />
|
|
341
|
+
</Button>
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const hasExtendedContent =
|
|
348
|
+
action.body?.params || action.body?.response || action.message || action.details;
|
|
349
|
+
|
|
350
|
+
return (
|
|
351
|
+
<div className="border rounded-lg bg-card hover:shadow-sm transition-all duration-200 group">
|
|
352
|
+
{/* Header */}
|
|
353
|
+
<div className="p-4 pb-3">
|
|
354
|
+
<div className="flex items-start justify-between">
|
|
355
|
+
<div className="flex items-start gap-3 flex-1">
|
|
356
|
+
<div className="p-2 rounded-lg bg-muted">
|
|
357
|
+
<IconComponent className="h-4 w-4 text-muted-foreground" />
|
|
358
|
+
</div>
|
|
359
|
+
<div className="flex-1 min-w-0">
|
|
360
|
+
<div className="flex items-center gap-2 mb-1">
|
|
361
|
+
<h4 className="font-semibold text-sm">{isActionLog ? actionName : usageType}</h4>
|
|
362
|
+
<span className="text-xs px-2 py-0.5 rounded bg-muted text-muted-foreground">
|
|
363
|
+
{isActionLog ? 'Action' : modelType}
|
|
364
|
+
</span>
|
|
365
|
+
{action.body?.promptCount && action.body.promptCount > 1 && (
|
|
366
|
+
<Badge variant="secondary" className="text-xs px-1.5">
|
|
367
|
+
{action.body.promptCount} prompts
|
|
368
|
+
</Badge>
|
|
369
|
+
)}
|
|
370
|
+
</div>
|
|
371
|
+
|
|
372
|
+
{/* Model and timing info */}
|
|
373
|
+
<div className="space-y-1">
|
|
374
|
+
{(modelKey || isActionLog) && (
|
|
375
|
+
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
376
|
+
<Zap className="h-3 w-3" />
|
|
377
|
+
<code className="font-mono">
|
|
378
|
+
{isActionLog
|
|
379
|
+
? `Action ID: ${action.body?.actionId?.slice(-8) || 'N/A'}`
|
|
380
|
+
: modelKey}
|
|
381
|
+
</code>
|
|
382
|
+
</div>
|
|
383
|
+
)}
|
|
384
|
+
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
385
|
+
<Clock className="h-3 w-3" />
|
|
386
|
+
<span>{formatDate(action.createdAt || action.timestamp)}</span>
|
|
387
|
+
{action.id && (
|
|
388
|
+
<>
|
|
389
|
+
<span>•</span>
|
|
390
|
+
<code className="text-[10px] bg-muted px-1 rounded">
|
|
391
|
+
{action.id.slice(-8)}
|
|
392
|
+
</code>
|
|
393
|
+
</>
|
|
394
|
+
)}
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
|
|
398
|
+
{/* Token usage */}
|
|
399
|
+
{tokenUsage && (
|
|
400
|
+
<div className="flex items-center gap-4 mt-2 text-xs">
|
|
401
|
+
<div className="flex items-center gap-1">
|
|
402
|
+
<span className="text-muted-foreground">Tokens:</span>
|
|
403
|
+
<span className="font-mono">{tokenUsage.total.toLocaleString()}</span>
|
|
404
|
+
</div>
|
|
405
|
+
{tokenUsage.prompt > 0 && (
|
|
406
|
+
<div className="flex items-center gap-1 text-muted-foreground">
|
|
407
|
+
<span>In:</span>
|
|
408
|
+
<span className="font-mono">{tokenUsage.prompt.toLocaleString()}</span>
|
|
409
|
+
</div>
|
|
410
|
+
)}
|
|
411
|
+
{tokenUsage.completion > 0 && (
|
|
412
|
+
<div className="flex items-center gap-1 text-muted-foreground">
|
|
413
|
+
<span>Out:</span>
|
|
414
|
+
<span className="font-mono">{tokenUsage.completion.toLocaleString()}</span>
|
|
415
|
+
</div>
|
|
416
|
+
)}
|
|
417
|
+
</div>
|
|
418
|
+
)}
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
|
|
422
|
+
{/* Action buttons */}
|
|
423
|
+
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
424
|
+
{hasExtendedContent && (
|
|
425
|
+
<Button
|
|
426
|
+
variant="ghost"
|
|
427
|
+
size="sm"
|
|
428
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
429
|
+
className="h-8 w-8 p-0"
|
|
430
|
+
title={isExpanded ? 'Collapse details' : 'Expand details'}
|
|
431
|
+
>
|
|
432
|
+
{isExpanded ? (
|
|
433
|
+
<ChevronUp className="h-3 w-3" />
|
|
434
|
+
) : (
|
|
435
|
+
<ChevronDown className="h-3 w-3" />
|
|
436
|
+
)}
|
|
437
|
+
</Button>
|
|
438
|
+
)}
|
|
439
|
+
{action.id && onDelete && (
|
|
440
|
+
<Button
|
|
441
|
+
variant="ghost"
|
|
442
|
+
size="sm"
|
|
443
|
+
onClick={() => {
|
|
444
|
+
if (typeof action.id === 'string') {
|
|
445
|
+
onDelete(action.id);
|
|
446
|
+
}
|
|
447
|
+
}}
|
|
448
|
+
className="h-8 w-8 p-0 text-destructive hover:text-destructive"
|
|
449
|
+
title="Delete log entry"
|
|
450
|
+
>
|
|
451
|
+
<Trash2 className="h-3 w-3" />
|
|
452
|
+
</Button>
|
|
453
|
+
)}
|
|
454
|
+
</div>
|
|
455
|
+
</div>
|
|
456
|
+
</div>
|
|
457
|
+
|
|
458
|
+
{/* Expandable content */}
|
|
459
|
+
{isExpanded && hasExtendedContent && (
|
|
460
|
+
<>
|
|
461
|
+
<Separator />
|
|
462
|
+
<div className="p-4 pt-3 space-y-4">
|
|
463
|
+
{renderParams()}
|
|
464
|
+
{renderResponse()}
|
|
465
|
+
|
|
466
|
+
{/* Additional metadata */}
|
|
467
|
+
{(action.message || action.details) && (
|
|
468
|
+
<div className="space-y-2">
|
|
469
|
+
<span className="text-xs font-medium text-muted-foreground">Additional Info</span>
|
|
470
|
+
<div className="bg-muted/30 rounded-md p-3">
|
|
471
|
+
{action.message && (
|
|
472
|
+
<div className="text-xs mb-2">
|
|
473
|
+
<span className="font-medium text-muted-foreground">Message: </span>
|
|
474
|
+
<span>{action.message}</span>
|
|
475
|
+
</div>
|
|
476
|
+
)}
|
|
477
|
+
{action.details && (
|
|
478
|
+
<div className="text-xs">
|
|
479
|
+
<span className="font-medium text-muted-foreground">Details: </span>
|
|
480
|
+
<span>{action.details}</span>
|
|
481
|
+
</div>
|
|
482
|
+
)}
|
|
483
|
+
</div>
|
|
484
|
+
</div>
|
|
485
|
+
)}
|
|
486
|
+
</div>
|
|
487
|
+
</>
|
|
488
|
+
)}
|
|
489
|
+
|
|
490
|
+
{/* Quick preview for collapsed state */}
|
|
491
|
+
{!isExpanded && hasExtendedContent && (
|
|
492
|
+
<>
|
|
493
|
+
<Separator />
|
|
494
|
+
<div className="p-4 pt-3">
|
|
495
|
+
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
496
|
+
<AlertCircle className="h-3 w-3" />
|
|
497
|
+
<span>
|
|
498
|
+
{(() => {
|
|
499
|
+
const parts = [];
|
|
500
|
+
if (action.body?.promptCount && action.body.promptCount > 0) {
|
|
501
|
+
parts.push(
|
|
502
|
+
`${action.body.promptCount} prompt${action.body.promptCount > 1 ? 's' : ''}`
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
if (action.body?.params) parts.push('parameters');
|
|
506
|
+
if (action.body?.response) parts.push('response data');
|
|
507
|
+
return parts.length > 0
|
|
508
|
+
? `Contains ${parts.join(' and ')}`
|
|
509
|
+
: 'Contains additional data';
|
|
510
|
+
})()}
|
|
511
|
+
</span>
|
|
512
|
+
<Button
|
|
513
|
+
variant="ghost"
|
|
514
|
+
size="sm"
|
|
515
|
+
onClick={() => setIsExpanded(true)}
|
|
516
|
+
className="h-5 px-2 text-xs ml-auto"
|
|
517
|
+
>
|
|
518
|
+
View details
|
|
519
|
+
</Button>
|
|
520
|
+
</div>
|
|
521
|
+
</div>
|
|
522
|
+
</>
|
|
523
|
+
)}
|
|
524
|
+
</div>
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function LoadingIndicator({
|
|
529
|
+
loadingMore,
|
|
530
|
+
onLoadMore,
|
|
531
|
+
}: {
|
|
532
|
+
loadingMore: boolean;
|
|
533
|
+
onLoadMore: () => void;
|
|
534
|
+
}) {
|
|
535
|
+
return (
|
|
536
|
+
<div className="flex justify-center py-6">
|
|
537
|
+
{loadingMore ? (
|
|
538
|
+
<div className="flex items-center gap-2 text-muted-foreground">
|
|
539
|
+
<LoaderIcon className="h-4 w-4 animate-spin" />
|
|
540
|
+
<span className="text-sm">Loading more actions...</span>
|
|
541
|
+
</div>
|
|
542
|
+
) : (
|
|
543
|
+
<Button variant="outline" onClick={onLoadMore} className="px-8">
|
|
544
|
+
Load More
|
|
545
|
+
</Button>
|
|
546
|
+
)}
|
|
547
|
+
</div>
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function EmptyState({
|
|
552
|
+
selectedType,
|
|
553
|
+
searchQuery,
|
|
554
|
+
}: {
|
|
555
|
+
selectedType: ActionType;
|
|
556
|
+
searchQuery: string;
|
|
557
|
+
}) {
|
|
558
|
+
return (
|
|
559
|
+
<div className="flex flex-col items-center justify-center flex-1 text-center p-8">
|
|
560
|
+
<Database className="h-16 w-16 text-muted-foreground/30 mb-4" />
|
|
561
|
+
<h3 className="text-lg font-medium mb-2">No Actions Found</h3>
|
|
562
|
+
<p className="text-muted-foreground max-w-md mb-4">
|
|
563
|
+
{searchQuery
|
|
564
|
+
? `No actions match "${searchQuery}". Try adjusting your search or filter.`
|
|
565
|
+
: selectedType === ActionType.all
|
|
566
|
+
? 'Actions will appear here once the agent has performed operations.'
|
|
567
|
+
: `No ${selectedType} actions found.`}
|
|
568
|
+
</p>
|
|
569
|
+
{searchQuery && (
|
|
570
|
+
<Button variant="outline" onClick={() => {}}>
|
|
571
|
+
Clear Search
|
|
572
|
+
</Button>
|
|
573
|
+
)}
|
|
574
|
+
</div>
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
export function AgentActionViewer({ agentId, roomId }: AgentActionViewerProps) {
|
|
579
|
+
const [selectedType, setSelectedType] = useState<ActionType>(ActionType.all);
|
|
580
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
581
|
+
const [visibleItems, setVisibleItems] = useState(ITEMS_PER_PAGE);
|
|
582
|
+
const [loadingMore, setLoadingMore] = useState(false);
|
|
583
|
+
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
584
|
+
|
|
585
|
+
// Exclude embedding operations by default
|
|
586
|
+
const excludeTypes = ['embedding', 'text_embedding'];
|
|
587
|
+
|
|
588
|
+
const { data: actions = [], isLoading, error } = useAgentActions(agentId, roomId, excludeTypes);
|
|
589
|
+
const { mutate: deleteLog } = useDeleteLog();
|
|
590
|
+
|
|
591
|
+
const { confirm, isOpen, onOpenChange, onConfirm, options } = useConfirmation();
|
|
592
|
+
|
|
593
|
+
// Filter and search actions
|
|
594
|
+
const filteredActions = actions.filter((action: AgentLog) => {
|
|
595
|
+
// Type filter
|
|
596
|
+
if (selectedType !== ActionType.all) {
|
|
597
|
+
const modelType = action.body?.modelType || '';
|
|
598
|
+
const usageType = getModelUsageType(modelType);
|
|
599
|
+
const isActionLog = action.type === 'action';
|
|
600
|
+
|
|
601
|
+
switch (selectedType) {
|
|
602
|
+
case ActionType.llm:
|
|
603
|
+
// Include both LLM calls and actions (which often contain LLM prompts)
|
|
604
|
+
if (usageType !== 'LLM' && !isActionLog) return false;
|
|
605
|
+
break;
|
|
606
|
+
case ActionType.transcription:
|
|
607
|
+
if (usageType !== 'Transcription') return false;
|
|
608
|
+
break;
|
|
609
|
+
case ActionType.image:
|
|
610
|
+
if (usageType !== 'Image') return false;
|
|
611
|
+
break;
|
|
612
|
+
case ActionType.other:
|
|
613
|
+
if (usageType !== 'Other' && usageType !== 'Unknown' && !isActionLog) return false;
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Search filter
|
|
619
|
+
if (searchQuery.trim()) {
|
|
620
|
+
const query = searchQuery.toLowerCase();
|
|
621
|
+
const searchableText = [
|
|
622
|
+
action.body?.modelType,
|
|
623
|
+
action.body?.modelKey,
|
|
624
|
+
action.id,
|
|
625
|
+
action.message,
|
|
626
|
+
JSON.stringify(action.body?.params),
|
|
627
|
+
JSON.stringify(action.body?.response),
|
|
628
|
+
]
|
|
629
|
+
.filter(Boolean)
|
|
630
|
+
.join(' ')
|
|
631
|
+
.toLowerCase();
|
|
632
|
+
|
|
633
|
+
return searchableText.includes(query);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return true;
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
// Handle scroll for infinite loading
|
|
640
|
+
const handleScroll = useCallback(() => {
|
|
641
|
+
if (!scrollContainerRef.current || loadingMore || visibleItems >= filteredActions.length) {
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const { scrollTop, scrollHeight, clientHeight } = scrollContainerRef.current;
|
|
646
|
+
const scrolledToBottom = scrollTop + clientHeight >= scrollHeight - 200;
|
|
647
|
+
|
|
648
|
+
if (scrolledToBottom) {
|
|
649
|
+
setLoadingMore(true);
|
|
650
|
+
setTimeout(() => {
|
|
651
|
+
setVisibleItems((prev) => Math.min(prev + ITEMS_PER_PAGE, filteredActions.length));
|
|
652
|
+
setLoadingMore(false);
|
|
653
|
+
}, 500);
|
|
654
|
+
}
|
|
655
|
+
}, [loadingMore, visibleItems, filteredActions.length]);
|
|
656
|
+
|
|
657
|
+
// Reset visible items when filter changes
|
|
658
|
+
useEffect(() => {
|
|
659
|
+
setVisibleItems(ITEMS_PER_PAGE);
|
|
660
|
+
}, [selectedType, searchQuery]);
|
|
661
|
+
|
|
662
|
+
// Set up scroll listener
|
|
663
|
+
useEffect(() => {
|
|
664
|
+
const scrollContainer = scrollContainerRef.current;
|
|
665
|
+
if (scrollContainer) {
|
|
666
|
+
scrollContainer.addEventListener('scroll', handleScroll);
|
|
667
|
+
return () => scrollContainer.removeEventListener('scroll', handleScroll);
|
|
668
|
+
}
|
|
669
|
+
}, [handleScroll]);
|
|
670
|
+
|
|
671
|
+
const visibleActions = filteredActions.slice(0, visibleItems);
|
|
672
|
+
const hasMoreToLoad = visibleItems < filteredActions.length;
|
|
673
|
+
const actionGroups = groupActionsByDate(visibleActions);
|
|
674
|
+
|
|
675
|
+
const handleDelete = (logId: string) => {
|
|
676
|
+
confirm(
|
|
677
|
+
{
|
|
678
|
+
title: 'Delete Log Entry',
|
|
679
|
+
description:
|
|
680
|
+
'Are you sure you want to permanently delete this log entry? This action cannot be undone.',
|
|
681
|
+
confirmText: 'Delete',
|
|
682
|
+
variant: 'destructive',
|
|
683
|
+
},
|
|
684
|
+
() => {
|
|
685
|
+
deleteLog({ agentId, logId: logId as UUID });
|
|
686
|
+
}
|
|
687
|
+
);
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
const handleLoadMore = () => {
|
|
691
|
+
setVisibleItems((prev) => prev + ITEMS_PER_PAGE);
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
// Loading state
|
|
695
|
+
if (isLoading && actions.length === 0) {
|
|
696
|
+
return (
|
|
697
|
+
<div className="flex flex-col h-[calc(100vh-100px)] min-h-[400px] w-full">
|
|
698
|
+
<div className="flex items-center justify-center flex-1">
|
|
699
|
+
<div className="flex flex-col items-center gap-4">
|
|
700
|
+
<LoaderIcon className="h-8 w-8 animate-spin text-muted-foreground" />
|
|
701
|
+
<div className="text-center">
|
|
702
|
+
<h3 className="font-medium">Loading Actions</h3>
|
|
703
|
+
<p className="text-sm text-muted-foreground">Fetching agent action history...</p>
|
|
704
|
+
</div>
|
|
705
|
+
</div>
|
|
706
|
+
</div>
|
|
707
|
+
</div>
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Error state
|
|
712
|
+
if (error) {
|
|
713
|
+
return (
|
|
714
|
+
<div className="flex flex-col h-[calc(100vh-100px)] min-h-[400px] w-full">
|
|
715
|
+
<div className="flex items-center justify-center flex-1">
|
|
716
|
+
<div className="text-center">
|
|
717
|
+
<Database className="h-12 w-12 text-destructive mx-auto mb-4" />
|
|
718
|
+
<h3 className="font-medium text-destructive">Failed to Load Actions</h3>
|
|
719
|
+
<p className="text-sm text-muted-foreground">
|
|
720
|
+
There was an error loading the agent actions.
|
|
721
|
+
</p>
|
|
722
|
+
</div>
|
|
723
|
+
</div>
|
|
724
|
+
</div>
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return (
|
|
729
|
+
<>
|
|
730
|
+
<div className="flex flex-col h-[calc(100vh-100px)] min-h-[400px] w-full">
|
|
731
|
+
{/* Header */}
|
|
732
|
+
<div className="flex justify-between items-center mb-4 px-4 pt-4 flex-none border-b pb-3">
|
|
733
|
+
<div className="flex items-center gap-2">
|
|
734
|
+
<h3 className="text-lg font-medium"> Actions</h3>
|
|
735
|
+
{!isLoading && (
|
|
736
|
+
<span className="ml-2 text-xs px-2 py-1 rounded bg-muted text-muted-foreground">
|
|
737
|
+
{filteredActions.length}
|
|
738
|
+
</span>
|
|
739
|
+
)}
|
|
740
|
+
</div>
|
|
741
|
+
<div className="flex items-center gap-2">
|
|
742
|
+
{/* Search */}
|
|
743
|
+
<div className="relative">
|
|
744
|
+
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
745
|
+
<Input
|
|
746
|
+
placeholder="Search actions..."
|
|
747
|
+
value={searchQuery}
|
|
748
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
749
|
+
className="pl-10 w-64"
|
|
750
|
+
/>
|
|
751
|
+
</div>
|
|
752
|
+
{/* Filter */}
|
|
753
|
+
<Select
|
|
754
|
+
value={selectedType}
|
|
755
|
+
onValueChange={(value) => setSelectedType(value as ActionType)}
|
|
756
|
+
>
|
|
757
|
+
<SelectTrigger className="w-40">
|
|
758
|
+
<SelectValue placeholder="Filter actions" />
|
|
759
|
+
</SelectTrigger>
|
|
760
|
+
<SelectContent>
|
|
761
|
+
<SelectItem value={ActionType.all}>All Actions</SelectItem>
|
|
762
|
+
<SelectItem value={ActionType.llm}>LLM Calls</SelectItem>
|
|
763
|
+
<SelectItem value={ActionType.transcription}>Transcriptions</SelectItem>
|
|
764
|
+
<SelectItem value={ActionType.image}>Image Operations</SelectItem>
|
|
765
|
+
<SelectItem value={ActionType.other}>Other</SelectItem>
|
|
766
|
+
</SelectContent>
|
|
767
|
+
</Select>
|
|
768
|
+
</div>
|
|
769
|
+
</div>
|
|
770
|
+
|
|
771
|
+
{/* Content */}
|
|
772
|
+
<div ref={scrollContainerRef} className="flex-1 overflow-y-auto px-4">
|
|
773
|
+
{filteredActions.length === 0 ? (
|
|
774
|
+
<EmptyState selectedType={selectedType} searchQuery={searchQuery} />
|
|
775
|
+
) : (
|
|
776
|
+
<div className="space-y-4">
|
|
777
|
+
{Object.entries(actionGroups).map(([date, actions]) => (
|
|
778
|
+
<div key={date} className="space-y-3">
|
|
779
|
+
<div className="flex items-center gap-3 py-2">
|
|
780
|
+
<Separator className="flex-1" />
|
|
781
|
+
<span className="text-sm font-medium text-muted-foreground px-2">{date}</span>
|
|
782
|
+
<Separator className="flex-1" />
|
|
783
|
+
</div>
|
|
784
|
+
<div className="space-y-3">
|
|
785
|
+
{actions.map((action, index) => (
|
|
786
|
+
<ActionCard
|
|
787
|
+
key={action.id || index}
|
|
788
|
+
action={action}
|
|
789
|
+
onDelete={handleDelete}
|
|
790
|
+
/>
|
|
791
|
+
))}
|
|
792
|
+
</div>
|
|
793
|
+
</div>
|
|
794
|
+
))}
|
|
795
|
+
|
|
796
|
+
{/* Load more */}
|
|
797
|
+
{hasMoreToLoad && (
|
|
798
|
+
<LoadingIndicator loadingMore={loadingMore} onLoadMore={handleLoadMore} />
|
|
799
|
+
)}
|
|
800
|
+
</div>
|
|
801
|
+
)}
|
|
802
|
+
</div>
|
|
803
|
+
</div>
|
|
804
|
+
<ConfirmationDialog
|
|
805
|
+
open={isOpen}
|
|
806
|
+
onOpenChange={onOpenChange}
|
|
807
|
+
title={options?.title || ''}
|
|
808
|
+
description={options?.description || ''}
|
|
809
|
+
confirmText={options?.confirmText}
|
|
810
|
+
cancelText={options?.cancelText}
|
|
811
|
+
variant={options?.variant}
|
|
812
|
+
onConfirm={onConfirm}
|
|
813
|
+
/>
|
|
814
|
+
</>
|
|
815
|
+
);
|
|
816
|
+
}
|