feat: 持久化 AI 审查结果,文件级内联展示与页面集成
This commit is contained in:
+141
-3
@@ -41,6 +41,16 @@
|
||||
开始审查
|
||||
</button>
|
||||
<div id="load-error" class="hidden mt-3 p-3 bg-red-50 border border-red-200 rounded-md text-red-700 text-sm"></div>
|
||||
|
||||
<!-- History -->
|
||||
<div class="mt-4 pt-4 border-t border-gray-200">
|
||||
<div class="flex items-center gap-3">
|
||||
<label class="text-sm font-medium text-gray-600">📋 历史评审:</label>
|
||||
<select id="history-select" class="flex-1 border rounded-md px-3 py-1.5 text-sm" onchange="loadHistoryReview(this.value)">
|
||||
<option value="">加载中...</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Progress -->
|
||||
@@ -376,12 +386,26 @@
|
||||
DiffViewer.init('review-diff-container');
|
||||
DiffViewer.renderDiff(diffString, {
|
||||
onComplete() {
|
||||
// Insert suggestions after all files are rendered
|
||||
// Track max severity per file for sidebar markers
|
||||
const fileSeverities = {};
|
||||
const severityOrder = { critical: 3, warning: 2, info: 1 };
|
||||
|
||||
// Insert file-level suggestions after all files are rendered
|
||||
allSuggestions.forEach((s, i) => {
|
||||
if (s.file && s.line) {
|
||||
DiffViewer.insertSuggestion(s.file, s.line, s.side || 'right', s.severity, s.content, 'sug-' + i);
|
||||
if (s.file) {
|
||||
DiffViewer.insertFileSuggestion(s.file, s.severity, s.content, 'sug-' + i);
|
||||
// Track max severity per file
|
||||
const cur = fileSeverities[s.file] || 'info';
|
||||
if ((severityOrder[s.severity] || 0) > (severityOrder[cur] || 0)) {
|
||||
fileSeverities[s.file] = s.severity;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Mark severity in file tree sidebar
|
||||
Object.entries(fileSeverities).forEach(([file, sev]) => {
|
||||
DiffViewer.markFileSeverity(file, sev);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -418,7 +442,121 @@
|
||||
return html;
|
||||
}
|
||||
|
||||
// ── History loading ────────────────────────────────────────
|
||||
|
||||
async function loadHistoryList() {
|
||||
try {
|
||||
const resp = await fetch(`/api/repos/${repoId}/review/analyses`);
|
||||
if (!resp.ok) return;
|
||||
const analyses = await resp.json();
|
||||
|
||||
const select = document.getElementById('history-select');
|
||||
select.innerHTML = '<option value="">选择历史评审...</option>';
|
||||
|
||||
analyses.forEach(a => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = a.id;
|
||||
const date = (a.created_at || '').substring(0, 16).replace('T', ' ');
|
||||
opt.textContent = `${a.base_ref} → ${a.head_ref} (${date})`;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
|
||||
if (analyses.length === 0) {
|
||||
select.innerHTML = '<option value="">暂无历史评审</option>';
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to load history:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadHistoryReview(analysisId) {
|
||||
if (!analysisId) return;
|
||||
|
||||
try {
|
||||
// Fetch the review result
|
||||
const resp = await fetch(`/api/repos/${repoId}/review/analyses/${analysisId}`);
|
||||
if (!resp.ok) throw new Error('Failed to load review');
|
||||
const data = await resp.json();
|
||||
|
||||
const result = data.result;
|
||||
if (!result || !result.file_reviews) {
|
||||
alert('该历史记录没有完整的评审数据');
|
||||
return;
|
||||
}
|
||||
|
||||
// Set current analysis ID for notes
|
||||
currentAnalysisId = parseInt(analysisId);
|
||||
|
||||
// Populate suggestions from stored data
|
||||
allSuggestions = [];
|
||||
fileReviews = {};
|
||||
|
||||
result.file_reviews.forEach(fr => {
|
||||
fileReviews[fr.file_name] = { filename: fr.file_name, suggestions: [], summary: '' };
|
||||
(fr.suggestions || []).forEach(s => {
|
||||
const content = s.description || '';
|
||||
const fullContent = content +
|
||||
(s.suggestion ? '\n\n**建议修改:** ' + s.suggestion : '') +
|
||||
(s.code_example ? '\n\n```\n' + s.code_example + '\n```' : '');
|
||||
const sug = { file: fr.file_name, severity: s.severity, content: fullContent };
|
||||
allSuggestions.push(sug);
|
||||
fileReviews[fr.file_name].suggestions.push(sug);
|
||||
});
|
||||
});
|
||||
|
||||
// Render summary
|
||||
const summary = result.summary || {};
|
||||
let summaryText = '';
|
||||
if (summary.score) summaryText += `整体评分: ${summary.score}/10\n\n`;
|
||||
if (summary.overall) summaryText += summary.overall;
|
||||
if (summary.findings) summaryText += '\n\n**主要发现:**\n' + summary.findings;
|
||||
if (summary.recommendations) summaryText += '\n\n**改进建议:**\n' + summary.recommendations;
|
||||
|
||||
document.getElementById('summary-content').innerHTML = renderMarkdown(summaryText);
|
||||
|
||||
// Clear and rebuild card view
|
||||
document.getElementById('file-reviews').innerHTML = '';
|
||||
allSuggestions.forEach(s => appendSuggestionCard(s));
|
||||
|
||||
// Show results
|
||||
document.getElementById('results').classList.remove('hidden');
|
||||
document.getElementById('review-actions').classList.remove('hidden');
|
||||
|
||||
// Load diff and render inline
|
||||
const base = data.base_ref;
|
||||
const head = data.head_ref;
|
||||
|
||||
// Load diff from API
|
||||
const diffResp = await fetch(`/api/repos/${repoId}/diff?base=${encodeURIComponent(base)}&head=${encodeURIComponent(head)}`);
|
||||
if (diffResp.ok) {
|
||||
const diffData = await diffResp.json();
|
||||
const diffString = diffData.diff || '';
|
||||
if (diffString) {
|
||||
renderInlineReview(diffString);
|
||||
}
|
||||
}
|
||||
|
||||
// Init note editor
|
||||
NoteEditor.init(repoId, currentAnalysisId);
|
||||
NoteEditor.loadNotes();
|
||||
|
||||
} catch (e) {
|
||||
console.error('Load history error:', e);
|
||||
alert('加载历史评审失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-load history from URL param
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const urlAnalysisId = urlParams.get('analysis_id');
|
||||
|
||||
loadRefs();
|
||||
loadHistoryList();
|
||||
|
||||
// If analysis_id in URL, auto-load after refs are ready
|
||||
if (urlAnalysisId) {
|
||||
setTimeout(() => loadHistoryReview(urlAnalysisId), 500);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user