feat: Phase 5 — 审查备注编辑器与 PDF 导出

This commit is contained in:
2026-06-19 22:10:13 +08:00
parent 1cfab2d2d1
commit b35d6f874b
10 changed files with 914 additions and 15 deletions
+32
View File
@@ -8,6 +8,7 @@
<script src="/static/js/sse.js"></script>
<script src="/static/js/diff-viewer.js"></script>
<script src="/static/js/review-inline.js"></script>
<script src="/static/js/note-editor.js"></script>
</head>
<body class="bg-gray-50 min-h-screen">
{{template "nav" .}}
@@ -58,6 +59,27 @@
<div id="summary-content" class="text-gray-700 prose text-sm"></div>
</div>
<!-- Overall Note Editor -->
<div id="overall-note-section" class="bg-white rounded-lg shadow-md p-6 mb-6">
<h2 class="text-lg font-semibold text-gray-900 mb-3">📝 整体备注</h2>
<div class="note-editor" data-note-key="overall:" data-scope="overall" data-scope-key="">
<div class="flex items-center justify-between mb-1">
<span class="text-xs text-gray-400">💬 备注 (Markdown)</span>
<span class="note-save-indicator text-xs"></span>
</div>
<textarea class="note-textarea" placeholder="添加整体备注..." rows="2"
oninput="NoteEditor.onInput(this, 'overall', '')"></textarea>
</div>
</div>
<!-- Actions Bar -->
<div id="review-actions" class="flex items-center gap-3 mb-6 hidden">
<button onclick="NoteEditor.exportPDF()" id="btn-export-pdf"
class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 text-sm">
📄 导出 PDF 报告
</button>
</div>
<!-- Inline Diff + Suggestions View -->
<div id="diff-review-container" class="mb-6">
<div class="flex items-center justify-between mb-3">
@@ -84,6 +106,7 @@
let diffVisible = true;
let allSuggestions = [];
let fileReviews = {};
let currentAnalysisId = null;
// Load refs on page load
async function loadRefs() {
@@ -148,8 +171,10 @@
// Reset state
allSuggestions = [];
fileReviews = {};
currentAnalysisId = null;
document.getElementById('summary-content').innerHTML = '';
document.getElementById('file-reviews').innerHTML = '';
document.getElementById('review-actions').classList.add('hidden');
// Progress tracking
let totalFiles = 0;
@@ -214,6 +239,7 @@
setTimeout(() => {
progress.classList.add('hidden');
results.classList.remove('hidden');
document.getElementById('review-actions').classList.remove('hidden');
btn.disabled = false;
btn.textContent = '开始审查';
}, 500);
@@ -225,6 +251,12 @@
currentSSE = null;
},
analysis_saved(data) {
currentAnalysisId = data.analysis_id;
// Initialize note editor with the analysis ID
NoteEditor.init(repoId, currentAnalysisId);
NoteEditor.loadNotes();
},
error(data) {
alert('审查失败: ' + (data.message || '未知错误'));
progress.classList.add('hidden');
+14
View File
@@ -0,0 +1,14 @@
{{define "note-editor"}}
<div class="note-editor mt-2" data-note-key="{{.Scope}}:{{.ScopeKey}}" data-scope="{{.Scope}}" data-scope-key="{{.ScopeKey}}">
<div class="flex items-center justify-between mb-1">
<span class="text-xs text-gray-400">💬 备注 (Markdown)</span>
<span class="note-save-indicator text-xs"></span>
</div>
<textarea
class="note-textarea"
placeholder="{{if eq .Scope "overall"}}添加整体备注...{{else if eq .Scope "file"}}添加文件级备注...{{else}}添加备注...{{end}}"
rows="2"
oninput="NoteEditor.onInput(this, '{{.Scope}}', '{{.ScopeKey}}')"
>{{.Content}}</textarea>
</div>
{{end}}
+126
View File
@@ -0,0 +1,126 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>PR-Helper 审查报告</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", sans-serif;
font-size: 13px;
line-height: 1.6;
color: #1f2937;
padding: 40px;
max-width: 900px;
margin: 0 auto;
}
h1 { font-size: 22px; margin-bottom: 8px; color: #111827; }
h2 { font-size: 16px; margin: 24px 0 12px; color: #111827; border-bottom: 2px solid #e5e7eb; padding-bottom: 6px; }
h3 { font-size: 14px; margin: 16px 0 8px; color: #374151; }
p { margin-bottom: 8px; }
.meta { color: #6b7280; font-size: 12px; margin-bottom: 20px; }
.meta span { display: inline-block; margin-right: 16px; }
.score { font-size: 28px; font-weight: 700; color: #2563eb; }
.score-label { font-size: 14px; color: #6b7280; margin-left: 4px; }
.section { margin-bottom: 24px; padding: 16px; border: 1px solid #e5e7eb; border-radius: 8px; }
.severity { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; margin-right: 6px; }
.severity-critical { background: #fef2f2; color: #991b1b; border: 1px solid #fecaca; }
.severity-warning { background: #fefce8; color: #854d0e; border: 1px solid #fef08a; }
.severity-info { background: #f0fdf4; color: #166534; border: 1px solid #bbf7d0; }
.file-section { margin-bottom: 16px; padding: 12px; border: 1px solid #e5e7eb; border-radius: 6px; }
.file-header { font-weight: 600; font-family: monospace; font-size: 13px; margin-bottom: 8px; color: #111827; }
.suggestion { margin: 8px 0; padding: 8px 12px; border-radius: 6px; font-size: 12px; }
.suggestion-critical { border-left: 3px solid #ef4444; background: #fef2f2; }
.suggestion-warning { border-left: 3px solid #f59e0b; background: #fefce8; }
.suggestion-info { border-left: 3px solid #22c55e; background: #f0fdf4; }
.note { margin-top: 8px; padding: 8px 12px; background: #f0f9ff; border-left: 3px solid #3b82f6; font-size: 12px; border-radius: 4px; }
.note-label { font-size: 11px; color: #6b7280; margin-bottom: 4px; }
.overall-note { padding: 12px 16px; background: #eff6ff; border: 1px solid #bfdbfe; border-radius: 8px; margin-top: 12px; }
pre { background: #f8fafc; border: 1px solid #e2e8f0; padding: 10px; border-radius: 6px; font-family: monospace; font-size: 12px; overflow-x: auto; margin: 8px 0; white-space: pre-wrap; word-wrap: break-word; }
code { font-family: monospace; background: #f1f5f9; padding: 1px 4px; border-radius: 3px; font-size: 12px; }
pre code { background: none; padding: 0; }
.footer { margin-top: 40px; padding-top: 16px; border-top: 1px solid #e5e7eb; text-align: center; color: #9ca3af; font-size: 11px; }
@media print {
body { padding: 20px; }
.section { break-inside: avoid; }
.file-section { break-inside: avoid; }
}
</style>
</head>
<body>
<h1>PR-Helper 审查报告</h1>
<div class="meta">
<span>📦 仓库: {{.RepoURL}}</span>
<span>🌿 分支: {{.BaseRef}} → {{.HeadRef}}</span>
<span>📅 审查时间: {{.ReviewedAt}}</span>
</div>
<!-- Overall Assessment -->
<div class="section">
<h2>整体评估</h2>
{{if .Score}}
<div style="margin-bottom: 12px;">
<span class="score">{{.Score}}</span><span class="score-label">/10</span>
</div>
{{end}}
{{if .Overall}}
<p><strong>总体评价:</strong> {{.Overall}}</p>
{{end}}
{{if .Findings}}
<p><strong>主要发现:</strong></p>
<p>{{.Findings}}</p>
{{end}}
{{if .Recommendations}}
<p><strong>改进建议:</strong></p>
<p>{{.Recommendations}}</p>
{{end}}
{{if .OverallNotes}}
<div class="overall-note">
<div class="note-label">💬 用户备注:</div>
{{range .OverallNotes}}
<p>{{.}}</p>
{{end}}
</div>
{{end}}
</div>
<!-- File Reviews -->
{{if .FileReviews}}
<h2>逐文件审查</h2>
{{range .FileReviews}}
<div class="file-section">
<div class="file-header">📄 {{.FileName}}
{{if .ChangeLines}}<span style="font-weight:normal;color:#6b7280;font-size:12px;">({{.ChangeLines}} 行变更)</span>{{end}}
</div>
{{range .Suggestions}}
<div class="suggestion suggestion-{{.Severity}}">
<span class="severity severity-{{.Severity}}">{{.SeverityCN}}</span>
<strong>{{.Description}}</strong>
{{if .Suggestion}}<p style="margin-top:4px;">建议: {{.Suggestion}}</p>{{end}}
{{if .CodeExample}}<pre><code>{{.CodeExample}}</code></pre>{{end}}
</div>
{{end}}
{{range .Notes}}
<div class="note">
<div class="note-label">💬 备注:</div>
<p>{{.}}</p>
</div>
{{end}}
</div>
{{end}}
{{end}}
<!-- Raw result fallback if no structured data -->
{{if and (not .FileReviews) .Result}}
<div class="section">
<h2>审查详情</h2>
<pre>{{.Result}}</pre>
</div>
{{end}}
<div class="footer">
PR-Helper — AI 代码审查报告 · {{.ReviewedAt}}
</div>
</body>
</html>