refactor: 合并 AI 评审按钮到审查 Tab,移除重复入口
Deploy PR-Helper / deploy (push) Successful in 2m20s

- 移除 Diff 视图中的「🤖 AI 评审」按钮及行内进度/总结区域
- Review Tab 的「开始审查」现在具备行内 Diff 建议插入能力
- 合并 startInlineReview 到 startReview,统一 SSE 流和变量
- 移除独立的 review-diff-container,建议直接插入 Diff Tab 的 diff 视图
- 完成后 Review Tab 显示总结/备注/卡片,Diff Tab 显示行内建议
This commit is contained in:
2026-06-23 22:10:35 +08:00
parent 8e8ea6a07d
commit 6f0fabf934
+72 -205
View File
@@ -51,9 +51,9 @@
border: 1px solid #fde68a;
}
@media print {
nav, footer, #review-actions, #btn-review, #btn-toggle-diff,
nav, footer, #review-actions, #btn-review,
.note-save-indicator, select, input, #review-progress,
#diff-review-container, #history-select,
#history-select,
label[for="base-ref"], label[for="head-ref"],
label[for="top-n"], label[for="concurrency"] { display: none !important; }
@@ -139,9 +139,6 @@
<div class="flex gap-1 ml-auto items-center">
<button onclick="setDiffView('side-by-side')" id="btn-side-by-side" class="px-3 py-1 text-xs bg-blue-100 text-blue-700 rounded font-medium">并排</button>
<button onclick="setDiffView('unified')" id="btn-unified" class="px-3 py-1 text-xs bg-gray-100 text-gray-600 rounded hover:bg-gray-200">统一</button>
<button onclick="startInlineReview()" id="btn-inline-review" class="px-3 py-1 text-xs bg-purple-100 text-purple-700 rounded font-medium hover:bg-purple-200 ml-2">
🤖 AI 评审
</button>
</div>
</div>
<div class="mt-2 flex items-center gap-3">
@@ -183,28 +180,6 @@
<!-- Diff Tab -->
<div id="panel-diff" class="p-4 hidden">
<div id="diff-container"></div>
<!-- Inline review progress -->
<div id="review-progress" class="hidden mt-4 bg-white rounded-lg border p-4">
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-medium text-gray-700">🤖 AI 代码审查</span>
<button onclick="cancelInlineReview()" class="text-xs text-gray-400 hover:text-gray-600">取消</button>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 mb-2">
<div id="review-progress-bar" class="bg-purple-600 h-2 rounded-full transition-all duration-300" style="width: 0%"></div>
</div>
<p id="review-progress-text" class="text-xs text-gray-500">准备中...</p>
</div>
<!-- Review summary (shown after completion) -->
<div id="review-summary" class="hidden mt-4 bg-white rounded-lg border p-4">
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-medium text-gray-700">审查总结</span>
<div class="flex gap-2">
<button onclick="showTab('review'); startReview()" class="text-xs text-purple-600 hover:underline">查看完整审查</button>
<button onclick="clearInlineReview()" class="text-xs text-gray-400 hover:text-gray-600">清除</button>
</div>
</div>
<div id="review-summary-content" class="text-sm text-gray-700"></div>
</div>
</div>
<!-- PR Description Tab -->
@@ -275,12 +250,15 @@
</div>
<!-- Progress -->
<div id="review-progress-full" class="mb-6 hidden">
<h3 class="font-semibold text-gray-700 mb-2">审查进度</h3>
<div class="w-full bg-gray-200 rounded-full h-2.5 mb-2">
<div id="review-progress-bar-full" class="bg-purple-600 h-2.5 rounded-full transition-all duration-300" style="width: 0%"></div>
<div id="review-progress" class="mb-6 hidden">
<div class="flex items-center justify-between mb-2">
<h3 class="font-semibold text-gray-700">🤖 审查进度</h3>
<button onclick="cancelReview()" class="text-xs text-gray-400 hover:text-gray-600">取消</button>
</div>
<p id="review-progress-text-full" class="text-sm text-gray-500">准备中...</p>
<div class="w-full bg-gray-200 rounded-full h-2.5 mb-2">
<div id="review-progress-bar" class="bg-purple-600 h-2.5 rounded-full transition-all duration-300" style="width: 0%"></div>
</div>
<p id="review-progress-text" class="text-sm text-gray-500">准备中...</p>
</div>
<!-- Results -->
@@ -316,27 +294,17 @@
</div>
<!-- Actions Bar -->
<div id="review-actions" class="flex items-center gap-3 mb-6 hidden">
<div id="review-actions" class="flex items-center gap-3 mb-6">
<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>
<button onclick="showTab('diff')" class="text-sm text-purple-600 hover:underline">
查看 Diff 中的行内建议 →
</button>
</div>
<!-- Inline Diff + Suggestions View -->
<div id="diff-review-container" class="mb-6">
<div class="flex items-center justify-between mb-3">
<h2 class="text-lg font-semibold text-gray-900">代码变更 & 审查建议</h2>
<div class="flex gap-2">
<button onclick="toggleDiffView()" id="btn-toggle-diff" class="px-3 py-1 text-xs bg-gray-100 rounded hover:bg-gray-200">
隐藏 Diff
</button>
</div>
</div>
<div id="review-diff-container"></div>
</div>
<!-- File Reviews (card-based fallback) -->
<!-- File Reviews -->
<div id="file-reviews" class="space-y-4"></div>
</div>
</div>
@@ -626,109 +594,15 @@
return Markdown.render(text);
}
// ── Inline AI Review (quick review in Diff tab) ─────────────────
let inlineReviewSSE = null;
let inlineSuggestions = [];
function startInlineReview() {
const base = document.getElementById('select-base').value;
const head = document.getElementById('select-head').value;
if (!base || !head) {
showToast('请先选择 Base 和 Head 并查看 Diff', 'warning');
return;
}
// Cancel previous if running
if (inlineReviewSSE) {
inlineReviewSSE.abort();
}
const btn = document.getElementById('btn-inline-review');
const progress = document.getElementById('review-progress');
const summary = document.getElementById('review-summary');
btn.disabled = true;
btn.textContent = '⏳ 审查中...';
progress.classList.remove('hidden');
summary.classList.add('hidden');
inlineSuggestions = [];
let totalFiles = 0;
let completedFiles = 0;
inlineReviewSSE = SSE.post(`/api/repos/${repoId}/review`, {
base, head, top_n: 0,
}, {
start(data) {
totalFiles = data.total_files || 0;
document.getElementById('review-progress-text').textContent = `准备审查 ${totalFiles} 个文件...`;
document.getElementById('review-progress-bar').style.width = '5%';
},
file_start(data) {
completedFiles++;
const pct = totalFiles > 0 ? Math.round((completedFiles / totalFiles) * 80 + 10) : 50;
document.getElementById('review-progress-bar').style.width = pct + '%';
document.getElementById('review-progress-text').textContent = `正在审查: ${data.file || ''} (${completedFiles}/${totalFiles})`;
},
suggestion(data) {
inlineSuggestions.push(data);
},
summary(data) {
const el = document.getElementById('review-summary-content');
if (data.score !== undefined) {
el.innerHTML = renderSummaryHtml(data);
} else {
el.innerHTML = renderMarkdown(data.content || '');
}
document.getElementById('review-progress-bar').style.width = '90%';
},
done() {
document.getElementById('review-progress-bar').style.width = '100%';
document.getElementById('review-progress-text').textContent = '审查完成!';
setTimeout(() => {
progress.classList.add('hidden');
summary.classList.remove('hidden');
btn.disabled = false;
btn.textContent = '🤖 AI 评审';
}, 500);
// Insert suggestions into the existing diff
insertInlineSuggestions();
inlineReviewSSE = null;
},
analysis_saved(data) {
// Update "查看完整审查" link behavior
},
error(data) {
showToast('审查失败: ' + (data.message || '未知错误'), 'error');
progress.classList.add('hidden');
btn.disabled = false;
btn.textContent = '🤖 AI 评审';
inlineReviewSSE = null;
},
});
}
function cancelInlineReview() {
if (inlineReviewSSE) {
inlineReviewSSE.abort();
inlineReviewSSE = null;
}
document.getElementById('review-progress').classList.add('hidden');
const btn = document.getElementById('btn-inline-review');
btn.disabled = false;
btn.textContent = '🤖 AI 评审';
}
// ── Insert suggestions into Diff tab's diff view ──────────────
function insertInlineSuggestions() {
const fileSeverities = {};
const severityOrder = { critical: 3, warning: 2, info: 1 };
inlineSuggestions.forEach((s, i) => {
allSuggestions.forEach((s, i) => {
if (s.file) {
DiffViewer.insertFileSuggestion(s.file, s.severity, s.content, 'inline-sug-' + i);
DiffViewer.insertFileSuggestion(s.file, s.severity, s.content, 'sug-' + i);
const cur = fileSeverities[s.file] || 'info';
if ((severityOrder[s.severity] || 0) > (severityOrder[cur] || 0)) {
fileSeverities[s.file] = s.severity;
@@ -746,12 +620,10 @@
}
}
function clearInlineReview() {
function clearInlineSuggestions() {
const container = document.getElementById('diff-container');
container.querySelectorAll('.file-suggestions-container').forEach(el => el.remove());
container.querySelectorAll('.file-tree-severity').forEach(el => el.remove());
document.getElementById('review-summary').classList.add('hidden');
inlineSuggestions = [];
}
// ── Generate PR Description ─────────────────────────────────────
@@ -847,10 +719,9 @@
document.body.removeChild(textarea);
}
// ── Full AI Code Review ─────────────────────────────────────────
// ── AI Code Review ─────────────────────────────────────────────
let currentSSE = null;
let diffVisible = true;
let allSuggestions = [];
let fileReviews = {};
let currentAnalysisId = null;
@@ -871,7 +742,7 @@
}
const btn = document.getElementById('btn-review');
const progress = document.getElementById('review-progress-full');
const progress = document.getElementById('review-progress');
const results = document.getElementById('review-results');
btn.disabled = true;
@@ -885,18 +756,16 @@
currentAnalysisId = null;
document.getElementById('summary-content').innerHTML = '';
document.getElementById('file-reviews').innerHTML = '';
document.getElementById('review-actions').classList.add('hidden');
// Clear previous inline suggestions from diff view
clearInlineSuggestions();
window._reviewDiff = null;
// Progress tracking
let totalFiles = 0;
let completedFiles = 0;
let startedFiles = 0;
function updateProgress() {
const pct = totalFiles > 0 ? Math.round((completedFiles / totalFiles) * 80 + 10) : 50;
document.getElementById('review-progress-bar-full').style.width = pct + '%';
}
const concurrency = document.getElementById('concurrency').value;
currentSSE = SSE.post(`/api/repos/${repoId}/review`, {
@@ -906,13 +775,13 @@
}, {
start(data) {
totalFiles = data.total_files || 0;
document.getElementById('review-progress-text-full').textContent = `准备审查 ${totalFiles} 个文件...`;
document.getElementById('review-progress-bar-full').style.width = '5%';
document.getElementById('review-progress-text').textContent = `准备审查 ${totalFiles} 个文件...`;
document.getElementById('review-progress-bar').style.width = '5%';
},
file_start(data) {
startedFiles++;
const currentFile = data.file || '';
document.getElementById('review-progress-text-full').textContent = `正在审查: ${currentFile} (${startedFiles}/${totalFiles})`;
document.getElementById('review-progress-text').textContent = `正在审查: ${currentFile} (${startedFiles}/${totalFiles})`;
if (!fileReviews[currentFile]) {
fileReviews[currentFile] = { filename: currentFile, suggestions: [], summary: '' };
@@ -929,8 +798,9 @@
},
file_end(data) {
completedFiles++;
updateProgress();
document.getElementById('review-progress-text-full').textContent = `已完成 ${completedFiles}/${totalFiles} 个文件`;
const pct = totalFiles > 0 ? Math.round((completedFiles / totalFiles) * 80 + 10) : 50;
document.getElementById('review-progress-bar').style.width = pct + '%';
document.getElementById('review-progress-text').textContent = `已完成 ${completedFiles}/${totalFiles} 个文件`;
},
file_summary(data) {
if (data.file && fileReviews[data.file]) {
@@ -949,25 +819,41 @@
} else {
el.innerHTML = renderMarkdown(data.content || '');
}
document.getElementById('review-progress-bar-full').style.width = '90%';
document.getElementById('review-progress-text-full').textContent = '生成总结...';
document.getElementById('review-progress-bar').style.width = '90%';
document.getElementById('review-progress-text').textContent = '生成总结...';
},
done() {
document.getElementById('review-progress-bar-full').style.width = '100%';
document.getElementById('review-progress-text-full').textContent = '审查完成!';
document.getElementById('review-progress-bar').style.width = '100%';
document.getElementById('review-progress-text').textContent = '审查完成!';
setTimeout(() => {
progress.classList.add('hidden');
results.classList.remove('hidden');
document.getElementById('review-actions').classList.remove('hidden');
btn.disabled = false;
btn.textContent = '开始审查';
updatePrintHeader();
}, 500);
// Render inline suggestions if diff is available
// Insert suggestions into the Diff tab's diff view
if (window._reviewDiff) {
renderInlineReview(window._reviewDiff);
DiffViewer.init('diff-container');
DiffViewer.renderDiff(window._reviewDiff, {
onComplete() {
insertInlineSuggestions();
}
});
} else {
// Diff not streamed; fetch it and render
fetch(`/api/repos/${repoId}/diff?base=${encodeURIComponent(base)}&head=${encodeURIComponent(head)}`, { credentials: 'same-origin' })
.then(r => r.json())
.then(d => {
if (d.diff) {
DiffViewer.init('diff-container');
DiffViewer.renderDiff(d.diff, {
onComplete() { insertInlineSuggestions(); }
});
}
}).catch(() => {});
}
currentSSE = null;
@@ -989,6 +875,17 @@
});
}
function cancelReview() {
if (currentSSE) {
currentSSE.abort();
currentSSE = null;
}
document.getElementById('review-progress').classList.add('hidden');
const btn = document.getElementById('btn-review');
btn.disabled = false;
btn.textContent = '开始审查';
}
function appendSuggestionCard(suggestion) {
const container = document.getElementById('file-reviews');
@@ -1025,41 +922,6 @@
return (str || '').replace(/[^a-zA-Z0-9]/g, '-');
}
function renderInlineReview(diffString) {
const container = document.getElementById('review-diff-container');
if (!container) return;
DiffViewer.init('review-diff-container');
DiffViewer.renderDiff(diffString, {
onComplete() {
const fileSeverities = {};
const severityOrder = { critical: 3, warning: 2, info: 1 };
allSuggestions.forEach((s, i) => {
if (s.file) {
DiffViewer.insertFileSuggestion(s.file, s.severity, s.content, 'sug-' + i);
const cur = fileSeverities[s.file] || 'info';
if ((severityOrder[s.severity] || 0) > (severityOrder[cur] || 0)) {
fileSeverities[s.file] = s.severity;
}
}
});
Object.entries(fileSeverities).forEach(([file, sev]) => {
DiffViewer.markFileSeverity(file, sev);
});
}
});
}
function toggleDiffView() {
const container = document.getElementById('review-diff-container');
const btn = document.getElementById('btn-toggle-diff');
diffVisible = !diffVisible;
container.style.display = diffVisible ? '' : 'none';
btn.textContent = diffVisible ? '隐藏 Diff' : '显示 Diff';
}
function updatePrintHeader() {
const baseEl = document.getElementById('select-base');
const headEl = document.getElementById('select-head');
@@ -1143,7 +1005,7 @@
document.getElementById('review-actions').classList.remove('hidden');
updatePrintHeader();
// Load diff and render inline
// Load diff and render with inline suggestions in Diff tab
const base = data.base_ref;
const head = data.head_ref;
@@ -1152,7 +1014,12 @@
const diffData = await diffResp.json();
const diffString = diffData.diff || '';
if (diffString) {
renderInlineReview(diffString);
DiffViewer.init('diff-container');
DiffViewer.renderDiff(diffString, {
onComplete() {
insertInlineSuggestions();
}
});
}
}