baseline: Clover 独立仓库首次基线提交

将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。
当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包),
M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
yangqianqian
2026-06-16 11:30:22 +08:00
commit 6a2632da70
253 changed files with 27467 additions and 0 deletions

View 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)