mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat: 完善 Gemini 功能与 Claude 保持一致
- 添加 Gemini 账户的 schedulable 字段和调度开关 API - 实现 Gemini 调度器的模型过滤功能 - 完善 Gemini 数据统计,记录 token 使用量 - 修复 Gemini 流式响应的 SSE 解析和 AbortController 支持 - 在教程页面和 README 中添加 Gemini CLI 环境变量说明 - 修复前端 Gemini 账户调度开关限制 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
18
README.md
18
README.md
@@ -451,21 +451,33 @@ docker-compose.yml 已包含:
|
||||
- **客户端限制**: 限制只允许特定客户端使用(如ClaudeCode、Gemini-CLI等)
|
||||
5. 保存,记下生成的Key
|
||||
|
||||
### 4. 开始使用Claude code
|
||||
### 4. 开始使用 Claude Code 和 Gemini CLI
|
||||
|
||||
现在你可以用自己的服务替换官方API了:
|
||||
|
||||
**设置环境变量:**
|
||||
**Claude Code 设置环境变量:**
|
||||
```bash
|
||||
export ANTHROPIC_BASE_URL="http://127.0.0.1:3000/api/" # 根据实际填写你服务器的ip地址或者域名
|
||||
export ANTHROPIC_AUTH_TOKEN="后台创建的API密钥"
|
||||
```
|
||||
|
||||
**使用claude:**
|
||||
**Gemini CLI 设置环境变量:**
|
||||
```bash
|
||||
export CODE_ASSIST_ENDPOINT="http://127.0.0.1:3000/gemini" # 根据实际填写你服务器的ip地址或者域名
|
||||
export GOOGLE_CLOUD_ACCESS_TOKEN="后台创建的API密钥" # 使用相同的API密钥即可
|
||||
export GOOGLE_GENAI_USE_GCA="true"
|
||||
```
|
||||
|
||||
**使用 Claude Code:**
|
||||
```bash
|
||||
claude
|
||||
```
|
||||
|
||||
**使用 Gemini CLI:**
|
||||
```bash
|
||||
gemini # 或其他 Gemini CLI 命令
|
||||
```
|
||||
|
||||
### 5. 第三方工具API接入
|
||||
|
||||
本服务支持多种API端点格式,方便接入不同的第三方工具(如Cherry Studio等):
|
||||
|
||||
@@ -1521,6 +1521,30 @@ router.post('/gemini-accounts/:accountId/refresh', authenticateAdmin, async (req
|
||||
}
|
||||
});
|
||||
|
||||
// 切换 Gemini 账户调度状态
|
||||
router.put('/gemini-accounts/:accountId/toggle-schedulable', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const { accountId } = req.params;
|
||||
|
||||
const account = await geminiAccountService.getAccount(accountId);
|
||||
if (!account) {
|
||||
return res.status(404).json({ error: 'Account not found' });
|
||||
}
|
||||
|
||||
// 将字符串 'true'/'false' 转换为布尔值,然后取反
|
||||
const currentSchedulable = account.schedulable === 'true';
|
||||
const newSchedulable = !currentSchedulable;
|
||||
|
||||
await geminiAccountService.updateAccount(accountId, { schedulable: String(newSchedulable) });
|
||||
|
||||
logger.success(`🔄 Admin toggled Gemini account schedulable status: ${accountId} -> ${newSchedulable ? 'schedulable' : 'not schedulable'}`);
|
||||
res.json({ success: true, schedulable: newSchedulable });
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to toggle Gemini account schedulable status:', error);
|
||||
res.status(500).json({ error: 'Failed to toggle schedulable status', message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 📊 账户使用统计
|
||||
|
||||
// 获取所有账户的使用统计
|
||||
|
||||
@@ -7,7 +7,7 @@ const { sendGeminiRequest, getAvailableModels } = require('../services/geminiRel
|
||||
const crypto = require('crypto');
|
||||
const sessionHelper = require('../utils/sessionHelper');
|
||||
const unifiedGeminiScheduler = require('../services/unifiedGeminiScheduler');
|
||||
const { OAuth2Client } = require('google-auth-library');
|
||||
// const { OAuth2Client } = require('google-auth-library'); // OAuth2Client is not used in this file
|
||||
|
||||
// 生成会话哈希
|
||||
function generateSessionHash(req) {
|
||||
@@ -108,7 +108,8 @@ router.post('/messages', authenticateApiKey, async (req, res) => {
|
||||
proxy: account.proxy,
|
||||
apiKeyId: apiKeyData.id,
|
||||
signal: abortController.signal,
|
||||
projectId: account.projectId
|
||||
projectId: account.projectId,
|
||||
accountId: account.id
|
||||
});
|
||||
|
||||
if (stream) {
|
||||
|
||||
@@ -280,6 +280,10 @@ async function createAccount(accountData) {
|
||||
isActive: 'true',
|
||||
status: 'active',
|
||||
|
||||
// 调度相关
|
||||
schedulable: accountData.schedulable !== undefined ? String(accountData.schedulable) : 'true',
|
||||
priority: accountData.priority || 50, // 调度优先级 (1-100,数字越小优先级越高)
|
||||
|
||||
// OAuth 相关字段(加密存储)
|
||||
geminiOauth: geminiOauth ? encrypt(geminiOauth) : '',
|
||||
accessToken: accessToken ? encrypt(accessToken) : '',
|
||||
@@ -293,6 +297,9 @@ async function createAccount(accountData) {
|
||||
// 项目编号(Google Cloud/Workspace 账号需要)
|
||||
projectId: accountData.projectId || '',
|
||||
|
||||
// 支持的模型列表(可选)
|
||||
supportedModels: accountData.supportedModels || [], // 空数组表示支持所有模型
|
||||
|
||||
// 时间戳
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
|
||||
@@ -3,7 +3,7 @@ const { HttpsProxyAgent } = require('https-proxy-agent');
|
||||
const { SocksProxyAgent } = require('socks-proxy-agent');
|
||||
const logger = require('../utils/logger');
|
||||
const config = require('../../config/config');
|
||||
const { recordUsageMetrics } = require('./apiKeyService');
|
||||
const apiKeyService = require('./apiKeyService');
|
||||
|
||||
// Gemini API 配置
|
||||
const GEMINI_API_BASE = 'https://cloudcode.googleapis.com/v1';
|
||||
@@ -123,7 +123,7 @@ function convertGeminiResponse(geminiResponse, model, stream = false) {
|
||||
}
|
||||
|
||||
// 处理流式响应
|
||||
async function* handleStreamResponse(response, model, apiKeyId) {
|
||||
async function* handleStreamResponse(response, model, apiKeyId, accountId = null) {
|
||||
let buffer = '';
|
||||
let totalUsage = {
|
||||
promptTokenCount: 0,
|
||||
@@ -168,10 +168,16 @@ async function* handleStreamResponse(response, model, apiKeyId) {
|
||||
if (data.candidates?.[0]?.finishReason === 'STOP') {
|
||||
// 记录使用量
|
||||
if (apiKeyId && totalUsage.totalTokenCount > 0) {
|
||||
await recordUsageMetrics(apiKeyId, {
|
||||
inputTokens: totalUsage.promptTokenCount,
|
||||
outputTokens: totalUsage.candidatesTokenCount,
|
||||
model: model
|
||||
await apiKeyService.recordUsage(
|
||||
apiKeyId,
|
||||
totalUsage.promptTokenCount || 0, // inputTokens
|
||||
totalUsage.candidatesTokenCount || 0, // outputTokens
|
||||
0, // cacheCreateTokens (Gemini 没有这个概念)
|
||||
0, // cacheReadTokens (Gemini 没有这个概念)
|
||||
model,
|
||||
accountId
|
||||
).catch(error => {
|
||||
logger.error('❌ Failed to record Gemini usage:', error);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -206,6 +212,10 @@ async function* handleStreamResponse(response, model, apiKeyId) {
|
||||
|
||||
yield 'data: [DONE]\n\n';
|
||||
} catch (error) {
|
||||
// 检查是否是请求被中止
|
||||
if (error.name === 'CanceledError' || error.code === 'ECONNABORTED') {
|
||||
logger.info('Stream request was aborted by client');
|
||||
} else {
|
||||
logger.error('Stream processing error:', error);
|
||||
yield `data: ${JSON.stringify({
|
||||
error: {
|
||||
@@ -215,6 +225,7 @@ async function* handleStreamResponse(response, model, apiKeyId) {
|
||||
})}\n\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 发送请求到 Gemini
|
||||
async function sendGeminiRequest({
|
||||
@@ -226,8 +237,10 @@ async function sendGeminiRequest({
|
||||
accessToken,
|
||||
proxy,
|
||||
apiKeyId,
|
||||
signal,
|
||||
projectId,
|
||||
location = 'us-central1'
|
||||
location = 'us-central1',
|
||||
accountId = null
|
||||
}) {
|
||||
// 确保模型名称格式正确
|
||||
if (!model.startsWith('models/')) {
|
||||
@@ -281,6 +294,12 @@ async function sendGeminiRequest({
|
||||
logger.debug('Using proxy for Gemini request');
|
||||
}
|
||||
|
||||
// 添加 AbortController 信号支持
|
||||
if (signal) {
|
||||
axiosConfig.signal = signal;
|
||||
logger.debug('AbortController signal attached to request');
|
||||
}
|
||||
|
||||
if (stream) {
|
||||
axiosConfig.responseType = 'stream';
|
||||
}
|
||||
@@ -290,23 +309,42 @@ async function sendGeminiRequest({
|
||||
const response = await axios(axiosConfig);
|
||||
|
||||
if (stream) {
|
||||
return handleStreamResponse(response, model, apiKeyId);
|
||||
return handleStreamResponse(response, model, apiKeyId, accountId);
|
||||
} else {
|
||||
// 非流式响应
|
||||
const openaiResponse = convertGeminiResponse(response.data, model, false);
|
||||
|
||||
// 记录使用量
|
||||
if (apiKeyId && openaiResponse.usage) {
|
||||
await recordUsageMetrics(apiKeyId, {
|
||||
inputTokens: openaiResponse.usage.prompt_tokens,
|
||||
outputTokens: openaiResponse.usage.completion_tokens,
|
||||
model: model
|
||||
await apiKeyService.recordUsage(
|
||||
apiKeyId,
|
||||
openaiResponse.usage.prompt_tokens || 0,
|
||||
openaiResponse.usage.completion_tokens || 0,
|
||||
0, // cacheCreateTokens
|
||||
0, // cacheReadTokens
|
||||
model,
|
||||
accountId
|
||||
).catch(error => {
|
||||
logger.error('❌ Failed to record Gemini usage:', error);
|
||||
});
|
||||
}
|
||||
|
||||
return openaiResponse;
|
||||
}
|
||||
} catch (error) {
|
||||
// 检查是否是请求被中止
|
||||
if (error.name === 'CanceledError' || error.code === 'ECONNABORTED') {
|
||||
logger.info('Gemini request was aborted by client');
|
||||
throw {
|
||||
status: 499,
|
||||
error: {
|
||||
message: 'Request canceled by client',
|
||||
type: 'canceled',
|
||||
code: 'request_canceled'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
logger.error('Gemini API request failed:', error.response?.data || error.message);
|
||||
|
||||
// 转换错误格式
|
||||
|
||||
@@ -18,7 +18,7 @@ class UnifiedClaudeScheduler {
|
||||
if (apiKeyData.claudeAccountId.startsWith('group:')) {
|
||||
const groupId = apiKeyData.claudeAccountId.replace('group:', '');
|
||||
logger.info(`🎯 API key ${apiKeyData.name} is bound to group ${groupId}, selecting from group`);
|
||||
return await this.selectAccountFromGroup(groupId, sessionHash, requestedModel, apiKeyData);
|
||||
return await this.selectAccountFromGroup(groupId, sessionHash, requestedModel);
|
||||
}
|
||||
|
||||
// 普通专属账户
|
||||
@@ -370,7 +370,7 @@ class UnifiedClaudeScheduler {
|
||||
}
|
||||
|
||||
// 👥 从分组中选择账户
|
||||
async selectAccountFromGroup(groupId, sessionHash = null, requestedModel = null, apiKeyData = null) {
|
||||
async selectAccountFromGroup(groupId, sessionHash = null, requestedModel = null) {
|
||||
try {
|
||||
// 获取分组信息
|
||||
const group = await accountGroupService.getGroup(groupId);
|
||||
@@ -426,7 +426,7 @@ class UnifiedClaudeScheduler {
|
||||
}
|
||||
} else if (group.platform === 'gemini') {
|
||||
// Gemini暂时不支持,预留接口
|
||||
logger.warn(`⚠️ Gemini group scheduling not yet implemented`);
|
||||
logger.warn('⚠️ Gemini group scheduling not yet implemented');
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,19 @@ class UnifiedGeminiScheduler {
|
||||
if (boundAccount && boundAccount.isActive === 'true' && boundAccount.status !== 'error') {
|
||||
const isRateLimited = await this.isAccountRateLimited(boundAccount.id);
|
||||
if (!isRateLimited) {
|
||||
// 检查模型支持
|
||||
if (requestedModel && boundAccount.supportedModels && boundAccount.supportedModels.length > 0) {
|
||||
// 处理可能带有 models/ 前缀的模型名
|
||||
const normalizedModel = requestedModel.replace('models/', '');
|
||||
const modelSupported = boundAccount.supportedModels.some(model =>
|
||||
model.replace('models/', '') === normalizedModel
|
||||
);
|
||||
if (!modelSupported) {
|
||||
logger.warn(`⚠️ Bound Gemini account ${boundAccount.name} does not support model ${requestedModel}`);
|
||||
return availableAccounts;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`🎯 Using bound dedicated Gemini account: ${boundAccount.name} (${apiKeyData.geminiAccountId})`);
|
||||
return [{
|
||||
...boundAccount,
|
||||
@@ -124,6 +137,19 @@ class UnifiedGeminiScheduler {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查模型支持
|
||||
if (requestedModel && account.supportedModels && account.supportedModels.length > 0) {
|
||||
// 处理可能带有 models/ 前缀的模型名
|
||||
const normalizedModel = requestedModel.replace('models/', '');
|
||||
const modelSupported = account.supportedModels.some(model =>
|
||||
model.replace('models/', '') === normalizedModel
|
||||
);
|
||||
if (!modelSupported) {
|
||||
logger.debug(`⏭️ Skipping Gemini account ${account.name} - doesn't support model ${requestedModel}`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否被限流
|
||||
const isRateLimited = await this.isAccountRateLimited(account.id);
|
||||
if (!isRateLimited) {
|
||||
@@ -269,7 +295,7 @@ class UnifiedGeminiScheduler {
|
||||
}
|
||||
|
||||
// 👥 从分组中选择账户
|
||||
async selectAccountFromGroup(groupId, sessionHash = null, requestedModel = null, apiKeyData = null) {
|
||||
async selectAccountFromGroup(groupId, sessionHash = null, requestedModel = null) {
|
||||
try {
|
||||
// 获取分组信息
|
||||
const group = await accountGroupService.getGroup(groupId);
|
||||
@@ -330,6 +356,19 @@ class UnifiedGeminiScheduler {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查模型支持
|
||||
if (requestedModel && account.supportedModels && account.supportedModels.length > 0) {
|
||||
// 处理可能带有 models/ 前缀的模型名
|
||||
const normalizedModel = requestedModel.replace('models/', '');
|
||||
const modelSupported = account.supportedModels.some(model =>
|
||||
model.replace('models/', '') === normalizedModel
|
||||
);
|
||||
if (!modelSupported) {
|
||||
logger.debug(`⏭️ Skipping Gemini account ${account.name} in group - doesn't support model ${requestedModel}`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否被限流
|
||||
const isRateLimited = await this.isAccountRateLimited(account.id);
|
||||
if (!isRateLimited) {
|
||||
|
||||
@@ -1050,8 +1050,10 @@ const toggleSchedulable = async (account) => {
|
||||
endpoint = `/admin/claude-accounts/${account.id}/toggle-schedulable`
|
||||
} else if (account.platform === 'claude-console') {
|
||||
endpoint = `/admin/claude-console-accounts/${account.id}/toggle-schedulable`
|
||||
} else if (account.platform === 'gemini') {
|
||||
endpoint = `/admin/gemini-accounts/${account.id}/toggle-schedulable`
|
||||
} else {
|
||||
showToast('Gemini账户暂不支持调度控制', 'warning')
|
||||
showToast('该账户类型暂不支持调度控制', 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -338,6 +338,85 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gemini CLI 环境变量设置 -->
|
||||
<div class="mt-8">
|
||||
<h5 class="text-base sm:text-lg font-semibold text-gray-800 mb-2 sm:mb-3 flex items-center">
|
||||
<i class="fas fa-robot text-green-600 mr-2" />
|
||||
配置 Gemini CLI 环境变量
|
||||
</h5>
|
||||
<p class="text-gray-700 mb-3 sm:mb-4 text-sm sm:text-base">
|
||||
如果你使用 Gemini CLI,需要设置以下环境变量:
|
||||
</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="bg-white rounded-lg p-3 sm:p-4 border border-green-200">
|
||||
<h6 class="font-medium text-sm sm:text-base text-gray-800 mb-2">
|
||||
PowerShell 设置方法
|
||||
</h6>
|
||||
<p class="text-gray-600 text-sm mb-3">
|
||||
在 PowerShell 中运行以下命令:
|
||||
</p>
|
||||
<div class="bg-gray-900 text-green-400 p-2 sm:p-3 rounded font-mono text-xs sm:text-sm overflow-x-auto">
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
$env:CODE_ASSIST_ENDPOINT = "{{ geminiBaseUrl }}"
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
$env:GOOGLE_CLOUD_ACCESS_TOKEN = "你的API密钥"
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
$env:GOOGLE_GENAI_USE_GCA = "true"
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-yellow-700 text-xs mt-2">
|
||||
💡 使用与 Claude Code 相同的 API 密钥即可。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg p-3 sm:p-4 border border-green-200">
|
||||
<h6 class="font-medium text-sm sm:text-base text-gray-800 mb-2">
|
||||
系统环境变量(永久设置)
|
||||
</h6>
|
||||
<p class="text-gray-600 text-sm mb-3">
|
||||
在系统环境变量中添加:
|
||||
</p>
|
||||
<div class="space-y-2">
|
||||
<div class="bg-gray-100 p-2 rounded text-sm">
|
||||
<strong>变量名:</strong> CODE_ASSIST_ENDPOINT<br>
|
||||
<strong>变量值:</strong> <span class="font-mono">{{ geminiBaseUrl }}</span>
|
||||
</div>
|
||||
<div class="bg-gray-100 p-2 rounded text-sm">
|
||||
<strong>变量名:</strong> GOOGLE_CLOUD_ACCESS_TOKEN<br>
|
||||
<strong>变量值:</strong> <span class="font-mono">你的API密钥</span>
|
||||
</div>
|
||||
<div class="bg-gray-100 p-2 rounded text-sm">
|
||||
<strong>变量名:</strong> GOOGLE_GENAI_USE_GCA<br>
|
||||
<strong>变量值:</strong> <span class="font-mono">true</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-green-50 border border-green-200 rounded-lg p-3 sm:p-4">
|
||||
<h6 class="font-medium text-green-800 mb-2">
|
||||
验证 Gemini CLI 环境变量
|
||||
</h6>
|
||||
<p class="text-green-700 text-sm mb-3">
|
||||
在 PowerShell 中验证:
|
||||
</p>
|
||||
<div class="bg-gray-900 text-green-400 p-2 sm:p-3 rounded font-mono text-xs sm:text-sm overflow-x-auto space-y-1">
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo $env:CODE_ASSIST_ENDPOINT
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo $env:GOOGLE_CLOUD_ACCESS_TOKEN
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo $env:GOOGLE_GENAI_USE_GCA
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第五步:开始使用 -->
|
||||
@@ -657,6 +736,105 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gemini CLI 环境变量设置 -->
|
||||
<div class="mt-8">
|
||||
<h5 class="text-base sm:text-lg font-semibold text-gray-800 mb-2 sm:mb-3 flex items-center">
|
||||
<i class="fas fa-robot text-green-600 mr-2" />
|
||||
配置 Gemini CLI 环境变量
|
||||
</h5>
|
||||
<p class="text-gray-700 mb-3 sm:mb-4 text-sm sm:text-base">
|
||||
如果你使用 Gemini CLI,需要设置以下环境变量:
|
||||
</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="bg-white rounded-lg p-3 sm:p-4 border border-green-200">
|
||||
<h6 class="font-medium text-sm sm:text-base text-gray-800 mb-2">
|
||||
Terminal 设置方法
|
||||
</h6>
|
||||
<p class="text-gray-600 text-sm mb-3">
|
||||
在 Terminal 中运行以下命令:
|
||||
</p>
|
||||
<div class="bg-gray-900 text-green-400 p-2 sm:p-3 rounded font-mono text-xs sm:text-sm overflow-x-auto">
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
export CODE_ASSIST_ENDPOINT="{{ geminiBaseUrl }}"
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
export GOOGLE_CLOUD_ACCESS_TOKEN="你的API密钥"
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
export GOOGLE_GENAI_USE_GCA="true"
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-yellow-700 text-xs mt-2">
|
||||
💡 使用与 Claude Code 相同的 API 密钥即可。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg p-3 sm:p-4 border border-green-200">
|
||||
<h6 class="font-medium text-sm sm:text-base text-gray-800 mb-2">
|
||||
永久设置方法
|
||||
</h6>
|
||||
<p class="text-gray-600 text-sm mb-3">
|
||||
添加到你的 shell 配置文件:
|
||||
</p>
|
||||
<div class="bg-gray-900 text-green-400 p-2 sm:p-3 rounded font-mono text-xs sm:text-sm overflow-x-auto mb-3">
|
||||
<div class="mb-2">
|
||||
# 对于 zsh (默认)
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo 'export CODE_ASSIST_ENDPOINT="{{ geminiBaseUrl }}"' >> ~/.zshrc
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo 'export GOOGLE_CLOUD_ACCESS_TOKEN="你的API密钥"' >> ~/.zshrc
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo 'export GOOGLE_GENAI_USE_GCA="true"' >> ~/.zshrc
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
source ~/.zshrc
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-900 text-green-400 p-2 sm:p-3 rounded font-mono text-xs sm:text-sm overflow-x-auto">
|
||||
<div class="mb-2">
|
||||
# 对于 bash
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo 'export CODE_ASSIST_ENDPOINT="{{ geminiBaseUrl }}"' >> ~/.bash_profile
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo 'export GOOGLE_CLOUD_ACCESS_TOKEN="你的API密钥"' >> ~/.bash_profile
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo 'export GOOGLE_GENAI_USE_GCA="true"' >> ~/.bash_profile
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
source ~/.bash_profile
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-green-50 border border-green-200 rounded-lg p-3 sm:p-4">
|
||||
<h6 class="font-medium text-green-800 mb-2">
|
||||
验证 Gemini CLI 环境变量
|
||||
</h6>
|
||||
<p class="text-green-700 text-sm mb-3">
|
||||
在 Terminal 中验证:
|
||||
</p>
|
||||
<div class="bg-gray-900 text-green-400 p-2 sm:p-3 rounded font-mono text-xs sm:text-sm overflow-x-auto space-y-1">
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo $CODE_ASSIST_ENDPOINT
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo $GOOGLE_CLOUD_ACCESS_TOKEN
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo $GOOGLE_GENAI_USE_GCA
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第四步:开始使用 -->
|
||||
@@ -987,6 +1165,105 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gemini CLI 环境变量设置 -->
|
||||
<div class="mt-8">
|
||||
<h5 class="text-base sm:text-lg font-semibold text-gray-800 mb-2 sm:mb-3 flex items-center">
|
||||
<i class="fas fa-robot text-green-600 mr-2" />
|
||||
配置 Gemini CLI 环境变量
|
||||
</h5>
|
||||
<p class="text-gray-700 mb-3 sm:mb-4 text-sm sm:text-base">
|
||||
如果你使用 Gemini CLI,需要设置以下环境变量:
|
||||
</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="bg-white rounded-lg p-3 sm:p-4 border border-green-200">
|
||||
<h6 class="font-medium text-sm sm:text-base text-gray-800 mb-2">
|
||||
终端设置方法
|
||||
</h6>
|
||||
<p class="text-gray-600 text-sm mb-3">
|
||||
在终端中运行以下命令:
|
||||
</p>
|
||||
<div class="bg-gray-900 text-green-400 p-2 sm:p-3 rounded font-mono text-xs sm:text-sm overflow-x-auto">
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
export CODE_ASSIST_ENDPOINT="{{ geminiBaseUrl }}"
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
export GOOGLE_CLOUD_ACCESS_TOKEN="你的API密钥"
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
export GOOGLE_GENAI_USE_GCA="true"
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-yellow-700 text-xs mt-2">
|
||||
💡 使用与 Claude Code 相同的 API 密钥即可。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg p-3 sm:p-4 border border-green-200">
|
||||
<h6 class="font-medium text-sm sm:text-base text-gray-800 mb-2">
|
||||
永久设置方法
|
||||
</h6>
|
||||
<p class="text-gray-600 text-sm mb-3">
|
||||
添加到你的 shell 配置文件:
|
||||
</p>
|
||||
<div class="bg-gray-900 text-green-400 p-2 sm:p-3 rounded font-mono text-xs sm:text-sm overflow-x-auto mb-3">
|
||||
<div class="mb-2">
|
||||
# 对于 bash (默认)
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo 'export CODE_ASSIST_ENDPOINT="{{ geminiBaseUrl }}"' >> ~/.bashrc
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo 'export GOOGLE_CLOUD_ACCESS_TOKEN="你的API密钥"' >> ~/.bashrc
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo 'export GOOGLE_GENAI_USE_GCA="true"' >> ~/.bashrc
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
source ~/.bashrc
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-900 text-green-400 p-2 sm:p-3 rounded font-mono text-xs sm:text-sm overflow-x-auto">
|
||||
<div class="mb-2">
|
||||
# 对于 zsh
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo 'export CODE_ASSIST_ENDPOINT="{{ geminiBaseUrl }}"' >> ~/.zshrc
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo 'export GOOGLE_CLOUD_ACCESS_TOKEN="你的API密钥"' >> ~/.zshrc
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo 'export GOOGLE_GENAI_USE_GCA="true"' >> ~/.zshrc
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
source ~/.zshrc
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-green-50 border border-green-200 rounded-lg p-3 sm:p-4">
|
||||
<h6 class="font-medium text-green-800 mb-2">
|
||||
验证 Gemini CLI 环境变量
|
||||
</h6>
|
||||
<p class="text-green-700 text-sm mb-3">
|
||||
在终端中验证:
|
||||
</p>
|
||||
<div class="bg-gray-900 text-green-400 p-2 sm:p-3 rounded font-mono text-xs sm:text-sm overflow-x-auto space-y-1">
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo $CODE_ASSIST_ENDPOINT
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo $GOOGLE_CLOUD_ACCESS_TOKEN
|
||||
</div>
|
||||
<div class="text-gray-300 whitespace-nowrap">
|
||||
echo $GOOGLE_GENAI_USE_GCA
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第四步:开始使用 -->
|
||||
@@ -1130,8 +1407,8 @@ const tutorialSystems = [
|
||||
{ key: 'linux', name: 'Linux / WSL2', icon: 'fab fa-linux' },
|
||||
]
|
||||
|
||||
// 当前基础URL
|
||||
const currentBaseUrl = computed(() => {
|
||||
// 获取基础URL前缀
|
||||
const getBaseUrlPrefix = () => {
|
||||
// 更健壮的获取 origin 的方法,兼容旧版浏览器和特殊环境
|
||||
let origin = ''
|
||||
|
||||
@@ -1163,11 +1440,21 @@ const currentBaseUrl = computed(() => {
|
||||
} else {
|
||||
// 最后的降级方案,使用相对路径
|
||||
console.warn('无法获取完整的 origin,将使用相对路径')
|
||||
return '/api'
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
return origin + '/api'
|
||||
return origin
|
||||
}
|
||||
|
||||
// 当前基础URL - Claude Code
|
||||
const currentBaseUrl = computed(() => {
|
||||
return getBaseUrlPrefix() + '/api'
|
||||
})
|
||||
|
||||
// Gemini CLI 基础URL
|
||||
const geminiBaseUrl = computed(() => {
|
||||
return getBaseUrlPrefix() + '/gemini'
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user