import os import tempfile import requests import speech_recognition as sr from pydub import AudioSegment from huggingface_hub import hf_hub_download from llama_cpp import Llama import gradio as gr from fastapi import Request, Response from twilio.twiml.messaging_response import MessagingResponse # ============ إعدادات (من Space Secrets) ============ HF_TOKEN = os.environ.get("HF_TOKEN") TWILIO_SID = os.environ.get("TWILIO_ACCOUNT_SID") TWILIO_AUTH = os.environ.get("TWILIO_AUTH_TOKEN") SYSTEM_PROMPT = ( "أنت مساعد ذكي ودود. أجب باللغة العربية فقط مهما كانت لغة السؤال. " "ممنوع استخدام أي لغة أخرى أو أي حروف غير عربية. " "أجب باختصار وبأسلوب محادثاتي طبيعي." ) # ============ تحميل موديل Qwen 2.5 3B GGUF ============ print("⏳ بتحميل موديل Qwen 2.5 3B GGUF ...") model_path = hf_hub_download( repo_id="Qwen/Qwen2.5-3B-Instruct-GGUF", filename="qwen2.5-3b-instruct-q4_k_m.gguf", token=HF_TOKEN, ) llm = Llama(model_path=model_path, n_ctx=2048, n_threads=4, verbose=False) print("✅ الموديل جاهز") def chat_llm(user_text: str, history: list | None = None) -> str: messages = [{"role": "system", "content": SYSTEM_PROMPT}] if history: for u, a in history: messages.append({"role": "user", "content": u}) messages.append({"role": "assistant", "content": a}) messages.append({"role": "user", "content": user_text}) out = llm.create_chat_completion( messages=messages, max_tokens=400, temperature=0.7, ) return out["choices"][0]["message"]["content"].strip() # ============ Speech Recognition (Google - مجاني) ============ def transcribe_audio(audio_path: str) -> str: sound = AudioSegment.from_file(audio_path) sound = sound.set_channels(1).set_frame_rate(16000) with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f: sound.export(f.name, format="wav") wav_path = f.name r = sr.Recognizer() with sr.AudioFile(wav_path) as source: audio = r.record(source) try: return r.recognize_google(audio, language="ar-EG") except sr.UnknownValueError: return "" # ============ واجهة Gradio ============ def handle_text(message, history): if not message.strip(): return "", history reply = chat_llm(message, history) return "", history + [(message, reply)] def handle_voice(audio, history): if not audio: return history text = transcribe_audio(audio) if not text: return history + [("(رسالة صوتية)", "معلش، ما قدرتش أفهم الصوت.")] reply = chat_llm(text, history) return history + [(f"🎤 {text}", reply)] with gr.Blocks(title="المساعد الصوتي الذكي") as demo: gr.Markdown("# 🤖 المساعد الصوتي الذكي بالعربي\nاكتبي أو سجّلي صوت — الرد بالعربي.") chatbot = gr.Chatbot(height=420, rtl=True) with gr.Row(): txt = gr.Textbox(placeholder="اكتبي رسالتك...", rtl=True, scale=4, show_label=False) send = gr.Button("إرسال", scale=1, variant="primary") mic = gr.Audio(sources=["microphone"], type="filepath", label="🎤 سجّلي صوت") send.click(handle_text, [txt, chatbot], [txt, chatbot]) txt.submit(handle_text, [txt, chatbot], [txt, chatbot]) mic.stop_recording(handle_voice, [mic, chatbot], [chatbot]) # ============ WhatsApp Webhook داخل نفس Gradio app ============ # Gradio بيستخدم FastAPI جوّه، فنقدر نضيف route عادي. @demo.app.post("/whatsapp") async def whatsapp_webhook(request: Request): form = await request.form() body = (form.get("Body") or "").strip() num_media = int(form.get("NumMedia") or 0) resp = MessagingResponse() try: if num_media > 0: media_url = form.get("MediaUrl0") media_type = form.get("MediaContentType0", "") if media_url and media_type.startswith("audio"): r = requests.get(media_url, auth=(TWILIO_SID, TWILIO_AUTH)) r.raise_for_status() with tempfile.NamedTemporaryFile(suffix=".ogg", delete=False) as f: f.write(r.content) audio_path = f.name transcript = transcribe_audio(audio_path) if not transcript: resp.message("معلش، مقدرتش أفهم الرسالة الصوتية.") return Response(str(resp), media_type="application/xml") body = f"{body}\n{transcript}" if body else transcript if not body: resp.message("اكتبي سؤالك أو ابعتي رسالة صوتية 🎤") return Response(str(resp), media_type="application/xml") reply = chat_llm(body) resp.message(reply) except Exception as e: print("whatsapp error:", e) resp.message("حصل خطأ مؤقت، حاولي تاني.") return Response(str(resp), media_type="application/xml") @demo.app.get("/whatsapp") def whatsapp_get(): return {"status": "WhatsApp webhook OK"} if __name__ == "__main__": demo.launch()