|
@@ -187,6 +187,7 @@ export async function ocrLastMessage(ipPort, method, avatarPath, area, folderPat
|
|
|
|
|
|
|
|
// 3. 保存截图到临时文件(如果提供了工作流文件夹,保存到 tmp/时间戳 目录)
|
|
// 3. 保存截图到临时文件(如果提供了工作流文件夹,保存到 tmp/时间戳 目录)
|
|
|
let screenshotPath;
|
|
let screenshotPath;
|
|
|
|
|
+ let tmpDir = null; // 用于跟踪需要删除的临时目录
|
|
|
if (folderPath) {
|
|
if (folderPath) {
|
|
|
const { mkdir } = await import('fs/promises');
|
|
const { mkdir } = await import('fs/promises');
|
|
|
// 确保 folderPath 是绝对路径
|
|
// 确保 folderPath 是绝对路径
|
|
@@ -206,7 +207,7 @@ export async function ocrLastMessage(ipPort, method, avatarPath, area, folderPat
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19).replace('T', '_');
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19).replace('T', '_');
|
|
|
- const tmpDir = join(absoluteFolderPath, 'tmp', timestamp);
|
|
|
|
|
|
|
+ tmpDir = join(absoluteFolderPath, 'tmp', timestamp);
|
|
|
await mkdir(tmpDir, { recursive: true });
|
|
await mkdir(tmpDir, { recursive: true });
|
|
|
screenshotPath = join(tmpDir, 'screenshot_ocr.png');
|
|
screenshotPath = join(tmpDir, 'screenshot_ocr.png');
|
|
|
} else {
|
|
} else {
|
|
@@ -216,45 +217,57 @@ export async function ocrLastMessage(ipPort, method, avatarPath, area, folderPat
|
|
|
const screenshotBuffer = Buffer.from(screenshotResult.data, 'base64');
|
|
const screenshotBuffer = Buffer.from(screenshotResult.data, 'base64');
|
|
|
await writeFile(screenshotPath, screenshotBuffer);
|
|
await writeFile(screenshotPath, screenshotBuffer);
|
|
|
|
|
|
|
|
- // 4. 调用 JS 实现进行OCR识别
|
|
|
|
|
- const normalizedScreenshotPath = screenshotPath.replace(/\\/g, '/');
|
|
|
|
|
- let result;
|
|
|
|
|
-
|
|
|
|
|
- if (method === 'full-screen') {
|
|
|
|
|
- // 全屏OCR识别
|
|
|
|
|
- result = await ocrFullScreenFromFunc(normalizedScreenshotPath, width, height);
|
|
|
|
|
- } else if (method === 'by-avatar' && avatarPath) {
|
|
|
|
|
- // 通过头像定位最后一条消息
|
|
|
|
|
- let friendAvatarArg = null;
|
|
|
|
|
- let myAvatarArg = null;
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 4. 调用 JS 实现进行OCR识别
|
|
|
|
|
+ const normalizedScreenshotPath = screenshotPath.replace(/\\/g, '/');
|
|
|
|
|
+ let result;
|
|
|
|
|
|
|
|
- if (isAbsolute(avatarPath)) {
|
|
|
|
|
- friendAvatarArg = avatarPath;
|
|
|
|
|
- myAvatarArg = avatarPath;
|
|
|
|
|
|
|
+ if (method === 'full-screen') {
|
|
|
|
|
+ // 全屏OCR识别
|
|
|
|
|
+ result = await ocrFullScreenFromFunc(normalizedScreenshotPath, width, height);
|
|
|
|
|
+ } else if (method === 'by-avatar' && avatarPath) {
|
|
|
|
|
+ // 通过头像定位最后一条消息
|
|
|
|
|
+ let friendAvatarArg = null;
|
|
|
|
|
+ let myAvatarArg = null;
|
|
|
|
|
+
|
|
|
|
|
+ if (isAbsolute(avatarPath)) {
|
|
|
|
|
+ friendAvatarArg = avatarPath;
|
|
|
|
|
+ myAvatarArg = avatarPath;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const folderName = avatarPath.split(/[/\\]/)[0];
|
|
|
|
|
+ const avatarName = avatarPath.split(/[/\\]/).slice(1).join('/');
|
|
|
|
|
+ friendAvatarArg = join(__dirname, '..', 'static', 'processing', folderName, avatarName);
|
|
|
|
|
+ myAvatarArg = friendAvatarArg;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const normalizedFriendAvatar = friendAvatarArg.replace(/\\/g, '/');
|
|
|
|
|
+ const normalizedMyAvatar = myAvatarArg.replace(/\\/g, '/');
|
|
|
|
|
+ result = await getLastMessageFromFunc(normalizedScreenshotPath, normalizedFriendAvatar, normalizedMyAvatar, width, height);
|
|
|
} else {
|
|
} else {
|
|
|
- const folderName = avatarPath.split(/[/\\]/)[0];
|
|
|
|
|
- const avatarName = avatarPath.split(/[/\\]/).slice(1).join('/');
|
|
|
|
|
- friendAvatarArg = join(__dirname, '..', 'static', 'processing', folderName, avatarName);
|
|
|
|
|
- myAvatarArg = friendAvatarArg;
|
|
|
|
|
|
|
+ // 默认使用全屏OCR
|
|
|
|
|
+ result = await ocrFullScreenFromFunc(normalizedScreenshotPath, width, height);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const normalizedFriendAvatar = friendAvatarArg.replace(/\\/g, '/');
|
|
|
|
|
- const normalizedMyAvatar = myAvatarArg.replace(/\\/g, '/');
|
|
|
|
|
- result = await getLastMessageFromFunc(normalizedScreenshotPath, normalizedFriendAvatar, normalizedMyAvatar, width, height);
|
|
|
|
|
- } else {
|
|
|
|
|
- // 默认使用全屏OCR
|
|
|
|
|
- result = await ocrFullScreenFromFunc(normalizedScreenshotPath, width, height);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (result.success) {
|
|
|
|
|
- // 返回兼容旧API的格式
|
|
|
|
|
- return {
|
|
|
|
|
- success: true,
|
|
|
|
|
- text: result.text || '',
|
|
|
|
|
- position: result.position || null
|
|
|
|
|
- };
|
|
|
|
|
- } else {
|
|
|
|
|
- return { success: false, error: result.error || 'OCR识别失败' };
|
|
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ // 返回兼容旧API的格式
|
|
|
|
|
+ return {
|
|
|
|
|
+ success: true,
|
|
|
|
|
+ text: result.text || '',
|
|
|
|
|
+ position: result.position || null
|
|
|
|
|
+ };
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return { success: false, error: result.error || 'OCR识别失败' };
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ // 5. 使用完后删除临时目录
|
|
|
|
|
+ if (tmpDir) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await rm(tmpDir, { recursive: true, force: true });
|
|
|
|
|
+ console.log(`已删除临时目录: ${tmpDir}`);
|
|
|
|
|
+ } catch (rmError) {
|
|
|
|
|
+ console.warn(`删除临时目录失败: ${tmpDir}`, rmError);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('OCR识别失败:', error);
|
|
console.error('OCR识别失败:', error);
|
|
@@ -287,6 +300,7 @@ export async function extractChatHistory(ipPort, friendAvatarPath, myAvatarPath,
|
|
|
|
|
|
|
|
// 3. 保存截图到临时文件(如果提供了工作流文件夹,保存到 tmp/时间戳 目录)
|
|
// 3. 保存截图到临时文件(如果提供了工作流文件夹,保存到 tmp/时间戳 目录)
|
|
|
let screenshotPath;
|
|
let screenshotPath;
|
|
|
|
|
+ let tmpDir = null; // 用于跟踪需要删除的临时目录
|
|
|
if (workflowFolderPath) {
|
|
if (workflowFolderPath) {
|
|
|
const { mkdir } = await import('fs/promises');
|
|
const { mkdir } = await import('fs/promises');
|
|
|
// 确保 workflowFolderPath 是绝对路径
|
|
// 确保 workflowFolderPath 是绝对路径
|
|
@@ -306,7 +320,7 @@ export async function extractChatHistory(ipPort, friendAvatarPath, myAvatarPath,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19).replace('T', '_');
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19).replace('T', '_');
|
|
|
- const tmpDir = join(absoluteWorkflowPath, 'tmp', timestamp);
|
|
|
|
|
|
|
+ tmpDir = join(absoluteWorkflowPath, 'tmp', timestamp);
|
|
|
await mkdir(tmpDir, { recursive: true });
|
|
await mkdir(tmpDir, { recursive: true });
|
|
|
screenshotPath = join(tmpDir, 'screenshot.png');
|
|
screenshotPath = join(tmpDir, 'screenshot.png');
|
|
|
} else {
|
|
} else {
|
|
@@ -326,46 +340,58 @@ export async function extractChatHistory(ipPort, friendAvatarPath, myAvatarPath,
|
|
|
return { success: false, error: `截图文件写入失败: ${err.message}` };
|
|
return { success: false, error: `截图文件写入失败: ${err.message}` };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 4. 调用 JS 函数提取聊天记录
|
|
|
|
|
- // 转换头像路径为绝对路径
|
|
|
|
|
- let friendAvatarArg = null;
|
|
|
|
|
- if (friendAvatarPath) {
|
|
|
|
|
- if (isAbsolute(friendAvatarPath)) {
|
|
|
|
|
- friendAvatarArg = friendAvatarPath;
|
|
|
|
|
- } else {
|
|
|
|
|
- friendAvatarArg = join(__dirname, '..', 'static', 'processing', friendAvatarPath);
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 4. 调用 JS 函数提取聊天记录
|
|
|
|
|
+ // 转换头像路径为绝对路径
|
|
|
|
|
+ let friendAvatarArg = null;
|
|
|
|
|
+ if (friendAvatarPath) {
|
|
|
|
|
+ if (isAbsolute(friendAvatarPath)) {
|
|
|
|
|
+ friendAvatarArg = friendAvatarPath;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ friendAvatarArg = join(__dirname, '..', 'static', 'processing', friendAvatarPath);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let myAvatarArg = null;
|
|
|
|
|
- if (myAvatarPath) {
|
|
|
|
|
- if (isAbsolute(myAvatarPath)) {
|
|
|
|
|
- myAvatarArg = myAvatarPath;
|
|
|
|
|
- } else {
|
|
|
|
|
- myAvatarArg = join(__dirname, '..', 'static', 'processing', myAvatarPath);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ let myAvatarArg = null;
|
|
|
|
|
+ if (myAvatarPath) {
|
|
|
|
|
+ if (isAbsolute(myAvatarPath)) {
|
|
|
|
|
+ myAvatarArg = myAvatarPath;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ myAvatarArg = join(__dirname, '..', 'static', 'processing', myAvatarPath);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 如果提供了工作流文件夹路径,转换为绝对路径
|
|
|
|
|
- let workflowFolderArg = null;
|
|
|
|
|
- if (workflowFolderPath) {
|
|
|
|
|
- if (isAbsolute(workflowFolderPath)) {
|
|
|
|
|
- workflowFolderArg = workflowFolderPath;
|
|
|
|
|
- } else {
|
|
|
|
|
- if (workflowFolderPath.startsWith('static/processing/')) {
|
|
|
|
|
- const folderName = workflowFolderPath.replace('static/processing/', '');
|
|
|
|
|
- workflowFolderArg = join(__dirname, '..', 'static', 'processing', folderName);
|
|
|
|
|
- } else if (workflowFolderPath.startsWith('static\\processing\\')) {
|
|
|
|
|
- const folderName = workflowFolderPath.replace('static\\processing\\', '');
|
|
|
|
|
- workflowFolderArg = join(__dirname, '..', 'static', 'processing', folderName);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 如果提供了工作流文件夹路径,转换为绝对路径
|
|
|
|
|
+ let workflowFolderArg = null;
|
|
|
|
|
+ if (workflowFolderPath) {
|
|
|
|
|
+ if (isAbsolute(workflowFolderPath)) {
|
|
|
|
|
+ workflowFolderArg = workflowFolderPath;
|
|
|
} else {
|
|
} else {
|
|
|
- workflowFolderArg = join(__dirname, '..', 'static', 'processing', workflowFolderPath);
|
|
|
|
|
|
|
+ if (workflowFolderPath.startsWith('static/processing/')) {
|
|
|
|
|
+ const folderName = workflowFolderPath.replace('static/processing/', '');
|
|
|
|
|
+ workflowFolderArg = join(__dirname, '..', 'static', 'processing', folderName);
|
|
|
|
|
+ } else if (workflowFolderPath.startsWith('static\\processing\\')) {
|
|
|
|
|
+ const folderName = workflowFolderPath.replace('static\\processing\\', '');
|
|
|
|
|
+ workflowFolderArg = join(__dirname, '..', 'static', 'processing', folderName);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ workflowFolderArg = join(__dirname, '..', 'static', 'processing', workflowFolderPath);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const result = await extractChatHistoryFromFunc(screenshotPath, friendAvatarArg, myAvatarArg, width, height, workflowFolderArg);
|
|
|
|
|
+ return result;
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ // 5. 使用完后删除临时目录
|
|
|
|
|
+ if (tmpDir) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await rm(tmpDir, { recursive: true, force: true });
|
|
|
|
|
+ console.log(`已删除临时目录: ${tmpDir}`);
|
|
|
|
|
+ } catch (rmError) {
|
|
|
|
|
+ console.warn(`删除临时目录失败: ${tmpDir}`, rmError);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- const result = await extractChatHistoryFromFunc(screenshotPath, friendAvatarArg, myAvatarArg, width, height, workflowFolderArg);
|
|
|
|
|
- return result;
|
|
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('提取聊天记录失败:', error);
|
|
console.error('提取聊天记录失败:', error);
|
|
|
if (error.message && error.message.includes('timeout')) {
|
|
if (error.message && error.message.includes('timeout')) {
|
|
@@ -604,6 +630,55 @@ export function registerIpcHandlers() {
|
|
|
await writeFile(filePath, JSON.stringify(historyData, null, 2), 'utf-8');
|
|
await writeFile(filePath, JSON.stringify(historyData, null, 2), 'utf-8');
|
|
|
|
|
|
|
|
console.log(`聊天记录已保存到: ${filePath}`);
|
|
console.log(`聊天记录已保存到: ${filePath}`);
|
|
|
|
|
+
|
|
|
|
|
+ // 限制 history 文件夹中最多保存 10 个文件,超过则删除最早的文件
|
|
|
|
|
+ try {
|
|
|
|
|
+ const files = await readdir(historyDir, { withFileTypes: true });
|
|
|
|
|
+ // 过滤出 JSON 文件(chat_*.json)
|
|
|
|
|
+ const jsonFiles = files
|
|
|
|
|
+ .filter(file => file.isFile() && file.name.startsWith('chat_') && file.name.endsWith('.json'))
|
|
|
|
|
+ .map(file => ({
|
|
|
|
|
+ name: file.name,
|
|
|
|
|
+ path: join(historyDir, file.name)
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
|
|
+ // 如果文件数量超过 10 个,删除最早的文件
|
|
|
|
|
+ if (jsonFiles.length > 10) {
|
|
|
|
|
+ // 获取所有文件的统计信息(创建时间或修改时间)
|
|
|
|
|
+ const filesWithStats = await Promise.all(
|
|
|
|
|
+ jsonFiles.map(async (file) => {
|
|
|
|
|
+ const stats = await stat(file.path);
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...file,
|
|
|
|
|
+ birthtime: stats.birthtime || stats.mtime, // 使用创建时间,如果没有则使用修改时间
|
|
|
|
|
+ mtime: stats.mtime
|
|
|
|
|
+ };
|
|
|
|
|
+ })
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // 按时间排序(最早的在前)
|
|
|
|
|
+ filesWithStats.sort((a, b) => {
|
|
|
|
|
+ const timeA = a.birthtime.getTime();
|
|
|
|
|
+ const timeB = b.birthtime.getTime();
|
|
|
|
|
+ return timeA - timeB;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 删除最早的文件,直到只剩下 10 个
|
|
|
|
|
+ const filesToDelete = filesWithStats.slice(0, filesWithStats.length - 10);
|
|
|
|
|
+ for (const file of filesToDelete) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await rm(file.path, { force: true });
|
|
|
|
|
+ console.log(`已删除最早的聊天记录文件: ${file.name}`);
|
|
|
|
|
+ } catch (deleteError) {
|
|
|
|
|
+ console.warn(`删除文件失败: ${file.name}`, deleteError);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (cleanupError) {
|
|
|
|
|
+ // 清理失败不影响保存操作
|
|
|
|
|
+ console.warn('清理旧聊天记录文件时出错:', cleanupError);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
return { success: true, filePath };
|
|
return { success: true, filePath };
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('保存聊天记录失败:', error);
|
|
console.error('保存聊天记录失败:', error);
|