fix: resolve issue with Gemini image format not passing aspect ratio configuration

This commit is contained in:
HouYunFei
2026-07-08 15:11:18 +08:00
parent 3080d6da10
commit 81144ea009
3 changed files with 42 additions and 1 deletions
+1
View File
@@ -5,6 +5,7 @@
+ [新增] 新增Codex App插件支持。
+ [新增] 配置与用户偏好新增独立页面和 Codex 连接配置 Tab。
+ [修复] 修复生图工作台重试成功结果刷新后丢失的问题。
+ [修复] 修复 Gemini 调用格式生图未传递尺寸比例配置的问题。
+ [新增] 新增GitHub Pages 前端静态站点发布 workflow。
+ [新增] 图片切图支持等分线直接拖拽调整,并可新增、删除和重置横向 / 纵向切图线。
@@ -16,6 +16,7 @@ description: 当前版本已实现但仍需人工验证的变更项
- 画布 Agent:右侧 Agent 面板移除网站 / 本机模式切换,固定使用本机 Agent。
- 配置与用户偏好:每个模型渠道新增 OpenAI / Gemini 调用格式选择,默认 OpenAI,切换默认格式时同步更新默认 Base URL;渠道 Tab 增加紧凑醒目的提醒,说明模型是否可选需要到“模型”Tab 选择。
- Gemini 调用格式:支持拉取 Gemini 模型列表,并用于文本问答、在线 Agent 工具调用、基础生图和图生图请求。
- Gemini 生图:生图工作台和画布生图会把尺寸配置转换为 Gemini `responseFormat.image.aspectRatio` 传入,需验证 1:1、9:16、16:9 及自定义像素尺寸的输出比例。
- 音频和视频生成:选择 Gemini 调用格式时先给出不支持提示,仍需使用 OpenAI 兼容渠道。
- WebDAV 同步:移除 Next.js 转发代理和连接方式选择,改为浏览器前端直接连接 WebDAV 服务。
- 提示词库:移除 Next.js route 抓取和服务端内存缓存,改为浏览器前端直连 GitHub 原始文件并缓存到 IndexedDB。
+40 -1
View File
@@ -112,6 +112,9 @@ const IMAGE_MAX_EDGE = 3840;
const IMAGE_MAX_RATIO = 3;
const IMAGE_OUTPUT_FORMAT = "png";
const GEMINI_SUPPORTED_RATIOS = ["1:1", "1:4", "1:8", "2:3", "3:2", "3:4", "4:1", "4:3", "4:5", "5:4", "8:1", "9:16", "16:9", "21:9"];
const GEMINI_IMAGE_SIZE_BY_QUALITY: Record<string, string> = { low: "1K", medium: "2K", high: "4K", standard: "1K", hd: "2K" };
function normalizeQuality(quality: string) {
const value = quality.trim().toLowerCase();
const normalized = QUALITY_ALIASES[value] || value;
@@ -180,6 +183,42 @@ function resolveRequestSize(quality: string | undefined, size: string) {
throw new Error("图像尺寸格式不支持,请使用 auto、9:16 或 1024x1024");
}
function resolveGeminiImageConfig(config: AiConfig) {
const value = config.size.trim();
const dimensions = parseImageDimensions(value);
const ratio = dimensions ? `${dimensions.width}:${dimensions.height}` : value;
const aspectRatio = value && value.toLowerCase() !== "auto" ? closestGeminiAspectRatio(ratio) : undefined;
const imageSize = supportsGeminiImageSize(config.model) ? resolveGeminiImageSize(config.quality, dimensions) : undefined;
const image = { ...(aspectRatio ? { aspectRatio } : {}), ...(imageSize ? { imageSize } : {}) };
return Object.keys(image).length ? { responseFormat: { image } } : {};
}
function closestGeminiAspectRatio(value: string) {
const ratio = parseImageRatio(value);
const target = ratio.width / ratio.height;
return GEMINI_SUPPORTED_RATIOS.reduce((best, item) => {
const current = parseImageRatio(item);
const bestRatio = parseImageRatio(best);
return Math.abs(current.width / current.height - target) < Math.abs(bestRatio.width / bestRatio.height - target) ? item : best;
});
}
function resolveGeminiImageSize(quality: string, dimensions: { width: number; height: number } | null) {
const normalizedQuality = normalizeQuality(quality);
if (normalizedQuality) return GEMINI_IMAGE_SIZE_BY_QUALITY[normalizedQuality];
if (!dimensions) return undefined;
const edge = Math.max(dimensions.width, dimensions.height);
if (edge <= 768) return "512";
if (edge <= 1536) return "1K";
if (edge <= 3072) return "2K";
return "4K";
}
function supportsGeminiImageSize(model: string) {
const value = model.toLowerCase();
return value.includes("gemini-3") || value.includes("3.1") || value.includes("3-pro");
}
function resolveImageDataUrl(item: Record<string, unknown>) {
if (typeof item.b64_json === "string" && item.b64_json) {
return `data:image/png;base64,${item.b64_json}`;
@@ -585,7 +624,7 @@ async function requestGeminiImagesOnce(config: AiConfig, prompt: string, referen
const response = await axios.post<GeminiPayload>(
geminiApiUrl(config, "generateContent"),
{
...toGeminiBody(config, [{ role: "user", content: prompt }], { generationConfig: { responseModalities: ["TEXT", "IMAGE"] } }),
...toGeminiBody(config, [{ role: "user", content: prompt }], { generationConfig: { responseModalities: ["TEXT", "IMAGE"], ...resolveGeminiImageConfig(config) } }),
contents: [{ role: "user", parts }],
},
{ headers: geminiHeaders(config), signal: options?.signal },