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
+72 -3
View File
@@ -69,6 +69,33 @@ def _load_platform_config() -> dict:
PLATFORM_CONFIG = _load_platform_config()
_AI_DISCLOSURE_TEXT = {
"zhihu": "本文由 AI 辅助创作,核心观点与数据经宇之然人工审核校对。",
"wechat": "本文由 AI 辅助创作,核心观点与数据经宇之然人工审核校对。",
"xiaohongshu": "✨ 本文由 AI 辅助生成,内容已人工核对~",
}
def _load_ai_disclosure_enabled() -> bool:
"""根据《人工智能生成合成内容标识办法》(2025-09-01 施行),AI 生成内容须主动声明。
默认开启;可通过 SystemConfig(disclose_ai=false) 或环境变量 YZR_DISCLOSE_AI=0 关闭。"""
try:
if os.environ.get("YZR_DISCLOSE_AI", "").strip() == "0":
return False
from app.database import SessionLocal
from app.models import SystemConfig
db = SessionLocal()
try:
sc = db.query(SystemConfig).filter(SystemConfig.key == "disclose_ai").first()
if sc and str(sc.value).strip().lower() in ("false", "0", "no"):
return False
finally:
db.close()
except Exception:
pass
return True
AI_DISCLOSURE_ENABLED = _load_ai_disclosure_enabled()
PLATFORM_NAMES = {
"zhihu": "知乎专栏",
"wechat": "微信公众号",
@@ -235,10 +262,40 @@ def inject_geo_metadata(html: str, title: str, content: str, platform: str, tags
return html
def inject_ai_disclosure(html: str, platform: str) -> str:
"""按《人工智能生成合成内容标识办法》注入 AI 生成声明。
显式:文首/文末标注「AI 生成」;隐式:head 注入 ai-generated meta 与 AIGC 隐式标识字段。"""
if not AI_DISCLOSURE_ENABLED:
return html
text = _AI_DISCLOSURE_TEXT.get(platform, _AI_DISCLOSURE_TEXT["zhihu"])
disclosure_block = f'\n<blockquote class="ai-disclosure">🤖 {text}</blockquote>\n'
# 文末声明(置于正文末尾,封面/互动钩子之前不影响阅读)
if "<!-- AI_DISCLOSURE -->" in html:
html = html.replace("<!-- AI_DISCLOSURE -->", disclosure_block.strip())
else:
# 在 </body> 前插入文末声明
html = html.replace("</body>", f"{disclosure_block}</body>")
# 隐式标识:head 注入声明 meta + AIGC 元数据字段(符合标识办法附录 E)
head_meta = (
'<meta name="ai-generated" content="true">\n'
'<meta name="ai-disclosure" content="本文含人工智能生成合成内容">\n'
'<script type="application/ld+json">\n'
'{"AIGC":{"Label":"1","ContentProducer":"宇之然","ProduceID":"yzr-' + platform + '",'
'"ContentPropagator":"宇之然","PropagateID":"yzr-' + platform + '"}}\n'
'</script>\n'
)
html = html.replace("</head>", head_meta + "</head>")
return html
class Writer:
def __init__(self, topic_id: str):
self.topic_id = topic_id
self.topic = self._load_topic()
self._degraded = False
outline_file = OUTLINE_DIR / TODAY / f"{topic_id}_outline.md"
if not outline_file.exists():
raise FileNotFoundError(f"Outline not found: {outline_file}")
@@ -334,6 +391,9 @@ class Writer:
except Exception as e:
logger.warning(f"LLM 扩写失败 [{platform}]: {e}")
# LLM 不可用或返回为空 → 降级为大纲要点拼接(非真正成稿)
self._degraded = True
logger.warning(f"⚠️ [{platform}] 章节「{section['title']}」LLM 扩写缺失,降级为大纲要点拼接(内容未达发布质量)")
lines = [l.strip() for l in content.split('\n') if not self._is_outline_noise(l)]
if lines:
sentences = []
@@ -608,8 +668,11 @@ class Writer:
html = html.replace("<!-- TAGS -->", tags_html)
else:
html = html.replace("<!-- TAGS -->", "")
html = inject_geo_metadata(html, title, adapted, platform, tags_html)
# 强制 AI 生成声明(合规要求,可配置关闭)
html = inject_ai_disclosure(html, platform)
return html
def save_html(self, html: str, platform: str, *, title: str = "", content: str = "") -> str:
@@ -634,8 +697,14 @@ class Writer:
html = self.generate_platform_html(markdown, platform)
results[platform] = str(self.save_html(html, platform, title=title, content=markdown))
self.mark_draft()
logger.info(f"撰写完成,状态已更新为待审查")
return {"ok": True, "files": results}
if self._degraded:
logger.error(
"❌ 撰写完成但存在 LLM 降级章节(大纲要点拼接,未达发布质量)。"
"选题保留为待审查,请检查 LLM 提供商连通性后重跑,勿直接发布降级稿。"
)
else:
logger.info(f"撰写完成,状态已更新为待审查")
return {"ok": True, "files": results, "degraded": self._degraded}
def main():
import argparse