Spaces:
Runtime error
Runtime error
File size: 7,707 Bytes
077865a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | import { useEffect, useState } from 'react'
import { BrowserRouter, Routes, Route, Navigate, NavLink, Link, useLocation, useNavigate } from 'react-router-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { Menu, Moon, Sun } from 'lucide-react'
import { Button, buttonVariants } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { AuthGate } from '@/components/auth-gate'
import { logout } from '@/lib/api'
import KeysPage from '@/pages/KeysPage'
import PlaygroundPage from '@/pages/PlaygroundPage'
import FallbackPage from '@/pages/FallbackPage'
import EmbeddingsPage from '@/pages/EmbeddingsPage'
import AnalyticsPage from '@/pages/AnalyticsPage'
const queryClient = new QueryClient()
const navItems = [
{ to: '/models', label: 'Models' },
{ to: '/playground', label: 'Playground' },
{ to: '/keys', label: 'Keys' },
{ to: '/analytics', label: 'Analytics' },
]
function getPreferredDarkMode() {
if (typeof window === 'undefined') {
return false
}
const stored = localStorage.getItem('theme')
return stored === 'dark' || (!stored && window.matchMedia('(prefers-color-scheme: dark)').matches)
}
function NavItem({ to, children }: { to: string; children: React.ReactNode }) {
return (
<NavLink
to={to}
className={({ isActive }) =>
`relative text-sm px-1 py-4 transition-colors ${
isActive
? 'text-foreground after:absolute after:inset-x-0 after:-bottom-px after:h-px after:bg-foreground'
: 'text-muted-foreground hover:text-foreground'
}`
}
>
{children}
</NavLink>
)
}
function useDarkMode() {
const [dark, setDark] = useState(getPreferredDarkMode)
useEffect(() => {
document.documentElement.classList.toggle('dark', dark)
}, [dark])
function toggle() {
setDark((current) => {
const next = !current
localStorage.setItem('theme', next ? 'dark' : 'light')
return next
})
}
return { dark, toggle }
}
function DarkModeToggle({ dark, onToggle }: { dark: boolean; onToggle: () => void }) {
return (
<Button
variant="ghost"
size="sm"
onClick={onToggle}
aria-label={dark ? 'Switch to light theme' : 'Switch to dark theme'}
>
{dark ? <Sun /> : <Moon />}
</Button>
)
}
function Brand() {
return (
<Link to="/" className="flex items-center gap-2 transition-opacity hover:opacity-70">
<span className="inline-block size-2 rounded-full bg-foreground" />
<span className="font-semibold tracking-tight text-sm">FreeLLMAPI</span>
</Link>
)
}
// True when the dashboard runs inside the desktop shell (Electron preload
// sets this). The navbar then doubles as the window title bar: draggable,
// padded for the macOS traffic lights, and without the web-only Sign out.
const isDesktopApp = typeof window !== 'undefined' && (window as any).__FREEAPI_DESKTOP__ === true
// The preload's own early classList.add can be lost (it may run before this
// document exists), so the client claims the class itself at module load —
// before the first React paint — keeping html.desktop CSS (transparent body,
// glass backdrop) reliable.
if (isDesktopApp) {
document.documentElement.classList.add('desktop')
}
function Navbar() {
const { dark, toggle } = useDarkMode()
const location = useLocation()
const navigate = useNavigate()
function isActiveRoute(to: string) {
return location.pathname === to
}
return (
<header
// In the desktop shell the body backdrop is already translucent glass;
// a lighter wash keeps the title bar from looking more solid than the page.
className={`sticky top-0 z-40 border-b backdrop-blur ${isDesktopApp ? 'bg-background/45' : 'bg-background/80'}`}
style={isDesktopApp ? ({ WebkitAppRegion: 'drag' } as React.CSSProperties) : undefined}
>
<div
className={`mx-auto flex max-w-6xl items-center px-4 sm:px-6 ${isDesktopApp ? 'pl-20 sm:pl-20' : ''}`}
style={isDesktopApp ? { minHeight: 52 } : undefined}
>
<Brand />
<nav
className="ml-10 hidden items-center gap-6 md:flex"
style={isDesktopApp ? ({ WebkitAppRegion: 'no-drag' } as React.CSSProperties) : undefined}
>
{navItems.map((item) => (
<NavItem key={item.to} to={item.to}>
{item.label}
</NavItem>
))}
</nav>
<div
className="ml-auto hidden items-center gap-1 md:flex"
style={isDesktopApp ? ({ WebkitAppRegion: 'no-drag' } as React.CSSProperties) : undefined}
>
<DarkModeToggle dark={dark} onToggle={toggle} />
{!isDesktopApp && (
<Button variant="ghost" size="sm" onClick={() => logout()}>
Sign out
</Button>
)}
</div>
<div className="ml-auto md:hidden">
<DropdownMenu>
<DropdownMenuTrigger
className={buttonVariants({ variant: 'ghost', size: 'icon' })}
aria-label="Open navigation menu"
>
<Menu />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-52">
<DropdownMenuGroup>
{navItems.map((item) => (
<DropdownMenuItem
key={item.to}
onClick={() => navigate(item.to)}
className={isActiveRoute(item.to) ? 'bg-accent text-accent-foreground font-medium' : undefined}
>
{item.label}
</DropdownMenuItem>
))}
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem onClick={toggle} className="justify-between">
<span>Theme</span>
{dark ? <Sun /> : <Moon />}
</DropdownMenuItem>
{!isDesktopApp && (
<DropdownMenuItem onClick={() => logout()}>Sign out</DropdownMenuItem>
)}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</header>
)
}
function App() {
return (
<QueryClientProvider client={queryClient}>
<BrowserRouter basename={import.meta.env.BASE_URL}>
<AuthGate>
<div className={`min-h-screen ${isDesktopApp ? 'desktop-backdrop' : 'bg-background'}`}>
<Navbar />
<main className="max-w-6xl mx-auto px-6 py-8">
<Routes>
<Route path="/" element={<Navigate to="/models/chat" replace />} />
<Route path="/models" element={<Navigate to="/models/chat" replace />} />
<Route path="/models/chat" element={<FallbackPage />} />
<Route path="/models/embeddings" element={<EmbeddingsPage />} />
<Route path="/playground" element={<PlaygroundPage />} />
<Route path="/keys" element={<KeysPage />} />
<Route path="/fallback" element={<Navigate to="/models/chat" replace />} />
<Route path="/analytics" element={<AnalyticsPage />} />
<Route path="/test" element={<Navigate to="/playground" replace />} />
<Route path="/health" element={<Navigate to="/keys" replace />} />
</Routes>
</main>
</div>
</AuthGate>
</BrowserRouter>
</QueryClientProvider>
)
}
export default App
|