Spaces:
Running
Running
#!/usr/bin/env python3 | |
import os | |
import json | |
import subprocess | |
import shutil | |
import socket | |
import time | |
import telegram_listener | |
HISTORY_PATH = "history.json" | |
# ترتيب تفضيلي للنماذج (سنختار أول نموذج متاح منها) | |
PREFERRED_MODELS = [ | |
"nous-hermes2", # مفضل أولاً إذا كان منصّباً | |
os.getenv("OLLAMA_MODEL", "mistral:instruct"), | |
"mistral:latest", | |
"gemma3:4b", | |
"tinyllama:latest", | |
] | |
# استيراد آمن لملف responses.py | |
try: | |
from responses import generate_reply as _generate_reply | |
except Exception: | |
def _generate_reply(*args, **kwargs): | |
return None | |
def ensure_ollama(): | |
""" | |
يتحقّق من توفّر ollama CLI (باستخدام --version) ويضمن أن السيرفر شغّال. | |
لو السيرفر غير شغّال، يشغّله وينتظر جاهزيته. | |
""" | |
# استخدم المسار المباشر أولاً لتجنّب أي shadow لملف باسم 'ollama' داخل المشروع | |
win_exe = r"C:\Users\osamawin\AppData\Local\Programs\Ollama\ollama.exe" | |
cli = win_exe if os.path.exists(win_exe) else shutil.which("ollama") | |
if not cli: | |
raise RuntimeError("ollama CLI غير موجود. ثبّته أو أضِفه للـ PATH.") | |
# تأكّد من الـ CLI | |
try: | |
subprocess.run([cli, "--version"], check=True, | |
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) | |
except Exception as e: | |
raise RuntimeError("تعذّر تشغيل 'ollama --version'. تأكّد من التثبيت.") from e | |
# تحقّق من أن السيرفر يستمع؛ استخدم OLLAMA_HOST إن وُجد وإلا الافتراضي 127.0.0.1:11434 | |
host = os.environ.get("OLLAMA_HOST", "127.0.0.1:11434") | |
ip, port = host.split(":") | |
port = int(port) | |
def _is_up(): | |
try: | |
with socket.create_connection((ip, port), timeout=0.8): | |
return True | |
except OSError: | |
return False | |
if _is_up(): | |
return | |
# شغّل السيرفر في الخلفية | |
cmd = [cli, "serve"] | |
if "OLLAMA_HOST" in os.environ: | |
cmd += ["--host", host] | |
subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) | |
# انتظر الجاهزية (TCP) | |
for _ in range(60): | |
if _is_up(): | |
return | |
time.sleep(0.2) | |
raise RuntimeError(f"فشل تشغيل ollama serve على {host}.") | |
def list_installed_models(): | |
""" | |
يرجع قائمة أسماء النماذج المنصّبة محلياً عبر 'ollama list'. | |
""" | |
try: | |
out = subprocess.run( | |
["ollama", "list"], | |
check=True, | |
stdout=subprocess.PIPE, | |
stderr=subprocess.PIPE, | |
text=True | |
).stdout | |
except subprocess.CalledProcessError as e: | |
raise RuntimeError(f"خطأ عند قراءة قائمة النماذج: {e.stderr.strip() or e.stdout.strip()}") | |
models = [] | |
for line in out.splitlines(): | |
line = line.strip() | |
if not line or line.startswith("NAME") or line.startswith("-"): | |
continue | |
# السطر يبدأ بـ NAME ثم أعمدة أخرى، ناخذ أول عمود | |
parts = line.split() | |
if parts: | |
models.append(parts[0]) | |
return models | |
def pick_default_model(installed): | |
for m in PREFERRED_MODELS: | |
if m in installed: | |
return m | |
if installed: | |
return installed[0] | |
raise RuntimeError("لا توجد نماذج منصّبة في Ollama. ثبّت نموذجاً أولاً (مثلاً: ollama pull mistral:instruct).") | |
def ollama_generate(model, prompt, timeout=120): | |
""" | |
يستدعي: ollama run <model> "<prompt>" | |
(بدون -p لأن إصدارك لا يدعمه) | |
""" | |
try: | |
res = subprocess.run( | |
["ollama", "run", model, prompt], # ← لا تستخدم -p | |
check=False, | |
stdout=subprocess.PIPE, | |
stderr=subprocess.PIPE, | |
text=True, | |
timeout=timeout, | |
) | |
out = (res.stdout or "").strip() | |
err = (res.stderr or "").strip() | |
if res.returncode != 0: | |
raise RuntimeError(err or out or "خروج غير صفري من ollama") | |
if not out: | |
raise RuntimeError(f"{model} لم يرجّع أي مخرجات.") | |
return out | |
except subprocess.TimeoutExpired: | |
raise RuntimeError(f"انتهى الوقت المحدد لطلب النموذج ({model}). جرّب لاحقاً أو خفّض طول البرومبت.") | |
def load_history(): | |
if os.path.exists(HISTORY_PATH): | |
with open(HISTORY_PATH, "r", encoding="utf-8") as f: | |
return json.load(f) | |
return [] | |
def save_history(history): | |
with open(HISTORY_PATH, "w", encoding="utf-8") as f: | |
json.dump(history, f, ensure_ascii=False, indent=2) | |
def simulate_server_scan(): | |
print("نورا: أبحث عن خوادم...") | |
fake_servers = ["192.168.1.5", "192.168.1.10", "192.168.1.20"] | |
for server in fake_servers: | |
print(f"نورا: تم العثور على خادم مفتوح في {server}") | |
def format_chat_prompt(history, user_utterance): | |
""" | |
نبني برومبت بسيط يحافظ على سياق مختصر. | |
يمكنك تطويره لاحقاً لتنسيق ChatML أو JSON حسب النموذج. | |
""" | |
system = "أنت المساعدة نورا. تحدثي بلغة عربية فصحى بسيطة." | |
lines = [f"system: {system}"] | |
for msg in history[-6:]: # آخر 6 رسائل فقط لتقليل الطول | |
role = msg.get("role", "user") | |
content = msg.get("content", "") | |
lines.append(f"{role}: {content}") | |
lines.append(f"user: {user_utterance}") | |
lines.append("assistant:") | |
return "\n".join(lines) | |
def chat(): | |
ensure_ollama() | |
installed = list_installed_models() | |
active_model = pick_default_model(installed) | |
chat_history = load_history() | |
print(f""" | |
نظام نورا الذكي (Ollama) | |
النموذج الحالي: {active_model} | |
أوامر خاصة: | |
- /models : عرض النماذج المنصّبة | |
- /model NAME : تبديل النموذج (مثال: /model mistral:instruct) | |
- scan : مسح الشبكة (محاكاة) | |
- خروج | exit | quit : إنهاء المحادثة | |
""") | |
while True: | |
try: | |
user_input = input("أنت: ").strip() | |
if not user_input: | |
continue | |
low = user_input.lower() | |
if low in ["خروج", "exit", "quit"]: | |
break | |
if low == "scan": | |
simulate_server_scan() | |
continue | |
if low == "/models": | |
print("النماذج المتاحة محلياً:") | |
for m in installed: | |
print(" -", m) | |
continue | |
if low.startswith("/model"): | |
# صيغة: /model NAME | |
parts = user_input.split(maxsplit=1) | |
if len(parts) == 1: | |
print(f"النموذج الحالي: {active_model}") | |
continue | |
candidate = parts[1].strip() | |
if candidate not in installed: | |
print(f"⚠️ النموذج '{candidate}' غير منصّب. النماذج المتاحة: {', '.join(installed)}") | |
continue | |
active_model = candidate | |
print(f"✅ تم تبديل النموذج إلى: {active_model}") | |
continue | |
# أولاً: ردود مخصصة من responses.py إن توفرت | |
custom_reply = None | |
try: | |
r = _generate_reply(user_input, username="أسامة") | |
# تجاهل رسالة الخطأ الجاهزة كي لا توقف تدفق الرد من النموذج | |
if r and not r.strip().startswith("عذراً، حدث خطأ"): | |
custom_reply = r | |
except Exception: | |
custom_reply = None | |
if custom_reply is not None: | |
print("نورا:", custom_reply) | |
chat_history.append({"role": "user", "content": user_input}) | |
chat_history.append({"role": "assistant", "content": custom_reply}) | |
if len(chat_history) % 3 == 0: | |
save_history(chat_history) | |
continue | |
# إذا لا يوجد رد مخصص → نستخدم النموذج النشط عبر Ollama | |
chat_history.append({"role": "user", "content": user_input}) | |
prompt = format_chat_prompt(chat_history, user_input) | |
print("نورا: أفكر... (", active_model, ")") | |
try: | |
model_reply = ollama_generate(active_model, prompt) | |
except RuntimeError as e: | |
# فشل؟ جرّب بدائل بالتتابع مع طباعة سبب الفشل | |
print(f"⚠️ فشل مع {active_model}: {e}\n🔁 أجرب بدائل...") | |
fallback = None | |
for m in PREFERRED_MODELS: | |
if m in installed and m != active_model: | |
try: | |
print(f"→ تجربة {m} ...") | |
model_reply = ollama_generate(m, prompt) | |
fallback = m | |
break | |
except Exception as ee: | |
print(f" × فشل {m}: {ee}") | |
continue | |
if fallback is None: | |
print("نورا: حدث خطأ:", str(e)) | |
continue | |
else: | |
active_model = fallback | |
print(f"✅ تم التبديل تلقائياً إلى: {active_model}") | |
# غالباً المخرجات ستكون مجرد نص رد | |
assistant_response = model_reply.strip() | |
print("نورا:", assistant_response) | |
chat_history.append({"role": "assistant", "content": assistant_response}) | |
# احفظ كل 3 رسائل لتقليل الكتابة | |
if len(chat_history) % 3 == 0: | |
save_history(chat_history) | |
except KeyboardInterrupt: | |
print("\nنورا: تم إنهاء المحادثة.") | |
break | |
except Exception as e: | |
print(f"نورا: حدث خطأ: {str(e)}") | |
continue | |
# حفظ السجل النهائي عند الخروج | |
save_history(chat_history) | |
if __name__ == "__main__": | |
chat() | |