#!/usr/bin/env python3
"""
خادم واجهة إعداد النموذج — يعمل على المنفذ 7860 قبل Open WebUI
يتيح تحميل النماذج من Hugging Face وتشغيلها دون إعادة البناء
"""
import http.server, threading, subprocess, os, json, sys
MODELS_DIR = "/data/models"
os.makedirs(MODELS_DIR, exist_ok=True)
state = {"status": "waiting", "message": "في انتظار إدخال رابط النموذج"}
httpd = None # يُعيَّن لاحقاً
HTML = """
مدير النماذج
🤖 مدير النماذج
حمّل نموذجاً من Hugging Face ثم شغّله — لا شيء يُحمَّل تلقائياً
النماذج المحفوظة في /data/models
"""
class Handler(http.server.BaseHTTPRequestHandler):
def log_message(self, *a): pass # إخفاء السجلات
def do_GET(self):
if self.path == '/api/status':
self.json(state)
elif self.path == '/api/models':
files = sorted([f for f in os.listdir(MODELS_DIR) if f.endswith('.gguf')]) if os.path.exists(MODELS_DIR) else []
self.json({"files": files})
else:
self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(HTML.encode('utf-8'))
def do_POST(self):
body = json.loads(self.rfile.read(int(self.headers.get('Content-Length', 0))))
if self.path == '/api/download':
threading.Thread(target=do_download, args=(body,), daemon=True).start()
self.json({"ok": True})
elif self.path == '/api/launch':
threading.Thread(target=do_launch, args=(body,), daemon=True).start()
self.json({"ok": True})
def json(self, data):
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(data).encode())
def do_download(body):
state['status'] = 'downloading'
state['message'] = f"جارٍ تحميل {body['file']} ..."
os.makedirs(MODELS_DIR, exist_ok=True)
files_to_dl = [body['file']]
if body.get('mmproj'):
files_to_dl.append(body['mmproj'])
for fname in files_to_dl:
state['message'] = f"جارٍ تحميل {fname} ..."
r = subprocess.run(
['hf', 'download', body['repo'], fname, '--local-dir', MODELS_DIR],
capture_output=True, text=True
)
if r.returncode != 0:
state['status'] = 'error'
state['message'] = r.stderr.strip() or 'فشل التحميل'
return
state['status'] = 'done'
state['message'] = f"اكتمل تحميل {body['file']} — اضغط تشغيل"
def do_launch(body):
state['status'] = 'launching'
state['message'] = 'جارٍ تشغيل llama.cpp وOpen WebUI...'
with open('/tmp/selected_model', 'w') as f:
f.write(body.get('model', ''))
with open('/tmp/selected_mmproj', 'w') as f:
f.write(body.get('mmproj', ''))
open('/tmp/launch_signal', 'w').close()
# إيقاف الخادم بشكل آمن من thread مختلف
threading.Thread(target=httpd.shutdown, daemon=True).start()
if __name__ == '__main__':
httpd = http.server.HTTPServer(('0.0.0.0', 7860), Handler)
print(">>> واجهة إعداد النموذج تعمل على http://0.0.0.0:7860", flush=True)
httpd.serve_forever()
print(">>> واجهة الإعداد أُغلقت، جارٍ تسليم المنفذ لـ Open WebUI...", flush=True)