From c27b77e489d5f3b401dc563401277e88c414e023 Mon Sep 17 00:00:00 2001 From: sw3205933776 <3205933776@qq.com> Date: Mon, 11 Aug 2025 11:30:05 +0800 Subject: [PATCH 1/9] chore: remove region validation --- src/store/chatStore.ts | 73 +----------------------------------------- 1 file changed, 1 insertion(+), 72 deletions(-) diff --git a/src/store/chatStore.ts b/src/store/chatStore.ts index 2273deae2..71d56b414 100644 --- a/src/store/chatStore.ts +++ b/src/store/chatStore.ts @@ -184,8 +184,7 @@ const chatStore = create()( } const base_Url = import.meta.env.DEV ? import.meta.env.VITE_PROXY_URL : import.meta.env.VITE_BASE_URL const api = type == 'share' ? `${base_Url}/api/chat/share/playback/${shareToken}?delay_time=${delayTime}` : type == 'replay' ? `${base_Url}/api/chat/steps/playback/${taskId}?delay_time=${delayTime}` : `${baseURL}/chat` - const isInChina = await getIsInChina(systemLanguage) - console.log("isInChina", isInChina); + const { tasks } = get() let historyId: string | null = null; let snapshots: any = []; @@ -1561,39 +1560,6 @@ const chatStore = create()( }) ); -// const filterMessage = (message: string, method_name: string = '') => { -// if (!message!.includes("=======================") && !message?.includes("Original Query") && !message?.startsWith('You need to process one given task') && method_name !== 'browser_take_screenshot' && message !== '{}' && !message?.startsWith('{"query"') && !message?.startsWith('{"entity"') && message !== '' && !message?.startsWith("{'warning':") && !message?.startsWith("{'results':") && !message?.startsWith(`{"index"`) && !message?.startsWith('- Ran Playwright code')) { -// if (message?.includes(`{"content"`)) { -// message = JSON.parse(message)?.content || '' -// } -// if (message?.startsWith('{"element"')) { -// message = JSON.parse(message)?.element || '' -// } -// if (message?.startsWith('{"url"')) { -// message = 'Open URL: ' + JSON.parse(message)?.url || '' -// } -// if (message?.startsWith('{"filename"')) { -// message = JSON.parse(message)?.filename || '' -// } -// if (method_name === 'browser_click' && message?.startsWith('{"element"')) { -// message = 'Click Element: ' + JSON.parse(message)?.element || '' -// } -// if (message?.startsWith('{"query"')) { -// message = 'Search: ' + JSON.parse(message)?.query || '' -// } -// if (message?.startsWith('{"result"')) { -// message = JSON.parse(message)?.result || '' -// } - -// // && !message?.startsWith("{'error':") -// if (message?.startsWith("{'error':")) { -// message = JSON.parse(message.replace(/'error'/g, '"error"'))?.error || '' -// } -// console.log(message) -// return message -// } -// return '' -// } const filterMessage = (message: AgentMessage) => { if (message.data.toolkit_name?.includes('Search ')) { message.data.toolkit_name='Search Toolkit' @@ -1610,45 +1576,8 @@ const filterMessage = (message: AgentMessage) => { } return message } -let isInChinaCache: boolean | null = null; -const getIsInChina = async (systemLanguage: string): Promise => { - if (isInChinaCache !== null) { - return isInChinaCache; - } - const fetchWithTimeout = (url: string, timeout = 3000): Promise => { - return new Promise((resolve, reject) => { - const timer = setTimeout(() => { - reject(new Error('Timeout')); - }, timeout); - - fetch(url) - .then((response) => { - clearTimeout(timer); - resolve(response); - }) - .catch((err) => { - clearTimeout(timer); - reject(err); - }); - }); - }; - - try { - const response = await fetchWithTimeout('https://ipinfo.io/json', 3000); - if (!response.ok) throw new Error('Network response was not ok'); - - const info = await response.json(); - console.log('country', info?.country) - isInChinaCache = info?.country === 'CN'; - return isInChinaCache; - } catch (error) { - console.warn('IP Timeout', error); - isInChinaCache = systemLanguage === 'zh-cn'; - return isInChinaCache; - } -}; export const useChatStore = chatStore; From 9c361159b845ee5a248717eabec337a8a2330e84 Mon Sep 17 00:00:00 2001 From: sw3205933776 <3205933776@qq.com> Date: Mon, 11 Aug 2025 16:48:34 +0800 Subject: [PATCH 2/9] feat: add log upload feature --- electron/main/index.ts | 40 +++++++++++++++++++++++++++++---- electron/main/utils/envUtil.ts | 4 ++-- electron/main/utils/log.ts | 18 +++++++++++++++ electron/preload/index.ts | 1 + package.json | 4 +++- src/components/TopBar/index.tsx | 33 ++++++++++++++++++--------- src/lib/index.ts | 15 +++++++++++++ src/store/chatStore.ts | 20 +++++++++-------- 8 files changed, 108 insertions(+), 27 deletions(-) create mode 100644 electron/main/utils/log.ts diff --git a/electron/main/index.ts b/electron/main/index.ts index a7efe8cb8..97402a950 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -15,6 +15,9 @@ import { getEnvPath, updateEnvBlock, removeEnvKey, getEmailFolderPath } from './ import { copyBrowserData } from './copy' import { findAvailablePort } from './init' import kill from 'tree-kill'; +import { zipFolder } from './utils/log' +import axios from 'axios'; +import FormData from 'form-data'; const userData = app.getPath('userData'); const versionFile = path.join(userData, 'version.txt'); @@ -318,7 +321,7 @@ function registerIpcHandlers() { }); ipcMain.handle('execute-command', async (event, command: string, email: string) => { log.info("execute-command", command); - const {MCP_REMOTE_CONFIG_DIR} = getEmailFolderPath(email); + const { MCP_REMOTE_CONFIG_DIR } = getEmailFolderPath(email); try { const { spawn } = await import('child_process'); @@ -440,6 +443,35 @@ function registerIpcHandlers() { } }); + ipcMain.handle('upload-log', async (event, email: string, taskId: string, baseUrl: string, token: string) => { + const { MCP_REMOTE_CONFIG_DIR } = getEmailFolderPath(email); + const logFolderName = `task_${taskId}`; + const logFolderPath = path.join(MCP_REMOTE_CONFIG_DIR, logFolderName); + // check log folder exists + if (!fs.existsSync(logFolderPath)) { + return { success: false, error: 'log folder not found' }; + } + const zipPath = path.join(MCP_REMOTE_CONFIG_DIR, `${logFolderName}.zip`); + await zipFolder(logFolderPath, zipPath) + + const formData = new FormData(); + formData.append('file', fs.createReadStream(zipPath)); + formData.append('task_id', taskId); + + const response = await axios.post(baseUrl + '/api/chat/logs', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + 'Authorization': `Bearer ${token}` + } + }); + + if (response.status === 200) { + return { success: true, data: response.data }; + } else { + return { success: false, error: response.data }; + } + }); + // ==================== MCP manage handler ==================== ipcMain.handle('mcp-install', async (event, name, mcp) => { addMcp(name, mcp); @@ -609,7 +641,7 @@ function registerIpcHandlers() { // ==================== delete folder handler ==================== ipcMain.handle('delete-folder', async (event, email: string) => { - const {MCP_REMOTE_CONFIG_DIR} = getEmailFolderPath(email); + const { MCP_REMOTE_CONFIG_DIR } = getEmailFolderPath(email); try { log.info('Deleting folder:', MCP_REMOTE_CONFIG_DIR); @@ -646,7 +678,7 @@ function registerIpcHandlers() { // ==================== get MCP config path handler ==================== ipcMain.handle('get-mcp-config-path', async (event, email: string) => { try { - const {MCP_REMOTE_CONFIG_DIR,tempEmail} = getEmailFolderPath(email); + const { MCP_REMOTE_CONFIG_DIR, tempEmail } = getEmailFolderPath(email); log.info('Getting MCP config path for email:', email); log.info('MCP config path:', MCP_REMOTE_CONFIG_DIR); return { @@ -664,7 +696,7 @@ function registerIpcHandlers() { }); // ==================== env handler ==================== - + ipcMain.handle('get-env-path', async (_event, email) => { return getEnvPath(email); }); diff --git a/electron/main/utils/envUtil.ts b/electron/main/utils/envUtil.ts index 82e2bc531..850c40cbc 100644 --- a/electron/main/utils/envUtil.ts +++ b/electron/main/utils/envUtil.ts @@ -90,9 +90,9 @@ export function getEmailFolderPath(email: string) { hasToken = false; } } catch (error) { - console.log("error", error); hasToken = false; } return { MCP_REMOTE_CONFIG_DIR, MCP_CONFIG_DIR, tempEmail, hasToken }; -} \ No newline at end of file +} + diff --git a/electron/main/utils/log.ts b/electron/main/utils/log.ts new file mode 100644 index 000000000..308f4d2ba --- /dev/null +++ b/electron/main/utils/log.ts @@ -0,0 +1,18 @@ +import fs from 'node:fs' +import archiver from 'archiver' +export function zipFolder(folderPath: string, outputZipPath: string) { + return new Promise((resolve, reject) => { + const output = fs.createWriteStream(outputZipPath) + const archive = archiver('zip', { zlib: { level: 9 } }) + + output.on('close', () => resolve(outputZipPath)) + archive.on('error', reject) + + archive.pipe(output) + archive.directory(folderPath, false) + archive.finalize() + }) +} + + + diff --git a/electron/preload/index.ts b/electron/preload/index.ts index 5c91e46ca..17525d191 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -44,6 +44,7 @@ contextBridge.exposeInMainWorld('electronAPI', { getShowWebview: () => ipcRenderer.invoke('get-show-webview'), webviewDestroy: (webviewId: string) => ipcRenderer.invoke('webview-destroy', webviewId), exportLog: () => ipcRenderer.invoke('export-log'), + uploadLog: (email: string, taskId: string, baseUrl: string, token: string) => ipcRenderer.invoke('upload-log', email, taskId, baseUrl, token), // mcp mcpInstall: (name: string, mcp: any) => ipcRenderer.invoke('mcp-install', name, mcp), mcpRemove: (name: string) => ipcRenderer.invoke('mcp-remove', name), diff --git a/package.json b/package.json index 1911f73df..4bc699bb4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eigent", - "version": "0.0.46", + "version": "0.0.47", "main": "dist-electron/main/index.js", "description": "Eigent", "author": "Eigent.AI", @@ -49,6 +49,7 @@ "@xterm/xterm": "^5.5.0", "@xyflow/react": "^12.6.4", "adm-zip": "^0.5.16", + "archiver": "^7.0.1", "axios": "^1.9.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -82,6 +83,7 @@ }, "devDependencies": { "@playwright/test": "^1.48.2", + "@types/archiver": "^6.0.3", "@types/lodash-es": "^4.17.12", "@types/papaparse": "^5.3.16", "@types/react": "^18.3.12", diff --git a/src/components/TopBar/index.tsx b/src/components/TopBar/index.tsx index 6e887a466..53b5b1fb7 100644 --- a/src/components/TopBar/index.tsx +++ b/src/components/TopBar/index.tsx @@ -1,5 +1,14 @@ import { useState, useRef, useEffect, useMemo } from "react"; -import { Settings, Minus, Square, X, FileDown, Menu, Plus } from "lucide-react"; +import { + Settings, + Minus, + Square, + X, + FileDown, + Menu, + Plus, + Import, +} from "lucide-react"; import "./index.css"; import folderIcon from "@/assets/Folder.svg"; import { Button } from "@/components/ui/button"; @@ -7,6 +16,7 @@ import { useLocation, useNavigate } from "react-router-dom"; import { useChatStore } from "@/store/chatStore"; import { useSidebarStore } from "@/store/sidebarStore"; import chevron_left from "@/assets/chevron_left.svg"; +import { getAuthStore } from "@/store/authStore"; function HeaderWin() { const titlebarRef = useRef(null); const controlsRef = useRef(null); @@ -16,6 +26,7 @@ function HeaderWin() { const chatStore = useChatStore(); const { toggle } = useSidebarStore(); const [isFullscreen, setIsFullscreen] = useState(false); + const { token } = getAuthStore(); useEffect(() => { const p = window.electronAPI.getPlatform(); setPlatform(p); @@ -140,20 +151,20 @@ function HeaderWin() { <> {activeTaskTitle === "New Project" ? ( + variant="ghost" + size="sm" + className="font-bold text-base no-drag" + onClick={createNewProject} + > + {activeTaskTitle} + ) : ( -
{activeTaskTitle}
+
+ {activeTaskTitle} +
)} )} - -
{/* right */} diff --git a/src/lib/index.ts b/src/lib/index.ts index 2977cf91a..f75a425d5 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,3 +1,5 @@ +import { getAuthStore } from "@/store/authStore" + export function getProxyBaseURL() { const isDev = import.meta.env.DEV @@ -56,4 +58,17 @@ export function hasStackKeys() { return import.meta.env.VITE_STACK_PROJECT_ID && import.meta.env.VITE_STACK_PUBLISHABLE_CLIENT_KEY && import.meta.env.VITE_STACK_SECRET_SERVER_KEY; +} + +export function uploadLog(taskId: string, type?: string | undefined) { + if (import.meta.env.VITE_USE_LOCAL_PROXY !== "true" && !type) { + const { email, token } = getAuthStore() + const baseUrl = import.meta.env.DEV ? import.meta.env.VITE_PROXY_URL : import.meta.env.VITE_BASE_URL + window.electronAPI.uploadLog( + email, + taskId, + baseUrl, + token + ); + } } \ No newline at end of file diff --git a/src/store/chatStore.ts b/src/store/chatStore.ts index 71d56b414..b4a97a9ea 100644 --- a/src/store/chatStore.ts +++ b/src/store/chatStore.ts @@ -1,11 +1,10 @@ import { fetchPost, fetchPut, getBaseURL, proxyFetchPost, proxyFetchPut, proxyFetchGet, uploadFile } from '@/api/http'; import { fetchEventSource } from '@microsoft/fetch-event-source'; import { create } from 'zustand'; -import { generateUniqueId } from "@/lib"; +import { generateUniqueId, uploadLog } from "@/lib"; import { FileText } from 'lucide-react'; import { getAuthStore, useWorkerList } from './authStore'; import { showCreditsToast } from '@/components/Toast/creditsToast'; -import { OAuth } from '@/lib/oauth'; import { showStorageToast } from '@/components/Toast/storageToast'; @@ -264,7 +263,7 @@ const chatStore = create()( } catch (error) { console.log('get-env-path error', error) } - + // create history if (!type) { const authStore = getAuthStore(); @@ -765,7 +764,7 @@ const chatStore = create()( addFileList(taskId, agentMessages.data.process_task_id as string, fileInfo); // Async file upload - if (!type && file_path && import.meta.env.VITE_USE_LOCAL_PROXY!=='true') { + if (!type && file_path && import.meta.env.VITE_USE_LOCAL_PROXY !== 'true') { (async () => { try { // Read file content using Electron API @@ -804,13 +803,14 @@ const chatStore = create()( console.log('error', agentMessages.data) showCreditsToast() setStatus(taskId, 'pause'); + uploadLog(taskId, type) return } if (agentMessages.step === "error") { console.error('Model error:', agentMessages.data) const errorMessage = agentMessages.data.message || 'An error occurred while processing your request'; - + // Create a new task to avoid "Task already exists" error // and completely reset the interface const newTaskId = create(); @@ -824,7 +824,7 @@ const chatStore = create()( role: "agent", content: `❌ **Error**: ${errorMessage}`, }); - + uploadLog(taskId, type) return } @@ -845,6 +845,8 @@ const chatStore = create()( } proxyFetchPut(`/api/chat/history/${historyId}`, obj) } + uploadLog(taskId, type) + let taskRunning = [...tasks[taskId].taskRunning]; let taskAssigning = [...tasks[taskId].taskAssigning]; @@ -1562,12 +1564,12 @@ const chatStore = create()( const filterMessage = (message: AgentMessage) => { if (message.data.toolkit_name?.includes('Search ')) { - message.data.toolkit_name='Search Toolkit' + message.data.toolkit_name = 'Search Toolkit' } if (message.data.method_name?.includes('search')) { - message.data.method_name='search' + message.data.method_name = 'search' } - + if (message.data.toolkit_name === 'Note Taking Toolkit') { message.data.message = message.data.message!.replace(/content='/g, '').replace(/', update=False/g, '').replace(/', update=True/g, '') } From 922996a23fed46cf349cf6a3c73fa75e2b4c63be Mon Sep 17 00:00:00 2001 From: sw3205933776 <3205933776@qq.com> Date: Mon, 11 Aug 2025 17:17:43 +0800 Subject: [PATCH 3/9] refactor: change process termination method from PID to port-based on app close --- electron/main/index.ts | 13 +++++++++++-- electron/main/init.ts | 2 +- vite.config.ts | 11 +---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/electron/main/index.ts b/electron/main/index.ts index 97402a950..446350ffc 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -4,7 +4,7 @@ import path from 'node:path' import os, { homedir } from 'node:os' import log from 'electron-log' import { update, registerUpdateIpcHandlers } from './update' -import { checkToolInstalled, installDependencies, startBackend } from './init' +import { checkToolInstalled, installDependencies, killProcessOnPort, startBackend } from './init' import { WebViewManager } from './webview' import { FileReader } from './fileReader' import { ChildProcessWithoutNullStreams } from 'node:child_process' @@ -989,6 +989,7 @@ const checkAndStartBackend = async () => { }); python_process?.on('exit', (code, signal) => { + log.info('Python process exited', { code, signal }); }); } else { @@ -997,8 +998,9 @@ const checkAndStartBackend = async () => { }; // ==================== process cleanup ==================== -const cleanupPythonProcess = () => { +const cleanupPythonProcess = async () => { try { + if (python_process?.pid) { log.info('Cleaning up Python process', { pid: python_process.pid }); kill(python_process.pid, 'SIGINT', (err) => { @@ -1011,6 +1013,13 @@ const cleanupPythonProcess = () => { } else { log.info('No Python process to clean up'); } + let port: number; + const portFile = path.join(userData, 'port.txt'); + if (fs.existsSync(portFile)) { + port = parseInt(fs.readFileSync(portFile, 'utf-8')); + log.info(`Found port from file: ${port}`); + await killProcessOnPort(port); + } } catch (error) { log.error('Error occurred while cleaning up process:', error); } diff --git a/electron/main/init.ts b/electron/main/init.ts index bf54b8923..70dbb7824 100644 --- a/electron/main/init.ts +++ b/electron/main/init.ts @@ -423,7 +423,7 @@ function checkPortAvailable(port: number): Promise { }); } -async function killProcessOnPort(port: number): Promise { +export async function killProcessOnPort(port: number): Promise { try { const platform = process.platform; let command: string; diff --git a/vite.config.ts b/vite.config.ts index b97c02c22..8e281dcac 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -86,13 +86,4 @@ export default defineConfig(({ command, mode }) => { } }) -process.on('SIGINT', () => { - try { - const backend = path.join(__dirname, 'backend') - const pid = readFileSync(backend + '/runtime/run.pid', 'utf-8') - process.kill(parseInt(pid), 'SIGINT') - } catch (e) { - console.log('no pid file') - console.log(e) - } -}) + From 60494b57b540ec29486f87d941b3edf3361179e4 Mon Sep 17 00:00:00 2001 From: sw3205933776 <3205933776@qq.com> Date: Tue, 12 Aug 2025 14:12:21 +0800 Subject: [PATCH 4/9] Update environment variables --- .env.development | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.development b/.env.development index 13a36dcc7..728efe831 100644 --- a/.env.development +++ b/.env.development @@ -1,5 +1,5 @@ VITE_BASE_URL=/api -VITE_PROXY_URL=https://dev.eigent.ai +VITE_PROXY_URL=http://localhost:3001 -VITE_USE_LOCAL_PROXY=false \ No newline at end of file +VITE_USE_LOCAL_PROXY=true \ No newline at end of file From f6580cd756445f771d85a22341662cd7f21d828d Mon Sep 17 00:00:00 2001 From: sw3205933776 <3205933776@qq.com> Date: Tue, 12 Aug 2025 14:14:24 +0800 Subject: [PATCH 5/9] chore: ignore .env.development file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7fdb72658..7d652a0f9 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ yarn.lock .env .env.local .env.production +.env.development .cursor From 8748ba5d6b07109cd2e0785cd38a8e36b435e060 Mon Sep 17 00:00:00 2001 From: sw3205933776 <3205933776@qq.com> Date: Tue, 12 Aug 2025 17:09:51 +0800 Subject: [PATCH 6/9] update --- .env.development | 6 +++++- .gitignore | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.env.development b/.env.development index 13a36dcc7..9a0628c35 100644 --- a/.env.development +++ b/.env.development @@ -2,4 +2,8 @@ VITE_BASE_URL=/api VITE_PROXY_URL=https://dev.eigent.ai -VITE_USE_LOCAL_PROXY=false \ No newline at end of file +VITE_USE_LOCAL_PROXY=true + +# VITE_PROXY_URL=http://localhost:3001 + +# VITE_USE_LOCAL_PROXY=false \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7fdb72658..7d652a0f9 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ yarn.lock .env .env.local .env.production +.env.development .cursor From def3044b7673c7ae103b5e6453dcfc10aa06512f Mon Sep 17 00:00:00 2001 From: sw3205933776 <3205933776@qq.com> Date: Tue, 12 Aug 2025 17:10:04 +0800 Subject: [PATCH 7/9] update --- .env.development | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.development b/.env.development index 9a0628c35..cc5d7b058 100644 --- a/.env.development +++ b/.env.development @@ -2,8 +2,8 @@ VITE_BASE_URL=/api VITE_PROXY_URL=https://dev.eigent.ai -VITE_USE_LOCAL_PROXY=true +VITE_USE_LOCAL_PROXY=false # VITE_PROXY_URL=http://localhost:3001 -# VITE_USE_LOCAL_PROXY=false \ No newline at end of file +# VITE_USE_LOCAL_PROXY=true \ No newline at end of file From cf87c2d109a5fe41a497eb1d49c6921a49a6a34e Mon Sep 17 00:00:00 2001 From: sw3205933776 <3205933776@qq.com> Date: Tue, 12 Aug 2025 17:13:17 +0800 Subject: [PATCH 8/9] update --- vite.config.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vite.config.ts b/vite.config.ts index 8e281dcac..442d027c6 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -86,4 +86,14 @@ export default defineConfig(({ command, mode }) => { } }) +process.on('SIGINT', () => { + try { + const backend = path.join(__dirname, 'backend') + const pid = readFileSync(backend + '/runtime/run.pid', 'utf-8') + process.kill(parseInt(pid), 'SIGINT') + } catch (e) { + console.log('no pid file') + console.log(e) + } +}) From ffa9edd377fa443ef700a569496fa7f318ba39cf Mon Sep 17 00:00:00 2001 From: Wendong-Fan Date: Tue, 12 Aug 2025 19:38:33 +0800 Subject: [PATCH 9/9] ehnance:Feature upload logs PR124 --- electron/main/index.ts | 122 ++++++++++++++++++++++++++----------- electron/main/utils/log.ts | 11 +++- src/lib/index.ts | 23 ++++--- 3 files changed, 109 insertions(+), 47 deletions(-) diff --git a/electron/main/index.ts b/electron/main/index.ts index 446350ffc..096671fc0 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -444,31 +444,68 @@ function registerIpcHandlers() { }); ipcMain.handle('upload-log', async (event, email: string, taskId: string, baseUrl: string, token: string) => { - const { MCP_REMOTE_CONFIG_DIR } = getEmailFolderPath(email); - const logFolderName = `task_${taskId}`; - const logFolderPath = path.join(MCP_REMOTE_CONFIG_DIR, logFolderName); - // check log folder exists - if (!fs.existsSync(logFolderPath)) { - return { success: false, error: 'log folder not found' }; - } - const zipPath = path.join(MCP_REMOTE_CONFIG_DIR, `${logFolderName}.zip`); - await zipFolder(logFolderPath, zipPath) + let zipPath: string | null = null; - const formData = new FormData(); - formData.append('file', fs.createReadStream(zipPath)); - formData.append('task_id', taskId); - - const response = await axios.post(baseUrl + '/api/chat/logs', formData, { - headers: { - 'Content-Type': 'multipart/form-data', - 'Authorization': `Bearer ${token}` + try { + // Validate required parameters + if (!email || !taskId || !baseUrl || !token) { + return { success: false, error: 'Missing required parameters' }; } - }); - if (response.status === 200) { - return { success: true, data: response.data }; - } else { - return { success: false, error: response.data }; + // Sanitize taskId to prevent path traversal attacks + const sanitizedTaskId = taskId.replace(/[^a-zA-Z0-9_-]/g, ''); + if (!sanitizedTaskId) { + return { success: false, error: 'Invalid task ID' }; + } + + const { MCP_REMOTE_CONFIG_DIR } = getEmailFolderPath(email); + const logFolderName = `task_${sanitizedTaskId}`; + const logFolderPath = path.join(MCP_REMOTE_CONFIG_DIR, logFolderName); + + // Check if log folder exists + if (!fs.existsSync(logFolderPath)) { + return { success: false, error: 'Log folder not found' }; + } + + zipPath = path.join(MCP_REMOTE_CONFIG_DIR, `${logFolderName}.zip`); + await zipFolder(logFolderPath, zipPath); + + // Create form data with file stream + const formData = new FormData(); + const fileStream = fs.createReadStream(zipPath); + formData.append('file', fileStream); + formData.append('task_id', sanitizedTaskId); + + // Upload with timeout + const response = await axios.post(baseUrl + '/api/chat/logs', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + 'Authorization': `Bearer ${token}` + }, + timeout: 60000, // 60 second timeout + maxContentLength: Infinity, + maxBodyLength: Infinity + }); + + fileStream.destroy(); + + if (response.status === 200) { + return { success: true, data: response.data }; + } else { + return { success: false, error: response.data }; + } + } catch (error: any) { + log.error('Failed to upload log:', error); + return { success: false, error: error.message || 'Upload failed' }; + } finally { + // Clean up zip file + if (zipPath && fs.existsSync(zipPath)) { + try { + fs.unlinkSync(zipPath); + } catch (cleanupError) { + log.error('Failed to clean up zip file:', cleanupError); + } + } } }); @@ -1000,26 +1037,39 @@ const checkAndStartBackend = async () => { // ==================== process cleanup ==================== const cleanupPythonProcess = async () => { try { - + // First attempt: Try to kill using PID if (python_process?.pid) { - log.info('Cleaning up Python process', { pid: python_process.pid }); - kill(python_process.pid, 'SIGINT', (err) => { - if (err) { - log.error('Failed to clean up process tree:', err); - } else { - log.info('Successfully cleaned up Python process tree'); - } + const pid = python_process.pid; + log.info('Cleaning up Python process', { pid }); + + await new Promise((resolve) => { + kill(pid, 'SIGINT', (err) => { + if (err) { + log.error('Failed to clean up process tree:', err); + } else { + log.info('Successfully cleaned up Python process tree'); + } + resolve(); + }); }); - } else { - log.info('No Python process to clean up'); } - let port: number; + + // Second attempt: Use port-based cleanup as fallback const portFile = path.join(userData, 'port.txt'); if (fs.existsSync(portFile)) { - port = parseInt(fs.readFileSync(portFile, 'utf-8')); - log.info(`Found port from file: ${port}`); - await killProcessOnPort(port); + try { + const port = parseInt(fs.readFileSync(portFile, 'utf-8').trim(), 10); + if (!isNaN(port) && port > 0 && port < 65536) { + log.info(`Attempting to kill process on port: ${port}`); + await killProcessOnPort(port); + } + fs.unlinkSync(portFile); + } catch (error) { + log.error('Error handling port file:', error); + } } + + python_process = null; } catch (error) { log.error('Error occurred while cleaning up process:', error); } diff --git a/electron/main/utils/log.ts b/electron/main/utils/log.ts index 308f4d2ba..8d8e03bfd 100644 --- a/electron/main/utils/log.ts +++ b/electron/main/utils/log.ts @@ -1,12 +1,19 @@ import fs from 'node:fs' +// @ts-ignore import archiver from 'archiver' -export function zipFolder(folderPath: string, outputZipPath: string) { +import log from 'electron-log' + +export function zipFolder(folderPath: string, outputZipPath: string): Promise { return new Promise((resolve, reject) => { const output = fs.createWriteStream(outputZipPath) const archive = archiver('zip', { zlib: { level: 9 } }) output.on('close', () => resolve(outputZipPath)) - archive.on('error', reject) + + archive.on('error', (err: any) => { + log.error('Archive error:', err); + reject(err); + }) archive.pipe(output) archive.directory(folderPath, false) diff --git a/src/lib/index.ts b/src/lib/index.ts index f75a425d5..0e4d34a0a 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -60,15 +60,20 @@ export function hasStackKeys() { import.meta.env.VITE_STACK_SECRET_SERVER_KEY; } -export function uploadLog(taskId: string, type?: string | undefined) { +export async function uploadLog(taskId: string, type?: string | undefined) { if (import.meta.env.VITE_USE_LOCAL_PROXY !== "true" && !type) { - const { email, token } = getAuthStore() - const baseUrl = import.meta.env.DEV ? import.meta.env.VITE_PROXY_URL : import.meta.env.VITE_BASE_URL - window.electronAPI.uploadLog( - email, - taskId, - baseUrl, - token - ); + try { + const { email, token } = getAuthStore() + const baseUrl = import.meta.env.DEV ? import.meta.env.VITE_PROXY_URL : import.meta.env.VITE_BASE_URL + + await window.electronAPI.uploadLog( + email, + taskId, + baseUrl, + token + ); + } catch (error) { + console.error('Failed to upload log:', error); + } } } \ No newline at end of file