@clikvn/agent-widget-embedded 0.0.3-dev → 0.0.5-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/AudioPlayer.d.ts +6 -0
- package/dist/components/Chat/AudioPlayer.d.ts.map +1 -0
- package/dist/components/Chat/AudioRecording.d.ts +9 -0
- package/dist/components/Chat/AudioRecording.d.ts.map +1 -0
- package/dist/components/Chat/Chat.d.ts.map +1 -1
- package/dist/components/Chat/Icons.d.ts +9 -1
- package/dist/components/Chat/Icons.d.ts.map +1 -1
- package/dist/components/Chat/Markdown.d.ts.map +1 -1
- package/dist/components/Chat/Message.d.ts +1 -0
- package/dist/components/Chat/Message.d.ts.map +1 -1
- package/dist/components/Chat/MultimodalInput.d.ts +2 -0
- package/dist/components/Chat/MultimodalInput.d.ts.map +1 -1
- package/dist/components/Chat/PreviewAttachment.d.ts.map +1 -1
- package/dist/features/AgentWidget/index.d.ts +2 -0
- package/dist/features/AgentWidget/index.d.ts.map +1 -1
- package/dist/hooks/useAudioRecording.d.ts +11 -0
- package/dist/hooks/useAudioRecording.d.ts.map +1 -0
- package/dist/hooks/useChat.d.ts +2 -0
- package/dist/hooks/useChat.d.ts.map +1 -1
- package/dist/hooks/useChatData.d.ts +2 -0
- package/dist/hooks/useChatData.d.ts.map +1 -1
- package/dist/hooks/useConfiguration.d.ts +1 -6
- package/dist/hooks/useConfiguration.d.ts.map +1 -1
- package/dist/index.html +43 -0
- package/dist/register.d.ts +5 -3
- package/dist/register.d.ts.map +1 -1
- package/dist/types/common.type.d.ts +5 -0
- package/dist/types/common.type.d.ts.map +1 -1
- package/dist/types/flowise.type.d.ts +8 -0
- package/dist/types/flowise.type.d.ts.map +1 -1
- package/dist/utils/audioRecording.d.ts +21 -0
- package/dist/utils/audioRecording.d.ts.map +1 -0
- package/dist/web.d.ts +1 -12
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +1 -1
- package/dist/window.d.ts +3 -15
- package/dist/window.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/components/Chat/AudioPlayer.tsx +43 -0
- package/src/components/Chat/Chat.tsx +5 -0
- package/src/components/Chat/Icons.tsx +50 -3
- package/src/components/Chat/Markdown.tsx +12 -1
- package/src/components/Chat/Message.tsx +6 -0
- package/src/components/Chat/MultimodalInput.tsx +132 -33
- package/src/components/Chat/PreviewAttachment.tsx +10 -5
- package/src/features/AgentWidget/index.tsx +5 -2
- package/src/hooks/useAudioRecording.ts +50 -0
- package/src/hooks/useChat.ts +23 -5
- package/src/hooks/useChatData.tsx +3 -0
- package/src/hooks/useConfiguration.tsx +1 -6
- package/src/register.tsx +5 -2
- package/src/types/common.type.ts +6 -0
- package/src/types/flowise.type.ts +9 -0
- package/src/utils/audioRecording.ts +371 -0
- package/src/window.ts +2 -15
package/dist/window.d.ts
CHANGED
|
@@ -1,17 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
apiHost: string;
|
|
4
|
-
agentId: string;
|
|
5
|
-
overrideConfig?: {
|
|
6
|
-
chatId?: string | undefined;
|
|
7
|
-
overrideConfig?: Record<string, any>;
|
|
8
|
-
} & Record<string, unknown>;
|
|
9
|
-
theme?: {
|
|
10
|
-
avatar?: string;
|
|
11
|
-
} & Record<string, unknown>;
|
|
12
|
-
listeners?: Record<EVENT_TYPE, (props: any) => void>;
|
|
13
|
-
};
|
|
14
|
-
export declare const initWidget: (props: VoiceAgentWidget & {
|
|
1
|
+
import { AgentWidgetType } from './register';
|
|
2
|
+
export declare const initWidget: (props: AgentWidgetType & {
|
|
15
3
|
id?: string;
|
|
16
4
|
}) => void;
|
|
17
5
|
export declare const destroy: () => void;
|
|
@@ -20,7 +8,7 @@ type AgentWidget = {
|
|
|
20
8
|
destroy: typeof destroy;
|
|
21
9
|
};
|
|
22
10
|
export declare const parseAgentVoice: () => {
|
|
23
|
-
initWidget: (props:
|
|
11
|
+
initWidget: (props: AgentWidgetType & {
|
|
24
12
|
id?: string;
|
|
25
13
|
}) => void;
|
|
26
14
|
destroy: () => void;
|
package/dist/window.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"window.d.ts","sourceRoot":"","sources":["../src/window.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"window.d.ts","sourceRoot":"","sources":["../src/window.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAyB,MAAM,YAAY,CAAC;AAIpE,eAAO,MAAM,UAAU,UAAW,eAAe,GAAG;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,SAYlE,CAAC;AAEF,eAAO,MAAM,OAAO,YAEnB,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,OAAO,EAAE,OAAO,OAAO,CAAC;CACzB,CAAC;AAQF,eAAO,MAAM,eAAe;wBA7BM,eAAe,GAAG;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE;;CAgCjE,CAAC;AAEH,eAAO,MAAM,wBAAwB,UAAW,WAAW,SAG1D,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clikvn/agent-widget-embedded",
|
|
3
3
|
"description": "This is agent widget",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.5-dev",
|
|
5
5
|
"author": "Clik JSC",
|
|
6
6
|
"license": "ISC",
|
|
7
7
|
"type": "module",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"@types/react-dom": "^18.3.1",
|
|
33
33
|
"class-variance-authority": "^0.7.1",
|
|
34
34
|
"clsx": "^2.1.1",
|
|
35
|
+
"device-detector-js": "^3.0.3",
|
|
35
36
|
"framer-motion": "^11.18.0",
|
|
36
37
|
"react": "^18.3.1",
|
|
37
38
|
"react-dom": "^18.3.1",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React, { useRef, useState } from 'react';
|
|
2
|
+
import { PlayIcon, StopIcon1 } from './Icons';
|
|
3
|
+
|
|
4
|
+
const AudioPlayer = ({
|
|
5
|
+
src,
|
|
6
|
+
autoplay = false,
|
|
7
|
+
}: {
|
|
8
|
+
src: string;
|
|
9
|
+
autoplay?: boolean;
|
|
10
|
+
}) => {
|
|
11
|
+
const audioRef = useRef<any>(null);
|
|
12
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
13
|
+
|
|
14
|
+
const handlePlayPause = (e: any) => {
|
|
15
|
+
e.preventDefault();
|
|
16
|
+
if (isPlaying) {
|
|
17
|
+
audioRef.current.pause();
|
|
18
|
+
} else {
|
|
19
|
+
audioRef.current.play();
|
|
20
|
+
}
|
|
21
|
+
setIsPlaying(!isPlaying);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
<button
|
|
27
|
+
className="rounded-full cursor-pointer h-fit w-[24px]"
|
|
28
|
+
onClick={handlePlayPause}
|
|
29
|
+
>
|
|
30
|
+
{isPlaying ? <StopIcon1 /> : <PlayIcon />}
|
|
31
|
+
</button>
|
|
32
|
+
<audio
|
|
33
|
+
ref={audioRef}
|
|
34
|
+
className="hidden"
|
|
35
|
+
src={src}
|
|
36
|
+
autoPlay={autoplay}
|
|
37
|
+
onEnded={() => setIsPlaying(false)}
|
|
38
|
+
></audio>
|
|
39
|
+
</>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default AudioPlayer;
|
|
@@ -25,6 +25,8 @@ export const Chat: FC<PropsType> = ({ id, agentId, initialMessages = [] }) => {
|
|
|
25
25
|
chatId,
|
|
26
26
|
append,
|
|
27
27
|
bot,
|
|
28
|
+
enableTTS,
|
|
29
|
+
setEnableTTS,
|
|
28
30
|
} = useChat({ id, initialMessages, agentId });
|
|
29
31
|
const { apiHost } = useConfiguration();
|
|
30
32
|
const [messagesContainerRef, messagesEndRef] =
|
|
@@ -47,6 +49,7 @@ export const Chat: FC<PropsType> = ({ id, agentId, initialMessages = [] }) => {
|
|
|
47
49
|
chatId={id}
|
|
48
50
|
message={message}
|
|
49
51
|
isLoading={isLoading && (messages || []).length - 1 === index}
|
|
52
|
+
enableTTS={enableTTS}
|
|
50
53
|
/>
|
|
51
54
|
))}
|
|
52
55
|
|
|
@@ -76,6 +79,8 @@ export const Chat: FC<PropsType> = ({ id, agentId, initialMessages = [] }) => {
|
|
|
76
79
|
setAttachments={setAttachments}
|
|
77
80
|
bot={bot}
|
|
78
81
|
apiHost={apiHost}
|
|
82
|
+
setEnableTTS={setEnableTTS}
|
|
83
|
+
enableTTS={enableTTS}
|
|
79
84
|
/>
|
|
80
85
|
</form>
|
|
81
86
|
</div>
|
|
@@ -477,14 +477,20 @@ export const MoreIcon = ({ size = 16 }: { size?: number }) => {
|
|
|
477
477
|
);
|
|
478
478
|
};
|
|
479
479
|
|
|
480
|
-
export const TrashIcon = ({
|
|
480
|
+
export const TrashIcon = ({
|
|
481
|
+
size = 16,
|
|
482
|
+
color,
|
|
483
|
+
}: {
|
|
484
|
+
size?: number;
|
|
485
|
+
color?: string;
|
|
486
|
+
}) => {
|
|
481
487
|
return (
|
|
482
488
|
<svg
|
|
483
489
|
height={size}
|
|
484
490
|
strokeLinejoin="round"
|
|
485
491
|
viewBox="0 0 16 16"
|
|
486
492
|
width={size}
|
|
487
|
-
style={{ color: 'currentcolor' }}
|
|
493
|
+
style={{ color: color || 'currentcolor' }}
|
|
488
494
|
>
|
|
489
495
|
<path
|
|
490
496
|
fillRule="evenodd"
|
|
@@ -859,9 +865,9 @@ export const CheckCirclFillIcon = ({ size = 16 }: { size?: number }) => {
|
|
|
859
865
|
export const MicrophoneIcon = ({ size = 16 }: { size?: number }) => {
|
|
860
866
|
return (
|
|
861
867
|
<svg
|
|
862
|
-
height={size}
|
|
863
868
|
strokeLinejoin="round"
|
|
864
869
|
viewBox="0 0 490.9 490.9"
|
|
870
|
+
height={size}
|
|
865
871
|
width={size}
|
|
866
872
|
style={{ color: 'currentcolor' }}
|
|
867
873
|
>
|
|
@@ -881,3 +887,44 @@ export const MicrophoneIcon = ({ size = 16 }: { size?: number }) => {
|
|
|
881
887
|
</svg>
|
|
882
888
|
);
|
|
883
889
|
};
|
|
890
|
+
|
|
891
|
+
export const CircleDotIcon = ({
|
|
892
|
+
size = 16,
|
|
893
|
+
color = 'red',
|
|
894
|
+
}: {
|
|
895
|
+
size?: number;
|
|
896
|
+
color?: string;
|
|
897
|
+
}) => (
|
|
898
|
+
<svg
|
|
899
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
900
|
+
height={size}
|
|
901
|
+
width={size}
|
|
902
|
+
viewBox="0 0 24 24"
|
|
903
|
+
fill="none"
|
|
904
|
+
stroke={color}
|
|
905
|
+
stroke-width="2"
|
|
906
|
+
stroke-linecap="round"
|
|
907
|
+
stroke-linejoin="round"
|
|
908
|
+
>
|
|
909
|
+
<circle cx="12" cy="12" r="10" />
|
|
910
|
+
<circle cx="12" cy="12" r="1" />
|
|
911
|
+
</svg>
|
|
912
|
+
);
|
|
913
|
+
|
|
914
|
+
export const PlayIcon = () => (
|
|
915
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
916
|
+
<path d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zM188.3 147.1c7.6-4.2 16.8-4.1 24.3 .5l144 88c7.1 4.4 11.5 12.1 11.5 20.5s-4.4 16.1-11.5 20.5l-144 88c-7.4 4.5-16.7 4.7-24.3 .5s-12.3-12.2-12.3-20.9l0-176c0-8.7 4.7-16.7 12.3-20.9z" />
|
|
917
|
+
</svg>
|
|
918
|
+
);
|
|
919
|
+
|
|
920
|
+
export const StopIcon1 = () => (
|
|
921
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
922
|
+
<path d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm192-96l128 0c17.7 0 32 14.3 32 32l0 128c0 17.7-14.3 32-32 32l-128 0c-17.7 0-32-14.3-32-32l0-128c0-17.7 14.3-32 32-32z" />
|
|
923
|
+
</svg>
|
|
924
|
+
);
|
|
925
|
+
|
|
926
|
+
export const VolumeIcon = () => (
|
|
927
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512">
|
|
928
|
+
<path d="M533.6 32.5C598.5 85.2 640 165.8 640 256s-41.5 170.7-106.4 223.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C557.5 398.2 592 331.2 592 256s-34.5-142.2-88.7-186.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM473.1 107c43.2 35.2 70.9 88.9 70.9 149s-27.7 113.8-70.9 149c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C475.3 341.3 496 301.1 496 256s-20.7-85.3-53.2-111.8c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zm-60.5 74.5C434.1 199.1 448 225.9 448 256s-13.9 56.9-35.4 74.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C393.1 284.4 400 271 400 256s-6.9-28.4-17.7-37.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM301.1 34.8C312.6 40 320 51.4 320 64l0 384c0 12.6-7.4 24-18.9 29.2s-25 3.1-34.4-5.3L131.8 352 64 352c-35.3 0-64-28.7-64-64l0-64c0-35.3 28.7-64 64-64l67.8 0L266.7 40.1c9.4-8.4 22.9-10.4 34.4-5.3z" />
|
|
929
|
+
</svg>
|
|
930
|
+
);
|
|
@@ -243,7 +243,18 @@ const NonMemoizedMarkdown: FC<{
|
|
|
243
243
|
content.indexOf('.jpeg') >= 0 ||
|
|
244
244
|
content.indexOf('.png') >= 0 ||
|
|
245
245
|
content.indexOf('.gif') >= 0);
|
|
246
|
-
|
|
246
|
+
const isAudioUrl =
|
|
247
|
+
content &&
|
|
248
|
+
(content.indexOf('.mp3') >= 0 ||
|
|
249
|
+
content.indexOf('.wav') >= 0 ||
|
|
250
|
+
content.indexOf('.ogg') >= 0);
|
|
251
|
+
if (isAudioUrl) {
|
|
252
|
+
return (
|
|
253
|
+
<audio controls>
|
|
254
|
+
<source src={content} />
|
|
255
|
+
</audio>
|
|
256
|
+
);
|
|
257
|
+
} else if (isImageUrl) {
|
|
247
258
|
return (
|
|
248
259
|
<a href={content} target="_blank" rel="noopener noreferrer">
|
|
249
260
|
<img
|
|
@@ -10,12 +10,14 @@ import {
|
|
|
10
10
|
ToolUsage,
|
|
11
11
|
} from '../../types/flowise.type';
|
|
12
12
|
import { cn } from '../../utils/commonUtils';
|
|
13
|
+
import AudioPlayer from './AudioPlayer';
|
|
13
14
|
|
|
14
15
|
type PropsType = {
|
|
15
16
|
chatId?: string;
|
|
16
17
|
message: ChatMessageType;
|
|
17
18
|
isLoading: boolean;
|
|
18
19
|
bot: BotType | null;
|
|
20
|
+
enableTTS?: boolean;
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
const getRole = (role?: MessageRoleType) => {
|
|
@@ -31,6 +33,7 @@ export const PreviewMessage: FC<PropsType> = ({
|
|
|
31
33
|
message,
|
|
32
34
|
isLoading,
|
|
33
35
|
bot,
|
|
36
|
+
enableTTS,
|
|
34
37
|
}) => {
|
|
35
38
|
const parseOutput = (outputData: any) => {
|
|
36
39
|
try {
|
|
@@ -136,6 +139,9 @@ export const PreviewMessage: FC<PropsType> = ({
|
|
|
136
139
|
</Markdown>
|
|
137
140
|
</div>
|
|
138
141
|
)}
|
|
142
|
+
{message?.ttsUrl && (
|
|
143
|
+
<AudioPlayer src={message.ttsUrl} autoplay={!!enableTTS} />
|
|
144
|
+
)}
|
|
139
145
|
</div>
|
|
140
146
|
</div>
|
|
141
147
|
</motion.div>
|
|
@@ -14,36 +14,23 @@ import {
|
|
|
14
14
|
generateUUID,
|
|
15
15
|
} from '../../utils/commonUtils';
|
|
16
16
|
import { PreviewAttachment } from './PreviewAttachment';
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
ArrowUpIcon,
|
|
19
|
+
CircleDotIcon,
|
|
20
|
+
MicrophoneIcon,
|
|
21
|
+
PlusIcon,
|
|
22
|
+
StopIcon,
|
|
23
|
+
TrashIcon,
|
|
24
|
+
VolumeIcon,
|
|
25
|
+
} from './Icons';
|
|
18
26
|
import { ChatMessageType, IFileUpload } from '../../types/flowise.type';
|
|
19
27
|
import { BotType } from '../../types/bot.type';
|
|
20
28
|
|
|
21
29
|
import { createAttachments } from '../../services/chat.service';
|
|
22
30
|
import { Button } from './ui/Button';
|
|
23
31
|
import { Textarea } from './ui/Textarea';
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
{
|
|
27
|
-
title: 'What is the weather',
|
|
28
|
-
label: 'in Ha Noi?',
|
|
29
|
-
action: 'What is the weather in Ha Noi?',
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
title: 'Create a travel plan for an traveling',
|
|
33
|
-
label: 'to Ha Noi',
|
|
34
|
-
action: 'Create a travel plan for an traveling to Ha Noi',
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
title: 'Top of tourist attractions',
|
|
38
|
-
label: 'in Ha Noi',
|
|
39
|
-
action: 'Top of tourist attractions in Ha Noi',
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
title: 'List of museums',
|
|
43
|
-
label: 'in Ha Noi',
|
|
44
|
-
action: 'List of museums in Ha Noi',
|
|
45
|
-
},
|
|
46
|
-
];
|
|
32
|
+
import { useAudioRecording } from '../../hooks/useAudioRecording';
|
|
33
|
+
import { useChatData } from '../../hooks/useChatData';
|
|
47
34
|
|
|
48
35
|
type PropsType = {
|
|
49
36
|
input: string;
|
|
@@ -63,6 +50,8 @@ type PropsType = {
|
|
|
63
50
|
setAttachments?: (func: (files: IFileUpload[]) => IFileUpload[]) => void;
|
|
64
51
|
bot: BotType | null;
|
|
65
52
|
apiHost: string;
|
|
53
|
+
setEnableTTS: (value: boolean) => void;
|
|
54
|
+
enableTTS: boolean;
|
|
66
55
|
};
|
|
67
56
|
|
|
68
57
|
export const MultimodalInput: FC<PropsType> = ({
|
|
@@ -80,7 +69,18 @@ export const MultimodalInput: FC<PropsType> = ({
|
|
|
80
69
|
setAttachments,
|
|
81
70
|
bot,
|
|
82
71
|
apiHost,
|
|
72
|
+
setEnableTTS,
|
|
73
|
+
enableTTS,
|
|
83
74
|
}) => {
|
|
75
|
+
const { suggestedActions = [] } = useChatData();
|
|
76
|
+
const {
|
|
77
|
+
isRecording,
|
|
78
|
+
setIsRecording,
|
|
79
|
+
onRecordingCancelled,
|
|
80
|
+
onRecordingStopped,
|
|
81
|
+
elapsedTime,
|
|
82
|
+
isLoadingRecording,
|
|
83
|
+
} = useAudioRecording();
|
|
84
84
|
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
|
85
85
|
const { width } = useWindowSize();
|
|
86
86
|
useEffect(() => {
|
|
@@ -130,7 +130,7 @@ export const MultimodalInput: FC<PropsType> = ({
|
|
|
130
130
|
handleSubmit(undefined, attachments);
|
|
131
131
|
setLocalStorageInput('');
|
|
132
132
|
if (setAttachments) {
|
|
133
|
-
setAttachments((
|
|
133
|
+
setAttachments((_) => []);
|
|
134
134
|
if (fileInputRef.current) {
|
|
135
135
|
(fileInputRef.current as HTMLInputElement).value = '';
|
|
136
136
|
}
|
|
@@ -157,6 +157,33 @@ export const MultimodalInput: FC<PropsType> = ({
|
|
|
157
157
|
[chatId]
|
|
158
158
|
);
|
|
159
159
|
|
|
160
|
+
const toAudioBase64 = (blob: Blob) => {
|
|
161
|
+
return new Promise<IFileUpload>((resolve) => {
|
|
162
|
+
let mimeType = '';
|
|
163
|
+
const pos = blob.type.indexOf(';');
|
|
164
|
+
if (pos === -1) {
|
|
165
|
+
mimeType = blob.type;
|
|
166
|
+
} else {
|
|
167
|
+
mimeType = blob.type.substring(0, pos);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// read blob and add to previews
|
|
171
|
+
const reader = new FileReader();
|
|
172
|
+
reader.readAsDataURL(blob);
|
|
173
|
+
reader.onloadend = () => {
|
|
174
|
+
const base64data = reader.result as string;
|
|
175
|
+
const upload: IFileUpload = {
|
|
176
|
+
tempId: generateUUID(),
|
|
177
|
+
data: base64data,
|
|
178
|
+
type: 'audio',
|
|
179
|
+
name: `audio_${Date.now()}.wav`,
|
|
180
|
+
mime: mimeType,
|
|
181
|
+
};
|
|
182
|
+
resolve(upload);
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
|
|
160
187
|
const toBase64 = async (
|
|
161
188
|
file: File,
|
|
162
189
|
type = 'file'
|
|
@@ -213,7 +240,6 @@ export const MultimodalInput: FC<PropsType> = ({
|
|
|
213
240
|
const handleFileChange = useCallback(
|
|
214
241
|
async (event: ChangeEvent<HTMLInputElement>) => {
|
|
215
242
|
const files = Array.from(event.target.files || []);
|
|
216
|
-
|
|
217
243
|
try {
|
|
218
244
|
const uploadPromises = files.map((file) => checkUploadFile(file));
|
|
219
245
|
const uploadedAttachments = await Promise.all(uploadPromises);
|
|
@@ -235,6 +261,27 @@ export const MultimodalInput: FC<PropsType> = ({
|
|
|
235
261
|
[setAttachments, checkUploadFile]
|
|
236
262
|
);
|
|
237
263
|
|
|
264
|
+
const handleSubmitRecording = useCallback(
|
|
265
|
+
async (blob: Blob) => {
|
|
266
|
+
try {
|
|
267
|
+
const audioFile = await toAudioBase64(blob);
|
|
268
|
+
handleSubmit(undefined, [audioFile]);
|
|
269
|
+
setIsRecording(false);
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error('Error uploading files!', error);
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
[handleSubmit, setIsRecording]
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const handleSend = useCallback(async () => {
|
|
278
|
+
if (isRecording) {
|
|
279
|
+
onRecordingStopped(handleSubmitRecording);
|
|
280
|
+
} else {
|
|
281
|
+
submitForm();
|
|
282
|
+
}
|
|
283
|
+
}, [submitForm, onRecordingStopped, handleSubmitRecording]);
|
|
284
|
+
|
|
238
285
|
return (
|
|
239
286
|
<div className="relative w-full flex flex-col gap-4">
|
|
240
287
|
{messages.length === 0 && (
|
|
@@ -317,7 +364,6 @@ export const MultimodalInput: FC<PropsType> = ({
|
|
|
317
364
|
onKeyDown={(event) => {
|
|
318
365
|
if (event.key === 'Enter' && !event.shiftKey) {
|
|
319
366
|
event.preventDefault();
|
|
320
|
-
|
|
321
367
|
if (isLoading) {
|
|
322
368
|
console.error(
|
|
323
369
|
'Please wait for the model to finish its response!'
|
|
@@ -325,7 +371,7 @@ export const MultimodalInput: FC<PropsType> = ({
|
|
|
325
371
|
} else if (uploadQueue.length) {
|
|
326
372
|
console.error('Please wait for file is uploading!');
|
|
327
373
|
} else {
|
|
328
|
-
|
|
374
|
+
handleSend();
|
|
329
375
|
}
|
|
330
376
|
}
|
|
331
377
|
}}
|
|
@@ -347,24 +393,77 @@ export const MultimodalInput: FC<PropsType> = ({
|
|
|
347
393
|
className="rounded-full p-1.5 h-fit absolute bottom-2 right-2 m-0.5 border dark:border-zinc-600"
|
|
348
394
|
onClick={(event) => {
|
|
349
395
|
event.preventDefault();
|
|
350
|
-
|
|
396
|
+
handleSend();
|
|
351
397
|
}}
|
|
352
|
-
disabled={
|
|
398
|
+
disabled={
|
|
399
|
+
!isRecording && (input.length === 0 || !!uploadQueue.length)
|
|
400
|
+
} // zero input or uploading
|
|
353
401
|
>
|
|
354
402
|
<ArrowUpIcon size={14} />
|
|
355
403
|
</Button>
|
|
356
404
|
)}
|
|
405
|
+
{isRecording ? (
|
|
406
|
+
<>
|
|
407
|
+
<div
|
|
408
|
+
className="rounded-full bg-background flex absolute p-1 bottom-2 right-[80px]"
|
|
409
|
+
data-testid="input"
|
|
410
|
+
>
|
|
411
|
+
<div className="flex items-center gap-3">
|
|
412
|
+
<span>
|
|
413
|
+
<CircleDotIcon color="red" />
|
|
414
|
+
</span>
|
|
415
|
+
<span>{elapsedTime || '00:00'}</span>
|
|
416
|
+
{isLoadingRecording && <span className="ml-1.5">Sending...</span>}
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
<Button
|
|
420
|
+
className="rounded-full p-1.5 h-fit absolute bottom-2 right-11 m-0.5 dark:border-zinc-700"
|
|
421
|
+
variant="outline"
|
|
422
|
+
onClick={(event) => {
|
|
423
|
+
event.preventDefault();
|
|
424
|
+
onRecordingCancelled();
|
|
425
|
+
}}
|
|
426
|
+
>
|
|
427
|
+
<TrashIcon size={14} color="red" />
|
|
428
|
+
</Button>
|
|
429
|
+
</>
|
|
430
|
+
) : (
|
|
431
|
+
<>
|
|
432
|
+
<Button
|
|
433
|
+
className="rounded-full p-1.5 h-fit absolute bottom-2 right-11 m-0.5 dark:border-zinc-700"
|
|
434
|
+
onClick={(event) => {
|
|
435
|
+
event.preventDefault();
|
|
436
|
+
setIsRecording(true);
|
|
437
|
+
}}
|
|
438
|
+
variant="outline"
|
|
439
|
+
disabled={isLoading}
|
|
440
|
+
>
|
|
441
|
+
<MicrophoneIcon size={14} />
|
|
442
|
+
</Button>
|
|
443
|
+
<Button
|
|
444
|
+
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' : ''}`}
|
|
445
|
+
onClick={(event) => {
|
|
446
|
+
event.preventDefault();
|
|
447
|
+
setEnableTTS(!enableTTS);
|
|
448
|
+
}}
|
|
449
|
+
variant="outline"
|
|
450
|
+
disabled={isLoading}
|
|
451
|
+
>
|
|
452
|
+
<VolumeIcon />
|
|
453
|
+
</Button>
|
|
454
|
+
</>
|
|
455
|
+
)}
|
|
357
456
|
|
|
358
457
|
<Button
|
|
359
|
-
className="rounded-full p-1.5 h-fit absolute bottom-2
|
|
458
|
+
className="rounded-full p-1.5 h-fit absolute bottom-2 left-2 m-0.5 dark:border-zinc-700"
|
|
360
459
|
onClick={(event) => {
|
|
361
460
|
event.preventDefault();
|
|
362
461
|
fileInputRef.current?.click();
|
|
363
462
|
}}
|
|
364
463
|
variant="outline"
|
|
365
|
-
disabled={isLoading}
|
|
464
|
+
disabled={isLoading || isRecording}
|
|
366
465
|
>
|
|
367
|
-
<
|
|
466
|
+
<PlusIcon size={14} />
|
|
368
467
|
</Button>
|
|
369
468
|
</div>
|
|
370
469
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IFileUpload } from '../../types/flowise.type';
|
|
2
2
|
import { LoaderIcon } from './Icons';
|
|
3
|
+
import React from 'react';
|
|
3
4
|
|
|
4
5
|
export const PreviewAttachment = ({
|
|
5
6
|
attachment,
|
|
@@ -11,9 +12,13 @@ export const PreviewAttachment = ({
|
|
|
11
12
|
const { name, data, mime, tempId } = attachment;
|
|
12
13
|
return (
|
|
13
14
|
<div className="flex flex-col gap-2">
|
|
14
|
-
<div className="w-30 p-
|
|
15
|
+
<div className="w-30 p-0 max-w-[400px] bg-muted rounded-md relative flex flex-col items-center justify-center">
|
|
15
16
|
{data ? (
|
|
16
|
-
mime.startsWith('
|
|
17
|
+
mime.startsWith('audio') ? (
|
|
18
|
+
<audio controls>
|
|
19
|
+
<source src={data} type={mime} />
|
|
20
|
+
</audio>
|
|
21
|
+
) : mime.startsWith('image') ? (
|
|
17
22
|
<img
|
|
18
23
|
key={tempId}
|
|
19
24
|
src={data}
|
|
@@ -33,9 +38,9 @@ export const PreviewAttachment = ({
|
|
|
33
38
|
</div>
|
|
34
39
|
)}
|
|
35
40
|
</div>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
{/*<div className="text-xs text-zinc-500 max-w-[200px] truncate ">*/}
|
|
42
|
+
{/* {name}*/}
|
|
43
|
+
{/*</div>*/}
|
|
39
44
|
</div>
|
|
40
45
|
);
|
|
41
46
|
};
|
|
@@ -5,6 +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
9
|
|
|
9
10
|
export type AgentWidgetType = {
|
|
10
11
|
apiHost: string;
|
|
@@ -12,6 +13,7 @@ export type AgentWidgetType = {
|
|
|
12
13
|
overrideConfig?: {
|
|
13
14
|
chatId?: string | undefined;
|
|
14
15
|
overrideConfig?: Record<string, unknown>;
|
|
16
|
+
suggestedActions?: SuggestionType[];
|
|
15
17
|
} & Record<string, unknown>;
|
|
16
18
|
theme?: {
|
|
17
19
|
avatar?: string;
|
|
@@ -27,14 +29,15 @@ const AgentWidget: FC<AgentWidgetType> = (props: AgentWidgetType) => {
|
|
|
27
29
|
config={{
|
|
28
30
|
apiHost: props.apiHost,
|
|
29
31
|
agentId: props.agentId,
|
|
30
|
-
|
|
31
|
-
overrideConfig: props.overrideConfig,
|
|
32
|
+
overrideConfig: props.overrideConfig?.overrideConfig,
|
|
32
33
|
theme: props.theme,
|
|
33
34
|
}}
|
|
34
35
|
>
|
|
35
36
|
<ChatDataProvider
|
|
36
37
|
data={{
|
|
37
38
|
chatId: props.overrideConfig?.chatId,
|
|
39
|
+
suggestedActions: props.overrideConfig?.suggestedActions,
|
|
40
|
+
listeners: props.listeners,
|
|
38
41
|
}}
|
|
39
42
|
>
|
|
40
43
|
<Agent />
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
cancelAudioRecording,
|
|
4
|
+
startAudioRecording,
|
|
5
|
+
stopAudioRecording,
|
|
6
|
+
} from '../utils/audioRecording';
|
|
7
|
+
|
|
8
|
+
export const useAudioRecording = () => {
|
|
9
|
+
const [elapsedTime, setElapsedTime] = useState('00:00');
|
|
10
|
+
const [recordingNotSupported, setRecordingNotSupported] = useState(false);
|
|
11
|
+
const [isLoadingRecording, setIsLoadingRecording] = useState(false);
|
|
12
|
+
const [isRecording, setIsRecording] = useState(false);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (isRecording) {
|
|
16
|
+
onRecordingStarted();
|
|
17
|
+
}
|
|
18
|
+
}, [isRecording]);
|
|
19
|
+
const onRecordingStarted = () => {
|
|
20
|
+
setIsRecording(true);
|
|
21
|
+
setIsLoadingRecording(false);
|
|
22
|
+
startAudioRecording(
|
|
23
|
+
setIsRecording,
|
|
24
|
+
setRecordingNotSupported,
|
|
25
|
+
setElapsedTime
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const onRecordingCancelled = () => {
|
|
30
|
+
if (!recordingNotSupported) cancelAudioRecording();
|
|
31
|
+
setIsRecording(false);
|
|
32
|
+
setRecordingNotSupported(false);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const onRecordingStopped = (onStop: null | ((blob: Blob) => void)) => {
|
|
36
|
+
setIsLoadingRecording(true);
|
|
37
|
+
stopAudioRecording(onStop);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
elapsedTime,
|
|
42
|
+
recordingNotSupported,
|
|
43
|
+
isLoadingRecording,
|
|
44
|
+
isRecording,
|
|
45
|
+
setIsRecording,
|
|
46
|
+
onRecordingCancelled,
|
|
47
|
+
onRecordingStopped,
|
|
48
|
+
onRecordingStarted,
|
|
49
|
+
};
|
|
50
|
+
};
|