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>
  );
}