ai-query对接新版freesun-agent接口的分支
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

951 lines
26 KiB

/**
* 文件作用: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<Array>} 对话记录列表
*/
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<Object>} 创建结果
*/
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<Array>} 对话内容
*/
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<boolean>} 是否成功
*/
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<boolean>} 是否成功
*/
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<Array>} 模型列表
*/
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<Array>} 知识库列表
*/
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,
};