fix(core): keep local providers disabled by default

- Keep no-key and local model sources disabled until explicit user configuration exists.
- Tighten default text and image model enablement checks.
- Cover environment-driven defaults for local providers.
This commit is contained in:
linshen committed 2026-05-16 09:47:21 +08:00
1 parent 269df1dcf7
commit b691f4f2c1
4 files changed
+77 -13

No files matched your search

@@ -14,6 +14,7 @@ const IMAGE_PROVIDER_ENV_KEYS = {
seedream: ['VITE_SEEDREAM_API_KEY', 'VITE_ARK_API_KEY'],
dashscope: ['VITE_DASHSCOPE_API_KEY'],
modelscope: ['VITE_MODELSCOPE_API_KEY'],
ollama: [],
cloudflare: ['VITE_CF_API_TOKEN'],
grok: ['VITE_GROK_API_KEY', 'VITE_XAI_API_KEY']
} as const
@@ -38,6 +39,7 @@ const IMAGE_BUILTIN_CONFIGS: readonly BuiltinImageConfigSpec[] = [
{ providerId: 'seedream', configId: 'image-seedream-50-lite', modelId: 'doubao-seedream-5-0-260128', displayName: 'Doubao Seedream 5.0 Lite' },
{ providerId: 'dashscope', configId: 'image-dashscope' },
{ providerId: 'modelscope', configId: 'image-modelscope' },
{ providerId: 'ollama', configId: 'image-ollama' },
{ providerId: 'cloudflare', configId: 'image-cloudflare-flux-klein' },
{ providerId: 'grok', configId: 'image-grok-imagine' }
] as const
@@ -63,6 +65,7 @@ const IMAGE_EXTRA_CONNECTION_ENV_KEYS: Record<string, Record<string, string[]>>
* 某些 Provider 需要多个字段都存在时才视为可用
*/
const IMAGE_REQUIRED_CONNECTION_FIELDS: Record<string, string[]> = {
ollama: [],
cloudflare: ['apiKey', 'accountId']
}
@@ -74,6 +77,18 @@ function getFirstEnvValue(envKeys: readonly string[]): string {
return ''
}
function hasConnectionValue(value: unknown): boolean {
return typeof value === 'string' ? value.trim().length > 0 : !!value
}
function shouldEnableFromRequiredFields(
connectionConfig: Record<string, unknown>,
requiredConnectionFields: readonly string[]
): boolean {
return requiredConnectionFields.length > 0
&& requiredConnectionFields.every(field => hasConnectionValue(connectionConfig[field]))
}
/**
* 图像模型默认配置生成器
* 返回完整的自包含配置对象,包含 provider 和 model 完整信息
@@ -123,10 +138,7 @@ export function getDefaultImageModels(registry?: IImageAdapterRegistry): Record<
}
const requiredConnectionFields = IMAGE_REQUIRED_CONNECTION_FIELDS[providerId] || ['apiKey']
const enabled = requiredConnectionFields.every(field => {
const value = connectionConfig[field]
return typeof value === 'string' ? value.trim().length > 0 : !!value
})
const enabled = shouldEnableFromRequiredFields(connectionConfig, requiredConnectionFields)
result[builtinConfig.configId] = {
id: builtinConfig.configId,
+25 -7
View File
@@ -19,6 +19,7 @@ const PROVIDER_ENV_KEYS = {
dashscope: ['VITE_DASHSCOPE_API_KEY'],
openrouter: ['VITE_OPENROUTER_API_KEY'],
modelscope: ['VITE_MODELSCOPE_API_KEY'],
ollama: [],
minimax: ['VITE_MINIMAX_API_KEY'],
cloudflare: ['VITE_CF_API_TOKEN'],
grok: ['VITE_GROK_API_KEY', 'VITE_XAI_API_KEY']
@@ -31,6 +32,7 @@ const PROVIDER_EXTRA_CONNECTION_ENV_KEYS: Record<string, Record<string, string[]
};
const PROVIDER_REQUIRED_CONNECTION_FIELDS: Record<string, string[]> = {
ollama: [],
cloudflare: ['apiKey', 'accountId']
};
@@ -42,6 +44,18 @@ function getFirstEnvValue(envKeys: readonly string[]): string {
return '';
}
function hasConnectionValue(value: unknown): boolean {
return typeof value === 'string' ? value.trim().length > 0 : !!value;
}
function shouldEnableFromRequiredFields(
connectionConfig: Record<string, unknown>,
requiredConnectionFields: readonly string[]
): boolean {
return requiredConnectionFields.length > 0
&& requiredConnectionFields.every((field) => hasConnectionValue(connectionConfig[field]));
}
/**
* 获取所有内置模型的 ID 列表
* 包括 PROVIDER_ENV_KEYS 中的所有 Provider 和 'custom'
@@ -83,10 +97,7 @@ export function getDefaultTextModels(registry?: ITextAdapterRegistry): Record<st
// 使用模型的默认参数值初始化 paramOverrides
const defaultParamValues = defaultModel.defaultParameterValues || {};
const requiredConnectionFields = PROVIDER_REQUIRED_CONNECTION_FIELDS[providerId] || ['apiKey'];
const enabled = requiredConnectionFields.every((field) => {
const value = connectionConfig[field];
return typeof value === 'string' ? value.trim().length > 0 : !!value;
});
const enabled = shouldEnableFromRequiredFields(connectionConfig, requiredConnectionFields);
result[providerId] = {
id: provider.id,
@@ -103,9 +114,16 @@ export function getDefaultTextModels(registry?: ITextAdapterRegistry): Record<st
// Custom 单独处理(baseURL 和 model 来自环境变量)
const openaiCompatibleAdapter = adapterRegistry.getAdapter('openai-compatible');
const customApiKey = getEnvVar('VITE_CUSTOM_API_KEY').trim();
const customBaseURL = getEnvVar('VITE_CUSTOM_API_BASE_URL');
const customModelId = getEnvVar('VITE_CUSTOM_API_MODEL') || 'custom-model';
const customBaseURL = getEnvVar('VITE_CUSTOM_API_BASE_URL').trim();
const rawCustomModelId = getEnvVar('VITE_CUSTOM_API_MODEL').trim();
const customModelId = rawCustomModelId || 'custom-model';
const rawCustomHeaders = getEnvVar('VITE_CUSTOM_API_HEADERS');
const hasExplicitCustomConfig = [
customApiKey,
customBaseURL,
rawCustomModelId,
rawCustomHeaders
].some((value) => value.trim().length > 0);
let customHeaders: Record<string, string> | undefined;
if (rawCustomHeaders) {
try {
@@ -133,7 +151,7 @@ export function getDefaultTextModels(registry?: ITextAdapterRegistry): Record<st
result.custom = {
id: 'custom',
name: 'OpenAI Compatible (Custom)',
enabled: true,
enabled: hasExplicitCustomConfig,
providerMeta: openaiCompatibleAdapter.getProvider(),
modelMeta: customModelMeta,
connectionConfig: {
@@ -86,6 +86,18 @@ describe('default image models', () => {
expect(models['image-openrouter-nanobanana'].enabled).toBe(false)
})
it('includes Ollama but keeps it disabled without explicit user configuration', () => {
const models = getDefaultImageModels(registry)
const ollamaConfig = models['image-ollama']
expect(ollamaConfig).toBeDefined()
expect(ollamaConfig.providerId).toBe('ollama')
expect(ollamaConfig.provider.requiresApiKey).toBe(false)
expect(ollamaConfig.connectionConfig?.apiKey).toBe('')
expect(ollamaConfig.connectionConfig?.baseURL).toBe('http://localhost:11434/v1')
expect(ollamaConfig.enabled).toBe(false)
})
it('OpenRouter model has correct provider and model information', () => {
process.env.VITE_OPENROUTER_API_KEY = 'test-key'
const models = getDefaultImageModels(registry)
@@ -123,6 +123,19 @@ describe('model defaults provider env mapping', () => {
expect(builtinModelIds).toContain('cloudflare')
})
it('should include ollama but keep it disabled without explicit user configuration', () => {
const builtinModelIds = getBuiltinModelIds()
const models = getDefaultTextModels()
expect(builtinModelIds).toContain('ollama')
expect(models.ollama).toBeDefined()
expect(models.ollama.providerMeta.id).toBe('ollama')
expect(models.ollama.providerMeta.requiresApiKey).toBe(false)
expect(models.ollama.connectionConfig.apiKey).toBe('')
expect(models.ollama.connectionConfig.baseURL).toBe('http://localhost:11434/v1')
expect(models.ollama.enabled).toBe(false)
})
it('should include cloudflare config and keep it disabled when credentials are empty', () => {
const models = getDefaultTextModels()
@@ -155,17 +168,26 @@ describe('model defaults provider env mapping', () => {
expect(models.cloudflare.connectionConfig.accountId).toBe('')
})
it('should expose the custom preset as OpenAI-compatible with chat completions by default', () => {
it('should expose the custom preset as OpenAI-compatible with chat completions but keep it disabled by default', () => {
const models = getDefaultTextModels()
expect(models.custom).toBeDefined()
expect(models.custom.providerMeta.id).toBe('openai-compatible')
expect(models.custom.providerMeta.name).toBe('OpenAI Compatible (Custom)')
expect(models.custom.enabled).toBe(true)
expect(models.custom.enabled).toBe(false)
expect(models.custom.connectionConfig.apiKey).toBe('')
expect(models.custom.connectionConfig.requestStyle).toBe('chat_completions')
})
it('should enable the custom preset when explicit custom connection config is provided', () => {
process.env.VITE_CUSTOM_API_BASE_URL = 'http://localhost:11434/v1'
const models = getDefaultTextModels()
expect(models.custom.enabled).toBe(true)
expect(models.custom.connectionConfig.baseURL).toBe('http://localhost:11434/v1')
})
it('should expose VITE_CUSTOM_API_HEADERS on the custom preset connection config', () => {
process.env.VITE_CUSTOM_API_HEADERS = '{"x-auth-token":"gateway-token"}'