mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-07-31 05:31:13 +08:00
fix: resolve issue with Gemini image format not passing aspect ratio configuration
This commit is contained in:
@@ -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。
|
||||
|
||||
@@ -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 },
|
||||
|
||||
Reference in New Issue
Block a user