Merge branch 'main' into tool_schema_temporary-fix

This commit is contained in:
Puzhen Zhang 2025-10-16 14:43:31 +01:00 committed by GitHub
commit 9de1d78f15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 222 additions and 29 deletions

View file

@ -559,6 +559,28 @@ export class FileReader {
return [];
}
}
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)&&fs.existsSync(logPath)) {
fs.rmSync(dirPath, { recursive: true, force: true });
fs.rmSync(logPath, { recursive: true, force: true });
}
return { success: true, path: { dirPath, logPath } };
} catch (err) {
console.error("Delete task files failed:", dirPath, err);
return { success: false, path: { dirPath, logPath } };
}
}
public getLogFolder(email: string): string {
const safeEmail = email.split('@')[0].replace(/[\\/*?:"<>|\s]/g, "_").replace(/^\.+|\.+$/g, "");

View file

@ -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;
@ -800,6 +807,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);
@ -922,8 +934,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 +1209,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<void>((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 +1246,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 +1332,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 +1360,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();
});

View file

@ -20,6 +20,9 @@ export class WebViewManager {
private webViews = new Map<string, WebViewInfo>()
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)
})
}
}

View file

@ -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 (error) {
console.warn("Local file cleanup failed:", error);
}
}
} catch (error) {
console.error("Failed to delete history task:", error);
}

View file

@ -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() {
/>
<div>
<div className="px-6 py-4 flex justify-between items-center">
<div className="text-2xl font-bold leading-4">{t("task-hub.ongoing-tasks")}</div>
<div className="text-2xl font-bold leading-4">
{t("task-hub.ongoing-tasks")}
</div>
<div className="flex items-center gap-md">
<SearchHistoryDialog />
@ -396,7 +409,10 @@ export default function Home() {
<div className=" flex-1 text-[14px] text-text-primary font-bold leading-9 overflow-hidden text-ellipsis whitespace-nowrap">
<Tooltip>
<TooltipTrigger asChild>
<span> {task.summaryTask || t("task-hub.new-project")}</span>
<span>
{" "}
{task.summaryTask || t("task-hub.new-project")}
</span>
</TooltipTrigger>
<TooltipContent>
<p> {task.summaryTask || t("task-hub.new-project")}</p>
@ -609,7 +625,8 @@ export default function Home() {
<TooltipTrigger asChild>
<span>
{" "}
{task?.question.split("|")[0] || t("task-hub.new-project")}
{task?.question.split("|")[0] ||
t("task-hub.new-project")}
</span>
</TooltipTrigger>
<TooltipContent
@ -618,7 +635,8 @@ export default function Home() {
>
<div>
{" "}
{task?.question.split("|")[0] || t("task-hub.new-project")}
{task?.question.split("|")[0] ||
t("task-hub.new-project")}
</div>
</TooltipContent>
</Tooltip>