Ranoosh / main.py
mrwabnalas40's picture
Update main.py
1dd4ff8 verified
raw
history blame
10.8 kB
#!/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()