diff --git a/components/chat.tsx b/components/chat.tsx index 136125564..da47c6c9d 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -37,6 +37,32 @@ import type { VisibilityType } from "@/components/visibility-selector"; import type { VideoGenerationParams } from "@/components/video-params-bar"; import type { ImageGenerationParams } from "@/components/image-params-bar"; +function normalizeImageParamsForProvider( + params: ImageGenerationParams, + providerType?: string +): ImageGenerationParams { + const normalized = { ...params }; + const currentCount = normalized.imageCount || 1; + + if (providerType === "ruxa-image") { + normalized.imageCount = 1; + return normalized; + } + + if (providerType === "alibaba-image") { + normalized.imageCount = Math.min(Math.max(currentCount, 1), 6); + return normalized; + } + + if (providerType === "volcano-vision") { + normalized.imageCount = Math.min(Math.max(currentCount, 1), 3); + return normalized; + } + + normalized.imageCount = Math.min(Math.max(currentCount, 1), 2); + return normalized; +} + export function Chat({ id, initialMessages, @@ -167,13 +193,14 @@ export function Chat({ // 🆕 保存图片参数到 localStorage const handleImageParamsChange = (params: ImageGenerationParams) => { - setImageParams(params); + const normalized = normalizeImageParamsForProvider(params, currentModel?.providerType); + setImageParams(normalized); try { - localStorage.setItem("gemini-image-params", JSON.stringify(params)); + localStorage.setItem("gemini-image-params", JSON.stringify(normalized)); } catch (e) { console.error("Failed to save image params:", e); } - console.log("[Chat] Image params updated:", params); + console.log("[Chat] Image params updated:", normalized); }; // 🆕 获取模型列表以检查是否是视频模型/图片模型 @@ -247,11 +274,17 @@ export function Chat({ // 🆕 当切换到图片模型时,强制更新 imageParams(确保组件重新渲染) useEffect(() => { if (isImageModel) { - // 强制更新一次 imageParams 以触发重新渲染 - setImageParams(prev => ({...prev})); - console.log("[Chat] 图片模型已激活,触发参数更新"); + setImageParams((prev) => { + const next = normalizeImageParamsForProvider(prev, currentModel?.providerType); + if (JSON.stringify(prev) === JSON.stringify(next)) return prev; + try { + localStorage.setItem("gemini-image-params", JSON.stringify(next)); + } catch {} + return next; + }); + console.log("[Chat] 图片模型已激活,参数已按当前模型规范化"); } - }, [isImageModel]); + }, [isImageModel, currentModel?.providerType]); useEffect(() => { currentModelIdRef.current = currentModelId; diff --git a/components/image-params-bar.tsx b/components/image-params-bar.tsx index 225ddf7b8..b463a0490 100644 --- a/components/image-params-bar.tsx +++ b/components/image-params-bar.tsx @@ -79,17 +79,11 @@ export function ImageParamsBar({ providerType = "gemini", // 默认 gemini imageGenConfig, }: ImageParamsBarProps) { - const [params, setParams] = useState({ - aspectRatio: "auto", - imageSize: "2K", // 默认 2K - imageCount: 1, // 默认 1 张 - promptExtend: true, // 阿里生图默认开启提示词优化 - watermark: false, - ...initialParams, - }); - - // 根据模型类型和配置确定可选的图片数量 const getImageCountOptions = () => { + // RUXA 当前按单图任务处理(优先级最高) + if (providerType === "ruxa-image") { + return [1]; + } // 优先使用模型配置中的 maxImages if (imageGenConfig?.maxImages) { return Array.from({ length: imageGenConfig.maxImages }, (_, i) => i + 1); @@ -99,10 +93,6 @@ export function ImageParamsBar({ // 阿里生图:1~6 张(qwen-image-edit-plus) return [1, 2, 3, 4, 5, 6]; } - if (providerType === "ruxa-image") { - // RUXA 当前按单图任务处理 - return [1]; - } if (providerType === "volcano-vision") { // 即梦4.5(豆包):1~3 张 return [1, 2, 3]; @@ -110,7 +100,41 @@ export function ImageParamsBar({ // Gemini:1~2 张 return [1, 2]; }; - + + const clampParamsByProvider = (raw: ImageGenerationParams): ImageGenerationParams => { + const options = getImageCountOptions(); + const fallbackCount = options[0] ?? 1; + const imageCount = options.includes(raw.imageCount || fallbackCount) ? (raw.imageCount || fallbackCount) : fallbackCount; + return { + ...raw, + imageCount, + }; + }; + + const isSameParams = (a: ImageGenerationParams, b: ImageGenerationParams) => + a.aspectRatio === b.aspectRatio && + a.imageSize === b.imageSize && + (a.imageCount || 1) === (b.imageCount || 1) && + a.size === b.size && + a.negativePrompt === b.negativePrompt && + a.promptExtend === b.promptExtend && + a.watermark === b.watermark && + a.seed === b.seed; + + const buildBaseParams = (source?: Partial) => + clampParamsByProvider({ + aspectRatio: "auto", + imageSize: "2K", + imageCount: 1, + promptExtend: true, + watermark: false, + ...source, + }); + + const [params, setParams] = useState({ + ...buildBaseParams(initialParams), + }); + const imageCountOptions = getImageCountOptions(); // 是否显示阿里生图专用参数 @@ -121,25 +145,26 @@ export function ImageParamsBar({ // 🆕 监听 initialParams 变化,确保状态同步 useEffect(() => { console.log("[ImageParamsBar] Mount/Update - initialParams:", initialParams); - // 如果 initialParams 存在,更新参数 - // 如果 initialParams 是 undefined,保持当前参数不变(使用 useState 的初始值) - if (initialParams !== undefined) { - setParams((prevParams) => ({ - // 先使用默认值 - aspectRatio: "auto", - imageSize: "2K", - imageCount: 1, - // 然后应用 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)) { + onParamsChange(nextParams); } - }, [initialParams]); + }, [initialParams, providerType, imageGenConfig?.maxImages]); const updateParam = ( key: K, value: ImageGenerationParams[K] ) => { - const newParams = { ...params, [key]: value }; + const newParams = clampParamsByProvider({ ...params, [key]: value }); console.log("[ImageParamsBar] Updating param:", key, "=", value); console.log("[ImageParamsBar] New params:", newParams); setParams(newParams);