yahya1912 commited on
Commit
8cb98fa
·
verified ·
1 Parent(s): 2a5b8f8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +124 -78
app.py CHANGED
@@ -6,10 +6,9 @@ import json, re, os, requests
6
  hf_token = os.getenv("HF_TOKEN")
7
  google_key = os.getenv("GOOGLE_MAPS_API_KEY")
8
 
9
- # إنشاء العميل - الطريقة الصحيحة
10
  client = InferenceClient("Qwen/Qwen2.5-7B-Instruct", token=hf_token)
11
 
12
- # ==================== SYSTEM PROMPT المُحسَّن ====================
13
  SYSTEM_PROMPT = """Analyze the Arabic map query and return ONLY a JSON object with these exact keys:
14
 
15
  1. "location": City, area, or "near me" if not specified.
@@ -26,40 +25,16 @@ SYSTEM_PROMPT = """Analyze the Arabic map query and return ONLY a JSON object wi
26
  - Government: ديوان بلدية, محكمة, مركز شرطة, سفارة, مركز حكومي
27
  - Outdoor: شاطئ, ممر مشاة, بحيرة, غابة, جبل, منتزه طبيعي
28
 
29
- 3. "sub_type": Specific type within category (e.g., "شاورما" for مطعم, "أطفال" for حديقة, "طوارئ" for مستشفى, "5 نجوم" for فندق) or null.
30
-
31
- 4. "features": A list of ALL descriptive tags mentioned (e.g., ["رخيص", "قريب", "نظيف", "ممتاز", "24 ساعة", "مجاني", "هادئ", "عائلي", "فاخر", "سريع", "جيد"]).
32
-
33
- 5. "sort_by": Standardized ranking signal. Use ONE of: rating, price, distance, relevance, open_now.
34
-
35
- Rules:
36
- - Extract ALL adjectives into "features", don't miss any.
37
- - If user mentions "أقرب/قريب/أقرب مني" → sort_by: "distance"
38
- - If user mentions "أحسن/أفضل/أعلى تقييم/ممتاز" → sort_by: "rating"
39
- - If user mentions "أرخص/رخيص/سعر منخفض" → sort_by: "price"
40
- - If user mentions "مفتوح الآن/مفتوح 24 ساعة" → sort_by: "open_now"
41
- - Default to "relevance" if unclear.
42
 
43
  Examples:
44
  Input: "أرخص وأقرب مستشفى طوارئ في جدة"
45
- Output: {"location": "جدة", "category": "مستشفى", "sub_type": "طوارئ", "features": ["أرخص", "أقرب"], "sort_by": "price"}
46
-
47
- Input: "حديقة أطفال نظيفة في الرياض"
48
- Output: {"location": "الرياض", "category": "حديقة", "sub_type": "أطفال", "features": ["نظيفة"], "sort_by": "relevance"}
49
-
50
- Input: "أفضل فندق 5 نجوم قريب من المطار في دبي"
51
- Output: {"location": "دبي", "category": "فندق", "sub_type": "5 نجوم", "features": ["أفضل", "قريب من المطار"], "sort_by": "rating"}
52
-
53
- Input: "محطة وقود 24 ساعة رخيصة"
54
- Output: {"location": "near me", "category": "محطة وقود", "sub_type": null, "features": ["24 ساعة", "رخيصة"], "sort_by": "price"}
55
-
56
- Input: "مقهى هادئ للعمل في الرياض"
57
- Output: {"location": "الرياض", "category": "مقهى", "sub_type": null, "features": ["هادئ", "للعمل"], "sort_by": "relevance"}"""
58
-
59
- # ==================== دوال البحث والمعالجة ====================
60
 
61
  def build_search_query(parsed_data):
62
- """بناء جملة بحث محسّنة من البيانات المستخرجة"""
63
  parts = []
64
 
65
  category = parsed_data.get("category", "")
@@ -76,12 +51,13 @@ def build_search_query(parsed_data):
76
 
77
  features = parsed_data.get("features", [])
78
  search_features = [f for f in features if any(keyword in f for keyword in
79
- ["24", "طوارئ", "أطفال", "مجاني", "مفتوح", "فاخر", "اقتصادي"])]
80
  parts.extend(search_features)
81
 
82
  return " ".join(parts)
83
 
84
- def search_google_maps(parsed_data):
 
85
  if not google_key:
86
  return "⚠️ الرجاء إضافة GOOGLE_MAPS_API_KEY في إعدادات الـ Space."
87
 
@@ -90,54 +66,85 @@ def search_google_maps(parsed_data):
90
  if not query.strip():
91
  query = f"{parsed_data.get('category', '')} {parsed_data.get('location', '')}"
92
 
93
- url = "https://maps.googleapis.com/maps/api/place/textsearch/json"
94
- params = {
95
- "query": query,
96
- "language": "ar",
97
- "key": google_key
 
 
98
  }
99
 
 
 
 
 
 
 
 
100
  sort_by = parsed_data.get("sort_by", "relevance")
 
 
 
101
 
102
  try:
103
- response = requests.get(url, params=params, timeout=10)
104
- data = response.json()
105
 
106
- if data.get("status") != "OK":
107
- error_msg = data.get("error_message", data.get("status", "Unknown error"))
 
 
 
108
  return f"⚠️ خطأ في API: {error_msg}"
109
 
110
- results = data.get("results", [])
111
 
112
- if not results:
113
  return "❌ لم يتم العثور على نتائج. جرب تعديل البحث."
114
 
 
115
  if sort_by == "rating":
116
- results = sorted(results, key=lambda x: x.get("rating", 0), reverse=True)
117
  elif sort_by == "price":
118
- results = sorted(results, key=lambda x: x.get("price_level", 4))
 
 
 
119
 
 
120
  output_list = []
121
- for i, place in enumerate(results[:6], 1):
122
- name = place.get("name", "غير معروف")
123
  rating = place.get("rating", "غير متوفر")
124
- total_ratings = place.get("user_ratings_total", 0)
125
- address = place.get("formatted_address", "العنوان غير متوفر")
126
 
127
- price_level = place.get("price_level")
128
- price_str = ""
129
- if price_level:
130
- price_str = "💰" * price_level
 
 
 
 
 
131
 
132
- open_now = place.get("opening_hours", {}).get("open_now")
 
 
133
  status = ""
134
  if open_now is True:
135
  status = " ✅ مفتوح الآن"
136
  elif open_now is False:
137
  status = " ❌ مغلق الآن"
138
 
 
139
  types = place.get("types", [])
140
- place_type = ", ".join([t.replace("_", " ") for t in types[:2]])
 
 
 
141
 
142
  card = f"""### {i}. 📍 {name}
143
  ⭐ **التقييم:** {rating}/5 ({total_ratings} تقييم) {price_str}
@@ -154,16 +161,65 @@ def search_google_maps(parsed_data):
154
  except Exception as e:
155
  return f"❌ خطأ في الاتصال: {str(e)}"
156
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  def parse_arabic_query(user_query):
158
- """تحليل الاستعلام باستخدام LLM - الطريقة الصحيحة"""
159
  try:
160
- # الطريقة الصحيحة لاستخدام InferenceClient مع النماذج المحادثة
161
  messages = [
162
  {"role": "system", "content": SYSTEM_PROMPT},
163
  {"role": "user", "content": user_query}
164
  ]
165
 
166
- # استخدام chat_completion (مع underscore) أو text_generation
167
  response = client.chat_completion(
168
  messages=messages,
169
  max_tokens=300,
@@ -171,7 +227,6 @@ def parse_arabic_query(user_query):
171
  stream=False
172
  )
173
 
174
- # استخراج المحتوى - قد يكون في أماكن مختلفة حسب الإصدار
175
  if hasattr(response, 'choices') and response.choices:
176
  raw_content = response.choices[0].message.content
177
  elif isinstance(response, dict) and 'choices' in response:
@@ -179,34 +234,27 @@ def parse_arabic_query(user_query):
179
  else:
180
  raw_content = str(response)
181
 
182
- # البحث عن JSON في الرد
183
  match = re.search(r'\{.*\}', raw_content, re.DOTALL)
184
  if not match:
185
- return {
186
- "error": "لم يتم العثور على JSON صالح",
187
- "raw_response": raw_content[:500]
188
- }
189
 
190
  parsed_data = json.loads(match.group(0))
191
  return parsed_data
192
 
193
- except json.JSONDecodeError as e:
194
- return {"error": f"خطأ في قراءة JSON: {str(e)}"}
195
  except Exception as e:
196
- return {"error": f"خطأ في تحليل الاستعلام: {str(e)}"}
197
 
198
  def main_process(user_query):
199
  if not user_query.strip():
200
  return {"error": "الرجاء إدخال استعلام"}, "لا توجد نتائج"
201
 
202
- # 1. تحليل القصد
203
  parsed_data = parse_arabic_query(user_query)
204
 
205
  if "error" in parsed_data:
206
  return parsed_data, f"⚠️ {parsed_data.get('error', 'خطأ غير معروف')}"
207
 
208
- # 2. البحث في Google Maps
209
- map_results = search_google_maps(parsed_data)
210
 
211
  return parsed_data, map_results
212
 
@@ -216,16 +264,18 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
216
  gr.Markdown("""
217
  # 🗺️ تطبيق البحث الذكي في الخرائط
218
  **يدعم جميع الفئات:** مطاعم، فنادق، مستشفيات، محطات وقود، حدائق، متاجر، وغيرها...
 
 
219
  """)
220
 
221
  with gr.Row():
222
  input_text = gr.Textbox(
223
  label="📝 ماذا تبحث عنه؟",
224
- placeholder="مثال: أرخص مستشفى طوارئ في جدة، أو حديقة أطفال نظيفة في الرياض، أو فندق 5 نجوم في دبي",
225
  lines=2
226
  )
227
 
228
- btn = gr.Button("🔍 ابحث الآن", variant="primary", scale=1)
229
 
230
  with gr.Row():
231
  with gr.Column(scale=1):
@@ -233,7 +283,6 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
233
  with gr.Column(scale=2):
234
  output_map = gr.Markdown(label="🗺️ نتائج Google Maps")
235
 
236
- # أمثلة سريعة
237
  gr.Markdown("### 💡 أمثلة للبحث:")
238
  examples = [
239
  "أرخص وأقرب مستشفى طوارئ في جدة",
@@ -241,21 +290,18 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
241
  "أفضل فندق 5 نجوم في دبي",
242
  "مقهى هادئ للعمل في الخبر",
243
  "محطة وقود 24 ساعة رخيصة",
244
- "صيدلية قريبة مني مفتوحة الآن"
245
  ]
246
 
247
- example_buttons = []
248
  with gr.Row():
249
  for ex in examples[:3]:
250
  btn_ex = gr.Button(ex, size="sm")
251
  btn_ex.click(lambda x=ex: x, None, input_text)
252
- example_buttons.append(btn_ex)
253
 
254
  with gr.Row():
255
  for ex in examples[3:]:
256
  btn_ex = gr.Button(ex, size="sm")
257
  btn_ex.click(lambda x=ex: x, None, input_text)
258
- example_buttons.append(btn_ex)
259
 
260
  btn.click(fn=main_process, inputs=input_text, outputs=[output_json, output_map])
261
 
 
6
  hf_token = os.getenv("HF_TOKEN")
7
  google_key = os.getenv("GOOGLE_MAPS_API_KEY")
8
 
9
+ # إنشاء العميل
10
  client = InferenceClient("Qwen/Qwen2.5-7B-Instruct", token=hf_token)
11
 
 
12
  SYSTEM_PROMPT = """Analyze the Arabic map query and return ONLY a JSON object with these exact keys:
13
 
14
  1. "location": City, area, or "near me" if not specified.
 
25
  - Government: ديوان بلدية, محكمة, مركز شرطة, سفارة, مركز حكومي
26
  - Outdoor: شاطئ, ممر مشاة, بحيرة, غابة, جبل, منتزه طبيعي
27
 
28
+ 3. "sub_type": Specific type within category or null.
29
+ 4. "features": A list of ALL descriptive tags mentioned.
30
+ 5. "sort_by": ONE of: rating, price, distance, relevance, open_now.
 
 
 
 
 
 
 
 
 
 
31
 
32
  Examples:
33
  Input: "أرخص وأقرب مستشفى طوارئ في جدة"
34
+ Output: {"location": "جدة", "category": "مستشفى", "sub_type": "طوارئ", "features": ["أرخص", "أقرب"], "sort_by": "price"}"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
  def build_search_query(parsed_data):
37
+ """بناء جملة بحث محسّنة"""
38
  parts = []
39
 
40
  category = parsed_data.get("category", "")
 
51
 
52
  features = parsed_data.get("features", [])
53
  search_features = [f for f in features if any(keyword in f for keyword in
54
+ ["24", "طوارئ", "أطفال", "مجاني", "مفتوح", "فاخر", "اقتصادي", "رخيص"])]
55
  parts.extend(search_features)
56
 
57
  return " ".join(parts)
58
 
59
+ def search_google_maps_new_api(parsed_data):
60
+ """استخدام Places API (New) v1"""
61
  if not google_key:
62
  return "⚠️ الرجاء إضافة GOOGLE_MAPS_API_KEY في إعدادات الـ Space."
63
 
 
66
  if not query.strip():
67
  query = f"{parsed_data.get('category', '')} {parsed_data.get('location', '')}"
68
 
69
+ # Places API (New) - Text Search v1
70
+ url = "https://places.googleapis.com/v1/places:searchText"
71
+
72
+ headers = {
73
+ "Content-Type": "application/json",
74
+ "X-Goog-Api-Key": google_key,
75
+ "X-Goog-FieldMask": "places.displayName,places.formattedAddress,places.rating,places.userRatingCount,places.priceLevel,places.currentOpeningHours,places.types,places.location"
76
  }
77
 
78
+ data = {
79
+ "textQuery": query,
80
+ "languageCode": "ar",
81
+ "maxResultCount": 10
82
+ }
83
+
84
+ # إضافة تفضيل الموقع إذا كان الترتيب حسب المسافة
85
  sort_by = parsed_data.get("sort_by", "relevance")
86
+ if sort_by == "distance" and parsed_data.get("location") != "near me":
87
+ # يمكن إضافة locationBias لاحقاً
88
+ pass
89
 
90
  try:
91
+ response = requests.post(url, headers=headers, json=data, timeout=15)
92
+ result = response.json()
93
 
94
+ if response.status_code != 200:
95
+ error_msg = result.get("error", {}).get("message", "Unknown error")
96
+ # إذا كان الخطأ بسبب عدم تفعيل API، نحاول الـ Legacy كاحتياط
97
+ if "not enabled" in error_msg.lower() or response.status_code == 403:
98
+ return search_google_maps_legacy(parsed_data, query)
99
  return f"⚠️ خطأ في API: {error_msg}"
100
 
101
+ places = result.get("places", [])
102
 
103
+ if not places:
104
  return "❌ لم يتم العثور على نتائج. جرب تعديل البحث."
105
 
106
+ # ترتيب النتائج
107
  if sort_by == "rating":
108
+ places = sorted(places, key=lambda x: x.get("rating", 0), reverse=True)
109
  elif sort_by == "price":
110
+ price_order = {"PRICE_LEVEL_UNSPECIFIED": 0, "PRICE_LEVEL_FREE": 1,
111
+ "PRICE_LEVEL_INEXPENSIVE": 2, "PRICE_LEVEL_MODERATE": 3,
112
+ "PRICE_LEVEL_EXPENSIVE": 4, "PRICE_LEVEL_VERY_EXPENSIVE": 5}
113
+ places = sorted(places, key=lambda x: price_order.get(x.get("priceLevel"), 99))
114
 
115
+ # تنسيق النتائج
116
  output_list = []
117
+ for i, place in enumerate(places[:6], 1):
118
+ name = place.get("displayName", {}).get("text", "غير معروف")
119
  rating = place.get("rating", "غير متوفر")
120
+ total_ratings = place.get("userRatingCount", 0)
121
+ address = place.get("formattedAddress", "العنوان غير متوفر")
122
 
123
+ # مستوى السعر
124
+ price_level = place.get("priceLevel")
125
+ price_map = {
126
+ "PRICE_LEVEL_INEXPENSIVE": "💰",
127
+ "PRICE_LEVEL_MODERATE": "💰💰",
128
+ "PRICE_LEVEL_EXPENSIVE": "💰💰💰",
129
+ "PRICE_LEVEL_VERY_EXPENSIVE": "💰💰💰💰"
130
+ }
131
+ price_str = price_map.get(price_level, "")
132
 
133
+ # حالة الافتتاح
134
+ hours = place.get("currentOpeningHours", {})
135
+ open_now = hours.get("openNow")
136
  status = ""
137
  if open_now is True:
138
  status = " ✅ مفتوح الآن"
139
  elif open_now is False:
140
  status = " ❌ مغلق الآن"
141
 
142
+ # الأنواع
143
  types = place.get("types", [])
144
+ place_type = ", ".join([t.replace("_", " ").replace("restaurant", "مطعم").replace("cafe", "مقهى")
145
+ .replace("hospital", "مستشفى").replace("park", "حديقة")
146
+ .replace("store", "متجر").replace("gas_station", "محطة وقود")[:15]
147
+ for t in types[:2]])
148
 
149
  card = f"""### {i}. 📍 {name}
150
  ⭐ **التقييم:** {rating}/5 ({total_ratings} تقييم) {price_str}
 
161
  except Exception as e:
162
  return f"❌ خطأ في الاتصال: {str(e)}"
163
 
164
+ def search_google_maps_legacy(parsed_data, query=None):
165
+ """الرجوع إلى Legacy API كاحتياط"""
166
+ if not query:
167
+ query = build_search_query(parsed_data)
168
+
169
+ url = "https://maps.googleapis.com/maps/api/place/textsearch/json"
170
+ params = {
171
+ "query": query,
172
+ "language": "ar",
173
+ "key": google_key
174
+ }
175
+
176
+ try:
177
+ response = requests.get(url, params=params, timeout=10)
178
+ data = response.json()
179
+
180
+ if data.get("status") != "OK":
181
+ return f"⚠️ خطأ في Legacy API أيضاً: {data.get('status')}. الرجاء تفعيل Places API (New) في Google Cloud Console."
182
+
183
+ results = data.get("results", [])
184
+
185
+ if not results:
186
+ return "❌ لم يتم العثور على نتائج."
187
+
188
+ sort_by = parsed_data.get("sort_by", "relevance")
189
+ if sort_by == "rating":
190
+ results = sorted(results, key=lambda x: x.get("rating", 0), reverse=True)
191
+
192
+ output_list = []
193
+ for i, place in enumerate(results[:6], 1):
194
+ name = place.get("name", "غير معروف")
195
+ rating = place.get("rating", "غير متوفر")
196
+ address = place.get("formatted_address", "العنوان غير متوفر")
197
+
198
+ price_level = place.get("price_level")
199
+ price_str = "💰" * price_level if price_level else ""
200
+
201
+ open_now = place.get("opening_hours", {}).get("open_now")
202
+ status = " ✅ مفتوح" if open_now else " ❌ مغلق" if open_now is False else ""
203
+
204
+ card = f"""### {i}. 📍 {name}
205
+ ⭐ **التقييم:** {rating}/5 {price_str}
206
+ 🏠 **العنوان:** {address}{status}
207
+ ---"""
208
+ output_list.append(card)
209
+
210
+ return "\n".join(output_list)
211
+
212
+ except Exception as e:
213
+ return f"❌ فشل في Legacy API أيضاً: {str(e)}"
214
+
215
  def parse_arabic_query(user_query):
216
+ """تحليل الاستعلام باستخدام LLM"""
217
  try:
 
218
  messages = [
219
  {"role": "system", "content": SYSTEM_PROMPT},
220
  {"role": "user", "content": user_query}
221
  ]
222
 
 
223
  response = client.chat_completion(
224
  messages=messages,
225
  max_tokens=300,
 
227
  stream=False
228
  )
229
 
 
230
  if hasattr(response, 'choices') and response.choices:
231
  raw_content = response.choices[0].message.content
232
  elif isinstance(response, dict) and 'choices' in response:
 
234
  else:
235
  raw_content = str(response)
236
 
 
237
  match = re.search(r'\{.*\}', raw_content, re.DOTALL)
238
  if not match:
239
+ return {"error": "لم يتم العثور على JSON صالح", "raw_response": raw_content[:500]}
 
 
 
240
 
241
  parsed_data = json.loads(match.group(0))
242
  return parsed_data
243
 
 
 
244
  except Exception as e:
245
+ return {"error": f"خطأ في التحليل: {str(e)}"}
246
 
247
  def main_process(user_query):
248
  if not user_query.strip():
249
  return {"error": "الرجاء إدخال استعلام"}, "لا توجد نتائج"
250
 
 
251
  parsed_data = parse_arabic_query(user_query)
252
 
253
  if "error" in parsed_data:
254
  return parsed_data, f"⚠️ {parsed_data.get('error', 'خطأ غير معروف')}"
255
 
256
+ # استخدام الـ New API مع fallback للـ Legacy
257
+ map_results = search_google_maps_new_api(parsed_data)
258
 
259
  return parsed_data, map_results
260
 
 
264
  gr.Markdown("""
265
  # 🗺️ تطبيق البحث الذكي في الخرائط
266
  **يدعم جميع الفئات:** مطاعم، فنادق، مستشفيات، محطات وقود، حدائق، متاجر، وغيرها...
267
+
268
+ ⚠️ **تأكد من تفعيل Places API (New) في Google Cloud Console**
269
  """)
270
 
271
  with gr.Row():
272
  input_text = gr.Textbox(
273
  label="📝 ماذا تبحث عنه؟",
274
+ placeholder="مثال: أرخص مستشفى طوارئ في جدة، أو حديقة أطفال نظيفة في الرياض",
275
  lines=2
276
  )
277
 
278
+ btn = gr.Button("🔍 ابحث ا��آن", variant="primary")
279
 
280
  with gr.Row():
281
  with gr.Column(scale=1):
 
283
  with gr.Column(scale=2):
284
  output_map = gr.Markdown(label="🗺️ نتائج Google Maps")
285
 
 
286
  gr.Markdown("### 💡 أمثلة للبحث:")
287
  examples = [
288
  "أرخص وأقرب مستشفى طوارئ في جدة",
 
290
  "أفضل فندق 5 نجوم في دبي",
291
  "مقهى هادئ للعمل في الخبر",
292
  "محطة وقود 24 ساعة رخيصة",
293
+ "صيدلية قريبة مفتوحة الآن"
294
  ]
295
 
 
296
  with gr.Row():
297
  for ex in examples[:3]:
298
  btn_ex = gr.Button(ex, size="sm")
299
  btn_ex.click(lambda x=ex: x, None, input_text)
 
300
 
301
  with gr.Row():
302
  for ex in examples[3:]:
303
  btn_ex = gr.Button(ex, size="sm")
304
  btn_ex.click(lambda x=ex: x, None, input_text)
 
305
 
306
  btn.click(fn=main_process, inputs=input_text, outputs=[output_json, output_map])
307