@clikvn/agent-widget-embedded 0.0.11-dev → 0.0.12-dev
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Chat/MultimodalInput.d.ts.map +1 -1
- package/dist/index.html +52 -12
- package/dist/web.js +1 -1
- package/package.json +3 -3
- package/.eslintrc +0 -34
- package/.prettierrc +0 -8
- package/src/assets/common.css +0 -148
- package/src/assets/tailwindcss.css +0 -3
- package/src/commons/constants/index.ts +0 -1
- package/src/commons/constants/variables.ts +0 -25
- package/src/components/Agent/index.tsx +0 -14
- package/src/components/Chat/AudioPlayer.tsx +0 -44
- package/src/components/Chat/Chat.tsx +0 -91
- package/src/components/Chat/Icons.tsx +0 -1796
- package/src/components/Chat/Markdown.tsx +0 -335
- package/src/components/Chat/Message.tsx +0 -217
- package/src/components/Chat/MultimodalInput.tsx +0 -505
- package/src/components/Chat/Overview.tsx +0 -46
- package/src/components/Chat/PreviewAttachment.tsx +0 -46
- package/src/components/Chat/SuggestedActions.tsx +0 -99
- package/src/components/Chat/ui/Button.tsx +0 -55
- package/src/components/Chat/ui/Textarea.tsx +0 -23
- package/src/constants.ts +0 -1
- package/src/env.d.ts +0 -10
- package/src/features/AgentWidget/index.tsx +0 -63
- package/src/global.d.ts +0 -1
- package/src/hooks/useAudioRecording.ts +0 -50
- package/src/hooks/useChat.ts +0 -262
- package/src/hooks/useChatData.tsx +0 -68
- package/src/hooks/useConfiguration.tsx +0 -63
- package/src/hooks/useScrollToBottom.ts +0 -31
- package/src/index.ts +0 -1
- package/src/models/FlowiseClient.ts +0 -103
- package/src/models.ts +0 -1
- package/src/register.tsx +0 -85
- package/src/services/apis.ts +0 -12
- package/src/services/bot.service.ts +0 -15
- package/src/services/chat.service.ts +0 -199
- package/src/types/bot.type.ts +0 -10
- package/src/types/chat.type.ts +0 -11
- package/src/types/common.type.ts +0 -24
- package/src/types/flowise.type.ts +0 -108
- package/src/types/user.type.ts +0 -15
- package/src/types.ts +0 -0
- package/src/utils/audioRecording.ts +0 -371
- package/src/utils/commonUtils.ts +0 -47
- package/src/utils/functionUtils.ts +0 -17
- package/src/utils/requestUtils.ts +0 -113
- package/src/utils/streamUtils.ts +0 -18
- package/src/web.ts +0 -6
- package/src/window.ts +0 -43
- package/tsconfig.json +0 -24
|
@@ -1,505 +0,0 @@
|
|
|
1
|
-
import { motion } from 'framer-motion';
|
|
2
|
-
import {
|
|
3
|
-
type ChangeEvent,
|
|
4
|
-
FC,
|
|
5
|
-
useCallback,
|
|
6
|
-
useEffect,
|
|
7
|
-
useRef,
|
|
8
|
-
useState,
|
|
9
|
-
} from 'react';
|
|
10
|
-
import { useLocalStorage, useWindowSize } from 'usehooks-ts';
|
|
11
|
-
import { BotType } from '../../types/bot.type';
|
|
12
|
-
import { ChatMessageType, IFileUpload } from '../../types/flowise.type';
|
|
13
|
-
import {
|
|
14
|
-
cn,
|
|
15
|
-
generateExtendedFileName,
|
|
16
|
-
generateUUID,
|
|
17
|
-
} from '../../utils/commonUtils';
|
|
18
|
-
import {
|
|
19
|
-
ArrowUpIcon,
|
|
20
|
-
CheckCirclFillIcon,
|
|
21
|
-
ClikCloseIcon,
|
|
22
|
-
ClikMessageIcon,
|
|
23
|
-
ClikMicrophoneIcon,
|
|
24
|
-
ClikPlusIcon,
|
|
25
|
-
ClikVolumeIcon,
|
|
26
|
-
ClikWaveIcon,
|
|
27
|
-
StopIcon,
|
|
28
|
-
} from './Icons';
|
|
29
|
-
import { PreviewAttachment } from './PreviewAttachment';
|
|
30
|
-
|
|
31
|
-
import { createAttachments } from '../../services/chat.service';
|
|
32
|
-
import { Button } from './ui/Button';
|
|
33
|
-
import { Textarea } from './ui/Textarea';
|
|
34
|
-
import { useAudioRecording } from '../../hooks/useAudioRecording';
|
|
35
|
-
import { useConfiguration } from '../../hooks/useConfiguration';
|
|
36
|
-
import { SuggestionType } from 'types/common.type';
|
|
37
|
-
import SuggestedActions from './SuggestedActions';
|
|
38
|
-
import { LAYOUT_MODE } from 'commons/constants';
|
|
39
|
-
|
|
40
|
-
const DEFAULT_COLOR_ICON = '#595959';
|
|
41
|
-
const ACTIVE_COLOR_ICON = '#0A82F7';
|
|
42
|
-
|
|
43
|
-
type PropsType = {
|
|
44
|
-
input: string;
|
|
45
|
-
setInput: (value: string) => void;
|
|
46
|
-
isLoading: boolean;
|
|
47
|
-
stop: () => void;
|
|
48
|
-
messages: ChatMessageType[];
|
|
49
|
-
setMessages: (messages: ChatMessageType[]) => void;
|
|
50
|
-
chatId: string;
|
|
51
|
-
handleSubmit: (
|
|
52
|
-
event?: { preventDefault?: () => void },
|
|
53
|
-
files?: IFileUpload[]
|
|
54
|
-
) => void;
|
|
55
|
-
className?: string;
|
|
56
|
-
append?: (message: ChatMessageType) => Promise<void>;
|
|
57
|
-
attachments?: IFileUpload[];
|
|
58
|
-
setAttachments?: (func: (files: IFileUpload[]) => IFileUpload[]) => void;
|
|
59
|
-
bot: BotType | null;
|
|
60
|
-
apiHost: string;
|
|
61
|
-
setEnableTTS: (value: boolean) => void;
|
|
62
|
-
enableTTS: boolean;
|
|
63
|
-
suggestedActions?: SuggestionType[];
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
export const MultimodalInput: FC<PropsType> = ({
|
|
67
|
-
input,
|
|
68
|
-
setInput,
|
|
69
|
-
isLoading,
|
|
70
|
-
stop,
|
|
71
|
-
messages,
|
|
72
|
-
setMessages,
|
|
73
|
-
chatId,
|
|
74
|
-
handleSubmit,
|
|
75
|
-
className,
|
|
76
|
-
append,
|
|
77
|
-
attachments,
|
|
78
|
-
setAttachments,
|
|
79
|
-
bot,
|
|
80
|
-
apiHost,
|
|
81
|
-
setEnableTTS,
|
|
82
|
-
enableTTS,
|
|
83
|
-
suggestedActions,
|
|
84
|
-
}) => {
|
|
85
|
-
const { theme } = useConfiguration();
|
|
86
|
-
const {
|
|
87
|
-
isRecording,
|
|
88
|
-
setIsRecording,
|
|
89
|
-
onRecordingCancelled,
|
|
90
|
-
onRecordingStopped,
|
|
91
|
-
elapsedTime,
|
|
92
|
-
isLoadingRecording,
|
|
93
|
-
} = useAudioRecording();
|
|
94
|
-
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
|
95
|
-
const { width } = useWindowSize();
|
|
96
|
-
|
|
97
|
-
const defaultRows = theme?.suggestion?.defaultRows || 1;
|
|
98
|
-
const expandedRows = theme?.suggestion?.expandedRows || 2;
|
|
99
|
-
const suggestedActionLayoutMode =
|
|
100
|
-
theme?.suggestion?.layoutMode || LAYOUT_MODE.SCROLL;
|
|
101
|
-
|
|
102
|
-
const [suggestedActionRows, setSuggestedActionRows] =
|
|
103
|
-
useState<number>(defaultRows); // only use for scroll mode SuggestedActions
|
|
104
|
-
|
|
105
|
-
useEffect(() => {
|
|
106
|
-
if (textareaRef.current) {
|
|
107
|
-
adjustHeight();
|
|
108
|
-
}
|
|
109
|
-
}, []);
|
|
110
|
-
|
|
111
|
-
const adjustHeight = () => {
|
|
112
|
-
if (textareaRef.current) {
|
|
113
|
-
(textareaRef.current as HTMLTextAreaElement).style.height = 'auto';
|
|
114
|
-
(textareaRef.current as HTMLTextAreaElement).style.height =
|
|
115
|
-
`${textareaRef.current?.scrollHeight + 2}px`;
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const [localStorageInput, setLocalStorageInput] = useLocalStorage(
|
|
120
|
-
'input',
|
|
121
|
-
''
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
useEffect(() => {
|
|
125
|
-
if (textareaRef.current) {
|
|
126
|
-
const domValue = textareaRef.current?.value;
|
|
127
|
-
// Prefer DOM value over localStorage to handle hydration
|
|
128
|
-
const finalValue = domValue || localStorageInput || '';
|
|
129
|
-
setInput(finalValue);
|
|
130
|
-
adjustHeight();
|
|
131
|
-
}
|
|
132
|
-
// Only run once after hydration
|
|
133
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
134
|
-
}, []);
|
|
135
|
-
|
|
136
|
-
useEffect(() => {
|
|
137
|
-
setLocalStorageInput(input);
|
|
138
|
-
}, [input, setLocalStorageInput]);
|
|
139
|
-
|
|
140
|
-
const handleInput = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
|
141
|
-
setInput(event.target.value);
|
|
142
|
-
adjustHeight();
|
|
143
|
-
if (suggestedActionRows !== defaultRows)
|
|
144
|
-
setSuggestedActionRows(defaultRows);
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
|
148
|
-
const [uploadQueue, setUploadQueue] = useState<Array<string>>([]);
|
|
149
|
-
|
|
150
|
-
const submitForm = useCallback(async () => {
|
|
151
|
-
handleSubmit(undefined, attachments);
|
|
152
|
-
setLocalStorageInput('');
|
|
153
|
-
if (setAttachments) {
|
|
154
|
-
setAttachments((_) => []);
|
|
155
|
-
if (fileInputRef.current) {
|
|
156
|
-
(fileInputRef.current as HTMLInputElement).value = '';
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if (width && width > 768) {
|
|
160
|
-
textareaRef.current?.focus();
|
|
161
|
-
}
|
|
162
|
-
}, [handleSubmit, setLocalStorageInput, width, attachments, chatId, bot]);
|
|
163
|
-
|
|
164
|
-
const uploadFile = useCallback(
|
|
165
|
-
async (file: File) => {
|
|
166
|
-
const formData = new FormData();
|
|
167
|
-
formData.append('file', file, generateExtendedFileName(file.name));
|
|
168
|
-
try {
|
|
169
|
-
return await createAttachments({
|
|
170
|
-
chatId,
|
|
171
|
-
apiHost,
|
|
172
|
-
body: formData,
|
|
173
|
-
});
|
|
174
|
-
} catch (error) {
|
|
175
|
-
console.error('Failed to upload file, please try again!');
|
|
176
|
-
}
|
|
177
|
-
},
|
|
178
|
-
[chatId]
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
const toAudioBase64 = (blob: Blob) => {
|
|
182
|
-
return new Promise<IFileUpload>((resolve) => {
|
|
183
|
-
let mimeType = '';
|
|
184
|
-
const pos = blob.type.indexOf(';');
|
|
185
|
-
if (pos === -1) {
|
|
186
|
-
mimeType = blob.type;
|
|
187
|
-
} else {
|
|
188
|
-
mimeType = blob.type.substring(0, pos);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// read blob and add to previews
|
|
192
|
-
const reader = new FileReader();
|
|
193
|
-
reader.readAsDataURL(blob);
|
|
194
|
-
reader.onloadend = () => {
|
|
195
|
-
const base64data = reader.result as string;
|
|
196
|
-
const upload: IFileUpload = {
|
|
197
|
-
tempId: generateUUID(),
|
|
198
|
-
data: base64data,
|
|
199
|
-
type: 'audio',
|
|
200
|
-
name: `audio_${Date.now()}.wav`,
|
|
201
|
-
mime: mimeType,
|
|
202
|
-
};
|
|
203
|
-
resolve(upload);
|
|
204
|
-
};
|
|
205
|
-
});
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
const toBase64 = async (
|
|
209
|
-
file: File,
|
|
210
|
-
type = 'file'
|
|
211
|
-
): Promise<IFileUpload | undefined> => {
|
|
212
|
-
return new Promise((resolve, reject) => {
|
|
213
|
-
const reader = new FileReader();
|
|
214
|
-
reader.readAsDataURL(file);
|
|
215
|
-
reader.onload = () => {
|
|
216
|
-
resolve({
|
|
217
|
-
tempId: generateUUID(),
|
|
218
|
-
data: reader.result as string,
|
|
219
|
-
type,
|
|
220
|
-
name: generateExtendedFileName(file.name),
|
|
221
|
-
mime: file.type,
|
|
222
|
-
});
|
|
223
|
-
};
|
|
224
|
-
reader.onerror = () => {
|
|
225
|
-
reject();
|
|
226
|
-
};
|
|
227
|
-
});
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
const checkUploadFile = useCallback(
|
|
231
|
-
async (file: File): Promise<IFileUpload | undefined> => {
|
|
232
|
-
if (file.type.startsWith('image')) {
|
|
233
|
-
return toBase64(file);
|
|
234
|
-
} else {
|
|
235
|
-
setUploadQueue([file.name]);
|
|
236
|
-
const response: any = await uploadFile(file);
|
|
237
|
-
if (response?.type == 'file:full') {
|
|
238
|
-
const f = response.result[0];
|
|
239
|
-
if (!f) {
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
return {
|
|
243
|
-
tempId: generateUUID(),
|
|
244
|
-
data: f.content,
|
|
245
|
-
name: f.name,
|
|
246
|
-
mime: f.mimeType,
|
|
247
|
-
type: response?.type,
|
|
248
|
-
};
|
|
249
|
-
} else if (response?.type == 'file:rag') {
|
|
250
|
-
const { addedDocs } = response.result;
|
|
251
|
-
if (!addedDocs.length) {
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
return toBase64(file, response?.type);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
},
|
|
258
|
-
[setUploadQueue, uploadFile]
|
|
259
|
-
);
|
|
260
|
-
|
|
261
|
-
const handleLogicShowFAQ = () => {
|
|
262
|
-
setSuggestedActionRows(
|
|
263
|
-
suggestedActionRows === defaultRows ? expandedRows : defaultRows
|
|
264
|
-
);
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
const handleFileChange = useCallback(
|
|
268
|
-
async (event: ChangeEvent<HTMLInputElement>) => {
|
|
269
|
-
const files = Array.from(event.target.files || []);
|
|
270
|
-
try {
|
|
271
|
-
const uploadPromises = files.map((file) => checkUploadFile(file));
|
|
272
|
-
const uploadedAttachments = await Promise.all(uploadPromises);
|
|
273
|
-
const successfullyUploadedAttachments = uploadedAttachments.filter(
|
|
274
|
-
(attachment) => attachment !== undefined
|
|
275
|
-
) as IFileUpload[];
|
|
276
|
-
if (setAttachments) {
|
|
277
|
-
setAttachments((currentAttachments: IFileUpload[]) => [
|
|
278
|
-
...currentAttachments,
|
|
279
|
-
...successfullyUploadedAttachments,
|
|
280
|
-
]);
|
|
281
|
-
}
|
|
282
|
-
} catch (error) {
|
|
283
|
-
console.error('Error uploading files!', error);
|
|
284
|
-
} finally {
|
|
285
|
-
setUploadQueue([]);
|
|
286
|
-
}
|
|
287
|
-
},
|
|
288
|
-
[setAttachments, checkUploadFile]
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
const handleSubmitRecording = useCallback(
|
|
292
|
-
async (blob: Blob) => {
|
|
293
|
-
try {
|
|
294
|
-
const audioFile = await toAudioBase64(blob);
|
|
295
|
-
handleSubmit(undefined, [audioFile]);
|
|
296
|
-
setIsRecording(false);
|
|
297
|
-
} catch (error) {
|
|
298
|
-
console.error('Error uploading files!', error);
|
|
299
|
-
}
|
|
300
|
-
},
|
|
301
|
-
[handleSubmit, setIsRecording]
|
|
302
|
-
);
|
|
303
|
-
|
|
304
|
-
const handleSend = useCallback(async () => {
|
|
305
|
-
if (isRecording) {
|
|
306
|
-
onRecordingStopped(handleSubmitRecording);
|
|
307
|
-
} else {
|
|
308
|
-
submitForm();
|
|
309
|
-
}
|
|
310
|
-
}, [submitForm, onRecordingStopped, handleSubmitRecording]);
|
|
311
|
-
|
|
312
|
-
return (
|
|
313
|
-
<div className="relative w-full flex flex-col gap-4">
|
|
314
|
-
{!!suggestedActions?.length && (
|
|
315
|
-
<SuggestedActions
|
|
316
|
-
suggestedActions={suggestedActions}
|
|
317
|
-
append={append}
|
|
318
|
-
layoutMode={suggestedActionLayoutMode}
|
|
319
|
-
suggestedActionRows={suggestedActionRows}
|
|
320
|
-
/>
|
|
321
|
-
)}
|
|
322
|
-
|
|
323
|
-
<input
|
|
324
|
-
type="file"
|
|
325
|
-
className="fixed -top-4 -left-4 size-0.5 opacity-0 pointer-events-none"
|
|
326
|
-
ref={fileInputRef}
|
|
327
|
-
multiple
|
|
328
|
-
onChange={handleFileChange}
|
|
329
|
-
tabIndex={-1}
|
|
330
|
-
/>
|
|
331
|
-
|
|
332
|
-
{((attachments && attachments.length > 0) || uploadQueue.length > 0) && (
|
|
333
|
-
<div className="flex flex-row gap-2 overflow-x-scroll items-end">
|
|
334
|
-
{attachments?.map((attachment) => (
|
|
335
|
-
<PreviewAttachment
|
|
336
|
-
key={attachment.tempId}
|
|
337
|
-
attachment={attachment}
|
|
338
|
-
/>
|
|
339
|
-
))}
|
|
340
|
-
|
|
341
|
-
{uploadQueue.map((filename) => (
|
|
342
|
-
<PreviewAttachment
|
|
343
|
-
key={filename}
|
|
344
|
-
attachment={{
|
|
345
|
-
data: '',
|
|
346
|
-
name: filename,
|
|
347
|
-
mime: '',
|
|
348
|
-
type: '',
|
|
349
|
-
}}
|
|
350
|
-
isUploading
|
|
351
|
-
/>
|
|
352
|
-
))}
|
|
353
|
-
</div>
|
|
354
|
-
)}
|
|
355
|
-
<Textarea
|
|
356
|
-
ref={textareaRef}
|
|
357
|
-
placeholder={theme?.input?.placeholder || 'Send a message...'}
|
|
358
|
-
value={input}
|
|
359
|
-
onChange={handleInput}
|
|
360
|
-
className={cn(
|
|
361
|
-
'min-h-[24px] max-h-[calc(75dvh)] overflow-hidden resize-none rounded-xl text-base bg-muted bg-[#ffffff]',
|
|
362
|
-
className
|
|
363
|
-
)}
|
|
364
|
-
rows={3}
|
|
365
|
-
autoFocus
|
|
366
|
-
onKeyDown={(event) => {
|
|
367
|
-
if (event.key === 'Enter' && !event.shiftKey) {
|
|
368
|
-
event.preventDefault();
|
|
369
|
-
if (isLoading) {
|
|
370
|
-
console.error(
|
|
371
|
-
'Please wait for the model to finish its response!'
|
|
372
|
-
);
|
|
373
|
-
} else if (uploadQueue.length) {
|
|
374
|
-
console.error('Please wait for file is uploading!');
|
|
375
|
-
} else {
|
|
376
|
-
handleSend();
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}}
|
|
380
|
-
/>
|
|
381
|
-
|
|
382
|
-
{isLoading ? (
|
|
383
|
-
<Button
|
|
384
|
-
className="rounded-full p-1.5 h-fit absolute bottom-2 right-2 m-0.5 border dark:border-zinc-600 bg-[#000000D9]"
|
|
385
|
-
onClick={(event) => {
|
|
386
|
-
event.preventDefault();
|
|
387
|
-
stop();
|
|
388
|
-
setMessages(messages);
|
|
389
|
-
}}
|
|
390
|
-
>
|
|
391
|
-
<StopIcon size={14} />
|
|
392
|
-
</Button>
|
|
393
|
-
) : (
|
|
394
|
-
<Button
|
|
395
|
-
className="rounded-full p-1.5 h-fit absolute bottom-2 right-2 m-0.5 border dark:border-zinc-600 bg-[#000000D9]"
|
|
396
|
-
onClick={(event) => {
|
|
397
|
-
event.preventDefault();
|
|
398
|
-
handleSend();
|
|
399
|
-
}}
|
|
400
|
-
disabled={
|
|
401
|
-
!isRecording && (input.length === 0 || !!uploadQueue.length)
|
|
402
|
-
} // zero input or uploading
|
|
403
|
-
>
|
|
404
|
-
<ArrowUpIcon size={14} />
|
|
405
|
-
</Button>
|
|
406
|
-
)}
|
|
407
|
-
|
|
408
|
-
<div className="absolute left-2 right-2 bottom-2 flex items-center">
|
|
409
|
-
<Button
|
|
410
|
-
className="rounded-full p-1.5 h-fit m-0.5 dark:border-zinc-700"
|
|
411
|
-
onClick={(event) => {
|
|
412
|
-
event.preventDefault();
|
|
413
|
-
fileInputRef.current?.click();
|
|
414
|
-
}}
|
|
415
|
-
variant="outline"
|
|
416
|
-
disabled={isLoading || isRecording}
|
|
417
|
-
>
|
|
418
|
-
<ClikPlusIcon size={14} />
|
|
419
|
-
</Button>
|
|
420
|
-
|
|
421
|
-
<Button
|
|
422
|
-
className={`rounded-full py-1 px-2 h-fit m-0.5 dark:border-zinc-700 text-[#595959] ${suggestedActionRows === expandedRows && 'border border-[#B9C6D6] bg-[#CBE1F3] text-[#0A82F7]'} `}
|
|
423
|
-
onClick={(event) => {
|
|
424
|
-
event.preventDefault();
|
|
425
|
-
handleLogicShowFAQ();
|
|
426
|
-
}}
|
|
427
|
-
variant="outline"
|
|
428
|
-
disabled={isLoading || isRecording}
|
|
429
|
-
>
|
|
430
|
-
<ClikMessageIcon
|
|
431
|
-
color={
|
|
432
|
-
suggestedActionRows === expandedRows
|
|
433
|
-
? ACTIVE_COLOR_ICON
|
|
434
|
-
: DEFAULT_COLOR_ICON
|
|
435
|
-
}
|
|
436
|
-
/>
|
|
437
|
-
FAQ
|
|
438
|
-
</Button>
|
|
439
|
-
|
|
440
|
-
{isRecording ? (
|
|
441
|
-
<>
|
|
442
|
-
<div
|
|
443
|
-
className="rounded-[100px] flex items-center bg-[#F3F3F3] gap-2 p-2"
|
|
444
|
-
data-testid="input"
|
|
445
|
-
>
|
|
446
|
-
<Button
|
|
447
|
-
className="rounded-full dark:border-zinc-700 p-0 h-6"
|
|
448
|
-
variant="outline"
|
|
449
|
-
onClick={(event) => {
|
|
450
|
-
event.preventDefault();
|
|
451
|
-
onRecordingCancelled();
|
|
452
|
-
}}
|
|
453
|
-
>
|
|
454
|
-
<ClikCloseIcon className="!w-full !h-full" />
|
|
455
|
-
</Button>
|
|
456
|
-
<div className="flex items-center gap-2 ">
|
|
457
|
-
<span>
|
|
458
|
-
<ClikWaveIcon />
|
|
459
|
-
</span>
|
|
460
|
-
<span>{elapsedTime || '00:00'}</span>
|
|
461
|
-
{isLoadingRecording && (
|
|
462
|
-
<span className="ml-1.5">Sending...</span>
|
|
463
|
-
)}
|
|
464
|
-
</div>
|
|
465
|
-
<Button
|
|
466
|
-
className="rounded-full dark:border-zinc-700 p-0 h-6"
|
|
467
|
-
variant="outline"
|
|
468
|
-
onClick={(event) => {
|
|
469
|
-
event.preventDefault();
|
|
470
|
-
}}
|
|
471
|
-
>
|
|
472
|
-
<CheckCirclFillIcon className="!w-full !h-full" />
|
|
473
|
-
</Button>
|
|
474
|
-
</div>
|
|
475
|
-
</>
|
|
476
|
-
) : (
|
|
477
|
-
<div>
|
|
478
|
-
<Button
|
|
479
|
-
className={`rounded-full py-1 px-2 h-fit m-0.5 dark:border-zinc-700 text-[#595959] ${enableTTS ? 'text-white hover:bg-primary/90 bg-primary' : ''}`}
|
|
480
|
-
onClick={(event) => {
|
|
481
|
-
event.preventDefault();
|
|
482
|
-
setEnableTTS(!enableTTS);
|
|
483
|
-
}}
|
|
484
|
-
variant="outline"
|
|
485
|
-
disabled={isLoading}
|
|
486
|
-
>
|
|
487
|
-
<ClikVolumeIcon /> Speak
|
|
488
|
-
</Button>
|
|
489
|
-
<Button
|
|
490
|
-
className="rounded-full py-1 px-2 gap-[4px] h-fit m-0.5 dark:border-zinc-700 text-[#595959]"
|
|
491
|
-
onClick={(event) => {
|
|
492
|
-
event.preventDefault();
|
|
493
|
-
setIsRecording(true);
|
|
494
|
-
}}
|
|
495
|
-
variant="outline"
|
|
496
|
-
disabled={isLoading}
|
|
497
|
-
>
|
|
498
|
-
<ClikMicrophoneIcon size={14} /> Talk
|
|
499
|
-
</Button>
|
|
500
|
-
</div>
|
|
501
|
-
)}
|
|
502
|
-
</div>
|
|
503
|
-
</div>
|
|
504
|
-
);
|
|
505
|
-
};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { motion } from 'framer-motion';
|
|
2
|
-
import { FC } from 'react';
|
|
3
|
-
import { BotType } from '../../types/bot.type';
|
|
4
|
-
import { useConfiguration } from '../../hooks/useConfiguration';
|
|
5
|
-
|
|
6
|
-
type PropsType = {
|
|
7
|
-
bot: BotType | null;
|
|
8
|
-
};
|
|
9
|
-
export const Overview: FC<PropsType> = ({ bot }: PropsType) => {
|
|
10
|
-
const { theme } = useConfiguration();
|
|
11
|
-
return (
|
|
12
|
-
<motion.div
|
|
13
|
-
key="overview"
|
|
14
|
-
className="max-w-3xl m-auto md:mt-20"
|
|
15
|
-
initial={{ opacity: 0, scale: 0.98 }}
|
|
16
|
-
animate={{ opacity: 1, scale: 1 }}
|
|
17
|
-
exit={{ opacity: 0, scale: 0.98 }}
|
|
18
|
-
transition={{ delay: 0.5 }}
|
|
19
|
-
>
|
|
20
|
-
<div className="rounded-xl p-6 flex flex-col gap-2 leading-relaxed text-center max-w-xl">
|
|
21
|
-
<p className="flex flex-row justify-center gap-4 items-center"></p>
|
|
22
|
-
{!theme?.overview ? (
|
|
23
|
-
<>
|
|
24
|
-
{bot?.avatar && (
|
|
25
|
-
<img
|
|
26
|
-
src={bot?.avatar}
|
|
27
|
-
alt={bot?.name ?? 'Avatar'}
|
|
28
|
-
width={40}
|
|
29
|
-
height={40}
|
|
30
|
-
className="rounded-full m-auto"
|
|
31
|
-
/>
|
|
32
|
-
)}
|
|
33
|
-
<p className="font-semibold text-xl">{bot?.name}</p>
|
|
34
|
-
</>
|
|
35
|
-
) : (
|
|
36
|
-
<>
|
|
37
|
-
<p className="font-semibold text-xl">{theme?.overview?.title}</p>
|
|
38
|
-
<p>{theme?.overview?.description}</p>
|
|
39
|
-
</>
|
|
40
|
-
)}
|
|
41
|
-
</div>
|
|
42
|
-
</motion.div>
|
|
43
|
-
);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export default Overview;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { IFileUpload } from '../../types/flowise.type';
|
|
2
|
-
import { LoaderIcon } from './Icons';
|
|
3
|
-
import React from 'react';
|
|
4
|
-
|
|
5
|
-
export const PreviewAttachment = ({
|
|
6
|
-
attachment,
|
|
7
|
-
isUploading = false,
|
|
8
|
-
}: {
|
|
9
|
-
attachment: IFileUpload;
|
|
10
|
-
isUploading?: boolean;
|
|
11
|
-
}) => {
|
|
12
|
-
const { name, data, mime, tempId } = attachment;
|
|
13
|
-
return (
|
|
14
|
-
<div className="flex flex-col gap-2">
|
|
15
|
-
<div className="w-30 p-0 max-w-[400px] bg-muted rounded-md relative flex flex-col items-center justify-center">
|
|
16
|
-
{data ? (
|
|
17
|
-
mime.startsWith('audio') ? (
|
|
18
|
-
<audio controls>
|
|
19
|
-
<source src={data} type={mime} />
|
|
20
|
-
</audio>
|
|
21
|
-
) : mime.startsWith('image') ? (
|
|
22
|
-
<img
|
|
23
|
-
key={tempId}
|
|
24
|
-
src={data}
|
|
25
|
-
alt={name ?? 'An image attachment'}
|
|
26
|
-
className="rounded-md size-full object-contain"
|
|
27
|
-
/>
|
|
28
|
-
) : (
|
|
29
|
-
<div className="w-full text-left truncate">{name}</div>
|
|
30
|
-
)
|
|
31
|
-
) : (
|
|
32
|
-
<div className="w-full text-left truncate">{name}</div>
|
|
33
|
-
)}
|
|
34
|
-
|
|
35
|
-
{isUploading && (
|
|
36
|
-
<div className="animate-spin absolute text-zinc-500">
|
|
37
|
-
<LoaderIcon />
|
|
38
|
-
</div>
|
|
39
|
-
)}
|
|
40
|
-
</div>
|
|
41
|
-
{/*<div className="text-xs text-zinc-500 max-w-[200px] truncate ">*/}
|
|
42
|
-
{/* {name}*/}
|
|
43
|
-
{/*</div>*/}
|
|
44
|
-
</div>
|
|
45
|
-
);
|
|
46
|
-
};
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { motion } from 'framer-motion';
|
|
3
|
-
import { SuggestionType } from 'types/common.type';
|
|
4
|
-
import { ChatMessageType } from 'types/flowise.type';
|
|
5
|
-
import { LAYOUT_MODE } from 'commons/constants';
|
|
6
|
-
|
|
7
|
-
interface SuggestedActionsProps {
|
|
8
|
-
suggestedActions?: SuggestionType[];
|
|
9
|
-
append?: (message: ChatMessageType) => Promise<void>;
|
|
10
|
-
layoutMode?: string;
|
|
11
|
-
suggestedActionRows?: number;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const SuggestedActions: React.FC<SuggestedActionsProps> = ({
|
|
15
|
-
suggestedActions,
|
|
16
|
-
append,
|
|
17
|
-
layoutMode = LAYOUT_MODE.SCROLL,
|
|
18
|
-
suggestedActionRows = 1,
|
|
19
|
-
}) => {
|
|
20
|
-
if (!suggestedActions?.length) return null;
|
|
21
|
-
|
|
22
|
-
const containerHeight = suggestedActionRows * 60;
|
|
23
|
-
|
|
24
|
-
const animation = {
|
|
25
|
-
initial: { opacity: 0, y: 20 },
|
|
26
|
-
animate: { opacity: 1, y: 0 },
|
|
27
|
-
exit: { opacity: 0, y: 20 },
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
return layoutMode === LAYOUT_MODE.GRID ? (
|
|
31
|
-
// UI Grid (2 columns)
|
|
32
|
-
<div className="grid sm:grid-cols-2 gap-2 w-full">
|
|
33
|
-
{suggestedActions.map((suggestedAction, index) => (
|
|
34
|
-
<motion.div
|
|
35
|
-
key={`suggested-action-${suggestedAction.title}-${index}`}
|
|
36
|
-
{...animation}
|
|
37
|
-
transition={{ delay: 0.05 * index }}
|
|
38
|
-
className={index > 1 ? 'hidden sm:block' : 'block'}
|
|
39
|
-
>
|
|
40
|
-
<ActionButton suggestedAction={suggestedAction} append={append} />
|
|
41
|
-
</motion.div>
|
|
42
|
-
))}
|
|
43
|
-
</div>
|
|
44
|
-
) : (
|
|
45
|
-
// UI Scroll
|
|
46
|
-
<div
|
|
47
|
-
className="w-full overflow-x-auto scrollbar-hide"
|
|
48
|
-
style={{ maxHeight: `${containerHeight}px` }}
|
|
49
|
-
>
|
|
50
|
-
<div
|
|
51
|
-
style={{
|
|
52
|
-
gap: '8px',
|
|
53
|
-
display: 'grid',
|
|
54
|
-
gridAutoFlow: 'column',
|
|
55
|
-
gridTemplateRows: `repeat(${suggestedActionRows}, minmax(0, 1fr))`,
|
|
56
|
-
}}
|
|
57
|
-
>
|
|
58
|
-
{suggestedActions.map((suggestedAction, index) => (
|
|
59
|
-
<motion.div
|
|
60
|
-
key={`suggested-action-${suggestedAction.title}-${index}`}
|
|
61
|
-
{...animation}
|
|
62
|
-
transition={{ delay: 0.05 * index }}
|
|
63
|
-
>
|
|
64
|
-
<ActionButton suggestedAction={suggestedAction} append={append} />
|
|
65
|
-
</motion.div>
|
|
66
|
-
))}
|
|
67
|
-
</div>
|
|
68
|
-
</div>
|
|
69
|
-
);
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
interface ActionButtonProps {
|
|
73
|
-
suggestedAction: SuggestionType;
|
|
74
|
-
append?: (message: ChatMessageType) => Promise<void>;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const ActionButton: React.FC<ActionButtonProps> = ({
|
|
78
|
-
suggestedAction,
|
|
79
|
-
append,
|
|
80
|
-
}) => (
|
|
81
|
-
<button
|
|
82
|
-
onClick={(e) => {
|
|
83
|
-
e.preventDefault();
|
|
84
|
-
if (append) {
|
|
85
|
-
append({ role: 'apiMessage', content: suggestedAction.action });
|
|
86
|
-
}
|
|
87
|
-
}}
|
|
88
|
-
className="text-left border rounded-xl px-4 py-3.5 text-sm flex-1 gap-1 sm:flex-col w-full h-auto justify-start items-start"
|
|
89
|
-
>
|
|
90
|
-
<span className="font-medium overflow-hidden whitespace-nowrap text-ellipsis w-full group-hover:overflow-visible group-hover:whitespace-normal">
|
|
91
|
-
{suggestedAction.title}
|
|
92
|
-
</span>
|
|
93
|
-
{!!suggestedAction.label && (
|
|
94
|
-
<span className="text-muted-foreground">{suggestedAction.label}</span>
|
|
95
|
-
)}
|
|
96
|
-
</button>
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
export default SuggestedActions;
|