/* * @Company: hugeInfo * @Author: lwh * @Date: 2025-04-15 20:04:56 * @LastEditTime: 2025-04-18 11:11:09 * @LastEditors: lwh * @Version: 1.0.0 * @Description: */ const app = getApp(); const $$ = require('../../utils/util'); // 详情接口 function getByIdApi(param) { return $$.request({ url: 'caseInfo/getCaseInfo', type: 'get', submitData: param || {}, service: 'mediate', }); } // 获取用户信息 function getUserInfoApi() { return $$.request({ url: 'paUser/personal', type: 'get', service: 'cust', }); } function getawApi(submitData) { return $$.request({ url: 'case-law/get-law', type: 'post', ai: true, submitData, service: 'mediate', }); } // 获取案例 function getCaseApi(submitData) { return $$.request({ url: 'case-law/get-case', type: 'post', ai: true, submitData, service: 'mediate', }); } // 获取调解策略 function getMediateStrategypi(submitData) { return $$.request({ url: 'case-law/getMediateStrategy', type: 'post', ai: true, submitData, service: 'mediate', }); } // AI对话接口 function getStreamChatApi(submitData) { return $$.request({ url: 'case-law/streamChat', type: 'post', ai: true, submitData, service: 'mediate', }); } Page({ recordMannager: wx.getRecorderManager(), data: { chatList: [], inputValue: '', scrollToView: '', userInfo: {}, imageUrl: $$.url.img, imgUrl: app.globalData.imgUrl, demoImgUrl: $$.url.img + 'Aimge.png', demoAIImgUrl: $$.url.img + '1.gif', typingMessage: null, typingIndex: 0, typingTimer: null, isTyping: false, isRecording: false, showModal: false, //按住说话显示 recordManager: null, caseId: '', // 案件ID caseDes: '', // 案件描述 isCardExpanded: false, // 控制卡片展开/收起状态 }, onLoad(options) { // 接收传入的参数 this.getById(options); // 初始化录音管理器 this.recordManager = wx.getRecorderManager(); // 监听录音结束事件 this.recordManager.onStop((res) => { this.setData({ isRecording: false, }); if (res.duration < 1000) { wx.showToast({ title: '录音时间太短', icon: 'none', }); return; } // 调用语音识别接口 wx.showLoading({ title: '识别中...', }); // 这里需要替换为实际的语音识别接口 // 模拟语音识别结果 setTimeout(() => { wx.hideLoading(); this.setData({ inputValue: '这是一段模拟的语音识别结果', }); }, 1500); }); }, // 获取纠纷案件详情 async getById(props) { $$.showLoading(); const res = await getByIdApi({ id: props.caseId, }); $$.hideLoading(); if (res.type) { let data = res.data || {}; this.setData({ submitData: data, oneList: [...data.personList, ...data.agentList], caseId: props.caseId, }); this.getFilesId(data.id); } }, // 开始录音 startRecord() { this.setData({ isRecording: true, }); this.recordManager.start({ duration: 60000, // 最长录音时间 sampleRate: 16000, numberOfChannels: 1, encodeBitRate: 48000, format: 'mp3', }); }, // 停止录音 stopRecord() { this.recordManager.stop(); }, // 获取用户信息 async getUserInfo() { $$.showLoading(); const res = await getUserInfoApi(); $$.hideLoading(); if (res.type) { this.setData({ userInfo: res.data, }); } }, // 输入框内容变化 onInputChange(e) { this.setData({ inputValue: e.detail.value, }); }, // 发送消息 async sendMessage() { const { inputValue, chatList } = this.data; if (!inputValue.trim()) return; // 清空相关数据 this.setData({ caseData: [], AIData: [], chatList: [], }); // 添加用户消息 this.addMessage('user', inputValue); // 清空输入框 this.setData({ inputValue: '', }); // 调用AI对话接口 const res = await getStreamChatApi({ // caseText: this.data?.submitData?.caseDes, caseText: '在广州市白云区松洲街松南菜市场,张平云因隔壁档口苏振昌把泡沫箱摆在其档口旁边,双方因泡沫箱摆放位置发生纠纷,苏振昌故意损坏了张平云档口的灯泡,张平云报警求助。民警将双方带回派出所进行调解。', query: inputValue, // conversationId: this.data.caseId, }); if (res.type) { // this.setData({ // chatList: [...chatList, { type: 'ai', content: res.data, time: new Date().getTime() }], // }); } // 模拟AI回复 setTimeout(() => { this.startTyping('感谢您的咨询,我会尽快为您解答。'); }, 1000); }, // 开始逐字显示 startTyping(content) { // 清除之前的定时器 if (this.data.typingTimer) { clearInterval(this.data.typingTimer); } // 设置正在打字状态 this.setData({ isTyping: true, }); // 添加一条空消息 const newMessage = { type: 'ai', content: '', time: new Date().getTime(), }; // 只保留最新的两条消息 const chatList = [...this.data.chatList, newMessage].slice(-2); this.setData({ chatList, typingMessage: content, typingIndex: 0, }); // 开始逐字显示 const timer = setInterval(() => { const { typingIndex, typingMessage, chatList } = this.data; if (typingIndex < typingMessage.length) { const newContent = typingMessage.substring(0, typingIndex + 1); // 更新最后一条消息的内容 const updatedChatList = [...chatList]; updatedChatList[updatedChatList.length - 1].content = newContent; this.setData({ chatList: updatedChatList, typingIndex: typingIndex + 1, }); // 减少滚动频率,只在段落结束或每30个字符时滚动一次 if (typingIndex % 30 === 0 || typingMessage.charAt(typingIndex) === '\n' || typingIndex === typingMessage.length - 1) { this.scrollToBottom(false); } } else { clearInterval(timer); this.setData({ typingMessage: null, typingIndex: 0, typingTimer: null, isTyping: false, }); // 完成打字后滚动到底部(使用平滑滚动) this.scrollToBottom(true); } }, 50); this.setData({ typingTimer: timer, }); }, // 添加消息 addMessage(type, content) { const { chatList } = this.data; const newMessage = { type, content, time: new Date().getTime(), }; // 只保留最新的两条消息 const newChatList = [...chatList, newMessage].slice(-2); this.setData({ chatList: newChatList, scrollToView: `msg-${newChatList.length - 1}`, }); // 自动滚动到底部 this.scrollToBottom(); }, // 滚动到底部 scrollToBottom(smooth = true) { setTimeout(() => { // 设置滚动位置 this.setData({ scrollToView: 'card-bottom', }); // 阻止频繁滚动引起的抖动 if (!this._scrollDebounce) { this._scrollDebounce = true; setTimeout(() => { this._scrollDebounce = false; }, 300); } }, 50); }, // 录音结束触发 _endRecord(e) { this._transferText(e); }, touchStart(e) { let that = this; wx.getSetting({ success(res) { if (res.authSetting['scope.record'] === false) { $$.hideLoading(); $$.showModal({ content: '抱歉!此功能需授权麦克风录音功能', confirmText: '跳转授权', success: (res) => { if (res.confirm) { wx.openSetting({ success(res) { if (res.authSetting['scope.record']) { $$.showToast({ title: '授权成功', }); } else { $$.showToast({ title: '授权失败', }); } }, }); } }, }); return false; } that.setData({ showModal: true, }); // 开始说话 const options = { duration: 60000, sampleRate: 16000, numberOfChannels: 1, encodeBitRate: 96000, format: 'pcm', }; that.recordMannager.start(options); that.recordMannager.onStart(() => console.log('开始录音')); that.recordMannager.onError((e) => { console.log('onError', e); $$.showToast({ title: '抱歉!录音时间过短,请重新录入', }); that.setData({ second: 60, showModal: false, }); }); }, }); }, touchEnd() { let that = this; that.recordMannager.onStop((e) => that._endRecord(e)); that.recordMannager.stop(); that.setData({ showModal: false, }); console.log('结束录音'); }, // 语音转文字 _transferText(e) { console.log('开始识别', e); $$.showLoading(); let speakUrl = e.tempFilePath; let that = this; wx.uploadFile({ url: `${$$.baseUrl}${$$.url.sys}/api/wechat/xfyun/speech`, filePath: speakUrl, name: 'fileNames', header: { Authorization: app.globalData.token, }, complete(res) { $$.hideLoading(); if (res.errMsg === 'uploadFile:ok') { const { code, data, msg } = JSON.parse(res.data); if (code === '0' || code === 0) { that.setData({ inputValue: that.data.inputValue + data || '', // number: (that.data.value + data || '').length, }); } else { $$.showToast({ icon: 'error', title: msg, }); } } else { $$.showToast({ icon: 'error', title: '录音转写失败', }); } }, }); }, // 返回上一页 handleBack() { wx.navigateBack(); }, onShow() { this.getUserInfo(); // 添加欢迎消息,使用逐字显示效果 setTimeout(() => { this.startTyping('您好,我是解纷数智人,有什么可以帮您?'); }, 500); }, onUnload() { // 清除定时器 if (this.data.typingTimer) { clearInterval(this.data.typingTimer); } }, // 处理快捷按钮点击 async handleQuickButton(e) { const { type } = e.currentTarget.dataset; let message = ''; // 清空相关数据 this.setData({ caseData: [], AIData: [], chatList: [], }); switch (type) { case 'case': message = '请帮我推荐一些类似的案例'; break; case 'law': message = '请提供相关的法律条文'; break; case 'strategy': message = '请给出调解策略建议'; break; } // 添加用户消息 this.addMessage('user', message); try { // 模拟AI回复 if (type === 'case') { const res = await getCaseApi({ caseClaim: this.data?.submitData?.caseClaim, caseDes: this.data?.submitData?.caseDes, caseId: this.data?.caseId, }); if (res.type) { if (!res.data || res.data.length === 0) { this.startTyping('抱歉,未找到相似的案例。'); return; } // 先显示正在分析的提示 await this.startTypingPromise('正在为您分析相似案例...\n\n'); // 设置数据并逐字显示 this.setData({ caseData: res.data, }); // 构建要显示的文本 const caseText = `为您找到${res.data.length}个相似案例,请点击查看详情。`; await this.startTypingPromise(caseText); } else { this.startTyping('抱歉,获取案例信息失败,请稍后重试。'); } } if (type === 'law') { const res = await getawApi({ caseId: this.data.caseId, }); if (res.type) { if (!res.data || res.data.length === 0) { this.startTyping('抱歉,未找到相关的法律条文。'); return; } // 先显示正在分析的提示 await this.startTypingPromise('正在为您查找相关法条...\n\n'); // 设置数据并逐字显示 this.setData({ AIData: res.data, }); // 构建要显示的文本 const lawText = `为您找到${res.data.length}条相关法律条文,请展开查看详情。`; await this.startTypingPromise(lawText); } else { this.startTyping('抱歉,获取法律条文失败,请稍后重试。'); } } if (type === 'strategy') { // 先展示提示信息 await this.startTypingPromise('正在为您分析调解策略...\n\n'); // 调用接口获取数据 const res = await getMediateStrategypi({ caseText: this.data?.submitData?.caseDes, // caseId: this.data.caseId, }); // 展示接口返回的数据 if (res.type) { // 确保上一条消息已完全展示后再显示新消息 setTimeout(() => { this.startTyping(res.data || ''); }, 500); } else { setTimeout(() => { this.startTyping('抱歉,获取调解策略失败,请稍后重试。'); }, 500); } } } catch (error) { console.error('请求失败:', error); this.startTyping('抱歉,服务器出现异常,请稍后重试。'); } }, // 返回Promise的逐字显示方法 startTypingPromise(content) { return new Promise((resolve) => { // 清除之前的定时器 if (this.data.typingTimer) { clearInterval(this.data.typingTimer); } // 设置正在打字状态 this.setData({ isTyping: true, }); // 添加一条空消息 const newMessage = { type: 'ai', content: '', time: new Date().getTime(), }; // 只保留最新的两条消息 const chatList = [...this.data.chatList, newMessage].slice(-2); this.setData({ chatList, typingMessage: content, typingIndex: 0, }); // 开始逐字显示 const timer = setInterval(() => { const { typingIndex, typingMessage, chatList } = this.data; if (typingIndex < typingMessage.length) { const newContent = typingMessage.substring(0, typingIndex + 1); // 更新最后一条消息的内容 const updatedChatList = [...chatList]; updatedChatList[updatedChatList.length - 1].content = newContent; this.setData({ chatList: updatedChatList, typingIndex: typingIndex + 1, }); // 减少滚动频率,只在段落结束或每30个字符时滚动一次 if (typingIndex % 30 === 0 || typingMessage.charAt(typingIndex) === '\n' || typingIndex === typingMessage.length - 1) { this.scrollToBottom(false); } } else { clearInterval(timer); this.setData({ typingMessage: null, typingIndex: 0, typingTimer: null, isTyping: false, }); // 完成打字后滚动到底部(使用平滑滚动) this.scrollToBottom(true); resolve(); // 完成打字效果后解析Promise } }, 50); this.setData({ typingTimer: timer, }); }); }, // 打开折叠法条 lawClick(e) { let item = e.currentTarget.dataset.item; let index = e.currentTarget.dataset.index; this.setData({ AIData: this.data.AIData.map((i, idx) => ({ ...i, show: idx === index ? (i.show ? false : true) : false, })), }); }, // 跳转案例详情 caseClick(e) { let url = e.currentTarget.dataset.url; let caseId = e.currentTarget.dataset.caseid; let caseType = e.currentTarget.dataset.casetype; let caseName = e.currentTarget.dataset.casename; wx.navigateTo({ url: url + '?caseId=' + caseId + '&type=' + caseType + '&caseName=' + caseName, }); }, // 切换卡片展开/收起状态 toggleCard() { this.setData({ isCardExpanded: !this.data.isCardExpanded, }); }, });