Commit Graph

156 Commits

Author SHA1 Message Date
song
21a04332ec fix: 修复 golangci-lint 检查错误
- SA1029: 创建 ctxkey 包定义类型安全的 context key
- ST1005: 错误字符串首字母改小写
- errcheck: 显式忽略 bytes.Buffer.Write 返回值
- 修复单元测试中 GatewayService 缺少 cfg 字段的问题
2025-12-29 17:46:52 +08:00
song
2bd288a677 Merge branch 'main' into feature/antigravity_auth 2025-12-29 17:04:40 +08:00
song
234e98f1b3 feat(antigravity): 保存 ineligibleTiers 原因信息 2025-12-29 16:55:17 +08:00
song
b31bfd53ab feat(antigravity): 添加专用路由,支持仅使用 antigravity 账户
添加 /antigravity/v1/* 和 /antigravity/v1beta/* 路由:
- 通过 ForcePlatform 中间件强制使用 antigravity 平台
- 跳过混合调度逻辑,仅调度 antigravity 账户
- 支持按分组优先查找,找不到时回退查询全部 antigravity 账户

修复 context key 类型不匹配问题:
- middleware 和 service 统一使用字符串常量 "ctx_force_platform"
- 解决 Go context.Value() 类型+值匹配导致的读取失败

其他改动:
- 嵌入式前端中间件白名单添加 /antigravity/ 路径
- e2e 测试 Gemini 端点 URL 添加 endpointPrefix 支持
2025-12-29 16:52:55 +08:00
song
1ad29032d3 feat(antigravity): 添加混合调度可选功能
- 后端:账户模型添加 IsMixedSchedulingEnabled() 方法,读取 extra.mixed_scheduling
- 后端:gateway_service 和 gemini_messages_compat_service 支持混合调度逻辑
- 后端:分组创建支持指定 platform 参数
- 前端:账户创建/编辑弹窗添加混合调度开关(仅 antigravity 账户显示)
- 前端:混合调度开关添加问号图标和 tooltip 说明
- 前端:GroupSelector 支持根据 mixedScheduling 属性过滤分组
- 前端:分组创建支持选择 platform
- 测试:e2e 测试添加 ENDPOINT_PREFIX 环境变量支持混合/隔离模式测试
- 测试:删除过时的 Claude signature 测试用例
2025-12-29 09:44:39 +08:00
shaw
32b4b139a4 fix: 修复因移除 SimpleMode 导致的测试编译错误
- 移除 api_contract_test.go 中的 SettingKeySimpleMode 引用
- 移除期望响应中的 simple_mode 字段
- 修复 NewSettingHandler 调用参数数量
2025-12-29 09:24:21 +08:00
shaw
31fef105c7 refactor: 移除旧版数据库配置的简易模式实现
移除与 PR #66 冲突的旧版简易模式实现(commit 7d4b7de)。
新版简易模式通过 run_mode 配置文件/环境变量控制,无需数据库设置。

后端变更:
- 移除 SettingKeySimpleMode 常量
- 移除 SystemSettings/PublicSettings 中的 SimpleMode 字段
- 移除 setting_handler 中的简易模式切换逻辑
- 移除 userService 依赖(不再需要自动设置管理员并发数)

前端变更:
- 移除 appStore.simpleMode 状态
- 移除设置页面的"使用模式"设置区块
- 移除 GroupsView 中的简易模式相关逻辑
- 移除相关国际化文案
2025-12-29 09:17:00 +08:00
IanShaw027
9e9811cbb3 test: 修复分组测试以适配默认分组
由于简易模式会自动创建3个默认分组(anthropic-default, openai-default, gemini-default),
需要更新测试用例的预期数量:
- TestList: 期望5个分组(3个默认 + 2个测试)
- TestListActive: 期望4个活跃分组(3个默认 + 1个测试)
- TestListActiveByPlatform: 期望2个Anthropic分组(1个默认 + 1个测试)
- TestListWithFilters_Platform: 期望2个OpenAI分组(1个默认 + 1个测试)
2025-12-29 03:31:03 +08:00
IanShaw027
ecfad788d9 feat(全栈): 实现简易模式核心功能
**功能概述**:
实现简易模式(Simple Mode),为个人用户和小团队提供简化的使用体验,隐藏复杂的分组、订阅、配额等概念。

**后端改动**:
1. 配置系统
   - 新增 run_mode 配置项(standard/simple)
   - 支持环境变量 RUN_MODE
   - 默认值为 standard

2. 数据库初始化
   - 自动创建3个默认分组:anthropic-default、openai-default、gemini-default
   - 默认分组配置:无并发限制、active状态、非独占
   - 幂等性保证:重复启动不会重复创建

3. 账号管理
   - 创建账号时自动绑定对应平台的默认分组
   - 如果未指定分组,自动查找并绑定默认分组

**前端改动**:
1. 状态管理
   - authStore 新增 isSimpleMode 计算属性
   - 从后端API获取并同步运行模式

2. UI隐藏
   - 侧边栏:隐藏分组管理、订阅管理、兑换码菜单
   - 账号管理页面:隐藏分组列
   - 创建/编辑账号对话框:隐藏分组选择器

3. 路由守卫
   - 限制访问分组、订阅、兑换码相关页面
   - 访问受限页面时自动重定向到仪表板

**配置示例**:
```yaml
run_mode: simple

run_mode: standard
```

**影响范围**:
- 后端:配置、数据库迁移、账号服务
- 前端:认证状态、路由、UI组件
- 部署:配置文件示例

**兼容性**:
- 简易模式和标准模式可无缝切换
- 不需要数据迁移
- 现有数据不受影响
2025-12-29 03:24:15 +08:00
song
cf1d0f23cc feat(antigravity): 添加账户类型(tier)显示功能 2025-12-29 01:25:09 +08:00
song
995adaeee4 test: 添加 Claude signature 场景 e2e 测试
- 新增 TestClaudeMessagesWithClaudeSignature 测试
- 验证历史 thinking block 带有 Claude signature 时的处理
- 修复配额刷新服务的次要问题
2025-12-29 00:44:07 +08:00
shaw
30b95cf5ce fix(usage): 分离 API 响应和窗口统计缓存,修复 5h 窗口未激活时的显示 bug
问题:
1. WindowStats 与 API 响应一起缓存 10 分钟,导致费用数据更新延迟
2. 当 5h 窗口未激活(ResetsAt 为空)时,FiveHour 为 nil,导致所有窗口的 WindowStats 都无法显示

修复:
- 分离缓存:API 响应缓存 10 分钟,窗口统计独立缓存 1 分钟
- RemainingSeconds 每次请求时实时计算
- FiveHour 对象始终创建(即使 ResetsAt 为空)
- addWindowStats 增强防护,支持 FiveHour 为 nil 时仍处理其他窗口
2025-12-28 23:12:44 +08:00
shaw
25b8a22648 fix(test): 测试用例添加 simple_mode 字段
API 响应新增 simple_mode 字段,同步更新测试期望值
2025-12-28 22:51:22 +08:00
shaw
0084da9ca5 fix: 修复 NewSettingHandler 参数不足导致的编译错误
- 测试文件添加第三个参数 userService(nil)
- Handler 添加 userService 空指针检查,防止测试环境 panic
2025-12-28 22:45:13 +08:00
song
08ce6de4db feat(antigravity): 添加配额窗口显示功能
后端:
- 新增 AntigravityQuotaRefresher 定时刷新配额
- Client 添加 FetchAvailableModels 方法获取模型配额
- 配额数据存入 account.extra.quota 字段

前端:
- AccountUsageCell 支持显示 Antigravity 账户配额
- UsageProgressBar 新增 amber 颜色
- 显示 G3P/G3F/G3I/C4.5 四个配额进度条
2025-12-28 22:29:01 +08:00
shaw
7d4b7deea9 feat: 添加简单模式功能
新增简单模式设置,适合个人使用场景:
- 隐藏多用户管理相关菜单(用户管理、兑换码等)
- 自动关闭用户注册功能
- 管理员并发数自动设为无限制(99999)
- 侧边栏根据模式动态调整菜单项

同时优化分组页面的"专属分组"功能,添加帮助提示说明使用场景
2025-12-28 22:19:18 +08:00
song
b6b739431c build: e2e 测试添加 build tag 避免 CI 运行
- 添加 //go:build e2e tag,CI 不会自动运行这些测试
- Makefile 添加 test-e2e 目标用于本地手动运行
2025-12-28 21:59:40 +08:00
song
ad15d9970c fix(gateway): Antigravity 账户 count_tokens 返回估算值
Antigravity 不支持 count_tokens 转发,直接返回估算值,
与 Antigravity-Manager 和 proxycast 实现保持一致。

修复 count_tokens 请求选择到 Antigravity 账户时导致 401 的问题。
2025-12-28 21:56:52 +08:00
song
ff57c860e3 test: 更新 thinking signature 测试用例
将测试从无效signature改为无signature场景:
- 无效 signature 应该被上游拒绝(预期行为)
- Gemini 模型接受没有 signature 的 thinking block
2025-12-28 21:40:35 +08:00
song
635d7e77e1 fix(antigravity): 只有 Gemini 模型支持 dummy thought signature
参考 Antigravity-Manager 的实现:
- 添加 allowDummyThought 参数,只有 gemini-* 模型才启用
- Claude 模型通过 Vertex API 需要有效的 thought signatures
- thinking block 保留原有 signature
- tool_use 只在 Gemini 模型时才使用 dummy signature
2025-12-28 21:36:21 +08:00
song
ba9eb684ed fix(antigravity): 与 proxycast 保持一致的 thought_signature 处理
- function_call 无条件添加 dummy thought_signature(与 proxycast 一致)
- thinking block 在 thinking 模式下统一使用 dummy signature 替换历史无效 signature
- 添加测试用例:TestClaudeMessagesWithInvalidThinkingSignature
2025-12-28 21:29:16 +08:00
song
9594c9c83a fix(antigravity): 修复 Gemini 3 thought_signature 和 schema 验证问题
- 添加 dummyThoughtSignature 常量,在 thinking 模式下为无 signature 的 tool_use 自动添加
- 增强 cleanJSONSchema:过滤 required 中不存在的属性,确保 type/properties 字段存在
- 扩展 excludedSchemaKeys:增加 $id, $ref, strict, const, examples 等不支持的字段
- 修复 429 重试逻辑:仅在所有重试失败后才标记账户为 rate_limited
- 添加 e2e 集成测试:TestClaudeMessagesWithThinkingAndTools
2025-12-28 21:25:04 +08:00
song
ff06583c5d Merge branch 'main' into feature/antigravity_auth 2025-12-28 18:46:18 +08:00
song
b0389ca4d2 feat: 实现 Antigravity Claude → Gemini 协议转换,haiku 映射到 gemini-3-flash 2025-12-28 18:41:55 +08:00
song
1d085d982b feat: 完善 Antigravity 多平台网关支持,修复 Gemini handler 分流逻辑 2025-12-28 17:48:52 +08:00
Wesley Liddick
18c6686fed Merge branch 'main' into feature/ui-improvements-clean 2025-12-28 03:22:11 -05:00
song
6648e6506c feat: 添加 Antigravity (Cloud AI Companion) OAuth 授权支持 2025-12-28 15:54:42 +08:00
IanShaw027
506cb21cb1 refactor(frontend): UI/UX改进和组件优化
- DataTable组件操作列自适应
- 优化各种Modal弹窗
- 统一API调用方式(AbortSignal)
- 添加全局订阅状态管理
- 优化各管理视图的交互和布局
- 修复国际化翻译问题
2025-12-28 14:53:36 +08:00
yangjianbo
fd51ff6970 fix: 代码的核心问题是判错条件用错了层级:
- apiKeyService.GetByKey(...) 返回的“找不到 API key”在这个项目里通常会被翻译成业务错误(比如
    service.ErrApiKeyNotFound 这类 ApplicationError),而不是直接把 gorm.ErrRecordNotFound 透传到中
    间件层。
  - 因此你在中间件里用 errors.Is(err, gorm.ErrRecordNotFound) 去判断“无效 key”,很容易匹配不到(尤其
    是:后面加 Redis 缓存、换存储实现、或测试里用 stub repo 时,根本不会出现 gorm 的错误)。
  - 匹配不到时就会走到 500 Failed to validate API key,导致无效 API key 被错误地当成服务端故障返回
    500(应该是 401)。

  修复思路:中间件不要依赖 gorm 的错误,改成判断业务层错误,例如:

  if errors.Is(err, service.ErrApiKeyNotFound) {
      abortWithGoogleError(c, 401, "Invalid API key")
      return
  }

  如果你把 GetByKey 的“not found”统一封装成业务错误,这样才不会被底层实现(gorm/redis/mock)影响。
2025-12-28 14:34:05 +08:00
shaw
fbdff4f34f fix: 防止订阅过期时间超出 JSON 序列化范围
问题:当分配订阅天数过大时,expires_at 年份可能超过 9999,
导致 time.Time JSON 序列化失败(RFC 3339 要求年份 <= 9999),
使后台无法显示和删除异常数据。

修复:
- handler 层添加 validity_days 最大值验证(max=36500,即100年)
- service 层添加 MaxValidityDays 和 MaxExpiresAt 双重保护
- 启动时自动修复已存在的异常数据(expires_at > 2099年)
2025-12-28 11:45:41 +08:00
shaw
0aa480283f Merge branch 'feat/deferred-batch-update' 2025-12-28 11:28:06 +08:00
shaw
cd9d31f5f2 fix: 修复NeedsRefresh bug导致刷新失败的问题 2025-12-28 11:23:52 +08:00
noreply
cbfce49aa1 feat: Schedule batch update for account last_used_at
Implement deferred batch update mechanism to reduce database load:

- Add DeferredService for batching account last_used_at updates
- Add TimingWheelService for efficient recurring task scheduling
- Integrate with GatewayService and OpenAIGatewayService
- Implement BatchUpdateLastUsed repository method using CASE...WHEN SQL
- Fix golangci-lint error: Replace interface{} with any

Benefits:
- Reduces database writes by batching updates (10-second intervals)
- Improves request throughput by deferring non-critical updates
- Maintains accurate account usage tracking for scheduling
2025-12-28 09:49:54 +08:00
shaw
0b9c4ae69e fix: 修复claude setup token授权效期短的问题 2025-12-27 20:42:00 +08:00
shaw
0d5a8a95c8 fix: 修复claude token刷新失效的问题 2025-12-27 20:13:39 +08:00
shaw
0743652d92 Merge branch 'feature/ui-and-backend-improvements' 2025-12-27 16:33:57 +08:00
IanShaw027
96bec5c9b1 fix(test): 实现GetUserStatsAggregated方法以支持新的统计查询
- 在stubUsageLogRepo中实现GetUserStatsAggregated方法
- 根据userLogs计算统计数据而不是返回错误
- 修复类型转换问题(int转int64)
2025-12-27 16:20:59 +08:00
IanShaw027
cfeb6b8b14 fix(test): 添加缺失的GetUserStatsAggregated方法 2025-12-27 16:15:32 +08:00
IanShaw027
481310dea0 fix(test): 修复CI测试失败
- 修复gofmt格式问题
- 为stubUsageLogRepo添加缺失的GetApiKeyStatsAggregated方法
2025-12-27 16:12:06 +08:00
IanShaw027
227d506c53 feat(backend): 增强使用统计和API密钥功能
- 优化使用统计处理逻辑
- 增强API密钥仓储层功能
- 改进账户使用服务
- 完善API契约测试覆盖
2025-12-27 16:03:57 +08:00
IanShaw027
36a86e9ab4 perf(backend): 优化数据库查询性能
- 合并多个独立查询为单个SQL查询
- 减少数据库往返次数
- 提升仪表板统计数据获取效率
2025-12-27 16:03:37 +08:00
shaw
f1e47291cd fix: 修复账号更新时分组绑定操作顺序导致的数据不一致问题
原逻辑先执行 Update 再验证 GroupIDs,如果验证失败会导致账号已更新但返回错误。
现改为先验证分组是否存在,再执行 Update 和 BindGroups。
2025-12-27 14:57:43 +08:00
shaw
d7e9ae38e4 Merge PR #49: feat: cc/codex/gemini 增加账号重试功能 2025-12-27 13:59:00 +08:00
IanShaw
3f92a43170 test: 完善 UsageLogRepo 测试 stub 的过滤逻辑 (#50) 2025-12-27 13:53:47 +08:00
shaw
2101f1d1c8 fix: 修复claude OAuth账户刷新token失败的bug 2025-12-27 13:50:35 +08:00
daodao97
f0f920e49f feat: cc/codex/gemini 增加账号重试 2025-12-27 12:27:47 +08:00
daodao97
95583fce83 feat: cc/codex support account retry 2025-12-27 12:05:38 +08:00
IanShaw
254f12543c feat(frontend): 前端界面优化与使用统计功能增强 (#46)
* feat(frontend): 前端界面优化与使用统计功能增强

主要改动:

1. 表格布局统一优化
   - 新增 TablePageLayout 通用布局组件
   - 统一所有管理页面的表格样式和交互
   - 优化 DataTable、Pagination、Select 等通用组件

2. 使用统计功能增强
   - 管理端: 添加完整的筛选和显示功能
   - 用户端: 完善 API Key 列显示
   - 后端: 优化使用统计数据结构和查询

3. 账户组件优化
   - 优化 AccountStatsModal、AccountUsageCell 等组件
   - 统一进度条和统计显示样式

4. 其他改进
   - 完善中英文国际化
   - 统一页面样式和交互体验
   - 优化各视图页面的响应式布局

* fix(test): 修复 stubUsageLogRepo.ListWithFilters 测试 stub

测试用例 GET /api/v1/usage 返回 500 是因为 stub 方法未实现,
现在正确返回基于 UserID 过滤的日志数据。

* feat(frontend): 统一日期时间显示格式

**主要改动**:
1. 增强 utils/format.ts:
   - 新增 formatDateOnly() - 格式: YYYY-MM-DD
   - 新增 formatDateTime() - 格式: YYYY-MM-DD HH:mm:ss

2. 全局替换视图中的格式化函数:
   - 移除各视图中的自定义 formatDate 函数
   - 统一导入使用 @/utils/format 中的函数
   - created_at/updated_at 使用 formatDateTime
   - expires_at 使用 formatDateOnly

3. 受影响的视图 (8个):
   - frontend/src/views/user/KeysView.vue
   - frontend/src/views/user/DashboardView.vue
   - frontend/src/views/user/UsageView.vue
   - frontend/src/views/user/RedeemView.vue
   - frontend/src/views/admin/UsersView.vue
   - frontend/src/views/admin/UsageView.vue
   - frontend/src/views/admin/RedeemView.vue
   - frontend/src/views/admin/SubscriptionsView.vue

**效果**:
- 日期统一显示为 YYYY-MM-DD
- 时间统一显示为 YYYY-MM-DD HH:mm:ss
- 提升可维护性,避免格式不一致

* fix(frontend): 补充遗漏的时间格式化统一

**补充修复**(基于 code review 发现的遗漏):

1. 增强 utils/format.ts:
   - 新增 formatTime() - 格式: HH:mm

2. 修复 4 个遗漏的文件:
   - src/views/admin/UsersView.vue
     * 删除 formatExpiresAt(),改用 formatDateTime()
     * 修复订阅过期时间 tooltip 显示格式不一致问题

   - src/views/user/ProfileView.vue
     * 删除 formatMemberSince(),改用 formatDate(date, 'YYYY-MM')
     * 统一会员起始时间显示格式

   - src/views/user/SubscriptionsView.vue
     * 修改 formatExpirationDate() 使用 formatDateOnly()
     * 保留天数计算逻辑

   - src/components/account/AccountStatusIndicator.vue
     * 删除本地 formatTime(),改用 utils/format 中的统一函数
     * 修复 rate limit 和 overload 重置时间显示

**验证**:
- TypeScript 类型检查通过 ✓
- 前端构建成功 ✓
- 所有剩余的 toLocaleString() 都是数字格式化,属于正确用法 ✓

**效果**:
- 订阅过期时间统一为 YYYY-MM-DD HH:mm:ss
- 会员起始时间统一为 YYYY-MM
- 重置时间统一为 HH:mm
- 消除所有不规范的原生 locale 方法调用
2025-12-27 10:50:25 +08:00
IanShaw
cf8a64528c fix: 修复 Gemini API 认证和 /responses 端点路由问题 (#45)
* fix(middleware): 修复 Gemini API Key 认证中间件用户上下文类型错误

修复了 ApiKeyAuthWithSubscriptionGoogle 中间件中设置用户上下文时的类型错误。

**问题:**
- 中间件直接设置 `apiKey.User` 对象到上下文
- 导致 handler 中获取 `AuthSubject` 时类型断言失败
- 所有 Gemini v1beta 端点返回 500 "User context not found"

**修复:**
- 改为设置 `AuthSubject` 结构体,与 `api_key_auth.go` 保持一致
- 添加 `ContextKeyUserRole` 设置以完整支持角色检查

**影响范围:**
- Gemini v1beta API 端点 (generateContent, streamGenerateContent)
- 使用 Google API Key 认证的所有请求

**测试:**
- 验证 Gemini CLI 调用成功返回 200
- 确认用户上下文正确传递到 handler

* fix(web): 修复 /responses 端点被前端中间件拦截的问题

- 将 /responses 路径添加到 API 白名单,防止其被当作前端路由处理
- 修复 /responses 端点返回 HTML 而非 API 响应的 BUG
- 解决 codex CLI stream 在远程服务器上断开连接的问题

根本原因:
在 6c469b4 提交中添加了 /responses 路由,但未同步更新前端嵌入中间件
的 API 白名单,导致该路由被拦截并返回 index.html 而非 API 响应。
2025-12-27 10:50:15 +08:00
IanShaw027
2714be99a9 feat(test): 添加 Gemini 双响应格式支持
添加对两种 Gemini 响应格式的支持:
- AI Studio: `{"candidates": [...]}`
- Gemini CLI: `{"response": {"candidates": [...]}}`

通过 unwrap 逻辑自动检测并适配两种格式,确保账号测试功能
对所有 Gemini 账号类型都能正常工作。

合并 PR #43 的剩余功能到 PR #37
2025-12-26 22:31:12 +08:00