chore: initial commit of ai site platform

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
whm
2026-07-31 10:31:17 +08:00
commit 4ca82fb58a
203 changed files with 45745 additions and 0 deletions

125
ai-service/shot_worker.py Normal file
View File

@@ -0,0 +1,125 @@
"""Standalone Playwright screenshot worker (fresh process, Windows-safe).
Invoked by fidelity_loop.capture_real_app_screenshot — never import uvicorn here.
"""
from __future__ import annotations
import asyncio
import base64
import json
import os
import sys
from pathlib import Path
def _force_proactor() -> None:
if sys.platform == "win32":
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
async def _shot_async(payload: dict) -> tuple[bytes | None, str, str | None]:
from playwright.async_api import async_playwright
draft = payload.get("draft") or {}
rows = payload.get("rows") or []
web = (os.getenv("WEB_BASE") or "http://127.0.0.1:5173").rstrip("/")
goto_timeout = int(os.getenv("FIDELITY_GOTO_TIMEOUT_MS") or "45000")
slug = str(((draft.get("meta") or {}).get("slug")) or "preview")
preview_id = str(payload.get("preview_id") or "preview")
inject_payload = {
"ok": True,
"id": preview_id,
"blueprint": draft,
"rows": rows,
"resource": payload.get("resource") or "records",
}
inject_js = (
"try{localStorage.removeItem('ajz_session');}catch(e){}"
"window.__AJZ_PREVIEW__ = "
+ json.dumps(inject_payload, ensure_ascii=False)
+ ";"
)
target = f"{web}/#/preview/{preview_id}"
def fmt(err: BaseException) -> str:
msg = str(err).strip() or repr(err)
return msg.replace("\n", " ")[:500]
try:
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
try:
page = await browser.new_page(viewport={"width": 1600, "height": 1100})
await page.add_init_script(inject_js)
async def fulfill(route):
await route.fulfill(
status=200,
content_type="application/json; charset=utf-8",
body=json.dumps(inject_payload, ensure_ascii=False).encode("utf-8"),
)
await page.route("**/api/v1/preview/**", fulfill)
await page.route("**/ai/api/v1/preview/**", fulfill)
await page.goto(target, wait_until="domcontentloaded", timeout=goto_timeout)
await page.evaluate(inject_js)
try:
await page.wait_for_selector(
".sf-shell-bar, .sf-root, .gen-app-ops, .gen-app, .sf-line-wrap",
timeout=45000,
)
except Exception:
try:
snip = (await page.locator("body").inner_text(timeout=2000))[:240]
except Exception:
snip = page.url
raise RuntimeError(
f"preview shell missing. page={page.url} text={snip!r}"
) from None
login_btns = page.locator("button").filter(has_text="登录")
shell = page.locator(".sf-shell-bar, .sf-root, .gen-app")
if await login_btns.count() and not await shell.count():
raise RuntimeError("preview fell back to login")
await page.wait_for_timeout(2500)
if await page.locator(".gen-app").count():
png = await page.locator(".gen-app").first.screenshot(type="png")
else:
png = await page.screenshot(type="png", full_page=True)
finally:
await browser.close()
return (
png,
f"已截取草稿预览(/#/preview/{preview_id},注入数据无需再拉 AI参与对比",
slug,
)
except Exception as e: # noqa: BLE001
return None, f"真页面截图失败: preview={fmt(e)}(目标 {target}", slug
def main() -> int:
_force_proactor()
inp = Path(os.environ["FIDELITY_SHOT_INPUT"])
outp = Path(os.environ["FIDELITY_SHOT_OUTPUT"])
payload = json.loads(inp.read_text(encoding="utf-8"))
png, note, slug = asyncio.run(_shot_async(payload))
out_dir = os.getenv("FIDELITY_SHOT_DIR") or ""
if png and out_dir:
d = Path(out_dir)
d.mkdir(parents=True, exist_ok=True)
(d / f"preview_{payload.get('preview_id') or 'x'}.png").write_bytes(png)
outp.write_text(
json.dumps(
{
"png_b64": base64.b64encode(png).decode("ascii") if png else None,
"note": note,
"slug": slug,
},
ensure_ascii=False,
),
encoding="utf-8",
)
return 0 if png else 2
if __name__ == "__main__":
raise SystemExit(main())