let state = { settings: { activeProfileId: null, activePresetId: null }, profiles: [], presets: [], personas: [], activePersonaId: null, creationDefaultId: null, }; let editingProfileId = null; let editingPersonaId = null; let activeSection = "api"; let lastImportedPresetId = null; /** presetId → entries API payload */ const expandedPresetEntries = new Map(); /** presetId → 当前展开编辑的 entry id(一次一条) */ const openPresetEntryId = new Map(); const profilesListEl = document.getElementById("profiles-list"); const personasListEl = document.getElementById("personas-list"); const presetsListEl = document.getElementById("presets-list"); const importReportEl = document.getElementById("import-report"); const profileDialog = document.getElementById("profile-dialog"); const profileForm = document.getElementById("profile-form"); const profileDialogTitle = document.getElementById("profile-dialog-title"); const personaDialog = document.getElementById("persona-dialog"); const personaForm = document.getElementById("persona-form"); const personaDialogTitle = document.getElementById("persona-dialog-title"); const presetFileEl = document.getElementById("preset-file"); const panelTitleEl = document.getElementById("panel-title"); const panelSubtitleEl = document.getElementById("panel-subtitle"); const panelActionsEl = document.getElementById("panel-actions"); const sectionApiEl = document.getElementById("section-api"); const sectionPersonaEl = document.getElementById("section-persona"); const sectionPresetEl = document.getElementById("section-preset"); const sectionStorageEl = document.getElementById("section-storage"); const activeSettingsBarEl = document.getElementById("active-settings-bar"); const settingsToastEl = document.getElementById("settings-toast"); const contextTraceKeepEl = document.getElementById("context-trace-keep"); const probeDialog = document.getElementById("preset-probe-dialog"); const probePresetNameEl = document.getElementById("probe-preset-name"); const probeMessageEl = document.getElementById("probe-message"); const probeLoreBeforeEl = document.getElementById("probe-lore-before"); const probeHistoryEl = document.getElementById("probe-history"); const probeLoreAfterEl = document.getElementById("probe-lore-after"); const probePostTurnEl = document.getElementById("probe-post-turn"); const probeSendEl = document.getElementById("probe-send"); const probeStatusEl = document.getElementById("probe-status"); const probeMessagesEl = document.getElementById("probe-messages"); const probeReplyWrapEl = document.getElementById("probe-reply-wrap"); const probeReplyEl = document.getElementById("probe-reply"); const DEFAULT_PROBE_CONTEXT = { message: "我推开门。", loreBefore: "写作要求:对本轮输入做忠实扩写;只展开已给出的动作与信息,不另起情节。", history: "扩写范围仅限本轮输入,勿补前因后果或无关对话。", loreAfter: "", postTurn: "", }; let probingPresetId = null; let probeBusy = false; async function api(path, options = {}) { const res = await fetch(path, { headers: { "Content-Type": "application/json" }, ...options, }); const data = await res.json(); if (!res.ok) throw new Error(data.error || "请求失败"); return data; } function escapeHtml(text) { return String(text) .replace(/&/g, "&") .replace(//g, ">"); } function maskKey(key) { if (!key) return "(未设置)"; if (key.length <= 8) return "****"; return `${key.slice(0, 4)}…${key.slice(-4)}`; } let toastTimer = null; function showToast(message, isError = false) { settingsToastEl.hidden = false; settingsToastEl.textContent = message; settingsToastEl.classList.toggle("error", isError); clearTimeout(toastTimer); toastTimer = setTimeout(() => { settingsToastEl.hidden = true; }, 4000); } function renderActiveBar() { const profile = state.profiles.find( (p) => p.id === state.settings.activeProfileId, ); const preset = state.presets.find( (p) => p.id === state.settings.activePresetId, ); const persona = state.personas.find((p) => p.id === state.activePersonaId); activeSettingsBarEl.innerHTML = ` 当前生效 API: ${profile ? escapeHtml(`${profile.name} · ${profile.model}`) : "未选用"} 角色: ${persona ? escapeHtml(persona.name) : "未选用"} 预设: ${preset ? escapeHtml(preset.name) : "未选用"} `; } function switchSection(section) { activeSection = section; document.querySelectorAll(".st-rail-item").forEach((el) => { el.classList.toggle("active", el.dataset.section === section); }); sectionApiEl.classList.toggle("hidden", section !== "api"); sectionPersonaEl?.classList.toggle("hidden", section !== "persona"); sectionPresetEl.classList.toggle("hidden", section !== "preset"); sectionStorageEl?.classList.toggle("hidden", section !== "storage"); renderPanelHeader(); } function renderPanelHeader() { panelActionsEl.innerHTML = ""; if (activeSection === "api") { panelTitleEl.textContent = "API"; panelSubtitleEl.textContent = "保存后点「选用」才会用于对话"; const btn = document.createElement("button"); btn.type = "button"; btn.className = "btn-primary"; btn.textContent = "新增"; btn.addEventListener("click", () => openProfileDialog()); panelActionsEl.appendChild(btn); } else if (activeSection === "persona") { panelTitleEl.textContent = "用户角色"; panelSubtitleEl.textContent = "游玩用当前选用;创作用「创作默认」(建议名为 @玩家)"; const btn = document.createElement("button"); btn.type = "button"; btn.className = "btn-primary"; btn.textContent = "新增"; btn.addEventListener("click", () => openPersonaDialog()); panelActionsEl.appendChild(btn); } else if (activeSection === "preset") { panelTitleEl.textContent = "预设"; panelSubtitleEl.textContent = "导入后可启用/编辑条目;选用后注入全部 LLM 请求"; const btn = document.createElement("button"); btn.type = "button"; btn.className = "btn-primary"; btn.textContent = "导入 JSON"; btn.addEventListener("click", () => presetFileEl.click()); panelActionsEl.appendChild(btn); } else { panelTitleEl.textContent = "上下文"; panelSubtitleEl.textContent = "控制每个对话保留多少条完整 prompt 痕迹"; } } function activateButtonHtml(active, id, action) { if (active) { return ``; } return ``; } function capabilityBadge(caps) { if (!caps || !caps.testedAt) { return `
能力:未探测(选用前建议点「探测」)
`; } const bit = (label, status) => { const cls = status === "ok" ? "cap-ok" : status === "fail" ? "cap-fail" : "cap-unk"; return `${escapeHtml(label)}:${escapeHtml(status)}`; }; const mode = caps.jsonSchema === "ok" ? "JSON Schema" : caps.forcedTool === "ok" ? "强制 Tool" : caps.jsonObject === "ok" ? "JSON Object" : "纯文本"; return `
投递:${escapeHtml(mode)} ${bit("schema", caps.jsonSchema)} ${bit("tool", caps.forcedTool)} ${bit("json", caps.jsonObject)}
`; } function renderProfiles() { profilesListEl.innerHTML = ""; if (!state.profiles.length) { profilesListEl.innerHTML = '

暂无配置,点击右上角「新增配置」。

'; return; } for (const p of state.profiles) { const active = p.id === state.settings.activeProfileId; const card = document.createElement("div"); card.className = `config-card${active ? " active" : ""}`; card.innerHTML = `
${escapeHtml(p.name.charAt(0).toUpperCase())}
${escapeHtml(p.name)} ${active ? '当前' : ""}
${escapeHtml(p.model)} · ${escapeHtml(p.baseUrl)}
Key: ${escapeHtml(maskKey(p.apiKey))}${ p.reasoningEffort ? ` · 思考: ${escapeHtml(p.reasoningEffort)}` : "" }
${capabilityBadge(p.capabilities)}
${activateButtonHtml(active, p.id, "activate-profile")}
`; profilesListEl.appendChild(card); } } function renderPresets() { presetsListEl.innerHTML = ""; if (!state.presets.length) { presetsListEl.innerHTML = '

暂无预设,点击右上角「导入 JSON」。

'; return; } for (const p of state.presets) { const active = p.id === state.settings.activePresetId; const expanded = expandedPresetEntries.get(p.id); const card = document.createElement("div"); card.className = `config-card${active ? " active" : ""}`; card.innerHTML = `
P
${escapeHtml(p.name)} ${active ? '当前' : ""}
${escapeHtml(p.source)} · 共 ${p.entryCount ?? "?"} 条 · 启用 ${p.enabledCount} · 注入 ${p.injectingCount ?? "?"} · ${escapeHtml(p.importedAt?.slice(0, 10) ?? "")}
${activateButtonHtml(active, p.id, "activate-preset")}
`; presetsListEl.appendChild(card); if (expanded) { renderPresetEntriesPanel(p.id, expanded); } } } const GENERATION_FIELDS = [ { key: "temperature", label: "temperature", step: "0.01", placeholder: "如 1" }, { key: "topP", label: "top P", step: "0.01", placeholder: "如 0.95" }, { key: "topK", label: "top K", step: "1", placeholder: "可选" }, { key: "minP", label: "min P", step: "0.01", placeholder: "可选" }, { key: "frequencyPenalty", label: "frequency penalty", step: "0.01", placeholder: "可选", }, { key: "presencePenalty", label: "presence penalty", step: "0.01", placeholder: "可选", }, { key: "repetitionPenalty", label: "repetition penalty", step: "0.01", placeholder: "可选", }, { key: "maxContextTokens", label: "最大上下文", step: "1", placeholder: "输入预算", }, { key: "maxOutputTokens", label: "最大输出", step: "1", placeholder: "如 60000", }, { key: "seed", label: "seed", step: "1", placeholder: "可选" }, { key: "variants", label: "n / variants", step: "1", placeholder: "可选", }, ]; function generationFieldValue(gen, key) { const v = gen?.[key]; return v === undefined || v === null ? "" : String(v); } function renderGenerationForm(gen) { const g = gen ?? {}; const numberFields = GENERATION_FIELDS.map( (f) => ` `, ).join(""); return `
生成参数 上半 · 采样与容量;空字段表示不发送
${numberFields}
`; } function collectGenerationFromPanel(panel) { const generation = {}; for (const input of panel.querySelectorAll("[data-gen-field]")) { const key = input.dataset.genField; if (!key) continue; if (input.type === "checkbox") { if (input.checked) generation.stream = true; continue; } const raw = input.value.trim(); if (!raw) continue; if (key === "reasoningEffort" || key === "verbosity") { generation[key] = raw; } else { const n = Number(raw); if (Number.isFinite(n)) generation[key] = n; } } return generation; } function renderPresetEntriesPanel(presetId, data) { const panel = document.getElementById(`preset-entries-${presetId}`); if (!panel || !data) return; const openId = openPresetEntryId.get(presetId) ?? null; const entriesHtml = data.entries .map((entry, idx) => { const roleClass = `role-${entry.role}`; const open = openId === entry.id; const preview = (entry.content || "").trim(); const previewText = preview ? preview.length > 72 ? `${preview.slice(0, 72)}…` : preview : entry.marker ? "(marker · 无正文)" : "(空)"; const status = entry.enabled ? entry.willInject ? '将注入' : '已启用 · 不注入' : '未启用'; return `
${idx + 1} ${escapeHtml(entry.role)} ${escapeHtml(entry.name)} ${status}
${open ? "" : `

${escapeHtml(previewText)}

`}
`; }) .join(""); panel.innerHTML = `
${renderGenerationForm(data.generation)}

下半 · 共 ${data.entries.length} 条 · 启用 ${data.enabledCount ?? 0} · 注入 ${data.injectingCount ?? 0}。点条目展开编辑;启用开关即时保存。

${entriesHtml || '

无条目

'}
`; } async function refreshPresetEntries(presetId) { const data = await api(`/api/presets/${encodeURIComponent(presetId)}/entries`); expandedPresetEntries.set(presetId, data); // 同步卡片上的计数 const cardMeta = state.presets.find((p) => p.id === presetId); if (cardMeta) { cardMeta.enabledCount = data.enabledCount; cardMeta.injectingCount = data.injectingCount; cardMeta.entryCount = data.entries?.length; } renderPresets(); } async function togglePresetEntries(presetId) { if (expandedPresetEntries.has(presetId)) { expandedPresetEntries.delete(presetId); openPresetEntryId.delete(presetId); renderPresets(); return; } await refreshPresetEntries(presetId); } async function patchPresetEntry(presetId, patch) { const data = await api(`/api/presets/${encodeURIComponent(presetId)}/entries`, { method: "PATCH", body: JSON.stringify({ entry: patch }), }); expandedPresetEntries.set(presetId, data); const cardMeta = state.presets.find((p) => p.id === presetId); if (cardMeta) { cardMeta.enabledCount = data.enabledCount; cardMeta.injectingCount = data.injectingCount; cardMeta.entryCount = data.entries?.length; } renderPresets(); const n = data.reloadedSessions ?? 0; showToast( n > 0 ? `已保存,并热更新 ${n} 个会话` : "已保存(选用此预设后才会进入请求)", ); return data; } async function patchPresetGeneration(presetId, generation) { const data = await api(`/api/presets/${encodeURIComponent(presetId)}/entries`, { method: "PATCH", body: JSON.stringify({ generation }), }); expandedPresetEntries.set(presetId, data); renderPresets(); const n = data.reloadedSessions ?? 0; showToast( n > 0 ? `生成参数已保存,并热更新 ${n} 个会话` : "生成参数已保存(选用此预设后才会进入请求)", ); return data; } async function loadAll() { const data = await api("/api/settings"); state.settings = data.settings; state.profiles = data.profiles; state.presets = data.presets; try { const personasData = await api("/api/personas"); state.personas = personasData.personas ?? []; state.activePersonaId = personasData.activeId ?? null; state.creationDefaultId = personasData.creationDefaultId ?? null; } catch { state.personas = []; state.activePersonaId = null; } if (contextTraceKeepEl) { contextTraceKeepEl.value = String( state.settings.contextTraceKeepLatest ?? 5, ); } renderActiveBar(); renderProfiles(); renderPersonas(); renderPresets(); } function renderPersonas() { if (!personasListEl) return; personasListEl.innerHTML = ""; if (!state.personas.length) { personasListEl.innerHTML = `
还没有用户角色。点右上角「新增」。
`; return; } for (const p of state.personas) { const active = p.id === state.activePersonaId; const creationDefault = p.id === state.creationDefaultId; const card = document.createElement("div"); card.className = `config-card persona-config-card${active ? " is-active" : ""}`; const desc = (p.description || "").trim(); card.innerHTML = `
${escapeHtml(p.name)}${ creationDefault ? `创作默认` : "" }
${ desc ? escapeHtml(desc.slice(0, 80)) + (desc.length > 80 ? "…" : "") : "(无人设正文)" }
${activateButtonHtml(active, p.id, "activate-persona")}
`; personasListEl.appendChild(card); } } function openPersonaDialog(persona = null) { editingPersonaId = persona?.id ?? null; personaDialogTitle.textContent = persona ? "编辑用户角色" : "新增用户角色"; personaForm.name.value = persona?.name ?? ""; personaForm.description.value = persona?.description ?? ""; personaForm.creationDefault.checked = persona ? persona.id === state.creationDefaultId : false; personaDialog.showModal(); } async function savePersona() { const payload = { name: personaForm.name.value, description: personaForm.description.value, activate: true, creationDefault: Boolean(personaForm.creationDefault?.checked), }; if (editingPersonaId) { await api(`/api/personas/${encodeURIComponent(editingPersonaId)}`, { method: "PUT", body: JSON.stringify(payload), }); await api(`/api/personas/${encodeURIComponent(editingPersonaId)}/activate`, { method: "POST", }); } else { await api("/api/personas", { method: "POST", body: JSON.stringify(payload), }); } personaDialog.close(); await loadAll(); showToast("用户角色已保存"); } function openProfileDialog(profile = null) { editingProfileId = profile?.id ?? null; profileDialogTitle.textContent = profile ? "编辑 API 配置" : "新增 API 配置"; profileForm.name.value = profile?.name ?? ""; profileForm.baseUrl.value = profile?.baseUrl ?? "https://api.deepseek.com"; profileForm.apiKey.value = profile?.apiKey ?? ""; profileForm.model.value = profile?.model ?? "deepseek-v4-pro"; profileForm.reasoningEffort.value = profile?.reasoningEffort ?? ""; profileDialog.showModal(); } async function saveProfile(activate) { const payload = { name: profileForm.name.value, baseUrl: profileForm.baseUrl.value, model: profileForm.model.value, reasoningEffort: profileForm.reasoningEffort.value, activate, }; const apiKey = profileForm.apiKey.value.trim(); if (apiKey || !editingProfileId) { payload.apiKey = apiKey; } let result; if (editingProfileId) { result = await api(`/api/profiles/${editingProfileId}`, { method: "PUT", body: JSON.stringify(payload), }); if (activate) { result = await api(`/api/profiles/${editingProfileId}/activate`, { method: "POST", }); } } else { result = await api("/api/profiles", { method: "POST", body: JSON.stringify(payload), }); } profileDialog.close(); await loadAll(); if (activate) { const n = result.reloadedSessions ?? 0; showToast(`已选用并生效${n > 0 ? `,已更新 ${n} 个活跃会话` : ""}`); } else { showToast("已保存。点击「选用并生效」后才会用于 LLM 请求。"); } } async function activateProfile(id) { const result = await api(`/api/profiles/${id}/activate`, { method: "POST" }); await loadAll(); const n = result.reloadedSessions ?? 0; showToast(`API 配置已生效${n > 0 ? `(${n} 个会话已更新)` : ""}`); } async function activatePreset(id) { const result = await api(`/api/presets/${id}/activate`, { method: "POST" }); await loadAll(); const n = result.reloadedSessions ?? 0; showToast(`预设已生效${n > 0 ? `(${n} 个会话已更新)` : ""}`); } document.querySelectorAll(".st-rail-item").forEach((btn) => { btn.addEventListener("click", () => switchSection(btn.dataset.section)); }); document.getElementById("profile-cancel").addEventListener("click", () => { profileDialog.close(); }); document.getElementById("persona-cancel")?.addEventListener("click", () => { personaDialog?.close(); }); personaForm?.addEventListener("submit", async (e) => { e.preventDefault(); try { await savePersona(); } catch (err) { showToast(err.message, true); } }); personasListEl?.addEventListener("change", async (e) => { const input = e.target.closest("input[data-action='creation-default']"); if (!input) return; const id = input.dataset.id; try { await api(`/api/personas/${encodeURIComponent(id)}`, { method: "PUT", body: JSON.stringify({ creationDefault: Boolean(input.checked) }), }); await loadAll(); showToast(input.checked ? "已设为创作默认" : "已取消创作默认"); } catch (err) { showToast(err.message, true); await loadAll(); } }); personasListEl?.addEventListener("click", async (e) => { const btn = e.target.closest("[data-action]"); if (!btn) return; if (btn.matches("input[data-action='creation-default']")) return; const id = btn.dataset.id; const action = btn.dataset.action; try { if (action === "activate-persona") { await api(`/api/personas/${encodeURIComponent(id)}/activate`, { method: "POST", }); await loadAll(); showToast("用户角色已选用"); } else if (action === "edit-persona") { openPersonaDialog(state.personas.find((p) => p.id === id)); } else if (action === "delete-persona") { if (!confirm("删除这个用户角色?")) return; await api(`/api/personas/${encodeURIComponent(id)}`, { method: "DELETE" }); await loadAll(); showToast("已删除"); } } catch (err) { showToast(err.message, true); } }); document.getElementById("profile-save").addEventListener("click", () => { saveProfile(false).catch((err) => showToast(err.message, true)); }); profileForm.addEventListener("submit", async (e) => { e.preventDefault(); try { await saveProfile(true); } catch (err) { showToast(err.message, true); } }); profilesListEl.addEventListener("click", async (e) => { const btn = e.target.closest("[data-action]"); if (!btn) return; const id = btn.dataset.id; const action = btn.dataset.action; try { if (action === "activate-profile") { await activateProfile(id); } else if (action === "edit-profile") { openProfileDialog(state.profiles.find((p) => p.id === id)); } else if (action === "probe-capabilities") { const el = document.getElementById(`test-${id}`); el.textContent = "探测中(含连通性,会打几次短请求)…"; el.className = "test-result"; const result = await api(`/api/profiles/${id}/probe-capabilities`, { method: "POST", }); await loadAll(); const caps = result.profile?.capabilities; const chatOk = caps?.chat === "ok"; const note = !chatOk ? `连接失败${caps?.notes?.[0] ? `:${caps.notes[0]}` : ""}` : result.deliveryModeLabel ? `连接正常 · 投递将用:${result.deliveryModeLabel}` : "探测完成"; const refreshed = document.getElementById(`test-${id}`); if (refreshed) { refreshed.textContent = note; refreshed.className = `test-result ${chatOk ? "ok" : "fail"}`; } showToast(note, !chatOk); } else if (action === "delete-profile") { if (!confirm("确定删除此 API 配置?")) return; await api(`/api/profiles/${id}`, { method: "DELETE" }); await loadAll(); showToast("已删除"); } } catch (err) { showToast(err.message, true); } }); presetsListEl.addEventListener("click", async (e) => { if (e.target.closest("[data-stop-expand]")) return; const expandHead = e.target.closest('[data-action="expand-entry"]'); if (expandHead) { const panel = expandHead.closest(".preset-entries-inner"); const presetId = panel?.dataset.presetId; const entryId = expandHead.dataset.id; if (!presetId || !entryId) return; const cur = openPresetEntryId.get(presetId); if (cur === entryId) openPresetEntryId.delete(presetId); else openPresetEntryId.set(presetId, entryId); const data = expandedPresetEntries.get(presetId); if (data) renderPresetEntriesPanel(presetId, data); return; } const btn = e.target.closest("[data-action]"); if (!btn) return; const id = btn.dataset.id; const action = btn.dataset.action; try { if (action === "activate-preset") { await activatePreset(id); } else if (action === "probe-preset") { openProbeDialog(id); } else if (action === "toggle-preset-entries") { await togglePresetEntries(id); } else if (action === "delete-preset") { if (!confirm("确定删除此预设?")) return; await api(`/api/presets/${id}`, { method: "DELETE" }); expandedPresetEntries.delete(id); openPresetEntryId.delete(id); await loadAll(); showToast("已删除"); } else if (action === "collapse-entry") { const panel = btn.closest(".preset-entries-inner"); const presetId = panel?.dataset.presetId; if (!presetId) return; openPresetEntryId.delete(presetId); const data = expandedPresetEntries.get(presetId); if (data) renderPresetEntriesPanel(presetId, data); } else if (action === "save-entry") { const article = btn.closest(".preset-entry"); const panel = btn.closest(".preset-entries-inner"); const presetId = panel?.dataset.presetId; if (!article || !presetId) return; const name = article.querySelector('[data-field="name"]')?.value ?? ""; const content = article.querySelector('[data-field="content"]')?.value ?? ""; openPresetEntryId.set(presetId, id); await patchPresetEntry(presetId, { id, name, content }); } else if (action === "save-generation") { const panel = btn.closest(".preset-entries-inner"); const presetId = panel?.dataset.presetId; if (!presetId) return; await patchPresetGeneration(presetId, collectGenerationFromPanel(panel)); } } catch (err) { showToast(err.message, true); } }); presetsListEl.addEventListener("keydown", (e) => { if (e.key !== "Enter" && e.key !== " ") return; const head = e.target.closest?.('[data-action="expand-entry"]'); if (!head) return; e.preventDefault(); head.click(); }); presetsListEl.addEventListener("change", async (e) => { const input = e.target.closest('input[data-action="toggle-entry"]'); if (!input) return; const article = input.closest(".preset-entry"); const panel = input.closest(".preset-entries-inner"); const presetId = panel?.dataset.presetId; const id = input.dataset.id; if (!presetId || !id) return; try { await patchPresetEntry(presetId, { id, enabled: input.checked }); } catch (err) { input.checked = !input.checked; showToast(err.message, true); } }); presetFileEl.addEventListener("change", async () => { const file = presetFileEl.files?.[0]; if (!file) return; try { const text = await file.text(); const raw = JSON.parse(text); const name = file.name.replace(/\.json$/i, ""); const report = await api("/api/presets/import", { method: "POST", body: JSON.stringify({ raw, name }), }); lastImportedPresetId = report.preset.id; importReportEl.hidden = false; importReportEl.innerHTML = `
${[ `已导入: ${report.preset.name}`, `条目: ${report.promptCount},启用: ${report.enabledCount}`, report.warnings.length ? `警告: ${report.warnings.join("; ")}` : "", ] .filter(Boolean) .join("
")}
`; document .getElementById("activate-imported-preset") ?.addEventListener("click", async () => { try { await activatePreset(lastImportedPresetId); } catch (err) { showToast(err.message, true); } }); await loadAll(); showToast("预设已导入,请点击「选用此预设并生效」"); } catch (err) { showToast(err.message, true); } finally { presetFileEl.value = ""; } }); function resetProbeForm() { probeMessageEl.value = DEFAULT_PROBE_CONTEXT.message; probeLoreBeforeEl.value = DEFAULT_PROBE_CONTEXT.loreBefore; probeHistoryEl.value = DEFAULT_PROBE_CONTEXT.history; probeLoreAfterEl.value = DEFAULT_PROBE_CONTEXT.loreAfter; probePostTurnEl.value = DEFAULT_PROBE_CONTEXT.postTurn; probeStatusEl.hidden = true; probeStatusEl.textContent = ""; probeReplyWrapEl.hidden = true; probeReplyEl.textContent = ""; probeMessagesEl.innerHTML = '

发送后显示实际拼进请求的消息。

'; } function openProbeDialog(presetId) { const preset = state.presets.find((p) => p.id === presetId); probingPresetId = presetId; probePresetNameEl.textContent = preset?.name ?? presetId; resetProbeForm(); probeDialog.showModal(); probeMessageEl.focus(); probeMessageEl.select(); } function renderProbeMessages(messages) { if (!messages?.length) { probeMessagesEl.innerHTML = '

这次没有拼出任何消息。

'; return; } probeMessagesEl.innerHTML = messages .map( (m, i) => `
${escapeHtml(m.role)} #${i + 1}
${escapeHtml(m.content)}
`, ) .join(""); } function setProbeBusy(busy) { probeBusy = busy; probeSendEl.disabled = busy; probeSendEl.textContent = busy ? "发送中…" : "发送一轮"; } async function sendProbe() { if (probeBusy || !probingPresetId) return; const message = probeMessageEl.value.trim(); if (!message) { probeStatusEl.hidden = false; probeStatusEl.textContent = "请填写本轮输入"; probeMessageEl.focus(); return; } setProbeBusy(true); probeStatusEl.hidden = false; probeStatusEl.textContent = "正在拼装上下文…"; probeReplyWrapEl.hidden = true; try { const result = await api(`/api/presets/${probingPresetId}/probe`, { method: "POST", body: JSON.stringify({ message, loreBefore: probeLoreBeforeEl.value, history: probeHistoryEl.value, loreAfter: probeLoreAfterEl.value, postTurn: probePostTurnEl.value, }), }); renderProbeMessages(result.messages); if (result.reply) { probeReplyWrapEl.hidden = false; probeReplyEl.textContent = result.reasoning ? `${result.reasoning}\n\n——\n\n${result.reply}` : result.reply; } if (result.error) { probeStatusEl.textContent = `上下文已拼装,生成失败:${result.error}`; } else if (result.skipReason) { probeStatusEl.textContent = result.skipReason; } else if (result.completed) { probeStatusEl.textContent = `已发送 ${result.messages.length} 条`; } else { probeStatusEl.textContent = `已拼装 ${result.messages.length} 条`; } } catch (err) { probeStatusEl.textContent = err.message; } finally { setProbeBusy(false); } } document.getElementById("probe-close")?.addEventListener("click", () => { probeDialog.close(); }); probeSendEl?.addEventListener("click", () => { sendProbe().catch((err) => showToast(err.message, true)); }); probeMessageEl?.addEventListener("keydown", (e) => { if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { e.preventDefault(); sendProbe().catch((err) => showToast(err.message, true)); } }); document.getElementById("btn-save-context-keep")?.addEventListener("click", async () => { try { const n = Number(contextTraceKeepEl?.value ?? 5); const data = await api("/api/settings", { method: "PUT", body: JSON.stringify({ contextTraceKeepLatest: n }), }); state.settings = data.settings; if (contextTraceKeepEl) { contextTraceKeepEl.value = String(data.settings.contextTraceKeepLatest ?? 5); } showToast(`已保存:每个对话保留最新 ${data.settings.contextTraceKeepLatest} 条上下文`); } catch (err) { showToast(err.message, true); } }); document.getElementById("btn-prune-context")?.addEventListener("click", async () => { try { const r = await api("/api/settings/context-traces/prune", { method: "POST" }); showToast( `已修剪 ${r.sessions} 个会话:保留 ${r.kept} 条痕迹,清除 ${r.cleared} 条`, ); } catch (err) { showToast(err.message, true); } }); document.getElementById("btn-clear-context")?.addEventListener("click", async () => { if (!confirm("清除所有已打开会话中保存的 LLM 请求上下文?消息正文不受影响。")) { return; } try { const r = await api("/api/settings/context-traces/clear", { method: "POST" }); showToast(`已清除 ${r.sessions} 个会话中的 ${r.cleared} 条上下文痕迹`); } catch (err) { showToast(err.message, true); } }); renderPanelHeader(); loadAll().catch((err) => { showToast(`加载设置失败: ${err.message}`, true); });