"use client"; import { useEffect, useState } from "react"; import { Button } from "@/components/ui/button"; import { Link2, Scan, SlidersHorizontal, Sparkles } from "lucide-react"; import { cn } from "@/lib/utils"; import { Input } from "@/components/ui/input"; import { Switch } from "@/components/ui/switch"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; // 图片生成参数接口 export interface ImageGenerationParams { aspectRatio: string; imageSize: string; imageCount?: number; // 生成图片数量 // 🆕 阿里生图专用参数 size?: string; // 自定义分辨率,如 "1024*1024" negativePrompt?: string; // 反向提示词 promptExtend?: boolean; // 提示词智能优化 watermark?: boolean; // 水印 seed?: number; // 随机种子 } interface ImageParamsBarProps { modelName?: string; initialParams?: Partial; onParamsChange?: (params: ImageGenerationParams) => void; inline?: boolean; // 内联模式:只显示参数按钮,不显示外层容器 providerType?: string; // 模型类型:gemini, volcano-vision, alibaba-image, ruxa-image 等 hideImageCount?: boolean; // 隐藏图片数量按钮(仅个别页面需要) hideAdvancedOptions?: boolean; // 隐藏高级设置按钮 inlineTheme?: "light" | "dark"; showInlineDivider?: boolean; // 🆕 图片生成模型特性配置(从模型配置中读取) imageGenConfig?: { maxImages?: number; supportsSize?: boolean; supports1K?: boolean; supports2K?: boolean; supports4K?: boolean; supportsPromptExtend?: boolean; supportsNegativePrompt?: boolean; supportsWatermark?: boolean; supportsSeed?: boolean; }; } // 图片比例选项(根据 Nano Banana 文档) const ASPECT_RATIO_OPTIONS = [ { value: "auto", label: "自动" }, { value: "1:1", label: "1:1" }, { value: "16:9", label: "16:9" }, { value: "9:16", label: "9:16" }, { value: "4:3", label: "4:3" }, { value: "3:4", label: "3:4" }, { value: "3:2", label: "3:2" }, { value: "2:3", label: "2:3" }, { value: "5:4", label: "5:4" }, { value: "4:5", label: "4:5" }, { value: "21:9", label: "21:9" }, ] as const; // 图片大小选项(仅支持 nano-banana-pro) const IMAGE_SIZE_OPTIONS = [ { value: "1K", label: "1K", disabled: false }, { value: "2K", label: "2K", disabled: false }, { value: "4K", label: "4K", disabled: false }, ] as const; const IMAGE_SIZE_PIXEL_MAP: Record = { "1K": 1024, "2K": 2048, "4K": 4096, }; export function ImageParamsBar({ modelName = "Gemini 图片生成", initialParams, onParamsChange, inline = false, providerType = "gemini", // 默认 gemini hideImageCount = false, hideAdvancedOptions = false, inlineTheme = "light", 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") { return [1]; } // 优先使用模型配置中的 maxImages if (imageGenConfig?.maxImages) { return Array.from({ length: imageGenConfig.maxImages }, (_, i) => i + 1); } // 否则根据 providerType 判断 if (providerType === "alibaba-image") { // 阿里生图:1~6 张(qwen-image-edit-plus) return [1, 2, 3, 4, 5, 6]; } if (providerType === "volcano-vision") { // 即梦4.5(豆包):1~3 张 return [1, 2, 3]; } // 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; // 根据模型配置校正分辨率:如果当前选择的档位被禁用,则降级到可用的档位 let imageSize = raw.imageSize || "2K"; if (imageGenConfig) { const sizeAllowed = (s: string) => { if (s === "1K") return imageGenConfig.supports1K !== false; if (s === "2K") return imageGenConfig.supports2K !== false; if (s === "4K") return imageGenConfig.supports4K !== false; return true; }; if (!sizeAllowed(imageSize)) { imageSize = ["2K", "1K", "4K"].find(sizeAllowed) || "2K"; } } return { ...raw, imageSize, 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(); const showImageCountSelector = !hideImageCount && imageCountOptions.length > 1; // 是否显示阿里生图专用参数 const isAlibabaImage = providerType === "alibaba-image"; const showPromptExtend = isAlibabaImage || imageGenConfig?.supportsPromptExtend; const showNegativePrompt = isAlibabaImage || imageGenConfig?.supportsNegativePrompt; // 根据模型配置过滤可用的分辨率档位 const availableSizeOptions = IMAGE_SIZE_OPTIONS.filter((option) => { if (option.disabled) return false; if (!imageGenConfig) return true; // 无配置时全部显示 if (option.value === "1K" && imageGenConfig.supports1K === false) return false; if (option.value === "2K" && imageGenConfig.supports2K === false) return false; if (option.value === "4K" && imageGenConfig.supports4K === false) return false; return true; }); const getComputedOutputSize = (aspectRatio: string, imageSize: string) => { const size = IMAGE_SIZE_PIXEL_MAP[imageSize] || IMAGE_SIZE_PIXEL_MAP["2K"]; if (aspectRatio === "auto" || aspectRatio === "1:1") { return { width: size, height: size }; } const [wRaw, hRaw] = aspectRatio.split(":").map((item) => Number(item)); if (!wRaw || !hRaw) { return { width: size, height: size }; } if (wRaw >= hRaw) { return { width: size, height: Math.max(1, Math.round((size * hRaw) / wRaw)), }; } return { width: Math.max(1, Math.round((size * wRaw) / hRaw)), height: size, }; }; const renderRatioGlyph = (ratioValue: string) => { if (ratioValue === "auto") { return ; } const [wRaw, hRaw] = ratioValue.split(":").map((item) => Number(item)); if (!wRaw || !hRaw) { return ; } const maxSide = 14; let width = maxSide; let height = maxSide; if (wRaw >= hRaw) { width = maxSide; height = Math.max(6, Math.round((maxSide * hRaw) / wRaw)); } else { height = maxSide; width = Math.max(6, Math.round((maxSide * wRaw) / hRaw)); } return ( ); }; // 仅同步展示状态;只有确实发生参数校正时才回写父层,避免和父组件互相触发更新。 useEffect(() => { const nextParams = buildBaseParams(initialParams); setParams((prevParams) => (isSameParams(prevParams, nextParams) ? prevParams : nextParams)); if (onParamsChange && hasNormalizedParamDiff(initialParams, nextParams)) { onParamsChange(nextParams); } }, [ imageGenConfig?.maxImages, imageGenConfig?.supports1K, imageGenConfig?.supports2K, imageGenConfig?.supports4K, initialParams, onParamsChange, providerType, ]); const updateParam = ( key: K, value: ImageGenerationParams[K] ) => { const newParams = clampParamsByProvider({ ...params, [key]: value }); setParams((prevParams) => (isSameParams(prevParams, newParams) ? prevParams : newParams)); if (!isSameParams(params, newParams)) { onParamsChange?.(newParams); } }; // 内联模式:只返回参数按钮,嵌入工具栏 if (inline) { const isDarkInline = inlineTheme === "dark"; const ratioLabel = params.aspectRatio === "auto" ? "智能比例" : params.aspectRatio; const sizeLabel = params.imageSize; const outputSize = getComputedOutputSize(params.aspectRatio, params.imageSize); return (

选择比例

{ASPECT_RATIO_OPTIONS.map((option) => ( ))}

选择分辨率

{availableSizeOptions.map((option) => ( ))}

尺寸

W {outputSize.width}
H {outputSize.height}
PX
{showImageCountSelector && (

生成数量

{imageCountOptions.map((count) => ( ))}
)} {/* 🆕 阿里生图专用:提示词优化 & 反向提示词 */} {!hideAdvancedOptions && (showPromptExtend || showNegativePrompt) && (
{/* 提示词智能优化 */} {showPromptExtend && (
提示词优化

AI 自动优化您的描述

updateParam("promptExtend", checked)} />
)} {/* 反向提示词 */} {showNegativePrompt && (
反向提示词 updateParam("negativePrompt", e.target.value)} className={cn( "h-7 rounded-[8px] text-[12px]", isDarkInline ? "border-white/12 bg-white/[0.04] text-white placeholder:text-white/28" : "border-[#dce1e8] bg-[#eef1f4]/90 text-[#2f3b48] placeholder:text-[#b0b8c4]" )} />
)}
)}
); } // 非内联模式(暂时不实现,因为只需要内联模式) return null; }