LifeStack / templates /index.html
Soham Banerjee
deploy: pure lifestack with partitioned wisdom pool
77da5ce
Raw History Blame Contribute Delete
96 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>LifeStack Portal | Meta OpenEnv 2026</title> <script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script> <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;800&display=swap" rel="stylesheet"> <style> body { font-family: 'Plus Jakarta Sans', sans-serif; background-color: #0f172a; color: #f8fafc; overflow-x: hidden; } .glass { background: rgba(15, 23, 42, 0.85); backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.08); box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37); } .tab-active { border-bottom: 2px solid #818cf8; color: #818cf8; } .metric-bar { transition: width 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); } @keyframes pulse-red { 0%, 100% { background-color: #ef4444; } 50% { background-color: #f87171; } } .pulse-red { animation: pulse-red 1s infinite; } ::-webkit-scrollbar { width: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: #334155; border-radius: 10px; } ::-webkit-scrollbar-thumb:hover { background: #475569; } .custom-scrollbar::-webkit-scrollbar { width: 4px; } .custom-scrollbar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); } </style> </head> <body class="p-4 md:p-8"> <div class="max-w-6xl mx-auto"> <!-- Header --> <header class="flex justify-between items-center mb-8"> <div> <h1 class="text-3xl font-extrabold tracking-tight text-transparent bg-clip-text bg-gradient-to-r from-indigo-400 to-cyan-400">πŸͺ LifeStack Engine</h1> <p class="text-slate-400 text-sm">Meta Γ— HuggingFace OpenEnv Hackathon Finale</p> </div> <div class="hidden md:block text-right"> <span class="px-3 py-1 rounded-full bg-indigo-500/10 text-indigo-400 text-xs font-bold border border-indigo-500/20">PRODUCTION HARDENED v3.0</span> </div> </header> <!-- Navigation Tabs --> <div class="flex gap-6 border-b border-slate-800 mb-8 overflow-x-auto no-scrollbar whitespace-nowrap"> <button onclick="showTab('situation')" id="tab-situation" class="pb-4 text-sm font-semibold transition tab-active">Situational Portal</button> <button onclick="showTab('custom')" id="tab-custom" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">πŸ’­ Try Your Case</button> <button onclick="showTab('compare')" id="tab-compare" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">⚑ Untrained vs GRPO-Trained</button> <button onclick="showTab('memeffect')" id="tab-memeffect" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">🧠 Memory Effect</button> <button onclick="showTab('arjun')" id="tab-arjun" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">πŸ—“οΈ Arjun's Journey</button> <button onclick="showTab('history')" id="tab-history" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">πŸ“Ό Episode Replay</button> <button onclick="showTab('lab')" id="tab-lab" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">🎭 Personality Lab</button> <button onclick="showTab('whatif')" id="tab-whatif" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">🧩 What-If Lab</button> <button onclick="showTab('tasks')" id="tab-tasks" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">πŸ—ΊοΈ Task Explorer</button> <button onclick="showTab('performance')" id="tab-performance" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">πŸ“Š Analytics</button> <button onclick="showTab('verification')" id="tab-verification" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">πŸ“¬ Verification</button> </div> <!-- ── TAB 1: SITUATION PORTAL ── --> <div id="content-situation" class="grid grid-cols-1 lg:grid-cols-3 gap-8"> <div class="lg:col-span-1 space-y-6"> <div class="glass p-6 rounded-2xl"> <h3 class="text-lg font-bold mb-4">Simulation Control</h3> <div class="space-y-4 mb-6"> <div class="text-sm"> <label class="block text-slate-400 mb-2">Subject Persona</label> <select id="person-select" onchange="resetDemoUI()" class="w-full bg-slate-900 border border-slate-700 rounded-lg px-4 py-2 text-white"> {% for p in persons %} <option value="{{ p }}">{{ p }}</option> {% endfor %} </select> </div> <div class="text-sm"> <label class="block text-slate-400 mb-2">Life Conflict</label> <select id="conflict-select" onchange="resetDemoUI()" class="w-full bg-slate-900 border border-slate-700 rounded-lg px-4 py-2 text-white"> {% for c in conflicts %} <option value="{{ c }}">{{ c }}</option> {% endfor %} </select> </div> </div> <div class="flex items-center justify-between p-3 bg-indigo-500/5 rounded-xl border border-indigo-500/20"> <div class="text-[10px] uppercase font-black text-indigo-400">Context Augmentation (RAG)</div> <label class="relative inline-flex items-center cursor-pointer"> <input type="checkbox" id="memory-toggle" class="sr-only peer"> <div class="w-9 h-5 bg-slate-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-indigo-600"></div> </label> </div> <button onclick="startDemo()" id="simulate-btn" class="w-full py-4 bg-indigo-600 hover:bg-indigo-500 rounded-xl font-bold transition flex items-center justify-center gap-2"> <span id="btn-icon">β–Ά</span> <span id="btn-text">Start Simulation</span> </button> </div> <!-- Trajectroy --> <div id="trajectory-panel" class="hidden glass p-6 rounded-2xl border-l-4 border-yellow-500"> <h4 class="text-[10px] font-bold text-slate-500 tracking-widest uppercase mb-2">AI Risk Prediction</h4> <p id="traj-summary" class="text-sm text-slate-200 mb-4 italic leading-relaxed">Analyzing...</p> <div class="h-1.5 bg-slate-800 rounded-full overflow-hidden"><div id="traj-bar" class="h-full bg-yellow-500 transition-all duration-700" style="width: 0%"></div></div> </div> </div> <div class="lg:col-span-1 glass p-6 rounded-2xl relative overflow-hidden"> <h3 class="text-lg font-bold flex justify-between mb-2"><span>Vital Metrics</span><span id="metric-phase" class="text-[10px] bg-slate-800 px-2 py-1 rounded text-slate-400 uppercase tracking-tighter">STABLE</span></h3> <!-- Domain Risk Heatmap --> <div class="mb-4"> <div class="text-[9px] font-black text-slate-600 uppercase tracking-widest mb-2">Domain Risk Heatmap</div> <div id="domain-heatmap" class="grid grid-cols-6 gap-1.5"> <div class="flex flex-col items-center gap-1"> <div class="w-full h-8 rounded-lg bg-slate-700 transition-all duration-700" id="hm-career" title="Career"></div> <span class="text-[8px] text-slate-500 font-bold">WORK</span> </div> <div class="flex flex-col items-center gap-1"> <div class="w-full h-8 rounded-lg bg-slate-700 transition-all duration-700" id="hm-finances" title="Finances"></div> <span class="text-[8px] text-slate-500 font-bold">MONEY</span> </div> <div class="flex flex-col items-center gap-1"> <div class="w-full h-8 rounded-lg bg-slate-700 transition-all duration-700" id="hm-relationships" title="Relationships"></div> <span class="text-[8px] text-slate-500 font-bold">SOCIAL</span> </div> <div class="flex flex-col items-center gap-1"> <div class="w-full h-8 rounded-lg bg-slate-700 transition-all duration-700" id="hm-physical_health" title="Physical Health"></div> <span class="text-[8px] text-slate-500 font-bold">BODY</span> </div> <div class="flex flex-col items-center gap-1"> <div class="w-full h-8 rounded-lg bg-slate-700 transition-all duration-700" id="hm-mental_wellbeing" title="Mental Wellbeing"></div> <span class="text-[8px] text-slate-500 font-bold">MIND</span> </div> <div class="flex flex-col items-center gap-1"> <div class="w-full h-8 rounded-lg bg-slate-700 transition-all duration-700" id="hm-time" title="Time"></div> <span class="text-[8px] text-slate-500 font-bold">TIME</span> </div> </div> </div> <div id="metrics-container" class="space-y-2.5 relative z-10 max-h-[520px] overflow-y-auto pr-2 custom-scrollbar"></div> <!-- Mini-Graph Overlay --> <div id="cascade-graph" class="absolute inset-0 opacity-10 pointer-events-none"></div> </div> <div class="lg:col-span-1 space-y-6"> <div id="action-card" class="hidden glass p-6 rounded-2xl border-l-4 border-indigo-500"> <div class="flex justify-between items-start mb-4"> <h3 id="action-type" class="text-xl font-extrabold text-indigo-400 tracking-tighter uppercase">--</h3> <div id="episode-tag" class="text-[10px] font-mono bg-indigo-500/20 px-2 py-1 rounded">ID: --</div> </div> <p id="action-desc" class="text-sm text-slate-300 mb-4">--</p> <div class="p-4 bg-slate-950/50 rounded-xl border border-slate-800 mb-4"> <p id="action-reasoning" class="text-xs text-slate-400 italic">--</p> </div> <div class="grid grid-cols-2 gap-3 text-xs font-bold"> <div class="p-3 bg-slate-900 rounded-xl">⏱️ <span id="action-time">--</span></div> <div class="p-3 bg-slate-900 rounded-xl">πŸ’΅ <span id="action-money">--</span></div> </div> </div> <div id="counterfactual-card" class="hidden glass p-6 rounded-2xl"><h4 class="text-xs font-bold text-slate-500 tracking-widest uppercase mb-4">βš–οΈ What-If Analysis</h4><div id="cf-container" class="space-y-3 text-[11px]"></div></div> <!-- Memory Retrieval Insight --> <div id="memory-card" class="hidden glass p-6 rounded-2xl border-l-4 border-green-500"> <h4 class="text-[10px] font-black text-green-500 tracking-widest uppercase mb-3 flex justify-between"><span>Retrieved Precedents (RAG)</span> <span class="bg-green-500/20 px-2 py-0.5 rounded text-white">+116% EFFICIENCY</span></h4> <div id="memory-container" class="space-y-3"></div> </div> </div> </div> <!-- ── 7-DAY FORECAST (spans full width below the portal) ── --> <div id="forecast-panel" class="hidden glass p-6 rounded-2xl mt-6 border-t-2 border-cyan-500/30"> <div class="flex justify-between items-center mb-4"> <div> <h3 class="text-base font-black tracking-tighter text-cyan-400">7-DAY LIFE TRAJECTORY</h3> <p class="text-[10px] text-slate-500 mt-0.5">What happens to your six domains over the next week if nothing extraordinary occurs β€” computed from the real LifeStack env rollout, not estimated.</p> </div> <div class="text-right"> <div class="text-[9px] font-black text-slate-600 uppercase tracking-widest">Discounted Reward (Ξ³=0.9)</div> <div id="forecast-total-reward" class="text-xl font-black text-cyan-400">--</div> </div> </div> <div class="relative h-52"> <canvas id="trajectoryChart"></canvas> </div> <!-- Per-day step summary --> <div id="forecast-steps" class="grid grid-cols-7 gap-2 mt-4 text-[9px]"></div> </div> <!-- ── TAB 2: TRY YOUR CASE ── --> <div id="content-custom" class="hidden space-y-6"> <div class="grid grid-cols-1 lg:grid-cols-4 gap-6"> <!-- Left: inputs --> <div class="glass p-6 rounded-2xl space-y-6"> <h3 class="text-xl font-bold">Describe Your Situation</h3> <textarea id="custom-situation" class="w-full bg-slate-950 border border-slate-800 rounded-xl p-4 text-sm text-slate-200 outline-none focus:border-indigo-500 h-32" placeholder="e.g. My boss gave me a huge deadline today, but it's my partner's birthday and I'm already exhausted..."></textarea> <div class="grid grid-cols-2 gap-4 text-xs font-bold uppercase text-slate-500"> <div class="space-y-4"> <label>πŸ’Ό Work Stress <input type="range" id="custom-work" class="w-full accent-indigo-500"></label> <label>πŸ’° Money Stress <input type="range" id="custom-money" class="w-full accent-indigo-500"></label> </div> <div class="space-y-4"> <label>❀️ Relationships <input type="range" id="custom-rel" class="w-full accent-indigo-500"></label> <label>⚑ Energy <input type="range" id="custom-energy" class="w-full accent-indigo-500"></label> </div> </div> <button onclick="runDigitalSync()" id="digital-sync-btn" class="w-full py-3 bg-gradient-to-r from-cyan-700 to-indigo-700 rounded-xl text-sm font-bold shadow-lg hover:opacity-90 transition flex items-center justify-center gap-2"> <span id="sync-icon">πŸ“‘</span> <span id="sync-text">Sync Digital Life (Gmail + Calendar + Fitness)</span> </button> <button onclick="runCustom()" class="w-full py-3 bg-indigo-600 rounded-xl text-sm font-bold shadow-lg shadow-indigo-500/20">Analyze & Plan</button> </div> <!-- Centre: Digital Sync panel --> <div id="digital-sync-panel" class="glass p-6 rounded-2xl space-y-4"> <div class="text-[9px] font-black text-slate-500 uppercase tracking-widest mb-2">Digital Life Signals</div> <div id="sync-idle" class="text-center py-16 text-slate-600 text-sm italic">Click "Sync Digital Life" to pull signals from your connected services.</div> <div id="sync-sources" class="hidden space-y-4"> <!-- Gmail source card --> <div class="p-4 rounded-xl border border-slate-700 bg-slate-900/50"> <div class="flex justify-between items-center mb-2"> <div class="flex items-center gap-2"> <span class="text-base">πŸ“§</span> <span class="text-xs font-black text-slate-300 uppercase tracking-wider">Gmail</span> </div> <span id="gmail-badge" class="text-[8px] font-black px-2 py-0.5 rounded-full border uppercase"></span> </div> <p id="gmail-sync-summary" class="text-[10px] text-slate-400 italic leading-relaxed"></p> <div id="gmail-threads" class="mt-2 space-y-1"></div> </div> <!-- Calendar source card --> <div class="p-4 rounded-xl border border-slate-700 bg-slate-900/50"> <div class="flex justify-between items-center mb-2"> <div class="flex items-center gap-2"> <span class="text-base">πŸ“…</span> <span class="text-xs font-black text-slate-300 uppercase tracking-wider">Google Calendar</span> </div> <span id="cal-badge" class="text-[8px] font-black px-2 py-0.5 rounded-full border uppercase"></span> </div> <p id="cal-sync-summary" class="text-[10px] text-slate-400 italic leading-relaxed"></p> <div id="cal-deadlines" class="mt-2 space-y-1"></div> </div> <!-- Fitness source card --> <div class="p-4 rounded-xl border border-slate-700 bg-slate-900/50"> <div class="flex justify-between items-center mb-2"> <div class="flex items-center gap-2"> <span class="text-base">πŸ’ͺ</span> <span class="text-xs font-black text-slate-300 uppercase tracking-wider">Fitness</span> </div> <span id="fit-badge" class="text-[8px] font-black px-2 py-0.5 rounded-full border uppercase bg-amber-500/10 border-amber-500/30 text-amber-400">Demo</span> </div> <p id="fit-sync-summary" class="text-[10px] text-slate-400 italic leading-relaxed"></p> <div id="fit-stats" class="mt-2 grid grid-cols-3 gap-2"></div> </div> </div> </div> <!-- Centre-right: Manual upload cards --> <div class="space-y-4"> <!-- Health Upload --> <div class="glass p-4 rounded-2xl border border-slate-700"> <div class="text-[9px] font-black text-slate-500 uppercase tracking-widest mb-3">πŸ’ͺ Health Data Upload</div> <div class="grid grid-cols-3 gap-2 mb-3"> <div> <label class="text-[9px] text-slate-500 uppercase font-bold">Sleep (h)</label> <input type="number" id="health-sleep" value="7" step="0.5" min="0" max="12" class="w-full bg-slate-900 border border-slate-700 rounded-lg px-2 py-1 text-xs text-white mt-1"> </div> <div> <label class="text-[9px] text-slate-500 uppercase font-bold">HR (bpm)</label> <input type="number" id="health-hr" value="70" min="40" max="120" class="w-full bg-slate-900 border border-slate-700 rounded-lg px-2 py-1 text-xs text-white mt-1"> </div> <div> <label class="text-[9px] text-slate-500 uppercase font-bold">Steps/day</label> <input type="number" id="health-steps" value="8000" step="500" min="0" max="30000" class="w-full bg-slate-900 border border-slate-700 rounded-lg px-2 py-1 text-xs text-white mt-1"> </div> </div> <button onclick="uploadHealthData()" class="w-full py-2 bg-emerald-700 hover:bg-emerald-600 rounded-lg text-xs font-bold transition">Upload Health Data</button> <p id="health-upload-status" class="text-[9px] text-slate-500 italic mt-2"></p> </div> <!-- Calendar Upload --> <div class="glass p-4 rounded-2xl border border-slate-700"> <div class="text-[9px] font-black text-slate-500 uppercase tracking-widest mb-3">πŸ“… Calendar Data Upload</div> <div class="grid grid-cols-2 gap-2 mb-3"> <div> <label class="text-[9px] text-slate-500 uppercase font-bold">Occupancy %</label> <input type="number" id="cal-occupancy" value="60" min="0" max="100" class="w-full bg-slate-900 border border-slate-700 rounded-lg px-2 py-1 text-xs text-white mt-1"> </div> <div> <label class="text-[9px] text-slate-500 uppercase font-bold">Back-to-back</label> <input type="number" id="cal-btb" value="2" min="0" max="20" class="w-full bg-slate-900 border border-slate-700 rounded-lg px-2 py-1 text-xs text-white mt-1"> </div> </div> <div class="mb-3"> <label class="text-[9px] text-slate-500 uppercase font-bold">Critical Deadlines (count)</label> <input type="number" id="cal-critical" value="1" min="0" max="20" class="w-full bg-slate-900 border border-slate-700 rounded-lg px-2 py-1 text-xs text-white mt-1"> </div> <button onclick="uploadCalendarData()" class="w-full py-2 bg-blue-700 hover:bg-blue-600 rounded-lg text-xs font-bold transition">Upload Calendar Data</button> <p id="cal-upload-status" class="text-[9px] text-slate-500 italic mt-2"></p> </div> </div> <!-- Right: result --> <div id="custom-result" class="hidden glass p-6 rounded-2xl overflow-y-auto max-h-[620px]"> <div class="flex justify-between items-center mb-6"><h3 class="text-lg font-bold">Personalised Resolution</h3><span id="custom-id" class="text-[10px] font-mono opacity-40">ID: --</span></div> <div id="custom-metrics-grid" class="mb-8"></div> <div id="custom-plan" class="p-4 bg-indigo-500/10 border border-indigo-500/20 rounded-xl space-y-4"></div> </div> </div> </div> <!-- ── TAB A: UNTRAINED LLM VS GRPO-TRAINED ── --> <div id="content-compare" class="hidden space-y-6"> <div class="glass p-8 rounded-2xl"> <div class="flex flex-col md:flex-row gap-6 items-end mb-8"> <div class="flex-1 space-y-2"> <div class="text-[9px] font-black text-slate-500 uppercase tracking-widest">Conflict Scenario</div> <select id="cmp-conflict" class="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-3 text-sm"> {% for c in conflicts %} <option value="{{ c }}">{{ c }}</option> {% endfor %} </select> </div> <div class="flex-1 space-y-2"> <div class="text-[9px] font-black text-slate-500 uppercase tracking-widest">Subject Persona</div> <select id="cmp-person" class="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-3 text-sm"> {% for p in persons %} <option value="{{ p }}">{{ p }}</option> {% endfor %} </select> </div> <button onclick="runComparison()" id="cmp-btn" class="px-10 py-3 bg-gradient-to-r from-indigo-600 to-cyan-600 rounded-xl font-bold shadow-lg shadow-indigo-600/20 active:scale-95 transition whitespace-nowrap">⚑ Run Comparison</button> </div> <div id="cmp-intro" class="text-center py-12 text-slate-500 italic">Select a conflict and run to see how GRPO training changes the LLM's decisions.</div> <div id="cmp-content" class="hidden grid grid-cols-1 md:grid-cols-2 gap-6"> <!-- Baseline (Untrained LLM) --> <div class="glass p-6 rounded-2xl border-t-4 border-slate-600 bg-slate-700/5"> <div class="flex justify-between items-center mb-4"> <div> <div class="text-[9px] font-black text-slate-500 uppercase tracking-widest">No RL Training</div> <h4 class="text-xl font-black text-slate-300 mt-0.5">Untrained LLM</h4> </div> <div class="text-right"> <div id="cmp-reward-baseline" class="text-2xl font-black text-slate-400">--</div> <div class="text-[8px] text-slate-600 uppercase font-black">reward</div> </div> </div> <div class="p-3 bg-slate-900/60 rounded-xl border border-slate-700 mb-4"> <div class="text-[9px] font-black text-slate-600 uppercase mb-1">Strategy</div> <p id="cmp-action-baseline" class="text-sm font-bold text-slate-300">--</p> <p id="cmp-reason-baseline" class="text-xs text-slate-500 italic mt-1">--</p> </div> <div id="cmp-metrics-baseline" class="space-y-1.5 text-[10px]"></div> </div> <!-- Trained --> <div class="glass p-6 rounded-2xl border-t-4 border-indigo-500 bg-indigo-500/5"> <div class="flex justify-between items-center mb-4"> <div> <div class="text-[9px] font-black text-indigo-400 uppercase tracking-widest">GRPO-Trained LifeStack</div> <h4 class="text-xl font-black text-white mt-0.5">LifeStack Agent</h4> </div> <div class="text-right"> <div id="cmp-reward-trained" class="text-2xl font-black text-indigo-400">--</div> <div class="text-[8px] text-indigo-600 uppercase font-black">reward</div> </div> </div> <div class="p-3 bg-indigo-950/50 rounded-xl border border-indigo-500/20 mb-4"> <div class="text-[9px] font-black text-indigo-500 uppercase mb-1">Strategy</div> <p id="cmp-action-trained" class="text-sm font-bold text-indigo-300">--</p> <p id="cmp-reason-trained" class="text-xs text-slate-400 italic mt-1">--</p> </div> <div id="cmp-metrics-trained" class="space-y-1.5 text-[10px]"></div> </div> </div> <!-- Delta badge --> <div id="cmp-delta" class="hidden mt-6 text-center"> <span class="inline-block px-6 py-2 rounded-full bg-green-500/10 border border-green-500/30 text-green-400 font-black text-sm"></span> </div> </div> </div> <!-- ── TAB E: MEMORY EFFECT ── --> <div id="content-memeffect" class="hidden space-y-6"> <div class="glass p-8 rounded-2xl"> <div class="flex justify-between items-start mb-6"> <div> <h3 class="text-2xl font-black tracking-tighter">Memory Effect Demo</h3> <p class="text-slate-400 text-sm mt-1 max-w-xl">Same conflict, same agent. Episode 1 runs cold (no prior context). Episode 2 retrieves the stored memory and reasons differently β€” showing the RAG flywheel in action.</p> </div> <div class="px-3 py-1 bg-green-500/10 border border-green-500/20 rounded text-[9px] font-black text-green-400 uppercase tracking-widest whitespace-nowrap">+116% Efficiency</div> </div> <div class="flex flex-col md:flex-row gap-6 items-end mb-8"> <div class="flex-1 space-y-2"> <div class="text-[9px] font-black text-slate-500 uppercase tracking-widest">Conflict</div> <select id="mem-conflict" class="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-3 text-sm"> {% for c in conflicts %} <option value="{{ c }}">{{ c }}</option> {% endfor %} </select> </div> <div class="flex-1 space-y-2"> <div class="text-[9px] font-black text-slate-500 uppercase tracking-widest">Persona</div> <select id="mem-person" class="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-3 text-sm"> {% for p in persons %} <option value="{{ p }}">{{ p }}</option> {% endfor %} </select> </div> <button onclick="runMemoryEffect()" id="mem-btn" class="px-10 py-3 bg-gradient-to-r from-green-700 to-emerald-600 rounded-xl font-bold active:scale-95 transition whitespace-nowrap">🧠 Run Both Episodes</button> </div> <div id="mem-intro" class="text-center py-12 text-slate-500 italic">Run both episodes to see how memory transforms the agent's reasoning.</div> <div id="mem-content" class="hidden grid grid-cols-1 md:grid-cols-2 gap-6"> <!-- Cold --> <div class="glass p-6 rounded-2xl border-t-4 border-slate-600"> <div class="flex justify-between items-center mb-4"> <div> <div class="text-[9px] font-black text-slate-500 uppercase tracking-widest">Episode 1 β€” Cold Start</div> <h4 class="text-lg font-black text-slate-300">No Memory</h4> </div> <span id="mem-reward-cold" class="text-xl font-black text-slate-400">--</span> </div> <div class="p-3 bg-slate-900/60 rounded-xl border border-slate-700 mb-4"> <p id="mem-action-cold" class="text-sm font-bold text-slate-300 mb-1">--</p> <p id="mem-reason-cold" class="text-xs text-slate-500 italic">--</p> </div> <div id="mem-metrics-cold" class="space-y-1.5 text-[10px]"></div> </div> <!-- Warm --> <div class="glass p-6 rounded-2xl border-t-4 border-green-500"> <div class="flex justify-between items-center mb-4"> <div> <div class="text-[9px] font-black text-green-400 uppercase tracking-widest">Episode 2 β€” With Memory</div> <h4 class="text-lg font-black text-white">RAG-Augmented</h4> </div> <span id="mem-reward-warm" class="text-xl font-black text-green-400">--</span> </div> <div class="p-3 bg-green-950/30 rounded-xl border border-green-500/20 mb-4"> <p id="mem-action-warm" class="text-sm font-bold text-green-300 mb-1">--</p> <p id="mem-reason-warm" class="text-xs text-slate-400 italic">--</p> </div> <div id="mem-retrieved" class="mb-4 space-y-2 text-[10px]"></div> <div id="mem-metrics-warm" class="space-y-1.5 text-[10px]"></div> </div> </div> <div id="mem-delta" class="hidden mt-6 text-center"> <span class="inline-block px-6 py-2 rounded-full bg-green-500/10 border border-green-500/30 text-green-400 font-black text-sm"></span> </div> </div> </div> <!-- ── TAB 3.5: PERSONALITY LAB ── --> <div id="content-lab" class="hidden space-y-8"> <div class="glass p-8 rounded-2xl"> <div class="flex flex-col md:flex-row gap-6 items-end mb-8"> <div class="flex-1 w-full"> <label class="block text-xs font-black text-slate-500 uppercase mb-2">Constant Conflict</label> <select id="lab-conflict" class="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-3 text-sm"> {% for c in conflicts %} <option value="{{ c }}">{{ c }}</option> {% endfor %} </select> </div> <div class="flex-1 w-full"> <label class="block text-xs font-black text-slate-500 uppercase mb-2">Persona A</label> <select id="lab-person-a" class="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-3 text-sm"> {% for p in persons %} <option value="{{ p }}" {% if loop.index0 == 0 %}selected{% endif %}>{{ p }}</option> {% endfor %} </select> </div> <div class="flex-1 w-full"> <label class="block text-xs font-black text-slate-500 uppercase mb-2">Persona B</label> <select id="lab-person-b" class="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-3 text-sm"> {% for p in persons %} <option value="{{ p }}" {% if loop.index0 == 2 %}selected{% endif %}>{{ p }}</option> {% endfor %} </select> </div> <button onclick="comparePersons()" id="compare-btn" class="px-8 py-3 bg-indigo-600 rounded-xl font-bold shadow-lg shadow-indigo-600/20 active:scale-95 transition">πŸ”₯ Compare Response</button> </div> <!-- OCEAN Radar Chart row --> <div id="ocean-radar-row" class="hidden mb-8 glass p-6 rounded-2xl border border-indigo-500/20"> <div class="text-[9px] font-black text-indigo-400 uppercase tracking-widest mb-4">OCEAN Personality Radar</div> <div class="relative h-64 max-w-md mx-auto"> <canvas id="oceanChart"></canvas> </div> <p class="text-[9px] text-slate-600 text-center mt-2 uppercase font-black">Openness Β· Conscientiousness Β· Extraversion Β· Agreeableness Β· Neuroticism</p> </div> <div class="grid grid-cols-1 md:grid-cols-2 gap-8"> <!-- Column A --> <div id="lab-col-a" class="hidden glass p-6 rounded-2xl border-t-4 border-indigo-500 space-y-6"> <div class="flex justify-between items-center"><h4 id="lab-name-a" class="font-black text-xl tracking-tighter">--</h4><span id="lab-reward-a" class="bg-indigo-500/20 px-2 py-1 rounded text-xs font-bold text-indigo-400">Score: --</span></div> <div id="lab-metrics-a" class="space-y-2"></div> <div class="p-4 bg-slate-950/50 rounded-xl border border-slate-800"><p id="lab-action-a" class="text-sm font-bold text-indigo-300 mb-2">--</p><p id="lab-reason-a" class="text-xs text-slate-400 italic">--</p></div> </div> <!-- Column B --> <div id="lab-col-b" class="hidden glass p-6 rounded-2xl border-t-4 border-pink-500 space-y-6"> <div class="flex justify-between items-center"><h4 id="lab-name-b" class="font-black text-xl tracking-tighter">--</h4><span id="lab-reward-b" class="bg-pink-500/20 px-2 py-1 rounded text-xs font-bold text-pink-400">Score: --</span></div> <div id="lab-metrics-b" class="space-y-2"></div> <div class="p-4 bg-slate-950/50 rounded-xl border border-slate-800"><p id="lab-action-b" class="text-sm font-bold text-pink-300 mb-2">--</p><p id="lab-reason-b" class="text-xs text-slate-400 italic">--</p></div> </div> </div> </div> </div> <!-- ── TAB 3: ARJUN'S JOURNEY ── --> <div id="content-arjun" class="hidden glass p-8 rounded-2xl"> <h3 class="text-2xl font-black mb-4">πŸ—“οΈ Longitudinal Context Persistence</h3> <p class="text-slate-400 mb-8 max-w-2xl">By activating Arjun's history, the agent retrieves memories of past decisions from **ChromaDB**. This transforms a zero-shot LLM into a context-aware coach that understands your specific relationship dynamics and work history.</p> <div class="bg-indigo-600/20 border border-indigo-500/30 p-6 rounded-2xl mb-8"> <h4 class="text-indigo-400 font-bold mb-2">Experience the v3 Flywheel:</h4> <ol class="text-sm list-decimal list-inside space-y-2 text-slate-300"> <li>Click <b>Activate History</b> to seed ChromaDB with Arjun's Week 1 & 2 data.</li> <li>Go back to <b>Situational Portal</b>.</li> <li>Select <b>Arjun (Startup Lead)</b> and run the simulation.</li> <li><b>Observe Reasoning:</b> The agent will explicitly mention precedents from his past (e.g., flight cancellations or funding stress).</li> </ol> </div> <button onclick="activateArjun()" id="arjun-btn" class="px-8 py-4 bg-indigo-600 rounded-xl font-bold shadow-xl shadow-indigo-500/30 active:scale-95 transition">πŸ”— Activate History (Seed ChromaDB)</button> <p id="arjun-status" class="mt-4 text-xs font-bold text-green-400"></p> </div> <!-- ── TAB 3.7: WHAT-IF LAB ── --> <div id="content-whatif" class="hidden space-y-8"> <div class="glass p-8 rounded-2xl"> <div class="flex justify-between items-center mb-8"> <h3 class="text-2xl font-black italic tracking-tighter">COUNTERFACTUAL EXPLORER</h3> <div class="px-4 py-1 bg-indigo-500/10 rounded text-[10px] font-black text-indigo-400 uppercase tracking-widest border border-indigo-500/20">Causal Reasoning Engine</div> </div> <div id="whatif-placeholder" class="text-center py-20 text-slate-500 italic">Run a simulation in the Situational Portal to unlock deep counterfactual analysis.</div> <div id="whatif-content" class="hidden grid grid-cols-1 md:grid-cols-3 gap-6"> <!-- Actual Choice --> <div class="glass p-6 rounded-2xl border-t-4 border-indigo-500 bg-indigo-500/5"> <div class="flex justify-between items-start mb-4"> <h4 class="text-[10px] font-black text-indigo-400 uppercase tracking-widest">ACTUAL CHOICE</h4> <span id="wi-reward-actual" class="text-xs font-black">--</span> </div> <h5 id="wi-name-actual" class="text-xl font-black mb-2 uppercase text-white">--</h5> <p id="wi-desc-actual" class="text-xs text-slate-400 mb-6 italic leading-relaxed">--</p> <div id="wi-metrics-actual" class="space-y-1.5 opacity-80 scale-90 origin-top"></div> </div> <!-- CF 1 --> <div class="glass p-6 rounded-2xl border-t-4 border-slate-700"> <div class="flex justify-between items-start mb-4"> <h4 class="text-[10px] font-black text-slate-500 uppercase tracking-widest">ALTERNATIVE A</h4> <span id="wi-reward-cf1" class="text-xs font-black">--</span> </div> <h5 id="wi-name-cf1" class="text-xl font-black mb-2 uppercase text-slate-200">--</h5> <p id="wi-desc-cf1" class="text-xs text-slate-400 mb-6 italic leading-relaxed">--</p> <div id="wi-metrics-cf1" class="space-y-1.5 opacity-80 scale-90 origin-top"></div> <div id="wi-tradeoff-cf1" class="mt-4 p-3 bg-red-500/5 rounded-lg text-[9px] text-red-300 font-bold uppercase select-none">--</div> </div> <!-- CF 2 --> <div class="glass p-6 rounded-2xl border-t-4 border-slate-700"> <div class="flex justify-between items-start mb-4"> <h4 class="text-[10px] font-black text-slate-500 uppercase tracking-widest">ALTERNATIVE B</h4> <span id="wi-reward-cf2" class="text-xs font-black">--</span> </div> <h5 id="wi-name-cf2" class="text-xl font-black mb-2 uppercase text-slate-200">--</h5> <p id="wi-desc-cf2" class="text-xs text-slate-400 mb-6 italic leading-relaxed">--</p> <div id="wi-metrics-cf2" class="space-y-1.5 opacity-80 scale-90 origin-top"></div> <div id="wi-tradeoff-cf2" class="mt-4 p-3 bg-red-500/5 rounded-lg text-[9px] text-red-300 font-bold uppercase select-none">--</div> </div> </div> </div> </div> <!-- ── TAB 3.8: EPISODE HISTORY ── --> <div id="content-history" class="hidden space-y-8"> <div class="glass p-8 rounded-2xl"> <div class="flex justify-between items-center mb-10"> <div> <h3 class="text-2xl font-black italic tracking-tighter">EPISODE REPLAY</h3> <p class="text-xs text-slate-500 uppercase font-bold tracking-widest mt-1">Audit log of agent reasoning & causal chains</p> </div> </div> <div id="history-container" class="space-y-6"> <div class="text-center py-20 text-slate-500 italic">No episodes logged yet. Start a simulation in the Portal!</div> </div> </div> </div> <!-- ── TAB 4: TASKS ── --> <div id="content-tasks" class="hidden space-y-6"> <div class="glass p-8 rounded-2xl"> <div class="flex justify-between items-center mb-6"> <h3 class="text-xl font-bold">🎯 Objective: <span id="task-goal" class="text-indigo-400 underline decoration-indigo-500/30">Select a task</span></h3> <button onclick="loadTaskDemo()" class="px-4 py-2 bg-white/5 rounded-lg text-xs font-bold border border-white/10">πŸ”„ Load Sample Task</button> </div> <div id="task-details" class="grid grid-cols-1 md:grid-cols-2 gap-8 text-sm"> <div class="p-6 bg-slate-900/50 rounded-2xl border border-slate-800"> <h4 class="text-[10px] uppercase font-black text-green-400 tracking-widest mb-4">πŸ›£οΈ Viable Routes</h4> <div id="task-routes" class="space-y-4"></div> </div> <div class="p-6 bg-slate-900/50 rounded-2xl border border-slate-800"> <h4 class="text-[10px] uppercase font-black text-yellow-400 tracking-widest mb-4">⚑ Progressive Milestones</h4> <div id="task-milestones" class="space-y-2"></div> </div> </div> </div> <div class="glass p-8 rounded-2xl"> <h4 class="text-[10px] uppercase font-black text-red-500 tracking-widest mb-4">β˜„οΈ World Event Log</h4> <div id="task-events" class="space-y-3"></div> </div> </div> <!-- ── TAB 5: ANALYTICS ── --> <div id="content-performance" class="hidden grid grid-cols-1 md:grid-cols-2 gap-8"> <div class="glass p-6 rounded-2xl flex flex-col items-center justify-center min-h-[400px]"> <h3 class="text-lg font-bold mb-6 w-full">Learning efficiency (GRPO)</h3> <canvas id="performanceChart"></canvas> </div> <div class="glass p-6 rounded-2xl"> <h3 class="text-lg font-bold mb-6">Memory Persistence</h3> <div id="stats-container" class="grid grid-cols-2 gap-4"></div> <div class="mt-8 p-6 bg-indigo-500/5 border border-indigo-500/10 rounded-2xl text-center"> <p class="text-xs text-slate-400 uppercase font-black tracking-widest mb-2">Model Strategy</p> <p class="text-sm font-bold italic text-indigo-300 italic">"Prioritizing long-term relationship preservation over short-term career gain in high-neuroticism subjects."</p> </div> </div> </div> <!-- ── TAB 6: VERIFICATION ── --> <div id="content-verification" class="hidden max-w-2xl mx-auto glass p-8 rounded-2xl"> <h3 class="text-xl font-bold mb-2">πŸ“¬ Outcome Signal</h3> <p class="text-slate-400 text-xs mb-8">Record the actual result of the agent's plan to close the reinforcement loop. High-fidelity feedback stored in ChromaDB improves future planning.</p> <form id="feedback-form" onsubmit="submitFeedback(event)" class="space-y-6"> <div><label class="block text-[10px] uppercase font-black text-slate-600 mb-2">Episode Trace ID</label> <input name="episode_id" required class="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-3 text-sm focus:border-indigo-500 outline-none"></div> <div class="grid grid-cols-2 gap-4"> <div><label class="block text-[10px] uppercase font-black text-slate-600 mb-2">Actual Hours</label> <input name="time" type="number" step="0.1" value="2.0" class="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-3 text-sm"></div> <div><label class="block text-[10px] uppercase font-black text-slate-600 mb-2">Success Score (0-10)</label> <input name="score" type="range" min="0" max="10" step="1" class="w-full mt-4 bg-slate-800 appearance-none h-1.5 rounded-full accent-indigo-500"></div> </div> <div><label class="block text-[10px] uppercase font-black text-slate-600 mb-2">Unexpected Deviations</label> <textarea name="notes" class="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-3 text-xs" rows="3" placeholder="Did anything unexpected happen?"></textarea></div> <button type="submit" class="w-full py-4 bg-indigo-600 rounded-xl font-bold shadow-lg shadow-indigo-600/20 active:scale-95 transition mt-4">Broadcast Feedback</button> <p id="fb-status" class="text-center font-bold text-xs mt-4"></p> </form> </div> </div> <script> const DOMAIN_EMOJI = { "career": "πŸ’Ό", "finances": "πŸ’°", "relationships": "❀️", "physical_health": "πŸ’ͺ", "mental_wellbeing": "🧠", "time": "πŸ“…" }; const INVERTED_METRICS = new Set(["stress_level", "debt_pressure", "workload", "commute_burden", "admin_overhead"]); let globalGmailSignals = null; let network = null; let graphData = { nodes: new vis.DataSet([]), edges: new vis.DataSet([]) }; async function initGraph() { const res = await fetch('/api/simulation/graph'); const data = await res.json(); // Map groups to colors const groupColors = { career: "#818cf8", finances: "#fbbf24", relationships: "#f87171", physical_health: "#4ade80", mental_wellbeing: "#22d3ee", time: "#a78bfa" }; const nodes = data.nodes.map(n => ({ ...n, color: { background: "#1e293b", border: groupColors[n.group], highlight: { background: groupColors[n.group], border: "#fff" } }, font: { color: "#94a3b8", size: 10 }, shape: "dot", size: 8 })); graphData.nodes.add(nodes); graphData.edges.add(data.edges); const container = document.getElementById('cascade-graph'); const options = { physics: { stabilization: true, barnesHut: { gravitationalConstant: -2000 } }, nodes: { borderWidth: 2 }, edges: { width: 1, smooth: { type: "continuous" } } }; network = new vis.Network(container, graphData, options); } window.addEventListener('DOMContentLoaded', initGraph); function showTab(id) { document.querySelectorAll('[id^="content-"]').forEach(el => el.classList.add('hidden')); document.getElementById('content-' + id).classList.remove('hidden'); document.querySelectorAll('[id^="tab-"]').forEach(el => el.classList.remove('border-b-2', 'border-indigo-500', 'text-white')); document.getElementById('tab-' + id).classList.add('border-b-2', 'border-indigo-500', 'text-white'); if (id === 'history') fetchHistory(); if (id === 'performance') loadStats(); } async function fetchHistory() { const res = await fetch('/api/history'); const data = await res.json(); const container = document.getElementById('history-container'); if (data.length === 0) return; container.innerHTML = data.map(ep => ` <div class="glass p-6 rounded-2xl border-l-4 border-indigo-500 bg-indigo-500/5 transition hover:bg-indigo-500/10 mb-4"> <div class="flex justify-between items-start mb-4"> <div> <span class="text-[10px] font-black text-indigo-400 uppercase tracking-widest bg-indigo-500/10 px-2 py-0.5 rounded">EPISODE ${ep.action.id}</span> <h4 class="text-lg font-black mt-1">${ep.conflict.title}</h4> <p class="text-[10px] text-slate-400 uppercase font-bold">${ep.conflict.person} β€’ ${ep.timestamp}</p> </div> <div class="text-right"> <div class="text-sm font-black text-indigo-400">${(ep.action.reward * 100).toFixed(0)}% RESOLVED</div> <div class="text-[9px] text-slate-500 uppercase font-bold">REWARD: ${ep.action.reward.toFixed(3)}</div> </div> </div> <div class="grid grid-cols-1 md:grid-cols-2 gap-6 items-center"> <div> <div class="text-[9px] font-black text-slate-500 uppercase tracking-widest mb-2">Internal Reasoning</div> <p class="text-xs text-slate-300 italic leading-relaxed">"${ep.action.reasoning}"</p> <div class="mt-4 flex gap-4"> <div class="bg-slate-800 p-2 rounded-lg"> <div class="text-[8px] text-slate-500 uppercase font-black mb-1">Choice</div> <div class="text-[10px] font-black text-white uppercase">${ep.action.type}</div> </div> <div class="bg-slate-800 p-2 rounded-lg"> <div class="text-[8px] text-slate-500 uppercase font-black mb-1">Target</div> <div class="text-[10px] font-black text-white uppercase">${ep.action.target}</div> </div> </div> </div> <div class="bg-slate-900/50 p-4 rounded-xl border border-slate-800"> <div class="text-[9px] font-black text-slate-500 uppercase tracking-widest mb-3">Metric Impact Grid</div> <div class="grid grid-cols-3 gap-2"> ${Object.entries(ep.metrics).slice(0, 9).map(([k, v]) => ` <div class="text-center"> <div class="text-[8px] text-slate-600 uppercase truncate">${k.split('.')[1]}</div> <div class="text-[10px] font-bold ${v < 40 ? 'text-red-400' : 'text-green-400'}">${v.toFixed(0)}%</div> </div> `).join('')} </div> </div> </div> </div> `).join(''); } async function loadStats() { const res = await fetch('/api/stats'); const stats = await res.json(); document.getElementById('stats-container').innerHTML = ` <div class="bg-indigo-500/10 p-5 rounded-2xl text-center border border-indigo-500/10 select-none"> <div class="text-[9px] uppercase font-black text-indigo-400 mb-1">Memories Seeded</div> <div class="text-3xl font-black">${stats.total_memories}</div> </div> <div class="bg-indigo-500/10 p-5 rounded-2xl text-center border border-indigo-500/10 select-none"> <div class="text-[9px] uppercase font-black text-indigo-400 mb-1">Feedback Loops</div> <div class="text-3xl font-black">${stats.feedback_count}</div> </div> `; initChart(stats.reward_history || [0.1, 0.2, 0.45, 0.61, 0.58, 0.65, 0.81, 0.79, 0.88, 0.91]); } function initChart(history) { const ctx = document.getElementById('performanceChart').getContext('2d'); if (window.myChart) window.myChart.destroy(); window.myChart = new Chart(ctx, { type: 'line', data: { labels: history.map((_, i) => `Ep ${i+1}`), datasets: [{ label: 'Reward', data: history, borderColor: '#818cf8', tension: 0.4, fill: true, backgroundColor: 'rgba(129, 140, 248, 0.05)' }] }, options: { maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true, grid: { color: 'rgba(255,255,255,0.05)' } }, x: { grid: { display: false } } } } }); } function getMetricColor(key, val) { const sub = key.split('.')[1]; if (INVERTED_METRICS.has(sub)) return val > 70 ? '#ef4444' : (val > 40 ? '#facc15' : '#4ade80'); return val > 70 ? '#4ade80' : (val > 40 ? '#facc15' : '#ef4444'); } function renderMetrics(containerId, metrics, statusMap = {}, before = {}) { const container = document.getElementById(containerId); if (!container) return; let html = ''; const domains = ["career", "finances", "relationships", "physical_health", "mental_wellbeing", "time"]; // Update Domain Risk Heatmap if (containerId === 'metrics-container') { domains.forEach(dom => { const cell = document.getElementById('hm-' + dom); if (!cell) return; const submetrics = Object.entries(metrics).filter(([k]) => k.startsWith(dom + '.')); if (!submetrics.length) return; // Flip inverted metrics so health is always "higher = better" const healthScores = submetrics.map(([k, v]) => { const sub = k.split('.')[1]; return INVERTED_METRICS.has(sub) ? (100 - v) : v; }); const avgHealth = healthScores.reduce((a, b) => a + b, 0) / healthScores.length; const minHealth = Math.min(...healthScores); // Blend: if ANY metric tanks below 35, drag the whole domain toward it. // This prevents a crisis in one metric being hidden by healthy siblings. const blended = minHealth < 35 ? (avgHealth * 0.35 + minHealth * 0.65) : avgHealth; // Thresholds calibrated to the actual ~30–70 range of baseline health scores const color = blended > 58 ? '#4ade80' // green β€” domain healthy : blended > 36 ? '#facc15' // yellow β€” domain stressed : '#ef4444'; // red β€” domain in crisis cell.style.backgroundColor = color; cell.title = `${dom.replace('_',' ')}: ${blended.toFixed(0)} health (min ${minHealth.toFixed(0)})`; }); } domains.forEach(dom => { html += `<div class="mt-5 first:mt-0 font-black text-[10px] text-indigo-400/80 uppercase tracking-widest border-b border-slate-800/50 pb-1.5 mb-2.5">${dom.replace('_',' ')}</div>`; Object.entries(metrics).filter(([k]) => k.startsWith(dom + '.')).forEach(([key, val]) => { const status = statusMap[key] || 'unchanged'; const color = status === 'unchanged' ? getMetricColor(key, val) : ''; const label = key.split('.')[1].replace(/_/g, ' '); let statusClass = (status === 'primary') ? 'pulse-red' : (status === 'first') ? 'bg-orange-500' : (status === 'second') ? 'bg-yellow-500' : (status === 'improved') ? 'bg-green-500' : 'bg-slate-700'; html += ` <div class="flex items-center gap-3 text-[11px] mb-2 group"> <div class="w-28 text-slate-400 capitalize truncate group-hover:text-slate-200 transition-colors">${label}</div> <div class="flex-1 h-2 bg-slate-950 rounded-full overflow-hidden self-center border border-white/5"> <div class="metric-bar h-full rounded-full ${statusClass}" style="width: ${val}%; background-color: ${color}"></div> </div> <div class="w-8 text-right font-black tabular-nums text-slate-300">${val.toFixed(0)}</div> </div>`; }); }); container.innerHTML = html; } function _setBtnState(icon, text, disabled = true) { const btn = document.getElementById('simulate-btn'); btn.disabled = disabled; document.getElementById('btn-icon').innerText = icon; document.getElementById('btn-text').innerText = text; } async function startDemo() { _setBtnState('πŸŒ€', 'Loading State...'); try { const person = document.getElementById('person-select').value; const conflict = document.getElementById('conflict-select').value; // 1. Initial State const startRes = await fetch('/api/simulation/start', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({conflict}) }); if (!startRes.ok) throw new Error(`/api/simulation/start β†’ ${startRes.status}`); const startData = await startRes.json(); renderMetrics('metrics-container', startData.metrics); // 2. Cascade Animation _setBtnState('⚑', 'Simulating Cascade...'); const cascadeRes = await fetch('/api/simulation/cascade', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({conflict}) }); if (!cascadeRes.ok) throw new Error(`/api/simulation/cascade β†’ ${cascadeRes.status}`); const cascadeData = await cascadeRes.json(); const phaseNames = ["STABLE", "DISRUPTION", "1ST CASCADE", "2ND CASCADE"]; for (let i = 0; i < cascadeData.frames.length; i++) { document.getElementById('metric-phase').innerText = phaseNames[i] || 'CASCADING'; const frame = cascadeData.frames[i]; renderMetrics('metrics-container', frame.flat, frame.status); const activeNodes = Object.entries(frame.status).filter(([k, v]) => v !== 'unchanged').map(([k]) => k); graphData.nodes.update(graphData.nodes.get().map(n => ({ id: n.id, size: activeNodes.includes(n.id) ? 15 : 8, font: { color: activeNodes.includes(n.id) ? "#fff" : "#94a3b8", size: activeNodes.includes(n.id) ? 12 : 10 }, shadow: activeNodes.includes(n.id) ? { enabled: true, color: "#fff", size: 10 } : { enabled: false } }))); await new Promise(r => setTimeout(r, 900)); } setTimeout(() => { graphData.nodes.update(graphData.nodes.get().map(n => ({ id: n.id, size: 8, font: { color: "#94a3b8", size: 10 }, shadow: { enabled: false } }))); }, 1000); // 3. AI Agent Decision (this is the slow step β€” LLM call) _setBtnState('🧠', 'AI Agent Deciding...'); const useMemory = document.getElementById('memory-toggle').checked; const actionRes = await fetch('/api/simulation/action', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({person, conflict, use_memory: useMemory}) }); if (!actionRes.ok) throw new Error(`/api/simulation/action β†’ ${actionRes.status}`); const finalData = await actionRes.json(); await new Promise(r => setTimeout(r, 600)); document.getElementById('metric-phase').innerText = 'RESOLVED'; renderMetrics('metrics-container', finalData.metrics, {}, startData.metrics); // Update UI cards document.getElementById('action-card').classList.remove('hidden'); document.getElementById('action-type').innerText = finalData.action.type; document.getElementById('action-desc').innerText = finalData.action.description; document.getElementById('action-reasoning').innerText = finalData.action.reasoning; document.getElementById('action-time').innerText = finalData.action.cost.time.toFixed(1) + 'h'; document.getElementById('action-money').innerText = '$' + (finalData.action.cost.money || 0); document.getElementById('episode-tag').innerText = `ID: ${finalData.action.id}`; if (finalData.action.memories_retrieved?.length > 0) { document.getElementById('memory-card').classList.remove('hidden'); document.getElementById('memory-container').innerHTML = finalData.action.memories_retrieved.map(m => ` <div class="p-2.5 bg-green-500/5 border border-green-500/10 rounded-xl"> <div class="flex justify-between text-[9px] font-black text-green-400 mb-1"> <span class="uppercase">Similar Conflict: ${m.action_type}</span> <span>Score: ${m.reward.toFixed(2)}</span> </div> <p class="text-[10px] text-slate-300 italic leading-tight">"${m.reasoning.substring(0, 80)}..."</p> </div> `).join(''); } else { document.getElementById('memory-card').classList.add('hidden'); } document.getElementById('trajectory-panel').classList.remove('hidden'); document.getElementById('traj-summary').innerText = finalData.prediction.summary; document.getElementById('traj-bar').style.width = (finalData.prediction.risk_score * 100) + '%'; document.getElementById('whatif-placeholder').classList.add('hidden'); document.getElementById('whatif-content').classList.remove('hidden'); document.getElementById('wi-name-actual').innerText = finalData.action.type; document.getElementById('wi-reward-actual').innerText = `REWARD: ${finalData.action.reward.toFixed(2)}`; document.getElementById('wi-desc-actual').innerText = finalData.action.description; renderMetrics('wi-metrics-actual', finalData.metrics); finalData.counterfactuals.forEach((cf, idx) => { const i = idx + 1; document.getElementById(`wi-name-cf${i}`).innerText = cf.action_type; document.getElementById(`wi-reward-cf${i}`).innerText = `REWARD: ${cf.reward.toFixed(2)}`; document.getElementById(`wi-desc-cf${i}`).innerText = cf.description; document.getElementById(`wi-tradeoff-cf${i}`).innerText = `Trade-off: ${cf.trade_off}`; renderMetrics(`wi-metrics-cf${i}`, cf.metrics); }); document.getElementById('counterfactual-card').classList.remove('hidden'); document.getElementById('cf-container').innerHTML = finalData.counterfactuals.map(cf => `<div class="p-2.5 bg-slate-900 border-l rounded-r-lg border-slate-700 leading-tight space-y-1"><div class="flex justify-between font-black"><span class="text-slate-500 uppercase">vs ${cf.action_type}</span><span class="text-indigo-400 font-bold">${cf.reward.toFixed(2)}</span></div><p class="text-slate-400 italic">"${cf.description}"</p><div class="text-[9px] text-slate-600"><b>IMPACT:</b> ${cf.trade_off}</div></div>`).join(''); _setBtnState('βœ…', 'Complete β€” Run Again', false); fetchAndRenderTrajectory(person, conflict); return; // skip finally reset so button stays "Complete" } catch (err) { console.error('Simulation error:', err); _setBtnState('❌', err.message.substring(0, 30) + '...'); await new Promise(r => setTimeout(r, 2000)); } finally { _setBtnState('β–Ά', 'Start Simulation', false); } } function resetDemoUI() { _setBtnState('β–Ά', 'Start Simulation', false); document.getElementById('action-card').classList.add('hidden'); document.getElementById('trajectory-panel').classList.add('hidden'); document.getElementById('forecast-panel').classList.add('hidden'); document.getElementById('counterfactual-card').classList.add('hidden'); document.getElementById('memory-card').classList.add('hidden'); document.getElementById('metric-phase').innerText = 'STABLE'; // Reset heatmap tiles to neutral ['career','finances','relationships','physical_health','mental_wellbeing','time'].forEach(dom => { const cell = document.getElementById('hm-' + dom); if (cell) { cell.style.backgroundColor = ''; cell.title = dom; } }); } async function fetchAndRenderTrajectory(person, conflict) { try { const res = await fetch('/api/simulation/trajectory', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ person, conflict }) }); const data = await res.json(); renderTrajectory(data); } catch (e) { console.error('Trajectory fetch failed:', e); } } function renderTrajectory(data) { const panel = document.getElementById('forecast-panel'); panel.classList.remove('hidden'); document.getElementById('forecast-total-reward').innerText = data.discounted_reward.toFixed(4); const traj = data.trajectory; const domains = ["career", "finances", "relationships", "physical_health", "mental_wellbeing", "time"]; const domainColors = { career: '#818cf8', finances: '#fbbf24', relationships: '#f87171', physical_health: '#4ade80', mental_wellbeing: '#22d3ee', time: '#a78bfa' }; const labels = ['Day 0', ...traj.map((_, i) => `Day ${i + 1}`)]; // Build per-domain avg series const day0 = data.day0_metrics; const domainAvg = (metrics, dom) => { const vals = Object.entries(metrics).filter(([k]) => k.startsWith(dom + '.')).map(([, v]) => v); return vals.length ? vals.reduce((a, b) => a + b, 0) / vals.length : 50; }; const datasets = domains.map(dom => ({ label: dom.replace('_', ' '), data: [domainAvg(day0, dom), ...traj.map(t => domainAvg(t.metrics, dom))], borderColor: domainColors[dom], backgroundColor: domainColors[dom] + '18', tension: 0.4, fill: false, pointRadius: 3, borderWidth: 2, })); const ctx = document.getElementById('trajectoryChart').getContext('2d'); if (window.trajectoryChart) window.trajectoryChart.destroy(); window.trajectoryChart = new Chart(ctx, { type: 'line', data: { labels, datasets }, options: { maintainAspectRatio: false, animation: { duration: 800, easing: 'easeInOutQuart' }, plugins: { legend: { display: true, position: 'top', labels: { color: '#94a3b8', font: { size: 9 }, boxWidth: 10, padding: 8 } }, tooltip: { callbacks: { label: ctx => ` ${ctx.dataset.label}: ${ctx.parsed.y.toFixed(1)}` } } }, scales: { x: { ticks: { color: '#64748b', font: { size: 9 } }, grid: { color: 'rgba(255,255,255,0.04)' } }, y: { min: 0, max: 100, ticks: { color: '#64748b', font: { size: 9 } }, grid: { color: 'rgba(255,255,255,0.04)' } } } } }); // Per-day step badges document.getElementById('forecast-steps').innerHTML = traj.map((t, i) => { const r = t.reward; const col = r > 0.05 ? 'text-green-400 border-green-500/20 bg-green-500/5' : r < -0.05 ? 'text-red-400 border-red-500/20 bg-red-500/5' : 'text-slate-500 border-slate-700 bg-slate-800/30'; return `<div class="text-center p-2 rounded-lg border ${col}"> <div class="font-black">Day ${t.step}</div> <div class="text-[8px] mt-0.5">${r >= 0 ? '+' : ''}${r.toFixed(3)}</div> <div class="text-[7px] text-slate-600 mt-0.5">Ξ³: ${t.discounted_contribution >= 0 ? '+' : ''}${t.discounted_contribution.toFixed(3)}</div> </div>`; }).join(''); } async function runDigitalSync() { const btn = document.getElementById('digital-sync-btn'); const icon = document.getElementById('sync-icon'); const txt = document.getElementById('sync-text'); btn.disabled = true; icon.innerText = 'πŸ”„'; txt.innerText = 'Syncing...'; document.getElementById('sync-idle').classList.add('hidden'); try { const res = await fetch('/api/digital/sync', { method: 'POST' }); const data = await res.json(); globalGmailSignals = data.merged_deltas; document.getElementById('sync-sources').classList.remove('hidden'); // Badge helper const badge = (id, isDemo) => { const el = document.getElementById(id); if (isDemo) { el.innerText = 'Demo'; el.className = 'text-[8px] font-black px-2 py-0.5 rounded-full border uppercase bg-amber-500/10 border-amber-500/30 text-amber-400'; } else { el.innerText = 'Live'; el.className = 'text-[8px] font-black px-2 py-0.5 rounded-full border uppercase bg-green-500/10 border-green-500/30 text-green-400'; } }; // Gmail const gs = data.sources.gmail; badge('gmail-badge', gs.is_demo); document.getElementById('gmail-sync-summary').innerText = gs.summary; const threads = (gs.signals.notable_threads || []); document.getElementById('gmail-threads').innerHTML = threads.map(t => `<div class="flex justify-between text-[9px] text-slate-500 border-t border-slate-800 pt-1"> <span class="italic truncate w-48">${t.subject}</span> <span class="text-slate-600 whitespace-nowrap ml-2">${t.time}</span> </div>` ).join(''); // Calendar const cs = data.sources.calendar; badge('cal-badge', cs.is_demo); document.getElementById('cal-sync-summary').innerText = cs.summary; const deadlines = (cs.signals.upcoming_deadlines || []); document.getElementById('cal-deadlines').innerHTML = deadlines.map(d => { const col = d.priority === 'critical' ? 'text-red-400' : 'text-yellow-400'; return `<div class="flex justify-between text-[9px] border-t border-slate-800 pt-1"> <span class="${col} font-bold truncate w-40">${d.title}</span> <span class="text-slate-600">in ${d.due_in_hours}h</span> </div>`; }).join(''); // Fitness const fs = data.sources.fitness; document.getElementById('fit-sync-summary').innerText = fs.summary; const fsig = fs.signals; document.getElementById('fit-stats').innerHTML = [ ['Sleep', `${fsig.avg_sleep_hours}h`, fsig.avg_sleep_hours < 6 ? 'text-red-400' : 'text-green-400'], ['HR', `${fsig.resting_heart_rate}bpm`, fsig.resting_heart_rate > 75 ? 'text-yellow-400' : 'text-green-400'], ['Steps', `${(fsig.daily_steps_avg/1000).toFixed(1)}k`, fsig.daily_steps_avg < 5000 ? 'text-red-400' : 'text-green-400'], ].map(([label, val, cls]) => `<div class="text-center bg-slate-800/50 rounded-lg p-2"> <div class="${cls} font-black text-xs">${val}</div> <div class="text-[8px] text-slate-600 uppercase font-bold mt-0.5">${label}</div> </div>` ).join(''); icon.innerText = 'βœ…'; txt.innerText = 'Synced β€” 3 Sources Active'; btn.className = btn.className.replace('from-cyan-700 to-indigo-700', 'from-green-800 to-emerald-700'); } catch (e) { icon.innerText = '❌'; txt.innerText = 'Sync Failed'; console.error(e); } finally { btn.disabled = false; } } async function runCustom() { const btn = document.querySelector('button[onclick="runCustom()"]'); const sit = document.getElementById('custom-situation').value; if (!sit) return alert("Describe your situation first!"); const originalText = btn.innerText; btn.disabled = true; btn.innerText = "🧠 Analyzing NLP Context..."; try { const res = await fetch('/api/custom/run', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ situation: sit, work_stress: document.getElementById('custom-work').value, money_stress: document.getElementById('custom-money').value, rel_quality: document.getElementById('custom-rel').value, energy_level: document.getElementById('custom-energy').value, time_pressure: 5, // Default gmail_signals: globalGmailSignals }) }); const data = await res.json(); // Show "Before" metrics first document.getElementById('custom-result').classList.remove('hidden'); document.getElementById('custom-id').innerText = `ID: ${data.action.id}`; renderMetrics('custom-metrics-grid', data.before_metrics); // Fetch and run cascade animation for this custom conflict // (Note: custom conflicts don't have a label, we'd need a separate logic or pass disruption) // For simplicity in the demo, we show the jump, but we can animate the delta await new Promise(r => setTimeout(r, 800)); renderMetrics('custom-metrics-grid', data.after_metrics, {}, data.before_metrics); document.getElementById('custom-plan').innerHTML = `<h4 class="font-black text-indigo-400 uppercase tracking-tighter text-xl">THE PLAN: ${data.action.type} β†’ ${data.action.target}</h4><p class="text-sm font-bold text-slate-200">"${data.action.description}"</p><div class="text-xs text-indigo-300 italic p-3 bg-indigo-500/10 rounded-lg"><b>Strategy:</b> ${data.action.reasoning}</div><div class="text-[10px] uppercase font-black text-slate-500">Subject: ${data.person.name}</div>`; } catch (e) { console.error(e); } finally { btn.disabled = false; btn.innerText = originalText; } } async function activateArjun() { const btn = document.getElementById('arjun-btn'); btn.innerText = "πŸ”— Seeding ChromaDB..."; const res = await fetch('/api/arjun/activate', { method: 'POST' }); const data = await res.json(); document.getElementById('arjun-status').innerText = data.message; btn.innerText = "βœ… History Loaded"; } async function loadTaskDemo() { const res = await fetch('/api/task/demo'); const data = await res.json(); document.getElementById('task-goal').innerText = data.goal; document.getElementById('task-routes').innerHTML = data.routes.map(r => `<div class="p-4 bg-slate-950/80 rounded-xl border-l-2 border-indigo-500"><b>${r.name}</b><p class="text-xs text-slate-400 mt-1">${r.description}</p></div>`).join(''); document.getElementById('task-milestones').innerHTML = data.milestones.map(m => `<div class="flex justify-between items-center bg-slate-950/50 p-2 rounded px-4"><span>${m.description}</span><span class="text-indigo-400 font-bold">⭐</span></div>`).join(''); document.getElementById('task-events').innerHTML = data.events.map(e => `<div class="p-3 border-l-2 border-red-500 bg-slate-950/80 flex gap-4 items-center"><b>STEP ${e.step}</b><span class="text-slate-400 text-xs">${e.description}</span></div>`).join(''); } async function submitFeedback(e) { e.preventDefault(); const formData = new FormData(e.target); const res = await fetch('/api/feedback/submit', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(Object.fromEntries(formData.entries())) }); document.getElementById('fb-status').innerText = "βœ… Outcome Broadcasted to ChromaDB!"; e.target.reset(); } // ── Feature A: Trained vs Baseline ── async function runComparison() { const btn = document.getElementById('cmp-btn'); btn.disabled = true; btn.innerText = "⚑ Running Both Agents..."; document.getElementById('cmp-intro').classList.add('hidden'); document.getElementById('cmp-content').classList.remove('hidden'); document.getElementById('cmp-delta').classList.add('hidden'); const conflict = document.getElementById('cmp-conflict').value; const person = document.getElementById('cmp-person').value; try { const res = await fetch('/api/comparison/run', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ conflict, person }) }); const data = await res.json(); const fill = (prefix, side) => { if (side.error) { document.getElementById(`cmp-reward-${prefix}`).innerText = 'ERR'; document.getElementById(`cmp-action-${prefix}`).innerText = side.error; return; } document.getElementById(`cmp-reward-${prefix}`).innerText = side.action.reward.toFixed(3); document.getElementById(`cmp-action-${prefix}`).innerText = `${side.action.type.toUpperCase()} β†’ ${side.action.target.toUpperCase()}: ${side.action.description}`; document.getElementById(`cmp-reason-${prefix}`).innerText = side.action.reasoning; renderMetrics(`cmp-metrics-${prefix}`, side.metrics); }; fill('baseline', data.baseline); fill('trained', data.trained); // Reward delta badge if (!data.baseline.error && !data.trained.error) { const delta = data.trained.action.reward - data.baseline.action.reward; const sign = delta >= 0 ? '+' : ''; const deltaEl = document.getElementById('cmp-delta'); deltaEl.classList.remove('hidden'); deltaEl.querySelector('span').innerText = `LifeStack outperforms baseline by ${sign}${(delta * 100).toFixed(1)}% reward (${sign}${delta.toFixed(3)})`; deltaEl.querySelector('span').className = `inline-block px-6 py-2 rounded-full border font-black text-sm ${delta >= 0 ? 'bg-green-500/10 border-green-500/30 text-green-400' : 'bg-red-500/10 border-red-500/30 text-red-400'}`; } } catch (e) { console.error(e); } finally { btn.disabled = false; btn.innerText = "⚑ Run Comparison"; } } // ── Feature E: Memory Effect ── async function runMemoryEffect() { const btn = document.getElementById('mem-btn'); btn.disabled = true; btn.innerText = "🧠 Running Episodes..."; document.getElementById('mem-intro').classList.add('hidden'); document.getElementById('mem-content').classList.remove('hidden'); document.getElementById('mem-delta').classList.add('hidden'); const conflict = document.getElementById('mem-conflict').value; const person = document.getElementById('mem-person').value; try { const res = await fetch('/api/memory/compare', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ conflict, person }) }); const data = await res.json(); const fillSide = (prefix, side) => { if (!side || !side.action) return; document.getElementById(`mem-reward-${prefix}`).innerText = (side.action.reward || 0).toFixed(3); document.getElementById(`mem-action-${prefix}`).innerText = `${(side.action.type || 'N/A').toUpperCase()} β†’ ${(side.action.target || 'N/A').toUpperCase()}`; document.getElementById(`mem-reason-${prefix}`).innerText = side.action.reasoning || 'No reasoning provided.'; renderMetrics(`mem-metrics-${prefix}`, side.metrics || {}); }; fillSide('cold', data.cold); fillSide('warm', data.warm); // Show retrieved memories const retrieved = data.warm.action.memories_retrieved || []; document.getElementById('mem-retrieved').innerHTML = retrieved.length ? '<div class="text-[9px] font-black text-green-500 uppercase mb-1">Retrieved Precedents</div>' + retrieved.map(m => `<div class="p-2 bg-green-500/5 border border-green-500/10 rounded-lg"> <span class="font-black text-green-400">${m.action_type}</span> <span class="text-slate-500 ml-2">score: ${m.reward?.toFixed(2) ?? '?'}</span> <p class="text-slate-400 italic mt-0.5">"${(m.reasoning || '').substring(0, 70)}..."</p> </div>`).join('') : '<p class="text-slate-600 text-[9px] italic">No precedents yet β€” this IS the first episode. Re-run to see memory kick in.</p>'; // Delta badge const delta = data.warm.action.reward - data.cold.action.reward; const sign = delta >= 0 ? '+' : ''; const deltaEl = document.getElementById('mem-delta'); deltaEl.classList.remove('hidden'); deltaEl.querySelector('span').innerText = `Memory improved reward by ${sign}${(delta * 100).toFixed(1)}% (${sign}${delta.toFixed(3)})`; deltaEl.querySelector('span').className = `inline-block px-6 py-2 rounded-full border font-black text-sm ${delta >= 0 ? 'bg-green-500/10 border-green-500/30 text-green-400' : 'bg-red-500/10 border-red-500/30 text-red-400'}`; } catch (e) { console.error(e); } finally { btn.disabled = false; btn.innerText = "🧠 Run Both Episodes"; } } // ── Upload helpers (F10) ── async function uploadHealthData() { const btn = event.target; const payload = { sleep_hours: parseFloat(document.getElementById('health-sleep').value), resting_heart_rate: parseFloat(document.getElementById('health-hr').value), daily_steps: parseFloat(document.getElementById('health-steps').value), }; btn.disabled = true; try { const res = await fetch('/api/data/health/upload', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload) }); const data = await res.json(); document.getElementById('health-upload-status').innerText = 'βœ… ' + data.summary; // Merge deltas into global signals so runCustom picks them up globalGmailSignals = Object.assign({}, globalGmailSignals || {}, data.deltas); } catch(e) { document.getElementById('health-upload-status').innerText = '❌ Upload failed'; } finally { btn.disabled = false; } } async function uploadCalendarData() { const btn = event.target; const criticalCount = parseInt(document.getElementById('cal-critical').value); const deadlines = Array.from({length: criticalCount}, (_, i) => ({title: `Deadline ${i+1}`, priority: 'critical', due_in_hours: 24 + i * 12})); const payload = { week_occupancy_pct: parseFloat(document.getElementById('cal-occupancy').value), back_to_back_blocks: parseInt(document.getElementById('cal-btb').value), upcoming_deadlines: deadlines, }; btn.disabled = true; try { const res = await fetch('/api/data/calendar/upload', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload) }); const data = await res.json(); document.getElementById('cal-upload-status').innerText = 'βœ… ' + data.summary; globalGmailSignals = Object.assign({}, globalGmailSignals || {}, data.deltas); } catch(e) { document.getElementById('cal-upload-status').innerText = '❌ Upload failed'; } finally { btn.disabled = false; } } // ── F4: OCEAN Radar Chart ── function renderOceanRadar(personA, personB) { const row = document.getElementById('ocean-radar-row'); row.classList.remove('hidden'); const labels = ['Openness', 'Conscientiousness', 'Extraversion', 'Agreeableness', 'Neuroticism']; const aVals = labels.map(l => personA.ocean[l.toLowerCase()]); const bVals = labels.map(l => personB.ocean[l.toLowerCase()]); const ctx = document.getElementById('oceanChart').getContext('2d'); if (window.oceanChart) window.oceanChart.destroy(); window.oceanChart = new Chart(ctx, { type: 'radar', data: { labels, datasets: [ { label: personA.name, data: aVals, borderColor: '#818cf8', backgroundColor: 'rgba(129,140,248,0.15)', pointBackgroundColor: '#818cf8', borderWidth: 2 }, { label: personB.name, data: bVals, borderColor: '#f472b6', backgroundColor: 'rgba(244,114,182,0.15)', pointBackgroundColor: '#f472b6', borderWidth: 2 }, ] }, options: { maintainAspectRatio: false, scales: { r: { min: 0, max: 100, ticks: { color: '#475569', font: { size: 8 }, stepSize: 25 }, grid: { color: 'rgba(255,255,255,0.05)' }, pointLabels: { color: '#94a3b8', font: { size: 9 } } } }, plugins: { legend: { labels: { color: '#94a3b8', font: { size: 9 } } } } } }); } async function comparePersons() { const btn = document.getElementById('compare-btn'); const conflict = document.getElementById('lab-conflict').value; const personA = document.getElementById('lab-person-a').value; const personB = document.getElementById('lab-person-b').value; btn.disabled = true; btn.innerText = "πŸŒ€ Simulating Both Paths..."; document.getElementById('lab-col-a').classList.remove('hidden'); document.getElementById('lab-col-b').classList.remove('hidden'); try { // Try dedicated OCEAN endpoint const res = await fetch('/api/personality/compare', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ conflict, person_a: personA, person_b: personB }) }); const data = await res.json(); if (data && !data.error && data.a && data.b) { ['a', 'b'].forEach(s => { const d = data[s]; document.getElementById(`lab-name-${s}`).innerText = d.name; document.getElementById(`lab-reward-${s}`).innerText = `Score: ${(d.action.reward || 0).toFixed(2)}`; document.getElementById(`lab-action-${s}`).innerText = `PLAN: ${(d.action.type || 'N/A').toUpperCase()} β†’ ${(d.action.target || 'N/A').toUpperCase()}`; document.getElementById(`lab-reason-${s}`).innerText = d.action.reasoning || 'No reasoning provided.'; renderMetrics(`lab-metrics-${s}`, d.metrics || {}); }); renderOceanRadar(data.a, data.b); } else { // Fallback: run individual simulations const runSim = async (person, suffix) => { const sres = await fetch('/api/simulation/action', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({person, conflict}) }); const sdata = await sres.json(); document.getElementById(`lab-name-${suffix}`).innerText = person.split('(')[0]; document.getElementById(`lab-reward-${suffix}`).innerText = `Score: ${(sdata.action.reward || 0).toFixed(2)}`; document.getElementById(`lab-action-${suffix}`).innerText = `PLAN: ${(sdata.action.type || 'N/A').toUpperCase()} β†’ ${(sdata.action.target || 'N/A').toUpperCase()}`; document.getElementById(`lab-reason-${suffix}`).innerText = sdata.action.reasoning || 'No reasoning provided.'; renderMetrics(`lab-metrics-${suffix}`, sdata.metrics || {}); }; await Promise.all([runSim(personA, 'a'), runSim(personB, 'b')]); } } catch(e) { console.error("Personality Lab Error:", e); } finally { btn.disabled = false; btn.innerText = "πŸ”₯ Compare Response"; } } </script> </body> </html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LifeStack Portal | Meta OpenEnv 2026</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;800&display=swap" rel="stylesheet">
<style>
body { font-family: 'Plus Jakarta Sans', sans-serif; background-color: #0f172a; color: #f8fafc; overflow-x: hidden; }
.glass { background: rgba(15, 23, 42, 0.85); backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.08); box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37); }
.tab-active { border-bottom: 2px solid #818cf8; color: #818cf8; }
.metric-bar { transition: width 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); }
@keyframes pulse-red { 0%, 100% { background-color: #ef4444; } 50% { background-color: #f87171; } }
.pulse-red { animation: pulse-red 1s infinite; }
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #334155; border-radius: 10px; }
::-webkit-scrollbar-thumb:hover { background: #475569; }
.custom-scrollbar::-webkit-scrollbar { width: 4px; }
.custom-scrollbar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); }
</style>
</head>
<body class="p-4 md:p-8">
<div class="max-w-6xl mx-auto">
<!-- Header -->
<header class="flex justify-between items-center mb-8">
<div>
<h1 class="text-3xl font-extrabold tracking-tight text-transparent bg-clip-text bg-gradient-to-r from-indigo-400 to-cyan-400">πŸͺ LifeStack Engine</h1>
<p class="text-slate-400 text-sm">Meta Γ— HuggingFace OpenEnv Hackathon Finale</p>
</div>
<div class="hidden md:block text-right">
<span class="px-3 py-1 rounded-full bg-indigo-500/10 text-indigo-400 text-xs font-bold border border-indigo-500/20">PRODUCTION HARDENED v3.0</span>
</div>
</header>
<!-- Navigation Tabs -->
<div class="flex gap-6 border-b border-slate-800 mb-8 overflow-x-auto no-scrollbar whitespace-nowrap">
<button onclick="showTab('situation')" id="tab-situation" class="pb-4 text-sm font-semibold transition tab-active">Situational Portal</button>
<button onclick="showTab('custom')" id="tab-custom" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">πŸ’­ Try Your Case</button>
<button onclick="showTab('compare')" id="tab-compare" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">⚑ Untrained vs GRPO-Trained</button>
<button onclick="showTab('memeffect')" id="tab-memeffect" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">🧠 Memory Effect</button>
<button onclick="showTab('arjun')" id="tab-arjun" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">πŸ—“οΈ Arjun's Journey</button>
<button onclick="showTab('history')" id="tab-history" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">πŸ“Ό Episode Replay</button>
<button onclick="showTab('lab')" id="tab-lab" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">🎭 Personality Lab</button>
<button onclick="showTab('whatif')" id="tab-whatif" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">🧩 What-If Lab</button>
<button onclick="showTab('tasks')" id="tab-tasks" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">πŸ—ΊοΈ Task Explorer</button>
<button onclick="showTab('performance')" id="tab-performance" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">πŸ“Š Analytics</button>
<button onclick="showTab('verification')" id="tab-verification" class="pb-4 text-sm font-semibold text-slate-400 hover:text-white transition">πŸ“¬ Verification</button>
</div>
<!-- ── TAB 1: SITUATION PORTAL ── -->
<div id="content-situation" class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div class="lg:col-span-1 space-y-6">
<div class="glass p-6 rounded-2xl">
<h3 class="text-lg font-bold mb-4">Simulation Control</h3>
<div class="space-y-4 mb-6">
<div class="text-sm">
<label class="block text-slate-400 mb-2">Subject Persona</label>
<select id="person-select" onchange="resetDemoUI()" class="w-full bg-slate-900 border border-slate-700 rounded-lg px-4 py-2 text-white">
{% for p in persons %} <option value="{{ p }}">{{ p }}</option> {% endfor %}
</select>
</div>
<div class="text-sm">
<label class="block text-slate-400 mb-2">Life Conflict</label>
<select id="conflict-select" onchange="resetDemoUI()" class="w-full bg-slate-900 border border-slate-700 rounded-lg px-4 py-2 text-white">
{% for c in conflicts %} <option value="{{ c }}">{{ c }}</option> {% endfor %}
</select>
</div>
</div>
<div class="flex items-center justify-between p-3 bg-indigo-500/5 rounded-xl border border-indigo-500/20">
<div class="text-[10px] uppercase font-black text-indigo-400">Context Augmentation (RAG)</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="memory-toggle" class="sr-only peer">
<div class="w-9 h-5 bg-slate-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-indigo-600"></div>
</label>
</div>
<button onclick="startDemo()" id="simulate-btn" class="w-full py-4 bg-indigo-600 hover:bg-indigo-500 rounded-xl font-bold transition flex items-center justify-center gap-2">
<span id="btn-icon">β–Ά</span> <span id="btn-text">Start Simulation</span>
</button>
</div>
<!-- Trajectroy -->
<div id="trajectory-panel" class="hidden glass p-6 rounded-2xl border-l-4 border-yellow-500">
<h4 class="text-[10px] font-bold text-slate-500 tracking-widest uppercase mb-2">AI Risk Prediction</h4>
<p id="traj-summary" class="text-sm text-slate-200 mb-4 italic leading-relaxed">Analyzing...</p>
<div class="h-1.5 bg-slate-800 rounded-full overflow-hidden"><div id="traj-bar" class="h-full bg-yellow-500 transition-all duration-700" style="width: 0%"></div></div>
</div>
</div>
<div class="lg:col-span-1 glass p-6 rounded-2xl relative overflow-hidden">
<h3 class="text-lg font-bold flex justify-between mb-2"><span>Vital Metrics</span><span id="metric-phase" class="text-[10px] bg-slate-800 px-2 py-1 rounded text-slate-400 uppercase tracking-tighter">STABLE</span></h3>
<!-- Domain Risk Heatmap -->
<div class="mb-4">
<div class="text-[9px] font-black text-slate-600 uppercase tracking-widest mb-2">Domain Risk Heatmap</div>
<div id="domain-heatmap" class="grid grid-cols-6 gap-1.5">
<div class="flex flex-col items-center gap-1">