Spaces:
Running
Running
# noura_server.py | |
import os, time, hmac, traceback | |
from typing import Any, Dict | |
from flask import Flask, request, jsonify | |
from dotenv import load_dotenv | |
import importlib | |
from concurrent.futures import ThreadPoolExecutor, TimeoutError as FuturesTimeoutError | |
# (اختياري) لو عندك هذه الملفات فعلاً: | |
try: | |
import analyzer, brain, core, knowledge_search, learner, media_analyzer, memory, self_learning, self_improvement # noqa: F401 | |
except Exception as e: | |
print("[WARN] helper modules import issue:", repr(e)) | |
# --- بيئة --- | |
load_dotenv() | |
API_KEY = (os.getenv("NOURA_API_KEY", "changeme") or "").strip() | |
MIN_SIM_DEFAULT = float(os.getenv("MIN_SIMILARITY", "0.82")) | |
ADAPTER_MODULE = os.getenv("SELF_LEARNING_MODULE", "self_learning_adapter") | |
# --- الأداپتر --- | |
sl = importlib.import_module(ADAPTER_MODULE) | |
sl.init({"min_similarity": MIN_SIM_DEFAULT}) | |
# (اختياري) مولّد ردود مخصص | |
try: | |
from telegram_listener import generate_reply as custom_generate_reply | |
except Exception: | |
custom_generate_reply = None | |
# --- Flask --- | |
app = Flask(__name__) | |
# --- ThreadPool للمهل --- | |
EXEC = ThreadPoolExecutor(max_workers=4) | |
def call_with_timeout(fn, *args, timeout=1.8, default=None, label=""): | |
fut = EXEC.submit(fn, *args) | |
try: | |
return fut.result(timeout=timeout) | |
except FuturesTimeoutError: | |
print(f"[TIMEOUT] {label or fn.__name__} > {timeout}s") | |
return default | |
except Exception as e: | |
print(f"[ERR] {label or fn.__name__} crashed:", repr(e)) | |
return default | |
def verify_auth(req) -> bool: | |
key = (req.headers.get("x-api-key") or "").strip() | |
ok = hmac.compare_digest(key, API_KEY) | |
if not ok: | |
print(f"[AUTH FAIL] header={repr(key)} expected={repr(API_KEY)}") | |
return ok | |
def health(): | |
return jsonify({"ok": True}), 200 | |
def chat(): | |
try: | |
if not verify_auth(request): | |
return jsonify({"error": "unauthorized"}), 401 | |
t0 = time.time() | |
data: Dict[str, Any] = request.get_json(force=True) or {} | |
user = (data.get("user") or "unknown").strip() | |
text = (data.get("text") or "").strip() | |
meta = data.get("meta") or {} | |
chat_id = str(meta.get("peer_id", "unknown")) | |
ts = int(time.time()) | |
reply = "" | |
used_memory = False | |
# 1) استرجاع من الذاكرة (بمهلة) | |
if text: | |
cand, score = call_with_timeout( | |
sl.find_best, chat_id, text, | |
timeout=1.8, default=(None, 0.0), label="find_best" | |
) or (None, 0.0) | |
if cand and score >= MIN_SIM_DEFAULT: | |
reply, used_memory = cand, True | |
# 2) مولد مخصص (اختياري) | |
if not reply and custom_generate_reply: | |
r = call_with_timeout(custom_generate_reply, text, timeout=1.5, default="", label="custom_generate_reply") | |
reply = (r or "").strip() | |
# 3) fallback | |
if not reply: | |
low = text.lower() | |
if any(w in low for w in ["سهم", "اسهم", "stocks", "stock"]): | |
reply = "تنبيه: لستُ مستشارًا ماليًا. اكتب سؤالك بدقة وسأساعدك بشكل عام." | |
else: | |
reply = f"وصلتني رسالتك: «{text}». ماذا تريد بعدها؟" | |
# 4) حفظ التعلم (لا يمنع الرد حتى لو تأخر) | |
if text: | |
_ = call_with_timeout(sl.add_example, chat_id, user, text, reply, meta, timeout=1.8, label="add_example") | |
src = "memory" if used_memory else "fallback" | |
print(f"[CHAT] chat_id={chat_id} src={src} dt={(time.time()-t0)*1000:.1f}ms text={text[:60]}") | |
return jsonify({"reply": reply, "ts": ts, "used_memory": used_memory}) | |
except Exception: | |
print("[ERR] /chat top-level:\n", traceback.format_exc()) | |
return jsonify({"error": "internal_error"}), 200 | |
def admin_learn_toggle(): | |
if not verify_auth(request): return jsonify({"error":"unauthorized"}), 401 | |
data = request.get_json(force=True) or {} | |
chat_id = str(data.get("chat_id")) | |
enabled = bool(data.get("enabled", True)) | |
min_sim = data.get("min_similarity") | |
ms = MIN_SIM_DEFAULT if min_sim is None else float(min_sim) | |
try: | |
sl.set_chat_config(chat_id, learn_enabled=enabled, min_similarity=ms) | |
except Exception as e: | |
print("[ERR] set_chat_config:\n", traceback.format_exc()) | |
return jsonify({"ok": True, "learn_enabled": enabled}) | |
def admin_stats(): | |
if not verify_auth(request): return jsonify({"error":"unauthorized"}), 401 | |
chat_id = request.args.get("chat_id") | |
try: | |
c = sl.stats(chat_id) | |
except Exception: | |
print("[ERR] stats:\n", traceback.format_exc()) | |
c = 0 | |
return jsonify({"ok": True, "count": c}) | |
if __name__ == "__main__": | |
app.run(host="0.0.0.0", port=9531) | |