- resolveAccountStatsCost now uses the final upstream model (after
account-level mapping) to match custom pricing rules, fixing the
issue where requested model (e.g. claude-sonnet-4-5) didn't match
rules configured for upstream model (e.g. claude-opus-4-6)
- Remove tryChannelPricing fallback — only custom rules are applied,
unmatched requests use default formula (total_cost × rate)
- Remove unused billingService and serviceTier parameters
- Update description: "启用后将支持自定义账号统计的模型价格"
- Add "verify" button next to saved unverified emails in
ProfileBalanceNotifyCard (send code → enter code → verify)
- Backend: VerifyAndAddNotifyEmail now marks existing unverified
emails as verified instead of returning "already exists"
- Inline verification UI with countdown timer and resend button
- Fix cached balance causing threshold crossing to never trigger:
read real-time balance from billingCacheService instead of stale
API key auth snapshot
- Remove email="" placeholder concept; all emails are user-managed
- Only send notifications to verified && non-disabled emails
- Frontend: pre-fill user's email in add input when list is empty
- Remove FilterEnabledEmails/IsPrimaryDisabled helpers (no longer needed)
- Change balance_notify_extra_emails and account_quota_notify_emails
from []string to []NotifyEmailEntry{email, disabled, verified}
- Add per-email enable/disable toggle for both user and admin notifications
- Add PUT /user/notify-email/toggle API endpoint
- Fix critical bug: API key auth cache snapshot missing balance notify
fields (Email, Username, BalanceNotifyEnabled, etc.), causing
notifications to never fire on cached request paths
- Bump cache snapshot version 3→4 to invalidate stale entries
- Add SQL migration 104 to convert old format data
- Backward compatible: parseNotifyEmails auto-detects old/new format
- User balance notify: max 3 emails (primary + 2 extra)
- Admin quota notify: unlimited emails, each with toggle
- Show system default threshold as placeholder in custom threshold input
- Display user's primary email with "Primary" badge
- Support adding multiple pending emails before verification
- Each pending email has independent send/verify/resend flow
- Expose balance_low_notify_threshold in PublicSettings API
- Clean up timers on unmount to prevent leaks
The service layer correctly populated BalanceLowNotifyEnabled and
AccountQuotaNotifyEnabled in PublicSettings, but the handler-to-DTO
mapping was missing. Users could not see the balance notify card because
the public settings API never returned these flags.
Only show the inline eye/copy buttons when provider.api_key has a value.
When only api_key_configured is true (saved key, not loaded), buttons are
hidden since there's nothing to show/copy.
- Basic settings now only shows the global toggle
- Custom pricing rules appear inside each platform tab when toggle is on
- Group selector in rules scoped to the current platform's groups
- Remove unused allFormGroupIds computed
- Add global toggle for account quota notification in admin settings
- Add percentage-based threshold type for per-account quota alerts
- Hide balance notify card on user profile when global toggle is off
- Expose balance_low_notify_enabled and account_quota_notify_enabled in PublicSettings
- Add threshold type (fixed/percentage) to QuotaNotifyToggle with $ / % switcher
- API Key show/copy buttons moved inside input field (inline icons)
- Proxy selector and test button on same row to save vertical space
- Test opens a dialog modal instead of inline display
- Hide all websearch config in channels/accounts when global toggle is off
Balance low notification only supports fixed USD amount threshold.
Percentage threshold is a quota concept, not applicable to balance.
Reverted threshold_type from admin settings, user profile, and all
backend/frontend layers. DB fields (balance_notify_threshold_type,
total_recharged) retained for potential future quota use.
- Fix GetByKeyForAuth not selecting balance notify fields (notifications
never triggered in gateway path)
- Fix provider-level ProxyURL never resolved: inject ProxyRepository into
SettingService, resolve proxy URLs when building Manager
- Fix admin manual balance adjustment not updating total_recharged
- Add threshold_type input validation (reject invalid values)
- Fix user threshold_type inheritance: custom threshold defaults to "fixed"
instead of inheriting global type (prevents $5 being treated as 5%)
- Add try-catch for clipboard.writeText (fails on non-HTTPS)
- Add SetTotalRecharged to user Update for admin balance operations
- Add threshold_type field (fixed/percentage) to system and user settings
- Add total_recharged field to users table, auto-incremented on balance credit
- Percentage mode: effective threshold = total_recharged × percentage / 100
- User-level threshold_type inherits from system default when not set
- Update admin settings UI with radio selector (fixed amount / percentage)
- Migration: 102_add_balance_notify_threshold_type.sql
- Remove Priority field, auto load-balance by quota remaining
- Replace QuotaRefreshInterval (daily/weekly/monthly) with SubscribedAt
(subscription date, monthly lazy refresh via Redis TTL)
- Add collapsible provider cards, API key show/copy, usage progress bar
- Add test endpoint (POST /web-search-emulation/test) bypassing quota
- Wire WebSearchManagerBuilder on startup (was never called before)
- Fix nextMonthlyReset day-of-month overflow (Jan 31 → Feb 28)
- Fix non-deterministic sort in selectByQuotaWeight
- Map ProxyID in builder for provider-level proxy tracking
- Fix frontend timezone drift in subscribed_at date picker
- Fix provider deletion index shift for expandedProviders state
- Use per-recipient context timeout in sendEmails to prevent later
recipients from failing due to shared timeout exhaustion
- Return updated user object from RemoveNotifyEmail handler for
frontend state consistency (matching VerifyNotifyEmail pattern)
- Fix accountCost calculation in finalizePostUsageBilling to match
postUsageBilling (always multiply by AccountRateMultiplier)
- Use strings.EqualFold for email dedup in collectBalanceNotifyRecipients
- Extract CheckAccountQuotaAfterIncrement into smaller functions:
buildQuotaDims + asyncSendQuotaAlert (< 30 lines each)
- Add "not splittable" comments for HTML template functions
- Extract QuotaNotifyToggle.vue sub-component to reduce
QuotaLimitCard.vue from 404 to 339 lines
- Use proxyutil.ConfigureTransportProxy for unified proxy protocol support
(HTTP/HTTPS/SOCKS5/SOCKS5H), replacing ad-hoc HTTP-only proxy code
- Proxy errors return ErrProxyUnavailable → gateway triggers account switch
via UpstreamFailoverError instead of fallback to direct connection
- Timeout: proxy dial 3s, TLS handshake 3s, data transfer 60s
- Mark proxy unavailable for 5 minutes in Redis on connectivity failure
- Quota-weighted load balancing: providers with quota_limit>0 are selected
by remaining quota (weighted random); quota_limit=0 providers treated as
0% weight and placed last
Allow channels to configure independent model pricing for account
statistics cost calculation, decoupled from user billing.
Backend:
- Migration 101: channels.apply_pricing_to_account_stats toggle,
channel_account_stats_pricing_rules/model_pricing tables,
usage_logs.account_stats_cost column
- resolveAccountStatsCost: match rules by group/account, then channel
pricing, fallback to original formula when unconfigured
- Integrate into both GatewayService.recordUsageCore and
OpenAIGatewayService.RecordUsage
- Update 8 account stats SQL queries to use
COALESCE(account_stats_cost, total_cost) * account_rate_multiplier
- 23 unit tests for matching, pricing lookup, and cost calculation
Frontend:
- Channel edit dialog: toggle + custom rules UI with group/account
multi-select and pricing entry cards
- API types and i18n (zh/en)
Service layer must not import redis directly (depguard rule). Replace
*redis.Client field with WebSearchManagerBuilder callback injected by
the infra layer. Also fix remaining errcheck in manager_test.go.
- Add errcheck handling for w.Write, json.Encode in test handlers
- Use defer func() { _ = resp.Body.Close() }() pattern
- Use safe two-return type assertions in websearch_config.go
- Fix gofmt alignment in setting_service.go struct fields
Inject web search capability for Claude Console (API Key) accounts that
don't natively support Anthropic's web_search tool. When a pure
web_search request is detected, the gateway calls Brave Search or Tavily
API directly and constructs an Anthropic-protocol-compliant SSE/JSON
response without forwarding to upstream.
Backend:
- New `pkg/websearch/` SDK: Brave and Tavily provider implementations
with io.LimitReader, proxy support, and Redis-based quota tracking
(Lua atomic INCR + TTL, DECR rollback on failure)
- Global config via `settings.web_search_emulation_config` (JSON) with
in-process cache + singleflight, input validation, API key merge on
save, and sanitized API responses
- Channel-level toggle via `channels.features_config` JSONB column
(DB migration 101)
- Account-level toggle via `accounts.extra.web_search_emulation`
- Request interception in `Forward()` with SSE streaming response
construction using json.Marshal (no manual string concatenation)
- Manager hot-reload: `RebuildWebSearchManager()` called on config save
and startup via `SetWebSearchRedisClient()`
- 70 unit tests covering providers, manager, config validation,
sanitization, tool detection, query extraction, and response building
Frontend:
- Settings → Gateway tab: Web Search Emulation config card with global
toggle, provider list (add/remove, API key, priority, quota, proxy)
- Channels → Anthropic tab: web search emulation toggle with global
state linkage (disabled when global off)
- Account Create/Edit modals: web search emulation toggle for API Key
type with Toggle component
- Full i18n coverage (zh + en)