Files
unified-media-manager/frontend/src/App.tsx
Christopher Mayor 468519fde1 feat: semantic search with Qdrant + Ollama embeddings
- Add SemanticSearchService with embed() + searchQdrant() methods
- Add GET /api/search/semantic endpoint (?q=query&k=5)
- Wire SemanticSearchService into router and cmd/server/main.go
- Add SemanticSearch React page with results + similarity scores
- Add 'Semantic Search' nav link in App.tsx
- Add unit tests with mocked Ollama + Qdrant HTTP servers (4 tests, all passing)
- Add GitHub issue templates (bug report, feature request)
- Add pull request template
2026-04-24 11:21:26 -07:00

80 lines
3.4 KiB
TypeScript

import { Routes, Route, NavLink } from 'react-router-dom'
import { lazy, Suspense } from 'react'
import { QueryProvider } from './api/queryClient'
import { ToastProvider } from './components/Toast'
import Loading from './components/Loading'
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Library = lazy(() => import('./pages/Library'))
const Discover = lazy(() => import('./pages/Discover'))
const Calendar = lazy(() => import('./pages/Calendar'))
const MediaDetail = lazy(() => import('./pages/MediaDetail'))
const Queue = lazy(() => import('./pages/Queue'))
const Requests = lazy(() => import('./pages/Requests'))
const Activity = lazy(() => import('./pages/Activity'))
const Blocklist = lazy(() => import('./pages/Blocklist'))
const Settings = lazy(() => import('./pages/Settings'))
const Search = lazy(() => import('./pages/Search'))
const SemanticSearch = lazy(() => import('./pages/SemanticSearch'))
const navItems = [
{ to: '/', label: 'Dashboard' },
{ to: '/library', label: 'Library' },
{ to: '/discover', label: 'Discover' },
{ to: '/calendar', label: 'Calendar' },
{ to: '/queue', label: 'Queue' },
{ to: '/search', label: 'Search' },
{ to: '/semantic-search', label: 'Semantic Search' },
{ to: '/activity', label: 'Activity' },
{ to: '/requests', label: 'Requests' },
{ to: '/blocklist', label: 'Blocklist' },
{ to: '/settings', label: 'Settings' },
]
export default function App() {
return (
<QueryProvider>
<ToastProvider>
<div className="min-h-screen bg-gray-950">
<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-50 focus:bg-indigo-600 focus:text-white focus:px-4 focus:py-2 focus:rounded focus:outline-none focus:ring-2 focus:ring-indigo-400">
Skip to content
</a>
<nav className="bg-gray-900 border-b border-gray-800 px-6 py-3 flex items-center gap-6">
<h1 className="text-xl font-bold text-indigo-400">UMM</h1>
{navItems.map(item => (
<NavLink
key={item.to}
to={item.to}
end={item.to === '/'}
className={({ isActive }) =>
`text-sm font-medium ${isActive ? 'text-indigo-400' : 'text-gray-400 hover:text-gray-200'}`
}
>
{item.label}
</NavLink>
))}
</nav>
<main id="main-content" className="p-6">
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/library" element={<Library />} />
<Route path="/discover" element={<Discover />} />
<Route path="/calendar" element={<Calendar />} />
<Route path="/library/:type/:id" element={<MediaDetail />} />
<Route path="/queue" element={<Queue />} />
<Route path="/search" element={<Search />} />
<Route path="/semantic-search" element={<SemanticSearch />} />
<Route path="/activity" element={<Activity />} />
<Route path="/requests" element={<Requests />} />
<Route path="/blocklist" element={<Blocklist />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
</main>
</div>
</ToastProvider>
</QueryProvider>
)
}