gijl commited on
Commit
c95b2ff
·
verified ·
1 Parent(s): 7caf1c9

Upload model_setup.py

Browse files
Files changed (1) hide show
  1. model_setup.py +194 -0
model_setup.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ خادم واجهة إعداد النموذج — يعمل على المنفذ 7860 قبل Open WebUI
4
+ يتيح تحميل النماذج من Hugging Face وتشغيلها دون إعادة البناء
5
+ """
6
+ import http.server, threading, subprocess, os, json, sys
7
+
8
+ MODELS_DIR = "/data/models"
9
+ os.makedirs(MODELS_DIR, exist_ok=True)
10
+
11
+ state = {"status": "waiting", "message": "في انتظار إدخال رابط النموذج"}
12
+ httpd = None # يُعيَّن لاحقاً
13
+
14
+ HTML = """<!DOCTYPE html>
15
+ <html lang="ar" dir="rtl">
16
+ <head>
17
+ <meta charset="UTF-8">
18
+ <meta name="viewport" content="width=device-width, initial-scale=1">
19
+ <title>مدير النماذج</title>
20
+ <style>
21
+ *{box-sizing:border-box;margin:0;padding:0}
22
+ body{font-family:'Segoe UI',Tahoma,sans-serif;background:#0f172a;color:#e2e8f0;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:1rem}
23
+ .wrap{width:100%;max-width:620px}
24
+ h1{font-size:1.75rem;color:#38bdf8;margin-bottom:.25rem}
25
+ .sub{color:#64748b;margin-bottom:2rem;font-size:.9rem}
26
+ .card{background:#1e293b;border:1px solid #334155;border-radius:14px;padding:1.5rem;margin-bottom:1.25rem}
27
+ .card-title{font-size:.7rem;text-transform:uppercase;letter-spacing:.1em;color:#64748b;margin-bottom:1rem}
28
+ label{display:block;font-size:.8rem;color:#94a3b8;margin-bottom:.3rem}
29
+ input{width:100%;padding:.6rem .9rem;background:#0f172a;border:1px solid #334155;border-radius:8px;color:#e2e8f0;font-size:.85rem;margin-bottom:.9rem;direction:ltr}
30
+ input:focus{outline:none;border-color:#38bdf8}
31
+ .btn{width:100%;padding:.75rem;border:none;border-radius:8px;font-size:.875rem;font-weight:600;cursor:pointer;transition:background .2s}
32
+ .btn-blue{background:#0ea5e9;color:#fff}.btn-blue:hover{background:#0284c7}
33
+ .btn-green{background:#059669;color:#fff;width:auto;padding:.4rem .85rem;font-size:.75rem}.btn-green:hover{background:#047857}
34
+ .btn:disabled{background:#334155;color:#475569;cursor:not-allowed}
35
+ .status{padding:.75rem 1rem;border-radius:8px;font-size:.85rem;margin-top:.75rem;display:none}
36
+ .info{background:#0c4a6e;color:#38bdf8}
37
+ .success{background:#064e3b;color:#34d399}
38
+ .error{background:#4c0519;color:#fb7185}
39
+ .loading{background:#1e1b4b;color:#818cf8}
40
+ .model-row{display:flex;align-items:center;gap:.75rem;padding:.7rem;background:#0f172a;border:1px solid #334155;border-radius:8px;margin-bottom:.5rem}
41
+ .model-name{font-size:.8rem;color:#94a3b8;flex:1;word-break:break-all;direction:ltr}
42
+ .empty{text-align:center;color:#475569;padding:1.5rem;font-size:.85rem}
43
+ @keyframes spin{to{transform:rotate(360deg)}}
44
+ .spin{display:inline-block;width:12px;height:12px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:spin .8s linear infinite;margin-right:.4rem;vertical-align:middle}
45
+ </style>
46
+ </head>
47
+ <body>
48
+ <div class="wrap">
49
+ <h1>🤖 مدير النماذج</h1>
50
+ <p class="sub">حمّل نموذجاً من Hugging Face ثم شغّله — لا شيء يُحمَّل تلقائياً</p>
51
+
52
+ <div class="card">
53
+ <div class="card-title">النماذج المحفوظة في /data/models</div>
54
+ <div id="model-list"><div class="empty">لا توجد نماذج بعد</div></div>
55
+ </div>
56
+
57
+ <div class="card">
58
+ <div class="card-title">تحميل نموذج جديد</div>
59
+ <label>معرّف المستودع (Repo ID)</label>
60
+ <input id="repo" placeholder="gijl/gemma-4-E2B-it-GGUF" value="gijl/gemma-4-E2B-it-GGUF"/>
61
+ <label>اسم ملف النموذج (.gguf)</label>
62
+ <input id="file" placeholder="model.gguf" value="gemma-4-E2B-it-UD-Q5_K_XL.gguf"/>
63
+ <label>ملف الرؤية mmproj (اختياري — للنماذج المتعددة الوسائط)</label>
64
+ <input id="mmproj" placeholder="mmproj-BF16.gguf ← اتركه فارغاً إن لم تحتجه"/>
65
+ <button class="btn btn-blue" id="dl-btn" onclick="startDownload()">⬇️ تحميل النموذج</button>
66
+ <div id="dl-status" class="status"></div>
67
+ </div>
68
+ </div>
69
+ <script>
70
+ async function loadModels(){
71
+ const {files}=await fetch('/api/models').then(r=>r.json());
72
+ const el=document.getElementById('model-list');
73
+ if(!files.length){el.innerHTML='<div class="empty">لا توجد نماذج بعد</div>';return;}
74
+ el.innerHTML=files.map(f=>`
75
+ <div class="model-row">
76
+ <span class="model-name">${f}</span>
77
+ <button class="btn btn-green" onclick="launchModel('${f}')">▶ تشغيل</button>
78
+ </div>`).join('');
79
+ }
80
+
81
+ async function startDownload(){
82
+ const repo=document.getElementById('repo').value.trim();
83
+ const file=document.getElementById('file').value.trim();
84
+ const mmproj=document.getElementById('mmproj').value.trim();
85
+ if(!repo||!file)return;
86
+ document.getElementById('dl-btn').disabled=true;
87
+ showStatus('loading','<span class="spin"></span> جارٍ بدء التحميل...');
88
+ await fetch('/api/download',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({repo,file,mmproj})});
89
+ poll();
90
+ }
91
+
92
+ async function launchModel(file){
93
+ showStatus('loading','<span class="spin"></span> جارٍ تشغيل '+file+'...');
94
+ await fetch('/api/launch',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({model:file,mmproj:''})});
95
+ poll();
96
+ }
97
+
98
+ function poll(){
99
+ const iv=setInterval(async()=>{
100
+ const d=await fetch('/api/status').then(r=>r.json());
101
+ if(d.status==='downloading') showStatus('loading','<span class="spin"></span> '+d.message);
102
+ else if(d.status==='done'){showStatus('success','✅ '+d.message);document.getElementById('dl-btn').disabled=false;loadModels();clearInterval(iv);}
103
+ else if(d.status==='error'){showStatus('error','❌ '+d.message);document.getElementById('dl-btn').disabled=false;clearInterval(iv);}
104
+ else if(d.status==='launching'){showStatus('loading','<span class="spin"></span> 🚀 '+d.message);clearInterval(iv);}
105
+ },2000);
106
+ }
107
+
108
+ function showStatus(type,msg){
109
+ const el=document.getElementById('dl-status');
110
+ el.className='status '+type;el.style.display='block';el.innerHTML=msg;
111
+ }
112
+
113
+ loadModels();
114
+ setInterval(loadModels,8000);
115
+ </script>
116
+ </body>
117
+ </html>"""
118
+
119
+
120
+ class Handler(http.server.BaseHTTPRequestHandler):
121
+ def log_message(self, *a): pass # إخفاء السجلات
122
+
123
+ def do_GET(self):
124
+ if self.path == '/api/status':
125
+ self.json(state)
126
+ elif self.path == '/api/models':
127
+ files = sorted([f for f in os.listdir(MODELS_DIR) if f.endswith('.gguf')]) if os.path.exists(MODELS_DIR) else []
128
+ self.json({"files": files})
129
+ else:
130
+ self.send_response(200)
131
+ self.send_header('Content-Type', 'text/html; charset=utf-8')
132
+ self.end_headers()
133
+ self.wfile.write(HTML.encode('utf-8'))
134
+
135
+ def do_POST(self):
136
+ body = json.loads(self.rfile.read(int(self.headers.get('Content-Length', 0))))
137
+ if self.path == '/api/download':
138
+ threading.Thread(target=do_download, args=(body,), daemon=True).start()
139
+ self.json({"ok": True})
140
+ elif self.path == '/api/launch':
141
+ threading.Thread(target=do_launch, args=(body,), daemon=True).start()
142
+ self.json({"ok": True})
143
+
144
+ def json(self, data):
145
+ self.send_response(200)
146
+ self.send_header('Content-Type', 'application/json')
147
+ self.end_headers()
148
+ self.wfile.write(json.dumps(data).encode())
149
+
150
+
151
+ def do_download(body):
152
+ state['status'] = 'downloading'
153
+ state['message'] = f"جارٍ تحميل {body['file']} ..."
154
+ os.makedirs(MODELS_DIR, exist_ok=True)
155
+
156
+ files_to_dl = [body['file']]
157
+ if body.get('mmproj'):
158
+ files_to_dl.append(body['mmproj'])
159
+
160
+ for fname in files_to_dl:
161
+ state['message'] = f"جارٍ تحميل {fname} ..."
162
+ r = subprocess.run(
163
+ ['hf', 'download', body['repo'], fname, '--local-dir', MODELS_DIR],
164
+ capture_output=True, text=True
165
+ )
166
+ if r.returncode != 0:
167
+ state['status'] = 'error'
168
+ state['message'] = r.stderr.strip() or 'فشل التحميل'
169
+ return
170
+
171
+ state['status'] = 'done'
172
+ state['message'] = f"اكتمل تحميل {body['file']} — اضغط تشغيل"
173
+
174
+
175
+ def do_launch(body):
176
+ state['status'] = 'launching'
177
+ state['message'] = 'جارٍ تشغيل llama.cpp وOpen WebUI...'
178
+
179
+ with open('/tmp/selected_model', 'w') as f:
180
+ f.write(body.get('model', ''))
181
+ with open('/tmp/selected_mmproj', 'w') as f:
182
+ f.write(body.get('mmproj', ''))
183
+
184
+ open('/tmp/launch_signal', 'w').close()
185
+
186
+ # إيقاف الخادم بشكل آمن من thread مختلف
187
+ threading.Thread(target=httpd.shutdown, daemon=True).start()
188
+
189
+
190
+ if __name__ == '__main__':
191
+ httpd = http.server.HTTPServer(('0.0.0.0', 7860), Handler)
192
+ print(">>> واجهة إعداد النموذج تعمل على http://0.0.0.0:7860", flush=True)
193
+ httpd.serve_forever()
194
+ print(">>> واجهة الإعداد أُغلقت، جارٍ تسليم المنفذ لـ Open WebUI...", flush=True)