mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
Simplify ENA planner API preset selection
This commit is contained in:
@@ -11,6 +11,10 @@ import {
|
||||
setActiveTaskProfileId,
|
||||
upsertTaskProfile,
|
||||
} from '../prompting/prompt-profiles.js';
|
||||
import {
|
||||
resolveDedicatedLlmProviderConfig,
|
||||
resolveLlmConfigSelection,
|
||||
} from '../llm/llm-preset-utils.js';
|
||||
import { debugLog } from '../runtime/debug-logging.js';
|
||||
import jsyaml from '../vendor/js-yaml.mjs';
|
||||
|
||||
@@ -74,6 +78,7 @@ function getDefaultSettings(options = {}) {
|
||||
|
||||
// Planner API
|
||||
api: {
|
||||
llmPreset: '',
|
||||
channel: 'openai',
|
||||
baseUrl: '',
|
||||
prefixMode: 'auto',
|
||||
@@ -563,21 +568,87 @@ function normalizeUrlBase(u) {
|
||||
return u.replace(/\/+$/g, '');
|
||||
}
|
||||
|
||||
function hasPlannerLegacyDedicatedApiConfig(api = {}) {
|
||||
return Boolean(
|
||||
String(api?.baseUrl || '').trim() &&
|
||||
String(api?.model || '').trim(),
|
||||
);
|
||||
}
|
||||
|
||||
function inferPlannerChannelFromUrl(url) {
|
||||
const resolved = resolveDedicatedLlmProviderConfig(String(url || '').trim());
|
||||
if (resolved.providerId === 'google-ai-studio') return 'gemini';
|
||||
if (resolved.providerId === 'anthropic-claude') return 'claude';
|
||||
return 'openai';
|
||||
}
|
||||
|
||||
function buildResolvedPlannerApiConfigFromLlmSelection(selection = {}) {
|
||||
const snapshot = selection?.config && typeof selection.config === 'object'
|
||||
? selection.config
|
||||
: {};
|
||||
const inputUrl = String(snapshot?.llmApiUrl || '').trim();
|
||||
const resolved = resolveDedicatedLlmProviderConfig(inputUrl);
|
||||
const baseUrl = String(resolved.apiUrl || inputUrl).trim();
|
||||
return {
|
||||
mode: selection?.requestedPresetName ? 'preset' : 'global',
|
||||
source: String(selection?.source || ''),
|
||||
requestedPresetName: String(selection?.requestedPresetName || ''),
|
||||
presetName: String(selection?.presetName || ''),
|
||||
fallbackReason: String(selection?.fallbackReason || ''),
|
||||
channel: inferPlannerChannelFromUrl(baseUrl),
|
||||
prefixMode: 'auto',
|
||||
customPrefix: '',
|
||||
baseUrl,
|
||||
apiKey: String(snapshot?.llmApiKey || '').trim(),
|
||||
model: String(snapshot?.llmModel || '').trim(),
|
||||
};
|
||||
}
|
||||
|
||||
function buildLegacyPlannerApiConfig(api = {}) {
|
||||
return {
|
||||
mode: 'legacy',
|
||||
source: 'legacy-ena-config',
|
||||
requestedPresetName: '',
|
||||
presetName: '',
|
||||
fallbackReason: '',
|
||||
channel: String(api?.channel || 'openai').trim() || 'openai',
|
||||
prefixMode: String(api?.prefixMode || 'auto').trim() || 'auto',
|
||||
customPrefix: String(api?.customPrefix || '').trim(),
|
||||
baseUrl: String(api?.baseUrl || '').trim(),
|
||||
apiKey: String(api?.apiKey || '').trim(),
|
||||
model: String(api?.model || '').trim(),
|
||||
};
|
||||
}
|
||||
|
||||
function resolvePlannerApiConfig() {
|
||||
const s = ensureSettings();
|
||||
const selectedPresetName = String(s?.api?.llmPreset || '').trim();
|
||||
if (selectedPresetName) {
|
||||
return buildResolvedPlannerApiConfigFromLlmSelection(
|
||||
resolveLlmConfigSelection(getBmeSettings(), selectedPresetName),
|
||||
);
|
||||
}
|
||||
if (hasPlannerLegacyDedicatedApiConfig(s?.api)) {
|
||||
return buildLegacyPlannerApiConfig(s.api);
|
||||
}
|
||||
return buildResolvedPlannerApiConfigFromLlmSelection(
|
||||
resolveLlmConfigSelection(getBmeSettings(), ''),
|
||||
);
|
||||
}
|
||||
|
||||
function getDefaultPrefixByChannel(channel) {
|
||||
if (channel === 'gemini') return '/v1beta';
|
||||
return '/v1';
|
||||
}
|
||||
|
||||
function buildApiPrefix() {
|
||||
const s = ensureSettings();
|
||||
if (s.api.prefixMode === 'custom' && s.api.customPrefix?.trim()) return s.api.customPrefix.trim();
|
||||
return getDefaultPrefixByChannel(s.api.channel);
|
||||
function buildApiPrefix(apiConfig = resolvePlannerApiConfig()) {
|
||||
if (apiConfig?.prefixMode === 'custom' && apiConfig?.customPrefix?.trim()) return apiConfig.customPrefix.trim();
|
||||
return getDefaultPrefixByChannel(apiConfig?.channel);
|
||||
}
|
||||
|
||||
function buildUrl(path) {
|
||||
const s = ensureSettings();
|
||||
const base = normalizeUrlBase(s.api.baseUrl);
|
||||
const prefix = buildApiPrefix();
|
||||
function buildUrl(path, apiConfig = resolvePlannerApiConfig()) {
|
||||
const base = normalizeUrlBase(apiConfig?.baseUrl);
|
||||
const prefix = buildApiPrefix(apiConfig);
|
||||
const p = prefix.startsWith('/') ? prefix : `/${prefix}`;
|
||||
const finalPrefix = p.replace(/\/+$/g, '');
|
||||
const finalPath = path.startsWith('/') ? path : `/${path}`;
|
||||
@@ -1273,16 +1344,15 @@ function filterPlannerPreview(rawPartial) {
|
||||
* --------------------------
|
||||
*/
|
||||
async function callPlanner(messages, options = {}) {
|
||||
const s = ensureSettings();
|
||||
if (!s.api.baseUrl) throw new Error('未配置 API URL');
|
||||
if (!s.api.apiKey) throw new Error('未配置 API KEY');
|
||||
if (!s.api.model) throw new Error('未选择模型');
|
||||
const apiConfig = resolvePlannerApiConfig();
|
||||
if (!apiConfig.baseUrl) throw new Error('未配置可用的 API URL');
|
||||
if (!apiConfig.model) throw new Error('未配置可用的模型');
|
||||
const generation = resolvePlannerGenerationSettings();
|
||||
|
||||
const url = buildUrl('/chat/completions');
|
||||
const url = buildUrl('/chat/completions', apiConfig);
|
||||
|
||||
const body = {
|
||||
model: s.api.model,
|
||||
model: apiConfig.model,
|
||||
messages,
|
||||
stream: generation.stream === true
|
||||
};
|
||||
@@ -1298,13 +1368,16 @@ async function callPlanner(messages, options = {}) {
|
||||
const plannerRequestTimeoutMs = getPlannerRequestTimeoutMs();
|
||||
const timeoutId = setTimeout(() => controller.abort(), plannerRequestTimeoutMs);
|
||||
try {
|
||||
const headers = {
|
||||
...getRequestHeaders(),
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
if (apiConfig.apiKey) {
|
||||
headers.Authorization = `Bearer ${apiConfig.apiKey}`;
|
||||
}
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...getRequestHeaders(),
|
||||
Authorization: `Bearer ${s.api.apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
signal: controller.signal
|
||||
});
|
||||
@@ -1364,16 +1437,18 @@ async function callPlanner(messages, options = {}) {
|
||||
}
|
||||
|
||||
async function fetchModelsForUi() {
|
||||
const s = ensureSettings();
|
||||
if (!s.api.baseUrl) throw new Error('请先填写 API URL');
|
||||
if (!s.api.apiKey) throw new Error('请先填写 API KEY');
|
||||
const url = buildUrl('/models');
|
||||
const apiConfig = resolvePlannerApiConfig();
|
||||
if (!apiConfig.baseUrl) throw new Error('当前没有可用的 API URL');
|
||||
const url = buildUrl('/models', apiConfig);
|
||||
const headers = {
|
||||
...getRequestHeaders(),
|
||||
};
|
||||
if (apiConfig.apiKey) {
|
||||
headers.Authorization = `Bearer ${apiConfig.apiKey}`;
|
||||
}
|
||||
const res = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
...getRequestHeaders(),
|
||||
Authorization: `Bearer ${s.api.apiKey}`
|
||||
}
|
||||
headers
|
||||
});
|
||||
if (!res.ok) {
|
||||
const text = await res.text().catch(() => '');
|
||||
@@ -1744,10 +1819,10 @@ async function buildPlannerMessages(rawUserInput) {
|
||||
* --------------------------
|
||||
*/
|
||||
async function runPlanningOnce(rawUserInput, silent = false, options = {}) {
|
||||
const s = ensureSettings();
|
||||
const apiConfig = resolvePlannerApiConfig();
|
||||
|
||||
const log = {
|
||||
time: nowISO(), ok: false, model: s.api.model,
|
||||
time: nowISO(), ok: false, model: apiConfig.model,
|
||||
requestMessages: [], rawReply: '', filteredReply: '', error: ''
|
||||
};
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
|
||||
const SECTION_SELECTOR = '[data-config-section="planner"]';
|
||||
const AUTOSAVE_DELAY_MS = 600;
|
||||
const LEGACY_PLANNER_LLM_OPTION = '__planner_legacy_dedicated__';
|
||||
|
||||
let bound = false;
|
||||
let unsubscribePlanner = null;
|
||||
@@ -149,15 +150,13 @@ function buildPlannerLlmSnapshot(source = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function getCurrentPlannerLlmSnapshot() {
|
||||
const rawUrl = String(
|
||||
$('bme-planner-api-base')?.value ?? cfgCache?.api?.baseUrl ?? '',
|
||||
).trim();
|
||||
function buildPlannerSnapshotFromConfigApi(api = {}) {
|
||||
const rawUrl = String(api?.baseUrl || '').trim();
|
||||
const resolved = resolveDedicatedLlmProviderConfig(rawUrl);
|
||||
return buildPlannerLlmSnapshot({
|
||||
llmApiUrl: resolved.apiUrl || rawUrl,
|
||||
llmApiKey: $('bme-planner-api-key')?.value ?? cfgCache?.api?.apiKey ?? '',
|
||||
llmModel: $('bme-planner-model')?.value ?? cfgCache?.api?.model ?? '',
|
||||
llmApiKey: api?.apiKey || '',
|
||||
llmModel: api?.model || '',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -171,22 +170,80 @@ function normalizePlannerPresetSnapshot(preset = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
function resolveMatchingPlannerLlmPresetName(snapshot = getCurrentPlannerLlmSnapshot()) {
|
||||
const { presets, activePreset } = getSharedLlmPresetState();
|
||||
const exactMatches = Object.keys(presets || {}).filter((name) =>
|
||||
isSameLlmConfigSnapshot(snapshot, normalizePlannerPresetSnapshot(presets[name])),
|
||||
function hasPlannerLegacyDedicatedApiConfig(api = {}) {
|
||||
return Boolean(
|
||||
String(api?.baseUrl || '').trim() &&
|
||||
String(api?.model || '').trim(),
|
||||
);
|
||||
if (exactMatches.length === 1) return exactMatches[0];
|
||||
if (exactMatches.length > 1 && activePreset && exactMatches.includes(activePreset)) {
|
||||
return activePreset;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function populatePlannerLlmPresetSelect(selectedPreset = resolveMatchingPlannerLlmPresetName()) {
|
||||
function resolvePlannerLlmSelectState(config = cfgCache || {}) {
|
||||
const api = config?.api && typeof config.api === 'object' ? config.api : {};
|
||||
const selectedPresetName = String(api?.llmPreset || '').trim();
|
||||
const { presets, activePreset } = getSharedLlmPresetState();
|
||||
|
||||
if (selectedPresetName) {
|
||||
if (Object.prototype.hasOwnProperty.call(presets || {}, selectedPresetName)) {
|
||||
return {
|
||||
value: selectedPresetName,
|
||||
mode: 'preset',
|
||||
};
|
||||
}
|
||||
return {
|
||||
value: '',
|
||||
mode: 'global',
|
||||
missingPresetName: selectedPresetName,
|
||||
};
|
||||
}
|
||||
|
||||
if (!hasPlannerLegacyDedicatedApiConfig(api)) {
|
||||
return {
|
||||
value: '',
|
||||
mode: 'global',
|
||||
};
|
||||
}
|
||||
|
||||
const legacySnapshot = buildPlannerSnapshotFromConfigApi(api);
|
||||
const globalSnapshot = buildPlannerLlmSnapshot(getSharedSettingsSnapshot());
|
||||
if (isSameLlmConfigSnapshot(legacySnapshot, globalSnapshot)) {
|
||||
return {
|
||||
value: '',
|
||||
mode: 'global',
|
||||
matchedLegacySource: 'global',
|
||||
};
|
||||
}
|
||||
|
||||
const exactMatches = Object.keys(presets || {}).filter((name) =>
|
||||
isSameLlmConfigSnapshot(legacySnapshot, normalizePlannerPresetSnapshot(presets[name])),
|
||||
);
|
||||
if (exactMatches.length === 1) {
|
||||
return {
|
||||
value: exactMatches[0],
|
||||
mode: 'preset',
|
||||
matchedLegacySource: 'preset',
|
||||
};
|
||||
}
|
||||
if (exactMatches.length > 1 && activePreset && exactMatches.includes(activePreset)) {
|
||||
return {
|
||||
value: activePreset,
|
||||
mode: 'preset',
|
||||
matchedLegacySource: 'preset',
|
||||
};
|
||||
}
|
||||
return {
|
||||
value: LEGACY_PLANNER_LLM_OPTION,
|
||||
mode: 'legacy',
|
||||
};
|
||||
}
|
||||
|
||||
function populatePlannerLlmPresetSelect(selectedPreset = resolvePlannerLlmSelectState().value) {
|
||||
const select = $('bme-planner-llm-preset-select');
|
||||
if (!select) return;
|
||||
|
||||
if (select.options.length > 0) {
|
||||
select.options[0].textContent = '-- 跟随全局(当前 BME API) --';
|
||||
}
|
||||
|
||||
while (select.options.length > 1) {
|
||||
select.remove(1);
|
||||
}
|
||||
@@ -201,45 +258,18 @@ function populatePlannerLlmPresetSelect(selectedPreset = resolveMatchingPlannerL
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
if (selectedPreset === LEGACY_PLANNER_LLM_OPTION) {
|
||||
const legacyOption = document.createElement('option');
|
||||
legacyOption.value = LEGACY_PLANNER_LLM_OPTION;
|
||||
legacyOption.textContent = '旧 ENA 独立连接(兼容)';
|
||||
select.appendChild(legacyOption);
|
||||
}
|
||||
|
||||
select.value = selectedPreset || '';
|
||||
}
|
||||
|
||||
function syncPlannerLlmPresetSelect() {
|
||||
populatePlannerLlmPresetSelect(resolveMatchingPlannerLlmPresetName());
|
||||
}
|
||||
|
||||
function inferPlannerApiConfigFromPreset(preset = {}) {
|
||||
const rawUrl = String(preset?.llmApiUrl || '').trim();
|
||||
const resolved = resolveDedicatedLlmProviderConfig(rawUrl);
|
||||
let channel = 'openai';
|
||||
if (resolved.providerId === 'google-ai-studio') channel = 'gemini';
|
||||
else if (resolved.providerId === 'anthropic-claude') channel = 'claude';
|
||||
|
||||
return {
|
||||
channel,
|
||||
prefixMode: 'auto',
|
||||
customPrefix: '',
|
||||
baseUrl: resolved.apiUrl || rawUrl,
|
||||
apiKey: String(preset?.llmApiKey || '').trim(),
|
||||
model: String(preset?.llmModel || '').trim(),
|
||||
};
|
||||
}
|
||||
|
||||
function applyPlannerLlmPresetToFields(name, preset = {}) {
|
||||
const inferred = inferPlannerApiConfigFromPreset(preset);
|
||||
const setVal = (id, value) => {
|
||||
const el = $(id);
|
||||
if (el) el.value = value;
|
||||
};
|
||||
|
||||
setVal('bme-planner-api-channel', inferred.channel || 'openai');
|
||||
setVal('bme-planner-prefix-mode', inferred.prefixMode || 'auto');
|
||||
setVal('bme-planner-prefix-custom', inferred.customPrefix || '');
|
||||
setVal('bme-planner-api-base', inferred.baseUrl || '');
|
||||
setVal('bme-planner-api-key', inferred.apiKey || '');
|
||||
setVal('bme-planner-model', inferred.model || '');
|
||||
updatePrefixModeUI();
|
||||
populatePlannerLlmPresetSelect(name);
|
||||
populatePlannerLlmPresetSelect(resolvePlannerLlmSelectState().value);
|
||||
}
|
||||
|
||||
/* ── Prompt block editor ────────────────────────────────────────────────── */
|
||||
@@ -508,6 +538,14 @@ function applyConfigToFields(cfg) {
|
||||
);
|
||||
updatePrefixModeUI();
|
||||
syncPlannerLlmPresetSelect();
|
||||
const llmSelectState = resolvePlannerLlmSelectState(cfgCache);
|
||||
if (llmSelectState.mode === 'legacy') {
|
||||
setLocalStatus('bme-planner-api-status', '当前仍在使用旧版 ENA 独立连接;切换为全局或预设后将不再保留这套隐藏配置。', '');
|
||||
} else if (llmSelectState.missingPresetName) {
|
||||
setLocalStatus('bme-planner-api-status', `已回退为跟随全局:缺少预设 ${llmSelectState.missingPresetName}`, 'error');
|
||||
} else {
|
||||
setLocalStatus('bme-planner-api-status', '', '');
|
||||
}
|
||||
|
||||
const keepSelected = cfgCache.activePromptTemplate || $('bme-planner-tpl-select')?.value || '';
|
||||
renderTemplateSelect(keepSelected);
|
||||
@@ -516,18 +554,32 @@ function applyConfigToFields(cfg) {
|
||||
|
||||
function collectPatch() {
|
||||
const getVal = (id) => $(id)?.value ?? '';
|
||||
const selectedPlannerPreset = String(getVal('bme-planner-llm-preset-select') || '').trim();
|
||||
const existingApi = cfgCache?.api && typeof cfgCache.api === 'object' ? cfgCache.api : {};
|
||||
const preserveLegacyApi = selectedPlannerPreset === LEGACY_PLANNER_LLM_OPTION;
|
||||
|
||||
return {
|
||||
enabled: toBool(getVal('bme-planner-enabled'), false),
|
||||
skipIfPlotPresent: toBool(getVal('bme-planner-skip-plot'), true),
|
||||
api: {
|
||||
channel: getVal('bme-planner-api-channel'),
|
||||
prefixMode: getVal('bme-planner-prefix-mode'),
|
||||
baseUrl: getVal('bme-planner-api-base').trim(),
|
||||
customPrefix: getVal('bme-planner-prefix-custom').trim(),
|
||||
apiKey: getVal('bme-planner-api-key'),
|
||||
model: getVal('bme-planner-model').trim(),
|
||||
},
|
||||
api: preserveLegacyApi
|
||||
? {
|
||||
llmPreset: '',
|
||||
channel: String(existingApi.channel || 'openai'),
|
||||
prefixMode: String(existingApi.prefixMode || 'auto'),
|
||||
customPrefix: String(existingApi.customPrefix || ''),
|
||||
baseUrl: String(existingApi.baseUrl || '').trim(),
|
||||
apiKey: String(existingApi.apiKey || ''),
|
||||
model: String(existingApi.model || '').trim(),
|
||||
}
|
||||
: {
|
||||
llmPreset: selectedPlannerPreset,
|
||||
channel: 'openai',
|
||||
prefixMode: 'auto',
|
||||
customPrefix: '',
|
||||
baseUrl: '',
|
||||
apiKey: '',
|
||||
model: '',
|
||||
},
|
||||
includeGlobalWorldbooks: toBool(getVal('bme-planner-include-global-wb'), false),
|
||||
excludeWorldbookPosition4: toBool(getVal('bme-planner-wb-pos4'), true),
|
||||
worldbookExcludeNames: csvToArr(getVal('bme-planner-wb-exclude-names')),
|
||||
@@ -680,19 +732,50 @@ function bindOnce(section) {
|
||||
$('bme-planner-llm-preset-select')?.addEventListener('change', () => {
|
||||
const select = $('bme-planner-llm-preset-select');
|
||||
const selectedName = String(select?.value || '');
|
||||
cfgCache = cfgCache || {};
|
||||
cfgCache.api = cfgCache.api && typeof cfgCache.api === 'object' ? cfgCache.api : {};
|
||||
if (!selectedName) {
|
||||
setLocalStatus('bme-planner-api-status', '', '');
|
||||
cfgCache.api.llmPreset = '';
|
||||
cfgCache.api.channel = 'openai';
|
||||
cfgCache.api.prefixMode = 'auto';
|
||||
cfgCache.api.customPrefix = '';
|
||||
cfgCache.api.baseUrl = '';
|
||||
cfgCache.api.apiKey = '';
|
||||
cfgCache.api.model = '';
|
||||
syncPlannerLlmPresetSelect();
|
||||
setLocalStatus('bme-planner-api-status', '已改为跟随全局 BME API', 'success');
|
||||
scheduleSave();
|
||||
return;
|
||||
}
|
||||
if (selectedName === LEGACY_PLANNER_LLM_OPTION) {
|
||||
syncPlannerLlmPresetSelect();
|
||||
setLocalStatus('bme-planner-api-status', '继续保留旧版 ENA 独立连接', '');
|
||||
scheduleSave();
|
||||
return;
|
||||
}
|
||||
const { presets } = getSharedLlmPresetState();
|
||||
const preset = presets?.[selectedName];
|
||||
if (!preset) {
|
||||
populatePlannerLlmPresetSelect('');
|
||||
setLocalStatus('bme-planner-api-status', '选中的 BME 模板不存在,已切回手动模式', 'error');
|
||||
if (!presets?.[selectedName]) {
|
||||
cfgCache.api.llmPreset = '';
|
||||
cfgCache.api.channel = 'openai';
|
||||
cfgCache.api.prefixMode = 'auto';
|
||||
cfgCache.api.customPrefix = '';
|
||||
cfgCache.api.baseUrl = '';
|
||||
cfgCache.api.apiKey = '';
|
||||
cfgCache.api.model = '';
|
||||
syncPlannerLlmPresetSelect();
|
||||
setLocalStatus('bme-planner-api-status', '选中的 API 预设不存在,已回退为跟随全局', 'error');
|
||||
scheduleSave();
|
||||
return;
|
||||
}
|
||||
applyPlannerLlmPresetToFields(selectedName, preset);
|
||||
setLocalStatus('bme-planner-api-status', `已套用 BME 模板:${selectedName}`, 'success');
|
||||
cfgCache.api.llmPreset = selectedName;
|
||||
cfgCache.api.channel = 'openai';
|
||||
cfgCache.api.prefixMode = 'auto';
|
||||
cfgCache.api.customPrefix = '';
|
||||
cfgCache.api.baseUrl = '';
|
||||
cfgCache.api.apiKey = '';
|
||||
cfgCache.api.model = '';
|
||||
syncPlannerLlmPresetSelect();
|
||||
setLocalStatus('bme-planner-api-status', `已切换为 API 预设:${selectedName}`, 'success');
|
||||
scheduleSave();
|
||||
});
|
||||
|
||||
|
||||
@@ -2920,110 +2920,23 @@
|
||||
<div>
|
||||
<div class="bme-config-card-title">规划 LLM · 连接</div>
|
||||
<div class="bme-config-card-subtitle">
|
||||
独立的规划 LLM 通道,与 BME 记忆 LLM 相互隔离。支持 OpenAI / Gemini / Claude 兼容协议。
|
||||
默认跟随当前全局 BME API,也可以切换到任意已保存的 API 预设。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bme-config-row bme-llm-preset-row">
|
||||
<label for="bme-planner-llm-preset-select">复用 BME LLM 配置模板</label>
|
||||
<label for="bme-planner-llm-preset-select">API 预设</label>
|
||||
<div class="bme-llm-preset-controls">
|
||||
<select
|
||||
id="bme-planner-llm-preset-select"
|
||||
class="bme-config-input"
|
||||
>
|
||||
<option value="">-- 手动模式 / 当前 ENA 配置 --</option>
|
||||
<option value="">-- 跟随全局(当前 BME API) --</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bme-config-help">
|
||||
直接复用主面板的 LLM 预设,将 URL、Key、Model 拷贝到 ENA 规划器,并自动推断渠道与默认前缀;套用后仍可单独微调。
|
||||
</div>
|
||||
<div class="bme-config-row">
|
||||
<label for="bme-planner-api-channel">渠道类型</label>
|
||||
<select id="bme-planner-api-channel" class="bme-config-input">
|
||||
<option value="openai">OpenAI 兼容</option>
|
||||
<option value="gemini">Gemini 兼容</option>
|
||||
<option value="claude">Claude 兼容</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="bme-config-row">
|
||||
<label for="bme-planner-prefix-mode">路径前缀</label>
|
||||
<select id="bme-planner-prefix-mode" class="bme-config-input">
|
||||
<option value="auto">自动(如 /v1)</option>
|
||||
<option value="custom">自定义</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="bme-config-row" id="bme-planner-prefix-custom-row" hidden>
|
||||
<label for="bme-planner-prefix-custom">自定义前缀</label>
|
||||
<input
|
||||
id="bme-planner-prefix-custom"
|
||||
class="bme-config-input"
|
||||
type="text"
|
||||
placeholder="/v1"
|
||||
/>
|
||||
</div>
|
||||
<div class="bme-config-row">
|
||||
<label for="bme-planner-api-base">API 地址</label>
|
||||
<input
|
||||
id="bme-planner-api-base"
|
||||
class="bme-config-input"
|
||||
type="text"
|
||||
placeholder="https://api.openai.com"
|
||||
/>
|
||||
</div>
|
||||
<div class="bme-config-row">
|
||||
<label for="bme-planner-api-key">API Key</label>
|
||||
<div class="bme-planner-inline-row">
|
||||
<input
|
||||
id="bme-planner-api-key"
|
||||
class="bme-config-input"
|
||||
type="password"
|
||||
placeholder="sk-..."
|
||||
/>
|
||||
<button
|
||||
class="bme-config-secondary-btn"
|
||||
id="bme-planner-toggle-key"
|
||||
type="button"
|
||||
>
|
||||
<span>显示</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bme-config-row">
|
||||
<label for="bme-planner-model">模型</label>
|
||||
<input
|
||||
id="bme-planner-model"
|
||||
class="bme-config-input"
|
||||
type="text"
|
||||
placeholder="gpt-4o / claude-3-5-sonnet / gemini-2.0-flash"
|
||||
/>
|
||||
</div>
|
||||
<div class="bme-model-fetch-block">
|
||||
<button
|
||||
class="bme-config-secondary-btn"
|
||||
id="bme-planner-fetch-models"
|
||||
type="button"
|
||||
>
|
||||
<i class="fa-solid fa-rotate"></i>
|
||||
<span>拉取模型</span>
|
||||
</button>
|
||||
<select
|
||||
id="bme-planner-model-select"
|
||||
class="bme-config-input bme-model-select"
|
||||
style="display: none"
|
||||
>
|
||||
<option value="">-- 从列表选择 --</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="bme-config-actions">
|
||||
<button
|
||||
class="bme-config-test-btn"
|
||||
id="bme-planner-test-conn"
|
||||
type="button"
|
||||
>
|
||||
<i class="fa-solid fa-plug"></i>
|
||||
<span>测试连接</span>
|
||||
</button>
|
||||
留空表示直接跟随当前全局 API;选择某个预设后,规划器会固定使用那套 URL / Key / Model。
|
||||
</div>
|
||||
<div class="bme-planner-status-text" id="bme-planner-api-status"></div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user