save user custom input model name

This commit is contained in:
duanfuxiang 2025-06-18 07:40:38 +08:00
parent 2363e964ad
commit 5c383c0634
3 changed files with 156 additions and 72 deletions

View File

@ -44,7 +44,7 @@ const keyMap: Record<ApiProvider, ProviderSettingKey> = {
'OpenAICompatible': 'openaicompatibleProvider', 'OpenAICompatible': 'openaicompatibleProvider',
}; };
const getProviderSettingKey = (provider: ApiProvider): ProviderSettingKey => { export const getProviderSettingKey = (provider: ApiProvider): ProviderSettingKey => {
return keyMap[provider]; return keyMap[provider];
}; };
@ -70,7 +70,7 @@ const CustomProviderSettings: React.FC<CustomProviderSettingsProps> = ({ plugin,
// 获取已设置API Key的提供商列表 // 获取已设置API Key的提供商列表
const getSettedProviders = (): ApiProvider[] => { const getSettedProviders = (): ApiProvider[] => {
return providers.filter(provider => { return providers.filter(provider => {
const providerSetting = getProviderSetting(provider); const providerSetting = getProviderSetting(provider);
return providerSetting.apiKey && providerSetting.apiKey.trim() !== ''; return providerSetting.apiKey && providerSetting.apiKey.trim() !== '';
}); });
@ -79,7 +79,7 @@ const CustomProviderSettings: React.FC<CustomProviderSettingsProps> = ({ plugin,
// 一键配置模型 // 一键配置模型
const handleOneClickConfig = () => { const handleOneClickConfig = () => {
const settedProviders = getSettedProviders(); const settedProviders = getSettedProviders();
if (settedProviders.length === 0) { if (settedProviders.length === 0) {
// 提示用户未设置任何key // 提示用户未设置任何key
alert(t("settings.ModelProvider.noApiKeySet")); alert(t("settings.ModelProvider.noApiKeySet"));
@ -88,7 +88,7 @@ const CustomProviderSettings: React.FC<CustomProviderSettingsProps> = ({ plugin,
// 选择chat和autocomplete的提供商按providers排序选择最靠前的 // 选择chat和autocomplete的提供商按providers排序选择最靠前的
const selectedProvider = providers.find(provider => settedProviders.includes(provider)); const selectedProvider = providers.find(provider => settedProviders.includes(provider));
// 选择embedding的提供商按embeddingProviders排序选择最靠前的 // 选择embedding的提供商按embeddingProviders排序选择最靠前的
const embeddingProvider = embeddingProviders.find(provider => settedProviders.includes(provider)); const embeddingProvider = embeddingProviders.find(provider => settedProviders.includes(provider));
@ -98,7 +98,7 @@ const CustomProviderSettings: React.FC<CustomProviderSettingsProps> = ({ plugin,
if (selectedProvider) { if (selectedProvider) {
const defaultModels = GetDefaultModelId(selectedProvider); const defaultModels = GetDefaultModelId(selectedProvider);
// 设置chat和autocomplete模型 // 设置chat和autocomplete模型
if (defaultModels.chat) { if (defaultModels.chat) {
newSettings.chatModelProvider = selectedProvider; newSettings.chatModelProvider = selectedProvider;
@ -116,7 +116,7 @@ const CustomProviderSettings: React.FC<CustomProviderSettingsProps> = ({ plugin,
if (embeddingProvider) { if (embeddingProvider) {
const embeddingDefaultModels = GetDefaultModelId(embeddingProvider); const embeddingDefaultModels = GetDefaultModelId(embeddingProvider);
// 设置embedding模型 // 设置embedding模型
if (embeddingDefaultModels.embedding) { if (embeddingDefaultModels.embedding) {
newSettings.embeddingModelProvider = embeddingProvider; newSettings.embeddingModelProvider = embeddingProvider;
@ -171,37 +171,37 @@ const CustomProviderSettings: React.FC<CustomProviderSettingsProps> = ({ plugin,
}); });
}; };
const testApiConnection = async (provider: ApiProvider) => { const testApiConnection = async (provider: ApiProvider, modelId?: string) => {
console.log(`Testing connection for ${provider}...`); console.log(`Testing connection for ${provider}...`);
try { try {
// 动态导入LLMManager以避免循环依赖 // 动态导入LLMManager以避免循环依赖
const { default: LLMManager } = await import('../../core/llm/manager'); const { default: LLMManager } = await import('../../core/llm/manager');
const { GetDefaultModelId } = await import('../../utils/api'); const { GetDefaultModelId } = await import('../../utils/api');
// 对于Ollama和OpenAICompatible不支持测试API连接 // 对于Ollama和OpenAICompatible不支持测试API连接
if (provider === ApiProvider.Ollama || provider === ApiProvider.OpenAICompatible) { if (provider === ApiProvider.Ollama || provider === ApiProvider.OpenAICompatible) {
throw new Error(t("settings.ModelProvider.testConnection.notSupported", { provider })); throw new Error(t("settings.ModelProvider.testConnection.notSupported", { provider }));
} }
// 创建LLM管理器实例 // 创建LLM管理器实例
const llmManager = new LLMManager(settings); const llmManager = new LLMManager(settings);
// 获取提供商的默认聊天模型 // 获取提供商的默认聊天模型
const defaultModels = GetDefaultModelId(provider); const defaultModels = GetDefaultModelId(provider);
const testModelId = defaultModels.chat; const testModelId = modelId || defaultModels.chat;
// 对于没有默认模型的提供商,使用通用的测试模型 // 对于没有默认模型的提供商,使用通用的测试模型
if (!testModelId) { if (!testModelId) {
throw new Error(t("settings.ModelProvider.testConnection.noDefaultModel", { provider })); throw new Error(t("settings.ModelProvider.testConnection.noDefaultModel", { provider }));
} }
// 构造测试模型对象 // 构造测试模型对象
const testModel = { const testModel = {
provider: provider, provider: provider,
modelId: testModelId modelId: testModelId
}; };
// 构造简单的测试请求 // 构造简单的测试请求
const testRequest = { const testRequest = {
messages: [ messages: [
@ -214,11 +214,11 @@ const CustomProviderSettings: React.FC<CustomProviderSettingsProps> = ({ plugin,
max_tokens: 10, max_tokens: 10,
temperature: 0 temperature: 0
}; };
// 设置超时选项 // 设置超时选项
const abortController = new AbortController(); const abortController = new AbortController();
const timeoutId = setTimeout(() => abortController.abort(), 10000); // 10秒超时 const timeoutId = setTimeout(() => abortController.abort(), 10000); // 10秒超时
try { try {
// 发起API调用测试 // 发起API调用测试
const response = await llmManager.generateResponse( const response = await llmManager.generateResponse(
@ -226,9 +226,9 @@ const CustomProviderSettings: React.FC<CustomProviderSettingsProps> = ({ plugin,
testRequest, testRequest,
{ signal: abortController.signal } { signal: abortController.signal }
); );
clearTimeout(timeoutId); clearTimeout(timeoutId);
// 检查响应是否有效 // 检查响应是否有效
if (response && response.choices && response.choices.length > 0) { if (response && response.choices && response.choices.length > 0) {
console.log(`${provider} connection test successful:`, response.choices[0]?.message?.content); console.log(`${provider} connection test successful:`, response.choices[0]?.message?.content);
@ -241,13 +241,13 @@ const CustomProviderSettings: React.FC<CustomProviderSettingsProps> = ({ plugin,
clearTimeout(timeoutId); clearTimeout(timeoutId);
throw apiError; throw apiError;
} }
} catch (error) { } catch (error) {
console.error(`${provider} connection test failed:`, error); console.error(`${provider} connection test failed:`, error);
// 根据错误类型提供更具体的错误信息 // 根据错误类型提供更具体的错误信息
let errorMessage = t("settings.ModelProvider.testConnection.connectionFailed"); let errorMessage = t("settings.ModelProvider.testConnection.connectionFailed");
if (error.message?.includes('API key')) { if (error.message?.includes('API key')) {
errorMessage = t("settings.ModelProvider.testConnection.invalidApiKey"); errorMessage = t("settings.ModelProvider.testConnection.invalidApiKey");
} else if (error.message?.includes('base URL') || error.message?.includes('baseURL')) { } else if (error.message?.includes('base URL') || error.message?.includes('baseURL')) {
@ -285,27 +285,58 @@ const CustomProviderSettings: React.FC<CustomProviderSettingsProps> = ({ plugin,
return settings[providerKey] || {}; return settings[providerKey] || {};
}; };
const updateChatModelId = (provider: ApiProvider, modelId: string) => { const updateChatModelId = (
provider: ApiProvider,
modelId: string,
isCustom: boolean = false
) => {
console.log(`updateChatModelId: ${provider} -> ${modelId}, isCustom: ${isCustom}`)
const providerSettingKey = getProviderSettingKey(provider);
const providerSettings = settings[providerSettingKey] || {};
const currentModels = providerSettings.models || new Set<string>();
handleSettingsUpdate({ handleSettingsUpdate({
...settings, ...settings,
chatModelProvider: provider, chatModelProvider: provider,
chatModelId: modelId chatModelId: modelId,
[providerSettingKey]: {
...providerSettings,
models: isCustom ? new Set([...currentModels, modelId]) : currentModels
}
}); });
}; };
const updateApplyModelId = (provider: ApiProvider, modelId: string) => { const updateApplyModelId = (provider: ApiProvider, modelId: string, isCustom: boolean = false) => {
console.log(`updateApplyModelId: ${provider} -> ${modelId}, isCustom: ${isCustom}`)
const providerSettingKey = getProviderSettingKey(provider);
const providerSettings = settings[providerSettingKey] || {};
const currentModels = providerSettings.models || new Set<string>();
handleSettingsUpdate({ handleSettingsUpdate({
...settings, ...settings,
applyModelProvider: provider, applyModelProvider: provider,
applyModelId: modelId applyModelId: modelId,
[providerSettingKey]: {
...providerSettings,
models: isCustom ? new Set([...currentModels, modelId]) : currentModels
}
}); });
}; };
const updateEmbeddingModelId = (provider: ApiProvider, modelId: string) => { const updateEmbeddingModelId = (provider: ApiProvider, modelId: string, isCustom: boolean = false) => {
console.log(`updateEmbeddingModelId: ${provider} -> ${modelId}, isCustom: ${isCustom}`)
const providerSettingKey = getProviderSettingKey(provider);
const providerSettings = settings[providerSettingKey] || {};
const currentModels = providerSettings.models || new Set<string>();
handleSettingsUpdate({ handleSettingsUpdate({
...settings, ...settings,
embeddingModelProvider: provider, embeddingModelProvider: provider,
embeddingModelId: modelId embeddingModelId: modelId,
[providerSettingKey]: {
...providerSettings,
models: isCustom ? new Set([...currentModels, modelId]) : currentModels
}
}); });
}; };
@ -313,7 +344,7 @@ const CustomProviderSettings: React.FC<CustomProviderSettingsProps> = ({ plugin,
const generateApiKeyDescription = (provider: ApiProvider): React.ReactNode => { const generateApiKeyDescription = (provider: ApiProvider): React.ReactNode => {
const apiUrl = getProviderApiUrl(provider); const apiUrl = getProviderApiUrl(provider);
const baseDescription = String(t("settings.ApiProvider.enterApiKeyDescription")); const baseDescription = String(t("settings.ApiProvider.enterApiKeyDescription"));
if (!apiUrl) { if (!apiUrl) {
// 如果没有URL直接移除占位符 // 如果没有URL直接移除占位符
return baseDescription.replace('{provider_api_url}', ''); return baseDescription.replace('{provider_api_url}', '');
@ -328,9 +359,9 @@ const CustomProviderSettings: React.FC<CustomProviderSettingsProps> = ({ plugin,
return ( return (
<> <>
{parts[0]} {parts[0]}
<a <a
href={apiUrl} href={apiUrl}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="provider-api-link" className="provider-api-link"
> >
@ -398,7 +429,7 @@ const CustomProviderSettings: React.FC<CustomProviderSettingsProps> = ({ plugin,
<div className="model-selection-section"> <div className="model-selection-section">
<div className="model-selection-header"> <div className="model-selection-header">
<h2 className="section-title">{t("settings.ModelProvider.modelSelection")}:</h2> <h2 className="section-title">{t("settings.ModelProvider.modelSelection")}:</h2>
<button <button
className="one-click-config-btn" className="one-click-config-btn"
onClick={handleOneClickConfig} onClick={handleOneClickConfig}
title={t("settings.ModelProvider.oneClickConfigTooltip")} title={t("settings.ModelProvider.oneClickConfigTooltip")}

View File

@ -8,6 +8,8 @@ import { InfioSettings } from "../../types/settings";
// import { PROVIDERS } from '../constants'; // import { PROVIDERS } from '../constants';
import { GetAllProviders, GetEmbeddingProviderModelIds, GetEmbeddingProviders, GetProviderModelIds } from "../../utils/api"; import { GetAllProviders, GetEmbeddingProviderModelIds, GetEmbeddingProviders, GetProviderModelIds } from "../../utils/api";
import { getProviderSettingKey } from "./ModelProviderSettings";
type TextSegment = { type TextSegment = {
text: string; text: string;
isHighlighted: boolean; isHighlighted: boolean;
@ -21,6 +23,7 @@ type SearchableItem = {
type HighlightedItem = { type HighlightedItem = {
id: string; id: string;
html: TextSegment[]; html: TextSegment[];
isCustom?: boolean;
}; };
// Type guard for Record<string, unknown> // Type guard for Record<string, unknown>
@ -154,7 +157,7 @@ export type ComboBoxComponentProps = {
settings?: InfioSettings | null; settings?: InfioSettings | null;
isEmbedding?: boolean, isEmbedding?: boolean,
description?: string; description?: string;
updateModel: (provider: ApiProvider, modelId: string) => void; updateModel: (provider: ApiProvider, modelId: string, isCustom?: boolean) => void;
}; };
export const ComboBoxComponent: React.FC<ComboBoxComponentProps> = ({ export const ComboBoxComponent: React.FC<ComboBoxComponentProps> = ({
@ -178,24 +181,49 @@ export const ComboBoxComponent: React.FC<ComboBoxComponentProps> = ({
const [modelIds, setModelIds] = useState<string[]>([]); const [modelIds, setModelIds] = useState<string[]>([]);
// 统一处理模型选择和保存
const handleModelSelect = (provider: ApiProvider, modelId: string, isCustom?: boolean) => {
console.log(`handleModelSelect: ${provider} -> ${modelId}`)
// 检查是否是自定义模型(不在官方模型列表中)
// const isCustomModel = !modelIds.includes(modelId);
updateModel(provider, modelId, isCustom);
};
// Replace useMemo with useEffect for async fetching // Replace useMemo with useEffect for async fetching
useEffect(() => { useEffect(() => {
const fetchModelIds = async () => { const fetchModelIds = async () => {
const ids = isEmbedding const ids = isEmbedding
? GetEmbeddingProviderModelIds(modelProvider) ? GetEmbeddingProviderModelIds(modelProvider)
: await GetProviderModelIds(modelProvider, settings); : await GetProviderModelIds(modelProvider, settings);
console.log(`📝 Fetched ${ids.length} official models for ${modelProvider}:`, ids);
setModelIds(ids); setModelIds(ids);
}; };
fetchModelIds(); fetchModelIds();
}, [modelProvider, isEmbedding, settings]); }, [modelProvider, isEmbedding, settings]);
const combinedModelIds = useMemo(() => {
const providerKey = getProviderSettingKey(modelProvider);
const providerModels = settings?.[providerKey]?.models;
console.log(`🔍 Custom models in settings for ${modelProvider}:`, providerModels ? Array.from(providerModels) : 'none')
// Ensure providerModels is a Set of strings
if (!providerModels || !(providerModels instanceof Set)) {
console.log(`📋 Using only official models (${modelIds.length}):`, modelIds);
return modelIds;
}
const additionalModels = Array.from(providerModels).filter((model): model is string => typeof model === 'string');
console.log(`📋 Combined models: ${modelIds.length} official + ${additionalModels.length} custom`);
return [...modelIds, ...additionalModels];
}, [modelIds, settings, modelProvider]);
const searchableItems = useMemo(() => { const searchableItems = useMemo(() => {
return modelIds.map((id) => ({ return combinedModelIds.map((id): SearchableItem => ({
id, id: String(id),
html: id, html: String(id),
})) }))
}, [modelIds]) }, [combinedModelIds])
// fuse, used for fuzzy search, simple configuration threshold can be adjusted as needed // fuse, used for fuzzy search, simple configuration threshold can be adjusted as needed
const fuse: Fuse<SearchableItem> = useMemo(() => { const fuse: Fuse<SearchableItem> = useMemo(() => {
@ -212,24 +240,25 @@ export const ComboBoxComponent: React.FC<ComboBoxComponentProps> = ({
// 根据 searchTerm 得到过滤后的数据列表 // 根据 searchTerm 得到过滤后的数据列表
const filteredOptions = useMemo(() => { const filteredOptions = useMemo(() => {
let results: HighlightedItem[] = searchTerm const results: HighlightedItem[] = searchTerm
? highlight(fuse.search(searchTerm)) ? highlight(fuse.search(searchTerm))
: searchableItems.map(item => ({ : searchableItems.map(item => ({
...item, ...item,
html: typeof item.html === 'string' ? [{ text: item.html, isHighlighted: false }] : item.html html: typeof item.html === 'string' ? [{ text: item.html, isHighlighted: false }] : item.html
})) }))
// 如果有搜索词,添加自定义选项(如果不存在完全匹配的话) // 如果有搜索词,添加自定义选项(如果不存在完全匹配的话)
if (searchTerm && searchTerm.trim()) { if (searchTerm && searchTerm.trim()) {
const exactMatch = searchableItems.some(item => item.id === searchTerm); const exactMatch = searchableItems.some(item => item.id === searchTerm);
if (!exactMatch) { if (!exactMatch) {
results.unshift({ results.unshift({
id: searchTerm, id: searchTerm,
html: [{ text: `${modelIds.length > 0 ? t("settings.ModelProvider.custom") : ''}${searchTerm}`, isHighlighted: false }] html: [{ text: `${modelIds.length > 0 ? t("settings.ModelProvider.custom") : ''}${searchTerm}`, isHighlighted: false }],
isCustom: true
}); });
} }
} }
return results return results
}, [searchableItems, searchTerm, fuse, modelIds.length]) }, [searchableItems, searchTerm, fuse, modelIds.length])
@ -251,10 +280,10 @@ export const ComboBoxComponent: React.FC<ComboBoxComponentProps> = ({
// Use proper type checking without type assertion // Use proper type checking without type assertion
const availableProviders = providers; const availableProviders = providers;
const isValidProvider = (value: string): value is ApiProvider => { const isValidProvider = (value: string): value is ApiProvider => {
// @ts-ignore // @ts-expect-error - checking if providers array includes the value
return (availableProviders as readonly string[]).includes(value); return availableProviders.includes(value);
}; };
if (isValidProvider(newProvider)) { if (isValidProvider(newProvider)) {
setModelProvider(newProvider); setModelProvider(newProvider);
} }
@ -346,11 +375,11 @@ export const ComboBoxComponent: React.FC<ComboBoxComponentProps> = ({
if (filteredOptions.length > 0) { if (filteredOptions.length > 0) {
const selectedOption = filteredOptions[selectedIndex]; const selectedOption = filteredOptions[selectedIndex];
if (selectedOption) { if (selectedOption) {
updateModel(modelProvider, selectedOption.id); handleModelSelect(modelProvider, selectedOption.id, selectedOption.isCustom);
} }
} else if (searchTerm.trim()) { } else if (searchTerm.trim()) {
// If no options but there is input content, use the input content directly // If no options but there is input content, use the input content directly
updateModel(modelProvider, searchTerm.trim()); handleModelSelect(modelProvider, searchTerm.trim(), true);
} }
setSearchTerm(""); setSearchTerm("");
setIsOpen(false); setIsOpen(false);
@ -373,7 +402,7 @@ export const ComboBoxComponent: React.FC<ComboBoxComponentProps> = ({
ref={(el) => (itemRefs.current[index] = el)} ref={(el) => (itemRefs.current[index] = el)}
onMouseEnter={() => setSelectedIndex(index)} onMouseEnter={() => setSelectedIndex(index)}
onClick={() => { onClick={() => {
updateModel(modelProvider, option.id); handleModelSelect(modelProvider, option.id, option.isCustom);
setSearchTerm(""); setSearchTerm("");
setIsOpen(false); setIsOpen(false);
}} }}

View File

@ -19,144 +19,168 @@ const InfioProviderSchema = z.object({
name: z.literal('Infio'), name: z.literal('Infio'),
apiKey: z.string().catch(''), apiKey: z.string().catch(''),
baseUrl: z.string().catch(''), baseUrl: z.string().catch(''),
useCustomUrl: z.boolean().catch(false) useCustomUrl: z.boolean().catch(false),
models: z.set(z.string()).catch(new Set())
}).catch({ }).catch({
name: 'Infio', name: 'Infio',
apiKey: '', apiKey: '',
baseUrl: '', baseUrl: '',
useCustomUrl: false useCustomUrl: false,
models: new Set()
}) })
const OpenRouterProviderSchema = z.object({ const OpenRouterProviderSchema = z.object({
name: z.literal('OpenRouter'), name: z.literal('OpenRouter'),
apiKey: z.string().catch(''), apiKey: z.string().catch(''),
baseUrl: z.string().catch(''), baseUrl: z.string().catch(''),
useCustomUrl: z.boolean().catch(false) useCustomUrl: z.boolean().catch(false),
models: z.set(z.string()).catch(new Set())
}).catch({ }).catch({
name: 'OpenRouter', name: 'OpenRouter',
apiKey: '', apiKey: '',
baseUrl: '', baseUrl: '',
useCustomUrl: false useCustomUrl: false,
models: new Set()
}) })
const SiliconFlowProviderSchema = z.object({ const SiliconFlowProviderSchema = z.object({
name: z.literal('SiliconFlow'), name: z.literal('SiliconFlow'),
apiKey: z.string().catch(''), apiKey: z.string().catch(''),
baseUrl: z.string().catch(''), baseUrl: z.string().catch(''),
useCustomUrl: z.boolean().catch(false) useCustomUrl: z.boolean().catch(false),
models: z.set(z.string()).catch(new Set())
}).catch({ }).catch({
name: 'SiliconFlow', name: 'SiliconFlow',
apiKey: '', apiKey: '',
baseUrl: '', baseUrl: '',
useCustomUrl: false useCustomUrl: false,
models: new Set()
}) })
const AlibabaQwenProviderSchema = z.object({ const AlibabaQwenProviderSchema = z.object({
name: z.literal('AlibabaQwen'), name: z.literal('AlibabaQwen'),
apiKey: z.string().catch(''), apiKey: z.string().catch(''),
baseUrl: z.string().catch(''), baseUrl: z.string().catch(''),
useCustomUrl: z.boolean().catch(false) useCustomUrl: z.boolean().catch(false),
models: z.set(z.string()).catch(new Set())
}).catch({ }).catch({
name: 'AlibabaQwen', name: 'AlibabaQwen',
apiKey: '', apiKey: '',
baseUrl: '', baseUrl: '',
useCustomUrl: false useCustomUrl: false,
models: new Set()
}) })
const AnthropicProviderSchema = z.object({ const AnthropicProviderSchema = z.object({
name: z.literal('Anthropic'), name: z.literal('Anthropic'),
apiKey: z.string().catch(''), apiKey: z.string().catch(''),
baseUrl: z.string().optional(), baseUrl: z.string().optional(),
useCustomUrl: z.boolean().catch(false) useCustomUrl: z.boolean().catch(false),
models: z.set(z.string()).catch(new Set())
}).catch({ }).catch({
name: 'Anthropic', name: 'Anthropic',
apiKey: '', apiKey: '',
baseUrl: '', baseUrl: '',
useCustomUrl: false useCustomUrl: false,
models: new Set()
}) })
const DeepSeekProviderSchema = z.object({ const DeepSeekProviderSchema = z.object({
name: z.literal('DeepSeek'), name: z.literal('DeepSeek'),
apiKey: z.string().catch(''), apiKey: z.string().catch(''),
baseUrl: z.string().catch(''), baseUrl: z.string().catch(''),
useCustomUrl: z.boolean().catch(false) useCustomUrl: z.boolean().catch(false),
models: z.set(z.string()).catch(new Set())
}).catch({ }).catch({
name: 'DeepSeek', name: 'DeepSeek',
apiKey: '', apiKey: '',
baseUrl: '', baseUrl: '',
useCustomUrl: false useCustomUrl: false,
models: new Set()
}) })
const GoogleProviderSchema = z.object({ const GoogleProviderSchema = z.object({
name: z.literal('Google'), name: z.literal('Google'),
apiKey: z.string().catch(''), apiKey: z.string().catch(''),
baseUrl: z.string().catch(''), baseUrl: z.string().catch(''),
useCustomUrl: z.boolean().catch(false) useCustomUrl: z.boolean().catch(false),
models: z.set(z.string()).catch(new Set())
}).catch({ }).catch({
name: 'Google', name: 'Google',
apiKey: '', apiKey: '',
baseUrl: '', baseUrl: '',
useCustomUrl: false useCustomUrl: false,
models: new Set()
}) })
const OpenAIProviderSchema = z.object({ const OpenAIProviderSchema = z.object({
name: z.literal('OpenAI'), name: z.literal('OpenAI'),
apiKey: z.string().catch(''), apiKey: z.string().catch(''),
baseUrl: z.string().optional(), baseUrl: z.string().optional(),
useCustomUrl: z.boolean().catch(false) useCustomUrl: z.boolean().catch(false),
models: z.set(z.string()).catch(new Set())
}).catch({ }).catch({
name: 'OpenAI', name: 'OpenAI',
apiKey: '', apiKey: '',
baseUrl: '', baseUrl: '',
useCustomUrl: false useCustomUrl: false,
models: new Set()
}) })
const OpenAICompatibleProviderSchema = z.object({ const OpenAICompatibleProviderSchema = z.object({
name: z.literal('OpenAICompatible'), name: z.literal('OpenAICompatible'),
apiKey: z.string().catch(''), apiKey: z.string().catch(''),
baseUrl: z.string().optional(), baseUrl: z.string().optional(),
useCustomUrl: z.boolean().catch(true) useCustomUrl: z.boolean().catch(true),
models: z.set(z.string()).catch(new Set())
}).catch({ }).catch({
name: 'OpenAICompatible', name: 'OpenAICompatible',
apiKey: '', apiKey: '',
baseUrl: '', baseUrl: '',
useCustomUrl: true useCustomUrl: true,
models: new Set()
}) })
const OllamaProviderSchema = z.object({ const OllamaProviderSchema = z.object({
name: z.literal('Ollama'), name: z.literal('Ollama'),
apiKey: z.string().catch('ollama'), apiKey: z.string().catch('ollama'),
baseUrl: z.string().catch(''), baseUrl: z.string().catch(''),
useCustomUrl: z.boolean().catch(false) useCustomUrl: z.boolean().catch(false),
models: z.set(z.string()).catch(new Set())
}).catch({ }).catch({
name: 'Ollama', name: 'Ollama',
apiKey: 'ollama', apiKey: 'ollama',
baseUrl: '', baseUrl: '',
useCustomUrl: true useCustomUrl: true,
models: new Set()
}) })
const GroqProviderSchema = z.object({ const GroqProviderSchema = z.object({
name: z.literal('Groq'), name: z.literal('Groq'),
apiKey: z.string().catch(''), apiKey: z.string().catch(''),
baseUrl: z.string().catch(''), baseUrl: z.string().catch(''),
useCustomUrl: z.boolean().catch(false) useCustomUrl: z.boolean().catch(false),
models: z.set(z.string()).catch(new Set())
}).catch({ }).catch({
name: 'Groq', name: 'Groq',
apiKey: '', apiKey: '',
baseUrl: '', baseUrl: '',
useCustomUrl: false useCustomUrl: false,
models: new Set()
}) })
const GrokProviderSchema = z.object({ const GrokProviderSchema = z.object({
name: z.literal('Grok'), name: z.literal('Grok'),
apiKey: z.string().catch(''), apiKey: z.string().catch(''),
baseUrl: z.string().catch(''), baseUrl: z.string().catch(''),
useCustomUrl: z.boolean().catch(false) useCustomUrl: z.boolean().catch(false),
models: z.set(z.string()).catch(new Set())
}).catch({ }).catch({
name: 'Grok', name: 'Grok',
apiKey: '', apiKey: '',
baseUrl: '', baseUrl: '',
useCustomUrl: false useCustomUrl: false,
models: new Set()
}) })
const ollamaModelSchema = z.object({ const ollamaModelSchema = z.object({