feat: 补充被覆盖的文件

This commit is contained in:
wenjinbo 2025-09-09 16:13:13 +08:00
parent 3b64c9fd79
commit 40eaa8e7c0
4 changed files with 1490 additions and 1820 deletions

View File

@ -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>支持格式TXTMDPDFWordExcelHTMLCSV </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 {

View File

@ -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}")

View File

@ -17,7 +17,7 @@ import java.util.HashSet;
*
* 2019年3月29日
*/
@Component
//@Component
public class FeignConfiguration extends FeignSudoUtil implements RequestInterceptor {

View File

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