代码测试
This commit is contained in:
705
web/index.html
705
web/index.html
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user