Spaces:
Running
Running
| // 🔄 会话状态管理 - 跟踪是否在连续对话模式 | |
| let inConversationMode = false; | |
| let lastActivityTime = Date.now(); | |
| const CONVERSATION_TIMEOUT = 5 * 60 * 1000; // 5分钟无活动自动退出 | |
| export default { | |
| speaker: { | |
| userId: "104544581", | |
| password: "xin12345", | |
| passToken: "V1:J7rrshrufaw8uWrlTMO7xxdYqvWIT0JZUqwbl9YuVuCnljlYhobJm6vO/rem/hRUvTASl/w3akwM4pDSC2GNH1gIVn6P7SCGjR4HIbpKzPzP3XtYeSW+5WYU9GIBlll9cqOzhi+5fi0VI3mU3K3F/tpZXa4JoTLxbRpRt9YB7DgFLjsKYD9elKSR7jr6zSnTIzgXsAntm1b7Yq//DHm1OB4fpy1Ldn4DXhR7PvwgA2MMRCrUMbE645GAKTb6fbf6mxz7MuTuDv4u56Juz+37yeoZBf3PNvyA+EbflsgnFeaE9cdVWArLIKFuBU983IZw4WuBpjxvo1X9qXvcfYSxBg==", | |
| did: "多多2", | |
| }, | |
| openai: { | |
| // 使用带搜索能力的模型 | |
| model: "gemini-2.5-flash-search", | |
| baseURL: "https://tdhxxzcu.ap-southeast-1.clawcloudrun.com/v1", | |
| apiKey: "Csrchina@163.com", | |
| }, | |
| context: { | |
| historyMaxLength: 10, | |
| }, | |
| prompt: { | |
| system: "你是一个智能助手,请以下方式回答:不需客套,直接回答问题;句子简短,但一定要完整清晰回答用户的所有问题;不用英文缩写、表情、Markdown、代码;数字用中文读法,时间与金额说全称。" | |
| }, | |
| // 首次触发关键词 | |
| callAIKeywords: ["请", "你"], | |
| // 退出连续对话的关键词 | |
| exitKeywords: ["退下", "再见", "拜拜", "结束对话"], | |
| async onMessage(engine, msg) { | |
| const text = msg.text; | |
| const now = Date.now(); | |
| // 检查是否超时,自动退出连续对话模式 | |
| if (inConversationMode && (now - lastActivityTime > CONVERSATION_TIMEOUT)) { | |
| inConversationMode = false; | |
| console.log("⏰ 连续对话超时,已自动退出"); | |
| } | |
| // 检查是否要退出连续对话 | |
| if (this.exitKeywords.some((e) => text.includes(e))) { | |
| if (inConversationMode) { | |
| inConversationMode = false; | |
| // 打断小爱并播放退出提示 | |
| await engine.speaker.abortXiaoAI(); | |
| await engine.MiOT.doAction(7, 3, "好的,有事再叫我"); | |
| console.log("👋 退出连续对话模式"); | |
| return { handled: true }; | |
| } | |
| } | |
| // 判断是否应该由 MiGPT 处理 | |
| const isFirstTrigger = this.callAIKeywords.some((e) => text.startsWith(e)); | |
| const shouldHandle = isFirstTrigger || inConversationMode; | |
| if (shouldHandle) { | |
| // 🔥 立即打断小爱(支持随时打断) | |
| await engine.speaker.abortXiaoAI(); | |
| // 进入/保持连续对话模式 | |
| if (!inConversationMode) { | |
| inConversationMode = true; | |
| console.log("🎯 进入连续对话模式"); | |
| } | |
| lastActivityTime = now; | |
| // 调用 AI 回答 | |
| const { text: reply } = await engine.askAI(msg); | |
| console.log(`🔊 ${reply}`); | |
| // 🎯 分段播放长文本,防止 TTS 超时中断 | |
| const maxLength = 200; // 单次播放最大字数 | |
| if (reply.length > maxLength) { | |
| // 按句号、问号、感叹号分段 | |
| const sentences = reply.match(/[^。!?]+[。!?]/g) || [reply]; | |
| let currentChunk = ""; | |
| for (const sentence of sentences) { | |
| if ((currentChunk + sentence).length > maxLength) { | |
| // 播放当前段落 | |
| if (currentChunk) { | |
| await engine.MiOT.doAction(7, 3, currentChunk); | |
| // 根据文字数量计算等待时间(每字约350ms)+ 1秒缓冲 | |
| const waitTime = currentChunk.length * 350 + 1000; | |
| await new Promise(resolve => setTimeout(resolve, waitTime)); | |
| } | |
| currentChunk = sentence; | |
| } else { | |
| currentChunk += sentence; | |
| } | |
| } | |
| // 播放最后一段 | |
| if (currentChunk) { | |
| await engine.MiOT.doAction(7, 3, currentChunk); | |
| } | |
| } else { | |
| // 短文本直接播放 | |
| await engine.MiOT.doAction(7, 3, reply); | |
| } | |
| return { handled: true }; | |
| } | |
| // 不在连续对话模式,且不是触发词,让原始小爱处理 | |
| }, | |
| }; |