1
1
<template >
2
- <div class = " document-segment " >
2
+ <div >
3
3
<!-- 上部分段设置部分 -->
4
4
<div class =" mb-20px" >
5
5
<div class =" mb-20px flex justify-between items-center" >
9
9
content =" 系统会自动将文档内容分割成多个段落,您可以根据需要调整分段方式和内容。"
10
10
placement =" top"
11
11
>
12
- <el- icon class =" ml-5px text-gray-400" >< Warning /></ el-icon >
12
+ <Icon icon = " ep:warning " class =" ml-5px text-gray-400" / >
13
13
</el-tooltip >
14
14
</div >
15
15
<div >
20
20
</div >
21
21
22
22
<div class =" segment-settings mb-20px" >
23
- <el-form :model = " segmentSettings " label-width =" 120px" >
23
+ <el-form label-width =" 120px" >
24
24
<el-form-item label =" 最大 Token 数" >
25
25
<el-input-number v-model =" modelData.segmentMaxTokens" :min =" 100" :max =" 2000" />
26
26
</el-form-item >
34
34
35
35
<!-- 文件选择器 -->
36
36
<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" >
38
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 >
39
+ <Icon icon = " ep:document " class =" text-danger mr-5px" / >
40
+ <span >{{ currentFile? .name || '请选择文件' }}</span >
41
+ <Icon icon = " ep:arrow-down " class =" ml-5px" / >
42
42
</div >
43
43
<template #dropdown >
44
44
<el-dropdown-menu >
45
45
<el-dropdown-item
46
- v-for =" (file, index) in uploadedFiles "
46
+ v-for =" (file, index) in modelData.list "
47
47
:key =" index"
48
48
@click =" selectFile(index)"
49
49
>
56
56
</div >
57
57
58
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 >
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 >
66
73
</div >
67
74
</template >
68
75
<el-empty v-else description =" 暂无预览内容" />
79
86
80
87
<script lang="ts" setup>
81
88
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'
83
92
84
93
const props = defineProps ({
85
94
modelValue: {
@@ -89,165 +98,99 @@ const props = defineProps({
89
98
})
90
99
91
100
const emit = defineEmits ([' update:modelValue' ])
101
+ const message = useMessage () // 消息提示
102
+ const parent = inject (' parent' , null ) // 获取父组件实例
92
103
93
- // 获取父组件实例
94
- const parent = inject (' parent' , null )
95
-
96
- // 表单数据
97
104
const modelData = computed ({
98
105
get : () => props .modelValue ,
99
106
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
+ }) // 表单数据
144
108
145
- // 当前选中的文件
146
- const currentFile = ref ( uploadedFiles . value [ 0 ] || null )
109
+ const splitLoading = ref ( false ) // 分段加载状态
110
+ const currentFile = ref < any >( null ) // 当前选中的文件
147
111
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 )
151
116
}
152
117
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
+ }
157
124
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
161
134
}
135
+ }
162
136
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
190
147
}
148
+
149
+ // 获取分段内容
150
+ await splitContent (currentFile .value )
191
151
}
192
152
193
- // 上一步按钮处理
153
+ /** 上一步按钮处理 */
194
154
const handlePrevStep = () => {
195
- // 获取父组件的goToPrevStep方法
196
155
const parentEl = parent || getCurrentInstance ()?.parent
197
156
if (parentEl && typeof parentEl .exposed ?.goToPrevStep === ' function' ) {
198
157
parentEl .exposed .goToPrevStep ()
199
158
}
200
159
}
201
160
202
- // 下一步按钮处理
161
+ /** 下一步按钮处理 */
203
162
const handleNextStep = () => {
204
- // 获取父组件的goToNextStep方法
205
163
const parentEl = parent || getCurrentInstance ()?.parent
206
164
if (parentEl && typeof parentEl .exposed ?.goToNextStep === ' function' ) {
207
165
parentEl .exposed .goToNextStep ()
208
166
}
209
167
}
210
168
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
+ }
227
174
228
- // 初始化
229
- onMounted (() => {
230
- // 确保 segments 存在
231
- if (! modelData .value .segments ) {
232
- modelData .value .segments = []
175
+ if (currentFile .value ) {
176
+ await splitContent (currentFile .value ) // 如果有选中的文件,获取分段内容
233
177
}
178
+ }
234
179
180
+ /** 初始化 */
181
+ onMounted (async () => {
235
182
// 确保 segmentMaxTokens 存在
236
183
if (! modelData .value .segmentMaxTokens ) {
237
184
modelData .value .segmentMaxTokens = 500
238
185
}
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 ]
246
189
}
247
190
248
- .file-preview {
249
- max-height : 600 px ;
250
- overflow-y : auto ;
191
+ // 如果有选中的文件,获取分段内容
192
+ if ( currentFile . value ) {
193
+ await splitContent ( currentFile . value )
251
194
}
252
- }
253
- </style >
195
+ })
196
+ </script >
0 commit comments