| 'use client'; |
|
|
| import { useRef, useEffect } from 'react'; |
| import Image from 'next/image'; |
| import { motion } from 'framer-motion'; |
| import { cn } from '@/lib/utils'; |
| import { useConnectionStatus, useMedgemmaStatus, useLocalStatus, useAppStore, useSettings } from '@/store/app-store'; |
| import { GlassTooltip } from '@/components/ui'; |
| import { TabNavigation } from './tab-navigation'; |
|
|
| export function Header() { |
| const connectionStatus = useConnectionStatus(); |
| const medgemmaStatus = useMedgemmaStatus(); |
| const localStatus = useLocalStatus(); |
| const settings = useSettings(); |
| const setConnectionStatus = useAppStore((state) => state.setConnectionStatus); |
| const setAvailableModels = useAppStore((state) => state.setAvailableModels); |
| const updateSettings = useAppStore((state) => state.updateSettings); |
|
|
| |
| const activeStatus = |
| settings.provider === 'local' |
| ? localStatus |
| : settings.provider === 'medgemma' |
| ? medgemmaStatus |
| : connectionStatus; |
| const providerLabel = |
| settings.provider === 'local' |
| ? 'Local' |
| : settings.provider === 'medgemma' |
| ? 'MedGemma' |
| : 'Ollama'; |
|
|
| const abortControllerRef = useRef<AbortController | null>(null); |
|
|
| |
| useEffect(() => { |
| return () => { |
| abortControllerRef.current?.abort(); |
| }; |
| }, []); |
|
|
| const handleRetryConnection = async () => { |
| |
| abortControllerRef.current?.abort(); |
| abortControllerRef.current = new AbortController(); |
|
|
| setConnectionStatus('checking'); |
| try { |
| const response = await fetch('/api/ollama', { |
| signal: abortControllerRef.current.signal, |
| }); |
| const data = await response.json(); |
| if (data.success) { |
| setConnectionStatus('connected'); |
| setAvailableModels(data.data.models); |
| |
| if (data.data.models.length > 0) { |
| updateSettings({ selectedModel: data.data.models[0] }); |
| } |
| } else { |
| setConnectionStatus('disconnected'); |
| } |
| } catch (error) { |
| |
| if (error instanceof Error && error.name !== 'AbortError') { |
| setConnectionStatus('disconnected'); |
| } |
| } |
| }; |
|
|
| return ( |
| <motion.header |
| className={cn( |
| 'fixed top-0 left-0 right-0 z-50', |
| 'glass-elevated', |
| 'border-b border-[var(--glass-border-subtle)]' |
| )} |
| initial={{ y: -100 }} |
| animate={{ y: 0 }} |
| transition={{ duration: 0.4, ease: [0.25, 0.46, 0.45, 0.94] }} |
| > |
| <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> |
| <div className="flex items-center justify-between h-16"> |
| {/* Logo / Title */} |
| <div className="flex items-center gap-3"> |
| <Image |
| src="/clinicpal-iOS-Default-1024x1024@1x.png" |
| alt="ClinicPal" |
| width={36} |
| height={36} |
| /> |
| <h1 className="text-lg font-semibold text-[var(--foreground)]"> |
| Clinipal - Clinical Error Detector |
| </h1> |
| </div> |
| |
| {/* Right side: Tab Navigation + Connection Status */} |
| <div className="flex items-center gap-4"> |
| {/* Tab Navigation */} |
| <div className="hidden md:block"> |
| <TabNavigation /> |
| </div> |
| |
| {/* Connection Status */} |
| <GlassTooltip |
| content={ |
| activeStatus === 'connected' |
| ? `${providerLabel} is connected and ready` |
| : activeStatus === 'disconnected' |
| ? `Cannot connect to ${providerLabel}. Click to retry.` |
| : `Checking connection to ${providerLabel}...` |
| } |
| side="bottom" |
| > |
| <button |
| onClick={ |
| activeStatus === 'disconnected' |
| ? handleRetryConnection |
| : undefined |
| } |
| className={cn( |
| 'flex items-center gap-2 px-3 py-1.5 rounded-full', |
| 'glass text-sm font-medium', |
| 'transition-all duration-200', |
| activeStatus === 'disconnected' && |
| 'hover:bg-[var(--glass-bg-elevated)] cursor-pointer' |
| )} |
| > |
| <span |
| className={cn( |
| 'w-2 h-2 rounded-full', |
| activeStatus === 'connected' && 'bg-green-500 pulse-glow-success', |
| activeStatus === 'disconnected' && 'bg-red-500', |
| activeStatus === 'checking' && 'bg-amber-500 animate-pulse' |
| )} |
| /> |
| <span className="hidden sm:inline"> |
| {activeStatus === 'connected' && `${providerLabel}`} |
| {activeStatus === 'disconnected' && `${providerLabel} Off`} |
| {activeStatus === 'checking' && 'Checking...'} |
| </span> |
| </button> |
| </GlassTooltip> |
| </div> |
| </div> |
| </div> |
| </motion.header> |
| ); |
| } |
|
|