AnesKAM commited on
Commit
e1ea770
·
verified ·
1 Parent(s): 89f0cbc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +99 -77
app.py CHANGED
@@ -110,13 +110,6 @@ def index():
110
  def favicon():
111
  return Response('', status=204)
112
 
113
- @app.route('/main.js')
114
- def main_js():
115
- js_path = os.path.join(TEMPLATE_DIR, 'main.js')
116
- if os.path.isfile(js_path):
117
- return send_file(js_path, mimetype='application/javascript')
118
- return Response('// main.js placeholder\n', status=200, mimetype='application/javascript')
119
-
120
  # ======================== أدوات مساعدة ========================
121
  def as_text(v):
122
  if v is None:
@@ -129,7 +122,7 @@ def as_text(v):
129
  return v.get('text') or v.get('body') or v.get('content') or ""
130
  return str(v)
131
 
132
- TOOLS_PROMPT = """You have access to tools. To use a tool, output ONE single valid JSON object at the exact point in your answer.
133
 
134
  Tool formats:
135
  - Web search: {"tool":"search","query":"search terms"}
@@ -145,7 +138,6 @@ Document content schema:
145
  Rules:
146
  - Only emit tools when user explicitly asks
147
  - Maximum ONE tool per response
148
- - After tool JSON, add a short sentence
149
  - Never fake tool results"""
150
 
151
  def build_system_prompt(user, interests, think):
@@ -155,11 +147,7 @@ def build_system_prompt(user, interests, think):
155
  ]
156
  user = user or {}
157
  if user.get('name'):
158
- lines.append(f"User's name is {user['name']}. Address them warmly but naturally.")
159
- if user.get('dob'):
160
- lines.append(f"User's birth date: {user['dob']}.")
161
- if user.get('gender'):
162
- lines.append(f"User's gender: {user['gender']}.")
163
  if interests:
164
  lines.append(f"User interests: {', '.join(interests[:15])}.")
165
 
@@ -260,14 +248,27 @@ def call_pollinations_text(messages, stream=False):
260
  response.raise_for_status()
261
  return response.json()
262
 
263
- # ======================== أدوات البحث ========================
264
- def web_search_duckduckgo(query, n=10):
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  try:
266
  r = requests.post(
267
  "https://html.duckduckgo.com/html/",
268
  data={"q": query},
269
- headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"},
270
- timeout=5,
271
  )
272
  results = []
273
  pattern = re.compile(
@@ -284,26 +285,26 @@ def web_search_duckduckgo(query, n=10):
284
  })
285
  if len(results) >= n:
286
  break
 
 
 
 
 
 
 
 
 
287
  return results
288
  except:
289
  return []
290
 
291
- def web_search_with_timeout(query, timeout=5):
292
- with concurrent.futures.ThreadPoolExecutor() as executor:
293
- future = executor.submit(web_search_duckduckgo, query)
294
- try:
295
- return future.result(timeout=timeout)
296
- except concurrent.futures.TimeoutError:
297
- logger.warning(f"[search] Timeout for: {query[:50]}")
298
- return []
299
-
300
- def image_search(query, n=8):
301
  try:
302
  r = requests.get(
303
  "https://www.bing.com/images/search",
304
  params={"q": query, "form": "HDRSC2"},
305
  headers={"User-Agent": "Mozilla/5.0"},
306
- timeout=5,
307
  )
308
  urls = re.findall(r'murl":"(.*?)"', r.text)
309
  return [{"image": u} for u in urls[:n]]
@@ -322,44 +323,37 @@ def extract_tool_from_text(text):
322
  pass
323
  return None
324
 
325
- def execute_tool(tool_data):
326
- """تنفيذ الأداة وإرجاع النتيجة"""
327
  tool_type = tool_data.get('tool')
328
 
329
  if tool_type == 'search':
330
  query = tool_data.get('query', '')
331
- logger.info(f"[tool] Searching: {query[:100]}")
332
- results = web_search_with_timeout(query, timeout=5)
333
  if results:
334
  formatted = "\n\n### نتائج البحث:\n\n"
335
- for i, r in enumerate(results[:5], 1):
336
- formatted += f"**{i}. {r.get('title', '')}**\n"
337
- formatted += f" {r.get('snippet', '')[:200]}\n"
338
- formatted += f" {r.get('url', '')}\n\n"
339
  return formatted
340
- return "\n\n⚠️ لم يتم العثور على نتائج.\n\n"
341
 
342
  elif tool_type == 'image_search':
343
  query = tool_data.get('query', '')
344
- logger.info(f"[tool] Image search: {query[:100]}")
345
  images = image_search(query)
346
  if images:
347
  formatted = "\n\n### الصور:\n\n"
348
  for i, img in enumerate(images[:4], 1):
349
- formatted += f"![صورة {i}]({img.get('image', '')})\n\n"
350
  return formatted
351
- return "\n\n⚠️ لم يتم العثور على صور.\n\n"
352
 
353
  elif tool_type == 'image':
354
- prompt = tool_data.get('prompt', '')
355
- logger.info(f"[tool] Image generation: {prompt[:100]}")
356
- return {"special": "image", "prompt": prompt}
357
 
358
  elif tool_type == 'document':
359
- logger.info(f"[tool] Document: {tool_data.get('format', 'pdf')}")
360
- return "\n\n📄 **جاري إنشاء المستند...**\n\n"
361
 
362
- return "\n\n⚠️ أداة غير معروفة.\n\n"
363
 
364
  # ======================== المستندات ========================
365
  def build_docx(cmd):
@@ -510,13 +504,49 @@ def chat():
510
  interests = data.get('interests') or []
511
  think = bool(data.get('think')) and model_type == 'pro'
512
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
513
  system_prompt = build_system_prompt(user, interests, think)
514
  full_messages = [{"role": "system", "content": system_prompt}] + normalize_messages(messages[-10:])
515
 
516
  def generate():
517
  attempt = 0
518
  full_response = ""
519
- tool_executed = False
520
 
521
  while True:
522
  attempt += 1
@@ -554,38 +584,31 @@ def chat():
554
  except json.JSONDecodeError:
555
  continue
556
 
557
- # استخراج وتنفيذ الأداة بعد اكتمال الرد
558
- if not tool_executed:
559
- tool = extract_tool_from_text(full_response)
560
- if tool:
561
- tool_executed = True
562
- logger.info(f"[tool] Found: {tool.get('tool')}")
 
563
 
564
- try:
565
- tool_result = execute_tool(tool)
566
-
567
- if isinstance(tool_result, dict) and tool_result.get('special') == 'image':
568
- # إرسال إشارة للواجهة لتوليد الصورة
569
- yield f"data: {json.dumps({'tool': 'image', 'prompt': tool_result['prompt']})}\n\n"
570
- elif tool_result:
571
- # إرسال نتيجة البحث كنص
572
- yield f"data: {json.dumps({'delta': tool_result})}\n\n"
573
- except Exception as tool_error:
574
- logger.error(f"[tool] Error: {tool_error}")
575
- error_msg = "\n\n⚠️ خطأ في تنفيذ الأداة: " + str(tool_error) + "\n\n"
576
- yield f"data: {json.dumps({'delta': error_msg})}\n\n"
577
 
578
  yield f"data: {json.dumps({'done': True})}\n\n"
579
  return
580
 
581
  except Exception as e:
582
  logger.warning(f"[chat] attempt #{attempt} failed: {e}")
583
- if MAX_RETRIES is not None and attempt >= MAX_RETRIES:
584
- yield f"data: {json.dumps({'error': str(e)})}\n\n"
585
- return
586
-
587
- yield f"data: {json.dumps({'retrying': True, 'attempt': attempt})}\n\n"
588
- time.sleep(RETRY_INTERVAL)
589
 
590
  return Response(stream_with_context(generate()), mimetype='text/event-stream')
591
 
@@ -685,7 +708,7 @@ def search_ep():
685
  q = _safe_json().get('query', '')
686
  if not q:
687
  return jsonify({'results': []})
688
- results = web_search_with_timeout(q, timeout=5)
689
  return jsonify({'results': results})
690
 
691
  @app.route('/api/image-search', methods=['POST'])
@@ -712,8 +735,7 @@ def health():
712
  'status': 'ok',
713
  'model': FLASH_MODEL,
714
  'provider': 'pollinations',
715
- 'search': 'local (free)',
716
- 'retryInterval': RETRY_INTERVAL
717
  })
718
 
719
  # ======================== Error handlers ========================
@@ -727,5 +749,5 @@ def internal_error(e):
727
 
728
  if __name__ == '__main__':
729
  port = int(os.environ.get('PORT', 7860))
730
- logger.info(f"Genisi running on port {port} | Flash: {FLASH_MODEL} | Search: local | Video: disabled")
731
  app.run(host='0.0.0.0', port=port, threaded=True)
 
110
  def favicon():
111
  return Response('', status=204)
112
 
 
 
 
 
 
 
 
113
  # ======================== أدوات مساعدة ========================
114
  def as_text(v):
115
  if v is None:
 
122
  return v.get('text') or v.get('body') or v.get('content') or ""
123
  return str(v)
124
 
125
+ TOOLS_PROMPT = """You have access to tools. To use a tool, output ONE single valid JSON object.
126
 
127
  Tool formats:
128
  - Web search: {"tool":"search","query":"search terms"}
 
138
  Rules:
139
  - Only emit tools when user explicitly asks
140
  - Maximum ONE tool per response
 
141
  - Never fake tool results"""
142
 
143
  def build_system_prompt(user, interests, think):
 
147
  ]
148
  user = user or {}
149
  if user.get('name'):
150
+ lines.append(f"User's name is {user['name']}.")
 
 
 
 
151
  if interests:
152
  lines.append(f"User interests: {', '.join(interests[:15])}.")
153
 
 
248
  response.raise_for_status()
249
  return response.json()
250
 
251
+ # ======================== أدوات البحث (سريعة) ========================
252
+ # كاش للبحث - يخزن النتائج لمدة 5 دقائق
253
+ search_cache = {}
254
+ CACHE_TTL = 300 # 5 دقائق
255
+
256
+ def web_search_duckduckgo(query, n=5):
257
+ """بحث سريع مع كاش"""
258
+ # التحقق من الكاش
259
+ cache_key = query.lower().strip()
260
+ if cache_key in search_cache:
261
+ cached_time, cached_results = search_cache[cache_key]
262
+ if time.time() - cached_time < CACHE_TTL:
263
+ logger.info(f"[search] Cache hit for: {query[:50]}")
264
+ return cached_results
265
+
266
  try:
267
  r = requests.post(
268
  "https://html.duckduckgo.com/html/",
269
  data={"q": query},
270
+ headers={"User-Agent": "Mozilla/5.0"},
271
+ timeout=3, # timeout قصير
272
  )
273
  results = []
274
  pattern = re.compile(
 
285
  })
286
  if len(results) >= n:
287
  break
288
+
289
+ # تخزين في الكاش
290
+ if results:
291
+ search_cache[cache_key] = (time.time(), results)
292
+ # تنظيف الكاش القديم
293
+ for k in list(search_cache.keys()):
294
+ if time.time() - search_cache[k][0] > CACHE_TTL:
295
+ del search_cache[k]
296
+
297
  return results
298
  except:
299
  return []
300
 
301
+ def image_search(query, n=6):
 
 
 
 
 
 
 
 
 
302
  try:
303
  r = requests.get(
304
  "https://www.bing.com/images/search",
305
  params={"q": query, "form": "HDRSC2"},
306
  headers={"User-Agent": "Mozilla/5.0"},
307
+ timeout=3,
308
  )
309
  urls = re.findall(r'murl&quot;:&quot;(.*?)&quot;', r.text)
310
  return [{"image": u} for u in urls[:n]]
 
323
  pass
324
  return None
325
 
326
+ def execute_tool_fast(tool_data):
327
+ """تنفيذ سريع للأداة"""
328
  tool_type = tool_data.get('tool')
329
 
330
  if tool_type == 'search':
331
  query = tool_data.get('query', '')
332
+ results = web_search_duckduckgo(query, n=5)
 
333
  if results:
334
  formatted = "\n\n### نتائج البحث:\n\n"
335
+ for i, r in enumerate(results, 1):
336
+ formatted += f"**{i}. {r['title']}**\n{r['snippet'][:150]}\n{r['url']}\n\n"
 
 
337
  return formatted
338
+ return "\n\nلم يتم العثور على نتائج.\n\n"
339
 
340
  elif tool_type == 'image_search':
341
  query = tool_data.get('query', '')
 
342
  images = image_search(query)
343
  if images:
344
  formatted = "\n\n### الصور:\n\n"
345
  for i, img in enumerate(images[:4], 1):
346
+ formatted += f"![صورة {i}]({img['image']})\n\n"
347
  return formatted
348
+ return "\n\nلم يتم العثور على صور.\n\n"
349
 
350
  elif tool_type == 'image':
351
+ return {"special": "image", "prompt": tool_data.get('prompt', '')}
 
 
352
 
353
  elif tool_type == 'document':
354
+ return "\n\nجاري إنشاء المستند...\n\n"
 
355
 
356
+ return None
357
 
358
  # ======================== المستندات ========================
359
  def build_docx(cmd):
 
504
  interests = data.get('interests') or []
505
  think = bool(data.get('think')) and model_type == 'pro'
506
 
507
+ # ✨ فحص إذا كانت الرسالة الأخيرة طلب بحث مباشر
508
+ last_msg = messages[-1].get('content', '') if messages else ''
509
+ direct_search = False
510
+ search_query = ""
511
+
512
+ # كشف طلب البحث المباشر
513
+ search_patterns = [
514
+ r'ابحث (?:عن|لي) (.+)',
515
+ r'بحث (?:عن) (.+)',
516
+ r'search (?:for)? (.+)',
517
+ r'find (.+)',
518
+ r'معلومات عن (.+)',
519
+ r'اخر اخبار (.+)',
520
+ r'من هو (.+)',
521
+ r'ما هي (.+)',
522
+ r'ما هو (.+)',
523
+ ]
524
+
525
+ for pattern in search_patterns:
526
+ match = re.search(pattern, last_msg, re.IGNORECASE)
527
+ if match:
528
+ direct_search = True
529
+ search_query = match.group(1).strip()
530
+ break
531
+
532
+ # إذا كان بحث مباشر، نجريه أولاً ونضيف النتائج للرسائل
533
+ if direct_search and search_query:
534
+ logger.info(f"[direct-search] Detected: {search_query}")
535
+ search_results = web_search_duckduckgo(search_query, n=5)
536
+ if search_results:
537
+ context = "\n\nمعلومات من الإنترنت:\n\n"
538
+ for r in search_results:
539
+ context += f"- {r['title']}: {r['snippet'][:200]}\n {r['url']}\n"
540
+
541
+ # إضافة السياق لآخر رسالة
542
+ messages[-1]['content'] = f"استخدم المعلومات التالية للإجابة:\n{context}\n\nسؤال المستخدم: {last_msg}"
543
+
544
  system_prompt = build_system_prompt(user, interests, think)
545
  full_messages = [{"role": "system", "content": system_prompt}] + normalize_messages(messages[-10:])
546
 
547
  def generate():
548
  attempt = 0
549
  full_response = ""
 
550
 
551
  while True:
552
  attempt += 1
 
584
  except json.JSONDecodeError:
585
  continue
586
 
587
+ # ✨ معالجة سريعة للأداة بعد الرد
588
+ tool = extract_tool_from_text(full_response)
589
+ if tool and not direct_search: # لا نعالج الأدوات إذا كان بحث مباشر
590
+ logger.info(f"[tool] Executing: {tool.get('tool')}")
591
+
592
+ try:
593
+ tool_result = execute_tool_fast(tool)
594
 
595
+ if isinstance(tool_result, dict) and tool_result.get('special') == 'image':
596
+ yield f"data: {json.dumps({'tool': 'image', 'prompt': tool_result['prompt']})}\n\n"
597
+ elif tool_result:
598
+ yield f"data: {json.dumps({'delta': tool_result})}\n\n"
599
+ except Exception as tool_error:
600
+ logger.error(f"[tool] Error: {tool_error}")
601
+ error_msg = "\n\nخطأ: " + str(tool_error) + "\n\n"
602
+ yield f"data: {json.dumps({'delta': error_msg})}\n\n"
 
 
 
 
 
603
 
604
  yield f"data: {json.dumps({'done': True})}\n\n"
605
  return
606
 
607
  except Exception as e:
608
  logger.warning(f"[chat] attempt #{attempt} failed: {e}")
609
+ yield f"data: {json.dumps({'error': str(e)})}\n\n"
610
+ yield f"data: {json.dumps({'done': True})}\n\n"
611
+ return
 
 
 
612
 
613
  return Response(stream_with_context(generate()), mimetype='text/event-stream')
614
 
 
708
  q = _safe_json().get('query', '')
709
  if not q:
710
  return jsonify({'results': []})
711
+ results = web_search_duckduckgo(q, n=5)
712
  return jsonify({'results': results})
713
 
714
  @app.route('/api/image-search', methods=['POST'])
 
735
  'status': 'ok',
736
  'model': FLASH_MODEL,
737
  'provider': 'pollinations',
738
+ 'search': 'local (fast)',
 
739
  })
740
 
741
  # ======================== Error handlers ========================
 
749
 
750
  if __name__ == '__main__':
751
  port = int(os.environ.get('PORT', 7860))
752
+ logger.info(f"Genisi running on port {port} | Flash: {FLASH_MODEL} | Search: direct+local")
753
  app.run(host='0.0.0.0', port=port, threaded=True)