bahi-bh commited on
Commit
d19fa2b
·
verified ·
1 Parent(s): 491dc9f

Create index.html

Browse files
Files changed (1) hide show
  1. index.html +1343 -0
index.html ADDED
@@ -0,0 +1,1343 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ar" dir="rtl" data-theme="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
6
+ <meta name="mobile-web-app-capable" content="yes">
7
+ <meta name="apple-mobile-web-app-capable" content="yes">
8
+ <title>g4fpro</title>
9
+ <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">
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
11
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
12
+ <style>
13
+ :root{
14
+ --bg:#0c0c12;--bg1:#141420;--bg2:#1b1b28;--bg3:#222232;--bg4:#2a2a3e;--bgh:#313148;
15
+ --ln:#1e1e2e;--ln2:#2c2c42;--ln3:#404055;
16
+ --t0:#e8e8f5;--t1:#b0b0cc;--t2:#6868a0;--t3:#404058;
17
+ --ac:#7c6fff;--ach:#8f84ff;--acbg:#7c6fff14;--acln:#7c6fff44;
18
+ --tl:#00d4ff;--tlbg:#00d4ff0e;
19
+ --gn:#22d48a;--gnbg:#22d48a0e;
20
+ --am:#f5a623;--ambg:#f5a6230e;
21
+ --rd:#ff5f6d;--rdbg:#ff5f6d0e;
22
+ --font:'Cairo',sans-serif;--mono:'JetBrains Mono',monospace;
23
+ --r1:5px;--r2:10px;--r3:14px;--r4:20px;
24
+ --s1:0 1px 4px #0009;--s2:0 4px 18px #000b;--s3:0 12px 40px #000e;
25
+ }
26
+ [data-theme=light]{
27
+ --bg:#f2f2fa;--bg1:#fff;--bg2:#f5f5fd;--bg3:#ebebf8;--bg4:#e2e2f0;--bgh:#d6d6ee;
28
+ --ln:#e8e8f6;--ln2:#d8d8ee;--ln3:#c0c0dc;
29
+ --t0:#0d0d1a;--t1:#2a2a44;--t2:#5a5a88;--t3:#9898b8;
30
+ --s1:0 1px 4px #0002;--s2:0 4px 18px #0003;--s3:0 12px 40px #0004;
31
+ }
32
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
33
+ html,body{height:100%;overflow:hidden;font-family:var(--font);background:var(--bg);color:var(--t0);-webkit-font-smoothing:antialiased}
34
+ button{cursor:pointer;font-family:var(--font);border:none;background:none;color:inherit}
35
+ input,textarea,select{font-family:var(--font);-webkit-appearance:none;appearance:none}
36
+ a{color:var(--tl)}
37
+ ::-webkit-scrollbar{width:3px;height:3px}
38
+ ::-webkit-scrollbar-thumb{background:var(--ln2);border-radius:2px}
39
+ ::-webkit-scrollbar-track{background:transparent}
40
+ #app{display:flex;height:100dvh;overflow:hidden}
41
+ #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}
42
+ #sb.desk-hide{width:0;min-width:0;border-left:none}
43
+ @media(max-width:680px){
44
+ #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}
45
+ #sb.mob-open{transform:translateX(0)}
46
+ }
47
+ .sb-head{padding:10px;border-bottom:1px solid var(--ln);flex-shrink:0}
48
+ .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}
49
+ .new-btn:hover{background:var(--ach)}.new-btn:active{transform:scale(.97)}
50
+ .cl{flex:1;overflow-y:auto;padding:4px}
51
+ .grp{font-size:.62rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:var(--t3);padding:8px 8px 3px}
52
+ .ci{display:flex;align-items:center;gap:7px;padding:7px 9px;border-radius:var(--r2);cursor:pointer;transition:.13s;position:relative}
53
+ .ci:hover{background:var(--bgh)}.ci.on{background:var(--acbg);border:1px solid var(--acln)}
54
+ .ci-dot{width:6px;height:6px;border-radius:50%;background:var(--t3);flex-shrink:0;transition:.13s}
55
+ .ci.on .ci-dot{background:var(--ac)}
56
+ .ci-info{flex:1;min-width:0}
57
+ .ci-title{font-size:.79rem;font-weight:600;color:var(--t1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
58
+ .ci.on .ci-title{color:var(--ac)}
59
+ .ci-sub{font-size:.64rem;color:var(--t3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:1px}
60
+ .ci-acts{display:none;gap:1px;flex-shrink:0}.ci:hover .ci-acts{display:flex}
61
+ .cia{width:22px;height:22px;border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:.65rem;color:var(--t2);transition:.13s}
62
+ .cia:hover{background:var(--bg4);color:var(--t0)}.cia.del:hover{color:var(--rd)}
63
+ .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}
64
+ .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}
65
+ .lbl i{color:var(--ac);font-size:.61rem}
66
+ .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}
67
+ .sel:focus{border-color:var(--ac);outline:none}
68
+ .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)}
69
+ .tog-lbl{font-size:.78rem;font-weight:600;color:var(--t1);display:flex;align-items:center;gap:5px}
70
+ .tog-lbl i{color:var(--tl);font-size:.7rem}
71
+ .tog{position:relative;width:34px;height:18px;flex-shrink:0}
72
+ .tog input{opacity:0;width:0;height:0;position:absolute}
73
+ .tog-t{position:absolute;inset:0;background:var(--bg4);border:1px solid var(--ln2);border-radius:9px;cursor:pointer;transition:.15s}
74
+ .tog-t::after{content:'';position:absolute;width:12px;height:12px;right:2px;top:2px;background:var(--t2);border-radius:50%;transition:.15s}
75
+ .tog input:checked~.tog-t{background:var(--tl);border-color:var(--tl)}
76
+ .tog input:checked~.tog-t::after{transform:translateX(-16px);background:#fff}
77
+ .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}
78
+ .sys-ta:focus{border-color:var(--ac);outline:none}.sys-ta::placeholder{color:var(--t3)}
79
+ #main{flex:1;min-width:0;display:flex;flex-direction:column;height:100dvh;background:var(--bg)}
80
+ #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}
81
+ .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}
82
+ .hb:hover{background:var(--bgh);color:var(--t0)}.hb:active{transform:scale(.91)}
83
+ .logo{display:flex;align-items:center;gap:6px;font-weight:900;font-size:.96rem;letter-spacing:-.03em;white-space:nowrap}
84
+ .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}
85
+ .logo-ac{color:var(--ac)}
86
+ .hdr-m{flex:1;min-width:0;text-align:center}
87
+ .hdr-t{font-size:.81rem;font-weight:700;color:var(--t1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
88
+ .hdr-r{display:flex;align-items:center;gap:5px;flex-shrink:0}
89
+ .hst{display:flex;align-items:center;gap:4px;font-size:.67rem;color:var(--gn);font-weight:600;white-space:nowrap}
90
+ .hst::before{content:'';width:5px;height:5px;background:var(--gn);border-radius:50%;animation:pu 2s ease infinite}
91
+ @keyframes pu{0%,100%{opacity:1}50%{opacity:.3}}
92
+ .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}
93
+ .pd{width:5px;height:5px;border-radius:50%;background:var(--ac);flex-shrink:0}
94
+ @media(max-width:480px){.ppill{display:none}}
95
+ #mwrap{flex:1;overflow-y:auto;position:relative;-webkit-overflow-scrolling:touch}
96
+ #msgs{max-width:780px;margin:0 auto;padding:18px 10px 14px;display:flex;flex-direction:column;gap:2px}
97
+ @media(max-width:680px){#msgs{padding:12px 8px 10px}}
98
+ #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}
99
+ @keyframes fup{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
100
+ .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}
101
+ @keyframes fl{0%,100%{transform:translateY(0)}50%{transform:translateY(-7px)}}
102
+ .wt{font-size:1.35rem;font-weight:900}.wt span{color:var(--ac)}
103
+ .ws{font-size:.85rem;color:var(--t2);max-width:340px;line-height:1.65}
104
+ .sg{display:grid;grid-template-columns:1fr 1fr;gap:6px;width:100%;max-width:420px}
105
+ @media(max-width:420px){.sg{grid-template-columns:1fr}}
106
+ .sc{padding:10px 11px;background:var(--bg2);border:1px solid var(--ln2);border-radius:var(--r2);text-align:right;cursor:pointer;transition:.13s}
107
+ .sc:hover{background:var(--bgh);border-color:var(--acln);transform:translateY(-2px)}.sc:active{transform:scale(.97)}
108
+ .sc-i{font-size:.95rem;display:block;margin-bottom:3px}
109
+ .sc-t{font-size:.76rem;color:var(--t1);font-weight:600;line-height:1.3}
110
+ .sc-s{font-size:.65rem;color:var(--t3);margin-top:1px}
111
+ .mr{display:flex;flex-direction:column;gap:2px;animation:ms .22s cubic-bezier(.34,1.4,.64,1)}
112
+ @keyframes ms{from{opacity:0;transform:translateY(5px)scale(.98)}to{opacity:1;transform:none}}
113
+ .mr.user{align-items:flex-end}.mr.assistant{align-items:flex-start}.mr.err{align-items:flex-start}
114
+ .mhdr{display:flex;align-items:center;gap:5px;padding:0 3px;font-size:.65rem;color:var(--t3)}
115
+ .mr.user .mhdr{flex-direction:row-reverse}
116
+ .mav{width:20px;height:20px;border-radius:5px;display:flex;align-items:center;justify-content:center;font-size:.6rem;color:#fff;flex-shrink:0}
117
+ .mav.u{background:var(--ac)}.mav.b{background:linear-gradient(135deg,var(--ac),var(--tl))}
118
+ .mtag{background:var(--bg3);border:1px solid var(--ln2);padding:1px 5px;border-radius:8px;font-size:.6rem;font-weight:700;color:var(--t2)}
119
+ .pinbdg{color:var(--am);font-size:.6rem}
120
+ .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}
121
+ @media(max-width:680px){.mb{max-width:91%;font-size:.87rem;padding:9px 11px}}
122
+ .mr.user .mb{background:var(--ac);color:#fff;border-bottom-left-radius:3px}
123
+ .mr.assistant .mb{background:var(--bg2);border:1px solid var(--ln2);color:var(--t0);border-bottom-right-radius:3px}
124
+ .mr.err .mb{background:var(--rdbg);border:1px solid #ff5f6d28;color:#ff9faa;border-radius:var(--r2)}
125
+ /* thinking */
126
+ .thinking-block{max-width:80%;background:var(--ambg);border:1px solid #f5a62330;border-radius:var(--r2);overflow:hidden;animation:ms .22s ease}
127
+ @media(max-width:680px){.thinking-block{max-width:91%}}
128
+ .thinking-header{display:flex;align-items:center;gap:6px;padding:7px 11px;cursor:pointer;user-select:none;transition:.13s}
129
+ .thinking-header:hover{background:#f5a62310}
130
+ .thinking-icon{color:var(--am);font-size:.75rem;flex-shrink:0}
131
+ .thinking-label{font-size:.73rem;font-weight:700;color:var(--am);flex:1}
132
+ .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}
133
+ .thinking-toggle{font-size:.65rem;color:var(--t3);transition:transform .2s}
134
+ .thinking-block.open .thinking-toggle{transform:rotate(180deg)}
135
+ .thinking-body{display:none;padding:6px 11px 9px;border-top:1px solid #f5a62320}
136
+ .thinking-block.open .thinking-body{display:block}
137
+ .thinking-line{font-size:.74rem;color:var(--t2);line-height:1.65;padding:2px 0}
138
+ .thinking-line::before{content:'› ';color:var(--am);opacity:.6;font-family:var(--mono)}
139
+ .thinking-block.streaming .thinking-icon{animation:spin 1.4s linear infinite}
140
+ @keyframes spin{to{transform:rotate(360deg)}}
141
+ .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}
142
+ @keyframes cr{0%,100%{opacity:1}50%{opacity:0}}
143
+ .macts{display:flex;gap:2px;padding:1px 3px;opacity:0;transition:opacity .13s;flex-wrap:wrap}
144
+ .mr:hover .macts,.mr:focus-within .macts{opacity:1}
145
+ .mr.user .macts{flex-direction:row-reverse}
146
+ .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}
147
+ .ma:hover{background:var(--bgh);color:var(--t0)}.ma i{font-size:.6rem}
148
+ .ma.rg{color:var(--ac);border-color:var(--acln)}.ma.rg:hover{background:var(--acbg)}
149
+ .ma.pn.on{color:var(--am);border-color:#f5a62335}
150
+ .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}
151
+ .etx:focus{outline:none}
152
+ .ebtns{display:flex;gap:6px;margin-top:5px;justify-content:flex-end}
153
+ .esv{padding:5px 13px;background:var(--ac);color:#fff;border-radius:5px;font-size:.77rem;font-weight:700;transition:.13s}
154
+ .esv:hover{background:var(--ach)}
155
+ .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}
156
+ .ecn:hover{background:var(--bgh)}
157
+ .fups{display:flex;flex-wrap:wrap;gap:5px;padding:1px 3px;margin-top:2px;animation:fup .3s ease}
158
+ .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}
159
+ .fup:hover{background:var(--tlbg);border-color:#00d4ff35;transform:translateY(-1px)}
160
+ .mb h1,.mb h2,.mb h3{font-weight:800;margin:10px 0 4px;line-height:1.3}
161
+ .mb h1{font-size:1.1em}.mb h2{font-size:1em}.mb h3{font-size:.95em}
162
+ .mb p{margin:4px 0}
163
+ .mb ul,.mb ol{padding-right:16px;margin:5px 0}.mb li{margin:3px 0;line-height:1.65}
164
+ .mb blockquote{border-right:3px solid var(--ac);padding-right:9px;margin:5px 0;color:var(--t2);font-style:italic}
165
+ .mb strong{font-weight:700;color:var(--tl)}.mb em{font-style:italic;color:var(--am)}
166
+ .mb table{width:100%;border-collapse:collapse;margin:7px 0;font-size:.82em;display:block;overflow-x:auto}
167
+ .mb th{background:var(--bg4);padding:5px 9px;border:1px solid var(--ln2);font-weight:700;text-align:right;white-space:nowrap}
168
+ .mb td{padding:5px 9px;border:1px solid var(--ln)}.mb tr:nth-child(even){background:var(--bg3)}
169
+ .mb a{color:var(--tl)}.mb hr{border:none;border-top:1px solid var(--ln2);margin:9px 0}
170
+ .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)}
171
+ .mr.user .mb code:not(pre code){background:#fff2;border-color:#fff2}
172
+ .cbw{position:relative;margin:8px 0;border-radius:var(--r2);overflow:hidden;border:1px solid #30363d;max-width:100%}
173
+ .cbh{display:flex;align-items:center;justify-content:space-between;padding:7px 12px;background:#161b22;border-bottom:1px solid #30363d;gap:8px}
174
+ .cb-lang{font-family:var(--mono);font-size:.68rem;color:#58a6ff;font-weight:500;display:flex;align-items:center;gap:5px}
175
+ .cb-lang::before{content:'';width:7px;height:7px;border-radius:50%;background:currentColor;opacity:.65;flex-shrink:0}
176
+ .cb-lang.l-html::before{background:#e44d26}.cb-lang.l-css::before{background:#264de4}
177
+ .cb-lang.l-js::before,.cb-lang.l-javascript::before{background:#f7df1e}
178
+ .cb-lang.l-python::before{background:#3572A5}
179
+ .cb-lang.l-bash::before,.cb-lang.l-sh::before{background:#4EAA25}
180
+ .cb-btns{display:flex;gap:4px}
181
+ .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}
182
+ .cb-copy:hover{background:#30363d;color:#e6edf3;border-color:#6e7681}
183
+ .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}
184
+ .cb-run:hover{background:#1f6feb30;border-color:#1f6feb}
185
+ .cbw pre{margin:0!important;padding:14px!important;background:#0d1117!important;font-size:.79rem!important;line-height:1.65!important;overflow-x:auto}
186
+ .cbw pre code{font-family:var(--mono)!important;white-space:pre;word-break:normal}
187
+ .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}
188
+ @media(max-width:680px){.artifact{max-width:91%}}
189
+ .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}
190
+ .art-info{flex:1;min-width:0}
191
+ .art-name{font-size:.8rem;font-weight:700;color:var(--t0);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
192
+ .art-meta{font-size:.64rem;color:var(--t3);margin-top:2px}
193
+ .art-btns{display:flex;gap:4px;flex-shrink:0;flex-wrap:wrap}
194
+ .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}
195
+ .ab i{font-size:.6rem}
196
+ .ab.view{background:var(--acbg);border:1px solid var(--acln);color:var(--ac)}.ab.view:hover{background:var(--ac);color:#fff}
197
+ .ab.copy{background:var(--bg3);border:1px solid var(--ln2);color:var(--t2)}.ab.copy:hover{background:var(--bgh);color:var(--t0)}
198
+ .ab.run{background:#1f6feb18;border:1px solid #1f6feb50;color:#58a6ff}.ab.run:hover{background:#1f6feb;color:#fff}
199
+ .ab.dl{background:var(--gnbg);border:1px solid #22d48a35;color:var(--gn)}.ab.dl:hover{background:var(--gn);color:#000}
200
+ /* typing indicator */
201
+ .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}
202
+ .typing-dots{display:flex;gap:4px}
203
+ .typing-dot{width:7px;height:7px;border-radius:50%;background:var(--ac);animation:td 1.4s ease infinite}
204
+ .typing-dot:nth-child(2){animation-delay:.2s}.typing-dot:nth-child(3){animation-delay:.4s}
205
+ @keyframes td{0%,80%,100%{transform:scale(.6);opacity:.3}40%{transform:scale(1.2);opacity:1}}
206
+ /* scroll button */
207
+ #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}
208
+ #scrbtn:hover{background:var(--bgh)}
209
+ #scrbtn.on{display:flex}
210
+ #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}
211
+ .ia-in{max-width:780px;margin:0 auto}
212
+ .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}
213
+ .ib:focus-within{border-color:var(--ac);box-shadow:0 0 0 3px #7c6fff16}
214
+ #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)}
215
+ @media(min-width:480px){#ci{font-size:.92rem}}
216
+ #ci::-webkit-scrollbar{display:none}
217
+ #ci::placeholder{color:var(--t3)}
218
+ #ci:disabled{opacity:.4}
219
+ .ibtn{width:36px;height:36px;border-radius:9px;display:flex;align-items:center;justify-content:center;font-size:.85rem;transition:.13s;flex-shrink:0}
220
+ #sndbtn{background:var(--ac);color:#fff;box-shadow:0 2px 9px #7c6fff40}
221
+ #sndbtn:hover:not(:disabled){background:var(--ach)}
222
+ #sndbtn:active:not(:disabled){transform:scale(.9)}
223
+ #sndbtn:disabled{opacity:.3;cursor:default;box-shadow:none}
224
+ #stopbtn{background:var(--rdbg);color:var(--rd);border:1px solid #ff5f6d28;display:none}
225
+ #stopbtn.on{display:flex}
226
+ #stopbtn:hover{background:#ff5f6d1e}
227
+ .ifoot{display:flex;align-items:center;justify-content:space-between;margin-top:5px;padding:0 1px;font-size:.66rem;color:var(--t3)}
228
+ .ihnts{display:flex;align-items:center;gap:7px}
229
+ @media(max-width:480px){.ihnts{display:none}}
230
+ kbd{background:var(--bg3);border:1px solid var(--ln2);border-radius:3px;padding:1px 4px;font-family:var(--mono);font-size:.59rem}
231
+ .tkc span{color:var(--ac);font-weight:700}
232
+ #ov{display:none;position:fixed;inset:0;background:#0009;z-index:40}
233
+ #ov.on{display:block}
234
+ /* sandbox modal */
235
+ #sbmod{position:fixed;inset:0;background:#000c;z-index:200;display:none;align-items:center;justify-content:center;padding:12px}
236
+ #sbmod.open{display:flex}
237
+ .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}
238
+ .sbw-head{display:flex;align-items:center;gap:8px;padding:10px 14px;background:var(--bg2);border-bottom:1px solid var(--ln2);flex-shrink:0}
239
+ .sbw-title{flex:1;font-size:.85rem;font-weight:700;display:flex;align-items:center;gap:7px}
240
+ .sbw-title i{color:var(--ac)}
241
+ .sb-tabs{display:flex;gap:3px}
242
+ .sb-tab{padding:5px 12px;border-radius:5px;font-size:.74rem;font-weight:600;color:var(--t2);transition:.13s;cursor:pointer}
243
+ .sb-tab.on{background:var(--acbg);color:var(--ac);border:1px solid var(--acln)}
244
+ .sb-tab:hover:not(.on){background:var(--bgh);color:var(--t1)}
245
+ .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}
246
+ .sbw-close:hover{background:var(--rdbg);color:var(--rd)}
247
+ .sbw-body{flex:1;display:flex;overflow:hidden}
248
+ @media(max-width:600px){.sbw-body{flex-direction:column}}
249
+ .sb-code-pane{flex:1;min-width:0;display:flex;flex-direction:column;border-left:1px solid var(--ln2)}
250
+ .sb-prev-pane{flex:1;min-width:0;display:flex;flex-direction:column}
251
+ .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}
252
+ .sb-pane-lbl i{margin-left:4px}
253
+ .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}
254
+ .sb-editor:focus{outline:none}
255
+ .sb-frame{flex:1;border:none;background:#fff}
256
+ .sbw-foot{display:flex;align-items:center;gap:8px;padding:8px 14px;background:var(--bg2);border-top:1px solid var(--ln2);flex-shrink:0}
257
+ .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}
258
+ .sb-run:hover{background:#2fea9e}.sb-run:active{transform:scale(.95)}
259
+ .sb-stat{font-size:.7rem;color:var(--t3);flex:1}
260
+ /* rename modal */
261
+ .modal{position:fixed;inset:0;background:#0009;z-index:100;display:flex;align-items:center;justify-content:center;padding:20px}
262
+ .mbox{background:var(--bg2);border:1px solid var(--ln2);border-radius:var(--r3);padding:20px;width:100%;max-width:340px;box-shadow:var(--s3)}
263
+ .mttl{font-size:.94rem;font-weight:800;margin-bottom:12px}
264
+ .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}
265
+ .mi:focus{border-color:var(--ac);outline:none}
266
+ .mbtns{display:flex;gap:7px;margin-top:12px;justify-content:flex-end}
267
+ .mok{padding:7px 15px;background:var(--ac);color:#fff;border-radius:var(--r1);font-weight:700;font-size:.82rem;transition:.13s}
268
+ .mok:hover{background:var(--ach)}
269
+ .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}
270
+ .mcc:hover{background:var(--bgh)}
271
+ /* toast */
272
+ #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}
273
+ .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}
274
+ .toast.ok{color:var(--gn);border-color:#22d48a35}.toast.er{color:var(--rd);border-color:#ff5f6d35}
275
+ @keyframes tp{from{opacity:0;transform:translateY(7px)scale(.87)}to{opacity:1;transform:none}}
276
+ /* show-thinking toggle in sidebar */
277
+ .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)}
278
+ </style>
279
+ </head>
280
+ <body>
281
+ <div id="app">
282
+ <aside id="sb">
283
+ <div class="sb-head">
284
+ <button class="new-btn" id="newConvBtn"><i class="fa-solid fa-plus"></i> محادثة جديدة</button>
285
+ </div>
286
+ <div class="cl" id="cl"></div>
287
+ <div class="sb-bot">
288
+ <div><div class="lbl"><i class="fa-solid fa-plug"></i>المزود</div>
289
+ <select class="sel" id="psel"><option value="">⏳ جاري التحميل...</option></select></div>
290
+ <div><div class="lbl"><i class="fa-solid fa-brain"></i>النموذج</div>
291
+ <select class="sel" id="msel"><option value="">اختر مزوداً</option></select></div>
292
+ <div>
293
+ <div class="think-tog-row">
294
+ <span class="tog-lbl"><i class="fa-solid fa-brain" style="color:var(--am)"></i>عرض التفكير</span>
295
+ <label class="tog"><input type="checkbox" id="showThinkTog" checked><div class="tog-t"></div></label>
296
+ </div>
297
+ </div>
298
+ <div class="tog-row">
299
+ <span class="tog-lbl"><i class="fa-solid fa-bolt"></i>البث المباشر</span>
300
+ <label class="tog"><input type="checkbox" id="stog" checked><div class="tog-t"></div></label>
301
+ </div>
302
+ <div class="tog-row">
303
+ <span class="tog-lbl"><i class="fa-solid fa-lightbulb"></i>اقتراحات المتابعة</span>
304
+ <label class="tog"><input type="checkbox" id="fupTog" checked><div class="tog-t"></div></label>
305
+ </div>
306
+ <div class="tog-row">
307
+ <span class="tog-lbl"><i class="fa-solid fa-circle-half-stroke"></i>الوضع الفاتح</span>
308
+ <label class="tog"><input type="checkbox" id="themeTog"><div class="tog-t"></div></label>
309
+ </div>
310
+ <div><div class="lbl"><i class="fa-solid fa-scroll"></i>تعليمات النظام</div>
311
+ <textarea class="sys-ta" id="sysp" placeholder="أنت مساعد ذكاء اصطناعي مفيد..."></textarea></div>
312
+ </div>
313
+ </aside>
314
+ <div id="ov"></div>
315
+ <div id="main">
316
+ <header id="hdr">
317
+ <button class="hb" id="sbToggleBtn"><i class="fa-solid fa-bars"></i></button>
318
+ <div class="logo"><div class="logo-mk"><i class="fa-solid fa-robot"></i></div>g4f<span class="logo-ac">pro</span></div>
319
+ <div class="hdr-m"><div class="hdr-t" id="htitle">محادثة جديدة</div></div>
320
+ <div class="hdr-r">
321
+ <div class="hst">متصل</div>
322
+ <div class="ppill"><div class="pd"></div><span id="ptxt">Auto</span></div>
323
+ <button class="hb" id="thbtn"><i class="fa-solid fa-moon"></i></button>
324
+ <button class="hb" id="reloadBtn"><i class="fa-solid fa-rotate" id="rlic"></i></button>
325
+ </div>
326
+ </header>
327
+ <div id="mwrap">
328
+ <div id="msgs"></div>
329
+ <button id="scrbtn"><i class="fa-solid fa-arrow-down"></i> للأسفل</button>
330
+ </div>
331
+ <div id="ia">
332
+ <div class="ia-in">
333
+ <div class="ib">
334
+ <textarea id="ci" placeholder="اكتب رسالتك..." rows="1" disabled></textarea>
335
+ <button class="ibtn" id="stopbtn"><i class="fa-solid fa-stop"></i></button>
336
+ <button class="ibtn" id="sndbtn" disabled><i class="fa-solid fa-paper-plane"></i></button>
337
+ </div>
338
+ <div class="ifoot">
339
+ <div class="ihnts"><span><kbd>Enter</kbd> إرسال</span><span><kbd>Shift+Enter</kbd> سطر</span></div>
340
+ <div class="tkc">~<span id="tkc">0</span> توكن</div>
341
+ </div>
342
+ </div>
343
+ </div>
344
+ </div>
345
+ </div>
346
+ <!-- Rename modal -->
347
+ <div class="modal" id="rmod" style="display:none">
348
+ <div class="mbox">
349
+ <div class="mttl">إعادة تسمية المحادثة</div>
350
+ <input class="mi" id="rmi" type="text" placeholder="اسم المحادثة...">
351
+ <div class="mbtns">
352
+ <button class="mcc" id="renCancelBtn">إلغاء</button>
353
+ <button class="mok" id="renSaveBtn">حفظ</button>
354
+ </div>
355
+ </div>
356
+ </div>
357
+ <!-- Sandbox modal -->
358
+ <div id="sbmod">
359
+ <div class="sbw">
360
+ <div class="sbw-head">
361
+ <div class="sbw-title"><i class="fa-solid fa-code"></i><span id="sb-fn">index.html</span></div>
362
+ <div class="sb-tabs">
363
+ <div class="sb-tab on" data-tab="split">تقسيم</div>
364
+ <div class="sb-tab" data-tab="code">كود</div>
365
+ <div class="sb-tab" data-tab="preview">معاينة</div>
366
+ </div>
367
+ <button class="sbw-close" id="sbCloseBtn"><i class="fa-solid fa-xmark"></i></button>
368
+ </div>
369
+ <div class="sbw-body">
370
+ <div class="sb-code-pane" id="sb-cpane">
371
+ <div class="sb-pane-lbl"><i class="fa-solid fa-code" style="color:var(--ac)"></i>محرر الكود</div>
372
+ <textarea class="sb-editor" id="sb-ed" spellcheck="false" dir="ltr"></textarea>
373
+ </div>
374
+ <div class="sb-prev-pane" id="sb-ppane">
375
+ <div class="sb-pane-lbl"><i class="fa-solid fa-eye" style="color:var(--tl)"></i>معاينة حية</div>
376
+ <iframe class="sb-frame" id="sb-iframe" sandbox="allow-scripts allow-same-origin" title="preview"></iframe>
377
+ </div>
378
+ </div>
379
+ <div class="sbw-foot">
380
+ <button class="sb-run" id="sbRunBtn"><i class="fa-solid fa-play"></i> تشغيل</button>
381
+ <span class="sb-stat" id="sb-stat">جاهز</span>
382
+ </div>
383
+ </div>
384
+ </div>
385
+ <div id="tw"></div>
386
+
387
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/11.1.1/marked.min.js"></script>
388
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
389
+ <script>
390
+ 'use strict';
391
+
392
+ /* ═══════════════════════════════════════════════════════
393
+ MODULE: Utils
394
+ ════════════���══════════════════════════════════════════ */
395
+ const Utils = (() => {
396
+ function esc(s) {
397
+ return String(s)
398
+ .replace(/&/g,'&amp;').replace(/</g,'&lt;')
399
+ .replace(/>/g,'&gt;').replace(/"/g,'&quot;');
400
+ }
401
+ function fmtBytes(n) { return n>1024?(n/1024).toFixed(1)+'KB':n+'B'; }
402
+ function uid() { return 'c-'+Date.now()+'-'+Math.random().toString(36).slice(2,8); }
403
+ function groupByDate(convs) {
404
+ const now=Date.now();
405
+ const groups={'اليوم':[],'أمس':[],'هذا الأسبوع':[],'أقدم':[]};
406
+ for(const c of convs){
407
+ const d=now-c.created;
408
+ if(d<86400000) groups['اليوم'].push(c);
409
+ else if(d<172800000) groups['أمس'].push(c);
410
+ else if(d<604800000) groups['هذا الأسبوع'].push(c);
411
+ else groups['أقدم'].push(c);
412
+ }
413
+ return groups;
414
+ }
415
+ function suggestFollowUps(text) {
416
+ const l=text.toLowerCase();
417
+ if(l.includes('html')||l.includes('css')||l.includes('كود')||l.includes('python')||l.includes('javascript'))
418
+ return ['اشرح هذا الكود','كيف أحسّنه؟','أضف ميزة جديدة'];
419
+ if(l.includes('شرح')||l.includes('مفهوم'))
420
+ return ['أعطني مثالاً عملياً','ما الفرق مع البدائل؟'];
421
+ return ['أخبرني أكثر','اشرح بمثال'];
422
+ }
423
+ function wrapForPreview(code,lang) {
424
+ if(lang==='css') return `<!DOCTYPE html><html><head><style>${code}</style></head><body></body></html>`;
425
+ 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>`;
426
+ return code;
427
+ }
428
+ function extractArtifacts(text) {
429
+ const WEB = new Set(['html', 'css', 'js', 'javascript']);
430
+ const results = [];
431
+ // هذا التعبير يضمن أن الكود لا يتمدد ويخرب الزوايا
432
+ const re = /```(\w+)?\n([\s\S]*?)```/g;
433
+ let m;
434
+ while ((m = re.exec(text)) !== null) {
435
+ const lang = (m[1] || 'text').toLowerCase();
436
+ const code = m[2].trim();
437
+ // منع الانهيار إذا كان الكود طويلاً جداً وعرضياً
438
+ if (code.length < 30) continue;
439
+ results.push({
440
+ lang,
441
+ code,
442
+ filename: `code.${lang === 'javascript' ? 'js' : lang}`
443
+ });
444
+ }
445
+ return results;
446
+ }
447
+
448
+ return {esc,fmtBytes,uid,groupByDate,suggestFollowUps,wrapForPreview,extractArtifacts};
449
+ })();
450
+
451
+ /* ═══════════════════════════════════════════════════════
452
+ MODULE: Store
453
+ ═══════════════════════════════════════════════════════ */
454
+ const Store = (() => {
455
+ const get=(k,d=null)=>{try{const v=localStorage.getItem(k);return v===null?d:JSON.parse(v);}catch{return d;}};
456
+ const set=(k,v)=>{try{localStorage.setItem(k,JSON.stringify(v));}catch{}};
457
+ return {
458
+ getConvs: () => get('g4c',[]),
459
+ setConvs: (v) => set('g4c',v),
460
+ getActiveId: () => get('g4a',''),
461
+ setActiveId: (v) => set('g4a',v),
462
+ getTheme: () => get('g4t','dark'),
463
+ setTheme: (v) => set('g4t',v),
464
+ getProvider: () => get('g4p',''),
465
+ setProvider: (v) => set('g4p',v),
466
+ getModel: () => get('g4m',''),
467
+ setModel: (v) => set('g4m',v),
468
+ getStream: () => get('g4s','1')==='1',
469
+ setStream: (v) => set('g4s',v?'1':'0'),
470
+ getShowThink: () => get('g4th','1')==='1',
471
+ setShowThink: (v) => set('g4th',v?'1':'0'),
472
+ getShowFup: () => get('g4fu','1')==='1',
473
+ setShowFup: (v) => set('g4fu',v?'1':'0'),
474
+ getDraft: () => get('g4d',''),
475
+ setDraft: (v) => set('g4d',v),
476
+ };
477
+ })();
478
+
479
+ /* ═══════════════════════════════════════════════════════
480
+ MODULE: API — все вызовы к серверу здесь
481
+ Сервер: Flask g4fpro v2.0
482
+ Эндпоинты:
483
+ GET /api/providers → {ok, providers:{name:{models,type,needs_auth}}, total}
484
+ GET /api/models/<provider> → {ok, models:[...]}
485
+ POST /api/reload → {ok, providers:N}
486
+ POST /chat → {ok, reply, thinking:[], model, provider, time, conversation_id}
487
+ POST /chat/stream → SSE: start | thinking | chunk | done | error
488
+ ═══════════════════════════════════════════════════════ */
489
+ const API = (() => {
490
+ const post=(url,body)=>fetch(url,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
491
+ async function getProviders(){const r=await fetch('/api/providers');return r.json();}
492
+ async function getModels(provider){const r=await fetch(`/api/models/${provider}`);return r.json();}
493
+ async function reload(){return post('/api/reload',{});}
494
+ async function chat(payload){const r=await post('/chat',payload);return r.json();}
495
+ function streamChat(payload){return post('/chat/stream',payload);}
496
+ return {getProviders,getModels,reload,chat,streamChat};
497
+ })();
498
+
499
+ /* ═══════════════════════════════════════════════════════
500
+ MODULE: MD — markdown renderer
501
+ ═══════════════════════════════════════════════════════ */
502
+ const MD = (() => {
503
+ const RUNNABLE=new Set(['html','css','js','javascript','jsx','tsx','svg']);
504
+ function _hi(code,lang){
505
+ try{
506
+ if(lang&&lang!=='text'&&hljs.getLanguage(lang)) return hljs.highlight(code,{language:lang}).value;
507
+ return hljs.highlightAuto(code).value;
508
+ }catch{return Utils.esc(code);}
509
+ }
510
+ function _buildBlock(lang,raw){
511
+ const hi=_hi(raw,lang);
512
+ const runBtn=RUNNABLE.has(lang)
513
+ ?`<button class="cb-run" data-action="run"><i class="fa-solid fa-play"></i> تشغيل</button>`:''
514
+ return `<div class="cbw">
515
+ <div class="cbh">
516
+ <span class="cb-lang l-${Utils.esc(lang)}" data-lang="${Utils.esc(lang)}">${Utils.esc(lang)||'text'}</span>
517
+ <div class="cb-btns">${runBtn}<button class="cb-copy" data-action="copy"><i class="fa-regular fa-copy"></i> نسخ</button></div>
518
+ </div>
519
+ <pre><code class="hljs language-${Utils.esc(lang)}">${hi}</code></pre>
520
+ </div>`;
521
+ }
522
+ function render(text){
523
+ if(!text)return'';
524
+ const blocks=[];
525
+ const ph=(lang,code)=>{const i=blocks.length;blocks.push({lang:(lang||'text').toLowerCase(),code:code.trim()});return`%%CB${i}%%`;};
526
+ const withPH = text.replace(/```(\w+)?\n([\s\S]+?)\n```/g, (_, lang, code) => ph(lang, code));
527
+
528
+ let html;
529
+ try{html=marked.parse(withPH);}catch{html=Utils.esc(withPH);}
530
+ html=html.replace(/%%CB(\d+)%%/g,(_,i)=>{const{lang,code}=blocks[parseInt(i)];return _buildBlock(lang,code);});
531
+ return html;
532
+ }
533
+ return {render};
534
+ })();
535
+
536
+ /* ═══════════════════════════════════════════════════════
537
+ MODULE: Sandbox
538
+ ═══════════════════════════════════════════════════════ */
539
+ const Sandbox = (() => {
540
+ let _deb=null;
541
+ const el={
542
+ modal:()=>document.getElementById('sbmod'),
543
+ fname:()=>document.getElementById('sb-fn'),
544
+ editor:()=>document.getElementById('sb-ed'),
545
+ frame:()=>document.getElementById('sb-iframe'),
546
+ status:()=>document.getElementById('sb-stat'),
547
+ cpane:()=>document.getElementById('sb-cpane'),
548
+ ppane:()=>document.getElementById('sb-ppane'),
549
+ };
550
+ function _write(code){
551
+ try{
552
+ const doc=el.frame().contentDocument||el.frame().contentWindow.document;
553
+ doc.open();doc.write(code);doc.close();
554
+ el.status().textContent='✅ تم التشغيل';el.status().style.color='var(--gn)';
555
+ }catch(e){el.status().textContent='❌ '+e.message.slice(0,60);el.status().style.color='var(--rd)';}
556
+ }
557
+ function open(code,filename){
558
+ el.fname().textContent=filename||'code';
559
+ el.editor().value=code||'';
560
+ el.modal().classList.add('open');
561
+ setTab('split');_write(code);
562
+ el.editor().oninput=()=>{clearTimeout(_deb);_deb=setTimeout(()=>_write(el.editor().value),400);};
563
+ }
564
+ function close(){el.modal().classList.remove('open');el.editor().oninput=null;clearTimeout(_deb);}
565
+ function run(){_write(el.editor().value);}
566
+ function setTab(tab){
567
+ document.querySelectorAll('.sb-tab').forEach(t=>t.classList.toggle('on',t.dataset.tab===tab));
568
+ const show=(pane,vis)=>{pane.style.display=vis?'flex':'none';};
569
+ show(el.cpane(),tab!=='preview');show(el.ppane(),tab!=='code');
570
+ }
571
+ return {open,close,run,setTab};
572
+ })();
573
+
574
+ /* ═══════════════════════════════════════════════════════
575
+ MODULE: UI — DOM builders
576
+ ═══════════════════════════════════════════════════════ */
577
+ const UI = (() => {
578
+ function msgHeader(role,model,time,pinned){
579
+ const hdr=document.createElement('div');hdr.className='mhdr';
580
+ const av=document.createElement('div');av.className=role==='user'?'mav u':'mav b';
581
+ av.innerHTML=role==='user'?'<i class="fa-solid fa-user"></i>':'<i class="fa-solid fa-robot"></i>';
582
+ const ts=document.createElement('span');ts.textContent=time;
583
+ if(role==='user'){
584
+ if(pinned)hdr.appendChild(_pin());hdr.appendChild(ts);hdr.appendChild(av);
585
+ } else {
586
+ hdr.appendChild(av);
587
+ if(model){const tag=document.createElement('span');tag.className='mtag';tag.textContent=model;hdr.appendChild(tag);}
588
+ hdr.appendChild(ts);if(pinned)hdr.appendChild(_pin());
589
+ }
590
+ return hdr;
591
+ }
592
+ function _pin(){const s=document.createElement('span');s.className='pinbdg';s.innerHTML='<i class="fa-solid fa-thumbtack"></i>';return s;}
593
+ function msgActions(role,idx,pinned){
594
+ const div=document.createElement('div');div.className='macts';
595
+ if(role==='user'){
596
+ div.innerHTML=`
597
+ <button class="ma" data-action="copy" data-idx="${idx}"><i class="fa-regular fa-copy"></i> نسخ</button>
598
+ <button class="ma" data-action="edit" data-idx="${idx}"><i class="fa-solid fa-pen"></i> تعديل</button>
599
+ <button class="ma pn${pinned?' on':''}" data-action="pin" data-idx="${idx}"><i class="fa-solid fa-thumbtack"></i></button>`;
600
+ } else {
601
+ div.innerHTML=`
602
+ <button class="ma" data-action="copy" data-idx="${idx}"><i class="fa-regular fa-copy"></i> نسخ</button>
603
+ <button class="ma rg" data-action="regen" data-idx="${idx}"><i class="fa-solid fa-rotate-right"></i> إعادة</button>
604
+ <button class="ma pn${pinned?' on':''}" data-action="pin" data-idx="${idx}"><i class="fa-solid fa-thumbtack"></i></button>`;
605
+ }
606
+ return div;
607
+ }
608
+ function thinkingBlock(lines,summary){
609
+ const div=document.createElement('div');div.className='thinking-block';
610
+ const sumText=summary||(lines.length?lines[0].slice(0,60)+'…':'');
611
+ const linesHTML=lines.map(l=>`<div class="thinking-line">${Utils.esc(l)}</div>`).join('');
612
+ div.innerHTML=`
613
+ <div class="thinking-header">
614
+ <i class="thinking-icon fa-solid fa-brain"></i>
615
+ <span class="thinking-label">تفكير النموذج (${lines.length} سطر)</span>
616
+ <span class="thinking-summary">${Utils.esc(sumText)}</span>
617
+ <i class="thinking-toggle fa-solid fa-chevron-down"></i>
618
+ </div>
619
+ <div class="thinking-body">${linesHTML}</div>`;
620
+ div.querySelector('.thinking-header').addEventListener('click',()=>div.classList.toggle('open'));
621
+ return div;
622
+ }
623
+ function artifactCard(art){
624
+ 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'};
625
+ const icon=ICONS[art.lang]||'fa-code';
626
+ const div=document.createElement('div');div.className='artifact';div._artData=art;
627
+ div.innerHTML=`
628
+ <div class="art-icon"><i class="fa-solid ${icon}"></i></div>
629
+ <div class="art-info">
630
+ <div class="art-name">${Utils.esc(art.filename)}</div>
631
+ <div class="art-meta">${art.lang.toUpperCase()} · ${Utils.fmtBytes(art.bytes)}</div>
632
+ </div>
633
+ <div class="art-btns">
634
+ <button class="ab view" data-action="art-view"><i class="fa-solid fa-eye"></i> عرض</button>
635
+ <button class="ab copy" data-action="art-copy"><i class="fa-regular fa-copy"></i> نسخ</button>
636
+ <button class="ab run" data-action="art-run"><i class="fa-solid fa-play"></i> تشغيل</button>
637
+ <button class="ab dl" data-action="art-dl"><i class="fa-solid fa-download"></i></button>
638
+ </div>`;
639
+ return div;
640
+ }
641
+ function typingRow(id,label){
642
+ const row=document.createElement('div');row.className='mr assistant';row.id=id;
643
+ const lbl=label||'يكتب...';
644
+ row.innerHTML=`
645
+ <div class="mhdr"><div class="mav b"><i class="fa-solid fa-robot"></i></div></div>
646
+ <div class="typing-ind">
647
+ <div class="typing-dots"><div class="typing-dot"></div><div class="typing-dot"></div><div class="typing-dot"></div></div>
648
+ <span style="font-size:.73rem;color:var(--t2)">${Utils.esc(lbl)}</span>
649
+ </div>`;
650
+ return row;
651
+ }
652
+ function welcomeScreen(){
653
+ const div=document.createElement('div');div.id='wlc';
654
+ div.innerHTML=`
655
+ <div class="wico"><i class="fa-solid fa-robot"></i></div>
656
+ <div class="wt">مرحباً في <span>g4fpro</span></div>
657
+ <div class="ws">واجهة AI تدعم جميع مزودي g4f تلقائياً — بدون مفاتيح API</div>
658
+ <div class="sg">
659
+ <div class="sc" data-prompt="اشرح الذكاء الاصطناعي بأسلوب بسيط"><span class="sc-i">🤖</span><div class="sc-t">اشرح الذكاء الاصطناعي</div><div class="sc-s">تفسير مبسط</div></div>
660
+ <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>
661
+ <div class="sc" data-prompt="اكتب رسالة بريد إلكتروني احترافية لطلب اجتماع"><span class="sc-i">✉️</span><div class="sc-t">رسالة احترافية</div><div class="sc-s">بريد عمل</div></div>
662
+ <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>
663
+ </div>`;
664
+ return div;
665
+ }
666
+ function followUpRow(suggestions){
667
+ const div=document.createElement('div');div.className='fups';
668
+ for(const s of suggestions){
669
+ const btn=document.createElement('button');
670
+ btn.className='fup';btn.textContent=s;btn.dataset.prompt=s;
671
+ div.appendChild(btn);
672
+ }
673
+ return div;
674
+ }
675
+ function toast(container,msg,type=''){
676
+ const el=document.createElement('div');el.className=`toast ${type}`.trim();el.textContent=msg;
677
+ container.appendChild(el);
678
+ setTimeout(()=>{el.style.transition='opacity .3s';el.style.opacity='0';setTimeout(()=>el.remove(),300);},2400);
679
+ }
680
+ return {msgHeader,msgActions,thinkingBlock,artifactCard,typingRow,welcomeScreen,followUpRow,toast};
681
+ })();
682
+
683
+ /* ═══════════════════════════════════════════════════════
684
+ MODULE: App
685
+ ═══════════════════════════════════════════════════════ */
686
+ const App = (() => {
687
+ // ── State ──────────────────────────────────────
688
+ const state = {
689
+ convs: Store.getConvs(),
690
+ activeId: Store.getActiveId(),
691
+ provs: {},
692
+ sbOpen: window.innerWidth > 680,
693
+ busy: false,
694
+ stopReq: false,
695
+ renId: null,
696
+ userScrolled:false,
697
+ };
698
+ const active=()=>state.convs.find(c=>c.id===state.activeId)||null;
699
+ function persist(){Store.setConvs(state.convs);Store.setActiveId(state.activeId);}
700
+ function getServerId(conv){if(!conv.serverId){conv.serverId=Utils.uid();persist();}return conv.serverId;}
701
+
702
+ // ── DOM refs ───────────────────────────────────
703
+ const $=id=>document.getElementById(id);
704
+ const DOM={
705
+ msgs: ()=>$('msgs'), mwrap: ()=>$('mwrap'),
706
+ ci: ()=>$('ci'), sndbtn: ()=>$('sndbtn'),
707
+ stopbtn:()=>$('stopbtn'),psel: ()=>$('psel'),
708
+ msel: ()=>$('msel'), stog: ()=>$('stog'),
709
+ sysp: ()=>$('sysp'), htitle: ()=>$('htitle'),
710
+ tw: ()=>$('tw'), cl: ()=>$('cl'),
711
+ ptxt: ()=>$('ptxt'), scrbtn: ()=>$('scrbtn'),
712
+ rmod: ()=>$('rmod'), rmi: ()=>$('rmi'),
713
+ rlic: ()=>$('rlic'), sb: ()=>$('sb'),
714
+ ov: ()=>$('ov'), thbtn: ()=>$('thbtn'),
715
+ showThinkTog: ()=>$('showThinkTog'),
716
+ fupTog: ()=>$('fupTog'),
717
+ themeTog: ()=>$('themeTog'),
718
+ };
719
+
720
+ // ── Scroll ─────────────────────────────────────
721
+ function scrollBot(force=false){
722
+ if(!force&&state.userScrolled)return;
723
+ const w=DOM.mwrap();
724
+ w.scrollTo({top:w.scrollHeight,behavior:'smooth'});
725
+ }
726
+ function _onScroll(){
727
+ const w=DOM.mwrap();
728
+ const atBottom=w.scrollHeight-w.scrollTop-w.clientHeight<120;
729
+ DOM.scrbtn().classList.toggle('on',!atBottom);
730
+ state.userScrolled=!atBottom;
731
+ }
732
+
733
+ // ── Toast ──────────────────────────────────────
734
+ function toast(msg,type=''){UI.toast(DOM.tw(),msg,type);}
735
+
736
+ // ── Busy ───────────────────────────────────────
737
+ function setBusy(on){
738
+ state.busy=on;
739
+ DOM.sndbtn().disabled=on;DOM.ci().disabled=on;
740
+ DOM.stopbtn().classList.toggle('on',on);
741
+ if(!on){state.stopReq=false;DOM.ci().focus();}
742
+ }
743
+ function enableInput(){
744
+ DOM.ci().disabled=false;DOM.sndbtn().disabled=false;DOM.ci().focus();
745
+ const draft=Store.getDraft();
746
+ if(draft){DOM.ci().value=draft;_resizeTa(DOM.ci());_updTok(draft);}
747
+ }
748
+
749
+ // ── Input helpers ──────────────────────────────
750
+ function _resizeTa(el){el.style.height='auto';el.style.height=Math.min(el.scrollHeight,150)+'px';}
751
+ function _updTok(text){$('tkc').textContent=Math.round(text.length/4);}
752
+ function _onInput(){const ci=DOM.ci();_resizeTa(ci);_updTok(ci.value);Store.setDraft(ci.value);}
753
+
754
+ // ── Conversations ──────────────────────────────
755
+ function newConv(){
756
+ const id='c'+Date.now();
757
+ const conv={id,title:'محادثة جديدة',msgs:[],created:Date.now(),pins:[]};
758
+ state.convs.unshift(conv);setActive(id);persist();renderConvList();
759
+ toast('✨ محادثة جديدة','ok');
760
+ if(window.innerWidth<=680)closeSidebar();
761
+ }
762
+ function setActive(id){
763
+ state.activeId=id;persist();renderConvList();renderMessages();
764
+ const c=state.convs.find(x=>x.id===id);
765
+ DOM.htitle().textContent=c?c.title:'محادثة';
766
+ }
767
+ function deleteConv(id){
768
+ if(!confirm('حذف هذه المحادثة نهائياً؟'))return;
769
+ state.convs=state.convs.filter(c=>c.id!==id);
770
+ if(state.activeId===id)state.activeId=state.convs[0]?.id||'';
771
+ persist();renderConvList();renderMessages();
772
+ }
773
+ function startRename(id){
774
+ state.renId=id;const c=state.convs.find(x=>x.id===id);
775
+ DOM.rmi().value=c?.title||'';DOM.rmod().style.display='flex';
776
+ setTimeout(()=>DOM.rmi().focus(),40);
777
+ }
778
+ function confirmRename(){
779
+ const v=DOM.rmi().value.trim();if(!v||!state.renId){closeModal();return;}
780
+ const c=state.convs.find(x=>x.id===state.renId);if(c)c.title=v;
781
+ persist();renderConvList();
782
+ if(state.activeId===state.renId)DOM.htitle().textContent=v;
783
+ closeModal();
784
+ }
785
+ function closeModal(){DOM.rmod().style.display='none';state.renId=null;}
786
+
787
+ // ── Render conv list ───────────────────────────
788
+ function renderConvList(){
789
+ const el=DOM.cl();
790
+ if(!state.convs.length){
791
+ 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>';
792
+ return;
793
+ }
794
+ const frag=document.createDocumentFragment();
795
+ const groups=Utils.groupByDate(state.convs);
796
+ for(const [label,list] of Object.entries(groups)){
797
+ if(!list.length)continue;
798
+ const grpEl=document.createElement('div');grpEl.className='grp';grpEl.textContent=label;frag.appendChild(grpEl);
799
+ for(const conv of list){
800
+ const lm=conv.msgs[conv.msgs.length-1];
801
+ const prev=lm?lm.content.slice(0,32)+(lm.content.length>32?'…':''):'لا توجد رسائل';
802
+ const item=document.createElement('div');
803
+ item.className=`ci${conv.id===state.activeId?' on':''}`;item.dataset.cid=conv.id;
804
+ item.innerHTML=`
805
+ <div class="ci-dot"></div>
806
+ <div class="ci-info">
807
+ <div class="ci-title">${Utils.esc(conv.title)}</div>
808
+ <div class="ci-sub">${Utils.esc(prev)}</div>
809
+ </div>
810
+ <div class="ci-acts">
811
+ <button class="cia" data-action="rename" data-cid="${conv.id}" title="تسمية"><i class="fa-solid fa-pen"></i></button>
812
+ <button class="cia del" data-action="delete" data-cid="${conv.id}" title="حذف"><i class="fa-solid fa-trash"></i></button>
813
+ </div>`;
814
+ frag.appendChild(item);
815
+ }
816
+ }
817
+ el.innerHTML='';el.appendChild(frag);
818
+ }
819
+
820
+ // ── Render messages ────────────────────────────
821
+ function renderMessages(){
822
+ const conv=active();const wrap=DOM.msgs();
823
+ if(!conv||!conv.msgs.length){wrap.innerHTML='';wrap.appendChild(UI.welcomeScreen());return;}
824
+ const frag=document.createDocumentFragment();
825
+ conv.msgs.forEach((m,i)=>frag.appendChild(buildMsgRow(m,i)));
826
+ wrap.innerHTML='';wrap.appendChild(frag);
827
+ state.userScrolled=false;scrollBot(true);
828
+ }
829
+
830
+ function buildMsgRow(msg,idx){
831
+ const conv=active();
832
+ const pinned=conv?.pins?.includes(idx)||false;
833
+ const t=new Date(msg.ts||Date.now()).toLocaleTimeString('ar-EG',{hour:'2-digit',minute:'2-digit'});
834
+ const row=document.createElement('div');row.className=`mr ${msg.role}`;row.dataset.i=idx;
835
+ if(msg.role!=='err') row.appendChild(UI.msgHeader(msg.role,msg.model||'',t,pinned));
836
+
837
+ // Thinking block — only if showThinkTog is on and thinking exists
838
+ const showThink=DOM.showThinkTog().checked;
839
+ if(showThink&&msg.thinking&&msg.thinking.length){
840
+ row.appendChild(UI.thinkingBlock(msg.thinking));
841
+ }
842
+
843
+ // Bubble
844
+ const bub=document.createElement('div');bub.className='mb';
845
+ if(msg.role==='assistant'){
846
+ bub.innerHTML=MD.render(msg.content);
847
+ } else {
848
+ const span=document.createElement('span');
849
+ const lines=msg.content.split('\n');
850
+ lines.forEach((line,i)=>{
851
+ span.appendChild(document.createTextNode(line));
852
+ if(i<lines.length-1)span.appendChild(document.createElement('br'));
853
+ });
854
+ bub.appendChild(span);
855
+ }
856
+ row.appendChild(bub);
857
+ if(msg.role==='user'||msg.role==='assistant') row.appendChild(UI.msgActions(msg.role,idx,pinned));
858
+ if(msg.role==='assistant') Utils.extractArtifacts(msg.content).forEach(art=>row.appendChild(UI.artifactCard(art)));
859
+ return row;
860
+ }
861
+
862
+ // ── Append user msg without full re-render ─────
863
+ function appendUserMsg(msg,idx){
864
+ $('wlc')?.remove();
865
+ DOM.msgs().appendChild(buildMsgRow(msg,idx));
866
+ }
867
+
868
+ // ── Send ───────────────────────────────────────
869
+ async function send(text){
870
+ text=(text||DOM.ci().value).trim();
871
+ if(!text||state.busy)return;
872
+ const prov=DOM.psel().value;const model=DOM.msel().value;
873
+ if(!prov){toast('⚠️ اختر مزوداً أولاً','er');return;}
874
+ if(!state.activeId||!active())newConv();
875
+ const conv=active();if(!conv)return;
876
+ $('wlc')?.remove();
877
+ const um={role:'user',content:text,ts:Date.now()};
878
+ conv.msgs.push(um);
879
+ if(conv.msgs.length===1){conv.title=text.slice(0,40)+(text.length>40?'…':'');DOM.htitle().textContent=conv.title;}
880
+ persist();renderConvList();
881
+ appendUserMsg(um,conv.msgs.length-1);
882
+ DOM.ci().value='';Store.setDraft('');_resizeTa(DOM.ci());_updTok('');
883
+ setBusy(true);state.userScrolled=false;scrollBot(true);
884
+ const lid='tp'+Date.now();
885
+ const typRow=UI.typingRow(lid,'يفكر...');
886
+ DOM.msgs().appendChild(typRow);scrollBot();
887
+ const payload={
888
+ message:text,model,provider:prov,
889
+ system_prompt:DOM.sysp().value.trim(),
890
+ conversation_id:getServerId(conv),
891
+ };
892
+ try{
893
+ if(DOM.stog().checked) await _doStream(payload,lid,conv,model);
894
+ else await _doSync(payload,lid,conv,model);
895
+ }catch(e){
896
+ document.getElementById(lid)?.remove();
897
+ const errRow=document.createElement('div');errRow.className='mr err';
898
+ errRow.innerHTML=`<div class="mb">❌ ${Utils.esc(e.message)}</div>`;
899
+ DOM.msgs().appendChild(errRow);setBusy(false);
900
+ }
901
+ }
902
+
903
+ // ── Sync call ──────────────────────────────────
904
+ async function _doSync(payload,lid,conv,model){
905
+ const data=await API.chat(payload);
906
+ document.getElementById(lid)?.remove();
907
+ if(data.conversation_id)conv.serverId=data.conversation_id;
908
+ const content=data.ok?data.reply:'❌ '+(data.error||'خطأ');
909
+ // Server returns thinking as array of strings
910
+ const thinking=data.ok?(data.thinking||[]):[];
911
+ const bm={role:'assistant',content,thinking,model,ts:Date.now()};
912
+ conv.msgs.push(bm);persist();
913
+ DOM.msgs().appendChild(buildMsgRow(bm,conv.msgs.length-1));
914
+ if(DOM.fupTog().checked) _appendFollowUps(content);
915
+ setBusy(false);scrollBot();
916
+ }
917
+
918
+ // ── Stream call ────────────────────────────────
919
+ // SSE events from server:
920
+ // {type:"start", conversation_id:"...", model:"...", provider:"..."}
921
+ // {type:"thinking", lines:["...","..."]}
922
+ // {type:"chunk", content:"..."}
923
+ // {type:"done"}
924
+ // {type:"error", content:"..."}
925
+ async function _doStream(payload,lid,conv,model){
926
+ state.stopReq=false;
927
+ const res=await API.streamChat(payload);
928
+ document.getElementById(lid)?.remove();
929
+
930
+ const bm={role:'assistant',content:'',thinking:[],model,ts:Date.now()};
931
+ conv.msgs.push(bm);
932
+ const idx=conv.msgs.length-1;
933
+
934
+ const row=document.createElement('div');row.className='mr assistant';row.dataset.i=idx;
935
+ const hdr=document.createElement('div');hdr.className='mhdr';
936
+ hdr.innerHTML=`<div class="mav b"><i class="fa-solid fa-robot"></i></div>${model?`<span class="mtag">${Utils.esc(model)}</span>`:''}`;
937
+ row.appendChild(hdr);
938
+
939
+ // Thinking placeholder (hidden until server sends thinking event)
940
+ let thinkBlockEl=null;
941
+ let thinkBodyEl=null;
942
+
943
+ // Live bubble
944
+ const bub=document.createElement('div');bub.className='mb';
945
+ bub.innerHTML='<span class="cur"></span>';
946
+ row.appendChild(bub);
947
+ DOM.msgs().appendChild(row);scrollBot();
948
+
949
+ const reader=res.body.getReader();const dec=new TextDecoder();
950
+ let full='',sseBuffer='',raf=null;
951
+ const schedRender=()=>{
952
+ if(raf)return;
953
+ raf=requestAnimationFrame(()=>{
954
+ bub.innerHTML=MD.render(full)+'<span class="cur"></span>';
955
+ scrollBot();raf=null;
956
+ });
957
+ };
958
+
959
+ try{
960
+ while(true){
961
+ if(state.stopReq){reader.cancel();break;}
962
+ const{value,done}=await reader.read();if(done)break;
963
+ sseBuffer+=dec.decode(value,{stream:true});
964
+ const lines=sseBuffer.split('\n');sseBuffer=lines.pop();
965
+ for(const line of lines){
966
+ if(!line.startsWith('data: '))continue;
967
+ let ev;try{ev=JSON.parse(line.slice(6));}catch{continue;}
968
+
969
+ if(ev.type==='start'){
970
+ if(ev.conversation_id)conv.serverId=ev.conversation_id;
971
+ }
972
+ else if(ev.type==='thinking'&&ev.lines?.length){
973
+ // Server extracted thinking block — show if toggle is on
974
+ bm.thinking=ev.lines;
975
+ const showThink=DOM.showThinkTog().checked;
976
+ if(showThink&&!thinkBlockEl){
977
+ thinkBlockEl=UI.thinkingBlock(ev.lines);
978
+ thinkBlockEl.classList.add('streaming');
979
+ row.insertBefore(thinkBlockEl,bub);
980
+ thinkBodyEl=thinkBlockEl.querySelector('.thinking-body');
981
+ } else if(showThink&&thinkBlockEl){
982
+ // Update lines
983
+ if(thinkBodyEl){
984
+ thinkBodyEl.innerHTML=ev.lines.map(l=>`<div class="thinking-line">${Utils.esc(l)}</div>`).join('');
985
+ }
986
+ }
987
+ }
988
+ else if(ev.type==='chunk'&&ev.content){
989
+ // Once chunks arrive, thinking is done streaming
990
+ if(thinkBlockEl)thinkBlockEl.classList.remove('streaming');
991
+ full+=ev.content;schedRender();
992
+ }
993
+ else if(ev.type==='error'){
994
+ full+='\n❌ '+ev.content;schedRender();
995
+ }
996
+ else if(ev.type==='done'){
997
+ break;
998
+ }
999
+ }
1000
+ }
1001
+ }catch(e){/* stream interrupted */}
1002
+
1003
+ // Final render without cursor
1004
+ cancelAnimationFrame(raf);
1005
+ bub.innerHTML=MD.render(full||'(لا يوجد رد)');
1006
+ if(thinkBlockEl)thinkBlockEl.classList.remove('streaming');
1007
+
1008
+ bm.content=full;persist();
1009
+
1010
+ // Replace streaming row with full interactive row
1011
+ const finalRow=buildMsgRow(bm,idx);
1012
+ row.replaceWith(finalRow);
1013
+ if(DOM.fupTog().checked) _appendFollowUps(full);
1014
+ setBusy(false);scrollBot();
1015
+ }
1016
+
1017
+ function _appendFollowUps(text){
1018
+ if(!text||text.startsWith('❌'))return;
1019
+ const row=UI.followUpRow(Utils.suggestFollowUps(text));
1020
+ DOM.msgs().appendChild(row);scrollBot();
1021
+ }
1022
+
1023
+ // ── Message actions ────────────────────────────
1024
+ function copyMsg(idx){
1025
+ const conv=active();
1026
+ navigator.clipboard?.writeText(conv?.msgs[idx]?.content||'');
1027
+ toast('✅ تم النسخ','ok');
1028
+ }
1029
+
1030
+ function copyCode(btn){
1031
+ const code=btn.closest('.cbw')?.querySelector('code');
1032
+ if(!code)return;
1033
+ navigator.clipboard?.writeText(code.textContent);
1034
+ const orig=btn.innerHTML;
1035
+ btn.innerHTML='<i class="fa-solid fa-check"></i> تم';
1036
+ setTimeout(()=>{btn.innerHTML=orig;},1500);
1037
+ toast('✅ تم نسخ الكود','ok');
1038
+ }
1039
+
1040
+ function runCodeBlock(btn){
1041
+ const cbw=btn.closest('.cbw');
1042
+ const code=cbw?.querySelector('code')?.textContent||'';
1043
+ const lang=cbw?.querySelector('.cb-lang')?.dataset.lang||'html';
1044
+ Sandbox.open(Utils.wrapForPreview(code,lang),`code.${lang==='javascript'?'js':lang}`);
1045
+ }
1046
+
1047
+ function editMsg(idx){
1048
+ const conv=active();const msg=conv?.msgs[idx];if(!msg)return;
1049
+ const row=DOM.msgs().querySelector(`.mr[data-i="${idx}"]`);if(!row)return;
1050
+ const bub=row.querySelector('.mb');const orig=msg.content;
1051
+ bub.innerHTML='';
1052
+ const ta=document.createElement('textarea');ta.className='etx';ta.value=orig;bub.appendChild(ta);
1053
+ const btns=document.createElement('div');btns.className='ebtns';
1054
+ const cancelBtn=document.createElement('button');cancelBtn.className='ecn';cancelBtn.textContent='إلغاء';
1055
+ cancelBtn.addEventListener('click',()=>cancelEdit(idx,orig,bub));
1056
+ const saveBtn=document.createElement('button');saveBtn.className='esv';saveBtn.textContent='إرسال';
1057
+ saveBtn.addEventListener('click',()=>saveEdit(idx,ta.value.trim()));
1058
+ btns.appendChild(cancelBtn);btns.appendChild(saveBtn);bub.appendChild(btns);
1059
+ ta.focus();ta.selectionStart=ta.value.length;
1060
+ }
1061
+
1062
+ function cancelEdit(idx,orig,bub){
1063
+ bub.innerHTML='';
1064
+ const span=document.createElement('span');
1065
+ const lines=orig.split('\n');
1066
+ lines.forEach((line,i)=>{
1067
+ span.appendChild(document.createTextNode(line));
1068
+ if(i<lines.length-1)span.appendChild(document.createElement('br'));
1069
+ });
1070
+ bub.appendChild(span);
1071
+ }
1072
+
1073
+ async function saveEdit(idx,newText){
1074
+ if(!newText)return;
1075
+ const conv=active();if(!conv)return;
1076
+ conv.msgs[idx].content=newText;conv.msgs[idx].ts=Date.now();
1077
+ conv.msgs=conv.msgs.slice(0,idx+1);
1078
+ persist();renderMessages();await _resend(newText);
1079
+ }
1080
+
1081
+ async function regen(idx){
1082
+ const conv=active();if(!conv)return;
1083
+ let ui=idx-1;
1084
+ while(ui>=0&&conv.msgs[ui].role!=='user')ui--;
1085
+ if(ui<0)return;
1086
+ const text=conv.msgs[ui].content;
1087
+ conv.msgs=conv.msgs.slice(0,idx);
1088
+ persist();renderMessages();await _resend(text);
1089
+ }
1090
+
1091
+ async function _resend(text){
1092
+ const conv=active();if(!conv)return;
1093
+ const payload={
1094
+ message:text,
1095
+ model:DOM.msel().value,
1096
+ provider:DOM.psel().value,
1097
+ system_prompt:DOM.sysp().value.trim(),
1098
+ conversation_id:getServerId(conv),
1099
+ };
1100
+ setBusy(true);
1101
+ const lid='tp'+Date.now();
1102
+ const typRow=UI.typingRow(lid,'يعيد التوليد...');
1103
+ DOM.msgs().appendChild(typRow);scrollBot();
1104
+ try{
1105
+ if(DOM.stog().checked) await _doStream(payload,lid,conv,DOM.msel().value);
1106
+ else await _doSync(payload,lid,conv,DOM.msel().value);
1107
+ }catch{document.getElementById(lid)?.remove();setBusy(false);}
1108
+ }
1109
+
1110
+ function togglePin(idx){
1111
+ const conv=active();if(!conv)return;
1112
+ if(!conv.pins)conv.pins=[];
1113
+ const i=conv.pins.indexOf(idx);
1114
+ if(i===-1){conv.pins.push(idx);toast('📌 تم التثبيت','ok');}
1115
+ else{conv.pins.splice(i,1);toast('تم إلغاء التثبيت','');}
1116
+ persist();renderMessages();
1117
+ }
1118
+
1119
+ // ── Artifact actions ───────────────────────────
1120
+ function artAction(type,el){
1121
+ const art=el.closest('.artifact')?._artData;if(!art)return;
1122
+ if(type==='art-view'||type==='art-run'){
1123
+ Sandbox.open(Utils.wrapForPreview(art.code,art.lang),art.filename);
1124
+ } else if(type==='art-copy'){
1125
+ navigator.clipboard?.writeText(art.code);
1126
+ const orig=el.innerHTML;el.innerHTML='<i class="fa-solid fa-check"></i> تم';
1127
+ setTimeout(()=>{el.innerHTML=orig;},1500);toast('✅ تم النسخ','ok');
1128
+ } else if(type==='art-dl'){
1129
+ const a=document.createElement('a');
1130
+ a.href=URL.createObjectURL(new Blob([art.code],{type:'text/plain'}));
1131
+ a.download=art.filename;a.click();URL.revokeObjectURL(a.href);
1132
+ toast('⬇️ جاري التحميل','ok');
1133
+ }
1134
+ }
1135
+
1136
+ // ── Providers ──────────────────────────────────
1137
+ async function loadProviders(){
1138
+ try{
1139
+ const data=await API.getProviders();
1140
+ if(!data.ok)throw new Error();
1141
+ state.provs=data.providers;
1142
+ _buildProviderSelect(data.providers);
1143
+ enableInput();
1144
+ toast(`✅ ${data.total} مزود`,'ok');
1145
+ _restoreProvider();
1146
+ }catch{
1147
+ toast('❌ فشل الاتصال','er');
1148
+ enableInput();
1149
+ }
1150
+ }
1151
+
1152
+ async function reloadProviders(){
1153
+ const ic=DOM.rlic();ic.classList.add('fa-spin');
1154
+ try{await API.reload();await loadProviders();}catch{}
1155
+ ic.classList.remove('fa-spin');
1156
+ }
1157
+
1158
+ function _buildProviderSelect(providers){
1159
+ const sel=DOM.psel();const frag=document.createDocumentFragment();
1160
+ // Auto first
1161
+ if(providers['Auto']){
1162
+ const o=document.createElement('option');o.value='Auto';o.textContent='⚡ Auto — تلقائي';frag.appendChild(o);
1163
+ }
1164
+ const tg=document.createElement('optgroup');tg.label='── بدون مصادقة ──';
1165
+ const ag=document.createElement('optgroup');ag.label='── تحتاج تسجيل ──';
1166
+ for(const[k,p] of Object.entries(providers)){
1167
+ if(k==='Auto')continue;
1168
+ const o=document.createElement('option');o.value=k;o.textContent=k;
1169
+ (p.needs_auth?ag:tg).appendChild(o);
1170
+ }
1171
+ if(tg.children.length)frag.appendChild(tg);
1172
+ if(ag.children.length)frag.appendChild(ag);
1173
+ sel.innerHTML='';sel.appendChild(frag);
1174
+ }
1175
+
1176
+ async function onProviderChange(){
1177
+ const pv=DOM.psel().value;
1178
+ Store.setProvider(pv);
1179
+ DOM.ptxt().textContent=pv||'—';
1180
+ const ms=DOM.msel();ms.innerHTML='<option>⏳ جاري...</option>';
1181
+ try{
1182
+ const data=await API.getModels(pv);
1183
+ const frag=document.createDocumentFragment();
1184
+ (data.models||['gpt-4o']).forEach(m=>{
1185
+ const o=document.createElement('option');o.value=m;o.textContent=m;frag.appendChild(o);
1186
+ });
1187
+ ms.innerHTML='';ms.appendChild(frag);
1188
+ _restoreModel();
1189
+ }catch{ms.innerHTML='<option value="gpt-4o">gpt-4o</option>';}
1190
+ }
1191
+
1192
+ function _restoreProvider(){
1193
+ const p=Store.getProvider();
1194
+ if(p&&[...DOM.psel().options].some(o=>o.value===p)){DOM.psel().value=p;onProviderChange();}
1195
+ else onProviderChange();
1196
+ }
1197
+
1198
+ function _restoreModel(){
1199
+ const m=Store.getModel();
1200
+ if(m&&[...DOM.msel().options].some(o=>o.value===m))DOM.msel().value=m;
1201
+ }
1202
+
1203
+ // ── Sidebar ────────────────────────────────────
1204
+ function toggleSidebar(){
1205
+ state.sbOpen=!state.sbOpen;
1206
+ const sb=DOM.sb();
1207
+ if(window.innerWidth>680){sb.classList.toggle('desk-hide',!state.sbOpen);}
1208
+ else{sb.classList.toggle('mob-open',state.sbOpen);DOM.ov().classList.toggle('on',state.sbOpen);}
1209
+ }
1210
+ function closeSidebar(){
1211
+ state.sbOpen=false;DOM.sb().classList.remove('mob-open');DOM.ov().classList.remove('on');
1212
+ }
1213
+
1214
+ // ── Theme ──────────────────────────────────────
1215
+ function toggleTheme(){
1216
+ const next=Store.getTheme()==='dark'?'light':'dark';
1217
+ Store.setTheme(next);
1218
+ document.documentElement.dataset.theme=next;
1219
+ _updateThemeIcon(next);
1220
+ DOM.themeTog().checked=next==='light';
1221
+ }
1222
+ function _updateThemeIcon(theme){
1223
+ DOM.thbtn().querySelector('i').className=theme==='dark'?'fa-solid fa-sun':'fa-solid fa-moon';
1224
+ }
1225
+
1226
+ // ── Event delegation ───────────────────────────
1227
+ function _delegateMsgs(e){
1228
+ const btn=e.target.closest('[data-action]');if(!btn)return;
1229
+ const action=btn.dataset.action;
1230
+ const idx=parseInt(btn.dataset.idx??'-1');
1231
+ if(action==='copy'&&btn.classList.contains('cb-copy')){copyCode(btn);return;}
1232
+ if(action==='run'&&btn.classList.contains('cb-run')){runCodeBlock(btn);return;}
1233
+ switch(action){
1234
+ case 'copy': copyMsg(idx);break;
1235
+ case 'edit': editMsg(idx);break;
1236
+ case 'pin': togglePin(idx);break;
1237
+ case 'regen': regen(idx);break;
1238
+ case 'art-view':case 'art-copy':case 'art-run':case 'art-dl': artAction(action,btn);break;
1239
+ }
1240
+ }
1241
+
1242
+ function _delegateConvList(e){
1243
+ const btn=e.target.closest('[data-action]');
1244
+ if(btn){
1245
+ e.stopPropagation();
1246
+ const action=btn.dataset.action;const cid=btn.dataset.cid;
1247
+ if(action==='rename')startRename(cid);
1248
+ if(action==='delete')deleteConv(cid);
1249
+ return;
1250
+ }
1251
+ const item=e.target.closest('.ci[data-cid]');
1252
+ if(item)setActive(item.dataset.cid);
1253
+ }
1254
+
1255
+ function _delegateWelcome(e){
1256
+ const card=e.target.closest('[data-prompt]');if(card)send(card.dataset.prompt);
1257
+ }
1258
+
1259
+ function _delegateFollowUps(e){
1260
+ const btn=e.target.closest('.fup[data-prompt]');if(btn)send(btn.dataset.prompt);
1261
+ }
1262
+
1263
+ // ── Init ───────────────────────────────────────
1264
+ function init(){
1265
+ if(window.marked)marked.setOptions({breaks:true,gfm:true});
1266
+
1267
+ // Theme
1268
+ const theme=Store.getTheme();
1269
+ document.documentElement.dataset.theme=theme;
1270
+ _updateThemeIcon(theme);
1271
+ DOM.themeTog().checked=theme==='light';
1272
+
1273
+ // Toggles restore
1274
+ DOM.showThinkTog().checked=Store.getShowThink();
1275
+ DOM.fupTog().checked=Store.getShowFup();
1276
+ DOM.stog().checked=Store.getStream();
1277
+
1278
+ // Sidebar initial state
1279
+ if(!state.sbOpen)DOM.sb().classList.add('desk-hide');
1280
+
1281
+ // Input events
1282
+ DOM.ci().addEventListener('input',_onInput);
1283
+ DOM.ci().addEventListener('keydown',e=>{
1284
+ if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();send();}
1285
+ });
1286
+
1287
+ // Scroll
1288
+ DOM.mwrap().addEventListener('scroll',_onScroll,{passive:true});
1289
+
1290
+ // Settings persist
1291
+ DOM.msel().addEventListener('change',()=>Store.setModel(DOM.msel().value));
1292
+ DOM.stog().addEventListener('change',()=>Store.setStream(DOM.stog().checked));
1293
+ DOM.showThinkTog().addEventListener('change',()=>{Store.setShowThink(DOM.showThinkTog().checked);renderMessages();});
1294
+ DOM.fupTog().addEventListener('change',()=>Store.setShowFup(DOM.fupTog().checked));
1295
+ DOM.themeTog().addEventListener('change',toggleTheme);
1296
+
1297
+ // Rename modal
1298
+ DOM.rmi().addEventListener('keydown',e=>{if(e.key==='Enter')confirmRename();if(e.key==='Escape')closeModal();});
1299
+ DOM.rmod().addEventListener('click',e=>{if(e.target===DOM.rmod())closeModal();});
1300
+
1301
+ // Header buttons
1302
+ $('newConvBtn').addEventListener('click',newConv);
1303
+ $('sbToggleBtn').addEventListener('click',toggleSidebar);
1304
+ DOM.ov().addEventListener('click',closeSidebar);
1305
+ DOM.thbtn().addEventListener('click',toggleTheme);
1306
+ $('reloadBtn').addEventListener('click',reloadProviders);
1307
+ DOM.sndbtn().addEventListener('click',()=>send());
1308
+ $('stopbtn').addEventListener('click',()=>{state.stopReq=true;setBusy(false);toast('تم إيقاف الرد','');});
1309
+ $('scrbtn').addEventListener('click',()=>{state.userScrolled=false;scrollBot(true);});
1310
+
1311
+ // Rename modal buttons
1312
+ $('renCancelBtn').addEventListener('click',closeModal);
1313
+ $('renSaveBtn').addEventListener('click',confirmRename);
1314
+
1315
+ // Sandbox buttons
1316
+ $('sbCloseBtn').addEventListener('click',Sandbox.close);
1317
+ $('sbRunBtn').addEventListener('click',Sandbox.run);
1318
+ document.querySelectorAll('.sb-tab').forEach(tab=>tab.addEventListener('click',()=>Sandbox.setTab(tab.dataset.tab)));
1319
+ document.addEventListener('keydown',e=>{if(e.key==='Escape')Sandbox.close();});
1320
+
1321
+ // Delegated events
1322
+ DOM.msgs().addEventListener('click',e=>{_delegateMsgs(e);_delegateWelcome(e);_delegateFollowUps(e);});
1323
+ DOM.cl().addEventListener('click',_delegateConvList);
1324
+
1325
+ // Provider select
1326
+ DOM.psel().addEventListener('change',onProviderChange);
1327
+
1328
+ // Load providers from server
1329
+ loadProviders();
1330
+ renderConvList();
1331
+
1332
+ if(state.activeId&&active()){renderMessages();}
1333
+ else if(state.convs.length){setActive(state.convs[0].id);}
1334
+ else{DOM.msgs().innerHTML='';DOM.msgs().appendChild(UI.welcomeScreen());}
1335
+ }
1336
+
1337
+ return {init};
1338
+ })();
1339
+
1340
+ App.init();
1341
+ </script>
1342
+ </body>
1343
+ </html