yt-chat-components 2.0.2 → 2.0.3
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/package.json
CHANGED
|
@@ -196,16 +196,13 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({
|
|
|
196
196
|
const [receivingMessage, setReceivingMessage] = useState(false);
|
|
197
197
|
const receivingMessageRef = useRef(receivingMessage);
|
|
198
198
|
const abortControllerRef = useRef(new AbortController());
|
|
199
|
-
const [fileList, setFileList] = useState<
|
|
200
|
-
|
|
201
|
-
>([]);
|
|
202
|
-
const scrollContainerRef = useRef(null);
|
|
199
|
+
const [fileList, setFileList] = useState<{ file: File; fileUrl: string; fileType: string; fileId: string, fileName: string }[]>([]);
|
|
200
|
+
const fileScrollContainerRef = useRef(null);
|
|
203
201
|
const sessionIdRef = useRef(null);
|
|
204
202
|
const [showLeftArrow, setShowLeftArrow] = useState(false); // 控制左侧箭头显示
|
|
205
203
|
const [showRightArrow, setShowRightArrow] = useState(false); // 控制右侧箭头显示
|
|
206
204
|
const isStream = true;//是否流式输出(手动开关)
|
|
207
205
|
const [recordState, setRecordState] = useState(false); // 录音状态。true为正在录音,false为停止录音
|
|
208
|
-
const [tagList, setTagList] = useState([]); // 问题标签列表
|
|
209
206
|
const {isTitleSideIcon, logoWidth, agentUrl, stopMessageUrl, sendMessageUrl, speakUrl, callUrl, smartBoyUrl, wonderBoyUrl, isHideTabSelector, customAiToldYou} = baseConfig;
|
|
210
207
|
const [inputContainerHeight, setInputContainerHeight] = useState('120px')
|
|
211
208
|
let content_id: string = null
|
|
@@ -216,14 +213,15 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({
|
|
|
216
213
|
let voiceChunks = []; // 临时存储录制的语音片段
|
|
217
214
|
// 滚动事件处理,选择文件时,文件内容超出显示框时,显示左右箭头
|
|
218
215
|
const messageContainerRef = useRef<HTMLDivElement>(null);
|
|
216
|
+
const [isUserScrolledUp, setIsUserScrolledUp] = useState(false);
|
|
219
217
|
|
|
220
218
|
useImperativeHandle(ref, () => ({
|
|
221
219
|
handleCallButtonClick:handleCallButtonClick
|
|
222
220
|
}))
|
|
223
221
|
|
|
224
|
-
const
|
|
225
|
-
if (
|
|
226
|
-
const { scrollLeft, clientWidth, scrollWidth } =
|
|
222
|
+
const handleFileScroll = () => {
|
|
223
|
+
if (fileScrollContainerRef.current) {
|
|
224
|
+
const { scrollLeft, clientWidth, scrollWidth } = fileScrollContainerRef.current;
|
|
227
225
|
|
|
228
226
|
// 判断内容是否超出容器
|
|
229
227
|
const isContentOverflowed = scrollWidth > clientWidth;
|
|
@@ -236,6 +234,50 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({
|
|
|
236
234
|
}
|
|
237
235
|
};
|
|
238
236
|
|
|
237
|
+
/**
|
|
238
|
+
* 监听用户滚动标志,一旦出现滚动事件就会执行判断
|
|
239
|
+
* 若处于底部且之前激活向上滚动,则使向上滚动标志失效
|
|
240
|
+
* 若不处于底部且未向上滚动,则激活向上滚动标志
|
|
241
|
+
* */
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
const container = messageContainerRef.current;
|
|
244
|
+
if (!container) return;
|
|
245
|
+
|
|
246
|
+
const handleScroll = () => {
|
|
247
|
+
const { scrollTop, scrollHeight, clientHeight } = container;
|
|
248
|
+
const isAtBottom = scrollHeight - scrollTop <= clientHeight + 10; // <= 更稳妥
|
|
249
|
+
|
|
250
|
+
if (!isAtBottom && !isUserScrolledUp) {
|
|
251
|
+
setIsUserScrolledUp(true);
|
|
252
|
+
} else if (isAtBottom && isUserScrolledUp) {
|
|
253
|
+
setIsUserScrolledUp(false);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
container.addEventListener('scroll', handleScroll);
|
|
258
|
+
return () => container.removeEventListener('scroll', handleScroll);
|
|
259
|
+
}, [isUserScrolledUp]);
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 判断是否需要滚动到底部
|
|
264
|
+
* */
|
|
265
|
+
const judgeToScrollEnd = (isForce) => {
|
|
266
|
+
const container = messageContainerRef.current;
|
|
267
|
+
if (!container) return;
|
|
268
|
+
|
|
269
|
+
const { scrollTop, scrollHeight, clientHeight } = container;
|
|
270
|
+
const isCurrentlyAtBottom = scrollHeight - scrollTop - clientHeight < 120; //
|
|
271
|
+
if (isForce) {
|
|
272
|
+
container.scrollTop = scrollHeight;
|
|
273
|
+
setIsUserScrolledUp(false); // 强制滚动后认为用户在底部
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (isCurrentlyAtBottom) {
|
|
277
|
+
container.scrollTop = scrollHeight;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
|
|
239
281
|
// 处理延时队列,因为后端返回的是 token token form token token.....会导致form渲染在token前面,所以用这个方式
|
|
240
282
|
// 当处理token的时候,form的信息放到这里,然后使用 delayMessageTimer 处理这个延时队列
|
|
241
283
|
// 处理逻辑是:收到 token 的时候,重置timer,由于timer一直被重置所以不会处理 delayMessageList,直到 1.5内没有新 token 才会处理延时队列
|
|
@@ -372,7 +414,7 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({
|
|
|
372
414
|
}
|
|
373
415
|
|
|
374
416
|
if (lastMessage.current) {
|
|
375
|
-
|
|
417
|
+
setTimeout(()=>judgeToScrollEnd(),100)
|
|
376
418
|
}
|
|
377
419
|
}
|
|
378
420
|
else if (event == 't_token') {
|
|
@@ -389,7 +431,7 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({
|
|
|
389
431
|
}
|
|
390
432
|
|
|
391
433
|
if (lastMessage.current) {
|
|
392
|
-
|
|
434
|
+
setTimeout(()=>judgeToScrollEnd(),100)
|
|
393
435
|
}
|
|
394
436
|
}
|
|
395
437
|
else if (event == 'status') {
|
|
@@ -627,7 +669,7 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({
|
|
|
627
669
|
});
|
|
628
670
|
}
|
|
629
671
|
setFileList([]);
|
|
630
|
-
|
|
672
|
+
handleFileScroll();
|
|
631
673
|
sendMessage(
|
|
632
674
|
input_value_type,
|
|
633
675
|
embedAppExtend,
|
|
@@ -711,7 +753,7 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({
|
|
|
711
753
|
setTimeout(() => {
|
|
712
754
|
if (lastMessage.current) {
|
|
713
755
|
console.error('开始滚动最后一条消息到可视区域')
|
|
714
|
-
|
|
756
|
+
judgeToScrollEnd();
|
|
715
757
|
}
|
|
716
758
|
}, 600);
|
|
717
759
|
};
|
|
@@ -721,9 +763,9 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({
|
|
|
721
763
|
* @param file File 对象
|
|
722
764
|
*/
|
|
723
765
|
const getFileType = (file) => {
|
|
724
|
-
if (!(file instanceof File)) {
|
|
725
|
-
|
|
726
|
-
}
|
|
766
|
+
// if (!(file instanceof File)) {
|
|
767
|
+
// throw new Error('Input must be a File object');
|
|
768
|
+
// }
|
|
727
769
|
|
|
728
770
|
const mimeType = file.type;
|
|
729
771
|
|
|
@@ -787,7 +829,7 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({
|
|
|
787
829
|
.then(res => {
|
|
788
830
|
setFileList(fileList.filter((item, i) => i !== index));
|
|
789
831
|
})
|
|
790
|
-
|
|
832
|
+
handleFileScroll();
|
|
791
833
|
};
|
|
792
834
|
|
|
793
835
|
/**
|
|
@@ -862,7 +904,7 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({
|
|
|
862
904
|
: item
|
|
863
905
|
)
|
|
864
906
|
);
|
|
865
|
-
|
|
907
|
+
handleFileScroll();
|
|
866
908
|
})
|
|
867
909
|
.catch((e) => {
|
|
868
910
|
setFileList((prevFileList) =>
|
|
@@ -889,12 +931,12 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({
|
|
|
889
931
|
// 向右滚动方法
|
|
890
932
|
const handleScrollRight = (direction) => {
|
|
891
933
|
if (direction == 'right') {
|
|
892
|
-
if (
|
|
893
|
-
|
|
934
|
+
if (fileScrollContainerRef.current) {
|
|
935
|
+
fileScrollContainerRef.current.scrollLeft += 200;
|
|
894
936
|
}
|
|
895
937
|
} else if (direction == 'left') {
|
|
896
|
-
if (
|
|
897
|
-
|
|
938
|
+
if (fileScrollContainerRef.current) {
|
|
939
|
+
fileScrollContainerRef.current.scrollLeft -= 200;
|
|
898
940
|
}
|
|
899
941
|
}
|
|
900
942
|
};
|
|
@@ -1006,21 +1048,12 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({
|
|
|
1006
1048
|
};
|
|
1007
1049
|
|
|
1008
1050
|
/**
|
|
1009
|
-
*
|
|
1010
|
-
*/
|
|
1011
|
-
useEffect(() => {
|
|
1012
|
-
if (lastMessage.current) lastMessage.current.scrollIntoView({ behavior: 'smooth' });
|
|
1013
|
-
}, [nowAIContentList]);
|
|
1014
|
-
|
|
1015
|
-
/* Refocus the User input whenever a new response is returned from the LLM */
|
|
1016
|
-
|
|
1051
|
+
* 当前对话历史消息发生变化时,强制滚动到底部
|
|
1052
|
+
* */
|
|
1017
1053
|
useEffect(() => {
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
// inputRef.current?.focus();
|
|
1022
|
-
}, 100);
|
|
1023
|
-
}, [messages, open, tags]);
|
|
1054
|
+
judgeToScrollEnd(true);
|
|
1055
|
+
setIsUserScrolledUp(false);
|
|
1056
|
+
}, [messages]);
|
|
1024
1057
|
|
|
1025
1058
|
const fetchChatHistory = async () => {
|
|
1026
1059
|
try {
|
|
@@ -1063,14 +1096,22 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({
|
|
|
1063
1096
|
}
|
|
1064
1097
|
|
|
1065
1098
|
// 处理最后的 ChatMessageType
|
|
1066
|
-
let latestMessage: ChatMessageType= finalChatHistoryList[finalChatHistoryList.length - 1]
|
|
1099
|
+
let latestMessage: ChatMessageType= finalChatHistoryList[finalChatHistoryList.length - 1];
|
|
1067
1100
|
latestMessage.messageItemList.push({
|
|
1068
1101
|
id: id,
|
|
1069
1102
|
name: sender_name,
|
|
1070
1103
|
message: text,
|
|
1071
1104
|
thinkMessage: reasoning_content,
|
|
1072
1105
|
// rawInfo: data,
|
|
1073
|
-
rawInfo: {
|
|
1106
|
+
rawInfo: {
|
|
1107
|
+
files: files.map((fileItem) => ({
|
|
1108
|
+
fileUrl: fileItem.url,
|
|
1109
|
+
name: fileItem.name,
|
|
1110
|
+
size: fileItem.size,
|
|
1111
|
+
fileType: getFileType(fileItem)
|
|
1112
|
+
}))
|
|
1113
|
+
},
|
|
1114
|
+
|
|
1074
1115
|
type: MessageType.text,
|
|
1075
1116
|
});
|
|
1076
1117
|
|
|
@@ -1337,7 +1378,7 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({
|
|
|
1337
1378
|
}
|
|
1338
1379
|
|
|
1339
1380
|
const inputNode = <div style={input_container_style} className="cl-input_container">
|
|
1340
|
-
<div className="w_file_preview" ref={
|
|
1381
|
+
<div className="w_file_preview" ref={fileScrollContainerRef} onScroll={handleFileScroll}>
|
|
1341
1382
|
<div className="w_toLeftBox" style={{display: showLeftArrow ? 'flex' : 'none'}}>
|
|
1342
1383
|
<div className="w_toLeft" onClick={() => handleScrollRight('left')}>
|
|
1343
1384
|
<img src={toLeftPng}/>
|
|
@@ -1506,7 +1547,7 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({
|
|
|
1506
1547
|
abortControllerRef.current.abort('disconnect');
|
|
1507
1548
|
abortControllerRef.current = new AbortController();
|
|
1508
1549
|
} else {
|
|
1509
|
-
handleSendMessage()
|
|
1550
|
+
handleSendMessage();
|
|
1510
1551
|
}
|
|
1511
1552
|
}}
|
|
1512
1553
|
>
|
|
@@ -1527,55 +1568,7 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({
|
|
|
1527
1568
|
}
|
|
1528
1569
|
return (
|
|
1529
1570
|
<>
|
|
1530
|
-
|
|
1531
|
-
className="w_tagListClass"
|
|
1532
|
-
style={isMobile ?
|
|
1533
|
-
{
|
|
1534
|
-
display: tagList?.length > 0 ? '' : 'none',
|
|
1535
|
-
zIndex: tagList?.length > 0 ? '2' : '-1',
|
|
1536
|
-
maxWidth: 'calc(100vw - 3.2rem - 24px)',
|
|
1537
|
-
flexWrap: "nowrap",
|
|
1538
|
-
overflowX: "auto",
|
|
1539
|
-
WebkitOverflowScrolling: 'touch',
|
|
1540
|
-
scrollbarWidth: "none",
|
|
1541
|
-
overflowY: "hidden",
|
|
1542
|
-
background: 'transparent',
|
|
1543
|
-
padding: '8px 0 8px 0',
|
|
1544
|
-
marginLeft: 10,
|
|
1545
|
-
} : {
|
|
1546
|
-
// bottom: fileList.length > 0 ? '130px' : '80px',
|
|
1547
|
-
display: tagList?.length > 0 ? '' : 'none',
|
|
1548
|
-
zIndex: tagList?.length > 0 ? '2' : '-1',
|
|
1549
|
-
}}
|
|
1550
|
-
>
|
|
1551
|
-
{
|
|
1552
|
-
tagList.map((item, index) => (
|
|
1553
|
-
<div
|
|
1554
|
-
key={index}
|
|
1555
|
-
className="w_tagItemBox"
|
|
1556
|
-
onClick={() => {
|
|
1557
|
-
if (receivingMessage) {
|
|
1558
|
-
return
|
|
1559
|
-
}
|
|
1560
|
-
handleSendMessage(item?.name);
|
|
1561
|
-
setFileList([]);
|
|
1562
|
-
}}
|
|
1563
|
-
>
|
|
1564
|
-
<div>
|
|
1565
|
-
<img
|
|
1566
|
-
style={item?.img ? {} : {display: 'none'}}
|
|
1567
|
-
src={'https://trans-from-yuntu-resourse.oss-cn-beijing.aliyuncs.com/' + item?.img}
|
|
1568
|
-
className="w_tagImgh"
|
|
1569
|
-
alt="Image"
|
|
1570
|
-
/>
|
|
1571
|
-
</div>
|
|
1572
|
-
<div className="w_tagItemText"
|
|
1573
|
-
style={isMobile ? {wordBreak: "keep-all", whiteSpace: "nowrap",} : {}}>{item?.name}</div>
|
|
1574
|
-
</div>
|
|
1575
|
-
))
|
|
1576
|
-
}
|
|
1577
|
-
{inputNode}
|
|
1578
|
-
</div>
|
|
1571
|
+
|
|
1579
1572
|
</>
|
|
1580
1573
|
)
|
|
1581
1574
|
}
|