fix: map grsai gpt image sizes
This commit is contained in:
parent
dd4426ded7
commit
2cf2162fba
@ -3,6 +3,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { 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";
|
||||
@ -204,6 +205,10 @@ export function ImageParamsBar({
|
||||
});
|
||||
|
||||
const getComputedOutputSize = (aspectRatio: string, imageSize: string) => {
|
||||
if (providerType === "grsai-gpt-image") {
|
||||
return getGrsaiGptImageOutputSize(aspectRatio, imageSize);
|
||||
}
|
||||
|
||||
const size = IMAGE_SIZE_PIXEL_MAP[imageSize] || IMAGE_SIZE_PIXEL_MAP["2K"];
|
||||
if (aspectRatio === "auto" || aspectRatio === "1:1") {
|
||||
return { width: size, height: size };
|
||||
|
||||
@ -781,14 +781,24 @@ function inferExplicitAspectRatioFromText(text: string): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function inferExplicitImageSizeFromText(text: string): "1K" | "2K" | "4K" | undefined {
|
||||
const normalized = String(text || "").toLowerCase();
|
||||
if (/(^|[^0-9a-z])4\s*k([^0-9a-z]|$)/.test(normalized)) return "4K";
|
||||
if (/(^|[^0-9a-z])2\s*k([^0-9a-z]|$)/.test(normalized)) return "2K";
|
||||
if (/(^|[^0-9a-z])1\s*k([^0-9a-z]|$)/.test(normalized)) return "1K";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function extractExplicitImageParamsFromText(
|
||||
text: string,
|
||||
): Pick<AgentRecommendedImageParams, "aspectRatio" | "imageCount"> {
|
||||
const output: Pick<AgentRecommendedImageParams, "aspectRatio" | "imageCount"> = {};
|
||||
): Pick<AgentRecommendedImageParams, "aspectRatio" | "imageCount" | "imageSize"> {
|
||||
const output: Pick<AgentRecommendedImageParams, "aspectRatio" | "imageCount" | "imageSize"> = {};
|
||||
const aspectRatio = inferExplicitAspectRatioFromText(text);
|
||||
const imageCount = inferExplicitImageCountFromText(text);
|
||||
const imageSize = inferExplicitImageSizeFromText(text);
|
||||
if (aspectRatio) output.aspectRatio = aspectRatio;
|
||||
if (imageCount) output.imageCount = imageCount;
|
||||
if (imageSize) output.imageSize = imageSize;
|
||||
return output;
|
||||
}
|
||||
|
||||
@ -1050,8 +1060,9 @@ function heuristicImageParams(
|
||||
analysis: AnalysisResult,
|
||||
): AgentRecommendedImageParams | undefined {
|
||||
const normalized = text.toLowerCase();
|
||||
const explicitImageSize = inferExplicitImageSizeFromText(text);
|
||||
const params: AgentRecommendedImageParams = {
|
||||
imageSize: "2K",
|
||||
imageSize: explicitImageSize || (analysis.intent === "image_edit" ? "4K" : "2K"),
|
||||
promptExtend: analysis.intent === "image_edit" ? false : true,
|
||||
};
|
||||
|
||||
|
||||
94
lib/ai/grsai-gpt-image-sizes.ts
Normal file
94
lib/ai/grsai-gpt-image-sizes.ts
Normal file
@ -0,0 +1,94 @@
|
||||
export type GrsaiGptImageSizeBucket = "1K" | "2K" | "4K";
|
||||
|
||||
type GrsaiGptImageSizeOption = {
|
||||
bucket: GrsaiGptImageSizeBucket;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
const GRSAI_GPT_IMAGE_SIZE_OPTIONS: GrsaiGptImageSizeOption[] = [
|
||||
{ bucket: "1K", width: 1024, height: 1024 },
|
||||
{ bucket: "1K", width: 1774, height: 887 },
|
||||
{ bucket: "1K", width: 887, height: 1774 },
|
||||
{ bucket: "1K", width: 1536, height: 1024 },
|
||||
{ bucket: "1K", width: 1024, height: 1536 },
|
||||
{ bucket: "2K", width: 2048, height: 2048 },
|
||||
{ bucket: "2K", width: 2048, height: 1152 },
|
||||
{ bucket: "2K", width: 1152, height: 2048 },
|
||||
{ bucket: "2K", width: 2048, height: 1360 },
|
||||
{ bucket: "2K", width: 1360, height: 2048 },
|
||||
{ bucket: "2K", width: 2048, height: 880 },
|
||||
{ bucket: "2K", width: 880, height: 2048 },
|
||||
{ bucket: "2K", width: 2048, height: 688 },
|
||||
{ bucket: "2K", width: 688, height: 2048 },
|
||||
{ bucket: "2K", width: 2048, height: 1024 },
|
||||
{ bucket: "2K", width: 1024, height: 2048 },
|
||||
{ bucket: "4K", width: 2880, height: 2880 },
|
||||
{ bucket: "4K", width: 3840, height: 2160 },
|
||||
{ bucket: "4K", width: 2160, height: 3840 },
|
||||
{ bucket: "4K", width: 3504, height: 2336 },
|
||||
{ bucket: "4K", width: 2336, height: 3504 },
|
||||
{ bucket: "4K", width: 3840, height: 1648 },
|
||||
{ bucket: "4K", width: 1648, height: 3840 },
|
||||
{ bucket: "4K", width: 3840, height: 1280 },
|
||||
{ bucket: "4K", width: 1280, height: 3840 },
|
||||
{ bucket: "4K", width: 3840, height: 1920 },
|
||||
{ bucket: "4K", width: 1920, height: 3840 },
|
||||
];
|
||||
|
||||
const GRSAI_GPT_IMAGE_SIZE_VALUES = new Set(
|
||||
GRSAI_GPT_IMAGE_SIZE_OPTIONS.map((option) => `${option.width}x${option.height}`)
|
||||
);
|
||||
|
||||
function normalizeGrsaiGptImageBucket(imageSize?: string): GrsaiGptImageSizeBucket {
|
||||
const normalized = String(imageSize || "").trim().toUpperCase();
|
||||
if (normalized === "1K" || normalized === "2K" || normalized === "4K") {
|
||||
return normalized;
|
||||
}
|
||||
return "2K";
|
||||
}
|
||||
|
||||
function parseAspectRatioValue(aspectRatio?: string): number {
|
||||
const normalized = String(aspectRatio || "").trim().toLowerCase();
|
||||
if (!normalized || normalized === "auto") {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const [widthRaw, heightRaw] = normalized.split(":").map((part) => Number(part));
|
||||
if (!Number.isFinite(widthRaw) || !Number.isFinite(heightRaw) || widthRaw <= 0 || heightRaw <= 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return widthRaw / heightRaw;
|
||||
}
|
||||
|
||||
export function getGrsaiGptImageOutputSize(aspectRatio?: string, imageSize?: string) {
|
||||
const bucket = normalizeGrsaiGptImageBucket(imageSize);
|
||||
const targetRatio = parseAspectRatioValue(aspectRatio);
|
||||
const candidates = GRSAI_GPT_IMAGE_SIZE_OPTIONS.filter((option) => option.bucket === bucket);
|
||||
const best = candidates.reduce((currentBest, option) => {
|
||||
const currentDelta = Math.abs(currentBest.width / currentBest.height - targetRatio);
|
||||
const optionDelta = Math.abs(option.width / option.height - targetRatio);
|
||||
if (optionDelta < currentDelta) {
|
||||
return option;
|
||||
}
|
||||
if (optionDelta === currentDelta) {
|
||||
const currentArea = currentBest.width * currentBest.height;
|
||||
const optionArea = option.width * option.height;
|
||||
return optionArea > currentArea ? option : currentBest;
|
||||
}
|
||||
return currentBest;
|
||||
}, candidates[0]);
|
||||
|
||||
return { width: best.width, height: best.height };
|
||||
}
|
||||
|
||||
export function resolveGrsaiGptImageSize(aspectRatio?: string, imageSize?: string): string {
|
||||
const requestedSize = String(imageSize || "").trim().toLowerCase();
|
||||
if (/^\d+x\d+$/.test(requestedSize) && GRSAI_GPT_IMAGE_SIZE_VALUES.has(requestedSize)) {
|
||||
return requestedSize;
|
||||
}
|
||||
|
||||
const { width, height } = getGrsaiGptImageOutputSize(aspectRatio, imageSize);
|
||||
return `${width}x${height}`;
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import "server-only";
|
||||
|
||||
import { resolveGrsaiGptImageSize } from "@/lib/ai/grsai-gpt-image-sizes";
|
||||
import { uploadFile } from "@/lib/storage/service";
|
||||
|
||||
interface GeminiImageConfig {
|
||||
@ -482,8 +483,11 @@ async function sendGeminiRequest(
|
||||
}
|
||||
|
||||
if (isGptImageModel) {
|
||||
requestBody.size = imageParams?.aspectRatio || "auto";
|
||||
requestBody.size = resolveGrsaiGptImageSize(imageParams?.aspectRatio, imageParams?.imageSize);
|
||||
requestBody.shutProgress = false;
|
||||
console.log(
|
||||
`${apiLogPrefix} Resolved size: aspectRatio=${imageParams?.aspectRatio || "auto"}, imageSize=${imageParams?.imageSize || "2K"} => ${requestBody.size}`
|
||||
);
|
||||
} else {
|
||||
// 添加 Nano Banana 特定参数
|
||||
if (imageParams?.aspectRatio) {
|
||||
@ -602,8 +606,11 @@ async function sendGeminiRequest(
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
if (error instanceof Error && error.name === "AbortError") {
|
||||
console.error("[Gemini Image] Request timeout after 120 seconds");
|
||||
throw new Error("Gemini 请求超时(120秒),将自动切换备用模型");
|
||||
const timeoutSeconds = Math.round(timeout / 1000);
|
||||
console.error(`${apiLogPrefix} Request timeout after ${timeoutSeconds} seconds`);
|
||||
throw new Error(
|
||||
`${isGptImageModel ? "Grsai GPT Image" : "Gemini"} 请求超时(${timeoutSeconds}秒),将自动切换备用模型`
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user