pythonprincess commited on
Commit
f14688e
Β·
verified Β·
1 Parent(s): 6498306

Upload gradio_app.py

Browse files
Files changed (1) hide show
  1. gradio_app.py +509 -0
gradio_app.py ADDED
@@ -0,0 +1,509 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ πŸ€– PENNY V2.2 Gradio Interface
3
+ Hugging Face Space Entry Point
4
+
5
+ This file connects PENNY's backend to a Gradio chat interface,
6
+ allowing users to interact with PENNY through a web UI on Hugging Face Spaces.
7
+ """
8
+
9
+ import gradio as gr
10
+ import logging
11
+ import sys
12
+ import asyncio
13
+ import os
14
+ from dotenv import load_dotenv # Add this
15
+ from typing import List, Tuple, Dict, Any
16
+ from datetime import datetime
17
+
18
+ # Load environment variables from .env file
19
+ load_dotenv() # Add this line
20
+
21
+ # Verify the key loaded (optional debug)
22
+ if os.getenv("AZURE_MAPS_KEY"):
23
+ print("βœ… AZURE_MAPS_KEY loaded successfully")
24
+ else:
25
+ print("⚠️ AZURE_MAPS_KEY not found!")
26
+
27
+ from typing import List, Tuple, Dict, Any
28
+ from datetime import datetime
29
+
30
+ # Setup logging
31
+ logging.basicConfig(
32
+ level=logging.INFO,
33
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
34
+ handlers=[logging.StreamHandler(sys.stdout)]
35
+ )
36
+ logger = logging.getLogger(__name__)
37
+
38
+ # ============================================================
39
+ # IMPORT PENNY MODULES - FIXED FOR ACTUAL FILE STRUCTURE
40
+ # ============================================================
41
+
42
+ try:
43
+ # Core orchestration and routing
44
+ from app.orchestrator import run_orchestrator
45
+ # REMOVED: from app.router import route_query # Function doesn't exist
46
+
47
+ # Utilities
48
+ from app.location_utils import geocode_address, get_user_location
49
+ from app.logging_utils import setup_logger
50
+
51
+ # Event and weather handling
52
+ from app.event_weather import get_event_recommendations_with_weather # FIXED: was get_weather_info
53
+
54
+ # Tool agent for officials and resources
55
+ from app.tool_agent import handle_tool_request # FIXED: removed non-existent functions
56
+
57
+ # REMOVED: initialize_models doesn't exist in model_loader
58
+ # from app.model_loader import initialize_models
59
+
60
+ # Intent classification
61
+ from app.intents import classify_intent, IntentType
62
+
63
+ logger.info("βœ… Successfully imported PENNY modules from app/")
64
+
65
+ except ImportError as import_error:
66
+ logger.error(f"❌ Failed to import PENNY modules: {import_error}")
67
+ logger.error(f" Make sure all files exist in app/ folder")
68
+ logger.error(f" Current error: {str(import_error)}")
69
+
70
+ # Create fallback functions so the interface can still load
71
+ async def run_orchestrator(message: str, context: Dict[str, Any]) -> Dict[str, Any]:
72
+ return {
73
+ "reply": "⚠️ PENNY is initializing. Please try again in a moment.",
74
+ "intent": "error",
75
+ "confidence": 0.0
76
+ }
77
+
78
+ def get_service_availability() -> Dict[str, bool]:
79
+ return {
80
+ "orchestrator": False,
81
+ "weather_service": False,
82
+ "event_database": False,
83
+ "resource_finder": False
84
+ }
85
+
86
+ # ============================================================
87
+ # SERVICE AVAILABILITY CHECK
88
+ # ============================================================
89
+
90
+ def get_service_availability() -> Dict[str, bool]:
91
+ """
92
+ Check which PENNY services are available.
93
+ Returns dict of service_name -> availability status.
94
+ """
95
+ services = {}
96
+
97
+ try:
98
+ # Check if orchestrator is callable
99
+ services["orchestrator"] = callable(run_orchestrator)
100
+ except:
101
+ services["orchestrator"] = False
102
+
103
+ try:
104
+ # Check if event/weather module loaded
105
+ from app.event_weather import get_event_recommendations_with_weather # FIXED
106
+ services["weather_service"] = True
107
+ except:
108
+ services["weather_service"] = False
109
+
110
+ try:
111
+ # Check if event database accessible
112
+ from app.event_weather import get_event_recommendations_with_weather # FIXED
113
+ services["event_database"] = True
114
+ except:
115
+ services["event_database"] = False
116
+
117
+ try:
118
+ # Check if tool agent loaded
119
+ from app.tool_agent import handle_tool_request # FIXED: was search_resources
120
+ services["resource_finder"] = True
121
+ except:
122
+ services["resource_finder"] = False
123
+
124
+ return services
125
+
126
+
127
+ # ============================================================
128
+ # SUPPORTED CITIES CONFIGURATION
129
+ # ============================================================
130
+
131
+ SUPPORTED_CITIES = [
132
+ "Atlanta, GA",
133
+ "Birmingham, AL",
134
+ "Chesterfield, VA",
135
+ "El Paso, TX",
136
+ "Norfolk, VA",
137
+ "Providence, RI",
138
+ "Seattle, WA"
139
+ ]
140
+
141
+ def get_city_choices() -> List[str]:
142
+ """Get list of supported cities for dropdown."""
143
+ try:
144
+ return ["Not sure / Other"] + sorted(SUPPORTED_CITIES)
145
+ except Exception as e:
146
+ logger.error(f"Error loading cities: {e}")
147
+ return ["Not sure / Other", "Norfolk, VA"]
148
+
149
+
150
+ # ============================================================
151
+ # CHAT HANDLER
152
+ # ============================================================
153
+
154
+ async def chat_with_penny(
155
+ message: str,
156
+ city: str,
157
+ history: List[Tuple[str, str]]
158
+ ) -> Tuple[List[Tuple[str, str]], str]:
159
+ """
160
+ Process user message through PENNY's orchestrator and return response.
161
+
162
+ Args:
163
+ message: User's input text
164
+ city: Selected city/location
165
+ history: Chat history (list of (user_msg, bot_msg) tuples)
166
+
167
+ Returns:
168
+ Tuple of (updated_history, empty_string_to_clear_input)
169
+ """
170
+ if not message.strip():
171
+ return history, ""
172
+
173
+ try:
174
+ # Build context from selected city
175
+ context = {
176
+ "timestamp": datetime.now().isoformat(),
177
+ "conversation_history": history[-5:] if history else [] # Last 5 exchanges
178
+ }
179
+
180
+ # Add location if specified
181
+ if city and city != "Not sure / Other":
182
+ context["location"] = city
183
+ context["tenant_id"] = city.split(",")[0].lower().replace(" ", "_")
184
+
185
+ logger.info(f"πŸ“¨ Processing: '{message[:60]}...' | City: {city}")
186
+
187
+ # Call PENNY's orchestrator
188
+ result = await run_orchestrator(message, context)
189
+
190
+ # Handle both dict and OrchestrationResult objects
191
+ if hasattr(result, 'to_dict'):
192
+ result = result.to_dict()
193
+ elif not isinstance(result, dict):
194
+ # Fallback: try to access attributes directly
195
+ reply = getattr(result, 'reply', "I'm having trouble right now. Please try again! πŸ’›")
196
+ intent = getattr(result, 'intent', 'unknown')
197
+ confidence = getattr(result, 'confidence', 0.0)
198
+ history.append((message, reply))
199
+ logger.info(f"βœ… Response generated | Intent: {intent} | Confidence: {confidence:.2f}")
200
+ return history, ""
201
+
202
+ # Extract response from dictionary
203
+ reply = result.get("reply", "I'm having trouble right now. Please try again! πŸ’›")
204
+ intent = result.get("intent", "unknown")
205
+ confidence = result.get("confidence", 0.0)
206
+
207
+ # Add to history
208
+ history.append((message, reply))
209
+
210
+ logger.info(f"βœ… Response generated | Intent: {intent} | Confidence: {confidence:.2f}")
211
+
212
+ return history, ""
213
+
214
+ except Exception as e:
215
+ logger.error(f"❌ Error processing message: {e}", exc_info=True)
216
+
217
+ error_reply = (
218
+ "I'm having trouble processing your request right now. "
219
+ "Please try again in a moment! πŸ’›\n\n"
220
+ f"_Error: {str(e)[:100]}_"
221
+ )
222
+ history.append((message, error_reply))
223
+ return history, ""
224
+
225
+
226
+ def chat_with_penny_sync(message: str, city: str, history: List[Tuple[str, str]]) -> Tuple[List[Tuple[str, str]], str]:
227
+ """
228
+ Synchronous wrapper for chat_with_penny to work with Gradio.
229
+ Gradio expects sync functions, so we create an event loop here.
230
+ """
231
+ try:
232
+ # Create new event loop for this call
233
+ loop = asyncio.new_event_loop()
234
+ asyncio.set_event_loop(loop)
235
+ result = loop.run_until_complete(chat_with_penny(message, city, history))
236
+ loop.close()
237
+ return result
238
+ except Exception as e:
239
+ logger.error(f"Error in sync wrapper: {e}")
240
+ error_msg = f"Error: {str(e)}"
241
+ history.append((message, error_msg))
242
+ return history, ""
243
+
244
+
245
+ # ============================================================
246
+ # SERVICE STATUS DISPLAY
247
+ # ============================================================
248
+
249
+ def get_service_status() -> str:
250
+ """Display current service availability status."""
251
+ try:
252
+ services = get_service_availability()
253
+ status_lines = ["**πŸ”§ PENNY Service Status:**\n"]
254
+
255
+ service_names = {
256
+ "orchestrator": "🧠 Core Orchestrator",
257
+ "weather_service": "🌀️ Weather Service",
258
+ "event_database": "πŸ“… Event Database",
259
+ "resource_finder": "πŸ›οΈ Resource Finder"
260
+ }
261
+
262
+ for service_key, available in services.items():
263
+ icon = "βœ…" if available else "⚠️"
264
+ status = "Online" if available else "Limited"
265
+ name = service_names.get(service_key, service_key.replace('_', ' ').title())
266
+ status_lines.append(f"{icon} **{name}**: {status}")
267
+
268
+ return "\n".join(status_lines)
269
+ except Exception as e:
270
+ logger.error(f"Error getting service status: {e}")
271
+ return "**⚠️ Status:** Unable to check service availability"
272
+
273
+
274
+ # ============================================================
275
+ # GRADIO UI DEFINITION
276
+ # ============================================================
277
+
278
+ # Custom CSS for enhanced styling
279
+ custom_css = """
280
+ #chatbot {
281
+ height: 500px;
282
+ overflow-y: auto;
283
+ border-radius: 8px;
284
+ }
285
+ .gradio-container {
286
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
287
+ }
288
+ #status-panel {
289
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
290
+ padding: 15px;
291
+ border-radius: 8px;
292
+ margin: 10px 0;
293
+ }
294
+ footer {
295
+ display: none !important;
296
+ }
297
+ .message-user {
298
+ background-color: #e3f2fd !important;
299
+ }
300
+ .message-bot {
301
+ background-color: #fff3e0 !important;
302
+ }
303
+ """
304
+
305
+ # Build the Gradio interface
306
+ with gr.Blocks(
307
+ theme=gr.themes.Soft(primary_hue="amber", secondary_hue="blue"),
308
+ css=custom_css,
309
+ title="PENNY V2.2 - Civic Assistant"
310
+ ) as demo:
311
+
312
+ # Header
313
+ gr.Markdown(
314
+ """
315
+ # πŸ€– PENNY V2.2 - People's Engagement Network Navigator for You
316
+
317
+ **Your multilingual civic assistant connecting residents to local government services and community resources.**
318
+
319
+ ### πŸ’¬ Ask me about:
320
+ - 🌀️ **Weather conditions** and forecasts
321
+ - πŸ“… **Community events** and activities
322
+ - πŸ›οΈ **Local resources** (shelters, libraries, food banks, healthcare)
323
+ - πŸ‘₯ **Elected officials** and government contacts
324
+ - 🌍 **Translation** services (27+ languages)
325
+ - πŸ“„ **Document assistance** and form help
326
+ """
327
+ )
328
+
329
+ with gr.Row():
330
+ with gr.Column(scale=2):
331
+ # City selector
332
+ city_dropdown = gr.Dropdown(
333
+ choices=get_city_choices(),
334
+ value="Norfolk, VA",
335
+ label="πŸ“ Select Your City",
336
+ info="Choose your city for location-specific information",
337
+ interactive=True
338
+ )
339
+
340
+ # Chat interface
341
+ chatbot = gr.Chatbot(
342
+ label="πŸ’¬ Chat with PENNY",
343
+ elem_id="chatbot",
344
+ avatar_images=(None, "πŸ€–"),
345
+ show_label=True,
346
+ height=500,
347
+ bubble_full_width=False
348
+ )
349
+
350
+ # Input row
351
+ with gr.Row():
352
+ msg_input = gr.Textbox(
353
+ placeholder="Type your message here... (e.g., 'What's the weather today?')",
354
+ show_label=False,
355
+ scale=4,
356
+ container=False,
357
+ lines=1
358
+ )
359
+ submit_btn = gr.Button("Send πŸ“€", variant="primary", scale=1)
360
+
361
+ # Clear button
362
+ clear_btn = gr.Button("πŸ—‘οΈ Clear Chat", variant="secondary", size="sm")
363
+
364
+ # Example queries
365
+ gr.Examples(
366
+ examples=[
367
+ ["What's the weather in Norfolk today?"],
368
+ ["Any community events this weekend?"],
369
+ ["I need help finding a food bank"],
370
+ ["Who is my city council representative?"],
371
+ ["Show me local libraries"],
372
+ ["Translate 'hello' to Spanish"],
373
+ ["Help me understand this document"]
374
+ ],
375
+ inputs=msg_input,
376
+ label="πŸ’‘ Try asking:"
377
+ )
378
+
379
+ with gr.Column(scale=1):
380
+ # Service status panel
381
+ status_display = gr.Markdown(
382
+ value=get_service_status(),
383
+ label="System Status",
384
+ elem_id="status-panel"
385
+ )
386
+
387
+ # Refresh status button
388
+ refresh_btn = gr.Button("πŸ”„ Refresh Status", size="sm", variant="secondary")
389
+
390
+ gr.Markdown(
391
+ """
392
+ ### 🌟 Key Features
393
+
394
+ - βœ… **27+ Languages** supported
395
+ - βœ… **Real-time weather** via Azure Maps
396
+ - βœ… **Community events** database
397
+ - βœ… **Local resource** finder
398
+ - βœ… **Government contact** lookup
399
+ - βœ… **Document processing** help
400
+ - βœ… **Multilingual** support
401
+
402
+ ---
403
+
404
+ ### πŸ“ Supported Cities
405
+
406
+ - Atlanta, GA
407
+ - Birmingham, AL
408
+ - Chesterfield, VA
409
+ - El Paso, TX
410
+ - Norfolk, VA
411
+ - Providence, RI
412
+ - Seattle, WA
413
+
414
+ ---
415
+
416
+ ### πŸ†˜ Need Help?
417
+
418
+ PENNY can assist with:
419
+ - Finding emergency services
420
+ - Locating government offices
421
+ - Understanding civic processes
422
+ - Accessing community programs
423
+
424
+ ---
425
+
426
+ πŸ’› *PENNY is here to help connect you with civic resources!*
427
+ """
428
+ )
429
+
430
+ # Event handlers
431
+ submit_btn.click(
432
+ fn=chat_with_penny_sync,
433
+ inputs=[msg_input, city_dropdown, chatbot],
434
+ outputs=[chatbot, msg_input]
435
+ )
436
+
437
+ msg_input.submit(
438
+ fn=chat_with_penny_sync,
439
+ inputs=[msg_input, city_dropdown, chatbot],
440
+ outputs=[chatbot, msg_input]
441
+ )
442
+
443
+ clear_btn.click(
444
+ fn=lambda: ([], ""),
445
+ inputs=None,
446
+ outputs=[chatbot, msg_input]
447
+ )
448
+
449
+ refresh_btn.click(
450
+ fn=get_service_status,
451
+ inputs=None,
452
+ outputs=status_display
453
+ )
454
+
455
+ # Footer
456
+ gr.Markdown(
457
+ """
458
+ ---
459
+ **Built with:** Python β€’ FastAPI β€’ Gradio β€’ Azure ML β€’ Hugging Face Transformers
460
+
461
+ **Version:** 2.2 | **Last Updated:** November 2025
462
+
463
+ _PENNY is an open-source civic engagement platform designed to improve access to government services._
464
+ """
465
+ )
466
+
467
+
468
+ # ============================================================
469
+ # INITIALIZATION AND LAUNCH
470
+ # ============================================================
471
+
472
+ def initialize_penny():
473
+ """Initialize PENNY services at startup."""
474
+ logger.info("=" * 70)
475
+ logger.info("πŸš€ Initializing PENNY V2.2 Gradio Interface")
476
+ logger.info("=" * 70)
477
+
478
+ # Display service availability at startup
479
+ logger.info("\nπŸ“Š Service Availability Check:")
480
+ services = get_service_availability()
481
+
482
+ all_available = True
483
+ for service, available in services.items():
484
+ status = "βœ… Available" if available else "❌ Not loaded"
485
+ logger.info(f" {service.ljust(20)}: {status}")
486
+ if not available:
487
+ all_available = False
488
+
489
+ if all_available:
490
+ logger.info("\nβœ… All services loaded successfully!")
491
+ else:
492
+ logger.warning("\n⚠️ Some services are not available. PENNY will run with limited functionality.")
493
+
494
+ logger.info("\n" + "=" * 70)
495
+ logger.info("πŸ€– PENNY is ready to help residents!")
496
+ logger.info("=" * 70 + "\n")
497
+
498
+
499
+ if __name__ == "__main__":
500
+ # Initialize services
501
+ initialize_penny()
502
+
503
+ # Launch the Gradio app
504
+ demo.launch(
505
+ server_name="0.0.0.0",
506
+ server_port=7860,
507
+ share=False,
508
+ show_error=True
509
+ )