SSE 消息重构已完成

This commit is contained in:
GeekMaster
2025-05-27 15:48:07 +08:00
parent e685876cc0
commit 32fc4d86a2
15 changed files with 394 additions and 339 deletions

View File

@@ -6,7 +6,7 @@
</div>
<div class="chat-item">
<div v-if="files.length > 0" class="file-list-box">
<div v-if="files && files.length > 0" class="file-list-box">
<div v-for="file in files" :key="file.url">
<div class="image" v-if="isImage(file.ext)">
<el-image :src="file.url" fit="cover" />
@@ -49,7 +49,7 @@
</div>
<div class="chat-item">
<div v-if="files.length > 0" class="file-list-box">
<div v-if="files && files.length > 0" class="file-list-box">
<div v-for="file in files" :key="file.url">
<div class="image" v-if="isImage(file.ext)">
<el-image :src="file.url" fit="cover" />
@@ -90,9 +90,8 @@
<script setup>
import { FormatFileSize, GetFileIcon, GetFileType } from '@/store/system'
import { httpPost } from '@/utils/http'
import { dateFormat, isImage, processPrompt } from '@/utils/libs'
import { Clock, Edit } from '@element-plus/icons-vue'
import { Clock } from '@element-plus/icons-vue'
import hl from 'highlight.js'
import MarkdownIt from 'markdown-it'
import emoji from 'markdown-it-emoji'
@@ -115,7 +114,7 @@ const md = new MarkdownIt({
if (lang && hl.getLanguage(lang)) {
const langHtml = `<span class="lang-name">${lang}</span>`
// 处理代码高亮
const preCode = hl.highlight(lang, str, true).value
const preCode = hl.highlight(str, { language: lang, ignoreIllegals: true }).value
// 将代码包裹在 pre 中
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn} ${langHtml}</pre>`
}
@@ -128,16 +127,19 @@ const md = new MarkdownIt({
})
md.use(mathjaxPlugin)
md.use(emoji)
const props = defineProps({
data: {
type: Object,
default: {
content: '',
content: {
text: '',
files: [],
},
created_at: '',
tokens: 0,
model: '',
icon: '',
files: [],
},
},
listStyle: {
@@ -146,8 +148,8 @@ const props = defineProps({
},
})
const finalTokens = ref(props.data.tokens)
const content = ref(processPrompt(props.data.content))
const files = ref(props.data.files)
const content = ref(processPrompt(props.data.content.text))
const files = ref(props.data.content.files)
// 定义emit事件
const emit = defineEmits(['edit'])

View File

@@ -9,8 +9,8 @@
<div class="chat-item">
<div
class="content-wrapper"
v-html="md.render(processContent(data.content))"
v-if="data.content"
v-html="md.render(processContent(data.content.text))"
v-if="data.content.text"
></div>
<div class="content-wrapper flex justify-start items-center" v-else>
<span class="mr-2">AI 思考中</span> <Thinking :duration="1.5" />
@@ -48,18 +48,6 @@
</el-tooltip>
</span>
</span>
<!-- <span class="bar-item">-->
<!-- <el-dropdown trigger="click">-->
<!-- <span class="el-dropdown-link">-->
<!-- <el-icon><More/></el-icon>-->
<!-- </span>-->
<!-- <template #dropdown>-->
<!-- <el-dropdown-menu>-->
<!-- <el-dropdown-item :icon="Headset" @click="synthesis(orgContent)">生成语音</el-dropdown-item>-->
<!-- </el-dropdown-menu>-->
<!-- </template>-->
<!-- </el-dropdown>-->
<!-- </span>-->
</div>
</div>
</div>
@@ -74,8 +62,8 @@
<div class="content-wrapper">
<div
class="content"
v-html="md.render(processContent(data.content))"
v-if="data.content"
v-html="md.render(processContent(data.content.text))"
v-if="data.content.text"
></div>
<div class="content flex justify-start items-center" v-else>
<span class="mr-2">AI 思考中</span> <Thinking :duration="1.5" />
@@ -83,10 +71,9 @@
</div>
<div class="bar text-gray-500" v-if="data.created_at">
<span class="bar-item text-sm"> {{ dateFormat(data.created_at) }}</span>
<!-- <span class="bar-item">tokens: {{ data.tokens }}</span>-->
<span class="bar-item bg">
<el-tooltip class="box-item" effect="dark" content="复制回答" placement="bottom">
<el-icon class="copy-reply" :data-clipboard-text="data.content">
<el-icon class="copy-reply" :data-clipboard-text="data.content.text">
<DocumentCopy />
</el-icon>
</el-tooltip>
@@ -106,7 +93,7 @@
placement="bottom"
v-if="!isPlaying"
>
<i class="iconfont icon-speaker" @click="synthesis(data.content)"></i>
<i class="iconfont icon-speaker" @click="synthesis(data.content.text)"></i>
</el-tooltip>
<el-tooltip
class="box-item"
@@ -145,7 +132,10 @@ const props = defineProps({
type: Object,
default: {
icon: '',
content: '',
content: {
text: '',
files: [],
},
created_at: '',
tokens: 0,
},

View File

@@ -744,71 +744,15 @@ const initData = async () => {
}
}
// 发送消息
const sendMessage = async function () {
if (!isLogin.value) {
console.log('未登录')
store.setShowLoginDialog(true)
return
}
if (canSend.value === false) {
ElMessage.warning('AI 正在作答中,请稍后...')
return
}
if (prompt.value.trim().length === 0 || canSend.value === false) {
showMessageError('请输入要发送的消息!')
return false
}
// 追加消息
chatData.value.push({
type: 'prompt',
id: randString(32),
icon: loginUser.value.avatar,
content: prompt.value,
model: getModelValue(modelID.value),
created_at: new Date().getTime() / 1000,
files: files.value,
})
// 添加空回复消息
const _role = getRoleById(roleId.value)
chatData.value.push({
chat_id: chatId,
role_id: roleId.value,
type: 'reply',
id: randString(32),
icon: _role['icon'],
content: '',
})
nextTick(() => {
document
.getElementById('chat-box')
.scrollTo(0, document.getElementById('chat-box').scrollHeight)
})
showHello.value = false
disableInput(false)
// 发送 SSE 请求
const sendSSERequest = async (message) => {
try {
await fetchEventSource('/api/chat/message', {
method: 'POST',
headers: {
Authorization: getUserToken(),
},
body: JSON.stringify({
user_id: loginUser.value.id,
role_id: roleId.value,
model_id: modelID.value,
chat_id: chatId.value,
content: prompt.value,
tools: toolSelected.value,
stream: stream.value,
files: files.value,
}),
body: JSON.stringify(message),
openWhenHidden: true,
onopen(response) {
if (response.ok && response.status === 200) {
@@ -849,6 +793,7 @@ const sendMessage = async function () {
})
.catch(() => {})
isNewMsg.value = true
tmpChatTitle.value = message.prompt
return
}
@@ -858,13 +803,13 @@ const sendMessage = async function () {
lineBuffer.value = data.body
const reply = chatData.value[chatData.value.length - 1]
if (reply) {
reply['content'] = lineBuffer.value
reply['content'].text = lineBuffer.value
}
} else {
lineBuffer.value += data.body
const reply = chatData.value[chatData.value.length - 1]
if (reply) {
reply['content'] = lineBuffer.value
reply['content'].text = lineBuffer.value
}
}
}
@@ -897,12 +842,77 @@ const sendMessage = async function () {
enableInput()
ElMessage.error('发送消息失败,请重试')
}
}
// 发送消息
const sendMessage = () => {
if (!isLogin.value) {
console.log('未登录')
store.setShowLoginDialog(true)
return
}
if (canSend.value === false) {
ElMessage.warning('AI 正在作答中,请稍后...')
return
}
if (prompt.value.trim().length === 0 || canSend.value === false) {
showMessageError('请输入要发送的消息!')
return false
}
// 追加消息
chatData.value.push({
type: 'prompt',
id: randString(32),
icon: loginUser.value.avatar,
content: {
text: prompt.value,
files: files.value,
},
model: getModelValue(modelID.value),
created_at: new Date().getTime() / 1000,
})
// 添加空回复消息
const _role = getRoleById(roleId.value)
chatData.value.push({
chat_id: chatId,
role_id: roleId.value,
type: 'reply',
id: randString(32),
icon: _role['icon'],
content: {
text: '',
files: [],
},
})
nextTick(() => {
document
.getElementById('chat-box')
.scrollTo(0, document.getElementById('chat-box').scrollHeight)
})
showHello.value = false
disableInput(false)
// 异步发送 SSE 请求
sendSSERequest({
user_id: loginUser.value.id,
role_id: roleId.value,
model_id: modelID.value,
chat_id: chatId.value,
prompt: prompt.value,
tools: toolSelected.value,
stream: stream.value,
files: files.value,
})
tmpChatTitle.value = prompt.value
prompt.value = ''
files.value = []
row.value = 1
return true
}
const getRoleById = function (rid) {
@@ -1139,7 +1149,10 @@ const loadChatHistory = function (chatId) {
type: 'reply',
id: randString(32),
icon: _role['icon'],
content: _role['hello_msg'],
content: {
text: _role['hello_msg'],
files: [],
},
})
return
}