diff --git a/CharacterWorldBook/src/cwb_apiService.js b/CharacterWorldBook/src/cwb_apiService.js new file mode 100644 index 0000000..7e45e4c --- /dev/null +++ b/CharacterWorldBook/src/cwb_apiService.js @@ -0,0 +1,548 @@ +import { state } from './cwb_state.js'; +import { logError, showToastr, escapeHtml } from './cwb_utils.js'; +import { getRequestHeaders } from '/script.js'; +import { extensionName } from '../../utils/settings.js'; +import { extension_settings, getContext } from "/scripts/extensions.js"; + +function normalizeApiResponse(responseData) { + let data = responseData; + if (typeof data === 'string') { + try { + data = JSON.parse(data); + } catch (e) { + console.error(`[${extensionName}] API响应JSON解析失败:`, e); + return { error: { message: 'Invalid JSON response' } }; + } + } + if (data && typeof data.data === 'object' && data.data !== null && !Array.isArray(data.data)) { + if (Object.hasOwn(data.data, 'data')) { + data = data.data; + } + } + if (data && data.choices && data.choices[0]) { + return { content: data.choices[0].message?.content?.trim() }; + } + if (data && data.content) { + return { content: data.content.trim() }; + } + if (data && data.data) { + return { data: data.data }; + } + if (data && data.error) { + return { error: data.error }; + } + return data; +} + + +function getCwbApiSettings() { + return { + apiMode: extension_settings[extensionName]?.cwb_api_mode || 'openai_test', + apiUrl: extension_settings[extensionName]?.cwb_api_url?.trim() || '', + apiKey: extension_settings[extensionName]?.cwb_api_key?.trim() || '', + model: extension_settings[extensionName]?.cwb_api_model || '', + tavernProfile: extension_settings[extensionName]?.cwb_tavern_profile || '' + }; +} + +async function callCwbSillyTavernPreset(messages, options) { + console.log('[CWB-ST预设] 使用SillyTavern预设调用'); + + if (!window.TavernHelper || !window.TavernHelper.triggerSlash) { + throw new Error('TavernHelper不可用,无法使用SillyTavern预设模式'); + } + + const context = getContext(); + if (!context) { + throw new Error('无法获取SillyTavern上下文'); + } + + const profileId = options.tavernProfile; + if (!profileId) { + throw new Error('未配置SillyTavern预设ID'); + } + + let originalProfile = ''; + let responsePromise; + + try { + originalProfile = await window.TavernHelper.triggerSlash('/profile'); + console.log(`[CWB-ST预设] 当前配置文件: ${originalProfile}`); + + const targetProfile = context.extensionSettings?.connectionManager?.profiles?.find(p => p.id === profileId); + if (!targetProfile) { + throw new Error(`未找到配置文件ID: ${profileId}`); + } + + const targetProfileName = targetProfile.name; + console.log(`[CWB-ST预设] 目标配置文件: ${targetProfileName}`); + + const currentProfile = await window.TavernHelper.triggerSlash('/profile'); + if (currentProfile !== targetProfileName) { + console.log(`[CWB-ST预设] 切换配置文件: ${currentProfile} -> ${targetProfileName}`); + const escapedProfileName = targetProfileName.replace(/"/g, '\\"'); + await window.TavernHelper.triggerSlash(`/profile await=true "${escapedProfileName}"`); + } + + if (!context.ConnectionManagerRequestService) { + throw new Error('ConnectionManagerRequestService不可用'); + } + + console.log(`[CWB-ST预设] 通过配置文件 ${targetProfileName} 发送请求`); + responsePromise = context.ConnectionManagerRequestService.sendRequest( + targetProfile.id, + messages, + options.maxTokens || 100000 + ); + + } finally { + try { + const currentProfileAfterCall = await window.TavernHelper.triggerSlash('/profile'); + if (originalProfile && originalProfile !== currentProfileAfterCall) { + console.log(`[CWB-ST预设] 恢复原始配置文件: ${currentProfileAfterCall} -> ${originalProfile}`); + const escapedOriginalProfile = originalProfile.replace(/"/g, '\\"'); + await window.TavernHelper.triggerSlash(`/profile await=true "${escapedOriginalProfile}"`); + } + } catch (restoreError) { + console.error('[CWB-ST预设] 恢复配置文件失败:', restoreError); + } + } + + const result = await responsePromise; + + if (!result) { + throw new Error('未收到API响应'); + } + + const normalizedResult = normalizeApiResponse(result); + if (normalizedResult.error) { + throw new Error(normalizedResult.error.message || 'SillyTavern预设API调用失败'); + } + + return normalizedResult.content; +} + +async function callCwbOpenAITest(messages, options) { + const response = await fetch('/api/backends/chat-completions/generate', { + method: 'POST', + headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' }, + body: JSON.stringify({ + chat_completion_source: 'openai', + custom_prompt_post_processing: 'strict', + enable_web_search: false, + frequency_penalty: 0, + group_names: [], + include_reasoning: false, + max_tokens: options.maxTokens || 100000, + messages: messages, + model: options.model, + presence_penalty: 0.12, + proxy_password: options.apiKey, + reasoning_effort: 'medium', + request_images: false, + reverse_proxy: options.apiUrl, + stream: false, + temperature: options.temperature || 1, + top_p: options.top_p || 1 + }) + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`CWB全兼容API请求失败: ${response.status} - ${errorText}`); + } + + const responseData = await response.json(); + return responseData?.choices?.[0]?.message?.content; +} + +export async function callCwbAPI(systemPrompt, userPromptContent, options = {}) { + const apiSettings = getCwbApiSettings(); + + const finalOptions = { + maxTokens: 100000, + temperature: 1, + model: apiSettings.model, + apiUrl: apiSettings.apiUrl, + apiKey: apiSettings.apiKey, + apiMode: apiSettings.apiMode, + tavernProfile: apiSettings.tavernProfile, + ...options + }; + + if (finalOptions.apiMode !== 'sillytavern_preset') { + if (!finalOptions.apiUrl || !finalOptions.model || !finalOptions.apiKey) { + throw new Error('API配置不完整,请检查URL、Key和模型配置'); + } + } else { + if (!finalOptions.tavernProfile) { + throw new Error('未配置SillyTavern预设ID'); + } + } + + const combinedSystemPrompt = `${state.currentBreakArmorPrompt}\n\n${systemPrompt}`; + + const messages = [ + { role: 'system', content: combinedSystemPrompt }, + { role: 'user', content: userPromptContent }, + ]; + + console.groupCollapsed(`[CWB] 统一API调用 @ ${new Date().toLocaleTimeString()}`); + console.log("【请求参数】:", { + mode: finalOptions.apiMode, + model: finalOptions.model, + maxTokens: finalOptions.maxTokens, + temperature: finalOptions.temperature, + messagesCount: messages.length + }); + console.log("【消息内容】:", messages); + + try { + let responseContent; + + switch (finalOptions.apiMode) { + case 'openai_test': + responseContent = await callCwbOpenAITest(messages, finalOptions); + break; + case 'sillytavern_preset': + responseContent = await callCwbSillyTavernPreset(messages, finalOptions); + break; + default: + throw new Error(`未支持的API模式: ${finalOptions.apiMode}`); + } + + if (!responseContent) { + throw new Error('未能获取AI响应内容'); + } + + console.log("【AI回复】:", responseContent); + console.groupEnd(); + + return responseContent.trim(); + + } catch (error) { + console.error(`[CWB] API调用发生错误:`, error); + console.groupEnd(); + throw error; + } +} + +export async function loadModels($panel) { + const apiSettings = getCwbApiSettings(); + const $modelSelect = $panel.find('#cwb-api-model'); + const $apiStatus = $panel.find('#cwb-api-status'); + + $apiStatus.text('状态: 正在加载模型列表...').css('color', '#61afef'); + showToastr('info', '正在加载模型列表...'); + + try { + let models = []; + + if (apiSettings.apiMode === 'sillytavern_preset') { + const context = getContext(); + if (!context?.extensionSettings?.connectionManager?.profiles) { + throw new Error('无法获取SillyTavern配置文件列表'); + } + + const targetProfile = context.extensionSettings.connectionManager.profiles.find(p => p.id === apiSettings.tavernProfile); + if (!targetProfile) { + throw new Error(`未找到配置文件ID: ${apiSettings.tavernProfile}`); + } + + if (targetProfile.openai_model) { + models.push({ id: targetProfile.openai_model, name: targetProfile.openai_model }); + } + + if (models.length === 0) { + throw new Error('当前预设未配置模型'); + } + + } else { + if (!apiSettings.apiUrl || !apiSettings.apiKey) { + throw new Error('API URL或Key未配置'); + } + + const response = await fetch('/api/backends/chat-completions/status', { + method: 'POST', + headers: { + ...getRequestHeaders(), + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + reverse_proxy: apiSettings.apiUrl, + proxy_password: apiSettings.apiKey, + chat_completion_source: 'openai' + }) + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`HTTP ${response.status}: ${errorText}`); + } + + const rawData = await response.json(); + const result = normalizeApiResponse(rawData); + const modelList = result.data || []; + + if (result.error || !Array.isArray(modelList)) { + const errorMessage = result.error?.message || 'API未返回有效的模型列表数组'; + throw new Error(errorMessage); + } + + models = modelList + .map(m => ({ + id: m.id || m.model || m, + name: m.id || m.model || m + })) + .filter(m => m.id) + .sort((a, b) => a.name.localeCompare(b.name)); + } + + $modelSelect.empty(); + if (models.length > 0) { + models.forEach(model => { + $modelSelect.append(jQuery('