2025-09-03 16:27:33 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="doc-upload-container">
|
|
|
|
|
|
<!-- 步骤条 -->
|
|
|
|
|
|
<el-steps :active="currentStep" align-center class="upload-steps">
|
2025-09-18 15:50:12 +08:00
|
|
|
|
<el-step :title="props.replaceFileId ? '选择替换文件' : '选择文件'" icon="Upload" />
|
2025-09-03 16:27:33 +08:00
|
|
|
|
<el-step title="解析模式" icon="Setting" />
|
|
|
|
|
|
<el-step :title="getStep3Title()" :icon="getStep3Icon()" />
|
|
|
|
|
|
</el-steps>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 步骤内容 -->
|
|
|
|
|
|
<div class="step-content">
|
|
|
|
|
|
<!-- 第一步:文件选择 -->
|
|
|
|
|
|
<div v-if="currentStep === 0" class="step-panel file-selection">
|
|
|
|
|
|
<div class="panel-header">
|
2025-09-18 15:50:12 +08:00
|
|
|
|
<h3>{{ props.replaceFileId ? '选择要替换的文件' : '选择要上传的文件' }}</h3>
|
2025-09-03 16:27:33 +08:00
|
|
|
|
<p class="panel-description">支持多种文档格式,拖拽或点击选择文件</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<el-upload
|
|
|
|
|
|
ref="uploadRef"
|
|
|
|
|
|
multiple
|
|
|
|
|
|
drag
|
|
|
|
|
|
:auto-upload="false"
|
|
|
|
|
|
:on-change="handleFileChange"
|
2025-09-18 15:50:12 +08:00
|
|
|
|
:file-list="[]"
|
|
|
|
|
|
:show-file-list="false"
|
2025-09-19 16:35:07 +08:00
|
|
|
|
accept=".txt,.md,.markdown,.mdx,.pdf,.html,.htm,.xlsx,.xls,.docx,.csv,.vtt,.properties"
|
2025-09-03 16:27:33 +08:00
|
|
|
|
class="upload-area"
|
|
|
|
|
|
:class="{ 'is-dragover': isDragOver }"
|
|
|
|
|
|
@drop="handleDrop"
|
|
|
|
|
|
@dragover="handleDragOver"
|
|
|
|
|
|
@dragleave="handleDragLeave"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="upload-content">
|
|
|
|
|
|
<el-icon class="upload-icon" :class="{ 'bounce': isDragOver }">
|
|
|
|
|
|
<Upload />
|
|
|
|
|
|
</el-icon>
|
|
|
|
|
|
<div class="upload-text">
|
|
|
|
|
|
<h4>拖拽文件到此处 或 <span class="link-text">点击选择</span></h4>
|
2025-09-19 16:35:07 +08:00
|
|
|
|
<p>支持 TXT、MD、PDF、DOCX、Excel、HTML、CSV 等格式(不支持DOC格式)</p>
|
2025-09-03 16:27:33 +08:00
|
|
|
|
<p>单个文件不超过 100MB,最多可选择 10 个文件</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-upload>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 已选文件列表 -->
|
|
|
|
|
|
<div v-if="uploadFiles.length > 0" class="selected-files">
|
|
|
|
|
|
<h4>已选择文件 ({{ uploadFiles.length }})</h4>
|
|
|
|
|
|
<div class="file-list">
|
|
|
|
|
|
<div v-for="(file, index) in uploadFiles" :key="index" class="file-item">
|
|
|
|
|
|
<el-icon class="file-icon"><Document /></el-icon>
|
|
|
|
|
|
<span class="file-name">{{ file.name }}</span>
|
|
|
|
|
|
<span class="file-size">{{ formatFileSize(file.size) }}</span>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="danger"
|
|
|
|
|
|
:icon="Delete"
|
|
|
|
|
|
text
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="removeFile(index)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 第二步:解析模式选择 -->
|
|
|
|
|
|
<div v-if="currentStep === 1" class="step-panel mode-selection">
|
|
|
|
|
|
<div class="panel-header">
|
|
|
|
|
|
<h3>选择解析模式</h3>
|
|
|
|
|
|
<p class="panel-description">根据您的需求选择合适的文档解析策略</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="mode-options">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="mode in analysisMode"
|
|
|
|
|
|
:key="mode.value"
|
|
|
|
|
|
class="mode-card"
|
|
|
|
|
|
:class="{ 'selected': uploadForm.analysisStrategyType === mode.value }"
|
|
|
|
|
|
@click="selectMode(mode.value)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="mode-icon">
|
|
|
|
|
|
<el-icon :size="32"><component :is="mode.icon" /></el-icon>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<h4>{{ mode.title }}</h4>
|
|
|
|
|
|
<p>{{ mode.description }}</p>
|
|
|
|
|
|
<div class="mode-features">
|
|
|
|
|
|
<el-tag v-for="feature in mode.features" :key="feature" size="small" type="info">
|
|
|
|
|
|
{{ feature }}
|
|
|
|
|
|
</el-tag>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 第三步:参数设置或进度显示 -->
|
|
|
|
|
|
<div v-if="currentStep === 2" class="step-panel config-panel">
|
|
|
|
|
|
<!-- 自定义模式 - 参数设置 -->
|
|
|
|
|
|
<div v-if="uploadForm.analysisStrategyType === 'custom'" class="custom-config">
|
|
|
|
|
|
<div class="panel-header">
|
|
|
|
|
|
<h3>自定义解析参数</h3>
|
|
|
|
|
|
<p class="panel-description">配置详细的文档解析参数</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<el-form :model="uploadForm.docAnalysisStrategy" label-width="140px" class="config-form">
|
|
|
|
|
|
<div class="form-section">
|
|
|
|
|
|
<h4 class="section-title">基础设置</h4>
|
2025-09-22 17:25:47 +08:00
|
|
|
|
<!--
|
2025-09-22 15:34:40 +08:00
|
|
|
|
<el-form-item label="策略名称">
|
|
|
|
|
|
<el-input v-model="uploadForm.docAnalysisStrategy.name" placeholder="请输入策略名称(可选)" />
|
|
|
|
|
|
</el-form-item>
|
2025-09-22 17:25:47 +08:00
|
|
|
|
-->
|
2025-09-03 16:27:33 +08:00
|
|
|
|
<el-form-item label="分段模式">
|
|
|
|
|
|
<el-select v-model="uploadForm.docAnalysisStrategy.segmentationMode" placeholder="选择分段模式">
|
2025-09-22 17:25:47 +08:00
|
|
|
|
<el-option label="普通分段" value="custom" />
|
|
|
|
|
|
<el-option label="父子分段" value="hierarchical" />
|
2025-09-03 16:27:33 +08:00
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item v-if="uploadForm.docAnalysisStrategy.segmentationMode === 'parent_child'" label="父分段召回模式">
|
|
|
|
|
|
<el-select v-model="uploadForm.docAnalysisStrategy.parentSegmentMode" placeholder="选择父分段召回模式">
|
|
|
|
|
|
<el-option label="全文召回" value="full-doc" />
|
|
|
|
|
|
<el-option label="段落召回" value="paragraph" />
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="索引技术">
|
|
|
|
|
|
<el-select v-model="uploadForm.docAnalysisStrategy.indexingTechnique" placeholder="选择索引技术">
|
|
|
|
|
|
<el-option label="高质量索引" value="high_quality" />
|
|
|
|
|
|
<el-option label="经济模式" value="economy" />
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 普通分段参数 -->
|
2025-09-22 17:25:47 +08:00
|
|
|
|
<div v-if="uploadForm.docAnalysisStrategy.segmentationMode === 'custom'" class="form-section">
|
2025-09-03 16:27:33 +08:00
|
|
|
|
<h4 class="section-title">分段参数</h4>
|
|
|
|
|
|
<el-form-item label="段分隔符">
|
|
|
|
|
|
<el-input v-model="uploadForm.docAnalysisStrategy.segmentSeparator" placeholder="###" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="最大长度">
|
|
|
|
|
|
<el-input-number
|
|
|
|
|
|
v-model="uploadForm.docAnalysisStrategy.maxLength"
|
|
|
|
|
|
:min="100"
|
|
|
|
|
|
:max="2000"
|
|
|
|
|
|
placeholder="500"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="重叠长度">
|
|
|
|
|
|
<el-input-number
|
|
|
|
|
|
v-model="uploadForm.docAnalysisStrategy.overlapLength"
|
|
|
|
|
|
:min="0"
|
|
|
|
|
|
:max="200"
|
|
|
|
|
|
placeholder="50"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 父子分段参数 -->
|
2025-09-22 17:25:47 +08:00
|
|
|
|
<div v-if="uploadForm.docAnalysisStrategy.segmentationMode === 'hierarchical'" class="form-section">
|
2025-09-03 16:27:33 +08:00
|
|
|
|
<!-- 父分段设置 (仅在段落召回模式下显示) -->
|
|
|
|
|
|
<div v-if="uploadForm.docAnalysisStrategy.parentSegmentMode === 'paragraph'">
|
|
|
|
|
|
<h4 class="section-title">父分段设置</h4>
|
|
|
|
|
|
<el-form-item label="父段分隔符">
|
|
|
|
|
|
<el-input v-model="uploadForm.docAnalysisStrategy.segmentSeparator" placeholder="###" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="父段最大长度">
|
|
|
|
|
|
<el-input-number
|
|
|
|
|
|
v-model="uploadForm.docAnalysisStrategy.maxLength"
|
|
|
|
|
|
:min="100"
|
|
|
|
|
|
:max="2000"
|
|
|
|
|
|
placeholder="500"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="父段重叠长度">
|
|
|
|
|
|
<el-input-number
|
|
|
|
|
|
v-model="uploadForm.docAnalysisStrategy.overlapLength"
|
|
|
|
|
|
:min="0"
|
|
|
|
|
|
:max="200"
|
|
|
|
|
|
placeholder="50"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 子分段设置 (始终显示) -->
|
|
|
|
|
|
<h4 class="section-title">子分段设置</h4>
|
|
|
|
|
|
<el-form-item label="子段分隔符">
|
|
|
|
|
|
<el-input v-model="uploadForm.docAnalysisStrategy.childSegmentSeparator" placeholder="\n\n" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="子段最大长度">
|
|
|
|
|
|
<el-input-number
|
|
|
|
|
|
v-model="uploadForm.docAnalysisStrategy.childMaxLength"
|
|
|
|
|
|
:min="50"
|
|
|
|
|
|
:max="1000"
|
|
|
|
|
|
placeholder="200"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="子段重叠长度">
|
|
|
|
|
|
<el-input-number
|
|
|
|
|
|
v-model="uploadForm.docAnalysisStrategy.childOverlapLength"
|
|
|
|
|
|
:min="0"
|
|
|
|
|
|
:max="100"
|
|
|
|
|
|
placeholder="20"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="form-section">
|
|
|
|
|
|
<h4 class="section-title">检索设置</h4>
|
|
|
|
|
|
<el-form-item label="搜索方法">
|
|
|
|
|
|
<el-select v-model="uploadForm.docAnalysisStrategy.searchMethod" placeholder="选择搜索方法">
|
2025-09-22 17:25:47 +08:00
|
|
|
|
<el-option label="语义搜索" value="semantic_search" />
|
|
|
|
|
|
<el-option label="全文搜索" value="full_text_search" />
|
|
|
|
|
|
<el-option label="混合搜索" value="hybrid_search" />
|
2025-09-03 16:27:33 +08:00
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="Top K">
|
|
|
|
|
|
<el-input-number
|
|
|
|
|
|
v-model="uploadForm.docAnalysisStrategy.topK"
|
|
|
|
|
|
:min="1"
|
|
|
|
|
|
:max="20"
|
|
|
|
|
|
placeholder="3"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="分数阈值">
|
|
|
|
|
|
<div class="score-threshold-config">
|
|
|
|
|
|
<el-switch
|
|
|
|
|
|
v-model="uploadForm.docAnalysisStrategy.scoreThresholdEnabled"
|
|
|
|
|
|
active-text="启用"
|
|
|
|
|
|
inactive-text="关闭"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<el-input-number
|
|
|
|
|
|
v-model="uploadForm.docAnalysisStrategy.scoreThreshold"
|
|
|
|
|
|
:min="0"
|
|
|
|
|
|
:max="1"
|
|
|
|
|
|
:step="0.1"
|
|
|
|
|
|
:precision="1"
|
|
|
|
|
|
:disabled="!uploadForm.docAnalysisStrategy.scoreThresholdEnabled"
|
|
|
|
|
|
placeholder="0.5"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="form-section">
|
|
|
|
|
|
<h4 class="section-title">重排序设置</h4>
|
|
|
|
|
|
<el-form-item label="启用重排序">
|
|
|
|
|
|
<el-switch
|
|
|
|
|
|
v-model="uploadForm.docAnalysisStrategy.rerankingEnable"
|
|
|
|
|
|
active-text="启用"
|
|
|
|
|
|
inactive-text="关闭"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 深度解析模式 - 进度显示 -->
|
|
|
|
|
|
<div v-else-if="uploadForm.analysisStrategyType === 'deep'" class="deep-analysis-progress">
|
|
|
|
|
|
<div class="panel-header">
|
|
|
|
|
|
<h3>数据预处理进度</h3>
|
|
|
|
|
|
<p class="panel-description">正在进行深度分析,请耐心等待</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="progress-container">
|
|
|
|
|
|
<div class="progress-item" v-for="(task, index) in processingTasks" :key="index">
|
|
|
|
|
|
<div class="task-info">
|
|
|
|
|
|
<span class="task-name">{{ task.name }}</span>
|
|
|
|
|
|
<span class="task-status">{{ task.status }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<el-progress
|
|
|
|
|
|
:percentage="task.progress"
|
|
|
|
|
|
:status="task.progress === 100 ? 'success' : undefined"
|
|
|
|
|
|
:stroke-width="8"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 自适应模式 - 确认信息 -->
|
|
|
|
|
|
<div v-else class="adaptive-confirm">
|
|
|
|
|
|
<div class="panel-header">
|
2025-09-18 15:50:12 +08:00
|
|
|
|
<h3>{{ props.replaceFileId ? '确认替换' : '确认上传' }}</h3>
|
2025-09-03 16:27:33 +08:00
|
|
|
|
<p class="panel-description">使用自适应模式,系统将自动选择最佳解析策略</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="confirm-info">
|
|
|
|
|
|
<div class="info-card">
|
|
|
|
|
|
<el-icon class="info-icon"><InfoFilled /></el-icon>
|
|
|
|
|
|
<div class="info-content">
|
|
|
|
|
|
<h4>自适应模式特点</h4>
|
|
|
|
|
|
<ul>
|
|
|
|
|
|
<li>自动识别文档类型和结构</li>
|
|
|
|
|
|
<li>智能选择最佳分段策略</li>
|
|
|
|
|
|
<li>优化索引质量和检索效果</li>
|
|
|
|
|
|
<li>无需手动配置参数</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 操作按钮 -->
|
|
|
|
|
|
<div class="step-actions">
|
|
|
|
|
|
<el-button v-if="currentStep > 0" @click="prevStep">上一步</el-button>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-if="currentStep < 2"
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
@click="nextStep"
|
|
|
|
|
|
:disabled="!canProceedToNext()"
|
|
|
|
|
|
>
|
|
|
|
|
|
下一步
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-if="currentStep === 2"
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
@click="handleUpload"
|
|
|
|
|
|
:loading="uploading"
|
|
|
|
|
|
:disabled="!canUpload()"
|
|
|
|
|
|
>
|
2025-09-18 15:50:12 +08:00
|
|
|
|
{{ props.replaceFileId ? '开始替换' : '开始上传' }}
|
2025-09-03 16:27:33 +08:00
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button @click="handleCancel">取消</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2025-09-22 17:25:47 +08:00
|
|
|
|
import { ref, reactive, computed, nextTick, watch } from 'vue'
|
2025-09-03 16:27:33 +08:00
|
|
|
|
import { ElMessage, ElNotification, ElLoading } from 'element-plus'
|
|
|
|
|
|
import type { UploadFile as ElUploadFile } from 'element-plus'
|
|
|
|
|
|
import {
|
|
|
|
|
|
Upload,
|
|
|
|
|
|
Setting,
|
|
|
|
|
|
Document,
|
|
|
|
|
|
Delete,
|
|
|
|
|
|
InfoFilled,
|
|
|
|
|
|
Star,
|
|
|
|
|
|
Cpu,
|
|
|
|
|
|
Tools,
|
|
|
|
|
|
Check
|
|
|
|
|
|
} from '@element-plus/icons-vue'
|
2025-09-18 15:50:12 +08:00
|
|
|
|
import { uploadDocument, deleteDocument } from '@/api/dataset'
|
2025-09-03 16:27:33 +08:00
|
|
|
|
|
|
|
|
|
|
// Props
|
|
|
|
|
|
interface Props {
|
|
|
|
|
|
datasetId: string
|
|
|
|
|
|
visible: boolean
|
2025-09-11 13:39:32 +08:00
|
|
|
|
parentId?: number
|
2025-09-18 15:50:12 +08:00
|
|
|
|
replaceFileId?: string
|
2025-09-03 16:27:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
|
|
|
|
datasetId: '',
|
2025-09-11 13:39:32 +08:00
|
|
|
|
visible: false,
|
2025-09-18 15:50:12 +08:00
|
|
|
|
parentId: undefined,
|
|
|
|
|
|
replaceFileId: undefined
|
2025-09-03 16:27:33 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// Emits
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
|
'update:visible': [value: boolean]
|
|
|
|
|
|
'success': []
|
|
|
|
|
|
}>()
|
|
|
|
|
|
|
|
|
|
|
|
// 响应式数据
|
|
|
|
|
|
const currentStep = ref(0)
|
|
|
|
|
|
const uploading = ref(false)
|
|
|
|
|
|
const uploadFiles = ref<ElUploadFile[]>([])
|
|
|
|
|
|
const isDragOver = ref(false)
|
|
|
|
|
|
const uploadRef = ref()
|
2025-09-19 16:35:07 +08:00
|
|
|
|
const isProcessingFileLimit = ref(false)
|
2025-09-03 16:27:33 +08:00
|
|
|
|
|
|
|
|
|
|
// 解析模式选项
|
|
|
|
|
|
const analysisMode = [
|
|
|
|
|
|
{
|
|
|
|
|
|
value: 'adaption',
|
|
|
|
|
|
title: '自适应模式',
|
|
|
|
|
|
description: '智能识别文档结构,自动选择最佳解析策略',
|
|
|
|
|
|
icon: 'Star',
|
|
|
|
|
|
features: ['智能识别', '自动优化', '快速处理']
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
value: 'deep',
|
|
|
|
|
|
title: '深度解析',
|
|
|
|
|
|
description: '深度分析扫描文档及图片内容,提供最高质量的解析结果',
|
|
|
|
|
|
icon: 'Cpu',
|
|
|
|
|
|
features: ['深度分析', '高质量', '扫描件']
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
value: 'custom',
|
|
|
|
|
|
title: '自定义模式',
|
|
|
|
|
|
description: '完全自定义解析参数,满足特殊需求',
|
|
|
|
|
|
icon: 'Tools',
|
|
|
|
|
|
features: ['完全自定义', '灵活配置', '专业级']
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
// 上传表单数据
|
|
|
|
|
|
const uploadForm = reactive({
|
|
|
|
|
|
analysisStrategyType: 'adaption',
|
|
|
|
|
|
docAnalysisStrategy: {
|
|
|
|
|
|
name: '',
|
2025-09-22 17:25:47 +08:00
|
|
|
|
segmentationMode: 'custom',
|
2025-09-03 16:27:33 +08:00
|
|
|
|
indexingTechnique: 'high_quality',
|
|
|
|
|
|
docForm: 'text_model',
|
|
|
|
|
|
parentSegmentMode: 'paragraph',
|
|
|
|
|
|
segmentSeparator: '###',
|
|
|
|
|
|
maxLength: 500,
|
|
|
|
|
|
overlapLength: 50,
|
|
|
|
|
|
childSegmentSeparator: '\n\n',
|
|
|
|
|
|
childMaxLength: 200,
|
|
|
|
|
|
childOverlapLength: 20,
|
2025-09-22 17:25:47 +08:00
|
|
|
|
searchMethod: 'hybrid_search',
|
2025-09-03 16:27:33 +08:00
|
|
|
|
rerankingEnable: false,
|
2025-09-22 17:25:47 +08:00
|
|
|
|
rerankingProviderName: 'langgenius/huggingface_tei/huggingface_tei',
|
|
|
|
|
|
rerankingModelName: 'bge-reanker-v2-m3',
|
2025-09-03 16:27:33 +08:00
|
|
|
|
topK: 3,
|
|
|
|
|
|
scoreThresholdEnabled: false,
|
|
|
|
|
|
scoreThreshold: 0.5,
|
|
|
|
|
|
describe: ''
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 深度解析进度任务
|
|
|
|
|
|
const processingTasks = ref([
|
|
|
|
|
|
{ name: '文档解析', progress: 0, status: '等待中' },
|
|
|
|
|
|
{ name: '内容提取', progress: 0, status: '等待中' },
|
|
|
|
|
|
{ name: '结构分析', progress: 0, status: '等待中' },
|
|
|
|
|
|
{ name: '索引构建', progress: 0, status: '等待中' }
|
|
|
|
|
|
])
|
|
|
|
|
|
|
2025-09-22 17:25:47 +08:00
|
|
|
|
// 监听分段模式变化,自动设置docForm
|
|
|
|
|
|
watch(() => uploadForm.docAnalysisStrategy.segmentationMode, (newMode) => {
|
|
|
|
|
|
if (newMode === 'custom') {
|
|
|
|
|
|
uploadForm.docAnalysisStrategy.docForm = 'text_model'
|
|
|
|
|
|
} else if (newMode === 'hierarchical') {
|
|
|
|
|
|
uploadForm.docAnalysisStrategy.docForm = 'hierarchical_model'
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-09-03 16:27:33 +08:00
|
|
|
|
// 计算属性
|
|
|
|
|
|
const getStep3Title = () => {
|
|
|
|
|
|
switch (uploadForm.analysisStrategyType) {
|
|
|
|
|
|
case 'custom': return '参数设置'
|
|
|
|
|
|
case 'deep': return '处理进度'
|
2025-09-18 15:50:12 +08:00
|
|
|
|
default: return props.replaceFileId ? '确认替换' : '确认上传'
|
2025-09-03 16:27:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const getStep3Icon = () => {
|
|
|
|
|
|
switch (uploadForm.analysisStrategyType) {
|
|
|
|
|
|
case 'custom': return 'Tools'
|
|
|
|
|
|
case 'deep': return 'Loading'
|
|
|
|
|
|
default: return 'Check'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 方法
|
|
|
|
|
|
const handleFileChange = (file: ElUploadFile, fileList: ElUploadFile[]) => {
|
2025-09-19 16:35:07 +08:00
|
|
|
|
// 防止无限循环
|
|
|
|
|
|
if (isProcessingFileLimit.value) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-03 16:27:33 +08:00
|
|
|
|
// 文件大小检查
|
|
|
|
|
|
if (file.size && file.size > 100 * 1024 * 1024) {
|
|
|
|
|
|
ElMessage.error('文件大小不能超过 100MB')
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-19 16:35:07 +08:00
|
|
|
|
// 文件数量检查,如果超过10个,只保留前10个
|
|
|
|
|
|
if (fileList.length > 10) {
|
|
|
|
|
|
isProcessingFileLimit.value = true
|
|
|
|
|
|
|
|
|
|
|
|
const truncatedList = fileList.slice(0, 10)
|
|
|
|
|
|
const removedCount = fileList.length - 10
|
|
|
|
|
|
|
|
|
|
|
|
ElMessage.warning({
|
|
|
|
|
|
message: `已选择 ${fileList.length} 个文件,超出限制。系统已自动保留前 10 个文件,移除了 ${removedCount} 个文件。`,
|
|
|
|
|
|
duration: 4000,
|
|
|
|
|
|
showClose: true
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 更新我们的文件列表
|
|
|
|
|
|
uploadFiles.value = truncatedList
|
|
|
|
|
|
|
|
|
|
|
|
// 清除并重新设置上传组件的文件列表
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
if (uploadRef.value) {
|
|
|
|
|
|
uploadRef.value.clearFiles()
|
|
|
|
|
|
|
|
|
|
|
|
// 重新添加前10个文件
|
|
|
|
|
|
truncatedList.forEach(uploadFile => {
|
|
|
|
|
|
if (uploadFile.raw) {
|
|
|
|
|
|
uploadRef.value.handleStart(uploadFile.raw)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重置标志
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
isProcessingFileLimit.value = false
|
|
|
|
|
|
}, 100)
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 更新文件列表
|
|
|
|
|
|
uploadFiles.value = fileList
|
|
|
|
|
|
}
|
2025-09-03 16:27:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleDrop = () => {
|
|
|
|
|
|
isDragOver.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleDragOver = () => {
|
|
|
|
|
|
isDragOver.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleDragLeave = () => {
|
|
|
|
|
|
isDragOver.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const removeFile = (index: number) => {
|
|
|
|
|
|
uploadFiles.value.splice(index, 1)
|
2025-09-18 15:50:12 +08:00
|
|
|
|
// 同步更新 el-upload 组件的内部文件列表
|
|
|
|
|
|
if (uploadRef.value) {
|
|
|
|
|
|
uploadRef.value.clearFiles()
|
|
|
|
|
|
// 重新添加剩余文件到 el-upload 组件
|
|
|
|
|
|
uploadFiles.value.forEach(file => {
|
|
|
|
|
|
uploadRef.value.handleStart(file.raw)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-09-03 16:27:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const formatFileSize = (size?: number) => {
|
|
|
|
|
|
if (!size) return '0 B'
|
|
|
|
|
|
const units = ['B', 'KB', 'MB', 'GB']
|
|
|
|
|
|
let index = 0
|
|
|
|
|
|
while (size >= 1024 && index < units.length - 1) {
|
|
|
|
|
|
size /= 1024
|
|
|
|
|
|
index++
|
|
|
|
|
|
}
|
|
|
|
|
|
return `${size.toFixed(1)} ${units[index]}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const selectMode = (mode: string) => {
|
|
|
|
|
|
uploadForm.analysisStrategyType = mode
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const canProceedToNext = () => {
|
|
|
|
|
|
if (currentStep.value === 0) {
|
|
|
|
|
|
return uploadFiles.value.length > 0
|
|
|
|
|
|
}
|
|
|
|
|
|
if (currentStep.value === 1) {
|
|
|
|
|
|
return uploadForm.analysisStrategyType !== ''
|
|
|
|
|
|
}
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const canUpload = () => {
|
2025-09-22 15:34:40 +08:00
|
|
|
|
// 自定义模式不需要策略名称验证,因为UI中已经隐藏了该字段
|
|
|
|
|
|
// 只要有基本的配置参数就可以上传
|
2025-09-03 16:27:33 +08:00
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const nextStep = () => {
|
|
|
|
|
|
if (canProceedToNext()) {
|
|
|
|
|
|
currentStep.value++
|
|
|
|
|
|
|
|
|
|
|
|
// 如果选择深度解析,模拟进度
|
|
|
|
|
|
if (currentStep.value === 2 && uploadForm.analysisStrategyType === 'deep') {
|
|
|
|
|
|
simulateDeepAnalysisProgress()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const prevStep = () => {
|
|
|
|
|
|
if (currentStep.value > 0) {
|
|
|
|
|
|
currentStep.value--
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const simulateDeepAnalysisProgress = () => {
|
|
|
|
|
|
processingTasks.value.forEach((task, index) => {
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
task.status = '处理中'
|
|
|
|
|
|
const interval = setInterval(() => {
|
|
|
|
|
|
task.progress += Math.random() * 10
|
|
|
|
|
|
if (task.progress >= 100) {
|
|
|
|
|
|
task.progress = 100
|
|
|
|
|
|
task.status = '已完成'
|
|
|
|
|
|
clearInterval(interval)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 200)
|
|
|
|
|
|
}, index * 1000)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleUpload = async () => {
|
|
|
|
|
|
if (!uploadFiles.value.length) {
|
|
|
|
|
|
ElMessage.warning('请选择要上传的文件')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const loading = ElLoading.service({
|
|
|
|
|
|
lock: true,
|
2025-09-18 15:50:12 +08:00
|
|
|
|
text: props.replaceFileId ? '正在替换文件...' : '正在上传文件...',
|
2025-09-03 16:27:33 +08:00
|
|
|
|
background: 'rgba(0, 0, 0, 0.7)'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
uploading.value = true
|
|
|
|
|
|
|
2025-09-18 15:50:12 +08:00
|
|
|
|
// 如果是文件替换,先删除旧文件
|
|
|
|
|
|
if (props.replaceFileId) {
|
|
|
|
|
|
// 关闭当前loading,显示删除进度
|
|
|
|
|
|
loading.close()
|
|
|
|
|
|
const deleteLoading = ElLoading.service({
|
|
|
|
|
|
lock: true,
|
|
|
|
|
|
text: '正在删除旧文件...',
|
|
|
|
|
|
background: 'rgba(0, 0, 0, 0.7)'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
await deleteDocument(parseInt(props.replaceFileId))
|
|
|
|
|
|
deleteLoading.close()
|
|
|
|
|
|
|
2025-09-19 16:35:07 +08:00
|
|
|
|
// 重新显示上传进度X
|
2025-09-18 15:50:12 +08:00
|
|
|
|
const uploadLoading = ElLoading.service({
|
|
|
|
|
|
lock: true,
|
|
|
|
|
|
text: '正在上传新文件...',
|
|
|
|
|
|
background: 'rgba(0, 0, 0, 0.7)'
|
|
|
|
|
|
})
|
|
|
|
|
|
// 更新loading引用
|
|
|
|
|
|
loading.close = uploadLoading.close
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-03 16:27:33 +08:00
|
|
|
|
// 构造请求数据,符合后端DocUploadReq结构
|
|
|
|
|
|
const requestData: any = {
|
|
|
|
|
|
datasetId: props.datasetId,
|
2025-09-11 13:39:32 +08:00
|
|
|
|
analysisStrategyType: uploadForm.analysisStrategyType,
|
2025-09-18 15:50:12 +08:00
|
|
|
|
parentId: props.parentId, // 添加parentId参数,根目录时为undefined/null
|
|
|
|
|
|
// 注意:删除 replaceFileId,因为我们已经手动删除了旧文件
|
2025-09-03 16:27:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是自定义模式,添加解析策略
|
|
|
|
|
|
if (uploadForm.analysisStrategyType === 'custom') {
|
|
|
|
|
|
requestData.docAnalysisStrategy = {
|
2025-09-22 15:34:40 +08:00
|
|
|
|
name: uploadForm.docAnalysisStrategy.name || '自定义策略',
|
2025-09-22 17:25:47 +08:00
|
|
|
|
segmentationMode: uploadForm.docAnalysisStrategy.segmentationMode,
|
2025-09-03 16:27:33 +08:00
|
|
|
|
indexingTechnique: uploadForm.docAnalysisStrategy.indexingTechnique,
|
2025-09-22 17:25:47 +08:00
|
|
|
|
docForm: uploadForm.docAnalysisStrategy.docForm,
|
2025-09-03 16:27:33 +08:00
|
|
|
|
parentSegmentMode: uploadForm.docAnalysisStrategy.parentSegmentMode,
|
|
|
|
|
|
segmentSeparator: uploadForm.docAnalysisStrategy.segmentSeparator,
|
|
|
|
|
|
maxLength: uploadForm.docAnalysisStrategy.maxLength,
|
|
|
|
|
|
overlapLength: uploadForm.docAnalysisStrategy.overlapLength,
|
|
|
|
|
|
childSegmentSeparator: uploadForm.docAnalysisStrategy.childSegmentSeparator || '',
|
|
|
|
|
|
childMaxLength: uploadForm.docAnalysisStrategy.childMaxLength || 200,
|
|
|
|
|
|
childOverlapLength: uploadForm.docAnalysisStrategy.childOverlapLength || 20,
|
|
|
|
|
|
searchMethod: uploadForm.docAnalysisStrategy.searchMethod,
|
|
|
|
|
|
rerankingEnable: uploadForm.docAnalysisStrategy.rerankingEnable,
|
|
|
|
|
|
rerankingProviderName: uploadForm.docAnalysisStrategy.rerankingProviderName || '',
|
|
|
|
|
|
rerankingModelName: uploadForm.docAnalysisStrategy.rerankingModelName || '',
|
|
|
|
|
|
topK: uploadForm.docAnalysisStrategy.topK,
|
|
|
|
|
|
scoreThresholdEnabled: uploadForm.docAnalysisStrategy.scoreThresholdEnabled,
|
|
|
|
|
|
scoreThreshold: uploadForm.docAnalysisStrategy.scoreThreshold,
|
|
|
|
|
|
describe: uploadForm.docAnalysisStrategy.describe || ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历上传文件
|
|
|
|
|
|
for (const file of uploadFiles.value) {
|
|
|
|
|
|
const formData = new FormData()
|
|
|
|
|
|
formData.append('file', file.raw!)
|
|
|
|
|
|
formData.append('request', new Blob([JSON.stringify(requestData)], {
|
|
|
|
|
|
type: 'application/json'
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
await uploadDocument(formData)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ElNotification({
|
2025-09-18 15:50:12 +08:00
|
|
|
|
title: props.replaceFileId ? '替换成功' : '上传成功',
|
|
|
|
|
|
message: props.replaceFileId
|
|
|
|
|
|
? `成功替换 ${uploadFiles.value.length} 个文件`
|
|
|
|
|
|
: `成功上传 ${uploadFiles.value.length} 个文件`,
|
2025-09-03 16:27:33 +08:00
|
|
|
|
type: 'success'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
emit('success')
|
|
|
|
|
|
handleCancel()
|
|
|
|
|
|
} catch (error) {
|
2025-09-18 15:50:12 +08:00
|
|
|
|
console.error(props.replaceFileId ? '替换失败:' : '上传失败:', error)
|
|
|
|
|
|
//ElMessage.error(props.replaceFileId ? '替换失败,请重试' : '上传失败,请重试')
|
2025-09-03 16:27:33 +08:00
|
|
|
|
} finally {
|
|
|
|
|
|
uploading.value = false
|
|
|
|
|
|
loading.close()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleCancel = () => {
|
|
|
|
|
|
// 重置状态
|
|
|
|
|
|
currentStep.value = 0
|
|
|
|
|
|
uploadFiles.value = []
|
|
|
|
|
|
uploadForm.analysisStrategyType = 'adaption'
|
|
|
|
|
|
uploadForm.docAnalysisStrategy.name = ''
|
|
|
|
|
|
|
|
|
|
|
|
emit('update:visible', false)
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
|
.doc-upload-container {
|
|
|
|
|
|
padding: 24px;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
|
|
|
|
|
|
.upload-steps {
|
|
|
|
|
|
margin-bottom: 32px;
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.el-step__title) {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-content {
|
|
|
|
|
|
min-height: 400px;
|
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-panel {
|
|
|
|
|
|
.panel-header {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
|
|
|
|
|
|
h3 {
|
|
|
|
|
|
margin: 0 0 8px 0;
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #303133;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel-description {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 文件选择步骤
|
|
|
|
|
|
.file-selection {
|
|
|
|
|
|
.upload-area {
|
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.el-upload-dragger) {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 200px;
|
|
|
|
|
|
border: 2px dashed #dcdfe6;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
background: #fafafa;
|
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover, &.is-dragover {
|
|
|
|
|
|
border-color: #409eff;
|
|
|
|
|
|
background: #f0f9ff;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.upload-content {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
|
|
|
|
|
|
.upload-icon {
|
|
|
|
|
|
font-size: 48px;
|
|
|
|
|
|
color: #c0c4cc;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
|
|
|
|
|
|
|
&.bounce {
|
|
|
|
|
|
animation: bounce 0.6s infinite alternate;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.upload-text {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
|
|
|
|
|
|
h4 {
|
|
|
|
|
|
margin: 0 0 8px 0;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
color: #303133;
|
|
|
|
|
|
|
|
|
|
|
|
.link-text {
|
|
|
|
|
|
color: #409eff;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
p {
|
|
|
|
|
|
margin: 4px 0;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.selected-files {
|
|
|
|
|
|
h4 {
|
|
|
|
|
|
margin: 0 0 12px 0;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #303133;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.file-list {
|
|
|
|
|
|
border: 1px solid #ebeef5;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
|
|
.file-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
|
border-bottom: 1px solid #ebeef5;
|
|
|
|
|
|
|
|
|
|
|
|
&:last-child {
|
|
|
|
|
|
border-bottom: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.file-icon {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
color: #409eff;
|
|
|
|
|
|
margin-right: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.file-name {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #303133;
|
|
|
|
|
|
margin-right: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.file-size {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
margin-right: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 模式选择步骤
|
|
|
|
|
|
.mode-selection {
|
|
|
|
|
|
.mode-options {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(3, 1fr);
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
|
|
|
|
|
|
.mode-card {
|
|
|
|
|
|
padding: 24px;
|
|
|
|
|
|
border: 2px solid #ebeef5;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
border-color: #409eff;
|
|
|
|
|
|
box-shadow: 0 2px 12px rgba(64, 158, 255, 0.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.selected {
|
|
|
|
|
|
border-color: #409eff;
|
|
|
|
|
|
background: #f0f9ff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mode-icon {
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
color: #409eff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
h4 {
|
|
|
|
|
|
margin: 0 0 8px 0;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #303133;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
p {
|
|
|
|
|
|
margin: 0 0 16px 0;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mode-features {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 配置面板
|
|
|
|
|
|
.config-panel {
|
|
|
|
|
|
.custom-config {
|
|
|
|
|
|
.config-form {
|
|
|
|
|
|
max-width: 600px;
|
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
|
|
|
|
|
|
|
.form-section {
|
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
background: #fafafa;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
|
margin: 0 0 16px 0;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #303133;
|
|
|
|
|
|
border-bottom: 1px solid #ebeef5;
|
|
|
|
|
|
padding-bottom: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.score-threshold-config {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.deep-analysis-progress {
|
|
|
|
|
|
.progress-container {
|
|
|
|
|
|
max-width: 500px;
|
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
|
|
|
|
|
|
|
.progress-item {
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
|
|
|
|
|
|
.task-info {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
|
|
|
|
|
|
.task-name {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #303133;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.task-status {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.adaptive-confirm {
|
|
|
|
|
|
.confirm-info {
|
|
|
|
|
|
max-width: 500px;
|
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
|
|
|
|
|
|
|
.info-card {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
background: #f0f9ff;
|
|
|
|
|
|
border: 1px solid #b3d8ff;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
|
|
|
|
|
|
.info-icon {
|
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
|
color: #409eff;
|
|
|
|
|
|
margin-right: 16px;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info-content {
|
|
|
|
|
|
h4 {
|
|
|
|
|
|
margin: 0 0 12px 0;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
color: #303133;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ul {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding-left: 16px;
|
|
|
|
|
|
|
|
|
|
|
|
li {
|
|
|
|
|
|
margin-bottom: 6px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
line-height: 1.4;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
padding-top: 24px;
|
|
|
|
|
|
border-top: 1px solid #ebeef5;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes bounce {
|
|
|
|
|
|
0% { transform: translateY(0); }
|
|
|
|
|
|
100% { transform: translateY(-10px); }
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|