Update app.py
Browse files
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 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
try:
|
| 25 |
tempdir = tempfile.gettempdir()
|
| 26 |
for item in os.listdir(tempdir):
|
|
@@ -43,7 +61,7 @@ try:
|
|
| 43 |
except:
|
| 44 |
pass
|
| 45 |
|
| 46 |
-
#
|
| 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 |
-
#
|
| 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 |
-
#
|
| 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 |
-
#
|
| 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 |
-
#
|
| 313 |
-
if 'pandas' in code and any(
|
| 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 |
-
|
| 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.
|
| 458 |
-
|
|
|
|
|
|
|
|
|
|
| 459 |
|
| 460 |
Important rules:
|
| 461 |
- For pandas Excel operations, always add: # pip install openpyxl
|
| 462 |
-
- Always
|
| 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 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 489 |
except Exception as e:
|
| 490 |
-
print(f"ERROR: {
|
| 491 |
-
# Fallback solution
|
| 492 |
try:
|
| 493 |
# Alternative approach
|
| 494 |
pass
|
| 495 |
-
except:
|
| 496 |
-
print("
|
| 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":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
#
|
| 528 |
-
generated_code = re.sub(r'^```python\s*', '', generated_code, flags=re.IGNORECASE
|
| 529 |
-
generated_code = re.sub(r'^```
|
|
|
|
| 530 |
|
| 531 |
-
|
|
|
|
|
|
|
|
|
|
| 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("
|
| 539 |
-
sys.exit(
|
|
|
|
| 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
|
| 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 |
-
|
| 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 |
-
#
|
| 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 |
-
|
| 631 |
-
|
| 632 |
-
|
| 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 |
-
#
|
| 676 |
if files:
|
| 677 |
-
|
| 678 |
-
|
|
|
|
| 679 |
|
| 680 |
-
#
|
| 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 |
-
|
| 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
|
| 699 |
print(f"MAIN ATTEMPT {attempt}/3 (Session: {session_id})")
|
| 700 |
-
print("=
|
| 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:
|
| 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 |
-
|
| 723 |
-
result_text
|
| 724 |
-
|
|
|
|
|
|
|
| 725 |
session_manager.cleanup_session(session_id)
|
| 726 |
-
|
|
|
|
| 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'][:
|
| 742 |
|
| 743 |
-
error_report += "\n\nLast generated code
|
|
|
|
| 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 |
-
|
|
|
|
| 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="
|
| 803 |
-
info="
|
| 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 |
+
)
|