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:
shaw
2025-08-04 16:53:11 +08:00
parent 15b4efa353
commit ef4f7483d3
9 changed files with 443 additions and 33 deletions

View File

@@ -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) {