pythonprincess commited on
Commit
0f78395
Β·
verified Β·
1 Parent(s): c82ee1b

Upload gradio_app.py

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