支持自定义api接口地址

This commit is contained in:
ChengHao
2025-05-08 18:52:42 +08:00
parent 4ef0b7f82c
commit d75955650d
9 changed files with 591 additions and 35 deletions

106
app.py
View File

@@ -41,6 +41,7 @@ API_KEYS_FILE = os.path.join(CONFIG_DIR, 'api_keys.json')
VERSION_FILE = os.path.join(CONFIG_DIR, 'version.json')
UPDATE_INFO_FILE = os.path.join(CONFIG_DIR, 'update_info.json')
PROMPT_FILE = os.path.join(CONFIG_DIR, 'prompts.json') # 新增提示词配置文件路径
PROXY_API_FILE = os.path.join(CONFIG_DIR, 'proxy_api.json') # 新增中转API配置文件路径
# 跟踪用户生成任务的字典
generation_tasks = {}
@@ -114,13 +115,31 @@ def create_model_instance(model_id, settings, is_reasoning=False):
# 获取maxTokens参数默认为8192
max_tokens = int(settings.get('maxTokens', 8192))
# 检查是否启用中转API
proxy_api_config = load_proxy_api()
base_url = None
if proxy_api_config.get('enabled', False):
# 根据模型类型选择对应的中转API
if "claude" in model_id.lower() or "anthropic" in model_id.lower():
base_url = proxy_api_config.get('apis', {}).get('anthropic', '')
elif any(keyword in model_id.lower() for keyword in ["gpt", "openai"]):
base_url = proxy_api_config.get('apis', {}).get('openai', '')
elif "deepseek" in model_id.lower():
base_url = proxy_api_config.get('apis', {}).get('deepseek', '')
elif "qvq" in model_id.lower() or "alibaba" in model_id.lower() or "qwen" in model_id.lower():
base_url = proxy_api_config.get('apis', {}).get('alibaba', '')
elif "gemini" in model_id.lower() or "google" in model_id.lower():
base_url = proxy_api_config.get('apis', {}).get('google', '')
# 创建模型实例
model_instance = ModelFactory.create_model(
model_name=model_id,
api_key=api_key,
temperature=None if is_reasoning else float(settings.get('temperature', 0.7)),
system_prompt=settings.get('systemPrompt'),
language=settings.get('language', '中文')
language=settings.get('language', '中文'),
base_url=base_url # 添加中转API URL
)
# 设置最大输出Token但不为阿里巴巴模型设置它们有自己内部的处理逻辑
@@ -159,24 +178,6 @@ def stream_model_response(response_generator, sid, model_name=None):
for response in response_generator:
# 处理Mathpix响应
if isinstance(response.get('content', ''), str) and 'mathpix' in response.get('model', ''):
socketio.emit('text_extracted', {
'content': response['content']
}, room=sid)
continue
# 获取状态和内容
status = response.get('status', '')
content = response.get('content', '')
# 根据不同的状态进行处理
if status == 'thinking':
# 仅对推理模型处理思考过程
if is_reasoning:
# 直接使用模型提供的完整思考内容
thinking_buffer = content
# 控制发送频率至少间隔0.3秒
current_time = time.time()
if current_time - last_emit_time >= 0.3:
socketio.emit('ai_response', {
'status': 'thinking',
@@ -774,9 +775,47 @@ def load_api_keys():
print(f"加载API密钥配置失败: {e}")
return {}
# 加载中转API配置
def load_proxy_api():
"""从配置文件加载中转API配置"""
try:
if os.path.exists(PROXY_API_FILE):
with open(PROXY_API_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
else:
# 如果文件不存在,创建默认配置
default_proxy_apis = {
"enabled": False,
"apis": {
"anthropic": "",
"openai": "",
"deepseek": "",
"alibaba": "",
"google": ""
}
}
save_proxy_api(default_proxy_apis)
return default_proxy_apis
except Exception as e:
print(f"加载中转API配置失败: {e}")
return {"enabled": False, "apis": {}}
# 保存中转API配置
def save_proxy_api(proxy_api_config):
"""保存中转API配置到文件"""
try:
# 确保配置目录存在
os.makedirs(os.path.dirname(PROXY_API_FILE), exist_ok=True)
with open(PROXY_API_FILE, 'w', encoding='utf-8') as f:
json.dump(proxy_api_config, f, ensure_ascii=False, indent=2)
return True
except Exception as e:
print(f"保存中转API配置失败: {e}")
return False
# 保存API密钥配置
def save_api_keys(api_keys):
"""保存API密钥到配置文件"""
try:
# 确保配置目录存在
os.makedirs(os.path.dirname(API_KEYS_FILE), exist_ok=True)
@@ -879,6 +918,33 @@ def remove_prompt(prompt_id):
print(f"删除提示词时出错: {e}")
return jsonify({"error": str(e)}), 500
@app.route('/api/proxy-api', methods=['GET'])
def get_proxy_api():
"""API端点获取中转API配置"""
try:
proxy_api_config = load_proxy_api()
return jsonify(proxy_api_config)
except Exception as e:
print(f"获取中转API配置时出错: {e}")
return jsonify({"error": str(e)}), 500
@app.route('/api/proxy-api', methods=['POST'])
def update_proxy_api():
"""API端点更新中转API配置"""
try:
new_config = request.json
if not isinstance(new_config, dict):
return jsonify({"success": False, "message": "无效的中转API配置格式"}), 400
# 保存回文件
if save_proxy_api(new_config):
return jsonify({"success": True, "message": "中转API配置已保存"})
else:
return jsonify({"success": False, "message": "保存中转API配置失败"}), 500
except Exception as e:
return jsonify({"success": False, "message": f"更新中转API配置错误: {str(e)}"}), 500
if __name__ == '__main__':
local_ip = get_local_ip()
print(f"Local IP Address: {local_ip}")

View File

@@ -0,0 +1,7 @@
{
"AnthropicApiBaseUrl": "",
"OpenaiApiBaseUrl": "",
"DeepseekApiBaseUrl": "",
"AlibabaApiBaseUrl": "",
"GoogleApiBaseUrl": ""
}

View File

@@ -4,6 +4,11 @@ from typing import Generator
from .base import BaseModel
class AnthropicModel(BaseModel):
def __init__(self, api_key, temperature=0.7, system_prompt=None, language=None, api_base_url=None):
super().__init__(api_key, temperature, system_prompt, language)
# 设置API基础URL默认为Anthropic官方API
self.api_base_url = api_base_url or "https://api.anthropic.com/v1"
def get_default_system_prompt(self) -> str:
return """You are an expert at analyzing questions and providing detailed solutions. When presented with an image of a question:
1. First read and understand the question carefully
@@ -82,8 +87,11 @@ class AnthropicModel(BaseModel):
print(f"Debug - 推理配置: max_tokens={max_tokens}, thinking={payload.get('thinking', payload.get('speed_mode', 'default'))}")
# 使用配置的API基础URL
api_endpoint = f"{self.api_base_url}/messages"
response = requests.post(
'https://api.anthropic.com/v1/messages',
api_endpoint,
headers=headers,
json=payload,
stream=True,
@@ -257,8 +265,11 @@ class AnthropicModel(BaseModel):
print(f"Debug - 图像分析推理配置: max_tokens={max_tokens}, thinking={payload.get('thinking', payload.get('speed_mode', 'default'))}")
# 使用配置的API基础URL
api_endpoint = f"{self.api_base_url}/messages"
response = requests.post(
'https://api.anthropic.com/v1/messages',
api_endpoint,
headers=headers,
json=payload,
stream=True,

View File

@@ -80,7 +80,8 @@ class ModelFactory:
print(f"无法加载基础Mathpix工具: {str(e)}")
@classmethod
def create_model(cls, model_name: str, api_key: str, temperature: float = 0.7, system_prompt: str = None, language: str = None) -> BaseModel:
def create_model(cls, model_name: str, api_key: str, temperature: float = 0.7,
system_prompt: str = None, language: str = None, api_base_url: str = None) -> BaseModel:
"""
Create a model instance based on the model name.
@@ -90,6 +91,7 @@ class ModelFactory:
temperature: The temperature to use for generation
system_prompt: The system prompt to use
language: The preferred language for responses
api_base_url: The base URL for API requests
Returns:
A model instance
@@ -107,7 +109,8 @@ class ModelFactory:
temperature=temperature,
system_prompt=system_prompt,
language=language,
model_name=model_name
model_name=model_name,
api_base_url=api_base_url
)
# 对于阿里巴巴模型,也需要传递正确的模型名称
elif 'qwen' in model_name.lower() or 'qvq' in model_name.lower() or 'alibaba' in model_name.lower():
@@ -116,7 +119,8 @@ class ModelFactory:
temperature=temperature,
system_prompt=system_prompt,
language=language,
model_name=model_name
model_name=model_name,
api_base_url=api_base_url
)
# 对于Mathpix模型不传递language参数
elif model_name == 'mathpix':
@@ -131,7 +135,8 @@ class ModelFactory:
api_key=api_key,
temperature=temperature,
system_prompt=system_prompt,
language=language
language=language,
api_base_url=api_base_url
)
@classmethod

View File

@@ -11,7 +11,7 @@ class GoogleModel(BaseModel):
支持Gemini 2.5 Pro等模型可处理文本和图像输入
"""
def __init__(self, api_key: str, temperature: float = 0.7, system_prompt: str = None, language: str = None, model_name: str = None):
def __init__(self, api_key: str, temperature: float = 0.7, system_prompt: str = None, language: str = None, model_name: str = None, api_base_url: str = None):
"""
初始化Google模型
@@ -21,13 +21,20 @@ class GoogleModel(BaseModel):
system_prompt: 系统提示词
language: 首选语言
model_name: 指定具体模型名称,如不指定则使用默认值
api_base_url: API基础URL用于设置自定义API端点
"""
super().__init__(api_key, temperature, system_prompt, language)
self.model_name = model_name or self.get_model_identifier()
self.max_tokens = 8192 # 默认最大输出token数
self.api_base_url = api_base_url
# 配置Google API
genai.configure(api_key=api_key)
if api_base_url:
# 如果提供了自定义API基础URL设置genai的api_url
genai.configure(api_key=api_key, transport="rest", client_options={"api_endpoint": api_base_url})
else:
# 使用默认API端点
genai.configure(api_key=api_key)
def get_default_system_prompt(self) -> str:
return """You are an expert at analyzing questions and providing detailed solutions. When presented with an image of a question:

View File

@@ -4,6 +4,11 @@ from openai import OpenAI
from .base import BaseModel
class OpenAIModel(BaseModel):
def __init__(self, api_key, temperature=0.7, system_prompt=None, language=None, api_base_url=None):
super().__init__(api_key, temperature, system_prompt, language)
# 设置API基础URL默认为OpenAI官方API
self.api_base_url = api_base_url
def get_default_system_prompt(self) -> str:
return """You are an expert at analyzing questions and providing detailed solutions. When presented with an image of a question:
1. First read and understand the question carefully
@@ -35,8 +40,11 @@ class OpenAIModel(BaseModel):
if 'https' in proxies:
os.environ['https_proxy'] = proxies['https']
# Initialize OpenAI client
client = OpenAI(api_key=self.api_key)
# Initialize OpenAI client with base_url if provided
if self.api_base_url:
client = OpenAI(api_key=self.api_key, base_url=self.api_base_url)
else:
client = OpenAI(api_key=self.api_key)
# Prepare messages
messages = [
@@ -123,8 +131,11 @@ class OpenAIModel(BaseModel):
if 'https' in proxies:
os.environ['https_proxy'] = proxies['https']
# Initialize OpenAI client
client = OpenAI(api_key=self.api_key)
# Initialize OpenAI client with base_url if provided
if self.api_base_url:
client = OpenAI(api_key=self.api_key, base_url=self.api_base_url)
else:
client = OpenAI(api_key=self.api_key)
# 使用系统提供的系统提示词,不再自动添加语言指令
system_prompt = self.system_prompt

View File

@@ -374,6 +374,26 @@ class SettingsManager {
// 模型选择器对象
this.modelSelector = null;
// 存储API密钥的对象
this.apiKeyValues = {
'AnthropicApiKey': '',
'OpenaiApiKey': '',
'DeepseekApiKey': '',
'AlibabaApiKey': '',
'GoogleApiKey': '',
'MathpixAppId': '',
'MathpixAppKey': ''
};
// 存储API基础URL的对象
this.apiBaseUrlValues = {
'AnthropicApiBaseUrl': '',
'OpenaiApiBaseUrl': '',
'DeepseekApiBaseUrl': '',
'AlibabaApiBaseUrl': '',
'GoogleApiBaseUrl': ''
};
// 加载模型配置
this.isInitialized = false;
this.initialize();
@@ -391,6 +411,12 @@ class SettingsManager {
this.setupEventListeners();
this.updateUIBasedOnModelType();
// 刷新API密钥状态
await this.refreshApiKeyStatus();
// 刷新API基础URL状态
await this.refreshApiBaseUrlStatus();
// 初始化可折叠内容逻辑
this.initCollapsibleContent();
@@ -824,7 +850,8 @@ class SettingsManager {
isReasoning: modelInfo.isReasoning || false,
provider: modelInfo.provider || 'unknown'
},
reasoningConfig: reasoningConfig
reasoningConfig: reasoningConfig,
apiBaseUrls: this.apiBaseUrlValues // 添加API基础URL值
};
}
@@ -1125,6 +1152,9 @@ class SettingsManager {
if (this.modelSelectorDisplay && this.modelDropdown) {
this.initCustomSelectorEvents();
}
// 初始化API基础URL编辑功能
this.initApiBaseUrlEditFunctions();
}
// 更新思考预算显示
@@ -1196,8 +1226,38 @@ class SettingsManager {
* 初始化可折叠内容的交互逻辑
*/
initCollapsibleContent() {
// 在新的实现中我们不再需要折叠API密钥区域因为所有功能都在同一区域完成
console.log('初始化API密钥编辑功能完成');
const collapsibleHeaders = document.querySelectorAll('.collapsible-header');
collapsibleHeaders.forEach(header => {
header.addEventListener('click', () => {
const content = header.nextElementSibling;
if (content && content.classList.contains('collapsible-content')) {
// 切换展开/折叠状态
content.classList.toggle('expanded');
// 切换箭头方向
const arrow = header.querySelector('i.fa-chevron-down, i.fa-chevron-up');
if (arrow) {
arrow.classList.toggle('fa-chevron-down');
arrow.classList.toggle('fa-chevron-up');
}
}
});
});
// 默认展开API基础URL设置区域
const apiBaseUrlHeader = document.querySelector('.api-url-settings .collapsible-header');
if (apiBaseUrlHeader) {
const content = apiBaseUrlHeader.nextElementSibling;
if (content) {
content.classList.add('expanded');
const arrow = apiBaseUrlHeader.querySelector('i.fa-chevron-down');
if (arrow) {
arrow.classList.remove('fa-chevron-down');
arrow.classList.add('fa-chevron-up');
}
}
}
}
/**
@@ -2068,6 +2128,18 @@ class SettingsManager {
this.proxyPortInput = document.getElementById('proxyPort');
this.proxySettings = document.getElementById('proxySettings');
// API基础URL相关元素
this.apiBaseUrlsList = document.getElementById('apiBaseUrlsList');
// 获取所有API基础URL状态元素
this.apiBaseUrlStatusElements = {
'AnthropicApiBaseUrl': document.getElementById('AnthropicApiBaseUrlStatus'),
'OpenaiApiBaseUrl': document.getElementById('OpenaiApiBaseUrlStatus'),
'DeepseekApiBaseUrl': document.getElementById('DeepseekApiBaseUrlStatus'),
'AlibabaApiBaseUrl': document.getElementById('AlibabaApiBaseUrlStatus'),
'GoogleApiBaseUrl': document.getElementById('GoogleApiBaseUrlStatus')
};
// 提示词管理相关元素
this.promptSelect = document.getElementById('promptSelect');
this.savePromptBtn = document.getElementById('savePromptBtn');
@@ -2219,6 +2291,235 @@ class SettingsManager {
this.promptDialogMask.classList.remove('hidden');
}
}
/**
* 刷新API基础URL状态
*/
async refreshApiBaseUrlStatus() {
try {
// 先将所有状态显示为"检查中"
Object.keys(this.apiBaseUrlValues).forEach(urlId => {
const statusElement = document.getElementById(`${urlId}Status`);
if (statusElement) {
statusElement.className = 'key-status checking';
statusElement.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 检查中...';
}
});
// 发送请求获取API基础URL
const response = await fetch('/api/base_urls', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
});
if (response.ok) {
const apiBaseUrls = await response.json();
this.updateApiBaseUrlStatus(apiBaseUrls);
console.log('API基础URL状态已刷新');
} else {
console.error('刷新API基础URL状态失败');
}
} catch (error) {
console.error('刷新API基础URL状态出错:', error);
}
}
/**
* 更新API基础URL状态显示
* @param {Object} apiBaseUrls 基础URL对象
*/
updateApiBaseUrlStatus(apiBaseUrls) {
if (!this.apiBaseUrlsList) return;
// 保存API基础URL值到内存中
for (const [key, value] of Object.entries(apiBaseUrls)) {
this.apiBaseUrlValues[key] = value;
}
// 找到所有基础URL状态元素
Object.keys(apiBaseUrls).forEach(urlId => {
const statusElement = document.getElementById(`${urlId}Status`);
if (!statusElement) return;
const value = apiBaseUrls[urlId];
if (value && value.trim() !== '') {
// 显示基础URL状态 - 已设置
statusElement.className = 'key-status set';
statusElement.innerHTML = `<i class="fas fa-check-circle"></i> 已设置`;
} else {
// 显示基础URL状态 - 未设置
statusElement.className = 'key-status not-set';
statusElement.innerHTML = `<i class="fas fa-times-circle"></i> 未设置`;
}
});
}
/**
* 保存单个API基础URL
* @param {string} urlType URL类型
* @param {string} value URL值
* @param {HTMLElement} urlStatus URL状态容器
*/
async saveApiBaseUrl(urlType, value, urlStatus) {
try {
// 显示保存中状态
const saveToast = this.createToast('正在保存API基础URL...', 'info', true);
// 创建要保存的数据对象
const apiBaseUrlsData = {};
apiBaseUrlsData[urlType] = value;
// 发送到服务器
const response = await fetch('/api/base_urls', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(apiBaseUrlsData)
});
// 移除保存中提示
if (saveToast) {
saveToast.remove();
}
if (response.ok) {
const result = await response.json();
if (result.success) {
// 更新基础URL状态显示
const statusElem = document.getElementById(`${urlType}Status`);
if (statusElem) {
if (value && value.trim() !== '') {
statusElem.className = 'key-status set';
statusElem.innerHTML = `<i class="fas fa-check-circle"></i> 已设置`;
} else {
statusElem.className = 'key-status not-set';
statusElem.innerHTML = `<i class="fas fa-times-circle"></i> 未设置`;
}
}
// 保存到内存
this.apiBaseUrlValues[urlType] = value;
// 显示成功提示
this.createToast('API基础URL已保存', 'success');
} else {
this.createToast(`保存失败: ${result.message || '未知错误'}`, 'error');
}
} else {
this.createToast('保存API基础URL失败', 'error');
}
} catch (error) {
console.error('保存API基础URL错误:', error);
this.createToast(`保存失败: ${error.message || '未知错误'}`, 'error');
}
}
/**
* 初始化API基础URL编辑相关功能
*/
initApiBaseUrlEditFunctions() {
// 1. 编辑按钮点击事件
document.querySelectorAll('.edit-api-base-url').forEach(button => {
button.addEventListener('click', (e) => {
// 阻止事件冒泡
e.stopPropagation();
const urlType = e.currentTarget.getAttribute('data-key-type');
const urlStatus = e.currentTarget.closest('.key-status-wrapper');
if (urlStatus) {
// 隐藏显示区域
const displayArea = urlStatus.querySelector('.key-display');
if (displayArea) displayArea.classList.add('hidden');
// 显示编辑区域
const editArea = urlStatus.querySelector('.key-edit');
if (editArea) {
editArea.classList.remove('hidden');
// 获取当前URL值并填入输入框
const urlInput = editArea.querySelector('.key-input');
if (urlInput) {
// 从状态文本中获取当前值(如果不是"未设置")
const statusElement = urlStatus.querySelector('.key-status');
if (statusElement && statusElement.textContent !== '未设置') {
urlInput.value = this.apiBaseUrlValues[urlType] || '';
} else {
urlInput.value = '';
}
// 聚焦输入框
setTimeout(() => {
urlInput.focus();
}, 100);
}
}
}
});
});
// 2. 保存按钮点击事件
document.querySelectorAll('.save-api-base-url').forEach(button => {
button.addEventListener('click', (e) => {
// 阻止事件冒泡
e.stopPropagation();
const urlType = e.currentTarget.getAttribute('data-key-type');
const urlStatus = e.currentTarget.closest('.key-status-wrapper');
if (urlStatus) {
// 获取输入的新URL值
const urlInput = urlStatus.querySelector('.key-input');
if (urlInput) {
const newValue = urlInput.value.trim();
// 保存到服务器
this.saveApiBaseUrl(urlType, newValue, urlStatus);
// 隐藏编辑区域
const editArea = urlStatus.querySelector('.key-edit');
if (editArea) editArea.classList.add('hidden');
// 显示状态区域
const displayArea = urlStatus.querySelector('.key-display');
if (displayArea) displayArea.classList.remove('hidden');
}
}
});
});
// 3. 输入框按下Enter保存
document.querySelectorAll('#apiBaseUrlsList .key-input').forEach(input => {
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
// 阻止事件冒泡
e.stopPropagation();
const saveButton = e.currentTarget.closest('.key-edit').querySelector('.save-api-base-url');
if (saveButton) {
saveButton.click();
}
} else if (e.key === 'Escape') {
// 阻止事件冒泡
e.stopPropagation();
// 取消编辑
const urlStatus = e.currentTarget.closest('.key-status-wrapper');
if (urlStatus) {
const editArea = urlStatus.querySelector('.key-edit');
if (editArea) editArea.classList.add('hidden');
const displayArea = urlStatus.querySelector('.key-display');
if (displayArea) displayArea.classList.remove('hidden');
}
}
});
});
}
}
// Export for use in other modules

View File

@@ -5323,3 +5323,41 @@ textarea,
display: none;
}
}
/* API基础URL设置区域 */
.api-url-settings {
margin-bottom: 20px;
}
.api-url-settings .collapsible-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
background-color: var(--bg-secondary);
border-radius: 4px;
cursor: pointer;
margin-bottom: 10px;
font-weight: 500;
transition: background-color 0.2s;
}
.api-url-settings .collapsible-header:hover {
background-color: var(--bg-hover);
}
.api-url-settings .collapsible-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
.api-url-settings .collapsible-content.expanded {
max-height: 1000px;
}
.api-url-settings small {
color: var(--text-muted);
font-weight: normal;
margin-left: 5px;
}

View File

@@ -472,6 +472,116 @@
</div>
</div>
<!-- 添加API基础URL设置区域 -->
<div class="settings-section api-url-settings">
<h3><i class="fas fa-link"></i> API基础URL设置</h3>
<div class="setting-group">
<div class="collapsible-header">
<span><i class="fas fa-info-circle"></i> 中转API配置 <small>(可选)</small></span>
<i class="fas fa-chevron-down"></i>
</div>
<div class="collapsible-content">
<div class="api-keys-list" id="apiBaseUrlsList">
<div class="api-key-status">
<span class="key-name">Anthropic API URL:</span>
<div class="key-status-wrapper">
<!-- 显示状态 -->
<div class="key-display">
<span id="AnthropicApiBaseUrlStatus" class="key-status" data-key="AnthropicApiBaseUrl">未设置</span>
<button class="btn-icon edit-api-base-url" data-key-type="AnthropicApiBaseUrl" title="编辑此URL">
<i class="fas fa-edit"></i>
</button>
</div>
<!-- 编辑状态 -->
<div class="key-edit hidden">
<input type="text" class="key-input" data-key-type="AnthropicApiBaseUrl" placeholder="https://api.anthropic.com/v1">
<button class="btn-icon save-api-base-url" data-key-type="AnthropicApiBaseUrl" title="保存URL">
<i class="fas fa-save"></i>
</button>
</div>
</div>
</div>
<div class="api-key-status">
<span class="key-name">OpenAI API URL:</span>
<div class="key-status-wrapper">
<!-- 显示状态 -->
<div class="key-display">
<span id="OpenaiApiBaseUrlStatus" class="key-status" data-key="OpenaiApiBaseUrl">未设置</span>
<button class="btn-icon edit-api-base-url" data-key-type="OpenaiApiBaseUrl" title="编辑此URL">
<i class="fas fa-edit"></i>
</button>
</div>
<!-- 编辑状态 -->
<div class="key-edit hidden">
<input type="text" class="key-input" data-key-type="OpenaiApiBaseUrl" placeholder="https://api.openai.com/v1">
<button class="btn-icon save-api-base-url" data-key-type="OpenaiApiBaseUrl" title="保存URL">
<i class="fas fa-save"></i>
</button>
</div>
</div>
</div>
<div class="api-key-status">
<span class="key-name">DeepSeek API URL:</span>
<div class="key-status-wrapper">
<!-- 显示状态 -->
<div class="key-display">
<span id="DeepseekApiBaseUrlStatus" class="key-status" data-key="DeepseekApiBaseUrl">未设置</span>
<button class="btn-icon edit-api-base-url" data-key-type="DeepseekApiBaseUrl" title="编辑此URL">
<i class="fas fa-edit"></i>
</button>
</div>
<!-- 编辑状态 -->
<div class="key-edit hidden">
<input type="text" class="key-input" data-key-type="DeepseekApiBaseUrl" placeholder="https://api.deepseek.com/v1">
<button class="btn-icon save-api-base-url" data-key-type="DeepseekApiBaseUrl" title="保存URL">
<i class="fas fa-save"></i>
</button>
</div>
</div>
</div>
<div class="api-key-status">
<span class="key-name">Alibaba API URL:</span>
<div class="key-status-wrapper">
<!-- 显示状态 -->
<div class="key-display">
<span id="AlibabaApiBaseUrlStatus" class="key-status" data-key="AlibabaApiBaseUrl">未设置</span>
<button class="btn-icon edit-api-base-url" data-key-type="AlibabaApiBaseUrl" title="编辑此URL">
<i class="fas fa-edit"></i>
</button>
</div>
<!-- 编辑状态 -->
<div class="key-edit hidden">
<input type="text" class="key-input" data-key-type="AlibabaApiBaseUrl" placeholder="https://dashscope.aliyuncs.com/api/v1">
<button class="btn-icon save-api-base-url" data-key-type="AlibabaApiBaseUrl" title="保存URL">
<i class="fas fa-save"></i>
</button>
</div>
</div>
</div>
<div class="api-key-status">
<span class="key-name">Google API URL:</span>
<div class="key-status-wrapper">
<!-- 显示状态 -->
<div class="key-display">
<span id="GoogleApiBaseUrlStatus" class="key-status" data-key="GoogleApiBaseUrl">未设置</span>
<button class="btn-icon edit-api-base-url" data-key-type="GoogleApiBaseUrl" title="编辑此URL">
<i class="fas fa-edit"></i>
</button>
</div>
<!-- 编辑状态 -->
<div class="key-edit hidden">
<input type="text" class="key-input" data-key-type="GoogleApiBaseUrl" placeholder="https://generativelanguage.googleapis.com/v1beta">
<button class="btn-icon save-api-base-url" data-key-type="GoogleApiBaseUrl" title="保存URL">
<i class="fas fa-save"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 3. 不常用的其他设置放在后面 -->
<div class="settings-section proxy-settings-section">
<h3><i class="fas fa-cog"></i> 其他设置</h3>