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" />
|
<a-input v-model:value="newDatabase.name" placeholder="新建知识库名称" size="large" />
|
||||||
|
|
||||||
<h3>嵌入模型</h3>
|
<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选择 -->
|
<!-- 仅对 LightRAG 提供语言选择和LLM选择 -->
|
||||||
<div v-if="newDatabase.kb_type === 'lightrag'">
|
<div v-if="newDatabase.kb_type === 'lightrag'">
|
||||||
@ -237,6 +242,7 @@ import { LockOutlined, InfoCircleOutlined, QuestionCircleOutlined, PlusOutlined
|
|||||||
import { databaseApi, typeApi } from '@/apis/knowledge_api';
|
import { databaseApi, typeApi } from '@/apis/knowledge_api';
|
||||||
import HeaderComponent from '@/components/HeaderComponent.vue';
|
import HeaderComponent from '@/components/HeaderComponent.vue';
|
||||||
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue';
|
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue';
|
||||||
|
import EmbeddingModelSelector from '@/components/EmbeddingModelSelector.vue';
|
||||||
import dayjs, { parseToShanghai } from '@/utils/time';
|
import dayjs, { parseToShanghai } from '@/utils/time';
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@ -250,12 +256,6 @@ const state = reactive({
|
|||||||
openNewDatabaseModel: false,
|
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 兼容;标签为中英文方便理解)
|
// 语言选项(值使用英文,以保证后端/LightRAG 兼容;标签为中英文方便理解)
|
||||||
const languageOptions = [
|
const languageOptions = [
|
||||||
|
|||||||
@ -39,35 +39,11 @@
|
|||||||
检查状态
|
检查状态
|
||||||
</a-button> -->
|
</a-button> -->
|
||||||
</div>
|
</div>
|
||||||
<a-select style="width: 320px"
|
<EmbeddingModelSelector
|
||||||
:value="configStore.config?.embed_model"
|
:value="configStore.config?.embed_model"
|
||||||
@change="handleChange('embed_model', $event)"
|
@change="handleChange('embed_model', $event)"
|
||||||
@dropdownVisibleChange="checkAllModelStatus"
|
style="width: 320px"
|
||||||
>
|
/>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card card-select">
|
<div class="card card-select">
|
||||||
<span class="label">{{ items?.reranker.des }}</span>
|
<span class="label">{{ items?.reranker.des }}</span>
|
||||||
@ -184,8 +160,8 @@ import {
|
|||||||
import HeaderComponent from '@/components/HeaderComponent.vue';
|
import HeaderComponent from '@/components/HeaderComponent.vue';
|
||||||
import ModelProvidersComponent from '@/components/ModelProvidersComponent.vue';
|
import ModelProvidersComponent from '@/components/ModelProvidersComponent.vue';
|
||||||
import UserManagementComponent from '@/components/UserManagementComponent.vue';
|
import UserManagementComponent from '@/components/UserManagementComponent.vue';
|
||||||
import { embeddingApi } from '@/apis/knowledge_api'
|
|
||||||
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue';
|
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue';
|
||||||
|
import EmbeddingModelSelector from '@/components/EmbeddingModelSelector.vue';
|
||||||
|
|
||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
@ -194,12 +170,6 @@ const state = reactive({
|
|||||||
loading: false,
|
loading: false,
|
||||||
section: 'base',
|
section: 'base',
|
||||||
windowWidth: window?.innerWidth || 0,
|
windowWidth: window?.innerWidth || 0,
|
||||||
modelStatuses: {}, // 存储embedding模型状态
|
|
||||||
checkingStatus: false // 是否正在检查状态
|
|
||||||
})
|
|
||||||
|
|
||||||
const embedModelChoices = computed(() => {
|
|
||||||
return Object.keys(configStore?.config?.embed_model_names || {}) || []
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const rerankerChoices = computed(() => {
|
const rerankerChoices = computed(() => {
|
||||||
@ -252,65 +222,12 @@ onMounted(() => {
|
|||||||
updateWindowWidth()
|
updateWindowWidth()
|
||||||
window.addEventListener('resize', updateWindowWidth)
|
window.addEventListener('resize', updateWindowWidth)
|
||||||
state.section = userStore.isSuperAdmin ? 'base' : 'user'
|
state.section = userStore.isSuperAdmin ? 'base' : 'user'
|
||||||
|
|
||||||
checkAllModelStatus()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('resize', updateWindowWidth)
|
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) => {
|
const openLink = (url) => {
|
||||||
window.open(url, '_blank')
|
window.open(url, '_blank')
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user