mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
Add memory LLM preset switching
This commit is contained in:
2
index.js
2
index.js
@@ -454,6 +454,8 @@ const defaultSettings = {
|
|||||||
llmApiUrl: "",
|
llmApiUrl: "",
|
||||||
llmApiKey: "",
|
llmApiKey: "",
|
||||||
llmModel: "",
|
llmModel: "",
|
||||||
|
llmPresets: {},
|
||||||
|
llmActivePreset: "",
|
||||||
|
|
||||||
// Embedding API 配置
|
// Embedding API 配置
|
||||||
embeddingApiUrl: "",
|
embeddingApiUrl: "",
|
||||||
|
|||||||
38
panel.html
38
panel.html
@@ -569,6 +569,44 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="bme-config-row bme-llm-preset-row">
|
||||||
|
<label for="bme-llm-preset-select">LLM 配置模板</label>
|
||||||
|
<div class="bme-llm-preset-controls">
|
||||||
|
<select
|
||||||
|
id="bme-llm-preset-select"
|
||||||
|
class="bme-config-input"
|
||||||
|
>
|
||||||
|
<option value="">-- 手动模式 --</option>
|
||||||
|
</select>
|
||||||
|
<button
|
||||||
|
id="bme-llm-preset-save"
|
||||||
|
class="bme-config-secondary-btn"
|
||||||
|
title="覆盖保存当前模板"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-floppy-disk"></i>
|
||||||
|
<span>保存</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
id="bme-llm-preset-save-as"
|
||||||
|
class="bme-config-secondary-btn"
|
||||||
|
title="另存为新模板"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-file-circle-plus"></i>
|
||||||
|
<span>另存为</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
id="bme-llm-preset-delete"
|
||||||
|
class="bme-config-secondary-btn"
|
||||||
|
title="删除当前模板"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-trash-can"></i>
|
||||||
|
<span>删除</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="bme-config-row">
|
<div class="bme-config-row">
|
||||||
<label for="bme-setting-llm-url">LLM API 地址</label>
|
<label for="bme-setting-llm-url">LLM API 地址</label>
|
||||||
<input
|
<input
|
||||||
|
|||||||
304
panel.js
304
panel.js
@@ -1866,7 +1866,11 @@ function _bindActions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _refreshConfigTab() {
|
function _refreshConfigTab() {
|
||||||
const settings = _getSettings?.() || {};
|
let settings = _normalizeLlmPresetSettings(_getSettings?.() || {});
|
||||||
|
const resolvedActiveLlmPreset = _resolveActiveLlmPresetName(settings);
|
||||||
|
if (resolvedActiveLlmPreset !== String(settings.llmActivePreset || "")) {
|
||||||
|
settings = _patchSettings({ llmActivePreset: resolvedActiveLlmPreset });
|
||||||
|
}
|
||||||
_refreshPlannerLauncher();
|
_refreshPlannerLauncher();
|
||||||
|
|
||||||
_setCheckboxValue("bme-setting-enabled", settings.enabled ?? true);
|
_setCheckboxValue("bme-setting-enabled", settings.enabled ?? true);
|
||||||
@@ -2148,6 +2152,8 @@ function _refreshConfigTab() {
|
|||||||
_setInputValue("bme-setting-llm-url", settings.llmApiUrl || "");
|
_setInputValue("bme-setting-llm-url", settings.llmApiUrl || "");
|
||||||
_setInputValue("bme-setting-llm-key", settings.llmApiKey || "");
|
_setInputValue("bme-setting-llm-key", settings.llmApiKey || "");
|
||||||
_setInputValue("bme-setting-llm-model", settings.llmModel || "");
|
_setInputValue("bme-setting-llm-model", settings.llmModel || "");
|
||||||
|
_populateLlmPresetSelect(settings.llmPresets || {}, resolvedActiveLlmPreset);
|
||||||
|
_syncLlmPresetControls(resolvedActiveLlmPreset);
|
||||||
_setInputValue("bme-setting-timeout-ms", settings.timeoutMs ?? 300000);
|
_setInputValue("bme-setting-timeout-ms", settings.timeoutMs ?? 300000);
|
||||||
|
|
||||||
_setInputValue("bme-setting-embed-url", settings.embeddingApiUrl || "");
|
_setInputValue("bme-setting-embed-url", settings.embeddingApiUrl || "");
|
||||||
@@ -2562,15 +2568,144 @@ function _bindConfigControls() {
|
|||||||
_patchSettings({ reflectEveryN: value }),
|
_patchSettings({ reflectEveryN: value }),
|
||||||
);
|
);
|
||||||
|
|
||||||
bindText("bme-setting-llm-url", (value) =>
|
const llmPresetSelect = document.getElementById("bme-llm-preset-select");
|
||||||
_patchSettings({ llmApiUrl: value.trim() }),
|
if (llmPresetSelect && llmPresetSelect.dataset.bmeBound !== "true") {
|
||||||
);
|
llmPresetSelect.addEventListener("change", () => {
|
||||||
bindText("bme-setting-llm-key", (value) =>
|
const selectedName = String(llmPresetSelect.value || "");
|
||||||
_patchSettings({ llmApiKey: value.trim() }),
|
if (!selectedName) {
|
||||||
);
|
const currentActivePreset = String(
|
||||||
bindText("bme-setting-llm-model", (value) =>
|
(_getSettings?.() || {}).llmActivePreset || "",
|
||||||
_patchSettings({ llmModel: value.trim() }),
|
);
|
||||||
);
|
if (currentActivePreset) {
|
||||||
|
_patchSettings({ llmActivePreset: "" });
|
||||||
|
}
|
||||||
|
_syncLlmPresetControls("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = _normalizeLlmPresetSettings(_getSettings?.() || {});
|
||||||
|
const preset = settings.llmPresets?.[selectedName];
|
||||||
|
if (!preset) {
|
||||||
|
_patchSettings({ llmActivePreset: "" });
|
||||||
|
_populateLlmPresetSelect(settings.llmPresets || {}, "");
|
||||||
|
_syncLlmPresetControls("");
|
||||||
|
toastr.warning("选中的模板不存在,已切回手动模式", "ST-BME");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_patchSettings({
|
||||||
|
llmApiUrl: preset.llmApiUrl,
|
||||||
|
llmApiKey: preset.llmApiKey,
|
||||||
|
llmModel: preset.llmModel,
|
||||||
|
llmActivePreset: selectedName,
|
||||||
|
});
|
||||||
|
_setInputValue("bme-setting-llm-url", preset.llmApiUrl);
|
||||||
|
_setInputValue("bme-setting-llm-key", preset.llmApiKey);
|
||||||
|
_setInputValue("bme-setting-llm-model", preset.llmModel);
|
||||||
|
_clearFetchedLlmModels();
|
||||||
|
_syncLlmPresetControls(selectedName);
|
||||||
|
});
|
||||||
|
llmPresetSelect.dataset.bmeBound = "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
const llmPresetSaveBtn = document.getElementById("bme-llm-preset-save");
|
||||||
|
if (llmPresetSaveBtn && llmPresetSaveBtn.dataset.bmeBound !== "true") {
|
||||||
|
llmPresetSaveBtn.addEventListener("click", () => {
|
||||||
|
const settings = _normalizeLlmPresetSettings(_getSettings?.() || {});
|
||||||
|
const activePreset = String(settings.llmActivePreset || "");
|
||||||
|
if (!activePreset) {
|
||||||
|
document.getElementById("bme-llm-preset-save-as")?.click();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextPresets = {
|
||||||
|
...(settings.llmPresets || {}),
|
||||||
|
[activePreset]: _getLlmConfigInputSnapshot(),
|
||||||
|
};
|
||||||
|
_patchSettings({ llmPresets: nextPresets });
|
||||||
|
_populateLlmPresetSelect(nextPresets, activePreset);
|
||||||
|
_syncLlmPresetControls(activePreset);
|
||||||
|
toastr.success("当前模板已保存", "ST-BME");
|
||||||
|
});
|
||||||
|
llmPresetSaveBtn.dataset.bmeBound = "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
const llmPresetSaveAsBtn = document.getElementById("bme-llm-preset-save-as");
|
||||||
|
if (llmPresetSaveAsBtn && llmPresetSaveAsBtn.dataset.bmeBound !== "true") {
|
||||||
|
llmPresetSaveAsBtn.addEventListener("click", () => {
|
||||||
|
const settings = _normalizeLlmPresetSettings(_getSettings?.() || {});
|
||||||
|
const activePreset = String(settings.llmActivePreset || "");
|
||||||
|
const suggestedName = activePreset
|
||||||
|
? `${activePreset} 副本`
|
||||||
|
: "新模板";
|
||||||
|
const nextName = window.prompt("请输入新模板名称", suggestedName);
|
||||||
|
if (nextName == null) return;
|
||||||
|
|
||||||
|
const trimmedName = String(nextName).trim();
|
||||||
|
if (!trimmedName) {
|
||||||
|
toastr.info("模板名称不能为空", "ST-BME");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (trimmedName in (settings.llmPresets || {})) {
|
||||||
|
toastr.info("模板名称已存在,请换一个", "ST-BME");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextPresets = {
|
||||||
|
...(settings.llmPresets || {}),
|
||||||
|
[trimmedName]: _getLlmConfigInputSnapshot(),
|
||||||
|
};
|
||||||
|
_patchSettings({
|
||||||
|
llmPresets: nextPresets,
|
||||||
|
llmActivePreset: trimmedName,
|
||||||
|
});
|
||||||
|
_populateLlmPresetSelect(nextPresets, trimmedName);
|
||||||
|
_syncLlmPresetControls(trimmedName);
|
||||||
|
toastr.success("已另存为新模板", "ST-BME");
|
||||||
|
});
|
||||||
|
llmPresetSaveAsBtn.dataset.bmeBound = "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
const llmPresetDeleteBtn = document.getElementById("bme-llm-preset-delete");
|
||||||
|
if (llmPresetDeleteBtn && llmPresetDeleteBtn.dataset.bmeBound !== "true") {
|
||||||
|
llmPresetDeleteBtn.addEventListener("click", () => {
|
||||||
|
const settings = _normalizeLlmPresetSettings(_getSettings?.() || {});
|
||||||
|
const activePreset = String(settings.llmActivePreset || "");
|
||||||
|
if (!activePreset) {
|
||||||
|
toastr.info("当前处于手动模式,没有可删除的模板", "ST-BME");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmed = window.confirm(
|
||||||
|
`确定要删除模板“${activePreset}”吗?当前输入框里的值会保留。`,
|
||||||
|
);
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
|
const nextPresets = { ...(settings.llmPresets || {}) };
|
||||||
|
delete nextPresets[activePreset];
|
||||||
|
_patchSettings({
|
||||||
|
llmPresets: nextPresets,
|
||||||
|
llmActivePreset: "",
|
||||||
|
});
|
||||||
|
_populateLlmPresetSelect(nextPresets, "");
|
||||||
|
_syncLlmPresetControls("");
|
||||||
|
toastr.success("模板已删除", "ST-BME");
|
||||||
|
});
|
||||||
|
llmPresetDeleteBtn.dataset.bmeBound = "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
bindText("bme-setting-llm-url", (value) => {
|
||||||
|
_patchSettings({ llmApiUrl: value.trim() });
|
||||||
|
_markLlmPresetDirty({ clearFetchedModels: true });
|
||||||
|
});
|
||||||
|
bindText("bme-setting-llm-key", (value) => {
|
||||||
|
_patchSettings({ llmApiKey: value.trim() });
|
||||||
|
_markLlmPresetDirty({ clearFetchedModels: true });
|
||||||
|
});
|
||||||
|
bindText("bme-setting-llm-model", (value) => {
|
||||||
|
_patchSettings({ llmModel: value.trim() });
|
||||||
|
_markLlmPresetDirty();
|
||||||
|
});
|
||||||
bindNumber("bme-setting-timeout-ms", 300000, 1000, 3600000, (value) =>
|
bindNumber("bme-setting-timeout-ms", 300000, 1000, 3600000, (value) =>
|
||||||
_patchSettings({ timeoutMs: value }),
|
_patchSettings({ timeoutMs: value }),
|
||||||
);
|
);
|
||||||
@@ -5600,6 +5735,155 @@ function _patchSettings(patch = {}, options = {}) {
|
|||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _normalizeLlmPresetSettings(settings = _getSettings?.() || {}) {
|
||||||
|
const rawPresets = settings?.llmPresets;
|
||||||
|
const normalizedPresets = {};
|
||||||
|
let changed =
|
||||||
|
!rawPresets ||
|
||||||
|
typeof rawPresets !== "object" ||
|
||||||
|
Array.isArray(rawPresets);
|
||||||
|
|
||||||
|
if (!changed) {
|
||||||
|
for (const [name, preset] of Object.entries(rawPresets)) {
|
||||||
|
if (!String(name || "").trim()) {
|
||||||
|
changed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!preset ||
|
||||||
|
typeof preset !== "object" ||
|
||||||
|
Array.isArray(preset) ||
|
||||||
|
typeof preset.llmApiUrl !== "string" ||
|
||||||
|
typeof preset.llmApiKey !== "string" ||
|
||||||
|
typeof preset.llmModel !== "string"
|
||||||
|
) {
|
||||||
|
changed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
normalizedPresets[name] = {
|
||||||
|
llmApiUrl: preset.llmApiUrl,
|
||||||
|
llmApiKey: preset.llmApiKey,
|
||||||
|
llmModel: preset.llmModel,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let activePreset =
|
||||||
|
typeof settings?.llmActivePreset === "string" ? settings.llmActivePreset : "";
|
||||||
|
if (activePreset && !Object.prototype.hasOwnProperty.call(normalizedPresets, activePreset)) {
|
||||||
|
activePreset = "";
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (typeof settings?.llmActivePreset !== "string") {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!changed) {
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _patchSettings({
|
||||||
|
llmPresets: normalizedPresets,
|
||||||
|
llmActivePreset: activePreset,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _resolveActiveLlmPresetName(settings = _getSettings?.() || {}) {
|
||||||
|
const activePreset = String(settings?.llmActivePreset || "");
|
||||||
|
if (!activePreset) return "";
|
||||||
|
const preset = settings?.llmPresets?.[activePreset];
|
||||||
|
if (!preset) return "";
|
||||||
|
return _isSameLlmConfigSnapshot(
|
||||||
|
{
|
||||||
|
llmApiUrl: String(settings?.llmApiUrl || ""),
|
||||||
|
llmApiKey: String(settings?.llmApiKey || ""),
|
||||||
|
llmModel: String(settings?.llmModel || ""),
|
||||||
|
},
|
||||||
|
preset,
|
||||||
|
)
|
||||||
|
? activePreset
|
||||||
|
: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function _isSameLlmConfigSnapshot(left = {}, right = {}) {
|
||||||
|
return (
|
||||||
|
String(left?.llmApiUrl || "") === String(right?.llmApiUrl || "") &&
|
||||||
|
String(left?.llmApiKey || "") === String(right?.llmApiKey || "") &&
|
||||||
|
String(left?.llmModel || "") === String(right?.llmModel || "")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getLlmConfigInputSnapshot() {
|
||||||
|
const settings = _getSettings?.() || {};
|
||||||
|
return {
|
||||||
|
llmApiUrl: String(
|
||||||
|
document.getElementById("bme-setting-llm-url")?.value ?? settings.llmApiUrl ?? "",
|
||||||
|
).trim(),
|
||||||
|
llmApiKey: String(
|
||||||
|
document.getElementById("bme-setting-llm-key")?.value ?? settings.llmApiKey ?? "",
|
||||||
|
).trim(),
|
||||||
|
llmModel: String(
|
||||||
|
document.getElementById("bme-setting-llm-model")?.value ?? settings.llmModel ?? "",
|
||||||
|
).trim(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function _populateLlmPresetSelect(presets = {}, activePreset = "") {
|
||||||
|
const select = document.getElementById("bme-llm-preset-select");
|
||||||
|
if (!select) return;
|
||||||
|
|
||||||
|
while (select.options.length > 1) {
|
||||||
|
select.remove(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(presets)
|
||||||
|
.sort((left, right) => left.localeCompare(right, "zh-Hans-CN"))
|
||||||
|
.forEach((name) => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = name;
|
||||||
|
option.textContent = name;
|
||||||
|
select.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
select.value = activePreset || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function _syncLlmPresetControls(activePreset = "") {
|
||||||
|
const select = document.getElementById("bme-llm-preset-select");
|
||||||
|
if (select) {
|
||||||
|
select.value = activePreset || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteBtn = document.getElementById("bme-llm-preset-delete");
|
||||||
|
if (deleteBtn) {
|
||||||
|
deleteBtn.disabled = !activePreset;
|
||||||
|
deleteBtn.title = activePreset ? "删除当前模板" : "手动模式下没有可删除的模板";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _clearFetchedLlmModels() {
|
||||||
|
fetchedMemoryLLMModels.length = 0;
|
||||||
|
const modelSelect = document.getElementById("bme-select-llm-model");
|
||||||
|
if (!modelSelect) return;
|
||||||
|
while (modelSelect.options.length > 1) {
|
||||||
|
modelSelect.remove(1);
|
||||||
|
}
|
||||||
|
modelSelect.value = "";
|
||||||
|
modelSelect.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
function _markLlmPresetDirty(options = {}) {
|
||||||
|
if (options.clearFetchedModels) {
|
||||||
|
_clearFetchedLlmModels();
|
||||||
|
}
|
||||||
|
|
||||||
|
const activePreset = String((_getSettings?.() || {}).llmActivePreset || "");
|
||||||
|
if (activePreset) {
|
||||||
|
_patchSettings({ llmActivePreset: "" });
|
||||||
|
}
|
||||||
|
_syncLlmPresetControls("");
|
||||||
|
}
|
||||||
|
|
||||||
function _highlightThemeChoice(themeName) {
|
function _highlightThemeChoice(themeName) {
|
||||||
if (!panelEl) return;
|
if (!panelEl) return;
|
||||||
panelEl.querySelectorAll(".bme-theme-option").forEach((opt) => {
|
panelEl.querySelectorAll(".bme-theme-option").forEach((opt) => {
|
||||||
|
|||||||
25
style.css
25
style.css
@@ -1423,6 +1423,22 @@
|
|||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bme-llm-preset-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bme-llm-preset-controls select {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bme-llm-preset-controls .bme-config-secondary-btn {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.bme-config-secondary-btn {
|
.bme-config-secondary-btn {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -2880,6 +2896,15 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bme-llm-preset-controls {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bme-llm-preset-controls .bme-config-secondary-btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.bme-inline-checkbox {
|
.bme-inline-checkbox {
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|||||||
Reference in New Issue
Block a user