修复重新生成的 Bug

This commit is contained in:
GeekMaster
2025-09-01 10:58:15 +08:00
parent 957954f5ee
commit 14524f0559
5 changed files with 99 additions and 62 deletions

View File

@@ -40,6 +40,7 @@ import (
const (
ChatEventStart = "start"
ChatEventEnd = "end"
ChatEventComplete = "complete"
ChatEventError = "error"
ChatEventMessageDelta = "message_delta"
ChatEventTitle = "title"
@@ -539,6 +540,7 @@ func (h *ChatHandler) subUserPower(userVo vo.User, input ChatInput, promptTokens
}
func (h *ChatHandler) saveChatHistory(
c *gin.Context,
req types.ApiRequest,
usage Usage,
message types.Message,
@@ -609,6 +611,22 @@ func (h *ChatHandler) saveChatHistory(
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 {
h.subUserPower(userVo, input, promptTokens, replyTokens)

View File

@@ -226,7 +226,7 @@ func (h *ChatHandler) sendOpenAiMessage(
TotalTokens: 0,
}
message.Content = usage.Content
h.saveChatHistory(req, usage, message, input, userVo, promptCreatedAt, replyCreatedAt)
h.saveChatHistory(c, req, usage, message, input, userVo, promptCreatedAt, replyCreatedAt)
}
} else { // 非流式输出
var respVo OpenAIResVo
@@ -242,7 +242,7 @@ func (h *ChatHandler) sendOpenAiMessage(
pushMessage(c, "text", content)
respVo.Usage.Prompt = input.Prompt
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

View File

@@ -159,6 +159,8 @@ func (s *MigrationService) MigrateConfigContent() error {
// 数据表迁移
func (s *MigrationService) TableMigration() {
// 新数据表
s.db.AutoMigrate(&model.Moderation{})
// 订单字段整理
if s.db.Migrator().HasColumn(&model.Order{}, "pay_type") {
s.db.Migrator().RenameColumn(&model.Order{}, "pay_type", "channel")

View File

@@ -168,7 +168,7 @@ const md = new MarkdownIt({
const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000)
// 显示复制代码按钮和展开/收起按钮
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>
</div><textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(
/<\/textarea>/g,
@@ -184,8 +184,8 @@ const md = new MarkdownIt({
preCode = md.utils.escapeHtml(str)
}
// 将代码包裹在 pre 中,添加收起状态的类
return `<pre class="code-container flex flex-col code-collapsed" data-code-id="${codeIndex}">
// 将代码包裹在 pre 中,添加展开状态的类(默认展开)
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>
<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>`
@@ -254,6 +254,9 @@ const toggleCodeBlock = (codeId) => {
}
}
// 将函数暴露到全局作用域
window.toggleCodeBlock = toggleCodeBlock
// 添加事件监听
onMounted(() => {
nextTick(() => {
@@ -265,24 +268,19 @@ onMounted(() => {
watchEffect(() => {
if (props.data.content.text) {
nextTick(() => {
setupCodeBlockEvents()
// 延迟一点时间确保DOM完全渲染
setTimeout(() => {
setupCodeBlockEvents()
}, 100)
})
}
})
const setupCodeBlockEvents = () => {
// 移除旧的事件监听器
const oldBtns = document.querySelectorAll('.expand-btn')
oldBtns.forEach((btn) => {
btn.removeEventListener('click', handleExpandClick)
})
// 为展开按钮添加点击事件
// 检查所有代码块并设置展开按钮的显示状态
const expandBtns = document.querySelectorAll('.expand-btn')
expandBtns.forEach((btn) => {
btn.addEventListener('click', handleExpandClick)
// 检查对应的代码块是否需要展开功能
expandBtns.forEach((btn) => {
const codeId = btn.getAttribute('data-code-id')
const codeContainer = document.querySelector(`pre[data-code-id="${codeId}"]`)
const codeElement = codeContainer?.querySelector('.hljs')
@@ -299,17 +297,20 @@ const setupCodeBlockEvents = () => {
btn.style.display = 'none'
// 移除收起状态的类,让短代码块完全展示
codeContainer.classList.remove('code-collapsed')
codeContainer.classList.add('code-expanded')
} else {
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>
<style lang="scss">

View File

@@ -329,18 +329,17 @@
</div>
<div class="flex little-btns">
<span class="send-btn tool-item-btn">
<!-- showStopGenerate -->
<el-button
type="info"
v-if="showStopGenerate"
@click="stopGenerate"
plain
>
<el-button type="info" v-if="isGenerating" @click="stopGenerate" plain>
<el-icon>
<VideoPause />
</el-icon>
</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-icon><Promotion /></el-icon>
</el-tooltip>
@@ -635,10 +634,10 @@ httpGet('/api/function/list')
})
const prompt = ref('')
const showStopGenerate = ref(false) // 停止生成
const isGenerating = ref(false)
const lineBuffer = ref('') // 输出缓冲行
const canSend = ref(true)
const isNewMsg = ref(true)
const abortController = ref(null)
onMounted(() => {
resizeElement()
@@ -693,9 +692,10 @@ const initData = async () => {
}
}
}
abortController.value = new AbortController()
// 发送 SSE 请求
const sendSSERequest = async (message) => {
isGenerating.value = true
try {
await fetchEventSource('/api/chat/message', {
method: 'POST',
@@ -710,6 +710,7 @@ const sendSSERequest = async (message) => {
retryDelay: 0,
// 设置最大重试次数为0
maxRetries: 0,
signal: abortController.value.signal,
onopen(response) {
if (response.ok && response.status === 200) {
console.log('SSE connection opened')
@@ -717,7 +718,8 @@ const sendSSERequest = async (message) => {
const errorMsg = `连接失败 (状态码: ${response.status})`
ElMessage.error(errorMsg)
console.error('SSE connection failed', response)
enableInput()
isGenerating.value = false
throw new Error(errorMsg)
}
},
@@ -732,12 +734,12 @@ const sendSSERequest = async (message) => {
'content'
].text = `<div class="bg-red-50 text-red-500 p-3 rounded-md">${data.body}</div>`
}
enableInput()
isGenerating.value = false
return
}
if (data.type === 'end') {
enableInput()
isGenerating.value = false
lineBuffer.value = '' // 清空缓冲
// 获取 token
@@ -795,6 +797,10 @@ const sendSSERequest = async (message) => {
}
}
if (data.type === 'complete') {
chatData.value[chatData.value.length - 1] = data.body
}
// 将聊天框的滚动条滑动到最底部
nextTick(() => {
document
@@ -804,13 +810,18 @@ const sendSSERequest = async (message) => {
})
} catch (error) {
console.error('Error processing message:', error)
enableInput()
isGenerating.value = false
ElMessage.error('消息处理出错,请重试')
}
},
onerror(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)
const reply = chatData.value[chatData.value.length - 1]
if (reply) {
@@ -821,12 +832,12 @@ const sendSSERequest = async (message) => {
},
onclose() {
console.log('SSE connection closed')
enableInput()
isGenerating.value = false
},
})
} catch (error) {
console.error('Failed to send message:', error)
enableInput()
isGenerating.value = false
ElMessage.error('发送消息失败,请重试')
}
}
@@ -839,12 +850,12 @@ const sendMessage = (messageId = 0) => {
return
}
if (canSend.value === false) {
if (isGenerating.value) {
ElMessage.warning('AI 正在作答中,请稍后...')
return
}
if (prompt.value.trim().length === 0 || canSend.value === false) {
if (prompt.value === '') {
showMessageError('请输入要发送的消息!')
return false
}
@@ -883,7 +894,6 @@ const sendMessage = (messageId = 0) => {
})
showHello.value = false
disableInput(false)
// 异步发送 SSE 请求
sendSSERequest({
@@ -961,7 +971,7 @@ const newChat = () => {
edit: false,
removing: false,
}
showStopGenerate.value = false
isGenerating.value = false
loadChatHistory(chatId.value)
router.push(`/chat/${chatId.value}`)
}
@@ -980,7 +990,7 @@ const loadChat = function (chat) {
roleId.value = chat.role_id
modelID.value = chat.model_id
chatId.value = chat.chat_id
showStopGenerate.value = false
isGenerating.value = false
loadChatHistory(chatId.value)
router.push(`/chat/${chatId.value}`)
}
@@ -1053,16 +1063,6 @@ const removeChat = function (chat) {
.catch(() => {})
}
const disableInput = (force) => {
canSend.value = false
showStopGenerate.value = !force
}
const enableInput = () => {
canSend.value = true
showStopGenerate.value = false
}
const onInput = (e) => {
// 根据输入的内容自动计算输入框的行数
const lineHeight = parseFloat(window.getComputedStyle(inputRef.value).lineHeight)
@@ -1165,20 +1165,36 @@ const loadChatHistory = function (chatId) {
// 停止生成
const stopGenerate = function () {
showStopGenerate.value = false
httpGet('/api/chat/stop?session_id=' + getClientId()).then(() => {
enableInput()
})
if (abortController.value) {
abortController.value.abort()
isGenerating.value = false
httpGet('/api/chat/stop?session_id=' + getClientId())
.then(() => {
console.log('会话已中断')
})
.catch((e) => {
showMessageError('中断对话失败:' + e.message)
})
}
}
// 重新生成
const reGenerate = function (messageId) {
// 恢复发送按钮状态
canSend.value = true
showStopGenerate.value = false
console.log(messageId)
if (isGenerating.value) {
ElMessage.warning('AI 正在作答中,请稍后...')
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
// 删除用户消息