Skip to content

Commit da91896

Browse files
committed
【功能新增】AI:知识库文档上传:50%,SplitStep 基本完成
1 parent aeb59de commit da91896

File tree

3 files changed

+117
-151
lines changed

3 files changed

+117
-151
lines changed
Lines changed: 83 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div class="document-segment">
2+
<div>
33
<!-- 上部分段设置部分 -->
44
<div class="mb-20px">
55
<div class="mb-20px flex justify-between items-center">
@@ -9,7 +9,7 @@
99
content="系统会自动将文档内容分割成多个段落,您可以根据需要调整分段方式和内容。"
1010
placement="top"
1111
>
12-
<el-icon class="ml-5px text-gray-400"><Warning /></el-icon>
12+
<Icon icon="ep:warning" class="ml-5px text-gray-400" />
1313
</el-tooltip>
1414
</div>
1515
<div>
@@ -20,7 +20,7 @@
2020
</div>
2121

2222
<div class="segment-settings mb-20px">
23-
<el-form :model="segmentSettings" label-width="120px">
23+
<el-form label-width="120px">
2424
<el-form-item label="最大 Token 数">
2525
<el-input-number v-model="modelData.segmentMaxTokens" :min="100" :max="2000" />
2626
</el-form-item>
@@ -34,16 +34,16 @@
3434

3535
<!-- 文件选择器 -->
3636
<div class="file-selector mb-10px">
37-
<el-dropdown v-if="uploadedFiles.length > 0" trigger="click">
37+
<el-dropdown v-if="modelData.list && modelData.list.length > 0" trigger="click">
3838
<div class="flex items-center cursor-pointer">
39-
<el-icon class="text-danger mr-5px"><Document /></el-icon>
40-
<span>{{ currentFile.name }}</span>
41-
<el-icon class="ml-5px"><ArrowDown /></el-icon>
39+
<Icon icon="ep:document" class="text-danger mr-5px" />
40+
<span>{{ currentFile?.name || '请选择文件' }}</span>
41+
<Icon icon="ep:arrow-down" class="ml-5px" />
4242
</div>
4343
<template #dropdown>
4444
<el-dropdown-menu>
4545
<el-dropdown-item
46-
v-for="(file, index) in uploadedFiles"
46+
v-for="(file, index) in modelData.list"
4747
:key="index"
4848
@click="selectFile(index)"
4949
>
@@ -56,13 +56,20 @@
5656
</div>
5757

5858
<!-- 文件内容预览 -->
59-
<div class="file-preview bg-gray-50 p-15px rounded-md">
60-
<template v-if="currentFile">
61-
<div v-for="(chunk, index) in currentFile.chunks" :key="index" class="mb-10px">
62-
<div class="text-gray-500 text-12px mb-5px"
63-
>Chunk-{{ index + 1 }} · {{ chunk.characters }} characters</div
64-
>
65-
<div class="bg-white p-10px rounded-md">{{ chunk.content }}</div>
59+
<div class="file-preview bg-gray-50 p-15px rounded-md max-h-600px overflow-y-auto">
60+
<div v-if="splitLoading" class="flex justify-center items-center py-20px">
61+
<Icon icon="ep:loading" class="is-loading" />
62+
<span class="ml-10px">正在加载分段内容...</span>
63+
</div>
64+
<template
65+
v-else-if="currentFile && currentFile.segments && currentFile.segments.length > 0"
66+
>
67+
<div v-for="(segment, index) in currentFile.segments" :key="index" class="mb-10px">
68+
<div class="text-gray-500 text-12px mb-5px">
69+
分片-{{ index + 1 }} · {{ segment.contentLength || 0 }} 字符数 ·
70+
{{ segment.tokens || 0 }} Token
71+
</div>
72+
<div class="bg-white p-10px rounded-md">{{ segment.content }}</div>
6673
</div>
6774
</template>
6875
<el-empty v-else description="暂无预览内容" />
@@ -79,7 +86,9 @@
7986

8087
<script lang="ts" setup>
8188
import { PropType, ref, computed, inject, onMounted, getCurrentInstance } from 'vue'
82-
import { Document, ArrowDown, Warning } from '@element-plus/icons-vue' // TODO @芋艿:icon 的处理
89+
import { Icon } from '@/components/Icon'
90+
import { KnowledgeSegmentApi } from '@/api/ai/knowledge/segment'
91+
import { useMessage } from '@/hooks/web/useMessage'
8392
8493
const props = defineProps({
8594
modelValue: {
@@ -89,165 +98,99 @@ const props = defineProps({
8998
})
9099
91100
const emit = defineEmits(['update:modelValue'])
101+
const message = useMessage() // 消息提示
102+
const parent = inject('parent', null) // 获取父组件实例
92103
93-
// 获取父组件实例
94-
const parent = inject('parent', null)
95-
96-
// 表单数据
97104
const modelData = computed({
98105
get: () => props.modelValue,
99106
set: (val) => emit('update:modelValue', val)
100-
})
101-
102-
// 分段设置
103-
const segmentSettings = ref({})
104-
105-
// 模拟上传的文件数据
106-
const uploadedFiles = ref([
107-
{
108-
name: '项目说明文档.pdf',
109-
type: 'pdf',
110-
chunks: [
111-
{
112-
characters: 120,
113-
content:
114-
'项目说明文档 - 智能知识库系统 本项目旨在构建一个智能知识库系统,能够对各类文档进行智能分析、分类和检索,提高企业知识管理效率。'
115-
},
116-
{
117-
characters: 180,
118-
content:
119-
'系统架构:前端采用Vue3+Element Plus构建用户界面,后端采用Spring Boot微服务架构,数据存储使用MySQL和Elasticsearch,文档解析使用Apache Tika,向量检索使用Milvus。'
120-
},
121-
{
122-
characters: 150,
123-
content:
124-
'核心功能:1. 文档上传与解析:支持多种格式文档上传,自动提取文本内容。2. 智能分段:根据语义自动将文档分割成合适的段落。3. 向量化存储:将文本转换为向量存储,支持语义检索。'
125-
},
126-
{
127-
characters: 160,
128-
content:
129-
'4. 智能检索:支持关键词和语义检索,快速找到相关内容。5. 知识图谱:自动构建领域知识图谱,展示知识间关联。6. 权限管理:细粒度的文档访问权限控制。7. 操作日志:记录用户操作,支持审计追踪。'
130-
},
131-
{
132-
characters: 130,
133-
content:
134-
'技术特点:1. 高性能:采用分布式架构,支持横向扩展。2. 高可用:关键组件冗余部署,确保系统稳定性。3. 安全性:数据传输加密,存储加密,多层次安全防护。'
135-
}
136-
]
137-
},
138-
{
139-
name: '项目说明文档.pdf',
140-
type: 'pdf',
141-
chunks: []
142-
}
143-
])
107+
}) // 表单数据
144108
145-
// 当前选中的文件
146-
const currentFile = ref(uploadedFiles.value[0] || null)
109+
const splitLoading = ref(false) // 分段加载状态
110+
const currentFile = ref<any>(null) // 当前选中的文件
147111
148-
// 选择文件
149-
const selectFile = (index) => {
150-
currentFile.value = uploadedFiles.value[index]
112+
/** 选择文件 */
113+
const selectFile = async (index: number) => {
114+
currentFile.value = modelData.value.list[index]
115+
await splitContent(currentFile.value)
151116
}
152117
153-
// 自动分段
154-
const handleAutoSegment = () => {
155-
// 根据文档类型和分段设置进行自动分段
156-
// 这里只是模拟实现,实际需要根据文档内容进行分析
118+
/** 获取文件分段内容 */
119+
const splitContent = async (file: any) => {
120+
if (!file || !file.url) {
121+
message.warning('文件 URL 不存在')
122+
return
123+
}
157124
158-
// 确保 segments 存在
159-
if (!modelData.value.segments) {
160-
modelData.value.segments = []
125+
splitLoading.value = true
126+
try {
127+
const data = await KnowledgeSegmentApi.splitContent(file.url, modelData.value.segmentMaxTokens) // 调用后端分段接口,获取文档的分段内容、字符数和 Token 数
128+
file.segments = data
129+
} catch (error) {
130+
console.error('获取分段内容失败:', file, error)
131+
message.error('获取分段内容失败')
132+
} finally {
133+
splitLoading.value = false
161134
}
135+
}
162136
163-
// 清空现有段落
164-
modelData.value.segments = []
165-
166-
// 模拟生成段落
167-
if (modelData.value.documentType === 'text' && modelData.value.content) {
168-
// 文本类型,按Token数分割
169-
const content = modelData.value.content
170-
const maxChars = Math.floor(modelData.value.segmentMaxTokens / 2) // 简单估算:1个token约等于2个字符
171-
let remaining = content
172-
173-
while (remaining.length > 0) {
174-
const segment = remaining.substring(0, maxChars)
175-
remaining = remaining.substring(maxChars)
176-
177-
modelData.value.segments.push({
178-
content: segment,
179-
order: modelData.value.segments.length + 1
180-
})
181-
}
182-
} else {
183-
// 其他类型文档,模拟生成5个段落
184-
for (let i = 0; i < 5; i++) {
185-
modelData.value.segments.push({
186-
content: `这是第 ${i + 1} 个自动生成的段落,实际内容将根据文档解析结果填充。`,
187-
order: i + 1
188-
})
189-
}
137+
/** 处理预览分段 */
138+
const handleAutoSegment = async () => {
139+
// 如果没有选中文件,默认选中第一个
140+
if (!currentFile.value && modelData.value.list && modelData.value.list.length > 0) {
141+
currentFile.value = modelData.value.list[0]
142+
}
143+
// 如果没有选中文件,提示请先选择文件
144+
if (!currentFile.value) {
145+
message.warning('请先选择文件')
146+
return
190147
}
148+
149+
// 获取分段内容
150+
await splitContent(currentFile.value)
191151
}
192152
193-
// 上一步按钮处理
153+
/** 上一步按钮处理 */
194154
const handlePrevStep = () => {
195-
// 获取父组件的goToPrevStep方法
196155
const parentEl = parent || getCurrentInstance()?.parent
197156
if (parentEl && typeof parentEl.exposed?.goToPrevStep === 'function') {
198157
parentEl.exposed.goToPrevStep()
199158
}
200159
}
201160
202-
// 下一步按钮处理
161+
/** 下一步按钮处理 */
203162
const handleNextStep = () => {
204-
// 获取父组件的goToNextStep方法
205163
const parentEl = parent || getCurrentInstance()?.parent
206164
if (parentEl && typeof parentEl.exposed?.goToNextStep === 'function') {
207165
parentEl.exposed.goToNextStep()
208166
}
209167
}
210168
211-
// 表单校验
212-
const validate = () => {
213-
return new Promise((resolve, reject) => {
214-
// 确保 segments 存在
215-
if (!modelData.value.segments || modelData.value.segments.length === 0) {
216-
reject(new Error('请先进行预览分段'))
217-
} else {
218-
resolve(true)
219-
}
220-
})
221-
}
222-
223-
// 对外暴露方法
224-
defineExpose({
225-
validate
226-
})
169+
/** 组件激活时自动调用分段接口 TODO 芋艿:需要看下 */
170+
const activated = async () => {
171+
if (!currentFile.value && modelData.value.list && modelData.value.list.length > 0) {
172+
currentFile.value = modelData.value.list[0] // 如果没有选中文件,默认选中第一个
173+
}
227174
228-
// 初始化
229-
onMounted(() => {
230-
// 确保 segments 存在
231-
if (!modelData.value.segments) {
232-
modelData.value.segments = []
175+
if (currentFile.value) {
176+
await splitContent(currentFile.value) // 如果有选中的文件,获取分段内容
233177
}
178+
}
234179
180+
/** 初始化 */
181+
onMounted(async () => {
235182
// 确保 segmentMaxTokens 存在
236183
if (!modelData.value.segmentMaxTokens) {
237184
modelData.value.segmentMaxTokens = 500
238185
}
239-
})
240-
</script>
241-
242-
<style lang="scss" scoped>
243-
.document-segment {
244-
.segment-content {
245-
padding: 10px;
186+
// 如果没有选中文件,默认选中第一个
187+
if (!currentFile.value && modelData.value.list && modelData.value.list.length > 0) {
188+
currentFile.value = modelData.value.list[0]
246189
}
247190
248-
.file-preview {
249-
max-height: 600px;
250-
overflow-y: auto;
191+
// 如果有选中的文件,获取分段内容
192+
if (currentFile.value) {
193+
await splitContent(currentFile.value)
251194
}
252-
}
253-
</style>
195+
})
196+
</script>

src/views/ai/knowledge/document/create/UploadStep.vue

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@
2323
:accept="acceptedFileTypes"
2424
>
2525
<div class="flex flex-col items-center justify-center py-20px">
26-
<el-icon class="text-[48px] text-[#c0c4cc] mb-10px"><upload-filled /></el-icon>
27-
<div class="el-upload__text text-[16px] text-[#606266]"
28-
>拖拽文件至此,或者
29-
<em class="text-[#409eff] not-italic cursor-pointer">选择文件</em></div
30-
>
26+
<Icon icon="ep:upload-filled" class="text-[48px] text-[#c0c4cc] mb-10px" />
27+
<div class="el-upload__text text-[16px] text-[#606266]">
28+
拖拽文件至此,或者
29+
<em class="text-[#409eff] not-italic cursor-pointer">选择文件</em>
30+
</div>
3131
<div class="el-upload__tip mt-10px text-[#909399] text-[12px]">
3232
已支持 {{ supportedFileTypes.join('、') }},每个文件不超过 {{ maxFileSize }} MB。
3333
</div>
@@ -45,11 +45,11 @@
4545
class="flex justify-between items-center py-4px px-12px border-l-4 border-l-[#409eff] rounded-sm shadow-sm hover:bg-[#ecf5ff] transition-all duration-300"
4646
>
4747
<div class="flex items-center">
48-
<el-icon class="mr-8px text-[#409eff]"><document /></el-icon>
48+
<Icon icon="ep:document" class="mr-8px text-[#409eff]" />
4949
<span class="text-[13px] text-[#303133] break-all">{{ file.name }}</span>
5050
</div>
5151
<el-button type="danger" link @click="removeFile(index)" class="ml-2">
52-
<el-icon><delete /></el-icon>
52+
<Icon icon="ep:delete" />
5353
</el-button>
5454
</div>
5555
</div>
@@ -69,10 +69,10 @@
6969

7070
<script lang="ts" setup>
7171
import { PropType, ref, computed, inject, getCurrentInstance, onMounted } from 'vue'
72-
import { Document, Delete } from '@element-plus/icons-vue' // TODO @芋艿:晚点改
7372
import { useMessage } from '@/hooks/web/useMessage'
7473
import { useUpload } from '@/components/UploadFile/src/useUpload'
7574
import { generateAcceptedFileTypes } from '@/utils'
75+
import { Icon } from '@/components/Icon'
7676
7777
const props = defineProps({
7878
modelValue: {
@@ -173,8 +173,8 @@ const beforeUpload = (file) => {
173173
* @param file 上传的文件
174174
*/
175175
const handleUploadSuccess = (response, file) => {
176+
// 添加到文件列表
176177
if (response && response.data) {
177-
// 添加到文件列表
178178
ensureListExists()
179179
emit('update:modelValue', {
180180
...props.modelValue,

0 commit comments

Comments
 (0)