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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<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>