@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,73 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from '@/lib/utils';
4
+
5
+ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
6
+ ({ className, ...props }, ref) => (
7
+ <div
8
+ ref={ref}
9
+ className={cn('rounded-lg border bg-card text-card-foreground shadow-sm', className)}
10
+ data-slot="card"
11
+ {...props}
12
+ />
13
+ )
14
+ );
15
+ Card.displayName = 'Card';
16
+
17
+ const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
18
+ ({ className, ...props }, ref) => (
19
+ <div
20
+ ref={ref}
21
+ className={cn('flex flex-col space-y-1.5 bg-black/50 rounded-t-lg p-2', className)}
22
+ data-slot="card-header"
23
+ {...props}
24
+ />
25
+ )
26
+ );
27
+ CardHeader.displayName = 'CardHeader';
28
+
29
+ const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
30
+ ({ className, ...props }, ref) => (
31
+ <h3
32
+ ref={ref}
33
+ className={cn('text-2xl font-semibold leading-none tracking-tight', className)}
34
+ data-slot="card-title"
35
+ {...props}
36
+ />
37
+ )
38
+ );
39
+ CardTitle.displayName = 'CardTitle';
40
+
41
+ const CardDescription = React.forwardRef<
42
+ HTMLParagraphElement,
43
+ React.HTMLAttributes<HTMLParagraphElement>
44
+ >(({ className, ...props }, ref) => (
45
+ <p
46
+ ref={ref}
47
+ className={cn('text-sm text-muted-foreground', className)}
48
+ data-slot="card-description"
49
+ {...props}
50
+ />
51
+ ));
52
+ CardDescription.displayName = 'CardDescription';
53
+
54
+ const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
55
+ ({ className, ...props }, ref) => (
56
+ <div ref={ref} className={cn('p-6 pt-0', className)} data-slot="card-content" {...props} />
57
+ )
58
+ );
59
+ CardContent.displayName = 'CardContent';
60
+
61
+ const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
62
+ ({ className, ...props }, ref) => (
63
+ <div
64
+ ref={ref}
65
+ className={cn('flex items-center p-6 pt-0', className)}
66
+ data-slot="card-footer"
67
+ {...props}
68
+ />
69
+ )
70
+ );
71
+ CardFooter.displayName = 'CardFooter';
72
+
73
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
@@ -0,0 +1,59 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Markdown } from './markdown';
5
+
6
+ interface AnimatedMarkdownProps {
7
+ children: string;
8
+ className?: string;
9
+ variant?: 'user' | 'agent';
10
+ shouldAnimate?: boolean;
11
+ messageId?: string;
12
+ maxDurationMs?: number; // Optional prop to control cap duration
13
+ }
14
+
15
+ export const AnimatedMarkdown: React.FC<AnimatedMarkdownProps> = ({
16
+ children,
17
+ className,
18
+ variant = 'agent',
19
+ shouldAnimate = false,
20
+ messageId,
21
+ maxDurationMs = 10000,
22
+ }) => {
23
+ const [visibleText, setVisibleText] = React.useState(shouldAnimate ? '' : children);
24
+
25
+ React.useEffect(() => {
26
+ if (!shouldAnimate || !children.trim()) {
27
+ setVisibleText(children);
28
+ return;
29
+ }
30
+
31
+ const safeDuration = Math.max(1000, maxDurationMs);
32
+
33
+ setVisibleText('');
34
+
35
+ const TYPING_INTERVAL = 20;
36
+ const totalChars = children.length;
37
+ const totalSteps = Math.ceil(safeDuration / TYPING_INTERVAL);
38
+ const charsPerStep = Math.max(1, Math.ceil(totalChars / totalSteps));
39
+
40
+ let visibleCharCount = 0;
41
+ const interval = setInterval(() => {
42
+ visibleCharCount += charsPerStep;
43
+ if (visibleCharCount >= totalChars) {
44
+ setVisibleText(children);
45
+ clearInterval(interval);
46
+ } else {
47
+ setVisibleText(children.slice(0, visibleCharCount));
48
+ }
49
+ }, TYPING_INTERVAL);
50
+
51
+ return () => clearInterval(interval);
52
+ }, [children, shouldAnimate, messageId, maxDurationMs]);
53
+
54
+ return (
55
+ <Markdown className={className} variant={variant}>
56
+ {visibleText}
57
+ </Markdown>
58
+ );
59
+ };
@@ -0,0 +1,178 @@
1
+ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
2
+ import { cn } from '@/lib/utils';
3
+ import { type VariantProps, cva } from 'class-variance-authority';
4
+ import * as React from 'react';
5
+ import { Button } from '../button';
6
+ import MessageLoading from './message-loading';
7
+
8
+ // ChatBubble
9
+ const chatBubbleVariant = cva('flex gap-2 max-w-[60%] relative group', {
10
+ variants: {
11
+ variant: {
12
+ received: 'self-start',
13
+ sent: 'self-end flex-row-reverse',
14
+ },
15
+ layout: {
16
+ default: '',
17
+ ai: 'max-w-full w-full items-center',
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: 'received',
22
+ layout: 'default',
23
+ },
24
+ });
25
+
26
+ interface ChatBubbleProps
27
+ extends React.HTMLAttributes<HTMLDivElement>,
28
+ VariantProps<typeof chatBubbleVariant> {}
29
+
30
+ const ChatBubble = React.forwardRef<HTMLDivElement, ChatBubbleProps>(
31
+ ({ className, variant, layout, children, ...props }, ref) => (
32
+ <div
33
+ className={cn(chatBubbleVariant({ variant, layout, className }), 'relative group')}
34
+ ref={ref}
35
+ {...props}
36
+ >
37
+ {React.Children.map(children, (child) =>
38
+ React.isValidElement(child) &&
39
+ typeof child.type !== 'string' &&
40
+ child.type !== React.Fragment
41
+ ? React.cloneElement(child, {
42
+ variant,
43
+ layout,
44
+ } as React.ComponentProps<typeof child.type>)
45
+ : child
46
+ )}
47
+ </div>
48
+ )
49
+ );
50
+ ChatBubble.displayName = 'ChatBubble';
51
+
52
+ // ChatBubbleAvatar
53
+ interface ChatBubbleAvatarProps {
54
+ src?: string;
55
+ fallback?: string;
56
+ className?: string;
57
+ }
58
+
59
+ const ChatBubbleAvatar: React.FC<ChatBubbleAvatarProps> = ({ src, fallback, className }) => (
60
+ <Avatar className={className}>
61
+ <AvatarImage src={src} alt="Avatar" />
62
+ <AvatarFallback>{fallback}</AvatarFallback>
63
+ </Avatar>
64
+ );
65
+
66
+ // ChatBubbleMessage
67
+ const chatBubbleMessageVariants = cva('', {
68
+ variants: {
69
+ variant: {
70
+ received: 'text-secondary-foreground',
71
+ sent: 'px-5 py-2.5 bg-secondary text-secondary-foreground rounded-lg',
72
+ },
73
+ layout: {
74
+ default: '',
75
+ ai: 'border-t w-full rounded-none bg-transparent',
76
+ },
77
+ },
78
+ defaultVariants: {
79
+ variant: 'received',
80
+ layout: 'default',
81
+ },
82
+ });
83
+
84
+ interface ChatBubbleMessageProps
85
+ extends React.HTMLAttributes<HTMLDivElement>,
86
+ VariantProps<typeof chatBubbleMessageVariants> {
87
+ isLoading?: boolean;
88
+ }
89
+
90
+ const ChatBubbleMessage = React.forwardRef<HTMLDivElement, ChatBubbleMessageProps>(
91
+ ({ className, variant, layout, isLoading = false, children, ...props }, ref) => (
92
+ <div
93
+ className={cn(
94
+ chatBubbleMessageVariants({ variant, layout, className }),
95
+ 'break-words max-w-full whitespace-pre-wrap'
96
+ )}
97
+ ref={ref}
98
+ {...props}
99
+ >
100
+ {isLoading ? (
101
+ <div className="flex items-center space-x-2">
102
+ <MessageLoading />
103
+ </div>
104
+ ) : (
105
+ children
106
+ )}
107
+ </div>
108
+ )
109
+ );
110
+ ChatBubbleMessage.displayName = 'ChatBubbleMessage';
111
+
112
+ // ChatBubbleTimestamp
113
+ interface ChatBubbleTimestampProps extends React.HTMLAttributes<HTMLDivElement> {
114
+ timestamp: string;
115
+ }
116
+
117
+ const ChatBubbleTimestamp: React.FC<ChatBubbleTimestampProps> = ({
118
+ timestamp,
119
+ className,
120
+ ...props
121
+ }) => (
122
+ <div className={cn('text-xs text-right select-none', className)} {...props}>
123
+ {timestamp}
124
+ </div>
125
+ );
126
+
127
+ // ChatBubbleAction
128
+ type ChatBubbleActionProps = React.ComponentProps<typeof Button> & {
129
+ icon: React.ReactNode;
130
+ };
131
+
132
+ const ChatBubbleAction: React.FC<ChatBubbleActionProps> = ({
133
+ icon,
134
+ onClick,
135
+ className,
136
+ variant = 'ghost',
137
+ size = 'icon',
138
+ ...props
139
+ }) => (
140
+ <Button variant={variant} size={size} className={className} onClick={onClick} {...props}>
141
+ {icon}
142
+ </Button>
143
+ );
144
+
145
+ interface ChatBubbleActionWrapperProps extends React.HTMLAttributes<HTMLDivElement> {
146
+ variant?: 'sent' | 'received';
147
+ className?: string;
148
+ }
149
+
150
+ const ChatBubbleActionWrapper = React.forwardRef<HTMLDivElement, ChatBubbleActionWrapperProps>(
151
+ ({ variant, className, children, ...props }, ref) => (
152
+ <div
153
+ ref={ref}
154
+ className={cn(
155
+ 'absolute top-1/2 -translate-y-1/2 flex opacity-0 group-hover:opacity-100 transition-opacity duration-200',
156
+ variant === 'sent'
157
+ ? '-left-1 -translate-x-full flex-row-reverse'
158
+ : '-right-1 translate-x-full',
159
+ className
160
+ )}
161
+ {...props}
162
+ >
163
+ {children}
164
+ </div>
165
+ )
166
+ );
167
+ ChatBubbleActionWrapper.displayName = 'ChatBubbleActionWrapper';
168
+
169
+ export {
170
+ ChatBubble,
171
+ ChatBubbleAvatar,
172
+ ChatBubbleMessage,
173
+ ChatBubbleTimestamp,
174
+ chatBubbleVariant,
175
+ chatBubbleMessageVariants,
176
+ ChatBubbleAction,
177
+ ChatBubbleActionWrapper,
178
+ };
@@ -0,0 +1,51 @@
1
+ import { cn } from '@/lib/utils';
2
+ import { StickToBottom } from 'use-stick-to-bottom';
3
+
4
+ export type ChatContainerRootProps = {
5
+ children: React.ReactNode;
6
+ className?: string;
7
+ } & React.HTMLAttributes<HTMLDivElement>;
8
+
9
+ export type ChatContainerContentProps = {
10
+ children: React.ReactNode;
11
+ className?: string;
12
+ } & React.HTMLAttributes<HTMLDivElement>;
13
+
14
+ export type ChatContainerScrollAnchorProps = {
15
+ className?: string;
16
+ ref?: React.RefObject<HTMLDivElement>;
17
+ } & React.HTMLAttributes<HTMLDivElement>;
18
+
19
+ function ChatContainerRoot({ children, className, ...props }: ChatContainerRootProps) {
20
+ return (
21
+ <StickToBottom
22
+ className={cn('flex overflow-y-auto', className)}
23
+ resize="smooth"
24
+ initial="smooth"
25
+ role="log"
26
+ {...props}
27
+ >
28
+ {children}
29
+ </StickToBottom>
30
+ );
31
+ }
32
+
33
+ function ChatContainerContent({ children, className, ...props }: ChatContainerContentProps) {
34
+ return (
35
+ <StickToBottom.Content className={cn('flex w-full flex-col', className)} {...props}>
36
+ {children}
37
+ </StickToBottom.Content>
38
+ );
39
+ }
40
+
41
+ function ChatContainerScrollAnchor({ className, ...props }: ChatContainerScrollAnchorProps) {
42
+ return (
43
+ <div
44
+ className={cn('h-px w-full shrink-0 scroll-mt-4', className)}
45
+ aria-hidden="true"
46
+ {...props}
47
+ />
48
+ );
49
+ }
50
+
51
+ export { ChatContainerRoot, ChatContainerContent, ChatContainerScrollAnchor };
@@ -0,0 +1,169 @@
1
+ /// <reference types="cypress" />
2
+ /// <reference path="../../../../cypress/support/types.d.ts" />
3
+
4
+ import React from 'react';
5
+ import { ChatInput } from './chat-input';
6
+
7
+ // Test component for form submission
8
+ const TestForm = () => {
9
+ const [value, setValue] = React.useState('');
10
+
11
+ const handleSubmit = (e: React.FormEvent) => {
12
+ e.preventDefault();
13
+ setValue('');
14
+ };
15
+
16
+ return (
17
+ <form onSubmit={handleSubmit}>
18
+ <ChatInput value={value} onChange={(e) => setValue(e.target.value)} />
19
+ <button type="submit">Send</button>
20
+ </form>
21
+ );
22
+ };
23
+
24
+ // Test component for chat form
25
+ const TestChatForm = () => {
26
+ const [messages, setMessages] = React.useState<string[]>([]);
27
+ const [input, setInput] = React.useState('');
28
+
29
+ const handleSubmit = (e: React.FormEvent) => {
30
+ e.preventDefault();
31
+ if (input.trim()) {
32
+ setMessages([...messages, input]);
33
+ setInput('');
34
+ }
35
+ };
36
+
37
+ return (
38
+ <div>
39
+ <div className="messages">
40
+ {messages.map((msg, i) => (
41
+ <div key={i}>{msg}</div>
42
+ ))}
43
+ </div>
44
+ <form onSubmit={handleSubmit}>
45
+ <ChatInput
46
+ value={input}
47
+ onChange={(e) => setInput(e.target.value)}
48
+ placeholder="Type a message..."
49
+ />
50
+ <button type="submit">Send</button>
51
+ </form>
52
+ </div>
53
+ );
54
+ };
55
+
56
+ describe('ChatInput Component', () => {
57
+ it('renders correctly with default props', () => {
58
+ cy.mount(<ChatInput placeholder="Type a message..." />);
59
+
60
+ cy.get('textarea').should('exist');
61
+ cy.get('textarea').should('have.attr', 'placeholder', 'Type a message...');
62
+ cy.get('textarea').should('have.attr', 'name', 'message');
63
+ cy.get('textarea').should('have.class', 'resize-none');
64
+ });
65
+
66
+ it('accepts user input', () => {
67
+ cy.mount(<ChatInput />);
68
+
69
+ cy.get('textarea').type('Hello, this is a test message');
70
+ cy.get('textarea').should('have.value', 'Hello, this is a test message');
71
+ });
72
+
73
+ it('applies custom placeholder', () => {
74
+ cy.mount(<ChatInput placeholder="Write your message here..." />);
75
+
76
+ cy.get('textarea').should('have.attr', 'placeholder', 'Write your message here...');
77
+ });
78
+
79
+ it('handles onChange events', () => {
80
+ const onChange = cy.stub();
81
+ cy.mount(<ChatInput onChange={onChange} />);
82
+
83
+ cy.get('textarea').type('Test');
84
+ cy.wrap(onChange).should('have.been.called');
85
+ });
86
+
87
+ it('handles onKeyDown events', () => {
88
+ const onKeyDown = cy.stub();
89
+ cy.mount(<ChatInput onKeyDown={onKeyDown} />);
90
+
91
+ cy.get('textarea').type('{enter}');
92
+ cy.wrap(onKeyDown).should('have.been.called');
93
+ });
94
+
95
+ it('can be disabled', () => {
96
+ cy.mount(<ChatInput disabled />);
97
+
98
+ cy.get('textarea').should('be.disabled');
99
+ cy.get('textarea').should('have.class', 'disabled:cursor-not-allowed');
100
+ cy.get('textarea').should('have.class', 'disabled:opacity-50');
101
+ });
102
+
103
+ it('applies custom className', () => {
104
+ cy.mount(<ChatInput className="custom-class" />);
105
+
106
+ cy.get('textarea').should('have.class', 'custom-class');
107
+ });
108
+
109
+ it('clears input on submit', () => {
110
+ cy.mount(<TestForm />);
111
+
112
+ cy.get('textarea').type('Test message');
113
+ cy.get('textarea').should('have.value', 'Test message');
114
+ cy.get('button').click();
115
+ cy.get('textarea').should('have.value', '');
116
+ });
117
+
118
+ it('works with autoComplete', () => {
119
+ cy.mount(<ChatInput autoComplete="on" />);
120
+
121
+ cy.get('textarea').should('have.attr', 'autocomplete', 'on');
122
+ });
123
+
124
+ it('supports maxLength', () => {
125
+ cy.mount(<ChatInput maxLength={100} />);
126
+
127
+ cy.get('textarea').should('have.attr', 'maxlength', '100');
128
+ });
129
+
130
+ it('maintains focus after mount', () => {
131
+ cy.mount(<ChatInput autoFocus />);
132
+
133
+ cy.get('textarea').should('have.focus');
134
+ });
135
+
136
+ it('handles paste events', () => {
137
+ const onPaste = cy.stub();
138
+ cy.mount(<ChatInput onPaste={onPaste} />);
139
+
140
+ cy.get('textarea').trigger('paste');
141
+ cy.wrap(onPaste).should('have.been.called');
142
+ });
143
+
144
+ it('supports different input modes', () => {
145
+ cy.mount(<ChatInput inputMode="text" />);
146
+
147
+ cy.get('textarea').should('have.attr', 'inputmode', 'text');
148
+ });
149
+
150
+ it('works in a chat form', () => {
151
+ cy.mount(<TestChatForm />);
152
+
153
+ cy.get('textarea').type('First message');
154
+ cy.get('button').click();
155
+ cy.get('.messages').should('contain', 'First message');
156
+ cy.get('textarea').should('have.value', '');
157
+
158
+ cy.get('textarea').type('Second message');
159
+ cy.get('button').click();
160
+ cy.get('.messages').should('contain', 'Second message');
161
+ });
162
+
163
+ it('supports data attributes', () => {
164
+ cy.mount(<ChatInput data-testid="chat-input" data-cy="chat-input" />);
165
+
166
+ cy.get('[data-testid="chat-input"]').should('exist');
167
+ cy.get('[data-cy="chat-input"]').should('exist');
168
+ });
169
+ });
@@ -0,0 +1,47 @@
1
+ import { Textarea } from '@/components/ui/textarea';
2
+ import { cn } from '@/lib/utils';
3
+ import * as React from 'react';
4
+
5
+ interface ChatInputProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
6
+
7
+ const MAX_HEIGHT = 160;
8
+
9
+ const ChatInput = React.forwardRef<HTMLTextAreaElement, ChatInputProps>(
10
+ ({ className, ...props }, ref) => {
11
+ const internalRef = React.useRef<HTMLTextAreaElement | null>(null);
12
+
13
+ const combinedRef = (node: HTMLTextAreaElement) => {
14
+ if (typeof ref === 'function') ref(node);
15
+ else if (ref) ref.current = node;
16
+ internalRef.current = node;
17
+ };
18
+
19
+ const resizeTextarea = () => {
20
+ const textarea = internalRef.current;
21
+ if (textarea) {
22
+ textarea.style.height = 'auto';
23
+ textarea.style.height = Math.min(textarea.scrollHeight, MAX_HEIGHT) + 'px';
24
+ }
25
+ };
26
+
27
+ React.useEffect(() => {
28
+ resizeTextarea();
29
+ }, [props.value]);
30
+
31
+ return (
32
+ <Textarea
33
+ autoComplete="off"
34
+ ref={combinedRef}
35
+ name="message"
36
+ className={cn(
37
+ 'px-4 py-3 bg-background text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring disabled:cursor-not-allowed disabled:opacity-50 w-full rounded-md flex items-center h-16 resize-none',
38
+ className
39
+ )}
40
+ {...props}
41
+ />
42
+ );
43
+ }
44
+ );
45
+ ChatInput.displayName = 'ChatInput';
46
+
47
+ export { ChatInput };
@@ -0,0 +1,61 @@
1
+ import { Button } from '@/components/ui/button';
2
+ import { ArrowDown } from 'lucide-react';
3
+ import * as React from 'react';
4
+
5
+ interface ChatMessageListProps extends React.HTMLAttributes<HTMLDivElement> {
6
+ scrollRef: React.RefObject<HTMLDivElement | null>;
7
+ contentRef?: React.RefObject<HTMLDivElement | null>; // Optional content ref for StickToBottom
8
+ isAtBottom: boolean;
9
+ scrollToBottom: () => void;
10
+ disableAutoScroll: () => void;
11
+ smooth?: boolean;
12
+ }
13
+
14
+ const ChatMessageList = React.forwardRef<HTMLDivElement, ChatMessageListProps>(
15
+ (
16
+ {
17
+ className,
18
+ children,
19
+ scrollRef,
20
+ contentRef,
21
+ isAtBottom,
22
+ scrollToBottom,
23
+ disableAutoScroll,
24
+ ...props
25
+ },
26
+ _ref
27
+ ) => {
28
+ return (
29
+ <div className="relative w-full h-full overflow-hidden">
30
+ <div
31
+ className={`flex flex-col w-full h-full p-2 overflow-y-auto ${className}`}
32
+ ref={scrollRef}
33
+ onWheel={disableAutoScroll}
34
+ onTouchMove={disableAutoScroll}
35
+ {...props}
36
+ >
37
+ <div className="flex flex-col gap-10" ref={contentRef}>
38
+ {children}
39
+ </div>
40
+ </div>
41
+
42
+ {!isAtBottom && (
43
+ <Button
44
+ onClick={() => {
45
+ scrollToBottom();
46
+ }}
47
+ size="icon"
48
+ variant="outline"
49
+ className="absolute bottom-2 left-1/2 transform -translate-x-1/2 inline-flex rounded-full shadow-md"
50
+ >
51
+ <ArrowDown className="h-4 w-4" />
52
+ </Button>
53
+ )}
54
+ </div>
55
+ );
56
+ }
57
+ );
58
+
59
+ ChatMessageList.displayName = 'ChatMessageList';
60
+
61
+ export { ChatMessageList };