feat: 增加gpt-image-2 模型
This commit is contained in:
@@ -27,6 +27,13 @@ DASHSCOPE_API_KEY=
|
|||||||
DASHSCOPE_BASE_URL=
|
DASHSCOPE_BASE_URL=
|
||||||
DASHSCOPE_MODEL=wan2.7-image-pro
|
DASHSCOPE_MODEL=wan2.7-image-pro
|
||||||
|
|
||||||
|
# --- RightAPI(gpt-image,OpenAI 兼容中转)---
|
||||||
|
RIGHTAPI_API_KEY=
|
||||||
|
RIGHTAPI_BASE_URL=https://rightapi.ai/draw
|
||||||
|
RIGHTAPI_IMAGE_MODEL=gpt-image-2
|
||||||
|
# 出图质量:auto | low | medium | high(high 单张约 1-5 分钟,超时自动兜底 600s)
|
||||||
|
RIGHTAPI_IMAGE_QUALITY=high
|
||||||
|
|
||||||
# --- DeepSeek(出图方案规划器)---
|
# --- DeepSeek(出图方案规划器)---
|
||||||
DEEPSEEK_API_KEY=
|
DEEPSEEK_API_KEY=
|
||||||
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
|
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ Chrome 插件(WXT + React + antd) Python 后端(FastAPI + SQLite
|
|||||||
- `generator.py`:图像 provider(图生图,参考图 = 采集主图)
|
- `generator.py`:图像 provider(图生图,参考图 = 采集主图)
|
||||||
- `doubao`:火山方舟 Seedream(默认,`ARK_API_KEY`)
|
- `doubao`:火山方舟 Seedream(默认,`ARK_API_KEY`)
|
||||||
- `tongyi`:通义万相/千问(`DASHSCOPE_API_KEY`,wan* 异步轮询 / qwen* 同步)
|
- `tongyi`:通义万相/千问(`DASHSCOPE_API_KEY`,wan* 异步轮询 / qwen* 同步)
|
||||||
|
- `rightapi`:gpt-image-2(OpenAI 兼容中转 `RIGHTAPI_API_KEY`,edits 参考图 / generations 同步)
|
||||||
|
- 插件只传模型名,服务端按模型名自动路由到对应 provider
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# ===== 服务 =====
|
||||||
|
# 注意:不要用 5000/7000(macOS 隔空播放接收器占用)
|
||||||
|
HOST=127.0.0.1
|
||||||
|
PORT=3300
|
||||||
|
# 拼给插件/前端的媒体访问地址(默认本机)
|
||||||
|
APP_BASE_URL=http://127.0.0.1:3300
|
||||||
|
|
||||||
|
# ===== 存储 =====
|
||||||
|
# 图片/数据库落盘目录(默认 <项目根>/data)
|
||||||
|
# DATA_DIR=/Users/joey/sites/seller-store/image-suite-studio/data
|
||||||
|
|
||||||
|
# ===== 图像生成 =====
|
||||||
|
# 默认 provider:doubao(火山方舟 Seedream)| tongyi(阿里 DashScope)| rightapi(gpt-image)
|
||||||
|
IMAGE_PROVIDER=doubao
|
||||||
|
# 单张生图请求超时(秒)
|
||||||
|
REQUEST_TIMEOUT=300
|
||||||
|
# 异步任务轮询上限(秒)
|
||||||
|
POLL_MAX_WAIT=600
|
||||||
|
|
||||||
|
# --- 豆包 / 火山方舟 ---
|
||||||
|
ARK_API_KEY=
|
||||||
|
ARK_BASE_URL=https://ark.cn-beijing.volces.com/api/v3/images/generations
|
||||||
|
ARK_IMAGE_MODEL=doubao-seedream-4-5-251128
|
||||||
|
|
||||||
|
# --- 通义 / DashScope ---
|
||||||
|
DASHSCOPE_API_KEY=
|
||||||
|
DASHSCOPE_BASE_URL=
|
||||||
|
DASHSCOPE_MODEL=wan2.7-image-pro
|
||||||
|
|
||||||
|
# --- RightAPI(gpt-image,OpenAI 兼容中转)---
|
||||||
|
RIGHTAPI_API_KEY=sk-b6b9ffba28b64ca795be31f6dee842c6
|
||||||
|
RIGHTAPI_BASE_URL=https://rightapi.ai/draw
|
||||||
|
RIGHTAPI_IMAGE_MODEL=gpt-image-2
|
||||||
|
# 出图质量:auto | low | medium | high(high 单张约 1-5 分钟,超时自动兜底 600s)
|
||||||
|
RIGHTAPI_IMAGE_QUALITY=high
|
||||||
@@ -20,5 +20,6 @@
|
|||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"typescript": "^5.5.3",
|
"typescript": "^5.5.3",
|
||||||
"wxt": "^0.19.0"
|
"wxt": "^0.19.0"
|
||||||
}
|
},
|
||||||
|
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export const PLATFORM_OPTIONS = [
|
|||||||
|
|
||||||
export type PlatformId = (typeof PLATFORM_OPTIONS)[number]['value'];
|
export type PlatformId = (typeof PLATFORM_OPTIONS)[number]['value'];
|
||||||
|
|
||||||
/** 生图模型(通义 DashScope):下拉可选 + 中文特点说明 */
|
/** 生图模型(下拉可选 + 中文特点说明;服务端按模型名路由 provider) */
|
||||||
export const IMAGE_MODEL_OPTIONS = [
|
export const IMAGE_MODEL_OPTIONS = [
|
||||||
{
|
{
|
||||||
value: 'qwen-image-3.0-pro',
|
value: 'qwen-image-3.0-pro',
|
||||||
@@ -114,6 +114,11 @@ export const IMAGE_MODEL_OPTIONS = [
|
|||||||
label: 'wan2.7-image-pro',
|
label: 'wan2.7-image-pro',
|
||||||
desc: '异步精修,质感与细节更强,适合高质量电商大片',
|
desc: '异步精修,质感与细节更强,适合高质量电商大片',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: 'gpt-image-2',
|
||||||
|
label: 'gpt-image-2',
|
||||||
|
desc: 'GPT 图像模型,构图与图内文案渲染最强,参考图高保真,单张 1-5 分钟',
|
||||||
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const PLATFORM_SPECS: Record<string, { lang: string; ratio: string; label: string }> = {
|
export const PLATFORM_SPECS: Record<string, { lang: string; ratio: string; label: string }> = {
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ from config import get_settings
|
|||||||
from db import get_db
|
from db import get_db
|
||||||
from models import Suite
|
from models import Suite
|
||||||
from schemas import (
|
from schemas import (
|
||||||
GenerateRequest, PLATFORM_SPECS, SUPPORTED_TYPES, TONGYI_MODELS, SuiteCreateResponse, TextMaterial,
|
GenerateRequest, PLATFORM_SPECS, SUPPORTED_TYPES, TONGYI_MODELS, RIGHTAPI_MODELS,
|
||||||
|
SuiteCreateResponse, TextMaterial, resolve_provider,
|
||||||
PlanRequest, PlanResponse, PlanItemOut,
|
PlanRequest, PlanResponse, PlanItemOut,
|
||||||
)
|
)
|
||||||
from services.generator import run_suite
|
from services.generator import run_suite
|
||||||
@@ -84,10 +85,13 @@ async def generate_suite(
|
|||||||
spec = PLATFORM_SPECS[req.platform]
|
spec = PLATFORM_SPECS[req.platform]
|
||||||
|
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
provider_name = req.provider or settings.image_provider
|
# 插件只传模型名:已知模型直接路由到对应 provider(gpt-image-2 → rightapi)
|
||||||
|
provider_name = resolve_provider(req.model, req.provider, settings.image_provider)
|
||||||
model = req.model
|
model = req.model
|
||||||
if provider_name == "tongyi" and model and model not in TONGYI_MODELS:
|
if provider_name == "tongyi" and model and model not in TONGYI_MODELS:
|
||||||
raise HTTPException(status_code=400, detail=f"不支持的模型: {model}(tongyi 支持: {TONGYI_MODELS})")
|
raise HTTPException(status_code=400, detail=f"不支持的模型: {model}(tongyi 支持: {TONGYI_MODELS})")
|
||||||
|
if provider_name == "rightapi" and model and model not in RIGHTAPI_MODELS:
|
||||||
|
raise HTTPException(status_code=400, detail=f"不支持的模型: {model}(rightapi 支持: {RIGHTAPI_MODELS})")
|
||||||
suite = Suite(
|
suite = Suite(
|
||||||
product_id=None,
|
product_id=None,
|
||||||
style_set=req.style_set,
|
style_set=req.style_set,
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
from config import get_settings
|
from config import get_settings
|
||||||
from db import get_db
|
from db import get_db
|
||||||
from models import Product, ProductAsset, Suite, SuiteImage, STATUS_OK
|
from models import Product, ProductAsset, Suite, SuiteImage, STATUS_OK
|
||||||
from schemas import PLATFORM_SPECS, SUPPORTED_TYPES, TONGYI_MODELS, SuiteCreateRequest, SuiteCreateResponse, SuiteImageOut, SuiteOut
|
from schemas import (
|
||||||
|
PLATFORM_SPECS, SUPPORTED_TYPES, TONGYI_MODELS, RIGHTAPI_MODELS,
|
||||||
|
SuiteCreateRequest, SuiteCreateResponse, SuiteImageOut, SuiteOut, resolve_provider,
|
||||||
|
)
|
||||||
from services import storage
|
from services import storage
|
||||||
from services.generator import run_suite
|
from services.generator import run_suite
|
||||||
|
|
||||||
@@ -76,9 +79,12 @@ async def create_suite(
|
|||||||
spec = PLATFORM_SPECS[req.platform]
|
spec = PLATFORM_SPECS[req.platform]
|
||||||
|
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
provider_name = req.provider or settings.image_provider
|
# 模型名优先路由:已知模型直接定位 provider(gpt-image-2 → rightapi)
|
||||||
|
provider_name = resolve_provider(req.model, req.provider, settings.image_provider)
|
||||||
if provider_name == "tongyi" and req.model and req.model not in TONGYI_MODELS:
|
if provider_name == "tongyi" and req.model and req.model not in TONGYI_MODELS:
|
||||||
raise HTTPException(status_code=400, detail=f"不支持的模型: {req.model}(tongyi 支持: {TONGYI_MODELS})")
|
raise HTTPException(status_code=400, detail=f"不支持的模型: {req.model}(tongyi 支持: {TONGYI_MODELS})")
|
||||||
|
if provider_name == "rightapi" and req.model and req.model not in RIGHTAPI_MODELS:
|
||||||
|
raise HTTPException(status_code=400, detail=f"不支持的模型: {req.model}(rightapi 支持: {RIGHTAPI_MODELS})")
|
||||||
suite = Suite(
|
suite = Suite(
|
||||||
product_id=product.id,
|
product_id=product.id,
|
||||||
style_set=req.style_set,
|
style_set=req.style_set,
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ class Settings(BaseSettings):
|
|||||||
dashscope_base_url: str = "" # 留空按模型自动选择万象异步/千问同步端点
|
dashscope_base_url: str = "" # 留空按模型自动选择万象异步/千问同步端点
|
||||||
dashscope_model: str = "wan2.7-image-pro"
|
dashscope_model: str = "wan2.7-image-pro"
|
||||||
|
|
||||||
|
# RightAPI(OpenAI 兼容中转,gpt-image 系列)
|
||||||
|
rightapi_api_key: str = ""
|
||||||
|
rightapi_base_url: str = "https://rightapi.ai/draw"
|
||||||
|
rightapi_image_model: str = "gpt-image-2"
|
||||||
|
rightapi_image_quality: str = "high" # auto | low | medium | high
|
||||||
|
|
||||||
# DeepSeek(出图方案规划器)
|
# DeepSeek(出图方案规划器)
|
||||||
deepseek_api_key: str = ""
|
deepseek_api_key: str = ""
|
||||||
deepseek_base_url: str = "https://api.deepseek.com/v1"
|
deepseek_base_url: str = "https://api.deepseek.com/v1"
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ async def health():
|
|||||||
"provider": settings.image_provider,
|
"provider": settings.image_provider,
|
||||||
"ark_configured": bool(settings.ark_api_key),
|
"ark_configured": bool(settings.ark_api_key),
|
||||||
"dashscope_configured": bool(settings.dashscope_api_key),
|
"dashscope_configured": bool(settings.dashscope_api_key),
|
||||||
|
"rightapi_configured": bool(settings.rightapi_api_key),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,22 @@ SUPPORTED_TYPES = [
|
|||||||
# 通义(DashScope)生图模型白名单:插件下拉可选的模型
|
# 通义(DashScope)生图模型白名单:插件下拉可选的模型
|
||||||
TONGYI_MODELS = ["qwen-image-3.0-pro", "wan2.7-image-pro"]
|
TONGYI_MODELS = ["qwen-image-3.0-pro", "wan2.7-image-pro"]
|
||||||
|
|
||||||
|
# RightAPI 生图模型白名单
|
||||||
|
RIGHTAPI_MODELS = ["gpt-image-2"]
|
||||||
|
|
||||||
|
# 模型 → provider 推断表:插件只传模型名,服务端据此路由(模型名优先于 provider 字段)
|
||||||
|
MODEL_PROVIDERS: dict[str, str] = {
|
||||||
|
**{m: "tongyi" for m in TONGYI_MODELS},
|
||||||
|
**{m: "rightapi" for m in RIGHTAPI_MODELS},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_provider(model: str | None, requested: str | None, default: str) -> str:
|
||||||
|
"""已知模型名直接定位 provider;未知模型回退到请求指定的 provider 或默认值。"""
|
||||||
|
if model and model in MODEL_PROVIDERS:
|
||||||
|
return MODEL_PROVIDERS[model]
|
||||||
|
return requested or default
|
||||||
|
|
||||||
|
|
||||||
# ── 采集上传 ──
|
# ── 采集上传 ──
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ def _image_size(provider: str, ratio: str, is_wan: bool = True) -> str:
|
|||||||
"""平台比例 → provider 尺寸参数。3:4 竖版(Ozon/WB),1:1 方图(国内)。"""
|
"""平台比例 → provider 尺寸参数。3:4 竖版(Ozon/WB),1:1 方图(国内)。"""
|
||||||
if provider == "doubao":
|
if provider == "doubao":
|
||||||
return "1536x2048" if ratio == "3:4" else "2048x2048"
|
return "1536x2048" if ratio == "3:4" else "2048x2048"
|
||||||
|
if provider == "rightapi":
|
||||||
|
# gpt-image 自定义尺寸约束:16 的倍数、长短边比 ≤ 3:1(1536x2048 合法)
|
||||||
|
return "1536x2048" if ratio == "3:4" else "2048x2048"
|
||||||
# tongyi:万象与千问的 size 语法相同(* 分隔),档位不同
|
# tongyi:万象与千问的 size 语法相同(* 分隔),档位不同
|
||||||
if ratio == "3:4":
|
if ratio == "3:4":
|
||||||
return "1536*2048" if is_wan else "768*1024"
|
return "1536*2048" if is_wan else "768*1024"
|
||||||
@@ -70,27 +73,34 @@ def _bytes_to_data_uri(data: bytes, mime: str) -> str:
|
|||||||
return f"data:{mime};base64,{base64.b64encode(data).decode()}"
|
return f"data:{mime};base64,{base64.b64encode(data).decode()}"
|
||||||
|
|
||||||
|
|
||||||
async def _resolve_ref(url: str) -> str:
|
async def _resolve_ref_bytes(url: str) -> tuple[bytes, str]:
|
||||||
"""参考图 URL → data URI。本地 media 文件直读磁盘;远程 URL 带 Referer 下载。
|
"""参考图 URL → (bytes, mime)。本地 media 文件直读磁盘;远程 URL 带 Referer 下载。
|
||||||
|
|
||||||
生图 API 的服务器无法访问 127.0.0.1,代理 URL 也不能直接透传,
|
生图 API 的服务器无法访问 127.0.0.1,代理 URL 也不能直接透传,
|
||||||
所以统一在本地解析成 base64 data URI 再进请求体。
|
所以统一在本地解析成原始字节再进请求体(data URI 或 multipart)。
|
||||||
"""
|
"""
|
||||||
if url.startswith("data:"):
|
if url.startswith("data:"):
|
||||||
return url
|
head, _, b64 = url.partition(",")
|
||||||
|
mime = head[5:].split(";", 1)[0] or "image/jpeg"
|
||||||
|
return base64.b64decode(b64), mime
|
||||||
path = storage.local_path(url)
|
path = storage.local_path(url)
|
||||||
if path is not None:
|
if path is not None:
|
||||||
mime = mimetypes.guess_type(path.name)[0] or "image/jpeg"
|
mime = mimetypes.guess_type(path.name)[0] or "image/jpeg"
|
||||||
return _bytes_to_data_uri(path.read_bytes(), mime)
|
return path.read_bytes(), mime
|
||||||
if url.startswith(("http://", "https://")):
|
if url.startswith(("http://", "https://")):
|
||||||
from api.proxy import guess_referer
|
from api.proxy import guess_referer
|
||||||
data, ctype = await storage.download_bytes(url, referer=guess_referer(url))
|
data, ctype = await storage.download_bytes(url, referer=guess_referer(url))
|
||||||
if not ctype.startswith("image/"):
|
if not ctype.startswith("image/"):
|
||||||
ctype = "image/jpeg"
|
ctype = "image/jpeg"
|
||||||
return _bytes_to_data_uri(data, ctype)
|
return data, ctype
|
||||||
raise FileNotFoundError(f"无法解析参考图: {url}")
|
raise FileNotFoundError(f"无法解析参考图: {url}")
|
||||||
|
|
||||||
|
|
||||||
|
async def _resolve_ref(url: str) -> str:
|
||||||
|
data, mime = await _resolve_ref_bytes(url)
|
||||||
|
return _bytes_to_data_uri(data, mime)
|
||||||
|
|
||||||
|
|
||||||
# ── Provider:豆包 Seedream(火山方舟)────────────────────────────────────
|
# ── Provider:豆包 Seedream(火山方舟)────────────────────────────────────
|
||||||
|
|
||||||
async def generate_doubao(prompt: str, ref_images: list[str], size: str = "2048x2048", model: str | None = None) -> bytes:
|
async def generate_doubao(prompt: str, ref_images: list[str], size: str = "2048x2048", model: str | None = None) -> bytes:
|
||||||
@@ -198,7 +208,56 @@ async def generate_tongyi(prompt: str, ref_images: list[str], size: str = "2048*
|
|||||||
return dl.content
|
return dl.content
|
||||||
|
|
||||||
|
|
||||||
GENERATORS = {"doubao": generate_doubao, "tongyi": generate_tongyi}
|
# ── Provider:RightAPI(gpt-image,OpenAI 兼容中转)──────────────────────
|
||||||
|
|
||||||
|
async def generate_rightapi(prompt: str, ref_images: list[str], size: str = "2048x2048", model: str | None = None) -> bytes:
|
||||||
|
"""gpt-image 系列:有参考图走 /v1/images/edits(multipart),无参考图走 /v1/images/generations。
|
||||||
|
|
||||||
|
OpenAI 兼容协议:响应固定 b64_json(不支持 response_format 参数,传了报 400);
|
||||||
|
同步调用无任务轮询,高质量档单张 1-5 分钟,超时按文档建议兜底 600s。
|
||||||
|
"""
|
||||||
|
s = get_settings()
|
||||||
|
if not s.rightapi_api_key:
|
||||||
|
raise RuntimeError("未配置 RIGHTAPI_API_KEY(.env)")
|
||||||
|
model = model or s.rightapi_image_model
|
||||||
|
base = s.rightapi_base_url.rstrip("/")
|
||||||
|
headers = {"Authorization": f"Bearer {s.rightapi_api_key}"}
|
||||||
|
|
||||||
|
async with httpx.AsyncClient(timeout=max(s.request_timeout, 600), verify=False) as client:
|
||||||
|
common = {
|
||||||
|
"model": model,
|
||||||
|
"prompt": prompt,
|
||||||
|
"size": size,
|
||||||
|
"quality": s.rightapi_image_quality,
|
||||||
|
"output_format": "jpeg", # 与落盘 .jpg 后缀一致
|
||||||
|
"n": 1,
|
||||||
|
}
|
||||||
|
if ref_images:
|
||||||
|
files = []
|
||||||
|
for i, u in enumerate(ref_images):
|
||||||
|
data, mime = await _resolve_ref_bytes(u)
|
||||||
|
files.append(("image[]", (f"ref-{i + 1}.{mime.split('/')[-1]}", data, mime)))
|
||||||
|
resp = await client.post(f"{base}/v1/images/edits", headers=headers, files=files, data=common)
|
||||||
|
else:
|
||||||
|
resp = await client.post(
|
||||||
|
f"{base}/v1/images/generations",
|
||||||
|
headers={**headers, "Content-Type": "application/json"},
|
||||||
|
json=common,
|
||||||
|
)
|
||||||
|
_raise_api_error(resp, "RightAPI")
|
||||||
|
item = resp.json()["data"][0]
|
||||||
|
b64 = item.get("b64_json") or ""
|
||||||
|
if b64:
|
||||||
|
return base64.b64decode(b64.split(",", 1)[-1] if "," in b64 else b64)
|
||||||
|
img_url = item.get("url") or ""
|
||||||
|
if not img_url:
|
||||||
|
raise RuntimeError(f"RightAPI 响应里没有图片数据: {item}")
|
||||||
|
dl = await client.get(img_url, timeout=s.request_timeout)
|
||||||
|
dl.raise_for_status()
|
||||||
|
return dl.content
|
||||||
|
|
||||||
|
|
||||||
|
GENERATORS = {"doubao": generate_doubao, "tongyi": generate_tongyi, "rightapi": generate_rightapi}
|
||||||
|
|
||||||
|
|
||||||
# ── 任务执行器 ────────────────────────────────────────────────────────────
|
# ── 任务执行器 ────────────────────────────────────────────────────────────
|
||||||
@@ -280,9 +339,10 @@ async def run_suite(suite_id: str) -> None:
|
|||||||
|
|
||||||
raw = suite.context if not product else (product.raw or {})
|
raw = suite.context if not product else (product.raw or {})
|
||||||
ctx = build_context(raw or {}, fallback_name=product.name if product else "")
|
ctx = build_context(raw or {}, fallback_name=product.name if product else "")
|
||||||
model = suite.model or (
|
model = suite.model or {
|
||||||
settings.dashscope_model if provider_name == "tongyi" else settings.ark_image_model
|
"tongyi": settings.dashscope_model,
|
||||||
)
|
"rightapi": settings.rightapi_image_model,
|
||||||
|
}.get(provider_name, settings.ark_image_model)
|
||||||
is_wan = provider_name == "tongyi" and _is_wan_model(model)
|
is_wan = provider_name == "tongyi" and _is_wan_model(model)
|
||||||
size = _image_size(provider_name, suite.ratio, is_wan=is_wan)
|
size = _image_size(provider_name, suite.ratio, is_wan=is_wan)
|
||||||
|
|
||||||
@@ -317,7 +377,9 @@ async def run_suite(suite_id: str) -> None:
|
|||||||
else:
|
else:
|
||||||
refs = _refs_for_job(list(suite.ref_images or []), job)
|
refs = _refs_for_job(list(suite.ref_images or []), job)
|
||||||
data = await generator(prompt, refs, size=size, model=model)
|
data = await generator(prompt, refs, size=size, model=model)
|
||||||
key = storage.write_bytes(data, key_prefix=f"suites/{suite.id}", ext=".jpg")
|
# 部分中转不遵守 output_format(要 jpeg 回 PNG),按魔数定扩展名
|
||||||
|
ext = ".png" if data[:8] == b"\x89PNG\r\n\x1a\n" else ".jpg"
|
||||||
|
key = storage.write_bytes(data, key_prefix=f"suites/{suite.id}", ext=ext)
|
||||||
image_row.stored_url = storage.public_url(key)
|
image_row.stored_url = storage.public_url(key)
|
||||||
image_row.status = STATUS_OK
|
image_row.status = STATUS_OK
|
||||||
ok += 1
|
ok += 1
|
||||||
|
|||||||
Reference in New Issue
Block a user