feat: 重构配置工作区

This commit is contained in:
Youzini-afk
2026-03-24 19:12:30 +08:00
parent 04ef48dbcd
commit e71305dc8b
6 changed files with 1711 additions and 1183 deletions

View File

@@ -111,10 +111,10 @@ git clone https://github.com/pjm0616/ST-Bionic-Memory-Ecology.git st-bme
## ⚡ 快速上手
1. **启用插件** — 右侧面板找到 "ST-BME 图谱记忆" 区块,勾选 "启用记忆图谱"
2. **配置 Embedding** — 选择向量模式(下面会说),有了向量搜索召回效果才好
3. **开始聊天** — 正常跟角色对话,插件会自动在后台提取和召回
4. **打开面板** — 左上角 ≡ 菜单 →「🧠 记忆图谱」查看图谱
1. **打开面板** — 左上角 ≡ 菜单 →「🧠 记忆图谱
2. **启用插件** — 进入面板的「配置 → 功能开关」,打开 ST-BME 自动记忆
3. **配置 Embedding** — 进入「配置 → API 配置」,选择向量模式并填好模型
4. **开始聊天** — 正常跟角色对话,插件会自动在后台提取和召回
> **最少配置:** 只勾选"启用"就能跑起来。默认会复用你当前的聊天模型做提取。
@@ -195,7 +195,7 @@ git clone https://github.com/pjm0616/ST-Bionic-Memory-Ecology.git st-bme
## 🖥️ 操控面板
从左上角 ≡ 菜单点「🧠 记忆图谱」打开面板,或在右侧设置里点「打开操控面板」
从左上角 ≡ 菜单点「🧠 记忆图谱」打开面板。
### 总览 Tab
- 统计数据(活跃节点、边、归档数、碎片率)
@@ -222,7 +222,14 @@ git clone https://github.com/pjm0616/ST-Bionic-Memory-Ecology.git st-bme
- 强制进化 — 让新记忆影响旧记忆
### 配置 Tab
在面板内可以直接修改记忆 LLM 和 Embedding 配置,无需退出面板。
配置页现在是一个完整的工作区,分成 5 个子页:
- API 配置
- 功能开关
- 详细参数
- 系统提示词
- 面板外观
桌面端会显示左侧竖向子导航,右侧显示宽版配置表单;移动端则改成顶部横向子页切换。
### 图谱可视化
桌面端右侧大区域显示力导向图谱,节点可拖拽、缩放、点击查看详情。支持 4 套配色主题切换。
@@ -289,7 +296,6 @@ ST-BME/
├── graph-renderer.js # Canvas 力导向图谱渲染器
├── panel.js # 操控面板交互逻辑
├── panel.html # 面板 HTML 模板
├── settings.html # 右侧设置面板
├── style.css # 全部样式
├── manifest.json # SillyTavern 扩展清单
└── tests/ # 测试脚本

248
index.js
View File

@@ -1568,254 +1568,6 @@ async function onReembedDirect() {
await onRebuildVectorIndex();
}
// ==================== 设置 UI ====================
function bindSettingsUI() {
const settings = getSettings();
// 开关
$("#st_bme_enabled")
.prop("checked", settings.enabled)
.on("change", function () {
settings.enabled = $(this).prop("checked");
saveSettingsDebounced();
});
// 提取频率
$("#st_bme_extract_every")
.val(settings.extractEvery)
.on("input", function () {
settings.extractEvery = clampInt($(this).val(), 1, 1, 50);
saveSettingsDebounced();
});
$("#st_bme_extract_context_turns")
.val(settings.extractContextTurns)
.on("input", function () {
settings.extractContextTurns = clampInt($(this).val(), 2, 0, 20);
saveSettingsDebounced();
});
// 召回开关
$("#st_bme_recall_enabled")
.prop("checked", settings.recallEnabled)
.on("change", function () {
settings.recallEnabled = $(this).prop("checked");
saveSettingsDebounced();
});
// LLM 精确召回
$("#st_bme_recall_llm")
.prop("checked", settings.recallEnableLLM)
.on("change", function () {
settings.recallEnableLLM = $(this).prop("checked");
saveSettingsDebounced();
});
$("#st_bme_recall_top_k")
.val(settings.recallTopK)
.on("input", function () {
settings.recallTopK = clampInt($(this).val(), 15, 1, 100);
saveSettingsDebounced();
});
$("#st_bme_recall_max_nodes")
.val(settings.recallMaxNodes)
.on("input", function () {
settings.recallMaxNodes = clampInt($(this).val(), 8, 1, 50);
saveSettingsDebounced();
});
// 注入深度
$("#st_bme_inject_depth")
.val(settings.injectDepth)
.on("input", function () {
settings.injectDepth = clampInt($(this).val(), 4, 0, 9999);
saveSettingsDebounced();
});
// 评分权重
$("#st_bme_graph_weight")
.val(settings.graphWeight)
.on("input", function () {
settings.graphWeight = clampFloat($(this).val(), 0.6, 0, 1);
saveSettingsDebounced();
});
$("#st_bme_vector_weight")
.val(settings.vectorWeight)
.on("input", function () {
settings.vectorWeight = clampFloat($(this).val(), 0.3, 0, 1);
saveSettingsDebounced();
});
$("#st_bme_importance_weight")
.val(settings.importanceWeight)
.on("input", function () {
settings.importanceWeight = clampFloat($(this).val(), 0.1, 0, 1);
saveSettingsDebounced();
});
// Embedding API
$("#st_bme_embed_url")
.val(settings.embeddingApiUrl)
.on("input", function () {
settings.embeddingApiUrl = $(this).val().trim();
saveSettingsDebounced();
});
$("#st_bme_embed_key")
.val(settings.embeddingApiKey)
.on("input", function () {
settings.embeddingApiKey = $(this).val().trim();
saveSettingsDebounced();
});
$("#st_bme_embed_model")
.val(settings.embeddingModel)
.on("input", function () {
settings.embeddingModel = $(this).val().trim();
saveSettingsDebounced();
});
// 操作按钮
$("#st_bme_btn_view_graph").on("click", onViewGraph);
$("#st_bme_btn_rebuild").on("click", onRebuild);
$("#st_bme_btn_compress").on("click", onManualCompress);
$("#st_bme_btn_export").on("click", onExportGraph);
$("#st_bme_btn_import").on("click", onImportGraph);
$("#st_bme_btn_view_injection").on("click", onViewLastInjection);
$("#st_bme_btn_test_embed").on("click", onTestEmbedding);
// ====== v2 增强设置 UI 绑定 ======
// P0: 记忆进化
$("#st_bme_evolution")
.prop("checked", settings.enableEvolution)
.on("change", function () {
settings.enableEvolution = $(this).prop("checked");
saveSettingsDebounced();
});
$("#st_bme_evo_neighbors")
.val(settings.evoNeighborCount)
.on("input", function () {
settings.evoNeighborCount = clampInt($(this).val(), 5, 1, 20);
saveSettingsDebounced();
});
$("#st_bme_evo_consolidate_every")
.val(settings.evoConsolidateEvery)
.on("input", function () {
settings.evoConsolidateEvery = clampInt($(this).val(), 50, 1, 500);
saveSettingsDebounced();
});
// P0: 精确对照
$("#st_bme_precise_conflict")
.prop("checked", settings.enablePreciseConflict)
.on("change", function () {
settings.enablePreciseConflict = $(this).prop("checked");
saveSettingsDebounced();
});
$("#st_bme_conflict_threshold")
.val(settings.conflictThreshold)
.on("input", function () {
settings.conflictThreshold = clampFloat($(this).val(), 0.85, 0.5, 0.99);
saveSettingsDebounced();
});
// P0: 全局概要
$("#st_bme_synopsis")
.prop("checked", settings.enableSynopsis)
.on("change", function () {
settings.enableSynopsis = $(this).prop("checked");
saveSettingsDebounced();
});
$("#st_bme_synopsis_every")
.val(settings.synopsisEveryN)
.on("input", function () {
settings.synopsisEveryN = clampInt($(this).val(), 5, 1, 100);
saveSettingsDebounced();
});
// P1: 认知边界
$("#st_bme_visibility")
.prop("checked", settings.enableVisibility ?? false)
.on("change", function () {
settings.enableVisibility = $(this).prop("checked");
saveSettingsDebounced();
});
// P1: 交叉检索
$("#st_bme_cross_recall")
.prop("checked", settings.enableCrossRecall ?? false)
.on("change", function () {
settings.enableCrossRecall = $(this).prop("checked");
saveSettingsDebounced();
});
// P2: 惊奇度分割
$("#st_bme_smart_trigger")
.prop("checked", settings.enableSmartTrigger)
.on("change", function () {
settings.enableSmartTrigger = $(this).prop("checked");
saveSettingsDebounced();
});
$("#st_bme_trigger_patterns")
.val(settings.triggerPatterns || "")
.on("input", function () {
settings.triggerPatterns = $(this).val();
saveSettingsDebounced();
});
$("#st_bme_smart_trigger_threshold")
.val(settings.smartTriggerThreshold)
.on("input", function () {
settings.smartTriggerThreshold = clampInt($(this).val(), 2, 1, 10);
saveSettingsDebounced();
});
// P2: 主动遗忘
$("#st_bme_sleep_cycle")
.prop("checked", settings.enableSleepCycle)
.on("change", function () {
settings.enableSleepCycle = $(this).prop("checked");
saveSettingsDebounced();
});
$("#st_bme_forget_threshold")
.val(settings.forgetThreshold)
.on("input", function () {
settings.forgetThreshold = clampFloat($(this).val(), 0.5, 0.1, 1);
saveSettingsDebounced();
});
$("#st_bme_sleep_every")
.val(settings.sleepEveryN)
.on("input", function () {
settings.sleepEveryN = clampInt($(this).val(), 10, 1, 200);
saveSettingsDebounced();
});
// P2: 概率触发
$("#st_bme_prob_recall")
.prop("checked", settings.enableProbRecall)
.on("change", function () {
settings.enableProbRecall = $(this).prop("checked");
saveSettingsDebounced();
});
$("#st_bme_prob_chance")
.val(settings.probRecallChance)
.on("input", function () {
settings.probRecallChance = clampFloat($(this).val(), 0.15, 0.01, 0.5);
saveSettingsDebounced();
});
// P2: 反思条目
$("#st_bme_reflection")
.prop("checked", settings.enableReflection)
.on("change", function () {
settings.enableReflection = $(this).prop("checked");
saveSettingsDebounced();
});
$("#st_bme_reflect_every")
.val(settings.reflectEveryN)
.on("input", function () {
settings.reflectEveryN = clampInt($(this).val(), 10, 1, 200);
saveSettingsDebounced();
});
}
// ==================== 初始化 ====================
(async function init() {

1101
panel.html

File diff suppressed because it is too large Load Diff

420
panel.js
View File

@@ -108,6 +108,8 @@ let panelEl = null;
let overlayEl = null;
let graphRenderer = null;
let mobileGraphRenderer = null;
let currentTabId = "dashboard";
let currentConfigSectionId = "api";
// 由 index.js 注入的引用
@@ -167,6 +169,10 @@ export async function initPanel({
_bindGraphControls();
_bindActions();
_bindConfigControls();
currentTabId =
panelEl?.querySelector(".bme-tab-btn.active")?.dataset.tab || "dashboard";
_applyWorkspaceMode();
_syncConfigSectionState();
}
/**
@@ -192,10 +198,11 @@ export function openPanel() {
mobileGraphRenderer.onNodeSelect = (node) => _showNodeDetail(node);
}
_refreshDashboard();
const activeTabId =
panelEl?.querySelector(".bme-tab-btn.active")?.dataset.tab || currentTabId;
_switchTab(activeTabId);
_refreshGraph();
_buildLegend();
_refreshConfigTab();
}
/**
@@ -213,6 +220,7 @@ export function updatePanelTheme(themeName) {
graphRenderer?.setTheme(themeName);
mobileGraphRenderer?.setTheme(themeName);
_buildLegend();
_highlightThemeChoice(themeName);
}
// ==================== Tab 切换 ====================
@@ -227,15 +235,18 @@ function _bindTabs() {
}
function _switchTab(tabId) {
currentTabId = tabId || "dashboard";
panelEl?.querySelectorAll(".bme-tab-btn").forEach((btn) => {
btn.classList.toggle("active", btn.dataset.tab === tabId);
btn.classList.toggle("active", btn.dataset.tab === currentTabId);
});
panelEl?.querySelectorAll(".bme-tab-pane").forEach((pane) => {
pane.classList.toggle("active", pane.id === `bme-pane-${tabId}`);
pane.classList.toggle("active", pane.id === `bme-pane-${currentTabId}`);
});
switch (tabId) {
_applyWorkspaceMode();
switch (currentTabId) {
case "dashboard":
_refreshDashboard();
break;
@@ -253,6 +264,33 @@ function _switchTab(tabId) {
}
}
function _applyWorkspaceMode() {
if (!panelEl) return;
const isConfig = currentTabId === "config";
panelEl.classList.toggle("config-mode", isConfig);
}
function _switchConfigSection(sectionId) {
currentConfigSectionId = sectionId || "api";
_syncConfigSectionState();
}
function _syncConfigSectionState() {
if (!panelEl) return;
panelEl.querySelectorAll(".bme-config-nav-btn").forEach((btn) => {
btn.classList.toggle(
"active",
btn.dataset.configSection === currentConfigSectionId,
);
});
panelEl.querySelectorAll(".bme-config-section").forEach((section) => {
section.classList.toggle(
"active",
section.dataset.configSection === currentConfigSectionId,
);
});
}
// ==================== 总览 Tab ====================
function _refreshDashboard() {
@@ -624,18 +662,87 @@ function _refreshConfigTab() {
_setCheckboxValue("bme-setting-enabled", settings.enabled ?? false);
_setCheckboxValue("bme-setting-recall-enabled", settings.recallEnabled ?? true);
_setCheckboxValue("bme-setting-recall-llm", settings.recallEnableLLM ?? true);
_setCheckboxValue("bme-setting-evolution-enabled", settings.enableEvolution ?? true);
_setCheckboxValue(
"bme-setting-precise-conflict-enabled",
settings.enablePreciseConflict ?? true,
);
_setCheckboxValue("bme-setting-synopsis-enabled", settings.enableSynopsis ?? true);
_setCheckboxValue(
"bme-setting-visibility-enabled",
settings.enableVisibility ?? false,
);
_setCheckboxValue(
"bme-setting-cross-recall-enabled",
settings.enableCrossRecall ?? false,
);
_setCheckboxValue(
"bme-setting-smart-trigger-enabled",
settings.enableSmartTrigger ?? false,
);
_setCheckboxValue(
"bme-setting-sleep-cycle-enabled",
settings.enableSleepCycle ?? false,
);
_setCheckboxValue(
"bme-setting-prob-recall-enabled",
settings.enableProbRecall ?? false,
);
_setCheckboxValue(
"bme-setting-reflection-enabled",
settings.enableReflection ?? false,
);
_setInputValue("bme-setting-extract-every", settings.extractEvery ?? 1);
_setInputValue(
"bme-setting-extract-context-turns",
settings.extractContextTurns ?? 2,
);
_setInputValue("bme-setting-recall-top-k", settings.recallTopK ?? 15);
_setInputValue("bme-setting-recall-max-nodes", settings.recallMaxNodes ?? 8);
_setInputValue("bme-setting-inject-depth", settings.injectDepth ?? 4);
_setInputValue("bme-setting-graph-weight", settings.graphWeight ?? 0.6);
_setInputValue("bme-setting-vector-weight", settings.vectorWeight ?? 0.3);
_setInputValue(
"bme-setting-importance-weight",
settings.importanceWeight ?? 0.1,
);
_setInputValue(
"bme-setting-evo-neighbor-count",
settings.evoNeighborCount ?? 5,
);
_setInputValue(
"bme-setting-evo-consolidate-every",
settings.evoConsolidateEvery ?? 50,
);
_setInputValue(
"bme-setting-conflict-threshold",
settings.conflictThreshold ?? 0.85,
);
_setInputValue("bme-setting-synopsis-every", settings.synopsisEveryN ?? 5);
_setInputValue(
"bme-setting-trigger-patterns",
settings.triggerPatterns || "",
);
_setInputValue(
"bme-setting-smart-trigger-threshold",
settings.smartTriggerThreshold ?? 2,
);
_setInputValue(
"bme-setting-forget-threshold",
settings.forgetThreshold ?? 0.5,
);
_setInputValue("bme-setting-sleep-every", settings.sleepEveryN ?? 10);
_setInputValue(
"bme-setting-prob-recall-chance",
settings.probRecallChance ?? 0.15,
);
_setInputValue("bme-setting-reflect-every", settings.reflectEveryN ?? 10);
_setInputValue("bme-setting-llm-url", settings.llmApiUrl || "");
_setInputValue("bme-setting-llm-key", settings.llmApiKey || "");
_setInputValue("bme-setting-llm-model", settings.llmModel || "");
_setCheckboxValue("bme-setting-recall-llm", settings.recallEnableLLM ?? true);
_setInputValue("bme-setting-recall-max-nodes", settings.recallMaxNodes ?? 8);
_setInputValue("bme-setting-embed-url", settings.embeddingApiUrl || "");
_setInputValue("bme-setting-embed-key", settings.embeddingApiKey || "");
@@ -671,96 +778,221 @@ function _refreshConfigTab() {
_setInputValue("bme-setting-compress-prompt", settings.compressPrompt || DEFAULT_PROMPTS.compress);
_setInputValue("bme-setting-synopsis-prompt", settings.synopsisPrompt || DEFAULT_PROMPTS.synopsis);
_setInputValue("bme-setting-reflection-prompt", settings.reflectionPrompt || DEFAULT_PROMPTS.reflection);
// 主题下拉菜单高亮
_highlightThemeOption(settings.panelTheme || "crimson");
_refreshGuardedConfigStates(settings);
_refreshPromptCardStates(settings);
_highlightThemeChoice(settings.panelTheme || "crimson");
_syncConfigSectionState();
}
function _bindConfigControls() {
if (!panelEl || panelEl.dataset.bmeConfigBound === "true") return;
bindCheckbox("bme-setting-enabled", (checked) =>
_updateSettings?.({ enabled: checked }),
panelEl.querySelectorAll(".bme-config-nav-btn").forEach((btn) => {
if (btn.dataset.bmeBound === "true") return;
btn.addEventListener("click", () => {
_switchConfigSection(btn.dataset.configSection || "api");
});
btn.dataset.bmeBound = "true";
});
bindCheckbox("bme-setting-enabled", (checked) => {
_patchSettings({ enabled: checked });
_refreshGuardedConfigStates();
});
bindCheckbox("bme-setting-recall-enabled", (checked) => {
_patchSettings({ recallEnabled: checked });
_refreshGuardedConfigStates();
});
bindCheckbox("bme-setting-recall-llm", (checked) => {
_patchSettings({ recallEnableLLM: checked });
_refreshGuardedConfigStates();
});
bindCheckbox("bme-setting-evolution-enabled", (checked) => {
_patchSettings({ enableEvolution: checked });
_refreshGuardedConfigStates();
});
bindCheckbox("bme-setting-precise-conflict-enabled", (checked) => {
_patchSettings({ enablePreciseConflict: checked });
_refreshGuardedConfigStates();
});
bindCheckbox("bme-setting-synopsis-enabled", (checked) => {
_patchSettings({ enableSynopsis: checked });
_refreshGuardedConfigStates();
});
bindCheckbox("bme-setting-visibility-enabled", (checked) =>
_patchSettings({ enableVisibility: checked }),
);
bindCheckbox("bme-setting-recall-enabled", (checked) =>
_updateSettings?.({ recallEnabled: checked }),
bindCheckbox("bme-setting-cross-recall-enabled", (checked) =>
_patchSettings({ enableCrossRecall: checked }),
);
bindCheckbox("bme-setting-smart-trigger-enabled", (checked) => {
_patchSettings({ enableSmartTrigger: checked });
_refreshGuardedConfigStates();
});
bindCheckbox("bme-setting-sleep-cycle-enabled", (checked) => {
_patchSettings({ enableSleepCycle: checked });
_refreshGuardedConfigStates();
});
bindCheckbox("bme-setting-prob-recall-enabled", (checked) => {
_patchSettings({ enableProbRecall: checked });
_refreshGuardedConfigStates();
});
bindCheckbox("bme-setting-reflection-enabled", (checked) => {
_patchSettings({ enableReflection: checked });
_refreshGuardedConfigStates();
});
bindNumber("bme-setting-extract-every", 1, 1, 50, (value) =>
_updateSettings?.({ extractEvery: value }),
_patchSettings({ extractEvery: value }),
);
bindNumber("bme-setting-extract-context-turns", 2, 0, 20, (value) =>
_updateSettings?.({ extractContextTurns: value }),
_patchSettings({ extractContextTurns: value }),
);
bindNumber("bme-setting-recall-top-k", 15, 1, 100, (value) =>
_patchSettings({ recallTopK: value }),
);
bindNumber("bme-setting-recall-max-nodes", 8, 1, 50, (value) =>
_patchSettings({ recallMaxNodes: value }),
);
bindNumber("bme-setting-inject-depth", 4, 0, 9999, (value) =>
_updateSettings?.({ injectDepth: value }),
_patchSettings({ injectDepth: value }),
);
bindFloat("bme-setting-graph-weight", 0.6, 0, 1, (value) =>
_patchSettings({ graphWeight: value }),
);
bindFloat("bme-setting-vector-weight", 0.3, 0, 1, (value) =>
_patchSettings({ vectorWeight: value }),
);
bindFloat("bme-setting-importance-weight", 0.1, 0, 1, (value) =>
_patchSettings({ importanceWeight: value }),
);
bindNumber("bme-setting-evo-neighbor-count", 5, 1, 20, (value) =>
_patchSettings({ evoNeighborCount: value }),
);
bindNumber("bme-setting-evo-consolidate-every", 50, 1, 500, (value) =>
_patchSettings({ evoConsolidateEvery: value }),
);
bindFloat("bme-setting-conflict-threshold", 0.85, 0.5, 0.99, (value) =>
_patchSettings({ conflictThreshold: value }),
);
bindNumber("bme-setting-synopsis-every", 5, 1, 100, (value) =>
_patchSettings({ synopsisEveryN: value }),
);
bindText("bme-setting-trigger-patterns", (value) =>
_patchSettings({ triggerPatterns: value }),
);
bindNumber("bme-setting-smart-trigger-threshold", 2, 1, 10, (value) =>
_patchSettings({ smartTriggerThreshold: value }),
);
bindFloat("bme-setting-forget-threshold", 0.5, 0.1, 1, (value) =>
_patchSettings({ forgetThreshold: value }),
);
bindNumber("bme-setting-sleep-every", 10, 1, 200, (value) =>
_patchSettings({ sleepEveryN: value }),
);
bindFloat("bme-setting-prob-recall-chance", 0.15, 0.01, 0.5, (value) =>
_patchSettings({ probRecallChance: value }),
);
bindNumber("bme-setting-reflect-every", 10, 1, 200, (value) =>
_patchSettings({ reflectEveryN: value }),
);
bindText("bme-setting-llm-url", (value) =>
_updateSettings?.({ llmApiUrl: value.trim() }),
_patchSettings({ llmApiUrl: value.trim() }),
);
bindText("bme-setting-llm-key", (value) =>
_updateSettings?.({ llmApiKey: value.trim() }),
_patchSettings({ llmApiKey: value.trim() }),
);
bindText("bme-setting-llm-model", (value) =>
_updateSettings?.({ llmModel: value.trim() }),
);
bindCheckbox("bme-setting-recall-llm", (checked) =>
_updateSettings?.({ recallEnableLLM: checked }),
);
bindNumber("bme-setting-recall-max-nodes", 8, 1, 50, (value) =>
_updateSettings?.({ recallMaxNodes: value }),
_patchSettings({ llmModel: value.trim() }),
);
bindText("bme-setting-embed-url", (value) =>
_updateSettings?.({ embeddingApiUrl: value.trim() }),
_patchSettings({ embeddingApiUrl: value.trim() }),
);
bindText("bme-setting-embed-key", (value) =>
_updateSettings?.({ embeddingApiKey: value.trim() }),
_patchSettings({ embeddingApiKey: value.trim() }),
);
bindText("bme-setting-embed-model", (value) =>
_updateSettings?.({ embeddingModel: value.trim() }),
_patchSettings({ embeddingModel: value.trim() }),
);
bindText("bme-setting-embed-mode", (value) => {
_updateSettings?.({ embeddingTransportMode: value });
_patchSettings({ embeddingTransportMode: value });
_toggleEmbedFields(value);
});
bindText("bme-setting-embed-backend-source", (value) => {
const patch = { embeddingBackendSource: value };
const settings = _getSettings?.() || {};
const patch = { embeddingBackendSource: value };
const suggestedModel = getSuggestedBackendModel(value);
if (!settings.embeddingBackendModel || settings.embeddingBackendModel === getSuggestedBackendModel(settings.embeddingBackendSource || "openai")) {
if (
!settings.embeddingBackendModel ||
settings.embeddingBackendModel ===
getSuggestedBackendModel(settings.embeddingBackendSource || "openai")
) {
patch.embeddingBackendModel = suggestedModel;
}
_updateSettings?.(patch);
_setInputValue("bme-setting-embed-backend-model", patch.embeddingBackendModel || settings.embeddingBackendModel || "");
_patchSettings(patch);
_setInputValue(
"bme-setting-embed-backend-model",
patch.embeddingBackendModel || settings.embeddingBackendModel || "",
);
});
bindText("bme-setting-embed-backend-model", (value) =>
_updateSettings?.({ embeddingBackendModel: value.trim() }),
_patchSettings({ embeddingBackendModel: value.trim() }),
);
bindText("bme-setting-embed-backend-url", (value) =>
_updateSettings?.({ embeddingBackendApiUrl: value.trim() }),
_patchSettings({ embeddingBackendApiUrl: value.trim() }),
);
bindCheckbox("bme-setting-embed-auto-suffix", (checked) =>
_updateSettings?.({ embeddingAutoSuffix: checked }),
_patchSettings({ embeddingAutoSuffix: checked }),
);
bindText("bme-setting-extract-prompt", (value) =>
_updateSettings?.({ extractPrompt: value }),
bindPromptText(
"bme-setting-extract-prompt",
"extractPrompt",
"extract",
);
bindText("bme-setting-recall-prompt", (value) =>
_updateSettings?.({ recallPrompt: value }),
bindPromptText(
"bme-setting-recall-prompt",
"recallPrompt",
"recall",
);
bindText("bme-setting-evolution-prompt", (value) =>
_updateSettings?.({ evolutionPrompt: value }),
bindPromptText(
"bme-setting-evolution-prompt",
"evolutionPrompt",
"evolution",
);
bindText("bme-setting-compress-prompt", (value) =>
_updateSettings?.({ compressPrompt: value }),
bindPromptText(
"bme-setting-compress-prompt",
"compressPrompt",
"compress",
);
bindText("bme-setting-synopsis-prompt", (value) =>
_updateSettings?.({ synopsisPrompt: value }),
bindPromptText(
"bme-setting-synopsis-prompt",
"synopsisPrompt",
"synopsis",
);
bindText("bme-setting-reflection-prompt", (value) =>
_updateSettings?.({ reflectionPrompt: value }),
bindPromptText(
"bme-setting-reflection-prompt",
"reflectionPrompt",
"reflection",
);
// 主题下拉菜单
panelEl.querySelectorAll(".bme-prompt-reset").forEach((button) => {
if (button.dataset.bmeBound === "true") return;
button.addEventListener("click", () => {
const settingKey = button.dataset.settingKey;
const promptKey = button.dataset.defaultPrompt;
const targetId = button.dataset.targetId;
if (!settingKey || !promptKey || !targetId) return;
_patchSettings({ [settingKey]: "" }, { refreshPrompts: true });
_setInputValue(targetId, DEFAULT_PROMPTS[promptKey] || "");
_refreshPromptCardStates();
});
button.dataset.bmeBound = "true";
});
const pickerBtn = document.getElementById("bme-theme-picker-btn");
const dropdown = document.getElementById("bme-theme-dropdown");
if (pickerBtn && dropdown) {
@@ -772,18 +1004,26 @@ function _bindConfigControls() {
opt.addEventListener("click", () => {
const theme = opt.dataset.theme;
if (!theme) return;
_updateSettings?.({ panelTheme: theme });
_highlightThemeOption(theme);
_patchSettings({ panelTheme: theme }, { refreshTheme: true });
dropdown.classList.remove("open");
});
});
// 点击外部关闭
document.addEventListener("click", () => {
dropdown.classList.remove("open");
});
dropdown.addEventListener("click", (e) => e.stopPropagation());
}
panelEl.querySelectorAll(".bme-theme-card").forEach((card) => {
if (card.dataset.bmeBound === "true") return;
card.addEventListener("click", () => {
const theme = card.dataset.theme;
if (!theme) return;
_patchSettings({ panelTheme: theme }, { refreshTheme: true });
});
card.dataset.bmeBound = "true";
});
document.getElementById("bme-test-llm")?.addEventListener("click", async () => {
await _actionHandlers.testMemoryLLM?.();
});
@@ -821,6 +1061,34 @@ function bindNumber(id, fallback, min, max, onChange) {
element.dataset.bmeBound = "true";
}
function bindFloat(id, fallback, min, max, onChange) {
const element = document.getElementById(id);
if (!element || element.dataset.bmeBound === "true") return;
element.addEventListener("input", () => {
let value = Number.parseFloat(element.value);
if (!Number.isFinite(value)) value = fallback;
value = Math.min(max, Math.max(min, value));
onChange(value);
});
element.dataset.bmeBound = "true";
}
function bindPromptText(id, settingKey, promptKey) {
const element = document.getElementById(id);
if (!element || element.dataset.bmeBound === "true") return;
const update = () => {
_patchSettings({ [settingKey]: element.value }, { refreshPrompts: true });
};
element.addEventListener("input", update);
element.addEventListener("change", update);
element.addEventListener("blur", () => {
if (!String(element.value || "").trim()) {
_setInputValue(id, DEFAULT_PROMPTS[promptKey] || "");
}
});
element.dataset.bmeBound = "true";
}
// ==================== 工具函数 ====================
function _setText(id, text) {
@@ -828,11 +1096,57 @@ function _setText(id, text) {
if (el) el.textContent = String(text);
}
function _highlightThemeOption(themeName) {
function _patchSettings(patch = {}, options = {}) {
const settings = _updateSettings?.(patch) || _getSettings?.() || {};
if (options.refreshGuards) _refreshGuardedConfigStates(settings);
if (options.refreshPrompts) _refreshPromptCardStates(settings);
if (options.refreshTheme) _highlightThemeChoice(settings.panelTheme || "crimson");
return settings;
}
function _highlightThemeChoice(themeName) {
if (!panelEl) return;
panelEl.querySelectorAll(".bme-theme-option").forEach((opt) => {
opt.classList.toggle("active", opt.dataset.theme === themeName);
});
panelEl.querySelectorAll(".bme-theme-card").forEach((card) => {
card.classList.toggle("active", card.dataset.theme === themeName);
});
}
function _refreshGuardedConfigStates(settings = _getSettings?.() || {}) {
if (!panelEl) return;
panelEl.querySelectorAll(".bme-guarded-card").forEach((card) => {
const guardKeys = String(card.dataset.guardSettings || "")
.split(",")
.map((key) => key.trim())
.filter(Boolean);
const enabled = guardKeys.every((key) => Boolean(settings[key]));
card.classList.toggle("is-disabled", !enabled);
const note = card.querySelector(".bme-config-guard-note");
note?.classList.toggle("visible", !enabled);
card.querySelectorAll("input, select, textarea, button").forEach((element) => {
element.disabled = !enabled;
});
});
}
function _refreshPromptCardStates(settings = _getSettings?.() || {}) {
if (!panelEl) return;
panelEl.querySelectorAll(".bme-prompt-card").forEach((card) => {
const settingKey = card.dataset.settingKey;
const statusEl = card.querySelector(".bme-prompt-status");
const resetButton = card.querySelector(".bme-prompt-reset");
const isCustom = Boolean(String(settings?.[settingKey] || "").trim());
card.classList.toggle("is-custom", isCustom);
if (statusEl) {
statusEl.textContent = isCustom ? "已自定义" : "默认";
statusEl.classList.toggle("is-custom", isCustom);
}
if (resetButton) {
resetButton.disabled = !isCustom;
}
});
}
function _toggleEmbedFields(mode) {

View File

@@ -1,450 +0,0 @@
<div class="st-bme-settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>ST-BME 图谱记忆</b>
<div
class="inline-drawer-icon fa-solid fa-circle-chevron-down down"
></div>
</div>
<div class="inline-drawer-content">
<!-- 基础设置 -->
<div class="st-bme-section">
<div class="st-bme-row">
<label class="checkbox_label" for="st_bme_enabled">
<input type="checkbox" id="st_bme_enabled" />
<span>启用记忆图谱</span>
</label>
</div>
<div class="st-bme-row">
<label for="st_bme_extract_every">每 N 条回复提取</label>
<input
type="number"
id="st_bme_extract_every"
class="text_pole"
min="1"
max="50"
value="1"
/>
</div>
<div class="st-bme-row">
<label for="st_bme_extract_context_turns">提取上下文轮数</label>
<input
type="number"
id="st_bme_extract_context_turns"
class="text_pole"
min="0"
max="20"
value="2"
/>
</div>
</div>
<hr class="st-bme-hr" />
<!-- 召回设置 -->
<div class="st-bme-section">
<h4 class="st-bme-section-title">召回设置</h4>
<div class="st-bme-row">
<label class="checkbox_label" for="st_bme_recall_enabled">
<input type="checkbox" id="st_bme_recall_enabled" />
<span>启用记忆召回注入</span>
</label>
</div>
<div class="st-bme-row">
<label class="checkbox_label" for="st_bme_recall_llm">
<input type="checkbox" id="st_bme_recall_llm" />
<span>启用 LLM 精确召回</span>
</label>
</div>
<div class="st-bme-row">
<label for="st_bme_recall_top_k">召回候选上限</label>
<input
type="number"
id="st_bme_recall_top_k"
class="text_pole"
min="1"
max="100"
value="15"
/>
</div>
<div class="st-bme-row">
<label for="st_bme_recall_max_nodes">LLM 精确召回上限</label>
<input
type="number"
id="st_bme_recall_max_nodes"
class="text_pole"
min="1"
max="50"
value="8"
/>
</div>
<div class="st-bme-row">
<label for="st_bme_inject_depth">注入深度</label>
<input
type="number"
id="st_bme_inject_depth"
class="text_pole"
min="0"
max="9999"
value="4"
/>
</div>
</div>
<hr class="st-bme-hr" />
<!-- 评分权重 -->
<div class="st-bme-section">
<h4 class="st-bme-section-title">混合评分权重</h4>
<div class="st-bme-row">
<label for="st_bme_graph_weight">图扩散权重</label>
<input
type="number"
id="st_bme_graph_weight"
class="text_pole"
min="0"
max="1"
step="0.1"
value="0.6"
/>
</div>
<div class="st-bme-row">
<label for="st_bme_vector_weight">向量权重</label>
<input
type="number"
id="st_bme_vector_weight"
class="text_pole"
min="0"
max="1"
step="0.1"
value="0.3"
/>
</div>
<div class="st-bme-row">
<label for="st_bme_importance_weight">重要性权重</label>
<input
type="number"
id="st_bme_importance_weight"
class="text_pole"
min="0"
max="1"
step="0.1"
value="0.1"
/>
</div>
</div>
<hr class="st-bme-hr" />
<!-- v2: 增强功能 -->
<div class="st-bme-section">
<h4 class="st-bme-section-title">
<i class="fa-solid fa-flask"></i> v2 增强功能
</h4>
<!-- P0 核心 -->
<div class="st-bme-subsection">
<span class="st-bme-badge st-bme-badge-p0">P0</span>
<div class="st-bme-row">
<label class="checkbox_label" for="st_bme_evolution">
<input type="checkbox" id="st_bme_evolution" />
<span>记忆进化 <small class="st-bme-hint">A-MEM</small></span>
</label>
</div>
<div class="st-bme-row st-bme-indent">
<label for="st_bme_evo_neighbors">近邻数量</label>
<input
type="number"
id="st_bme_evo_neighbors"
class="text_pole"
min="1"
max="10"
value="5"
/>
</div>
<div class="st-bme-row st-bme-indent">
<label for="st_bme_evo_consolidate_every">整理频率</label>
<input
type="number"
id="st_bme_evo_consolidate_every"
class="text_pole"
min="1"
max="500"
value="50"
/>
</div>
<div class="st-bme-row">
<label class="checkbox_label" for="st_bme_precise_conflict">
<input type="checkbox" id="st_bme_precise_conflict" />
<span>精确对照 <small class="st-bme-hint">Mem0</small></span>
</label>
</div>
<div class="st-bme-row st-bme-indent">
<label for="st_bme_conflict_threshold">对照阈值</label>
<input
type="number"
id="st_bme_conflict_threshold"
class="text_pole"
min="0.5"
max="0.99"
step="0.05"
value="0.85"
/>
</div>
<div class="st-bme-row">
<label class="checkbox_label" for="st_bme_synopsis">
<input type="checkbox" id="st_bme_synopsis" />
<span>全局概要 <small class="st-bme-hint">MemoRAG</small></span>
</label>
</div>
<div class="st-bme-row st-bme-indent">
<label for="st_bme_synopsis_every">每 N 次提取更新</label>
<input
type="number"
id="st_bme_synopsis_every"
class="text_pole"
min="1"
max="20"
value="5"
/>
</div>
</div>
<!-- P1 推荐 -->
<div class="st-bme-subsection">
<span class="st-bme-badge st-bme-badge-p1">P1</span>
<div class="st-bme-row">
<label class="checkbox_label" for="st_bme_visibility">
<input type="checkbox" id="st_bme_visibility" />
<span>认知边界 <small class="st-bme-hint">RoleRAG</small></span>
</label>
</div>
<div class="st-bme-row">
<label class="checkbox_label" for="st_bme_cross_recall">
<input type="checkbox" id="st_bme_cross_recall" />
<span>交叉检索 <small class="st-bme-hint">AriGraph</small></span>
</label>
</div>
</div>
<!-- P2 可选 -->
<div class="st-bme-subsection">
<span class="st-bme-badge st-bme-badge-p2">P2</span>
<div class="st-bme-row">
<label class="checkbox_label" for="st_bme_smart_trigger">
<input type="checkbox" id="st_bme_smart_trigger" />
<span>惊奇度分割 <small class="st-bme-hint">EM-LLM</small></span>
</label>
</div>
<div class="st-bme-row st-bme-indent">
<label for="st_bme_trigger_patterns">自定义触发模式</label>
<input
type="text"
id="st_bme_trigger_patterns"
class="text_pole"
placeholder="突然,真相,秘密,背叛 或 正则"
/>
</div>
<div class="st-bme-row st-bme-indent">
<label for="st_bme_smart_trigger_threshold">触发阈值</label>
<input
type="number"
id="st_bme_smart_trigger_threshold"
class="text_pole"
min="1"
max="10"
value="2"
/>
</div>
<div class="st-bme-row">
<label class="checkbox_label" for="st_bme_sleep_cycle">
<input type="checkbox" id="st_bme_sleep_cycle" />
<span>主动遗忘 <small class="st-bme-hint">SleepGate</small></span>
</label>
</div>
<div class="st-bme-row st-bme-indent">
<label for="st_bme_forget_threshold">遗忘阈值</label>
<input
type="number"
id="st_bme_forget_threshold"
class="text_pole"
min="0.1"
max="1.0"
step="0.1"
value="0.5"
/>
</div>
<div class="st-bme-row st-bme-indent">
<label for="st_bme_sleep_every">每 N 次提取遗忘</label>
<input
type="number"
id="st_bme_sleep_every"
class="text_pole"
min="1"
max="200"
value="10"
/>
</div>
<div class="st-bme-row">
<label class="checkbox_label" for="st_bme_prob_recall">
<input type="checkbox" id="st_bme_prob_recall" />
<span>概率触发回忆</span>
</label>
</div>
<div class="st-bme-row st-bme-indent">
<label for="st_bme_prob_chance">触发概率</label>
<input
type="number"
id="st_bme_prob_chance"
class="text_pole"
min="0.01"
max="0.5"
step="0.05"
value="0.15"
/>
</div>
<div class="st-bme-row">
<label class="checkbox_label" for="st_bme_reflection">
<input type="checkbox" id="st_bme_reflection" />
<span>反思条目 <small class="st-bme-hint">Reflexion</small></span>
</label>
</div>
<div class="st-bme-row st-bme-indent">
<label for="st_bme_reflect_every">每 N 次提取反思</label>
<input
type="number"
id="st_bme_reflect_every"
class="text_pole"
min="3"
max="30"
value="10"
/>
</div>
</div>
</div>
<hr class="st-bme-hr" />
<!-- Embedding API -->
<div class="st-bme-section">
<h4 class="st-bme-section-title">Embedding API</h4>
<div class="st-bme-row">
<label for="st_bme_embed_url">API 地址</label>
<input
type="text"
id="st_bme_embed_url"
class="text_pole"
placeholder="https://api.openai.com/v1"
/>
</div>
<div class="st-bme-row">
<label for="st_bme_embed_key">API Key</label>
<input
type="password"
id="st_bme_embed_key"
class="text_pole"
placeholder="sk-..."
/>
</div>
<div class="st-bme-row">
<label for="st_bme_embed_model">模型</label>
<input
type="text"
id="st_bme_embed_model"
class="text_pole"
placeholder="text-embedding-3-small"
/>
</div>
<div class="st-bme-row">
<button id="st_bme_btn_test_embed" class="menu_button">
<i class="fa-solid fa-plug"></i> 测试连接
</button>
</div>
</div>
<hr class="st-bme-hr" />
<!-- 操控面板 -->
<div class="st-bme-section">
<h4 class="st-bme-section-title">
<i class="fa-solid fa-display"></i> 操控面板
</h4>
<div class="st-bme-row">
<label for="st_bme_panel_theme">面板主题</label>
<select id="st_bme_panel_theme" class="text_pole">
<option value="crimson">🔴 Crimson Synth</option>
<option value="cyan">🔵 Neon Cyan</option>
<option value="amber">🟡 Amber Console</option>
<option value="violet">🟣 Violet Haze</option>
</select>
</div>
<div class="st-bme-btn-group">
<button id="st_bme_btn_open_panel" class="menu_button">
<i class="fa-solid fa-brain"></i> 打开操控面板
</button>
</div>
</div>
<hr class="st-bme-hr" />
<!-- 操作 -->
<div class="st-bme-section">
<h4 class="st-bme-section-title">操作</h4>
<div class="st-bme-btn-group">
<button id="st_bme_btn_view_graph" class="menu_button">
<i class="fa-solid fa-diagram-project"></i> 查看图谱
</button>
<button id="st_bme_btn_view_injection" class="menu_button">
<i class="fa-solid fa-syringe"></i> 查看注入
</button>
</div>
<div class="st-bme-btn-group">
<button id="st_bme_btn_rebuild" class="menu_button">
<i class="fa-solid fa-rotate"></i> 重建图谱
</button>
<button id="st_bme_btn_compress" class="menu_button">
<i class="fa-solid fa-compress"></i> 手动压缩
</button>
</div>
<div class="st-bme-btn-group">
<button id="st_bme_btn_export" class="menu_button">
<i class="fa-solid fa-download"></i> 导出
</button>
<button id="st_bme_btn_import" class="menu_button">
<i class="fa-solid fa-upload"></i> 导入
</button>
</div>
</div>
</div>
</div>
</div>

655
style.css
View File

@@ -1,132 +1,5 @@
/* ST-BME 样式 */
.st-bme-settings {
--bme-accent: #e94560;
--bme-accent-dim: rgba(233, 69, 96, 0.15);
--bme-surface: rgba(255, 255, 255, 0.03);
--bme-border: rgba(255, 255, 255, 0.08);
}
.st-bme-section {
padding: 8px 0;
}
.st-bme-section-title {
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--bme-accent);
margin: 0 0 8px 0;
font-weight: 600;
}
.st-bme-section-title i {
margin-right: 4px;
}
.st-bme-hr {
border: none;
border-top: 1px solid var(--bme-border);
margin: 4px 0;
}
.st-bme-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 4px 0;
}
.st-bme-row label {
font-size: 12px;
flex-shrink: 0;
min-width: 100px;
}
.st-bme-row .text_pole {
max-width: 180px;
}
.st-bme-row .checkbox_label {
justify-content: flex-start;
gap: 8px;
}
.st-bme-row.st-bme-indent {
padding-left: 24px;
}
.st-bme-btn-group {
display: flex;
gap: 6px;
margin: 4px 0;
}
.st-bme-btn-group .menu_button {
flex: 1;
font-size: 12px;
padding: 6px 10px;
border-radius: 6px;
border: 1px solid var(--bme-border);
transition: all 0.2s ease;
}
.st-bme-btn-group .menu_button:hover {
border-color: var(--bme-accent);
background: var(--bme-accent-dim);
}
.st-bme-btn-group .menu_button i {
margin-right: 4px;
}
/* v2: 子分区 */
.st-bme-subsection {
position: relative;
padding: 6px 0 6px 4px;
margin: 4px 0;
border-left: 2px solid var(--bme-border);
}
/* v2: 优先级 badge */
.st-bme-badge {
display: inline-block;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.5px;
padding: 1px 6px;
border-radius: 3px;
margin-bottom: 4px;
}
.st-bme-badge-p0 {
background: rgba(233, 69, 96, 0.25);
color: #e94560;
border: 1px solid rgba(233, 69, 96, 0.4);
}
.st-bme-badge-p1 {
background: rgba(255, 193, 7, 0.2);
color: #ffc107;
border: 1px solid rgba(255, 193, 7, 0.35);
}
.st-bme-badge-p2 {
background: rgba(76, 175, 80, 0.15);
color: #66bb6a;
border: 1px solid rgba(76, 175, 80, 0.3);
}
/* v2: 技术来源提示 */
.st-bme-hint {
font-size: 10px;
color: rgba(255, 255, 255, 0.35);
font-style: italic;
}
/* ==================== 操控面板 ==================== */
/* --- Overlay --- */
#st-bme-panel-overlay {
position: fixed;
@@ -404,6 +277,89 @@
padding: 12px;
}
.bme-config-sidebar {
display: none;
flex: 1;
flex-direction: column;
gap: 14px;
padding: 18px 12px 16px;
overflow-y: auto;
}
.bme-config-sidebar-header {
padding: 0 2px;
}
.bme-config-sidebar-kicker,
.bme-config-workspace-kicker,
.bme-config-section-kicker {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.9px;
color: var(--bme-primary);
margin-bottom: 6px;
}
.bme-config-sidebar-title {
font-size: 18px;
font-weight: 700;
color: var(--bme-on-surface);
margin-bottom: 6px;
}
.bme-config-sidebar-help,
.bme-config-workspace-desc,
.bme-config-section-desc,
.bme-config-card-subtitle {
font-size: 12px;
line-height: 1.5;
color: var(--bme-on-surface-dim);
}
.bme-config-nav {
display: flex;
gap: 8px;
}
.bme-config-nav-desktop {
flex-direction: column;
}
.bme-config-nav-mobile {
display: none;
}
.bme-config-nav-btn {
display: flex;
align-items: center;
gap: 10px;
border: 1px solid var(--bme-border);
background: var(--bme-surface-low);
color: var(--bme-on-surface-dim);
border-radius: 10px;
padding: 10px 12px;
cursor: pointer;
transition: border-color 0.15s, background 0.15s, color 0.15s, transform 0.15s;
font-size: 12px;
text-align: left;
}
.bme-config-nav-btn:hover {
border-color: var(--bme-primary);
color: var(--bme-on-surface);
background: var(--bme-surface-high);
}
.bme-config-nav-btn.active {
border-color: var(--bme-primary);
color: var(--bme-primary);
background: var(--bme-primary-dim);
}
.bme-config-nav-btn i {
font-size: 13px;
}
.bme-tab-pane {
display: none;
}
@@ -517,6 +473,41 @@
overflow: hidden;
}
.bme-graph-workspace {
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
position: relative;
}
.bme-config-workspace {
display: none;
flex: 1;
flex-direction: column;
min-height: 0;
background:
radial-gradient(circle at top right, rgba(233, 69, 96, 0.1), transparent 32%),
linear-gradient(180deg, rgba(255, 255, 255, 0.02), transparent 20%),
var(--bme-surface-lowest, #0e0e11);
}
#st-bme-panel.config-mode .bme-tab-content {
display: none;
}
#st-bme-panel.config-mode .bme-config-sidebar {
display: flex;
}
#st-bme-panel.config-mode .bme-graph-workspace {
display: none;
}
#st-bme-panel.config-mode .bme-config-workspace {
display: flex;
}
.bme-graph-toolbar {
display: flex;
align-items: center;
@@ -792,13 +783,82 @@
background: rgba(255, 82, 82, 0.1);
}
/* --- Config Tab --- */
.bme-config-card {
background: var(--bme-surface-low);
border: 1px solid var(--bme-border);
border-radius: 8px;
padding: 12px;
margin-bottom: 10px;
/* --- Config Workspace --- */
.bme-config-workspace-header {
padding: 20px 22px 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
flex-shrink: 0;
}
.bme-config-workspace-title,
.bme-config-section-title {
margin: 0;
color: var(--bme-on-surface);
}
.bme-config-workspace-title {
font-size: 24px;
line-height: 1.2;
margin-bottom: 8px;
}
.bme-config-sections {
flex: 1;
overflow-y: auto;
padding: 18px 22px 22px;
}
.bme-config-section {
display: none;
}
.bme-config-section.active {
display: block;
}
.bme-config-section-head {
margin-bottom: 18px;
}
.bme-config-section-title {
font-size: 20px;
line-height: 1.25;
margin-bottom: 8px;
}
.bme-config-grid {
display: grid;
grid-template-columns: 1fr;
gap: 14px;
}
.bme-config-grid-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.bme-config-card,
.bme-prompt-card {
background: rgba(255, 255, 255, 0.025);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 14px;
padding: 16px;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.02);
}
.bme-config-card-head,
.bme-prompt-card-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 14px;
}
.bme-config-card-title {
font-size: 14px;
font-weight: 700;
color: var(--bme-on-surface);
margin-bottom: 4px;
}
.bme-config-help {
@@ -812,7 +872,11 @@
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 10px;
margin-bottom: 12px;
}
.bme-config-row:last-child {
margin-bottom: 0;
}
.bme-config-row.inline {
@@ -821,21 +885,21 @@
}
.bme-config-row label {
font-size: 11px;
font-size: 12px;
color: var(--bme-on-surface);
}
.bme-config-input,
.bme-config-textarea {
width: 100%;
background: var(--bme-surface-lowest);
border: 1px solid var(--bme-border);
border-radius: 6px;
padding: 8px 10px;
background: rgba(0, 0, 0, 0.22);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 10px;
padding: 10px 12px;
color: var(--bme-on-surface);
font-size: 12px;
outline: none;
transition: border-color 0.15s, box-shadow 0.15s;
transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
}
.bme-config-input:focus,
@@ -844,49 +908,215 @@
box-shadow: 0 0 0 2px var(--bme-primary-dim);
}
.bme-config-input:disabled,
.bme-config-textarea:disabled {
background: rgba(255, 255, 255, 0.035);
color: rgba(255, 255, 255, 0.45);
cursor: not-allowed;
}
.bme-config-textarea {
min-height: 140px;
min-height: 180px;
resize: vertical;
line-height: 1.5;
line-height: 1.55;
font-family: 'Cascadia Code', 'Fira Code', monospace;
}
.bme-prompt-group {
margin-top: 8px;
border: 1px solid var(--bme-border);
border-radius: 6px;
overflow: hidden;
}
.bme-prompt-group summary {
padding: 8px 12px;
cursor: pointer;
font-size: 12px;
font-weight: 500;
color: var(--bme-on-surface);
background: var(--bme-surface-lowest);
user-select: none;
transition: background 0.15s;
}
.bme-prompt-group summary:hover {
background: var(--bme-surface-low);
}
.bme-prompt-group[open] summary {
border-bottom: 1px solid var(--bme-border);
}
.bme-prompt-group .bme-config-textarea {
border: none;
border-radius: 0;
min-height: 120px;
}
.bme-config-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
flex-wrap: wrap;
}
.bme-config-guard-note {
display: none;
align-self: center;
font-size: 11px;
color: var(--bme-accent3, #ffc107);
background: rgba(255, 193, 7, 0.12);
border: 1px solid rgba(255, 193, 7, 0.18);
border-radius: 999px;
padding: 5px 10px;
}
.bme-config-guard-note.visible {
display: inline-flex;
}
.bme-guarded-card.is-disabled {
opacity: 0.72;
}
.bme-config-placeholder {
background: var(--bme-surface-low);
border: 1px dashed var(--bme-border);
border-radius: 12px;
padding: 16px;
}
.bme-config-placeholder-title {
font-size: 13px;
font-weight: 700;
color: var(--bme-on-surface);
margin-bottom: 6px;
}
.bme-config-placeholder-help {
font-size: 11px;
color: var(--bme-on-surface-dim);
line-height: 1.5;
}
.bme-toggle-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.bme-toggle-item {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
padding: 12px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.05);
cursor: pointer;
}
.bme-toggle-item:hover {
border-color: rgba(255, 255, 255, 0.12);
background: rgba(255, 255, 255, 0.035);
}
.bme-toggle-copy {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.bme-toggle-title {
font-size: 12px;
font-weight: 700;
color: var(--bme-on-surface);
}
.bme-toggle-desc {
font-size: 11px;
line-height: 1.45;
color: var(--bme-on-surface-dim);
}
.bme-toggle-item input {
width: 18px;
height: 18px;
margin-top: 2px;
flex-shrink: 0;
accent-color: var(--bme-primary);
}
.bme-prompt-card-actions {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.bme-prompt-status {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 64px;
padding: 4px 10px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.06);
color: var(--bme-on-surface-dim);
font-size: 11px;
font-weight: 600;
}
.bme-prompt-status.is-custom {
background: var(--bme-primary-dim);
color: var(--bme-primary);
}
.bme-prompt-reset:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.bme-theme-card-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.bme-theme-card {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
padding: 14px;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.07);
background: rgba(255, 255, 255, 0.025);
color: var(--bme-on-surface);
text-align: left;
cursor: pointer;
transition: border-color 0.15s, background 0.15s, transform 0.15s;
}
.bme-theme-card:hover {
border-color: var(--bme-primary);
transform: translateY(-1px);
}
.bme-theme-card.active {
border-color: var(--bme-primary);
background: var(--bme-primary-dim);
}
.bme-theme-card-swatch {
width: 16px;
height: 16px;
border-radius: 50%;
flex-shrink: 0;
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.14);
}
.bme-theme-card-copy {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
flex: 1;
}
.bme-theme-card-title {
font-size: 13px;
font-weight: 700;
}
.bme-theme-card-desc {
font-size: 11px;
line-height: 1.45;
color: var(--bme-on-surface-dim);
}
.bme-theme-card.active .bme-theme-card-desc {
color: var(--bme-primary-text, var(--bme-on-surface));
}
.bme-theme-card-check {
opacity: 0;
color: var(--bme-primary);
}
.bme-theme-card.active .bme-theme-card-check {
opacity: 1;
}
/* --- Mobile Bottom Tab Bar --- */
@@ -972,18 +1202,24 @@
/* --- Scrollbar --- */
.bme-tab-content::-webkit-scrollbar,
.bme-config-sidebar::-webkit-scrollbar,
.bme-config-sections::-webkit-scrollbar,
.bme-injection-preview::-webkit-scrollbar,
.bme-node-detail::-webkit-scrollbar {
width: 4px;
}
.bme-tab-content::-webkit-scrollbar-track,
.bme-config-sidebar::-webkit-scrollbar-track,
.bme-config-sections::-webkit-scrollbar-track,
.bme-injection-preview::-webkit-scrollbar-track,
.bme-node-detail::-webkit-scrollbar-track {
background: transparent;
}
.bme-tab-content::-webkit-scrollbar-thumb,
.bme-config-sidebar::-webkit-scrollbar-thumb,
.bme-config-sections::-webkit-scrollbar-thumb,
.bme-injection-preview::-webkit-scrollbar-thumb,
.bme-node-detail::-webkit-scrollbar-thumb {
background: var(--bme-surface-highest);
@@ -1011,7 +1247,6 @@
flex-direction: column;
}
/* 手机端sidebar 全宽显示,成为主内容区 */
.bme-panel-sidebar {
width: 100%;
min-width: unset;
@@ -1019,27 +1254,81 @@
border-right: none;
}
/* 隐藏 sidebar 顶部 tab 列表,改用底部 tab bar */
.bme-panel-sidebar > .bme-tab-list {
display: none;
}
/* 手机端 tab content 撑满剩余空间 */
.bme-tab-content {
padding: 12px 14px;
}
/* 手机端:隐藏桌面端图谱区(大图谱) */
.bme-panel-main {
display: none;
}
/* 手机端底部 Tab Bar 显示 */
.bme-panel-tabbar {
display: flex;
}
/* 总览 Tab: 统计卡横向滚动 */
.bme-resize-handle,
.bme-config-sidebar {
display: none !important;
}
.bme-config-nav-mobile {
display: flex;
overflow-x: auto;
padding: 0 14px 6px;
margin-bottom: 4px;
-webkit-overflow-scrolling: touch;
}
.bme-config-nav-mobile .bme-config-nav-btn {
flex: 0 0 auto;
white-space: nowrap;
}
.bme-config-nav-mobile::-webkit-scrollbar {
display: none;
}
.bme-config-workspace-header {
padding: 18px 14px 10px;
}
.bme-config-workspace-title {
font-size: 20px;
}
.bme-config-sections {
padding: 14px;
}
.bme-config-grid-2,
.bme-theme-card-grid {
grid-template-columns: 1fr;
}
.bme-config-card-head,
.bme-prompt-card-head {
flex-direction: column;
align-items: flex-start;
}
.bme-prompt-card-actions {
width: 100%;
justify-content: space-between;
}
#st-bme-panel.config-mode .bme-panel-sidebar {
display: none;
}
#st-bme-panel.config-mode .bme-panel-main {
display: flex;
flex: 1;
}
.bme-stats-grid {
display: flex;
gap: 6px;
@@ -1053,7 +1342,6 @@
flex-shrink: 0;
}
/* 搜索栏改为纵向堆叠 */
.bme-search-bar {
flex-direction: column;
}
@@ -1062,7 +1350,6 @@
width: 100%;
}
/* 操作按钮放大触控区域 */
.bme-action-btn {
padding: 18px 8px;
font-size: 12px;
@@ -1072,7 +1359,6 @@
font-size: 22px;
}
/* 节点详情:手机端全宽覆盖 */
.bme-node-detail {
width: 100%;
}
@@ -1081,7 +1367,6 @@
font-size: 9px;
}
/* 手机端图谱预览(嵌在总览 Tab 内) */
.bme-mobile-graph-preview {
display: block;
height: 200px;