#!/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()