feat: 补充被覆盖的文件
This commit is contained in:
parent
3b64c9fd79
commit
40eaa8e7c0
|
@ -1,7 +1,17 @@
|
|||
<template>
|
||||
<div class="main-container">
|
||||
<div class="page-header">
|
||||
<div class="header-top">
|
||||
<el-button
|
||||
type="text"
|
||||
:icon="ArrowLeft"
|
||||
@click="handleGoBack"
|
||||
class="back-button"
|
||||
>
|
||||
{{t('vabI18n.knowledge.document.buttons.back')}}
|
||||
</el-button>
|
||||
<h2 class="page-title">{{ datasetName }}</h2>
|
||||
</div>
|
||||
<div class="page-description">
|
||||
{{t('vabI18n.knowledge.document.header.description')}} {{ pagination.total }} {{ $t(pagination.total > 1
|
||||
? 'vabI18n.knowledge.document.header.descriptionEnds'
|
||||
|
@ -251,159 +261,20 @@
|
|||
</transition>
|
||||
</div>
|
||||
|
||||
<!-- 上传文件对话框 -->
|
||||
<el-dialog v-model="uploadDialogVisible" :title="t('vabI18n.knowledge.document.buttons.upload')" width="720px" class="upload-dialog">
|
||||
<el-form :model="uploadForm" label-width="120px" class="upload-form">
|
||||
<el-form-item label="索引技术" label-width="auto">
|
||||
<el-radio-group v-model="uploadForm.indexingTechnique" class="indexing-radio-group">
|
||||
<el-radio value="high_quality">高质量索引(推荐)</el-radio>
|
||||
<el-radio value="economy">经济模式</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="form-tip">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
<span>高质量索引提供更好的搜索精度,经济模式处理速度更快</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="预处理规则" label-width="auto">
|
||||
<el-checkbox-group v-model="uploadForm.preProcessingRules" class="preprocessing-group">
|
||||
<el-checkbox value="remove_extra_spaces">移除多余空格</el-checkbox>
|
||||
<el-checkbox value="remove_urls_emails">移除URL和邮箱</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
<div class="form-tip">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
<span>预处理可以提高文档的索引质量和搜索效果</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="文档分段设置" label-width="auto">
|
||||
<div class="segment-config">
|
||||
<div class="config-row">
|
||||
<label class="field-label">分段标识符</label>
|
||||
<el-input
|
||||
v-model="uploadForm.segmentSeparator"
|
||||
placeholder="###"
|
||||
class="config-input"
|
||||
/>
|
||||
</div>
|
||||
<div class="config-row">
|
||||
<label class="field-label">最大分段长度</label>
|
||||
<el-input-number
|
||||
v-model="uploadForm.segmentMaxTokens"
|
||||
:min="100"
|
||||
:max="2000"
|
||||
placeholder="500"
|
||||
class="config-input"
|
||||
controls-position="right"
|
||||
/>
|
||||
</div>
|
||||
<div class="config-row">
|
||||
<label class="field-label">分段重叠长度</label>
|
||||
<el-input-number
|
||||
v-model="uploadForm.chunkOverlap"
|
||||
:min="0"
|
||||
:max="200"
|
||||
placeholder="50"
|
||||
class="config-input"
|
||||
controls-position="right"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="检索模式设置" label-width="auto">
|
||||
<div class="retrieval-config">
|
||||
<div class="config-row">
|
||||
<label class="field-label">召回条数 (TopK)</label>
|
||||
<el-select v-model="uploadForm.retrievalModel.topK" placeholder="选择召回条数" class="config-input">
|
||||
<el-option label="1条" :value="1" />
|
||||
<el-option label="2条" :value="2" />
|
||||
<el-option label="3条" :value="3" />
|
||||
<el-option label="4条" :value="4" />
|
||||
<el-option label="5条" :value="5" />
|
||||
<el-option label="6条" :value="6" />
|
||||
<el-option label="8条" :value="8" />
|
||||
<el-option label="10条" :value="10" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="config-row">
|
||||
<label class="field-label">分数阈值设置</label>
|
||||
<div class="score-config">
|
||||
<el-switch
|
||||
v-model="uploadForm.retrievalModel.scoreThresholdEnabled"
|
||||
active-text="启用"
|
||||
inactive-text="关闭"
|
||||
class="score-switch"
|
||||
/>
|
||||
<el-input-number
|
||||
v-model="uploadForm.retrievalModel.scoreThreshold"
|
||||
:min="0"
|
||||
:max="1"
|
||||
:step="0.1"
|
||||
:precision="1"
|
||||
placeholder="0.5"
|
||||
:disabled="!uploadForm.retrievalModel.scoreThresholdEnabled"
|
||||
class="score-input"
|
||||
controls-position="right"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="高级选项" label-width="auto">
|
||||
<el-checkbox v-model="uploadForm.deepAnalysis" class="deep-analysis-checkbox">
|
||||
{{t('vabI18n.knowledge.document.uploadDialog.deepAnalysisOption')}}
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
multiple
|
||||
drag
|
||||
:auto-upload="false"
|
||||
:on-change="handleFileChange"
|
||||
:file-list="uploadFiles"
|
||||
:limit="10"
|
||||
accept=".txt,.md,.markdown,.mdx,.pdf,.html,.htm,.xlsx,.xls,.doc,.docx,.csv,.vtt,.properties"
|
||||
class="upload-component"
|
||||
:class="{ 'is-dragover': isDragOver }"
|
||||
@drop="handleDrop"
|
||||
@dragover="handleDragOver"
|
||||
@dragleave="handleDragLeave"
|
||||
<!-- 上传文件对话框 - 使用新的DocUpload组件 -->
|
||||
<el-dialog
|
||||
v-model="uploadDialogVisible"
|
||||
:title="t('vabI18n.knowledge.document.buttons.upload')"
|
||||
width="900px"
|
||||
:close-on-click-modal="false"
|
||||
class="upload-dialog"
|
||||
>
|
||||
<div class="upload-dragger-content">
|
||||
<el-icon class="upload-icon" :class="{ 'bounce': isDragOver }">
|
||||
<Upload />
|
||||
</el-icon>
|
||||
<div class="upload-text">
|
||||
<p class="primary-text">点击选择文件 或 拖拽文件到此处</p>
|
||||
<p class="secondary-text">支持批量上传,最多可选择 10 个文件</p>
|
||||
</div>
|
||||
<el-button type="primary" class="upload-button">
|
||||
<el-icon><Upload /></el-icon>
|
||||
浏览文件
|
||||
</el-button>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="upload-tip">
|
||||
<div class="tip-row">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
<span>支持格式:TXT、MD、PDF、Word、Excel、HTML、CSV 等</span>
|
||||
</div>
|
||||
<div class="tip-row">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
<span>单个文件大小不超过 100MB,建议文件内容清晰完整</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleCancelUpload">{{t('vabI18n.knowledge.document.uploadDialog.cancel')}}</el-button>
|
||||
<el-button type="primary" @click="handleUpload" :loading="uploading">{{t('vabI18n.knowledge.document.uploadDialog.upload')}}</el-button>
|
||||
</template>
|
||||
<DocUpload
|
||||
:visible="uploadDialogVisible"
|
||||
:dataset-id="datasetId"
|
||||
@update:visible="uploadDialogVisible = $event"
|
||||
@success="handleUploadSuccess"
|
||||
/>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 预览抽屉 -->
|
||||
|
@ -468,10 +339,11 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
|
||||
import VueOfficePdf from '@vue-office/pdf'
|
||||
import { getDatasetDocPage, uploadDocument, deleteDocument, downloadDocument, previewDocumentUrl, renameDocument, getDeepAnalysisList } from '@/api/dataset'
|
||||
import DocUpload from './DocUpload.vue'
|
||||
//引入VueOfficeDocx组件
|
||||
import VueOfficeDocx from '@vue-office/docx'
|
||||
//引入相关样式
|
||||
|
@ -489,7 +361,7 @@ import hljs from 'highlight.js'
|
|||
import 'highlight.js/styles/github.css'
|
||||
|
||||
import { ElLoading, ElMessage, ElNotification, ElMessageBox } from 'element-plus'
|
||||
import type { UploadFile as ElUploadFile, UploadFiles } from 'element-plus'
|
||||
|
||||
|
||||
import {
|
||||
Search,
|
||||
|
@ -504,41 +376,25 @@ import {
|
|||
Loading,
|
||||
DataAnalysis,
|
||||
Document,
|
||||
InfoFilled
|
||||
InfoFilled,
|
||||
ArrowLeft
|
||||
} from '@element-plus/icons-vue'
|
||||
import { watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
// 添加uploadRef引用
|
||||
const uploadRef = ref()
|
||||
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { token } = userStore
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const datasetId = ref('');
|
||||
const datasetName = ref('');
|
||||
const previewLoading = ref(true)
|
||||
|
||||
const uploadDialogVisible = ref(false)
|
||||
const uploading = ref(false)
|
||||
const uploadFiles = ref<ElUploadFile[]>([])
|
||||
const isDragOver = ref(false)
|
||||
|
||||
const uploadForm = reactive({
|
||||
indexingTechnique: 'high_quality',
|
||||
preProcessingRules: ['remove_extra_spaces', 'remove_urls_emails'],
|
||||
segmentSeparator: '###',
|
||||
segmentMaxTokens: 500,
|
||||
chunkOverlap: 50,
|
||||
deepAnalysis: false,
|
||||
retrievalModel: {
|
||||
topK: 3,
|
||||
scoreThresholdEnabled: false,
|
||||
scoreThreshold: 0.5
|
||||
}
|
||||
})
|
||||
|
||||
// 重命名相关
|
||||
const renameDialogVisible = ref(false)
|
||||
|
@ -793,7 +649,7 @@ const pagination = reactive({
|
|||
// 文件列表数据
|
||||
const fileList = ref<FileItem[]>([])
|
||||
|
||||
const fileInput = ref<HTMLInputElement>()
|
||||
|
||||
//预览右抽屉状态
|
||||
const previewDrawerVisible = ref(false)
|
||||
const previewFileType = ref('')
|
||||
|
@ -922,20 +778,6 @@ const formatNumber = (num: number) => {
|
|||
}
|
||||
|
||||
// 处理取消上传
|
||||
const handleCancelUpload = () => {
|
||||
uploadDialogVisible.value = false
|
||||
uploadFiles.value = []
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
|
||||
// 监听对话框关闭事件
|
||||
watch(uploadDialogVisible, (newVal) => {
|
||||
if (!newVal) {
|
||||
uploadFiles.value = []
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
})
|
||||
|
||||
// 监听预览抽屉关闭事件,清理预览内容
|
||||
watch(previewDrawerVisible, (newVal) => {
|
||||
if (!newVal) {
|
||||
|
@ -961,7 +803,6 @@ const errorHandler = () => {
|
|||
}
|
||||
|
||||
const triggerFileInput = () => {
|
||||
fileInput.value?.click()
|
||||
uploadDialogVisible.value = true
|
||||
}
|
||||
|
||||
|
@ -969,101 +810,17 @@ const indexMethod = (index: number) => {
|
|||
return (pagination.current - 1) * pagination.size + index + 1
|
||||
}
|
||||
|
||||
// 文件选择处理
|
||||
const handleFileChange = (file: ElUploadFile, files: UploadFiles) => {
|
||||
uploadFiles.value = files
|
||||
}
|
||||
|
||||
// 拖拽事件处理
|
||||
const handleDragOver = (event: DragEvent) => {
|
||||
event.preventDefault()
|
||||
isDragOver.value = true
|
||||
}
|
||||
|
||||
const handleDragLeave = (event: DragEvent) => {
|
||||
event.preventDefault()
|
||||
isDragOver.value = false
|
||||
}
|
||||
|
||||
const handleDrop = (event: DragEvent) => {
|
||||
event.preventDefault()
|
||||
isDragOver.value = false
|
||||
}
|
||||
|
||||
// 上传处理
|
||||
const handleUpload = async () => {
|
||||
if (uploadFiles.value.length === 0) {
|
||||
ElMessage.warning(t('vabI18n.knowledge.document.messages.noUploadFile'))
|
||||
return
|
||||
}
|
||||
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: t('vabI18n.knowledge.document.messages.uploadLoading'),
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
|
||||
try {
|
||||
// 构造请求数据
|
||||
const processRule = {
|
||||
rules: {
|
||||
preProcessingRules: uploadForm.preProcessingRules.map(rule => ({
|
||||
id: rule,
|
||||
enabled: true
|
||||
})),
|
||||
segmentation: {
|
||||
separator: uploadForm.segmentSeparator || '###',
|
||||
maxTokens: uploadForm.segmentMaxTokens
|
||||
},
|
||||
chunkOverlap: uploadForm.chunkOverlap,
|
||||
},
|
||||
mode: "custom"
|
||||
}
|
||||
|
||||
// 遍历上传文件
|
||||
for (const file of uploadFiles.value) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file.raw!)
|
||||
formData.append('request', new Blob([JSON.stringify({
|
||||
datasetId: datasetId.value,
|
||||
indexingTechnique: uploadForm.indexingTechnique,
|
||||
processRule: processRule,
|
||||
deepAnalysis: uploadForm.deepAnalysis,
|
||||
retrievalModel: uploadForm.retrievalModel
|
||||
})], {
|
||||
type: 'application/json'
|
||||
}))
|
||||
|
||||
await uploadDocument(formData)
|
||||
}
|
||||
|
||||
ElNotification({
|
||||
title: t('vabI18n.knowledge.document.messages.uploadSuccess'),
|
||||
message: `${uploadFiles.value.length} ${t('vabI18n.knowledge.document.messages.uploadSuccessEnd')}`,
|
||||
type: 'success'
|
||||
})
|
||||
|
||||
// 处理DocUpload组件的上传成功回调
|
||||
const handleUploadSuccess = async () => {
|
||||
uploadDialogVisible.value = false
|
||||
await fetchDocuments()
|
||||
|
||||
// 如果启用了深度解析,开始监控进度
|
||||
if (uploadForm.deepAnalysis) {
|
||||
setTimeout(async () => {
|
||||
await fetchDeepAnalysisList()
|
||||
}, 2000) // 2秒后获取任务列表
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('上传错误:', error)
|
||||
ElNotification({
|
||||
title: t('vabI18n.knowledge.document.errors.uploadFailed'),
|
||||
message: error instanceof Error ? error.message : t('vabI18n.knowledge.document.messages.uploadFailed'),
|
||||
type: 'error'
|
||||
title: t('vabI18n.knowledge.document.messages.uploadSuccess'),
|
||||
message: t('vabI18n.knowledge.document.messages.uploadSuccessEnd'),
|
||||
type: 'success'
|
||||
})
|
||||
} finally {
|
||||
loading.close()
|
||||
uploadFiles.value = []
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
}
|
||||
|
||||
//文件下载
|
||||
|
@ -1218,6 +975,11 @@ const handleRefresh = () => {
|
|||
fetchDocuments()
|
||||
}
|
||||
|
||||
// 返回知识库列表
|
||||
const handleGoBack = () => {
|
||||
router.push('/datasets')
|
||||
}
|
||||
|
||||
const formatTimestampToLocaleString = (timestamp: number): string => {
|
||||
const date = new Date(timestamp * 1000)
|
||||
return date.toLocaleString()
|
||||
|
@ -1256,7 +1018,7 @@ const handleDelete = async (row: FileItem) => {
|
|||
// 成功处理
|
||||
ElNotification({
|
||||
title: t('vabI18n.knowledge.document.messages.deleteSuccessOk'),
|
||||
message: `t('vabI18n.knowledge.document.messages.deleteSuccess') "${row.fileName}" t('vabI18n.knowledge.document.messages.deleteSuccessEnd')`,
|
||||
message: `${t('vabI18n.knowledge.document.messages.deleteSuccess')}"${row.fileName}"${t('vabI18n.knowledge.document.messages.deleteSuccessEnd')}`,
|
||||
type: 'success'
|
||||
})
|
||||
|
||||
|
@ -1304,11 +1066,29 @@ const handleSearch = () => {
|
|||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.header-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.back-button {
|
||||
color: #409eff;
|
||||
font-size: 14px;
|
||||
padding: 8px 12px;
|
||||
|
||||
&:hover {
|
||||
background-color: #ecf5ff;
|
||||
color: #337ecc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0 0 8px 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page-description {
|
||||
|
@ -1727,118 +1507,8 @@ const handleSearch = () => {
|
|||
}
|
||||
}
|
||||
|
||||
.upload-form {
|
||||
.indexing-radio-group {
|
||||
::v-deep .el-radio {
|
||||
margin-right: 24px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.el-radio__label {
|
||||
font-weight: 500;
|
||||
color: #495057;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preprocessing-group {
|
||||
::v-deep .el-checkbox {
|
||||
margin-right: 24px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.el-checkbox__label {
|
||||
font-weight: 500;
|
||||
color: #495057;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
padding: 8px 12px;
|
||||
background: #e8f4fd;
|
||||
border: 1px solid #bee5eb;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
color: #0c5460;
|
||||
|
||||
.el-icon {
|
||||
font-size: 14px;
|
||||
color: #17a2b8;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.segment-config,
|
||||
.retrieval-config {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
|
||||
.config-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #495057;
|
||||
min-width: 120px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.config-input {
|
||||
flex: 1;
|
||||
max-width: 200px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.score-config {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
margin-left: 16px;
|
||||
|
||||
.score-switch {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.score-input {
|
||||
flex: 1;
|
||||
max-width: 120px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.deep-analysis-checkbox {
|
||||
::v-deep .el-checkbox__label {
|
||||
font-weight: 500;
|
||||
color: #495057;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-component {
|
||||
margin-top: 8px;
|
||||
|
||||
::v-deep .el-upload__tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 8px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preview-drawer {
|
||||
::v-deep .el-drawer__header {
|
||||
|
|
|
@ -10,8 +10,8 @@ import org.springframework.beans.factory.annotation.Value;
|
|||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Order(3)
|
||||
//@Component
|
||||
//@Order(3)
|
||||
public class EsStartupImporter implements CommandLineRunner {
|
||||
|
||||
@Value("${elasticsearch.dirPath}")
|
||||
|
|
|
@ -17,7 +17,7 @@ import java.util.HashSet;
|
|||
*
|
||||
* 2019年3月29日
|
||||
*/
|
||||
@Component
|
||||
//@Component
|
||||
public class FeignConfiguration extends FeignSudoUtil implements RequestInterceptor {
|
||||
|
||||
|
||||
|
|
|
@ -100,13 +100,13 @@ public class DocumentUploadReq implements Serializable {
|
|||
@JsonAlias({"maxTokens"})
|
||||
private Integer maxTokens;
|
||||
|
||||
// @JsonProperty("chunk_overlap")
|
||||
// @JsonAlias({"chunkOverlap"})
|
||||
// private Integer chunkOverlap;
|
||||
//
|
||||
// @JsonProperty("chunk_size")
|
||||
// @JsonAlias({"chunkSize"})
|
||||
// private Integer chunkSize;
|
||||
@JsonProperty("chunk_overlap")
|
||||
@JsonAlias({"chunkOverlap"})
|
||||
private Integer chunkOverlap;
|
||||
|
||||
@JsonProperty("chunk_size")
|
||||
@JsonAlias({"chunkSize"})
|
||||
private Integer chunkSize;
|
||||
}
|
||||
|
||||
@Data
|
||||
|
|
Loading…
Reference in New Issue