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 { Languages, Menu, MoreHorizontal, Moon, Sun } from 'lucide-react'
import { buttonVariants } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { AuthGate } from '@/components/auth-gate'
import { I18nProvider, useI18n, SUPPORTED_LOCALES, type Locale } from '@/i18n'
import { logout } from '@/lib/api'
import KeysPage from '@/pages/KeysPage'
import PlaygroundPage from '@/pages/PlaygroundPage'
import FallbackPage from '@/pages/FallbackPage'
import ModelDetailPage from '@/pages/ModelDetailPage'
import FusionPage from '@/pages/FusionPage'
import EmbeddingsPage from '@/pages/EmbeddingsPage'
import ImagePage from '@/pages/ImagePage'
import AudioPage from '@/pages/AudioPage'
import MediaDetailPage from '@/pages/MediaDetailPage'
import EmbeddingDetailPage from '@/pages/EmbeddingDetailPage'
import AnalyticsPage from '@/pages/AnalyticsPage'
import PremiumPage from '@/pages/PremiumPage'
const queryClient = new QueryClient()
const navItems = [
{ to: '/models', labelKey: 'nav.models' },
{ to: '/playground', labelKey: 'nav.playground' },
{ to: '/keys', labelKey: 'nav.keys' },
{ to: '/analytics', labelKey: 'nav.analytics' },
{ to: '/premium', labelKey: 'nav.premium' },
]
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 (
`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}
)
}
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 Brand() {
return (
FreeLLMAPI
)
}
// 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')
}
// Language picker as a dropdown submenu, shared by the desktop (⋯) and mobile
// (☰) menus. Radio items show a check on the active locale; selecting one calls
// setLocale, which persists and re-renders every t() synchronously.
function LanguageSubMenu() {
const { locale, setLocale, t } = useI18n()
return (
{t('nav.language')} setLocale(v as Locale)}>
{SUPPORTED_LOCALES.map((code) => (
{t(`languages.${code}`)}
))}
)
}
function Navbar() {
const { dark, toggle } = useDarkMode()
const { t } = useI18n()
const location = useLocation()
const navigate = useNavigate()
function isActiveRoute(to: string) {
return location.pathname === to
}
return (