diff --git a/chat_history.json b/chat_history.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/chat_history.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/server.js b/server.js index 6057d2b..e6f86d8 100644 --- a/server.js +++ b/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, () => { - console.log(`服务器运行在端口 ${PORT}`); - console.log(`访问 http://localhost:${PORT} 开始使用`); -}); \ No newline at end of file +server.listen(PORT, async () => { + console.log(`服务器运行在端口 ${PORT}`); + await initializeServer(); +}); + +// 导出消息历史管理器供其他模块使用 +module.exports = { messageHistory }; +console.log(`访问 http://localhost:${PORT} 开始使用`); \ No newline at end of file diff --git a/src/chat_with_audio.js b/src/chat_with_audio.js index 6a0e6f8..4e32fdf 100644 --- a/src/chat_with_audio.js +++ b/src/chat_with_audio.js @@ -10,82 +10,183 @@ let isPlaying = false; let audioQueue = []; let isProcessingQueue = false; -async function chatWithAudioStream(userInput) { - // 验证配置 - if (!validateConfig()) { - throw new Error('配置不完整,请检查config.js文件中的API密钥设置'); - } - - console.log('用户输入:', userInput); - - // 获取配置 - const llmConfig = getLLMConfig(); - const minimaxiConfig = getMinimaxiConfig(); - const audioConfig = getAudioConfig(); - - // 清空音频队列 - audioQueue = []; - - // 定义段落处理函数 - const handleSegment = async (segment) => { - console.log('\n=== 处理文本段落 ==='); - console.log('段落内容:', segment); +// 历史消息缓存 +let historyMessage = []; +let isInitialized = false; + +// 初始化历史消息 +async function initializeHistoryMessage(recentCount = 5) { + if (isInitialized) return historyMessage; try { - // 为每个段落生成音频 - const audioResult = await requestMinimaxi({ - apiKey: minimaxiConfig.apiKey, - groupId: minimaxiConfig.groupId, - body: { - model: audioConfig.model, - text: segment, - stream: audioConfig.stream, - language_boost: audioConfig.language_boost, - output_format: audioConfig.output_format, - voice_setting: audioConfig.voiceSetting, - audio_setting: audioConfig.audioSetting, - }, - stream: true, - }); - - // 将音频添加到播放队列 - if (audioResult && audioResult.data && audioResult.data.audio) { - audioQueue.push({ - text: segment, - audioHex: audioResult.data.audio - }); - console.log('音频已添加到队列,队列长度:', audioQueue.length); - - // 开始处理队列 - processAudioQueue(); - } + 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); + console.error('获取历史消息失败,使用默认格式:', error); + historyMessage = [ + { role: 'system', content: 'You are a helpful assistant.' } + ]; + isInitialized = true; + return historyMessage; } - }; - - // 1. 请求大模型回答,并实时处理段落 - 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 // 传入段落处理回调 - }); - - console.log('\n=== 大模型完整回答 ==='); - console.log("llmResponse: ", llmResponse); - - return { - userInput, - llmResponse, - audioQueue: audioQueue.map(item => ({ text: item.text, hasAudio: !!item.audioHex })) - }; } +// 获取当前历史消息(同步方法) +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密钥设置'); + } + + console.log('用户输入:', userInput); + + // 获取配置 + const llmConfig = getLLMConfig(); + const minimaxiConfig = getMinimaxiConfig(); + const audioConfig = getAudioConfig(); + + // 清空音频队列 + audioQueue = []; + + // 定义段落处理函数 + const handleSegment = async (segment) => { + console.log('\n=== 处理文本段落 ==='); + console.log('段落内容:', segment); + + try { + // 为每个段落生成音频 + const audioResult = await requestMinimaxi({ + apiKey: minimaxiConfig.apiKey, + groupId: minimaxiConfig.groupId, + body: { + model: audioConfig.model, + text: segment, + stream: audioConfig.stream, + language_boost: audioConfig.language_boost, + output_format: audioConfig.output_format, + voice_setting: audioConfig.voiceSetting, + audio_setting: audioConfig.audioSetting, + }, + stream: true, + }); + + // 将音频添加到播放队列 + if (audioResult && audioResult.data && audioResult.data.audio) { + audioQueue.push({ + text: segment, + audioHex: audioResult.data.audio + }); + console.log('音频已添加到队列,队列长度:', audioQueue.length); + + // 开始处理队列 + processAudioQueue(); + } + } catch (error) { + console.error('生成音频失败:', error); + } + }; + + // 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: 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, + audioQueue: audioQueue.map(item => ({ text: item.text, hasAudio: !!item.audioHex })) + }; +} + +// 导出初始化函数,供外部调用 +export { chatWithAudioStream, initializeHistoryMessage, getCurrentHistoryMessage }; + // 处理音频播放队列 async function processAudioQueue() { if (isProcessingQueue) return; @@ -209,4 +310,4 @@ async function playAudioStreamNode(audioHex) { -export { chatWithAudioStream, playAudioStream, playAudioStreamNode}; \ No newline at end of file +// export { chatWithAudioStream, playAudioStream, playAudioStreamNode, initializeHistoryMessage, getCurrentHistoryMessage }; \ No newline at end of file diff --git a/src/index.html b/src/index.html index c729b17..86323ab 100644 --- a/src/index.html +++ b/src/index.html @@ -77,6 +77,6 @@ - + \ No newline at end of file diff --git a/src/index.js b/src/index.js index ec15aa5..e34b052 100644 --- a/src/index.js +++ b/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: () => { @@ -39,6 +49,8 @@ class WebRTCChat { this.voiceStatus.textContent = message; } }); + + console.log('WebRTC 聊天应用初始化完成'); this.initializeElements(); this.initializeSocket(); @@ -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', () => { - new WebRTCChat(); -}); \ No newline at end of file + console.log('DOMContentLoaded 事件触发'); + try { + new WebRTCChat(); + } catch (error) { + console.error('WebRTCChat 初始化失败:', error); + } +}); diff --git a/src/message_history.js b/src/message_history.js new file mode 100644 index 0000000..8cae774 --- /dev/null +++ b/src/message_history.js @@ -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 }; \ No newline at end of file diff --git a/videos/0.mp4 b/videos/0.mp4 new file mode 100644 index 0000000..96bd366 Binary files /dev/null and b/videos/0.mp4 differ diff --git a/videos/1-m.mp4 b/videos/1-m.mp4 new file mode 100644 index 0000000..6948e57 Binary files /dev/null and b/videos/1-m.mp4 differ diff --git a/videos/2.mp4 b/videos/2.mp4 new file mode 100644 index 0000000..152d359 Binary files /dev/null and b/videos/2.mp4 differ diff --git a/videos/4-m.mp4 b/videos/4-m.mp4 new file mode 100644 index 0000000..fb48b27 Binary files /dev/null and b/videos/4-m.mp4 differ diff --git a/videos/asd.mp4 b/videos/asd.mp4 deleted file mode 100644 index ec00ec5..0000000 Binary files a/videos/asd.mp4 and /dev/null differ diff --git a/videos/jkl.mp4 b/videos/jkl.mp4 deleted file mode 100644 index 747cefe..0000000 Binary files a/videos/jkl.mp4 and /dev/null differ diff --git a/videos/zxc.mp4 b/videos/zxc.mp4 deleted file mode 100644 index 8b2d55f..0000000 Binary files a/videos/zxc.mp4 and /dev/null differ