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.
 
 
 

1873 lines
66 KiB

/**
* 文件作用:superAgent 会话与对话流状态 hook
* 职责范围:
* 1. 管理普通对话、标书任务会话和左侧列表状态
* 2. 处理流式消息、工具事件、checkpoint 事件
* 3. 处理新建会话、文件上传、发送消息和历史会话切换
*
* 不负责:
* - 渲染对话 UI
* - 渲染右侧工作区
* - 管理登录弹窗 UI
*
* 维护说明:
* - 标书任务身份由后端 session 加本地 tender session 标记共同维护
*/
"use strict";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { message } from "antd";
import {
cancelSuperAgentSession,
createSuperAgentSession,
deleteSuperAgentSession,
respondSuperAgentCheckpointStream,
sendSuperAgentChat,
uploadSuperAgentFile,
} from "../actions/api";
import { createResourceFileFromUpload } from "../actions/resourceLibrary";
import useSuperAgentSidebar from "./useSuperAgentSidebar";
import {
TASK_STAGE,
TASK_WELCOME_CONTENT,
TASK_WELCOME_TITLE,
TENDER_TASK_WELCOME_CONTENT,
TENDER_TASK_WELCOME_TITLE,
} from "../constants";
import {
clearPendingEmptySession,
clearActiveSessionId,
getSuperAgentToolArguments,
getSuperAgentToolCallId,
getSuperAgentToolName,
getSuperAgentToolResult,
isSuperAgentQuestionTool,
isSuperAgentRunLogTool,
markTenderSessionId,
markPendingEmptySession,
removeTenderSessionId,
resolveApiFolderId,
saveActiveSessionId,
} from "../utils/session";
import {
createSuperAgentTraceId,
summarizeSuperAgentPayload,
superAgentError,
superAgentLog,
superAgentWarn,
} from "../utils/debugLog";
import {
mergeCheckpointEventData,
} from "../utils/workspace";
import { normalizeCheckpoint } from "../utils/checkpoint";
import { isWorkflowBodyText } from "../utils/tenderWorkflow";
const getActionData = (res) => {
const payload = res?.payload ?? res;
return payload?.data ?? payload;
};
const ensureActionSuccess = (res, fallbackMessage) => {
const payload = res?.payload ?? res;
if (payload?.success === false) {
throw new Error(payload?.message || payload?.error || fallbackMessage);
}
return getActionData(res);
};
const buildSessionParams = (sessionId) => ({
sessionId,
":sessionId": sessionId,
});
const hasFilePathPrefix = (filePath = "") => {
return /^(https?:)?\/\//i.test(filePath) || /^(data|blob):/i.test(filePath);
};
const buildQiniuFilePath = (filePath = "") => {
const cleanFilePath = String(filePath || "").trim();
if (!cleanFilePath || hasFilePathPrefix(cleanFilePath)) return cleanFilePath;
const qiniuDomain = window?.env?.FS_QINIU_DOMAIN || "";
const cleanDomain = String(qiniuDomain || "").replace(/\/+$/, "");
if (!cleanDomain) return cleanFilePath;
return `${cleanDomain}/${cleanFilePath.replace(/^\/+/, "")}`;
};
//[文件上下文] 上传文件只向智能体传递访问地址,不附加固定业务文案。
const buildUploadedFileReference = (fileList = []) => {
return fileList
.map((file) => {
const fileName = file?.name || file?.originalFilename || "未命名文件";
const rawFilePath = file?.url || file?.path || file?.objectKey || file?.localPath || "";
const filePath = buildQiniuFilePath(rawFilePath);
if (!filePath) return "";
return `(文件名:${fileName},文件路径:${filePath}`;
})
.filter(Boolean)
.join("\n");
};
const useSuperAgentChat = ({
currentUserId,
openAuthModal,
setWorkspaceOpen,
actions,
dispatch,
}) => {
const [routeType, setRouteType] = useState("chat");
const [inputValue, setInputValue] = useState("");
const {
sidebarTree,
taskList,
ordinaryChatList,
isLoadingSidebar,
collapsedFolderMap,
loadSidebar,
patchSessionInTree,
removeSessionFromTree,
toggleFolderCollapsed,
handleCreateFolder,
handleRenameFolder,
handleDeleteFolder,
handleRenameSession,
handleMoveSession,
} = useSuperAgentSidebar({ currentUserId, actions, dispatch });
const [activeTaskId, setActiveTaskId] = useState("");
const [taskStage, setTaskStage] = useState(TASK_STAGE.IDLE);
const [conversationMessages, setConversationMessages] = useState([]);
const [activeSessionId, setActiveSessionId] = useState("");
const isLoadingSessions = isLoadingSidebar;
const [isUploadingFile, setIsUploadingFile] = useState(false);
const [uploadProgress, setUploadProgress] = useState(null);
const [isStreamingAnswer, setIsStreamingAnswer] = useState(false);
const [isCreatingChat, setIsCreatingChat] = useState(false);
const [isCreatingTask, setIsCreatingTask] = useState(false);
const [uploadedFileList, setUploadedFileList] = useState([]);
const [agentCategory, setAgentCategory] = useState("全部应用");
const [agentKeyword, setAgentKeyword] = useState("");
/** 任务大厅已选类型:null 表示尚未选择;tender 表示已进入标书引导 */
const [selectedTaskType, setSelectedTaskType] = useState(null);
const chatAbortControllerRef = useRef(null);
const streamingMessageKeyRef = useRef("");
const chatRunPromiseRef = useRef(Promise.resolve());
const chatRunActiveRef = useRef(false);
const creatingSessionRef = useRef(false);
const sidebarRefreshTimerRef = useRef(null);
const artifactProgressSessionRef = useRef("");
const taskProgressUpsertAtRef = useRef(0);
const taskProgressPendingRef = useRef(null);
const taskProgressTimerRef = useRef(null);
const activeSessionIdRef = useRef("");
useEffect(() => {
activeSessionIdRef.current = activeSessionId;
}, [activeSessionId]);
const activeTask = useMemo(
() => taskList.find((task) => task.id === activeTaskId) || null,
[activeTaskId, taskList]
);
const activeChat = useMemo(
() =>
ordinaryChatList.find((chat) => {
const chatId = chat.id || chat.chatId;
return chatId === activeSessionId;
}) || null,
[activeSessionId, ordinaryChatList]
);
const findSessionTypeById = (sessionId) => {
if (!sessionId) return "";
const matchedTask = taskList.find((task) => {
const taskId = task.id || task.chatId;
return taskId === sessionId;
});
if (matchedTask) return "task";
const matchedChat = ordinaryChatList.find((chat) => {
const chatId = chat.id || chat.chatId;
return chatId === sessionId;
});
if (matchedChat) return "conversation";
return "";
};
const terminalCheckpointStatuses = new Set([
"confirmed",
"edited",
"completed",
"done",
"success",
"cancelled",
"canceled",
]);
const isTaskRoute = routeType === "task";
const isAgentRoute = routeType === "agents";
const isResourceLibraryRoute = routeType === "assets";
const breadcrumbTitle = isAgentRoute
? "发现智能体"
: isResourceLibraryRoute
? "资源库"
: isTaskRoute
? activeTask?.title || "新任务"
: activeChat?.title || "新对话";
const updateTaskById = (taskId, patch) => {
if (!taskId) return;
patchSessionInTree(taskId, { ...patch, updatedAt: Date.now() });
};
const updateActiveTask = (patch) => {
if (!activeTaskId) return;
updateTaskById(activeTaskId, patch);
};
const isDefaultSessionTitle = (title) => {
const value = String(title || "").trim().toLowerCase();
return (
!value ||
value === "新对话" ||
value === "未命名任务" ||
value === "new session"
);
};
const buildInstantSessionTitle = (text) => {
const title = String(text || "").replace(/\s+/g, " ").trim();
if (!title) return "";
return title.length > 24 ? `${title.slice(0, 24)}...` : title;
};
const patchSessionTitleAfterUserMessage = ({
sessionId,
isTaskSession = false,
text = "",
}) => {
if (!sessionId) return;
const currentSessionList = isTaskSession ? taskList : ordinaryChatList;
const currentSession = currentSessionList.find((item) => {
const itemId = item.id || item.chatId;
return itemId === sessionId;
});
if (currentSession && !isDefaultSessionTitle(currentSession.title)) return;
const title = buildInstantSessionTitle(text);
if (!title) return;
patchSessionInTree(sessionId, {
title,
time: Date.now(),
updatedAt: Date.now(),
});
};
const addAssistantMessage = (messageData) => {
setConversationMessages((prev) => [
...prev,
{
key: `assistant_${Date.now()}_${prev.length}`,
role: "assistant",
...messageData,
},
]);
};
const upsertAssistantMessage = (messageData) => {
const messageKey = messageData?.key;
if (!messageKey) {
addAssistantMessage(messageData);
return;
}
setConversationMessages((prev) => {
const hasMessage = prev.some((item) => item.key === messageKey);
if (!hasMessage) {
return [
...prev,
{
role: "assistant",
...messageData,
},
];
}
const shouldSyncBodyCheckpoint =
messageData.type === "taskProgress" && messageData.task;
return prev.map((item) => {
if (item.key === messageKey) return { ...item, ...messageData };
if (!shouldSyncBodyCheckpoint || item.type !== "checkpoint") return item;
const checkpoint = item?.checkpoint?.checkpoint || item?.checkpoint || {};
const uiType = String(checkpoint.ui_type || checkpoint.uiType || "");
if (uiType !== "tree-markdown") return item;
const nextTask = {
...(item.task || {}),
...messageData.task,
};
return {
...item,
task: nextTask,
checkpoint: {
...(item.checkpoint || {}),
task: nextTask,
},
};
});
});
};
const addUserMessage = (content, fileList = [], messageSessionId = "") => {
clearPendingEmptySession(activeSessionId || activeTaskId);
setConversationMessages((prev) => [
...prev,
{
key: `user_${Date.now()}_${prev.length}`,
role: "user",
content,
files: fileList,
sessionId: messageSessionId || activeSessionId || activeTaskId,
},
]);
};
const updateConversationMessage = (messageKey, updater) => {
setConversationMessages((prev) =>
prev.map((item) => {
if (item.key !== messageKey) return item;
const patch = typeof updater === "function" ? updater(item) : updater;
return { ...item, ...patch };
})
);
};
const removeConversationMessage = (messageKey) => {
if (!messageKey) return;
setConversationMessages((prev) => prev.filter((item) => item.key !== messageKey));
};
useEffect(() => {
return () => {
if (chatAbortControllerRef.current) {
chatAbortControllerRef.current.abort();
}
if (sidebarRefreshTimerRef.current) {
window.clearTimeout(sidebarRefreshTimerRef.current);
}
if (taskProgressTimerRef.current) {
window.clearTimeout(taskProgressTimerRef.current);
}
};
}, []);
const removeSidebarSessionLocally = (sessionId, itemType) => {
if (!sessionId) return;
clearPendingEmptySession(sessionId);
if (itemType === "task") {
removeTenderSessionId(sessionId);
}
removeSessionFromTree(sessionId);
};
const clearActiveSidebarSession = (sessionId, shouldForce = false) => {
if (!shouldForce && activeSessionId !== sessionId) return;
clearActiveSessionId(sessionId);
setActiveSessionId("");
setActiveTaskId("");
setTaskStage(TASK_STAGE.IDLE);
setConversationMessages([]);
setUploadedFileList([]);
setRouteType("chat");
setWorkspaceOpen(false);
};
const createAndActivateSession = async ({
isTaskSession = false,
sessionKind,
folderId = null,
title = "",
shouldResetMessages = false,
} = {}) => {
if (!currentUserId) {
openAuthModal("login");
throw new Error("请先登录后再使用飞小尚超级智能体");
}
const sessionType = sessionKind || (isTaskSession ? "task" : "conversation");
const requestBody = { type: sessionType, reuse_empty: true };
const apiFolderId = resolveApiFolderId(folderId);
if (apiFolderId) requestBody.folder_id = apiFolderId;
if (title) requestBody.title = title;
const session = await dispatch(createSuperAgentSession({
query: { userId: currentUserId },
body: requestBody,
})).then((res) => ensureActionSuccess(res, "创建超级智能体会话失败"));
const sessionData = session?.data || session;
const nextSessionId = sessionData?.session_id || sessionData?.id;
if (!nextSessionId) throw new Error("创建会话失败,后端未返回 session_id");
const returnedType =
sessionData?.session_kind ||
(sessionData?.type === "task" || sessionData?.type === "conversation"
? sessionData.type
: sessionType);
const isReused = Boolean(sessionData?.reused);
superAgentLog("chat.session", "create_or_reuse.done", {
sessionId: nextSessionId,
reused: isReused,
sessionType: returnedType,
folderId: apiFolderId,
});
if (isReused) {
const tipText = returnedType === "task"
? "已有未开始的任务,已为您定位"
: "已有未开始的对话,已为您定位";
message.info(tipText);
}
setActiveSessionId(nextSessionId);
saveActiveSessionId(nextSessionId);
setWorkspaceOpen(false);
setUploadedFileList([]);
markPendingEmptySession(nextSessionId, returnedType, apiFolderId);
if (shouldResetMessages) setConversationMessages([]);
if (returnedType === "task") {
markTenderSessionId(nextSessionId);
setActiveTaskId(nextSessionId);
} else {
removeTenderSessionId(nextSessionId);
setActiveTaskId("");
}
await loadSidebar({ silent: true });
return nextSessionId;
};
const getCurrentSessionType = () => {
if (!activeSessionId) return "";
const sidebarSessionType = findSessionTypeById(activeSessionId);
if (sidebarSessionType) return sidebarSessionType;
if (activeTaskId && activeSessionId === activeTaskId) return "task";
return "conversation";
};
const ensureActiveSession = async (options = {}) => {
const shouldCreateTaskSession =
options.isTaskSession === undefined ? isTaskRoute : options.isTaskSession;
const expectedSessionType = shouldCreateTaskSession ? "task" : "conversation";
const currentSessionType = getCurrentSessionType();
if (activeSessionId && currentSessionType === expectedSessionType) {
return activeSessionId;
}
return createAndActivateSession({ isTaskSession: shouldCreateTaskSession });
};
const appendStreamingText = (messageKey, text) => {
if (!text) return;
updateConversationMessage(messageKey, (item) => ({
title: item.title || "飞小尚",
content: `${item.content || ""}${text}`,
}));
};
//[合并实时日志事件,使用事件 id 去重并累计询问、命令次数]
const updateRunLogFromEvent = (messageKey, eventName, eventData = {}) => {
updateConversationMessage(messageKey, (item) => {
const currentRunLog = item.runLog || {};
const startedAt = currentRunLog.startedAt || Date.now();
const toolName = getSuperAgentToolName(eventData) || "工具";
const eventId = getSuperAgentToolCallId(eventData);
const handledEventIds = Array.isArray(currentRunLog.handledEventIds)
? currentRunLog.handledEventIds
: [];
const currentEntries = Array.isArray(currentRunLog.entries)
? currentRunLog.entries
: [];
const shouldCountTool = eventName === "tool_start" && isSuperAgentRunLogTool(toolName);
const fallbackRunningEntry = currentEntries.find(
(entry) => entry.name === toolName && entry.status === "running"
);
const countKey =
eventId ||
fallbackRunningEntry?.id ||
`${toolName}_${handledEventIds.length + 1}`;
const matchedIndex = currentEntries.findIndex((entry) => {
if (eventId) return entry.id === eventId;
return entry.id === countKey || (entry.name === toolName && entry.status === "running");
});
const isResultEvent = eventName === "tool_result" || eventName === "tool_end";
const isNewTool =
shouldCountTool &&
matchedIndex < 0 &&
!handledEventIds.includes(countKey);
const isQuestion = isNewTool && isSuperAgentQuestionTool(toolName);
let nextEntries = currentEntries;
if (isNewTool) {
nextEntries = [
...currentEntries,
{
id: countKey,
name: toolName,
kind: isQuestion ? "question" : "command",
status: "running",
arguments: getSuperAgentToolArguments(eventData),
result: "",
},
];
}
if (isResultEvent) {
if (matchedIndex >= 0) {
nextEntries = nextEntries.map((entry, index) =>
index === matchedIndex
? {
...entry,
status: "done",
result: getSuperAgentToolResult(eventData),
}
: entry
);
} else if (isSuperAgentRunLogTool(toolName)) {
nextEntries = [
...nextEntries,
{
id: countKey,
name: toolName,
kind: isSuperAgentQuestionTool(toolName) ? "question" : "command",
status: "done",
arguments: getSuperAgentToolArguments(eventData),
result: getSuperAgentToolResult(eventData),
},
];
}
}
return {
runLog: {
...currentRunLog,
startedAt,
status: "running",
questionCount: Number(currentRunLog.questionCount || 0) + (isQuestion ? 1 : 0),
commandCount:
Number(currentRunLog.commandCount || 0) + (isNewTool && !isQuestion ? 1 : 0),
elapsedMs: Date.now() - startedAt,
handledEventIds: isNewTool
? [...handledEventIds, countKey]
: handledEventIds,
entries: nextEntries,
},
};
});
};
//[结束当前运行日志并记录本轮处理耗时]
const finishRunLog = (runLog = null) => {
if (!runLog) return null;
const startedAt = Number(runLog.startedAt || 0);
return {
...runLog,
status: "done",
elapsedMs: startedAt ? Date.now() - startedAt : Number(runLog.elapsedMs || 0),
entries: (runLog.entries || []).map((entry) => ({
...entry,
status: "done",
})),
};
};
const clearWaitingNoticeMessages = () => {
setConversationMessages((prev) =>
prev.filter((item) => !item.isWaitingNotice)
);
};
const normalizeProgressEventData = (eventData = {}) => {
const task = eventData.task || eventData.data?.task || eventData;
return {
task_type: task?.task_type || task?.taskType || "",
status: task?.status || "",
stage: task?.stage || eventData.stage || "",
progress: Number(task?.progress ?? eventData.progress ?? 0),
section_id: task?.section_id || task?.sectionId || eventData.section_id || eventData.sectionId || "",
section_title: task?.section_title || task?.sectionTitle || eventData.section_title || eventData.sectionTitle || "",
done: Number(task?.done ?? eventData.done ?? 0),
total: Number(task?.total ?? eventData.total ?? 0),
};
};
const scheduleSidebarRefresh = useCallback(() => {
if (sidebarRefreshTimerRef.current) {
window.clearTimeout(sidebarRefreshTimerRef.current);
}
//[done 后延迟刷新侧栏,避免与对话收尾同一帧抢布局]
sidebarRefreshTimerRef.current = window.setTimeout(() => {
sidebarRefreshTimerRef.current = null;
loadSidebar({ silent: true });
}, 1800);
}, [loadSidebar]);
const upsertTaskProgressMessage = (sessionId, progressData, options = {}) => {
if (!sessionId) return;
const progress = Math.max(0, Math.min(100, Number(progressData.progress || 0)));
const stageText = progressData.stage || "任务处理中";
const statusText = progressData.status || "running";
const normalizedTask = {
...progressData,
stage: stageText,
progress,
status: statusText,
};
updateTaskById(sessionId, {
stage: stageText,
progress,
status: statusText,
taskType: progressData.task_type || "",
});
if (options.suppressMessage) return;
const flushProgressMessage = (taskPayload) => {
upsertAssistantMessage({
key: `session_progress_${sessionId}`,
role: "assistant",
type: "taskProgress",
title: "正文生成",
task: taskPayload,
sessionId,
});
};
const isTerminal =
progress >= 100 ||
["reviewing", "writed", "completed", "failed", "cancelled", "error"].includes(
String(statusText || "").toLowerCase()
);
const now = Date.now();
if (isTerminal || now - taskProgressUpsertAtRef.current >= 800) {
taskProgressUpsertAtRef.current = now;
taskProgressPendingRef.current = null;
flushProgressMessage(normalizedTask);
return;
}
//[进度卡节流:合并短时间内的多次 progress 事件]
taskProgressPendingRef.current = normalizedTask;
if (!taskProgressTimerRef.current) {
taskProgressTimerRef.current = window.setTimeout(() => {
taskProgressTimerRef.current = null;
const pending = taskProgressPendingRef.current;
if (!pending) return;
taskProgressPendingRef.current = null;
taskProgressUpsertAtRef.current = Date.now();
flushProgressMessage(pending);
}, 800);
}
};
const extractEventText = (data) => {
if (!data || typeof data !== "object") return "";
return (
data.content ||
data.text ||
data.message ||
data.delta ||
data.chunk ||
data.token ||
data?.choices?.[0]?.delta?.content ||
data?.choices?.[0]?.text ||
data?.data?.content ||
data?.data?.text ||
""
);
};
const handleSuperAgentEvent = (eventPayload, assistantMessageKey, options = {}) => {
const eventName = eventPayload?.event || "message";
const eventData = eventPayload?.data || {};
const eventText = extractEventText(eventData);
superAgentLog("chat.event", "handle", {
sessionId: options.sessionId,
assistantMessageKey,
eventName,
checkpointId:
eventData?.checkpoint_id ||
eventData?.checkpointId ||
eventData?.checkpoint?.checkpoint_id ||
eventData?.checkpoint?.checkpointId,
checkpointType:
eventData?.checkpoint_type ||
eventData?.checkpoint?.checkpoint_type ||
eventData?.checkpoint?.type,
progress: eventData?.progress ?? eventData?.task?.progress,
status: eventData?.status ?? eventData?.task?.status,
textLen: eventText?.length || 0,
data: summarizeSuperAgentPayload(eventData),
});
if (["text", "token", "content", "delta", "chunk"].includes(eventName)) {
if (eventText) clearWaitingNoticeMessages();
appendStreamingText(assistantMessageKey, eventText);
return;
}
if (["thinking", "reasoning", "thought", "tool_start", "tool_result", "tool_end"].includes(eventName)) {
updateRunLogFromEvent(assistantMessageKey, eventName, eventData);
return;
}
if (eventName === "progress") {
const progressData = normalizeProgressEventData(eventData);
const sectionTitle = progressData.section_title || progressData.stage || "正文";
const doneText =
progressData.done && progressData.total
? `${progressData.done}/${progressData.total}`
: "";
clearWaitingNoticeMessages();
const progress = Math.max(0, Math.min(100, Number(progressData.progress || 0)));
const statusText = progressData.status || "running";
const normalizedStatus = String(statusText).toLowerCase();
const isArtifactProgress = progressData.task_type === "artifact_build";
const isArtifactCompleted =
isArtifactProgress &&
!["failed", "cancelled", "error"].includes(normalizedStatus) &&
(progress >= 100 ||
["reviewing", "writed", "completed", "done", "success"].includes(
normalizedStatus
));
if (isArtifactProgress) {
artifactProgressSessionRef.current = options.sessionId;
}
if (isArtifactCompleted) {
}
updateConversationMessage(assistantMessageKey, (item) => ({
title: progress >= 100 ? "正文生成完成" : `正在生成:${sectionTitle}`,
loadingText:
progress >= 100
? `正文已生成完成${doneText},可点击按钮打开正文工作区`
: `正文生成中${doneText},可点击按钮打开正文工作区`,
task: {
...progressData,
progress,
status: statusText,
},
}));
//[正文进度使用独立进度卡session_progress_渲染,流式消息不转为taskProgress避免重复卡片]
upsertTaskProgressMessage(options.sessionId, progressData);
return;
}
if (eventName === "checkpoint") {
const checkpointData =
eventData.checkpoint || eventData.data?.checkpoint || eventData;
const taskData =
eventData.task || eventData.data?.task || checkpointData.task || null;
clearWaitingNoticeMessages();
const checkpointId =
checkpointData.checkpoint_id || checkpointData.checkpointId || checkpointData.id || "";
setConversationMessages((prev) => {
const existingCheckpoint = prev.find((item) => {
const itemCheckpointId =
item?.checkpoint?.checkpoint_id ||
item?.checkpoint?.checkpointId ||
item?.checkpoint?.id ||
"";
return checkpointId && itemCheckpointId === checkpointId;
});
const buildCheckpointCard = (item, mergedCheckpointData) => {
const normalizedCheckpoint = normalizeCheckpoint(mergedCheckpointData);
const localStatus = String(item?.checkpointStatus || "").toLowerCase();
const keepsLocalStatus =
localStatus === "running" ||
localStatus === "submitting" ||
terminalCheckpointStatuses.has(localStatus);
return {
type: "checkpoint",
title: normalizedCheckpoint.title,
content: normalizedCheckpoint.description,
checkpoint: normalizedCheckpoint,
task: taskData,
sessionId: options.sessionId,
checkpointStatus: keepsLocalStatus
? localStatus
: normalizedCheckpoint.checkpoint_status,
loadingText: "",
};
};
const canRemoveStreamingMessage = (item = {}) => {
if (!item || item.key !== assistantMessageKey) return false;
if (String(item.content || "").trim()) return false;
if (item.runLog) return false;
return true;
};
const buildCheckpointKey = (fallbackItem = {}) => {
if (fallbackItem.key && fallbackItem.type === "checkpoint") return fallbackItem.key;
if (checkpointId && options.sessionId) {
return `state_checkpoint_${options.sessionId}_${checkpointId}`;
}
return `checkpoint_${assistantMessageKey}`;
};
if (existingCheckpoint && existingCheckpoint.key !== assistantMessageKey) {
const mergedCheckpointData = mergeCheckpointEventData(
existingCheckpoint,
checkpointData
);
const checkpointCard = buildCheckpointCard(
existingCheckpoint,
mergedCheckpointData
);
return prev
.filter((item) => !canRemoveStreamingMessage(item))
.map((item) =>
item.key === existingCheckpoint.key
? { ...item, ...checkpointCard }
: item
);
}
const currentItem = prev.find((item) => item.key === assistantMessageKey) || {};
const mergedCheckpointData = mergeCheckpointEventData(currentItem, checkpointData);
const checkpointCard = buildCheckpointCard(currentItem, mergedCheckpointData);
const shouldReplaceCurrentItem = canRemoveStreamingMessage(currentItem);
if (shouldReplaceCurrentItem) {
return prev.map((item) =>
item.key === assistantMessageKey ? { ...item, ...checkpointCard } : item
);
}
const nextCheckpointCard = {
...checkpointCard,
key: buildCheckpointKey(currentItem),
role: "assistant",
};
const hasCheckpointCard = prev.some((item) => item.key === nextCheckpointCard.key);
if (hasCheckpointCard) {
return prev.map((item) =>
item.key === nextCheckpointCard.key
? { ...item, ...nextCheckpointCard }
: item
);
}
return [...prev, nextCheckpointCard];
});
return;
}
if (eventName === "checkpoint_done") {
const checkpointId = eventData.checkpoint_id || eventData.checkpointId || eventData.id;
setConversationMessages((prev) =>
prev.map((item) => {
const itemCheckpointId =
item?.checkpoint?.checkpoint_id ||
item?.checkpoint?.checkpointId ||
item?.checkpoint?.id;
if (!checkpointId || itemCheckpointId !== checkpointId) return item;
const localStatus = String(item.checkpointStatus || "").toLowerCase();
if (terminalCheckpointStatuses.has(localStatus)) return item;
return {
...item,
checkpointStatus: "running",
};
})
);
return;
}
if (eventName === "done") {
if (artifactProgressSessionRef.current === options.sessionId) {
artifactProgressSessionRef.current = "";
}
clearWaitingNoticeMessages();
updateConversationMessage(assistantMessageKey, (item) => {
//[旁路答疑或正常流式:已有正文则保留;checkpoint 卡片本身不算失败]
if (item.type === "checkpoint") {
return {
title: item.title || "需要确认",
loadingText: "",
runLog: finishRunLog(item.runLog),
};
}
const hasAnswerContent = Boolean(item.content || eventText);
if (!hasAnswerContent) {
if (options.allowEmptyDone) {
return {
title: "飞小尚",
content: "已确认,正在继续推进任务。",
runLog: finishRunLog(item.runLog),
};
}
return {
title: "处理失败",
content: "超级智能体未返回有效内容,请检查服务连接或稍后重试。",
runLog: finishRunLog(item.runLog),
};
}
const answerContent = item.content || eventText;
//[旁路答疑:若模型误吐标书流程文案,替换为简短提示,避免弹出流程卡]
if (
(options.sideQa || item.sideQa) &&
isWorkflowBodyText(answerContent)
) {
return {
title: "飞小尚",
content:
"当前仍在确认卡片旁路答疑阶段,我只能回答与当前任务/卡片相关的简单问题,不会重新展示标书生成流程。请继续提问,或点击上方确认卡片按钮推进任务。",
loadingText: "",
sideQa: true,
runLog: finishRunLog(item.runLog),
};
}
return {
title: item.title || "飞小尚",
content: answerContent,
loadingText: "",
sideQa: Boolean(options.sideQa || item.sideQa),
runLog: finishRunLog(item.runLog),
};
});
scheduleSidebarRefresh();
return;
}
if (eventName === "error") {
if (artifactProgressSessionRef.current === options.sessionId) {
artifactProgressSessionRef.current = "";
}
clearWaitingNoticeMessages();
updateConversationMessage(assistantMessageKey, (item) => ({
title: "处理失败",
content: eventText || eventData.error || "超级智能体处理失败,请稍后重试。",
runLog: finishRunLog(item.runLog),
}));
return;
}
if (eventText) clearWaitingNoticeMessages();
if (eventText) appendStreamingText(assistantMessageKey, eventText);
};
const executeMessageToAgent = async (messageText, options = {}) => {
const content = String(messageText || "").trim();
if (!content) return;
const traceId = createSuperAgentTraceId("send");
let nextAbortController = null;
try {
const sessionId =
options.sessionId ||
(await ensureActiveSession({ isTaskSession: options.isTaskSession }));
superAgentLog("chat.send", "start", {
traceId,
sessionId,
currentUserId,
isStreamingAnswer,
content,
options,
});
if (!options.skipUserMessage) {
clearPendingEmptySession(sessionId);
const hasUserDisplayText = Object.prototype.hasOwnProperty.call(
options,
"userDisplayText"
);
const userDisplayText = hasUserDisplayText ? options.userDisplayText : content;
addUserMessage(userDisplayText, options.userFiles || [], sessionId);
patchSessionTitleAfterUserMessage({
sessionId,
isTaskSession: Boolean(options.isTaskSession),
text: userDisplayText || content,
});
}
if (chatAbortControllerRef.current) chatAbortControllerRef.current.abort();
nextAbortController = new AbortController();
chatAbortControllerRef.current = nextAbortController;
setIsStreamingAnswer(true);
const runChatOnce = async ({
chatMessage,
assistantTitle,
loadingText,
allowEmptyDone = false,
}) => {
const assistantMessageKey = `assistant_stream_${Date.now()}_${Math.random()}`;
const runState = {
hasCheckpoint: false,
hasError: false,
};
const sideQa = Boolean(options.sideQa);
streamingMessageKeyRef.current = assistantMessageKey;
setConversationMessages((prev) => [
...prev,
{
key: assistantMessageKey,
role: "assistant",
type: "plain",
title: assistantTitle,
loadingText,
content: "",
sessionId,
sideQa,
},
]);
superAgentLog("chat.send", "run.start", {
traceId,
sessionId,
assistantMessageKey,
assistantTitle,
allowEmptyDone,
sideQa,
chatMessage,
});
await sendSuperAgentChat({
sessionId,
userId: currentUserId,
message: chatMessage,
displayContent: options.displayContent || chatMessage,
attachmentIds: options.attachmentIds || [],
signal: nextAbortController.signal,
onEvent: (eventPayload) => {
const eventName = eventPayload?.event || "message";
if (eventName === "checkpoint") {
runState.hasCheckpoint = true;
}
if (eventName === "error") {
runState.hasError = true;
}
handleSuperAgentEvent(eventPayload, assistantMessageKey, {
allowEmptyDone,
sideQa,
sessionId,
isTaskSession: options.isTaskSession,
restoreContentOnDone: options.restoreContentOnDone,
});
},
});
//[部分服务不会发送 done 事件;本轮进度仍由 useStream 业务事件维护]
if (artifactProgressSessionRef.current === sessionId) {
artifactProgressSessionRef.current = "";
}
superAgentLog("chat.send", "run.done", {
traceId,
sessionId,
assistantMessageKey,
runState,
});
return runState;
};
const firstRunState = await runChatOnce({
chatMessage: content,
assistantTitle: options.waitingTitle || "飞小尚",
loadingText: options.waitingText || "正在输出",
allowEmptyDone: options.autoContinueOnce,
});
const shouldAutoContinue =
options.autoContinueOnce &&
!firstRunState.hasCheckpoint &&
!firstRunState.hasError;
if (shouldAutoContinue) {
superAgentWarn("chat.send", "auto_continue", {
traceId,
sessionId,
autoContinueMessage: options.autoContinueMessage || "继续",
firstRunState,
});
await runChatOnce({
chatMessage: options.autoContinueMessage || "继续",
assistantTitle: "飞小尚",
loadingText: "正在输出",
});
}
} catch (error) {
superAgentError("chat.send", "error", {
traceId,
error,
messageText: content,
options,
});
if (error?.name !== "AbortError") {
message.error(error?.message || "超级智能体请求失败");
}
if (options.throwOnError) {
throw error;
}
} finally {
if (chatAbortControllerRef.current !== nextAbortController) return;
superAgentLog("chat.send", "finish", {
traceId,
messageText: content,
options,
});
setIsStreamingAnswer(false);
chatAbortControllerRef.current = null;
streamingMessageKeyRef.current = "";
}
};
const sendMessageToAgent = (messageText, options = {}) => {
const content = String(messageText || "").trim();
if (!content) return Promise.resolve();
if (chatRunActiveRef.current && !options.allowWhileStreaming) {
superAgentWarn("chat.send", "skip.streaming", {
messageText: content,
options,
});
return Promise.resolve();
}
const previousRun = chatRunPromiseRef.current.catch(() => undefined);
const nextRun = chatRunActiveRef.current
? previousRun.then(() => executeMessageToAgent(content, options))
: executeMessageToAgent(content, options);
chatRunActiveRef.current = true;
let trackedRun;
trackedRun = Promise.resolve(nextRun).finally(() => {
if (chatRunPromiseRef.current === trackedRun) {
chatRunActiveRef.current = false;
}
});
chatRunPromiseRef.current = trackedRun;
return trackedRun;
};
/**
* 功能:提交 checkpoint action 并直接消费 respond SSE
* 使用场景:确认、修改、重新生成等 checkpoint 操作
*
* 入参:
* - sessionId 当前任务会话 ID
* - respondBody checkpoint/respond 请求体
* - actionText 前端展示文案
*
* 返回:本次 SSE 是否收到事件、是否收到错误事件
*
* 注意:respond 接口本身会恢复 LangGraph 中断,不再额外调用 /api/chat。
*/
const respondCheckpointToAgent = async ({
sessionId,
respondBody,
actionText = {},
onFirstEvent,
}) => {
if (!sessionId) {
throw new Error("当前任务会话不存在,请重新选择任务后再操作");
}
const traceId = createSuperAgentTraceId("checkpoint_respond");
const assistantMessageKey = `assistant_checkpoint_${Date.now()}_${Math.random()}`;
const runState = {
hasEvent: false,
hasError: false,
errorMessage: "",
};
let nextAbortController = null;
try {
if (chatAbortControllerRef.current) chatAbortControllerRef.current.abort();
nextAbortController = new AbortController();
chatAbortControllerRef.current = nextAbortController;
streamingMessageKeyRef.current = assistantMessageKey;
setIsStreamingAnswer(true);
setConversationMessages((prev) => [
...prev,
{
key: assistantMessageKey,
role: "assistant",
type: "plain",
title: actionText.title || "飞小尚正在继续处理",
loadingText: actionText.loadingText || "正在请求 AI 继续推进任务,请稍候",
content: "",
sessionId,
},
]);
superAgentLog("chat.checkpoint", "respond.start", {
traceId,
sessionId,
respondBody,
});
await respondSuperAgentCheckpointStream({
body: respondBody,
userId: currentUserId,
sessionId,
signal: nextAbortController.signal,
onEvent: (eventPayload) => {
const eventName = eventPayload?.event || "message";
if (eventName === "error") {
runState.hasError = true;
runState.errorMessage =
eventPayload?.data?.message ||
eventPayload?.data?.error ||
eventPayload?.data?.content ||
"提交确认结果失败";
} else {
runState.hasEvent = true;
if (!runState.firstEventFired && onFirstEvent) {
runState.firstEventFired = true;
onFirstEvent();
}
}
handleSuperAgentEvent(eventPayload, assistantMessageKey, {
allowEmptyDone: true,
sessionId,
isTaskSession: true,
});
},
});
if (artifactProgressSessionRef.current === sessionId) {
artifactProgressSessionRef.current = "";
}
superAgentLog("chat.checkpoint", "respond.done", {
traceId,
sessionId,
runState,
});
return runState;
} catch (error) {
superAgentError("chat.checkpoint", "respond.error", {
traceId,
sessionId,
respondBody,
runState,
error,
});
if (error?.name !== "AbortError") {
updateConversationMessage(assistantMessageKey, (item) => ({
title: "处理失败",
content: error?.message || "提交确认后继续推进失败",
runLog: finishRunLog(item.runLog),
}));
}
throw error;
} finally {
if (chatAbortControllerRef.current !== nextAbortController) return;
setIsStreamingAnswer(false);
chatAbortControllerRef.current = null;
streamingMessageKeyRef.current = "";
}
};
const handleStopAnswer = () => {
const currentAbortController = chatAbortControllerRef.current;
const streamingMessageKey = streamingMessageKeyRef.current;
if (!currentAbortController) return;
superAgentWarn("chat.send", "abort", {
streamingMessageKey,
});
currentAbortController.abort();
chatAbortControllerRef.current = null;
streamingMessageKeyRef.current = "";
setIsStreamingAnswer(false);
if (!streamingMessageKey) return;
updateConversationMessage(streamingMessageKey, (item) => {
const hasContent = Boolean(item.content);
return {
title: hasContent ? item.title || "飞小尚" : "已停止生成",
content: hasContent ? item.content : "已停止生成。",
loadingText: "",
runLog: { ...(item.runLog || {}), status: "stopped" },
};
});
};
//[显式停止按钮:中止SSE + 通知后端取消任务]
const handleStopAndCancel = () => {
handleStopAnswer();
const sessionId = activeSessionId || activeTaskId;
if (sessionId && currentUserId) {
cancelSuperAgentSession({ sessionId, userId: currentUserId }).catch((error) => {
superAgentWarn("chat.stop", "cancel_failed", {
sessionId,
error: error?.message,
});
});
}
};
const startTaskConversation = () => {
setRouteType("task");
setTaskStage(activeTask?.stage || TASK_STAGE.IDLE);
setWorkspaceOpen(false);
if (!conversationMessages.length) {
addAssistantMessage({
type: "taskStart",
title: TASK_WELCOME_TITLE,
content: TASK_WELCOME_CONTENT,
});
}
return activeSessionId || activeTaskId;
};
const resetRouteView = (nextRouteType) => {
setRouteType(nextRouteType);
setWorkspaceOpen(false);
setActiveSessionId("");
setActiveTaskId("");
clearActiveSessionId();
setConversationMessages([]);
setTaskStage(TASK_STAGE.IDLE);
setUploadedFileList([]);
setSelectedTaskType(null);
};
const buildEmptyTaskStartMessage = () => ({
key: `assistant_task_start_${Date.now()}`,
role: "assistant",
type: "taskStart",
title: TASK_WELCOME_TITLE,
content: TASK_WELCOME_CONTENT,
});
const enterTenderTaskGuide = () => {
setSelectedTaskType("tender");
setConversationMessages((prev) => {
const hasTaskStart = prev.some((item) => item?.type === "taskStart");
if (!hasTaskStart) {
return [
{
key: `assistant_task_start_${Date.now()}`,
role: "assistant",
type: "taskStart",
title: TENDER_TASK_WELCOME_TITLE,
content: TENDER_TASK_WELCOME_CONTENT,
taskType: "tender",
},
...prev,
];
}
return prev.map((item) =>
item?.type === "taskStart"
? {
...item,
title: TENDER_TASK_WELCOME_TITLE,
content: TENDER_TASK_WELCOME_CONTENT,
taskType: "tender",
}
: item
);
});
};
const handleSelectTaskType = (taskKey) => {
if (taskKey === "tender") {
//[投标文件:与「新对话」快捷入口一致,走对话引导,不直接出静态流程卡]
// handleTenderQuickStart 定义在后方,此处仅作类型兜底;UI 点击应直接调 handleTenderQuickStart
setSelectedTaskType("tender");
return;
}
message.info("该任务类型即将上线,敬请期待");
};
const handleCreateNewChat = async () => {
if (creatingSessionRef.current || isCreatingChat || isCreatingTask) return;
try {
creatingSessionRef.current = true;
setIsCreatingChat(true);
resetRouteView("chat");
await createAndActivateSession({ isTaskSession: false, shouldResetMessages: true });
} catch (error) {
message.error(error?.message || "创建新对话失败");
} finally {
setIsCreatingChat(false);
creatingSessionRef.current = false;
}
};
const handleCreateNewTask = async () => {
if (creatingSessionRef.current || isCreatingChat || isCreatingTask) return;
try {
creatingSessionRef.current = true;
setIsCreatingTask(true);
resetRouteView("task");
await createAndActivateSession({ isTaskSession: true, shouldResetMessages: true });
setRouteType("task");
setTaskStage(TASK_STAGE.IDLE);
addAssistantMessage({
type: "taskStart",
title: TASK_WELCOME_TITLE,
content: TASK_WELCOME_CONTENT,
});
} catch (error) {
message.error(error?.message || "创建新任务失败");
} finally {
setIsCreatingTask(false);
creatingSessionRef.current = false;
}
};
const handleCreateSessionInFolder = async (realm, folderId) => {
if (creatingSessionRef.current || isCreatingChat || isCreatingTask) return;
const isTaskRealm = realm === "task";
// 普通对话不挂文件夹;folderId 仅对任务生效;虚拟默认 → null
const targetFolderId = isTaskRealm ? resolveApiFolderId(folderId) : null;
try {
creatingSessionRef.current = true;
if (isTaskRealm) setIsCreatingTask(true);
else setIsCreatingChat(true);
if (isTaskRealm) {
resetRouteView("task");
await createAndActivateSession({
isTaskSession: true,
folderId: targetFolderId,
shouldResetMessages: true,
});
setRouteType("task");
setTaskStage(TASK_STAGE.IDLE);
addAssistantMessage({
type: "taskStart",
title: TASK_WELCOME_TITLE,
content: TASK_WELCOME_CONTENT,
});
} else {
resetRouteView("chat");
await createAndActivateSession({
isTaskSession: false,
folderId: null,
shouldResetMessages: true,
});
}
} catch (error) {
message.error(error?.message || "创建会话失败");
} finally {
setIsCreatingChat(false);
setIsCreatingTask(false);
creatingSessionRef.current = false;
}
};
const openAgentSquare = () => {
setRouteType("agents");
setWorkspaceOpen(false);
};
const openResourceLibrary = () => {
setRouteType("assets");
setWorkspaceOpen(false);
};
const handleTenderQuickStart = () => {
//[新任务 / 新对话入口统一:发起对话引导,由 AI 回复后再渲染流程/上传卡]
setSelectedTaskType("tender");
if (!isTaskRoute) {
startTaskConversation();
} else {
setRouteType("task");
}
setTaskStage(TASK_STAGE.BASIC_INFO);
sendMessageToAgent("我要生成投标文件,请引导我上传招标文件并开始标书生成流程。", {
isTaskSession: true,
});
};
const handleUploadFile = (uploadPayload) => {
const file = uploadPayload?.file || uploadPayload;
const fileName = file?.name || "";
if (isUploadingFile) {
message.warning("文件正在上传,请稍候");
return false;
}
if (!file || !fileName) {
message.warning("请选择要上传的文件");
return false;
}
const currentSessionType = getCurrentSessionType();
//[选中文件后直接上传到当前会话;无会话时沿用当前页面类型创建会话]
performUpload(file, fileName, currentSessionType);
return false;
};
//[执行上传文件的实际逻辑]
const performUpload = (file, fileName, currentSessionType) => {
const currentSessionId = activeSessionId || activeTaskId;
const shouldCreateSession = !currentSessionId;
const shouldUseTaskSession =
currentSessionType === "task" || (shouldCreateSession && isTaskRoute);
const shouldCreateNewTask = shouldCreateSession && shouldUseTaskSession;
superAgentLog("chat.upload", "perform.start", {
fileName,
currentSessionId,
currentSessionType,
shouldCreateSession,
shouldUseTaskSession,
shouldCreateNewTask,
file,
});
if (shouldCreateNewTask) {
startTaskConversation();
setSelectedTaskType("tender");
updateActiveTask({
title: fileName.replace(/\.[^.]+$/, "") || "AI 标书任务",
fileName,
stage: TASK_STAGE.BASIC_INFO,
});
setTaskStage(TASK_STAGE.BASIC_INFO);
} else if (shouldUseTaskSession) {
setSelectedTaskType("tender");
}
const uploadAndStart = async () => {
setIsUploadingFile(true);
setUploadProgress({
fileName,
loaded: 0,
total: Number(file?.size || 0),
percent: 0,
status: "preparing",
});
try {
const sessionId =
currentSessionId ||
(await createAndActivateSession({
isTaskSession: shouldUseTaskSession,
title: shouldUseTaskSession
? fileName.replace(/\.[^.]+$/, "") || "AI 标书任务"
: "",
}));
superAgentLog("chat.upload", "session.ready", {
sessionId,
fileName,
shouldUseTaskSession,
});
const uploadResult = await uploadSuperAgentFile({
sessionId,
userId: currentUserId,
file,
onProgress: (progress) => {
setUploadProgress({
fileName,
...progress,
status: "uploading",
});
},
});
setUploadProgress((previous) => ({
...previous,
percent: 100,
status: "processing",
}));
const uploadData = uploadResult?.data || uploadResult || {};
try {
await createResourceFileFromUpload(uploadData);
} catch (resourceError) {
superAgentWarn("chat.upload", "resource_library_save_failed", {
fileName,
error: resourceError?.message,
});
message.warning("文件已上传,但保存到资源库失败");
}
const uploadedFileName =
uploadData.filename ||
uploadData.original_filename ||
uploadData.originalFilename ||
fileName;
const rawUploadedFilePath =
uploadData.path ||
uploadData.url ||
uploadData.object_key ||
uploadData.objectKey ||
"";
const uploadedFilePath = buildQiniuFilePath(rawUploadedFilePath);
const uploadedLocalPath = uploadData.local_path || uploadData.localPath || "";
const uploadedFileStorage = uploadData.storage || "";
const normalizedUploadedFileStorage =
uploadedFileStorage || uploadData.storage_provider || uploadData.storageProvider || "";
superAgentLog("chat.upload", "upload.done", {
sessionId,
uploadedFileName,
uploadedFilePath,
uploadedLocalPath,
uploadedFileStorage: normalizedUploadedFileStorage,
uploadResult,
});
const uploadedFile = {
id: `${sessionId}_${uploadedFileName}_${Date.now()}`,
attachmentId: uploadData.id || "",
name: uploadedFileName,
url: uploadedFilePath,
objectKey: uploadData.object_key || uploadData.objectKey || "",
localPath: uploadedLocalPath,
storage: normalizedUploadedFileStorage,
};
if (shouldCreateNewTask) {
updateActiveTask({
title: uploadedFileName.replace(/\.[^.]+$/, "") || "AI 标书任务",
fileName: uploadedFileName,
filePath: uploadedFilePath,
localPath: uploadedLocalPath,
fileStorage: normalizedUploadedFileStorage,
});
}
setUploadedFileList((prev) => [...prev, uploadedFile]);
message.success("文件上传成功,请输入指令后发送");
} catch (error) {
superAgentError("chat.upload", "error", {
fileName,
currentSessionType,
error,
});
message.error(error?.message || "上传文件失败");
} finally {
setIsUploadingFile(false);
setUploadProgress(null);
}
};
uploadAndStart();
};
const handleRemoveUploadedFile = (fileId) => {
setUploadedFileList((prev) => prev.filter((file) => file.id !== fileId));
};
const confirmBasicInfo = () => {
superAgentLog("chat.confirm", "basic_info", { activeSessionId, activeTaskId });
setTaskStage(TASK_STAGE.SCORE);
sendMessageToAgent("我确认基本信息无误,请继续解析评分点。", {
isTaskSession: true,
allowWhileStreaming: true,
waitingTitle: "飞小尚正在解析评分点",
waitingText: "已收到基本信息确认,正在分析评分点,请稍候",
});
};
const confirmScoreItems = () => {
superAgentLog("chat.confirm", "score_items", { activeSessionId, activeTaskId });
setTaskStage(TASK_STAGE.CATALOG);
sendMessageToAgent("我确认评分点,请继续生成投标文件大纲。", {
isTaskSession: true,
allowWhileStreaming: true,
waitingTitle: "飞小尚正在生成大纲",
waitingText: "已收到评分点确认,正在生成投标文件大纲,请稍候",
});
};
const confirmCatalog = () => {
superAgentLog("chat.confirm", "catalog", { activeSessionId, activeTaskId });
setTaskStage(TASK_STAGE.CONTENT);
sendMessageToAgent("我确认大纲,请继续生成投标文件正文。", {
isTaskSession: true,
allowWhileStreaming: true,
taskAction: "continue_generation",
restoreContentOnDone: true,
waitingTitle: "飞小尚正在生成正文",
waitingText: "已收到大纲确认,正在生成正文内容,请稍候",
});
};
const handleSend = () => {
const userInput = inputValue.trim();
const currentFileList = uploadedFileList;
const fileReferenceText = buildUploadedFileReference(currentFileList);
const finalMessage = [fileReferenceText, userInput].filter(Boolean).join("\n");
if (!finalMessage || isStreamingAnswer) return;
superAgentLog("chat.input", "send", {
currentSessionType: getCurrentSessionType(),
uploadedFileCount: currentFileList.length,
hasTaskKeyword:
userInput.includes("标书") ||
userInput.includes("投标") ||
userInput.includes("招标"),
finalMessage,
});
setInputValue("");
setUploadedFileList([]);
if (!currentFileList.length && userInput.includes("发现智能体")) {
addUserMessage(userInput);
openAgentSquare();
return;
}
const currentSessionType = getCurrentSessionType();
const hasTaskKeyword =
userInput.includes("标书") ||
userInput.includes("投标") ||
userInput.includes("招标");
const shouldUseTaskSession = currentSessionType === "task" || isTaskRoute;
if (shouldUseTaskSession) {
startTaskConversation();
if (hasTaskKeyword || currentFileList.length > 0) {
setSelectedTaskType("tender");
}
}
sendMessageToAgent(finalMessage, {
isTaskSession: shouldUseTaskSession,
userDisplayText: userInput,
userFiles: currentFileList,
//[文件引用写入历史展示字段,刷新或切换会话后才能还原文件标签]
displayContent: finalMessage,
attachmentIds: Array.from(
new Set(currentFileList.map((file) => file.attachmentId).filter(Boolean))
),
});
};
const handleSelectTask = async (task) => {
activeSessionIdRef.current = task.id;
setActiveTaskId(task.id);
setActiveSessionId(task.id);
saveActiveSessionId(task.id);
setRouteType("task");
setTaskStage(task.stage || TASK_STAGE.IDLE);
setWorkspaceOpen(false);
setUploadedFileList([]);
setSelectedTaskType(null);
setConversationMessages([]);
//[useStream 是唯一会话恢复来源,stream.messages 负责在当前 thread 完成快照加载后还原消息]
setConversationMessages([buildEmptyTaskStartMessage()]);
return { success: true };
};
const handleSelectChat = async (chat) => {
const sessionId = chat.id || chat.chatId;
activeSessionIdRef.current = sessionId;
setActiveSessionId(sessionId);
saveActiveSessionId(sessionId);
setActiveTaskId("");
setRouteType("chat");
setTaskStage(TASK_STAGE.IDLE);
setWorkspaceOpen(false);
setUploadedFileList([]);
setSelectedTaskType(null);
setConversationMessages([]);
//[useStream 是唯一会话恢复来源,stream.messages 负责在当前 thread 完成快照加载后还原消息]
return { success: true };
};
const handleDeleteSidebarSession = async (item, itemType) => {
const sessionId = item?.id || item?.chatId;
if (!sessionId) {
message.error("删除失败,缺少会话 ID");
return;
}
try {
await dispatch(deleteSuperAgentSession({
params: buildSessionParams(sessionId),
query: { userId: currentUserId },
})).then((res) => ensureActionSuccess(res, "删除超级智能体会话失败"));
removeSidebarSessionLocally(sessionId, itemType);
clearActiveSidebarSession(sessionId);
await loadSidebar({ silent: true });
message.success("删除成功");
} catch (error) {
message.error(error?.message || "删除超级智能体会话失败");
}
};
const handleDeleteSidebarSessionItem = (session) => {
const sessionKind = session?.sessionKind || session?.type || "conversation";
return handleDeleteSidebarSession(session, sessionKind === "task" ? "task" : "chat");
};
return {
routeType,
inputValue,
setInputValue,
taskList,
activeTaskId,
activeTask,
taskStage,
setTaskStage,
conversationMessages,
uploadedFileList,
uploadProgress,
ordinaryChatList,
sidebarTree,
collapsedFolderMap,
activeSessionId,
isLoadingSessions,
isUploadingFile,
isStreamingAnswer,
isCreatingChat,
isCreatingTask,
agentCategory,
setAgentCategory,
agentKeyword,
setAgentKeyword,
isTaskRoute,
isAgentRoute,
isResourceLibraryRoute,
breadcrumbTitle,
selectedTaskType,
addUserMessage,
upsertAssistantMessage,
updateTaskById,
updateActiveTask,
updateConversationMessage,
removeConversationMessage,
createAndActivateSession,
sendMessageToAgent,
respondCheckpointToAgent,
handleStopAnswer,
handleStopAndCancel,
handleCreateNewChat,
handleCreateNewTask,
openAgentSquare,
openResourceLibrary,
handleSelectTaskType,
handleTenderQuickStart,
handleUploadFile,
handleRemoveUploadedFile,
confirmBasicInfo,
confirmScoreItems,
confirmCatalog,
handleSend,
handleSelectTask,
handleSelectChat,
handleDeleteSidebarSession,
handleCreateFolder,
handleRenameFolder,
handleDeleteFolder,
handleCreateSessionInFolder,
handleDeleteSidebarSessionItem,
handleRenameSession,
handleMoveSession,
toggleFolderCollapsed,
loadSidebar,
};
};
export default useSuperAgentChat;