funda-ui 4.6.151 → 4.6.333
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/Chatbox/index.css +88 -14
- package/Chatbox/index.d.ts +5 -0
- package/Chatbox/index.js +457 -281
- package/Utils/format-string.d.ts +64 -0
- package/Utils/format-string.js +157 -0
- package/lib/cjs/Chatbox/index.d.ts +5 -0
- package/lib/cjs/Chatbox/index.js +457 -281
- package/lib/cjs/Utils/format-string.d.ts +64 -0
- package/lib/cjs/Utils/format-string.js +157 -0
- package/lib/css/Chatbox/index.css +88 -14
- package/lib/esm/Chatbox/PureLoader.tsx +4 -2
- package/lib/esm/Chatbox/TypingEffect.tsx +12 -41
- package/lib/esm/Chatbox/index.scss +118 -28
- package/lib/esm/Chatbox/index.tsx +148 -36
- package/lib/esm/Chatbox/utils/func.ts +52 -1
- package/lib/esm/Textarea/index.tsx +0 -2
- package/lib/esm/Utils/libs/format-string.ts +106 -0
- package/package.json +1 -1
|
@@ -2,49 +2,69 @@
|
|
|
2
2
|
|
|
3
3
|
/*=================== Chatbox (Loading) =================*/
|
|
4
4
|
.custom-chatbox-loader-container {
|
|
5
|
+
--custom-chatbox-loader-color: #b9caf7;
|
|
6
|
+
--custom-chatbox-loader-color2: #0d6efd;
|
|
7
|
+
|
|
8
|
+
|
|
5
9
|
width: 130px;
|
|
6
10
|
text-align: start;
|
|
7
11
|
|
|
8
12
|
.custom-chatbox-loader {
|
|
9
13
|
height: 4px;
|
|
10
14
|
width: 100%;
|
|
11
|
-
--c: no-repeat linear-gradient(var(--
|
|
12
|
-
background: var(--c), var(--c),
|
|
15
|
+
--c: no-repeat linear-gradient(var(--custom-chatbox-loader-color2) 0 0);
|
|
16
|
+
background: var(--c), var(--c), var(--custom-chatbox-loader-color);
|
|
13
17
|
background-size: 60% 100%;
|
|
14
|
-
animation:
|
|
18
|
+
animation: loader-move 3s infinite;
|
|
15
19
|
}
|
|
16
|
-
}
|
|
17
20
|
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
background:
|
|
22
|
+
@keyframes loader-move {
|
|
23
|
+
0% {
|
|
24
|
+
background-position: -150% 0, -150% 0
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
66% {
|
|
28
|
+
background-position: 250% 0, -150% 0
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
100% {
|
|
32
|
+
background-position: 250% 0, 250% 0
|
|
26
33
|
}
|
|
27
34
|
}
|
|
28
|
-
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
|
|
32
|
-
@keyframes cssAnim--loadermove {
|
|
33
|
-
0% {
|
|
34
|
-
background-position: -150% 0, -150% 0
|
|
35
|
-
}
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
.custom-chatbox-mini-loader {
|
|
40
|
+
--custom-chatbox-miniloader-color: rgba(0,0,0,.5);
|
|
41
|
+
|
|
42
|
+
width: 15px;
|
|
43
|
+
height: 15px;
|
|
44
|
+
margin: .5rem;
|
|
45
|
+
margin-bottom: 0;
|
|
46
|
+
border: 3px dotted var(--custom-chatbox-miniloader-color);
|
|
47
|
+
border-radius: 50%;
|
|
48
|
+
display: inline-block;
|
|
49
|
+
position: relative;
|
|
50
|
+
box-sizing: border-box;
|
|
51
|
+
animation: mini-loader-spin 1s linear infinite;
|
|
52
|
+
|
|
53
|
+
@keyframes mini-loader-spin {
|
|
54
|
+
0% {
|
|
55
|
+
transform: rotate(0deg);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
100% {
|
|
59
|
+
transform: rotate(360deg);
|
|
60
|
+
}
|
|
40
61
|
|
|
41
|
-
100% {
|
|
42
|
-
background-position: 250% 0, 250% 0
|
|
43
62
|
}
|
|
44
63
|
}
|
|
45
64
|
|
|
46
65
|
|
|
47
66
|
|
|
67
|
+
|
|
48
68
|
/*=================== Chatbox (Core) =================*/
|
|
49
69
|
|
|
50
70
|
.custom-chatbox-circle {
|
|
@@ -144,6 +164,9 @@
|
|
|
144
164
|
--custom-chatbox-toolkit-btn-radius: 20px;
|
|
145
165
|
--custom-chatbox-questions-bg: #f5f5f5;
|
|
146
166
|
--custom-chatbox-questions-hover-bg: #e9e9e9;
|
|
167
|
+
--custom-chatbox-content-html-elem-border-color: #ddd;
|
|
168
|
+
--custom-chatbox-content-html-elem-bg: rgba(0,0,0,.05);
|
|
169
|
+
|
|
147
170
|
|
|
148
171
|
|
|
149
172
|
min-width: var(--custom-chatbox-w);
|
|
@@ -165,6 +188,8 @@
|
|
|
165
188
|
padding: 0;
|
|
166
189
|
font-size: 0.75rem;
|
|
167
190
|
margin-bottom: .5rem;
|
|
191
|
+
background: var(--custom-chatbox-msg-bg);
|
|
192
|
+
padding: .5rem;
|
|
168
193
|
}
|
|
169
194
|
|
|
170
195
|
summary {
|
|
@@ -201,7 +226,7 @@
|
|
|
201
226
|
|
|
202
227
|
/* message list */
|
|
203
228
|
.messages {
|
|
204
|
-
height: calc(100% -
|
|
229
|
+
height: calc(100% - 110px);
|
|
205
230
|
overflow-y: auto;
|
|
206
231
|
margin-bottom: 10px;
|
|
207
232
|
font-size: 13px;
|
|
@@ -220,10 +245,11 @@
|
|
|
220
245
|
}
|
|
221
246
|
|
|
222
247
|
|
|
223
|
-
> div {
|
|
248
|
+
> div:not(.newchat-btn) {
|
|
224
249
|
margin: 5px 0;
|
|
225
250
|
padding: 3px 5px;
|
|
226
251
|
border-radius: 0.35rem;
|
|
252
|
+
position: relative;
|
|
227
253
|
}
|
|
228
254
|
|
|
229
255
|
p {
|
|
@@ -254,6 +280,45 @@
|
|
|
254
280
|
margin-top: .3rem;
|
|
255
281
|
display: inline-block;
|
|
256
282
|
text-align: start;
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
/* Custom HTML Styles */
|
|
286
|
+
.table-container {
|
|
287
|
+
overflow-x: auto;
|
|
288
|
+
margin-bottom: .5rem;
|
|
289
|
+
|
|
290
|
+
&::-webkit-scrollbar {
|
|
291
|
+
height: 10px;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
&::-webkit-scrollbar-thumb {
|
|
295
|
+
background: rgba(0, 0, 0, 0.2);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
table {
|
|
299
|
+
width: 100%;
|
|
300
|
+
border-collapse: collapse;
|
|
301
|
+
border-radius: 0.35rem;
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
thead {
|
|
306
|
+
background: var(--custom-chatbox-content-html-elem-bg);
|
|
307
|
+
|
|
308
|
+
tr {
|
|
309
|
+
white-space: nowrap;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
th, td {
|
|
314
|
+
padding: .25rem;
|
|
315
|
+
text-align: left;
|
|
316
|
+
border: 1px solid var(--custom-chatbox-content-html-elem-border-color);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
|
|
257
322
|
}
|
|
258
323
|
|
|
259
324
|
|
|
@@ -275,6 +340,8 @@
|
|
|
275
340
|
|
|
276
341
|
.qa-content {
|
|
277
342
|
width: var(--custom-chatbox-content-w);
|
|
343
|
+
background: transparent;
|
|
344
|
+
padding-top: 0;
|
|
278
345
|
}
|
|
279
346
|
}
|
|
280
347
|
|
|
@@ -283,6 +350,26 @@
|
|
|
283
350
|
|
|
284
351
|
}
|
|
285
352
|
}
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
/* copy button */
|
|
356
|
+
.copy-btn {
|
|
357
|
+
position: absolute;
|
|
358
|
+
left: calc(var(--custom-chatbox-content-w) - .7rem);
|
|
359
|
+
bottom: 0.5rem;
|
|
360
|
+
z-index: 1;
|
|
361
|
+
background: transparent;
|
|
362
|
+
border: none;
|
|
363
|
+
padding: 4px;
|
|
364
|
+
cursor: pointer;
|
|
365
|
+
opacity: 0.6;
|
|
366
|
+
transition: opacity 0.2s;
|
|
367
|
+
|
|
368
|
+
&:hover {
|
|
369
|
+
opacity: 1;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
286
373
|
}
|
|
287
374
|
|
|
288
375
|
/* dot loading */
|
|
@@ -456,7 +543,12 @@
|
|
|
456
543
|
/* new chat button */
|
|
457
544
|
.newchat-btn {
|
|
458
545
|
text-align: center;
|
|
459
|
-
|
|
546
|
+
position: absolute;
|
|
547
|
+
bottom: 95px;
|
|
548
|
+
left: 50%;
|
|
549
|
+
transform: translateX(-50%);
|
|
550
|
+
z-index: 1;
|
|
551
|
+
|
|
460
552
|
> button {
|
|
461
553
|
padding: 3px 6px;
|
|
462
554
|
background-color: var(--custom-chatbox-newchat-btn-color);
|
|
@@ -572,6 +664,7 @@
|
|
|
572
664
|
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
|
|
573
665
|
margin-bottom: 10px;
|
|
574
666
|
max-height: 300px;
|
|
667
|
+
min-width: 120px;
|
|
575
668
|
overflow-y: auto;
|
|
576
669
|
animation: dropupAnimation 0.2s ease;
|
|
577
670
|
|
|
@@ -606,6 +699,7 @@
|
|
|
606
699
|
color: var(--custom-chatbox-gray-color);
|
|
607
700
|
}
|
|
608
701
|
|
|
702
|
+
&.selected:not(.cancel),
|
|
609
703
|
&:hover {
|
|
610
704
|
background-color: var(--custom-chatbox-toolkit-opt-active-color);
|
|
611
705
|
}
|
|
@@ -614,8 +708,6 @@
|
|
|
614
708
|
}
|
|
615
709
|
|
|
616
710
|
|
|
617
|
-
|
|
618
|
-
|
|
619
711
|
/* default questions */
|
|
620
712
|
.default-questions-title {
|
|
621
713
|
margin-bottom: .5rem;
|
|
@@ -643,8 +735,6 @@
|
|
|
643
735
|
}
|
|
644
736
|
|
|
645
737
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
738
|
|
|
649
739
|
|
|
650
740
|
}
|
|
@@ -51,6 +51,7 @@ export interface FloatingButton {
|
|
|
51
51
|
value: string;
|
|
52
52
|
onClick: string;
|
|
53
53
|
isSelect?: boolean; // Mark whether it is a drop-down selection button
|
|
54
|
+
dynamicOptions?: boolean; // Mark whether to use dynamic options
|
|
54
55
|
[key: string]: any; // Allows dynamic `onSelect__<number>` attributes, such as `onSelect__1`, `onSelect__2`, ...
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -114,10 +115,14 @@ export type ChatboxProps = {
|
|
|
114
115
|
newChatButton?: FloatingButton;
|
|
115
116
|
customMethods?: CustomMethod[]; // [{"name": "method1", "func": "() => { console.log('test'); }"}, ...]
|
|
116
117
|
defaultQuestions?: QuestionData;
|
|
118
|
+
showCopyBtn?: boolean; // Whether to show copy button for each reply
|
|
119
|
+
autoCopyReply?: boolean; // Whether to automatically copy reply to clipboard
|
|
117
120
|
customRequest?: CustomRequestFunction;
|
|
118
121
|
renderParser?: (input: string) => Promise<string>;
|
|
119
122
|
requestBodyFormatter?: (body: any, contextData: Record<string, any>, conversationHistory: MessageDetail[]) => Promise<Record<string, any>>;
|
|
123
|
+
copiedContentFormatter?: (string: string) => string;
|
|
120
124
|
nameFormatter?: (input: string) => string;
|
|
125
|
+
onCopyCallback?: (res: Record<string, any>) => void;
|
|
121
126
|
onQuestionClick?: (text: string, methods: Record<string, Function>) => void;
|
|
122
127
|
onInputChange?: (controlRef: React.RefObject<any>, val: string) => any;
|
|
123
128
|
onInputCallback?: (input: string) => Promise<string>;
|
|
@@ -201,8 +206,10 @@ const Chatbox = (props: ChatboxProps) => {
|
|
|
201
206
|
setShow(false);
|
|
202
207
|
},
|
|
203
208
|
clearData: () => {
|
|
204
|
-
|
|
209
|
+
// Update both the conversation history and displayed messages
|
|
205
210
|
conversationHistory.current = [];
|
|
211
|
+
setMsgList([]);
|
|
212
|
+
|
|
206
213
|
},
|
|
207
214
|
sendMsg: () => {
|
|
208
215
|
handleClickSafe();
|
|
@@ -214,6 +221,11 @@ const Chatbox = (props: ChatboxProps) => {
|
|
|
214
221
|
conversationHistory.current = conversationHistory.current.slice(-maxLength);
|
|
215
222
|
}
|
|
216
223
|
},
|
|
224
|
+
setHistory: (messages: MessageDetail[]) => {
|
|
225
|
+
// Update both the conversation history and displayed messages
|
|
226
|
+
conversationHistory.current = [...messages];
|
|
227
|
+
setMsgList([...messages]);
|
|
228
|
+
},
|
|
217
229
|
setVal: (v: string) => {
|
|
218
230
|
if (inputContentRef.current) inputContentRef.current.set(v);
|
|
219
231
|
},
|
|
@@ -289,9 +301,13 @@ const Chatbox = (props: ChatboxProps) => {
|
|
|
289
301
|
maxHistoryLength,
|
|
290
302
|
customRequest,
|
|
291
303
|
onQuestionClick,
|
|
304
|
+
onCopyCallback,
|
|
292
305
|
renderParser,
|
|
293
306
|
requestBodyFormatter,
|
|
307
|
+
copiedContentFormatter,
|
|
294
308
|
nameFormatter,
|
|
309
|
+
showCopyBtn,
|
|
310
|
+
autoCopyReply,
|
|
295
311
|
onInputChange,
|
|
296
312
|
onInputCallback,
|
|
297
313
|
onChunk,
|
|
@@ -363,9 +379,13 @@ const Chatbox = (props: ChatboxProps) => {
|
|
|
363
379
|
newChatButton,
|
|
364
380
|
customRequest,
|
|
365
381
|
onQuestionClick,
|
|
382
|
+
onCopyCallback,
|
|
366
383
|
renderParser,
|
|
367
384
|
requestBodyFormatter,
|
|
385
|
+
copiedContentFormatter,
|
|
368
386
|
nameFormatter,
|
|
387
|
+
showCopyBtn,
|
|
388
|
+
autoCopyReply,
|
|
369
389
|
onInputChange,
|
|
370
390
|
onInputCallback,
|
|
371
391
|
onChunk,
|
|
@@ -386,6 +406,57 @@ const Chatbox = (props: ChatboxProps) => {
|
|
|
386
406
|
|
|
387
407
|
}
|
|
388
408
|
|
|
409
|
+
//================================================================
|
|
410
|
+
// Clipboard
|
|
411
|
+
//================================================================
|
|
412
|
+
const chatboxCopyToClipboard = async (text: string) => {
|
|
413
|
+
|
|
414
|
+
let _content: string = text;
|
|
415
|
+
if (typeof args().copiedContentFormatter === 'function') {
|
|
416
|
+
_content = args().copiedContentFormatter(text);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
try {
|
|
420
|
+
// Try using the modern Clipboard API first
|
|
421
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
422
|
+
await navigator.clipboard.writeText(_content);
|
|
423
|
+
args().onCopyCallback?.({
|
|
424
|
+
success: true,
|
|
425
|
+
message: 'Text copied to clipboard',
|
|
426
|
+
});
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Fallback for older browsers
|
|
431
|
+
const textArea = document.createElement('textarea');
|
|
432
|
+
textArea.value = _content;
|
|
433
|
+
textArea.style.position = 'fixed';
|
|
434
|
+
textArea.style.left = '-999999px';
|
|
435
|
+
textArea.style.top = '-999999px';
|
|
436
|
+
document.body.appendChild(textArea);
|
|
437
|
+
textArea.focus();
|
|
438
|
+
textArea.select();
|
|
439
|
+
|
|
440
|
+
try {
|
|
441
|
+
document.execCommand('copy');
|
|
442
|
+
textArea.remove();
|
|
443
|
+
args().onCopyCallback?.({
|
|
444
|
+
success: true,
|
|
445
|
+
message: 'Text copied to clipboard',
|
|
446
|
+
});
|
|
447
|
+
return true;
|
|
448
|
+
} catch (err) {
|
|
449
|
+
textArea.remove();
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
} catch (err) {
|
|
453
|
+
args().onCopyCallback?.({
|
|
454
|
+
success: false,
|
|
455
|
+
message: `Failed to copy text: ${err}`,
|
|
456
|
+
});
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
};
|
|
389
460
|
|
|
390
461
|
|
|
391
462
|
//================================================================
|
|
@@ -427,16 +498,28 @@ const Chatbox = (props: ChatboxProps) => {
|
|
|
427
498
|
return newState;
|
|
428
499
|
});
|
|
429
500
|
};
|
|
430
|
-
const executeButtonAction = (actionStr: string, buttonId: string, buttonElement: HTMLButtonElement) => {
|
|
501
|
+
const executeButtonAction = async (actionStr: string, buttonId: string, buttonElement: HTMLButtonElement) => {
|
|
431
502
|
try {
|
|
432
|
-
// Create a new function to execute
|
|
433
503
|
const actionFn = new Function('method', 'isActive', 'button', actionStr);
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
504
|
+
|
|
505
|
+
// !!!REQUIRED "await"
|
|
506
|
+
// "customMethods" may be asynchronous
|
|
507
|
+
const result = await actionFn(exposedMethods(), !activeButtons[buttonId], buttonElement);
|
|
508
|
+
|
|
509
|
+
// If the returned result is an array, it is a dynamic option
|
|
510
|
+
if (Array.isArray(result) && Object.keys(dynamicOptions).length === 0) {
|
|
511
|
+
const options: FloatingButtonSelectOption[] = result.map(item => {
|
|
512
|
+
const [key, value] = Object.entries(item)[0];
|
|
513
|
+
const [label, val, onClick] = (value as string).split('{#}').map((s: string) => s.trim());
|
|
514
|
+
return { label, value: val, onClick };
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
// Update dynamic options
|
|
518
|
+
setDynamicOptions(prev => ({
|
|
519
|
+
...prev,
|
|
520
|
+
[buttonId]: options
|
|
521
|
+
}));
|
|
438
522
|
}
|
|
439
|
-
*/
|
|
440
523
|
|
|
441
524
|
// Update the button status
|
|
442
525
|
const newState = !activeButtons[buttonId];
|
|
@@ -445,17 +528,28 @@ const Chatbox = (props: ChatboxProps) => {
|
|
|
445
528
|
[buttonId]: newState
|
|
446
529
|
}));
|
|
447
530
|
|
|
448
|
-
return
|
|
449
|
-
|
|
450
|
-
|
|
531
|
+
return result;
|
|
451
532
|
} catch (error) {
|
|
452
533
|
console.error('Error executing button action:', error);
|
|
453
534
|
}
|
|
454
535
|
};
|
|
536
|
+
|
|
537
|
+
|
|
455
538
|
|
|
456
539
|
// options
|
|
457
540
|
const [selectedOpt, setSelectedOpt] = useState<Record<string, string | number>>({});
|
|
458
|
-
|
|
541
|
+
// Store dynamic options
|
|
542
|
+
const [dynamicOptions, setDynamicOptions] = useState<Record<string, FloatingButtonSelectOption[]>>({});
|
|
543
|
+
|
|
544
|
+
const getButtonOptions = (btn: FloatingButton, buttonId: string): FloatingButtonSelectOption[] => {
|
|
545
|
+
// If you are using the dynamic option and already have a cache, return the option for caching
|
|
546
|
+
//---------
|
|
547
|
+
if (btn.dynamicOptions && dynamicOptions[buttonId]) {
|
|
548
|
+
return dynamicOptions[buttonId];
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Use the static option from "props"
|
|
552
|
+
//---------
|
|
459
553
|
const options: FloatingButtonSelectOption[] = [];
|
|
460
554
|
let index = 1;
|
|
461
555
|
|
|
@@ -790,6 +884,11 @@ const Chatbox = (props: ChatboxProps) => {
|
|
|
790
884
|
// Update the message list state
|
|
791
885
|
setMsgList((prevMessages) => [...prevMessages, newMessage]);
|
|
792
886
|
|
|
887
|
+
// Auto copy reply if enabled
|
|
888
|
+
if (args().autoCopyReply && sender === args().answerNameRes) {
|
|
889
|
+
chatboxCopyToClipboard(content);
|
|
890
|
+
}
|
|
891
|
+
|
|
793
892
|
};
|
|
794
893
|
|
|
795
894
|
const sendMessage = async () => {
|
|
@@ -1125,7 +1224,10 @@ const Chatbox = (props: ChatboxProps) => {
|
|
|
1125
1224
|
}
|
|
1126
1225
|
}, [props.defaultMessages]);
|
|
1127
1226
|
|
|
1128
|
-
|
|
1227
|
+
useEffect(() => {
|
|
1228
|
+
// Bind chatboxCopyToClipboard to window so it can be called in HTML code
|
|
1229
|
+
(window as any).chatboxCopyToClipboard = chatboxCopyToClipboard;
|
|
1230
|
+
}, []);
|
|
1129
1231
|
|
|
1130
1232
|
return (
|
|
1131
1233
|
<>
|
|
@@ -1190,39 +1292,42 @@ const Chatbox = (props: ChatboxProps) => {
|
|
|
1190
1292
|
|
|
1191
1293
|
</> : null}
|
|
1192
1294
|
{/**------------- /NO DATA -------------*/}
|
|
1193
|
-
|
|
1194
|
-
|
|
1295
|
+
|
|
1195
1296
|
|
|
1196
1297
|
{/**------------- MESSAGES LIST -------------*/}
|
|
1197
1298
|
<div className="messages" ref={msgContainerRef}>
|
|
1198
1299
|
|
|
1199
1300
|
{msgList.map((msg, index) => {
|
|
1200
1301
|
|
|
1302
|
+
const copyTargetId = `${args().prefix || 'custom-'}chatbox-content--${chatId}${index}`;
|
|
1201
1303
|
const isAnimProgress = tempAnimText !== '' && msg.sender !== args().questionNameRes && index === msgList.length - 1 && loading;
|
|
1202
1304
|
const hasAnimated = animatedMessagesRef.current.has(index);
|
|
1203
1305
|
|
|
1204
1306
|
// Mark the message as animated;
|
|
1205
1307
|
animatedMessagesRef.current.add(index);
|
|
1206
1308
|
|
|
1309
|
+
const timeShow = `<span class="qa-timestamp">${msg.timestamp}</span>${args().showCopyBtn && msg.tag?.indexOf('[reply]') >= 0 ?(`<button class="copy-btn" onclick="window.chatboxCopyToClipboard(document.querySelector('#${copyTargetId} .qa-content-inner').innerHTML)"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M8 4v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7.242a2 2 0 0 0-.602-1.43L16.083 2.57A2 2 0 0 0 14.685 2H10a2 2 0 0 0-2 2z"/><path d="M16 18v2a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h2"/></svg></button>`) : ''}`;
|
|
1310
|
+
|
|
1207
1311
|
return <div key={index} className={msg.tag?.indexOf('[reply]') < 0 ? 'request' : 'reply'} style={{ display: isAnimProgress ? 'none' : '' }}>
|
|
1208
1312
|
<div className="qa-name" dangerouslySetInnerHTML={{ __html: `${msg.sender}` }}></div>
|
|
1209
1313
|
|
|
1314
|
+
|
|
1210
1315
|
{msg.sender === args().questionNameRes ? <>
|
|
1211
|
-
<div className="qa-content" dangerouslySetInnerHTML={{ __html:
|
|
1316
|
+
<div className="qa-content" id={copyTargetId} dangerouslySetInnerHTML={{ __html: `<div class="qa-content-inner">${msg.content}</div> ${timeShow}` }}></div>
|
|
1212
1317
|
</> : <>
|
|
1213
1318
|
|
|
1214
1319
|
{enableStreamMode ? <>
|
|
1215
|
-
<div className="qa-content" dangerouslySetInnerHTML={{ __html:
|
|
1320
|
+
<div className="qa-content" id={copyTargetId} dangerouslySetInnerHTML={{ __html: `<div class="qa-content-inner">${msg.content}</div> ${timeShow}` }}></div>
|
|
1216
1321
|
</> : <>
|
|
1217
|
-
<div className="qa-content">
|
|
1322
|
+
<div className="qa-content" id={copyTargetId}>
|
|
1218
1323
|
{hasAnimated ? (
|
|
1219
|
-
<div dangerouslySetInnerHTML={{ __html:
|
|
1324
|
+
<div dangerouslySetInnerHTML={{ __html: `<div class="qa-content-inner">${msg.content}</div> ${timeShow}` }}></div>
|
|
1220
1325
|
) : (
|
|
1221
1326
|
<TypingEffect
|
|
1222
1327
|
onUpdate={() => {
|
|
1223
1328
|
scrollToBottom();
|
|
1224
1329
|
}}
|
|
1225
|
-
content={
|
|
1330
|
+
content={`<div class="qa-content-inner">${msg.content}</div> ${timeShow}`}
|
|
1226
1331
|
speed={10}
|
|
1227
1332
|
/>
|
|
1228
1333
|
)}
|
|
@@ -1431,7 +1536,7 @@ const Chatbox = (props: ChatboxProps) => {
|
|
|
1431
1536
|
|
|
1432
1537
|
|
|
1433
1538
|
{/**------------- SEND LOADING -------------*/}
|
|
1434
|
-
{args().sendLoading ? <div className="loading"><div style={{ display: loading ? 'block' : 'none' }}><PureLoader customClassName="w-100" txt="" /></div></div> : null}
|
|
1539
|
+
{args().sendLoading ? <div className="loading"><div style={{ display: loading ? 'block' : 'none' }}><PureLoader prefix={args().prefix} customClassName="w-100" txt="" /></div></div> : null}
|
|
1435
1540
|
{/**------------- /SEND LOADING -------------*/}
|
|
1436
1541
|
|
|
1437
1542
|
|
|
@@ -1443,8 +1548,8 @@ const Chatbox = (props: ChatboxProps) => {
|
|
|
1443
1548
|
const isActive = activeButtons[_id];
|
|
1444
1549
|
|
|
1445
1550
|
if (btn.isSelect) {
|
|
1446
|
-
const options = getButtonOptions(btn);
|
|
1447
|
-
|
|
1551
|
+
const options = getButtonOptions(btn, _id);
|
|
1552
|
+
|
|
1448
1553
|
return (
|
|
1449
1554
|
<div key={index} className="toolkit-select-wrapper">
|
|
1450
1555
|
<button
|
|
@@ -1456,6 +1561,9 @@ const Chatbox = (props: ChatboxProps) => {
|
|
|
1456
1561
|
...prev,
|
|
1457
1562
|
[_id]: !prev[_id]
|
|
1458
1563
|
}));
|
|
1564
|
+
|
|
1565
|
+
//
|
|
1566
|
+
executeButtonAction(btn.onClick, _id, e.currentTarget);
|
|
1459
1567
|
}}
|
|
1460
1568
|
>
|
|
1461
1569
|
<span dangerouslySetInnerHTML={{
|
|
@@ -1474,18 +1582,23 @@ const Chatbox = (props: ChatboxProps) => {
|
|
|
1474
1582
|
</svg></span>
|
|
1475
1583
|
</button>
|
|
1476
1584
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1585
|
+
{/* OPTIONS */}
|
|
1479
1586
|
<div className={`toolkit-select-options ${isActive ? 'active' : ''}`}>
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1587
|
+
|
|
1588
|
+
{options.length > 0 ? <>
|
|
1589
|
+
{options.map((option: FloatingButtonSelectOption, optIndex: number) => (
|
|
1590
|
+
<div
|
|
1591
|
+
key={optIndex}
|
|
1592
|
+
className={`toolkit-select-option ${option.value || ''} ${selectedOpt.curIndex === optIndex ? 'selected' : ''}`}
|
|
1593
|
+
onClick={() => handleExecuteButtonSelect(_id, option, optIndex, option.value)}
|
|
1594
|
+
>
|
|
1595
|
+
<span dangerouslySetInnerHTML={{ __html: option.label }}></span>
|
|
1596
|
+
</div>
|
|
1597
|
+
))}
|
|
1598
|
+
</> : <>
|
|
1599
|
+
<div className={`${args().prefix || 'custom-'}chatbox-mini-loader`}></div>
|
|
1600
|
+
</>}
|
|
1601
|
+
|
|
1489
1602
|
</div>
|
|
1490
1603
|
</div>
|
|
1491
1604
|
);
|
|
@@ -1497,8 +1610,7 @@ const Chatbox = (props: ChatboxProps) => {
|
|
|
1497
1610
|
key={index}
|
|
1498
1611
|
id={_id}
|
|
1499
1612
|
className={`${btn.value || ''} ${isActive ? 'active' : ''}`}
|
|
1500
|
-
onClick={(e: React.MouseEvent<HTMLButtonElement>) =>
|
|
1501
|
-
executeButtonAction(btn.onClick, _id, e.currentTarget)}
|
|
1613
|
+
onClick={(e: React.MouseEvent<HTMLButtonElement>) => executeButtonAction(btn.onClick, _id, e.currentTarget)}
|
|
1502
1614
|
>
|
|
1503
1615
|
<span dangerouslySetInnerHTML={{ __html: btn.label }}></span>
|
|
1504
1616
|
</button>
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// app.ts
|
|
2
2
|
import type { ChatboxProps } from '../index';
|
|
3
3
|
|
|
4
|
+
export interface HtmlTagPlaceholder {
|
|
5
|
+
original: string;
|
|
6
|
+
placeholder: string;
|
|
7
|
+
type: 'table' | 'img' | 'svg';
|
|
8
|
+
}
|
|
4
9
|
|
|
5
10
|
export function isValidJSON(str: string){
|
|
6
11
|
try {
|
|
@@ -13,7 +18,7 @@ export function isValidJSON(str: string){
|
|
|
13
18
|
|
|
14
19
|
export function formatLatestDisplayContent(str: string) {
|
|
15
20
|
// Regular expression to match <details> tags and their content
|
|
16
|
-
|
|
21
|
+
let output = str.replace(/<details class="think"[^>]*>([\s\S]*?)<\/details>/g, (match, content) => {
|
|
17
22
|
// Use regex to match the content inside the "div.think-content"
|
|
18
23
|
const thinkContentMatch = content.match(/<div class="think-content">([\s\S]*?)<\/div>/);
|
|
19
24
|
|
|
@@ -28,6 +33,12 @@ export function formatLatestDisplayContent(str: string) {
|
|
|
28
33
|
|
|
29
34
|
return match; // If not empty, return the original matched content
|
|
30
35
|
});
|
|
36
|
+
|
|
37
|
+
// Then handle tables without is-init class
|
|
38
|
+
output = output.replace(/<table(?![^>]*\bis-init\b)([^>]*)>([\s\S]*?)<\/table>/g, (match, attributes, content) => {
|
|
39
|
+
// Add is-init class to table and wrap it in container div
|
|
40
|
+
return `<div class="table-container"><table class="is-init"${attributes}>${content}</table></div>`;
|
|
41
|
+
});
|
|
31
42
|
|
|
32
43
|
return output;
|
|
33
44
|
}
|
|
@@ -127,3 +138,43 @@ export function isStreamResponse(response: Response): boolean {
|
|
|
127
138
|
return response.body instanceof ReadableStream;
|
|
128
139
|
};
|
|
129
140
|
|
|
141
|
+
|
|
142
|
+
export function extractHtmlTags(html: string): { processedHtml: string; placeholders: HtmlTagPlaceholder[] } {
|
|
143
|
+
const placeholders: HtmlTagPlaceholder[] = [];
|
|
144
|
+
let processedHtml = html;
|
|
145
|
+
|
|
146
|
+
// <table>
|
|
147
|
+
processedHtml = processedHtml.replace(/<table[^>]*>[\s\S]*?<\/table>/g, (match) => {
|
|
148
|
+
const placeholder = `[TABLE_${placeholders.length}]`;
|
|
149
|
+
placeholders.push({
|
|
150
|
+
original: `<div class="table-container">${match?.replace('<table', '<table class="is-init"')}</div>`,
|
|
151
|
+
placeholder,
|
|
152
|
+
type: 'table'
|
|
153
|
+
});
|
|
154
|
+
return placeholder;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// <img>
|
|
158
|
+
processedHtml = processedHtml.replace(/<img[^>]*>/g, (match) => {
|
|
159
|
+
const placeholder = `[IMG_${placeholders.length}]`;
|
|
160
|
+
placeholders.push({
|
|
161
|
+
original: match,
|
|
162
|
+
placeholder,
|
|
163
|
+
type: 'img'
|
|
164
|
+
});
|
|
165
|
+
return placeholder;
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// <svg>
|
|
169
|
+
processedHtml = processedHtml.replace(/<svg[^>]*>[\s\S]*?<\/svg>/g, (match) => {
|
|
170
|
+
const placeholder = `[SVG_${placeholders.length}]`;
|
|
171
|
+
placeholders.push({
|
|
172
|
+
original: match,
|
|
173
|
+
placeholder,
|
|
174
|
+
type: 'svg'
|
|
175
|
+
});
|
|
176
|
+
return placeholder;
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
return { processedHtml, placeholders };
|
|
180
|
+
};
|