diff --git a/web/package-lock.json b/web/package-lock.json
index a1ade004..69d7c822 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -30,7 +30,7 @@
"postcss": "^8.5.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
- "react-hook-form": "^7.56.2",
+ "react-hook-form": "^7.56.3",
"tailwind-merge": "^3.2.0",
"tailwindcss": "^4.1.5",
"uuidjs": "^5.1.0",
@@ -6863,9 +6863,9 @@
}
},
"node_modules/react-hook-form": {
- "version": "7.56.2",
- "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.56.2.tgz",
- "integrity": "sha512-vpfuHuQMF/L6GpuQ4c3ZDo+pRYxIi40gQqsCmmfUBwm+oqvBhKhwghCuj2o00YCgSfU6bR9KC/xnQGWm3Gr08A==",
+ "version": "7.56.3",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.56.3.tgz",
+ "integrity": "sha512-IK18V6GVbab4TAo1/cz3kqajxbDPGofdF0w7VHdCo0Nt8PrPlOZcuuDq9YYIV1BtjcX78x0XsldbQRQnQXWXmw==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
diff --git a/web/package.json b/web/package.json
index 2a1105bc..d058d419 100644
--- a/web/package.json
+++ b/web/package.json
@@ -33,7 +33,7 @@
"postcss": "^8.5.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
- "react-hook-form": "^7.56.2",
+ "react-hook-form": "^7.56.3",
"tailwind-merge": "^3.2.0",
"tailwindcss": "^4.1.5",
"uuidjs": "^5.1.0",
diff --git a/web/src/app/home/assets/langbot-logo.webp b/web/src/app/assets/langbot-logo.webp
similarity index 100%
rename from web/src/app/home/assets/langbot-logo.webp
rename to web/src/app/assets/langbot-logo.webp
diff --git a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx
index c18e2a59..8f0ef356 100644
--- a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx
+++ b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx
@@ -8,7 +8,7 @@ import {
} from '@/app/home/components/home-sidebar/HomeSidebarChild';
import { useRouter, usePathname } from 'next/navigation';
import { sidebarConfigList } from '@/app/home/components/home-sidebar/sidbarConfigList';
-import langbotIcon from '../../assets/langbot-logo.webp';
+import langbotIcon from '@/app/assets/langbot-logo.webp';
import { Button } from '@/components/ui/button';
// TODO 侧边导航栏要加动画
diff --git a/web/src/app/home/layout.tsx b/web/src/app/home/layout.tsx
index 2d2d7e56..f12f2973 100644
--- a/web/src/app/home/layout.tsx
+++ b/web/src/app/home/layout.tsx
@@ -1,14 +1,10 @@
'use client';
-import '@ant-design/v5-patch-for-react-19';
import styles from './layout.module.css';
import HomeSidebar from '@/app/home/components/home-sidebar/HomeSidebar';
import HomeTitleBar from '@/app/home/components/home-titlebar/HomeTitleBar';
import React, { useState } from 'react';
import { SidebarChildVO } from '@/app/home/components/home-sidebar/HomeSidebarChild';
-// import { Layout } from 'antd';
-
-// const { Sider, Content } = Layout;
export default function HomeLayout({
children,
diff --git a/web/src/app/infra/http/HttpClient.ts b/web/src/app/infra/http/HttpClient.ts
index f7acb654..9243a1bb 100644
--- a/web/src/app/infra/http/HttpClient.ts
+++ b/web/src/app/infra/http/HttpClient.ts
@@ -129,7 +129,13 @@ class HttpClient {
switch (status) {
case 401:
- window.location.href = '/login';
+
+ console.log('401 error: ', errMessage, error.request);
+ console.log('responseURL', error.request.responseURL)
+ localStorage.removeItem('token');
+ if (!error.request.responseURL.includes('/check-token')) {
+ window.location.href = '/login';
+ }
break;
case 403:
console.error('Permission denied:', errMessage);
diff --git a/web/src/app/layout.tsx b/web/src/app/layout.tsx
index fe4f4284..6d6e7ad0 100644
--- a/web/src/app/layout.tsx
+++ b/web/src/app/layout.tsx
@@ -2,8 +2,8 @@ import './global.css';
import type { Metadata } from 'next';
export const metadata: Metadata = {
- title: 'Create Next App',
- description: 'Generated by create next app',
+ title: 'LangBot',
+ description: 'LangBot 是大模型原生即时通信机器人平台',
};
export default function RootLayout({
diff --git a/web/src/app/login/layout.tsx b/web/src/app/login/layout.tsx
index f341f627..4996a7ac 100644
--- a/web/src/app/login/layout.tsx
+++ b/web/src/app/login/layout.tsx
@@ -1,7 +1,6 @@
'use client';
import React from 'react';
-import { ConfigProvider, theme } from 'antd';
export default function LoginLayout({
children,
@@ -9,18 +8,8 @@ export default function LoginLayout({
children: React.ReactNode;
}>) {
return (
-
-
- {children}
-
-
+
+ {children}
+
);
}
diff --git a/web/src/app/login/login.module.css b/web/src/app/login/login.module.css
deleted file mode 100644
index b6435980..00000000
--- a/web/src/app/login/login.module.css
+++ /dev/null
@@ -1,98 +0,0 @@
-.container {
- width: 100%;
- height: 100vh;
- display: flex;
- justify-content: center;
- align-items: center;
- background-color: #f5f5f5;
-}
-
-.login {
- display: flex;
- width: 30%;
- height: 80vh;
- background-color: white;
- border-radius: 16px;
- overflow: hidden;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
-}
-
-.left {
- flex: 1;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- padding: 2rem;
-}
-
-
-.loginForm {
- width: 100%;
- max-width: 360px;
-}
-
-.title {
- font-size: 2rem;
- font-weight: 600;
- margin-bottom: 2rem;
- text-align: center;
-}
-
-.loginButton {
- width: 100%;
- height: 40px;
- margin-top: 1rem;
-}
-
-.divider {
- margin: 1.5rem 0;
- text-align: center;
-}
-
-.socialLogin {
- display: flex;
- justify-content: space-between;
- gap: 1rem;
-}
-
-.socialButton {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.rememberMe {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 1rem;
-
- .forgetPassword {
- margin-right: 1rem;
- }
-}
-
-/* 修改Logo样式,调整位置确保正确对齐 */
-.logoContainer {
- position: absolute;
- top: 20px;
- right: 20px;
- z-index: 10;
- display: flex;
- justify-content: center;
- align-items: center;
- width: 70px;
- height: 70px;
- background-color: rgba(255, 255, 255, 0.8);
- border-radius: 50%;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
-}
-
-.logo {
- width: 60px;
- height: 60px;
- border-radius: 50%;
- object-fit: cover;
-}
diff --git a/web/src/app/login/page.tsx b/web/src/app/login/page.tsx
index 3a8eee2c..afdafbdd 100644
--- a/web/src/app/login/page.tsx
+++ b/web/src/app/login/page.tsx
@@ -1,59 +1,75 @@
'use client';
-import { Button, Input, Form, Checkbox, Divider } from 'antd';
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Checkbox } from "@/components/ui/checkbox";
+import { Label } from "@/components/ui/label";
+import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import * as z from "zod";
import {
- GoogleOutlined,
- LockOutlined,
- UserOutlined,
- QqOutlined,
-} from '@ant-design/icons';
-import styles from './login.module.css';
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
import { useEffect, useState } from 'react';
-
import { httpClient } from '@/app/infra/http/HttpClient';
-import '@ant-design/v5-patch-for-react-19';
import { useRouter } from 'next/navigation';
+import { Mail, Lock } from "lucide-react";
+import langbotIcon from '@/app/assets/langbot-logo.webp';
-export default function Home() {
+const formSchema = z.object({
+ email: z.string().email("请输入有效的邮箱地址"),
+ password: z.string().min(1, "请输入密码"),
+});
+
+export default function Login() {
const router = useRouter();
- const [form] = Form.useForm();
- const [rememberMe, setRememberMe] = useState(false);
- const [isRegisterMode, setIsRegisterMode] = useState(false);
- const [isInitialized, setIsInitialized] = useState(false);
+
+ const form = useForm>({
+ resolver: zodResolver(formSchema),
+ defaultValues: {
+ email: "",
+ password: "",
+ },
+ });
useEffect(() => {
getIsInitialized();
+ checkIfAlreadyLoggedIn();
}, []);
- // 检查是否为首次启动项目,只为首次启动的用户提供注册资格
function getIsInitialized() {
httpClient
.checkIfInited()
.then((res) => {
- setIsInitialized(res.initialized);
+ if (!res.initialized) {
+ router.push('/register');
+ }
})
.catch((err) => {
console.log('error at getIsInitialized: ', err);
});
}
- function handleFormSubmit(formField: LoginField) {
- if (isRegisterMode) {
- handleRegister(formField.email, formField.password);
- } else {
- handleLogin(formField.email, formField.password);
- }
- }
-
- function handleRegister(username: string, password: string) {
- httpClient
- .initUser(username, password)
+ function checkIfAlreadyLoggedIn() {
+ httpClient.checkUserToken()
.then((res) => {
- console.log('init user success: ', res);
+ if (res.token) {
+ localStorage.setItem('token', res.token);
+ router.push('/home');
+ }
})
.catch((err) => {
- console.log('init user error: ', err);
+ console.log('error at checkIfAlreadyLoggedIn: ', err);
});
}
+ function onSubmit(values: z.infer) {
+ handleLogin(values.email, values.password);
+ }
function handleLogin(username: string, password: string) {
httpClient
@@ -69,136 +85,73 @@ export default function Home() {
}
return (
- // 使用 Ant Design 的组件库,使用 antd 的样式
- // 仅前端样式,无交互功能。
-
-
- {/* login 类是整个 container,使用 flex 左右布局 */}
-
- {/* left 为注册的表单,需要填入的内容有:邮箱,密码 */}
-
-
- {isRegisterMode && (
-
注册 LangBot 账号
- )}
- {!isRegisterMode && (
-
欢迎回到 LangBot
- )}
-
+
+
+
+
+ 欢迎回到 LangBot 👋
+
+
+ 登录以继续
+
+
+
+
+ render={({ field }) => (
+
+ 邮箱
+
+
+
+
+
+
+
+
+ )}
+ />
-
- }
- />
-
-
-
+ render={({ field }) => (
+
+ 密码
+
+
+
+
+
+
+
+
+ )}
+ />
-
-
或
-
-
- }
- size="large"
- disabled={true}
- >
- 使用谷歌账号登录
-
-
-
-
- }
- size="large"
- disabled={true}
- >
- 使用QQ账号登录
-
-
-
-
-
-
+
+
+
+
);
}
-
-interface LoginField {
- email: string;
- password: string;
-}
diff --git a/web/src/app/page.tsx b/web/src/app/page.tsx
index 29814463..c2943fe9 100644
--- a/web/src/app/page.tsx
+++ b/web/src/app/page.tsx
@@ -1,3 +1,12 @@
+'use client';
+
+import { useRouter } from 'next/navigation';
+import { useEffect } from 'react';
+
export default function Home() {
+ const router = useRouter();
+ useEffect(() => {
+ router.push('/login');
+ }, []);
return ;
}
diff --git a/web/src/app/register/layout.tsx b/web/src/app/register/layout.tsx
new file mode 100644
index 00000000..c93e0bde
--- /dev/null
+++ b/web/src/app/register/layout.tsx
@@ -0,0 +1,15 @@
+'use client';
+
+import React from 'react';
+
+export default function RegisterLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/web/src/app/register/page.tsx b/web/src/app/register/page.tsx
new file mode 100644
index 00000000..81e6ff8e
--- /dev/null
+++ b/web/src/app/register/page.tsx
@@ -0,0 +1,146 @@
+'use client';
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Checkbox } from "@/components/ui/checkbox";
+import { Label } from "@/components/ui/label";
+import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import * as z from "zod";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+ FormDescription,
+} from "@/components/ui/form";
+import { useEffect, useState } from 'react';
+import { httpClient } from '@/app/infra/http/HttpClient';
+import { useRouter } from 'next/navigation';
+import { Mail, Lock } from "lucide-react";
+import langbotIcon from '@/app/assets/langbot-logo.webp';
+
+const formSchema = z.object({
+ email: z.string().email("请输入有效的邮箱地址"),
+ password: z.string().min(1, "请输入密码"),
+});
+
+export default function Register() {
+ const router = useRouter();
+
+ const form = useForm>({
+ resolver: zodResolver(formSchema),
+ defaultValues: {
+ email: "",
+ password: "",
+ },
+ });
+
+ useEffect(() => {
+ getIsInitialized();
+ }, []);
+
+ function getIsInitialized() {
+ httpClient
+ .checkIfInited()
+ .then((res) => {
+ if (res.initialized) {
+ router.push('/login');
+ }
+ })
+ .catch((err) => {
+ console.log('error at getIsInitialized: ', err);
+ });
+ }
+
+ function onSubmit(values: z.infer) {
+ handleRegister(values.email, values.password);
+ }
+
+ function handleRegister(username: string, password: string) {
+ httpClient
+ .initUser(username, password)
+ .then((res) => {
+ console.log('init user success: ', res);
+ router.push('/login');
+ })
+ .catch((err) => {
+ console.log('init user error: ', err);
+ });
+ }
+
+ return (
+
+
+
+
+
+ 初始化 LangBot 👋
+
+
+ 这是您首次启动 LangBot
+
+ 您填写的邮箱和密码将作为初始管理员账号
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/src/components/ui/card.tsx b/web/src/components/ui/card.tsx
new file mode 100644
index 00000000..d05bbc6c
--- /dev/null
+++ b/web/src/components/ui/card.tsx
@@ -0,0 +1,92 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Card({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardAction,
+ CardDescription,
+ CardContent,
+}