@clikvn/agent-widget-embedded 0.0.1-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/.eslintrc +34 -0
- package/.prettierrc +8 -0
- package/README.md +20 -0
- package/base.json +21 -0
- package/dist/commons/constants/index.d.ts +2 -0
- package/dist/commons/constants/index.d.ts.map +1 -0
- package/dist/commons/constants/variables.d.ts +5 -0
- package/dist/commons/constants/variables.d.ts.map +1 -0
- package/dist/components/Agent/index.d.ts +3 -0
- package/dist/components/Agent/index.d.ts.map +1 -0
- package/dist/components/Chat/Chat.d.ts +10 -0
- package/dist/components/Chat/Chat.d.ts.map +1 -0
- package/dist/components/Chat/Icons.d.ts +120 -0
- package/dist/components/Chat/Icons.d.ts.map +1 -0
- package/dist/components/Chat/Markdown.d.ts +7 -0
- package/dist/components/Chat/Markdown.d.ts.map +1 -0
- package/dist/components/Chat/Message.d.ts +15 -0
- package/dist/components/Chat/Message.d.ts.map +1 -0
- package/dist/components/Chat/MultimodalInput.d.ts +24 -0
- package/dist/components/Chat/MultimodalInput.d.ts.map +1 -0
- package/dist/components/Chat/Overview.d.ts +8 -0
- package/dist/components/Chat/Overview.d.ts.map +1 -0
- package/dist/components/Chat/PreviewAttachment.d.ts +6 -0
- package/dist/components/Chat/PreviewAttachment.d.ts.map +1 -0
- package/dist/components/Chat/ui/Button.d.ts +12 -0
- package/dist/components/Chat/ui/Button.d.ts.map +1 -0
- package/dist/components/Chat/ui/Textarea.d.ts +6 -0
- package/dist/components/Chat/ui/Textarea.d.ts.map +1 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/features/AgentWidget/index.d.ts +16 -0
- package/dist/features/AgentWidget/index.d.ts.map +1 -0
- package/dist/hooks/useChat.d.ts +25 -0
- package/dist/hooks/useChat.d.ts.map +1 -0
- package/dist/hooks/useChatData.d.ts +18 -0
- package/dist/hooks/useChatData.d.ts.map +1 -0
- package/dist/hooks/useConfiguration.d.ts +20 -0
- package/dist/hooks/useConfiguration.d.ts.map +1 -0
- package/dist/hooks/useConnection.d.ts +15 -0
- package/dist/hooks/useConnection.d.ts.map +1 -0
- package/dist/hooks/useScrollToBottom.d.ts +6 -0
- package/dist/hooks/useScrollToBottom.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/models/FlowiseClient.d.ts +20 -0
- package/dist/models/FlowiseClient.d.ts.map +1 -0
- package/dist/models.d.ts +2 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/register.d.ts +30 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/services/apis.d.ts +7 -0
- package/dist/services/apis.d.ts.map +1 -0
- package/dist/services/bot.service.d.ts +3 -0
- package/dist/services/bot.service.d.ts.map +1 -0
- package/dist/services/chat.service.d.ts +32 -0
- package/dist/services/chat.service.d.ts.map +1 -0
- package/dist/services/user.service.d.ts +3 -0
- package/dist/services/user.service.d.ts.map +1 -0
- package/dist/types/agentType.d.ts +11 -0
- package/dist/types/agentType.d.ts.map +1 -0
- package/dist/types/bot.type.d.ts +11 -0
- package/dist/types/bot.type.d.ts.map +1 -0
- package/dist/types/chat.type.d.ts +10 -0
- package/dist/types/chat.type.d.ts.map +1 -0
- package/dist/types/common.type.d.ts +11 -0
- package/dist/types/common.type.d.ts.map +1 -0
- package/dist/types/flowise.type.d.ts +90 -0
- package/dist/types/flowise.type.d.ts.map +1 -0
- package/dist/types/user.type.d.ts +14 -0
- package/dist/types/user.type.d.ts.map +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/commonUtils.d.ts +7 -0
- package/dist/utils/commonUtils.d.ts.map +1 -0
- package/dist/utils/functionUtils.d.ts +3 -0
- package/dist/utils/functionUtils.d.ts.map +1 -0
- package/dist/utils/requestUtils.d.ts +16 -0
- package/dist/utils/requestUtils.d.ts.map +1 -0
- package/dist/utils/streamUtils.d.ts +5 -0
- package/dist/utils/streamUtils.d.ts.map +1 -0
- package/dist/web.d.ts +18 -0
- package/dist/web.d.ts.map +1 -0
- package/dist/web.js +1 -0
- package/dist/window.d.ts +29 -0
- package/dist/window.d.ts.map +1 -0
- package/package.json +91 -0
- package/rollup.config.js +56 -0
- package/src/assets/common.css +148 -0
- package/src/assets/tailwindcss.css +3 -0
- package/src/commons/constants/index.ts +1 -0
- package/src/commons/constants/variables.ts +20 -0
- package/src/components/Agent/index.tsx +14 -0
- package/src/components/Chat/Chat.tsx +84 -0
- package/src/components/Chat/Icons.tsx +883 -0
- package/src/components/Chat/Markdown.tsx +324 -0
- package/src/components/Chat/Message.tsx +185 -0
- package/src/components/Chat/MultimodalInput.tsx +371 -0
- package/src/components/Chat/Overview.tsx +47 -0
- package/src/components/Chat/PreviewAttachment.tsx +41 -0
- package/src/components/Chat/ui/Button.tsx +55 -0
- package/src/components/Chat/ui/Textarea.tsx +23 -0
- package/src/constants.ts +1 -0
- package/src/env.d.ts +10 -0
- package/src/features/AgentWidget/index.tsx +47 -0
- package/src/global.d.ts +1 -0
- package/src/hooks/useChat.ts +225 -0
- package/src/hooks/useChatData.tsx +68 -0
- package/src/hooks/useConfiguration.tsx +54 -0
- package/src/hooks/useScrollToBottom.ts +31 -0
- package/src/index.ts +1 -0
- package/src/models/FlowiseClient.ts +103 -0
- package/src/models.ts +1 -0
- package/src/register.tsx +66 -0
- package/src/services/apis.ts +10 -0
- package/src/services/bot.service.ts +15 -0
- package/src/services/chat.service.ts +164 -0
- package/src/types/bot.type.ts +10 -0
- package/src/types/chat.type.ts +11 -0
- package/src/types/common.type.ts +11 -0
- package/src/types/flowise.type.ts +99 -0
- package/src/types/user.type.ts +15 -0
- package/src/types.ts +0 -0
- package/src/utils/commonUtils.ts +47 -0
- package/src/utils/functionUtils.ts +17 -0
- package/src/utils/requestUtils.ts +113 -0
- package/src/utils/streamUtils.ts +18 -0
- package/src/web.ts +6 -0
- package/src/window.ts +55 -0
- package/tailwind.config.cjs +122 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ChangeEvent,
|
|
3
|
+
FC,
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import { motion } from 'framer-motion';
|
|
10
|
+
import { useLocalStorage, useWindowSize } from 'usehooks-ts';
|
|
11
|
+
import {
|
|
12
|
+
cn,
|
|
13
|
+
generateExtendedFileName,
|
|
14
|
+
generateUUID,
|
|
15
|
+
} from '../../utils/commonUtils';
|
|
16
|
+
import { PreviewAttachment } from './PreviewAttachment';
|
|
17
|
+
import { ArrowUpIcon, PaperclipIcon, StopIcon } from './Icons';
|
|
18
|
+
import { ChatMessageType, IFileUpload } from '../../types/flowise.type';
|
|
19
|
+
import { BotType } from '../../types/bot.type';
|
|
20
|
+
|
|
21
|
+
import { createAttachments } from '../../services/chat.service';
|
|
22
|
+
import { Button } from './ui/Button';
|
|
23
|
+
import { Textarea } from './ui/Textarea';
|
|
24
|
+
|
|
25
|
+
const suggestedActions = [
|
|
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
|
+
];
|
|
47
|
+
|
|
48
|
+
type PropsType = {
|
|
49
|
+
input: string;
|
|
50
|
+
setInput: (value: string) => void;
|
|
51
|
+
isLoading: boolean;
|
|
52
|
+
stop: () => void;
|
|
53
|
+
messages: ChatMessageType[];
|
|
54
|
+
setMessages: (messages: ChatMessageType[]) => void;
|
|
55
|
+
chatId: string;
|
|
56
|
+
handleSubmit: (
|
|
57
|
+
event?: { preventDefault?: () => void },
|
|
58
|
+
files?: IFileUpload[]
|
|
59
|
+
) => void;
|
|
60
|
+
className?: string;
|
|
61
|
+
append?: (message: ChatMessageType) => Promise<void>;
|
|
62
|
+
attachments?: IFileUpload[];
|
|
63
|
+
setAttachments?: (func: (files: IFileUpload[]) => IFileUpload[]) => void;
|
|
64
|
+
bot: BotType | null;
|
|
65
|
+
apiHost: string;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const MultimodalInput: FC<PropsType> = ({
|
|
69
|
+
input,
|
|
70
|
+
setInput,
|
|
71
|
+
isLoading,
|
|
72
|
+
stop,
|
|
73
|
+
messages,
|
|
74
|
+
setMessages,
|
|
75
|
+
chatId,
|
|
76
|
+
handleSubmit,
|
|
77
|
+
className,
|
|
78
|
+
append,
|
|
79
|
+
attachments,
|
|
80
|
+
setAttachments,
|
|
81
|
+
bot,
|
|
82
|
+
apiHost,
|
|
83
|
+
}) => {
|
|
84
|
+
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
|
85
|
+
const { width } = useWindowSize();
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (textareaRef.current) {
|
|
88
|
+
adjustHeight();
|
|
89
|
+
}
|
|
90
|
+
}, []);
|
|
91
|
+
|
|
92
|
+
const adjustHeight = () => {
|
|
93
|
+
if (textareaRef.current) {
|
|
94
|
+
(textareaRef.current as HTMLTextAreaElement).style.height = 'auto';
|
|
95
|
+
(textareaRef.current as HTMLTextAreaElement).style.height =
|
|
96
|
+
`${textareaRef.current?.scrollHeight + 2}px`;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const [localStorageInput, setLocalStorageInput] = useLocalStorage(
|
|
101
|
+
'input',
|
|
102
|
+
''
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (textareaRef.current) {
|
|
107
|
+
const domValue = textareaRef.current?.value;
|
|
108
|
+
// Prefer DOM value over localStorage to handle hydration
|
|
109
|
+
const finalValue = domValue || localStorageInput || '';
|
|
110
|
+
setInput(finalValue);
|
|
111
|
+
adjustHeight();
|
|
112
|
+
}
|
|
113
|
+
// Only run once after hydration
|
|
114
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
115
|
+
}, []);
|
|
116
|
+
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
setLocalStorageInput(input);
|
|
119
|
+
}, [input, setLocalStorageInput]);
|
|
120
|
+
|
|
121
|
+
const handleInput = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
|
122
|
+
setInput(event.target.value);
|
|
123
|
+
adjustHeight();
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
|
127
|
+
const [uploadQueue, setUploadQueue] = useState<Array<string>>([]);
|
|
128
|
+
|
|
129
|
+
const submitForm = useCallback(async () => {
|
|
130
|
+
handleSubmit(undefined, attachments);
|
|
131
|
+
setLocalStorageInput('');
|
|
132
|
+
if (setAttachments) {
|
|
133
|
+
setAttachments((currentAttachments: IFileUpload[]) => []);
|
|
134
|
+
if (fileInputRef.current) {
|
|
135
|
+
(fileInputRef.current as HTMLInputElement).value = '';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (width && width > 768) {
|
|
139
|
+
textareaRef.current?.focus();
|
|
140
|
+
}
|
|
141
|
+
}, [handleSubmit, setLocalStorageInput, width, attachments, chatId, bot]);
|
|
142
|
+
|
|
143
|
+
const uploadFile = useCallback(
|
|
144
|
+
async (file: File) => {
|
|
145
|
+
const formData = new FormData();
|
|
146
|
+
formData.append('file', file, generateExtendedFileName(file.name));
|
|
147
|
+
try {
|
|
148
|
+
return await createAttachments({
|
|
149
|
+
chatId,
|
|
150
|
+
apiHost,
|
|
151
|
+
body: formData,
|
|
152
|
+
});
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error('Failed to upload file, please try again!');
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
[chatId]
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const toBase64 = async (
|
|
161
|
+
file: File,
|
|
162
|
+
type = 'file'
|
|
163
|
+
): Promise<IFileUpload | undefined> => {
|
|
164
|
+
return new Promise((resolve, reject) => {
|
|
165
|
+
const reader = new FileReader();
|
|
166
|
+
reader.readAsDataURL(file);
|
|
167
|
+
reader.onload = () => {
|
|
168
|
+
resolve({
|
|
169
|
+
tempId: generateUUID(),
|
|
170
|
+
data: reader.result as string,
|
|
171
|
+
type,
|
|
172
|
+
name: generateExtendedFileName(file.name),
|
|
173
|
+
mime: file.type,
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
reader.onerror = () => {
|
|
177
|
+
reject();
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const checkUploadFile = useCallback(
|
|
183
|
+
async (file: File): Promise<IFileUpload | undefined> => {
|
|
184
|
+
if (file.type.startsWith('image')) {
|
|
185
|
+
return toBase64(file);
|
|
186
|
+
} else {
|
|
187
|
+
setUploadQueue([file.name]);
|
|
188
|
+
const response: any = await uploadFile(file);
|
|
189
|
+
if (response?.type == 'file:full') {
|
|
190
|
+
const f = response.result[0];
|
|
191
|
+
if (!f) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
tempId: generateUUID(),
|
|
196
|
+
data: f.content,
|
|
197
|
+
name: f.name,
|
|
198
|
+
mime: f.mimeType,
|
|
199
|
+
type: response?.type,
|
|
200
|
+
};
|
|
201
|
+
} else if (response?.type == 'file:rag') {
|
|
202
|
+
const { addedDocs } = response.result;
|
|
203
|
+
if (!addedDocs.length) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
return toBase64(file, response?.type);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
[setUploadQueue, uploadFile]
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const handleFileChange = useCallback(
|
|
214
|
+
async (event: ChangeEvent<HTMLInputElement>) => {
|
|
215
|
+
const files = Array.from(event.target.files || []);
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const uploadPromises = files.map((file) => checkUploadFile(file));
|
|
219
|
+
const uploadedAttachments = await Promise.all(uploadPromises);
|
|
220
|
+
const successfullyUploadedAttachments = uploadedAttachments.filter(
|
|
221
|
+
(attachment) => attachment !== undefined
|
|
222
|
+
) as IFileUpload[];
|
|
223
|
+
if (setAttachments) {
|
|
224
|
+
setAttachments((currentAttachments: IFileUpload[]) => [
|
|
225
|
+
...currentAttachments,
|
|
226
|
+
...successfullyUploadedAttachments,
|
|
227
|
+
]);
|
|
228
|
+
}
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.error('Error uploading files!', error);
|
|
231
|
+
} finally {
|
|
232
|
+
setUploadQueue([]);
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
[setAttachments, checkUploadFile]
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
return (
|
|
239
|
+
<div className="relative w-full flex flex-col gap-4">
|
|
240
|
+
{messages.length === 0 && (
|
|
241
|
+
<div className="grid sm:grid-cols-2 gap-2 w-full">
|
|
242
|
+
{suggestedActions.map((suggestedAction, index) => (
|
|
243
|
+
<motion.div
|
|
244
|
+
initial={{ opacity: 0, y: 20 }}
|
|
245
|
+
animate={{ opacity: 1, y: 0 }}
|
|
246
|
+
exit={{ opacity: 0, y: 20 }}
|
|
247
|
+
transition={{ delay: 0.05 * index }}
|
|
248
|
+
key={`suggested-action-${suggestedAction.title}-${index}`}
|
|
249
|
+
className={index > 1 ? 'hidden sm:block' : 'block'}
|
|
250
|
+
>
|
|
251
|
+
<Button
|
|
252
|
+
variant="ghost"
|
|
253
|
+
onClick={async () => {
|
|
254
|
+
if (append) {
|
|
255
|
+
append({
|
|
256
|
+
role: 'apiMessage',
|
|
257
|
+
content: suggestedAction.action,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}}
|
|
261
|
+
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"
|
|
262
|
+
>
|
|
263
|
+
<span className="font-medium">{suggestedAction.title}</span>
|
|
264
|
+
<span className="text-muted-foreground">
|
|
265
|
+
{suggestedAction.label}
|
|
266
|
+
</span>
|
|
267
|
+
</Button>
|
|
268
|
+
</motion.div>
|
|
269
|
+
))}
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
272
|
+
|
|
273
|
+
<input
|
|
274
|
+
type="file"
|
|
275
|
+
className="fixed -top-4 -left-4 size-0.5 opacity-0 pointer-events-none"
|
|
276
|
+
ref={fileInputRef}
|
|
277
|
+
multiple
|
|
278
|
+
onChange={handleFileChange}
|
|
279
|
+
tabIndex={-1}
|
|
280
|
+
/>
|
|
281
|
+
|
|
282
|
+
{((attachments && attachments.length > 0) || uploadQueue.length > 0) && (
|
|
283
|
+
<div className="flex flex-row gap-2 overflow-x-scroll items-end">
|
|
284
|
+
{attachments?.map((attachment) => (
|
|
285
|
+
<PreviewAttachment
|
|
286
|
+
key={attachment.tempId}
|
|
287
|
+
attachment={attachment}
|
|
288
|
+
/>
|
|
289
|
+
))}
|
|
290
|
+
|
|
291
|
+
{uploadQueue.map((filename) => (
|
|
292
|
+
<PreviewAttachment
|
|
293
|
+
key={filename}
|
|
294
|
+
attachment={{
|
|
295
|
+
data: '',
|
|
296
|
+
name: filename,
|
|
297
|
+
mime: '',
|
|
298
|
+
type: '',
|
|
299
|
+
}}
|
|
300
|
+
isUploading
|
|
301
|
+
/>
|
|
302
|
+
))}
|
|
303
|
+
</div>
|
|
304
|
+
)}
|
|
305
|
+
|
|
306
|
+
<Textarea
|
|
307
|
+
ref={textareaRef}
|
|
308
|
+
placeholder="Send a message..."
|
|
309
|
+
value={input}
|
|
310
|
+
onChange={handleInput}
|
|
311
|
+
className={cn(
|
|
312
|
+
'min-h-[24px] max-h-[calc(75dvh)] overflow-hidden resize-none rounded-xl text-base bg-muted',
|
|
313
|
+
className
|
|
314
|
+
)}
|
|
315
|
+
rows={3}
|
|
316
|
+
autoFocus
|
|
317
|
+
onKeyDown={(event) => {
|
|
318
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
319
|
+
event.preventDefault();
|
|
320
|
+
|
|
321
|
+
if (isLoading) {
|
|
322
|
+
console.error(
|
|
323
|
+
'Please wait for the model to finish its response!'
|
|
324
|
+
);
|
|
325
|
+
} else if (uploadQueue.length) {
|
|
326
|
+
console.error('Please wait for file is uploading!');
|
|
327
|
+
} else {
|
|
328
|
+
submitForm();
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}}
|
|
332
|
+
/>
|
|
333
|
+
|
|
334
|
+
{isLoading ? (
|
|
335
|
+
<Button
|
|
336
|
+
className="rounded-full p-1.5 h-fit absolute bottom-2 right-2 m-0.5 border dark:border-zinc-600"
|
|
337
|
+
onClick={(event) => {
|
|
338
|
+
event.preventDefault();
|
|
339
|
+
stop();
|
|
340
|
+
setMessages(messages);
|
|
341
|
+
}}
|
|
342
|
+
>
|
|
343
|
+
<StopIcon size={14} />
|
|
344
|
+
</Button>
|
|
345
|
+
) : (
|
|
346
|
+
<Button
|
|
347
|
+
className="rounded-full p-1.5 h-fit absolute bottom-2 right-2 m-0.5 border dark:border-zinc-600"
|
|
348
|
+
onClick={(event) => {
|
|
349
|
+
event.preventDefault();
|
|
350
|
+
submitForm();
|
|
351
|
+
}}
|
|
352
|
+
disabled={input.length === 0 || !!uploadQueue.length}
|
|
353
|
+
>
|
|
354
|
+
<ArrowUpIcon size={14} />
|
|
355
|
+
</Button>
|
|
356
|
+
)}
|
|
357
|
+
|
|
358
|
+
<Button
|
|
359
|
+
className="rounded-full p-1.5 h-fit absolute bottom-2 right-11 m-0.5 dark:border-zinc-700"
|
|
360
|
+
onClick={(event) => {
|
|
361
|
+
event.preventDefault();
|
|
362
|
+
fileInputRef.current?.click();
|
|
363
|
+
}}
|
|
364
|
+
variant="outline"
|
|
365
|
+
disabled={isLoading}
|
|
366
|
+
>
|
|
367
|
+
<PaperclipIcon size={14} />
|
|
368
|
+
</Button>
|
|
369
|
+
</div>
|
|
370
|
+
);
|
|
371
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { motion } from 'framer-motion';
|
|
2
|
+
import { FC } from 'react';
|
|
3
|
+
import { BotType } from '../../types/bot.type';
|
|
4
|
+
|
|
5
|
+
type PropsType = {
|
|
6
|
+
bot: BotType | null;
|
|
7
|
+
};
|
|
8
|
+
export const Overview: FC<PropsType> = ({ bot }: PropsType) => {
|
|
9
|
+
return (
|
|
10
|
+
<motion.div
|
|
11
|
+
key="overview"
|
|
12
|
+
className="max-w-3xl mx-auto md:mt-20"
|
|
13
|
+
initial={{ opacity: 0, scale: 0.98 }}
|
|
14
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
15
|
+
exit={{ opacity: 0, scale: 0.98 }}
|
|
16
|
+
transition={{ delay: 0.5 }}
|
|
17
|
+
>
|
|
18
|
+
<div className="rounded-xl p-6 flex flex-col gap-2 leading-relaxed text-center max-w-xl">
|
|
19
|
+
<p className="flex flex-row justify-center gap-4 items-center"></p>
|
|
20
|
+
{bot?.avatar && (
|
|
21
|
+
<img
|
|
22
|
+
src={bot?.avatar}
|
|
23
|
+
alt={bot?.name ?? 'Avatar'}
|
|
24
|
+
width={40}
|
|
25
|
+
height={40}
|
|
26
|
+
className="rounded-full m-auto"
|
|
27
|
+
/>
|
|
28
|
+
)}
|
|
29
|
+
|
|
30
|
+
<p className="font-semibold text-xl">{`${bot?.name}`}</p>
|
|
31
|
+
<p>
|
|
32
|
+
{/* You can learn more about the AI by visiting the{' '}*/}
|
|
33
|
+
{/* <Link*/}
|
|
34
|
+
{/* className="font-medium underline underline-offset-4"*/}
|
|
35
|
+
{/* href="https://showai.ai"*/}
|
|
36
|
+
{/* target="_blank"*/}
|
|
37
|
+
{/* >*/}
|
|
38
|
+
{/* ShowAI*/}
|
|
39
|
+
{/* </Link>*/}
|
|
40
|
+
{/* .*/}
|
|
41
|
+
</p>
|
|
42
|
+
</div>
|
|
43
|
+
</motion.div>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default Overview;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { IFileUpload } from '../../types/flowise.type';
|
|
2
|
+
import { LoaderIcon } from './Icons';
|
|
3
|
+
|
|
4
|
+
export const PreviewAttachment = ({
|
|
5
|
+
attachment,
|
|
6
|
+
isUploading = false,
|
|
7
|
+
}: {
|
|
8
|
+
attachment: IFileUpload;
|
|
9
|
+
isUploading?: boolean;
|
|
10
|
+
}) => {
|
|
11
|
+
const { name, data, mime, tempId } = attachment;
|
|
12
|
+
return (
|
|
13
|
+
<div className="flex flex-col gap-2">
|
|
14
|
+
<div className="w-30 p-2 max-w-[200px] bg-muted rounded-md relative flex flex-col items-center justify-center">
|
|
15
|
+
{data ? (
|
|
16
|
+
mime.startsWith('image') ? (
|
|
17
|
+
<img
|
|
18
|
+
key={tempId}
|
|
19
|
+
src={data}
|
|
20
|
+
alt={name ?? 'An image attachment'}
|
|
21
|
+
className="rounded-md size-full object-contain"
|
|
22
|
+
/>
|
|
23
|
+
) : (
|
|
24
|
+
<div className="w-full text-left truncate">{name}</div>
|
|
25
|
+
)
|
|
26
|
+
) : (
|
|
27
|
+
<div className="w-full text-left truncate">{name}</div>
|
|
28
|
+
)}
|
|
29
|
+
|
|
30
|
+
{isUploading && (
|
|
31
|
+
<div className="animate-spin absolute text-zinc-500">
|
|
32
|
+
<LoaderIcon />
|
|
33
|
+
</div>
|
|
34
|
+
)}
|
|
35
|
+
</div>
|
|
36
|
+
<div className="text-xs text-zinc-500 max-w-[200px] truncate ">
|
|
37
|
+
{name}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
2
|
+
import { cn } from '../../../utils/commonUtils';
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
4
|
+
import { ButtonHTMLAttributes, forwardRef } from 'react';
|
|
5
|
+
|
|
6
|
+
const buttonVariants = cva(
|
|
7
|
+
'inline-flex items-center gap-2 justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
12
|
+
destructive:
|
|
13
|
+
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
|
14
|
+
outline:
|
|
15
|
+
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
|
16
|
+
secondary:
|
|
17
|
+
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
18
|
+
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
|
19
|
+
link: 'text-primary underline-offset-4 hover:underline',
|
|
20
|
+
},
|
|
21
|
+
size: {
|
|
22
|
+
default: 'h-10 px-4 py-2',
|
|
23
|
+
sm: 'h-9 rounded-md px-3',
|
|
24
|
+
lg: 'h-11 rounded-md px-8',
|
|
25
|
+
icon: 'h-10 w-10',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
defaultVariants: {
|
|
29
|
+
variant: 'default',
|
|
30
|
+
size: 'default',
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export interface ButtonProps
|
|
36
|
+
extends ButtonHTMLAttributes<HTMLButtonElement>,
|
|
37
|
+
VariantProps<typeof buttonVariants> {
|
|
38
|
+
asChild?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
42
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
43
|
+
const Comp = asChild ? Slot : 'button';
|
|
44
|
+
return (
|
|
45
|
+
<Comp
|
|
46
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
47
|
+
ref={ref}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
Button.displayName = 'Button';
|
|
54
|
+
|
|
55
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { cn } from '../../../utils/commonUtils';
|
|
2
|
+
import { forwardRef, TextareaHTMLAttributes } from 'react';
|
|
3
|
+
|
|
4
|
+
export interface TextareaProps
|
|
5
|
+
extends TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
|
6
|
+
|
|
7
|
+
const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
8
|
+
({ className, ...props }, ref) => {
|
|
9
|
+
return (
|
|
10
|
+
<textarea
|
|
11
|
+
className={cn(
|
|
12
|
+
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
13
|
+
className
|
|
14
|
+
)}
|
|
15
|
+
ref={ref}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
Textarea.displayName = 'Textarea';
|
|
22
|
+
|
|
23
|
+
export { Textarea };
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const agentWidgetElementName = 'clik-agent-widget';
|
package/src/env.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
import { ConfigurationProvider } from '../../hooks/useConfiguration';
|
|
3
|
+
import { EVENT_TYPE } from '../../models';
|
|
4
|
+
import Agent from '../../components/Agent';
|
|
5
|
+
import { ChatDataProvider } from '../../hooks/useChatData';
|
|
6
|
+
import styles from '../../assets/tailwindcss.css';
|
|
7
|
+
import commonStyles from '../../assets/common.css';
|
|
8
|
+
|
|
9
|
+
export type AgentWidgetType = {
|
|
10
|
+
apiHost: string;
|
|
11
|
+
agentId: string;
|
|
12
|
+
overrideConfig?: {
|
|
13
|
+
chatId?: string | undefined;
|
|
14
|
+
} & Record<string, unknown>;
|
|
15
|
+
theme?: {
|
|
16
|
+
avatar?: string;
|
|
17
|
+
} & Record<string, unknown>;
|
|
18
|
+
listeners?: Record<EVENT_TYPE, (props: any) => void>;
|
|
19
|
+
};
|
|
20
|
+
const AgentWidget: FC<AgentWidgetType> = (props: AgentWidgetType) => {
|
|
21
|
+
return (
|
|
22
|
+
<div className="w-full h-full text-sm">
|
|
23
|
+
<style>{styles}</style>
|
|
24
|
+
<style>{commonStyles}</style>
|
|
25
|
+
<ConfigurationProvider
|
|
26
|
+
config={{
|
|
27
|
+
apiHost: props.apiHost,
|
|
28
|
+
agentId: props.agentId,
|
|
29
|
+
listeners: props.listeners,
|
|
30
|
+
overrideConfig: props.overrideConfig,
|
|
31
|
+
theme: props.theme,
|
|
32
|
+
}}
|
|
33
|
+
>
|
|
34
|
+
<ChatDataProvider
|
|
35
|
+
data={{
|
|
36
|
+
chatId: props.overrideConfig?.chatId,
|
|
37
|
+
theme: props.theme,
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
<Agent />
|
|
41
|
+
</ChatDataProvider>
|
|
42
|
+
</ConfigurationProvider>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default AgentWidget;
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module '*.css';
|