feat: 增强知识库索引与设置页面模块化拆分

扩展知识库索引任务和 RAG 检索支持增量入库和文档去重,优
化本体检测和规则匹配精度,前端设置页面拆分为 LLM、邮件
和 Hermes 员工同步子面板并重构样式,新增日志详情组件和
知识入库日志模型,补充单元测试覆盖。
This commit is contained in:
caoxiaozhu
2026-05-22 23:47:28 +08:00
parent 88ff04bef8
commit 5b388d08c0
84 changed files with 10170 additions and 2599 deletions

View File

@@ -0,0 +1,194 @@
import { ref } from 'vue'
import { testModelConnectivity } from '../../services/settings.js'
import { useToast } from '../../composables/useToast.js'
const MODEL_SECRET_MASK = '********'
const MODEL_TEST_CONFIGS = {
main: {
label: '主模型',
providerKey: 'mainProvider',
modelKey: 'mainModel',
endpointKey: 'mainEndpoint',
apiKeyKey: 'mainApiKey',
capability: 'chat'
},
backup: {
label: '备份模型',
providerKey: 'backupProvider',
modelKey: 'backupModel',
endpointKey: 'backupEndpoint',
apiKeyKey: 'backupApiKey',
capability: 'chat'
},
embedding: {
label: 'Embedding 模型',
providerKey: 'embeddingProvider',
modelKey: 'embeddingModel',
endpointKey: 'embeddingEndpoint',
apiKeyKey: 'embeddingApiKey',
capability: 'embedding'
},
reranker: {
label: 'Reranker 模型',
providerKey: 'rerankerProvider',
modelKey: 'rerankerModel',
endpointKey: 'rerankerEndpoint',
apiKeyKey: 'rerankerApiKey',
capability: 'reranker'
}
}
const CUSTOM_OPENAI_PROVIDER = 'Custom OpenAI Compatible'
const PROVIDER_ENDPOINTS = {
MiniMax: 'https://api.minimaxi.com/v1',
GLM: 'https://open.bigmodel.cn/api/paas/v4/',
Kimi: 'https://api.moonshot.ai/v1',
Ali: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
Codex: 'https://api.openai.com/v1',
Claude: 'https://api.anthropic.com/v1/',
Gemini: 'https://generativelanguage.googleapis.com/v1beta/openai/',
[CUSTOM_OPENAI_PROVIDER]: ''
}
const RERANKER_PROVIDER_ENDPOINTS = {
Ali: 'https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank',
[CUSTOM_OPENAI_PROVIDER]: ''
}
const LEGACY_PROVIDER_MAP = {
'OpenAI Compatible': 'Codex',
'Azure OpenAI': CUSTOM_OPENAI_PROVIDER,
Ollama: CUSTOM_OPENAI_PROVIDER,
'自定义网关': CUSTOM_OPENAI_PROVIDER
}
function normalizeValue(value) {
return String(value ?? '').trim()
}
function normalizeProviderValue(value, fallback = 'Codex') {
const normalized = normalizeValue(value)
const providerOptions = Object.keys(PROVIDER_ENDPOINTS)
if (providerOptions.includes(normalized)) {
return normalized
}
if (LEGACY_PROVIDER_MAP[normalized]) {
return LEGACY_PROVIDER_MAP[normalized]
}
return fallback
}
function getProviderEndpoint(provider) {
return PROVIDER_ENDPOINTS[provider] ?? ''
}
function getRerankerEndpoint(provider) {
return RERANKER_PROVIDER_ENDPOINTS[provider] ?? getProviderEndpoint(provider)
}
function isModelConfigReady(provider, model, endpoint) {
return Boolean(normalizeValue(provider) && normalizeValue(model) && normalizeValue(endpoint))
}
function isModelSecretMask(value) {
return value === MODEL_SECRET_MASK
}
export default {
name: 'LlmSettingsPanel',
props: {
llmForm: {
type: Object,
required: true
},
providerOptions: {
type: Array,
required: true
}
},
setup(props) {
const { toast } = useToast()
const modelTestState = ref({
main: { status: 'idle', message: '' },
backup: { status: 'idle', message: '' },
embedding: { status: 'idle', message: '' },
reranker: { status: 'idle', message: '' }
})
function applyProviderPreset(testKey) {
const config = MODEL_TEST_CONFIGS[testKey]
const provider = normalizeProviderValue(props.llmForm[config.providerKey], CUSTOM_OPENAI_PROVIDER)
props.llmForm[config.providerKey] = provider
props.llmForm[config.endpointKey] =
testKey === 'reranker' ? getRerankerEndpoint(provider) : getProviderEndpoint(provider)
}
function getModelTestState(testKey) {
return modelTestState.value[testKey] || { status: 'idle', message: '' }
}
function isModelTesting(testKey) {
return getModelTestState(testKey).status === 'testing'
}
function clearModelSecretMask(testKey) {
const config = MODEL_TEST_CONFIGS[testKey]
if (isModelSecretMask(props.llmForm[config.apiKeyKey])) {
props.llmForm[config.apiKeyKey] = ''
}
}
async function testModelConnection(testKey) {
const config = MODEL_TEST_CONFIGS[testKey]
const provider = props.llmForm[config.providerKey]
const model = props.llmForm[config.modelKey]
const endpoint = props.llmForm[config.endpointKey]
const apiKey = props.llmForm[config.apiKeyKey]
if (!isModelConfigReady(provider, model, endpoint)) {
const message = `请先完整填写${config.label}的供应商、模型名称和接口地址。`
modelTestState.value[testKey] = { status: 'error', message }
toast(message)
return
}
modelTestState.value[testKey] = { status: 'testing', message: '正在测试模型连通性...' }
const payload = {
provider,
model,
endpoint,
api_key: isModelSecretMask(apiKey) ? '' : apiKey,
capability: config.capability,
slot: testKey
}
try {
const result = await testModelConnectivity(payload)
modelTestState.value[testKey] = {
status: result.ok ? 'success' : 'error',
message: result.detail || (result.ok ? '模型连接成功。' : '模型连接失败。')
}
toast(modelTestState.value[testKey].message)
} catch (error) {
const message = error.message || '模型测试请求失败,请确认 FastAPI 已启动。'
modelTestState.value[testKey] = { status: 'error', message }
toast(message)
}
}
return {
applyProviderPreset,
getModelTestState,
isModelTesting,
clearModelSecretMask,
testModelConnection
}
}
}