diff --git a/electron/preload/index.ts b/electron/preload/index.ts
index 7ef37abf1..cd98efe1b 100644
--- a/electron/preload/index.ts
+++ b/electron/preload/index.ts
@@ -68,6 +68,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
checkAndInstallDepsOnUpdate: () => ipcRenderer.invoke('install-dependencies'),
checkInstallBrowser: () => ipcRenderer.invoke('check-install-browser'),
getInstallationStatus: () => ipcRenderer.invoke('get-installation-status'),
+ getBackendPort: () => ipcRenderer.invoke('get-backend-port'),
restartBackend: () => ipcRenderer.invoke('restart-backend'),
onInstallDependenciesStart: (callback: () => void) => {
ipcRenderer.on('install-dependencies-start', callback);
diff --git a/src/components/GroupedHistoryView/ProjectGroup.tsx b/src/components/GroupedHistoryView/ProjectGroup.tsx
index 1410ac3a9..041e2d33a 100644
--- a/src/components/GroupedHistoryView/ProjectGroup.tsx
+++ b/src/components/GroupedHistoryView/ProjectGroup.tsx
@@ -192,7 +192,7 @@ export default function ProjectGroup({
)} */}
- {!isOngoing && hasIssue && (
+ {/* {!isOngoing && hasIssue && (
- )}
+ )} */}
)} */}
- {!isOngoing && hasIssue && (
+ {/* {!isOngoing && hasIssue && (
{t("layout.issue") || "Issue"}
- )}
-
+ )} */}
+
{/* Menu button */}
diff --git a/src/hooks/useInstallationSetup.ts b/src/hooks/useInstallationSetup.ts
index b7bca2e3e..4654cd72f 100644
--- a/src/hooks/useInstallationSetup.ts
+++ b/src/hooks/useInstallationSetup.ts
@@ -1,4 +1,4 @@
-import { useEffect, useRef } from 'react';
+import { useEffect, useRef, useCallback } from 'react';
import { useInstallationStore } from '@/store/installationStore';
import { useAuthStore } from '@/store/authStore';
@@ -7,7 +7,7 @@ import { useAuthStore } from '@/store/authStore';
* This should be called once in your App component or Layout component
*/
export const useInstallationSetup = () => {
- const { initState, setInitState } = useAuthStore();
+ const { initState, setInitState, email } = useAuthStore();
const hasCheckedOnMount = useRef(false);
const installationCompleted = useRef(false);
@@ -19,6 +19,100 @@ export const useInstallationSetup = () => {
const setError = useInstallationStore(state => state.setError);
const setBackendError = useInstallationStore(state => state.setBackendError);
const setWaitingBackend = useInstallationStore(state => state.setWaitingBackend);
+ const needsBackendRestart = useInstallationStore(state => state.needsBackendRestart);
+ const setNeedsBackendRestart = useInstallationStore(state => state.setNeedsBackendRestart);
+
+ // Shared function to poll backend status
+ const startBackendPolling = useCallback(() => {
+ console.log('[useInstallationSetup] Starting backend polling');
+
+ // Immediately check backend status once
+ const checkBackendStatus = async () => {
+ try {
+ const backendPort = await window.electronAPI.getBackendPort();
+ if (backendPort && backendPort > 0) {
+ console.log('[useInstallationSetup] Backend immediately detected on port:', backendPort);
+
+ // Verify backend is actually responding
+ const response = await fetch(`http://localhost:${backendPort}/health`).catch(() => null);
+ if (response && response.ok) {
+ console.log('[useInstallationSetup] Backend health check passed immediately');
+ backendReady.current = true;
+ setSuccess();
+ setInitState('done');
+ setNeedsBackendRestart(false);
+ return true; // Backend is ready, no need to poll
+ }
+ }
+ } catch (error) {
+ console.log('[useInstallationSetup] Initial backend check failed:', error);
+ }
+ return false; // Backend not ready, need to poll
+ };
+
+ // Check immediately, then start polling if needed
+ checkBackendStatus().then((isReady) => {
+ if (isReady) {
+ console.log('[useInstallationSetup] Backend already ready, skipping polling');
+ return;
+ }
+
+ console.log('[useInstallationSetup] Backend not ready, starting polling');
+
+ // Poll backend status every 2 seconds to ensure we catch when it's ready
+ // This is a fallback in case the backend-ready event is missed
+ const pollInterval = setInterval(async () => {
+ try {
+ const backendPort = await window.electronAPI.getBackendPort();
+ if (backendPort && backendPort > 0) {
+ console.log('[useInstallationSetup] Backend poll detected ready on port:', backendPort);
+
+ // Verify backend is actually responding
+ const response = await fetch(`http://localhost:${backendPort}/health`).catch(() => null);
+ if (response && response.ok) {
+ console.log('[useInstallationSetup] Backend health check passed');
+ clearInterval(pollInterval);
+
+ if (!backendReady.current) {
+ backendReady.current = true;
+ setSuccess();
+ setInitState('done');
+ // Clear the flag after backend is ready
+ setNeedsBackendRestart(false);
+ }
+ }
+ }
+ } catch (error) {
+ console.log('[useInstallationSetup] Backend poll check failed:', error);
+ }
+ }, 2000);
+
+ // Clear polling after 30 seconds to prevent infinite polling
+ setTimeout(() => {
+ clearInterval(pollInterval);
+ }, 30000);
+ });
+ }, [setSuccess, setInitState, setNeedsBackendRestart]);
+
+ // Monitor for backend restart after logout
+ useEffect(() => {
+ // When user logs in after logout, needsBackendRestart will be true
+ if (needsBackendRestart && email !== null) {
+ console.log('[useInstallationSetup] Detected login after logout, waiting for backend restart');
+
+ // For account switching, tools are already installed, only backend needs restart
+ // So we mark installation as completed and only wait for backend
+ installationCompleted.current = true;
+ backendReady.current = false;
+
+ // Set to waiting-backend state
+ setWaitingBackend();
+
+ // Start polling for backend
+ startBackendPolling();
+ }
+ }, [needsBackendRestart, email, setWaitingBackend, startBackendPolling]);
+
useEffect(() => {
if (hasCheckedOnMount.current) {
@@ -36,6 +130,9 @@ export const useInstallationSetup = () => {
console.log('[useInstallationSetup] Tools already installed, waiting for backend');
installationCompleted.current = true;
setWaitingBackend();
+
+ // Start polling for backend when tools are already installed
+ startBackendPolling();
}
if (initState !== 'done') {
diff --git a/src/i18n/locales/ar/dashboard.json b/src/i18n/locales/ar/dashboard.json
index 11e1a9cce..3f3c000e8 100644
--- a/src/i18n/locales/ar/dashboard.json
+++ b/src/i18n/locales/ar/dashboard.json
@@ -22,5 +22,6 @@
"search-agent": "وكيل البحث",
"document-agent": "وكيل المستندات",
"multi-modal-agent": "وكيل متعدد الوسائط",
- "social-media-agent": "وكيل وسائل التواصل الاجتماعي"
+ "social-media-agent": "وكيل وسائل التواصل الاجتماعي",
+ "no-projects-found": "لا توجد مشاريع"
}
diff --git a/src/i18n/locales/de/dashboard.json b/src/i18n/locales/de/dashboard.json
index dc2921bb5..365be9711 100644
--- a/src/i18n/locales/de/dashboard.json
+++ b/src/i18n/locales/de/dashboard.json
@@ -22,5 +22,6 @@
"search-agent": "Such-Agent",
"document-agent": "Dokument-Agent",
"multi-modal-agent": "Multi-Modal-Agent",
- "social-media-agent": "Social-Media-Agent"
+ "social-media-agent": "Social-Media-Agent",
+ "no-projects-found": "Keine Projekte gefunden."
}
diff --git a/src/i18n/locales/en-us/dashboard.json b/src/i18n/locales/en-us/dashboard.json
index cc9fd130a..9b2e6cd17 100644
--- a/src/i18n/locales/en-us/dashboard.json
+++ b/src/i18n/locales/en-us/dashboard.json
@@ -22,5 +22,6 @@
"search-agent": "Search Agent",
"document-agent": "Document Agent",
"multi-modal-agent": "Multi Modal Agent",
- "social-media-agent": "Social Media Agent"
+ "social-media-agent": "Social Media Agent",
+ "no-projects-found": "No projects found."
}
diff --git a/src/i18n/locales/es/dashboard.json b/src/i18n/locales/es/dashboard.json
index fee7a535a..77c301f61 100644
--- a/src/i18n/locales/es/dashboard.json
+++ b/src/i18n/locales/es/dashboard.json
@@ -22,5 +22,6 @@
"search-agent": "Agente de Búsqueda",
"document-agent": "Agente de Documentos",
"multi-modal-agent": "Agente Multi Modal",
- "social-media-agent": "Agente de Redes Sociales"
+ "social-media-agent": "Agente de Redes Sociales",
+ "no-projects-found": "No se encontraron proyectos."
}
diff --git a/src/i18n/locales/fr/dashboard.json b/src/i18n/locales/fr/dashboard.json
index 5b3d290f0..447e5d94e 100644
--- a/src/i18n/locales/fr/dashboard.json
+++ b/src/i18n/locales/fr/dashboard.json
@@ -22,5 +22,6 @@
"search-agent": "Agent de Recherche",
"document-agent": "Agent de Documents",
"multi-modal-agent": "Agent Multi Modal",
- "social-media-agent": "Agent de Médias Sociaux"
+ "social-media-agent": "Agent de Médias Sociaux",
+ "no-projects-found": "Aucun projet trouvé."
}
diff --git a/src/i18n/locales/it/dashboard.json b/src/i18n/locales/it/dashboard.json
index f2392c27b..73b0fff5b 100644
--- a/src/i18n/locales/it/dashboard.json
+++ b/src/i18n/locales/it/dashboard.json
@@ -22,5 +22,6 @@
"search-agent": "Agente di Ricerca",
"document-agent": "Agente Documenti",
"multi-modal-agent": "Agente Multi Modale",
- "social-media-agent": "Agente Social Media"
+ "social-media-agent": "Agente Social Media",
+ "no-projects-found": "Nessun progetto trovato."
}
diff --git a/src/i18n/locales/ja/dashboard.json b/src/i18n/locales/ja/dashboard.json
index 587ab3a39..aba32f28b 100644
--- a/src/i18n/locales/ja/dashboard.json
+++ b/src/i18n/locales/ja/dashboard.json
@@ -22,5 +22,6 @@
"search-agent": "検索エージェント",
"document-agent": "ドキュメントエージェント",
"multi-modal-agent": "マルチモーダルエージェント",
- "social-media-agent": "ソーシャルメディアエージェント"
+ "social-media-agent": "ソーシャルメディアエージェント",
+ "no-projects-found": "プロジェクトが見つかりませんでした。"
}
diff --git a/src/i18n/locales/ko/dashboard.json b/src/i18n/locales/ko/dashboard.json
index 1645b6ccf..05092ff6d 100644
--- a/src/i18n/locales/ko/dashboard.json
+++ b/src/i18n/locales/ko/dashboard.json
@@ -22,5 +22,6 @@
"search-agent": "검색 에이전트",
"document-agent": "문서 에이전트",
"multi-modal-agent": "멀티모달 에이전트",
- "social-media-agent": "소셜미디어 에이전트"
+ "social-media-agent": "소셜미디어 에이전트",
+ "no-projects-found": "프로젝트를 찾을 수 없습니다."
}
diff --git a/src/i18n/locales/ru/dashboard.json b/src/i18n/locales/ru/dashboard.json
index 1ea357ca5..af8730d56 100644
--- a/src/i18n/locales/ru/dashboard.json
+++ b/src/i18n/locales/ru/dashboard.json
@@ -22,5 +22,6 @@
"search-agent": "Агент поиска",
"document-agent": "Агент документов",
"multi-modal-agent": "Мультимодальный агент",
- "social-media-agent": "Агент социальных сетей"
+ "social-media-agent": "Агент социальных сетей",
+ "no-projects-found": "Проекты не найдены"
}
diff --git a/src/i18n/locales/zh-Hans/dashboard.json b/src/i18n/locales/zh-Hans/dashboard.json
index 7b99594ac..8ab137037 100644
--- a/src/i18n/locales/zh-Hans/dashboard.json
+++ b/src/i18n/locales/zh-Hans/dashboard.json
@@ -22,5 +22,6 @@
"search-agent": "搜索智能体",
"document-agent": "文档智能体",
"multi-modal-agent": "多模态智能体",
- "social-media-agent": "社交媒体智能体"
+ "social-media-agent": "社交媒体智能体",
+ "no-projects-found": "没有找到项目"
}
diff --git a/src/i18n/locales/zh-Hant/dashboard.json b/src/i18n/locales/zh-Hant/dashboard.json
index 3e63d8d0e..a29288a7f 100644
--- a/src/i18n/locales/zh-Hant/dashboard.json
+++ b/src/i18n/locales/zh-Hant/dashboard.json
@@ -22,5 +22,6 @@
"search-agent": "搜尋智能體",
"document-agent": "文件智能體",
"multi-modal-agent": "多模態智能體",
- "social-media-agent": "社群媒體智能體"
+ "social-media-agent": "社群媒體智能體",
+ "no-projects-found": "沒有找到專案"
}
diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx
index 3bd0e87f1..1a04220b3 100644
--- a/src/pages/Login.tsx
+++ b/src/pages/Login.tsx
@@ -20,7 +20,7 @@ const HAS_STACK_KEYS = hasStackKeys();
let lock = false;
export default function Login() {
const app = HAS_STACK_KEYS ? useStackApp() : null;
- const { setAuth,setModelType } = useAuthStore();
+ const { setAuth, setModelType, setLocalProxyValue } = useAuthStore();
const navigate = useNavigate();
const location = useLocation();
const [hidePassword, setHidePassword] = useState(true);
@@ -103,7 +103,10 @@ export default function Login() {
}
setAuth({ email: formData.email, ...data });
- setModelType('cloud')
+ setModelType('cloud');
+ // Record VITE_USE_LOCAL_PROXY value at login
+ const localProxyValue = import.meta.env.VITE_USE_LOCAL_PROXY || null;
+ setLocalProxyValue(localProxyValue);
navigate("/");
} catch (error: any) {
console.error("Login failed:", error);
@@ -124,8 +127,11 @@ export default function Login() {
return;
}
console.log("data", data);
- setModelType('cloud')
+ setModelType('cloud');
setAuth({ email: formData.email, ...data });
+ // Record VITE_USE_LOCAL_PROXY value at login
+ const localProxyValue = import.meta.env.VITE_USE_LOCAL_PROXY || null;
+ setLocalProxyValue(localProxyValue);
navigate("/");
} catch (error: any) {
console.error("Login failed:", error);
diff --git a/src/pages/Setting/General.tsx b/src/pages/Setting/General.tsx
index 85fc89477..5afda8cb2 100644
--- a/src/pages/Setting/General.tsx
+++ b/src/pages/Setting/General.tsx
@@ -6,6 +6,7 @@ import light from "@/assets/light.png";
import dark from "@/assets/dark.png";
import transparent from "@/assets/transparent.png";
import { useAuthStore } from "@/store/authStore";
+import { useInstallationStore } from "@/store/installationStore";
import { useNavigate } from "react-router-dom";
import { proxyFetchPut, proxyFetchGet } from "@/api/http";
import { createRef, RefObject } from "react";
@@ -29,6 +30,10 @@ import useChatStoreAdapter from "@/hooks/useChatStoreAdapter";
export default function SettingGeneral() {
const { t } = useTranslation();
const authStore = useAuthStore();
+
+ const resetInstallation = useInstallationStore(state => state.reset);
+ const setNeedsBackendRestart = useInstallationStore(state => state.setNeedsBackendRestart);
+
const navigate = useNavigate();
const [isLoading, setIsLoading] = useState(false);
const setAppearance = authStore.setAppearance;
@@ -159,6 +164,10 @@ export default function SettingGeneral() {
size="xs"
onClick={() => {
chatStore.clearTasks();
+
+ resetInstallation(); // Reset installation state for new account
+ setNeedsBackendRestart(true); // Mark that backend is restarting
+
authStore.logout();
navigate("/login");
}}
diff --git a/src/pages/Setting/Models.tsx b/src/pages/Setting/Models.tsx
index b81360704..0a9f58113 100644
--- a/src/pages/Setting/Models.tsx
+++ b/src/pages/Setting/Models.tsx
@@ -820,7 +820,7 @@ const [errors, setErrors] = useState<
size="sm"
className="focus-none"
disabled={!canSwitch || loading === idx}
- onClick={() => handleVerify(idx)}
+ onClick={() => handleSwitch(idx, false)}
>
Default
@@ -830,7 +830,7 @@ const [errors, setErrors] = useState<
variant="ghost"
size="sm"
disabled={!canSwitch || loading === idx}
- onClick={() => handleVerify(idx)}
+ onClick={() => handleSwitch(idx, true)}
className={canSwitch ? "!text-text-label" : ""}
>
{!canSwitch ? "Not Configured" : "Set as Default"}
diff --git a/src/routers/index.tsx b/src/routers/index.tsx
index 193766bd5..223d0a52e 100644
--- a/src/routers/index.tsx
+++ b/src/routers/index.tsx
@@ -16,12 +16,28 @@ const ProtectedRoute = () => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [initialized, setInitialized] = useState(false);
- const authStore = useAuthStore();
+ const { token, localProxyValue, logout } = useAuthStore();
useEffect(() => {
- setIsAuthenticated(!!authStore.token);
+ // Check VITE_USE_LOCAL_PROXY value on app startup
+ if (token) {
+ const currentProxyValue = import.meta.env.VITE_USE_LOCAL_PROXY || null;
+ const storedProxyValue = localProxyValue;
+
+ // If stored value exists and differs from current, logout
+ if (storedProxyValue !== null && storedProxyValue !== currentProxyValue) {
+ console.warn('VITE_USE_LOCAL_PROXY value changed, logging out user');
+ logout();
+ setIsAuthenticated(false);
+ setLoading(false);
+ setInitialized(true);
+ return;
+ }
+ }
+
+ setIsAuthenticated(!!token);
setLoading(false);
setInitialized(true);
- }, [authStore.token]);
+ }, [token, localProxyValue, logout]);
if (loading || !initialized) {
return (
diff --git a/src/store/authStore.ts b/src/store/authStore.ts
index aa7d3ff57..3bdf08f5d 100644
--- a/src/store/authStore.ts
+++ b/src/store/authStore.ts
@@ -33,12 +33,16 @@ interface AuthState {
// shared token
share_token?: string | null;
+ // local proxy value recorded at login
+ localProxyValue?: string | null;
+
// worker list data
workerListData: { [key: string]: Agent[] };
- // auth related methods
- setAuth: (auth: AuthInfo) => void;
- logout: () => void;
+ // auth related methods
+ setAuth: (auth: AuthInfo) => void;
+ logout: () => void;
+ setLocalProxyValue: (value: string | null) => void;
// set related methods
setAppearance: (appearance: string) => void;
@@ -69,18 +73,21 @@ const authStore = create()(
cloud_model_type: 'gpt-4.1',
initState: 'permissions',
share_token: null,
+ localProxyValue: null,
workerListData: {},
// auth related methods
setAuth: ({ token, username, email, user_id }) =>
set({ token, username, email, user_id }),
-
- logout: () =>
- set({
- token: null,
- username: null,
- email: null,
- user_id: null
+
+ logout: () =>
+ set({
+ token: null,
+ username: null,
+ email: null,
+ user_id: null,
+ initState: 'carousel',
+ localProxyValue: null
}),
// set related methods
@@ -99,6 +106,8 @@ const authStore = create()(
setIsFirstLaunch: (isFirstLaunch) => set({ isFirstLaunch }),
+ setLocalProxyValue: (value) => set({ localProxyValue: value }),
+
// worker related methods
setWorkerList: (workerList) => {
const { email } = get();
@@ -152,6 +161,7 @@ const authStore = create()(
cloud_model_type: state.cloud_model_type,
initState: state.initState,
isFirstLaunch: state.isFirstLaunch,
+ localProxyValue: state.localProxyValue,
workerListData: state.workerListData,
}),
}
diff --git a/src/store/chatStore.ts b/src/store/chatStore.ts
index 83fd79a8e..e5cf51d4d 100644
--- a/src/store/chatStore.ts
+++ b/src/store/chatStore.ts
@@ -534,7 +534,7 @@ const chatStore = (initial?: Partial) => createStore()(
api_key: apiModel.api_key,
api_url: apiModel.api_url,
extra_params: apiModel.extra_params,
- installed_mcp: mcpLocal,
+ installed_mcp: [],
language: systemLanguage,
allow_local_system: true,
attaches: (messageAttaches || targetChatStore.getState().tasks[newTaskId]?.attaches || []).map(f => f.filePath),
diff --git a/src/store/installationStore.ts b/src/store/installationStore.ts
index d5e9e9d15..4015add41 100644
--- a/src/store/installationStore.ts
+++ b/src/store/installationStore.ts
@@ -27,6 +27,7 @@ interface InstallationStoreState {
error?: string;
backendError?: string; // Separate error for backend startup failures
isVisible: boolean;
+ needsBackendRestart: boolean; // Flag to indicate backend is restarting after logout
// Actions
startInstallation: () => void;
@@ -35,6 +36,7 @@ interface InstallationStoreState {
setError: (error: string) => void;
setBackendError: (error: string) => void;
setWaitingBackend: () => void;
+ setNeedsBackendRestart: (needs: boolean) => void;
retryInstallation: () => void;
retryBackend: () => Promise;
completeSetup: () => void;
@@ -55,6 +57,7 @@ const initialState = {
error: undefined,
backendError: undefined,
isVisible: false,
+ needsBackendRestart: false,
};
// Create the installation store
@@ -110,6 +113,11 @@ export const useInstallationStore = create()(
isVisible: true,
}),
+ setNeedsBackendRestart: (needs: boolean) =>
+ set({
+ needsBackendRestart: needs,
+ }),
+
setBackendError: (error: string) =>
set({
backendError: error,
diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts
index 116a06a8f..f604c697e 100644
--- a/src/types/electron.d.ts
+++ b/src/types/electron.d.ts
@@ -44,7 +44,13 @@ interface ElectronAPI {
envWrite: (email: string, kv: { key: string, value: string }) => Promise;
envRemove: (email: string, key: string) => Promise;
getEnvPath: (email: string) => Promise;
- executeCommand: (command: string,email:string) => Promise<{ success: boolean; stdout?: string; stderr?: string; error?: string }>;
+ executeCommand: (command: string, email: string) => Promise<{ success: boolean; stdout?: string; stderr?: string; error?: string }>;
+ readFile: (filePath: string) => Promise;
+ readFileAsDataUrl: (path: string) => Promise;
+ deleteFolder: (email: string) => Promise;
+ getMcpConfigPath: (email: string) => Promise;
+ uploadLog: (email: string, taskId: string, baseUrl: string, token: string) => Promise;
+ startBrowserImport: (args?: any) => Promise;
checkAndInstallDepsOnUpdate: () => Promise<{ success: boolean; error?: string }>;
checkInstallBrowser: () => Promise<{ data:any[] }>;
getInstallationStatus: () => Promise<{
@@ -55,16 +61,18 @@ interface ElectronAPI {
timestamp?: number;
error?: string
}>;
+ getBackendPort: () => Promise;
restartBackend: () => Promise<{ success: boolean; error?: string }>;
onInstallDependenciesStart: (callback: () => void) => void;
onInstallDependenciesLog: (callback: (data: { type: string; data: string }) => void) => void;
onInstallDependenciesComplete: (callback: (data: { success: boolean; code?: number; error?: string }) => void) => void;
- onUpdateNotification: (callback: (data: {
- type: string;
- currentVersion: string;
- previousVersion: string;
- reason: string;
+ onUpdateNotification: (callback: (data: {
+ type: string;
+ currentVersion: string;
+ previousVersion: string;
+ reason: string;
}) => void) => void;
+ onBackendReady: (callback: (data: { success: boolean; port?: number; error?: string }) => void) => void;
removeAllListeners: (channel: string) => void;
getEmailFolderPath: (email: string) => Promise<{
MCP_REMOTE_CONFIG_DIR: string;