mirror of
https://github.com/tiennm99/ccs.git
synced 2026-08-06 22:21:27 +00:00
fix(settings): stabilize websearch and quota checks
This commit is contained in:
@@ -180,14 +180,11 @@ export function getCliInstallHints(): string[] {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function buildWebSearchReadiness(
|
||||||
* Get WebSearch readiness status for display.
|
enabled: boolean,
|
||||||
*/
|
providers: WebSearchCliInfo[]
|
||||||
export function getWebSearchReadiness(): WebSearchStatus {
|
): WebSearchStatus {
|
||||||
const wsConfig = getWebSearchConfig();
|
if (!enabled) {
|
||||||
const providers = getWebSearchCliProviders();
|
|
||||||
|
|
||||||
if (!wsConfig.enabled) {
|
|
||||||
return {
|
return {
|
||||||
readiness: 'unavailable',
|
readiness: 'unavailable',
|
||||||
message: 'Disabled in config',
|
message: 'Disabled in config',
|
||||||
@@ -223,6 +220,15 @@ export function getWebSearchReadiness(): WebSearchStatus {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get WebSearch readiness status for display.
|
||||||
|
*/
|
||||||
|
export function getWebSearchReadiness(): WebSearchStatus {
|
||||||
|
const wsConfig = getWebSearchConfig();
|
||||||
|
const providers = getWebSearchCliProviders();
|
||||||
|
return buildWebSearchReadiness(wsConfig.enabled, providers);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display WebSearch status (single line, equilibrium UX).
|
* Display WebSearch status (single line, equilibrium UX).
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -8,16 +8,15 @@ import { afterEach, beforeEach, describe, expect, it, mock } from 'bun:test';
|
|||||||
import * as fs from 'node:fs';
|
import * as fs from 'node:fs';
|
||||||
import * as os from 'node:os';
|
import * as os from 'node:os';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import {
|
|
||||||
buildCodexQuotaWindows,
|
|
||||||
buildCodexCoreUsageSummary,
|
|
||||||
fetchCodexQuota,
|
|
||||||
getUnknownCodexWindowLabels,
|
|
||||||
} from '../../../src/cliproxy/quota-fetcher-codex';
|
|
||||||
|
|
||||||
let tmpDir: string;
|
let tmpDir: string;
|
||||||
let originalCcsHome: string | undefined;
|
let originalCcsHome: string | undefined;
|
||||||
let originalFetch: typeof fetch;
|
let originalFetch: typeof fetch;
|
||||||
|
let moduleVersion = 0;
|
||||||
|
let buildCodexQuotaWindows: typeof import('../../../src/cliproxy/quota-fetcher-codex').buildCodexQuotaWindows;
|
||||||
|
let buildCodexCoreUsageSummary: typeof import('../../../src/cliproxy/quota-fetcher-codex').buildCodexCoreUsageSummary;
|
||||||
|
let fetchCodexQuota: typeof import('../../../src/cliproxy/quota-fetcher-codex').fetchCodexQuota;
|
||||||
|
let getUnknownCodexWindowLabels: typeof import('../../../src/cliproxy/quota-fetcher-codex').getUnknownCodexWindowLabels;
|
||||||
|
|
||||||
function createCodexAccount(
|
function createCodexAccount(
|
||||||
accountId: string,
|
accountId: string,
|
||||||
@@ -29,7 +28,26 @@ function createCodexAccount(
|
|||||||
fs.writeFileSync(path.join(authDir, tokenFile), JSON.stringify(tokenPayload));
|
fs.writeFileSync(path.join(authDir, tokenFile), JSON.stringify(tokenPayload));
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
|
moduleVersion += 1;
|
||||||
|
mock.restore();
|
||||||
|
|
||||||
|
const configGenerator = await import(
|
||||||
|
`../../../src/cliproxy/config-generator?codex-config-generator=${moduleVersion}`
|
||||||
|
);
|
||||||
|
const accountManager = await import(
|
||||||
|
`../../../src/cliproxy/account-manager?codex-account-manager=${moduleVersion}`
|
||||||
|
);
|
||||||
|
mock.module('../../../src/cliproxy/config-generator', () => configGenerator);
|
||||||
|
mock.module('../../../src/cliproxy/account-manager', () => accountManager);
|
||||||
|
|
||||||
|
({
|
||||||
|
buildCodexQuotaWindows,
|
||||||
|
buildCodexCoreUsageSummary,
|
||||||
|
fetchCodexQuota,
|
||||||
|
getUnknownCodexWindowLabels,
|
||||||
|
} = await import(`../../../src/cliproxy/quota-fetcher-codex?codex-fetcher=${moduleVersion}`));
|
||||||
|
|
||||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-codex-quota-test-'));
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-codex-quota-test-'));
|
||||||
originalCcsHome = process.env.CCS_HOME;
|
originalCcsHome = process.env.CCS_HOME;
|
||||||
process.env.CCS_HOME = tmpDir;
|
process.env.CCS_HOME = tmpDir;
|
||||||
@@ -37,6 +55,7 @@ beforeEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
mock.restore();
|
||||||
global.fetch = originalFetch;
|
global.fetch = originalFetch;
|
||||||
if (originalCcsHome !== undefined) {
|
if (originalCcsHome !== undefined) {
|
||||||
process.env.CCS_HOME = originalCcsHome;
|
process.env.CCS_HOME = originalCcsHome;
|
||||||
|
|||||||
@@ -7,21 +7,18 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
import { afterEach, beforeEach, describe, expect, it, mock } from 'bun:test';
|
||||||
import {
|
|
||||||
buildGeminiCliBuckets,
|
|
||||||
resolveGeminiCliProjectId,
|
|
||||||
} from '../../../src/cliproxy/quota-fetcher-gemini-cli';
|
|
||||||
import { refreshGeminiToken } from '../../../src/cliproxy/auth/gemini-token-refresh';
|
|
||||||
import { getProviderAuthDir } from '../../../src/cliproxy/config-generator';
|
|
||||||
import { getCapturedFetchRequests, mockFetch, restoreFetch } from '../../mocks';
|
import { getCapturedFetchRequests, mockFetch, restoreFetch } from '../../mocks';
|
||||||
|
|
||||||
describe('Gemini CLI Quota Fetcher', () => {
|
describe('Gemini CLI Quota Fetcher', () => {
|
||||||
let tempHome: string;
|
let tempHome: string;
|
||||||
let originalHome: string | undefined;
|
|
||||||
let originalCcsHome: string | undefined;
|
|
||||||
let originalGeminiClientId: string | undefined;
|
let originalGeminiClientId: string | undefined;
|
||||||
let originalGeminiClientSecret: string | undefined;
|
let originalGeminiClientSecret: string | undefined;
|
||||||
|
let moduleVersion = 0;
|
||||||
|
let buildGeminiCliBuckets: typeof import('../../../src/cliproxy/quota-fetcher-gemini-cli').buildGeminiCliBuckets;
|
||||||
|
let resolveGeminiCliProjectId: typeof import('../../../src/cliproxy/quota-fetcher-gemini-cli').resolveGeminiCliProjectId;
|
||||||
|
let refreshGeminiToken: typeof import('../../../src/cliproxy/auth/gemini-token-refresh').refreshGeminiToken;
|
||||||
|
let getProviderAuthDir: typeof import('../../../src/cliproxy/config-generator').getProviderAuthDir;
|
||||||
|
|
||||||
function writeGeminiToken(token: Record<string, unknown>): string {
|
function writeGeminiToken(token: Record<string, unknown>): string {
|
||||||
const authDir = getProviderAuthDir('gemini');
|
const authDir = getProviderAuthDir('gemini');
|
||||||
@@ -31,35 +28,51 @@ describe('Gemini CLI Quota Fetcher', () => {
|
|||||||
return tokenPath;
|
return tokenPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
|
moduleVersion += 1;
|
||||||
|
mock.restore();
|
||||||
|
|
||||||
tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-gemini-refresh-'));
|
tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-gemini-refresh-'));
|
||||||
originalHome = process.env.HOME;
|
|
||||||
originalCcsHome = process.env.CCS_HOME;
|
|
||||||
originalGeminiClientId = process.env.CCS_GEMINI_OAUTH_CLIENT_ID;
|
originalGeminiClientId = process.env.CCS_GEMINI_OAUTH_CLIENT_ID;
|
||||||
originalGeminiClientSecret = process.env.CCS_GEMINI_OAUTH_CLIENT_SECRET;
|
originalGeminiClientSecret = process.env.CCS_GEMINI_OAUTH_CLIENT_SECRET;
|
||||||
|
|
||||||
process.env.HOME = tempHome;
|
|
||||||
process.env.CCS_HOME = tempHome;
|
|
||||||
delete process.env.CCS_GEMINI_OAUTH_CLIENT_ID;
|
delete process.env.CCS_GEMINI_OAUTH_CLIENT_ID;
|
||||||
delete process.env.CCS_GEMINI_OAUTH_CLIENT_SECRET;
|
delete process.env.CCS_GEMINI_OAUTH_CLIENT_SECRET;
|
||||||
|
|
||||||
|
const authDir = path.join(tempHome, '.ccs', 'cliproxy', 'auth');
|
||||||
|
const actualConfigGenerator = await import(
|
||||||
|
`../../../src/cliproxy/config-generator?gemini-config-generator-actual=${moduleVersion}`
|
||||||
|
);
|
||||||
|
const actualAccountManager = await import(
|
||||||
|
`../../../src/cliproxy/account-manager?gemini-account-manager-actual=${moduleVersion}`
|
||||||
|
);
|
||||||
|
mock.module('../../../src/cliproxy/config-generator', () => ({
|
||||||
|
...actualConfigGenerator,
|
||||||
|
getProviderAuthDir: () => authDir,
|
||||||
|
}));
|
||||||
|
mock.module('../../../src/cliproxy/account-manager', () => ({
|
||||||
|
...actualAccountManager,
|
||||||
|
getDefaultAccount: () => null,
|
||||||
|
getProviderAccounts: () => [],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const configGenerator = await import(
|
||||||
|
`../../../src/cliproxy/config-generator?gemini-config-generator=${moduleVersion}`
|
||||||
|
);
|
||||||
|
({ buildGeminiCliBuckets, resolveGeminiCliProjectId } = await import(
|
||||||
|
`../../../src/cliproxy/quota-fetcher-gemini-cli?gemini-quota-fetcher=${moduleVersion}`
|
||||||
|
));
|
||||||
|
({ refreshGeminiToken } = await import(
|
||||||
|
`../../../src/cliproxy/auth/gemini-token-refresh?gemini-refresh=${moduleVersion}`
|
||||||
|
));
|
||||||
|
({ getProviderAuthDir } = configGenerator);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
mock.restore();
|
||||||
restoreFetch();
|
restoreFetch();
|
||||||
fs.rmSync(tempHome, { recursive: true, force: true });
|
fs.rmSync(tempHome, { recursive: true, force: true });
|
||||||
|
|
||||||
if (originalHome === undefined) {
|
|
||||||
delete process.env.HOME;
|
|
||||||
} else {
|
|
||||||
process.env.HOME = originalHome;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (originalCcsHome === undefined) {
|
|
||||||
delete process.env.CCS_HOME;
|
|
||||||
} else {
|
|
||||||
process.env.CCS_HOME = originalCcsHome;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (originalGeminiClientId === undefined) {
|
if (originalGeminiClientId === undefined) {
|
||||||
delete process.env.CCS_GEMINI_OAUTH_CLIENT_ID;
|
delete process.env.CCS_GEMINI_OAUTH_CLIENT_ID;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,126 +1,83 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
import { describe, expect, it } from 'bun:test';
|
||||||
import * as fs from 'fs';
|
import { buildWebSearchReadiness } from '../../../../src/utils/websearch/status';
|
||||||
import * as os from 'os';
|
import type { WebSearchCliInfo } from '../../../../src/utils/websearch/types';
|
||||||
import * as path from 'path';
|
|
||||||
import { getWebSearchReadiness } from '../../../../src/utils/websearch/status';
|
|
||||||
|
|
||||||
function writeWebSearchConfig(tempRoot: string, lines: string[]): void {
|
function provider(overrides: Partial<WebSearchCliInfo> & Pick<WebSearchCliInfo, 'id' | 'name'>): WebSearchCliInfo {
|
||||||
fs.writeFileSync(path.join(tempRoot, '.ccs', 'config.yaml'), lines.join('\n'), 'utf8');
|
return {
|
||||||
|
id: overrides.id,
|
||||||
|
kind: overrides.kind ?? 'backend',
|
||||||
|
name: overrides.name,
|
||||||
|
enabled: overrides.enabled ?? false,
|
||||||
|
available: overrides.available ?? false,
|
||||||
|
version: overrides.version ?? null,
|
||||||
|
requiresApiKey: overrides.requiresApiKey ?? false,
|
||||||
|
description: overrides.description ?? '',
|
||||||
|
detail: overrides.detail ?? '',
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('websearch readiness', () => {
|
describe('websearch readiness', () => {
|
||||||
const originalCcsHome = process.env.CCS_HOME;
|
|
||||||
const originalBraveKey = process.env.BRAVE_API_KEY;
|
|
||||||
const originalExaKey = process.env.EXA_API_KEY;
|
|
||||||
const originalTavilyKey = process.env.TAVILY_API_KEY;
|
|
||||||
let tempRoot = '';
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-websearch-status-'));
|
|
||||||
process.env.CCS_HOME = tempRoot;
|
|
||||||
delete process.env.BRAVE_API_KEY;
|
|
||||||
delete process.env.EXA_API_KEY;
|
|
||||||
delete process.env.TAVILY_API_KEY;
|
|
||||||
fs.mkdirSync(path.join(tempRoot, '.ccs'), { recursive: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
if (originalCcsHome !== undefined) process.env.CCS_HOME = originalCcsHome;
|
|
||||||
else delete process.env.CCS_HOME;
|
|
||||||
|
|
||||||
if (originalBraveKey !== undefined) process.env.BRAVE_API_KEY = originalBraveKey;
|
|
||||||
else delete process.env.BRAVE_API_KEY;
|
|
||||||
|
|
||||||
if (originalExaKey !== undefined) process.env.EXA_API_KEY = originalExaKey;
|
|
||||||
else delete process.env.EXA_API_KEY;
|
|
||||||
|
|
||||||
if (originalTavilyKey !== undefined) process.env.TAVILY_API_KEY = originalTavilyKey;
|
|
||||||
else delete process.env.TAVILY_API_KEY;
|
|
||||||
|
|
||||||
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('is ready by default because DuckDuckGo is enabled', () => {
|
it('is ready by default because DuckDuckGo is enabled', () => {
|
||||||
const readiness = getWebSearchReadiness();
|
const readiness = buildWebSearchReadiness(true, [
|
||||||
|
provider({
|
||||||
|
id: 'duckduckgo',
|
||||||
|
name: 'DuckDuckGo',
|
||||||
|
enabled: true,
|
||||||
|
available: true,
|
||||||
|
detail: 'Built-in (5 results)',
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
expect(readiness.readiness).toBe('ready');
|
expect(readiness.readiness).toBe('ready');
|
||||||
expect(readiness.message).toContain('DuckDuckGo');
|
expect(readiness.message).toContain('DuckDuckGo');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reports setup required when only Tavily is enabled without an API key', () => {
|
it('reports setup required when only Tavily is enabled without an API key', () => {
|
||||||
writeWebSearchConfig(tempRoot, [
|
const readiness = buildWebSearchReadiness(true, [
|
||||||
'version: 10',
|
provider({
|
||||||
'websearch:',
|
id: 'tavily',
|
||||||
' enabled: true',
|
name: 'Tavily',
|
||||||
' providers:',
|
enabled: true,
|
||||||
' exa:',
|
available: false,
|
||||||
' enabled: false',
|
requiresApiKey: true,
|
||||||
' max_results: 5',
|
apiKeyEnvVar: 'TAVILY_API_KEY',
|
||||||
' tavily:',
|
detail: 'Set TAVILY_API_KEY',
|
||||||
' enabled: true',
|
}),
|
||||||
' max_results: 5',
|
provider({
|
||||||
' duckduckgo:',
|
id: 'duckduckgo',
|
||||||
' enabled: false',
|
name: 'DuckDuckGo',
|
||||||
' max_results: 5',
|
enabled: false,
|
||||||
' brave:',
|
available: false,
|
||||||
' enabled: false',
|
detail: 'Built-in (5 results)',
|
||||||
' max_results: 5',
|
}),
|
||||||
' gemini:',
|
|
||||||
' enabled: false',
|
|
||||||
' model: "gemini-2.5-flash"',
|
|
||||||
' timeout: 55',
|
|
||||||
' opencode:',
|
|
||||||
' enabled: false',
|
|
||||||
' model: "opencode/grok-code"',
|
|
||||||
' timeout: 90',
|
|
||||||
' grok:',
|
|
||||||
' enabled: false',
|
|
||||||
' timeout: 55',
|
|
||||||
'',
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const readiness = getWebSearchReadiness();
|
|
||||||
|
|
||||||
expect(readiness.readiness).toBe('needs_setup');
|
expect(readiness.readiness).toBe('needs_setup');
|
||||||
expect(readiness.message).toContain('Tavily');
|
expect(readiness.message).toContain('Tavily');
|
||||||
expect(readiness.message).toContain('TAVILY_API_KEY');
|
expect(readiness.message).toContain('TAVILY_API_KEY');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('prefers API-backed readiness when Exa is enabled and configured', () => {
|
it('prefers API-backed readiness when Exa is enabled and configured', () => {
|
||||||
process.env.EXA_API_KEY = 'exa-test-key';
|
const readiness = buildWebSearchReadiness(true, [
|
||||||
writeWebSearchConfig(tempRoot, [
|
provider({
|
||||||
'version: 10',
|
id: 'exa',
|
||||||
'websearch:',
|
name: 'Exa',
|
||||||
' enabled: true',
|
enabled: true,
|
||||||
' providers:',
|
available: true,
|
||||||
' exa:',
|
requiresApiKey: true,
|
||||||
' enabled: true',
|
apiKeyEnvVar: 'EXA_API_KEY',
|
||||||
' max_results: 5',
|
detail: 'API key detected (5 results)',
|
||||||
' tavily:',
|
}),
|
||||||
' enabled: false',
|
provider({
|
||||||
' max_results: 5',
|
id: 'duckduckgo',
|
||||||
' duckduckgo:',
|
name: 'DuckDuckGo',
|
||||||
' enabled: false',
|
enabled: false,
|
||||||
' max_results: 5',
|
available: false,
|
||||||
' brave:',
|
detail: 'Built-in (5 results)',
|
||||||
' enabled: false',
|
}),
|
||||||
' max_results: 5',
|
|
||||||
' gemini:',
|
|
||||||
' enabled: false',
|
|
||||||
' model: "gemini-2.5-flash"',
|
|
||||||
' timeout: 55',
|
|
||||||
' opencode:',
|
|
||||||
' enabled: false',
|
|
||||||
' model: "opencode/grok-code"',
|
|
||||||
' timeout: 90',
|
|
||||||
' grok:',
|
|
||||||
' enabled: false',
|
|
||||||
' timeout: 55',
|
|
||||||
'',
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const readiness = getWebSearchReadiness();
|
|
||||||
|
|
||||||
expect(readiness.readiness).toBe('ready');
|
expect(readiness.readiness).toBe('ready');
|
||||||
expect(readiness.message).toContain('Exa');
|
expect(readiness.message).toContain('Exa');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -350,24 +350,6 @@ export default function WebSearchSection() {
|
|||||||
fetchRawConfig();
|
fetchRawConfig();
|
||||||
}, [fetchConfig, fetchStatus, fetchRawConfig]);
|
}, [fetchConfig, fetchStatus, fetchRawConfig]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!config?.providers) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextDrafts: Record<string, string> = {};
|
|
||||||
for (const provider of [...BACKEND_PROVIDERS, ...LEGACY_PROVIDERS]) {
|
|
||||||
for (const field of provider.fields ?? []) {
|
|
||||||
nextDrafts[`${provider.id}.${field.key}`] = getConfiguredValue(
|
|
||||||
config.providers,
|
|
||||||
provider.id,
|
|
||||||
field
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setFieldDrafts(nextDrafts);
|
|
||||||
}, [config]);
|
|
||||||
|
|
||||||
const providerStatus = useMemo(
|
const providerStatus = useMemo(
|
||||||
() => new Map((status?.providers ?? []).map((provider) => [provider.id, provider])),
|
() => new Map((status?.providers ?? []).map((provider) => [provider.id, provider])),
|
||||||
[status?.providers]
|
[status?.providers]
|
||||||
@@ -410,7 +392,10 @@ export default function WebSearchSection() {
|
|||||||
const currentProviders = (config.providers ?? {}) as WebSearchProvidersConfig;
|
const currentProviders = (config.providers ?? {}) as WebSearchProvidersConfig;
|
||||||
const currentProviderConfig = currentProviders[providerId] ?? {};
|
const currentProviderConfig = currentProviders[providerId] ?? {};
|
||||||
const currentValue = currentProviderConfig[field.key] ?? field.defaultValue;
|
const currentValue = currentProviderConfig[field.key] ?? field.defaultValue;
|
||||||
const normalized = normalizeFieldValue(field, fieldDrafts[fieldId] ?? String(currentValue));
|
const normalized = normalizeFieldValue(
|
||||||
|
field,
|
||||||
|
fieldDrafts[fieldId] ?? getConfiguredValue(currentProviders, providerId, field)
|
||||||
|
);
|
||||||
|
|
||||||
setFieldDrafts((current) => ({ ...current, [fieldId]: String(normalized) }));
|
setFieldDrafts((current) => ({ ...current, [fieldId]: String(normalized) }));
|
||||||
|
|
||||||
@@ -443,7 +428,13 @@ export default function WebSearchSection() {
|
|||||||
return {
|
return {
|
||||||
id: fieldId,
|
id: fieldId,
|
||||||
label: field.label,
|
label: field.label,
|
||||||
value: fieldDrafts[fieldId] ?? String(field.defaultValue),
|
value:
|
||||||
|
fieldDrafts[fieldId] ??
|
||||||
|
getConfiguredValue(
|
||||||
|
(config?.providers ?? {}) as WebSearchProvidersConfig,
|
||||||
|
provider.id,
|
||||||
|
field
|
||||||
|
),
|
||||||
placeholder: field.placeholder,
|
placeholder: field.placeholder,
|
||||||
type: field.type,
|
type: field.type,
|
||||||
helpText: field.helpText,
|
helpText: field.helpText,
|
||||||
|
|||||||
Reference in New Issue
Block a user