Download src/components/layout/app-shell.tsx from Vrda/clinicpal: direct link, hf CLI and curl.
- Browser
- Download file 3.93 kB
-
https://huggingface.co/spaces/Vrda/clinicpal/resolve/main/src/components/layout/app-shell.tsx
- Command line
-
hf download hf://spaces/Vrda/clinicpal/src/components/layout/app-shell.tsx
-
curl -L -o app-shell.tsx https://huggingface.co/spaces/Vrda/clinicpal/resolve/main/src/components/layout/app-shell.tsx
3.93 kB
| 'use client'; | |
| import { useEffect, type ReactNode } from 'react'; | |
| import { AnimatePresence, motion } from 'framer-motion'; | |
| import { AmbientBackground } from '@/components/effects/ambient-background'; | |
| import { Header } from './header'; | |
| import { MobileTabBar } from './tab-navigation'; | |
| import { useAppStore, useActiveTab } from '@/store/app-store'; | |
| interface AppShellProps { | |
| analyzeContent: ReactNode; | |
| settingsContent: ReactNode; | |
| } | |
| export function AppShell({ analyzeContent, settingsContent }: AppShellProps) { | |
| const activeTab = useActiveTab(); | |
| const setConnectionStatus = useAppStore((state) => state.setConnectionStatus); | |
| const setMedgemmaStatus = useAppStore((state) => state.setMedgemmaStatus); | |
| const setLocalStatus = useAppStore((state) => state.setLocalStatus); | |
| const setAvailableModels = useAppStore((state) => state.setAvailableModels); | |
| const updateSettings = useAppStore((state) => state.updateSettings); | |
| const settings = useAppStore((state) => state.settings); | |
| // Check connections on mount | |
| useEffect(() => { | |
| const abortController = new AbortController(); | |
| const checkConnection = async () => { | |
| // Check Ollama | |
| try { | |
| const response = await fetch('/api/ollama', { | |
| signal: abortController.signal, | |
| }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| setConnectionStatus('connected'); | |
| setAvailableModels(data.data.models); | |
| // Auto-select first model if none selected | |
| if (data.data.models.length > 0 && !settings.selectedModel) { | |
| updateSettings({ selectedModel: data.data.models[0] }); | |
| } | |
| } else { | |
| setConnectionStatus('disconnected'); | |
| } | |
| } catch (error) { | |
| if (error instanceof Error && error.name !== 'AbortError') { | |
| setConnectionStatus('disconnected'); | |
| } | |
| } | |
| // Check MedGemma backend (Vast.ai) | |
| try { | |
| const response = await fetch('/api/analyze-medgemma?provider=local', { | |
| signal: abortController.signal, | |
| }); | |
| const data = await response.json(); | |
| if (data.success && data.data?.connected) { | |
| setMedgemmaStatus('connected'); | |
| } else { | |
| setMedgemmaStatus('disconnected'); | |
| } | |
| } catch (error) { | |
| if (error instanceof Error && error.name !== 'AbortError') { | |
| setMedgemmaStatus('disconnected'); | |
| } | |
| } | |
| // Check Local MLX backend (Mac Studio) | |
| try { | |
| const response = await fetch('/api/analyze-medgemma?provider=local', { | |
| signal: abortController.signal, | |
| }); | |
| const data = await response.json(); | |
| if (data.success && data.data?.connected) { | |
| setLocalStatus('connected'); | |
| } else { | |
| setLocalStatus('disconnected'); | |
| } | |
| } catch (error) { | |
| if (error instanceof Error && error.name !== 'AbortError') { | |
| setLocalStatus('disconnected'); | |
| } | |
| } | |
| }; | |
| checkConnection(); | |
| return () => { | |
| abortController.abort(); | |
| }; | |
| // eslint-disable-next-line react-hooks/exhaustive-deps | |
| }, []); | |
| return ( | |
| <div className="min-h-screen"> | |
| <AmbientBackground /> | |
| <Header /> | |
| {/* Main content area */} | |
| <main className="pt-20 pb-24 md:pb-8 px-4 sm:px-6 lg:px-8"> | |
| <div className="max-w-7xl mx-auto"> | |
| {/* Content */} | |
| <AnimatePresence mode="wait"> | |
| <motion.div | |
| key={activeTab} | |
| initial={{ opacity: 0, y: 10 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| exit={{ opacity: 0, y: -10 }} | |
| transition={{ duration: 0.2 }} | |
| > | |
| {activeTab === 'analyze' ? analyzeContent : settingsContent} | |
| </motion.div> | |
| </AnimatePresence> | |
| </div> | |
| </main> | |
| {/* Mobile bottom tab bar */} | |
| <MobileTabBar /> | |
| </div> | |
| ); | |
| } | |