@copilotz/chat-ui 0.7.4 → 0.7.5

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 (163) hide show
  1. package/dist/index.cjs +3 -3
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js +3 -3
  4. package/dist/index.js.map +1 -1
  5. package/dist/styles.css +6 -3
  6. package/package.json +1 -1
  7. package/dist/components/chat/AgentSelectors.d.ts +0 -50
  8. package/dist/components/chat/AgentSelectors.d.ts.map +0 -1
  9. package/dist/components/chat/AgentSelectors.js +0 -67
  10. package/dist/components/chat/AgentSelectors.js.map +0 -1
  11. package/dist/components/chat/AssistantActivity.d.ts +0 -11
  12. package/dist/components/chat/AssistantActivity.d.ts.map +0 -1
  13. package/dist/components/chat/AssistantActivity.js +0 -82
  14. package/dist/components/chat/AssistantActivity.js.map +0 -1
  15. package/dist/components/chat/ChatHeader.d.ts +0 -56
  16. package/dist/components/chat/ChatHeader.d.ts.map +0 -1
  17. package/dist/components/chat/ChatHeader.js +0 -71
  18. package/dist/components/chat/ChatHeader.js.map +0 -1
  19. package/dist/components/chat/ChatInput.d.ts +0 -25
  20. package/dist/components/chat/ChatInput.d.ts.map +0 -1
  21. package/dist/components/chat/ChatInput.js +0 -732
  22. package/dist/components/chat/ChatInput.js.map +0 -1
  23. package/dist/components/chat/ChatUI.d.ts +0 -4
  24. package/dist/components/chat/ChatUI.d.ts.map +0 -1
  25. package/dist/components/chat/ChatUI.js +0 -470
  26. package/dist/components/chat/ChatUI.js.map +0 -1
  27. package/dist/components/chat/Message.d.ts +0 -37
  28. package/dist/components/chat/Message.d.ts.map +0 -1
  29. package/dist/components/chat/Message.js +0 -262
  30. package/dist/components/chat/Message.js.map +0 -1
  31. package/dist/components/chat/Sidebar.d.ts +0 -52
  32. package/dist/components/chat/Sidebar.d.ts.map +0 -1
  33. package/dist/components/chat/Sidebar.js +0 -112
  34. package/dist/components/chat/Sidebar.js.map +0 -1
  35. package/dist/components/chat/ThreadManager.d.ts +0 -18
  36. package/dist/components/chat/ThreadManager.d.ts.map +0 -1
  37. package/dist/components/chat/ThreadManager.js +0 -108
  38. package/dist/components/chat/ThreadManager.js.map +0 -1
  39. package/dist/components/chat/UserContext.d.ts +0 -15
  40. package/dist/components/chat/UserContext.d.ts.map +0 -1
  41. package/dist/components/chat/UserContext.js +0 -39
  42. package/dist/components/chat/UserContext.js.map +0 -1
  43. package/dist/components/chat/UserMenu.d.ts +0 -38
  44. package/dist/components/chat/UserMenu.d.ts.map +0 -1
  45. package/dist/components/chat/UserMenu.js +0 -44
  46. package/dist/components/chat/UserMenu.js.map +0 -1
  47. package/dist/components/chat/UserProfile.d.ts +0 -51
  48. package/dist/components/chat/UserProfile.d.ts.map +0 -1
  49. package/dist/components/chat/UserProfile.js +0 -206
  50. package/dist/components/chat/UserProfile.js.map +0 -1
  51. package/dist/components/chat/VoiceComposer.d.ts +0 -29
  52. package/dist/components/chat/VoiceComposer.d.ts.map +0 -1
  53. package/dist/components/chat/VoiceComposer.js +0 -99
  54. package/dist/components/chat/VoiceComposer.js.map +0 -1
  55. package/dist/components/ui/Spinner.d.ts +0 -7
  56. package/dist/components/ui/Spinner.d.ts.map +0 -1
  57. package/dist/components/ui/Spinner.js +0 -14
  58. package/dist/components/ui/Spinner.js.map +0 -1
  59. package/dist/components/ui/accordion.d.ts +0 -8
  60. package/dist/components/ui/accordion.d.ts.map +0 -1
  61. package/dist/components/ui/accordion.js +0 -14
  62. package/dist/components/ui/accordion.js.map +0 -1
  63. package/dist/components/ui/alert-dialog.d.ts +0 -15
  64. package/dist/components/ui/alert-dialog.d.ts.map +0 -1
  65. package/dist/components/ui/alert-dialog.js +0 -66
  66. package/dist/components/ui/alert-dialog.js.map +0 -1
  67. package/dist/components/ui/avatar.d.ts +0 -7
  68. package/dist/components/ui/avatar.d.ts.map +0 -1
  69. package/dist/components/ui/avatar.js +0 -15
  70. package/dist/components/ui/avatar.js.map +0 -1
  71. package/dist/components/ui/badge.d.ts +0 -10
  72. package/dist/components/ui/badge.d.ts.map +0 -1
  73. package/dist/components/ui/badge.js +0 -23
  74. package/dist/components/ui/badge.js.map +0 -1
  75. package/dist/components/ui/button.d.ts +0 -11
  76. package/dist/components/ui/button.d.ts.map +0 -1
  77. package/dist/components/ui/button.js +0 -32
  78. package/dist/components/ui/button.js.map +0 -1
  79. package/dist/components/ui/card.d.ts +0 -10
  80. package/dist/components/ui/card.d.ts.map +0 -1
  81. package/dist/components/ui/card.js +0 -25
  82. package/dist/components/ui/card.js.map +0 -1
  83. package/dist/components/ui/collapsible.d.ts +0 -6
  84. package/dist/components/ui/collapsible.d.ts.map +0 -1
  85. package/dist/components/ui/collapsible.js +0 -6
  86. package/dist/components/ui/collapsible.js.map +0 -1
  87. package/dist/components/ui/context-menu.d.ts +0 -26
  88. package/dist/components/ui/context-menu.d.ts.map +0 -1
  89. package/dist/components/ui/context-menu.js +0 -51
  90. package/dist/components/ui/context-menu.js.map +0 -1
  91. package/dist/components/ui/dialog.d.ts +0 -16
  92. package/dist/components/ui/dialog.d.ts.map +0 -1
  93. package/dist/components/ui/dialog.js +0 -63
  94. package/dist/components/ui/dialog.js.map +0 -1
  95. package/dist/components/ui/dropdown-menu.d.ts +0 -26
  96. package/dist/components/ui/dropdown-menu.d.ts.map +0 -1
  97. package/dist/components/ui/dropdown-menu.js +0 -51
  98. package/dist/components/ui/dropdown-menu.js.map +0 -1
  99. package/dist/components/ui/input.d.ts +0 -4
  100. package/dist/components/ui/input.d.ts.map +0 -1
  101. package/dist/components/ui/input.js +0 -7
  102. package/dist/components/ui/input.js.map +0 -1
  103. package/dist/components/ui/progress.d.ts +0 -5
  104. package/dist/components/ui/progress.d.ts.map +0 -1
  105. package/dist/components/ui/progress.js +0 -8
  106. package/dist/components/ui/progress.js.map +0 -1
  107. package/dist/components/ui/scroll-area.d.ts +0 -8
  108. package/dist/components/ui/scroll-area.d.ts.map +0 -1
  109. package/dist/components/ui/scroll-area.js +0 -16
  110. package/dist/components/ui/scroll-area.js.map +0 -1
  111. package/dist/components/ui/select.d.ts +0 -12
  112. package/dist/components/ui/select.d.ts.map +0 -1
  113. package/dist/components/ui/select.js +0 -21
  114. package/dist/components/ui/select.js.map +0 -1
  115. package/dist/components/ui/separator.d.ts +0 -5
  116. package/dist/components/ui/separator.d.ts.map +0 -1
  117. package/dist/components/ui/separator.js +0 -9
  118. package/dist/components/ui/separator.js.map +0 -1
  119. package/dist/components/ui/sheet.d.ts +0 -14
  120. package/dist/components/ui/sheet.d.ts.map +0 -1
  121. package/dist/components/ui/sheet.js +0 -66
  122. package/dist/components/ui/sheet.js.map +0 -1
  123. package/dist/components/ui/sidebar.d.ts +0 -70
  124. package/dist/components/ui/sidebar.d.ts.map +0 -1
  125. package/dist/components/ui/sidebar.js +0 -212
  126. package/dist/components/ui/sidebar.js.map +0 -1
  127. package/dist/components/ui/skeleton.d.ts +0 -3
  128. package/dist/components/ui/skeleton.d.ts.map +0 -1
  129. package/dist/components/ui/skeleton.js +0 -7
  130. package/dist/components/ui/skeleton.js.map +0 -1
  131. package/dist/components/ui/textarea.d.ts +0 -4
  132. package/dist/components/ui/textarea.d.ts.map +0 -1
  133. package/dist/components/ui/textarea.js +0 -7
  134. package/dist/components/ui/textarea.js.map +0 -1
  135. package/dist/components/ui/tooltip.d.ts +0 -8
  136. package/dist/components/ui/tooltip.d.ts.map +0 -1
  137. package/dist/components/ui/tooltip.js +0 -18
  138. package/dist/components/ui/tooltip.js.map +0 -1
  139. package/dist/config/chatConfig.d.ts +0 -4
  140. package/dist/config/chatConfig.d.ts.map +0 -1
  141. package/dist/config/chatConfig.js +0 -173
  142. package/dist/config/chatConfig.js.map +0 -1
  143. package/dist/hooks/use-mobile.d.ts +0 -2
  144. package/dist/hooks/use-mobile.d.ts.map +0 -1
  145. package/dist/hooks/use-mobile.js +0 -16
  146. package/dist/hooks/use-mobile.js.map +0 -1
  147. package/dist/index.d.ts.map +0 -1
  148. package/dist/lib/chatUtils.d.ts +0 -18
  149. package/dist/lib/chatUtils.d.ts.map +0 -1
  150. package/dist/lib/chatUtils.js +0 -64
  151. package/dist/lib/chatUtils.js.map +0 -1
  152. package/dist/lib/utils.d.ts +0 -6
  153. package/dist/lib/utils.d.ts.map +0 -1
  154. package/dist/lib/utils.js +0 -46
  155. package/dist/lib/utils.js.map +0 -1
  156. package/dist/lib/voiceCompose.d.ts +0 -6
  157. package/dist/lib/voiceCompose.d.ts.map +0 -1
  158. package/dist/lib/voiceCompose.js +0 -344
  159. package/dist/lib/voiceCompose.js.map +0 -1
  160. package/dist/types/chatTypes.d.ts +0 -386
  161. package/dist/types/chatTypes.d.ts.map +0 -1
  162. package/dist/types/chatTypes.js +0 -2
  163. package/dist/types/chatTypes.js.map +0 -1
@@ -1,732 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import React, { useState, useRef, useCallback, useEffect, memo } from 'react';
3
- import { useChatUserContext } from './UserContext';
4
- import { createObjectUrlFromDataUrl } from '../../lib/utils';
5
- import { appendVoiceSegments, mergeVoiceTranscripts, resolveVoiceProviderFactory } from '../../lib/voiceCompose';
6
- import { Button } from '../ui/button';
7
- import { Textarea } from '../ui/textarea';
8
- import { Card, CardContent } from '../ui/card';
9
- import { Badge } from '../ui/badge';
10
- import { Progress } from '../ui/progress';
11
- import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip';
12
- import { VoiceComposer } from './VoiceComposer';
13
- import { Send, Paperclip, Mic, Image, Video, FileText, X, Square, Play, Pause, Loader2, } from 'lucide-react';
14
- function getActiveMentionMatch(value, caret) {
15
- const prefix = value.slice(0, caret);
16
- const match = /(^|\s)@([\w.-]*)$/.exec(prefix);
17
- if (!match)
18
- return null;
19
- const query = match[2] ?? '';
20
- return {
21
- start: prefix.length - query.length - 1,
22
- end: caret,
23
- query,
24
- };
25
- }
26
- function resolveTargetFromMentions(value, agents) {
27
- const matches = value.matchAll(/(^|\s)@([\w.-]+)/g);
28
- for (const match of matches) {
29
- const mention = match[2]?.toLowerCase();
30
- if (!mention)
31
- continue;
32
- const agent = agents.find((candidate) => candidate.id.toLowerCase() === mention ||
33
- candidate.name.toLowerCase() === mention);
34
- if (agent)
35
- return agent;
36
- }
37
- return null;
38
- }
39
- // File upload progress component - memoized
40
- const FileUploadItem = memo(function FileUploadItem({ file, progress, onCancel }) {
41
- const guessTypeFromName = (name) => {
42
- const ext = (name || '').split('.').pop()?.toLowerCase();
43
- switch (ext) {
44
- case 'jpg':
45
- case 'jpeg':
46
- case 'png':
47
- case 'gif':
48
- case 'webp':
49
- case 'bmp':
50
- case 'svg':
51
- return 'image/*';
52
- case 'mp4':
53
- case 'mov':
54
- case 'm4v':
55
- case 'webm':
56
- return 'video/*';
57
- case 'mp3':
58
- case 'wav':
59
- case 'm4a':
60
- case 'ogg':
61
- return 'audio/*';
62
- default:
63
- return '';
64
- }
65
- };
66
- const getFileIcon = (type, name) => {
67
- const t = typeof type === 'string' && type.length > 0 ? type : guessTypeFromName(name);
68
- if (t.startsWith('image/'))
69
- return _jsx(Image, { className: "h-4 w-4" });
70
- if (t.startsWith('video/'))
71
- return _jsx(Video, { className: "h-4 w-4" });
72
- if (t.startsWith('audio/'))
73
- return _jsx(Mic, { className: "h-4 w-4" });
74
- return _jsx(FileText, { className: "h-4 w-4" });
75
- };
76
- const formatFileSize = (bytes) => {
77
- if (bytes === 0)
78
- return '0 Bytes';
79
- const k = 1024;
80
- const sizes = ['Bytes', 'KB', 'MB', 'GB'];
81
- const i = Math.floor(Math.log(bytes) / Math.log(k));
82
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
83
- };
84
- return (_jsx(Card, { className: "relative", children: _jsx(CardContent, { className: "p-3", children: _jsxs("div", { className: "flex items-center gap-3", children: [getFileIcon(file.type, file.name), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: "text-sm font-medium truncate", children: file.name }), _jsx("p", { className: "text-xs text-muted-foreground", children: formatFileSize(file.size ?? 0) }), _jsx(Progress, { value: progress, className: "h-1 mt-1" })] }), _jsx(Button, { variant: "ghost", size: "icon", className: "h-6 w-6", onClick: onCancel, children: _jsx(X, { className: "h-3 w-3" }) })] }) }) }));
85
- });
86
- // Attachment preview component - memoized
87
- const AttachmentPreview = memo(function AttachmentPreview({ attachment, onRemove }) {
88
- const [isPlaying, setIsPlaying] = useState(false);
89
- const [audioPlaybackSrc, setAudioPlaybackSrc] = useState(attachment.dataUrl);
90
- const audioRef = useRef(null);
91
- useEffect(() => {
92
- if (attachment.kind !== 'audio' || !attachment.dataUrl.startsWith('data:')) {
93
- setAudioPlaybackSrc(attachment.dataUrl);
94
- return;
95
- }
96
- const objectUrl = createObjectUrlFromDataUrl(attachment.dataUrl);
97
- if (!objectUrl) {
98
- setAudioPlaybackSrc(attachment.dataUrl);
99
- return;
100
- }
101
- setAudioPlaybackSrc(objectUrl);
102
- return () => {
103
- URL.revokeObjectURL(objectUrl);
104
- };
105
- }, [attachment.kind, attachment.dataUrl]);
106
- const handlePlayPause = () => {
107
- if (audioRef.current) {
108
- if (isPlaying) {
109
- audioRef.current.pause();
110
- }
111
- else {
112
- audioRef.current.play();
113
- }
114
- setIsPlaying(!isPlaying);
115
- }
116
- };
117
- const formatDuration = (ms) => {
118
- if (!ms)
119
- return '';
120
- const seconds = Math.floor(ms / 1000);
121
- const minutes = Math.floor(seconds / 60);
122
- return `${minutes}:${(seconds % 60).toString().padStart(2, '0')}`;
123
- };
124
- return (_jsx(Card, { className: "relative group", children: _jsxs(CardContent, { className: "p-2", children: [attachment.kind === 'image' && (_jsxs("div", { className: "relative", children: [_jsx("img", { src: attachment.dataUrl, alt: attachment.fileName || 'Attachment', className: "w-full h-20 object-cover rounded" }), _jsx("div", { className: "absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity rounded flex items-center justify-center", children: _jsx(Button, { variant: "destructive", size: "icon", className: "h-6 w-6", onClick: onRemove, children: _jsx(X, { className: "h-3 w-3" }) }) })] })), attachment.kind === 'video' && (_jsxs("div", { className: "relative", children: [_jsx("video", { src: attachment.dataUrl, poster: attachment.poster, className: "w-full h-20 object-cover rounded", muted: true }), _jsx("div", { className: "absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity rounded flex items-center justify-center", children: _jsx(Button, { variant: "destructive", size: "icon", className: "h-6 w-6", onClick: onRemove, children: _jsx(X, { className: "h-3 w-3" }) }) }), _jsx(Badge, { className: "absolute bottom-1 right-1 text-xs", children: formatDuration(attachment.durationMs) })] })), attachment.kind === 'audio' && (_jsxs("div", { className: "flex items-center gap-2 p-2", children: [_jsx(Button, { variant: "outline", size: "icon", className: "h-8 w-8", onClick: handlePlayPause, children: isPlaying ? _jsx(Pause, { className: "h-3 w-3" }) : _jsx(Play, { className: "h-3 w-3" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("p", { className: "text-xs font-medium", children: attachment.fileName || 'Audio' }), _jsx("p", { className: "text-xs text-muted-foreground", children: formatDuration(attachment.durationMs) })] }), _jsx("audio", { ref: audioRef, onPlay: () => setIsPlaying(true), onPause: () => setIsPlaying(false), onEnded: () => setIsPlaying(false), preload: "metadata", children: _jsx("source", { src: audioPlaybackSrc, type: attachment.mimeType }) }), _jsx(Button, { variant: "ghost", size: "icon", className: "h-6 w-6 opacity-0 group-hover:opacity-100 transition-opacity", onClick: onRemove, children: _jsx(X, { className: "h-3 w-3" }) })] })), attachment.fileName && attachment.kind !== 'audio' && (_jsx("div", { className: "absolute bottom-0 left-0 right-0 bg-black/70 text-white text-xs p-1 rounded-b", children: _jsx("p", { className: "truncate", children: attachment.fileName }) }))] }) }));
125
- });
126
- const resolveVoiceErrorMessage = (error, config) => {
127
- if (error instanceof DOMException && error.name === 'NotAllowedError') {
128
- return config?.labels?.voicePermissionDenied || 'Microphone access was denied.';
129
- }
130
- if (error instanceof Error && error.message.trim().length > 0) {
131
- return error.message;
132
- }
133
- return config?.labels?.voiceCaptureError || 'Unable to capture audio.';
134
- };
135
- const clearVoiceTranscript = () => ({});
136
- const resolveVoiceSegmentDuration = (segment) => segment.attachment.durationMs ?? 0;
137
- export const ChatInput = memo(function ChatInput({ value, onChange, onSubmit, attachments, onAttachmentsChange, placeholder = 'Type your message...', disabled = false, isGenerating = false, onStopGeneration, enableFileUpload = true, enableAudioRecording = true, maxAttachments = 4, maxFileSize = 10 * 1024 * 1024, // 10MB
138
- acceptedFileTypes = ['image/*', 'video/*', 'audio/*'], className = '', config, mentionAgents = [], onTargetAgentChange, }) {
139
- const voiceDefaultMode = config?.voiceCompose?.defaultMode ?? 'text';
140
- const voiceReviewMode = config?.voiceCompose?.reviewMode ?? 'manual';
141
- const voiceAutoSendDelayMs = config?.voiceCompose?.autoSendDelayMs ?? 5000;
142
- const voicePersistComposer = config?.voiceCompose?.persistComposer ?? true;
143
- const voiceShowTranscriptPreview = config?.voiceCompose?.showTranscriptPreview ?? true;
144
- const voiceTranscriptMode = config?.voiceCompose?.transcriptMode ?? 'final-only';
145
- const voiceMaxRecordingMs = config?.voiceCompose?.maxRecordingMs;
146
- const { setContext } = useChatUserContext();
147
- const [uploadProgress, setUploadProgress] = useState(new Map());
148
- const [isVoiceComposerOpen, setIsVoiceComposerOpen] = useState(() => enableAudioRecording && voiceDefaultMode === 'voice');
149
- const [voiceState, setVoiceState] = useState('idle');
150
- const [voiceDraft, setVoiceDraft] = useState(null);
151
- const [voiceTranscript, setVoiceTranscript] = useState(clearVoiceTranscript);
152
- const [voiceDurationMs, setVoiceDurationMs] = useState(0);
153
- const [voiceAudioLevel, setVoiceAudioLevel] = useState(0);
154
- const [voiceCountdownMs, setVoiceCountdownMs] = useState(0);
155
- const [isVoiceAutoSendActive, setIsVoiceAutoSendActive] = useState(false);
156
- const [voiceError, setVoiceError] = useState(null);
157
- const [activeMention, setActiveMention] = useState(null);
158
- const [activeMentionIndex, setActiveMentionIndex] = useState(0);
159
- const textareaRef = useRef(null);
160
- const fileInputRef = useRef(null);
161
- const voiceProviderRef = useRef(null);
162
- const voiceDraftRef = useRef(null);
163
- const voiceAppendBaseRef = useRef(null);
164
- const voiceAppendBaseDurationRef = useRef(0);
165
- const filteredMentionAgents = React.useMemo(() => {
166
- if (!activeMention || mentionAgents.length === 0)
167
- return [];
168
- const query = activeMention.query.trim().toLowerCase();
169
- const rank = (agent) => {
170
- const id = agent.id.toLowerCase();
171
- const name = agent.name.toLowerCase();
172
- if (!query)
173
- return 0;
174
- if (name.startsWith(query) || id.startsWith(query))
175
- return 0;
176
- if (name.includes(query) || id.includes(query))
177
- return 1;
178
- return 2;
179
- };
180
- return mentionAgents
181
- .filter((agent) => rank(agent) < 2)
182
- .sort((left, right) => {
183
- const rankDiff = rank(left) - rank(right);
184
- if (rankDiff !== 0)
185
- return rankDiff;
186
- return left.name.localeCompare(right.name);
187
- })
188
- .slice(0, 6);
189
- }, [activeMention, mentionAgents]);
190
- const isMentionMenuOpen = filteredMentionAgents.length > 0;
191
- const syncMentionState = useCallback((nextValue, nextCaret) => {
192
- const caret = typeof nextCaret === 'number'
193
- ? nextCaret
194
- : textareaRef.current?.selectionStart ?? nextValue.length;
195
- const nextMatch = getActiveMentionMatch(nextValue, caret);
196
- setActiveMention((prev) => {
197
- if (prev?.start === nextMatch?.start &&
198
- prev?.end === nextMatch?.end &&
199
- prev?.query === nextMatch?.query) {
200
- return prev;
201
- }
202
- return nextMatch;
203
- });
204
- setActiveMentionIndex(0);
205
- }, []);
206
- // Cleanup recording on unmount
207
- useEffect(() => {
208
- return () => {
209
- if (voiceProviderRef.current) {
210
- void voiceProviderRef.current.destroy();
211
- voiceProviderRef.current = null;
212
- }
213
- };
214
- }, []);
215
- useEffect(() => {
216
- voiceDraftRef.current = voiceDraft;
217
- }, [voiceDraft]);
218
- useEffect(() => {
219
- if (!isMentionMenuOpen) {
220
- setActiveMentionIndex(0);
221
- return;
222
- }
223
- setActiveMentionIndex((prev) => prev >= filteredMentionAgents.length ? 0 : prev);
224
- }, [filteredMentionAgents.length, isMentionMenuOpen]);
225
- const selectMentionAgent = useCallback((agent) => {
226
- if (!activeMention)
227
- return;
228
- const replacement = `@${agent.name} `;
229
- const nextValue = value.slice(0, activeMention.start) +
230
- replacement +
231
- value.slice(activeMention.end);
232
- const nextCaret = activeMention.start + replacement.length;
233
- onChange(nextValue);
234
- onTargetAgentChange?.(agent.id);
235
- setActiveMention(null);
236
- setActiveMentionIndex(0);
237
- requestAnimationFrame(() => {
238
- textareaRef.current?.focus();
239
- textareaRef.current?.setSelectionRange(nextCaret, nextCaret);
240
- });
241
- }, [activeMention, onChange, onTargetAgentChange, value]);
242
- const handleSubmit = (e) => {
243
- e.preventDefault();
244
- if ((!value.trim() && attachments.length === 0) || disabled || isGenerating)
245
- return;
246
- const mentionedAgent = resolveTargetFromMentions(value, mentionAgents);
247
- if (mentionedAgent) {
248
- onTargetAgentChange?.(mentionedAgent.id);
249
- }
250
- onSubmit(value.trim(), attachments);
251
- onChange('');
252
- onAttachmentsChange([]);
253
- setActiveMention(null);
254
- setActiveMentionIndex(0);
255
- };
256
- const handleKeyDown = (e) => {
257
- if (isMentionMenuOpen) {
258
- if (e.key === 'ArrowDown') {
259
- e.preventDefault();
260
- setActiveMentionIndex((prev) => prev >= filteredMentionAgents.length - 1 ? 0 : prev + 1);
261
- return;
262
- }
263
- if (e.key === 'ArrowUp') {
264
- e.preventDefault();
265
- setActiveMentionIndex((prev) => prev <= 0 ? filteredMentionAgents.length - 1 : prev - 1);
266
- return;
267
- }
268
- if ((e.key === 'Enter' || e.key === 'Tab') && filteredMentionAgents[activeMentionIndex]) {
269
- e.preventDefault();
270
- selectMentionAgent(filteredMentionAgents[activeMentionIndex]);
271
- return;
272
- }
273
- if (e.key === 'Escape') {
274
- e.preventDefault();
275
- setActiveMention(null);
276
- setActiveMentionIndex(0);
277
- return;
278
- }
279
- }
280
- if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && window.innerWidth > 768) {
281
- e.preventDefault();
282
- handleSubmit(e);
283
- }
284
- };
285
- const processFile = async (file) => {
286
- if (file.size > maxFileSize) {
287
- alert(`File too large. Max allowed: ${Math.round(maxFileSize / 1024 / 1024)}MB`);
288
- return null;
289
- }
290
- const fileId = `${Date.now()}_${Math.random().toString(36).slice(2)}`;
291
- // Start upload progress
292
- setUploadProgress(prev => new Map(prev.set(fileId, {
293
- fileName: file.name,
294
- progress: 0,
295
- status: 'uploading',
296
- })));
297
- try {
298
- // Simulate upload progress
299
- for (let progress = 0; progress <= 100; progress += 20) {
300
- await new Promise(resolve => setTimeout(resolve, 100));
301
- setUploadProgress(prev => new Map(prev.set(fileId, {
302
- fileName: file.name,
303
- progress,
304
- status: 'uploading',
305
- })));
306
- }
307
- const dataUrl = await new Promise((resolve, reject) => {
308
- const reader = new FileReader();
309
- reader.onload = () => resolve(reader.result);
310
- reader.onerror = reject;
311
- reader.readAsDataURL(file);
312
- });
313
- setUploadProgress(prev => {
314
- const newMap = new Map(prev);
315
- newMap.delete(fileId);
316
- return newMap;
317
- });
318
- const attachment = {
319
- kind: file.type.startsWith('image/') ? 'image' :
320
- file.type.startsWith('video/') ? 'video' :
321
- file.type.startsWith('audio/') ? 'audio' : 'image',
322
- dataUrl,
323
- mimeType: file.type,
324
- fileName: file.name,
325
- size: file.size,
326
- };
327
- // For video files, try to get duration
328
- if (attachment.kind === 'video') {
329
- try {
330
- const video = document.createElement('video');
331
- video.src = dataUrl;
332
- await new Promise((resolve) => {
333
- video.onloadedmetadata = resolve;
334
- });
335
- attachment.durationMs = video.duration * 1000;
336
- }
337
- catch (error) {
338
- console.warn('Could not get video duration:', error);
339
- }
340
- }
341
- // If it's an image, mark as latest reference image in shared context
342
- if (attachment.kind === 'image') {
343
- setContext({ lastReferenceImage: { dataUrl: attachment.dataUrl, mimeType: attachment.mimeType, addedAt: Date.now() } });
344
- }
345
- return attachment;
346
- }
347
- catch (error) {
348
- console.error('Error processing file:', error);
349
- setUploadProgress(prev => {
350
- const newMap = new Map(prev);
351
- newMap.delete(fileId);
352
- return newMap;
353
- });
354
- alert('Failed to process file');
355
- return null;
356
- }
357
- };
358
- const handleFileSelect = async (e) => {
359
- const files = e.target.files;
360
- if (!files)
361
- return;
362
- const remainingSlots = maxAttachments - attachments.length;
363
- const filesToProcess = Array.from(files).slice(0, remainingSlots);
364
- for (const file of filesToProcess) {
365
- const attachment = await processFile(file);
366
- if (attachment) {
367
- onAttachmentsChange([...attachments, attachment]);
368
- }
369
- }
370
- // Reset input
371
- e.target.value = '';
372
- };
373
- const handleDrop = useCallback(async (e) => {
374
- e.preventDefault();
375
- if (!enableFileUpload)
376
- return;
377
- const files = Array.from(e.dataTransfer.files);
378
- const remainingSlots = maxAttachments - attachments.length;
379
- const filesToProcess = files.slice(0, remainingSlots);
380
- for (const file of filesToProcess) {
381
- const attachment = await processFile(file);
382
- if (attachment) {
383
- onAttachmentsChange([...attachments, attachment]);
384
- }
385
- }
386
- }, [attachments, enableFileUpload, maxAttachments, onAttachmentsChange]);
387
- const handleDragOver = useCallback((e) => {
388
- e.preventDefault();
389
- }, []);
390
- const resetVoiceComposerState = useCallback((nextState = 'idle') => {
391
- setVoiceState(nextState);
392
- setVoiceDraft(null);
393
- voiceDraftRef.current = null;
394
- voiceAppendBaseRef.current = null;
395
- voiceAppendBaseDurationRef.current = 0;
396
- setVoiceTranscript(clearVoiceTranscript());
397
- setVoiceDurationMs(0);
398
- setVoiceAudioLevel(0);
399
- setVoiceCountdownMs(0);
400
- setIsVoiceAutoSendActive(false);
401
- setVoiceError(null);
402
- }, []);
403
- const armVoiceDraftForAppend = useCallback((segment) => {
404
- voiceAppendBaseRef.current = segment;
405
- voiceAppendBaseDurationRef.current = segment ? resolveVoiceSegmentDuration(segment) : 0;
406
- }, []);
407
- const handleVoiceProviderStateChange = useCallback((nextState) => {
408
- if (voiceReviewMode === 'armed' &&
409
- (nextState === 'waiting_for_speech' || nextState === 'listening')) {
410
- const currentDraft = voiceDraftRef.current;
411
- if (currentDraft) {
412
- armVoiceDraftForAppend(currentDraft);
413
- }
414
- }
415
- if (voiceReviewMode === 'armed' &&
416
- nextState === 'listening' &&
417
- voiceDraftRef.current) {
418
- setVoiceCountdownMs(voiceAutoSendDelayMs);
419
- setIsVoiceAutoSendActive(false);
420
- }
421
- setVoiceState(nextState);
422
- }, [armVoiceDraftForAppend, voiceAutoSendDelayMs, voiceReviewMode]);
423
- const ensureVoiceProvider = useCallback(async () => {
424
- if (voiceProviderRef.current) {
425
- return voiceProviderRef.current;
426
- }
427
- const createProvider = resolveVoiceProviderFactory(config?.voiceCompose?.createProvider);
428
- const provider = await createProvider({
429
- onStateChange: handleVoiceProviderStateChange,
430
- onAudioLevelChange: setVoiceAudioLevel,
431
- onDurationChange: (durationMs) => {
432
- setVoiceDurationMs(voiceAppendBaseDurationRef.current + durationMs);
433
- },
434
- onTranscriptChange: (transcript) => {
435
- const baseTranscript = voiceAppendBaseRef.current?.transcript;
436
- setVoiceTranscript(baseTranscript
437
- ? mergeVoiceTranscripts(baseTranscript, transcript)
438
- : transcript);
439
- },
440
- onSegmentReady: (segment) => {
441
- void (async () => {
442
- const previousSegment = voiceAppendBaseRef.current;
443
- try {
444
- const nextSegment = previousSegment
445
- ? await appendVoiceSegments(previousSegment, segment)
446
- : segment;
447
- voiceDraftRef.current = nextSegment;
448
- setVoiceDraft(nextSegment);
449
- setVoiceTranscript(nextSegment.transcript ?? clearVoiceTranscript());
450
- setVoiceDurationMs(resolveVoiceSegmentDuration(nextSegment));
451
- setVoiceAudioLevel(0);
452
- setVoiceCountdownMs(voiceAutoSendDelayMs);
453
- setIsVoiceAutoSendActive(voiceAutoSendDelayMs > 0);
454
- setVoiceError(null);
455
- if (voiceReviewMode === 'armed') {
456
- armVoiceDraftForAppend(nextSegment);
457
- }
458
- else {
459
- armVoiceDraftForAppend(null);
460
- }
461
- setVoiceState((currentState) => voiceReviewMode === 'armed' && (currentState === 'waiting_for_speech' ||
462
- currentState === 'listening')
463
- ? currentState
464
- : 'review');
465
- }
466
- catch (error) {
467
- const resolvedError = resolveVoiceErrorMessage(error, config);
468
- armVoiceDraftForAppend(null);
469
- setVoiceAudioLevel(0);
470
- setVoiceCountdownMs(0);
471
- setIsVoiceAutoSendActive(false);
472
- if (previousSegment) {
473
- voiceDraftRef.current = previousSegment;
474
- setVoiceDraft(previousSegment);
475
- setVoiceTranscript(previousSegment.transcript ?? clearVoiceTranscript());
476
- setVoiceDurationMs(resolveVoiceSegmentDuration(previousSegment));
477
- setVoiceError(resolvedError);
478
- setVoiceState('review');
479
- return;
480
- }
481
- voiceDraftRef.current = null;
482
- setVoiceDraft(null);
483
- setVoiceTranscript(clearVoiceTranscript());
484
- setVoiceDurationMs(0);
485
- setVoiceError(resolvedError);
486
- setVoiceState('error');
487
- }
488
- })();
489
- },
490
- onError: (error) => {
491
- const previousSegment = voiceAppendBaseRef.current;
492
- armVoiceDraftForAppend(null);
493
- setVoiceError(resolveVoiceErrorMessage(error, config));
494
- setVoiceAudioLevel(0);
495
- setVoiceCountdownMs(0);
496
- setIsVoiceAutoSendActive(false);
497
- if (previousSegment) {
498
- voiceDraftRef.current = previousSegment;
499
- setVoiceDraft(previousSegment);
500
- setVoiceTranscript(previousSegment.transcript ?? clearVoiceTranscript());
501
- setVoiceDurationMs(resolveVoiceSegmentDuration(previousSegment));
502
- setVoiceState('review');
503
- return;
504
- }
505
- voiceDraftRef.current = null;
506
- setVoiceDraft(null);
507
- setVoiceTranscript(clearVoiceTranscript());
508
- setVoiceDurationMs(0);
509
- setVoiceState('error');
510
- },
511
- }, {
512
- maxRecordingMs: voiceMaxRecordingMs,
513
- });
514
- voiceProviderRef.current = provider;
515
- return provider;
516
- }, [armVoiceDraftForAppend, config, handleVoiceProviderStateChange, voiceAutoSendDelayMs, voiceMaxRecordingMs, voiceReviewMode]);
517
- const closeVoiceComposer = useCallback(async () => {
518
- voiceAppendBaseRef.current = null;
519
- voiceAppendBaseDurationRef.current = 0;
520
- setIsVoiceComposerOpen(false);
521
- setVoiceError(null);
522
- setVoiceCountdownMs(0);
523
- setVoiceAudioLevel(0);
524
- setVoiceTranscript(clearVoiceTranscript());
525
- setVoiceDraft(null);
526
- voiceDraftRef.current = null;
527
- setVoiceDurationMs(0);
528
- setVoiceState('idle');
529
- if (voiceProviderRef.current) {
530
- await voiceProviderRef.current.cancel();
531
- }
532
- }, []);
533
- const startVoiceCapture = useCallback(async (appendToDraft = false) => {
534
- if (disabled || isGenerating) {
535
- return;
536
- }
537
- const previousDraft = appendToDraft ? voiceDraftRef.current : null;
538
- const previousDurationMs = previousDraft ? resolveVoiceSegmentDuration(previousDraft) : 0;
539
- setIsVoiceComposerOpen(true);
540
- setVoiceError(null);
541
- setVoiceCountdownMs(0);
542
- setVoiceAudioLevel(0);
543
- setIsVoiceAutoSendActive(false);
544
- voiceAppendBaseRef.current = previousDraft;
545
- voiceAppendBaseDurationRef.current = previousDurationMs;
546
- if (!previousDraft) {
547
- setVoiceDraft(null);
548
- voiceDraftRef.current = null;
549
- setVoiceTranscript(clearVoiceTranscript());
550
- setVoiceDurationMs(0);
551
- }
552
- else {
553
- setVoiceTranscript(previousDraft.transcript ?? clearVoiceTranscript());
554
- setVoiceDurationMs(previousDurationMs);
555
- }
556
- try {
557
- const provider = await ensureVoiceProvider();
558
- await provider.start();
559
- }
560
- catch (error) {
561
- const resolvedError = resolveVoiceErrorMessage(error, config);
562
- voiceAppendBaseRef.current = null;
563
- voiceAppendBaseDurationRef.current = 0;
564
- setVoiceAudioLevel(0);
565
- setVoiceCountdownMs(0);
566
- setIsVoiceAutoSendActive(false);
567
- if (previousDraft) {
568
- voiceDraftRef.current = previousDraft;
569
- setVoiceDraft(previousDraft);
570
- setVoiceTranscript(previousDraft.transcript ?? clearVoiceTranscript());
571
- setVoiceDurationMs(previousDurationMs);
572
- setVoiceError(resolvedError);
573
- setVoiceState('review');
574
- return;
575
- }
576
- voiceDraftRef.current = null;
577
- setVoiceDraft(null);
578
- setVoiceTranscript(clearVoiceTranscript());
579
- setVoiceDurationMs(0);
580
- setVoiceError(resolvedError);
581
- setVoiceState('error');
582
- }
583
- }, [disabled, isGenerating, ensureVoiceProvider, config]);
584
- const stopVoiceCapture = useCallback(async () => {
585
- if (!voiceProviderRef.current)
586
- return;
587
- try {
588
- await voiceProviderRef.current.stop();
589
- }
590
- catch (error) {
591
- setVoiceError(resolveVoiceErrorMessage(error, config));
592
- setVoiceState('error');
593
- }
594
- }, [config]);
595
- const cancelVoiceCapture = useCallback(async () => {
596
- voiceAppendBaseRef.current = null;
597
- voiceAppendBaseDurationRef.current = 0;
598
- if (voiceProviderRef.current) {
599
- await voiceProviderRef.current.cancel();
600
- }
601
- resetVoiceComposerState('idle');
602
- }, [resetVoiceComposerState]);
603
- const finalizeVoiceComposerAfterSend = useCallback(() => {
604
- if (voicePersistComposer) {
605
- resetVoiceComposerState('idle');
606
- setIsVoiceComposerOpen(true);
607
- return;
608
- }
609
- void closeVoiceComposer();
610
- }, [voicePersistComposer, resetVoiceComposerState, closeVoiceComposer]);
611
- const sendVoiceDraft = useCallback(() => {
612
- void (async () => {
613
- if (!voiceDraft || disabled || isGenerating) {
614
- return;
615
- }
616
- setVoiceState('sending');
617
- setVoiceCountdownMs(0);
618
- setIsVoiceAutoSendActive(false);
619
- if (voiceProviderRef.current) {
620
- await voiceProviderRef.current.cancel();
621
- }
622
- onSubmit('', [...attachments, voiceDraft.attachment]);
623
- onChange('');
624
- onAttachmentsChange([]);
625
- finalizeVoiceComposerAfterSend();
626
- })();
627
- }, [
628
- voiceDraft,
629
- disabled,
630
- isGenerating,
631
- onSubmit,
632
- attachments,
633
- onChange,
634
- onAttachmentsChange,
635
- finalizeVoiceComposerAfterSend,
636
- ]);
637
- const cancelVoiceAutoSend = useCallback(() => {
638
- void (async () => {
639
- if (voiceReviewMode === 'armed' && voiceProviderRef.current) {
640
- await voiceProviderRef.current.cancel();
641
- }
642
- armVoiceDraftForAppend(null);
643
- setVoiceAudioLevel(0);
644
- setVoiceState('review');
645
- })();
646
- setVoiceCountdownMs(0);
647
- setIsVoiceAutoSendActive(false);
648
- }, [armVoiceDraftForAppend, voiceReviewMode]);
649
- const pauseVoiceReview = useCallback(async () => {
650
- if (voiceState === 'listening') {
651
- await stopVoiceCapture();
652
- return;
653
- }
654
- if (voiceReviewMode === 'armed' && voiceProviderRef.current) {
655
- await voiceProviderRef.current.cancel();
656
- }
657
- armVoiceDraftForAppend(null);
658
- setVoiceAudioLevel(0);
659
- setVoiceState('review');
660
- }, [armVoiceDraftForAppend, stopVoiceCapture, voiceReviewMode, voiceState]);
661
- useEffect(() => {
662
- if (!voiceDraft ||
663
- voiceAutoSendDelayMs <= 0 ||
664
- !isVoiceAutoSendActive) {
665
- return;
666
- }
667
- const canContinueCounting = voiceState === 'review' || (voiceReviewMode === 'armed' &&
668
- voiceState === 'waiting_for_speech');
669
- if (!canContinueCounting) {
670
- return;
671
- }
672
- const timer = setInterval(() => {
673
- setVoiceCountdownMs((previous) => {
674
- const remaining = Math.max(0, previous - 100);
675
- if (remaining <= 0) {
676
- clearInterval(timer);
677
- queueMicrotask(() => {
678
- sendVoiceDraft();
679
- });
680
- }
681
- return remaining;
682
- });
683
- }, 100);
684
- return () => clearInterval(timer);
685
- }, [voiceState, voiceDraft, voiceReviewMode, voiceAutoSendDelayMs, isVoiceAutoSendActive, sendVoiceDraft]);
686
- const removeAttachment = (index) => {
687
- const newAttachments = attachments.filter((_, i) => i !== index);
688
- onAttachmentsChange(newAttachments);
689
- };
690
- const canAddMoreAttachments = attachments.length < maxAttachments;
691
- const showVoiceComposer = enableAudioRecording && isVoiceComposerOpen;
692
- return (_jsx(TooltipProvider, { children: _jsx("div", { className: `border-t py-0 bg-transparent ${className}`, children: _jsxs("div", { className: "px-0 md:p-2 pb-1 space-y-4 bg-transparent", children: [uploadProgress.size > 0 && (_jsx("div", { className: "space-y-2", children: Array.from(uploadProgress.entries()).map(([id, progress]) => (_jsx(FileUploadItem, { file: { name: progress.fileName }, progress: progress.progress, onCancel: () => {
693
- setUploadProgress(prev => {
694
- const newMap = new Map(prev);
695
- newMap.delete(id);
696
- return newMap;
697
- });
698
- } }, id))) })), attachments.length > 0 && (_jsx("div", { className: "grid grid-cols-4 gap-2", children: attachments.map((attachment, index) => (_jsx(AttachmentPreview, { attachment: attachment, onRemove: () => removeAttachment(index) }, index))) })), showVoiceComposer ? (_jsx("div", { className: "mb-1 flex justify-center", children: _jsx(VoiceComposer, { state: voiceState, transcript: voiceTranscript, transcriptMode: voiceTranscriptMode, showTranscriptPreview: voiceShowTranscriptPreview, attachment: voiceDraft?.attachment ?? null, durationMs: voiceDurationMs, audioLevel: voiceAudioLevel, countdownMs: voiceCountdownMs, autoSendDelayMs: voiceAutoSendDelayMs, isAutoSendActive: isVoiceAutoSendActive, reviewMode: voiceReviewMode, errorMessage: voiceError, disabled: disabled || isGenerating, labels: config?.labels, onStart: () => {
699
- void startVoiceCapture();
700
- }, onStop: () => {
701
- void stopVoiceCapture();
702
- }, onPauseReview: () => {
703
- void pauseVoiceReview();
704
- }, onCancelAutoSend: () => {
705
- cancelVoiceAutoSend();
706
- }, onDiscard: () => {
707
- void cancelVoiceCapture();
708
- }, onRecordAgain: () => {
709
- void startVoiceCapture(true);
710
- }, onSendNow: sendVoiceDraft, onExit: () => {
711
- void closeVoiceComposer();
712
- } }) })) : (_jsx("form", { onSubmit: handleSubmit, className: "mb-1 flex justify-center", children: _jsxs("div", { className: "flex items-end gap-2 p-3 border rounded-lg bg-background w-full md:min-w-3xl max-w-3xl", onDrop: handleDrop, onDragOver: handleDragOver, children: [enableFileUpload && canAddMoreAttachments && (_jsxs(_Fragment, { children: [_jsx("input", { ref: fileInputRef, type: "file", multiple: true, accept: acceptedFileTypes.join(','), onChange: handleFileSelect, className: "hidden" }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { type: "button", variant: "outline", size: "icon", className: "h-10 w-10", onClick: (e) => {
713
- e.preventDefault();
714
- e.stopPropagation();
715
- fileInputRef.current?.click();
716
- }, disabled: disabled, children: _jsx(Paperclip, { className: "h-4 w-4" }) }) }), _jsx(TooltipContent, { children: config?.labels?.attachFileTooltip })] })] })), _jsxs("div", { className: "relative flex-1", children: [_jsx(Textarea, { ref: textareaRef, value: value, onChange: (e) => {
717
- onChange(e.target.value);
718
- syncMentionState(e.target.value, e.target.selectionStart ?? e.target.value.length);
719
- }, onSelect: (e) => {
720
- const target = e.target;
721
- syncMentionState(target.value, target.selectionStart ?? target.value.length);
722
- }, onClick: (e) => {
723
- const target = e.target;
724
- syncMentionState(target.value, target.selectionStart ?? target.value.length);
725
- }, onKeyDown: handleKeyDown, placeholder: placeholder, disabled: disabled, className: "max-h-[120px] resize-none border-0 bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0", rows: 1 }), isMentionMenuOpen && (_jsx("div", { className: "absolute bottom-full left-0 right-0 mb-2 overflow-hidden rounded-md border bg-popover shadow-md", children: _jsx("div", { className: "p-1", children: filteredMentionAgents.map((agent, index) => (_jsxs("button", { type: "button", className: `flex w-full items-center gap-2 rounded-sm px-3 py-2 text-left text-sm ${index === activeMentionIndex ? 'bg-accent text-accent-foreground' : 'hover:bg-accent/60'}`, onMouseDown: (mouseEvent) => {
726
- mouseEvent.preventDefault();
727
- selectMentionAgent(agent);
728
- }, children: [_jsx("span", { className: "font-medium", children: agent.name }), agent.description && (_jsx("span", { className: "truncate text-xs text-muted-foreground", children: agent.description }))] }, agent.id))) }) }))] }), enableAudioRecording && canAddMoreAttachments && !value.trim() && (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { type: "button", variant: "outline", size: "icon", className: "h-10 w-10", onClick: () => {
729
- void startVoiceCapture();
730
- }, disabled: disabled || isGenerating, children: _jsx(Mic, { className: "h-4 w-4" }) }) }), _jsx(TooltipContent, { children: config?.labels?.voiceEnter })] })), isGenerating ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { type: "button", variant: "outline", size: "icon", className: "h-10 w-10", onClick: onStopGeneration, children: _jsx(Square, { className: "h-4 w-4" }) }) }), _jsx(TooltipContent, { children: config?.labels?.stopGenerationTooltip })] })) : (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { type: "submit", size: "icon", className: "h-10 w-10", disabled: disabled || (!value.trim() && attachments.length === 0), children: disabled ? (_jsx(Loader2, { className: "h-4 w-4 animate-spin" })) : (_jsx(Send, { className: "h-4 w-4" })) }) }), _jsx(TooltipContent, { children: config?.labels?.sendMessageTooltip })] }))] }) })), _jsxs("div", { className: "text-[10px] text-muted-foreground text-center", children: [window.innerWidth > 768 ? config?.labels?.inputHelpText : '', attachments.length > 0 && (_jsxs(_Fragment, { children: [" \u2022 ", attachments.length, "/", maxAttachments, " anexos"] })), config?.labels?.footerLabel && (_jsxs(_Fragment, { children: [" \u2022 ", config.labels.footerLabel] }))] })] }) }) }));
731
- });
732
- //# sourceMappingURL=ChatInput.js.map