@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,121 @@
|
|
|
1
|
+
import { UUID } from '@elizaos/core';
|
|
2
|
+
import { Avatar, AvatarImage } from './ui/avatar';
|
|
3
|
+
import { formatAgentName } from '@/lib/utils';
|
|
4
|
+
import { useState } from 'react';
|
|
5
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
|
|
6
|
+
|
|
7
|
+
interface AgentAvatarStackProps {
|
|
8
|
+
agentIds: UUID[];
|
|
9
|
+
agentNames: string[];
|
|
10
|
+
agentAvatars: Record<string, string | null>;
|
|
11
|
+
size?: 'sm' | 'md' | 'lg';
|
|
12
|
+
maxStack?: number;
|
|
13
|
+
showExtraTooltip?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default function AgentAvatarStack({
|
|
17
|
+
agentIds,
|
|
18
|
+
agentNames,
|
|
19
|
+
agentAvatars,
|
|
20
|
+
size = 'md',
|
|
21
|
+
maxStack = 2,
|
|
22
|
+
showExtraTooltip = false,
|
|
23
|
+
}: AgentAvatarStackProps) {
|
|
24
|
+
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
|
25
|
+
const displayAgents = agentIds.slice(0, maxStack);
|
|
26
|
+
const isMultiple = displayAgents.length > 1;
|
|
27
|
+
const hiddenCount = agentIds.length - maxStack;
|
|
28
|
+
const showExtra = showExtraTooltip && agentIds.length > maxStack;
|
|
29
|
+
|
|
30
|
+
const baseSize = size === 'sm' ? 24 : size === 'lg' ? 40 : 32;
|
|
31
|
+
const avatarSizeClass = isMultiple
|
|
32
|
+
? size === 'sm'
|
|
33
|
+
? 'size-6'
|
|
34
|
+
: size === 'lg'
|
|
35
|
+
? 'size-10'
|
|
36
|
+
: 'size-8'
|
|
37
|
+
: size === 'sm'
|
|
38
|
+
? 'size-6'
|
|
39
|
+
: size === 'lg'
|
|
40
|
+
? 'size-10'
|
|
41
|
+
: 'size-8';
|
|
42
|
+
|
|
43
|
+
const visibleCount = showExtra ? maxStack + 1 : maxStack;
|
|
44
|
+
const overlapFactor = showExtraTooltip ? 1 : 0.6;
|
|
45
|
+
const avatarOffset = Math.floor(baseSize * (overlapFactor / visibleCount));
|
|
46
|
+
|
|
47
|
+
const getAvatarContent = (agentId: UUID, index: number) => {
|
|
48
|
+
const avatarSrc = agentAvatars[agentId] || '/elizaos-icon.png';
|
|
49
|
+
return agentAvatars[agentId] ? (
|
|
50
|
+
<AvatarImage src={avatarSrc} alt="Agent avatar" />
|
|
51
|
+
) : (
|
|
52
|
+
<div className="rounded-full bg-gray-600 w-full h-full flex-shrink-0 flex items-center justify-center">
|
|
53
|
+
{formatAgentName(agentNames[index])}
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const handleMouseEnter = (index: number) => {
|
|
59
|
+
if (showExtraTooltip) {
|
|
60
|
+
setHoveredIndex(index);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const handleMouseLeave = () => {
|
|
65
|
+
setHoveredIndex(null);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div
|
|
70
|
+
className="relative flex items-center text-xs"
|
|
71
|
+
style={{ height: baseSize, width: baseSize }}
|
|
72
|
+
>
|
|
73
|
+
{displayAgents.length === 1 ? (
|
|
74
|
+
<Avatar className={`${avatarSizeClass} rounded-full overflow-hidden`}>
|
|
75
|
+
{getAvatarContent(displayAgents[0], 0)}
|
|
76
|
+
</Avatar>
|
|
77
|
+
) : (
|
|
78
|
+
<>
|
|
79
|
+
{displayAgents.map((agentId, index) => (
|
|
80
|
+
<Avatar
|
|
81
|
+
onMouseEnter={() => handleMouseEnter(index)}
|
|
82
|
+
onMouseLeave={() => handleMouseLeave()}
|
|
83
|
+
key={agentId}
|
|
84
|
+
className={`${avatarSizeClass} rounded-full overflow-hidden absolute border border-2 border-card`}
|
|
85
|
+
style={{
|
|
86
|
+
zIndex: hoveredIndex === index ? agentIds.length + 1 : index,
|
|
87
|
+
left: `${(index - (visibleCount - 1) / 2) * avatarOffset}px`,
|
|
88
|
+
top: `${(index - (visibleCount - 1) / 2) * avatarOffset}px`,
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
{getAvatarContent(agentId, index)}
|
|
92
|
+
</Avatar>
|
|
93
|
+
))}
|
|
94
|
+
{showExtra && (
|
|
95
|
+
<Tooltip>
|
|
96
|
+
<TooltipTrigger asChild>
|
|
97
|
+
<div
|
|
98
|
+
className={`${avatarSizeClass} rounded-full bg-gray-500 text-foreground/60 flex items-center justify-center absolute border border-2 border-card`}
|
|
99
|
+
style={{
|
|
100
|
+
zIndex: displayAgents.length,
|
|
101
|
+
left: `${(displayAgents.length - (visibleCount - 1) / 2) * avatarOffset}px`,
|
|
102
|
+
top: `${(displayAgents.length - (visibleCount - 1) / 2) * avatarOffset}px`,
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
+{hiddenCount}
|
|
106
|
+
</div>
|
|
107
|
+
</TooltipTrigger>
|
|
108
|
+
<TooltipContent side="bottom">
|
|
109
|
+
<div className="flex flex-col">
|
|
110
|
+
{agentNames.slice(maxStack).map((name, index) => (
|
|
111
|
+
<span key={index}>{name}</span>
|
|
112
|
+
))}
|
|
113
|
+
</div>
|
|
114
|
+
</TooltipContent>
|
|
115
|
+
</Tooltip>
|
|
116
|
+
)}
|
|
117
|
+
</>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/// <reference types="cypress" />
|
|
2
|
+
/// <reference path="../../cypress/support/types.d.ts" />
|
|
3
|
+
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import type { AgentWithStatus } from '@/types';
|
|
6
|
+
import { AgentStatus } from '@elizaos/core';
|
|
7
|
+
|
|
8
|
+
// Create a minimal test component that represents AgentCard functionality
|
|
9
|
+
const TestAgentCard: React.FC<{
|
|
10
|
+
agent: Partial<AgentWithStatus>;
|
|
11
|
+
onChat: (agent: Partial<AgentWithStatus>) => void;
|
|
12
|
+
}> = ({ agent, onChat }) => {
|
|
13
|
+
if (!agent || !agent.id) {
|
|
14
|
+
return <div data-testid="agent-card-error">Agent data not available.</div>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const agentName = agent.name || 'Unnamed Agent';
|
|
18
|
+
const isActive = agent.status === AgentStatus.ACTIVE;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div data-testid="agent-card" className="agent-card">
|
|
22
|
+
<div data-testid="agent-name">{agentName}</div>
|
|
23
|
+
<div
|
|
24
|
+
data-testid="status-indicator"
|
|
25
|
+
className={`status-dot ${isActive ? 'active' : 'inactive'}`}
|
|
26
|
+
/>
|
|
27
|
+
<div data-testid="agent-status">
|
|
28
|
+
{agent.status === AgentStatus.ACTIVE
|
|
29
|
+
? 'active'
|
|
30
|
+
: agent.status === AgentStatus.INACTIVE
|
|
31
|
+
? 'inactive'
|
|
32
|
+
: 'unknown'}
|
|
33
|
+
</div>
|
|
34
|
+
{agent.settings?.avatar && (
|
|
35
|
+
<img
|
|
36
|
+
data-testid="agent-avatar"
|
|
37
|
+
src={
|
|
38
|
+
typeof agent.settings.avatar === 'string' ? agent.settings.avatar : '/elizaos-icon.png'
|
|
39
|
+
}
|
|
40
|
+
alt={agentName}
|
|
41
|
+
/>
|
|
42
|
+
)}
|
|
43
|
+
{!agent.settings?.avatar && (
|
|
44
|
+
<div data-testid="agent-initials">{agentName.substring(0, 2).toUpperCase()}</div>
|
|
45
|
+
)}
|
|
46
|
+
{isActive ? (
|
|
47
|
+
<button data-testid="chat-button" onClick={() => onChat(agent)}>
|
|
48
|
+
Chat
|
|
49
|
+
</button>
|
|
50
|
+
) : (
|
|
51
|
+
<button data-testid="start-button">Start</button>
|
|
52
|
+
)}
|
|
53
|
+
<button data-testid="card-button" onClick={() => onChat(agent)} className="card-clickable">
|
|
54
|
+
Card Click
|
|
55
|
+
</button>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
describe('AgentCard Component', () => {
|
|
61
|
+
const mockAgent: Partial<AgentWithStatus> = {
|
|
62
|
+
id: '12345678-1234-1234-1234-123456789012',
|
|
63
|
+
name: 'Test Agent',
|
|
64
|
+
username: 'testagent',
|
|
65
|
+
status: AgentStatus.INACTIVE,
|
|
66
|
+
settings: {
|
|
67
|
+
avatar: 'https://example.com/avatar.png',
|
|
68
|
+
},
|
|
69
|
+
bio: 'Test agent bio',
|
|
70
|
+
enabled: true,
|
|
71
|
+
createdAt: Date.now(),
|
|
72
|
+
updatedAt: Date.now(),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const activeAgent: Partial<AgentWithStatus> = {
|
|
76
|
+
...mockAgent,
|
|
77
|
+
status: AgentStatus.ACTIVE,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
it('renders agent information correctly', () => {
|
|
81
|
+
const onChat = cy.stub();
|
|
82
|
+
|
|
83
|
+
cy.mount(<TestAgentCard agent={mockAgent} onChat={onChat} />);
|
|
84
|
+
|
|
85
|
+
// Check agent name is displayed
|
|
86
|
+
cy.get('[data-testid="agent-name"]').should('contain.text', 'Test Agent');
|
|
87
|
+
|
|
88
|
+
// Check status indicator exists
|
|
89
|
+
cy.get('[data-testid="status-indicator"]').should('exist');
|
|
90
|
+
|
|
91
|
+
// Check agent card exists
|
|
92
|
+
cy.get('[data-testid="agent-card"]').should('exist');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('displays active agent correctly', () => {
|
|
96
|
+
const onChat = cy.stub();
|
|
97
|
+
|
|
98
|
+
cy.mount(<TestAgentCard agent={activeAgent} onChat={onChat} />);
|
|
99
|
+
|
|
100
|
+
// Status should show active
|
|
101
|
+
cy.get('[data-testid="agent-status"]').should('contain.text', 'active');
|
|
102
|
+
|
|
103
|
+
// Chat button should be visible
|
|
104
|
+
cy.get('[data-testid="chat-button"]').should('exist');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('handles missing agent data gracefully', () => {
|
|
108
|
+
const onChat = cy.stub();
|
|
109
|
+
|
|
110
|
+
cy.mount(<TestAgentCard agent={{}} onChat={onChat} />);
|
|
111
|
+
|
|
112
|
+
// Should show error message
|
|
113
|
+
cy.get('[data-testid="agent-card-error"]').should('contain.text', 'Agent data not available');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('shows start button for inactive agents', () => {
|
|
117
|
+
const onChat = cy.stub();
|
|
118
|
+
|
|
119
|
+
cy.mount(<TestAgentCard agent={mockAgent} onChat={onChat} />);
|
|
120
|
+
|
|
121
|
+
// Start button should be visible
|
|
122
|
+
cy.get('[data-testid="start-button"]').should('contain.text', 'Start');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('shows chat button for active agents', () => {
|
|
126
|
+
const onChat = cy.stub();
|
|
127
|
+
|
|
128
|
+
cy.mount(<TestAgentCard agent={activeAgent} onChat={onChat} />);
|
|
129
|
+
|
|
130
|
+
// Chat button should be visible
|
|
131
|
+
cy.get('[data-testid="chat-button"]').should('contain.text', 'Chat');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('handles chat button click for active agents', () => {
|
|
135
|
+
const onChat = cy.stub();
|
|
136
|
+
|
|
137
|
+
cy.mount(<TestAgentCard agent={activeAgent} onChat={onChat} />);
|
|
138
|
+
|
|
139
|
+
// Click chat button
|
|
140
|
+
cy.get('[data-testid="chat-button"]').click();
|
|
141
|
+
|
|
142
|
+
// Verify onChat was called
|
|
143
|
+
cy.wrap(onChat).should('have.been.calledWith', activeAgent);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('navigates when card is clicked', () => {
|
|
147
|
+
const onChat = cy.stub();
|
|
148
|
+
|
|
149
|
+
cy.mount(<TestAgentCard agent={mockAgent} onChat={onChat} />);
|
|
150
|
+
|
|
151
|
+
// Click the card button
|
|
152
|
+
cy.get('[data-testid="card-button"]').click();
|
|
153
|
+
|
|
154
|
+
// Verify onChat was called
|
|
155
|
+
cy.wrap(onChat).should('have.been.calledWith', mockAgent);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('shows agent avatar when provided', () => {
|
|
159
|
+
const onChat = cy.stub();
|
|
160
|
+
|
|
161
|
+
cy.mount(<TestAgentCard agent={mockAgent} onChat={onChat} />);
|
|
162
|
+
|
|
163
|
+
// Should show avatar
|
|
164
|
+
cy.get('[data-testid="agent-avatar"]').should('exist');
|
|
165
|
+
cy.get('[data-testid="agent-avatar"]').should(
|
|
166
|
+
'have.attr',
|
|
167
|
+
'src',
|
|
168
|
+
'https://example.com/avatar.png'
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('shows initials fallback when no avatar', () => {
|
|
173
|
+
const onChat = cy.stub();
|
|
174
|
+
const agentWithoutAvatar = {
|
|
175
|
+
...mockAgent,
|
|
176
|
+
settings: {},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
cy.mount(<TestAgentCard agent={agentWithoutAvatar} onChat={onChat} />);
|
|
180
|
+
|
|
181
|
+
// Should show initials fallback
|
|
182
|
+
cy.get('[data-testid="agent-initials"]').should('contain.text', 'TE');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('displays agent status correctly', () => {
|
|
186
|
+
const onChat = cy.stub();
|
|
187
|
+
|
|
188
|
+
cy.mount(<TestAgentCard agent={mockAgent} onChat={onChat} />);
|
|
189
|
+
|
|
190
|
+
// Check status is displayed
|
|
191
|
+
cy.get('[data-testid="agent-status"]').should('contain.text', 'inactive');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('handles different button states', () => {
|
|
195
|
+
const onChat = cy.stub();
|
|
196
|
+
|
|
197
|
+
// Test inactive agent - should show start button
|
|
198
|
+
cy.mount(<TestAgentCard agent={mockAgent} onChat={onChat} />);
|
|
199
|
+
cy.get('[data-testid="start-button"]').should('exist');
|
|
200
|
+
|
|
201
|
+
// Test active agent - should show chat button
|
|
202
|
+
cy.mount(<TestAgentCard agent={activeAgent} onChat={onChat} />);
|
|
203
|
+
cy.get('[data-testid="chat-button"]').should('exist');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('handles long agent names', () => {
|
|
207
|
+
const onChat = cy.stub();
|
|
208
|
+
const longNameAgent = {
|
|
209
|
+
...mockAgent,
|
|
210
|
+
name: 'This is a very long agent name that should be displayed',
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
cy.mount(<TestAgentCard agent={longNameAgent} onChat={onChat} />);
|
|
214
|
+
|
|
215
|
+
// Check that name is displayed
|
|
216
|
+
cy.get('[data-testid="agent-name"]').should('contain.text', 'This is a very long agent name');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('renders with proper structure', () => {
|
|
220
|
+
const onChat = cy.stub();
|
|
221
|
+
|
|
222
|
+
cy.mount(<TestAgentCard agent={mockAgent} onChat={onChat} />);
|
|
223
|
+
|
|
224
|
+
// Check basic structure elements exist
|
|
225
|
+
cy.get('[data-testid="agent-card"]').should('exist');
|
|
226
|
+
cy.get('[data-testid="agent-name"]').should('exist');
|
|
227
|
+
cy.get('[data-testid="status-indicator"]').should('exist');
|
|
228
|
+
cy.get('[data-testid="agent-status"]').should('exist');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('handles button interactions', () => {
|
|
232
|
+
const onChat = cy.stub();
|
|
233
|
+
|
|
234
|
+
cy.mount(<TestAgentCard agent={activeAgent} onChat={onChat} />);
|
|
235
|
+
|
|
236
|
+
// Click chat button
|
|
237
|
+
cy.get('[data-testid="chat-button"]').click();
|
|
238
|
+
cy.wrap(onChat).should('have.been.calledWith', activeAgent);
|
|
239
|
+
|
|
240
|
+
// Click card button
|
|
241
|
+
cy.get('[data-testid="card-button"]').click();
|
|
242
|
+
cy.wrap(onChat).should('have.been.calledWith', activeAgent);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('shows appropriate content for different agent states', () => {
|
|
246
|
+
const onChat = cy.stub();
|
|
247
|
+
|
|
248
|
+
// Test with different agent configurations
|
|
249
|
+
const testCases = [
|
|
250
|
+
{ agent: mockAgent, expectedButton: 'start-button' },
|
|
251
|
+
{ agent: activeAgent, expectedButton: 'chat-button' },
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
testCases.forEach(({ agent, expectedButton }) => {
|
|
255
|
+
cy.mount(<TestAgentCard agent={agent} onChat={onChat} />);
|
|
256
|
+
cy.get(`[data-testid="${expectedButton}"]`).should('exist');
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useNavigate } from 'react-router-dom';
|
|
3
|
+
import { Card, CardContent } from '@/components/ui/card';
|
|
4
|
+
import { Button } from '@/components/ui/button';
|
|
5
|
+
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
|
|
6
|
+
import { Switch } from '@/components/ui/switch';
|
|
7
|
+
import { formatAgentName, cn, getAgentAvatar } from '@/lib/utils';
|
|
8
|
+
import type { Agent } from '@elizaos/core';
|
|
9
|
+
import { AgentStatus as CoreAgentStatus } from '@elizaos/core';
|
|
10
|
+
import { Settings } from 'lucide-react';
|
|
11
|
+
import { useAgentManagement } from '@/hooks/use-agent-management';
|
|
12
|
+
import type { AgentWithStatus } from '@/types';
|
|
13
|
+
import clientLogger from '@/lib/logger';
|
|
14
|
+
|
|
15
|
+
interface AgentCardProps {
|
|
16
|
+
agent: Partial<AgentWithStatus>;
|
|
17
|
+
onChat: (forceNew: boolean) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const AgentCard: React.FC<AgentCardProps> = ({ agent, onChat }) => {
|
|
21
|
+
const navigate = useNavigate();
|
|
22
|
+
const { startAgent, stopAgent, isAgentStarting, isAgentStopping } = useAgentManagement();
|
|
23
|
+
|
|
24
|
+
if (!agent || !agent.id) {
|
|
25
|
+
clientLogger.error('[AgentCard] Agent data or ID is missing', { agent });
|
|
26
|
+
return (
|
|
27
|
+
<Card className="p-4 min-h-[100px] flex items-center justify-center text-muted-foreground">
|
|
28
|
+
Agent data not available.
|
|
29
|
+
</Card>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const agentIdForNav = agent.id;
|
|
34
|
+
const agentName = agent.name || 'Unnamed Agent';
|
|
35
|
+
|
|
36
|
+
const description = Array.isArray(agent.bio)
|
|
37
|
+
? agent.bio.filter(Boolean).join(' ').trim()
|
|
38
|
+
: (typeof agent.bio === 'string' && agent.bio.trim()) ||
|
|
39
|
+
'Engages with all types of questions and conversations';
|
|
40
|
+
const isActive = agent.status === CoreAgentStatus.ACTIVE;
|
|
41
|
+
const isStarting = isAgentStarting(agent.id);
|
|
42
|
+
const isStopping = isAgentStopping(agent.id);
|
|
43
|
+
|
|
44
|
+
const agentForMutation: Agent = {
|
|
45
|
+
id: agent.id!,
|
|
46
|
+
name: agentName,
|
|
47
|
+
username: agent.username || agentName,
|
|
48
|
+
bio: agent.bio || '',
|
|
49
|
+
messageExamples: agent.messageExamples || [],
|
|
50
|
+
postExamples: agent.postExamples || [],
|
|
51
|
+
topics: agent.topics || [],
|
|
52
|
+
adjectives: agent.adjectives || [],
|
|
53
|
+
knowledge: agent.knowledge || [],
|
|
54
|
+
plugins: agent.plugins || [],
|
|
55
|
+
settings: agent.settings || {},
|
|
56
|
+
secrets: agent.secrets || {},
|
|
57
|
+
style: agent.style || {},
|
|
58
|
+
system: agent.system || undefined,
|
|
59
|
+
templates: agent.templates || {},
|
|
60
|
+
enabled: typeof agent.enabled === 'boolean' ? agent.enabled : true,
|
|
61
|
+
status: agent.status || CoreAgentStatus.INACTIVE,
|
|
62
|
+
createdAt: typeof agent.createdAt === 'number' ? agent.createdAt : Date.now(),
|
|
63
|
+
updatedAt: typeof agent.updatedAt === 'number' ? agent.updatedAt : Date.now(),
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const handleStart = () => {
|
|
67
|
+
startAgent(agentForMutation);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const handleStop = () => {
|
|
71
|
+
stopAgent(agentForMutation);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const handleNewChat = (forceNew: boolean = false) => {
|
|
75
|
+
onChat(forceNew);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const handleSettings = () => {
|
|
79
|
+
navigate(`/settings/${agentIdForNav}`);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const handleToggle = () => {
|
|
83
|
+
if (isActive) {
|
|
84
|
+
handleStop();
|
|
85
|
+
} else {
|
|
86
|
+
handleStart();
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Card
|
|
92
|
+
className={cn(
|
|
93
|
+
'w-full transition-all bg-card border border-border/50 rounded-sm hover:bg-card/50 cursor-pointer',
|
|
94
|
+
isActive ? '' : 'opacity-75'
|
|
95
|
+
)}
|
|
96
|
+
data-testid="agent-card"
|
|
97
|
+
onClick={(e) => {
|
|
98
|
+
e.stopPropagation();
|
|
99
|
+
handleNewChat();
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
<CardContent className="p-0 relative h-full">
|
|
103
|
+
{/* Toggle Switch - positioned absolutely in top-right */}
|
|
104
|
+
<div className="absolute top-3 right-3">
|
|
105
|
+
<Switch
|
|
106
|
+
checked={isActive}
|
|
107
|
+
onCheckedChange={(checked) => {
|
|
108
|
+
if (checked !== isActive) {
|
|
109
|
+
handleToggle();
|
|
110
|
+
}
|
|
111
|
+
}}
|
|
112
|
+
onClick={(e) => e.stopPropagation()}
|
|
113
|
+
aria-label={`Toggle ${agentName}`}
|
|
114
|
+
disabled={isStarting || isStopping}
|
|
115
|
+
className={cn(
|
|
116
|
+
isActive
|
|
117
|
+
? 'data-[state=checked]:!bg-green-600'
|
|
118
|
+
: 'data-[state=unchecked]:!bg-gray-500/80'
|
|
119
|
+
)}
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div className="flex flex-col justify-between h-full">
|
|
124
|
+
<div className="flex items-center gap-4 p-2 h-[90%]">
|
|
125
|
+
{/* Avatar */}
|
|
126
|
+
<Avatar className="h-16 w-16 flex-shrink-0 rounded-sm">
|
|
127
|
+
<AvatarImage src={getAgentAvatar(agent)} alt={agentName} />
|
|
128
|
+
<AvatarFallback className="text-lg font-medium rounded-sm">
|
|
129
|
+
{formatAgentName(agentName)}
|
|
130
|
+
</AvatarFallback>
|
|
131
|
+
</Avatar>
|
|
132
|
+
|
|
133
|
+
{/* Content - Name and Description */}
|
|
134
|
+
<div className="flex-1 min-w-0">
|
|
135
|
+
<h3 className="font-semibold text-xl mb-1 truncate" title={agentName}>
|
|
136
|
+
{agentName}
|
|
137
|
+
</h3>
|
|
138
|
+
<p className="text-sm text-muted-foreground line-clamp-2 leading-relaxed">
|
|
139
|
+
{description}
|
|
140
|
+
</p>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
<div className="border-t border-muted" />
|
|
144
|
+
<div className="flex items-center justify-between py-1 px-2">
|
|
145
|
+
{/* Settings button */}
|
|
146
|
+
<Button
|
|
147
|
+
variant="ghost"
|
|
148
|
+
size="sm"
|
|
149
|
+
onClick={(e) => {
|
|
150
|
+
e.stopPropagation();
|
|
151
|
+
handleSettings();
|
|
152
|
+
}}
|
|
153
|
+
className="h-8 w-8 p-0 hover:bg-muted/50 cursor-pointer"
|
|
154
|
+
>
|
|
155
|
+
<Settings className="h-4 w-4 text-muted-foreground" />
|
|
156
|
+
</Button>
|
|
157
|
+
|
|
158
|
+
{/* New Chat button - ghost variant */}
|
|
159
|
+
<Button
|
|
160
|
+
variant="outline"
|
|
161
|
+
size="sm"
|
|
162
|
+
onClick={(e) => {
|
|
163
|
+
e.stopPropagation();
|
|
164
|
+
handleNewChat(true);
|
|
165
|
+
}}
|
|
166
|
+
className="h-8 px-2 rounded-sm bg-muted hover:bg-muted/50 cursor-pointer"
|
|
167
|
+
>
|
|
168
|
+
New Chat
|
|
169
|
+
</Button>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</CardContent>
|
|
173
|
+
</Card>
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export default AgentCard;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import CharacterForm from '@/components/character-form';
|
|
2
|
+
import { useToast } from '@/hooks/use-toast';
|
|
3
|
+
import { createElizaClient } from '@/lib/api-client-config';
|
|
4
|
+
import type { Agent } from '@elizaos/core';
|
|
5
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
6
|
+
import { useState, useRef } from 'react';
|
|
7
|
+
import { useNavigate } from 'react-router-dom';
|
|
8
|
+
import AvatarPanel from './avatar-panel';
|
|
9
|
+
import PluginsPanel from './plugins-panel';
|
|
10
|
+
import { SecretPanel, type SecretPanelRef } from './secret-panel';
|
|
11
|
+
import { useAgentUpdate } from '@/hooks/use-agent-update';
|
|
12
|
+
import { getTemplateById } from '@/config/agent-templates';
|
|
13
|
+
|
|
14
|
+
// Define a partial agent for initialization from the "none" template
|
|
15
|
+
const defaultCharacter: Partial<Agent> = getTemplateById('none')?.template || {
|
|
16
|
+
name: '',
|
|
17
|
+
username: '',
|
|
18
|
+
system: '',
|
|
19
|
+
bio: [] as string[],
|
|
20
|
+
topics: [] as string[],
|
|
21
|
+
adjectives: [] as string[],
|
|
22
|
+
plugins: ['@elizaos/plugin-sql', '@elizaos/plugin-openai', '@elizaos/plugin-bootstrap'],
|
|
23
|
+
settings: { secrets: {} },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default function AgentCreator() {
|
|
27
|
+
const navigate = useNavigate();
|
|
28
|
+
const { toast } = useToast();
|
|
29
|
+
const queryClient = useQueryClient();
|
|
30
|
+
const [initialCharacter] = useState<Partial<Agent>>({
|
|
31
|
+
...defaultCharacter,
|
|
32
|
+
});
|
|
33
|
+
const secretPanelRef = useRef<SecretPanelRef>(null);
|
|
34
|
+
const [currentSecrets, setCurrentSecrets] = useState<Record<string, string | null>>({});
|
|
35
|
+
|
|
36
|
+
// Use agent update hook for proper handling of nested fields
|
|
37
|
+
const agentState = useAgentUpdate(initialCharacter as Agent);
|
|
38
|
+
|
|
39
|
+
const ensureRequiredFields = (character: Agent): Agent => {
|
|
40
|
+
return {
|
|
41
|
+
...character,
|
|
42
|
+
bio: character.bio ?? [],
|
|
43
|
+
messageExamples: character.messageExamples ?? [],
|
|
44
|
+
postExamples: character.postExamples ?? [],
|
|
45
|
+
topics: character.topics ?? [],
|
|
46
|
+
adjectives: character.adjectives ?? [],
|
|
47
|
+
plugins: character.plugins ?? [],
|
|
48
|
+
style: {
|
|
49
|
+
all: character.style?.all ?? [],
|
|
50
|
+
chat: character.style?.chat ?? [],
|
|
51
|
+
post: character.style?.post ?? [],
|
|
52
|
+
},
|
|
53
|
+
settings: character.settings ?? { secrets: {} },
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const handleSubmit = async (character: Agent) => {
|
|
58
|
+
try {
|
|
59
|
+
const completeCharacter = ensureRequiredFields(character);
|
|
60
|
+
|
|
61
|
+
// Get secrets from state (or ref as fallback)
|
|
62
|
+
const secrets = currentSecrets || secretPanelRef.current?.getSecrets() || {};
|
|
63
|
+
if (secrets && Object.keys(secrets).length > 0) {
|
|
64
|
+
// Add secrets to the character settings
|
|
65
|
+
completeCharacter.settings = {
|
|
66
|
+
...completeCharacter.settings,
|
|
67
|
+
secrets,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const elizaClient = createElizaClient();
|
|
72
|
+
await elizaClient.agents.createAgent({ agent: completeCharacter });
|
|
73
|
+
|
|
74
|
+
// Invalidate the characters query to refresh the characters list
|
|
75
|
+
queryClient.invalidateQueries({ queryKey: ['characters'] });
|
|
76
|
+
|
|
77
|
+
toast({
|
|
78
|
+
title: 'Success',
|
|
79
|
+
description: 'Agent created successfully!',
|
|
80
|
+
});
|
|
81
|
+
queryClient.invalidateQueries({ queryKey: ['agents'] });
|
|
82
|
+
navigate('/');
|
|
83
|
+
} catch (error) {
|
|
84
|
+
toast({
|
|
85
|
+
title: 'Error',
|
|
86
|
+
description: error instanceof Error ? error.message : 'Failed to create agent',
|
|
87
|
+
variant: 'destructive',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div className="h-full w-full">
|
|
94
|
+
<CharacterForm
|
|
95
|
+
characterValue={agentState.agent}
|
|
96
|
+
setCharacterValue={agentState}
|
|
97
|
+
title="Create Agent"
|
|
98
|
+
description="Configure your AI agent's behavior and capabilities."
|
|
99
|
+
onSubmit={handleSubmit}
|
|
100
|
+
onReset={() => {
|
|
101
|
+
agentState.reset();
|
|
102
|
+
setCurrentSecrets({});
|
|
103
|
+
}}
|
|
104
|
+
onDelete={() => {
|
|
105
|
+
navigate('/');
|
|
106
|
+
}}
|
|
107
|
+
onTemplateChange={() => {
|
|
108
|
+
setCurrentSecrets({});
|
|
109
|
+
}}
|
|
110
|
+
isAgent={true}
|
|
111
|
+
secretPanelRef={secretPanelRef}
|
|
112
|
+
customComponents={[
|
|
113
|
+
{
|
|
114
|
+
name: 'Plugins',
|
|
115
|
+
component: (
|
|
116
|
+
<PluginsPanel characterValue={agentState.agent} setCharacterValue={agentState} />
|
|
117
|
+
),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'Secret',
|
|
121
|
+
component: (
|
|
122
|
+
<SecretPanel
|
|
123
|
+
characterValue={agentState.agent}
|
|
124
|
+
ref={secretPanelRef}
|
|
125
|
+
onChange={(secrets) => {
|
|
126
|
+
// Only update local state, don't update agent state to avoid circular updates
|
|
127
|
+
setCurrentSecrets(secrets);
|
|
128
|
+
}}
|
|
129
|
+
/>
|
|
130
|
+
),
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'Avatar',
|
|
134
|
+
component: (
|
|
135
|
+
<AvatarPanel characterValue={agentState.agent} setCharacterValue={agentState} />
|
|
136
|
+
),
|
|
137
|
+
},
|
|
138
|
+
]}
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|