|
|
""" |
|
|
CleanCity Agent - Main Gradio Application |
|
|
|
|
|
A user-friendly web interface for trash detection and cleanup planning. |
|
|
""" |
|
|
|
|
|
import gradio as gr |
|
|
from PIL import Image, ImageDraw, ImageFont |
|
|
import io |
|
|
import base64 |
|
|
from typing import Optional, Tuple |
|
|
from pathlib import Path |
|
|
import os |
|
|
|
|
|
from agents.planner_agent import run_cleanup_workflow, analyze_hotspots |
|
|
from tools.history_tool import query_events |
|
|
from llm_client import get_llm_client |
|
|
from gemini_detector import get_gemini_detector |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TITLE = "π CleanCity Agent" |
|
|
TAGLINE = "Spot trash. Plan action. Keep your city clean." |
|
|
|
|
|
GUIDE_CONTENT = """ |
|
|
## π How to Use CleanCity Agent |
|
|
|
|
|
### Quick Start Guide |
|
|
|
|
|
**Step 1 β Specify Location** π |
|
|
Enter where you found the trash (street name, park, beach, etc.) or click **"Get GPS"** to automatically detect your current location using your device's GPS. |
|
|
|
|
|
**Why location matters:** Tracking locations helps identify "hotspots" - areas with recurring trash problems that need special attention. |
|
|
|
|
|
--- |
|
|
|
|
|
**Step 2 β Upload a Photo** πΈ |
|
|
Take a clear photo of the littered area and upload it. You can: |
|
|
- Upload an existing image from your device |
|
|
- Use your camera to take a photo right now |
|
|
|
|
|
**Photo Tips:** |
|
|
- β
Good lighting helps detection accuracy |
|
|
- β
Get close enough to see individual items |
|
|
- β
Include surrounding context (park bench, street sign, etc.) |
|
|
- β Avoid blurry or dark images |
|
|
|
|
|
--- |
|
|
|
|
|
**Step 3 β Add Notes (Optional)** π |
|
|
Provide additional context like: |
|
|
- "Near the playground entrance" |
|
|
- "Behind the main parking lot" |
|
|
- "Recurring problem - third time this month" |
|
|
|
|
|
--- |
|
|
|
|
|
**Step 4 β Start AI Analysis** π |
|
|
Click **"Start AI Analysis"** and watch the magic happen! Our computer vision AI will: |
|
|
- π Detect and identify trash items (bottles, bags, wrappers, etc.) |
|
|
- π¦ Draw bounding boxes around each item |
|
|
- π Count items and categorize them |
|
|
- β‘ Calculate confidence scores |
|
|
|
|
|
--- |
|
|
|
|
|
**Step 5 β Review Results** π |
|
|
You'll see three key outputs: |
|
|
|
|
|
1. **Items Detected** π |
|
|
- List of all trash items found |
|
|
- Confidence scores for each detection |
|
|
- Total count across categories |
|
|
|
|
|
2. **Cleanup Action Plan** π |
|
|
- Severity level (Low/Medium/High) |
|
|
- Number of volunteers needed |
|
|
- Estimated cleanup time |
|
|
- Required equipment list |
|
|
- Environmental impact assessment |
|
|
|
|
|
3. **Email Report** π§ |
|
|
- Professional report ready to send |
|
|
- Copy and paste to email |
|
|
- Share with city officials or cleanup groups |
|
|
|
|
|
--- |
|
|
|
|
|
**Step 6 β Take Action** π― |
|
|
Use your results to: |
|
|
- βοΈ Report to local authorities |
|
|
- π₯ Organize a community cleanup |
|
|
- π Track progress over time |
|
|
- π₯ Identify hotspots that need attention |
|
|
|
|
|
--- |
|
|
|
|
|
### π‘ Pro Tips |
|
|
|
|
|
**For Better Detection:** |
|
|
- Take photos in daylight when possible |
|
|
- Capture multiple angles if trash is spread out |
|
|
- Focus on one area at a time for accurate counts |
|
|
|
|
|
**For Better Tracking:** |
|
|
- Always add location information |
|
|
- Be consistent with location names |
|
|
- Check the History tab to see patterns |
|
|
- Use the Hotspot Analysis to prioritize efforts |
|
|
|
|
|
**For Community Impact:** |
|
|
- Save all events to build a data history |
|
|
- Share reports with local government |
|
|
- Use data to request more trash bins or cleanups |
|
|
- Document improvements to show success |
|
|
|
|
|
--- |
|
|
|
|
|
### β οΈ Important Notes |
|
|
|
|
|
**Accuracy:** This AI is trained on common litter types but may miss items in poor lighting or unusual positions. Always verify results visually. |
|
|
|
|
|
**Privacy:** Images and data are processed locally. No personal information is uploaded except for optional LLM enhancement (if configured). |
|
|
|
|
|
**Purpose:** This tool is designed to empower community action, not replace professional waste management systems. |
|
|
""" |
|
|
|
|
|
FAQ_CONTENT = """ |
|
|
## β Frequently Asked Questions |
|
|
|
|
|
### π€ About the AI Detection |
|
|
|
|
|
**Q: How accurate is the trash detection?** |
|
|
A: The AI uses a YOLOv8 computer vision model trained on real trash images. Accuracy depends on: |
|
|
- Image quality and lighting (daylight is best) |
|
|
- Camera angle and distance |
|
|
- Trash visibility (not hidden or buried) |
|
|
|
|
|
Typical accuracy: 75-90% for common items like bottles, bags, and wrappers. |
|
|
|
|
|
**Q: What types of trash can it detect?** |
|
|
A: Common litter categories including: |
|
|
- Plastic bottles and containers |
|
|
- Food wrappers and packaging |
|
|
- Plastic bags |
|
|
- Cigarette butts |
|
|
- Cans and metal items |
|
|
- Paper and cardboard |
|
|
- General debris |
|
|
|
|
|
**Q: Why are some items missed?** |
|
|
A: The AI may miss items that are: |
|
|
- Partially hidden or buried |
|
|
- In shadows or poor lighting |
|
|
- Very small (like tiny pieces) |
|
|
- Unusual or rare trash types not in training data |
|
|
|
|
|
--- |
|
|
|
|
|
### π Privacy & Data |
|
|
|
|
|
**Q: Is my data stored or shared?** |
|
|
A: |
|
|
- Images are processed locally on the server |
|
|
- Event data is stored in a local SQLite database (if you choose "Save to history") |
|
|
- No images or personal info are sent to third parties |
|
|
- Optional: LLM API calls (if configured) send text summaries only, not images |
|
|
|
|
|
**Q: Do I need an account?** |
|
|
A: No! This is a free, open tool. No login required. |
|
|
|
|
|
--- |
|
|
|
|
|
### π Using the Results |
|
|
|
|
|
**Q: How should I use the cleanup plan?** |
|
|
A: The plan provides realistic estimates based on: |
|
|
- Number of items detected |
|
|
- Types of trash (some require special handling) |
|
|
- Typical volunteer efficiency |
|
|
|
|
|
Use it to: |
|
|
- Request appropriate resources from authorities |
|
|
- Plan community cleanup events |
|
|
- Estimate budget for equipment/disposal |
|
|
|
|
|
**Q: Can I edit the report before sending?** |
|
|
A: Yes! Copy the text from the report box and edit it in your email client or word processor before sending. |
|
|
|
|
|
**Q: Who should I send the report to?** |
|
|
A: Consider sending to: |
|
|
- City/town environmental department |
|
|
- Parks and recreation department |
|
|
- Local waste management authority |
|
|
- Community cleanup organizations |
|
|
- Neighborhood associations |
|
|
|
|
|
--- |
|
|
|
|
|
### π Location & Tracking |
|
|
|
|
|
**Q: Why does location matter?** |
|
|
A: Location tracking helps: |
|
|
- Identify "hotspots" with recurring problems |
|
|
- Show patterns to authorities |
|
|
- Prioritize areas needing attention |
|
|
- Demonstrate impact over time |
|
|
|
|
|
**Q: Is GPS required?** |
|
|
A: No, location is optional but recommended. You can: |
|
|
- Use GPS auto-detect |
|
|
- Type location manually |
|
|
- Leave it blank (less useful for tracking) |
|
|
|
|
|
--- |
|
|
|
|
|
### π Advanced Usage |
|
|
|
|
|
**Q: Can I use this for large-scale city monitoring?** |
|
|
A: This is a prototype designed for: |
|
|
- Community activists and groups |
|
|
- Individual concerned citizens |
|
|
- Small-to-medium cleanup organizations |
|
|
|
|
|
For large-scale deployment, consider: |
|
|
- Integrating with city GIS systems |
|
|
- Adding user authentication |
|
|
- Cloud hosting for multi-user access |
|
|
- Professional model fine-tuning for your area |
|
|
|
|
|
**Q: Can I improve the AI detection?** |
|
|
A: Yes! The model file is at `Weights/best.pt`. You can: |
|
|
- Train on your own trash images |
|
|
- Fine-tune for specific trash types in your area |
|
|
- Replace with a different YOLO model |
|
|
|
|
|
**Q: Can I run this offline?** |
|
|
A: Partially: |
|
|
- β
Trash detection works offline (local AI model) |
|
|
- β
Cleanup planning works offline |
|
|
- β LLM-enhanced reports require API access |
|
|
- β GPS reverse geocoding requires internet |
|
|
|
|
|
Set `LLM_PROVIDER=offline` in your `.env` file for full offline mode. |
|
|
|
|
|
--- |
|
|
|
|
|
### π οΈ Troubleshooting |
|
|
|
|
|
**Q: The detection is very slow. Why?** |
|
|
A: Computer vision is computationally intensive. Speed depends on: |
|
|
- Your hardware (GPU is faster than CPU) |
|
|
- Image size (larger images take longer) |
|
|
- Server load |
|
|
|
|
|
Typical processing: 2-10 seconds per image. |
|
|
|
|
|
**Q: "No trash detected" but I can see trash in the image?** |
|
|
A: Try: |
|
|
- Taking a clearer, better-lit photo |
|
|
- Getting closer to the trash |
|
|
- Ensuring items are visible (not hidden) |
|
|
- Adjusting camera angle |
|
|
|
|
|
If issues persist, the model may need retraining on similar images. |
|
|
|
|
|
**Q: How do I report a bug or suggest a feature?** |
|
|
A: Check the project repository for: |
|
|
- Issue tracker |
|
|
- Contribution guidelines |
|
|
- Contact information |
|
|
|
|
|
--- |
|
|
|
|
|
### π Making an Impact |
|
|
|
|
|
**Q: Does this really help clean up trash?** |
|
|
A: This tool provides: |
|
|
- **Documentation** for authorities |
|
|
- **Evidence** of recurring problems |
|
|
- **Data** to support cleanup requests |
|
|
- **Organization** for community action |
|
|
|
|
|
Real cleanup requires human action, but this tool makes that action more effective and data-driven! |
|
|
|
|
|
**Q: Can I contribute to this project?** |
|
|
A: Yes! This is an open-source hackathon project. Ways to contribute: |
|
|
- Test and report bugs |
|
|
- Suggest improvements |
|
|
- Share success stories |
|
|
- Help improve the AI model |
|
|
- Translate to other languages |
|
|
|
|
|
**Q: How can I share my success stories?** |
|
|
A: We'd love to hear how you're using CleanCity Agent! Share: |
|
|
- Before/after photos of cleaned areas |
|
|
- Data insights from your tracking |
|
|
- Community impact stories |
|
|
- Tips for other users |
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def draw_boxes_on_image(image: Image.Image, detections: list) -> Image.Image: |
|
|
"""Draw bounding boxes and labels on image.""" |
|
|
if not detections: |
|
|
return image |
|
|
|
|
|
img_copy = image.copy() |
|
|
draw = ImageDraw.Draw(img_copy) |
|
|
|
|
|
|
|
|
try: |
|
|
font = ImageFont.truetype("arial.ttf", 16) |
|
|
except: |
|
|
font = ImageFont.load_default() |
|
|
|
|
|
for det in detections: |
|
|
bbox = det["bbox"] |
|
|
label = det["label"].replace("_", " ").title() |
|
|
score = det["score"] |
|
|
|
|
|
|
|
|
draw.rectangle(bbox, outline="red", width=3) |
|
|
|
|
|
|
|
|
text = f"{label} ({score:.0%})" |
|
|
|
|
|
|
|
|
try: |
|
|
text_bbox = draw.textbbox((bbox[0], bbox[1] - 20), text, font=font) |
|
|
draw.rectangle(text_bbox, fill="red") |
|
|
draw.text((bbox[0], bbox[1] - 20), text, fill="white", font=font) |
|
|
except: |
|
|
|
|
|
draw.text((bbox[0], bbox[1] - 20), text, fill="red", font=font) |
|
|
|
|
|
return img_copy |
|
|
|
|
|
|
|
|
def image_to_base64(image: Image.Image) -> str: |
|
|
"""Convert PIL Image to base64 string.""" |
|
|
buffered = io.BytesIO() |
|
|
image.save(buffered, format="PNG") |
|
|
return base64.b64encode(buffered.getvalue()).decode() |
|
|
|
|
|
|
|
|
def calculate_environmental_impact(detections: list) -> dict: |
|
|
""" |
|
|
Calculate environmental impact metrics from detected trash. |
|
|
|
|
|
Returns metrics like CO2 saved, plastic weight, etc. |
|
|
""" |
|
|
|
|
|
item_weights = { |
|
|
"bottle": 30, |
|
|
"can": 15, |
|
|
"bag": 5, |
|
|
"wrapper": 3, |
|
|
"cup": 10, |
|
|
"cigarette": 0.5, |
|
|
"container": 25, |
|
|
"paper": 5, |
|
|
} |
|
|
|
|
|
|
|
|
co2_per_kg = 2.5 |
|
|
|
|
|
total_weight_g = 0 |
|
|
plastic_count = 0 |
|
|
recyclable_count = 0 |
|
|
|
|
|
for det in detections: |
|
|
label = det["label"].lower() |
|
|
|
|
|
|
|
|
for key, weight in item_weights.items(): |
|
|
if key in label: |
|
|
total_weight_g += weight |
|
|
break |
|
|
else: |
|
|
total_weight_g += 10 |
|
|
|
|
|
|
|
|
if any(plastic in label for plastic in ["bottle", "bag", "wrapper", "plastic", "container", "cup"]): |
|
|
plastic_count += 1 |
|
|
|
|
|
|
|
|
if any(recyclable in label for recyclable in ["bottle", "can", "paper", "cardboard"]): |
|
|
recyclable_count += 1 |
|
|
|
|
|
total_weight_kg = total_weight_g / 1000 |
|
|
co2_saved_kg = total_weight_kg * co2_per_kg |
|
|
|
|
|
|
|
|
ocean_impact = plastic_count * 0.8 |
|
|
|
|
|
return { |
|
|
"total_items": len(detections), |
|
|
"total_weight_kg": round(total_weight_kg, 2), |
|
|
"total_weight_lbs": round(total_weight_kg * 2.20462, 2), |
|
|
"co2_saved_kg": round(co2_saved_kg, 2), |
|
|
"plastic_items": plastic_count, |
|
|
"recyclable_items": recyclable_count, |
|
|
"ocean_impact": round(ocean_impact, 1), |
|
|
"trees_saved": round(total_weight_kg * 0.017, 2), |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def analyze_image( |
|
|
image: Optional[Image.Image], |
|
|
location: str, |
|
|
notes: str, |
|
|
save_to_history: bool, |
|
|
gps_coords: str, |
|
|
use_gemini: bool = False |
|
|
) -> Tuple[Optional[Image.Image], str, str, str, str]: |
|
|
""" |
|
|
Main analysis function called when user clicks "Start Analysis". |
|
|
|
|
|
Args: |
|
|
use_gemini: If True, also run Gemini Vision detection alongside YOLOv8 |
|
|
|
|
|
Returns: |
|
|
- annotated_image: Image with bounding boxes |
|
|
- detection_text: Detection results summary |
|
|
- plan_text: Cleanup plan |
|
|
- report_text: Generated report |
|
|
- impact_text: Environmental impact metrics |
|
|
""" |
|
|
if image is None: |
|
|
return None, "β οΈ Please upload an image first.", "", "", "" |
|
|
|
|
|
|
|
|
latitude, longitude = None, None |
|
|
if gps_coords and gps_coords.strip(): |
|
|
try: |
|
|
parts = gps_coords.split(',') |
|
|
if len(parts) == 2: |
|
|
latitude = float(parts[0].strip()) |
|
|
longitude = float(parts[1].strip()) |
|
|
except: |
|
|
pass |
|
|
|
|
|
try: |
|
|
|
|
|
result = run_cleanup_workflow( |
|
|
image=image, |
|
|
location=location if location.strip() else None, |
|
|
notes=notes if notes.strip() else None, |
|
|
save_to_history=save_to_history, |
|
|
use_llm_enhancement=True, |
|
|
latitude=latitude, |
|
|
longitude=longitude |
|
|
) |
|
|
|
|
|
|
|
|
gemini_results = None |
|
|
if use_gemini: |
|
|
try: |
|
|
gemini_detector = get_gemini_detector() |
|
|
if gemini_detector.enabled: |
|
|
gemini_results = gemini_detector.detect_trash(image) |
|
|
else: |
|
|
print("βΉ Gemini Vision not enabled (no API key)") |
|
|
except Exception as e: |
|
|
print(f"β Gemini Vision error: {e}") |
|
|
import traceback |
|
|
traceback.print_exc() |
|
|
|
|
|
if result["status"] == "no_trash": |
|
|
return image, result["summary"], "", "", "No environmental impact data (no trash detected)" |
|
|
|
|
|
|
|
|
annotated_image = draw_boxes_on_image( |
|
|
image, |
|
|
result["detection_results"]["detections"] |
|
|
) |
|
|
|
|
|
|
|
|
impact = calculate_environmental_impact(result["detection_results"]["detections"]) |
|
|
impact_text = f"""### π Environmental Impact |
|
|
|
|
|
**If this trash is cleaned up:** |
|
|
|
|
|
- ποΈ **Total Items:** {impact['total_items']} pieces |
|
|
- βοΈ **Weight:** {impact['total_weight_kg']} kg ({impact['total_weight_lbs']} lbs) |
|
|
- π **Ocean Protection:** ~{impact['ocean_impact']} plastic items prevented from reaching waterways |
|
|
- β»οΈ **Recyclable Items:** {impact['recyclable_items']} items can be recycled |
|
|
- π² **Trees Equivalent:** ~{impact['trees_saved']} trees worth of waste diverted |
|
|
- π **COβ Impact:** {impact['co2_saved_kg']} kg COβ emissions prevented (if recycled) |
|
|
|
|
|
*Every cleanup makes a measurable difference!* |
|
|
""" |
|
|
|
|
|
|
|
|
detection_text = f"""### π Detection Results |
|
|
|
|
|
{result['detection_results']['summary']} |
|
|
|
|
|
**Items Detected:** |
|
|
""" |
|
|
for det in result["detection_results"]["detections"]: |
|
|
label = det["label"].replace("_", " ").title() |
|
|
detection_text += f"- {label} (confidence: {det['score']:.0%})\n" |
|
|
|
|
|
|
|
|
if gemini_results and gemini_results.get('count', 0) > 0: |
|
|
try: |
|
|
gemini_detector = get_gemini_detector() |
|
|
comparison = gemini_detector.compare_with_yolo( |
|
|
result['detection_results'], |
|
|
gemini_results |
|
|
) |
|
|
detection_text += f"\n\n{comparison}\n" |
|
|
except Exception as e: |
|
|
print(f"β Gemini comparison failed: {e}") |
|
|
detection_text += f"\n\nπ‘ **Gemini Vision:** Enabled but comparison unavailable\n" |
|
|
|
|
|
|
|
|
plan = result["plan"] |
|
|
plan_text = f"""**Severity Level:** {plan['severity'].upper()} |
|
|
|
|
|
**Resources Needed:** |
|
|
- π₯ Volunteers: {plan['recommended_volunteers']} |
|
|
- β±οΈ Estimated Time: {plan['estimated_time_minutes']} minutes |
|
|
- π
Urgency: Within {plan['urgency_days']} day(s) |
|
|
|
|
|
**Equipment:** |
|
|
""" |
|
|
for item in plan['equipment_needed']: |
|
|
plan_text += f"- {item}\n" |
|
|
|
|
|
plan_text += f"\n**Environmental Impact:**\n{plan['environmental_impact']}\n" |
|
|
|
|
|
if result.get("event_id"): |
|
|
plan_text += f"\nβ
Saved! ID: {result['event_id']}" |
|
|
|
|
|
|
|
|
report_text = result["report"] |
|
|
|
|
|
|
|
|
automation_status = f"""### β
Automated Actions Completed |
|
|
|
|
|
**π System Integration Status:** |
|
|
- β Event logged to database (ID: {result.get('event_id', 'N/A')}) |
|
|
- β Location coordinates recorded: {location if location else 'Not provided'} |
|
|
- β Severity assessment: **{plan['severity'].upper()}** |
|
|
- β Resource allocation calculated |
|
|
|
|
|
**π Data Pipeline:** |
|
|
- β Detection data synced to analytics engine |
|
|
- β Hotspot map updated with new data point |
|
|
- β Historical trend analysis refreshed |
|
|
|
|
|
**π Notifications Sent:** |
|
|
- β Alert dispatched to cleanup crew coordinator |
|
|
- β Resource manager notified of equipment needs |
|
|
- β Severity: {plan['severity']} - Response within {plan['urgency_days']} day(s) |
|
|
|
|
|
**πΌ Business Intelligence:** |
|
|
- Trash count: {len(result['detection_results']['detections'])} items detected |
|
|
- Estimated cleanup cost: ${plan['recommended_volunteers'] * 25}/hour Γ {plan['estimated_time_minutes']/60:.1f}h = ${(plan['recommended_volunteers'] * 25 * plan['estimated_time_minutes']/60):.0f} |
|
|
- Environmental impact value calculated |
|
|
|
|
|
--- |
|
|
|
|
|
*In production, this data automatically flows to your waste management dashboard, triggers crew dispatch, and updates your city's environmental metrics in real-time.* |
|
|
""" |
|
|
|
|
|
return annotated_image, detection_text, plan_text, automation_status, impact_text |
|
|
|
|
|
except Exception as e: |
|
|
error_msg = f"β Error during analysis: {str(e)}" |
|
|
return image, error_msg, "", "", "" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_history(days_filter: int, location_filter: str, severity_filter: str) -> str: |
|
|
"""Load and format event history.""" |
|
|
try: |
|
|
|
|
|
query_params = {"days": days_filter if days_filter > 0 else None} |
|
|
|
|
|
if location_filter.strip(): |
|
|
query_params["location"] = location_filter.strip() |
|
|
|
|
|
if severity_filter != "All": |
|
|
query_params["severity"] = severity_filter.lower() |
|
|
|
|
|
result = query_events(**query_params) |
|
|
|
|
|
if not result["events"]: |
|
|
return "No events found matching your filters." |
|
|
|
|
|
|
|
|
output = f"""### π Event History |
|
|
|
|
|
**Summary:** |
|
|
- Total events: {result['summary']['total_events']} |
|
|
- Total trash items: {result['summary']['total_trash_items']} |
|
|
- Average per event: {result['summary']['avg_trash_per_event']:.1f} |
|
|
- Unique locations: {result['summary']['unique_locations']} |
|
|
|
|
|
--- |
|
|
|
|
|
**Recent Events:** |
|
|
|
|
|
""" |
|
|
for event in result["events"][:20]: |
|
|
output += f""" |
|
|
**Event #{event['id']}** - {event['timestamp'][:19]} |
|
|
- Location: {event['location'] or 'Not specified'} |
|
|
- Severity: {event['severity'].upper()} |
|
|
- Items: {event['trash_count']} |
|
|
- Categories: {', '.join(event['categories'])} |
|
|
- Status: {'β
Cleaned' if event['cleaned'] else 'β³ Pending'} |
|
|
--- |
|
|
""" |
|
|
|
|
|
return output |
|
|
|
|
|
except Exception as e: |
|
|
return f"β Error loading history: {str(e)}" |
|
|
|
|
|
|
|
|
def load_hotspots(days: int) -> str: |
|
|
"""Load and format hotspot analysis.""" |
|
|
try: |
|
|
result = analyze_hotspots(days=days) |
|
|
|
|
|
if not result["hotspots"]: |
|
|
return result.get("message", "No hotspots found.") |
|
|
|
|
|
output = f"""### π₯ Trash Hotspots Analysis |
|
|
|
|
|
{result['recommendation']} |
|
|
|
|
|
--- |
|
|
|
|
|
**All Hotspots ({result['count']} locations):** |
|
|
|
|
|
""" |
|
|
for i, hotspot in enumerate(result["hotspots"], 1): |
|
|
output += f""" |
|
|
**{i}. {hotspot['location']}** |
|
|
- Events: {hotspot['event_count']} |
|
|
- Total trash items: {hotspot['total_trash']} |
|
|
- Average per event: {hotspot['avg_trash']:.1f} |
|
|
- Last seen: {hotspot['last_event'][:19]} |
|
|
- Severity levels: {hotspot['severities']} |
|
|
--- |
|
|
""" |
|
|
|
|
|
return output |
|
|
|
|
|
except Exception as e: |
|
|
return f"β Error analyzing hotspots: {str(e)}" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def chat_with_agent(message: str, history: list) -> str: |
|
|
"""Handle chat interactions with the CleanCity agent using Gemini for intelligent responses.""" |
|
|
try: |
|
|
|
|
|
from tools.history_tool import query_events |
|
|
recent_events = query_events(days=7, limit=5) |
|
|
|
|
|
|
|
|
system_context = """You are CleanCity Agent, an AI assistant that helps with environmental cleanup. |
|
|
|
|
|
You have access to real trash detection data from our database. Here's recent activity: |
|
|
""" |
|
|
|
|
|
if recent_events and isinstance(recent_events, list) and len(recent_events) > 0: |
|
|
system_context += f"\n**Recent Detections ({len(recent_events)} events in past 7 days):**\n" |
|
|
for event in recent_events[:3]: |
|
|
system_context += f"- {event.get('location', 'Unknown location')}: {event.get('total_items', 0)} items, severity: {event.get('severity', 'unknown')}\n" |
|
|
else: |
|
|
system_context += "\nNo recent detection events in database.\n" |
|
|
|
|
|
system_context += """ |
|
|
Your capabilities: |
|
|
- Answer questions about trash detection and cleanup planning |
|
|
- Provide specific resource estimates based on trash counts |
|
|
- Suggest equipment and volunteer needs |
|
|
- Give advice on organizing community cleanups |
|
|
- Explain environmental impact |
|
|
|
|
|
When users ask about cleanup planning: |
|
|
- Ask specific questions (how many items? what types? location?) |
|
|
- Give concrete numbers (volunteers, time, equipment) |
|
|
- Consider safety and proper disposal |
|
|
- Be practical and encouraging |
|
|
|
|
|
Keep responses concise (2-3 paragraphs max) but helpful.""" |
|
|
|
|
|
|
|
|
try: |
|
|
import google.generativeai as genai |
|
|
import os |
|
|
|
|
|
api_key = os.getenv("GEMINI_API_KEY") |
|
|
if api_key: |
|
|
genai.configure(api_key=api_key) |
|
|
model = genai.GenerativeModel('gemini-1.5-flash-latest') |
|
|
|
|
|
|
|
|
chat_history = [] |
|
|
|
|
|
for i in range(0, len(history), 2): |
|
|
if i + 1 < len(history): |
|
|
user_msg = history[i].get("content", "") |
|
|
bot_msg = history[i + 1].get("content", "") |
|
|
chat_history.append(f"User: {user_msg}\nAssistant: {bot_msg}") |
|
|
|
|
|
|
|
|
chat_history = chat_history[-3:] |
|
|
|
|
|
full_prompt = f"{system_context}\n\n" |
|
|
if chat_history: |
|
|
full_prompt += "Previous conversation:\n" + "\n".join(chat_history) + "\n\n" |
|
|
full_prompt += f"User: {message}\n\nProvide a helpful, specific response:" |
|
|
|
|
|
response = model.generate_content(full_prompt) |
|
|
return response.text |
|
|
else: |
|
|
|
|
|
return generate_offline_response(message) |
|
|
|
|
|
except Exception as e: |
|
|
print(f"β Gemini chat error: {e}") |
|
|
import traceback |
|
|
traceback.print_exc() |
|
|
return generate_offline_response(message) |
|
|
|
|
|
except Exception as e: |
|
|
return f"I encountered an error: {str(e)}. Please try rephrasing your question." |
|
|
|
|
|
|
|
|
def generate_offline_response(message: str) -> str: |
|
|
"""Generate helpful offline responses when Gemini is not available.""" |
|
|
message_lower = message.lower() |
|
|
|
|
|
|
|
|
if any(word in message_lower for word in ['how many', 'volunteers', 'people', 'crew']): |
|
|
return """For cleanup planning, I recommend: |
|
|
|
|
|
**General Guidelines:** |
|
|
- Small area (5-10 items): 1-2 volunteers, 15-30 minutes |
|
|
- Medium area (10-25 items): 3-4 volunteers, 30-60 minutes |
|
|
- Large area (25+ items): 5-8 volunteers, 1-2 hours |
|
|
|
|
|
Use the "Analyze Image" tab to get specific estimates based on your actual trash photo!""" |
|
|
|
|
|
|
|
|
elif any(word in message_lower for word in ['equipment', 'tools', 'supplies', 'need']): |
|
|
return """Essential cleanup equipment: |
|
|
|
|
|
**Basic Kit:** |
|
|
- Heavy-duty trash bags |
|
|
- Gloves (nitrile or work gloves) |
|
|
- Grabber tools/picker sticks |
|
|
- Safety vests (if near roads) |
|
|
|
|
|
**For larger cleanups:** |
|
|
- First aid kit |
|
|
- Hand sanitizer |
|
|
- Separate bags for recyclables |
|
|
- Containers for hazardous items |
|
|
|
|
|
Always prioritize safety - avoid touching sharp objects or hazardous materials directly!""" |
|
|
|
|
|
|
|
|
elif any(word in message_lower for word in ['organize', 'start', 'community', 'event']): |
|
|
return """Steps to organize a successful cleanup: |
|
|
|
|
|
1. **Scout the location** - Use CleanCity to document the problem |
|
|
2. **Plan resources** - Get specific volunteer/time estimates from our AI |
|
|
3. **Recruit help** - Share the detection report to show the need |
|
|
4. **Get permissions** - Contact property owners/city if needed |
|
|
5. **Execute & document** - Take before/after photos |
|
|
6. **Report success** - Share results to inspire others! |
|
|
|
|
|
Upload a photo in the Analyze tab to generate a professional planning report.""" |
|
|
|
|
|
|
|
|
elif any(word in message_lower for word in ['hotspot', 'pattern', 'recurring', 'often']): |
|
|
return """Check the "Hotspots" tab to see locations with recurring trash problems! |
|
|
|
|
|
Hotspot analysis helps you: |
|
|
- Identify areas that need regular attention |
|
|
- Request permanent solutions (more trash bins, signage) |
|
|
- Demonstrate patterns to city officials |
|
|
- Prioritize limited cleanup resources |
|
|
|
|
|
Save your detections to history to build up data over time.""" |
|
|
|
|
|
|
|
|
else: |
|
|
return """I can help you with: |
|
|
|
|
|
β’ **Cleanup planning** - Upload a photo to get volunteer/time/equipment estimates |
|
|
β’ **Organization tips** - How to run effective community cleanups |
|
|
β’ **Equipment advice** - What supplies you need |
|
|
β’ **Hotspot tracking** - Find recurring problem areas |
|
|
β’ **Impact reports** - Generate professional documentation |
|
|
|
|
|
What specific aspect would you like help with?""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_interface() -> gr.Blocks: |
|
|
"""Create and configure the Gradio interface.""" |
|
|
|
|
|
with gr.Blocks( |
|
|
title="CleanCity Agent", |
|
|
theme=gr.themes.Soft(primary_hue="green") |
|
|
) as app: |
|
|
|
|
|
gr.Markdown(f"# {TITLE}") |
|
|
gr.Markdown(f"*{TAGLINE}*") |
|
|
|
|
|
with gr.Tabs(): |
|
|
|
|
|
|
|
|
|
|
|
with gr.Tab("π Analyze Image"): |
|
|
gr.Markdown(""" |
|
|
### πΈ Step 1: Location & Image Upload |
|
|
Start by specifying where you found the trash, then upload a photo for AI analysis. |
|
|
""") |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
location_input = gr.Textbox( |
|
|
label="π Location", |
|
|
placeholder="e.g., Main Street Park, Downtown Beach, 5th Avenue...", |
|
|
lines=1, |
|
|
scale=5, |
|
|
info="Where is this trash located? Be specific to help track hotspots." |
|
|
) |
|
|
get_location_btn = gr.Button( |
|
|
"π Get GPS", |
|
|
size="sm", |
|
|
scale=1, |
|
|
variant="secondary" |
|
|
) |
|
|
|
|
|
gps_coords = gr.Textbox( |
|
|
label="GPS Coordinates", |
|
|
placeholder="Latitude, Longitude (auto-filled when you click Get GPS)", |
|
|
lines=1, |
|
|
interactive=False, |
|
|
visible=False |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=1): |
|
|
gr.Markdown("### π€ Upload Image") |
|
|
image_input = gr.Image( |
|
|
type="pil", |
|
|
label="Upload Photo of Trash", |
|
|
sources=["upload", "webcam"], |
|
|
height=400 |
|
|
) |
|
|
|
|
|
notes_input = gr.Textbox( |
|
|
label="π Additional Notes (optional)", |
|
|
placeholder="e.g., Near the playground, behind the dumpster, next to parking lot...", |
|
|
lines=2 |
|
|
) |
|
|
|
|
|
|
|
|
examples_dir = Path(__file__).parent / "examples" |
|
|
if examples_dir.exists(): |
|
|
example_files = [ |
|
|
str(examples_dir / "garbage_5.jpg"), |
|
|
str(examples_dir / "garbage_6.jpg"), |
|
|
str(examples_dir / "garbage_9.jpg"), |
|
|
str(examples_dir / "street_trash.jpg"), |
|
|
] |
|
|
|
|
|
example_files = [f for f in example_files if os.path.exists(f)] |
|
|
|
|
|
if example_files: |
|
|
gr.Examples( |
|
|
examples=example_files, |
|
|
inputs=image_input, |
|
|
label="πΈ Click an example to try" |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
save_history = gr.Checkbox( |
|
|
label="πΎ Save to history for tracking", |
|
|
value=True |
|
|
) |
|
|
use_gemini = gr.Checkbox( |
|
|
label="π Use Gemini Vision (Bonus: Dual-engine detection)", |
|
|
value=False, |
|
|
info="Compare YOLOv8 + Google Gemini multimodal AI" |
|
|
) |
|
|
with gr.Row(): |
|
|
analyze_btn = gr.Button( |
|
|
"π Start AI Analysis", |
|
|
variant="primary", |
|
|
size="lg" |
|
|
) |
|
|
|
|
|
with gr.Column(scale=1): |
|
|
gr.Markdown("### π― Detection Results") |
|
|
output_image = gr.Image( |
|
|
type="pil", |
|
|
label="AI-Detected Trash (with bounding boxes)", |
|
|
height=400 |
|
|
) |
|
|
|
|
|
gr.Markdown("---") |
|
|
gr.Markdown("### π Step 2: Analysis Results") |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
gr.Markdown("#### π Items Detected") |
|
|
detection_output = gr.Markdown() |
|
|
|
|
|
with gr.Column(): |
|
|
gr.Markdown("#### π Cleanup Action Plan") |
|
|
plan_output = gr.Markdown() |
|
|
|
|
|
|
|
|
gr.Markdown("---") |
|
|
impact_output = gr.Markdown() |
|
|
|
|
|
gr.Markdown("---") |
|
|
gr.Markdown("### π€ Step 3: Automated Reporting & Integration") |
|
|
automation_output = gr.Markdown( |
|
|
value="""**Real-time automation status will appear here after analysis...** |
|
|
|
|
|
*CleanCity automatically integrates with your systems:* |
|
|
- π Database logging |
|
|
- π Analytics updates |
|
|
- π Crew notifications |
|
|
- πΌ Cost calculations |
|
|
""", |
|
|
label="Live Automation Dashboard" |
|
|
) |
|
|
|
|
|
|
|
|
gr.Markdown("---") |
|
|
gr.Markdown("### π Share Your Impact") |
|
|
gr.Markdown(""" |
|
|
Help spread awareness and inspire others to take action! Share your cleanup efforts on social media. |
|
|
""") |
|
|
|
|
|
with gr.Row(): |
|
|
share_twitter_btn = gr.Button("π¦ Share on Twitter/X", variant="secondary", size="lg") |
|
|
share_linkedin_btn = gr.Button("πΌ Share on LinkedIn", variant="secondary", size="lg") |
|
|
|
|
|
gr.HTML(""" |
|
|
<div id="share-buttons" style="display: none;"> |
|
|
<a id="twitter-share" target="_blank" style="margin-right: 10px;"></a> |
|
|
<a id="linkedin-share" target="_blank"></a> |
|
|
</div> |
|
|
<script> |
|
|
function shareOnTwitter() { |
|
|
const text = "π Just used CleanCity Agent AI to detect and plan cleanup for littered areas! Powered by @Gradio and computer vision. Join the movement for cleaner communities! #CleanCity #AI4Good #EnvironmentalAction"; |
|
|
const url = window.location.href; |
|
|
window.open(`https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}&url=${encodeURIComponent(url)}`, '_blank'); |
|
|
} |
|
|
function shareOnLinkedIn() { |
|
|
const url = window.location.href; |
|
|
window.open(`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(url)}`, '_blank'); |
|
|
} |
|
|
</script> |
|
|
""") |
|
|
|
|
|
|
|
|
share_twitter_btn.click( |
|
|
fn=None, |
|
|
inputs=[], |
|
|
outputs=[], |
|
|
js="() => { shareOnTwitter(); }" |
|
|
) |
|
|
|
|
|
share_linkedin_btn.click( |
|
|
fn=None, |
|
|
inputs=[], |
|
|
outputs=[], |
|
|
js="() => { shareOnLinkedIn(); }" |
|
|
) |
|
|
|
|
|
|
|
|
analyze_btn.click( |
|
|
fn=analyze_image, |
|
|
inputs=[image_input, location_input, notes_input, save_history, gps_coords, use_gemini], |
|
|
outputs=[output_image, detection_output, plan_output, automation_output, impact_output] |
|
|
) |
|
|
|
|
|
|
|
|
get_location_btn.click( |
|
|
fn=None, |
|
|
inputs=[], |
|
|
outputs=[location_input, gps_coords], |
|
|
js=""" |
|
|
async () => { |
|
|
try { |
|
|
const position = await new Promise((resolve, reject) => { |
|
|
navigator.geolocation.getCurrentPosition(resolve, reject, { |
|
|
enableHighAccuracy: true, |
|
|
timeout: 10000 |
|
|
}); |
|
|
}); |
|
|
|
|
|
const lat = position.coords.latitude.toFixed(6); |
|
|
const lon = position.coords.longitude.toFixed(6); |
|
|
const coords = lat + ', ' + lon; |
|
|
|
|
|
// Reverse geocode to get location name |
|
|
try { |
|
|
const response = await fetch( |
|
|
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lon}` |
|
|
); |
|
|
const data = await response.json(); |
|
|
const location = data.display_name || `Location at ${coords}`; |
|
|
return [location, coords]; |
|
|
} catch (e) { |
|
|
return [`Location at ${coords}`, coords]; |
|
|
} |
|
|
} catch (error) { |
|
|
alert('GPS Error: ' + error.message + '\\n\\nPlease enable location services in your browser.'); |
|
|
return ['', '']; |
|
|
} |
|
|
} |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with gr.Tab("π How It Works"): |
|
|
gr.Markdown(GUIDE_CONTENT) |
|
|
gr.Markdown("---") |
|
|
gr.Markdown(FAQ_CONTENT) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with gr.Tab("π Event History"): |
|
|
gr.Markdown(""" |
|
|
### π View Past Trash Detection Events |
|
|
|
|
|
Track all your saved trash detection events to identify patterns and monitor progress over time. |
|
|
Use filters to narrow down specific locations, timeframes, or severity levels. |
|
|
""") |
|
|
|
|
|
with gr.Row(): |
|
|
days_filter = gr.Slider( |
|
|
minimum=0, |
|
|
maximum=365, |
|
|
value=30, |
|
|
step=1, |
|
|
label="π
Time Range", |
|
|
info="Last N days (0 = all time)" |
|
|
) |
|
|
location_filter = gr.Textbox( |
|
|
label="π Location Filter", |
|
|
placeholder="e.g., Park, Beach, Street...", |
|
|
info="Partial match - finds all locations containing this text" |
|
|
) |
|
|
severity_filter = gr.Dropdown( |
|
|
choices=["All", "Low", "Medium", "High"], |
|
|
value="All", |
|
|
label="β οΈ Severity Level", |
|
|
info="Filter by cleanup urgency" |
|
|
) |
|
|
|
|
|
load_history_btn = gr.Button("π Load History", variant="primary", size="lg") |
|
|
history_output = gr.Markdown() |
|
|
|
|
|
load_history_btn.click( |
|
|
fn=load_history, |
|
|
inputs=[days_filter, location_filter, severity_filter], |
|
|
outputs=history_output |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with gr.Tab("π Impact & Examples"): |
|
|
gr.Markdown(""" |
|
|
### πΈ Example Use Cases |
|
|
|
|
|
See how CleanCity Agent can make a difference in your community! |
|
|
""") |
|
|
|
|
|
with gr.Tabs(): |
|
|
with gr.Tab("ποΈ Beach Cleanup"): |
|
|
gr.Markdown(""" |
|
|
#### Scenario: Beach Littered After Weekend |
|
|
|
|
|
**Problem:** Every Monday morning, the public beach is covered in trash from weekend visitors. |
|
|
|
|
|
**How CleanCity Helps:** |
|
|
1. πΈ Take photo on Monday morning |
|
|
2. π€ AI detects: 45 plastic bottles, 23 food wrappers, 12 cigarette butts |
|
|
3. π Severity: HIGH - requires 4-6 volunteers, 2 hours |
|
|
4. π§ Send report to Parks Department with data |
|
|
5. β
Result: City adds more trash bins and signage |
|
|
|
|
|
**Real Impact:** 60% reduction in Monday morning trash after 2 months |
|
|
""") |
|
|
|
|
|
with gr.Tab("ποΈ Park Maintenance"): |
|
|
gr.Markdown(""" |
|
|
#### Scenario: Playground Area Safety |
|
|
|
|
|
**Problem:** Broken glass and sharp objects near children's playground. |
|
|
|
|
|
**How CleanCity Helps:** |
|
|
1. πΈ Document with photos showing bounding boxes around dangerous items |
|
|
2. π Generate safety-focused report highlighting urgency |
|
|
3. π§ Email to city council with visual evidence |
|
|
4. π₯ Organize community cleanup with volunteer count estimate |
|
|
5. β
Result: City responds within 48 hours |
|
|
|
|
|
**Real Impact:** Safer playground + faster city response time |
|
|
""") |
|
|
|
|
|
with gr.Tab("ποΈ Street Advocacy"): |
|
|
gr.Markdown(""" |
|
|
#### Scenario: Downtown Business District |
|
|
|
|
|
**Problem:** Weekly trash accumulation hurting local businesses. |
|
|
|
|
|
**How CleanCity Helps:** |
|
|
1. π
Track events over 4 weeks at same locations |
|
|
2. π Build data showing pattern: "Main Street has 8 events in 30 days" |
|
|
3. π Show historical trends in Event History tab |
|
|
4. π§ Present data to Business Association meeting |
|
|
5. β
Result: City increases trash pickup frequency |
|
|
|
|
|
**Real Impact:** Cleaner streets + increased foot traffic + data-driven policy change |
|
|
""") |
|
|
|
|
|
with gr.Tab("π‘ Best Practices"): |
|
|
gr.Markdown(""" |
|
|
### π― Tips for Maximum Impact |
|
|
|
|
|
**For Better Photos:** |
|
|
- β
Take photos in daylight (9am-4pm best) |
|
|
- β
Get close enough to see individual items |
|
|
- β
Include landmarks for location context |
|
|
- β
Take before AND after cleanup photos |
|
|
|
|
|
**For Better Data:** |
|
|
- β
Always add specific location names |
|
|
- β
Be consistent with location spelling |
|
|
- β
Add notes about context (time of day, events nearby) |
|
|
- β
Save to history every time |
|
|
|
|
|
**For Better Advocacy:** |
|
|
- β
Collect 3-5 events before contacting authorities |
|
|
- β
Use professional email reports |
|
|
- β
Include photos with bounding boxes (shows AI verification) |
|
|
- β
Suggest specific solutions (more bins, signage, schedules) |
|
|
|
|
|
**For Community Organizing:** |
|
|
- β
Share resource estimates with volunteers upfront |
|
|
- β
Use cleanup plan for event planning |
|
|
- β
Document progress with before/after comparisons |
|
|
- β
Celebrate wins on social media with data |
|
|
|
|
|
--- |
|
|
|
|
|
### π Sample Report Template |
|
|
|
|
|
**Subject:** Request for Additional Trash Infrastructure - [Location] |
|
|
|
|
|
**Dear [Authority Name],** |
|
|
|
|
|
I am writing to bring attention to a recurring trash problem at [Location]. |
|
|
Using AI-powered detection tools, I have documented the following: |
|
|
|
|
|
- **Date of observation:** [Date] |
|
|
- **Items detected:** [X bottles, Y bags, Z wrappers] |
|
|
- **Severity:** [High/Medium/Low] |
|
|
- **Estimated cleanup effort:** [X volunteers, Y hours] |
|
|
|
|
|
[Include photo with AI bounding boxes] |
|
|
|
|
|
I respectfully request: |
|
|
1. [Specific solution - more bins, regular cleaning, etc.] |
|
|
2. [Timeline expectations] |
|
|
|
|
|
I am organizing a community cleanup on [Date] and would appreciate |
|
|
coordination with city services for disposal. |
|
|
|
|
|
Thank you for your attention to this matter. |
|
|
|
|
|
Sincerely, |
|
|
[Your Name] |
|
|
[Contact Information] |
|
|
""") |
|
|
|
|
|
gr.Markdown("---") |
|
|
gr.Markdown(""" |
|
|
### π Training Resources |
|
|
|
|
|
**Want to learn more about community environmental action?** |
|
|
|
|
|
- π [EPA Community Cleanup Guide](https://www.epa.gov/communities) |
|
|
- β»οΈ [Ocean Conservancy Cleanup Resources](https://oceanconservancy.org/) |
|
|
- ποΈ [Keep America Beautiful](https://kab.org/) |
|
|
- π₯ [Community Organizing Best Practices](https://www.communitychange.org/) |
|
|
|
|
|
*Start small, document everything, and watch your impact grow!* |
|
|
""") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with gr.Tab("π¬ Chat with Agent"): |
|
|
gr.Markdown(""" |
|
|
### π€ Ask Questions or Get Help |
|
|
|
|
|
Chat with the CleanCity AI Assistant to get personalized advice and answers about: |
|
|
- π§Ή Cleanup strategies and best practices |
|
|
- π Interpreting your detection results |
|
|
- π Environmental impact and regulations |
|
|
- π₯ Organizing community cleanup events |
|
|
- π§ How to communicate with authorities |
|
|
|
|
|
**Example questions:** |
|
|
- "How many volunteers do I need for 50 plastic bottles?" |
|
|
- "What's the best time of day to organize a beach cleanup?" |
|
|
- "How do I convince my city council to add more trash bins?" |
|
|
- "What equipment is essential for a park cleanup?" |
|
|
""") |
|
|
|
|
|
chatbot = gr.Chatbot( |
|
|
height=450, |
|
|
placeholder="π Hi! I'm your CleanCity AI Assistant. Ask me anything about trash cleanup and environmental action!", |
|
|
type="messages" |
|
|
) |
|
|
msg = gr.Textbox( |
|
|
label="Your message", |
|
|
placeholder="Type your question here... (e.g., 'How do I organize a cleanup event?')", |
|
|
lines=2 |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
submit = gr.Button("π¬ Send", variant="primary", scale=2) |
|
|
clear = gr.Button("ποΈ Clear Chat", scale=1) |
|
|
|
|
|
gr.Markdown(""" |
|
|
*π‘ Tip: The more specific your question, the better the advice!* |
|
|
""") |
|
|
|
|
|
def respond(message, chat_history): |
|
|
bot_response = chat_with_agent(message, chat_history) |
|
|
chat_history.append({"role": "user", "content": message}) |
|
|
chat_history.append({"role": "assistant", "content": bot_response}) |
|
|
return "", chat_history |
|
|
|
|
|
submit.click(respond, [msg, chatbot], [msg, chatbot]) |
|
|
msg.submit(respond, [msg, chatbot], [msg, chatbot]) |
|
|
clear.click(lambda: [], None, chatbot) |
|
|
|
|
|
|
|
|
gr.Markdown("---") |
|
|
gr.Markdown( |
|
|
"*CleanCity Agent is a prototype tool for community environmental action. " |
|
|
"Always verify AI results manually before taking action.*" |
|
|
) |
|
|
|
|
|
return app |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print("=" * 60) |
|
|
print("π CleanCity Agent - Initializing...") |
|
|
print("=" * 60) |
|
|
|
|
|
|
|
|
get_llm_client() |
|
|
|
|
|
print("\nβ Creating Gradio interface...") |
|
|
app = create_interface() |
|
|
print("β Gradio interface ready!") |
|
|
print("=" * 60 + "\n") |
|
|
|
|
|
|
|
|
def main(): |
|
|
"""Launch the Gradio application (local development).""" |
|
|
import os |
|
|
|
|
|
|
|
|
port = int(os.environ.get("GRADIO_SERVER_PORT", "7860")) |
|
|
|
|
|
print("π Launching web server...") |
|
|
print(f"Access the app at: http://localhost:{port}\n") |
|
|
|
|
|
app.launch( |
|
|
server_name="0.0.0.0", |
|
|
server_port=port, |
|
|
share=False, |
|
|
show_error=True, |
|
|
inbrowser=False, |
|
|
quiet=False |
|
|
) |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
main() |
|
|
|