mirror of
https://github.com/yangjian102621/geekai.git
synced 2026-04-22 11:04:26 +08:00
acomplish re-generating for mobile chat page
This commit is contained in:
@@ -194,7 +194,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, input ChatInput, c *gin.C
|
||||
}
|
||||
|
||||
if userVo.Power < input.ChatModel.Power {
|
||||
return fmt.Errorf("您当前剩余算力 %d 已不足以支付当前模型的单次对话需要消耗的算力 %d,[立即购买](/member)。", userVo.Power, input.ChatModel.Power)
|
||||
return fmt.Errorf("您的算力不足,请购买算力。")
|
||||
}
|
||||
|
||||
if userVo.ExpiredTime > 0 && userVo.ExpiredTime <= time.Now().Unix() {
|
||||
|
||||
@@ -8,24 +8,40 @@
|
||||
<div class="triangle"></div>
|
||||
<div class="content-box" ref="contentRef">
|
||||
<div
|
||||
v-if="content"
|
||||
:data-clipboard-text="orgContent"
|
||||
class="content content-mobile"
|
||||
v-html="content"
|
||||
v-if="content"
|
||||
></div>
|
||||
<div v-else-if="error">
|
||||
<div class="content content-mobile !text-red-500">{{ error }}</div>
|
||||
</div>
|
||||
<div class="content content-mobile flex justify-start items-center" v-else>
|
||||
<span class="mr-2">AI 思考中</span> <Thinking :duration="1.5" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮区域 -->
|
||||
<div class="action-buttons" v-if="showActions && orgContent">
|
||||
<van-button
|
||||
size="mini"
|
||||
type="primary"
|
||||
plain
|
||||
@click="handleRegenerate"
|
||||
:disabled="isGenerating"
|
||||
>
|
||||
{{ isGenerating ? '生成中...' : '重新生成' }}
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { showImagePreview } from 'vant'
|
||||
import Thinking from '../Thinking.vue'
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object,
|
||||
@@ -42,12 +58,37 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: '/images/gpt-icon.png',
|
||||
},
|
||||
showActions: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
isGenerating: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
messageId: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
error: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
const emits = defineEmits(['regenerate'])
|
||||
|
||||
const content = computed(() => {
|
||||
return props.content.text
|
||||
})
|
||||
|
||||
const contentRef = ref(null)
|
||||
|
||||
// 处理重新生成
|
||||
const handleRegenerate = () => {
|
||||
emits('regenerate', props.messageId)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const imgs = contentRef.value.querySelectorAll('img')
|
||||
for (let i = 0; i < imgs.length; i++) {
|
||||
@@ -201,6 +242,17 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
margin-top: 8px;
|
||||
padding-left: 5px;
|
||||
|
||||
.van-button {
|
||||
font-size: 12px;
|
||||
height: 24px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -789,6 +789,7 @@ const sendSSERequest = async (message) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 回答完毕,更新完整的消息内容
|
||||
if (data.type === 'complete') {
|
||||
chatData.value[chatData.value.length - 1] = data.body
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
</template>
|
||||
</van-nav-bar>
|
||||
|
||||
<!-- 移除分享面板 -->
|
||||
|
||||
<div class="chat-list-wrapper">
|
||||
<div id="message-list-box" :style="{ height: winHeight + 'px' }" class="message-list-box">
|
||||
<van-list
|
||||
@@ -41,6 +39,11 @@
|
||||
:content="item.content"
|
||||
:icon="item.icon"
|
||||
:org-content="item.orgContent"
|
||||
:message-id="item.id"
|
||||
:is-generating="isGenerating"
|
||||
:show-actions="item.showAction"
|
||||
:error="item.error"
|
||||
@regenerate="handleRegenerate"
|
||||
/>
|
||||
</van-cell>
|
||||
</van-list>
|
||||
@@ -50,23 +53,21 @@
|
||||
<div class="chat-box-wrapper">
|
||||
<van-sticky ref="bottomBarRef" :offset-bottom="0" position="bottom">
|
||||
<van-cell-group inset style="--van-cell-background: var(--van-cell-background-light)">
|
||||
<!-- <div class="flex flex-row p-2">
|
||||
<file-list :files="[{ url: 'https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png', name: 'test.png', ext: 'png', size: 1024 }]" />
|
||||
</div> -->
|
||||
<van-field v-model="prompt" center clearable placeholder="输入你的问题">
|
||||
<template #left-icon>
|
||||
<!-- <div class="flex flex-row">
|
||||
<span class="rounded-full size-6 flex items-center justify-center"><i class="iconfont icon-attachment-cl"></i></span>
|
||||
</div> -->
|
||||
</template>
|
||||
|
||||
<van-field
|
||||
v-model="prompt"
|
||||
center
|
||||
clearable
|
||||
placeholder="输入你的问题"
|
||||
@keyup.enter="sendMessage"
|
||||
>
|
||||
<template #button>
|
||||
<van-button size="small" type="primary" @click="sendMessage">发送</van-button>
|
||||
<van-button size="small" type="primary" v-if="!isGenerating" @click="sendMessage"
|
||||
>发送</van-button
|
||||
>
|
||||
</template>
|
||||
<template #extra>
|
||||
<div class="icon-box">
|
||||
<van-icon v-if="showStopGenerate" name="stop-circle-o" @click="stopGenerate" />
|
||||
<van-icon v-if="showReGenerate" name="play-circle-o" @click="reGenerate" />
|
||||
<van-icon v-if="isGenerating" name="stop-circle-o" @click="stopGenerate" />
|
||||
</div>
|
||||
</template>
|
||||
</van-field>
|
||||
@@ -74,19 +75,6 @@
|
||||
</van-sticky>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <van-overlay :show="showMic" z-index="100">-->
|
||||
<!-- <div class="mic-wrapper">-->
|
||||
<!-- <div class="image">-->
|
||||
<!-- <van-image-->
|
||||
<!-- width="100"-->
|
||||
<!-- height="100"-->
|
||||
<!-- src="/images/mic.gif"-->
|
||||
<!-- />-->
|
||||
<!-- </div>-->
|
||||
<!-- <van-button type="success" @click="stopVoice">说完了</van-button>-->
|
||||
<!-- </div>-->
|
||||
<!-- </van-overlay>-->
|
||||
</div>
|
||||
|
||||
<van-popup v-model:show="showPicker" position="bottom" class="popup">
|
||||
@@ -142,7 +130,6 @@ const modelValue = ref('')
|
||||
const title = ref(router.currentRoute.value.query['title'])
|
||||
const chatId = ref(router.currentRoute.value.query['chat_id'])
|
||||
const loginUser = ref(null)
|
||||
// const showMic = ref(false)
|
||||
const showPicker = ref(false)
|
||||
const columns = ref([roles.value, models.value])
|
||||
const selectedValues = ref([roleId.value, modelId.value])
|
||||
@@ -250,12 +237,6 @@ md.use(emoji)
|
||||
onMounted(() => {
|
||||
winHeight.value =
|
||||
window.innerHeight - navBarRef.value.$el.offsetHeight - bottomBarRef.value.$el.offsetHeight - 70
|
||||
|
||||
// 移除 Clipboard.js 相关内容
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// Remove WebSocket handler cleanup
|
||||
})
|
||||
|
||||
const newChat = (item) => {
|
||||
@@ -272,10 +253,7 @@ const newChat = (item) => {
|
||||
}
|
||||
|
||||
const onLoad = () => {
|
||||
// checkSession().then(() => {
|
||||
// connect()
|
||||
// }).catch(() => {
|
||||
// })
|
||||
// 加载更多消息的逻辑可以在这里实现
|
||||
}
|
||||
|
||||
const loadChatHistory = () => {
|
||||
@@ -294,6 +272,7 @@ const loadChatHistory = () => {
|
||||
text: role.hello_msg,
|
||||
},
|
||||
orgContent: role.hello_msg,
|
||||
showAction: false,
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -303,8 +282,8 @@ const loadChatHistory = () => {
|
||||
chatData.value.push(data[i])
|
||||
continue
|
||||
}
|
||||
|
||||
data[i].orgContent = data[i].content
|
||||
data[i].showAction = true
|
||||
data[i].orgContent = data[i].content.text
|
||||
data[i].content.text = md.render(processContent(data[i].content.text))
|
||||
chatData.value.push(data[i])
|
||||
}
|
||||
@@ -326,13 +305,12 @@ const loadChatHistory = () => {
|
||||
|
||||
// 创建 socket 连接
|
||||
const prompt = ref('')
|
||||
const showStopGenerate = ref(false) // 停止生成
|
||||
const showReGenerate = ref(false) // 重新生成
|
||||
const previousText = ref('') // 上一次提问
|
||||
const lineBuffer = ref('') // 输出缓冲行
|
||||
const canSend = ref(true)
|
||||
const isGenerating = ref(false)
|
||||
const isNewMsg = ref(true)
|
||||
const stream = ref(store.chatStream)
|
||||
const abortController = new AbortController()
|
||||
watch(
|
||||
() => store.chatStream,
|
||||
(newValue) => {
|
||||
@@ -340,18 +318,6 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
const disableInput = (force) => {
|
||||
canSend.value = false
|
||||
showReGenerate.value = false
|
||||
showStopGenerate.value = !force
|
||||
}
|
||||
|
||||
const enableInput = () => {
|
||||
canSend.value = true
|
||||
showReGenerate.value = previousText.value !== ''
|
||||
showStopGenerate.value = false
|
||||
}
|
||||
|
||||
// 将聊天框的滚动条滑动到最底部
|
||||
const scrollListBox = () => {
|
||||
document
|
||||
@@ -362,6 +328,7 @@ const scrollListBox = () => {
|
||||
// 发送 SSE 请求
|
||||
const sendSSERequest = async (message) => {
|
||||
try {
|
||||
isGenerating.value = true
|
||||
await fetchEventSource('/api/chat/message', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -369,6 +336,13 @@ const sendSSERequest = async (message) => {
|
||||
},
|
||||
body: JSON.stringify(message),
|
||||
openWhenHidden: true,
|
||||
// 重试机制,避免连接断开后一直重试
|
||||
retry: 3000,
|
||||
// 设置重试延迟为0,确保不重试
|
||||
retryDelay: 3000,
|
||||
// 设置最大重试次数为0
|
||||
maxRetries: 3,
|
||||
signal: abortController.signal,
|
||||
onopen(response) {
|
||||
if (response.ok && response.status === 200) {
|
||||
console.log('SSE connection opened')
|
||||
@@ -380,13 +354,13 @@ const sendSSERequest = async (message) => {
|
||||
try {
|
||||
const data = JSON.parse(msg.data)
|
||||
if (data.type === 'error') {
|
||||
showMessageError(data.body)
|
||||
enableInput()
|
||||
chatData.value[chatData.value.length - 1].error = data.body
|
||||
isGenerating.value = false
|
||||
return
|
||||
}
|
||||
|
||||
if (data.type === 'end') {
|
||||
enableInput()
|
||||
isGenerating.value = false
|
||||
lineBuffer.value = '' // 清空缓冲
|
||||
isNewMsg.value = true
|
||||
return
|
||||
@@ -429,32 +403,49 @@ const sendSSERequest = async (message) => {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 回答完毕,更新完整的消息内容
|
||||
if (data.type === 'complete') {
|
||||
data.body.showAction = true
|
||||
data.body.orgContent = data.body.content.text
|
||||
chatData.value[chatData.value.length - 1] = data.body
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing message:', error)
|
||||
enableInput()
|
||||
isGenerating.value = false
|
||||
showMessageError('消息处理出错,请重试')
|
||||
}
|
||||
},
|
||||
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
|
||||
showMessageError('连接已断开,请重试')
|
||||
},
|
||||
onclose() {
|
||||
console.log('SSE connection closed')
|
||||
enableInput()
|
||||
isGenerating.value = false
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
try {
|
||||
abortController.value && abortController.value.abort()
|
||||
} catch (e) {
|
||||
console.error('AbortController abort error:', e)
|
||||
}
|
||||
console.error('Failed to send message:', error)
|
||||
enableInput()
|
||||
isGenerating.value = false
|
||||
showMessageError('发送消息失败,请重试')
|
||||
}
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
const sendMessage = () => {
|
||||
if (canSend.value === false) {
|
||||
if (isGenerating.value) {
|
||||
showToast('AI 正在作答中,请稍后...')
|
||||
return
|
||||
}
|
||||
@@ -489,8 +480,6 @@ const sendMessage = () => {
|
||||
scrollListBox()
|
||||
})
|
||||
|
||||
disableInput(false)
|
||||
|
||||
// 发送 SSE 请求
|
||||
sendSSERequest({
|
||||
user_id: loginUser.value.id,
|
||||
@@ -506,16 +495,51 @@ const sendMessage = () => {
|
||||
return true
|
||||
}
|
||||
|
||||
// 重新生成
|
||||
const reGenerate = () => {
|
||||
disableInput(false)
|
||||
const text = '重新生成上述问题的答案:' + previousText.value
|
||||
// 追加消息
|
||||
// 停止生成
|
||||
const stopGenerate = function () {
|
||||
if (abortController.value) {
|
||||
abortController.value.abort()
|
||||
isGenerating.value = false
|
||||
httpGet('/api/chat/stop?session_id=' + getClientId())
|
||||
.then(() => {
|
||||
console.log('会话已中断')
|
||||
})
|
||||
.catch((e) => {
|
||||
showMessageError('中断对话失败:' + e.message)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 处理从ChatReply组件触发的重新生成
|
||||
const handleRegenerate = (messageId) => {
|
||||
if (isGenerating.value) {
|
||||
showToast('AI 正在作答中,请稍后...')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('messageId', messageId)
|
||||
console.log('chatData.value', chatData.value)
|
||||
|
||||
// 判断 messageId 是整数
|
||||
if (messageId !== '' && isNaN(messageId)) {
|
||||
showToast('消息 ID 不合法,无法重新生成')
|
||||
return
|
||||
}
|
||||
|
||||
chatData.value = chatData.value.filter((item) => item.id < messageId && !item.isHello)
|
||||
const userPrompt = chatData.value.pop()
|
||||
|
||||
// 添加空回复消息
|
||||
const _role = getRoleById(roleId.value)
|
||||
chatData.value.push({
|
||||
type: 'prompt',
|
||||
chat_id: chatId,
|
||||
role_id: roleId.value,
|
||||
type: 'reply',
|
||||
id: randString(32),
|
||||
icon: loginUser.value.avatar,
|
||||
content: renderInputText(text),
|
||||
icon: _role['icon'],
|
||||
content: {
|
||||
text: '',
|
||||
},
|
||||
})
|
||||
|
||||
// 发送 SSE 请求
|
||||
@@ -524,13 +548,12 @@ const reGenerate = () => {
|
||||
role_id: roleId.value,
|
||||
model_id: modelId.value,
|
||||
chat_id: chatId.value,
|
||||
prompt: previousText.value,
|
||||
last_msg_id: messageId,
|
||||
prompt: userPrompt.content.text,
|
||||
stream: stream.value,
|
||||
})
|
||||
}
|
||||
|
||||
// 移除 showShare、shareOptions、shareChat 相关内容
|
||||
|
||||
const getRoleById = function (rid) {
|
||||
for (let i = 0; i < roles.value.length; i++) {
|
||||
if (roles.value[i]['id'] === rid) {
|
||||
|
||||
Reference in New Issue
Block a user