-
+
(null);
const titlebarRef = useRef(null);
const [platform, setPlatform] = useState('');
- const validateEmail = (email: string) => {
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
- return emailRegex.test(email);
- };
-
- const validateForm = () => {
- const newErrors = {
- email: '',
- password: '',
- };
-
- if (!formData.email) {
- newErrors.email = t('layout.please-enter-email-address');
- } else if (!validateEmail(formData.email)) {
- newErrors.email = t('layout.please-enter-a-valid-email-address');
- }
-
- if (!formData.password) {
- newErrors.password = t('layout.please-enter-password');
- } else if (formData.password.length < 8) {
- newErrors.password = t('layout.password-must-be-at-least-8-characters');
- }
-
- setErrors(newErrors);
- return !newErrors.email && !newErrors.password;
- };
-
const getLoginErrorMessage = useCallback(
(data: any) => {
if (!data || typeof data !== 'object' || typeof data.code !== 'number') {
@@ -122,37 +89,12 @@ export default function Login() {
[t]
);
- const handleInputChange = (field: string, value: string) => {
- setFormData((prev) => ({
- ...prev,
- [field]: value,
- }));
-
- if (errors[field as keyof typeof errors]) {
- setErrors((prev) => ({
- ...prev,
- [field]: '',
- }));
- }
-
- if (generalError) {
- setGeneralError('');
- }
- };
-
- //
- const handleLogin = async () => {
- if (!validateForm()) {
- return;
- }
-
+ // Auto login for local mode - calls /api/auto-login
+ const handleAutoLogin = async () => {
setGeneralError('');
setIsLoading(true);
try {
- const data = await proxyFetchPost('/api/login', {
- email: formData.email,
- password: formData.password,
- });
+ const data = await proxyFetchPost('/api/auto-login', {});
const errorMessage = getLoginErrorMessage(data);
if (errorMessage) {
@@ -160,22 +102,21 @@ export default function Login() {
return;
}
- setAuth({ email: formData.email, ...data });
- setModelType('cloud');
- // Record VITE_USE_LOCAL_PROXY value at login
- const localProxyValue = import.meta.env.VITE_USE_LOCAL_PROXY || null;
- setLocalProxyValue(localProxyValue);
+ setAuth({ email: data.email, ...data });
+ setLocalProxyValue(import.meta.env.VITE_USE_LOCAL_PROXY || null);
+ setModelType('custom');
+ setInitState('done');
+ setIsFirstLaunch(false);
navigate('/');
} catch (error: any) {
- console.error('Login failed:', error);
- setGeneralError(
- t('layout.login-failed-please-check-your-email-and-password')
- );
+ console.error('Auto login failed:', error);
+ setGeneralError(t('layout.login-failed-please-try-again'));
} finally {
setIsLoading(false);
}
};
+ // Hybrid/app mode: handle Stack Auth callback (reuse existing OAuth flow)
const handleLoginByStack = useCallback(
async (token: string) => {
try {
@@ -191,10 +132,8 @@ export default function Login() {
setGeneralError(errorMessage);
return;
}
- console.log('data', data);
setModelType('cloud');
- setAuth({ email: formData.email, ...data });
- // Record VITE_USE_LOCAL_PROXY value at login
+ setAuth({ email: data.email, ...data });
const localProxyValue = import.meta.env.VITE_USE_LOCAL_PROXY || null;
setLocalProxyValue(localProxyValue);
navigate('/');
@@ -208,7 +147,6 @@ export default function Login() {
}
},
[
- formData.email,
navigate,
setAuth,
setModelType,
@@ -220,33 +158,10 @@ export default function Login() {
]
);
- const handleReloadBtn = async (type: string) => {
- if (!app) {
- console.error('Stack app not initialized');
- return;
- }
- console.log('handleReloadBtn1', type);
- const cookies = document.cookie.split('; ');
- cookies.forEach((cookie) => {
- const [name] = cookie.split('=');
- if (name.startsWith('stack-oauth-outer-')) {
- document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/`;
- }
- });
- console.log('handleReloadBtn2', type);
- await app.signInWithOAuth(type);
- };
-
const handleGetToken = useCallback(
async (code: string) => {
const code_verifier = localStorage.getItem('stack-oauth-outer-');
const formData = new URLSearchParams();
- console.log(
- 'import.meta.env.PROD',
- import.meta.env.PROD
- ? `${import.meta.env.VITE_BASE_URL}/api/redirect/callback`
- : `${import.meta.env.VITE_PROXY_URL}/api/redirect/callback`
- );
formData.append(
'redirect_uri',
import.meta.env.PROD
@@ -273,7 +188,7 @@ export default function Login() {
body: formData,
}
);
- const data = await res.json(); // parse response data
+ const data = await res.json();
return data.access_token;
} catch (error) {
console.error(error);
@@ -298,6 +213,44 @@ export default function Login() {
[location.pathname, handleLoginByStack, handleGetToken, setIsLoading]
);
+ // Listen for direct token callback from Electron (eigent.ai login redirect)
+ useEffect(() => {
+ const handleTokenReceived = async (_event: any, token: string) => {
+ if (!token) return;
+ setIsLoading(true);
+ // Temporarily set token so proxyFetchGet can use it for auth
+ setAuth({ email: '', token, username: '', user_id: 0 });
+ setLocalProxyValue(import.meta.env.VITE_USE_LOCAL_PROXY || null);
+ try {
+ const userInfo = await proxyFetchGet('/api/user');
+ if (userInfo && userInfo.email) {
+ setAuth({
+ token,
+ email: userInfo.email,
+ username:
+ userInfo.username ||
+ userInfo.nickname ||
+ userInfo.fullname ||
+ userInfo.email?.split('@')[0] ||
+ '',
+ user_id:
+ userInfo.id || JSON.parse(atob(token.split('.')[1])).id || 0,
+ });
+ }
+ } catch (e) {
+ console.error('Failed to fetch user info:', e);
+ }
+ navigate('/');
+ };
+
+ window.ipcRenderer?.on('auth-token-received', handleTokenReceived);
+
+ return () => {
+ window.ipcRenderer?.off('auth-token-received', handleTokenReceived);
+ };
+ }, [setAuth, setLocalProxyValue, navigate]);
+
+ // Listen for auth code callback from Electron (Stack Auth OAuth flow)
useEffect(() => {
window.ipcRenderer?.on('auth-code-received', handleAuthCode);
@@ -318,7 +271,6 @@ export default function Login() {
// Handle before-close event for login page
useEffect(() => {
const handleBeforeClose = () => {
- // On login page, always close directly without confirmation
window.electronAPI.closeWindow(true);
};
@@ -329,6 +281,91 @@ export default function Login() {
};
}, []);
+ // Hybrid/app mode: prepare auth callback URL on mount (don't auto-open browser)
+ useEffect(() => {
+ if (IS_LOCAL_MODE) return;
+
+ const prepareCallbackUrl = async () => {
+ let cbUrl: string;
+ if (import.meta.env.PROD) {
+ cbUrl = 'eigent://auth/callback';
+ } else {
+ cbUrl = 'eigent://auth/callback';
+ try {
+ const url = await window.ipcRenderer?.invoke('get-auth-callback-url');
+ if (url) cbUrl = url;
+ } catch (e) {
+ // Fallback to eigent:// protocol
+ }
+ }
+ setCallbackUrl(cbUrl);
+ };
+
+ prepareCallbackUrl();
+ }, []);
+
+ // Render local mode: "Start Eigent" button only
+ const renderLocalMode = () => (
+
+

+
+ Eigent
+
+ {generalError && (
+
+ {generalError}
+
+ )}
+
+
+ );
+
+ // Render hybrid/app mode: waiting for external login callback
+ const renderHybridMode = () => (
+
+

+
+ {t('layout.login')}
+
+ {isLoading && (
+
+ {t('layout.logging-in')}...
+
+ )}
+
+
+ );
+
return (
{/* Titlebar with drag region and window controls */}
@@ -373,119 +410,7 @@ export default function Login() {
backgroundPosition: 'center',
}}
>
-
-

-
-
- {t('layout.login')}
-
-
-
- {HAS_STACK_KEYS && (
-
-
-
-
- )}
- {HAS_STACK_KEYS && (
-
- {t('layout.or')}
-
- )}
-
- {generalError && (
-
- {generalError}
-
- )}
-
-
-
-
+ {IS_LOCAL_MODE ? renderLocalMode() : renderHybridMode()}
diff --git a/src/pages/SignUp.tsx b/src/pages/SignUp.tsx
index 2b1194fa..e3f38d52 100644
--- a/src/pages/SignUp.tsx
+++ b/src/pages/SignUp.tsx
@@ -32,6 +32,7 @@ import { hasStackKeys } from '@/lib';
import { useTranslation } from 'react-i18next';
const HAS_STACK_KEYS = hasStackKeys();
+const IS_LOCAL_MODE = import.meta.env.VITE_USE_LOCAL_PROXY === 'true';
let lock = false;
export default function SignUp() {
// Always call hooks unconditionally - React Hooks must be called in the same order
@@ -40,6 +41,25 @@ export default function SignUp() {
const { setAuth, initState: _initState } = useAuthStore();
const navigate = useNavigate();
const location = useLocation();
+
+ // Local mode: no signup needed, redirect to home
+ useEffect(() => {
+ if (IS_LOCAL_MODE) {
+ navigate('/', { replace: true });
+ }
+ }, [navigate]);
+
+ // Hybrid/app mode without Stack keys: redirect to external signup
+ useEffect(() => {
+ if (!IS_LOCAL_MODE && !HAS_STACK_KEYS) {
+ window.open(
+ 'https://www.eigent.ai/signup',
+ '_blank',
+ 'noopener,noreferrer'
+ );
+ navigate('/login', { replace: true });
+ }
+ }, [navigate]);
const [hidePassword, setHidePassword] = useState(true);
const { t } = useTranslation();
const [formData, setFormData] = useState({
diff --git a/src/routers/index.tsx b/src/routers/index.tsx
index 301a8618..f7e283ea 100644
--- a/src/routers/index.tsx
+++ b/src/routers/index.tsx
@@ -12,6 +12,7 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+import { proxyFetchPost } from '@/api/http';
import { useAuthStore } from '@/store/authStore';
import { lazy, useEffect, useReducer } from 'react';
import { Navigate, Outlet, Route, Routes } from 'react-router-dom';
@@ -24,6 +25,8 @@ const Home = lazy(() => import('@/pages/Home'));
const History = lazy(() => import('@/pages/History'));
const NotFound = lazy(() => import('@/pages/NotFound'));
+const IS_LOCAL_MODE = import.meta.env.VITE_USE_LOCAL_PROXY === 'true';
+
interface AuthState {
loading: boolean;
isAuthenticated: boolean;
@@ -61,7 +64,16 @@ const ProtectedRoute = () => {
initialized: false,
});
- const { token, localProxyValue, logout } = useAuthStore();
+ const {
+ token,
+ localProxyValue,
+ logout,
+ setAuth,
+ setLocalProxyValue,
+ setInitState,
+ setIsFirstLaunch,
+ setModelType,
+ } = useAuthStore();
useEffect(() => {
// Check VITE_USE_LOCAL_PROXY value on app startup
if (token) {
@@ -77,8 +89,38 @@ const ProtectedRoute = () => {
}
}
+ // Local mode: auto-login when no token
+ if (IS_LOCAL_MODE && !token) {
+ proxyFetchPost('/api/auto-login', {})
+ .then((data) => {
+ if (data && data.token) {
+ setAuth({ email: data.email, ...data });
+ setLocalProxyValue(import.meta.env.VITE_USE_LOCAL_PROXY || null);
+ setModelType('custom');
+ setInitState('done');
+ setIsFirstLaunch(false);
+ dispatch({
+ type: 'INITIALIZE',
+ payload: { isAuthenticated: true },
+ });
+ } else {
+ dispatch({
+ type: 'INITIALIZE',
+ payload: { isAuthenticated: false },
+ });
+ }
+ })
+ .catch(() => {
+ dispatch({
+ type: 'INITIALIZE',
+ payload: { isAuthenticated: false },
+ });
+ });
+ return;
+ }
+
dispatch({ type: 'INITIALIZE', payload: { isAuthenticated: !!token } });
- }, [token, localProxyValue, logout]);
+ }, [token, localProxyValue, logout, setAuth, setLocalProxyValue]);
if (state.loading || !state.initialized) {
return (