feat(embedding): 新增 EmbeddingModelSelector 组件并替换原有选择器
将嵌入模型选择逻辑提取为独立组件,提供模型状态显示功能 在 DataBaseView 和 SettingView 中使用新组件替代原有实现
This commit is contained in:
parent
55bb52c112
commit
82bc35a501
138
web/src/components/EmbeddingModelSelector.vue
Normal file
138
web/src/components/EmbeddingModelSelector.vue
Normal file
@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<a-select
|
||||
:style="style"
|
||||
:size="size"
|
||||
:value="value"
|
||||
@change="handleSelect"
|
||||
@dropdownVisibleChange="checkAllModelStatus"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(name, idx) in embedModelChoices" :key="idx"
|
||||
:value="name"
|
||||
>
|
||||
<div style="display: flex; align-items: center; gap: 8px; min-width: 0;">
|
||||
<span style="flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
{{ name }} ({{ configStore.config?.embed_model_names[name]?.dimension }})
|
||||
</span>
|
||||
<span
|
||||
:style="{
|
||||
color: getModelStatusColor(name),
|
||||
fontSize: '11px',
|
||||
fontWeight: 'bold',
|
||||
flexShrink: 0,
|
||||
padding: '2px 4px',
|
||||
borderRadius: '3px',
|
||||
}"
|
||||
:title="getModelStatusTooltip(name)"
|
||||
>
|
||||
{{ getModelStatusIcon(name) }}
|
||||
</span>
|
||||
</div>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive } from 'vue'
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
import { embeddingApi } from '@/apis/knowledge_api'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
const configStore = useConfigStore()
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
validator: (value) => ['default', 'large', 'small'].includes(value)
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择嵌入模型'
|
||||
},
|
||||
style: {
|
||||
type: Object,
|
||||
default: () => ({ width: '320px' })
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:value', 'change'])
|
||||
|
||||
const state = reactive({
|
||||
modelStatuses: {},
|
||||
checkingStatus: false
|
||||
})
|
||||
|
||||
const embedModelChoices = computed(() => {
|
||||
return Object.keys(configStore?.config?.embed_model_names || {}) || []
|
||||
})
|
||||
|
||||
// 检查所有embedding模型状态
|
||||
const checkAllModelStatus = async () => {
|
||||
try {
|
||||
state.checkingStatus = true
|
||||
const response = await embeddingApi.getAllModelsStatus()
|
||||
if (response.status.models) {
|
||||
state.modelStatuses = response.status.models
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查所有模型状态失败:', error)
|
||||
message.error('获取模型状态失败')
|
||||
} finally {
|
||||
state.checkingStatus = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取模型状态图标
|
||||
const getModelStatusIcon = (modelId) => {
|
||||
const status = state.modelStatuses[modelId]
|
||||
if (!status) return '○'
|
||||
if (status.status === 'available') return '✓'
|
||||
if (status.status === 'unavailable') return '✗'
|
||||
if (status.status === 'error') return '⚠'
|
||||
return '○'
|
||||
}
|
||||
|
||||
// 获取模型状态颜色
|
||||
const getModelStatusColor = (modelId) => {
|
||||
const status = state.modelStatuses[modelId]
|
||||
if (!status) return 'var(--gray-500)'
|
||||
if (status.status === 'available') return 'var(--color-success-500)'
|
||||
if (status.status === 'unavailable') return 'var(--color-error-500)'
|
||||
if (status.status === 'error') return 'var(--color-warning-500)'
|
||||
return 'var(--gray-500)'
|
||||
}
|
||||
|
||||
// 获取模型状态提示文本
|
||||
const getModelStatusTooltip = (modelId) => {
|
||||
const status = state.modelStatuses[modelId]
|
||||
if (!status) return '状态未知'
|
||||
|
||||
let statusText = ''
|
||||
if (status.status === 'available') statusText = '可用'
|
||||
else if (status.status === 'unavailable') statusText = '不可用'
|
||||
else if (status.status === 'error') statusText = '错误'
|
||||
|
||||
const message = status.message || '无详细信息'
|
||||
return `${statusText}: ${message}`
|
||||
}
|
||||
|
||||
const handleSelect = (value) => {
|
||||
emit('update:value', value)
|
||||
emit('change', value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
// 如果需要添加特定样式,可以在这里添加
|
||||
</style>
|
||||
@ -50,7 +50,12 @@
|
||||
<a-input v-model:value="newDatabase.name" placeholder="新建知识库名称" size="large" />
|
||||
|
||||
<h3>嵌入模型</h3>
|
||||
<a-select v-model:value="newDatabase.embed_model_name" :options="embedModelOptions" style="width: 100%;" size="large" />
|
||||
<EmbeddingModelSelector
|
||||
v-model:value="newDatabase.embed_model_name"
|
||||
style="width: 100%;"
|
||||
size="large"
|
||||
placeholder="请选择嵌入模型"
|
||||
/>
|
||||
|
||||
<!-- 仅对 LightRAG 提供语言选择和LLM选择 -->
|
||||
<div v-if="newDatabase.kb_type === 'lightrag'">
|
||||
@ -237,6 +242,7 @@ import { LockOutlined, InfoCircleOutlined, QuestionCircleOutlined, PlusOutlined
|
||||
import { databaseApi, typeApi } from '@/apis/knowledge_api';
|
||||
import HeaderComponent from '@/components/HeaderComponent.vue';
|
||||
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue';
|
||||
import EmbeddingModelSelector from '@/components/EmbeddingModelSelector.vue';
|
||||
import dayjs, { parseToShanghai } from '@/utils/time';
|
||||
|
||||
const route = useRoute()
|
||||
@ -250,12 +256,6 @@ const state = reactive({
|
||||
openNewDatabaseModel: false,
|
||||
})
|
||||
|
||||
const embedModelOptions = computed(() => {
|
||||
return Object.keys(configStore.config?.embed_model_names || {}).map(key => ({
|
||||
label: `${key} (${configStore.config?.embed_model_names[key]?.dimension})`,
|
||||
value: key,
|
||||
}))
|
||||
})
|
||||
|
||||
// 语言选项(值使用英文,以保证后端/LightRAG 兼容;标签为中英文方便理解)
|
||||
const languageOptions = [
|
||||
|
||||
@ -39,35 +39,11 @@
|
||||
检查状态
|
||||
</a-button> -->
|
||||
</div>
|
||||
<a-select style="width: 320px"
|
||||
<EmbeddingModelSelector
|
||||
:value="configStore.config?.embed_model"
|
||||
@change="handleChange('embed_model', $event)"
|
||||
@dropdownVisibleChange="checkAllModelStatus"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(name, idx) in embedModelChoices" :key="idx"
|
||||
:value="name"
|
||||
>
|
||||
<div style="display: flex; align-items: center; gap: 8px; min-width: 0;">
|
||||
<span style="flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
{{ name }}
|
||||
</span>
|
||||
<span
|
||||
:style="{
|
||||
color: getModelStatusColor(name),
|
||||
fontSize: '11px',
|
||||
fontWeight: 'bold',
|
||||
flexShrink: 0,
|
||||
padding: '2px 4px',
|
||||
borderRadius: '3px',
|
||||
}"
|
||||
:title="getModelStatusTooltip(name)"
|
||||
>
|
||||
{{ getModelStatusIcon(name) }}
|
||||
</span>
|
||||
</div>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
style="width: 320px"
|
||||
/>
|
||||
</div>
|
||||
<div class="card card-select">
|
||||
<span class="label">{{ items?.reranker.des }}</span>
|
||||
@ -184,8 +160,8 @@ import {
|
||||
import HeaderComponent from '@/components/HeaderComponent.vue';
|
||||
import ModelProvidersComponent from '@/components/ModelProvidersComponent.vue';
|
||||
import UserManagementComponent from '@/components/UserManagementComponent.vue';
|
||||
import { embeddingApi } from '@/apis/knowledge_api'
|
||||
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue';
|
||||
import EmbeddingModelSelector from '@/components/EmbeddingModelSelector.vue';
|
||||
|
||||
const configStore = useConfigStore()
|
||||
const userStore = useUserStore()
|
||||
@ -194,12 +170,6 @@ const state = reactive({
|
||||
loading: false,
|
||||
section: 'base',
|
||||
windowWidth: window?.innerWidth || 0,
|
||||
modelStatuses: {}, // 存储embedding模型状态
|
||||
checkingStatus: false // 是否正在检查状态
|
||||
})
|
||||
|
||||
const embedModelChoices = computed(() => {
|
||||
return Object.keys(configStore?.config?.embed_model_names || {}) || []
|
||||
})
|
||||
|
||||
const rerankerChoices = computed(() => {
|
||||
@ -252,65 +222,12 @@ onMounted(() => {
|
||||
updateWindowWidth()
|
||||
window.addEventListener('resize', updateWindowWidth)
|
||||
state.section = userStore.isSuperAdmin ? 'base' : 'user'
|
||||
|
||||
checkAllModelStatus()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', updateWindowWidth)
|
||||
})
|
||||
|
||||
|
||||
|
||||
// 检查所有embedding模型状态
|
||||
const checkAllModelStatus = async () => {
|
||||
try {
|
||||
state.checkingStatus = true
|
||||
const response = await embeddingApi.getAllModelsStatus()
|
||||
if (response.status.models) {
|
||||
state.modelStatuses = response.status.models
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查所有模型状态失败:', error)
|
||||
message.error('获取模型状态失败')
|
||||
} finally {
|
||||
state.checkingStatus = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取模型状态图标
|
||||
const getModelStatusIcon = (modelId) => {
|
||||
const status = state.modelStatuses[modelId]
|
||||
if (!status) return '○'
|
||||
if (status.status === 'available') return '✓'
|
||||
if (status.status === 'unavailable') return '✗'
|
||||
if (status.status === 'error') return '⚠'
|
||||
return '○'
|
||||
}
|
||||
|
||||
// 获取模型状态颜色
|
||||
const getModelStatusColor = (modelId) => {
|
||||
const status = state.modelStatuses[modelId]
|
||||
if (!status) return 'var(--gray-500)'
|
||||
if (status.status === 'available') return 'var(--color-success-500)'
|
||||
if (status.status === 'unavailable') return 'var(--color-error-500)'
|
||||
if (status.status === 'error') return 'var(--color-warning-500)'
|
||||
return 'var(--gray-500)'
|
||||
}
|
||||
// 获取模型状态提示文本
|
||||
const getModelStatusTooltip = (modelId) => {
|
||||
const status = state.modelStatuses[modelId]
|
||||
if (!status) return '状态未知'
|
||||
|
||||
let statusText = ''
|
||||
if (status.status === 'available') statusText = '可用'
|
||||
else if (status.status === 'unavailable') statusText = '不可用'
|
||||
else if (status.status === 'error') statusText = '错误'
|
||||
|
||||
const message = status.message || '无详细信息'
|
||||
return `${statusText}: ${message}`
|
||||
}
|
||||
|
||||
const openLink = (url) => {
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user