Loading Enterprise OS...

`; } function renderDemoLanding(){ return `
`+PIXEL_T_SVG+`
Triva Enterprise OS
Intelligent Operations Platform

Your Enterprise.
AI-Powered.

Deploy a complete AI operating system for your organization. Autonomous agents, smart workflows, multi-department management.

92 AI Tools Smart DataHub FlexPool Engine Auto Workflows Voice + Chat AI Multi-Department

Loading availability...

Welcome Back

Sign in with your email, phone, or company name

Don't have an account?

Request Free Demo Access
Demo Admin
`; } // ═══ Pixel Background Animation ═══ function initPixelBackground(){ const bg=document.getElementById('pixel-bg'); if(!bg)return; const colors=['#f97316','#c2410c','#34d399','#fbbf24','#047857','#0ea5e9','#4e8ef7']; for(let i=0;i<60;i++){ const p=document.createElement('div'); p.className='demo-pixel'; const size=Math.random()*3+1; p.style.cssText=`width:${size}px;height:${size}px;left:${Math.random()*100}%;top:${Math.random()*100}%;background:${colors[Math.floor(Math.random()*colors.length)]};animation-delay:${Math.random()*4}s;animation-duration:${3+Math.random()*4}s;opacity:${0.1+Math.random()*0.3}`; bg.appendChild(p); } } // ═══ Promotional Offers Engine (Multilingual) ═══ const PROMOS = [ { icon: "fa-rocket", title: { ar: "عرض حصري — نسخة تجريبية مجانية لمدة 72 ساعة", en: "Exclusive Offer — Free 72-Hour Enterprise Trial", de: "Exklusiv — 72 Stunden kostenlose Enterprise-Testversion", fr: "Offre exclusive — Essai Enterprise gratuit de 72h", es: "Oferta exclusiva — Prueba Enterprise gratuita de 72h", tr: "Ozel Teklif — 72 Saat Ucretsiz Enterprise Deneme", zh: "独家优惠 — 72小时免费企业试用", ja: "限定オファー — 72時間無料エンタープライズ試用", ko: "독점 제안 — 72시간 무료 엔터프라이즈 체험", hi: "विशेष प्रस्ताव — 72 घंटे का मुफ्त एंटरप्राइज ट्रायल", pt: "Oferta exclusiva — Teste Enterprise gratuito de 72h" }, desc: { ar: "احصل على وصول كامل لجميع أدوات الذكاء الاصطناعي مع بيئة معزولة بالكامل لمؤسستك", en: "Get full access to 92 AI tools with a completely isolated environment for your organization", de: "Voller Zugang zu 40+ KI-Tools mit vollstandig isolierter Umgebung", fr: "Acces complet a plus de 40 outils IA dans un environnement totalement isole", es: "Acceso completo a mas de 40 herramientas de IA en un entorno completamente aislado", tr: "Tamamen izole ortamda 40'tan fazla yapay zeka aracina tam erisim", zh: "在完全隔离的环境中访问40+人工智能工具", ja: "完全に隔離された環境で40以上のAIツールにフルアクセス", ko: "완전히 격리된 환경에서 40개 이상의 AI 도구에 대한 전체 액세스", hi: "अपने संगठन के लिए 40+ AI टूल्स तक पूर्ण पहुंच", pt: "Acesso total a mais de 40 ferramentas de IA em ambiente isolado" } }, { icon: "fa-building-columns", title: { ar: "مصمّم خصيصاً للقطاعات الحكومية والمؤسسات الكبرى", en: "Built for Government & Large Enterprises", de: "Speziell fur Behorden und Grossunternehmen", fr: "Concu pour les gouvernements et grandes entreprises", es: "Disenado para gobiernos y grandes empresas", tr: "Devlet ve Buyuk Isletmeler Icin Tasarlandi", zh: "专为政府和大型企业打造", ja: "政府機関・大企業向けに構築", ko: "정부 및 대기업을 위해 구축", hi: "सरकार और बड़े उद्यमों के लिए निर्मित", pt: "Projetado para governos e grandes empresas" }, desc: { ar: "نظام تشغيل متكامل بالذكاء الاصطناعي — إدارة الأقسام، سير العمل، التقارير التلقائية، والتصعيدات الذكية", en: "Complete AI operating system — departments, workflows, auto-reports, and smart escalations", de: "Komplettes KI-Betriebssystem — Abteilungen, Workflows, Auto-Berichte, intelligente Eskalationen", fr: "Systeme d'exploitation IA complet — departements, workflows, rapports automatiques", es: "Sistema operativo IA completo — departamentos, flujos de trabajo, reportes automaticos", tr: "Tam yapay zeka isletim sistemi — departmanlar, is akislari, otomatik raporlar", zh: "完整AI操作系统——部门、工作流、自动报告、智能升级", ja: "完全なAIオペレーティングシステム——部門、ワークフロー、自動レポート", ko: "완벽한 AI 운영 체제 — 부서, 워크플로우, 자동 보고서", hi: "संपूर्ण AI ऑपरेटिंग सिस्टम — विभाग, वर्कफ़्लो, स्वचालित रिपोर्ट", pt: "Sistema operacional IA completo — departamentos, fluxos de trabalho, relatorios automaticos" } }, { icon: "fa-file-powerpoint", title: { ar: "إنشاء العروض التقديمية والمستندات بالذكاء الاصطناعي", en: "AI-Powered Presentations & Document Generation", de: "KI-gestutzte Prasentationen und Dokumentenerstellung", fr: "Presentations et documents generes par IA", es: "Presentaciones y documentos generados por IA", tr: "Yapay Zeka ile Sunum ve Dokuman Olusturma", zh: "AI驱动的演示文稿和文档生成", ja: "AIパワードプレゼンテーションとドキュメント生成", ko: "AI 기반 프레젠테이션 및 문서 생성", hi: "AI-संचालित प्रस्तुतियाँ और दस्तावेज़ निर्माण", pt: "Apresentacoes e documentos gerados por IA" }, desc: { ar: "وكيل ذكي ينشئ لك عروض PPTX وتقارير Word وجداول Excel مع دعم كامل للغة العربية", en: "Smart agent creates PPTX presentations, Word reports, and Excel spreadsheets with full multilingual support", de: "Intelligenter Agent erstellt PPTX-Prasentationen, Word-Berichte und Excel-Tabellen", fr: "Agent intelligent creant des presentations PPTX, rapports Word et tableurs Excel", es: "Agente inteligente que crea presentaciones PPTX, informes Word y hojas Excel", tr: "Akilli ajan PPTX sunumlar, Word raporlar ve Excel tablolar olusturur", zh: "智能代理创建PPTX演示文稿、Word报告和Excel电子表格", ja: "スマートエージェントがPPTXプレゼンテーション、Wordレポート、Excelスプレッドシートを作成", ko: "스마트 에이전트가 PPTX 프레젠테이션, Word 보고서, Excel 스프레드시트를 생성", hi: "स्मार्ट एजेंट PPTX प्रस्तुतियाँ, Word रिपोर्ट और Excel स्प्रेडशीट बनाता है", pt: "Agente inteligente cria apresentacoes PPTX, relatorios Word e planilhas Excel" } }, { icon: "fa-shield-halved", title: { ar: "حماية متقدمة — بيئة معزولة بالكامل لكل شركة", en: "Advanced Security — Fully Isolated Per-Company Environment", de: "Erweiterte Sicherheit — Vollstandig isolierte Firmenumgebung", fr: "Securite avancee — Environnement totalement isole par entreprise", es: "Seguridad avanzada — Entorno completamente aislado por empresa", tr: "Gelismis Guvenlik — Sirket Basina Tam Izole Ortam", zh: "高级安全——每家公司完全隔离的环境", ja: "高度なセキュリティ——企業ごとに完全に隔離された環境", ko: "고급 보안 — 회사별 완전 격리 환경", hi: "उन्नत सुरक्षा — प्रत्येक कंपनी के लिए पूर्ण पृथक वातावरण", pt: "Seguranca avancada — Ambiente totalmente isolado por empresa" }, desc: { ar: "كل شركة تحصل على قاعدة بيانات خاصة ومستقلة بالكامل — لا مشاركة بيانات على الإطلاق", en: "Each company gets its own dedicated database — zero data sharing between tenants", de: "Jedes Unternehmen erhalt eine eigene Datenbank — kein Datenaustausch zwischen Mandanten", fr: "Chaque entreprise dispose de sa propre base de donnees — zero partage de donnees", es: "Cada empresa obtiene su propia base de datos — cero comparticion de datos", tr: "Her sirket kendi ozel veritabanina sahiptir — sifir veri paylasimi", zh: "每家公司都有自己的专用数据库——租户之间零数据共享", ja: "各企業が専用のデータベースを取得——テナント間のデータ共有ゼロ", ko: "각 회사는 전용 데이터베이스를 보유 — 테넌트 간 데이터 공유 제로", hi: "प्रत्येक कंपनी को अपना समर्पित डेटाबेस मिलता है — शून्य डेटा साझाकरण", pt: "Cada empresa recebe seu proprio banco de dados dedicado — zero compartilhamento" } } ]; const LANG_FLAGS = { en:{flag:"\u{1F1EC}\u{1F1E7}",name:"English"}, de:{flag:"\u{1F1E9}\u{1F1EA}",name:"Deutsch"}, fr:{flag:"\u{1F1EB}\u{1F1F7}",name:"Francais"}, es:{flag:"\u{1F1EA}\u{1F1F8}",name:"Espanol"}, pt:{flag:"\u{1F1E7}\u{1F1F7}",name:"Portugues"}, tr:{flag:"\u{1F1F9}\u{1F1F7}",name:"Turkce"}, ar:{flag:"\u{1F1F8}\u{1F1E6}",name:"العربية"}, zh:{flag:"\u{1F1E8}\u{1F1F3}",name:"中文"}, ja:{flag:"\u{1F1EF}\u{1F1F5}",name:"日本語"}, ko:{flag:"\u{1F1F0}\u{1F1F7}",name:"한국어"}, hi:{flag:"\u{1F1EE}\u{1F1F3}",name:"हिन्दी"} }; let _promoLang = "en"; let _promoIndex = 0; let _promoInterval = null; function detectPromoLang(){ const bl = navigator.language?.toLowerCase() || "en"; const map = {de:"de",fr:"fr",es:"es",pt:"pt",tr:"tr",ar:"ar",zh:"zh",ja:"ja",ko:"ko",hi:"hi"}; for(const [prefix,lang] of Object.entries(map)){ if(bl.startsWith(prefix)) return lang; } return "en"; } function renderPromo(){ const banner = document.getElementById("promo-banner"); if(!banner) return; const promo = PROMOS[_promoIndex]; const icon = document.getElementById("promo-icon"); const title = document.getElementById("promo-title"); const desc = document.getElementById("promo-desc"); const langBar = document.getElementById("promo-lang-bar"); if(!icon||!title||!desc||!langBar) return; const isRtl = _promoLang === "ar"; banner.querySelector("div").style.direction = isRtl ? "rtl" : "ltr"; banner.querySelector("div").style.textAlign = isRtl ? "right" : "left"; icon.className = "fas " + promo.icon; title.textContent = promo.title[_promoLang] || promo.title.en; desc.textContent = promo.desc[_promoLang] || promo.desc.en; // Language selector pills langBar.innerHTML = Object.entries(LANG_FLAGS).map(([code, data]) => `` ).join(""); banner.style.opacity = "1"; } function startPromoRotation(){ _promoLang = detectPromoLang(); renderPromo(); } setTimeout(startPromoRotation, 1200); // ═══ Custom Industry Toggle ═══ function toggleCustomIndustry(){ const sel=document.getElementById('reg-industry'); const custom=document.getElementById('reg-industry-custom'); if(sel&&custom){ if(sel.value==='Other'){custom.style.display='block';custom.focus();} else{custom.style.display='none';custom.value='';} } } // ═══ Inline Field Validation ═══ function showFieldError(id,msg){ const el=document.getElementById(id); if(!el)return; el.style.borderColor='#ef4444'; el.style.boxShadow='0 0 0 3px rgba(239,68,68,0.1)'; // Remove existing error label const prev=el.parentElement.querySelector('.field-error'); if(prev)prev.remove(); // Add error label const label=document.createElement('div'); label.className='field-error'; label.style.cssText='font-size:10px;color:#ef4444;margin-top:4px;display:flex;align-items:center;gap:4px'; label.innerHTML=' '+msg; el.parentElement.appendChild(label); // Auto-clear on focus el.addEventListener('focus',function clearErr(){ el.style.borderColor='#1a1a1a';el.style.boxShadow=''; const err=el.parentElement.querySelector('.field-error'); if(err)err.remove(); el.removeEventListener('focus',clearErr); },{once:true}); } function clearFieldErrors(){ document.querySelectorAll('.field-error').forEach(e=>e.remove()); document.querySelectorAll('.demo-input').forEach(e=>{e.style.borderColor='#1a1a1a';e.style.boxShadow='';}); } // ═══ Multi-Step Login Flow ═══ async function checkCompanyStep(){ clearFieldErrors(); const company=document.getElementById('login-company')?.value?.trim(); if(!company){showFieldError('login-company','Please enter your email, phone, or company name');return;} const btn=document.querySelector('.demo-btn'); if(btn){btn.disabled=true;btn.innerHTML='  Checking...';} try{ const r=await fetch(API+"/api/demo/check-company",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({company_name:company})}); const d=await r.json(); if(d.status==='active'){ showLoginCredentials(d.company_name||company); } else if(d.status==='expired'){ showExpiredCompany(d.company_name||company,d.expired_at); } else if(d.status==='pending_approval'){ showPendingStatus(d.company_name||company); } else if(d.status==='rejected'){ showRejectedStatus(d.company_name||company); } else { showCompanyNotFound(company); } }catch(e){ toast('Connection error','error'); if(btn){btn.disabled=false;btn.innerHTML='  Continue';} } } function showLoginCredentials(company){ document.getElementById('step-dot-1').classList.add('done'); document.getElementById('step-dot-1').classList.remove('active'); document.getElementById('step-dot-2').classList.add('active'); const card=document.getElementById('auth-step-content'); card.style.animation='slideUp .3s ease'; card.innerHTML=`
${company}
Try different account
`; document.querySelector('#demo-auth-card h3').textContent='Sign In'; document.querySelector('#demo-auth-card h3+p').textContent='Enter your credentials'; } function resetLoginStep(){ document.getElementById('step-dot-1').classList.add('active'); document.getElementById('step-dot-1').classList.remove('done'); document.getElementById('step-dot-2').classList.remove('active'); const card=document.getElementById('auth-step-content'); card.innerHTML=`
`; document.querySelector('#demo-auth-card h3').textContent='Welcome Back'; document.querySelector('#demo-auth-card h3+p').textContent='Sign in with your email, phone, or company name'; } async function doTenantLoginStep(company){ clearFieldErrors(); const username=document.getElementById('tl-user')?.value?.trim(); const password=document.getElementById('tl-pass')?.value; let hasError=false; if(!username){showFieldError('tl-user','Username is required');hasError=true;} if(!password){showFieldError('tl-pass','Password is required');hasError=true;} if(hasError)return; const btn=document.querySelector('.demo-btn'); if(btn){btn.disabled=true;btn.innerHTML='  Signing in...';} try{ const r=await fetch(API+"/api/demo/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({company_name:company,username,password})}); const d=await r.json(); if(r.ok&&d.token){ token=d.token;currentUser=d.user; localStorage.setItem("agency_token",token); localStorage.setItem("agency_user",JSON.stringify(currentUser)); localStorage.setItem("sidebar_collapsed","0"); toast("Welcome back, "+company,"success"); render(); }else{ toast(d.detail||"Invalid credentials","error"); if(btn){btn.disabled=false;btn.innerHTML='  Sign In';} } }catch(e){toast(e.message,"error");if(btn){btn.disabled=false;btn.innerHTML='  Sign In';}} } function showExpiredCompany(company,expiredAt){ const card=document.getElementById('auth-step-content'); const expDate=new Date(expiredAt).toLocaleDateString('en-US',{year:'numeric',month:'short',day:'numeric'}); card.innerHTML=`

${company}

Demo expired on ${expDate}

Your demo session has expired but your data is safely preserved. Request an extension to continue where you left off:

Back
`; document.querySelector('#demo-auth-card h3').textContent='Demo Expired'; document.querySelector('#demo-auth-card h3+p').textContent='Request a new demo session'; } async function submitExpiredRequest(company){ clearFieldErrors(); const reason=document.getElementById('expired-reason')?.value?.trim(); const email=document.getElementById('expired-email')?.value?.trim(); let hasError=false; if(!email){showFieldError('expired-email','Email is required');hasError=true;} if(!reason){showFieldError('expired-reason','Please provide a reason for re-access');hasError=true;} if(hasError)return; const btn=document.querySelector('.demo-btn'); if(btn){btn.disabled=true;btn.innerHTML='  Submitting...';} try{ // REQUEST EXTENSION — NOT a new account. Keeps existing data intact. const r=await fetch(API+"/api/demo/request-extension",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({ company_name:company,email:email,reason:reason })}); const d=await r.json(); if(r.ok){ showPendingStatus(company); toast('Extension request submitted. Your data is safe.','success'); }else{ toast(d.detail||'Failed to submit','error'); if(btn){btn.disabled=false;btn.innerHTML='  Request Extension';} } }catch(e){toast(e.message,'error');if(btn){btn.disabled=false;btn.innerHTML='  Request Extension';}} } function showPendingStatus(company){ const card=document.getElementById('auth-step-content'); card.innerHTML=`

Pending Approval

Your demo request for ${company} is being reviewed by our team. You'll receive access once approved.

Awaiting administrator review

You will be notified by email when your demo is ready.

Back
`; document.querySelector('#demo-auth-card h3').textContent='Request Submitted'; document.querySelector('#demo-auth-card h3+p').textContent=''; } function showRejectedStatus(company){ const card=document.getElementById('auth-step-content'); card.innerHTML=`

Request Declined

Your previous demo request for ${company} was not approved. Please contact us for more information.

Try different company
`; } function showCompanyNotFound(company){ const card=document.getElementById('auth-step-content'); card.innerHTML=`

Account not found

No active demo found for ${company}.

Try again
`; } // ═══ Registration Page (Separate Full Page) ═══ function showDemoRegisterPage(){ document.getElementById("root").innerHTML=`
Back to Login
`+PIXEL_T_SVG+` Triva Enterprise OS

Start Your Free Trial

Get a fully isolated environment with all features unlocked. Your data is completely private.

Isolated Environment

Separate database for your company. No data mixing.

Full AI Access

92 AI tools, function calling, multi-model support.

Admin Review

Requests are reviewed by our team before activation.

24-Hour Access

Extendable up to 72 hours. Auto-cleaned after expiry.

Create Demo Account

Fill in your details. We'll review and activate your demo.

Login Credentials (for after approval)

Trial Duration

Need more time? Request extended access (subject to review)

By registering, you agree to responsible use of the demo environment.

`; setTimeout(()=>initPixelBackground(),100); // Swap pixel-bg id const pbr=document.getElementById('pixel-bg-reg'); if(pbr)pbr.id='pixel-bg'; } async function submitDemoRegistration(){ clearFieldErrors(); const company=document.getElementById('reg-company')?.value?.trim(); const email=document.getElementById('reg-email')?.value?.trim(); const phoneCode=document.getElementById('reg-phone-code')?.value||'+966'; const phoneNum=document.getElementById('reg-phone')?.value?.trim()||''; const phone=phoneNum?phoneCode+phoneNum:''; const size=document.getElementById('reg-size')?.value||''; let industry=document.getElementById('reg-industry')?.value||''; if(industry==='Other'){ const custom=document.getElementById('reg-industry-custom')?.value?.trim(); if(custom)industry=custom; } const username=document.getElementById('reg-user')?.value?.trim(); const password=document.getElementById('reg-pass')?.value; // Trial duration const trialRadio=document.querySelector('input[name="trial_hours"]:checked'); const trialHours=trialRadio?parseInt(trialRadio.value):24; const extRadio=document.querySelector('input[name="extended_days"]:checked'); const extDays=extRadio?parseInt(extRadio.value):0; const orgFor=document.getElementById('reg-org-for')?.value?.trim()||''; const extReason=document.getElementById('reg-ext-reason')?.value?.trim()||''; let hasError=false; if(!company){showFieldError('reg-company','Company name is required');hasError=true;} if(!email){showFieldError('reg-email','Email is required');hasError=true;} else if(!/^[^@]+@[^@]+\.[^@]+$/.test(email)){showFieldError('reg-email','Please enter a valid email');hasError=true;} if(!username){showFieldError('reg-user','Username is required');hasError=true;} if(!password||password.length<4){showFieldError('reg-pass','Password must be at least 4 characters');hasError=true;} if(extDays>0&&!orgFor){showFieldError('reg-org-for','Organization name is required for extended access');hasError=true;} if(extDays>0&&!extReason){showFieldError('reg-ext-reason','Please provide a reason for extended access');hasError=true;} if(hasError)return; const btn=document.querySelector('.demo-btn'); if(btn){btn.disabled=true;btn.innerHTML='  Submitting...';} try{ const r=await fetch(API+"/api/demo/request",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({ company_name:company,username,password,email,phone,company_size:size,industry, trial_hours:trialHours,extended_days:extDays,organization_for:orgFor,extended_reason:extReason })}); const d=await r.json(); if(r.ok){ showRegistrationSuccess(company,email,trialHours,extDays); }else{ toast(d.detail||'Registration failed','error'); if(btn){btn.disabled=false;btn.innerHTML='  Submit Demo Request';} } }catch(e){toast(e.message,'error');if(btn){btn.disabled=false;btn.innerHTML='  Submit Demo Request';}} } let _selectedTrialHours=24; let _selectedExtDays=0; function selectTrialDuration(el,hours){ _selectedTrialHours=hours; document.querySelectorAll('.trial-opt').forEach(o=>{o.style.border='1px solid #1a1a1a';o.style.background='#0a0a0a';o.style.color='#666';o.classList.remove('selected');}); el.style.border='1px solid #f97316';el.style.background='rgba(249,115,22,0.08)';el.style.color='#f97316';el.classList.add('selected'); el.closest('label').querySelector('input').checked=true; } function selectExtDuration(el,days){ const wasSelected=el.classList.contains('ext-selected'); document.querySelectorAll('.ext-opt').forEach(o=>{o.style.border='1px solid #1a1a1a';o.style.background='#0a0a0a';o.style.color='#555';o.classList.remove('ext-selected');}); const extFields=document.getElementById('ext-fields'); if(wasSelected){ _selectedExtDays=0; document.querySelectorAll('input[name="extended_days"]').forEach(r=>r.checked=false); if(extFields)extFields.style.display='none'; }else{ _selectedExtDays=days; el.style.border='1px solid #f59e0b';el.style.background='rgba(245,158,11,0.08)';el.style.color='#f59e0b';el.classList.add('ext-selected'); el.closest('label').querySelector('input').checked=true; if(extFields)extFields.style.display='flex'; } } function showRegistrationSuccess(company,email,trialHours,extDays){ const extMsg=extDays>0?`

Extended Access Request (${extDays} days)

Your extended access request will be reviewed separately. You can start using the platform with your ${trialHours}-hour trial immediately after the initial approval.

`:''; document.getElementById("root").innerHTML=`

Request Submitted!

Your demo request for ${company} has been submitted.

Trial Duration: ${trialHours} hours

${extMsg}
What happens next?
1 Admin reviews your request
2 Your isolated environment gets provisioned
3 You receive an email at ${email}
`; setTimeout(()=>initPixelBackground(),100); } // ═══ Old functions kept for compatibility ═══ function showTenantLogin(){ // Redirect to main landing with login flow render(); } async function registerTenant(){ // Redirect to new registration page showDemoRegisterPage(); } function showProvisioning(company){ document.getElementById("root").innerHTML=`
`+PIXEL_T_SVG+`

Building Your Enterprise OS

Setting up ${company}

Creating isolated database...
Deploying AI engine...
Configuring departments...
Finalizing setup...
`; } // ═══ Demo Admin Panel ═══ let _demoAdminToken = localStorage.getItem("demo_admin_token")||""; function renderDemoAdminPanel(){ if(!_demoAdminToken) return renderDemoAdminLogin(); return `
`+PIXEL_T_SVG+`
Demo Admin Panel
MANAGEMENT CONSOLE
Back

Settings

Quick Actions

`; } function renderDemoAdminLogin(){ return `
`+PIXEL_T_SVG+`

Demo Admin

Enter admin password

`; } async function loginDemoAdmin(){ const pass=document.getElementById('admin-pass')?.value; if(!pass)return; try{ const r=await fetch(API+"/api/demo-admin/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({password:pass})}); const d=await r.json(); if(r.ok&&d.token){_demoAdminToken=d.token;localStorage.setItem("demo_admin_token",d.token);document.getElementById("root").innerHTML=renderDemoAdminPanel();setTimeout(loadAdminData,300);} else{toast(d.detail||"Invalid password","error");} }catch(e){toast("Connection error","error");} } let _adminData=null,_adminTab="pending"; async function loadAdminData(){ try{ const r=await fetch(API+"/api/demo-admin/dashboard",{headers:{"X-Admin-Token":_demoAdminToken}}); if(r.status===403){_demoAdminToken='';localStorage.removeItem('demo_admin_token');location.reload();return;} _adminData=await r.json(); renderAdminStats();renderAdminSettings();showAdminTab(_adminTab); }catch(e){console.error(e);} } function renderAdminStats(){ if(!_adminData)return; const d=_adminData; const expired=d.tenants.filter(t=>t.status==='expired').length; document.getElementById("admin-stats").innerHTML=[ {l:"Active",v:d.active_count,c:"#f97316",i:"fa-circle-check"}, {l:"Pending",v:d.pending_count,c:"#f59e0b",i:"fa-hourglass-half"}, {l:"Expired",v:expired,c:"#666",i:"fa-clock-rotate-left"}, {l:"Total",v:d.total_count,c:"#3b82f6",i:"fa-database"}, {l:"Events",v:d.usage_summary?.total_events||0,c:"#a855f7",i:"fa-chart-line"}, ].map(s=>`
${s.v}
${s.l}
`).join(""); // Render charts renderAdminCharts(); } function renderAdminCharts(){ if(!_adminData)return; const us=_adminData.usage_summary||{}; const charts=document.getElementById("admin-charts"); // Top Pages chart const pages=us.top_pages||[]; const tools=us.top_ai_tools||[]; const maxP=Math.max(...pages.map(p=>p.count),1); const maxT=Math.max(...tools.map(t=>t.count),1); charts.innerHTML=`

Most Visited Pages

${pages.length===0?'

No data yet

':pages.slice(0,8).map(p=>`
${p.page}
${p.count}
`).join('')}

AI Tool Usage

${tools.length===0?'

No AI tool data yet

':tools.slice(0,8).map(t=>`
${t.tool}
${t.count}
`).join('')}
`; } function renderAdminSettings(){ if(!_adminData)return; const s=_adminData.settings; document.getElementById("admin-settings-form").innerHTML=`
`; } async function saveAdminSettings(){ const data={}; const mt=document.getElementById("set-max-tenants")?.value;if(mt)data.max_tenants=parseInt(mt); const dh=document.getElementById("set-default-hours")?.value;if(dh)data.default_hours=parseInt(dh); const mh=document.getElementById("set-max-hours")?.value;if(mh)data.max_hours=parseInt(mh); const ap=document.getElementById("set-admin-pass")?.value;if(ap)data.admin_password=ap; try{const r=await fetch(API+"/api/demo-admin/settings",{method:"POST",headers:{"X-Admin-Token":_demoAdminToken,"Content-Type":"application/json"},body:JSON.stringify(data)});if(r.ok){toast("Settings saved","success");loadAdminData();}}catch(e){toast("Error","error");} } function renderAdminTenants(){ if(!_adminData)return; const tab=_adminTab||'pending'; const list=document.getElementById("admin-tenants-list"); if(!list)return; let tenants=_adminData.tenants||[]; if(tab==='pending')tenants=tenants.filter(t=>t.status==='pending_approval'); else if(tab==='active')tenants=tenants.filter(t=>t.status==='active'); if(tenants.length===0){list.innerHTML='
No '+tab+' tenants
';return;} let html=''; tenants.forEach(function(t){ const sc={active:"#f97316",pending_approval:"#f59e0b",expired:"#666",rejected:"#ef4444"}[t.status]||"#555"; const exp=t.expires_at?new Date(t.expires_at).toLocaleString():"\u2014"; html+='
'; html+='
'; html+='
'+t.company_name+''+t.status.replace('_',' ')+'
'; let btns=''; if(t.status==='pending_approval'){ const trialH=t.trial_hours||24; const extD=t.extended_days_requested||0; const extR=t.extended_reason||''; const orgF=t.organization_for||''; const extStatus=t.extension_status||'none'; btns+=''; btns+=''; btns+=''; if(extD>0){ html+='
'; html+='
'; html+='
Extended Access Request: '+extD+' days
'; if(orgF)html+='
Organization: '+orgF+'
'; if(extR)html+='
Reason: '+extR+'
'; html+='
'; html+=''; html+='Status: '+(extStatus==='approved'?'Approved':extStatus==='pending'?'Pending':'Not Requested')+''; html+='
'; // Re-open the info div html+='
'; } } if(t.status==='active'){ btns+=''; btns+=''; if(t.status==='active'){btns+='';} if(t.status==='suspended'){btns+='';} } html+='
'+btns+'
'; html+='
'; if(t.admin_email)html+=' '+t.admin_email+''; if(t.admin_phone)html+=' '+t.admin_phone+''; if(t.industry)html+=' '+t.industry+''; if(t.company_size)html+=' '+t.company_size+''; if(t.user_count)html+=' '+t.user_count+' users'; html+=' '+exp+'
'; }); list.innerHTML=html; } function showAdminTab(tab){ document.querySelectorAll('.admin-tab').forEach(b=>{b.style.borderBottom='1px solid #1a1a1a';b.style.background='transparent';b.style.color='#555';}); const btn=document.getElementById('atab-'+tab); if(btn){btn.style.borderBottom='2px solid #f97316';btn.style.background='#0a0a0a';btn.style.color='#f97316';} const tenantsList=document.getElementById('admin-tenants-list'); const providersPanel=document.getElementById('admin-providers-panel'); if(tab==='providers'){ if(tenantsList)tenantsList.style.display='none'; if(providersPanel){providersPanel.style.display='block';loadAdminProviders();} }else{ if(tenantsList)tenantsList.style.display='block'; if(providersPanel)providersPanel.style.display='none'; _adminTab=tab;renderAdminTenants(); } } async function adminApproveTenant(id){ const durSelect=document.getElementById('approve-dur-'+id); const hours=durSelect?parseInt(durSelect.value):24; try{ const r=await fetch(API+"/api/demo-admin/tenant/"+id+"/approve",{method:"POST",headers:{"X-Admin-Token":_demoAdminToken,"Content-Type":"application/json"},body:JSON.stringify({hours:hours})}); if(r.ok){toast("Approved ("+hours+"h)","success");loadAdminData();} else{const d=await r.json();toast(d.detail||"Failed","error");} }catch(e){toast("Error","error");} } async function adminApproveExtension(id,days){ try{ const r=await fetch(API+"/api/demo-admin/tenant/"+id+"/approve-extension",{method:"POST",headers:{"X-Admin-Token":_demoAdminToken,"Content-Type":"application/json"},body:JSON.stringify({days:days})}); if(r.ok){toast("Extension approved (+"+days+" days)","success");loadAdminData();} else{const d=await r.json();toast(d.detail||"Failed","error");} }catch(e){toast("Error","error");} } async function adminRejectTenant(id){try{const r=await fetch(API+"/api/demo-admin/tenant/"+id+"/reject",{method:"POST",headers:{"X-Admin-Token":_demoAdminToken}});if(r.ok){toast("Rejected","success");loadAdminData();}}catch(e){toast("Error","error");}} async function adminResetPassword(id,company){ const newPass=prompt('New password for '+company+':'); if(!newPass||newPass.length<4){if(newPass!==null)toast('Password must be at least 4 characters','error');return;} try{ const r=await fetch(API+"/api/demo-admin/tenant/"+id+"/reset-password",{method:"POST",headers:{"X-Admin-Token":_demoAdminToken,"Content-Type":"application/json"},body:JSON.stringify({password:newPass})}); if(r.ok){toast("Password reset for "+company,"success");} else{const d=await r.json();toast(d.detail||"Failed","error");} }catch(e){toast("Error","error");} } async function adminCleanExpired(){ if(!confirm('Clean all expired tenant databases?'))return; try{ const r=await fetch(API+"/api/demo-admin/clean-expired",{method:"POST",headers:{"X-Admin-Token":_demoAdminToken}}); if(r.ok){const d=await r.json();toast("Cleaned "+d.cleaned+" expired tenants","success");loadAdminData();} }catch(e){toast("Error","error");} } function adminExportData(){ if(!_adminData)return; const data=JSON.stringify(_adminData,null,2); const blob=new Blob([data],{type:'application/json'}); const url=URL.createObjectURL(blob); const a=document.createElement('a');a.href=url;a.download='demo_admin_export_'+new Date().toISOString().split('T')[0]+'.json';a.click();URL.revokeObjectURL(url); toast("Data exported","success"); } async function adminExtendTenant(id,h){if(!h)return;try{const r=await fetch(API+"/api/demo-admin/tenant/"+id+"/update",{method:"POST",headers:{"X-Admin-Token":_demoAdminToken,"Content-Type":"application/json"},body:JSON.stringify({hours:parseInt(h)})});if(r.ok){toast("Extended +"+h+"h","success");loadAdminData();}}catch(e){toast("Error","error");}} async function loadAdminProviders(){ const panel=document.getElementById('admin-providers-panel'); if(!panel)return; panel.innerHTML='
Loading providers...
'; try{ const r=await fetch(API+"/api/demo-admin/providers",{headers:{"X-Admin-Token":_demoAdminToken}}); if(!r.ok){panel.innerHTML='
Could not load providers
';return;} const d=await r.json(); renderAdminProviders(panel,d.providers||[]); }catch(e){panel.innerHTML='
Error: '+e.message+'
';} } function renderAdminProviders(panel,providers){ let html='

AI Providers ('+providers.length+')

'; if(providers.length===0){ html+='
No AI providers configured
Providers can be added from the agency admin panel
'; }else{ providers.forEach(function(p){ const isActive=p.is_active?true:false; const models=typeof p.models==='string'?JSON.parse(p.models||'[]'):p.models||[]; html+='
'; html+='
'; html+='
'+p.name+'
'; html+='
'; html+=''; html+=''; html+='
'; html+='
'; models.forEach(function(m){html+=''+m+'';}); html+='
'; html+='
API Key: \u2022\u2022\u2022\u2022\u2022\u2022 | Status: '+(isActive?'Active':'Inactive')+'
'; html+='
'; }); } panel.innerHTML=html; } async function toggleDemoProvider(pid,active){ try{ await fetch(API+"/api/demo-admin/providers/"+pid,{method:"PUT",headers:{"X-Admin-Token":_demoAdminToken,"Content-Type":"application/json"},body:JSON.stringify({is_active:active})}); loadAdminProviders(); }catch(e){toast(e.message,"error");} } async function deleteDemoProvider(pid,name){ if(!confirm("Delete provider "+name+"?"))return; try{ await fetch(API+"/api/demo-admin/providers/"+pid,{method:"DELETE",headers:{"X-Admin-Token":_demoAdminToken}}); loadAdminProviders(); }catch(e){toast(e.message,"error");} } setTimeout(()=>{if(_demoAdminToken&&window.location.hash==='#demo-admin')loadAdminData();},500); const PIXEL_T_SVG=` `; // ═══ Usage Analytics Tracking ═══ function trackEvent(type, data){ if(!token)return; try{fetch(API+"/api/demo/track",{method:"POST",headers:{"Authorization":"Bearer "+token,"Content-Type":"application/json"},body:JSON.stringify({type,data})}).catch(()=>{});}catch(e){} } // Track page navigation const _origNav = typeof nav === 'function' ? nav : null; function trackNav(el){ const page=el?.dataset?.page||el?.getAttribute?.('data-page')||''; if(page)trackEvent('page_view',page); if(_origNav)_origNav(el); else if(typeof loadPage==='function')loadPage(page); } // Auto-track on page load after login const _origLoadPage = typeof loadPage === 'function' ? loadPage : null; function initTracking(){ // Hook into loadPage to track every page navigation if(typeof window.loadPage === 'function' && !window._trackingHooked){ const orig = window.loadPage; window.loadPage = function(page){ trackEvent('page_view', page || 'unknown'); return orig.apply(this, arguments); }; window._trackingHooked = true; } // Track initial page trackEvent('page_view', 'chat'); // Track sidebar nav clicks setTimeout(()=>{ document.querySelectorAll('.nav-item').forEach(item=>{ item.addEventListener('click',()=>{ const page = item.dataset?.page || item.textContent?.trim()?.substring(0,30) || ''; if(page) trackEvent('feature_use', page); }); }); }, 1000); } setTimeout(()=>{if(token)initTracking();},2000); function finishProvisioning(company,username,password,expires){ document.getElementById("root").innerHTML=`

Your OS is Ready!

${company} environment is live

Username${username}
Password${password}
Expires${new Date(expires).toLocaleString()}

Save your credentials. You can extend your trial up to 72 hours.

`; } function showMTQueue(position,message){ document.getElementById("root").innerHTML=`

All Slots Occupied

${message||"Please try again later."}

#${position}

Your position in queue

`; } // Load slots info + init pixel background setTimeout(async()=>{ try{ const r=await fetch(API+"/api/demo/slots"); const d=await r.json(); const el=document.getElementById("demo-slots-info"); if(el){ const avail=d.available||0; const color=avail>5?"#f97316":avail>2?"#f59e0b":"#ef4444"; el.innerHTML=' '+avail+' of '+d.max+' slots available'+(d.queue_length>0?'  |  '+d.queue_length+' in queue':''); } }catch(e){} initPixelBackground(); },300); function showDemoQueue(position, total, maxConcurrent, email){ const card=document.querySelector('.demo-card'); if(!card)return; card.innerHTML=`

Demo is Full

${maxConcurrent} companies are currently using the demo.

#${position}
Your position in queue
`; } function showDemoCredentials(username, password, expires){ const overlay=document.createElement('div'); overlay.style.cssText='position:fixed;inset:0;z-index:10000;background:rgba(0,0,0,0.7);display:flex;align-items:center;justify-content:center;backdrop-filter:blur(4px)'; overlay.innerHTML=`

Demo Access Ready!

Save these credentials — valid for 24 hours

Username ${username}
Password ${password}

You can use these to log back in anytime within 24 hours

`; document.body.appendChild(overlay); } function showDemoExpiryNotification(hours,minutes){ const notif=document.createElement('div'); notif.style.cssText='position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:10001;background:var(--bg2);border:1px solid rgba(245,158,11,0.3);border-radius:16px;padding:28px;max-width:440px;width:90%;box-shadow:0 20px 40px rgba(0,0,0,0.5)'; notif.innerHTML=`

Demo Expiring Soon

Your demo session expires in ${hours}h ${minutes}m. Want to continue with a full platform?

`; const backdrop=document.createElement('div'); backdrop.style.cssText='position:fixed;inset:0;z-index:10000;background:rgba(0,0,0,0.5)'; backdrop.onclick=()=>{backdrop.remove();notif.remove()}; document.body.appendChild(backdrop); document.body.appendChild(notif); } async function submitInterest(btn){ const email=$("#interest-email")?.value?.trim(); const company=$("#interest-company")?.value?.trim(); const msg=$("#interest-msg")?.value?.trim(); if(!email){toast("Please enter your email","error");return;} btn.disabled=true;btn.textContent="Sending..."; try{ await fetch(API+"/api/demo/interest",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email,company,message:msg,name:currentUser?.full_name||"",source:"demo_expiry"})}); btn.closest('div[style*=fixed]').remove(); document.querySelector('div[style*="inset:0"]')?.remove(); toast("Thank you! Our team will contact you shortly."); }catch(e){toast("Failed to submit","error");btn.disabled=false;btn.textContent="Request Full Access";} } function showDemoLogin(){ const card=document.querySelector('.card'); if(!card)return; card.innerHTML=`

Demo Login

Enter the credentials you received

Back to registration
`; } async function doDemoLogin(){ const username=$("#demo-login-user")?.value?.trim(); const password=$("#demo-login-pass")?.value?.trim(); if(!username||!password){toast("Please fill in all fields","error");return;} try{ const r=await fetch(API+"/api/auth/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username,password})}); const d=await r.json(); if(r.ok&&d.token){ token=d.token;currentUser=d.user;currentUser.is_demo=true; localStorage.setItem("agency_token",token); localStorage.setItem("agency_user",JSON.stringify(currentUser)); toast("Welcome back!"); render(); }else{toast(d.detail||"Invalid credentials","error");} }catch(e){toast(e.message,"error")} } function showRegisterForm(){ const box=document.querySelector(".login-box"); if(!box)return; box.innerHTML=`

Request Access

Submit details for admin approval

`; } async function submitRegistration(){ const name=document.getElementById("reg-name")?.value; const position=document.getElementById("reg-position")?.value; const empid=document.getElementById("reg-empid")?.value; const email=document.getElementById("reg-email")?.value; if(!name||!email){toast("Name and email required","error");return} try{ const r=await fetch("/api/auth/request-access",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({full_name:name,position,employee_id:empid,email})}); const d=await r.json(); if(d.status==="pending"){toast("Request submitted! Admin will review.","success");setTimeout(()=>render(),2000);} else{toast(d.detail||"Error","error")} }catch(e){toast(e.message,"error")} } function renderActivation(){ return `

License Activation

Enter your license key to activate this agency

`; } function renderBrandSetup(){ return `

Company Setup

Set your company identity. This may be permanently locked.

`; } function renderApp(){ return `
⚠️ DEVELOPMENT ENVIRONMENT — DO NOT USE FOR PRODUCTION ⚠️
`; } function toggleSidebar(){ const s=document.getElementById("app-sidebar"); if(s)s.classList.toggle("collapsed"); localStorage.setItem("sidebar_collapsed",s?.classList.contains("collapsed")?"1":"0"); } function showSidebarOverlay(){ const sidebar=document.getElementById("app-sidebar"); if(!sidebar)return; // Show sidebar as overlay on chat page sidebar.style.display="flex"; sidebar.classList.remove("collapsed"); sidebar.style.position="absolute"; sidebar.style.left="0"; sidebar.style.top="0"; sidebar.style.bottom="0"; sidebar.style.zIndex="100"; sidebar.style.boxShadow="4px 0 20px rgba(0,0,0,.5)"; // Add overlay backdrop let overlay=document.getElementById("sidebar-overlay"); if(!overlay){ overlay=document.createElement("div"); overlay.id="sidebar-overlay"; overlay.style.cssText="position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:99;cursor:pointer"; overlay.onclick=()=>{hideSidebarOverlay();}; document.body.appendChild(overlay); } } function hideSidebarOverlay(){ const sidebar=document.getElementById("app-sidebar"); if(sidebar&¤tPage==="chat"){ sidebar.style.display="none"; sidebar.style.position=""; sidebar.style.left=""; sidebar.style.boxShadow=""; } const overlay=document.getElementById("sidebar-overlay"); if(overlay)overlay.remove(); } function toggleNavCat(cat){ cat.classList.toggle("collapsed-cat"); // Save category state per user const states={}; document.querySelectorAll(".nav-category").forEach(c=>{ const k=c.dataset.cat; if(k)states[k]=c.classList.contains("collapsed-cat"); }); localStorage.setItem("nav_cat_states",JSON.stringify(states)); } function restoreNavCatStates(){ try{ const states=JSON.parse(localStorage.getItem("nav_cat_states")||"{}"); document.querySelectorAll(".nav-category").forEach(c=>{ const k=c.dataset.cat; if(k&&states[k])c.classList.add("collapsed-cat"); }); }catch(e){} } function setSidebarView(mode){ localStorage.setItem("sidebar_view",mode); const sidebar=document.getElementById("app-sidebar"); if(!sidebar)return; sidebar.classList.remove("view-list","view-grid","view-compact"); if(mode==="list"){ sidebar.classList.add("view-list"); document.querySelectorAll(".nav-cat-panel").forEach(p=>p.classList.add("open")); }else{ sidebar.classList.add("view-grid"); document.querySelectorAll(".nav-cat-panel").forEach(p=>p.classList.remove("open")); document.querySelectorAll(".nav-cat-btn").forEach(b=>b.classList.remove("active-cat")); } document.querySelectorAll(".sidebar-view-toggle button").forEach(b=>b.classList.remove("active")); const btn=document.getElementById("sv-"+mode); if(btn)btn.classList.add("active"); } function initSidebarView(){ const mode=localStorage.getItem("sidebar_view")||"grid"; setSidebarView(mode); } function openMobileSidebar(){ var sb=document.getElementById('app-sidebar'); var ov=document.getElementById('sidebar-overlay'); if(sb){sb.classList.add('mobile-open');sb.classList.remove('collapsed');} if(ov)ov.classList.add('show'); } function closeMobileSidebar(){ var sb=document.getElementById('app-sidebar'); var ov=document.getElementById('sidebar-overlay'); if(sb)sb.classList.remove('mobile-open'); if(ov)ov.classList.remove('show'); } function toggleCatPanel(cat, btn){ document.querySelectorAll('.nav-cat-panel').forEach(function(p){p.classList.remove('open')}); document.querySelectorAll('.nav-cat-btn').forEach(function(b){b.classList.remove('active-cat')}); var panel=document.getElementById('panel-'+cat); if(panel && !panel.classList.contains('open')){panel.classList.add('open');btn.classList.add('active-cat');} } function nav(el){ if(window.innerWidth<=768) closeMobileSidebar(); $$(".nav-item").forEach(n=>n.classList.remove("active")); el.classList.add("active"); currentPage=el.dataset.page;localStorage.setItem("agency_current_page",currentPage); const m=document.getElementById("page-content");if(m){m.scrollTop=0;m.classList.toggle("chat-active",el.dataset.page==="chat");} // Always show sidebar const sidebar=document.getElementById("app-sidebar"); if(sidebar){sidebar.style.display="flex";sidebar.style.position="";sidebar.style.boxShadow="";} hideSidebarOverlay(); // Show chat sessions in sidebar when on chat page const chatSessions=document.getElementById("sidebar-chat-sessions"); if(chatSessions){chatSessions.style.display=currentPage==="chat"?"block":"none";} loadPage(currentPage); } async function loadPage(page){ const c=$("#page-content");if(!c)return; c.classList.toggle("chat-active",page==="chat"); try{ const info=await api("/api/system/info"); const bn=$("#brand-name-display");if(bn)bn.textContent=info.company||"Enterprise Agency"; const bv=$("#brand-version");if(bv)bv.textContent="v"+info.version; }catch{} if(page==="chat")renderChat(c); else if(page==="dashboard")await renderDashboard(c); else if(page==="agents")await renderAgents(c); else if(page==="tasks")await renderTasks(c); else if(page==="messages")await renderMessages(c); else if(page==="schedule")await renderSchedule(c); else if(page==="registry")await renderRegistry(c); else if(page==="escalations")await renderEscalations(c); else if(page==="contracts")await renderContracts(c); else if(page==="customer-agents")await renderCustomerAgents(c); else if(page==="channels")await renderChannels(c); else if(page==="ai-providers")await renderAIProviders(c); else if(page==="flexpool")await renderFlexPool(c); else if(page==="datahub")await renderDataHub(c); else if(page.startsWith("datahub-"))await renderDataHubTable(c,page.replace("datahub-","")); else if(page.startsWith("flexpool-"))await renderFlexPoolModule(c,page.replace("flexpool-","")); else if(page==="platform-assistant")await renderPlatformAssistant(c); else if(page==="plugins")await renderPlugins(c); else if(page==="network")await renderNetwork(c); else if(page==="license")await renderLicense(c); else if(page==="updates")await renderUpdates(c); else if(page==="connections")await renderConnections(c); else if(page==="reports")await renderReports(c); else if(page==="learning")await renderLearning(c); else if(page==="settings")await renderSettings(c); else if(page==="feature-licenses")await renderFeatureLicenses(c); else if(page==="users")await renderUsers(c); else if(page==="departments")await renderDepartments(c); else if(page==="org-tree")await renderOrgTree(c); else if(page==="notifications")await renderNotifications(c); else if(page==="service-keys")await renderServiceKeys(c); else if(page==="cross-dept")await renderCrossDept(c); else if(page==="meetings")await renderMeetings(c); else if(page==="goals")await renderGoals(c); else if(page==="hr")await renderHR(c); else if(page==="assets")await renderAssets(c); else if(page==="knowledge")await renderKnowledge(c); else if(page==="announcements")await renderAnnouncements(c); else if(page==="workflows")await renderWorkflows(c); else if(page==="org-import")await renderOrgImport(c); else if(page==="auth-settings")await renderAuthSettings(c); else if(page.startsWith("custom-app-"))await renderCustomApp(c,page.replace("custom-app-","")); // Update notification badge try{const n=await api("/api/notifications");const b=$("#notif-badge");if(b){b.style.display=n.unread_count>0?"block":"none";}}catch{} loadCustomAppsNav(); } async function loadCustomAppsNav(){ try{ const d=await api("/api/custom-apps"); const nav=document.getElementById("custom-apps-nav"); if(!nav||!d.apps||d.apps.length===0){if(nav)nav.innerHTML='';return;} nav.innerHTML='
Custom Apps
'+ d.apps.map(a=>``).join(''); }catch{} } async function renderCustomApp(c,appId){ try{ const d=await api("/api/custom-apps/"+appId); const app=d.app;const records=d.records||[];const cols=app.columns||[]; c.innerHTML=`

${app.name}

${app.description||'Custom Application'} — ${records.length} records

${cols.map(c=>``).join('')}${records.length>0?records.map(r=>`${cols.map(c=>{ const val=r[c.name]||''; if(c.options){const colors={Open:'#3b82f6','In Progress':'#f59e0b',Done:'#f97316',Closed:'#6b7280',High:'#ef4444',Medium:'#f59e0b',Low:'#22c55e'};const clr=colors[val]||'var(--text2)';return ``;} return ``; }).join('')}`).join(''):``}
${c.name}
${val}${val}
No records yet
`; }catch(e){c.innerHTML=`

Error: ${e.message}

`;} } function showAddRecordModal(appId,cols){ const fields=typeof cols==='string'?JSON.parse(cols):cols; const modal=document.createElement('div'); modal.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:9999;display:flex;align-items:center;justify-content:center'; modal.innerHTML=`

Add Record

${fields.map(f=>f.options?`
`:`
`).join('')}
`; document.body.appendChild(modal); } async function submitNewRecord(appId,btn){ const modal=btn.closest('div[style*=fixed]');const inputs=modal.querySelectorAll('[data-field]');const data={}; inputs.forEach(i=>{data[i.dataset.field]=i.value;}); try{await api("/api/custom-apps/"+appId+"/records",{method:"POST",body:JSON.stringify(data)});modal.remove();toast("Record added","success");loadPage("custom-app-"+appId);}catch(e){toast(e.message,"error");} } async function deleteAppRecord(appId,rowid){ if(!confirm("Delete?"))return; try{await api("/api/custom-apps/"+appId+"/records/"+rowid,{method:"DELETE"});toast("Deleted","success");loadPage("custom-app-"+appId);}catch(e){toast(e.message,"error");} } // ═══ Page Renderers ═══ async function renderDashboard(c){ const info=await api("/api/system/info"); const stats=await api("/api/dashboard/stats").catch(()=>({})); const greeting=getTimeGreeting(); const workStatus=getWorkStatus(); c.innerHTML=`

${greeting}, ${currentUser?.username||'Admin'}

${info.company||'Enterprise Agency'} — ${new Date().toLocaleDateString(undefined,{weekday:'long',year:'numeric',month:'long',day:'numeric'})}

${workStatus.msg}
${stats.total_users||info.users_count||0}
Total Users
${stats.today_attendance||0}
Present Today
${stats.upcoming_meetings||0}
Upcoming Meetings
${stats.active_goals||0}
Active Goals
${stats.total_departments||0}
Departments
${stats.total_messages||0}
Chat Messages

Goals Progress

${(stats.goals_progress||[]).length===0?'

No active goals

': (stats.goals_progress||[]).map(g=>'
'+g.title+''+g.pct+'%
').join('')}

Pending Actions

${stats.pending_workflows||0}
Workflow Requests
${stats.pending_leaves||0}
Leave Requests

Recent Announcements

${(stats.recent_announcements||[]).length===0?'

No announcements

': (stats.recent_announcements||[]).map(a=>'
'+a.title+' '+a.priority+'
'+new Date(a.created_at).toLocaleDateString()+'
').join('')}

Recent Workflows

${(stats.recent_workflows||[]).length===0?'

No workflows

': (stats.recent_workflows||[]).map(w=>'
'+w.title+' '+w.status+'
'+w.workflow_type+' · '+new Date(w.created_at).toLocaleDateString()+'
').join('')}

System Info

${info.company} v${info.version} ${info.agents_count} Agents ${info.network_mode} ${info.license_status}
`; } async function renderAgents(c){ const d=await api("/api/agents"); const logs=await api("/api/agents/logs").catch(()=>({logs:[]})); const ragDocs=await api("/api/rag/documents").catch(()=>({documents:[],total:0})); c.innerHTML=`

Agents

${d.agents.length} agents deployed · ${ragDocs.total} RAG documents

${d.agents.map(a=>`
${a.name}
${a.role} · ${(a.skill_ids||[]).length} skills
${a.custom_instructions?`
Instructions: ${a.custom_instructions.substring(0,80)}...
`:''}
`).join("")}
`; } function showAgentTab(btn,id){btn.parentElement.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));btn.classList.add('active');['agents-list','agent-execute','rag-store','agent-logs'].forEach(t=>{const el=$('#'+t);if(el){el.classList.toggle('hidden',t!==id)}})} async function renderMessages(c){ const d=await api("/api/messages?channel=general"); c.innerHTML=`

Messages

Internal messaging

${d.messages.length===0?'

No messages yet

': d.messages.reverse().map(m=>`
${(m.sender_name||'?')[0].toUpperCase()}
${m.sender_name||'Unknown'} ${new Date(m.created_at).toLocaleString()} ${m.is_escalation?'ESCALATED':''}

${m.content}

`).join("")}
`; } async function renderEscalations(c){ const d=await api("/api/escalations"); c.innerHTML=`

Escalations

${d.escalations.length} escalations

${d.escalations.length===0?'

No escalations

': d.escalations.map(e=>`
${e.reason||'Escalated'}
Priority: ${e.priority} · ${new Date(e.created_at).toLocaleString()}
${e.status.toUpperCase()} ${e.status==='open'?``:''}
`).join("")}`; } async function renderNetwork(c){ const d=await api("/api/admin/network").catch(()=>({mode:"unknown",logs:[]})); const isOnline=d.mode==="online"; c.innerHTML=`

Network Control

Manage external connectivity

Current Mode: ${d.mode.toUpperCase()}

${isOnline?'External connections are ALLOWED':'All external connections are BLOCKED (Air-Gapped)'}

DANGER ZONE

Changing network mode affects all connectivity. Admin PIN required.

Network Logs

${d.logs.map(l=>`
${l.action}${new Date(l.created_at).toLocaleString()}
`).join("")||'

No logs

'}
`; } async function renderLicense(c){ const d=await api("/api/admin/license").catch(()=>({valid:false,status:"ERROR",license:{},renewals:[]})); c.innerHTML=`

License Management

Manage your agency license

License Status: ${d.status}

${d.license.expires_at?`

Expires: ${new Date(d.license.expires_at).toLocaleDateString()}

`:''} ${d.license.activated_at?`

Activated: ${new Date(d.license.activated_at).toLocaleDateString()}

`:''} ${d.license.hostname?`

Host: ${d.license.hostname}

`:''}

Offline License Renewal

Enter path to .triva-renewal file

Renewal History

${d.renewals.map(r=>`
Renewed ${new Date(r.applied_at).toLocaleString()} — New expiry: ${r.new_expiry}
`).join("")||'

No renewals

'}
`; } async function renderUpdates(c){ const d=await api("/api/admin/updates").catch(()=>({current_version:"?",history:[]})); c.innerHTML=`

Safe Updates

Current version: ${d.current_version}

Apply Update

Updates are applied safely with automatic backup. Your data is never affected.

Before applying: a full backup is created automatically. You can rollback at any time.

Upload Encrypted Package

Upload a .triva file from the platform. Auto-detects: system update, feature unlock, agent training, or knowledge injection.

Update History

${d.history.map(h=>`
v${h.version} — ${h.method} — ${new Date(h.applied_at).toLocaleString()}
`).join("")||'

No updates applied

'}
`; } async function uploadPackage(){ const data=$("#pkg-data")?.value?.trim();const filename=$("#pkg-filename")?.value||"unknown.triva";const el=$("#pkg-result"); if(!data){toast("Paste encrypted package data first","error");return} el.innerHTML=' Applying...'; try{const d=await api("/api/admin/upload-package",{method:"POST",body:JSON.stringify({encrypted_data:data,filename})}); el.innerHTML=` Applied: ${d.type} — ${JSON.stringify(d.details)}`; }catch(e){el.innerHTML=` ${e.message}`}} async function rollbackUpdate(version){ if(!confirm("Rollback to v"+version+"? Your data will be preserved."))return; try{const d=await api("/api/admin/updates/rollback",{method:"POST",body:JSON.stringify({target_version:version})}); toast(d.message||"Rolled back to v"+version,"success");loadPage("updates")}catch(e){toast(e.message,"error")} } async function renderConnections(c){ const d=await api("/api/admin/connections").catch(()=>({vector_stores:[],databases:[]})); c.innerHTML=`

Connections

Vector stores & database connections

${d.vector_stores.map(v=>`
${v.name}${v.provider}

${v.connection_string||'Local'}

`).join("")||'

No vector stores configured

'}
`; } // ═══ DEPARTMENTS — Organizational Structure ═══ async function renderDepartments(c){ const d=await api("/api/org/departments").catch(()=>({departments:[]})); const users=await api("/api/auth/users").catch(()=>({users:[]})); const info=await api("/api/system/info").catch(()=>({})); const isAdmin=currentUser&¤tUser.role==='admin'; const isManager=currentUser&&(currentUser.role==='admin'||currentUser.role==='manager'); const myDepts=d.departments.filter(dept=>dept.manager_id===currentUser?.id); c.innerHTML=`

Departments

${d.departments.length} active departments${info.industry?.sector?' · Sector: '+info.industry.sector:''}

${isAdmin?`

Create Department

`:''}
${d.departments.length===0?'

No departments configured. Contact admin to set up organizational structure.

': d.departments.map(dept=>{ const isMgr=dept.manager_id===currentUser?.id; return `
${dept.name} ${dept.name_ar?''+dept.name_ar+'':''}
${dept.manager_name||'No Manager'} · ${dept.employee_count||0} employees ${dept.agent_specialization?'· '+dept.agent_specialization:''}
${isMgr||isAdmin?'':''} ${isAdmin?'':''} ${isAdmin?'':''}
${dept.rag_enabled?' RAG Enabled':''}
`}).join('')}
`; } async function createDept(){ const name=document.getElementById("dept-name")?.value; const nameAr=document.getElementById("dept-name-ar")?.value; const mgr=document.getElementById("dept-manager")?.value; const spec=document.getElementById("dept-spec")?.value; const desc=document.getElementById("dept-desc")?.value; const skey=document.getElementById("dept-skey")?.value; if(!name){toast("Department name required","error");return} try{await api("/api/org/departments",{method:"POST",body:JSON.stringify({name,name_ar:nameAr,manager_id:mgr,agent_specialization:spec,description:desc,service_key:skey})}); toast("Department created");loadPage("departments")}catch(e){toast(e.message,"error")} } async function deleteDept(id){if(!confirm("Delete this department?"))return;try{await api("/api/org/departments/"+id,{method:"DELETE",body:JSON.stringify({})});toast("Department deleted");loadPage("departments")}catch(e){toast(e.message,"error")}} async function editDept(id){toast("Edit feature: use service key to modify","info")} async function viewDeptEmployees(deptId,deptName){ const panel=document.getElementById("dept-employees-panel"); panel.style.display="block"; const d=await api("/api/org/departments/"+deptId+"/employees").catch(()=>({department:{},employees:[]})); const users=await api("/api/auth/users").catch(()=>({users:[]})); const perfData=await api("/api/org/performance/"+deptId).catch(()=>({employees:[]})); const isAdmin=currentUser&¤tUser.role==='admin'; const isMgr=d.department?.manager_id===currentUser?.id; panel.innerHTML=`

${deptName} — Employees (${d.employees.length})

${isMgr||isAdmin?`
`:''}
${d.employees.length===0?'

No employees in this department yet.

': d.employees.map(emp=>{ const perf=perfData.employees?.find(p=>p.user_id===emp.user_id)||{}; const total=perf.tasks_total||0;const completed=perf.tasks_completed||0;const overdue=perf.tasks_overdue||0; const pct=total>0?Math.round(completed/total*100):0; const barColor=overdue>0?'var(--danger)':pct>=80?'var(--accent)':'var(--warn)'; return `
${(emp.username||'?')[0].toUpperCase()}
${emp.username||'Unknown'} ${emp.position||'employee'} ${overdue>0?' '+overdue+' overdue':''}
${emp.email||''}
${completed}/${total} tasks (${pct}%)
${isMgr||isAdmin?'':''}
`}).join('')}
`; panel.scrollIntoView({behavior:'smooth'}); } async function addEmployee(deptId){ const uid=document.getElementById("add-emp-user")?.value; const pos=document.getElementById("add-emp-pos")?.value; if(!uid){toast("Select a user","error");return} try{await api("/api/org/departments/"+deptId+"/employees",{method:"POST",body:JSON.stringify({user_id:uid,position:pos||"employee"})}); toast("Employee added");loadPage("departments")}catch(e){toast(e.message,"error")} } async function removeEmployee(deptId,empId){ if(!confirm("Remove this employee from the department?"))return; try{await api("/api/org/departments/"+deptId+"/employees/"+empId,{method:"DELETE"});toast("Employee removed");loadPage("departments")}catch(e){toast(e.message,"error")} } // ═══ ORG TREE — Visual Hierarchy ═══ async function renderOrgTree(c){ const d=await api("/api/org/tree").catch(()=>({tree:[]})); const info=await api("/api/system/info").catch(()=>({})); c.innerHTML=`

Organizational Chart

Company hierarchy and department structure

${info.company||'Organization'}
${d.tree.length} departments · ${d.tree.reduce((a,b)=>a+b.employee_count,0)} total employees ${info.industry?.sector?' · '+info.industry.sector:''}
${d.tree.length===0?'

No departments configured yet

': d.tree.map(dept=>`
${dept.name} ${dept.name_ar?'('+dept.name_ar+')':''} ${dept.employee_count} employees
${dept.manager?'
Manager: '+dept.manager+'
':''} ${dept.employees.length>0?'
'+dept.employees.map(e=>`
${e.username} ${e.position||e.role}
`).join('')+'
':''}
`).join('')}
`; } // ═══ NOTIFICATIONS ═══ async function renderNotifications(c){ const d=await api("/api/notifications").catch(()=>({notifications:[],unread_count:0})); c.innerHTML=`

Notifications

${d.unread_count} unread notifications

${d.unread_count>0?'':''}
${d.notifications.length===0?'

No notifications yet

': d.notifications.map(n=>{ const icons={info:'fa-info-circle',escalation:'fa-exclamation-triangle',cross_dept:'fa-arrows-left-right',warning:'fa-warning'}; const colors={info:'var(--info)',escalation:'var(--danger)',cross_dept:'var(--accent)',warning:'var(--warn)'}; return `
${n.title}
${n.message}
${new Date(n.created_at).toLocaleString()}
${!n.is_read?'':''}
`}).join('')}
`; } async function markRead(id){try{await api("/api/notifications/"+id+"/read",{method:"POST"});loadPage("notifications")}catch(e){toast(e.message,"error")}} async function markAllRead(){try{await api("/api/notifications/read-all",{method:"POST"});toast("All marked as read");loadPage("notifications")}catch(e){toast(e.message,"error")}} // ═══ SERVICE KEYS ═══ async function renderServiceKeys(c){ const d=await api("/api/admin/service-keys").catch(()=>({keys:[]})); c.innerHTML=`

Service Keys

Generate and manage one-time operation keys for department modifications

Generate New Key

${d.keys.length===0?'

No service keys generated yet

': d.keys.map(k=>`
${k.service_type} ${k.used?'Used':'Available'}
${k.description||'No description'}
Created: ${new Date(k.created_at).toLocaleString()}${k.expires_at?' · Expires: '+new Date(k.expires_at).toLocaleString():''}
${k.used_at?'
Used: '+new Date(k.used_at).toLocaleString()+'
':''}
`).join('')}
`; } async function generateServiceKey(){ const type=document.getElementById("sk-type")?.value; const desc=document.getElementById("sk-desc")?.value; const exp=parseInt(document.getElementById("sk-exp")?.value||"48"); try{ const d=await api("/api/admin/service-keys/generate",{method:"POST",body:JSON.stringify({service_type:type,description:desc,expires_hours:exp})}); document.getElementById("generated-key").style.display="block"; document.getElementById("generated-key-value").textContent=d.key; toast("Service key generated. Copy it now!"); }catch(e){toast(e.message,"error")} } // ═══ CROSS-DEPARTMENT MESSAGES ═══ async function renderCrossDept(c){ const d=await api("/api/org/cross-dept-messages").catch(()=>({messages:[]})); const depts=await api("/api/org/departments").catch(()=>({departments:[]})); c.innerHTML=`

Inter-Department Communication

Send and receive messages between departments

New Message

${d.messages.length===0?'

No inter-department messages yet

': d.messages.map(m=>{ const fromDept=depts.departments.find(d=>d.id===m.from_dept_id); const toDept=depts.departments.find(d=>d.id===m.to_dept_id); return `
${m.subject||'No Subject'} ${m.status}
${fromDept?.name||m.from_dept_id} → ${toDept?.name||m.to_dept_id}
${m.content}
${new Date(m.created_at).toLocaleString()}
`}).join('')}
`; } async function sendCrossDeptMsg(){ const to=document.getElementById("xdm-to")?.value; const subject=document.getElementById("xdm-subject")?.value; const content=document.getElementById("xdm-content")?.value; if(!to||!content){toast("Department and message required","error");return} try{await api("/api/org/cross-dept-messages",{method:"POST",body:JSON.stringify({to_dept_id:to,subject:subject,content:content})}); toast("Message sent");loadPage("cross-dept")}catch(e){toast(e.message,"error")} } async function renderUsers(c){ const d=await api("/api/auth/users").catch(()=>({users:[]})); const regs=await api("/api/admin/registrations").catch(()=>({registrations:[]})); const pending=regs.registrations.filter(r=>r.status==="pending"); c.innerHTML=`

Users

${d.users.length} registered users${pending.length>0?' · '+pending.length+' pending approvals':''}

${pending.length>0?`

Pending Registrations (${pending.length})

${pending.map(r=>`
${r.full_name[0].toUpperCase()}
${r.full_name} ${r.position||''} ${r.employee_id?'#'+r.employee_id:''}
${r.email}
`).join('')}
`:''}

Add User

Bulk Import (Excel/CSV)

Upload CSV with columns: name, email, role, employee_id

Click or drag CSV file here
${d.users.map(u=>{const rc={'admin':'yellow','manager':'green','operator':'blue','viewer':'blue'};return `
${u.username[0].toUpperCase()}
${u.username}${u.role.toUpperCase()}
${u.email||'No email'} · ${u.last_login?'Last: '+new Date(u.last_login).toLocaleString():'Never logged in'}
`}).join("")}
`; } async function approveReg(rid){ const role=document.getElementById("reg-role-"+rid)?.value||"viewer"; try{await api("/api/admin/registrations/"+rid+"/approve",{method:"POST",body:JSON.stringify({role})});toast("Approved!");loadPage("users")}catch(e){toast(e.message,"error")} } async function rejectReg(rid){ try{await api("/api/admin/registrations/"+rid+"/reject",{method:"POST"});toast("Rejected");loadPage("users")}catch(e){toast(e.message,"error")} } function handleExcelDrop(e){ e.preventDefault();e.currentTarget.style.borderColor='var(--border)'; const file=e.dataTransfer.files[0];if(file)processExcelFile(file); } function handleExcelFile(input){ const file=input.files[0];if(file)processExcelFile(file); } function processExcelFile(file){ const reader=new FileReader(); reader.onload=async function(e){ const text=e.target.result; try{ const d=await api("/api/admin/users/upload-excel",{method:"POST",body:JSON.stringify({csv_data:text})}); const res=document.getElementById("excel-result"); if(res)res.innerHTML=`

Created: ${d.created} users${d.errors?.length?' · Errors: '+d.errors.join(', '):''}

`; toast(d.created+" users created","success"); loadPage("users"); }catch(err){toast(err.message,"error")} }; reader.readAsText(file); } async function renderReports(c){ const d=await api("/api/admin/reports").catch(()=>({reports:[]})); c.innerHTML=`

Usage Reports

Generate, review, and approve reports before sending

${d.reports.length===0?'

No reports generated yet

': d.reports.map(r=>{const rd=JSON.parse(r.report_data||'{}');return `
Report ${r.status.toUpperCase()}
${new Date(r.created_at).toLocaleString()}
${r.status==='pending'?` `:''} ${r.status==='approved'?``:''}
Messages: ${rd.metrics?.messages||0} | Escalations: ${rd.metrics?.escalations||0} | Users: ${rd.metrics?.users||0} | Agent Tasks: ${rd.metrics?.agent_tasks||0}
`}).join("")}`; } async function renderLearning(c){ const d=await api("/api/agents/learning-stats").catch(()=>({stats:[],recent:[]})); const agents=await api("/api/agents").catch(()=>({agents:[]})); c.innerHTML=`

Agent Learning

Continuous improvement through feedback

${d.stats.map(s=>`
${(s.avg_score||0).toFixed(1)}
${s.agent_name} (${s.feedback_count} reviews)
`).join("")||'
0
No feedback yet
'}

Submit Feedback

Recent Feedback

${d.recent.length===0?'

No feedback entries yet

': d.recent.map(r=>`
${r.agent_name} ${r.quality_score}/5
Task: ${(r.task||'').substring(0,80)}
Feedback: ${(r.feedback||'').substring(0,120)}
${new Date(r.learned_at).toLocaleString()}
`).join("")}
`; } async function renderTasks(c){ const d=await api("/api/tasks").catch(()=>({tasks:[]})); const users=await api("/api/auth/users").catch(()=>({users:[]})); const userRole=currentUser?.role||'viewer'; const canCreate=userRole!=='viewer'; c.innerHTML=`

Tasks & Hand-over

Manage tasks and hand them over between team members

${canCreate?'':''}
${d.tasks.length===0?'

No tasks yet

': d.tasks.map(t=>{ const chain=JSON.parse(t.handover_chain||'[]'); const statusColors={open:'blue',in_progress:'yellow',completed:'green',cancelled:'red'}; return `
${t.title} ${t.project?''+t.project+'':''} ${t.status.toUpperCase()} ${t.priority}
${(t.description||'').substring(0,150)}
Assigned: ${t.assignee_name||'Unassigned'} | Created: ${new Date(t.created_at).toLocaleDateString()}
${chain.length>1?'
'+chain.length+' hand-overs
':''}
${t.status!=='completed'?` `:'Done'}
`}).join("")}
`; } async function renderContracts(c){ const d=await api("/api/contracts").catch(()=>({contracts:[]})); const users=await api("/api/auth/users").catch(()=>({users:[]})); const userRole=currentUser?.role||'viewer'; const canCreate=['admin','manager'].includes(userRole); c.innerHTML=`

Contracts & Invoices

Manage contracts, invoices, and financial documents

${canCreate?'':''}
${d.contracts.length===0?'

No contracts yet

': d.contracts.map(ct=>{ const statusColors={draft:'blue',active:'green',completed:'green',cancelled:'red',overdue:'red',pending:'yellow'}; return `
${ct.title} ${ct.contract_type} ${ct.status.toUpperCase()}
${ct.amount} ${ct.currency} — ${ct.client_name||'No client'}
Assigned: ${ct.assignee_name||'Unassigned'} | ${new Date(ct.created_at).toLocaleDateString()}${ct.due_date?' | Due: '+new Date(ct.due_date).toLocaleDateString():''}
${canCreate?``:''}
`}).join("")}
`; } // ═══ DataHub — Airtable-like Interface ═══ async function renderDataHub(c){ const d=await api("/api/admin/flexpool/modules").catch(()=>({modules:[]})); const depts=await api("/api/admin/departments").catch(()=>({departments:[]})); const modules=d.modules||[]; const departments=depts.departments||[]; const totalRecords=modules.reduce((s,m)=>s+(m.record_count||0),0); c.innerHTML=`

DataHub

Your data, your way — build tables, link records, automate workflows

${modules.length} tables
${totalRecords} records
${departments.length>0?`
${departments.map(d=>``).join('')}
`:''}
${modules.filter(m=>!window._fpDeptFilter||m.department_id===window._fpDeptFilter||!m.department_id).length===0?`

No tables yet

Create a table, import from CSV, or ask the AI assistant

`:''} ${modules.filter(m=>!window._fpDeptFilter||m.department_id===window._fpDeptFilter||!m.department_id).map(m=>`
${m.name}
${m.description||''}${m.department_name&&m.department_name!=='Global'?' · '+m.department_name:''}
${m.record_count||0} records ${(m.columns||[]).length} cols ${(m.relations||[]).length>0?`${m.relations.length}`:''} ${m.automations_count>0?`${m.automations_count}`:''}
`).join('')}
Tip: Ask the AI assistant to build tables for you — just describe what you need
`; } async function renderDataHubTable(c,moduleId){ const d=await api("/api/flexpool/"+moduleId+"/data?limit=500").catch(()=>null); if(!d||!d.module){c.innerHTML='

Table not found

';return;} const mod=d.module;const modDict=typeof mod==='object'?mod:{}; const cols=JSON.parse(modDict.columns||'[]'); const records=d.records||[]; const viewMode=localStorage.getItem('dh_view_'+moduleId)||'grid'; const dateCols=cols.filter(c=>c.type==='DATE'); const selectCols=cols.filter(c=>c.type==='SELECT'); const textCols=cols.filter(c=>c.type==='TEXT'); const firstTextCol=textCols.length>0?textCols[0].name.toLowerCase().replace(/ /g,'_'):(cols.length>0?cols[0].name.toLowerCase().replace(/ /g,'_'):'id'); c.innerHTML=`

${modDict.name||'Table'}

${d.total||0} records
${['grid','kanban','gallery','calendar','form'].map(v=>{ const icons={grid:'fa-table-cells',kanban:'fa-columns',gallery:'fa-grip',calendar:'fa-calendar',form:'fa-wpforms'}; const labels={grid:'Grid',kanban:'Kanban',gallery:'Gallery',calendar:'Cal',form:'Form'}; return ''; }).join('')}
`; // Store records in global cache for expand access window._dhRecordCache={}; records.forEach(r=>{window._dhRecordCache[r.id]=r;}); // Render view const vc=document.getElementById('dh-view-container'); if(viewMode==='kanban'){ const gc=selectCols[0];if(!gc){vc.innerHTML='

Kanban needs a Select column

'; }else{const gcn=gc.name.toLowerCase().replace(/ /g,'_');const groups={};(gc.options||['No Value']).forEach(o=>{groups[o]=[];});groups['No Value']=groups['No Value']||[]; records.forEach(r=>{const v=r[gcn]||'No Value';if(!groups[v])groups[v]=[];groups[v].push(r);}); vc.innerHTML='
'+Object.entries(groups).map(([label,items])=>'
'+label+''+items.length+'
'+items.map(r=>'
'+((r[firstTextCol]||r.id)+'')+'
'+(r.created_at?new Date(r.created_at).toLocaleDateString():'')+'
').join('')+(items.length===0?'
Empty
':'')+'
').join('')+'
'; } }else if(viewMode==='gallery'){ vc.innerHTML='
'+ (records.length===0?'
No records
':'')+ records.map(r=>'
'+(r[firstTextCol]||r.id)+'
'+cols.slice(1,4).map(co=>{const v=r[co.name.toLowerCase().replace(/ /g,'_')]||'';return v?'
'+co.name+': '+v+'
':'';}).join('')+'
'+(r.created_at?new Date(r.created_at).toLocaleDateString():'')+'
').join('')+'
'; }else if(viewMode==='calendar'){ if(dateCols.length===0){vc.innerHTML='

Add a Date column to enable Calendar view

'; }else{const dcn=dateCols[0].name.toLowerCase().replace(/ /g,'_');const now=new Date();const yr=now.getFullYear();const mo=now.getMonth();const fd=new Date(yr,mo,1).getDay();const dim=new Date(yr,mo+1,0).getDate();const mn=now.toLocaleString('default',{month:'long',year:'numeric'}); const byDay={};records.forEach(r=>{const v=r[dcn];if(!v)return;const dd=new Date(v);if(dd.getMonth()===mo&&dd.getFullYear()===yr){const day=dd.getDate();if(!byDay[day])byDay[day]=[];byDay[day].push(r);}}); let cells='';for(let i=0;i
'; for(let day=1;day<=dim;day++){const isT=day===now.getDate();const dr=byDay[day]||[]; cells+='
'+day+'
'+dr.slice(0,3).map(r=>'
'+(r[firstTextCol]||r.id)+'
').join('')+(dr.length>3?'
+'+(dr.length-3)+' more
':'')+'
';} vc.innerHTML='
'+mn+'
'+'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(',').map(d=>'
'+d+'
').join('')+'
'+cells+'
';} }else if(viewMode==='form'){ vc.innerHTML='

Data Collection Form

Submit data directly into this table

'+cols.map(col=>{const cn=col.name.toLowerCase().replace(/ /g,'_'); if(col.type==='SELECT'&&col.options)return '
'; if(col.type==='BOOLEAN')return '
'; const it=col.type==='INTEGER'||col.type==='REAL'?'number':col.type==='DATE'?'date':col.type==='EMAIL'?'email':col.type==='URL'?'url':'text'; return '
';}).join('')+'
'; }else{ // Default: Grid view vc.innerHTML='
'+cols.map(col=>'').join('')+''+ (records.length===0?'':'')+ records.map(r=>''+cols.map(col=>{const cn=col.name.toLowerCase().replace(/ /g,'_');let val=r[cn]||'';const relD=r['_rel_'+cn+'_display']; if(relD)return ''; if(col.type==='BOOLEAN')return ''; if(col.type==='SELECT'&&val)return ''; return '';}).join('')+ ''+ '').join('')+'
'+(col.type==='RELATION'?'':'')+col.name+' '+col.type+'Created
No records
'+relD+''+(val==='true'?'':'')+''+val+''+val+''+(r.created_at?new Date(r.created_at).toLocaleDateString():'')+'
'; } // Load relation dropdowns document.querySelectorAll('.fp-rel-field').forEach(async sel=>{ const tid=sel.dataset.target;if(!tid)return; try{const td=await api('/api/flexpool/'+tid+'/data?limit=200');const tmod=td.module;const tc=JSON.parse((typeof tmod==='object'?tmod:{}).columns||'[]'); const dc=tc.length>0?tc[0].name.toLowerCase().replace(/ /g,'_'):'id'; (td.records||[]).forEach(r=>{const o=document.createElement('option');o.value=r.id;o.textContent=r[dc]||r.id;sel.appendChild(o);});}catch(e){} }); } function showDataHubCreate(){ const p=document.getElementById("dh-create-panel");if(p){p.style.display="block";_fpColCount=0;const cols=document.getElementById("fp-columns");if(cols)cols.innerHTML="";addFlexColumn();p.scrollIntoView({behavior:"smooth"});} const ip=document.getElementById("dh-import-panel");if(ip)ip.style.display="none"; } function showDataHubImport(){ const p=document.getElementById("dh-import-panel");if(p){p.style.display="block";p.scrollIntoView({behavior:"smooth"});} const cp=document.getElementById("dh-create-panel");if(cp)cp.style.display="none"; } async function submitDataHubImport(){ const name=document.getElementById("dh-imp-name")?.value; const csv=document.getElementById("dh-imp-csv")?.value; const dept=document.getElementById("dh-imp-dept")?.value||""; if(!name||!csv){toast("Name and CSV data required","error");return;} try{ const r=await api("/api/datahub/import-csv",{method:"POST",body:JSON.stringify({name,csv_data:csv,department_id:dept})}); toast("Imported "+r.imported_rows+" rows into '"+name+"'"); loadPage("datahub"); }catch(e){toast(e.message||"Import failed","error");} } function handleFileImport(input){ const file=input.files[0];if(!file)return; const reader=new FileReader(); reader.onload=function(e){ document.getElementById("dh-imp-csv").value=e.target.result; if(!document.getElementById("dh-imp-name").value){ document.getElementById("dh-imp-name").value=file.name.replace(/\.\w+$/,'').replace(/[_-]/g,' '); } toast("File loaded: "+file.name); }; reader.readAsText(file); } function switchDHView(mid,view){localStorage.setItem('dh_view_'+mid,view);renderDataHubTable(document.getElementById('page-content'),mid);} function dhInlineEdit(cell,mid,rid,colName){ const oldVal=cell.innerText; const inp=document.createElement('input');inp.className='input';inp.value=oldVal; inp.style.cssText='font-size:12px;padding:2px 6px;width:100%;margin:-2px 0'; inp.onblur=async function(){try{await api('/api/flexpool/'+mid+'/data/'+rid,{method:'PUT',body:JSON.stringify({[colName]:inp.value})});cell.innerText=inp.value;toast('Updated');}catch(e){cell.innerText=oldVal;toast(e.message,'error');}}; inp.onkeydown=function(e){if(e.key==='Enter')inp.blur();if(e.key==='Escape'){cell.innerText=oldVal;}}; cell.innerHTML='';cell.appendChild(inp);inp.focus(); } function dhExpandRow(mid,rid,dataStr){ const data=dataStr?JSON.parse(dataStr):(window._dhRecordCache&&window._dhRecordCache[rid])||{}; if(!data||Object.keys(data).length===0){toast('Record not found','error');return;} try{ const overlay=document.createElement('div');overlay.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:200;display:flex;align-items:center;justify-content:center;padding:20px'; overlay.onclick=e=>{if(e.target===overlay)overlay.remove();}; const keys=Object.keys(data).filter(k=>!k.startsWith('_rel_')&&k!=='id'&&k!=='created_by'&&k!=='created_at'&&k!=='updated_at'); overlay.innerHTML='

Record Details

'+keys.map(k=>{const v=data[k];const relD=data['_rel_'+k+'_display'];return '
'+k.replace(/_/g,' ')+'
'+(relD?''+relD:(v||'—'))+'
';}).join('')+'
'; document.body.appendChild(overlay);}catch(e){console.error(e);} } async function exportDHToCSV(mid){ try{const d=await api('/api/flexpool/'+mid+'/data?limit=10000');const mod=d.module;const cols=JSON.parse((typeof mod==='object'?mod:{}).columns||'[]');const records=d.records||[]; const headers=cols.map(c=>c.name);let csv=headers.join(',')+'\n'; records.forEach(r=>{csv+=cols.map(c=>{const v=(r[c.name.toLowerCase().replace(/ /g,'_')]||'').toString();return '"'+v.replace(/"/g,'""')+'"';}).join(',')+'\n';}); const blob=new Blob([csv],{type:'text/csv'});const url=URL.createObjectURL(blob);const a=document.createElement('a');a.href=url;a.download=(mod.name||'data')+'.csv';a.click();URL.revokeObjectURL(url); toast('Exported '+records.length+' records');}catch(e){toast(e.message,'error');} } async function submitDHForm(mid){ const fields=document.querySelectorAll('.dh-form-field');const data={}; fields.forEach(f=>{const col=f.dataset.col;if(f.type==='checkbox')data[col]=f.checked?'true':'false';else if(f.value)data[col]=f.value;}); try{await api('/api/flexpool/'+mid+'/data',{method:'POST',body:JSON.stringify(data)}); toast('Record submitted!');fields.forEach(f=>{if(f.type==='checkbox')f.checked=false;else f.value='';});}catch(e){toast(e.message,'error');} } // ═══ FlexPool Renderers ═══ async function renderFlexPool(c){ const d=await api("/api/admin/flexpool/modules").catch(()=>({modules:[]})); const depts=await api("/api/admin/departments").catch(()=>({departments:[]})); const modules=d.modules||[]; const departments=depts.departments||[]; const totalRecords=modules.reduce((s,m)=>s+(m.record_count||0),0); const totalRels=modules.reduce((s,m)=>s+(m.relations||[]).length,0); const totalAutos=modules.reduce((s,m)=>s+(m.automations_count||0),0); c.innerHTML=`

FlexPool

Relational tables with automation — build anything, connect everything

${modules.length}
Tables
${totalRecords}
Records
${totalRels}
Relations
${totalAutos}
Automations
${departments.length>0?`
${departments.map(d=>``).join('')}
`:''}
${modules.filter(m=>!window._fpDeptFilter||m.department_id===window._fpDeptFilter||!m.department_id).length===0?`

No tables yet

Create a table or ask the AI assistant to build one

`:''} ${modules.filter(m=>!window._fpDeptFilter||m.department_id===window._fpDeptFilter||!m.department_id).map(m=>`
${m.name}
${m.description||''}${m.department_name?' · '+m.department_name:''}
${m.record_count||0} records ${(m.columns||[]).length} columns ${(m.relations||[]).length>0?`${m.relations.length} links`:''} ${m.automations_count>0?`${m.automations_count} auto`:''}
`).join('')}
`; } async function renderFlexPoolModule(c,moduleId){ const d=await api("/api/flexpool/"+moduleId+"/data").catch(()=>null); if(!d||!d.module){c.innerHTML=`

Module not found

`;return;} const mod=d.module; const cols=JSON.parse(mod.columns||'[]'); const records=d.records||[]; c.innerHTML=`

${mod.name}

${mod.description||''} — ${d.total||0} records

${cols.map(col=>``).join('')} ${records.length===0?``:''} ${records.map(r=>` ${cols.map(col=>{ const val=r[col.name.toLowerCase().replace(/ /g,'_')]||''; return ``; }).join('')} `).join('')}
${col.name}Created Actions
No records yet
${val}${r.created_at?new Date(r.created_at).toLocaleDateString():''}
`; } async function renderAIProviders(c){ const d=await api("/api/admin/ai-providers").catch(()=>({providers:[]})); const KNOWN_PROVIDERS=[ {type:"openai",name:"OpenAI",url:"https://api.openai.com/v1",models:["gpt-4o","gpt-4o-mini","gpt-4-turbo","gpt-3.5-turbo","o1","o1-mini"],docs:"https://platform.openai.com/api-keys",icon:"fa-brain",color:"#f97316",logo:"https://cdn.openai.com/API/logo-assets/openai-logomark.png"}, {type:"anthropic",name:"Anthropic",url:"https://api.anthropic.com/v1",models:["claude-sonnet-4-20250514","claude-3-5-sonnet-20241022","claude-3-5-haiku-20241022","claude-3-opus-20240229"],docs:"https://console.anthropic.com/settings/keys",icon:"fa-message",color:"#d97706",logo:"https://www.anthropic.com/images/icons/safari-pinned-tab.svg"}, {type:"google",name:"Google Gemini",url:"https://generativelanguage.googleapis.com/v1beta",models:["gemini-2.0-flash","gemini-1.5-pro","gemini-1.5-flash"],docs:"https://aistudio.google.com/apikey",icon:"fa-gem",color:"#3b82f6",logo:"https://www.gstatic.com/lamda/images/gemini_sparkle_v002_d4735304ff6292a690b6.svg"}, {type:"openrouter",name:"OpenRouter",url:"https://openrouter.ai/api/v1",models:["openai/gpt-4o","anthropic/claude-3.5-sonnet","google/gemini-2.0-flash-exp","meta-llama/llama-3.1-405b-instruct"],docs:"https://openrouter.ai/keys",icon:"fa-route",color:"#8b5cf6",logo:""}, {type:"deepseek",name:"DeepSeek",url:"https://api.deepseek.com/v1",models:["deepseek-chat","deepseek-reasoner","deepseek-coder"],docs:"https://platform.deepseek.com/api_keys",icon:"fa-magnifying-glass",color:"#06b6d4",logo:""}, {type:"mistral",name:"Mistral AI",url:"https://api.mistral.ai/v1",models:["mistral-large-latest","mistral-medium-latest","mistral-small-latest","codestral-latest"],docs:"https://console.mistral.ai/api-keys/",icon:"fa-wind",color:"#f97316",logo:""}, {type:"groq",name:"Groq",url:"https://api.groq.com/openai/v1",models:["llama-3.3-70b-versatile","llama-3.1-8b-instant","mixtral-8x7b-32768"],docs:"https://console.groq.com/keys",icon:"fa-bolt",color:"#ef4444",logo:""}, {type:"cohere",name:"Cohere",url:"https://api.cohere.com/v2",models:["command-r-plus","command-r","command-light"],docs:"https://dashboard.cohere.com/api-keys",icon:"fa-circle-nodes",color:"#14b8a6",logo:""}, {type:"together",name:"Together AI",url:"https://api.together.xyz/v1",models:["meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo","meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo"],docs:"https://api.together.ai/settings/api-keys",icon:"fa-people-group",color:"#a855f7",logo:""}, {type:"fireworks",name:"Fireworks AI",url:"https://api.fireworks.ai/inference/v1",models:["accounts/fireworks/models/llama-v3p1-405b-instruct"],docs:"https://fireworks.ai/api-keys",icon:"fa-fire",color:"#f43f5e",logo:""}, {type:"perplexity",name:"Perplexity",url:"https://api.perplexity.ai",models:["sonar-pro","sonar"],docs:"https://perplexity.ai/settings/api",icon:"fa-search",color:"#22d3ee",logo:""}, {type:"azure",name:"Azure OpenAI",url:"",models:[],docs:"https://portal.azure.com",icon:"fa-cloud",color:"#0284c7",logo:""}, {type:"local",name:"Local (Ollama/vLLM)",url:"http://localhost:11434/v1",models:["llama3","mistral","codellama"],docs:"https://ollama.com",icon:"fa-server",color:"#64748b",logo:""}, {type:"custom",name:"Custom Provider",url:"",models:[],docs:"",icon:"fa-plug",color:"#94a3b8",logo:""}, ]; const existing=d.providers.map(p=>p.provider_type); const available=KNOWN_PROVIDERS.filter(p=>!existing.includes(p.type)); const provKnownMap={};KNOWN_PROVIDERS.forEach(p=>provKnownMap[p.type]=p); // View mode from localStorage let viewMode=localStorage.getItem('ai_prov_view')||'grid'; function setView(m){viewMode=m;localStorage.setItem('ai_prov_view',m);renderAIProviders(c);} // Render provider card for grid view function gridCard(p){ const k=provKnownMap[p.provider_type]||{icon:'fa-microchip',color:'var(--accent)',logo:''}; const models=JSON.parse(p.models||'[]'); const logoHtml=k.logo?`
`:``; return `
${logoHtml}
${p.name}
${p.provider_type}
Models (${models.length})
${models.map(m=>`${m}`).join('')}${models.length===0?'None':''}
${p.api_key?p.api_key.slice(0,8)+'...':'Not set'} ${p.base_url?'
'+p.base_url:''}
`; } // Render provider row for list view function listRow(p){ const k=provKnownMap[p.provider_type]||{icon:'fa-microchip',color:'var(--accent)'}; const models=JSON.parse(p.models||'[]'); return `
${p.name}
${p.provider_type}
${p.is_active?'Active':'Disabled'} ${models.join(', ')||'—'} ${p.api_key?p.api_key.slice(0,12)+'...':'—'}
`; } // Render standard compact card function stdCard(p){ const k=provKnownMap[p.provider_type]||{icon:'fa-microchip',color:'var(--accent)'}; const models=JSON.parse(p.models||'[]'); return `
${p.name} ${p.provider_type} ${p.is_active?'Active':'Disabled'}
Models: ${models.join(', ')||'None'}
`; } // Summary stats const activeCount=d.providers.filter(p=>p.is_active).length; const totalModels=d.providers.reduce((s,p)=>{try{return s+JSON.parse(p.models||'[]').length}catch(e){return s}},0); c.innerHTML=`

AI Providers

Manage AI model providers, API keys, and configurations

${d.providers.length}
Providers
${activeCount}
Active
${totalModels}
Models
Add Provider
${available.map(p=>``).join("")} ${available.length===0?'All known providers configured':''}
${d.providers.length===0?'

No AI providers configured

Click a provider above to get started

':''} ${viewMode==='grid'?`
${d.providers.map(p=>gridCard(p)).join('')}
`:''} ${viewMode==='list'?`
${d.providers.map(p=>listRow(p)).join('')}
ProviderStatusModelsAPI KeyTestActions
`:''} ${viewMode==='standard'?d.providers.map(p=>stdCard(p)).join(''):''}
`; // Re-apply view toggle behavior document.querySelectorAll('#ai-prov-container').forEach(el=>{ const btns=c.querySelectorAll('[title$="View"]'); btns.forEach(btn=>{btn.addEventListener('click',()=>{renderAIProviders(c);});}); }); } let _paSessionId=null; let _paMessages=[]; async function renderPlatformAssistant(c){ if(!_paSessionId)_paSessionId='pa-'+Date.now().toString(36); c.innerHTML=`

Platform Assistant

AI-powered development assistant — ask about services, configuration, or request new features

${_paMessages.length===0?'

I am the Platform Development Assistant.

Ask me to:
- Configure AI providers
- Setup channels (Telegram, WhatsApp)
- Estimate pricing for new services
- Optimize agent performance
- Troubleshoot issues

': _paMessages.map(m=>`
${m.content}
${m.at?new Date(m.at).toLocaleTimeString():''}
`).join("")}
`; const msgDiv=$("#pa-messages");if(msgDiv)msgDiv.scrollTop=msgDiv.scrollHeight; } async function sendPAMessage(){ const input=$("#pa-input");if(!input||!input.value.trim())return; const msg=input.value.trim();input.value=""; const now=new Date().toISOString(); _paMessages.push({role:"user",content:msg,at:now}); const msgDiv=$("#pa-messages"); msgDiv.innerHTML+=`
${msg}
`; msgDiv.innerHTML+=`
Thinking...
`; msgDiv.scrollTop=msgDiv.scrollHeight; try{ const d=await api("/api/platform-assistant/chat",{method:"POST",body:JSON.stringify({message:msg,session_id:_paSessionId})}); const typing=$("#pa-typing");if(typing)typing.remove(); _paMessages.push({role:"assistant",content:d.response,at:new Date().toISOString()}); msgDiv.innerHTML+=`
${d.response}
`; }catch(e){ const typing=$("#pa-typing");if(typing)typing.remove(); msgDiv.innerHTML+=`
Error: ${e.message}
`; } msgDiv.scrollTop=msgDiv.scrollHeight; } async function renderSettings(c){ const d=await api("/api/admin/settings").catch(()=>({settings:{}})); const s=d.settings||{}; c.innerHTML=`

Settings

System configuration and security

Security

General

System Info

Network Mode: ${s.network_mode||'unknown'}

Brand Locked: Check via Dashboard

`; } async function renderCustomerAgents(c){ const d=await api("/api/admin/customer-agents").catch(()=>({agents:[]})); const users=await api("/api/auth/users").catch(()=>({users:[]})); const convs=await api("/api/admin/conversations").catch(()=>({conversations:[]})); c.innerHTML=`

Customer Service Agents

Configure AI agents that serve your customers via chat, Telegram, and WhatsApp

${d.agents.length===0?'

No customer agents configured. Create one to start serving customers.

': d.agents.map(a=>`
${a.name} ${a.agent_type} ${a.is_active?'Active':'Disabled'}
Welcome: "${a.welcome_message}"
${a.escalate_to?'
Escalates to employee
':''}
`).join("")}
${convs.conversations.length>0?`

Recent Conversations

${convs.conversations.slice(0,10).map(cv=>`
${cv.customer_name} ${cv.channel} ${cv.status} ${cv.agent_name?''+cv.agent_name+'':''}
${cv.last_message||''}
${cv.message_count} messages | ${new Date(cv.updated_at).toLocaleString()}
`).join("")}
`:''}`; } async function renderChannels(c){ const d=await api("/api/admin/channels").catch(()=>({channels:[]})); const widgetUrl=window.location.origin+"/api/widget/embed.js"; c.innerHTML=`

Communication Channels

Configure how customers reach your agents

${d.channels.map(ch=>{ const icons={widget:'fas fa-comment-dots',telegram:'fab fa-telegram',whatsapp:'fab fa-whatsapp'}; const colors={widget:'var(--accent)',telegram:'#229ED9',whatsapp:'#25D366'}; const cfg=ch.config||{}; return `
${ch.channel_type.charAt(0).toUpperCase()+ch.channel_type.slice(1)} ${ch.is_active?'Active':'Disabled'}
${ch.channel_type==='widget'?`
Widget Configuration
Copy and paste this script before </body> on your website:
<script> (function(){ var w=document.createElement('script'); w.src='${widgetUrl}'; w.async=true; w.dataset.position='${cfg.position||'bottom-right'}'; w.dataset.theme='${cfg.theme||'dark'}'; w.dataset.color='${cfg.color||'#0ea5e9'}'; w.dataset.title='${cfg.widget_title||'Chat'}'; document.head.appendChild(w); })(); </script>
`:''} ${ch.channel_type==='telegram'?`
${window.location.origin}/api/webhook/telegram
`:''} ${ch.channel_type==='whatsapp'?`
${window.location.origin}/api/webhook/whatsapp
Setup Guide:
1. Go to Meta Developer Portal
2. Create an app > Add WhatsApp product
3. Get Phone Number ID and Permanent Access Token
4. Set webhook URL to the address above
5. Subscribe to messages webhook field
`:''}
`}).join("")}
`; } async function renderFeatureLicenses(c){ const d=await api("/api/admin/feature-licenses").catch(()=>({licenses:[]})); const labels={customer_agents:'Customer Service Agents',telegram:'Telegram Integration',whatsapp:'WhatsApp Integration',widget:'Website Chat Widget',rag_vector_store:'RAG / Vector Store',external_db:'External Database',usage_reports:'Usage Reports',continuous_learning:'Continuous Learning',tasks_handover:'Tasks & Hand-over',contracts:'Contracts & Invoices'}; const icons={customer_agents:'fa-headset',telegram:'fa-paper-plane',whatsapp:'fa-comments',widget:'fa-comment-dots',rag_vector_store:'fa-database',external_db:'fa-server',usage_reports:'fa-chart-bar',continuous_learning:'fa-brain',tasks_handover:'fa-list-check',contracts:'fa-file-invoice'}; c.innerHTML=`

Feature Licenses

Manage which features are active. Locked features require an unlock code from the developer.

${d.licenses.map(fl=>`
${labels[fl.feature_key]||fl.feature_key} ${fl.is_unlocked?' Unlocked':' Locked'} ${fl.unlocked_at?'
Unlocked: '+new Date(fl.unlocked_at).toLocaleDateString()+'
':''}
${!fl.is_unlocked?` ` :``}
`).join("")}
`; } // ═══ PROFESSIONAL CHAT — Advanced Orchestrator ═══ let chatSessions = []; function getTimeGreeting(){ const h=new Date().getHours(); if(h>=5&&h<12) return "Good Morning"; if(h>=12&&h<17) return "Good Afternoon"; if(h>=17&&h<21) return "Good Evening"; return "Hello"; } function getWorkStatus(){ const now=new Date(); const h=now.getHours(); const day=now.getDay(); if(day===0||day===6) return {active:false, msg:"Weekend — recharge and relax"}; if(h>=8&&h<17) return {active:true, msg:"Working hours"}; if(h>=17&&h<20) return {active:false, msg:"After hours — take a break"}; return {active:false, msg:"Outside working hours"}; } function renderChat(c){ // Show main sidebar — unified navigation const sidebar=document.getElementById("app-sidebar"); if(sidebar){sidebar.style.display="flex";} const userName=(currentUser?.full_name||currentUser?.username||"").split(" ")[0]||"User"; const greeting=getTimeGreeting(); const workStatus=getWorkStatus(); const statusIcon=workStatus.active?'':''; const hasHistory=chatHistory.length>0; c.innerHTML=`
${currentSessionId?'':'New Chat'}
${!hasHistory?`
${statusIcon} ${workStatus.msg}
${greeting}, ${userName}
I am the intelligent orchestrator. I manage agents and coordinate tasks between them.
How can I help you today?
Department Report
Active Tasks
Agent Performance
Draft Memo
`:''}
`; // Render existing messages chatHistory.forEach(m=>{ const msgs=document.getElementById("chat-messages"); if(msgs)msgs.innerHTML+=renderChatBubble(m.role,m.content,m.agent,m.time); }); const msgs=document.getElementById("chat-messages"); if(msgs)msgs.scrollTop=msgs.scrollHeight; loadRagDocs(); loadChatSessions(); } async function loadChatSessions(){ try{ const d=await api("/api/chat/sessions"); chatSessions=d.sessions||[]; renderSessionsList(); }catch{chatSessions=[];} } function renderSessionsList(filter=""){ const list=document.getElementById("chat-sessions-list"); const sidebarList=document.getElementById("sidebar-chat-list"); const filtered=filter?chatSessions.filter(s=>(s.title||"").toLowerCase().includes(filter.toLowerCase())):chatSessions; // Build session HTML let fullHtml=''; let sidebarHtml=''; if(filtered.length===0){ fullHtml='
No previous chats
Start a new conversation
'; sidebarHtml='
No chats
'; } else { // Group by date const groups={}; const now=new Date(); const today=now.toDateString(); const yesterday=new Date(now-86400000).toDateString(); filtered.forEach(s=>{ const d=new Date(s.updated_at||s.created_at); let label; if(d.toDateString()===today)label="Today"; else if(d.toDateString()===yesterday)label="Yesterday"; else{const diff=Math.floor((now-d)/86400000);label=diff<7?"This Week":diff<30?"This Month":"Older";} if(!groups[label])groups[label]=[]; groups[label].push(s); }); for(const[label,sessions] of Object.entries(groups)){ fullHtml+=`
${label}
`; sessions.forEach(s=>{ const isActive=currentSessionId===s.id; const title=(s.title||"Chat").substring(0,35); const time=new Date(s.updated_at||s.created_at).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'}); fullHtml+=`
${title}
${s.message_count||0} messages · ${time}
`; // Sidebar compact version sidebarHtml+=`
${title}
`; }); } } if(list)list.innerHTML=fullHtml; if(sidebarList)sidebarList.innerHTML=sidebarHtml; // Show sidebar chat sessions panel const scp=document.getElementById("sidebar-chat-sessions"); if(scp&¤tPage==="chat")scp.style.display="block"; } function filterSessions(v){renderSessionsList(v)} function toggleSidebarGroup(gid){ const el=document.getElementById(gid); const chevron=document.getElementById(gid+"-chevron"); if(!el)return; const isHidden=el.style.display==="none"; el.style.display=isHidden?"block":"none"; if(chevron){chevron.className="fas fa-chevron-"+(isHidden?"down":"right");} } async function loadSession(sid){ try{ const d=await api("/api/chat/sessions/"+sid); currentSessionId=sid; chatHistory=(d.messages||[]).map(m=>({role:m.role,content:m.content,agent:m.agent,time:m.at})); const titleEl=document.getElementById("chat-session-title"); if(titleEl)titleEl.textContent=d.title||"Chat"; // Re-render messages const msgs=document.getElementById("chat-messages"); if(msgs){ msgs.innerHTML=''; chatHistory.forEach(m=>msgs.innerHTML+=renderChatBubble(m.role,m.content,m.agent,m.time)); msgs.scrollTop=msgs.scrollHeight; } // Update active state document.querySelectorAll('.session-item').forEach(el=>el.classList.remove('active')); const active=document.querySelector(`.session-item[data-sid="${sid}"]`); if(active)active.classList.add('active'); }catch(e){toast(e.message,"error")} } function newChat(){ currentSessionId=null; chatHistory=[]; chatFiles=[]; selectedRagDocs=[]; const c=document.getElementById("page-content"); if(c)renderChat(c); } async function deleteSession(sid){ if(!confirm("Delete this chat?"))return; try{ await api("/api/chat/sessions/"+sid,{method:"DELETE"}); chatSessions=chatSessions.filter(s=>s.id!==sid); if(currentSessionId===sid){currentSessionId=null;chatHistory=[];} const c=document.getElementById("page-content"); if(c)renderChat(c); }catch(e){toast(e.message,"error")} } async function deleteCurrentChat(){ if(!currentSessionId){chatHistory=[];const c=document.getElementById("page-content");if(c)renderChat(c);return;} await deleteSession(currentSessionId); } function toggleSessionsPanel(){ const p=document.getElementById("chat-sessions-panel"); if(p)p.classList.toggle("hidden"); } function autoResize(el){el.style.height='40px';el.style.height=Math.min(el.scrollHeight,120)+'px'} // ═══ Voice Input — Web Speech API (All Languages) ═══ let _voiceRecognition=null; let _voiceActive=false; function toggleVoiceInput(){ if(!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)){ toast('Voice input not supported in this browser','error');return; } if(_voiceActive){stopVoice();return;} const SR=window.SpeechRecognition||window.webkitSpeechRecognition; _voiceRecognition=new SR(); _voiceRecognition.continuous=true; _voiceRecognition.interimResults=true; // Auto-detect language from browser or use multilingual const uiLang=localStorage.getItem('triva_lang')||'en'; const langMap={'ar':'ar-SA','en':'en-US','fr':'fr-FR','es':'es-ES','de':'de-DE','tr':'tr-TR','ur':'ur-PK','hi':'hi-IN','zh':'zh-CN','ja':'ja-JP','ko':'ko-KR','ru':'ru-RU','pt':'pt-BR'}; const lang=langMap[uiLang]||navigator.language||''; _voiceRecognition.lang=lang; let finalText=''; let interimText=''; _voiceRecognition.onstart=function(){ _voiceActive=true; const btn=document.getElementById('voice-btn'); if(btn){btn.style.color='#ef4444';btn.style.background='rgba(239,68,68,.1)';} document.getElementById('voice-pulse').style.display='block'; const inp=document.getElementById('chat-input'); if(inp)inp.placeholder='Listening... ('+lang+')'; }; _voiceRecognition.onresult=function(e){ interimText='';finalText=''; for(let i=0;iNo RAG documents uploaded yet
': chatRagDocs.map(doc=>`
${doc.filename||doc.title||'Document'}
${doc.chunk_count||0} chunks
${(selectedRagDocs||[]).includes(doc.id)?'':''}
`).join(''); }else{dd.classList.remove("show")} } let selectedRagDocs=[]; function toggleRagDoc(id,name){ const idx=selectedRagDocs.indexOf(id); if(idx>-1)selectedRagDocs.splice(idx,1);else selectedRagDocs.push(id); const ctx=document.getElementById("chat-rag-context"); const names=document.getElementById("rag-context-names"); if(selectedRagDocs.length>0){ctx.style.display="block";names.textContent=selectedRagDocs.length+" document(s) selected";}else{ctx.style.display="none"} toggleRagDropdown();toggleRagDropdown(); } function clearRagContext(){selectedRagDocs=[];document.getElementById("chat-rag-context").style.display="none"} function handleChatFiles(input){ const previews=document.getElementById("chat-file-previews"); chatFiles=[...input.files]; previews.innerHTML=chatFiles.map((f,i)=>`
${f.name.length>25?f.name.substring(0,22)+'...':f.name} (${(f.size/1024).toFixed(1)}KB) ×
`).join(''); } function removeChatFile(idx){chatFiles.splice(idx,1);const previews=document.getElementById("chat-file-previews");previews.innerHTML=chatFiles.map((f,i)=>`
${f.name} ×
`).join('')} function renderRichContent(text){ if(!text)return ''; let html=text; // Tables: detect |---|---| pattern const tableRegex=/(?:^|\n)(\|.+\|\n\|[-:| ]+\|\n(?:\|.+\|\n?)+)/gm; html=html.replace(tableRegex,(match)=>{ const rows=match.trim().split('\n').filter(r=>r.trim()); if(rows.length<2)return match; const headers=rows[0].split('|').filter(c=>c.trim()); const dataRows=rows.slice(2); return ''+headers.map(h=>'').join('')+''+ dataRows.map(r=>''+r.split('|').filter(c=>c.trim()).map(c=>'').join('')+'').join('')+'
'+h.trim()+'
'+c.trim()+'
'; }); // Code blocks html=html.replace(/```(\w*)\n([\s\S]*?)```/g,(m,lang,code)=>{ const id='code-'+Math.random().toString(36).substr(2,6); return `
${code.replace(//g,'>')}
`; }); // Inline code html=html.replace(/`([^`]+)`/g,'$1'); // Bold html=html.replace(/\*\*(.+?)\*\*/g,'$1'); // Italic html=html.replace(/\*(.+?)\*/g,'$1'); // Headers html=html.replace(/^### (.+)$/gm,'

$1

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

$1

'); // Unordered lists html=html.replace(/^[\-\*] (.+)$/gm,'
  • $1
  • '); // Ordered lists html=html.replace(/^\d+\. (.+)$/gm,'
  • $1
  • '); // Line breaks html=html.replace(/\n/g,'
    '); return html; } function renderChatBubble(role,content,agent,time){ const isUser=role==="user"; const icon=isUser?'fa-user':'fa-microchip'; const delegated=agent?`
    Handled by: ${agent}
    `:''; const rendered=isUser?content.replace(/\n/g,'
    '):renderRichContent(content); return `
    ${rendered}${delegated}
    ${time||new Date().toLocaleTimeString()}
    `; } async function sendChat(){ const inp=document.getElementById("chat-input"); const msg=inp.value.trim();if(!msg&&chatFiles.length===0)return; inp.value="";inp.style.height="40px"; const msgs=document.getElementById("chat-messages"); const time=new Date().toLocaleTimeString(); const status=document.getElementById("chat-status"); // Hide welcome screen on first message const welcome=document.getElementById("chat-welcome"); if(welcome)welcome.remove(); let fileInfo=""; if(chatFiles.length>0){ status.textContent="Uploading files..."; for(const f of chatFiles){ try{ const fd=new FormData();fd.append("file",f); const r=await fetch(window.API_BASE+"/api/rag/upload",{method:"POST",headers:{"Authorization":"Bearer "+window.AUTH_TOKEN},body:fd}); if(r.ok){ const d=await r.json(); fileInfo+="\n[Uploaded: "+f.name+" (ID: "+(d.id||"")+")]"; // For text-based files, read and include content preview if(f.name.match(/\.(txt|csv|md)$/i)){ try{ const txt=await f.text(); if(txt.length<5000) fileInfo+="\n[File Content:]\n"+txt; else fileInfo+="\n[File Content (first 4000 chars):]\n"+txt.substring(0,4000)+"..."; }catch{} } } }catch{} } chatFiles=[];document.getElementById("chat-file-previews").innerHTML=""; } const fullMsg=msg+(fileInfo?" "+fileInfo:""); chatHistory.push({role:"user",content:fullMsg,time:time}); msgs.innerHTML+=renderChatBubble("user",fullMsg,null,time); msgs.scrollTop=msgs.scrollHeight; msgs.innerHTML+=`
    Thinking...
    `; msgs.scrollTop=msgs.scrollHeight; status.textContent="Processing..."; try{ if(!currentSessionId){ currentSessionId=currentUser?.id+'-'+Date.now().toString(36); localStorage.setItem("agency_session_id",currentSessionId); } const payload={message:fullMsg,session_id:currentSessionId,include_context:true}; if(selectedRagDocs.length>0)payload.rag_doc_ids=selectedRagDocs; const d=await api("/api/orchestrator/chat",{method:"POST",body:JSON.stringify(payload)}); document.getElementById("typing")?.remove(); const reply=d.response||"I could not process that request."; const agentUsed=d.delegated_to||null; chatHistory.push({role:"assistant",content:reply,agent:agentUsed,time:new Date().toLocaleTimeString()}); msgs.innerHTML+=renderChatBubble("assistant",reply,agentUsed,new Date().toLocaleTimeString()); msgs.scrollTop=msgs.scrollHeight; status.textContent=""; // Update session title from first message const titleEl=document.getElementById("chat-session-title"); if(titleEl && chatHistory.length<=2) titleEl.textContent=fullMsg.substring(0,40); // Refresh sessions sidebar loadChatSessions(); }catch(e){ document.getElementById("typing")?.remove(); msgs.innerHTML+=renderChatBubble("assistant","Error: "+e.message,null,new Date().toLocaleTimeString()); msgs.scrollTop=msgs.scrollHeight; status.textContent=""; } } function clearChat(){newChat();} async function exportChat(){ const blob=new Blob([JSON.stringify(chatHistory,null,2)],{type:"application/json"}); const url=URL.createObjectURL(blob);const a=document.createElement("a");a.href=url;a.download="chat_export_"+Date.now()+".json";a.click(); toast("Chat exported"); } async function shareChat(){ try{ const d=await api("/api/chat/sessions",{method:"POST",body:JSON.stringify({messages:chatHistory,shared:true})}); toast("Chat saved to registry. Session: "+(d.session_id||"OK")); }catch(e){toast(e.message,"error")} } // ═══ SCHEDULE — Cron/Scheduled Tasks ═══ async function renderSchedule(c){ const d=await api("/api/schedule/tasks").catch(()=>({tasks:[]})); const agents=await api("/api/agents").catch(()=>({agents:[]})); c.innerHTML=`

    Scheduled Tasks

    Create and manage recurring agent tasks

    New Schedule

    ${d.tasks.length===0?'

    No scheduled tasks yet

    ': d.tasks.map(t=>`
    ${t.agent_name}
    ${t.task_description}
    ${t.frequency} ${t.active?'Active':'Paused'}
    `).join('')}
    `; } async function createSchedule(){ const agent=document.getElementById("sched-agent")?.value; const freq=document.getElementById("sched-freq")?.value; const task=document.getElementById("sched-task")?.value; const cron=document.getElementById("sched-cron")?.value; if(!agent||!task){toast("Agent and task required","error");return} try{await api("/api/schedule/tasks",{method:"POST",body:JSON.stringify({agent_name:agent,frequency:freq,cron_expr:cron,task_description:task})}); toast("Schedule created");loadPage("schedule");}catch(e){toast(e.message,"error")} } async function toggleSchedule(id){try{await api("/api/schedule/tasks/"+id+"/toggle",{method:"POST"});loadPage("schedule")}catch(e){toast(e.message,"error")}} async function deleteSchedule(id){try{await api("/api/schedule/tasks/"+id,{method:"DELETE"});toast("Deleted");loadPage("schedule")}catch(e){toast(e.message,"error")}} // ═══ REGISTRY — Chat History/Sessions ═══ async function renderRegistry(c){ const d=await api("/api/chat/sessions").catch(()=>({sessions:[]})); c.innerHTML=`

    Chat Registry

    Saved conversations and session history

    ${d.sessions.length===0?'

    No saved sessions yet. Use the chat Share button to save conversations.

    ': d.sessions.map(s=>`
    ${s.title||'Session'}
    ${s.message_count||0} messages · ${new Date(s.created_at).toLocaleString()}
    `).join('')}
    `; } async function viewSession(id){ try{const d=await api("/api/chat/sessions/"+id); const c=document.getElementById("page-content"); c.innerHTML=`

    ${d.title||'Session'}

    ${d.messages?.length||0} messages

    ${(d.messages||[]).map(m=>renderChatBubble(m.role,m.content,m.agent,m.time)).join('')}
    `;}catch(e){toast(e.message,"error")} } async function exportSession(id){try{const d=await api("/api/chat/sessions/"+id); const blob=new Blob([JSON.stringify(d,null,2)],{type:"application/json"});const url=URL.createObjectURL(blob);const a=document.createElement("a");a.href=url;a.download="session_"+id+".json";a.click();toast("Exported")}catch(e){toast(e.message,"error")}} async function shareSession(id){toast("Share link: /sessions/"+id,"success")} // ═══ PLUGINS ═══ async function renderPlugins(c){ const d=await api("/api/plugins").catch(()=>({plugins:[]})); const isolated=await api("/api/admin/isolation").catch(()=>({isolated:false})); const isIsolated=isolated.isolated; c.innerHTML=`

    Plugins

    Install and manage platform extensions

    ${isIsolated?'
    Platform Isolated
    External plugins are blocked. Only internal plugins can be installed.
    ':''}

    Install Plugin

    ${d.plugins.length===0?'

    No plugins installed

    Add an internal or external plugin above

    ': d.plugins.map(p=>{ const isExt=p.connection_type==='external'||p.endpoint?.startsWith('http'); return `
    ${p.name} v${p.version||'1.0'} ${isExt?'EXTERNAL':'INTERNAL'}
    ${p.endpoint||'Local plugin'}
    ${p.docs?'
    '+p.docs.substring(0,80)+'
    ':''}
    ${p.active?'Active':'Disabled'}
    `}).join('')}
    `; } function pluginConnTypeChanged(){ const ct=document.getElementById("plug-conn-type")?.value; const w=document.getElementById("plug-ext-warning"); if(w)w.style.display=ct==="external"?"block":"none"; } async function installPlugin(){ const name=document.getElementById("plug-name")?.value; const ver=document.getElementById("plug-ver")?.value; const url=document.getElementById("plug-url")?.value; const key=document.getElementById("plug-key")?.value; const cfg=document.getElementById("plug-config")?.value; const docs=document.getElementById("plug-docs")?.value; const connType=document.getElementById("plug-conn-type")?.value||"internal"; if(!name){toast("Plugin name required","error");return} // Warn about external connections if(connType==="external"){ if(!confirm("WARNING: This plugin will connect to external servers. Data may be sent outside the platform. Continue?")){return;} } let config={};try{if(cfg)config=JSON.parse(cfg)}catch{toast("Invalid JSON config","error");return} try{await api("/api/plugins",{method:"POST",body:JSON.stringify({name,version:ver,endpoint:url,api_key:key,config,connection_type:connType,docs})}); toast("Plugin installed");loadPage("plugins")}catch(e){toast(e.message,"error")} } async function togglePlugin(id){try{await api("/api/plugins/"+id+"/toggle",{method:"POST"});loadPage("plugins")}catch(e){toast(e.message,"error")}} async function removePlugin(id){try{await api("/api/plugins/"+id,{method:"DELETE"});toast("Removed");loadPage("plugins")}catch(e){toast(e.message,"error")}} // ═══ MEETINGS — Enterprise Meeting Management ═══ async function renderMeetings(c){ const d=await api("/api/meetings/my").catch(()=>({meetings:[]})); const rooms=await api("/api/meetings/rooms").catch(()=>({rooms:[]})); const users=await api("/api/auth/users").catch(()=>({users:[]})); c.innerHTML=`

    Meetings

    Schedule and manage meetings

    ${currentUser?.role==='admin'?'':''}
    ${d.meetings.length===0?'
    No meetings scheduled
    ': d.meetings.map(m=>{ const mDate=new Date(m.scheduled_at); const isPast=mDate
    ${m.title} ${isPast?'Past':'Upcoming'}
    ${mDate.toLocaleDateString()} ${mDate.toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'})} · ${m.duration||60} min
    ${m.room_name?'
    '+m.room_name+'
    ':''} ${m.agenda?'
    '+m.agenda.substring(0,100)+'
    ':''}
    ${(m.attendees||[]).length} attendees
    ${!isPast?'':''}
    `}).join('')}
    `; } function showNewMeeting(){const f=document.getElementById("new-meeting-form");if(f)f.style.display=f.style.display==="none"?"block":"none"} function showNewRoom(){const f=document.getElementById("new-room-form");if(f)f.style.display=f.style.display==="none"?"block":"none"} async function createMeeting(){ const title=document.getElementById("meet-title")?.value; const room_id=document.getElementById("meet-room")?.value; const dt=document.getElementById("meet-datetime")?.value; const duration=parseInt(document.getElementById("meet-duration")?.value||"60"); const agenda=document.getElementById("meet-agenda")?.value; const sel=document.getElementById("meet-attendees"); const attendees=sel?Array.from(sel.selectedOptions).map(o=>o.value):[]; if(!title||!dt){toast("Title and time are required","error");return} try{await api("/api/meetings/schedule",{method:"POST",body:JSON.stringify({title,room_id,scheduled_at:new Date(dt).toISOString(),duration,agenda,attendee_ids:attendees})}); toast("Meeting created");loadPage("meetings")}catch(e){toast(e.message,"error")} } async function createRoom(){ const name=document.getElementById("room-name")?.value; const capacity=parseInt(document.getElementById("room-capacity")?.value||"10"); const location=document.getElementById("room-location")?.value; if(!name){toast("Room name is required","error");return} try{await api("/api/meetings/rooms",{method:"POST",body:JSON.stringify({name,capacity,location})}); toast("Room added");loadPage("meetings")}catch(e){toast(e.message,"error")} } // ═══ GOALS & KPIs ═══ async function renderGoals(c){ const d=await api("/api/goals/my").catch(()=>({goals:[]})); c.innerHTML=`

    Goals & KPIs

    Track goals and key performance indicators

    ${d.goals.length}
    Total Goals
    ${d.goals.filter(g=>g.status==='completed').length}
    Completed
    ${d.goals.filter(g=>g.status==='active').length}
    Active
    ${d.goals.length>0?Math.round(d.goals.reduce((s,g)=>s+(g.current_value/Math.max(g.target_value,1)*100),0)/d.goals.length):0}%
    Avg Progress
    ${d.goals.length===0?'
    No goals yet
    ': d.goals.map(g=>{ const pct=Math.min(100,Math.round(g.current_value/Math.max(g.target_value,1)*100)); return `
    ${g.title} ${g.goal_type} ${g.status} ${g.description?'
    '+g.description.substring(0,100)+'
    ':''}
    ${g.current_value} / ${g.target_value}${pct}%
    `}).join('')}
    `; } function showNewGoal(){const f=document.getElementById("new-goal-form");if(f)f.style.display=f.style.display==="none"?"block":"none"} async function createGoal(){ const title=document.getElementById("goal-title")?.value; const goal_type=document.getElementById("goal-type")?.value; const target_value=parseFloat(document.getElementById("goal-target")?.value||"100"); const due_date=document.getElementById("goal-due")?.value; const description=document.getElementById("goal-desc")?.value; if(!title){toast("Title is required","error");return} try{await api("/api/goals/create",{method:"POST",body:JSON.stringify({title,goal_type,target_value,due_date,description})}); toast("Goal created");loadPage("goals")}catch(e){toast(e.message,"error")} } async function updateGoal(gid){ const val=parseFloat(document.getElementById("gv-"+gid)?.value||"0"); try{await api("/api/goals/"+gid+"/update",{method:"PUT",body:JSON.stringify({current_value:val})}); toast("Updated");loadPage("goals")}catch(e){toast(e.message,"error")} } // ═══ HR — Human Resources ═══ async function renderHR(c){ const attendance=await api("/api/hr/attendance/my").catch(()=>({records:[]})); const leaves=await api("/api/hr/leave/my").catch(()=>({leaves:[]})); c.innerHTML=`

    Human Resources

    Attendance & Leave Management

    Attendance Log

    ${attendance.records.length===0?'

    No attendance records

    ': attendance.records.slice(0,10).map(r=>`
    ${new Date(r.date).toLocaleDateString()} ${r.clock_in?new Date(r.clock_in).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'}):'-'} — ${r.clock_out?new Date(r.clock_out).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'}):'Not clocked out'}
    `).join('')}

    Leave Requests

    ${leaves.leaves.length===0?'

    No leave requests

    ': leaves.leaves.map(l=>`
    ${l.leave_type} ${l.status==='approved'?'Approved':l.status==='rejected'?'Rejected':'Pending'}
    ${l.start_date} — ${l.end_date} ${l.reason?' · '+l.reason:''}
    `).join('')}
    `; } function showLeaveRequest(){const f=document.getElementById("leave-form");if(f)f.style.display=f.style.display==="none"?"block":"none"} async function clockIn(){try{await api("/api/hr/attendance/clock-in",{method:"POST"});toast("Clocked in");loadPage("hr")}catch(e){toast(e.message,"error")}} async function clockOut(){try{await api("/api/hr/attendance/clock-out",{method:"POST"});toast("Clocked out");loadPage("hr")}catch(e){toast(e.message,"error")}} async function submitLeave(){ const leave_type=document.getElementById("leave-type")?.value; const start_date=document.getElementById("leave-start")?.value; const end_date=document.getElementById("leave-end")?.value; const reason=document.getElementById("leave-reason")?.value; if(!start_date||!end_date){toast("Select start and end dates","error");return} try{await api("/api/hr/leave/request",{method:"POST",body:JSON.stringify({leave_type,start_date,end_date,reason})}); toast("Leave request submitted");loadPage("hr")}catch(e){toast(e.message,"error")} } // ═══ ASSETS ═══ async function renderAssets(c){ const d=await api("/api/assets").catch(()=>({assets:[]})); const users=await api("/api/auth/users").catch(()=>({users:[]})); c.innerHTML=`

    Assets

    Manage organization assets

    ${currentUser?.role==='admin'?'':''} ${d.assets.length===0?'
    No assets registered
    ': '
    '+ d.assets.map(a=>'').join('')+ '
    NameTypeSerialAssigned ToStatusAction
    '+a.name+''+a.asset_type+''+(a.serial_number||'-')+''+(a.assigned_to_name||'Unassigned')+''+(a.status==='assigned'?'Assigned':'Available')+''+(currentUser?.role==='admin'&&a.status!=='assigned'?'':'')+'
    '}
    `; } function showNewAsset(){const f=document.getElementById("new-asset-form");if(f)f.style.display=f.style.display==="none"?"block":"none"} async function createAsset(){ const name=document.getElementById("asset-name")?.value; const asset_type=document.getElementById("asset-type")?.value; const serial_number=document.getElementById("asset-serial")?.value; if(!name){toast("Asset name is required","error");return} try{await api("/api/assets",{method:"POST",body:JSON.stringify({name,asset_type,serial_number})});toast("Asset added");loadPage("assets")}catch(e){toast(e.message,"error")} } async function assignAsset(aid,uid){ if(!uid)return; try{await api("/api/assets/"+aid+"/assign",{method:"PUT",body:JSON.stringify({user_id:uid})});toast("Asset assigned");loadPage("assets")}catch(e){toast(e.message,"error")} } // ═══ KNOWLEDGE BASE ═══ async function renderKnowledge(c){ const stats=await api("/api/admin/training-stats").catch(()=>({})); const rag=await api("/api/rag/documents").catch(()=>({documents:[]})); const requests=await api("/api/admin/knowledge-requests").catch(()=>({requests:[]})); c.innerHTML=`

    Knowledge Base

    Manage organizational knowledge and accumulated intelligence

    ${stats.knowledge_articles||0}
    Knowledge Articles
    ${stats.rag_documents||0}
    RAG Documents
    ${requests.requests.filter(r=>r.status==='pending').length}
    Pending Requests

    Knowledge Documents

    ${rag.documents.length===0?'

    No documents yet

    ': rag.documents.map(d=>'
    '+(d.topic||'Untitled')+'
    '+(d.content||'').substring(0,120)+'...
    Source: '+(d.source||'Unknown')+' · '+new Date(d.created_at).toLocaleDateString()+'
    ').join('')} ${requests.requests.length>0?'

    Pending Knowledge Requests

    '+ requests.requests.filter(r=>r.status==='pending').map(r=>'
    '+r.question+'
    From: '+r.asked_by+' · To: '+r.asked_to_role+'
    '+(currentUser?.role==='admin'||currentUser?.role==='manager'?'
    ':'')+'
    ').join(''):''}
    `; } function showAddKnowledge(){const f=document.getElementById("add-knowledge-form");if(f)f.style.display=f.style.display==="none"?"block":"none"} async function addKnowledge(){ const topic=document.getElementById("kb-topic")?.value; const content=document.getElementById("kb-content")?.value; const source=document.getElementById("kb-source")?.value; if(!topic||!content){toast("Topic and content are required","error");return} try{await api("/api/admin/add-knowledge",{method:"POST",body:JSON.stringify({topic,content,source})});toast("Knowledge added");loadPage("knowledge")}catch(e){toast(e.message,"error")} } async function approveKnowledgeReq(rid){ const answer=document.getElementById("ka-"+rid)?.value; if(!answer){toast("Enter the answer first","error");return} try{await api("/api/admin/knowledge-requests/"+rid+"/approve",{method:"POST",body:JSON.stringify({answer,approved:true})});toast("Approved & saved");loadPage("knowledge")}catch(e){toast(e.message,"error")} } // ═══ ANNOUNCEMENTS ═══ async function renderAnnouncements(c){ const d=await api("/api/announcements").catch(()=>({announcements:[]})); const polls=await api("/api/polls").catch(()=>({polls:[]})); const canPost=currentUser?.role==='admin'||currentUser?.role==='manager'; c.innerHTML=`

    Announcements & Polls

    Company-wide communication

    ${canPost?`
    `:''}

    Announcements

    ${d.announcements.length===0?'

    No announcements

    ': d.announcements.map(a=>'
    '+a.title+' '+a.priority+'
    '+a.content+'
    '+a.author+' · '+new Date(a.created_at).toLocaleDateString()+'
    ').join('')}

    Polls

    ${polls.polls.length===0?'

    No polls

    ': polls.polls.map(p=>{const total=Object.values(p.results||{}).reduce((s,v)=>s+v,0);return '
    '+p.question+' '+total+' votes
    '+ (p.options||[]).map(o=>{const votes=(p.results||{})[o]||0;const pct=total>0?Math.round(votes/total*100):0;return '
    '+o+''+votes+' ('+pct+'%)
    '}).join('')+ '
    '}).join('')}
    `; } function showNewAnnouncement(){const f=document.getElementById("new-announce-form");if(f)f.style.display=f.style.display==="none"?"block":"none"} function showNewPoll(){const f=document.getElementById("new-poll-form");if(f)f.style.display=f.style.display==="none"?"block":"none"} async function postAnnouncement(){ const title=document.getElementById("ann-title")?.value; const content=document.getElementById("ann-content")?.value; const priority=document.getElementById("ann-priority")?.value; if(!title||!content){toast("Title and content are required","error");return} try{await api("/api/announcements",{method:"POST",body:JSON.stringify({title,content,priority})});toast("Announcement published");loadPage("announcements")}catch(e){toast(e.message,"error")} } async function postPoll(){ const question=document.getElementById("poll-q")?.value; const opts=(document.getElementById("poll-opts")?.value||"").split("\n").filter(o=>o.trim()); if(!question||opts.length<2){toast("Question and at least 2 options required","error");return} try{await api("/api/polls",{method:"POST",body:JSON.stringify({question,options:opts})});toast("Poll created");loadPage("announcements")}catch(e){toast(e.message,"error")} } async function votePoll(pid){ const opt=prompt("Enter your choice:"); if(!opt)return; try{await api("/api/polls/"+pid+"/vote",{method:"POST",body:JSON.stringify({option:opt})});toast("Vote recorded");loadPage("announcements")}catch(e){toast(e.message,"error")} } // ═══ WORKFLOWS ═══ async function renderWorkflows(c){ const pending=await api("/api/workflows/pending").catch(()=>({workflows:[]})); c.innerHTML=`

    Workflows

    Manage processes and approvals

    ${pending.workflows.length===0?'
    No pending requests
    ': pending.workflows.map(w=>'
    '+w.title+' '+w.workflow_type+' '+(w.status==='approved'?'Approved':w.status==='rejected'?'Rejected':'Pending')+'
    '+(w.details||'').substring(0,150)+'
    By: '+(w.requester||'')+' · '+new Date(w.created_at).toLocaleDateString()+'
    '+(w.status==='pending'&&(currentUser?.role==='admin'||currentUser?.role==='manager')?'
    ':'')+'
    ').join('')}
    `; } function showNewWorkflow(){const f=document.getElementById("new-workflow-form");if(f)f.style.display=f.style.display==="none"?"block":"none"} async function createWorkflow(){ const workflow_type=document.getElementById("wf-type")?.value; const title=document.getElementById("wf-title")?.value; const details=document.getElementById("wf-details")?.value; if(!title){toast("Title is required","error");return} try{await api("/api/workflows/create",{method:"POST",body:JSON.stringify({workflow_type,title,details})});toast("Request submitted");loadPage("workflows")}catch(e){toast(e.message,"error")} } async function workflowAction(wid,action){ const comment=prompt("Comment (optional):"); try{await api("/api/workflows/"+wid+"/action",{method:"PUT",body:JSON.stringify({action,comment:comment||""})});toast(action==='approve'?"Approved":"Rejected");loadPage("workflows")}catch(e){toast(e.message,"error")} } // ═══ ORG IMPORT ═══ async function renderOrgImport(c){ const history=await api("/api/admin/import-history").catch(()=>({imports:[]})); c.innerHTML=`

    Organization Import

    Import departments and employees from CSV, Excel, or structured data

    CSV / Excel Import

    Upload a CSV with columns: department, parent_department, username, email, role, position, employee_id

    AI-Assisted Import

    Describe your org structure in plain text and let AI parse it into departments and users

    ${history.imports.length>0?'

    Import History

    '+ history.imports.map(h=>'
    '+h.import_type.toUpperCase()+' — '+(h.filename||'manual')+''+h.status+'
    '+h.departments_created+' departments, '+h.users_created+' users · '+new Date(h.created_at).toLocaleDateString()+'
    ').join(''):''}
    `; } function showImportCSV(){ const f=document.getElementById("import-csv-form"); const a=document.getElementById("import-ai-form"); if(f)f.style.display=f.style.display==="none"?"block":"none"; if(a)a.style.display="none"; } function showImportAI(){ const f=document.getElementById("import-ai-form"); const c=document.getElementById("import-csv-form"); if(f)f.style.display=f.style.display==="none"?"block":"none"; if(c)c.style.display="none"; } function loadCSVFile(input){ const file=input.files[0]; if(!file)return; const reader=new FileReader(); reader.onload=function(e){ document.getElementById("csv-data").value=e.target.result; toast("File loaded: "+file.name); }; reader.readAsText(file); } function parseCSV(text){ const lines=text.trim().split("\n"); if(lines.length<2)return []; const headers=lines[0].split(",").map(h=>h.trim().toLowerCase().replace(/[^a-z_]/g,'')); const rows=[]; for(let i=1;iv.trim()); const row={}; headers.forEach((h,j)=>{row[h]=vals[j]||""}); rows.push(row); } return rows; } async function doCSVImport(){ const text=document.getElementById("csv-data")?.value; if(!text||!text.trim()){toast("Paste CSV data or upload a file","error");return} const rows=parseCSV(text); if(rows.length===0){toast("No valid rows found","error");return} try{ const result=await api("/api/admin/import-org",{method:"POST",body:JSON.stringify({type:"csv",rows,filename:"manual_csv"})}); const resDiv=document.getElementById("import-result"); if(resDiv){ resDiv.style.display="block"; resDiv.innerHTML='

    Import Complete

    '+result.departments_created+' departments created, '+result.users_created+' users created

    '+(result.errors.length>0?'
    '+result.errors.map(e=>'
    '+e+'
    ').join('')+'
    ':'')+'
    '; } toast("Import completed!"); }catch(e){toast(e.message,"error")} } async function doAIImport(){ const text=document.getElementById("ai-org-text")?.value; if(!text||!text.trim()){toast("Describe your org structure","error");return} toast("Processing with AI..."); try{ const aiResp=await api("/api/orchestrator/chat",{method:"POST",body:JSON.stringify({message:"Parse the following organization description into a structured JSON array. Each row should have: department, parent_department, username (generate if not given), email (generate if not given), role (admin/manager/user), position, employee_id. Return ONLY the JSON array, no other text:\n\n"+text,session_id:"system_org_import"})}); const content=(aiResp.response||aiResp.content||"").trim(); let jsonMatch=content.match(/\[\s*\{[\s\S]*\}\s*\]/); if(jsonMatch){ const rows=JSON.parse(jsonMatch[0]); const preview=document.getElementById("ai-import-preview"); if(preview){ preview.innerHTML='

    Preview ('+rows.length+' entries)

    '+Object.keys(rows[0]).map(k=>'').join('')+''+rows.map(r=>''+Object.values(r).map(v=>'').join('')+'').join('')+'
    '+k+'
    '+v+'
    '; window._aiImportRows=rows; } }else{ toast("AI could not parse the structure. Try a clearer description.","error"); } }catch(e){toast(e.message,"error")} } async function confirmAIImport(){ if(!window._aiImportRows){toast("No data to import","error");return} try{ const result=await api("/api/admin/import-org",{method:"POST",body:JSON.stringify({type:"ai",rows:window._aiImportRows,filename:"ai_parsed"})}); toast(result.departments_created+" departments, "+result.users_created+" users imported!"); loadPage("org-import"); }catch(e){toast(e.message,"error")} } // ═══ AUTH SETTINGS ═══ async function renderAuthSettings(c){ const settings=await api("/api/admin/auth-settings").catch(()=>({})); c.innerHTML=`

    Authentication Settings

    Configure how users authenticate to this system

    Authentication Method

    Invite User

    `; document.getElementById("auth-method")?.addEventListener("change",function(){ const sso=document.getElementById("sso-fields"); if(sso)sso.style.display=this.value==="sso"?"block":"none"; }); } async function saveAuthSettings(){ const method=document.getElementById("auth-method")?.value; const provider=document.getElementById("sso-provider")?.value; const domain=document.getElementById("sso-domain")?.value; const selfReg=document.getElementById("allow-self-reg")?.checked; try{await api("/api/admin/auth-settings",{method:"POST",body:JSON.stringify({auth_method:method,sso_provider:provider,sso_domain:domain,allow_self_registration:String(selfReg)})}); toast("Auth settings saved");}catch(e){toast(e.message,"error")} } async function inviteUser(){ const username=document.getElementById("invite-username")?.value; const email=document.getElementById("invite-email")?.value; const role=document.getElementById("invite-role")?.value; const password=document.getElementById("invite-password")?.value||username+"123"; const position=document.getElementById("invite-position")?.value; const employee_id=document.getElementById("invite-empid")?.value; if(!username){toast("Username is required","error");return} try{await api("/api/auth/register",{method:"POST",body:JSON.stringify({username,email,password,role,position,employee_id})}); toast("User created! They will be prompted to change password on first login.");loadPage("auth-settings")}catch(e){toast(e.message,"error")} } // ═══ Actions ═══ async function generateReport(){ try{const d=await api("/api/admin/reports/generate",{method:"POST"}); toast("Report generated — pending approval");loadPage("reports"); }catch(e){toast(e.message,"error")} } async function approveReport(rid){ const pin=prompt("Enter Admin PIN to approve this report:"); if(!pin)return; try{await api("/api/admin/reports/"+rid+"/approve",{method:"POST",body:JSON.stringify({admin_pin:pin})}); toast("Report approved");loadPage("reports"); }catch(e){toast(e.message,"error")} } async function rejectReport(rid){ try{await api("/api/admin/reports/"+rid+"/reject",{method:"POST"}); toast("Report rejected");loadPage("reports"); }catch(e){toast(e.message,"error")} } async function exportReport(rid){ try{const d=await api("/api/admin/reports/"+rid+"/export"); const blob=new Blob([JSON.stringify(d.report,null,2)],{type:"application/json"}); const url=URL.createObjectURL(blob);const a=document.createElement("a"); a.href=url;a.download="report_"+rid+".json";a.click();URL.revokeObjectURL(url); toast("Report exported"); }catch(e){toast(e.message,"error")} } async function doLogin(){ try{ const d=await api("/api/auth/login",{method:"POST",body:JSON.stringify({username:$("#login-user").value,password:$("#login-pass").value})}); token=d.token;currentUser=d.user;localStorage.setItem("agency_token",token);localStorage.setItem("agency_user",JSON.stringify(d.user));window.AUTH_TOKEN=token; // Handle demo users if(d.is_demo){ toast("Welcome to demo environment"); render();return; } // Handle new verified users who need billing if(d.needs_billing){ toast("Account verified! Redirecting to billing..."); render();return; } toast("Logged in");render(); }catch(e){toast(e.message,"error")} } function renderForcePasswordChange(){ return ``; } async function doForcePasswordChange(){ const old_pw=document.getElementById("pw-old")?.value; const new_pw=document.getElementById("pw-new")?.value; const confirm_pw=document.getElementById("pw-confirm")?.value; if(!old_pw||!new_pw){toast("Fill in all fields","error");return} if(new_pw.length<6){toast("Password must be at least 6 characters","error");return} if(new_pw!==confirm_pw){toast("Passwords do not match","error");return} try{ await api("/api/auth/change-password",{method:"POST",body:JSON.stringify({old_password:old_pw,new_password:new_pw})}); currentUser.force_password_change=false; localStorage.setItem("agency_user",JSON.stringify(currentUser)); toast("Password updated successfully"); render(); }catch(e){toast(e.message,"error")} } async function doActivate(){ try{ const key=$("#act-key").value.trim(); if(!key){toast("Please enter your license key","error");return;} const r=await fetch(API+"/api/setup/activate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({license_key:key})}); const d=await r.json(); if(r.ok){toast("License activated successfully");render();} else{toast(d.detail||"Invalid license key","error");} }catch(e){toast(e.message,"error")} } async function doBrand(){ try{ await fetch(API+"/api/setup/brand",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({company_name:$("#brand-name").value,company_logo:$("#brand-logo").value})}); toast("Brand set");render(); }catch(e){toast(e.message,"error")} } async function sendMsg(){const v=$("#msg-input");if(!v||!v.value.trim())return;try{await api("/api/messages",{method:"POST",body:JSON.stringify({content:v.value,channel:"general"})});v.value="";loadPage("messages");toast("Sent");}catch(e){toast(e.message,"error")}} async function resolveEsc(id){try{await api("/api/escalations/"+id+"/resolve",{method:"PUT"});loadPage("escalations");toast("Resolved");}catch(e){toast(e.message,"error")}} async function toggleNet(mode){const pin=$("#net-pin");try{await api("/api/admin/network/toggle",{method:"POST",body:JSON.stringify({mode,admin_pin:pin?pin.value:""})});loadPage("network");toast("Network: "+mode);}catch(e){toast(e.message,"error")}} async function renewLic(){const p=$("#renew-path");try{await api("/api/admin/license/renew",{method:"POST",body:JSON.stringify({file_path:p?p.value:""})});loadPage("license");toast("License renewed");}catch(e){toast(e.message,"error")}} async function applyUpdate(){const p=$("#update-path");try{await api("/api/admin/updates/apply-offline",{method:"POST",body:JSON.stringify({file_path:p?p.value:""})});loadPage("updates");toast("Update applied");}catch(e){toast(e.message,"error")}} async function addVS(){try{await api("/api/admin/connections/vector-store",{method:"POST",body:JSON.stringify({name:$("#vs-name").value,connection_string:$("#vs-conn").value})});loadPage("connections");toast("Vector store added");}catch(e){toast(e.message,"error")}} async function addDB(){try{await api("/api/admin/connections/database",{method:"POST",body:JSON.stringify({name:$("#db-name").value,connection_string:$("#db-conn").value})});loadPage("connections");toast("Database added");}catch(e){toast(e.message,"error")}} async function addUser(){try{await api("/api/auth/register",{method:"POST",body:JSON.stringify({username:$("#new-user").value,password:$("#new-pass").value,role:$("#new-role").value})});loadPage("users");toast("User added");}catch(e){toast(e.message,"error")}} async function executeAgent(){const agent=$("#exec-agent").value;const task=$("#exec-task").value;if(!task)return;const rag=$("#exec-rag")?.checked; const res=$("#exec-result");res.innerHTML='

    Processing...

    '; try{const d=await api("/api/agents/execute",{method:"POST",body:JSON.stringify({agent_name:agent,task,use_rag:rag})}); res.innerHTML=`

    Result ${d.duration_ms}ms ${d.rag_docs>0?`${d.rag_docs} RAG docs`:''}

    ${d.result}
    `; }catch(e){res.innerHTML=`

    ${e.message}

    `;}} async function addRagDoc(){try{await api("/api/rag/documents",{method:"POST",body:JSON.stringify({title:$("#rag-title").value,content:$("#rag-content").value,source:$("#rag-source").value})});loadPage("agents");showAgentTab(document.querySelectorAll('.tab-btn')[2],'rag-store');toast("Document indexed");}catch(e){toast(e.message,"error")}} async function searchRag(){const q=$("#rag-search").value;const res=$("#rag-results");try{const d=await api("/api/rag/search?q="+encodeURIComponent(q)); res.innerHTML=d.results.length?d.results.map(r=>`
    ${r.title||'Untitled'}

    ${(r.content||'').substring(0,200)}...

    `).join(''):'

    No results

    '; }catch(e){toast(e.message,"error")}} async function submitFeedback(){try{await api("/api/agents/feedback",{method:"POST",body:JSON.stringify({agent_name:$("#fb-agent").value,task:$("#fb-task").value,feedback:$("#fb-text").value,quality_score:parseInt($("#fb-score").value)})});toast("Feedback submitted");loadPage("learning");}catch(e){toast(e.message,"error")}} // ── Tasks Actions ── function showNewTask(){const f=$("#new-task-form");if(f)f.style.display=f.style.display==='none'?'block':'none'} async function createTask(){try{await api("/api/tasks",{method:"POST",body:JSON.stringify({title:$("#task-title").value,description:$("#task-desc").value,project:$("#task-project").value,priority:$("#task-priority").value,assigned_to:$("#task-assign").value})});toast("Task created");loadPage("tasks");}catch(e){toast(e.message,"error")}} function showHandover(tid){const f=$("#handover-"+tid);if(f)f.style.display=f.style.display==='none'?'flex':'none'} async function doHandover(tid){try{await api("/api/tasks/"+tid+"/handover",{method:"POST",body:JSON.stringify({to_user:$("#ho-target-"+tid).value,note:$("#ho-note-"+tid).value})});toast("Task handed over");loadPage("tasks");}catch(e){toast(e.message,"error")}} async function updateTaskStatus(tid,st){if(!st)return;try{await api("/api/tasks/"+tid+"/status",{method:"PUT",body:JSON.stringify({status:st})});toast("Status: "+st);loadPage("tasks");}catch(e){toast(e.message,"error")}} async function searchTasks(){const q=$("#task-search").value;try{const d=await api("/api/tasks/search?q="+encodeURIComponent(q));const list=$("#tasks-list");if(list&&d.tasks){list.innerHTML=d.tasks.length?d.tasks.map(t=>'
    '+t.title+' '+(t.status||'')+'
    '+t.assignee_name+'
    ').join(''):'

    No results

    ';}}catch(e){toast(e.message,"error")}} // ── Contracts Actions ── function showNewContract(){const f=$("#new-contract-form");if(f)f.style.display=f.style.display==='none'?'block':'none'} async function createContract(){try{await api("/api/contracts",{method:"POST",body:JSON.stringify({title:$("#ct-title").value,contract_type:$("#ct-type").value,client_name:$("#ct-client").value,amount:parseFloat($("#ct-amount").value)||0,currency:$("#ct-currency").value,assigned_to:$("#ct-assign").value,due_date:$("#ct-due").value,details:$("#ct-details").value})});toast("Contract created");loadPage("contracts");}catch(e){toast(e.message,"error")}} async function updateContractStatus(cid,st){if(!st)return;try{await api("/api/contracts/"+cid,{method:"PUT",body:JSON.stringify({status:st})});toast("Status: "+st);loadPage("contracts");}catch(e){toast(e.message,"error")}} async function searchContracts(){const q=$("#contract-search").value;try{const d=await api("/api/contracts/search?q="+encodeURIComponent(q));const list=$("#contracts-list");if(list&&d.contracts){list.innerHTML=d.contracts.length?d.contracts.map(ct=>'
    '+ct.title+' '+ct.contract_type+'
    '+ct.client_name+' — '+(ct.amount||0)+' '+(ct.currency||'')+'
    ').join(''):'

    No results

    ';}}catch(e){toast(e.message,"error")}} // ── AI Provider Actions (Enhanced) ── const _KNOWN_PROV={openai:{name:"OpenAI",url:"https://api.openai.com/v1",models:"gpt-4o,gpt-4o-mini,gpt-4-turbo,gpt-3.5-turbo",docs:"https://platform.openai.com/api-keys"},anthropic:{name:"Anthropic",url:"https://api.anthropic.com/v1",models:"claude-sonnet-4-20250514,claude-3-5-sonnet-20241022,claude-3-5-haiku-20241022",docs:"https://console.anthropic.com/settings/keys"},google:{name:"Google Gemini",url:"https://generativelanguage.googleapis.com/v1beta",models:"gemini-2.0-flash,gemini-1.5-pro,gemini-1.5-flash",docs:"https://aistudio.google.com/apikey"},openrouter:{name:"OpenRouter",url:"https://openrouter.ai/api/v1",models:"openai/gpt-4o,anthropic/claude-3.5-sonnet",docs:"https://openrouter.ai/keys"},deepseek:{name:"DeepSeek",url:"https://api.deepseek.com/v1",models:"deepseek-chat,deepseek-reasoner",docs:"https://platform.deepseek.com/api_keys"},mistral:{name:"Mistral AI",url:"https://api.mistral.ai/v1",models:"mistral-large-latest,mistral-small-latest,codestral-latest",docs:"https://console.mistral.ai/api-keys/"},groq:{name:"Groq",url:"https://api.groq.com/openai/v1",models:"llama-3.3-70b-versatile,mixtral-8x7b-32768",docs:"https://console.groq.com/keys"},cohere:{name:"Cohere",url:"https://api.cohere.com/v2",models:"command-r-plus,command-r",docs:"https://dashboard.cohere.com/api-keys"},together:{name:"Together AI",url:"https://api.together.xyz/v1",models:"meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo",docs:"https://api.together.ai/settings/api-keys"},fireworks:{name:"Fireworks AI",url:"https://api.fireworks.ai/inference/v1",models:"accounts/fireworks/models/llama-v3p1-405b-instruct",docs:"https://fireworks.ai/api-keys"},perplexity:{name:"Perplexity",url:"https://api.perplexity.ai",models:"sonar-pro,sonar",docs:"https://perplexity.ai/settings/api"},azure:{name:"Azure OpenAI",url:"",models:"",docs:"https://portal.azure.com"},local:{name:"Local (Ollama/vLLM)",url:"http://localhost:11434/v1",models:"llama3,mistral",docs:"https://ollama.com"},custom:{name:"Custom Provider",url:"",models:"",docs:""}}; function quickAddProvider(type){const p=_KNOWN_PROV[type];if(!p)return;const panel=$("#provider-setup-panel");panel.style.display="block";$("#setup-provider-title").textContent="Configure "+p.name;$("#prov-type-hidden").value=type;$("#prov-key").value="";$("#prov-url").value=p.url;const cb=$("#prov-models-checkboxes");const models=p.models?p.models.split(","):[];cb.innerHTML=models.map(m=>``).join("")+(models.length===0?'':"");$("#prov-models").value=models.join(",");$("#setup-docs-link").innerHTML=p.docs?` Get API Key`:"";panel.scrollIntoView({behavior:"smooth"})} async function submitProvider(){const type=$("#prov-type-hidden").value;const p=_KNOWN_PROV[type];const key=$("#prov-key").value;if(!key){toast("API key is required","error");return}const checked=[...document.querySelectorAll(".prov-model-cb:checked")].map(c=>c.value);const custom=$("#prov-models-custom");const models=checked.length?checked:(custom?custom.value.split(",").map(m=>m.trim()).filter(Boolean):[]);if(!models.length){toast("Select at least one model","error");return}try{await api("/api/admin/ai-providers",{method:"POST",body:JSON.stringify({name:p?p.name:type,provider_type:type,api_key:key,base_url:$("#prov-url").value,models:models})});toast("Provider added successfully");loadPage("ai-providers");}catch(e){toast(e.message,"error")}} async function testProviderReal(pid){const el=$("#test-result-"+pid);if(el)el.innerHTML=' Testing connection...';try{const d=await api("/api/admin/ai-providers/"+pid+"/test",{method:"POST"});if(el){if(d.status==="pass"){el.innerHTML=` Connected (${d.response_time_ms}ms)${d.model_returned?' — Model: '+d.model_returned:''}`}else{el.innerHTML=` Failed: ${d.error||'Unknown error'}`}}}catch(e){if(el)el.innerHTML=` ${e.message}`}} function editProvider(pid,name,url,models){const panel=$("#edit-provider-panel");panel.style.display="block";$("#edit-provider-title").textContent="Edit "+name;$("#edit-pid").value=pid;$("#edit-key").value="";$("#edit-url").value=url;const activeModels=models?models.split(","):[];const type=name.toLowerCase().replace(/\s+/g,"");const prov=Object.values(_KNOWN_PROV).find(p=>p.name===name)||{models:""};const allModels=prov.models?prov.models.split(","):[];const merged=[...new Set([...allModels,...activeModels])].filter(Boolean);const cb=$("#edit-models-checkboxes");cb.innerHTML=merged.map(m=>``).join("")+(merged.length===0?'':"");$("#edit-models").value=models;panel.scrollIntoView({behavior:"smooth"})} async function saveProviderEdit(){const pid=$("#edit-pid").value;const data={};const key=$("#edit-key").value;if(key)data.api_key=key;data.base_url=$("#edit-url").value;const checked=[...document.querySelectorAll(".edit-model-cb:checked")].map(c=>c.value);const custom=$("#edit-models-custom");data.models=checked.length?checked:(custom?custom.value.split(",").map(m=>m.trim()).filter(Boolean):[]);try{await api("/api/admin/ai-providers/"+pid,{method:"PUT",body:JSON.stringify(data)});toast("Provider updated");loadPage("ai-providers");}catch(e){toast(e.message,"error")}} async function toggleProviderActive(pid,active){try{await api("/api/admin/ai-providers/"+pid,{method:"PUT",body:JSON.stringify({is_active:active})});toast(active?"Provider enabled":"Provider disabled");loadPage("ai-providers");}catch(e){toast(e.message,"error")}} async function deleteProvider(pid){if(!confirm("Delete this AI provider?"))return;try{await api("/api/admin/ai-providers/"+pid,{method:"DELETE"});toast("Provider deleted");loadPage("ai-providers");}catch(e){toast(e.message,"error")}} // ══ FlexPool Actions ══ let _fpColCount=0; function showCreateFlexModule(){const p=$("#flex-create-panel");p.style.display="block";_fpColCount=0;$("#fp-columns").innerHTML="";addFlexColumn();p.scrollIntoView({behavior:"smooth"});} function addFlexColumn(){_fpColCount++;const d=document.createElement("div");d.style.cssText="display:flex;gap:6px;align-items:center;margin-bottom:6px";d.innerHTML=``;$("#fp-columns").appendChild(d);} function fpColTypeChanged(sel){const row=sel.parentElement;const defInput=row.querySelector('.fp-col-default');const targetSel=row.querySelector('.fp-col-target');if(sel.value==='RELATION'){defInput.style.display='none';targetSel.style.display='block';targetSel.innerHTML='';api("/api/admin/flexpool/modules").then(d=>{(d.modules||[]).forEach(m=>{targetSel.innerHTML+=``;});}).catch(()=>{});}else if(sel.value==='SELECT'){defInput.placeholder='Options (comma-separated)';defInput.style.display='block';targetSel.style.display='none';}else{defInput.placeholder='Default';defInput.style.display='block';targetSel.style.display='none';}} async function submitFlexModule(){const name=$("#fp-name").value;if(!name){toast("Name required","error");return;}const colEls=document.querySelectorAll("#fp-columns > div");const cols=[];colEls.forEach(el=>{const n=el.querySelector(".fp-col-name").value.trim();const t=el.querySelector(".fp-col-type").value;const d=el.querySelector(".fp-col-default").value;const target=el.querySelector(".fp-col-target")?.value;const col={name:n,type:t};if(d)col.default=d;if(t==='RELATION'&&target){col.target_module=target;col.relation_type='many_to_one';}if(t==='SELECT'&&d)col.options=d.split(',').map(s=>s.trim());if(n)cols.push(col);});if(cols.length===0){toast("Add at least one column","error");return;}const dept=document.getElementById("fp-dept")?.value||"";const perms={view:document.getElementById("fp-perm-view")?.value||"all",edit:document.getElementById("fp-perm-edit")?.value||"department",delete:document.getElementById("fp-perm-delete")?.value||"admin"};try{const r=await api("/api/admin/flexpool/modules",{method:"POST",body:JSON.stringify({name,description:$("#fp-desc").value,columns:cols,icon:$("#fp-icon").value,color:$("#fp-color").value,department_id:dept,permissions:perms})});toast("Table '"+name+"' created"+(r.relations?" with "+r.relations+" relation(s)":""));loadPage("flexpool");}catch(e){toast(e.message||"Failed to create","error");}} async function deleteFlexModule(id,name){if(!confirm("Delete table '"+name+"'? All data and automations will be lost."))return;try{await api("/api/admin/flexpool/modules/"+id,{method:"DELETE"});toast("Table deleted");loadPage("flexpool");}catch(e){toast(e.message,"error");}} async function showFlexAutomations(moduleId){ const d=await api("/api/flexpool/"+moduleId+"/automations").catch(()=>({automations:[]})); const modules=await api("/api/admin/flexpool/modules").catch(()=>({modules:[]})); const autos=d.automations||[]; const content=document.getElementById("page-content"); content.innerHTML=`

    Automations

    ${autos.length} automation(s) for this table

    Create Automation

    ${autos.length===0?'

    No automations yet

    ':''} ${autos.map(a=>`
    ${a.name}
    ${a.trigger_type.replace('on_','On ')} ${a.action_type} ${a.trigger_count>0?`Triggered ${a.trigger_count}x`:''}
    `).join('')}
    `; } function autoActionChanged(){ const action=document.getElementById("auto-action")?.value; const area=document.getElementById("auto-config-area"); if(!area)return; if(action==="notify"){ area.innerHTML=`
    `; }else if(action==="webhook"){ area.innerHTML=`
    `; }else if(action==="update_record"||action==="create_record"){ area.innerHTML=`
    `; } } async function submitFlexAutomation(moduleId){ const name=document.getElementById("auto-name")?.value; if(!name){toast("Name required","error");return;} const trigger=document.getElementById("auto-trigger")?.value||"on_create"; const action=document.getElementById("auto-action")?.value||"notify"; let config={}; if(action==="notify"){config={message:document.getElementById("auto-cfg-message")?.value||"",notify_target:document.getElementById("auto-cfg-target")?.value||"admin"};} else if(action==="webhook"){config={url:document.getElementById("auto-cfg-url")?.value||""};} else{const mapping=document.getElementById("auto-cfg-mapping")?.value;config={target_module:document.getElementById("auto-cfg-target-mod")?.value||"",field_mapping:mapping?JSON.parse(mapping):{}};} try{await api("/api/flexpool/"+moduleId+"/automations",{method:"POST",body:JSON.stringify({name,trigger_type:trigger,action_type:action,action_config:config})}); toast("Automation created");showFlexAutomations(moduleId);}catch(e){toast(e.message,"error");} } async function toggleFlexAuto(autoId,moduleId){try{await api("/api/flexpool/automations/"+autoId+"/toggle",{method:"PUT"});showFlexAutomations(moduleId);}catch(e){toast(e.message,"error");}} async function deleteFlexAuto(autoId,moduleId){if(!confirm("Delete this automation?"))return;try{await api("/api/flexpool/automations/"+autoId,{method:"DELETE"});showFlexAutomations(moduleId);}catch(e){toast(e.message,"error");}} function showFlexRecordForm(mid){const f=$("#fp-record-form");f.style.display="block";$("#fp-form-title").textContent="New Record";$("#fp-edit-id").value="";document.querySelectorAll(".fp-field").forEach(i=>i.value="");f.scrollIntoView({behavior:"smooth"});} function editFlexRecord(mid,rid,dataStr){try{const data=JSON.parse(dataStr);const f=$("#fp-record-form");f.style.display="block";$("#fp-form-title").textContent="Edit Record";$("#fp-edit-id").value=rid;document.querySelectorAll(".fp-field").forEach(i=>{const col=i.dataset.col;if(data[col]!==undefined)i.value=data[col];});}catch(e){console.error(e);}} async function submitFlexRecord(mid){const editId=$("#fp-edit-id").value;const data={};document.querySelectorAll(".fp-field").forEach(i=>{const col=i.dataset.col;if(i.value)data[col]=i.value;});try{if(editId){await api("/api/flexpool/"+mid+"/data/"+editId,{method:"PUT",body:JSON.stringify(data)});toast("Record updated");}else{await api("/api/flexpool/"+mid+"/data",{method:"POST",body:JSON.stringify(data)});toast("Record added");}loadPage("flexpool-"+mid);}catch(e){toast(e.message,"error");}} async function deleteFlexRecord(mid,rid){if(!confirm("Delete this record?"))return;try{await api("/api/flexpool/"+mid+"/data/"+rid,{method:"DELETE"});toast("Deleted");loadPage("flexpool-"+mid);}catch(e){toast(e.message,"error");}} let _flexSearchTimer=null; function flexSearchDebounce(mid){clearTimeout(_flexSearchTimer);_flexSearchTimer=setTimeout(async()=>{const q=$("#fp-search").value;try{const d=await api("/api/flexpool/"+mid+"/data?search="+encodeURIComponent(q));const cols=JSON.parse((typeof d.module==='object'?d.module:{}).columns||'[]'); // Update cache if(window._dhRecordCache)d.records.forEach(r=>{window._dhRecordCache[r.id]=r;}); const tbody=document.querySelector("#fp-data-table tbody");if(!tbody)return;tbody.innerHTML=d.records.length===0?`No results`:d.records.map(r=>`${cols.map(col=>`${r[col.name.toLowerCase().replace(/ /g,'_')]||''}`).join('')}${r.created_at?new Date(r.created_at).toLocaleDateString():''}`).join('');}catch(e){console.error(e);}},300);} // ── Settings Actions ── async function changePin(){const pin=$("#new-pin").value;if(!pin||pin.length<4){toast("PIN must be 4+ chars","error");return}try{await api("/api/admin/settings",{method:"PUT",body:JSON.stringify({network_admin_pin:pin})});toast("PIN updated");$("#new-pin").value="";}catch(e){toast(e.message,"error")}} async function saveSettings(){try{await api("/api/admin/settings",{method:"PUT",body:JSON.stringify({app_version:$("#set-version").value,auto_update:$("#set-autoupdate").value})});toast("Settings saved");}catch(e){toast(e.message,"error")}} // ── Customer Agents Actions ── function showNewCustAgent(){const f=$("#new-cust-agent-form");if(f)f.style.display=f.style.display==='none'?'block':'none'} async function createCustAgent(){try{await api("/api/admin/customer-agents",{method:"POST",body:JSON.stringify({name:$("#ca-name").value,agent_type:$("#ca-type").value,welcome_message:$("#ca-welcome").value,system_prompt:$("#ca-prompt").value,escalate_to:$("#ca-escalate").value||null})});toast("Agent created");loadPage("customer-agents");}catch(e){toast(e.message,"error")}} async function deleteCustAgent(aid){if(!confirm("Delete this customer agent?"))return;try{await api("/api/admin/customer-agents/"+aid,{method:"DELETE"});toast("Agent deleted");loadPage("customer-agents");}catch(e){toast(e.message,"error")}} // ── Channel Actions ── async function toggleChannel(type,active){try{await api("/api/admin/channels/"+type,{method:"PUT",body:JSON.stringify({is_active:active})});toast(type+" "+(active?"enabled":"disabled"));loadPage("channels");}catch(e){toast(e.message,"error")}} async function saveChannelConfig(type,config){try{await api("/api/admin/channels/"+type,{method:"PUT",body:JSON.stringify({is_active:1,config})});toast(type+" config saved");loadPage("channels");}catch(e){toast(e.message,"error")}} function copyWidgetCode(){const el=$("#widget-code-block");if(!el)return;const text=el.textContent.replace(/</g,"<").replace(/>/g,">").replace(/&/g,"&");navigator.clipboard.writeText(text).then(()=>toast("Widget code copied!")).catch(()=>toast("Failed to copy","error"))} function updateWidgetCode(){const pos=$("#widget-position")?.value||"bottom-right";const theme=$("#widget-theme")?.value||"dark";const color=$("#widget-color")?.value||"#0ea5e9";const title=$("#widget-title")?.value||"Chat";const el=$("#widget-code-block");if(el){el.textContent='\n(function(){\n var w=document.createElement("script");\n w.src="'+window.location.origin+'/widget.js";\n w.async=true;\n w.dataset.position="'+pos+'";\n w.dataset.theme="'+theme+'";\n w.dataset.color="'+color+'";\n w.dataset.title="'+title+'";\n document.head.appendChild(w);\n})();\n';}} async function setTelegramWebhook(){const token=$("#tg-token")?.value;if(!token){toast("Enter bot token first","error");return}const webhookUrl=window.location.origin+"/api/webhook/telegram";const el=$("#tg-test-result");el.innerHTML=' Setting webhook...';try{const resp=await fetch("https://api.telegram.org/bot"+token+"/setWebhook",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:webhookUrl})});const d=await resp.json();if(d.ok){el.innerHTML=' Webhook set successfully!'}else{el.innerHTML=` Failed: ${d.description||"Unknown error"}`}}catch(e){el.innerHTML=` ${e.message}`}} async function testTelegram(){const token=$("#tg-token")?.value;if(!token){toast("Enter bot token first","error");return}const el=$("#tg-test-result");el.innerHTML=' Testing...';try{const resp=await fetch("https://api.telegram.org/bot"+token+"/getMe");const d=await resp.json();if(d.ok){el.innerHTML=` Connected! Bot: @${d.result.username} (${d.result.first_name})`}else{el.innerHTML=` Invalid token: ${d.description}`}}catch(e){el.innerHTML=` ${e.message}`}} async function testWhatsApp(){const phone=$("#wa-phone")?.value;const token=$("#wa-token")?.value;if(!phone||!token){toast("Enter Phone Number ID and Access Token first","error");return}const el=$("#wa-test-result");el.innerHTML=' Testing...';try{const resp=await fetch("https://graph.facebook.com/v21.0/"+phone,{headers:{"Authorization":"Bearer "+token}});const d=await resp.json();if(d.id){el.innerHTML=` Connected! Phone: ${d.display_phone_number||d.id} (${d.verified_name||"OK"})`}else{el.innerHTML=` Error: ${d.error?.message||JSON.stringify(d)}`}}catch(e){el.innerHTML=` ${e.message}`}} // ── Feature License Actions ── async function unlockFeature(key){const code=$("#unlock-"+key)?.value;if(!code){toast("Enter unlock code","error");return}try{await api("/api/admin/feature-licenses/unlock",{method:"POST",body:JSON.stringify({feature_key:key,unlock_code:code})});toast("Feature unlocked!");loadPage("feature-licenses");}catch(e){toast(e.message,"error")}} async function lockFeature(key){if(!confirm("Lock this feature?"))return;try{await api("/api/admin/feature-licenses/lock",{method:"POST",body:JSON.stringify({feature_key:key})});toast("Feature locked");loadPage("feature-licenses");}catch(e){toast(e.message,"error")}} // ═══ Init ═══ try{render().catch(function(e){console.error("Render error:",e);document.getElementById("root").innerHTML='

    Failed to load. Please refresh.

    ';});}catch(e){console.error("Init error:",e);document.getElementById("root").innerHTML='

    Error loading. Click to retry

    ';} // ═══ Enhanced Admin Functions ═══ async function adminSuspendTenant(tid){ if(!confirm('Suspend this tenant?'))return; try{ const r=await fetch(API+'/api/demo-admin/tenant/'+tid+'/update',{method:'POST',headers:{'Content-Type':'application/json','X-Admin-Token':_demoAdminToken},body:JSON.stringify({status:'suspended'})}); if(r.ok){toast('Tenant suspended','success');loadAdminData();}else{toast('Failed','error');} }catch(e){toast('Error','error');} } async function adminReactivateTenant(tid){ try{ const r=await fetch(API+'/api/demo-admin/tenant/'+tid+'/update',{method:'POST',headers:{'Content-Type':'application/json','X-Admin-Token':_demoAdminToken},body:JSON.stringify({status:'active'})}); if(r.ok){toast('Reactivated','success');loadAdminData();}else{toast('Failed','error');} }catch(e){toast('Error','error');} } async function adminExtendTenantDays(tid,days){ try{ const r=await fetch(API+'/api/demo-admin/tenant/'+tid+'/approve-extension',{method:'POST',headers:{'Content-Type':'application/json','X-Admin-Token':_demoAdminToken},body:JSON.stringify({days:days})}); if(r.ok){toast('+'+days+' days','success');loadAdminData();}else{const d=await r.json();toast(d.detail||'Failed','error');} }catch(e){toast('Error','error');} } function renderAgentAnalytics(){ const panel=document.getElementById('admin-analytics-panel'); if(!panel||!_adminData)return; const us=_adminData.usage_summary||{}; const tools=us.top_ai_tools||[]; const pages=us.top_pages||[]; const aiCalls=tools.reduce((s,t)=>s+t.count,0); const maxT=Math.max(...tools.map(t=>t.count),1); const maxP=Math.max(...pages.map(p=>p.count),1); panel.innerHTML='
    '+ '

    Agent Intelligence Report

    '+ '
    '+ '
    '+(us.total_events||0)+'
    Total Events
    '+ '
    '+aiCalls+'
    AI Tool Calls
    '+ '
    '+pages.length+'
    Pages Used
    '+ '
    '+tools.length+'
    Unique Tools
    '+ '

    AI Tool Usage

    '+ '
    '+(tools.length===0?'
    No data yet
    ':tools.map(function(t){return '
    '+t.tool+'
    '+t.count+'
    ';}).join(''))+'
    '; } // Override showAdminTab to handle analytics var _origShowAdminTab=typeof showAdminTab==='function'?showAdminTab:null; var _patchedShowTab=function(tab){ var analyticsEl=document.getElementById('admin-analytics-panel'); if(analyticsEl)analyticsEl.style.display='none'; if(tab==='analytics'){ document.getElementById('admin-tenants-list').style.display='none'; document.getElementById('admin-providers-panel').style.display='none'; if(analyticsEl)analyticsEl.style.display='block'; document.querySelectorAll('.admin-tab').forEach(function(b){b.style.background='transparent';b.style.color='#888';b.style.borderBottom='1px solid #1a1a1a';}); var at=document.getElementById('atab-analytics'); if(at){at.style.background='#0a0a0a';at.style.color='#f97316';at.style.borderBottom='2px solid #f97316';} renderAgentAnalytics(); return; } if(_origShowAdminTab)_origShowAdminTab(tab); }; showAdminTab=_patchedShowTab; // ═══ Agent Report Functions ═══ async function loadAgentReport(){ const panel=document.getElementById('admin-report-panel'); if(!panel)return; panel.innerHTML='
    Loading report...
    '; try{ const r=await fetch(API+'/api/demo-admin/agent-report',{headers:{'X-Admin-Token':_demoAdminToken}}); if(!r.ok)throw new Error('Failed'); const d=await r.json(); renderAgentReport(panel,d); }catch(e){panel.innerHTML='
    Failed to load report
    ';} } function renderAgentReport(panel,data){ var h='
    '; h+='

    Agent Interaction Report

    '; h+='
    '; h+='
    '+data.total_tenants+'
    Active Tenants
    '; h+='
    '+data.total_calls+'
    Total Tool Calls
    '; h+='
    '+data.total_failures+'
    Total Failures
    '; h+='
    '; // Per-tenant details (data.tenants||[]).forEach(function(t){ var sc=t.status==='active'?'#f97316':t.status==='expired'?'#aaa':'#f59e0b'; h+='
    '; h+='
    '; h+='
    '+t.company+' '+t.status+'
    '; h+=''+t.users+' users | '+t.chat_sessions+' sessions
    '; h+='
    '; h+=' '+t.total_tool_calls+' calls'; h+=' '+t.total_successes+' success'; h+=' '+t.success_rate+'%'; h+='
    '; // Top tools if(t.top_tools&&t.top_tools.length>0){ h+='
    Top Commands:
    '; t.top_tools.slice(0,5).forEach(function(tool){ var rate=tool.calls>0?Math.round(100*tool.successes/tool.calls):0; h+='
    '; h+=''+tool.tool_name+''; h+=''+tool.calls+'x'; h+=''+rate+'%'; h+='
    '; }); } // Failures if(t.recent_failures&&t.recent_failures.length>0){ h+='
    Recent Failures:
    '; t.recent_failures.slice(0,3).forEach(function(f){ h+='
    '; h+=''+f.tool_name+': '+(f.error_msg||'').substring(0,100)+'
    '; }); } h+='
    '; }); h+='
    '; panel.innerHTML=h; } // Patch showAdminTab for report tab var _origShowTab2=showAdminTab; showAdminTab=function(tab){ var reportEl=document.getElementById('admin-report-panel'); if(reportEl)reportEl.style.display='none'; if(tab==='report'){ document.getElementById('admin-tenants-list').style.display='none'; document.getElementById('admin-providers-panel').style.display='none'; var analyticsEl=document.getElementById('admin-analytics-panel'); if(analyticsEl)analyticsEl.style.display='none'; if(reportEl)reportEl.style.display='block'; document.querySelectorAll('.admin-tab').forEach(function(b){b.style.background='transparent';b.style.color='#888';b.style.borderBottom='1px solid #1a1a1a';}); var at=document.getElementById('atab-report'); if(at){at.style.background='#0a0a0a';at.style.color='#f97316';at.style.borderBottom='2px solid #f97316';} loadAgentReport(); return; } _origShowTab2(tab); };