From 51ea17e8d9b301775cfbf07eda5e022b4cd3983d Mon Sep 17 00:00:00 2001 From: hengmaoqing Date: Mon, 25 May 2026 11:53:39 +0800 Subject: [PATCH] =?UTF-8?q?feat(image):=20=E5=AE=9E=E7=8E=B0=E5=9B=BE?= =?UTF-8?q?=E5=83=8F=E7=94=9F=E6=88=90=E8=B4=A8=E9=87=8F=E4=B8=8E=E5=B0=BA?= =?UTF-8?q?=E5=AF=B8=E8=87=AA=E9=80=82=E5=BA=94=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 QUALITY_BASE 配置对象定义低中高质量的基础像素值 - 实现 resolveSize 函数将质量等级与宽高比转换为精确像素尺寸 - 在图像生成请求中集成像素尺寸解析逻辑 - 更新图像编辑功能以支持动态像素尺寸设置 - 优化 API 请求参数传递方式使用展开运算符 - 调整页面组件中的质量选项显示标签增加像素信息 --- web/src/app/(user)/image/page.tsx | 7 +++++- web/src/services/api/image.ts | 42 ++++++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/web/src/app/(user)/image/page.tsx b/web/src/app/(user)/image/page.tsx index 63ef5db..4640f29 100644 --- a/web/src/app/(user)/image/page.tsx +++ b/web/src/app/(user)/image/page.tsx @@ -51,7 +51,12 @@ type GenerationLog = { type UpdateAiConfig = (key: K, value: AiConfig[K]) => void; const sizeOptions = ["auto", "1:1", "3:2", "2:3", "4:3", "3:4", "16:9", "9:16"].map((value) => ({ label: value, value })); -const qualityOptions = ["auto", "low", "medium", "high"].map((value) => ({ label: value, value })); +const qualityOptions = [ + { label: "auto", value: "auto" }, + { label: "low / 1K", value: "low" }, + { label: "medium / 2K", value: "medium" }, + { label: "high / 4K", value: "high" }, +]; const LOG_STORE_KEY = "infinite-canvas:image_generation_logs"; const LOG_STORE_PREFIX = `${LOG_STORE_KEY}:`; const logStore = localforage.createInstance({ name: "infinite-canvas", storeName: "image_generation_logs" }); diff --git a/web/src/services/api/image.ts b/web/src/services/api/image.ts index 3f4b233..bb4523e 100644 --- a/web/src/services/api/image.ts +++ b/web/src/services/api/image.ts @@ -18,6 +18,37 @@ type ImageApiResponse = { msg?: string; }; +const QUALITY_BASE: Record = { + low: 1024, + medium: 2048, + high: 2880, +}; + +/** Map "quality + ratio" to an explicit pixel dimension like "3840x2160". Returns undefined when quality is auto. */ +function resolveSize(quality: string, ratio: string): string | undefined { + const basePixels = QUALITY_BASE[quality]; + if (!basePixels || ratio === "auto" || !ratio) return undefined; + + const parts = ratio.split(":"); + if (parts.length !== 2) return undefined; + const w = Number(parts[0]); + const h = Number(parts[1]); + if (!w || !h) return undefined; + + const targetPixels = basePixels * basePixels; + const isLandscape = w >= h; + const longRatio = isLandscape ? w / h : h / w; + + const longSideRaw = Math.sqrt(targetPixels * longRatio); + const longSide = Math.floor(longSideRaw / 16) * 16; + const shortSide = Math.round((longSide / longRatio) / 16) * 16; + + const width = isLandscape ? longSide : shortSide; + const height = isLandscape ? shortSide : longSide; + + return `${width}x${height}`; +} + function resolveImageDataUrl(item: Record) { if (typeof item.b64_json === "string" && item.b64_json) { return `data:image/png;base64,${item.b64_json}`; @@ -94,6 +125,7 @@ function withSystemMessage(config: AiConfig, messages: ChatCompletionMessage[]) export async function requestGeneration(config: AiConfig, prompt: string) { const n = Math.max(1, Math.min(15, Math.floor(Math.abs(Number(config.count)) || 1))); + const pixelSize = resolveSize(config.quality, config.size); try { const response = await axios.post( aiApiUrl(config, "/images/generations"), @@ -101,8 +133,7 @@ export async function requestGeneration(config: AiConfig, prompt: string) { model: config.model, prompt: withSystemPrompt(config, prompt), n, - quality: config.quality || undefined, - size: config.size || undefined, + ...(pixelSize ? { quality: config.quality, size: pixelSize } : {}), response_format: "b64_json", }, { @@ -117,16 +148,15 @@ export async function requestGeneration(config: AiConfig, prompt: string) { export async function requestEdit(config: AiConfig, prompt: string, references: ReferenceImage[]) { const n = Math.max(1, Math.min(15, Math.floor(Math.abs(Number(config.count)) || 1))); + const pixelSize = resolveSize(config.quality, config.size); const formData = new FormData(); formData.set("model", config.model); formData.set("prompt", withSystemPrompt(config, prompt)); formData.set("n", String(n)); formData.set("response_format", "b64_json"); - if (config.quality) { + if (pixelSize) { formData.set("quality", config.quality); - } - if (config.size) { - formData.set("size", config.size); + formData.set("size", pixelSize); } const files = await Promise.all(references.map(async (image) => dataUrlToFile({ ...image, dataUrl: await imageToDataUrl(image) }))); files.forEach((file) => formData.append("image", file));