812 lines
35 KiB
JavaScript
812 lines
35 KiB
JavaScript
/**
|
|
* API配置管理器
|
|
* 负责管理API配置弹窗和API相关设置
|
|
*/
|
|
|
|
import { app } from "../../../../scripts/app.js";
|
|
import { logger } from '../utils/logger.js';
|
|
import {
|
|
createSettingsDialog,
|
|
createFormGroup,
|
|
createInputGroup,
|
|
createSelectGroup,
|
|
createHorizontalFormGroup
|
|
} from "./settings.js";
|
|
|
|
class APIConfigManager {
|
|
constructor() {
|
|
this.llmAllProviders = {};
|
|
this.visionAllProviders = {};
|
|
this.llmModel = null;
|
|
this.llmApiKey = null;
|
|
this.llmBaseUrl = null;
|
|
this.visionModel = null;
|
|
this.visionApiKey = null;
|
|
this.visionBaseUrl = null;
|
|
}
|
|
|
|
/**
|
|
* 显示API配置弹窗
|
|
*/
|
|
showAPIConfigModal() {
|
|
try {
|
|
logger.debug('打开API配置弹窗');
|
|
|
|
createSettingsDialog({
|
|
title: 'API管理器',
|
|
dialogClassName: 'api-config-dialog',
|
|
disableBackdropAndCloseOnClickOutside: true,
|
|
renderContent: (container) => {
|
|
this._createAPIConfigUI(container);
|
|
},
|
|
onSave: async () => {
|
|
try {
|
|
const container = document.querySelector('.settings-modal .p-dialog-content');
|
|
const controls = container.formControls;
|
|
|
|
// 先保存当前显示的提供商配置
|
|
const currentLLMProvider = controls.llm.provider.value;
|
|
this._saveLLMProviderConfig(currentLLMProvider);
|
|
|
|
const currentVisionProvider = controls.vision.provider.value;
|
|
this._saveVisionProviderConfig(currentVisionProvider);
|
|
|
|
// 处理百度翻译配置
|
|
const baiduConfig = {
|
|
app_id: controls.baidu.appId.value.trim(),
|
|
};
|
|
const baiduSecretEl = controls.baidu.secret;
|
|
const baiduSecretValue = baiduSecretEl.value.trim();
|
|
const isBaiduMasked = baiduSecretValue === '••••••••••••••••••••••••••••••••••••••••••••••••';
|
|
|
|
// 只有当密钥不是掩码,或者掩码不代表现有密钥时,才包含secret_key
|
|
if (!isBaiduMasked || baiduSecretEl.dataset.hasKey !== 'true') {
|
|
baiduConfig.secret_key = baiduSecretValue;
|
|
}
|
|
|
|
// 获取配置值
|
|
const config = {
|
|
baidu: baiduConfig,
|
|
llm: {
|
|
current_provider: currentLLMProvider,
|
|
providers: this.llmAllProviders || {}
|
|
},
|
|
vision: {
|
|
current_provider: currentVisionProvider,
|
|
providers: this.visionAllProviders || {}
|
|
}
|
|
};
|
|
|
|
// 确保每个提供商都有配置
|
|
const providerList = ["zhipu", "siliconflow", "custom"];
|
|
providerList.forEach(provider => {
|
|
// 确保LLM提供商配置存在
|
|
if (!config.llm.providers[provider]) {
|
|
config.llm.providers[provider] = {
|
|
model: provider === "zhipu" ? "glm-4-flash-250414" :
|
|
(provider === "siliconflow" ? "Qwen/Qwen2.5-7B-Instruct" : ""),
|
|
base_url: provider === "zhipu" ? "https://open.bigmodel.cn/api/paas/v4/chat/completions" :
|
|
(provider === "siliconflow" ? "https://api.siliconflow.cn/v1/chat/completions" : ""),
|
|
api_key: "",
|
|
temperature: 0.7,
|
|
max_tokens: 2000,
|
|
top_p: 0.9
|
|
};
|
|
}
|
|
|
|
// 确保视觉模型提供商配置存在
|
|
if (!config.vision.providers[provider]) {
|
|
config.vision.providers[provider] = {
|
|
model: provider === "zhipu" ? "glm-4v-flash" :
|
|
(provider === "siliconflow" ? "THUDM/GLM-4.1V-9B-Thinking" : ""),
|
|
base_url: provider === "zhipu" ? "https://open.bigmodel.cn/api/paas/v4/chat/completions" :
|
|
(provider === "siliconflow" ? "https://api.siliconflow.cn/v1/chat/completions" : ""),
|
|
api_key: "",
|
|
temperature: 0.7,
|
|
max_tokens: 2000,
|
|
top_p: 0.9
|
|
};
|
|
}
|
|
});
|
|
|
|
console.log("保存配置:", JSON.stringify(config)); // 调试信息
|
|
|
|
// 保存配置
|
|
await Promise.all([
|
|
fetch('/prompt_assistant/api/config/baidu_translate', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(config.baidu)
|
|
}),
|
|
fetch('/prompt_assistant/api/config/llm', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(config.llm)
|
|
}),
|
|
fetch('/prompt_assistant/api/config/vision', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(config.vision)
|
|
})
|
|
]).then(responses => {
|
|
// 检查所有响应是否成功
|
|
const failedResponses = responses.filter(response => !response.ok);
|
|
if (failedResponses.length > 0) {
|
|
// 至少有一个请求失败
|
|
throw new Error(`配置保存失败,服务器返回错误: ${failedResponses[0].status}`);
|
|
}
|
|
|
|
// 所有请求成功
|
|
app.extensionManager.toast.add({
|
|
severity: "success",
|
|
summary: "配置已更新",
|
|
life: 3000
|
|
});
|
|
}).catch(error => {
|
|
// 显示错误提示
|
|
app.extensionManager.toast.add({
|
|
severity: "error",
|
|
summary: "配置保存失败",
|
|
detail: error.message || "保存配置时发生错误",
|
|
life: 3000
|
|
});
|
|
|
|
// 重新抛出错误,以便外层catch可以处理
|
|
throw error;
|
|
});
|
|
} catch (error) {
|
|
// 这里捕获的是其他类型的错误,如代码执行错误
|
|
app.extensionManager.toast.add({
|
|
severity: "error",
|
|
summary: "配置保存失败",
|
|
detail: error.message || "保存配置时发生错误",
|
|
life: 3000
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
});
|
|
} catch (error) {
|
|
logger.error(`打开API配置弹窗失败: ${error.message}`);
|
|
app.extensionManager.toast.add({
|
|
severity: "error",
|
|
summary: "打开配置失败",
|
|
detail: error.message || "打开配置弹窗过程中发生错误",
|
|
life: 3000
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 创建API配置UI
|
|
* @param {HTMLElement} container 容器元素
|
|
*/
|
|
_createAPIConfigUI(container) {
|
|
// 创建表单
|
|
const form = document.createElement('form');
|
|
form.onsubmit = (e) => e.preventDefault();
|
|
form.className = 'api-config-form';
|
|
|
|
// 1. 百度翻译 API 配置
|
|
const baiduSection = this._createBaiduSection();
|
|
|
|
// 2. LLM 配置
|
|
const llmSection = this._createLLMSection();
|
|
|
|
// 3. 视觉模型配置
|
|
const visionSection = this._createVisionSection();
|
|
|
|
// 添加所有部分到表单
|
|
form.appendChild(baiduSection);
|
|
form.appendChild(llmSection);
|
|
form.appendChild(visionSection);
|
|
container.appendChild(form);
|
|
|
|
// 加载已有配置
|
|
this._loadAPIConfig(container);
|
|
}
|
|
|
|
/**
|
|
* 创建百度翻译API配置部分
|
|
* @returns {HTMLElement} 百度翻译API配置部分的DOM元素
|
|
*/
|
|
_createBaiduSection() {
|
|
// 百度翻译 API 配置
|
|
const baiduSection = createFormGroup('百度翻译配置', [
|
|
{ text: '百度翻译API申请', url: 'https://fanyi-api.baidu.com/' }
|
|
]);
|
|
|
|
// 创建输入框
|
|
const baiduAppId = createInputGroup('', '请输入百度翻译 AppID');
|
|
const baiduSecret = createInputGroup('', '请输入百度翻译密钥');
|
|
|
|
// 创建水平布局组
|
|
const baiduGroup = createHorizontalFormGroup([
|
|
{ label: 'AppID', element: baiduAppId.input },
|
|
{ label: 'Secret Key', element: baiduSecret.input }
|
|
]);
|
|
|
|
baiduSection.appendChild(baiduGroup);
|
|
|
|
// 保存输入框引用,便于后续获取值
|
|
this.baiduAppId = baiduAppId.input;
|
|
this.baiduSecret = baiduSecret.input;
|
|
|
|
return baiduSection;
|
|
}
|
|
|
|
/**
|
|
* 创建LLM配置部分
|
|
* @returns {HTMLElement} LLM配置部分的DOM元素
|
|
*/
|
|
_createLLMSection() {
|
|
// LLM 配置
|
|
const llmSection = createFormGroup('大模型配置', [
|
|
{ text: '智谱API申请', url: 'https://www.bigmodel.cn/invite?icode=Wz1tQAT40T9M8vwp%2F1db7nHEaazDlIZGj9HxftzTbt4%3D' },
|
|
{ text: '硅基流动API申请', url: 'https://cloud.siliconflow.cn/i/FCDL2zBQ' }
|
|
]);
|
|
|
|
// 创建选择器和输入框
|
|
const llmProvider = createSelectGroup('', [
|
|
{ value: 'zhipu', text: '智谱' },
|
|
{ value: 'siliconflow', text: '硅基流动' },
|
|
{ value: 'custom', text: '自定义' }
|
|
]);
|
|
const llmModelInput = createInputGroup('', '请输入模型名称');
|
|
this.llmModel = llmModelInput.input; // 保存引用以供外部函数使用
|
|
|
|
// 创建base_url输入框(初始隐藏)
|
|
const llmBaseUrlInput = createInputGroup('Base URL', '请输入API基础URL,例如: https://api.example.com/v1');
|
|
this.llmBaseUrl = llmBaseUrlInput.input; // 保存引用以供外部函数使用
|
|
llmBaseUrlInput.group.style.display = 'none'; // 初始隐藏
|
|
|
|
// 在base_url输入框下添加提示信息
|
|
const llmBaseUrlHint = document.createElement('div');
|
|
llmBaseUrlHint.className = 'base-url-hint';
|
|
llmBaseUrlHint.style.fontSize = '12px';
|
|
llmBaseUrlHint.style.color = '#666';
|
|
llmBaseUrlHint.style.marginTop = '4px';
|
|
llmBaseUrlHint.style.marginBottom = '8px';
|
|
llmBaseUrlHint.textContent = '注意: 请输入基础URL,不要包含/chat/completions路径';
|
|
llmBaseUrlInput.group.appendChild(llmBaseUrlHint);
|
|
|
|
// 创建水平布局组
|
|
const llmProviderGroup = createHorizontalFormGroup([
|
|
{ label: '模型提供商', element: llmProvider.group },
|
|
{ label: '模型名称', element: this.llmModel }
|
|
]);
|
|
|
|
// API Key 保持单独一行
|
|
const llmApiKeyInput = createInputGroup('API Key', '请输入模型 API Key');
|
|
this.llmApiKey = llmApiKeyInput.input; // 保存引用以供外部函数使用
|
|
|
|
// ---模型参数配置---
|
|
// 创建温度和最大token输入框
|
|
const llmTemperatureInput = createInputGroup('', '0.1-2.0,控制输出随机性');
|
|
llmTemperatureInput.input.type = 'number';
|
|
llmTemperatureInput.input.min = '0.1';
|
|
llmTemperatureInput.input.max = '2.0';
|
|
llmTemperatureInput.input.step = '0.1';
|
|
llmTemperatureInput.input.value = '0.7'; // 默认值
|
|
this.llmTemperature = llmTemperatureInput.input;
|
|
|
|
const llmMaxTokensInput = createInputGroup('', '1-32000,控制最大输出长度');
|
|
llmMaxTokensInput.input.type = 'number';
|
|
llmMaxTokensInput.input.min = '1';
|
|
llmMaxTokensInput.input.max = '32000';
|
|
llmMaxTokensInput.input.step = '1';
|
|
llmMaxTokensInput.input.value = '2000'; // 默认值
|
|
this.llmMaxTokens = llmMaxTokensInput.input;
|
|
|
|
const llmTopPInput = createInputGroup('', '0.1-1.0,控制核采样范围');
|
|
llmTopPInput.input.type = 'number';
|
|
llmTopPInput.input.min = '0.1';
|
|
llmTopPInput.input.max = '1.0';
|
|
llmTopPInput.input.step = '0.1';
|
|
llmTopPInput.input.value = '0.9'; // 默认值
|
|
this.llmTopP = llmTopPInput.input;
|
|
|
|
// 创建模型参数水平布局组(三个参数在同一行)
|
|
const llmParamsGroup = createHorizontalFormGroup([
|
|
{ label: '温度 (Temperature)', element: this.llmTemperature },
|
|
{ label: '核采样 (Top-P)', element: this.llmTopP },
|
|
{ label: '最大Token数', element: this.llmMaxTokens }
|
|
]);
|
|
|
|
llmSection.appendChild(llmProviderGroup);
|
|
llmSection.appendChild(llmBaseUrlInput.group); // 添加base_url输入框
|
|
llmSection.appendChild(llmApiKeyInput.group);
|
|
llmSection.appendChild(llmParamsGroup); // 添加模型参数配置(三个参数在同一行)
|
|
|
|
// 监听LLM提供商选择变化
|
|
llmProvider.select.addEventListener('change', () => {
|
|
const oldProvider = llmProvider.select.dataset.previousValue;
|
|
const provider = llmProvider.select.value;
|
|
|
|
// 如果存在旧提供商,保存其配置
|
|
if (oldProvider) {
|
|
this._saveLLMProviderConfig(oldProvider);
|
|
}
|
|
|
|
// 根据是否选择自定义类型显示/隐藏base_url输入框
|
|
llmBaseUrlInput.group.style.display = provider === 'custom' ? 'block' : 'none';
|
|
|
|
// 加载选定提供商的配置
|
|
if (this.llmAllProviders && this.llmAllProviders[provider]) {
|
|
const providerConfig = this.llmAllProviders[provider];
|
|
this.llmModel.value = providerConfig.model || '';
|
|
|
|
// 设置API密钥(使用掩码)
|
|
if (providerConfig.api_key || providerConfig.has_key) {
|
|
this._setMaskedValue(this.llmApiKey, providerConfig.api_key || "dummy-key");
|
|
} else {
|
|
this.llmApiKey.value = '';
|
|
this.llmApiKey.dataset.hasKey = 'false';
|
|
}
|
|
|
|
// 加载模型参数
|
|
this.llmTemperature.value = providerConfig.temperature || '0.7';
|
|
this.llmMaxTokens.value = providerConfig.max_tokens || '2000';
|
|
this.llmTopP.value = providerConfig.top_p || '0.9';
|
|
|
|
if (provider === 'custom') {
|
|
this.llmBaseUrl.value = providerConfig.base_url || '';
|
|
}
|
|
} else {
|
|
// 设置默认模型名称
|
|
if (provider === 'zhipu') {
|
|
this.llmModel.value = 'glm-4-flash-250414';
|
|
} else if (provider === 'siliconflow') {
|
|
this.llmModel.value = 'Qwen/Qwen2.5-7B-Instruct';
|
|
} else {
|
|
this.llmModel.value = '';
|
|
}
|
|
// 清空API Key和base_url
|
|
this.llmApiKey.value = '';
|
|
this.llmApiKey.dataset.hasKey = 'false';
|
|
// 设置默认模型参数
|
|
this.llmTemperature.value = '0.7';
|
|
this.llmMaxTokens.value = '2000';
|
|
this.llmTopP.value = '0.9';
|
|
if (provider === 'custom') {
|
|
this.llmBaseUrl.value = '';
|
|
}
|
|
}
|
|
|
|
// 记录当前选择的提供商
|
|
llmProvider.select.dataset.previousValue = provider;
|
|
});
|
|
|
|
// 保存选择器引用,便于后续获取值
|
|
this.llmProvider = llmProvider.select;
|
|
|
|
return llmSection;
|
|
}
|
|
|
|
/**
|
|
* 创建视觉模型配置部分
|
|
* @returns {HTMLElement} 视觉模型配置部分的DOM元素
|
|
*/
|
|
_createVisionSection() {
|
|
// 视觉模型配置
|
|
const visionSection = createFormGroup('视觉模型配置', []);
|
|
|
|
// 创建选择器和输入框
|
|
const visionProvider = createSelectGroup('', [
|
|
{ value: 'zhipu', text: '智谱' },
|
|
{ value: 'siliconflow', text: '硅基流动' },
|
|
{ value: 'custom', text: '自定义' }
|
|
]);
|
|
const visionModelInput = createInputGroup('', '请输入模型名称');
|
|
this.visionModel = visionModelInput.input; // 保存引用以供外部函数使用
|
|
|
|
// 创建base_url输入框(初始隐藏)
|
|
const visionBaseUrlInput = createInputGroup('Base URL', '请输入API基础URL,例如: https://api.example.com/v1');
|
|
this.visionBaseUrl = visionBaseUrlInput.input; // 保存引用以供外部函数使用
|
|
visionBaseUrlInput.group.style.display = 'none'; // 初始隐藏
|
|
|
|
// 在base_url输入框下添加提示信息
|
|
const visionBaseUrlHint = document.createElement('div');
|
|
visionBaseUrlHint.className = 'base-url-hint';
|
|
visionBaseUrlHint.style.fontSize = '12px';
|
|
visionBaseUrlHint.style.color = '#666';
|
|
visionBaseUrlHint.style.marginTop = '4px';
|
|
visionBaseUrlHint.style.marginBottom = '8px';
|
|
visionBaseUrlHint.textContent = '注意: 请输入基础URL,不要包含/chat/completions路径';
|
|
visionBaseUrlInput.group.appendChild(visionBaseUrlHint);
|
|
|
|
// 创建水平布局组
|
|
const visionProviderGroup = createHorizontalFormGroup([
|
|
{ label: '模型提供商', element: visionProvider.group },
|
|
{ label: '模型名称', element: this.visionModel }
|
|
]);
|
|
|
|
// API Key 保持单独一行
|
|
const visionApiKeyInput = createInputGroup('API Key', '请输入模型 API Key');
|
|
this.visionApiKey = visionApiKeyInput.input; // 保存引用以供外部函数使用
|
|
|
|
// ---视觉模型参数配置---
|
|
// 创建温度和最大token输入框
|
|
const visionTemperatureInput = createInputGroup('', '0.1-2.0,控制输出随机性');
|
|
visionTemperatureInput.input.type = 'number';
|
|
visionTemperatureInput.input.min = '0.1';
|
|
visionTemperatureInput.input.max = '2.0';
|
|
visionTemperatureInput.input.step = '0.1';
|
|
visionTemperatureInput.input.value = '0.7'; // 默认值
|
|
this.visionTemperature = visionTemperatureInput.input;
|
|
|
|
const visionMaxTokensInput = createInputGroup('', '1-32000,控制最大输出长度');
|
|
visionMaxTokensInput.input.type = 'number';
|
|
visionMaxTokensInput.input.min = '1';
|
|
visionMaxTokensInput.input.max = '32000';
|
|
visionMaxTokensInput.input.step = '1';
|
|
visionMaxTokensInput.input.value = '2000'; // 默认值
|
|
this.visionMaxTokens = visionMaxTokensInput.input;
|
|
|
|
const visionTopPInput = createInputGroup('', '0.1-1.0,控制核采样范围');
|
|
visionTopPInput.input.type = 'number';
|
|
visionTopPInput.input.min = '0.1';
|
|
visionTopPInput.input.max = '1.0';
|
|
visionTopPInput.input.step = '0.1';
|
|
visionTopPInput.input.value = '0.9'; // 默认值
|
|
this.visionTopP = visionTopPInput.input;
|
|
|
|
// 创建视觉模型参数水平布局组(三个参数在同一行)
|
|
const visionParamsGroup = createHorizontalFormGroup([
|
|
{ label: '温度 (Temperature)', element: this.visionTemperature },
|
|
{ label: '核采样 (Top-P)', element: this.visionTopP },
|
|
{ label: '最大Token数', element: this.visionMaxTokens }
|
|
]);
|
|
|
|
visionSection.appendChild(visionProviderGroup);
|
|
visionSection.appendChild(visionBaseUrlInput.group); // 添加base_url输入框
|
|
visionSection.appendChild(visionApiKeyInput.group);
|
|
visionSection.appendChild(visionParamsGroup); // 添加视觉模型参数配置(三个参数在同一行)
|
|
|
|
// 监听视觉模型提供商选择变化
|
|
visionProvider.select.addEventListener('change', () => {
|
|
const oldProvider = visionProvider.select.dataset.previousValue;
|
|
const provider = visionProvider.select.value;
|
|
|
|
// 如果存在旧提供商,保存其配置
|
|
if (oldProvider) {
|
|
this._saveVisionProviderConfig(oldProvider);
|
|
}
|
|
|
|
// 根据是否选择自定义类型显示/隐藏base_url输入框
|
|
visionBaseUrlInput.group.style.display = provider === 'custom' ? 'block' : 'none';
|
|
|
|
// 加载选定提供商的配置
|
|
if (this.visionAllProviders && this.visionAllProviders[provider]) {
|
|
const providerConfig = this.visionAllProviders[provider];
|
|
this.visionModel.value = providerConfig.model || '';
|
|
|
|
// 设置API密钥(使用掩码)
|
|
if (providerConfig.api_key || providerConfig.has_key) {
|
|
this._setMaskedValue(this.visionApiKey, providerConfig.api_key || "dummy-key");
|
|
} else {
|
|
this.visionApiKey.value = '';
|
|
this.visionApiKey.dataset.hasKey = 'false';
|
|
}
|
|
|
|
// 加载模型参数
|
|
this.visionTemperature.value = providerConfig.temperature || '0.7';
|
|
this.visionMaxTokens.value = providerConfig.max_tokens || '2000';
|
|
this.visionTopP.value = providerConfig.top_p || '0.9';
|
|
|
|
if (provider === 'custom') {
|
|
this.visionBaseUrl.value = providerConfig.base_url || '';
|
|
}
|
|
} else {
|
|
// 设置默认模型名称
|
|
if (provider === 'zhipu') {
|
|
this.visionModel.value = 'glm-4v-flash';
|
|
} else if (provider === 'siliconflow') {
|
|
this.visionModel.value = 'THUDM/GLM-4.1V-9B-Thinking';
|
|
} else {
|
|
this.visionModel.value = '';
|
|
}
|
|
// 清空API Key和base_url
|
|
this.visionApiKey.value = '';
|
|
this.visionApiKey.dataset.hasKey = 'false';
|
|
// 设置默认模型参数
|
|
this.visionTemperature.value = '0.7';
|
|
this.visionMaxTokens.value = '2000';
|
|
this.visionTopP.value = '0.9';
|
|
if (provider === 'custom') {
|
|
this.visionBaseUrl.value = '';
|
|
}
|
|
}
|
|
|
|
// 记录当前选择的提供商
|
|
visionProvider.select.dataset.previousValue = provider;
|
|
});
|
|
|
|
// 保存选择器引用,便于后续获取值
|
|
this.visionProvider = visionProvider.select;
|
|
|
|
return visionSection;
|
|
}
|
|
|
|
/**
|
|
* 设置掩码值,同时保存原始状态
|
|
* @param {HTMLInputElement} input 输入元素
|
|
* @param {string} value 原始值
|
|
*/
|
|
_setMaskedValue(input, value) {
|
|
const hasValue = value && value.trim() !== '';
|
|
|
|
if (hasValue) {
|
|
// 显示掩码
|
|
input.value = '••••••••••••••••••••••••••••••••••••••••••••••••';
|
|
// 标记为已设置
|
|
input.dataset.hasKey = 'true';
|
|
} else {
|
|
input.value = '';
|
|
input.dataset.hasKey = 'false';
|
|
}
|
|
|
|
// 添加焦点事件处理
|
|
if (!input._maskedHandlersAdded) {
|
|
input.addEventListener('focus', function () {
|
|
if (this.dataset.hasKey === 'true' && this.value === '••••••••••••••••••••••••••••••••••••••••••••••••') {
|
|
this.value = '';
|
|
}
|
|
});
|
|
|
|
input.addEventListener('blur', function () {
|
|
if (this.dataset.hasKey === 'true' && this.value === '') {
|
|
this.value = '••••••••••••••••••••••••••••••••••••••••••••••••';
|
|
}
|
|
});
|
|
|
|
input._maskedHandlersAdded = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 加载API配置
|
|
* @param {HTMLElement} container 容器元素
|
|
*/
|
|
async _loadAPIConfig(container) {
|
|
try {
|
|
const [baiduConfig, llmConfig, visionConfig] = await Promise.all([
|
|
fetch('/prompt_assistant/api/config/baidu_translate').then(r => r.json()),
|
|
fetch('/prompt_assistant/api/config/llm').then(r => r.json()),
|
|
fetch('/prompt_assistant/api/config/vision').then(r => r.json())
|
|
]);
|
|
|
|
// 设置百度翻译配置
|
|
if (baiduConfig.app_id) {
|
|
this.baiduAppId.value = baiduConfig.app_id;
|
|
}
|
|
if (baiduConfig.secret_key) {
|
|
// 使用掩码显示密钥
|
|
this._setMaskedValue(this.baiduSecret, baiduConfig.secret_key);
|
|
}
|
|
|
|
// 存储所有提供商的配置
|
|
this.llmAllProviders = llmConfig.providers || {};
|
|
this.visionAllProviders = visionConfig.providers || {};
|
|
|
|
// 为所有提供商预处理API密钥状态
|
|
const providerList = ["zhipu", "siliconflow", "custom"];
|
|
|
|
// 处理LLM提供商的API密钥
|
|
providerList.forEach(provider => {
|
|
if (this.llmAllProviders[provider] && this.llmAllProviders[provider].api_key) {
|
|
// 保留API密钥值,但不在此处设置掩码(在切换提供商时会设置)
|
|
this.llmAllProviders[provider].has_key = true;
|
|
}
|
|
});
|
|
|
|
// 处理视觉模型提供商的API密钥
|
|
providerList.forEach(provider => {
|
|
if (this.visionAllProviders[provider] && this.visionAllProviders[provider].api_key) {
|
|
// 保留API密钥值,但不在此处设置掩码(在切换提供商时会设置)
|
|
this.visionAllProviders[provider].has_key = true;
|
|
}
|
|
});
|
|
|
|
// 设置 LLM 配置
|
|
if (llmConfig.provider) {
|
|
this.llmProvider.value = llmConfig.provider;
|
|
this.llmProvider.dispatchEvent(new Event('change'));
|
|
}
|
|
if (llmConfig.model) {
|
|
this.llmModel.value = llmConfig.model;
|
|
}
|
|
if (llmConfig.api_key) {
|
|
// 使用掩码显示密钥
|
|
this._setMaskedValue(this.llmApiKey, llmConfig.api_key);
|
|
}
|
|
// 加载模型参数
|
|
if (llmConfig.temperature !== undefined) {
|
|
this.llmTemperature.value = llmConfig.temperature;
|
|
}
|
|
if (llmConfig.max_tokens !== undefined) {
|
|
this.llmMaxTokens.value = llmConfig.max_tokens;
|
|
}
|
|
if (llmConfig.top_p !== undefined) {
|
|
this.llmTopP.value = llmConfig.top_p;
|
|
}
|
|
if (llmConfig.base_url && llmConfig.provider === 'custom') {
|
|
this.llmBaseUrl.value = llmConfig.base_url;
|
|
}
|
|
|
|
// 设置视觉模型配置
|
|
if (visionConfig.provider) {
|
|
this.visionProvider.value = visionConfig.provider;
|
|
this.visionProvider.dispatchEvent(new Event('change'));
|
|
}
|
|
if (visionConfig.model) {
|
|
this.visionModel.value = visionConfig.model;
|
|
}
|
|
if (visionConfig.api_key) {
|
|
// 使用掩码显示密钥
|
|
this._setMaskedValue(this.visionApiKey, visionConfig.api_key);
|
|
}
|
|
// 加载视觉模型参数
|
|
if (visionConfig.temperature !== undefined) {
|
|
this.visionTemperature.value = visionConfig.temperature;
|
|
}
|
|
if (visionConfig.max_tokens !== undefined) {
|
|
this.visionMaxTokens.value = visionConfig.max_tokens;
|
|
}
|
|
if (visionConfig.top_p !== undefined) {
|
|
this.visionTopP.value = visionConfig.top_p;
|
|
}
|
|
if (visionConfig.base_url && visionConfig.provider === 'custom') {
|
|
this.visionBaseUrl.value = visionConfig.base_url;
|
|
}
|
|
|
|
// 将表单控件暴露给保存回调
|
|
container.formControls = {
|
|
baidu: { appId: this.baiduAppId, secret: this.baiduSecret },
|
|
llm: {
|
|
provider: this.llmProvider,
|
|
temperature: this.llmTemperature,
|
|
top_p: this.llmTopP,
|
|
max_tokens: this.llmMaxTokens
|
|
},
|
|
vision: {
|
|
provider: this.visionProvider,
|
|
temperature: this.visionTemperature,
|
|
top_p: this.visionTopP,
|
|
max_tokens: this.visionMaxTokens
|
|
}
|
|
};
|
|
} catch (error) {
|
|
logger.error("加载API配置失败:", error);
|
|
app.extensionManager.toast.add({
|
|
severity: "error",
|
|
summary: "加载失败",
|
|
detail: error.message || "加载API配置过程中发生错误",
|
|
life: 3000
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 保存LLM提供商配置
|
|
* @param {string} provider 提供商名称
|
|
*/
|
|
_saveLLMProviderConfig(provider) {
|
|
if (!this.llmAllProviders) this.llmAllProviders = {};
|
|
|
|
// 获取当前配置
|
|
let baseUrl = '';
|
|
if (provider === 'custom') {
|
|
baseUrl = this.llmBaseUrl ? this.llmBaseUrl.value.trim() : '';
|
|
// 确保base_url不以/结尾
|
|
if (baseUrl && baseUrl.endsWith('/')) {
|
|
baseUrl = baseUrl.slice(0, -1);
|
|
}
|
|
// 确保base_url不以/chat/completions结尾
|
|
if (baseUrl && baseUrl.endsWith('/chat/completions')) {
|
|
baseUrl = baseUrl.slice(0, -16); // 移除'/chat/completions'
|
|
}
|
|
} else {
|
|
baseUrl = provider === 'zhipu' ? 'https://open.bigmodel.cn/api/paas/v4' :
|
|
(provider === 'siliconflow' ? 'https://api.siliconflow.cn/v1' : '');
|
|
}
|
|
|
|
// 检查API密钥是否为掩码
|
|
let apiKey = this.llmApiKey.value.trim();
|
|
const isMasked = apiKey === '••••••••••••••••••••••••••••••••••••••••••••••••';
|
|
|
|
// 创建提供商配置对象
|
|
const providerConfig = {
|
|
model: this.llmModel.value.trim(),
|
|
base_url: baseUrl,
|
|
temperature: parseFloat(this.llmTemperature.value) || 0.7,
|
|
max_tokens: parseInt(this.llmMaxTokens.value) || 2000,
|
|
top_p: parseFloat(this.llmTopP.value) || 0.9
|
|
};
|
|
|
|
// 保留现有配置中的has_key标记
|
|
if (this.llmAllProviders[provider] && this.llmAllProviders[provider].has_key) {
|
|
providerConfig.has_key = true;
|
|
}
|
|
|
|
// 只有当API密钥不是掩码时才更新
|
|
if (!isMasked || this.llmApiKey.dataset.hasKey !== 'true') {
|
|
providerConfig.api_key = apiKey;
|
|
// 如果输入了新的API密钥,设置has_key标记
|
|
if (apiKey) {
|
|
providerConfig.has_key = true;
|
|
} else {
|
|
// 如果清空了API密钥,移除has_key标记
|
|
providerConfig.has_key = false;
|
|
}
|
|
}
|
|
|
|
// 更新配置
|
|
this.llmAllProviders[provider] = providerConfig;
|
|
|
|
logger.debug(`已保存 ${provider} 提供商的LLM配置`);
|
|
}
|
|
|
|
/**
|
|
* 保存视觉模型提供商配置
|
|
* @param {string} provider 提供商名称
|
|
*/
|
|
_saveVisionProviderConfig(provider) {
|
|
if (!this.visionAllProviders) this.visionAllProviders = {};
|
|
|
|
// 获取当前配置
|
|
let baseUrl = '';
|
|
if (provider === 'custom') {
|
|
baseUrl = this.visionBaseUrl ? this.visionBaseUrl.value.trim() : '';
|
|
// 确保base_url不以/结尾
|
|
if (baseUrl && baseUrl.endsWith('/')) {
|
|
baseUrl = baseUrl.slice(0, -1);
|
|
}
|
|
// 确保base_url不以/chat/completions结尾
|
|
if (baseUrl && baseUrl.endsWith('/chat/completions')) {
|
|
baseUrl = baseUrl.slice(0, -16); // 移除'/chat/completions'
|
|
}
|
|
} else {
|
|
baseUrl = provider === 'zhipu' ? 'https://open.bigmodel.cn/api/paas/v4' :
|
|
(provider === 'siliconflow' ? 'https://api.siliconflow.cn/v1' : '');
|
|
}
|
|
|
|
// 检查API密钥是否为掩码
|
|
let apiKey = this.visionApiKey.value.trim();
|
|
const isMasked = apiKey === '••••••••••••••••••••••••••••••••••••••••••••••••';
|
|
|
|
// 创建提供商配置对象
|
|
const providerConfig = {
|
|
model: this.visionModel.value.trim(),
|
|
base_url: baseUrl,
|
|
temperature: parseFloat(this.visionTemperature.value) || 0.7,
|
|
max_tokens: parseInt(this.visionMaxTokens.value) || 2000,
|
|
top_p: parseFloat(this.visionTopP.value) || 0.9
|
|
};
|
|
|
|
// 保留现有配置中的has_key标记
|
|
if (this.visionAllProviders[provider] && this.visionAllProviders[provider].has_key) {
|
|
providerConfig.has_key = true;
|
|
}
|
|
|
|
// 只有当API密钥不是掩码时才更新
|
|
if (!isMasked || this.visionApiKey.dataset.hasKey !== 'true') {
|
|
providerConfig.api_key = apiKey;
|
|
// 如果输入了新的API密钥,设置has_key标记
|
|
if (apiKey) {
|
|
providerConfig.has_key = true;
|
|
} else {
|
|
// 如果清空了API密钥,移除has_key标记
|
|
providerConfig.has_key = false;
|
|
}
|
|
}
|
|
|
|
// 更新配置
|
|
this.visionAllProviders[provider] = providerConfig;
|
|
|
|
logger.debug(`已保存 ${provider} 提供商的视觉模型配置`);
|
|
}
|
|
}
|
|
|
|
// 导出API配置管理器实例
|
|
export const apiConfigManager = new APIConfigManager();
|