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
44f322bb
authored
Jul 07, 2024
by
YunaiV
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【代码优化】AI:聊天对话 index.vue 代码梳理 30%
parent
1064bbe5
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
200 additions
and
241 deletions
+200
-241
src/api/ai/chat/message/index.ts
+3
-3
src/views/ai/chat/index/components/conversation/ConversationList.vue
+1
-1
src/views/ai/chat/index/components/message/MessageList.vue
+3
-3
src/views/ai/chat/index/components/message/MessageListEmpty.vue
+0
-0
src/views/ai/chat/index/components/message/MessageLoading.vue
+0
-0
src/views/ai/chat/index/components/message/MessageNewConversation.vue
+5
-6
src/views/ai/chat/index/components/role/RoleCategoryList.vue
+0
-0
src/views/ai/chat/index/components/role/RoleList.vue
+0
-0
src/views/ai/chat/index/components/role/index.vue
+1
-1
src/views/ai/chat/index/index.vue
+187
-227
No files found.
src/api/ai/chat/message/index.ts
View file @
44f322bb
...
@@ -30,7 +30,7 @@ export const ChatMessageApi = {
...
@@ -30,7 +30,7 @@ export const ChatMessageApi = {
// 发送 Stream 消息
// 发送 Stream 消息
// 为什么不用 axios 呢?因为它不支持 SSE 调用
// 为什么不用 axios 呢?因为它不支持 SSE 调用
sendStream
:
async
(
send
ChatMessage
Stream
:
async
(
conversationId
:
number
,
conversationId
:
number
,
content
:
string
,
content
:
string
,
ctrl
,
ctrl
,
...
@@ -60,11 +60,11 @@ export const ChatMessageApi = {
...
@@ -60,11 +60,11 @@ export const ChatMessageApi = {
},
},
// 删除消息
// 删除消息
delete
:
async
(
id
:
string
)
=>
{
delete
ChatMessage
:
async
(
id
:
string
)
=>
{
return
await
request
.
delete
({
url
:
`/ai/chat/message/delete?id=
${
id
}
`
})
return
await
request
.
delete
({
url
:
`/ai/chat/message/delete?id=
${
id
}
`
})
},
},
// 删除
消息 - 对话所有
消息
// 删除
指定对话的
消息
deleteByConversationId
:
async
(
conversationId
:
number
)
=>
{
deleteByConversationId
:
async
(
conversationId
:
number
)
=>
{
return
await
request
.
delete
({
return
await
request
.
delete
({
url
:
`/ai/chat/message/delete-by-conversation-id?conversationId=
${
conversationId
}
`
url
:
`/ai/chat/message/delete-by-conversation-id?conversationId=
${
conversationId
}
`
...
...
src/views/ai/chat/index/components/conversation/ConversationList.vue
View file @
44f322bb
...
@@ -98,7 +98,7 @@
...
@@ -98,7 +98,7 @@
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
ChatConversationApi
,
ChatConversationVO
}
from
'@/api/ai/chat/conversation'
import
{
ChatConversationApi
,
ChatConversationVO
}
from
'@/api/ai/chat/conversation'
import
{
ref
}
from
'vue'
import
{
ref
}
from
'vue'
import
Role
from
'../
../
role/index.vue'
import
Role
from
'../role/index.vue'
import
{
Bottom
,
Top
}
from
'@element-plus/icons-vue'
import
{
Bottom
,
Top
}
from
'@element-plus/icons-vue'
import
roleAvatarDefaultImg
from
'@/assets/ai/gpt.svg'
import
roleAvatarDefaultImg
from
'@/assets/ai/gpt.svg'
...
...
src/views/ai/chat/index/
Message
.vue
→
src/views/ai/chat/index/
components/message/MessageList
.vue
View file @
44f322bb
...
@@ -17,7 +17,7 @@
...
@@ -17,7 +17,7 @@
<el-button
class=
"btn-cus"
link
@
click=
"noCopy(item.content)"
>
<el-button
class=
"btn-cus"
link
@
click=
"noCopy(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
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"
src=
"@/assets/ai/delete.svg"
style=
"height: 17px"
/>
</el-button>
</el-button>
</div>
</div>
...
@@ -70,7 +70,7 @@ import { useClipboard } from '@vueuse/core'
...
@@ -70,7 +70,7 @@ import { useClipboard } from '@vueuse/core'
import
{
PropType
}
from
'vue'
import
{
PropType
}
from
'vue'
import
{
ArrowDownBold
,
Edit
,
RefreshRight
}
from
'@element-plus/icons-vue'
import
{
ArrowDownBold
,
Edit
,
RefreshRight
}
from
'@element-plus/icons-vue'
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'
...
@@ -138,7 +138,7 @@ const noCopy = async (content) => {
...
@@ -138,7 +138,7 @@ const noCopy = async (content) => {
*/
*/
const
onDelete
=
async
(
id
)
=>
{
const
onDelete
=
async
(
id
)
=>
{
// 删除 message
// 删除 message
await
ChatMessageApi
.
delete
(
id
)
await
ChatMessageApi
.
delete
ChatMessage
(
id
)
ElMessage
({
ElMessage
({
message
:
'删除成功!'
,
message
:
'删除成功!'
,
type
:
'success'
type
:
'success'
...
...
src/views/ai/chat/index/
Cha
tEmpty.vue
→
src/views/ai/chat/index/
components/message/MessageLis
tEmpty.vue
View file @
44f322bb
File moved
src/views/ai/chat/index/MessageLoading.vue
→
src/views/ai/chat/index/
components/message/
MessageLoading.vue
View file @
44f322bb
File moved
src/views/ai/chat/index/
MessageNewChat
.vue
→
src/views/ai/chat/index/
components/message/MessageNewConversation
.vue
View file @
44f322bb
<!--
message
新增对话 -->
<!--
无聊天对话时,在 message 区域,可以
新增对话 -->
<
template
>
<
template
>
<div
class=
"new-chat"
>
<div
class=
"new-chat"
>
<div
class=
"box-center"
>
<div
class=
"box-center"
>
...
@@ -11,14 +11,13 @@
...
@@ -11,14 +11,13 @@
</
template
>
</
template
>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
// 定义钩子
const
emits
=
defineEmits
([
'onNewConversation'
])
const
emits
=
defineEmits
([
'onNewChat'
])
/**
/**
* 新建 c
hat
* 新建 c
onversation 聊天对话
*/
*/
const
handlerNewChat
=
async
()
=>
{
const
handlerNewChat
=
()
=>
{
await
emits
(
'onNewChat
'
)
emits
(
'onNewConversation
'
)
}
}
</
script
>
</
script
>
<
style
scoped
lang=
"scss"
>
<
style
scoped
lang=
"scss"
>
...
...
src/views/ai/chat/index/role/RoleCategoryList.vue
→
src/views/ai/chat/index/
components/
role/RoleCategoryList.vue
View file @
44f322bb
File moved
src/views/ai/chat/index/role/RoleList.vue
→
src/views/ai/chat/index/
components/
role/RoleList.vue
View file @
44f322bb
File moved
src/views/ai/chat/index/role/index.vue
→
src/views/ai/chat/index/
components/
role/index.vue
View file @
44f322bb
...
@@ -68,7 +68,7 @@
...
@@ -68,7 +68,7 @@
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
ref
}
from
'vue'
import
{
ref
}
from
'vue'
import
Header
from
'.
/../components
/Header.vue'
import
Header
from
'.
.
/Header.vue'
import
RoleList
from
'./RoleList.vue'
import
RoleList
from
'./RoleList.vue'
import
ChatRoleForm
from
'@/views/ai/model/chatRole/ChatRoleForm.vue'
import
ChatRoleForm
from
'@/views/ai/model/chatRole/ChatRoleForm.vue'
import
RoleCategoryList
from
'./RoleCategoryList.vue'
import
RoleCategoryList
from
'./RoleCategoryList.vue'
...
...
src/views/ai/chat/index/index.vue
View file @
44f322bb
...
@@ -4,7 +4,7 @@
...
@@ -4,7 +4,7 @@
<ConversationList
<ConversationList
:active-id=
"activeConversationId"
:active-id=
"activeConversationId"
ref=
"conversationListRef"
ref=
"conversationListRef"
@
onConversationCreate=
"handleConversationCreate"
@
onConversationCreate=
"handleConversationCreate
Success
"
@
onConversationClick=
"handleConversationClick"
@
onConversationClick=
"handleConversationClick"
@
onConversationClear=
"handleConversationClear"
@
onConversationClear=
"handleConversationClear"
@
onConversationDelete=
"handlerConversationDelete"
@
onConversationDelete=
"handlerConversationDelete"
...
@@ -27,7 +27,7 @@
...
@@ -27,7 +27,7 @@
</el-button>
</el-button>
<!-- TODO @fan:下面两个 icon,可以使用类似
<Icon
icon=
"ep:question-filled"
/>
替代哈 -->
<!-- TODO @fan:下面两个 icon,可以使用类似
<Icon
icon=
"ep:question-filled"
/>
替代哈 -->
<el-button
size=
"small"
:icon=
"Download"
class=
"btn"
/>
<el-button
size=
"small"
:icon=
"Download"
class=
"btn"
/>
<el-button
size=
"small"
:icon=
"Top"
class=
"btn"
@
click=
"handle
rGoTop
"
/>
<el-button
size=
"small"
:icon=
"Top"
class=
"btn"
@
click=
"handle
GoTopMessage
"
/>
</div>
</div>
</el-header>
</el-header>
...
@@ -37,22 +37,25 @@
...
@@ -37,22 +37,25 @@
<div
class=
"message-container"
>
<div
class=
"message-container"
>
<!-- 情况一:消息加载中 -->
<!-- 情况一:消息加载中 -->
<MessageLoading
v-if=
"activeMessageListLoading"
/>
<MessageLoading
v-if=
"activeMessageListLoading"
/>
<!-- 情况二:未选中对话 -->
<!-- 情况二:无聊天对话时 -->
<MessageNewChat
v-if=
"!activeConversation"
@
on-new-chat=
"handlerNewChat"
/>
<MessageNewConversation
v-if=
"!activeConversation"
@
on-new-conversation=
"handleConversationCreate"
/>
<!-- 情况三:消息列表为空 -->
<!-- 情况三:消息列表为空 -->
<
Cha
tEmpty
<
MessageLis
tEmpty
v-if=
"!activeMessageListLoading && messageList.length === 0 && activeConversation"
v-if=
"!activeMessageListLoading && messageList.length === 0 && activeConversation"
@
on-prompt=
"doSend"
@
on-prompt=
"doSend
Message
"
/>
/>
<!-- 情况四:消息列表不为空 -->
<!-- 情况四:消息列表不为空 -->
<Message
<Message
List
v-if=
"!activeMessageListLoading && messageList.length > 0"
v-if=
"!activeMessageListLoading && messageList.length > 0"
ref=
"messageRef"
ref=
"messageRef"
:conversation=
"activeConversation"
:conversation=
"activeConversation"
:list=
"messageList"
:list=
"messageList"
@
on-delete-success=
"handle
r
MessageDelete"
@
on-delete-success=
"handleMessageDelete"
@
on-edit=
"handle
r
MessageEdit"
@
on-edit=
"handleMessageEdit"
@
on-refresh=
"handle
r
MessageRefresh"
@
on-refresh=
"handleMessageRefresh"
/>
/>
</div>
</div>
</div>
</div>
...
@@ -64,8 +67,8 @@
...
@@ -64,8 +67,8 @@
<textarea
<textarea
class=
"prompt-input"
class=
"prompt-input"
v-model=
"prompt"
v-model=
"prompt"
@
keydown=
"
onSend
"
@
keydown=
"
handleSendByKeydown
"
@
input=
"
on
PromptInput"
@
input=
"
handle
PromptInput"
@
compositionstart=
"onCompositionstart"
@
compositionstart=
"onCompositionstart"
@
compositionend=
"onCompositionend"
@
compositionend=
"onCompositionend"
placeholder=
"问我任何问题...(Shift+Enter 换行,按下 Enter 发送)"
placeholder=
"问我任何问题...(Shift+Enter 换行,按下 Enter 发送)"
...
@@ -78,7 +81,7 @@
...
@@ -78,7 +81,7 @@
<el-button
<el-button
type=
"primary"
type=
"primary"
size=
"default"
size=
"default"
@
click=
"
onSendBt
n"
@
click=
"
handleSendByButto
n"
:loading=
"conversationInProgress"
:loading=
"conversationInProgress"
v-if=
"conversationInProgress == false"
v-if=
"conversationInProgress == false"
>
>
...
@@ -110,10 +113,10 @@ import { ChatMessageApi, ChatMessageVO } from '@/api/ai/chat/message'
...
@@ -110,10 +113,10 @@ import { ChatMessageApi, ChatMessageVO } from '@/api/ai/chat/message'
import
{
ChatConversationApi
,
ChatConversationVO
}
from
'@/api/ai/chat/conversation'
import
{
ChatConversationApi
,
ChatConversationVO
}
from
'@/api/ai/chat/conversation'
import
ConversationList
from
'./components/conversation/ConversationList.vue'
import
ConversationList
from
'./components/conversation/ConversationList.vue'
import
ConversationUpdateForm
from
'./components/conversation/ConversationUpdateForm.vue'
import
ConversationUpdateForm
from
'./components/conversation/ConversationUpdateForm.vue'
import
Message
from
'./Message
.vue'
import
Message
List
from
'./components/message/MessageList
.vue'
import
ChatEmpty
from
'./Cha
tEmpty.vue'
import
MessageListEmpty
from
'./components/message/MessageLis
tEmpty.vue'
import
MessageLoading
from
'./MessageLoading.vue'
import
MessageLoading
from
'./
components/message/
MessageLoading.vue'
import
MessageNewC
hat
from
'./MessageNewChat
.vue'
import
MessageNewC
onversation
from
'./components/message/MessageNewConversation
.vue'
import
{
Download
,
Top
}
from
'@element-plus/icons-vue'
import
{
Download
,
Top
}
from
'@element-plus/icons-vue'
const
route
=
useRoute
()
// 路由
const
route
=
useRoute
()
// 路由
...
@@ -141,8 +144,8 @@ const inputTimeout = ref<any>() // 处理输入中回车的定时器
...
@@ -141,8 +144,8 @@ const inputTimeout = ref<any>() // 处理输入中回车的定时器
const
prompt
=
ref
<
string
>
()
// prompt
const
prompt
=
ref
<
string
>
()
// prompt
const
enableContext
=
ref
<
boolean
>
(
true
)
// 是否开启上下文
const
enableContext
=
ref
<
boolean
>
(
true
)
// 是否开启上下文
// 接收 Stream 消息
// 接收 Stream 消息
const
f
ullText
=
ref
(
''
)
const
receiveMessageF
ullText
=
ref
(
''
)
const
d
isplayedText
=
ref
(
''
)
const
receiveMessageD
isplayedText
=
ref
(
''
)
// =========== 【聊天对话】相关 ===========
// =========== 【聊天对话】相关 ===========
...
@@ -216,6 +219,11 @@ const handleConversationUpdateSuccess = async () => {
...
@@ -216,6 +219,11 @@ const handleConversationUpdateSuccess = async () => {
/** 处理聊天对话的创建成功 */
/** 处理聊天对话的创建成功 */
const
handleConversationCreate
=
async
()
=>
{
const
handleConversationCreate
=
async
()
=>
{
// 创建对话
await
conversationListRef
.
value
.
createConversation
()
}
/** 处理聊天对话的创建成功 */
const
handleConversationCreateSuccess
=
async
()
=>
{
// 创建新的对话,清空输入框
// 创建新的对话,清空输入框
prompt
.
value
=
''
prompt
.
value
=
''
}
}
...
@@ -240,7 +248,7 @@ const getMessageList = async () => {
...
@@ -240,7 +248,7 @@ const getMessageList = async () => {
// 滚动到最下面
// 滚动到最下面
await
nextTick
()
await
nextTick
()
scrollToBottom
()
await
scrollToBottom
()
}
finally
{
}
finally
{
// time 定时器,如果加载速度很快,就不进入加载中
// time 定时器,如果加载速度很快,就不进入加载中
if
(
activeMessageListLoadingTimer
.
value
)
{
if
(
activeMessageListLoadingTimer
.
value
)
{
...
@@ -261,7 +269,6 @@ const messageList = computed(() => {
...
@@ -261,7 +269,6 @@ const messageList = computed(() => {
return
activeMessageList
.
value
return
activeMessageList
.
value
}
}
// 没有消息时,如果有 systemMessage 则展示它
// 没有消息时,如果有 systemMessage 则展示它
// TODO add by 芋艿:这个消息下面,不能有复制、删除按钮
if
(
activeConversation
.
value
?.
systemMessage
)
{
if
(
activeConversation
.
value
?.
systemMessage
)
{
return
[
return
[
{
{
...
@@ -274,118 +281,40 @@ const messageList = computed(() => {
...
@@ -274,118 +281,40 @@ const messageList = computed(() => {
return
[]
return
[]
})
})
// =========== 自提滚动效果
/** 处理删除 message 消息 */
const
handleMessageDelete
=
()
=>
{
// TODO @fan:这个方法,要不加个方法注释
if
(
conversationInProgress
.
value
)
{
const
textRoll
=
async
()
=>
{
message
.
alert
(
'回答中,不能删除!'
)
let
index
=
0
return
try
{
// 只能执行一次
if
(
textRoleRunning
.
value
)
{
return
}
// 设置状态
textRoleRunning
.
value
=
true
displayedText
.
value
=
''
const
task
=
async
()
=>
{
// 调整速度
const
diff
=
(
fullText
.
value
.
length
-
displayedText
.
value
.
length
)
/
10
if
(
diff
>
5
)
{
textSpeed
.
value
=
10
}
else
if
(
diff
>
2
)
{
textSpeed
.
value
=
30
}
else
if
(
diff
>
1.5
)
{
textSpeed
.
value
=
50
}
else
{
textSpeed
.
value
=
100
}
// 对话结束,就按 30 的速度
if
(
!
conversationInProgress
.
value
)
{
textSpeed
.
value
=
10
}
// console.log('index
<
fullText
.
value
.
length
'
,
index
<
fullText
.
value
.
length
,
conversationInProgress
.
value
)
if
(
index
<
fullText
.
value
.
length
)
{
displayedText
.
value
+=
fullText
.
value
[
index
]
index
++
// 更新 message
const
lastMessage
=
activeMessageList
.
value
[
activeMessageList
.
value
.
length
-
1
]
lastMessage
.
content
=
displayedText
.
value
// TODO @fan:ist.value?,还是 ist.value.length 哈?
activeMessageList
.
value
[
activeMessageList
.
value
-
1
]
=
lastMessage
// 滚动到住下面
await
scrollToBottom
()
// 重新设置任务
timer
=
setTimeout
(
task
,
textSpeed
.
value
)
}
else
{
// 不是对话中可以结束
if
(
!
conversationInProgress
.
value
)
{
textRoleRunning
.
value
=
false
clearTimeout
(
timer
)
console
.
log
(
'字体滚动退出!'
)
}
else
{
// 重新设置任务
timer
=
setTimeout
(
task
,
textSpeed
.
value
)
}
}
}
let
timer
=
setTimeout
(
task
,
textSpeed
.
value
)
}
finally
{
}
}
// 刷新 message 列表
getMessageList
()
}
}
// ============ 处理对话滚动 ==============
/** 处理 message 清空 */
const
handlerMessageClear
=
async
()
=>
{
function
scrollToBottom
(
isIgnore
?:
boolean
)
{
if
(
!
activeConversationId
.
value
)
{
// isIgnore = isIgnore !== null ? isIgnore : false
return
nextTick
(()
=>
{
}
if
(
messageRef
.
value
)
{
try
{
messageRef
.
value
.
scrollToBottom
(
isIgnore
)
// 确认提示
}
await
message
.
delConfirm
(
'确认清空对话消息?'
)
})
// 清空对话
await
ChatMessageApi
.
deleteByConversationId
(
activeConversationId
.
value
)
// 刷新 message 列表
activeMessageList
.
value
=
[]
}
catch
{}
}
}
// ============= 处理聊天输入回车发送 =============
/** 回到 message 列表的顶部 */
const
handleGoTopMessage
=
()
=>
{
// TODO @fan:是不是可以通过 @keydown.enter、@keydown.shift.enter 来实现,回车发送、shift+回车换行;主要看看,是不是可以简化 isComposing 相关的逻辑
messageRef
.
value
.
handlerGoTop
()
const
onCompositionstart
=
()
=>
{
isComposing
.
value
=
true
}
}
const
onCompositionend
=
()
=>
{
// =========== 【发送消息】相关 ===========
// console.log('输入结束...')
setTimeout
(()
=>
{
isComposing
.
value
=
false
},
200
)
}
const
onPromptInput
=
(
event
)
=>
{
/** 处理来自 keydown 的发送消息 */
// 非输入法 输入设置为 true
const
handleSendByKeydown
=
async
(
event
)
=>
{
if
(
!
isComposing
.
value
)
{
// 回车 event data 是 null
if
(
event
.
data
==
null
)
{
return
}
isComposing
.
value
=
true
}
// 清理定时器
if
(
inputTimeout
.
value
)
{
clearTimeout
(
inputTimeout
.
value
)
}
// 重置定时器
inputTimeout
.
value
=
setTimeout
(()
=>
{
isComposing
.
value
=
false
},
400
)
}
// ============== 对话消息相关 =================
/**
* 发送消息
*/
const
onSend
=
async
(
event
)
=>
{
// 判断用户是否在输入
// 判断用户是否在输入
if
(
isComposing
.
value
)
{
if
(
isComposing
.
value
)
{
return
return
...
@@ -402,53 +331,79 @@ const onSend = async (event) => {
...
@@ -402,53 +331,79 @@ const onSend = async (event) => {
event
.
preventDefault
()
// 防止默认的换行行为
event
.
preventDefault
()
// 防止默认的换行行为
}
else
{
}
else
{
// 发送消息
// 发送消息
await
doSend
(
content
)
await
doSend
Message
(
content
)
event
.
preventDefault
()
// 防止默认的提交行为
event
.
preventDefault
()
// 防止默认的提交行为
}
}
}
}
}
}
const
onSendBtn
=
async
()
=>
{
/** 处理来自【发送】按钮的发送消息 */
await
doSend
(
prompt
.
value
?.
trim
()
as
string
)
const
handleSendByButton
=
()
=>
{
doSendMessage
(
prompt
.
value
?.
trim
()
as
string
)
}
}
const
doSend
=
async
(
content
:
string
)
=>
{
/** 处理 prompt 输入变化 */
if
(
content
.
length
<
2
)
{
const
handlePromptInput
=
(
event
)
=>
{
// TODO @fan:这个 message.error(`上传文件大小不能超过${props.fileSize}MB!`) 可以替代,这种形式
// 非输入法 输入设置为 true
ElMessage
({
if
(
!
isComposing
.
value
)
{
message
:
'请输入内容!'
,
// 回车 event data 是 null
type
:
'error'
if
(
event
.
data
==
null
)
{
})
return
}
isComposing
.
value
=
true
}
// 清理定时器
if
(
inputTimeout
.
value
)
{
clearTimeout
(
inputTimeout
.
value
)
}
// 重置定时器
inputTimeout
.
value
=
setTimeout
(()
=>
{
isComposing
.
value
=
false
},
400
)
}
// TODO @fan:是不是可以通过 @keydown.enter、@keydown.shift.enter 来实现,回车发送、shift+回车换行;主要看看,是不是可以简化 isComposing 相关的逻辑
const
onCompositionstart
=
()
=>
{
isComposing
.
value
=
true
}
const
onCompositionend
=
()
=>
{
// console.log('输入结束...')
setTimeout
(()
=>
{
isComposing
.
value
=
false
},
200
)
}
/** 真正执行【发送】消息操作 */
const
doSendMessage
=
async
(
content
:
string
)
=>
{
// 校验
if
(
content
.
length
<
1
)
{
message
.
error
(
'发送失败,原因:内容为空!'
)
return
return
}
}
// TODO @fan:这个 message.error(`上传文件大小不能超过${props.fileSize}MB!`) 可以替代,这种形式
if
(
activeConversationId
.
value
==
null
)
{
if
(
activeConversationId
.
value
==
null
)
{
ElMessage
({
message
.
error
(
'还没创建对话,不能发送!'
)
message
:
'还没创建对话,不能发送!'
,
type
:
'error'
})
return
return
}
}
// 清空输入框
// 清空输入框
prompt
.
value
=
''
prompt
.
value
=
''
//
TODO @fan:idea 这里会报类型错误,是不是可以解决下哈
//
执行发送
const
userMessage
=
{
await
doSendMessageStream
(
{
conversationId
:
activeConversationId
.
value
,
conversationId
:
activeConversationId
.
value
,
content
:
content
content
:
content
}
as
ChatMessageVO
}
as
ChatMessageVO
)
// stream
await
doSendStream
(
userMessage
)
}
}
const
doSendStream
=
async
(
userMessage
:
ChatMessageVO
)
=>
{
// TODO @fan:= = 不知道哪里被改动了。点击【发送】后,不会跳转到消息最底部了。。
/** 真正执行【发送】消息操作 */
const
doSendMessageStream
=
async
(
userMessage
:
ChatMessageVO
)
=>
{
// 创建 AbortController 实例,以便中止请求
// 创建 AbortController 实例,以便中止请求
conversationInAbortController
.
value
=
new
AbortController
()
conversationInAbortController
.
value
=
new
AbortController
()
// 标记对话进行中
// 标记对话进行中
conversationInProgress
.
value
=
true
conversationInProgress
.
value
=
true
// 设置为空
// 设置为空
fullText
.
value
=
''
receiveMessageFullText
.
value
=
''
try
{
try
{
// 先添加两个假数据,等 stream 返回再替换
//
1.1
先添加两个假数据,等 stream 返回再替换
activeMessageList
.
value
.
push
({
activeMessageList
.
value
.
push
({
id
:
-
1
,
id
:
-
1
,
conversationId
:
activeConversationId
.
value
,
conversationId
:
activeConversationId
.
value
,
...
@@ -459,26 +414,25 @@ const doSendStream = async (userMessage: ChatMessageVO) => {
...
@@ -459,26 +414,25 @@ const doSendStream = async (userMessage: ChatMessageVO) => {
activeMessageList
.
value
.
push
({
activeMessageList
.
value
.
push
({
id
:
-
2
,
id
:
-
2
,
conversationId
:
activeConversationId
.
value
,
conversationId
:
activeConversationId
.
value
,
type
:
'
system
'
,
type
:
'
assistant
'
,
content
:
'思考中...'
,
content
:
'思考中...'
,
createTime
:
new
Date
()
createTime
:
new
Date
()
}
as
ChatMessageVO
)
}
as
ChatMessageVO
)
// 滚动到最下面
// 1.2 滚动到最下面
// TODO @fan:可以 await nextTick();然后同步调用 scrollToBottom()
nextTick
(
async
()
=>
{
nextTick
(
async
()
=>
{
await
scrollToBottom
()
await
scrollToBottom
()
// 底部
})
})
// 开始滚动
//
1.3
开始滚动
textRoll
()
textRoll
()
// 发送 event stream
let
isFirstMessage
=
true
// TODO @fan:isFirstChunk 会更精准
// 2. 发送 event stream
ChatMessageApi
.
sendStream
(
let
isFirstChunk
=
true
// 是否是第一个 chunk 消息段
userMessage
.
conversationId
,
// TODO 芋艿:这里可能要在优化;
await
ChatMessageApi
.
sendChatMessageStream
(
userMessage
.
conversationId
,
userMessage
.
content
,
userMessage
.
content
,
conversationInAbortController
.
value
,
conversationInAbortController
.
value
,
enableContext
.
value
,
enableContext
.
value
,
async
(
res
)
=>
{
async
(
res
)
=>
{
console
.
log
(
'res'
,
res
)
const
{
code
,
data
,
msg
}
=
JSON
.
parse
(
res
.
data
)
const
{
code
,
data
,
msg
}
=
JSON
.
parse
(
res
.
data
)
if
(
code
!==
0
)
{
if
(
code
!==
0
)
{
message
.
alert
(
`对话异常!
${
msg
}
`
)
message
.
alert
(
`对话异常!
${
msg
}
`
)
...
@@ -490,9 +444,9 @@ const doSendStream = async (userMessage: ChatMessageVO) => {
...
@@ -490,9 +444,9 @@ const doSendStream = async (userMessage: ChatMessageVO) => {
return
return
}
}
// 首次返回需要添加一个 message 到页面,后面的都是更新
// 首次返回需要添加一个 message 到页面,后面的都是更新
if
(
isFirst
Message
)
{
if
(
isFirst
Chunk
)
{
isFirst
Message
=
false
isFirst
Chunk
=
false
// 弹出两个
假数据
// 弹出两个假数据
activeMessageList
.
value
.
pop
()
activeMessageList
.
value
.
pop
()
activeMessageList
.
value
.
pop
()
activeMessageList
.
value
.
pop
()
// 更新返回的数据
// 更新返回的数据
...
@@ -500,32 +454,23 @@ const doSendStream = async (userMessage: ChatMessageVO) => {
...
@@ -500,32 +454,23 @@ const doSendStream = async (userMessage: ChatMessageVO) => {
activeMessageList
.
value
.
push
(
data
.
receive
)
activeMessageList
.
value
.
push
(
data
.
receive
)
}
}
// debugger
// debugger
fullText
.
value
=
f
ullText
.
value
+
data
.
receive
.
content
receiveMessageFullText
.
value
=
receiveMessageF
ullText
.
value
+
data
.
receive
.
content
// 滚动到最下面
// 滚动到最下面
await
scrollToBottom
()
await
scrollToBottom
()
},
},
(
error
)
=>
{
(
error
)
=>
{
message
.
alert
(
`对话异常!
${
error
}
`
)
message
.
alert
(
`对话异常!
${
error
}
`
)
// TODO @fan:是不是可以复用 stopStream 方法
stopStream
()
// 标记对话结束
conversationInProgress
.
value
=
false
// 结束 stream 对话
conversationInAbortController
.
value
.
abort
()
},
},
()
=>
{
()
=>
{
// TODO @fan:是不是可以复用 stopStream 方法
stopStream
()
// 标记对话结束
conversationInProgress
.
value
=
false
// 结束 stream 对话
conversationInAbortController
.
value
.
abort
()
}
}
)
)
}
finally
{
}
catch
{}
}
}
}
/** 停止 stream 流式调用 */
const
stopStream
=
async
()
=>
{
const
stopStream
=
async
()
=>
{
console
.
log
(
'stopStream...'
)
// tip:如果 stream 进行中的 message,就需要调用 controller 结束
// tip:如果 stream 进行中的 message,就需要调用 controller 结束
if
(
conversationInAbortController
.
value
)
{
if
(
conversationInAbortController
.
value
)
{
conversationInAbortController
.
value
.
abort
()
conversationInAbortController
.
value
.
abort
()
...
@@ -534,75 +479,90 @@ const stopStream = async () => {
...
@@ -534,75 +479,90 @@ const stopStream = async () => {
conversationInProgress
.
value
=
false
conversationInProgress
.
value
=
false
}
}
// ============== message 数据 =================
/** 编辑 message:设置为 prompt,可以再次编辑 */
const
handleMessageEdit
=
(
message
:
ChatMessageVO
)
=>
{
/**
prompt
.
value
=
message
.
content
* 对话 - 新建
*/
// TODO @fan:应该是 handleXXX,handler 是名词哈
const
handlerNewChat
=
async
()
=>
{
// 创建对话
await
conversationListRef
.
value
.
createConversation
()
}
}
/**
/** 刷新 message:基于指定消息,再次发起对话 */
* 删除 message
const
handleMessageRefresh
=
(
message
:
ChatMessageVO
)
=>
{
*/
doSendMessage
(
message
.
content
)
const
handlerMessageDelete
=
async
()
=>
{
if
(
conversationInProgress
.
value
)
{
message
.
alert
(
'回答中,不能删除!'
)
return
}
// 刷新 message
await
getMessageList
()
}
}
/**
// ============== 【消息滚动】相关 =============
* 编辑 message:设置为 prompt,可以再次编辑
*/
const
handlerMessageEdit
=
async
(
message
:
ChatMessageVO
)
=>
{
prompt
.
value
=
message
.
content
}
/**
/** 滚动到 message 底部 */
* 刷新 message:基于指定消息,再次发起对话
const
scrollToBottom
=
async
(
isIgnore
?:
boolean
)
=>
{
*/
await
nextTick
()
const
handlerMessageRefresh
=
async
(
message
:
ChatMessageVO
)
=>
{
if
(
messageRef
.
value
)
{
await
doSend
(
message
.
content
)
messageRef
.
value
.
scrollToBottom
(
isIgnore
)
}
}
}
/**
/** 自提滚动效果 */
* 回到顶部
const
textRoll
=
async
()
=>
{
*/
let
index
=
0
const
handlerGoTop
=
async
()
=>
{
try
{
await
messageRef
.
value
.
handlerGoTop
()
// 只能执行一次
}
if
(
textRoleRunning
.
value
)
{
return
}
// 设置状态
textRoleRunning
.
value
=
true
receiveMessageDisplayedText
.
value
=
''
const
task
=
async
()
=>
{
// 调整速度
const
diff
=
(
receiveMessageFullText
.
value
.
length
-
receiveMessageDisplayedText
.
value
.
length
)
/
10
if
(
diff
>
5
)
{
textSpeed
.
value
=
10
}
else
if
(
diff
>
2
)
{
textSpeed
.
value
=
30
}
else
if
(
diff
>
1.5
)
{
textSpeed
.
value
=
50
}
else
{
textSpeed
.
value
=
100
}
// 对话结束,就按 30 的速度
if
(
!
conversationInProgress
.
value
)
{
textSpeed
.
value
=
10
}
/**
if
(
index
<
receiveMessageFullText
.
value
.
length
)
{
* message 清除
receiveMessageDisplayedText
.
value
+=
receiveMessageFullText
.
value
[
index
]
*/
index
++
const
handlerMessageClear
=
async
()
=>
{
if
(
!
activeConversationId
.
value
)
{
// 更新 message
return
const
lastMessage
=
activeMessageList
.
value
[
activeMessageList
.
value
.
length
-
1
]
}
lastMessage
.
content
=
receiveMessageDisplayedText
.
value
// TODO @fan:需要 try catch 下,不然点击取消会报异常
// 滚动到住下面
// 确认提示
await
scrollToBottom
()
await
message
.
delConfirm
(
'确认清空对话消息?'
)
// 重新设置任务
// 清空对话
timer
=
setTimeout
(
task
,
textSpeed
.
value
)
await
ChatMessageApi
.
deleteByConversationId
(
activeConversationId
.
value
)
}
else
{
// TODO @fan:是不是直接置空就好啦;
// 不是对话中可以结束
// 刷新 message 列表
if
(
!
conversationInProgress
.
value
)
{
await
getMessageList
()
textRoleRunning
.
value
=
false
clearTimeout
(
timer
)
}
else
{
// 重新设置任务
timer
=
setTimeout
(
task
,
textSpeed
.
value
)
}
}
}
let
timer
=
setTimeout
(
task
,
textSpeed
.
value
)
}
catch
{}
}
}
/** 初始化 **/
/** 初始化 **/
onMounted
(
async
()
=>
{
onMounted
(
async
()
=>
{
//
设置当前对话 TODO 角色仓库过来的,自带 conversationId 需要
选中
//
如果有 conversationId 参数,则默认
选中
if
(
route
.
query
.
conversationId
)
{
if
(
route
.
query
.
conversationId
)
{
const
id
=
route
.
query
.
conversationId
as
unknown
as
number
const
id
=
route
.
query
.
conversationId
as
unknown
as
number
activeConversationId
.
value
=
id
activeConversationId
.
value
=
id
await
getConversation
(
id
)
await
getConversation
(
id
)
}
}
// 获取列表数据
// 获取列表数据
activeMessageListLoading
.
value
=
true
activeMessageListLoading
.
value
=
true
await
getMessageList
()
await
getMessageList
()
...
...
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