From 9ce50205ef1a27a258a30399650c6cd145ec2ba2 Mon Sep 17 00:00:00 2001 From: puzhen <1303385763@qq.com> Date: Thu, 9 Oct 2025 17:36:23 +0100 Subject: [PATCH 1/5] update --- electron/main/index.ts | 103 ++++++++++++++++++++++++++++++++++----- electron/main/webview.ts | 72 ++++++++++++++++++++++++--- 2 files changed, 156 insertions(+), 19 deletions(-) diff --git a/electron/main/index.ts b/electron/main/index.ts index 733b8b67d..057ac8860 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -51,6 +51,13 @@ findAvailablePort(browser_port).then(port => { app.commandLine.appendSwitch('remote-debugging-port', port + ''); }); +// Memory optimization settings +app.commandLine.appendSwitch('js-flags', '--max-old-space-size=4096'); +app.commandLine.appendSwitch('force-gpu-mem-available-mb', '512'); +app.commandLine.appendSwitch('max_old_space_size', '4096'); +app.commandLine.appendSwitch('enable-features', 'MemoryPressureReduction'); +app.commandLine.appendSwitch('renderer-process-limit', '8'); + // ==================== app config ==================== process.env.APP_ROOT = MAIN_DIST; process.env.VITE_PUBLIC = VITE_PUBLIC; @@ -922,8 +929,8 @@ async function createWindow() { fileReader = new FileReader(win); webViewManager = new WebViewManager(win); - // create multiple webviews - for (let i = 1; i <= 8; i++) { + // create initial webviews (reduced from 8 to 3) + for (let i = 1; i <= 3; i++) { webViewManager.createWebview(i === 1 ? undefined : i.toString()); } @@ -1197,14 +1204,24 @@ const cleanupPythonProcess = async () => { const pid = python_process.pid; log.info('Cleaning up Python process', { pid }); + // Remove all listeners to prevent memory leaks + python_process.removeAllListeners(); + await new Promise((resolve) => { - kill(pid, 'SIGINT', (err) => { + kill(pid, 'SIGTERM', (err) => { if (err) { - log.error('Failed to clean up process tree:', err); + log.error('Failed to clean up process tree with SIGTERM:', err); + // Try SIGKILL as fallback + kill(pid, 'SIGKILL', (killErr) => { + if (killErr) { + log.error('Failed to force kill process tree:', killErr); + } + resolve(); + }); } else { log.info('Successfully cleaned up Python process tree'); + resolve(); } - resolve(); }); }); } @@ -1224,17 +1241,38 @@ const cleanupPythonProcess = async () => { } } + // Clean up any temporary files in userData + try { + const tempFiles = ['backend.lock', 'uv_installing.lock']; + for (const file of tempFiles) { + const filePath = path.join(userData, file); + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + } + } + } catch (error) { + log.error('Error cleaning up temp files:', error); + } + python_process = null; } catch (error) { log.error('Error occurred while cleaning up process:', error); } }; -// brefore close +// before close const handleBeforeClose = () => { + let isQuitting = false; + + app.on('before-quit', () => { + isQuitting = true; + }); + win?.on("close", (event) => { - event.preventDefault(); - win?.webContents.send("before-close"); + if (!isQuitting) { + event.preventDefault(); + win?.webContents.send("before-close"); + } }) } @@ -1289,8 +1327,15 @@ app.whenReady().then(() => { // ==================== window close event ==================== app.on('window-all-closed', () => { log.info('window-all-closed'); - webViewManager = null; + + // Clean up WebView manager + if (webViewManager) { + webViewManager.destroy(); + webViewManager = null; + } + win = null; + if (process.platform !== 'darwin') { app.quit(); } @@ -1310,12 +1355,44 @@ app.on('activate', () => { }); // ==================== app exit event ==================== -app.on('before-quit', () => { +app.on('before-quit', async (event) => { log.info('before-quit'); log.info('quit python_process.pid: ' + python_process?.pid); - if (win) { - win.destroy(); + + // Prevent default quit to ensure cleanup completes + event.preventDefault(); + + try { + // Clean up resources + if (webViewManager) { + webViewManager.destroy(); + webViewManager = null; + } + + if (win && !win.isDestroyed()) { + win.destroy(); + win = null; + } + + // Wait for Python process cleanup + await cleanupPythonProcess(); + + // Clean up file reader if exists + if (fileReader) { + fileReader = null; + } + + // Clear any remaining timeouts/intervals + if (global.gc) { + global.gc(); + } + + log.info('All cleanup completed, exiting...'); + } catch (error) { + log.error('Error during cleanup:', error); + } finally { + // Force quit after cleanup + app.exit(0); } - cleanupPythonProcess(); }); diff --git a/electron/main/webview.ts b/electron/main/webview.ts index 123d464d2..8036dda6d 100644 --- a/electron/main/webview.ts +++ b/electron/main/webview.ts @@ -20,6 +20,9 @@ export class WebViewManager { private webViews = new Map() private win: BrowserWindow | null = null private size: Size = { x: 0, y: 0, width: 0, height: 0 } + private maxInactiveWebviews = 5 + private lastCleanupTime = Date.now() + constructor(window: BrowserWindow) { this.win = window } @@ -63,6 +66,12 @@ export class WebViewManager { webPreferences: { nodeIntegration: false, contextIsolation: true, + backgroundThrottling: true, + offscreen: false, + sandbox: true, + disableBlinkFeatures: 'Accelerated2dCanvas', + enableBlinkFeatures: 'IdleDetection', + autoplayPolicy: 'document-user-activation-required', }, }) view.webContents.on('did-finish-load', () => { @@ -119,13 +128,22 @@ export class WebViewManager { webViewInfo.view.setBounds({ x: -1919, y: -1079, width: 1920, height: 1080 }) const activeSize = this.getActiveWebview().length const allSize = Array.from(this.webViews.values()).length - if (allSize - activeSize <= 3) { + const inactiveSize = allSize - activeSize + + // Clean up inactive webviews if too many + if (inactiveSize > this.maxInactiveWebviews && Date.now() - this.lastCleanupTime > 30000) { + this.cleanupInactiveWebviews() + this.lastCleanupTime = Date.now() + } + + // Create new webviews if needed + if (inactiveSize <= 2) { const existingKeys = Array.from(this.webViews.keys()).map(Number).filter(n => !isNaN(n)) const maxId = existingKeys.length > 0 ? Math.max(...existingKeys) : 0 const startId = maxId + 1 - // Create webviews sequentially to avoid race conditions - for (let i = 0; i < 3; i++) { + // Create only 2 new webviews to reduce memory usage + for (let i = 0; i < 2; i++) { const nextId = (startId + i).toString() this.createWebview(nextId, 'about:blank?use=0') } @@ -190,6 +208,10 @@ export class WebViewManager { let newId = Number(id) webViewInfo.view.setBounds({ x: -9999 + newId * 100, y: -9999 + newId * 100, width: 100, height: 100 }) webViewInfo.isShow = false + + if (webViewInfo.view.webContents && !webViewInfo.view.webContents.isDestroyed()) { + webViewInfo.view.webContents.setBackgroundThrottling(true) + } return { success: true } } @@ -198,19 +220,36 @@ export class WebViewManager { let newId = Number(webview.id) webview.view.setBounds({ x: -9999 + newId * 100, y: -9999 + newId * 100, width: 100, height: 100 }) webview.isShow = false + + if (webview.view.webContents && !webview.view.webContents.isDestroyed()) { + webview.view.webContents.setBackgroundThrottling(true) + } }) } - public showWebview(id: string) { - const webViewInfo = this.webViews.get(id) + public async showWebview(id: string) { + let webViewInfo = this.webViews.get(id) + + // If webview doesn't exist, create it if (!webViewInfo) { - return { success: false, error: `Webview with id ${id} not found` } + console.log(`Webview ${id} not found, creating new one`) + const createResult = await this.createWebview(id, 'about:blank?use=0') + if (!createResult.success) { + return { success: false, error: `Failed to create webview ${id}` } + } + webViewInfo = this.webViews.get(id)! } + const currentUrl = webViewInfo.view.webContents.getURL(); this.win?.webContents.send("url-updated", currentUrl); webViewInfo.isShow = true this.changeViewSize(id, this.size) console.log("showWebview", id, this.size) + + if (webViewInfo.view.webContents && !webViewInfo.view.webContents.isDestroyed()) { + webViewInfo.view.webContents.setBackgroundThrottling(false) + } + if (this.win && !this.win.isDestroyed()) { this.win.webContents.send('webview-show', id) } @@ -228,6 +267,14 @@ export class WebViewManager { return { success: false, error: `Webview with id ${id} not found` } } + if (!webViewInfo.view.webContents.isDestroyed()) { + webViewInfo.view.webContents.removeAllListeners() + webViewInfo.view.webContents.session.clearCache() + webViewInfo.view.webContents.session.clearStorageData({ + storages: ['cookies', 'localstorage', 'websql', 'indexdb', 'serviceworkers', 'cachestorage'] + }) + } + // remove webview from parent container if (this.win?.contentView) { this.win.contentView.removeChildView(webViewInfo.view) @@ -254,5 +301,18 @@ export class WebViewManager { }) this.webViews.clear() } + + private cleanupInactiveWebviews() { + const inactiveWebviews = Array.from(this.webViews.entries()) + .filter(([id, info]) => !info.isActive && !info.isShow && info.currentUrl === 'about:blank?use=0') + .sort((a, b) => parseInt(a[0]) - parseInt(b[0])) + + const toRemove = inactiveWebviews.slice(this.maxInactiveWebviews) + + toRemove.forEach(([id, _]) => { + console.log(`Cleaning up inactive webview: ${id}`) + this.destroyWebview(id) + }) + } } From 5d3bec027f0422cc506a558b22768f6e5844ef90 Mon Sep 17 00:00:00 2001 From: LuoPengcheng <2653972504@qq.com> Date: Tue, 14 Oct 2025 16:35:43 +0800 Subject: [PATCH 2/5] enhance:delete the corresponding folder in the local tasks directory --- electron/main/fileReader.ts | 15 +++++++++++++++ electron/main/index.ts | 5 +++++ src/components/HistorySidebar/index.tsx | 11 +++++++++++ 3 files changed, 31 insertions(+) diff --git a/electron/main/fileReader.ts b/electron/main/fileReader.ts index 99bb22198..0b711e513 100644 --- a/electron/main/fileReader.ts +++ b/electron/main/fileReader.ts @@ -559,6 +559,21 @@ export class FileReader { return []; } } + + public deleteTaskFiles(email: string, taskId: string): { success: boolean; path: string } { + const safeEmail = email.split('@')[0].replace(/[\\/*?:"<>|\s]/g, "_").replace(/^\.+|\.+$/g, ""); + const userHome = app.getPath('home'); + const dirPath = path.join(userHome, "eigent", safeEmail, `taskId_${taskId}`); + try { + if (fs.existsSync(dirPath)) { + fs.rmSync(dirPath, { recursive: true, force: true }); + } + return { success: true, path: dirPath }; + } catch (err) { + console.error("Delete task files failed:", dirPath, err); + return { success: false, path: dirPath }; + } + } public getLogFolder(email: string): string { const safeEmail = email.split('@')[0].replace(/[\\/*?:"<>|\s]/g, "_").replace(/^\.+|\.+$/g, ""); diff --git a/electron/main/index.ts b/electron/main/index.ts index 733b8b67d..12e34f034 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -800,6 +800,11 @@ function registerIpcHandlers() { return manager.getFileList(email, taskId); }); + ipcMain.handle('delete-task-files', async (_, email: string, taskId: string) => { + const manager = checkManagerInstance(fileReader, 'FileReader'); + return manager.deleteTaskFiles(email, taskId); + }); + ipcMain.handle('get-log-folder', async (_, email: string) => { const manager = checkManagerInstance(fileReader, 'FileReader'); return manager.getLogFolder(email); diff --git a/src/components/HistorySidebar/index.tsx b/src/components/HistorySidebar/index.tsx index 141b55256..acce5a8ea 100644 --- a/src/components/HistorySidebar/index.tsx +++ b/src/components/HistorySidebar/index.tsx @@ -37,6 +37,7 @@ import { proxyFetchGet, proxyFetchDelete, proxyFetchPost } from "@/api/http"; import { Tag } from "../ui/tag"; import { share } from "@/lib/share"; import { useTranslation } from "react-i18next"; +import {getAuthStore} from "@/store/authStore"; export default function HistorySidebar() { const { t } = useTranslation(); @@ -175,6 +176,16 @@ export default function HistorySidebar() { try { const res = await proxyFetchDelete(`/api/chat/history/${curHistoryId}`); console.log(res); + // also delete local files for this task if available (via Electron IPC) + const email = getAuthStore() + const history = historyTasks.find((item) => item.id === curHistoryId); + if (history?.task_id && (window as any).ipcRenderer) { + try { + await (window as any).ipcRenderer.invoke('delete-task-files', email, history.task_id); + } catch (e) { + console.warn("Local file cleanup failed:", e); + } + } } catch (error) { console.error("Failed to delete history task:", error); } From 96c2b8bc8f36677b9cf36b3f4d6876574edddc61 Mon Sep 17 00:00:00 2001 From: LuoPengcheng <2653972504@qq.com> Date: Tue, 14 Oct 2025 19:30:04 +0800 Subject: [PATCH 3/5] fix:return email:String --- electron/main/fileReader.ts | 2 +- src/components/HistorySidebar/index.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/electron/main/fileReader.ts b/electron/main/fileReader.ts index 0b711e513..fbcca1828 100644 --- a/electron/main/fileReader.ts +++ b/electron/main/fileReader.ts @@ -563,7 +563,7 @@ export class FileReader { public deleteTaskFiles(email: string, taskId: string): { success: boolean; path: string } { const safeEmail = email.split('@')[0].replace(/[\\/*?:"<>|\s]/g, "_").replace(/^\.+|\.+$/g, ""); const userHome = app.getPath('home'); - const dirPath = path.join(userHome, "eigent", safeEmail, `taskId_${taskId}`); + const dirPath = path.join(userHome, "eigent", safeEmail, `task_${taskId}`); try { if (fs.existsSync(dirPath)) { fs.rmSync(dirPath, { recursive: true, force: true }); diff --git a/src/components/HistorySidebar/index.tsx b/src/components/HistorySidebar/index.tsx index acce5a8ea..f3f1b69a5 100644 --- a/src/components/HistorySidebar/index.tsx +++ b/src/components/HistorySidebar/index.tsx @@ -177,13 +177,13 @@ export default function HistorySidebar() { const res = await proxyFetchDelete(`/api/chat/history/${curHistoryId}`); console.log(res); // also delete local files for this task if available (via Electron IPC) - const email = getAuthStore() + const {email} = getAuthStore() const history = historyTasks.find((item) => item.id === curHistoryId); if (history?.task_id && (window as any).ipcRenderer) { try { await (window as any).ipcRenderer.invoke('delete-task-files', email, history.task_id); - } catch (e) { - console.warn("Local file cleanup failed:", e); + } catch (error) { + console.warn("Local file cleanup failed:", error); } } } catch (error) { From a25583c31d817c0283596c6aef0c93a070d69693 Mon Sep 17 00:00:00 2001 From: LuoPengcheng <2653972504@qq.com> Date: Thu, 16 Oct 2025 00:27:09 +0800 Subject: [PATCH 4/5] enhance:also delete the logfile --- electron/main/fileReader.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/electron/main/fileReader.ts b/electron/main/fileReader.ts index fbcca1828..4cf1ff034 100644 --- a/electron/main/fileReader.ts +++ b/electron/main/fileReader.ts @@ -560,20 +560,27 @@ export class FileReader { } } - public deleteTaskFiles(email: string, taskId: string): { success: boolean; path: string } { + public deleteTaskFiles(email: string, taskId: string): { + success: boolean; + path: { dirPath: string; logPath: string } + } + { const safeEmail = email.split('@')[0].replace(/[\\/*?:"<>|\s]/g, "_").replace(/^\.+|\.+$/g, ""); const userHome = app.getPath('home'); const dirPath = path.join(userHome, "eigent", safeEmail, `task_${taskId}`); + const logPath = path.join(userHome, ".eigent", safeEmail, `task_${taskId}`); try { - if (fs.existsSync(dirPath)) { + if (fs.existsSync(dirPath)&&fs.existsSync(logPath)) { fs.rmSync(dirPath, { recursive: true, force: true }); + fs.rmSync(logPath, { recursive: true, force: true }); } - return { success: true, path: dirPath }; + return { success: true, path: { dirPath, logPath } }; } catch (err) { console.error("Delete task files failed:", dirPath, err); - return { success: false, path: dirPath }; + return { success: false, path: { dirPath, logPath } }; } } + public getLogFolder(email: string): string { const safeEmail = email.split('@')[0].replace(/[\\/*?:"<>|\s]/g, "_").replace(/^\.+|\.+$/g, ""); From 97ac30a719aaba2a94dae82d6a66b434e2db6d5c Mon Sep 17 00:00:00 2001 From: sw3205933776 <3205933776@qq.com> Date: Thu, 16 Oct 2025 17:45:04 +0800 Subject: [PATCH 5/5] update --- src/pages/History.tsx | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/pages/History.tsx b/src/pages/History.tsx index c0e8548e7..a63ca4435 100644 --- a/src/pages/History.tsx +++ b/src/pages/History.tsx @@ -34,20 +34,17 @@ import { PopoverTrigger, PopoverClose, } from "@/components/ui/popover"; -import { - fetchPut, - proxyFetchDelete, - proxyFetchGet, -} from "@/api/http"; +import { fetchPut, proxyFetchDelete, proxyFetchGet } from "@/api/http"; import AlertDialog from "@/components/ui/alertDialog"; import { generateUniqueId } from "@/lib"; import { SearchHistoryDialog } from "@/components/SearchHistoryDialog"; import { Tag } from "@/components/ui/tag"; import { share } from "@/lib/share"; import { useTranslation } from "react-i18next"; +import {getAuthStore} from "@/store/authStore"; export default function Home() { - const {t} = useTranslation() + const { t } = useTranslation(); const navigate = useNavigate(); const chatStore = useChatStore(); const { history_type, setHistoryType } = useGlobalStore(); @@ -160,6 +157,20 @@ export default function Home() { try { const res = await proxyFetchDelete(`/api/chat/history/${curHistoryId}`); console.log(res); + // also delete local files for this task if available (via Electron IPC) + const { email } = getAuthStore(); + const history = historyTasks.find((item) => item.id === curHistoryId); + if (history?.task_id && (window as any).ipcRenderer) { + try { + await (window as any).ipcRenderer.invoke( + "delete-task-files", + email, + history.task_id + ); + } catch (error) { + console.warn("Local file cleanup failed:", error); + } + } } catch (error) { console.error("Failed to delete history task:", error); } @@ -249,7 +260,9 @@ export default function Home() { />
-
{t("task-hub.ongoing-tasks")}
+
+ {t("task-hub.ongoing-tasks")} +
@@ -396,7 +409,10 @@ export default function Home() {
- {task.summaryTask || t("task-hub.new-project")} + + {" "} + {task.summaryTask || t("task-hub.new-project")} +

{task.summaryTask || t("task-hub.new-project")}

@@ -609,7 +625,8 @@ export default function Home() { {" "} - {task?.question.split("|")[0] || t("task-hub.new-project")} + {task?.question.split("|")[0] || + t("task-hub.new-project")}
{" "} - {task?.question.split("|")[0] || t("task-hub.new-project")} + {task?.question.split("|")[0] || + t("task-hub.new-project")}