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

229
scripts/e2e_flow.py Normal file
View File

@@ -0,0 +1,229 @@
#!/usr/bin/env python3
"""端到端AI 生成 → publish → capsule → agent 解密列表 → import + 新能力冒烟"""
from __future__ import annotations
import sys
import time
from pathlib import Path
import httpx
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "agent_sdk"))
from client import AgentClient, decrypt_capsule # noqa: E402
PLATFORM = "http://127.0.0.1:8180"
AI = "http://127.0.0.1:8180/ai"
def main() -> None:
excel = ROOT / "ai-service" / "sample_inventory.xlsx"
if not excel.exists():
import subprocess
subprocess.check_call([sys.executable, str(ROOT / "ai-service" / "make_sample_excel.py")])
with httpx.Client(timeout=60.0) as http:
# 网关无 token 应 401
denied = http.get(f"{PLATFORM}/api/v1/audit/logs")
assert denied.status_code == 401, denied.text
print("gateway jwt deny ok")
tok = http.post(
f"{PLATFORM}/api/v1/auth/login",
json={"username": "demo", "password": "demo123"},
)
tok.raise_for_status()
auth = tok.json()
token, agent_key = auth["access_token"], auth["agent_key"]
assert auth.get("role") in ("owner", "editor", "viewer"), auth
print("login ok", auth.get("username"), "role", auth.get("role"), "tenant", auth.get("tenant_id"))
headers = {"Authorization": f"Bearer {token}"}
# 对象存储
up = http.post(
f"{PLATFORM}/api/v1/storage",
headers=headers,
files={"file": ("note.txt", b"hello-storage", "text/plain")},
)
up.raise_for_status()
upj = up.json()
assert upj.get("key") and upj.get("url"), upj
print("storage upload", upj["key"])
# 多模态(无 Key 时启发式)
png = (
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01"
b"\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDATx\x9cc\xf8\x0f\x00"
b"\x00\x01\x01\x00\x05\x18\xd8N\x00\x00\x00\x00IEND\xaeB`\x82"
)
files = {
"excel": (
"sample_inventory.xlsx",
excel.read_bytes(),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
),
"images": ("ui_list.png", png, "image/png"),
}
data = {"prompt": "根据库存表和截图生成可筛选库存列表", "storage_mode": "schema_per_app"}
gen = http.post(f"{AI}/api/v1/apps/generate", data=data, files=files)
gen.raise_for_status()
gj = gen.json()
draft = gj["draft"]
warns = " ".join(gj.get("warnings") or [])
vision_ok = (
"看图" in warns
or "截图" in warns
or bool(draft.get("meta", {}).get("source", {}).get("vision_summary"))
)
assert vision_ok, warns
slug = draft["meta"]["slug"]
resource = draft["apis"]["resources"][0]["path"].lstrip("/")
print("generate", slug, resource, "confidence", gj["confidence"])
# 唯一 slug避免重复发布
slug = f"{slug}_{int(time.time())}"
draft["meta"]["slug"] = slug
draft["apis"]["base_path"] = f"/api/v1/apps/{slug}"
print("slug", slug)
pub = http.post(
f"{PLATFORM}/api/v1/apps/{slug}/publish",
headers=headers,
json={"blueprint": draft},
)
if pub.status_code >= 400:
print(pub.text)
pub.raise_for_status()
print("published", pub.json()["schema_name"], "memory_mode", pub.json()["memory_mode"])
# 审计
audit = http.get(f"{PLATFORM}/api/v1/audit/logs", headers=headers, params={"page": 1})
audit.raise_for_status()
actions = [x.get("action") for x in audit.json().get("items") or []]
assert any(a.startswith("publish.") for a in actions) or any(
a == "storage.upload" for a in actions
), actions
print("audit actions sample", actions[:5])
cap = http.get(f"{PLATFORM}/api/v1/apps/{slug}/agent-capsule", headers=headers)
cap.raise_for_status()
capsule = cap.json()["capsule"]
assert capsule.startswith("AJZ1."), capsule
desc = decrypt_capsule(agent_key, capsule)
assert "resources" in desc and desc["app_slug"] == slug
print("capsule decrypted resources:", [r["name"] for r in desc["resources"]])
assert "/api/v1/apps/" not in capsule
client = AgentClient(token, agent_key, capsule)
created = client.create(
resource,
{
"sku": "E2E-1",
"product_name": "测试商品",
"warehouse": "华东仓",
"qty": 3,
"unit_price": 9.9,
"status": "在售",
},
)
print("agent create id", created.get("id"))
listed = client.list(resource, warehouse="华东仓")
print("agent list total", listed.get("total"))
csv_body = "sku,product_name,warehouse,qty,unit_price,status\nE2E-2,导入商品,华南仓,5,19.9,在售\n"
try:
from openpyxl import Workbook
import io
wb = Workbook()
ws = wb.active
ws.append(["sku", "product_name", "warehouse", "qty", "unit_price", "status"])
ws.append(["E2E-2", "导入商品", "华南仓", 5, 19.9, "在售"])
buf = io.BytesIO()
wb.save(buf)
files = {
"file": (
"imp.xlsx",
buf.getvalue(),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
}
except Exception:
files = {"file": ("imp.csv", csv_body.encode("utf-8"), "text/csv")}
imp = http.post(
f"{PLATFORM}/api/v1/apps/{slug}/{resource}/import",
headers=headers,
files=files,
)
imp.raise_for_status()
print("import", imp.json())
ag = http.get(
f"{PLATFORM}/api/v1/apps/{slug}/{resource}/aggregate",
headers=headers,
params={"group_by": "warehouse", "sum": "qty"},
)
ag.raise_for_status()
print("aggregate", ag.json())
# database_per_app 冒烟
data2 = {"prompt": "独立库库存台账", "storage_mode": "database_per_app"}
gen2 = http.post(
f"{AI}/api/v1/apps/generate",
data=data2,
files={
"excel": (
"sample_inventory.xlsx",
excel.read_bytes(),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
)
gen2.raise_for_status()
draft2 = gen2.json()["draft"]
assert draft2["storage"]["mode"] == "database_per_app"
slug2 = f"dbapp_{int(time.time())}"
draft2["meta"]["slug"] = slug2
draft2["apis"]["base_path"] = f"/api/v1/apps/{slug2}"
pub2 = http.post(
f"{PLATFORM}/api/v1/apps/{slug2}/publish",
headers=headers,
json={"blueprint": draft2},
)
if pub2.status_code >= 400:
print("database_per_app publish:", pub2.text)
pub2.raise_for_status()
pj2 = pub2.json()
assert pj2.get("database_name"), pj2
print("database_per_app", pj2["database_name"], "schema", pj2["schema_name"])
# viewer token 不应能 publish
issued = http.post(
f"{PLATFORM}/api/v1/auth/token",
json={
"tenant_id": auth["tenant_id"],
"user_id": auth["user_id"],
"role": "viewer",
"secret": "dev-only-change-me",
},
)
if issued.status_code == 200:
vtok = issued.json()["access_token"]
forbid = http.post(
f"{PLATFORM}/api/v1/apps/{slug}/publish",
headers={"Authorization": f"Bearer {vtok}"},
json={"blueprint": draft},
)
assert forbid.status_code == 403, forbid.text
print("rbac viewer deny publish ok")
else:
print("skip rbac token issue:", issued.status_code, issued.text[:120])
print("E2E OK")
if __name__ == "__main__":
main()