From e34af4434b63003c60d5ed498ff7476702ec131c Mon Sep 17 00:00:00 2001 From: Waleed Alzarooni Date: Mon, 8 Sep 2025 16:43:11 +0100 Subject: [PATCH 01/19] fixed closing notification functionality --- src/components/Dialog/CloseNotice.tsx | 39 +++++++++++++++++++++++++++ src/components/Layout/index.tsx | 27 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 src/components/Dialog/CloseNotice.tsx diff --git a/src/components/Dialog/CloseNotice.tsx b/src/components/Dialog/CloseNotice.tsx new file mode 100644 index 000000000..2e19ef298 --- /dev/null +++ b/src/components/Dialog/CloseNotice.tsx @@ -0,0 +1,39 @@ +import { useCallback } from "react"; +import { Button } from "../ui/button"; +import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "../ui/dialog"; + +interface Props { + open: boolean; + onOpenChange: (open: boolean) => void; + trigger?: React.ReactNode; +} +export default function CloseNoticeDialog({open, onOpenChange, trigger}: Props) { + + const onSubmit = useCallback(() => { + window.electronAPI.closeWindow(true) + }, []) + + return + {trigger && {trigger}} + + + + Close notice + + +
+ A task is currently running. Exiting will terminate it. Are you sure you want to exit? +
+ + + + + + +
+
+} \ No newline at end of file diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx index 260b72ce5..685531bfe 100644 --- a/src/components/Layout/index.tsx +++ b/src/components/Layout/index.tsx @@ -6,10 +6,32 @@ import { useAuthStore } from "@/store/authStore"; import { useEffect, useState } from "react"; import { AnimationJson } from "@/components/AnimationJson"; import animationData from "@/assets/animation/onboarding_success.json"; +import CloseNoticeDialog from "../Dialog/CloseNotice"; +import { useChatStore } from "@/store/chatStore"; const Layout = () => { const { initState, setInitState, isFirstLaunch, setIsFirstLaunch } = useAuthStore(); const [isInstalling, setIsInstalling] = useState(false); + const [noticeOpen, setNoticeOpen] = useState(false); + const chatStore = useChatStore(); + + useEffect(() => { + const handleBeforeClose = () => { + const currentStatus = chatStore.tasks[chatStore.activeTaskId as string]?.status; + if(["pending", "running", "pause"].includes(currentStatus)) { + setNoticeOpen(true); + } else { + window.electronAPI.closeWindow(true); + } + }; + + window.ipcRenderer.on("before-close", handleBeforeClose); + + return () => { + window.ipcRenderer.removeAllListeners("before-close"); + }; + }, [chatStore.tasks, chatStore.activeTaskId]); + useEffect(() => { const checkToolInstalled = async () => { // in render process @@ -25,6 +47,7 @@ const Layout = () => { }; checkToolInstalled(); }, []); + return (
@@ -46,6 +69,10 @@ const Layout = () => { )} +
); From ea4755005dbc0ffe107b346a553f7adb5df9d1aa Mon Sep 17 00:00:00 2001 From: sw3205933776 <3205933776@qq.com> Date: Tue, 9 Sep 2025 18:25:26 +0800 Subject: [PATCH 02/19] ix: resolve issue where tasks from previous account kept running after switching accounts --- electron/main/index.ts | 7 +++++++ src/pages/Setting/Models.tsx | 3 --- src/store/chatStore.ts | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/electron/main/index.ts b/electron/main/index.ts index af7152835..e39e1e5e4 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -312,6 +312,13 @@ function registerIpcHandlers() { }); ipcMain.handle('get-app-version', () => app.getVersion()); ipcMain.handle('get-backend-port', () => backendPort); + ipcMain.handle('restart-backend', () => { + if (backendPort) { + cleanupPythonProcess(); + checkAndStartBackend(); + } + return { success: true }; + }); ipcMain.handle('get-system-language', getSystemLanguage); ipcMain.handle('is-fullscreen', () => win?.isFullScreen() || false); ipcMain.handle('get-home-dir', () => { diff --git a/src/pages/Setting/Models.tsx b/src/pages/Setting/Models.tsx index 5bb082586..577119b80 100644 --- a/src/pages/Setting/Models.tsx +++ b/src/pages/Setting/Models.tsx @@ -682,9 +682,6 @@ export default function SettingModels() { GPT-5 GPT-5 mini GPT-5 nano - - Claude Opus 4.1 - Claude Sonnet 4 diff --git a/src/store/chatStore.ts b/src/store/chatStore.ts index 7069fa406..ef7268ecc 100644 --- a/src/store/chatStore.ts +++ b/src/store/chatStore.ts @@ -1639,6 +1639,7 @@ const chatStore = create()( const { create } = get() console.log('clearTasks') fetchDelete('/task/stop-all') + window.ipcRenderer.invoke('restart-backend') const newTaskId = create() set((state) => ({ ...state, From bf42d595cd6eb02426aa65ac6ea175e745345fb9 Mon Sep 17 00:00:00 2001 From: sw3205933776 <3205933776@qq.com> Date: Thu, 11 Sep 2025 18:19:57 +0800 Subject: [PATCH 03/19] update --- electron/main/index.ts | 6 +++--- src/store/chatStore.ts | 27 +++++++++++++++------------ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/electron/main/index.ts b/electron/main/index.ts index e39e1e5e4..f3022c8ff 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -312,10 +312,10 @@ function registerIpcHandlers() { }); ipcMain.handle('get-app-version', () => app.getVersion()); ipcMain.handle('get-backend-port', () => backendPort); - ipcMain.handle('restart-backend', () => { + ipcMain.handle('restart-backend', async () => { if (backendPort) { - cleanupPythonProcess(); - checkAndStartBackend(); + await cleanupPythonProcess(); + await checkAndStartBackend(); } return { success: true }; }); diff --git a/src/store/chatStore.ts b/src/store/chatStore.ts index ef7268ecc..198322048 100644 --- a/src/store/chatStore.ts +++ b/src/store/chatStore.ts @@ -497,7 +497,7 @@ const chatStore = create()( setTaskAssigning(taskId, taskAssigning) return; } - + // Activate agent if (agentMessages.step === "activate_agent" || agentMessages.step === "deactivate_agent") { let taskAssigning = [...tasks[taskId].taskAssigning] @@ -607,7 +607,7 @@ const chatStore = create()( if (taskAssigning && taskAssigning[assigneeAgentIndex]) { // Check if task already exists in the agent's task list const existingTaskIndex = taskAssigning[assigneeAgentIndex].tasks.findIndex(item => item.id === task_id); - + if (existingTaskIndex !== -1) { // Task already exists, update its status taskAssigning[assigneeAgentIndex].tasks[existingTaskIndex].status = "running"; @@ -624,7 +624,7 @@ const chatStore = create()( taskAssigning[assigneeAgentIndex].tasks.push(taskTemp ?? { id: task_id, content, status: "running", }); } } - + // Only update or add to taskRunning, never duplicate if (taskRunningIndex === -1) { // Task not in taskRunning, add it @@ -1639,16 +1639,19 @@ const chatStore = create()( const { create } = get() console.log('clearTasks') fetchDelete('/task/stop-all') - window.ipcRenderer.invoke('restart-backend') - const newTaskId = create() - set((state) => ({ - ...state, - tasks: { - [newTaskId]: { - ...state.tasks[newTaskId], + window.ipcRenderer.invoke('restart-backend').then((res) => { + console.log('restart-backend', res) + const newTaskId = create() + set((state) => ({ + ...state, + tasks: { + [newTaskId]: { + ...state.tasks[newTaskId], + }, }, - }, - })) + })) + }) + }, }) ); From 1788465fe11afad5b61cb9b07763d56e35561376 Mon Sep 17 00:00:00 2001 From: sw3205933776 <3205933776@qq.com> Date: Thu, 11 Sep 2025 18:37:55 +0800 Subject: [PATCH 04/19] fix: browser not working due to no active page available (#328) --- electron/main/webview.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/electron/main/webview.ts b/electron/main/webview.ts index 20816fd1b..4ac0a7738 100644 --- a/electron/main/webview.ts +++ b/electron/main/webview.ts @@ -120,10 +120,12 @@ export class WebViewManager { const activeSize = this.getActiveWebview().length const allSize = Array.from(this.webViews.values()).length if (allSize - activeSize <= 3) { - const newId = Array.from(this.webViews.keys()).length + 2 + const newId = (Math.max(0, ...Array.from(this.webViews.keys()).map(Number)) || 0) + 2 this.createWebview(newId.toString(), 'about:blank?use=0') this.createWebview((newId + 1).toString(), 'about:blank?use=0') this.createWebview((newId + 2).toString(), 'about:blank?use=0') + this.createWebview((newId + 3).toString(), 'about:blank?use=0') + this.createWebview((newId + 4).toString(), 'about:blank?use=0') } // setTimeout(() => { From 8ab8e6d591cf02e41386bd782f2985260ea8af32 Mon Sep 17 00:00:00 2001 From: Sun Tao <2605127667@qq.com> Date: Fri, 12 Sep 2025 15:31:05 +0800 Subject: [PATCH 05/19] update --- backend/app/component/environment.py | 44 +++++++++++++++++++++-- backend/app/controller/chat_controller.py | 4 +++ backend/app/controller/task_controller.py | 3 ++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/backend/app/component/environment.py b/backend/app/component/environment.py index 3096d84c6..514ff9590 100644 --- a/backend/app/component/environment.py +++ b/backend/app/component/environment.py @@ -5,10 +5,36 @@ from fastapi import APIRouter, FastAPI from dotenv import load_dotenv import importlib from typing import Any, overload +import threading + +# Thread-local storage for user-specific environment +_thread_local = threading.local() + +# Default global environment path +default_env_path = os.path.join(os.path.expanduser("~"), ".eigent", ".env") +load_dotenv(dotenv_path=default_env_path) -env_path = os.path.join(os.path.expanduser("~"), ".eigent", ".env") -load_dotenv(dotenv_path=env_path) +def set_user_env_path(env_path: str | None = None): + """ + Set user-specific environment path for current thread. + If env_path is None, uses default global environment. + """ + if env_path and os.path.exists(env_path): + _thread_local.env_path = env_path + # Load user-specific environment variables + load_dotenv(dotenv_path=env_path, override=True) + else: + # Clear thread-local env_path to fall back to global + if hasattr(_thread_local, 'env_path'): + delattr(_thread_local, 'env_path') + + +def get_current_env_path() -> str: + """ + Get current environment path (either user-specific or default). + """ + return getattr(_thread_local, 'env_path', default_env_path) @overload @@ -24,6 +50,20 @@ def env(key: str, default: Any) -> Any: ... def env(key: str, default=None): + """ + Get environment variable. + First checks thread-local user-specific environment, + then falls back to global environment. + """ + # If we have a user-specific environment path, try to reload it to get latest values + if hasattr(_thread_local, 'env_path') and os.path.exists(_thread_local.env_path): + # Temporarily load user-specific env to get the latest value + from dotenv import dotenv_values + user_env_values = dotenv_values(_thread_local.env_path) + if key in user_env_values: + return user_env_values[key] or default + + # Fall back to global environment return os.getenv(key, default) diff --git a/backend/app/controller/chat_controller.py b/backend/app/controller/chat_controller.py index e5c248198..45bafd41e 100644 --- a/backend/app/controller/chat_controller.py +++ b/backend/app/controller/chat_controller.py @@ -20,6 +20,7 @@ from app.service.task import ( create_task_lock, get_task_lock, ) +from app.component.environment import set_user_env_path router = APIRouter(tags=["chat"]) @@ -33,6 +34,9 @@ chat_logger = traceroot.get_logger('chat_controller') async def post(data: Chat, request: Request): chat_logger.info(f"Starting new chat session for task_id: {data.task_id}, user: {data.email}") task_lock = create_task_lock(data.task_id) + + # Set user-specific environment path for this thread + set_user_env_path(data.env_path) load_dotenv(dotenv_path=data.env_path) # logger.debug(f"start chat: {data.model_dump_json()}") diff --git a/backend/app/controller/task_controller.py b/backend/app/controller/task_controller.py index 7a8a034b0..f8f104f1b 100644 --- a/backend/app/controller/task_controller.py +++ b/backend/app/controller/task_controller.py @@ -15,6 +15,7 @@ from app.service.task import ( task_locks, ) import asyncio +from app.component.environment import set_user_env_path router = APIRouter(tags=["task"]) @@ -49,6 +50,8 @@ def take_control(id: str, data: TakeControl): @router.post("/task/{id}/add-agent", name="add new agent") def add_agent(id: str, data: NewAgent): + # Set user-specific environment path for this thread + set_user_env_path(data.env_path) load_dotenv(dotenv_path=data.env_path) asyncio.run(get_task_lock(id).put_queue(ActionNewAgent(**data.model_dump()))) return Response(status_code=204) From c0b6f11fd1d50db2ab756371ac6750c24c8c4d5c Mon Sep 17 00:00:00 2001 From: Sun Tao <2605127667@qq.com> Date: Fri, 12 Sep 2025 15:52:17 +0800 Subject: [PATCH 06/19] Update agent.py --- backend/app/utils/agent.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/backend/app/utils/agent.py b/backend/app/utils/agent.py index 5d542b2b6..5dc1f4a5a 100644 --- a/backend/app/utils/agent.py +++ b/backend/app/utils/agent.py @@ -1,5 +1,6 @@ import asyncio import json +import os import platform from threading import Event import traceback @@ -1438,7 +1439,17 @@ async def get_mcp_tools(mcp_server: McpServers): traceroot_logger.info(f"Getting MCP tools for {len(mcp_server['mcpServers'])} servers") if len(mcp_server["mcpServers"]) == 0: return [] - mcp_toolkit = MCPToolkit(config_dict={**mcp_server}, timeout=180) + + # Ensure unified auth directory for all mcp-remote servers to avoid re-authentication on each task + config_dict = {**mcp_server} + for server_config in config_dict["mcpServers"].values(): + if "env" not in server_config: + server_config["env"] = {} + # Set global auth directory to persist authentication across tasks + if "MCP_REMOTE_CONFIG_DIR" not in server_config["env"]: + server_config["env"]["MCP_REMOTE_CONFIG_DIR"] = env("MCP_REMOTE_CONFIG_DIR", os.path.expanduser("~/.mcp-auth")) + + mcp_toolkit = MCPToolkit(config_dict=config_dict, timeout=20) try: await mcp_toolkit.connect() traceroot_logger.info(f"Successfully connected to MCP toolkit with {len(mcp_server['mcpServers'])} servers") From ef94b777d1783bad0f4a6ef529bfc352b7c2ac92 Mon Sep 17 00:00:00 2001 From: a7m-1st Date: Fri, 12 Sep 2025 15:22:52 +0300 Subject: [PATCH 07/19] enhance: update mcp config to support frontend fields --- electron/main/utils/mcpConfig.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/electron/main/utils/mcpConfig.ts b/electron/main/utils/mcpConfig.ts index ca351f5c0..cf08775f5 100644 --- a/electron/main/utils/mcpConfig.ts +++ b/electron/main/utils/mcpConfig.ts @@ -8,6 +8,7 @@ const MCP_CONFIG_PATH = path.join(MCP_CONFIG_DIR, 'mcp.json'); type McpServerConfig = { command: string; args: string[]; + description?: string; env?: Record; } | { url: string; @@ -17,7 +18,7 @@ type McpServersConfig = { [name: string]: McpServerConfig; }; -type ConfigFile = { +export type ConfigFile = { mcpServers: McpServersConfig; }; From 024673ca63ec2dcb3a6ecc70d1e6be48f3ad42af Mon Sep 17 00:00:00 2001 From: a7m-1st Date: Fri, 12 Sep 2025 15:23:36 +0300 Subject: [PATCH 08/19] fix: loading section --- src/pages/Setting/MCP.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Setting/MCP.tsx b/src/pages/Setting/MCP.tsx index 23bf13732..34010d016 100644 --- a/src/pages/Setting/MCP.tsx +++ b/src/pages/Setting/MCP.tsx @@ -335,13 +335,13 @@ export default function SettingMCP() { {!isLoading && !error && items.length === 0 && (
No MCP servers
)} - + />} Date: Fri, 12 Sep 2025 15:24:35 +0300 Subject: [PATCH 09/19] fix: duplicate mcp server names --- src/pages/Setting/MCP.tsx | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/pages/Setting/MCP.tsx b/src/pages/Setting/MCP.tsx index 34010d016..40ab2b188 100644 --- a/src/pages/Setting/MCP.tsx +++ b/src/pages/Setting/MCP.tsx @@ -20,6 +20,7 @@ import { getProxyBaseURL } from "@/lib"; import { useAuthStore } from "@/store/authStore"; import { toast } from "sonner"; +import { ConfigFile } from "electron/main/utils/mcpConfig"; export default function SettingMCP() { const navigate = useNavigate(); @@ -236,9 +237,27 @@ export default function SettingMCP() { setInstalling(true); try { if (addType === "local") { - let data; + let data:ConfigFile; try { data = JSON.parse(localJson); + + // validate mcpServers structure + if (!data.mcpServers || typeof data.mcpServers !== "object") { + throw new Error("Invalid mcpServers"); + } + + // check for name conflicts with existing items + const serverNames = Object.keys(data.mcpServers); + const conflict = serverNames.find((name) => + items.some((d) => d.mcp_name === name) + ); + if (conflict) { + toast.error(`MCP server "${conflict}" already exists`, { + closeButton: true, + }); + setInstalling(false); + return; + } } catch (e) { toast.error("Invalid JSON", { closeButton: true }); setInstalling(false); From 6cd10fed292d5f14c3e63f3af50a2856ff0ba96f Mon Sep 17 00:00:00 2001 From: a7m-1st Date: Fri, 12 Sep 2025 15:26:47 +0300 Subject: [PATCH 10/19] fix: update mcp.json & disable name editing to avoid duplicates --- src/pages/Setting/MCP.tsx | 17 ++++++++++++++--- .../Setting/components/MCPConfigDialog.tsx | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/pages/Setting/MCP.tsx b/src/pages/Setting/MCP.tsx index 40ab2b188..f8c126646 100644 --- a/src/pages/Setting/MCP.tsx +++ b/src/pages/Setting/MCP.tsx @@ -196,13 +196,24 @@ export default function SettingMCP() { setSaving(true); setErrorMsg(null); try { - await proxyFetchPut(`/api/mcp/users/${showConfig.id}`, { + const mcpData = { mcp_name: configForm.mcp_name, mcp_desc: configForm.mcp_desc, command: configForm.command, args: arrayToArgsJson(configForm.argsArr), env: configForm.env, - }); + } + await proxyFetchPut(`/api/mcp/users/${showConfig.id}`, mcpData); + + if (window.ipcRenderer) { + window.ipcRenderer.invoke("mcp-update", mcpData.mcp_name, { + description: configForm.mcp_desc, + command: configForm.command, + args: arrayToArgsJson(configForm.argsArr), + env: configForm.env, + }) + } + setShowConfig(null); fetchList(); } catch (err: any) { @@ -236,7 +247,7 @@ export default function SettingMCP() { const handleInstall = async () => { setInstalling(true); try { - if (addType === "local") { + if (addType === "local") { let data:ConfigFile; try { data = JSON.parse(localJson); diff --git a/src/pages/Setting/components/MCPConfigDialog.tsx b/src/pages/Setting/components/MCPConfigDialog.tsx index d58a36e77..32b56fb02 100644 --- a/src/pages/Setting/components/MCPConfigDialog.tsx +++ b/src/pages/Setting/components/MCPConfigDialog.tsx @@ -34,7 +34,7 @@ export default function MCPConfigDialog({ open, form, mcp, onChange, onSave, onC
- onChange({ ...form, mcp_name: e.target.value })} disabled={loading} /> + onChange({ ...form, mcp_name: e.target.value })} disabled readOnly />
From 4879ca4a0c62579fe5113ce25e58ebbd25c35a7f Mon Sep 17 00:00:00 2001 From: a7m-1st Date: Fri, 12 Sep 2025 17:04:59 +0300 Subject: [PATCH 11/19] refactor: spacing --- src/pages/Setting/MCP.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Setting/MCP.tsx b/src/pages/Setting/MCP.tsx index f8c126646..ae917bbb3 100644 --- a/src/pages/Setting/MCP.tsx +++ b/src/pages/Setting/MCP.tsx @@ -247,7 +247,7 @@ export default function SettingMCP() { const handleInstall = async () => { setInstalling(true); try { - if (addType === "local") { + if (addType === "local") { let data:ConfigFile; try { data = JSON.parse(localJson); From 7afdd5d99c683923e58e476f607238bc428d77f4 Mon Sep 17 00:00:00 2001 From: Wendong-Fan Date: Mon, 15 Sep 2025 16:19:13 +0800 Subject: [PATCH 12/19] enhance: resolve issue where tasks from previous account kept running PR 332 --- electron/main/index.ts | 19 +++++++++++++++---- src/store/chatStore.ts | 36 +++++++++++++++++++++++------------- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/electron/main/index.ts b/electron/main/index.ts index f3022c8ff..dd0394ed0 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -313,11 +313,22 @@ function registerIpcHandlers() { ipcMain.handle('get-app-version', () => app.getVersion()); ipcMain.handle('get-backend-port', () => backendPort); ipcMain.handle('restart-backend', async () => { - if (backendPort) { - await cleanupPythonProcess(); - await checkAndStartBackend(); + try { + if (backendPort) { + log.info('Restarting backend service...'); + await cleanupPythonProcess(); + await checkAndStartBackend(); + log.info('Backend restart completed successfully'); + return { success: true }; + } else { + log.warn('No backend port found, starting fresh backend'); + await checkAndStartBackend(); + return { success: true }; + } + } catch (error) { + log.error('Failed to restart backend:', error); + return { success: false, error: String(error) }; } - return { success: true }; }); ipcMain.handle('get-system-language', getSystemLanguage); ipcMain.handle('is-fullscreen', () => win?.isFullScreen() || false); diff --git a/src/store/chatStore.ts b/src/store/chatStore.ts index 198322048..7ab3e6670 100644 --- a/src/store/chatStore.ts +++ b/src/store/chatStore.ts @@ -1638,20 +1638,30 @@ const chatStore = create()( clearTasks: () => { const { create } = get() console.log('clearTasks') - fetchDelete('/task/stop-all') - window.ipcRenderer.invoke('restart-backend').then((res) => { - console.log('restart-backend', res) - const newTaskId = create() - set((state) => ({ - ...state, - tasks: { - [newTaskId]: { - ...state.tasks[newTaskId], - }, - }, - })) - }) + // Fire and forget task stop, but ensure restart happens after it + fetchDelete('/task/stop-all') + .then(() => { + console.log('All tasks stopped successfully') + return window.ipcRenderer.invoke('restart-backend') + }) + .then((res) => { + console.log('restart-backend', res) + }) + .catch((error) => { + console.error('Error in clearTasks cleanup:', error) + }) + + // Immediately create new task to maintain UI responsiveness + const newTaskId = create() + set((state) => ({ + ...state, + tasks: { + [newTaskId]: { + ...state.tasks[newTaskId], + }, + }, + })) }, }) ); From f4a90e8c69934103c016d554a5d094170d694f08 Mon Sep 17 00:00:00 2001 From: Wendong-Fan Date: Mon, 15 Sep 2025 16:51:50 +0800 Subject: [PATCH 13/19] enhance: PR 335 browser not working due to no active page available --- electron/main/webview.ts | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/electron/main/webview.ts b/electron/main/webview.ts index 4ac0a7738..123d464d2 100644 --- a/electron/main/webview.ts +++ b/electron/main/webview.ts @@ -112,20 +112,23 @@ export class WebViewManager { } console.log(`Webview ${id} navigated to: ${navigationUrl}`) if (webViewInfo.isActive && webViewInfo.isShow && navigationUrl !== 'about:blank?use=0' && navigationUrl !== 'about:blank') { - console.log("did-navigate", id, url) - this.win?.webContents.send("url-updated", url); + console.log("did-navigate", id, navigationUrl) + this.win?.webContents.send("url-updated", navigationUrl); return } 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 newId = (Math.max(0, ...Array.from(this.webViews.keys()).map(Number)) || 0) + 2 - this.createWebview(newId.toString(), 'about:blank?use=0') - this.createWebview((newId + 1).toString(), 'about:blank?use=0') - this.createWebview((newId + 2).toString(), 'about:blank?use=0') - this.createWebview((newId + 3).toString(), 'about:blank?use=0') - this.createWebview((newId + 4).toString(), 'about:blank?use=0') + 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++) { + const nextId = (startId + i).toString() + this.createWebview(nextId, 'about:blank?use=0') + } } // setTimeout(() => { @@ -244,8 +247,12 @@ export class WebViewManager { } } - public distroy() { - // TODO: Destroy all webviews + public destroy() { + // Destroy all webviews + Array.from(this.webViews.keys()).forEach(id => { + this.destroyWebview(id) + }) + this.webViews.clear() } } From c2b9b16018a00290b18c6660682f2b7a8c14680d Mon Sep 17 00:00:00 2001 From: sw3205933776 <3205933776@qq.com> Date: Mon, 15 Sep 2025 17:59:55 +0800 Subject: [PATCH 14/19] refactor: remove /task/stop-all API request --- src/store/chatStore.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/store/chatStore.ts b/src/store/chatStore.ts index 7ab3e6670..64649dabe 100644 --- a/src/store/chatStore.ts +++ b/src/store/chatStore.ts @@ -1639,12 +1639,7 @@ const chatStore = create()( const { create } = get() console.log('clearTasks') - // Fire and forget task stop, but ensure restart happens after it - fetchDelete('/task/stop-all') - .then(() => { - console.log('All tasks stopped successfully') - return window.ipcRenderer.invoke('restart-backend') - }) + window.ipcRenderer.invoke('restart-backend') .then((res) => { console.log('restart-backend', res) }) @@ -1652,6 +1647,7 @@ const chatStore = create()( console.error('Error in clearTasks cleanup:', error) }) + // Immediately create new task to maintain UI responsiveness const newTaskId = create() set((state) => ({ From 9c5f7412f460a922c5b48db3ad2c6a550db9f670 Mon Sep 17 00:00:00 2001 From: a7m-1st Date: Mon, 15 Sep 2025 15:45:40 +0300 Subject: [PATCH 15/19] chore: map with unique keys & correct default config --- src/pages/Setting/MCP.tsx | 7 +------ src/pages/Setting/components/MCPList.tsx | 4 ++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/pages/Setting/MCP.tsx b/src/pages/Setting/MCP.tsx index ae917bbb3..c92a8d981 100644 --- a/src/pages/Setting/MCP.tsx +++ b/src/pages/Setting/MCP.tsx @@ -289,12 +289,7 @@ export default function SettingMCP() { } setShowAdd(false); setLocalJson(`{ - "mcp_id": 0, - "mcp_name": "", - "mcp_desc": "", - "command": "", - "args": "", - "env": {} + "mcpServers": {} }`); setRemoteName(""); setRemoteUrl(""); diff --git a/src/pages/Setting/components/MCPList.tsx b/src/pages/Setting/components/MCPList.tsx index 498109e62..04199e261 100644 --- a/src/pages/Setting/components/MCPList.tsx +++ b/src/pages/Setting/components/MCPList.tsx @@ -12,9 +12,9 @@ interface MCPListProps { export default function MCPList({ items, onSetting, onDelete, onSwitch, switchLoading }: MCPListProps) { return (
- {items.map(item => ( + {items.map((item, index) => ( Date: Mon, 15 Sep 2025 15:49:42 +0300 Subject: [PATCH 16/19] fix: parse args if string or array from server & handle edge cases --- electron/main/index.ts | 18 ++++++ electron/main/utils/mcpConfig.ts | 56 ++++++++++++++++++- .../Setting/components/MCPConfigDialog.tsx | 2 +- src/pages/Setting/components/utils.ts | 28 +++++++++- 4 files changed, 100 insertions(+), 4 deletions(-) diff --git a/electron/main/index.ts b/electron/main/index.ts index af7152835..822d8171e 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -516,6 +516,15 @@ function registerIpcHandlers() { // ==================== MCP manage handler ==================== ipcMain.handle('mcp-install', async (event, name, mcp) => { + // Convert args from JSON string to array if needed + if (mcp.args && typeof mcp.args === 'string') { + try { + mcp.args = JSON.parse(mcp.args); + } catch (e) { + // If parsing fails, split by comma as fallback + mcp.args = mcp.args.split(',').map((arg: string) => arg.trim()).filter((arg: string) => arg !== ''); + } + } addMcp(name, mcp); return { success: true }; }); @@ -526,6 +535,15 @@ function registerIpcHandlers() { }); ipcMain.handle('mcp-update', async (event, name, mcp) => { + // Convert args from JSON string to array if needed + if (mcp.args && typeof mcp.args === 'string') { + try { + mcp.args = JSON.parse(mcp.args); + } catch (e) { + // If parsing fails, split by comma as fallback + mcp.args = mcp.args.split(',').map((arg: string) => arg.trim()).filter((arg: string) => arg !== ''); + } + } updateMcp(name, mcp); return { success: true }; }); diff --git a/electron/main/utils/mcpConfig.ts b/electron/main/utils/mcpConfig.ts index cf08775f5..b3400cae9 100644 --- a/electron/main/utils/mcpConfig.ts +++ b/electron/main/utils/mcpConfig.ts @@ -43,6 +43,28 @@ export function readMcpConfig(): ConfigFile { if (!parsed.mcpServers || typeof parsed.mcpServers !== 'object') { return getDefaultConfig(); } + + // Normalize args field - ensure it's always an array + Object.keys(parsed.mcpServers).forEach(serverName => { + const server = parsed.mcpServers[serverName]; + if (server.args) { + const args = server.args as any; + if (typeof args === 'string') { + try { + // Try to parse as JSON string first + server.args = JSON.parse(args); + } catch (e) { + // If parsing fails, split by comma as fallback + server.args = args.split(',').map((arg: string) => arg.trim()).filter((arg: string) => arg !== ''); + } + } + // Ensure it's always an array of strings + if (Array.isArray(server.args)) { + server.args = server.args.map((arg: any) => String(arg)); + } + } + }); + return parsed; } catch (e) { return getDefaultConfig(); @@ -59,7 +81,22 @@ export function writeMcpConfig(config: ConfigFile): void { export function addMcp(name: string, mcp: McpServerConfig): void { const config = readMcpConfig(); if (!config.mcpServers[name]) { - config.mcpServers[name] = mcp; + // Ensure args is an array before adding + const normalizedMcp = { ...mcp }; + if ('args' in normalizedMcp && normalizedMcp.args) { + const args = normalizedMcp.args as any; + if (typeof args === 'string') { + try { + normalizedMcp.args = JSON.parse(args); + } catch (e) { + normalizedMcp.args = args.split(',').map((arg: string) => arg.trim()).filter((arg: string) => arg !== ''); + } + } + if (Array.isArray(normalizedMcp.args)) { + normalizedMcp.args = normalizedMcp.args.map((arg: any) => String(arg)); + } + } + config.mcpServers[name] = normalizedMcp; writeMcpConfig(config); } } @@ -75,6 +112,21 @@ export function removeMcp(name: string): void { export function updateMcp(name: string, mcp: McpServerConfig): void { const config = readMcpConfig(); - config.mcpServers[name] = mcp; + // Ensure args is an array before updating + const normalizedMcp = { ...mcp }; + if ('args' in normalizedMcp && normalizedMcp.args) { + const args = normalizedMcp.args as any; + if (typeof args === 'string') { + try { + normalizedMcp.args = JSON.parse(args); + } catch (e) { + normalizedMcp.args = args.split(',').map((arg: string) => arg.trim()).filter((arg: string) => arg !== ''); + } + } + if (Array.isArray(normalizedMcp.args)) { + normalizedMcp.args = normalizedMcp.args.map((arg: any) => String(arg)); + } + } + config.mcpServers[name] = normalizedMcp; writeMcpConfig(config); } \ No newline at end of file diff --git a/src/pages/Setting/components/MCPConfigDialog.tsx b/src/pages/Setting/components/MCPConfigDialog.tsx index 32b56fb02..4440f5231 100644 --- a/src/pages/Setting/components/MCPConfigDialog.tsx +++ b/src/pages/Setting/components/MCPConfigDialog.tsx @@ -45,7 +45,7 @@ export default function MCPConfigDialog({ open, form, mcp, onChange, onSave, onC onChange({ ...form, command: e.target.value })} disabled={loading} />
- +