Untitled
unknown
plain_text
10 months ago
16 kB
12
Indexable
import React, { useEffect, useMemo, useRef, useState } from "react";
import { LineChart, Line, CartesianGrid, XAxis, YAxis, Tooltip, ResponsiveContainer, AreaChart, Area } from "recharts";
// --- Helpers ---
function clamp(n: number, min = 0, max = 100) {
return Math.max(min, Math.min(max, n));
}
function formatTime(ms: number) {
const totalSec = Math.floor(ms / 1000);
const m = Math.floor(totalSec / 60).toString().padStart(2, "0");
const s = (totalSec % 60).toString().padStart(2, "0");
return `${m}:${s}`;
}
// --- Mock sensors (simulates cognitive state from typing/blinks/HRV) ---
function useMockCognitiveSensors(running: boolean) {
const [score, setScore] = useState(72);
const [typingSpeed, setTypingSpeed] = useState(52); // wpm
const [blinkRate, setBlinkRate] = useState(18); // per min
const [hrv, setHrv] = useState(58); // ms (proxy)
useEffect(() => {
if (!running) return;
const id = setInterval(() => {
// Oscillate score with noise; lower on long sessions to trigger nudges
setScore((prev) => {
const drift = Math.sin(Date.now() / 35000) * 3;
const fatigue = Math.sin(Date.now() / 90000) * -4;
const noise = (Math.random() - 0.5) * 5;
return clamp(prev + drift + fatigue + noise);
});
setTypingSpeed((t) => clamp(t + (Math.random() - 0.5) * 4, 20, 100));
setBlinkRate((b) => clamp(b + (Math.random() - 0.5) * 2, 8, 30));
setHrv((h) => clamp(h + (Math.random() - 0.5) * 3, 35, 95));
}, 1500);
return () => clearInterval(id);
}, [running]);
return { score, typingSpeed, blinkRate, hrv, setScore };
}
// --- Main Component ---
export default function CortexAIMVP() {
const [running, setRunning] = useState(false);
const sessionStartRef = useRef<number | null>(null);
const [nudges, setNudges] = useState<{ id: string; msg: string; type: "break" | "breath" | "task" | "learn"; ts: number }[]>([]);
const [ackNudges, setAckNudges] = useState<Record<string, boolean>>({});
const [points, setPoints] = useState(0);
const [streak, setStreak] = useState(0);
const [dailyHistory, setDailyHistory] = useState<{ time: string; score: number }[]>([]);
const { score, typingSpeed, blinkRate, hrv, setScore } = useMockCognitiveSensors(running);
// --- Simulate real-time logging for daily chart ---
useEffect(() => {
if (!running) return;
const id = setInterval(() => {
const t = new Date();
setDailyHistory((arr) => [...arr.slice(-240), { time: `${t.getHours()}:${t.getMinutes().toString().padStart(2, "0")}`, score }]);
}, 3000);
return () => clearInterval(id);
}, [running, score]);
// --- Nudge rules ---
useEffect(() => {
if (!running) return;
const t = Date.now();
const low = score < 55;
const dropping = dailyHistory.length > 3 && score - dailyHistory[dailyHistory.length - 3].score < -10;
if (low) {
pushNudge("break", "You’re showing signs of fatigue. Take a 3‑minute reset (stand, stretch, hydrate).", t);
} else if (dropping) {
pushNudge("breath", "Focus drop detected. Try a 60‑second guided breathing exercise?", t);
} else if (score > 82 && Math.random() < 0.06) {
pushNudge("task", "You’re sharp right now — great moment to tackle a hard task.", t);
} else if (Math.random() < 0.03) {
pushNudge("learn", "Want a quick 2‑minute recap of what you’re studying?", t);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [score]);
function pushNudge(type: "break" | "breath" | "task" | "learn", msg: string, ts: number) {
// avoid spamming: only add if last nudge > 60s ago of same type
const lastSame = nudges.filter((n) => n.type === type).slice(-1)[0];
if (lastSame && ts - lastSame.ts < 60000) return;
const id = `${type}-${ts}`;
setNudges((arr) => [{ id, msg, type, ts }, ...arr].slice(0, 6));
}
// --- Sessions & gamification ---
const [elapsed, setElapsed] = useState(0);
useEffect(() => {
if (!running) return;
if (sessionStartRef.current == null) sessionStartRef.current = Date.now();
const id = setInterval(() => {
setElapsed(Date.now() - (sessionStartRef.current || Date.now()));
}, 500);
return () => clearInterval(id);
}, [running]);
function startSession() {
setRunning(true);
sessionStartRef.current = Date.now();
setElapsed(0);
setNudges([]);
}
function stopSession() {
setRunning(false);
const durationMin = (Date.now() - (sessionStartRef.current || Date.now())) / 60000;
const sessionPoints = Math.round(durationMin * (score / 10));
setPoints((p) => p + sessionPoints);
setStreak((s) => s + 1);
sessionStartRef.current = null;
}
function acknowledge(id: string, bonus = 5) {
setAckNudges((m) => ({ ...m, [id]: true }));
setPoints((p) => p + bonus);
// small positive bump to score when user follows an intervention
setScore((val) => clamp(val + 4));
}
// --- Weekly summary mock ---
const weekly = useMemo(() => {
// generate 7-day trend using points and random walk
const base = 68;
return Array.from({ length: 7 }).map((_, i) => ({
day: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"][i],
score: clamp(base + Math.sin(i * 0.7) * 8 + (i === 6 ? streak : 0) + (points / 120)),
}));
}, [points, streak]);
const status = score >= 80 ? "⚡ Sharp" : score >= 60 ? "🙂 Fair" : "😴 Fatigued";
// --- Leaderboard mock ---
const leaderboard = [
{ name: "Ava", pts: 1240 },
{ name: "Liam", pts: 1175 },
{ name: "You", pts: points },
{ name: "Maya", pts: 930 },
].sort((a, b) => b.pts - a.pts);
// --- Badges ---
const badges = [
{ id: "streak3", title: "3‑Day Streak", earned: streak >= 3 },
{ id: "focus60", title: "60‑Min Focus", earned: elapsed >= 60 * 60000 },
{ id: "points500", title: "500 MindPoints", earned: points >= 500 },
];
return (
<div className="min-h-screen w-full bg-neutral-950 text-neutral-100">
<div className="max-w-6xl mx-auto p-6">
{/* Header */}
<header className="flex items-center justify-between gap-4">
<div>
<h1 className="text-3xl font-bold tracking-tight">CortexAI</h1>
<p className="text-neutral-400">Optimize your mind · Where neuroscience meets AI</p>
</div>
<div className="flex items-center gap-3">
<div className="px-3 py-1 rounded-full bg-neutral-800 text-sm">Streak: <span className="font-semibold">{streak}d</span></div>
<div className="px-3 py-1 rounded-full bg-neutral-800 text-sm">MindPoints: <span className="font-semibold">{points}</span></div>
{running ? (
<button onClick={stopSession} className="px-4 py-2 rounded-xl bg-pink-600 hover:bg-pink-500 transition">Stop Session</button>
) : (
<button onClick={startSession} className="px-4 py-2 rounded-xl bg-teal-600 hover:bg-teal-500 transition">Start Focus Session</button>
)}
</div>
</header>
{/* Top grid */}
<section className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-6">
{/* Cognitive Score Card */}
<div className="col-span-1 md:col-span-1 bg-neutral-900 rounded-2xl p-5 shadow-lg">
<div className="text-neutral-400 text-sm">Current Cognitive Score</div>
<div className="mt-2 flex items-end gap-3">
<div className="text-6xl font-extrabold">{Math.round(score)}</div>
<span className="text-neutral-400 mb-1">/ 100</span>
</div>
<div className="mt-2 text-teal-400 font-medium">{status}</div>
<div className="mt-4 grid grid-cols-3 gap-3 text-sm">
<div className="bg-neutral-800 rounded-xl p-3">
<div className="text-neutral-400">Typing</div>
<div className="font-semibold">{Math.round(typingSpeed)} wpm</div>
</div>
<div className="bg-neutral-800 rounded-xl p-3">
<div className="text-neutral-400">Blink</div>
<div className="font-semibold">{Math.round(blinkRate)}/min</div>
</div>
<div className="bg-neutral-800 rounded-xl p-3">
<div className="text-neutral-400">HRV</div>
<div className="font-semibold">{Math.round(hrv)} ms</div>
</div>
</div>
<div className="mt-4 text-sm text-neutral-400">Session Time: <span className="text-neutral-200 font-semibold">{formatTime(elapsed)}</span></div>
</div>
{/* Real-time timeline */}
<div className="col-span-1 md:col-span-2 bg-neutral-900 rounded-2xl p-5 shadow-lg">
<div className="text-neutral-300 font-medium mb-3">Today’s Focus Trend</div>
<div className="h-44">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={dailyHistory.length ? dailyHistory : [{ time: "--", score: score }] }>
<defs>
<linearGradient id="grad" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#14b8a6" stopOpacity={0.6}/>
<stop offset="95%" stopColor="#14b8a6" stopOpacity={0}/>
</linearGradient>
</defs>
<CartesianGrid stroke="#27272a" strokeDasharray="3 3" />
<XAxis dataKey="time" stroke="#a1a1aa" hide/>
<YAxis domain={[0, 100]} stroke="#a1a1aa"/>
<Tooltip contentStyle={{ background: "#171717", border: "1px solid #27272a", borderRadius: 12, color: "#e5e5e5" }} />
<Area type="monotone" dataKey="score" stroke="#14b8a6" fill="url(#grad)"/>
</AreaChart>
</ResponsiveContainer>
</div>
<p className="text-neutral-400 text-sm mt-3">Live tracking updates every few seconds during a Focus Session. Nudges trigger on drops or fatigue.</p>
</div>
</section>
{/* Nudges & Actions */}
<section className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-6">
<div className="bg-neutral-900 rounded-2xl p-5 shadow-lg md:col-span-2">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold">AI Nudges</h2>
<span className="text-sm text-neutral-400">Respond to earn MindPoints</span>
</div>
<div className="mt-4 space-y-3">
{nudges.length === 0 && (
<div className="text-neutral-500 text-sm">No nudges yet. Stay sharp — CortexAI will notify you when intervention helps.</div>
)}
{nudges.map((n) => (
<div key={n.id} className="flex items-start justify-between gap-4 bg-neutral-800 rounded-xl p-4">
<div>
<div className="text-sm uppercase tracking-wide text-neutral-400">{n.type}</div>
<div className="font-medium">{n.msg}</div>
<div className="text-xs text-neutral-500 mt-1">{new Date(n.ts).toLocaleTimeString()}</div>
</div>
<div className="flex gap-2 shrink-0">
{!ackNudges[n.id] ? (
<>
{n.type === "break" && (
<button onClick={() => acknowledge(n.id)} className="px-3 py-2 bg-indigo-600 hover:bg-indigo-500 rounded-lg">Take Break</button>
)}
{n.type === "breath" && (
<button onClick={() => acknowledge(n.id)} className="px-3 py-2 bg-teal-600 hover:bg-teal-500 rounded-lg">Start Breathing</button>
)}
{n.type === "task" && (
<button onClick={() => acknowledge(n.id)} className="px-3 py-2 bg-emerald-600 hover:bg-emerald-500 rounded-lg">Start Hard Task</button>
)}
{n.type === "learn" && (
<button onClick={() => acknowledge(n.id)} className="px-3 py-2 bg-pink-600 hover:bg-pink-500 rounded-lg">Get Recap</button>
)}
</>
) : (
<div className="px-3 py-2 rounded-lg bg-neutral-700 text-neutral-300">Acknowledged ✓</div>
)}
</div>
</div>
))}
</div>
</div>
{/* Gamification panel */}
<div className="bg-neutral-900 rounded-2xl p-5 shadow-lg">
<h2 className="text-lg font-semibold">Gamification</h2>
<div className="mt-3">
<div className="text-sm text-neutral-400">Badges</div>
<div className="mt-2 flex flex-wrap gap-2">
{badges.map((b) => (
<span key={b.id} className={`px-3 py-1 rounded-full text-sm ${b.earned ? "bg-teal-700" : "bg-neutral-800 text-neutral-500"}`}>{b.title}</span>
))}
</div>
</div>
<div className="mt-4">
<div className="text-sm text-neutral-400 mb-1">Leaderboard</div>
<div className="space-y-1">
{leaderboard.map((u, i) => (
<div key={u.name} className="flex items-center justify-between bg-neutral-800 rounded-xl px-3 py-2">
<div className="flex items-center gap-3">
<div className="w-6 h-6 rounded-full bg-neutral-700 flex items-center justify-center text-xs">{i + 1}</div>
<div>{u.name}</div>
</div>
<div className="font-semibold">{u.pts}</div>
</div>
))}
</div>
</div>
<button className="mt-4 w-full px-4 py-2 rounded-xl bg-neutral-800 hover:bg-neutral-700">Share my Cognitive Score</button>
</div>
</section>
{/* Reports */}
<section className="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6">
{/* Daily summary */}
<div className="bg-neutral-900 rounded-2xl p-5 shadow-lg">
<h2 className="text-lg font-semibold">Daily Report</h2>
<p className="text-neutral-400 text-sm mt-1">Key insights from today’s focus sessions.</p>
<ul className="mt-3 list-disc list-inside text-neutral-200 space-y-1">
<li>Best window: <span className="text-teal-400">9:00–11:00 AM</span> (historical default)</li>
<li>Largest drop at <span className="text-pink-400">2:30 PM</span> — consider lighter tasks then</li>
<li>{Object.values(ackNudges).filter(Boolean).length} interventions acknowledged · +{Object.values(ackNudges).filter(Boolean).length * 5} MindPoints</li>
</ul>
</div>
{/* Weekly map */}
<div className="bg-neutral-900 rounded-2xl p-5 shadow-lg">
<h2 className="text-lg font-semibold">Weekly Cognitive Map</h2>
<div className="h-48 mt-2">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={weekly}>
<CartesianGrid stroke="#27272a" strokeDasharray="3 3" />
<XAxis dataKey="day" stroke="#a1a1aa" />
<YAxis domain={[0, 100]} stroke="#a1a1aa" />
<Tooltip contentStyle={{ background: "#171717", border: "1px solid #27272a", borderRadius: 12, color: "#e5e5e5" }} />
<Line type="monotone" dataKey="score" stroke="#14b8a6" strokeWidth={3} dot={false} />
</LineChart>
</ResponsiveContainer>
</div>
<p className="text-neutral-400 text-sm mt-3">Recommendation: Schedule deep work in the morning; lighter tasks after 3 PM.</p>
</div>
</section>
{/* Footer */}
<footer className="mt-10 text-center text-neutral-500 text-sm">
CortexAI MVP · v0.1 — Demo with simulated signals (typing, blink, HRV). Real integrations (Apple Watch, keyboard OS hooks, webcam ML) plug into the same interfaces.
</footer>
</div>
</div>
);
}Editor is loading...
Leave a Comment