fix: isolate canvas mode switches
This commit is contained in:
parent
3f8219f415
commit
d4936a8ca4
@ -311,6 +311,14 @@ interface CanvasChatPanelProps {
|
|||||||
|
|
||||||
type CanvasComposerMode = "agent" | "image" | "video" | "detail" | "clawd";
|
type CanvasComposerMode = "agent" | "image" | "video" | "detail" | "clawd";
|
||||||
|
|
||||||
|
function getCanvasComposerModeSessionLabel(mode: CanvasComposerMode) {
|
||||||
|
if (mode === "clawd") return "Clawd";
|
||||||
|
if (mode === "video") return "视频";
|
||||||
|
if (mode === "detail") return "AI详情";
|
||||||
|
if (mode === "image") return "图像";
|
||||||
|
return "Agent";
|
||||||
|
}
|
||||||
|
|
||||||
type VideoStoryboardStreamData =
|
type VideoStoryboardStreamData =
|
||||||
| {
|
| {
|
||||||
stage: "detecting";
|
stage: "detecting";
|
||||||
@ -1321,6 +1329,7 @@ export function CanvasChatPanel({
|
|||||||
() => requestedChatSessionId || chatId
|
() => requestedChatSessionId || chatId
|
||||||
);
|
);
|
||||||
const activeChatIdRef = useRef<string>(requestedChatSessionId || chatId);
|
const activeChatIdRef = useRef<string>(requestedChatSessionId || chatId);
|
||||||
|
const activeMessagesRef = useRef<ChatMessage[]>([]);
|
||||||
const inFlightChatIdRef = useRef<string | null>(null);
|
const inFlightChatIdRef = useRef<string | null>(null);
|
||||||
const inFlightRawChatIdRef = useRef<string | null>(null);
|
const inFlightRawChatIdRef = useRef<string | null>(null);
|
||||||
const [chatSessions, setChatSessions] = useState<CanvasChatSession[]>([]);
|
const [chatSessions, setChatSessions] = useState<CanvasChatSession[]>([]);
|
||||||
@ -1528,6 +1537,29 @@ export function CanvasChatPanel({
|
|||||||
);
|
);
|
||||||
}, [getSendGuard]);
|
}, [getSendGuard]);
|
||||||
|
|
||||||
|
const startNewChatSession = useCallback(
|
||||||
|
(options?: { title?: string; toastMessage?: string }) => {
|
||||||
|
const now = Date.now();
|
||||||
|
const newChatSessionId = `canvas-${crypto.randomUUID()}`;
|
||||||
|
const fallbackTitle = `对话 ${chatSessions.length + 1}`;
|
||||||
|
const newSession: CanvasChatSession = {
|
||||||
|
id: newChatSessionId,
|
||||||
|
title: options?.title || fallbackTitle,
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
};
|
||||||
|
|
||||||
|
forceHistoryHydrationRef.current = null;
|
||||||
|
releaseSendGuard(activeChatId);
|
||||||
|
setChatSessions((prevSessions) => [newSession, ...prevSessions]);
|
||||||
|
setActiveChatId(newChatSessionId);
|
||||||
|
replaceChatSessionQuery(newChatSessionId);
|
||||||
|
toast.success(options?.toastMessage || "已新建聊天窗口");
|
||||||
|
return newChatSessionId;
|
||||||
|
},
|
||||||
|
[activeChatId, chatSessions.length, releaseSendGuard, replaceChatSessionQuery]
|
||||||
|
);
|
||||||
|
|
||||||
const handleTypewriterActiveChange = useCallback((typingKey: string, active: boolean) => {
|
const handleTypewriterActiveChange = useCallback((typingKey: string, active: boolean) => {
|
||||||
setActiveTypingSections((prev) => {
|
setActiveTypingSections((prev) => {
|
||||||
if (active) {
|
if (active) {
|
||||||
@ -3479,6 +3511,21 @@ export function CanvasChatPanel({
|
|||||||
: isPresetAgentSelection
|
: isPresetAgentSelection
|
||||||
? getToolbarItem("chatModePresetAgent").label
|
? getToolbarItem("chatModePresetAgent").label
|
||||||
: "Agent";
|
: "Agent";
|
||||||
|
const hasActiveChatContext = useCallback(() => {
|
||||||
|
const editorText = (textareaRef.current?.textContent || "")
|
||||||
|
.replace(/\u200b/g, " ")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
const hasDetailSlotContent = detailSlots.some((slot) => Boolean(slot?.url));
|
||||||
|
return (
|
||||||
|
activeMessagesRef.current.length > 0 ||
|
||||||
|
Boolean(input.trim()) ||
|
||||||
|
Boolean(editorText) ||
|
||||||
|
attachments.length > 0 ||
|
||||||
|
Boolean(selectedSkillTagId) ||
|
||||||
|
hasDetailSlotContent
|
||||||
|
);
|
||||||
|
}, [attachments.length, detailSlots, input, selectedSkillTagId]);
|
||||||
const getComposerModeMenuItemClass = useCallback(
|
const getComposerModeMenuItemClass = useCallback(
|
||||||
(mode: CanvasComposerMode) =>
|
(mode: CanvasComposerMode) =>
|
||||||
cn(
|
cn(
|
||||||
@ -3801,6 +3848,58 @@ export function CanvasChatPanel({
|
|||||||
toast.error("Clawd 未开启");
|
toast.error("Clawd 未开启");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (mode === "agent") {
|
||||||
|
if (!effectiveAgentEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mode === "detail" && !canUseDetailGeneration) {
|
||||||
|
toast.error("AI详情生成功能未开启");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const preferredImageModelId =
|
||||||
|
mode === "image"
|
||||||
|
? allModels.find(
|
||||||
|
(model) =>
|
||||||
|
model.id === lastImageModelId &&
|
||||||
|
model.id !== "auto" &&
|
||||||
|
isCanvasImageModel(model) &&
|
||||||
|
!model.isMultimodal
|
||||||
|
)?.id || firstImageModelId
|
||||||
|
: null;
|
||||||
|
if (mode === "image" && !preferredImageModelId) {
|
||||||
|
toast.error("当前没有可用的图像模型");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const preferredVideoModelId =
|
||||||
|
mode === "video"
|
||||||
|
? allModels.find((model) => model.id === lastVideoModelId && isCanvasVideoModel(model))?.id ||
|
||||||
|
firstVideoModelId
|
||||||
|
: null;
|
||||||
|
if (mode === "video" && !preferredVideoModelId) {
|
||||||
|
toast.error("当前没有可用的视频模型");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSwitchingMode = mode !== activeComposerMode;
|
||||||
|
if (
|
||||||
|
isSwitchingMode &&
|
||||||
|
(getSendGuard(activeChatId).inFlight || isClawdSubmitting || isDetailGenerating)
|
||||||
|
) {
|
||||||
|
toast.message("当前对话正在处理中,完成后再切换模式");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSwitchingMode && hasActiveChatContext()) {
|
||||||
|
const modeLabel = getCanvasComposerModeSessionLabel(mode);
|
||||||
|
startNewChatSession({
|
||||||
|
title: `${modeLabel} 对话`,
|
||||||
|
toastMessage: `已为 ${modeLabel} 新建聊天窗口`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
activeComposerModeRef.current = mode;
|
activeComposerModeRef.current = mode;
|
||||||
setComposerModeOverride(mode);
|
setComposerModeOverride(mode);
|
||||||
|
|
||||||
@ -3810,9 +3909,6 @@ export function CanvasChatPanel({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mode === "agent") {
|
if (mode === "agent") {
|
||||||
if (!effectiveAgentEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const preferredAgentModelId =
|
const preferredAgentModelId =
|
||||||
allModels.find(
|
allModels.find(
|
||||||
(model) =>
|
(model) =>
|
||||||
@ -3826,10 +3922,6 @@ export function CanvasChatPanel({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mode === "detail") {
|
if (mode === "detail") {
|
||||||
if (!canUseDetailGeneration) {
|
|
||||||
toast.error("AI详情生成功能未开启");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const detailModelId = effectiveAgentEnabled ? "auto" : "";
|
const detailModelId = effectiveAgentEnabled ? "auto" : "";
|
||||||
currentModelIdRef.current = detailModelId;
|
currentModelIdRef.current = detailModelId;
|
||||||
setSelectedModelId(detailModelId);
|
setSelectedModelId(detailModelId);
|
||||||
@ -3839,35 +3931,16 @@ export function CanvasChatPanel({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mode === "image") {
|
if (mode === "image") {
|
||||||
const preferredImageModelId =
|
const nextImageModelId = preferredImageModelId || "";
|
||||||
allModels.find(
|
currentModelIdRef.current = nextImageModelId;
|
||||||
(model) =>
|
setSelectedModelId(nextImageModelId);
|
||||||
model.id === lastImageModelId &&
|
|
||||||
model.id !== "auto" &&
|
|
||||||
isCanvasImageModel(model) &&
|
|
||||||
!model.isMultimodal
|
|
||||||
)?.id || firstImageModelId;
|
|
||||||
|
|
||||||
if (!preferredImageModelId) {
|
|
||||||
toast.error("当前没有可用的图像模型");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentModelIdRef.current = preferredImageModelId;
|
|
||||||
setSelectedModelId(preferredImageModelId);
|
|
||||||
setIsDetailGenerationMode(false);
|
setIsDetailGenerationMode(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const preferredVideoModelId =
|
const nextVideoModelId = preferredVideoModelId || "";
|
||||||
allModels.find((model) => model.id === lastVideoModelId && isCanvasVideoModel(model))?.id ||
|
currentModelIdRef.current = nextVideoModelId;
|
||||||
firstVideoModelId;
|
setSelectedModelId(nextVideoModelId);
|
||||||
|
|
||||||
if (!preferredVideoModelId) {
|
|
||||||
toast.error("当前没有可用的视频模型");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentModelIdRef.current = preferredVideoModelId;
|
|
||||||
setSelectedModelId(preferredVideoModelId);
|
|
||||||
setIsDetailGenerationMode(false);
|
setIsDetailGenerationMode(false);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
@ -3875,16 +3948,21 @@ export function CanvasChatPanel({
|
|||||||
effectiveAgentEnabled,
|
effectiveAgentEnabled,
|
||||||
isClawdModeEnabled,
|
isClawdModeEnabled,
|
||||||
allModels,
|
allModels,
|
||||||
computedComposerMode,
|
activeChatId,
|
||||||
|
activeComposerMode,
|
||||||
firstImageModelId,
|
firstImageModelId,
|
||||||
firstVideoModelId,
|
firstVideoModelId,
|
||||||
hasSelectedAgentPreset,
|
getSendGuard,
|
||||||
|
hasActiveChatContext,
|
||||||
|
isClawdSubmitting,
|
||||||
|
isDetailGenerating,
|
||||||
lastConversationModelId,
|
lastConversationModelId,
|
||||||
lastImageModelId,
|
lastImageModelId,
|
||||||
isCanvasConversationOnlyModel,
|
isCanvasConversationOnlyModel,
|
||||||
isCanvasImageModel,
|
isCanvasImageModel,
|
||||||
isCanvasVideoModel,
|
isCanvasVideoModel,
|
||||||
lastVideoModelId,
|
lastVideoModelId,
|
||||||
|
startNewChatSession,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -5262,6 +5340,10 @@ export function CanvasChatPanel({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
activeMessagesRef.current = messages;
|
||||||
|
}, [messages]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status === "ready") {
|
if (status === "ready") {
|
||||||
releaseSendGuard(activeChatId);
|
releaseSendGuard(activeChatId);
|
||||||
@ -10126,22 +10208,8 @@ export function CanvasChatPanel({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const createNewChatSession = useCallback(() => {
|
const createNewChatSession = useCallback(() => {
|
||||||
const now = Date.now();
|
startNewChatSession();
|
||||||
const newChatSessionId = `canvas-${crypto.randomUUID()}`;
|
}, [startNewChatSession]);
|
||||||
const fallbackTitle = `对话 ${chatSessions.length + 1}`;
|
|
||||||
const newSession: CanvasChatSession = {
|
|
||||||
id: newChatSessionId,
|
|
||||||
title: fallbackTitle,
|
|
||||||
createdAt: now,
|
|
||||||
updatedAt: now,
|
|
||||||
};
|
|
||||||
|
|
||||||
releaseSendGuard(activeChatId);
|
|
||||||
setChatSessions((prevSessions) => [newSession, ...prevSessions]);
|
|
||||||
setActiveChatId(newChatSessionId);
|
|
||||||
replaceChatSessionQuery(newChatSessionId);
|
|
||||||
toast.success("已新建聊天窗口");
|
|
||||||
}, [activeChatId, chatSessions.length, releaseSendGuard, replaceChatSessionQuery]);
|
|
||||||
|
|
||||||
const switchToSession = useCallback((sessionId: string) => {
|
const switchToSession = useCallback((sessionId: string) => {
|
||||||
if (!sessionId || sessionId === activeChatId) {
|
if (!sessionId || sessionId === activeChatId) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user