更新生成接口,支持上传参考图并使用 URL 代替 Base64 格式

This commit is contained in:
公司git 2026-07-16 11:38:43 +08:00
parent efaa26314c
commit bc05afbcb0
2 changed files with 51 additions and 50 deletions

View File

@ -1,5 +1,5 @@
from flask import Blueprint, request, jsonify, session, current_app from flask import Blueprint, request, jsonify, session, current_app
from urllib.parse import unquote, urlparse from urllib.parse import unquote, urljoin, urlparse
from config import Config from config import Config
from extensions import db, redis_client, s3_client from extensions import db, redis_client, s3_client
from models import User, SavedPrompt from models import User, SavedPrompt
@ -72,6 +72,34 @@ def generate():
user = db.session.get(User, user_id) user = db.session.get(User, user_id)
data = request.json if request.is_json else request.form data = request.json if request.is_json else request.form
image_urls = data.get(
"image",
data.get("image_urls", data.get("image_data", [])),
) or []
if isinstance(image_urls, str):
image_urls = [image_urls]
elif not isinstance(image_urls, (list, tuple)):
return jsonify({"error": "参考图地址格式无效"}), 400
normalized_image_urls = []
for image_url in image_urls:
if not isinstance(image_url, str) or not image_url.strip():
return jsonify({"error": "参考图地址无效"}), 400
image_url = image_url.strip()
parsed_input = urlparse(image_url)
if image_url.startswith("/"):
absolute_url = urljoin(request.url_root, image_url)
elif parsed_input.scheme in ("http", "https") and parsed_input.netloc:
absolute_url = image_url
else:
return jsonify({"error": "参考图必须使用 HTTP(S) URL不能使用 Base64"}), 400
parsed_url = urlparse(absolute_url)
if parsed_url.scheme not in ("http", "https") or not parsed_url.netloc:
return jsonify({"error": "参考图必须使用 HTTP(S) URL不能使用 Base64"}), 400
normalized_image_urls.append(absolute_url)
api_key, target_api, cost, use_trial, error = validate_generation_request(user, data) api_key, target_api, cost, use_trial, error = validate_generation_request(user, data)
if error: if error:
return jsonify({"error": error}), 400 return jsonify({"error": error}), 400
@ -92,7 +120,6 @@ def generate():
) )
return jsonify(result), status_code return jsonify(result), status_code
image_data = data.get("image_data", [])
if model_value == "gpt-image-2": if model_value == "gpt-image-2":
payload = { payload = {
"prompt": prompt, "prompt": prompt,
@ -101,8 +128,8 @@ def generate():
"size": data.get("size") or "2048x2048", "size": data.get("size") or "2048x2048",
"quality": "high", "quality": "high",
} }
if image_data: if normalized_image_urls:
payload["image"] = [img.split(",", 1)[1] if "," in img else img for img in image_data] payload["image"] = normalized_image_urls
else: else:
payload = { payload = {
"prompt": prompt, "prompt": prompt,
@ -110,8 +137,8 @@ def generate():
"response_format": "url", "response_format": "url",
"aspect_ratio": data.get("ratio"), "aspect_ratio": data.get("ratio"),
} }
if image_data: if normalized_image_urls:
payload["image"] = [img.split(",", 1)[1] if "," in img else img for img in image_data] payload["image"] = normalized_image_urls
if model_value in ("nano-banana-2", "gemini-3.1-flash-image-preview") and data.get("size"): if model_value in ("nano-banana-2", "gemini-3.1-flash-image-preview") and data.get("size"):
payload["image_size"] = data.get("size") payload["image_size"] = data.get("size")

View File

@ -713,53 +713,27 @@ document.getElementById('submitBtn').onclick = async () => {
currentGeneratedUrls = []; // 重置当前生成列表 currentGeneratedUrls = []; // 重置当前生成列表
try { try {
let image_data = []; let image = [];
// 1. 将图片转换为 Base64 (并压缩过大图片) // 1. 上传参考图,生图接口统一使用 URL
if (uploadedFiles.length > 0) { if (uploadedFiles.length > 0) {
btnText.innerText = "正在准备图片数据..."; btnText.innerText = "正在上传参考图...";
const processImageFile = (file) => new Promise((resolve, reject) => { const formData = new FormData();
const reader = new FileReader(); uploadedFiles.forEach(file => formData.append('images', file));
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
const maxDim = 2048;
let w = img.width;
let h = img.height;
if (w <= maxDim && h <= maxDim) { const uploadResponse = await fetch('/api/upload', {
resolve(e.target.result); method: 'POST',
return; body: formData
}
if (w > h) {
if (w > maxDim) {
h = Math.round(h * (maxDim / w));
w = maxDim;
}
} else {
if (h > maxDim) {
w = Math.round(w * (maxDim / h));
h = maxDim;
}
}
const canvas = document.createElement('canvas');
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, w, h);
// 保持原格式,如果不是 png 则默认 jpeg (0.9 质量)
const outType = file.type === 'image/png' ? 'image/png' : 'image/jpeg';
resolve(canvas.toDataURL(outType, 0.9));
};
img.onerror = reject;
img.src = e.target.result;
};
reader.onerror = reject;
reader.readAsDataURL(file);
}); });
image_data = await Promise.all(uploadedFiles.map(f => processImageFile(f))); const uploadResult = await uploadResponse.json();
if (!uploadResponse.ok || uploadResult.error) {
throw new Error(uploadResult.error || '参考图上传失败');
}
image = (uploadResult.urls || []).map(url => new URL(url, window.location.origin).href);
if (image.length !== uploadedFiles.length) {
throw new Error('部分参考图上传失败');
}
} }
// 2. 并行启动多个生成任务 // 2. 并行启动多个生成任务
@ -785,7 +759,7 @@ document.getElementById('submitBtn').onclick = async () => {
model: document.getElementById('modelSelect').value, model: document.getElementById('modelSelect').value,
ratio: document.getElementById('modelSelect').value === GPT_IMAGE_EDIT_MODEL ? undefined : document.getElementById('ratioSelect').value, ratio: document.getElementById('modelSelect').value === GPT_IMAGE_EDIT_MODEL ? undefined : document.getElementById('ratioSelect').value,
size: document.getElementById('sizeSelect').value, size: document.getElementById('sizeSelect').value,
image_data // 发送 Base64 数组 image
}) })
}); });
const res = await r.json(); const res = await r.json();