|
|
<template>
|
|
|
<div class="kb-upload-page">
|
|
|
<div class="kb-upload-steps-bar">
|
|
|
<el-steps :active="step" finish-status="success" process-status="finish" align-center>
|
|
|
<el-step title="上传文档"/>
|
|
|
<el-step title="预览文档"/>
|
|
|
<!-- <el-step title="文档预览" />-->
|
|
|
</el-steps>
|
|
|
</div>
|
|
|
<div v-if="step === 0" class="kb-upload-step1">
|
|
|
<el-form :model="form" ref="formRef" label-width="100px" class="kb-upload-form">
|
|
|
<!-- <el-form-item label="标题" prop="title">-->
|
|
|
<!-- <el-input v-model="form.title" placeholder="请输入文档标题" style="width:90%" />-->
|
|
|
<!-- </el-form-item>-->
|
|
|
|
|
|
<el-form-item label="上传文档" prop="file">
|
|
|
<el-upload
|
|
|
single
|
|
|
drag
|
|
|
style="width:80%"
|
|
|
:action="uploadImgUrl"
|
|
|
:data="form"
|
|
|
:before-upload="beforeUpload"
|
|
|
:on-success="handleUploadSuccess"
|
|
|
:on-error="handleUploadError"
|
|
|
:limit="limit"
|
|
|
ref="fileUploadRef"
|
|
|
:show-file-list="true"
|
|
|
:headers="headers"
|
|
|
:file-list="fileList"
|
|
|
:before-remove="beforeRemove"
|
|
|
>
|
|
|
<i class="el-icon-upload"></i>
|
|
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
|
|
<div class="el-upload__tip" slot="tip">只能上传一个文档,支持<b style="color:#f56c6c">PDF、Word</b>等格式,最大<b
|
|
|
style="color:#f56c6c">10MB</b></div>
|
|
|
</el-upload>
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
<div style="text-align:right;margin-top:24px;">
|
|
|
<el-button @click="onCancel">取消</el-button>
|
|
|
<el-button type="primary" @click="onNext" :loading="isProcessing" :disabled="isProcessing">下一步</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div v-else class="kb-upload-step2">
|
|
|
<KnowledgeContentPreview :doc="previewDoc"/>
|
|
|
<div style="text-align:right;margin-top:24px;">
|
|
|
<el-button type="primary" @click="onCancel">关闭</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
import {ref, watch} from 'vue'
|
|
|
import {ElMessage} from 'element-plus'
|
|
|
import KnowledgeContentPreview from './knowledgeContentPreview.vue'
|
|
|
import useUserStore from "@/store/modules/user";
|
|
|
import {UploadRawFile} from 'element-plus';
|
|
|
import {
|
|
|
vectorizeKnowledgeContent,
|
|
|
removeContentFile,
|
|
|
} from "@/api/ai/skill/aiKnowledgeBase";
|
|
|
import {getToken} from "@/utils/auth";
|
|
|
|
|
|
const step = ref(0)
|
|
|
const formRef = ref()
|
|
|
const form = ref({title: '', file: null, knowledgeBaseId: null, modelId: null})
|
|
|
const fileList = ref([])
|
|
|
|
|
|
const fileUploadRef = ref<ElUploadInstance>();
|
|
|
|
|
|
const number = ref(0)
|
|
|
const uploadList = ref([])
|
|
|
const dialogImageUrl = ref("")
|
|
|
const dialogVisible = ref(false)
|
|
|
const limit = ref(1)
|
|
|
const hideUpload = ref(false)
|
|
|
const baseUrl = import.meta.env.VITE_APP_BASE_API;
|
|
|
const uploadImgUrl = ref(baseUrl + "/ai/aiKnowledgeBase/uploadKnowledgeContent") // 上传的图片服务器地址
|
|
|
const headers = ref({
|
|
|
Authorization: "Bearer " + getToken(),
|
|
|
clientid: import.meta.env.VITE_APP_CLIENT_ID
|
|
|
})
|
|
|
|
|
|
const embeddingKnowledgeContentVo = ref({});
|
|
|
const emit = defineEmits<{
|
|
|
(e: 'cancel'): void
|
|
|
(e: 'confirm', payload?: any): void
|
|
|
}>()
|
|
|
|
|
|
|
|
|
const segments = ref([
|
|
|
'分段1内容',
|
|
|
'分段2内容',
|
|
|
'分段3内容'
|
|
|
])
|
|
|
|
|
|
interface Options {
|
|
|
file: '';
|
|
|
fileName: string;
|
|
|
previews: any; // 预览数据
|
|
|
outputType: string;
|
|
|
visible: boolean;
|
|
|
}
|
|
|
|
|
|
const {proxy} = getCurrentInstance() as ComponentInternalInstance;
|
|
|
|
|
|
const cropper = ref<any>({});
|
|
|
//图片裁剪数据
|
|
|
const options = reactive<Options>({
|
|
|
file: '',
|
|
|
outputType: 'png',
|
|
|
fileName: '',
|
|
|
previews: {},
|
|
|
visible: false
|
|
|
});
|
|
|
|
|
|
|
|
|
/** 上传预处理 */
|
|
|
const beforeUpload = (file: UploadRawFile): any => {
|
|
|
// 校检文件类型
|
|
|
// if (props.fileType.length) {
|
|
|
// const fileName = file.name.split('.');
|
|
|
// const fileExt = fileName[fileName.length - 1];
|
|
|
// const isTypeOk = props.fileType.indexOf(fileExt) >= 0;
|
|
|
// if (!isTypeOk) {
|
|
|
// proxy?.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join('/')}格式文件!`);
|
|
|
// return false;
|
|
|
// }
|
|
|
// }
|
|
|
// 校检文件大小
|
|
|
// if (props.fileSize) {
|
|
|
// const isLt = file.size / 1024 / 1024 < props.fileSize;
|
|
|
// if (!isLt) {
|
|
|
// proxy?.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
|
|
|
// return false;
|
|
|
// }
|
|
|
// }
|
|
|
if (fileList.value.length >= limit.value) {
|
|
|
proxy?.$modal.msgError(`最多上传${limit.value}个文件`);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
proxy?.$modal.loading('正在上传文件,请稍候...');
|
|
|
number.value++;
|
|
|
return true;
|
|
|
};
|
|
|
|
|
|
// 上传成功回调
|
|
|
const handleUploadSuccess = (res: any, file, fileList) => {
|
|
|
console.log('上传成功:', res)
|
|
|
if (res.code === 200) {
|
|
|
embeddingKnowledgeContentVo.value = res.data
|
|
|
embeddingKnowledgeContentVo.value.knowledgeBaseId = form.value.knowledgeBaseId;
|
|
|
embeddingKnowledgeContentVo.value.modelId = form.value.modelId;
|
|
|
uploadList.value.push({
|
|
|
name: res.data.fileName,
|
|
|
url: res.data.url,
|
|
|
knowledgeContentId: res.data.knowledgeContentId
|
|
|
});
|
|
|
uploadedSuccessfully();
|
|
|
} else {
|
|
|
number.value--;
|
|
|
proxy?.$modal.closeLoading();
|
|
|
proxy?.$modal.msgError(res.msg);
|
|
|
fileUploadRef.value?.handleRemove(file);
|
|
|
uploadedSuccessfully();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 上传结束处理
|
|
|
const uploadedSuccessfully = () => {
|
|
|
if (number.value > 0 && uploadList.value.length === number.value) {
|
|
|
fileList.value = fileList.value.filter((f) => f.url !== undefined).concat(uploadList.value);
|
|
|
uploadList.value = [];
|
|
|
number.value = 0;
|
|
|
// emit('update:modelValue', listToString(fileList.value));
|
|
|
// emit('update:modelValue', fileList.value);
|
|
|
proxy?.$modal.closeLoading();
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/** 删除预处理 */
|
|
|
const beforeRemove = (file: UploadRawFile) => {
|
|
|
let knowledgeContentId = file.knowledgeContentId;
|
|
|
return new Promise((resolve, reject) => {
|
|
|
// 模拟确认对话框
|
|
|
ElMessageBox.confirm(
|
|
|
`确定删除 ${file.name} ?`,
|
|
|
'警告',
|
|
|
{
|
|
|
confirmButtonText: '确定',
|
|
|
cancelButtonText: '取消',
|
|
|
type: 'warning',
|
|
|
}
|
|
|
).then(() => {
|
|
|
// 模拟API调用
|
|
|
removeContentFile(knowledgeContentId)
|
|
|
.then(() => {
|
|
|
resolve(true); // 允许删除
|
|
|
})
|
|
|
.catch(() => {
|
|
|
resolve(false); // 阻止删除
|
|
|
});
|
|
|
}).catch(() => {
|
|
|
// 用户点击取消,阻止删除
|
|
|
resolve(false);
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
// 删除文件
|
|
|
const handleDelete = (index: number) => {
|
|
|
let ossId = fileList.value[index].ossId;
|
|
|
// delOss(ossId);
|
|
|
fileList.value.splice(index, 1);
|
|
|
// emit('update:modelValue', listToString(fileList.value));
|
|
|
};
|
|
|
|
|
|
|
|
|
// 上传失败回调
|
|
|
const handleUploadError = (error: any) => {
|
|
|
console.error('上传失败:', error)
|
|
|
|
|
|
// 添加到上传记录
|
|
|
const record = {
|
|
|
fileName: '上传失败',
|
|
|
fileSize: '未知',
|
|
|
uploadTime: new Date().toLocaleString(),
|
|
|
status: 'error'
|
|
|
}
|
|
|
// uploadRecords.value.unshift(record)
|
|
|
|
|
|
ElMessage.error('文件上传失败')
|
|
|
}
|
|
|
|
|
|
|
|
|
const previewDoc = ref(null)
|
|
|
|
|
|
function handleFileChange(file) {
|
|
|
form.value.file = file.raw
|
|
|
fileList.value = [file]
|
|
|
}
|
|
|
|
|
|
const isProcessing = ref(false)
|
|
|
|
|
|
function onNext() {
|
|
|
// if (!form.value.title) return ElMessage.warning('请输入文档标题')
|
|
|
if (fileList.value.length === 0) return ElMessage.warning('请上传文档')
|
|
|
// 实际应上传并解析文档,获取分段内容
|
|
|
// step.value = 1
|
|
|
processNext();
|
|
|
}
|
|
|
|
|
|
async function processNext() {
|
|
|
try {
|
|
|
isProcessing.value = true
|
|
|
proxy?.$modal.loading('正在向量化文档,请稍候...')
|
|
|
|
|
|
// 如果有可用的向量化参数,则调用后端处理(可选)
|
|
|
if (embeddingKnowledgeContentVo.value && Object.keys(embeddingKnowledgeContentVo.value).length > 0) {
|
|
|
await vectorizeKnowledgeContent(embeddingKnowledgeContentVo.value)
|
|
|
previewDoc.value = embeddingKnowledgeContentVo.value
|
|
|
step.value = 1
|
|
|
ElMessage.success('向量化完成')
|
|
|
} else {
|
|
|
ElMessage.error('处理失败,请重试')
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
console.error(err)
|
|
|
} finally {
|
|
|
proxy?.$modal.closeLoading()
|
|
|
isProcessing.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function onCancel() {
|
|
|
// TODO: 返回或关闭页面
|
|
|
ElMessage.info('已取消')
|
|
|
emit('cancel')
|
|
|
}
|
|
|
|
|
|
function onConfirm() {
|
|
|
}
|
|
|
|
|
|
|
|
|
watch([() => form.value.title, segments], () => {
|
|
|
previewDoc.value.title = form.value.title
|
|
|
previewDoc.value.segments = segments.value
|
|
|
})
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
let paramKnowledgeBaseId = proxy.$route.params?.knowledgeBaseId
|
|
|
form.value.knowledgeBaseId = paramKnowledgeBaseId;
|
|
|
let paramModelId = proxy.$route.params?.modelId;
|
|
|
form.value.modelId = paramModelId;
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
<style>
|
|
|
.kb-upload-page {
|
|
|
height: 90%;
|
|
|
width: 100%;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
.kb-upload-step1 {
|
|
|
padding: 32px 24px 0 24px;
|
|
|
width: 100%;
|
|
|
height: 80%;
|
|
|
margin: 0 auto;
|
|
|
}
|
|
|
|
|
|
.kb-upload-form {
|
|
|
padding: 32px 24px 0 24px;
|
|
|
width: 70%;
|
|
|
max-width: 800px;
|
|
|
margin: 0 auto;
|
|
|
}
|
|
|
|
|
|
.kb-upload-form .el-form-item__content {
|
|
|
width: 100%;
|
|
|
}
|
|
|
|
|
|
.kb-upload-form .el-input {
|
|
|
width: 100%;
|
|
|
}
|
|
|
|
|
|
.kb-upload-form .el-upload {
|
|
|
width: 100%;
|
|
|
}
|
|
|
|
|
|
.kb-upload-preview-title {
|
|
|
font-size: 18px;
|
|
|
font-weight: bold;
|
|
|
margin-bottom: 16px;
|
|
|
}
|
|
|
|
|
|
.kb-upload-segment-content {
|
|
|
background: #f5f7fa;
|
|
|
border-radius: 4px;
|
|
|
padding: 10px 16px;
|
|
|
color: #333;
|
|
|
margin-bottom: 8px;
|
|
|
}
|
|
|
|
|
|
.kb-upload-steps-bar {
|
|
|
background: #f5f7fa;
|
|
|
padding: 32px 0 24px 0;
|
|
|
margin-bottom: 32px;
|
|
|
border-radius: 8px 8px 0 0;
|
|
|
box-shadow: 0 2px 8px #f0f1f2;
|
|
|
}
|
|
|
|
|
|
.kb-upload-steps-bar .el-steps {
|
|
|
max-width: 520px;
|
|
|
margin: 0 auto;
|
|
|
}
|
|
|
|
|
|
.kb-upload-steps-bar .el-step__icon {
|
|
|
width: 120px !important;
|
|
|
height: 120px !important;
|
|
|
font-size: 40px !important;
|
|
|
border-width: 10px !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
.kb-upload-steps-bar .el-step.is-process .el-step__icon,
|
|
|
.kb-upload-steps-bar .el-step.is-success .el-step__icon {
|
|
|
border-color: #409EFF !important;
|
|
|
border-width: 10px !important;
|
|
|
background: #409EFF !important;
|
|
|
color: #fff !important;
|
|
|
box-shadow: 0 0 16px #409EFF55;
|
|
|
}
|
|
|
|
|
|
.kb-upload-steps-bar .el-step__title {
|
|
|
font-size: 22px;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
|
|
|
.kb-upload-steps-bar .el-step.is-process .el-step__title,
|
|
|
.kb-upload-steps-bar .el-step.is-success .el-step__title {
|
|
|
color: #409EFF !important;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
|
|
|
.kb-upload-steps-bar .el-step__line {
|
|
|
margin-top: 50px;
|
|
|
height: 10px !important;
|
|
|
border-radius: 3px;
|
|
|
}
|
|
|
|
|
|
.kb-upload-steps-bar .el-step.is-success .el-step__line {
|
|
|
background: #409EFF !important;
|
|
|
}
|
|
|
|
|
|
.kb-upload-steps-bar .el-step.is-process .el-step__line {
|
|
|
background: #409EFF !important;
|
|
|
}
|
|
|
|
|
|
.kb-upload-steps-bar .el-step.is-wait .el-step__icon {
|
|
|
border-color: #e0e0e0 !important;
|
|
|
border-width: 3px !important;
|
|
|
background: #f0f1f2 !important;
|
|
|
color: #bbb !important;
|
|
|
}
|
|
|
|
|
|
.kb-upload-steps-bar .el-step.is-wait .el-step__line {
|
|
|
background: #e0e0e0 !important;
|
|
|
}
|
|
|
|
|
|
.kb-upload-steps-bar .el-step.is-success .el-step__icon {
|
|
|
background: #409EFF !important; /* 你想要的蓝色 */
|
|
|
border-color: #409EFF !important;
|
|
|
color: #fff !important;
|
|
|
}
|
|
|
|
|
|
.kb-upload-step2 {
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
}
|
|
|
|
|
|
.el-dialog__body {
|
|
|
height: 100% !important;
|
|
|
min-height: 100% !important;
|
|
|
padding: 0;
|
|
|
}
|
|
|
</style>
|