更新生成接口,支持上传参考图并使用 URL 代替 Base64 格式
This commit is contained in:
parent
efaa26314c
commit
bc05afbcb0
@ -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")
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user