Merge branch 'new_female' into kehu_female
This commit is contained in:
		
						commit
						cae92aac52
					
				
							
								
								
									
										97
									
								
								server.js
									
									
									
									
									
								
							
							
						
						
									
										97
									
								
								server.js
									
									
									
									
									
								
							| @ -8,7 +8,17 @@ const { MessageHistory } = require('./src/message_history.js'); | |||||||
| 
 | 
 | ||||||
| const app = express(); | const app = express(); | ||||||
| const server = http.createServer(app); | const server = http.createServer(app); | ||||||
| const io = socketIo(server); | const io = socketIo(server, { | ||||||
|  |   pingTimeout: 300000,    // 60秒超时
 | ||||||
|  |   pingInterval: 25000,   // 25秒心跳间隔
 | ||||||
|  |   upgradeTimeout: 30000, // 30秒升级超时
 | ||||||
|  |   allowEIO3: true,       // 允许Engine.IO v3客户端
 | ||||||
|  |   transports: ['websocket', 'polling'], // 支持多种传输方式
 | ||||||
|  |   cors: { | ||||||
|  |     origin: "*", | ||||||
|  |     methods: ["GET", "POST"] | ||||||
|  |   } | ||||||
|  | }); | ||||||
| 
 | 
 | ||||||
| // 创建消息历史管理器
 | // 创建消息历史管理器
 | ||||||
| const messageHistory = new MessageHistory(); | const messageHistory = new MessageHistory(); | ||||||
| @ -121,32 +131,66 @@ function saveSceneState() { | |||||||
| let currentSceneIndex = 0; | let currentSceneIndex = 0; | ||||||
| const scenes = [ | const scenes = [ | ||||||
|   { |   { | ||||||
|     name: '起床', |     name: '起床-坐在床上', | ||||||
|     defaultVideo: '8-5-qc-bd-female.mp4', |     defaultVideo: '8-5-qc-bd-female.mp4', | ||||||
|     interactionVideo: '8-5-qc-sh-female.mp4', |     interactionVideo: '8-5-qc-sh-female.mp4', | ||||||
|     tag: 'wakeup', |     tag: 'wakeup', | ||||||
|     apiKey: 'bot-20250730213756-l627w' // 起床场景的API key
 |     apiKey: 'bot-20250730213756-l627w', // 起床场景的API key
 | ||||||
|  |     openingLines: [ | ||||||
|  |       '哈啊~ 头发是不是超乱?别笑话我哦... 不过,能看到你的脸,就算乱糟糟的我也好开心。', | ||||||
|  |       "刚醒就看到你,比闹钟好使一百倍。", | ||||||
|  |       // "昨晚梦里喊你名字喊醒了,现在视频看到你,心跳比梦里还烫。",
 | ||||||
|  |       // "你穿我T恤的样子...让我想咬住衣领不放。",
 | ||||||
|  |       "宝宝早安!偷偷告诉你...我昨晚梦见你啦,所以醒来第一件事就是找你~", | ||||||
|  |       // "喉咙里有咖啡味...要不要来尝尝?记得上次你咬我锁骨时,咖啡还温着。",
 | ||||||
|  |       "早,宝宝。吃早餐没?我还没... 好想有人送早餐来床上哦。" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: '走路', |     name: '走路去公司的路上', | ||||||
|     defaultVideo: '8-5-sb-bd-female.mp4', |     defaultVideo: '8-5-sb-bd-female.mp4', | ||||||
|     interactionVideo: '8-5-sb-sh-female.mp4', |     interactionVideo: '8-5-sb-sh-female.mp4', | ||||||
|     tag: 'driving', |     tag: 'walk', | ||||||
|     apiKey: 'bot-20250703161810-sgkrh' // 开车场景的API key
 |     apiKey: 'bot-20250703161810-sgkrh', // 开车场景的API key
 | ||||||
|  |     openingLines: [ | ||||||
|  |       '宝宝,看到你那边阳光好好哦。我这边有点风,吹着挺舒服的,要是你在旁边牵着手就更好了。', | ||||||
|  |       '宝宝,我快到公司啦。今天太阳真好,晒得人暖暖的... 你那边天气咋样?', | ||||||
|  |       '宝宝~ 我刚出地铁站就看到你啦!吃早餐没?', | ||||||
|  |       // '看到那个大钟楼没?每次路过都想你。想着要是能牵着你的手,站在最高层往下看该多好。',
 | ||||||
|  |       '宝宝,我快到公司了,你到公司了吗?', | ||||||
|  |       '宝宝,今天太阳真舒服!想起昨晚说的周末想去湖边散步,要是这天气保持住就好啦!', | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: '喝茶', |     name: '在公司楼下喝咖啡', | ||||||
|     defaultVideo: '8-8-hc-bd-2.mp4', |     defaultVideo: '8-8-hc-bd-2.mp4', | ||||||
|     interactionVideo: '8-5-hc-sh-female.mp4', |     interactionVideo: '8-5-hc-sh-female.mp4', | ||||||
|     tag: 'tea', |     tag: 'coffee', | ||||||
|     apiKey: 'bot-20250805140055-ccdr6' // 喝茶场景的API key
 |     apiKey: 'bot-20250805140055-ccdr6', // 喝茶场景的API key
 | ||||||
|  |     openingLines: [ | ||||||
|  |       '宝宝,今天这杯拿铁拉花好丑哦... 不过没关系,看着你的帅脸就平衡啦,你比咖啡提神!', | ||||||
|  |       '嗯... 咖啡香香的,宝宝的声音也好好听。好想时间停在这一小会儿,就我们俩。', | ||||||
|  |       '哇,今天换了个口味,燕麦拿铁!宝宝你肯定嫌没味道,就爱喝你那美式... 苦死了。', | ||||||
|  |       '宝宝,刚才店员问我糖浆加多少,我脱口而出‘和我男朋友一样’,说完自己都脸红了...', | ||||||
|  |       '对了宝宝,昨天说帮你找的资料,我存手机了,喝完这杯咖啡就发你哈!记得看。', | ||||||
|  |       // '这杯好苦…但一看到你,就自动回甘了。比加十包糖都管用。你说你是不是我的专属甜味剂?'
 | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: '睡觉', |     name: '敷面膜-准备睡觉', | ||||||
|     defaultVideo: '8-8-sj-bd.mp4', |     defaultVideo: '8-8-sj-bd.mp4', | ||||||
|     interactionVideo: '8-8-sj-sh.mp4', |     interactionVideo: '8-8-sj-sh.mp4', | ||||||
|     tag: 'sleep', |     tag: 'sleep', | ||||||
|     apiKey: 'bot-20250808120020-jfkmk' // 吃饭场景的API key
 |     apiKey: 'bot-20250808120020-jfkmk', // 睡觉场景的API key
 | ||||||
|  |     openingLines: [ | ||||||
|  |       '宝宝~ 敷着面膜和你视频,感觉像在做双倍美容,心里也美美的。', | ||||||
|  |       '嗯?宝宝打来啦... 刚躺下贴上面膜,你就来了,像算准时间一样,真贴心。', | ||||||
|  |       '哈,宝宝,选了个清洁面膜,有点刺刺的。你用的啥洗面奶来着?忘了...', | ||||||
|  |       '早...哦不,晚安宝宝!敷面膜呢,你看我像不像外星人?哈哈...嘶,不能笑!', | ||||||
|  |       '宝宝,你那边也躺下了?我弄完面膜就睡。今天累不累?', | ||||||
|  |       '宝宝... 这面膜说要敷15分钟,正好陪你唠会儿。不过我得小声,怕长皱纹!', | ||||||
|  |       '好啦宝宝,面膜快干了,得去洗了。你先睡?... 嗯,梦里见呗。' | ||||||
|  |     ] | ||||||
|   } |   } | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
| @ -263,6 +307,37 @@ app.get('/api/default-video', (req, res) => { | |||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | // 在现有的API接口后添加
 | ||||||
|  | app.get('/api/current-scene/opening-line', (req, res) => { | ||||||
|  |   try { | ||||||
|  |     const currentScene = getCurrentScene(); | ||||||
|  |     if (currentScene && currentScene.openingLines && currentScene.openingLines.length > 0) { | ||||||
|  |       // 随机选择一个开场白
 | ||||||
|  |       const randomIndex = Math.floor(Math.random() * currentScene.openingLines.length); | ||||||
|  |       const selectedOpeningLine = currentScene.openingLines[randomIndex]; | ||||||
|  |        | ||||||
|  |       res.json({ | ||||||
|  |         success: true, | ||||||
|  |         openingLine: selectedOpeningLine, | ||||||
|  |         sceneName: currentScene.name, | ||||||
|  |         sceneTag: currentScene.tag | ||||||
|  |       }); | ||||||
|  |     } else { | ||||||
|  |       res.json({ | ||||||
|  |         success: false, | ||||||
|  |         message: '当前场景没有配置开场白' | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('获取开场白失败:', error); | ||||||
|  |     res.status(500).json({ | ||||||
|  |       success: false, | ||||||
|  |       message: '获取开场白失败', | ||||||
|  |       error: error.message | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| // Socket.IO 连接处理
 | // Socket.IO 连接处理
 | ||||||
| io.on('connection', (socket) => { | io.on('connection', (socket) => { | ||||||
|   console.log('用户连接:', socket.id); |   console.log('用户连接:', socket.id); | ||||||
|  | |||||||
| @ -26,12 +26,13 @@ async function initializeHistoryMessage(recentCount = 5) { | |||||||
|         const data = await response.json(); |         const data = await response.json(); | ||||||
|         historyMessage = data.messages || []; |         historyMessage = data.messages || []; | ||||||
|         isInitialized = true; |         isInitialized = true; | ||||||
|         console.log("历史消息初始化完成:", historyMessage.length, "条消息"); |         console.log("历史消息初始化完成:", historyMessage.length, "条消息", historyMessage); | ||||||
|  | 
 | ||||||
|         return historyMessage; |         return historyMessage; | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|         console.error('获取历史消息失败,使用默认格式:', error); |         console.error('获取历史消息失败,使用默认格式:', error); | ||||||
|         historyMessage = [ |         historyMessage = [ | ||||||
|             { role: 'system', content: 'You are a helpful assistant.' } |             // { role: 'system', content: 'You are a helpful assistant.' }
 | ||||||
|         ]; |         ]; | ||||||
|         isInitialized = true; |         isInitialized = true; | ||||||
|         return historyMessage; |         return historyMessage; | ||||||
| @ -42,7 +43,7 @@ async function initializeHistoryMessage(recentCount = 5) { | |||||||
| function getCurrentHistoryMessage() { | function getCurrentHistoryMessage() { | ||||||
|     if (!isInitialized) { |     if (!isInitialized) { | ||||||
|         console.warn('历史消息未初始化,返回默认消息'); |         console.warn('历史消息未初始化,返回默认消息'); | ||||||
|         return [{ role: 'system', content: 'You are a helpful assistant.' }]; |         return []; | ||||||
|     } |     } | ||||||
|     return [...historyMessage]; // 返回副本,避免外部修改
 |     return [...historyMessage]; // 返回副本,避免外部修改
 | ||||||
| } | } | ||||||
| @ -60,16 +61,15 @@ function updateHistoryMessage(userInput, assistantResponse) { | |||||||
|     ); |     ); | ||||||
|      |      | ||||||
|     // 可选:限制历史消息数量,保持最近的对话
 |     // 可选:限制历史消息数量,保持最近的对话
 | ||||||
|     // const maxMessages = 20; // 保留最近10轮对话(20条消息)
 |     const maxMessages = 20; // 保留最近10轮对话(20条消息)
 | ||||||
|     // if (historyMessage.length > maxMessages) {
 |     if (historyMessage.length > maxMessages) { | ||||||
|     //     // 保留系统消息和最近的对话
 |         // 保留系统消息和最近的对话
 | ||||||
|     //     const systemMessages = historyMessage.filter(msg => msg.role === 'system');
 |         const systemMessages = historyMessage.filter(msg => msg.role === 'system'); | ||||||
|     //     const recentMessages = historyMessage.slice(-maxMessages + systemMessages.length);
 |         const recentMessages = historyMessage.slice(-maxMessages + systemMessages.length); | ||||||
|     //     historyMessage = [...systemMessages, ...recentMessages.filter(msg => msg.role !== 'system')];
 |         historyMessage = [...systemMessages, ...recentMessages.filter(msg => msg.role !== 'system')]; | ||||||
|     // }
 |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 保存消息到服务端
 |  | ||||||
| // 保存消息到服务端
 | // 保存消息到服务端
 | ||||||
| async function saveMessage(userInput, assistantResponse) { | async function saveMessage(userInput, assistantResponse) { | ||||||
|     try { |     try { | ||||||
| @ -197,7 +197,7 @@ async function chatWithAudioStream(userInput) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 导出初始化函数,供外部调用
 | // 导出初始化函数,供外部调用
 | ||||||
| export { chatWithAudioStream, initializeHistoryMessage, getCurrentHistoryMessage }; | export { chatWithAudioStream, initializeHistoryMessage, getCurrentHistoryMessage, saveMessage, updateHistoryMessage }; | ||||||
| 
 | 
 | ||||||
| // 处理音频播放队列
 | // 处理音频播放队列
 | ||||||
| async function processAudioQueue() { | async function processAudioQueue() { | ||||||
|  | |||||||
							
								
								
									
										171
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										171
									
								
								src/index.js
									
									
									
									
									
								
							| @ -1,7 +1,8 @@ | |||||||
| console.log('视频文件:'); | console.log('视频文件:'); | ||||||
| // WebRTC 音视频通话应用
 | // WebRTC 音视频通话应用
 | ||||||
| // import { chatWithAudioStream } from './chat_with_audio.js';
 | // import { chatWithAudioStream } from './chat_with_audio.js';
 | ||||||
| import { chatWithAudioStream, initializeHistoryMessage } from './chat_with_audio.js'; | import { chatWithAudioStream, initializeHistoryMessage, updateHistoryMessage } from './chat_with_audio.js'; | ||||||
|  | 
 | ||||||
| import { AudioProcessor } from './audio_processor.js'; | import { AudioProcessor } from './audio_processor.js'; | ||||||
| 
 | 
 | ||||||
| // 在应用初始化时调用
 | // 在应用初始化时调用
 | ||||||
| @ -74,6 +75,10 @@ class WebRTCChat { | |||||||
|         this.preloadVideoResources(); |         this.preloadVideoResources(); | ||||||
|         this.bindEvents(); |         this.bindEvents(); | ||||||
| 
 | 
 | ||||||
|  |         // 添加开场白相关属性
 | ||||||
|  |         this.openingAudioData = null; | ||||||
|  |         this.isOpeningAudioReady = false; | ||||||
|  |          | ||||||
|         // 在初始化完成后预加载常用视频
 |         // 在初始化完成后预加载常用视频
 | ||||||
|         // setTimeout(() => {
 |         // setTimeout(() => {
 | ||||||
|         //     this.logMessage('开始预加载常用视频...', 'info');
 |         //     this.logMessage('开始预加载常用视频...', 'info');
 | ||||||
| @ -227,12 +232,117 @@ class WebRTCChat { | |||||||
|     async initializeHistory() { |     async initializeHistory() { | ||||||
|         try { |         try { | ||||||
|             await initializeHistoryMessage(100); |             await initializeHistoryMessage(100); | ||||||
|  | 
 | ||||||
|             console.log('历史消息初始化完成'); |             console.log('历史消息初始化完成'); | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
|             console.error('历史消息初始化失败:', error); |             console.error('历史消息初始化失败:', error); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |     // 新增方法:初始化开场白音频
 | ||||||
|  |     async initializeOpeningAudio() { | ||||||
|  |         try { | ||||||
|  |             console.log('开始初始化开场白音频...'); | ||||||
|  |              | ||||||
|  |             // 获取当前场景的开场白
 | ||||||
|  |             const response = await fetch('/api/current-scene/opening-line'); | ||||||
|  |             const data = await response.json(); | ||||||
|  |              | ||||||
|  |             if (data.success && data.openingLine) { | ||||||
|  |                 console.log(`获取到开场白: ${data.openingLine}`); | ||||||
|  |                  | ||||||
|  |                 // 生成开场白音频
 | ||||||
|  |                 await this.generateOpeningAudio(data.openingLine); | ||||||
|  |                 this.logMessage(`开场白音频已准备就绪: ${data.openingLine}`, 'success'); | ||||||
|  |             } else { | ||||||
|  |                 console.warn('未获取到开场白:', data.message); | ||||||
|  |             } | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('初始化开场白音频失败:', error); | ||||||
|  |             this.logMessage(`开场白音频初始化失败: ${error.message}`, 'error'); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // 新增方法:生成开场白音频
 | ||||||
|  |     async generateOpeningAudio(text) { | ||||||
|  |         try { | ||||||
|  |             // 动态导入 minimaxi_stream 模块
 | ||||||
|  |             const { requestMinimaxi } = await import('./minimaxi_stream.js'); | ||||||
|  |             const { getMinimaxiConfig, getAudioConfig, getLLMConfigByScene } = await import('./config.js'); | ||||||
|  |             const { saveMessage } = await import('./chat_with_audio.js'); | ||||||
|  |              | ||||||
|  |             const minimaxiConfig = getMinimaxiConfig(); | ||||||
|  |             const audioConfig = getAudioConfig(); | ||||||
|  |             const llmConfig = await getLLMConfigByScene(); | ||||||
|  |              | ||||||
|  |             const requestBody = { | ||||||
|  |                 model: audioConfig.model, | ||||||
|  |                 text: text, | ||||||
|  |                 voice_setting: audioConfig.voiceSetting, | ||||||
|  |                 audio_setting: audioConfig.audioSetting, | ||||||
|  |                 language_boost: 'auto', | ||||||
|  |                 output_format: 'hex' | ||||||
|  |             }; | ||||||
|  |              | ||||||
|  |             console.log('开始生成开场白音频...'); | ||||||
|  |              | ||||||
|  |             // 生成音频数据
 | ||||||
|  |             const audioHexData = await requestMinimaxi({ | ||||||
|  |                 apiKey: minimaxiConfig.apiKey, | ||||||
|  |                 groupId: minimaxiConfig.groupId, | ||||||
|  |                 body: requestBody, | ||||||
|  |                 stream: false, // 非流式,一次性获取完整音频
 | ||||||
|  |                 textPlay: false | ||||||
|  |             }); | ||||||
|  |              | ||||||
|  |             if (audioHexData && audioHexData.data && audioHexData.data.audio) { | ||||||
|  |                 this.openingAudioData = audioHexData.data.audio; | ||||||
|  |                 this.isOpeningAudioReady = true; | ||||||
|  |                 console.log('开场白音频生成成功'); | ||||||
|  |             } | ||||||
|  |             // 先更新本地历史消息
 | ||||||
|  |             updateHistoryMessage(`场景切换-${llmConfig.sceneName}`, text); | ||||||
|  | 
 | ||||||
|  |             await saveMessage(`场景切换-${llmConfig.sceneName}`,text); | ||||||
|  | 
 | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('生成开场白音频失败:', error); | ||||||
|  |             throw error; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // 新增方法:播放开场白音频
 | ||||||
|  |     async playOpeningAudio() { | ||||||
|  |         if (!this.isOpeningAudioReady || !this.openingAudioData) { | ||||||
|  |             console.warn('开场白音频未准备就绪'); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         try { | ||||||
|  |             // 动态导入 addAudioToQueue 函数
 | ||||||
|  |             const { addAudioToQueue } = await import('./minimaxi_stream.js'); | ||||||
|  |              | ||||||
|  |             console.log('将开场白音频添加到队列'); | ||||||
|  |             await addAudioToQueue(this.openingAudioData); | ||||||
|  |              | ||||||
|  |             this.logMessage('开场白音频已开始播放', 'success'); | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('播放开场白音频失败:', error); | ||||||
|  |             this.logMessage(`播放开场白音频失败: ${error.message}`, 'error'); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // 新增方法:获取开场白音频时长
 | ||||||
|  |     getOpeningAudioDuration() { | ||||||
|  |         // 估算开场白音频时长,可以根据实际情况调整
 | ||||||
|  |         // 这里假设平均每个字符对应100ms的音频时长
 | ||||||
|  |         if (this.openingAudioData) { | ||||||
|  |             // 简单估算:假设开场白大约3-5秒
 | ||||||
|  |             return 4000; // 4秒
 | ||||||
|  |         } | ||||||
|  |         return 3000; // 默认3秒
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     async loadVideoMapping() { |     async loadVideoMapping() { | ||||||
|         try { |         try { | ||||||
|             const response = await fetch('/api/video-mapping'); |             const response = await fetch('/api/video-mapping'); | ||||||
| @ -431,6 +541,7 @@ class WebRTCChat { | |||||||
| 
 | 
 | ||||||
|     // 预创建重要视频流
 |     // 预创建重要视频流
 | ||||||
|     async precreateImportantVideos() { |     async precreateImportantVideos() { | ||||||
|  | 
 | ||||||
|         if (this.isInitialized) return; |         if (this.isInitialized) return; | ||||||
|          |          | ||||||
|         console.log('开始预创建重要流...', 'info'); |         console.log('开始预创建重要流...', 'info'); | ||||||
| @ -1151,6 +1262,9 @@ class WebRTCChat { | |||||||
|             // 切换到通话中图标
 |             // 切换到通话中图标
 | ||||||
|             this.switchToCallingIcon(); |             this.switchToCallingIcon(); | ||||||
| 
 | 
 | ||||||
|  |             // 在初始化完成后生成开场白音频
 | ||||||
|  |             await this.initializeOpeningAudio(); | ||||||
|  |              | ||||||
|             // 现在才开始显示视频
 |             // 现在才开始显示视频
 | ||||||
|             await this.startDefaultVideoStream(); |             await this.startDefaultVideoStream(); | ||||||
|              |              | ||||||
| @ -1163,35 +1277,54 @@ class WebRTCChat { | |||||||
|             console.log('麦克风权限获取成功'); |             console.log('麦克风权限获取成功'); | ||||||
|              |              | ||||||
|             await this.createPeerConnection(); |             await this.createPeerConnection(); | ||||||
|             await this.startVoiceRecording(); |  | ||||||
|              |              | ||||||
|             this.startButton.disabled = true; |             this.startButton.disabled = true; | ||||||
|             this.startButton.style.opacity = '0.5' |         this.startButton.style.opacity = '0.5' | ||||||
|             this.stopButton.disabled = false; |         this.stopButton.disabled = false; | ||||||
| 
 | 
 | ||||||
|             // 隐藏头像,显示视频
 |         // 隐藏头像,显示视频
 | ||||||
|             if (this.videoContainer) { |         if (this.videoContainer) { | ||||||
| 
 | 
 | ||||||
|                 this.videoContainer.classList.add('calling'); |             this.videoContainer.classList.add('calling'); | ||||||
|             } |         } | ||||||
|          |          | ||||||
|             // 显示结束通话按钮
 |         // 显示结束通话按钮
 | ||||||
|             this.stopButton.style.display = 'block'; |         this.stopButton.style.display = 'block'; | ||||||
|          |          | ||||||
|          |          | ||||||
|          |          | ||||||
|             this.updateAudioStatus('已连接', 'connected'); |         this.updateAudioStatus('已连接', 'connected'); | ||||||
|             this.logMessage('音频通话已开始', 'success'); |         this.logMessage('音频通话已开始', 'success'); | ||||||
|          |          | ||||||
|             // 确保视频映射已加载
 |         // 确保视频映射已加载
 | ||||||
|             if (Object.keys(this.videoMapping).length === 0) { |         if (Object.keys(this.videoMapping).length === 0) { | ||||||
|                 await this.loadVideoMapping(); |             await this.loadVideoMapping(); | ||||||
|             } |         } | ||||||
|          |          | ||||||
|             this.logMessage(`视频映射已加载: ${Object.keys(this.videoMapping).length} 个映射`, 'info'); |         this.logMessage(`视频映射已加载: ${Object.keys(this.videoMapping).length} 个映射`, 'info'); | ||||||
|          |          | ||||||
|             // 通知服务器通话开始
 |         // 通知服务器通话开始
 | ||||||
|             this.socket.emit('call-started'); |         this.socket.emit('call-started'); | ||||||
|  |          | ||||||
|  |         // 播放开场白,然后启动语音录制
 | ||||||
|  |         if (this.isOpeningAudioReady) { | ||||||
|  |             console.log('播放开场白音频...'); | ||||||
|  |             await this.playOpeningAudio(); | ||||||
|  |              | ||||||
|  |             // 等待开场白播放完成后再启动语音录制
 | ||||||
|  |             setTimeout(async () => { | ||||||
|  |                 console.log('开场白播放完成,启动语音录制...'); | ||||||
|  |                 await this.startVoiceRecording(); | ||||||
|  |                 this.logMessage('语音录制已启动,可以开始对话', 'success'); | ||||||
|  |             }, this.getOpeningAudioDuration() + 1000); // 开场白时长 + 1秒缓冲
 | ||||||
|  |         } else { | ||||||
|  |             console.warn('开场白音频尚未准备就绪,延迟启动语音录制'); | ||||||
|  |             // 如果没有开场白,延迟500ms后启动录制
 | ||||||
|  |             setTimeout(async () => { | ||||||
|  |                 await this.startVoiceRecording(); | ||||||
|  |                 this.logMessage('语音录制已启动,可以开始对话', 'success'); | ||||||
|  |             }, 500); | ||||||
|  |         } | ||||||
|              |              | ||||||
|             // 开始播放当前场景的默认视频
 |             // 开始播放当前场景的默认视频
 | ||||||
|             // await this.precreateImportantVideos();
 |             // await this.precreateImportantVideos();
 | ||||||
|  | |||||||
| @ -1,5 +1,35 @@ | |||||||
| // 以流式方式请求LLM大模型接口,并打印流式返回内容
 | // 以流式方式请求LLM大模型接口,并打印流式返回内容
 | ||||||
| 
 | 
 | ||||||
|  | // 过滤旁白内容的函数
 | ||||||
|  | function filterNarration(text) { | ||||||
|  |   if (!text) return text; | ||||||
|  |    | ||||||
|  |   // 匹配各种括号内的旁白内容
 | ||||||
|  |   // 包括:()、【】、[]、{}、〈〉、《》等
 | ||||||
|  |   const narrationPatterns = [ | ||||||
|  |     /([^)]*)/g,  // 中文圆括号
 | ||||||
|  |     /\([^)]*\)/g,   // 英文圆括号
 | ||||||
|  |     /【[^】]*】/g,   // 中文方括号
 | ||||||
|  |     /\[[^\]]*\]/g,  // 英文方括号
 | ||||||
|  |     /\{[^}]*\}/g,   // 花括号
 | ||||||
|  |     /〈[^〉]*〉/g,   // 中文尖括号
 | ||||||
|  |     /《[^》]*》/g,   // 中文书名号
 | ||||||
|  |     /<[^>]*>/g     // 英文尖括号
 | ||||||
|  |   ]; | ||||||
|  |    | ||||||
|  |   let filteredText = text; | ||||||
|  |    | ||||||
|  |   // 逐个应用过滤规则
 | ||||||
|  |   narrationPatterns.forEach(pattern => { | ||||||
|  |     filteredText = filteredText.replace(pattern, ''); | ||||||
|  |   }); | ||||||
|  |    | ||||||
|  |   // 清理多余的空格和换行
 | ||||||
|  |   filteredText = filteredText.replace(/\s+/g, ' ').trim(); | ||||||
|  |    | ||||||
|  |   return filteredText; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| async function requestLLMStream({ apiKey, model, messages, onSegment }) { | async function requestLLMStream({ apiKey, model, messages, onSegment }) { | ||||||
|   const response = await fetch('https://ark.cn-beijing.volces.com/api/v3/bots/chat/completions', { |   const response = await fetch('https://ark.cn-beijing.volces.com/api/v3/bots/chat/completions', { | ||||||
|     method: 'POST', |     method: 'POST', | ||||||
| @ -54,7 +84,14 @@ async function requestLLMStream({ apiKey, model, messages, onSegment }) { | |||||||
|             // 处理最后的待处理文本(无论长度是否大于5个字)
 |             // 处理最后的待处理文本(无论长度是否大于5个字)
 | ||||||
|             if (pendingText.trim() && onSegment) { |             if (pendingText.trim() && onSegment) { | ||||||
|               console.log('处理最后的待处理文本:', pendingText.trim()); |               console.log('处理最后的待处理文本:', pendingText.trim()); | ||||||
|               await onSegment(pendingText.trim(), true); |               // 过滤旁白内容
 | ||||||
|  |               const filteredText = filterNarration(pendingText.trim()); | ||||||
|  |               if (filteredText.trim()) { | ||||||
|  |                 console.log('过滤旁白后的最后文本:', filteredText); | ||||||
|  |                 await onSegment(filteredText, true); | ||||||
|  |               } else { | ||||||
|  |                 console.log('最后的文本被完全过滤,跳过'); | ||||||
|  |               } | ||||||
|             } |             } | ||||||
|             continue; |             continue; | ||||||
|           } |           } | ||||||
| @ -65,12 +102,15 @@ async function requestLLMStream({ apiKey, model, messages, onSegment }) { | |||||||
|               const deltaContent = obj.choices[0].delta.content; |               const deltaContent = obj.choices[0].delta.content; | ||||||
|               content += deltaContent; |               content += deltaContent; | ||||||
|               pendingText += deltaContent; |               pendingText += deltaContent; | ||||||
|               console.log('LLM内容片段:', deltaContent); |               console.log('【未过滤】LLM内容片段:', pendingText); | ||||||
|                |                | ||||||
|               // 检查是否包含分段分隔符
 |               // 先过滤旁白,再检查分段分隔符
 | ||||||
|               if (segmentDelimiters.test(pendingText)) { |               const filteredPendingText = filterNarration(pendingText); | ||||||
|                 // 按分隔符分割文本
 |                | ||||||
|                 const segments = pendingText.split(segmentDelimiters); |               // 检查过滤后的文本是否包含分段分隔符
 | ||||||
|  |               if (segmentDelimiters.test(filteredPendingText)) { | ||||||
|  |                 // 按分隔符分割已过滤的文本
 | ||||||
|  |                 const segments = filteredPendingText.split(segmentDelimiters); | ||||||
|                  |                  | ||||||
|                 // 重新组合处理:只处理足够长的完整段落
 |                 // 重新组合处理:只处理足够长的完整段落
 | ||||||
|                 let accumulatedText = ''; |                 let accumulatedText = ''; | ||||||
| @ -81,25 +121,30 @@ async function requestLLMStream({ apiKey, model, messages, onSegment }) { | |||||||
|                   if (segment) { |                   if (segment) { | ||||||
|                     accumulatedText += segment; |                     accumulatedText += segment; | ||||||
|                     // 找到分隔符
 |                     // 找到分隔符
 | ||||||
|                     const delimiterMatch = pendingText.match(segmentDelimiters); |                     const delimiterMatch = filteredPendingText.match(segmentDelimiters); | ||||||
|                     if (delimiterMatch) { |                     if (delimiterMatch) { | ||||||
|                       accumulatedText += delimiterMatch[0]; |                       accumulatedText += delimiterMatch[0]; | ||||||
|                     } |                     } | ||||||
|                      |                      | ||||||
|                     // 如果累积文本长度大于5个字,处理它
 |                     // 如果累积文本长度大于5个字,处理它
 | ||||||
|                     if (accumulatedText.length > 6 && onSegment) { |                     if (accumulatedText.length > 8 && onSegment) { | ||||||
|                       console.log('检测到完整段落:', accumulatedText); |                       console.log('【已过滤】检测到完整段落:', accumulatedText); | ||||||
|                       await onSegment(accumulatedText, false); |                       // 文本已经过滤过旁白,直接使用
 | ||||||
|  |                       if (accumulatedText.trim()) { | ||||||
|  |                         console.log('处理过滤后的文本:', accumulatedText); | ||||||
|  |                         await onSegment(accumulatedText, false); | ||||||
|  |                       } | ||||||
|                       hasProcessed = true; |                       hasProcessed = true; | ||||||
|                       accumulatedText = ''; // 重置
 |                       accumulatedText = ''; // 重置
 | ||||||
|                     } |                     } | ||||||
|                   } |                   } | ||||||
|                 } |                 } | ||||||
|                  |                  | ||||||
|                 // 更新pendingText
 |                 // 更新pendingText - 使用原始文本但需要相应调整
 | ||||||
|                 if (hasProcessed) { |                 if (hasProcessed) { | ||||||
|                   // 保留未处理的累积文本和最后一个不完整段落
 |                   // 计算已处理的原始文本长度,更新pendingText
 | ||||||
|                   pendingText = accumulatedText + (segments[segments.length - 1] || ''); |                   const processedLength = pendingText.length - (segments[segments.length - 1] || '').length; | ||||||
|  |                   pendingText = pendingText.substring(processedLength); | ||||||
|                 } |                 } | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -96,7 +96,7 @@ async function processAudioQueue() { | |||||||
|   // await new Promise(resolve => setTimeout(resolve, 300));
 |   // await new Promise(resolve => setTimeout(resolve, 300));
 | ||||||
|    |    | ||||||
|   const text = 'default' |   const text = 'default' | ||||||
| 	  console.log("音频结束------------------------:", window.webrtcApp.currentVideoTag, isPlaying) |   console.log("音频结束------------------------:", window.webrtcApp.currentVideoTag, isPlaying) | ||||||
|   if (window.webrtcApp.currentVideoTag != text && !isPlaying) { |   if (window.webrtcApp.currentVideoTag != text && !isPlaying) { | ||||||
|     isFirstChunk = true |     isFirstChunk = true | ||||||
|     window.webrtcApp.currentVideoTag = text |     window.webrtcApp.currentVideoTag = text | ||||||
| @ -431,4 +431,4 @@ function generateUUID() { | |||||||
|   }); |   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export { requestMinimaxi, requestVolcanTTS }; | export { requestMinimaxi, requestVolcanTTS, addAudioToQueue }; | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user