Genisi / stati /index.html
AnesKAM's picture
Update stati/index.html
6f676ab verified
Raw
History Blame Contribute Delete
83.8 kB
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Genisi AI</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🤖</text></svg>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;500;600;700;800&family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/marked@12.0.2/marked.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css">
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script>
<style>
*{margin:0;padding:0;box-sizing:border-box;}
:root{
--sidebar-bg:#dde3ea;--sidebar-hover:rgba(0,0,0,0.08);--sidebar-active:rgba(0,0,0,0.10);
--bg:#ffffff;--text:#1f1f1f;--text2:#444746;--muted:#6b6f6e;--divider:#e3e6ea;
--white:#fff;--purple:#7c3aed;--upgrade-bg:#f3e8ff;--card:#fff;--code-bg:#0d1117;
--input-shadow:0 1px 3px rgba(0,0,0,0.10),0 1px 2px rgba(0,0,0,0.06);
--menu-shadow:0 2px 6px 2px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.3);
--sidebar-w:268px;--transition:cubic-bezier(0.2,0,0,1);
}
html[data-theme="dark"]{
--sidebar-bg:#1b1c1d;--sidebar-hover:rgba(255,255,255,0.08);--sidebar-active:rgba(255,255,255,0.12);
--bg:#131314;--text:#e3e3e3;--text2:#c4c7c5;--muted:#9aa0a6;--divider:#3c4043;
--white:#1e1f20;--upgrade-bg:#2d2535;--card:#1e1f20;
}
html,body{height:100%;font-family:'Inter','Cairo',sans-serif;background:var(--bg);color:var(--text);overflow:hidden;}
html[dir="rtl"] body{font-family:'Cairo','Inter',sans-serif;}
.shell{display:flex;height:100vh;position:relative;}
.sidebar{width:var(--sidebar-w);min-width:var(--sidebar-w);background:var(--sidebar-bg);display:flex;flex-direction:column;overflow:hidden;transition:width .25s var(--transition),min-width .25s var(--transition);position:relative;z-index:20;}
.sidebar.closed{width:0;min-width:0;}
.sb-head{display:flex;align-items:center;padding:10px 8px 4px 16px;gap:4px;height:52px;flex-shrink:0;}
.sb-logo{display:flex;align-items:center;gap:9px;flex:1;min-width:0;text-decoration:none;}
.sb-logo img{border-radius:50%;object-fit:cover;flex-shrink:0;}
.sb-logo-text{font-size:18px;font-weight:700;color:var(--text);white-space:nowrap;}
.sb-ico-btn{width:40px;height:40px;border:none;background:transparent;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text2);transition:background .15s;flex-shrink:0;}
.sb-ico-btn:hover{background:var(--sidebar-hover);}
.sb-scroll{flex:1;overflow-y:auto;overflow-x:hidden;padding:4px 0 8px;}
.sb-scroll::-webkit-scrollbar{width:4px;}
.sb-scroll::-webkit-scrollbar-thumb{background:rgba(128,128,128,0.4);border-radius:2px;}
.sb-nav{padding:4px 8px 0;}
.sb-item{display:flex;align-items:center;gap:12px;padding:10px 14px;border-radius:28px;cursor:pointer;font-size:14px;font-weight:500;color:var(--text);white-space:nowrap;transition:background .15s;user-select:none;}
.sb-item:hover{background:var(--sidebar-hover);}
.sb-item svg{flex-shrink:0;color:var(--text2);}
.sb-section{font-size:11px;font-weight:600;color:var(--muted);padding:14px 16px 4px;letter-spacing:.6px;text-transform:uppercase;white-space:nowrap;}
.recent-wrap{padding:0 8px;}
.recent-item{display:flex;align-items:center;padding:8px 14px;border-radius:28px;cursor:pointer;font-size:13.5px;color:var(--text);white-space:nowrap;gap:6px;transition:background .15s;}
.recent-item:hover{background:var(--sidebar-hover);}
.recent-item.active{background:var(--sidebar-active);}
.recent-item .ri-text{flex:1;overflow:hidden;text-overflow:ellipsis;}
.recent-item .ri-more{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0;opacity:0;transition:opacity .15s,background .15s;color:var(--text2);}
.recent-item:hover .ri-more{opacity:1;}
.recent-item .ri-more:hover{background:rgba(128,128,128,0.25);}
.sb-bottom{flex-shrink:0;padding:6px 8px 10px;border-top:1px solid var(--divider);}
.sb-settings{display:flex;align-items:center;gap:12px;padding:10px 14px;border-radius:28px;cursor:pointer;font-size:14px;font-weight:500;color:var(--text);white-space:nowrap;transition:background .15s;}
.sb-settings:hover{background:var(--sidebar-hover);}
.sb-settings svg{color:var(--text2);}
.sb-user{display:flex;align-items:center;gap:10px;padding:8px 14px;border-radius:28px;cursor:pointer;transition:background .15s;}
.sb-user:hover{background:var(--sidebar-hover);}
.avatar{width:30px;height:30px;border-radius:50%;background:var(--purple);color:#fff;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:700;flex-shrink:0;}
.sb-user-name{font-size:14px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.main{flex:1;display:flex;flex-direction:column;min-width:0;position:relative;background:var(--bg);}
.topbar{position:absolute;top:0;inset-inline:0;height:52px;z-index:15;display:flex;align-items:center;padding:0 8px;pointer-events:none;opacity:0;transition:opacity .2s;}
.topbar.show{opacity:1;pointer-events:all;}
.tb-left{display:flex;align-items:center;gap:0;}
.tb-btn{width:40px;height:40px;border:none;background:transparent;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text2);transition:background .15s;}
.tb-btn:hover{background:var(--sidebar-hover);}
.home{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:0 20px 80px;position:relative;overflow:hidden;}
.home::before{content:'';position:absolute;top:50%;left:50%;transform:translate(-50%,-55%);width:900px;height:700px;background:radial-gradient(ellipse 50% 45% at 50% 50%,rgba(168,85,247,.30) 0%,rgba(168,85,247,.15) 30%,transparent 60%),radial-gradient(ellipse 40% 50% at 55% 45%,rgba(147,112,219,.18) 0%,transparent 65%),radial-gradient(ellipse 45% 40% at 45% 60%,rgba(196,124,255,.16) 0%,transparent 70%);filter:blur(90px);pointer-events:none;z-index:0;}
.greeting{font-size:38px;font-weight:600;color:var(--text);margin-bottom:28px;text-align:center;position:relative;z-index:1;animation:greet .45s ease both;}
@keyframes greet{from{opacity:0;transform:translateY(14px);}to{opacity:1;transform:translateY(0);}}
.input-wrap{width:100%;max-width:720px;position:relative;z-index:1;}
.input-stack{display:flex;flex-direction:column;width:100%;}
.attachments{display:flex;flex-wrap:wrap;gap:8px;padding:0 4px 8px;}
.att-chip{display:flex;align-items:center;gap:8px;background:var(--upgrade-bg);color:var(--purple);border-radius:12px;padding:6px 10px;font-size:13px;max-width:220px;}
.att-chip img{width:32px;height:32px;border-radius:6px;object-fit:cover;flex-shrink:0;}
.att-chip .att-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.att-chip .att-x{cursor:pointer;border:none;background:transparent;color:inherit;font-weight:700;font-size:16px;line-height:1;}
.input-box{background:var(--card);border-radius:28px;box-shadow:var(--input-shadow);display:flex;align-items:flex-end;padding:10px 16px;gap:4px;border:1.5px solid var(--divider);transition:box-shadow .2s,border-color .2s;}
.input-box:focus-within{border-color:transparent;box-shadow:0 0 28px rgba(124,58,237,.25),0 0 60px rgba(168,85,247,.12);}
.input-plus,.mic-btn{width:36px;height:36px;border:none;background:transparent;border-radius:50%;cursor:pointer;flex-shrink:0;display:flex;align-items:center;justify-content:center;color:var(--text2);transition:background .15s;}
.input-plus:hover,.mic-btn:hover{background:var(--sidebar-hover);}
.mic-btn.recording{background:#e8453c;color:#fff;}
.input-ta{flex:1;border:none;outline:none;background:transparent;font-family:inherit;font-size:16px;color:var(--text);line-height:1.5;resize:none;max-height:180px;min-height:24px;padding:7px 6px;scrollbar-width:none;}
.input-ta::-webkit-scrollbar{display:none;}
.input-ta::placeholder{color:var(--muted);}
.input-right{display:flex;align-items:center;gap:2px;flex-shrink:0;padding-bottom:2px;}
.model-btn{display:flex;align-items:center;gap:3px;padding:7px 10px;border-radius:20px;border:none;background:transparent;font-family:inherit;font-size:14px;color:var(--text2);font-weight:600;cursor:pointer;white-space:nowrap;transition:background .15s;}
.model-btn:hover{background:var(--sidebar-hover);}
.send-btn{width:36px;height:36px;border:none;background:var(--purple);border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#fff;transition:background .15s,transform .1s;}
.send-btn:hover{background:#6d28d9;}
.send-btn:active{transform:scale(.93);}
.send-btn:disabled{background:#c4c7c5;cursor:default;}
.send-btn.stop{background:#1f1f1f;}
html[data-theme="dark"] .send-btn.stop{background:#e3e3e3;color:#1f1f1f;}
.search-pane{position:absolute;inset:0;background:var(--bg);z-index:30;display:none;flex-direction:column;padding:12px 60px 20px;}
.search-pane.open{display:flex;}
.sp-top{display:flex;align-items:center;gap:12px;height:56px;flex-shrink:0;}
.sp-input-wrap{flex:1;display:flex;align-items:center;gap:10px;background:var(--card);border:1.5px solid var(--divider);border-radius:26px;padding:10px 16px;box-shadow:var(--input-shadow);}
.sp-input{flex:1;border:none;outline:none;background:transparent;font-size:16px;font-family:inherit;color:var(--text);}
.sp-x{border:none;background:transparent;cursor:pointer;color:var(--muted);display:flex;}
.sp-sect{font-size:12px;font-weight:600;color:var(--muted);padding:16px 4px 6px;text-transform:uppercase;}
.sp-results{flex:1;overflow-y:auto;}
.sp-row{display:flex;align-items:center;gap:14px;padding:11px 12px;border-radius:12px;cursor:pointer;font-size:14px;color:var(--text);transition:background .12s;}
.sp-row:hover{background:var(--sidebar-hover);}
.sp-row .sr-text{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.sp-row .sr-date{font-size:13px;color:var(--muted);white-space:nowrap;}
.sp-row.hide{display:none;}
.settings-panel{position:fixed;top:0;inset-inline-end:0;bottom:0;width:380px;max-width:90vw;background:var(--card);box-shadow:-2px 0 10px rgba(0,0,0,0.15);z-index:100;transform:translateX(100%);transition:transform .3s var(--transition);display:flex;flex-direction:column;overflow:hidden;}
html[dir="rtl"] .settings-panel{transform:translateX(-100%);}
.settings-panel.open{transform:translateX(0)!important;}
.settings-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--divider);}
.settings-title{font-size:16px;font-weight:700;}
.settings-close{width:36px;height:36px;border:none;background:transparent;border-radius:50%;cursor:pointer;color:var(--text2);display:flex;align-items:center;justify-content:center;}
.settings-close:hover{background:var(--sidebar-hover);}
.settings-content{flex:1;overflow-y:auto;padding:20px;}
.settings-group{margin-bottom:24px;}
.settings-label{font-size:14px;font-weight:700;margin-bottom:12px;display:block;}
.settings-option{display:flex;align-items:center;gap:12px;padding:12px;border-radius:12px;cursor:pointer;transition:background .15s;}
.settings-option:hover{background:var(--sidebar-hover);}
.settings-option input{width:18px;height:18px;cursor:pointer;accent-color:var(--purple);}
.option-text{font-size:14px;flex:1;}
.settings-divider{height:1px;background:var(--divider);margin:16px 0;}
.btn-purple{width:100%;padding:11px;background:var(--upgrade-bg);color:var(--purple);border:none;border-radius:10px;font-weight:700;cursor:pointer;font-family:inherit;}
.overlay{position:fixed;inset:0;background:rgba(0,0,0,0);z-index:50;opacity:0;pointer-events:none;transition:opacity .3s;}
.overlay.active{opacity:1;pointer-events:all;background:rgba(0,0,0,0.4);}
.chat-view{flex:1;display:none;flex-direction:column;min-height:0;}
.shell.chat .home{display:none;}
.shell.chat .chat-view{display:flex;}
.msgs{flex:1;overflow-y:auto;padding:70px 0 12px;}
.msgs::-webkit-scrollbar{width:6px;}
.msgs::-webkit-scrollbar-thumb{background:var(--divider);border-radius:3px;}
.msgs-inner{max-width:760px;width:100%;margin:0 auto;padding:0 20px;display:flex;flex-direction:column;gap:20px;}
.msg-user{align-self:flex-end;background:var(--upgrade-bg);color:var(--text);padding:12px 18px;border-radius:20px 20px 5px 20px;max-width:78%;font-size:15px;line-height:1.6;word-break:break-word;white-space:pre-wrap;}
html[dir="rtl"] .msg-user{border-radius:20px 20px 20px 5px;align-self:flex-start;}
.msg-ai{align-self:flex-start;max-width:92%;width:100%;}
html[dir="rtl"] .msg-ai{align-self:flex-end;}
.msg-ai-body{font-size:15.5px;line-height:1.75;color:var(--text);}
.msg-ai-body p{margin:0 0 12px;}
.msg-ai-body ul,.msg-ai-body ol{margin:0 0 12px;padding-inline-start:24px;}
.msg-ai-body h1,.msg-ai-body h2,.msg-ai-body h3{margin:14px 0 8px;}
.msg-ai-body a{color:var(--purple);}
.msg-ai-body blockquote{border-inline-start:3px solid var(--purple);padding-inline-start:12px;color:var(--muted);margin:0 0 12px;}
.msg-ai-body table{border-collapse:collapse;width:100%;margin:12px 0;font-size:14px;border-radius:8px;overflow:hidden;}
.msg-ai-body th,.msg-ai-body td{border:1px solid var(--divider);padding:8px 12px;text-align:start;}
.msg-ai-body th{background:var(--upgrade-bg);color:var(--purple);font-weight:700;}
.msg-ai-body tr:nth-child(even) td{background:rgba(124,58,237,0.04);}
.streaming{white-space:pre-wrap;}
.gen-img{max-width:100%;border-radius:14px;margin:8px 0;display:block;}
.img-actions{display:flex;gap:8px;margin:4px 0 8px;}
.code-wrap{position:relative;margin:12px 0;border-radius:12px;overflow:hidden;border:1px solid var(--divider);direction:ltr;}
.code-bar{display:flex;align-items:center;justify-content:space-between;background:#161b22;color:#c9d1d9;padding:6px 12px;font-size:12px;font-family:monospace;}
.copy-btn{background:transparent;border:1px solid #30363d;color:#c9d1d9;border-radius:6px;padding:3px 10px;font-size:12px;cursor:pointer;font-family:inherit;}
.copy-btn:hover{background:#21262d;}
.code-wrap pre{margin:0;}
.code-wrap pre code{display:block;padding:14px;background:var(--code-bg);overflow-x:auto;font-family:'Consolas',monospace;font-size:13.5px;line-height:1.6;}
.dots span{display:inline-block;width:6px;height:6px;border-radius:50%;margin:0 2px;background:var(--text);animation:dot 1.2s infinite;}
.dots span:nth-child(2){animation-delay:.18s;}
.dots span:nth-child(3){animation-delay:.36s;}
@keyframes dot{0%,80%,100%{transform:scale(.6);opacity:.4;}40%{transform:scale(1);opacity:1;}}
.worm-square-wrap{display:flex;align-items:center;padding:8px 4px;}
.worm-square{position:relative;width:32px;height:22px;}
.worm-square span{position:absolute;width:5.5px;height:5.5px;border-radius:50%;background:var(--purple);box-shadow:0 0 6px rgba(124,58,237,0.5);transition:background 0.3s,box-shadow 0.3s;}
html[data-theme="dark"] .worm-square span{box-shadow:0 0 8px rgba(196,124,255,0.6);}
.worm-square span:nth-child(1){animation:wormDot1 2.8s infinite cubic-bezier(0.4,0,0.2,1);}
.worm-square span:nth-child(2){animation:wormDot2 2.8s infinite cubic-bezier(0.4,0,0.2,1);}
.worm-square span:nth-child(3){animation:wormDot3 2.8s infinite cubic-bezier(0.4,0,0.2,1);}
.worm-square span:nth-child(4){animation:wormDot4 2.8s infinite cubic-bezier(0.4,0,0.2,1);}
@keyframes wormDot1{0%{left:25px;top:8px;transform:scale(1);}12%{left:24px;top:2px;transform:scale(1.15);}24%{left:22px;top:8px;transform:scale(1);}36%{left:20px;top:8px;transform:scale(1);}48%{left:18px;top:3px;transform:scale(1);}66%{left:18px;top:13px;transform:scale(1.25);background:#a855f7;box-shadow:0 0 12px #a855f7;}84%{left:22px;top:10px;transform:scale(1.05);}100%{left:25px;top:8px;transform:scale(1);}}
@keyframes wormDot2{0%{left:17px;top:8px;transform:scale(1);}12%{left:16px;top:8px;transform:scale(1);}24%{left:15px;top:2px;transform:scale(1.15);}36%{left:13px;top:8px;transform:scale(1);}48%{left:8px;top:3px;transform:scale(1);}66%{left:18px;top:3px;transform:scale(1.25);background:#a855f7;box-shadow:0 0 12px #a855f7;}84%{left:18px;top:6px;transform:scale(1.05);}100%{left:17px;top:8px;transform:scale(1);}}
@keyframes wormDot3{0%{left:9px;top:8px;transform:scale(1);}12%{left:9px;top:8px;transform:scale(1);}24%{left:9px;top:8px;transform:scale(1);}36%{left:9px;top:2px;transform:scale(1.15);}48%{left:8px;top:13px;transform:scale(1);}66%{left:8px;top:3px;transform:scale(1.25);background:#a855f7;box-shadow:0 0 12px #a855f7;}84%{left:8px;top:6px;transform:scale(1.05);}100%{left:9px;top:8px;transform:scale(1);}}
@keyframes wormDot4{0%{left:1px;top:8px;transform:scale(1);}12%{left:2px;top:8px;transform:scale(1);}24%{left:3px;top:8px;transform:scale(1);}36%{left:4px;top:8px;transform:scale(1);}48%{left:18px;top:13px;transform:scale(1);}66%{left:8px;top:13px;transform:scale(1.25);background:#a855f7;box-shadow:0 0 12px #a855f7;}84%{left:4px;top:10px;transform:scale(1.05);}100%{left:1px;top:8px;transform:scale(1);}}
.pro-thinking-wrap{display:flex;align-items:center;padding:6px 0;}
.pro-thinking{display:inline-flex;align-items:center;gap:10px;padding:8px 18px;background:linear-gradient(135deg,rgba(124,58,237,0.1),rgba(168,85,247,0.05));border:1px solid rgba(124,58,237,0.3);border-radius:24px;animation:proBoxPulse 2.2s infinite ease-in-out;box-shadow:0 0 15px rgba(124,58,237,0.15);}
html[data-theme="dark"] .pro-thinking{background:linear-gradient(135deg,rgba(168,85,247,0.15),rgba(124,58,237,0.08));border-color:rgba(196,124,255,0.35);}
.pro-glow-icon{color:#a855f7;display:flex;align-items:center;justify-content:center;animation:proIconGlow 2.2s infinite ease-in-out;}
.pro-glow-text{font-size:14.5px;font-weight:700;background:linear-gradient(90deg,#7c3aed,#c084fc,#7c3aed);background-size:200% auto;-webkit-background-clip:text;-webkit-text-fill-color:transparent;animation:proTextShimmer 2.2s infinite linear,proTextGlow 2.2s infinite ease-in-out;letter-spacing:0.4px;}
@keyframes proBoxPulse{0%,100%{box-shadow:0 0 12px rgba(124,58,237,0.15),inset 0 0 8px rgba(124,58,237,0.05);transform:scale(0.99);border-color:rgba(124,58,237,0.25);}50%{box-shadow:0 0 25px rgba(168,85,247,0.45),0 0 45px rgba(124,58,237,0.25),inset 0 0 15px rgba(168,85,247,0.15);transform:scale(1.02);border-color:rgba(196,124,255,0.7);}}
@keyframes proTextGlow{0%,100%{filter:drop-shadow(0 0 4px rgba(124,58,237,0.4));}50%{filter:drop-shadow(0 0 12px rgba(196,124,255,0.9)) drop-shadow(0 0 20px rgba(168,85,247,0.6));}}
@keyframes proTextShimmer{to{background-position:200% center;}}
@keyframes proIconGlow{0%,100%{transform:scale(0.95) rotate(0deg);filter:drop-shadow(0 0 4px rgba(168,85,247,0.4));}50%{transform:scale(1.15) rotate(180deg);filter:drop-shadow(0 0 14px rgba(196,124,255,0.9));}}
.retry-banner{display:flex;align-items:center;gap:10px;padding:10px 16px;margin:8px 0;background:linear-gradient(135deg,rgba(245,158,11,0.1),rgba(245,158,11,0.05));border:1px solid rgba(245,158,11,0.3);border-radius:14px;font-size:13px;color:#b45309;animation:retryPulse 2s infinite ease-in-out;}
html[data-theme="dark"] .retry-banner{color:#fbbf24;border-color:rgba(245,158,11,0.4);background:linear-gradient(135deg,rgba(245,158,11,0.15),rgba(245,158,11,0.05));}
.retry-banner svg{flex-shrink:0;animation:spin 2s linear infinite;}
@keyframes spin{to{transform:rotate(360deg);}}
@keyframes retryPulse{0%,100%{opacity:0.85;}50%{opacity:1;}}
.suggests{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px;}
.suggest{background:var(--card);border:1px solid var(--divider);color:var(--text);border-radius:18px;padding:8px 16px;font-size:14px;cursor:pointer;font-family:inherit;transition:background .15s;text-decoration:none;display:inline-block;}
.suggest:hover{background:var(--sidebar-hover);}
.chat-input-bar{padding:10px 20px 18px;display:none;justify-content:center;flex-shrink:0;}
.shell.chat .chat-input-bar{display:flex;}
.model-menu{position:absolute;bottom:calc(100% + 6px);inset-inline-end:0;background:var(--card);border-radius:14px;box-shadow:var(--menu-shadow);padding:6px;min-width:200px;z-index:200;display:none;}
.model-menu.open{display:block;}
.mm-item{display:flex;flex-direction:column;padding:10px 14px;border-radius:10px;cursor:pointer;}
.mm-item:hover{background:var(--sidebar-hover);}
.mm-item b{font-size:14px;}
.mm-item small{font-size:12px;color:var(--muted);}
.modal{position:fixed;inset:0;z-index:300;display:none;align-items:center;justify-content:center;background:rgba(0,0,0,0.5);padding:20px;}
.modal.open{display:flex;}
.modal-card{background:var(--card);border-radius:24px;max-width:460px;width:100%;padding:32px;box-shadow:0 20px 60px rgba(0,0,0,0.3);max-height:90vh;overflow-y:auto;}
.modal-card h2{font-size:24px;font-weight:700;margin-bottom:8px;text-align:center;}
.modal-card p.sub{color:var(--muted);text-align:center;margin-bottom:24px;font-size:14px;}
.field{margin-bottom:16px;}
.field label{display:block;font-size:14px;font-weight:600;margin-bottom:6px;}
.field input,.field select{width:100%;padding:12px 14px;border:1.5px solid var(--divider);border-radius:12px;background:var(--bg);color:var(--text);font-family:inherit;font-size:15px;outline:none;}
.field input:focus,.field select:focus{border-color:var(--purple);}
.gender-row{display:flex;gap:8px;}
.gender-opt{flex:1;text-align:center;padding:12px 6px;border:1.5px solid var(--divider);border-radius:12px;cursor:pointer;font-size:13px;font-weight:600;transition:all .15s;}
.gender-opt.sel{border-color:var(--purple);background:var(--upgrade-bg);color:var(--purple);}
.modal-btn{width:100%;padding:14px;background:var(--purple);color:#fff;border:none;border-radius:14px;font-size:16px;font-weight:700;cursor:pointer;font-family:inherit;margin-top:8px;}
.modal-btn:hover{background:#6d28d9;}
.howto h3{font-size:16px;font-weight:700;margin:16px 0 6px;color:var(--purple);}
.howto ul{padding-inline-start:20px;line-height:1.9;font-size:14px;}
.brand-star{display:block;margin:0 auto 12px;border-radius:50%;object-fit:cover;}
.doc-preview{border:1px solid var(--divider);border-radius:14px;background:#fff;padding:28px 32px;margin:12px 0;box-shadow:0 2px 14px rgba(124,58,237,.08);max-height:520px;overflow-y:auto;}
.doc-paper{font-family:'Inter','Cairo',sans-serif;color:#1f1f1f;line-height:1.7;}
.doc-title{font-size:26px;font-weight:700;color:#7c3aed;text-align:center;border-bottom:3px solid #7c3aed;padding-bottom:12px;margin-bottom:18px;}
.doc-paper h2{font-size:20px;color:#7c3aed;margin:16px 0 8px;}
.doc-paper h3{font-size:17px;color:#7c3aed;margin:14px 0 6px;}
.doc-paper p{margin:0 0 10px;font-size:15px;}
.doc-paper ul{padding-inline-start:22px;margin:0 0 10px;}
.doc-paper li{margin:4px 0;font-size:15px;}
.doc-paper table{border-collapse:collapse;width:100%;margin:12px 0;}
.doc-paper th{background:#7c3aed;color:#fff;padding:8px 12px;text-align:start;}
.doc-paper td{border:1px solid #e3e6ea;padding:8px 12px;}
.doc-slide{border:1px solid #eadcff;border-radius:12px;padding:16px 18px;margin:12px 0;position:relative;background:linear-gradient(135deg,#faf5ff,#fff);animation:slideIn .4s ease both;}
@keyframes slideIn{from{opacity:0;transform:translateY(10px);}to{opacity:1;transform:translateY(0);}}
.doc-slide-n{position:absolute;top:12px;inset-inline-end:14px;width:24px;height:24px;border-radius:50%;background:#7c3aed;color:#fff;font-size:12px;display:flex;align-items:center;justify-content:center;font-weight:700;}
.doc-dl{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin:6px 0 4px;}
.doc-dl-label{font-size:13px;color:var(--muted);font-weight:600;}
.doc-dl-btn{background:var(--upgrade-bg);color:var(--purple);border:1px solid transparent;border-radius:14px;padding:7px 16px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;transition:background .15s;}
.doc-dl-btn:hover{background:#e9d5ff;}
.doc-dl-btn:disabled{opacity:.6;cursor:default;}
.video-box{border:1px solid var(--divider);border-radius:16px;overflow:hidden;margin:12px 0;background:#000;}
.video-stage{position:relative;width:100%;background:#000;display:flex;align-items:center;justify-content:center;}
.video-stage video{width:100%;display:block;}
.video-ctrl{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--card);flex-wrap:wrap;}
.video-ctrl button{display:flex;align-items:center;gap:6px;background:var(--upgrade-bg);color:var(--purple);border:none;border-radius:12px;padding:8px 16px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;transition:background .15s;}
.video-ctrl button:hover{background:#e9d5ff;}
.video-ctrl button:disabled{opacity:.55;cursor:default;}
.video-ctrl .vstatus{font-size:13px;color:var(--muted);margin-inline-start:auto;}
svg{user-select:none;}
button svg{pointer-events:none;}
</style>
</head>
<body>
<div class="shell" id="shell">
<div class="sidebar" id="sidebar">
<div class="sb-head">
<a class="sb-logo" href="#" onclick="return false;">
<img src="https://qph.cf2.poecdn.net/main-thumb-pb-6867722-200-mqrjiwnfubtpxjfdovmmfgdaicgosphq.jpeg" alt="Genisi" width="30" height="30" onerror="this.style.display='none'">
<span class="sb-logo-text">Genisi</span>
</a>
<button class="sb-ico-btn" id="btnCloseSidebar">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="3" width="18" height="18" rx="2.5"/><line x1="9" y1="3" x2="9" y2="21"/></svg>
</button>
</div>
<div class="sb-scroll">
<div class="sb-nav">
<div class="sb-item" id="btnNewChatSB">
<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
<span data-i18n="newChat">New chat</span>
</div>
<div class="sb-item" id="btnOpenSearchSB">
<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
<span data-i18n="searchChats">Search chats</span>
</div>
</div>
<div class="sb-section" data-i18n="recent">Recent</div>
<div class="recent-wrap" id="recentWrap"></div>
</div>
<div class="sb-bottom">
<div class="sb-settings" id="btnOpenSettings">
<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9.4 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
<span data-i18n="settings">Settings</span>
</div>
<div class="sb-user" id="btnOpenHowto">
<div class="avatar" id="userAvatar">G</div>
<span class="sb-user-name" id="userName">Genisi user</span>
</div>
</div>
</div>
<div class="main" id="main">
<div class="topbar" id="topbar">
<div class="tb-left">
<button class="tb-btn" id="btnOpenSidebarTop">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="3" width="18" height="18" rx="2.5"/><line x1="9" y1="3" x2="9" y2="21"/></svg>
</button>
<button class="tb-btn" id="btnNewChatTop">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
</button>
<button class="tb-btn" id="btnOpenSearchTop">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
</button>
</div>
</div>
<div class="home" id="homeView">
<h1 class="greeting" id="greeting">How can I help you?</h1>
<div class="input-wrap">
<div class="input-stack">
<div class="attachments" id="homeAtts"></div>
<div class="input-box">
<button class="input-plus" id="homeUpload">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
</button>
<textarea class="input-ta" id="homeTA" data-ph="askGenisi" placeholder="Ask Genisi" rows="1"></textarea>
<div class="input-right">
<div style="position:relative">
<button class="model-btn" id="modelBtnHome">
<span class="modelLabel">Flash</span>
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><polyline points="6 9 12 15 18 9"/></svg>
</button>
<div class="model-menu" id="modelMenuHome">
<div class="mm-item" data-model="flash"><b>Flash</b><small data-i18n="flashDesc">Fast &amp; smart</small></div>
<div class="mm-item" data-model="pro"><b>Pro</b><small data-i18n="proDesc">More accurate</small></div>
</div>
</div>
<button class="mic-btn" id="homeMic">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg>
</button>
<button class="send-btn" id="homeSend" disabled>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="20" x2="12" y2="5"/><polyline points="6 11 12 5 18 11"/></svg>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="chat-view" id="chatView">
<div class="msgs" id="msgs"><div class="msgs-inner" id="msgsInner"></div></div>
<div class="chat-input-bar">
<div class="input-wrap">
<div class="input-stack">
<div class="attachments" id="chatAtts"></div>
<div class="input-box">
<button class="input-plus" id="chatUpload">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
</button>
<textarea class="input-ta" id="chatTA" data-ph="askGenisi" placeholder="Ask Genisi" rows="1"></textarea>
<div class="input-right">
<div style="position:relative">
<button class="model-btn" id="modelBtnChat">
<span class="model-label">Flash</span>
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><polyline points="6 9 12 15 18 9"/></svg>
</button>
<div class="model-menu" id="modelMenuChat">
<div class="mm-item" data-model="flash"><b>Flash</b><small data-i18n="flashDesc">Fast &amp; smart</small></div>
<div class="mm-item" data-model="pro"><b>Pro</b><small data-i18n="proDesc">More accurate</small></div>
</div>
</div>
<button class="mic-btn" id="chatMic">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg>
</button>
<button class="send-btn" id="chatSend" disabled>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="20" x2="12" y2="5"/><polyline points="6 11 12 5 18 11"/></svg>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="search-pane" id="searchPane">
<div class="sp-top">
<div class="sp-input-wrap">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
<input class="sp-input" id="spInput" data-ph="searchChats" placeholder="Search chats" autocomplete="off">
<button class="sp-x" id="btnCloseSearch">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
</div>
</div>
<div class="sp-sect" data-i18n="recent">Recent</div>
<div class="sp-results" id="spResults"></div>
</div>
</div>
<div class="settings-panel" id="settingsPanel">
<div class="settings-header">
<h2 class="settings-title" data-i18n="settings">Settings</h2>
<button class="settings-close" id="btnCloseSettings">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
</div>
<div class="settings-content">
<div class="settings-group">
<label class="settings-label" data-i18n="theme">Theme</label>
<label class="settings-option"><input type="radio" name="theme" value="light"><span class="option-text" data-i18n="light">Light</span></label>
<label class="settings-option"><input type="radio" name="theme" value="dark"><span class="option-text" data-i18n="dark">Dark</span></label>
<label class="settings-option"><input type="radio" name="theme" value="auto"><span class="option-text" data-i18n="auto">Auto</span></label>
</div>
<div class="settings-divider"></div>
<div class="settings-group">
<label class="settings-label" data-i18n="language">Language</label>
<label class="settings-option"><input type="radio" name="lang" value="en"><span class="option-text">English</span></label>
<label class="settings-option"><input type="radio" name="lang" value="ar"><span class="option-text">العربية</span></label>
<label class="settings-option"><input type="radio" name="lang" value="fr"><span class="option-text">Français</span></label>
</div>
<div class="settings-divider"></div>
<div class="settings-group">
<label class="settings-label" data-i18n="defaultModel">Default model</label>
<label class="settings-option"><input type="radio" name="dmodel" value="flash"><span class="option-text">Flash</span></label>
<label class="settings-option"><input type="radio" name="dmodel" value="pro"><span class="option-text">Pro</span></label>
</div>
<div class="settings-divider"></div>
<div class="settings-group">
<label class="settings-label" data-i18n="voice">Voice</label>
<label class="settings-option"><input type="checkbox" id="ttsAuto"><span class="option-text" data-i18n="autoTts">Auto-play audio</span></label>
</div>
<div class="settings-divider"></div>
<div class="settings-group"><button class="btn-purple" id="btnHowtoFromSettings" data-i18n="howtoBtn">How to use Genisi?</button></div>
<div class="settings-group"><button class="btn-purple" style="background:#fde8e8;color:#d33" id="btnClearAll" data-i18n="clearBtn">Clear all chats and data</button></div>
</div>
</div>
<div class="overlay" id="overlay"></div>
<div class="modal" id="welcomeModal">
<div class="modal-card">
<img class="brand-star" src="https://qph.cf2.poecdn.net/main-thumb-pb-6867722-200-mqrjiwnfubtpxjfdovmmfgdaicgosphq.jpeg" alt="Genisi" width="56" height="56" onerror="this.style.display='none'">
<h2 data-i18n="welcome">Welcome to Genisi</h2>
<p class="sub" data-i18n="welcomeSub">Let's personalize your experience. Your data is stored only in your browser for privacy.</p>
<div class="field"><label data-i18n="name">Name</label><input id="obName" data-ph="yourName" placeholder="Your name"></div>
<div class="field"><label data-i18n="dob">Date of birth</label><input id="obDob" type="date"></div>
<div class="field">
<label data-i18n="gender">Gender</label>
<div class="gender-row">
<div class="gender-opt" data-g="Male" data-i18n="male">Male</div>
<div class="gender-opt" data-g="Female" data-i18n="female">Female</div>
<div class="gender-opt" data-g="" data-i18n="noSay">Prefer not to say</div>
</div>
</div>
<button class="modal-btn" id="btnConfirmOnboard" data-i18n="confirm">Confirm &amp; continue</button>
</div>
</div>
<div class="modal" id="howtoModal">
<div class="modal-card howto">
<img class="brand-star" src="https://qph.cf2.poecdn.net/main-thumb-pb-6867722-200-mqrjiwnfubtpxjfdovmmfgdaicgosphq.jpeg" alt="Genisi" width="56" height="56" onerror="this.style.display='none'">
<h2 data-i18n="howtoTitle">How to use Genisi</h2>
<p class="sub" id="howtoIntro"></p>
<div id="howtoBody"></div>
<button class="modal-btn" id="btnCloseHowto" data-i18n="getStarted">Get started</button>
</div>
</div>
<script>
// ===================== Safe JSON parse helper =====================
function safeJSON(response) {
return response.text().then(function(txt) {
if (!txt || !txt.trim()) return {};
try { return JSON.parse(txt); } catch(e) { return {}; }
});
}
// ===================== localStorage helper (safe for tracking prevention) =====================
var LS = {
get: function(k, def) {
try { var v = localStorage.getItem(k); return v !== null ? JSON.parse(v) : def; }
catch(e) { return def; }
},
set: function(k, v) {
try { localStorage.setItem(k, JSON.stringify(v)); } catch(e) {}
},
clear: function() {
try { localStorage.clear(); } catch(e) {}
}
};
// ===================== i18n =====================
var I18N = {
en: {
newChat:"New chat",searchChats:"Search chats",recent:"Recent",settings:"Settings",
askGenisi:"Ask Genisi",theme:"Theme",light:"Light",dark:"Dark",auto:"Auto",
language:"Language",defaultModel:"Default model",voice:"Voice",autoTts:"Auto-play audio",
howtoBtn:"How to use Genisi?",clearBtn:"Clear all chats and data",
flashDesc:"Fast & smart",proDesc:"More accurate",
welcome:"Welcome to Genisi",
welcomeSub:"Let's personalize your experience. Your data is stored only in your browser for privacy.",
name:"Name",yourName:"Your name",dob:"Date of birth",gender:"Gender",
male:"Male",female:"Female",noSay:"Prefer not to say",confirm:"Confirm & continue",
howtoTitle:"How to use Genisi",getStarted:"Get started",
thinking:"Thinking...",showThinking:"Show thinking",
generatingImage:"Generating image...",searching:"Searching...",
searchingImages:"Finding images...",creatingDoc:"Creating document...",
downloadDoc:"Download as",downloadImg:"Download image",imagesLeft:"left",
errConnect:"Connection failed",stopped:"Response stopped",newConvo:"New conversation",
micUnsupported:"Your browser doesn't support voice input. Use Chrome.",
confirmClear:"Clear all chats and data? This cannot be undone.",
cutoff:"Response was too long and got cut off. Retry or ask for shorter content.",
unsupportedFile:"This file type isn't supported by the current model.",
playVideo:"Play",exportVideo:"Download MP4",
buildingVideo:"Generating video (this can take a while)...",
recording:"Recording...",videoReady:"Video ready",
retrying:"Retrying in",retryAttempt:"Attempt",retrySeconds:"seconds",
intro:'Genisi is an Algerian AI developed by the <b>AnesNT</b> project, currently owned by <b>Anes Kameche</b>.',
howto:'<h3>Chat</h3><ul><li>Type your question and switch between <b>Flash</b> (fast) and <b>Pro</b> (accurate) anytime.</li><li>Code blocks with copy, tables, and math.</li></ul><h3>Images</h3><ul><li>Ask for an image, generated with high realism. Download with one click.</li><li>Limit: 3 images every 3 hours.</li></ul><h3>Documents</h3><ul><li>Preview Word, PowerPoint, Excel and PDF, then download in any format.</li></ul><h3>Video</h3><ul><li>Ask for a short AI-generated video, then download it as MP4.</li></ul><h3>Files & Voice</h3><ul><li>Upload files, voice dictation, and read responses aloud.</li></ul>'
},
ar: {
newChat:"محادثة جديدة",searchChats:"البحث في المحادثات",recent:"الأخيرة",settings:"الإعدادات",
askGenisi:"اسأل Genisi",theme:"المظهر",light:"فاتح",dark:"داكن",auto:"تلقائي",
language:"اللغة",defaultModel:"النموذج الافتراضي",voice:"الصوت",autoTts:"تشغيل تلقائي للصوت",
howtoBtn:"كيف تستخدم Genisi؟",clearBtn:"مسح كل المحادثات والبيانات",
flashDesc:"سريع وذكي",proDesc:"أدق وأقوى",
welcome:"مرحباً بك في Genisi",
welcomeSub:"لنخصّص تجربتك. بياناتك تُحفظ في متصفحك فقط لضمان خصوصيتك.",
name:"الاسم",yourName:"اسمك",dob:"تاريخ الميلاد",gender:"الجنس",
male:"ذكر",female:"أنثى",noSay:"أفضّل عدم الإجابة",confirm:"تأكيد ومتابعة",
howtoTitle:"كيف تستخدم Genisi",getStarted:"ابدأ الآن",
thinking:"يفكّر...",showThinking:"عرض التفكير",
generatingImage:"جاري توليد الصورة...",searching:"جاري البحث...",
searchingImages:"جاري إيجاد الصور...",creatingDoc:"جاري إنشاء المستند...",
downloadDoc:"تحميل بصيغة",downloadImg:"تحميل الصورة",imagesLeft:"متبقٍ",
errConnect:"تعذّر الاتصال",stopped:"تم إيقاف الرد",newConvo:"محادثة جديدة",
micUnsupported:"متصفحك لا يدعم الإملاء الصوتي. استخدم Chrome.",
confirmClear:"مسح كل المحادثات والبيانات؟ لا يمكن التراجع.",
cutoff:"الرد طويل وانقطع. أعِد المحاولة أو اطلب محتوى أقصر.",
unsupportedFile:"هذا النوع من الملفات غير مدعوم في النموذج الحالي.",
playVideo:"تشغيل",exportVideo:"تحميل MP4",
buildingVideo:"جاري توليد الفيديو (قد يستغرق بعض الوقت)...",
recording:"جاري التسجيل...",videoReady:"الفيديو جاهز",
retrying:"إعادة المحاولة خلال",retryAttempt:"المحاولة",retrySeconds:"ثانية",
intro:'Genisi ذكاء اصطناعي جزائري طُوّر من طرف مشروع <b>AnesNT</b>، ويمتلكه حالياً <b>Anes Kameche (أنس كامش)</b>.',
howto:'<h3>المحادثة</h3><ul><li>اكتب سؤالك وبدّل بين <b>Flash</b> (سريع) و <b>Pro</b> (دقيق) في أي وقت.</li><li>أكواد مع نسخ، جداول، ومعادلات.</li></ul><h3>الصور</h3><ul><li>اطلب صورة بدقة واقعية، ونزّلها بضغطة.</li><li>الحد: 3 صور كل 3 ساعات.</li></ul><h3>المستندات</h3><ul><li>عاين Word, PowerPoint, Excel و PDF ثم حمّلها بأي صيغة.</li></ul><h3>الفيديو</h3><ul><li>اطلب فيديو قصير مولّد بالذكاء الاصطناعي وحمّله MP4.</li></ul><h3>الملفات والصوت</h3><ul><li>ارفع ملفات، إملاء صوتي، وقراءة الردود.</li></ul>'
},
fr: {
newChat:"Nouvelle discussion",searchChats:"Rechercher",recent:"Récents",settings:"Paramètres",
askGenisi:"Demandez à Genisi",theme:"Thème",light:"Clair",dark:"Sombre",auto:"Auto",
language:"Langue",defaultModel:"Modèle par défaut",voice:"Voix",autoTts:"Lecture audio auto",
howtoBtn:"Comment utiliser Genisi ?",clearBtn:"Effacer toutes les données",
flashDesc:"Rapide et intelligent",proDesc:"Plus précis",
welcome:"Bienvenue sur Genisi",
welcomeSub:"Personnalisons votre expérience. Vos données restent dans votre navigateur.",
name:"Nom",yourName:"Votre nom",dob:"Date de naissance",gender:"Genre",
male:"Homme",female:"Femme",noSay:"Préfère ne pas dire",confirm:"Confirmer et continuer",
howtoTitle:"Comment utiliser Genisi",getStarted:"Commencer",
thinking:"Réflexion...",showThinking:"Voir la réflexion",
generatingImage:"Génération de l'image...",searching:"Recherche...",
searchingImages:"Recherche d'images...",creatingDoc:"Création du document...",
downloadDoc:"Télécharger en",downloadImg:"Télécharger l'image",imagesLeft:"restant(s)",
errConnect:"Échec de connexion",stopped:"Réponse arrêtée",newConvo:"Nouvelle discussion",
micUnsupported:"Votre navigateur ne supporte pas la dictée. Utilisez Chrome.",
confirmClear:"Effacer toutes les discussions et données ? Irréversible.",
cutoff:"Réponse trop longue et interrompue. Réessayez ou demandez plus court.",
unsupportedFile:"Ce type de fichier n'est pas pris en charge par le modèle actuel.",
playVideo:"Lire",exportVideo:"Télécharger MP4",
buildingVideo:"Génération de la vidéo (peut prendre du temps)...",
recording:"Enregistrement...",videoReady:"Vidéo prête",
retrying:"Nouvelle tentative dans",retryAttempt:"Tentative",retrySeconds:"secondes",
intro:'Genisi est une IA algérienne développée par le projet <b>AnesNT</b>, actuellement détenue par <b>Anes Kameche</b>.',
howto:'<h3>Discussion</h3><ul><li>Posez votre question et basculez entre <b>Flash</b> et <b>Pro</b>.</li><li>Code avec copie, tableaux et équations.</li></ul><h3>Images</h3><ul><li>Demandez une image réaliste, téléchargeable en un clic.</li><li>Limite : 3 images / 3 heures.</li></ul><h3>Documents</h3><ul><li>Aperçu Word, PowerPoint, Excel et PDF, puis téléchargement.</li></ul><h3>Vidéo</h3><ul><li>Demandez une courte vidéo générée par IA et téléchargez-la en MP4.</li></ul><h3>Fichiers & Voix</h3><ul><li>Téléversez des fichiers, dictée vocale, lecture des réponses.</li></ul>'
}
};
function t(k) { return (I18N[state.settings.lang] || I18N.en)[k] || I18N.en[k] || k; }
var CAPS = {
flash: {vision:true, imageMimes:["image/jpeg","image/png","image/webp","image/gif"],
docExts:["pdf","docx","txt","md","csv","json","js","ts","py","html","css","java","c","cpp"]},
pro: {vision:false, imageMimes:[],
docExts:["pdf","docx","txt","md","csv","json","js","ts","py","html","css","java","c","cpp"]}
};
function acceptFor(model) {
var c = CAPS[model] || CAPS.flash;
return [].concat(c.vision ? c.imageMimes : [], c.docExts.map(function(e){return "."+e;})).join(",");
}
// ===================== State =====================
var state = {
user: LS.get("genisi_user", null),
settings: LS.get("genisi_settings", {theme:"light", lang:"en", model:"flash", ttsAuto:false}),
sessions: LS.get("genisi_sessions", []),
interests: LS.get("genisi_interests", []),
currentId: null,
busy: false,
abort: null
};
var clientId = LS.get("genisi_client", null) || (Date.now() + "-" + Math.random().toString(36).slice(2));
LS.set("genisi_client", clientId);
var pendingAttachments = [];
function save() {
LS.set("genisi_user", state.user);
LS.set("genisi_settings", state.settings);
LS.set("genisi_sessions", state.sessions);
LS.set("genisi_interests", state.interests);
}
function $(s) { return document.querySelector(s); }
function cur() { return state.sessions.find(function(s){return s.id === state.currentId;}); }
// ===================== Init =====================
function init() {
applyTheme();
applyLang();
document.querySelectorAll('input[name="theme"]').forEach(function(r){r.checked = r.value === state.settings.theme;});
document.querySelectorAll('input[name="lang"]').forEach(function(r){r.checked = r.value === state.settings.lang;});
document.querySelectorAll('input[name="dmodel"]').forEach(function(r){r.checked = r.value === state.settings.model;});
$("#ttsAuto").checked = !!state.settings.ttsAuto;
setModel(state.settings.model, true);
buildRecents();
if (state.user) { refreshUserUI(); } else { $("#welcomeModal").classList.add("open"); }
if (window.marked) marked.setOptions({breaks:true, gfm:true});
bindEvents();
}
// ===================== Event binding =====================
function bindEvents() {
$("#btnCloseSidebar").onclick = closeSidebar;
$("#btnOpenSidebarTop").onclick = openSidebar;
$("#btnNewChatSB").onclick = newChat;
$("#btnNewChatTop").onclick = newChat;
$("#btnOpenSearchSB").onclick = openSearch;
$("#btnOpenSearchTop").onclick = openSearch;
$("#btnOpenSettings").onclick = openSettings;
$("#btnOpenHowto").onclick = openHowto;
$("#btnCloseSettings").onclick = closeSettings;
$("#overlay").onclick = closeSettings;
$("#btnHowtoFromSettings").onclick = openHowto;
$("#btnClearAll").onclick = clearAll;
$("#btnCloseHowto").onclick = closeHowto;
$("#btnCloseSearch").onclick = closeSearch;
$("#btnConfirmOnboard").onclick = confirmOnboard;
$("#ttsAuto").onchange = function(e) { state.settings.ttsAuto = e.target.checked; save(); };
document.querySelectorAll('input[name="theme"]').forEach(function(r){r.onchange = function(){setTheme(r.value);};});
document.querySelectorAll('input[name="lang"]').forEach(function(r){r.onchange = function(){setLang(r.value);};});
document.querySelectorAll('input[name="dmodel"]').forEach(function(r){r.onchange = function(){setModel(r.value);};});
$("#modelBtnHome").onclick = function(e){toggleModelMenu(e,"modelMenuHome");};
$("#modelBtnChat").onclick = function(e){toggleModelMenu(e,"modelMenuChat");};
document.querySelectorAll(".mm-item").forEach(function(el){el.onclick = function(){setModel(el.dataset.model);};});
document.addEventListener("click", function(){document.querySelectorAll(".model-menu").forEach(function(m){m.classList.remove("open");});});
$("#homeTA").oninput = function(){onTaInput($("#homeTA"),"homeSend");};
$("#homeTA").onkeydown = function(e){onTaKey(e,"home");};
$("#chatTA").oninput = function(){onTaInput($("#chatTA"),"chatSend");};
$("#chatTA").onkeydown = function(e){onTaKey(e,"chat");};
$("#homeSend").onclick = function(){send("home");};
$("#chatSend").onclick = function(){send("chat");};
$("#homeUpload").onclick = function(){uploadFiles();};
$("#chatUpload").onclick = function(){uploadFiles();};
$("#homeMic").onclick = function(){toggleMic("homeTA",$("#homeMic"));};
$("#chatMic").onclick = function(){toggleMic("chatTA",$("#chatMic"));};
$("#spInput").oninput = function(){filterSP($("#spInput").value);};
document.querySelectorAll(".gender-opt").forEach(function(el){el.onclick = function(){pickGender(el);};});
$("#recentWrap").addEventListener("click", function(e) {
var item = e.target.closest(".recent-item");
if (!item) return;
var delBtn = e.target.closest(".ri-more");
if (delBtn) { e.stopPropagation(); deleteSession(item.dataset.id); return; }
loadSession(item.dataset.id);
});
$("#spResults").addEventListener("click", function(e) {
var row = e.target.closest(".sp-row");
if (row) { loadSession(row.dataset.id); closeSearch(); }
});
}
// ===================== Theme / Lang =====================
function applyLang() {
var l = state.settings.lang, rtl = l === "ar";
document.documentElement.lang = l;
document.documentElement.dir = rtl ? "rtl" : "ltr";
document.querySelectorAll("[data-i18n]").forEach(function(el) {
var k = el.getAttribute("data-i18n"), v = t(k);
(k === "intro" || k === "howto") ? (el.innerHTML = v) : (el.textContent = v);
});
document.querySelectorAll("[data-ph]").forEach(function(el){el.placeholder = t(el.getAttribute("data-ph"));});
if ($("#howtoIntro")) $("#howtoIntro").innerHTML = t("intro");
if ($("#howtoBody")) $("#howtoBody").innerHTML = t("howto");
if ($("#greeting")) $("#greeting").textContent = state.user ? greetText() : t("askGenisi");
}
function setLang(l) { state.settings.lang = l; save(); applyLang(); }
function setTheme(th) { state.settings.theme = th; save(); applyTheme(); }
function applyTheme() {
var th = state.settings.theme;
if (th === "auto") th = matchMedia("(prefers-color-scheme:dark)").matches ? "dark" : "light";
document.documentElement.setAttribute("data-theme", th);
}
// ===================== Onboarding =====================
var pickedGender = null;
function pickGender(el) {
document.querySelectorAll(".gender-opt").forEach(function(o){o.classList.remove("sel");});
el.classList.add("sel");
pickedGender = el.dataset.g;
}
function confirmOnboard() {
var name = $("#obName").value.trim();
if (!name) { $("#obName").focus(); return; }
state.user = {name:name, dob:$("#obDob").value || "", gender:pickedGender || ""};
save(); refreshUserUI();
$("#welcomeModal").classList.remove("open");
$("#howtoModal").classList.add("open");
}
function refreshUserUI() {
if (!state.user) return;
$("#userName").textContent = state.user.name;
$("#userAvatar").textContent = (state.user.name[0] || "G").toUpperCase();
if ($("#greeting")) $("#greeting").textContent = greetText();
}
function greetText() {
var n = (state.user && state.user.name) || "", l = state.settings.lang;
if (l === "ar") return "كيف يمكنني مساعدتك" + (n ? "، " + n : "") + "؟";
if (l === "fr") return "Comment puis-je vous aider" + (n ? ", " + n : "") + " ?";
return "How can I help you" + (n ? ", " + n : "") + "?";
}
// ===================== Sidebar / Settings / Search =====================
function closeSidebar() { $("#sidebar").classList.add("closed"); $("#topbar").classList.add("show"); }
function openSidebar() { $("#sidebar").classList.remove("closed"); $("#topbar").classList.remove("show"); }
function openSettings() { $("#settingsPanel").classList.add("open"); $("#overlay").classList.add("active"); }
function closeSettings() { $("#settingsPanel").classList.remove("open"); $("#overlay").classList.remove("active"); }
function openHowto() { $("#howtoModal").classList.add("open"); }
function closeHowto() { $("#howtoModal").classList.remove("open"); }
function openSearch() { $("#searchPane").classList.add("open"); buildSP(); setTimeout(function(){$("#spInput").focus();},50); }
function closeSearch() { $("#searchPane").classList.remove("open"); $("#spInput").value = ""; }
// ===================== Model =====================
function toggleModelMenu(e, id) {
e.stopPropagation();
document.querySelectorAll(".model-menu").forEach(function(m){if(m.id !== id) m.classList.remove("open");});
$("#" + id).classList.toggle("open");
}
function setModel(m, silent) {
state.settings.model = m; save();
var label = m === "flash" ? "Flash" : "Pro";
document.querySelectorAll(".modelLabel, .model-label").forEach(function(e){e.textContent = label;});
document.querySelectorAll('input[name="dmodel"]').forEach(function(r){r.checked = r.value === m;});
var caps = CAPS[m] || CAPS.flash;
pendingAttachments = pendingAttachments.filter(function(a){return a.kind === "image" ? caps.vision : true;});
renderAttachments();
if (!silent) document.querySelectorAll(".model-menu").forEach(function(mm){mm.classList.remove("open");});
}
// ===================== Sessions =====================
function newChat() {
state.currentId = null;
$("#shell").classList.remove("chat");
$("#msgsInner").innerHTML = "";
$("#homeTA").value = ""; $("#homeSend").disabled = true;
clearAttachments();
if ($("#greeting")) $("#greeting").textContent = greetText();
buildRecents(); closeSearch();
}
function buildRecents() {
$("#recentWrap").innerHTML = state.sessions.map(function(s) {
var active = s.id === state.currentId ? " active" : "";
var titleTxt = escapeHtml(s.title || t("newConvo"));
return '<div class="recent-item' + active + '" data-id="' + escapeHtml(s.id) + '">' +
'<span class="ri-text">' + titleTxt + '</span>' +
'<span class="ri-more" title="Delete">' +
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">' +
'<polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>' +
'</svg></span></div>';
}).join("");
}
function buildSP() {
$("#spResults").innerHTML = state.sessions.map(function(s) {
var key = escapeHtml((s.title || "").toLowerCase());
var titleTxt = escapeHtml(s.title || t("newConvo"));
return '<div class="sp-row" data-id="' + escapeHtml(s.id) + '" data-t="' + key + '">' +
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>' +
'<span class="sr-text">' + titleTxt + '</span>' +
'<span class="sr-date">' + escapeHtml(s.date || "") + '</span></div>';
}).join("");
}
function filterSP(q) {
q = q.toLowerCase();
document.querySelectorAll("#spResults .sp-row").forEach(function(r){
r.classList.toggle("hide", q && r.dataset.t.indexOf(q) === -1);
});
}
function deleteSession(id) {
state.sessions = state.sessions.filter(function(s){return s.id !== id;});
if (state.currentId === id) newChat();
save(); buildRecents();
}
function loadSession(id) {
state.currentId = id;
var s = cur(); if (!s) return;
$("#shell").classList.add("chat");
$("#msgsInner").innerHTML = "";
s.messages.forEach(function(m){
if (m.role === "user") addUser(m.content, m.attachments);
else addAI(m.content, false);
});
buildRecents(); renderMathAll(); scrollDown();
}
// ===================== Textarea helpers =====================
function onTaInput(el, sendId) {
el.style.height = "auto";
el.style.height = Math.min(el.scrollHeight, 180) + "px";
if (!state.busy) $("#" + sendId).disabled = !el.value.trim();
}
function onTaKey(e, mode) {
if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(mode); }
}
// ===================== File upload =====================
function uploadFiles() {
var model = state.settings.model;
var input = document.createElement("input");
input.type = "file"; input.multiple = true; input.accept = acceptFor(model);
input.onchange = function(e) {
Array.from(e.target.files).forEach(function(f){doUpload(f, model);});
};
input.click();
}
function doUpload(file, model) {
var fd = new FormData();
fd.append("file", file);
fd.append("model", model);
fetch("/api/upload", {method:"POST", body:fd})
.then(function(r){return safeJSON(r);})
.then(function(j) {
if (j.message && !j.kind) { alert(j.message); return; }
if (j.kind === "image") pendingAttachments.push({kind:"image", name:j.name, dataUrl:j.dataUrl});
else pendingAttachments.push({kind:"text", name:j.name, text:j.text});
renderAttachments();
})
.catch(function(){alert(t("unsupportedFile"));});
}
function attChipHtml(a, idx) {
var icon = a.kind === "image"
? '<img src="' + a.dataUrl + '" alt="">'
: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>';
var xBtn = idx !== undefined ? '<button class="att-x" data-idx="' + idx + '">×</button>' : "";
return '<div class="att-chip">' + icon + '<span class="att-name">' + escapeHtml(a.name) + '</span>' + xBtn + '</div>';
}
function renderAttachments() {
[$("#homeAtts"), $("#chatAtts")].forEach(function(w) {
if (!w) return;
w.innerHTML = pendingAttachments.map(function(a,i){return attChipHtml(a,i);}).join("");
w.querySelectorAll(".att-x").forEach(function(btn) {
btn.onclick = function(){pendingAttachments.splice(+btn.dataset.idx, 1); renderAttachments();};
});
});
}
function clearAttachments() { pendingAttachments = []; renderAttachments(); }
// ===================== Utilities =====================
function escapeHtml(s) {
return (s || "").replace(/[&<>"']/g, function(c) {
return {"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c];
});
}
function addUser(text, atts) {
var el = document.createElement("div");
el.className = "msg-user";
if (atts && atts.length) {
var box = document.createElement("div");
box.className = "attachments"; box.style.padding = "0 0 8px";
box.innerHTML = atts.map(function(a){return attChipHtml(a);}).join("");
el.appendChild(box);
}
var tx = document.createElement("div");
tx.textContent = text;
el.appendChild(tx);
$("#msgsInner").appendChild(el);
scrollDown();
return el;
}
function addAI(md) {
var wrap = document.createElement("div");
wrap.className = "msg-ai";
var body = document.createElement("div");
body.className = "msg-ai-body";
wrap.appendChild(body);
$("#msgsInner").appendChild(wrap);
if (md !== undefined) renderInto(body, md);
scrollDown();
return {wrap:wrap, body:body};
}
function renderFormatted(text, autoClose) {
var cleaned = cleanForRender(text || "");
if (autoClose) {
var codeMatches = cleaned.match(/```/g) || [];
if (codeMatches.length % 2 !== 0) { if (!cleaned.endsWith("\n")) cleaned += "\n"; cleaned += "```"; }
var mathMatches = cleaned.match(/\$\$/g) || [];
if (mathMatches.length % 2 !== 0) cleaned += " $$";
var openBracket = (cleaned.match(/\\\[/g) || []).length;
var closeBracket = (cleaned.match(/\\\]/g) || []).length;
if (openBracket > closeBracket) cleaned += " \\]";
}
return window.marked ? marked.parse(cleaned) : escapeHtml(cleaned);
}
function renderInto(body, md) {
body.innerHTML = renderFormatted(md, true);
enhanceCode(body);
renderMath(body);
}
function enhanceCode(scope) {
scope.querySelectorAll("pre code").forEach(function(code) {
if (code.dataset.done) return;
code.dataset.done = "1";
var pre = code.parentElement;
var langMatch = code.className.match(/language-(\w+)/);
var lang = langMatch ? langMatch[1] : "code";
var wrap = document.createElement("div");
wrap.className = "code-wrap";
var bar = document.createElement("div");
bar.className = "code-bar";
bar.innerHTML = "<span>" + lang + "</span>";
var btn = document.createElement("button");
btn.className = "copy-btn"; btn.textContent = "Copy";
btn.onclick = function() {
navigator.clipboard.writeText(code.innerText);
btn.textContent = "Copied";
setTimeout(function(){btn.textContent = "Copy";}, 1500);
};
bar.appendChild(btn);
pre.parentNode.insertBefore(wrap, pre);
wrap.appendChild(bar);
wrap.appendChild(pre);
try { if (window.hljs) hljs.highlightElement(code); } catch(e) {}
});
}
function renderMath(scope) {
if (window.renderMathInElement) {
try {
renderMathInElement(scope, {
delimiters: [
{left:"$$",right:"$$",display:true},{left:"\\[",right:"\\]",display:true},
{left:"\\(",right:"\\)",display:false},{left:"$",right:"$",display:false}
],
throwOnError: false, errorColor: "var(--purple)"
});
} catch(e) {}
}
}
function renderMathAll() { document.querySelectorAll(".msg-ai-body").forEach(renderMath); }
function scrollDown() { var m = $("#msgs"); m.scrollTop = m.scrollHeight; }
// ===================== Send buttons state =====================
function setSendButtons(stop) {
[["#homeSend","home"],["#chatSend","chat"]].forEach(function(pair) {
var btn = $(pair[0]); if (!btn) return;
if (stop) {
btn.classList.add("stop"); btn.disabled = false;
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><rect x="5" y="5" width="14" height="14" rx="2"/></svg>';
btn.onclick = stopResponse;
} else {
btn.classList.remove("stop");
btn.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="20" x2="12" y2="5"/><polyline points="6 11 12 5 18 11"/></svg>';
btn.onclick = function(){send(pair[1]);};
}
});
}
function stopResponse() { if (state.abort) state.abort.abort(); }
// ===================== Main send =====================
function send(mode) {
var ta = mode === "home" ? $("#homeTA") : $("#chatTA");
var text = ta.value.trim();
if ((!text && !pendingAttachments.length) || state.busy) return;
if (!state.currentId) {
var id = Date.now() + "";
state.sessions.unshift({id:id, title:"", date:"now", messages:[]});
state.currentId = id;
}
var s = cur();
$("#shell").classList.add("chat");
if (mode === "home") {
$("#msgsInner").innerHTML = "";
s.messages.forEach(function(m) {
if (m.role === "user") addUser(m.content, m.attachments);
else addAI(m.content, false);
});
}
ta.value = ""; ta.style.height = "auto";
var atts = pendingAttachments.slice();
addUser(text, atts);
s.messages.push({role:"user", content:text, attachments:atts});
clearAttachments(); buildRecents();
var aiEl = addAI(undefined, true);
var body = aiEl.body;
var answer = document.createElement("div");
answer.className = "streaming";
body.appendChild(answer);
// Loading animation
if (state.settings.model === "pro") {
answer.innerHTML = '<div class="pro-thinking-wrap"><div class="pro-thinking"><span class="pro-glow-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/></svg></span><span class="pro-glow-text">' + escapeHtml(t("thinking")) + '</span></div></div>';
} else {
answer.innerHTML = '<div class="worm-square-wrap"><div class="worm-square"><span></span><span></span><span></span><span></span></div></div>';
}
state.busy = true;
setSendButtons(true);
state.abort = new AbortController();
var full = "", rafPending = false, retryBanner = null, retryCountdown = null;
function paint() {
rafPending = false;
answer.classList.add("streaming");
answer.innerHTML = renderFormatted(full, true);
enhanceCode(answer);
renderMath(answer);
scrollDown();
}
function showRetryBanner(attempt, nextRetryIn, error) {
if (retryBanner) retryBanner.remove();
retryBanner = document.createElement("div");
retryBanner.className = "retry-banner";
var secs = nextRetryIn || 300;
retryBanner.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-6.22-8.56"/><polyline points="21 3 21 9 15 9"/></svg>' +
'<span>' + t("retryAttempt") + ' #' + attempt + ' — ' + t("retrying") + ' <b id="retryCount">' + secs + '</b> ' + t("retrySeconds") + '</span>';
body.insertBefore(retryBanner, answer);
var remaining = secs;
var countEl = retryBanner.querySelector("#retryCount");
if (retryCountdown) clearInterval(retryCountdown);
retryCountdown = setInterval(function() {
remaining--;
if (countEl) countEl.textContent = remaining;
if (remaining <= 0) clearInterval(retryCountdown);
}, 1000);
scrollDown();
}
function removeRetryBanner() {
if (retryCountdown) { clearInterval(retryCountdown); retryCountdown = null; }
if (retryBanner) { retryBanner.remove(); retryBanner = null; }
}
fetch("/api/chat", {
method: "POST",
headers: {"Content-Type":"application/json"},
signal: state.abort.signal,
body: JSON.stringify({
messages: s.messages,
model: state.settings.model,
user: state.user || {},
interests: state.interests,
think: state.settings.model === "pro"
})
}).then(function(res) {
var reader = res.body.getReader(), dec = new TextDecoder(), buf = "";
function processChunk(result) {
if (result.done) return finishStream();
buf += dec.decode(result.value, {stream:true});
var parts = buf.split("\n\n");
buf = parts.pop();
for (var i = 0; i < parts.length; i++) {
var line = parts[i].replace(/^data: /, "").trim();
if (!line) continue;
var j;
try { j = JSON.parse(line); } catch(e) { continue; }
// ===== Retry event from backend =====
if (j.retrying) {
removeRetryBanner();
showRetryBanner(j.attempt, j.nextRetryIn, j.error);
continue;
}
if (j.error) {
removeRetryBanner();
answer.innerHTML = '<p style="color:#d33">' + escapeHtml(j.error) + '</p>';
continue;
}
if (j.done) continue;
// Got real content — remove retry banner if any
if (full.length === 0) removeRetryBanner();
full += j.delta || "";
if (!rafPending) { rafPending = true; requestAnimationFrame(paint); }
}
return reader.read().then(processChunk);
}
return reader.read().then(processChunk);
}).then(function() {
// handled in finishStream via the chain
}).catch(function(err) {
removeRetryBanner();
if (err.name === "AbortError") {
var partial = full;
answer.classList.remove("streaming");
answer.innerHTML = partial.trim()
? renderFormatted(partial, true)
: '<p style="color:var(--muted)">' + t("stopped") + '</p>';
enhanceCode(answer); renderMath(answer);
if (partial.trim()) s.messages.push({role:"assistant", content:partial});
maybeTitle(s); save(); buildRecents();
} else {
answer.innerHTML = '<p style="color:#d33">' + t("errConnect") + ": " + escapeHtml(String(err.message || err)) + '</p>';
}
}).finally(function() {
removeRetryBanner();
state.busy = false;
state.abort = null;
setSendButtons(false);
$("#homeSend").disabled = true;
$("#chatSend").disabled = true;
});
function finishStream() {
removeRetryBanner();
var finalAns = full;
answer.classList.remove("streaming");
answer.innerHTML = renderFormatted(finalAns, true);
enhanceCode(answer);
renderMath(answer);
handleTools(finalAns, answer);
var sugg = extractSuggestions(full);
if (sugg.length) renderSuggestions(answer, sugg);
s.messages.push({role:"assistant", content:finalAns});
if (state.settings.ttsAuto) speak(stripForTTS(finalAns));
maybeTitle(s);
learnInterests(text);
save(); buildRecents();
}
}
// ===================== Tool extraction =====================
function extractToolJSON(text) {
var key = text.indexOf('"tool"');
if (key === -1) return null;
var start = text.lastIndexOf("{", key);
while (start !== -1) {
var depth = 0, inStr = false, esc = false;
for (var i = start; i < text.length; i++) {
var ch = text[i];
if (esc) { esc = false; continue; }
if (ch === "\\") { esc = true; continue; }
if (ch === '"') { inStr = !inStr; continue; }
if (inStr) continue;
if (ch === "{") depth++;
else if (ch === "}") { depth--; if (depth === 0) return text.slice(start, i + 1); }
}
start = text.lastIndexOf("{", start - 1);
}
return null;
}
function cleanForRender(text) {
var out = text || "";
var raw = extractToolJSON(out);
if (raw) out = out.replace(raw, "");
out = out.replace(/```[a-zA-Z]*\s*\{[\s\S]*?"tool"[\s\S]*?```/g, "");
return out.replace(/SUGGESTIONS:\s*\[[\s\S]*?\]\s*$/, "").trim();
}
function extractSuggestions(text) {
var m = text.match(/SUGGESTIONS:\s*(\[[\s\S]*?\])/);
if (!m) return [];
try { return JSON.parse(m[1]).slice(0, 4); } catch(e) { return []; }
}
function renderSuggestions(scope, list) {
var box = document.createElement("div");
box.className = "suggests";
list.forEach(function(txt) {
var b = document.createElement("button");
b.className = "suggest"; b.textContent = txt;
b.onclick = function(){$("#chatTA").value = txt; send("chat");};
box.appendChild(b);
});
scope.appendChild(box);
}
function downloadUrl(url, filename) {
var a = document.createElement("a");
a.href = url; a.download = filename;
document.body.appendChild(a); a.click(); a.remove();
}
// ===================== Tool handlers =====================
function handleTools(text, scope) {
var raw = extractToolJSON(text);
if (!raw) return;
var cmd;
try { cmd = JSON.parse(raw); } catch(e) {
var n = document.createElement("p");
n.style.color = "#d33"; n.textContent = t("cutoff");
scope.appendChild(n); return;
}
if (!cmd || !cmd.tool) return;
if (cmd.tool === "image") {
var n = document.createElement("p");
n.textContent = t("generatingImage");
scope.appendChild(n);
fetch("/api/image", {method:"POST", headers:{"Content-Type":"application/json"},
body:JSON.stringify({prompt:cmd.prompt, clientId:clientId})})
.then(function(r){return safeJSON(r);})
.then(function(j) {
if (j.image) {
var fname = "genisi-" + Date.now() + ".png";
var div = document.createElement("div");
div.innerHTML = '<img class="gen-img" src="' + escapeHtml(j.image) + '" alt="">' +
'<div class="img-actions"><button class="suggest" id="dlImg">' + t("downloadImg") + '</button></div>' +
'<p style="color:var(--muted);font-size:13px">' + (j.remaining||0) + ' ' + t("imagesLeft") + '</p>';
div.querySelector("#dlImg").onclick = function(){downloadUrl(j.image, fname);};
n.replaceWith(div);
} else {
n.textContent = j.message || "";
}
})
.catch(function(){n.textContent = "error";});
} else if (cmd.tool === "search") {
var n = document.createElement("p");
n.textContent = t("searching");
scope.appendChild(n);
fetch("/api/search", {method:"POST", headers:{"Content-Type":"application/json"},
body:JSON.stringify({query:cmd.query})})
.then(function(r){return safeJSON(r);})
.then(function(j) {
n.innerHTML = (j.results || []).map(function(x) {
return '<a href="' + escapeHtml(x.url) + '" target="_blank">' + escapeHtml(x.title) + '</a>' +
'<br><small>' + escapeHtml(x.snippet) + '</small>';
}).join("<br><br>") || "No results.";
})
.catch(function(){n.textContent = "error";});
} else if (cmd.tool === "image_search") {
var n = document.createElement("div");
scope.appendChild(n);
fetchImages(cmd.query).then(function(imgs) {
n.innerHTML = imgs.slice(0,6).map(function(u) {
return '<img class="gen-img" style="display:inline-block;width:31%;margin:1%" src="' + escapeHtml(proxify(u)) + '">';
}).join("");
}).catch(function(){n.textContent = "error";});
} else if (cmd.tool === "document") {
renderDocPreview(cmd, scope);
} else if (cmd.tool === "video") {
renderVideo(cmd, scope);
}
}
// ===================== Document helpers =====================
function asTextC(v) {
if (v == null) return "";
if (typeof v === "string") return v;
if (Array.isArray(v)) return v.map(asTextC).join("\n");
if (typeof v === "object") return v.text || v.body || v.content || "";
return String(v);
}
function docToHtml(cmd) {
var c = cmd.content || {}, esc = escapeHtml, blocks = [];
if (c.heading) blocks.push("<h2>" + esc(c.heading) + "</h2>");
(c.paragraphs || []).forEach(function(p){blocks.push("<p>" + esc(asTextC(p)) + "</p>");});
(c.sections || []).forEach(function(s) {
if (s.title) blocks.push("<h3>" + esc(s.title) + "</h3>");
var b = s.body || s.content || s.text;
if (b) blocks.push("<p>" + esc(asTextC(b)) + "</p>");
(s.bullets || s.points || []).forEach(function(x){blocks.push("<li>" + esc(asTextC(x)) + "</li>");});
});
(c.slides || []).forEach(function(s, i) {
blocks.push('<div class="doc-slide"><span class="doc-slide-n">' + (i+1) + '</span><h3>' +
esc(s.title||"") + '</h3><ul>' +
(s.bullets||s.points||[]).map(function(x){return "<li>"+esc(asTextC(x))+"</li>";}).join("") +
"</ul></div>");
});
if (c.sheets || c.headers) {
var sh = (c.sheets && c.sheets[0]) || c;
if (sh.headers) {
blocks.push("<table><thead><tr>" +
sh.headers.map(function(h){return "<th>"+esc(asTextC(h))+"</th>";}).join("") +
"</tr></thead><tbody>" +
(sh.rows || []).map(function(r){return "<tr>"+r.map(function(x){return "<td>"+esc(asTextC(x))+"</td>";}).join("")+"</tr>";}).join("") +
"</tbody></table>");
}
}
(c.bullets || c.points || []).forEach(function(x){blocks.push("<li>" + esc(asTextC(x)) + "</li>");});
return '<div class="doc-paper"><h1 class="doc-title">' + esc(cmd.title || "Document") + "</h1>" + blocks.join("") + "</div>";
}
function renderDocPreview(cmd, scope) {
var wrap = document.createElement("div");
wrap.className = "doc-preview";
wrap.innerHTML = docToHtml(cmd);
scope.appendChild(wrap);
var bar = document.createElement("div");
bar.className = "doc-dl";
bar.innerHTML = '<span class="doc-dl-label">' + t("downloadDoc") + "</span>";
[["pdf","PDF"],["docx","Word"],["pptx","PowerPoint"],["xlsx","Excel"]].forEach(function(pair) {
var b = document.createElement("button");
b.className = "doc-dl-btn"; b.textContent = pair[1];
b.onclick = function(){downloadDoc(Object.assign({}, cmd, {format:pair[0]}), b);};
bar.appendChild(b);
});
scope.appendChild(bar);
}
function downloadDoc(cmd, btn) {
var old = btn.textContent;
btn.textContent = "..."; btn.disabled = true;
fetch("/api/document", {method:"POST", headers:{"Content-Type":"application/json"}, body:JSON.stringify(cmd)})
.then(function(r) {
if (!r.ok) throw 0;
return r.blob();
})
.then(function(blob) {
var url = URL.createObjectURL(blob);
var a = document.createElement("a");
a.href = url; a.download = (cmd.title || "genisi") + "." + cmd.format;
document.body.appendChild(a); a.click(); a.remove();
URL.revokeObjectURL(url);
})
.catch(function(){alert("error");})
.finally(function(){btn.textContent = old; btn.disabled = false;});
}
// ===================== Media helpers =====================
function proxify(u) {
if (!u) return u;
if (u.indexOf("data:") === 0) return u;
return "/api/img?u=" + encodeURIComponent(u);
}
function fetchImages(query) {
return fetch("/api/image-search", {method:"POST", headers:{"Content-Type":"application/json"}, body:JSON.stringify({query:query})})
.then(function(r){return safeJSON(r);})
.then(function(j){return (j.images || []).map(function(x){return x.image || x.thumbnail;}).filter(Boolean);});
}
function renderVideo(cmd, scope) {
var status = document.createElement("p");
status.textContent = t("buildingVideo");
scope.appendChild(status);
return fetch("/api/video", {method:"POST", headers:{"Content-Type":"application/json"},
body:JSON.stringify({prompt:cmd.prompt||cmd.query||cmd.title||"", clientId:clientId})})
.then(function(r){return safeJSON(r);})
.then(function(j) {
if (!j.video) { status.textContent = j.message || "error"; return; }
status.remove();
var box = document.createElement("div");
box.className = "video-box";
var stage = document.createElement("div");
stage.className = "video-stage";
var video = document.createElement("video");
video.src = j.video; video.controls = true;
stage.appendChild(video);
box.appendChild(stage);
var ctrl = document.createElement("div");
ctrl.className = "video-ctrl";
var dlBtn = document.createElement("button");
dlBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg><span>' + t("exportVideo") + '</span>';
dlBtn.onclick = function(){downloadUrl(j.video, (cmd.title||"genisi-video")+".mp4");};
var vstat = document.createElement("span");
vstat.className = "vstatus";
vstat.textContent = j.remaining !== undefined ? j.remaining + " " + t("imagesLeft") : "";
ctrl.appendChild(dlBtn); ctrl.appendChild(vstat);
box.appendChild(ctrl);
scope.appendChild(box);
})
.catch(function(){status.textContent = t("errConnect");});
}
// ===================== Misc =====================
function maybeTitle(s) {
var u = s.messages.filter(function(m){return m.role === "user";}).length;
if (u !== 1 && u !== 3) return;
fetch("/api/title", {method:"POST", headers:{"Content-Type":"application/json"},
body:JSON.stringify({messages:s.messages})})
.then(function(r){return safeJSON(r);})
.then(function(j) {
if (j.title) { s.title = j.title; save(); buildRecents(); }
})
.catch(function(){});
}
function learnInterests(text) {
var words = (text || "").toLowerCase().match(/[\u0600-\u06FF\w]{4,}/g) || [];
var stop = new Set(["this","that","with","please","about","comment","pourquoi","هذا","هذه","التي","الذي","كيف"]);
words.filter(function(w){return !stop.has(w);}).slice(0,3).forEach(function(w) {
if (state.interests.indexOf(w) === -1) state.interests.push(w);
});
state.interests = state.interests.slice(-30);
save();
}
function stripForTTS(x) {
return x.replace(/```[\s\S]*?```/g,"").replace(/\{[\s\S]*?\}/g,"").replace(/SUGGESTIONS:.*$/,"");
}
function speak(text) {
if (!text.trim()) return;
fetch("/api/tts", {method:"POST", headers:{"Content-Type":"application/json"}, body:JSON.stringify({text:text})})
.then(function(r) {
if (!r.ok) throw 0;
return r.blob();
})
.then(function(blob){new Audio(URL.createObjectURL(blob)).play();})
.catch(function() {
if (window.speechSynthesis) speechSynthesis.speak(new SpeechSynthesisUtterance(text));
});
}
var recog = null;
function toggleMic(taId, btn) {
var SR = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SR) { alert(t("micUnsupported")); return; }
if (recog) { recog.stop(); recog = null; btn.classList.remove("recording"); return; }
recog = new SR();
recog.lang = state.settings.lang === "ar" ? "ar-SA" : state.settings.lang === "fr" ? "fr-FR" : "en-US";
recog.continuous = false; recog.interimResults = true;
btn.classList.add("recording");
var ta = $("#" + taId), base = ta.value;
recog.onresult = function(e) {
var tx = "";
for (var i = 0; i < e.results.length; i++) tx += e.results[i][0].transcript;
ta.value = (base ? base + " " : "") + tx;
onTaInput(ta, taId === "homeTA" ? "homeSend" : "chatSend");
};
recog.onend = function() { btn.classList.remove("recording"); recog = null; };
recog.onerror = function() { btn.classList.remove("recording"); recog = null; };
recog.start();
}
function clearAll() {
if (confirm(t("confirmClear"))) { LS.clear(); location.reload(); }
}
// Start
init();
</script>
<script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a1759fac19f0335d',t:'MTc4MzQxNDQ1OA=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>
</html>