diff --git a/README.md b/README.md index ce8f9dc..ab91888 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ## ✨ 功能特性 - 🚀 **基于 Cloudflare Workers**:全球分布式网络、低延迟、高性能 -- 🔐 **多渠道**:支持 OpenAI、Azure OpenAI、Claude 等多种 AI 服务商 +- 🔐 **多渠道**:支持 OpenAI、Gemini、Azure OpenAI、Claude 等多种 AI 服务商 - 🎫 **负载均衡**:支持多KEY/渠道间分配请求,自动重试+自动轮换,确保高可用性 - 📊 **用量统计***:Analytics Engine 实时统计,精确成本核算与日志回溯 - 💰 **定价管理**:灵活的模型定价配置 @@ -57,7 +57,7 @@ one-api-workers/ │ │ └── index.ts # 管理接口路由 │ ├── providers/ # AI 服务商 │ │ ├── azure-openai-proxy.ts # Azure OpenAI 代理 -│ │ ├── openai-proxy.ts # OpenAI 代理 +│ │ ├── openai-proxy.ts # OpenAI / Gemini 兼容代理 │ │ ├── claude-proxy.ts # Claude 代理 │ │ ├── openai-responses-proxy.ts # OpenAI Responses 代理 │ │ ├── azure-openai-responses-proxy.ts # Azure Responses 代理 @@ -286,6 +286,23 @@ curl https://your-domain.com/v1/responses \ } ``` +**Gemini 配置** + +Gemini 通过官方 OpenAI 兼容层接入,建议将 `endpoint` 配置为 `https://generativelanguage.googleapis.com/v1beta/openai/`。 + +```json +{ + "name": "My Gemini Channel", + "type": "gemini", + "endpoint": "https://generativelanguage.googleapis.com/v1beta/openai/", + "api_key": "AIza-your-gemini-api-key", + "deployment_mapper": { + "gemini-2.5-flash": "gemini-2.5-flash", + "gemini-2.5-pro": "gemini-2.5-pro" + } +} +``` + **Azure OpenAI 配置** ```json @@ -348,7 +365,7 @@ curl https://your-domain.com/v1/responses \ **配置字段说明**: - `name`: 渠道显示名称 -- `type`: 服务商类型(`openai`、`azure-openai`、`claude`、`openai-responses`、`azure-openai-responses`) +- `type`: 服务商类型(`openai`、`gemini`、`azure-openai`、`claude`、`openai-responses`、`azure-openai-responses`) - `endpoint`: API 端点地址 - `api_key`: API 密钥 - `api_version`: API 版本(Azure OpenAI / Claude 可用;Azure Responses v1 请留空) diff --git a/frontend/src/pages/Channels.tsx b/frontend/src/pages/Channels.tsx index 6222da5..c336802 100644 --- a/frontend/src/pages/Channels.tsx +++ b/frontend/src/pages/Channels.tsx @@ -35,12 +35,13 @@ type ModelRow = { id: string; name: string } const channelTypes = [ { value: 'openai', label: 'OpenAI' }, + { value: 'openai-responses', label: 'OpenAI Responses' }, + { value: 'claude', label: 'Claude' }, + { value: 'gemini', label: 'Gemini' }, + { value: 'openai-audio', label: 'OpenAI Audio' }, + { value: 'claude-to-openai', label: 'Claude → OpenAI' }, { value: 'azure-openai', label: 'Azure OpenAI' }, { value: 'azure-openai-audio', label: 'Azure OpenAI Audio' }, - { value: 'openai-audio', label: 'OpenAI Audio' }, - { value: 'claude', label: 'Claude' }, - { value: 'claude-to-openai', label: 'Claude → OpenAI' }, - { value: 'openai-responses', label: 'OpenAI Responses' }, { value: 'azure-openai-responses', label: 'Azure OpenAI Responses' }, ] @@ -283,6 +284,10 @@ const getChannelRequestPreviewPath = (type: string | undefined): string => { } } +const isGeminiChannelType = (type: string | undefined): boolean => { + return type === 'gemini' +} + const isOpenAIStyleChannelType = (type: string | undefined): boolean => { return ['openai', 'openai-audio', 'openai-responses', 'claude-to-openai'].includes(type || '') } @@ -335,11 +340,42 @@ const buildClaudeEndpointPreview = (endpoint: string, requestPath: string): stri return buildPrefixedEndpointPreview(endpoint, requestPath) } +const buildGeminiEndpointPreview = (endpoint: string, requestPath: string): string => { + const targetUrl = new URL(endpoint) + const currentBasePath = trimSlashes(targetUrl.pathname) + const normalizedRequestPath = requestPath.replace(/^\/v1(?=\/|$)/, '') + const geminiBasePath = currentBasePath.endsWith('v1beta/openai') + ? currentBasePath + : currentBasePath.endsWith('v1beta') + ? joinPath(currentBasePath, 'openai') + : currentBasePath.endsWith('openai') + ? currentBasePath + : joinPath(currentBasePath, 'v1beta/openai') + + targetUrl.pathname = joinPath(geminiBasePath, normalizedRequestPath) + return targetUrl.toString() +} + const buildFallbackEndpointPreview = (endpoint: string, requestPath: string): string => { const normalizedEndpoint = endpoint.trim().replace(/\/+$/, '') return `${normalizedEndpoint}${requestPath}` } +const getChannelEndpointPlaceholder = (type: string | undefined): string => { + switch (type) { + case 'gemini': + return 'https://generativelanguage.googleapis.com/v1beta/openai/' + case 'azure-openai': + case 'azure-openai-audio': + case 'azure-openai-responses': + return 'https://your-resource.openai.azure.com/' + case 'claude': + return 'https://api.anthropic.com/v1/' + default: + return 'https://api.openai.com/v1/' + } +} + const getChannelEndpointPreview = (type: string | undefined, endpoint: string): string => { const trimmedEndpoint = endpoint.trim() const requestPath = getChannelRequestPreviewPath(type) @@ -352,6 +388,10 @@ const getChannelEndpointPreview = (type: string | undefined, endpoint: string): return buildClaudeEndpointPreview(trimmedEndpoint, requestPath) } + if (isGeminiChannelType(type)) { + return buildGeminiEndpointPreview(trimmedEndpoint, requestPath) + } + if (isAzureChannelType(type)) { return buildAzureEndpointPreview(trimmedEndpoint, requestPath) } @@ -725,6 +765,7 @@ export function Channels({ ) }) const endpointPreview = getChannelEndpointPreview(formData.type, formData.endpoint) + const endpointPlaceholder = getChannelEndpointPlaceholder(formData.type) if (view === 'list') { return ( @@ -772,7 +813,7 @@ export function Channels({

添加您的第一个渠道

- 连接到 AI 服务商(如 OpenAI、Azure、Claude),用于代理和转发 API 请求。 + 连接到 AI 服务商(如 OpenAI、Gemini、Azure、Claude),用于代理和转发 API 请求。