mirror of
https://github.com/yangjian102621/geekai.git
synced 2026-04-18 00:54:32 +08:00
修复重新生成的 Bug
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
// 删除用户消息
|
||||
|
||||
Reference in New Issue
Block a user