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;