diff --git a/static/js/diff-viewer.js b/static/js/diff-viewer.js
index 79016bd..b75e107 100644
--- a/static/js/diff-viewer.js
+++ b/static/js/diff-viewer.js
@@ -199,6 +199,34 @@ const DiffViewer = {
}
},
+ // ── Diff too large guard ────────────────────────────────────────
+
+ _isTooLarge(files) {
+ const MAX_FILES = 300;
+ const MAX_LINES = 10000;
+ const totalAdd = files.reduce((s, f) => s + (f.additions || 0), 0);
+ const totalDel = files.reduce((s, f) => s + (f.deletions || 0), 0);
+ return files.length > MAX_FILES || (totalAdd + totalDel) > MAX_LINES;
+ },
+
+ _renderTooLargeWarning(files, totalAdd, totalDel, onExpand) {
+ const id = 'diff-expand-' + Math.random().toString(36).slice(2, 8);
+ this.container.innerHTML = `
+
+
⚠️ 变更过大,已跳过渲染
+
+ 共 ${files.length} 个文件,
+ +${totalAdd}
+ -${totalDel}
+
+
渲染大量变更可能导致浏览器卡顿
+
+
`;
+ document.getElementById(id).addEventListener('click', onExpand);
+ },
+
// ── Incremental rendering pipeline ──────────────────────────────
// Render diff with file tree sidebar — incremental, non-blocking
@@ -213,6 +241,17 @@ const DiffViewer = {
// Parse files for tree (fast — just regex + string ops)
this.files = this._parseDiffFiles(diffString);
+
+ // Guard: if diff is too large, show warning instead of rendering
+ if (!options.forceRender && this._isTooLarge(this.files)) {
+ const totalAdd = this.files.reduce((s, f) => s + f.additions, 0);
+ const totalDel = this.files.reduce((s, f) => s + f.deletions, 0);
+ const self = this;
+ this._renderTooLargeWarning(this.files, totalAdd, totalDel, function () {
+ self.renderDiff(diffString, { ...options, forceRender: true });
+ });
+ return;
+ }
const tree = this._buildTree(this.files);
// Split diff into per-file chunks for incremental rendering
@@ -285,6 +324,17 @@ const DiffViewer = {
const combinedDiff = files.map(f => f.patch).join('\n');
this.rawDiff = combinedDiff;
+ // Guard: if diff is too large, show warning
+ if (!options.forceRender && this._isTooLarge(this.files)) {
+ const totalAdd = this.files.reduce((s, f) => s + f.additions, 0);
+ const totalDel = this.files.reduce((s, f) => s + f.deletions, 0);
+ const self = this;
+ this._renderTooLargeWarning(this.files, totalAdd, totalDel, function () {
+ self.renderFiles(files, { ...options, forceRender: true });
+ });
+ return;
+ }
+
// Split into per-file chunks
this._chunks = this.files.map(f => f._patch).filter(Boolean);
this._chunkIndex = 0;