/** * 文件作用:superAgent 超级智能体模块文件 * 职责范围: * 1. 承载 superAgent 业务 session、workspace 和附件能力 * 2. 在不影响 newAiChat 的前提下复用现有组件和接口 * 3. 支撑标书任务工作区的前端编排 * * 不负责: * - 修改 newAiChat 原有页面行为 * - 实现后端标书解析、正文生成或导出接口 * - 封装 v2 useStream 协议;新版流由 useSuperAgentStream 直接交给 SDK 管理 * * 维护说明: * - 不负责消息流、thread 快照或 interrupt 恢复,这些能力统一由 useStream 管理 */ "use strict"; import { danaApi, fastgptApiTable, superAgentServiceApiTable, basicAction, } from "@u"; import { buildSuperAgentHeaders, buildSuperAgentPath, buildSuperAgentServiceUrl, requestAgentJson, } from "../utils/agentApi"; import request from "superagent"; import { createSuperAgentTraceId, summarizeSuperAgentPayload, superAgentError, superAgentLog, } from "../utils/debugLog"; const createDirectJsonAction = ({ method, path, fallbackMessage }) => (options = {}) => async () => { const data = await requestAgentJson({ method, path, params: options.params, query: options.query, body: options.body, fallbackMessage, }); return { payload: { data } }; }; // SuperAgent 前端业务接口使用同源路径,由 web/config.cjs 转发到 Agent 服务。 export const listSuperAgentSessions = createDirectJsonAction({ method: "GET", path: superAgentServiceApiTable.conversations, fallbackMessage: "获取超级智能体会话失败", }); export const createSuperAgentSession = (options = {}) => async () => { const requestBody = options.body || {}; const sessionType = requestBody.type === "task" || requestBody.type === "conversation" ? requestBody.type : "conversation"; const body = { type: sessionType, name: String(requestBody.name || requestBody.title || (sessionType === "conversation" ? "新对话" : "新任务")), }; const tasksetId = requestBody.taskset_id ?? requestBody.tasksetId ?? requestBody.folder_id ?? requestBody.folderId; if (sessionType === "task") { body.taskset_id = tasksetId; } const data = await requestSuperAgentJson({ method: "POST", path: superAgentServiceApiTable.conversations, query: options.query, body, fallbackMessage: "创建超级智能体会话失败", }); return { payload: { data } }; }; export const getSuperAgentSessionSections = createDirectJsonAction({ method: "GET", path: superAgentServiceApiTable.artifactChapters, fallbackMessage: "获取超级智能体章节列表失败", }); export const getSuperAgentSessionSection = createDirectJsonAction({ method: "GET", path: superAgentServiceApiTable.artifactChapter, fallbackMessage: "获取超级智能体章节内容失败", }); export const updateSuperAgentSessionSection = createDirectJsonAction({ method: "PUT", path: superAgentServiceApiTable.artifactChapter, fallbackMessage: "保存超级智能体章节内容失败", }); export const deleteSuperAgentSession = createDirectJsonAction({ method: "DELETE", path: superAgentServiceApiTable.conversationDetail, fallbackMessage: "删除超级智能体会话失败", }); export const getSuperAgentSidebar = createDirectJsonAction({ method: "GET", path: superAgentServiceApiTable.tasksets, fallbackMessage: "获取超级智能体侧栏失败", }); export const createSuperAgentFolder = createDirectJsonAction({ method: "POST", path: superAgentServiceApiTable.tasksets, fallbackMessage: "创建文件夹失败", }); export const deleteSuperAgentFolder = createDirectJsonAction({ method: "DELETE", path: superAgentServiceApiTable.tasksetDetail, fallbackMessage: "删除文件夹失败", }); export const respondSuperAgentCheckpoint = (options = {}) => async () => { const body = options.body || {}; const sessionId = body.session_id || body.sessionId; const data = await requestSuperAgentJson({ method: "POST", path: superAgentServiceApiTable.threadCommands, params: { sessionId }, query: options.query, body, fallbackMessage: "提交确认结果失败", }); return { payload: { data } }; }; export const respondSuperAgentCheckpointStream = async ({ body, userId, onEvent, signal, sessionId, }) => { const requestBody = body || {}; const traceId = createSuperAgentTraceId("checkpoint_respond"); const requestSessionId = sessionId || requestBody.session_id || requestBody.sessionId || ""; superAgentLog("api.checkpoint", "request.start", { traceId, sessionId: requestSessionId, checkpointId: requestBody.checkpoint_id, action: requestBody.action, }); await requestSuperAgentStream({ path: buildSuperAgentPath(superAgentServiceApiTable.threadCommands, { sessionId: requestSessionId, }), userId, body: requestBody, onEvent, signal, fallbackMessage: "提交确认结果失败", traceId, logScope: "api.checkpoint", sessionId: requestSessionId, }); return { success: true, sessionId: requestSessionId, }; }; const requestSuperAgentJson = async (options) => requestAgentJson(options); export const updateSuperAgentFolder = async ({ folderId, userId, body }) => requestSuperAgentJson({ method: "PATCH", path: superAgentServiceApiTable.tasksetDetail, params: { folderId }, query: { userId }, body, fallbackMessage: "更新文件夹失败", }); export const patchSuperAgentSession = async ({ sessionId, userId, body }) => requestSuperAgentJson({ method: "PATCH", path: superAgentServiceApiTable.conversationDetail, params: { sessionId }, query: { userId }, body, fallbackMessage: "更新会话失败", }); export const cancelSuperAgentSession = async ({ sessionId, userId }) => requestSuperAgentJson({ method: "POST", path: superAgentServiceApiTable.threadCancel, params: { sessionId }, query: { userId }, fallbackMessage: "取消任务失败", keepalive: true, }); export const getSuperAgentSessionSectionsDirect = async ({ sessionId, userId, includeContent = 1, }) => { const path = buildSuperAgentPath(superAgentServiceApiTable.artifactChapters, { sessionId, }); const query = new URLSearchParams(); if (userId) query.set("userId", userId); if (includeContent) query.set("include_content", String(includeContent)); query.set("_t", String(Date.now())); const response = await fetch( buildSuperAgentServiceUrl(`${path}${query.toString() ? `?${query.toString()}` : ""}`), { method: "GET", headers: await buildLegacyCompatibleHeaders(), cache: "no-store", } ); return parseJsonResponse(response, "获取正文章节失败"); }; export const getSuperAgentSessionSectionDirect = async ({ sessionId, sectionId, userId, }) => { const path = buildSuperAgentPath(superAgentServiceApiTable.artifactChapter, { sessionId, sectionId, }); const query = new URLSearchParams(); if (userId) query.set("userId", userId); query.set("_t", String(Date.now())); const response = await fetch( buildSuperAgentServiceUrl(`${path}${query.toString() ? `?${query.toString()}` : ""}`), { method: "GET", headers: await buildLegacyCompatibleHeaders(), cache: "no-store", } ); return parseJsonResponse(response, "获取章节内容失败"); }; const getExternalKnowledgeBaseList = basicAction.get("/aichat/knowledge", { tip: false, reducerName: "superAgent", }); //[解析优化后的引用数据] const parseOptimizedQuoteData = (content = "") => { try { //[去除Markdown代码块包裹] const jsonText = content .replace(/^```(?:json)?\s*/i, "") .replace(/```\s*$/i, "") .trim(); const data = JSON.parse(jsonText); return data && typeof data === "object" ? data : null; } catch (error) { return null; } }; //[优化引用内容] const optimizeQuoteData = async (quoteData = {}) => { const content = quoteData?.q || quoteData?.content || ""; if (!content) return quoteData; try { const res = await fetch("/_api/aichat/quote/optimize", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ quoteData, }), }); const data = await res.json(); const optimizedPayload = data?.data && typeof data.data === "object" ? data.data : {}; const optimizedContent = ( optimizedPayload.q || optimizedPayload.content || data?.content || "" ).trim(); if (!res.ok || !optimizedContent) { return quoteData; } const optimizedData = parseOptimizedQuoteData(optimizedContent); if (optimizedData) { return { ...quoteData, ...optimizedPayload, ...optimizedData, q: optimizedData.q || optimizedData.content || optimizedContent, content: optimizedData.content || optimizedData.q || optimizedContent, originalQ: quoteData.q, originalContent: quoteData.content, }; } return { ...quoteData, ...optimizedPayload, q: optimizedContent, content: optimizedContent, originalQ: quoteData.q, originalContent: quoteData.content, }; } catch (error) { console.error("优化引用数据失败:", error); return quoteData; } }; /** * 记录用户查询 * @param {string} question 查询问题 * @param {string} userId 用户ID */ export const recordQuery = async ({ question, userId, clientId, feature, curIp, }) => { try { await request .post(danaApi.oaiQueryRecord) .send({ question, userId, clientId, feature, curIp }); // DanaRequest.post(danaApi.oaiQueryRecord, { // question, // }); } catch (error) { console.error("记录查询失败", error); } }; /** * 获取对话记录列表 * @param {string} chatKey 用户ID * @param {string} appId 应用ID * @returns {Promise} 对话记录列表 */ export const getQueryItems = async (chatKey, appId) => { try { const res = await request.get(danaApi.oaiQueryItems).query({ chat_key: chatKey, app_id: appId, }); return res.body; } catch (error) { console.error("获取对话记录失败", error); return []; } }; /** * 创建或更新对话记录 * @param {Object} params 参数对象 * @param {string} params.chat_id 对话ID * @param {string} params.app_id 应用ID * @param {string} params.title 对话标题 * @returns {Promise} 创建结果 */ export const createOrUpdateChat = async (params) => { try { const res = await request.post(danaApi.oaiQueryItems).send(params); return res.body; } catch (error) { console.error("创建/更新对话失败", error); throw error; } }; /** * 删除对话 * @param {Object} params 参数对象 * @param {string} params.chatId 对话ID */ export const deleteChatItem = async (params) => { return new Promise((resolve, reject) => { request .delete(danaApi.oaiDeleteItem) .query(params) .end((err, res) => { if (err) { console.error("删除对话失败", err); reject(err); } else { resolve(res.body); } }); }); }; /** * 初始化对话内容 * @param {Object} params 参数对象 * @param {string} params.appId 应用ID * @param {string} params.chatId 对话ID * @param {string} params.fastgptAppKey FastGPT应用密钥 * @returns {Promise} 对话内容 */ export const initChatContent = async ({ appId, chatId, fastgptAppKey }) => { try { const url = `/_fgpt/api${fastgptApiTable.chatInit}`; const res = await fetch(url, { method: "POST", headers: { Authorization: `Bearer ${fastgptAppKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ appId, chatId, offset: 0, pageSize: 14, loadCustomFeedbacks: true, }), mode: "cors", }); const data = await res.json(); if (data.code == 200) { return data?.data?.list || []; } else { console.error("获取对话内容失败:", data); return []; } } catch (error) { console.error("获取对话内容失败:", error); throw error; } }; /** * 初始化新对话 * @param {Object} params 参数对象 * @param {string} params.appId 应用ID * @param {string} params.chatId 对话ID * @param {string} params.fastgptAppKey FastGPT应用密钥 * @returns {Promise} 是否成功 */ export const initNewChat = async ({ appId, chatId, fastgptAppKey }) => { try { const url = `/_fgpt/api/core/chat/init?appId=${appId}&chatId=${chatId}`; const res = await fetch(url, { method: "GET", headers: { Authorization: `Bearer ${fastgptAppKey}`, "Content-Type": "application/json", }, }); const data = await res.json(); return data?.code == 200; } catch (error) { console.error("初始化新对话失败:", error); return false; } }; /** * 提交反馈 * @param {Object} params 反馈参数 * @param {string} appId 应用ID * @param {string} chatId 对话ID * @param {string} fastgptAppKey FastGPT应用密钥 * @returns {Promise} 是否成功 */ export const submitFeedback = async (params, appId, chatId, fastgptAppKey) => { try { const url = `/_fgpt${fastgptApiTable.feedback}`; const res = await fetch(url, { method: "POST", headers: { Authorization: `Bearer ${fastgptAppKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ ...params, appId, chatId, }), }); const data = await res.json(); return data?.code == 200; } catch (error) { console.error("提交反馈失败:", error); return false; } }; /** * 获取引用内容 */ export const getQuote = async (params, fastgptAppKey) => { try { const url = `/_fgpt${fastgptApiTable.getQuote}`; const res = await fetch(url, { method: "POST", headers: { Authorization: `Bearer ${fastgptAppKey}`, "Content-Type": "application/json", }, body: JSON.stringify(params), }); const data = await res.json(); return data; } catch (error) { console.error("获取引用内容失败:", error); return false; } }; /** * 获取引用数据 */ export const getQuoteData = async (params, fastgptAppKey, options = {}) => { try { const url = `/_fgpt${fastgptApiTable.getQuoteData}`; const res = await fetch(url, { method: "POST", headers: { Authorization: `Bearer ${fastgptAppKey}`, "Content-Type": "application/json", }, body: JSON.stringify(params), }); const data = await res.json(); if (data?.code === 200 && data?.data && options?.onOptimized) { optimizeQuoteData(data.data).then((optimizedQuoteData) => { options.onOptimized(optimizedQuoteData); }); } return data; } catch (error) { console.error("获取引用数据失败:", error); return false; } }; /** * 获取模型列表 * @param {string} fastgptToken FastGPT令牌 * @returns {Promise} 模型列表 */ export const getModelList = async (fastgptToken) => { try { const res = await fetch(`/_fgpt/api/core/ai/model/list`, { method: "GET", headers: { Token: fastgptToken, "Content-Type": "application/json", }, }); const data = await res.json(); return data.data || []; } catch (error) { console.error("获取模型列表失败:", error); return []; } }; /** * 获取知识库列表 * @param {string} fastgptAppKey FastGPT应用密钥 * @param {string|null} parentId 父级ID * @returns {Promise} 知识库列表 */ export const getDatasetList = async (fastgptAppKey, parentId = null) => { try { const res = await fetch(`/_fgpt/api/core/dataset/list`, { method: "POST", headers: { Authorization: `Bearer ${fastgptAppKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ parentId }), }); const data = await res.json(); const result = []; for (let d of data.data || []) { if (d.name == "CK库表清单") { continue; } if (d.type == "folder") { d.children = await getDatasetList(fastgptAppKey, d._id); } result.push(d); } return result; } catch (error) { console.error("获取知识库列表失败:", error); return []; } }; export { buildSuperAgentServiceUrl, buildSuperAgentPath } from "../utils/agentApi"; const parseJsonResponse = async (response, fallbackMessage) => { const text = await response.text(); let data = {}; try { data = text ? JSON.parse(text) : {}; } catch (error) { superAgentError("api", "json_parse_failed", { status: response.status, fallbackMessage, responseText: text, error, }); throw new Error(text || fallbackMessage); } if (!response.ok) { superAgentError("api", "http_error", { status: response.status, fallbackMessage, data, }); throw new Error(data?.message || data?.error || fallbackMessage); } return data; }; const buildLegacyCompatibleHeaders = async (extraHeaders = {}) => { return buildSuperAgentHeaders(extraHeaders); }; const requestSuperAgentStream = async ({ path, userId, body, onEvent, signal, fallbackMessage, traceId, logScope, sessionId, }) => { const response = await fetch(buildSuperAgentServiceUrl(path), { method: "POST", headers: await buildLegacyCompatibleHeaders({ "Content-Type": "application/json", }), body: JSON.stringify(body), signal, }); superAgentLog(logScope, "response.headers", { traceId, sessionId, ok: response.ok, status: response.status, contentType: response.headers.get("content-type"), }); if (!response.ok) { await parseJsonResponse(response, fallbackMessage); return; } if (!response.body) { throw new Error("当前浏览器不支持流式响应"); } const reader = response.body.getReader(); const decoder = new TextDecoder("utf-8"); let buffer = ""; let eventCount = 0; let byteCount = 0; const emitParsedEvent = (block) => { const lines = block.split(/\r?\n/); let eventName = "message"; const dataLines = []; lines.forEach((line) => { if (line.startsWith("event:")) { eventName = line.slice(6).trim() || "message"; } if (line.startsWith("data:")) { dataLines.push(line.slice(5).trim()); } }); const rawData = dataLines.join("\n"); let data = rawData; try { data = rawData ? JSON.parse(rawData) : {}; } catch (error) { data = { content: rawData }; } eventCount += 1; superAgentLog(logScope, "sse.event", { traceId, sessionId, eventCount, event: eventName, data: summarizeSuperAgentPayload(data), }); onEvent({ event: eventName, data, }); }; while (true) { const { done, value } = await reader.read(); if (done) break; byteCount += value?.byteLength || 0; buffer += decoder.decode(value, { stream: true }); const blocks = buffer.split(/\n\n/); buffer = blocks.pop() || ""; blocks.forEach((block) => { const trimmedBlock = block.trim(); if (!trimmedBlock) return; emitParsedEvent(trimmedBlock); }); } const lastBlock = buffer.trim(); if (lastBlock) { emitParsedEvent(lastBlock); } superAgentLog(logScope, "stream.done", { traceId, sessionId, eventCount, byteCount, }); }; /** * 功能:发送超级智能体对话并消费 SSE 事件 * 场景:普通文本、文件上传后的任务推进、checkpoint 后续指令都从这里进入 * 注意:后端事件名是业务事实来源,前端只做展示和交互承接 * 这是一个特殊的流式接口,不能使用basicAction,需要自定义实现 */ export const sendSuperAgentChat = async ({ sessionId, userId, message, displayContent, attachmentIds = [], onEvent, signal, }) => { const traceId = createSuperAgentTraceId("chat"); superAgentLog("api.chat", "request.start", { traceId, sessionId, userId, message, }); const chatPath = buildSuperAgentPath(superAgentServiceApiTable.threadEvents, { sessionId, }); await requestSuperAgentStream({ path: chatPath, userId, body: { session_id: sessionId, content: message, display_content: displayContent || message, ...(attachmentIds.length ? { attachment_ids: attachmentIds } : {}), }, onEvent, signal, fallbackMessage: "超级智能体对话失败", traceId, logScope: "api.chat", sessionId, }); }; /** * 功能:上传标书相关文件 * 场景:用户在输入区或流程卡片上传招标文件、附件、补充材料 * 注意:上传完成后仍然由任务消息流接口继续驱动后续任务 * 这是一个特殊的文件上传接口,不能使用basicAction,需要自定义实现 */ export const uploadSuperAgentFile = async ({ sessionId, userId, file, onProgress }) => { const traceId = createSuperAgentTraceId("upload"); superAgentLog("api.upload", "request.start", { traceId, sessionId, userId, file, }); const formData = new FormData(); formData.append("session_id", sessionId); formData.append("file", file); const uploadPath = buildSuperAgentPath(superAgentServiceApiTable.attachmentUploadIntents, { conversationId: sessionId, }); const agentHeaders = await buildLegacyCompatibleHeaders(); const result = await new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open("POST", buildSuperAgentServiceUrl(uploadPath)); Object.entries(agentHeaders).forEach(([key, value]) => { if (value !== undefined && value !== null && value !== "") { xhr.setRequestHeader(key, value); } }); xhr.upload.onprogress = (event) => { const total = Number(event.total || file?.size || 0); const loaded = Number(event.loaded || 0); const percent = total > 0 ? Math.min(100, Math.round((loaded / total) * 100)) : 0; if (typeof onProgress === "function") { onProgress({ loaded, total, percent }); } }; xhr.onerror = () => reject(new Error("上传文件失败,请检查网络连接")); xhr.onabort = () => reject(new Error("文件上传已取消")); xhr.onload = () => { let data = {}; try { data = xhr.responseText ? JSON.parse(xhr.responseText) : {}; } catch (error) { superAgentError("api.upload", "json_parse_failed", { traceId, sessionId, status: xhr.status, responseText: xhr.responseText, error, }); reject(new Error(xhr.responseText || "上传文件失败")); return; } if (xhr.status < 200 || xhr.status >= 300) { superAgentError("api.upload", "http_error", { traceId, sessionId, status: xhr.status, data, }); reject(new Error(data?.message || data?.error || "上传文件失败")); return; } resolve(data); }; xhr.send(formData); }); superAgentLog("api.upload", "response.done", { traceId, sessionId, result, }); return result; }; export const buildSuperAgentDownloadUrl = (filePath, sessionId = "") => { const query = new URLSearchParams({ path: filePath || "" }); if (sessionId) { query.set("session_id", sessionId); } return buildSuperAgentServiceUrl(`${superAgentServiceApiTable.attachments}?${query.toString()}`); }; export const buildSuperAgentSessionDownloadUrl = (sessionId, userId = "", options = {}) => { const path = buildSuperAgentPath(superAgentServiceApiTable.artifacts, { sessionId, }); const query = new URLSearchParams(); if (userId) { query.set("userId", userId); } if (options.draft) { query.set("draft", "1"); } const queryString = query.toString(); return buildSuperAgentServiceUrl(`${path}${queryString ? `?${queryString}` : ""}`); }; export const updateSuperAgentSessionSectionDirect = async ({ sessionId, sectionId, userId, content, status = "edited", title = "", order, }) => { const traceId = createSuperAgentTraceId("section_save"); const path = buildSuperAgentPath(superAgentServiceApiTable.artifactChapter, { sessionId, sectionId, }); const query = new URLSearchParams(); if (userId) { query.set("userId", userId); } superAgentLog("api.section", "update.start", { traceId, sessionId, sectionId, userId, status, title, order, content, }); const response = await fetch(buildSuperAgentServiceUrl(`${path}${query.toString() ? `?${query.toString()}` : ""}`), { method: "PUT", headers: await buildLegacyCompatibleHeaders({ "Content-Type": "application/json", }), body: JSON.stringify({ content: content || "", status, title, order, }), }); const result = await parseJsonResponse(response, "保存章节内容失败"); superAgentLog("api.section", "update.done", { traceId, sessionId, sectionId, result, }); return result; }; export default { getExternalKnowledgeBaseList, };