添加本地提供者支持,包括本地嵌入模型的描述、特性和设置,更新相关类型和API函数以支持新模型。

This commit is contained in:
duanfuxiang 2025-07-04 11:43:37 +08:00
parent 8b3babc28e
commit bed96a5233
6 changed files with 155 additions and 32 deletions

View File

@ -257,6 +257,11 @@ export default {
searchOrEnterModelName: 'Search or enter model name...',
enterCustomModelName: 'Enter custom model name',
custom: 'Custom: ',
localProviderDescription: 'Local embedding models run on your device using WASM technology, providing privacy and offline capability.',
localProviderFeature0: 'Currently only supports embedding models',
localProviderFeature1: 'Complete privacy - data never leaves your device',
localProviderFeature2: 'No API costs - runs entirely locally',
localProviderFeature3: 'Offline capability - works without internet connection',
testConnection: {
testApiConnection: 'Test API Connection',
testingConnection: 'Testing connection...',

View File

@ -259,6 +259,11 @@ export default {
searchOrEnterModelName: '搜索或输入模型名称...',
enterCustomModelName: '输入自定义模型名称',
custom: '自定义: ',
localProviderDescription: '本地嵌入模型使用 WASM 技术在您的设备上运行,提供隐私保护和离线功能。',
localProviderFeature0: '目前仅支持嵌入模型',
localProviderFeature1: '完全隐私 - 数据不会离开您的设备',
localProviderFeature2: '无 API 费用 - 完全本地运行',
localProviderFeature3: '离线功能 - 无需网络连接即可工作',
testConnection: {
testApiConnection: '测试 API 连接',
testingConnection: '正在测试连接...',

View File

@ -4,7 +4,10 @@ import { t } from '../../lang/helpers';
import InfioPlugin from "../../main";
import { ApiProvider } from '../../types/llm/model';
import { InfioSettings } from '../../types/settings';
import { GetAllProviders, GetDefaultModelId, GetEmbeddingProviders } from '../../utils/api';
import {
GetAllProviders, GetDefaultModelId, GetEmbeddingProviders,
localProviderDefaultEmbeddingModelId
} from '../../utils/api';
import { getProviderApiUrl } from '../../utils/provider-urls';
import { ApiKeyComponent, CustomUrlComponent } from './FormComponents';
@ -27,7 +30,8 @@ type ProviderSettingKey =
| 'groqProvider'
| 'grokProvider'
| 'ollamaProvider'
| 'openaicompatibleProvider';
| 'openaicompatibleProvider'
| 'localproviderProvider';
const keyMap: Record<ApiProvider, ProviderSettingKey> = {
'Infio': 'infioProvider',
@ -42,6 +46,7 @@ const keyMap: Record<ApiProvider, ProviderSettingKey> = {
'Grok': 'grokProvider',
'Ollama': 'ollamaProvider',
'OpenAICompatible': 'openaicompatibleProvider',
'LocalProvider': 'localproviderProvider',
};
export const getProviderSettingKey = (provider: ApiProvider): ProviderSettingKey => {
@ -124,6 +129,11 @@ const CustomProviderSettings: React.FC<CustomProviderSettingsProps> = ({ plugin,
hasUpdates = true;
console.debug(t("settings.ModelProvider.embeddingModelConfigured", { provider: embeddingProvider, model: embeddingDefaultModels.embedding }));
}
} else { // use local provider
newSettings.embeddingModelProvider = ApiProvider.LocalProvider;
newSettings.embeddingModelId = localProviderDefaultEmbeddingModelId;
hasUpdates = true;
console.debug(t("settings.ModelProvider.embeddingModelConfigured", { provider: ApiProvider.LocalProvider, model: localProviderDefaultEmbeddingModelId }));
}
// 一次性更新所有设置
@ -294,12 +304,12 @@ const CustomProviderSettings: React.FC<CustomProviderSettingsProps> = ({ plugin,
const providerSettingKey = getProviderSettingKey(provider);
const providerSettings = settings[providerSettingKey] || {};
const currentModels = providerSettings.models || [];
// 如果是自定义模型且不在列表中,则添加
const updatedModels = isCustom && !currentModels.includes(modelId)
? [...currentModels, modelId]
const updatedModels = isCustom && !currentModels.includes(modelId)
? [...currentModels, modelId]
: currentModels;
handleSettingsUpdate({
...settings,
chatModelProvider: provider,
@ -316,12 +326,12 @@ const CustomProviderSettings: React.FC<CustomProviderSettingsProps> = ({ plugin,
const providerSettingKey = getProviderSettingKey(provider);
const providerSettings = settings[providerSettingKey] || {};
const currentModels = providerSettings.models || [];
// 如果是自定义模型且不在列表中,则添加
const updatedModels = isCustom && !currentModels.includes(modelId)
? [...currentModels, modelId]
const updatedModels = isCustom && !currentModels.includes(modelId)
? [...currentModels, modelId]
: currentModels;
handleSettingsUpdate({
...settings,
applyModelProvider: provider,
@ -338,12 +348,12 @@ const CustomProviderSettings: React.FC<CustomProviderSettingsProps> = ({ plugin,
const providerSettingKey = getProviderSettingKey(provider);
const providerSettings = settings[providerSettingKey] || {};
const currentModels = providerSettings.models || [];
// 如果是自定义模型且不在列表中,则添加
const updatedModels = isCustom && !currentModels.includes(modelId)
? [...currentModels, modelId]
const updatedModels = isCustom && !currentModels.includes(modelId)
? [...currentModels, modelId]
: currentModels;
handleSettingsUpdate({
...settings,
embeddingModelProvider: provider,
@ -392,25 +402,43 @@ const CustomProviderSettings: React.FC<CustomProviderSettingsProps> = ({ plugin,
return (
<div className="provider-config">
{provider !== ApiProvider.Ollama && (
<ApiKeyComponent
name={t("settings.ModelProvider.setApiKey", { provider })}
placeholder={t("settings.ApiProvider.enterApiKey")}
description={generateApiKeyDescription(provider)}
value={providerSetting.apiKey || ''}
onChange={(value) => updateProviderApiKey(provider, value)}
onTest={() => testApiConnection(provider)}
/>
)}
{provider === ApiProvider.LocalProvider ? (
<div className="local-provider-info">
<p className="local-provider-description">
{t("settings.ModelProvider.localProviderDescription")}
</p>
<div className="local-provider-features">
<ul>
<li> {t("settings.ModelProvider.localProviderFeature0")}</li>
<li> {t("settings.ModelProvider.localProviderFeature1")}</li>
<li> {t("settings.ModelProvider.localProviderFeature2")}</li>
<li> {t("settings.ModelProvider.localProviderFeature3")}</li>
</ul>
</div>
</div>
) : (
<>
{provider !== ApiProvider.Ollama && (
<ApiKeyComponent
name={t("settings.ModelProvider.setApiKey", { provider })}
placeholder={t("settings.ApiProvider.enterApiKey")}
description={generateApiKeyDescription(provider)}
value={providerSetting.apiKey || ''}
onChange={(value) => updateProviderApiKey(provider, value)}
onTest={() => testApiConnection(provider)}
/>
)}
<CustomUrlComponent
name={t("settings.ApiProvider.useCustomBaseUrl")}
placeholder={t("settings.ApiProvider.enterCustomUrl")}
useCustomUrl={providerSetting.useCustomUrl || false}
baseUrl={providerSetting.baseUrl || ''}
onToggleCustomUrl={(value) => updateProviderUseCustomUrl(provider, value)}
onChangeBaseUrl={(value) => updateProviderBaseUrl(provider, value)}
/>
<CustomUrlComponent
name={t("settings.ApiProvider.useCustomBaseUrl")}
placeholder={t("settings.ApiProvider.enterCustomUrl")}
useCustomUrl={providerSetting.useCustomUrl || false}
baseUrl={providerSetting.baseUrl || ''}
onToggleCustomUrl={(value) => updateProviderUseCustomUrl(provider, value)}
onChangeBaseUrl={(value) => updateProviderBaseUrl(provider, value)}
/>
</>
)}
</div>
);
};
@ -684,6 +712,43 @@ const CustomProviderSettings: React.FC<CustomProviderSettingsProps> = ({ plugin,
background: var(--background-primary-alt);
border-color: var(--background-modifier-border-hover);
}
/* LocalProvider 特殊样式 */
.local-provider-info {
padding: var(--size-4-3);
background: var(--background-secondary);
border-radius: var(--radius-m);
border: 1px solid var(--background-modifier-border);
}
.local-provider-description {
color: var(--text-normal);
font-size: var(--font-ui-medium);
margin-bottom: var(--size-4-2);
line-height: 1.5;
}
.local-provider-features {
margin-top: var(--size-4-2);
}
.local-provider-features ul {
list-style: none;
padding: 0;
margin: 0;
color: var(--text-muted);
}
.local-provider-features li {
padding: var(--size-2-1) 0;
font-size: var(--font-ui-small);
line-height: 1.4;
}
.theme-dark .local-provider-info {
background: var(--background-secondary-alt);
border-color: var(--background-modifier-border-hover);
}
`}
</style>
</div>

View File

@ -11,6 +11,7 @@ export enum ApiProvider {
Grok = "Grok",
Ollama = "Ollama",
OpenAICompatible = "OpenAICompatible",
LocalProvider = "LocalProvider",
}
export type LLMModel = {

View File

@ -183,6 +183,20 @@ const GrokProviderSchema = z.object({
models: []
})
const LocalProviderSchema = z.object({
name: z.literal('LocalProvider'),
apiKey: z.string().catch(''),
baseUrl: z.string().catch(''),
useCustomUrl: z.boolean().catch(false),
models: z.array(z.string()).catch([])
}).catch({
name: 'LocalProvider',
apiKey: '',
baseUrl: '',
useCustomUrl: false,
models: []
})
const ollamaModelSchema = z.object({
baseUrl: z.string().catch(''),
model: z.string().catch(''),
@ -255,6 +269,7 @@ export const InfioSettingsSchema = z.object({
groqProvider: GroqProviderSchema,
grokProvider: GrokProviderSchema,
openaicompatibleProvider: OpenAICompatibleProviderSchema,
localproviderProvider: LocalProviderSchema,
// MCP Servers
mcpEnabled: z.boolean().catch(false),

View File

@ -1635,6 +1635,24 @@ export const grokModels = {
}
} as const satisfies Record<string, ModelInfo>
// LocalProvider (本地嵌入模型)
export const localProviderDefaultModelId = null // this is not supported for chat/autocomplete
export const localProviderDefaultAutoCompleteModelId = null // this is not supported for chat/autocomplete
export const localProviderDefaultEmbeddingModelId: keyof typeof localProviderEmbeddingModels = "Xenova/all-MiniLM-L6-v2"
export const localProviderEmbeddingModels = {
'Xenova/all-MiniLM-L6-v2': { dimensions: 384, description: 'All-MiniLM-L6-v2 (推荐,轻量级)' },
'Xenova/bge-small-en-v1.5': { dimensions: 384, description: 'BGE-small-en-v1.5' },
'Xenova/bge-base-en-v1.5': { dimensions: 768, description: 'BGE-base-en-v1.5 (更高质量)' },
'Xenova/jina-embeddings-v2-base-zh': { dimensions: 768, description: 'Jina-v2-base-zh (中英双语)' },
'Xenova/jina-embeddings-v2-small-en': { dimensions: 512, description: 'Jina-v2-small-en' },
'Xenova/multilingual-e5-small': { dimensions: 384, description: 'E5-small (多语言)' },
'Xenova/multilingual-e5-base': { dimensions: 768, description: 'E5-base (多语言,更高质量)' },
'Xenova/gte-small': { dimensions: 384, description: 'GTE-small' },
'Xenova/e5-small-v2': { dimensions: 384, description: 'E5-small-v2' },
'Xenova/e5-base-v2': { dimensions: 768, description: 'E5-base-v2 (更高质量)' }
} as const satisfies Record<string, EmbeddingModelInfo>
/// helper functions
// get all providers, used for the provider dropdown
export const GetAllProviders = (): ApiProvider[] => {
@ -1651,6 +1669,7 @@ export const GetAllProviders = (): ApiProvider[] => {
ApiProvider.Groq,
ApiProvider.Ollama,
ApiProvider.OpenAICompatible,
ApiProvider.LocalProvider,
]
}
@ -1663,6 +1682,7 @@ export const GetEmbeddingProviders = (): ApiProvider[] => {
ApiProvider.SiliconFlow,
ApiProvider.OpenAICompatible,
ApiProvider.Ollama,
ApiProvider.LocalProvider,
]
}
@ -1695,6 +1715,8 @@ export const GetProviderModels = async (provider: ApiProvider, settings?: InfioS
return {}
case ApiProvider.OpenAICompatible:
return {}
case ApiProvider.LocalProvider:
return {}
default:
return {}
}
@ -1729,6 +1751,8 @@ export const GetProviderModelsWithSettings = async (provider: ApiProvider, setti
return {}
case ApiProvider.OpenAICompatible:
return {}
case ApiProvider.LocalProvider:
return {} // LocalProvider only supports embedding models
default:
return {}
}
@ -1755,6 +1779,8 @@ export const GetEmbeddingProviderModels = (provider: ApiProvider): Record<string
return openAINativeEmbeddingModels;
case ApiProvider.AlibabaQwen:
return qwenEmbeddingModels;
case ApiProvider.LocalProvider:
return localProviderEmbeddingModels;
default:
return {}
}
@ -1832,6 +1858,12 @@ export const GetDefaultModelId = (provider: ApiProvider): { chat: string, autoCo
"autoComplete": grokDefaultAutoCompleteModelId,
"embedding": grokDefaultEmbeddingModelId,
}
case ApiProvider.LocalProvider:
return {
"chat": localProviderDefaultModelId,
"autoComplete": localProviderDefaultAutoCompleteModelId,
"embedding": localProviderDefaultEmbeddingModelId,
}
default:
return {
"chat": null,