@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,196 @@
|
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
|
+
import { Button } from '@/components/ui/button';
|
|
3
|
+
import { ChatInput } from '@/components/ui/chat/chat-input';
|
|
4
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
|
5
|
+
import { AudioRecorder } from '@/components/audio-recorder';
|
|
6
|
+
import { Loader2, Paperclip, Send, FileText, X } from 'lucide-react';
|
|
7
|
+
import { Agent, UUID, ChannelType } from '@elizaos/core';
|
|
8
|
+
import type { UploadingFile } from '@/hooks/use-file-upload';
|
|
9
|
+
|
|
10
|
+
interface ChatInputAreaProps {
|
|
11
|
+
input: string;
|
|
12
|
+
setInput: (value: string) => void;
|
|
13
|
+
inputDisabled: boolean;
|
|
14
|
+
selectedFiles: UploadingFile[];
|
|
15
|
+
removeFile: (fileId: string) => void;
|
|
16
|
+
handleFileChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
17
|
+
handleSendMessage: (e: React.FormEvent<HTMLFormElement>) => void;
|
|
18
|
+
handleKeyDown: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
|
19
|
+
chatType: ChannelType.DM | ChannelType.GROUP;
|
|
20
|
+
targetAgentData?: Agent;
|
|
21
|
+
formRef: React.RefObject<HTMLFormElement | null>;
|
|
22
|
+
inputRef: React.RefObject<HTMLTextAreaElement | null>;
|
|
23
|
+
fileInputRef: React.RefObject<HTMLInputElement | null>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const ChatInputArea: React.FC<ChatInputAreaProps> = ({
|
|
27
|
+
input,
|
|
28
|
+
setInput,
|
|
29
|
+
inputDisabled,
|
|
30
|
+
selectedFiles,
|
|
31
|
+
removeFile,
|
|
32
|
+
handleFileChange,
|
|
33
|
+
handleSendMessage,
|
|
34
|
+
handleKeyDown,
|
|
35
|
+
chatType,
|
|
36
|
+
targetAgentData,
|
|
37
|
+
formRef,
|
|
38
|
+
inputRef,
|
|
39
|
+
fileInputRef,
|
|
40
|
+
}) => {
|
|
41
|
+
return (
|
|
42
|
+
<div className="px-2 sm:px-4 pb-2 sm:pb-4 mt-auto flex-shrink-0" data-testid="chat-container">
|
|
43
|
+
{inputDisabled && (
|
|
44
|
+
<div className="px-2 pb-2 text-sm text-muted-foreground flex items-center gap-2">
|
|
45
|
+
<div className="flex gap-0.5 items-center justify-center">
|
|
46
|
+
<span className="w-[6px] h-[6px] bg-white rounded-full animate-bounce [animation-delay:0s]" />
|
|
47
|
+
<span className="w-[6px] h-[6px] bg-white rounded-full animate-bounce [animation-delay:0.2s]" />
|
|
48
|
+
<span className="w-[6px] h-[6px] bg-white rounded-full animate-bounce [animation-delay:0.4s]" />
|
|
49
|
+
</div>
|
|
50
|
+
<span className="text-xs sm:text-sm">
|
|
51
|
+
{chatType === ChannelType.DM && targetAgentData
|
|
52
|
+
? `${targetAgentData.name} is thinking`
|
|
53
|
+
: 'Agent is thinking'}
|
|
54
|
+
</span>
|
|
55
|
+
<div className="flex">
|
|
56
|
+
<span className="animate-pulse [animation-delay:0ms]">.</span>
|
|
57
|
+
<span className="animate-pulse [animation-delay:200ms]">.</span>
|
|
58
|
+
<span className="animate-pulse [animation-delay:400ms]">.</span>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
)}
|
|
62
|
+
<form
|
|
63
|
+
ref={formRef}
|
|
64
|
+
onSubmit={handleSendMessage}
|
|
65
|
+
className="relative rounded-md border bg-card p-2 sm:p-3"
|
|
66
|
+
>
|
|
67
|
+
{selectedFiles.length > 0 && (
|
|
68
|
+
<div className="flex flex-wrap gap-2 sm:gap-3 p-2 sm:p-3 pb-0 max-h-32 sm:max-h-40 overflow-y-auto">
|
|
69
|
+
{selectedFiles.map((fileData) => {
|
|
70
|
+
const blobUrl = fileData.blobUrl || URL.createObjectURL(fileData.file);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div key={fileData.id} className="relative p-1 sm:p-2">
|
|
74
|
+
<div className="relative w-12 h-12 sm:w-16 sm:h-16 rounded-lg overflow-hidden border bg-muted">
|
|
75
|
+
{fileData.isUploading && (
|
|
76
|
+
<div className="absolute inset-0 bg-black/50 flex items-center justify-center z-10">
|
|
77
|
+
<Loader2 className="h-3 w-3 sm:h-4 sm:w-4 animate-spin text-white" />
|
|
78
|
+
</div>
|
|
79
|
+
)}
|
|
80
|
+
{fileData.file.type.startsWith('image/') ? (
|
|
81
|
+
<img
|
|
82
|
+
alt="Selected file"
|
|
83
|
+
src={blobUrl}
|
|
84
|
+
className="w-full h-full object-cover"
|
|
85
|
+
/>
|
|
86
|
+
) : fileData.file.type.startsWith('video/') ? (
|
|
87
|
+
<video src={blobUrl} className="w-full h-full object-cover" muted />
|
|
88
|
+
) : (
|
|
89
|
+
<div className="w-full h-full flex items-center justify-center bg-muted">
|
|
90
|
+
<FileText className="h-4 w-4 sm:h-8 sm:w-8 text-muted-foreground" />
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
{fileData.error && (
|
|
94
|
+
<div className="absolute bottom-0 left-0 right-0 bg-red-500 text-white text-xs p-1 text-center">
|
|
95
|
+
Error
|
|
96
|
+
</div>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
<Button
|
|
100
|
+
onClick={() => removeFile(fileData.id)}
|
|
101
|
+
className="absolute -right-1 -top-1 size-[16px] sm:size-[20px] ring-2 ring-background z-20"
|
|
102
|
+
variant="outline"
|
|
103
|
+
size="icon"
|
|
104
|
+
disabled={fileData.isUploading}
|
|
105
|
+
>
|
|
106
|
+
<X className="h-2 w-2 sm:h-3 sm:w-3" />
|
|
107
|
+
</Button>
|
|
108
|
+
<div className="text-xs text-center mt-1 truncate w-12 sm:w-16">
|
|
109
|
+
{fileData.file.name}
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
})}
|
|
114
|
+
</div>
|
|
115
|
+
)}
|
|
116
|
+
<ChatInput
|
|
117
|
+
ref={inputRef}
|
|
118
|
+
onKeyDown={handleKeyDown}
|
|
119
|
+
value={input}
|
|
120
|
+
onChange={({ target }) => setInput(target.value)}
|
|
121
|
+
placeholder={
|
|
122
|
+
inputDisabled
|
|
123
|
+
? chatType === ChannelType.DM && targetAgentData
|
|
124
|
+
? `${targetAgentData.name} is thinking...`
|
|
125
|
+
: 'Agent is processing...'
|
|
126
|
+
: chatType === ChannelType.DM
|
|
127
|
+
? 'Type your message here...'
|
|
128
|
+
: 'Message group...'
|
|
129
|
+
}
|
|
130
|
+
className="min-h-12 resize-none rounded-none bg-card border-0 px-0 py-0 shadow-none focus-visible:ring-0 text-sm sm:text-base"
|
|
131
|
+
disabled={
|
|
132
|
+
inputDisabled || (chatType === ChannelType.DM && targetAgentData?.status === 'inactive')
|
|
133
|
+
}
|
|
134
|
+
data-testid="chat-input"
|
|
135
|
+
/>
|
|
136
|
+
<div className="flex items-center pt-0 gap-1">
|
|
137
|
+
<Tooltip>
|
|
138
|
+
<TooltipTrigger asChild>
|
|
139
|
+
<div>
|
|
140
|
+
<Button
|
|
141
|
+
variant="ghost"
|
|
142
|
+
size="icon"
|
|
143
|
+
className="h-8 w-8 sm:h-10 sm:w-10"
|
|
144
|
+
onClick={() => {
|
|
145
|
+
if (fileInputRef.current) fileInputRef.current.click();
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
<Paperclip className="size-3 sm:size-4" />
|
|
149
|
+
<span className="sr-only">Attach file</span>
|
|
150
|
+
</Button>
|
|
151
|
+
<input
|
|
152
|
+
type="file"
|
|
153
|
+
ref={fileInputRef}
|
|
154
|
+
onChange={handleFileChange}
|
|
155
|
+
accept="image/*,video/*,audio/*,.pdf,.doc,.docx,.txt,.rtf,..."
|
|
156
|
+
multiple
|
|
157
|
+
className="hidden"
|
|
158
|
+
/>
|
|
159
|
+
</div>
|
|
160
|
+
</TooltipTrigger>
|
|
161
|
+
<TooltipContent side="left">
|
|
162
|
+
<p>Attach files</p>
|
|
163
|
+
</TooltipContent>
|
|
164
|
+
</Tooltip>
|
|
165
|
+
{chatType === ChannelType.DM && targetAgentData?.id && (
|
|
166
|
+
<AudioRecorder
|
|
167
|
+
agentId={targetAgentData.id}
|
|
168
|
+
onChange={(newInput: string) => setInput(newInput)}
|
|
169
|
+
/>
|
|
170
|
+
)}
|
|
171
|
+
<Button
|
|
172
|
+
disabled={
|
|
173
|
+
inputDisabled ||
|
|
174
|
+
(chatType === ChannelType.DM && targetAgentData?.status === 'inactive') ||
|
|
175
|
+
selectedFiles.some((f) => f.isUploading)
|
|
176
|
+
}
|
|
177
|
+
type="submit"
|
|
178
|
+
size="sm"
|
|
179
|
+
className="ml-auto gap-1.5 h-[26px] sm:h-[30px] px-2 sm:px-3"
|
|
180
|
+
data-testid="send-button"
|
|
181
|
+
>
|
|
182
|
+
{inputDisabled || selectedFiles.some((f) => f.isUploading) ? (
|
|
183
|
+
<div className="flex gap-0.5 items-center justify-center">
|
|
184
|
+
<span className="w-[4px] h-[4px] bg-gray-500 rounded-full animate-bounce [animation-delay:0s]" />
|
|
185
|
+
<span className="w-[4px] h-[4px] bg-gray-500 rounded-full animate-bounce [animation-delay:0.2s]" />
|
|
186
|
+
<span className="w-[4px] h-[4px] bg-gray-500 rounded-full animate-bounce [animation-delay:0.4s]" />
|
|
187
|
+
</div>
|
|
188
|
+
) : (
|
|
189
|
+
<Send className="size-3 sm:size-3.5" />
|
|
190
|
+
)}
|
|
191
|
+
</Button>
|
|
192
|
+
</div>
|
|
193
|
+
</form>
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Avatar, AvatarImage } from '@/components/ui/avatar';
|
|
3
|
+
import { ChatMessageList } from '@/components/ui/chat/chat-message-list';
|
|
4
|
+
import { ChatBubble } from '@/components/ui/chat/chat-bubble';
|
|
5
|
+
import { MemoizedMessageContent } from './chat';
|
|
6
|
+
import { UUID, Agent, ChannelType } from '@elizaos/core';
|
|
7
|
+
import type { UiMessage } from '@/hooks/use-query-hooks';
|
|
8
|
+
import { cn } from '@/lib/utils';
|
|
9
|
+
import { getAgentAvatar } from '@/lib/utils';
|
|
10
|
+
|
|
11
|
+
interface ChatMessageListComponentProps {
|
|
12
|
+
messages: UiMessage[];
|
|
13
|
+
isLoadingMessages: boolean;
|
|
14
|
+
chatType: ChannelType.GROUP | ChannelType.DM;
|
|
15
|
+
currentClientEntityId: string;
|
|
16
|
+
targetAgentData?: Agent;
|
|
17
|
+
allAgents: Partial<Agent>[];
|
|
18
|
+
animatedMessageId: string | null;
|
|
19
|
+
scrollRef: React.RefObject<HTMLDivElement | null>;
|
|
20
|
+
contentRef?: React.RefObject<HTMLDivElement | null>; // Optional content ref for StickToBottom
|
|
21
|
+
isAtBottom: boolean;
|
|
22
|
+
scrollToBottom: () => void;
|
|
23
|
+
disableAutoScroll: () => void;
|
|
24
|
+
finalChannelId: UUID | undefined;
|
|
25
|
+
getAgentInMessage?: (agentId: UUID) => Partial<Agent> | undefined;
|
|
26
|
+
agentAvatarMap?: Record<UUID, string | null>;
|
|
27
|
+
onDeleteMessage: (messageId: string) => void;
|
|
28
|
+
onRetryMessage: (messageText: string) => void;
|
|
29
|
+
selectedGroupAgentId?: UUID | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const ChatMessageListComponent: React.FC<ChatMessageListComponentProps> = ({
|
|
33
|
+
messages,
|
|
34
|
+
isLoadingMessages,
|
|
35
|
+
chatType,
|
|
36
|
+
currentClientEntityId,
|
|
37
|
+
targetAgentData,
|
|
38
|
+
allAgents,
|
|
39
|
+
animatedMessageId,
|
|
40
|
+
scrollRef,
|
|
41
|
+
contentRef,
|
|
42
|
+
isAtBottom,
|
|
43
|
+
scrollToBottom,
|
|
44
|
+
disableAutoScroll,
|
|
45
|
+
finalChannelId,
|
|
46
|
+
getAgentInMessage,
|
|
47
|
+
agentAvatarMap,
|
|
48
|
+
onDeleteMessage,
|
|
49
|
+
onRetryMessage,
|
|
50
|
+
selectedGroupAgentId,
|
|
51
|
+
}) => {
|
|
52
|
+
// Filter messages based on selected agent in group chat
|
|
53
|
+
const filteredMessages = React.useMemo(() => {
|
|
54
|
+
if (chatType === ChannelType.GROUP && selectedGroupAgentId) {
|
|
55
|
+
return messages.filter((message) => {
|
|
56
|
+
// Show user messages and messages from selected agent
|
|
57
|
+
const isUser = message.senderId === currentClientEntityId;
|
|
58
|
+
const isSelectedAgent = message.senderId === selectedGroupAgentId;
|
|
59
|
+
return isUser || isSelectedAgent;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return messages;
|
|
63
|
+
}, [messages, chatType, selectedGroupAgentId, currentClientEntityId]);
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<ChatMessageList
|
|
67
|
+
key={finalChannelId || 'no-channel'}
|
|
68
|
+
scrollRef={scrollRef}
|
|
69
|
+
contentRef={contentRef}
|
|
70
|
+
isAtBottom={isAtBottom}
|
|
71
|
+
scrollToBottom={scrollToBottom}
|
|
72
|
+
disableAutoScroll={disableAutoScroll}
|
|
73
|
+
className="h-full w-full"
|
|
74
|
+
data-testid="chat-messages"
|
|
75
|
+
>
|
|
76
|
+
{isLoadingMessages && filteredMessages.length === 0 && (
|
|
77
|
+
<div className="flex flex-1 justify-center items-center">
|
|
78
|
+
<p>Loading messages...</p>
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
81
|
+
{!isLoadingMessages && filteredMessages.length === 0 && (
|
|
82
|
+
<div className="flex flex-1 justify-center items-center">
|
|
83
|
+
<p>No messages yet. Start the conversation!</p>
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
{filteredMessages.map((message: UiMessage, index: number) => {
|
|
87
|
+
const isUser = message.senderId === currentClientEntityId;
|
|
88
|
+
const shouldAnimate =
|
|
89
|
+
index === filteredMessages.length - 1 &&
|
|
90
|
+
message.isAgent &&
|
|
91
|
+
message.id === animatedMessageId;
|
|
92
|
+
|
|
93
|
+
const senderAgent =
|
|
94
|
+
!isUser && getAgentInMessage ? getAgentInMessage(message.senderId) : undefined;
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div
|
|
98
|
+
key={`${message.id}-${message.createdAt}`}
|
|
99
|
+
className={cn('flex gap-1', isUser ? 'justify-end' : 'justify-start')}
|
|
100
|
+
>
|
|
101
|
+
<ChatBubble
|
|
102
|
+
variant={isUser ? 'sent' : 'received'}
|
|
103
|
+
className={`flex flex-col gap-1 ${isUser ? 'flex-row-reverse' : ''}`}
|
|
104
|
+
>
|
|
105
|
+
{!isUser && chatType === ChannelType.GROUP && (
|
|
106
|
+
<div className="flex items-center gap-2 text-muted-foreground">
|
|
107
|
+
<Avatar className="size-5 border rounded-full select-none">
|
|
108
|
+
<AvatarImage
|
|
109
|
+
src={getAgentAvatar(
|
|
110
|
+
senderAgent ||
|
|
111
|
+
(agentAvatarMap && message.senderId && allAgents
|
|
112
|
+
? allAgents.find((a: Partial<Agent>) => a.id === message.senderId)
|
|
113
|
+
: undefined)
|
|
114
|
+
)}
|
|
115
|
+
/>
|
|
116
|
+
</Avatar>
|
|
117
|
+
<div>{senderAgent?.name}</div>
|
|
118
|
+
</div>
|
|
119
|
+
)}
|
|
120
|
+
<MemoizedMessageContent
|
|
121
|
+
message={message}
|
|
122
|
+
agentForTts={
|
|
123
|
+
chatType === ChannelType.DM ? targetAgentData : (senderAgent as Agent | undefined)
|
|
124
|
+
}
|
|
125
|
+
shouldAnimate={shouldAnimate}
|
|
126
|
+
onDelete={onDeleteMessage}
|
|
127
|
+
onRetry={onRetryMessage}
|
|
128
|
+
isUser={isUser}
|
|
129
|
+
getAgentInMessage={chatType === ChannelType.GROUP ? getAgentInMessage : undefined}
|
|
130
|
+
agentAvatarMap={chatType === ChannelType.GROUP ? agentAvatarMap : undefined}
|
|
131
|
+
chatType={chatType}
|
|
132
|
+
/>
|
|
133
|
+
</ChatBubble>
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
})}
|
|
137
|
+
</ChatMessageList>
|
|
138
|
+
);
|
|
139
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Button } from '@/components/ui/button';
|
|
4
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
|
5
|
+
import { cn } from '@/lib/utils';
|
|
6
|
+
import { CheckCircle, ChevronDown, Loader2, Settings, XCircle } from 'lucide-react';
|
|
7
|
+
import { useState } from 'react';
|
|
8
|
+
|
|
9
|
+
export type ToolPart = {
|
|
10
|
+
type: string;
|
|
11
|
+
state: 'input-streaming' | 'input-available' | 'output-available' | 'output-error';
|
|
12
|
+
input?: Record<string, unknown>;
|
|
13
|
+
output?: Record<string, unknown>;
|
|
14
|
+
toolCallId?: string;
|
|
15
|
+
errorText?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type ToolProps = {
|
|
19
|
+
toolPart: ToolPart;
|
|
20
|
+
defaultOpen?: boolean;
|
|
21
|
+
className?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const Tool = ({ toolPart, defaultOpen = false, className }: ToolProps) => {
|
|
25
|
+
const [isOpen, setIsOpen] = useState(defaultOpen);
|
|
26
|
+
|
|
27
|
+
const { state, input, output, toolCallId } = toolPart;
|
|
28
|
+
|
|
29
|
+
const getStateIcon = () => {
|
|
30
|
+
switch (state) {
|
|
31
|
+
case 'input-streaming':
|
|
32
|
+
return <Loader2 className="h-4 w-4 animate-spin text-blue-500" />;
|
|
33
|
+
case 'input-available':
|
|
34
|
+
return <Settings className="h-4 w-4 text-orange-500" />;
|
|
35
|
+
case 'output-available':
|
|
36
|
+
return <CheckCircle className="h-4 w-4 text-green-500" />;
|
|
37
|
+
case 'output-error':
|
|
38
|
+
return <XCircle className="h-4 w-4 text-red-500" />;
|
|
39
|
+
default:
|
|
40
|
+
return <Settings className="text-muted-foreground h-4 w-4" />;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const getStateBadge = () => {
|
|
45
|
+
const baseClasses = 'px-2 py-1 rounded-full text-xs font-medium';
|
|
46
|
+
switch (state) {
|
|
47
|
+
case 'input-streaming':
|
|
48
|
+
return (
|
|
49
|
+
<span
|
|
50
|
+
className={cn(
|
|
51
|
+
baseClasses,
|
|
52
|
+
'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
|
|
53
|
+
)}
|
|
54
|
+
>
|
|
55
|
+
Processing
|
|
56
|
+
</span>
|
|
57
|
+
);
|
|
58
|
+
case 'input-available':
|
|
59
|
+
return (
|
|
60
|
+
<span
|
|
61
|
+
className={cn(
|
|
62
|
+
baseClasses,
|
|
63
|
+
'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
|
|
64
|
+
)}
|
|
65
|
+
>
|
|
66
|
+
Ready
|
|
67
|
+
</span>
|
|
68
|
+
);
|
|
69
|
+
case 'output-available':
|
|
70
|
+
return (
|
|
71
|
+
<span
|
|
72
|
+
className={cn(
|
|
73
|
+
baseClasses,
|
|
74
|
+
'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
|
|
75
|
+
)}
|
|
76
|
+
>
|
|
77
|
+
Completed
|
|
78
|
+
</span>
|
|
79
|
+
);
|
|
80
|
+
case 'output-error':
|
|
81
|
+
return (
|
|
82
|
+
<span
|
|
83
|
+
className={cn(
|
|
84
|
+
baseClasses,
|
|
85
|
+
'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
|
|
86
|
+
)}
|
|
87
|
+
>
|
|
88
|
+
Error
|
|
89
|
+
</span>
|
|
90
|
+
);
|
|
91
|
+
default:
|
|
92
|
+
return (
|
|
93
|
+
<span
|
|
94
|
+
className={cn(
|
|
95
|
+
baseClasses,
|
|
96
|
+
'bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400'
|
|
97
|
+
)}
|
|
98
|
+
>
|
|
99
|
+
Pending
|
|
100
|
+
</span>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const formatValue = (value: unknown): string => {
|
|
106
|
+
if (value === null) return 'null';
|
|
107
|
+
if (value === undefined) return 'undefined';
|
|
108
|
+
if (typeof value === 'string') return value;
|
|
109
|
+
if (typeof value === 'object') {
|
|
110
|
+
return JSON.stringify(value, null, 2);
|
|
111
|
+
}
|
|
112
|
+
return String(value);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div className={cn('border-border mt-3 overflow-hidden rounded-lg border', className)}>
|
|
117
|
+
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
|
118
|
+
<CollapsibleTrigger asChild>
|
|
119
|
+
<Button
|
|
120
|
+
variant="ghost"
|
|
121
|
+
className="bg-background h-auto w-full justify-between rounded-b-none px-3 py-2 font-normal"
|
|
122
|
+
>
|
|
123
|
+
<div className="flex items-center gap-2">
|
|
124
|
+
{getStateIcon()}
|
|
125
|
+
<span className="font-mono text-sm font-medium">{toolPart.type}</span>
|
|
126
|
+
{getStateBadge()}
|
|
127
|
+
</div>
|
|
128
|
+
<ChevronDown className={cn('h-4 w-4', isOpen && 'rotate-180')} />
|
|
129
|
+
</Button>
|
|
130
|
+
</CollapsibleTrigger>
|
|
131
|
+
<CollapsibleContent
|
|
132
|
+
className={cn(
|
|
133
|
+
'border-border border-t',
|
|
134
|
+
'data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down overflow-hidden'
|
|
135
|
+
)}
|
|
136
|
+
>
|
|
137
|
+
<div className="bg-background space-y-3 p-3">
|
|
138
|
+
{input && Object.keys(input).length > 0 && (
|
|
139
|
+
<div>
|
|
140
|
+
<h4 className="text-muted-foreground mb-2 text-sm font-medium">Input</h4>
|
|
141
|
+
<div className="bg-background rounded border p-2 font-mono text-sm">
|
|
142
|
+
{Object.entries(input).map(([key, value]) => (
|
|
143
|
+
<div key={key} className="mb-1">
|
|
144
|
+
<span className="text-muted-foreground">{key}:</span>{' '}
|
|
145
|
+
<span>{formatValue(value)}</span>
|
|
146
|
+
</div>
|
|
147
|
+
))}
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
|
|
152
|
+
{output && (
|
|
153
|
+
<div>
|
|
154
|
+
<h4 className="text-muted-foreground mb-2 text-sm font-medium">Output</h4>
|
|
155
|
+
<div className="bg-background max-h-60 overflow-auto rounded border p-2 font-mono text-sm">
|
|
156
|
+
<pre className="whitespace-pre-wrap">{formatValue(output)}</pre>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
|
|
161
|
+
{state === 'output-error' && toolPart.errorText && (
|
|
162
|
+
<div>
|
|
163
|
+
<h4 className="mb-2 text-sm font-medium text-red-500">Error</h4>
|
|
164
|
+
<div className="bg-background rounded border border-red-200 p-2 text-sm dark:border-red-950 dark:bg-red-900/20">
|
|
165
|
+
{toolPart.errorText}
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
)}
|
|
169
|
+
|
|
170
|
+
{state === 'input-streaming' && (
|
|
171
|
+
<div className="text-muted-foreground text-sm">Processing tool call...</div>
|
|
172
|
+
)}
|
|
173
|
+
|
|
174
|
+
{toolCallId && (
|
|
175
|
+
<div className="text-muted-foreground border-t border-blue-200 pt-2 text-xs">
|
|
176
|
+
<span className="font-mono">Call ID: {toolCallId}</span>
|
|
177
|
+
</div>
|
|
178
|
+
)}
|
|
179
|
+
</div>
|
|
180
|
+
</CollapsibleContent>
|
|
181
|
+
</Collapsible>
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export { Tool };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Button } from '@/components/ui/button';
|
|
2
|
+
import { Card, CardContent } from '@/components/ui/card';
|
|
3
|
+
import { Separator } from '@/components/ui/separator';
|
|
4
|
+
import { Plus, Settings } from 'lucide-react';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { useNavigate } from 'react-router-dom';
|
|
7
|
+
|
|
8
|
+
const AddAgentCard: React.FC = () => {
|
|
9
|
+
const navigate = useNavigate();
|
|
10
|
+
|
|
11
|
+
const handleClick = () => {
|
|
12
|
+
navigate('/create');
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const handleCardClick = (e: React.MouseEvent) => {
|
|
16
|
+
// Only trigger if clicking on the card background, not on interactive elements
|
|
17
|
+
const target = e.target as HTMLElement;
|
|
18
|
+
if (target.closest('button')) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
handleClick();
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Card
|
|
26
|
+
className="w-full cursor-pointer hover:shadow-lg transition-all border-2 border-dashed border-muted-foreground/30 bg-card/50 hover:bg-card/80"
|
|
27
|
+
onClick={handleCardClick}
|
|
28
|
+
onKeyPress={(e) => e.key === 'Enter' && handleClick()}
|
|
29
|
+
tabIndex={0}
|
|
30
|
+
role="button"
|
|
31
|
+
aria-label="Create new agent"
|
|
32
|
+
data-testid="add-agent-button"
|
|
33
|
+
>
|
|
34
|
+
<CardContent className="p-4 relative">
|
|
35
|
+
{/* Empty space for toggle alignment */}
|
|
36
|
+
<div className="absolute top-3 right-3">
|
|
37
|
+
<div className="h-4 w-8"></div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div className="flex items-start gap-4 pr-10">
|
|
41
|
+
{/* Icon placeholder matching avatar size */}
|
|
42
|
+
<div className="h-16 w-16 flex-shrink-0 rounded-xl border-2 border-dashed border-muted-foreground/50 flex items-center justify-center bg-muted/20">
|
|
43
|
+
<Plus className="h-6 w-6 text-muted-foreground" />
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
{/* Content */}
|
|
47
|
+
<div className="flex-1 min-w-0">
|
|
48
|
+
<h3 className="font-semibold text-xl mb-1 text-muted-foreground">Create New Agent</h3>
|
|
49
|
+
<p className="text-sm text-muted-foreground/70 line-clamp-2 leading-relaxed">
|
|
50
|
+
Add a new AI agent to your <br></br> collection
|
|
51
|
+
</p>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<Separator className="my-3" />
|
|
56
|
+
|
|
57
|
+
<div className="flex items-center justify-end">
|
|
58
|
+
{/* Create button styled like New Chat */}
|
|
59
|
+
<Button
|
|
60
|
+
variant="outline"
|
|
61
|
+
size="sm"
|
|
62
|
+
onClick={(e) => {
|
|
63
|
+
e.stopPropagation();
|
|
64
|
+
handleClick();
|
|
65
|
+
}}
|
|
66
|
+
className="h-8 px-4 rounded-sm bg-background border-muted-foreground/20 hover:bg-muted/30"
|
|
67
|
+
>
|
|
68
|
+
<Plus className="h-4 w-4 mr-2" />
|
|
69
|
+
Create
|
|
70
|
+
</Button>
|
|
71
|
+
</div>
|
|
72
|
+
</CardContent>
|
|
73
|
+
</Card>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default AddAgentCard;
|