diff --git a/frontend-web/src/pages/StrategyRoutes.tsx b/frontend-web/src/pages/StrategyRoutes.tsx index 4ed27e5a..f49d0805 100644 --- a/frontend-web/src/pages/StrategyRoutes.tsx +++ b/frontend-web/src/pages/StrategyRoutes.tsx @@ -61,47 +61,28 @@ export default function StrategyRoutes() { /> {/* 任务管理 */} }> + } /> {/* XK任务 */} - } + element={} /> - } + element={} /> {/* pg中任务 */} - } + element={} /> - } + element={} /> - } + element={} /> {/* COH中任务 */} } /> - } /> - {/* MNTY任务 */} - - } - /> - - } - /> - - } - /> + } /> + } /> + } /> {/* 资源管理 */} { + 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); setIsLoading(true); setIsPreConnectLoading(true); + try { - const response = await fetch(`${generateUrl}/generate`, { + const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: query }), @@ -1109,20 +1130,14 @@ const GeneralQA: React.FC<{ }; 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(), - }; - }); + updatePane(side, (pane) => ({ ...pane, thinkingTrace: createMultiRoundStreamTrace() })); } const reader = response.body.getReader(); - // 保存 reader 引用,用于"停止生成"功能 + // 保存 reader 引用,用于“停止生成”功能 if (!side) { readerRef.current = reader; } else { @@ -1135,8 +1150,8 @@ const GeneralQA: React.FC<{ while (true) { const { done, value } = await reader.read(); if (done) { - console.log('聊天记录', state.current.compareResult); - if (!side) { + console.log('聊天记录', logCompareResult ? state.current.compareResult : state.current.messageList); + if (!guardHistoryBySide || !side) { chatHistoryAddSearch(); } break; @@ -1163,7 +1178,7 @@ const GeneralQA: React.FC<{ break; } case 'tool_call': { - handleToolCall(data as ToolCallData, assistantId, side); + handleToolCall(data as ToolCallData, assistantId, passSideToToolCall ? side : null); break; } case 'message': { @@ -1228,464 +1243,27 @@ const GeneralQA: React.FC<{ } }; - /** - * 处理任务检索 请求。 - * - * 多轮接口采用 SSE:每行 `data:` 都描述一个事件。组件根据事件类型分别更新 taskId、工具调用、 - * 最终回答和引用来源。当前用户入口暂时走本地模拟,此方法保留给接口连通后切换使用。 - */ - const handleTaskRun = async (query: string, side: 'left' | 'right' | null = null) => { - setError(null); - setIsLoading(true); - setIsPreConnectLoading(true); + // 多轮 RAG 检索:透传 side、仅非对比模式落库、日志打印 compareResult。 + const handleRun = (query: string, side: 'left' | 'right' | null = null) => + runMultiRoundStream(`${generateUrl}/generate`, query, side, { + passSideToToolCall: true, + guardHistoryBySide: true, + logCompareResult: true, + }); - try { - const response = await fetch(`${taskChatUrl}/generate`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ prompt: query }), - }); + // 任务检索:透传 side,但 done 时无条件落库。 + const handleTaskRun = (query: string, side: 'left' | 'right' | null = null) => + runMultiRoundStream(`${taskChatUrl}/generate`, query, side, { + passSideToToolCall: true, + }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } + // 画像检索:不透传 side,done 时无条件落库。 + const handleHxRun = (query: string, side: 'left' | 'right' | null = null) => + runMultiRoundStream(`${taskChatUrl}/generate_b2`, query, side); - 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, 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); - } - }; + // 新闻报告生成:不透传 side,done 时无条件落库。 + const newsReportRun = (query: string, side: 'left' | 'right' | null = null) => + runMultiRoundStream(`${taskChatUrl}/generate_report`, query, side); /** * 单一模式下的发送入口。 diff --git a/frontend-web/src/strategy-components/components/task-components/FanganTy.tsx b/frontend-web/src/strategy-components/components/task-components/FanganTy.tsx index b2831a51..6b42e2c2 100644 --- a/frontend-web/src/strategy-components/components/task-components/FanganTy.tsx +++ b/frontend-web/src/strategy-components/components/task-components/FanganTy.tsx @@ -3,15 +3,9 @@ import "./css/iframe.css"; import { useTheme } from "@/contexts/ThemeContext"; -interface FanganTyPageProps { - title: string; - category: string; - subCategory: string; -} - const TYURL = import.meta.env.VITE_AIPI_B2ACTOR_URL || ""; -const FanganTyPage: React.FC = () => { +const FanganTyPage: React.FC = () => { const { theme } = useTheme(); const isDark = theme === "dark"; const [iframeUrl, setIframeUrl] = useState(""); diff --git a/frontend-web/src/strategy-components/components/task-components/XXTy.tsx b/frontend-web/src/strategy-components/components/task-components/XXTy.tsx index bd0c8c7e..51abae74 100644 --- a/frontend-web/src/strategy-components/components/task-components/XXTy.tsx +++ b/frontend-web/src/strategy-components/components/task-components/XXTy.tsx @@ -3,15 +3,9 @@ import "./css/iframe.css"; import { useTheme } from "@/contexts/ThemeContext"; -interface XXTyPageProps { - title: string; - category: string; - subCategory: string; -} - const TYURL = import.meta.env.VITE_AIPI_B2ACTOR_URL || ""; -const XXTyPage: React.FC = () => { +const XXTyPage: React.FC = () => { const { theme } = useTheme(); const isDark = theme === "dark"; const [iframeUrl, setIframeUrl] = useState(""); diff --git a/frontend-web/src/strategy-components/components/task-components/XingdongTy.tsx b/frontend-web/src/strategy-components/components/task-components/XingdongTy.tsx index 6e635ab3..98322871 100644 --- a/frontend-web/src/strategy-components/components/task-components/XingdongTy.tsx +++ b/frontend-web/src/strategy-components/components/task-components/XingdongTy.tsx @@ -3,15 +3,9 @@ import "./css/iframe.css"; import { useTheme } from "@/contexts/ThemeContext"; -interface XingdongTyPageProps { - title: string; - category: string; - subCategory: string; -} - const TYURL = import.meta.env.VITE_AIPI_B2ACTOR_URL || ""; -const XingdongTyPage: React.FC = () => { +const XingdongTyPage: React.FC = () => { const { theme } = useTheme(); const isDark = theme === "dark"; const [iframeUrl, setIframeUrl] = useState("");