mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-05-19 16:31:36 +00:00
Merge branch 'main' into tool_schema_temporary-fix
This commit is contained in:
commit
9de1d78f15
5 changed files with 222 additions and 29 deletions
|
|
@ -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, "");
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue