From 1c64718391b5e005d2ef03c2513763aa224cf4a4 Mon Sep 17 00:00:00 2001 From: SilenceLurker Date: Wed, 11 Mar 2026 20:50:08 +0800 Subject: [PATCH] feat(api): bridge ApiProfileManager to all AI callers, fix config split - Add core/api/api-resolver.js: getSlotProfile(slot) + providerToApiMode() - core/api.js: getApiSettings() async, reads 'main' slot profile first - NccsApi.js: getNccsApiSettings() async, reads 'nccs' slot profile first - Ngms_api.js: getNgmsApiSettings() async, reads 'ngms' slot profile first - JqyhApi.js: getJqyhApiSettings() async, reads 'jqyh' slot profile first - All callers updated with await; legacy DOM/extension_settings as fallback - .gitignore: add WorkDiary.md and Structure.md (local-only files) Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 2 ++ core/api.js | 44 ++++++++++++++++++++++++++------------- core/api/JqyhApi.js | 38 +++++++++++++++++++++++---------- core/api/NccsApi.js | 42 ++++++++++++++++++++++++++----------- core/api/Ngms_api.js | 39 ++++++++++++++++++++++++---------- core/api/api-resolver.js | 45 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 162 insertions(+), 48 deletions(-) create mode 100644 core/api/api-resolver.js diff --git a/.gitignore b/.gitignore index e69de29..98d3247 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,2 @@ +WorkDiary.md +Structure.md diff --git a/core/api.js b/core/api.js index e409dee..c64b0b1 100644 --- a/core/api.js +++ b/core/api.js @@ -1,5 +1,6 @@ import { extension_settings, getContext } from "/scripts/extensions.js"; import { characters } from "/script.js"; +import { getSlotProfile } from './api/api-resolver.js'; import { world_names } from "/scripts/world-info.js"; import { extensionName } from "../utils/settings.js"; import { extractContentByTag, replaceContentByTag, extractFullTagBlock } from '../utils/tagProcessor.js'; @@ -433,28 +434,43 @@ async function fetchSillyTavernPresetModels() { } -export function getApiSettings() { +export async function getApiSettings() { + // 优先读取 'main' 槽位分配的 Profile + const profile = await getSlotProfile('main'); + if (profile) { + return { + apiProvider: profile.provider, + apiUrl: profile.apiUrl, + apiKey: profile.apiKey ?? '', + model: profile.model, + maxTokens: profile.maxTokens ?? 65500, + temperature: profile.temperature ?? 1.0, + tavernProfile: '', + }; + } + + // 降级:读旧 DOM 面板配置 const settings = extension_settings[extensionName] || {}; const apiProvider = document.getElementById('amily2_api_provider')?.value || 'openai'; - + let model; if (apiProvider === 'sillytavern_preset') { const context = getContext(); const profileId = document.getElementById('amily2_preset_selector')?.value; - const profile = context.extensionSettings?.connectionManager?.profiles?.find(p => p.id === profileId); - model = profile?.openai_model || 'Preset Model'; + const stProfile = context.extensionSettings?.connectionManager?.profiles?.find(p => p.id === profileId); + model = stProfile?.openai_model || 'Preset Model'; } else { model = document.getElementById('amily2_model')?.value; } return { - apiProvider: apiProvider, - apiUrl: document.getElementById('amily2_api_url')?.value.trim() || '', - apiKey: document.getElementById('amily2_api_key')?.value.trim() || '', - model: model, - maxTokens: settings.maxTokens || 4000, - temperature: settings.temperature || 0.7, - tavernProfile: document.getElementById('amily2_preset_selector')?.value || '' + apiProvider, + apiUrl: document.getElementById('amily2_api_url')?.value.trim() || '', + apiKey: document.getElementById('amily2_api_key')?.value.trim() || '', + model, + maxTokens: settings.maxTokens || 4000, + temperature: settings.temperature || 0.7, + tavernProfile: document.getElementById('amily2_preset_selector')?.value || '', }; } @@ -468,8 +484,8 @@ export async function testApiConnection() { $button.prop("disabled", true).html(' 测试中'); try { - const apiSettings = getApiSettings(); - + const apiSettings = await getApiSettings(); + if (apiSettings.apiProvider === 'sillytavern_preset') { if (!apiSettings.tavernProfile) { throw new Error("请先在下方选择一个SillyTavern预设"); @@ -518,7 +534,7 @@ export async function callAI(messages, options = {}) { return null; } - const apiSettings = getApiSettings(); + const apiSettings = await getApiSettings(); const finalOptions = { maxTokens: apiSettings.maxTokens, diff --git a/core/api/JqyhApi.js b/core/api/JqyhApi.js index 9a4fb34..fbda8d6 100644 --- a/core/api/JqyhApi.js +++ b/core/api/JqyhApi.js @@ -2,6 +2,7 @@ import { extension_settings, getContext } from "/scripts/extensions.js"; import { characters, this_chid, getRequestHeaders, saveSettingsDebounced, eventSource, event_types } from "/script.js"; import { extensionName } from "../../utils/settings.js"; import { amilyHelper } from '../../core/tavern-helper/main.js'; +import { getSlotProfile, providerToApiMode } from './api-resolver.js'; let ChatCompletionService = undefined; try { @@ -42,15 +43,30 @@ function normalizeApiResponse(responseData) { return data; } -export function getJqyhApiSettings() { +export async function getJqyhApiSettings() { + // 优先读取 'jqyh' 槽位分配的 Profile + const profile = await getSlotProfile('jqyh'); + if (profile) { + return { + apiMode: providerToApiMode(profile.provider), + apiUrl: profile.apiUrl, + apiKey: profile.apiKey ?? '', + model: profile.model, + maxTokens: profile.maxTokens ?? 65500, + temperature: profile.temperature ?? 1.0, + tavernProfile: '', + }; + } + + // 降级:读旧 extension_settings 字段 return { - apiMode: extension_settings[extensionName]?.jqyhApiMode || 'openai_test', - apiUrl: extension_settings[extensionName]?.jqyhApiUrl?.trim() || '', - apiKey: extension_settings[extensionName]?.jqyhApiKey?.trim() || '', - model: extension_settings[extensionName]?.jqyhModel || '', - maxTokens: extension_settings[extensionName]?.jqyhMaxTokens || 4000, - temperature: extension_settings[extensionName]?.jqyhTemperature || 0.7, - tavernProfile: extension_settings[extensionName]?.jqyhTavernProfile || '' + apiMode: extension_settings[extensionName]?.jqyhApiMode || 'openai_test', + apiUrl: extension_settings[extensionName]?.jqyhApiUrl?.trim() || '', + apiKey: extension_settings[extensionName]?.jqyhApiKey?.trim() || '', + model: extension_settings[extensionName]?.jqyhModel || '', + maxTokens: extension_settings[extensionName]?.jqyhMaxTokens || 4000, + temperature: extension_settings[extensionName]?.jqyhTemperature || 0.7, + tavernProfile: extension_settings[extensionName]?.jqyhTavernProfile || '', }; } @@ -60,7 +76,7 @@ export async function callJqyhAI(messages, options = {}) { return null; } - const apiSettings = getJqyhApiSettings(); + const apiSettings = await getJqyhApiSettings(); const finalOptions = { maxTokens: apiSettings.maxTokens, @@ -258,7 +274,7 @@ async function callJqyhSillyTavernPreset(messages, options) { export async function fetchJqyhModels() { console.log('[Amily2号-Jqyh外交部] 开始获取模型列表'); - const apiSettings = getJqyhApiSettings(); + const apiSettings = await getJqyhApiSettings(); try { if (apiSettings.apiMode === 'sillytavern_preset') { @@ -339,7 +355,7 @@ export async function fetchJqyhModels() { export async function testJqyhApiConnection() { console.log('[Amily2号-Jqyh外交部] 开始API连接测试'); - const apiSettings = getJqyhApiSettings(); + const apiSettings = await getJqyhApiSettings(); if (apiSettings.apiMode === 'sillytavern_preset') { if (!apiSettings.tavernProfile) { diff --git a/core/api/NccsApi.js b/core/api/NccsApi.js index b044607..408ac1a 100644 --- a/core/api/NccsApi.js +++ b/core/api/NccsApi.js @@ -2,6 +2,7 @@ import { extension_settings, getContext } from "/scripts/extensions.js"; import { characters, this_chid, getRequestHeaders, saveSettingsDebounced, eventSource, event_types } from "/script.js"; import { extensionName } from "../../utils/settings.js"; import { amilyHelper } from '../../core/tavern-helper/main.js'; +import { getSlotProfile, providerToApiMode } from './api-resolver.js'; let ChatCompletionService = undefined; try { @@ -36,17 +37,34 @@ if (window.Amily2Bus) { toastr.error("核心组件 Amily2Bus 丢失,请检查安装。", "Nccs-System"); } -export function getNccsApiSettings() { +export async function getNccsApiSettings() { + // 优先读取 'nccs' 槽位分配的 Profile + const profile = await getSlotProfile('nccs'); + if (profile) { + return { + nccsEnabled: true, + apiMode: providerToApiMode(profile.provider), + apiUrl: profile.apiUrl, + apiKey: profile.apiKey ?? '', + model: profile.model, + maxTokens: profile.maxTokens ?? 65500, + temperature: profile.temperature ?? 1.0, + tavernProfile: '', + useFakeStream: false, + }; + } + + // 降级:读旧 extension_settings 字段 return { - nccsEnabled: extension_settings[extensionName]?.nccsEnabled || false, - apiMode: extension_settings[extensionName]?.nccsApiMode || 'openai_test', - apiUrl: extension_settings[extensionName]?.nccsApiUrl?.trim() || '', - apiKey: extension_settings[extensionName]?.nccsApiKey?.trim() || '', - model: extension_settings[extensionName]?.nccsModel || '', - maxTokens: extension_settings[extensionName]?.nccsMaxTokens || 4000, - temperature: extension_settings[extensionName]?.nccsTemperature || 0.7, + nccsEnabled: extension_settings[extensionName]?.nccsEnabled || false, + apiMode: extension_settings[extensionName]?.nccsApiMode || 'openai_test', + apiUrl: extension_settings[extensionName]?.nccsApiUrl?.trim() || '', + apiKey: extension_settings[extensionName]?.nccsApiKey?.trim() || '', + model: extension_settings[extensionName]?.nccsModel || '', + maxTokens: extension_settings[extensionName]?.nccsMaxTokens || 4000, + temperature: extension_settings[extensionName]?.nccsTemperature || 0.7, tavernProfile: extension_settings[extensionName]?.nccsTavernProfile || '', - useFakeStream: extension_settings[extensionName]?.nccsFakeStreamEnabled || false + useFakeStream: extension_settings[extensionName]?.nccsFakeStreamEnabled || false, }; } @@ -60,7 +78,7 @@ export async function callNccsAI(messages, options = {}) { return null; } - const settings = getNccsApiSettings(); + const settings = await getNccsApiSettings(); const finalOptions = { ...settings, ...options @@ -238,7 +256,7 @@ async function callNccsSillyTavernPreset(messages, options) { export async function fetchNccsModels() { console.log('[Amily2号-Nccs外交部] 开始获取模型列表'); - const apiSettings = getNccsApiSettings(); + const apiSettings = await getNccsApiSettings(); try { if (apiSettings.apiMode === 'sillytavern_preset') { @@ -320,7 +338,7 @@ export async function fetchNccsModels() { export async function testNccsApiConnection() { console.log('[Amily2号-Nccs外交部] 开始API连接测试'); - const apiSettings = getNccsApiSettings(); + const apiSettings = await getNccsApiSettings(); if (apiSettings.apiMode === 'sillytavern_preset') { if (!apiSettings.tavernProfile) { diff --git a/core/api/Ngms_api.js b/core/api/Ngms_api.js index b6d3d99..b121738 100644 --- a/core/api/Ngms_api.js +++ b/core/api/Ngms_api.js @@ -2,6 +2,7 @@ import { extension_settings, getContext } from "/scripts/extensions.js"; import { characters, this_chid, getRequestHeaders, saveSettingsDebounced, eventSource, event_types } from "/script.js"; import { extensionName } from "../../utils/settings.js"; import { amilyHelper } from '../../core/tavern-helper/main.js'; +import { getSlotProfile, providerToApiMode } from './api-resolver.js'; let ChatCompletionService = undefined; try { @@ -42,16 +43,32 @@ function normalizeApiResponse(responseData) { return data; } -export function getNgmsApiSettings() { +export async function getNgmsApiSettings() { + // 优先读取 'ngms' 槽位分配的 Profile + const profile = await getSlotProfile('ngms'); + if (profile) { + return { + apiMode: providerToApiMode(profile.provider), + apiUrl: profile.apiUrl, + apiKey: profile.apiKey ?? '', + model: profile.model, + maxTokens: profile.maxTokens ?? 65500, + temperature: profile.temperature ?? 1.0, + tavernProfile: '', + useFakeStream: false, + }; + } + + // 降级:读旧 extension_settings 字段 return { - apiMode: extension_settings[extensionName]?.ngmsApiMode || 'openai_test', - apiUrl: extension_settings[extensionName]?.ngmsApiUrl?.trim() || '', - apiKey: extension_settings[extensionName]?.ngmsApiKey?.trim() || '', - model: extension_settings[extensionName]?.ngmsModel || '', - maxTokens: extension_settings[extensionName]?.ngmsMaxTokens || 4000, - temperature: extension_settings[extensionName]?.ngmsTemperature || 0.7, + apiMode: extension_settings[extensionName]?.ngmsApiMode || 'openai_test', + apiUrl: extension_settings[extensionName]?.ngmsApiUrl?.trim() || '', + apiKey: extension_settings[extensionName]?.ngmsApiKey?.trim() || '', + model: extension_settings[extensionName]?.ngmsModel || '', + maxTokens: extension_settings[extensionName]?.ngmsMaxTokens || 4000, + temperature: extension_settings[extensionName]?.ngmsTemperature || 0.7, tavernProfile: extension_settings[extensionName]?.ngmsTavernProfile || '', - useFakeStream: extension_settings[extensionName]?.ngmsFakeStreamEnabled || false + useFakeStream: extension_settings[extensionName]?.ngmsFakeStreamEnabled || false, }; } @@ -61,7 +78,7 @@ export async function callNgmsAI(messages, options = {}) { return null; } - const apiSettings = getNgmsApiSettings(); + const apiSettings = await getNgmsApiSettings(); const finalOptions = { maxTokens: apiSettings.maxTokens, @@ -324,7 +341,7 @@ async function callNgmsSillyTavernPreset(messages, options) { export async function fetchNgmsModels() { console.log('[Amily2号-Ngms外交部] 开始获取模型列表'); - const apiSettings = getNgmsApiSettings(); + const apiSettings = await getNgmsApiSettings(); try { if (apiSettings.apiMode === 'sillytavern_preset') { @@ -407,7 +424,7 @@ export async function fetchNgmsModels() { export async function testNgmsApiConnection() { console.log('[Amily2号-Ngms外交部] 开始API连接测试'); - const apiSettings = getNgmsApiSettings(); + const apiSettings = await getNgmsApiSettings(); if (apiSettings.apiMode === 'sillytavern_preset') { if (!apiSettings.tavernProfile) { diff --git a/core/api/api-resolver.js b/core/api/api-resolver.js new file mode 100644 index 0000000..d4c60b8 --- /dev/null +++ b/core/api/api-resolver.js @@ -0,0 +1,45 @@ +/** + * api-resolver.js — API 配置槽位解析器 + * + * 职责: + * 优先从 ApiProfileManager 读取功能槽分配的 Profile(含解密 Key), + * 无分配时返回 null,由调用方执行旧配置兜底。 + * + * 使用方式: + * const profile = await getSlotProfile('main'); + * if (profile) { // 用 profile.provider / apiUrl / apiKey / model ... } + * else { // 回退到旧 DOM / extension_settings 读取 } + * + * provider → apiMode 映射(供 Nccs / Ngms / Jqyh 内部 switch 使用): + * 'openai' → 'openai_test' (经 ST 后端代理发送,规避 CORS) + * 'google' → 'openai_test' (Google OpenAI-compat 同样走代理) + * 'sillytavern_backend'→ 'openai_test' + * 'sillytavern_preset' → 'sillytavern_preset' + */ + +import { apiProfileManager } from '../../utils/config/ApiProfileManager.js'; + +/** + * 将 Profile.provider 映射到子模块使用的 apiMode 字段。 + * @param {string} provider + * @returns {'openai_test'|'sillytavern_preset'} + */ +export function providerToApiMode(provider) { + return provider === 'sillytavern_preset' ? 'sillytavern_preset' : 'openai_test'; +} + +/** + * 获取功能槽对应的完整 Profile(含解密 Key)。 + * 未分配或读取失败时返回 null。 + * + * @param {string} slot 功能槽名(见 ApiProfileManager.SLOTS) + * @returns {Promise} + */ +export async function getSlotProfile(slot) { + try { + return await apiProfileManager.getAssignedProfile(slot); + } catch (e) { + console.warn(`[ApiResolver] 读取槽位 "${slot}" 失败,降级到旧配置:`, e); + return null; + } +}