feat: 完成对话手动停止的功能

This commit is contained in:
zoujing
2025-08-06 11:47:07 +08:00
parent 6e658c9967
commit 65653525a0
6 changed files with 182 additions and 114 deletions

17
.editorconfig Normal file
View File

@@ -0,0 +1,17 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = space
indent_size = 4
tab_width = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
insert_final_newline = false

View File

@@ -26,7 +26,8 @@
maxlength="300" maxlength="300"
/> />
<view class="input-container-send" @click="sendMessage"> <view class="input-container-send" @click="sendMessage">
<image src='/static/input_send_icon.png'></image> <image v-if="props.isSessionActive" src='/static/input_stop_icon.png'></image>
<image v-else src='/static/input_send_icon.png'></image>
</view> </view>
</view> </view>
</template> </template>
@@ -36,7 +37,9 @@ import { ref, watch, nextTick, onMounted, onUnmounted } from 'vue'
const props = defineProps({ const props = defineProps({
inputMessage: String, inputMessage: String,
holdKeyboard: Boolean holdKeyboard: Boolean,
isSessionActive: Boolean,
stopRequest: Function
}) })
const emit = defineEmits(['update:inputMessage', 'send', 'noHideKeyboard', 'keyboardShow', 'keyboardHide']) const emit = defineEmits(['update:inputMessage', 'send', 'noHideKeyboard', 'keyboardShow', 'keyboardHide'])
@@ -46,6 +49,7 @@ const inputMessage = ref(props.inputMessage || '')
const isFocused = ref(false) const isFocused = ref(false)
const keyboardHeight = ref(0) const keyboardHeight = ref(0)
// 保持和父组件同步 // 保持和父组件同步
watch(() => props.inputMessage, (val) => { watch(() => props.inputMessage, (val) => {
inputMessage.value = val inputMessage.value = val
@@ -65,16 +69,24 @@ onMounted(() => {
}) })
const sendMessage = () => { const sendMessage = () => {
if (!inputMessage.value.trim()) return; if (props.isSessionActive) {
emit('send', inputMessage.value) // 如果会话进行中,调用停止请求函数
inputMessage.value = '' if (props.stopRequest) {
emit('update:inputMessage', inputMessage.value) props.stopRequest();
}
// 发送后保持焦点(可选) } else {
if (props.holdKeyboard && textareaRef.value) { // 否则发送新消息
nextTick(() => { if (!inputMessage.value.trim()) return;
textareaRef.value.focus() emit('send', inputMessage.value)
}) inputMessage.value = ''
emit('update:inputMessage', inputMessage.value)
// 发送后保持焦点(可选)
if (props.holdKeyboard && textareaRef.value) {
nextTick(() => {
textareaRef.value.focus()
})
}
} }
} }
@@ -128,7 +140,7 @@ defineExpose({
margin: 0 12px; margin: 0 12px;
/* 确保输入框在安全区域内 */ /* 确保输入框在安全区域内 */
margin-bottom: 8px; margin-bottom: 8px;
.input-container-voice { .input-container-voice {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -137,13 +149,13 @@ defineExpose({
height: 44px; height: 44px;
flex-shrink: 0; flex-shrink: 0;
align-self: flex-end; align-self: flex-end;
image { image {
width: 22px; width: 22px;
height: 22px; height: 22px;
} }
} }
.input-container-send { .input-container-send {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -152,13 +164,13 @@ defineExpose({
height: 44px; height: 44px;
flex-shrink: 0; flex-shrink: 0;
align-self: flex-end; align-self: flex-end;
image { image {
width: 28px; width: 28px;
height: 28px; height: 28px;
} }
} }
.textarea { .textarea {
flex: 1; flex: 1;
max-height: 92px; max-height: 92px;
@@ -169,4 +181,4 @@ defineExpose({
align-items: center; align-items: center;
} }
} }
</style> </style>

View File

@@ -2,7 +2,7 @@
<view class="chat-container" @touchend="handleTouchEnd"> <view class="chat-container" @touchend="handleTouchEnd">
<!-- 顶部的背景 --> <!-- 顶部的背景 -->
<ChatTopBgImg class="chat-container-bg"></ChatTopBgImg> <ChatTopBgImg class="chat-container-bg"></ChatTopBgImg>
<view class="chat-content"> <view class="chat-content">
<!-- 顶部自定义导航栏 --> <!-- 顶部自定义导航栏 -->
<view class="nav-bar-container" :style="{ <view class="nav-bar-container" :style="{
@@ -10,7 +10,7 @@
}"> }">
<ChatTopNavBar @openDrawer="openDrawer"></ChatTopNavBar> <ChatTopNavBar @openDrawer="openDrawer"></ChatTopNavBar>
</view> </view>
<!-- 消息列表可滚动区域 --> <!-- 消息列表可滚动区域 -->
<scroll-view <scroll-view
:scroll-top="scrollTop" :scroll-top="scrollTop"
@@ -19,15 +19,15 @@
class="area-msg-list" class="area-msg-list"
> >
<!-- welcome栏 --> <!-- welcome栏 -->
<ChatTopWelcome class="chat-container-top-bannar" <ChatTopWelcome class="chat-container-top-bannar"
:initPageImages="mainPageDataModel.initPageImages" :initPageImages="mainPageDataModel.initPageImages"
:welcomeContent="mainPageDataModel.welcomeContent"> :welcomeContent="mainPageDataModel.welcomeContent">
</ChatTopWelcome> </ChatTopWelcome>
<view class="area-msg-list-content" v-for="item in chatMsgList" :key="item.msgId" :id="item.msgId"> <view class="area-msg-list-content" v-for="item in chatMsgList" :key="item.msgId" :id="item.msgId">
<template v-if="item.msgType === MessageRole.AI"> <template v-if="item.msgType === MessageRole.AI">
<ChatCardAI class="message-item-ai" :text="item.msg"> <ChatCardAI class="message-item-ai" :text="item.msg">
<template #content v-if="item.toolCall"> <template #content v-if="item.toolCall">
<QuickBookingComponent v-if="item.toolCall.componentName === CompName.quickBookingCard"/> <QuickBookingComponent v-if="item.toolCall.componentName === CompName.quickBookingCard"/>
<DiscoveryCardComponent v-else-if="item.toolCall.componentName === CompName.discoveryCard"/> <DiscoveryCardComponent v-else-if="item.toolCall.componentName === CompName.discoveryCard"/>
<CreateServiceOrder v-else-if="item.toolCall.componentName === CompName.createWorkOrderCard"/> <CreateServiceOrder v-else-if="item.toolCall.componentName === CompName.createWorkOrderCard"/>
@@ -36,39 +36,41 @@
<!-- 这个是底部 --> <!-- 这个是底部 -->
<AttachListComponent v-if="item.question" :question="item.question" @replySent="handleReply"/> <AttachListComponent v-if="item.question" :question="item.question" @replySent="handleReply"/>
</template> </template>
</ChatCardAI> </ChatCardAI>
</template> </template>
<template v-else-if="item.msgType === MessageRole.ME"> <template v-else-if="item.msgType === MessageRole.ME">
<ChatCardMine class="message-item-mine" :text="item.msg"> <ChatCardMine class="message-item-mine" :text="item.msg">
</ChatCardMine> </ChatCardMine>
</template> </template>
<template v-else> <template v-else>
<ChatCardOther class="message-item-other" :text="item.msg"> <ChatCardOther class="message-item-other" :text="item.msg">
<ChatMoreTips @replySent="handleReply" :itemList="mainPageDataModel.guideWords"/> <ChatMoreTips @replySent="handleReply" :itemList="mainPageDataModel.guideWords"/>
<ActivityListComponent v-if="mainPageDataModel.activityList.length > 0" :activityList="mainPageDataModel.activityList"/> <ActivityListComponent v-if="mainPageDataModel.activityList.length > 0" :activityList="mainPageDataModel.activityList"/>
<RecommendPostsComponent v-if="mainPageDataModel.recommendTheme.length > 0" :recommendThemeList="mainPageDataModel.recommendTheme" /> <RecommendPostsComponent v-if="mainPageDataModel.recommendTheme.length > 0" :recommendThemeList="mainPageDataModel.recommendTheme" />
</ChatCardOther> </ChatCardOther>
</template> </template>
</view> </view>
</scroll-view> </scroll-view>
<!-- 输入框区域 --> <!-- 输入框区域 -->
<view class="footer-area"> <view class="footer-area">
<ChatQuickAccess @replySent="handleReplyInstruct"/> <ChatQuickAccess @replySent="handleReplyInstruct"/>
<ChatInputArea <ChatInputArea
ref="inputAreaRef" ref="inputAreaRef"
v-model="inputMessage" v-model="inputMessage"
:holdKeyboard="holdKeyboard" :holdKeyboard="holdKeyboard"
@send="sendMessageAction" :is-session-active="isSessionActive"
@noHideKeyboard="handleNoHideKeyboard" :stop-request="stopRequest"
@keyboardShow="handleKeyboardShow" @send="sendMessageAction"
@keyboardHide="handleKeyboardHide" @noHideKeyboard="handleNoHideKeyboard"
/> @keyboardShow="handleKeyboardShow"
@keyboardHide="handleKeyboardHide"
/>
</view> </view>
</view> </view>
</view> </view>
@@ -79,7 +81,7 @@
import { ref } from 'vue' import { ref } from 'vue'
import { defineEmits } from 'vue' import { defineEmits } from 'vue'
import { onLoad } from '@dcloudio/uni-app'; import { onLoad } from '@dcloudio/uni-app';
import { SCROLL_TO_BOTTOM, RECOMMEND_POSTS_TITLE, SEND_COMMAND_TEXT } from '@/constant/constant' import { SCROLL_TO_BOTTOM, RECOMMEND_POSTS_TITLE, SEND_COMMAND_TEXT } from '@/constant/constant'
import { MessageRole, MessageType, CompName } from '../../model/ChatModel'; import { MessageRole, MessageType, CompName } from '../../model/ChatModel';
import ChatTopWelcome from './ChatTopWelcome.vue'; import ChatTopWelcome from './ChatTopWelcome.vue';
@@ -92,44 +94,46 @@
import ChatMoreTips from './ChatMoreTips.vue'; import ChatMoreTips from './ChatMoreTips.vue';
import ChatInputArea from './ChatInputArea.vue' import ChatInputArea from './ChatInputArea.vue'
import CommandWrapper from '@/components/CommandWrapper/index.vue' import CommandWrapper from '@/components/CommandWrapper/index.vue'
import QuickBookingComponent from '../module/booking/QuickBookingComponent.vue' import QuickBookingComponent from '../module/booking/QuickBookingComponent.vue'
import DiscoveryCardComponent from '../module/discovery/DiscoveryCardComponent.vue'; import DiscoveryCardComponent from '../module/discovery/DiscoveryCardComponent.vue';
import ActivityListComponent from '../module/banner/ActivityListComponent.vue'; import ActivityListComponent from '../module/banner/ActivityListComponent.vue';
import RecommendPostsComponent from '../module/recommend/RecommendPostsComponent.vue'; import RecommendPostsComponent from '../module/recommend/RecommendPostsComponent.vue';
import AttachListComponent from '../module/attach/AttachListComponent.vue'; import AttachListComponent from '../module/attach/AttachListComponent.vue';
import CreateServiceOrder from '@/components/CreateServiceOrder/index.vue' import CreateServiceOrder from '@/components/CreateServiceOrder/index.vue'
import { agentChatStream } from '@/request/api/AgentChatStream'; import { agentChatStream } from '@/request/api/AgentChatStream';
import { mainPageData } from '@/request/api/MainPageDataApi'; import { mainPageData } from '@/request/api/MainPageDataApi';
import { conversationMsgList, recentConversation } from '@/request/api/ConversationApi'; import { conversationMsgList, recentConversation } from '@/request/api/ConversationApi';
/// 导航栏相关 /// 导航栏相关
const statusBarHeight = ref(20); const statusBarHeight = ref(20);
/// 输入框组件引用 /// 输入框组件引用
const inputAreaRef = ref(null); const inputAreaRef = ref(null);
const timer = ref(null) const timer = ref(null)
/// focus时点击页面的时候不收起键盘 /// focus时点击页面的时候不收起键盘
const holdKeyboard = ref(false) const holdKeyboard = ref(false)
/// 是否在键盘弹出,点击界面时关闭键盘 /// 是否在键盘弹出,点击界面时关闭键盘
const holdKeyboardFlag = ref(true) const holdKeyboardFlag = ref(true)
/// 键盘高度 /// 键盘高度
const keyboardHeight = ref(0) const keyboardHeight = ref(0)
/// 是否显示键盘 /// 是否显示键盘
const isKeyboardShow = ref(false) const isKeyboardShow = ref(false)
///(控制滚动位置) ///(控制滚动位置)
const scrollTop = ref(99999); const scrollTop = ref(99999);
/// 会话列表 /// 会话列表
const chatMsgList = ref([]) const chatMsgList = ref([])
/// 输入口的输入消息 /// 输入口的输入消息
const inputMessage = ref('') const inputMessage = ref('')
/// 加载中
let currentAIMsgIndex = 0
/// 从个渠道获取如二维,没有的时候就返回首页的数据 /// 从个渠道获取如二维,没有的时候就返回首页的数据
const sceneId = ref('') const sceneId = ref('')
/// agentId 首页接口中获取 /// agentId 首页接口中获取
@@ -140,13 +144,15 @@
const mainPageDataModel = ref({}) const mainPageDataModel = ref({})
// 会话进行中标志 // 会话进行中标志
let isSessionActive = false; const isSessionActive = ref(false);
// 请求任务引用
const requestTaskRef = ref(null);
/// 指令 /// 指令
let commonType = '' let commonType = ''
// 打开抽屉 // 打开抽屉
const emits = defineEmits(['openDrawer']) const emits = defineEmits(['openDrawer'])
const openDrawer = () => { const openDrawer = () => {
@@ -164,12 +170,12 @@
holdKeyboardFlag.value = true holdKeyboardFlag.value = true
}, 100) }, 100)
} }
// 点击输入框、发送按钮时,不收键盘 // 点击输入框、发送按钮时,不收键盘
const handleNoHideKeyboard = () => { const handleNoHideKeyboard = () => {
holdKeyboardFlag.value = false holdKeyboardFlag.value = false
} }
// 键盘弹起事件 // 键盘弹起事件
const handleKeyboardShow = (height) => { const handleKeyboardShow = (height) => {
keyboardHeight.value = height keyboardHeight.value = height
@@ -180,7 +186,7 @@
scrollToBottom() scrollToBottom()
}, 150) }, 150)
} }
// 键盘收起事件 // 键盘收起事件
const handleKeyboardHide = () => { const handleKeyboardHide = () => {
keyboardHeight.value = 0 keyboardHeight.value = 0
@@ -202,7 +208,7 @@
scrollToBottom() scrollToBottom()
}, 100) }, 100)
} }
/// 发送普通消息 /// 发送普通消息
const handleReply = (text) => { const handleReply = (text) => {
sendMessage(text) sendMessage(text)
@@ -229,7 +235,7 @@
if (!inputText.trim()) return; if (!inputText.trim()) return;
handleNoHideKeyboard() handleNoHideKeyboard()
sendMessage(inputText) sendMessage(inputText)
if(!isSessionActive) { if(!isSessionActive.value) {
inputMessage.value = '' inputMessage.value = ''
} }
// 发送消息后保持键盘状态 // 发送消息后保持键盘状态
@@ -248,14 +254,14 @@
} }
}); });
}); });
onMounted( async() => { onMounted( async() => {
getMainPageData() getMainPageData()
await loadRecentConversation() await loadRecentConversation()
loadConversationMsgList() loadConversationMsgList()
addNoticeListener() addNoticeListener()
}) })
const addNoticeListener = () => { const addNoticeListener = () => {
uni.$on(SCROLL_TO_BOTTOM, (value) => { uni.$on(SCROLL_TO_BOTTOM, (value) => {
setTimeout(() => { setTimeout(() => {
@@ -279,7 +285,7 @@
} }
}) })
} }
/// 获取最近一次的会话id /// 获取最近一次的会话id
const loadRecentConversation = async() => { const loadRecentConversation = async() => {
const res = await recentConversation() const res = await recentConversation()
@@ -287,7 +293,7 @@
conversationId.value = res.data.conversationId conversationId.value = res.data.conversationId
} }
} }
/// 加载历史消息的数据 /// 加载历史消息的数据
let historyCurrentPageNum = 1 let historyCurrentPageNum = 1
const loadConversationMsgList = async() => { const loadConversationMsgList = async() => {
@@ -305,7 +311,7 @@
scrollToBottom() scrollToBottom()
} }
} }
/// 初始化数据 首次数据加载的时候 /// 初始化数据 首次数据加载的时候
const initData = () => { const initData = () => {
const msg = { const msg = {
@@ -315,18 +321,18 @@
} }
chatMsgList.value.push(msg) chatMsgList.value.push(msg)
} }
/// 发送消息的参数拼接 /// 发送消息的参数拼接
const sendMessage = (message, isInstruct = false) => { const sendMessage = (message, isInstruct = false) => {
if (isSessionActive) { if (isSessionActive.value) {
uni.showToast({ uni.showToast({
title: '请等待当前回复完成', title: '请等待当前回复完成',
icon: 'none' icon: 'none'
}); });
return; return;
} }
isSessionActive = true; isSessionActive.value = true;
const newMsg = { const newMsg = {
msgId: `msg_${chatMsgList.value.length}`, msgId: `msg_${chatMsgList.value.length}`,
msgType: MessageRole.ME, msgType: MessageRole.ME,
@@ -337,10 +343,10 @@
} }
} }
chatMsgList.value.push(newMsg) chatMsgList.value.push(newMsg)
sendChat(message, isInstruct) sendChat(message, isInstruct)
console.log("发送的新消息:",JSON.stringify(newMsg)) console.log("发送的新消息:",JSON.stringify(newMsg))
} }
/// 打字机效果实现的变量 /// 打字机效果实现的变量
let loadingTimer = null; let loadingTimer = null;
let typeWriterTimer = null; let typeWriterTimer = null;
@@ -355,7 +361,7 @@
messageType: isInstruct ? 1 : 0, messageType: isInstruct ? 1 : 0,
messageContent: isInstruct ? commonType : message messageContent: isInstruct ? commonType : message
} }
// 插入AI消息 // 插入AI消息
const aiMsg = { const aiMsg = {
msgId: `msg_${chatMsgList.value.length}`, msgId: `msg_${chatMsgList.value.length}`,
@@ -368,7 +374,8 @@
} }
chatMsgList.value.push(aiMsg) chatMsgList.value.push(aiMsg)
const aiMsgIndex = chatMsgList.value.length - 1 const aiMsgIndex = chatMsgList.value.length - 1
currentAIMsgIndex = aiMsgIndex
// 动态加载中动画 // 动态加载中动画
let dotCount = 1; let dotCount = 1;
loadingTimer && clearInterval(loadingTimer); loadingTimer && clearInterval(loadingTimer);
@@ -376,16 +383,16 @@
dotCount = dotCount % 3 + 1; dotCount = dotCount % 3 + 1;
chatMsgList.value[aiMsgIndex].msg = '加载中' + '.'.repeat(dotCount); chatMsgList.value[aiMsgIndex].msg = '加载中' + '.'.repeat(dotCount);
}, 400); }, 400);
aiMsgBuffer = ''; aiMsgBuffer = '';
isTyping = false; isTyping = false;
if (typeWriterTimer) { if (typeWriterTimer) {
clearTimeout(typeWriterTimer); clearTimeout(typeWriterTimer);
typeWriterTimer = null; typeWriterTimer = null;
} }
// 流式接收内容 // 流式接收内容
agentChatStream(args, (chunk) => { const { promise, requestTask } = agentChatStream(args, (chunk) => {
console.log('分段内容:', chunk) console.log('分段内容:', chunk)
if (chunk && chunk.error) { if (chunk && chunk.error) {
chatMsgList.value[aiMsgIndex].msg = '请求错误,请重试'; chatMsgList.value[aiMsgIndex].msg = '请求错误,请重试';
@@ -393,7 +400,7 @@
loadingTimer = null; loadingTimer = null;
isTyping = false; isTyping = false;
typeWriterTimer = null; typeWriterTimer = null;
isSessionActive = false; // 出错也允许再次发送 isSessionActive.value = false; // 出错也允许再次发送
console.error('流式错误:', chunk.message, chunk.detail); console.error('流式错误:', chunk.message, chunk.detail);
return; return;
} }
@@ -406,7 +413,7 @@
} }
// 把新内容追加到缓冲区 // 把新内容追加到缓冲区
aiMsgBuffer += chunk.content; aiMsgBuffer += chunk.content;
// 启动打字机(只启动一次) // 启动打字机(只启动一次)
if (!isTyping) { if (!isTyping) {
isTyping = true; isTyping = true;
@@ -423,7 +430,7 @@
loadingTimer = null; loadingTimer = null;
isTyping = false; isTyping = false;
typeWriterTimer = null; typeWriterTimer = null;
// 补全:如果消息内容还停留在'加载中.'或为空,则给出友好提示 // 补全:如果消息内容还停留在'加载中.'或为空,则给出友好提示
const msg = chatMsgList.value[aiMsgIndex].msg; const msg = chatMsgList.value[aiMsgIndex].msg;
console.log('msg:', msg) console.log('msg:', msg)
@@ -433,33 +440,41 @@
chatMsgList.value[aiMsgIndex].msg = ''; chatMsgList.value[aiMsgIndex].msg = '';
} }
} }
// 如果有组件 // 如果有组件
if(chunk.toolCall) { if(chunk.toolCall) {
console.log('chunk.toolCall:', chunk.toolCall) console.log('chunk.toolCall:', chunk.toolCall)
chatMsgList.value[aiMsgIndex].toolCall = chunk.toolCall chatMsgList.value[aiMsgIndex].toolCall = chunk.toolCall
} }
// 如果有问题,则设置问题 // 如果有问题,则设置问题
if(chunk.question && chunk.question.length > 0) { if(chunk.question && chunk.question.length > 0) {
chatMsgList.value[aiMsgIndex].question = chunk.question chatMsgList.value[aiMsgIndex].question = chunk.question
} }
isSessionActive = false; isSessionActive.value = false;
scrollToBottom(); scrollToBottom();
} }
}, 50); }, 50);
} }
}).catch(e => { })
isSessionActive = false; // 出错也允许再次发送
console.log('error:', e) // 存储请求任务
requestTaskRef.value = requestTask;
// 可选处理Promise完成/失败, 已经在回调中处理数据,此处无需再处理
promise.then(data => {
console.log('请求完成');
}).catch(err => {
isSessionActive.value = false; // 出错也允许再次发送
console.log('error:', err)
}); });
// 打字机函数 // 打字机函数
function typeWriter() { function typeWriter() {
if (aiMsgBuffer.length > 0) { if (aiMsgBuffer.length > 0) {
chatMsgList.value[aiMsgIndex].msg += aiMsgBuffer[0]; chatMsgList.value[aiMsgIndex].msg += aiMsgBuffer[0];
aiMsgBuffer = aiMsgBuffer.slice(1); aiMsgBuffer = aiMsgBuffer.slice(1);
nextTick(() => { nextTick(() => {
scrollToBottom(); scrollToBottom();
}); });
@@ -468,12 +483,35 @@
// 等待新内容到来,不结束 // 等待新内容到来,不结束
typeWriterTimer = setTimeout(typeWriter, 30); typeWriterTimer = setTimeout(typeWriter, 30);
} }
} }
} }
// 停止请求函数
const stopRequest = () => {
if (requestTaskRef.value && requestTaskRef.value.abort) {
requestTaskRef.value.abort();
// 重置状态
isSessionActive.value = false;
const msg = chatMsgList.value[currentAIMsgIndex].msg;
if (!msg || msg === '加载中.' || msg.startsWith('加载中')) {
chatMsgList.value[currentAIMsgIndex].msg = '已终止请求,请重试';
}
// 清除计时器
if (loadingTimer) {
clearInterval(loadingTimer);
loadingTimer = null;
}
if (typeWriterTimer) {
clearTimeout(typeWriterTimer);
typeWriterTimer = null;
}
setTimeoutScrollToBottom()
}
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "styles/ChatMainList.scss"; @import "styles/ChatMainList.scss";
</style> </style>

View File

@@ -7,16 +7,17 @@ const API = '/agent/assistant/chat';
* 获取AI聊天流式信息仅微信小程序支持 * 获取AI聊天流式信息仅微信小程序支持
* @param {Object} params 请求参数 * @param {Object} params 请求参数
* @param {Function} onChunk 回调,每收到一段数据触发 * @param {Function} onChunk 回调,每收到一段数据触发
* @returns {Promise} * @returns {Object} 包含Promise和requestTask的对象
*/ */
function agentChatStream(params, onChunk) { function agentChatStream(params, onChunk) {
return new Promise((resolve, reject) => { let requestTask;
const promise = new Promise((resolve, reject) => {
const token = uni.getStorageSync('token'); const token = uni.getStorageSync('token');
let hasError = false; let hasError = false;
console.log("发送请求内容: ", params) console.log("发送请求内容: ", params)
// #ifdef MP-WEIXIN // #ifdef MP-WEIXIN
const requestTask = uni.request({ requestTask = uni.request({
url: BASE_URL + API, // 替换为你的接口地址 url: BASE_URL + API, // 替换为你的接口地址
method: 'POST', method: 'POST',
data: params, data: params,
@@ -28,22 +29,21 @@ function agentChatStream(params, onChunk) {
}, },
responseType: 'arraybuffer', responseType: 'arraybuffer',
success(res) { success(res) {
resolve(res.data); resolve(res.data);
}, },
fail(err) { fail(err) {
console.log("====> ", JSON.stringify(err)) console.log("====> ", JSON.stringify(err))
reject(err); reject(err);
}, },
complete(res) { complete(res) {
if(res.statusCode !== 200) { if(res.statusCode !== 200) {
console.log("====> ", JSON.stringify(res)) console.log("====> ", JSON.stringify(res))
if (onChunk) {
if (onChunk) { onChunk({ error: true, message: '服务器错误', detail: res });
onChunk({ error: true, message: '服务器错误', detail: res }); }
} reject(res);
reject(res); }
} }
}
}); });
requestTask.onHeadersReceived(res => { requestTask.onHeadersReceived(res => {
@@ -75,18 +75,19 @@ function agentChatStream(params, onChunk) {
}); });
// #endif // #endif
}); });
return {
promise,
requestTask
};
} }
// window.atob兼容性处理 // window.atob兼容性处理
const weAtob = (string) => { const weAtob = (string) => {
const b64re = const b64re = /^(?:[A-Za-z\d+/]{4})*?(?:[A-Za-z\d+/]{2}(?:==)?|[A-Za-z\d+/]{3}=?)?$/;
/^(?:[A-Za-z\d+/]{4})*?(?:[A-Za-z\d+/]{2}(?:==)?|[A-Za-z\d+/]{3}=?)?$/; const b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
const b64 =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
// 去除空白字符 // 去除空白字符
string = String(string).replace(/[\t\n\f\r ]+/g, ''); string = String(string).replace(/[\t\n\f\r ]+/g, '');
// 验证 Base64 编码 // 验证 Base64 编码
if (!b64re.test(string)) { if (!b64re.test(string)) {
throw new TypeError( throw new TypeError(
@@ -150,4 +151,4 @@ function parseSSEChunk(raw) {
return results; return results;
} }
export { agentChatStream } export { agentChatStream }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
static/input_stop_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB