choizhang commited on
Commit
4421a58
·
1 Parent(s): 0a4b242

Added language and theme switching function to login page and homepage

Browse files
lightrag_webui/src/App.tsx CHANGED
@@ -1,5 +1,4 @@
1
  import { useState, useCallback } from 'react'
2
- import ThemeProvider from '@/components/ThemeProvider'
3
  import MessageAlert from '@/components/MessageAlert'
4
  import ApiKeyAlert from '@/components/ApiKeyAlert'
5
  import StatusIndicator from '@/components/graph/StatusIndicator'
@@ -52,7 +51,6 @@ function App() {
52
  }, [message, setApiKeyInvalid])
53
 
54
  return (
55
- <ThemeProvider>
56
  <main className="flex h-screen w-screen overflow-x-hidden">
57
  <Tabs
58
  defaultValue={currentTab}
@@ -79,7 +77,6 @@ function App() {
79
  {message !== null && !apiKeyInvalid && <MessageAlert />}
80
  {apiKeyInvalid && <ApiKeyAlert />}
81
  </main>
82
- </ThemeProvider>
83
  )
84
  }
85
 
 
1
  import { useState, useCallback } from 'react'
 
2
  import MessageAlert from '@/components/MessageAlert'
3
  import ApiKeyAlert from '@/components/ApiKeyAlert'
4
  import StatusIndicator from '@/components/graph/StatusIndicator'
 
51
  }, [message, setApiKeyInvalid])
52
 
53
  return (
 
54
  <main className="flex h-screen w-screen overflow-x-hidden">
55
  <Tabs
56
  defaultValue={currentTab}
 
77
  {message !== null && !apiKeyInvalid && <MessageAlert />}
78
  {apiKeyInvalid && <ApiKeyAlert />}
79
  </main>
 
80
  )
81
  }
82
 
lightrag_webui/src/AppRouter.tsx CHANGED
@@ -3,6 +3,7 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
3
  import { Toaster } from 'sonner'
4
  import App from './App'
5
  import LoginPage from '@/features/LoginPage'
 
6
 
7
  interface ProtectedRouteProps {
8
  children: React.ReactNode
@@ -20,20 +21,22 @@ const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
20
 
21
  const AppRouter = () => {
22
  return (
23
- <BrowserRouter>
24
- <Routes>
25
- <Route path="/login" element={<LoginPage />} />
26
- <Route
27
- path="/*"
28
- element={
29
- <ProtectedRoute>
30
- <App />
31
- </ProtectedRoute>
32
- }
33
- />
34
- </Routes>
35
- <Toaster position="top-center" />
36
- </BrowserRouter>
 
 
37
  )
38
  }
39
 
 
3
  import { Toaster } from 'sonner'
4
  import App from './App'
5
  import LoginPage from '@/features/LoginPage'
6
+ import ThemeProvider from '@/components/ThemeProvider'
7
 
8
  interface ProtectedRouteProps {
9
  children: React.ReactNode
 
21
 
22
  const AppRouter = () => {
23
  return (
24
+ <ThemeProvider>
25
+ <BrowserRouter>
26
+ <Routes>
27
+ <Route path="/login" element={<LoginPage />} />
28
+ <Route
29
+ path="/*"
30
+ element={
31
+ <ProtectedRoute>
32
+ <App />
33
+ </ProtectedRoute>
34
+ }
35
+ />
36
+ </Routes>
37
+ <Toaster position="top-center" />
38
+ </BrowserRouter>
39
+ </ThemeProvider>
40
  )
41
  }
42
 
lightrag_webui/src/components/LanguageToggle.tsx ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Button from '@/components/ui/Button'
2
+ import { useCallback } from 'react'
3
+ import { controlButtonVariant } from '@/lib/constants'
4
+ import { useTranslation } from 'react-i18next'
5
+ import { useSettingsStore } from '@/stores/settings'
6
+
7
+ /**
8
+ * Component that toggles the language between English and Chinese.
9
+ */
10
+ export default function LanguageToggle() {
11
+ const { i18n } = useTranslation()
12
+ const currentLanguage = i18n.language
13
+ const setLanguage = useSettingsStore.use.setLanguage()
14
+
15
+ const setEnglish = useCallback(() => {
16
+ i18n.changeLanguage('en')
17
+ setLanguage('en')
18
+ }, [i18n, setLanguage])
19
+
20
+ const setChinese = useCallback(() => {
21
+ i18n.changeLanguage('zh')
22
+ setLanguage('zh')
23
+ }, [i18n, setLanguage])
24
+
25
+ if (currentLanguage === 'zh') {
26
+ return (
27
+ <Button
28
+ onClick={setEnglish}
29
+ variant={controlButtonVariant}
30
+ tooltip="Switch to English"
31
+ size="icon"
32
+ side="bottom"
33
+ >
34
+
35
+ </Button>
36
+ )
37
+ }
38
+ return (
39
+ <Button
40
+ onClick={setChinese}
41
+ variant={controlButtonVariant}
42
+ tooltip="切换到中文"
43
+ size="icon"
44
+ side="bottom"
45
+ >
46
+ EN
47
+ </Button>
48
+ )
49
+ }
lightrag_webui/src/features/LoginPage.tsx CHANGED
@@ -3,15 +3,19 @@ import { useNavigate } from 'react-router-dom'
3
  import { useAuthStore } from '@/stores/state'
4
  import { loginToServer } from '@/api/lightrag'
5
  import { toast } from 'sonner'
 
6
 
7
  import { Card, CardContent, CardHeader } from '@/components/ui/Card'
8
  import Input from '@/components/ui/Input'
9
  import Button from '@/components/ui/Button'
10
  import { ZapIcon } from 'lucide-react'
 
 
11
 
12
  const LoginPage = () => {
13
  const navigate = useNavigate()
14
  const { login } = useAuthStore()
 
15
  const [loading, setLoading] = useState(false)
16
  const [username, setUsername] = useState('')
17
  const [password, setPassword] = useState('')
@@ -19,7 +23,7 @@ const LoginPage = () => {
19
  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
20
  e.preventDefault()
21
  if (!username || !password) {
22
- toast.error('Please enter your username and password')
23
  return
24
  }
25
 
@@ -28,10 +32,10 @@ const LoginPage = () => {
28
  const response = await loginToServer(username, password)
29
  login(response.access_token)
30
  navigate('/')
31
- toast.success('Login succeeded')
32
  } catch (error) {
33
  console.error('Login failed...', error)
34
- toast.error('Login failed, please check username and password')
35
  } finally {
36
  setLoading(false)
37
  }
@@ -39,6 +43,10 @@ const LoginPage = () => {
39
 
40
  return (
41
  <div className="flex h-screen w-screen items-center justify-center bg-gradient-to-br from-emerald-50 to-teal-100 dark:from-gray-900 dark:to-gray-800">
 
 
 
 
42
  <Card className="w-full max-w-[480px] shadow-lg mx-4">
43
  <CardHeader className="flex items-center justify-center space-y-2 pb-8 pt-6">
44
  <div className="flex flex-col items-center space-y-4">
@@ -49,7 +57,7 @@ const LoginPage = () => {
49
  <div className="text-center space-y-2">
50
  <h1 className="text-3xl font-bold tracking-tight">LightRAG</h1>
51
  <p className="text-muted-foreground text-sm">
52
- Please enter your account and password to log in to the system
53
  </p>
54
  </div>
55
  </div>
@@ -58,11 +66,11 @@ const LoginPage = () => {
58
  <form onSubmit={handleSubmit} className="space-y-6">
59
  <div className="flex items-center gap-4">
60
  <label htmlFor="username" className="text-sm font-medium w-16 shrink-0">
61
- username
62
  </label>
63
  <Input
64
  id="username"
65
- placeholder="Please input a username"
66
  value={username}
67
  onChange={(e) => setUsername(e.target.value)}
68
  required
@@ -71,12 +79,12 @@ const LoginPage = () => {
71
  </div>
72
  <div className="flex items-center gap-4">
73
  <label htmlFor="password" className="text-sm font-medium w-16 shrink-0">
74
- password
75
  </label>
76
  <Input
77
  id="password"
78
  type="password"
79
- placeholder="Please input a password"
80
  value={password}
81
  onChange={(e) => setPassword(e.target.value)}
82
  required
@@ -88,7 +96,7 @@ const LoginPage = () => {
88
  className="w-full h-11 text-base font-medium mt-2"
89
  disabled={loading}
90
  >
91
- {loading ? 'Logging in...' : 'Login'}
92
  </Button>
93
  </form>
94
  </CardContent>
 
3
  import { useAuthStore } from '@/stores/state'
4
  import { loginToServer } from '@/api/lightrag'
5
  import { toast } from 'sonner'
6
+ import { useTranslation } from 'react-i18next'
7
 
8
  import { Card, CardContent, CardHeader } from '@/components/ui/Card'
9
  import Input from '@/components/ui/Input'
10
  import Button from '@/components/ui/Button'
11
  import { ZapIcon } from 'lucide-react'
12
+ import ThemeToggle from '@/components/ThemeToggle'
13
+ import LanguageToggle from '@/components/LanguageToggle'
14
 
15
  const LoginPage = () => {
16
  const navigate = useNavigate()
17
  const { login } = useAuthStore()
18
+ const { t } = useTranslation()
19
  const [loading, setLoading] = useState(false)
20
  const [username, setUsername] = useState('')
21
  const [password, setPassword] = useState('')
 
23
  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
24
  e.preventDefault()
25
  if (!username || !password) {
26
+ toast.error(t('login.errorEmptyFields'))
27
  return
28
  }
29
 
 
32
  const response = await loginToServer(username, password)
33
  login(response.access_token)
34
  navigate('/')
35
+ toast.success(t('login.successMessage'))
36
  } catch (error) {
37
  console.error('Login failed...', error)
38
+ toast.error(t('login.errorInvalidCredentials'))
39
  } finally {
40
  setLoading(false)
41
  }
 
43
 
44
  return (
45
  <div className="flex h-screen w-screen items-center justify-center bg-gradient-to-br from-emerald-50 to-teal-100 dark:from-gray-900 dark:to-gray-800">
46
+ <div className="absolute top-4 right-4 flex items-center gap-2">
47
+ <LanguageToggle />
48
+ <ThemeToggle />
49
+ </div>
50
  <Card className="w-full max-w-[480px] shadow-lg mx-4">
51
  <CardHeader className="flex items-center justify-center space-y-2 pb-8 pt-6">
52
  <div className="flex flex-col items-center space-y-4">
 
57
  <div className="text-center space-y-2">
58
  <h1 className="text-3xl font-bold tracking-tight">LightRAG</h1>
59
  <p className="text-muted-foreground text-sm">
60
+ {t('login.description')}
61
  </p>
62
  </div>
63
  </div>
 
66
  <form onSubmit={handleSubmit} className="space-y-6">
67
  <div className="flex items-center gap-4">
68
  <label htmlFor="username" className="text-sm font-medium w-16 shrink-0">
69
+ {t('login.username')}
70
  </label>
71
  <Input
72
  id="username"
73
+ placeholder={t('login.usernamePlaceholder')}
74
  value={username}
75
  onChange={(e) => setUsername(e.target.value)}
76
  required
 
79
  </div>
80
  <div className="flex items-center gap-4">
81
  <label htmlFor="password" className="text-sm font-medium w-16 shrink-0">
82
+ {t('login.password')}
83
  </label>
84
  <Input
85
  id="password"
86
  type="password"
87
+ placeholder={t('login.passwordPlaceholder')}
88
  value={password}
89
  onChange={(e) => setPassword(e.target.value)}
90
  required
 
96
  className="w-full h-11 text-base font-medium mt-2"
97
  disabled={loading}
98
  >
99
+ {loading ? t('login.loggingIn') : t('login.loginButton')}
100
  </Button>
101
  </form>
102
  </CardContent>
lightrag_webui/src/features/SiteHeader.tsx CHANGED
@@ -1,6 +1,7 @@
1
  import Button from '@/components/ui/Button'
2
  import { SiteInfo } from '@/lib/constants'
3
  import ThemeToggle from '@/components/ThemeToggle'
 
4
  import { TabsList, TabsTrigger } from '@/components/ui/Tabs'
5
  import { useSettingsStore } from '@/stores/settings'
6
  import { useAuthStore } from '@/stores/state'
@@ -82,6 +83,7 @@ export default function SiteHeader() {
82
  <GithubIcon className="size-4" aria-hidden="true" />
83
  </a>
84
  </Button>
 
85
  <ThemeToggle />
86
  <Button
87
  variant="ghost"
 
1
  import Button from '@/components/ui/Button'
2
  import { SiteInfo } from '@/lib/constants'
3
  import ThemeToggle from '@/components/ThemeToggle'
4
+ import LanguageToggle from '@/components/LanguageToggle'
5
  import { TabsList, TabsTrigger } from '@/components/ui/Tabs'
6
  import { useSettingsStore } from '@/stores/settings'
7
  import { useAuthStore } from '@/stores/state'
 
83
  <GithubIcon className="size-4" aria-hidden="true" />
84
  </a>
85
  </Button>
86
+ <LanguageToggle />
87
  <ThemeToggle />
88
  <Button
89
  variant="ghost"
lightrag_webui/src/i18n.js CHANGED
@@ -1,9 +1,23 @@
1
  import i18n from "i18next";
2
  import { initReactI18next } from "react-i18next";
 
3
 
4
  import en from "./locales/en.json";
5
  import zh from "./locales/zh.json";
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  i18n
8
  .use(initReactI18next)
9
  .init({
@@ -11,7 +25,7 @@ i18n
11
  en: { translation: en },
12
  zh: { translation: zh }
13
  },
14
- lng: "en", // default
15
  fallbackLng: "en",
16
  interpolation: {
17
  escapeValue: false
 
1
  import i18n from "i18next";
2
  import { initReactI18next } from "react-i18next";
3
+ import { useSettingsStore } from "./stores/settings";
4
 
5
  import en from "./locales/en.json";
6
  import zh from "./locales/zh.json";
7
 
8
+ const getStoredLanguage = () => {
9
+ try {
10
+ const settingsString = localStorage.getItem('settings-storage');
11
+ if (settingsString) {
12
+ const settings = JSON.parse(settingsString);
13
+ return settings.state?.language || 'en';
14
+ }
15
+ } catch (e) {
16
+ console.error('Failed to get stored language:', e);
17
+ }
18
+ return 'en';
19
+ };
20
+
21
  i18n
22
  .use(initReactI18next)
23
  .init({
 
25
  en: { translation: en },
26
  zh: { translation: zh }
27
  },
28
+ lng: getStoredLanguage(), // 使用存储的语言设置
29
  fallbackLng: "en",
30
  interpolation: {
31
  escapeValue: false
lightrag_webui/src/locales/en.json CHANGED
@@ -10,6 +10,18 @@
10
  "switchToDark": "Switch to dark theme"
11
  }
12
  },
 
 
 
 
 
 
 
 
 
 
 
 
13
  "documentPanel": {
14
  "clearDocuments": {
15
  "button": "Clear",
 
10
  "switchToDark": "Switch to dark theme"
11
  }
12
  },
13
+ "login": {
14
+ "description": "Please enter your account and password to log in to the system",
15
+ "username": "Username",
16
+ "usernamePlaceholder": "Please input a username",
17
+ "password": "Password",
18
+ "passwordPlaceholder": "Please input a password",
19
+ "loginButton": "Login",
20
+ "loggingIn": "Logging in...",
21
+ "successMessage": "Login succeeded",
22
+ "errorEmptyFields": "Please enter your username and password",
23
+ "errorInvalidCredentials": "Login failed, please check username and password"
24
+ },
25
  "documentPanel": {
26
  "clearDocuments": {
27
  "button": "Clear",
lightrag_webui/src/locales/zh.json CHANGED
@@ -10,6 +10,18 @@
10
  "switchToDark": "切换到暗色主题"
11
  }
12
  },
 
 
 
 
 
 
 
 
 
 
 
 
13
  "documentPanel": {
14
  "clearDocuments": {
15
  "button": "清除",
 
10
  "switchToDark": "切换到暗色主题"
11
  }
12
  },
13
+ "login": {
14
+ "description": "请输入您的账号和密码登录系统",
15
+ "username": "用户名",
16
+ "usernamePlaceholder": "请输入用户名",
17
+ "password": "密码",
18
+ "passwordPlaceholder": "请输入密码",
19
+ "loginButton": "登录",
20
+ "loggingIn": "登录中...",
21
+ "successMessage": "登录成功",
22
+ "errorEmptyFields": "请输入您的用户名和密码",
23
+ "errorInvalidCredentials": "登录失败,请检查用户名和密码"
24
+ },
25
  "documentPanel": {
26
  "clearDocuments": {
27
  "button": "清除",
lightrag_webui/src/stores/settings.ts CHANGED
@@ -6,6 +6,7 @@ import { Message, QueryRequest } from '@/api/lightrag'
6
 
7
  type Theme = 'dark' | 'light' | 'system'
8
  type Tab = 'documents' | 'knowledge-graph' | 'retrieval' | 'api'
 
9
 
10
  interface SettingsState {
11
  // Graph viewer settings
@@ -46,6 +47,9 @@ interface SettingsState {
46
  theme: Theme
47
  setTheme: (theme: Theme) => void
48
 
 
 
 
49
  enableHealthCheck: boolean
50
  setEnableHealthCheck: (enable: boolean) => void
51
 
@@ -57,6 +61,7 @@ const useSettingsStoreBase = create<SettingsState>()(
57
  persist(
58
  (set) => ({
59
  theme: 'system',
 
60
 
61
  showPropertyPanel: true,
62
  showNodeSearchBar: true,
@@ -99,6 +104,8 @@ const useSettingsStoreBase = create<SettingsState>()(
99
 
100
  setTheme: (theme: Theme) => set({ theme }),
101
 
 
 
102
  setGraphLayoutMaxIterations: (iterations: number) =>
103
  set({
104
  graphLayoutMaxIterations: iterations
 
6
 
7
  type Theme = 'dark' | 'light' | 'system'
8
  type Tab = 'documents' | 'knowledge-graph' | 'retrieval' | 'api'
9
+ type Language = 'en' | 'zh'
10
 
11
  interface SettingsState {
12
  // Graph viewer settings
 
47
  theme: Theme
48
  setTheme: (theme: Theme) => void
49
 
50
+ language: Language
51
+ setLanguage: (language: Language) => void
52
+
53
  enableHealthCheck: boolean
54
  setEnableHealthCheck: (enable: boolean) => void
55
 
 
61
  persist(
62
  (set) => ({
63
  theme: 'system',
64
+ language: 'en',
65
 
66
  showPropertyPanel: true,
67
  showNodeSearchBar: true,
 
104
 
105
  setTheme: (theme: Theme) => set({ theme }),
106
 
107
+ setLanguage: (language: Language) => set({ language }),
108
+
109
  setGraphLayoutMaxIterations: (iterations: number) =>
110
  set({
111
  graphLayoutMaxIterations: iterations