diff --git a/docker-compose.e2e.yml b/docker-compose.e2e.yml index ec7f41b..e98a983 100644 --- a/docker-compose.e2e.yml +++ b/docker-compose.e2e.yml @@ -50,7 +50,7 @@ services: ports: - "3334:3000" healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3000/"] + test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] interval: 5s timeout: 3s retries: 10 @@ -58,7 +58,7 @@ services: ports: - "3334:3000" healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3000/"] + test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] interval: 5s timeout: 3s retries: 10 diff --git a/docker-compose.yml b/docker-compose.yml index ebebc6e..0fc889a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,7 +19,7 @@ services: restart: unless-stopped healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3000/"] + test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] interval: 30s timeout: 5s retries: 3 diff --git a/frontend/index.html b/frontend/index.html index e4b78ea..728704d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,9 +2,9 @@ - + - Vite + React + TS + Gitea AI Assistant
diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 0000000..54c3b87 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1 @@ + diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5c3715e..a3e54ea 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6,6 +6,7 @@ import { RepositoryManager } from './components/RepositoryManager'; import { ConfigManager } from './components/ConfigManager'; import { ReviewConfigPage } from './components/ReviewConfigPage'; import { Toaster } from "@/components/ui/sonner" +import { useTheme } from 'next-themes' function AuthGuard({ children }: { children: React.ReactNode }) { const { isAuthenticated, isLoading } = useAuth(); @@ -32,7 +33,9 @@ function AuthGuard({ children }: { children: React.ReactNode }) { return <>{children}; } -function App() { +function AppContent() { + const { resolvedTheme } = useTheme(); + return ( @@ -51,9 +54,13 @@ function App() { } /> - + ); } +function App() { + return ; +} + export default App; diff --git a/frontend/src/index.css b/frontend/src/index.css index d858db0..1236f3f 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -72,11 +72,11 @@ } .bg-grid-pattern { background-size: 40px 40px; - background-image: linear-gradient(to right, rgba(255, 255, 255, 0.05) 1px, transparent 1px), - linear-gradient(to bottom, rgba(255, 255, 255, 0.05) 1px, transparent 1px); + background-image: linear-gradient(to right, hsl(var(--foreground) / 0.05) 1px, transparent 1px), + linear-gradient(to bottom, hsl(var(--foreground) / 0.05) 1px, transparent 1px); } .glass-panel { - @apply bg-zinc-950/50 backdrop-blur-xl border border-white/10 shadow-2xl; + @apply bg-card/80 backdrop-blur-xl border border-border shadow-2xl; } .tech-glow { box-shadow: 0 0 20px -5px hsl(var(--primary) / 0.5); diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 4b5549a..43c91a9 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -18,4 +18,20 @@ api.interceptors.request.use( } ); +// 添加响应拦截器,处理 401 未授权自动跳转登录页 +api.interceptors.response.use( + (response) => response, + (error) => { + if (axios.isAxiosError(error) && error.response?.status === 401) { + localStorage.removeItem('authToken'); + // 避免在登录接口本身触发跳转 + const isLoginRequest = error.config?.url?.includes('/login'); + if (!isLoginRequest) { + window.location.href = '/'; + } + } + return Promise.reject(error); + } +); + export default api; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 51afcdd..3d8be74 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -4,6 +4,7 @@ import ReactDOM from 'react-dom/client' import App from './App.tsx' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import axios from 'axios' +import { ThemeProvider } from 'next-themes' const queryClient = new QueryClient({ defaultOptions: { @@ -21,13 +22,13 @@ const queryClient = new QueryClient({ }, }); -// Force dark mode as requested -document.documentElement.classList.add('dark'); ReactDOM.createRoot(document.getElementById('root')!).render( - - - + + + + + , ) diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index 9223012..41c30d6 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -1,7 +1,8 @@ import { useState, useEffect } from 'react'; import { NavLink, Outlet, useLocation } from 'react-router-dom'; import { Button } from '@/components/ui/button'; -import { LogOut, Bot, FolderGit2, Sliders, Menu, X, PanelLeftClose, PanelLeftOpen, FileSearch } from 'lucide-react'; +import { LogOut, Bot, FolderGit2, Sliders, Menu, X, PanelLeftClose, PanelLeftOpen, FileSearch, Sun, Moon } from 'lucide-react'; +import { useTheme } from 'next-themes'; const navItems = [ { path: '/repos', label: '仓库管理', icon: FolderGit2 }, @@ -11,6 +12,7 @@ const navItems = [ export default function DashboardPage() { const location = useLocation(); + const { setTheme, resolvedTheme } = useTheme(); const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); @@ -33,24 +35,24 @@ export default function DashboardPage() { {/* Mobile Overlay */} {isMobileMenuOpen && (
setIsMobileMenuOpen(false)} /> )} {/* Sidebar */}