Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import anthropic | |
| import os | |
| import json | |
| import asyncio | |
| from typing import Dict, List | |
| from datetime import datetime | |
| from PIL import Image | |
| import io | |
| import base64 | |
| from dotenv import load_dotenv | |
| from mcpserver import MCPOrchestrator | |
| from llamaindex_rag import LlamaIndexEnvironmentalRAG | |
| load_dotenv() | |
| # Initialize Anthropic client and MCP Orchestrator | |
| client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY")) | |
| mcp = MCPOrchestrator(api_key=os.environ.get("ANTHROPIC_API_KEY")) | |
| llamaindex_rag = LlamaIndexEnvironmentalRAG() | |
| # ===== AUTONOMOUS AGENT SYSTEM ===== | |
| class EcoAgent: | |
| """Autonomous agent with planning, reasoning, and execution phases""" | |
| def __init__(self): | |
| self.execution_log = [] | |
| self.plan = [] | |
| def plan_assessment(self, product_name: str) -> List[str]: | |
| """PHASE 1: PLANNING - Agent creates execution plan""" | |
| # Optimized: Return static plan to save time | |
| self.plan = [ | |
| "Identify product category", | |
| "Analyze materials and composition", | |
| "Calculate lifecycle carbon footprint", | |
| "Assess environmental issues", | |
| "Find sustainable alternatives", | |
| "Generate recommendations" | |
| ] | |
| self.execution_log.append(f"Plan created: {len(self.plan)} steps") | |
| return self.plan | |
| def reason_about_product(self, product_name: str) -> str: | |
| """PHASE 2: REASONING - Agent reasons about product context""" | |
| reasoning_prompt = f"""As an environmental expert, reason about this product: {product_name} | |
| Analyze: | |
| 1. What category does this product belong to? | |
| 2. What are the likely materials and manufacturing processes? | |
| 3. What environmental concerns are most critical? | |
| 4. What lifecycle stage has the most impact? | |
| Provide a concise reasoning summary (3-4 sentences).""" | |
| try: | |
| message = client.messages.create( | |
| model="claude-sonnet-4-20250514", | |
| max_tokens=600, | |
| messages=[{"role": "user", "content": reasoning_prompt}] | |
| ) | |
| reasoning = message.content[0].text.strip() | |
| self.execution_log.append(f"Reasoning completed") | |
| return reasoning | |
| except Exception as e: | |
| return f"Product requires environmental assessment with focus on materials and lifecycle." | |
| # ===== RAG COMPONENT: RETRIEVAL AUGMENTED GENERATION ===== | |
| def retrieve_environmental_knowledge(product_name: str, reasoning: str) -> str: | |
| """RAG: Retrieve relevant environmental knowledge using LlamaIndex semantic search""" | |
| print(f"🔍 LlamaIndex searching for: {product_name}") | |
| # Use LlamaIndex for advanced semantic search | |
| knowledge = llamaindex_rag.retrieve_knowledge(product_name, top_k=2) | |
| return f"**LlamaIndex Retrieved Knowledge:**\n\n{knowledge}" | |
| # ===== IMPROVED MCP INTEGRATION: VISION ANALYSIS ===== | |
| def analyze_image_with_mcp(image) -> tuple: | |
| """Use MCP Vision Server for deep image analysis with improved fallback""" | |
| if image is None: | |
| return "", None | |
| try: | |
| img_copy = image.copy() | |
| max_size = (1568, 1568) | |
| img_copy.thumbnail(max_size, Image.Resampling.LANCZOS) | |
| buffered = io.BytesIO() | |
| img_copy.save(buffered, format="JPEG", quality=85) | |
| image_base64 = base64.b64encode(buffered.getvalue()).decode() | |
| # Try MCP first | |
| try: | |
| mcp_result = mcp.call_mcp_tool( | |
| "vision", | |
| "analyze_product_image", | |
| image_base64=image_base64, | |
| query="Identify product and materials for environmental assessment" | |
| ) | |
| if mcp_result.get("status") == "success": | |
| analysis = mcp_result.get("analysis", {}) | |
| product_type = analysis.get("product_type", "") | |
| # Check if we got a valid product name (not generic placeholder) | |
| if product_type and product_type.lower() not in ["unknown", "unknown product", "product", "item"]: | |
| return product_type, mcp_result | |
| except: | |
| pass | |
| # Always use direct Claude Vision as primary method for reliability | |
| message = client.messages.create( | |
| model="claude-sonnet-4-20250514", | |
| max_tokens=500, | |
| messages=[{ | |
| "role": "user", | |
| "content": [ | |
| { | |
| "type": "image", | |
| "source": { | |
| "type": "base64", | |
| "media_type": "image/jpeg", | |
| "data": image_base64, | |
| }, | |
| }, | |
| { | |
| "type": "text", | |
| "text": """What is this product? Identify it clearly and specifically. | |
| Examples of good responses: | |
| - "wireless headphones" | |
| - "plastic water bottle" | |
| - "laptop computer" | |
| - "smartphone" | |
| - "coffee mug" | |
| - "running shoes" | |
| - "cotton t-shirt" | |
| Return ONLY the product name in 2-5 words. Be specific about the actual product, not generic terms.""" | |
| } | |
| ] | |
| }] | |
| ) | |
| product_name = message.content[0].text.strip().strip('"').strip("'").lower() | |
| # Create simple vision data for direct Claude response | |
| vision_data = { | |
| "status": "success", | |
| "analysis": { | |
| "product_type": product_name, | |
| "detection_method": "direct_vision" | |
| } | |
| } | |
| return product_name, vision_data | |
| except Exception as e: | |
| return f"Error: {str(e)}", None | |
| # ===== MCP INTEGRATION: LIFECYCLE ASSESSMENT ===== | |
| def mcp_lifecycle_assessment(product_name: str, product_data: Dict) -> Dict: | |
| """Use MCP Reasoning Server for lifecycle analysis""" | |
| try: | |
| # MCP TOOL CALL: Lifecycle Impact Calculation | |
| lifecycle_result = mcp.call_mcp_tool( | |
| "reasoning", | |
| "calculate_lifecycle_impact", | |
| product_data=product_data | |
| ) | |
| return lifecycle_result | |
| except Exception as e: | |
| return {"status": "error", "error": str(e)} | |
| # ===== PHASE 3: EXECUTION WITH CONTEXT ENGINEERING ===== | |
| def execute_assessment(agent: EcoAgent, product_name: str, vision_data: Dict = None) -> Dict: | |
| """PHASE 3: EXECUTION - Agent executes assessment with RAG and MCP""" | |
| # Step 1: Retrieve knowledge (RAG) | |
| reasoning = agent.reason_about_product(product_name) | |
| rag_context = retrieve_environmental_knowledge(product_name, reasoning) | |
| agent.execution_log.append(f"Retrieved environmental knowledge (RAG)") | |
| # Step 2: Prepare product data | |
| product_data = {"name": product_name, "query": product_name} | |
| if vision_data and vision_data.get("status") == "success": | |
| analysis = vision_data.get("analysis", {}) | |
| # Only add materials if they're meaningful (not generic placeholders) | |
| materials = analysis.get("materials", []) | |
| if materials and not any("material" in str(m).lower() for m in materials): | |
| product_data["materials"] = materials | |
| packaging = analysis.get("packaging_materials", []) | |
| if packaging: | |
| product_data["packaging"] = packaging | |
| category = analysis.get("product_category", "") | |
| if category: | |
| product_data["category"] = category | |
| agent.execution_log.append(f"Vision analysis integrated") | |
| # Step 3: MCP Lifecycle Assessment | |
| lifecycle_result = mcp_lifecycle_assessment(product_name, product_data) | |
| if lifecycle_result.get("status") == "success": | |
| agent.execution_log.append(f"Lifecycle assessment completed via MCP") | |
| else: | |
| agent.execution_log.append(f"MCP lifecycle assessment skipped") | |
| # Step 4: CONTEXT ENGINEERING - Comprehensive prompt with all context | |
| engineered_prompt = f"""You are an environmental assessment expert. Assess this product: {product_name} | |
| **AGENT REASONING:** | |
| {reasoning} | |
| **RETRIEVED KNOWLEDGE (RAG):** | |
| {rag_context} | |
| **MCP LIFECYCLE ANALYSIS:** | |
| {json.dumps(lifecycle_result, indent=2) if lifecycle_result.get("status") == "success" else "Pending detailed analysis"} | |
| **VISION ANALYSIS:** | |
| {json.dumps(vision_data.get("analysis", {}), indent=2) if vision_data else "No image data"} | |
| Using ALL the above context, provide a comprehensive assessment in this EXACT JSON format: | |
| {{ | |
| "name": "{product_name}", | |
| "eco_score": 5.5, | |
| "carbon": "XX kg CO2e or XX g CO2e", | |
| "issues": "Main environmental concerns separated by commas", | |
| "alternative": "Best eco-friendly alternative with score (X.X/10) - Key benefit", | |
| "lifecycle_insights": "Key insights from lifecycle analysis", | |
| "materials_analysis": "Summary of materials and their impact" | |
| }} | |
| IMPORTANT: | |
| - name MUST be: {product_name} | |
| - eco_score: realistic number 1-10 for THIS SPECIFIC product | |
| - alternative: Recommend ONLY NEW sustainable products (NOT second-hand, vintage, used, refurbished, or pre-owned items). Focus on eco-friendly materials, sustainable manufacturing, certifications, or innovative green technologies | |
| - Be specific and accurate based on the context provided | |
| - Return ONLY valid JSON, no extra text. | |
| Examples of good alternatives: | |
| - For shoes: "Vegan leather shoes made from recycled materials with score (8.5/10) - Zero animal products and 70% lower carbon footprint" | |
| - For electronics: "Energy Star certified laptop with modular design with score (7.8/10) - 50% longer lifespan and easy repair" | |
| - For clothing: "Organic cotton t-shirt with Fair Trade certification with score (8.2/10) - Pesticide-free farming and ethical production" | |
| DO NOT recommend: second-hand, vintage, used, refurbished, pre-owned, thrift store items.""" | |
| try: | |
| message = client.messages.create( | |
| model="claude-sonnet-4-20250514", | |
| max_tokens=2000, | |
| messages=[{"role": "user", "content": engineered_prompt}] | |
| ) | |
| response_text = message.content[0].text.strip() | |
| if "```json" in response_text: | |
| response_text = response_text.split("```json")[1].split("```")[0].strip() | |
| elif "```" in response_text: | |
| response_text = response_text.split("```")[1].split("```")[0].strip() | |
| product_assessment = json.loads(response_text) | |
| product_assessment["name"] = product_name.title() | |
| if isinstance(product_assessment.get("eco_score"), str): | |
| product_assessment["eco_score"] = float(product_assessment["eco_score"]) | |
| agent.execution_log.append(f"Final assessment generated") | |
| return { | |
| "assessment": product_assessment, | |
| "reasoning": reasoning, | |
| "rag_context": rag_context, | |
| "lifecycle": lifecycle_result, | |
| "execution_log": agent.execution_log.copy() | |
| } | |
| except Exception as e: | |
| agent.execution_log.append(f"Error: {str(e)}") | |
| return { | |
| "assessment": { | |
| "name": product_name.title(), | |
| "eco_score": 5.0, | |
| "carbon": "Data unavailable", | |
| "issues": "Assessment in progress", | |
| "alternative": "Consult environmental guidelines", | |
| "lifecycle_insights": "Analysis pending" | |
| }, | |
| "execution_log": agent.execution_log.copy() | |
| } | |
| def format_score(score) -> str: | |
| """Format eco score with emoji""" | |
| try: | |
| score_num = float(score) | |
| except (ValueError, TypeError): | |
| score_num = 5.0 | |
| if score_num >= 8: | |
| return f"{score_num}/10 Excellent" | |
| elif score_num >= 6: | |
| return f"{score_num}/10 Good" | |
| elif score_num >= 4: | |
| return f"{score_num}/10 Moderate" | |
| else: | |
| return f"{score_num}/10 Poor" | |
| # ===== GRADIO 6 FEATURE: Progress Tracking ===== | |
| def assess_product_agentic(image: str, progress=gr.Progress()) -> str: | |
| """Main assessment with Gradio 6 Progress tracking""" | |
| try: | |
| progress(0, desc="Initializing agent...") | |
| agent = EcoAgent() | |
| # Get product query | |
| query = "" | |
| source = "" | |
| vision_data = None | |
| if image is not None: | |
| progress(0.1, desc="Analyzing image...") | |
| query, vision_data = analyze_image_with_mcp(image) | |
| # Don't stop if image analysis fails, just log it | |
| if query and not query.startswith("Error"): | |
| source = f"**Detected:** {query}\n\n" | |
| if vision_data and vision_data.get("status") == "success": | |
| analysis = vision_data.get("analysis", {}) | |
| materials = analysis.get("materials", []) | |
| if materials and not any("material" in str(m).lower() for m in materials): | |
| source += f"**Materials:** {', '.join(materials)}\n\n" | |
| else: | |
| # Image analysis failed, clear query so text input can be used | |
| query = "" | |
| # Only return error if BOTH image and text are missing/invalid | |
| if not query: | |
| return "**Take a photo** or type a product name to get started!" | |
| # PHASE 1: PLANNING | |
| progress(0.3, desc="Agent planning assessment...") | |
| plan = agent.plan_assessment(query) | |
| # PHASE 2: REASONING | |
| progress(0.5, desc="Agent reasoning...") | |
| # PHASE 3: EXECUTION | |
| progress(0.7, desc="Executing assessment...") | |
| result = execute_assessment(agent, query, vision_data) | |
| progress(0.9, desc="Formatting results...") | |
| product = result["assessment"] | |
| # Get score for color coding | |
| eco_score = float(product.get('eco_score', 5.0)) | |
| # Determine score color based on value | |
| if eco_score < 5: | |
| score_color = "#ef4444" # Red | |
| score_label = "Poor" | |
| score_bg = "#fee2e2" | |
| elif eco_score < 7: | |
| score_color = "#f59e0b" # Amber | |
| score_label = "Moderate" | |
| score_bg = "#fef3c7" | |
| else: | |
| score_color = "#10b981" # Green | |
| score_label = "Good" | |
| score_bg = "#d1fae5" | |
| # Format main output with styled HTML | |
| main_output = f""" | |
| <div style="background: white; border-radius: 16px; padding: 2rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1);"> | |
| <div style="text-align: center; margin-bottom: 2rem;"> | |
| <div style="display: inline-block; background: {score_bg}; padding: 1.5rem 2.5rem; border-radius: 16px; margin-bottom: 1rem;"> | |
| <div style="font-size: 3.5rem; font-weight: 800; color: {score_color}; margin-bottom: 0.5rem;">{eco_score}/10</div> | |
| <div style="font-size: 1.2rem; font-weight: 600; color: {score_color}; text-transform: uppercase; letter-spacing: 0.05em;">{score_label}</div> | |
| </div> | |
| <h1 style="font-size: 2rem; font-weight: 700; color: #1e293b; margin: 1rem 0;">{product['name']}</h1> | |
| </div> | |
| <div style="display: grid; gap: 1.5rem;"> | |
| <div style="background: #f8fafc; border-left: 4px solid #10b981; padding: 1.5rem; border-radius: 8px;"> | |
| <h3 style="font-size: 1.1rem; font-weight: 600; color: #059669; margin: 0 0 0.75rem 0;">🌱 Carbon Footprint</h3> | |
| <p style="font-size: 1rem; color: #475569; margin: 0; line-height: 1.6;">{product.get('carbon', 'N/A')}</p> | |
| </div> | |
| <div style="background: #f8fafc; border-left: 4px solid #ef4444; padding: 1.5rem; border-radius: 8px;"> | |
| <h3 style="font-size: 1.1rem; font-weight: 600; color: #dc2626; margin: 0 0 0.75rem 0;">⚠️ Environmental Issues</h3> | |
| <p style="font-size: 1rem; color: #475569; margin: 0; line-height: 1.6;">{product.get('issues', 'N/A')}</p> | |
| </div> | |
| {"<div style='background: #f8fafc; border-left: 4px solid #3b82f6; padding: 1.5rem; border-radius: 8px;'><h3 style='font-size: 1.1rem; font-weight: 600; color: #2563eb; margin: 0 0 0.75rem 0;'>🔬 Materials Analysis</h3><p style='font-size: 1rem; color: #475569; margin: 0; line-height: 1.6;'>" + product['materials_analysis'] + "</p></div>" if 'materials_analysis' in product else ""} | |
| {"<div style='background: #f8fafc; border-left: 4px solid #8b5cf6; padding: 1.5rem; border-radius: 8px;'><h3 style='font-size: 1.1rem; font-weight: 600; color: #7c3aed; margin: 0 0 0.75rem 0;'>♻️ Lifecycle Insights</h3><p style='font-size: 1rem; color: #475569; margin: 0; line-height: 1.6;'>" + product['lifecycle_insights'] + "</p></div>" if 'lifecycle_insights' in product else ""} | |
| <div style="background: linear-gradient(135deg, #10b981 0%, #059669 100%); padding: 2rem; border-radius: 12px; margin-top: 1rem;"> | |
| <h3 style="font-size: 1.3rem; font-weight: 700; color: white; margin: 0 0 1rem 0; display: flex; align-items: center; gap: 0.5rem;"> | |
| <span style="font-size: 1.5rem;">✨</span> Better Alternative | |
| </h3> | |
| <p style="font-size: 1.05rem; color: white; margin: 0; line-height: 1.7; font-weight: 500;">{product.get('alternative', 'N/A')}</p> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| progress(1.0, desc="Complete!") | |
| return main_output | |
| except Exception as e: | |
| import traceback | |
| return f"**Error:**\n\n{str(e)}\n\n```\n{traceback.format_exc()}\n```" | |
| # ===== ECO SAGE ADVISOR INSPIRED DESIGN ===== | |
| eco_sage_css = """ | |
| /* Eco Sage Advisor Inspired Design */ | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap'); | |
| * { | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
| } | |
| :root { | |
| --primary: #10b981; | |
| --primary-dark: #059669; | |
| --danger: #ef4444; | |
| --warning: #f59e0b; | |
| --info: #3b82f6; | |
| } | |
| body { | |
| background: #f9fafb; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| .gradio-container { | |
| max-width: 1200px !important; | |
| margin: 0 auto !important; | |
| padding: 2rem 1rem !important; | |
| } | |
| /* Hero Section */ | |
| .hero-section { | |
| text-align: center; | |
| padding: 3rem 1rem; | |
| background: rgba(255, 255, 255, 0.95); | |
| border-radius: 24px; | |
| margin-bottom: 2rem; | |
| box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); | |
| } | |
| /* Blocks/Cards */ | |
| .block { | |
| background: rgba(255, 255, 255, 0.98) !important; | |
| border: none !important; | |
| border-radius: 20px !important; | |
| padding: 2rem !important; | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05) !important; | |
| backdrop-filter: blur(10px); | |
| } | |
| /* Input Fields */ | |
| input, textarea { | |
| background: #ffffff !important; | |
| border: 2px solid #e5e7eb !important; | |
| border-radius: 12px !important; | |
| padding: 14px 18px !important; | |
| font-size: 16px !important; | |
| color: #1f2937 !important; | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| input:focus, textarea:focus { | |
| border-color: var(--primary) !important; | |
| box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.1) !important; | |
| outline: none !important; | |
| transform: translateY(-1px); | |
| } | |
| input::placeholder, textarea::placeholder { | |
| color: #9ca3af !important; | |
| } | |
| /* Primary Button - Hero Style */ | |
| #assess-btn { | |
| background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important; | |
| color: white !important; | |
| border: none !important; | |
| border-radius: 16px !important; | |
| padding: 18px 36px !important; | |
| font-weight: 700 !important; | |
| font-size: 1.15rem !important; | |
| letter-spacing: 0.02em; | |
| cursor: pointer !important; | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| box-shadow: 0 10px 25px -5px rgba(16, 185, 129, 0.4), 0 10px 10px -5px rgba(16, 185, 129, 0.04) !important; | |
| text-transform: none !important; | |
| width: 100% !important; | |
| } | |
| #assess-btn:hover { | |
| transform: translateY(-3px) scale(1.02); | |
| box-shadow: 0 20px 35px -5px rgba(16, 185, 129, 0.5), 0 10px 10px -5px rgba(16, 185, 129, 0.08) !important; | |
| background: linear-gradient(135deg, #059669 0%, #047857 100%) !important; | |
| } | |
| #assess-btn:active { | |
| transform: translateY(-1px) scale(0.98); | |
| } | |
| /* Result Box */ | |
| #result-box { | |
| background: transparent !important; | |
| border: none !important; | |
| padding: 0 !important; | |
| box-shadow: none !important; | |
| } | |
| #result-box > div { | |
| background: transparent !important; | |
| } | |
| /* Image Upload Area - FIXED SIZE */ | |
| # .image-container { | |
| # border: 3px dashed #d1d5db !important; | |
| # border-radius: 20px !important; | |
| # background: linear-gradient(135deg, #f9fafb 0%, #ffffff 100%) !important; | |
| # transition: all 0.3s ease; | |
| # } | |
| .image-container:hover { | |
| border-color: var(--primary) !important; | |
| background: linear-gradient(135deg, #ecfdf5 0%, #f0fdf4 100%) !important; | |
| transform: scale(1.01); | |
| } | |
| /* Hide large upload icon */ | |
| .image-container .upload-icon { | |
| display: none !important; | |
| } | |
| /* Resize upload text and icon */ | |
| .image-container button { | |
| font-size: 0.95rem !important; | |
| padding: 0.75rem 1.5rem !important; | |
| } | |
| /* Make upload area more compact */ | |
| .image-container > div { | |
| padding: 1rem !important; | |
| } | |
| /* Hide the huge center icon in Gradio Image component */ | |
| .image-container svg { | |
| width: 40px !important; | |
| height: 40px !important; | |
| opacity: 0.5; | |
| } | |
| .image-container .wrap { | |
| min-height: 250px !important; | |
| } | |
| /* Labels */ | |
| label { | |
| color: #374151 !important; | |
| font-weight: 500 !important; | |
| font-size: 1rem !important; | |
| margin-bottom: 0.75rem !important; | |
| display: block !important; | |
| } | |
| /* Progress Bar */ | |
| .progress-bar { | |
| background: linear-gradient(90deg, #10b981 0%, #059669 100%) !important; | |
| border-radius: 9999px !important; | |
| } | |
| .progress-bar-wrap { | |
| background: rgba(16, 185, 129, 0.1) !important; | |
| border-radius: 9999px !important; | |
| } | |
| /* OR Divider */ | |
| .or-divider { | |
| text-align: center; | |
| margin: 1.5rem 0; | |
| position: relative; | |
| } | |
| .or-divider::before, | |
| .or-divider::after { | |
| content: ''; | |
| position: absolute; | |
| top: 50%; | |
| width: 40%; | |
| height: 2px; | |
| background: linear-gradient(to right, transparent, #d1d5db, transparent); | |
| } | |
| .or-divider::before { | |
| left: 0; | |
| } | |
| .or-divider::after { | |
| right: 0; | |
| } | |
| /* Toast/Info Messages */ | |
| .toast { | |
| background: white !important; | |
| border-left: 4px solid var(--primary) !important; | |
| border-radius: 12px !important; | |
| box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1) !important; | |
| } | |
| /* Scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 10px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: #f1f5f9; | |
| border-radius: 10px; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: linear-gradient(180deg, #10b981, #059669); | |
| border-radius: 10px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: linear-gradient(180deg, #059669, #047857); | |
| } | |
| /* Mobile Responsive */ | |
| @media (max-width: 768px) { | |
| .gradio-container { | |
| padding: 1rem 0.5rem !important; | |
| } | |
| .hero-section { | |
| padding: 2rem 1rem; | |
| } | |
| .block { | |
| padding: 1.5rem !important; | |
| } | |
| #assess-btn { | |
| padding: 16px 28px !important; | |
| font-size: 1rem !important; | |
| } | |
| } | |
| /* Animations */ | |
| @keyframes fadeIn { | |
| from { | |
| opacity: 0; | |
| transform: translateY(10px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .block { | |
| animation: fadeIn 0.5s ease-out; | |
| } | |
| /* Footer */ | |
| footer { | |
| background: rgba(255, 255, 255, 0.1) !important; | |
| backdrop-filter: blur(10px); | |
| border-radius: 16px; | |
| margin-top: 3rem; | |
| padding: 2rem; | |
| } | |
| footer p { | |
| color: white !important; | |
| text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); | |
| } | |
| """ | |
| with gr.Blocks(title="Future Earth") as app: | |
| gr.HTML(f""" | |
| <style> | |
| {eco_sage_css} | |
| </style> | |
| <div class="hero-section"> | |
| <!-- Logo and Title Row --> | |
| <div style="display: flex; align-items: center; justify-content: center; gap: 1rem; margin-bottom: 1.5rem;"> | |
| <div style="display: flex; align-items: baseline; gap: 0.5rem;"> | |
| <h1 style="font-size: 2.5rem; font-weight: 800; color: #047857; margin: 0; line-height: 1;">Future Earth</h1> | |
| </div> | |
| </div> | |
| <!-- Main Heading --> | |
| <h2 style="font-size: 2.25rem; font-weight: 700; color: #111827; margin: 0 0 1rem 0; line-height: 1.2;">Agentic Eco Advisor</h2> | |
| <!-- Subtitle --> | |
| <p style="font-size: 1.125rem; color: #6b7280; margin: 0; font-weight: 400; max-width: 800px; margin-left: auto; margin-right: auto;">Analyze products instantly and discover their environmental footprint</p> | |
| </div> | |
| """) | |
| with gr.Column(): | |
| image_input = gr.Image( | |
| label="Upload Product Image", | |
| type="pil", | |
| sources=["webcam", "upload"], | |
| height=300, | |
| elem_classes="image-container", | |
| show_label=True | |
| ) | |
| assess_btn = gr.Button("🌱 Analyze Environmental Impact", elem_id="assess-btn", size="lg") | |
| result_output = gr.HTML( | |
| label="", | |
| value=""" | |
| <div style="background: white; border-radius: 20px; padding: 3rem; text-align: center; box-shadow: 0 10px 25px -5px rgba(0,0,0,0.1);"> | |
| <div style="font-size: 4rem; margin-bottom: 1rem;">🌿</div> | |
| <h2 style="font-size: 1.5rem; font-weight: 700; color: #1e293b; margin-bottom: 1rem;">Ready to Analyze</h2> | |
| <p style="font-size: 1.1rem; color: #64748b; margin: 0;">Upload a product image or type a product name to get started with your environmental assessment</p> | |
| </div> | |
| """, | |
| elem_id="result-box" | |
| ) | |
| gr.HTML(""" | |
| <footer style="text-align: center;"> | |
| <p style="font-size: 0.85rem; font-weight: 600; margin: 0; color: #1f2937 !important; text-shadow: none !important;">♻️ Powered by Advanced AI • MCP • RAG Technology</p> | |
| <p style="font-size: 0.85rem; margin-top: 0.75rem; opacity: 0.9; color: #374151 !important; text-shadow: none !important;">Building a sustainable future through intelligent environmental analysis</p> | |
| <div style="margin-top: 1.5rem; display: flex; justify-content: center; gap: 2rem; flex-wrap: wrap;"> | |
| <span style="font-size: 0.85rem; color: #1f2937 !important;">🌱 Sustainable Products</span> | |
| <span style="font-size: 0.85rem; color: #1f2937 !important;">📊 Data-Driven Insights</span> | |
| <span style="font-size: 0.85rem; color: #1f2937 !important;">🔬 Scientific Analysis</span> | |
| </div> | |
| </footer> | |
| """) | |
| # Event handling | |
| assess_btn.click( | |
| fn=assess_product_agentic, | |
| inputs=[image_input], | |
| outputs=[result_output] | |
| ).then( | |
| fn=lambda: gr.Info("✅ Assessment Complete!"), | |
| inputs=None, | |
| outputs=None | |
| ) | |
| if __name__ == "__main__": | |
| app.launch() |