mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-05-19 07:59:39 +00:00
Merge branch 'main' into add_lark
This commit is contained in:
commit
ceb556f111
13 changed files with 1096 additions and 729 deletions
1
.npmrc
1
.npmrc
|
|
@ -3,4 +3,5 @@
|
|||
shamefully-hoist=true
|
||||
|
||||
# For China 🇨🇳 developers
|
||||
# registry=https://registry.npmmirror.com
|
||||
# electron_mirror=https://npmmirror.com/mirrors/electron/
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ Built on [CAMEL-AI][camel-site]'s acclaimed open-source project, our system intr
|
|||
|
||||
<br/>
|
||||
|
||||
[![][image-join-us]][join-us]
|
||||
|
||||
<details>
|
||||
<summary><kbd>Table of contents</kbd></summary>
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
3.10.16
|
||||
3.10.15
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -112,6 +112,7 @@
|
|||
"autoprefixer": "^10.4.20",
|
||||
"electron": "^33.2.0",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-devtools-installer": "^4.0.0",
|
||||
"i18next": "^25.4.2",
|
||||
"jsdom": "^26.1.0",
|
||||
"postcss": "^8.4.49",
|
||||
|
|
@ -133,4 +134,4 @@
|
|||
"engines": {
|
||||
"node": ">=18.0.0 <23.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
debug=false
|
||||
url_prefix=/api
|
||||
secret_key=postgres
|
||||
database_url=postgresql://postgres:postgres@localhost:5432/postgres
|
||||
# Chat Share Secret Key
|
||||
database_url=postgresql://postgres:123456@localhost:5432/postgres
|
||||
# Chat Share Secret Key
|
||||
CHAT_SHARE_SECRET_KEY=put-your-secret-key-here
|
||||
CHAT_SHARE_SALT=put-your-encode-salt-here
|
||||
|
||||
|
|
|
|||
|
|
@ -21,10 +21,15 @@
|
|||
---
|
||||
|
||||
### 快速开始(Docker 推荐)
|
||||
前置要求:已安装 Docker Desktop。
|
||||
|
||||
#### 前置要求
|
||||
- **Docker Desktop**:已安装并运行
|
||||
- **Python**:3.10.*(推荐使用 3.10.15)
|
||||
- **Node.js**:>=18.0.0 <23.0.0
|
||||
|
||||
#### 启动步骤
|
||||
|
||||
1) 启动服务
|
||||
-
|
||||
```bash
|
||||
cd server
|
||||
# 复制 .env.example 为 .env(或者按照.env.example的格式创建.env)
|
||||
|
|
@ -83,8 +88,11 @@ docker logs -f eigent_postgres | cat
|
|||
# 1) 停止容器中的 API 服务,仅保留数据库
|
||||
docker stop eigent_api
|
||||
|
||||
# 2) 本地启动(需提供数据库连接串)
|
||||
# 2) 初始化数据库(首次或数据库结构变更时)
|
||||
cd server
|
||||
uv run alembic upgrade head
|
||||
|
||||
# 3) 本地启动(需提供数据库连接串)
|
||||
# 方式 A:在当前 shell 导出环境变量
|
||||
export database_url=postgresql://postgres:123456@localhost:5432/eigent
|
||||
uv run uvicorn main:api --reload --port 3001 --host 0.0.0.0
|
||||
|
|
@ -107,4 +115,4 @@ uv run pybabel init -i messages.pot -d lang -l zh_CN
|
|||
uv run pybabel compile -d lang -l zh_CN
|
||||
```
|
||||
|
||||
如需完全离线环境,请仅使用本地模型与本地 MCP 服务器,并避免配置任何外部 Provider 与远程 MCP 地址。
|
||||
如需完全离线环境,请仅使用本地模型与本地 MCP 服务器,并避免配置任何外部 Provider 与远程 MCP 地址。
|
||||
|
|
|
|||
|
|
@ -21,7 +21,13 @@ Note: All the above data is stored in the local PostgreSQL volume in Docker (see
|
|||
---
|
||||
|
||||
### Quick Start (Docker)
|
||||
Prerequisite: Docker Desktop installed.
|
||||
|
||||
#### Prerequisites
|
||||
- **Docker Desktop**: Installed and running
|
||||
- **Python**: 3.10.* (3.10.15 recommended)
|
||||
- **Node.js**: >=18.0.0 <23.0.0
|
||||
|
||||
#### Setup Steps
|
||||
|
||||
1) Start services
|
||||
```bash
|
||||
|
|
@ -81,8 +87,11 @@ You can run the API locally with hot-reload while keeping the database in Docker
|
|||
# Stop API in container, keep DB
|
||||
docker stop eigent_api
|
||||
|
||||
# Run locally (provide DB connection string)
|
||||
# Initialize database (first-time or when DB schema changes)
|
||||
cd server
|
||||
uv run alembic upgrade head
|
||||
|
||||
# Run locally (provide DB connection string)
|
||||
export database_url=postgresql://postgres:123456@localhost:5432/eigent
|
||||
uv run uvicorn main:api --reload --port 3001 --host 0.0.0.0
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi import APIRouter, Depends, HTTPException, Form
|
||||
from fastapi_babel import _
|
||||
from sqlmodel import Session
|
||||
from app.component import code
|
||||
|
|
@ -7,7 +7,13 @@ from app.component.database import session
|
|||
from app.component.encrypt import password_verify
|
||||
from app.component.stack_auth import StackAuth
|
||||
from app.exception.exception import UserException
|
||||
from app.model.user.user import LoginByPasswordIn, LoginResponse, Status, User, RegisterIn
|
||||
from app.model.user.user import (
|
||||
LoginByPasswordIn,
|
||||
LoginResponse,
|
||||
Status,
|
||||
User,
|
||||
RegisterIn,
|
||||
)
|
||||
from app.component.environment import env
|
||||
from utils import traceroot_wrapper as traceroot
|
||||
|
||||
|
|
@ -19,25 +25,62 @@ router = APIRouter(tags=["Login/Registration"])
|
|||
|
||||
@router.post("/login", name="login by email or password")
|
||||
@traceroot.trace()
|
||||
async def by_password(data: LoginByPasswordIn, session: Session = Depends(session)) -> LoginResponse:
|
||||
async def by_password(
|
||||
data: LoginByPasswordIn, session: Session = Depends(session)
|
||||
) -> LoginResponse:
|
||||
"""
|
||||
User login with email and password
|
||||
"""
|
||||
email = data.email
|
||||
user = User.by(User.email == email, s=session).one_or_none()
|
||||
|
||||
|
||||
if not user:
|
||||
logger.warning("Login failed: user not found", extra={"email": email})
|
||||
raise UserException(code.password, _("Account or password error"))
|
||||
|
||||
|
||||
if not password_verify(data.password, user.password):
|
||||
logger.warning("Login failed: invalid password", extra={"user_id": user.id, "email": email})
|
||||
logger.warning(
|
||||
"Login failed: invalid password", extra={"user_id": user.id, "email": email}
|
||||
)
|
||||
raise UserException(code.password, _("Account or password error"))
|
||||
|
||||
|
||||
logger.info("User login successful", extra={"user_id": user.id, "email": email})
|
||||
return LoginResponse(token=Auth.create_access_token(user.id), email=user.email)
|
||||
|
||||
|
||||
@router.post("/dev_login", name="OAuth2 password flow login (for Swagger UI)")
|
||||
@traceroot.trace()
|
||||
async def dev_login(
|
||||
username: str = Form(...), # OAuth2 uses 'username' but we accept email
|
||||
password: str = Form(...),
|
||||
session: Session = Depends(session),
|
||||
) -> dict:
|
||||
"""
|
||||
OAuth2 password flow compatible login endpoint for Swagger UI.
|
||||
This endpoint accepts form data (username/password) and returns an access token.
|
||||
"""
|
||||
# Use username as email (OAuth2 standard uses 'username' field)
|
||||
email = username
|
||||
user = User.by(User.email == email, s=session).one_or_none()
|
||||
|
||||
if not user:
|
||||
logger.warning("OAuth2 login failed: user not found", extra={"email": email})
|
||||
raise HTTPException(status_code=401, detail="Incorrect username or password")
|
||||
|
||||
if not password_verify(password, user.password):
|
||||
logger.warning(
|
||||
"OAuth2 login failed: invalid password",
|
||||
extra={"user_id": user.id, "email": email},
|
||||
)
|
||||
raise HTTPException(status_code=401, detail="Incorrect username or password")
|
||||
|
||||
token = Auth.create_access_token(user.id)
|
||||
logger.info("OAuth2 login successful", extra={"user_id": user.id, "email": email})
|
||||
|
||||
# Return OAuth2 compatible response
|
||||
return {"access_token": token, "token_type": "bearer"}
|
||||
|
||||
|
||||
@router.post("/login-by_stack", name="login by stack")
|
||||
@traceroot.trace()
|
||||
async def by_stack_auth(
|
||||
|
|
@ -50,16 +93,21 @@ async def by_stack_auth(
|
|||
stack_id = await StackAuth.user_id(token)
|
||||
info = await StackAuth.user_info(token)
|
||||
except Exception as e:
|
||||
logger.error("Stack auth failed", extra={"type": type, "error": str(e)}, exc_info=True)
|
||||
logger.error(
|
||||
"Stack auth failed", extra={"type": type, "error": str(e)}, exc_info=True
|
||||
)
|
||||
raise HTTPException(500, detail=_("Authentication failed"))
|
||||
|
||||
|
||||
user = User.by(User.stack_id == stack_id, s=session).one_or_none()
|
||||
|
||||
if not user:
|
||||
if type != "signup":
|
||||
logger.warning("Stack auth signup blocked: user not found", extra={"stack_id": stack_id, "type": type})
|
||||
logger.warning(
|
||||
"Stack auth signup blocked: user not found",
|
||||
extra={"stack_id": stack_id, "type": type},
|
||||
)
|
||||
raise UserException(code.error, _("User not found"))
|
||||
|
||||
|
||||
with session as s:
|
||||
try:
|
||||
user = User(
|
||||
|
|
@ -72,18 +120,37 @@ async def by_stack_auth(
|
|||
s.add(user)
|
||||
s.commit()
|
||||
s.refresh(user)
|
||||
logger.info("New user registered via stack", extra={"user_id": user.id, "email": user.email, "stack_id": stack_id})
|
||||
return LoginResponse(token=Auth.create_access_token(user.id), email=user.email)
|
||||
logger.info(
|
||||
"New user registered via stack",
|
||||
extra={
|
||||
"user_id": user.id,
|
||||
"email": user.email,
|
||||
"stack_id": stack_id,
|
||||
},
|
||||
)
|
||||
return LoginResponse(
|
||||
token=Auth.create_access_token(user.id), email=user.email
|
||||
)
|
||||
except Exception as e:
|
||||
s.rollback()
|
||||
logger.error("Stack auth registration failed", extra={"stack_id": stack_id, "error": str(e)}, exc_info=True)
|
||||
logger.error(
|
||||
"Stack auth registration failed",
|
||||
extra={"stack_id": stack_id, "error": str(e)},
|
||||
exc_info=True,
|
||||
)
|
||||
raise UserException(code.error, _("Failed to register"))
|
||||
else:
|
||||
if user.status == Status.Block:
|
||||
logger.warning("Blocked user login attempt", extra={"user_id": user.id, "stack_id": stack_id})
|
||||
logger.warning(
|
||||
"Blocked user login attempt",
|
||||
extra={"user_id": user.id, "stack_id": stack_id},
|
||||
)
|
||||
raise UserException(code.error, _("Your account has been blocked."))
|
||||
|
||||
logger.info("User login via stack successful", extra={"user_id": user.id, "email": user.email, "stack_id": stack_id})
|
||||
|
||||
logger.info(
|
||||
"User login via stack successful",
|
||||
extra={"user_id": user.id, "email": user.email, "stack_id": stack_id},
|
||||
)
|
||||
return LoginResponse(token=Auth.create_access_token(user.id), email=user.email)
|
||||
|
||||
|
||||
|
|
@ -91,9 +158,11 @@ async def by_stack_auth(
|
|||
@traceroot.trace()
|
||||
async def register(data: RegisterIn, session: Session = Depends(session)):
|
||||
email = data.email
|
||||
|
||||
|
||||
if User.by(User.email == email, s=session).one_or_none():
|
||||
logger.warning("Registration failed: email already exists", extra={"email": email})
|
||||
logger.warning(
|
||||
"Registration failed: email already exists", extra={"email": email}
|
||||
)
|
||||
raise UserException(code.error, _("Email already registered"))
|
||||
|
||||
with session as s:
|
||||
|
|
@ -105,10 +174,17 @@ async def register(data: RegisterIn, session: Session = Depends(session)):
|
|||
s.add(user)
|
||||
s.commit()
|
||||
s.refresh(user)
|
||||
logger.info("User registered successfully", extra={"user_id": user.id, "email": email})
|
||||
logger.info(
|
||||
"User registered successfully",
|
||||
extra={"user_id": user.id, "email": email},
|
||||
)
|
||||
except Exception as e:
|
||||
s.rollback()
|
||||
logger.error("User registration failed", extra={"email": email, "error": str(e)}, exc_info=True)
|
||||
logger.error(
|
||||
"User registration failed",
|
||||
extra={"email": email, "error": str(e)},
|
||||
exc_info=True,
|
||||
)
|
||||
raise UserException(code.error, _("Failed to register"))
|
||||
|
||||
return {"status": "success"}
|
||||
|
||||
return {"status": "success"}
|
||||
|
|
|
|||
30
server/docker-compose.dev.yml
Normal file
30
server/docker-compose.dev.yml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
services:
|
||||
# PostgreSQL Database Only
|
||||
postgres:
|
||||
image: postgres:15
|
||||
container_name: eigent_postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: eigent
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: 123456
|
||||
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- eigent_network
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "pg_isready -U postgres -d eigent" ]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
eigent_network:
|
||||
driver: bridge
|
||||
|
|
@ -12,22 +12,33 @@ from app import api
|
|||
from app.component.environment import auto_include_routers, env
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
# Import middleware to register BabelMiddleware
|
||||
import app.middleware # noqa: F401
|
||||
|
||||
# Import exception handlers to register them
|
||||
import app.exception.handler # noqa: F401
|
||||
|
||||
# Only initialize traceroot if enabled
|
||||
if traceroot.is_enabled():
|
||||
from traceroot.integrations.fastapi import connect_fastapi
|
||||
|
||||
connect_fastapi(api)
|
||||
|
||||
logger = traceroot.get_logger("server_main")
|
||||
|
||||
prefix = env("url_prefix", "")
|
||||
auto_include_routers(api, prefix, "app/controller")
|
||||
public_dir = os.environ.get("PUBLIC_DIR") or os.path.join(os.path.dirname(__file__), "app", "public")
|
||||
public_dir = os.environ.get("PUBLIC_DIR") or os.path.join(
|
||||
os.path.dirname(__file__), "app", "public"
|
||||
)
|
||||
if not os.path.isdir(public_dir):
|
||||
try:
|
||||
os.makedirs(public_dir, exist_ok=True)
|
||||
logger.warning(f"Public directory did not exist. Created: {public_dir}")
|
||||
except Exception as e:
|
||||
logger.error(f"Public directory missing and could not be created: {public_dir}. Error: {e}")
|
||||
logger.error(
|
||||
f"Public directory missing and could not be created: {public_dir}. Error: {e}"
|
||||
)
|
||||
public_dir = None
|
||||
|
||||
if public_dir and os.path.isdir(public_dir):
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export const INIT_PROVODERS: Provider[] = [
|
|||
model_type: ""
|
||||
},
|
||||
{
|
||||
id: 'bedrock',
|
||||
id: 'aws-bedrock',
|
||||
name: 'AWS Bedrock',
|
||||
apiKey: '',
|
||||
apiHost: '',
|
||||
|
|
|
|||
|
|
@ -1,432 +1,466 @@
|
|||
import { useAuthStore } from "@/store/authStore";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import { useCallback, useEffect, useState, useRef } from "react";
|
||||
import { useStackApp } from "@stackframe/react";
|
||||
import loginGif from "@/assets/login.gif";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useCallback, useEffect, useState, useRef } from 'react';
|
||||
import { useStackApp } from '@stackframe/react';
|
||||
import loginGif from '@/assets/login.gif';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Input } from '@/components/ui/input';
|
||||
|
||||
import github2 from "@/assets/github2.svg";
|
||||
import google from "@/assets/google.svg";
|
||||
import eye from "@/assets/eye.svg";
|
||||
import eyeOff from "@/assets/eye-off.svg";
|
||||
import { proxyFetchPost } from "@/api/http";
|
||||
import { hasStackKeys } from "@/lib";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import WindowControls from "@/components/WindowControls";
|
||||
import github2 from '@/assets/github2.svg';
|
||||
import google from '@/assets/google.svg';
|
||||
import eye from '@/assets/eye.svg';
|
||||
import eyeOff from '@/assets/eye-off.svg';
|
||||
import { proxyFetchPost } from '@/api/http';
|
||||
import { hasStackKeys } from '@/lib';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import WindowControls from '@/components/WindowControls';
|
||||
|
||||
const HAS_STACK_KEYS = hasStackKeys();
|
||||
let lock = false;
|
||||
export default function Login() {
|
||||
const app = HAS_STACK_KEYS ? useStackApp() : null;
|
||||
const { setAuth, setModelType, setLocalProxyValue } = useAuthStore();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [hidePassword, setHidePassword] = useState(true);
|
||||
const { t } = useTranslation();
|
||||
const [formData, setFormData] = useState({
|
||||
email: "",
|
||||
password: "",
|
||||
});
|
||||
const [errors, setErrors] = useState({
|
||||
email: "",
|
||||
password: "",
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [generalError, setGeneralError] = useState("");
|
||||
const titlebarRef = useRef<HTMLDivElement>(null);
|
||||
const [platform, setPlatform] = useState<string>("");
|
||||
const app = HAS_STACK_KEYS ? useStackApp() : null;
|
||||
const { setAuth, setModelType, setLocalProxyValue } = useAuthStore();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [hidePassword, setHidePassword] = useState(true);
|
||||
const { t } = useTranslation();
|
||||
const [formData, setFormData] = useState({
|
||||
email: '',
|
||||
password: '',
|
||||
});
|
||||
const [errors, setErrors] = useState({
|
||||
email: '',
|
||||
password: '',
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [generalError, setGeneralError] = useState('');
|
||||
const titlebarRef = useRef<HTMLDivElement>(null);
|
||||
const [platform, setPlatform] = useState<string>('');
|
||||
|
||||
const validateEmail = (email: string) => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
};
|
||||
const validateEmail = (email: string) => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
};
|
||||
|
||||
const validateForm = () => {
|
||||
const newErrors = {
|
||||
email: "",
|
||||
password: "",
|
||||
};
|
||||
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.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 < 6) {
|
||||
newErrors.password = t("layout.password-must-be-at-least-8-characters");
|
||||
}
|
||||
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;
|
||||
};
|
||||
setErrors(newErrors);
|
||||
return !newErrors.email && !newErrors.password;
|
||||
};
|
||||
|
||||
const getLoginErrorMessage = (data: any) => {
|
||||
if (!data || typeof data !== "object" || typeof data.code !== "number") {
|
||||
return "";
|
||||
}
|
||||
const getLoginErrorMessage = (data: any) => {
|
||||
if (!data || typeof data !== 'object' || typeof data.code !== 'number') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (data.code === 0) {
|
||||
return "";
|
||||
}
|
||||
if (data.code === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (data.code === 10) {
|
||||
return data.text || t("layout.login-failed-please-check-your-email-and-password");
|
||||
}
|
||||
if (data.code === 10) {
|
||||
return (
|
||||
data.text ||
|
||||
t('layout.login-failed-please-check-your-email-and-password')
|
||||
);
|
||||
}
|
||||
|
||||
if (data.code === 1 && Array.isArray(data.error) && data.error.length > 0) {
|
||||
const firstError = data.error[0];
|
||||
if (typeof firstError === "string") {
|
||||
return firstError;
|
||||
}
|
||||
if (typeof firstError?.msg === "string") {
|
||||
return firstError.msg;
|
||||
}
|
||||
if (typeof firstError?.message === "string") {
|
||||
return firstError.message;
|
||||
}
|
||||
}
|
||||
if (data.code === 1 && Array.isArray(data.error) && data.error.length > 0) {
|
||||
const firstError = data.error[0];
|
||||
if (typeof firstError === 'string') {
|
||||
return firstError;
|
||||
}
|
||||
if (typeof firstError?.msg === 'string') {
|
||||
return firstError.msg;
|
||||
}
|
||||
if (typeof firstError?.message === 'string') {
|
||||
return firstError.message;
|
||||
}
|
||||
}
|
||||
|
||||
return data.text || t("layout.login-failed-please-try-again");
|
||||
};
|
||||
return data.text || t('layout.login-failed-please-try-again');
|
||||
};
|
||||
|
||||
const handleInputChange = (field: string, value: string) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
}));
|
||||
const handleInputChange = (field: string, value: string) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
}));
|
||||
|
||||
if (errors[field as keyof typeof errors]) {
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
[field]: "",
|
||||
}));
|
||||
}
|
||||
if (errors[field as keyof typeof errors]) {
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
[field]: '',
|
||||
}));
|
||||
}
|
||||
|
||||
if (generalError) {
|
||||
setGeneralError("");
|
||||
}
|
||||
};
|
||||
if (generalError) {
|
||||
setGeneralError('');
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
const handleLogin = async () => {
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
//
|
||||
const handleLogin = async () => {
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setGeneralError("");
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const data = await proxyFetchPost("/api/login", {
|
||||
email: formData.email,
|
||||
password: formData.password,
|
||||
});
|
||||
setGeneralError('');
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const data = await proxyFetchPost('/api/login', {
|
||||
email: formData.email,
|
||||
password: formData.password,
|
||||
});
|
||||
|
||||
const errorMessage = getLoginErrorMessage(data);
|
||||
if (errorMessage) {
|
||||
setGeneralError(errorMessage);
|
||||
return;
|
||||
}
|
||||
const errorMessage = getLoginErrorMessage(data);
|
||||
if (errorMessage) {
|
||||
setGeneralError(errorMessage);
|
||||
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);
|
||||
navigate("/");
|
||||
} catch (error: any) {
|
||||
console.error("Login failed:", error);
|
||||
setGeneralError(t("layout.login-failed-please-check-your-email-and-password"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
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);
|
||||
navigate('/');
|
||||
} catch (error: any) {
|
||||
console.error('Login failed:', error);
|
||||
setGeneralError(
|
||||
t('layout.login-failed-please-check-your-email-and-password')
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoginByStack = async (token: string) => {
|
||||
try {
|
||||
const data = await proxyFetchPost("/api/login-by_stack?token=" + token, {
|
||||
token: token,
|
||||
});
|
||||
const handleLoginByStack = async (token: string) => {
|
||||
try {
|
||||
const data = await proxyFetchPost('/api/login-by_stack?token=' + token, {
|
||||
token: token,
|
||||
});
|
||||
|
||||
const errorMessage = getLoginErrorMessage(data);
|
||||
if (errorMessage) {
|
||||
setGeneralError(errorMessage);
|
||||
return;
|
||||
}
|
||||
console.log("data", data);
|
||||
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);
|
||||
setGeneralError(t("layout.login-failed-please-check-your-email-and-password"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
const errorMessage = getLoginErrorMessage(data);
|
||||
if (errorMessage) {
|
||||
setGeneralError(errorMessage);
|
||||
return;
|
||||
}
|
||||
console.log('data', data);
|
||||
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);
|
||||
setGeneralError(
|
||||
t('layout.login-failed-please-check-your-email-and-password')
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReloadBtn = async (type: string) => {
|
||||
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 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 = 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
|
||||
? `${import.meta.env.VITE_BASE_URL}/api/redirect/callback`
|
||||
: `${import.meta.env.VITE_PROXY_URL}/api/redirect/callback`
|
||||
);
|
||||
formData.append("code_verifier", code_verifier || "");
|
||||
formData.append("code", code);
|
||||
formData.append("grant_type", "authorization_code");
|
||||
formData.append("client_id", "aa49cdd0-318e-46bd-a540-0f1e5f2b391f");
|
||||
formData.append(
|
||||
"client_secret",
|
||||
"pck_t13egrd9ve57tz52kfcd2s4h1zwya5502z43kr5xv5cx8"
|
||||
);
|
||||
const handleGetToken = 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
|
||||
? `${import.meta.env.VITE_BASE_URL}/api/redirect/callback`
|
||||
: `${import.meta.env.VITE_PROXY_URL}/api/redirect/callback`
|
||||
);
|
||||
formData.append('code_verifier', code_verifier || '');
|
||||
formData.append('code', code);
|
||||
formData.append('grant_type', 'authorization_code');
|
||||
formData.append('client_id', 'aa49cdd0-318e-46bd-a540-0f1e5f2b391f');
|
||||
formData.append(
|
||||
'client_secret',
|
||||
'pck_t13egrd9ve57tz52kfcd2s4h1zwya5502z43kr5xv5cx8'
|
||||
);
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
"https://api.stack-auth.com/api/v1/auth/oauth/token",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
||||
},
|
||||
body: formData,
|
||||
}
|
||||
);
|
||||
const data = await res.json(); // parse response data
|
||||
return data.access_token;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
try {
|
||||
const res = await fetch(
|
||||
'https://api.stack-auth.com/api/v1/auth/oauth/token',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||
},
|
||||
body: formData,
|
||||
}
|
||||
);
|
||||
const data = await res.json(); // parse response data
|
||||
return data.access_token;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAuthCode = useCallback(
|
||||
async (event: any, code: string) => {
|
||||
if (lock || location.pathname !== "/login") return;
|
||||
const handleAuthCode = useCallback(
|
||||
async (event: any, code: string) => {
|
||||
if (lock || location.pathname !== '/login') return;
|
||||
|
||||
lock = true;
|
||||
setIsLoading(true);
|
||||
let accessToken = await handleGetToken(code);
|
||||
handleLoginByStack(accessToken);
|
||||
setTimeout(() => {
|
||||
lock = false;
|
||||
}, 1500);
|
||||
},
|
||||
[location.pathname]
|
||||
);
|
||||
lock = true;
|
||||
setIsLoading(true);
|
||||
let accessToken = await handleGetToken(code);
|
||||
handleLoginByStack(accessToken);
|
||||
setTimeout(() => {
|
||||
lock = false;
|
||||
}, 1500);
|
||||
},
|
||||
[location.pathname]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
window.ipcRenderer?.on("auth-code-received", handleAuthCode);
|
||||
useEffect(() => {
|
||||
window.ipcRenderer?.on('auth-code-received', handleAuthCode);
|
||||
|
||||
return () => {
|
||||
window.ipcRenderer?.off("auth-code-received", handleAuthCode);
|
||||
};
|
||||
}, []);
|
||||
return () => {
|
||||
window.ipcRenderer?.off('auth-code-received', handleAuthCode);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const p = window.electronAPI.getPlatform();
|
||||
setPlatform(p);
|
||||
useEffect(() => {
|
||||
const p = window.electronAPI.getPlatform();
|
||||
setPlatform(p);
|
||||
|
||||
if (platform === "darwin") {
|
||||
titlebarRef.current?.classList.add("mac");
|
||||
}
|
||||
}, [platform]);
|
||||
if (platform === 'darwin') {
|
||||
titlebarRef.current?.classList.add('mac');
|
||||
}
|
||||
}, [platform]);
|
||||
|
||||
// Handle before-close event for login page
|
||||
useEffect(() => {
|
||||
const handleBeforeClose = () => {
|
||||
// On login page, always close directly without confirmation
|
||||
window.electronAPI.closeWindow(true);
|
||||
};
|
||||
// Handle before-close event for login page
|
||||
useEffect(() => {
|
||||
const handleBeforeClose = () => {
|
||||
// On login page, always close directly without confirmation
|
||||
window.electronAPI.closeWindow(true);
|
||||
};
|
||||
|
||||
window.ipcRenderer?.on("before-close", handleBeforeClose);
|
||||
window.ipcRenderer?.on('before-close', handleBeforeClose);
|
||||
|
||||
return () => {
|
||||
window.ipcRenderer?.off("before-close", handleBeforeClose);
|
||||
};
|
||||
}, []);
|
||||
return () => {
|
||||
window.ipcRenderer?.off('before-close', handleBeforeClose);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col relative overflow-hidden">
|
||||
{/* Titlebar with drag region and window controls */}
|
||||
<div
|
||||
className="absolute top-0 left-0 right-0 flex !h-9 items-center justify-between pl-2 py-1 z-50"
|
||||
id="login-titlebar"
|
||||
ref={titlebarRef}
|
||||
style={{ WebkitAppRegion: 'drag' } as React.CSSProperties}
|
||||
>
|
||||
{/* Left spacer for macOS */}
|
||||
<div
|
||||
className={`${
|
||||
platform === "darwin" ? "w-[70px]" : "w-0"
|
||||
} flex items-center justify-center`}
|
||||
style={{ WebkitAppRegion: 'no-drag' } as React.CSSProperties}
|
||||
>
|
||||
{platform === "darwin" && <span className="text-label-md text-text-heading font-bold">Eigent</span>}
|
||||
</div>
|
||||
return (
|
||||
<div className="h-full flex flex-col relative overflow-hidden">
|
||||
{/* Titlebar with drag region and window controls */}
|
||||
<div
|
||||
className="absolute top-0 left-0 right-0 flex !h-9 items-center justify-between pl-2 py-1 z-50"
|
||||
id="login-titlebar"
|
||||
ref={titlebarRef}
|
||||
style={{ WebkitAppRegion: 'drag' } as React.CSSProperties}
|
||||
>
|
||||
{/* Left spacer for macOS */}
|
||||
<div
|
||||
className={`${
|
||||
platform === 'darwin' ? 'w-[70px]' : 'w-0'
|
||||
} flex items-center justify-center`}
|
||||
style={{ WebkitAppRegion: 'no-drag' } as React.CSSProperties}
|
||||
>
|
||||
{platform === 'darwin' && (
|
||||
<span className="text-label-md text-text-heading font-bold">
|
||||
Eigent
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Center drag region */}
|
||||
<div
|
||||
className="h-full flex-1 flex items-center"
|
||||
style={{ WebkitAppRegion: 'drag' } as React.CSSProperties}
|
||||
>
|
||||
<div className="flex-1 h-10"></div>
|
||||
</div>
|
||||
{/* Center drag region */}
|
||||
<div
|
||||
className="h-full flex-1 flex items-center"
|
||||
style={{ WebkitAppRegion: 'drag' } as React.CSSProperties}
|
||||
>
|
||||
<div className="flex-1 h-10"></div>
|
||||
</div>
|
||||
|
||||
{/* Right window controls */}
|
||||
<div
|
||||
style={{ WebkitAppRegion: 'no-drag', pointerEvents: 'auto' } as React.CSSProperties}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<WindowControls />
|
||||
</div>
|
||||
</div>
|
||||
{/* Right window controls */}
|
||||
<div
|
||||
style={
|
||||
{
|
||||
WebkitAppRegion: 'no-drag',
|
||||
pointerEvents: 'auto',
|
||||
} as React.CSSProperties
|
||||
}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<WindowControls />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main content - image extends to top, form has padding */}
|
||||
<div className={`p-2 flex items-center justify-center gap-2 h-full`}>
|
||||
<div className="flex items-center justify-center h-full rounded-3xl bg-white-100%">
|
||||
<img src={loginGif} className="rounded-3xl h-full object-cover" />
|
||||
</div>
|
||||
<div className="h-full flex-1 flex flex-col items-center justify-center pt-11">
|
||||
<div className="flex-1 flex flex-col w-80 items-center justify-center">
|
||||
<div className="flex self-stretch items-end justify-between mb-4">
|
||||
<div className="text-text-heading text-heading-lg font-bold ">
|
||||
{t("layout.login")}
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
window.open(
|
||||
"https://www.eigent.ai/signup",
|
||||
"_blank",
|
||||
"noopener,noreferrer"
|
||||
)
|
||||
}
|
||||
>
|
||||
{t("layout.sign-up")}
|
||||
</Button>
|
||||
</div>
|
||||
{HAS_STACK_KEYS && (
|
||||
<div className="w-full pt-6">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
onClick={() => handleReloadBtn("google")}
|
||||
className="w-full rounded-[24px] mb-4 transition-all duration-300 ease-in-out text-[#F5F5F5] text-center font-inter text-[15px] font-bold leading-[22px] justify-center"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<img src={google} className="w-5 h-5" />
|
||||
<span className="ml-2">{t("layout.continue-with-google-login")}</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
onClick={() => handleReloadBtn("github")}
|
||||
className="w-full rounded-[24px] mb-4 transition-all duration-300 ease-in-out text-[#F5F5F5] text-center font-inter text-[15px] font-bold leading-[22px] justify-center"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<img src={github2} className="w-5 h-5" />
|
||||
<span className="ml-2">{t("layout.continue-with-github-login")}</span>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{HAS_STACK_KEYS && (
|
||||
<div className="mt-2 w-full text-[#222] text-center font-inter text-[15px] font-medium leading-[22px] mb-6">
|
||||
{t("layout.or")}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-4 w-full">
|
||||
{generalError && (
|
||||
<p className="text-text-cuation text-label-md mt-1 mb-4">
|
||||
{generalError}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex flex-col gap-4 w-full mb-4 relative">
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
size="default"
|
||||
title={t("layout.email")}
|
||||
placeholder={t("layout.enter-your-email")}
|
||||
required
|
||||
value={formData.email}
|
||||
onChange={(e) => handleInputChange("email", e.target.value)}
|
||||
state={errors.email ? "error" : undefined}
|
||||
note={errors.email}
|
||||
onEnter={handleLogin}
|
||||
/>
|
||||
{/* Main content - image extends to top, form has padding */}
|
||||
<div className={`p-2 flex items-center justify-center gap-2 h-full`}>
|
||||
<div className="flex items-center justify-center h-full rounded-3xl bg-white-100%">
|
||||
<img src={loginGif} className="rounded-3xl h-full object-cover" />
|
||||
</div>
|
||||
<div className="h-full flex-1 flex flex-col items-center justify-center pt-11">
|
||||
<div className="flex-1 flex flex-col w-80 items-center justify-center">
|
||||
<div className="flex self-stretch items-end justify-between mb-4">
|
||||
<div className="text-text-heading text-heading-lg font-bold ">
|
||||
{t('layout.login')}
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (import.meta.env.VITE_USE_LOCAL_PROXY === 'true') {
|
||||
navigate('/signup');
|
||||
} else {
|
||||
window.open(
|
||||
'https://www.eigent.ai/signup',
|
||||
'_blank',
|
||||
'noopener,noreferrer'
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('layout.sign-up')}
|
||||
</Button>
|
||||
</div>
|
||||
{HAS_STACK_KEYS && (
|
||||
<div className="w-full pt-6">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
onClick={() => handleReloadBtn('google')}
|
||||
className="w-full rounded-[24px] mb-4 transition-all duration-300 ease-in-out text-[#F5F5F5] text-center font-inter text-[15px] font-bold leading-[22px] justify-center"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<img src={google} className="w-5 h-5" />
|
||||
<span className="ml-2">
|
||||
{t('layout.continue-with-google-login')}
|
||||
</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
onClick={() => handleReloadBtn('github')}
|
||||
className="w-full rounded-[24px] mb-4 transition-all duration-300 ease-in-out text-[#F5F5F5] text-center font-inter text-[15px] font-bold leading-[22px] justify-center"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<img src={github2} className="w-5 h-5" />
|
||||
<span className="ml-2">
|
||||
{t('layout.continue-with-github-login')}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{HAS_STACK_KEYS && (
|
||||
<div className="mt-2 w-full text-[#222] text-center font-inter text-[15px] font-medium leading-[22px] mb-6">
|
||||
{t('layout.or')}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-4 w-full">
|
||||
{generalError && (
|
||||
<p className="text-text-cuation text-label-md mt-1 mb-4">
|
||||
{generalError}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex flex-col gap-4 w-full mb-4 relative">
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
size="default"
|
||||
title={t('layout.email')}
|
||||
placeholder={t('layout.enter-your-email')}
|
||||
required
|
||||
value={formData.email}
|
||||
onChange={(e) => handleInputChange('email', e.target.value)}
|
||||
state={errors.email ? 'error' : undefined}
|
||||
note={errors.email}
|
||||
onEnter={handleLogin}
|
||||
/>
|
||||
|
||||
<Input
|
||||
id="password"
|
||||
title={t("layout.password")}
|
||||
size="default"
|
||||
type={hidePassword ? "password" : "text"}
|
||||
required
|
||||
placeholder={t("layout.enter-your-password")}
|
||||
value={formData.password}
|
||||
onChange={(e) =>
|
||||
handleInputChange("password", e.target.value)
|
||||
}
|
||||
state={errors.password ? "error" : undefined}
|
||||
note={errors.password}
|
||||
backIcon={<img src={hidePassword ? eye : eyeOff} />}
|
||||
onBackIconClick={() => setHidePassword(!hidePassword)}
|
||||
onEnter={handleLogin}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleLogin}
|
||||
size="md"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
className="w-full rounded-full"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<span className="flex-1">
|
||||
{isLoading ? t("layout.logging-in") : t("layout.log-in")}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
onClick={() => window.open("https://www.eigent.ai/privacy-policy", "_blank", "noopener,noreferrer")}
|
||||
>
|
||||
{t("layout.privacy-policy")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<Input
|
||||
id="password"
|
||||
title={t('layout.password')}
|
||||
size="default"
|
||||
type={hidePassword ? 'password' : 'text'}
|
||||
required
|
||||
placeholder={t('layout.enter-your-password')}
|
||||
value={formData.password}
|
||||
onChange={(e) =>
|
||||
handleInputChange('password', e.target.value)
|
||||
}
|
||||
state={errors.password ? 'error' : undefined}
|
||||
note={errors.password}
|
||||
backIcon={<img src={hidePassword ? eye : eyeOff} />}
|
||||
onBackIconClick={() => setHidePassword(!hidePassword)}
|
||||
onEnter={handleLogin}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleLogin}
|
||||
size="md"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
className="w-full rounded-full"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<span className="flex-1">
|
||||
{isLoading ? t('layout.logging-in') : t('layout.log-in')}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
onClick={() =>
|
||||
window.open(
|
||||
'https://www.eigent.ai/privacy-policy',
|
||||
'_blank',
|
||||
'noopener,noreferrer'
|
||||
)
|
||||
}
|
||||
>
|
||||
{t('layout.privacy-policy')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue