mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
Fix plugin-only memory settings and UI flow
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// ST-BME: 层级压缩引擎
|
||||
// 超过阈值的节点被 LLM 总结为更高层级的压缩节点
|
||||
|
||||
import { createNode, addNode, getActiveNodes, getNode } from './graph.js';
|
||||
import { createNode, addNode, createEdge, addEdge, getActiveNodes, getNode } from './graph.js';
|
||||
import { callLLMForJSON } from './llm.js';
|
||||
import { embedText } from './embedding.js';
|
||||
|
||||
@@ -100,6 +100,7 @@ async function compressLevel({ graph, typeDef, level, embeddingConfig, force })
|
||||
}
|
||||
|
||||
addNode(graph, compressedNode);
|
||||
migrateBatchEdges(graph, batch, compressedNode);
|
||||
created++;
|
||||
|
||||
// 归档子节点
|
||||
@@ -113,6 +114,40 @@ async function compressLevel({ graph, typeDef, level, embeddingConfig, force })
|
||||
return { created, archived };
|
||||
}
|
||||
|
||||
function migrateBatchEdges(graph, batch, compressedNode) {
|
||||
const batchIds = new Set(batch.map(node => node.id));
|
||||
const activeNodeIds = new Set(getActiveNodes(graph).map(node => node.id));
|
||||
|
||||
for (const edge of graph.edges) {
|
||||
if (edge.invalidAt || edge.expiredAt) continue;
|
||||
|
||||
const fromInside = batchIds.has(edge.fromId);
|
||||
const toInside = batchIds.has(edge.toId);
|
||||
if (!fromInside && !toInside) continue;
|
||||
if (fromInside && toInside) continue;
|
||||
|
||||
const newFromId = fromInside ? compressedNode.id : edge.fromId;
|
||||
const newToId = toInside ? compressedNode.id : edge.toId;
|
||||
|
||||
if (newFromId === newToId) continue;
|
||||
if (!activeNodeIds.has(newFromId) || !activeNodeIds.has(newToId)) continue;
|
||||
if (!getNode(graph, newFromId) || !getNode(graph, newToId)) continue;
|
||||
|
||||
const migratedEdge = createEdge({
|
||||
fromId: newFromId,
|
||||
toId: newToId,
|
||||
relation: edge.relation,
|
||||
strength: edge.strength,
|
||||
edgeType: edge.edgeType,
|
||||
});
|
||||
migratedEdge.validAt = edge.validAt ?? migratedEdge.validAt;
|
||||
migratedEdge.invalidAt = edge.invalidAt ?? migratedEdge.invalidAt;
|
||||
migratedEdge.expiredAt = edge.expiredAt ?? migratedEdge.expiredAt;
|
||||
|
||||
addEdge(graph, migratedEdge);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用 LLM 总结一批节点
|
||||
*/
|
||||
|
||||
25
embedding.js
25
embedding.js
@@ -6,6 +6,13 @@
|
||||
* 调用外部 API 获取文本向量,并提供暴力搜索 cosine 相似度
|
||||
*/
|
||||
|
||||
function normalizeOpenAICompatibleBaseUrl(value) {
|
||||
return String(value || '')
|
||||
.trim()
|
||||
.replace(/\/+(chat\/completions|embeddings)$/i, '')
|
||||
.replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用外部 Embedding API
|
||||
*
|
||||
@@ -17,19 +24,18 @@
|
||||
* @returns {Promise<Float64Array|null>} 向量或 null
|
||||
*/
|
||||
export async function embedText(text, config) {
|
||||
if (!text || !config.apiUrl || !config.apiKey || !config.model) {
|
||||
const apiUrl = normalizeOpenAICompatibleBaseUrl(config?.apiUrl);
|
||||
if (!text || !apiUrl || !config?.model) {
|
||||
console.warn('[ST-BME] Embedding 配置不完整,跳过');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = `${config.apiUrl.replace(/\/+$/, '')}/embeddings`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
const response = await fetch(`${apiUrl}/embeddings`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${config.apiKey}`,
|
||||
...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: config.model,
|
||||
@@ -66,18 +72,17 @@ export async function embedText(text, config) {
|
||||
* @returns {Promise<(Float64Array|null)[]>}
|
||||
*/
|
||||
export async function embedBatch(texts, config) {
|
||||
if (!texts.length || !config.apiUrl || !config.apiKey || !config.model) {
|
||||
const apiUrl = normalizeOpenAICompatibleBaseUrl(config?.apiUrl);
|
||||
if (!texts.length || !apiUrl || !config?.model) {
|
||||
return texts.map(() => null);
|
||||
}
|
||||
|
||||
try {
|
||||
const url = `${config.apiUrl.replace(/\/+$/, '')}/embeddings`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
const response = await fetch(`${apiUrl}/embeddings`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${config.apiKey}`,
|
||||
...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: config.model,
|
||||
|
||||
@@ -77,19 +77,22 @@ export class GraphRenderer {
|
||||
*/
|
||||
loadGraph(graph) {
|
||||
this.nodeMap.clear();
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const viewportWidth = this.canvas.width / dpr;
|
||||
const viewportHeight = this.canvas.height / dpr;
|
||||
|
||||
// 转换节点
|
||||
const activeNodes = graph.nodes.filter(n => !n.archived);
|
||||
this.nodes = activeNodes.map((n, i) => {
|
||||
const angle = (2 * Math.PI * i) / activeNodes.length;
|
||||
const r = Math.min(this.canvas.width, this.canvas.height) * 0.3;
|
||||
const r = Math.min(viewportWidth, viewportHeight) * 0.3;
|
||||
const node = {
|
||||
id: n.id,
|
||||
type: n.type || 'event',
|
||||
name: getNodeDisplayName(n),
|
||||
importance: n.importance || 5,
|
||||
x: this.canvas.width / 2 + r * Math.cos(angle) + (Math.random() - 0.5) * 40,
|
||||
y: this.canvas.height / 2 + r * Math.sin(angle) + (Math.random() - 0.5) * 40,
|
||||
x: viewportWidth / 2 + r * Math.cos(angle) + (Math.random() - 0.5) * 40,
|
||||
y: viewportHeight / 2 + r * Math.sin(angle) + (Math.random() - 0.5) * 40,
|
||||
vx: 0,
|
||||
vy: 0,
|
||||
pinned: false,
|
||||
|
||||
12
graph.js
12
graph.js
@@ -328,9 +328,15 @@ export function getNodeEdges(graph, nodeId) {
|
||||
*/
|
||||
export function buildAdjacencyMap(graph) {
|
||||
const adj = new Map();
|
||||
const activeNodeIds = new Set(
|
||||
graph.nodes.filter((node) => !node.archived).map((node) => node.id),
|
||||
);
|
||||
|
||||
for (const edge of graph.edges) {
|
||||
if (!isEdgeActive(edge)) continue;
|
||||
if (!activeNodeIds.has(edge.fromId) || !activeNodeIds.has(edge.toId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!adj.has(edge.fromId)) adj.set(edge.fromId, []);
|
||||
adj.get(edge.fromId).push({
|
||||
@@ -358,9 +364,15 @@ export function buildAdjacencyMap(graph) {
|
||||
*/
|
||||
export function buildTemporalAdjacencyMap(graph) {
|
||||
const adj = new Map();
|
||||
const activeNodeIds = new Set(
|
||||
graph.nodes.filter((node) => !node.archived).map((node) => node.id),
|
||||
);
|
||||
|
||||
for (const edge of graph.edges) {
|
||||
if (!isEdgeActive(edge)) continue;
|
||||
if (!activeNodeIds.has(edge.fromId) || !activeNodeIds.has(edge.toId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!adj.has(edge.fromId)) adj.set(edge.fromId, []);
|
||||
adj.get(edge.fromId).push({
|
||||
|
||||
133
index.js
133
index.js
@@ -4,6 +4,7 @@
|
||||
import {
|
||||
eventSource,
|
||||
event_types,
|
||||
getRequestHeaders,
|
||||
saveSettingsDebounced,
|
||||
} from "../../../../script.js";
|
||||
import {
|
||||
@@ -39,6 +40,8 @@ let _themesModule = null;
|
||||
|
||||
const MODULE_NAME = "st_bme";
|
||||
const GRAPH_METADATA_KEY = "st_bme_graph";
|
||||
const SERVER_SETTINGS_FILENAME = "st-bme-settings.json";
|
||||
const SERVER_SETTINGS_URL = `/user/files/${SERVER_SETTINGS_FILENAME}`;
|
||||
|
||||
// ==================== 默认设置 ====================
|
||||
|
||||
@@ -132,6 +135,7 @@ let lastInjectionContent = "";
|
||||
let lastExtractedItems = []; // 最近提取的节点(面板展示用)
|
||||
let lastRecalledItems = []; // 最近召回的节点(面板展示用)
|
||||
let extractionCount = 0; // v2: 提取次数计数器(定期触发概要/遗忘/反思)
|
||||
let serverSettingsSaveTimer = null;
|
||||
|
||||
function getNodeDisplayName(node) {
|
||||
return (
|
||||
@@ -223,11 +227,115 @@ function getEmbeddingConfig() {
|
||||
};
|
||||
}
|
||||
|
||||
function getPersistedSettingsSnapshot(settings = getSettings()) {
|
||||
const persisted = {};
|
||||
for (const key of Object.keys(defaultSettings)) {
|
||||
persisted[key] = settings[key];
|
||||
}
|
||||
return persisted;
|
||||
}
|
||||
|
||||
function mergePersistedSettings(loaded = {}) {
|
||||
const merged = { ...defaultSettings };
|
||||
for (const key of Object.keys(defaultSettings)) {
|
||||
if (Object.prototype.hasOwnProperty.call(loaded, key)) {
|
||||
merged[key] = loaded[key];
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
function encodeBase64Utf8(text) {
|
||||
const bytes = new TextEncoder().encode(String(text ?? ""));
|
||||
const chunkSize = 0x8000;
|
||||
let binary = "";
|
||||
|
||||
for (let i = 0; i < bytes.length; i += chunkSize) {
|
||||
binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
|
||||
}
|
||||
|
||||
return btoa(binary);
|
||||
}
|
||||
|
||||
async function loadServerSettings() {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${SERVER_SETTINGS_URL}?t=${Date.now()}`,
|
||||
{ cache: "no-store" },
|
||||
);
|
||||
|
||||
if (response.status === 404) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText || `HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const loaded = await response.json();
|
||||
if (loaded && typeof loaded === "object" && !Array.isArray(loaded)) {
|
||||
extension_settings[MODULE_NAME] = mergePersistedSettings(loaded);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("[ST-BME] 读取服务端设置失败,回退到本地运行时设置:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveServerSettings(settings = getSettings()) {
|
||||
const payload = JSON.stringify(
|
||||
getPersistedSettingsSnapshot(settings),
|
||||
null,
|
||||
2,
|
||||
);
|
||||
|
||||
const response = await fetch("/api/files/upload", {
|
||||
method: "POST",
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
name: SERVER_SETTINGS_FILENAME,
|
||||
data: encodeBase64Utf8(payload),
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const message = await response.text().catch(() => response.statusText);
|
||||
throw new Error(message || `HTTP ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleServerSettingsSave() {
|
||||
clearTimeout(serverSettingsSaveTimer);
|
||||
serverSettingsSaveTimer = setTimeout(async () => {
|
||||
try {
|
||||
await saveServerSettings();
|
||||
} catch (error) {
|
||||
console.error("[ST-BME] 保存服务端设置失败:", error);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function updateModuleSettings(patch = {}) {
|
||||
const settings = getSettings();
|
||||
Object.assign(settings, patch);
|
||||
extension_settings[MODULE_NAME] = settings;
|
||||
saveSettingsDebounced();
|
||||
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(patch, "enabled") &&
|
||||
patch.enabled === false
|
||||
) {
|
||||
try {
|
||||
const context = getContext();
|
||||
context.setExtensionPrompt(MODULE_NAME, "", 1, 0);
|
||||
lastInjectionContent = "";
|
||||
lastRecalledItems = [];
|
||||
} catch (error) {
|
||||
console.warn("[ST-BME] 关闭插件时清理注入失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
scheduleServerSettingsSave();
|
||||
return settings;
|
||||
}
|
||||
|
||||
@@ -630,6 +738,12 @@ async function runRecall() {
|
||||
function onChatChanged() {
|
||||
loadGraphFromChat();
|
||||
lastInjectionContent = "";
|
||||
try {
|
||||
const context = getContext();
|
||||
context.setExtensionPrompt(MODULE_NAME, "", 1, 0);
|
||||
} catch (error) {
|
||||
console.warn("[ST-BME] 清理旧注入失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function onGenerationAfterCommands() {
|
||||
@@ -758,8 +872,8 @@ async function onViewLastInjection() {
|
||||
|
||||
async function onTestEmbedding() {
|
||||
const config = getEmbeddingConfig();
|
||||
if (!config.apiUrl || !config.apiKey) {
|
||||
toastr.warning("请先配置 Embedding API 地址和 Key");
|
||||
if (!config.apiUrl || !config.model) {
|
||||
toastr.warning("请先配置 Embedding API 地址和模型");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1152,6 +1266,8 @@ function bindSettingsUI() {
|
||||
// ==================== 初始化 ====================
|
||||
|
||||
(async function init() {
|
||||
await loadServerSettings();
|
||||
|
||||
// 注册事件钩子
|
||||
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
|
||||
eventSource.on(
|
||||
@@ -1229,19 +1345,6 @@ function bindSettingsUI() {
|
||||
}
|
||||
}
|
||||
|
||||
// 主题选择绑定
|
||||
$('#st_bme_panel_theme')
|
||||
.val(settings.panelTheme || 'crimson')
|
||||
.on('change', function () {
|
||||
const theme = $(this).val();
|
||||
updateModuleSettings({ panelTheme: theme });
|
||||
_themesModule?.applyTheme(theme);
|
||||
_panelModule?.updatePanelTheme(theme);
|
||||
});
|
||||
|
||||
// 打开面板按钮
|
||||
$('#st_bme_btn_open_panel').on('click', () => _panelModule?.openPanel());
|
||||
|
||||
console.log("[ST-BME] 操控面板初始化完成");
|
||||
} catch (panelError) {
|
||||
console.error("[ST-BME] 操控面板加载失败(核心功能不受影响):", panelError);
|
||||
|
||||
50
llm.js
50
llm.js
@@ -2,19 +2,27 @@
|
||||
// 包装 ST 的 sendOpenAIRequest,提供结构化 JSON 输出和重试机制
|
||||
|
||||
import { extension_settings } from "../../../extensions.js";
|
||||
import { sendOpenAIRequest } from "../../../openai.js";
|
||||
import { chat_completion_sources, sendOpenAIRequest } from "../../../openai.js";
|
||||
import { getRequestHeaders } from "../../../../script.js";
|
||||
|
||||
const MODULE_NAME = "st_bme";
|
||||
|
||||
function getMemoryLLMConfig() {
|
||||
const settings = extension_settings[MODULE_NAME] || {};
|
||||
return {
|
||||
apiUrl: String(settings.llmApiUrl || '').trim(),
|
||||
apiUrl: normalizeOpenAICompatibleBaseUrl(settings.llmApiUrl),
|
||||
apiKey: String(settings.llmApiKey || '').trim(),
|
||||
model: String(settings.llmModel || '').trim(),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeOpenAICompatibleBaseUrl(value) {
|
||||
return String(value || '')
|
||||
.trim()
|
||||
.replace(/\/+(chat\/completions|embeddings)$/i, '')
|
||||
.replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
function hasDedicatedLLMConfig(config = getMemoryLLMConfig()) {
|
||||
return Boolean(config.apiUrl && config.model);
|
||||
}
|
||||
@@ -25,34 +33,40 @@ async function callDedicatedOpenAICompatible(messages, { signal } = {}) {
|
||||
return await sendOpenAIRequest('quiet', messages, signal);
|
||||
}
|
||||
|
||||
const url = `${config.apiUrl.replace(/\/+$/, '')}/chat/completions`;
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
if (config.apiKey) {
|
||||
headers.Authorization = `Bearer ${config.apiKey}`;
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
const response = await fetch('/api/backends/chat-completions/generate', {
|
||||
method: 'POST',
|
||||
headers,
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
chat_completion_source: chat_completion_sources.OPENAI,
|
||||
reverse_proxy: config.apiUrl,
|
||||
proxy_password: config.apiKey || '',
|
||||
model: config.model,
|
||||
messages,
|
||||
temperature: 0.2,
|
||||
max_tokens: 1200,
|
||||
max_completion_tokens: 1200,
|
||||
stream: false,
|
||||
}),
|
||||
signal,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text().catch(() => '');
|
||||
throw new Error(
|
||||
`Memory LLM API error ${response.status}: ${errorText || response.statusText}`,
|
||||
);
|
||||
const responseText = await response.text().catch(() => '');
|
||||
let data;
|
||||
|
||||
try {
|
||||
data = responseText ? JSON.parse(responseText) : {};
|
||||
} catch {
|
||||
data = { error: { message: responseText || response.statusText } };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (!response.ok) {
|
||||
const message = data?.error?.message || response.statusText;
|
||||
throw new Error(`Memory LLM proxy error ${response.status}: ${message}`);
|
||||
}
|
||||
|
||||
if (data?.error?.message) {
|
||||
throw new Error(`Memory LLM proxy error: ${data.error.message}`);
|
||||
}
|
||||
const content = data?.choices?.[0]?.message?.content;
|
||||
if (typeof content === 'string') {
|
||||
return content;
|
||||
|
||||
33
panel.html
33
panel.html
@@ -142,10 +142,38 @@
|
||||
|
||||
<!-- Config Tab -->
|
||||
<div class="bme-tab-pane" id="bme-pane-config">
|
||||
<div class="bme-config-card">
|
||||
<div class="bme-section-header">基础开关</div>
|
||||
<div class="bme-config-row inline">
|
||||
<label class="checkbox_label" for="bme-setting-enabled">
|
||||
<input id="bme-setting-enabled" type="checkbox" />
|
||||
<span>启用 ST-BME 自动记忆</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="bme-config-row inline">
|
||||
<label class="checkbox_label" for="bme-setting-recall-enabled">
|
||||
<input id="bme-setting-recall-enabled" type="checkbox" />
|
||||
<span>启用生成前记忆召回</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="bme-config-row">
|
||||
<label for="bme-setting-extract-every">每 N 条回复提取</label>
|
||||
<input id="bme-setting-extract-every" class="bme-config-input" type="number" min="1" max="50" />
|
||||
</div>
|
||||
<div class="bme-config-row">
|
||||
<label for="bme-setting-extract-context-turns">提取上下文轮数</label>
|
||||
<input id="bme-setting-extract-context-turns" class="bme-config-input" type="number" min="0" max="20" />
|
||||
</div>
|
||||
<div class="bme-config-row">
|
||||
<label for="bme-setting-inject-depth">注入深度</label>
|
||||
<input id="bme-setting-inject-depth" class="bme-config-input" type="number" min="0" max="9999" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bme-config-card">
|
||||
<div class="bme-section-header">记忆 LLM</div>
|
||||
<div class="bme-config-help">
|
||||
这里配置的是 ST-BME 的第二套记忆 LLM。留空时,提取/召回/概要/反思会复用当前 SillyTavern 聊天模型。
|
||||
这里配置的是 ST-BME 的第二套记忆 LLM。留空时,提取/召回/概要/反思会复用当前 SillyTavern 聊天模型;填写后会通过 SillyTavern 现有后端代理转发到 OpenAI 兼容接口,不要求改酒馆本体。
|
||||
</div>
|
||||
<div class="bme-config-row">
|
||||
<label for="bme-setting-llm-url">LLM API 地址</label>
|
||||
@@ -178,6 +206,9 @@
|
||||
|
||||
<div class="bme-config-card">
|
||||
<div class="bme-section-header">Embedding</div>
|
||||
<div class="bme-config-help">
|
||||
图谱向量仍使用 OpenAI 兼容的 <code>/v1/embeddings</code> 接口。当前发布版不改酒馆本体,因此这里不会依赖额外宿主补丁;若目标服务不支持浏览器直连,请改用支持 CORS 的服务或本地可直连端点。
|
||||
</div>
|
||||
<div class="bme-config-row">
|
||||
<label for="bme-setting-embed-url">Embedding API 地址</label>
|
||||
<input id="bme-setting-embed-url" class="bme-config-input" type="text" placeholder="https://api.openai.com/v1" />
|
||||
|
||||
25
panel.js
25
panel.js
@@ -440,6 +440,15 @@ function _bindActions() {
|
||||
function _refreshConfigTab() {
|
||||
const settings = _getSettings?.() || {};
|
||||
|
||||
_setCheckboxValue("bme-setting-enabled", settings.enabled ?? false);
|
||||
_setCheckboxValue("bme-setting-recall-enabled", settings.recallEnabled ?? true);
|
||||
_setInputValue("bme-setting-extract-every", settings.extractEvery ?? 1);
|
||||
_setInputValue(
|
||||
"bme-setting-extract-context-turns",
|
||||
settings.extractContextTurns ?? 2,
|
||||
);
|
||||
_setInputValue("bme-setting-inject-depth", settings.injectDepth ?? 4);
|
||||
|
||||
_setInputValue("bme-setting-llm-url", settings.llmApiUrl || "");
|
||||
_setInputValue("bme-setting-llm-key", settings.llmApiKey || "");
|
||||
_setInputValue("bme-setting-llm-model", settings.llmModel || "");
|
||||
@@ -460,6 +469,22 @@ function _refreshConfigTab() {
|
||||
function _bindConfigControls() {
|
||||
if (!panelEl || panelEl.dataset.bmeConfigBound === "true") return;
|
||||
|
||||
bindCheckbox("bme-setting-enabled", (checked) =>
|
||||
_updateSettings?.({ enabled: checked }),
|
||||
);
|
||||
bindCheckbox("bme-setting-recall-enabled", (checked) =>
|
||||
_updateSettings?.({ recallEnabled: checked }),
|
||||
);
|
||||
bindNumber("bme-setting-extract-every", 1, 1, 50, (value) =>
|
||||
_updateSettings?.({ extractEvery: value }),
|
||||
);
|
||||
bindNumber("bme-setting-extract-context-turns", 2, 0, 20, (value) =>
|
||||
_updateSettings?.({ extractContextTurns: value }),
|
||||
);
|
||||
bindNumber("bme-setting-inject-depth", 4, 0, 9999, (value) =>
|
||||
_updateSettings?.({ injectDepth: value }),
|
||||
);
|
||||
|
||||
bindText("bme-setting-llm-url", (value) =>
|
||||
_updateSettings?.({ llmApiUrl: value.trim() }),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user