chenpei d0dc94e36d feat: 将弹幕功能替换为AI聊天助手
新功能:
- 创建AI聊天助手组件(AIChatWidget)
- 支持文字+图片多模态对话
- 集成Gemini AI API
- 支持图片编辑和内容生成
- 实时对话历史管理
- 优雅的UI设计,支持黑夜模式

技术实现:
- 后端API代理Gemini API(/api/ai-chat)
- 支持最多5张图片上传
- 流式响应处理
- 对话历史管理
- Base64图片处理

配置:
- 在.env中添加Gemini API配置
- GEMINI_API_KEY
- GEMINI_API_BASE_URL
- GEMINI_MODEL

替换原弹幕功能,提供更强大的AI助手体验!
2025-10-27 17:45:37 +08:00

142 lines
4.9 KiB
JavaScript
Executable File

const fs = require('fs');
const path = require('path');
const logger = require('./logger');
/**
* 清理指定目录中超过指定时间的文件
* @param {string} directory 要清理的目录路径
* @param {number} maxAgeHours 文件最大保留时间(小时)
* @param {object} customLogger 自定义日志对象(可选)
* @param {boolean} isDryRun 是否为模拟运行模式,不实际删除文件
* @returns {Promise<{deletedFiles: number, sizeFreed: number}>} 删除的文件数量和释放的空间大小
*/
async function cleanupDirectory(directory, maxAgeHours = 24, customLogger = null, isDryRun = false) {
// 使用提供的日志器或默认日志器
const log = customLogger || logger;
try {
// 确保目录存在
if (!fs.existsSync(directory)) {
log.warn(`清理目录不存在: ${directory}`);
return { deletedFiles: 0, sizeFreed: 0 };
}
// 获取当前时间
const now = Date.now();
const maxAgeMs = maxAgeHours * 60 * 60 * 1000;
let deletedCount = 0;
let totalSize = 0;
// 读取目录内所有文件
const files = fs.readdirSync(directory);
log.info(`扫描目录 ${directory} 中的 ${files.length} 个文件`);
for (const file of files) {
const filePath = path.join(directory, file);
// 获取文件状态
const stats = fs.statSync(filePath);
// 跳过目录
if (stats.isDirectory()) {
continue;
}
// 计算文件年龄
const fileAge = now - stats.mtime.getTime();
// 如果文件超过最大年龄,删除它
if (fileAge > maxAgeMs) {
const fileSize = stats.size;
totalSize += fileSize;
const ageHours = (fileAge / 1000 / 60 / 60).toFixed(1);
const fileSizeMB = (fileSize / 1024 / 1024).toFixed(2);
if (isDryRun) {
// 模拟模式,只记录不删除
log.info(`将删除文件: ${filePath} (${fileSizeMB}MB, 年龄: ${ageHours}小时)`);
deletedCount++;
} else {
try {
fs.unlinkSync(filePath);
deletedCount++;
log.info(`已删除文件: ${filePath} (${fileSizeMB}MB, 年龄: ${ageHours}小时)`);
} catch (error) {
log.error(`删除文件失败: ${filePath}, 错误: ${error.message}`);
}
}
}
}
const actionText = isDryRun ? '将删除' : '删除了';
log.info(`清理${isDryRun ? '模拟' : ''}完成: ${actionText} ${deletedCount} 个文件, 总计${isDryRun ? '将' : ''}释放 ${(totalSize / 1024 / 1024).toFixed(2)}MB 空间`);
return { deletedFiles: deletedCount, sizeFreed: totalSize };
} catch (error) {
log.error(`清理目录时出错: ${directory}, 错误: ${error.message}`);
return { deletedFiles: 0, sizeFreed: 0 };
}
}
/**
* 执行所有目录的清理操作
* @param {object} customLogger 自定义日志对象(可选)
* @param {boolean} isDryRun 是否为模拟运行,不实际删除文件
* @returns {Promise<{totalFiles: number, totalSize: number}>} 清理结果
*/
async function runCleanup(customLogger = null, isDryRun = false) {
// 使用提供的日志器或默认日志器
const log = customLogger || logger;
log.info(`开始执行文件清理操作${isDryRun ? ' (模拟运行)' : ''}...`);
const baseDir = path.join(__dirname, '..');
let totalFiles = 0;
let totalSize = 0;
// 清理临时上传目录
const tempUploadsDir = path.join(baseDir, 'uploads/temp');
const tempResult = await cleanupDirectory(tempUploadsDir, 24, log, isDryRun); // 清理24小时前的文件
totalFiles += tempResult.deletedFiles;
totalSize += tempResult.sizeFreed;
// 清理处理后的文件目录
const processedDir = path.join(baseDir, 'uploads/processed');
const processedResult = await cleanupDirectory(processedDir, 72, log, isDryRun); // 清理72小时前的文件
totalFiles += processedResult.deletedFiles;
totalSize += processedResult.sizeFreed;
// 清理公共上传目录
const publicUploadsDir = path.join(baseDir, '../public/uploads');
const publicResult = await cleanupDirectory(publicUploadsDir, 168, log, isDryRun); // 清理7天前的文件
totalFiles += publicResult.deletedFiles;
totalSize += publicResult.sizeFreed;
const totalSizeMB = (totalSize / (1024 * 1024)).toFixed(2);
log.info(`文件清理操作完成,${isDryRun ? '将' : '已'}清理 ${totalFiles} 个文件,${isDryRun ? '将' : ''}释放 ${totalSizeMB} MB 空间`);
return {
totalFiles,
totalSize
};
}
// 导出函数供其他模块使用
module.exports = {
cleanupDirectory,
runCleanup
};
// 如果直接运行此脚本,执行清理
if (require.main === module) {
runCleanup()
.then(() => {
console.log('清理任务已完成');
process.exit(0);
})
.catch(error => {
console.error('清理任务失败:', error);
process.exit(1);
});
}