ai-chatbot/components/image-params-bar.tsx
Your Name a473d9710c
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
修复GEMINI生图二次对话4K/2K问题
- 修复ImageParamsBar组件的useEffect逻辑,确保参数正确同步
- 修复chat.tsx中imageParams的初始化,确保所有字段都有值
- 解决二次对话时选择4K但生成2K的问题
- 确保从localStorage恢复的参数能正确传递给ImageParamsBar组件

问题根源:
- ImageParamsBar的useEffect只在initialParams存在时执行
- 如果initialParams是undefined,会使用默认值2K
- imageParams可能未正确传递,导致参数丢失

修复方案:
- 修改useEffect条件判断,使用!== undefined而不是truthy检查
- 确保imageParams始终有完整的默认值结构
- 添加详细的分析报告文档
2026-01-02 21:18:46 +08:00

185 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Check, Image, Ratio, Maximize2, Images } from "lucide-react";
import { cn } from "@/lib/utils";
// 图片生成参数接口
export interface ImageGenerationParams {
aspectRatio: string;
imageSize: string;
imageCount?: number; // 🆕 生成图片数量
}
interface ImageParamsBarProps {
modelName?: string;
initialParams?: Partial<ImageGenerationParams>;
onParamsChange?: (params: ImageGenerationParams) => void;
inline?: boolean; // 内联模式:只显示参数按钮,不显示外层容器
providerType?: string; // 🆕 模型类型:gemini, volcano-vision 等
}
// 图片比例选项(根据 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 }, // 4K较慢
] as const;
export function ImageParamsBar({
modelName = "Gemini 图片生成",
initialParams,
onParamsChange,
inline = false,
providerType = "gemini", // 默认 gemini
}: ImageParamsBarProps) {
const [params, setParams] = useState<ImageGenerationParams>({
aspectRatio: "auto",
imageSize: "2K", // 默认 2K
imageCount: 1, // 🆕 默认 1 张
...initialParams,
});
// 🆕 根据模型类型确定可选的图片数量
const getImageCountOptions = () => {
if (providerType === "volcano-vision") {
// 即梦4.5(豆包):1~3 张
return [1, 2, 3];
}
// Gemini:1~2 张
return [1, 2];
};
const imageCountOptions = getImageCountOptions();
// 🆕 监听 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,
}));
}
}, [initialParams]);
const updateParam = <K extends keyof ImageGenerationParams>(
key: K,
value: ImageGenerationParams[K]
) => {
const newParams = { ...params, [key]: value };
console.log("[ImageParamsBar] Updating param:", key, "=", value);
console.log("[ImageParamsBar] New params:", newParams);
setParams(newParams);
onParamsChange?.(newParams);
};
// 内联模式:只返回参数按钮,嵌入工具栏
if (inline) {
return (
<div className="flex items-center ml-1 border-l border-border/50 pl-1">
{/* 图片比例 */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 px-2 gap-1 focus-visible:ring-0 focus-visible:ring-offset-0 focus:outline-none focus:ring-0">
<Ratio size={14} />
<span className="font-medium text-xs">{params.aspectRatio}</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="center" className="w-48">
<div className="p-1.5 grid grid-cols-3 gap-1">
{ASPECT_RATIO_OPTIONS.map((option) => (
<Button
key={option.value}
variant={params.aspectRatio === option.value ? "secondary" : "ghost"}
size="sm"
className="h-7 text-xs focus-visible:ring-0 focus-visible:ring-offset-0 focus:outline-none focus:ring-0"
onClick={() => updateParam("aspectRatio", option.value)}
>
{option.label}
</Button>
))}
</div>
</DropdownMenuContent>
</DropdownMenu>
{/* 分辨率 */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 px-2 gap-1 focus-visible:ring-0 focus-visible:ring-offset-0 focus:outline-none focus:ring-0">
<Maximize2 size={14} />
<span className="font-medium text-xs">{params.imageSize}</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="center" className="min-w-[100px]">
{IMAGE_SIZE_OPTIONS.map((option) => (
<DropdownMenuItem
key={option.value}
onClick={() => updateParam("imageSize", option.value)}
className="focus-visible:ring-0 focus-visible:ring-offset-0 focus:outline-none focus:ring-0"
>
<Check className={cn("mr-2 h-4 w-4", params.imageSize === option.value ? "opacity-100" : "opacity-0")} />
{option.label}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
{/* 🆕 图片数量 */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 px-2 gap-1 focus-visible:ring-0 focus-visible:ring-offset-0 focus:outline-none focus:ring-0">
<Images size={14} />
<span className="font-medium text-xs">{params.imageCount || 1}张</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="center" className="min-w-[80px]">
{imageCountOptions.map((count) => (
<DropdownMenuItem
key={count}
onClick={() => updateParam("imageCount", count)}
className="focus-visible:ring-0 focus-visible:ring-offset-0 focus:outline-none focus:ring-0"
>
<Check className={cn("mr-2 h-4 w-4", (params.imageCount || 1) === count ? "opacity-100" : "opacity-0")} />
{count}张
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}
// 非内联模式(暂时不实现,因为只需要内联模式)
return null;
}