diff --git a/static/app/component-loader.js b/static/app/component-loader.js index 116d13c..52768ed 100644 --- a/static/app/component-loader.js +++ b/static/app/component-loader.js @@ -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' }, diff --git a/static/app/i18n.js b/static/app/i18n.js index cb472a9..3fd2789 100644 --- a/static/app/i18n.js +++ b/static/app/i18n.js @@ -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', diff --git a/static/app/models-manager.js b/static/app/models-manager.js new file mode 100644 index 0000000..d0b7399 --- /dev/null +++ b/static/app/models-manager.js @@ -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} 模型数据 + */ +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} 是否复制成功 + */ +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 = ` + + ${t('models.copied') || '已复制'}: ${modelName} + `; + + 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 = ` +
+ +

${t('models.empty') || '暂无可用模型'}

+
+ `; + 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 += ` +
+
+
+ +

${providerDisplayName}

+ ${modelList.length} +
+
+ +
+
+
+ ${modelList.map(model => ` +
+
+ +
+ ${escapeHtml(model)} +
+ +
+
+ `).join('')} +
+
+ `; + } + + 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 = ` +
+ +

${t('models.loadError') || '加载模型列表失败'}

+
+ `; + } +} + +/** + * 刷新模型列表 + */ +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 +}; \ No newline at end of file diff --git a/static/components/section-guide.css b/static/components/section-guide.css new file mode 100644 index 0000000..6265ed6 --- /dev/null +++ b/static/components/section-guide.css @@ -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); +} \ No newline at end of file diff --git a/static/components/section-guide.html b/static/components/section-guide.html new file mode 100644 index 0000000..99399be --- /dev/null +++ b/static/components/section-guide.html @@ -0,0 +1,242 @@ + + +
+

使用指南

+ + +
+

项目简介

+
+

AIClient2API 是一个突破客户端限制的 API 代理服务,将 Gemini、Antigravity、Qwen Code、Kiro 等原本仅限客户端内使用的免费大模型,转换为可供任何应用调用的标准 OpenAI 兼容接口。

+
+
+
+

统一接入

+

通过标准 OpenAI 兼容协议,一次配置即可接入多种大模型

+
+
+
+

突破限制

+

利用 OAuth 授权机制,有效突破免费 API 速率和配额限制

+
+
+
+

协议转换

+

支持 OpenAI、Claude、Gemini 三大协议间的智能转换

+
+
+
+

账号池管理

+

支持多账号轮询、自动故障转移和配置降级

+
+
+
+
+ + +
+

操作流程图

+
+
+
+
1
+
+

配置管理

+

在「配置管理」页面设置基本参数

+
    +
  • 设置 API Key
  • +
  • 选择启动时初始化的模型提供商
  • +
  • 配置高级选项
  • +
+
+
+ +
+ +
+
2
+
+

生成授权

+

在「提供商池管理」页面生成 OAuth 授权

+
+
+
方式一:OAuth 授权
+
    +
  • 点击「生成授权」按钮
  • +
  • 在弹窗中完成 OAuth 登录
  • +
  • 凭据自动保存
  • +
+
+
+
+
方式二:手动上传
+
    +
  • 新增提供商节点
  • +
  • 上传已有的授权文件
  • +
  • 手动关联凭据路径
  • +
+
+
+
+
+ +
+ +
+
3
+
+

管理凭据

+

在「凭据文件管理」页面查看和管理凭据

+
    +
  • 查看已生成的凭据文件
  • +
  • 自动关联到提供商池
  • +
  • 删除无效凭据
  • +
+
+
+ +
+ +
+
4
+
+

开始使用

+

在「仪表盘」查看路由示例并开始调用 API

+
    +
  • 查看路由调用示例
  • +
  • 复制 API 端点地址
  • +
  • 在客户端中配置使用
  • +
+
+
+
+
+
+ + +
+

客户端配置指南

+
+

以下是常见 AI 客户端的配置方法,将 API 端点设置为本服务地址即可使用:

+ +
+
+

Cherry Studio

+
+
    +
  1. 打开设置 → 模型服务商
  2. +
  3. 添加自定义服务商
  4. +
  5. 设置 API 地址为: http://localhost:3000/{provider}/v1
  6. +
  7. 填入 API Key(配置文件中的 REQUIRED_API_KEY)
  8. +
+
+
+ +
+

Cline / Continue

+
+
    +
  1. 打开 VS Code 设置
  2. +
  3. 搜索 Cline 或 Continue 配置
  4. +
  5. 设置 API Base URL 为: http://localhost:3000/{provider}/v1
  6. +
  7. 填入 API Key 和模型名称
  8. +
+
+
+ +
+

通用 cURL 调用

+
+
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
+  }'
+
+
+
+ +
+ + 提示:将 {provider} 替换为实际的提供商路径,如 gemini-cli-oauth、claude-kiro-oauth 等。可在仪表盘的路由示例中查看完整路径。 +
+
+
+ + +
+

Ollama 协议使用

+
+

本项目支持 Ollama 协议,可以通过统一接口访问所有支持的模型。

+ +
+

列出所有可用模型

+
curl http://localhost:3000/ollama/api/tags \
+  -H "Authorization: Bearer YOUR_API_KEY"
+
+ +
+

聊天接口

+
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": "你好"}]
+  }'
+
+
+
+ + +
+

可用模型列表

+
+
+
+ + 点击模型名称可直接复制到剪贴板 +
+
+ + +
+
+ +
+ + 加载中... +
+
+
+
+
+ + +
+

常见问题

+
+
+
+
Q: 请求返回 404 错误怎么办?
+
A: 检查接口路径是否正确。某些客户端会自动在 Base URL 后追加路径,导致路径重复。请查看控制台中的实际请求 URL,移除多余的路径部分。
+
+
+
Q: 请求返回 429 错误怎么办?
+
A: 429 表示请求频率过高。建议配置多个账号到提供商池,启用轮询机制;或配置 Fallback 链实现跨类型降级。
+
+
+
Q: OAuth 授权失败怎么办?
+
A: 确保 OAuth 回调端口可访问(Gemini: 8085, Antigravity: 8086, Kiro: 19876-19880)。Docker 用户需确保已正确映射这些端口。
+
+
+
Q: 流式响应中断怎么办?
+
A: 检查网络稳定性,增加客户端请求超时时间。如使用代理,确保代理支持长连接。
+
+
+
+
+
\ No newline at end of file diff --git a/static/components/section-tutorial.css b/static/components/section-tutorial.css new file mode 100644 index 0000000..b0c28c6 --- /dev/null +++ b/static/components/section-tutorial.css @@ -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); +} \ No newline at end of file diff --git a/static/components/section-tutorial.html b/static/components/section-tutorial.html new file mode 100644 index 0000000..fd2d73f --- /dev/null +++ b/static/components/section-tutorial.html @@ -0,0 +1,443 @@ + + +
+

配置教程

+ + +
+

配置文件说明

+
+

所有配置文件都存放在 configs/ 目录下。主要配置文件包括:

+ +
+
+
+ + config.json + 必需 +
+

主配置文件,包含 API Key、端口、模型提供商等核心设置

+
+
+
+ + provider_pools.json + 必需 +
+

提供商池配置,用于多账号轮询和故障转移

+
+
+
+ + plugins.json + 可选 +
+

插件配置,用于启用或禁用系统插件

+
+
+
+ + pwd + 可选 +
+

后台登录密码文件,默认密码为 admin123

+
+
+
+
+ + +
+

主配置详解 (config.json)

+
+
+

基础配置

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
参数类型默认值说明
REQUIRED_API_KEYstring-访问本服务所需的 API Key
SERVER_PORTnumber3000服务监听端口
HOSTstring0.0.0.0服务监听地址
MODEL_PROVIDERstring-默认模型提供商
+
+
+ +
+

系统提示配置

+
+ + + + + + + + + + + + + + + + + + + + + + + +
参数类型默认值说明
SYSTEM_PROMPT_FILE_PATHstring-系统提示文件路径
SYSTEM_PROMPT_MODEstringoverwrite系统提示模式:overwrite(覆盖) 或 append(追加)
+
+
+ +
+

重试配置

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
参数类型默认值说明
REQUEST_MAX_RETRIESnumber3最大重试次数
REQUEST_BASE_DELAYnumber1000重试基础延迟(毫秒)
MAX_ERROR_COUNTnumber3提供商最大错误次数,超过后标记为不健康
+
+
+ +
+

配置示例

+
{
+    "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"
+}
+
+
+
+ + +
+

提供商池配置 (provider_pools.json)

+
+

提供商池用于配置多个账号,实现负载均衡和故障转移。每个提供商类型可以配置多个账号节点。

+ +
+

节点配置参数

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
参数类型说明
uuidstring节点唯一标识,自动生成
namestring节点自定义名称
oauthCredsFilePathstringOAuth 凭据文件路径
checkHealthboolean是否启用健康检查
checkModelstring健康检查使用的模型
notSupportedModelsarray该节点不支持的模型列表
disabledboolean是否禁用该节点
+
+
+ +
+

配置示例

+
{
+    "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"
+        }
+    ]
+}
+
+
+
+ + +
+

Fallback 降级配置

+
+

当某一提供商类型的所有账号都不可用时,可以自动切换到配置的备用提供商。

+ +
+

跨类型 Fallback 链

+

在 config.json 中配置 providerFallbackChain,指定每个提供商类型的备用类型:

+
{
+    "providerFallbackChain": {
+        "gemini-cli-oauth": ["gemini-antigravity"],
+        "gemini-antigravity": ["gemini-cli-oauth"],
+        "claude-kiro-oauth": ["claude-custom"],
+        "claude-custom": ["claude-kiro-oauth"]
+    }
+}
+
+ +
+

跨协议模型映射

+

当主提供商不可用时,可以将特定模型映射到其他协议的提供商:

+
{
+    "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"
+        }
+    }
+}
+
+
+
+ + +
+

代理配置

+
+

支持为特定提供商配置代理,用于网络受限环境。

+ +
+

代理配置参数

+
+ + + + + + + + + + + + + + + + + + + + +
参数类型说明
PROXY_URLstring代理地址,支持 HTTP、HTTPS、SOCKS5
PROXY_ENABLED_PROVIDERSarray启用代理的提供商列表
+
+
+ +
+

配置示例

+
{
+    "PROXY_URL": "http://127.0.0.1:7890",
+    "PROXY_ENABLED_PROVIDERS": [
+        "gemini-cli-oauth",
+        "gemini-antigravity"
+    ]
+}
+
+ +
+ + 支持的代理类型:HTTP (http://)、HTTPS (https://)、SOCKS5 (socks5://) +
+
+
+ + +
+

OAuth 授权配置

+
+

各提供商的 OAuth 凭据文件默认存储位置:

+ +
+
+
+ + Gemini +
+ ~/.gemini/oauth_creds.json +
+
+
+ + Antigravity +
+ ~/.antigravity/oauth_creds.json +
+
+
+ + Kiro +
+ ~/.aws/sso/cache/kiro-auth-token.json +
+
+
+ + Qwen +
+ ~/.qwen/oauth_creds.json +
+
+ +
+ + 推荐通过 Web UI 控制台的"提供商池管理"页面点击"生成授权"按钮进行可视化授权,系统会自动保存凭据文件。 +
+
+
+ + +
+

日志配置

+
+
+

提示日志配置

+
+ + + + + + + + + + + + + + + + + + + + +
参数类型说明
PROMPT_LOG_MODEstring日志模式:none(关闭)、console(控制台)、file(文件)
PROMPT_LOG_BASE_NAMEstring日志文件基础名称
+
+
+ +
+

配置示例

+
{
+    "PROMPT_LOG_MODE": "file",
+    "PROMPT_LOG_BASE_NAME": "prompt_log"
+}
+
+
+
+
\ No newline at end of file diff --git a/static/components/sidebar.html b/static/components/sidebar.html index e9beb2c..7d5c9d3 100644 --- a/static/components/sidebar.html +++ b/static/components/sidebar.html @@ -5,6 +5,12 @@ 仪表盘 + + 使用指南 + + + 配置教程 + 配置管理 diff --git a/static/index.html b/static/index.html index 73c2a53..8e28fc8 100644 --- a/static/index.html +++ b/static/index.html @@ -36,6 +36,7 @@ +