WebRtc_QingGan/server.js
2025-07-29 02:50:08 +08:00

290 lines
8.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require('express');
const http = require('http');
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);
// 创建消息历史管理器
const messageHistory = new MessageHistory();
// 服务器启动时初始化历史消息
async function initializeServer() {
try {
await messageHistory.initialize();
console.log('消息历史初始化完成');
} catch (error) {
console.error('初始化消息历史失败:', error);
}
}
// 中间件
app.use(cors());
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 = {
// 'say-6s-m-e': '1-m.mp4',
'default': 'd-3s.mp4',
// 'say-5s-amplitude': '2.mp4',
// 'say-5s-m-e': '4.mp4',
// 'say-5s-m-sw': '5.mp4',
'say-3s-m-sw': 's-1.mp4',
};
// 默认视频流配置
const DEFAULT_VIDEO = 'd-3s.mp4';
const INTERACTION_TIMEOUT = 10000; // 10秒后回到默认视频
// 获取视频列表
app.get('/api/videos', (req, res) => {
const videosDir = path.join(__dirname, 'videos');
fs.readdir(videosDir, (err, files) => {
if (err) {
return res.status(500).json({ error: '无法读取视频目录' });
}
const videoFiles = files.filter(file =>
file.endsWith('.mp4') || file.endsWith('.webm') || file.endsWith('.avi')
);
res.json({ videos: videoFiles });
});
});
// 获取视频映射
app.get('/api/video-mapping', (req, res) => {
res.json({ mapping: videoMapping });
});
// 获取默认视频
app.get('/api/default-video', (req, res) => {
res.json({
defaultVideo: DEFAULT_VIDEO,
autoLoop: true
});
});
// Socket.IO 连接处理
io.on('connection', (socket) => {
console.log('用户连接:', socket.id);
connectedClients.set(socket.id, {
socket: socket,
currentVideo: DEFAULT_VIDEO,
isInInteraction: false
});
// 处理WebRTC信令 - 用于传输视频流
socket.on('offer', (data) => {
console.log('收到offer:', socket.id);
socket.broadcast.emit('offer', {
...data,
from: socket.id
});
});
socket.on('answer', (data) => {
console.log('收到answer:', socket.id);
socket.broadcast.emit('answer', {
...data,
from: socket.id
});
});
socket.on('ice-candidate', (data) => {
console.log('收到ice-candidate:', socket.id);
socket.broadcast.emit('ice-candidate', {
...data,
from: socket.id
});
});
// 处理视频流切换请求
socket.on('switch-video-stream', (data) => {
const { videoFile, type, text } = data;
console.log(`用户 ${socket.id} 请求切换视频流: ${videoFile} (${type})`);
// 更新客户端状态
const client = connectedClients.get(socket.id);
if (client) {
client.currentVideo = videoFile;
client.isInInteraction = true;
}
// 广播视频流切换指令给所有用户
io.emit('video-stream-switched', {
videoFile,
type,
text,
from: socket.id
});
// 如果是交互类型,设置定时器回到默认视频
if (type === 'text' || type === 'voice') {
setTimeout(() => {
console.log(`交互超时,用户 ${socket.id} 回到默认视频`);
if (client) {
client.currentVideo = DEFAULT_VIDEO;
client.isInInteraction = false;
}
// 广播回到默认视频的指令
io.emit('video-stream-switched', {
videoFile: DEFAULT_VIDEO,
type: 'default',
from: socket.id
});
}, INTERACTION_TIMEOUT);
}
});
// 处理通话开始
socket.on('call-started', () => {
console.log('通话开始,用户:', socket.id);
const client = connectedClients.get(socket.id);
if (client) {
client.currentVideo = DEFAULT_VIDEO;
client.isInInteraction = false;
}
io.emit('call-started', { from: socket.id });
});
// 处理文本输入
socket.on('text-input', (data) => {
const { text } = data;
console.log('收到文本输入:', text, '来自用户:', socket.id);
// 根据文本查找对应视频
let videoFile = videoMapping['default'];
for (const [key, value] of Object.entries(videoMapping)) {
if (text.toLowerCase().includes(key.toLowerCase())) {
videoFile = value;
break;
}
}
console.log(`用户 ${socket.id} 文本输入 "${text}" 对应视频: ${videoFile}`);
// 发送视频流切换请求
socket.emit('switch-video-stream', {
videoFile,
type: 'text',
text
});
});
// 处理语音输入
socket.on('voice-input', (data) => {
const { audioData, text } = data;
console.log('收到语音输入:', text, '来自用户:', socket.id);
// 根据语音识别的文本查找对应视频
let videoFile = videoMapping['default'];
for (const [key, value] of Object.entries(videoMapping)) {
if (text.toLowerCase().includes(key.toLowerCase())) {
videoFile = value;
break;
}
}
console.log(`用户 ${socket.id} 语音输入 "${text}" 对应视频: ${videoFile}`);
// 发送视频流切换请求
socket.emit('switch-video-stream', {
videoFile,
type: 'voice',
text
});
});
// 处理回到默认视频请求
socket.on('return-to-default', () => {
console.log('用户请求回到默认视频:', socket.id);
const client = connectedClients.get(socket.id);
if (client) {
client.currentVideo = DEFAULT_VIDEO;
client.isInInteraction = false;
}
socket.emit('switch-video-stream', {
videoFile: DEFAULT_VIDEO,
type: 'default'
});
});
// 断开连接
socket.on('disconnect', () => {
console.log('用户断开连接:', socket.id);
connectedClients.delete(socket.id);
});
});
// 启动服务器
const PORT = process.env.PORT || 3000;
server.listen(PORT, async () => {
console.log(`服务器运行在端口 ${PORT}`);
await initializeServer();
});
// 导出消息历史管理器供其他模块使用
module.exports = { messageHistory };
console.log(`访问 http://localhost:${PORT} 开始使用`);