"""校验并生成 feedback-issue 提交前的预览与 payload 文件。""" from __future__ import annotations import argparse from pathlib import Path from typing import Any, Optional from feedback_issue_common import ( ALLOWED_ENVIRONMENTS, ALLOWED_ISSUE_TYPES, MAX_PREVIEW_LOGS_CHARS, MAX_TITLE_CHARS, build_issue_body, check_content_quality, load_diagnostics_logs, read_json_file, result_payload, runtime_file, sanitize_logs, truncate, validate_enum, write_json_file, ) REQUIRED_DRAFT_FIELDS = ( "title", "version", "environment", "issue_type", "description", "original_user_request", "diagnostics_file", ) def normalize_draft(raw: dict[str, Any]) -> tuple[dict[str, Any], list[str]]: """规范化草稿字段并返回缺失字段列表。""" draft = {key: str(raw.get(key) or "").strip() for key in REQUIRED_DRAFT_FIELDS} missing = [key for key, value in draft.items() if not value] draft["title"] = truncate(draft["title"], MAX_TITLE_CHARS, marker="...") return draft, missing def validate_draft(draft: dict[str, Any], logs: str) -> Optional[str]: """校验草稿枚举和内容质量,返回错误信息或 None。""" for value, allowed, field_name in ( (draft["environment"], ALLOWED_ENVIRONMENTS, "environment"), (draft["issue_type"], ALLOWED_ISSUE_TYPES, "issue_type"), ): error = validate_enum(value, allowed, field_name) if error: return error return check_content_quality( title=draft["title"], description=draft["description"], original_user_request=draft["original_user_request"], logs=logs, ) def build_preview_text(draft: dict[str, Any], logs: str, diagnostics: dict[str, Any]) -> str: """构造给用户确认的 Markdown 预览文本。""" preview_logs = sanitize_logs(logs, MAX_PREVIEW_LOGS_CHARS) or "会话中未捕获到相关后端日志。" source_files = diagnostics.get("source_files") or [] sources = "\n".join(f"- {item}" for item in source_files) or "- 未命中具体日志文件" return ( "请确认是否提交以下问题反馈:\n\n" f"标题:{draft['title']}\n" f"版本:{draft['version']}\n" f"环境:{draft['environment']}\n" f"类型:{draft['issue_type']}\n\n" "诊断来源:\n" f"{sources}\n\n" "问题描述:\n" f"{draft['description'].strip()}\n\n" "日志预览(已脱敏):\n" f"```bash\n{preview_logs}\n```\n\n" "如内容无误,请回复「确认」;如需调整,请回复「修改:...」。" ) def prepare_issue(draft_file: str | Path) -> dict[str, Any]: """读取草稿 JSON,校验后写出 payload 与 preview 文件。""" raw = read_json_file(draft_file) draft, missing = normalize_draft(raw) if missing: return { "success": False, "reason": "missing_fields", "message": f"草稿缺少必填字段:{', '.join(missing)}", } try: logs, diagnostics = load_diagnostics_logs(draft["diagnostics_file"]) except Exception as err: return { "success": False, "reason": "diagnostics_missing", "message": f"无法读取诊断日志文件:{err}", } error = validate_draft(draft, logs) if error: return { "success": False, "reason": "invalid_draft", "message": error, } payload = { **draft, "diagnostics_file": str(draft["diagnostics_file"]), } payload_file = runtime_file("payload", ".json") preview_file = runtime_file("preview", ".md") write_json_file(payload_file, payload) preview_text = build_preview_text(draft, logs, diagnostics) preview_file.write_text(preview_text, encoding="utf-8") body_preview = build_issue_body( version=draft["version"], environment=draft["environment"], issue_type=draft["issue_type"], description=draft["description"], logs=logs, ) return { "success": True, "payload_file": str(payload_file), "preview_file": str(preview_file), "body_chars": len(body_preview), "log_bytes": len(logs.encode("utf-8", errors="replace")), "log_lines": len(logs.splitlines()) if logs else 0, "message": ( "已生成 Issue 预览和提交 payload。请把 preview_file 内容完整展示给用户," "等待明确「确认」后再调用 submit_feedback_issue.py。" ), } def parse_args() -> argparse.Namespace: """解析命令行参数。""" parser = argparse.ArgumentParser(description="生成 MoviePilot 反馈 Issue 预览") parser.add_argument("--draft-file", required=True, help="包含 Issue 草稿字段的 JSON 文件") return parser.parse_args() def main() -> int: """脚本入口:校验草稿并输出 JSON 结果。""" args = parse_args() result = prepare_issue(args.draft_file) print(result_payload(**result)) return 0 if result.get("success") else 2 if __name__ == "__main__": raise SystemExit(main())