UniApp / 小程序集成
支持微信小程序、H5、App (iOS/Android),兼容 Vue 3 和 Vue 2。提供流式消息、会话持久化、自动重试。
安装
1npm install @lingshuai/chat-uniapp
快速开始
1<template>2 <view class="chat-container">3 <view v-if="!tempToken" class="loading">加载中...</view>4 <template v-else>5 <scroll-view scroll-y class="message-list">6 <view v-for="msg in messages" :key="msg.id" class="message">7 <text :class="msg.role">{{ msg.content }}</text>8 </view>9 </scroll-view>10 <view class="input-bar">11 <input v-model="inputText" placeholder="输入消息..." />12 <button @tap="handleSend" :disabled="loading">发送</button>13 </view>14 </template>15 </view>16</template>1718<script setup lang="ts">19import { ref, onMounted } from 'vue';20import { useChat } from '@lingshuai/chat-uniapp';2122const inputText = ref('');23const tempToken = ref('');2425// ⭐ 从后端动态获取临时Token26onMounted(async () => {27 const res = await uni.request({28 url: 'https://your-backend.com/api/auth/temp-token',29 method: 'POST',30 header: { 'content-type': 'application/json' }31 });32 tempToken.value = res.data.tempToken;33});3435const {36 messages, // 消息列表37 loading, // 发送中状态38 error, // 错误信息39 sendMessage, // 发送消息40 resetConversation, // 重置会话41 abort, // 中止请求42} = useChat({43 apiKey: tempToken, // ⭐ 使用动态获取的临时Token(tmp_live_xxx格式)44 appId: 'your-app-id',45 baseUrl: 'https://pinyou.xin/api',46});4748const handleSend = async () => {49 if (!inputText.value.trim()) return;50 const content = inputText.value;51 inputText.value = '';52 await sendMessage(content);53};54</script>
App 端真流式传输(RenderJS)
App 端默认使用 plus.net 模拟流式。要启用真正的流式传输,需注册 RenderStreamWorker 组件。
1// main.js2import RenderStreamWorker from '@lingshuai/chat-uniapp/components/RenderStreamWorker';3Vue.component('RenderStreamWorker', RenderStreamWorker);
1<template>2 <view class="chat-page">3 <RenderStreamWorker />4 <!-- 你的聊天 UI -->5 </view>6</template>
多模态(图片 / 视频)
支持两种方式:直接传入自托管文件 URL(推荐),或通过平台上传。自动适配微信小程序 / App / H5 平台。
1<script setup lang="ts">2import { ref, onMounted } from 'vue';3import { useChat } from '@lingshuai/chat-uniapp';45const tempToken = ref('');67// ⭐ 从后端动态获取临时Token8onMounted(async () => {9 const res = await uni.request({10 url: 'https://your-backend.com/api/auth/temp-token',11 method: 'POST'12 });13 tempToken.value = res.data.tempToken;14});1516const { sendWithUrls } = useChat({17 apiKey: tempToken, // ⭐ 使用动态获取的临时Token18 appId: 'your-app-id',19});2021// 直接传入您自己 OSS/CDN 上的文件 URL22await sendWithUrls('请描述这张图片', [23 { type: 'image', url: 'https://your-oss.com/photo.jpg', mimeType: 'image/jpeg' }24]);25</script>
1<script setup lang="ts">2import { ref, onMounted } from 'vue';3import { useChat } from '@lingshuai/chat-uniapp';45const tempToken = ref('');67// ⭐ 从后端动态获取临时Token8onMounted(async () => {9 const res = await uni.request({10 url: 'https://your-backend.com/api/auth/temp-token',11 method: 'POST'12 });13 tempToken.value = res.data.tempToken;14});1516const {17 uploading,18 sendImageFromAlbum,19 sendVideo,20} = useChat({21 apiKey: tempToken, // ⭐ 使用动态获取的临时Token22 appId: 'your-app-id',23});2425const handlePickImage = async () => {26 await sendImageFromAlbum('请分析这张图片');27};28</script>2930<template>31 <view>32 <button @tap="handlePickImage">选择图片</button>33 <text v-if="uploading">上传中...</text>34 </view>35</template>
通过平台上传的文件为临时存储,AI 处理完成后即删除。SDK 会在本地缓存附件路径(localPath),当前设备可继续预览,但换设备后不可用。如需跨设备持久化,请使用方式一。
ASR 语音转文字(实时转写)
长按说话 → 边说边看到实时转写文字 → 松开后将转写文本作为普通文本消息发送。SDK 会根据运行平台自动选择最优识别模式。
1<script setup lang="ts">2import { ref, onMounted } from 'vue';3import { useChat } from '@lingshuai/chat-uniapp';45const tempToken = ref('');67// ⭐ 从后端动态获取临时Token8onMounted(async () => {9 const res = await uni.request({10 url: 'https://your-backend.com/api/auth/temp-token',11 method: 'POST'12 });13 tempToken.value = res.data.tempToken;14});1516const {17 asrState, // 'idle' | 'connecting' | 'listening' | 'transcribing' | 'error'18 asrText, // 已确认的转写文本19 asrInterim, // 正在识别的临时文本(实时更新)20 startVoiceInput,21 stopVoiceInput,22 cancelVoiceInput,23} = useChat({24 apiKey: tempToken, // ⭐ 使用动态获取的临时Token25 appId: 'your-app-id',26});2728// 长按开始29const onTouchStart = () => startVoiceInput();3031// 松开结束 → 自动发送转写文本32const onTouchEnd = async () => {33 await stopVoiceInput();34};3536// 上滑取消37const onCancel = () => cancelVoiceInput();38</script>3940<template>41 <view>42 <!-- 实时转写预览 -->43 <view v-if="asrState !== 'idle'" class="asr-preview">44 <text>{{ asrText }}{{ asrInterim }}</text>45 <text v-if="asrState === 'connecting'" class="hint">连接中...</text>46 <text v-if="asrState === 'transcribing'" class="hint">识别中...</text>47 </view>4849 <!-- 长按说话按钮 -->50 <button51 @touchstart="onTouchStart"52 @touchend="onTouchEnd"53 :disabled="asrState === 'connecting' || asrState === 'transcribing'"54 >55 {{ asrState === 'listening' ? '松开发送' : '按住说话' }}56 </button>57 </view>58</template>
工作流程
- 调用
startVoiceInput()→ 建立 WebSocket 连接 → 收到 ready 后自动开始录音 - 录音过程中,音频帧实时发送到后端 → DashScope ASR 返回中间结果(partial)和确认结果(final) →
asrInterim/asrText实时更新 - 调用
stopVoiceInput()→ 停止录音 → 等待最终结果 → 自动调用sendMessage()发送文本
各平台 ASR 模式
微信小程序
WebSocket 实时流式识别。原生 onFrameRecorded 支持 PCM 帧回调,边说边转写。
H5
文件上传模式。录完整段后上传转写,体验流畅。
App (iOS/Android)
默认文件上传模式。如需实时转写,可集成 Recorder-UniCore 插件(见下方)。
App 端实时 ASR:集成 Recorder-UniCore 插件
UniApp App 端原生 RecorderManager 不支持 onFrameRecorded,无法获取实时 PCM 帧。如需 App 端实时转写,推荐使用 Recorder-UniCore 插件,通过其 onProcess 回调获取实时 PCM 数据。
1import { useChat } from '@lingshuai/chat-uniapp';2import { Recorder } from 'recorder-uni-core'; // 需先安装插件34const { asrText, asrInterim, asrState } = useChat({ ... });5const client = /* 获取 ApiClient 实例 */;67// 1. 建立 ASR WebSocket 连接8client.startAsrSession({9 format: 'pcm',10 sampleRate: 16000,11 onMessage: (msg) => {12 if (msg.type === 'partial') asrInterim.value = msg.text || '';13 if (msg.type === 'final') {14 asrText.value += msg.text || '';15 asrInterim.value = '';16 }17 if (msg.type === 'complete') asrState.value = 'idle';18 },19 onError: (err) => console.error(err),20});2122// 2. 使用 Recorder-UniCore 录音,实时喂入音频帧23const rec = Recorder({ type: 'pcm', sampleRate: 16000, bitRate: 16 });24rec.open(() => {25 rec.start();26});27rec.onProcess = (buffers, powerLevel, duration, sampleRate) => {28 // buffers 是 PCM Int16 数据,转为 ArrayBuffer 发送29 const pcm = buffers[buffers.length - 1];30 const ab = new ArrayBuffer(pcm.length * 2);31 const view = new DataView(ab);32 for (let i = 0; i < pcm.length; i++) {33 view.setInt16(i * 2, pcm[i], true);34 }35 client.sendAsrAudioChunk(ab);36};3738// 3. 停止时39rec.stop(() => {40 client.stopAsrSession();41});
注意:Recorder-UniCore 为第三方插件,SDK 不强制依赖。上述代码仅为集成参考,请根据实际项目调整。
TTS 语音播报
UniApp SDK 使用 WebSocket 流式 TTS,通过 PCM 实时播放,支持自动播报和单条消息播放。必须在页面中放置 <TtsAudioPlayer /> 组件才能正常播放音频!
⚠️ 重要:TtsAudioPlayer 组件
TTS 功能依赖 <TtsAudioPlayer /> 组件来处理跨端音频播放。如果不放置此组件,TTS 将无法播放音频!
1<template>2 <view class="chat-page">3 <!-- 必须放置 TtsAudioPlayer 组件! -->4 <!-- 它会自动处理 App端 renderjs + Web Audio API 的 PCM 播放 -->5 <TtsAudioPlayer />67 <!-- 你的聊天内容 -->8 <view v-for="msg in messages" :key="msg.id">9 <text>{{ msg.content }}</text>10 </view>11 </view>12</template>1314<script setup lang="ts">15import { ref, onMounted } from 'vue';16// 引入组件17import { TtsAudioPlayer } from '@lingshuai/chat-uniapp/components/TtsAudioPlayer';18import { useChat } from '@lingshuai/chat-uniapp';1920const tempToken = ref('');2122// ⭐ 从后端动态获取临时Token23onMounted(async () => {24 const res = await uni.request({25 url: 'https://your-backend.com/api/auth/temp-token',26 method: 'POST'27 });28 tempToken.value = res.data.tempToken;29});3031const { messages, playTts, stopTts, ttsPlayingMessageId } = useChat({32 apiKey: tempToken, // ⭐ 使用动态获取的临时Token33 appId: 'your-app-id',34});35</script>
1<template>2 <view class="chat-page">3 <!-- 【必须】TTS 音频播放组件 -->4 <TtsAudioPlayer />56 <!-- TTS 控制按钮 -->7 <view class="tts-controls">8 <button @tap="toggleTts">9 {{ ttsMuted ? '🔇 开启语音' : '🔊 关闭语音' }}10 </button>11 </view>1213 <!-- 消息列表 -->14 <view v-for="msg in messages" :key="msg.id" class="message">15 <text>{{ msg.content }}</text>16 <!-- AI 消息显示播放按钮 -->17 <view v-if="msg.role === 'assistant'" class="msg-actions">18 <button @tap="handlePlay(msg)" :disabled="ttsPlayState === 'loading'">19 <text v-if="ttsPlayingMessageId === msg.id">20 {{ ttsPlayState === 'loading' ? '⏳ 加载中...' : '🔊 播放中...' }}21 </text>22 <text v-else>▶️ 播放</text>23 </button>24 <button v-if="ttsPlayingMessageId === msg.id" @tap="stopTts">⏹️ 停止</button>25 </view>26 </view>27 </view>28</template>2930<script setup lang="ts">31import { ref, onMounted } from 'vue';32import { TtsAudioPlayer } from '@lingshuai/chat-uniapp/components/TtsAudioPlayer';33import { useChat } from '@lingshuai/chat-uniapp';3435const tempToken = ref('');3637// ⭐ 从后端动态获取临时Token38onMounted(async () => {39 const res = await uni.request({40 url: 'https://your-backend.com/api/auth/temp-token',41 method: 'POST'42 });43 tempToken.value = res.data.tempToken;44});4546const {47 messages,48 ttsMuted,49 setTtsMuted,50 playTts,51 stopTts,52 ttsPlayingMessageId,53 ttsPlayState, // 'idle' | 'loading' | 'playing' | 'paused' | 'error'54} = useChat({55 apiKey: tempToken, // ⭐ 使用动态获取的临时Token56 appId: 'your-app-id',57 ttsMode: 'websocket', // TTS 模式,默认 'websocket'(独立 WebSocket 双流实时合成)58});5960// 切换自动播报61const toggleTts = () => setTtsMuted(!ttsMuted.value);6263// 单条消息播放64const handlePlay = (msg) => {65 stopTts(); // 先停止当前播放66 playTts(msg.id, msg.content);67};68</script>
跨端播放原理
| 平台 | 播放方式 | 说明 |
|---|---|---|
| App (iOS/Android) | renderjs + Web Audio API | 在 WebView 中使用 AudioContext 播放 PCM |
| 微信小程序 | InnerAudioContext | 小程序原生音频 API(需转换为 MP3) |
| H5 | Web Audio API | 浏览器原生 AudioContext |
💡 常见问题
- Q: 点击播放没声音?
- A: 检查是否在页面中放置了
<TtsAudioPlayer />组件 - Q: App 端播放报错?
- A: 确保
<TtsAudioPlayer />放在 template 根节点下,不要嵌套在条件渲染中 - Q: 小程序播放有延迟?
- A: 小程序需要先将 PCM 转换为 MP3 格式,会有一定延迟,属于正常现象
RTC 实时语音对话(ASR + 对话 + TTS + 打断)
全双工实时语音交互:按住说话 → 实时识别 → AI流式回复 → 边生成边播放 → 支持打断。完整集成预扣费、MCP工具调用、知识库检索、提示词模板等平台能力。
核心特性
- ASR语音识别:用户说话实时转文字
- 流式对话:AI流式生成回复文本
- 流式TTS:边生成边合成语音,边播放
- 打断机制:用户说话时自动打断AI播放
- 预扣费/扣费:完整集成平台计费系统
- 临时Token认证:安全的无状态认证
- MCP工具调用:支持数据库查询、知识库检索、网页查询
- 提示词模板:支持项目绑定的提示词模板
- 用量统计:完整记录Token、TTS字符数、通话时长
1<template>2 <view class="rtc-chat-page">3 <!-- 连接状态 -->4 <view class="status-bar">5 <view :class="['dot', isConnected ? 'connected' : 'disconnected']"></view>6 <text>{{ isConnected ? '已连接' : '未连接' }}</text>7 </view>89 <!-- 识别结果 -->10 <view v-if="transcription" class="bubble user">11 <text class="label">你说:</text>12 <text>{{ transcription }}</text>13 </view>1415 <!-- AI响应 -->16 <view v-if="aiResponse" class="bubble ai">17 <text class="label">AI:</text>18 <text>{{ aiResponse }}</text>19 </view>2021 <!-- 状态指示器 -->22 <view class="state-indicator">23 <view v-if="isListening" class="listening-wave">24 <view class="bar" v-for="i in 5" :key="i"></view>25 </view>26 <text v-if="isListening">正在聆听...</text>27 <text v-else-if="isAiSpeaking">AI正在回复...</text>28 <text v-else-if="isActive">等待说话...</text>29 </view>3031 <!-- 一键开关按钮 -->32 <view class="controls">33 <button34 :class="['main-btn', isActive ? 'active' : '']"35 :disabled="!isConnected"36 @click="toggleConversation"37 >38 {{ isActive ? '结束对话' : '开始对话' }}39 </button>4041 <button v-if="isAiSpeaking" class="interrupt-btn" @click="interrupt">42 打断43 </button>44 </view>4546 <!-- 使用提示 -->47 <view v-if="!isActive" class="tips">48 <text>💡 点击"开始对话"后直接说话,无需按住</text>49 <text>🎤 1.5秒静音后自动发送</text>50 <text>⚡ AI回复完成后自动继续监听</text>51 </view>52 </view>53</template>5455<script setup lang="ts">56import { onMounted, onUnmounted } from 'vue';57import { useRtcChat } from '@lingshuai/chat-uniapp/composables/useRtcChat';5859const {60 isConnected,61 isActive,62 isListening,63 isAiSpeaking,64 transcription,65 aiResponse,66 connect,67 startConversation,68 stopConversation,69 interrupt,70 disconnect71} = useRtcChat({72 projectId: 'your-project-id',73 apiKey: 'sk_live_xxxxxxxxxxxxxx',74 // ⭐ 生产环境推荐使用 tokenProvider 动态获取临时Token75 // tokenProvider: async () => {76 // const res = await uni.request({ url: 'https://your-backend.com/api/auth/temp-token' });77 // return res.data.data.tempToken;78 // },79});8081onMounted(() => connect());82onUnmounted(() => disconnect());8384const toggleConversation = () => {85 isActive.value ? stopConversation() : startConversation();86};87</script>
WebSocket 协议
连接地址:wss://pinyou.xin/api/ws/rtc?token=tmp_xxx
客户端 → 服务端:
{ action: 'start', projectId: 'xxx' }- 开始会话{ action: 'audio', data: 'base64...', end: false }- 发送音频{ action: 'interrupt' }- 打断对话{ action: 'end' }- 结束会话
服务端 → 客户端:
{ type: 'ready', sessionId: 'xxx' }- 准备就绪{ type: 'transcription', text: '你好' }- ASR识别结果{ type: 'text', delta: '我是' }- AI流式文本{ type: 'audio', data: 'base64...', seq: 0 }- TTS音频流{ type: 'interrupted' }- 已打断{ type: 'complete', usage: {...} }- 对话完成{ type: 'error', message: 'xxx' }- 错误
⚠️ 音频格式要求
- 格式:PCM
- 采样率:16000 Hz
- 声道:单声道
- 位深:16 bit
- 编码:Base64
💡 使用建议
- Q: 如何获取临时Token?
- A: 调用后端
/api/v1/auth/exchange-token接口,传入API Key获取临时Token - Q: 打断机制如何工作?
- A: 用户开始说话时,SDK自动检测并发送interrupt消息,停止当前TTS播放,清空音频队列
- Q: 如何查看用量统计?
- A: 会话结束后,数据自动保存到
rtc_sessions表,包含Token数、TTS字符数、通话时长等 - Q: 支持哪些平台?
- A: 微信小程序、H5、App (iOS/Android),自动适配各平台录音和播放API
✅ 与普通ASR/TTS的区别
| 功能 | 普通模式 | RTC模式 |
|---|---|---|
| 交互方式 | 串行:说完 → 识别 → 对话 → 播放 | 并行:边说边识别,边生成边播放 |
| 打断支持 | ❌ 不支持 | ✅ 支持 |
| 延迟 | 较高(需等待完整流程) | 极低(流式处理) |
| 适用场景 | 文本聊天 + 语音输入/播报 | 实时语音对话、语音助手 |
平台支持
微信小程序
enableChunked 流式传输,自动处理 UTF-8 多字节字符分片。
H5
Fetch API + ReadableStream 标准 SSE 流式传输。
App (iOS/Android)
RenderJS 真流式传输,未注册时自动降级为 plus.net 模拟。
高级配置
1import { useChat } from '@lingshuai/chat-uniapp';23const {4 messages, loading, sendMessage,5 clearHistory, getStorageInfo, exportMessages, importMessages6} = useChat({7 apiKey: 'tmp_live_xxxxxxxxxxxxxx' // ⭐ 用临时Token,8 appId: 'your-app-id',9 maxRetries: 3, // 最大重试次数10 retryDelay: 1000, // 重试间隔(毫秒)11 maxStoredMessages: 200, // 本地最大存储消息数12 autoCleanup: true, // 自动清理旧消息13});1415// 查看存储信息16const info = await getStorageInfo();17console.log(`消息数: ${info.messageCount}, 占用: ${info.estimatedSize}`);1819// 导出 / 导入消息记录20const json = await exportMessages();21await importMessages(json);
API 参考
useChat 配置项、返回值、类型定义一览。
ChatConfig(初始化配置)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| apiKey | string | 是 | 临时Token(tmp_live_xxx)⭐ 不要用API Key |
| appId | string | 是 | 项目 ID(UUID 格式) |
| baseUrl | string | 否 | API 基础地址(默认 https://pinyou.xin/api) |
| timeout | number | 否 | 请求超时毫秒数(默认 30000) |
| userId | string | 否 | 终端用户标识 |
| sessionId | string | 否 | 会话 ID(不传则自动生成) |
| maxRetries | number | 否 | 最大重试次数(默认 3) |
| retryDelay | number | 否 | 重试间隔毫秒数(默认 1000) |
| maxStoredMessages | number | 否 | 本地最大存储消息数(默认 100) |
| autoCleanup | boolean | 否 | 自动清理旧消息(默认 true) |
| ttsVoice | string | 否 | 默认 TTS 音色 ID |
| ttsMode | 'websocket' | 'stream' | 否 | TTS 模式(默认 'websocket')。websocket = 独立 WebSocket 双流实时合成;stream = SSE 内嵌(已废弃) |
| tokenProvider | () => Promise<string> | 否 | 临时 Token 动态获取函数。设置后优先使用 tokenProvider 返回的 Token 进行认证,适用于 Token 自动刷新场景。与 apiKey 二选一 |
| voiceRecorder | VoiceRecorderConfig | 否 | 语音录制配置(见下方) |
响应式状态(Ref)
| 属性 | 类型 | 说明 |
|---|---|---|
| messages | Ref<Message[]> | 消息列表 |
| loading | Ref<boolean> | 是否正在发送消息 |
| uploading | Ref<boolean> | 是否正在上传文件 |
| error | Ref<string | null> | 错误信息 |
| conversationId | Ref<string | null> | 当前会话 ID |
| ttsPlayState | Ref<TtsPlayState> | 播放状态:'idle' | 'loading' | 'playing' | 'paused' | 'error' |
| ttsPlayingMessageId | Ref<string | null> | 正在播放的消息 ID |
| ttsMuted | Ref<boolean> | 是否静音(true = 关闭自动播报) |
| asrState | Ref<AsrState> | ASR 状态:'idle' | 'connecting' | 'listening' | 'transcribing' | 'error' |
| asrText | Ref<string> | ASR 已确认的转写文本 |
| asrInterim | Ref<string> | ASR 正在识别的临时文本(实时更新) |
消息 / 文件方法
| 方法 | 签名 | 说明 |
|---|---|---|
| sendMessage | (content: string) => Promise<void> | 发送文本消息 |
| sendWithAttachments | (content, attachments) => Promise<void> | 发送带附件的消息 |
| uploadFile | (filePath, type) => Promise<UploadResult> | 上传文件,返回上传结果 |
| sendImageFromAlbum | (content?) => Promise<void> | 从相册选图并发送 |
| sendImage | (filePath, content?) => Promise<void> | 发送指定路径图片 |
| sendVideo | (filePath, content?) => Promise<void> | 发送视频 |
| sendWithUrls | (content, urls: MediaAttachment[]) => Promise<void> | 直接传入已有 URL(如自有 OSS/CDN),无需先上传 |
| copyMessage | (messageId: string) => Promise<void> | 将指定消息内容复制到剪贴板 |
ASR 语音输入方法
| 方法 | 签名 | 说明 |
|---|---|---|
| startVoiceInput | () => void | 开启 ASR 实时转写(WebSocket 连接 → 录音 → 实时识别) |
| stopVoiceInput | () => Promise<void> | 停止录音并等待最终结果,自动发送转写文本 |
| cancelVoiceInput | () => void | 取消语音输入(不发送) |
会话管理 / 本地存储
| 方法 | 签名 | 说明 |
|---|---|---|
| resetConversation | () => Promise<void> | 重置会话(开启新对话) |
| clearHistory | () => Promise<void> | 清空本地消息记录 |
| abort | () => void | 中止当前流式请求 |
| getStorageInfo | () => Promise<StorageInfo> | 获取本地存储信息 |
| exportMessages | () => Promise<string> | 导出消息为 JSON 字符串 |
| importMessages | (json: string) => Promise<boolean> | 导入消息 |
TTS 语音播报方法
| 方法 | 签名 | 说明 |
|---|---|---|
| playTts | (messageId, text, options?) => Promise<void> | 播放单条消息语音 |
| pauseTts | () => void | 暂停播放 |
| resumeTts | () => void | 恢复播放 |
| stopTts | () => void | 停止播放 |
| setTtsMuted | (muted: boolean) => void | 开关流式自动播报 |
| setTtsAutoPlay | (enabled: boolean) => void | 开关自动播报(setTtsMuted 反向别名) |
| generateTts | (text, options?) => Promise<TtsResult> | 仅合成音频,不播放 |
| getTtsVoices | () => Promise<TtsVoiceOption[]> | 获取可用音色列表 |
类型定义
1// 消息2interface Message {3 id: string; // 消息 ID4 conversationId: string; // 所属会话 ID5 role: 'user' | 'assistant' | 'system'; // 角色6 content: string; // 消息内容7 status: 'sending' | 'streaming' | 'success' | 'error'; // 消息状态8 createdAt: Date; // 创建时间9 error?: string; // 错误信息(status 为 error 时)10 attachments?: MediaAttachment[]; // 多模态附件11}1213// 媒体类型14type MediaType = 'image' | 'audio' | 'video' | 'file';1516// 媒体附件17interface MediaAttachment {18 type: MediaType; // 附件类型19 url: string; // 公网访问地址20 mimeType?: string; // MIME 类型21 name?: string; // 文件名22 objectName?: string; // MinIO 对象名称(上传后自动返回)23 size?: number; // 文件大小(字节)24 duration?: number; // 时长(秒),音视频专用25 localPath?: string; // 本地文件路径(UniApp 端离线预览用)26}2728// 上传结果29interface UploadResult {30 url: string; // 公网 URL31 objectName: string; // 对象存储路径32 mimeType: string; // MIME 类型33 size: number; // 文件大小(字节)34 name?: string; // 文件名35}3637// TTS 合成选项38interface TtsOptions {39 voice?: string; // 音色 ID40 projectId?: string; // 项目 ID41 conversationId?: string; // 会话 ID42}4344// TTS 合成结果45interface TtsResult {46 audioUrl: string; // 音频文件 URL47 characters: number; // 字符数48 model: string; // TTS 模型名称49 voice: string; // 使用的音色 ID50}5152// TTS 播放状态53type TtsPlayState = 'idle' | 'loading' | 'playing' | 'paused' | 'error';5455// TTS 音色选项56interface TtsVoiceOption {57 id: string; // 音色 ID58 name: string; // 音色名称59 description: string; // 音色描述60 isDefault: boolean; // 是否默认音色61}6263// 本地存储信息64interface StorageInfo {65 messageCount: number; // 已存储消息数66 estimatedSize: string; // 预估占用空间(可读格式)67 oldestMessageTime: number | null; // 最早消息时间戳(毫秒)68 newestMessageTime: number | null; // 最新消息时间戳(毫秒)69 conversationId: string | null; // 当前会话 ID70}7172// 语音录制配置73interface VoiceRecorderConfig {74 maxDuration?: number; // 最大录制时长(秒,默认 60)75 sampleRate?: number; // 采样率(Hz,各平台自动选择)76 format?: string; // 录音格式(各平台自动选择)77}7879// 录音结果80interface RecordResult {81 tempFilePath: string; // 临时文件路径82 duration: number; // 时长(秒)83 fileSize?: number; // 文件大小(字节)84 format: string; // 音频格式85 mimeType: string; // MIME 类型(用于上传)86}8788// 录音状态89type RecorderState = 'idle' | 'recording' | 'paused';9091// ASR 语音识别状态92type AsrState = 'idle' | 'connecting' | 'listening' | 'transcribing' | 'error';9394// ASR WebSocket 消息95interface AsrMessage {96 type: 'ready' | 'partial' | 'final' | 'complete' | 'error' | 'cancelled';97 text?: string; // 转写文本98 index?: number; // 句子序号99 message?: string; // 错误信息(type 为 error 时)100}
SSE 事件类型
| 事件 type | 字段 | 说明 |
|---|---|---|
| init | conversationId | 流式连接建立,返回会话 ID |
| message | delta | 增量文本片段 |
| analyzing | message | 多模态内容分析中(视频/音频/图片),可用于展示加载提示 |
| tts_start | text, seq | TTS 合成开始(SSE 内嵌模式) |
| tts_chunk | data (base64), seq | TTS 音频数据块(SSE 内嵌模式,base64 编码) |
| tts_end | seq, characters | 单句 TTS 合成结束 |
| tts_flush | — | 所有 TTS 合成完毕 |
| done | — | 流式响应结束 |
| error | error, message | 服务端错误信息 |
文件上传限制
图片
最大 10MB
jpeg, png, gif, webp, bmp
音频
最大 25MB
mp3, wav, ogg, aac, webm, flac, amr, opus, silk
视频
最大 50MB
mp4, webm, quicktime
文件
最大 30MB
pdf, doc/docx, xls/xlsx, ppt/pptx, txt, csv, md, json, zip