File size: 3,928 Bytes
9bc2f29 eed2ff1 9bc2f29 | 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 | '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>
);
}
|