fix: stabilize agent mode state syncing
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-03-19 14:17:35 +08:00
parent 337fd15471
commit af7e98a4dc
2 changed files with 110 additions and 32 deletions

View File

@ -111,6 +111,47 @@ interface AgentModeReferenceSearchResponse {
};
}
function areImageGenerationParamsEqual(
left: ImageGenerationParams,
right: ImageGenerationParams
) {
return (
left.aspectRatio === right.aspectRatio &&
left.imageSize === right.imageSize &&
(left.imageCount || 1) === (right.imageCount || 1) &&
left.size === right.size &&
left.negativePrompt === right.negativePrompt &&
left.promptExtend === right.promptExtend &&
left.watermark === right.watermark &&
left.seed === right.seed
);
}
function arePickedAttachmentsEqual(
left: Array<PickedAttachment | null>,
right: Array<PickedAttachment | null>
) {
if (left === right) return true;
if (left.length !== right.length) return false;
for (let index = 0; index < left.length; index += 1) {
const leftItem = left[index];
const rightItem = right[index];
if (!leftItem && !rightItem) continue;
if (!leftItem || !rightItem) return false;
if (
leftItem.url !== rightItem.url ||
leftItem.name !== rightItem.name ||
leftItem.contentType !== rightItem.contentType ||
leftItem.assetId !== rightItem.assetId
) {
return false;
}
}
return true;
}
type ToolStepKey =
| "preset"
| "brand-model"
@ -551,15 +592,24 @@ export function AgentGenerationWorkspaceDarkV2({
useEffect(() => {
if (normalizedIncomingWorkspaceState.selectedModelId) {
setSelectedModelId(normalizedIncomingWorkspaceState.selectedModelId);
const nextModelId = normalizedIncomingWorkspaceState.selectedModelId;
setSelectedModelId((current) =>
current === nextModelId ? current : nextModelId
);
return;
}
if (selectedAgentPreset?.defaultModelId) {
setSelectedModelId(selectedAgentPreset.defaultModelId);
const nextModelId = selectedAgentPreset.defaultModelId;
setSelectedModelId((current) =>
current === nextModelId ? current : nextModelId
);
return;
}
if (modelsData?.defaultModelId) {
setSelectedModelId(modelsData.defaultModelId);
const nextModelId = modelsData.defaultModelId;
setSelectedModelId((current) =>
current === nextModelId ? current : nextModelId
);
}
}, [
modelsData?.defaultModelId,
@ -570,7 +620,7 @@ export function AgentGenerationWorkspaceDarkV2({
// 初始化附件
useEffect(() => {
if (!hasAgentUploadSlots) {
setAttachments([]);
setAttachments((current) => (current.length === 0 ? current : []));
return;
}
const nextSlots: Array<PickedAttachment | null> = Array.from(
@ -603,7 +653,7 @@ export function AgentGenerationWorkspaceDarkV2({
result[index] = nextSlots[index] as PickedAttachment;
}
}
return result;
return arePickedAttachmentsEqual(current, result) ? current : result;
});
}, [
agentUploadSlots.length,
@ -647,7 +697,7 @@ export function AgentGenerationWorkspaceDarkV2({
slots[modelSlotIndex] = null;
}
return slots;
return arePickedAttachmentsEqual(current, slots) ? current : slots;
});
lastSyncedBrandModelUrlRef.current = nextAttachment?.url || null;
@ -812,8 +862,8 @@ export function AgentGenerationWorkspaceDarkV2({
}
workspaceStateSignatureRef.current = incomingSignature;
setInput(normalizedIncomingWorkspaceState.input);
setAttachments(
normalizedIncomingWorkspaceState.attachments.map((item) =>
setAttachments((current) => {
const nextAttachments = normalizedIncomingWorkspaceState.attachments.map((item) =>
item
? {
url: item.url,
@ -822,13 +872,20 @@ export function AgentGenerationWorkspaceDarkV2({
assetId: item.assetId || undefined,
}
: null
)
);
setSelectedModelId(normalizedIncomingWorkspaceState.selectedModelId || "auto");
setImageParams({
aspectRatio: normalizedIncomingWorkspaceState.imageParams?.aspectRatio || "auto",
imageSize: normalizedIncomingWorkspaceState.imageParams?.imageSize || "2K",
imageCount: normalizedIncomingWorkspaceState.imageParams?.imageCount || 1,
);
return arePickedAttachmentsEqual(current, nextAttachments) ? current : nextAttachments;
});
setSelectedModelId((current) => {
const nextModelId = normalizedIncomingWorkspaceState.selectedModelId || "auto";
return current === nextModelId ? current : nextModelId;
});
setImageParams((current) => {
const nextParams = {
aspectRatio: normalizedIncomingWorkspaceState.imageParams?.aspectRatio || "auto",
imageSize: normalizedIncomingWorkspaceState.imageParams?.imageSize || "2K",
imageCount: normalizedIncomingWorkspaceState.imageParams?.imageCount || 1,
};
return areImageGenerationParamsEqual(current, nextParams) ? current : nextParams;
});
}, [normalizedIncomingWorkspaceState]);

View File

@ -1,6 +1,6 @@
"use client";
import { useState, useEffect } from "react";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { Link2, Scan, SlidersHorizontal, Sparkles } from "lucide-react";
import { cn } from "@/lib/utils";
@ -89,6 +89,28 @@ export function ImageParamsBar({
showInlineDivider = true,
imageGenConfig,
}: ImageParamsBarProps) {
const hasNormalizedParamDiff = (
source: Partial<ImageGenerationParams> | undefined,
normalized: ImageGenerationParams
) => {
if (!source) return false;
const keys: Array<keyof ImageGenerationParams> = [
"aspectRatio",
"imageSize",
"imageCount",
"size",
"negativePrompt",
"promptExtend",
"watermark",
"seed",
];
return keys.some(
(key) => source[key] !== undefined && source[key] !== normalized[key]
);
};
const getImageCountOptions = () => {
// RUXA 当前按单图任务处理(优先级最高)
if (providerType === "ruxa-image" || providerType === "openai-image" || providerType === "midjourney") {
@ -228,33 +250,32 @@ export function ImageParamsBar({
);
};
// 🆕 监听 initialParams 变化,确保状态同步
// 仅同步展示状态;只有确实发生参数校正时才回写父层,避免和父组件互相触发更新。
useEffect(() => {
console.log("[ImageParamsBar] Mount/Update - initialParams:", initialParams);
const rawParams = {
aspectRatio: "auto",
imageSize: "2K",
imageCount: 1,
promptExtend: true,
watermark: false,
...(initialParams || {}),
} as ImageGenerationParams;
const nextParams = buildBaseParams(initialParams);
setParams((prevParams) => (isSameParams(prevParams, nextParams) ? prevParams : nextParams));
if (initialParams !== undefined && onParamsChange && !isSameParams(rawParams, nextParams)) {
if (onParamsChange && hasNormalizedParamDiff(initialParams, nextParams)) {
onParamsChange(nextParams);
}
}, [initialParams, providerType, imageGenConfig?.maxImages, imageGenConfig?.supports1K, imageGenConfig?.supports2K, imageGenConfig?.supports4K]);
}, [
imageGenConfig?.maxImages,
imageGenConfig?.supports1K,
imageGenConfig?.supports2K,
imageGenConfig?.supports4K,
initialParams,
onParamsChange,
providerType,
]);
const updateParam = <K extends keyof ImageGenerationParams>(
key: K,
value: ImageGenerationParams[K]
) => {
const newParams = clampParamsByProvider({ ...params, [key]: value });
console.log("[ImageParamsBar] Updating param:", key, "=", value);
console.log("[ImageParamsBar] New params:", newParams);
setParams(newParams);
onParamsChange?.(newParams);
setParams((prevParams) => (isSameParams(prevParams, newParams) ? prevParams : newParams));
if (!isSameParams(params, newParams)) {
onParamsChange?.(newParams);
}
};
// 内联模式:只返回参数按钮,嵌入工具栏