1
1
<!-- AI 对话 -->
2
2
<template >
3
- <el-aside width =" 260px" class =" conversation-container" style = " height : 100% " >
3
+ <el-aside width =" 260px" class =" conversation-container h- 100%" >
4
4
<!-- 左顶部:对话 -->
5
- <div style = " height : 100% " >
5
+ <div class = " h- 100%" >
6
6
<el-button class =" w-1/1 btn-new-conversation" type =" primary" @click =" createConversation" >
7
7
<Icon icon =" ep:plus" class =" mr-5px" />
8
8
新建对话
23
23
24
24
<!-- 左中间:对话列表 -->
25
25
<div class =" conversation-list" >
26
+ <!-- 情况一:加载中 -->
26
27
<el-empty v-if =" loading" description =" ." :v-loading =" loading" />
27
-
28
+ <!-- 情况二:按照 group 分组,展示聊天会话 list 列表 -->
28
29
<div v-for =" conversationKey in Object.keys(conversationMap)" :key =" conversationKey" >
29
30
<div
30
31
class =" conversation-item classify-title"
50
51
<span class =" title" >{{ conversation.title }}</span >
51
52
</div >
52
53
<div class =" button-wrapper" v-show =" hoverConversationId === conversation.id" >
53
- <el-button class =" btn" link @click.stop =" handlerTop (conversation)" >
54
+ <el-button class =" btn" link @click.stop =" handleTop (conversation)" >
54
55
<el-icon title =" 置顶" v-if =" !conversation.pinned" ><Top /></el-icon >
55
56
<el-icon title =" 置顶" v-if =" conversation.pinned" ><Bottom /></el-icon >
56
57
</el-button >
68
69
</div >
69
70
</div >
70
71
</div >
71
- <!-- 底部站位 -->
72
- <div style = " height : 160px ; width : 100% " ></div >
72
+ <!-- 底部占位 -->
73
+ <div class = " h- 160px w- 100%" ></div >
73
74
</div >
74
75
</div >
75
76
76
77
<!-- 左底部:工具栏 -->
77
- <!-- TODO @fan:下面两个 icon,可以使用类似 <Icon icon="ep:question-filled" /> 替代哈 -->
78
78
<div class =" tool-box" >
79
79
<div @click =" handleRoleRepository" >
80
80
<Icon icon =" ep:user" />
86
86
</div >
87
87
</div >
88
88
89
- <!-- ============= 额外组件 ============= -->
90
-
91
89
<!-- 角色仓库抽屉 -->
92
- <el-drawer v-model =" drawer " title =" 角色仓库" size =" 754px" >
93
- <Role />
90
+ <el-drawer v-model =" roleRepositoryOpen " title =" 角色仓库" size =" 754px" >
91
+ <RoleRepository />
94
92
</el-drawer >
95
93
</el-aside >
96
94
</template >
97
95
98
96
<script setup lang="ts">
99
97
import { ChatConversationApi , ChatConversationVO } from ' @/api/ai/chat/conversation'
100
- import { ref } from ' vue'
101
- import Role from ' ../role/index.vue'
98
+ import RoleRepository from ' ../role/RoleRepository.vue'
102
99
import { Bottom , Top } from ' @element-plus/icons-vue'
103
100
import roleAvatarDefaultImg from ' @/assets/ai/gpt.svg'
104
101
105
102
const message = useMessage () // 消息弹窗
106
103
107
104
// 定义属性
108
105
const searchName = ref <string >(' ' ) // 对话搜索
109
- const activeConversationId = ref <string | null >(null ) // 选中的对话,默认为 null
110
- const hoverConversationId = ref <string | null >(null ) // 悬浮上去的对话
106
+ const activeConversationId = ref <number | null >(null ) // 选中的对话,默认为 null
107
+ const hoverConversationId = ref <number | null >(null ) // 悬浮上去的对话
111
108
const conversationList = ref ([] as ChatConversationVO []) // 对话列表
112
109
const conversationMap = ref <any >({}) // 对话分组 (置顶、今天、三天前、一星期前、一个月前)
113
- const drawer = ref <boolean >(false ) // 角色仓库抽屉 TODO @fan:roleDrawer 会不会好点哈
114
110
const loading = ref <boolean >(false ) // 加载中
115
111
const loadingTime = ref <any >() // 加载中定时器
116
112
@@ -130,75 +126,58 @@ const emits = defineEmits([
130
126
' onConversationDelete'
131
127
])
132
128
133
- /**
134
- * 对话 - 搜索
135
- */
129
+ /** 搜索对话 */
136
130
const searchConversation = async (e ) => {
137
131
// 恢复数据
138
132
if (! searchName .value .trim ().length ) {
139
- conversationMap .value = await conversationTimeGroup (conversationList .value )
133
+ conversationMap .value = await getConversationGroupByCreateTime (conversationList .value )
140
134
} else {
141
135
// 过滤
142
136
const filterValues = conversationList .value .filter ((item ) => {
143
137
return item .title .includes (searchName .value .trim ())
144
138
})
145
- conversationMap .value = await conversationTimeGroup (filterValues )
139
+ conversationMap .value = await getConversationGroupByCreateTime (filterValues )
146
140
}
147
141
}
148
142
149
- /**
150
- * 对话 - 点击
151
- */
152
- const handleConversationClick = async (id : string ) => {
143
+ /** 点击对话 */
144
+ const handleConversationClick = async (id : number ) => {
153
145
// 过滤出选中的对话
154
146
const filterConversation = conversationList .value .filter ((item ) => {
155
147
return item .id === id
156
148
})
157
149
// 回调 onConversationClick
158
- // TODO @fan: 这里 idea 会报黄色警告,有办法解下么?
159
- const res = emits (' onConversationClick' , filterConversation [0 ])
150
+ // noinspection JSVoidFunctionReturnValueUsed
151
+ const success = emits (' onConversationClick' , filterConversation [0 ])
160
152
// 切换对话
161
- if (res ) {
153
+ if (success ) {
162
154
activeConversationId .value = id
163
155
}
164
156
}
165
157
166
- /**
167
- * 对话 - 获取列表
168
- */
158
+ /** 获取对话列表 */
169
159
const getChatConversationList = async () => {
170
160
try {
171
- // 0. 加载中
161
+ // 加载中
172
162
loadingTime .value = setTimeout (() => {
173
163
loading .value = true
174
164
}, 50 )
175
- // 1. 获取 对话数据
176
- const res = await ChatConversationApi .getChatConversationMyList ()
177
- // 2. 排序
178
- res .sort ((a , b ) => {
165
+
166
+ // 1.1 获取 对话数据
167
+ conversationList .value = await ChatConversationApi .getChatConversationMyList ()
168
+ // 1.2 排序
169
+ conversationList .value .sort ((a , b ) => {
179
170
return b .createTime - a .createTime
180
171
})
181
- conversationList .value = res
182
- // 3. 默认选中
183
- if (! activeId ?.value ) {
184
- // await handleConversationClick(res[0].id)
185
- } else {
186
- // tip: 删除的刚好是选中的,那么需要重新挑选一个来进行选中
187
- // const filterConversationList = conversationList.value.filter(item => {
188
- // return item.id === activeId.value
189
- // })
190
- // if (filterConversationList.length <= 0) {
191
- // await handleConversationClick(res[0].id)
192
- // }
193
- }
194
- // 4. 没有任何对话情况
172
+ // 1.3 没有任何对话情况
195
173
if (conversationList .value .length === 0 ) {
196
174
activeConversationId .value = null
197
175
conversationMap .value = {}
198
176
return
199
177
}
200
- // 5. 对话根据时间分组(置顶、今天、一天前、三天前、七天前、30天前)
201
- conversationMap .value = await conversationTimeGroup (conversationList .value )
178
+
179
+ // 2. 对话根据时间分组(置顶、今天、一天前、三天前、七天前、30 天前)
180
+ conversationMap .value = await getConversationGroupByCreateTime (conversationList .value )
202
181
} finally {
203
182
// 清理定时器
204
183
if (loadingTime .value ) {
@@ -209,8 +188,10 @@ const getChatConversationList = async () => {
209
188
}
210
189
}
211
190
212
- const conversationTimeGroup = async (list : ChatConversationVO []) => {
191
+ /** 按照 creteTime 创建时间,进行分组 */
192
+ const getConversationGroupByCreateTime = async (list : ChatConversationVO []) => {
213
193
// 排序、指定、时间分组(今天、一天前、三天前、七天前、30天前)
194
+ // noinspection NonAsciiCharacters
214
195
const groupMap = {
215
196
置顶: [],
216
197
今天: [],
@@ -233,7 +214,7 @@ const conversationTimeGroup = async (list: ChatConversationVO[]) => {
233
214
continue
234
215
}
235
216
// 计算时间差(单位:毫秒)
236
- const diff = now - conversation .updateTime
217
+ const diff = now - conversation .createTime
237
218
// 根据时间间隔判断
238
219
if (diff < oneDay ) {
239
220
groupMap [' 今天' ].push (conversation )
@@ -250,9 +231,7 @@ const conversationTimeGroup = async (list: ChatConversationVO[]) => {
250
231
return groupMap
251
232
}
252
233
253
- /**
254
- * 对话 - 新建
255
- */
234
+ /** 新建对话 */
256
235
const createConversation = async () => {
257
236
// 1. 新建对话
258
237
const conversationId = await ChatConversationApi .createChatConversationMy (
@@ -266,9 +245,7 @@ const createConversation = async () => {
266
245
emits (' onConversationCreate' )
267
246
}
268
247
269
- /**
270
- * 对话 - 更新标题
271
- */
248
+ /** 修改对话的标题 */
272
249
const updateConversationTitle = async (conversation : ChatConversationVO ) => {
273
250
// 1. 二次确认
274
251
const { value } = await ElMessageBox .prompt (' 修改标题' , {
@@ -296,9 +273,7 @@ const updateConversationTitle = async (conversation: ChatConversationVO) => {
296
273
}
297
274
}
298
275
299
- /**
300
- * 删除聊天对话
301
- */
276
+ /** 删除聊天对话 */
302
277
const deleteChatConversation = async (conversation : ChatConversationVO ) => {
303
278
try {
304
279
// 删除的二次确认
@@ -313,72 +288,56 @@ const deleteChatConversation = async (conversation: ChatConversationVO) => {
313
288
} catch {}
314
289
}
315
290
316
- /**
317
- * 对话置顶
318
- */
319
- // TODO @fan:应该是 handleXXX,handler 是名词哈
320
- const handlerTop = async (conversation : ChatConversationVO ) => {
291
+ /** 清空对话 */
292
+ const handleClearConversation = async () => {
293
+ try {
294
+ await message .confirm (' 确认后对话会全部清空,置顶的对话除外。' )
295
+ await ChatConversationApi .deleteChatConversationMyByUnpinned ()
296
+ ElMessage ({
297
+ message: ' 操作成功!' ,
298
+ type: ' success'
299
+ })
300
+ // 清空 对话 和 对话内容
301
+ activeConversationId .value = null
302
+ // 获取 对话列表
303
+ await getChatConversationList ()
304
+ // 回调 方法
305
+ emits (' onConversationClear' )
306
+ } catch {}
307
+ }
308
+
309
+ /** 对话置顶 */
310
+ const handleTop = async (conversation : ChatConversationVO ) => {
321
311
// 更新对话置顶
322
312
conversation .pinned = ! conversation .pinned
323
313
await ChatConversationApi .updateChatConversationMy (conversation )
324
314
// 刷新对话
325
315
await getChatConversationList ()
326
316
}
327
317
328
- // TODO @fan:类似 ============ 分块的,最后后面也有 ============ 哈
329
- // ============ 角色仓库
318
+ // ============ 角色仓库 ============
330
319
331
- /**
332
- * 角色仓库抽屉
333
- */
320
+ /** 角色仓库抽屉 */
321
+ const roleRepositoryOpen = ref <boolean >(false ) // 角色仓库是否打开
334
322
const handleRoleRepository = async () => {
335
- drawer .value = ! drawer .value
336
- }
337
-
338
- // ============= 清空对话
339
-
340
- /**
341
- * 清空对话
342
- */
343
- const handleClearConversation = async () => {
344
- // TODO @fan:可以使用 await message.confirm( 简化,然后使用 await 改成同步的逻辑,会更简洁
345
- ElMessageBox .confirm (' 确认后对话会全部清空,置顶的对话除外。' , ' 确认提示' , {
346
- confirmButtonText: ' 确认' ,
347
- cancelButtonText: ' 取消' ,
348
- type: ' warning'
349
- })
350
- .then (async () => {
351
- await ChatConversationApi .deleteChatConversationMyByUnpinned ()
352
- ElMessage ({
353
- message: ' 操作成功!' ,
354
- type: ' success'
355
- })
356
- // 清空 对话 和 对话内容
357
- activeConversationId .value = null
358
- // 获取 对话列表
359
- await getChatConversationList ()
360
- // 回调 方法
361
- emits (' onConversationClear' )
362
- })
363
- .catch (() => {})
323
+ roleRepositoryOpen .value = ! roleRepositoryOpen .value
364
324
}
365
325
366
- // ============ 组件 onMounted
367
-
326
+ /** 监听选中的对话 */
368
327
const { activeId } = toRefs (props )
369
328
watch (activeId , async (newValue , oldValue ) => {
370
- // 更新选中
371
329
activeConversationId .value = newValue as string
372
330
})
373
331
374
332
// 定义 public 方法
375
333
defineExpose ({ createConversation })
376
334
335
+ /** 初始化 */
377
336
onMounted (async () => {
378
337
// 获取 对话列表
379
338
await getChatConversationList ()
380
339
// 默认选中
381
- if (props .activeId != null ) {
340
+ if (props .activeId ) {
382
341
activeConversationId .value = props .activeId
383
342
} else {
384
343
// 首次默认选中第一个
0 commit comments