diff --git a/blueprints/api.py b/blueprints/api.py index 82ff818..4b642ad 100644 --- a/blueprints/api.py +++ b/blueprints/api.py @@ -1,5 +1,5 @@ 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 extensions import db, redis_client, s3_client from models import User, SavedPrompt @@ -72,6 +72,34 @@ def generate(): user = db.session.get(User, user_id) 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) if error: return jsonify({"error": error}), 400 @@ -92,7 +120,6 @@ def generate(): ) return jsonify(result), status_code - image_data = data.get("image_data", []) if model_value == "gpt-image-2": payload = { "prompt": prompt, @@ -101,8 +128,8 @@ def generate(): "size": data.get("size") or "2048x2048", "quality": "high", } - if image_data: - payload["image"] = [img.split(",", 1)[1] if "," in img else img for img in image_data] + if normalized_image_urls: + payload["image"] = normalized_image_urls else: payload = { "prompt": prompt, @@ -110,8 +137,8 @@ def generate(): "response_format": "url", "aspect_ratio": data.get("ratio"), } - if image_data: - payload["image"] = [img.split(",", 1)[1] if "," in img else img for img in image_data] + if normalized_image_urls: + payload["image"] = normalized_image_urls if model_value in ("nano-banana-2", "gemini-3.1-flash-image-preview") and data.get("size"): payload["image_size"] = data.get("size") diff --git a/static/js/main.js b/static/js/main.js index fb6849b..f5bb531 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -713,53 +713,27 @@ document.getElementById('submitBtn').onclick = async () => { currentGeneratedUrls = []; // 重置当前生成列表 try { - let image_data = []; + let image = []; - // 1. 将图片转换为 Base64 (并压缩过大图片) + // 1. 上传参考图,生图接口统一使用 URL if (uploadedFiles.length > 0) { - btnText.innerText = "正在准备图片数据..."; - const processImageFile = (file) => new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = (e) => { - const img = new Image(); - img.onload = () => { - const maxDim = 2048; - let w = img.width; - let h = img.height; + btnText.innerText = "正在上传参考图..."; + const formData = new FormData(); + uploadedFiles.forEach(file => formData.append('images', file)); - if (w <= maxDim && h <= maxDim) { - resolve(e.target.result); - return; - } - - 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); + const uploadResponse = await fetch('/api/upload', { + method: 'POST', + body: formData }); - 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. 并行启动多个生成任务 @@ -785,7 +759,7 @@ document.getElementById('submitBtn').onclick = async () => { model: document.getElementById('modelSelect').value, ratio: document.getElementById('modelSelect').value === GPT_IMAGE_EDIT_MODEL ? undefined : document.getElementById('ratioSelect').value, size: document.getElementById('sizeSelect').value, - image_data // 发送 Base64 数组 + image }) }); const res = await r.json();