🛠️ fix(chat): enhance message generation stop behavior
This commit improves the handling of message generation interruption in the playground chat interface, ensuring both content and thinking process are properly preserved. Key changes: - Preserve existing content when stopping message generation - Handle both direct reasoningContent and <think> tag formats - Extract and merge thinking process from unclosed <think> tags - Maintain consistent thinking chain format with separators - Auto-collapse reasoning panel after stopping for cleaner UI Technical details: - Modified onStopGenerator to properly handle SSE connection closure - Added regex pattern to extract thinking content from <think> tags - Implemented proper state management for incomplete messages - Ensured all content types are preserved in their respective fields This fix resolves the issue where thinking chain content would be lost when stopping message generation mid-stream.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
import React, { useCallback, useContext, useEffect, useState, useRef } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { UserContext } from '../../context/User/index.js';
|
import { UserContext } from '../../context/User/index.js';
|
||||||
import {
|
import {
|
||||||
@@ -100,6 +100,7 @@ const Playground = () => {
|
|||||||
const [groups, setGroups] = useState([]);
|
const [groups, setGroups] = useState([]);
|
||||||
const [showSettings, setShowSettings] = useState(true);
|
const [showSettings, setShowSettings] = useState(true);
|
||||||
const [styleState, styleDispatch] = useContext(StyleContext);
|
const [styleState, styleDispatch] = useContext(StyleContext);
|
||||||
|
const sseSourceRef = useRef(null);
|
||||||
|
|
||||||
const handleInputChange = (name, value) => {
|
const handleInputChange = (name, value) => {
|
||||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||||
@@ -210,9 +211,13 @@ const Playground = () => {
|
|||||||
payload: JSON.stringify(payload),
|
payload: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 保存 source 引用以便后续停止生成
|
||||||
|
sseSourceRef.current = source;
|
||||||
|
|
||||||
source.addEventListener('message', (e) => {
|
source.addEventListener('message', (e) => {
|
||||||
if (e.data === '[DONE]') {
|
if (e.data === '[DONE]') {
|
||||||
source.close();
|
source.close();
|
||||||
|
sseSourceRef.current = null;
|
||||||
completeMessage();
|
completeMessage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -240,6 +245,7 @@ const Playground = () => {
|
|||||||
const errorMessage = e.data || t('请求发生错误');
|
const errorMessage = e.data || t('请求发生错误');
|
||||||
streamMessageUpdate(errorMessage, 'content');
|
streamMessageUpdate(errorMessage, 'content');
|
||||||
completeMessage('error');
|
completeMessage('error');
|
||||||
|
sseSourceRef.current = null;
|
||||||
source.close();
|
source.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -352,6 +358,51 @@ const Playground = () => {
|
|||||||
});
|
});
|
||||||
}, [setMessage]);
|
}, [setMessage]);
|
||||||
|
|
||||||
|
const onStopGenerator = useCallback(() => {
|
||||||
|
if (sseSourceRef.current) {
|
||||||
|
sseSourceRef.current.close();
|
||||||
|
sseSourceRef.current = null;
|
||||||
|
setMessage((prevMessage) => {
|
||||||
|
const lastMessage = prevMessage[prevMessage.length - 1];
|
||||||
|
if (lastMessage.status === 'loading' || lastMessage.status === 'incomplete') {
|
||||||
|
let content = lastMessage.content || '';
|
||||||
|
let reasoningContent = lastMessage.reasoningContent || '';
|
||||||
|
|
||||||
|
// 处理 <think> 标签格式的思维链
|
||||||
|
if (content.includes('<think>')) {
|
||||||
|
const thinkTagRegex = /<think>([\s\S]*?)(?:<\/think>|$)/g;
|
||||||
|
let thoughts = [];
|
||||||
|
let replyParts = [];
|
||||||
|
let lastIndex = 0;
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = thinkTagRegex.exec(content)) !== null) {
|
||||||
|
replyParts.push(content.substring(lastIndex, match.index));
|
||||||
|
thoughts.push(match[1]);
|
||||||
|
lastIndex = match.index + match[0].length;
|
||||||
|
}
|
||||||
|
replyParts.push(content.substring(lastIndex));
|
||||||
|
|
||||||
|
// 更新内容和思维链
|
||||||
|
content = replyParts.join('').trim();
|
||||||
|
if (thoughts.length > 0) {
|
||||||
|
reasoningContent = thoughts.join('\n\n---\n\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...prevMessage.slice(0, -1), {
|
||||||
|
...lastMessage,
|
||||||
|
status: 'complete',
|
||||||
|
reasoningContent: reasoningContent,
|
||||||
|
content: content,
|
||||||
|
isReasoningExpanded: false // 停止时折叠思维链面板
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
return prevMessage;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [setMessage]);
|
||||||
|
|
||||||
const SettingsToggle = () => {
|
const SettingsToggle = () => {
|
||||||
if (!styleState.isMobile) return null;
|
if (!styleState.isMobile) return null;
|
||||||
return (
|
return (
|
||||||
@@ -664,6 +715,8 @@ const Playground = () => {
|
|||||||
chats={message}
|
chats={message}
|
||||||
onMessageSend={onMessageSend}
|
onMessageSend={onMessageSend}
|
||||||
showClearContext
|
showClearContext
|
||||||
|
showStopGenerate
|
||||||
|
onStopGenerator={onStopGenerator}
|
||||||
onClear={() => {
|
onClear={() => {
|
||||||
setMessage([]);
|
setMessage([]);
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user