File size: 5,343 Bytes
3832cbe
a974e8c
 
 
 
 
 
 
c3ca188
a974e8c
 
c3ca188
 
a974e8c
 
3832cbe
a974e8c
 
 
 
d02a476
3832cbe
a974e8c
 
 
 
 
 
 
c3ca188
a974e8c
 
 
 
 
 
 
 
 
 
 
 
c3ca188
a974e8c
 
 
 
c3ca188
a974e8c
 
 
 
 
 
 
 
 
 
3832cbe
a974e8c
 
 
 
 
 
 
c3ca188
 
a974e8c
c3ca188
a974e8c
 
 
 
 
 
 
c3ca188
a974e8c
 
3832cbe
a974e8c
 
c3ca188
a974e8c
d02a476
c3ca188
 
 
a974e8c
 
 
 
 
 
c3ca188
 
 
a974e8c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c3ca188
a974e8c
 
 
 
 
 
 
 
 
 
 
 
 
c3ca188
 
 
 
d02a476
 
c3ca188
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
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()