`;
}
// ═══ 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=`
`;
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.
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)
Your request will be reviewed. You can start with the short trial immediately after approval.
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?
1Admin reviews your request
2Your isolated environment gets provisioned
3You 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 `
`;
}
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=`
`;
}
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
`;
// 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='
`;
// 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
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('');
}
function removeChatFile(idx){chatFiles.splice(idx,1);const previews=document.getElementById("chat-file-previews");previews.innerHTML=chatFiles.map((f,i)=>`
'+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=>'
'+k+'
').join('')+'
'+rows.map(r=>'
'+Object.values(r).map(v=>'
'+v+'
').join('')+'
').join('')+'
';
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 `
Security Setup Required
Please change your default password before continuing
`;
}
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='