add messages chat
This commit is contained in:
		
							parent
							
								
									d808bbfe26
								
							
						
					
					
						commit
						6f3b1c2d43
					
				
							
								
								
									
										1
									
								
								chat_history.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								chat_history.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| [] | ||||
							
								
								
									
										94
									
								
								server.js
									
									
									
									
									
								
							
							
						
						
									
										94
									
								
								server.js
									
									
									
									
									
								
							| @ -4,15 +4,24 @@ const socketIo = require('socket.io'); | ||||
| const cors = require('cors'); | ||||
| const path = require('path'); | ||||
| const fs = require('fs'); | ||||
| const { MessageHistory } = require('./src/message_history.js'); | ||||
| 
 | ||||
| const app = express(); | ||||
| const server = http.createServer(app); | ||||
| const io = socketIo(server, { | ||||
|   cors: { | ||||
|     origin: "*", | ||||
|     methods: ["GET", "POST"] | ||||
| const io = socketIo(server); | ||||
| 
 | ||||
| // 创建消息历史管理器
 | ||||
| const messageHistory = new MessageHistory(); | ||||
| 
 | ||||
| // 服务器启动时初始化历史消息
 | ||||
| async function initializeServer() { | ||||
|     try { | ||||
|         await messageHistory.initialize(); | ||||
|         console.log('消息历史初始化完成'); | ||||
|     } catch (error) { | ||||
|         console.error('初始化消息历史失败:', error); | ||||
|     } | ||||
| }); | ||||
| } | ||||
| 
 | ||||
| // 中间件
 | ||||
| app.use(cors()); | ||||
| @ -20,22 +29,72 @@ app.use(express.json()); | ||||
| app.use(express.static('src')); | ||||
| app.use('/videos', express.static('videos')); | ||||
| 
 | ||||
| // API路由 - 获取历史消息(用于LLM上下文)
 | ||||
| app.get('/api/messages/for-llm', (req, res) => { | ||||
|     try { | ||||
|         const { includeSystem = true, recentCount = 5 } = req.query; | ||||
|         const messages = messageHistory.getMessagesForLLM( | ||||
|             includeSystem === 'true',  | ||||
|             parseInt(recentCount) | ||||
|         ); | ||||
|         res.json({ messages }); | ||||
|     } catch (error) { | ||||
|         console.error('获取LLM消息失败:', error); | ||||
|         res.status(500).json({ error: '获取消息失败' }); | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| // API路由 - 保存新消息
 | ||||
| app.post('/api/messages/save', async (req, res) => { | ||||
|     try { | ||||
|         const { userInput, assistantResponse } = req.body; | ||||
|         if (!userInput || !assistantResponse) { | ||||
|             return res.status(400).json({ error: '缺少必要参数' }); | ||||
|         } | ||||
|          | ||||
|         await messageHistory.addMessage(userInput, assistantResponse); | ||||
|         res.json({ success: true, message: '消息已保存' }); | ||||
|     } catch (error) { | ||||
|         console.error('保存消息失败:', error); | ||||
|         res.status(500).json({ error: '保存消息失败' }); | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| // API路由 - 获取完整历史(可选,用于调试或展示)
 | ||||
| app.get('/api/messages/history', (req, res) => { | ||||
|     try { | ||||
|         const history = messageHistory.getFullHistory(); | ||||
|         res.json({ history }); | ||||
|     } catch (error) { | ||||
|         console.error('获取历史消息失败:', error); | ||||
|         res.status(500).json({ error: '获取历史消息失败' }); | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| // API路由 - 清空历史
 | ||||
| app.delete('/api/messages/clear', async (req, res) => { | ||||
|     try { | ||||
|         await messageHistory.clearHistory(); | ||||
|         res.json({ success: true, message: '历史消息已清空' }); | ||||
|     } catch (error) { | ||||
|         console.error('清空历史消息失败:', error); | ||||
|         res.status(500).json({ error: '清空历史消息失败' }); | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| // 存储连接的客户端和他们的视频流状态
 | ||||
| const connectedClients = new Map(); | ||||
| 
 | ||||
| // 视频映射配置
 | ||||
| const videoMapping = { | ||||
|   '你好': 'asd.mp4', | ||||
|   'hello': 'asd.mp4', | ||||
|   '再见': 'zxc.mp4', | ||||
|   'goodbye': 'zxc.mp4', | ||||
|   '谢谢': 'jkl.mp4', | ||||
|   'thank you': 'jkl.mp4', | ||||
|   '默认': 'asd.mp4' | ||||
|   'say-6s-m-e': '1-m.mp4', | ||||
|   'default': '0.mp4', | ||||
|   'say-5s-amplitude': '2.mp4', | ||||
|   'say-5s-m-e': 'zxc.mp4' | ||||
| }; | ||||
| 
 | ||||
| // 默认视频流配置
 | ||||
| const DEFAULT_VIDEO = 'asd.mp4'; | ||||
| const DEFAULT_VIDEO = '0.mp4'; | ||||
| const INTERACTION_TIMEOUT = 10000; // 10秒后回到默认视频
 | ||||
| 
 | ||||
| // 获取视频列表
 | ||||
| @ -217,8 +276,13 @@ io.on('connection', (socket) => { | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| // 启动服务器
 | ||||
| const PORT = process.env.PORT || 3000; | ||||
| server.listen(PORT, () => { | ||||
| server.listen(PORT, async () => { | ||||
|     console.log(`服务器运行在端口 ${PORT}`); | ||||
|   console.log(`访问 http://localhost:${PORT} 开始使用`); | ||||
|     await initializeServer(); | ||||
| }); | ||||
| 
 | ||||
| // 导出消息历史管理器供其他模块使用
 | ||||
| module.exports = { messageHistory }; | ||||
| console.log(`访问 http://localhost:${PORT} 开始使用`); | ||||
| @ -10,7 +10,95 @@ let isPlaying = false; | ||||
| let audioQueue = []; | ||||
| let isProcessingQueue = false; | ||||
| 
 | ||||
| // 历史消息缓存
 | ||||
| let historyMessage = []; | ||||
| let isInitialized = false; | ||||
| 
 | ||||
| // 初始化历史消息
 | ||||
| async function initializeHistoryMessage(recentCount = 5) { | ||||
|     if (isInitialized) return historyMessage; | ||||
|      | ||||
|     try { | ||||
|         const response = await fetch(`/api/messages/for-llm?includeSystem=true&recentCount=${recentCount}`); | ||||
|         if (!response.ok) { | ||||
|             throw new Error('获取历史消息失败'); | ||||
|         } | ||||
|         const data = await response.json(); | ||||
|         historyMessage = data.messages || []; | ||||
|         isInitialized = true; | ||||
|         console.log("历史消息初始化完成:", historyMessage.length, "条消息"); | ||||
|         return historyMessage; | ||||
|     } catch (error) { | ||||
|         console.error('获取历史消息失败,使用默认格式:', error); | ||||
|         historyMessage = [ | ||||
|             { role: 'system', content: 'You are a helpful assistant.' } | ||||
|         ]; | ||||
|         isInitialized = true; | ||||
|         return historyMessage; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // 获取当前历史消息(同步方法)
 | ||||
| function getCurrentHistoryMessage() { | ||||
|     if (!isInitialized) { | ||||
|         console.warn('历史消息未初始化,返回默认消息'); | ||||
|         return [{ role: 'system', content: 'You are a helpful assistant.' }]; | ||||
|     } | ||||
|     return [...historyMessage]; // 返回副本,避免外部修改
 | ||||
| } | ||||
| 
 | ||||
| // 更新历史消息
 | ||||
| function updateHistoryMessage(userInput, assistantResponse) { | ||||
|     if (!isInitialized) { | ||||
|         console.warn('历史消息未初始化,无法更新'); | ||||
|         return; | ||||
|     } | ||||
|      | ||||
|     historyMessage.push( | ||||
|         { role: 'user', content: userInput }, | ||||
|         { role: 'assistant', content: assistantResponse } | ||||
|     ); | ||||
|      | ||||
|     // 可选:限制历史消息数量,保持最近的对话
 | ||||
|     const maxMessages = 20; // 保留最近10轮对话(20条消息)
 | ||||
|     if (historyMessage.length > maxMessages) { | ||||
|         // 保留系统消息和最近的对话
 | ||||
|         const systemMessages = historyMessage.filter(msg => msg.role === 'system'); | ||||
|         const recentMessages = historyMessage.slice(-maxMessages + systemMessages.length); | ||||
|         historyMessage = [...systemMessages, ...recentMessages.filter(msg => msg.role !== 'system')]; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // 保存消息到服务端
 | ||||
| async function saveMessage(userInput, assistantResponse) { | ||||
|     try { | ||||
|         const response = await fetch('/api/messages/save', { | ||||
|             method: 'POST', | ||||
|             headers: { | ||||
|                 'Content-Type': 'application/json' | ||||
|             }, | ||||
|             body: JSON.stringify({ | ||||
|                 userInput, | ||||
|                 assistantResponse | ||||
|             }) | ||||
|         }); | ||||
|          | ||||
|         if (!response.ok) { | ||||
|             throw new Error('保存消息失败'); | ||||
|         } | ||||
|          | ||||
|         console.log('消息已保存到服务端'); | ||||
|     } catch (error) { | ||||
|         console.error('保存消息失败:', error); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| async function chatWithAudioStream(userInput) { | ||||
|     // 确保历史消息已初始化
 | ||||
|     if (!isInitialized) { | ||||
|         await initializeHistoryMessage(); | ||||
|     } | ||||
|      | ||||
|     // 验证配置
 | ||||
|     if (!validateConfig()) { | ||||
|         throw new Error('配置不完整,请检查config.js文件中的API密钥设置'); | ||||
| @ -64,21 +152,31 @@ async function chatWithAudioStream(userInput) { | ||||
|         } | ||||
|     }; | ||||
|      | ||||
|   // 1. 请求大模型回答,并实时处理段落
 | ||||
|     // 1. 获取包含历史的消息列表
 | ||||
|     console.log('\n=== 获取历史消息 ==='); | ||||
|     const messages = getCurrentHistoryMessage(); | ||||
|     messages.push({role: 'user', content: userInput}); | ||||
|     console.log('发送的消息数量:', messages); | ||||
|      | ||||
|     // 2. 请求大模型回答
 | ||||
|     console.log('\n=== 请求大模型回答 ==='); | ||||
|     const llmResponse = await requestLLMStream({ | ||||
|         apiKey: llmConfig.apiKey, | ||||
|         model: llmConfig.model, | ||||
|     messages: [ | ||||
|       { role: 'system', content: 'You are a helpful assistant.' }, | ||||
|       { role: 'user', content: userInput }, | ||||
|     ], | ||||
|     onSegment: handleSegment // 传入段落处理回调
 | ||||
|         messages: messages, | ||||
|         onSegment: handleSegment | ||||
|     }); | ||||
|      | ||||
|     console.log('\n=== 大模型完整回答 ==='); | ||||
|     console.log("llmResponse: ", llmResponse); | ||||
|      | ||||
|     // 3. 保存对话到服务端
 | ||||
|     await saveMessage(userInput, llmResponse); | ||||
|      | ||||
|     // 4. 更新本地历史消息
 | ||||
|     updateHistoryMessage(userInput, llmResponse); | ||||
|     console.log('历史消息数量:', historyMessage.length); | ||||
|      | ||||
|     return { | ||||
|         userInput, | ||||
|         llmResponse, | ||||
| @ -86,6 +184,9 @@ async function chatWithAudioStream(userInput) { | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| // 导出初始化函数,供外部调用
 | ||||
| export { chatWithAudioStream, initializeHistoryMessage, getCurrentHistoryMessage }; | ||||
| 
 | ||||
| // 处理音频播放队列
 | ||||
| async function processAudioQueue() { | ||||
|   if (isProcessingQueue) return; | ||||
| @ -209,4 +310,4 @@ async function playAudioStreamNode(audioHex) { | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| export { chatWithAudioStream, playAudioStream, playAudioStreamNode}; | ||||
| // export { chatWithAudioStream, playAudioStream, playAudioStreamNode, initializeHistoryMessage, getCurrentHistoryMessage };
 | ||||
| @ -77,6 +77,6 @@ | ||||
|     <video id="remoteVideo" autoplay playsinline style="display: none;"></video> | ||||
| 
 | ||||
|     <script src="/socket.io/socket.io.js"></script> | ||||
|     <script type="module" src="index.js"></script> | ||||
|     <script type="module" src="./index.js"></script> | ||||
| </body> | ||||
| </html>  | ||||
							
								
								
									
										30
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								src/index.js
									
									
									
									
									
								
							| @ -1,9 +1,17 @@ | ||||
| console.log('视频文件:'); | ||||
| // WebRTC 音视频通话应用
 | ||||
| import { chatWithAudioStream } from './chat_with_audio.js'; | ||||
| // import { chatWithAudioStream } from './chat_with_audio.js';
 | ||||
| import { chatWithAudioStream, initializeHistoryMessage } from './chat_with_audio.js'; | ||||
| import { AudioProcessor } from './audio_processor.js'; | ||||
| 
 | ||||
| // 在应用初始化时调用
 | ||||
| class WebRTCChat { | ||||
|     constructor() { | ||||
|         console.log('WebRTCChat 构造函数开始执行'); | ||||
|          | ||||
|         // 初始化历史消息(异步)
 | ||||
|         this.initializeHistory(); | ||||
|          | ||||
|         this.socket = null; | ||||
|         this.localStream = null; | ||||
|         this.peerConnection = null; | ||||
| @ -11,11 +19,13 @@ class WebRTCChat { | ||||
|         this.mediaRecorder = null; | ||||
|         this.audioChunks = []; | ||||
|         this.videoMapping = {}; | ||||
|         this.defaultVideo = 'asd.mp4'; | ||||
|         this.defaultVideo = '0.mp4'; | ||||
|         this.currentVideo = null; | ||||
|         this.videoStreams = new Map(); // 存储不同视频的MediaStream
 | ||||
|         this.currentVideoStream = null; | ||||
|          | ||||
|         // 初始化音频处理器
 | ||||
|         console.log('开始初始化音频处理器'); | ||||
|         // 初始化音频处理器
 | ||||
|         this.audioProcessor = new AudioProcessor({ | ||||
|             onSpeechStart: () => { | ||||
| @ -40,6 +50,8 @@ class WebRTCChat { | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         console.log('WebRTC 聊天应用初始化完成'); | ||||
|          | ||||
|         this.initializeElements(); | ||||
|         this.initializeSocket(); | ||||
|         this.loadVideoMapping(); | ||||
| @ -117,6 +129,15 @@ class WebRTCChat { | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     async initializeHistory() { | ||||
|         try { | ||||
|             await initializeHistoryMessage(); | ||||
|             console.log('历史消息初始化完成'); | ||||
|         } catch (error) { | ||||
|             console.error('历史消息初始化失败:', error); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async loadVideoMapping() { | ||||
|         try { | ||||
|             const response = await fetch('/api/video-mapping'); | ||||
| @ -782,5 +803,10 @@ class WebRTCChat { | ||||
| 
 | ||||
| // 页面加载完成后初始化应用
 | ||||
| document.addEventListener('DOMContentLoaded', () => { | ||||
|     console.log('DOMContentLoaded 事件触发'); | ||||
|     try { | ||||
|         new WebRTCChat(); | ||||
|     } catch (error) { | ||||
|         console.error('WebRTCChat 初始化失败:', error); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										134
									
								
								src/message_history.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								src/message_history.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,134 @@ | ||||
| const fs = require('fs').promises; | ||||
| const path = require('path'); | ||||
| 
 | ||||
| class MessageHistory { | ||||
|     constructor() { | ||||
|         this.historyFile = path.join(__dirname, '..', 'chat_history.json'); | ||||
|         this.messages = []; // 内存中的消息历史
 | ||||
|         this.maxMessages = 100; // 最大保存消息数量
 | ||||
|     } | ||||
| 
 | ||||
|     // 服务器启动时初始化,从JSON文件预加载历史
 | ||||
|     async initialize() { | ||||
|         try { | ||||
|             const data = await fs.readFile(this.historyFile, 'utf8'); | ||||
|             this.messages = JSON.parse(data); | ||||
|             console.log(`已加载 ${this.messages.length} 条历史消息`); | ||||
|         } catch (error) { | ||||
|             if (error.code === 'ENOENT') { | ||||
|                 console.log('历史文件不存在,创建新的消息历史'); | ||||
|                 this.messages = []; | ||||
|                 await this.saveToFile(); | ||||
|             } else { | ||||
|                 console.error('加载历史消息失败:', error); | ||||
|                 this.messages = []; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // 添加新消息(用户输入和助手回复)
 | ||||
|     async addMessage(userInput, assistantResponse) { | ||||
|         // 添加用户消息
 | ||||
|         this.messages.push({ | ||||
|             role: 'user', | ||||
|             content: userInput, | ||||
|             timestamp: new Date().toISOString() | ||||
|         }); | ||||
| 
 | ||||
|         // 添加助手回复
 | ||||
|         this.messages.push({ | ||||
|             role: 'assistant',  | ||||
|             content: assistantResponse, | ||||
|             timestamp: new Date().toISOString() | ||||
|         }); | ||||
| 
 | ||||
|         // 限制消息数量(保留最近的对话)
 | ||||
|         if (this.messages.length > this.maxMessages * 2) { | ||||
|             this.messages = this.messages.slice(-this.maxMessages * 2); | ||||
|         } | ||||
| 
 | ||||
|         // 同时保存到文件和内存
 | ||||
|         await this.saveToFile(); | ||||
|          | ||||
|         console.log(`已保存对话,当前历史消息数: ${this.messages.length}`); | ||||
|     } | ||||
| 
 | ||||
|     // 获取用于大模型的消息格式
 | ||||
|     getMessagesForLLM(includeSystem = true, recentCount = 10) { | ||||
|         const messages = []; | ||||
|          | ||||
|         // 添加系统消息
 | ||||
|         if (includeSystem) { | ||||
|             messages.push({ | ||||
|                 role: 'system', | ||||
|                 content: 'You are a helpful assistant.' | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         // 获取最近的对话历史
 | ||||
|         const recentMessages = this.messages.slice(-recentCount * 2); // 用户+助手成对出现
 | ||||
|         messages.push(...recentMessages.map(msg => ({ | ||||
|             role: msg.role, | ||||
|             content: msg.content | ||||
|         }))); | ||||
| 
 | ||||
|         return messages; | ||||
|     } | ||||
| 
 | ||||
|     // 获取完整历史(包含时间戳)
 | ||||
|     getFullHistory() { | ||||
|         return [...this.messages]; | ||||
|     } | ||||
| 
 | ||||
|     // 清空历史
 | ||||
|     async clearHistory() { | ||||
|         this.messages = []; | ||||
|         await this.saveToFile(); | ||||
|         console.log('历史消息已清空'); | ||||
|     } | ||||
| 
 | ||||
|     // 保存到JSON文件
 | ||||
|     async saveToFile() { | ||||
|         try { | ||||
|             await fs.writeFile(this.historyFile, JSON.stringify(this.messages, null, 2), 'utf8'); | ||||
|         } catch (error) { | ||||
|             console.error('保存历史消息到文件失败:', error); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // 导出历史(备份)
 | ||||
|     async exportHistory(filename) { | ||||
|         const exportPath = filename || `chat_backup_${new Date().toISOString().split('T')[0]}.json`; | ||||
|         try { | ||||
|             await fs.writeFile(exportPath, JSON.stringify(this.messages, null, 2), 'utf8'); | ||||
|             console.log(`历史消息已导出到: ${exportPath}`); | ||||
|             return exportPath; | ||||
|         } catch (error) { | ||||
|             console.error('导出历史消息失败:', error); | ||||
|             throw error; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // 导入历史
 | ||||
|     async importHistory(filename) { | ||||
|         try { | ||||
|             const data = await fs.readFile(filename, 'utf8'); | ||||
|             const importedMessages = JSON.parse(data); | ||||
|              | ||||
|             // 验证消息格式
 | ||||
|             if (Array.isArray(importedMessages)) { | ||||
|                 this.messages = importedMessages; | ||||
|                 await this.saveToFile(); | ||||
|                 console.log(`已导入 ${this.messages.length} 条历史消息`); | ||||
|                 return true; | ||||
|             } else { | ||||
|                 throw new Error('无效的消息格式'); | ||||
|             } | ||||
|         } catch (error) { | ||||
|             console.error('导入历史消息失败:', error); | ||||
|             throw error; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| module.exports = { MessageHistory }; | ||||
							
								
								
									
										
											BIN
										
									
								
								videos/0.mp4
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/0.mp4
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/1-m.mp4
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/1-m.mp4
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/2.mp4
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/2.mp4
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/4-m.mp4
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/4-m.mp4
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/asd.mp4
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/asd.mp4
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/jkl.mp4
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/jkl.mp4
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/zxc.mp4
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/zxc.mp4
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user