Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
admin
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
46eb8969
authored
May 25, 2024
by
YunaiV
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【代码评审】AI:review 聊天对话的实现
parent
4c259cd6
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
105 additions
and
91 deletions
+105
-91
src/views/ai/chat/ChatEmpty.vue
+0
-5
src/views/ai/chat/Conversation.vue
+21
-20
src/views/ai/chat/Message.vue
+3
-1
src/views/ai/chat/components/Header.vue
+0
-2
src/views/ai/chat/index.vue
+29
-12
src/views/ai/chat/role/RoleList.vue
+1
-1
src/views/ai/chat/role/index.vue
+51
-50
No files found.
src/views/ai/chat/ChatEmpty.vue
View file @
46eb8969
<
template
>
<div
class=
"chat-empty"
>
...
...
@@ -15,7 +14,6 @@
</
template
>
<
script
setup
lang=
"ts"
>
const
router
=
useRouter
()
const
promptList
=
ref
<
any
[]
>
()
// 角色列表
promptList
.
value
=
[
{
...
...
@@ -31,9 +29,6 @@ const emits = defineEmits(['onPrompt'])
const
handlerPromptClick
=
async
({
prompt
})
=>
{
emits
(
'onPrompt'
,
prompt
)
}
onMounted
(
async
()
=>
{
})
</
script
>
<
style
scoped
lang=
"scss"
>
.chat-empty
{
...
...
src/views/ai/chat/Conversation.vue
View file @
46eb8969
...
...
@@ -27,7 +27,6 @@
<el-empty
v-if=
"loading"
description=
"."
:v-loading=
"loading"
/>
<!-- TODO done @fain:置顶、聊天记录、一星期钱、30天前,前端对数据重新做一下分组,或者后端接口改一下 -->
<div
v-for=
"conversationKey in Object.keys(conversationMap)"
:key=
"conversationKey"
>
<div
class=
"conversation-item classify-title"
v-if=
"conversationMap[conversationKey].length"
>
<el-text
class=
"mx-1"
size=
"small"
tag=
"b"
>
{{ conversationKey }}
</el-text>
...
...
@@ -47,7 +46,6 @@
<img
class=
"avatar"
:src=
"conversation.roleAvatar"
/>
<span
class=
"title"
>
{{ conversation.title }}
</span>
</div>
<!-- TODO done @fan:缺一个【置顶】按钮,效果改成 hover 上去展示 -->
<div
class=
"button-wrapper"
v-show=
"hoverConversationId === conversation.id"
>
<el-button
class=
"btn"
link
@
click
.
stop=
"handlerTop(conversation)"
>
<el-icon
title=
"置顶"
v-if=
"!conversation.pinned"
><Top
/></el-icon>
...
...
@@ -74,6 +72,7 @@
</div>
<!-- 左底部:工具栏 -->
<!-- TODO @fan:下面两个 icon,可以使用类似 <Icon icon="ep:question-filled" /> 替代哈 -->
<div
class=
"tool-box"
>
<div
@
click=
"handleRoleRepository"
>
<Icon
icon=
"ep:user"
/>
...
...
@@ -87,7 +86,7 @@
<!-- ============= 额外组件 ============= -->
<!--
角色仓库抽屉
-->
<!--
角色仓库抽屉
-->
<el-drawer
v-model=
"drawer"
title=
"角色仓库"
size=
"754px"
>
<Role/>
</el-drawer>
...
...
@@ -109,7 +108,7 @@ const activeConversationId = ref<string | null>(null) // 选中的对话,默
const
hoverConversationId
=
ref
<
string
|
null
>
(
null
)
// 悬浮上去的对话
const
conversationList
=
ref
([]
as
ChatConversationVO
[])
// 对话列表
const
conversationMap
=
ref
<
any
>
({})
// 对话分组 (置顶、今天、三天前、一星期前、一个月前)
const
drawer
=
ref
<
boolean
>
(
false
)
// 角色仓库抽屉
const
drawer
=
ref
<
boolean
>
(
false
)
// 角色仓库抽屉
TODO @fan:roleDrawer 会不会好点哈
const
loading
=
ref
<
boolean
>
(
false
)
// 加载中
const
loadingTime
=
ref
<
any
>
()
// 加载中定时器
...
...
@@ -154,6 +153,7 @@ const handleConversationClick = async (id: string) => {
return
item
.
id
===
id
})
// 回调 onConversationClick
// TODO @fan: 这里 idea 会报黄色警告,有办法解下么?
const
res
=
emits
(
'onConversationClick'
,
filterConversation
[
0
])
// 切换对话
if
(
res
)
{
...
...
@@ -166,18 +166,18 @@ const handleConversationClick = async (id: string) => {
*/
const
getChatConversationList
=
async
()
=>
{
try
{
// 0
、
加载中
// 0
.
加载中
loadingTime
.
value
=
setTimeout
(()
=>
{
loading
.
value
=
true
},
50
)
// 1
、
获取 对话数据
// 1
.
获取 对话数据
const
res
=
await
ChatConversationApi
.
getChatConversationMyList
()
// 2
、
排序
// 2
.
排序
res
.
sort
((
a
,
b
)
=>
{
return
b
.
createTime
-
a
.
createTime
})
conversationList
.
value
=
res
// 3
、
默认选中
// 3
.
默认选中
if
(
!
activeId
?.
value
)
{
// await handleConversationClick(res[0].id)
}
else
{
...
...
@@ -189,13 +189,13 @@ const getChatConversationList = async () => {
// await handleConversationClick(res[0].id)
// }
}
// 4
、没有
任何对话情况
// 4
. 没有
任何对话情况
if
(
conversationList
.
value
.
length
===
0
)
{
activeConversationId
.
value
=
null
conversationMap
.
value
=
{}
return
}
// 5
、
对话根据时间分组(置顶、今天、一天前、三天前、七天前、30天前)
// 5
.
对话根据时间分组(置顶、今天、一天前、三天前、七天前、30天前)
conversationMap
.
value
=
await
conversationTimeGroup
(
conversationList
.
value
)
}
finally
{
// 清理定时器
...
...
@@ -253,15 +253,15 @@ const conversationTimeGroup = async (list: ChatConversationVO[]) => {
* 对话 - 新建
*/
const
createConversation
=
async
()
=>
{
// 1
、
新建对话
// 1
.
新建对话
const
conversationId
=
await
ChatConversationApi
.
createChatConversationMy
(
{}
as
unknown
as
ChatConversationVO
)
// 2
、
获取对话内容
// 2
.
获取对话内容
await
getChatConversationList
()
// 3
、
选中对话
// 3
.
选中对话
await
handleConversationClick
(
conversationId
)
// 4
、
回调
// 4
.
回调
emits
(
'onConversationCreate'
)
}
...
...
@@ -269,21 +269,21 @@ const createConversation = async () => {
* 对话 - 更新标题
*/
const
updateConversationTitle
=
async
(
conversation
:
ChatConversationVO
)
=>
{
// 1
、
二次确认
// 1
.
二次确认
const
{
value
}
=
await
ElMessageBox
.
prompt
(
'修改标题'
,
{
inputPattern
:
/^
[\s\S]
*.*
\S[\s\S]
*$/
,
// 判断非空,且非空格
inputErrorMessage
:
'标题不能为空'
,
inputValue
:
conversation
.
title
})
// 2
、
发起修改
// 2
.
发起修改
await
ChatConversationApi
.
updateChatConversationMy
({
id
:
conversation
.
id
,
title
:
value
}
as
ChatConversationVO
)
message
.
success
(
'重命名成功'
)
// 刷新列表
//
3.
刷新列表
await
getChatConversationList
()
// 过滤当前切换的
//
4.
过滤当前切换的
const
filterConversationList
=
conversationList
.
value
.
filter
(
item
=>
{
return
item
.
id
===
conversation
.
id
})
...
...
@@ -316,6 +316,7 @@ const deleteChatConversation = async (conversation: ChatConversationVO) => {
/**
* 对话置顶
*/
// TODO @fan:应该是 handleXXX,handler 是名词哈
const
handlerTop
=
async
(
conversation
:
ChatConversationVO
)
=>
{
// 更新对话置顶
conversation
.
pinned
=
!
conversation
.
pinned
...
...
@@ -324,9 +325,9 @@ const handlerTop = async (conversation: ChatConversationVO) => {
await
getChatConversationList
()
}
// TODO @fan:类似 ============ 分块的,最后后面也有 ============ 哈
// ============ 角色仓库
/**
* 角色仓库抽屉
*/
...
...
@@ -336,11 +337,11 @@ const handleRoleRepository = async () => {
// ============= 清空对话
/**
* 清空对话
*/
const
handleClearConversation
=
async
()
=>
{
// TODO @fan:可以使用 await message.confirm( 简化,然后使用 await 改成同步的逻辑,会更简洁
ElMessageBox
.
confirm
(
'确认后对话会全部清空,置顶的对话除外。'
,
'确认提示'
,
...
...
src/views/ai/chat/Message.vue
View file @
46eb8969
...
...
@@ -57,7 +57,7 @@
</div>
</div>
</div>
<!--
回到底部
-->
<!--
回到底部
-->
<div
v-if=
"isScrolling"
class=
"to-bottom"
@
click=
"handleGoBottom"
>
<el-button
:icon=
"ArrowDownBold"
circle
/>
</div>
...
...
@@ -106,6 +106,7 @@ const messageList = computed(() => {
const
scrollToBottom
=
async
(
isIgnore
?:
boolean
)
=>
{
await
nextTick
(()
=>
{
// TODO @fan:中文写作习惯,中英文之间要有空格;另外,nextick 哈,idea 如果有绿色波兰线,可以关注下
//注意要使用nexttick以免获取不到dom
if
(
isIgnore
||
!
isScrolling
.
value
)
{
messageContainer
.
value
.
scrollTop
=
...
...
@@ -184,6 +185,7 @@ const handlerGoTop = async () => {
}
// 监听 list
// TODO @fan:这个木有,是不是删除啦
const
{
list
,
conversationId
}
=
toRefs
(
props
)
watch
(
list
,
async
(
newValue
,
oldValue
)
=>
{
console
.
log
(
'watch list'
,
list
)
...
...
src/views/ai/chat/components/Header.vue
View file @
46eb8969
...
...
@@ -45,6 +45,4 @@ defineProps({
flex-direction
:
row
;
}
}
</
style
>
src/views/ai/chat/index.vue
View file @
46eb8969
...
...
@@ -10,27 +10,27 @@
/>
<!-- 右侧:对话详情 -->
<el-container
class=
"detail-container"
>
<!-- 右顶部 TODO 芋艿:右对齐 -->
<el-header
class=
"header"
>
<div
class=
"title"
>
{{
activeConversation
?.
title
?
activeConversation
?.
title
:
'对话'
}}
<span
v-if=
"list.length"
>
(
{{
list
.
length
}}
)
</span>
</div>
<div
class=
"btns"
v-if=
"activeConversation"
>
<!-- TODO @fan:样式改下;这里我已经改成点击后,弹出了 -->
<el-button
type=
"primary"
bg
text=
"plain"
size=
"small"
@
click=
"openChatConversationUpdateForm"
>
<el-button
type=
"primary"
bg
plain
size=
"small"
@
click=
"openChatConversationUpdateForm"
>
<span
v-html=
"activeConversation?.modelName"
></span>
<Icon
icon=
"ep:setting"
style=
"margin-left: 10px"
/>
</el-button>
<el-button
size=
"small"
class=
"btn"
@
click=
"handlerMessageClear"
>
<!-- TODO @fan:style 部分,可以考虑用 unocss 替代 -->
<img
src=
"@/assets/ai/clear.svg"
style=
"height: 14px;"
/>
</el-button>
<!-- TODO @fan:下面两个 icon,可以使用类似
<Icon
icon=
"ep:question-filled"
/>
替代哈 -->
<el-button
size=
"small"
:icon=
"Download"
class=
"btn"
/>
<el-button
size=
"small"
:icon=
"Top"
class=
"btn"
@
click=
"handlerGoTop"
/>
</div>
</el-header>
<!-- main -->
<!-- main
:消息列表
-->
<el-main
class=
"main-container"
>
<div
>
<div
class=
"message-container"
>
...
...
@@ -87,7 +87,7 @@
</el-container>
<!-- ========= 额外组件 ========== -->
<!-- 更新对话
f
orm -->
<!-- 更新对话
F
orm -->
<ChatConversationUpdateForm
ref=
"chatConversationUpdateFormRef"
@
success=
"handlerTitleSuccess"
...
...
@@ -96,6 +96,7 @@
</
template
>
<
script
setup
lang=
"ts"
>
// TODO @fan:是不是把 index.vue 相关的,在这里新建一个 index 目录,然后挪进去哈。因为 /ai/chat 还会有其它功能。例如说,现在的 /ai/chat/manager 管理
import
Conversation
from
'./Conversation.vue'
import
Message
from
'./Message.vue'
import
ChatEmpty
from
'./ChatEmpty.vue'
...
...
@@ -120,15 +121,17 @@ const prompt = ref<string>() // prompt
const
userInfo
=
ref
<
ProfileVO
>
()
// 用户信息
const
enableContext
=
ref
<
boolean
>
(
true
)
// 是否开启上下文
// TODO @fan:这几个变量,可以注释在补下哈;另外,fullText 可以明确是生成中的消息 Text,这样更容易理解哈;
const
fullText
=
ref
(
''
);
const
displayedText
=
ref
(
''
);
const
textSpeed
=
ref
<
number
>
(
50
);
// Typing speed in milliseconds
const
textRoleRunning
=
ref
<
boolean
>
(
false
);
// Typing speed in milliseconds
// chat message 列表
// TODO @fan:list、listLoading、listLoadingTime 不能体现出来是消息列表,是不是可以变量再优化下
const
list
=
ref
<
ChatMessageVO
[]
>
([])
// 列表的数据
const
listLoading
=
ref
<
boolean
>
(
false
)
// 是否加载中
const
listLoadingTime
=
ref
<
any
>
()
// time定时器,如果加载速度很快,就不进入加载中
const
listLoadingTime
=
ref
<
any
>
()
// time
定时器,如果加载速度很快,就不进入加载中
// 判断 消息列表 滚动的位置(用于判断是否需要滚动到消息最下方)
const
messageRef
=
ref
()
...
...
@@ -140,6 +143,7 @@ const defaultRoleAvatar = 'http://test.yudao.iocoder.cn/eaef5f41acb911dd718429a0
// =========== 自提滚动效果
// TODO @fan:这个方法,要不加个方法注释
const
textRoll
=
async
()
=>
{
let
index
=
0
;
try
{
...
...
@@ -162,7 +166,7 @@ const textRoll = async () => {
}
else
{
textSpeed
.
value
=
100
}
// 对话结束,就按
30
的速度
// 对话结束,就按
30
的速度
if
(
!
conversationInProgress
.
value
)
{
textSpeed
.
value
=
10
}
...
...
@@ -176,6 +180,7 @@ const textRoll = async () => {
// 更新 message
const
lastMessage
=
list
.
value
[
list
.
value
.
length
-
1
]
lastMessage
.
content
=
displayedText
.
value
// TODO @fan:ist.value?,还是 ist.value.length 哈?
list
.
value
[
list
.
value
-
1
]
=
lastMessage
// 滚动到住下面
await
scrollToBottom
()
...
...
@@ -212,6 +217,7 @@ function scrollToBottom(isIgnore?: boolean) {
// ============= 处理聊天输入回车发送 =============
// TODO @fan:是不是可以通过 @keydown.enter、@keydown.shift.enter 来实现,回车发送、shift+回车换行;主要看看,是不是可以简化 isComposing 相关的逻辑
const
onCompositionstart
=
()
=>
{
isComposing
.
value
=
true
}
...
...
@@ -276,12 +282,14 @@ const onSendBtn = async () => {
const
doSend
=
async
(
content
:
string
)
=>
{
if
(
content
.
length
<
2
)
{
// TODO @fan:这个 message.error(`上传文件大小不能超过${props.fileSize}MB!`) 可以替代,这种形式
ElMessage
({
message
:
'请输入内容!'
,
type
:
'error'
})
return
}
// TODO @fan:这个 message.error(`上传文件大小不能超过${props.fileSize}MB!`) 可以替代,这种形式
if
(
activeConversationId
.
value
==
null
)
{
ElMessage
({
message
:
'还没创建对话,不能发送!'
,
...
...
@@ -289,9 +297,9 @@ const doSend = async (content: string) => {
})
return
}
// TODO 芋艿:这块交互要在优化;应该是先插入到 UI 界面,里面会有当前的消息,和正在思考中;之后发起请求;
// 清空输入框
prompt
.
value
=
''
// TODO @fan:idea 这里会报类型错误,是不是可以解决下哈
const
userMessage
=
{
conversationId
:
activeConversationId
.
value
,
content
:
content
...
...
@@ -309,6 +317,7 @@ const doSendStream = async (userMessage: ChatMessageVO) => {
fullText
.
value
=
''
try
{
// 先添加两个假数据,等 stream 返回再替换
// TODO @fan:idea 这里会报类型错误,是不是可以解决下哈
list
.
value
.
push
({
id
:
-
1
,
conversationId
:
activeConversationId
.
value
,
...
...
@@ -326,13 +335,14 @@ const doSendStream = async (userMessage: ChatMessageVO) => {
createTime
:
new
Date
()
}
as
ChatMessageVO
)
// 滚动到最下面
// TODO @fan:可以 await nextTick();然后同步调用 scrollToBottom()
nextTick
(
async
()
=>
{
await
scrollToBottom
()
})
// 开始滚动
textRoll
()
// 发送 event stream
let
isFirstMessage
=
true
let
isFirstMessage
=
true
// TODO @fan:isFirstChunk 会更精准
ChatMessageApi
.
sendStream
(
userMessage
.
conversationId
,
// TODO 芋艿:这里可能要在优化;
userMessage
.
content
,
...
...
@@ -367,12 +377,14 @@ const doSendStream = async (userMessage: ChatMessageVO) => {
},
(
error
)
=>
{
message
.
alert
(
`对话异常!
${
error
}
`
)
// TODO @fan:是不是可以复用 stopStream 方法
// 标记对话结束
conversationInProgress
.
value
=
false
// 结束 stream 对话
conversationInAbortController
.
value
.
abort
()
},
()
=>
{
// TODO @fan:是不是可以复用 stopStream 方法
// 标记对话结束
conversationInProgress
.
value
=
false
// 结束 stream 对话
...
...
@@ -412,6 +424,7 @@ const messageList = computed(() => {
return
[]
})
// TODO @fan:一般情况下,项目方法注释用 /** */,啊哈,主要保持风格统一,= = 少占点行哈,
/**
* 获取 - message 列表
*/
...
...
@@ -500,6 +513,7 @@ const handleConversationClick = async (conversation: ChatConversationVO) => {
* 对话 - 清理全部对话
*/
const
handlerConversationClear
=
async
()
=>
{
// TODO @fan:需要加一个 对话进行中,不允许切换
activeConversationId
.
value
=
null
activeConversation
.
value
=
null
list
.
value
=
[]
...
...
@@ -509,7 +523,7 @@ const handlerConversationClear = async ()=> {
* 对话 - 删除
*/
const
handlerConversationDelete
=
async
(
delConversation
:
ChatConversationVO
)
=>
{
// 删除的对话如果是当前选中的,那么
久
重置
// 删除的对话如果是当前选中的,那么
就
重置
if
(
activeConversationId
.
value
===
delConversation
.
id
)
{
await
handlerConversationClear
()
}
...
...
@@ -532,6 +546,7 @@ const getConversation = async (id: string | null) => {
/**
* 对话 - 新建
*/
// TODO @fan:应该是 handleXXX,handler 是名词哈
const
handlerNewChat
=
async
()
=>
{
// 创建对话
await
conversationRef
.
value
.
createConversation
()
...
...
@@ -552,14 +567,14 @@ const handlerMessageDelete = async () => {
}
/**
* 编辑 message
* 编辑 message
:设置为 prompt,可以再次编辑
*/
const
handlerMessageEdit
=
async
(
message
:
ChatMessageVO
)
=>
{
prompt
.
value
=
message
.
content
}
/**
*
编辑 message
*
刷新 message:基于指定消息,再次发起对话
*/
const
handlerMessageRefresh
=
async
(
message
:
ChatMessageVO
)
=>
{
await
doSend
(
message
.
content
)
...
...
@@ -579,10 +594,12 @@ const handlerMessageClear = async () => {
if
(
!
activeConversationId
.
value
)
{
return
}
// TODO @fan:需要 try catch 下,不然点击取消会报异常
// 确认提示
await
message
.
delConfirm
(
"确认清空对话消息?"
)
// 清空对话
await
ChatMessageApi
.
deleteByConversationId
(
activeConversationId
.
value
as
string
)
// TODO @fan:是不是直接置空就好啦;
// 刷新 message 列表
await
getMessageList
()
}
...
...
src/views/ai/chat/role/RoleList.vue
View file @
46eb8969
...
...
@@ -10,6 +10,7 @@
<el-icon><More
/></el-icon>
</el-button>
</span>
<!-- TODO @fan:下面两个 icon,可以使用类似
<Icon
icon=
"ep:question-filled"
/>
替代哈 -->
<template
#
dropdown
>
<el-dropdown-menu>
<el-dropdown-item
:command=
"['edit', role]"
>
...
...
@@ -31,7 +32,6 @@
<div
class=
"content-container"
>
<div
class=
"title"
>
{{ role.name }}
</div>
<div
class=
"description"
>
{{ role.description }}
</div>
</div>
<div
class=
"btn-container"
>
<el-button
type=
"primary"
size=
"small"
@
click=
"handleUseClick(role)"
>
使用
</el-button>
...
...
src/views/ai/chat/role/index.vue
View file @
46eb8969
<!-- chat 角色仓库 -->
<
template
>
<el-container
class=
"role-container"
>
<ChatRoleForm
ref=
"formRef"
@
success=
"handlerAddRoleSuccess"
/>
<ChatRoleForm
ref=
"formRef"
@
success=
"handlerAddRoleSuccess"
/>
<!-- header -->
<Header
title=
"角色仓库"
style=
"position: relative"
/>
<Header
title=
"角色仓库"
style=
"position: relative"
/>
<!-- main -->
<el-main
class=
"role-main"
>
<div
class=
"search-container"
>
...
...
@@ -17,9 +17,15 @@
:suffix-icon=
"Search"
@
change=
"getActiveTabsRole"
/>
<el-button
v-if=
"activeRole == 'my-role'"
type=
"primary"
@
click=
"handlerAddRole"
style=
"margin-left: 20px;"
>
<el-button
v-if=
"activeRole == 'my-role'"
type=
"primary"
@
click=
"handlerAddRole"
style=
"margin-left: 20px"
>
<!-- TODO @fan:下面两个 icon,可以使用类似
<Icon
icon=
"ep:question-filled"
/>
替代哈 -->
<el-icon>
<User/>
<User
/>
</el-icon>
添加角色
</el-button>
...
...
@@ -35,7 +41,8 @@
@
on-edit=
"handlerCardEdit"
@
on-use=
"handlerCardUse"
@
on-page=
"handlerCardPage('my')"
style=
"margin-top: 20px;"
/>
style=
"margin-top: 20px"
/>
</el-tab-pane>
<el-tab-pane
label=
"公共角色"
name=
"public-role"
>
<RoleCategoryList
...
...
@@ -50,34 +57,33 @@
@
on-edit=
"handlerCardEdit"
@
on-use=
"handlerCardUse"
@
on-page=
"handlerCardPage('public')"
style=
"margin-top: 20px;"
loading
/>
style=
"margin-top: 20px"
loading
/>
</el-tab-pane>
</el-tabs>
</el-main>
</el-container>
</
template
>
<!-- setup -->
<
script
setup
lang=
"ts"
>
import
{
ref
}
from
"vue"
;
import
{
ref
}
from
'vue'
import
Header
from
'@/views/ai/chat/components/Header.vue'
import
RoleList
from
'./RoleList.vue'
import
ChatRoleForm
from
'@/views/ai/model/chatRole/ChatRoleForm.vue'
import
RoleCategoryList
from
'./RoleCategoryList.vue'
import
{
ChatRoleApi
,
ChatRolePageReqVO
,
ChatRoleVO
}
from
'@/api/ai/model/chatRole'
import
{
ChatConversationApi
,
ChatConversationVO
}
from
'@/api/ai/chat/conversation'
import
{
TabsPaneContext
}
from
"element-plus"
;
import
{
Search
,
User
}
from
"@element-plus/icons-vue"
;
import
{
ChatRoleApi
,
ChatRolePageReqVO
,
ChatRoleVO
}
from
'@/api/ai/model/chatRole'
import
{
ChatConversationApi
,
ChatConversationVO
}
from
'@/api/ai/chat/conversation'
import
{
TabsPaneContext
}
from
'element-plus'
import
{
Search
,
User
}
from
'@element-plus/icons-vue'
// 获取路由
const
router
=
useRouter
()
const
router
=
useRouter
()
// 路由对象
// 属性定义
const
loading
=
ref
<
boolean
>
(
false
)
// 加载中
const
activeRole
=
ref
<
string
>
(
'my-role'
)
// 选中的角色
const
activeRole
=
ref
<
string
>
(
'my-role'
)
// 选中的角色
TODO @fan:是不是叫 activeTab 会更明确一点哈。选中的角色,会以为是某个角色
const
search
=
ref
<
string
>
(
''
)
// 加载中
// TODO @fan:要不 myPage、pubPage,搞成类似 const queryParams = reactive({ ,分别搞成两个大的参数哈?
const
myPageNo
=
ref
<
number
>
(
1
)
// my 分页下标
const
myPageSize
=
ref
<
number
>
(
50
)
// my 分页大小
const
myRoleList
=
ref
<
ChatRoleVO
[]
>
([])
// my 分页大小
...
...
@@ -86,18 +92,16 @@ const publicPageSize = ref<number>(50) // public 分页大小
const
publicRoleList
=
ref
<
ChatRoleVO
[]
>
([])
// public 分页大小
const
activeCategory
=
ref
<
string
>
(
'全部'
)
// 选择中的分类
const
categoryList
=
ref
<
string
[]
>
([])
// 角色分类类别
/** 添加/修改操作 */
const
formRef
=
ref
()
// tabs 点击
/** tabs 点击 */
const
handleTabsClick
=
async
(
tab
:
TabsPaneContext
)
=>
{
// 设置切换状态
const
activeTabs
=
tab
.
paneName
+
''
activeRole
.
value
=
activeTabs
;
activeRole
.
value
=
tab
.
paneName
+
''
// 切换的时候重新加载数据
await
getActiveTabsRole
()
}
/
/ 获取 my role
/
** 获取 my role 我的角色 */
const
getMyRole
=
async
(
append
?:
boolean
)
=>
{
const
params
:
ChatRolePageReqVO
=
{
pageNo
:
myPageNo
.
value
,
...
...
@@ -105,7 +109,7 @@ const getMyRole = async (append?: boolean) => {
name
:
search
.
value
,
publicStatus
:
false
}
const
{
total
,
list
}
=
await
ChatRoleApi
.
getMyPage
(
params
)
const
{
total
,
list
}
=
await
ChatRoleApi
.
getMyPage
(
params
)
if
(
append
)
{
myRoleList
.
value
.
push
.
apply
(
myRoleList
.
value
,
list
)
}
else
{
...
...
@@ -113,7 +117,7 @@ const getMyRole = async (append?: boolean) => {
}
}
/
/ 获取 public role
/
** 获取 public role 公共角色 */
const
getPublicRole
=
async
(
append
?:
boolean
)
=>
{
const
params
:
ChatRolePageReqVO
=
{
pageNo
:
publicPageNo
.
value
,
...
...
@@ -122,7 +126,7 @@ const getPublicRole = async (append?: boolean) => {
name
:
search
.
value
,
publicStatus
:
true
}
const
{
total
,
list
}
=
await
ChatRoleApi
.
getMyPage
(
params
)
const
{
total
,
list
}
=
await
ChatRoleApi
.
getMyPage
(
params
)
if
(
append
)
{
publicRoleList
.
value
.
push
.
apply
(
publicRoleList
.
value
,
list
)
}
else
{
...
...
@@ -130,7 +134,7 @@ const getPublicRole = async (append?: boolean) => {
}
}
/
/ 获取选中的 tabs 角色
/
** 获取选中的 tabs 角色 */
const
getActiveTabsRole
=
async
()
=>
{
if
(
activeRole
.
value
===
'my-role'
)
{
myPageNo
.
value
=
1
...
...
@@ -141,14 +145,14 @@ const getActiveTabsRole = async () => {
}
}
/
/ 获取角色分类列表
/
** 获取角色分类列表 */
const
getRoleCategoryList
=
async
()
=>
{
const
res
=
await
ChatRoleApi
.
getCategoryList
()
const
defaultRole
=
[
'全部'
]
categoryList
.
value
=
[...
defaultRole
,
...
res
]
}
/
/ 处理分类点击
/
** 处理分类点击 */
const
handlerCategoryClick
=
async
(
category
:
string
)
=>
{
// 切换选择的分类
activeCategory
.
value
=
category
...
...
@@ -156,11 +160,18 @@ const handlerCategoryClick = async (category: string) => {
await
getActiveTabsRole
()
}
// 添加角色
/** 添加/修改操作 */
const
formRef
=
ref
()
const
handlerAddRole
=
async
()
=>
{
formRef
.
value
.
open
(
'my-create'
,
null
,
'添加角色'
)
}
/** 添加角色成功 */
const
handlerAddRoleSuccess
=
async
(
e
)
=>
{
// 刷新数据
await
getActiveTabsRole
()
}
// card 删除
const
handlerCardDelete
=
async
(
role
)
=>
{
await
ChatRoleApi
.
deleteMy
(
role
.
id
)
...
...
@@ -173,7 +184,7 @@ const handlerCardEdit = async (role) => {
formRef
.
value
.
open
(
'my-update'
,
role
.
id
,
'编辑角色'
)
}
/
/ card 分页
/
** card 分页:获取下一页 */
const
handlerCardPage
=
async
(
type
)
=>
{
console
.
log
(
'handlerCardPage'
,
type
)
try
{
...
...
@@ -190,39 +201,33 @@ const handlerCardPage = async (type) => {
}
}
/
/ card 使用
/
** 选择 card 角色:新建聊天对话 */
const
handlerCardUse
=
async
(
role
)
=>
{
// 1. 创建对话
const
data
:
ChatConversationVO
=
{
roleId
:
role
.
id
}
as
unknown
as
ChatConversationVO
// 创建对话
const
conversation
=
await
ChatConversationApi
.
createChatConversationMy
(
data
)
//
调整页面
router
.
push
({
const
conversationId
=
await
ChatConversationApi
.
createChatConversationMy
(
data
)
// 2. 跳转页面
//
TODO @fan:最好用 name,后续可能会改~~~
await
router
.
push
({
path
:
`/ai/chat`
,
query
:
{
conversationId
:
conversation
,
conversationId
:
conversation
Id
}
})
}
// 添加角色成功
const
handlerAddRoleSuccess
=
async
(
e
)
=>
{
console
.
log
(
e
)
// 刷新数据
await
getActiveTabsRole
()
}
//
/** 初始化 **/
onMounted
(
async
()
=>
{
// 获取分类
await
getRoleCategoryList
()
// 获取 role 数据
await
getActiveTabsRole
()
})
// TODO @fan:css 是不是可以融合到 scss 里面呀?
</
script
>
<
style
lang=
"css"
>
.el-tabs__content
{
position
:
relative
;
height
:
100%
;
...
...
@@ -232,11 +237,9 @@ onMounted(async () => {
.el-tabs__nav-scroll
{
margin
:
10px
20px
;
}
</
style
>
<!-- 样式 -->
<
style
scoped
lang=
"scss"
>
//
跟容器
.role-container
{
position
:
absolute
;
...
...
@@ -290,6 +293,4 @@ onMounted(async () => {
}
}
}
</
style
>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment