Files

196 lines
7.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { extension_settings } from "/scripts/extensions.js";
import { getRequestHeaders } from "/script.js";
import { extensionName } from "../../utils/settings.js";
import { getSlotProfile } from '../api/api-resolver.js';
const DEFAULT_CONFIG = {
apiUrl: "",
apiKey: "",
model: "",
maxTokens: 4000,
temperature: 0.7
};
/** 同步读取旧版配置UI 加载 / 保存用) */
export function getApiConfig(role) {
const settings = extension_settings[extensionName] || {};
const configKey = `acc_${role}_config`;
return { ...DEFAULT_CONFIG, ...(settings[configKey] || {}) };
}
/** 异步读取配置Profile 优先fallback 到旧版 */
async function _resolveConfig(role) {
const profile = await getSlotProfile('autoCharCard');
if (profile) {
return {
apiUrl: profile.apiUrl,
apiKey: profile.apiKey ?? '',
model: profile.model,
maxTokens: profile.maxTokens ?? DEFAULT_CONFIG.maxTokens,
temperature: profile.temperature ?? DEFAULT_CONFIG.temperature,
};
}
return getApiConfig(role);
}
export function setApiConfig(role, config) {
if (!extension_settings[extensionName]) {
extension_settings[extensionName] = {};
}
const configKey = `acc_${role}_config`;
extension_settings[extensionName][configKey] = { ...getApiConfig(role), ...config };
}
export async function callAi(role, messages, options = {}, onChunk = null) {
const config = { ...(await _resolveConfig(role)), ...options };
const roleName = role === 'executor' ? '执行者(模型A)' : '规划者(模型B)';
if (!config.apiUrl || !config.apiKey || !config.model) {
throw new Error(`[自动构建器] ${roleName} API 配置不完整,请检查 URL、Key 和模型设置。`);
}
console.log(`[自动构建器] 正在调用 AI (${roleName})...`, { model: config.model, messagesCount: messages.length, stream: !!onChunk });
const body = {
chat_completion_source: 'openai',
messages: messages,
model: config.model,
reverse_proxy: config.apiUrl,
proxy_password: config.apiKey,
stream: !!onChunk,
max_tokens: config.maxTokens > 0 ? config.maxTokens : undefined,
temperature: config.temperature,
top_p: 1,
custom_prompt_post_processing: 'strict',
enable_web_search: false,
frequency_penalty: 0,
presence_penalty: 0,
};
try {
const response = await fetch('/api/backends/chat-completions/generate', {
method: 'POST',
headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`API 请求失败: ${response.status} - ${errorText}`);
}
if (onChunk) {
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let fullContent = "";
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop();
for (const line of lines) {
const trimmedLine = line.trim();
if (trimmedLine.startsWith('data: ')) {
const dataStr = trimmedLine.slice(6).trim();
if (dataStr === '[DONE]') continue;
try {
const data = JSON.parse(dataStr);
const delta = data.choices[0].delta?.content || "";
if (delta) {
fullContent += delta;
onChunk(delta);
}
} catch (e) {
}
}
}
}
console.log(`[自动构建器] AI (${roleName}) 流式响应结束。长度: ${fullContent.length}`);
return fullContent;
} else {
const responseData = await response.json();
if (!responseData || !responseData.choices || responseData.choices.length === 0) {
if (responseData.error) {
throw new Error(`API 返回错误: ${responseData.error.message || JSON.stringify(responseData.error)}`);
}
throw new Error('API 返回了空响应。');
}
const content = responseData.choices[0].message?.content;
if (!content) {
console.warn(`[自动构建器] AI (${roleName}) 响应内容为空。完整响应:`, responseData);
if (responseData.choices && responseData.choices[0]) {
console.warn("Choices[0]:", responseData.choices[0]);
}
}
console.log(`[自动构建器] AI (${roleName}) 响应接收成功。长度: ${content?.length}`);
return content;
}
} catch (error) {
console.error(`[自动构建器] AI (${roleName}) 调用失败:`, error);
throw error;
}
}
export async function testConnection(role, config = {}) {
try {
const response = await callAi(role, [
{ role: 'user', content: 'Say hello' }
], { maxTokens: 50, ...config });
if (!response) {
return { success: false, error: "API 返回了空内容 (可能是被安全过滤或模型无响应)" };
}
return { success: true };
} catch (error) {
console.error(`[自动构建器] ${role} 连接测试失败:`, error);
return { success: false, error: error.message };
}
}
export async function fetchModels(apiUrl, apiKey) {
// 若未传参,尝试从 Profile 或旧配置读取
if (!apiUrl || !apiKey) {
const resolved = await _resolveConfig('executor');
apiUrl = apiUrl || resolved.apiUrl;
apiKey = apiKey || resolved.apiKey;
}
try {
const response = await fetch('/api/backends/chat-completions/status', {
method: 'POST',
headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify({
reverse_proxy: apiUrl,
proxy_password: apiKey,
chat_completion_source: 'openai'
})
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
const models = Array.isArray(data) ? data : (data.data || data.models || []);
return models.map(m => {
const id = m.id || m.model || m.name || m;
return typeof id === 'string' ? id : JSON.stringify(id);
}).sort();
} catch (error) {
console.error('[自动构建器] 获取模型列表失败:', error);
throw error;
}
}