318 lines
9.8 KiB
Vue
318 lines
9.8 KiB
Vue
<template>
|
||
<el-dialog width="40%" :title="form.storeId ? '编辑' : '新增'" v-model="visible" :close-on-click-modal="false" draggable>
|
||
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
|
||
<el-row :gutter="24">
|
||
<el-col :span="24" class="mb20">
|
||
<el-form-item label="类型" prop="storeType">
|
||
<el-select v-model="form.storeType" placeholder="请选择类型">
|
||
<el-option :label="item.label" :value="item.value" v-for="(item, index) in embed_store_type" :key="index"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
|
||
<el-col :span="24" class="mb20">
|
||
<el-form-item label="名称" prop="name">
|
||
<el-input v-model="form.name" placeholder="请输入名称" />
|
||
</el-form-item>
|
||
</el-col>
|
||
|
||
<el-col :span="24" class="mb20" v-if="form.storeType !== 'qdrant' && form.storeType !== 'redis' && form.storeType !== 'pgvector'">
|
||
<el-form-item label="URI" prop="uri">
|
||
<el-input v-model="form.uri" placeholder="请输入链接地址" />
|
||
</el-form-item>
|
||
</el-col>
|
||
|
||
<el-col :span="24" class="mb20" v-if="form.storeType === 'qdrant' || form.storeType === 'redis' || form.storeType === 'pgvector'">
|
||
<el-form-item label="Host" prop="host">
|
||
<el-input v-model="form.host" placeholder="请输入Host" />
|
||
</el-form-item>
|
||
</el-col>
|
||
|
||
<el-col :span="24" class="mb20" v-if="form.storeType === 'qdrant' || form.storeType === 'redis' || form.storeType === 'pgvector'">
|
||
<el-form-item label="端口" prop="port">
|
||
<el-input-number v-model="form.port" placeholder="请输入端口" />
|
||
</el-form-item>
|
||
</el-col>
|
||
|
||
<el-col :span="24" class="mb20" v-if="form.storeType !== 'pgvector'">
|
||
<el-form-item label="密钥" prop="apiKey">
|
||
<el-input v-model="form.apiKey" placeholder="请输入密钥" />
|
||
</el-form-item>
|
||
</el-col>
|
||
|
||
<el-col :span="24" class="mb20" v-if="form.storeType === 'milvus'">
|
||
<el-form-item label="数据库" prop="extData">
|
||
<el-input v-model="form.extData" placeholder="请输入数据库" />
|
||
</el-form-item>
|
||
</el-col>
|
||
|
||
<!-- PgVector 特有配置 -->
|
||
<template v-if="form.storeType === 'pgvector'">
|
||
<el-col :span="24" class="mb20">
|
||
<el-form-item label="用户名" prop="pgUsername">
|
||
<el-input v-model="form.pgUsername" placeholder="请输入用户名" />
|
||
</el-form-item>
|
||
</el-col>
|
||
|
||
<el-col :span="24" class="mb20">
|
||
<el-form-item label="密码" prop="pgPassword">
|
||
<el-input v-model="form.pgPassword" type="password" placeholder="请输入密码" show-password />
|
||
</el-form-item>
|
||
</el-col>
|
||
|
||
<el-col :span="24" class="mb20">
|
||
<el-form-item label="数据库" prop="pgDatabase">
|
||
<el-input v-model="form.pgDatabase" placeholder="请输入数据库名" />
|
||
</el-form-item>
|
||
</el-col>
|
||
|
||
<el-col :span="24" class="mb20">
|
||
<el-form-item label="维度" prop="pgDimension">
|
||
<el-input-number v-model="form.pgDimension" :min="1" :max="4096" placeholder="请输入向量维度" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</template>
|
||
|
||
<el-col :span="24" class="mb20">
|
||
<el-form-item prop="useTls" v-if="form.storeType === 'qdrant'">
|
||
<template #label> TLS<tip content="HTTPS安全认证" /> </template>
|
||
<el-radio-group v-model="form.useTls">
|
||
<el-radio :key="index" :label="item.value" border v-for="(item, index) in yes_no_type">{{ item.label }} </el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="visible = false">取消</el-button>
|
||
<el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
</template>
|
||
|
||
<script setup lang="ts" name="AiEmbedStoreDialog">
|
||
import { useDict } from '/@/hooks/dict';
|
||
import { useMessage } from '/@/hooks/message';
|
||
import { getObj, addObj, putObj, validateExist } from '/@/api/knowledge/aiEmbedStore';
|
||
const emit = defineEmits(['refresh']);
|
||
|
||
// 定义变量内容
|
||
const dataFormRef = ref();
|
||
const visible = ref(false);
|
||
const loading = ref(false);
|
||
// 定义字典
|
||
const { embed_store_type, yes_no_type } = useDict('embed_store_type', 'yes_no_type');
|
||
|
||
// 提交表单数据
|
||
const form = reactive({
|
||
storeId: '',
|
||
name: '',
|
||
storeType: 'milvus',
|
||
host: '127.0.0.1',
|
||
port: 6334,
|
||
uri: 'http://127.0.0.1:19530',
|
||
apiKey: '',
|
||
extData: 'default',
|
||
useTls: '0',
|
||
// PgVector 特有字段
|
||
pgUsername: 'postgres',
|
||
pgPassword: 'postgres',
|
||
pgDatabase: 'database',
|
||
pgDimension: 4096,
|
||
});
|
||
|
||
// 监听 storeType 变化,设置默认端口
|
||
watch(
|
||
() => form.storeType,
|
||
(newType) => {
|
||
if (newType === 'pgvector') {
|
||
form.port = 5432;
|
||
} else if (newType === 'qdrant') {
|
||
form.port = 6334;
|
||
} else if (newType === 'redis') {
|
||
form.port = 6379;
|
||
}
|
||
}
|
||
);
|
||
|
||
// 定义校验规则
|
||
const dataRules = ref({
|
||
name: [
|
||
{ required: true, message: '名称不能为空', trigger: 'blur' },
|
||
{ max: 64, message: '长度不能超过64个字符', trigger: 'blur' },
|
||
{
|
||
validator: (rule: any, value: any, callback: any) => {
|
||
validateExist(rule, value, callback, form.storeId !== '');
|
||
},
|
||
trigger: 'blur',
|
||
},
|
||
],
|
||
storeType: [{ required: true, message: '类型不能为空', trigger: 'blur' }],
|
||
host: [
|
||
{ required: true, message: 'Host不能为空', trigger: 'blur' },
|
||
{ max: 255, message: '长度不能超过255个字符', trigger: 'blur' },
|
||
],
|
||
port: [
|
||
{ required: true, message: '端口不能为空', trigger: 'blur' },
|
||
{ type: 'number', max: 65535, message: '端口不能超过65535', trigger: 'blur' },
|
||
],
|
||
uri: [
|
||
{ required: true, message: '地址不能为空', trigger: 'blur' },
|
||
{ max: 255, message: '长度不能超过255个字符', trigger: 'blur' },
|
||
{
|
||
validator: (rule: any, value: any, callback: any) => {
|
||
// URI变化时,重新验证数据库字段
|
||
nextTick(() => {
|
||
if (dataFormRef.value) {
|
||
dataFormRef.value.validateField('extData');
|
||
}
|
||
});
|
||
callback();
|
||
},
|
||
trigger: 'blur',
|
||
},
|
||
],
|
||
extData: [
|
||
{ max: 255, message: '长度不能超过255个字符', trigger: 'blur' },
|
||
{
|
||
validator: (rule: any, value: any, callback: any) => {
|
||
// 检查URI是否包含.zilliz.com.cn(在线服务)
|
||
if (form.uri && form.uri.includes('.zilliz.com.cn')) {
|
||
if (!value) {
|
||
callback(new Error('使用Zilliz在线服务时,数据库字段不能为空'));
|
||
return;
|
||
}
|
||
// 检查数据库字段格式是否为 db_xxxx
|
||
const dbPattern = /^db_\w+$/;
|
||
if (!dbPattern.test(value)) {
|
||
callback(new Error('使用Zilliz在线服务时,数据库字段必须是 db_id 的形式'));
|
||
return;
|
||
}
|
||
}
|
||
callback();
|
||
},
|
||
trigger: 'blur',
|
||
},
|
||
],
|
||
// PgVector 校验规则
|
||
pgUsername: [
|
||
{ required: true, message: '用户名不能为空', trigger: 'blur' },
|
||
{ max: 64, message: '长度不能超过64个字符', trigger: 'blur' },
|
||
],
|
||
pgPassword: [
|
||
{ required: true, message: '密码不能为空', trigger: 'blur' },
|
||
{ max: 255, message: '长度不能超过255个字符', trigger: 'blur' },
|
||
],
|
||
pgDatabase: [
|
||
{ required: true, message: '数据库名不能为空', trigger: 'blur' },
|
||
{ max: 64, message: '长度不能超过64个字符', trigger: 'blur' },
|
||
],
|
||
pgDimension: [
|
||
{ required: true, message: '维度不能为空', trigger: 'blur' },
|
||
{ type: 'number', min: 1, max: 4096, message: '维度必须在1-4096之间', trigger: 'blur' },
|
||
],
|
||
});
|
||
|
||
// 打开弹窗
|
||
const openDialog = (id: string) => {
|
||
visible.value = true;
|
||
form.storeId = '';
|
||
form.extData = '';
|
||
|
||
// 重置表单数据
|
||
nextTick(() => {
|
||
dataFormRef.value?.resetFields();
|
||
});
|
||
|
||
// 获取aiEmbedStore信息
|
||
if (id) {
|
||
form.storeId = id;
|
||
getaiEmbedStoreData(id);
|
||
}
|
||
};
|
||
|
||
// 提交
|
||
const onSubmit = async () => {
|
||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||
if (!valid) return false;
|
||
|
||
try {
|
||
loading.value = true;
|
||
// 修复TypeScript错误:正确处理apiKey的类型
|
||
const submitForm = { ...form };
|
||
|
||
// 处理 pgvector 类型的特殊逻辑
|
||
if (form.storeType === 'pgvector') {
|
||
// 将 pgvector 的配置信息存储到 extData 中
|
||
const pgConfig = {
|
||
username: form.pgUsername,
|
||
password: form.pgPassword,
|
||
database: form.pgDatabase,
|
||
dimension: form.pgDimension,
|
||
};
|
||
submitForm.extData = JSON.stringify(pgConfig);
|
||
|
||
// 清空不需要的字段
|
||
submitForm.apiKey = '';
|
||
} else {
|
||
// 处理其他类型的 apiKey
|
||
if (submitForm.apiKey?.includes('***')) {
|
||
submitForm.apiKey = '';
|
||
}
|
||
}
|
||
|
||
// 移除 pgvector 特有的临时字段
|
||
delete (submitForm as any).pgUsername;
|
||
delete (submitForm as any).pgPassword;
|
||
delete (submitForm as any).pgDatabase;
|
||
delete (submitForm as any).pgDimension;
|
||
|
||
form.storeId ? await putObj(submitForm) : await addObj(submitForm);
|
||
useMessage().success(form.storeId ? '修改成功' : '添加成功');
|
||
visible.value = false;
|
||
emit('refresh');
|
||
} catch (err: any) {
|
||
useMessage().error(err.msg);
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
|
||
// 初始化表单数据
|
||
const getaiEmbedStoreData = (id: string) => {
|
||
// 获取数据
|
||
loading.value = true;
|
||
getObj({ storeId: id })
|
||
.then((res: any) => {
|
||
Object.assign(form, res.data);
|
||
|
||
// 如果是 pgvector 类型,解析 extData 中的配置
|
||
if (form.storeType === 'pgvector' && form.extData) {
|
||
try {
|
||
const pgConfig = JSON.parse(form.extData);
|
||
form.pgUsername = pgConfig.username || 'postgres';
|
||
form.pgPassword = pgConfig.password || 'postgres';
|
||
form.pgDatabase = pgConfig.database || 'database';
|
||
form.pgDimension = pgConfig.dimension || 4096;
|
||
} catch (e) {
|
||
// 解析失败时使用默认值
|
||
form.pgUsername = 'postgres';
|
||
form.pgPassword = 'postgres';
|
||
form.pgDatabase = 'database';
|
||
form.pgDimension = 4096;
|
||
}
|
||
}
|
||
})
|
||
.finally(() => {
|
||
loading.value = false;
|
||
});
|
||
};
|
||
|
||
// 暴露变量
|
||
defineExpose({
|
||
openDialog,
|
||
});
|
||
</script>
|