mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-06-01 21:50:14 +08:00
160 lines
5.1 KiB
Python
160 lines
5.1 KiB
Python
"""校验并生成 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())
|