Fix/multi bug 11 21 (#751)

This commit is contained in:
Wendong-Fan 2025-11-21 18:09:09 +08:00 committed by GitHub
commit 8d01497530
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 209 additions and 43 deletions

View file

@ -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);

View file

@ -192,7 +192,7 @@ export default function ProjectGroup({
</motion.div>
)} */}
{!isOngoing && hasIssue && (
{/* {!isOngoing && hasIssue && (
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
@ -202,7 +202,7 @@ export default function ProjectGroup({
{t("layout.issue") || "Issue"}
</Tag>
</motion.div>
)}
)} */}
</div>
</div>
<TooltipSimple
@ -353,12 +353,12 @@ export default function ProjectGroup({
</Tag>
)} */}
{!isOngoing && hasIssue && (
{/* {!isOngoing && hasIssue && (
<Tag variant="warning" size="sm">
{t("layout.issue") || "Issue"}
</Tag>
)}
)} */}
{/* Menu button */}
<DropdownMenu>
<DropdownMenuTrigger asChild>

View file

@ -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') {

View file

@ -22,5 +22,6 @@
"search-agent": "وكيل البحث",
"document-agent": "وكيل المستندات",
"multi-modal-agent": "وكيل متعدد الوسائط",
"social-media-agent": "وكيل وسائل التواصل الاجتماعي"
"social-media-agent": "وكيل وسائل التواصل الاجتماعي",
"no-projects-found": "لا توجد مشاريع"
}

View file

@ -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."
}

View file

@ -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."
}

View file

@ -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."
}

View file

@ -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é."
}

View file

@ -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."
}

View file

@ -22,5 +22,6 @@
"search-agent": "検索エージェント",
"document-agent": "ドキュメントエージェント",
"multi-modal-agent": "マルチモーダルエージェント",
"social-media-agent": "ソーシャルメディアエージェント"
"social-media-agent": "ソーシャルメディアエージェント",
"no-projects-found": "プロジェクトが見つかりませんでした。"
}

View file

@ -22,5 +22,6 @@
"search-agent": "검색 에이전트",
"document-agent": "문서 에이전트",
"multi-modal-agent": "멀티모달 에이전트",
"social-media-agent": "소셜미디어 에이전트"
"social-media-agent": "소셜미디어 에이전트",
"no-projects-found": "프로젝트를 찾을 수 없습니다."
}

View file

@ -22,5 +22,6 @@
"search-agent": "Агент поиска",
"document-agent": "Агент документов",
"multi-modal-agent": "Мультимодальный агент",
"social-media-agent": "Агент социальных сетей"
"social-media-agent": "Агент социальных сетей",
"no-projects-found": "Проекты не найдены"
}

View file

@ -22,5 +22,6 @@
"search-agent": "搜索智能体",
"document-agent": "文档智能体",
"multi-modal-agent": "多模态智能体",
"social-media-agent": "社交媒体智能体"
"social-media-agent": "社交媒体智能体",
"no-projects-found": "没有找到项目"
}

View file

@ -22,5 +22,6 @@
"search-agent": "搜尋智能體",
"document-agent": "文件智能體",
"multi-modal-agent": "多模態智能體",
"social-media-agent": "社群媒體智能體"
"social-media-agent": "社群媒體智能體",
"no-projects-found": "沒有找到專案"
}

View file

@ -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);

View file

@ -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");
}}

View file

@ -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
<Check />
@ -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"}

View file

@ -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 (

View file

@ -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<AuthState>()(
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<AuthState>()(
setIsFirstLaunch: (isFirstLaunch) => set({ isFirstLaunch }),
setLocalProxyValue: (value) => set({ localProxyValue: value }),
// worker related methods
setWorkerList: (workerList) => {
const { email } = get();
@ -152,6 +161,7 @@ const authStore = create<AuthState>()(
cloud_model_type: state.cloud_model_type,
initState: state.initState,
isFirstLaunch: state.isFirstLaunch,
localProxyValue: state.localProxyValue,
workerListData: state.workerListData,
}),
}

View file

@ -534,7 +534,7 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
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),

View file

@ -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<void>;
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<InstallationStoreState>()(
isVisible: true,
}),
setNeedsBackendRestart: (needs: boolean) =>
set({
needsBackendRestart: needs,
}),
setBackendError: (error: string) =>
set({
backendError: error,

View file

@ -44,7 +44,13 @@ interface ElectronAPI {
envWrite: (email: string, kv: { key: string, value: string }) => Promise<any>;
envRemove: (email: string, key: string) => Promise<any>;
getEnvPath: (email: string) => Promise<string>;
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<any>;
readFileAsDataUrl: (path: string) => Promise<string>;
deleteFolder: (email: string) => Promise<any>;
getMcpConfigPath: (email: string) => Promise<string>;
uploadLog: (email: string, taskId: string, baseUrl: string, token: string) => Promise<any>;
startBrowserImport: (args?: any) => Promise<any>;
checkAndInstallDepsOnUpdate: () => Promise<{ success: boolean; error?: string }>;
checkInstallBrowser: () => Promise<{ data:any[] }>;
getInstallationStatus: () => Promise<{
@ -55,16 +61,18 @@ interface ElectronAPI {
timestamp?: number;
error?: string
}>;
getBackendPort: () => Promise<number | null>;
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;