46 lines
1.3 KiB
JavaScript
46 lines
1.3 KiB
JavaScript
/**
|
|
* Detect and collapse degenerate generation loops. No Bare imports.
|
|
*
|
|
* Small Qwen runs with no predict cap will repeat a clause until the idle
|
|
* timeout. Catch the repeating tail and stop the stream.
|
|
*/
|
|
|
|
function repeatingUnit(text, minLen, copies) {
|
|
const s = String(text || '');
|
|
const min = minLen > 0 ? minLen : 24;
|
|
const need = copies > 0 ? copies : 3;
|
|
if (s.length < min * need) return '';
|
|
const window = s.length > 2400 ? s.slice(-2400) : s;
|
|
const maxLen = Math.min(180, Math.floor(window.length / need));
|
|
for (let len = min; len <= maxLen; len++) {
|
|
const unit = window.slice(-len);
|
|
if (unit.replace(/\s+/g, '').length < 12) continue;
|
|
let matched = true;
|
|
for (let i = 2; i <= need; i++) {
|
|
if (window.slice(-len * i, -len * (i - 1)) !== unit) {
|
|
matched = false;
|
|
break;
|
|
}
|
|
}
|
|
if (matched) return unit;
|
|
}
|
|
return '';
|
|
}
|
|
|
|
function isRepeating(text) {
|
|
return !!repeatingUnit(text);
|
|
}
|
|
|
|
function escapeRe(value) {
|
|
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
}
|
|
|
|
function collapseRepeats(text) {
|
|
const s = String(text || '');
|
|
const unit = repeatingUnit(s);
|
|
if (!unit) return s.trim();
|
|
return s.replace(new RegExp('(?:' + escapeRe(unit) + '){3,}$'), unit).trim();
|
|
}
|
|
|
|
module.exports = { repeatingUnit, isRepeating, collapseRepeats };
|