import gradio as gr import requests import os import io from PIL import Image API_URL = "http://127.0.0.1:8000" def ui_wrapper(user_prompt, progress=gr.Progress()): """Wrapper that calls the API for generation with progress tracking.""" try: if not user_prompt or not user_prompt.strip(): raise gr.Error("Please enter a subject to generate an archive.") # Stage 1: Refining prompt progress(0.1, desc="🔍 Analyzing your request...") # Stage 2: Generating progress(0.3, desc="🎨 Generating historical archive... (this may take 1-2 minutes)") # Call API response = requests.post(f"{API_URL}/agent/generate", json={ "prompt": user_prompt, "use_curator": True }, timeout=120) # Long timeout for SDXL # Stage 3: Processing result # Archiving happens in background now, so we skip the wait if response.status_code == 200: # Load image from bytes progress(1.0, desc="✅ Generated! Indexing in background...") return Image.open(io.BytesIO(response.content)) elif response.status_code == 400: raise gr.Error("Invalid prompt. Try describing a historical subject or scene.") elif response.status_code == 500: raise gr.Error("The archive server is experiencing issues. Please try again in a moment.") else: raise gr.Error("Could not generate archive. The server may be busy or offline.") except requests.exceptions.Timeout: raise gr.Error("⏱️ Generation is taking longer than expected. Try a simpler subject or check back later.") except requests.exceptions.ConnectionError: raise gr.Error("❌ Could not connect to the archive server. Make sure `api.py` is running on port 8000.") except gr.Error: raise except Exception as e: raise gr.Error(f"Unexpected error: {str(e)}") def search_wrapper(query): """Calls API for semantic search with improved error handling.""" try: if not query or not query.strip(): raise gr.Error("Please enter search keywords to explore the archive.") response = requests.post(f"{API_URL}/curator/search", json={ "query": query, "limit": 9 }, timeout=30) if response.status_code == 200: data = response.json() # Convert results to Gradio Gallery format [(path, label)] gallery_items = [] for item in data.get("results", []): # Ensure path is accessible or mapped gallery_items.append((item['path'], item['tags'])) if not gallery_items: gr.Info("🔍 No artifacts found. Try different keywords or broader terms.") return gallery_items else: print(f"Search API Error: {response.text}") raise gr.Error("Search failed. The archive server may be offline.") except requests.exceptions.Timeout: raise gr.Error("Search is taking too long. Please try again.") except requests.exceptions.ConnectionError: raise gr.Error("Cannot connect to archive. Make sure `api.py` is running.") except gr.Error: raise except Exception as e: print(f"Search failed: {e}") raise gr.Error(f"Search error: {str(e)}") # Interface Definition with Custom CSS custom_css = """ button { transition: all 0.2s ease-in-out !important; } button:hover { transform: scale(1.02); opacity: 0.95; } button:active { transform: scale(0.98); } button:focus-visible, textarea:focus-visible, input:focus-visible { outline: 2px solid #8B5CF6 !important; outline-offset: 2px !important; } /* Responsive gallery spacing */ @media (max-width: 768px) { .gallery { gap: 0.5rem !important; } } """ with gr.Blocks(title="Cora — Historical Archive Generator") as app: # Enhanced Header gr.Markdown( """ # 🏛️ Cora — The Archive Curator **Generate AI-powered historical illustrations** using SDXL and Visual RAG. Transform simple prompts into detailed historical scenes, artifacts, and engravings. **How it works**: Your prompt is enhanced by an AI curator, then rendered as a historically-themed illustration and archived for future semantic search. """ ) # Tabs with gr.Tabs(): with gr.Tab("🎨 Generate"): with gr.Row(): with gr.Column(scale=1): # Controls txt_input = gr.Textbox( label="Historical Subject", placeholder="Describe a historical scene, artifact, or character...", lines=3, info="Be specific! Mention time periods, cultures, or contexts for best results." ) btn_generate = gr.Button("🔮 Generate Archive", variant="primary", size="lg") # Input Examples gr.Examples( examples=[ ["A Roman gladiator training in the Colosseum at sunset"], ["Medieval blacksmith forging a sword, sparks flying"], ["Viking longship sailing through a stormy Nordic sea"], ["Renaissance artist's studio with easels and oil paintings"], ["Ancient Egyptian scribe writing on papyrus scrolls"], ["Samurai warrior in ceremonial armor during cherry blossom season"] ], inputs=txt_input, label="💡 Example Prompts (click to use)" ) with gr.Column(scale=1): # Output output_image = gr.Image( label="🖼️ Recovered Artifact", show_label=True ) # Event Wiring with Progress btn_generate.click( fn=ui_wrapper, inputs=[txt_input], outputs=output_image, show_progress="full" ) with gr.Tab("📚 The Archive"): gr.Markdown( """ ### Search the Visual Memory Use semantic search to find archived artifacts by keywords, themes, or cultural markers. """ ) with gr.Row(): search_box = gr.Textbox( label="Search Query", placeholder="Try: 'roman armor', 'medieval', 'warrior', or 'architecture'", scale=4 ) btn_search = gr.Button("🔍 Search", variant="secondary", scale=1) # Responsive Gallery: columns adapt based on viewport # Gradio 4.0+ supports column lists [mobile, tablet, desktop] gallery = gr.Gallery( label="Found Artifacts", columns=[1, 2, 3], # 1 col on mobile, 2 on tablet, 3 on desktop height="auto", object_fit="contain", show_label=True ) # Event Wiring btn_search.click( fn=search_wrapper, inputs=[search_box], outputs=gallery ) # Search Examples gr.Examples( examples=[ ["roman gladiator"], ["medieval castle"], ["viking warrior"], ["renaissance art"] ], inputs=search_box, label="💡 Quick Search Terms" ) if __name__ == "__main__": app.launch( server_port=7861, theme=gr.themes.Monochrome(), css=custom_css )