1
1
<template >
2
2
<div class =" document-segment" >
3
+ <!-- 上部分段设置部分 -->
3
4
<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 >
18
20
</div >
19
- </div >
20
21
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 >
34
29
</div >
35
30
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 >
40
57
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 =" 暂无预览内容" />
64
69
</div >
65
70
</div >
66
71
73
78
</template >
74
79
75
80
<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 的处理
77
83
78
84
const props = defineProps ({
79
85
modelValue: {
@@ -94,52 +100,84 @@ const modelData = computed({
94
100
})
95
101
96
102
// 分段设置
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 )
101
147
102
- // 当前展开的段落
103
- const activeSegments = ref ([0 ])
148
+ // 选择文件
149
+ const selectFile = (index ) => {
150
+ currentFile .value = uploadedFiles .value [index ]
151
+ }
104
152
105
153
// 自动分段
106
154
const handleAutoSegment = () => {
107
155
// 根据文档类型和分段设置进行自动分段
108
156
// 这里只是模拟实现,实际需要根据文档内容进行分析
109
157
158
+ // 确保 segments 存在
159
+ if (! modelData .value .segments ) {
160
+ modelData .value .segments = []
161
+ }
162
+
110
163
// 清空现有段落
111
164
modelData .value .segments = []
112
165
113
166
// 模拟生成段落
114
167
if (modelData .value .documentType === ' text' && modelData .value .content ) {
115
- // 文本类型,直接按段落或字数分割
168
+ // 文本类型,按Token数分割
116
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 )
117
176
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
128
180
})
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
- }
143
181
}
144
182
} else {
145
183
// 其他类型文档,模拟生成5个段落
@@ -150,30 +188,6 @@ const handleAutoSegment = () => {
150
188
})
151
189
}
152
190
}
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
- })
177
191
}
178
192
179
193
// 上一步按钮处理
@@ -197,16 +211,11 @@ const handleNextStep = () => {
197
211
// 表单校验
198
212
const validate = () => {
199
213
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 (' 请先进行预览分段' ))
202
217
} 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 )
210
219
}
211
220
})
212
221
}
@@ -218,9 +227,14 @@ defineExpose({
218
227
219
228
// 初始化
220
229
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
224
238
}
225
239
})
226
240
</script >
@@ -230,5 +244,10 @@ onMounted(() => {
230
244
.segment-content {
231
245
padding : 10px ;
232
246
}
247
+
248
+ .file-preview {
249
+ max-height : 600px ;
250
+ overflow-y : auto ;
251
+ }
233
252
}
234
253
</style >
0 commit comments