Skip to content

Commit aeb59de

Browse files
committed
【功能新增】AI:知识库文档上传:40%,SplitStep 初始化
1 parent 7cd6a5d commit aeb59de

File tree

2 files changed

+151
-130
lines changed

2 files changed

+151
-130
lines changed
Lines changed: 142 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,71 @@
11
<template>
22
<div class="document-segment">
3+
<!-- 上部分段设置部分 -->
34
<div class="mb-20px">
4-
<el-alert
5-
title="文档分段说明"
6-
type="info"
7-
description="系统会自动将文档内容分割成多个段落,您可以根据需要调整分段方式和内容。"
8-
show-icon
9-
:closable="false"
10-
/>
11-
</div>
12-
13-
<div class="mb-20px flex justify-between items-center">
14-
<div class="text-16px font-bold">分段设置</div>
15-
<div>
16-
<el-button type="primary" @click="handleAutoSegment">自动分段</el-button>
17-
<el-button @click="handleAddSegment">添加段落</el-button>
5+
<div class="mb-20px flex justify-between items-center">
6+
<div class="text-16px font-bold flex items-center">
7+
分段设置
8+
<el-tooltip
9+
content="系统会自动将文档内容分割成多个段落,您可以根据需要调整分段方式和内容。"
10+
placement="top"
11+
>
12+
<el-icon class="ml-5px text-gray-400"><Warning /></el-icon>
13+
</el-tooltip>
14+
</div>
15+
<div>
16+
<el-button type="primary" plain size="small" @click="handleAutoSegment">
17+
预览分段
18+
</el-button>
19+
</div>
1820
</div>
19-
</div>
2021

21-
<div class="segment-settings mb-20px">
22-
<el-form :model="segmentSettings" label-width="120px">
23-
<el-form-item label="分段方式">
24-
<el-radio-group v-model="segmentSettings.type">
25-
<el-radio :label="1">按段落分割</el-radio>
26-
<el-radio :label="2">按字数分割</el-radio>
27-
<el-radio :label="3">按标题分割</el-radio>
28-
</el-radio-group>
29-
</el-form-item>
30-
<el-form-item label="最大字数" v-if="segmentSettings.type === 2">
31-
<el-input-number v-model="segmentSettings.maxChars" :min="100" :max="5000" />
32-
</el-form-item>
33-
</el-form>
22+
<div class="segment-settings mb-20px">
23+
<el-form :model="segmentSettings" label-width="120px">
24+
<el-form-item label="最大 Token 数">
25+
<el-input-number v-model="modelData.segmentMaxTokens" :min="100" :max="2000" />
26+
</el-form-item>
27+
</el-form>
28+
</div>
3429
</div>
3530

36-
<div class="segment-list">
37-
<div class="text-16px font-bold mb-10px">段落列表 ({{ modelData.segments.length }})</div>
38-
39-
<el-empty v-if="modelData.segments.length === 0" description="暂无段落数据" />
31+
<!-- 下部文件预览部分 -->
32+
<div class="mb-10px">
33+
<div class="text-16px font-bold mb-10px">分段预览</div>
34+
35+
<!-- 文件选择器 -->
36+
<div class="file-selector mb-10px">
37+
<el-dropdown v-if="uploadedFiles.length > 0" trigger="click">
38+
<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>
42+
</div>
43+
<template #dropdown>
44+
<el-dropdown-menu>
45+
<el-dropdown-item
46+
v-for="(file, index) in uploadedFiles"
47+
:key="index"
48+
@click="selectFile(index)"
49+
>
50+
{{ file.name }}
51+
</el-dropdown-item>
52+
</el-dropdown-menu>
53+
</template>
54+
</el-dropdown>
55+
<div v-else class="text-gray-400">暂无上传文件</div>
56+
</div>
4057

41-
<div v-else>
42-
<el-collapse v-model="activeSegments">
43-
<el-collapse-item
44-
v-for="(segment, index) in modelData.segments"
45-
:key="index"
46-
:title="`段落 ${index + 1}`"
47-
:name="index"
48-
>
49-
<div class="segment-content">
50-
<el-input
51-
v-model="segment.content"
52-
type="textarea"
53-
:rows="5"
54-
placeholder="段落内容"
55-
/>
56-
<div class="mt-10px flex justify-end">
57-
<el-button type="danger" size="small" @click="handleDeleteSegment(index)">
58-
删除段落
59-
</el-button>
60-
</div>
61-
</div>
62-
</el-collapse-item>
63-
</el-collapse>
58+
<!-- 文件内容预览 -->
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>
66+
</div>
67+
</template>
68+
<el-empty v-else description="暂无预览内容" />
6469
</div>
6570
</div>
6671

@@ -73,7 +78,8 @@
7378
</template>
7479

7580
<script lang="ts" setup>
76-
import { PropType } from 'vue'
81+
import { PropType, ref, computed, inject, onMounted, getCurrentInstance } from 'vue'
82+
import { Document, ArrowDown, Warning } from '@element-plus/icons-vue' // TODO @芋艿:icon 的处理
7783
7884
const props = defineProps({
7985
modelValue: {
@@ -94,52 +100,84 @@ const modelData = computed({
94100
})
95101
96102
// 分段设置
97-
const segmentSettings = ref({
98-
type: 1, // 1: 按段落, 2: 按字数, 3: 按标题
99-
maxChars: 1000
100-
})
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+
])
144+
145+
// 当前选中的文件
146+
const currentFile = ref(uploadedFiles.value[0] || null)
101147
102-
// 当前展开的段落
103-
const activeSegments = ref([0])
148+
// 选择文件
149+
const selectFile = (index) => {
150+
currentFile.value = uploadedFiles.value[index]
151+
}
104152
105153
// 自动分段
106154
const handleAutoSegment = () => {
107155
// 根据文档类型和分段设置进行自动分段
108156
// 这里只是模拟实现,实际需要根据文档内容进行分析
109157
158+
// 确保 segments 存在
159+
if (!modelData.value.segments) {
160+
modelData.value.segments = []
161+
}
162+
110163
// 清空现有段落
111164
modelData.value.segments = []
112165
113166
// 模拟生成段落
114167
if (modelData.value.documentType === 'text' && modelData.value.content) {
115-
// 文本类型,直接按段落或字数分割
168+
// 文本类型,按Token数分割
116169
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)
117176
118-
if (segmentSettings.value.type === 1) {
119-
// 按段落分割
120-
const paragraphs = content.split(/\n\s*\n/)
121-
paragraphs.forEach((paragraph) => {
122-
if (paragraph.trim()) {
123-
modelData.value.segments.push({
124-
content: paragraph.trim(),
125-
order: modelData.value.segments.length + 1
126-
})
127-
}
177+
modelData.value.segments.push({
178+
content: segment,
179+
order: modelData.value.segments.length + 1
128180
})
129-
} else if (segmentSettings.value.type === 2) {
130-
// 按字数分割
131-
const maxChars = segmentSettings.value.maxChars
132-
let remaining = content
133-
134-
while (remaining.length > 0) {
135-
const segment = remaining.substring(0, maxChars)
136-
remaining = remaining.substring(maxChars)
137-
138-
modelData.value.segments.push({
139-
content: segment,
140-
order: modelData.value.segments.length + 1
141-
})
142-
}
143181
}
144182
} else {
145183
// 其他类型文档,模拟生成5个段落
@@ -150,30 +188,6 @@ const handleAutoSegment = () => {
150188
})
151189
}
152190
}
153-
154-
// 默认展开第一个段落
155-
activeSegments.value = [0]
156-
}
157-
158-
// 添加段落
159-
const handleAddSegment = () => {
160-
modelData.value.segments.push({
161-
content: '',
162-
order: modelData.value.segments.length + 1
163-
})
164-
165-
// 展开新添加的段落
166-
activeSegments.value = [modelData.value.segments.length - 1]
167-
}
168-
169-
// 删除段落
170-
const handleDeleteSegment = (index) => {
171-
modelData.value.segments.splice(index, 1)
172-
173-
// 更新段落顺序
174-
modelData.value.segments.forEach((segment, idx) => {
175-
segment.order = idx + 1
176-
})
177191
}
178192
179193
// 上一步按钮处理
@@ -197,16 +211,11 @@ const handleNextStep = () => {
197211
// 表单校验
198212
const validate = () => {
199213
return new Promise((resolve, reject) => {
200-
if (modelData.value.segments.length === 0) {
201-
reject(new Error('请至少添加一个段落'))
214+
// 确保 segments 存在
215+
if (!modelData.value.segments || modelData.value.segments.length === 0) {
216+
reject(new Error('请先进行预览分段'))
202217
} else {
203-
// 检查是否有空段落
204-
const emptySegment = modelData.value.segments.find((segment) => !segment.content.trim())
205-
if (emptySegment) {
206-
reject(new Error('存在空段落,请填写内容或删除'))
207-
} else {
208-
resolve(true)
209-
}
218+
resolve(true)
210219
}
211220
})
212221
}
@@ -218,9 +227,14 @@ defineExpose({
218227
219228
// 初始化
220229
onMounted(() => {
221-
// 如果已有段落数据,默认展开第一个
222-
if (modelData.value.segments && modelData.value.segments.length > 0) {
223-
activeSegments.value = [0]
230+
// 确保 segments 存在
231+
if (!modelData.value.segments) {
232+
modelData.value.segments = []
233+
}
234+
235+
// 确保 segmentMaxTokens 存在
236+
if (!modelData.value.segmentMaxTokens) {
237+
modelData.value.segmentMaxTokens = 500
224238
}
225239
})
226240
</script>
@@ -230,5 +244,10 @@ onMounted(() => {
230244
.segment-content {
231245
padding: 10px;
232246
}
247+
248+
.file-preview {
249+
max-height: 600px;
250+
overflow-y: auto;
251+
}
233252
}
234253
</style>

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
<el-form ref="formRef" :model="modelData" label-width="0" class="mt-20px">
33
<el-form-item class="mb-20px">
44
<div class="w-full">
5-
<div class="w-full border-2 border-[#dcdfe6] rounded-md text-center hover:border-[#409eff]">
5+
<div
6+
class="w-full border-2 border-dashed border-[#dcdfe6] rounded-md p-20px text-center hover:border-[#409eff]"
7+
>
68
<el-upload
79
ref="uploadRef"
810
class="upload-demo"
@@ -35,18 +37,18 @@
3537

3638
<div
3739
v-if="modelData.list && modelData.list.length > 0"
38-
class="mt-15px grid grid-cols-1 gap-3"
40+
class="mt-15px grid grid-cols-1 gap-2"
3941
>
4042
<div
4143
v-for="(file, index) in modelData.list"
4244
:key="index"
43-
class="flex justify-between items-center p-10px border-2 border-[#c0c4cc] rounded-md shadow-sm hover:border-[#409eff] transition-colors duration-300"
45+
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"
4446
>
4547
<div class="flex items-center">
46-
<el-icon class="mr-8px text-[#909399]"><document /></el-icon>
47-
<span class="text-[14px] text-[#606266] break-all">{{ file.name }}</span>
48+
<el-icon class="mr-8px text-[#409eff]"><document /></el-icon>
49+
<span class="text-[13px] text-[#303133] break-all">{{ file.name }}</span>
4850
</div>
49-
<el-button type="danger" link @click="removeFile(index)">
51+
<el-button type="danger" link @click="removeFile(index)" class="ml-2">
5052
<el-icon><delete /></el-icon>
5153
</el-button>
5254
</div>
@@ -67,7 +69,7 @@
6769

6870
<script lang="ts" setup>
6971
import { PropType, ref, computed, inject, getCurrentInstance, onMounted } from 'vue'
70-
import { UploadFilled, Document, Delete } from '@element-plus/icons-vue'
72+
import { Document, Delete } from '@element-plus/icons-vue' // TODO @芋艿:晚点改
7173
import { useMessage } from '@/hooks/web/useMessage'
7274
import { useUpload } from '@/components/UploadFile/src/useUpload'
7375
import { generateAcceptedFileTypes } from '@/utils'

0 commit comments

Comments
 (0)