部分优化
This commit is contained in:
@@ -34,9 +34,9 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ===== ai正在思考弹窗 ===== -->
|
||||
<view v-if="isThinking" class="ncd-overlay" @click="closeNewChatModal">
|
||||
<view class="ncd-card ai-card" @click.stop>
|
||||
<!-- ===== ai正在思考弹窗(仅本设备发起时显示) ===== -->
|
||||
<view v-if="isThinking && isSelfSent" class="ncd-overlay" @click.stop>
|
||||
<view class="ncd-card ai-card">
|
||||
<view class="thinking-content">
|
||||
<view class="loading-dots">
|
||||
<view class="dot"></view>
|
||||
@@ -44,10 +44,26 @@
|
||||
<view class="dot"></view>
|
||||
</view>
|
||||
<text class="thinking-text">AI 正在思考中...</text>
|
||||
<view class="stop-thinking-btn" @click="stopConversation">
|
||||
<text>⏹ 中断对话</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ===== 消息详情弹窗 ===== -->
|
||||
<view v-if="showMessageModal" class="ncd-overlay" @click="closeMessageDetail">
|
||||
<view class="ncd-card message-detail-card" @click.stop>
|
||||
<view class="ncd-header">
|
||||
<text class="ncd-title-zh">消息详情</text>
|
||||
<uni-icons type="closeempty" color="#ff0000" size="24" @click="closeMessageDetail"></uni-icons>
|
||||
</view>
|
||||
<scroll-view class="message-detail-content" scroll-y>
|
||||
<text>{{ detailMessageContent }}</text>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<view class="chat-page page-container">
|
||||
<!-- 弹窗出来时的笼罩层 -->
|
||||
@@ -102,6 +118,7 @@
|
||||
<!-- <view v-if="message.role ==='user'" class="iconfont icon-yonghuziliao"></view> -->
|
||||
</view>
|
||||
<view class="chat-content" :class="{'chat-content-user':message.role==='user'}"
|
||||
@click="showMessageDetail(message)"
|
||||
v-if="message.content && String(message.content).trim() !== ''">
|
||||
<!-- 文本内容 -->
|
||||
<view v-if="parseFileInfo(message.content).textContent"
|
||||
@@ -202,27 +219,24 @@
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="chat-interactive-container">
|
||||
<view class="chat-interactive-container" :class="{ 'interactive-disabled': isThinking }">
|
||||
<view class="chat-interactive-group">
|
||||
<view class="chat-interactive-btn" @click="uploadPhoto">拍照上传</view>
|
||||
<view class="chat-interactive-btn" @click="uploadFile()">上传文件</view>
|
||||
<view class="chat-interactive-btn" :class="{ disabled: isThinking }" @click="!isThinking && uploadPhoto()">拍照上传</view>
|
||||
<view class="chat-interactive-btn" :class="{ disabled: isThinking }" @click="!isThinking && uploadFile()">上传文件</view>
|
||||
</view>
|
||||
<view class="image-list" v-if="previewFileArray.length > 0">
|
||||
<view class="image-title">已选择: ({{ previewFileArray.length }})</view>
|
||||
<view class="image-grid">
|
||||
<view class="image-item" v-for="(item, index) in previewFileArray" :key="index">
|
||||
<view class="image-info">
|
||||
<text class="image-name">{{ item.name }}</text>
|
||||
<text class="delete-btn" @click.stop="deleteImage(index)">删除</text>
|
||||
</view>
|
||||
<view class="image-tags">
|
||||
<view class="image-tag" v-for="(item, index) in previewFileArray" :key="index">
|
||||
<text class="tag-name">{{ item.name }}</text>
|
||||
<text class="tag-close" @click.stop="deleteImage(index)">×</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="chat-input-container">
|
||||
<textarea class="message-input" placeholder="输入消息..." @confirm="sendMessage" confirm-type="send"
|
||||
<view class="chat-input-container" :class="{ 'input-disabled': isThinking }">
|
||||
<textarea class="message-input" :placeholder="isThinking ? (isSelfSent ? 'AI 正在回复中...' : '电脑正在运行,请稍后') : '输入消息...'" :disabled="isThinking"
|
||||
v-model="textMessage" auto-height></textarea>
|
||||
<view class="input-btn-group">
|
||||
<view class="iconfont icon-tingzhi icon-btn"></view>
|
||||
<view class="input-btn-group send-message-btn" :class="{ disabled: isThinking }" @click="!isThinking && sendMessage()">
|
||||
<view class="iconfont icon-fasong"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -242,7 +256,8 @@
|
||||
watch
|
||||
} from 'vue';
|
||||
import {
|
||||
onShow
|
||||
onShow,
|
||||
onUnload
|
||||
} from '@dcloudio/uni-app';
|
||||
import ChatSidebar from '@/components/ChatSidebar.vue'
|
||||
import {
|
||||
@@ -570,19 +585,7 @@
|
||||
}
|
||||
// 删除指定图片
|
||||
const deleteImage = (index) => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除这张图片吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
previewFileArray.value.splice(index, 1);
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
previewFileArray.value.splice(index, 1);
|
||||
};
|
||||
|
||||
const fileList = ref([])
|
||||
@@ -646,9 +649,22 @@
|
||||
// })
|
||||
|
||||
const isThinking = ref(false)
|
||||
const isSelfSent = ref(false) // 是否当前设备发起的 AI 对话(用于区分弹窗/仅锁输入)
|
||||
const isUploading = ref(false)
|
||||
const uploadedFiles = ref([])
|
||||
|
||||
// 消息详情弹窗
|
||||
const showMessageModal = ref(false)
|
||||
const detailMessageContent = ref('')
|
||||
const showMessageDetail = (message) => {
|
||||
detailMessageContent.value = message.content || ''
|
||||
showMessageModal.value = true
|
||||
}
|
||||
const closeMessageDetail = () => {
|
||||
showMessageModal.value = false
|
||||
detailMessageContent.value = ''
|
||||
}
|
||||
|
||||
const sendMessage = async () => {
|
||||
const message = textMessage.value.trim()
|
||||
if (!message && previewFileArray.value.length === 0) return
|
||||
@@ -811,16 +827,18 @@
|
||||
}
|
||||
socketStore.send(data)
|
||||
isThinking.value = true
|
||||
// 3分钟超时
|
||||
isSelfSent.value = true // 标记为当前设备发起
|
||||
// 10分钟超时
|
||||
aiResponseTimer = setTimeout(() => {
|
||||
uni.showToast({
|
||||
title: 'AI回复超时,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
isThinking.value = false
|
||||
isSelfSent.value = false
|
||||
textMessage.value = ''
|
||||
uploadedFiles.value = []
|
||||
}, 180000)
|
||||
}, 600000)
|
||||
} catch (error) {
|
||||
isThinking.value = false
|
||||
uni.showToast({
|
||||
@@ -830,21 +848,65 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 全局监听 socketStore.isThinking:true → false 时表示 AI 回复结束
|
||||
// 中断 AI 对话
|
||||
const stopConversation = async () => {
|
||||
console.log('中断对话 - 当前会话ID:', currentSessionId.value)
|
||||
try {
|
||||
await socketStore.send({
|
||||
type: 'stop',
|
||||
conversation_id: currentSessionId.value
|
||||
})
|
||||
console.log('中断指令已发送成功')
|
||||
} catch (err) {
|
||||
console.error('中断指令发送失败:', err)
|
||||
}
|
||||
// 立即关闭弹窗,清除超时
|
||||
clearTimeout(aiResponseTimer)
|
||||
isThinking.value = false
|
||||
isSelfSent.value = false
|
||||
textMessage.value = ''
|
||||
uploadedFiles.value = []
|
||||
// socketStore.isThinking 和 takeConversationMessages 由后端中断确认驱动(见 handleBusinessMessage)
|
||||
uni.showToast({ title: '已中断', icon: 'none', duration: 1500 })
|
||||
}
|
||||
|
||||
// 全局监听 socketStore.isThinking:跨设备同步思考状态,锁/解锁输入框
|
||||
watch(() => socketStore.isThinking, (newVal, oldVal) => {
|
||||
console.log("isThinking 变化:", oldVal, "→", newVal);
|
||||
// AI 回复结束:从 true 变为 false,且已有回复内容
|
||||
if (oldVal && !newVal && socketStore.messageString) {
|
||||
console.log("isThinking 变化:", oldVal, "→", newVal, "messageString:", socketStore.messageString);
|
||||
// false → true:后端开始回复(当前设备或其他设备触发),锁定输入框
|
||||
if (!oldVal && newVal) {
|
||||
console.log('AI开始回复(跨设备同步),锁定输入框');
|
||||
isThinking.value = true
|
||||
return
|
||||
}
|
||||
// true → false:AI 回复结束或中断,解锁输入框并刷新消息列表
|
||||
if (oldVal && !newVal) {
|
||||
clearTimeout(aiResponseTimer)
|
||||
console.log("ai返回的消息:", socketStore.messageString);
|
||||
console.log('AI回复结束,刷新消息列表');
|
||||
console.log('AI回复结束(正常/中断),解锁并刷新消息列表');
|
||||
takeConversationMessages()
|
||||
isThinking.value = false
|
||||
isSelfSent.value = false
|
||||
textMessage.value = ''
|
||||
uploadedFiles.value = []
|
||||
}
|
||||
})
|
||||
|
||||
// 监听 auth_fail,当 pc 端不在线时弹窗提示并取消思考状态
|
||||
watch(() => socketStore.authFailReason, (reason) => {
|
||||
if (reason === 'pc offline') {
|
||||
isThinking.value = false
|
||||
socketStore.authFailReason = ''
|
||||
nextTick(() => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: 'PC端不在线,不可聊天',
|
||||
showCancel: false,
|
||||
confirmText: '知道了'
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const textMessage = ref('')
|
||||
|
||||
// 获取用户历史会话列表
|
||||
@@ -1018,7 +1080,7 @@
|
||||
const takeConversationMessages = async () => {
|
||||
try {
|
||||
allmessages.value = await getConversationMessages(userToken.value, currentSessionId.value) || [];
|
||||
console.log("获取到的所有消息:", JSON.stringify(allmessages.value) );
|
||||
// console.log("获取到的所有消息:", JSON.stringify(allmessages.value) );
|
||||
// currentMessages.value = allmessages.value.slice(-pageInfoNumber);
|
||||
// 数据获取后执行滚动
|
||||
scrollToBottom();
|
||||
@@ -1124,10 +1186,12 @@
|
||||
watch(currentSessionId, (newId) => {
|
||||
if (newId) {
|
||||
uni.setStorageSync('currentSessionId', currentSessionId.value)
|
||||
// takeConversationMessages();
|
||||
switch (ChatType.value) {
|
||||
case 0:
|
||||
takeConversationMessages();
|
||||
// 切换 AI 会话时重新连接 socket(绑定新 conversationId)
|
||||
socketStore.disconnect()
|
||||
handleConnect()
|
||||
break;
|
||||
case 1:
|
||||
takeFriendMessages();
|
||||
@@ -1135,7 +1199,6 @@
|
||||
case 2:
|
||||
takeGroupMessages();
|
||||
break;
|
||||
// 可选:默认兜底
|
||||
default:
|
||||
takeConversationMessages();
|
||||
break;
|
||||
@@ -1167,6 +1230,10 @@
|
||||
if (currentSessionId.value) {
|
||||
takeConversationMessages();
|
||||
}
|
||||
// AI 对话页面:主动建立 socket 连接,确保消息实时推送
|
||||
if (userToken.value && currentSessionId.value && !socketStore.isConnected) {
|
||||
handleConnect()
|
||||
}
|
||||
} else if (chatType === 1) {
|
||||
await takeFriendList()
|
||||
takeFriendMessages();
|
||||
@@ -1177,6 +1244,11 @@
|
||||
await takeGroupMessages()
|
||||
}
|
||||
})
|
||||
|
||||
// 离开 AI 对话页面时断开 socket
|
||||
onUnload(() => {
|
||||
socketStore.disconnect()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
Reference in New Issue
Block a user