feat: 添加模型管理功能和使用指南页面

新增模型管理功能,包括模型列表展示、复制功能和样式
添加使用指南和配置教程页面,包含操作流程图和客户端配置说明
更新侧边栏导航和组件加载器以支持新页面
This commit is contained in:
hex2077 2026-01-15 17:33:36 +08:00
parent a6656e6b9a
commit 9d383fec26
9 changed files with 2642 additions and 0 deletions

View file

@ -100,6 +100,8 @@ async function initializeComponents() {
// 最后加载所有 section 组件
const sectionComponents = [
{ path: `${basePath}section-dashboard.html`, container: '#content-container', position: 'beforeend' },
{ path: `${basePath}section-guide.html`, container: '#content-container', position: 'beforeend' },
{ path: `${basePath}section-tutorial.html`, container: '#content-container', position: 'beforeend' },
{ path: `${basePath}section-config.html`, container: '#content-container', position: 'beforeend' },
{ path: `${basePath}section-upload-config.html`, container: '#content-container', position: 'beforeend' },
{ path: `${basePath}section-providers.html`, container: '#content-container', position: 'beforeend' },

View file

@ -26,12 +26,15 @@ const translations = {
// Navigation
'nav.main': '主导航',
'nav.dashboard': '仪表盘',
'nav.guide': '使用指南',
'nav.tutorial': '配置教程',
'nav.config': '配置管理',
'nav.providers': '提供商池管理',
'nav.upload': '凭据文件管理',
'nav.usage': '用量查询',
'nav.logs': '实时日志',
'nav.plugins': '插件管理',
'nav.models': '可用模型',
// Dashboard
'dashboard.title': '系统概览',
@ -523,6 +526,138 @@ const translations = {
'plugins.load.failed': '加载插件列表失败',
'plugins.restart.required': '更改已保存',
// Models
'models.title': '可用模型列表',
'models.note': '点击模型名称可直接复制到剪贴板',
'models.empty': '暂无可用模型',
'models.loadError': '加载模型列表失败',
'models.copied': '已复制',
'models.clickToCopy': '点击复制',
// Guide
'guide.title': '使用指南',
'guide.intro.title': '项目简介',
'guide.intro.desc': 'AIClient2API 是一个突破客户端限制的 API 代理服务,将 Gemini、Antigravity、Qwen Code、Kiro 等原本仅限客户端内使用的免费大模型,转换为可供任何应用调用的标准 OpenAI 兼容接口。',
'guide.intro.feature1.title': '统一接入',
'guide.intro.feature1.desc': '通过标准 OpenAI 兼容协议,一次配置即可接入多种大模型',
'guide.intro.feature2.title': '突破限制',
'guide.intro.feature2.desc': '利用 OAuth 授权机制,有效突破免费 API 速率和配额限制',
'guide.intro.feature3.title': '协议转换',
'guide.intro.feature3.desc': '支持 OpenAI、Claude、Gemini 三大协议间的智能转换',
'guide.intro.feature4.title': '账号池管理',
'guide.intro.feature4.desc': '支持多账号轮询、自动故障转移和配置降级',
'guide.providers.title': '支持的模型提供商',
'guide.providers.badge.oauth': 'OAuth 授权',
'guide.providers.badge.experimental': '实验性',
'guide.providers.badge.free': '免费使用',
'guide.providers.badge.official': '官方 API',
'guide.providers.gemini.desc': '通过 Google OAuth 认证访问 Gemini 模型,支持 gemini-2.0-flash-exp 等模型',
'guide.providers.antigravity.desc': '通过 Google 内部接口访问 Gemini 3 Pro、Claude Sonnet 4.5 等模型',
'guide.providers.kiro.desc': '通过 Kiro 客户端免费使用 Claude Opus 4.5、Claude Sonnet 4.5 等模型',
'guide.providers.qwen.desc': '通过阿里云 OAuth 认证访问 Qwen3 Coder Plus 等模型',
'guide.providers.claude.desc': '使用 Claude 官方 API 或第三方代理访问 Claude 系列模型',
'guide.providers.openai.desc': '使用 OpenAI 官方 API 或第三方代理访问 GPT 系列模型',
'guide.providers.iflow.desc': '通过 iFlow OAuth 认证访问 Qwen、Kimi、DeepSeek、GLM 等模型',
'guide.providers.orchids.desc': '通过 Orchids 平台免费使用 Claude Sonnet 4.5 等模型',
'guide.client.title': '客户端配置指南',
'guide.client.desc': '以下是常见 AI 客户端的配置方法,将 API 端点设置为本服务地址即可使用:',
'guide.client.cherry.step1': '打开设置 → 模型服务商',
'guide.client.cherry.step2': '添加自定义服务商',
'guide.client.cherry.step3': '设置 API 地址为: http://localhost:3000/{provider}',
'guide.client.cherry.step4': '填入 API Key配置文件中的 REQUIRED_API_KEY',
'guide.client.cline.step1': '打开 VS Code 设置',
'guide.client.cline.step2': '搜索 Cline 或 Continue 配置',
'guide.client.cline.step3': '设置 API Base URL 为: http://localhost:3000/{provider}/v1',
'guide.client.cline.step4': '填入 API Key 和模型名称',
'guide.client.note': '提示:将 {provider} 替换为实际的提供商路径,如 gemini-cli-oauth、claude-kiro-oauth 等。可在仪表盘的路由示例中查看完整路径。',
'guide.ollama.title': 'Ollama 协议使用',
'guide.ollama.desc': '本项目支持 Ollama 协议,可以通过统一接口访问所有支持的模型。',
'guide.ollama.listModels': '列出所有可用模型',
'guide.ollama.chat': '聊天接口',
'guide.faq.title': '常见问题',
'guide.faq.q1': 'Q: 请求返回 404 错误怎么办?',
'guide.faq.a1': 'A: 检查接口路径是否正确。某些客户端会自动在 Base URL 后追加路径,导致路径重复。请查看控制台中的实际请求 URL移除多余的路径部分。',
'guide.faq.q2': 'Q: 请求返回 429 错误怎么办?',
'guide.faq.a2': 'A: 429 表示请求频率过高。建议配置多个账号到提供商池,启用轮询机制;或配置 Fallback 链实现跨类型降级。',
'guide.faq.q3': 'Q: OAuth 授权失败怎么办?',
'guide.faq.a3': 'A: 确保 OAuth 回调端口可访问Gemini: 8085, Antigravity: 8086, Kiro: 19876-19880。Docker 用户需确保已正确映射这些端口。',
'guide.faq.q4': 'Q: 如何查看可用的模型列表?',
'guide.faq.a4': 'A: 在侧边栏点击"可用模型"页面,可以查看所有已配置提供商支持的模型列表。点击模型名称可直接复制。',
'guide.faq.q5': 'Q: 流式响应中断怎么办?',
'guide.faq.a5': 'A: 检查网络稳定性,增加客户端请求超时时间。如使用代理,确保代理支持长连接。',
// Guide - Flow
'guide.flow.title': '操作流程图',
'guide.flow.step1.title': '配置管理',
'guide.flow.step1.desc': '在「配置管理」页面设置基本参数',
'guide.flow.step2.title': '生成授权',
'guide.flow.step2.desc': '在「提供商池管理」页面生成 OAuth 授权',
'guide.flow.step3.title': '管理凭据',
'guide.flow.step3.desc': '在「凭据文件管理」页面查看和管理凭据',
'guide.flow.step4.title': '开始使用',
'guide.flow.step4.desc': '在「仪表盘」查看路由示例并开始调用 API',
// Tutorial
'tutorial.title': '配置教程',
'tutorial.config.title': '配置文件说明',
'tutorial.config.desc': '所有配置文件都存放在 configs/ 目录下。主要配置文件包括:',
'tutorial.config.badge.required': '必需',
'tutorial.config.badge.optional': '可选',
'tutorial.config.file.config': '主配置文件,包含 API Key、端口、模型提供商等核心设置',
'tutorial.config.file.pools': '提供商池配置,用于多账号轮询和故障转移',
'tutorial.config.file.plugins': '插件配置,用于启用或禁用系统插件',
'tutorial.config.file.pwd': '后台登录密码文件,默认密码为 admin123',
'tutorial.main.title': '主配置详解 (config.json)',
'tutorial.main.table.param': '参数',
'tutorial.main.table.type': '类型',
'tutorial.main.table.default': '默认值',
'tutorial.main.table.desc': '说明',
'tutorial.main.basic.title': '基础配置',
'tutorial.main.basic.apikey': '访问本服务所需的 API Key',
'tutorial.main.basic.port': '服务监听端口',
'tutorial.main.basic.host': '服务监听地址',
'tutorial.main.basic.provider': '默认模型提供商',
'tutorial.main.prompt.title': '系统提示配置',
'tutorial.main.prompt.file': '系统提示文件路径',
'tutorial.main.prompt.mode': '系统提示模式overwrite(覆盖) 或 append(追加)',
'tutorial.main.retry.title': '重试配置',
'tutorial.main.retry.max': '最大重试次数',
'tutorial.main.retry.delay': '重试基础延迟(毫秒)',
'tutorial.main.retry.error': '提供商最大错误次数,超过后标记为不健康',
'tutorial.main.example.title': '配置示例',
'tutorial.pool.title': '提供商池配置 (provider_pools.json)',
'tutorial.pool.desc': '提供商池用于配置多个账号,实现负载均衡和故障转移。每个提供商类型可以配置多个账号节点。',
'tutorial.pool.node.title': '节点配置参数',
'tutorial.pool.node.uuid': '节点唯一标识,自动生成',
'tutorial.pool.node.name': '节点自定义名称',
'tutorial.pool.node.oauth': 'OAuth 凭据文件路径',
'tutorial.pool.node.health': '是否启用健康检查',
'tutorial.pool.node.model': '健康检查使用的模型',
'tutorial.pool.node.unsupported': '该节点不支持的模型列表',
'tutorial.pool.node.disabled': '是否禁用该节点',
'tutorial.pool.example.title': '配置示例',
'tutorial.fallback.title': 'Fallback 降级配置',
'tutorial.fallback.desc': '当某一提供商类型的所有账号都不可用时,可以自动切换到配置的备用提供商。',
'tutorial.fallback.chain.title': '跨类型 Fallback 链',
'tutorial.fallback.chain.desc': '在 config.json 中配置 providerFallbackChain指定每个提供商类型的备用类型',
'tutorial.fallback.model.title': '跨协议模型映射',
'tutorial.fallback.model.desc': '当主提供商不可用时,可以将特定模型映射到其他协议的提供商:',
'tutorial.proxy.title': '代理配置',
'tutorial.proxy.desc': '支持为特定提供商配置代理,用于网络受限环境。',
'tutorial.proxy.config.title': '代理配置参数',
'tutorial.proxy.url': '代理地址,支持 HTTP、HTTPS、SOCKS5',
'tutorial.proxy.providers': '启用代理的提供商列表',
'tutorial.proxy.example.title': '配置示例',
'tutorial.proxy.note': '支持的代理类型HTTP (http://)、HTTPS (https://)、SOCKS5 (socks5://)',
'tutorial.oauth.title': 'OAuth 授权配置',
'tutorial.oauth.desc': '各提供商的 OAuth 凭据文件默认存储位置:',
'tutorial.oauth.note': '推荐通过 Web UI 控制台的"提供商池管理"页面点击"生成授权"按钮进行可视化授权,系统会自动保存凭据文件。',
'tutorial.log.title': '日志配置',
'tutorial.log.prompt.title': '提示日志配置',
'tutorial.log.mode': '日志模式none(关闭)、console(控制台)、file(文件)',
'tutorial.log.basename': '日志文件基础名称',
'tutorial.log.example.title': '配置示例',
// Common
'common.confirm': '确定',
'common.cancel': '取消',
@ -588,12 +723,15 @@ const translations = {
// Navigation
'nav.main': 'Main Navigation',
'nav.dashboard': 'Dashboard',
'nav.guide': 'User Guide',
'nav.tutorial': 'Configuration Tutorial',
'nav.config': 'Configuration',
'nav.providers': 'Provider Pools',
'nav.upload': 'Credential Files',
'nav.usage': 'Usage Query',
'nav.logs': 'Real-time Logs',
'nav.plugins': 'Plugin Management',
'nav.models': 'Available Models',
// Dashboard
'dashboard.title': 'System Overview',
@ -1085,6 +1223,138 @@ const translations = {
'plugins.load.failed': 'Failed to load plugins list',
'plugins.restart.required': 'Changes saved',
// Models
'models.title': 'Available Models',
'models.note': 'Click model name to copy to clipboard',
'models.empty': 'No models available',
'models.loadError': 'Failed to load models',
'models.copied': 'Copied',
'models.clickToCopy': 'Click to copy',
// Guide
'guide.title': 'User Guide',
'guide.intro.title': 'Introduction',
'guide.intro.desc': 'AIClient2API is an API proxy service that breaks client restrictions, converting free large models like Gemini, Antigravity, Qwen Code, and Kiro into standard OpenAI-compatible interfaces that any application can call.',
'guide.intro.feature1.title': 'Unified Access',
'guide.intro.feature1.desc': 'Access multiple large models with a single configuration through standard OpenAI-compatible protocol',
'guide.intro.feature2.title': 'Break Limits',
'guide.intro.feature2.desc': 'Effectively bypass free API rate and quota limits using OAuth authorization',
'guide.intro.feature3.title': 'Protocol Conversion',
'guide.intro.feature3.desc': 'Support intelligent conversion between OpenAI, Claude, and Gemini protocols',
'guide.intro.feature4.title': 'Account Pool',
'guide.intro.feature4.desc': 'Support multi-account polling, automatic failover, and configuration degradation',
'guide.providers.title': 'Supported Model Providers',
'guide.providers.badge.oauth': 'OAuth',
'guide.providers.badge.experimental': 'Experimental',
'guide.providers.badge.free': 'Free',
'guide.providers.badge.official': 'Official API',
'guide.providers.gemini.desc': 'Access Gemini models via Google OAuth, supporting gemini-2.0-flash-exp and more',
'guide.providers.antigravity.desc': 'Access Gemini 3 Pro, Claude Sonnet 4.5 via Google internal interface',
'guide.providers.kiro.desc': 'Free access to Claude Opus 4.5, Claude Sonnet 4.5 via Kiro client',
'guide.providers.qwen.desc': 'Access Qwen3 Coder Plus via Alibaba Cloud OAuth',
'guide.providers.claude.desc': 'Access Claude models via official API or third-party proxy',
'guide.providers.openai.desc': 'Access GPT models via official API or third-party proxy',
'guide.providers.iflow.desc': 'Access Qwen, Kimi, DeepSeek, GLM via iFlow OAuth',
'guide.providers.orchids.desc': 'Free access to Claude Sonnet 4.5 via Orchids platform',
'guide.client.title': 'Client Configuration Guide',
'guide.client.desc': 'Here are configuration methods for common AI clients. Set the API endpoint to this service address:',
'guide.client.cherry.step1': 'Open Settings → Model Providers',
'guide.client.cherry.step2': 'Add custom provider',
'guide.client.cherry.step3': 'Set API URL to: http://localhost:3000/{provider}',
'guide.client.cherry.step4': 'Enter API Key (REQUIRED_API_KEY from config)',
'guide.client.cline.step1': 'Open VS Code Settings',
'guide.client.cline.step2': 'Search for Cline or Continue configuration',
'guide.client.cline.step3': 'Set API Base URL to: http://localhost:3000/{provider}/v1',
'guide.client.cline.step4': 'Enter API Key and model name',
'guide.client.note': 'Tip: Replace {provider} with the actual provider path, such as gemini-cli-oauth, claude-kiro-oauth, etc. See the routing examples on the dashboard for full paths.',
'guide.ollama.title': 'Ollama Protocol Usage',
'guide.ollama.desc': 'This project supports Ollama protocol, allowing unified access to all supported models.',
'guide.ollama.listModels': 'List all available models',
'guide.ollama.chat': 'Chat interface',
'guide.faq.title': 'FAQ',
'guide.faq.q1': 'Q: What to do if request returns 404 error?',
'guide.faq.a1': 'A: Check if the API path is correct. Some clients automatically append paths to Base URL, causing duplication. Check the actual request URL in the console and remove redundant path parts.',
'guide.faq.q2': 'Q: What to do if request returns 429 error?',
'guide.faq.a2': 'A: 429 means request rate is too high. Configure multiple accounts in the provider pool with polling; or configure Fallback chain for cross-type degradation.',
'guide.faq.q3': 'Q: What to do if OAuth authorization fails?',
'guide.faq.a3': 'A: Ensure OAuth callback ports are accessible (Gemini: 8085, Antigravity: 8086, Kiro: 19876-19880). Docker users need to map these ports correctly.',
'guide.faq.q4': 'Q: How to view available models?',
'guide.faq.a4': 'A: Click "Available Models" in the sidebar to view all models supported by configured providers. Click model name to copy.',
'guide.faq.q5': 'Q: What to do if streaming response is interrupted?',
'guide.faq.a5': 'A: Check network stability, increase client request timeout. If using proxy, ensure it supports long connections.',
// Guide - Flow
'guide.flow.title': 'Operation Flowchart',
'guide.flow.step1.title': 'Configuration',
'guide.flow.step1.desc': 'Set basic parameters in "Configuration" page',
'guide.flow.step2.title': 'Generate Auth',
'guide.flow.step2.desc': 'Generate OAuth authorization in "Provider Pools" page',
'guide.flow.step3.title': 'Manage Credentials',
'guide.flow.step3.desc': 'View and manage credentials in "Credential Files" page',
'guide.flow.step4.title': 'Start Using',
'guide.flow.step4.desc': 'View routing examples in "Dashboard" and start calling API',
// Tutorial
'tutorial.title': 'Configuration Tutorial',
'tutorial.config.title': 'Configuration Files',
'tutorial.config.desc': 'All configuration files are stored in the configs/ directory. Main configuration files include:',
'tutorial.config.badge.required': 'Required',
'tutorial.config.badge.optional': 'Optional',
'tutorial.config.file.config': 'Main config file with API Key, port, model provider settings',
'tutorial.config.file.pools': 'Provider pool config for multi-account polling and failover',
'tutorial.config.file.plugins': 'Plugin config for enabling/disabling system plugins',
'tutorial.config.file.pwd': 'Admin password file, default password is admin123',
'tutorial.main.title': 'Main Config Details (config.json)',
'tutorial.main.table.param': 'Parameter',
'tutorial.main.table.type': 'Type',
'tutorial.main.table.default': 'Default',
'tutorial.main.table.desc': 'Description',
'tutorial.main.basic.title': 'Basic Configuration',
'tutorial.main.basic.apikey': 'API Key required to access this service',
'tutorial.main.basic.port': 'Service listening port',
'tutorial.main.basic.host': 'Service listening address',
'tutorial.main.basic.provider': 'Default model provider',
'tutorial.main.prompt.title': 'System Prompt Configuration',
'tutorial.main.prompt.file': 'System prompt file path',
'tutorial.main.prompt.mode': 'System prompt mode: overwrite or append',
'tutorial.main.retry.title': 'Retry Configuration',
'tutorial.main.retry.max': 'Maximum retry count',
'tutorial.main.retry.delay': 'Base retry delay (milliseconds)',
'tutorial.main.retry.error': 'Max provider error count before marking unhealthy',
'tutorial.main.example.title': 'Configuration Example',
'tutorial.pool.title': 'Provider Pool Config (provider_pools.json)',
'tutorial.pool.desc': 'Provider pool configures multiple accounts for load balancing and failover. Each provider type can have multiple account nodes.',
'tutorial.pool.node.title': 'Node Configuration Parameters',
'tutorial.pool.node.uuid': 'Unique node identifier, auto-generated',
'tutorial.pool.node.name': 'Custom node name',
'tutorial.pool.node.oauth': 'OAuth credentials file path',
'tutorial.pool.node.health': 'Enable health check',
'tutorial.pool.node.model': 'Model used for health check',
'tutorial.pool.node.unsupported': 'List of unsupported models for this node',
'tutorial.pool.node.disabled': 'Whether to disable this node',
'tutorial.pool.example.title': 'Configuration Example',
'tutorial.fallback.title': 'Fallback Configuration',
'tutorial.fallback.desc': 'When all accounts of a provider type are unavailable, automatically switch to configured backup providers.',
'tutorial.fallback.chain.title': 'Cross-Type Fallback Chain',
'tutorial.fallback.chain.desc': 'Configure providerFallbackChain in config.json to specify backup types for each provider:',
'tutorial.fallback.model.title': 'Cross-Protocol Model Mapping',
'tutorial.fallback.model.desc': 'When primary provider is unavailable, map specific models to other protocol providers:',
'tutorial.proxy.title': 'Proxy Configuration',
'tutorial.proxy.desc': 'Support proxy configuration for specific providers in restricted network environments.',
'tutorial.proxy.config.title': 'Proxy Configuration Parameters',
'tutorial.proxy.url': 'Proxy URL, supports HTTP, HTTPS, SOCKS5',
'tutorial.proxy.providers': 'List of providers using proxy',
'tutorial.proxy.example.title': 'Configuration Example',
'tutorial.proxy.note': 'Supported proxy types: HTTP (http://), HTTPS (https://), SOCKS5 (socks5://)',
'tutorial.oauth.title': 'OAuth Configuration',
'tutorial.oauth.desc': 'Default storage locations for OAuth credentials of each provider:',
'tutorial.oauth.note': 'Recommended: Use the "Generate Auth" button in Provider Pool Management page for visual authorization. Credentials will be saved automatically.',
'tutorial.log.title': 'Log Configuration',
'tutorial.log.prompt.title': 'Prompt Log Configuration',
'tutorial.log.mode': 'Log mode: none, console, or file',
'tutorial.log.basename': 'Log file base name',
'tutorial.log.example.title': 'Configuration Example',
// Common
'common.togglePassword': 'Show/Hide Password',
'common.confirm': 'Confirm',

View file

@ -0,0 +1,289 @@
/**
* Models Manager - 管理可用模型列表的显示和复制功能
* Models Manager - Manages the display and copy functionality of available models
*/
import { t } from './i18n.js';
// 模型数据缓存
let modelsCache = null;
/**
* 获取所有提供商的可用模型
* @returns {Promise<Object>} 模型数据
*/
async function fetchProviderModels() {
if (modelsCache) {
return modelsCache;
}
try {
const response = await fetch('/api/provider-models', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('authToken') || ''}`
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
modelsCache = await response.json();
return modelsCache;
} catch (error) {
console.error('[Models Manager] Failed to fetch provider models:', error);
throw error;
}
}
/**
* 复制文本到剪贴板
* @param {string} text - 要复制的文本
* @returns {Promise<boolean>} 是否复制成功
*/
async function copyToClipboard(text) {
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text);
return true;
}
// Fallback for older browsers
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-9999px';
textArea.style.top = '-9999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
const successful = document.execCommand('copy');
document.body.removeChild(textArea);
return successful;
} catch (error) {
console.error('[Models Manager] Failed to copy to clipboard:', error);
return false;
}
}
/**
* 显示复制成功的 Toast 提示
* @param {string} modelName - 模型名称
*/
function showCopyToast(modelName) {
const toastContainer = document.getElementById('toastContainer');
if (!toastContainer) return;
const toast = document.createElement('div');
toast.className = 'toast toast-success';
toast.innerHTML = `
<i class="fas fa-check-circle"></i>
<span>${t('models.copied') || '已复制'}: ${modelName}</span>
`;
toastContainer.appendChild(toast);
// 自动移除
setTimeout(() => {
toast.classList.add('toast-fade-out');
setTimeout(() => {
toast.remove();
}, 300);
}, 2000);
}
/**
* 渲染模型列表
* @param {Object} models - 模型数据
*/
function renderModelsList(models) {
const container = document.getElementById('modelsList');
if (!container) return;
// 检查是否有模型数据
const providerTypes = Object.keys(models);
if (providerTypes.length === 0) {
container.innerHTML = `
<div class="models-empty">
<i class="fas fa-cube"></i>
<p data-i18n="models.empty">${t('models.empty') || '暂无可用模型'}</p>
</div>
`;
return;
}
// 渲染每个提供商的模型组
let html = '';
for (const providerType of providerTypes) {
const modelList = models[providerType];
if (!modelList || modelList.length === 0) continue;
const providerDisplayName = getProviderDisplayName(providerType);
const providerIcon = getProviderIcon(providerType);
html += `
<div class="provider-models-group" data-provider="${providerType}">
<div class="provider-models-header" onclick="window.toggleProviderModels('${providerType}')">
<div class="provider-models-title">
<i class="${providerIcon}"></i>
<h3>${providerDisplayName}</h3>
<span class="provider-models-count">${modelList.length}</span>
</div>
<div class="provider-models-toggle">
<i class="fas fa-chevron-down"></i>
</div>
</div>
<div class="provider-models-content" id="models-${providerType}">
${modelList.map(model => `
<div class="model-item" onclick="window.copyModelName('${escapeHtml(model)}', this)" title="${t('models.clickToCopy') || '点击复制'}">
<div class="model-item-icon">
<i class="fas fa-cube"></i>
</div>
<span class="model-item-name">${escapeHtml(model)}</span>
<div class="model-item-copy">
<i class="fas fa-copy"></i>
</div>
</div>
`).join('')}
</div>
</div>
`;
}
container.innerHTML = html;
}
/**
* 获取提供商显示名称
* @param {string} providerType - 提供商类型
* @returns {string} 显示名称
*/
function getProviderDisplayName(providerType) {
const displayNames = {
'gemini-cli-oauth': 'Gemini CLI (OAuth)',
'gemini-antigravity': 'Gemini Antigravity',
'claude-custom': 'Claude Custom',
'claude-kiro-oauth': 'Claude Kiro (OAuth)',
'claude-orchids-oauth': 'Claude Orchids (OAuth)',
'openai-custom': 'OpenAI Custom',
'openaiResponses-custom': 'OpenAI Responses Custom',
'openai-qwen-oauth': 'Qwen (OAuth)',
'openai-iflow': 'iFlow'
};
return displayNames[providerType] || providerType;
}
/**
* 获取提供商图标
* @param {string} providerType - 提供商类型
* @returns {string} 图标类名
*/
function getProviderIcon(providerType) {
if (providerType.includes('gemini')) {
return 'fas fa-gem';
} else if (providerType.includes('claude')) {
return 'fas fa-robot';
} else if (providerType.includes('openai') || providerType.includes('qwen') || providerType.includes('iflow')) {
return 'fas fa-brain';
}
return 'fas fa-server';
}
/**
* HTML 转义
* @param {string} text - 原始文本
* @returns {string} 转义后的文本
*/
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
/**
* 切换提供商模型列表的展开/折叠状态
* @param {string} providerType - 提供商类型
*/
function toggleProviderModels(providerType) {
const group = document.querySelector(`.provider-models-group[data-provider="${providerType}"]`);
if (!group) return;
const header = group.querySelector('.provider-models-header');
const content = group.querySelector('.provider-models-content');
if (content.classList.contains('collapsed')) {
content.classList.remove('collapsed');
header.classList.remove('collapsed');
} else {
content.classList.add('collapsed');
header.classList.add('collapsed');
}
}
/**
* 复制模型名称
* @param {string} modelName - 模型名称
* @param {HTMLElement} element - 点击的元素
*/
async function copyModelName(modelName, element) {
const success = await copyToClipboard(modelName);
if (success) {
// 添加复制成功的视觉反馈
element.classList.add('copied');
setTimeout(() => {
element.classList.remove('copied');
}, 1000);
// 显示 Toast 提示
showCopyToast(modelName);
}
}
/**
* 初始化模型管理器
*/
async function initModelsManager() {
const container = document.getElementById('modelsList');
if (!container) return;
try {
const models = await fetchProviderModels();
renderModelsList(models);
} catch (error) {
container.innerHTML = `
<div class="models-empty">
<i class="fas fa-exclamation-triangle"></i>
<p>${t('models.loadError') || '加载模型列表失败'}</p>
</div>
`;
}
}
/**
* 刷新模型列表
*/
async function refreshModels() {
modelsCache = null;
await initModelsManager();
}
// 导出到全局作用域供 HTML 调用
window.toggleProviderModels = toggleProviderModels;
window.copyModelName = copyModelName;
window.refreshModels = refreshModels;
// 监听组件加载完成事件
window.addEventListener('componentsLoaded', () => {
initModelsManager();
});
// 导出函数
export {
initModelsManager,
refreshModels,
fetchProviderModels
};

View file

@ -0,0 +1,913 @@
/* Guide Section Styles */
/* 操作流程图样式 */
.process-flow {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
justify-content: center;
gap: 0.5rem;
padding: 1.5rem 0;
}
.flow-step {
display: flex;
flex-direction: column;
align-items: center;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 1.25rem;
width: 200px;
min-height: 200px;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.flow-step:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
border-color: var(--primary-color);
}
.step-number {
width: 40px;
height: 40px;
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
font-weight: 700;
margin-bottom: 1rem;
box-shadow: 0 4px 12px rgba(var(--primary-rgb, 59, 130, 246), 0.3);
}
.step-content {
text-align: center;
flex: 1;
}
.step-content h4 {
font-size: 0.95rem;
font-weight: 600;
color: var(--text-primary);
margin: 0 0 0.5rem 0;
}
.step-content p {
font-size: 0.8rem;
color: var(--text-secondary);
margin: 0 0 0.75rem 0;
line-height: 1.4;
}
.step-content ul {
list-style: none;
padding: 0;
margin: 0;
text-align: left;
}
.step-content ul li {
font-size: 0.75rem;
color: var(--text-secondary);
padding: 0.25rem 0;
padding-left: 1rem;
position: relative;
}
.step-content ul li::before {
content: "•";
position: absolute;
left: 0;
color: var(--primary-color);
font-weight: bold;
}
.step-content ul li code {
font-size: 0.7rem;
background: var(--bg-tertiary);
padding: 0.1rem 0.3rem;
border-radius: 3px;
}
/* 并行分支样式 */
.flow-step-branch {
width: 280px;
}
.branch-options {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-top: 0.5rem;
}
.branch-option {
background: var(--bg-tertiary);
padding: 0.75rem;
border-radius: 8px;
border: 1px solid var(--border-color);
}
.branch-label {
font-size: 0.8rem;
font-weight: 600;
color: var(--primary-color);
margin-bottom: 0.5rem;
}
.branch-divider {
text-align: center;
font-size: 0.75rem;
color: var(--text-muted);
font-weight: 500;
position: relative;
}
.branch-divider::before,
.branch-divider::after {
content: "";
position: absolute;
top: 50%;
width: 40%;
height: 1px;
background: var(--border-color);
}
.branch-divider::before {
left: 0;
}
.branch-divider::after {
right: 0;
}
.branch-option ul {
margin: 0;
}
.branch-option ul li {
font-size: 0.7rem;
padding: 0.15rem 0;
}
.flow-arrow {
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
color: var(--primary-color);
font-weight: bold;
padding: 0 0.25rem;
margin-top: 80px;
}
/* 响应式流程图 */
@media (max-width: 1200px) {
.process-flow {
gap: 0.75rem;
}
.flow-step {
width: 180px;
min-height: 180px;
padding: 1rem;
}
.flow-arrow {
font-size: 1.25rem;
margin-top: 70px;
}
}
@media (max-width: 992px) {
.process-flow {
flex-direction: column;
align-items: center;
}
.flow-step {
width: 100%;
max-width: 400px;
min-height: auto;
flex-direction: row;
gap: 1rem;
}
.step-number {
margin-bottom: 0;
flex-shrink: 0;
}
.step-content {
text-align: left;
}
.flow-arrow {
transform: rotate(90deg);
margin-top: 0;
padding: 0.5rem 0;
}
}
/* Guide Panel */
.guide-panel {
background: var(--bg-primary);
padding: 1.5rem;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
margin-bottom: 1.5rem;
border: 1px solid var(--border-color);
}
.guide-panel h3 {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary);
margin: 0 0 1rem 0;
display: flex;
align-items: center;
gap: 0.5rem;
}
.guide-panel h3 i {
color: var(--primary-color);
}
.guide-content {
color: var(--text-secondary);
line-height: 1.6;
}
.guide-content > p {
margin-bottom: 1.5rem;
}
/* Feature Grid */
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.feature-card {
background: var(--bg-secondary);
padding: 1.25rem;
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
transition: var(--transition);
}
.feature-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
border-color: var(--primary-30);
}
.feature-icon {
width: 48px;
height: 48px;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
color: var(--primary-color);
background: var(--primary-10);
margin-bottom: 0.75rem;
}
.feature-card h4 {
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
margin: 0 0 0.5rem 0;
}
.feature-card p {
font-size: 0.875rem;
color: var(--text-secondary);
margin: 0;
}
/* Provider List */
.provider-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
}
.provider-item {
background: var(--bg-secondary);
padding: 1rem;
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
transition: var(--transition);
}
.provider-item:hover {
border-color: var(--primary-30);
box-shadow: var(--shadow-sm);
}
.provider-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
flex-wrap: wrap;
}
.provider-icon {
font-size: 1.25rem;
}
.provider-icon.gemini { color: #4285f4; }
.provider-icon.antigravity { color: #ea4335; }
.provider-icon.kiro { color: #9b59b6; }
.provider-icon.qwen { color: #ff6a00; }
.provider-icon.claude { color: #d97706; }
.provider-icon.openai { color: #10a37f; }
.provider-icon.iflow { color: #3b82f6; }
.provider-icon.orchids { color: #22c55e; }
.provider-name {
font-weight: 600;
color: var(--text-primary);
}
.provider-badge {
font-size: 0.7rem;
padding: 0.15rem 0.5rem;
border-radius: 9999px;
font-weight: 500;
}
.provider-badge.oauth {
background: var(--primary-10);
color: var(--primary-color);
}
.provider-badge.experimental {
background: var(--warning-bg);
color: var(--warning-text);
}
.provider-badge.free {
background: var(--success-bg);
color: var(--success-text);
}
.provider-badge.official {
background: var(--info-bg);
color: var(--info-text);
}
.provider-desc {
font-size: 0.875rem;
color: var(--text-secondary);
margin: 0;
}
/* Client Config List */
.client-config-list {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.client-config-item {
background: var(--bg-secondary);
padding: 1.25rem;
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
}
.client-config-item h4 {
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
margin: 0 0 1rem 0;
display: flex;
align-items: center;
gap: 0.5rem;
}
.client-config-item h4 i {
color: var(--primary-color);
}
.config-steps ol {
margin: 0;
padding-left: 1.5rem;
}
.config-steps ol li {
margin-bottom: 0.5rem;
color: var(--text-secondary);
}
.config-steps ol li:last-child {
margin-bottom: 0;
}
.config-steps code {
background: var(--bg-tertiary);
padding: 0.15rem 0.4rem;
border-radius: var(--radius-sm);
font-size: 0.85rem;
color: var(--primary-color);
}
.config-steps pre {
background: var(--bg-tertiary);
padding: 1rem;
border-radius: var(--radius-md);
overflow-x: auto;
margin: 0;
}
.config-steps pre code {
background: none;
padding: 0;
font-size: 0.8rem;
color: var(--text-primary);
white-space: pre;
}
/* Guide Note */
.guide-note {
display: flex;
align-items: flex-start;
gap: 0.75rem;
background: var(--info-bg);
padding: 1rem;
border-radius: var(--radius-md);
margin-top: 1rem;
border: 1px solid var(--info-border);
}
.guide-note i {
color: var(--info-text);
font-size: 1rem;
margin-top: 0.1rem;
}
.guide-note span {
color: var(--info-text);
font-size: 0.875rem;
line-height: 1.5;
}
/* API Example */
.api-example {
background: var(--bg-secondary);
padding: 1rem;
border-radius: var(--radius-md);
margin-bottom: 1rem;
border: 1px solid var(--border-color);
}
.api-example h4 {
font-size: 0.9rem;
font-weight: 600;
color: var(--text-primary);
margin: 0 0 0.75rem 0;
}
.api-example pre {
background: var(--bg-tertiary);
padding: 1rem;
border-radius: var(--radius-md);
overflow-x: auto;
margin: 0;
}
.api-example pre code {
font-size: 0.8rem;
color: var(--text-primary);
white-space: pre;
}
/* Model Prefix List */
.model-prefix-list {
background: var(--bg-secondary);
padding: 1rem;
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
}
.model-prefix-list h4 {
font-size: 0.9rem;
font-weight: 600;
color: var(--text-primary);
margin: 0 0 0.75rem 0;
}
.model-prefix-list ul {
margin: 0;
padding-left: 1.5rem;
}
.model-prefix-list ul li {
margin-bottom: 0.5rem;
color: var(--text-secondary);
}
.model-prefix-list ul li:last-child {
margin-bottom: 0;
}
.model-prefix-list code {
background: var(--bg-tertiary);
padding: 0.15rem 0.4rem;
border-radius: var(--radius-sm);
font-size: 0.85rem;
color: var(--primary-color);
font-weight: 600;
}
/* FAQ List */
.faq-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.faq-item {
background: var(--bg-secondary);
padding: 1rem;
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
}
.faq-question {
font-weight: 600;
color: var(--text-primary);
margin-bottom: 0.5rem;
}
.faq-answer {
color: var(--text-secondary);
font-size: 0.9rem;
line-height: 1.6;
}
/* Responsive */
@media (max-width: 768px) {
.feature-grid {
grid-template-columns: 1fr;
}
.provider-list {
grid-template-columns: 1fr;
}
.guide-panel {
padding: 1rem;
}
}
/* Dark Theme */
[data-theme="dark"] .guide-panel {
background: var(--bg-primary);
}
[data-theme="dark"] .feature-card,
[data-theme="dark"] .provider-item,
[data-theme="dark"] .client-config-item,
[data-theme="dark"] .api-example,
[data-theme="dark"] .model-prefix-list,
[data-theme="dark"] .faq-item {
background: var(--bg-secondary);
}
[data-theme="dark"] .config-steps pre,
[data-theme="dark"] .api-example pre {
background: var(--bg-tertiary);
}
/* ========================================
可用模型列表样式 ( section-models.css 合并)
======================================== */
/* 模型描述区域 */
.models-description {
margin-bottom: 1.5rem;
}
/* 模型容器 */
.models-container {
background: var(--bg-secondary);
padding: 1.25rem;
border-radius: var(--radius-lg);
border: 1px solid var(--border-color);
}
.models-list {
display: flex;
flex-direction: column;
gap: 1.25rem;
}
/* 加载状态 */
.models-loading {
display: flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
padding: 2.5rem;
color: var(--text-secondary);
font-size: 1rem;
}
.models-loading i {
font-size: 1.25rem;
color: var(--primary-color);
}
/* 提供商模型组 */
.provider-models-group {
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
overflow: hidden;
transition: var(--transition);
background: var(--bg-primary);
}
.provider-models-group:hover {
border-color: var(--primary-color);
box-shadow: var(--shadow-md);
}
/* 提供商标题 */
.provider-models-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.875rem 1.25rem;
background: linear-gradient(135deg, var(--bg-tertiary) 0%, var(--bg-secondary) 100%);
border-bottom: 1px solid var(--border-color);
cursor: pointer;
transition: var(--transition);
}
.provider-models-header:hover {
background: linear-gradient(135deg, var(--primary-10) 0%, var(--bg-tertiary) 100%);
}
.provider-models-title {
display: flex;
align-items: center;
gap: 0.75rem;
}
.provider-models-title i {
font-size: 1.125rem;
color: var(--primary-color);
}
.provider-models-title h3 {
margin: 0;
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
}
.provider-models-count {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 1.5rem;
height: 1.5rem;
padding: 0 0.4rem;
background: var(--primary-color);
color: white;
border-radius: 9999px;
font-size: 0.7rem;
font-weight: 600;
}
.provider-models-toggle {
color: var(--text-secondary);
transition: var(--transition);
}
.provider-models-header.collapsed .provider-models-toggle {
transform: rotate(-90deg);
}
/* 模型列表内容 */
.provider-models-content {
padding: 0.875rem 1.25rem;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 0.625rem;
}
.provider-models-content.collapsed {
display: none;
}
/* 单个模型项 */
.model-item {
display: flex;
align-items: center;
gap: 0.625rem;
padding: 0.625rem 0.875rem;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
cursor: pointer;
transition: var(--transition);
position: relative;
overflow: hidden;
}
.model-item:hover {
border-color: var(--primary-color);
background: var(--primary-10);
transform: translateY(-2px);
box-shadow: var(--shadow-sm);
}
.model-item:active {
transform: translateY(0);
}
.model-item-icon {
display: flex;
align-items: center;
justify-content: center;
width: 1.75rem;
height: 1.75rem;
background: var(--primary-10);
border-radius: var(--radius-sm);
color: var(--primary-color);
flex-shrink: 0;
font-size: 0.75rem;
}
.model-item-name {
flex: 1;
font-size: 0.8rem;
font-weight: 500;
color: var(--text-primary);
word-break: break-all;
}
.model-item-copy {
display: flex;
align-items: center;
justify-content: center;
width: 1.25rem;
height: 1.25rem;
color: var(--text-tertiary);
opacity: 0;
transition: var(--transition);
font-size: 0.75rem;
}
.model-item:hover .model-item-copy {
opacity: 1;
color: var(--primary-color);
}
/* 复制成功动画 */
.model-item.copied {
border-color: var(--success-color);
background: var(--success-10);
}
.model-item.copied .model-item-copy {
opacity: 1;
color: var(--success-color);
}
.model-item.copied::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--success-color);
opacity: 0.1;
animation: copyFlash 0.3s ease-out;
}
@keyframes copyFlash {
0% {
opacity: 0.3;
}
100% {
opacity: 0;
}
}
/* 空状态 */
.models-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2.5rem;
color: var(--text-secondary);
text-align: center;
}
.models-empty i {
font-size: 2.5rem;
margin-bottom: 0.75rem;
opacity: 0.5;
}
.models-empty p {
margin: 0;
font-size: 0.9rem;
}
/* 高亮说明样式 */
.models-description .highlight-note {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.875rem 1rem;
background: linear-gradient(135deg, var(--info-bg) 0%, var(--info-bg-light, var(--info-bg)) 100%);
border: 1px solid var(--info-border);
border-radius: 0.5rem;
color: var(--info-text);
font-weight: 500;
width: 100%;
box-sizing: border-box;
font-size: 0.875rem;
}
.models-description .highlight-note i {
color: var(--info-color, var(--info-text));
font-size: 1.125rem;
flex-shrink: 0;
}
.models-description .highlight-note span {
flex: 1;
text-align: center;
}
/* 模型列表响应式 */
@media (max-width: 768px) {
.provider-models-content {
grid-template-columns: 1fr;
}
.provider-models-header {
padding: 0.75rem 1rem;
}
.provider-models-title h3 {
font-size: 0.9rem;
}
.model-item {
padding: 0.5rem 0.75rem;
}
}
/* 模型列表暗黑主题 */
[data-theme="dark"] .provider-models-group {
background: var(--bg-primary);
border-color: var(--border-color);
}
[data-theme="dark"] .provider-models-header {
background: linear-gradient(135deg, var(--bg-tertiary) 0%, var(--bg-secondary) 100%);
}
[data-theme="dark"] .provider-models-header:hover {
background: linear-gradient(135deg, var(--primary-10) 0%, var(--bg-tertiary) 100%);
}
[data-theme="dark"] .model-item {
background: var(--bg-secondary);
border-color: var(--border-color);
}
[data-theme="dark"] .model-item:hover {
background: var(--primary-10);
border-color: var(--primary-color);
}
[data-theme="dark"] .models-description .highlight-note {
background: linear-gradient(135deg, var(--info-bg) 0%, var(--info-bg-light, var(--info-bg)) 100%);
border-color: var(--info-border);
color: var(--info-text);
}
[data-theme="dark"] .models-container {
background: var(--bg-secondary);
}

View file

@ -0,0 +1,242 @@
<link rel="stylesheet" href="components/section-guide.css">
<!-- Guide Section -->
<section id="guide" class="section" aria-labelledby="guide-title">
<h2 id="guide-title" data-i18n="guide.title">使用指南</h2>
<!-- 项目简介 -->
<div class="guide-panel">
<h3><i class="fas fa-info-circle"></i> <span data-i18n="guide.intro.title">项目简介</span></h3>
<div class="guide-content">
<p data-i18n="guide.intro.desc">AIClient2API 是一个突破客户端限制的 API 代理服务,将 Gemini、Antigravity、Qwen Code、Kiro 等原本仅限客户端内使用的免费大模型,转换为可供任何应用调用的标准 OpenAI 兼容接口。</p>
<div class="feature-grid">
<div class="feature-card">
<div class="feature-icon"><i class="fas fa-plug"></i></div>
<h4 data-i18n="guide.intro.feature1.title">统一接入</h4>
<p data-i18n="guide.intro.feature1.desc">通过标准 OpenAI 兼容协议,一次配置即可接入多种大模型</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fas fa-rocket"></i></div>
<h4 data-i18n="guide.intro.feature2.title">突破限制</h4>
<p data-i18n="guide.intro.feature2.desc">利用 OAuth 授权机制,有效突破免费 API 速率和配额限制</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fas fa-exchange-alt"></i></div>
<h4 data-i18n="guide.intro.feature3.title">协议转换</h4>
<p data-i18n="guide.intro.feature3.desc">支持 OpenAI、Claude、Gemini 三大协议间的智能转换</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fas fa-server"></i></div>
<h4 data-i18n="guide.intro.feature4.title">账号池管理</h4>
<p data-i18n="guide.intro.feature4.desc">支持多账号轮询、自动故障转移和配置降级</p>
</div>
</div>
</div>
</div>
<!-- 操作流程图 -->
<div class="guide-panel">
<h3><i class="fas fa-project-diagram"></i> <span data-i18n="guide.flow.title">操作流程图</span></h3>
<div class="guide-content">
<div class="process-flow">
<div class="flow-step">
<div class="step-number">1</div>
<div class="step-content">
<h4 data-i18n="guide.flow.step1.title">配置管理</h4>
<p data-i18n="guide.flow.step1.desc">在「配置管理」页面设置基本参数</p>
<ul>
<li>设置 API Key</li>
<li>选择启动时初始化的模型提供商</li>
<li>配置高级选项</li>
</ul>
</div>
</div>
<div class="flow-arrow"></div>
<div class="flow-step flow-step-branch">
<div class="step-number">2</div>
<div class="step-content">
<h4 data-i18n="guide.flow.step2.title">生成授权</h4>
<p data-i18n="guide.flow.step2.desc">在「提供商池管理」页面生成 OAuth 授权</p>
<div class="branch-options">
<div class="branch-option">
<div class="branch-label">方式一OAuth 授权</div>
<ul>
<li>点击「生成授权」按钮</li>
<li>在弹窗中完成 OAuth 登录</li>
<li>凭据自动保存</li>
</ul>
</div>
<div class="branch-divider"></div>
<div class="branch-option">
<div class="branch-label">方式二:手动上传</div>
<ul>
<li>新增提供商节点</li>
<li>上传已有的授权文件</li>
<li>手动关联凭据路径</li>
</ul>
</div>
</div>
</div>
</div>
<div class="flow-arrow"></div>
<div class="flow-step">
<div class="step-number">3</div>
<div class="step-content">
<h4 data-i18n="guide.flow.step3.title">管理凭据</h4>
<p data-i18n="guide.flow.step3.desc">在「凭据文件管理」页面查看和管理凭据</p>
<ul>
<li>查看已生成的凭据文件</li>
<li>自动关联到提供商池</li>
<li>删除无效凭据</li>
</ul>
</div>
</div>
<div class="flow-arrow"></div>
<div class="flow-step">
<div class="step-number">4</div>
<div class="step-content">
<h4 data-i18n="guide.flow.step4.title">开始使用</h4>
<p data-i18n="guide.flow.step4.desc">在「仪表盘」查看路由示例并开始调用 API</p>
<ul>
<li>查看路由调用示例</li>
<li>复制 API 端点地址</li>
<li>在客户端中配置使用</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- 客户端配置指南 -->
<div class="guide-panel">
<h3><i class="fas fa-desktop"></i> <span data-i18n="guide.client.title">客户端配置指南</span></h3>
<div class="guide-content">
<p data-i18n="guide.client.desc">以下是常见 AI 客户端的配置方法,将 API 端点设置为本服务地址即可使用:</p>
<div class="client-config-list">
<div class="client-config-item">
<h4><i class="fas fa-cherry"></i> Cherry Studio</h4>
<div class="config-steps">
<ol>
<li data-i18n="guide.client.cherry.step1">打开设置 → 模型服务商</li>
<li data-i18n="guide.client.cherry.step2">添加自定义服务商</li>
<li data-i18n="guide.client.cherry.step3">设置 API 地址为: <code>http://localhost:3000/{provider}/v1</code></li>
<li data-i18n="guide.client.cherry.step4">填入 API Key配置文件中的 REQUIRED_API_KEY</li>
</ol>
</div>
</div>
<div class="client-config-item">
<h4><i class="fas fa-terminal"></i> Cline / Continue</h4>
<div class="config-steps">
<ol>
<li data-i18n="guide.client.cline.step1">打开 VS Code 设置</li>
<li data-i18n="guide.client.cline.step2">搜索 Cline 或 Continue 配置</li>
<li data-i18n="guide.client.cline.step3">设置 API Base URL 为: <code>http://localhost:3000/{provider}/v1</code></li>
<li data-i18n="guide.client.cline.step4">填入 API Key 和模型名称</li>
</ol>
</div>
</div>
<div class="client-config-item">
<h4><i class="fas fa-code"></i> 通用 cURL 调用</h4>
<div class="config-steps">
<pre><code>curl http://localhost:3000/{provider}/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"model": "模型名称",
"messages": [{"role": "user", "content": "Hello!"}],
"max_tokens": 1000
}'</code></pre>
</div>
</div>
</div>
<div class="guide-note">
<i class="fas fa-lightbulb"></i>
<span data-i18n="guide.client.note">提示:将 {provider} 替换为实际的提供商路径,如 gemini-cli-oauth、claude-kiro-oauth 等。可在仪表盘的路由示例中查看完整路径。</span>
</div>
</div>
</div>
<!-- Ollama 协议使用 -->
<div class="guide-panel">
<h3><i class="fas fa-llama"></i> <span data-i18n="guide.ollama.title">Ollama 协议使用</span></h3>
<div class="guide-content">
<p data-i18n="guide.ollama.desc">本项目支持 Ollama 协议,可以通过统一接口访问所有支持的模型。</p>
<div class="api-example">
<h4 data-i18n="guide.ollama.listModels">列出所有可用模型</h4>
<pre><code>curl http://localhost:3000/ollama/api/tags \
-H "Authorization: Bearer YOUR_API_KEY"</code></pre>
</div>
<div class="api-example">
<h4 data-i18n="guide.ollama.chat">聊天接口</h4>
<pre><code>curl http://localhost:3000/ollama/api/chat \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"model": "[Claude] claude-sonnet-4.5",
"messages": [{"role": "user", "content": "你好"}]
}'</code></pre>
</div>
</div>
</div>
<!-- 可用模型列表 -->
<div class="guide-panel">
<h3><i class="fas fa-cube"></i> <span data-i18n="models.title">可用模型列表</span></h3>
<div class="guide-content">
<div class="models-description">
<div class="highlight-note">
<i class="fas fa-info-circle"></i>
<span data-i18n="models.note">点击模型名称可直接复制到剪贴板</span>
</div>
</div>
<!-- Models Container -->
<div class="models-container">
<div id="modelsList" class="models-list">
<!-- Models will be loaded here -->
<div class="models-loading">
<i class="fas fa-spinner fa-spin"></i>
<span data-i18n="common.loading">加载中...</span>
</div>
</div>
</div>
</div>
</div>
<!-- 常见问题 -->
<div class="guide-panel">
<h3><i class="fas fa-question-circle"></i> <span data-i18n="guide.faq.title">常见问题</span></h3>
<div class="guide-content">
<div class="faq-list">
<div class="faq-item">
<div class="faq-question" data-i18n="guide.faq.q1">Q: 请求返回 404 错误怎么办?</div>
<div class="faq-answer" data-i18n="guide.faq.a1">A: 检查接口路径是否正确。某些客户端会自动在 Base URL 后追加路径,导致路径重复。请查看控制台中的实际请求 URL移除多余的路径部分。</div>
</div>
<div class="faq-item">
<div class="faq-question" data-i18n="guide.faq.q2">Q: 请求返回 429 错误怎么办?</div>
<div class="faq-answer" data-i18n="guide.faq.a2">A: 429 表示请求频率过高。建议配置多个账号到提供商池,启用轮询机制;或配置 Fallback 链实现跨类型降级。</div>
</div>
<div class="faq-item">
<div class="faq-question" data-i18n="guide.faq.q3">Q: OAuth 授权失败怎么办?</div>
<div class="faq-answer" data-i18n="guide.faq.a3">A: 确保 OAuth 回调端口可访问Gemini: 8085, Antigravity: 8086, Kiro: 19876-19880。Docker 用户需确保已正确映射这些端口。</div>
</div>
<div class="faq-item">
<div class="faq-question" data-i18n="guide.faq.q5">Q: 流式响应中断怎么办?</div>
<div class="faq-answer" data-i18n="guide.faq.a5">A: 检查网络稳定性,增加客户端请求超时时间。如使用代理,确保代理支持长连接。</div>
</div>
</div>
</div>
</div>
</section>

View file

@ -0,0 +1,476 @@
/* Tutorial Section Styles */
/* 操作流程图样式 */
.process-flow {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
justify-content: center;
gap: 0.5rem;
padding: 1.5rem 0;
}
.flow-step {
display: flex;
flex-direction: column;
align-items: center;
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 1.25rem;
width: 180px;
min-height: 200px;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.flow-step:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
border-color: var(--primary-color);
}
.step-number {
width: 40px;
height: 40px;
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
font-weight: 700;
margin-bottom: 1rem;
box-shadow: 0 4px 12px rgba(var(--primary-rgb, 59, 130, 246), 0.3);
}
.step-content {
text-align: center;
flex: 1;
}
.step-content h4 {
font-size: 0.95rem;
font-weight: 600;
color: var(--text-color);
margin: 0 0 0.5rem 0;
}
.step-content p {
font-size: 0.8rem;
color: var(--text-secondary);
margin: 0 0 0.75rem 0;
line-height: 1.4;
}
.step-content ul {
list-style: none;
padding: 0;
margin: 0;
text-align: left;
}
.step-content ul li {
font-size: 0.75rem;
color: var(--text-secondary);
padding: 0.25rem 0;
padding-left: 1rem;
position: relative;
}
.step-content ul li::before {
content: "•";
position: absolute;
left: 0;
color: var(--primary-color);
font-weight: bold;
}
.step-content ul li code {
font-size: 0.7rem;
background: var(--code-bg);
padding: 0.1rem 0.3rem;
border-radius: 3px;
}
.flow-arrow {
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
color: var(--primary-color);
font-weight: bold;
padding: 0 0.25rem;
margin-top: 80px;
}
/* 响应式流程图 */
@media (max-width: 1200px) {
.process-flow {
gap: 0.75rem;
}
.flow-step {
width: 160px;
min-height: 180px;
padding: 1rem;
}
.flow-arrow {
font-size: 1.25rem;
margin-top: 70px;
}
}
@media (max-width: 992px) {
.process-flow {
flex-direction: column;
align-items: center;
}
.flow-step {
width: 100%;
max-width: 400px;
min-height: auto;
flex-direction: row;
gap: 1rem;
}
.step-number {
margin-bottom: 0;
flex-shrink: 0;
}
.step-content {
text-align: left;
}
.flow-arrow {
transform: rotate(90deg);
margin-top: 0;
padding: 0.5rem 0;
}
}
/* Tutorial Panel */
.tutorial-panel {
background: var(--bg-primary);
padding: 1.5rem;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
margin-bottom: 1.5rem;
border: 1px solid var(--border-color);
}
.tutorial-panel h3 {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary);
margin: 0 0 1rem 0;
display: flex;
align-items: center;
gap: 0.5rem;
}
.tutorial-panel h3 i {
color: var(--primary-color);
}
.tutorial-content {
color: var(--text-secondary);
line-height: 1.6;
}
.tutorial-content > p {
margin-bottom: 1rem;
}
.tutorial-content code {
background: var(--bg-tertiary);
padding: 0.15rem 0.4rem;
border-radius: var(--radius-sm);
font-size: 0.85rem;
color: var(--primary-color);
}
/* Config File List */
.config-file-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.config-file-item {
background: var(--bg-secondary);
padding: 1rem;
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
transition: var(--transition);
}
.config-file-item:hover {
border-color: var(--primary-30);
box-shadow: var(--shadow-sm);
}
.file-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.file-header i {
color: var(--primary-color);
}
.file-name {
font-weight: 600;
color: var(--text-primary);
font-family: monospace;
}
.file-badge {
font-size: 0.65rem;
padding: 0.1rem 0.4rem;
border-radius: 9999px;
font-weight: 500;
margin-left: auto;
}
.file-badge.required {
background: var(--danger-bg);
color: var(--danger-text);
}
.file-badge.optional {
background: var(--info-bg);
color: var(--info-text);
}
.file-desc {
font-size: 0.875rem;
color: var(--text-secondary);
margin: 0;
}
/* Config Section */
.config-section {
margin-bottom: 1.5rem;
}
.config-section:last-child {
margin-bottom: 0;
}
.config-section h4 {
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
margin: 0 0 0.75rem 0;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--border-color);
}
/* Config Table */
.config-table {
overflow-x: auto;
}
.config-table table {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
}
.config-table th,
.config-table td {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid var(--border-color);
}
.config-table th {
background: var(--bg-secondary);
font-weight: 600;
color: var(--text-primary);
}
.config-table td {
color: var(--text-secondary);
}
.config-table td code {
background: var(--bg-tertiary);
padding: 0.15rem 0.4rem;
border-radius: var(--radius-sm);
font-size: 0.8rem;
color: var(--primary-color);
white-space: nowrap;
}
.config-table tr:hover td {
background: var(--bg-secondary);
}
/* Config Example */
.config-example {
background: var(--bg-secondary);
padding: 1rem;
border-radius: var(--radius-md);
margin-top: 1rem;
border: 1px solid var(--border-color);
}
.config-example h4 {
font-size: 0.9rem;
font-weight: 600;
color: var(--text-primary);
margin: 0 0 0.75rem 0;
}
.config-example pre {
background: var(--bg-tertiary);
padding: 1rem;
border-radius: var(--radius-md);
overflow-x: auto;
margin: 0;
}
.config-example pre code {
background: none;
padding: 0;
font-size: 0.8rem;
color: var(--text-primary);
white-space: pre;
}
.config-section pre {
background: var(--bg-tertiary);
padding: 1rem;
border-radius: var(--radius-md);
overflow-x: auto;
margin: 0.75rem 0 0 0;
}
.config-section pre code {
background: none;
padding: 0;
font-size: 0.8rem;
color: var(--text-primary);
white-space: pre;
}
/* Tutorial Note */
.tutorial-note {
display: flex;
align-items: flex-start;
gap: 0.75rem;
background: var(--info-bg);
padding: 1rem;
border-radius: var(--radius-md);
margin-top: 1rem;
border: 1px solid var(--info-border);
}
.tutorial-note i {
color: var(--info-text);
font-size: 1rem;
margin-top: 0.1rem;
}
.tutorial-note span {
color: var(--info-text);
font-size: 0.875rem;
line-height: 1.5;
}
/* OAuth Path List */
.oauth-path-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.oauth-path-item {
background: var(--bg-secondary);
padding: 1rem;
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
}
.path-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.path-header i {
font-size: 1.25rem;
}
.path-header i.fa-gem { color: #4285f4; }
.path-header i.fa-rocket { color: #ea4335; }
.path-header i.fa-robot { color: #9b59b6; }
.path-header i.fa-code { color: #ff6a00; }
.path-provider {
font-weight: 600;
color: var(--text-primary);
}
.path-value {
display: block;
background: var(--bg-tertiary);
padding: 0.5rem 0.75rem;
border-radius: var(--radius-sm);
font-size: 0.8rem;
color: var(--text-primary);
font-family: monospace;
word-break: break-all;
}
/* Responsive */
@media (max-width: 768px) {
.config-file-list {
grid-template-columns: 1fr;
}
.oauth-path-list {
grid-template-columns: 1fr;
}
.tutorial-panel {
padding: 1rem;
}
.config-table th,
.config-table td {
padding: 0.5rem;
font-size: 0.8rem;
}
}
/* Dark Theme */
[data-theme="dark"] .tutorial-panel {
background: var(--bg-primary);
}
[data-theme="dark"] .config-file-item,
[data-theme="dark"] .config-example,
[data-theme="dark"] .oauth-path-item {
background: var(--bg-secondary);
}
[data-theme="dark"] .config-table th {
background: var(--bg-secondary);
}
[data-theme="dark"] .config-example pre,
[data-theme="dark"] .config-section pre {
background: var(--bg-tertiary);
}

View file

@ -0,0 +1,443 @@
<link rel="stylesheet" href="components/section-tutorial.css">
<!-- Tutorial Section -->
<section id="tutorial" class="section" aria-labelledby="tutorial-title">
<h2 id="tutorial-title" data-i18n="tutorial.title">配置教程</h2>
<!-- 配置文件说明 -->
<div class="tutorial-panel">
<h3><i class="fas fa-file-code"></i> <span data-i18n="tutorial.config.title">配置文件说明</span></h3>
<div class="tutorial-content">
<p data-i18n="tutorial.config.desc">所有配置文件都存放在 <code>configs/</code> 目录下。主要配置文件包括:</p>
<div class="config-file-list">
<div class="config-file-item">
<div class="file-header">
<i class="fas fa-file-alt"></i>
<span class="file-name">config.json</span>
<span class="file-badge required" data-i18n="tutorial.config.badge.required">必需</span>
</div>
<p class="file-desc" data-i18n="tutorial.config.file.config">主配置文件,包含 API Key、端口、模型提供商等核心设置</p>
</div>
<div class="config-file-item">
<div class="file-header">
<i class="fas fa-file-alt"></i>
<span class="file-name">provider_pools.json</span>
<span class="file-badge required" data-i18n="tutorial.config.badge.required">必需</span>
</div>
<p class="file-desc" data-i18n="tutorial.config.file.pools">提供商池配置,用于多账号轮询和故障转移</p>
</div>
<div class="config-file-item">
<div class="file-header">
<i class="fas fa-file-alt"></i>
<span class="file-name">plugins.json</span>
<span class="file-badge optional" data-i18n="tutorial.config.badge.optional">可选</span>
</div>
<p class="file-desc" data-i18n="tutorial.config.file.plugins">插件配置,用于启用或禁用系统插件</p>
</div>
<div class="config-file-item">
<div class="file-header">
<i class="fas fa-lock"></i>
<span class="file-name">pwd</span>
<span class="file-badge optional" data-i18n="tutorial.config.badge.optional">可选</span>
</div>
<p class="file-desc" data-i18n="tutorial.config.file.pwd">后台登录密码文件,默认密码为 admin123</p>
</div>
</div>
</div>
</div>
<!-- 主配置详解 -->
<div class="tutorial-panel">
<h3><i class="fas fa-cogs"></i> <span data-i18n="tutorial.main.title">主配置详解 (config.json)</span></h3>
<div class="tutorial-content">
<div class="config-section">
<h4 data-i18n="tutorial.main.basic.title">基础配置</h4>
<div class="config-table">
<table>
<thead>
<tr>
<th data-i18n="tutorial.main.table.param">参数</th>
<th data-i18n="tutorial.main.table.type">类型</th>
<th data-i18n="tutorial.main.table.default">默认值</th>
<th data-i18n="tutorial.main.table.desc">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>REQUIRED_API_KEY</code></td>
<td>string</td>
<td>-</td>
<td data-i18n="tutorial.main.basic.apikey">访问本服务所需的 API Key</td>
</tr>
<tr>
<td><code>SERVER_PORT</code></td>
<td>number</td>
<td>3000</td>
<td data-i18n="tutorial.main.basic.port">服务监听端口</td>
</tr>
<tr>
<td><code>HOST</code></td>
<td>string</td>
<td>0.0.0.0</td>
<td data-i18n="tutorial.main.basic.host">服务监听地址</td>
</tr>
<tr>
<td><code>MODEL_PROVIDER</code></td>
<td>string</td>
<td>-</td>
<td data-i18n="tutorial.main.basic.provider">默认模型提供商</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="config-section">
<h4 data-i18n="tutorial.main.prompt.title">系统提示配置</h4>
<div class="config-table">
<table>
<thead>
<tr>
<th data-i18n="tutorial.main.table.param">参数</th>
<th data-i18n="tutorial.main.table.type">类型</th>
<th data-i18n="tutorial.main.table.default">默认值</th>
<th data-i18n="tutorial.main.table.desc">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>SYSTEM_PROMPT_FILE_PATH</code></td>
<td>string</td>
<td>-</td>
<td data-i18n="tutorial.main.prompt.file">系统提示文件路径</td>
</tr>
<tr>
<td><code>SYSTEM_PROMPT_MODE</code></td>
<td>string</td>
<td>overwrite</td>
<td data-i18n="tutorial.main.prompt.mode">系统提示模式overwrite(覆盖) 或 append(追加)</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="config-section">
<h4 data-i18n="tutorial.main.retry.title">重试配置</h4>
<div class="config-table">
<table>
<thead>
<tr>
<th data-i18n="tutorial.main.table.param">参数</th>
<th data-i18n="tutorial.main.table.type">类型</th>
<th data-i18n="tutorial.main.table.default">默认值</th>
<th data-i18n="tutorial.main.table.desc">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>REQUEST_MAX_RETRIES</code></td>
<td>number</td>
<td>3</td>
<td data-i18n="tutorial.main.retry.max">最大重试次数</td>
</tr>
<tr>
<td><code>REQUEST_BASE_DELAY</code></td>
<td>number</td>
<td>1000</td>
<td data-i18n="tutorial.main.retry.delay">重试基础延迟(毫秒)</td>
</tr>
<tr>
<td><code>MAX_ERROR_COUNT</code></td>
<td>number</td>
<td>3</td>
<td data-i18n="tutorial.main.retry.error">提供商最大错误次数,超过后标记为不健康</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="config-example">
<h4 data-i18n="tutorial.main.example.title">配置示例</h4>
<pre><code>{
"REQUIRED_API_KEY": "your-api-key",
"SERVER_PORT": 3000,
"HOST": "0.0.0.0",
"MODEL_PROVIDER": "gemini-cli-oauth",
"SYSTEM_PROMPT_FILE_PATH": "configs/input_system_prompt.txt",
"SYSTEM_PROMPT_MODE": "overwrite",
"REQUEST_MAX_RETRIES": 3,
"REQUEST_BASE_DELAY": 1000,
"MAX_ERROR_COUNT": 3,
"PROVIDER_POOLS_FILE_PATH": "configs/provider_pools.json"
}</code></pre>
</div>
</div>
</div>
<!-- 提供商池配置 -->
<div class="tutorial-panel">
<h3><i class="fas fa-layer-group"></i> <span data-i18n="tutorial.pool.title">提供商池配置 (provider_pools.json)</span></h3>
<div class="tutorial-content">
<p data-i18n="tutorial.pool.desc">提供商池用于配置多个账号,实现负载均衡和故障转移。每个提供商类型可以配置多个账号节点。</p>
<div class="config-section">
<h4 data-i18n="tutorial.pool.node.title">节点配置参数</h4>
<div class="config-table">
<table>
<thead>
<tr>
<th data-i18n="tutorial.main.table.param">参数</th>
<th data-i18n="tutorial.main.table.type">类型</th>
<th data-i18n="tutorial.main.table.desc">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>uuid</code></td>
<td>string</td>
<td data-i18n="tutorial.pool.node.uuid">节点唯一标识,自动生成</td>
</tr>
<tr>
<td><code>name</code></td>
<td>string</td>
<td data-i18n="tutorial.pool.node.name">节点自定义名称</td>
</tr>
<tr>
<td><code>oauthCredsFilePath</code></td>
<td>string</td>
<td data-i18n="tutorial.pool.node.oauth">OAuth 凭据文件路径</td>
</tr>
<tr>
<td><code>checkHealth</code></td>
<td>boolean</td>
<td data-i18n="tutorial.pool.node.health">是否启用健康检查</td>
</tr>
<tr>
<td><code>checkModel</code></td>
<td>string</td>
<td data-i18n="tutorial.pool.node.model">健康检查使用的模型</td>
</tr>
<tr>
<td><code>notSupportedModels</code></td>
<td>array</td>
<td data-i18n="tutorial.pool.node.unsupported">该节点不支持的模型列表</td>
</tr>
<tr>
<td><code>disabled</code></td>
<td>boolean</td>
<td data-i18n="tutorial.pool.node.disabled">是否禁用该节点</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="config-example">
<h4 data-i18n="tutorial.pool.example.title">配置示例</h4>
<pre><code>{
"gemini-cli-oauth": [
{
"uuid": "gemini-account-1",
"name": "Gemini 账号 1",
"oauthCredsFilePath": "configs/gemini/oauth_creds_1.json",
"checkHealth": true,
"checkModel": "gemini-2.0-flash-exp"
},
{
"uuid": "gemini-account-2",
"name": "Gemini 账号 2",
"oauthCredsFilePath": "configs/gemini/oauth_creds_2.json",
"checkHealth": true,
"notSupportedModels": ["gemini-3.0-pro"]
}
],
"claude-kiro-oauth": [
{
"uuid": "kiro-account-1",
"name": "Kiro 账号 1",
"oauthCredsFilePath": "configs/kiro/kiro-auth-token.json",
"checkHealth": true,
"checkModel": "claude-sonnet-4-5"
}
]
}</code></pre>
</div>
</div>
</div>
<!-- Fallback 配置 -->
<div class="tutorial-panel">
<h3><i class="fas fa-random"></i> <span data-i18n="tutorial.fallback.title">Fallback 降级配置</span></h3>
<div class="tutorial-content">
<p data-i18n="tutorial.fallback.desc">当某一提供商类型的所有账号都不可用时,可以自动切换到配置的备用提供商。</p>
<div class="config-section">
<h4 data-i18n="tutorial.fallback.chain.title">跨类型 Fallback 链</h4>
<p data-i18n="tutorial.fallback.chain.desc">在 config.json 中配置 providerFallbackChain指定每个提供商类型的备用类型</p>
<pre><code>{
"providerFallbackChain": {
"gemini-cli-oauth": ["gemini-antigravity"],
"gemini-antigravity": ["gemini-cli-oauth"],
"claude-kiro-oauth": ["claude-custom"],
"claude-custom": ["claude-kiro-oauth"]
}
}</code></pre>
</div>
<div class="config-section">
<h4 data-i18n="tutorial.fallback.model.title">跨协议模型映射</h4>
<p data-i18n="tutorial.fallback.model.desc">当主提供商不可用时,可以将特定模型映射到其他协议的提供商:</p>
<pre><code>{
"modelFallbackMapping": {
"gemini-claude-opus-4-5-thinking": {
"targetProviderType": "claude-kiro-oauth",
"targetModel": "claude-opus-4-5"
},
"claude-opus-4-5": {
"targetProviderType": "gemini-antigravity",
"targetModel": "gemini-claude-opus-4-5-thinking"
}
}
}</code></pre>
</div>
</div>
</div>
<!-- 代理配置 -->
<div class="tutorial-panel">
<h3><i class="fas fa-globe"></i> <span data-i18n="tutorial.proxy.title">代理配置</span></h3>
<div class="tutorial-content">
<p data-i18n="tutorial.proxy.desc">支持为特定提供商配置代理,用于网络受限环境。</p>
<div class="config-section">
<h4 data-i18n="tutorial.proxy.config.title">代理配置参数</h4>
<div class="config-table">
<table>
<thead>
<tr>
<th data-i18n="tutorial.main.table.param">参数</th>
<th data-i18n="tutorial.main.table.type">类型</th>
<th data-i18n="tutorial.main.table.desc">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>PROXY_URL</code></td>
<td>string</td>
<td data-i18n="tutorial.proxy.url">代理地址,支持 HTTP、HTTPS、SOCKS5</td>
</tr>
<tr>
<td><code>PROXY_ENABLED_PROVIDERS</code></td>
<td>array</td>
<td data-i18n="tutorial.proxy.providers">启用代理的提供商列表</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="config-example">
<h4 data-i18n="tutorial.proxy.example.title">配置示例</h4>
<pre><code>{
"PROXY_URL": "http://127.0.0.1:7890",
"PROXY_ENABLED_PROVIDERS": [
"gemini-cli-oauth",
"gemini-antigravity"
]
}</code></pre>
</div>
<div class="tutorial-note">
<i class="fas fa-info-circle"></i>
<span data-i18n="tutorial.proxy.note">支持的代理类型HTTP (http://)、HTTPS (https://)、SOCKS5 (socks5://)</span>
</div>
</div>
</div>
<!-- OAuth 授权配置 -->
<div class="tutorial-panel">
<h3><i class="fas fa-key"></i> <span data-i18n="tutorial.oauth.title">OAuth 授权配置</span></h3>
<div class="tutorial-content">
<p data-i18n="tutorial.oauth.desc">各提供商的 OAuth 凭据文件默认存储位置:</p>
<div class="oauth-path-list">
<div class="oauth-path-item">
<div class="path-header">
<i class="fas fa-gem"></i>
<span class="path-provider">Gemini</span>
</div>
<code class="path-value">~/.gemini/oauth_creds.json</code>
</div>
<div class="oauth-path-item">
<div class="path-header">
<i class="fas fa-rocket"></i>
<span class="path-provider">Antigravity</span>
</div>
<code class="path-value">~/.antigravity/oauth_creds.json</code>
</div>
<div class="oauth-path-item">
<div class="path-header">
<i class="fas fa-robot"></i>
<span class="path-provider">Kiro</span>
</div>
<code class="path-value">~/.aws/sso/cache/kiro-auth-token.json</code>
</div>
<div class="oauth-path-item">
<div class="path-header">
<i class="fas fa-code"></i>
<span class="path-provider">Qwen</span>
</div>
<code class="path-value">~/.qwen/oauth_creds.json</code>
</div>
</div>
<div class="tutorial-note">
<i class="fas fa-lightbulb"></i>
<span data-i18n="tutorial.oauth.note">推荐通过 Web UI 控制台的"提供商池管理"页面点击"生成授权"按钮进行可视化授权,系统会自动保存凭据文件。</span>
</div>
</div>
</div>
<!-- 日志配置 -->
<div class="tutorial-panel">
<h3><i class="fas fa-file-alt"></i> <span data-i18n="tutorial.log.title">日志配置</span></h3>
<div class="tutorial-content">
<div class="config-section">
<h4 data-i18n="tutorial.log.prompt.title">提示日志配置</h4>
<div class="config-table">
<table>
<thead>
<tr>
<th data-i18n="tutorial.main.table.param">参数</th>
<th data-i18n="tutorial.main.table.type">类型</th>
<th data-i18n="tutorial.main.table.desc">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>PROMPT_LOG_MODE</code></td>
<td>string</td>
<td data-i18n="tutorial.log.mode">日志模式none(关闭)、console(控制台)、file(文件)</td>
</tr>
<tr>
<td><code>PROMPT_LOG_BASE_NAME</code></td>
<td>string</td>
<td data-i18n="tutorial.log.basename">日志文件基础名称</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="config-example">
<h4 data-i18n="tutorial.log.example.title">配置示例</h4>
<pre><code>{
"PROMPT_LOG_MODE": "file",
"PROMPT_LOG_BASE_NAME": "prompt_log"
}</code></pre>
</div>
</div>
</div>
</section>

View file

@ -5,6 +5,12 @@
<a href="#dashboard" class="nav-item active" data-section="dashboard" aria-label="Dashboard" data-i18n-aria-label="nav.dashboard">
<i class="fas fa-tachometer-alt" aria-hidden="true"></i> <span data-i18n="nav.dashboard">仪表盘</span>
</a>
<a href="#guide" class="nav-item" data-section="guide" aria-label="User Guide" data-i18n-aria-label="nav.guide">
<i class="fas fa-book" aria-hidden="true"></i> <span data-i18n="nav.guide">使用指南</span>
</a>
<a href="#tutorial" class="nav-item" data-section="tutorial" aria-label="Configuration Tutorial" data-i18n-aria-label="nav.tutorial">
<i class="fas fa-graduation-cap" aria-hidden="true"></i> <span data-i18n="nav.tutorial">配置教程</span>
</a>
<a href="#config" class="nav-item" data-section="config" aria-label="Config Management" data-i18n-aria-label="nav.config">
<i class="fas fa-cog" aria-hidden="true"></i> <span data-i18n="nav.config">配置管理</span>
</a>

View file

@ -36,6 +36,7 @@
<script type="module" src="app/language-switcher.js"></script>
<script type="module" src="app/theme-switcher.js"></script>
<script type="module" src="app/auth.js"></script>
<script type="module" src="app/models-manager.js"></script>
<script type="module">
// 导入组件加载器
import { initializeComponents } from './app/component-loader.js';