fix and add fea v1
This commit is contained in:
		
							
								
								
									
										157
									
								
								.augmentignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								.augmentignore
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,157 @@ | ||||
| # Node.js dependencies | ||||
| node_modules/ | ||||
| frontend/node_modules/ | ||||
|  | ||||
| # Build outputs and distributions | ||||
| dist/ | ||||
| build/ | ||||
| frontend/dist/ | ||||
| public/dist/ | ||||
|  | ||||
| # Electron binaries | ||||
| electron/dist/ | ||||
|  | ||||
| # Logs | ||||
| logs/ | ||||
| *.log | ||||
|  | ||||
| # Database files | ||||
| *.db | ||||
| *.sqlite | ||||
| *.sqlite3 | ||||
|  | ||||
| # Temporary files | ||||
| tmp/ | ||||
| temp/ | ||||
| .tmp/ | ||||
|  | ||||
| # Cache directories | ||||
| .cache/ | ||||
| .npm/ | ||||
| .yarn/ | ||||
|  | ||||
| # IDE and editor files | ||||
| .vscode/ | ||||
| .idea/ | ||||
| *.swp | ||||
| *.swo | ||||
| *~ | ||||
|  | ||||
| # OS generated files | ||||
| .DS_Store | ||||
| .DS_Store? | ||||
| ._* | ||||
| .Spotlight-V100 | ||||
| .Trashes | ||||
| ehthumbs.db | ||||
| Thumbs.db | ||||
|  | ||||
| # Package manager lock files (optional - uncomment if you want to exclude) | ||||
| package-lock.json | ||||
| pnpm-lock.yaml | ||||
| # yarn.lock | ||||
|  | ||||
| # Build tools and binaries | ||||
| app-builder-bin/ | ||||
| better-sqlite3/build/ | ||||
| bufferutil/build/ | ||||
| google-gax/build/ | ||||
|  | ||||
| # Large vendor libraries | ||||
| @google-cloud/ | ||||
| @electron/ | ||||
| javascript-obfuscator/dist/ | ||||
| @esbuild/ | ||||
|  | ||||
| # Development and test files | ||||
| coverage/ | ||||
| .nyc_output/ | ||||
| test-results/ | ||||
|  | ||||
| # Documentation build | ||||
| docs/build/ | ||||
| docs/dist/ | ||||
|  | ||||
| # Environment files with sensitive data | ||||
| .env.local | ||||
| .env.production | ||||
| .env.development | ||||
|  | ||||
| # Backup files | ||||
| *.bak | ||||
| *.backup | ||||
| *.old | ||||
|  | ||||
| # Binary executables and libraries | ||||
| *.exe | ||||
| *.dll | ||||
| *.so | ||||
| *.dylib | ||||
| *.bin | ||||
| *.dmg | ||||
| *.pkg | ||||
| *.msi | ||||
| *.deb | ||||
| *.rpm | ||||
| *.iobj | ||||
| *.ipdb | ||||
|  | ||||
| # Large media files (if any) | ||||
| *.mp4 | ||||
| *.avi | ||||
| *.mov | ||||
| *.wmv | ||||
| *.flv | ||||
| *.webm | ||||
| *.mkv | ||||
| *.m4v | ||||
|  | ||||
| # Archive files | ||||
| *.zip | ||||
| *.rar | ||||
| *.7z | ||||
| *.tar | ||||
| *.tar.gz | ||||
| *.tar.bz2 | ||||
|  | ||||
| # SSL certificates and keys | ||||
| ssl/ | ||||
| *.pem | ||||
| *.key | ||||
| *.crt | ||||
| *.cert | ||||
|  | ||||
| # Data directories that might contain large files | ||||
| data/ | ||||
| myUserData/ | ||||
| out/ | ||||
| run/ | ||||
| data/cache/ | ||||
| data/temp/ | ||||
| data/logs/ | ||||
|  | ||||
| # Specific large directories found in this project | ||||
| **/electron/dist/ | ||||
| **/app-builder-bin/ | ||||
| **/better-sqlite3/build/ | ||||
| **/bufferutil/build/ | ||||
| **/google-gax/build/ | ||||
| **/@google-cloud/translate/build/ | ||||
| **/@img/sharp-*/ | ||||
| **/javascript-obfuscator/dist/ | ||||
|  | ||||
| # Frontend build artifacts | ||||
| frontend/dist/ | ||||
| public/dist/ | ||||
|  | ||||
| # Any .git directories (if nested repos exist) | ||||
| **/.git/ | ||||
|  | ||||
| # Large dependency patterns | ||||
| **/@electron/ | ||||
| **/@esbuild/ | ||||
| **/element-plus/dist/ | ||||
| **/vite/dist/ | ||||
| **/rollup/dist/ | ||||
| **/protobufjs/dist/ | ||||
| **/vue/dist/ | ||||
							
								
								
									
										23
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								README.md
									
									
									
									
									
								
							| @ -1 +1,22 @@ | ||||
| Telegram 协议版多开 | ||||
| # 量子翻译应用 | ||||
|  | ||||
| Telegram 协议版多开翻译应用,支持多平台消息翻译和管理。 | ||||
|  | ||||
| ## 开发规范 | ||||
|  | ||||
| ### 多语言国际化 | ||||
| 本项目支持多语言切换,所有开发者必须遵循多语言开发规范: | ||||
|  | ||||
| 📖 **[多语言国际化开发规范](../INTERNATIONALIZATION_GUIDELINES.md)** | ||||
|  | ||||
| **重要提醒**: | ||||
| - 禁止在代码中硬编码中文或其他语言文本 | ||||
| - 所有用户可见的文本必须使用国际化机制 | ||||
| - 新功能开发前请先阅读国际化开发规范 | ||||
| - 提交代码前必须测试语言切换功能 | ||||
|  | ||||
| ### 核心要求 | ||||
| 1. **前端**:使用 Vue I18n 进行国际化 | ||||
| 2. **后端/主进程**:使用 `getI18nText()` 函数 | ||||
| 3. **webview**:实现语言更新函数和事件监听 | ||||
| 4. **测试**:确保语言切换后所有功能正常 | ||||
|  | ||||
| @ -25,7 +25,7 @@ module.exports = () => { | ||||
|       }, | ||||
|       titleBarStyle: 'hidden', | ||||
|       frame: true, | ||||
|       show: false, | ||||
|       show: true, | ||||
|       icon: path.join(getBaseDir(), 'public', 'images', 'logo-32.png'), | ||||
|     }, | ||||
|     logger: { | ||||
|  | ||||
| @ -56,6 +56,9 @@ class TranslateController { | ||||
|     async translateText(args,event) { | ||||
|         return await translateService.translateText(args,event); | ||||
|     } | ||||
|     async refreshTranslateRoutes(args,event) { | ||||
|         return await translateService.refreshTranslateRoutes(args,event); | ||||
|     } | ||||
|  | ||||
|     async createTranslateConfig(args,event) { | ||||
|         return await translateService.createTranslateConfig(args,event); | ||||
| @ -74,6 +77,14 @@ class TranslateController { | ||||
|         return await translateService.deleteNote(args,event); | ||||
|     } | ||||
|  | ||||
|     async clearTranslateCache(args, event) { | ||||
|         return await translateService.clearTranslateCache(args, event); | ||||
|     } | ||||
|  | ||||
|     async refreshSessionTranslateButtons(args, event) { | ||||
|         return await translateService.refreshSessionTranslateButtons(args, event); | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
| TranslateController.toString = () => '[class TranslateController]'; | ||||
|  | ||||
| @ -31,6 +31,22 @@ contextBridge.exposeInMainWorld("electronAPI", { | ||||
|   getLanguage: (args) => { | ||||
|     return ipcRenderer.invoke("get-language", args); | ||||
|   }, | ||||
|   // 增强监控功能的IPC方法 | ||||
|   avatarChangeNotify: (args) => { | ||||
|     return ipcRenderer.invoke("avatar-change-notify", args); | ||||
|   }, | ||||
|   profileChangeNotify: (args) => { | ||||
|     return ipcRenderer.invoke("profile-change-notify", args); | ||||
|   }, | ||||
|   statusChangeNotify: (args) => { | ||||
|     return ipcRenderer.invoke("status-change-notify", args); | ||||
|   }, | ||||
|   aboutChangeNotify: (args) => { | ||||
|     return ipcRenderer.invoke("about-change-notify", args); | ||||
|   }, | ||||
|   statusUpdateNotify: (args) => { | ||||
|     return ipcRenderer.invoke("status-update-notify", args); | ||||
|   }, | ||||
|  | ||||
|   onMessageFromMain: (callback) => ipcRenderer.on('message-from-main', (event, message) => callback(message)), | ||||
| }); | ||||
|  | ||||
| @ -237,6 +237,58 @@ const ipcMainListener = () => { | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   // 语言设置变更处理 | ||||
|   ipcMain.handle("language-change", async (event, args) => { | ||||
|     const { language } = args; | ||||
|     if (language) { | ||||
|       // 存储语言设置到应用配置 | ||||
|       app.globalLanguage = language; | ||||
|  | ||||
|       // 通知所有webview更新语言设置 | ||||
|       const { BrowserWindow } = require('electron'); | ||||
|       const windows = BrowserWindow.getAllWindows(); | ||||
|  | ||||
|       for (const window of windows) { | ||||
|         const webContents = window.webContents; | ||||
|  | ||||
|         // 通知主窗口 | ||||
|         if (webContents === getMainWindow().webContents) { | ||||
|           webContents.send("global-language-changed", { language }); | ||||
|         } | ||||
|  | ||||
|         // 通知所有子视图 | ||||
|         for (const [partitionId, view] of app.viewsMap.entries()) { | ||||
|           if (view && !view.webContents.isDestroyed()) { | ||||
|             try { | ||||
|               await view.webContents.executeJavaScript(` | ||||
|                 if (window.updateLanguage) { | ||||
|                   window.updateLanguage('${language}'); | ||||
|                 } | ||||
|                 // 触发自定义事件 | ||||
|                 window.dispatchEvent(new CustomEvent('languageChanged', { | ||||
|                   detail: { language: '${language}' } | ||||
|                 })); | ||||
|               `); | ||||
|             } catch (error) { | ||||
|               console.warn(`Failed to update language for partition ${partitionId}:`, error); | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       return { status: true, message: 'Language updated successfully' }; | ||||
|     } | ||||
|     return { status: false, message: 'Invalid language parameter' }; | ||||
|   }); | ||||
|  | ||||
|   // 获取当前语言设置 | ||||
|   ipcMain.handle("get-current-language", async (event, args) => { | ||||
|     return { | ||||
|       status: true, | ||||
|       data: { language: app.globalLanguage || 'zh' } | ||||
|     }; | ||||
|   }); | ||||
|  | ||||
|   //消息数量变更 | ||||
|   ipcMain.handle("msg-count-change", async (event, args) => { | ||||
|     logger.info("msg-count-change", args); | ||||
| @ -300,6 +352,216 @@ const ipcMainListener = () => { | ||||
|   ipcMain.handle("get-ws-base-url", async (event, args) => { | ||||
|     return { status: true, data: app.wsBaseUrl }; | ||||
|   }); | ||||
|  | ||||
|   // 增强监控功能的IPC处理器 | ||||
|  | ||||
|   // 头像变化通知 | ||||
|   ipcMain.handle("avatar-change-notify", async (event, args) => { | ||||
|     logger.info("avatar-change-notify", args); | ||||
|     const { platform, phoneNumber, oldAvatarUrl, newAvatarUrl, changeTime } = args; | ||||
|     const senderId = event.sender.id; | ||||
|     const mainWin = getMainWindow(); | ||||
|  | ||||
|     if (!mainWin || mainWin.isDestroyed()) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     // 记录头像变化到数据库 | ||||
|     try { | ||||
|       await app.sdb.insert("follow_record", { | ||||
|         userId: phoneNumber, | ||||
|         platform: platform, | ||||
|         content: JSON.stringify({ | ||||
|           type: "avatar_change", | ||||
|           oldAvatarUrl: oldAvatarUrl, | ||||
|           newAvatarUrl: newAvatarUrl, | ||||
|           changeTime: changeTime | ||||
|         }), | ||||
|         timestamp: changeTime | ||||
|       }); | ||||
|  | ||||
|       console.log(`${platform}: 头像变化已记录 - ${phoneNumber}`); | ||||
|  | ||||
|       // 通知前端 | ||||
|       if (!mainWin.isDestroyed()) { | ||||
|         mainWin.webContents.send("avatar-change-notify", { | ||||
|           platform, | ||||
|           phoneNumber, | ||||
|           oldAvatarUrl, | ||||
|           newAvatarUrl, | ||||
|           changeTime | ||||
|         }); | ||||
|       } | ||||
|     } catch (error) { | ||||
|       logger.error("记录头像变化失败", error); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   // 用户资料变化通知 | ||||
|   ipcMain.handle("profile-change-notify", async (event, args) => { | ||||
|     logger.info("profile-change-notify", args); | ||||
|     const { platform, changeType, oldValue, newValue, changeTime } = args; | ||||
|     const senderId = event.sender.id; | ||||
|     const mainWin = getMainWindow(); | ||||
|  | ||||
|     if (!mainWin || mainWin.isDestroyed()) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     try { | ||||
|       await app.sdb.insert("follow_record", { | ||||
|         userId: "current_user", // 可以根据需要调整 | ||||
|         platform: platform, | ||||
|         content: JSON.stringify({ | ||||
|           type: "profile_change", | ||||
|           changeType: changeType, | ||||
|           oldValue: oldValue, | ||||
|           newValue: newValue, | ||||
|           changeTime: changeTime | ||||
|         }), | ||||
|         timestamp: changeTime | ||||
|       }); | ||||
|  | ||||
|       console.log(`${platform}: 用户资料变化已记录 - ${changeType}`); | ||||
|  | ||||
|       // 通知前端 | ||||
|       if (!mainWin.isDestroyed()) { | ||||
|         mainWin.webContents.send("profile-change-notify", { | ||||
|           platform, | ||||
|           changeType, | ||||
|           oldValue, | ||||
|           newValue, | ||||
|           changeTime | ||||
|         }); | ||||
|       } | ||||
|     } catch (error) { | ||||
|       logger.error("记录用户资料变化失败", error); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   // 状态变化通知 | ||||
|   ipcMain.handle("status-change-notify", async (event, args) => { | ||||
|     logger.info("status-change-notify", args); | ||||
|     const { platform, changeType, oldValue, newValue, changeTime } = args; | ||||
|     const senderId = event.sender.id; | ||||
|     const mainWin = getMainWindow(); | ||||
|  | ||||
|     if (!mainWin || mainWin.isDestroyed()) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     try { | ||||
|       await app.sdb.insert("follow_record", { | ||||
|         userId: "current_user", | ||||
|         platform: platform, | ||||
|         content: JSON.stringify({ | ||||
|           type: "status_change", | ||||
|           changeType: changeType, | ||||
|           oldValue: oldValue, | ||||
|           newValue: newValue, | ||||
|           changeTime: changeTime | ||||
|         }), | ||||
|         timestamp: changeTime | ||||
|       }); | ||||
|  | ||||
|       console.log(`${platform}: 状态变化已记录 - ${changeType}`); | ||||
|  | ||||
|       // 通知前端 | ||||
|       if (!mainWin.isDestroyed()) { | ||||
|         mainWin.webContents.send("status-change-notify", { | ||||
|           platform, | ||||
|           changeType, | ||||
|           oldValue, | ||||
|           newValue, | ||||
|           changeTime | ||||
|         }); | ||||
|       } | ||||
|     } catch (error) { | ||||
|       logger.error("记录状态变化失败", error); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   // 关于信息变化通知 | ||||
|   ipcMain.handle("about-change-notify", async (event, args) => { | ||||
|     logger.info("about-change-notify", args); | ||||
|     const { platform, changeType, oldValue, newValue, changeTime } = args; | ||||
|     const senderId = event.sender.id; | ||||
|     const mainWin = getMainWindow(); | ||||
|  | ||||
|     if (!mainWin || mainWin.isDestroyed()) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     try { | ||||
|       await app.sdb.insert("follow_record", { | ||||
|         userId: "current_user", | ||||
|         platform: platform, | ||||
|         content: JSON.stringify({ | ||||
|           type: "about_change", | ||||
|           changeType: changeType, | ||||
|           oldValue: oldValue, | ||||
|           newValue: newValue, | ||||
|           changeTime: changeTime | ||||
|         }), | ||||
|         timestamp: changeTime | ||||
|       }); | ||||
|  | ||||
|       console.log(`${platform}: 关于信息变化已记录 - ${changeType}`); | ||||
|  | ||||
|       // 通知前端 | ||||
|       if (!mainWin.isDestroyed()) { | ||||
|         mainWin.webContents.send("about-change-notify", { | ||||
|           platform, | ||||
|           changeType, | ||||
|           oldValue, | ||||
|           newValue, | ||||
|           changeTime | ||||
|         }); | ||||
|       } | ||||
|     } catch (error) { | ||||
|       logger.error("记录关于信息变化失败", error); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   // 状态更新通知(动态/Stories) | ||||
|   ipcMain.handle("status-update-notify", async (event, args) => { | ||||
|     logger.info("status-update-notify", args); | ||||
|     const { platform, updateType, timestamp, details } = args; | ||||
|     const senderId = event.sender.id; | ||||
|     const mainWin = getMainWindow(); | ||||
|  | ||||
|     if (!mainWin || mainWin.isDestroyed()) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     try { | ||||
|       await app.sdb.insert("follow_record", { | ||||
|         userId: "current_user", | ||||
|         platform: platform, | ||||
|         content: JSON.stringify({ | ||||
|           type: "status_update", | ||||
|           updateType: updateType, | ||||
|           details: details, | ||||
|           timestamp: timestamp | ||||
|         }), | ||||
|         timestamp: timestamp | ||||
|       }); | ||||
|  | ||||
|       console.log(`${platform}: 状态更新已记录 - ${updateType}`); | ||||
|  | ||||
|       // 通知前端 | ||||
|       if (!mainWin.isDestroyed()) { | ||||
|         mainWin.webContents.send("status-update-notify", { | ||||
|           platform, | ||||
|           updateType, | ||||
|           timestamp, | ||||
|           details | ||||
|         }); | ||||
|       } | ||||
|     } catch (error) { | ||||
|       logger.error("记录状态更新失败", error); | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  | ||||
| @ -261,6 +261,12 @@ const initializeTableData = async () => { | ||||
|         enName: "Youdao Translate", | ||||
|         otherArgs: `{"apiUrl": "https://openapi.youdao.com/api","appId": "","apiKey": ""}`, | ||||
|         enable: 1 | ||||
|       }, { | ||||
|         name: "xiaoNiu", | ||||
|         zhName: "小牛翻译", | ||||
|         enName: "XiaoNiu Translate", | ||||
|         otherArgs: `{"apiUrl": "https://api.xiaoniu.com/translate","appId": "","apiKey": "","secretKey": ""}`, | ||||
|         enable: 1 | ||||
|       }, | ||||
|       { | ||||
|         name: "tengXun", | ||||
| @ -273,6 +279,7 @@ const initializeTableData = async () => { | ||||
|         zhName: "百度翻译", | ||||
|         enName: "Baidu Translate", | ||||
|         otherArgs: `{"apiUrl": "https://fanyi.baidu.com/v2transapi","appId": "","apiKey": "","secretKey": ""}`, | ||||
|         enable: 1 | ||||
|       }, { | ||||
|         name: "huoShan", | ||||
|         zhName: "火山翻译", | ||||
| @ -309,7 +316,7 @@ const initializeTableData = async () => { | ||||
|       name: item.name, | ||||
|       zhName: item.zhName, | ||||
|       enName: item.enName, | ||||
|       // otherArgs: item.otherArgs, | ||||
|       otherArgs: item.otherArgs, | ||||
|       enable: item.enable | ||||
|     }; | ||||
|     await app.sdb.insert("translate_route", args); | ||||
|  | ||||
| @ -6,6 +6,29 @@ const quickReply = async (args)=>{ | ||||
|  | ||||
| console.log("指纹屏蔽脚本执行"); | ||||
|  | ||||
| // 全局语言设置 | ||||
| window.currentAppLanguage = 'zh'; // 默认中文 | ||||
|  | ||||
| // 语言更新函数 | ||||
| window.updateLanguage = function(language) { | ||||
|   window.currentAppLanguage = language; | ||||
|   console.log('CustomWeb language updated to:', language); | ||||
|  | ||||
|   // 更新页面语言设置 | ||||
|   document.documentElement.lang = language; | ||||
|  | ||||
|   // 触发自定义语言更新事件 | ||||
|   window.dispatchEvent(new CustomEvent('appLanguageChanged', { | ||||
|     detail: { language } | ||||
|   })); | ||||
| }; | ||||
|  | ||||
| // 监听语言变更事件 | ||||
| window.addEventListener('languageChanged', function(event) { | ||||
|   const { language } = event.detail; | ||||
|   window.updateLanguage(language); | ||||
| }); | ||||
|  | ||||
| // 禁用webdriver | ||||
| Object.defineProperty(navigator, "webdriver", { | ||||
|   get: () => false, | ||||
|  | ||||
| @ -551,6 +551,40 @@ const monitorMainNode = ()=> { | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|     // 检查并显示已有的翻译缓存 | ||||
|     const checkAndDisplayExistingTranslation = async (text, leftDiv, rightDiv) => { | ||||
|         try { | ||||
|             const to = trcConfig.receiveTargetLanguage; | ||||
|             // 调用翻译API,但设置isFilter为true,这样只会查询缓存不会实际翻译 | ||||
|             const res = await ipc.translateText({ | ||||
|                 route: trcConfig.historyTranslateRoute || trcConfig.translateRoute, | ||||
|                 text: text, | ||||
|                 from: trcConfig.receiveSourceLanguage, | ||||
|                 to: to, | ||||
|                 refresh: 'false', | ||||
|                 mode: trcConfig.mode, | ||||
|                 isFilter: 'true' // 关键:只查询缓存,不实际翻译 | ||||
|             }); | ||||
|  | ||||
|             if (res.status && res.data) { | ||||
|                 // 找到了缓存的翻译结果,显示它 | ||||
|                 leftDiv.innerHTML = res.data; | ||||
|                 leftDiv.style.color = 'var(--color-text)'; | ||||
|                 // 缓存存在时,刷新按钮正常显示 | ||||
|                 rightDiv.style.display = ''; | ||||
|             } else { | ||||
|                 // 没有缓存,显示默认状态 | ||||
|                 leftDiv.textContent = ''; | ||||
|                 rightDiv.style.display = ''; | ||||
|             } | ||||
|         } catch (error) { | ||||
|             console.log('检查翻译缓存时出错:', error); | ||||
|             // 出错时显示默认状态 | ||||
|             leftDiv.textContent = ''; | ||||
|             rightDiv.style.display = ''; | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     const createTranslateButtonForMessage = async (msgSpan) => { | ||||
|         let text = getMsgText(msgSpan.parentNode); | ||||
|         if (!text) return; | ||||
| @ -589,7 +623,8 @@ const monitorMainNode = ()=> { | ||||
|             const from = trcConfig.receiveSourceLanguage; | ||||
|             const to = trcConfig.receiveTargetLanguage; | ||||
|             const mode = trcConfig.mode; | ||||
|             const res = await ipc.translateText({route: route, text: text,from:from, to: to,refresh:'true',mode:mode}); | ||||
|             // 修复:刷新按钮不自动清理缓存,保留翻译历史 | ||||
|             const res = await ipc.translateText({route: route, text: text,from:from, to: to,refresh:'false',mode:mode}); | ||||
|             if (res.status) { | ||||
|                 leftDiv.innerHTML = res.data; | ||||
|                 rightDiv.style.display = ''; | ||||
| @ -607,6 +642,9 @@ const monitorMainNode = ()=> { | ||||
|         // 插入到消息元素右侧 | ||||
|         msgSpan.parentNode.insertBefore(translateDiv, msgSpan.nextSibling); | ||||
|  | ||||
|         // 检查是否已有翻译缓存并显示 | ||||
|         await checkAndDisplayExistingTranslation(text, leftDiv, rightDiv); | ||||
|  | ||||
|         const receiveTranslateStatus = trcConfig.receiveTranslateStatus; | ||||
|         logger.info('发送消息 当前配置:',trcConfig) | ||||
|         if (receiveTranslateStatus === 'true') { | ||||
|  | ||||
| @ -448,6 +448,40 @@ const monitorMainNode = ()=> { | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|     // 检查并显示已有的翻译缓存 | ||||
|     const checkAndDisplayExistingTranslation = async (text, leftDiv, rightDiv) => { | ||||
|         try { | ||||
|             const to = trcConfig.receiveTargetLanguage; | ||||
|             // 调用翻译API,但设置isFilter为true,这样只会查询缓存不会实际翻译 | ||||
|             const res = await ipc.translateText({ | ||||
|                 route: trcConfig.historyTranslateRoute || trcConfig.translateRoute, | ||||
|                 text: text, | ||||
|                 from: trcConfig.receiveSourceLanguage, | ||||
|                 to: to, | ||||
|                 refresh: 'false', | ||||
|                 mode: trcConfig.mode, | ||||
|                 isFilter: 'true' // 关键:只查询缓存,不实际翻译 | ||||
|             }); | ||||
|  | ||||
|             if (res.status && res.data) { | ||||
|                 // 找到了缓存的翻译结果,显示它 | ||||
|                 leftDiv.innerHTML = res.data; | ||||
|                 leftDiv.style.color = 'green'; | ||||
|                 // 缓存存在时,刷新按钮正常显示 | ||||
|                 rightDiv.style.display = ''; | ||||
|             } else { | ||||
|                 // 没有缓存,显示默认状态 | ||||
|                 leftDiv.textContent = ''; | ||||
|                 rightDiv.style.display = ''; | ||||
|             } | ||||
|         } catch (error) { | ||||
|             console.log('检查翻译缓存时出错:', error); | ||||
|             // 出错时显示默认状态 | ||||
|             leftDiv.textContent = ''; | ||||
|             rightDiv.style.display = ''; | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     //为每条消息创建翻译按钮 | ||||
|     const createTranslateButtonForMessage = async ( msgSpan) => { | ||||
|         let text = msgSpan?.childNodes[0].textContent; | ||||
| @ -486,7 +520,8 @@ const monitorMainNode = ()=> { | ||||
|             const from = trcConfig.receiveSourceLanguage; | ||||
|             const to = trcConfig.receiveTargetLanguage; | ||||
|             const mode = trcConfig.mode; | ||||
|             const res = await ipc.translateText({route: route, text: text,from:from, to: to,refresh:'true',mode:mode}); | ||||
|             // 修复:刷新按钮不自动清理缓存,保留翻译历史 | ||||
|             const res = await ipc.translateText({route: route, text: text,from:from, to: to,refresh:'false',mode:mode}); | ||||
|             if (res.status) { | ||||
|                 leftDiv.innerHTML = res.data; | ||||
|                 rightDiv.style.display = ''; | ||||
| @ -502,6 +537,9 @@ const monitorMainNode = ()=> { | ||||
|         // 插入到消息元素右侧 | ||||
|         msgSpan.appendChild(translateDiv); | ||||
|  | ||||
|         // 检查是否已有翻译缓存并显示 | ||||
|         await checkAndDisplayExistingTranslation(text, leftDiv, rightDiv); | ||||
|  | ||||
|         const receiveTranslateStatus = trcConfig.receiveTranslateStatus; | ||||
|         if (receiveTranslateStatus === 'true') { | ||||
|             let text = msgSpan?.childNodes[0].textContent; | ||||
|  | ||||
| @ -3,6 +3,38 @@ | ||||
|  | ||||
| console.log("指纹屏蔽脚本执行"); | ||||
|  | ||||
| // 全局语言设置 | ||||
| window.currentAppLanguage = 'zh'; // 默认中文 | ||||
|  | ||||
| // 语言更新函数 | ||||
| window.updateLanguage = function(language) { | ||||
|   window.currentAppLanguage = language; | ||||
|   console.log('WhatsApp language updated to:', language); | ||||
|  | ||||
|   // 更新所有需要国际化的文本 | ||||
|   updateTranslateTexts(); | ||||
| }; | ||||
|  | ||||
| // 监听语言变更事件 | ||||
| window.addEventListener('languageChanged', function(event) { | ||||
|   const { language } = event.detail; | ||||
|   window.updateLanguage(language); | ||||
| }); | ||||
|  | ||||
| // 更新翻译相关文本 | ||||
| function updateTranslateTexts() { | ||||
|   // 更新输入框placeholder | ||||
|   const placeholderDiv = document.querySelector('footer div[aria-owns="emoji-suggestion"][contenteditable="true"] + div div'); | ||||
|   if (placeholderDiv && trcConfig && trcConfig.sendTargetLanguage) { | ||||
|     // 重新设置placeholder文本 | ||||
|     setTimeout(() => { | ||||
|       if (window.replacePlaceholder) { | ||||
|         window.replacePlaceholder(); | ||||
|       } | ||||
|     }, 100); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // 禁用webdriver | ||||
| Object.defineProperty(navigator, "webdriver", { | ||||
|   get: () => false, | ||||
| @ -45,6 +77,12 @@ const getCurrentUserId = (item) => { | ||||
|   } | ||||
|   return userNum; | ||||
| }; | ||||
| // 增强的WhatsApp监控系统 - 监控头像变化、动态等 | ||||
| let lastAvatarUrl = ""; | ||||
| let lastStatusText = ""; | ||||
| let lastProfileName = ""; | ||||
| let lastAboutText = ""; | ||||
|  | ||||
| const onlineStatusCheck = () => { | ||||
|   setInterval(async () => { | ||||
|     const element = localStorage["last-wid-md"]; | ||||
| @ -52,14 +90,39 @@ const onlineStatusCheck = () => { | ||||
|       const phoneNumber = element.split(":")[0].replace(/"/g, ""); | ||||
|       const profilePicUrl = localStorage.WACachedProfilePicEURL; | ||||
|       const avatarUrl = profilePicUrl.replace(/^"|"$/g, ""); | ||||
|       //   const msgCount = await getNewMsgCount(); | ||||
|  | ||||
|       // 增强监控:检测头像变化 | ||||
|       if (lastAvatarUrl && lastAvatarUrl !== avatarUrl) { | ||||
|         console.log("WhatsApp: 检测到头像变化", { | ||||
|           oldAvatar: lastAvatarUrl, | ||||
|           newAvatar: avatarUrl, | ||||
|           phoneNumber: phoneNumber | ||||
|         }); | ||||
|  | ||||
|         // 通知主进程头像变化 | ||||
|         ipc.avatarChangeNotify({ | ||||
|           platform: "WhatsApp", | ||||
|           phoneNumber: phoneNumber, | ||||
|           oldAvatarUrl: lastAvatarUrl, | ||||
|           newAvatarUrl: avatarUrl, | ||||
|           changeTime: new Date().toISOString() | ||||
|         }); | ||||
|       } | ||||
|       lastAvatarUrl = avatarUrl; | ||||
|  | ||||
|       // 增强监控:获取用户状态信息 | ||||
|       const userStatus = await getUserStatusInfo(); | ||||
|  | ||||
|       const args = { | ||||
|         platform: "WhatsApp", | ||||
|         onlineStatus: true, | ||||
|         avatarUrl: avatarUrl, | ||||
|         nickName: "", | ||||
|         nickName: userStatus.profileName || "", | ||||
|         phoneNumber: phoneNumber, | ||||
|         userName: phoneNumber, | ||||
|         statusText: userStatus.statusText || "", | ||||
|         aboutText: userStatus.aboutText || "", | ||||
|         lastSeen: userStatus.lastSeen || "", | ||||
|         // msgCount: msgCount, | ||||
|       }; | ||||
|       ipc.loginNotify(args); | ||||
| @ -72,6 +135,9 @@ const onlineStatusCheck = () => { | ||||
|         nickName: "", | ||||
|         phoneNumber: "", | ||||
|         userName: "", | ||||
|         statusText: "", | ||||
|         aboutText: "", | ||||
|         lastSeen: "", | ||||
|         // msgCount: 0, | ||||
|       }; | ||||
|       ipc.loginNotify(args); | ||||
| @ -79,6 +145,119 @@ const onlineStatusCheck = () => { | ||||
|     } | ||||
|   }, 60000); // 每隔60秒调用一次 | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 获取用户状态信息(增强监控功能) | ||||
|  * 监控用户的个人资料变化、状态文本、关于信息等 | ||||
|  */ | ||||
| const getUserStatusInfo = async () => { | ||||
|   try { | ||||
|     const statusInfo = { | ||||
|       profileName: "", | ||||
|       statusText: "", | ||||
|       aboutText: "", | ||||
|       lastSeen: "", | ||||
|       isOnline: false | ||||
|     }; | ||||
|  | ||||
|     // 尝试从多个可能的位置获取用户信息 | ||||
|     // 1. 从localStorage获取缓存的用户信息 | ||||
|     try { | ||||
|       const cachedUserInfo = localStorage.getItem('WACachedUserInfo'); | ||||
|       if (cachedUserInfo) { | ||||
|         const userInfo = JSON.parse(cachedUserInfo); | ||||
|         statusInfo.profileName = userInfo.name || userInfo.pushname || ""; | ||||
|         statusInfo.aboutText = userInfo.status || ""; | ||||
|       } | ||||
|     } catch (e) { | ||||
|       console.log("WhatsApp: 获取缓存用户信息失败", e); | ||||
|     } | ||||
|  | ||||
|     // 2. 从DOM获取当前显示的用户信息 | ||||
|     try { | ||||
|       // 获取用户名 | ||||
|       const nameElements = document.querySelectorAll('[data-testid="conversation-info-header-chat-title"]'); | ||||
|       if (nameElements.length > 0) { | ||||
|         statusInfo.profileName = nameElements[0].textContent.trim(); | ||||
|       } | ||||
|  | ||||
|       // 获取状态文本(在线状态、最后在线时间等) | ||||
|       const statusElements = document.querySelectorAll('[data-testid="conversation-info-header-chat-subtitle"]'); | ||||
|       if (statusElements.length > 0) { | ||||
|         const statusText = statusElements[0].textContent.trim(); | ||||
|         statusInfo.lastSeen = statusText; | ||||
|         statusInfo.isOnline = statusText.includes('在线') || statusText.includes('online'); | ||||
|       } | ||||
|     } catch (e) { | ||||
|       console.log("WhatsApp: 从DOM获取用户信息失败", e); | ||||
|     } | ||||
|  | ||||
|     // 3. 检测状态变化并记录 | ||||
|     if (lastProfileName && lastProfileName !== statusInfo.profileName) { | ||||
|       console.log("WhatsApp: 检测到用户名变化", { | ||||
|         oldName: lastProfileName, | ||||
|         newName: statusInfo.profileName | ||||
|       }); | ||||
|  | ||||
|       // 通知主进程用户名变化 | ||||
|       ipc.profileChangeNotify({ | ||||
|         platform: "WhatsApp", | ||||
|         changeType: "profileName", | ||||
|         oldValue: lastProfileName, | ||||
|         newValue: statusInfo.profileName, | ||||
|         changeTime: new Date().toISOString() | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     if (lastStatusText && lastStatusText !== statusInfo.lastSeen) { | ||||
|       console.log("WhatsApp: 检测到状态变化", { | ||||
|         oldStatus: lastStatusText, | ||||
|         newStatus: statusInfo.lastSeen | ||||
|       }); | ||||
|  | ||||
|       // 通知主进程状态变化 | ||||
|       ipc.statusChangeNotify({ | ||||
|         platform: "WhatsApp", | ||||
|         changeType: "lastSeen", | ||||
|         oldValue: lastStatusText, | ||||
|         newValue: statusInfo.lastSeen, | ||||
|         changeTime: new Date().toISOString() | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     if (lastAboutText && lastAboutText !== statusInfo.aboutText) { | ||||
|       console.log("WhatsApp: 检测到关于信息变化", { | ||||
|         oldAbout: lastAboutText, | ||||
|         newAbout: statusInfo.aboutText | ||||
|       }); | ||||
|  | ||||
|       // 通知主进程关于信息变化 | ||||
|       ipc.aboutChangeNotify({ | ||||
|         platform: "WhatsApp", | ||||
|         changeType: "aboutText", | ||||
|         oldValue: lastAboutText, | ||||
|         newValue: statusInfo.aboutText, | ||||
|         changeTime: new Date().toISOString() | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     // 更新缓存的状态 | ||||
|     lastProfileName = statusInfo.profileName; | ||||
|     lastStatusText = statusInfo.lastSeen; | ||||
|     lastAboutText = statusInfo.aboutText; | ||||
|  | ||||
|     return statusInfo; | ||||
|   } catch (error) { | ||||
|     console.error("WhatsApp: 获取用户状态信息失败", error); | ||||
|     return { | ||||
|       profileName: "", | ||||
|       statusText: "", | ||||
|       aboutText: "", | ||||
|       lastSeen: "", | ||||
|       isOnline: false | ||||
|     }; | ||||
|   } | ||||
| }; | ||||
| const getNewMsgCount = () => { | ||||
|   try { | ||||
|     let newMsgCount = 0; // 累加消息数量 | ||||
| @ -368,16 +547,26 @@ const addTranslateListener = () => { | ||||
|     if (trcConfig && trcConfig.sendTargetLanguage) { | ||||
|       try { | ||||
|         const res = await ipc.getLanguage({ code: trcConfig.sendTargetLanguage }); | ||||
|         if (res && res.status && res.data && res.data.zhName) { | ||||
|           placeholderDiv.innerHTML = `翻译成[${res.data.zhName}]后发送。`; | ||||
|         if (res && res.status && res.data) { | ||||
|           const currentLanguage = window.currentAppLanguage || 'zh'; | ||||
|           const languageName = currentLanguage === 'zh' ? res.data.zhName : res.data.enName; | ||||
|           const translateText = currentLanguage === 'zh' ? '翻译成' : 'Translate to'; | ||||
|           const afterText = currentLanguage === 'zh' ? '后发送。' : ' and send.'; | ||||
|           placeholderDiv.innerHTML = `${translateText}[${languageName || res.data.zhName}]${afterText}`; | ||||
|         } else { | ||||
|           placeholderDiv.innerHTML = '翻译成目标语言后发送。'; | ||||
|           const currentLanguage = window.currentAppLanguage || 'zh'; | ||||
|           const defaultText = currentLanguage === 'zh' ? '翻译成目标语言后发送。' : 'Translate to target language and send.'; | ||||
|           placeholderDiv.innerHTML = defaultText; | ||||
|         } | ||||
|       } catch (e) { | ||||
|         placeholderDiv.innerHTML = '翻译成目标语言后发送。'; | ||||
|         const currentLanguage = window.currentAppLanguage || 'zh'; | ||||
|         const defaultText = currentLanguage === 'zh' ? '翻译成目标语言后发送。' : 'Translate to target language and send.'; | ||||
|         placeholderDiv.innerHTML = defaultText; | ||||
|       } | ||||
|     } else { | ||||
|       placeholderDiv.innerHTML = '翻译成目标语言后发送。'; | ||||
|       const currentLanguage = window.currentAppLanguage || 'zh'; | ||||
|       const defaultText = currentLanguage === 'zh' ? '翻译成目标语言后发送。' : 'Translate to target language and send.'; | ||||
|       placeholderDiv.innerHTML = defaultText; | ||||
|     } | ||||
|   } | ||||
|   replacePlaceholder(); | ||||
| @ -407,7 +596,7 @@ const addTranslateListener = () => { | ||||
|   const handleInput = async () => { | ||||
|     // const flag = trcConfig.translatePreview; | ||||
|     const flag = false; | ||||
|     const textContent = getMsgText(editableDiv); | ||||
|     const textContent = await getMsgText(editableDiv); | ||||
|     const sendStatus = trcConfig.sendTranslateStatus; | ||||
|     if (textContent === "" || !textContent) { | ||||
|       styledTextarea.setContent("..."); | ||||
| @ -439,7 +628,7 @@ const addTranslateListener = () => { | ||||
|     const sendTranslateStatus = trcConfig.sendTranslateStatus === "true"; | ||||
|     const sendPreview = trcConfig.translatePreview === "true"; | ||||
|     const translateStatus = styledTextarea.getTranslateStatus(); | ||||
|     const textContent = getMsgText(editableDiv); | ||||
|     const textContent = await getMsgText(editableDiv); | ||||
|  | ||||
|     // 跳过空消息 | ||||
|     if (!textContent || textContent.trim() === "") return; | ||||
| @ -595,19 +784,186 @@ const sessionChange = async () => { | ||||
| }; | ||||
| const debouncedSessionChange = debounce(sessionChange, 200); | ||||
|  | ||||
| const getMsgText = (node) => { | ||||
|   // 获取所有 span 元素 | ||||
|   const spans = node.querySelectorAll("span"); | ||||
|   // 用于存储每一行的内容 | ||||
|   let content = []; | ||||
|   spans.forEach((span) => { | ||||
|     // 获取 span 的文本内容并去除前后空格 | ||||
|     const text = span.textContent.trim(); | ||||
|     // 如果内容为空,则添加空字符串,否则添加文本内容 | ||||
|     content.push(text === "" ? "" : text); | ||||
| const getMsgText = async (node) => { | ||||
|   // 首先尝试展开长消息并等待完成 | ||||
|   const expanded = await expandLongMessage(node); | ||||
|  | ||||
|   // 如果成功展开,等待额外时间确保DOM完全更新 | ||||
|   if (expanded) { | ||||
|     await new Promise(resolve => setTimeout(resolve, 500)); | ||||
|   } | ||||
|  | ||||
|   // 更精确的文本提取策略 | ||||
|   let fullText = ''; | ||||
|  | ||||
|   // 策略1:尝试获取完整的消息文本(展开后) | ||||
|   const messageTextElements = node.querySelectorAll('span[dir="ltr"], span[dir="rtl"], span[dir="auto"]'); | ||||
|   if (messageTextElements.length > 0) { | ||||
|     const textParts = []; | ||||
|     messageTextElements.forEach((element) => { | ||||
|       const text = element.textContent.trim(); | ||||
|       // 排除"查看更多"等按钮文本 | ||||
|       if (text && !isExpandButtonText(text)) { | ||||
|         textParts.push(text); | ||||
|       } | ||||
|     }); | ||||
|     fullText = textParts.join(' '); | ||||
|   } | ||||
|  | ||||
|   // 策略2:如果策略1没有获取到内容,使用原有方法 | ||||
|   if (!fullText) { | ||||
|     const spans = node.querySelectorAll("span"); | ||||
|     let content = []; | ||||
|     spans.forEach((span) => { | ||||
|       const text = span.textContent.trim(); | ||||
|       // 排除"查看更多"等按钮文本 | ||||
|       if (text && !isExpandButtonText(text)) { | ||||
|         content.push(text === "" ? "" : text); | ||||
|       } | ||||
|     }); | ||||
|     fullText = content.join("\n"); | ||||
|   } | ||||
|  | ||||
|   // 策略3:如果仍然没有内容,直接获取节点的文本内容 | ||||
|   if (!fullText) { | ||||
|     fullText = node.textContent.trim(); | ||||
|     // 移除"查看更多"等按钮文本 | ||||
|     fullText = removeExpandButtonText(fullText); | ||||
|   } | ||||
|  | ||||
|   return fullText; | ||||
| }; | ||||
|  | ||||
| // 检查是否是展开按钮的文本 | ||||
| const isExpandButtonText = (text) => { | ||||
|   const expandTexts = [ | ||||
|     'more', '查看更多', '展开', 'show more', 'see more', 'read more', | ||||
|     'ver más', 'voir plus', 'mehr anzeigen', 'mostra di più', | ||||
|     'показать больше', 'もっと見る', '더 보기', 'ver mais' | ||||
|   ]; | ||||
|   const lowerText = text.toLowerCase().trim(); | ||||
|   return expandTexts.some(expandText => lowerText.includes(expandText)); | ||||
| }; | ||||
|  | ||||
| // 移除展开按钮文本 | ||||
| const removeExpandButtonText = (text) => { | ||||
|   const expandTexts = [ | ||||
|     'more', '查看更多', '展开', 'show more', 'see more', 'read more', | ||||
|     'ver más', 'voir plus', 'mehr anzeigen', 'mostra di più', | ||||
|     'показать больше', 'もっと見る', '더 보기', 'ver mais' | ||||
|   ]; | ||||
|  | ||||
|   let cleanText = text; | ||||
|   expandTexts.forEach(expandText => { | ||||
|     const regex = new RegExp(expandText, 'gi'); | ||||
|     cleanText = cleanText.replace(regex, ''); | ||||
|   }); | ||||
|  | ||||
|   return cleanText.trim(); | ||||
| }; | ||||
|  | ||||
| // 展开长消息的函数 | ||||
| const expandLongMessage = async (node) => { | ||||
|   return new Promise((resolve) => { | ||||
|     try { | ||||
|       // 更精确的"查看更多"按钮文本匹配(支持多语言) | ||||
|       const expandTexts = [ | ||||
|         'more', '查看更多', '展开', 'show more', 'see more', 'read more', | ||||
|         'ver más', 'voir plus', 'mehr anzeigen', 'mostra di più', | ||||
|         'показать больше', 'もっと見る', '더 보기', 'ver mais', | ||||
|         'показати більше', 'показать ещё', 'περισσότερα' | ||||
|       ]; | ||||
|  | ||||
|       // 查找"查看更多"按钮的各种可能选择器 | ||||
|       const expandSelectors = [ | ||||
|         'span[role="button"]', | ||||
|         'div[role="button"]', | ||||
|         'button', | ||||
|         '[data-testid="msg-expand"]', | ||||
|         '.message-expand', | ||||
|         'span[tabindex="0"]', | ||||
|         'div[tabindex="0"]', | ||||
|         // 添加更多可能的选择器 | ||||
|         'span[aria-label*="more"]', | ||||
|         'span[aria-label*="查看更多"]', | ||||
|         'div[aria-label*="more"]', | ||||
|         'div[aria-label*="查看更多"]' | ||||
|       ]; | ||||
|  | ||||
|       let expandButton = null; | ||||
|  | ||||
|       // 策略1:通过选择器和文本内容查找 | ||||
|       for (const selector of expandSelectors) { | ||||
|         const buttons = node.querySelectorAll(selector); | ||||
|         for (const button of buttons) { | ||||
|           const buttonText = button.textContent.toLowerCase().trim(); | ||||
|           const ariaLabel = button.getAttribute('aria-label') || ''; | ||||
|  | ||||
|           // 检查按钮文本或aria-label是否包含展开相关的关键词 | ||||
|           if (expandTexts.some(text => | ||||
|             buttonText.includes(text) || ariaLabel.toLowerCase().includes(text) | ||||
|           )) { | ||||
|             expandButton = button; | ||||
|             break; | ||||
|           } | ||||
|         } | ||||
|         if (expandButton) break; | ||||
|       } | ||||
|  | ||||
|       // 策略2:在整个消息容器中查找展开按钮 | ||||
|       if (!expandButton) { | ||||
|         // 扩大搜索范围到消息的父容器 | ||||
|         const messageContainer = node.closest('[data-id]') || node.parentElement; | ||||
|         if (messageContainer) { | ||||
|           const allElements = messageContainer.querySelectorAll('span, div, a, button'); | ||||
|           for (const element of allElements) { | ||||
|             const text = element.textContent.toLowerCase().trim(); | ||||
|             const ariaLabel = element.getAttribute('aria-label') || ''; | ||||
|             const hasExpandText = expandTexts.some(expandText => | ||||
|               text.includes(expandText) || ariaLabel.toLowerCase().includes(expandText) | ||||
|             ); | ||||
|  | ||||
|             if (hasExpandText) { | ||||
|               const isClickable = element.style.cursor === 'pointer' || | ||||
|                                  element.getAttribute('role') === 'button' || | ||||
|                                  element.getAttribute('tabindex') === '0' || | ||||
|                                  element.onclick !== null || | ||||
|                                  element.tagName === 'BUTTON'; | ||||
|  | ||||
|               if (isClickable) { | ||||
|                 expandButton = element; | ||||
|                 break; | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       // 如果找到展开按钮,点击它 | ||||
|       if (expandButton) { | ||||
|         console.log('找到展开按钮,正在展开长消息...', expandButton.textContent); | ||||
|  | ||||
|         // 记录展开前的文本长度 | ||||
|         const beforeText = node.textContent.length; | ||||
|  | ||||
|         expandButton.click(); | ||||
|  | ||||
|         // 等待DOM更新,并检查文本是否真的展开了 | ||||
|         setTimeout(() => { | ||||
|           const afterText = node.textContent.length; | ||||
|           const expanded = afterText > beforeText; | ||||
|           console.log(`长消息展开${expanded ? '成功' : '失败'},文本长度:${beforeText} -> ${afterText}`); | ||||
|           resolve(expanded); | ||||
|         }, 500); // 增加等待时间确保DOM完全更新 | ||||
|       } else { | ||||
|         console.log('未找到展开按钮,消息可能已经是完整的'); | ||||
|         resolve(false); | ||||
|       } | ||||
|     } catch (error) { | ||||
|       console.warn('展开长消息时出错:', error); | ||||
|       resolve(false); | ||||
|     } | ||||
|   }); | ||||
|   // 将内容数组拼接成一个字符串,用换行符分隔 | ||||
|   return content.join("\n"); | ||||
| }; | ||||
|  | ||||
| // 当前选中的会话DOM节点 | ||||
| @ -685,6 +1041,12 @@ const monitorMainNode = () => { | ||||
|    * 为所有消息添加翻译按钮 | ||||
|    */ | ||||
|   const addTranslateButtonToAllMessages = async () => { | ||||
|     // 如果正在更新配置,跳过历史消息翻译 | ||||
|     if (window.isConfigUpdating) { | ||||
|       console.log('配置更新中,跳过历史消息翻译'); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     // 选择发送和接收的消息元素 | ||||
|     const messageElements = document.querySelectorAll( | ||||
|       "div[role='row'] .message-out, div[role='row'] .message-in" | ||||
| @ -727,11 +1089,76 @@ const monitorMainNode = () => { | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   // 检查并显示已有的翻译缓存 | ||||
|   const checkAndDisplayExistingTranslation = async (text, leftDiv, rightDiv) => { | ||||
|     try { | ||||
|       const to = trcConfig.receiveTargetLanguage; | ||||
|       // 调用翻译API,但设置isFilter为true,这样只会查询缓存不会实际翻译 | ||||
|       const res = await ipc.translateText({ | ||||
|         route: trcConfig.historyTranslateRoute || trcConfig.translateRoute, | ||||
|         text: text, | ||||
|         from: trcConfig.receiveSourceLanguage, | ||||
|         to: to, | ||||
|         refresh: 'false', | ||||
|         mode: trcConfig.mode, | ||||
|         isFilter: 'true' // 关键:只查询缓存,不实际翻译 | ||||
|       }); | ||||
|  | ||||
|       if (res.status && res.data) { | ||||
|         // 找到了缓存的翻译结果,显示它 | ||||
|         const translatedText = res.data; | ||||
|         if (translatedText.length > 300) { | ||||
|           // 创建折叠的翻译结果 | ||||
|           const shortText = translatedText.substring(0, 300) + '...'; | ||||
|           leftDiv.innerHTML = ` | ||||
|             <div class="translate-content"> | ||||
|               <div class="translate-short">${shortText}</div> | ||||
|               <div class="translate-full" style="display: none;">${translatedText}</div> | ||||
|               <span class="translate-toggle" style="color: #007bff; cursor: pointer; text-decoration: underline; font-size: 12px;">查看更多</span> | ||||
|             </div> | ||||
|           `; | ||||
|  | ||||
|           // 添加展开/折叠功能 | ||||
|           const toggleBtn = leftDiv.querySelector('.translate-toggle'); | ||||
|           const shortDiv = leftDiv.querySelector('.translate-short'); | ||||
|           const fullDiv = leftDiv.querySelector('.translate-full'); | ||||
|  | ||||
|           toggleBtn.addEventListener('click', () => { | ||||
|             if (fullDiv.style.display === 'none') { | ||||
|               shortDiv.style.display = 'none'; | ||||
|               fullDiv.style.display = 'block'; | ||||
|               toggleBtn.textContent = '收起'; | ||||
|             } else { | ||||
|               shortDiv.style.display = 'block'; | ||||
|               fullDiv.style.display = 'none'; | ||||
|               toggleBtn.textContent = '查看更多'; | ||||
|             } | ||||
|           }); | ||||
|         } else { | ||||
|           leftDiv.textContent = translatedText; | ||||
|         } | ||||
|         leftDiv.style.color = "var(--WDS-content-action-emphasized)"; | ||||
|         // 缓存存在时,刷新按钮正常显示 | ||||
|         rightDiv.style.display = ""; | ||||
|       } else { | ||||
|         // 没有缓存,显示默认状态 | ||||
|         leftDiv.textContent = ""; | ||||
|         rightDiv.style.display = ""; | ||||
|       } | ||||
|     } catch (error) { | ||||
|       console.log('检查翻译缓存时出错:', error); | ||||
|       // 出错时显示默认状态 | ||||
|       leftDiv.textContent = ""; | ||||
|       rightDiv.style.display = ""; | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   //为每条消息创建翻译按钮 | ||||
|   const createTranslateButtonForMessage = async (msgSpan, msgDateTime) => { | ||||
|     let text = getMsgText(msgSpan); | ||||
|     let text = await getMsgText(msgSpan); | ||||
|     if (!text) return; | ||||
|     const translateDiv = document.createElement("div"); | ||||
|     translateDiv.setAttribute('data-translate-btn', 'true'); // 添加标识 | ||||
|     translateDiv.style.cssText = ` | ||||
|         min-height: 20px; | ||||
|         justify-content: space-between; | ||||
| @ -739,11 +1166,21 @@ const monitorMainNode = () => { | ||||
|         border-top:1px dashed var(--message-primary); | ||||
|         color:var(--secondary); | ||||
|         display: flex;`; | ||||
|  | ||||
|     const leftDiv = document.createElement("div"); | ||||
|     leftDiv.className = 'translate-result'; // 添加类名标识 | ||||
|  | ||||
|     // 继承原消息的样式属性,但保持翻译结果的基本布局 | ||||
|     const originalStyles = window.getComputedStyle(msgSpan); | ||||
|     leftDiv.style.cssText = ` | ||||
|             min-height: 20px; | ||||
|             font-size:12px; | ||||
|             color:var(--WDS-content-action-emphasized);`; | ||||
|             font-size: ${originalStyles.fontSize}; | ||||
|             font-family: ${originalStyles.fontFamily}; | ||||
|             line-height: ${originalStyles.lineHeight}; | ||||
|             color: ${originalStyles.color}; | ||||
|             flex: 1; | ||||
|             word-wrap: break-word; | ||||
|             white-space: pre-wrap;`; | ||||
|     const rightDiv = document.createElement("div"); | ||||
|     rightDiv.style.cssText = ` | ||||
|             cursor: pointer; | ||||
| @ -754,7 +1191,7 @@ const monitorMainNode = () => { | ||||
|         `; | ||||
|     rightDiv.textContent = "🔄"; | ||||
|     rightDiv.addEventListener("click", async (e) => { | ||||
|       let text = getMsgText(msgSpan); | ||||
|       let text = await getMsgText(msgSpan); | ||||
|       text = text.replace(/\n/g, "<br>"); | ||||
|       leftDiv.style.color = "var(--WDS-content-action-emphasized)"; | ||||
|       leftDiv.textContent = `翻译中...`; | ||||
| @ -767,29 +1204,69 @@ const monitorMainNode = () => { | ||||
|       const from = trcConfig.receiveSourceLanguage; | ||||
|       const to = trcConfig.receiveTargetLanguage; | ||||
|       const mode = trcConfig.mode; | ||||
|       // 修复:刷新按钮不自动清理缓存,保留翻译历史 | ||||
|       // 双击刷新按钮可强制清理缓存重新翻译 | ||||
|       const isForceRefresh = event.detail === 2; // 检测双击 | ||||
|       const res = await ipc.translateText({ | ||||
|         route: route, | ||||
|         text: text, | ||||
|         from: from, | ||||
|         to: to, | ||||
|         refresh: "true", | ||||
|         refresh: isForceRefresh ? "true" : "false", | ||||
|         mode: mode, | ||||
|       }); | ||||
|       if (res.status) { | ||||
|         leftDiv.innerHTML = res.data; | ||||
|         // 处理翻译结果的显示,保持格式和样式 | ||||
|         const translatedText = res.data; | ||||
|  | ||||
|         // 处理长翻译结果的折叠显示 | ||||
|         if (translatedText.length > 300) { | ||||
|           // 创建折叠的翻译结果 | ||||
|           const shortText = translatedText.substring(0, 300) + '...'; | ||||
|           leftDiv.innerHTML = ` | ||||
|             <div class="translate-content"> | ||||
|               <div class="translate-short">${shortText}</div> | ||||
|               <div class="translate-full" style="display: none;">${translatedText}</div> | ||||
|               <span class="translate-toggle" style="color: #007bff; cursor: pointer; text-decoration: underline; font-size: 12px;">查看更多</span> | ||||
|             </div> | ||||
|           `; | ||||
|  | ||||
|           // 添加展开/折叠功能 | ||||
|           const toggleBtn = leftDiv.querySelector('.translate-toggle'); | ||||
|           const shortDiv = leftDiv.querySelector('.translate-short'); | ||||
|           const fullDiv = leftDiv.querySelector('.translate-full'); | ||||
|  | ||||
|           toggleBtn.addEventListener('click', () => { | ||||
|             if (fullDiv.style.display === 'none') { | ||||
|               shortDiv.style.display = 'none'; | ||||
|               fullDiv.style.display = 'block'; | ||||
|               toggleBtn.textContent = '收起'; | ||||
|             } else { | ||||
|               shortDiv.style.display = 'block'; | ||||
|               fullDiv.style.display = 'none'; | ||||
|               toggleBtn.textContent = '查看更多'; | ||||
|             } | ||||
|           }); | ||||
|         } else { | ||||
|           leftDiv.textContent = translatedText; | ||||
|         } | ||||
|         rightDiv.style.display = ""; | ||||
|       } else { | ||||
|         leftDiv.style.color = "red"; | ||||
|         leftDiv.textContent = `${res.message}`; | ||||
|         leftDiv.style.color = "var(--system-message-text, #d73502)"; | ||||
|         leftDiv.textContent = `翻译失败: ${res.message}`; | ||||
|         rightDiv.style.display = ""; | ||||
|       } | ||||
|     }); | ||||
|     // 组装结构 | ||||
|     translateDiv.appendChild(leftDiv); | ||||
|     translateDiv.appendChild(rightDiv); | ||||
|  | ||||
|     // 插入到消息元素右侧 | ||||
|     msgSpan.appendChild(translateDiv); | ||||
|  | ||||
|     // 检查是否已有翻译缓存并显示 | ||||
|     await checkAndDisplayExistingTranslation(text, leftDiv, rightDiv); | ||||
|  | ||||
|     const receiveTranslateStatus = trcConfig.receiveTranslateStatus; | ||||
|     if (receiveTranslateStatus === "true") { | ||||
|  | ||||
| @ -805,7 +1282,7 @@ const monitorMainNode = () => { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       let text = getMsgText(msgSpan); | ||||
|       let text = await getMsgText(msgSpan); | ||||
|       text = text.replace(/\n/g, "<br>"); | ||||
|       leftDiv.style.color = "var(--WDS-content-action-emphasized)"; | ||||
|       leftDiv.textContent = `翻译中...`; | ||||
| @ -823,7 +1300,38 @@ const monitorMainNode = () => { | ||||
|         mode: mode, | ||||
|       }); | ||||
|       if (res.status) { | ||||
|         leftDiv.innerHTML = res.data; | ||||
|         // 处理长翻译结果的折叠显示 | ||||
|         const translatedText = res.data; | ||||
|         if (translatedText.length > 300) { | ||||
|           // 创建折叠的翻译结果 | ||||
|           const shortText = translatedText.substring(0, 300) + '...'; | ||||
|           leftDiv.innerHTML = ` | ||||
|             <div class="translate-content"> | ||||
|               <div class="translate-short">${shortText}</div> | ||||
|               <div class="translate-full" style="display: none;">${translatedText}</div> | ||||
|               <span class="translate-toggle" style="color: #007bff; cursor: pointer; text-decoration: underline; font-size: 12px;">查看更多</span> | ||||
|             </div> | ||||
|           `; | ||||
|  | ||||
|           // 添加展开/折叠功能 | ||||
|           const toggleBtn = leftDiv.querySelector('.translate-toggle'); | ||||
|           const shortDiv = leftDiv.querySelector('.translate-short'); | ||||
|           const fullDiv = leftDiv.querySelector('.translate-full'); | ||||
|  | ||||
|           toggleBtn.addEventListener('click', () => { | ||||
|             if (fullDiv.style.display === 'none') { | ||||
|               shortDiv.style.display = 'none'; | ||||
|               fullDiv.style.display = 'block'; | ||||
|               toggleBtn.textContent = '收起'; | ||||
|             } else { | ||||
|               shortDiv.style.display = 'block'; | ||||
|               fullDiv.style.display = 'none'; | ||||
|               toggleBtn.textContent = '查看更多'; | ||||
|             } | ||||
|           }); | ||||
|         } else { | ||||
|           leftDiv.textContent = translatedText; | ||||
|         } | ||||
|         rightDiv.style.display = ""; | ||||
|       } else { | ||||
|         leftDiv.style.color = "red"; | ||||
| @ -1011,6 +1519,7 @@ const initWhatsAppObserver = () => { | ||||
|   } | ||||
|  | ||||
|   const observerCallback = (mutationsList, observer) => { | ||||
|     // 原有的未读消息监控 | ||||
|     const currentTotalUnread = calculateUnreadFromChatList(); | ||||
|     if (currentTotalUnread !== lastSentTotalUnread) { | ||||
|       lastSentTotalUnread = currentTotalUnread; | ||||
| @ -1021,6 +1530,119 @@ const initWhatsAppObserver = () => { | ||||
|       ipc.messageCountUpdate(args); | ||||
|       console.log(`WhatsApp: 未读消息总数更新并发送: ${currentTotalUnread}`); | ||||
|     } | ||||
|  | ||||
|     // 增强监控:检测动态更新和其他变化 | ||||
|     for (let mutation of mutationsList) { | ||||
|       try { | ||||
|         // 监控状态更新(Stories/动态) | ||||
|         if (mutation.type === 'childList') { | ||||
|           // 检测状态/动态相关的DOM变化 | ||||
|           const addedNodes = Array.from(mutation.addedNodes); | ||||
|           addedNodes.forEach(node => { | ||||
|             if (node.nodeType === Node.ELEMENT_NODE) { | ||||
|               // 检测状态更新指示器 | ||||
|               if (node.querySelector && ( | ||||
|                 node.querySelector('[data-testid="status-image"]') || | ||||
|                 node.querySelector('[data-testid="status-ring"]') || | ||||
|                 node.classList.contains('status-update') || | ||||
|                 node.querySelector('.status-update') | ||||
|               )) { | ||||
|                 console.log("WhatsApp: 检测到状态/动态更新", { | ||||
|                   nodeType: node.tagName, | ||||
|                   className: node.className, | ||||
|                   timestamp: new Date().toISOString() | ||||
|                 }); | ||||
|  | ||||
|                 // 通知主进程状态更新 | ||||
|                 ipc.statusUpdateNotify({ | ||||
|                   platform: "WhatsApp", | ||||
|                   updateType: "status", | ||||
|                   timestamp: new Date().toISOString(), | ||||
|                   details: { | ||||
|                     nodeType: node.tagName, | ||||
|                     className: node.className | ||||
|                   } | ||||
|                 }); | ||||
|               } | ||||
|  | ||||
|               // 检测新消息或消息状态变化 | ||||
|               if (node.querySelector && ( | ||||
|                 node.querySelector('[data-testid="msg-check"]') || | ||||
|                 node.querySelector('[data-testid="msg-dblcheck"]') || | ||||
|                 node.querySelector('[data-testid="msg-dblcheck-ack"]') | ||||
|               )) { | ||||
|                 console.log("WhatsApp: 检测到消息状态变化", { | ||||
|                   timestamp: new Date().toISOString() | ||||
|                 }); | ||||
|               } | ||||
|  | ||||
|               // 检测用户在线状态变化 | ||||
|               if (node.querySelector && ( | ||||
|                 node.querySelector('[data-testid="chat-subtitle"]') || | ||||
|                 node.textContent.includes('在线') || | ||||
|                 node.textContent.includes('online') || | ||||
|                 node.textContent.includes('最后在线') || | ||||
|                 node.textContent.includes('last seen') | ||||
|               )) { | ||||
|                 console.log("WhatsApp: 检测到用户状态变化", { | ||||
|                   content: node.textContent, | ||||
|                   timestamp: new Date().toISOString() | ||||
|                 }); | ||||
|               } | ||||
|             } | ||||
|           }); | ||||
|         } | ||||
|  | ||||
|         // 监控属性变化(如头像、状态等) | ||||
|         if (mutation.type === 'attributes') { | ||||
|           const target = mutation.target; | ||||
|  | ||||
|           // 监控头像变化 | ||||
|           if (mutation.attributeName === 'src' && | ||||
|               (target.classList.contains('avatar') || | ||||
|                target.getAttribute('data-testid') === 'avatar' || | ||||
|                target.closest('[data-testid="avatar"]'))) { | ||||
|             console.log("WhatsApp: 检测到头像DOM变化", { | ||||
|               oldSrc: mutation.oldValue, | ||||
|               newSrc: target.src, | ||||
|               timestamp: new Date().toISOString() | ||||
|             }); | ||||
|           } | ||||
|  | ||||
|           // 监控状态文本变化 | ||||
|           if (mutation.attributeName === 'title' || | ||||
|               mutation.attributeName === 'aria-label') { | ||||
|             console.log("WhatsApp: 检测到状态文本变化", { | ||||
|               attribute: mutation.attributeName, | ||||
|               oldValue: mutation.oldValue, | ||||
|               newValue: target.getAttribute(mutation.attributeName), | ||||
|               timestamp: new Date().toISOString() | ||||
|             }); | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         // 监控文本内容变化 | ||||
|         if (mutation.type === 'characterData') { | ||||
|           const target = mutation.target; | ||||
|           const parentElement = target.parentElement; | ||||
|  | ||||
|           // 检查是否是状态相关的文本变化 | ||||
|           if (parentElement && ( | ||||
|             parentElement.getAttribute('data-testid') === 'chat-subtitle' || | ||||
|             parentElement.classList.contains('status-text') || | ||||
|             parentElement.closest('[data-testid="chat-subtitle"]') | ||||
|           )) { | ||||
|             console.log("WhatsApp: 检测到状态文本内容变化", { | ||||
|               oldData: mutation.oldValue, | ||||
|               newData: target.data, | ||||
|               timestamp: new Date().toISOString() | ||||
|             }); | ||||
|           } | ||||
|         } | ||||
|       } catch (error) { | ||||
|         console.error("WhatsApp: 监控DOM变化时发生错误", error); | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const observer = new MutationObserver(observerCallback); | ||||
| @ -1048,6 +1670,8 @@ const initWhatsAppObserver = () => { | ||||
|   }, 1000) | ||||
| }; | ||||
|  | ||||
|  | ||||
|  | ||||
| // 监听whatsapp消息 | ||||
| // window.addEventListener('DOMContentLoaded',initWhatsAppObserver); | ||||
| initWhatsAppObserver(); | ||||
| @ -1056,19 +1680,26 @@ onlineStatusCheck(); | ||||
| // 替换用户名 | ||||
| function replaceUsername(contactInfo = null) { | ||||
|   // 定位Whatsapp的用户名元素 | ||||
|   const listItem = document.querySelectorAll("div#pane-side div[role='listitem'") | ||||
|   const listItem = document.querySelectorAll("div#pane-side div[role='listitem']") | ||||
|   listItem.forEach(item => { | ||||
|     const titleElement = item.querySelector('div[role="gridcell"] span') | ||||
|     const titleElement = item.querySelector('div[role="gridcell"] span[title]') | ||||
|     if (!titleElement) return; | ||||
|  | ||||
|     const title = titleElement.innerText.replace(/\s*/g, "") | ||||
|     const originalTitle = titleElement.getAttribute('title') || title | ||||
|  | ||||
|     // 遍历联系人信息,如果联系人信息中包含用户名,则替换用户名 | ||||
|     if (contactInfo) { | ||||
|       contactInfo.forEach(info => { | ||||
|         const { userId, nickName } = info | ||||
|         if (title.includes(userId) && nickName) { | ||||
|         // 更精确的匹配逻辑 | ||||
|         if ((title.includes(userId) || originalTitle.includes(userId)) && nickName && nickName.trim()) { | ||||
|           titleElement.innerText = nickName | ||||
|           titleElement.style.fontWeight = 'bold' | ||||
|           titleElement.style.color = 'red' | ||||
|           titleElement.style.color = '#1976d2' // 使用更好的颜色 | ||||
|           // 保存原始信息到data属性 | ||||
|           titleElement.setAttribute('data-original-name', originalTitle) | ||||
|           titleElement.setAttribute('data-remark', nickName) | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|  | ||||
| @ -67,6 +67,10 @@ class ContactInfoService { | ||||
|       ); | ||||
|       // 返回最新配置(可选) | ||||
|       const updatedConfig = await app.sdb.selectOne('contact_info', { id:id }); | ||||
|  | ||||
|       // 同步到服务器 | ||||
|       await this._syncContactInfoToServer(updatedConfig); | ||||
|  | ||||
|       return { | ||||
|         status: true, | ||||
|         message:'更新成功', | ||||
| @ -76,6 +80,29 @@ class ContactInfoService { | ||||
|       return {status:true,message:error.message}; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // 同步联系人信息到服务器 | ||||
|   async _syncContactInfoToServer(contactInfo) { | ||||
|     try { | ||||
|       const authInfo = app.authInfo; | ||||
|       if (!authInfo) return; // 如果没有认证信息,跳过同步 | ||||
|  | ||||
|       const url = app.baseUrl + '/sync_contact_info'; | ||||
|       const data = { | ||||
|         username: authInfo.userName, | ||||
|         key: authInfo.authKey, | ||||
|         device: authInfo.machineCode, | ||||
|         contactInfo: contactInfo | ||||
|       }; | ||||
|  | ||||
|       const { post } = require('ee-core/request'); | ||||
|       await post(url, data, { timeout: 10000 }); | ||||
|       console.log('联系人信息已同步到服务器'); | ||||
|     } catch (error) { | ||||
|       console.warn('同步联系人信息到服务器失败:', error.message); | ||||
|       // 不抛出错误,避免影响本地更新 | ||||
|     } | ||||
|   } | ||||
|   async getFollowRecord(args,event) { | ||||
|     const {userId,platform} = args; | ||||
|     if (!platform?.trim() && !userId?.trim()) { | ||||
|  | ||||
| @ -10,6 +10,71 @@ const VolcEngineSDK = require("volcengine-sdk"); | ||||
| const { getTimeStr } = require("../utils/CommonUtils"); | ||||
| const { ApiInfo, ServiceInfo, Credentials, API, Request } = VolcEngineSDK; | ||||
|  | ||||
| // 国际化文本 | ||||
| const i18nTexts = { | ||||
|   zh: { | ||||
|     parameterError: '参数传递错误', | ||||
|     configNotExist: '配置不存在或窗口被关闭', | ||||
|     querySuccess: '查询成功', | ||||
|     missingRequiredParams: '缺少必要参数,编码和名称为必填项', | ||||
|     codeExists: '当前编码已存在,请使用不同的编码', | ||||
|     writeDataFailed: '数据写入失败,请稍后重试', | ||||
|     addSuccess: '语言配置添加成功', | ||||
|     addFailed: '添加失败,系统错误', | ||||
|     idRequired: 'id不能为空', | ||||
|     deleteSuccess: '成功删除{count}条数据', | ||||
|     noDataFound: '没有查询到这条数据', | ||||
|     paramsMissing: '参数缺失,请检查 ID、名称和code。', | ||||
|     updateSuccess: '语言配置更新成功', | ||||
|     updateFailed: '没有找到对应的语言配置,更新失败。', | ||||
|     systemError: '更新失败,系统错误', | ||||
|     translateTextEmpty: '翻译文本或编码不能为空!', | ||||
|     languageNotSupported: '不支持当前翻译语言!请检查翻译编码配置!', | ||||
|     contentOrSessionIdEmpty: '内容或窗口会话ID不能为空', | ||||
|     parameterIncomplete: '参数不完整:缺少partitionId', | ||||
|     dataUpdateSuccess: '数据更新成功', | ||||
|     clearCacheConfirm: '确认清理当前会话历史翻译缓存吗?', | ||||
|     clearCacheSuccess: '清理缓存成功' | ||||
|   }, | ||||
|   en: { | ||||
|     parameterError: 'Parameter error', | ||||
|     configNotExist: 'Configuration does not exist or window is closed', | ||||
|     querySuccess: 'Query successful', | ||||
|     missingRequiredParams: 'Missing required parameters, code and name are required', | ||||
|     codeExists: 'Code already exists, please use a different code', | ||||
|     writeDataFailed: 'Data write failed, please try again later', | ||||
|     addSuccess: 'Language configuration added successfully', | ||||
|     addFailed: 'Add failed, system error', | ||||
|     idRequired: 'ID cannot be empty', | ||||
|     deleteSuccess: 'Successfully deleted {count} records', | ||||
|     noDataFound: 'No data found', | ||||
|     paramsMissing: 'Parameters missing, please check ID, name and code.', | ||||
|     updateSuccess: 'Language configuration updated successfully', | ||||
|     updateFailed: 'Language configuration not found, update failed.', | ||||
|     systemError: 'Update failed, system error', | ||||
|     translateTextEmpty: 'Translation text or code cannot be empty!', | ||||
|     languageNotSupported: 'Current translation language not supported! Please check translation code configuration!', | ||||
|     contentOrSessionIdEmpty: 'Content or window session ID cannot be empty', | ||||
|     parameterIncomplete: 'Incomplete parameters: missing partitionId', | ||||
|     dataUpdateSuccess: 'Data updated successfully', | ||||
|     clearCacheConfirm: 'Confirm clearing translation cache for current session?', | ||||
|     clearCacheSuccess: 'Cache cleared successfully' | ||||
|   } | ||||
| }; | ||||
|  | ||||
| // 获取国际化文本 | ||||
| function getI18nText(key, language = 'zh', params = {}) { | ||||
|   const lang = language || app.globalLanguage || 'zh'; | ||||
|   let text = i18nTexts[lang]?.[key] || i18nTexts.zh[key] || key; | ||||
|  | ||||
|   // 替换参数 | ||||
|   Object.keys(params).forEach(param => { | ||||
|     text = text.replace(`{${param}}`, params[param]); | ||||
|   }); | ||||
|  | ||||
|   return text; | ||||
| } | ||||
|  | ||||
| class TranslateService { | ||||
|  | ||||
|   /** | ||||
| @ -38,12 +103,29 @@ class TranslateService { | ||||
|     if (platform?.trim() && userId?.trim()) { | ||||
|       const configById = await app.sdb.selectOne('translate_config', { userId: userId }) | ||||
|       if (configById) { | ||||
|         console.log('找到用户配置:', configById); | ||||
|         // 检查并修复translateRoute为空的情况 | ||||
|         if (!configById.translateRoute || configById.translateRoute === 'null') { | ||||
|           console.log('用户配置translateRoute为空,正在修复...'); | ||||
|           await app.sdb.update('translate_config', { translateRoute: 'deepl' }, { userId: userId }); | ||||
|           configById.translateRoute = 'deepl'; | ||||
|           console.log('已修复用户配置translateRoute为deepl'); | ||||
|         } | ||||
|         return { status: true, message: '查询成功', data: configById } | ||||
|       } else { | ||||
|         const config = await app.sdb.selectOne('translate_config', { platform: platform }) | ||||
|         if (config) { | ||||
|           console.log('找到平台配置:', config); | ||||
|           // 检查并修复translateRoute为空的情况 | ||||
|           if (!config.translateRoute || config.translateRoute === 'null') { | ||||
|             console.log('平台配置translateRoute为空,正在修复...'); | ||||
|             await app.sdb.update('translate_config', { translateRoute: 'deepl' }, { platform: platform }); | ||||
|             config.translateRoute = 'deepl'; | ||||
|             console.log('已修复平台配置translateRoute为deepl'); | ||||
|           } | ||||
|           return { status: true, message: '查询成功', data: config } | ||||
|         } else { | ||||
|           console.log('未找到配置,将创建新配置'); | ||||
|           return { status: false, message: '查询失败,配置不存在' } | ||||
|         } | ||||
|       } | ||||
| @ -53,6 +135,14 @@ class TranslateService { | ||||
|     if (userId?.trim()) { | ||||
|       const configById = await app.sdb.selectOne('translate_config', { userId: userId }) | ||||
|       if (configById) { | ||||
|         console.log('找到用户配置:', configById); | ||||
|         // 检查并修复translateRoute为空的情况 | ||||
|         if (!configById.translateRoute || configById.translateRoute === 'null') { | ||||
|           console.log('用户配置translateRoute为空,正在修复...'); | ||||
|           await app.sdb.update('translate_config', { translateRoute: 'deepl' }, { userId: userId }); | ||||
|           configById.translateRoute = 'deepl'; | ||||
|           console.log('已修复用户配置translateRoute为deepl'); | ||||
|         } | ||||
|         return { status: true, message: '查询成功', data: configById } | ||||
|       } else { | ||||
|         return { status: false, message: '查询失败,配置不存在' } | ||||
| @ -97,6 +187,16 @@ class TranslateService { | ||||
|         await app.sdb.insert('translate_config', initialData); | ||||
|       } | ||||
|       const data = await app.sdb.selectOne('translate_config', { platform: platform }) | ||||
|       console.log('最终返回的配置数据:', data); | ||||
|  | ||||
|       // 检查并修复translateRoute为空的情况 | ||||
|       if (data && (!data.translateRoute || data.translateRoute === 'null')) { | ||||
|         console.log('配置存在但translateRoute为空,正在修复...'); | ||||
|         await app.sdb.update('translate_config', { translateRoute: 'deepl' }, { platform: platform }); | ||||
|         data.translateRoute = 'deepl'; | ||||
|         console.log('已修复translateRoute为deepl'); | ||||
|       } | ||||
|  | ||||
|       return { status: true, message: '查询成功2', data: data } | ||||
|     } | ||||
|     // if (partitionId) { | ||||
| @ -245,7 +345,7 @@ class TranslateService { | ||||
|     if (!status?.trim() && !partitionId?.trim()) { | ||||
|       return { | ||||
|         status: false, | ||||
|         message: '参数传递错误' | ||||
|         message: getI18nText('parameterError') | ||||
|       } | ||||
|     } | ||||
|     //获取窗口对象并查询是否有userid | ||||
| @ -259,29 +359,29 @@ class TranslateService { | ||||
|           const count = await app.sdb.update('translate_config', { friendTranslateStatus: status }, { id: configById.id }) | ||||
|           logger.info('状态修改成功:', args) | ||||
|           if (partitionId) await this._routeUpdateNotify(partitionId) | ||||
|           return { status: true, message: '数据更新成功' } | ||||
|           return { status: true, message: getI18nText('dataUpdateSuccess') } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return { status: false, message: '配置不存在或窗口被关闭' } | ||||
|     return { status: false, message: getI18nText('configNotExist') } | ||||
|   } | ||||
|  | ||||
|   async getLanguageList(args, event) { | ||||
|     const list = await app.sdb.select('language_list', {}) | ||||
|     if (list) return { status: true, message: '查询成功', data: list } | ||||
|     return { status: true, message: '查询成功', data: [] } | ||||
|     if (list) return { status: true, message: getI18nText('querySuccess'), data: list } | ||||
|     return { status: true, message: getI18nText('querySuccess'), data: [] } | ||||
|   } | ||||
|  | ||||
|   async addLanguage(args, event) { | ||||
|     const { code, zhName, enName, youDao, baidu, huoShan, xiaoNiu, google, timestamp } = args; | ||||
|     // 参数验证 | ||||
|     if (!code || !zhName) { | ||||
|       return { status: false, message: '缺少必要参数,编码和名称为必填项' }; | ||||
|       return { status: false, message: getI18nText('missingRequiredParams') }; | ||||
|     } | ||||
|     // 检查编码是否已存在 | ||||
|     const info = await app.sdb.selectOne('language_list', { code: code }); | ||||
|     if (info) { | ||||
|       return { status: false, message: '当前编码已存在,请使用不同的编码' }; | ||||
|       return { status: false, message: getI18nText('codeExists') }; | ||||
|     } | ||||
|     // 准备插入的数据 | ||||
|     const rows = { code: code, zhName: zhName }; | ||||
| @ -300,23 +400,23 @@ class TranslateService { | ||||
|       const id = await app.sdb.insert('language_list', rows); | ||||
|  | ||||
|       if (!id) { | ||||
|         return { status: false, message: '数据写入失败,请稍后重试' }; | ||||
|         return { status: false, message: getI18nText('writeDataFailed') }; | ||||
|       } | ||||
|  | ||||
|       // 查询插入后的数据 | ||||
|       const data = await app.sdb.selectOne('language_list', { id: id }); | ||||
|  | ||||
|       return { status: true, message: '语言配置添加成功', data: data }; | ||||
|       return { status: true, message: getI18nText('addSuccess'), data: data }; | ||||
|     } catch (error) { | ||||
|       return { status: false, message: `添加失败,系统错误:${error.message}` }; | ||||
|       return { status: false, message: `${getI18nText('addFailed')}:${error.message}` }; | ||||
|     } | ||||
|   } | ||||
|   async deleteLanguage(args, event) { | ||||
|     const { id } = args; | ||||
|     if (!id) return { status: false, message: 'id不能为空' } | ||||
|     if (!id) return { status: false, message: getI18nText('idRequired') } | ||||
|     const count = await app.sdb.delete('language_list', { id: id }) | ||||
|     if (count > 0) return { status: true, message: `成功删除${count}条数据` } | ||||
|     return { status: false, message: `没有查询到这条数据` } | ||||
|     if (count > 0) return { status: true, message: getI18nText('deleteSuccess', undefined, { count }) } | ||||
|     return { status: false, message: getI18nText('noDataFound') } | ||||
|   } | ||||
|   async editLanguage(args, event) { | ||||
|     const { id, zhName, enName, code, youDao, baidu, huoShan, xiaoNiu, google } = args; | ||||
| @ -412,6 +512,60 @@ class TranslateService { | ||||
|   } | ||||
|  | ||||
|  | ||||
|   // 文本分段函数 | ||||
|   _splitTextForTranslation(text, maxLength = 2500) { | ||||
|     // 如果文本长度小于限制,直接返回 | ||||
|     if (text.length <= maxLength) { | ||||
|       return [text]; | ||||
|     } | ||||
|  | ||||
|     const segments = []; | ||||
|     let currentSegment = ''; | ||||
|  | ||||
|     // 按句子分割(优先按句号、问号、感叹号分割) | ||||
|     const sentences = text.split(/([.!?。!?\n])/); | ||||
|  | ||||
|     for (let i = 0; i < sentences.length; i++) { | ||||
|       const sentence = sentences[i]; | ||||
|  | ||||
|       // 如果当前段落加上新句子超过限制 | ||||
|       if ((currentSegment + sentence).length > maxLength) { | ||||
|         if (currentSegment.trim()) { | ||||
|           segments.push(currentSegment.trim()); | ||||
|           currentSegment = sentence; | ||||
|         } else { | ||||
|           // 如果单个句子就超过限制,强制分割 | ||||
|           const words = sentence.split(' '); | ||||
|           let wordSegment = ''; | ||||
|           for (const word of words) { | ||||
|             if ((wordSegment + ' ' + word).length > maxLength) { | ||||
|               if (wordSegment.trim()) { | ||||
|                 segments.push(wordSegment.trim()); | ||||
|                 wordSegment = word; | ||||
|               } else { | ||||
|                 // 单个词就超过限制,直接添加 | ||||
|                 segments.push(word); | ||||
|               } | ||||
|             } else { | ||||
|               wordSegment += (wordSegment ? ' ' : '') + word; | ||||
|             } | ||||
|           } | ||||
|           if (wordSegment.trim()) { | ||||
|             currentSegment = wordSegment; | ||||
|           } | ||||
|         } | ||||
|       } else { | ||||
|         currentSegment += sentence; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     if (currentSegment.trim()) { | ||||
|       segments.push(currentSegment.trim()); | ||||
|     } | ||||
|  | ||||
|     return segments; | ||||
|   } | ||||
|  | ||||
|   async translateText(args, event) { | ||||
|     const { route, text, from, to, partitionId, isFilter, mode, sourceTo } = args; | ||||
|     const routeMap = { | ||||
| @ -422,13 +576,19 @@ class TranslateService { | ||||
|       xiaoNiu: this._xiaoNiuTranslateText | ||||
|     }; | ||||
|  | ||||
|     if (isFilter === 'false') { | ||||
|       //查询是否存在缓存 | ||||
|       const cache = await app.sdb.selectOne('translate_cache', { partitionId: partitionId, toCode: to, text: text }); | ||||
|       if (cache) { | ||||
|         return { status: true, message: '翻译成功', data: cache.translateText }; | ||||
|       } | ||||
|     // 查询是否存在缓存 | ||||
|     const cache = await app.sdb.selectOne('translate_cache', { partitionId: partitionId, toCode: to, text: text }); | ||||
|     if (cache) { | ||||
|       return { status: true, message: '翻译成功', data: cache.translateText }; | ||||
|     } | ||||
|  | ||||
|     // 如果isFilter为true,只查询缓存,不进行实际翻译 | ||||
|     if (isFilter === 'true') { | ||||
|       return { status: false, message: '未找到翻译缓存' }; | ||||
|     } | ||||
|  | ||||
|     // 检查是否需要分段处理(针对长文本和腾讯翻译) | ||||
|     const needSegmentation = text.length > 2500 || (route === 'tengXun' && text.length > 2000); | ||||
|     // logger.info('文本翻译:', args); | ||||
|     try { | ||||
|       if (mode === 'cloud') { | ||||
| @ -436,38 +596,111 @@ class TranslateService { | ||||
|         const url = app.baseUrl + '/translate_api'; | ||||
|         if (!authInfo) throw new Error('用户信息不存在!') | ||||
|         const { userName, machineCode, authKey } = authInfo; | ||||
|         const data = { | ||||
|           translation_service: route, | ||||
|           username: userName, | ||||
|           source_lang: from, | ||||
|           target_lang: sourceTo, | ||||
|           text: text, | ||||
|           key: authKey, | ||||
|           device: machineCode | ||||
|  | ||||
|         if (needSegmentation) { | ||||
|           // 分段翻译 | ||||
|           const segments = this._splitTextForTranslation(text, route === 'tengXun' ? 2000 : 2500); | ||||
|           const translatedSegments = []; | ||||
|  | ||||
|           for (const segment of segments) { | ||||
|             const data = { | ||||
|               translation_service: route, | ||||
|               username: userName, | ||||
|               source_lang: from, | ||||
|               target_lang: sourceTo, | ||||
|               text: segment, | ||||
|               key: authKey, | ||||
|               device: machineCode | ||||
|             } | ||||
|  | ||||
|             const result = await post(url, data); | ||||
|             if (result?.data?.translate_text) { | ||||
|               translatedSegments.push(result.data.translate_text); | ||||
|             } else { | ||||
|               throw new Error(`分段翻译失败: ${result?.message || '未知错误'}`); | ||||
|             } | ||||
|  | ||||
|             // 添加短暂延迟避免API限制 | ||||
|             await new Promise(resolve => setTimeout(resolve, 100)); | ||||
|           } | ||||
|  | ||||
|           const finalResult = translatedSegments.join(''); | ||||
|  | ||||
|           if (finalResult && isFilter === 'false') { | ||||
|             //写入翻译消息缓存表 | ||||
|             await app.sdb.insert('translate_cache', { route: route, text: text, translateText: finalResult, fromCode: from, toCode: to, partitionId: partitionId, timestamp: getTimeStr() }); | ||||
|           } | ||||
|  | ||||
|           return { status: true, message: '翻译成功', data: finalResult }; | ||||
|         } else { | ||||
|           // 正常翻译 | ||||
|           const data = { | ||||
|             translation_service: route, | ||||
|             username: userName, | ||||
|             source_lang: from, | ||||
|             target_lang: sourceTo, | ||||
|             text: text, | ||||
|             key: authKey, | ||||
|             device: machineCode | ||||
|           } | ||||
|           logger.info('translate_api data:', data) | ||||
|           const result = await post(url, data) | ||||
|           if (result?.data && isFilter === 'false') { | ||||
|             //写入翻译消息缓存表 | ||||
|             await app.sdb.insert('translate_cache', { route: route, text: text, translateText: result.data.translate_text, fromCode: from, toCode: to, partitionId: partitionId, timestamp: getTimeStr() }); | ||||
|           } | ||||
|           return { status: true, message: '翻译成功', data: result.data.translate_text }; | ||||
|         } | ||||
|         logger.info('translate_api data:', data) | ||||
|         const result = await post(url, data) | ||||
|         if (result?.data && isFilter === 'false') { | ||||
|           //写入翻译消息缓存表 | ||||
|           await app.sdb.insert('translate_cache', { route: route, text: text, translateText: result.data.translate_text, fromCode: from, toCode: to, partitionId: partitionId, timestamp: getTimeStr() }); | ||||
|         } | ||||
|         return { status: true, message: '翻译成功', data: result.data.translate_text }; | ||||
|       } | ||||
|       if (mode === 'local') { | ||||
|         if (!routeMap[route]) { | ||||
|           throw new Error(`不支持的翻译服务: ${route}`); | ||||
|         } | ||||
|         // 调用指定的翻译函数 | ||||
|         const result = await routeMap[route].call(this, text, to, route); | ||||
|         // logger.info(result); | ||||
|         if (result && isFilter === 'false') { | ||||
|           //写入翻译消息缓存表 | ||||
|           await app.sdb.insert('translate_cache', { route: route, text: text, translateText: result, fromCode: from, toCode: to, partitionId: partitionId, timestamp: getTimeStr() }); | ||||
|  | ||||
|         if (needSegmentation) { | ||||
|           // 分段翻译 | ||||
|           const segments = this._splitTextForTranslation(text, route === 'tengXun' ? 2000 : 2500); | ||||
|           const translatedSegments = []; | ||||
|  | ||||
|           for (const segment of segments) { | ||||
|             const result = await routeMap[route].call(this, segment, to, route); | ||||
|             if (result) { | ||||
|               translatedSegments.push(result); | ||||
|             } else { | ||||
|               throw new Error(`分段翻译失败`); | ||||
|             } | ||||
|  | ||||
|             // 添加短暂延迟避免API限制 | ||||
|             await new Promise(resolve => setTimeout(resolve, 100)); | ||||
|           } | ||||
|  | ||||
|           const finalResult = translatedSegments.join(''); | ||||
|  | ||||
|           if (finalResult && isFilter === 'false') { | ||||
|             //写入翻译消息缓存表 | ||||
|             await app.sdb.insert('translate_cache', { route: route, text: text, translateText: finalResult, fromCode: from, toCode: to, partitionId: partitionId, timestamp: getTimeStr() }); | ||||
|           } | ||||
|  | ||||
|           return { status: true, message: '翻译成功', data: finalResult }; | ||||
|         } else { | ||||
|           // 正常翻译 | ||||
|           const result = await routeMap[route].call(this, text, to, route); | ||||
|           // logger.info(result); | ||||
|           if (result && isFilter === 'false') { | ||||
|             //写入翻译消息缓存表 | ||||
|             await app.sdb.insert('translate_cache', { route: route, text: text, translateText: result, fromCode: from, toCode: to, partitionId: partitionId, timestamp: getTimeStr() }); | ||||
|           } | ||||
|           return { status: true, message: '翻译成功', data: result }; | ||||
|         } | ||||
|         return { status: true, message: '翻译成功', data: result }; | ||||
|       } | ||||
|     } catch (err) { | ||||
|       const msg = err.response?.data?.error || err.message; | ||||
|       let msg = err.response?.data?.error || err.message; | ||||
|  | ||||
|       // 检查是否是字符不足的错误 | ||||
|       if (msg && (msg.includes('账户可用字符不足') || msg.includes('字符不足') || msg.includes('insufficient'))) { | ||||
|         msg = '第三方接口字符已用完,请充值后继续使用'; | ||||
|       } | ||||
|  | ||||
|       logger.error('翻译请求失败:', msg) | ||||
|       return { status: false, message: msg }; | ||||
|     } | ||||
| @ -478,7 +711,14 @@ class TranslateService { | ||||
|     if (!partitionId) return; | ||||
|     const view = app.viewsMap.get(partitionId); | ||||
|     if (view && !view.webContents.isDestroyed()) { | ||||
|       await view.webContents.executeJavaScript('updateConfigInfo()') | ||||
|       // 设置标志位,避免翻译历史消息 | ||||
|       await view.webContents.executeJavaScript(` | ||||
|         window.isConfigUpdating = true; | ||||
|         updateConfigInfo(); | ||||
|         setTimeout(() => { | ||||
|           window.isConfigUpdating = false; | ||||
|         }, 1000); | ||||
|       `) | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @ -698,6 +938,106 @@ class TranslateService { | ||||
|     await app.sdb.insert('translate_config', initialData); | ||||
|   } | ||||
|  | ||||
|   async refreshTranslateRoutes(args, event) { | ||||
|     try { | ||||
|       // 获取远程翻译路由配置 | ||||
|       const url = app.baseUrl + '/translate/list_route' | ||||
|       const res = await get(url, {}, { timeout: 30000 }) | ||||
|  | ||||
|       let translationRoute = [] | ||||
|       const { code, data } = res.data | ||||
|       if (code === 2000) { | ||||
|         translationRoute = data | ||||
|       } else { | ||||
|         // 使用默认翻译线路配置 | ||||
|         translationRoute = [ | ||||
|           { | ||||
|             name: "deepl", | ||||
|             zhName: "DeepL翻译", | ||||
|             enName: "DeepL Translate", | ||||
|             otherArgs: `{"apiUrl": "https://api-free.deepl.com/v2/translate","apiKey": "","secretKey": ""}`, | ||||
|             enable: 1 | ||||
|           }, | ||||
|           { | ||||
|             name: "deepseek", | ||||
|             zhName: "DeepSeek翻译", | ||||
|             enName: "DeepSeek Translate", | ||||
|             otherArgs: `{"apiUrl": "https://api.deepseek.com/api/v1/translate","apiKey": "","secretKey": ""}`, | ||||
|             enable: 1 | ||||
|           }, | ||||
|           { | ||||
|             name: "youDao", | ||||
|             zhName: "有道翻译", | ||||
|             enName: "Youdao Translate", | ||||
|             otherArgs: `{"apiUrl": "https://openapi.youdao.com/api","appId": "","apiKey": ""}`, | ||||
|             enable: 1 | ||||
|           }, | ||||
|           { | ||||
|             name: "tengXun", | ||||
|             zhName: "腾讯翻译", | ||||
|             enName: "Tencent Translate", | ||||
|             otherArgs: `{"apiUrl": "https://fanyi.qq.com/api","appId": "","apiKey": ""}`, | ||||
|             enable: 1 | ||||
|           }, | ||||
|           { | ||||
|             name: "baidu", | ||||
|             zhName: "百度翻译", | ||||
|             enName: "Baidu Translate", | ||||
|             otherArgs: `{"apiUrl": "https://fanyi.baidu.com/v2transapi","appId": "","apiKey": "","secretKey": ""}`, | ||||
|             enable: 1 | ||||
|           }, | ||||
|           { | ||||
|             name: "huoShan", | ||||
|             zhName: "火山翻译", | ||||
|             enName: "HuoShan Translate", | ||||
|             otherArgs: `{"apiUrl": "https://api.hushan.ai/v1/translation","appId": "","apiKey": "","secretKey": ""}`, | ||||
|             enable: 1 | ||||
|           }, | ||||
|           { | ||||
|             name: "google", | ||||
|             zhName: "谷歌翻译", | ||||
|             enName: "Google Translate", | ||||
|             otherArgs: `{"apiUrl": "https://translation.googleapis.com/language/translate/v2","appId": "","apiKey": "","secretKey": ""}`, | ||||
|             enable: 1 | ||||
|           }, | ||||
|           { | ||||
|             name: "bing", | ||||
|             zhName: "必应翻译", | ||||
|             enName: "Bing Translate", | ||||
|             otherArgs: `{"apiUrl": "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0","appId": "","apiKey": "","secretKey": ""}`, | ||||
|             enable: 1 | ||||
|           }, | ||||
|           { | ||||
|             name: "chatGpt4o", | ||||
|             zhName: "ChatGpt4o翻译", | ||||
|             enName: "ChatGpt4o Translate", | ||||
|             otherArgs: `{"apiUrl": "https://api.chatgpt4o.com/translate","appId": "","apiKey": "","secretKey": ""}`, | ||||
|             enable: 1 | ||||
|           } | ||||
|         ]; | ||||
|       } | ||||
|  | ||||
|       // 清空并重新插入翻译路由 | ||||
|       await app.sdb.deleteAll("translate_route"); | ||||
|  | ||||
|       for (let item of translationRoute) { | ||||
|         const routeArgs = { | ||||
|           name: item.name, | ||||
|           zhName: item.zhName, | ||||
|           enName: item.enName, | ||||
|           otherArgs: item.otherArgs, | ||||
|           enable: item.enable | ||||
|         }; | ||||
|         await app.sdb.insert("translate_route", routeArgs); | ||||
|       } | ||||
|  | ||||
|       return { status: true, message: '翻译路由刷新成功', data: translationRoute }; | ||||
|     } catch (error) { | ||||
|       console.error('刷新翻译路由失败:', error); | ||||
|       return { status: false, message: `刷新翻译路由失败: ${error.message}` }; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   async listNote(args, event) { | ||||
|     // args: { userId } | ||||
|     try { | ||||
| @ -761,6 +1101,117 @@ class TranslateService { | ||||
|       return { status: false, message: e.message }; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // 清除翻译缓存 | ||||
|   async clearTranslateCache(args, event) { | ||||
|     const { partitionId, platform } = args; | ||||
|     try { | ||||
|       let deleteCount = 0; | ||||
|  | ||||
|       if (partitionId) { | ||||
|         // 清除指定会话的翻译缓存 | ||||
|         deleteCount = await app.sdb.delete('translate_cache', { partitionId: partitionId }); | ||||
|       } else if (platform) { | ||||
|         // 清除指定平台的翻译缓存 | ||||
|         const cacheList = await app.sdb.select('translate_cache', {}); | ||||
|         for (const cache of cacheList) { | ||||
|           if (cache.partitionId && cache.partitionId.includes(platform)) { | ||||
|             await app.sdb.delete('translate_cache', { id: cache.id }); | ||||
|             deleteCount++; | ||||
|           } | ||||
|         } | ||||
|       } else { | ||||
|         // 清除所有翻译缓存 | ||||
|         deleteCount = await app.sdb.delete('translate_cache', {}); | ||||
|       } | ||||
|  | ||||
|       return { | ||||
|         status: true, | ||||
|         message: `成功清除${deleteCount}条翻译缓存`, | ||||
|         data: { deleteCount } | ||||
|       }; | ||||
|     } catch (error) { | ||||
|       return { | ||||
|         status: false, | ||||
|         message: `清除翻译缓存失败: ${error.message}` | ||||
|       }; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // 刷新会话翻译按钮 | ||||
|   async refreshSessionTranslateButtons(args, event) { | ||||
|     const { partitionId } = args; | ||||
|     try { | ||||
|       if (!partitionId) { | ||||
|         return { | ||||
|           status: false, | ||||
|           message: '参数不完整:缺少partitionId' | ||||
|         }; | ||||
|       } | ||||
|  | ||||
|       // 获取当前分区的webContents | ||||
|       const { BrowserWindow } = require('electron'); | ||||
|       const windows = BrowserWindow.getAllWindows(); | ||||
|  | ||||
|       let refreshed = false; | ||||
|       for (const window of windows) { | ||||
|         const webContents = window.webContents; | ||||
|  | ||||
|         // 检查是否是目标分区 | ||||
|         if (webContents.session.partition === `persist:${partitionId}`) { | ||||
|           // 先弹出确认对话框,然后清理缓存并刷新对话内容区域 | ||||
|           webContents.executeJavaScript(` | ||||
|             // 获取当前语言设置 | ||||
|             const currentLanguage = window.currentAppLanguage || 'zh'; | ||||
|             const confirmText = currentLanguage === 'zh' ? | ||||
|               '确认清理当前会话历史翻译缓存吗?' : | ||||
|               'Confirm clearing translation cache for current session?'; | ||||
|             const successText = currentLanguage === 'zh' ? | ||||
|               '清理缓存成功' : | ||||
|               'Cache cleared successfully'; | ||||
|  | ||||
|             // 弹出确认对话框 | ||||
|             const confirmed = confirm(confirmText); | ||||
|             if (confirmed) { | ||||
|               // 用户确认后,显示成功提示 | ||||
|               alert(successText); | ||||
|  | ||||
|               // 直接刷新整个WhatsApp页面内容,这样对话框内容会重新加载 | ||||
|               window.location.reload(); | ||||
|             } | ||||
|             confirmed; // 返回确认结果 | ||||
|           `).then((confirmed) => { | ||||
|             if (confirmed) { | ||||
|               console.log(`用户确认清理分区 ${partitionId} 的翻译缓存,页面已刷新`); | ||||
|             } else { | ||||
|               console.log(`用户取消清理分区 ${partitionId} 的翻译缓存`); | ||||
|             } | ||||
|           }).catch(err => { | ||||
|             console.warn(`向分区 ${partitionId} 发送清理确认对话框失败:`, err); | ||||
|           }); | ||||
|  | ||||
|           refreshed = true; | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       if (!refreshed) { | ||||
|         console.warn(`未找到分区 ${partitionId} 的会话页面`); | ||||
|       } | ||||
|  | ||||
|       return { | ||||
|         status: true, | ||||
|         message: refreshed ? '清理缓存操作已发送' : '未找到对应的会话页面' | ||||
|       }; | ||||
|  | ||||
|     } catch (error) { | ||||
|       console.error('发送清理缓存操作失败:', error); | ||||
|       return { | ||||
|         status: false, | ||||
|         message: `发送清理缓存操作失败: ${error.message}` | ||||
|       }; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| TranslateService.toString = () => '[class TranslateService]'; | ||||
|  | ||||
|  | ||||
| @ -160,6 +160,10 @@ class WindowService { | ||||
|           view.webContents.setWebRTCIPHandlingPolicy("disable_non_proxied_udp"); | ||||
|  | ||||
|           await view.webContents.loadURL(webUrl, { userAgent: userAgent }); | ||||
|  | ||||
|           // 同步语言设置到新创建的webview | ||||
|           this._syncLanguageToWebview(view); | ||||
|  | ||||
|           // loadWithTimeout | ||||
|         } catch (err) { | ||||
|           logger.error("加载页面失败:", err); | ||||
| @ -607,15 +611,14 @@ class WindowService { | ||||
|       ) { | ||||
|         // 获取全局代理配置 | ||||
|         const globalConfig = await app.sdb.selectOne("global_proxy_config"); | ||||
|         if (globalConfig && globalConfig.proxyStatus === "true") { | ||||
|         if (globalConfig && globalConfig.proxyStatus === "true" && globalConfig.proxyIp && globalConfig.proxyPort) { | ||||
|           config = globalConfig; | ||||
|           logger.info(`[${partitionId}] Proxy: Use global proxy config`); | ||||
|         } else { | ||||
|           await view.webContents.session.setProxy({ mode: 'system' }); | ||||
|           logger.info(`[${partitionId}] Proxy: 使用系统代理设置`); | ||||
|           return; | ||||
|         } | ||||
|         //  else { | ||||
|         //   await view.webContents.session.setProxy({ mode: 'system' }); | ||||
|         //   logger.info(`[${partitionId}] Proxy: 使用系统代理设置`); | ||||
|         //   return; | ||||
|         // } | ||||
|       } | ||||
|  | ||||
|       let proxyRules; | ||||
| @ -856,6 +859,39 @@ class WindowService { | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * 同步语言设置到webview | ||||
|    * @param {BrowserView} view - webview实例 | ||||
|    */ | ||||
|   _syncLanguageToWebview(view) { | ||||
|     if (view && !view.webContents.isDestroyed()) { | ||||
|       const currentLanguage = app.globalLanguage || 'zh'; | ||||
|  | ||||
|       // 等待页面加载完成后再设置语言 | ||||
|       view.webContents.once('dom-ready', () => { | ||||
|         try { | ||||
|           view.webContents.executeJavaScript(` | ||||
|             // 设置全局语言变量 | ||||
|             if (window.updateLanguage) { | ||||
|               window.updateLanguage('${currentLanguage}'); | ||||
|             } else { | ||||
|               window.currentAppLanguage = '${currentLanguage}'; | ||||
|             } | ||||
|  | ||||
|             // 触发语言变更事件 | ||||
|             window.dispatchEvent(new CustomEvent('languageChanged', { | ||||
|               detail: { language: '${currentLanguage}' } | ||||
|             })); | ||||
|  | ||||
|             console.log('Language synced to webview:', '${currentLanguage}'); | ||||
|           `); | ||||
|         } catch (error) { | ||||
|           logger.warn('Failed to sync language to webview:', error); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| WindowService.toString = () => "[class WindowService]"; | ||||
|  | ||||
|  | ||||
| @ -30,15 +30,23 @@ import { useMenuStore } from '@/stores/menuStore'; | ||||
| import UpdateProgress from "@/views/components/UpdateProgress.vue"; | ||||
| import router from "@/router" | ||||
| import { ElMessage } from 'element-plus' | ||||
| import { useI18n } from 'vue-i18n' | ||||
|  | ||||
| const menuStore = useMenuStore(); | ||||
| const { locale } = useI18n(); | ||||
| const barTitle = ref(''); | ||||
| const appPlatform = ref(''); | ||||
|  | ||||
| onMounted(async () => { | ||||
|   const loadingElement = document.getElementById('loadingPage'); | ||||
|   if (loadingElement) { | ||||
|     loadingElement.remove(); | ||||
|   } | ||||
|  | ||||
|   // Initialize language from localStorage | ||||
|   const savedLanguage = localStorage.getItem('language') || 'zh'; | ||||
|   locale.value = savedLanguage; | ||||
|  | ||||
|   const res = await ipc.invoke(ipcApiRoute.getSystemInfo, {}); | ||||
|   const { name, platform, version } = res; | ||||
|   barTitle.value = name + ` v${version}`; | ||||
|  | ||||
| @ -55,6 +55,7 @@ const ipcApiRoute = { | ||||
|   getRouteList: 'controller/translate/getRouteList', | ||||
|   testRoute: 'controller/translate/testRoute', | ||||
|   translateText: 'controller/translate/translateText', | ||||
|   refreshTranslateRoutes: 'controller/translate/refreshTranslateRoutes', | ||||
|  | ||||
|   createTranslateConfig: 'controller/translate/createTranslateConfig', | ||||
|   listNote: 'controller/translate/listNote', | ||||
| @ -85,6 +86,10 @@ const ipcApiRoute = { | ||||
|   editReply: 'controller/quickreply/editReply', | ||||
|   deleteReply: 'controller/quickreply/deleteReply', | ||||
|   deleteAllReply: 'controller/quickreply/deleteAllReply', | ||||
|  | ||||
|   //翻译缓存相关 | ||||
|   clearTranslateCache: 'controller/translate/clearTranslateCache', | ||||
|   refreshSessionTranslateButtons: 'controller/translate/refreshSessionTranslateButtons', | ||||
| } | ||||
|  | ||||
| export { | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| <script setup> | ||||
| import { useI18n } from 'vue-i18n'; | ||||
| import { ref, computed } from 'vue'; | ||||
| import { ref, computed, onMounted } from 'vue'; | ||||
|  | ||||
| // 定义props | ||||
| const props = defineProps({ | ||||
| @ -13,6 +13,13 @@ const props = defineProps({ | ||||
| const { locale } = useI18n(); | ||||
| const currentLanguage = ref(locale.value); | ||||
|  | ||||
| // 在组件挂载时读取保存的语言设置 | ||||
| onMounted(() => { | ||||
|   const savedLanguage = localStorage.getItem('language') || 'zh'; | ||||
|   locale.value = savedLanguage; | ||||
|   currentLanguage.value = savedLanguage; | ||||
| }); | ||||
|  | ||||
| // 计算样式 | ||||
| const switchStyle = computed(() => ({ | ||||
|   width: `${props.size}px`, | ||||
| @ -23,9 +30,24 @@ const fontSize = computed(() => ({ | ||||
|   fontSize: `${Math.max(props.size * 0.4375, 12)}px` // 保持字体大小为容器大小的0.4375倍,最小12px | ||||
| })); | ||||
|  | ||||
| const handleLanguageChange = (lang) => { | ||||
| const handleLanguageChange = async (lang) => { | ||||
|   locale.value = lang; | ||||
|   currentLanguage.value = lang; | ||||
|  | ||||
|   // 保存到localStorage,使用统一的键名 | ||||
|   localStorage.setItem('language', lang); | ||||
|  | ||||
|   // 通知主进程语言变更 | ||||
|   if (window.electronAPI && window.electronAPI.ipcRenderer) { | ||||
|     try { | ||||
|       await window.electronAPI.ipcRenderer.invoke('language-change', { language: lang }); | ||||
|       console.log('Language change notified to main process:', lang); | ||||
|     } catch (error) { | ||||
|       console.warn('Failed to notify language change to main process:', error); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  | ||||
| }; | ||||
| </script> | ||||
|  | ||||
|  | ||||
| @ -207,6 +207,7 @@ export default { | ||||
|     appId: 'App ID', | ||||
|     settings: 'Translation Settings', | ||||
|     clearCacheTitle: 'Confirm clearing translation cache for current session?', | ||||
|     clearCacheSuccess: 'Cache cleared successfully', | ||||
|     mode: 'Translation Mode', | ||||
|     localTranslate: 'Local', | ||||
|     cloudTranslate: 'Cloud', | ||||
| @ -229,7 +230,35 @@ export default { | ||||
|     createPersonalizedTranslation: 'Create Personalized Translation', | ||||
|     chooseContactFirst: 'Please select a contact first', | ||||
|     noUserId: 'Unable to get user ID, please select a conversation or refresh the page', | ||||
|     historyTranslateRoute: 'History Message Translation Route' | ||||
|     historyTranslateRoute: 'History Message Translation Route', | ||||
|     // Error messages | ||||
|     errors: { | ||||
|       parameterError: 'Parameter error', | ||||
|       configNotExist: 'Configuration does not exist or window is closed', | ||||
|       querySuccess: 'Query successful', | ||||
|       missingRequiredParams: 'Missing required parameters, code and name are required', | ||||
|       codeExists: 'Code already exists, please use a different code', | ||||
|       writeDataFailed: 'Data write failed, please try again later', | ||||
|       addSuccess: 'Language configuration added successfully', | ||||
|       addFailed: 'Add failed, system error', | ||||
|       idRequired: 'ID cannot be empty', | ||||
|       deleteSuccess: 'Successfully deleted {count} records', | ||||
|       noDataFound: 'No data found', | ||||
|       paramsMissing: 'Parameters missing, please check ID, name and code.', | ||||
|       updateSuccess: 'Language configuration updated successfully', | ||||
|       updateFailed: 'Language configuration not found, update failed.', | ||||
|       systemError: 'Update failed, system error', | ||||
|       translateTextEmpty: 'Translation text or code cannot be empty!', | ||||
|       languageNotSupported: 'Current translation language not supported! Please check translation code configuration!', | ||||
|       contentOrSessionIdEmpty: 'Content or window session ID cannot be empty', | ||||
|       parameterIncomplete: 'Incomplete parameters: missing partitionId', | ||||
|       dataUpdateSuccess: 'Data updated successfully' | ||||
|     }, | ||||
|     // Input placeholders | ||||
|     placeholders: { | ||||
|       translateToTarget: 'Translate to target language and send.', | ||||
|       translateTo: 'Translate to [{language}] and send.' | ||||
|     } | ||||
|   }, | ||||
|   quickReply: { | ||||
|     title: 'Quick Reply', | ||||
|  | ||||
| @ -3,9 +3,15 @@ import en from './en' | ||||
| import zh from './zh' | ||||
| import km from './km' | ||||
|  | ||||
| // 从localStorage获取保存的语言设置,默认为中文 | ||||
| const getInitialLocale = () => { | ||||
|   const savedLanguage = localStorage.getItem('app-language'); | ||||
|   return savedLanguage || 'zh'; | ||||
| }; | ||||
|  | ||||
| const i18n = createI18n({ | ||||
|   legacy: false, | ||||
|   locale: 'zh', | ||||
|   locale: getInitialLocale(), | ||||
|   fallbackLocale: 'en', | ||||
|   messages: { | ||||
|     en, | ||||
| @ -14,4 +20,16 @@ const i18n = createI18n({ | ||||
|   } | ||||
| }) | ||||
|  | ||||
| // 监听全局语言变更事件 | ||||
| if (window.electronAPI && window.electronAPI.ipcRenderer) { | ||||
|   window.electronAPI.ipcRenderer.on('global-language-changed', (event, data) => { | ||||
|     const { language } = data; | ||||
|     if (language && i18n.global.locale.value !== language) { | ||||
|       i18n.global.locale.value = language; | ||||
|       localStorage.setItem('language', language); | ||||
|       console.log('Language updated from main process:', language); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
|  | ||||
| export default i18n | ||||
| @ -200,6 +200,7 @@ export default { | ||||
|     appId: '应用ID', | ||||
|     settings: '翻译设置', | ||||
|     clearCacheTitle: '确认清理当前会话历史翻译缓存吗?', | ||||
|     clearCacheSuccess: '清理缓存成功', | ||||
|     mode: '翻译模式', | ||||
|     localTranslate: '本地翻译', | ||||
|     cloudTranslate: '云端翻译', | ||||
| @ -222,7 +223,35 @@ export default { | ||||
|     createPersonalizedTranslation: '新建个性化翻译', | ||||
|     chooseContactFirst: '请先选择联系人', | ||||
|     noUserId: '无法获取用户ID,请选择会话或刷新页面', | ||||
|     historyTranslateRoute: '翻译历史消息' | ||||
|     historyTranslateRoute: '翻译历史消息', | ||||
|     // 错误消息 | ||||
|     errors: { | ||||
|       parameterError: '参数传递错误', | ||||
|       configNotExist: '配置不存在或窗口被关闭', | ||||
|       querySuccess: '查询成功', | ||||
|       missingRequiredParams: '缺少必要参数,编码和名称为必填项', | ||||
|       codeExists: '当前编码已存在,请使用不同的编码', | ||||
|       writeDataFailed: '数据写入失败,请稍后重试', | ||||
|       addSuccess: '语言配置添加成功', | ||||
|       addFailed: '添加失败,系统错误', | ||||
|       idRequired: 'id不能为空', | ||||
|       deleteSuccess: '成功删除{count}条数据', | ||||
|       noDataFound: '没有查询到这条数据', | ||||
|       paramsMissing: '参数缺失,请检查 ID、名称和code。', | ||||
|       updateSuccess: '语言配置更新成功', | ||||
|       updateFailed: '没有找到对应的语言配置,更新失败。', | ||||
|       systemError: '更新失败,系统错误', | ||||
|       translateTextEmpty: '翻译文本或编码不能为空!', | ||||
|       languageNotSupported: '不支持当前翻译语言!请检查翻译编码配置!', | ||||
|       contentOrSessionIdEmpty: '内容或窗口会话ID不能为空', | ||||
|       parameterIncomplete: '参数不完整:缺少partitionId', | ||||
|       dataUpdateSuccess: '数据更新成功' | ||||
|     }, | ||||
|     // 输入框提示 | ||||
|     placeholders: { | ||||
|       translateToTarget: '翻译成目标语言后发送。', | ||||
|       translateTo: '翻译成[{language}]后发送。' | ||||
|     } | ||||
|   }, | ||||
|   quickReply: { | ||||
|     title: '快捷回复', | ||||
|  | ||||
| @ -21,8 +21,17 @@ | ||||
|       </div> | ||||
|       <!-- 搜索框区域 --> | ||||
|       <div class="menu-search-bar" style="padding: 8px 12px 0 12px; margin-bottom: 5px;"> | ||||
|         <el-input v-model="menuSearchText" placeholder="搜索联系人/备注/用户名" clearable size="small" | ||||
|           prefix-icon="el-icon-search" /> | ||||
|         <el-input | ||||
|           v-model="menuSearchText" | ||||
|           placeholder="搜索联系人/备注/用户名" | ||||
|           clearable | ||||
|           size="small" | ||||
|           @input="handleSearchInput" | ||||
|           @clear="handleSearchClear"> | ||||
|           <template #prefix> | ||||
|             <el-icon><Search /></el-icon> | ||||
|           </template> | ||||
|         </el-input> | ||||
|       </div> | ||||
|       <!-- 开关,切换普通区和置顶区 --> | ||||
|       <div class="menu-switch" v-if="isCollapse"> | ||||
| @ -279,7 +288,8 @@ import { | ||||
|   ArrowDown, | ||||
|   ArrowUp, | ||||
|   Fold, | ||||
|   Expand | ||||
|   Expand, | ||||
|   Search | ||||
| } from '@element-plus/icons-vue' | ||||
|  | ||||
| import { ref, markRaw, watch, onMounted, onUnmounted, computed, nextTick } from 'vue' | ||||
| @ -342,8 +352,20 @@ const clearTimer = () => { | ||||
|  | ||||
| const menuSearchText = ref('') | ||||
|  | ||||
| // 搜索处理函数 | ||||
| const handleSearchInput = (value) => { | ||||
|   console.log('搜索输入:', value) | ||||
|   // 由于使用了computed,这里不需要额外处理,filteredChildren会自动响应 | ||||
| } | ||||
|  | ||||
| const handleSearchClear = () => { | ||||
|   console.log('清空搜索') | ||||
|   menuSearchText.value = '' | ||||
| } | ||||
|  | ||||
| const filteredChildren = computed(() => (children, menuId) => { | ||||
|   let nMenus = children | ||||
|   let nMenus = children || [] | ||||
|  | ||||
|   if (currentArea.value === 'top') { | ||||
|     nMenus = nMenus.filter(child => child.isTop === true || child.isTop === 'true') | ||||
|   } else { | ||||
|  | ||||
| @ -179,6 +179,16 @@ const handleSave = async () => { | ||||
|   }) | ||||
| } | ||||
|  | ||||
| // 监听会话切换,重新获取配置信息 | ||||
| watch( | ||||
|   () => menuStore.currentPartitionId, | ||||
|   async (newValue, oldValue) => { | ||||
|     if (newValue && newValue !== oldValue) { | ||||
|       await getConfigInfo() | ||||
|     } | ||||
|   } | ||||
| ); | ||||
|  | ||||
| onMounted(() => { | ||||
|   getConfigInfo() | ||||
| }) | ||||
|  | ||||
| @ -43,12 +43,15 @@ | ||||
|           <div class="content-left"> | ||||
|             <el-text>{{ t('translate.route') }}</el-text> | ||||
|           </div> | ||||
|           <div class="content-right"> | ||||
|           <div class="content-right" style="display: flex; gap: 8px;"> | ||||
|             <el-select v-model="configInfo.translateRoute" :placeholder="t('translate.selectRoute')" size="default" | ||||
|               style="width: 100%" @change="handleTranslateRouteChange"> | ||||
|               style="flex: 1" @change="handleTranslateRouteChange"> | ||||
|               <el-option v-for="item in menuStore.translationRoute" :key="item.name" :label="item[currentLanguageName]" | ||||
|                 :value="item.name" /> | ||||
|             </el-select> | ||||
|             <el-button size="default" type="primary" :icon="RefreshIcon" @click="refreshTranslateRoutes" | ||||
|               :loading="refreshLoading" title="刷新翻译路由"> | ||||
|             </el-button> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="content-container-radio"> | ||||
| @ -207,7 +210,7 @@ import { ipcApiRoute } from "@/api"; | ||||
| import CleanIcon from "@/components/icons/CleanIcon.vue"; | ||||
| import { useMenuStore } from '@/stores/menuStore'; | ||||
| import { ElMessage } from "element-plus"; | ||||
| import { QuestionFilled } from "@element-plus/icons-vue"; | ||||
| import { QuestionFilled, Refresh } from "@element-plus/icons-vue"; | ||||
| import { useI18n } from 'vue-i18n'; | ||||
| import { config, nextTick } from "process"; | ||||
|  | ||||
| @ -218,6 +221,8 @@ const showLocalTranslate = ref(false); | ||||
| const activeTab = ref('current'); | ||||
| // const currentUserId = ref(null); // 移除本地currentUserId | ||||
| const loading = ref(false); // 新增 loading 状态 | ||||
| const refreshLoading = ref(false); // 刷新按钮loading状态 | ||||
| const RefreshIcon = Refresh; // 刷新图标 | ||||
|  | ||||
| // 修改计算属性名称使其更通用 | ||||
| const currentLanguageName = computed(() => { | ||||
| @ -365,8 +370,40 @@ onMounted(async () => { | ||||
| }) | ||||
|  | ||||
| // 清理缓存 | ||||
| const cleanMsgCache = () => { | ||||
|   // 实现清理逻辑 | ||||
| const cleanMsgCache = async () => { | ||||
|   try { | ||||
|     const res = await ipc.invoke(ipcApiRoute.clearTranslateCache, { | ||||
|       partitionId: menuStore.currentPartitionId, | ||||
|       platform: menuStore.currentMenu | ||||
|     }); | ||||
|  | ||||
|     if (res.status) { | ||||
|       ElMessage.success(res.message || '清理缓存成功'); | ||||
|  | ||||
|       // 刷新当前会话页面 | ||||
|       await refreshCurrentSession(); | ||||
|     } else { | ||||
|       ElMessage.error(res.message || '清理缓存失败'); | ||||
|     } | ||||
|   } catch (error) { | ||||
|     console.error('清理缓存失败:', error); | ||||
|     ElMessage.error('清理缓存失败: ' + (error.message || '未知错误')); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // 刷新当前会话页面 | ||||
| const refreshCurrentSession = async () => { | ||||
|   try { | ||||
|     if (menuStore.currentPartitionId) { | ||||
|       // 通知当前会话页面刷新翻译按钮 | ||||
|       await ipc.invoke(ipcApiRoute.refreshSessionTranslateButtons, { | ||||
|         partitionId: menuStore.currentPartitionId | ||||
|       }); | ||||
|       console.log('当前会话页面已刷新'); | ||||
|     } | ||||
|   } catch (error) { | ||||
|     console.warn('刷新当前会话页面失败:', error); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // 语言列表 | ||||
| @ -483,6 +520,30 @@ const handleCreatePersonalConfig = async () => { | ||||
|   } | ||||
| } | ||||
|  | ||||
| // 刷新翻译路由 | ||||
| const refreshTranslateRoutes = async () => { | ||||
|   refreshLoading.value = true; | ||||
|   try { | ||||
|     const res = await ipc.invoke(ipcApiRoute.refreshTranslateRoutes, {}); | ||||
|     if (res.status) { | ||||
|       // 重新获取翻译路由列表 | ||||
|       const routeRes = await ipc.invoke(ipcApiRoute.getRouteList, {}); | ||||
|       if (routeRes.status && routeRes.data) { | ||||
|         const finalRoutes = routeRes.data.filter(item => item.enable == 1); | ||||
|         menuStore.setTranslationRoute(finalRoutes); | ||||
|         ElMessage.success('翻译路由刷新成功'); | ||||
|       } | ||||
|     } else { | ||||
|       ElMessage.error(res.message || '刷新失败'); | ||||
|     } | ||||
|   } catch (error) { | ||||
|     console.error('刷新翻译路由失败:', error); | ||||
|     ElMessage.error('刷新翻译路由失败: ' + (error.message || '未知错误')); | ||||
|   } finally { | ||||
|     refreshLoading.value = false; | ||||
|   } | ||||
| } | ||||
|  | ||||
| // onUnmounted(() => { | ||||
| //   ipc.removeAllListeners('translate-config-update') | ||||
| // }) | ||||
|  | ||||
| @ -257,6 +257,16 @@ const getUserInfo = async () => { | ||||
|     addWatchers() | ||||
|   } | ||||
| } | ||||
| // 监听会话切换,重新获取用户信息 | ||||
| watch( | ||||
|   () => menuStore.currentPartitionId, | ||||
|   async (newValue, oldValue) => { | ||||
|     if (newValue && newValue !== oldValue) { | ||||
|       await getUserInfo() | ||||
|     } | ||||
|   } | ||||
| ); | ||||
|  | ||||
| onMounted(async () => { | ||||
|   await getUserInfo() | ||||
| }) | ||||
|  | ||||
							
								
								
									
										58
									
								
								public/dist/index.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										58
									
								
								public/dist/index.html
									
									
									
									
										vendored
									
									
								
							| @ -1,11 +1,11 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="zh-CN" class="dark"> | ||||
|   <head> | ||||
|     <meta charset="utf-8"> | ||||
|     <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0" /> | ||||
|     <title></title> | ||||
|     <!-- 优化:vue渲染未完成之前,先加一个css动画 --> | ||||
| <!DOCTYPE html> | ||||
| <html lang="zh-CN" class="dark"> | ||||
|   <head> | ||||
|     <meta charset="utf-8"> | ||||
|     <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0" /> | ||||
|     <title></title> | ||||
|     <!-- 优化:vue渲染未完成之前,先加一个css动画 --> | ||||
|     <style> | ||||
|       #loadingPage { | ||||
|         background-color: #dedede; | ||||
| @ -84,24 +84,24 @@ | ||||
|         } | ||||
|       } | ||||
|  | ||||
|     </style> | ||||
|     <script type="module" crossorigin src="./assets/index-jqD4rXg9.js"></script> | ||||
|     <link rel="stylesheet" crossorigin href="./assets/index-C-v6BnYe.css"> | ||||
|   </head> | ||||
|   <body style="padding: 0; margin: 0;"> | ||||
|     <div id="loadingPage"> | ||||
|       <div class='base'> | ||||
|         <div class='cube'></div> | ||||
|         <div class='cube'></div> | ||||
|         <div class='cube'></div> | ||||
|         <div class='cube'></div> | ||||
|         <div class='cube'></div> | ||||
|         <div class='cube'></div> | ||||
|         <div class='cube'></div> | ||||
|         <div class='cube'></div> | ||||
|         <div class='cube'></div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div id="app"></div> | ||||
|   </body> | ||||
| </html> | ||||
|     </style> | ||||
|     <script type="module" crossorigin src="./assets/index-UhLAJDIM.js"></script> | ||||
|     <link rel="stylesheet" crossorigin href="./assets/index-LGRos5i2.css"> | ||||
|   </head> | ||||
|   <body style="padding: 0; margin: 0;"> | ||||
|     <div id="loadingPage"> | ||||
|       <div class='base'> | ||||
|         <div class='cube'></div> | ||||
|         <div class='cube'></div> | ||||
|         <div class='cube'></div> | ||||
|         <div class='cube'></div> | ||||
|         <div class='cube'></div> | ||||
|         <div class='cube'></div> | ||||
|         <div class='cube'></div> | ||||
|         <div class='cube'></div> | ||||
|         <div class='cube'></div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div id="app"></div> | ||||
|  | ||||
|   </body> | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 unknown
					unknown