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("""
Llama 3.1 8B Instruct Q4_K_M | 60+ Tools | Fully Offline | No API Key