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