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
1a6afa32
authored
Jul 08, 2024
by
YunaiV
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【代码优化】AI:聊天对话 index.vue 代码梳理 40%(message 部分)
parent
2b078911
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
61 additions
and
98 deletions
+61
-98
src/api/ai/chat/conversation/index.ts
+1
-0
src/views/ai/chat/index/components/message/MessageList.vue
+44
-83
src/views/ai/chat/index/components/message/MessageListEmpty.vue
+15
-11
src/views/ai/chat/index/components/message/MessageNewConversation.vue
+1
-4
No files found.
src/api/ai/chat/conversation/index.ts
View file @
1a6afa32
...
@@ -12,6 +12,7 @@ export interface ChatConversationVO {
...
@@ -12,6 +12,7 @@ export interface ChatConversationVO {
temperature
:
number
// 温度参数
temperature
:
number
// 温度参数
maxTokens
:
number
// 单条回复的最大 Token 数量
maxTokens
:
number
// 单条回复的最大 Token 数量
maxContexts
:
number
// 上下文的最大 Message 数量
maxContexts
:
number
// 上下文的最大 Message 数量
createTime
?:
Date
// 创建时间
// 额外字段
// 额外字段
systemMessage
?:
string
// 角色设定
systemMessage
?:
string
// 角色设定
modelName
?:
string
// 模型名字
modelName
?:
string
// 模型名字
...
...
src/views/ai/chat/index/components/message/MessageList.vue
View file @
1a6afa32
<
template
>
<
template
>
<div
ref=
"messageContainer"
style=
"height: 100%; overflow-y: auto; position:
relative"
>
<div
ref=
"messageContainer"
class=
"h-100% overflow-y
relative"
>
<div
class=
"chat-list"
v-for=
"(item, index) in list"
:key=
"index"
>
<div
class=
"chat-list"
v-for=
"(item, index) in list"
:key=
"index"
>
<!--
靠左 message
-->
<!--
靠左 message:system、assistant 类型
-->
<div
class=
"left-message message-item"
v-if=
"item.type !== 'user'"
>
<div
class=
"left-message message-item"
v-if=
"item.type !== 'user'"
>
<div
class=
"avatar"
>
<div
class=
"avatar"
>
<el-avatar
:src=
"roleAvatar"
/>
<el-avatar
:src=
"roleAvatar"
/>
...
@@ -14,16 +14,16 @@
...
@@ -14,16 +14,16 @@
<MarkdownView
class=
"left-text"
:content=
"item.content"
/>
<MarkdownView
class=
"left-text"
:content=
"item.content"
/>
</div>
</div>
<div
class=
"left-btns"
>
<div
class=
"left-btns"
>
<el-button
class=
"btn-cus"
link
@
click=
"
noCopy
(item.content)"
>
<el-button
class=
"btn-cus"
link
@
click=
"
copyContent
(item.content)"
>
<img
class=
"btn-image"
src=
"@/assets/ai/copy.svg"
/>
<img
class=
"btn-image"
src=
"@/assets/ai/copy.svg"
/>
</el-button>
</el-button>
<el-button
v-if=
"item.id > 0"
class=
"btn-cus"
link
@
click=
"onDelete(item.id)"
>
<el-button
v-if=
"item.id > 0"
class=
"btn-cus"
link
@
click=
"onDelete(item.id)"
>
<img
class=
"btn-image
"
src=
"@/assets/ai/delete.svg"
style=
"height: 17px
"
/>
<img
class=
"btn-image
h-17px"
src=
"@/assets/ai/delete.svg
"
/>
</el-button>
</el-button>
</div>
</div>
</div>
</div>
</div>
</div>
<!--
靠右 message
-->
<!--
靠右 message:user 类型
-->
<div
class=
"right-message message-item"
v-if=
"item.type === 'user'"
>
<div
class=
"right-message message-item"
v-if=
"item.type === 'user'"
>
<div
class=
"avatar"
>
<div
class=
"avatar"
>
<el-avatar
:src=
"userAvatar"
/>
<el-avatar
:src=
"userAvatar"
/>
...
@@ -36,15 +36,11 @@
...
@@ -36,15 +36,11 @@
<div
class=
"right-text"
>
{{
item
.
content
}}
</div>
<div
class=
"right-text"
>
{{
item
.
content
}}
</div>
</div>
</div>
<div
class=
"right-btns"
>
<div
class=
"right-btns"
>
<el-button
class=
"btn-cus"
link
@
click=
"
noCopy
(item.content)"
>
<el-button
class=
"btn-cus"
link
@
click=
"
copyContent
(item.content)"
>
<img
class=
"btn-image"
src=
"@/assets/ai/copy.svg"
/>
<img
class=
"btn-image"
src=
"@/assets/ai/copy.svg"
/>
</el-button>
</el-button>
<el-button
class=
"btn-cus"
link
@
click=
"onDelete(item.id)"
>
<el-button
class=
"btn-cus"
link
@
click=
"onDelete(item.id)"
>
<img
<img
class=
"btn-image h-17px mr-12px"
src=
"@/assets/ai/delete.svg"
/>
class=
"btn-image"
src=
"@/assets/ai/delete.svg"
style=
"height: 17px; margin-right: 12px"
/>
</el-button>
</el-button>
<el-button
class=
"btn-cus"
link
@
click=
"onRefresh(item)"
>
<el-button
class=
"btn-cus"
link
@
click=
"onRefresh(item)"
>
<el-icon
size=
"17"
><RefreshRight
/></el-icon>
<el-icon
size=
"17"
><RefreshRight
/></el-icon>
...
@@ -63,23 +59,25 @@
...
@@ -63,23 +59,25 @@
</div>
</div>
</
template
>
</
template
>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
PropType
}
from
'vue'
import
{
formatDate
}
from
'@/utils/formatTime'
import
{
formatDate
}
from
'@/utils/formatTime'
import
MarkdownView
from
'@/components/MarkdownView/index.vue'
import
MarkdownView
from
'@/components/MarkdownView/index.vue'
import
{
ChatMessageApi
,
ChatMessageVO
}
from
'@/api/ai/chat/message'
import
{
useClipboard
}
from
'@vueuse/core'
import
{
useClipboard
}
from
'@vueuse/core'
import
{
PropType
}
from
'vue'
import
{
ArrowDownBold
,
Edit
,
RefreshRight
}
from
'@element-plus/icons-vue'
import
{
ArrowDownBold
,
Edit
,
RefreshRight
}
from
'@element-plus/icons-vue'
import
{
ChatMessageApi
,
ChatMessageVO
}
from
'@/api/ai/chat/message'
import
{
ChatConversationVO
}
from
'@/api/ai/chat/conversation'
import
{
ChatConversationVO
}
from
'@/api/ai/chat/conversation'
import
{
useUserStore
}
from
'@/store/modules/user'
import
{
useUserStore
}
from
'@/store/modules/user'
import
userAvatarDefaultImg
from
'@/assets/imgs/avatar.gif'
import
userAvatarDefaultImg
from
'@/assets/imgs/avatar.gif'
import
roleAvatarDefaultImg
from
'@/assets/ai/gpt.svg'
import
roleAvatarDefaultImg
from
'@/assets/ai/gpt.svg'
const
message
=
useMessage
()
// 消息弹窗
const
{
copy
}
=
useClipboard
()
// 初始化 copy 到粘贴板
const
{
copy
}
=
useClipboard
()
// 初始化 copy 到粘贴板
// 判断 消息列表 滚动的位置(用于判断是否需要滚动到消息最下方)
const
userStore
=
useUserStore
()
// 判断“消息列表”滚动的位置(用于判断是否需要滚动到消息最下方)
const
messageContainer
:
any
=
ref
(
null
)
const
messageContainer
:
any
=
ref
(
null
)
const
isScrolling
=
ref
(
false
)
//用于判断用户是否在滚动
const
isScrolling
=
ref
(
false
)
//用于判断用户是否在滚动
const
userStore
=
useUserStore
()
const
userAvatar
=
computed
(()
=>
userStore
.
user
.
avatar
??
userAvatarDefaultImg
)
const
userAvatar
=
computed
(()
=>
userStore
.
user
.
avatar
??
userAvatarDefaultImg
)
const
roleAvatar
=
computed
(()
=>
props
.
conversation
.
roleAvatar
??
roleAvatarDefaultImg
)
const
roleAvatar
=
computed
(()
=>
props
.
conversation
.
roleAvatar
??
roleAvatarDefaultImg
)
...
@@ -95,12 +93,16 @@ const props = defineProps({
...
@@ -95,12 +93,16 @@ const props = defineProps({
}
}
})
})
const
{
list
}
=
toRefs
(
props
)
// 消息列表
const
emits
=
defineEmits
([
'onDeleteSuccess'
,
'onRefresh'
,
'onEdit'
])
// 定义 emits
// ============ 处理对话滚动 ==============
// ============ 处理对话滚动 ==============
/** 滚动到底部 */
const
scrollToBottom
=
async
(
isIgnore
?:
boolean
)
=>
{
const
scrollToBottom
=
async
(
isIgnore
?:
boolean
)
=>
{
// 注意要使用 nextTick 以免获取不到dom
await
nextTick
(()
=>
{
await
nextTick
(()
=>
{
// TODO @fan:中文写作习惯,中英文之间要有空格;另外,nextick 哈,idea 如果有绿色波兰线,可以关注下
//注意要使用nexttick以免获取不到dom
if
(
isIgnore
||
!
isScrolling
.
value
)
{
if
(
isIgnore
||
!
isScrolling
.
value
)
{
messageContainer
.
value
.
scrollTop
=
messageContainer
.
value
.
scrollTop
=
messageContainer
.
value
.
scrollHeight
-
messageContainer
.
value
.
offsetHeight
messageContainer
.
value
.
scrollHeight
-
messageContainer
.
value
.
offsetHeight
...
@@ -122,75 +124,48 @@ function handleScroll() {
...
@@ -122,75 +124,48 @@ function handleScroll() {
}
}
}
}
/**
/** 回到底部 */
* 复制
const
handleGoBottom
=
async
()
=>
{
*/
const
scrollContainer
=
messageContainer
.
value
const
noCopy
=
async
(
content
)
=>
{
scrollContainer
.
scrollTop
=
scrollContainer
.
scrollHeight
copy
(
content
)
}
ElMessage
({
message
:
'复制成功!'
,
/** 回到顶部 */
type
:
'success'
const
handlerGoTop
=
async
()
=>
{
})
const
scrollContainer
=
messageContainer
.
value
scrollContainer
.
scrollTop
=
0
}
defineExpose
({
scrollToBottom
,
handlerGoTop
})
// 提供方法给 parent 调用
// ============ 处理消息操作 ==============
/** 复制 */
const
copyContent
=
async
(
content
)
=>
{
await
copy
(
content
)
message
.
success
(
'复制成功!'
)
}
}
/**
/** 删除 */
* 删除
*/
const
onDelete
=
async
(
id
)
=>
{
const
onDelete
=
async
(
id
)
=>
{
// 删除 message
// 删除 message
await
ChatMessageApi
.
deleteChatMessage
(
id
)
await
ChatMessageApi
.
deleteChatMessage
(
id
)
ElMessage
({
message
.
success
(
'删除成功!'
)
message
:
'删除成功!'
,
type
:
'success'
})
// 回调
// 回调
emits
(
'onDeleteSuccess'
)
emits
(
'onDeleteSuccess'
)
}
}
/**
/** 刷新 */
* 刷新
*/
const
onRefresh
=
async
(
message
:
ChatMessageVO
)
=>
{
const
onRefresh
=
async
(
message
:
ChatMessageVO
)
=>
{
emits
(
'onRefresh'
,
message
)
emits
(
'onRefresh'
,
message
)
}
}
/**
/** 编辑 */
* 编辑
*/
const
onEdit
=
async
(
message
:
ChatMessageVO
)
=>
{
const
onEdit
=
async
(
message
:
ChatMessageVO
)
=>
{
emits
(
'onEdit'
,
message
)
emits
(
'onEdit'
,
message
)
}
}
/**
/** 初始化 */
* 回到底部
*/
const
handleGoBottom
=
async
()
=>
{
const
scrollContainer
=
messageContainer
.
value
scrollContainer
.
scrollTop
=
scrollContainer
.
scrollHeight
}
/**
* 回到顶部
*/
const
handlerGoTop
=
async
()
=>
{
const
scrollContainer
=
messageContainer
.
value
scrollContainer
.
scrollTop
=
0
}
// 监听 list
// TODO @fan:这个木有,是不是删除啦
const
{
list
,
conversationId
}
=
toRefs
(
props
)
watch
(
list
,
async
(
newValue
,
oldValue
)
=>
{
console
.
log
(
'watch list'
,
list
)
})
// 提供方法给 parent 调用
defineExpose
({
scrollToBottom
,
handlerGoTop
})
// 定义 emits
const
emits
=
defineEmits
([
'onDeleteSuccess'
,
'onRefresh'
,
'onEdit'
])
// onMounted
onMounted
(
async
()
=>
{
onMounted
(
async
()
=>
{
messageContainer
.
value
.
addEventListener
(
'scroll'
,
handleScroll
)
messageContainer
.
value
.
addEventListener
(
'scroll'
,
handleScroll
)
})
})
...
@@ -199,15 +174,7 @@ onMounted(async () => {
...
@@ -199,15 +174,7 @@ onMounted(async () => {
<
style
scoped
lang=
"scss"
>
<
style
scoped
lang=
"scss"
>
.message-container
{
.message-container
{
position
:
relative
;
position
:
relative
;
//
top
:
0
;
//
bottom
:
0
;
//
left
:
0
;
//
right
:
0
;
//
width
:
100%
;
//
height
:
100%
;
overflow-y
:
scroll
;
overflow-y
:
scroll
;
//
padding
:
0
15px
;
//
z-index
:
-1
;
}
}
//
中间
//
中间
...
@@ -231,11 +198,6 @@ onMounted(async () => {
...
@@ -231,11 +198,6 @@ onMounted(async () => {
justify-content
:
flex-start
;
justify-content
:
flex-start
;
}
}
.avatar
{
//
height
:
170px
;
//
width
:
170px
;
}
.message
{
.message
{
display
:
flex
;
display
:
flex
;
flex-direction
:
column
;
flex-direction
:
column
;
...
@@ -272,7 +234,6 @@ onMounted(async () => {
...
@@ -272,7 +234,6 @@ onMounted(async () => {
color
:
#fff
;
color
:
#fff
;
display
:
inline
;
display
:
inline
;
background-color
:
#267fff
;
background-color
:
#267fff
;
color
:
#fff
;
box-shadow
:
0
0
0
1px
#267fff
;
box-shadow
:
0
0
0
1px
#267fff
;
border-radius
:
10px
;
border-radius
:
10px
;
padding
:
10px
;
padding
:
10px
;
...
...
src/views/ai/chat/index/components/message/MessageListEmpty.vue
View file @
1a6afa32
<!-- 消息列表为空时,展示 prompt 列表 -->
<
template
>
<
template
>
<div
class=
"chat-empty"
>
<div
class=
"chat-empty"
>
<!-- title -->
<!-- title -->
<div
class=
"center-container"
>
<div
class=
"center-container"
>
<div
class=
"title"
>
芋
艿
AI
</div>
<div
class=
"title"
>
芋
道
AI
</div>
<div
class=
"role-list"
>
<div
class=
"role-list"
>
<div
class=
"role-item"
v-for=
"prompt in promptList"
:key=
"prompt.prompt"
@
click=
"handlerPromptClick(prompt)"
>
<div
{{
prompt
.
prompt
}}
class=
"role-item"
v-for=
"prompt in promptList"
:key=
"prompt.prompt"
@
click=
"handlerPromptClick(prompt)"
>
{{
prompt
.
prompt
}}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</
template
>
</
template
>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
const
promptList
=
[
const
promptList
=
ref
<
any
[]
>
()
// 角色列表
promptList
.
value
=
[
{
{
"prompt"
:
"今天气怎么样?"
,
prompt
:
'今天气怎么样?'
},
},
{
{
"prompt"
:
"写一首好听的诗歌?"
,
prompt
:
'写一首好听的诗歌?'
}
}
]
]
// prompt 列表
const
emits
=
defineEmits
([
'onPrompt'
])
const
emits
=
defineEmits
([
'onPrompt'
])
/** 选中 prompt 点击 */
const
handlerPromptClick
=
async
({
prompt
})
=>
{
const
handlerPromptClick
=
async
({
prompt
})
=>
{
emits
(
'onPrompt'
,
prompt
)
emits
(
'onPrompt'
,
prompt
)
}
}
...
...
src/views/ai/chat/index/components/message/MessageNewConversation.vue
View file @
1a6afa32
...
@@ -9,13 +9,10 @@
...
@@ -9,13 +9,10 @@
</div>
</div>
</div>
</div>
</
template
>
</
template
>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
const
emits
=
defineEmits
([
'onNewConversation'
])
const
emits
=
defineEmits
([
'onNewConversation'
])
/**
/** 新建 conversation 聊天对话 */
* 新建 conversation 聊天对话
*/
const
handlerNewChat
=
()
=>
{
const
handlerNewChat
=
()
=>
{
emits
(
'onNewConversation'
)
emits
(
'onNewConversation'
)
}
}
...
...
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