Files
yu-zhi-ran/scripts/creator.py
T
lt e1ba31afda 版本1.0.4 - 发布前准备
- 修复system.py缩进错误
- 优化前端页面样式(待重构)
- 改进API接口结构
- 完善文档和自动化脚本
- 平台基本功能稳定运行
2026-04-29 09:32:43 +08:00

170 lines
6.5 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
宇之然内容创作流水线(研究 → 大纲 → 撰写 → 合规优化)v2
"""
import json, datetime, logging, sys, subprocess
from pathlib import Path
from typing import Dict
PROJECT_ROOT = Path('/root/openclaw-workspace/projects/yu-zhi-ran')
sys.path.insert(0, str(PROJECT_ROOT))
DATA_DIR = PROJECT_ROOT / "automation" / "data"
TOPICS_FILE = DATA_DIR / "sustainability_topics.json"
LOGS_DIR = PROJECT_ROOT / "automation" / "logs"
TODAY = datetime.datetime.now().strftime("%Y-%m-%d")
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(LOGS_DIR / f"creator_{TODAY}.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
def select_next_topic(topic_id: str = None) -> Dict:
"""选择并锁定要创作的选题"""
def save_topics(topics_list):
with open(TOPICS_FILE, 'w', encoding='utf-8') as f:
json.dump(topics_list, f, ensure_ascii=False, indent=2)
topics = json.loads(TOPICS_FILE.read_text(encoding='utf-8'))
if topic_id:
# 指定ID,尝试直接锁定
topic = next((t for t in topics if t['id'] == topic_id), None)
if not topic:
raise ValueError(f"Topic {topic_id} not found")
# 检查状态:禁止已发布状态重新创作
current_status = topic.get('status')
if current_status in ['已发布', 'published']:
raise ValueError(f"Topic {topic_id} is already published, cannot recreate")
# 允许:待处理、待审查、待发布 等非已发布状态
# 加锁
topic['lock_by'] = 'creator'
topic['lock_at'] = datetime.datetime.now().isoformat()
save_topics(topics)
return topic
# 自动选择:优先选pending且无锁的
def is_available(t):
status = t.get('status')
# 只处理 pending 或 待处理
if status not in ['pending', '待处理']:
return False
# 检查锁
lock_by = t.get('lock_by')
if lock_by:
# 如果有人锁了,检查是否超时(>2小时)
lock_at_str = t.get('lock_at')
if lock_at_str:
try:
lock_at = datetime.datetime.fromisoformat(lock_at_str)
if (datetime.datetime.now() - lock_at).total_seconds() < 7200:
return False
except:
pass # 解析失败,认为是有效锁
else:
return False
return True
available = [t for t in topics if is_available(t)]
if not available:
raise ValueError("No available topics to create (all locked or wrong status)")
available.sort(key=lambda t: t.get('priority_score', 0), reverse=True)
chosen = available[0]
# 锁定
chosen['lock_by'] = 'creator'
chosen['lock_at'] = datetime.datetime.now().isoformat()
save_topics(topics)
return chosen
def run_step(script_name: str, topic_id: str) -> bool:
"""运行一个流水线步骤(research/outline/writer"""
script_path = PROJECT_ROOT / "scripts" / script_name
cmd = ["python3", str(script_path), "--topic-id", topic_id]
logger.info(f"Running: {' '.join(cmd)}")
result = subprocess.run(cmd, cwd=str(PROJECT_ROOT), capture_output=True, text=True, timeout=1800) # 30分钟超时,适应AI撰写
if result.returncode != 0:
logger.error(f"{script_name} 失败: {result.stderr}")
return False
logger.info(f"{script_name} 完成: {result.stdout.strip()}")
return True
def run_optimizer_step(topic_id: str) -> bool:
"""运行合规优化步骤(只针对单个选题)"""
script_path = PROJECT_ROOT / "scripts" / "compliance_optimizer.py"
cmd = ["python3", str(script_path), "--topic-ids", topic_id]
logger.info(f"Running: {' '.join(cmd)}")
result = subprocess.run(cmd, cwd=str(PROJECT_ROOT), capture_output=True, text=True, timeout=600)
if result.returncode != 0:
logger.error(f"compliance_optimizer 失败: {result.stderr}")
return False
logger.info(f"compliance_optimizer 完成: {result.stdout.strip()}")
return True
def run_pipeline(topic_id: str = None) -> Dict:
"""运行完整流水线:研究 → 大纲 → 撰写 → 合规优化"""
tid = None
try:
topic = select_next_topic(topic_id)
tid = topic['id']
logger.info(f"开始创作流水线: topic_id={tid}, title={topic.get('title')}")
# 1. 研究
if not run_step("research.py", tid):
return {"ok": False, "error": "research step failed"}
# 2. 大纲
if not run_step("outline.py", tid):
return {"ok": False, "error": "outline step failed"}
# 3. 撰写
if not run_step("writer.py", tid):
return {"ok": False, "error": "writer step failed"}
# 4. 合规优化(自动审核并标记为「待发布」)
if not run_optimizer_step(tid):
return {"ok": False, "error": "optimizer step failed"}
logger.info(f"创作流水线完成: topic_id={tid}")
return {"ok": True, "topic_id": tid, "stdout": f"SUCCESS: Topic {tid} processed through full pipeline"}
except Exception as e:
logger.exception("流水线执行失败")
return {"ok": False, "error": str(e)}
finally:
# 清理锁(无论成功失败)
if tid:
try:
topics = json.loads(TOPICS_FILE.read_text(encoding='utf-8'))
for t in topics:
if t.get('id') == tid:
# 如果成功或需要人工,保留状态,但清除锁
t['lock_by'] = None
t['lock_at'] = None
break
with open(TOPICS_FILE, 'w', encoding='utf-8') as f:
json.dump(topics, f, ensure_ascii=False, indent=2)
logger.debug(f"已清理选题锁: {tid}")
except Exception as ex:
logger.error(f"清理锁失败: {ex}")
def main():
import argparse
parser = argparse.ArgumentParser(description='内容创作流水线(研究→大纲→撰写→合规优化)')
parser.add_argument('--topic-id', help='指定选题ID,不指定则自动选择待处理选题')
args = parser.parse_args()
result = run_pipeline(args.topic_id)
print(json.dumps(result, ensure_ascii=False))
sys.exit(0 if result['ok'] else 1)
if __name__ == "__main__":
main()