clinicpal / src /components /layout /app-shell.tsx
Vrda's picture
Deploy ClinIcPal frontend
eed2ff1 verified
'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>
);
}