๐Ÿงฌ

Your DNA Already Knows Things About Your Future

Upload nearly any genetic test and enter a personalized AI experience built around your variants, pathways, and biological tendencies. Health intelligence before care is needed.

v13.2248
23andMe ยท AncestryDNA ยท VCF ยท PDF 2,900+ studies AI-powered analysis
Privacy ยท Terms ยท Educational use only
๐Ÿฆ‰ HYPATIA ยท GENE CHAT
Context-aware โ€” I know your variant profile
Hey! I'm Hypatia. Upload your genetic test data and I can walk you through what your variants mean, what supplements to focus on, and build a personalized report. What do you wanna explore?
`; const blob=new Blob([html],{type:'text/html'}); const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='palindrome-report-'+Date.now()+'.html';a.click();URL.revokeObjectURL(a.href);toast('Report downloaded!'); } async function emailReport(){ if(!currentReport?.token)return toast('Generate a report first',false); const to=prompt('Send report to email address:'); if(!to||!to.includes('@'))return; toast('Sending...'); try{const d=await API('/devlab/gene/report/email',{token:currentReport.token,to});if(d.ok)toast('Report sent to '+to);else toast(d.error||'Send failed',false)}catch(e){toast('Failed: '+e.message,false)} } // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• // Subscription Paywall // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• function showPaywall(matchedCount,homCount){ const overlay=document.createElement('div'); overlay.id='paywall-overlay'; overlay.style.cssText='position:fixed;inset:0;z-index:60000;background:rgba(5,10,20,.96);display:flex;align-items:center;justify-content:center;animation:uo-in .3s ease;overflow-y:auto;padding:20px'; overlay.innerHTML=`
๐Ÿงฌ
${matchedCount} Variants Matched
${homCount} homozygous ยท Subscribe to unlock full pathway analysis, AI interview, and personalized report
Loading plans...
Already subscribed? Dismiss
`; document.body.appendChild(overlay); // Load tiers fetch(`${CONFIG.apiBase}/geneius/subscription/tiers`,{credentials:'include'}).then(r=>r.json()).then(d=>{ if(!d.ok)return; const tiers=d.tiers||{}; const plans=document.getElementById('pw-plans'); plans.innerHTML=Object.entries(tiers).filter(([k,v])=>v.price_cents>0).map(([k,v])=>`
${k==='premium'?'
Popular
':''}
${esc(v.name)}
$${(v.price_cents/100).toFixed(0)}/mo
`).join(''); }).catch(()=>{}); } async function subscribePlan(plan){ try{ const d=await API('/geneius/checkout/subscribe',{plan}); if(d.ok&&d.url)window.location.href=d.url; else toast(d.error||'Checkout failed',false); }catch(e){toast('Error: '+e.message,false)} } // Check ?subscribed=1 on page load (function(){const u=new URLSearchParams(window.location.search);if(u.get('subscribed')==='1'){toast('Subscription activated! Upload your genetic data to begin.');history.replaceState(null,'',location.pathname)}})(); // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• // Consultation Booking // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• async function showConsultationBooking(){ const overlay=document.createElement('div'); overlay.id='consult-overlay'; overlay.style.cssText='position:fixed;inset:0;z-index:60000;background:rgba(5,10,20,.96);display:flex;align-items:center;justify-content:center;animation:uo-in .3s ease;overflow-y:auto;padding:20px'; overlay.innerHTML='
โณ
Loading specialists...
'; document.body.appendChild(overlay); try{ const d=await fetch(`${CONFIG.apiBase}/geneius/specialists`,{credentials:'include'}).then(r=>r.json()); if(!d.ok||!d.specialists?.length){overlay.innerHTML='
๐Ÿ˜”
No specialists available right now.
';return} const spec=d.specialists[0]; // Load availability const av=await fetch(`${CONFIG.apiBase}/geneius/specialist/${spec.id}/availability`,{credentials:'include'}).then(r=>r.json()); const slots=av.slots||[]; const dates=[...new Set(slots.map(s=>s.date))].slice(0,14); overlay.innerHTML=`
Book a Specialist Consultation
๐Ÿงฌ
${esc(spec.name)}
${esc(spec.title)}
$${(spec.consultation_fee/100).toFixed(0)} ยท ${spec.consultation_duration_min} min ยท Google Meet
${esc(spec.bio)}
Select a Date
${dates.map((dt,i)=>{const d=new Date(dt+'T12:00:00Z');return``}).join('')||'No availability'}
Available Times
`; window._csSlots=slots; window._csSelDate=dates[0]||''; window._csSelTime=''; window._csFee=spec.consultation_fee; if(dates[0])renderTimeSlots(dates[0]); }catch(e){overlay.innerHTML='
Failed to load: '+e.message+'
'} } function selectDate(dt,el){ window._csSelDate=dt;window._csSelTime=''; document.querySelectorAll('.cs-date-btn').forEach(b=>{b.style.background='var(--pg-surface)';b.style.color='var(--pg-text-dim)'}); el.style.background='var(--pg-primary)';el.style.color='white'; renderTimeSlots(dt); const btn=document.getElementById('cs-book-btn');btn.disabled=true;btn.style.opacity='.5';btn.textContent='Select a time to continue'; } function renderTimeSlots(dt){ const el=document.getElementById('cs-times'); const daySlots=(window._csSlots||[]).filter(s=>s.date===dt); el.innerHTML=daySlots.map(s=>``).join('')||'No slots on this date'; } function selectTime(t,el){ window._csSelTime=t; document.querySelectorAll('.cs-time-btn').forEach(b=>{b.style.background='var(--pg-surface)';b.style.color='var(--pg-text-dim)'}); el.style.background='var(--pg-primary)';el.style.color='white'; const btn=document.getElementById('cs-book-btn');btn.disabled=false;btn.style.opacity='1';btn.textContent=`Book & Pay โ€” $${(window._csFee/100).toFixed(0)}`; } async function bookConsultation(specId){ const btn=document.getElementById('cs-book-btn');btn.disabled=true;btn.textContent='Processing...'; try{ const d=await API('/geneius/checkout/consultation',{specialist_id:specId,slot_date:window._csSelDate,slot_time:window._csSelTime}); if(d.free_credit){ // Premium user with free credit โ€” book directly const bd=await API('/geneius/consultation/book',{specialist_id:specId,slot_date:window._csSelDate,slot_time:window._csSelTime,stripe_session_id:d.session_id,free_credit:true}); if(bd.ok){document.getElementById('consult-overlay').remove();showBookingConfirmation(bd)}else toast(bd.error||'Booking failed',false); }else if(d.ok&&d.url){window.location.href=d.url} else toast(d.error||'Checkout failed',false); }catch(e){toast('Error: '+e.message,false)} btn.disabled=false;btn.textContent='Book & Pay'; } function showBookingConfirmation(bd){ const overlay=document.createElement('div'); overlay.style.cssText='position:fixed;inset:0;z-index:60000;background:rgba(5,10,20,.96);display:flex;align-items:center;justify-content:center;padding:20px'; overlay.innerHTML=`
โœ…
Consultation Booked!
${esc(bd.specialist_name)} ยท ${esc(bd.start?.split('T')[0]||'')}
${bd.meet_link?`๐ŸŽฅ Join Google Meet`:''}
Calendar invites sent to both you and your specialist.
`; document.body.appendChild(overlay); } // Check ?consultation_paid=1 on load (function(){const u=new URLSearchParams(window.location.search);if(u.get('consultation_paid')==='1'){ const sid=u.get('session_id')||'',spec=u.get('spec')||'',date=u.get('date')||'',time=u.get('time')||''; history.replaceState(null,'',location.pathname); if(sid&&spec){API('/geneius/consultation/book',{specialist_id:spec,slot_date:date,slot_time:time,stripe_session_id:sid}).then(d=>{if(d.ok)showBookingConfirmation(d);else toast(d.error||'Booking failed',false)}).catch(e=>toast('Error: '+e.message,false))} }})(); function renderMd(raw){ if(!raw) return ''; let s = raw.replace(/&/g,'&').replace(//g,'>'); s = s.replace(/\*\*([^*\n]+)\*\*/g, '$1'); s = s.replace(/\*([^*\n]+)\*/g, '$1'); s = s.replace(/`([^`\n]+)`/g, '$1'); // Markdown links: [text](url) โ†’ clickable links styled as buttons for shop URLs s = s.replace(/\[([^\]]+)\]\((https:\/\/purepeptides\.vip[^)]+)\)/g, '$1'); s = s.replace(/\[([^\]]+)\]\((https:\/\/peptide-protocol\.com[^)]+)\)/g, '$1'); // Generic markdown links s = s.replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g, '$1'); // Tables: | col | col | โ†’ HTML table s = s.replace(/^\|(.+)\|\s*$/gm, function(match, row){ const cells = row.split('|').map(c=>c.trim()); if(cells.every(c=>/^[-:]+$/.test(c))) return ''; // separator row const tag = cells.every(c=>c===c.toUpperCase()&&c.length>1) ? 'th' : 'td'; return ''+cells.map(c=>'<'+tag+' style="padding:6px 10px;border-bottom:1px solid var(--pg-border);font-size:11px;text-align:left">'+c+'').join('')+''; }); s = s.replace(/(.*<\/tr>)/gs, '$1
'); s = s.replace(/^### (.+)$/gm, '

$1

'); s = s.replace(/^## (.+)$/gm, '

$1

'); s = s.replace(/^# (.+)$/gm, '

$1

'); s = s.replace(/\n/g, '
'); // Add CTA footer after report content s += '
'; s += '
Ready to Optimize Your Genetics?
'; s += '
'; s += '\ud83d\udc8a Shop Supplements'; s += '\ud83e\uddec Peptide Research Guide'; s += ''; s += '
'; return s; } // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• // Admin Spreadsheet โ€” Card CRUD // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• const ADMIN_FIELDS=[ {key:'gene_symbol',label:'Gene'},{key:'rsid',label:'rsID'},{key:'common_name',label:'Common Name'},{key:'category',label:'Category'}, {key:'actionability_tier',label:'Tier'},{key:'scientific_confidence',label:'Confidence'}, {key:'env',label:'Env',type:'select',opts:['staging','production','sandbox']}, {key:'status',label:'Status',type:'select',opts:['draft','in_research','needs_review','adjudication_ready','approved_staging','approved_production','archived']}, {key:'supplements',label:'Supplements',wide:true},{key:'diet_notes',label:'Diet/Lifestyle',wide:true}, {key:'safety_note',label:'Safety Note',wide:true},{key:'evidence_summary_short',label:'Evidence Summary',wide:true}, {key:'mechanism_summary',label:'Mechanism',wide:true} ]; async function loadAdminCards(){ const env=document.getElementById('admin-env').value; try{const d=await API('/devlab/gene/card/list',{env});if(d.ok){adminCards=d.cards||[];renderAdminTable()}} catch(e){toast('Failed to load: '+e.message,false)} } function renderAdminTable(filter=''){ const tbody=document.getElementById('admin-tbody'); let cards=adminCards; if(filter){const f=filter.toLowerCase();cards=cards.filter(c=>Object.values(c).some(v=>String(v).toLowerCase().includes(f)))} document.getElementById('admin-count').textContent=`${cards.length} cards`; tbody.innerHTML=cards.map(c=>{ const isE=editingRow===c.record_id; return`
${isE ?`` :`` }
${ADMIN_FIELDS.map(f=>{ const val=c[f.key]||''; if(isE){ if(f.type==='select')return``; return``; } if(f.key==='env')return`${esc(val)}`; if(f.key==='status')return`${esc(val)}`; return`${esc(String(val).slice(0,120))}${String(val).length>120?'...':''}`; }).join('')}`; }).join(''); } function editCard(rid){editingRow=rid;renderAdminTable(document.getElementById('admin-search').value);setTimeout(()=>{const r=document.querySelector(`tr[data-rid="${rid}"]`);if(r)r.querySelector('.cell-input')?.focus()},50)} function cancelEdit(){editingRow=null;renderAdminTable(document.getElementById('admin-search').value)} async function saveCard(rid){ const row=document.querySelector(`tr[data-rid="${rid}"]`);if(!row)return; const card=adminCards.find(c=>c.record_id===rid);if(!card)return; const updated={...card}; row.querySelectorAll('.cell-input').forEach(inp=>{const f=inp.dataset.field;if(f)updated[f]=inp.value}); try{ const d=await API('/devlab/gene/card/save',{card:updated}); if(d.ok){const idx=adminCards.findIndex(c=>c.record_id===rid);if(idx>=0)adminCards[idx]=updated;editingRow=null;renderAdminTable(document.getElementById('admin-search').value);toast(`Saved ${updated.gene_symbol} ${updated.rsid}`);if(allCards.length>0)loadCards()} else toast('Save failed: '+(d.error||''),false); }catch(e){toast('Save failed: '+e.message,false)} } async function deleteCard(rid){ const card=adminCards.find(c=>c.record_id===rid); if(!confirm(`Delete ${card?.gene_symbol} ${card?.rsid}? Cannot be undone.`))return; try{const d=await API('/devlab/gene/card/delete',{record_id:rid});if(d.ok){adminCards=adminCards.filter(c=>c.record_id!==rid);renderAdminTable(document.getElementById('admin-search').value);toast(`Deleted ${card?.gene_symbol}`);if(allCards.length>0)loadCards()}else toast('Delete failed: '+(d.error||''),false)} catch(e){toast('Delete failed: '+e.message,false)} } // โ”€โ”€ Gene Discovery + Platform Stats โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ async function runGeneDiscovery(){ const btn=document.getElementById('btn-discovery');btn.disabled=true;btn.textContent='Running...'; document.getElementById('discovery-status').textContent='Discovery running...'; document.getElementById('platform-stats-bar').style.display='flex'; try{ // Run discovery via bridge (calls gene_discovery.py as subprocess) const d=await API('/devlab/gene/research-queue/add',{item:{gene_symbol:'DISCOVERY_RUN',reason:'Admin-triggered gene discovery run',status:'running'}}); toast('Discovery triggered โ€” check Lab Queue for results'); document.getElementById('discovery-status').textContent='Triggered! Cards will appear as they\'re created.'; // Refresh stats after a delay setTimeout(loadPlatformStats, 5000); setTimeout(()=>{loadAdminCards();loadPlatformStats()}, 30000); }catch(e){toast('Failed: '+e.message,false)} btn.disabled=false;btn.textContent='\ud83e\uddec Run Discovery'; } async function loadPlatformStats(){ document.getElementById('platform-stats-bar').style.display='flex'; try{ const d=await fetch(`${CONFIG.apiBase}/geneius/stats`,{credentials:'include'}).then(r=>r.json()); if(d.ok){ document.getElementById('stat-genes').textContent=d.unique_genes||'-'; document.getElementById('stat-cards').textContent=d.total_cards||'-'; document.getElementById('stat-studies').textContent=(d.studies_cited||0).toLocaleString(); document.getElementById('stat-points').textContent=(d.data_points_analyzed||0).toLocaleString(); document.getElementById('stat-supps').textContent=d.supplement_recommendations||'-'; document.getElementById('stat-peps').textContent=d.peptide_recommendations||'-'; } }catch(e){} } // Auto-load stats when admin tab opens const _origAdminLoad=loadAdminCards; loadAdminCards=function(){_origAdminLoad.apply(this,arguments);loadPlatformStats();}; async function addNewCard(){ const gene=prompt('Gene symbol (e.g. MTHFR):');if(!gene)return;const rsid=prompt('rsID (e.g. rs1801133):'); const nc={gene_symbol:gene,rsid:rsid||'',common_name:'',category:'',actionability_tier:'4',scientific_confidence:'D',env:'staging',status:'draft',inferred_phenotype:'',mechanism_summary:'',safety_note:'',supplements:'',diet_notes:'',peptide_badge:'',evidence_summary_short:'TBD / needs curation',source_links:'',treatment_sources:''}; try{const d=await API('/devlab/gene/card/save',{card:nc});if(d.ok){toast(`Created card for ${gene}`);loadAdminCards()}else toast('Create failed: '+(d.error||''),false)}catch(e){toast('Create failed: '+e.message,false)} } function filterAdmin(){renderAdminTable(document.getElementById('admin-search').value)} async function promoteCard(rid){ const card=adminCards.find(c=>c.record_id===rid);if(!card)return; const env=card.env||'staging'; const next=env==='sandbox'?'staging':env==='staging'?'production':null; if(!next)return toast('Already in production',false); if(!confirm(`Promote ${card.gene_symbol} from ${env} to ${next}?`))return; try{const d=await API('/devlab/gene/card/promote',{record_id:rid,target_env:next});if(d.ok){toast(`${card.gene_symbol} promoted to ${next}`);loadAdminCards();if(allCards.length>0)loadCards()}else toast(d.error||'Failed',false)}catch(e){toast('Failed: '+e.message,false)} } // Session persistence let sessionId=null; async function saveSession(){ const session={variants:uploadedMatched,clusters:uploadedClusters,interview:interviewHistory,timestamp:new Date().toISOString()}; try{const d=await API('/devlab/gene/session/save',{session});if(d.ok)sessionId=d.session_id}catch(e){} } // Adaptive interview via /devlab/gene/interview let interviewMode='intake'; async function startInterview(){ if(!uploadedMatched.length)return; try{ const d=await API('/devlab/gene/interview',{variants:uploadedMatched.slice(0,20),history:[],profile:{},clusters:uploadedClusters}); if(d.ok&&d.question){ let msg=d.question; if(d.reason)msg+=`
Why I'm asking \u25be
${esc(d.reason)}
`; addMsg('ai',msg); interviewMode=d.done?'exploration':'phenotype'; } }catch(e){} } // Learning loop โ€” save completed case after report generation async function saveLearningCase(){ if(!currentReport?.token||!uploadedMatched.length)return; const caseData={token:currentReport.token,variants:uploadedMatched,clusters:uploadedClusters,interview:interviewHistory,completed_at:new Date().toISOString()}; try{await API('/devlab/gene/session/save',{session:{...caseData,session_id:sessionId||undefined,type:'completed_case'}})}catch(e){} } // Research Queue let rqItems=[]; async function loadResearchQueue(){ try{const d=await API('/devlab/gene/research-queue',{});if(d.ok){rqItems=d.queue||[];renderRQ()}}catch(e){} } function renderRQ(){ const el=document.getElementById('rq-list'); if(!rqItems.length){el.innerHTML='
No items in research queue
';return} el.innerHTML=rqItems.map(r=>`
${esc(r.gene||r.gene_symbol||'\u2014')}${esc(r.rsid||'')} ${esc(r.notes||r.reason||'')}${esc(r.status||'queued')}
`).join(''); } async function addToQueue(){ const gene=prompt('Gene symbol:');if(!gene)return; const rsid=prompt('rsID (optional):'); const notes=prompt('Research notes/reason:'); try{ const d=await API('/devlab/gene/research-queue/add',{item:{gene_symbol:gene,gene,rsid:rsid||'',notes:notes||'',reason:notes||''}}); if(d.ok){toast('Added '+gene+' to queue');loadResearchQueue()}else toast(d.error||'Failed',false); }catch(e){toast('Failed: '+e.message,false)} } // Chat let chatOpen=false,chatBusy=false; function toggleChat(){chatOpen=!chatOpen;document.getElementById('hyp-panel').classList.toggle('open',chatOpen);if(chatOpen)setTimeout(()=>document.getElementById('hyp-input')?.focus(),100)} function addMsg(role,text){const msgs=document.getElementById('hyp-msgs');const div=document.createElement('div');div.className=`hyp-msg ${role}`;div.innerHTML=text.replace(/\n/g,'
').replace(/\*\*(.+?)\*\*/g,'$1');msgs.appendChild(div);msgs.scrollTop=msgs.scrollHeight} function buildCtx(){const p=[];if(uploadedMatched.length){const hom=uploadedMatched.filter(v=>v.homozygous).map(v=>`${v.gene} ${v.rsid} (${v.genotype})`);if(hom.length)p.push('Homozygous: '+hom.join(', '));const het=uploadedMatched.filter(v=>!v.homozygous).slice(0,10).map(v=>`${v.gene} ${v.rsid}`);if(het.length)p.push('Heterozygous: '+het.join(', '))}return p.join('\n')} async function chatSend(){ if(chatBusy)return;const inp=document.getElementById('hyp-input');const msg=inp.value.trim();if(!msg)return;inp.value='';chatBusy=true;addMsg('user',msg); const ctx=buildCtx(),msgs=document.getElementById('hyp-msgs');const dots=document.createElement('div');dots.className='hyp-dots';dots.id='hyp-dots';dots.innerHTML='';msgs.appendChild(dots);msgs.scrollTop=msgs.scrollHeight; // Use adaptive interview if we have uploaded data, otherwise general chat if(uploadedMatched.length>0&&interviewMode!=='exploration'){ try{const d=await API('/devlab/gene/interview',{variants:uploadedMatched.slice(0,20),history:interviewHistory,profile:{},clusters:uploadedClusters});dots.remove(); if(d.ok){ interviewHistory.push({question:d.question||'',answer:msg}); let reply=d.question||'Thanks! I have enough info for your report now.'; if(d.reason)reply+=`
Why I asked \u25be
${esc(d.reason)}
`; if(d.done){reply+='
Interview complete! Go to the Report tab to generate your personalized report.
';interviewMode='exploration'} addMsg('ai',reply);saveSession(); }else{interviewHistory.push({question:'',answer:msg});addMsg('ai','Let me think about that...');} }catch(e){dots.remove();interviewHistory.push({question:'',answer:msg});addMsg('ai','Connection issue โ€” try again.')} }else{ // Check for lab commands first const ml=msg.toLowerCase(); const labCmd= (ml.includes('run discovery')||ml.includes('discover new'))?'discover': (ml.includes('audit')||ml.includes('check cards'))?'audit': (ml.includes('enrich')||ml.includes('fill cards')||ml.includes('update cards'))?'enrich': (ml.includes('harvest')||ml.includes('collect studies')||ml.includes('download studies'))?'harvest': (ml.includes('lab queue')||ml.includes('pending discoveries'))?'labqueue': (ml.includes('grade')||ml.includes('regrade')||ml.includes('re-grade'))?'grade': (ml.includes('full cycle')||ml.includes('run everything'))?'full-cycle': (ml.includes('db stats')||ml.includes('database stats')||ml.includes('study count'))?'stats': (ml.includes('interest')||ml.includes('cool findings'))?'interests':null; if(labCmd){ dots.remove(); if(labCmd==='labqueue'){ showAdminSub('labqueue');document.querySelector('[data-tab="admin"]').click(); addMsg('ai','Opening the Lab Queue for you! Check the Admin tab.');chatBusy=false;return; } addMsg('sys',`Running lab command: ${labCmd}...`); const agent=(['harvest','stats','interests'].includes(labCmd))?'research_harvester_v1':'gene_lab_curator_v1'; const action=labCmd==='harvest'?'harvest':labCmd; try{ const d=await API('/devlab/ptt/run-agent-step',{agent,action,task:'',viewer_role:'admin',mode:'full_operator'}); let reply=d.ok?'Done! ':'Failed. '; if(d.output_parsed){ const p=d.output_parsed; if(labCmd==='audit')reply+=`${p.total||0} cards: ${p.tbd?.length||0} TBD, ${p.incomplete?.length||0} incomplete, ${p.well_curated||0} curated.`; else if(labCmd==='discover')reply+=`Found ${p.gene_count||0} new genes + ${p.peptide_count||0} new peptides. Check Lab Queue!`; else if(labCmd==='stats')reply+=`${p.total_studies||0} studies in DB. ${p.gene_topics||0} gene topics, ${p.peptide_topics||0} peptide topics. ${p.pending_alerts||0} alerts pending.`; else reply+=JSON.stringify(p).slice(0,200); }else reply+=(d.output||'').slice(0,200); addMsg('ai',reply); }catch(e){addMsg('ai','Lab command failed: '+e.message)} chatBusy=false;return; } // ALL messages go through research โ€” Hypatia always has her lab team on call try{ const d=await API('/devlab/gene/research',{query:msg});dots.remove(); if(d.ok&&(d.answer||d.local_cards?.length||d.cited_studies?.length)){ let reply=d.answer||''; // Cited studies from local research DB if(d.cited_studies?.length){ reply+='
'; reply+='
\ud83d\udcda Cited Studies
'; for(const s of d.cited_studies.slice(0,5))reply+=`
\u00b7 ${s.title?.slice(0,90)} ${s.authors?.split(';')[0]||''}, ${s.journal||''} ${s.pubdate||''}
`; reply+='
'; } // PubMed live results if(d.pubmed?.articles?.length){ reply+='
'; reply+='PubMed: '; for(const a of d.pubmed.articles.slice(0,3))reply+=`\u00b7 ${a.title?.slice(0,60)} `; reply+='
'; } // Knowledge base matches if(d.local_cards?.length)reply+=`
\ud83e\uddec ${d.local_cards.length} matching cards in knowledge base
`; // Sources footer if(d.sources?.length)reply+=`
Sources: ${d.sources.join(' \u00b7 ')}
`; if(!reply)reply='Hmm, I didn\'t find much on that. Try asking about a specific gene (like MTHFR), variant (rs1801133), or peptide (BPC-157)!'; addMsg('ai',reply);interviewHistory.push({question:msg,answer:reply}); }else{ // Fallback to general chat if research returned nothing dots.remove(); try{const r=await fetch('/api/ideas/chat',{method:'POST',headers:{'Content-Type':'application/json'},credentials:'include',body:JSON.stringify({message:msg+(ctx?`\n\n[Gene context: ${ctx.slice(0,400)}]`:''),mode:'brain_chat',context_note:'Gene Console V2. '+(ctx?ctx.slice(0,200):'No upload yet.')})});const data=await r.json();addMsg('ai',data?.reply||data?.response||data?.message||'Connection issue.');interviewHistory.push({question:msg,answer:data?.reply||''})} catch(e2){addMsg('ai','Connection issue \u2014 try again.')} } }catch(e){dots.remove();addMsg('ai','Connection issue \u2014 try again.')} } chatBusy=false; } // Admin gating + sub-tabs async function checkAdminAccess(){ try{ const d=await fetch('/api/ideas/auth/whoami',{credentials:'include'}).then(r=>r.json()); const role=d?.viewer?.role||'user'; if(role==='admin'||role==='super_admin'){document.getElementById('admin-content').style.display='flex';document.getElementById('admin-gate').style.display='none'} else{document.getElementById('admin-content').style.display='none';document.getElementById('admin-gate').style.display='block'} }catch(e){} } function showAdminSub(id){ document.querySelectorAll('.admin-sub').forEach(s=>s.style.display='none'); document.getElementById('sub-'+id).style.display=id==='cards'?'flex':'block'; document.querySelectorAll('[id^=asub-]').forEach(b=>b.style.fontWeight='600'); document.getElementById('asub-'+id).style.fontWeight='800'; if(id==='missions')loadMissions(); if(id==='automation')loadDbStats(); } // Lab Team direct agent calls function labChat(agent){document.getElementById('lab-agent-select').value=agent;showAdminSub('labteam')} async function runLabAgent(){ const agent=document.getElementById('lab-agent-select').value; const action=document.getElementById('lab-action-select').value; const task=document.getElementById('lab-task').value.trim(); if(!task){toast('Enter a gene or query',false);return} document.getElementById('lab-output').textContent='Running '+agent+' --action '+action+'...'; try{ const d=await API('/devlab/ptt/run-agent-step',{agent,action,task,viewer_role:'admin',mode:'full_operator'}); document.getElementById('lab-output').textContent=d.output_parsed?JSON.stringify(d.output_parsed,null,2):d.output||JSON.stringify(d,null,2); }catch(e){document.getElementById('lab-output').textContent='Error: '+e.message} } // Curator + Harvester buttons async function runCurator(action){ const btn=document.getElementById('btn-curator');if(btn){btn.disabled=true;btn.textContent='Running...'} document.getElementById('curator-status').textContent='Running '+action+'...'; toast('Curator '+action+' started...'); try{ const d=await API('/devlab/ptt/run-agent-step',{agent:'gene_lab_curator_v1',action,task:'',viewer_role:'admin',mode:'full_operator'}); document.getElementById('curator-status').textContent=d.ok?'Done! Check output below.':'Error: '+(d.error||'failed'); if(d.output_parsed)document.getElementById('lab-output').textContent=JSON.stringify(d.output_parsed,null,2); toast(d.ok?'Curator '+action+' complete':'Curator failed',d.ok); }catch(e){document.getElementById('curator-status').textContent='Error: '+e.message;toast('Failed',false)} if(btn){btn.disabled=false;btn.textContent='Run Full Cycle Now'} } async function runHarvester(action){ const btn=document.getElementById('btn-harvest');if(btn&&action==='harvest'){btn.disabled=true;btn.textContent='Harvesting...'} document.getElementById('harvest-status').textContent='Running '+action+'...'; toast('Harvester '+action+' started...'); try{ const d=await API('/devlab/ptt/run-agent-step',{agent:'research_harvester_v1',action,task:'',viewer_role:'admin',mode:'full_operator'}); if(action==='stats'&&d.output_parsed)document.getElementById('db-stats').innerHTML=formatStats(d.output_parsed); else document.getElementById('harvest-status').textContent=d.ok?'Done!':'Error'; toast(d.ok?'Harvester '+action+' complete':'Failed',d.ok); }catch(e){toast('Failed',false)} if(btn&&action==='harvest'){btn.disabled=false;btn.textContent='Run Full Harvest Now'} } function formatStats(d){ if(!d)return'No data'; return`Studies: ${d.total_studies||0} | Genes: ${d.gene_topics||0} (${d.gene_studies||0} papers) | Peptides: ${d.peptide_topics||0} (${d.peptide_studies||0} papers) | Recent 24h: ${d.recent_24h||0} | Alerts: ${d.pending_alerts||0}`; } async function loadDbStats(){ try{const d=await API('/devlab/ptt/run-agent-step',{agent:'research_harvester_v1',action:'stats',task:'',viewer_role:'admin',mode:'full_operator'}); if(d.output_parsed)document.getElementById('db-stats').innerHTML=formatStats(d.output_parsed); }catch(e){} } // Research Missions const MISSIONS_KEY='pg_research_missions'; function loadMissionsData(){try{return JSON.parse(localStorage.getItem(MISSIONS_KEY)||'[]')}catch{return[]}} function saveMissionsData(m){localStorage.setItem(MISSIONS_KEY,JSON.stringify(m))} async function createMission(){ const title=document.getElementById('rm-title').value.trim(); const objective=document.getElementById('rm-objective').value.trim(); const type=document.getElementById('rm-type').value; const targets=document.getElementById('rm-targets').value.trim(); if(!title||!objective){toast('Title and objective required',false);return} const mission={id:'RM-'+Date.now(),title,objective,type,targets:targets.split(',').map(s=>s.trim()).filter(Boolean),status:'active',created:new Date().toISOString(),steps:[],results:[]}; // Also submit as proposal try{await API('/missions/propose',{title:'[Lab Mission] '+title,summary:objective,category:'Gene Lab',tags:['research','lab_mission']});}catch(e){} const missions=loadMissionsData();missions.unshift(mission);saveMissionsData(missions); document.getElementById('rm-title').value='';document.getElementById('rm-objective').value='';document.getElementById('rm-targets').value=''; toast('Mission launched: '+title);loadMissions(); // Auto-run first research step if(mission.targets.length>0){ for(const t of mission.targets.slice(0,3)){ try{await API('/devlab/gene/research',{query:t+' '+objective.slice(0,50)})}catch(e){} } toast('Initial research dispatched for '+mission.targets.slice(0,3).join(', ')); } } function loadMissions(){ const el=document.getElementById('missions-list');if(!el)return; const missions=loadMissionsData(); if(!missions.length){el.innerHTML='
No active missions. Create one above.
';return} el.innerHTML=missions.map(m=>{ const stColor={'active':'var(--pg-success)','completed':'var(--pg-info)','paused':'var(--pg-warning)'}[m.status]||'var(--pg-text-dim)'; return`
${esc(m.title)}
${esc(m.status)}
${esc(m.objective?.slice(0,120))}
${esc(m.type)} ยท Targets: ${esc((m.targets||[]).join(', ')||'general')} ยท ${new Date(m.created).toLocaleDateString()}
`; }).join(''); } async function runMissionResearch(id){ const missions=loadMissionsData();const m=missions.find(x=>x.id===id);if(!m)return; toast('Dispatching research for: '+m.title); const targets=m.targets.length>0?m.targets:['gene variant actionable']; for(const t of targets.slice(0,5)){ try{await API('/devlab/gene/research',{query:t+' '+m.objective?.slice(0,60)})}catch(e){} } toast('Research dispatched!'); } function completeMission(id){const m=loadMissionsData();const i=m.findIndex(x=>x.id===id);if(i>=0){m[i].status='completed';saveMissionsData(m);loadMissions();toast('Mission completed')}} function deleteMission(id){if(!confirm('Delete this mission?'))return;const m=loadMissionsData().filter(x=>x.id!==id);saveMissionsData(m);loadMissions();toast('Mission deleted')} // Lab Queue async function loadLabQueue(status){ const el=document.getElementById('labqueue-list');if(!el)return; el.innerHTML='
Loading...
'; try{ const d=await API('/devlab/ptt/run-agent-step',{agent:'gene_lab_curator_v1',action:'audit',task:'',viewer_role:'admin',mode:'full_operator'}); // Load queue file directly via research endpoint hack }catch(e){} // Use localStorage as bridge since we can't directly read the queue file from frontend // Instead, call discover which populates and returns the queue try{ const items=JSON.parse(localStorage.getItem('pg_lab_queue')||'[]'); renderLabQueue(items.filter(i=>!status||i.status===status)); }catch(e){el.innerHTML='
Run "Discovery" first to populate the queue.
'} } function renderLabQueue(items){ const el=document.getElementById('labqueue-list'); if(!items.length){el.innerHTML='
No items in queue. Run "Run Discovery Now" to find new genes and peptides.
';return} el.innerHTML=items.map(q=>{ const typeColor=q.type==='gene'?'var(--pg-primary)':'var(--pg-info)'; const stColor={'pending':'var(--pg-warning)','approved':'var(--pg-success)','rejected':'var(--pg-danger)','card_created':'var(--pg-primary)'}[q.status]||'var(--pg-text-dim)'; return`
${esc(q.type)} ${esc(q.name)}
${esc(q.status)}
${esc(q.reason||'')}
${esc(q.submitted_by||'')} ยท ${q.submitted_at?new Date(q.submitted_at).toLocaleDateString():''}
${q.status==='pending'?`
`:''}
`; }).join(''); } async function approveLabItem(id,type,name){ // Update status in localStorage queue const items=JSON.parse(localStorage.getItem('pg_lab_queue')||'[]'); const idx=items.findIndex(i=>i.id===id); if(idx>=0){items[idx].status='approved';items[idx].reviewed_at=new Date().toISOString();localStorage.setItem('pg_lab_queue',JSON.stringify(items))} // Create a card for it if(type==='gene'){ const nc={gene_symbol:name,rsid:'',common_name:'',category:'',actionability_tier:'4',scientific_confidence:'D',env:'staging',status:'draft',inferred_phenotype:'',mechanism_summary:'',safety_note:'',supplements:'',diet_notes:'',peptide_badge:'',evidence_summary_short:'TBD / needs curation',source_links:'',treatment_sources:''}; await API('/devlab/gene/card/save',{card:nc}); toast('Card created for '+name+' โ€” will be enriched on next curator cycle'); }else{ toast('Approved: '+name+' โ€” add to peptide KB manually or via lab team'); } loadLabQueue('pending'); } function rejectLabItem(id){ const items=JSON.parse(localStorage.getItem('pg_lab_queue')||'[]'); const idx=items.findIndex(i=>i.id===id); if(idx>=0){items[idx].status='rejected';items[idx].reviewed_at=new Date().toISOString();localStorage.setItem('pg_lab_queue',JSON.stringify(items))} toast('Rejected');loadLabQueue('pending'); } async function researchLabItem(name){ toast('Researching '+name+'...'); const d=await API('/devlab/gene/research',{query:name+' mechanism therapeutic actionable'}); if(d.ok&&d.answer){addMsg('ai',d.answer);if(!chatOpen)toggleChat()} else toast('Research returned no results',false); } // โ”€โ”€ Medications / PGx Checker โ”€โ”€ let userMeds=[]; function addMed(){ const inp=document.getElementById('med-input');const v=inp.value.trim();if(!v)return; userMeds.push(v);inp.value=''; document.getElementById('med-list').innerHTML=userMeds.map((m,i)=> `${esc(m)}โœ•` ).join(''); } async function checkPGx(){ if(!userMeds.length){toast('Add at least one medication',false);return} const btn=document.getElementById('pgx-btn');btn.disabled=true;btn.textContent='Checking...'; try{ const d=await API('/geneius/pgx/check',{medications:userMeds}); const el=document.getElementById('pgx-results'); if(!d.ok){el.innerHTML='
'+esc(d.error)+'
';return} if(!d.interactions.length){el.innerHTML='
โœ… No interactions found with your genotype. Your medications appear compatible with your pharmacogenomics profile.
';return} el.innerHTML=d.interactions.map(ix=>{ const sevCol=ix.severity==='high'?'var(--pg-danger)':'var(--pg-warning)'; return`
${esc(ix.medication)} โ†’ ${esc(ix.gene)}
${esc(ix.severity)}
${esc(ix.effect)}
${ix.has_genotype?`
๐Ÿงฌ Your genotype: ${esc(ix.gene)} = ${ix.homozygous?'HOMOZYGOUS':'HETEROZYGOUS'} ${esc(ix.genotype||'')}
`:'
โš ๏ธ No genotype data for this gene โ€” upload genetic test to personalize
'}
`; }).join(''); toast(d.interactions_found+' interaction(s) found'); }catch(e){toast('Check failed: '+e.message,false)} btn.disabled=false;btn.textContent='๐Ÿ”ฌ Check Interactions'; } // โ”€โ”€ Biomarkers โ”€โ”€ async function saveBiomarkers(){ const markers={}; document.querySelectorAll('#bio-inputs [data-marker]').forEach(inp=>{ const v=parseFloat(inp.value);if(!isNaN(v))markers[inp.dataset.marker]=v; }); if(!Object.keys(markers).length){toast('Enter at least one value',false);return} try{ const d=await API('/geneius/biomarkers/save',{markers}); if(d.ok){toast('Lab values saved! '+d.total_entries+' entries on file');loadBioHistory()} else toast(d.error||'Save failed',false); }catch(e){toast('Failed: '+e.message,false)} } async function loadBioHistory(){ const el=document.getElementById('bio-history');if(!el)return; try{ const d=await fetch(`${CONFIG.apiBase}/geneius/biomarkers`,{credentials:'include'}).then(r=>r.json()); if(!d.ok||!d.entries?.length){el.innerHTML='No lab values recorded yet.';return} const latest=d.entries[d.entries.length-1]; el.innerHTML=`
Latest Entry (${new Date(latest.date).toLocaleDateString()}):
`+ Object.entries(latest.markers).map(([k,v])=>`${esc(k)}: ${v}`).join('')+ `
${d.entries.length} total entries on file
`; }catch(e){el.innerHTML='Failed to load history'} } // โ”€โ”€ Journey / Achievements โ”€โ”€ async function loadJourney(){ try{ const d=await API('/geneius/journey',{}); if(!d.ok)return; // Inject achievements into dashboard if visible if(d.achievements?.length){ const achvHtml='
๐Ÿ† Achievements ('+d.total_achievements+')
'+ d.achievements.map(a=>`
${a.icon}${esc(a.name)}
`).join('')+ '
'; const dash=document.getElementById('dash-content'); if(dash&&!dash.querySelector('.achv-block')){const div=document.createElement('div');div.className='achv-block';div.innerHTML=achvHtml;dash.appendChild(div)} } }catch(e){} } // Init // Check if returning user โ€” skip splash if(sessionStorage.getItem('pg_entered')){document.getElementById('splash').classList.add('hidden');document.getElementById('main-app').style.display='flex';loadPageGate()} loadCards(); // Restore genotype from server if available (async function(){ try{ const g=await fetch(`${CONFIG.apiBase}/geneius/genotype/profile`,{credentials:'include'}).then(r=>r.json()); if(g.ok&&g.has_data&&g.variants?.length&&!uploadedMatched.length){ uploadedMatched=g.variants; const cd=await API('/devlab/gene/pathway-clusters',{variants:uploadedMatched}); if(cd.ok)uploadedClusters=cd.clusters||[]; buildDashboard(); } loadJourney(); }catch(e){} })(); // โ”€โ”€ DNA Double Helix Splash โ€” full-screen, responsive, mouse-reactive โ”€โ”€ (function(){ const c=document.getElementById('splash-canvas');if(!c)return; const ctx=c.getContext('2d'); let W,H,mx=-999,my=-999,dpr=1; // Responsive sizing โ€” use window dimensions, apply devicePixelRatio for retina function resize(){ dpr=Math.min(window.devicePixelRatio||1,2); W=window.innerWidth; H=window.innerHeight; c.width=W*dpr; c.height=H*dpr; c.style.width=W+'px'; c.style.height=H+'px'; ctx.setTransform(dpr,0,0,dpr,0,0); } resize(); window.addEventListener('resize',resize); // Mouse / touch tracking window.addEventListener('mousemove',e=>{mx=e.clientX;my=e.clientY}); window.addEventListener('mouseleave',()=>{mx=-999;my=-999}); window.addEventListener('touchmove',e=>{ if(e.touches[0]){mx=e.touches[0].clientX;my=e.touches[0].clientY;} },{passive:true}); window.addEventListener('touchend',()=>{mx=-999;my=-999}); // Breakpoint-aware settings function cfg(){ if(W<=480) return{helixR:W*.22,nodes:28,nodeR:2.8,rungs:14,speed:.0006,repR:90,bgParts:40}; if(W<=768) return{helixR:W*.2,nodes:36,nodeR:3.2,rungs:18,speed:.0005,repR:110,bgParts:60}; if(W<=1024) return{helixR:Math.min(W*.18,180),nodes:44,nodeR:3.5,rungs:22,speed:.00045,repR:130,bgParts:80}; return{helixR:Math.min(W*.15,220),nodes:52,nodeR:4,rungs:26,speed:.0004,repR:160,bgParts:100}; } // Background ambient particles let bgPs=[]; function initBg(n){ bgPs=[]; for(let i=0;i-900){const dx=p.x-mx,dy=p.y-my,d=Math.sqrt(dx*dx+dy*dy); if(d0){const f=(1-d/s.repR)*.6;p.vx+=dx/d*f;p.vy+=dy/d*f;}} p.x+=p.vx; p.y+=p.vy; p.vx*=.985; p.vy*=.985; if(p.x<0)p.x=W; if(p.x>W)p.x=0; if(p.y<0)p.y=H; if(p.y>H)p.y=0; ctx.globalAlpha=p.a; ctx.fillStyle='rgba('+p.col+',1)'; ctx.beginPath(); ctx.arc(p.x,p.y,p.r,0,Math.PI*2); ctx.fill(); } // โ”€โ”€ DNA Double Helix โ€” centered, vertical, rotating โ”€โ”€ const cx=W/2, cy=H/2; const helixH=H*.75; // helix spans 75% of viewport height const steps=s.nodes; const R=s.helixR; const nr=s.nodeR; // Build helix points const strand1=[],strand2=[]; for(let i=0;i-900){ const d1=Math.sqrt((x1-mx)**2+(y-my)**2); if(d10){const f=(1-d1/s.repR)*18;dx1=(x1-mx)/d1*f;dy1=(y-my)/d1*f;} const d2=Math.sqrt((x2-mx)**2+(y-my)**2); if(d20){const f=(1-d2/s.repR)*18;dx2=(x2-mx)/d2*f;dy2=(y-my)/d2*f;} } strand1.push({x:x1+dx1,y:y+dy1,z:z1}); strand2.push({x:x2+dx2,y:y+dy2,z:z2}); } // Draw rungs (base pairs) โ€” behind strands ctx.globalAlpha=1; for(let i=0;i