diff --git a/backend/cmd/server/VERSION b/backend/cmd/server/VERSION index 68dda295..5657b5e3 100644 --- a/backend/cmd/server/VERSION +++ b/backend/cmd/server/VERSION @@ -1 +1 @@ -0.1.110.51 +0.1.112.3 diff --git a/backend/internal/handler/admin/setting_handler.go b/backend/internal/handler/admin/setting_handler.go index 2324cc70..2a87e95c 100644 --- a/backend/internal/handler/admin/setting_handler.go +++ b/backend/internal/handler/admin/setting_handler.go @@ -1939,7 +1939,7 @@ func (h *SettingHandler) GetWebSearchEmulationConfig(c *gin.Context) { response.ErrorFrom(c, err) return } - response.Success(c, service.SanitizeWebSearchConfig(c.Request.Context(), cfg)) + response.Success(c, service.PopulateWebSearchUsage(c.Request.Context(), cfg)) } // UpdateWebSearchEmulationConfig 更新 Web Search 模拟配置 diff --git a/backend/internal/service/websearch_config.go b/backend/internal/service/websearch_config.go index 5658cec3..239e882a 100644 --- a/backend/internal/service/websearch_config.go +++ b/backend/internal/service/websearch_config.go @@ -277,6 +277,28 @@ func TestWebSearch(ctx context.Context, query string) (*WebSearchTestResult, err }, nil } +// PopulateWebSearchUsage returns a copy with quota usage populated from Redis (api_key kept as-is). +func PopulateWebSearchUsage(ctx context.Context, cfg *WebSearchEmulationConfig) *WebSearchEmulationConfig { + if cfg == nil { + return nil + } + out := *cfg + out.Providers = make([]WebSearchProviderConfig, len(cfg.Providers)) + + mgr := getWebSearchManager() + + for i, p := range cfg.Providers { + out.Providers[i] = p + out.Providers[i].APIKeyConfigured = p.APIKey != "" + + if mgr != nil { + used, _ := mgr.GetUsage(ctx, p.Type) + out.Providers[i].QuotaUsed = used + } + } + return &out +} + // SanitizeWebSearchConfig returns a copy with api_key fields masked and quota usage populated. func SanitizeWebSearchConfig(ctx context.Context, cfg *WebSearchEmulationConfig) *WebSearchEmulationConfig { if cfg == nil { diff --git a/frontend/src/views/admin/SettingsView.vue b/frontend/src/views/admin/SettingsView.vue index 3ef1c0ba..12f67187 100644 --- a/frontend/src/views/admin/SettingsView.vue +++ b/frontend/src/views/admin/SettingsView.vue @@ -1775,8 +1775,8 @@ @click.stop /> - - {{ provider.quota_used ?? 0 }} / {{ provider.quota_limit }} + + {{ provider.quota_used ?? 0 }} / {{ provider.quota_limit > 0 ? provider.quota_limit : '∞' }} {{ t('admin.settings.webSearchEmulation.apiKeyConfigured') }} @@ -1797,10 +1797,10 @@ v-model="provider.api_key" :type="apiKeyVisible[pIdx] ? 'text' : 'password'" class="input w-full text-sm" - :class="provider.api_key ? 'pr-16' : ''" + :class="(provider.api_key || provider.api_key_configured) ? 'pr-16' : ''" :placeholder="provider.api_key_configured ? '••••••••' : t('admin.settings.webSearchEmulation.apiKeyPlaceholder')" /> -
+
-
+
{{ t('admin.settings.webSearchEmulation.quotaUsage') }}: -
+
- {{ provider.quota_used ?? 0 }} / {{ provider.quota_limit }} +
+ {{ provider.quota_used ?? 0 }} / {{ provider.quota_limit > 0 ? provider.quota_limit : '∞' }}
@@ -3164,9 +3167,13 @@ async function loadWebSearchConfig() { async function saveWebSearchConfig(): Promise { try { + const providers = webSearchConfig.providers.map((p: WebSearchProviderConfig) => ({ + ...p, + quota_limit: typeof p.quota_limit === 'number' && p.quota_limit > 0 ? p.quota_limit : 0, + })) await adminAPI.settings.updateWebSearchEmulationConfig({ enabled: webSearchConfig.enabled, - providers: webSearchConfig.providers as WebSearchProviderConfig[], + providers, }) return true } catch (err: unknown) {