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;