代码优化
This commit is contained in:
1 parent
214e794451
commit
2371364911
5 files changed
+62
-548
No files matched your search
@@ -61,47 +61,28 @@ export default function StrategyRoutes() {
|
|||||||
/>
|
/>
|
||||||
{/* 任务管理 */}
|
{/* 任务管理 */}
|
||||||
<Route path="task-management" element={<TaskManagement />}>
|
<Route path="task-management" element={<TaskManagement />}>
|
||||||
|
<Route index element={<Navigate to="xk-task-jc" replace />} />
|
||||||
{/* XK任务 */}
|
{/* XK任务 */}
|
||||||
<Route
|
<Route
|
||||||
path="xk-task-jc"
|
path="xk-task-jc"
|
||||||
element={
|
element={<TaskXkFrom gopath="#/pageIndie/pageHome/pageSwrw" />}
|
||||||
<TaskXkFrom
|
|
||||||
gopath={'#/pageIndie/pageHome/pageSwrw'}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="xk-task-dispatch"
|
path="xk-task-dispatch"
|
||||||
element={
|
element={<TaskXkFrom gopath="#/pageIndie/pageJczl" />}
|
||||||
<TaskXkFrom
|
|
||||||
gopath={'#/pageIndie/pageJczl'}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
{/* pg中任务 */}
|
{/* pg中任务 */}
|
||||||
<Route
|
<Route
|
||||||
path="effect-task-pg"
|
path="effect-task-pg"
|
||||||
element={
|
element={<TaskPgFrom gopath="task" />}
|
||||||
<TaskPgFrom
|
|
||||||
gopath="task"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="effect-action-pg"
|
path="effect-action-pg"
|
||||||
element={
|
element={<TaskPgFrom gopath="xd" />}
|
||||||
<TaskPgFrom
|
|
||||||
gopath="xd"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="effect-info-pg"
|
path="effect-info-pg"
|
||||||
element={
|
element={<TaskPgFrom gopath="info" />}
|
||||||
<TaskPgFrom
|
|
||||||
gopath="info"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
{/* COH中任务 */}
|
{/* COH中任务 */}
|
||||||
<Route
|
<Route
|
||||||
@@ -114,7 +95,7 @@ export default function StrategyRoutes() {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="plan-task/plan-task-fx"
|
path="plan-task/plan-task-fx"
|
||||||
element={
|
element={
|
||||||
<TaskAnalyzes
|
<TaskAnalyzes
|
||||||
@@ -124,7 +105,7 @@ export default function StrategyRoutes() {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="plan-task/plan-task-action"
|
path="plan-task/plan-task-action"
|
||||||
element={
|
element={
|
||||||
<ActionAnalyzes
|
<ActionAnalyzes
|
||||||
@@ -135,36 +116,9 @@ export default function StrategyRoutes() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{/* MNTY任务 */}
|
{/* MNTY任务 */}
|
||||||
<Route
|
<Route path="mnty-task-mechanism" element={<XingdongTyPage />} />
|
||||||
path="mnty-task-mechanism"
|
<Route path="mnty-task-plan" element={<FanganTyPage />} />
|
||||||
element={
|
<Route path="mnty-task-zsty" element={<XXTyPage />} />
|
||||||
<XingdongTyPage
|
|
||||||
title="XDTY任务"
|
|
||||||
category="MNTY任务"
|
|
||||||
subCategory="MNTY任务"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="mnty-task-plan"
|
|
||||||
element={
|
|
||||||
<FanganTyPage
|
|
||||||
title="方案TY任务"
|
|
||||||
category="MNTY任务"
|
|
||||||
subCategory="MNTY任务"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="mnty-task-zsty"
|
|
||||||
element={
|
|
||||||
<XXTyPage
|
|
||||||
title="XXTY任务"
|
|
||||||
category="MNTY任务"
|
|
||||||
subCategory="MNTY任务"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Route>
|
</Route>
|
||||||
{/* 资源管理 */}
|
{/* 资源管理 */}
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
@@ -1074,17 +1074,38 @@ const GeneralQA: React.FC<{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理真实多轮 RAG 请求。
|
* 统一的多轮 SSE 生成请求。
|
||||||
|
*
|
||||||
|
* handleRun / handleTaskRun / handleHxRun / newsReportRun 原本是四份几乎一致的拷贝,
|
||||||
|
* 仅请求地址和少量 side 处理细节不同。这里抽成一个函数,差异通过 options 严格保留:
|
||||||
|
* - passSideToToolCall:是否把 side 透传给 handleToolCall(对比模式下写入对应面板)
|
||||||
|
* - guardHistoryBySide:是否仅在非对比模式(!side)时落库,避免对比模式重复落库
|
||||||
|
* - logCompareResult:仅影响 done 时调试日志打印的对象(compareResult / messageList)
|
||||||
*
|
*
|
||||||
* 多轮接口采用 SSE:每行 `data:` 都描述一个事件。组件根据事件类型分别更新 taskId、工具调用、
|
* 多轮接口采用 SSE:每行 `data:` 都描述一个事件。组件根据事件类型分别更新 taskId、工具调用、
|
||||||
* 最终回答和引用来源。当前用户入口暂时走本地模拟,此方法保留给接口连通后切换使用。
|
* 最终回答和引用来源。
|
||||||
*/
|
*/
|
||||||
const handleRun = async (query: string, side: 'left' | 'right' | null = null) => {
|
const runMultiRoundStream = async (
|
||||||
|
url: string,
|
||||||
|
query: string,
|
||||||
|
side: 'left' | 'right' | null = null,
|
||||||
|
options: {
|
||||||
|
passSideToToolCall?: boolean;
|
||||||
|
guardHistoryBySide?: boolean;
|
||||||
|
logCompareResult?: boolean;
|
||||||
|
} = {},
|
||||||
|
) => {
|
||||||
|
const {
|
||||||
|
passSideToToolCall = false,
|
||||||
|
guardHistoryBySide = false,
|
||||||
|
logCompareResult = false,
|
||||||
|
} = options;
|
||||||
setError(null);
|
setError(null);
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setIsPreConnectLoading(true);
|
setIsPreConnectLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${generateUrl}/generate`, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ prompt: query }),
|
body: JSON.stringify({ prompt: query }),
|
||||||
@@ -1109,20 +1130,14 @@ const GeneralQA: React.FC<{
|
|||||||
};
|
};
|
||||||
if (!side) {
|
if (!side) {
|
||||||
state.current.messageList.push(assistantMessage); // 数据存储
|
state.current.messageList.push(assistantMessage); // 数据存储
|
||||||
// 接口连通后再展示多轮检索主题与过程
|
|
||||||
setMessages((prev) => [...prev, assistantMessage]);
|
setMessages((prev) => [...prev, assistantMessage]);
|
||||||
} else if (side === 'left' || side === 'right') {
|
} else if (side === 'left' || side === 'right') {
|
||||||
state.current.compareResult[state.current.compareIndex][side].thinkingTrace = createMultiRoundStreamTrace();
|
state.current.compareResult[state.current.compareIndex][side].thinkingTrace = createMultiRoundStreamTrace();
|
||||||
updatePane(side, (pane) => {
|
updatePane(side, (pane) => ({ ...pane, thinkingTrace: createMultiRoundStreamTrace() }));
|
||||||
return {
|
|
||||||
...pane,
|
|
||||||
thinkingTrace: createMultiRoundStreamTrace(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const reader = response.body.getReader();
|
const reader = response.body.getReader();
|
||||||
// 保存 reader 引用,用于"停止生成"功能
|
// 保存 reader 引用,用于“停止生成”功能
|
||||||
if (!side) {
|
if (!side) {
|
||||||
readerRef.current = reader;
|
readerRef.current = reader;
|
||||||
} else {
|
} else {
|
||||||
@@ -1135,8 +1150,8 @@ const GeneralQA: React.FC<{
|
|||||||
while (true) {
|
while (true) {
|
||||||
const { done, value } = await reader.read();
|
const { done, value } = await reader.read();
|
||||||
if (done) {
|
if (done) {
|
||||||
console.log('聊天记录', state.current.compareResult);
|
console.log('聊天记录', logCompareResult ? state.current.compareResult : state.current.messageList);
|
||||||
if (!side) {
|
if (!guardHistoryBySide || !side) {
|
||||||
chatHistoryAddSearch();
|
chatHistoryAddSearch();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1163,7 +1178,7 @@ const GeneralQA: React.FC<{
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'tool_call': {
|
case 'tool_call': {
|
||||||
handleToolCall(data as ToolCallData, assistantId, side);
|
handleToolCall(data as ToolCallData, assistantId, passSideToToolCall ? side : null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'message': {
|
case 'message': {
|
||||||
@@ -1228,464 +1243,27 @@ const GeneralQA: React.FC<{
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// 多轮 RAG 检索:透传 side、仅非对比模式落库、日志打印 compareResult。
|
||||||
* 处理任务检索 请求。
|
const handleRun = (query: string, side: 'left' | 'right' | null = null) =>
|
||||||
*
|
runMultiRoundStream(`${generateUrl}/generate`, query, side, {
|
||||||
* 多轮接口采用 SSE:每行 `data:` 都描述一个事件。组件根据事件类型分别更新 taskId、工具调用、
|
passSideToToolCall: true,
|
||||||
* 最终回答和引用来源。当前用户入口暂时走本地模拟,此方法保留给接口连通后切换使用。
|
guardHistoryBySide: true,
|
||||||
*/
|
logCompareResult: true,
|
||||||
const handleTaskRun = async (query: string, side: 'left' | 'right' | null = null) => {
|
});
|
||||||
setError(null);
|
|
||||||
setIsLoading(true);
|
|
||||||
setIsPreConnectLoading(true);
|
|
||||||
|
|
||||||
try {
|
// 任务检索:透传 side,但 done 时无条件落库。
|
||||||
const response = await fetch(`${taskChatUrl}/generate`, {
|
const handleTaskRun = (query: string, side: 'left' | 'right' | null = null) =>
|
||||||
method: 'POST',
|
runMultiRoundStream(`${taskChatUrl}/generate`, query, side, {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
passSideToToolCall: true,
|
||||||
body: JSON.stringify({ prompt: query }),
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
// 画像检索:不透传 side,done 时无条件落库。
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
const handleHxRun = (query: string, side: 'left' | 'right' | null = null) =>
|
||||||
}
|
runMultiRoundStream(`${taskChatUrl}/generate_b2`, query, side);
|
||||||
|
|
||||||
if (!response.body) {
|
// 新闻报告生成:不透传 side,done 时无条件落库。
|
||||||
throw new Error('Response body is null');
|
const newsReportRun = (query: string, side: 'left' | 'right' | null = null) =>
|
||||||
}
|
runMultiRoundStream(`${taskChatUrl}/generate_report`, query, side);
|
||||||
setIsPreConnectLoading(false);
|
|
||||||
|
|
||||||
const assistantId = `assistant-${Date.now()}`;
|
|
||||||
const assistantMessage: Message = {
|
|
||||||
id: assistantId,
|
|
||||||
role: 'assistant',
|
|
||||||
content: '',
|
|
||||||
timestamp: new Date(),
|
|
||||||
thinkingTrace: createMultiRoundStreamTrace(),
|
|
||||||
};
|
|
||||||
if (!side) {
|
|
||||||
state.current.messageList.push(assistantMessage); // 数据存储
|
|
||||||
// 接口连通后再展示多轮检索主题与过程
|
|
||||||
setMessages((prev) => [...prev, assistantMessage]);
|
|
||||||
} else if (side === 'left' || side === 'right') {
|
|
||||||
state.current.compareResult[state.current.compareIndex][side].thinkingTrace = createMultiRoundStreamTrace();
|
|
||||||
updatePane(side, (pane) => {
|
|
||||||
return {
|
|
||||||
...pane,
|
|
||||||
thinkingTrace: createMultiRoundStreamTrace(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = response.body.getReader();
|
|
||||||
if (!side) {
|
|
||||||
readerRef.current = reader;
|
|
||||||
} else {
|
|
||||||
compareReaderRef.current[side] = reader;
|
|
||||||
}
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
let buffer = '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
const { done, value } = await reader.read();
|
|
||||||
if (done) {
|
|
||||||
console.log('聊天记录', state.current.messageList);
|
|
||||||
chatHistoryAddSearch();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer += decoder.decode(value, { stream: true });
|
|
||||||
const lines = buffer.split('\n');
|
|
||||||
const lastLine = lines.pop();
|
|
||||||
buffer = lastLine || '';
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
if (!line.startsWith('data: ')) continue;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const parsedData = JSON.parse(line.slice(6)) as {
|
|
||||||
event: string;
|
|
||||||
data: any;
|
|
||||||
};
|
|
||||||
const { event, data } = parsedData;
|
|
||||||
|
|
||||||
switch (event) {
|
|
||||||
case 'task_created': {
|
|
||||||
taskIdRef.current = data?.task_id || null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'tool_call': {
|
|
||||||
handleToolCall(data as ToolCallData, assistantId, side);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'message': {
|
|
||||||
if (typeof data === 'string') {
|
|
||||||
setMessages((prevMessages) => prevMessages.map((msg) => {
|
|
||||||
if (msg.id !== assistantId || !msg.thinkingTrace) return msg;
|
|
||||||
return {
|
|
||||||
...msg,
|
|
||||||
thinkingTrace: {
|
|
||||||
...msg.thinkingTrace,
|
|
||||||
finalAnswerMarkdown: `${msg.thinkingTrace.finalAnswerMarkdown}${data}`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'references': {
|
|
||||||
const refs = Array.isArray(data?.references) ? data.references : [];
|
|
||||||
const mappedRefs = refs.map(mapReferenceToSource);
|
|
||||||
setSearchProcess((prev) => ({
|
|
||||||
...prev,
|
|
||||||
hits: mappedRefs.length,
|
|
||||||
}));
|
|
||||||
setMessages((prevMessages) => prevMessages.map((msg) => (
|
|
||||||
msg.id === assistantId
|
|
||||||
? { ...msg, sources: mappedRefs }
|
|
||||||
: msg
|
|
||||||
)));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'done': {
|
|
||||||
setIsLoading(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
console.log(`未知事件类型: ${event}`, data);
|
|
||||||
}
|
|
||||||
} catch (parseError) {
|
|
||||||
console.error('JSON解析错误:', parseError);
|
|
||||||
console.error('原始数据:', line.slice(6));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (readerRef.current) {
|
|
||||||
readerRef.current.releaseLock();
|
|
||||||
readerRef.current = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (runError) {
|
|
||||||
console.error('处理请求时发生错误:', runError);
|
|
||||||
setIsLoading(false);
|
|
||||||
setError(runError instanceof Error ? runError.message : '发生未知错误,请稍后重试');
|
|
||||||
setIsPreConnectLoading(false);
|
|
||||||
} finally {
|
|
||||||
// 无论成功还是失败,都要重置加载状态和解锁提交
|
|
||||||
setIsPreConnectLoading(false);
|
|
||||||
setIsLoading(false);
|
|
||||||
submitLockRef.current = false;
|
|
||||||
handleStopCompareSide(side);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理画像检索 请求。
|
|
||||||
*
|
|
||||||
* 多轮接口采用 SSE:每行 `data:` 都描述一个事件。组件根据事件类型分别更新 taskId、工具调用、
|
|
||||||
* 最终回答和引用来源。当前用户入口暂时走本地模拟,此方法保留给接口连通后切换使用。
|
|
||||||
*/
|
|
||||||
const handleHxRun = async (query: string, side: 'left' | 'right' | null = null) => {
|
|
||||||
setError(null);
|
|
||||||
setIsLoading(true);
|
|
||||||
setIsPreConnectLoading(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${taskChatUrl}/generate_b2`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ prompt: query }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.body) {
|
|
||||||
throw new Error('Response body is null');
|
|
||||||
}
|
|
||||||
setIsPreConnectLoading(false);
|
|
||||||
|
|
||||||
const assistantId = `assistant-${Date.now()}`;
|
|
||||||
const assistantMessage: Message = {
|
|
||||||
id: assistantId,
|
|
||||||
role: 'assistant',
|
|
||||||
content: '',
|
|
||||||
timestamp: new Date(),
|
|
||||||
thinkingTrace: createMultiRoundStreamTrace(),
|
|
||||||
};
|
|
||||||
if (!side) {
|
|
||||||
state.current.messageList.push(assistantMessage); // 数据存储
|
|
||||||
// 接口连通后再展示多轮检索主题与过程
|
|
||||||
setMessages((prev) => [...prev, assistantMessage]);
|
|
||||||
} else if (side === 'left' || side === 'right') {
|
|
||||||
state.current.compareResult[state.current.compareIndex][side].thinkingTrace = createMultiRoundStreamTrace();
|
|
||||||
updatePane(side, (pane) => {
|
|
||||||
return {
|
|
||||||
...pane,
|
|
||||||
thinkingTrace: createMultiRoundStreamTrace(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = response.body.getReader();
|
|
||||||
if (!side) {
|
|
||||||
readerRef.current = reader;
|
|
||||||
} else {
|
|
||||||
compareReaderRef.current[side] = reader;
|
|
||||||
}
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
let buffer = '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
const { done, value } = await reader.read();
|
|
||||||
if (done) {
|
|
||||||
console.log('聊天记录', state.current.messageList);
|
|
||||||
chatHistoryAddSearch();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer += decoder.decode(value, { stream: true });
|
|
||||||
const lines = buffer.split('\n');
|
|
||||||
const lastLine = lines.pop();
|
|
||||||
buffer = lastLine || '';
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
if (!line.startsWith('data: ')) continue;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const parsedData = JSON.parse(line.slice(6)) as {
|
|
||||||
event: string;
|
|
||||||
data: any;
|
|
||||||
};
|
|
||||||
const { event, data } = parsedData;
|
|
||||||
|
|
||||||
switch (event) {
|
|
||||||
case 'task_created': {
|
|
||||||
taskIdRef.current = data?.task_id || null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'tool_call': {
|
|
||||||
handleToolCall(data as ToolCallData, assistantId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'message': {
|
|
||||||
if (typeof data === 'string') {
|
|
||||||
setMessages((prevMessages) => prevMessages.map((msg) => {
|
|
||||||
if (msg.id !== assistantId || !msg.thinkingTrace) return msg;
|
|
||||||
return {
|
|
||||||
...msg,
|
|
||||||
thinkingTrace: {
|
|
||||||
...msg.thinkingTrace,
|
|
||||||
finalAnswerMarkdown: `${msg.thinkingTrace.finalAnswerMarkdown}${data}`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'references': {
|
|
||||||
const refs = Array.isArray(data?.references) ? data.references : [];
|
|
||||||
const mappedRefs = refs.map(mapReferenceToSource);
|
|
||||||
setSearchProcess((prev) => ({
|
|
||||||
...prev,
|
|
||||||
hits: mappedRefs.length,
|
|
||||||
}));
|
|
||||||
setMessages((prevMessages) => prevMessages.map((msg) => (
|
|
||||||
msg.id === assistantId
|
|
||||||
? { ...msg, sources: mappedRefs }
|
|
||||||
: msg
|
|
||||||
)));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'done': {
|
|
||||||
setIsLoading(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
console.log(`未知事件类型: ${event}`, data);
|
|
||||||
}
|
|
||||||
} catch (parseError) {
|
|
||||||
console.error('JSON解析错误:', parseError);
|
|
||||||
console.error('原始数据:', line.slice(6));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (readerRef.current) {
|
|
||||||
readerRef.current.releaseLock();
|
|
||||||
readerRef.current = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (runError) {
|
|
||||||
console.error('处理请求时发生错误:', runError);
|
|
||||||
setIsLoading(false);
|
|
||||||
setError(runError instanceof Error ? runError.message : '发生未知错误,请稍后重试');
|
|
||||||
setIsPreConnectLoading(false);
|
|
||||||
} finally {
|
|
||||||
// 无论成功还是失败,都要重置加载状态和解锁提交
|
|
||||||
setIsPreConnectLoading(false);
|
|
||||||
setIsLoading(false);
|
|
||||||
submitLockRef.current = false;
|
|
||||||
handleStopCompareSide(side);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理画像检索 请求。
|
|
||||||
*
|
|
||||||
* 多轮接口采用 SSE:每行 `data:` 都描述一个事件。组件根据事件类型分别更新 taskId、工具调用、
|
|
||||||
* 最终回答和引用来源。当前用户入口暂时走本地模拟,此方法保留给接口连通后切换使用。
|
|
||||||
*/
|
|
||||||
const newsReportRun = async (query: string, side: 'left' | 'right' | null = null) => {
|
|
||||||
setError(null);
|
|
||||||
setIsLoading(true);
|
|
||||||
setIsPreConnectLoading(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${taskChatUrl}/generate_report`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ prompt: query }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.body) {
|
|
||||||
throw new Error('Response body is null');
|
|
||||||
}
|
|
||||||
setIsPreConnectLoading(false);
|
|
||||||
|
|
||||||
const assistantId = `assistant-${Date.now()}`;
|
|
||||||
const assistantMessage: Message = {
|
|
||||||
id: assistantId,
|
|
||||||
role: 'assistant',
|
|
||||||
content: '',
|
|
||||||
timestamp: new Date(),
|
|
||||||
thinkingTrace: createMultiRoundStreamTrace(),
|
|
||||||
};
|
|
||||||
if (!side) {
|
|
||||||
state.current.messageList.push(assistantMessage); // 数据存储
|
|
||||||
// 接口连通后再展示多轮检索主题与过程
|
|
||||||
setMessages((prev) => [...prev, assistantMessage]);
|
|
||||||
} else if (side === 'left' || side === 'right') {
|
|
||||||
state.current.compareResult[state.current.compareIndex][side].thinkingTrace = createMultiRoundStreamTrace();
|
|
||||||
updatePane(side, (pane) => {
|
|
||||||
return {
|
|
||||||
...pane,
|
|
||||||
thinkingTrace: createMultiRoundStreamTrace(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = response.body.getReader();
|
|
||||||
if (!side) {
|
|
||||||
readerRef.current = reader;
|
|
||||||
} else {
|
|
||||||
compareReaderRef.current[side] = reader;
|
|
||||||
}
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
let buffer = '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
const { done, value } = await reader.read();
|
|
||||||
if (done) {
|
|
||||||
console.log('聊天记录', state.current.messageList);
|
|
||||||
chatHistoryAddSearch();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer += decoder.decode(value, { stream: true });
|
|
||||||
const lines = buffer.split('\n');
|
|
||||||
const lastLine = lines.pop();
|
|
||||||
buffer = lastLine || '';
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
if (!line.startsWith('data: ')) continue;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const parsedData = JSON.parse(line.slice(6)) as {
|
|
||||||
event: string;
|
|
||||||
data: any;
|
|
||||||
};
|
|
||||||
const { event, data } = parsedData;
|
|
||||||
|
|
||||||
switch (event) {
|
|
||||||
case 'task_created': {
|
|
||||||
taskIdRef.current = data?.task_id || null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'tool_call': {
|
|
||||||
handleToolCall(data as ToolCallData, assistantId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'message': {
|
|
||||||
if (typeof data === 'string') {
|
|
||||||
setMessages((prevMessages) => prevMessages.map((msg) => {
|
|
||||||
if (msg.id !== assistantId || !msg.thinkingTrace) return msg;
|
|
||||||
return {
|
|
||||||
...msg,
|
|
||||||
thinkingTrace: {
|
|
||||||
...msg.thinkingTrace,
|
|
||||||
finalAnswerMarkdown: `${msg.thinkingTrace.finalAnswerMarkdown}${data}`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'references': {
|
|
||||||
const refs = Array.isArray(data?.references) ? data.references : [];
|
|
||||||
const mappedRefs = refs.map(mapReferenceToSource);
|
|
||||||
setSearchProcess((prev) => ({
|
|
||||||
...prev,
|
|
||||||
hits: mappedRefs.length,
|
|
||||||
}));
|
|
||||||
setMessages((prevMessages) => prevMessages.map((msg) => (
|
|
||||||
msg.id === assistantId
|
|
||||||
? { ...msg, sources: mappedRefs }
|
|
||||||
: msg
|
|
||||||
)));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'done': {
|
|
||||||
setIsLoading(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
console.log(`未知事件类型: ${event}`, data);
|
|
||||||
}
|
|
||||||
} catch (parseError) {
|
|
||||||
console.error('JSON解析错误:', parseError);
|
|
||||||
console.error('原始数据:', line.slice(6));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (readerRef.current) {
|
|
||||||
readerRef.current.releaseLock();
|
|
||||||
readerRef.current = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (runError) {
|
|
||||||
console.error('处理请求时发生错误:', runError);
|
|
||||||
setIsLoading(false);
|
|
||||||
setError(runError instanceof Error ? runError.message : '发生未知错误,请稍后重试');
|
|
||||||
setIsPreConnectLoading(false);
|
|
||||||
} finally {
|
|
||||||
// 无论成功还是失败,都要重置加载状态和解锁提交
|
|
||||||
setIsPreConnectLoading(false);
|
|
||||||
setIsLoading(false);
|
|
||||||
submitLockRef.current = false;
|
|
||||||
handleStopCompareSide(side);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单一模式下的发送入口。
|
* 单一模式下的发送入口。
|
||||||
|
|||||||
@@ -3,15 +3,9 @@ import "./css/iframe.css";
|
|||||||
|
|
||||||
import { useTheme } from "@/contexts/ThemeContext";
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
|
|
||||||
interface FanganTyPageProps {
|
|
||||||
title: string;
|
|
||||||
category: string;
|
|
||||||
subCategory: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TYURL = import.meta.env.VITE_AIPI_B2ACTOR_URL || "";
|
const TYURL = import.meta.env.VITE_AIPI_B2ACTOR_URL || "";
|
||||||
|
|
||||||
const FanganTyPage: React.FC<FanganTyPageProps> = () => {
|
const FanganTyPage: React.FC = () => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const isDark = theme === "dark";
|
const isDark = theme === "dark";
|
||||||
const [iframeUrl, setIframeUrl] = useState("");
|
const [iframeUrl, setIframeUrl] = useState("");
|
||||||
|
|||||||
@@ -3,15 +3,9 @@ import "./css/iframe.css";
|
|||||||
|
|
||||||
import { useTheme } from "@/contexts/ThemeContext";
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
|
|
||||||
interface XXTyPageProps {
|
|
||||||
title: string;
|
|
||||||
category: string;
|
|
||||||
subCategory: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TYURL = import.meta.env.VITE_AIPI_B2ACTOR_URL || "";
|
const TYURL = import.meta.env.VITE_AIPI_B2ACTOR_URL || "";
|
||||||
|
|
||||||
const XXTyPage: React.FC<XXTyPageProps> = () => {
|
const XXTyPage: React.FC = () => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const isDark = theme === "dark";
|
const isDark = theme === "dark";
|
||||||
const [iframeUrl, setIframeUrl] = useState("");
|
const [iframeUrl, setIframeUrl] = useState("");
|
||||||
|
|||||||
@@ -3,15 +3,9 @@ import "./css/iframe.css";
|
|||||||
|
|
||||||
import { useTheme } from "@/contexts/ThemeContext";
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
|
|
||||||
interface XingdongTyPageProps {
|
|
||||||
title: string;
|
|
||||||
category: string;
|
|
||||||
subCategory: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TYURL = import.meta.env.VITE_AIPI_B2ACTOR_URL || "";
|
const TYURL = import.meta.env.VITE_AIPI_B2ACTOR_URL || "";
|
||||||
|
|
||||||
const XingdongTyPage: React.FC<XingdongTyPageProps> = () => {
|
const XingdongTyPage: React.FC = () => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const isDark = theme === "dark";
|
const isDark = theme === "dark";
|
||||||
const [iframeUrl, setIframeUrl] = useState("");
|
const [iframeUrl, setIframeUrl] = useState("");
|
||||||
|
|||||||
Reference in new issue
Block a user