Spaces:
No application file
No application file
File size: 6,241 Bytes
9a16769 | 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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | import { useStore } from '../store';
import { useAIAdaptation } from '../hooks/useAIAdaptation';
interface SliderProps {
label: string;
value: number;
min: number;
max: number;
step: number;
unit: string;
onChange: (v: number) => void;
colorClass?: string;
}
function Slider({ label, value, min, max, step, unit, onChange, colorClass }: SliderProps) {
return (
<div className="flex flex-col gap-1">
<div className="flex justify-between text-xs text-blue-200 font-mono">
<span>{label}</span>
<span className={`font-bold ${colorClass ?? 'text-cyan-300'}`}>
{value.toFixed(1)} {unit}
</span>
</div>
<input
type="range"
min={min}
max={max}
step={step}
value={value}
onChange={(e) => onChange(parseFloat(e.target.value))}
className="slider-thumb w-full h-1 appearance-none rounded cursor-pointer
bg-gradient-to-r from-blue-900 to-cyan-500 accent-cyan-400"
/>
<div className="flex justify-between text-[10px] text-blue-600">
<span>{min}{unit}</span>
<span>{max}{unit}</span>
</div>
</div>
);
}
function Badge({ children, color }: { children: React.ReactNode; color: string }) {
return (
<span className={`px-2 py-0.5 rounded text-xs font-bold ${color}`}>
{children}
</span>
);
}
export default function ControlPanel() {
useAIAdaptation(); // attach AI calls
const {
waveAmplitude, setWaveAmplitude,
temperature, setTemperature,
waveExpansion, setWaveExpansion,
finScale, finDesign, resistance, designNote,
isLoadingAI, aiSource,
} = useStore();
return (
<div
className="absolute top-4 left-4 z-10 flex flex-col gap-4 p-5 rounded-2xl
backdrop-blur-md bg-[rgba(8,20,45,0.82)] border border-blue-800
shadow-2xl text-white select-none"
style={{ width: 280 }}
>
{/* Header */}
<div className="flex items-center gap-2 border-b border-blue-800 pb-3">
<span className="text-2xl">🚢</span>
<div>
<h1 className="text-sm font-bold text-cyan-300 leading-none">NavSim 3D</h1>
<p className="text-[10px] text-blue-400">Simulación Hidrodinámica</p>
</div>
{isLoadingAI && (
<span className="ml-auto text-[10px] text-yellow-300 animate-pulse">⟳ IA activa</span>
)}
</div>
{/* Sliders */}
<Slider
label="🌊 Oleaje (Amplitud)"
value={waveAmplitude}
min={0} max={8} step={0.1}
unit="m"
onChange={setWaveAmplitude}
colorClass="text-cyan-300"
/>
<Slider
label="🌡 Temperatura"
value={temperature}
min={0} max={40} step={0.5}
unit="°C"
onChange={setTemperature}
colorClass="text-orange-300"
/>
<Slider
label="↔ Expansión de Olas"
value={waveExpansion}
min={0.1} max={4.0} step={0.1}
unit="×"
onChange={setWaveExpansion}
colorClass="text-purple-300"
/>
{/* AI Results */}
<div className="border-t border-blue-800 pt-3 flex flex-col gap-2">
<div className="flex items-center justify-between">
<span className="text-xs text-blue-300 font-semibold">🤖 IA Adaptativa</span>
{aiSource === 'huggingface' && <Badge color="bg-green-900 text-green-300">HuggingFace</Badge>}
{aiSource === 'algorithmic' && <Badge color="bg-yellow-900 text-yellow-300">Algorítmico</Badge>}
</div>
{/* Fin Scale meter */}
<div className="flex items-center gap-2">
<span className="text-[10px] text-blue-400 w-20">Aletas</span>
<div className="flex-1 bg-blue-950 rounded h-2">
<div
className="h-2 rounded bg-gradient-to-r from-blue-500 to-cyan-400 transition-all duration-700"
style={{ width: `${((finScale - 0.5) / 1.5) * 100}%` }}
/>
</div>
<span className="text-[11px] text-cyan-300 font-mono w-8">{finScale.toFixed(2)}×</span>
</div>
{/* Fin Design */}
{finDesign && (
<div className="bg-blue-950 rounded-lg p-2 text-[10px] text-blue-300 font-mono grid grid-cols-2 gap-x-3 gap-y-0.5">
<span>Perfil:</span><span className="text-cyan-200">{finDesign.profile}</span>
<span>Envergadura:</span><span className="text-cyan-200">{finDesign.span} m</span>
<span>Flecha:</span><span className="text-cyan-200">{finDesign.sweepAngle}°</span>
<span>Espesor:</span><span className="text-cyan-200">{(finDesign.thickness * 100).toFixed(1)}%</span>
<span>Cantidad:</span><span className="text-cyan-200">{finDesign.quantity}</span>
</div>
)}
{/* Resistance */}
{resistance && (
<div className="bg-blue-950 rounded-lg p-2 text-[10px] font-mono">
<p className="text-blue-400 mb-1 font-semibold">Resistencia (N)</p>
<div className="grid grid-cols-2 gap-x-3 gap-y-0.5">
<span className="text-blue-300">Oleaje:</span>
<span className="text-red-300">{resistance.waveResistance.toLocaleString()}</span>
<span className="text-blue-300">Viscosa:</span>
<span className="text-orange-300">{resistance.viscousResistance.toLocaleString()}</span>
<span className="text-blue-300">Aletas:</span>
<span className="text-yellow-300">{resistance.finPenalty.toLocaleString()}</span>
<span className="text-blue-200 font-bold">Total:</span>
<span className="text-white font-bold">{resistance.totalResistance.toLocaleString()}</span>
<span className="text-blue-300">Fn:</span>
<span className="text-purple-300">{resistance.froudeNumber}</span>
<span className="text-blue-300">Re:</span>
<span className="text-purple-300">{resistance.reynoldsNumber}</span>
</div>
</div>
)}
{/* Design note */}
<p className="text-[9px] text-blue-500 italic leading-tight">{designNote}</p>
</div>
</div>
);
}
|