增加强制插入功能和NoAss标记

This commit is contained in:
Air
2026-01-19 15:23:43 +08:00
parent 47eccd0e7a
commit 844707e16d
3 changed files with 43 additions and 19 deletions

View File

@@ -6,8 +6,7 @@
"js": "index.js",
"css": "style.css",
"author": "biex",
"version": "2.4.0",
"homePage": "https://github.com/RT15548/LittleWhiteBox"
,
"version": "2.4.4",
"homePage": "https://github.com/EVA09/LittleWhiteBox",
"generate_interceptor": "xiaobaixGenerateInterceptor"
}
}

View File

@@ -1576,20 +1576,34 @@
<option value="150">150</option>
<option value="200">200</option>
</select></div>
</select>
</div>
<div class="settings-row">
<div class="settings-field-inline"><input type="checkbox" id="trigger-enabled"><label
for="trigger-enabled">启用自动总结</label></div>
<div class="settings-field-inline"><input type="checkbox" id="trigger-stream" checked><label
for="trigger-stream">启用流式生成</label></div>
</div>
<div class="settings-hint" style="margin-top:8px">若 API 不支持非流式请求,请勾选"启用流式生成"</div>
</div>
<div class="settings-row">
<div class="settings-field full"><label>头部包裹词应对NoAss配置</label><input type="text"
id="trigger-wrapper-head" placeholder="添加到开头"></div>
</div>
<div class="settings-row">
<div class="settings-field full"><label>尾部包裹词应对NoAss配置</label><input type="text"
id="trigger-wrapper-tail" placeholder="添加到结尾"></div>
</div>
<div class="settings-row">
<div class="settings-field-inline"><input type="checkbox" id="trigger-enabled"><label
for="trigger-enabled">启用自动总结</label></div>
<div class="settings-field-inline"><input type="checkbox" id="trigger-stream" checked><label
for="trigger-stream">启用流式生成</label></div>
<div class="settings-field-inline"><input type="checkbox" id="trigger-insert-at-end"><label
for="trigger-insert-at-end">强制插入到聊天最后</label></div>
</div>
<div class="settings-hint" style="margin-top:8px">若 API 不支持非流式请求,请勾选"启用流式生成"</div>
</div>
<div class="modal-foot">
<button class="btn" id="settings-cancel">取消</button><button class="btn btn-p"
id="settings-save">保存</button>
</div>
<div class="modal-foot"><button class="btn" id="settings-cancel">取消</button><button class="btn btn-p"
id="settings-save">保存</button></div>
</div>
</div>
</div>
<div class="modal fullscreen" id="rel-fs-modal">
<div class="modal-bg" id="rel-fs-backdrop"></div>
@@ -1612,7 +1626,7 @@
const $ = id => document.getElementById(id), $$ = sel => document.querySelectorAll(sel);
const escapeHtml = (v) => String(v ?? "").replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", "\"": "&quot;", "'": "&#39;" })[c]);
const h = (v) => escapeHtml(v);
const config = { api: { provider: 'st', url: '', key: '', model: '', modelCache: [] }, gen: { temperature: null, top_p: null, top_k: null, presence_penalty: null, frequency_penalty: null }, trigger: { enabled: false, interval: 20, timing: 'after_ai', useStream: true, maxPerRun: 100 } };
const config = { api: { provider: 'st', url: '', key: '', model: '', modelCache: [] }, gen: { temperature: null, top_p: null, top_k: null, presence_penalty: null, frequency_penalty: null }, trigger: { enabled: false, interval: 20, timing: 'after_ai', useStream: true, maxPerRun: 100, wrapperHead: '', wrapperTail: '', forceInsertAtEnd: false } };
let summaryData = { keywords: [], events: [], characters: { main: [], relationships: [] }, arcs: [] }, localGenerating = false, relationChart = null, relationChartFullscreen = null, currentEditSection = null, currentCharacterId = null, allNodes = [], allLinks = [], activeRelationTooltip = null;
const providerDefaults = { st: { url: '', needKey: false, canFetch: false, needManualModel: false }, openai: { url: 'https://api.openai.com', needKey: true, canFetch: true, needManualModel: false }, google: { url: 'https://generativelanguage.googleapis.com', needKey: true, canFetch: false, needManualModel: true }, claude: { url: 'https://api.anthropic.com', needKey: true, canFetch: false, needManualModel: true }, deepseek: { url: 'https://api.deepseek.com', needKey: true, canFetch: true, needManualModel: false }, cohere: { url: 'https://api.cohere.ai', needKey: true, canFetch: false, needManualModel: true }, custom: { url: '', needKey: true, canFetch: true, needManualModel: false } };
@@ -1690,8 +1704,8 @@
function saveEditor() { const section = currentEditSection, es = $('editor-struct'), ta = $('editor-ta'); let parsed; try { if (section === 'keywords') { const oldMap = new Map((summaryData.keywords || []).map(k => [k.text, k])); parsed = ta.value.trim().split('\n').filter(l => l.trim()).map(line => { const [text, weight] = line.split('|').map(s => s.trim()); return preserveAddedAt({ text: text || '', weight: weight || '一般' }, oldMap.get(text)) }) } else if (section === 'events') { const oldMap = new Map((summaryData.events || []).map(e => [e.id, e])); parsed = Array.from(es.querySelectorAll('.event-item')).map(it => { const id = it.dataset.id; return preserveAddedAt({ id, title: it.querySelector('.event-title').value.trim(), timeLabel: it.querySelector('.event-time').value.trim(), summary: it.querySelector('.event-summary').value.trim(), participants: it.querySelector('.event-participants').value.trim().split(/[,、,]/).map(s => s.trim()).filter(Boolean), type: it.querySelector('.event-type').value, weight: it.querySelector('.event-weight').value }, oldMap.get(id)) }).filter(e => e.title || e.summary) } else if (section === 'characters') { const oldMainMap = new Map((summaryData.characters?.main || []).map(m => [getCharName(m), m])), mainNames = Array.from(es.querySelectorAll('.char-main-name')).map(i => i.value.trim()).filter(Boolean), main = mainNames.map(n => preserveAddedAt({ name: n }, oldMainMap.get(n))); const oldRelMap = new Map((summaryData.characters?.relationships || []).map(r => [`${r.from}->${r.to}`, r])), rels = Array.from(es.querySelectorAll('.char-rel-item')).map(it => { const from = it.querySelector('.char-rel-from').value.trim(), to = it.querySelector('.char-rel-to').value.trim(); return preserveAddedAt({ from, to, label: it.querySelector('.char-rel-label').value.trim(), trend: it.querySelector('.char-rel-trend').value }, oldRelMap.get(`${from}->${to}`)) }).filter(r => r.from && r.to); parsed = { main, relationships: rels } } else if (section === 'arcs') { const oldArcMap = new Map((summaryData.arcs || []).map(a => [a.name, a])); parsed = Array.from(es.querySelectorAll('.arc-item')).map(it => { const name = it.querySelector('.arc-name').value.trim(), oldArc = oldArcMap.get(name), oldMomentMap = new Map((oldArc?.moments || []).map(m => [typeof m === 'string' ? m : m.text, m])), momentsRaw = it.querySelector('.arc-moments').value.trim(), moments = momentsRaw ? momentsRaw.split('\n').map(s => s.trim()).filter(Boolean).map(t => preserveAddedAt({ text: t }, oldMomentMap.get(t))) : []; return preserveAddedAt({ name, trajectory: it.querySelector('.arc-trajectory').value.trim(), progress: Math.max(0, Math.min(1, (parseFloat(it.querySelector('.arc-progress').value) || 0) / 100)), moments }, oldArc) }).filter(a => a.name || a.trajectory || a.moments?.length) } } catch (e) { $('editor-err').textContent = `格式错误: ${e.message}`; $('editor-err').classList.add('visible'); return } postMsg('UPDATE_SECTION', { section, data: parsed }); if (section === 'keywords') renderKeywords(parsed); else if (section === 'events') { renderTimeline(parsed); $('stat-events').textContent = parsed.length } else if (section === 'characters') renderRelations(parsed); else if (section === 'arcs') renderArcs(parsed); closeEditor() }
function updateProviderUI(provider) { const pv = providerDefaults[provider] || providerDefaults.custom, isSt = provider === 'st'; $('api-url-row').classList.toggle('hidden', isSt); $('api-key-row').classList.toggle('hidden', !pv.needKey); $('api-model-manual-row').classList.toggle('hidden', isSt || !pv.needManualModel); $('api-model-select-row').classList.toggle('hidden', isSt || pv.needManualModel || !config.api.modelCache.length); $('api-connect-row').classList.toggle('hidden', isSt || !pv.canFetch); const urlInput = $('api-url'); if (!urlInput.value && pv.url) urlInput.value = pv.url }
function openSettings() { const pn = id => { const v = $(id).value; return v === '' ? null : parseFloat(v) }; $('api-provider').value = config.api.provider; $('api-url').value = config.api.url; $('api-key').value = config.api.key; $('api-model-text').value = config.api.model; $('gen-temp').value = config.gen.temperature ?? ''; $('gen-top-p').value = config.gen.top_p ?? ''; $('gen-top-k').value = config.gen.top_k ?? ''; $('gen-presence').value = config.gen.presence_penalty ?? ''; $('gen-frequency').value = config.gen.frequency_penalty ?? ''; $('trigger-enabled').checked = config.trigger.enabled; $('trigger-interval').value = config.trigger.interval; $('trigger-timing').value = config.trigger.timing; $('trigger-stream').checked = config.trigger.useStream !== false; $('trigger-max-per-run').value = config.trigger.maxPerRun || 100; const en = $('trigger-enabled'); if (config.trigger.timing === 'manual') { en.checked = false; en.disabled = true; en.parentElement.style.opacity = '.5' } else { en.disabled = false; en.parentElement.style.opacity = '1' } if (config.api.modelCache.length) { $('api-model-select').innerHTML = config.api.modelCache.map(m => `<option value="${m}"${m === config.api.model ? ' selected' : ''}>${m}</option>`).join('') } updateProviderUI(config.api.provider); $('settings-modal').classList.add('active'); postMsg('SETTINGS_OPENED') }
function closeSettings(save) { if (save) { const pn = id => { const v = $(id).value; return v === '' ? null : parseFloat(v) }; const provider = $('api-provider').value, pv = providerDefaults[provider] || providerDefaults.custom; config.api.provider = provider; config.api.url = $('api-url').value; config.api.key = $('api-key').value; config.api.model = provider === 'st' ? '' : pv.needManualModel ? $('api-model-text').value : $('api-model-select').value; config.gen.temperature = pn('gen-temp'); config.gen.top_p = pn('gen-top-p'); config.gen.top_k = pn('gen-top-k'); config.gen.presence_penalty = pn('gen-presence'); config.gen.frequency_penalty = pn('gen-frequency'); const timing = $('trigger-timing').value; config.trigger.timing = timing; config.trigger.enabled = timing === 'manual' ? false : $('trigger-enabled').checked; config.trigger.interval = parseInt($('trigger-interval').value) || 20; config.trigger.useStream = $('trigger-stream').checked; config.trigger.maxPerRun = parseInt($('trigger-max-per-run').value) || 100; saveConfig() } $('settings-modal').classList.remove('active'); postMsg('SETTINGS_CLOSED') }
function openSettings() { const pn = id => { const v = $(id).value; return v === '' ? null : parseFloat(v) }; $('api-provider').value = config.api.provider; $('api-url').value = config.api.url; $('api-key').value = config.api.key; $('api-model-text').value = config.api.model; $('gen-temp').value = config.gen.temperature ?? ''; $('gen-top-p').value = config.gen.top_p ?? ''; $('gen-top-k').value = config.gen.top_k ?? ''; $('gen-presence').value = config.gen.presence_penalty ?? ''; $('gen-frequency').value = config.gen.frequency_penalty ?? ''; $('trigger-enabled').checked = config.trigger.enabled; $('trigger-interval').value = config.trigger.interval; $('trigger-timing').value = config.trigger.timing; $('trigger-stream').checked = config.trigger.useStream !== false; $('trigger-max-per-run').value = config.trigger.maxPerRun || 100; $('trigger-wrapper-head').value = config.trigger.wrapperHead || ''; $('trigger-wrapper-tail').value = config.trigger.wrapperTail || ''; $('trigger-insert-at-end').checked = !!config.trigger.forceInsertAtEnd; const en = $('trigger-enabled'); if (config.trigger.timing === 'manual') { en.checked = false; en.disabled = true; en.parentElement.style.opacity = '.5' } else { en.disabled = false; en.parentElement.style.opacity = '1' } if (config.api.modelCache.length) { $('api-model-select').innerHTML = config.api.modelCache.map(m => `<option value="${m}"${m === config.api.model ? ' selected' : ''}>${m}</option>`).join('') } updateProviderUI(config.api.provider); $('settings-modal').classList.add('active'); postMsg('SETTINGS_OPENED') }
function closeSettings(save) { if (save) { const pn = id => { const v = $(id).value; return v === '' ? null : parseFloat(v) }; const provider = $('api-provider').value, pv = providerDefaults[provider] || providerDefaults.custom; config.api.provider = provider; config.api.url = $('api-url').value; config.api.key = $('api-key').value; config.api.model = provider === 'st' ? '' : pv.needManualModel ? $('api-model-text').value : $('api-model-select').value; config.gen.temperature = pn('gen-temp'); config.gen.top_p = pn('gen-top-p'); config.gen.top_k = pn('gen-top-k'); config.gen.presence_penalty = pn('gen-presence'); config.gen.frequency_penalty = pn('gen-frequency'); const timing = $('trigger-timing').value; config.trigger.timing = timing; config.trigger.enabled = timing === 'manual' ? false : $('trigger-enabled').checked; config.trigger.interval = parseInt($('trigger-interval').value) || 20; config.trigger.useStream = $('trigger-stream').checked; config.trigger.maxPerRun = parseInt($('trigger-max-per-run').value) || 100; config.trigger.wrapperHead = $('trigger-wrapper-head').value; config.trigger.wrapperTail = $('trigger-wrapper-tail').value; config.trigger.forceInsertAtEnd = $('trigger-insert-at-end').checked; saveConfig() } $('settings-modal').classList.remove('active'); postMsg('SETTINGS_CLOSED') }
async function fetchModels() { const btn = $('btn-connect'), provider = $('api-provider').value; if (!providerDefaults[provider]?.canFetch) { alert('当前渠道不支持自动拉取模型'); return } let baseUrl = $('api-url').value.trim().replace(/\/+$/, ''); const apiKey = $('api-key').value.trim(); if (!apiKey) { alert('请先填写 API KEY'); return } btn.disabled = true; btn.textContent = '连接中...'; try { const tryFetch = async url => { const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}`, Accept: 'application/json' } }); return res.ok ? (await res.json())?.data?.map(m => m?.id).filter(Boolean) || null : null }; if (baseUrl.endsWith('/v1')) baseUrl = baseUrl.slice(0, -3); let models = await tryFetch(`${baseUrl}/v1/models`); if (!models) models = await tryFetch(`${baseUrl}/models`); if (!models?.length) throw new Error('未获取到模型列表'); config.api.modelCache = [...new Set(models)]; const sel = $('api-model-select'); sel.innerHTML = config.api.modelCache.map(m => `<option value="${m}">${m}</option>`).join(''); $('api-model-select-row').classList.remove('hidden'); if (!config.api.model && models.length) { config.api.model = models[0]; sel.value = models[0] } else if (config.api.model) sel.value = config.api.model; saveConfig(); alert(`成功获取 ${models.length} 个模型`) } catch (e) { alert('连接失败:' + (e.message || '请检查 URL 和 KEY')) } finally { btn.disabled = false; btn.textContent = '连接 / 拉取模型列表' } }
$$('.sec-btn[data-section]').forEach(b => b.onclick = () => openEditor(b.dataset.section));

View File

@@ -926,7 +926,15 @@ function updateSummaryExtensionPrompt() {
return;
}
const text = formatSummaryForPrompt(store);
const cfg = getSummaryPanelConfig();
let text = formatSummaryForPrompt(store);
if (cfg.trigger?.wrapperHead) {
text = cfg.trigger.wrapperHead + '\n' + text;
}
if (cfg.trigger?.wrapperTail) {
text = text + '\n' + cfg.trigger.wrapperTail;
}
if (!text.trim()) {
delete extension_prompts[SUMMARY_PROMPT_KEY];
return;
@@ -941,7 +949,10 @@ function updateSummaryExtensionPrompt() {
let depth = length - lastIdx - 1;
if (depth < 0) depth = 0;
depth = 1000;
if (cfg.trigger?.forceInsertAtEnd) {
depth = 10000;
}
extension_prompts[SUMMARY_PROMPT_KEY] = {
value: text,
position: extension_prompt_types.IN_CHAT,
@@ -1026,7 +1037,7 @@ function registerEvents() {
name: '待发送消息队列',
getSize: () => pendingFrameMessages.length,
getBytes: () => {
try { return JSON.stringify(pendingFrameMessages || []).length * 2; }
try { return JSON.stringify(pendingFrameMessages || []).length * 2; }
catch { return 0; }
},
clear: () => {