baseline: Clover 独立仓库首次基线提交
将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。 当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包), M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
103
backend/scripts/real_image_check.py
Normal file
103
backend/scripts/real_image_check.py
Normal file
@@ -0,0 +1,103 @@
|
||||
"""真实出图验证(正式留存版,不带_tmp,勿当临时文件清理)
|
||||
用法: cd Clover/backend && python3 scripts/real_image_check.py
|
||||
key 只从 ../.env 读,不硬编码不打印明文。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import asyncio, base64, io, logging, os, sys, time
|
||||
from pathlib import Path
|
||||
import httpx
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
_env = ROOT / ".env"
|
||||
if _env.exists():
|
||||
for _l in _env.read_text().splitlines():
|
||||
_l = _l.strip()
|
||||
if _l and not _l.startswith("#") and "=" in _l:
|
||||
k, _, v = _l.partition("=")
|
||||
os.environ.setdefault(k.strip(), v.strip())
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
|
||||
logger = logging.getLogger("img")
|
||||
|
||||
TEST_PROMPT = "\n".join([
|
||||
"生成一张小红书可上传的独立竖版3:4图文海报,目标1024x1536,不是App截图。",
|
||||
"产品:倍分子素颜霜(美妆护肤·素颜霜)。",
|
||||
"画面角色:封面-钩子。画面主体:自然光生活场景,手持素颜霜或产品在桌面前景,像iPhone实拍封面。",
|
||||
"图上文字:主标题「黄黑皮逆袭|伪素颜天花板」,点位「黄黑皮也能拥有的妈生好皮」。",
|
||||
"核心卖点:黄黑皮提亮、伪素颜自然、不假白不卡纹、洗面奶就能卸、懒人必备。",
|
||||
"成分:烟酰胺(改善暗沉)、水解珍珠(锁水保湿)、角鲨烷(不卡纹)。",
|
||||
"视觉风格:小红书种草风,明亮干净,暖色调,模拟iPhone主摄浅景深。",
|
||||
"重要限制:禁止生成App截图/界面元素;禁止肤色变白/功效前后对比;"
|
||||
"避免美白/祛斑/速效/医用等违规词;必须保持产品包装与参考图一致。",
|
||||
])
|
||||
|
||||
_REF = ROOT / "test/output/01_连通测试.png"
|
||||
|
||||
|
||||
async def _apiports(key, url, ref, model, size):
|
||||
payload = {"model": model, "prompt": TEST_PROMPT, "n": 1, "size": size,
|
||||
"image": base64.b64encode(ref).decode()}
|
||||
async with httpx.AsyncClient(timeout=300.0) as c:
|
||||
r = await c.post(url, json=payload, headers={"Authorization": f"Bearer {key}"})
|
||||
if r.status_code != 200:
|
||||
raise RuntimeError(f"HTTP {r.status_code}: {r.text[:200]}")
|
||||
return _parse(r.json())
|
||||
|
||||
|
||||
async def _codeproxy(key, base, ref, model, size):
|
||||
files = [("model", (None, model)), ("prompt", (None, TEST_PROMPT)),
|
||||
("size", (None, size)), ("n", (None, "1")),
|
||||
("image[]", ("ref.png", io.BytesIO(ref), "image/png"))]
|
||||
async with httpx.AsyncClient(timeout=300.0) as c:
|
||||
r = await c.post(f"{base}/images/edits", files=files,
|
||||
headers={"Authorization": f"Bearer {key}"})
|
||||
if r.status_code != 200:
|
||||
raise RuntimeError(f"HTTP {r.status_code}: {r.text[:200]}")
|
||||
return _parse(r.json())
|
||||
|
||||
|
||||
def _parse(j):
|
||||
item = (j.get("data") or [{}])[0]
|
||||
if "b64_json" in item:
|
||||
return base64.b64decode(item["b64_json"])
|
||||
if "url" in item:
|
||||
rr = httpx.get(item["url"], timeout=30.0); rr.raise_for_status()
|
||||
return rr.content
|
||||
raise ValueError(f"无法解析: {list(item.keys())}")
|
||||
|
||||
|
||||
async def run():
|
||||
model = os.environ.get("MODEL_IMAGE", "gpt-image-2")
|
||||
size = os.environ.get("IMAGE_SIZE", "1024x1536")
|
||||
primary = os.environ.get("IMAGE_PROVIDER_PRIMARY", "apiports").lower()
|
||||
fallback = os.environ.get("IMAGE_PROVIDER_FALLBACK", "codeproxy").lower()
|
||||
out_dir = Path(__file__).parent.parent / "verify_output"
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
ref = _REF.read_bytes()
|
||||
t0 = time.time(); img = None; used = None
|
||||
for prov in [primary, fallback]:
|
||||
key = os.environ.get(f"{prov.upper()}_KEY", "")
|
||||
url = os.environ.get(f"{prov.upper()}_BASE_URL", "")
|
||||
if not key or not url:
|
||||
continue
|
||||
logger.info("[%s] 出图中 model=%s size=%s", prov, model, size)
|
||||
try:
|
||||
img = await (_apiports(key, url, ref, model, size) if prov == "apiports"
|
||||
else _codeproxy(key, url, ref, model, size))
|
||||
used = prov; break
|
||||
except Exception as e:
|
||||
logger.error("[%s] 失败: %s", prov, e)
|
||||
dt = time.time() - t0
|
||||
print("\n===== 出图结果 =====")
|
||||
if img:
|
||||
out = out_dir / "素颜霜_封面hook.png"
|
||||
out.write_bytes(img)
|
||||
print(f" provider: {used}\n 耗时: {dt:.1f}s\n 大小: {len(img)//1024}KB\n 路径: {out}")
|
||||
else:
|
||||
print(f" 耗时: {dt:.1f}s\n 状态: 全失败")
|
||||
print("====================\n")
|
||||
return img is not None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(0 if asyncio.run(run()) else 1)
|
||||
Reference in New Issue
Block a user