mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-26 23:44:19 +00:00
feat: add webchat Word-by-word output
fix:webchat on message stream bug
This commit is contained in:
@@ -42,9 +42,24 @@ export default function DebugDialog({
|
|||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const popoverRef = useRef<HTMLDivElement>(null);
|
const popoverRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const scrollToBottom = () => {
|
// const scrollToBottom = () => {
|
||||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
// messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||||
};
|
// };
|
||||||
|
|
||||||
|
const scrollToBottom = useCallback(() => {
|
||||||
|
// 使用setTimeout确保在DOM更新后执行滚动
|
||||||
|
setTimeout(() => {
|
||||||
|
const scrollArea = document.querySelector('.scroll-area') as HTMLElement;
|
||||||
|
if (scrollArea) {
|
||||||
|
scrollArea.scrollTo({
|
||||||
|
top: scrollArea.scrollHeight,
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 同时确保messagesEndRef也滚动到视图
|
||||||
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}, 0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const loadMessages = useCallback(
|
const loadMessages = useCallback(
|
||||||
async (pipelineId: string) => {
|
async (pipelineId: string) => {
|
||||||
@@ -60,10 +75,10 @@ export default function DebugDialog({
|
|||||||
},
|
},
|
||||||
[sessionType],
|
[sessionType],
|
||||||
);
|
);
|
||||||
|
// 在useEffect中监听messages变化时滚动
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}, [messages]);
|
}, [messages, scrollToBottom]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
@@ -175,7 +190,7 @@ export default function DebugDialog({
|
|||||||
const botMessage: Message = {
|
const botMessage: Message = {
|
||||||
id: -1,
|
id: -1,
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
content: '',
|
content: '生成中...',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
message_chain: [{ type: 'Plain', text: '' }],
|
message_chain: [{ type: 'Plain', text: '' }],
|
||||||
};
|
};
|
||||||
@@ -191,8 +206,9 @@ export default function DebugDialog({
|
|||||||
setHasAt(false);
|
setHasAt(false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const botMessageId = botMessage.id;
|
let fullContent = ''; // 保存完整内容
|
||||||
let accumulatedContent = '';
|
let displayContent = ''; // 当前显示内容
|
||||||
|
let typingInterval: NodeJS.Timeout;
|
||||||
|
|
||||||
await httpClient.sendStreamingWebChatMessage(
|
await httpClient.sendStreamingWebChatMessage(
|
||||||
sessionType,
|
sessionType,
|
||||||
@@ -201,40 +217,76 @@ export default function DebugDialog({
|
|||||||
(data) => {
|
(data) => {
|
||||||
// 处理流式响应数据
|
// 处理流式响应数据
|
||||||
if (data.message) {
|
if (data.message) {
|
||||||
accumulatedContent += data.message.content;
|
// 更新完整内容
|
||||||
|
fullContent = data.message.content;
|
||||||
|
|
||||||
// 更新bot消息
|
// 清除之前的打字效果
|
||||||
setMessages((prevMessages) => {
|
if (typingInterval) {
|
||||||
const updatedMessages = [...prevMessages];
|
clearInterval(typingInterval);
|
||||||
// const botMessageIndex = updatedMessages.findIndex(
|
}
|
||||||
// (msg) =>
|
|
||||||
// msg.id === botMessageId && msg.role === 'assistant',
|
|
||||||
// );
|
|
||||||
// 使用索引来更新消息,而不是id匹配
|
|
||||||
const botMessageIndex = updatedMessages.length - 1;
|
|
||||||
|
|
||||||
if (botMessageIndex !== -1) {
|
// 开始新的打字效果
|
||||||
const updatedBotMessage = {
|
let currentPos = displayContent.length;
|
||||||
...updatedMessages[botMessageIndex],
|
const targetContent = fullContent;
|
||||||
content: accumulatedContent,
|
|
||||||
message_chain: [
|
typingInterval = setInterval(() => {
|
||||||
{ type: 'Plain', text: accumulatedContent },
|
if (currentPos < targetContent.length) {
|
||||||
],
|
displayContent = targetContent.substring(0, currentPos + 1);
|
||||||
};
|
currentPos++;
|
||||||
updatedMessages[botMessageIndex] = updatedBotMessage;
|
|
||||||
|
// 更新bot消息
|
||||||
|
setMessages((prevMessages) => {
|
||||||
|
const updatedMessages = [...prevMessages];
|
||||||
|
const botMessageIndex = updatedMessages.length - 1;
|
||||||
|
|
||||||
|
if (botMessageIndex !== -1) {
|
||||||
|
const updatedBotMessage = {
|
||||||
|
...updatedMessages[botMessageIndex],
|
||||||
|
content: displayContent,
|
||||||
|
message_chain: [
|
||||||
|
{ type: 'Plain', text: displayContent },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
updatedMessages[botMessageIndex] = updatedBotMessage;
|
||||||
|
}
|
||||||
|
setTimeout(scrollToBottom, 0); // 确保在状态更新后滚动
|
||||||
|
return updatedMessages;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
clearInterval(typingInterval);
|
||||||
}
|
}
|
||||||
|
}, 30); // 调整这个值可以改变打字速度
|
||||||
return updatedMessages;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
// 流传输完成
|
// 流传输完成
|
||||||
console.log('Streaming completed');
|
console.log('Streaming completed');
|
||||||
|
if (typingInterval) {
|
||||||
|
clearInterval(typingInterval);
|
||||||
|
}
|
||||||
|
// 确保最终内容完全显示
|
||||||
|
setMessages((prevMessages) => {
|
||||||
|
const updatedMessages = [...prevMessages];
|
||||||
|
const botMessageIndex = updatedMessages.length - 1;
|
||||||
|
|
||||||
|
if (botMessageIndex !== -1) {
|
||||||
|
const updatedBotMessage = {
|
||||||
|
...updatedMessages[botMessageIndex],
|
||||||
|
content: fullContent,
|
||||||
|
message_chain: [{ type: 'Plain', text: fullContent }],
|
||||||
|
};
|
||||||
|
updatedMessages[botMessageIndex] = updatedBotMessage;
|
||||||
|
}
|
||||||
|
setTimeout(scrollToBottom, 0); // 确保在状态更新后滚动
|
||||||
|
return updatedMessages;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
// 处理错误
|
// 处理错误
|
||||||
console.error('Streaming error:', error);
|
console.error('Streaming error:', error);
|
||||||
|
if (typingInterval) {
|
||||||
|
clearInterval(typingInterval);
|
||||||
|
}
|
||||||
if (sessionType === 'person') {
|
if (sessionType === 'person') {
|
||||||
toast.error(t('pipelines.debugDialog.sendFailed'));
|
toast.error(t('pipelines.debugDialog.sendFailed'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -380,12 +380,12 @@ class HttpClient {
|
|||||||
|
|
||||||
// 处理完整的JSON对象
|
// 处理完整的JSON对象
|
||||||
const lines = buffer.split('\n\n');
|
const lines = buffer.split('\n\n');
|
||||||
buffer = '';
|
buffer = lines.pop() || '';
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (line.startsWith('data:')) {
|
if (line.startsWith('data:')) {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(line.slice(6));
|
const data = JSON.parse(line.slice(5));
|
||||||
|
|
||||||
if (data.type === 'end') {
|
if (data.type === 'end') {
|
||||||
// 流传输结束
|
// 流传输结束
|
||||||
|
|||||||
Reference in New Issue
Block a user