diff --git a/components/agents/AgentGenerationWorkspaceDarkV2.tsx b/components/agents/AgentGenerationWorkspaceDarkV2.tsx index 9f4a59dc1..e64a5e5c7 100644 --- a/components/agents/AgentGenerationWorkspaceDarkV2.tsx +++ b/components/agents/AgentGenerationWorkspaceDarkV2.tsx @@ -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, + right: Array +) { + 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 = 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]); diff --git a/components/image-params-bar.tsx b/components/image-params-bar.tsx index 0f5a97a2c..d1ae79b38 100644 --- a/components/image-params-bar.tsx +++ b/components/image-params-bar.tsx @@ -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 | undefined, + normalized: ImageGenerationParams + ) => { + if (!source) return false; + + const keys: Array = [ + "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 = ( 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); + } }; // 内联模式:只返回参数按钮,嵌入工具栏