@clikvn/agent-widget-embedded 0.0.9-dev → 0.0.11-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/commons/constants/variables.d.ts +4 -0
- package/dist/commons/constants/variables.d.ts.map +1 -1
- package/dist/components/Chat/Chat.d.ts.map +1 -1
- package/dist/components/Chat/Icons.d.ts +19 -1
- package/dist/components/Chat/Icons.d.ts.map +1 -1
- package/dist/components/Chat/Message.d.ts.map +1 -1
- package/dist/components/Chat/MultimodalInput.d.ts +3 -1
- package/dist/components/Chat/MultimodalInput.d.ts.map +1 -1
- package/dist/components/Chat/SuggestedActions.d.ts +12 -0
- package/dist/components/Chat/SuggestedActions.d.ts.map +1 -0
- package/dist/features/AgentWidget/index.d.ts +8 -2
- package/dist/features/AgentWidget/index.d.ts.map +1 -1
- package/dist/hooks/useChat.d.ts +2 -0
- package/dist/hooks/useChat.d.ts.map +1 -1
- package/dist/hooks/useConfiguration.d.ts +8 -1
- package/dist/hooks/useConfiguration.d.ts.map +1 -1
- package/dist/index.html +12 -51
- package/dist/services/apis.d.ts +1 -0
- package/dist/services/apis.d.ts.map +1 -1
- package/dist/services/chat.service.d.ts +7 -1
- package/dist/services/chat.service.d.ts.map +1 -1
- package/dist/types/common.type.d.ts +6 -0
- package/dist/types/common.type.d.ts.map +1 -1
- package/dist/web.js +1 -1
- package/package.json +2 -1
- package/src/commons/constants/variables.ts +5 -0
- package/src/components/Chat/Chat.tsx +2 -0
- package/src/components/Chat/Icons.tsx +867 -1
- package/src/components/Chat/Message.tsx +42 -16
- package/src/components/Chat/MultimodalInput.tsx +140 -107
- package/src/components/Chat/SuggestedActions.tsx +99 -0
- package/src/features/AgentWidget/index.tsx +8 -2
- package/src/hooks/useChat.ts +19 -2
- package/src/hooks/useConfiguration.tsx +8 -1
- package/src/services/apis.ts +2 -0
- package/src/services/chat.service.ts +36 -1
- package/src/types/common.type.ts +7 -0
- package/dist/components/Chat/AudioRecording.d.ts +0 -9
- package/dist/components/Chat/AudioRecording.d.ts.map +0 -1
- package/dist/hooks/useConnection.d.ts +0 -15
- package/dist/hooks/useConnection.d.ts.map +0 -1
- package/dist/services/user.service.d.ts +0 -3
- package/dist/services/user.service.d.ts.map +0 -1
- package/dist/types/agentType.d.ts +0 -11
- package/dist/types/agentType.d.ts.map +0 -1
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from '../../types/flowise.type';
|
|
12
12
|
import { cn } from '../../utils/commonUtils';
|
|
13
13
|
import AudioPlayer from './AudioPlayer';
|
|
14
|
+
import { useConfiguration } from 'hooks/useConfiguration';
|
|
14
15
|
|
|
15
16
|
type PropsType = {
|
|
16
17
|
chatId?: string;
|
|
@@ -35,6 +36,7 @@ export const PreviewMessage: FC<PropsType> = ({
|
|
|
35
36
|
bot,
|
|
36
37
|
enableTTS,
|
|
37
38
|
}) => {
|
|
39
|
+
const { theme } = useConfiguration();
|
|
38
40
|
const parseOutput = (outputData: any) => {
|
|
39
41
|
try {
|
|
40
42
|
return JSON.parse(outputData);
|
|
@@ -56,12 +58,21 @@ export const PreviewMessage: FC<PropsType> = ({
|
|
|
56
58
|
return { customToolResults, toolResults };
|
|
57
59
|
}, [message.usedTools]);
|
|
58
60
|
|
|
61
|
+
const role = useMemo(() => {
|
|
62
|
+
return getRole(message.role);
|
|
63
|
+
}, [message.role]);
|
|
64
|
+
|
|
65
|
+
const assistantAvatar =
|
|
66
|
+
role === 'user'
|
|
67
|
+
? theme?.userMessage?.avatarSrc
|
|
68
|
+
: theme?.botMessage?.avatarSrc || bot?.avatar;
|
|
69
|
+
|
|
59
70
|
return (
|
|
60
71
|
<motion.div
|
|
61
72
|
className="w-full mx-auto max-w-3xl px-4 group/message"
|
|
62
73
|
initial={{ y: 5, opacity: 0 }}
|
|
63
74
|
animate={{ y: 0, opacity: 1 }}
|
|
64
|
-
data-role={
|
|
75
|
+
data-role={role}
|
|
65
76
|
>
|
|
66
77
|
<div className="flex justify-end text-[14px]">
|
|
67
78
|
{message?.fileUploads && !!message.fileUploads.length && (
|
|
@@ -70,15 +81,21 @@ export const PreviewMessage: FC<PropsType> = ({
|
|
|
70
81
|
</div>
|
|
71
82
|
<div
|
|
72
83
|
className={cn(
|
|
73
|
-
|
|
84
|
+
`group-data-[role=user]/message:bg-primary group-data-[role=user]/message:text-primary-foreground flex gap-4 px-3 w-full group-data-[role=user]/message:w-fit group-data-[role=user]/message:ml-auto group-data-[role=user]/message:max-w-2xl py-2 rounded-xl `
|
|
74
85
|
)}
|
|
86
|
+
style={{
|
|
87
|
+
backgroundColor:
|
|
88
|
+
role === 'user'
|
|
89
|
+
? theme?.userMessage?.backgroundColor
|
|
90
|
+
: theme?.botMessage?.backgroundColor || undefined,
|
|
91
|
+
}}
|
|
75
92
|
>
|
|
76
|
-
{message.role === 'apiMessage' && (
|
|
93
|
+
{theme?.botMessage?.showAvatar && message.role === 'apiMessage' && (
|
|
77
94
|
<div className="size-8 flex items-center rounded-full justify-center ring-1 shrink-0 ring-border">
|
|
78
|
-
{
|
|
95
|
+
{assistantAvatar ? (
|
|
79
96
|
<img
|
|
80
|
-
src={
|
|
81
|
-
alt={bot
|
|
97
|
+
src={assistantAvatar}
|
|
98
|
+
alt={bot?.name ?? 'User Avatar'}
|
|
82
99
|
width={24}
|
|
83
100
|
height={24}
|
|
84
101
|
className="rounded-full"
|
|
@@ -133,7 +150,15 @@ export const PreviewMessage: FC<PropsType> = ({
|
|
|
133
150
|
{/* </div>*/}
|
|
134
151
|
{/* )}*/}
|
|
135
152
|
{message.content && (
|
|
136
|
-
<div
|
|
153
|
+
<div
|
|
154
|
+
className="flex flex-col gap-4"
|
|
155
|
+
style={{
|
|
156
|
+
color:
|
|
157
|
+
getRole(message.role) === 'user'
|
|
158
|
+
? theme?.userMessage?.textColor
|
|
159
|
+
: theme?.botMessage?.textColor || undefined,
|
|
160
|
+
}}
|
|
161
|
+
>
|
|
137
162
|
<Markdown usedTools={message?.usedTools}>
|
|
138
163
|
{message.content as string}
|
|
139
164
|
</Markdown>
|
|
@@ -151,6 +176,10 @@ export const PreviewMessage: FC<PropsType> = ({
|
|
|
151
176
|
export const ThinkingMessage = ({ bot }: { bot: BotType | null }) => {
|
|
152
177
|
const role = 'assistant';
|
|
153
178
|
|
|
179
|
+
const { theme } = useConfiguration();
|
|
180
|
+
|
|
181
|
+
const assistantAvatar = theme?.botMessage?.avatarSrc || bot?.avatar;
|
|
182
|
+
|
|
154
183
|
return (
|
|
155
184
|
<motion.div
|
|
156
185
|
className="w-full mx-auto max-w-3xl px-4 group/message "
|
|
@@ -166,20 +195,17 @@ export const ThinkingMessage = ({ bot }: { bot: BotType | null }) => {
|
|
|
166
195
|
}
|
|
167
196
|
)}
|
|
168
197
|
>
|
|
169
|
-
|
|
170
|
-
|
|
198
|
+
{theme?.botMessage?.showAvatar && (
|
|
199
|
+
<div className="size-8 flex items-center rounded-full justify-center ring-1 shrink-0 ring-border">
|
|
171
200
|
<img
|
|
172
|
-
src={
|
|
173
|
-
alt={bot
|
|
201
|
+
src={assistantAvatar}
|
|
202
|
+
alt={bot?.name ?? 'User Avatar'}
|
|
174
203
|
width={24}
|
|
175
204
|
height={24}
|
|
176
205
|
className="rounded-full"
|
|
177
206
|
/>
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
)}
|
|
181
|
-
</div>
|
|
182
|
-
|
|
207
|
+
</div>
|
|
208
|
+
)}
|
|
183
209
|
<div className="flex flex-col gap-2 w-full">
|
|
184
210
|
<div className="flex flex-col gap-4 text-muted-foreground">
|
|
185
211
|
Thinking...
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { motion } from 'framer-motion';
|
|
1
2
|
import {
|
|
2
3
|
type ChangeEvent,
|
|
3
4
|
FC,
|
|
@@ -6,32 +7,38 @@ import {
|
|
|
6
7
|
useRef,
|
|
7
8
|
useState,
|
|
8
9
|
} from 'react';
|
|
9
|
-
import { motion } from 'framer-motion';
|
|
10
10
|
import { useLocalStorage, useWindowSize } from 'usehooks-ts';
|
|
11
|
+
import { BotType } from '../../types/bot.type';
|
|
12
|
+
import { ChatMessageType, IFileUpload } from '../../types/flowise.type';
|
|
11
13
|
import {
|
|
12
14
|
cn,
|
|
13
15
|
generateExtendedFileName,
|
|
14
16
|
generateUUID,
|
|
15
17
|
} from '../../utils/commonUtils';
|
|
16
|
-
import { PreviewAttachment } from './PreviewAttachment';
|
|
17
18
|
import {
|
|
18
19
|
ArrowUpIcon,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
CheckCirclFillIcon,
|
|
21
|
+
ClikCloseIcon,
|
|
22
|
+
ClikMessageIcon,
|
|
23
|
+
ClikMicrophoneIcon,
|
|
24
|
+
ClikPlusIcon,
|
|
25
|
+
ClikVolumeIcon,
|
|
26
|
+
ClikWaveIcon,
|
|
22
27
|
StopIcon,
|
|
23
|
-
TrashIcon,
|
|
24
|
-
VolumeIcon,
|
|
25
28
|
} from './Icons';
|
|
26
|
-
import {
|
|
27
|
-
import { BotType } from '../../types/bot.type';
|
|
29
|
+
import { PreviewAttachment } from './PreviewAttachment';
|
|
28
30
|
|
|
29
31
|
import { createAttachments } from '../../services/chat.service';
|
|
30
32
|
import { Button } from './ui/Button';
|
|
31
33
|
import { Textarea } from './ui/Textarea';
|
|
32
34
|
import { useAudioRecording } from '../../hooks/useAudioRecording';
|
|
33
|
-
import { useChatData } from '../../hooks/useChatData';
|
|
34
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';
|
|
35
42
|
|
|
36
43
|
type PropsType = {
|
|
37
44
|
input: string;
|
|
@@ -53,6 +60,7 @@ type PropsType = {
|
|
|
53
60
|
apiHost: string;
|
|
54
61
|
setEnableTTS: (value: boolean) => void;
|
|
55
62
|
enableTTS: boolean;
|
|
63
|
+
suggestedActions?: SuggestionType[];
|
|
56
64
|
};
|
|
57
65
|
|
|
58
66
|
export const MultimodalInput: FC<PropsType> = ({
|
|
@@ -72,9 +80,9 @@ export const MultimodalInput: FC<PropsType> = ({
|
|
|
72
80
|
apiHost,
|
|
73
81
|
setEnableTTS,
|
|
74
82
|
enableTTS,
|
|
83
|
+
suggestedActions,
|
|
75
84
|
}) => {
|
|
76
85
|
const { theme } = useConfiguration();
|
|
77
|
-
const { suggestedActions = [] } = useChatData();
|
|
78
86
|
const {
|
|
79
87
|
isRecording,
|
|
80
88
|
setIsRecording,
|
|
@@ -85,6 +93,15 @@ export const MultimodalInput: FC<PropsType> = ({
|
|
|
85
93
|
} = useAudioRecording();
|
|
86
94
|
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
|
87
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
|
+
|
|
88
105
|
useEffect(() => {
|
|
89
106
|
if (textareaRef.current) {
|
|
90
107
|
adjustHeight();
|
|
@@ -123,6 +140,8 @@ export const MultimodalInput: FC<PropsType> = ({
|
|
|
123
140
|
const handleInput = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
|
124
141
|
setInput(event.target.value);
|
|
125
142
|
adjustHeight();
|
|
143
|
+
if (suggestedActionRows !== defaultRows)
|
|
144
|
+
setSuggestedActionRows(defaultRows);
|
|
126
145
|
};
|
|
127
146
|
|
|
128
147
|
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
|
@@ -239,6 +258,12 @@ export const MultimodalInput: FC<PropsType> = ({
|
|
|
239
258
|
[setUploadQueue, uploadFile]
|
|
240
259
|
);
|
|
241
260
|
|
|
261
|
+
const handleLogicShowFAQ = () => {
|
|
262
|
+
setSuggestedActionRows(
|
|
263
|
+
suggestedActionRows === defaultRows ? expandedRows : defaultRows
|
|
264
|
+
);
|
|
265
|
+
};
|
|
266
|
+
|
|
242
267
|
const handleFileChange = useCallback(
|
|
243
268
|
async (event: ChangeEvent<HTMLInputElement>) => {
|
|
244
269
|
const files = Array.from(event.target.files || []);
|
|
@@ -286,37 +311,13 @@ export const MultimodalInput: FC<PropsType> = ({
|
|
|
286
311
|
|
|
287
312
|
return (
|
|
288
313
|
<div className="relative w-full flex flex-col gap-4">
|
|
289
|
-
{
|
|
290
|
-
<
|
|
291
|
-
{suggestedActions
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
transition={{ delay: 0.05 * index }}
|
|
297
|
-
key={`suggested-action-${suggestedAction.title}-${index}`}
|
|
298
|
-
className={index > 1 ? 'hidden sm:block' : 'block'}
|
|
299
|
-
>
|
|
300
|
-
<Button
|
|
301
|
-
variant="ghost"
|
|
302
|
-
onClick={async () => {
|
|
303
|
-
if (append) {
|
|
304
|
-
append({
|
|
305
|
-
role: 'apiMessage',
|
|
306
|
-
content: suggestedAction.action,
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
}}
|
|
310
|
-
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"
|
|
311
|
-
>
|
|
312
|
-
<span className="font-medium">{suggestedAction.title}</span>
|
|
313
|
-
<span className="text-muted-foreground">
|
|
314
|
-
{suggestedAction.label}
|
|
315
|
-
</span>
|
|
316
|
-
</Button>
|
|
317
|
-
</motion.div>
|
|
318
|
-
))}
|
|
319
|
-
</div>
|
|
314
|
+
{!!suggestedActions?.length && (
|
|
315
|
+
<SuggestedActions
|
|
316
|
+
suggestedActions={suggestedActions}
|
|
317
|
+
append={append}
|
|
318
|
+
layoutMode={suggestedActionLayoutMode}
|
|
319
|
+
suggestedActionRows={suggestedActionRows}
|
|
320
|
+
/>
|
|
320
321
|
)}
|
|
321
322
|
|
|
322
323
|
<input
|
|
@@ -351,14 +352,13 @@ export const MultimodalInput: FC<PropsType> = ({
|
|
|
351
352
|
))}
|
|
352
353
|
</div>
|
|
353
354
|
)}
|
|
354
|
-
|
|
355
355
|
<Textarea
|
|
356
356
|
ref={textareaRef}
|
|
357
357
|
placeholder={theme?.input?.placeholder || 'Send a message...'}
|
|
358
358
|
value={input}
|
|
359
359
|
onChange={handleInput}
|
|
360
360
|
className={cn(
|
|
361
|
-
'min-h-[24px] max-h-[calc(75dvh)] overflow-hidden resize-none rounded-xl text-base bg-muted',
|
|
361
|
+
'min-h-[24px] max-h-[calc(75dvh)] overflow-hidden resize-none rounded-xl text-base bg-muted bg-[#ffffff]',
|
|
362
362
|
className
|
|
363
363
|
)}
|
|
364
364
|
rows={3}
|
|
@@ -381,7 +381,7 @@ export const MultimodalInput: FC<PropsType> = ({
|
|
|
381
381
|
|
|
382
382
|
{isLoading ? (
|
|
383
383
|
<Button
|
|
384
|
-
className="rounded-full p-1.5 h-fit absolute bottom-2 right-2 m-0.5 border dark:border-zinc-600"
|
|
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
385
|
onClick={(event) => {
|
|
386
386
|
event.preventDefault();
|
|
387
387
|
stop();
|
|
@@ -392,7 +392,7 @@ export const MultimodalInput: FC<PropsType> = ({
|
|
|
392
392
|
</Button>
|
|
393
393
|
) : (
|
|
394
394
|
<Button
|
|
395
|
-
className="rounded-full p-1.5 h-fit absolute bottom-2 right-2 m-0.5 border dark:border-zinc-600"
|
|
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
396
|
onClick={(event) => {
|
|
397
397
|
event.preventDefault();
|
|
398
398
|
handleSend();
|
|
@@ -404,69 +404,102 @@ export const MultimodalInput: FC<PropsType> = ({
|
|
|
404
404
|
<ArrowUpIcon size={14} />
|
|
405
405
|
</Button>
|
|
406
406
|
)}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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>
|
|
419
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>
|
|
420
500
|
</div>
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
variant="outline"
|
|
424
|
-
onClick={(event) => {
|
|
425
|
-
event.preventDefault();
|
|
426
|
-
onRecordingCancelled();
|
|
427
|
-
}}
|
|
428
|
-
>
|
|
429
|
-
<TrashIcon size={14} color="red" />
|
|
430
|
-
</Button>
|
|
431
|
-
</>
|
|
432
|
-
) : (
|
|
433
|
-
<>
|
|
434
|
-
<Button
|
|
435
|
-
className="rounded-full p-1.5 h-fit absolute bottom-2 right-11 m-0.5 dark:border-zinc-700"
|
|
436
|
-
onClick={(event) => {
|
|
437
|
-
event.preventDefault();
|
|
438
|
-
setIsRecording(true);
|
|
439
|
-
}}
|
|
440
|
-
variant="outline"
|
|
441
|
-
disabled={isLoading}
|
|
442
|
-
>
|
|
443
|
-
<MicrophoneIcon size={14} />
|
|
444
|
-
</Button>
|
|
445
|
-
<Button
|
|
446
|
-
className={`rounded-full p-1.5 h-fit absolute bottom-2 right-[80px] m-0.5 dark:border-zinc-700 ${enableTTS ? 'text-white hover:bg-primary/90 bg-primary' : ''}`}
|
|
447
|
-
onClick={(event) => {
|
|
448
|
-
event.preventDefault();
|
|
449
|
-
setEnableTTS(!enableTTS);
|
|
450
|
-
}}
|
|
451
|
-
variant="outline"
|
|
452
|
-
disabled={isLoading}
|
|
453
|
-
>
|
|
454
|
-
<VolumeIcon />
|
|
455
|
-
</Button>
|
|
456
|
-
</>
|
|
457
|
-
)}
|
|
458
|
-
|
|
459
|
-
<Button
|
|
460
|
-
className="rounded-full p-1.5 h-fit absolute bottom-2 left-2 m-0.5 dark:border-zinc-700"
|
|
461
|
-
onClick={(event) => {
|
|
462
|
-
event.preventDefault();
|
|
463
|
-
fileInputRef.current?.click();
|
|
464
|
-
}}
|
|
465
|
-
variant="outline"
|
|
466
|
-
disabled={isLoading || isRecording}
|
|
467
|
-
>
|
|
468
|
-
<PlusIcon size={14} />
|
|
469
|
-
</Button>
|
|
501
|
+
)}
|
|
502
|
+
</div>
|
|
470
503
|
</div>
|
|
471
504
|
);
|
|
472
505
|
};
|
|
@@ -0,0 +1,99 @@
|
|
|
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;
|
|
@@ -5,7 +5,7 @@ import Agent from '../../components/Agent';
|
|
|
5
5
|
import { ChatDataProvider } from '../../hooks/useChatData';
|
|
6
6
|
import styles from '../../assets/tailwindcss.css';
|
|
7
7
|
import commonStyles from '../../assets/common.css';
|
|
8
|
-
import { SuggestionType } from '../../types/common.type';
|
|
8
|
+
import { ConfigMessageType, SuggestionType } from '../../types/common.type';
|
|
9
9
|
|
|
10
10
|
export type AgentWidgetType = {
|
|
11
11
|
apiHost: string;
|
|
@@ -16,7 +16,13 @@ export type AgentWidgetType = {
|
|
|
16
16
|
suggestedActions?: SuggestionType[];
|
|
17
17
|
} & Record<string, unknown>;
|
|
18
18
|
theme?: {
|
|
19
|
-
|
|
19
|
+
botMessage?: ConfigMessageType;
|
|
20
|
+
userMessage?: ConfigMessageType;
|
|
21
|
+
suggestion?: {
|
|
22
|
+
defaultRows?: number; // only for scroll mode
|
|
23
|
+
expandedRows?: number; // only for scroll mode
|
|
24
|
+
layoutMode?: 'grid' | 'scroll';
|
|
25
|
+
};
|
|
20
26
|
input?: {
|
|
21
27
|
placeholder?: string;
|
|
22
28
|
};
|
package/src/hooks/useChat.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import useSWR from 'swr';
|
|
2
2
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { StreamResponse } from '../models/FlowiseClient';
|
|
4
|
-
import { predict } from '../services/chat.service';
|
|
4
|
+
import { getSuggestions, predict } from '../services/chat.service';
|
|
5
5
|
import {
|
|
6
6
|
ChatMessageMetadataType,
|
|
7
7
|
ChatMessageType,
|
|
@@ -14,6 +14,7 @@ import { generateUUID } from '../utils/commonUtils';
|
|
|
14
14
|
import { getBot } from '../services/bot.service';
|
|
15
15
|
import { BotType } from '../types/bot.type';
|
|
16
16
|
import { useConfiguration } from './useConfiguration';
|
|
17
|
+
import { SuggestionType } from 'types/common.type';
|
|
17
18
|
|
|
18
19
|
type PropsType = {
|
|
19
20
|
id?: string;
|
|
@@ -35,17 +36,20 @@ type ReturnType = {
|
|
|
35
36
|
bot: BotType | null;
|
|
36
37
|
enableTTS: boolean;
|
|
37
38
|
setEnableTTS: (value: boolean) => void;
|
|
39
|
+
suggestions: SuggestionType[]
|
|
38
40
|
};
|
|
39
41
|
|
|
40
42
|
export const useChat = (props: PropsType): ReturnType => {
|
|
41
43
|
const { id, initialMessages, initialInput, agentId } = props;
|
|
42
|
-
const { apiHost, overrideConfig } = useConfiguration();
|
|
44
|
+
const { apiHost, overrideConfig, theme } = useConfiguration();
|
|
43
45
|
|
|
44
46
|
const idKey = id ?? generateUUID();
|
|
45
47
|
const chatIdRef = useRef<string>(idKey);
|
|
46
48
|
const [chatId, setChatId] = useState(idKey);
|
|
47
49
|
const [bot, setBot] = useState<BotType | null>(null);
|
|
48
50
|
const [enableTTS, setEnableTTS] = useState(false);
|
|
51
|
+
const [suggestions, setSuggestions] = useState<SuggestionType[]>([]);
|
|
52
|
+
|
|
49
53
|
|
|
50
54
|
const updateChatId = (uuid: string) => {
|
|
51
55
|
chatIdRef.current = uuid;
|
|
@@ -62,6 +66,17 @@ export const useChat = (props: PropsType): ReturnType => {
|
|
|
62
66
|
getBot(agentId || 'default', apiHost).then((res) => setBot(res));
|
|
63
67
|
}, [agentId, apiHost]);
|
|
64
68
|
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (bot?.id && chatId) {
|
|
71
|
+
updateSuggestions(`Agent name: ${bot.name}, Title: ${theme?.overview?.title}`)
|
|
72
|
+
}
|
|
73
|
+
}, [bot?.id, chatId])
|
|
74
|
+
|
|
75
|
+
const updateSuggestions = useCallback(async (question?: string) => {
|
|
76
|
+
const resSuggestions = await getSuggestions({ id: chatId, question, apiHost });
|
|
77
|
+
setSuggestions(resSuggestions);
|
|
78
|
+
}, [setSuggestions, chatId]);
|
|
79
|
+
|
|
65
80
|
const processResponseStream = async (
|
|
66
81
|
msgs: ChatMessageType[],
|
|
67
82
|
req: PredictionData
|
|
@@ -94,6 +109,7 @@ export const useChat = (props: PropsType): ReturnType => {
|
|
|
94
109
|
lastMsg.content = newMessage.metaData.question;
|
|
95
110
|
}
|
|
96
111
|
mutateMessages([...msgs, { ...newMessage }]);
|
|
112
|
+
updateSuggestions(`Agent name: ${bot?.name}, Title: ${theme?.overview?.title}`);
|
|
97
113
|
} else if (chunk.event == 'audio') {
|
|
98
114
|
newMessage.ttsUrl = chunk.data as string;
|
|
99
115
|
mutateMessages([...msgs, { ...newMessage }]);
|
|
@@ -241,5 +257,6 @@ export const useChat = (props: PropsType): ReturnType => {
|
|
|
241
257
|
bot,
|
|
242
258
|
enableTTS,
|
|
243
259
|
setEnableTTS,
|
|
260
|
+
suggestions
|
|
244
261
|
};
|
|
245
262
|
};
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import React, { createContext, useEffect, useState } from 'react';
|
|
2
|
+
import { ConfigMessageType } from 'types/common.type';
|
|
2
3
|
|
|
3
4
|
type ConfigurationData = {
|
|
4
5
|
apiHost: string;
|
|
5
6
|
agentId: string;
|
|
6
7
|
overrideConfig?: Record<string, any>;
|
|
7
8
|
theme?: {
|
|
8
|
-
|
|
9
|
+
suggestion?: {
|
|
10
|
+
defaultRows?: number; // only for scroll mode
|
|
11
|
+
expandedRows?: number; // only for scroll mode
|
|
12
|
+
layoutMode?: 'grid' | 'scroll';
|
|
13
|
+
};
|
|
14
|
+
botMessage?: ConfigMessageType;
|
|
15
|
+
userMessage?: ConfigMessageType;
|
|
9
16
|
input?: {
|
|
10
17
|
placeholder?: string;
|
|
11
18
|
};
|