"use client"; import { useEffect, useState } from "react"; import { Button } from "@/components/ui/button"; import { ChevronDown, ChevronUp, Link2, Scan, SlidersHorizontal, Sparkles } from "lucide-react"; import { getGrsaiGptImageOutputSize } from "@/lib/ai/grsai-gpt-image-sizes"; 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" | "minimal"; 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; type ImageSizeValue = (typeof IMAGE_SIZE_OPTIONS)[number]["value"]; function normalizeImageSizeValue(value?: string, fallback: ImageSizeValue = "2K"): ImageSizeValue { const normalized = String(value || "").trim().toUpperCase(); if (normalized === "1K" || normalized === "2K" || normalized === "4K") { return normalized; } return fallback; } 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 = () => { if (imageGenConfig?.maxImages) { return Array.from({ length: imageGenConfig.maxImages }, (_, i) => i + 1); } if (inlineTheme === "minimal") { return Array.from({ length: 10 }, (_, i) => i + 1); } if (providerType === "ruxa-image") { return [1]; } if ( providerType === "openai-image" || providerType === "stepfun-image" || providerType === "midjourney" ) { return [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 isImageSizeAllowed = (s: ImageSizeValue) => { if (!imageGenConfig) return true; if (s === "1K") return imageGenConfig.supports1K !== false; if (s === "2K") return imageGenConfig.supports2K !== false; if (s === "4K") return imageGenConfig.supports4K !== false; return true; }; const getFallbackImageSize = () => (["2K", "1K", "4K"] as const).find(isImageSizeAllowed) || "2K"; 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 = normalizeImageSizeValue(raw.imageSize); if (!isImageSizeAllowed(imageSize)) { imageSize = getFallbackImageSize(); } 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) => { const hasSourceAspectRatio = typeof source?.aspectRatio === "string" && source.aspectRatio.trim().length > 0; return clampParamsByProvider({ aspectRatio: inlineTheme === "minimal" ? "1:1" : "auto", imageSize: "2K", imageCount: 1, promptExtend: true, watermark: false, ...source, ...(inlineTheme === "minimal" && !hasSourceAspectRatio ? { aspectRatio: "1:1" } : {}), }); }; const [params, setParams] = useState({ ...buildBaseParams(initialParams), }); const [minimalParamsOpen, setMinimalParamsOpen] = useState(false); const [qualityLabel, setQualityLabel] = useState<"自动" | "高" | "中" | "低">("中"); 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; return isImageSizeAllowed(option.value); }); const getComputedOutputSize = (aspectRatio: string, imageSize: string) => { if (providerType === "grsai-gpt-image") { return getGrsaiGptImageOutputSize(aspectRatio, imageSize); } const size = IMAGE_SIZE_PIXEL_MAP[normalizeImageSizeValue(imageSize)]; 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); } }; const updateParams = (patch: Partial) => { const newParams = clampParamsByProvider({ ...params, ...patch }); setParams((prevParams) => (isSameParams(prevParams, newParams) ? prevParams : newParams)); if (!isSameParams(params, newParams)) { onParamsChange?.(newParams); } }; // 内联模式:只返回参数按钮,嵌入工具栏 if (inline) { const isDarkInline = inlineTheme === "dark"; const isMinimalInline = inlineTheme === "minimal"; const outputSize = getComputedOutputSize(params.aspectRatio, params.imageSize); if (isMinimalInline) { const sizeOptions = [ { label: "1:1", aspectRatio: "1:1", imageSize: "1K" }, { label: "3:2", aspectRatio: "3:2", imageSize: "1K" }, { label: "2:3", aspectRatio: "2:3", imageSize: "1K" }, { label: "4:3", aspectRatio: "4:3", imageSize: "1K" }, { label: "3:4", aspectRatio: "3:4", imageSize: "1K" }, { label: "9:16", aspectRatio: "9:16", imageSize: "1K" }, { label: "1:1(2k)", aspectRatio: "1:1", imageSize: "2K" }, { label: "16:9(2k)", aspectRatio: "16:9", imageSize: "2K" }, { label: "9:16(2k)", aspectRatio: "9:16", imageSize: "2K" }, { label: "16:9(4K)", aspectRatio: "16:9", imageSize: "4K" }, { label: "9:16(4K)", aspectRatio: "9:16", imageSize: "4K" }, { label: "auto", aspectRatio: "auto", imageSize: params.imageSize || "1K" }, ].filter((option) => option.aspectRatio === "auto" || isImageSizeAllowed(normalizeImageSizeValue(option.imageSize)) ); const imageCountOptions = getImageCountOptions(); const selectedAspectLabel = params.aspectRatio === "auto" ? "auto" : params.aspectRatio; const selectedImageCount = params.imageCount || 1; const renderMinimalRatioGlyph = (ratioValue: string) => { if (ratioValue === "auto") { return auto; } const [wRaw, hRaw] = ratioValue.split(":").map((item) => Number(item)); if (!wRaw || !hRaw) { return ; } const maxSide = 18; let width = maxSide; let height = maxSide; if (wRaw >= hRaw) { width = maxSide; height = Math.max(7, Math.round((maxSide * hRaw) / wRaw)); } else { height = maxSide; width = Math.max(7, Math.round((maxSide * wRaw) / hRaw)); } return ( ); }; return (
质量
{(["自动", "高", "中", "低"] as const).map((quality) => ( ))}
尺寸
W {outputSize.width}
H {outputSize.height}
Size
{sizeOptions.map((option) => { const selected = params.aspectRatio === option.aspectRatio && (option.aspectRatio === "auto" || params.imageSize === option.imageSize); return ( ); })}
Image
{imageCountOptions.map((count) => ( ))}
); } 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; }