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
051a610e
authored
May 14, 2024
by
YunaiV
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【新增】AI:格式化 chat 对话的代码
parent
3d15eadb
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
139 additions
and
113 deletions
+139
-113
src/api/ai/chat/conversation/index.ts
+24
-10
src/api/ai/chat/message/index.ts
+15
-14
src/views/ai/chat/index.vue
+100
-89
No files found.
src/api/ai/chat/conversation/index.ts
View file @
051a610e
import
request
from
'@/config/axios'
import
request
from
'@/config/axios'
//
聊天
VO
//
AI 聊天会话
VO
export
interface
ChatConversationVO
{
export
interface
ChatConversationVO
{
id
:
string
// 会话
编号
id
:
number
// ID
编号
userId
:
string
// 用户编号
userId
:
number
// 用户编号
title
:
string
// 会话标题
title
:
string
// 会话标题
pinned
:
string
// 是否置顶
pinned
:
boolean
// 是否置顶
roleId
:
string
// 角色编号
roleId
:
number
// 角色编号
model
:
number
// 模型标志
modelId
:
number
// 模型编号
modelId
:
number
// 模型编号
temperature
:
string
// 温度参数
model
:
string
// 模型标志
maxTokens
:
string
// 单条回复的最大 Token 数量
temperature
:
number
// 温度参数
maxContexts
:
string
// 上下文的最大 Message 数量
maxTokens
:
number
// 单条回复的最大 Token 数量
maxContexts
:
number
// 上下文的最大 Message 数量
// 额外字段
roleAvatar
?:
string
// 角色头像
modelMaxTokens
?:
string
// 模型的单条回复的最大 Token 数量
modelMaxContexts
?:
string
// 模型的上下文的最大 Message 数量
}
}
export
interface
ChatConversationUpdateVO
{
export
interface
ChatConversationUpdateVO
{
...
@@ -24,7 +28,7 @@ export interface ChatConversationUpdateVO {
...
@@ -24,7 +28,7 @@ export interface ChatConversationUpdateVO {
maxContexts
:
string
// 上下文的最大 Message 数量
maxContexts
:
string
// 上下文的最大 Message 数量
}
}
// AI
chat 聊天
// AI
聊天会话 API
export
const
ChatConversationApi
=
{
export
const
ChatConversationApi
=
{
// 获取 Conversation
// 获取 Conversation
get
:
async
(
id
:
string
)
=>
{
get
:
async
(
id
:
string
)
=>
{
...
@@ -34,4 +38,14 @@ export const ChatConversationApi = {
...
@@ -34,4 +38,14 @@ export const ChatConversationApi = {
update
:
async
(
data
:
ChatConversationUpdateVO
)
=>
{
update
:
async
(
data
:
ChatConversationUpdateVO
)
=>
{
return
await
request
.
put
({
url
:
`/ai/chat/conversation/update`
,
data
})
return
await
request
.
put
({
url
:
`/ai/chat/conversation/update`
,
data
})
},
},
// 新增【我的】聊天会话
createChatConversationMy
:
async
(
data
?:
ChatConversationVO
)
=>
{
return
await
request
.
post
({
url
:
`/ai/chat/conversation/create-my`
,
data
})
},
// 获得【我的】聊天会话列表
getChatConversationMyList
:
async
()
=>
{
return
await
request
.
get
({
url
:
`/ai/chat/conversation/my-list`
})
}
}
}
src/api/ai/chat/message/index.ts
View file @
051a610e
import
request
from
'@/config/axios'
import
request
from
'@/config/axios'
import
{
fetchEventSource
}
from
'@microsoft/fetch-event-source'
;
import
{
fetchEventSource
}
from
'@microsoft/fetch-event-source'
import
{
getAccessToken
}
from
'@/utils/auth'
import
{
getAccessToken
}
from
'@/utils/auth'
import
{
config
}
from
'@/config/axios/config'
import
{
config
}
from
'@/config/axios/config'
// 聊天VO
// 聊天VO
export
interface
ChatMessageVO
{
export
interface
ChatMessageVO
{
...
@@ -24,26 +24,28 @@ export interface ChatMessageSendVO {
...
@@ -24,26 +24,28 @@ export interface ChatMessageSendVO {
// AI chat 聊天
// AI chat 聊天
export
const
ChatMessageApi
=
{
export
const
ChatMessageApi
=
{
// 消息列表
// 消息列表
messageList
:
async
(
conversationId
:
string
)
=>
{
messageList
:
async
(
conversationId
:
string
)
=>
{
return
await
request
.
get
({
url
:
`/ai/chat/message/list-by-conversation-id?conversationId=
${
conversationId
}
`
})
return
await
request
.
get
({
url
:
`/ai/chat/message/list-by-conversation-id?conversationId=
${
conversationId
}
`
})
},
},
// 发送 add 消息
// 发送 add 消息
add
:
async
(
data
:
ChatMessageSendVO
)
=>
{
add
:
async
(
data
:
ChatMessageSendVO
)
=>
{
return
await
request
.
post
({
url
:
`/ai/chat/message/add`
,
data
})
return
await
request
.
post
({
url
:
`/ai/chat/message/add`
,
data
})
},
},
// 发送 send 消息
// 发送 send 消息
send
:
async
(
data
:
ChatMessageSendVO
)
=>
{
send
:
async
(
data
:
ChatMessageSendVO
)
=>
{
return
await
request
.
post
({
url
:
`/ai/chat/message/send`
,
data
})
return
await
request
.
post
({
url
:
`/ai/chat/message/send`
,
data
})
},
},
// 发送 send stream 消息
// 发送 send stream 消息
// TODO axios 可以么? https://apifox.com/apiskills/how-to-create-axios-stream/
sendStream
:
async
(
id
:
string
,
ctrl
,
onMessage
,
onError
,
onClose
)
=>
{
sendStream
:
async
(
id
:
string
,
ctrl
,
onMessage
,
onError
,
onClose
)
=>
{
const
token
=
getAccessToken
()
const
token
=
getAccessToken
()
return
fetchEventSource
(
`
${
config
.
base_url
}
/ai/chat/message/send-stream`
,
{
return
fetchEventSource
(
`
${
config
.
base_url
}
/ai/chat/message/send-stream`
,
{
method
:
'post'
,
method
:
'post'
,
headers
:
{
headers
:
{
'Content-Type'
:
'application/json'
,
'Content-Type'
:
'application/json'
,
...
@@ -51,18 +53,17 @@ export const ChatMessageApi = {
...
@@ -51,18 +53,17 @@ export const ChatMessageApi = {
},
},
openWhenHidden
:
true
,
openWhenHidden
:
true
,
body
:
JSON
.
stringify
({
body
:
JSON
.
stringify
({
id
:
id
,
id
:
id
}),
}),
onmessage
:
onMessage
,
onmessage
:
onMessage
,
onerror
:
onError
,
onerror
:
onError
,
onclose
:
onClose
,
onclose
:
onClose
,
signal
:
ctrl
.
signal
,
signal
:
ctrl
.
signal
})
;
})
},
},
// 发送 send 消息
// 发送 send 消息
delete
:
async
(
id
:
string
)
=>
{
delete
:
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
}
`
})
},
}
}
}
src/views/ai/chat/index.vue
View file @
051a610e
...
@@ -5,7 +5,7 @@
...
@@ -5,7 +5,7 @@
<div>
<div>
<!-- 左顶部:新建对话 -->
<!-- 左顶部:新建对话 -->
<el-button
class=
"w-1/1 btn-new-conversation"
type=
"primary"
>
<el-button
class=
"w-1/1 btn-new-conversation"
type=
"primary"
>
<Icon
icon=
"ep:plus"
class=
"mr-5px"
/>
<Icon
icon=
"ep:plus"
class=
"mr-5px"
/>
新建对话
新建对话
</el-button>
</el-button>
<!-- 左顶部:搜索对话 -->
<!-- 左顶部:搜索对话 -->
...
@@ -17,7 +17,7 @@
...
@@ -17,7 +17,7 @@
@
keyup=
"searchConversation"
@
keyup=
"searchConversation"
>
>
<template
#
prefix
>
<template
#
prefix
>
<Icon
icon=
"ep:search"
/>
<Icon
icon=
"ep:search"
/>
</
template
>
</
template
>
</el-input>
</el-input>
<!-- 左中间:对话列表 -->
<!-- 左中间:对话列表 -->
...
@@ -32,15 +32,15 @@
...
@@ -32,15 +32,15 @@
@
click=
"changeConversation(conversation)"
@
click=
"changeConversation(conversation)"
>
>
<div
class=
"title-wrapper"
>
<div
class=
"title-wrapper"
>
<img
class=
"avatar"
:src=
"conversation.avatar"
/>
<img
class=
"avatar"
:src=
"conversation.avatar"
/>
<span
class=
"title"
>
{{ conversation.title }}
</span>
<span
class=
"title"
>
{{ conversation.title }}
</span>
</div>
</div>
<div
class=
"button-wrapper"
>
<div
class=
"button-wrapper"
>
<el-icon
title=
"编辑"
@
click=
"updateConversationTitle(conversation)"
>
<el-icon
title=
"编辑"
@
click=
"updateConversationTitle(conversation)"
>
<Icon
icon=
"ep:edit"
/>
<Icon
icon=
"ep:edit"
/>
</el-icon>
</el-icon>
<el-icon
title=
"删除会话"
@
click=
"deleteConversationTitle(conversation)"
>
<el-icon
title=
"删除会话"
@
click=
"deleteConversationTitle(conversation)"
>
<Icon
icon=
"ep:delete"
/>
<Icon
icon=
"ep:delete"
/>
</el-icon>
</el-icon>
</div>
</div>
</div>
</div>
...
@@ -50,11 +50,11 @@
...
@@ -50,11 +50,11 @@
<!-- 左底部:工具栏 -->
<!-- 左底部:工具栏 -->
<div
class=
"tool-box"
>
<div
class=
"tool-box"
>
<div>
<div>
<Icon
icon=
"ep:user"
/>
<Icon
icon=
"ep:user"
/>
<el-text
size=
"small"
>
角色仓库
</el-text>
<el-text
size=
"small"
>
角色仓库
</el-text>
</div>
</div>
<div>
<div>
<Icon
icon=
"ep:delete"
/>
<Icon
icon=
"ep:delete"
/>
<el-text
size=
"small"
>
清空未置顶对话
</el-text>
<el-text
size=
"small"
>
清空未置顶对话
</el-text>
</div>
</div>
</div>
</div>
...
@@ -67,24 +67,25 @@
...
@@ -67,24 +67,25 @@
{{ useConversation?.title }}
{{ useConversation?.title }}
</div>
</div>
<div>
<div>
<el-dropdown
style=
"margin-right: 12px
;"
@
command=
"modalClick"
>
<el-dropdown
style=
"margin-right: 12px
"
@
command=
"modalClick"
>
<el-button
type=
"primary"
>
<el-button
type=
"primary"
>
<span
v-html=
"useModal?.name"
></span>
<Icon
icon=
"ep:setting"
style=
"margin-left: 10px;"
/>
<span
v-html=
"useModal?.name"
></span>
<Icon
icon=
"ep:setting"
style=
"margin-left: 10px"
/>
</el-button>
</el-button>
<
template
#
dropdown
>
<
template
#
dropdown
>
<el-dropdown-menu
v-for=
"(item, index) in modalList"
:key=
"index"
>
<el-dropdown-menu
v-for=
"(item, index) in modalList"
:key=
"index"
>
<el-dropdown-item
:command=
"item"
>
{{
item
.
name
}}
</el-dropdown-item>
<el-dropdown-item
:command=
"item"
>
{{
item
.
name
}}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown-menu>
</
template
>
</
template
>
</el-dropdown>
</el-dropdown>
<el-button>
<el-button>
<Icon
icon=
"ep:user"
/>
<Icon
icon=
"ep:user"
/>
</el-button>
</el-button>
<el-button>
<el-button>
<Icon
icon=
"ep:download"
/>
<Icon
icon=
"ep:download"
/>
</el-button>
</el-button>
<el-button>
<el-button>
<Icon
icon=
"ep:arrow-up"
/>
<Icon
icon=
"ep:arrow-up"
/>
</el-button>
</el-button>
</div>
</div>
</el-header>
</el-header>
...
@@ -110,11 +111,11 @@
...
@@ -110,11 +111,11 @@
</div>
</div>
<div
class=
"left-btns"
>
<div
class=
"left-btns"
>
<div
class=
"btn-cus"
@
click=
"noCopy(item.content)"
>
<div
class=
"btn-cus"
@
click=
"noCopy(item.content)"
>
<img
class=
"btn-image"
src=
"../../../assets/ai/copy.svg"
/>
<img
class=
"btn-image"
src=
"../../../assets/ai/copy.svg"
/>
<el-text
class=
"btn-cus-text"
>
复制
</el-text>
<el-text
class=
"btn-cus-text"
>
复制
</el-text>
</div>
</div>
<div
class=
"btn-cus"
style=
"margin-left: 20px
;
"
@
click=
"onDelete(item.id)"
>
<div
class=
"btn-cus"
style=
"margin-left: 20px"
@
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-text
class=
"btn-cus-text"
>
删除
</el-text>
<el-text
class=
"btn-cus-text"
>
删除
</el-text>
</div>
</div>
</div>
</div>
...
@@ -136,35 +137,47 @@
...
@@ -136,35 +137,47 @@
</div>
</div>
<div
class=
"right-btns"
>
<div
class=
"right-btns"
>
<div
class=
"btn-cus"
@
click=
"noCopy(item.content)"
>
<div
class=
"btn-cus"
@
click=
"noCopy(item.content)"
>
<img
class=
"btn-image"
src=
"@/assets/ai/copy.svg"
/>
<img
class=
"btn-image"
src=
"@/assets/ai/copy.svg"
/>
<el-text
class=
"btn-cus-text"
>
复制
</el-text>
<el-text
class=
"btn-cus-text"
>
复制
</el-text>
</div>
</div>
<div
class=
"btn-cus"
style=
"margin-left: 20px
;
"
@
click=
"onDelete(item.id)"
>
<div
class=
"btn-cus"
style=
"margin-left: 20px"
@
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-text
class=
"btn-cus-text"
>
删除
</el-text>
<el-text
class=
"btn-cus-text"
>
删除
</el-text>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</el-main>
</el-main>
<el-footer
class=
"footer-container"
>
<el-footer
class=
"footer-container"
>
<form
@
submit
.
prevent=
"onSend"
class=
"prompt-from"
>
<form
@
submit
.
prevent=
"onSend"
class=
"prompt-from"
>
<textarea
class=
"prompt-input"
v-model=
"prompt"
@
keyup
.
enter=
"onSend"
<textarea
class=
"prompt-input"
v-model=
"prompt"
@
keyup
.
enter=
"onSend"
@
input=
"onPromptInput"
@
input=
"onPromptInput"
@
compositionstart=
"onCompositionstart"
@
compositionstart=
"onCompositionstart"
@
compositionend=
"onCompositionend"
@
compositionend=
"onCompositionend"
placeholder=
"问我任何问题...(Shift+Enter 换行,按下 Enter 发送)"
></textarea>
placeholder=
"问我任何问题...(Shift+Enter 换行,按下 Enter 发送)"
></textarea>
<div
class=
"prompt-btns"
>
<div
class=
"prompt-btns"
>
<el-switch/>
<el-switch
/>
<el-button
type=
"primary"
size=
"default"
@
click=
"onSend()"
<el-button
:loading=
"conversationInProgress"
v-if=
"conversationInProgress == false"
>
type=
"primary"
size=
"default"
@
click=
"onSend()"
:loading=
"conversationInProgress"
v-if=
"conversationInProgress == false"
>
{{ conversationInProgress ? '进行中' : '发送' }}
{{ conversationInProgress ? '进行中' : '发送' }}
</el-button>
</el-button>
<el-button
type=
"danger"
size=
"default"
@
click=
"stopStream()"
<el-button
v-if=
"conversationInProgress == true"
>
type=
"danger"
size=
"default"
@
click=
"stopStream()"
v-if=
"conversationInProgress == true"
>
停止
停止
</el-button>
</el-button>
</div>
</div>
...
@@ -175,32 +188,31 @@
...
@@ -175,32 +188,31 @@
</template>
</template>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
ChatMessageApi
,
ChatMessageSendVO
,
ChatMessageVO
}
from
"@/api/ai/chat/message"
import
{
ChatMessageApi
,
ChatMessageSendVO
,
ChatMessageVO
}
from
'@/api/ai/chat/message'
import
{
import
{
ChatConversationApi
,
ChatConversationApi
,
ChatConversationUpdateVO
,
ChatConversationUpdateVO
,
ChatConversationVO
ChatConversationVO
}
from
"@/api/ai/chat/conversation"
}
from
'@/api/ai/chat/conversation'
import
{
ChatModelApi
,
ChatModelVO
}
from
"@/api/ai/model/chatModel"
import
{
ChatModelApi
,
ChatModelVO
}
from
'@/api/ai/model/chatModel'
import
{
formatDate
}
from
"@/utils/formatTime"
import
{
formatDate
}
from
'@/utils/formatTime'
import
{
useClipboard
}
from
"@vueuse/core"
;
import
{
useClipboard
}
from
'@vueuse/core'
// 转换 markdown
// 转换 markdown
import
{
marked
}
from
'marked'
;
import
{
marked
}
from
'marked'
// 代码高亮 https://highlightjs.org/
// 代码高亮 https://highlightjs.org/
import
'highlight.js/styles/vs2015.min.css'
;
import
'highlight.js/styles/vs2015.min.css'
import
hljs
from
'highlight.js'
;
import
hljs
from
'highlight.js'
// 自定义渲染器
// 自定义渲染器
const
renderer
=
{
const
renderer
=
{
code
(
code
,
language
,
c
)
{
code
(
code
,
language
,
c
)
{
const
highlightHtml
=
hljs
.
highlight
(
code
,
{
language
:
language
,
ignoreIllegals
:
true
}).
value
const
highlightHtml
=
hljs
.
highlight
(
code
,
{
language
:
language
,
ignoreIllegals
:
true
}).
value
const
copyHtml
=
`<div id="copy" data-copy='
${
code
}
' style="position: absolute; right: 10px; top: 5px; color: #fff;cursor: pointer;">复制</div>`
const
copyHtml
=
`<div id="copy" data-copy='
${
code
}
' style="position: absolute; right: 10px; top: 5px; color: #fff;cursor: pointer;">复制</div>`
return
`<pre>
${
copyHtml
}
<code class="hljs">
${
highlightHtml
}
</code></pre>`
return
`<pre>
${
copyHtml
}
<code class="hljs">
${
highlightHtml
}
</code></pre>`
}
,
}
}
;
}
marked
.
use
({
marked
.
use
({
renderer
:
renderer
,
renderer
:
renderer
})
})
const
conversationList
=
[
const
conversationList
=
[
...
@@ -218,7 +230,7 @@ const conversationList = [
...
@@ -218,7 +230,7 @@ const conversationList = [
}
}
]
]
// 初始化 copy 到粘贴板
// 初始化 copy 到粘贴板
const
{
copy
}
=
useClipboard
();
const
{
copy
}
=
useClipboard
()
const
searchName
=
ref
(
''
)
// 查询的内容
const
searchName
=
ref
(
''
)
// 查询的内容
const
inputTimeout
=
ref
<
any
>
()
// 处理输入中回车的定时器
const
inputTimeout
=
ref
<
any
>
()
// 处理输入中回车的定时器
...
@@ -230,8 +242,8 @@ const conversationInAbortController = ref<any>() // 对话进行中 abort 控制
...
@@ -230,8 +242,8 @@ const conversationInAbortController = ref<any>() // 对话进行中 abort 控制
const
prompt
=
ref
<
string
>
()
// prompt
const
prompt
=
ref
<
string
>
()
// prompt
// 判断 消息列表 滚动的位置(用于判断是否需要滚动到消息最下方)
// 判断 消息列表 滚动的位置(用于判断是否需要滚动到消息最下方)
const
messageContainer
:
any
=
ref
(
null
)
;
const
messageContainer
:
any
=
ref
(
null
)
const
isScrolling
=
ref
(
false
)
//用于判断用户是否在滚动
const
isScrolling
=
ref
(
false
)
//用于判断用户是否在滚动
const
isComposing
=
ref
(
false
)
// 判断用户是否在输入
const
isComposing
=
ref
(
false
)
// 判断用户是否在输入
/** chat message 列表 */
/** chat message 列表 */
...
@@ -241,7 +253,6 @@ const useModal = ref<ChatModelVO>() // 使用的modal
...
@@ -241,7 +253,6 @@ const useModal = ref<ChatModelVO>() // 使用的modal
const
useConversation
=
ref
<
ChatConversationVO
>
()
// 使用的 Conversation
const
useConversation
=
ref
<
ChatConversationVO
>
()
// 使用的 Conversation
const
modalList
=
ref
<
ChatModelVO
[]
>
([])
// 列表的数据
const
modalList
=
ref
<
ChatModelVO
[]
>
([])
// 列表的数据
const
changeConversation
=
(
conversation
)
=>
{
const
changeConversation
=
(
conversation
)
=>
{
console
.
log
(
conversation
)
console
.
log
(
conversation
)
conversationId
.
value
=
conversation
.
id
conversationId
.
value
=
conversation
.
id
...
@@ -272,11 +283,11 @@ const onSend = async () => {
...
@@ -272,11 +283,11 @@ const onSend = async () => {
if
(
conversationInProgress
.
value
)
{
if
(
conversationInProgress
.
value
)
{
return
return
}
}
const
content
=
prompt
.
value
?.
trim
()
;
const
content
=
prompt
.
value
?.
trim
()
if
(
content
?.
length
<
2
)
{
if
(
content
?.
length
<
2
)
{
ElMessage
({
ElMessage
({
message
:
'请输入内容!'
,
message
:
'请输入内容!'
,
type
:
'error'
,
type
:
'error'
})
})
return
return
}
}
...
@@ -284,13 +295,13 @@ const onSend = async () => {
...
@@ -284,13 +295,13 @@ const onSend = async () => {
prompt
.
value
=
''
prompt
.
value
=
''
const
requestParams
=
{
const
requestParams
=
{
conversationId
:
conversationId
.
value
,
conversationId
:
conversationId
.
value
,
content
:
content
,
content
:
content
}
as
unknown
as
ChatMessageSendVO
}
as
unknown
as
ChatMessageSendVO
// 添加 message
// 添加 message
const
userMessage
=
await
ChatMessageApi
.
add
(
requestParams
)
as
unknown
as
ChatMessageVO
;
const
userMessage
=
(
await
ChatMessageApi
.
add
(
requestParams
))
as
unknown
as
ChatMessageVO
list
.
value
.
push
(
userMessage
)
list
.
value
.
push
(
userMessage
)
// 滚动到住下面
// 滚动到住下面
scrollToBottom
()
;
scrollToBottom
()
// stream
// stream
await
doSendStream
(
userMessage
)
await
doSendStream
(
userMessage
)
//
//
...
@@ -305,45 +316,49 @@ const doSendStream = async (userMessage: ChatMessageVO) => {
...
@@ -305,45 +316,49 @@ const doSendStream = async (userMessage: ChatMessageVO) => {
// 发送 event stream
// 发送 event stream
let
isFirstMessage
=
true
let
isFirstMessage
=
true
let
content
=
''
let
content
=
''
ChatMessageApi
.
sendStream
(
userMessage
.
id
,
conversationInAbortController
.
value
,
(
message
)
=>
{
ChatMessageApi
.
sendStream
(
userMessage
.
id
,
conversationInAbortController
.
value
,
(
message
)
=>
{
console
.
log
(
'message'
,
message
)
console
.
log
(
'message'
,
message
)
const
data
=
JSON
.
parse
(
message
.
data
)
as
unknown
as
ChatMessageVO
const
data
=
JSON
.
parse
(
message
.
data
)
as
unknown
as
ChatMessageVO
// 如果没有内容结束链接
// 如果没有内容结束链接
if
(
data
.
content
===
''
)
{
if
(
data
.
content
===
''
)
{
// 标记对话结束
// 标记对话结束
conversationInProgress
.
value
=
false
;
conversationInProgress
.
value
=
false
// 结束 stream 对话
// 结束 stream 对话
conversationInAbortController
.
value
.
abort
()
conversationInAbortController
.
value
.
abort
()
}
}
// 首次返回需要添加一个 message 到页面,后面的都是更新
// 首次返回需要添加一个 message 到页面,后面的都是更新
if
(
isFirstMessage
)
{
if
(
isFirstMessage
)
{
isFirstMessage
=
false
;
isFirstMessage
=
false
list
.
value
.
push
(
data
)
list
.
value
.
push
(
data
)
}
else
{
}
else
{
content
=
content
+
data
.
content
content
=
content
+
data
.
content
const
lastMessage
=
list
.
value
[
list
.
value
.
length
-
1
];
const
lastMessage
=
list
.
value
[
list
.
value
.
length
-
1
]
lastMessage
.
content
=
marked
(
content
)
as
unknown
as
string
lastMessage
.
content
=
marked
(
content
)
as
unknown
as
string
list
.
value
[
list
.
value
-
1
]
=
lastMessage
list
.
value
[
list
.
value
-
1
]
=
lastMessage
}
}
// 滚动到最下面
// 滚动到最下面
scrollToBottom
();
scrollToBottom
()
},
(
error
)
=>
{
},
(
error
)
=>
{
console
.
log
(
'error'
,
error
)
console
.
log
(
'error'
,
error
)
// 标记对话结束
// 标记对话结束
conversationInProgress
.
value
=
false
;
conversationInProgress
.
value
=
false
// 结束 stream 对话
// 结束 stream 对话
conversationInAbortController
.
value
.
abort
()
conversationInAbortController
.
value
.
abort
()
},
()
=>
{
},
()
=>
{
console
.
log
(
'close'
)
console
.
log
(
'close'
)
// 标记对话结束
// 标记对话结束
conversationInProgress
.
value
=
false
;
conversationInProgress
.
value
=
false
// 结束 stream 对话
// 结束 stream 对话
conversationInAbortController
.
value
.
abort
()
conversationInAbortController
.
value
.
abort
()
})
}
)
}
finally
{
}
finally
{
}
}
}
}
/** 查询列表 */
/** 查询列表 */
...
@@ -354,28 +369,28 @@ const messageList = async () => {
...
@@ -354,28 +369,28 @@ const messageList = async () => {
// 处理 markdown
// 处理 markdown
// marked(this.markdownText)
// marked(this.markdownText)
res
.
map
(
item
=>
{
res
.
map
(
(
item
)
=>
{
// item.content = marked(item.content)
// item.content = marked(item.content)
if
(
item
.
type
!==
'user'
)
{
if
(
item
.
type
!==
'user'
)
{
item
.
content
=
marked
(
item
.
content
)
item
.
content
=
marked
(
item
.
content
)
}
}
})
})
list
.
value
=
res
;
list
.
value
=
res
// 滚动到最下面
// 滚动到最下面
scrollToBottom
()
;
scrollToBottom
()
}
finally
{
}
finally
{
}
}
}
}
function
scrollToBottom
()
{
function
scrollToBottom
()
{
nextTick
(()
=>
{
nextTick
(()
=>
{
//注意要使用nexttick以免获取不到dom
//注意要使用nexttick以免获取不到dom
console
.
log
(
'isScrolling.value'
,
isScrolling
.
value
)
console
.
log
(
'isScrolling.value'
,
isScrolling
.
value
)
if
(
!
isScrolling
.
value
)
{
if
(
!
isScrolling
.
value
)
{
messageContainer
.
value
.
scrollTop
=
messageContainer
.
value
.
scrollHeight
-
messageContainer
.
value
.
offsetHeight
messageContainer
.
value
.
scrollTop
=
messageContainer
.
value
.
scrollHeight
-
messageContainer
.
value
.
offsetHeight
}
}
})
})
}
}
...
@@ -399,7 +414,7 @@ function noCopy(content) {
...
@@ -399,7 +414,7 @@ function noCopy(content) {
copy
(
content
)
copy
(
content
)
ElMessage
({
ElMessage
({
message
:
'复制成功!'
,
message
:
'复制成功!'
,
type
:
'success'
,
type
:
'success'
})
})
}
}
...
@@ -408,12 +423,12 @@ const onDelete = async (id) => {
...
@@ -408,12 +423,12 @@ const onDelete = async (id) => {
await
ChatMessageApi
.
delete
(
id
)
await
ChatMessageApi
.
delete
(
id
)
ElMessage
({
ElMessage
({
message
:
'删除成功!'
,
message
:
'删除成功!'
,
type
:
'success'
,
type
:
'success'
})
})
// tip:如果 stream 进行中的 message,就需要调用 controller 结束
// tip:如果 stream 进行中的 message,就需要调用 controller 结束
stopStream
()
stopStream
()
// 重新获取 message 列表
// 重新获取 message 列表
await
messageList
()
;
await
messageList
()
}
}
const
stopStream
=
async
()
=>
{
const
stopStream
=
async
()
=>
{
...
@@ -428,7 +443,7 @@ const stopStream = async () => {
...
@@ -428,7 +443,7 @@ const stopStream = async () => {
const
modalClick
=
async
(
command
)
=>
{
const
modalClick
=
async
(
command
)
=>
{
const
update
=
{
const
update
=
{
id
:
conversationId
.
value
,
id
:
conversationId
.
value
,
modelId
:
command
.
id
,
modelId
:
command
.
id
}
as
unknown
as
ChatConversationUpdateVO
}
as
unknown
as
ChatConversationUpdateVO
// 切换 modal
// 切换 modal
useModal
.
value
=
command
useModal
.
value
=
command
...
@@ -438,13 +453,13 @@ const modalClick = async (command) => {
...
@@ -438,13 +453,13 @@ const modalClick = async (command) => {
const
getModalList
=
async
()
=>
{
const
getModalList
=
async
()
=>
{
// 获取模型 as unknown as ChatModelVO
// 获取模型 as unknown as ChatModelVO
modalList
.
value
=
await
ChatModelApi
.
getChatModelSimpleList
(
0
)
as
unknown
as
ChatModelVO
[]
modalList
.
value
=
(
await
ChatModelApi
.
getChatModelSimpleList
(
0
)
)
as
unknown
as
ChatModelVO
[]
}
}
// 输入
// 输入
const
onCompositionstart
=
()
=>
{
const
onCompositionstart
=
()
=>
{
console
.
log
(
'onCompositionstart。。。.'
)
console
.
log
(
'onCompositionstart。。。.'
)
isComposing
.
value
=
true
isComposing
.
value
=
true
}
}
const
onCompositionend
=
()
=>
{
const
onCompositionend
=
()
=>
{
...
@@ -455,7 +470,6 @@ const onCompositionend = () => {
...
@@ -455,7 +470,6 @@ const onCompositionend = () => {
},
200
)
},
200
)
}
}
const
onPromptInput
=
(
event
)
=>
{
const
onPromptInput
=
(
event
)
=>
{
// 非输入法 输入设置为 true
// 非输入法 输入设置为 true
if
(
!
isComposing
.
value
)
{
if
(
!
isComposing
.
value
)
{
...
@@ -483,7 +497,7 @@ const getConversation = async (conversationId: string) => {
...
@@ -483,7 +497,7 @@ const getConversation = async (conversationId: string) => {
console
.
log
(
'useConversation.value'
,
useConversation
.
value
)
console
.
log
(
'useConversation.value'
,
useConversation
.
value
)
// 选中 modal
// 选中 modal
if
(
useConversation
.
value
)
{
if
(
useConversation
.
value
)
{
modalList
.
value
.
forEach
(
item
=>
{
modalList
.
value
.
forEach
(
(
item
)
=>
{
if
(
useConversation
.
value
?.
modelId
===
item
.
id
)
{
if
(
useConversation
.
value
?.
modelId
===
item
.
id
)
{
useModal
.
value
=
item
useModal
.
value
=
item
}
}
...
@@ -494,11 +508,11 @@ const getConversation = async (conversationId: string) => {
...
@@ -494,11 +508,11 @@ const getConversation = async (conversationId: string) => {
/** 初始化 **/
/** 初始化 **/
onMounted
(
async
()
=>
{
onMounted
(
async
()
=>
{
// 获取模型
// 获取模型
getModalList
()
;
getModalList
()
// 获取对话信息
// 获取对话信息
getConversation
(
conversationId
.
value
)
;
getConversation
(
conversationId
.
value
)
// 获取列表数据
// 获取列表数据
messageList
()
;
messageList
()
// scrollToBottom();
// scrollToBottom();
// await nextTick
// await nextTick
// 监听滚动事件,判断用户滚动状态
// 监听滚动事件,判断用户滚动状态
...
@@ -510,17 +524,13 @@ onMounted(async () => {
...
@@ -510,17 +524,13 @@ onMounted(async () => {
copy
(
e
.
target
?.
dataset
?.
copy
)
copy
(
e
.
target
?.
dataset
?.
copy
)
ElMessage
({
ElMessage
({
message
:
'复制成功!'
,
message
:
'复制成功!'
,
type
:
'success'
,
type
:
'success'
})
})
}
}
})
})
})
})
</
script
>
</
script
>
<
style
lang=
"scss"
scoped
>
<
style
lang=
"scss"
scoped
>
.ai-layout
{
.ai-layout
{
//
TODO
@范
这里height不能
100%
先这样临时处理
//
TODO
@范
这里height不能
100%
先这样临时处理
position
:
absolute
;
position
:
absolute
;
...
@@ -710,8 +720,8 @@ onMounted(async () => {
...
@@ -710,8 +720,8 @@ onMounted(async () => {
display
:
flex
;
display
:
flex
;
flex-direction
:
column
;
flex-direction
:
column
;
overflow-wrap
:
break-word
;
overflow-wrap
:
break-word
;
background-color
:
rgba
(
228
,
228
,
228
,
0.8
0
);
background-color
:
rgba
(
228
,
228
,
228
,
0.8
);
box-shadow
:
0
0
0
1px
rgba
(
228
,
228
,
228
,
0.8
0
);
box-shadow
:
0
0
0
1px
rgba
(
228
,
228
,
228
,
0.8
);
border-radius
:
10px
;
border-radius
:
10px
;
padding
:
10px
10px
5px
10px
;
padding
:
10px
10px
5px
10px
;
...
@@ -727,10 +737,10 @@ onMounted(async () => {
...
@@ -727,10 +737,10 @@ onMounted(async () => {
.right-text
{
.right-text
{
font-size
:
0.95rem
;
font-size
:
0.95rem
;
color
:
#
FFF
;
color
:
#
fff
;
display
:
inline
;
display
:
inline
;
background-color
:
#267fff
;
background-color
:
#267fff
;
color
:
#
FFF
;
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
;
...
@@ -739,7 +749,8 @@ onMounted(async () => {
...
@@ -739,7 +749,8 @@ onMounted(async () => {
}
}
}
}
.left-btns
,
.right-btns
{
.left-btns
,
.right-btns
{
display
:
flex
;
display
:
flex
;
flex-direction
:
row
;
flex-direction
:
row
;
margin-top
:
8px
;
margin-top
:
8px
;
...
@@ -822,7 +833,7 @@ onMounted(async () => {
...
@@ -822,7 +833,7 @@ onMounted(async () => {
line-height
:
1.6rem
;
line-height
:
1.6rem
;
letter-spacing
:
0em
;
letter-spacing
:
0em
;
text-align
:
left
;
text-align
:
left
;
color
:
#3
B3E
55
;
color
:
#3
b3e
55
;
max-width
:
100%
;
max-width
:
100%
;
pre
{
pre
{
...
...
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