260 lines
12 KiB
TypeScript
260 lines
12 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { Sparkles, Brain, Zap, Heart, Eye, TrendingUp, AlertTriangle, CheckCircle } from 'lucide-react';
|
|
|
|
interface NeuroScoreProps {
|
|
analysis: {
|
|
scores: {
|
|
dopamine: number;
|
|
serotonin: number;
|
|
cognitiveEase: number;
|
|
commercialFit: number;
|
|
};
|
|
feedback: string[];
|
|
improvements: {
|
|
dopamine: string[];
|
|
serotonin: string[];
|
|
cognitiveEase: string[];
|
|
commercialFit: string[];
|
|
};
|
|
prediction: string;
|
|
} | null;
|
|
loading?: boolean;
|
|
onApplyImprovement?: (suggestion: string) => void;
|
|
}
|
|
|
|
const ScoreBar = ({ label, score, icon: Icon, color, barColor, onClick, isSelected }: { label: string; score: number; icon: any; color: string; barColor: string, onClick?: () => void, isSelected?: boolean }) => (
|
|
<div
|
|
onClick={onClick}
|
|
className={`mb-4 transition-all duration-300 ${onClick ? 'cursor-pointer hover:bg-white/5 p-2 -mx-2 rounded-lg' : ''} ${isSelected ? 'bg-white/10 ring-1 ring-white/20' : ''}`}
|
|
>
|
|
<div className="flex justify-between items-center mb-1">
|
|
<div className="flex items-center gap-2 text-stone-300">
|
|
<Icon className={`w-4 h-4 ${color}`} />
|
|
<span className="text-sm font-medium">{label}</span>
|
|
</div>
|
|
<span className={`text-sm font-bold ${score >= 8 ? 'text-green-400' : score >= 5 ? 'text-yellow-400' : 'text-red-400'}`}>
|
|
{score}/10
|
|
</span>
|
|
</div>
|
|
<div className="h-2 bg-stone-800/50 rounded-full overflow-hidden backdrop-blur-sm border border-white/5">
|
|
<motion.div
|
|
initial={{ width: 0 }}
|
|
animate={{ width: `${score * 10}%` }}
|
|
transition={{ duration: 1, ease: "easeOut" }}
|
|
className={`h-full ${barColor}`}
|
|
/>
|
|
</div>
|
|
{isSelected && (
|
|
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="text-[10px] text-stone-400 mt-1 italic text-center">
|
|
Viewing fixes for this metric...
|
|
</motion.div>
|
|
)}
|
|
</div>
|
|
);
|
|
|
|
const NeuroScorecard: React.FC<NeuroScoreProps> = ({ analysis, loading, onApplyImprovement }) => {
|
|
const [selectedMetric, setSelectedMetric] = useState<string | null>(null);
|
|
|
|
// Get fixes based on selection or aggregate all
|
|
const currentImprovements = React.useMemo(() => {
|
|
if (!analysis?.improvements) return [];
|
|
|
|
// Map UI labels to API keys
|
|
const keyMap: Record<string, keyof typeof analysis.improvements> = {
|
|
'Dopamine': 'dopamine',
|
|
'Serotonin': 'serotonin',
|
|
'Cognitive Ease': 'cognitiveEase',
|
|
'Commercial Fit': 'commercialFit'
|
|
};
|
|
|
|
if (selectedMetric && keyMap[selectedMetric]) {
|
|
return analysis.improvements[keyMap[selectedMetric]] || [];
|
|
}
|
|
|
|
// Return all improvements flattened if nothing selected
|
|
return [
|
|
...analysis.improvements.dopamine,
|
|
...analysis.improvements.serotonin,
|
|
...analysis.improvements.cognitiveEase,
|
|
...analysis.improvements.commercialFit
|
|
];
|
|
}, [analysis, selectedMetric]);
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="p-8 rounded-2xl bg-white/5 backdrop-blur-xl border border-white/10 flex flex-col items-center justify-center text-center min-h-[400px]">
|
|
<motion.div
|
|
animate={{ rotate: 360 }}
|
|
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
|
|
className="mb-6 relative"
|
|
>
|
|
<div className="absolute inset-0 bg-blue-500/20 blur-xl rounded-full" />
|
|
<Brain className="w-16 h-16 text-blue-400 relative z-10" />
|
|
</motion.div>
|
|
<h3 className="text-xl font-bold text-white mb-2">Analyzing Neuro-Triggers...</h3>
|
|
<p className="text-stone-400 max-w-xs">Connecting to Gemini Vision to evaluate Dopamine, Serotonin, and Market Fit.</p>
|
|
|
|
<div className="mt-8 flex gap-2">
|
|
{[0, 1, 2].map((i) => (
|
|
<motion.div
|
|
key={i}
|
|
animate={{ scale: [1, 1.5, 1], opacity: [0.3, 1, 0.3] }}
|
|
transition={{ duration: 1, repeat: Infinity, delay: i * 0.2 }}
|
|
className="w-2 h-2 rounded-full bg-blue-400"
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!analysis) return null;
|
|
|
|
return (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
className="bg-stone-900/40 backdrop-blur-2xl border border-white/10 rounded-2xl overflow-hidden shadow-2xl"
|
|
>
|
|
{/* Header */}
|
|
<div className="p-6 border-b border-white/5 bg-gradient-to-r from-blue-500/10 to-purple-500/10">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 bg-blue-500/20 rounded-lg">
|
|
<Brain className="w-6 h-6 text-blue-400" />
|
|
</div>
|
|
<div>
|
|
<h2 className="text-xl font-bold text-white">Neuro-Scorecard</h2>
|
|
<p className="text-xs text-stone-400">AI-Predicted Market Performance</p>
|
|
</div>
|
|
</div>
|
|
<div className={`px-4 py-1.5 rounded-full border ${analysis.prediction.includes('High') ? 'bg-green-500/20 border-green-500/30 text-green-400' :
|
|
analysis.prediction.includes('Medium') ? 'bg-yellow-500/20 border-yellow-500/30 text-yellow-400' :
|
|
'bg-red-500/20 border-red-500/30 text-red-400'
|
|
} text-sm font-semibold flex items-center gap-2`}>
|
|
<TrendingUp className="w-4 h-4" />
|
|
{analysis.prediction}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-0 divide-y md:divide-y-0 md:divide-x divide-white/10">
|
|
|
|
{/* Column 1: Scores */}
|
|
<div className="p-6 bg-black/20">
|
|
<h3 className="text-sm font-bold text-white/50 uppercase tracking-widest mb-6 flex justify-between items-center">
|
|
Vital Signs
|
|
{selectedMetric && (
|
|
<button onClick={() => setSelectedMetric(null)} className="text-[10px] text-blue-400 hover:underline">
|
|
Show All
|
|
</button>
|
|
)}
|
|
</h3>
|
|
<ScoreBar
|
|
label="Dopamine Hit (Excitement)"
|
|
score={analysis.scores.dopamine}
|
|
icon={Zap}
|
|
color="text-yellow-400"
|
|
barColor="bg-yellow-400"
|
|
onClick={() => setSelectedMetric('Dopamine')}
|
|
isSelected={selectedMetric === 'Dopamine'}
|
|
/>
|
|
<ScoreBar
|
|
label="Serotonin Flow (Trust/Calm)"
|
|
score={analysis.scores.serotonin}
|
|
icon={Heart}
|
|
color="text-pink-400"
|
|
barColor="bg-pink-400"
|
|
onClick={() => setSelectedMetric('Serotonin')}
|
|
isSelected={selectedMetric === 'Serotonin'}
|
|
/>
|
|
<ScoreBar
|
|
label="Cognitive Ease (Clarity)"
|
|
score={analysis.scores.cognitiveEase}
|
|
icon={Eye}
|
|
color="text-blue-400"
|
|
barColor="bg-blue-400"
|
|
onClick={() => setSelectedMetric('Cognitive Ease')}
|
|
isSelected={selectedMetric === 'Cognitive Ease'}
|
|
/>
|
|
<ScoreBar
|
|
label="Commercial Fit (Pro Quality)"
|
|
score={analysis.scores.commercialFit}
|
|
icon={Sparkles}
|
|
color="text-purple-400"
|
|
barColor="bg-purple-400"
|
|
onClick={() => setSelectedMetric('Commercial Fit')}
|
|
isSelected={selectedMetric === 'Commercial Fit'}
|
|
/>
|
|
</div>
|
|
|
|
{/* Column 2: Improvements */}
|
|
<div className="p-6 relative">
|
|
<h3 className="text-sm font-bold text-red-400/80 uppercase tracking-widest mb-6 flex items-center gap-2">
|
|
<AlertTriangle className="w-4 h-4" />
|
|
{selectedMetric ? `${selectedMetric} Fixes` : 'Critical Fixes'}
|
|
</h3>
|
|
|
|
<ul className="space-y-4 max-h-[300px] overflow-y-auto pr-2 scrollbar-thin scrollbar-thumb-white/10">
|
|
{currentImprovements.length > 0 ? (
|
|
currentImprovements.map((fix, idx) => (
|
|
<motion.li
|
|
key={idx}
|
|
initial={{ opacity: 0, x: -10 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ delay: idx * 0.1 }}
|
|
className="flex items-start gap-3 text-stone-300 text-sm group"
|
|
>
|
|
<div className="w-1.5 h-1.5 rounded-full bg-red-400 mt-1.5 flex-shrink-0 group-hover:scale-150 transition-transform" />
|
|
<div className="flex-1">
|
|
<span className="leading-relaxed block mb-2">{fix}</span>
|
|
{onApplyImprovement && (
|
|
<button
|
|
onClick={() => onApplyImprovement(fix)}
|
|
className="text-[10px] bg-red-500/10 hover:bg-red-500/20 text-red-300 px-2 py-1 rounded border border-red-500/20 transition-colors uppercase tracking-wider font-bold"
|
|
>
|
|
+ Add to Refine
|
|
</button>
|
|
)}
|
|
</div>
|
|
</motion.li>
|
|
))
|
|
) : (
|
|
<div className="text-center py-8">
|
|
<p className="text-stone-500 text-xs italic mb-4">
|
|
No specific fixes found for this metric.
|
|
</p>
|
|
</div>
|
|
)}
|
|
</ul>
|
|
</div>
|
|
|
|
{/* Column 3: Feedback */}
|
|
<div className="p-6 bg-white/2">
|
|
<h3 className="text-sm font-bold text-green-400/80 uppercase tracking-widest mb-6 flex items-center gap-2">
|
|
<CheckCircle className="w-4 h-4" />
|
|
Winning Traits
|
|
</h3>
|
|
<ul className="space-y-4">
|
|
{analysis.feedback.map((point, idx) => (
|
|
<motion.li
|
|
key={idx}
|
|
initial={{ opacity: 0, x: -10 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ delay: idx * 0.1 + 0.2 }}
|
|
className="flex items-start gap-3 text-stone-300 text-sm"
|
|
>
|
|
<div className="w-1.5 h-1.5 rounded-full bg-green-400 mt-1.5 flex-shrink-0" />
|
|
<span className="leading-relaxed">{point}</span>
|
|
</motion.li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
export default NeuroScorecard;
|