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:
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,
|
||||
|
||||
@@ -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"}'
|
||||
|
||||
|
||||
Reference in new issue
Block a user