Spaces:
Paused
Paused
| import os | |
| output_dir = "/mnt/agents/output" | |
| # 5. app.py - Updated for local model with loading indicator | |
| app_content = r'''import os | |
| import sys | |
| import gradio as gr | |
| import tempfile | |
| import uuid | |
| from pathlib import Path | |
| from config import ALLOWED_EXTENSIONS, MAX_FILE_SIZE, JAILBREAK_SYSTEM_PROMPT | |
| from agent import AgentManager, AutonomousAgent, ModelLoader | |
| from tools import describe_image, ocr_image, read_file | |
| # Initialize agent manager | |
| agent_manager = AgentManager() | |
| # CSS for premium dark theme | |
| CUSTOM_CSS = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap'); | |
| :root { | |
| --bg-primary: #0d1117; | |
| --bg-secondary: #161b22; | |
| --bg-tertiary: #21262d; | |
| --bg-hover: #30363d; | |
| --border-color: #30363d; | |
| --text-primary: #e6edf3; | |
| --text-secondary: #8b949e; | |
| --text-muted: #6e7681; | |
| --accent-primary: #58a6ff; | |
| --accent-secondary: #1f6feb; | |
| --accent-success: #3fb950; | |
| --accent-warning: #d29922; | |
| --accent-danger: #f85149; | |
| --accent-purple: #bc8cff; | |
| --accent-cyan: #39c5cf; | |
| --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
| --font-mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; | |
| } | |
| * { | |
| font-family: var(--font-sans) !important; | |
| } | |
| body, .gradio-container { | |
| background-color: var(--bg-primary) !important; | |
| color: var(--text-primary) !important; | |
| } | |
| .gradio-container { | |
| max-width: 1200px !important; | |
| padding: 0 !important; | |
| } | |
| .header-container { | |
| background: linear-gradient(135deg, var(--bg-secondary) 0%, var(--bg-primary) 100%); | |
| border-bottom: 1px solid var(--border-color); | |
| padding: 24px 32px; | |
| margin-bottom: 0; | |
| } | |
| .header-title { | |
| font-size: 28px; | |
| font-weight: 700; | |
| color: var(--text-primary); | |
| margin: 0; | |
| letter-spacing: -0.5px; | |
| } | |
| .header-subtitle { | |
| font-size: 14px; | |
| color: var(--text-secondary); | |
| margin-top: 4px; | |
| } | |
| .header-badge { | |
| display: inline-block; | |
| background: linear-gradient(135deg, var(--accent-secondary), var(--accent-purple)); | |
| color: white; | |
| padding: 2px 10px; | |
| border-radius: 12px; | |
| font-size: 11px; | |
| font-weight: 600; | |
| margin-left: 10px; | |
| vertical-align: middle; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| .chatbot-container { | |
| background: var(--bg-primary) !important; | |
| border: 1px solid var(--border-color) !important; | |
| border-radius: 12px !important; | |
| min-height: 500px; | |
| } | |
| .chatbot-container .message { | |
| border-radius: 12px !important; | |
| padding: 14px 18px !important; | |
| margin: 8px 0 !important; | |
| font-size: 14px !important; | |
| line-height: 1.6 !important; | |
| } | |
| .chatbot-container .message.user { | |
| background: linear-gradient(135deg, var(--accent-secondary), #2d7d9a) !important; | |
| color: white !important; | |
| margin-left: 40px !important; | |
| border-bottom-right-radius: 4px !important; | |
| } | |
| .chatbot-container .message.bot { | |
| background: var(--bg-secondary) !important; | |
| color: var(--text-primary) !important; | |
| margin-right: 40px !important; | |
| border: 1px solid var(--border-color) !important; | |
| border-bottom-left-radius: 4px !important; | |
| } | |
| .chatbot-container .message.bot pre { | |
| background: var(--bg-primary) !important; | |
| border: 1px solid var(--border-color) !important; | |
| border-radius: 8px !important; | |
| padding: 12px !important; | |
| overflow-x: auto !important; | |
| } | |
| .chatbot-container .message.bot code { | |
| font-family: var(--font-mono) !important; | |
| font-size: 13px !important; | |
| color: var(--accent-cyan) !important; | |
| } | |
| .chatbot-container .message.bot pre code { | |
| color: var(--text-primary) !important; | |
| } | |
| .input-container { | |
| background: var(--bg-secondary) !important; | |
| border: 1px solid var(--border-color) !important; | |
| border-radius: 12px !important; | |
| padding: 16px !important; | |
| } | |
| .input-container textarea { | |
| background: var(--bg-primary) !important; | |
| border: 1px solid var(--border-color) !important; | |
| border-radius: 8px !important; | |
| color: var(--text-primary) !important; | |
| font-size: 14px !important; | |
| padding: 12px !important; | |
| resize: none !important; | |
| } | |
| .input-container textarea:focus { | |
| border-color: var(--accent-primary) !important; | |
| box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.1) !important; | |
| outline: none !important; | |
| } | |
| .input-container textarea::placeholder { | |
| color: var(--text-muted) !important; | |
| } | |
| button.primary { | |
| background: linear-gradient(135deg, var(--accent-secondary), var(--accent-primary)) !important; | |
| color: white !important; | |
| border: none !important; | |
| border-radius: 8px !important; | |
| padding: 10px 20px !important; | |
| font-weight: 600 !important; | |
| font-size: 14px !important; | |
| transition: all 0.2s ease !important; | |
| } | |
| button.primary:hover { | |
| transform: translateY(-1px) !important; | |
| box-shadow: 0 4px 12px rgba(31, 111, 235, 0.3) !important; | |
| } | |
| button.secondary { | |
| background: var(--bg-tertiary) !important; | |
| color: var(--text-primary) !important; | |
| border: 1px solid var(--border-color) !important; | |
| border-radius: 8px !important; | |
| padding: 10px 20px !important; | |
| font-weight: 500 !important; | |
| font-size: 14px !important; | |
| transition: all 0.2s ease !important; | |
| } | |
| button.secondary:hover { | |
| background: var(--bg-hover) !important; | |
| border-color: var(--text-muted) !important; | |
| } | |
| button.danger { | |
| background: var(--accent-danger) !important; | |
| color: white !important; | |
| border: none !important; | |
| border-radius: 8px !important; | |
| padding: 10px 20px !important; | |
| font-weight: 600 !important; | |
| font-size: 14px !important; | |
| } | |
| .controls-panel { | |
| background: var(--bg-secondary) !important; | |
| border: 1px solid var(--border-color) !important; | |
| border-radius: 12px !important; | |
| padding: 20px !important; | |
| } | |
| .controls-panel h3 { | |
| font-size: 14px; | |
| font-weight: 600; | |
| color: var(--text-primary); | |
| margin-bottom: 16px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| .upload-container { | |
| background: var(--bg-primary) !important; | |
| border: 2px dashed var(--border-color) !important; | |
| border-radius: 12px !important; | |
| padding: 20px !important; | |
| text-align: center !important; | |
| transition: all 0.2s ease !important; | |
| } | |
| .upload-container:hover { | |
| border-color: var(--accent-primary) !important; | |
| background: rgba(88, 166, 255, 0.05) !important; | |
| } | |
| .status-indicator { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| font-size: 12px; | |
| font-weight: 500; | |
| padding: 4px 10px; | |
| border-radius: 20px; | |
| background: var(--bg-tertiary); | |
| } | |
| .status-indicator.online { | |
| color: var(--accent-success); | |
| } | |
| .status-indicator.online::before { | |
| content: ''; | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| background: var(--accent-success); | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| } | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| height: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: var(--bg-primary); | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: var(--bg-hover); | |
| border-radius: 4px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: var(--text-muted); | |
| } | |
| .tool-badge { | |
| display: inline-block; | |
| background: var(--bg-tertiary); | |
| border: 1px solid var(--border-color); | |
| color: var(--accent-primary); | |
| padding: 2px 8px; | |
| border-radius: 4px; | |
| font-size: 11px; | |
| font-family: var(--font-mono); | |
| font-weight: 500; | |
| } | |
| .loading-dots { | |
| display: inline-flex; | |
| gap: 4px; | |
| } | |
| .loading-dots span { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| background: var(--accent-primary); | |
| animation: bounce 1.4s infinite ease-in-out both; | |
| } | |
| .loading-dots span:nth-child(1) { animation-delay: -0.32s; } | |
| .loading-dots span:nth-child(2) { animation-delay: -0.16s; } | |
| @keyframes bounce { | |
| 0%, 80%, 100% { transform: scale(0); } | |
| 40% { transform: scale(1); } | |
| } | |
| @media (max-width: 768px) { | |
| .header-container { | |
| padding: 16px 20px; | |
| } | |
| .header-title { | |
| font-size: 22px; | |
| } | |
| .chatbot-container { | |
| min-height: 350px; | |
| } | |
| } | |
| .gr-box, .gr-form, .gr-panel { | |
| background: var(--bg-secondary) !important; | |
| border-color: var(--border-color) !important; | |
| } | |
| .gr-input, .gr-textarea { | |
| background: var(--bg-primary) !important; | |
| border-color: var(--border-color) !important; | |
| color: var(--text-primary) !important; | |
| } | |
| .gr-button { | |
| border-radius: 8px !important; | |
| } | |
| .gr-slider { | |
| accent-color: var(--accent-primary) !important; | |
| } | |
| .gr-checkbox { | |
| accent-color: var(--accent-primary) !important; | |
| } | |
| .gr-image { | |
| border-radius: 12px !important; | |
| border: 1px solid var(--border-color) !important; | |
| } | |
| .copy-btn { | |
| position: absolute; | |
| top: 8px; | |
| right: 8px; | |
| background: var(--bg-hover) !important; | |
| border: 1px solid var(--border-color) !important; | |
| color: var(--text-secondary) !important; | |
| padding: 4px 10px !important; | |
| border-radius: 6px !important; | |
| font-size: 11px !important; | |
| cursor: pointer !important; | |
| opacity: 0; | |
| transition: opacity 0.2s ease; | |
| } | |
| pre:hover .copy-btn { | |
| opacity: 1; | |
| } | |
| .copy-btn:hover { | |
| background: var(--bg-tertiary) !important; | |
| color: var(--text-primary) !important; | |
| } | |
| """ | |
| # Session storage | |
| user_sessions = {} | |
| def get_session_id(): | |
| """Generate a unique session ID.""" | |
| return str(uuid.uuid4()) | |
| def process_file_upload(file_obj): | |
| """Process uploaded file and return content/description.""" | |
| if file_obj is None: | |
| return None, "No file uploaded." | |
| file_path = file_obj.name if hasattr(file_obj, 'name') else str(file_obj) | |
| ext = Path(file_path).suffix.lower() | |
| if ext not in ALLOWED_EXTENSIONS: | |
| return None, f"Unsupported file type: {ext}. Allowed: {', '.join(ALLOWED_EXTENSIONS)}" | |
| # Check file size | |
| try: | |
| size = os.path.getsize(file_path) | |
| if size > MAX_FILE_SIZE: | |
| return None, f"File too large: {size / 1024 / 1024:.1f}MB (max {MAX_FILE_SIZE / 1024 / 1024}MB)" | |
| except: | |
| pass | |
| # Read file content | |
| content = read_file(file_path) | |
| # For images, also describe them | |
| if ext in ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']: | |
| description = describe_image(file_path) | |
| return content, f"**Image uploaded.**\n\n**AI Description:** {description}\n\n**OCR Text:** {ocr_image(file_path)}" | |
| return content, f"**File uploaded:** `{Path(file_path).name}`\n\n**Content preview:**\n```\n{content[:2000]}{'...' if len(content) > 2000 else ''}\n```" | |
| def chat_response(message, history, session_id, deepthink, force_search, image_file, doc_file): | |
| """Process chat message and return streaming response.""" | |
| if not session_id: | |
| session_id = get_session_id() | |
| agent = agent_manager.get_agent(session_id) | |
| # Process file uploads | |
| file_context = "" | |
| if image_file is not None: | |
| _, file_info = process_file_upload(image_file) | |
| file_context += f"\n\n{file_info}" | |
| if doc_file is not None: | |
| content, file_info = process_file_upload(doc_file) | |
| file_context += f"\n\n{file_info}" | |
| # Combine message with file context | |
| full_message = message + file_context if file_context else message | |
| # Stream the response | |
| bot_response = "" | |
| for text, is_final in agent.run_stream(full_message, deepthink=deepthink, force_search=force_search): | |
| bot_response += text | |
| if is_final: | |
| break | |
| # Yield intermediate updates for streaming effect | |
| yield bot_response, history + [[message, bot_response]], session_id | |
| # Final yield | |
| history.append([message, bot_response]) | |
| yield "", history, session_id | |
| def new_chat(): | |
| """Start a new chat session.""" | |
| session_id = get_session_id() | |
| return [], session_id, "" | |
| def create_interface(): | |
| """Create the Gradio interface.""" | |
| with gr.Blocks(css=CUSTOM_CSS, title="AI Agent - Local") as demo: | |
| # Session state | |
| session_id = gr.State(value=get_session_id()) | |
| # Header | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.HTML(""" | |
| <div class="header-container"> | |
| <h1 class="header-title"> | |
| AI Agent | |
| <span class="header-badge">Local 8B</span> | |
| </h1> | |
| <p class="header-subtitle">Llama 3.1 8B Instruct Q4_K_M | 60+ Tools | Fully Offline | No API Key</p> | |
| </div> | |
| """) | |
| # Main layout | |
| with gr.Row(): | |
| # Left sidebar - Controls | |
| with gr.Column(scale=1, min_width=250): | |
| with gr.Column(elem_classes=["controls-panel"]): | |
| gr.Markdown("### Controls") | |
| deepthink_toggle = gr.Checkbox( | |
| label="DeepThink", | |
| value=False, | |
| info="Enable step-by-step reasoning" | |
| ) | |
| force_search_toggle = gr.Checkbox( | |
| label="Force Search", | |
| value=False, | |
| info="Always search the web first" | |
| ) | |
| gr.Markdown("---") | |
| new_chat_btn = gr.Button( | |
| "New Chat", | |
| variant="secondary", | |
| size="sm" | |
| ) | |
| gr.Markdown("---") | |
| gr.Markdown("### Upload Files") | |
| image_upload = gr.Image( | |
| label="Image", | |
| type="filepath", | |
| height=150, | |
| elem_classes=["upload-container"] | |
| ) | |
| file_upload = gr.File( | |
| label="Document", | |
| file_types=[".pdf", ".docx", ".txt", ".py", ".json", ".csv", ".html", ".md", ".xml", ".yaml", ".yml", ".js", ".css", ".sql"], | |
| height=100, | |
| elem_classes=["upload-container"] | |
| ) | |
| gr.Markdown("---") | |
| gr.Markdown(""" | |
| <div style="font-size: 11px; color: var(--text-muted);"> | |
| <b>Available Tools:</b><br/> | |
| Web Search, Browse, Screenshot, PDF, OCR, Translate, Weather, News, Stocks, Calculator, Python, Shell, File Ops, Base64, Hash, Password, QR, Barcode, URL Shortener, DNS, IP Lookup, WHOIS, Ping, Port Scan, Encode/Decode, HTML/Markdown, JSON/CSV, Random, UUID, Timestamp, Word Count, Diff, Regex, Sentiment, Summarize, NER, YouTube, Links, Emails, Phones, Currency, Units, Code Format, Git, System Info, Directory, Compress | |
| </div> | |
| """) | |
| # Right side - Chat | |
| with gr.Column(scale=3): | |
| # Chatbot | |
| chatbot = gr.Chatbot( | |
| label="", | |
| height=550, | |
| elem_classes=["chatbot-container"], | |
| bubble_full_width=False, | |
| show_copy_button=True, | |
| latex_delimiters=[{"left": "$$", "right": "$$", "display": True}, {"left": "$", "right": "$", "display": False}], | |
| render_markdown=True | |
| ) | |
| # Input area | |
| with gr.Row(): | |
| msg_input = gr.Textbox( | |
| placeholder="Ask anything... The AI can search the web, run code, analyze files, and more.", | |
| show_label=False, | |
| lines=2, | |
| max_lines=5, | |
| container=False, | |
| elem_classes=["input-container"] | |
| ) | |
| with gr.Row(): | |
| submit_btn = gr.Button("Send", variant="primary", size="sm") | |
| clear_btn = gr.Button("Clear", variant="secondary", size="sm") | |
| # Event handlers | |
| submit_btn.click( | |
| fn=chat_response, | |
| inputs=[msg_input, chatbot, session_id, deepthink_toggle, force_search_toggle, image_upload, file_upload], | |
| outputs=[msg_input, chatbot, session_id] | |
| ) | |
| msg_input.submit( | |
| fn=chat_response, | |
| inputs=[msg_input, chatbot, session_id, deepthink_toggle, force_search_toggle, image_upload, file_upload], | |
| outputs=[msg_input, chatbot, session_id] | |
| ) | |
| new_chat_btn.click( | |
| fn=new_chat, | |
| outputs=[chatbot, session_id, msg_input] | |
| ) | |
| clear_btn.click( | |
| fn=lambda: ([], get_session_id(), ""), | |
| outputs=[chatbot, session_id, msg_input] | |
| ) | |
| return demo | |
| if __name__ == "__main__": | |
| demo = create_interface() | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=int(os.getenv("PORT", 7860)), | |
| share=False, | |
| show_api=False, | |
| favicon_path=None | |
| ) | |
| ''' | |
| with open(f"{output_dir}/app.py", "w") as f: | |
| f.write(app_content) | |
| print("app.py written") | |
| import py_compile | |
| try: | |
| py_compile.compile(f"{output_dir}/app.py", doraise=True) | |
| print("app.py compiles successfully!") | |
| except Exception as e: | |
| print(f"Compile error: {e}") |