2025-05-30 22:14:44 +08:00
|
|
|
|
import { useCallback } from 'react';
|
|
|
|
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
|
|
import { SSE } from 'sse';
|
|
|
|
|
|
import {
|
|
|
|
|
|
API_ENDPOINTS,
|
|
|
|
|
|
MESSAGE_STATUS,
|
|
|
|
|
|
DEBUG_TABS
|
2025-06-03 16:13:50 +08:00
|
|
|
|
} from '../constants/playground.constants';
|
2025-05-30 22:14:44 +08:00
|
|
|
|
import {
|
2025-06-03 23:56:39 +08:00
|
|
|
|
getUserIdFromLocalStorage,
|
|
|
|
|
|
handleApiError,
|
2025-05-30 22:14:44 +08:00
|
|
|
|
processThinkTags,
|
|
|
|
|
|
processIncompleteThinkTags
|
2025-06-03 23:56:39 +08:00
|
|
|
|
} from '../helpers';
|
2025-05-30 22:14:44 +08:00
|
|
|
|
|
|
|
|
|
|
export const useApiRequest = (
|
|
|
|
|
|
setMessage,
|
|
|
|
|
|
setDebugData,
|
|
|
|
|
|
setActiveDebugTab,
|
2025-06-02 21:21:46 +08:00
|
|
|
|
sseSourceRef,
|
|
|
|
|
|
saveMessages
|
2025-05-30 22:14:44 +08:00
|
|
|
|
) => {
|
|
|
|
|
|
const { t } = useTranslation();
|
|
|
|
|
|
|
2025-05-31 01:35:13 +08:00
|
|
|
|
// 处理消息自动关闭逻辑的公共函数
|
|
|
|
|
|
const applyAutoCollapseLogic = useCallback((message, isThinkingComplete = true) => {
|
|
|
|
|
|
const shouldAutoCollapse = isThinkingComplete && !message.hasAutoCollapsed;
|
|
|
|
|
|
return {
|
|
|
|
|
|
isThinkingComplete,
|
|
|
|
|
|
hasAutoCollapsed: shouldAutoCollapse || message.hasAutoCollapsed,
|
|
|
|
|
|
isReasoningExpanded: shouldAutoCollapse ? false : message.isReasoningExpanded,
|
|
|
|
|
|
};
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
2025-05-30 22:14:44 +08:00
|
|
|
|
// 流式消息更新
|
|
|
|
|
|
const streamMessageUpdate = useCallback((textChunk, type) => {
|
|
|
|
|
|
setMessage(prevMessage => {
|
|
|
|
|
|
const lastMessage = prevMessage[prevMessage.length - 1];
|
2025-06-02 06:21:05 +08:00
|
|
|
|
if (!lastMessage) return prevMessage;
|
|
|
|
|
|
if (lastMessage.role !== 'assistant') return prevMessage;
|
2025-05-30 22:14:44 +08:00
|
|
|
|
if (lastMessage.status === MESSAGE_STATUS.ERROR) {
|
|
|
|
|
|
return prevMessage;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (lastMessage.status === MESSAGE_STATUS.LOADING ||
|
|
|
|
|
|
lastMessage.status === MESSAGE_STATUS.INCOMPLETE) {
|
|
|
|
|
|
|
|
|
|
|
|
let newMessage = { ...lastMessage };
|
|
|
|
|
|
|
|
|
|
|
|
if (type === 'reasoning') {
|
|
|
|
|
|
newMessage = {
|
|
|
|
|
|
...newMessage,
|
|
|
|
|
|
reasoningContent: (lastMessage.reasoningContent || '') + textChunk,
|
|
|
|
|
|
status: MESSAGE_STATUS.INCOMPLETE,
|
2025-05-31 01:12:45 +08:00
|
|
|
|
isThinkingComplete: false,
|
2025-05-30 22:14:44 +08:00
|
|
|
|
};
|
|
|
|
|
|
} else if (type === 'content') {
|
|
|
|
|
|
const shouldCollapseReasoning = !lastMessage.content && lastMessage.reasoningContent;
|
|
|
|
|
|
const newContent = (lastMessage.content || '') + textChunk;
|
|
|
|
|
|
|
|
|
|
|
|
let shouldCollapseFromThinkTag = false;
|
2025-05-31 01:12:45 +08:00
|
|
|
|
let thinkingCompleteFromTags = lastMessage.isThinkingComplete;
|
|
|
|
|
|
|
2025-05-30 22:14:44 +08:00
|
|
|
|
if (lastMessage.isReasoningExpanded && newContent.includes('</think>')) {
|
|
|
|
|
|
const thinkMatches = newContent.match(/<think>/g);
|
|
|
|
|
|
const thinkCloseMatches = newContent.match(/<\/think>/g);
|
|
|
|
|
|
if (thinkMatches && thinkCloseMatches &&
|
|
|
|
|
|
thinkCloseMatches.length >= thinkMatches.length) {
|
|
|
|
|
|
shouldCollapseFromThinkTag = true;
|
2025-05-31 01:12:45 +08:00
|
|
|
|
thinkingCompleteFromTags = true; // think标签闭合也标记思考完成
|
2025-05-30 22:14:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-31 01:12:45 +08:00
|
|
|
|
// 如果开始接收content内容,且之前有reasoning内容,或者think标签已闭合,则标记思考完成
|
|
|
|
|
|
const isThinkingComplete = (lastMessage.reasoningContent && !lastMessage.isThinkingComplete) ||
|
|
|
|
|
|
thinkingCompleteFromTags;
|
|
|
|
|
|
|
2025-05-31 01:35:13 +08:00
|
|
|
|
const autoCollapseState = applyAutoCollapseLogic(lastMessage, isThinkingComplete);
|
2025-05-31 01:29:19 +08:00
|
|
|
|
|
2025-05-30 22:14:44 +08:00
|
|
|
|
newMessage = {
|
|
|
|
|
|
...newMessage,
|
|
|
|
|
|
content: newContent,
|
|
|
|
|
|
status: MESSAGE_STATUS.INCOMPLETE,
|
2025-05-31 01:35:13 +08:00
|
|
|
|
...autoCollapseState,
|
2025-05-30 22:14:44 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return [...prevMessage.slice(0, -1), newMessage];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return prevMessage;
|
|
|
|
|
|
});
|
2025-05-31 01:35:13 +08:00
|
|
|
|
}, [setMessage, applyAutoCollapseLogic]);
|
2025-05-30 22:14:44 +08:00
|
|
|
|
|
|
|
|
|
|
// 完成消息
|
|
|
|
|
|
const completeMessage = useCallback((status = MESSAGE_STATUS.COMPLETE) => {
|
|
|
|
|
|
setMessage(prevMessage => {
|
|
|
|
|
|
const lastMessage = prevMessage[prevMessage.length - 1];
|
|
|
|
|
|
if (lastMessage.status === MESSAGE_STATUS.COMPLETE ||
|
|
|
|
|
|
lastMessage.status === MESSAGE_STATUS.ERROR) {
|
|
|
|
|
|
return prevMessage;
|
|
|
|
|
|
}
|
2025-05-31 01:29:19 +08:00
|
|
|
|
|
2025-05-31 01:35:13 +08:00
|
|
|
|
const autoCollapseState = applyAutoCollapseLogic(lastMessage, true);
|
2025-05-31 01:29:19 +08:00
|
|
|
|
|
2025-06-02 21:21:46 +08:00
|
|
|
|
const updatedMessages = [
|
2025-05-30 22:14:44 +08:00
|
|
|
|
...prevMessage.slice(0, -1),
|
|
|
|
|
|
{
|
|
|
|
|
|
...lastMessage,
|
|
|
|
|
|
status: status,
|
2025-05-31 01:35:13 +08:00
|
|
|
|
...autoCollapseState,
|
2025-05-30 22:14:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
];
|
2025-06-02 21:21:46 +08:00
|
|
|
|
|
2025-06-02 21:39:51 +08:00
|
|
|
|
// 在消息完成时保存,传入更新后的消息列表
|
2025-06-02 21:21:46 +08:00
|
|
|
|
if (status === MESSAGE_STATUS.COMPLETE || status === MESSAGE_STATUS.ERROR) {
|
2025-06-02 21:39:51 +08:00
|
|
|
|
setTimeout(() => saveMessages(updatedMessages), 0);
|
2025-06-02 21:21:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return updatedMessages;
|
2025-05-30 22:14:44 +08:00
|
|
|
|
});
|
2025-06-02 21:21:46 +08:00
|
|
|
|
}, [setMessage, applyAutoCollapseLogic, saveMessages]);
|
2025-05-30 22:14:44 +08:00
|
|
|
|
|
|
|
|
|
|
// 非流式请求
|
|
|
|
|
|
const handleNonStreamRequest = useCallback(async (payload) => {
|
|
|
|
|
|
setDebugData(prev => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
request: payload,
|
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
|
response: null
|
|
|
|
|
|
}));
|
|
|
|
|
|
setActiveDebugTab(DEBUG_TABS.REQUEST);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(API_ENDPOINTS.CHAT_COMPLETIONS, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
'New-Api-User': getUserIdFromLocalStorage(),
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
let errorBody = '';
|
|
|
|
|
|
try {
|
|
|
|
|
|
errorBody = await response.text();
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
errorBody = '无法读取错误响应体';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const errorInfo = handleApiError(
|
|
|
|
|
|
new Error(`HTTP error! status: ${response.status}, body: ${errorBody}`),
|
|
|
|
|
|
response
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
setDebugData(prev => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
response: JSON.stringify(errorInfo, null, 2)
|
|
|
|
|
|
}));
|
|
|
|
|
|
setActiveDebugTab(DEBUG_TABS.RESPONSE);
|
|
|
|
|
|
|
|
|
|
|
|
throw new Error(`HTTP error! status: ${response.status}, body: ${errorBody}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
setDebugData(prev => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
response: JSON.stringify(data, null, 2)
|
|
|
|
|
|
}));
|
|
|
|
|
|
setActiveDebugTab(DEBUG_TABS.RESPONSE);
|
|
|
|
|
|
|
|
|
|
|
|
if (data.choices?.[0]) {
|
|
|
|
|
|
const choice = data.choices[0];
|
|
|
|
|
|
let content = choice.message?.content || '';
|
|
|
|
|
|
let reasoningContent = choice.message?.reasoning_content || '';
|
|
|
|
|
|
|
|
|
|
|
|
const processed = processThinkTags(content, reasoningContent);
|
|
|
|
|
|
|
|
|
|
|
|
setMessage(prevMessage => {
|
|
|
|
|
|
const newMessages = [...prevMessage];
|
|
|
|
|
|
const lastMessage = newMessages[newMessages.length - 1];
|
|
|
|
|
|
if (lastMessage?.status === MESSAGE_STATUS.LOADING) {
|
2025-05-31 01:35:13 +08:00
|
|
|
|
const autoCollapseState = applyAutoCollapseLogic(lastMessage, true);
|
2025-05-31 01:29:19 +08:00
|
|
|
|
|
2025-05-30 22:14:44 +08:00
|
|
|
|
newMessages[newMessages.length - 1] = {
|
|
|
|
|
|
...lastMessage,
|
|
|
|
|
|
content: processed.content,
|
|
|
|
|
|
reasoningContent: processed.reasoningContent,
|
|
|
|
|
|
status: MESSAGE_STATUS.COMPLETE,
|
2025-05-31 01:35:13 +08:00
|
|
|
|
...autoCollapseState,
|
2025-05-30 22:14:44 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
return newMessages;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Non-stream request error:', error);
|
|
|
|
|
|
|
|
|
|
|
|
const errorInfo = handleApiError(error);
|
|
|
|
|
|
setDebugData(prev => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
response: JSON.stringify(errorInfo, null, 2)
|
|
|
|
|
|
}));
|
|
|
|
|
|
setActiveDebugTab(DEBUG_TABS.RESPONSE);
|
|
|
|
|
|
|
|
|
|
|
|
setMessage(prevMessage => {
|
|
|
|
|
|
const newMessages = [...prevMessage];
|
|
|
|
|
|
const lastMessage = newMessages[newMessages.length - 1];
|
|
|
|
|
|
if (lastMessage?.status === MESSAGE_STATUS.LOADING) {
|
2025-05-31 01:35:13 +08:00
|
|
|
|
const autoCollapseState = applyAutoCollapseLogic(lastMessage, true);
|
2025-05-31 01:29:19 +08:00
|
|
|
|
|
2025-05-30 22:14:44 +08:00
|
|
|
|
newMessages[newMessages.length - 1] = {
|
|
|
|
|
|
...lastMessage,
|
|
|
|
|
|
content: t('请求发生错误: ') + error.message,
|
|
|
|
|
|
status: MESSAGE_STATUS.ERROR,
|
2025-05-31 01:35:13 +08:00
|
|
|
|
...autoCollapseState,
|
2025-05-30 22:14:44 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
return newMessages;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-05-31 01:35:13 +08:00
|
|
|
|
}, [setDebugData, setActiveDebugTab, setMessage, t, applyAutoCollapseLogic]);
|
2025-05-30 22:14:44 +08:00
|
|
|
|
|
|
|
|
|
|
// SSE请求
|
|
|
|
|
|
const handleSSE = useCallback((payload) => {
|
|
|
|
|
|
setDebugData(prev => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
request: payload,
|
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
|
response: null
|
|
|
|
|
|
}));
|
|
|
|
|
|
setActiveDebugTab(DEBUG_TABS.REQUEST);
|
|
|
|
|
|
|
|
|
|
|
|
const source = new SSE(API_ENDPOINTS.CHAT_COMPLETIONS, {
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
'New-Api-User': getUserIdFromLocalStorage(),
|
|
|
|
|
|
},
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
payload: JSON.stringify(payload),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
sseSourceRef.current = source;
|
|
|
|
|
|
|
|
|
|
|
|
let responseData = '';
|
|
|
|
|
|
let hasReceivedFirstResponse = false;
|
|
|
|
|
|
|
|
|
|
|
|
source.addEventListener('message', (e) => {
|
|
|
|
|
|
if (e.data === '[DONE]') {
|
|
|
|
|
|
source.close();
|
|
|
|
|
|
sseSourceRef.current = null;
|
|
|
|
|
|
setDebugData(prev => ({ ...prev, response: responseData }));
|
|
|
|
|
|
completeMessage();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const payload = JSON.parse(e.data);
|
|
|
|
|
|
responseData += e.data + '\n';
|
|
|
|
|
|
|
|
|
|
|
|
if (!hasReceivedFirstResponse) {
|
|
|
|
|
|
setActiveDebugTab(DEBUG_TABS.RESPONSE);
|
|
|
|
|
|
hasReceivedFirstResponse = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const delta = payload.choices?.[0]?.delta;
|
|
|
|
|
|
if (delta) {
|
|
|
|
|
|
if (delta.reasoning_content) {
|
|
|
|
|
|
streamMessageUpdate(delta.reasoning_content, 'reasoning');
|
|
|
|
|
|
}
|
|
|
|
|
|
if (delta.content) {
|
|
|
|
|
|
streamMessageUpdate(delta.content, 'content');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to parse SSE message:', error);
|
|
|
|
|
|
const errorInfo = `解析错误: ${error.message}`;
|
|
|
|
|
|
|
|
|
|
|
|
setDebugData(prev => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
response: responseData + `\n\nError: ${errorInfo}`
|
|
|
|
|
|
}));
|
|
|
|
|
|
setActiveDebugTab(DEBUG_TABS.RESPONSE);
|
|
|
|
|
|
|
|
|
|
|
|
streamMessageUpdate(t('解析响应数据时发生错误'), 'content');
|
|
|
|
|
|
completeMessage(MESSAGE_STATUS.ERROR);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
source.addEventListener('error', (e) => {
|
|
|
|
|
|
console.error('SSE Error:', e);
|
|
|
|
|
|
const errorMessage = e.data || t('请求发生错误');
|
|
|
|
|
|
|
|
|
|
|
|
const errorInfo = handleApiError(new Error(errorMessage));
|
|
|
|
|
|
errorInfo.readyState = source.readyState;
|
|
|
|
|
|
|
|
|
|
|
|
setDebugData(prev => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
response: responseData + '\n\nSSE Error:\n' + JSON.stringify(errorInfo, null, 2)
|
|
|
|
|
|
}));
|
|
|
|
|
|
setActiveDebugTab(DEBUG_TABS.RESPONSE);
|
|
|
|
|
|
|
|
|
|
|
|
streamMessageUpdate(errorMessage, 'content');
|
|
|
|
|
|
completeMessage(MESSAGE_STATUS.ERROR);
|
|
|
|
|
|
sseSourceRef.current = null;
|
|
|
|
|
|
source.close();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
source.addEventListener('readystatechange', (e) => {
|
|
|
|
|
|
if (e.readyState >= 2 && source.status !== undefined && source.status !== 200) {
|
|
|
|
|
|
const errorInfo = handleApiError(new Error('HTTP状态错误'));
|
|
|
|
|
|
errorInfo.status = source.status;
|
|
|
|
|
|
errorInfo.readyState = source.readyState;
|
|
|
|
|
|
|
|
|
|
|
|
setDebugData(prev => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
response: responseData + '\n\nHTTP Error:\n' + JSON.stringify(errorInfo, null, 2)
|
|
|
|
|
|
}));
|
|
|
|
|
|
setActiveDebugTab(DEBUG_TABS.RESPONSE);
|
|
|
|
|
|
|
|
|
|
|
|
source.close();
|
|
|
|
|
|
streamMessageUpdate(t('连接已断开'), 'content');
|
|
|
|
|
|
completeMessage(MESSAGE_STATUS.ERROR);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
source.stream();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to start SSE stream:', error);
|
|
|
|
|
|
const errorInfo = handleApiError(error);
|
|
|
|
|
|
|
|
|
|
|
|
setDebugData(prev => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
response: 'Stream启动失败:\n' + JSON.stringify(errorInfo, null, 2)
|
|
|
|
|
|
}));
|
|
|
|
|
|
setActiveDebugTab(DEBUG_TABS.RESPONSE);
|
|
|
|
|
|
|
|
|
|
|
|
streamMessageUpdate(t('建立连接时发生错误'), 'content');
|
|
|
|
|
|
completeMessage(MESSAGE_STATUS.ERROR);
|
|
|
|
|
|
}
|
2025-05-31 01:35:13 +08:00
|
|
|
|
}, [setDebugData, setActiveDebugTab, streamMessageUpdate, completeMessage, t, applyAutoCollapseLogic]);
|
2025-05-30 22:14:44 +08:00
|
|
|
|
|
|
|
|
|
|
// 停止生成
|
|
|
|
|
|
const onStopGenerator = useCallback(() => {
|
2025-06-02 23:56:58 +08:00
|
|
|
|
// 如果仍有活动的 SSE 连接,首先关闭
|
2025-05-30 22:14:44 +08:00
|
|
|
|
if (sseSourceRef.current) {
|
|
|
|
|
|
sseSourceRef.current.close();
|
|
|
|
|
|
sseSourceRef.current = null;
|
2025-06-02 23:56:58 +08:00
|
|
|
|
}
|
2025-05-30 22:14:44 +08:00
|
|
|
|
|
2025-06-02 23:56:58 +08:00
|
|
|
|
// 无论是否存在 SSE 连接,都尝试处理最后一条正在生成的消息
|
|
|
|
|
|
setMessage(prevMessage => {
|
|
|
|
|
|
if (prevMessage.length === 0) return prevMessage;
|
|
|
|
|
|
const lastMessage = prevMessage[prevMessage.length - 1];
|
2025-05-30 22:14:44 +08:00
|
|
|
|
|
2025-06-02 23:56:58 +08:00
|
|
|
|
if (lastMessage.status === MESSAGE_STATUS.LOADING ||
|
|
|
|
|
|
lastMessage.status === MESSAGE_STATUS.INCOMPLETE) {
|
2025-05-30 22:14:44 +08:00
|
|
|
|
|
2025-06-02 23:56:58 +08:00
|
|
|
|
const processed = processIncompleteThinkTags(
|
|
|
|
|
|
lastMessage.content || '',
|
|
|
|
|
|
lastMessage.reasoningContent || ''
|
|
|
|
|
|
);
|
2025-05-31 01:29:19 +08:00
|
|
|
|
|
2025-06-02 23:56:58 +08:00
|
|
|
|
const autoCollapseState = applyAutoCollapseLogic(lastMessage, true);
|
2025-06-02 21:21:46 +08:00
|
|
|
|
|
2025-06-02 23:56:58 +08:00
|
|
|
|
const updatedMessages = [
|
|
|
|
|
|
...prevMessage.slice(0, -1),
|
|
|
|
|
|
{
|
|
|
|
|
|
...lastMessage,
|
|
|
|
|
|
status: MESSAGE_STATUS.COMPLETE,
|
|
|
|
|
|
reasoningContent: processed.reasoningContent || null,
|
|
|
|
|
|
content: processed.content,
|
|
|
|
|
|
...autoCollapseState,
|
|
|
|
|
|
}
|
|
|
|
|
|
];
|
2025-06-02 21:21:46 +08:00
|
|
|
|
|
2025-06-02 23:56:58 +08:00
|
|
|
|
// 停止生成时也保存,传入更新后的消息列表
|
|
|
|
|
|
setTimeout(() => saveMessages(updatedMessages), 0);
|
|
|
|
|
|
|
|
|
|
|
|
return updatedMessages;
|
|
|
|
|
|
}
|
|
|
|
|
|
return prevMessage;
|
|
|
|
|
|
});
|
2025-06-02 21:21:46 +08:00
|
|
|
|
}, [setMessage, applyAutoCollapseLogic, saveMessages]);
|
2025-05-30 22:14:44 +08:00
|
|
|
|
|
|
|
|
|
|
// 发送请求
|
|
|
|
|
|
const sendRequest = useCallback((payload, isStream) => {
|
|
|
|
|
|
if (isStream) {
|
|
|
|
|
|
handleSSE(payload);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
handleNonStreamRequest(payload);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [handleSSE, handleNonStreamRequest]);
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
sendRequest,
|
|
|
|
|
|
onStopGenerator,
|
|
|
|
|
|
streamMessageUpdate,
|
|
|
|
|
|
completeMessage,
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|