Asem75 commited on
Commit
11053ab
·
verified ·
1 Parent(s): a70364a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1 -189
app.py CHANGED
@@ -1,189 +1 @@
1
- import os
2
- import re
3
- import warnings
4
- import gradio as gr
5
- from gradio_client import Client
6
- import torch
7
- from transformers import AutoModelForCausalLM, AutoTokenizer
8
-
9
- # ======================== كتم التحذيرات ========================
10
- warnings.filterwarnings("ignore")
11
-
12
- # ======================== الإعدادات ========================
13
- DEVICE = "cpu"
14
- WHISPER_MODEL = "openai/whisper-base"
15
- MODEL_ID = "Qwen/Qwen2.5-0.5B-Instruct"
16
- RADAR_SPACE_URL = "Asem75/Aiocr_Radar"
17
-
18
- print("⏳ تحميل النموذج...")
19
- _llm_tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
20
- _llm_model = AutoModelForCausalLM.from_pretrained(
21
- MODEL_ID, torch_dtype=torch.float32, device_map="cpu", low_cpu_mem_usage=True
22
- )
23
- print("✅ النموذج جاهز")
24
-
25
- # ======================== Whisper ========================
26
- _whisper_pipe = None
27
-
28
- def load_whisper():
29
- global _whisper_pipe
30
- if _whisper_pipe is not None:
31
- return _whisper_pipe
32
- from transformers import pipeline
33
- _whisper_pipe = pipeline("automatic-speech-recognition", model=WHISPER_MODEL, device=DEVICE, chunk_length_s=30)
34
- return _whisper_pipe
35
-
36
-
37
- # ======================== الرادار ========================
38
- # ⚠️ إصلاح هام: نقطة API "/synth_arabic" على مساحة الرادار تستقبل
39
- # بالضبط معاملين فقط: (text, speaker_name) — وهي مصمّمة خصيصاً لهذا
40
- # الاستخدام الخارجي. الكود القديم هنا كان يستدعيها بـ7 معاملات تطابق
41
- # توقيع دالة الواجهة الداخلية القديمة (synth_arabic) وليس نقطة الـ API
42
- # الفعلية، فكان هذا الاستدعاء يفشل دائماً (يُمسَك بصمت داخل except).
43
- #
44
- # ⚠️ إصلاح ثانٍ: "ar-JO-IsmaeelNeural" و"ar-AE-AminaNeural" غير
45
- # موجودين أصلاً في قائمة أصوات مايكروسوفت الرسمية (الأردن: Sana/Taim
46
- # فقط، الإمارات: Fatima/Hamdan فقط؛ Amina هي صوت جزائري ar-DZ تحديداً) —
47
- # استخدامهما يسبب NoAudioReceived. استبدلتهما بأصوات حقيقية موثّقة.
48
- def call_radar_api(text, subject, detected_emotion):
49
- try:
50
- client = Client(RADAR_SPACE_URL)
51
- word_count = len(text.split())
52
-
53
- if word_count <= 35:
54
- speaker_name = "ar-EG-SalmaNeural"
55
- else:
56
- voice_map = {
57
- "🧮 الرياضيات والعلوم": "ar-JO-TaimNeural",
58
- "🕌 اللغة العربية والتربية الإسلامية": "ar-SA-HamedNeural",
59
- "🔤 اللغة الإنجليزية": "en-US-JennyNeural",
60
- }
61
- speaker_name = voice_map.get(subject, "ar-JO-TaimNeural")
62
-
63
- audio_url = client.predict(
64
- text, speaker_name,
65
- api_name="/synth_arabic"
66
- )
67
-
68
- if audio_url and isinstance(audio_url, (list, tuple)):
69
- audio_url = audio_url[0]
70
- return str(audio_url).strip() if audio_url else None
71
- except Exception as e:
72
- print(f"⚠️ خطأ الرادار: {e}")
73
- return None
74
-
75
-
76
- # ======================== دالة المحادثة ========================
77
- def teacher_chat(audio_mic, subject, chat_history_state):
78
- """
79
- التاريخ يُحفظ في gr.State كقائمة قواميس نظيفة بالضبط:
80
- [{"role": "user"/"assistant", "content": "..."}, ...]
81
- """
82
- if audio_mic is None:
83
- return chat_history_state, None, "🎤 الرجاء تسجيل السؤال أولاً"
84
-
85
- # 1. تحويل الصوت لنص
86
- try:
87
- whisper = load_whisper()
88
- result = whisper(audio_mic, generate_kwargs={"language": "arabic"})
89
- user_text = result["text"].strip()
90
- except Exception as e:
91
- return chat_history_state, None, f"⚠️ خطأ: {e}"
92
-
93
- if not user_text:
94
- return chat_history_state, None, "❌ لم يتم التعرف على كلام"
95
-
96
- print(f"🗣️ الطالب: {user_text}")
97
-
98
- # 2. بناء messages
99
- system_prompt = (
100
- "أنت الأستاذ عبود، معلم خبير. "
101
- "أجب بإيجاز (30 كلمة كحد أقصى). "
102
- "لا تعط الإجابة كاملة، قسمها لخطوات. "
103
- "اطرح سؤالاً في النهاية. "
104
- "أضف [حالة: حماس] أو [حالة: هدوء]."
105
- )
106
- messages = [{"role": "system", "content": system_prompt}]
107
- for msg in chat_history_state:
108
- messages.append({"role": msg["role"], "content": msg["content"]})
109
- messages.append({"role": "user", "content": user_text})
110
-
111
- # 3. توليد الرد
112
- try:
113
- text_prompt = _llm_tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
114
- model_inputs = _llm_tokenizer([text_prompt], return_tensors="pt").to(DEVICE)
115
-
116
- with torch.no_grad():
117
- generated_ids = _llm_model.generate(
118
- **model_inputs, max_new_tokens=80, temperature=0.4,
119
- do_sample=True, pad_token_id=_llm_tokenizer.eos_token_id, use_cache=True
120
- )
121
-
122
- generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)]
123
- full_response = _llm_tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0].strip()
124
-
125
- emotion_match = re.search(r'\[حالة:\s*(\w+)\]', full_response)
126
- detected_emotion = emotion_match.group(1) if emotion_match else "هدوء"
127
- cleaned_response = re.sub(r'\[حالة:\s*\w+\]', '', full_response).strip()
128
-
129
- if not cleaned_response.endswith(('.', '؟', '!')):
130
- cleaned_response += '.'
131
-
132
- print(f"🤖 الأستاذ: {cleaned_response}")
133
- except Exception as e:
134
- return chat_history_state, None, f"⚠️ خطأ النموذج: {e}"
135
-
136
- # 4. تحديث التاريخ في الـ state (قائمة قواميس نظيفة دائماً)
137
- chat_history_state = chat_history_state + [
138
- {"role": "user", "content": user_text},
139
- {"role": "assistant", "content": cleaned_response},
140
- ]
141
-
142
- # 5. توليد الصوت
143
- audio_path = call_radar_api(cleaned_response, subject, detected_emotion)
144
- status = f"✅ ({detected_emotion})" if audio_path else "⚠️ فشل الصوت"
145
- return chat_history_state, audio_path, status
146
-
147
-
148
- # ======================== الواجهة ========================
149
- with gr.Blocks(title="الأستاذ عبود", theme=gr.themes.Soft()) as demo:
150
- gr.Markdown("# 🎓 المنصة التعليمية - الأستاذ عبود")
151
-
152
- chat_history_state = gr.State([])
153
-
154
- with gr.Row():
155
- with gr.Column(scale=2):
156
- subject_dropdown = gr.Dropdown(
157
- choices=["🧮 الرياضيات والعلوم", "🕌 اللغة العربية والتربية الإسلامية", "🔤 اللغة الإنجليزية"],
158
- value="🧮 الرياضيات والعلوم",
159
- label="📚 المادة"
160
- )
161
-
162
- # ⚠️ الإصلاح الجذري: type="messages" صراحةً (وهو الافتراضي في
163
- # Gradio 6 لكن التحديد الصريح أوضح وأكثر أماناً مستقبلاً)
164
- chatbot_display = gr.Chatbot(label="الحوار", height=400, type="messages")
165
-
166
- mic_input = gr.Audio(label="🎤 سؤالك", type="filepath", sources=["microphone"])
167
- send_btn = gr.Button("🚀 إرسال", variant="primary")
168
-
169
- with gr.Column(scale=1):
170
- audio_output = gr.Audio(label="🔊 الرد", type="filepath")
171
- status_output = gr.Textbox(label="الحالة")
172
-
173
- # ⚠️ الإصلاح الجذري الفعلي: لا حاجة لأي تحويل لصيغة tuples القديمة
174
- # ([user_msg, asst_msg]) — هذا بالضبط كان يكسر Gradio 6. التاريخ
175
- # القادم من teacher_chat هو بالفعل بصيغة messages الصحيحة تماماً
176
- # (قائمة قواميس role/content)، فنمرره مباشرة لكل من الـ State
177
- # وعنصر العرض chatbot_display بدون أي تعديل.
178
- def process_and_display(audio_mic, subject, history_state):
179
- new_history, audio, status = teacher_chat(audio_mic, subject, history_state)
180
- return new_history, new_history, audio, status, None
181
-
182
- send_btn.click(
183
- fn=process_and_display,
184
- inputs=[mic_input, subject_dropdown, chat_history_state],
185
- outputs=[chat_history_state, chatbot_display, audio_output, status_output, mic_input]
186
- )
187
-
188
- if __name__ == "__main__":
189
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
1
+ eduassistant75@gmail.com