Spaces:
Sleeping
Sleeping
| 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 عادي. | |
| 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") | |
| def whatsapp_get(): | |
| return {"status": "WhatsApp webhook OK"} | |
| if __name__ == "__main__": | |
| demo.launch() | |