Commit Graph
3694 Commits
Author SHA1 Message Date
erio 261ea08892 fix: correct account stats pricing priority order
Priority was wrong:
- Before: custom rules → LiteLLM (when ApplyPricingToAccountStats) → nil
- After:  custom rules → totalCost (when ApplyPricingToAccountStats) → LiteLLM → nil

When ApplyPricingToAccountStats is enabled, use the request's actual
client billing cost (before multiplier) as account_stats_cost, instead
of recalculating from LiteLLM per-token prices which produced incorrect
values for per-request billing mode.

LiteLLM model pricing is now the final fallback (priority 3), used only
when neither custom rules nor ApplyPricingToAccountStats apply.
2026-04-13 16:45:10 +08:00
erio c6a9da4ea9 debug: add notification path logging for beta investigation
Add slog.Info/Debug logs to notifyBalanceLow, notifyAccountQuota,
CheckBalanceAfterDeduction, and sendEmails to diagnose why balance
and quota notifications are not being sent in beta environment.
2026-04-13 16:20:07 +08:00
erio 49131efb42 chore: bump version to 0.1.110.25 2026-04-13 16:00:52 +08:00
erio 9c9945071f fix: expose account_stats_cost in usage log API and fix frontend display
Backend:
- Add AccountStatsCost field to AdminUsageLog DTO
- Map AccountStatsCost in UsageLogFromServiceAdmin mapper
- Replace println with slog.Warn in api_key_auth_cache_impl.go

Frontend:
- Add account_stats_cost to AdminUsageLog TypeScript interface
- Usage tooltip and table: prefer account_stats_cost when available,
  fallback to total_cost * multiplier (mirrors SQL COALESCE logic)
- Excel export: same COALESCE fallback
2026-04-13 16:00:52 +08:00
erio 2ec9e345f6 style: fix gofmt alignment in InstanceSelection struct 2026-04-13 15:51:11 +08:00
erio 71e598a5bd chore: bump version to 0.1.110.24 2026-04-13 15:30:06 +08:00
erio 068407d884 fix: add missing AccountQuotaNotifyEnabled to admin settings API
The field was present in SystemSettings response DTO and service layer
but missing from:
- UpdateSettingsRequest (admin handler) - saves were silently ignored
- GET/PUT response mapping in admin handler
- UpdateSettingsRequest (non-admin dto)

This caused the toggle to always revert to off after saving.
2026-04-13 15:30:06 +08:00
erio 76c5043db7 fix(frontend): simplify websearch select labels and reduce width
- "默认(跟随渠道)" → "默认", "Default (follow channel)" → "Default"
- Move "follows channel config" info to description text
- Reduce select width from w-32 to w-24 in both Edit and Create modals
2026-04-13 15:20:00 +08:00
erio 913bd1137a chore: bump version to 0.1.110.23 2026-04-13 15:13:56 +08:00
erio c85272606d fix(frontend): hide quota notify toggle when global setting is disabled
QuotaLimitCard now requires quotaNotifyGlobalEnabled prop to control
visibility of QuotaNotifyToggle components. When the global account
quota notification is disabled in admin settings, per-account threshold
toggles are hidden in both Edit and Create account modals.
2026-04-13 15:13:56 +08:00
erio fbef97c552 chore: bump version to 0.1.110.22 2026-04-13 15:03:49 +08:00
erio f7f2c82981 fix: use DB transaction return values for balance/quota notification crossing detection
Replace the stale Redis cache read + invalidation hack with direct DB
transaction RETURNING values:

- deductUsageBillingBalance: add RETURNING balance to get post-deduction balance
- incrementUsageBillingAccountQuota: extend RETURNING to include all 6 quota
  dimensions (daily/weekly/total used+limit)
- Expand UsageBillingApplyResult with NewBalance and QuotaState fields
- finalizePostUsageBilling uses result directly, no Redis read needed
- CheckAccountQuotaAfterIncrement accepts optional QuotaState to skip
  fetchFreshAccount when DB values are available

This eliminates the race condition where QueueDeductBalance async update
hadn't propagated to Redis before GetUserBalance read, causing oldBalance
to be inflated and threshold crossing detection to always fail.
2026-04-13 15:03:49 +08:00
erio ee460dbcbb chore: bump version to 0.1.110.21 2026-04-13 14:21:53 +08:00
erio c37cff6a86 fix: round 3 audit fixes - SMTP header sanitization and goroutine safety
- Move sanitizeEmailHeader to SendEmailWithConfig entry point, covering all
  email senders (verify code, password reset, ops alerts, notifications)
- Add panic recovery to UpdateBalance goroutine
- Fix stale comment in getAccountQuotaNotifyEmails (email="" no longer used)
- Log error instead of silently discarding verifyNotifyCode cache update failure
2026-04-13 14:21:37 +08:00
erio c11fc081b7 chore: bump version to 0.1.110.20 2026-04-13 13:59:49 +08:00
erio ad351d4252 fix: audit fixes for websearch, notifications, and channel pricing
P0: fix wildcard matching test assertion (config order, not longest prefix)
P0: add TotalRecharged to auth cache snapshot (v5) for percentage threshold
P1: move pricing rules into per-platform sections in ChannelsView
P1: populate account name cache when editing existing channel rules
P1: sanitize email subject headers to prevent SMTP injection
P1: make Redis INCR+EXPIRE idempotent for rate limiting
P1: deep copy FeaturesConfig in Channel.Clone()
P2: clean up stale email="" placeholder comments
P2: replace log.Printf with slog in email_service.go
2026-04-13 13:59:35 +08:00
erio 47a13d3105 fix(frontend): lower QR code error correction level to reduce density
Payment QR codes used 'H' (30% redundancy) error correction which made
the codes too dense to scan on some phones. Lower to 'M' (15%) when a
logo overlay is present and 'L' (7%) without logo, significantly
reducing module density while maintaining scannability.

Closes #1607
2026-04-13 13:19:24 +08:00
erio 48d7c2d0ba fix(payment): fix Alipay/Wxpay direct provider type mapping and enable cross-provider load balancing
Two issues fixed:

1. Alipay.SupportedTypes() returned ["alipay_direct"] and Wxpay returned
   ["wxpay_direct"], but the frontend sends payment_type="alipay"/"wxpay".
   The registry lookup failed with "payment method (alipay) is not
   configured". Fix: return the base types ["alipay"]/["wxpay"].

2. When multiple providers support the same payment type (e.g. EasyPay
   and Alipay direct both handle "alipay"), only the last-registered
   provider's instances were reachable — the registry mapped one type to
   one provider key, and SelectInstance queried by that single key.

   Fix: bypass the registry in invokeProvider and let SelectInstance
   query across all providers when providerKey is empty. The selected
   instance's own ProviderKey (now included in InstanceSelection) is
   used to create the correct provider, enabling true cross-provider
   load balancing.

Closes #1592
2026-04-13 13:19:24 +08:00
erio ff66bb5c36 chore: bump version to 0.1.110.19 2026-04-13 12:41:37 +08:00
erio 22a4c1f711 fix: address remaining audit findings (rate limit, wildcard order, SSE errors)
Severe fixes:
- SendNotifyEmailCode: add user-level rate limit (5 codes per 10min)
  via Redis counter, new EmailCache methods IncrNotifyCodeUserRate/
  GetNotifyCodeUserRate
- findPricingForModel: change wildcard matching from longest-prefix-first
  to configuration-order-first (first match wins), matching channel
  cache behavior
- channel_service.go: fix misleading comments about prefix length sorting

Medium fixes:
- SSE flushSSEJSON: check fmt.Fprintf write errors, chain error handling
  in writeWebSearchStreamResponse with early termination
- validateWebSearchConfig: validate APIKey non-empty after merge for
  enabled configurations
- user_service.go: log.Printf replaced with slog.Error
- buildNotifyVerifyEmailBody: extract HTML template to const
- SendNotifyEmailCode: split into checkNotifyCodeRateLimit,
  saveNotifyVerifyCode, sendNotifyVerifyEmail (each ≤30 lines)
- VerifyAndAddNotifyEmail: split into verifyNotifyCode,
  addOrVerifyNotifyEmail (each ≤30 lines)
- Document known TOCTOU race in addOrVerifyNotifyEmail (small window,
  harmless worst case)
2026-04-13 12:41:37 +08:00
erio 68f546e3ec chore: bump version to 0.1.110.18 2026-04-13 12:07:09 +08:00
erio 07ff71f78c fix: address audit findings across websearch, notify, and channel pricing
Backend fixes:
- Fix balance notify ignoring percentage threshold type (was treating
  percentage value as fixed USD amount)
- Remove dead code parseJSONStringArray
- Add ImageOutputTokens to tryModelFilePricing calculation
- Unify zero-value check: cost == 0 → cost <= 0 in calculateTokenStatsCost
- Use MarshalNotifyEmails instead of json.Marshal for consistency
- Rename quotaDim.oldUsed → currentUsed for clarity
- Extract HTML email templates to const variables (function ≤30 lines)

Test fixes:
- Rewrite account_websearch_test.go for GetWebSearchEmulationMode tri-state
- Add 6 tryModelFilePricing test cases

Frontend fixes:
- Replace hardcoded '未命名' with i18n key
- Extract getBillingModeLabel/getBillingModeBadgeClass to shared utils
- Replace inline type with imported NotifyEmailEntry
- Pass platform to AccountStats pricing rules via inferRulePlatform()
- Add billing mode constants (BILLING_MODE_TOKEN/PER_REQUEST/IMAGE)
2026-04-13 12:07:09 +08:00
erio ae9ea2e766 chore: bump version to 0.1.110.17 2026-04-13 11:37:08 +08:00
erio 834ba46da9 feat: WebSearch tri-state, account stats pricing fix, quota cache fix, usage tooltip
WebSearch tri-state switch:
- Account-level web_search_emulation changed from bool to tri-state
  string: "default" (follow channel) / "enabled" / "disabled"
- shouldEmulateWebSearch checks channel config when account is "default"
- SQL migration converts old bool values
- Frontend select replaces toggle in Edit/CreateAccountModal

Account stats pricing:
- resolveAccountStatsCost uses upstream model (post-mapping) for matching
- Priority: custom rules → model pricing file (when toggle on) → default
- Custom rules always configurable, independent of toggle
- Account ID field changed to searchable selector filtered by platform
- Description updated to reflect new behavior

Quota notification cache fix:
- CheckAccountQuotaAfterIncrement fetches real-time account from DB
- Reconstructs pre-increment usage for accurate threshold crossing detection
- New AccountQuotaReader interface (minimal: GetByID only)

Usage tooltip:
- Per-request/image billing shows per-request price instead of $0 token price
- Token billing continues to show input/output price per million tokens
2026-04-13 11:37:08 +08:00
erio 4f4267d646 chore: bump version to 0.1.110.16 2026-04-13 02:28:31 +08:00
erio 3f0fbecf35 fix(channel): use upstream model for account stats pricing and remove channel pricing fallback
- 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: "启用后将支持自定义账号统计的模型价格"
2026-04-13 02:28:31 +08:00
erio e85ea0694b chore: bump version to 0.1.110.15 2026-04-13 01:40:13 +08:00
erio ef16d28481 fix(notify): add verification flow for saved unverified emails
- 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
2026-04-13 01:40:13 +08:00
erio 621f767e29 chore: bump version to 0.1.110.14 2026-04-13 01:29:22 +08:00
erio 794e75a32b fix(notify): use real-time balance for crossing detection and simplify email logic
- 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)
2026-04-13 01:29:07 +08:00
erio 92ccda5859 chore: bump version to 0.1.110.13 2026-04-13 00:52:56 +08:00
erio ac4876646b feat(notify): convert email lists to NotifyEmailEntry struct with toggle support
- 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
2026-04-13 00:52:42 +08:00
erio db264c4a12 feat(payment): add per-provider allow_user_refund control
- Add allow_user_refund field to payment_provider_instances (migration 103)
- Backend: validateRefundRequest checks allow_user_refund for user requests
- Backend: PrepareRefund checks refund_enabled for admin refunds
- Legacy orders (no provider_instance_id) default to blocking refund
- Cascade: disabling refund_enabled auto-disables allow_user_refund
- Frontend: ProviderCard/Dialog show allow_user_refund toggle when refund_enabled
- Frontend: UserOrdersView checks eligible providers before showing refund button
- New API: GET /payment/orders/refund-eligible-providers
2026-04-12 21:38:34 +08:00
erio 80734ebab4 fix(notify): add explicit save button for balance threshold
Replace blur-based auto-save with an explicit Save button so users
know when their threshold is persisted. Shows success toast on save.
2026-04-12 20:45:58 +08:00
erio f0708df77c fix(notify): add duplicate email check message and improve extra email UX 2026-04-12 20:40:31 +08:00
erio 456df20d45 feat(notify): improve balance notify card UX
- 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
2026-04-12 20:29:26 +08:00
erio 8a629e1085 fix(notify): add balance/quota notify flags to PublicSettings DTO and handler
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.
2026-04-12 20:10:24 +08:00
erio df6ff64b76 fix(websearch): hide show/copy buttons when API key is empty
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.
2026-04-12 19:46:26 +08:00
erio 30cfceaabd fix(websearch): add 15s timeout for admin test search 2026-04-12 18:51:34 +08:00
erio cb4ac59d5a refactor(channels): move account stats pricing rules from basic to platform tabs
- 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
2026-04-12 18:29:21 +08:00
erio adc5fa3ee1 chore: bump version to 0.1.110.12 2026-04-12 18:12:13 +08:00
erio 10e1897a43 fix: address audit findings for notify, websearch and security
- Fix GetByKeyForAuth missing user.FieldEmail and user.FieldUsername (notifications sent to empty address)
- Guard against empty email in collectBalanceNotifyRecipients
- Remove non-atomic TotalRecharged read-modify-write in admin balance adjustment
- HTML-escape userName/siteName/accountName in notification email templates
- Fix timer leak in ProfileBalanceNotifyCard (add onUnmounted cleanup)
- Add warning log on websearch proxy URL resolution failure
2026-04-12 18:11:47 +08:00
erio b7d23054f3 feat(notify): add global toggles, percentage threshold, and visibility control
- 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
2026-04-12 17:49:58 +08:00
erio 3ac935f047 fix(websearch): improve settings UI and hide config when globally disabled
- 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
2026-04-12 15:59:45 +08:00
erio a3da2465b7 chore: bump version to 0.1.110.10 2026-04-12 15:01:26 +08:00
erio 877e681afd fix(notify): remove percentage threshold from balance notification
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.
2026-04-12 15:01:10 +08:00
erio 08235a462c chore: bump version to 0.1.110.9 2026-04-12 14:48:26 +08:00
erio 8fe7110d7f fix: address audit findings for websearch and balance notification
- 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
2026-04-12 14:43:12 +08:00
erio 72c836141e feat(notify): add percentage threshold type for balance low notification
- 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
2026-04-12 13:53:02 +08:00
erio f059ad02be chore: bump version to 0.1.110.8 2026-04-12 13:18:30 +08:00