fix: isolate canvas chat sessions during streaming
This commit is contained in:
parent
2d59e3e3c4
commit
2979446337
@ -2227,6 +2227,9 @@ export function CanvasChatPanel({
|
||||
const [detailModeMessageStartIndex, setDetailModeMessageStartIndex] = useState(0);
|
||||
const canvasId = useMemo(() => toRawCanvasChatId(chatId), [chatId]);
|
||||
const [activeChatId, setActiveChatId] = useState<string>(chatId);
|
||||
const activeChatIdRef = useRef<string>(chatId);
|
||||
const inFlightChatIdRef = useRef<string | null>(null);
|
||||
const inFlightRawChatIdRef = useRef<string | null>(null);
|
||||
const [chatSessions, setChatSessions] = useState<CanvasChatSession[]>([]);
|
||||
const [isSessionMenuOpen, setIsSessionMenuOpen] = useState(false);
|
||||
const [selectedSkillTagId, setSelectedSkillTagId] = useState<string | null>(null);
|
||||
@ -2536,6 +2539,7 @@ export function CanvasChatPanel({
|
||||
}, [fullscreenPreview]);
|
||||
|
||||
useEffect(() => {
|
||||
activeChatIdRef.current = activeChatId;
|
||||
setTypedAssistantSections({});
|
||||
setActiveTypingSections({});
|
||||
setSelectedSkillTagId(null);
|
||||
@ -4816,16 +4820,19 @@ export function CanvasChatPanel({
|
||||
},
|
||||
}),
|
||||
onData: (dataPart: any) => {
|
||||
const eventChatId = inFlightChatIdRef.current || activeChatId;
|
||||
const isEventForActiveChat = eventChatId === activeChatIdRef.current;
|
||||
|
||||
if (dataPart.type === "data-imageGenerationStatus") {
|
||||
try {
|
||||
const payload: ImageGenerationStatusStreamPayload =
|
||||
typeof dataPart.data === "string" ? JSON.parse(dataPart.data) : dataPart.data;
|
||||
if (!payload || typeof payload !== "object") return;
|
||||
if (payload.phase === "completed") {
|
||||
clearStreamImageGenerationStateForChat(activeChatId);
|
||||
clearStreamImageGenerationStateForChat(eventChatId);
|
||||
return;
|
||||
}
|
||||
setStreamImageGenerationStateForChat(activeChatId, {
|
||||
setStreamImageGenerationStateForChat(eventChatId, {
|
||||
phase: payload.phase || "processing",
|
||||
providerType: payload.providerType,
|
||||
modelName: getAgentSafeModelName(payload.modelName),
|
||||
@ -4876,7 +4883,7 @@ export function CanvasChatPanel({
|
||||
if (!payload || typeof payload !== "object") return;
|
||||
setAgentTargetLockStateByChatId((prev) => ({
|
||||
...prev,
|
||||
[activeChatId]: payload,
|
||||
[eventChatId]: payload,
|
||||
}));
|
||||
} catch (e) {
|
||||
console.error("[CanvasChat] Failed to parse data-agentTargetLock:", e);
|
||||
@ -4889,7 +4896,7 @@ export function CanvasChatPanel({
|
||||
const payload: AgentProgressStreamPayload =
|
||||
typeof dataPart.data === "string" ? JSON.parse(dataPart.data) : dataPart.data;
|
||||
if (!payload || typeof payload !== "object") return;
|
||||
setAgentProgressStateForChat(activeChatId, {
|
||||
setAgentProgressStateForChat(eventChatId, {
|
||||
stage: payload.stage || "planning",
|
||||
currentStep: payload.currentStep || "analyze",
|
||||
title: payload.title,
|
||||
@ -4961,8 +4968,8 @@ export function CanvasChatPanel({
|
||||
});
|
||||
if (hasImageGenStartMarker) {
|
||||
const previousState =
|
||||
streamImageGenerationStateByChatId[activeChatId] || null;
|
||||
setStreamImageGenerationStateForChat(activeChatId, {
|
||||
streamImageGenerationStateByChatId[eventChatId] || null;
|
||||
setStreamImageGenerationStateForChat(eventChatId, {
|
||||
phase: "processing",
|
||||
providerType:
|
||||
previousState?.providerType ||
|
||||
@ -4976,7 +4983,11 @@ export function CanvasChatPanel({
|
||||
});
|
||||
}
|
||||
if (hasImageGenEndMarker || incomingImageUrls.length > 0 || incomingVideoUrls.length > 0) {
|
||||
clearStreamImageGenerationStateForChat(activeChatId);
|
||||
clearStreamImageGenerationStateForChat(eventChatId);
|
||||
}
|
||||
|
||||
if (!isEventForActiveChat) {
|
||||
return;
|
||||
}
|
||||
|
||||
setMessages((prevMessages) => {
|
||||
@ -5264,20 +5275,23 @@ export function CanvasChatPanel({
|
||||
}
|
||||
},
|
||||
onFinish: async () => {
|
||||
if (!rawChatId) return;
|
||||
const finishedChatId = inFlightChatIdRef.current || activeChatId;
|
||||
const finishedRawChatId =
|
||||
inFlightRawChatIdRef.current || toRawCanvasChatId(finishedChatId);
|
||||
if (!finishedRawChatId) return;
|
||||
|
||||
const syncMessagesFromDatabase = (dbMessages: ChatMessage[]) => {
|
||||
const historySignature = buildCanvasHistoryMessagesSignature(rawChatId, dbMessages);
|
||||
if (finishedChatId !== activeChatIdRef.current) {
|
||||
void mutate(`/api/messages?chatId=${finishedRawChatId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const historySignature = buildCanvasHistoryMessagesSignature(finishedRawChatId, dbMessages);
|
||||
loadedHistorySignatureRef.current = historySignature;
|
||||
processedMessageIds.current = new Set();
|
||||
hasHydratedHistoryRef.current = true;
|
||||
|
||||
setMessages((currentMessages) => {
|
||||
const mergedMap = new Map<string, ChatMessage>();
|
||||
currentMessages.forEach((message) => mergedMap.set(message.id, message));
|
||||
dbMessages.forEach((message) => mergedMap.set(message.id, message));
|
||||
return sortCanvasMessagesByCreatedAt(Array.from(mergedMap.values()));
|
||||
});
|
||||
setMessages(sortCanvasMessagesByCreatedAt(dbMessages));
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: "auto", block: "end" });
|
||||
@ -5285,7 +5299,7 @@ export function CanvasChatPanel({
|
||||
};
|
||||
|
||||
const reloadMessagesOnce = async () => {
|
||||
const response = await fetch(`/api/messages?chatId=${rawChatId}&_t=${Date.now()}`, {
|
||||
const response = await fetch(`/api/messages?chatId=${finishedRawChatId}&_t=${Date.now()}`, {
|
||||
cache: "no-store",
|
||||
});
|
||||
if (!response.ok) {
|
||||
@ -5333,17 +5347,27 @@ export function CanvasChatPanel({
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[CanvasChat] Failed to sync messages after finish:", error);
|
||||
} finally {
|
||||
if (inFlightChatIdRef.current === finishedChatId) {
|
||||
inFlightChatIdRef.current = null;
|
||||
inFlightRawChatIdRef.current = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
clearStreamImageGenerationStateForChat(activeChatId);
|
||||
setAgentProgressStateForChat(activeChatId, {
|
||||
const failedChatId = inFlightChatIdRef.current || activeChatId;
|
||||
clearStreamImageGenerationStateForChat(failedChatId);
|
||||
setAgentProgressStateForChat(failedChatId, {
|
||||
stage: "failed",
|
||||
currentStep: "execute",
|
||||
title: "本轮请求失败",
|
||||
message: error instanceof Error ? error.message : "请求失败,请稍后重试",
|
||||
});
|
||||
releaseSendGuard(activeChatId);
|
||||
releaseSendGuard(failedChatId);
|
||||
if (inFlightChatIdRef.current === failedChatId) {
|
||||
inFlightChatIdRef.current = null;
|
||||
inFlightRawChatIdRef.current = null;
|
||||
}
|
||||
const fallbackMessage =
|
||||
error instanceof Error ? error.message : "生成失败";
|
||||
const responseData = (error as any)?.data;
|
||||
@ -5367,7 +5391,9 @@ export function CanvasChatPanel({
|
||||
});
|
||||
}
|
||||
|
||||
toast.error(fallbackMessage || "生成失败");
|
||||
if (failedChatId === activeChatIdRef.current) {
|
||||
toast.error(fallbackMessage || "生成失败");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -5408,6 +5434,8 @@ export function CanvasChatPanel({
|
||||
|
||||
// 强制同步当前模型,避免模型切换后瞬时发送命中旧模型
|
||||
currentModelIdRef.current = selectedModelId;
|
||||
inFlightChatIdRef.current = activeChatId;
|
||||
inFlightRawChatIdRef.current = rawChatId;
|
||||
|
||||
sendMessage({
|
||||
role: "user",
|
||||
@ -5424,7 +5452,7 @@ export function CanvasChatPanel({
|
||||
if (editableDiv) {
|
||||
editableDiv.innerHTML = "";
|
||||
}
|
||||
}, [selectedModelId, status, sendMessage, clearSelection, launchAutoSendTick]);
|
||||
}, [activeChatId, rawChatId, selectedModelId, status, sendMessage, clearSelection, launchAutoSendTick]);
|
||||
|
||||
// 当历史消息加载完成后,更新消息列表(支持同一会话的增量刷新)
|
||||
const loadedHistorySignatureRef = useRef<string>("");
|
||||
@ -8601,6 +8629,8 @@ export function CanvasChatPanel({
|
||||
signature: sendSignature,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
inFlightChatIdRef.current = activeChatId;
|
||||
inFlightRawChatIdRef.current = rawChatId;
|
||||
clearStreamImageGenerationStateForChat(activeChatId);
|
||||
clearAgentProgressStateForChat(activeChatId);
|
||||
|
||||
@ -8612,6 +8642,10 @@ export function CanvasChatPanel({
|
||||
});
|
||||
} catch (error) {
|
||||
releaseSendGuard(activeChatId);
|
||||
if (inFlightChatIdRef.current === activeChatId) {
|
||||
inFlightChatIdRef.current = null;
|
||||
inFlightRawChatIdRef.current = null;
|
||||
}
|
||||
contextPayloadRef.current = null;
|
||||
console.error("[CanvasChat] sendMessage failed:", error);
|
||||
toast.error("发送失败,请重试");
|
||||
@ -8637,6 +8671,7 @@ export function CanvasChatPanel({
|
||||
activeComposerMode,
|
||||
agentEntryMode,
|
||||
agentRequiredUploadCount,
|
||||
rawChatId,
|
||||
input,
|
||||
attachments,
|
||||
hasAgentUploadSlots,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user