diff --git a/handlers/pages.go b/handlers/pages.go
index 28bdf43..7954165 100644
--- a/handlers/pages.go
+++ b/handlers/pages.go
@@ -39,18 +39,6 @@ func (h *PageHandler) Index(c *gin.Context) {
}
func (h *PageHandler) Repo(c *gin.Context) {
- data := userData(c)
- data["ID"] = c.Param("id")
- c.HTML(http.StatusOK, "pages/repo.html", data)
-}
-
-func (h *PageHandler) Generate(c *gin.Context) {
- data := userData(c)
- data["ID"] = c.Param("id")
- c.HTML(http.StatusOK, "pages/generate.html", data)
-}
-
-func (h *PageHandler) Review(c *gin.Context) {
user := GetCurrentUser(c)
topN := "20"
concurrency := "5"
@@ -68,7 +56,7 @@ func (h *PageHandler) Review(c *gin.Context) {
data["ID"] = c.Param("id")
data["TopN"] = topN
data["Concurrency"] = concurrency
- c.HTML(http.StatusOK, "pages/review.html", data)
+ c.HTML(http.StatusOK, "pages/repo.html", data)
}
func (h *PageHandler) Settings(c *gin.Context) {
diff --git a/main.go b/main.go
index bfd6457..33135db 100644
--- a/main.go
+++ b/main.go
@@ -103,8 +103,6 @@ func main() {
// Page routes
r.GET("/", authMw, pages.Index)
r.GET("/repo/:id", authMw, pages.Repo)
- r.GET("/repo/:id/generate", authMw, pages.Generate)
- r.GET("/repo/:id/review", authMw, pages.Review)
r.GET("/settings", authMw, pages.Settings)
// API routes
diff --git a/templates/pages/generate.html b/templates/pages/generate.html
deleted file mode 100644
index b485e04..0000000
--- a/templates/pages/generate.html
+++ /dev/null
@@ -1,255 +0,0 @@
-
-
-
- {{template "head" .}}
-
-
-
-
-
- {{template "nav" .}}
-
-
- 生成 PR 描述
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{template "footer" .}}
-
-
-
-
diff --git a/templates/pages/repo.html b/templates/pages/repo.html
index c1dd2e8..bcab9b5 100644
--- a/templates/pages/repo.html
+++ b/templates/pages/repo.html
@@ -12,6 +12,7 @@
+
@@ -60,14 +103,6 @@
仓库详情
-
@@ -131,6 +166,12 @@
+
+
@@ -158,13 +199,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
设为 0 分析全部文件
+
+
+
+
+
同时分析的文件数 (1-20)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
📝 整体备注
+
+
+ 💬 备注 (Markdown)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
代码变更 & 审查建议
+
+
+
+
+
+
+
+
+
+
+
@@ -175,6 +350,7 @@
const repoId = '{{.ID}}';
let currentRefs = [];
+ // ── Init ────────────────────────────────────────────────────────
async function init() {
try {
// Load repo info
@@ -184,8 +360,7 @@
const repo = repos.find(r => r.id == repoId || r.id === parseInt(repoId));
if (repo) {
document.getElementById('repo-url').textContent = repo.url;
- document.getElementById('btn-generate').href = `/repo/${repoId}/generate`;
- document.getElementById('btn-review').href = `/repo/${repoId}/review`;
+ document.getElementById('print-repo-url').textContent = repo.url;
} else {
showError('仓库未找到 (ID: ' + repoId + ')');
return;
@@ -205,7 +380,6 @@
} catch (e) { /* ignore */ }
populateRefSelects(currentRefs, commits);
- updateButtonLinks();
}
// Init graph with click-to-select
@@ -215,6 +389,9 @@
// Init diff viewer
DiffViewer.init('diff-container');
+ // Load review history
+ loadHistoryList();
+
// Show main content
document.getElementById('loading').classList.add('hidden');
document.getElementById('main-content').classList.remove('hidden');
@@ -231,6 +408,8 @@
document.getElementById('error-message').textContent = message;
}
+ // ── Ref Selects ─────────────────────────────────────────────────
+
function populateRefSelects(refs, commits) {
const selects = ['select-base', 'select-head'];
const branches = refs.filter(r => !r.is_tag);
@@ -309,10 +488,8 @@
if (baseHash) {
const val = baseRef ? baseRef.name : baseHash;
- // Check if value exists as an option, add if not
ensureOption(baseSelect, val, baseRef ? baseRef.name : GitGraph.getRefName(baseHash));
baseSelect.value = val;
-
document.getElementById('sel-base-badge').textContent = baseRef ? baseRef.name : GitGraph.getRefName(baseHash);
} else {
baseSelect.value = '';
@@ -322,7 +499,6 @@
const val = headRef ? headRef.name : headHash;
ensureOption(headSelect, val, headRef ? headRef.name : GitGraph.getRefName(headHash));
headSelect.value = val;
-
document.getElementById('sel-head-badge').textContent = headRef ? headRef.name : GitGraph.getRefName(headHash);
} else {
headSelect.value = '';
@@ -342,8 +518,6 @@
showTab('diff');
loadDiff();
}
-
- updateButtonLinks();
}
// Ensure a select has an option with given value
@@ -370,38 +544,30 @@
if (baseHash || headHash) {
GitGraph.setSelection(baseHash || null, headHash || null);
}
- updateButtonLinks();
};
baseSelect.addEventListener('change', sync);
headSelect.addEventListener('change', sync);
}
- function updateButtonLinks() {
- const base = document.getElementById('select-base').value;
- const head = document.getElementById('select-head').value;
- const params = new URLSearchParams();
- if (base) params.set('base', base);
- if (head) params.set('head', head);
- const qs = params.toString();
- const suffix = qs ? '?' + qs : '';
- document.getElementById('btn-generate').href = `/repo/${repoId}/generate${suffix}`;
- document.getElementById('btn-review').href = `/repo/${repoId}/review${suffix}`;
- }
+ // ── Tab Navigation ──────────────────────────────────────────────
+
+ const TABS = ['graph', 'diff', 'generate', 'review'];
+ const TAB_STYLES = {
+ active: 'border-blue-500 text-blue-600',
+ inactive: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300',
+ };
function showTab(tab) {
- document.getElementById('panel-graph').classList.toggle('hidden', tab !== 'graph');
- document.getElementById('panel-diff').classList.toggle('hidden', tab !== 'diff');
- document.getElementById('tab-graph').classList.toggle('border-blue-500', tab === 'graph');
- document.getElementById('tab-graph').classList.toggle('text-blue-600', tab === 'graph');
- document.getElementById('tab-graph').classList.toggle('border-transparent', tab !== 'graph');
- document.getElementById('tab-graph').classList.toggle('text-gray-500', tab !== 'graph');
- document.getElementById('tab-diff').classList.toggle('border-blue-500', tab === 'diff');
- document.getElementById('tab-diff').classList.toggle('text-blue-600', tab === 'diff');
- document.getElementById('tab-diff').classList.toggle('border-transparent', tab !== 'diff');
- document.getElementById('tab-diff').classList.toggle('text-gray-500', tab !== 'diff');
+ TABS.forEach(t => {
+ document.getElementById('panel-' + t).classList.toggle('hidden', t !== tab);
+ const tabBtn = document.getElementById('tab-' + t);
+ tabBtn.className = `px-6 py-3 text-sm font-medium border-b-2 ${t === tab ? TAB_STYLES.active : TAB_STYLES.inactive}`;
+ });
}
+ // ── Diff ────────────────────────────────────────────────────────
+
function loadDiff() {
const base = document.getElementById('select-base').value;
const head = document.getElementById('select-head').value;
@@ -415,7 +581,6 @@
function setDiffView(mode) {
DiffViewer.setViewMode(mode);
- // Update button styles
const btnSide = document.getElementById('btn-side-by-side');
const btnUnified = document.getElementById('btn-unified');
@@ -428,7 +593,8 @@
}
}
- // ── Utilities ────────────────────────────────────────────────
+ // ── Utilities ───────────────────────────────────────────────────
+
function escapeHtml(text) {
if (!text) return '';
return String(text)
@@ -456,7 +622,11 @@
return html;
}
- // ── Inline AI Review ────────────────────────────────────────
+ function renderMarkdown(text) {
+ return Markdown.render(text);
+ }
+
+ // ── Inline AI Review (quick review in Diff tab) ─────────────────
let inlineReviewSSE = null;
let inlineSuggestions = [];
@@ -509,7 +679,7 @@
if (data.score !== undefined) {
el.innerHTML = renderSummaryHtml(data);
} else {
- el.innerHTML = renderInlineMarkdown(data.content || '');
+ el.innerHTML = renderMarkdown(data.content || '');
}
document.getElementById('review-progress-bar').style.width = '90%';
},
@@ -529,8 +699,7 @@
inlineReviewSSE = null;
},
analysis_saved(data) {
- const viewLink = document.getElementById('btn-view-full-review');
- viewLink.href = `/repo/${repoId}/review?base=${encodeURIComponent(base)}&head=${encodeURIComponent(head)}`;
+ // Update "查看完整审查" link behavior
},
error(data) {
showToast('审查失败: ' + (data.message || '未知错误'), 'error');
@@ -578,7 +747,6 @@
}
function clearInlineReview() {
- // Remove all suggestion elements from the diff
const container = document.getElementById('diff-container');
container.querySelectorAll('.file-suggestions-container').forEach(el => el.remove());
container.querySelectorAll('.file-tree-severity').forEach(el => el.remove());
@@ -586,8 +754,416 @@
inlineSuggestions = [];
}
- function renderInlineMarkdown(text) {
- return Markdown.render(text);
+ // ── Generate PR Description ─────────────────────────────────────
+
+ let markdownContent = '';
+ let generateSSE = null;
+
+ function generatePR() {
+ const base = document.getElementById('select-base').value;
+ const head = document.getElementById('select-head').value;
+
+ if (!base || !head) {
+ showToast('请选择 Base 和 Head 分支', 'warning');
+ return;
+ }
+
+ // Cancel previous if running
+ if (generateSSE) {
+ generateSSE.abort();
+ }
+
+ const btn = document.getElementById('btn-generate');
+ const output = document.getElementById('generate-output');
+ const indicator = document.getElementById('generating-indicator');
+
+ btn.disabled = true;
+ btn.textContent = '生成中...';
+ btn.classList.add('opacity-70', 'cursor-not-allowed');
+ output.classList.remove('hidden');
+ indicator.classList.remove('hidden');
+
+ // Clear previous results
+ document.getElementById('pr-rendered').innerHTML = '';
+ document.getElementById('pr-markdown').textContent = '';
+ markdownContent = '';
+
+ generateSSE = SSE.post(`/api/repos/${repoId}/generate`, {
+ base, head,
+ }, {
+ content(data) {
+ markdownContent += data.content || '';
+ document.getElementById('pr-rendered').innerHTML = Markdown.render(markdownContent);
+ document.getElementById('pr-markdown').textContent = markdownContent;
+ },
+ done() {
+ indicator.classList.add('hidden');
+ btn.disabled = false;
+ btn.textContent = '生成 PR 描述';
+ btn.classList.remove('opacity-70', 'cursor-not-allowed');
+ generateSSE = null;
+ },
+ error(data) {
+ showToast('生成失败: ' + (data.message || '未知错误'), 'error');
+ indicator.classList.add('hidden');
+ btn.disabled = false;
+ btn.textContent = '生成 PR 描述';
+ btn.classList.remove('opacity-70', 'cursor-not-allowed');
+ generateSSE = null;
+ },
+ });
+ }
+
+ function copyMarkdown() {
+ const markdown = markdownContent || document.getElementById('pr-markdown').textContent;
+ if (!markdown.trim()) {
+ showToast('没有可复制的内容,请先生成 PR 描述', 'warning');
+ return;
+ }
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ navigator.clipboard.writeText(markdown).then(() => {
+ showToast('已复制到剪贴板', 'success');
+ }).catch(() => {
+ fallbackCopy(markdown);
+ });
+ } else {
+ fallbackCopy(markdown);
+ }
+ }
+
+ function fallbackCopy(text) {
+ const textarea = document.createElement('textarea');
+ textarea.value = text;
+ textarea.style.cssText = 'position:fixed;left:0;top:0;opacity:0;';
+ document.body.appendChild(textarea);
+ textarea.focus();
+ textarea.select();
+ try {
+ const ok = document.execCommand('copy');
+ showToast(ok ? '已复制到剪贴板' : '复制失败,请手动选择文本复制', ok ? 'success' : 'error');
+ } catch (e) {
+ showToast('复制失败,请手动选择文本复制', 'error');
+ }
+ document.body.removeChild(textarea);
+ }
+
+ // ── Full AI Code Review ─────────────────────────────────────────
+
+ let currentSSE = null;
+ let diffVisible = true;
+ let allSuggestions = [];
+ let fileReviews = {};
+ let currentAnalysisId = null;
+
+ function startReview() {
+ const base = document.getElementById('select-base').value;
+ const head = document.getElementById('select-head').value;
+ const topN = document.getElementById('top-n').value;
+
+ if (!base || !head) {
+ showToast('请选择 Base 和 Head 分支', 'warning');
+ return;
+ }
+
+ // Abort any previous stream
+ if (currentSSE) {
+ currentSSE.abort();
+ }
+
+ const btn = document.getElementById('btn-review');
+ const progress = document.getElementById('review-progress-full');
+ const results = document.getElementById('review-results');
+
+ btn.disabled = true;
+ btn.textContent = '审查中...';
+ progress.classList.remove('hidden');
+ results.classList.add('hidden');
+
+ // 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;
+ 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`, {
+ base, head,
+ top_n: parseInt(topN) || 0,
+ concurrency: parseInt(concurrency) || 5,
+ }, {
+ start(data) {
+ totalFiles = data.total_files || 0;
+ document.getElementById('review-progress-text-full').textContent = `准备审查 ${totalFiles} 个文件...`;
+ document.getElementById('review-progress-bar-full').style.width = '5%';
+ },
+ file_start(data) {
+ startedFiles++;
+ const currentFile = data.file || '';
+ document.getElementById('review-progress-text-full').textContent = `正在审查: ${currentFile} (${startedFiles}/${totalFiles})`;
+
+ if (!fileReviews[currentFile]) {
+ fileReviews[currentFile] = { filename: currentFile, suggestions: [], summary: '' };
+ }
+ },
+ suggestion(data) {
+ allSuggestions.push(data);
+
+ if (data.file && fileReviews[data.file]) {
+ fileReviews[data.file].suggestions.push(data);
+ }
+
+ appendSuggestionCard(data);
+ },
+ file_end(data) {
+ completedFiles++;
+ updateProgress();
+ document.getElementById('review-progress-text-full').textContent = `已完成 ${completedFiles}/${totalFiles} 个文件`;
+ },
+ file_summary(data) {
+ if (data.file && fileReviews[data.file]) {
+ fileReviews[data.file].summary = data.content || '';
+ }
+ },
+ diff(data) {
+ if (data.diff) {
+ window._reviewDiff = data.diff;
+ }
+ },
+ summary(data) {
+ const el = document.getElementById('summary-content');
+ if (data.score !== undefined) {
+ el.innerHTML = renderSummaryHtml(data);
+ } else {
+ el.innerHTML = renderMarkdown(data.content || '');
+ }
+ document.getElementById('review-progress-bar-full').style.width = '90%';
+ document.getElementById('review-progress-text-full').textContent = '生成总结...';
+ },
+ done() {
+ document.getElementById('review-progress-bar-full').style.width = '100%';
+ document.getElementById('review-progress-text-full').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
+ if (window._reviewDiff) {
+ renderInlineReview(window._reviewDiff);
+ }
+
+ currentSSE = null;
+ },
+ analysis_saved(data) {
+ currentAnalysisId = data.analysis_id;
+ NoteEditor.init(repoId, currentAnalysisId);
+ NoteEditor.loadNotes();
+ // Refresh history list
+ loadHistoryList();
+ },
+ error(data) {
+ showToast('审查失败: ' + (data.message || '未知错误'), 'error');
+ progress.classList.add('hidden');
+ btn.disabled = false;
+ btn.textContent = '开始审查';
+ currentSSE = null;
+ },
+ });
+ }
+
+ function appendSuggestionCard(suggestion) {
+ const container = document.getElementById('file-reviews');
+
+ let fileSection = document.getElementById('file-review-' + cssId(suggestion.file));
+ if (!fileSection) {
+ fileSection = document.createElement('div');
+ fileSection.id = 'file-review-' + cssId(suggestion.file);
+ fileSection.className = 'bg-white rounded-lg shadow-md border-l-4 border-gray-200 mb-4';
+ fileSection.innerHTML = `
+
+
${suggestion.file || ''}
+
+
+ `;
+ container.appendChild(fileSection);
+ }
+
+ // Update border color based on max severity
+ const severities = { critical: 0, warning: 1, info: 2 };
+ const currentMax = fileSection.getAttribute('data-max-severity') || 'info';
+ if ((severities[suggestion.severity] || 2) < (severities[currentMax] || 2)) {
+ fileSection.setAttribute('data-max-severity', suggestion.severity);
+ const colors = { critical: 'border-red-500', warning: 'border-yellow-500', info: 'border-green-500' };
+ fileSection.className = fileSection.className.replace(/border-\w+-500/, '') + ' ' + (colors[suggestion.severity] || colors.info);
+ }
+
+ const sugContainer = fileSection.querySelector('.suggestions-container');
+ const card = document.createElement('div');
+ card.innerHTML = ReviewInline.renderSuggestionCard(suggestion);
+ sugContainer.appendChild(card.firstElementChild);
+ }
+
+ function cssId(str) {
+ 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');
+ const baseText = baseEl?.options[baseEl.selectedIndex]?.text || baseEl?.value || '';
+ const headText = headEl?.options[headEl.selectedIndex]?.text || headEl?.value || '';
+ document.querySelectorAll('.print-base-ref').forEach(el => el.textContent = baseText);
+ document.querySelectorAll('.print-head-ref').forEach(el => el.textContent = headText);
+ document.querySelectorAll('.print-date').forEach(el => el.textContent = new Date().toLocaleString('zh-CN'));
+ }
+
+ // ── History ─────────────────────────────────────────────────────
+
+ async function loadHistoryList() {
+ try {
+ const resp = await fetch(`/api/repos/${repoId}/review/analyses`, { credentials: 'same-origin' });
+ if (!resp.ok) return;
+ const analyses = await resp.json();
+
+ const select = document.getElementById('history-select');
+ select.innerHTML = '';
+
+ 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 = '';
+ }
+ } catch (e) {
+ console.warn('Failed to load history:', e);
+ }
+ }
+
+ async function loadHistoryReview(analysisId) {
+ if (!analysisId) return;
+
+ try {
+ const resp = await fetch(`/api/repos/${repoId}/review/analyses/${analysisId}`, { credentials: 'same-origin' });
+ if (!resp.ok) throw new Error('加载评审数据失败');
+ const data = await resp.json();
+
+ const result = data.result;
+ if (!result || !result.file_reviews) {
+ showToast('该历史记录没有完整的评审数据', 'warning');
+ return;
+ }
+
+ 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 || {};
+ document.getElementById('summary-content').innerHTML = renderSummaryHtml(summary);
+
+ // Clear and rebuild card view
+ document.getElementById('file-reviews').innerHTML = '';
+ allSuggestions.forEach(s => appendSuggestionCard(s));
+
+ // Show results
+ document.getElementById('review-results').classList.remove('hidden');
+ document.getElementById('review-actions').classList.remove('hidden');
+ updatePrintHeader();
+
+ // Load diff and render inline
+ const base = data.base_ref;
+ const head = data.head_ref;
+
+ const diffResp = await fetch(`/api/repos/${repoId}/diff?base=${encodeURIComponent(base)}&head=${encodeURIComponent(head)}`, { credentials: 'same-origin' });
+ 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);
+ showToast('加载历史评审失败: ' + e.message, 'error');
+ }
}
init();
diff --git a/templates/pages/review.html b/templates/pages/review.html
deleted file mode 100644
index 8a8eafb..0000000
--- a/templates/pages/review.html
+++ /dev/null
@@ -1,665 +0,0 @@
-
-
-
- {{template "head" .}}
-
-
-
-
-
-
-
-
-
-
-
-
- {{template "nav" .}}
-
-
- AI 代码审查
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
设为 0 分析全部文件
-
-
-
-
-
同时分析的文件数 (1-20)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
📝 整体备注
-
-
- 💬 备注 (Markdown)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
代码变更 & 审查建议
-
-
-
-
-
-
-
-
-
-
-
-
- {{template "footer" .}}
-
-
-
-