Untitled

 avatar
unknown
plain_text
10 months ago
16 kB
11
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