fix: show websearch API key visibility/copy buttons for saved providers
The buttons were hidden because v-if only checked provider.api_key, which is always empty for saved providers (backend sanitizes it). Now also checks api_key_configured. Copy button is disabled when no actual key is available (only configured placeholder shown).
This commit is contained in:
@@ -1 +1 @@
|
|||||||
0.1.110.51
|
0.1.112.3
|
||||||
|
|||||||
@@ -1939,7 +1939,7 @@ func (h *SettingHandler) GetWebSearchEmulationConfig(c *gin.Context) {
|
|||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
response.Success(c, service.SanitizeWebSearchConfig(c.Request.Context(), cfg))
|
response.Success(c, service.PopulateWebSearchUsage(c.Request.Context(), cfg))
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateWebSearchEmulationConfig 更新 Web Search 模拟配置
|
// UpdateWebSearchEmulationConfig 更新 Web Search 模拟配置
|
||||||
|
|||||||
@@ -277,6 +277,28 @@ func TestWebSearch(ctx context.Context, query string) (*WebSearchTestResult, err
|
|||||||
}, nil
|
}, 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.
|
// SanitizeWebSearchConfig returns a copy with api_key fields masked and quota usage populated.
|
||||||
func SanitizeWebSearchConfig(ctx context.Context, cfg *WebSearchEmulationConfig) *WebSearchEmulationConfig {
|
func SanitizeWebSearchConfig(ctx context.Context, cfg *WebSearchEmulationConfig) *WebSearchEmulationConfig {
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
|
|||||||
@@ -1775,8 +1775,8 @@
|
|||||||
@click.stop
|
@click.stop
|
||||||
/>
|
/>
|
||||||
<!-- Quota summary in collapsed state -->
|
<!-- Quota summary in collapsed state -->
|
||||||
<span v-if="!expandedProviders[pIdx] && provider.quota_limit > 0" class="text-xs text-gray-400">
|
<span v-if="!expandedProviders[pIdx]" class="text-xs text-gray-400">
|
||||||
{{ provider.quota_used ?? 0 }} / {{ provider.quota_limit }}
|
{{ provider.quota_used ?? 0 }} / {{ provider.quota_limit > 0 ? provider.quota_limit : '∞' }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="!expandedProviders[pIdx] && provider.api_key_configured" class="text-xs text-green-500">
|
<span v-if="!expandedProviders[pIdx] && provider.api_key_configured" class="text-xs text-green-500">
|
||||||
{{ t('admin.settings.webSearchEmulation.apiKeyConfigured') }}
|
{{ t('admin.settings.webSearchEmulation.apiKeyConfigured') }}
|
||||||
@@ -1797,10 +1797,10 @@
|
|||||||
v-model="provider.api_key"
|
v-model="provider.api_key"
|
||||||
:type="apiKeyVisible[pIdx] ? 'text' : 'password'"
|
:type="apiKeyVisible[pIdx] ? 'text' : 'password'"
|
||||||
class="input w-full text-sm"
|
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')"
|
:placeholder="provider.api_key_configured ? '••••••••' : t('admin.settings.webSearchEmulation.apiKeyPlaceholder')"
|
||||||
/>
|
/>
|
||||||
<div v-if="provider.api_key" class="absolute inset-y-0 right-0 flex items-center pr-1.5">
|
<div v-if="provider.api_key || provider.api_key_configured" class="absolute inset-y-0 right-0 flex items-center pr-1.5">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
class="rounded p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||||
@@ -1818,7 +1818,9 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
class="rounded p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||||
|
:class="{ 'opacity-30 cursor-not-allowed': !provider.api_key }"
|
||||||
:title="t('admin.settings.webSearchEmulation.copyApiKey')"
|
:title="t('admin.settings.webSearchEmulation.copyApiKey')"
|
||||||
|
:disabled="!provider.api_key"
|
||||||
@click="copyApiKey(pIdx)"
|
@click="copyApiKey(pIdx)"
|
||||||
>
|
>
|
||||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
@@ -1849,16 +1851,17 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Usage display -->
|
<!-- Usage display -->
|
||||||
<div v-if="provider.quota_limit > 0" class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-xs text-gray-500">{{ t('admin.settings.webSearchEmulation.quotaUsage') }}:</span>
|
<span class="text-xs text-gray-500">{{ t('admin.settings.webSearchEmulation.quotaUsage') }}:</span>
|
||||||
<div class="flex-1 rounded-full bg-gray-200 dark:bg-dark-600" style="height: 6px">
|
<div v-if="provider.quota_limit > 0" class="flex-1 rounded-full bg-gray-200 dark:bg-dark-600" style="height: 6px">
|
||||||
<div
|
<div
|
||||||
class="h-full rounded-full transition-all"
|
class="h-full rounded-full transition-all"
|
||||||
:class="quotaPercentage(provider) > 90 ? 'bg-red-500' : quotaPercentage(provider) > 70 ? 'bg-yellow-500' : 'bg-green-500'"
|
:class="quotaPercentage(provider) > 90 ? 'bg-red-500' : quotaPercentage(provider) > 70 ? 'bg-yellow-500' : 'bg-green-500'"
|
||||||
:style="{ width: Math.min(quotaPercentage(provider), 100) + '%' }"
|
:style="{ width: Math.min(quotaPercentage(provider), 100) + '%' }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-xs text-gray-500">{{ provider.quota_used ?? 0 }} / {{ provider.quota_limit }}</span>
|
<div v-else class="flex-1" />
|
||||||
|
<span class="text-xs text-gray-500">{{ provider.quota_used ?? 0 }} / {{ provider.quota_limit > 0 ? provider.quota_limit : '∞' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Proxy + Test on same row -->
|
<!-- Proxy + Test on same row -->
|
||||||
@@ -3164,9 +3167,13 @@ async function loadWebSearchConfig() {
|
|||||||
|
|
||||||
async function saveWebSearchConfig(): Promise<boolean> {
|
async function saveWebSearchConfig(): Promise<boolean> {
|
||||||
try {
|
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({
|
await adminAPI.settings.updateWebSearchEmulationConfig({
|
||||||
enabled: webSearchConfig.enabled,
|
enabled: webSearchConfig.enabled,
|
||||||
providers: webSearchConfig.providers as WebSearchProviderConfig[],
|
providers,
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
|
|||||||
Reference in New Issue
Block a user