mirror of
https://github.com/yangjian102621/geekai.git
synced 2026-04-29 22:44:30 +08:00
修复重新生成的 Bug
This commit is contained in:
@@ -40,6 +40,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
ChatEventStart = "start"
|
ChatEventStart = "start"
|
||||||
ChatEventEnd = "end"
|
ChatEventEnd = "end"
|
||||||
|
ChatEventComplete = "complete"
|
||||||
ChatEventError = "error"
|
ChatEventError = "error"
|
||||||
ChatEventMessageDelta = "message_delta"
|
ChatEventMessageDelta = "message_delta"
|
||||||
ChatEventTitle = "title"
|
ChatEventTitle = "title"
|
||||||
@@ -539,6 +540,7 @@ func (h *ChatHandler) subUserPower(userVo vo.User, input ChatInput, promptTokens
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *ChatHandler) saveChatHistory(
|
func (h *ChatHandler) saveChatHistory(
|
||||||
|
c *gin.Context,
|
||||||
req types.ApiRequest,
|
req types.ApiRequest,
|
||||||
usage Usage,
|
usage Usage,
|
||||||
message types.Message,
|
message types.Message,
|
||||||
@@ -609,6 +611,22 @@ func (h *ChatHandler) saveChatHistory(
|
|||||||
logger.Error("failed to save reply history message: ", err)
|
logger.Error("failed to save reply history message: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 发送完整聊天记录给前端
|
||||||
|
var messageVo vo.ChatMessage
|
||||||
|
err = utils.CopyObject(historyReplyMsg, &messageVo)
|
||||||
|
if err == nil {
|
||||||
|
// 解析内容
|
||||||
|
var content vo.MsgContent
|
||||||
|
err = utils.JsonDecode(historyReplyMsg.Content, &content)
|
||||||
|
if err != nil {
|
||||||
|
content.Text = historyReplyMsg.Content
|
||||||
|
}
|
||||||
|
messageVo.Content = content
|
||||||
|
messageVo.CreatedAt = historyReplyMsg.CreatedAt.Unix()
|
||||||
|
messageVo.UpdatedAt = historyReplyMsg.UpdatedAt.Unix()
|
||||||
|
pushMessage(c, ChatEventComplete, messageVo)
|
||||||
|
}
|
||||||
|
|
||||||
// 更新用户算力
|
// 更新用户算力
|
||||||
if input.ChatModel.Power > 0 {
|
if input.ChatModel.Power > 0 {
|
||||||
h.subUserPower(userVo, input, promptTokens, replyTokens)
|
h.subUserPower(userVo, input, promptTokens, replyTokens)
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ func (h *ChatHandler) sendOpenAiMessage(
|
|||||||
TotalTokens: 0,
|
TotalTokens: 0,
|
||||||
}
|
}
|
||||||
message.Content = usage.Content
|
message.Content = usage.Content
|
||||||
h.saveChatHistory(req, usage, message, input, userVo, promptCreatedAt, replyCreatedAt)
|
h.saveChatHistory(c, req, usage, message, input, userVo, promptCreatedAt, replyCreatedAt)
|
||||||
}
|
}
|
||||||
} else { // 非流式输出
|
} else { // 非流式输出
|
||||||
var respVo OpenAIResVo
|
var respVo OpenAIResVo
|
||||||
@@ -242,7 +242,7 @@ func (h *ChatHandler) sendOpenAiMessage(
|
|||||||
pushMessage(c, "text", content)
|
pushMessage(c, "text", content)
|
||||||
respVo.Usage.Prompt = input.Prompt
|
respVo.Usage.Prompt = input.Prompt
|
||||||
respVo.Usage.Content = content
|
respVo.Usage.Content = content
|
||||||
h.saveChatHistory(req, respVo.Usage, respVo.Choices[0].Message, input, userVo, promptCreatedAt, time.Now())
|
h.saveChatHistory(c, req, respVo.Usage, respVo.Choices[0].Message, input, userVo, promptCreatedAt, time.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -159,6 +159,8 @@ func (s *MigrationService) MigrateConfigContent() error {
|
|||||||
|
|
||||||
// 数据表迁移
|
// 数据表迁移
|
||||||
func (s *MigrationService) TableMigration() {
|
func (s *MigrationService) TableMigration() {
|
||||||
|
// 新数据表
|
||||||
|
s.db.AutoMigrate(&model.Moderation{})
|
||||||
// 订单字段整理
|
// 订单字段整理
|
||||||
if s.db.Migrator().HasColumn(&model.Order{}, "pay_type") {
|
if s.db.Migrator().HasColumn(&model.Order{}, "pay_type") {
|
||||||
s.db.Migrator().RenameColumn(&model.Order{}, "pay_type", "channel")
|
s.db.Migrator().RenameColumn(&model.Order{}, "pay_type", "channel")
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ const md = new MarkdownIt({
|
|||||||
const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000)
|
const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000)
|
||||||
// 显示复制代码按钮和展开/收起按钮
|
// 显示复制代码按钮和展开/收起按钮
|
||||||
const copyBtn = `<div class="flex">
|
const copyBtn = `<div class="flex">
|
||||||
<span class="text-[12px] mr-2 text-[#00e0e0] cursor-pointer expand-btn" data-code-id="${codeIndex}">展开</span>
|
<span class="text-[12px] mr-2 text-[#00e0e0] cursor-pointer expand-btn" data-code-id="${codeIndex}" onclick="window.toggleCodeBlock('${codeIndex}')">收起</span>
|
||||||
<span class="copy-code-btn" data-clipboard-action="copy" data-clipboard-target="#copy-target-${codeIndex}">复制</span>
|
<span class="copy-code-btn" data-clipboard-action="copy" data-clipboard-target="#copy-target-${codeIndex}">复制</span>
|
||||||
</div><textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(
|
</div><textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(
|
||||||
/<\/textarea>/g,
|
/<\/textarea>/g,
|
||||||
@@ -184,8 +184,8 @@ const md = new MarkdownIt({
|
|||||||
preCode = md.utils.escapeHtml(str)
|
preCode = md.utils.escapeHtml(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将代码包裹在 pre 中,添加收起状态的类
|
// 将代码包裹在 pre 中,添加展开状态的类(默认展开)
|
||||||
return `<pre class="code-container flex flex-col code-collapsed" data-code-id="${codeIndex}">
|
return `<pre class="code-container flex flex-col code-expanded" data-code-id="${codeIndex}">
|
||||||
<div class="flex justify-between bg-[#50505a] w-full rounded-tl-[10px] rounded-tr-[10px] px-3 py-1">${langHtml}${copyBtn}</div>
|
<div class="flex justify-between bg-[#50505a] w-full rounded-tl-[10px] rounded-tr-[10px] px-3 py-1">${langHtml}${copyBtn}</div>
|
||||||
<code class="language-${lang} hljs">${preCode}</code>
|
<code class="language-${lang} hljs">${preCode}</code>
|
||||||
<span class="copy-code-btn absolute right-3 bottom-3" data-clipboard-action="copy" data-clipboard-target="#copy-target-${codeIndex}">复制</span></pre>`
|
<span class="copy-code-btn absolute right-3 bottom-3" data-clipboard-action="copy" data-clipboard-target="#copy-target-${codeIndex}">复制</span></pre>`
|
||||||
@@ -254,6 +254,9 @@ const toggleCodeBlock = (codeId) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将函数暴露到全局作用域
|
||||||
|
window.toggleCodeBlock = toggleCodeBlock
|
||||||
|
|
||||||
// 添加事件监听
|
// 添加事件监听
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@@ -265,24 +268,19 @@ onMounted(() => {
|
|||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (props.data.content.text) {
|
if (props.data.content.text) {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
setupCodeBlockEvents()
|
// 延迟一点时间确保DOM完全渲染
|
||||||
|
setTimeout(() => {
|
||||||
|
setupCodeBlockEvents()
|
||||||
|
}, 100)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const setupCodeBlockEvents = () => {
|
const setupCodeBlockEvents = () => {
|
||||||
// 移除旧的事件监听器
|
// 检查所有代码块并设置展开按钮的显示状态
|
||||||
const oldBtns = document.querySelectorAll('.expand-btn')
|
|
||||||
oldBtns.forEach((btn) => {
|
|
||||||
btn.removeEventListener('click', handleExpandClick)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 为展开按钮添加点击事件
|
|
||||||
const expandBtns = document.querySelectorAll('.expand-btn')
|
const expandBtns = document.querySelectorAll('.expand-btn')
|
||||||
expandBtns.forEach((btn) => {
|
|
||||||
btn.addEventListener('click', handleExpandClick)
|
|
||||||
|
|
||||||
// 检查对应的代码块是否需要展开功能
|
expandBtns.forEach((btn) => {
|
||||||
const codeId = btn.getAttribute('data-code-id')
|
const codeId = btn.getAttribute('data-code-id')
|
||||||
const codeContainer = document.querySelector(`pre[data-code-id="${codeId}"]`)
|
const codeContainer = document.querySelector(`pre[data-code-id="${codeId}"]`)
|
||||||
const codeElement = codeContainer?.querySelector('.hljs')
|
const codeElement = codeContainer?.querySelector('.hljs')
|
||||||
@@ -299,17 +297,20 @@ const setupCodeBlockEvents = () => {
|
|||||||
btn.style.display = 'none'
|
btn.style.display = 'none'
|
||||||
// 移除收起状态的类,让短代码块完全展示
|
// 移除收起状态的类,让短代码块完全展示
|
||||||
codeContainer.classList.remove('code-collapsed')
|
codeContainer.classList.remove('code-collapsed')
|
||||||
|
codeContainer.classList.add('code-expanded')
|
||||||
} else {
|
} else {
|
||||||
btn.style.display = 'inline'
|
btn.style.display = 'inline'
|
||||||
|
// 确保长代码块默认展开
|
||||||
|
if (
|
||||||
|
!codeContainer.classList.contains('code-expanded') &&
|
||||||
|
!codeContainer.classList.contains('code-collapsed')
|
||||||
|
) {
|
||||||
|
codeContainer.classList.add('code-expanded')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleExpandClick = (e) => {
|
|
||||||
const codeId = e.target.getAttribute('data-code-id')
|
|
||||||
toggleCodeBlock(codeId)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -329,18 +329,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex little-btns">
|
<div class="flex little-btns">
|
||||||
<span class="send-btn tool-item-btn">
|
<span class="send-btn tool-item-btn">
|
||||||
<!-- showStopGenerate -->
|
<el-button type="info" v-if="isGenerating" @click="stopGenerate" plain>
|
||||||
<el-button
|
|
||||||
type="info"
|
|
||||||
v-if="showStopGenerate"
|
|
||||||
@click="stopGenerate"
|
|
||||||
plain
|
|
||||||
>
|
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<VideoPause />
|
<VideoPause />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button @click="sendMessage()" style="color: #754ff6" v-else>
|
<el-button
|
||||||
|
@click="sendMessage()"
|
||||||
|
style="color: #754ff6"
|
||||||
|
:disabled="isGenerating"
|
||||||
|
v-else
|
||||||
|
>
|
||||||
<el-tooltip class="box-item" effect="dark" content="发送">
|
<el-tooltip class="box-item" effect="dark" content="发送">
|
||||||
<el-icon><Promotion /></el-icon>
|
<el-icon><Promotion /></el-icon>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
@@ -635,10 +634,10 @@ httpGet('/api/function/list')
|
|||||||
})
|
})
|
||||||
|
|
||||||
const prompt = ref('')
|
const prompt = ref('')
|
||||||
const showStopGenerate = ref(false) // 停止生成
|
const isGenerating = ref(false)
|
||||||
const lineBuffer = ref('') // 输出缓冲行
|
const lineBuffer = ref('') // 输出缓冲行
|
||||||
const canSend = ref(true)
|
|
||||||
const isNewMsg = ref(true)
|
const isNewMsg = ref(true)
|
||||||
|
const abortController = ref(null)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
resizeElement()
|
resizeElement()
|
||||||
@@ -693,9 +692,10 @@ const initData = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
abortController.value = new AbortController()
|
||||||
// 发送 SSE 请求
|
// 发送 SSE 请求
|
||||||
const sendSSERequest = async (message) => {
|
const sendSSERequest = async (message) => {
|
||||||
|
isGenerating.value = true
|
||||||
try {
|
try {
|
||||||
await fetchEventSource('/api/chat/message', {
|
await fetchEventSource('/api/chat/message', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -710,6 +710,7 @@ const sendSSERequest = async (message) => {
|
|||||||
retryDelay: 0,
|
retryDelay: 0,
|
||||||
// 设置最大重试次数为0
|
// 设置最大重试次数为0
|
||||||
maxRetries: 0,
|
maxRetries: 0,
|
||||||
|
signal: abortController.value.signal,
|
||||||
onopen(response) {
|
onopen(response) {
|
||||||
if (response.ok && response.status === 200) {
|
if (response.ok && response.status === 200) {
|
||||||
console.log('SSE connection opened')
|
console.log('SSE connection opened')
|
||||||
@@ -717,7 +718,8 @@ const sendSSERequest = async (message) => {
|
|||||||
const errorMsg = `连接失败 (状态码: ${response.status})`
|
const errorMsg = `连接失败 (状态码: ${response.status})`
|
||||||
ElMessage.error(errorMsg)
|
ElMessage.error(errorMsg)
|
||||||
console.error('SSE connection failed', response)
|
console.error('SSE connection failed', response)
|
||||||
enableInput()
|
isGenerating.value = false
|
||||||
|
|
||||||
throw new Error(errorMsg)
|
throw new Error(errorMsg)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -732,12 +734,12 @@ const sendSSERequest = async (message) => {
|
|||||||
'content'
|
'content'
|
||||||
].text = `<div class="bg-red-50 text-red-500 p-3 rounded-md">${data.body}</div>`
|
].text = `<div class="bg-red-50 text-red-500 p-3 rounded-md">${data.body}</div>`
|
||||||
}
|
}
|
||||||
enableInput()
|
isGenerating.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.type === 'end') {
|
if (data.type === 'end') {
|
||||||
enableInput()
|
isGenerating.value = false
|
||||||
lineBuffer.value = '' // 清空缓冲
|
lineBuffer.value = '' // 清空缓冲
|
||||||
|
|
||||||
// 获取 token
|
// 获取 token
|
||||||
@@ -795,6 +797,10 @@ const sendSSERequest = async (message) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.type === 'complete') {
|
||||||
|
chatData.value[chatData.value.length - 1] = data.body
|
||||||
|
}
|
||||||
|
|
||||||
// 将聊天框的滚动条滑动到最底部
|
// 将聊天框的滚动条滑动到最底部
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
document
|
document
|
||||||
@@ -804,13 +810,18 @@ const sendSSERequest = async (message) => {
|
|||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error processing message:', error)
|
console.error('Error processing message:', error)
|
||||||
enableInput()
|
isGenerating.value = false
|
||||||
ElMessage.error('消息处理出错,请重试')
|
ElMessage.error('消息处理出错,请重试')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onerror(err) {
|
onerror(err) {
|
||||||
console.error('SSE Error:', err)
|
console.error('SSE Error:', err)
|
||||||
enableInput()
|
try {
|
||||||
|
abortController.value && abortController.value.abort()
|
||||||
|
} catch (e) {
|
||||||
|
console.error('AbortController abort error:', e)
|
||||||
|
}
|
||||||
|
isGenerating.value = false
|
||||||
// ElMessage.error('连接已断开,发生错误:' + err.message)
|
// ElMessage.error('连接已断开,发生错误:' + err.message)
|
||||||
const reply = chatData.value[chatData.value.length - 1]
|
const reply = chatData.value[chatData.value.length - 1]
|
||||||
if (reply) {
|
if (reply) {
|
||||||
@@ -821,12 +832,12 @@ const sendSSERequest = async (message) => {
|
|||||||
},
|
},
|
||||||
onclose() {
|
onclose() {
|
||||||
console.log('SSE connection closed')
|
console.log('SSE connection closed')
|
||||||
enableInput()
|
isGenerating.value = false
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to send message:', error)
|
console.error('Failed to send message:', error)
|
||||||
enableInput()
|
isGenerating.value = false
|
||||||
ElMessage.error('发送消息失败,请重试')
|
ElMessage.error('发送消息失败,请重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -839,12 +850,12 @@ const sendMessage = (messageId = 0) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canSend.value === false) {
|
if (isGenerating.value) {
|
||||||
ElMessage.warning('AI 正在作答中,请稍后...')
|
ElMessage.warning('AI 正在作答中,请稍后...')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prompt.value.trim().length === 0 || canSend.value === false) {
|
if (prompt.value === '') {
|
||||||
showMessageError('请输入要发送的消息!')
|
showMessageError('请输入要发送的消息!')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -883,7 +894,6 @@ const sendMessage = (messageId = 0) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
showHello.value = false
|
showHello.value = false
|
||||||
disableInput(false)
|
|
||||||
|
|
||||||
// 异步发送 SSE 请求
|
// 异步发送 SSE 请求
|
||||||
sendSSERequest({
|
sendSSERequest({
|
||||||
@@ -961,7 +971,7 @@ const newChat = () => {
|
|||||||
edit: false,
|
edit: false,
|
||||||
removing: false,
|
removing: false,
|
||||||
}
|
}
|
||||||
showStopGenerate.value = false
|
isGenerating.value = false
|
||||||
loadChatHistory(chatId.value)
|
loadChatHistory(chatId.value)
|
||||||
router.push(`/chat/${chatId.value}`)
|
router.push(`/chat/${chatId.value}`)
|
||||||
}
|
}
|
||||||
@@ -980,7 +990,7 @@ const loadChat = function (chat) {
|
|||||||
roleId.value = chat.role_id
|
roleId.value = chat.role_id
|
||||||
modelID.value = chat.model_id
|
modelID.value = chat.model_id
|
||||||
chatId.value = chat.chat_id
|
chatId.value = chat.chat_id
|
||||||
showStopGenerate.value = false
|
isGenerating.value = false
|
||||||
loadChatHistory(chatId.value)
|
loadChatHistory(chatId.value)
|
||||||
router.push(`/chat/${chatId.value}`)
|
router.push(`/chat/${chatId.value}`)
|
||||||
}
|
}
|
||||||
@@ -1053,16 +1063,6 @@ const removeChat = function (chat) {
|
|||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
const disableInput = (force) => {
|
|
||||||
canSend.value = false
|
|
||||||
showStopGenerate.value = !force
|
|
||||||
}
|
|
||||||
|
|
||||||
const enableInput = () => {
|
|
||||||
canSend.value = true
|
|
||||||
showStopGenerate.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const onInput = (e) => {
|
const onInput = (e) => {
|
||||||
// 根据输入的内容自动计算输入框的行数
|
// 根据输入的内容自动计算输入框的行数
|
||||||
const lineHeight = parseFloat(window.getComputedStyle(inputRef.value).lineHeight)
|
const lineHeight = parseFloat(window.getComputedStyle(inputRef.value).lineHeight)
|
||||||
@@ -1165,20 +1165,36 @@ const loadChatHistory = function (chatId) {
|
|||||||
|
|
||||||
// 停止生成
|
// 停止生成
|
||||||
const stopGenerate = function () {
|
const stopGenerate = function () {
|
||||||
showStopGenerate.value = false
|
if (abortController.value) {
|
||||||
httpGet('/api/chat/stop?session_id=' + getClientId()).then(() => {
|
abortController.value.abort()
|
||||||
enableInput()
|
isGenerating.value = false
|
||||||
})
|
httpGet('/api/chat/stop?session_id=' + getClientId())
|
||||||
|
.then(() => {
|
||||||
|
console.log('会话已中断')
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
showMessageError('中断对话失败:' + e.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新生成
|
// 重新生成
|
||||||
const reGenerate = function (messageId) {
|
const reGenerate = function (messageId) {
|
||||||
// 恢复发送按钮状态
|
// 恢复发送按钮状态
|
||||||
canSend.value = true
|
if (isGenerating.value) {
|
||||||
showStopGenerate.value = false
|
ElMessage.warning('AI 正在作答中,请稍后...')
|
||||||
console.log(messageId)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
chatData.value = chatData.value.filter((item) => item.id < messageId)
|
console.log('messageId', messageId)
|
||||||
|
|
||||||
|
// 判断 messageId 是整数
|
||||||
|
if (messageId !== '' && isNaN(messageId)) {
|
||||||
|
ElMessage.warning('消息 ID 不合法,无法重新生成')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chatData.value = chatData.value.filter((item) => item.id <= messageId)
|
||||||
// 保存用户消息内容,填入输入框
|
// 保存用户消息内容,填入输入框
|
||||||
const userPrompt = chatData.value[chatData.value.length - 1].content.text
|
const userPrompt = chatData.value[chatData.value.length - 1].content.text
|
||||||
// 删除用户消息
|
// 删除用户消息
|
||||||
|
|||||||
Reference in New Issue
Block a user