suprimedev commited on
Commit
548cf99
·
verified ·
1 Parent(s): badc8d0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +125 -108
app.py CHANGED
@@ -20,7 +20,25 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
20
  import time
21
  from urllib.parse import quote
22
 
23
- # Clean up any existing temp files on startup to save space
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  try:
25
  tempdir = tempfile.gettempdir()
26
  for item in os.listdir(tempdir):
@@ -43,7 +61,7 @@ try:
43
  except:
44
  pass
45
 
46
- # Use OpenRouter API (OpenAI-compatible)
47
  OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
48
  client = openai.OpenAI(
49
  api_key=OPENROUTER_API_KEY,
@@ -55,6 +73,7 @@ MODEL_NAME = "x-ai/grok-4-fast"
55
  # Global thread pool for concurrent execution
56
  executor = ThreadPoolExecutor(max_workers=5) # حداکثر ۵ درخواست همزمان
57
 
 
58
  class SessionManager:
59
  """مدیریت سشن‌های مستقل برای هر کاربر"""
60
 
@@ -105,6 +124,7 @@ class SessionManager:
105
  # مدیر سشن گلوبال
106
  session_manager = SessionManager()
107
 
 
108
  class ErrorAnalyzer:
109
  """Analyze errors and suggest fixes"""
110
 
@@ -188,39 +208,38 @@ class ErrorAnalyzer:
188
  "original_error": error_message
189
  }
190
 
 
191
  def indent_code(code, spaces=4):
192
  """Indent the code by the specified number of spaces."""
193
  indented_lines = []
194
  for line in code.split('\n'):
195
- if line.strip():
196
  indented_lines.append(' ' * spaces + line)
197
  else:
198
  indented_lines.append(line)
199
  return '\n'.join(indented_lines)
200
 
 
201
  def detect_required_packages(code):
202
  """Detect required packages from Python code (optimized for accuracy)."""
203
  required_packages = set()
204
 
205
- # Pre-installed packages from requirements.txt
206
  pre_installed = {
207
  'gradio', 'openai', 'pillow', 'rembg', 'numpy', 'opencv-python', 'scikit-learn',
208
  'tensorflow', 'torch', 'lxml', 'requests', 'matplotlib', 'seaborn', 'onnxruntime',
209
  'proglog', 'openpyxl', 'moviepy'
210
  }
211
 
212
- # Import patterns
213
  import_patterns = [
214
  r'^(?:import|from)\s+(\w+)(?:\.\w+)*',
215
  ]
216
 
217
- # Patterns for pip install in code/comments
218
  pip_patterns = [
219
  r'#?\s*pip\s+install\s+([^\s#]+)',
220
  r'#?\s*install\s+([^\s#]+)'
221
  ]
222
 
223
- # Usage patterns that require additional packages
224
  usage_patterns = {
225
  r'\.to_excel': 'openpyxl',
226
  r'\.read_excel': 'openpyxl',
@@ -238,7 +257,7 @@ def detect_required_packages(code):
238
  r'openpyxl': 'openpyxl',
239
  }
240
 
241
- # Check for pip install comments
242
  for pattern in pip_patterns:
243
  matches = re.findall(pattern, code, re.IGNORECASE | re.MULTILINE)
244
  for match in matches:
@@ -247,7 +266,7 @@ def detect_required_packages(code):
247
  if pkg and pkg not in pre_installed:
248
  required_packages.add(pkg)
249
 
250
- # Check imports with mapping
251
  for line in code.split('\n'):
252
  line = line.strip()
253
  if line.startswith(('import ', 'from ')):
@@ -304,21 +323,20 @@ def detect_required_packages(code):
304
  if module not in pre_installed:
305
  required_packages.add(module)
306
 
307
- # Check for usage patterns that need additional packages
308
  for pattern, package in usage_patterns.items():
309
  if re.search(pattern, code, re.IGNORECASE) and package not in pre_installed:
310
  required_packages.add(package)
311
 
312
- # Special case: if pandas is imported and Excel operations detected
313
- if 'pandas' in code and any(pattern in code for pattern in ['.to_excel', '.read_excel', 'ExcelWriter']):
314
  if 'openpyxl' not in pre_installed:
315
  required_packages.add('openpyxl')
316
 
317
- # Remove pre-installed packages
318
  required_packages = required_packages - pre_installed
319
-
320
  return list(required_packages)
321
 
 
322
  def install_package(package_name):
323
  """Install a package using pip if it's not already installed."""
324
  try:
@@ -381,22 +399,7 @@ def install_packages_if_needed(packages):
381
 
382
  return len(failed_packages) == 0
383
 
384
- def build_proxy_url(original_url: str) -> str:
385
- """
386
- تبدیل لینک اصلی به لینک پروکسی:
387
- https://api.talkbot.ir/download?f=s7&g=<urlencoded(original_url)>
388
- اگر از قبل به این فرمت باشد، همان را برمی‌گرداند.
389
- """
390
- original_url = original_url.strip()
391
- if not original_url:
392
- return original_url
393
-
394
- if original_url.startswith("https://api.talkbot.ir/download?f=s7&g="):
395
- return original_url
396
-
397
- encoded = quote(original_url, safe='')
398
- return f"https://api.talkbot.ir/download?f=s7&g={encoded}"
399
-
400
  def download_file_from_url(url: str, temp_dir: str) -> Optional[str]:
401
  """Download a file from URL to temp_dir and return local path."""
402
  try:
@@ -418,6 +421,7 @@ def download_file_from_url(url: str, temp_dir: str) -> Optional[str]:
418
  print(f"Failed to download {url}: {e}")
419
  return None
420
 
 
421
  def generate_code_with_openrouter(instruction, file_paths, previous_errors=None, attempt=1):
422
  """Generate Python code using OpenRouter API with error awareness."""
423
 
@@ -441,6 +445,7 @@ def generate_code_with_openrouter(instruction, file_paths, previous_errors=None,
441
  alternative_approaches += "\n- Implement fallback solutions"
442
  alternative_approaches += "\n- Generate mock data if files are problematic"
443
 
 
444
  prompt_template = textwrap.dedent("""
445
  You are a Python expert. Instruction: "{instruction}"
446
  Input files: {file_paths_str} (use file_paths[0] for first file, iterate for multiple; if empty, generate based on instruction alone).
@@ -454,27 +459,26 @@ Write a complete Python script that:
454
  4. Add comprehensive error handling with try-except blocks
455
  5. Create output directory if needed using os.makedirs(exist_ok=True)
456
  6. Save output to a temp directory using tempfile.mkdtemp()
457
- 7. Print "OUTPUT_FILE_PATH: /full/path/to/output" at the end using os.path.abspath()
458
- 8. If file operations fail, try alternative approaches
 
 
 
459
 
460
  Important rules:
461
  - For pandas Excel operations, always add: # pip install openpyxl
462
- - Always use absolute paths with os.path.abspath()
463
- - Create directories before saving files
464
  - Handle common errors (FileNotFoundError, PermissionError, etc.)
465
  - If a library fails, try alternatives (e.g., csv instead of pandas)
466
- - No __name__ == '__main__', no functions, just direct code
467
  - Add detailed error messages
 
 
 
468
 
469
- Example with error handling:
470
  import os
471
  import tempfile
472
- try:
473
- import pandas as pd
474
- # pip install pandas openpyxl
475
- except ImportError:
476
- print("Pandas not available, using csv module")
477
- import csv
478
 
479
  file_paths = {file_paths_list}
480
 
@@ -482,18 +486,21 @@ try:
482
  temp_dir = tempfile.mkdtemp()
483
  os.makedirs(temp_dir, exist_ok=True)
484
  output_path = os.path.join(temp_dir, 'output.xlsx')
485
-
486
  # Your main logic here with error handling
487
-
488
- print(f"OUTPUT_FILE_PATH: {{os.path.abspath(output_path)}}")
 
 
 
489
  except Exception as e:
490
- print(f"ERROR: {{e}}")
491
- # Fallback solution
492
  try:
493
  # Alternative approach
494
  pass
495
- except:
496
- print("All approaches failed")
497
 
498
  Output ONLY Python code, no markdown.
499
  """)
@@ -515,7 +522,12 @@ Output ONLY Python code, no markdown.
515
  messages=[
516
  {
517
  "role": "system",
518
- "content": "Output only clean executable Python code with comprehensive error handling."
 
 
 
 
 
519
  },
520
  {"role": "user", "content": prompt}
521
  ],
@@ -524,20 +536,25 @@ Output ONLY Python code, no markdown.
524
 
525
  generated_code = response.choices[0].message.content.strip()
526
 
527
- # Clean code blocks if model wrapped with ```python ...```
528
- generated_code = re.sub(r'^```python\s*', '', generated_code, flags=re.IGNORECASE).strip()
529
- generated_code = re.sub(r'^```|```$', '', generated_code).strip()
 
530
 
531
- return generated_code if generated_code else "import sys\nprint('OUTPUT_TEXT: No code generated')\nsys.exit(0)"
 
 
 
532
 
533
  except Exception as api_error:
534
  error_msg = f"API Error: {api_error}"
535
  print(error_msg)
536
- # Return simple fallback
537
  return """import sys
538
- print("OUTPUT_TEXT: Code generation failed due to API error")
539
- sys.exit(0)"""
 
540
 
 
541
  def execute_code_with_retry(code: str, session_id: str, max_attempts: int = 3) -> Tuple[bool, str, Optional[str]]:
542
  """Execute code with retry logic and error recovery"""
543
  tf_path = None
@@ -554,11 +571,13 @@ def execute_code_with_retry(code: str, session_id: str, max_attempts: int = 3) -
554
  print("Detected packages:", required_packages)
555
  install_packages_if_needed(required_packages)
556
 
557
- # Step 2: Wrap code
558
  indented = indent_code(code)
559
  wrapped_code = (
560
  "try:\n"
561
  f"{indented}\n"
 
 
562
  "except Exception as e:\n"
563
  " print(f'ERROR: {e}')\n"
564
  " import traceback; traceback.print_exc()\n"
@@ -573,16 +592,16 @@ def execute_code_with_retry(code: str, session_id: str, max_attempts: int = 3) -
573
  except SyntaxError as se:
574
  error_msg = f"Syntax Error: {se}"
575
  if attempt < max_attempts:
576
- print(f"Syntax error on attempt {attempt}, will regenerate code")
577
  return False, error_msg, None
578
  else:
579
  return False, error_msg, None
580
 
581
- # Step 4: Create temp file
582
  print("Creating temp file...")
583
  session_temp_dir = session_manager.get_session_temp_dir(session_id)
584
  if not session_temp_dir:
585
- session_temp_dir = tempfile.mkdtemp()
586
 
587
  with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False, dir=session_temp_dir) as tf:
588
  tf.write(wrapped_code)
@@ -608,37 +627,31 @@ def execute_code_with_retry(code: str, session_id: str, max_attempts: int = 3) -
608
  pass
609
 
610
  if rc != 0:
611
- error_msg = f"Execution failed (RC {rc}):\nStderr: {stderr}"
612
  print(error_msg)
613
 
614
  if attempt < max_attempts:
615
- # Analyze error for potential package fixes (already partly handled above)
616
- error_analysis = ErrorAnalyzer.analyze_error(stderr, code)
617
- if error_analysis['packages']:
618
- print(f"Attempting to install missing packages: {error_analysis['packages']}")
619
- for pkg in error_analysis['packages']:
620
- install_package(pkg)
621
- return False, stderr, None
622
  else:
623
  return False, error_msg, None
624
 
625
- # Extract output
626
  output_path_match = re.search(r'OUTPUT_FILE_PATH:\s*(.+)', stdout, re.I)
627
  output_text_match = re.search(r'OUTPUT_TEXT:\s*(.+)', stdout, re.I | re.DOTALL)
 
628
 
629
  if output_path_match:
630
- output_path = output_path_match.group(1).strip()
631
- if os.path.exists(output_path):
632
- return True, stdout, output_path
633
- else:
634
- error_msg = f"Output path not found: {output_path}"
635
- if attempt < max_attempts:
636
- print(error_msg)
637
- return False, error_msg, None
638
- else:
639
- return False, error_msg, None
640
  elif output_text_match:
641
  return True, output_text_match.group(1).strip(), None
 
 
 
 
 
 
642
  else:
643
  if stdout.strip():
644
  return True, stdout, None
@@ -662,6 +675,7 @@ def execute_code_with_retry(code: str, session_id: str, max_attempts: int = 3) -
662
 
663
  return False, "Max attempts reached", None
664
 
 
665
  def process_request_async(instruction, files, urls_input):
666
  """تابع اصلی پردازش درخواست به صورت همزمان"""
667
  session_id = session_manager.create_session()
@@ -672,19 +686,19 @@ def process_request_async(instruction, files, urls_input):
672
 
673
  file_paths = []
674
 
675
- # Handle uploaded files
676
  if files:
677
- # Gradio File objects -> use .name (temporary file path)
678
- file_paths = [f.name for f in files if f is not None]
 
679
 
680
- # Handle URLs via proxy domain to hide real source domain
681
  if urls_input and urls_input.strip():
682
  session_temp_dir = session_manager.get_session_temp_dir(session_id)
683
  urls = [url.strip() for url in urls_input.split(',') if url.strip()]
684
  downloaded_paths = []
685
  for url in urls:
686
- proxy_url = build_proxy_url(url)
687
- local_path = download_file_from_url(proxy_url, session_temp_dir)
688
  if local_path:
689
  downloaded_paths.append(local_path)
690
  file_paths.extend(downloaded_paths)
@@ -693,13 +707,12 @@ def process_request_async(instruction, files, urls_input):
693
  previous_errors = []
694
  generated_codes = []
695
 
696
- # Main retry loop
697
- for attempt in range(1, 4):
698
- print("\n" + "=" * 50)
699
  print(f"MAIN ATTEMPT {attempt}/3 (Session: {session_id})")
700
- print("=" * 50)
701
 
702
- # Generate code
703
  print("Generating code...")
704
  generated_code = generate_code_with_openrouter(
705
  instruction,
@@ -713,17 +726,19 @@ def process_request_async(instruction, files, urls_input):
713
  return f"کد ضعیف تولید شد: {generated_code}", None
714
 
715
  generated_codes.append(generated_code)
716
- print(f"Generated code preview: {generated_code[:200]}...")
717
 
718
- # Try to execute
719
  success, output, file_path = execute_code_with_retry(generated_code, session_id, max_attempts=2)
720
 
721
  if success:
722
- result_text = "✅ Success on attempt {}/3!\n\n".format(attempt)
723
- result_text += "Generated Code:\n```python\n" + generated_code + "\n```\n\n"
724
- result_text += "Output:\n" + (output if output is not None else "")
 
 
725
  session_manager.cleanup_session(session_id)
726
- return result_text, file_path
 
727
  else:
728
  print(f"\n❌ Attempt {attempt} failed")
729
  error_analysis = ErrorAnalyzer.analyze_error(output or "", generated_code)
@@ -738,9 +753,10 @@ def process_request_async(instruction, files, urls_input):
738
  for i, err in enumerate(previous_errors, 1):
739
  error_report += f"\nAttempt {i}:\n"
740
  error_report += f"- Error type: {err['error_type']}\n"
741
- error_report += f"- Details: {err['original_error'][:200]}...\n"
742
 
743
- error_report += "\n\nLast generated code:\n```python\n" + generated_code + "\n```"
 
744
  session_manager.cleanup_session(session_id)
745
  return error_report, None
746
 
@@ -753,6 +769,7 @@ def process_request_async(instruction, files, urls_input):
753
  session_manager.cleanup_session(session_id)
754
  return error_msg, None
755
 
 
756
  def process_request_wrapper(instruction, files, urls_input):
757
  """Wrapper function for Gradio to handle async execution"""
758
  future = executor.submit(process_request_async, instruction, files, urls_input)
@@ -763,7 +780,7 @@ def process_request_wrapper(instruction, files, urls_input):
763
  except Exception as e:
764
  return f"Error processing request: {str(e)}", None
765
 
766
- # Gradio Interface
767
  with gr.Blocks(title="AI File Processor - Self Correcting - Concurrent") as demo:
768
  gr.Markdown("""
769
  # 🤖 AI File Processor - Self Correcting Edition
@@ -783,8 +800,9 @@ with gr.Blocks(title="AI File Processor - Self Correcting - Concurrent") as demo
783
  - "این فایل CSV را به JSON تبدیل کن"
784
 
785
  **نکته:** می‌توانید فایل‌ها را آپلود کنید یا لینک‌های فایل را (جدا شده با کاما) وارد کنید.
786
- لینک‌های دانلود به صورت خودکار به فرمت زیر تبدیل می‌شوند تا دامنه اصلی مخفی بماند:
787
- https://api.talkbot.ir/download?f=s7&g=<urlencoded(original_url)>
 
788
  """)
789
 
790
  with gr.Row():
@@ -799,15 +817,15 @@ with gr.Blocks(title="AI File Processor - Self Correcting - Concurrent") as demo
799
  files = gr.File(file_count="multiple", label="فایل‌های آپلود شده (اختیاری)")
800
  urls = gr.Textbox(
801
  label="لینک فایل‌ها (جدا با کاما، اختیاری)",
802
- placeholder="مثال: https://example.com/file1.csv, https://example.com/file2.jpg",
803
- info="پروکسی: لینک‌ها به صورت خودکار به https://api.talkbot.ir/download?f=s7&g=... تبدیل می‌شوند."
804
  )
805
 
806
  btn = gr.Button("🚀 اجرا", variant="primary")
807
 
808
  with gr.Row():
809
  output = gr.Textbox(label="نتیجه", lines=15)
810
- file_out = gr.File(label="فایل خروجی (در صورت تولید)")
811
 
812
  gr.Examples(
813
  examples=[
@@ -821,11 +839,10 @@ with gr.Blocks(title="AI File Processor - Self Correcting - Concurrent") as demo
821
 
822
  btn.click(fn=process_request_wrapper, inputs=[instruction, files, urls], outputs=[output, file_out])
823
 
 
824
  if __name__ == "__main__":
825
- # Cleanup old sessions on startup
826
  session_manager.cleanup_old_sessions()
827
 
828
- # Launch with concurrency settings
829
  try:
830
  demo.queue(max_size=20)
831
  except TypeError:
@@ -839,4 +856,4 @@ if __name__ == "__main__":
839
  server_name="0.0.0.0",
840
  server_port=7860,
841
  show_error=True
842
- )
 
20
  import time
21
  from urllib.parse import quote
22
 
23
+ # ================== تنظیمات لینک عمومی دانلود ==================
24
+ # دامنه عمومی نمایش داده شده به کاربر
25
+ PUBLIC_DOWNLOAD_BASE = "https://api.talkbot.ir/download?f=s7&g="
26
+
27
+ def build_public_download_url(output_path: str) -> str:
28
+ """
29
+ تبدیل مسیر واقعی فایل روی سرور به لینک عمومی دانلود.
30
+ در این نسخه، g برابر است با مسیر ابسولوت URL-Encode شده.
31
+ اگر منطق اختصاصی (توکن، هش و ...) دارید، اینجا اعمال کنید.
32
+ """
33
+ try:
34
+ abs_path = os.path.abspath(output_path)
35
+ encoded = quote(abs_path, safe="")
36
+ return f"{PUBLIC_DOWNLOAD_BASE}{encoded}"
37
+ except Exception:
38
+ # اگر هر مشکلی در تولید لینک بود، برای جلوگیری از لو رفتن ساختار داخلی، چیزی خنثی برمی‌گردانیم.
39
+ return f"{PUBLIC_DOWNLOAD_BASE}"
40
+
41
+ # ================== پاکسازی اولیه تمپ‌ها ==================
42
  try:
43
  tempdir = tempfile.gettempdir()
44
  for item in os.listdir(tempdir):
 
61
  except:
62
  pass
63
 
64
+ # ================== OpenRouter تنظیمات ==================
65
  OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
66
  client = openai.OpenAI(
67
  api_key=OPENROUTER_API_KEY,
 
73
  # Global thread pool for concurrent execution
74
  executor = ThreadPoolExecutor(max_workers=5) # حداکثر ۵ درخواست همزمان
75
 
76
+ # ================== Session Manager ==================
77
  class SessionManager:
78
  """مدیریت سشن‌های مستقل برای هر کاربر"""
79
 
 
124
  # مدیر سشن گلوبال
125
  session_manager = SessionManager()
126
 
127
+ # ================== Error Analyzer ==================
128
  class ErrorAnalyzer:
129
  """Analyze errors and suggest fixes"""
130
 
 
208
  "original_error": error_message
209
  }
210
 
211
+ # ================== Utility: Indent Code ==================
212
  def indent_code(code, spaces=4):
213
  """Indent the code by the specified number of spaces."""
214
  indented_lines = []
215
  for line in code.split('\n'):
216
+ if line.strip(): # Only indent non-empty lines
217
  indented_lines.append(' ' * spaces + line)
218
  else:
219
  indented_lines.append(line)
220
  return '\n'.join(indented_lines)
221
 
222
+ # ================== Package Detection ==================
223
  def detect_required_packages(code):
224
  """Detect required packages from Python code (optimized for accuracy)."""
225
  required_packages = set()
226
 
227
+ # Pre-installed packages from requirements.txt (فرضی)
228
  pre_installed = {
229
  'gradio', 'openai', 'pillow', 'rembg', 'numpy', 'opencv-python', 'scikit-learn',
230
  'tensorflow', 'torch', 'lxml', 'requests', 'matplotlib', 'seaborn', 'onnxruntime',
231
  'proglog', 'openpyxl', 'moviepy'
232
  }
233
 
 
234
  import_patterns = [
235
  r'^(?:import|from)\s+(\w+)(?:\.\w+)*',
236
  ]
237
 
 
238
  pip_patterns = [
239
  r'#?\s*pip\s+install\s+([^\s#]+)',
240
  r'#?\s*install\s+([^\s#]+)'
241
  ]
242
 
 
243
  usage_patterns = {
244
  r'\.to_excel': 'openpyxl',
245
  r'\.read_excel': 'openpyxl',
 
257
  r'openpyxl': 'openpyxl',
258
  }
259
 
260
+ # pip install hints
261
  for pattern in pip_patterns:
262
  matches = re.findall(pattern, code, re.IGNORECASE | re.MULTILINE)
263
  for match in matches:
 
266
  if pkg and pkg not in pre_installed:
267
  required_packages.add(pkg)
268
 
269
+ # imports
270
  for line in code.split('\n'):
271
  line = line.strip()
272
  if line.startswith(('import ', 'from ')):
 
323
  if module not in pre_installed:
324
  required_packages.add(module)
325
 
326
+ # usage patterns
327
  for pattern, package in usage_patterns.items():
328
  if re.search(pattern, code, re.IGNORECASE) and package not in pre_installed:
329
  required_packages.add(package)
330
 
331
+ # pandas + excel
332
+ if 'pandas' in code and any(p in code for p in ['.to_excel', '.read_excel', 'ExcelWriter']):
333
  if 'openpyxl' not in pre_installed:
334
  required_packages.add('openpyxl')
335
 
 
336
  required_packages = required_packages - pre_installed
 
337
  return list(required_packages)
338
 
339
+ # ================== Package Installer ==================
340
  def install_package(package_name):
341
  """Install a package using pip if it's not already installed."""
342
  try:
 
399
 
400
  return len(failed_packages) == 0
401
 
402
+ # ================== Download from URL ==================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
  def download_file_from_url(url: str, temp_dir: str) -> Optional[str]:
404
  """Download a file from URL to temp_dir and return local path."""
405
  try:
 
421
  print(f"Failed to download {url}: {e}")
422
  return None
423
 
424
+ # ================== Code Generation with OpenRouter ==================
425
  def generate_code_with_openrouter(instruction, file_paths, previous_errors=None, attempt=1):
426
  """Generate Python code using OpenRouter API with error awareness."""
427
 
 
445
  alternative_approaches += "\n- Implement fallback solutions"
446
  alternative_approaches += "\n- Generate mock data if files are problematic"
447
 
448
+ # توجه: در این Template، مستقیماً لینک عمومی را با دامنه api.talkbot.ir تولید می‌کنیم
449
  prompt_template = textwrap.dedent("""
450
  You are a Python expert. Instruction: "{instruction}"
451
  Input files: {file_paths_str} (use file_paths[0] for first file, iterate for multiple; if empty, generate based on instruction alone).
 
459
  4. Add comprehensive error handling with try-except blocks
460
  5. Create output directory if needed using os.makedirs(exist_ok=True)
461
  6. Save output to a temp directory using tempfile.mkdtemp()
462
+ 7. At the end, print ONLY ONE line with:
463
+ OUTPUT_FILE_PATH: https://api.talkbot.ir/download?f=s7&g=<encoded_or_safe_identifier>
464
+ where <encoded_or_safe_identifier> is based on the output file name or path using urllib.parse.quote.
465
+ 8. Do NOT print local absolute paths of the server.
466
+ 9. If file operations fail, try alternative approaches.
467
 
468
  Important rules:
469
  - For pandas Excel operations, always add: # pip install openpyxl
470
+ - Always create directories before saving files
 
471
  - Handle common errors (FileNotFoundError, PermissionError, etc.)
472
  - If a library fails, try alternatives (e.g., csv instead of pandas)
473
+ - No __name__ == '__main__', no custom functions wrapping main logic, just direct code
474
  - Add detailed error messages
475
+ - Do not print debugging info; only meaningful messages and the final OUTPUT_FILE_PATH or ERROR.
476
+
477
+ Example (pattern):
478
 
 
479
  import os
480
  import tempfile
481
+ from urllib.parse import quote
 
 
 
 
 
482
 
483
  file_paths = {file_paths_list}
484
 
 
486
  temp_dir = tempfile.mkdtemp()
487
  os.makedirs(temp_dir, exist_ok=True)
488
  output_path = os.path.join(temp_dir, 'output.xlsx')
489
+
490
  # Your main logic here with error handling
491
+
492
+ # IMPORTANT: Generate safe public URL instead of local path
493
+ safe_id = quote(os.path.abspath(output_path), safe="")
494
+ public_url = f"https://api.talkbot.ir/download?f=s7&g={safe_id}"
495
+ print(f"OUTPUT_FILE_PATH: {public_url}")
496
  except Exception as e:
497
+ print(f"ERROR: {e}")
498
+ # Fallback solution (if any)
499
  try:
500
  # Alternative approach
501
  pass
502
+ except Exception as e2:
503
+ print(f"ERROR: Fallback failed: {e2}")
504
 
505
  Output ONLY Python code, no markdown.
506
  """)
 
522
  messages=[
523
  {
524
  "role": "system",
525
+ "content": (
526
+ "Output only clean executable Python code with comprehensive error handling. "
527
+ "Never include Markdown code fences. "
528
+ "Always end by printing exactly one line that starts with 'OUTPUT_FILE_PATH:' "
529
+ "or an 'ERROR:' message."
530
+ )
531
  },
532
  {"role": "user", "content": prompt}
533
  ],
 
536
 
537
  generated_code = response.choices[0].message.content.strip()
538
 
539
+ # حذف احتمالی بلاک‌های ```python
540
+ generated_code = re.sub(r'^```python\s*', '', generated_code, flags=re.IGNORECASE | re.MULTILINE)
541
+ generated_code = re.sub(r'^```\s*', '', generated_code, flags=re.MULTILINE)
542
+ generated_code = re.sub(r'```\s*$', '', generated_code, flags=re.MULTILINE).strip()
543
 
544
+ if not generated_code:
545
+ return "import sys\nprint('OUTPUT_TEXT: No code generated')\nsys.exit(0)"
546
+
547
+ return generated_code
548
 
549
  except Exception as api_error:
550
  error_msg = f"API Error: {api_error}"
551
  print(error_msg)
 
552
  return """import sys
553
+ print("ERROR: Code generation failed due to API error")
554
+ sys.exit(1)
555
+ """
556
 
557
+ # ================== Execute Code with Retry ==================
558
  def execute_code_with_retry(code: str, session_id: str, max_attempts: int = 3) -> Tuple[bool, str, Optional[str]]:
559
  """Execute code with retry logic and error recovery"""
560
  tf_path = None
 
571
  print("Detected packages:", required_packages)
572
  install_packages_if_needed(required_packages)
573
 
574
+ # Step 2: Wrap code with top-level try/except
575
  indented = indent_code(code)
576
  wrapped_code = (
577
  "try:\n"
578
  f"{indented}\n"
579
+ "except SystemExit:\n"
580
+ " raise\n"
581
  "except Exception as e:\n"
582
  " print(f'ERROR: {e}')\n"
583
  " import traceback; traceback.print_exc()\n"
 
592
  except SyntaxError as se:
593
  error_msg = f"Syntax Error: {se}"
594
  if attempt < max_attempts:
595
+ print(f"Syntax error on attempt {attempt}, will retry with new code")
596
  return False, error_msg, None
597
  else:
598
  return False, error_msg, None
599
 
600
+ # Step 4: Create temp file in session dir
601
  print("Creating temp file...")
602
  session_temp_dir = session_manager.get_session_temp_dir(session_id)
603
  if not session_temp_dir:
604
+ session_temp_dir = tempfile.mkdtemp(prefix='session_fallback_')
605
 
606
  with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False, dir=session_temp_dir) as tf:
607
  tf.write(wrapped_code)
 
627
  pass
628
 
629
  if rc != 0:
630
+ error_msg = f"Execution failed (RC {rc}):\nStdout: {stdout}\nStderr: {stderr}"
631
  print(error_msg)
632
 
633
  if attempt < max_attempts:
634
+ return False, stdout + "\n" + stderr, None
 
 
 
 
 
 
635
  else:
636
  return False, error_msg, None
637
 
638
+ # Step 6: Parse OUTPUT
639
  output_path_match = re.search(r'OUTPUT_FILE_PATH:\s*(.+)', stdout, re.I)
640
  output_text_match = re.search(r'OUTPUT_TEXT:\s*(.+)', stdout, re.I | re.DOTALL)
641
+ error_match = re.search(r'ERROR:\s*(.+)', stdout, re.I | re.DOTALL)
642
 
643
  if output_path_match:
644
+ public_url = output_path_match.group(1).strip()
645
+ # اینجا public_url مستقیماً به کاربر داده می‌شود (دامنه داخلی در آن نیست)
646
+ return True, public_url, None
 
 
 
 
 
 
 
647
  elif output_text_match:
648
  return True, output_text_match.group(1).strip(), None
649
+ elif error_match:
650
+ # اگر فقط ERROR بود
651
+ if attempt < max_attempts:
652
+ return False, error_match.group(1).strip(), None
653
+ else:
654
+ return False, error_match.group(1).strip(), None
655
  else:
656
  if stdout.strip():
657
  return True, stdout, None
 
675
 
676
  return False, "Max attempts reached", None
677
 
678
+ # ================== Main Async Processing ==================
679
  def process_request_async(instruction, files, urls_input):
680
  """تابع اصلی پردازش درخواست به صورت همزمان"""
681
  session_id = session_manager.create_session()
 
686
 
687
  file_paths = []
688
 
689
+ # Uploaded files
690
  if files:
691
+ for f in files:
692
+ if hasattr(f, 'name'):
693
+ file_paths.append(f.name)
694
 
695
+ # URLs -> download into session temp dir
696
  if urls_input and urls_input.strip():
697
  session_temp_dir = session_manager.get_session_temp_dir(session_id)
698
  urls = [url.strip() for url in urls_input.split(',') if url.strip()]
699
  downloaded_paths = []
700
  for url in urls:
701
+ local_path = download_file_from_url(url, session_temp_dir)
 
702
  if local_path:
703
  downloaded_paths.append(local_path)
704
  file_paths.extend(downloaded_paths)
 
707
  previous_errors = []
708
  generated_codes = []
709
 
710
+ # Main retry loop for generation+execution
711
+ for attempt in range(1, 4): # 3 attempts
712
+ print(f"\n{'='*50}")
713
  print(f"MAIN ATTEMPT {attempt}/3 (Session: {session_id})")
714
+ print(f"{'='*50}")
715
 
 
716
  print("Generating code...")
717
  generated_code = generate_code_with_openrouter(
718
  instruction,
 
726
  return f"کد ضعیف تولید شد: {generated_code}", None
727
 
728
  generated_codes.append(generated_code)
729
+ print(f"Generated code preview:\n{generated_code[:400]}...\n")
730
 
 
731
  success, output, file_path = execute_code_with_retry(generated_code, session_id, max_attempts=2)
732
 
733
  if success:
734
+ # در اینجا output معمولا همان لینک عمومی است
735
+ result_text = (
736
+ f" Success on attempt {attempt}!\n\n"
737
+ f"Output:\n{output}"
738
+ )
739
  session_manager.cleanup_session(session_id)
740
+ # برای gr.File خروجی مستقیم نداریم، چون دانلود خارجی است
741
+ return result_text, None
742
  else:
743
  print(f"\n❌ Attempt {attempt} failed")
744
  error_analysis = ErrorAnalyzer.analyze_error(output or "", generated_code)
 
753
  for i, err in enumerate(previous_errors, 1):
754
  error_report += f"\nAttempt {i}:\n"
755
  error_report += f"- Error type: {err['error_type']}\n"
756
+ error_report += f"- Details: {err['original_error'][:300]}...\n"
757
 
758
+ error_report += "\n\nLast generated code (for debugging):\n"
759
+ error_report += generated_code
760
  session_manager.cleanup_session(session_id)
761
  return error_report, None
762
 
 
769
  session_manager.cleanup_session(session_id)
770
  return error_msg, None
771
 
772
+ # ================== Gradio Wrapper ==================
773
  def process_request_wrapper(instruction, files, urls_input):
774
  """Wrapper function for Gradio to handle async execution"""
775
  future = executor.submit(process_request_async, instruction, files, urls_input)
 
780
  except Exception as e:
781
  return f"Error processing request: {str(e)}", None
782
 
783
+ # ================== Gradio Interface ==================
784
  with gr.Blocks(title="AI File Processor - Self Correcting - Concurrent") as demo:
785
  gr.Markdown("""
786
  # 🤖 AI File Processor - Self Correcting Edition
 
800
  - "این فایل CSV را به JSON تبدیل کن"
801
 
802
  **نکته:** می‌توانید فایل‌ها را آپلود کنید یا لینک‌های فایل را (جدا شده با کاما) وارد کنید.
803
+
804
+ خروجی لینک دانلود به صورت عمومی و امن نمایش داده می‌شود، مانند:
805
+ https://api.talkbot.ir/download?f=s7&g=...
806
  """)
807
 
808
  with gr.Row():
 
817
  files = gr.File(file_count="multiple", label="فایل‌های آپلود شده (اختیاری)")
818
  urls = gr.Textbox(
819
  label="لینک فایل‌ها (جدا با کاما، اختیاری)",
820
+ placeholder="https://example.com/file1.csv, https://example.com/file2.jpg",
821
+ info="لینک‌ها دانلود خواهند شد."
822
  )
823
 
824
  btn = gr.Button("🚀 اجرا", variant="primary")
825
 
826
  with gr.Row():
827
  output = gr.Textbox(label="نتیجه", lines=15)
828
+ file_out = gr.File(label="فایل خروجی (در صورت نیاز داخلی)", visible=False)
829
 
830
  gr.Examples(
831
  examples=[
 
839
 
840
  btn.click(fn=process_request_wrapper, inputs=[instruction, files, urls], outputs=[output, file_out])
841
 
842
+ # ================== Launch ==================
843
  if __name__ == "__main__":
 
844
  session_manager.cleanup_old_sessions()
845
 
 
846
  try:
847
  demo.queue(max_size=20)
848
  except TypeError:
 
856
  server_name="0.0.0.0",
857
  server_port=7860,
858
  show_error=True
859
+ )