feat: websearch quota enhancements and balance notify hint

- QuotaLimit changed to *int64 (null=unlimited, >0=limited)
- Add reset-usage endpoint (POST /admin/settings/web-search-emulation/reset-usage)
- Show quota usage in header always (collapsed and expanded)
- Add reset quota button in expanded provider view
- Quota input: empty=unlimited with ∞ placeholder, must be >0 if set
- Add email verification hint on balance notify card
This commit is contained in:
erio
2026-04-14 08:03:27 +08:00
parent 1e6912ea2e
commit 7c7292935e
12 changed files with 121 additions and 28 deletions

View File

@@ -24,7 +24,7 @@ type WebSearchProviderConfig struct {
Type string `json:"type"` // websearch.ProviderTypeBrave | Tavily
APIKey string `json:"api_key,omitempty"` // secret — omitted in API responses
APIKeyConfigured bool `json:"api_key_configured"` // read-only mask
QuotaLimit int64 `json:"quota_limit"` // 0 = unlimited
QuotaLimit *int64 `json:"quota_limit"` // nil = unlimited, >0 = limited
SubscribedAt *int64 `json:"subscribed_at,omitempty"` // subscription start (unix seconds); quota resets monthly
QuotaUsed int64 `json:"quota_used,omitempty"` // read-only: current usage from Redis
ProxyID *int64 `json:"proxy_id"` // optional proxy association
@@ -52,8 +52,8 @@ func validateWebSearchConfig(cfg *WebSearchEmulationConfig) error {
if !validProviderTypes[p.Type] {
return fmt.Errorf("provider[%d]: invalid type %q", i, p.Type)
}
if p.QuotaLimit < 0 {
return fmt.Errorf("provider[%d]: quota_limit must be >= 0", i)
if p.QuotaLimit != nil && *p.QuotaLimit < 0 {
return fmt.Errorf("provider[%d]: quota_limit must be > 0 or null", i)
}
if seen[p.Type] {
return fmt.Errorf("provider[%d]: duplicate type %q", i, p.Type)
@@ -299,6 +299,15 @@ func PopulateWebSearchUsage(ctx context.Context, cfg *WebSearchEmulationConfig)
return &out
}
// ResetWebSearchUsage deletes the Redis quota key for the given provider type.
func ResetWebSearchUsage(ctx context.Context, providerType string) error {
mgr := getWebSearchManager()
if mgr == nil {
return fmt.Errorf("web search manager not initialized")
}
return mgr.ResetUsage(ctx, providerType)
}
// SanitizeWebSearchConfig returns a copy with api_key fields masked and quota usage populated.
func SanitizeWebSearchConfig(ctx context.Context, cfg *WebSearchEmulationConfig) *WebSearchEmulationConfig {
if cfg == nil {