129 lines
4.1 KiB
Python
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
"""人审工作台:只读 muse.db,写面仅 reviews / revisions / adopt。"""
from __future__ import annotations
import json
import sys
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import parse_qs, urlparse
PROJECT_ROOT = next(
parent
for parent in (Path(__file__).resolve().parent, *Path(__file__).resolve().parents)
if (parent / "AGENTS.md").is_file() and (parent / ".git").exists()
)
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from muse.store import add_review, connect, default_db_path # noqa: E402
WRITE_ACTIONS = frozenset({"reviews", "revisions", "adopt"})
def _db():
return connect(default_db_path())
def queue_payload() -> list[dict]:
with _db() as conn:
rows = conn.execute(
"SELECT id, kind, created_at, output_text FROM runs ORDER BY created_at DESC LIMIT 50"
).fetchall()
return [dict(row) for row in rows]
def adopt(run_id: str, reviewer: str, reason: str) -> int:
"""采纳走 flow 合同:只记 reviews.action=adopt,不直接写正文文件。"""
return add_review(
run_id=run_id,
target="candidate",
action="adopt",
reviewer=reviewer,
reason=reason,
)
def revise(run_id: str, reviewer: str, reason: str, before_text: str, after_text: str) -> int:
return add_review(
run_id=run_id,
target="candidate",
action="revise",
reviewer=reviewer,
reason=reason,
before_text=before_text,
after_text=after_text,
)
class Handler(BaseHTTPRequestHandler):
def _send(self, code: int, body: str, content_type: str = "text/html; charset=utf-8") -> None:
data = body.encode("utf-8")
self.send_response(code)
self.send_header("Content-Type", content_type)
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
def do_GET(self) -> None: # noqa: N802
parsed = urlparse(self.path)
if parsed.path in {"/", "/queue"}:
items = queue_payload()
rows = "".join(
f"<li><a href='/review/{item['id']}'>{item['id']}</a> {item['kind']}</li>"
for item in items
)
self._send(200, f"<main><h1>待审队列</h1><ul>{rows}</ul></main>")
return
if parsed.path.startswith("/review/"):
run_id = parsed.path.rsplit("/", 1)[-1]
self._send(200, f"<main><h1>审查 {run_id}</h1><pre tabindex='0'></pre></main>")
return
self._send(404, "not found")
def do_POST(self) -> None: # noqa: N802
parsed = urlparse(self.path)
length = int(self.headers.get("Content-Length") or 0)
raw = self.rfile.read(length).decode("utf-8") if length else ""
form = {k: v[0] for k, v in parse_qs(raw).items()}
action = parsed.path.strip("/")
if action not in WRITE_ACTIONS:
self._send(404, "unknown write")
return
reviewer = form.get("reviewer") or ""
run_id = form.get("run_id") or ""
reason = form.get("reason") or ""
if action == "adopt":
review_id = adopt(run_id, reviewer, reason)
elif action == "revisions" or (action == "reviews" and form.get("action") == "revise"):
review_id = revise(
run_id,
reviewer,
reason,
form.get("before_text") or "",
form.get("after_text") or "",
)
else:
review_id = add_review(
run_id=run_id,
target=form.get("target") or "candidate",
action=form.get("action") or "reject",
reviewer=reviewer,
reason=reason,
)
self._send(200, json.dumps({"reviewId": review_id}), "application/json")
def log_message(self, fmt: str, *args) -> None:
return
def serve(host: str = "127.0.0.1", port: int = 8770) -> None:
ThreadingHTTPServer((host, port), Handler).serve_forever()
if __name__ == "__main__":
serve()