Ggff44 / index.html
bahi-bh's picture
Create index.html
d19fa2b verified
<!DOCTYPE html>
<html lang="ar" dir="rtl" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>g4fpro</title>
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;500;600;700;900&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
<style>
:root{
--bg:#0c0c12;--bg1:#141420;--bg2:#1b1b28;--bg3:#222232;--bg4:#2a2a3e;--bgh:#313148;
--ln:#1e1e2e;--ln2:#2c2c42;--ln3:#404055;
--t0:#e8e8f5;--t1:#b0b0cc;--t2:#6868a0;--t3:#404058;
--ac:#7c6fff;--ach:#8f84ff;--acbg:#7c6fff14;--acln:#7c6fff44;
--tl:#00d4ff;--tlbg:#00d4ff0e;
--gn:#22d48a;--gnbg:#22d48a0e;
--am:#f5a623;--ambg:#f5a6230e;
--rd:#ff5f6d;--rdbg:#ff5f6d0e;
--font:'Cairo',sans-serif;--mono:'JetBrains Mono',monospace;
--r1:5px;--r2:10px;--r3:14px;--r4:20px;
--s1:0 1px 4px #0009;--s2:0 4px 18px #000b;--s3:0 12px 40px #000e;
}
[data-theme=light]{
--bg:#f2f2fa;--bg1:#fff;--bg2:#f5f5fd;--bg3:#ebebf8;--bg4:#e2e2f0;--bgh:#d6d6ee;
--ln:#e8e8f6;--ln2:#d8d8ee;--ln3:#c0c0dc;
--t0:#0d0d1a;--t1:#2a2a44;--t2:#5a5a88;--t3:#9898b8;
--s1:0 1px 4px #0002;--s2:0 4px 18px #0003;--s3:0 12px 40px #0004;
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
html,body{height:100%;overflow:hidden;font-family:var(--font);background:var(--bg);color:var(--t0);-webkit-font-smoothing:antialiased}
button{cursor:pointer;font-family:var(--font);border:none;background:none;color:inherit}
input,textarea,select{font-family:var(--font);-webkit-appearance:none;appearance:none}
a{color:var(--tl)}
::-webkit-scrollbar{width:3px;height:3px}
::-webkit-scrollbar-thumb{background:var(--ln2);border-radius:2px}
::-webkit-scrollbar-track{background:transparent}
#app{display:flex;height:100dvh;overflow:hidden}
#sb{width:268px;min-width:268px;background:var(--bg1);border-left:1px solid var(--ln);display:flex;flex-direction:column;height:100dvh;overflow:hidden;flex-shrink:0;transition:width .22s ease,min-width .22s ease}
#sb.desk-hide{width:0;min-width:0;border-left:none}
@media(max-width:680px){
#sb{position:fixed;right:0;top:0;bottom:0;width:282px!important;min-width:0;transform:translateX(110%);box-shadow:var(--s3);transition:transform .22s ease;z-index:50}
#sb.mob-open{transform:translateX(0)}
}
.sb-head{padding:10px;border-bottom:1px solid var(--ln);flex-shrink:0}
.new-btn{width:100%;display:flex;align-items:center;gap:7px;padding:9px 12px;background:var(--ac);color:#fff;border-radius:var(--r2);font-size:.87rem;font-weight:700;transition:.14s}
.new-btn:hover{background:var(--ach)}.new-btn:active{transform:scale(.97)}
.cl{flex:1;overflow-y:auto;padding:4px}
.grp{font-size:.62rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:var(--t3);padding:8px 8px 3px}
.ci{display:flex;align-items:center;gap:7px;padding:7px 9px;border-radius:var(--r2);cursor:pointer;transition:.13s;position:relative}
.ci:hover{background:var(--bgh)}.ci.on{background:var(--acbg);border:1px solid var(--acln)}
.ci-dot{width:6px;height:6px;border-radius:50%;background:var(--t3);flex-shrink:0;transition:.13s}
.ci.on .ci-dot{background:var(--ac)}
.ci-info{flex:1;min-width:0}
.ci-title{font-size:.79rem;font-weight:600;color:var(--t1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ci.on .ci-title{color:var(--ac)}
.ci-sub{font-size:.64rem;color:var(--t3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:1px}
.ci-acts{display:none;gap:1px;flex-shrink:0}.ci:hover .ci-acts{display:flex}
.cia{width:22px;height:22px;border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:.65rem;color:var(--t2);transition:.13s}
.cia:hover{background:var(--bg4);color:var(--t0)}.cia.del:hover{color:var(--rd)}
.sb-bot{border-top:1px solid var(--ln);padding:10px;flex-shrink:0;display:flex;flex-direction:column;gap:7px;overflow-y:auto;max-height:58vh}
.lbl{font-size:.63rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--t3);margin-bottom:3px;display:flex;align-items:center;gap:4px}
.lbl i{color:var(--ac);font-size:.61rem}
.sel{width:100%;padding:7px 24px 7px 7px;background:var(--bg3);border:1px solid var(--ln2);border-radius:var(--r1);color:var(--t0);font-size:.8rem;font-weight:600;cursor:pointer;direction:rtl;transition:.13s;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath fill='%23668' d='M5 6L0 0h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:left 7px center;background-size:8px}
.sel:focus{border-color:var(--ac);outline:none}
.tog-row{display:flex;align-items:center;justify-content:space-between;padding:6px 8px;background:var(--bg3);border:1px solid var(--ln2);border-radius:var(--r1)}
.tog-lbl{font-size:.78rem;font-weight:600;color:var(--t1);display:flex;align-items:center;gap:5px}
.tog-lbl i{color:var(--tl);font-size:.7rem}
.tog{position:relative;width:34px;height:18px;flex-shrink:0}
.tog input{opacity:0;width:0;height:0;position:absolute}
.tog-t{position:absolute;inset:0;background:var(--bg4);border:1px solid var(--ln2);border-radius:9px;cursor:pointer;transition:.15s}
.tog-t::after{content:'';position:absolute;width:12px;height:12px;right:2px;top:2px;background:var(--t2);border-radius:50%;transition:.15s}
.tog input:checked~.tog-t{background:var(--tl);border-color:var(--tl)}
.tog input:checked~.tog-t::after{transform:translateX(-16px);background:#fff}
.sys-ta{width:100%;padding:6px 8px;background:var(--bg3);border:1px solid var(--ln2);border-radius:var(--r1);color:var(--t0);font-size:.78rem;line-height:1.5;resize:none;min-height:50px;max-height:86px;direction:rtl;transition:.13s}
.sys-ta:focus{border-color:var(--ac);outline:none}.sys-ta::placeholder{color:var(--t3)}
#main{flex:1;min-width:0;display:flex;flex-direction:column;height:100dvh;background:var(--bg)}
#hdr{height:50px;background:var(--bg1);border-bottom:1px solid var(--ln);display:flex;align-items:center;padding:0 12px;gap:8px;flex-shrink:0}
.hb{width:32px;height:32px;border-radius:var(--r1);background:var(--bg3);border:1px solid var(--ln2);color:var(--t1);display:flex;align-items:center;justify-content:center;font-size:.82rem;transition:.13s;flex-shrink:0}
.hb:hover{background:var(--bgh);color:var(--t0)}.hb:active{transform:scale(.91)}
.logo{display:flex;align-items:center;gap:6px;font-weight:900;font-size:.96rem;letter-spacing:-.03em;white-space:nowrap}
.logo-mk{width:27px;height:27px;background:linear-gradient(135deg,var(--ac),var(--tl));border-radius:7px;display:flex;align-items:center;justify-content:center;color:#fff;font-size:.72rem}
.logo-ac{color:var(--ac)}
.hdr-m{flex:1;min-width:0;text-align:center}
.hdr-t{font-size:.81rem;font-weight:700;color:var(--t1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.hdr-r{display:flex;align-items:center;gap:5px;flex-shrink:0}
.hst{display:flex;align-items:center;gap:4px;font-size:.67rem;color:var(--gn);font-weight:600;white-space:nowrap}
.hst::before{content:'';width:5px;height:5px;background:var(--gn);border-radius:50%;animation:pu 2s ease infinite}
@keyframes pu{0%,100%{opacity:1}50%{opacity:.3}}
.ppill{display:flex;align-items:center;gap:4px;padding:3px 8px;background:var(--bg3);border:1px solid var(--ln2);border-radius:14px;font-size:.67rem;font-weight:700;color:var(--t2);white-space:nowrap;max-width:116px;overflow:hidden;text-overflow:ellipsis}
.pd{width:5px;height:5px;border-radius:50%;background:var(--ac);flex-shrink:0}
@media(max-width:480px){.ppill{display:none}}
#mwrap{flex:1;overflow-y:auto;position:relative;-webkit-overflow-scrolling:touch}
#msgs{max-width:780px;margin:0 auto;padding:18px 10px 14px;display:flex;flex-direction:column;gap:2px}
@media(max-width:680px){#msgs{padding:12px 8px 10px}}
#wlc{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:calc(100dvh - 140px);gap:18px;text-align:center;padding:20px;animation:fup .4s ease}
@keyframes fup{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
.wico{width:66px;height:66px;background:linear-gradient(135deg,var(--ac),var(--tl));border-radius:18px;display:flex;align-items:center;justify-content:center;font-size:1.6rem;color:#fff;animation:fl 3.5s ease-in-out infinite}
@keyframes fl{0%,100%{transform:translateY(0)}50%{transform:translateY(-7px)}}
.wt{font-size:1.35rem;font-weight:900}.wt span{color:var(--ac)}
.ws{font-size:.85rem;color:var(--t2);max-width:340px;line-height:1.65}
.sg{display:grid;grid-template-columns:1fr 1fr;gap:6px;width:100%;max-width:420px}
@media(max-width:420px){.sg{grid-template-columns:1fr}}
.sc{padding:10px 11px;background:var(--bg2);border:1px solid var(--ln2);border-radius:var(--r2);text-align:right;cursor:pointer;transition:.13s}
.sc:hover{background:var(--bgh);border-color:var(--acln);transform:translateY(-2px)}.sc:active{transform:scale(.97)}
.sc-i{font-size:.95rem;display:block;margin-bottom:3px}
.sc-t{font-size:.76rem;color:var(--t1);font-weight:600;line-height:1.3}
.sc-s{font-size:.65rem;color:var(--t3);margin-top:1px}
.mr{display:flex;flex-direction:column;gap:2px;animation:ms .22s cubic-bezier(.34,1.4,.64,1)}
@keyframes ms{from{opacity:0;transform:translateY(5px)scale(.98)}to{opacity:1;transform:none}}
.mr.user{align-items:flex-end}.mr.assistant{align-items:flex-start}.mr.err{align-items:flex-start}
.mhdr{display:flex;align-items:center;gap:5px;padding:0 3px;font-size:.65rem;color:var(--t3)}
.mr.user .mhdr{flex-direction:row-reverse}
.mav{width:20px;height:20px;border-radius:5px;display:flex;align-items:center;justify-content:center;font-size:.6rem;color:#fff;flex-shrink:0}
.mav.u{background:var(--ac)}.mav.b{background:linear-gradient(135deg,var(--ac),var(--tl))}
.mtag{background:var(--bg3);border:1px solid var(--ln2);padding:1px 5px;border-radius:8px;font-size:.6rem;font-weight:700;color:var(--t2)}
.pinbdg{color:var(--am);font-size:.6rem}
.mb{max-width:80%;padding:10px 14px;border-radius:var(--r3);font-size:.89rem;line-height:1.78;word-wrap:break-word;overflow-wrap:break-word;overflow:hidden}
@media(max-width:680px){.mb{max-width:91%;font-size:.87rem;padding:9px 11px}}
.mr.user .mb{background:var(--ac);color:#fff;border-bottom-left-radius:3px}
.mr.assistant .mb{background:var(--bg2);border:1px solid var(--ln2);color:var(--t0);border-bottom-right-radius:3px}
.mr.err .mb{background:var(--rdbg);border:1px solid #ff5f6d28;color:#ff9faa;border-radius:var(--r2)}
/* thinking */
.thinking-block{max-width:80%;background:var(--ambg);border:1px solid #f5a62330;border-radius:var(--r2);overflow:hidden;animation:ms .22s ease}
@media(max-width:680px){.thinking-block{max-width:91%}}
.thinking-header{display:flex;align-items:center;gap:6px;padding:7px 11px;cursor:pointer;user-select:none;transition:.13s}
.thinking-header:hover{background:#f5a62310}
.thinking-icon{color:var(--am);font-size:.75rem;flex-shrink:0}
.thinking-label{font-size:.73rem;font-weight:700;color:var(--am);flex:1}
.thinking-summary{font-size:.67rem;color:var(--t3);margin-right:4px;font-style:italic;max-width:180px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.thinking-toggle{font-size:.65rem;color:var(--t3);transition:transform .2s}
.thinking-block.open .thinking-toggle{transform:rotate(180deg)}
.thinking-body{display:none;padding:6px 11px 9px;border-top:1px solid #f5a62320}
.thinking-block.open .thinking-body{display:block}
.thinking-line{font-size:.74rem;color:var(--t2);line-height:1.65;padding:2px 0}
.thinking-line::before{content:'› ';color:var(--am);opacity:.6;font-family:var(--mono)}
.thinking-block.streaming .thinking-icon{animation:spin 1.4s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
.cur{display:inline-block;width:2px;height:.88em;background:var(--ac);border-radius:1px;margin-right:1px;vertical-align:middle;animation:cr .7s step-end infinite}
@keyframes cr{0%,100%{opacity:1}50%{opacity:0}}
.macts{display:flex;gap:2px;padding:1px 3px;opacity:0;transition:opacity .13s;flex-wrap:wrap}
.mr:hover .macts,.mr:focus-within .macts{opacity:1}
.mr.user .macts{flex-direction:row-reverse}
.ma{display:flex;align-items:center;gap:3px;padding:3px 7px;border-radius:5px;font-size:.65rem;font-weight:600;color:var(--t2);background:var(--bg2);border:1px solid var(--ln2);transition:.13s;white-space:nowrap}
.ma:hover{background:var(--bgh);color:var(--t0)}.ma i{font-size:.6rem}
.ma.rg{color:var(--ac);border-color:var(--acln)}.ma.rg:hover{background:var(--acbg)}
.ma.pn.on{color:var(--am);border-color:#f5a62335}
.etx{width:100%;background:var(--bg3);border:2px solid var(--ac);border-radius:var(--r2);padding:9px 10px;color:var(--t0);font-family:var(--font);font-size:.88rem;line-height:1.55;resize:none;direction:rtl;min-height:52px}
.etx:focus{outline:none}
.ebtns{display:flex;gap:6px;margin-top:5px;justify-content:flex-end}
.esv{padding:5px 13px;background:var(--ac);color:#fff;border-radius:5px;font-size:.77rem;font-weight:700;transition:.13s}
.esv:hover{background:var(--ach)}
.ecn{padding:5px 13px;background:var(--bg4);color:var(--t1);border-radius:5px;font-size:.77rem;font-weight:600;border:1px solid var(--ln2);transition:.13s}
.ecn:hover{background:var(--bgh)}
.fups{display:flex;flex-wrap:wrap;gap:5px;padding:1px 3px;margin-top:2px;animation:fup .3s ease}
.fup{padding:4px 10px;background:var(--bg2);border:1px solid var(--ln2);border-radius:14px;font-size:.72rem;font-weight:600;color:var(--tl);transition:.13s}
.fup:hover{background:var(--tlbg);border-color:#00d4ff35;transform:translateY(-1px)}
.mb h1,.mb h2,.mb h3{font-weight:800;margin:10px 0 4px;line-height:1.3}
.mb h1{font-size:1.1em}.mb h2{font-size:1em}.mb h3{font-size:.95em}
.mb p{margin:4px 0}
.mb ul,.mb ol{padding-right:16px;margin:5px 0}.mb li{margin:3px 0;line-height:1.65}
.mb blockquote{border-right:3px solid var(--ac);padding-right:9px;margin:5px 0;color:var(--t2);font-style:italic}
.mb strong{font-weight:700;color:var(--tl)}.mb em{font-style:italic;color:var(--am)}
.mb table{width:100%;border-collapse:collapse;margin:7px 0;font-size:.82em;display:block;overflow-x:auto}
.mb th{background:var(--bg4);padding:5px 9px;border:1px solid var(--ln2);font-weight:700;text-align:right;white-space:nowrap}
.mb td{padding:5px 9px;border:1px solid var(--ln)}.mb tr:nth-child(even){background:var(--bg3)}
.mb a{color:var(--tl)}.mb hr{border:none;border-top:1px solid var(--ln2);margin:9px 0}
.mb code:not(pre code){background:#fff1;padding:2px 5px;border-radius:4px;font-family:var(--mono);font-size:.8em;border:1px solid var(--ln2)}
.mr.user .mb code:not(pre code){background:#fff2;border-color:#fff2}
.cbw{position:relative;margin:8px 0;border-radius:var(--r2);overflow:hidden;border:1px solid #30363d;max-width:100%}
.cbh{display:flex;align-items:center;justify-content:space-between;padding:7px 12px;background:#161b22;border-bottom:1px solid #30363d;gap:8px}
.cb-lang{font-family:var(--mono);font-size:.68rem;color:#58a6ff;font-weight:500;display:flex;align-items:center;gap:5px}
.cb-lang::before{content:'';width:7px;height:7px;border-radius:50%;background:currentColor;opacity:.65;flex-shrink:0}
.cb-lang.l-html::before{background:#e44d26}.cb-lang.l-css::before{background:#264de4}
.cb-lang.l-js::before,.cb-lang.l-javascript::before{background:#f7df1e}
.cb-lang.l-python::before{background:#3572A5}
.cb-lang.l-bash::before,.cb-lang.l-sh::before{background:#4EAA25}
.cb-btns{display:flex;gap:4px}
.cb-copy{display:flex;align-items:center;gap:3px;padding:3px 8px;background:#21262d;border:1px solid #30363d;border-radius:5px;color:#8b949e;font-size:.66rem;font-family:var(--font);transition:.13s;cursor:pointer}
.cb-copy:hover{background:#30363d;color:#e6edf3;border-color:#6e7681}
.cb-run{display:flex;align-items:center;gap:3px;padding:3px 9px;background:#1f6feb18;border:1px solid #1f6feb50;border-radius:5px;color:#58a6ff;font-size:.66rem;font-family:var(--font);transition:.13s;cursor:pointer}
.cb-run:hover{background:#1f6feb30;border-color:#1f6feb}
.cbw pre{margin:0!important;padding:14px!important;background:#0d1117!important;font-size:.79rem!important;line-height:1.65!important;overflow-x:auto}
.cbw pre code{font-family:var(--mono)!important;white-space:pre;word-break:normal}
.artifact{display:flex;align-items:center;gap:10px;padding:10px 13px;background:var(--bg2);border:1px solid var(--ln2);border-right:3px solid var(--ac);border-radius:var(--r2);margin-top:8px;max-width:80%;animation:fup .3s ease}
@media(max-width:680px){.artifact{max-width:91%}}
.art-icon{width:34px;height:34px;border-radius:8px;background:var(--acbg);border:1px solid var(--acln);display:flex;align-items:center;justify-content:center;font-size:.85rem;color:var(--ac);flex-shrink:0}
.art-info{flex:1;min-width:0}
.art-name{font-size:.8rem;font-weight:700;color:var(--t0);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.art-meta{font-size:.64rem;color:var(--t3);margin-top:2px}
.art-btns{display:flex;gap:4px;flex-shrink:0;flex-wrap:wrap}
.ab{display:flex;align-items:center;gap:3px;padding:4px 8px;border-radius:5px;font-size:.66rem;font-weight:600;transition:.13s;white-space:nowrap}
.ab i{font-size:.6rem}
.ab.view{background:var(--acbg);border:1px solid var(--acln);color:var(--ac)}.ab.view:hover{background:var(--ac);color:#fff}
.ab.copy{background:var(--bg3);border:1px solid var(--ln2);color:var(--t2)}.ab.copy:hover{background:var(--bgh);color:var(--t0)}
.ab.run{background:#1f6feb18;border:1px solid #1f6feb50;color:#58a6ff}.ab.run:hover{background:#1f6feb;color:#fff}
.ab.dl{background:var(--gnbg);border:1px solid #22d48a35;color:var(--gn)}.ab.dl:hover{background:var(--gn);color:#000}
/* typing indicator */
.typing-ind{display:flex;align-items:center;gap:8px;padding:9px 13px;background:var(--bg2);border:1px solid var(--ln2);border-radius:var(--r3);border-bottom-right-radius:3px;width:fit-content}
.typing-dots{display:flex;gap:4px}
.typing-dot{width:7px;height:7px;border-radius:50%;background:var(--ac);animation:td 1.4s ease infinite}
.typing-dot:nth-child(2){animation-delay:.2s}.typing-dot:nth-child(3){animation-delay:.4s}
@keyframes td{0%,80%,100%{transform:scale(.6);opacity:.3}40%{transform:scale(1.2);opacity:1}}
/* scroll button */
#scrbtn{position:absolute;bottom:14px;left:50%;transform:translateX(-50%);display:none;align-items:center;gap:4px;padding:6px 14px;background:var(--bg3);border:1px solid var(--ln3);border-radius:16px;font-size:.73rem;font-weight:700;color:var(--t1);box-shadow:var(--s2);transition:.13s;z-index:5;white-space:nowrap}
#scrbtn:hover{background:var(--bgh)}
#scrbtn.on{display:flex}
#ia{background:var(--bg1);border-top:1px solid var(--ln);padding:9px 10px 11px;padding-bottom:calc(11px + env(safe-area-inset-bottom));flex-shrink:0}
.ia-in{max-width:780px;margin:0 auto}
.ib{display:flex;align-items:flex-end;gap:6px;background:var(--bg2);border:1.5px solid var(--ln2);border-radius:var(--r4);padding:7px 8px;transition:.13s}
.ib:focus-within{border-color:var(--ac);box-shadow:0 0 0 3px #7c6fff16}
#ci{flex:1;background:none;border:none;color:var(--t0);font-size:16px;line-height:1.55;resize:none;min-height:24px;max-height:150px;direction:rtl;padding:2px 0;scrollbar-width:none;font-family:var(--font)}
@media(min-width:480px){#ci{font-size:.92rem}}
#ci::-webkit-scrollbar{display:none}
#ci::placeholder{color:var(--t3)}
#ci:disabled{opacity:.4}
.ibtn{width:36px;height:36px;border-radius:9px;display:flex;align-items:center;justify-content:center;font-size:.85rem;transition:.13s;flex-shrink:0}
#sndbtn{background:var(--ac);color:#fff;box-shadow:0 2px 9px #7c6fff40}
#sndbtn:hover:not(:disabled){background:var(--ach)}
#sndbtn:active:not(:disabled){transform:scale(.9)}
#sndbtn:disabled{opacity:.3;cursor:default;box-shadow:none}
#stopbtn{background:var(--rdbg);color:var(--rd);border:1px solid #ff5f6d28;display:none}
#stopbtn.on{display:flex}
#stopbtn:hover{background:#ff5f6d1e}
.ifoot{display:flex;align-items:center;justify-content:space-between;margin-top:5px;padding:0 1px;font-size:.66rem;color:var(--t3)}
.ihnts{display:flex;align-items:center;gap:7px}
@media(max-width:480px){.ihnts{display:none}}
kbd{background:var(--bg3);border:1px solid var(--ln2);border-radius:3px;padding:1px 4px;font-family:var(--mono);font-size:.59rem}
.tkc span{color:var(--ac);font-weight:700}
#ov{display:none;position:fixed;inset:0;background:#0009;z-index:40}
#ov.on{display:block}
/* sandbox modal */
#sbmod{position:fixed;inset:0;background:#000c;z-index:200;display:none;align-items:center;justify-content:center;padding:12px}
#sbmod.open{display:flex}
.sbw{background:var(--bg1);border:1px solid var(--ln2);border-radius:var(--r3);width:100%;max-width:1000px;height:90vh;max-height:90vh;display:flex;flex-direction:column;box-shadow:var(--s3);overflow:hidden}
.sbw-head{display:flex;align-items:center;gap:8px;padding:10px 14px;background:var(--bg2);border-bottom:1px solid var(--ln2);flex-shrink:0}
.sbw-title{flex:1;font-size:.85rem;font-weight:700;display:flex;align-items:center;gap:7px}
.sbw-title i{color:var(--ac)}
.sb-tabs{display:flex;gap:3px}
.sb-tab{padding:5px 12px;border-radius:5px;font-size:.74rem;font-weight:600;color:var(--t2);transition:.13s;cursor:pointer}
.sb-tab.on{background:var(--acbg);color:var(--ac);border:1px solid var(--acln)}
.sb-tab:hover:not(.on){background:var(--bgh);color:var(--t1)}
.sbw-close{width:28px;height:28px;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.8rem;color:var(--t2);transition:.13s}
.sbw-close:hover{background:var(--rdbg);color:var(--rd)}
.sbw-body{flex:1;display:flex;overflow:hidden}
@media(max-width:600px){.sbw-body{flex-direction:column}}
.sb-code-pane{flex:1;min-width:0;display:flex;flex-direction:column;border-left:1px solid var(--ln2)}
.sb-prev-pane{flex:1;min-width:0;display:flex;flex-direction:column}
.sb-pane-lbl{padding:6px 12px;font-size:.66rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--t3);background:var(--bg2);border-bottom:1px solid var(--ln);flex-shrink:0}
.sb-pane-lbl i{margin-left:4px}
.sb-editor{flex:1;resize:none;background:#0d1117;border:none;color:#e6edf3;font-family:var(--mono);font-size:.8rem;line-height:1.65;padding:14px;overflow:auto;direction:ltr;text-align:left}
.sb-editor:focus{outline:none}
.sb-frame{flex:1;border:none;background:#fff}
.sbw-foot{display:flex;align-items:center;gap:8px;padding:8px 14px;background:var(--bg2);border-top:1px solid var(--ln2);flex-shrink:0}
.sb-run{display:flex;align-items:center;gap:5px;padding:6px 14px;background:var(--gn);color:#000;border-radius:var(--r1);font-size:.8rem;font-weight:700;transition:.13s}
.sb-run:hover{background:#2fea9e}.sb-run:active{transform:scale(.95)}
.sb-stat{font-size:.7rem;color:var(--t3);flex:1}
/* rename modal */
.modal{position:fixed;inset:0;background:#0009;z-index:100;display:flex;align-items:center;justify-content:center;padding:20px}
.mbox{background:var(--bg2);border:1px solid var(--ln2);border-radius:var(--r3);padding:20px;width:100%;max-width:340px;box-shadow:var(--s3)}
.mttl{font-size:.94rem;font-weight:800;margin-bottom:12px}
.mi{width:100%;padding:8px 10px;background:var(--bg3);border:1.5px solid var(--ln2);border-radius:var(--r1);color:var(--t0);font-size:.88rem;direction:rtl;transition:.13s}
.mi:focus{border-color:var(--ac);outline:none}
.mbtns{display:flex;gap:7px;margin-top:12px;justify-content:flex-end}
.mok{padding:7px 15px;background:var(--ac);color:#fff;border-radius:var(--r1);font-weight:700;font-size:.82rem;transition:.13s}
.mok:hover{background:var(--ach)}
.mcc{padding:7px 15px;background:var(--bg4);color:var(--t1);border-radius:var(--r1);font-weight:600;font-size:.82rem;border:1px solid var(--ln2);transition:.13s}
.mcc:hover{background:var(--bgh)}
/* toast */
#tw{position:fixed;bottom:72px;left:50%;transform:translateX(-50%);z-index:300;display:flex;flex-direction:column;align-items:center;gap:5px;pointer-events:none}
.toast{padding:7px 15px;background:var(--bg3);border:1px solid var(--ln2);border-radius:16px;font-size:.77rem;font-weight:600;color:var(--t1);box-shadow:var(--s2);white-space:nowrap;animation:tp .2s ease}
.toast.ok{color:var(--gn);border-color:#22d48a35}.toast.er{color:var(--rd);border-color:#ff5f6d35}
@keyframes tp{from{opacity:0;transform:translateY(7px)scale(.87)}to{opacity:1;transform:none}}
/* show-thinking toggle in sidebar */
.think-tog-row{display:flex;align-items:center;justify-content:space-between;padding:6px 8px;background:var(--bg3);border:1px solid var(--ln2);border-radius:var(--r1)}
</style>
</head>
<body>
<div id="app">
<aside id="sb">
<div class="sb-head">
<button class="new-btn" id="newConvBtn"><i class="fa-solid fa-plus"></i> محادثة جديدة</button>
</div>
<div class="cl" id="cl"></div>
<div class="sb-bot">
<div><div class="lbl"><i class="fa-solid fa-plug"></i>المزود</div>
<select class="sel" id="psel"><option value="">⏳ جاري التحميل...</option></select></div>
<div><div class="lbl"><i class="fa-solid fa-brain"></i>النموذج</div>
<select class="sel" id="msel"><option value="">اختر مزوداً</option></select></div>
<div>
<div class="think-tog-row">
<span class="tog-lbl"><i class="fa-solid fa-brain" style="color:var(--am)"></i>عرض التفكير</span>
<label class="tog"><input type="checkbox" id="showThinkTog" checked><div class="tog-t"></div></label>
</div>
</div>
<div class="tog-row">
<span class="tog-lbl"><i class="fa-solid fa-bolt"></i>البث المباشر</span>
<label class="tog"><input type="checkbox" id="stog" checked><div class="tog-t"></div></label>
</div>
<div class="tog-row">
<span class="tog-lbl"><i class="fa-solid fa-lightbulb"></i>اقتراحات المتابعة</span>
<label class="tog"><input type="checkbox" id="fupTog" checked><div class="tog-t"></div></label>
</div>
<div class="tog-row">
<span class="tog-lbl"><i class="fa-solid fa-circle-half-stroke"></i>الوضع الفاتح</span>
<label class="tog"><input type="checkbox" id="themeTog"><div class="tog-t"></div></label>
</div>
<div><div class="lbl"><i class="fa-solid fa-scroll"></i>تعليمات النظام</div>
<textarea class="sys-ta" id="sysp" placeholder="أنت مساعد ذكاء اصطناعي مفيد..."></textarea></div>
</div>
</aside>
<div id="ov"></div>
<div id="main">
<header id="hdr">
<button class="hb" id="sbToggleBtn"><i class="fa-solid fa-bars"></i></button>
<div class="logo"><div class="logo-mk"><i class="fa-solid fa-robot"></i></div>g4f<span class="logo-ac">pro</span></div>
<div class="hdr-m"><div class="hdr-t" id="htitle">محادثة جديدة</div></div>
<div class="hdr-r">
<div class="hst">متصل</div>
<div class="ppill"><div class="pd"></div><span id="ptxt">Auto</span></div>
<button class="hb" id="thbtn"><i class="fa-solid fa-moon"></i></button>
<button class="hb" id="reloadBtn"><i class="fa-solid fa-rotate" id="rlic"></i></button>
</div>
</header>
<div id="mwrap">
<div id="msgs"></div>
<button id="scrbtn"><i class="fa-solid fa-arrow-down"></i> للأسفل</button>
</div>
<div id="ia">
<div class="ia-in">
<div class="ib">
<textarea id="ci" placeholder="اكتب رسالتك..." rows="1" disabled></textarea>
<button class="ibtn" id="stopbtn"><i class="fa-solid fa-stop"></i></button>
<button class="ibtn" id="sndbtn" disabled><i class="fa-solid fa-paper-plane"></i></button>
</div>
<div class="ifoot">
<div class="ihnts"><span><kbd>Enter</kbd> إرسال</span><span><kbd>Shift+Enter</kbd> سطر</span></div>
<div class="tkc">~<span id="tkc">0</span> توكن</div>
</div>
</div>
</div>
</div>
</div>
<!-- Rename modal -->
<div class="modal" id="rmod" style="display:none">
<div class="mbox">
<div class="mttl">إعادة تسمية المحادثة</div>
<input class="mi" id="rmi" type="text" placeholder="اسم المحادثة...">
<div class="mbtns">
<button class="mcc" id="renCancelBtn">إلغاء</button>
<button class="mok" id="renSaveBtn">حفظ</button>
</div>
</div>
</div>
<!-- Sandbox modal -->
<div id="sbmod">
<div class="sbw">
<div class="sbw-head">
<div class="sbw-title"><i class="fa-solid fa-code"></i><span id="sb-fn">index.html</span></div>
<div class="sb-tabs">
<div class="sb-tab on" data-tab="split">تقسيم</div>
<div class="sb-tab" data-tab="code">كود</div>
<div class="sb-tab" data-tab="preview">معاينة</div>
</div>
<button class="sbw-close" id="sbCloseBtn"><i class="fa-solid fa-xmark"></i></button>
</div>
<div class="sbw-body">
<div class="sb-code-pane" id="sb-cpane">
<div class="sb-pane-lbl"><i class="fa-solid fa-code" style="color:var(--ac)"></i>محرر الكود</div>
<textarea class="sb-editor" id="sb-ed" spellcheck="false" dir="ltr"></textarea>
</div>
<div class="sb-prev-pane" id="sb-ppane">
<div class="sb-pane-lbl"><i class="fa-solid fa-eye" style="color:var(--tl)"></i>معاينة حية</div>
<iframe class="sb-frame" id="sb-iframe" sandbox="allow-scripts allow-same-origin" title="preview"></iframe>
</div>
</div>
<div class="sbw-foot">
<button class="sb-run" id="sbRunBtn"><i class="fa-solid fa-play"></i> تشغيل</button>
<span class="sb-stat" id="sb-stat">جاهز</span>
</div>
</div>
</div>
<div id="tw"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/11.1.1/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script>
'use strict';
/* ═══════════════════════════════════════════════════════
MODULE: Utils
═══════════════════════════════════════════════════════ */
const Utils = (() => {
function esc(s) {
return String(s)
.replace(/&/g,'&amp;').replace(/</g,'&lt;')
.replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
function fmtBytes(n) { return n>1024?(n/1024).toFixed(1)+'KB':n+'B'; }
function uid() { return 'c-'+Date.now()+'-'+Math.random().toString(36).slice(2,8); }
function groupByDate(convs) {
const now=Date.now();
const groups={'اليوم':[],'أمس':[],'هذا الأسبوع':[],'أقدم':[]};
for(const c of convs){
const d=now-c.created;
if(d<86400000) groups['اليوم'].push(c);
else if(d<172800000) groups['أمس'].push(c);
else if(d<604800000) groups['هذا الأسبوع'].push(c);
else groups['أقدم'].push(c);
}
return groups;
}
function suggestFollowUps(text) {
const l=text.toLowerCase();
if(l.includes('html')||l.includes('css')||l.includes('كود')||l.includes('python')||l.includes('javascript'))
return ['اشرح هذا الكود','كيف أحسّنه؟','أضف ميزة جديدة'];
if(l.includes('شرح')||l.includes('مفهوم'))
return ['أعطني مثالاً عملياً','ما الفرق مع البدائل؟'];
return ['أخبرني أكثر','اشرح بمثال'];
}
function wrapForPreview(code,lang) {
if(lang==='css') return `<!DOCTYPE html><html><head><style>${code}</style></head><body></body></html>`;
if(lang==='js'||lang==='javascript') return `<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><div id="out"></div><script>${code}<\/script></body></html>`;
return code;
}
function extractArtifacts(text) {
const WEB = new Set(['html', 'css', 'js', 'javascript']);
const results = [];
// هذا التعبير يضمن أن الكود لا يتمدد ويخرب الزوايا
const re = /```(\w+)?\n([\s\S]*?)```/g;
let m;
while ((m = re.exec(text)) !== null) {
const lang = (m[1] || 'text').toLowerCase();
const code = m[2].trim();
// منع الانهيار إذا كان الكود طويلاً جداً وعرضياً
if (code.length < 30) continue;
results.push({
lang,
code,
filename: `code.${lang === 'javascript' ? 'js' : lang}`
});
}
return results;
}
return {esc,fmtBytes,uid,groupByDate,suggestFollowUps,wrapForPreview,extractArtifacts};
})();
/* ═══════════════════════════════════════════════════════
MODULE: Store
═══════════════════════════════════════════════════════ */
const Store = (() => {
const get=(k,d=null)=>{try{const v=localStorage.getItem(k);return v===null?d:JSON.parse(v);}catch{return d;}};
const set=(k,v)=>{try{localStorage.setItem(k,JSON.stringify(v));}catch{}};
return {
getConvs: () => get('g4c',[]),
setConvs: (v) => set('g4c',v),
getActiveId: () => get('g4a',''),
setActiveId: (v) => set('g4a',v),
getTheme: () => get('g4t','dark'),
setTheme: (v) => set('g4t',v),
getProvider: () => get('g4p',''),
setProvider: (v) => set('g4p',v),
getModel: () => get('g4m',''),
setModel: (v) => set('g4m',v),
getStream: () => get('g4s','1')==='1',
setStream: (v) => set('g4s',v?'1':'0'),
getShowThink: () => get('g4th','1')==='1',
setShowThink: (v) => set('g4th',v?'1':'0'),
getShowFup: () => get('g4fu','1')==='1',
setShowFup: (v) => set('g4fu',v?'1':'0'),
getDraft: () => get('g4d',''),
setDraft: (v) => set('g4d',v),
};
})();
/* ═══════════════════════════════════════════════════════
MODULE: API — все вызовы к серверу здесь
Сервер: Flask g4fpro v2.0
Эндпоинты:
GET /api/providers → {ok, providers:{name:{models,type,needs_auth}}, total}
GET /api/models/<provider> → {ok, models:[...]}
POST /api/reload → {ok, providers:N}
POST /chat → {ok, reply, thinking:[], model, provider, time, conversation_id}
POST /chat/stream → SSE: start | thinking | chunk | done | error
═══════════════════════════════════════════════════════ */
const API = (() => {
const post=(url,body)=>fetch(url,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
async function getProviders(){const r=await fetch('/api/providers');return r.json();}
async function getModels(provider){const r=await fetch(`/api/models/${provider}`);return r.json();}
async function reload(){return post('/api/reload',{});}
async function chat(payload){const r=await post('/chat',payload);return r.json();}
function streamChat(payload){return post('/chat/stream',payload);}
return {getProviders,getModels,reload,chat,streamChat};
})();
/* ═══════════════════════════════════════════════════════
MODULE: MD — markdown renderer
═══════════════════════════════════════════════════════ */
const MD = (() => {
const RUNNABLE=new Set(['html','css','js','javascript','jsx','tsx','svg']);
function _hi(code,lang){
try{
if(lang&&lang!=='text'&&hljs.getLanguage(lang)) return hljs.highlight(code,{language:lang}).value;
return hljs.highlightAuto(code).value;
}catch{return Utils.esc(code);}
}
function _buildBlock(lang,raw){
const hi=_hi(raw,lang);
const runBtn=RUNNABLE.has(lang)
?`<button class="cb-run" data-action="run"><i class="fa-solid fa-play"></i> تشغيل</button>`:''
return `<div class="cbw">
<div class="cbh">
<span class="cb-lang l-${Utils.esc(lang)}" data-lang="${Utils.esc(lang)}">${Utils.esc(lang)||'text'}</span>
<div class="cb-btns">${runBtn}<button class="cb-copy" data-action="copy"><i class="fa-regular fa-copy"></i> نسخ</button></div>
</div>
<pre><code class="hljs language-${Utils.esc(lang)}">${hi}</code></pre>
</div>`;
}
function render(text){
if(!text)return'';
const blocks=[];
const ph=(lang,code)=>{const i=blocks.length;blocks.push({lang:(lang||'text').toLowerCase(),code:code.trim()});return`%%CB${i}%%`;};
const withPH = text.replace(/```(\w+)?\n([\s\S]+?)\n```/g, (_, lang, code) => ph(lang, code));
let html;
try{html=marked.parse(withPH);}catch{html=Utils.esc(withPH);}
html=html.replace(/%%CB(\d+)%%/g,(_,i)=>{const{lang,code}=blocks[parseInt(i)];return _buildBlock(lang,code);});
return html;
}
return {render};
})();
/* ═══════════════════════════════════════════════════════
MODULE: Sandbox
═══════════════════════════════════════════════════════ */
const Sandbox = (() => {
let _deb=null;
const el={
modal:()=>document.getElementById('sbmod'),
fname:()=>document.getElementById('sb-fn'),
editor:()=>document.getElementById('sb-ed'),
frame:()=>document.getElementById('sb-iframe'),
status:()=>document.getElementById('sb-stat'),
cpane:()=>document.getElementById('sb-cpane'),
ppane:()=>document.getElementById('sb-ppane'),
};
function _write(code){
try{
const doc=el.frame().contentDocument||el.frame().contentWindow.document;
doc.open();doc.write(code);doc.close();
el.status().textContent='✅ تم التشغيل';el.status().style.color='var(--gn)';
}catch(e){el.status().textContent='❌ '+e.message.slice(0,60);el.status().style.color='var(--rd)';}
}
function open(code,filename){
el.fname().textContent=filename||'code';
el.editor().value=code||'';
el.modal().classList.add('open');
setTab('split');_write(code);
el.editor().oninput=()=>{clearTimeout(_deb);_deb=setTimeout(()=>_write(el.editor().value),400);};
}
function close(){el.modal().classList.remove('open');el.editor().oninput=null;clearTimeout(_deb);}
function run(){_write(el.editor().value);}
function setTab(tab){
document.querySelectorAll('.sb-tab').forEach(t=>t.classList.toggle('on',t.dataset.tab===tab));
const show=(pane,vis)=>{pane.style.display=vis?'flex':'none';};
show(el.cpane(),tab!=='preview');show(el.ppane(),tab!=='code');
}
return {open,close,run,setTab};
})();
/* ═══════════════════════════════════════════════════════
MODULE: UI — DOM builders
═══════════════════════════════════════════════════════ */
const UI = (() => {
function msgHeader(role,model,time,pinned){
const hdr=document.createElement('div');hdr.className='mhdr';
const av=document.createElement('div');av.className=role==='user'?'mav u':'mav b';
av.innerHTML=role==='user'?'<i class="fa-solid fa-user"></i>':'<i class="fa-solid fa-robot"></i>';
const ts=document.createElement('span');ts.textContent=time;
if(role==='user'){
if(pinned)hdr.appendChild(_pin());hdr.appendChild(ts);hdr.appendChild(av);
} else {
hdr.appendChild(av);
if(model){const tag=document.createElement('span');tag.className='mtag';tag.textContent=model;hdr.appendChild(tag);}
hdr.appendChild(ts);if(pinned)hdr.appendChild(_pin());
}
return hdr;
}
function _pin(){const s=document.createElement('span');s.className='pinbdg';s.innerHTML='<i class="fa-solid fa-thumbtack"></i>';return s;}
function msgActions(role,idx,pinned){
const div=document.createElement('div');div.className='macts';
if(role==='user'){
div.innerHTML=`
<button class="ma" data-action="copy" data-idx="${idx}"><i class="fa-regular fa-copy"></i> نسخ</button>
<button class="ma" data-action="edit" data-idx="${idx}"><i class="fa-solid fa-pen"></i> تعديل</button>
<button class="ma pn${pinned?' on':''}" data-action="pin" data-idx="${idx}"><i class="fa-solid fa-thumbtack"></i></button>`;
} else {
div.innerHTML=`
<button class="ma" data-action="copy" data-idx="${idx}"><i class="fa-regular fa-copy"></i> نسخ</button>
<button class="ma rg" data-action="regen" data-idx="${idx}"><i class="fa-solid fa-rotate-right"></i> إعادة</button>
<button class="ma pn${pinned?' on':''}" data-action="pin" data-idx="${idx}"><i class="fa-solid fa-thumbtack"></i></button>`;
}
return div;
}
function thinkingBlock(lines,summary){
const div=document.createElement('div');div.className='thinking-block';
const sumText=summary||(lines.length?lines[0].slice(0,60)+'…':'');
const linesHTML=lines.map(l=>`<div class="thinking-line">${Utils.esc(l)}</div>`).join('');
div.innerHTML=`
<div class="thinking-header">
<i class="thinking-icon fa-solid fa-brain"></i>
<span class="thinking-label">تفكير النموذج (${lines.length} سطر)</span>
<span class="thinking-summary">${Utils.esc(sumText)}</span>
<i class="thinking-toggle fa-solid fa-chevron-down"></i>
</div>
<div class="thinking-body">${linesHTML}</div>`;
div.querySelector('.thinking-header').addEventListener('click',()=>div.classList.toggle('open'));
return div;
}
function artifactCard(art){
const ICONS={html:'fa-file-code',css:'fa-palette',js:'fa-square-js',javascript:'fa-square-js',jsx:'fa-atom',tsx:'fa-atom',svg:'fa-image'};
const icon=ICONS[art.lang]||'fa-code';
const div=document.createElement('div');div.className='artifact';div._artData=art;
div.innerHTML=`
<div class="art-icon"><i class="fa-solid ${icon}"></i></div>
<div class="art-info">
<div class="art-name">${Utils.esc(art.filename)}</div>
<div class="art-meta">${art.lang.toUpperCase()} · ${Utils.fmtBytes(art.bytes)}</div>
</div>
<div class="art-btns">
<button class="ab view" data-action="art-view"><i class="fa-solid fa-eye"></i> عرض</button>
<button class="ab copy" data-action="art-copy"><i class="fa-regular fa-copy"></i> نسخ</button>
<button class="ab run" data-action="art-run"><i class="fa-solid fa-play"></i> تشغيل</button>
<button class="ab dl" data-action="art-dl"><i class="fa-solid fa-download"></i></button>
</div>`;
return div;
}
function typingRow(id,label){
const row=document.createElement('div');row.className='mr assistant';row.id=id;
const lbl=label||'يكتب...';
row.innerHTML=`
<div class="mhdr"><div class="mav b"><i class="fa-solid fa-robot"></i></div></div>
<div class="typing-ind">
<div class="typing-dots"><div class="typing-dot"></div><div class="typing-dot"></div><div class="typing-dot"></div></div>
<span style="font-size:.73rem;color:var(--t2)">${Utils.esc(lbl)}</span>
</div>`;
return row;
}
function welcomeScreen(){
const div=document.createElement('div');div.id='wlc';
div.innerHTML=`
<div class="wico"><i class="fa-solid fa-robot"></i></div>
<div class="wt">مرحباً في <span>g4fpro</span></div>
<div class="ws">واجهة AI تدعم جميع مزودي g4f تلقائياً — بدون مفاتيح API</div>
<div class="sg">
<div class="sc" data-prompt="اشرح الذكاء الاصطناعي بأسلوب بسيط"><span class="sc-i">🤖</span><div class="sc-t">اشرح الذكاء الاصطناعي</div><div class="sc-s">تفسير مبسط</div></div>
<div class="sc" data-prompt="اكتب صفحة HTML احترافية مع CSS"><span class="sc-i">💻</span><div class="sc-t">صفحة HTML + CSS</div><div class="sc-s">مع معاينة مباشرة</div></div>
<div class="sc" data-prompt="اكتب رسالة بريد إلكتروني احترافية لطلب اجتماع"><span class="sc-i">✉️</span><div class="sc-t">رسالة احترافية</div><div class="sc-s">بريد عمل</div></div>
<div class="sc" data-prompt="ما أفضل 5 مكتبات Python لعام 2025 مع أمثلة؟"><span class="sc-i">📊</span><div class="sc-t">أفضل مكتبات 2025</div><div class="sc-s">Python</div></div>
</div>`;
return div;
}
function followUpRow(suggestions){
const div=document.createElement('div');div.className='fups';
for(const s of suggestions){
const btn=document.createElement('button');
btn.className='fup';btn.textContent=s;btn.dataset.prompt=s;
div.appendChild(btn);
}
return div;
}
function toast(container,msg,type=''){
const el=document.createElement('div');el.className=`toast ${type}`.trim();el.textContent=msg;
container.appendChild(el);
setTimeout(()=>{el.style.transition='opacity .3s';el.style.opacity='0';setTimeout(()=>el.remove(),300);},2400);
}
return {msgHeader,msgActions,thinkingBlock,artifactCard,typingRow,welcomeScreen,followUpRow,toast};
})();
/* ═══════════════════════════════════════════════════════
MODULE: App
═══════════════════════════════════════════════════════ */
const App = (() => {
// ── State ──────────────────────────────────────
const state = {
convs: Store.getConvs(),
activeId: Store.getActiveId(),
provs: {},
sbOpen: window.innerWidth > 680,
busy: false,
stopReq: false,
renId: null,
userScrolled:false,
};
const active=()=>state.convs.find(c=>c.id===state.activeId)||null;
function persist(){Store.setConvs(state.convs);Store.setActiveId(state.activeId);}
function getServerId(conv){if(!conv.serverId){conv.serverId=Utils.uid();persist();}return conv.serverId;}
// ── DOM refs ───────────────────────────────────
const $=id=>document.getElementById(id);
const DOM={
msgs: ()=>$('msgs'), mwrap: ()=>$('mwrap'),
ci: ()=>$('ci'), sndbtn: ()=>$('sndbtn'),
stopbtn:()=>$('stopbtn'),psel: ()=>$('psel'),
msel: ()=>$('msel'), stog: ()=>$('stog'),
sysp: ()=>$('sysp'), htitle: ()=>$('htitle'),
tw: ()=>$('tw'), cl: ()=>$('cl'),
ptxt: ()=>$('ptxt'), scrbtn: ()=>$('scrbtn'),
rmod: ()=>$('rmod'), rmi: ()=>$('rmi'),
rlic: ()=>$('rlic'), sb: ()=>$('sb'),
ov: ()=>$('ov'), thbtn: ()=>$('thbtn'),
showThinkTog: ()=>$('showThinkTog'),
fupTog: ()=>$('fupTog'),
themeTog: ()=>$('themeTog'),
};
// ── Scroll ─────────────────────────────────────
function scrollBot(force=false){
if(!force&&state.userScrolled)return;
const w=DOM.mwrap();
w.scrollTo({top:w.scrollHeight,behavior:'smooth'});
}
function _onScroll(){
const w=DOM.mwrap();
const atBottom=w.scrollHeight-w.scrollTop-w.clientHeight<120;
DOM.scrbtn().classList.toggle('on',!atBottom);
state.userScrolled=!atBottom;
}
// ── Toast ──────────────────────────────────────
function toast(msg,type=''){UI.toast(DOM.tw(),msg,type);}
// ── Busy ───────────────────────────────────────
function setBusy(on){
state.busy=on;
DOM.sndbtn().disabled=on;DOM.ci().disabled=on;
DOM.stopbtn().classList.toggle('on',on);
if(!on){state.stopReq=false;DOM.ci().focus();}
}
function enableInput(){
DOM.ci().disabled=false;DOM.sndbtn().disabled=false;DOM.ci().focus();
const draft=Store.getDraft();
if(draft){DOM.ci().value=draft;_resizeTa(DOM.ci());_updTok(draft);}
}
// ── Input helpers ──────────────────────────────
function _resizeTa(el){el.style.height='auto';el.style.height=Math.min(el.scrollHeight,150)+'px';}
function _updTok(text){$('tkc').textContent=Math.round(text.length/4);}
function _onInput(){const ci=DOM.ci();_resizeTa(ci);_updTok(ci.value);Store.setDraft(ci.value);}
// ── Conversations ──────────────────────────────
function newConv(){
const id='c'+Date.now();
const conv={id,title:'محادثة جديدة',msgs:[],created:Date.now(),pins:[]};
state.convs.unshift(conv);setActive(id);persist();renderConvList();
toast('✨ محادثة جديدة','ok');
if(window.innerWidth<=680)closeSidebar();
}
function setActive(id){
state.activeId=id;persist();renderConvList();renderMessages();
const c=state.convs.find(x=>x.id===id);
DOM.htitle().textContent=c?c.title:'محادثة';
}
function deleteConv(id){
if(!confirm('حذف هذه المحادثة نهائياً؟'))return;
state.convs=state.convs.filter(c=>c.id!==id);
if(state.activeId===id)state.activeId=state.convs[0]?.id||'';
persist();renderConvList();renderMessages();
}
function startRename(id){
state.renId=id;const c=state.convs.find(x=>x.id===id);
DOM.rmi().value=c?.title||'';DOM.rmod().style.display='flex';
setTimeout(()=>DOM.rmi().focus(),40);
}
function confirmRename(){
const v=DOM.rmi().value.trim();if(!v||!state.renId){closeModal();return;}
const c=state.convs.find(x=>x.id===state.renId);if(c)c.title=v;
persist();renderConvList();
if(state.activeId===state.renId)DOM.htitle().textContent=v;
closeModal();
}
function closeModal(){DOM.rmod().style.display='none';state.renId=null;}
// ── Render conv list ───────────────────────────
function renderConvList(){
const el=DOM.cl();
if(!state.convs.length){
el.innerHTML='<div style="padding:22px 10px;text-align:center;color:var(--t3);font-size:.77rem"><i class="fa-regular fa-comment" style="font-size:1.3rem;display:block;margin-bottom:8px;opacity:.28"></i>لا توجد محادثات</div>';
return;
}
const frag=document.createDocumentFragment();
const groups=Utils.groupByDate(state.convs);
for(const [label,list] of Object.entries(groups)){
if(!list.length)continue;
const grpEl=document.createElement('div');grpEl.className='grp';grpEl.textContent=label;frag.appendChild(grpEl);
for(const conv of list){
const lm=conv.msgs[conv.msgs.length-1];
const prev=lm?lm.content.slice(0,32)+(lm.content.length>32?'…':''):'لا توجد رسائل';
const item=document.createElement('div');
item.className=`ci${conv.id===state.activeId?' on':''}`;item.dataset.cid=conv.id;
item.innerHTML=`
<div class="ci-dot"></div>
<div class="ci-info">
<div class="ci-title">${Utils.esc(conv.title)}</div>
<div class="ci-sub">${Utils.esc(prev)}</div>
</div>
<div class="ci-acts">
<button class="cia" data-action="rename" data-cid="${conv.id}" title="تسمية"><i class="fa-solid fa-pen"></i></button>
<button class="cia del" data-action="delete" data-cid="${conv.id}" title="حذف"><i class="fa-solid fa-trash"></i></button>
</div>`;
frag.appendChild(item);
}
}
el.innerHTML='';el.appendChild(frag);
}
// ── Render messages ────────────────────────────
function renderMessages(){
const conv=active();const wrap=DOM.msgs();
if(!conv||!conv.msgs.length){wrap.innerHTML='';wrap.appendChild(UI.welcomeScreen());return;}
const frag=document.createDocumentFragment();
conv.msgs.forEach((m,i)=>frag.appendChild(buildMsgRow(m,i)));
wrap.innerHTML='';wrap.appendChild(frag);
state.userScrolled=false;scrollBot(true);
}
function buildMsgRow(msg,idx){
const conv=active();
const pinned=conv?.pins?.includes(idx)||false;
const t=new Date(msg.ts||Date.now()).toLocaleTimeString('ar-EG',{hour:'2-digit',minute:'2-digit'});
const row=document.createElement('div');row.className=`mr ${msg.role}`;row.dataset.i=idx;
if(msg.role!=='err') row.appendChild(UI.msgHeader(msg.role,msg.model||'',t,pinned));
// Thinking block — only if showThinkTog is on and thinking exists
const showThink=DOM.showThinkTog().checked;
if(showThink&&msg.thinking&&msg.thinking.length){
row.appendChild(UI.thinkingBlock(msg.thinking));
}
// Bubble
const bub=document.createElement('div');bub.className='mb';
if(msg.role==='assistant'){
bub.innerHTML=MD.render(msg.content);
} else {
const span=document.createElement('span');
const lines=msg.content.split('\n');
lines.forEach((line,i)=>{
span.appendChild(document.createTextNode(line));
if(i<lines.length-1)span.appendChild(document.createElement('br'));
});
bub.appendChild(span);
}
row.appendChild(bub);
if(msg.role==='user'||msg.role==='assistant') row.appendChild(UI.msgActions(msg.role,idx,pinned));
if(msg.role==='assistant') Utils.extractArtifacts(msg.content).forEach(art=>row.appendChild(UI.artifactCard(art)));
return row;
}
// ── Append user msg without full re-render ─────
function appendUserMsg(msg,idx){
$('wlc')?.remove();
DOM.msgs().appendChild(buildMsgRow(msg,idx));
}
// ── Send ───────────────────────────────────────
async function send(text){
text=(text||DOM.ci().value).trim();
if(!text||state.busy)return;
const prov=DOM.psel().value;const model=DOM.msel().value;
if(!prov){toast('⚠️ اختر مزوداً أولاً','er');return;}
if(!state.activeId||!active())newConv();
const conv=active();if(!conv)return;
$('wlc')?.remove();
const um={role:'user',content:text,ts:Date.now()};
conv.msgs.push(um);
if(conv.msgs.length===1){conv.title=text.slice(0,40)+(text.length>40?'…':'');DOM.htitle().textContent=conv.title;}
persist();renderConvList();
appendUserMsg(um,conv.msgs.length-1);
DOM.ci().value='';Store.setDraft('');_resizeTa(DOM.ci());_updTok('');
setBusy(true);state.userScrolled=false;scrollBot(true);
const lid='tp'+Date.now();
const typRow=UI.typingRow(lid,'يفكر...');
DOM.msgs().appendChild(typRow);scrollBot();
const payload={
message:text,model,provider:prov,
system_prompt:DOM.sysp().value.trim(),
conversation_id:getServerId(conv),
};
try{
if(DOM.stog().checked) await _doStream(payload,lid,conv,model);
else await _doSync(payload,lid,conv,model);
}catch(e){
document.getElementById(lid)?.remove();
const errRow=document.createElement('div');errRow.className='mr err';
errRow.innerHTML=`<div class="mb">❌ ${Utils.esc(e.message)}</div>`;
DOM.msgs().appendChild(errRow);setBusy(false);
}
}
// ── Sync call ──────────────────────────────────
async function _doSync(payload,lid,conv,model){
const data=await API.chat(payload);
document.getElementById(lid)?.remove();
if(data.conversation_id)conv.serverId=data.conversation_id;
const content=data.ok?data.reply:'❌ '+(data.error||'خطأ');
// Server returns thinking as array of strings
const thinking=data.ok?(data.thinking||[]):[];
const bm={role:'assistant',content,thinking,model,ts:Date.now()};
conv.msgs.push(bm);persist();
DOM.msgs().appendChild(buildMsgRow(bm,conv.msgs.length-1));
if(DOM.fupTog().checked) _appendFollowUps(content);
setBusy(false);scrollBot();
}
// ── Stream call ────────────────────────────────
// SSE events from server:
// {type:"start", conversation_id:"...", model:"...", provider:"..."}
// {type:"thinking", lines:["...","..."]}
// {type:"chunk", content:"..."}
// {type:"done"}
// {type:"error", content:"..."}
async function _doStream(payload,lid,conv,model){
state.stopReq=false;
const res=await API.streamChat(payload);
document.getElementById(lid)?.remove();
const bm={role:'assistant',content:'',thinking:[],model,ts:Date.now()};
conv.msgs.push(bm);
const idx=conv.msgs.length-1;
const row=document.createElement('div');row.className='mr assistant';row.dataset.i=idx;
const hdr=document.createElement('div');hdr.className='mhdr';
hdr.innerHTML=`<div class="mav b"><i class="fa-solid fa-robot"></i></div>${model?`<span class="mtag">${Utils.esc(model)}</span>`:''}`;
row.appendChild(hdr);
// Thinking placeholder (hidden until server sends thinking event)
let thinkBlockEl=null;
let thinkBodyEl=null;
// Live bubble
const bub=document.createElement('div');bub.className='mb';
bub.innerHTML='<span class="cur"></span>';
row.appendChild(bub);
DOM.msgs().appendChild(row);scrollBot();
const reader=res.body.getReader();const dec=new TextDecoder();
let full='',sseBuffer='',raf=null;
const schedRender=()=>{
if(raf)return;
raf=requestAnimationFrame(()=>{
bub.innerHTML=MD.render(full)+'<span class="cur"></span>';
scrollBot();raf=null;
});
};
try{
while(true){
if(state.stopReq){reader.cancel();break;}
const{value,done}=await reader.read();if(done)break;
sseBuffer+=dec.decode(value,{stream:true});
const lines=sseBuffer.split('\n');sseBuffer=lines.pop();
for(const line of lines){
if(!line.startsWith('data: '))continue;
let ev;try{ev=JSON.parse(line.slice(6));}catch{continue;}
if(ev.type==='start'){
if(ev.conversation_id)conv.serverId=ev.conversation_id;
}
else if(ev.type==='thinking'&&ev.lines?.length){
// Server extracted thinking block — show if toggle is on
bm.thinking=ev.lines;
const showThink=DOM.showThinkTog().checked;
if(showThink&&!thinkBlockEl){
thinkBlockEl=UI.thinkingBlock(ev.lines);
thinkBlockEl.classList.add('streaming');
row.insertBefore(thinkBlockEl,bub);
thinkBodyEl=thinkBlockEl.querySelector('.thinking-body');
} else if(showThink&&thinkBlockEl){
// Update lines
if(thinkBodyEl){
thinkBodyEl.innerHTML=ev.lines.map(l=>`<div class="thinking-line">${Utils.esc(l)}</div>`).join('');
}
}
}
else if(ev.type==='chunk'&&ev.content){
// Once chunks arrive, thinking is done streaming
if(thinkBlockEl)thinkBlockEl.classList.remove('streaming');
full+=ev.content;schedRender();
}
else if(ev.type==='error'){
full+='\n❌ '+ev.content;schedRender();
}
else if(ev.type==='done'){
break;
}
}
}
}catch(e){/* stream interrupted */}
// Final render without cursor
cancelAnimationFrame(raf);
bub.innerHTML=MD.render(full||'(لا يوجد رد)');
if(thinkBlockEl)thinkBlockEl.classList.remove('streaming');
bm.content=full;persist();
// Replace streaming row with full interactive row
const finalRow=buildMsgRow(bm,idx);
row.replaceWith(finalRow);
if(DOM.fupTog().checked) _appendFollowUps(full);
setBusy(false);scrollBot();
}
function _appendFollowUps(text){
if(!text||text.startsWith('❌'))return;
const row=UI.followUpRow(Utils.suggestFollowUps(text));
DOM.msgs().appendChild(row);scrollBot();
}
// ── Message actions ────────────────────────────
function copyMsg(idx){
const conv=active();
navigator.clipboard?.writeText(conv?.msgs[idx]?.content||'');
toast('✅ تم النسخ','ok');
}
function copyCode(btn){
const code=btn.closest('.cbw')?.querySelector('code');
if(!code)return;
navigator.clipboard?.writeText(code.textContent);
const orig=btn.innerHTML;
btn.innerHTML='<i class="fa-solid fa-check"></i> تم';
setTimeout(()=>{btn.innerHTML=orig;},1500);
toast('✅ تم نسخ الكود','ok');
}
function runCodeBlock(btn){
const cbw=btn.closest('.cbw');
const code=cbw?.querySelector('code')?.textContent||'';
const lang=cbw?.querySelector('.cb-lang')?.dataset.lang||'html';
Sandbox.open(Utils.wrapForPreview(code,lang),`code.${lang==='javascript'?'js':lang}`);
}
function editMsg(idx){
const conv=active();const msg=conv?.msgs[idx];if(!msg)return;
const row=DOM.msgs().querySelector(`.mr[data-i="${idx}"]`);if(!row)return;
const bub=row.querySelector('.mb');const orig=msg.content;
bub.innerHTML='';
const ta=document.createElement('textarea');ta.className='etx';ta.value=orig;bub.appendChild(ta);
const btns=document.createElement('div');btns.className='ebtns';
const cancelBtn=document.createElement('button');cancelBtn.className='ecn';cancelBtn.textContent='إلغاء';
cancelBtn.addEventListener('click',()=>cancelEdit(idx,orig,bub));
const saveBtn=document.createElement('button');saveBtn.className='esv';saveBtn.textContent='إرسال';
saveBtn.addEventListener('click',()=>saveEdit(idx,ta.value.trim()));
btns.appendChild(cancelBtn);btns.appendChild(saveBtn);bub.appendChild(btns);
ta.focus();ta.selectionStart=ta.value.length;
}
function cancelEdit(idx,orig,bub){
bub.innerHTML='';
const span=document.createElement('span');
const lines=orig.split('\n');
lines.forEach((line,i)=>{
span.appendChild(document.createTextNode(line));
if(i<lines.length-1)span.appendChild(document.createElement('br'));
});
bub.appendChild(span);
}
async function saveEdit(idx,newText){
if(!newText)return;
const conv=active();if(!conv)return;
conv.msgs[idx].content=newText;conv.msgs[idx].ts=Date.now();
conv.msgs=conv.msgs.slice(0,idx+1);
persist();renderMessages();await _resend(newText);
}
async function regen(idx){
const conv=active();if(!conv)return;
let ui=idx-1;
while(ui>=0&&conv.msgs[ui].role!=='user')ui--;
if(ui<0)return;
const text=conv.msgs[ui].content;
conv.msgs=conv.msgs.slice(0,idx);
persist();renderMessages();await _resend(text);
}
async function _resend(text){
const conv=active();if(!conv)return;
const payload={
message:text,
model:DOM.msel().value,
provider:DOM.psel().value,
system_prompt:DOM.sysp().value.trim(),
conversation_id:getServerId(conv),
};
setBusy(true);
const lid='tp'+Date.now();
const typRow=UI.typingRow(lid,'يعيد التوليد...');
DOM.msgs().appendChild(typRow);scrollBot();
try{
if(DOM.stog().checked) await _doStream(payload,lid,conv,DOM.msel().value);
else await _doSync(payload,lid,conv,DOM.msel().value);
}catch{document.getElementById(lid)?.remove();setBusy(false);}
}
function togglePin(idx){
const conv=active();if(!conv)return;
if(!conv.pins)conv.pins=[];
const i=conv.pins.indexOf(idx);
if(i===-1){conv.pins.push(idx);toast('📌 تم التثبيت','ok');}
else{conv.pins.splice(i,1);toast('تم إلغاء التثبيت','');}
persist();renderMessages();
}
// ── Artifact actions ───────────────────────────
function artAction(type,el){
const art=el.closest('.artifact')?._artData;if(!art)return;
if(type==='art-view'||type==='art-run'){
Sandbox.open(Utils.wrapForPreview(art.code,art.lang),art.filename);
} else if(type==='art-copy'){
navigator.clipboard?.writeText(art.code);
const orig=el.innerHTML;el.innerHTML='<i class="fa-solid fa-check"></i> تم';
setTimeout(()=>{el.innerHTML=orig;},1500);toast('✅ تم النسخ','ok');
} else if(type==='art-dl'){
const a=document.createElement('a');
a.href=URL.createObjectURL(new Blob([art.code],{type:'text/plain'}));
a.download=art.filename;a.click();URL.revokeObjectURL(a.href);
toast('⬇️ جاري التحميل','ok');
}
}
// ── Providers ──────────────────────────────────
async function loadProviders(){
try{
const data=await API.getProviders();
if(!data.ok)throw new Error();
state.provs=data.providers;
_buildProviderSelect(data.providers);
enableInput();
toast(`✅ ${data.total} مزود`,'ok');
_restoreProvider();
}catch{
toast('❌ فشل الاتصال','er');
enableInput();
}
}
async function reloadProviders(){
const ic=DOM.rlic();ic.classList.add('fa-spin');
try{await API.reload();await loadProviders();}catch{}
ic.classList.remove('fa-spin');
}
function _buildProviderSelect(providers){
const sel=DOM.psel();const frag=document.createDocumentFragment();
// Auto first
if(providers['Auto']){
const o=document.createElement('option');o.value='Auto';o.textContent='⚡ Auto — تلقائي';frag.appendChild(o);
}
const tg=document.createElement('optgroup');tg.label='── بدون مصادقة ──';
const ag=document.createElement('optgroup');ag.label='── تحتاج تسجيل ──';
for(const[k,p] of Object.entries(providers)){
if(k==='Auto')continue;
const o=document.createElement('option');o.value=k;o.textContent=k;
(p.needs_auth?ag:tg).appendChild(o);
}
if(tg.children.length)frag.appendChild(tg);
if(ag.children.length)frag.appendChild(ag);
sel.innerHTML='';sel.appendChild(frag);
}
async function onProviderChange(){
const pv=DOM.psel().value;
Store.setProvider(pv);
DOM.ptxt().textContent=pv||'—';
const ms=DOM.msel();ms.innerHTML='<option>⏳ جاري...</option>';
try{
const data=await API.getModels(pv);
const frag=document.createDocumentFragment();
(data.models||['gpt-4o']).forEach(m=>{
const o=document.createElement('option');o.value=m;o.textContent=m;frag.appendChild(o);
});
ms.innerHTML='';ms.appendChild(frag);
_restoreModel();
}catch{ms.innerHTML='<option value="gpt-4o">gpt-4o</option>';}
}
function _restoreProvider(){
const p=Store.getProvider();
if(p&&[...DOM.psel().options].some(o=>o.value===p)){DOM.psel().value=p;onProviderChange();}
else onProviderChange();
}
function _restoreModel(){
const m=Store.getModel();
if(m&&[...DOM.msel().options].some(o=>o.value===m))DOM.msel().value=m;
}
// ── Sidebar ────────────────────────────────────
function toggleSidebar(){
state.sbOpen=!state.sbOpen;
const sb=DOM.sb();
if(window.innerWidth>680){sb.classList.toggle('desk-hide',!state.sbOpen);}
else{sb.classList.toggle('mob-open',state.sbOpen);DOM.ov().classList.toggle('on',state.sbOpen);}
}
function closeSidebar(){
state.sbOpen=false;DOM.sb().classList.remove('mob-open');DOM.ov().classList.remove('on');
}
// ── Theme ──────────────────────────────────────
function toggleTheme(){
const next=Store.getTheme()==='dark'?'light':'dark';
Store.setTheme(next);
document.documentElement.dataset.theme=next;
_updateThemeIcon(next);
DOM.themeTog().checked=next==='light';
}
function _updateThemeIcon(theme){
DOM.thbtn().querySelector('i').className=theme==='dark'?'fa-solid fa-sun':'fa-solid fa-moon';
}
// ── Event delegation ───────────────────────────
function _delegateMsgs(e){
const btn=e.target.closest('[data-action]');if(!btn)return;
const action=btn.dataset.action;
const idx=parseInt(btn.dataset.idx??'-1');
if(action==='copy'&&btn.classList.contains('cb-copy')){copyCode(btn);return;}
if(action==='run'&&btn.classList.contains('cb-run')){runCodeBlock(btn);return;}
switch(action){
case 'copy': copyMsg(idx);break;
case 'edit': editMsg(idx);break;
case 'pin': togglePin(idx);break;
case 'regen': regen(idx);break;
case 'art-view':case 'art-copy':case 'art-run':case 'art-dl': artAction(action,btn);break;
}
}
function _delegateConvList(e){
const btn=e.target.closest('[data-action]');
if(btn){
e.stopPropagation();
const action=btn.dataset.action;const cid=btn.dataset.cid;
if(action==='rename')startRename(cid);
if(action==='delete')deleteConv(cid);
return;
}
const item=e.target.closest('.ci[data-cid]');
if(item)setActive(item.dataset.cid);
}
function _delegateWelcome(e){
const card=e.target.closest('[data-prompt]');if(card)send(card.dataset.prompt);
}
function _delegateFollowUps(e){
const btn=e.target.closest('.fup[data-prompt]');if(btn)send(btn.dataset.prompt);
}
// ── Init ───────────────────────────────────────
function init(){
if(window.marked)marked.setOptions({breaks:true,gfm:true});
// Theme
const theme=Store.getTheme();
document.documentElement.dataset.theme=theme;
_updateThemeIcon(theme);
DOM.themeTog().checked=theme==='light';
// Toggles restore
DOM.showThinkTog().checked=Store.getShowThink();
DOM.fupTog().checked=Store.getShowFup();
DOM.stog().checked=Store.getStream();
// Sidebar initial state
if(!state.sbOpen)DOM.sb().classList.add('desk-hide');
// Input events
DOM.ci().addEventListener('input',_onInput);
DOM.ci().addEventListener('keydown',e=>{
if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();send();}
});
// Scroll
DOM.mwrap().addEventListener('scroll',_onScroll,{passive:true});
// Settings persist
DOM.msel().addEventListener('change',()=>Store.setModel(DOM.msel().value));
DOM.stog().addEventListener('change',()=>Store.setStream(DOM.stog().checked));
DOM.showThinkTog().addEventListener('change',()=>{Store.setShowThink(DOM.showThinkTog().checked);renderMessages();});
DOM.fupTog().addEventListener('change',()=>Store.setShowFup(DOM.fupTog().checked));
DOM.themeTog().addEventListener('change',toggleTheme);
// Rename modal
DOM.rmi().addEventListener('keydown',e=>{if(e.key==='Enter')confirmRename();if(e.key==='Escape')closeModal();});
DOM.rmod().addEventListener('click',e=>{if(e.target===DOM.rmod())closeModal();});
// Header buttons
$('newConvBtn').addEventListener('click',newConv);
$('sbToggleBtn').addEventListener('click',toggleSidebar);
DOM.ov().addEventListener('click',closeSidebar);
DOM.thbtn().addEventListener('click',toggleTheme);
$('reloadBtn').addEventListener('click',reloadProviders);
DOM.sndbtn().addEventListener('click',()=>send());
$('stopbtn').addEventListener('click',()=>{state.stopReq=true;setBusy(false);toast('تم إيقاف الرد','');});
$('scrbtn').addEventListener('click',()=>{state.userScrolled=false;scrollBot(true);});
// Rename modal buttons
$('renCancelBtn').addEventListener('click',closeModal);
$('renSaveBtn').addEventListener('click',confirmRename);
// Sandbox buttons
$('sbCloseBtn').addEventListener('click',Sandbox.close);
$('sbRunBtn').addEventListener('click',Sandbox.run);
document.querySelectorAll('.sb-tab').forEach(tab=>tab.addEventListener('click',()=>Sandbox.setTab(tab.dataset.tab)));
document.addEventListener('keydown',e=>{if(e.key==='Escape')Sandbox.close();});
// Delegated events
DOM.msgs().addEventListener('click',e=>{_delegateMsgs(e);_delegateWelcome(e);_delegateFollowUps(e);});
DOM.cl().addEventListener('click',_delegateConvList);
// Provider select
DOM.psel().addEventListener('change',onProviderChange);
// Load providers from server
loadProviders();
renderConvList();
if(state.activeId&&active()){renderMessages();}
else if(state.convs.length){setActive(state.convs[0].id);}
else{DOM.msgs().innerHTML='';DOM.msgs().appendChild(UI.welcomeScreen());}
}
return {init};
})();
App.init();
</script>
</body>
</html