You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

437 lines
11 KiB
Vue

<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">PDFWord</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>