@elizaos/client 1.5.5-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +350 -0
  3. package/dist/assets/empty-module-CLMscLYw.js +1 -0
  4. package/dist/assets/main-BBZ_3lkn.css +5999 -0
  5. package/dist/assets/main-C5zNUkXH.js +7 -0
  6. package/dist/assets/main-Dz64ENQg.js +614 -0
  7. package/dist/assets/react-vendor-DM5m98rr.js +545 -0
  8. package/dist/assets/ui-vendor-BQCqNqg0.js +1 -0
  9. package/dist/elizaos-avatar.png +0 -0
  10. package/dist/elizaos-icon.png +0 -0
  11. package/dist/elizaos-logo-light.png +0 -0
  12. package/dist/elizaos.webp +0 -0
  13. package/dist/favicon.ico +0 -0
  14. package/dist/images/agents/agent1.png +0 -0
  15. package/dist/images/agents/agent2.png +0 -0
  16. package/dist/images/agents/agent3.png +0 -0
  17. package/dist/images/agents/agent4.png +0 -0
  18. package/dist/images/agents/agent5.png +0 -0
  19. package/dist/index.html +14 -0
  20. package/index.html +24 -0
  21. package/package.json +159 -0
  22. package/postcss.config.js +3 -0
  23. package/public/elizaos-avatar.png +0 -0
  24. package/public/elizaos-icon.png +0 -0
  25. package/public/elizaos-logo-light.png +0 -0
  26. package/public/elizaos.webp +0 -0
  27. package/public/favicon.ico +0 -0
  28. package/public/images/agents/agent1.png +0 -0
  29. package/public/images/agents/agent2.png +0 -0
  30. package/public/images/agents/agent3.png +0 -0
  31. package/public/images/agents/agent4.png +0 -0
  32. package/public/images/agents/agent5.png +0 -0
  33. package/src/App.tsx +222 -0
  34. package/src/components/AgentDetailsPanel.tsx +147 -0
  35. package/src/components/ChatInputArea.tsx +196 -0
  36. package/src/components/ChatMessageListComponent.tsx +139 -0
  37. package/src/components/actionTool.tsx +186 -0
  38. package/src/components/add-agent-card.tsx +77 -0
  39. package/src/components/agent-action-viewer.tsx +816 -0
  40. package/src/components/agent-avatar-stack.tsx +121 -0
  41. package/src/components/agent-card.cy.tsx +259 -0
  42. package/src/components/agent-card.tsx +177 -0
  43. package/src/components/agent-creator.tsx +142 -0
  44. package/src/components/agent-log-viewer.tsx +645 -0
  45. package/src/components/agent-memory-edit-overlay.tsx +461 -0
  46. package/src/components/agent-memory-viewer.tsx +504 -0
  47. package/src/components/agent-settings.tsx +270 -0
  48. package/src/components/agent-sidebar.tsx +178 -0
  49. package/src/components/api-key-dialog.tsx +113 -0
  50. package/src/components/app-sidebar.tsx +685 -0
  51. package/src/components/array-input.tsx +116 -0
  52. package/src/components/audio-recorder.tsx +292 -0
  53. package/src/components/avatar-panel.tsx +141 -0
  54. package/src/components/character-form.tsx +1138 -0
  55. package/src/components/chat.tsx +1813 -0
  56. package/src/components/combobox.tsx +187 -0
  57. package/src/components/confirmation-dialog.tsx +59 -0
  58. package/src/components/connection-error-banner.tsx +101 -0
  59. package/src/components/connection-status.cy.tsx +73 -0
  60. package/src/components/connection-status.tsx +155 -0
  61. package/src/components/copy-button.tsx +35 -0
  62. package/src/components/delete-button.tsx +24 -0
  63. package/src/components/env-settings.tsx +261 -0
  64. package/src/components/group-card.tsx +160 -0
  65. package/src/components/group-panel.tsx +543 -0
  66. package/src/components/input-copy.tsx +21 -0
  67. package/src/components/logs-page.tsx +41 -0
  68. package/src/components/media-content.tsx +385 -0
  69. package/src/components/memory-graph.tsx +170 -0
  70. package/src/components/missing-secrets-dialog.tsx +72 -0
  71. package/src/components/onboarding-tour.tsx +247 -0
  72. package/src/components/page-title.tsx +8 -0
  73. package/src/components/plugins-panel.tsx +383 -0
  74. package/src/components/profile-card.tsx +66 -0
  75. package/src/components/profile-overlay.tsx +283 -0
  76. package/src/components/retry-button.tsx +28 -0
  77. package/src/components/secret-panel.tsx +1505 -0
  78. package/src/components/server-management.tsx +264 -0
  79. package/src/components/split-button.tsx +148 -0
  80. package/src/components/stop-agent-button.tsx +99 -0
  81. package/src/components/ui/alert-dialog.cy.tsx +333 -0
  82. package/src/components/ui/alert-dialog.tsx +115 -0
  83. package/src/components/ui/alert.tsx +49 -0
  84. package/src/components/ui/avatar.cy.tsx +180 -0
  85. package/src/components/ui/avatar.tsx +57 -0
  86. package/src/components/ui/badge.cy.tsx +146 -0
  87. package/src/components/ui/badge.tsx +43 -0
  88. package/src/components/ui/button.cy.tsx +177 -0
  89. package/src/components/ui/button.tsx +56 -0
  90. package/src/components/ui/card.cy.tsx +160 -0
  91. package/src/components/ui/card.tsx +73 -0
  92. package/src/components/ui/chat/animated-markdown.tsx +59 -0
  93. package/src/components/ui/chat/chat-bubble.tsx +178 -0
  94. package/src/components/ui/chat/chat-container.tsx +51 -0
  95. package/src/components/ui/chat/chat-input.cy.tsx +169 -0
  96. package/src/components/ui/chat/chat-input.tsx +47 -0
  97. package/src/components/ui/chat/chat-message-list.tsx +61 -0
  98. package/src/components/ui/chat/chat-tts-button.tsx +199 -0
  99. package/src/components/ui/chat/code-block.tsx +79 -0
  100. package/src/components/ui/chat/expandable-chat.tsx +131 -0
  101. package/src/components/ui/chat/hooks/useAutoScroll.ts +86 -0
  102. package/src/components/ui/chat/markdown.tsx +209 -0
  103. package/src/components/ui/chat/message-loading.tsx +48 -0
  104. package/src/components/ui/checkbox.cy.tsx +170 -0
  105. package/src/components/ui/checkbox.tsx +30 -0
  106. package/src/components/ui/collapsible.cy.tsx +283 -0
  107. package/src/components/ui/collapsible.tsx +9 -0
  108. package/src/components/ui/command.cy.tsx +313 -0
  109. package/src/components/ui/command.tsx +143 -0
  110. package/src/components/ui/dialog.cy.tsx +279 -0
  111. package/src/components/ui/dialog.tsx +104 -0
  112. package/src/components/ui/dropdown-menu.cy.tsx +273 -0
  113. package/src/components/ui/dropdown-menu.tsx +281 -0
  114. package/src/components/ui/input.cy.tsx +82 -0
  115. package/src/components/ui/input.tsx +27 -0
  116. package/src/components/ui/label.cy.tsx +157 -0
  117. package/src/components/ui/label.tsx +19 -0
  118. package/src/components/ui/resizable.tsx +42 -0
  119. package/src/components/ui/scroll-area.cy.tsx +242 -0
  120. package/src/components/ui/scroll-area.tsx +46 -0
  121. package/src/components/ui/select.cy.tsx +277 -0
  122. package/src/components/ui/select.tsx +155 -0
  123. package/src/components/ui/separator.cy.tsx +145 -0
  124. package/src/components/ui/separator.tsx +29 -0
  125. package/src/components/ui/sheet.cy.tsx +324 -0
  126. package/src/components/ui/sheet.tsx +119 -0
  127. package/src/components/ui/sidebar.tsx +734 -0
  128. package/src/components/ui/skeleton.cy.tsx +149 -0
  129. package/src/components/ui/skeleton.tsx +17 -0
  130. package/src/components/ui/split-button.cy.tsx +274 -0
  131. package/src/components/ui/split-button.tsx +112 -0
  132. package/src/components/ui/switch.tsx +28 -0
  133. package/src/components/ui/tabs.cy.tsx +271 -0
  134. package/src/components/ui/tabs.tsx +53 -0
  135. package/src/components/ui/textarea.cy.tsx +136 -0
  136. package/src/components/ui/textarea.tsx +26 -0
  137. package/src/components/ui/toast.cy.tsx +209 -0
  138. package/src/components/ui/toast.tsx +126 -0
  139. package/src/components/ui/toaster.tsx +29 -0
  140. package/src/components/ui/tooltip.cy.tsx +244 -0
  141. package/src/components/ui/tooltip.tsx +30 -0
  142. package/src/config/agent-templates.ts +349 -0
  143. package/src/config/voice-models.ts +181 -0
  144. package/src/constants.ts +23 -0
  145. package/src/context/AuthContext.tsx +44 -0
  146. package/src/context/ConnectionContext.tsx +194 -0
  147. package/src/entry.tsx +9 -0
  148. package/src/hooks/__tests__/use-agent-tab-state.test.ts +137 -0
  149. package/src/hooks/__tests__/use-agent-update.test.tsx +250 -0
  150. package/src/hooks/__tests__/use-character-convert.test.ts +102 -0
  151. package/src/hooks/__tests__/use-panel-width-state.test.ts +243 -0
  152. package/src/hooks/__tests__/use-sidebar-state.test.ts +117 -0
  153. package/src/hooks/use-agent-management.ts +130 -0
  154. package/src/hooks/use-agent-tab-state.ts +74 -0
  155. package/src/hooks/use-agent-update.ts +469 -0
  156. package/src/hooks/use-character-convert.ts +138 -0
  157. package/src/hooks/use-confirmation.ts +55 -0
  158. package/src/hooks/use-delete-agent.ts +123 -0
  159. package/src/hooks/use-dm-channels.ts +198 -0
  160. package/src/hooks/use-elevenlabs-voices.ts +83 -0
  161. package/src/hooks/use-file-upload.ts +224 -0
  162. package/src/hooks/use-mobile.tsx +19 -0
  163. package/src/hooks/use-onboarding.tsx +49 -0
  164. package/src/hooks/use-panel-width-state.ts +147 -0
  165. package/src/hooks/use-partial-update.ts +288 -0
  166. package/src/hooks/use-plugin-details.ts +462 -0
  167. package/src/hooks/use-plugins.ts +119 -0
  168. package/src/hooks/use-query-hooks.ts +1263 -0
  169. package/src/hooks/use-server-agents.ts +62 -0
  170. package/src/hooks/use-server-version.tsx +47 -0
  171. package/src/hooks/use-sidebar-state.ts +50 -0
  172. package/src/hooks/use-socket-chat.ts +264 -0
  173. package/src/hooks/use-toast.ts +260 -0
  174. package/src/hooks/use-version.tsx +64 -0
  175. package/src/index.css +146 -0
  176. package/src/lib/api-client-config.ts +53 -0
  177. package/src/lib/api-type-mappers.ts +196 -0
  178. package/src/lib/export-utils.ts +123 -0
  179. package/src/lib/logger.ts +19 -0
  180. package/src/lib/media-utils.ts +170 -0
  181. package/src/lib/pca.test.ts +17 -0
  182. package/src/lib/pca.ts +52 -0
  183. package/src/lib/socketio-manager.ts +664 -0
  184. package/src/lib/utils.ts +168 -0
  185. package/src/main.tsx +16 -0
  186. package/src/mocks/empty-module.ts +12 -0
  187. package/src/mocks/node-module.ts +57 -0
  188. package/src/polyfills.ts +37 -0
  189. package/src/routes/agent-detail.tsx +30 -0
  190. package/src/routes/agent-list.tsx +27 -0
  191. package/src/routes/agent-settings.tsx +48 -0
  192. package/src/routes/character-detail.tsx +52 -0
  193. package/src/routes/character-form.tsx +79 -0
  194. package/src/routes/character-list.tsx +38 -0
  195. package/src/routes/chat.tsx +128 -0
  196. package/src/routes/createAgent.tsx +13 -0
  197. package/src/routes/group-new.tsx +50 -0
  198. package/src/routes/group.tsx +29 -0
  199. package/src/routes/home.tsx +218 -0
  200. package/src/routes/not-found.tsx +71 -0
  201. package/src/test/setup.ts +154 -0
  202. package/src/types/crypto-browserify.d.ts +4 -0
  203. package/src/types/index.ts +13 -0
  204. package/src/types/rooms.ts +8 -0
  205. package/src/types.ts +84 -0
  206. package/src/vite-env.d.ts +40 -0
  207. package/tailwind.config.ts +90 -0
  208. package/tsconfig.json +10 -0
  209. package/vite.config.ts +102 -0
@@ -0,0 +1,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;