docs(terminology): 统一术语标准 - 将"供应商"规范为"提供商"

将项目中所有文档、注释和用户界面中的"供应商"术语统一修改为"提供商",提升术语一致性。同时:
- 优化提供商健康检查频率从30分钟调整为10分钟,提高监控及时性
- 新增路由URL动态更新功能,支持不同部署环境的路径适配
- 更新相关样式类名和注释,确保代码与文档术语保持一致
This commit is contained in:
hex2077 2025-11-14 00:10:27 +08:00
parent ee050c77f2
commit 0149bf6558
12 changed files with 137 additions and 106 deletions

View file

@ -202,7 +202,7 @@ install-and-run.bat
**⚙️ 配置管理**实时参数修改支持所有提供商Gemini、OpenAI、Claude、Kiro、Qwen包含高级设置和文件上传
**🔗 供商池**:监控活动连接、提供商健康统计、启用/禁用管理
**🔗 供商池**:监控活动连接、提供商健康统计、启用/禁用管理
**📁 配置文件**OAuth 凭据集中管理,支持搜索过滤和文件操作
@ -261,17 +261,17 @@ install-and-run.bat
---
### 🔄 模型供商切换
### 🔄 模型供商切换
本项目提供两种灵活的模型切换方式,满足不同使用场景的需求。
通过在 API 请求路径中指定供商标识,实现即时切换:
通过在 API 请求路径中指定供商标识,实现即时切换:
| 路由路径 | 说明 | 适用场景 |
|---------|------|---------|
| `/claude-custom` | 使用配置文件中的 Claude API | 官方 Claude API 调用 |
| `/claude-kiro-oauth` | 通过 Kiro OAuth 访问 Claude | 免费使用 Claude Sonnet 4.5 |
| `/openai-custom` | 使用 OpenAI 供商处理请求 | 标准 OpenAI API 调用 |
| `/openai-custom` | 使用 OpenAI 供商处理请求 | 标准 OpenAI API 调用 |
| `/gemini-cli-oauth` | 通过 Gemini CLI OAuth 访问 | 突破 Gemini 免费限制 |
| `/openai-qwen-oauth` | 通过 Qwen OAuth 访问 | 使用 Qwen Code Plus |
| `/openaiResponses-custom` | OpenAI Responses API | 结构化对话场景 |

View file

@ -2,7 +2,7 @@
## 概述
AIClient2API 现在包含一个功能完整的可视化 Web UI 管理控制台,允许您通过浏览器轻松管理配置、监控供商池状态、查看实时日志、配置AI模型提供商等。
AIClient2API 现在包含一个功能完整的可视化 Web UI 管理控制台,允许您通过浏览器轻松管理配置、监控供商池状态、查看实时日志、配置AI模型提供商等。
## 功能特性
@ -14,7 +14,7 @@ AIClient2API 现在包含一个功能完整的可视化 Web UI 管理控制台
### 📊 实时监控
- **仪表盘**显示运行时间、系统信息、Node.js版本、服务器时间、内存使用
- **商池管理**:查看各提供商账户状态、使用统计、错误率
- **供商池管理**:查看各提供商账户状态、使用统计、错误率
- **活动统计**:活动连接、活跃提供商、健康提供商数量
### ⚙️ 配置管理
@ -32,7 +32,7 @@ AIClient2API 现在包含一个功能完整的可视化 Web UI 管理控制台
- 提示日志配置
- 请求重试机制(最大重试次数、基础延迟)
- OAuth令牌自动刷新设置
- 供商池配置文件路径
- 供商池配置文件路径
### 🔧 上传配置管理
- 搜索配置文件功能
@ -87,7 +87,7 @@ node src/api-server.js --port 3000 --api-key 123456
1. **仪表盘** - 系统概览、统计信息和路径路由示例
2. **配置管理** - 修改服务器配置和提供商设置
3. **商池管理** - 管理多个API提供商账户
3. **供商池管理** - 管理多个API提供商账户
4. **上传配置管理** - 管理配置文件和搜索过滤
5. **实时日志** - 查看服务器运行日志
@ -103,7 +103,7 @@ node src/api-server.js --port 3000 --api-key 123456
### 系统信息
- `GET /api/system` - 获取系统信息
- `GET /api/providers` - 获取供商池信息
- `GET /api/providers` - 获取供商池信息
### 实时数据
- `GET /api/events` - Server-Sent Events 流,用于实时更新
@ -203,7 +203,7 @@ A: 在配置管理页面选择对应的OAuth提供商填写项目ID和OAuth
A: 上传配置文件可以将本地配置文件上传到服务器,方便管理和在不同环境间同步配置。
### Q: 如何查看更详细的提供商信息?
A: 在"供商池"页面可以看到每个提供商的使用次数、错误次数、最后使用时间等详细信息。
A: 在"供商池"页面可以看到每个提供商的使用次数、错误次数、最后使用时间等详细信息。
### Q: 配置修改后需要重启服务器吗?
A: 大部分配置如系统提示、API密钥会立即生效但网络端口等更改需要重启服务器。

View file

@ -12,7 +12,7 @@ export class ProviderPoolManager {
this.providerStatus = {}; // Tracks health and usage for each provider instance
this.roundRobinIndex = {}; // Tracks the current index for round-robin selection for each provider type
this.maxErrorCount = options.maxErrorCount || 3; // Default to 1 errors before marking unhealthy
this.healthCheckInterval = options.healthCheckInterval || 30 * 60 * 1000; // Default to 30 minutes
this.healthCheckInterval = options.healthCheckInterval || 10 * 60 * 1000; // Default to 10 minutes
// 优化1: 添加防抖机制,避免频繁的文件 I/O 操作
this.saveDebounceTime = options.saveDebounceTime || 1000; // 默认1秒防抖
@ -140,9 +140,9 @@ export class ProviderPoolManager {
}
/**
* 禁用指定
* @param {string} providerType - 商类型
* @param {object} providerConfig - 商配置
* 禁用指定供商
* @param {string} providerType - 供商类型
* @param {object} providerConfig - 供商配置
*/
disableProvider(providerType, providerConfig) {
const pool = this.providerStatus[providerType];
@ -159,9 +159,9 @@ export class ProviderPoolManager {
}
/**
* 启用指定
* @param {string} providerType - 商类型
* @param {object} providerConfig - 商配置
* 启用指定供商
* @param {string} providerType - 供商类型
* @param {object} providerConfig - 供商配置
*/
enableProvider(providerType, providerConfig) {
const pool = this.providerStatus[providerType];

View file

@ -628,7 +628,7 @@ export async function handleUIApiRequests(method, pathParam, req, res, currentCo
timestamp: new Date().toISOString()
});
// 广播商更新事件
// 广播供商更新事件
broadcastEvent('provider_update', {
action: 'add',
providerType,
@ -1216,13 +1216,13 @@ async function scanConfigFiles(currentConfig, providerPoolManager) {
}
}
// 使用最新的商池数据
// 使用最新的供商池数据
let providerPools = currentConfig.providerPools;
if (providerPoolManager && providerPoolManager.providerPools) {
providerPools = providerPoolManager.providerPools;
}
// 检查商池文件中的所有OAuth凭据路径 - 标准化路径格式
// 检查供商池文件中的所有OAuth凭据路径 - 标准化路径格式
if (providerPools) {
for (const [providerType, providers] of Object.entries(providerPools)) {
for (const provider of providers) {
@ -1409,7 +1409,7 @@ function getFileUsageInfo(relativePath, fileName, usedPaths, currentConfig) {
});
}
// 检查商池中的使用情况
// 检查供商池中的使用情况
if (currentConfig.providerPools) {
// 使用 flatMap 将双重循环优化为单层循环 O(n)
const allProviders = Object.entries(currentConfig.providerPools).flatMap(
@ -1424,7 +1424,7 @@ function getFileUsageInfo(relativePath, fileName, usedPaths, currentConfig) {
(pathsEqual(relativePath, provider.GEMINI_OAUTH_CREDS_FILE_PATH) ||
pathsEqual(relativePath, provider.GEMINI_OAUTH_CREDS_FILE_PATH.replace(/\\/g, '/')))) {
providerUsages.push({
type: '商池',
type: '供商池',
location: `Gemini OAuth凭据 (节点${index + 1})`,
providerType: providerType,
providerIndex: index,
@ -1436,7 +1436,7 @@ function getFileUsageInfo(relativePath, fileName, usedPaths, currentConfig) {
(pathsEqual(relativePath, provider.KIRO_OAUTH_CREDS_FILE_PATH) ||
pathsEqual(relativePath, provider.KIRO_OAUTH_CREDS_FILE_PATH.replace(/\\/g, '/')))) {
providerUsages.push({
type: '商池',
type: '供商池',
location: `Kiro OAuth凭据 (节点${index + 1})`,
providerType: providerType,
providerIndex: index,
@ -1448,7 +1448,7 @@ function getFileUsageInfo(relativePath, fileName, usedPaths, currentConfig) {
(pathsEqual(relativePath, provider.QWEN_OAUTH_CREDS_FILE_PATH) ||
pathsEqual(relativePath, provider.QWEN_OAUTH_CREDS_FILE_PATH.replace(/\\/g, '/')))) {
providerUsages.push({
type: '商池',
type: '供商池',
location: `Qwen OAuth凭据 (节点${index + 1})`,
providerType: providerType,
providerIndex: index,

View file

@ -86,7 +86,7 @@ async function loadConfiguration() {
if (cronRefreshTokenEl) cronRefreshTokenEl.checked = data.CRON_REFRESH_TOKEN || false;
if (providerPoolsFilePathEl) providerPoolsFilePathEl.value = data.PROVIDER_POOLS_FILE_PATH || '';
// 触发商配置显示
// 触发供商配置显示
handleProviderChange();
// 根据Gemini凭据类型设置显示
@ -105,7 +105,7 @@ async function loadConfiguration() {
handleKiroCredsTypeChange({ target: kiroRadio });
}
// 检查并设置商池菜单显示状态
// 检查并设置供商池菜单显示状态
const providerPoolsFilePath = data.PROVIDER_POOLS_FILE_PATH;
const providersMenuItem = document.querySelector('.nav-item[data-section="providers"]');
if (providerPoolsFilePath && providerPoolsFilePath.trim() !== '') {
@ -131,7 +131,7 @@ async function saveConfiguration() {
systemPrompt: document.getElementById('systemPrompt')?.value || '',
};
// 根据不同商保存不同的配置
// 根据不同供商保存不同的配置
const provider = document.getElementById('modelProvider')?.value;
switch (provider) {
@ -194,12 +194,12 @@ async function saveConfiguration() {
showToast('配置已保存', 'success');
// 检查当前是否在商池管理页面,如果是则刷新数据
// 检查当前是否在供商池管理页面,如果是则刷新数据
const providersSection = document.getElementById('providers');
if (providersSection && providersSection.classList.contains('active')) {
// 当前在商池页面,刷新数据
// 当前在供商池页面,刷新数据
await loadProviders();
showToast('商池数据已刷新', 'success');
showToast('供商池数据已刷新', 'success');
}
} catch (error) {
console.error('Failed to save configuration:', error);

View file

@ -66,7 +66,7 @@ function initEventListeners() {
button.addEventListener('click', handlePasswordToggle);
});
// 商池配置监听
// 供商池配置监听
const providerPoolsInput = document.getElementById('providerPoolsFilePath');
if (providerPoolsInput) {
providerPoolsInput.addEventListener('input', handleProviderPoolsConfigChange);
@ -92,7 +92,7 @@ function initEventListeners() {
}
/**
* 商配置切换处理
* 供商配置切换处理
*/
function handleProviderChange() {
const selectedProvider = elements.modelProvider?.value;
@ -100,12 +100,12 @@ function handleProviderChange() {
const allProviderConfigs = document.querySelectorAll('.provider-config');
// 隐藏所有商配置
// 隐藏所有供商配置
allProviderConfigs.forEach(config => {
config.style.display = 'none';
});
// 显示当前选中的商配置
// 显示当前选中的供商配置
const targetConfig = document.querySelector(`[data-provider="${selectedProvider}"]`);
if (targetConfig) {
targetConfig.style.display = 'block';
@ -172,7 +172,7 @@ function handlePasswordToggle(event) {
}
/**
* 商池配置变化处理
* 供商池配置变化处理
* @param {Event} event - 事件对象
*/
function handleProviderPoolsConfigChange(event) {
@ -180,13 +180,13 @@ function handleProviderPoolsConfigChange(event) {
const providersMenuItem = document.querySelector('.nav-item[data-section="providers"]');
if (filePath) {
// 显示商池菜单
// 显示供商池菜单
if (providersMenuItem) providersMenuItem.style.display = 'flex';
} else {
// 隐藏商池菜单
// 隐藏供商池菜单
if (providersMenuItem) providersMenuItem.style.display = 'none';
// 如果当前在商池页面,切换到仪表盘
// 如果当前在供商池页面,切换到仪表盘
if (providersMenuItem && providersMenuItem.classList.contains('active')) {
const dashboardItem = document.querySelector('.nav-item[data-section="dashboard"]');
const dashboardSection = document.getElementById('dashboard');

View file

@ -106,19 +106,19 @@ function updateProviderStatus(data) {
}
/**
* 处理商更新事件
* 处理供商更新事件
* @param {Object} data - 更新数据
*/
function handleProviderUpdate(data) {
if (data.action && data.providerType) {
// 如果当前打开的模态框是更新事件的商类型,则刷新该模态框
// 如果当前打开的模态框是更新事件的供商类型,则刷新该模态框
const modal = document.querySelector('.provider-modal');
if (modal && modal.getAttribute('data-provider-type') === data.providerType) {
if (typeof refreshProviderConfig === 'function') {
refreshProviderConfig(data.providerType);
}
} else {
// 否则更新主界面的商列表
// 否则更新主界面的供商列表
if (typeof loadProviders === 'function') {
loadProviders();
}

View file

@ -4,8 +4,8 @@ import { showToast, getFieldLabel, getProviderTypeFields } from './utils.js';
import { handleProviderPasswordToggle } from './event-handlers.js';
/**
* 显示商管理模态框
* @param {Object} data - 商数据
* 显示供商管理模态框
* @param {Object} data - 供商数据
*/
function showProviderManagerModal(data) {
const { providerType, providers, totalCount, healthyCount } = data;
@ -27,7 +27,7 @@ function showProviderManagerModal(data) {
modal.innerHTML = `
<div class="provider-modal-content">
<div class="provider-modal-header">
<h3><i class="fas fa-cogs"></i> ${providerType} </h3>
<h3><i class="fas fa-cogs"></i> ${providerType} </h3>
<button class="modal-close" onclick="window.closeProviderModal(this)">
<i class="fas fa-times"></i>
</button>
@ -44,7 +44,7 @@ function showProviderManagerModal(data) {
</div>
<div class="provider-summary-actions">
<button class="btn btn-success" onclick="window.showAddProviderForm('${providerType}')">
<i class="fas fa-plus"></i>
<i class="fas fa-plus"></i>
</button>
</div>
</div>
@ -149,8 +149,8 @@ function closeProviderModal(button) {
}
/**
* 渲染商列表
* @param {Array} providers - 商数组
* 渲染供商列表
* @param {Array} providers - 供商数组
* @returns {string} HTML字符串
*/
function renderProviderList(providers) {
@ -188,7 +188,7 @@ function renderProviderList(providers) {
</div>
</div>
<div class="provider-actions-group">
<button class="btn-small ${toggleButtonClass}" onclick="window.toggleProviderStatus('${provider.uuid}', event)" title="${toggleButtonText}此商">
<button class="btn-small ${toggleButtonClass}" onclick="window.toggleProviderStatus('${provider.uuid}', event)" title="${toggleButtonText}此供商">
<i class="${toggleButtonIcon}"></i> ${toggleButtonText}
</button>
<button class="btn-small btn-edit" onclick="window.editProvider('${provider.uuid}', event)">
@ -210,8 +210,8 @@ function renderProviderList(providers) {
}
/**
* 渲染商配置
* @param {Object} provider - 商对象
* 渲染供商配置
* @param {Object} provider - 供商对象
* @returns {string} HTML字符串
*/
function renderProviderConfig(provider) {
@ -385,7 +385,7 @@ function renderProviderConfig(provider) {
/**
* 获取字段显示顺序
* @param {Object} provider - 商对象
* @param {Object} provider - 供商对象
* @returns {Array} 字段键数组
*/
function getFieldOrder(provider) {
@ -405,8 +405,8 @@ function getFieldOrder(provider) {
}
/**
* 切换商详情显示
* @param {string} uuid - 商UUID
* 切换供商详情显示
* @param {string} uuid - 供商UUID
*/
function toggleProviderDetails(uuid) {
const content = document.getElementById(`content-${uuid}`);
@ -416,8 +416,8 @@ function toggleProviderDetails(uuid) {
}
/**
* 编辑
* @param {string} uuid - 商UUID
* 编辑供商
* @param {string} uuid - 供商UUID
* @param {Event} event - 事件对象
*/
function editProvider(uuid, event) {
@ -465,7 +465,7 @@ function editProvider(uuid, event) {
const toggleButtonClass = isCurrentlyDisabled ? 'btn-success' : 'btn-warning';
actionsGroup.innerHTML = `
<button class="btn-small ${toggleButtonClass}" onclick="window.toggleProviderStatus('${uuid}', event)" title="${toggleButtonText}此商">
<button class="btn-small ${toggleButtonClass}" onclick="window.toggleProviderStatus('${uuid}', event)" title="${toggleButtonText}此供商">
<i class="${toggleButtonIcon}"></i> ${toggleButtonText}
</button>
<button class="btn-small btn-save" onclick="window.saveProvider('${uuid}', event)">
@ -480,7 +480,7 @@ function editProvider(uuid, event) {
/**
* 取消编辑
* @param {string} uuid - 商UUID
* @param {string} uuid - 供商UUID
* @param {Event} event - 事件对象
*/
function cancelEdit(uuid, event) {
@ -523,7 +523,7 @@ function cancelEdit(uuid, event) {
const toggleButtonClass = isCurrentlyDisabled ? 'btn-success' : 'btn-warning';
actionsGroup.innerHTML = `
<button class="btn-small ${toggleButtonClass}" onclick="window.toggleProviderStatus('${uuid}', event)" title="${toggleButtonText}此商">
<button class="btn-small ${toggleButtonClass}" onclick="window.toggleProviderStatus('${uuid}', event)" title="${toggleButtonText}此供商">
<i class="${toggleButtonIcon}"></i> ${toggleButtonText}
</button>
<button class="btn-small btn-edit" onclick="window.editProvider('${uuid}', event)">
@ -536,8 +536,8 @@ function cancelEdit(uuid, event) {
}
/**
* 保存
* @param {string} uuid - 商UUID
* 保存供商
* @param {string} uuid - 供商UUID
* @param {Event} event - 事件对象
*/
async function saveProvider(uuid, event) {
@ -564,8 +564,8 @@ async function saveProvider(uuid, event) {
try {
await window.apiClient.put(`/providers/${encodeURIComponent(providerType)}/${uuid}`, { providerConfig });
showToast('商配置更新成功', 'success');
// 重新获取该商类型的最新配置
showToast('供商配置更新成功', 'success');
// 重新获取该供商类型的最新配置
await refreshProviderConfig(providerType);
} catch (error) {
console.error('Failed to update provider:', error);
@ -574,14 +574,14 @@ async function saveProvider(uuid, event) {
}
/**
* 删除
* @param {string} uuid - 商UUID
* 删除供商
* @param {string} uuid - 供商UUID
* @param {Event} event - 事件对象
*/
async function deleteProvider(uuid, event) {
event.stopPropagation();
if (!confirm('确定要删除这个商配置吗?此操作不可恢复。')) {
if (!confirm('确定要删除这个供商配置吗?此操作不可恢复。')) {
return;
}
@ -590,7 +590,7 @@ async function deleteProvider(uuid, event) {
try {
await window.apiClient.delete(`/providers/${encodeURIComponent(providerType)}/${uuid}`);
showToast('商配置删除成功', 'success');
showToast('供商配置删除成功', 'success');
// 重新获取最新配置
await refreshProviderConfig(providerType);
} catch (error) {
@ -600,15 +600,15 @@ async function deleteProvider(uuid, event) {
}
/**
* 重新获取并刷新商配置
* @param {string} providerType - 商类型
* 重新获取并刷新供商配置
* @param {string} providerType - 供商类型
*/
async function refreshProviderConfig(providerType) {
try {
// 重新获取该商类型的最新数据
// 重新获取该供商类型的最新数据
const data = await window.apiClient.get(`/providers/${encodeURIComponent(providerType)}`);
// 如果当前显示的是该商类型的模态框,则更新模态框
// 如果当前显示的是该供商类型的模态框,则更新模态框
const modal = document.querySelector('.provider-modal');
if (modal && modal.getAttribute('data-provider-type') === providerType) {
// 更新统计信息
@ -622,14 +622,14 @@ async function refreshProviderConfig(providerType) {
healthyCountElement.textContent = data.healthyCount;
}
// 重新渲染商列表
// 重新渲染供商列表
const providerList = modal.querySelector('.provider-list');
if (providerList) {
providerList.innerHTML = renderProviderList(data.providers);
}
}
// 同时更新主界面的商统计数据
// 同时更新主界面的供商统计数据
if (typeof window.loadProviders === 'function') {
await window.loadProviders();
}
@ -640,8 +640,8 @@ async function refreshProviderConfig(providerType) {
}
/**
* 显示添加商表单
* @param {string} providerType - 商类型
* 显示添加供商表单
* @param {string} providerType - 供商类型
*/
function showAddProviderForm(providerType) {
const modal = document.querySelector('.provider-modal');
@ -655,7 +655,7 @@ function showAddProviderForm(providerType) {
const form = document.createElement('div');
form.className = 'add-provider-form';
form.innerHTML = `
<h4><i class="fas fa-plus"></i> </h4>
<h4><i class="fas fa-plus"></i> </h4>
<div class="form-grid">
<div class="form-group">
<label>检查模型名称 <span class="optional-mark">(选填)</span></label>
@ -696,7 +696,7 @@ function showAddProviderForm(providerType) {
/**
* 添加动态配置字段
* @param {HTMLElement} form - 表单元素
* @param {string} providerType - 商类型
* @param {string} providerType - 供商类型
*/
function addDynamicConfigFields(form, providerType) {
const configFields = form.querySelector('#dynamicConfigFields');
@ -802,7 +802,7 @@ function addDynamicConfigFields(form, providerType) {
}
/**
* 为添加新商表单中的密码切换按钮绑定事件监听器
* 为添加新供商表单中的密码切换按钮绑定事件监听器
* @param {HTMLElement} form - 表单元素
*/
function bindAddFormPasswordToggleListeners(form) {
@ -827,8 +827,8 @@ function bindAddFormPasswordToggleListeners(form) {
}
/**
* 添加新
* @param {string} providerType - 商类型
* 添加新供商
* @param {string} providerType - 供商类型
*/
async function addProvider(providerType) {
const checkModelName = document.getElementById('newCheckModelName')?.value;
@ -870,7 +870,7 @@ async function addProvider(providerType) {
providerType,
providerConfig
});
showToast('商配置添加成功', 'success');
showToast('供商配置添加成功', 'success');
// 移除添加表单
const form = document.querySelector('.add-provider-form');
if (form) {
@ -885,8 +885,8 @@ async function addProvider(providerType) {
}
/**
* 切换商禁用/启用状态
* @param {string} uuid - 商UUID
* 切换供商禁用/启用状态
* @param {string} uuid - 供商UUID
* @param {Event} event - 事件对象
*/
async function toggleProviderStatus(uuid, event) {
@ -896,12 +896,12 @@ async function toggleProviderStatus(uuid, event) {
const providerType = providerDetail.closest('.provider-modal').getAttribute('data-provider-type');
const currentProvider = providerDetail.closest('.provider-modal').querySelector(`[data-uuid="${uuid}"]`);
// 获取当前商信息
// 获取当前供商信息
const isCurrentlyDisabled = currentProvider.classList.contains('disabled');
const action = isCurrentlyDisabled ? 'enable' : 'disable';
const confirmMessage = isCurrentlyDisabled ?
`确定要启用这个商配置吗?` :
`确定要禁用这个商配置吗?禁用后该供商将不会被选中使用。`;
`确定要启用这个供商配置吗?` :
`确定要禁用这个供商配置吗?禁用后该供商将不会被选中使用。`;
if (!confirm(confirmMessage)) {
return;
@ -909,8 +909,8 @@ async function toggleProviderStatus(uuid, event) {
try {
await window.apiClient.post(`/providers/${encodeURIComponent(providerType)}/${uuid}/${action}`, { action });
showToast(`${isCurrentlyDisabled ? '启用' : '禁用'}成功`, 'success');
// 重新获取该商类型的最新配置
showToast(`供商${isCurrentlyDisabled ? '启用' : '禁用'}成功`, 'success');
// 重新获取该供商类型的最新配置
await refreshProviderConfig(providerType);
} catch (error) {
console.error('Failed to toggle provider status:', error);

View file

@ -1,4 +1,4 @@
// 商管理功能模块
// 供商管理功能模块
import { providerStats, updateProviderStats } from './constants.js';
import { showToast } from './utils.js';
@ -96,7 +96,7 @@ function renderProviders(providers) {
container.innerHTML = '';
// 检查是否有商池数据
// 检查是否有供商池数据
const hasProviders = Object.keys(providers).length > 0;
const statsGrid = document.querySelector('#providers .stats-grid');
@ -114,10 +114,10 @@ function renderProviders(providers) {
];
// 获取所有提供商类型并按指定顺序排序
// 优先显示预定义的所有商类型,即使某些供商没有数据也要显示
// 优先显示预定义的所有供商类型,即使某些供商没有数据也要显示
let allProviderTypes;
if (hasProviders) {
// 合并预定义类型和实际存在的类型,确保显示所有预定义
// 合并预定义类型和实际存在的类型,确保显示所有预定义供商
const actualProviderTypes = Object.keys(providers);
allProviderTypes = [...new Set([...providerDisplayOrder, ...actualProviderTypes])];
} else {
@ -205,7 +205,7 @@ function renderProviders(providers) {
providerDiv.classList.add('empty-provider');
}
// 添加点击事件 - 整个商组都可以点击
// 添加点击事件 - 整个供商组都可以点击
providerDiv.addEventListener('click', (e) => {
e.preventDefault();
openProviderManager(providerType);
@ -286,7 +286,7 @@ function updateProviderStatsDisplay(activeProviders, healthyProviders, totalAcco
}
/**
* 打开商管理模态框
* 打开供商管理模态框
* @param {string} providerType - 提供商类型
*/
async function openProviderManager(providerType) {
@ -296,7 +296,7 @@ async function openProviderManager(providerType) {
showProviderManagerModal(data);
} catch (error) {
console.error('Failed to load provider details:', error);
showToast('加载商详情失败', 'error');
showToast('加载供商详情失败', 'error');
}
}

View file

@ -450,7 +450,7 @@ textarea.form-control {
cursor: pointer;
}
/* 商配置组 */
/* 供商配置组 */
.provider-config {
border: 1px solid var(--border-color);
border-radius: 0.5rem;
@ -1191,7 +1191,7 @@ input:checked + .toggle-slider:before {
font-style: italic;
}
/* 商类型显示 */
/* 供商类型显示 */
.provider-type-text {
font-size: 16px;
font-weight: 600;
@ -2288,7 +2288,7 @@ input:checked + .toggle-slider:before {
background: var(--success-color);
}
/* 商池关联的特殊样式 */
/* 供商池关联的特殊样式 */
.usage-detail-item[data-usage-type="provider_pool"] {
background: linear-gradient(135deg, #f0f9ff 0%, #ffffff 100%);
border-color: #0ea5e9;
@ -2700,7 +2700,7 @@ input:checked + .toggle-slider:before {
}
}
/* 禁用商状态样式 */
/* 禁用供商状态样式 */
.provider-item-detail.disabled {
opacity: 0.6;
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
@ -2776,7 +2776,7 @@ input:checked + .toggle-slider:before {
transform: translateY(0);
}
/* 商状态指示器 */
/* 供商状态指示器 */
.provider-status .disabled-indicator {
position: relative;
}

View file

@ -169,7 +169,7 @@ function generateUsageInfoHtml(config) {
const typeLabels = {
'main_config': '主要配置',
'provider_pool': '商池',
'provider_pool': '供商池',
'multiple': '多种用途'
};
@ -567,7 +567,7 @@ function showDeleteConfirmModal(config) {
<ul>
<li>相关的AI服务无法正常工作</li>
<li>配置管理中的设置失效</li>
<li>商池配置丢失</li>
<li>供商池配置丢失</li>
</ul>
<p><strong>建议</strong></p>
</div>

View file

@ -42,8 +42,8 @@
<a href="#config" class="nav-item" data-section="config" aria-label="配置管理">
<i class="fas fa-cog" aria-hidden="true"></i> <span>配置管理</span>
</a>
<a href="#providers" class="nav-item" data-section="providers" aria-label="商池管理">
<i class="fas fa-network-wired" aria-hidden="true"></i> <span>商池管理</span>
<a href="#providers" class="nav-item" data-section="providers" aria-label="供商池管理">
<i class="fas fa-network-wired" aria-hidden="true"></i> <span>供商池管理</span>
</a>
<a href="#upload-config" class="nav-item" data-section="upload-config" aria-label="上传配置管理">
<i class="fas fa-upload" aria-hidden="true"></i> <span>上传配置管理</span>
@ -602,9 +602,9 @@
</div>
<div class="form-group pool-section">
<label for="providerPoolsFilePath">商池配置文件路径</label>
<label for="providerPoolsFilePath">供商池配置文件路径</label>
<input type="text" id="providerPoolsFilePath" class="form-control" value="" placeholder="例如: provider_pools.json">
<small class="form-text">配置了供商池后,可在供商池管理中查看详细信息</small>
<small class="form-text">配置了供商池后,可在供商池管理中查看详细信息</small>
</div>
<!-- 系统提示配置移到最下面 -->
@ -680,7 +680,7 @@
<!-- Provider Pools Section -->
<section id="providers" class="section" aria-labelledby="providers-title">
<h2 id="providers-title">商池管理</h2>
<h2 id="providers-title">供商池管理</h2>
<!-- Provider Pool Stats -->
<div class="stats-grid">
<div class="stat-card">
@ -764,7 +764,38 @@
}
});
}
// 更新路径路由示例中的URL前缀
updateRoutingExamplesURLs();
})();
// 更新路径路由示例中的URL前缀
function updateRoutingExamplesURLs() {
// 获取当前页面的基础URL去掉index.html
const currentURL = window.location.href;
const baseURL = currentURL.replace(/\/index\.html$/, '');
// 更新所有端点路径
const endpointPaths = document.querySelectorAll('.endpoint-path');
endpointPaths.forEach(element => {
const originalPath = element.textContent;
if (!originalPath.startsWith(baseURL)) {
// 确保baseURL不以斜杠结尾然后正确拼接路径
const cleanBaseURL = baseURL.replace(/\/$/, '');
const cleanPath = originalPath.startsWith('/') ? originalPath : '/' + originalPath;
element.textContent = cleanBaseURL + cleanPath;
}
});
// 更新curl命令中的URL
const curlCodes = document.querySelectorAll('.usage-example pre code');
curlCodes.forEach(element => {
const curlCommand = element.textContent;
// 替换curl命令中的http://localhost:3000部分
const updatedCommand = curlCommand.replace(/curl http:\/\/localhost:3000/g, `curl ${baseURL}`);
element.textContent = updatedCommand;
});
}
</script>
<script type="module" src="app/app.js"></script>
</body>