Skip to content

feat: 分渠道额度(互斥模式+周期重置)+ 对话详情独立库(管理员可见) - #8

Open
LoSealL wants to merge 18 commits into
developfrom
feature/per-channel-quota-and-chat-detail-db
Open

feat: 分渠道额度(互斥模式+周期重置)+ 对话详情独立库(管理员可见)#8
LoSealL wants to merge 18 commits into
developfrom
feature/per-channel-quota-and-chat-detail-db

Conversation

@LoSealL

@LoSealL LoSealL commented Jul 25, 2026

Copy link
Copy Markdown

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

AI 辅助声明: 本分支代码由 AI(opencode + GLM-5.2,subagent-driven-development 流程)在人工设计与审查下辅助生成并提交。提交者 Wenyi Tang 非仓库历史核心维护者。每个任务均经过 spec compliance + code quality 双重审查与回归验证。

📝 变更描述 / Description

围绕单个 API-Key(令牌)实现两个相互独立、均可独立开关的功能:

功能一:分渠道额度(互斥模式)

  • 令牌新增 channel_quota_mode 开关。开启后令牌总额度 remain_quota 不再生效,改为按「令牌 × 渠道」独立额度(新表 token_channel_quotas)。
  • 计费链路在 service 层集中改造,未触及 40+ 渠道适配器:
    • 预扣费:PreConsumeTokenQuotaChannel 命中已配置渠道则扣减渠道行,未配置渠道不限额(不自动建行),额度不足返回 403。
    • BillingSessionpreConsume/Settle/Refund/Reserve/reserveToken/shouldTrust 均按渠道模式路由;信任旁路在渠道模式下强制禁用以保证精确记账。
    • 周期重置复用令牌自身的 reset_period + next_reset_time,到点批量重置该令牌所有渠道行(各自恢复到自己的 reset_quota)。
  • 管理 API:GET/PUT /api/token/:id/channel_quotas(令牌所有者或管理员)。
  • 前端(web/default):令牌编辑抽屉新增「分渠道额度」开关;开启后隐藏总额度相关输入,展示「渠道 + 额度」子表格(编辑器禁用重复渠道),周期沿用令牌已有选择器。

功能二:对话详情独立库(管理员可见)

  • 令牌新增 chat_log_enabled 开关。开启且配置了 CHAT_LOG_SQL_DSN 时,完整请求/响应 JSON 写入独立库(未配置则静默禁用)。
  • 新增 CHATLOG_DB 句柄与 InitChatLogDB(仿 InitLogDB),支持 SQLite/MySQL/PostgreSQL;MySQL 上将大文本列升为 LONGTEXT,PG/SQLite 用 TEXT(无限长)。
  • 协议无关采集:chatLogCaptureWriter 包装 gin.ResponseWriter,缓冲响应字节(默认上限 256KB,超限截断并标记),流式与非流式统一覆盖(嵌入类型透传 Flush/Hijack)。
  • 中继入口 controller/relay.goRelay 在令牌开启且库已配置时条件式安装采集器,成功路径异步落库(gopool 闭包内不访问 gin context,避免回收竞态)。
  • 管理员独占查询 API:GET /api/chat_logs(分页 + 元数据)、GET /api/chat_logs/:id(含请求/响应体),挂载在 AdminAuth 中间件之后。
  • 前端(web/default):新增管理员「对话详情」页面(TanStack Table 列表 + Sheet 详情用 CodeBlock 展示 JSON),仅 role >= ADMIN 可见,侧边栏入口已加。

向后兼容

两个新开关默认 falseCHAT_LOG_SQL_DSN 默认未设 → 对存量令牌与部署零行为变化。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix)
  • ✨ 新功能 (New feature)
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • 无对应 Issue(按用户需求开发)

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 本仓库无既有分渠道额度 / 对话详情独立库功能。
  • Bug fix 说明: N/A(新功能)
  • 变更理解: 完整实现 + spec compliance + code quality 双重 subagent 审查 + 整体终审;计费安全不变式(无负数扣费、并发原子 gorm.Expr、信任旁路在渠道模式禁用、gin context 不在 goroutine 内访问)已核对。
  • 范围聚焦: 50 个文件改动均围绕这两项功能;未夹带无关重构(前端构建期间产生的 ~190 个 lint 自动修复已回滚,未纳入本 PR)。
  • 本地验证: go build ./... 通过;后端 26 个新增测试全部 PASS(model/service/controller 三层);前端 bun run buildbun run typecheck 通过;既有 3 个失败用例(TestChannelFieldsAreClassifiedTestDetectKeyLimitParsesChineseQuotaTestObserveChannelAffinityUsageCacheByRelayFormat_*)已在 base 74af36d3 复现,与本 PR 无关。
  • 安全合规: 无敏感凭据;JSON 全部走 common.* 封装;DB 代码三库兼容(GORM 方法 + gorm.Expr,仅 MySQL LONGTEXT 升级为有意为之的方言分支)。

📸 运行证明 / Proof of Work

后端测试结果(go test,节选):

--- PASS: TestTokenChannelQuota_CRUD (0.01s)
--- PASS: TestTokenChannelQuota_NegativeQuotaRejected (0.00s)
--- PASS: TestUpsertTokenChannelQuota_OverwritesExistingRow (0.00s)
--- PASS: TestReplaceTokenChannelQuotas_FullOverwrite (0.00s)
--- PASS: TestMaybeResetTokenQuota_ChannelMode (0.00s)
--- PASS: TestMaybeResetTokenQuota_ChannelMode_NotYetTime (0.00s)
--- PASS: TestPreConsumeTokenQuota_ChannelMode_HitRow (0.00s)
--- PASS: TestPreConsumeTokenQuota_ChannelMode_Insufficient (0.00s)
--- PASS: TestPreConsumeTokenQuota_ChannelMode_NoRow_NoLimit (0.00s)
--- PASS: TestPreConsumeTokenQuota_TotalMode_Delegates (0.00s)
--- PASS: TestBillingSession_SettleChannelMode_Supplement (0.00s)
--- PASS: TestBillingSession_SettleChannelMode_RefundDelta (0.00s)
--- PASS: TestBillingSession_SettleChannelMode_UnconfiguredChannel_NoOp (0.01s)
--- PASS: TestBillingSession_RefundChannelMode (0.00s)
--- PASS: TestChatLog_CreateAndQuery (0.00s)
--- PASS: TestChatLog_SearchFilters (0.00s)
--- PASS: TestChatLogCaptureWriter_BuffersBytes (0.00s)
--- PASS: TestChatLogCaptureWriter_TruncatesAtCap (0.00s)
--- PASS: TestChatLogCaptureWriter_NoBufferWhenMaxZero (0.00s)
--- PASS: TestChatLogCaptureWriter_MultiWriteAccumulates (0.00s)
--- PASS: TestChatLogCaptureWriter_PreservesStatusCode (0.00s)
--- PASS: TestMaybeInstallChatLogCapture_DisabledReturnsNil (0.00s)
--- PASS: TestMaybeInstallChatLogCapture_EnabledWhenDBOn (0.00s)
--- PASS: TestChatLogRecorder_PersistInsertsRow (0.00s)
--- PASS: TestGetTokenChannelQuotas_OwnerAllowed (0.00s)
--- PASS: TestGetTokenChannelQuotas_ForbiddenForOtherUser (0.00s)
--- PASS: TestUpdateTokenChannelQuotas_Replace (0.00s)
--- PASS: TestAdminGetChatLogs_ListAndDetail (0.00s)
--- PASS: TestAdminGetChatLogs_ForbiddenForNonAdmin (0.00s)

前端:bun run buildready built in 5.78sbun run typecheck → 0 errors。

设计与实现文档

  • 设计文档:docs/superpowers/specs/2026-07-24-per-channel-quota-and-chat-detail-db-design.md
  • 实现计划:docs/superpowers/plans/2026-07-24-per-channel-quota-and-chat-detail-db.md

配置说明(部署方需知)

  • 功能一:在令牌编辑页开启「分渠道额度」并配置每渠道额度即可;周期沿用令牌 reset_period
  • 功能二:设置环境变量 CHAT_LOG_SQL_DSN(与 SQL_DSN/LOG_SQL_DSN 同格式,支持 SQLite/MySQL/PostgreSQL),并在需要记录的令牌上开启「对话详情记录」。未配置 DSN 时该功能静默禁用。

Copilot AI review requested due to automatic review settings July 25, 2026 03:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds two token-scoped features to new-api: (1) per-channel token quota mode with periodic resets, and (2) admin-only chat log detail storage in a standalone database, plus corresponding admin UI in web/default.

Changes:

  • Implement per-channel quota mode (token_channel_quotas) and route token billing/pre-consume/settle/refund through channel rows when enabled.
  • Add chat-log standalone DB initialization/migration + request/response capture and admin-only query APIs (/api/chat_logs).
  • Add admin UI pages/routes for Chat Logs, plus per-channel quota editor in the token edit drawer (with i18n updates).

Reviewed changes

Copilot reviewed 50 out of 50 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
web/default/src/routeTree.gen.ts Registers the new authenticated /chat-logs route.
web/default/src/routes/_authenticated/chat-logs/index.tsx Adds admin-gated route loader + search schema for Chat Logs page.
web/default/src/hooks/use-sidebar-data.ts Adds “Chat Logs” entry to admin sidebar.
web/default/src/features/chat-logs/api.ts Adds frontend API client for chat log list/detail endpoints.
web/default/src/features/chat-logs/types.ts Defines chat log DTOs used by the UI.
web/default/src/features/chat-logs/index.tsx Adds Chat Logs page layout wrapper.
web/default/src/features/chat-logs/components/chat-logs-table.tsx Implements admin table view + filters + detail sheet trigger.
web/default/src/features/chat-logs/components/chat-log-detail-sheet.tsx Implements chat log detail viewer with request/response JSON display.
web/default/src/features/keys/types.ts Extends token schema/types with channel_quota_mode and per-channel quota DTOs.
web/default/src/features/keys/api.ts Adds token per-channel quota GET/PUT client calls.
web/default/src/features/keys/lib/api-key-form.ts Adds form fields/defaults and payload mapping for channel_quota_mode.
web/default/src/features/keys/components/channel-quota-editor.tsx New per-channel quota editor UI component.
web/default/src/features/keys/components/api-keys-mutate-drawer.tsx Integrates per-channel quota mode toggle + quota editor + API calls in token drawer.
web/default/src/i18n/locales/en.json Adds new UI translation keys for per-channel quota + chat logs.
web/default/src/i18n/locales/zh.json Adds new UI translation keys for per-channel quota + chat logs.
web/default/src/i18n/locales/zh-TW.json Adds new keys for zh-TW (some values currently left as English).
web/default/src/i18n/locales/fr.json Adds new UI translation keys for per-channel quota + chat logs.
web/default/src/i18n/locales/ru.json Adds new UI translation keys for per-channel quota + chat logs.
web/default/src/i18n/locales/ja.json Adds new UI translation keys for per-channel quota + chat logs.
web/default/src/i18n/locales/vi.json Adds new UI translation keys for per-channel quota + chat logs.
common/database.go Adds chat-log database-type tracking helpers.
model/main.go Adds CHATLOG_DB handle + InitChatLogDB + chat log DB migration logic; migrates TokenChannelQuota in main DB.
model/token.go Adds channel_quota_mode and chat_log_enabled to Token and updates selectable update fields.
model/token_channel_quota.go Introduces token_channel_quotas model and CRUD/atomic quota updates.
model/token_reset.go Extends quota reset to reset all channel rows when channel quota mode is enabled.
model/chat_log.go Adds chat log model and query helpers for standalone DB.
service/quota.go Adds PreConsumeTokenQuotaChannel with channel-row enforcement + no-row unlimited semantics.
service/pre_consume_quota.go Disables trust bypass for channel quota mode; routes pre-consume through PreConsumeTokenQuotaChannel.
service/billing_session.go Routes pre-consume/settle/refund/reserve through per-channel quota logic when enabled.
service/chat_log_capture.go Implements response writer wrapper to buffer response bytes (currently missing WriteString capture).
service/chat_log_persist.go Installs capture wrapper conditionally and persists request/response to chat-log DB asynchronously.
router/chat-log-router.go Adds admin-only chat log API routing.
router/api-router.go Registers chat log routes and token channel quota endpoints.
controller/token.go Adds token per-channel quota GET/PUT endpoints.
controller/relay.go Conditionally installs chat log capture + persists on successful relay.
controller/chat_log.go Adds admin-only list/detail chat log APIs.
main.go Initializes chat log DB at startup.
service/task_billing_test.go Updates test DB setup to include new models/handles.
service/quota_channel_test.go Adds tests for per-channel pre-consume behavior.
service/billing_session_channel_test.go Adds tests for channel-mode settle/refund behavior.
service/chat_log_capture_test.go Tests capture buffering/truncation/status behavior.
service/chat_log_persist_test.go Tests recorder persistence into chat log DB.
model/token_channel_quota_test.go Adds model-level tests for per-channel quotas.
model/token_reset_test.go Adds tests for channel-mode periodic reset behavior.
model/chat_log_test.go Adds model tests for chat log insert/query.
model/task_cas_test.go Updates test DB setup to include new models/handles.
controller/token_channel_quota_handler_test.go Adds controller tests for token channel quota endpoints.
controller/chat_log_test.go Adds controller tests for admin chat log APIs.
docs/superpowers/specs/2026-07-24-per-channel-quota-and-chat-detail-db-design.md Adds design documentation for both features.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +54 to +58
func (w *chatLogCaptureWriter) capturedBytes() (string, bool) {
w.mu.Lock()
defer w.mu.Unlock()
return w.buffer.String(), w.truncated
}
Comment thread controller/token.go
Comment on lines +426 to +430
if err := model.ReplaceTokenChannelQuotas(tokenId, req.Items); err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"success": true})
Comment on lines +201 to +210
if s.channelQuotaMode {
s.channelHit = s.channelHit || hit
}

s.preConsumedQuota += delta
s.tokenConsumed += delta
if s.channelQuotaMode && s.channelHit {
s.channelConsumed += delta
} else if !s.channelQuotaMode {
s.tokenConsumed += delta
}
Comment on lines +218 to +232
if (data.channel_quota_mode) {
try {
const cqResult = await updateTokenChannelQuotas(
currentRow.id,
data.channel_quotas ?? []
)
if (!cqResult.success) {
toast.error(
cqResult.message || t(ERROR_MESSAGES.UPDATE_FAILED)
)
}
} catch (error) {
handleServerError(error)
}
}
Comment on lines 772 to 776
"Chat Client Name": "聊天用戶端名稱",
"Chat client name is required": "聊天用戶端名稱為必填項",
"Chat configuration JSON": "聊天設定 JSON",
"Chat Logs": "Chat Logs",
"Chat preset not found": "未找到聊天預設",
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants