feat: 创作工作台统一 + AI味检测/白标 + 移动端补全 + 合规发布闭环

- 新增创作工作台 studio.html:合并选题/内容工厂/文章管理为单一 tab 入口(iframe embed 模式)
- 新增 AI味检测模块(ai_slop API + 页面,合规软硬问题分级)
- 新增白标品牌配置(branding API + 页面 + deploy 私有化交付包)
- 发布闭环:publishing 放宽至 editor + records/mark-published 接口
- 移动端响应式补全(admin/calendar/ai-slop 表格卡片兜底)
- 修复菜单幂等播种缺陷(按 path 对齐,避免功能页孤立)
- 新增短视频脚本 shortvideo.py 与 2026 市场调研简报
This commit is contained in:
yuzhiran
2026-07-11 12:36:01 +08:00
parent 3d596275d5
commit 7e953afbd2
39 changed files with 2418 additions and 311 deletions
+56 -1
View File
@@ -1,4 +1,10 @@
import os
import sys
import subprocess
from pathlib import Path
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
from sqlalchemy.orm import Session
from sqlalchemy import desc
from typing import Optional
@@ -6,10 +12,18 @@ from datetime import datetime, timezone
from ..database import get_db
from ..models import ExternalProduct, PromotionCampaign, CampaignKeyword, SEOAudit, KeywordRanking, OptimizationTask, User
from .auth import get_current_user, org_filter
from .auth import get_current_user, get_current_admin, org_filter
router = APIRouter(prefix="/api/external", tags=["external_promotion"])
PROJECT_ROOT = Path(__file__).parent.parent.parent.parent.parent
SEO_AUDITOR_PATH = PROJECT_ROOT / "scripts" / "seo_auditor.py"
class AuditRunRequest(BaseModel):
product_id: Optional[str] = None
url: Optional[str] = None
def _org_filtered_query(db, model, current_user):
query = db.query(model)
@@ -467,3 +481,44 @@ def external_overview(
"type_breakdown": type_breakdown,
}
}
# ===== Run SEO Audit (delegate to scripts/seo_auditor.py) =====
@router.post("/audit/run")
def run_audit(
body: AuditRunRequest,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_admin),
):
cmd = [sys.executable, str(SEO_AUDITOR_PATH)]
if body.product_id:
cmd += ["--product-id", str(body.product_id)]
elif body.url:
cmd += ["--url", body.url]
else:
raise HTTPException(status_code=400, detail="product_id 或 url 至少提供一个")
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
except subprocess.TimeoutExpired:
raise HTTPException(status_code=408, detail="SEO 审计超时")
except Exception as e:
raise HTTPException(status_code=500, detail=f"审计执行失败: {str(e)}")
if result.returncode != 0:
raise HTTPException(
status_code=502,
detail={
"message": "SEO 审计脚本返回错误",
"stderr": result.stderr,
"stdout": result.stdout,
},
)
return {
"ok": True,
"stdout": result.stdout,
"stderr": result.stderr,
"returncode": result.returncode,
}