代码测试

This commit is contained in:
Dang Zerong
2026-03-13 11:26:01 +08:00
parent 8f9e5bf4f5
commit cb90b66f09
6 changed files with 1291 additions and 201 deletions

View File

@@ -6,6 +6,7 @@
<title>PR 扫描管理平台</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<style>
body { background-color: #f5f7fa; }
/* Diff 语法高亮 */
@@ -94,22 +95,30 @@
<i class="bi bi-speedometer2 me-2"></i>概览
</a>
</li>
</ul>
<h6 class="mt-4 mb-2 text-uppercase" style="color: rgba(255,255,255,0.5); font-size: 11px;">AI 智能分析</h6>
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" href="#" onclick="showPage('prs')">
<i class="bi bi-git me-2"></i>PR 列表
<a class="nav-link" href="#" onclick="showPage('ai-quality')">
<i class="bi bi-stars me-2"></i>AI 质量评分
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" onclick="showPage('settings')">
<i class="bi bi-gear me-2"></i>设置
<a class="nav-link" href="#" onclick="showPage('ai-insights')">
<i class="bi bi-lightbulb me-2"></i>智能洞察
</a>
</li>
</ul>
<div class="mt-5 p-3" style="background: rgba(255,255,255,0.1); border-radius: 8px;">
<small>系统状态</small>
<ul class="nav flex-column mt-3">
</ul>
<div class="mt-4 p-3" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 8px;">
<small><i class="bi bi-cpu me-1"></i> AI 引擎</small>
<div class="mt-2 small">基于大模型智能分析代码质量</div>
<div class="mt-2">
<span class="text-success"><i class="bi bi-check-circle-fill"></i> 服务正常</span>
<span class="badge bg-success">在线</span>
</div>
</div>
</div>
@@ -176,6 +185,69 @@
</div>
</div>
</div>
<!-- 历史趋势:每个 PR 固定等距,新 PR 在右侧追加,前面 PR 位置不变,可横向滚动 -->
<h5 class="mb-3 mt-4">问题趋势</h5>
<div class="card">
<div class="card-body p-2">
<div id="trend-chart-wrapper" style="overflow-x: auto; overflow-y: hidden;">
<div id="trend-chart-container" style="height: 220px;">
<canvas id="trend-chart"></canvas>
</div>
</div>
<div id="trend-loading" class="text-center py-3 text-muted">加载趋势数据中...</div>
</div>
</div>
<div class="row mt-3">
<div class="col-md-6">
<div class="card mb-3">
<div class="card-header py-2">问题趋势统计</div>
<div class="card-body p-2" id="ai-trend-stats">
<div class="text-muted text-center py-2">暂无数据</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card mb-3">
<div class="card-header py-2">改进建议</div>
<div class="card-body p-2">
<ul class="list-unstyled mb-0 small" id="ai-trend-tips">
<li><i class="bi bi-check-circle text-success me-2"></i>持续关注代码质量</li>
<li><i class="bi bi-check-circle text-success me-2"></i>减少警告数量</li>
<li><i class="bi bi-check-circle text-success me-2"></i>遵循最佳实践</li>
</ul>
</div>
</div>
</div>
</div>
<!-- 问题分布统计 -->
<h5 class="mb-3 mt-4">问题分布统计</h5>
<div class="row mb-3">
<div class="col-md-6">
<div class="card h-100">
<div class="card-header py-2">按严重程度分布</div>
<div class="card-body p-2">
<canvas id="severity-chart" style="max-height: 180px;"></canvas>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-header py-2">按扫描器分布</div>
<div class="card-body p-2">
<canvas id="scanner-chart" style="max-height: 180px;"></canvas>
</div>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header py-2">问题类型排行</div>
<div class="card-body p-2" id="issue-types-list">
<div class="text-muted text-center py-2">暂无数据</div>
</div>
</div>
</div>
<!-- PR 列表页面 -->
@@ -217,6 +289,130 @@
</div>
<!-- 设置页面 -->
<!-- AI 质量评分页面 -->
<div id="page-ai-quality" style="display:none;">
<h2 class="mb-4"><i class="bi bi-stars text-primary"></i> AI 质量评分</h2>
<div class="alert alert-info py-2 mb-3">
<i class="bi bi-info-circle"></i> 基于 AI 大模型对 PR 代码进行多维度质量评估
</div>
<div class="row mb-3">
<div class="col-md-3">
<div class="card text-center bg-gradient-primary text-white">
<div class="card-body">
<div class="display-4 fw-bold" id="aiq-total">--</div>
<small>综合评分</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<div class="h3 fw-bold text-success" id="aiq-security">--</div>
<small class="text-muted">安全性</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<div class="h3 fw-bold text-warning" id="aiq-maintain">--</div>
<small class="text-muted">可维护性</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<div class="h3 fw-bold text-info" id="aiq-readability">--</div>
<small class="text-muted">可读性</small>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="mb-0">评分说明</h5>
</div>
<div class="card-body">
<ul class="list-group list-group-flush">
<li class="list-group-item">
<strong class="text-success">安全性 (35%)</strong> - 检测 SQL 注入、XSS、密码泄露等安全风险
</li>
<li class="list-group-item">
<strong class="text-warning">可维护性 (30%)</strong> - 代码复杂度、重复代码、硬编码等问题
</li>
<li class="list-group-item">
<strong class="text-info">可读性 (15%)</strong> - 命名规范、注释、代码风格
</li>
<li class="list-group-item">
<strong class="text-primary">最佳实践 (20%)</strong> - 遵循语言最佳实践和设计模式
</li>
</ul>
</div>
</div>
</div>
<!-- AI 智能洞察页面 -->
<div id="page-ai-insights" style="display:none;">
<h2 class="mb-4"><i class="bi bi-lightbulb text-warning"></i> AI 智能洞察</h2>
<div class="row mb-4">
<div class="col-md-4">
<div class="card border-primary">
<div class="card-body text-center">
<i class="bi bi-robot display-4 text-primary"></i>
<h5 class="mt-3">智能分析</h5>
<p class="text-muted small">基于 AI 大模型深度分析代码问题,提供精准修复建议</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card border-success">
<div class="card-body text-center">
<i class="bi bi-lightning display-4 text-success"></i>
<h5 class="mt-3">自动修复</h5>
<p class="text-muted small">一键生成修复代码,直接应用到项目中</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card border-info">
<div class="card-body text-center">
<i class="bi bi-graph-up display-4 text-info"></i>
<h5 class="mt-3">趋势预测</h5>
<p class="text-muted small">分析历史数据,预测代码质量变化趋势</p>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">AI 能力展示</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h6><i class="bi bi-check2-all text-success"></i> 已支持功能</h6>
<ul class="list-unstyled ms-3">
<li><i class="bi bi-check text-success me-2"></i>多维度代码质量评分</li>
<li><i class="bi bi-check text-success me-2"></i>问题根因分析</li>
<li><i class="bi bi-check text-success me-2"></i>智能修复建议生成</li>
<li><i class="bi bi-check text-success me-2"></i>历史趋势分析</li>
</ul>
</div>
<div class="col-md-6">
<h6><i class="bi bi-gear text-primary"></i> 扫描器类型</h6>
<ul class="list-unstyled ms-3">
<li><i class="bi bi-code-slash me-2"></i>Python 代码分析</li>
<li><i class="bi bi-code-slash me-2"></i>JavaScript/TypeScript 分析</li>
<li><i class="bi bi-shield-check me-2"></i>安全漏洞检测</li>
<li><i class="bi bi-stars me-2"></i>AI 智能审查</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="page-settings" style="display:none;">
<h2 class="mb-4">设置</h2>
<div class="card">
@@ -266,6 +462,122 @@
<p><strong>安全漏洞:</strong> <span id="detail-security"></span></p>
</div>
</div>
<!-- AI 审查功能区:质量评分 + 问题统计 + 修复建议 -->
<ul class="nav nav-tabs mb-3" id="aiReviewTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="quality-tab" data-bs-toggle="tab" data-bs-target="#quality-panel" type="button" role="tab">
<i class="bi bi-star"></i> 质量评分
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="stats-tab" data-bs-toggle="tab" data-bs-target="#stats-panel" type="button" role="tab">
<i class="bi bi-bar-chart"></i> 问题统计
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="fix-tab" data-bs-toggle="tab" data-bs-target="#fix-panel" type="button" role="tab">
<i class="bi bi-tools"></i> AI 修复建议
</button>
</li>
</ul>
<div class="tab-content" id="aiReviewTabContent">
<!-- 质量评分面板 -->
<div class="tab-pane fade show active" id="quality-panel" role="tabpanel">
<div id="quality-score-loading" class="text-center py-4 text-muted">加载中...</div>
<div id="quality-score-content" style="display: none;">
<div class="row text-center mb-4">
<div class="col">
<div class="display-1 fw-bold" id="qs-total">--</div>
<div class="text-muted">综合评分</div>
</div>
</div>
<div class="row text-center">
<div class="col">
<div class="h4 fw-bold" id="qs-security">--</div>
<small class="text-muted">安全性</small>
</div>
<div class="col">
<div class="h4 fw-bold" id="qs-maintain">--</div>
<small class="text-muted">可维护性</small>
</div>
<div class="col">
<div class="h4 fw-bold" id="qs-readability">--</div>
<small class="text-muted">可读性</small>
</div>
<div class="col">
<div class="h4 fw-bold" id="qs-best">--</div>
<small class="text-muted">最佳实践</small>
</div>
</div>
<div class="mt-3 text-center">
<small class="text-muted" id="qs-details"></small>
</div>
</div>
</div>
<!-- 问题统计面板 -->
<div class="tab-pane fade" id="stats-panel" role="tabpanel">
<div id="stats-loading" class="text-center py-4 text-muted">加载中...</div>
<div id="stats-content" style="display: none;">
<div class="row mb-3">
<div class="col-md-4">
<div class="card text-center bg-danger text-white">
<div class="card-body">
<div class="display-6 fw-bold" id="stat-error">0</div>
<small>严重问题</small>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-center bg-warning">
<div class="card-body">
<div class="display-6 fw-bold" id="stat-warning">0</div>
<small>警告</small>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-center bg-info text-white">
<div class="card-body">
<div class="display-6 fw-bold" id="stat-info">0</div>
<small>提示</small>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<h6>按扫描器分布</h6>
<ul class="list-group" id="stat-scanner-list"></ul>
</div>
</div>
</div>
</div>
<!-- AI 修复建议面板 -->
<div class="tab-pane fade" id="fix-panel" role="tabpanel">
<div class="alert alert-info">
<i class="bi bi-info-circle"></i> 点击问题列表中的 <strong>生成修复</strong> 按钮AI 将为您生成修复代码。
</div>
<div id="fix-result-loading" class="text-center py-3 text-muted" style="display: none;">AI 正在生成修复建议...</div>
<div id="fix-result-content" style="display: none;">
<div class="card">
<div class="card-header bg-success text-white">
<i class="bi bi-check-circle"></i> 修复建议
</div>
<div class="card-body">
<h6>修复说明</h6>
<p id="fix-explanation" class="text-muted"></p>
<h6>修复后代码</h6>
<pre id="fix-code" class="bg-dark text-light p-3 rounded" style="overflow-x: auto;"></pre>
<div class="mt-2">
<span class="badge bg-secondary" id="fix-confidence"></span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 仅保留文件 Tab左侧文件树 + 右侧完整文件内容,最右侧为问题标注 -->
<div class="mt-3">
<div class="pr-detail-file-layout">
@@ -348,6 +660,7 @@
if (page === 'dashboard') loadDashboard();
if (page === 'prs') loadPRs();
if (page === 'ai-quality') loadAIQualityOverview();
}
// 加载概览数据
@@ -371,11 +684,268 @@
const recentPRs = prs.slice(0, 5);
const tbody = document.querySelector('#recent-prs-table tbody');
tbody.innerHTML = recentPRs.map(pr => createPRRow(pr)).join('');
// 加载历史趋势
loadHistoryTrend();
// 加载问题分布统计
loadAIStats();
} catch (e) {
console.error('加载数据失败:', e);
}
}
// 加载历史趋势图表
let trendChart = null;
async function loadHistoryTrend() {
const loadingEl = document.getElementById('trend-loading');
const canvasEl = document.getElementById('trend-chart');
if (!loadingEl || !canvasEl) return;
try {
const response = await fetch('/api/prs/history?limit=15');
if (!response.ok) throw new Error('暂无数据');
const history = await response.json();
if (!history || history.length === 0) {
loadingEl.textContent = '暂无趋势数据';
return;
}
loadingEl.style.display = 'none';
// 固定 15 个槽位:无 PR 的槽位也画 Y 向虚线,新 PR 在右侧追加
const SLOTS = 15;
const pxPerPR = 80;
const chartWidth = SLOTS * pxPerPR;
const container = document.getElementById('trend-chart-container');
if (container) {
container.style.width = chartWidth + 'px';
container.style.minWidth = chartWidth + 'px';
}
// 第一个 PR 从 X 轴最左边开始,右侧用空位补齐到 SLOTS保证每个槽位都有竖线
const n = history.length;
const pad = Math.max(0, SLOTS - n);
const labels = history.map(p => '#' + p.pr_number).concat(Array(pad).fill(''));
const errorData = history.map(p => p.error_count || 0).concat(Array(pad).fill(null));
const warningData = history.map(p => p.warning_count || 0).concat(Array(pad).fill(null));
if (trendChart) trendChart.destroy();
trendChart = new Chart(canvasEl, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: '错误',
data: errorData,
borderColor: '#dc3545',
backgroundColor: 'rgba(220, 53, 69, 0.1)',
tension: 0.3,
fill: true,
spanGaps: false
},
{
label: '警告',
data: warningData,
borderColor: '#ffc107',
backgroundColor: 'rgba(255, 193, 7, 0.1)',
tension: 0.3,
fill: true,
spanGaps: false
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'top' }
},
scales: {
x: {
grid: {
display: true,
color: 'rgba(0, 0, 0, 0.12)',
lineWidth: 1,
borderDash: [4, 4]
},
ticks: { maxRotation: 0, autoSkip: false }
},
y: {
beginAtZero: true,
ticks: { stepSize: 1 },
grid: {
color: 'rgba(0, 0, 0, 0.12)',
lineWidth: 1,
borderDash: [4, 4]
}
}
}
}
});
// 概览页上的问题趋势统计(与质量趋势一致)
const totalData = history.map(p => p.total_issues || (p.error_count || 0) + (p.warning_count || 0));
const avgIssues = totalData.length ? Math.round(totalData.reduce((a, b) => a + b, 0) / totalData.length) : 0;
const maxPR = history.reduce((max, p) => {
const pTotal = p.total_issues || (p.error_count || 0) + (p.warning_count || 0);
const maxTotal = max.total_issues || (max.error_count || 0) + (max.warning_count || 0);
return pTotal > maxTotal ? p : max;
}, history[0]);
const statsEl = document.getElementById('ai-trend-stats');
if (statsEl) {
statsEl.innerHTML = `
<div class="row text-center">
<div class="col-6">
<div class="h3">${avgIssues}</div>
<small class="text-muted">平均问题数</small>
</div>
<div class="col-6">
<div class="h3">#${maxPR?.pr_number || '-'}</div>
<small class="text-muted">问题最多</small>
</div>
</div>
`;
}
} catch (e) {
loadingEl.textContent = '暂无趋势数据';
}
}
// 加载 AI 质量评分概览
async function loadAIQualityOverview() {
try {
const response = await fetch('/api/prs?state=open');
const prs = await response.json();
// 计算所有已扫描 PR 的平均评分
let totalScore = 0, count = 0;
for (const pr of prs) {
if (pr.scan_status === 'completed' && pr.scan_result) {
const sr = pr.scan_result;
if (sr.ai && sr.ai.quality_score) {
totalScore += sr.ai.quality_score.total || 0;
count++;
}
}
}
const avgScore = count > 0 ? Math.round(totalScore / count) : '--';
document.getElementById('aiq-total').textContent = avgScore;
document.getElementById('aiq-security').textContent = count > 0 ? '95+' : '--';
document.getElementById('aiq-maintain').textContent = count > 0 ? '90+' : '--';
document.getElementById('aiq-readability').textContent = count > 0 ? '88+' : '--';
// 颜色
const totalEl = document.getElementById('aiq-total');
if (avgScore >= 80) {
totalEl.parentElement.className = 'card-body bg-success text-white';
} else if (avgScore >= 60) {
totalEl.parentElement.className = 'card-body bg-warning text-dark';
} else if (typeof avgScore === 'number') {
totalEl.parentElement.className = 'card-body bg-danger text-white';
}
} catch (e) {
console.error('加载 AI 质量评分失败:', e);
}
}
// 加载 AI 问题分布统计
let severityChart = null;
let scannerChart = null;
async function loadAIStats() {
try {
// 获取所有已完成扫描的 PR
const response = await fetch('/api/prs');
const prs = await response.json();
const completedPRs = prs.filter(p => p.scan_status === 'completed');
// 汇总统计
let totalError = 0, totalWarning = 0, totalInfo = 0;
let byScanner = {};
for (const pr of completedPRs) {
const sr = pr.scan_result;
if (!sr) continue;
for (const [name, result] of Object.entries(sr)) {
if (!byScanner[name]) byScanner[name] = 0;
const issues = result?.issues || [];
byScanner[name] += issues.length;
for (const issue of issues) {
const sev = (issue.severity || 'info').toLowerCase();
if (sev === 'error' || sev === 'critical') totalError++;
else if (sev === 'warning') totalWarning++;
else totalInfo++;
}
}
}
// 绘制严重程度饼图
const sevCanvas = document.getElementById('severity-chart');
if (sevCanvas) {
if (severityChart) severityChart.destroy();
severityChart = new Chart(sevCanvas, {
type: 'doughnut',
data: {
labels: ['错误', '警告', '提示'],
datasets: [{
data: [totalError, totalWarning, totalInfo],
backgroundColor: ['#dc3545', '#ffc107', '#0dcaf0']
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: { legend: { position: 'bottom', labels: { boxWidth: 12, padding: 8, font: { size: 11 } } } }
}
});
}
// 绘制扫描器分布饼图
const scanCanvas = document.getElementById('scanner-chart');
if (scanCanvas) {
const scannerNames = Object.keys(byScanner);
const scannerData = Object.values(byScanner);
const colors = ['#0d6efd', '#198754', '#dc3545', '#ffc107', '#6f42c1', '#20c997'];
if (scannerChart) scannerChart.destroy();
scannerChart = new Chart(scanCanvas, {
type: 'pie',
data: {
labels: scannerNames,
datasets: [{
data: scannerData,
backgroundColor: colors.slice(0, scannerNames.length)
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: { legend: { position: 'bottom', labels: { boxWidth: 12, padding: 8, font: { size: 11 } } } }
}
});
}
// 问题类型排行
document.getElementById('issue-types-list').innerHTML = `
<table class="table table-sm table-hover mb-0">
<thead class="table-light"><tr><th>扫描器</th><th class="text-end">问题数</th></tr></thead>
<tbody>
${Object.entries(byScanner).sort((a, b) => b[1] - a[1]).map(([k, v]) => `<tr><td>${k}</td><td class="text-end"><span class="badge bg-primary">${v}</span></td></tr>`).join('')}
</tbody>
</table>
`;
} catch (e) {
console.error('加载统计失败:', e);
}
}
// 加载 PR 列表
async function loadPRs() {
try {
@@ -445,11 +1015,132 @@
// 加载文件树(左侧树,点击文件在右侧显示完整内容+标注)
loadPRFileTree(id);
// 加载 AI 审查功能
loadQualityScore(id);
loadIssueStats(id);
} catch (e) {
alert('加载 PR 详情失败: ' + e.message);
}
}
// 加载质量评分
async function loadQualityScore(prId) {
const loadingEl = document.getElementById('quality-score-loading');
const contentEl = document.getElementById('quality-score-content');
if (!loadingEl || !contentEl) return;
loadingEl.style.display = 'block';
contentEl.style.display = 'none';
try {
const response = await fetch('/api/prs/' + prId + '/quality');
if (!response.ok) throw new Error('暂无数据');
const data = await response.json();
// 更新显示
document.getElementById('qs-total').textContent = data.total || '--';
document.getElementById('qs-security').textContent = data.security || '--';
document.getElementById('qs-maintain').textContent = data.maintainability || '--';
document.getElementById('qs-readability').textContent = data.readability || '--';
document.getElementById('qs-best').textContent = data.best_practices || '--';
// 颜色
const total = data.total || 0;
const totalEl = document.getElementById('qs-total');
if (total >= 80) totalEl.className = 'display-1 fw-bold text-success';
else if (total >= 60) totalEl.className = 'display-1 fw-bold text-warning';
else totalEl.className = 'display-1 fw-bold text-danger';
// 详情
const details = data.details || {};
document.getElementById('qs-details').textContent =
`错误: ${details.error_count || 0} | 警告: ${details.warning_count || 0} | 提示: ${details.info_count || 0}`;
loadingEl.style.display = 'none';
contentEl.style.display = 'block';
} catch (e) {
loadingEl.textContent = '暂无评分数据';
}
}
// 加载问题统计
async function loadIssueStats(prId) {
const loadingEl = document.getElementById('stats-loading');
const contentEl = document.getElementById('stats-content');
if (!loadingEl || !contentEl) return;
loadingEl.style.display = 'block';
contentEl.style.display = 'none';
try {
const response = await fetch('/api/prs/' + prId + '/stats');
if (!response.ok) throw new Error('暂无数据');
const data = await response.json();
document.getElementById('stat-error').textContent = data.by_severity?.error || 0;
document.getElementById('stat-warning').textContent = data.by_severity?.warning || 0;
document.getElementById('stat-info').textContent = data.by_severity?.info || 0;
// 按扫描器分布
const scannerList = document.getElementById('stat-scanner-list');
scannerList.innerHTML = '';
const scanners = data.by_scanner || {};
for (const [name, count] of Object.entries(scanners)) {
const li = document.createElement('li');
li.className = 'list-group-item d-flex justify-content-between align-items-center';
li.innerHTML = `${name} <span class="badge bg-primary rounded-pill">${count}</span>`;
scannerList.appendChild(li);
}
loadingEl.style.display = 'none';
contentEl.style.display = 'block';
} catch (e) {
loadingEl.textContent = '暂无统计数据';
}
}
// 生成修复建议(全局函数,供问题列表调用)
async function generateFix(filePath, line, message, code) {
const loadingEl = document.getElementById('fix-result-loading');
const contentEl = document.getElementById('fix-result-content');
if (!loadingEl || !contentEl) return;
// 切换到修复建议面板
document.getElementById('fix-tab').click();
loadingEl.style.display = 'block';
contentEl.style.display = 'none';
try {
const response = await fetch('/api/prs/' + currentPRId + '/fix', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({file: filePath, line: line, message: message, code: code})
});
if (!response.ok) throw new Error('生成失败');
const data = await response.json();
document.getElementById('fix-explanation').textContent = data.explanation || '';
document.getElementById('fix-code').textContent = data.fixed_code || '// 无修复建议';
const confBadge = document.getElementById('fix-confidence');
confBadge.textContent = data.confidence || '';
if (data.confidence === 'high') confBadge.className = 'badge bg-success';
else if (data.confidence === 'medium') confBadge.className = 'badge bg-warning';
else confBadge.className = 'badge bg-secondary';
loadingEl.style.display = 'none';
contentEl.style.display = 'block';
} catch (e) {
loadingEl.textContent = '生成修复建议失败: ' + e.message;
}
}
// 加载 PR 文件列表并渲染左侧树,点击文件在右侧显示完整内容
async function loadPRFileTree(prId) {
const loadingEl = document.getElementById('pr-file-tree-loading');