|
|
|
@ -1,12 +1,16 @@ |
|
|
|
/** |
|
|
|
/** |
|
|
|
* Chat 聊天页面 |
|
|
|
* Chat 聊天页面 — 活动智能体交互 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
|
|
|
|
const app = getApp<IAppOption>() |
|
|
|
|
|
|
|
const plugin = requirePlugin('WechatSI') |
|
|
|
|
|
|
|
const recognitionManager = plugin.getRecordRecognitionManager() |
|
|
|
|
|
|
|
|
|
|
|
interface Message { |
|
|
|
interface Message { |
|
|
|
id: string |
|
|
|
id: string |
|
|
|
role: 'user' | 'ai' |
|
|
|
type: 'u_msg' | 'a_msg' |
|
|
|
content: string |
|
|
|
role: 'user' | 'agent' |
|
|
|
timestamp: number |
|
|
|
msg: string |
|
|
|
|
|
|
|
time: string |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Page({ |
|
|
|
Page({ |
|
|
|
@ -17,52 +21,109 @@ Page({ |
|
|
|
inputText: '', |
|
|
|
inputText: '', |
|
|
|
/** 输入模式:text 或 voice */ |
|
|
|
/** 输入模式:text 或 voice */ |
|
|
|
inputMode: 'voice' as 'text' | 'voice', |
|
|
|
inputMode: 'voice' as 'text' | 'voice', |
|
|
|
/** 是否正在录音 */ |
|
|
|
/** 是否正在录音识别 */ |
|
|
|
isRecording: false, |
|
|
|
isRecording: false, |
|
|
|
/** 录音提示文本 */ |
|
|
|
/** 录音提示文本 */ |
|
|
|
recordingTip: '松手发送,上移取消', |
|
|
|
recordingTip: '松手结束,上移取消', |
|
|
|
/** 录音管理器 */ |
|
|
|
/** 是否取消本次识别 */ |
|
|
|
recorderManager: null as WechatMiniprogram.RecorderManager | null, |
|
|
|
cancelled: false, |
|
|
|
/** 滚动到指定消息 */ |
|
|
|
/** 滚动到指定消息 */ |
|
|
|
scrollToView: '', |
|
|
|
scrollToView: '', |
|
|
|
/** 页面顶部距离 */ |
|
|
|
/** 当前会话ID,为空则新建 */ |
|
|
|
pageTop: 0, |
|
|
|
sessionId: '', |
|
|
|
/** 导航栏背景 */ |
|
|
|
/** 是否正在等待AI回复 */ |
|
|
|
background: 'transparent', |
|
|
|
sending: false, |
|
|
|
|
|
|
|
/** 接口返回的活动ID,有值时显示草稿入口 */ |
|
|
|
|
|
|
|
activityId: '', |
|
|
|
}, |
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
onLoad() { |
|
|
|
_sessionId: '', |
|
|
|
// 初始化录音管理器
|
|
|
|
|
|
|
|
const recorderManager = wx.getRecorderManager() |
|
|
|
onLoad(options: Record<string, string>) { |
|
|
|
recorderManager.onStart(() => { |
|
|
|
const sessionId = options.session_id || '' |
|
|
|
this.setData({ isRecording: true, recordingTip: '松手发送,上移取消' }) |
|
|
|
this._sessionId = sessionId |
|
|
|
}) |
|
|
|
this.setData({ sessionId }) |
|
|
|
recorderManager.onStop((res) => { |
|
|
|
|
|
|
|
|
|
|
|
// 语音识别回调
|
|
|
|
|
|
|
|
recognitionManager.onStart = () => { |
|
|
|
|
|
|
|
this.setData({ cancelled: false, recordingTip: '松手结束,上移取消' }) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
recognitionManager.onStop = (res: any) => { |
|
|
|
this.setData({ isRecording: false }) |
|
|
|
this.setData({ isRecording: false }) |
|
|
|
if (res.duration < 1000) { |
|
|
|
if (this.data.cancelled) return |
|
|
|
wx.showToast({ title: '录音时间太短', icon: 'none' }) |
|
|
|
const result = (res.result || '').trim() |
|
|
|
return |
|
|
|
if (result) { |
|
|
|
|
|
|
|
const separator = this.data.inputText ? ' ' : '' |
|
|
|
|
|
|
|
this.setData({ inputText: this.data.inputText + separator + result, inputMode: 'text' }) |
|
|
|
} |
|
|
|
} |
|
|
|
// 发送语音消息
|
|
|
|
} |
|
|
|
this.sendVoiceMessage(res.tempFilePath, res.duration) |
|
|
|
recognitionManager.onError = (err: any) => { |
|
|
|
}) |
|
|
|
|
|
|
|
recorderManager.onError((err) => { |
|
|
|
|
|
|
|
this.setData({ isRecording: false }) |
|
|
|
this.setData({ isRecording: false }) |
|
|
|
wx.showToast({ title: '录音失败', icon: 'none' }) |
|
|
|
if (err.errCode === -30003) return |
|
|
|
console.error('录音错误:', err) |
|
|
|
wx.showToast({ title: '语音识别失败', icon: 'none' }) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 登录后加载历史
|
|
|
|
|
|
|
|
app.waitLogin({ type: 1 }).then(() => { |
|
|
|
|
|
|
|
if (sessionId) { |
|
|
|
|
|
|
|
this.loadSessionDetail(sessionId) |
|
|
|
|
|
|
|
} |
|
|
|
}) |
|
|
|
}) |
|
|
|
this.setData({ recorderManager }) |
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** 从 a_msg 的 msg 字段中提取展示文本(可能为 JSON 字符串) */ |
|
|
|
|
|
|
|
parseAgentMsg(raw: string): string { |
|
|
|
|
|
|
|
if (!raw) return '' |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
const obj = JSON.parse(raw) |
|
|
|
|
|
|
|
if (obj && typeof obj.msg === 'string') return obj.msg |
|
|
|
|
|
|
|
} catch {} |
|
|
|
|
|
|
|
return raw |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
// 获取系统信息设置页面顶部距离
|
|
|
|
/** 从接口33加载会话详情(历史消息) */ |
|
|
|
const systemInfo = wx.getSystemInfoSync() |
|
|
|
async loadSessionDetail(sessionId: string) { |
|
|
|
this.setData({ pageTop: systemInfo.statusBarHeight + 44 }) |
|
|
|
try { |
|
|
|
|
|
|
|
const res = await wx.ajax({ |
|
|
|
|
|
|
|
url: '/session/detail', |
|
|
|
|
|
|
|
method: 'GET', |
|
|
|
|
|
|
|
data: { session_id: sessionId }, |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
if (res && res.messages) { |
|
|
|
|
|
|
|
const messages = this.handleRemoveFirstMessage(res.messages).map((m: Message) => ({ |
|
|
|
|
|
|
|
...m, |
|
|
|
|
|
|
|
msg: m.type === 'a_msg' ? this.parseAgentMsg(m.msg) : m.msg, |
|
|
|
|
|
|
|
})) |
|
|
|
|
|
|
|
this.setData({ messages }) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (res?.session?.activityId) { |
|
|
|
|
|
|
|
this.setData({ activityId: res.session.activityId }) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
this.scrollToBottom() |
|
|
|
|
|
|
|
} catch (err) { |
|
|
|
|
|
|
|
console.error('加载会话详情失败:', err) |
|
|
|
|
|
|
|
} |
|
|
|
}, |
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
handleRemoveFirstMessage(messages: Message[]): Message[] { |
|
|
|
* 切换到语音模式 |
|
|
|
if (messages.length === 0) return messages |
|
|
|
*/ |
|
|
|
return messages.slice(1) |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** 点击"最近对话"按钮 */ |
|
|
|
|
|
|
|
goHistory() { |
|
|
|
|
|
|
|
wx.navigateTo({ url: '/pages/chatHistory/index' }) |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** 点击"查看当前草稿"按钮 */ |
|
|
|
|
|
|
|
goActDraft() { |
|
|
|
|
|
|
|
if (this.data.activityId) { |
|
|
|
|
|
|
|
wx.navigateTo({ url: `/pages/actAdd/index?id=${this.data.activityId}` }) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** 切换到语音模式 */ |
|
|
|
switchToVoice() { |
|
|
|
switchToVoice() { |
|
|
|
// 如果 input 有内容,不允许切换到语音模式
|
|
|
|
|
|
|
|
if (this.data.inputText.trim()) { |
|
|
|
if (this.data.inputText.trim()) { |
|
|
|
wx.showToast({ title: '请先发送或清空内容', icon: 'none' }) |
|
|
|
wx.showToast({ title: '请先发送或清空内容', icon: 'none' }) |
|
|
|
return |
|
|
|
return |
|
|
|
@ -70,181 +131,185 @@ Page({ |
|
|
|
this.setData({ inputMode: 'voice' }) |
|
|
|
this.setData({ inputMode: 'voice' }) |
|
|
|
}, |
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** 切换到文本模式 */ |
|
|
|
* 切换到文本模式 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
switchToText() { |
|
|
|
switchToText() { |
|
|
|
this.setData({ inputMode: 'text' }) |
|
|
|
this.setData({ inputMode: 'text' }) |
|
|
|
}, |
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** 输入框内容变化 */ |
|
|
|
* 输入框内容变化 |
|
|
|
onInputChange(e: any) { |
|
|
|
*/ |
|
|
|
|
|
|
|
onInputChange(e: WechatMiniprogram.InputEvent) { |
|
|
|
|
|
|
|
this.setData({ inputText: e.detail.value }) |
|
|
|
this.setData({ inputText: e.detail.value }) |
|
|
|
}, |
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** 发送文本消息 */ |
|
|
|
* 发送文本消息 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
onSendText() { |
|
|
|
onSendText() { |
|
|
|
const { inputText } = this.data |
|
|
|
const { inputText, sending } = this.data |
|
|
|
if (!inputText.trim()) return |
|
|
|
if (!inputText.trim() || sending) return |
|
|
|
|
|
|
|
this.doInteract(inputText.trim()) |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
// 添加用户消息
|
|
|
|
/** 调用接口31: 活动智能体交互 */ |
|
|
|
|
|
|
|
async doInteract(uMsg: string) { |
|
|
|
|
|
|
|
this.setData({ sending: true, inputText: '' }) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 先将用户消息追加到列表
|
|
|
|
const userMessage: Message = { |
|
|
|
const userMessage: Message = { |
|
|
|
id: `user_${Date.now()}`, |
|
|
|
id: `temp_u_${Date.now()}`, |
|
|
|
|
|
|
|
type: 'u_msg', |
|
|
|
role: 'user', |
|
|
|
role: 'user', |
|
|
|
content: inputText.trim(), |
|
|
|
msg: uMsg, |
|
|
|
timestamp: Date.now(), |
|
|
|
time: this.formatTime(new Date()), |
|
|
|
} |
|
|
|
} |
|
|
|
this.setData({ |
|
|
|
const messages = [...this.handleRemoveFirstMessage(this.data.messages), userMessage] |
|
|
|
messages: [...this.data.messages, userMessage], |
|
|
|
this.setData({ messages }) |
|
|
|
inputText: '', |
|
|
|
this.scrollToBottom() |
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟 AI 回复
|
|
|
|
try { |
|
|
|
this.simulateAIResponse() |
|
|
|
const reqData: Record<string, any> = { u_msg: uMsg } |
|
|
|
}, |
|
|
|
if (this._sessionId) { |
|
|
|
|
|
|
|
reqData.session_id = this._sessionId |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
const res = await wx.ajax({ |
|
|
|
* chat-input 区域长按开始录音 |
|
|
|
url: '/activity/interact', |
|
|
|
*/ |
|
|
|
method: 'POST', |
|
|
|
onChatInputLongPress() { |
|
|
|
data: reqData, |
|
|
|
// 文本模式下有内容时,不允许录音
|
|
|
|
}) |
|
|
|
if (this.data.inputMode === 'text' && this.data.inputText.trim()) { |
|
|
|
|
|
|
|
wx.showToast({ title: '请先发送或清空内容', icon: 'none' }) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const { recorderManager } = this.data |
|
|
|
// 更新sessionId
|
|
|
|
if (!recorderManager) return |
|
|
|
if (res.session?.session_id) { |
|
|
|
|
|
|
|
this._sessionId = res.session.session_id |
|
|
|
|
|
|
|
this.setData({ sessionId: res.session.session_id }) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
recorderManager.start({ |
|
|
|
// 内容不合规提示
|
|
|
|
duration: 60000, |
|
|
|
if (res.resp === 'RESP_ILLEGAL') { |
|
|
|
sampleRate: 16000, |
|
|
|
wx.showToast({ title: '内容不合规,请重新输入', icon: 'none' }) |
|
|
|
numberOfChannels: 1, |
|
|
|
this.setData({ sending: false }) |
|
|
|
encodeBitRate: 48000, |
|
|
|
return |
|
|
|
format: 'mp3', |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
// 提取活动ID
|
|
|
|
* chat-input 区域触摸开始 |
|
|
|
if (res.data.activity_id) { |
|
|
|
*/ |
|
|
|
this.setData({ activityId: res.data.activity_id }) |
|
|
|
onChatInputStart() { |
|
|
|
|
|
|
|
// 用于检测长按,不做实际操作
|
|
|
|
|
|
|
|
// 长按逻辑由 bindlongpress 处理
|
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* chat-input 区域触摸结束 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
onChatInputEnd() { |
|
|
|
|
|
|
|
const { recorderManager, isRecording } = this.data |
|
|
|
|
|
|
|
if (!recorderManager || !isRecording) return |
|
|
|
|
|
|
|
recorderManager.stop() |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* chat-input 区域触摸取消 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
onChatInputCancel() { |
|
|
|
|
|
|
|
const { recorderManager, isRecording } = this.data |
|
|
|
|
|
|
|
if (!recorderManager || !isRecording) return |
|
|
|
|
|
|
|
recorderManager.stop() |
|
|
|
|
|
|
|
this.setData({ isRecording: false, recordingTip: '录音已取消' }) |
|
|
|
|
|
|
|
wx.showToast({ title: '录音已取消', icon: 'none' }) |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* voiceing 区域触摸移动(检测手指是否移出) |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
onVoiceingTouchMove(e: WechatMiniprogram.TouchEvent) { |
|
|
|
|
|
|
|
const { isRecording, recordingTip } = this.data |
|
|
|
|
|
|
|
if (!isRecording) return |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取触摸点位置
|
|
|
|
|
|
|
|
const touch = e.touches[0] |
|
|
|
|
|
|
|
const systemInfo = wx.getSystemInfoSync() |
|
|
|
|
|
|
|
const screenHeight = systemInfo.screenHeight |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// voiceing 区域高度为 448rpx,约 224px
|
|
|
|
|
|
|
|
// 当手指 Y 坐标小于 (屏幕高度 - voiceing高度) 时,说明手指移出了区域
|
|
|
|
|
|
|
|
const voiceingHeight = 224 // 448rpx ≈ 224px
|
|
|
|
|
|
|
|
const threshold = screenHeight - voiceingHeight |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (touch.clientY < threshold) { |
|
|
|
|
|
|
|
// 手指移出区域,准备取消
|
|
|
|
|
|
|
|
if (recordingTip !== '上移取消录音') { |
|
|
|
|
|
|
|
this.setData({ recordingTip: '上移取消录音' }) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
|
|
|
|
// 手指在区域内,恢复提示
|
|
|
|
// 用服务端返回的消息列表替换
|
|
|
|
if (recordingTip !== '松手发送,上移取消') { |
|
|
|
if (res.session && res.session.msg) { |
|
|
|
this.setData({ recordingTip: '松手发送,上移取消' }) |
|
|
|
const messages = this.handleRemoveFirstMessage(res.session.msg).map((m: Message) => ({ |
|
|
|
|
|
|
|
...m, |
|
|
|
|
|
|
|
msg: m.type === 'a_msg' ? this.parseAgentMsg(m.msg) : m.msg, |
|
|
|
|
|
|
|
})) |
|
|
|
|
|
|
|
this.setData({ messages }) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
this.scrollToBottom() |
|
|
|
* voiceing 区域触摸结束 |
|
|
|
} catch (err: any) { |
|
|
|
*/ |
|
|
|
// AI服务异常时,服务端可能返回session_id用于重试
|
|
|
|
onVoiceingTouchEnd(e: WechatMiniprogram.TouchEvent) { |
|
|
|
if (err?.data?.session_id) { |
|
|
|
const { recorderManager, isRecording, recordingTip } = this.data |
|
|
|
this._sessionId = err.data.session_id |
|
|
|
if (!recorderManager || !isRecording) return |
|
|
|
this.setData({ sessionId: err.data.session_id }) |
|
|
|
|
|
|
|
} |
|
|
|
// 获取触摸点位置
|
|
|
|
wx.showToast({ title: '发送失败,请重试', icon: 'none' }) |
|
|
|
const changedTouch = e.changedTouches[0] |
|
|
|
} finally { |
|
|
|
const systemInfo = wx.getSystemInfoSync() |
|
|
|
this.setData({ sending: false }) |
|
|
|
const screenHeight = systemInfo.screenHeight |
|
|
|
|
|
|
|
const voiceingHeight = 224 // 448rpx ≈ 224px
|
|
|
|
|
|
|
|
const threshold = screenHeight - voiceingHeight |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果手指移出区域,取消录音
|
|
|
|
|
|
|
|
if (changedTouch.clientY < threshold || recordingTip === '上移取消录音') { |
|
|
|
|
|
|
|
recorderManager.stop() |
|
|
|
|
|
|
|
this.setData({ isRecording: false, recordingTip: '录音已取消' }) |
|
|
|
|
|
|
|
wx.showToast({ title: '录音已取消', icon: 'none' }) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
// 手指在区域内,正常结束录音并发送
|
|
|
|
|
|
|
|
recorderManager.stop() |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** 重置当前会话 */ |
|
|
|
* 发送语音消息 |
|
|
|
resetSession() { |
|
|
|
*/ |
|
|
|
if (!this._sessionId) return |
|
|
|
sendVoiceMessage(filePath: string, duration: number) { |
|
|
|
|
|
|
|
const userMessage: Message = { |
|
|
|
wx.showModal({ |
|
|
|
id: `user_${Date.now()}`, |
|
|
|
title: '提示', |
|
|
|
role: 'user', |
|
|
|
content: '确定要重置当前对话吗?', |
|
|
|
content: `[语音 ${Math.ceil(duration / 1000)}秒]`, |
|
|
|
success: async (res) => { |
|
|
|
timestamp: Date.now(), |
|
|
|
if (!res.confirm) return |
|
|
|
} |
|
|
|
try { |
|
|
|
this.setData({ |
|
|
|
await wx.ajax({ |
|
|
|
messages: [...this.data.messages, userMessage], |
|
|
|
url: '/activity/interact', |
|
|
|
|
|
|
|
method: 'POST', |
|
|
|
|
|
|
|
data: { |
|
|
|
|
|
|
|
session_id: this._sessionId, |
|
|
|
|
|
|
|
reset: true, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
this._sessionId = '' |
|
|
|
|
|
|
|
this.setData({ sessionId: '', messages: [], activityId: '' }) |
|
|
|
|
|
|
|
wx.showToast({ title: '已重置', icon: 'success' }) |
|
|
|
|
|
|
|
} catch { |
|
|
|
|
|
|
|
wx.showToast({ title: '重置失败', icon: 'none' }) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}, |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
// 模拟 AI 回复
|
|
|
|
/** 格式化时间 */ |
|
|
|
this.simulateAIResponse() |
|
|
|
formatTime(date: Date): string { |
|
|
|
|
|
|
|
const pad = (n: number) => n.toString().padStart(2, '0') |
|
|
|
|
|
|
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}` |
|
|
|
}, |
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** 滚动到底部 */ |
|
|
|
* 模拟 AI 回复 |
|
|
|
scrollToBottom() { |
|
|
|
*/ |
|
|
|
|
|
|
|
simulateAIResponse() { |
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
setTimeout(() => { |
|
|
|
const aiMessage: Message = { |
|
|
|
this.setData({ scrollToView: 'scroll-bottom-safe' }) |
|
|
|
id: `ai_${Date.now()}`, |
|
|
|
}, 100) |
|
|
|
role: 'ai', |
|
|
|
}, |
|
|
|
content: '好的,我已经收到您的消息,正在为您处理中...', |
|
|
|
|
|
|
|
timestamp: Date.now(), |
|
|
|
/** 阻止滚动穿透 */ |
|
|
|
} |
|
|
|
preventTouchMove() {}, |
|
|
|
this.setData({ |
|
|
|
|
|
|
|
messages: [...this.data.messages, aiMessage], |
|
|
|
// ===== 语音识别相关 =====
|
|
|
|
|
|
|
|
onVoiceTouchStart() {}, |
|
|
|
|
|
|
|
onVoiceLongPress() { |
|
|
|
|
|
|
|
wx.authorize({ scope: 'scope.record' }) |
|
|
|
|
|
|
|
.then(() => { |
|
|
|
|
|
|
|
this.setData({ isRecording: true, cancelled: false, recordingTip: '松手结束,上移取消' }) |
|
|
|
|
|
|
|
recognitionManager.start({ lang: 'zh_CN' }) |
|
|
|
}) |
|
|
|
}) |
|
|
|
}, 1000) |
|
|
|
.catch(() => { |
|
|
|
|
|
|
|
wx.showModal({ |
|
|
|
|
|
|
|
title: '提示', |
|
|
|
|
|
|
|
content: '需要录音权限才能使用语音输入,是否前往设置开启?', |
|
|
|
|
|
|
|
success: (res) => { |
|
|
|
|
|
|
|
if (res.confirm) wx.openSetting() |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
isAboveCancelZone(clientY: number) { |
|
|
|
|
|
|
|
const { screenHeight } = wx.getWindowInfo() |
|
|
|
|
|
|
|
return clientY < screenHeight - 224 |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onVoiceTouchMove(e: WechatMiniprogram.TouchEvent) { |
|
|
|
|
|
|
|
if (!this.data.isRecording) return |
|
|
|
|
|
|
|
const above = this.isAboveCancelZone(e.touches[0].clientY) |
|
|
|
|
|
|
|
const tip = above ? '松手取消识别' : '松手结束,上移取消' |
|
|
|
|
|
|
|
if (this.data.recordingTip !== tip) { |
|
|
|
|
|
|
|
this.setData({ recordingTip: tip }) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onVoiceTouchEnd(e: WechatMiniprogram.TouchEvent) { |
|
|
|
|
|
|
|
if (!this.data.isRecording) return |
|
|
|
|
|
|
|
const cancel = this.isAboveCancelZone(e.changedTouches[0].clientY) || this.data.recordingTip === '松手取消识别' |
|
|
|
|
|
|
|
if (cancel) { |
|
|
|
|
|
|
|
this.setData({ cancelled: true, inputText: '', isRecording: false, recordingTip: '识别已取消' }) |
|
|
|
|
|
|
|
recognitionManager.stop() |
|
|
|
|
|
|
|
wx.showToast({ title: '识别已取消', icon: 'none' }) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
recognitionManager.stop() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onVoiceTouchCancel() { |
|
|
|
|
|
|
|
if (!this.data.isRecording) return |
|
|
|
|
|
|
|
this.setData({ cancelled: true, inputText: '', isRecording: false, recordingTip: '识别已取消' }) |
|
|
|
|
|
|
|
recognitionManager.stop() |
|
|
|
|
|
|
|
wx.showToast({ title: '识别已取消', icon: 'none' }) |
|
|
|
}, |
|
|
|
}, |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|