pythonprincess commited on
Commit
3be012e
Β·
verified Β·
1 Parent(s): 24b9dc5

Upload gradio_app.py

Browse files
Files changed (1) hide show
  1. gradio_app.py +497 -0
gradio_app.py ADDED
@@ -0,0 +1,497 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ # Extract response
191
+ reply = result.get("reply", "I'm having trouble right now. Please try again! πŸ’›")
192
+ intent = result.get("intent", "unknown")
193
+ confidence = result.get("confidence", 0.0)
194
+
195
+ # Add to history
196
+ history.append((message, reply))
197
+
198
+ logger.info(f"βœ… Response generated | Intent: {intent} | Confidence: {confidence:.2f}")
199
+
200
+ return history, ""
201
+
202
+ except Exception as e:
203
+ logger.error(f"❌ Error processing message: {e}", exc_info=True)
204
+
205
+ error_reply = (
206
+ "I'm having trouble processing your request right now. "
207
+ "Please try again in a moment! πŸ’›\n\n"
208
+ f"_Error: {str(e)[:100]}_"
209
+ )
210
+ history.append((message, error_reply))
211
+ return history, ""
212
+
213
+
214
+ def chat_with_penny_sync(message: str, city: str, history: List[Tuple[str, str]]) -> Tuple[List[Tuple[str, str]], str]:
215
+ """
216
+ Synchronous wrapper for chat_with_penny to work with Gradio.
217
+ Gradio expects sync functions, so we create an event loop here.
218
+ """
219
+ try:
220
+ # Create new event loop for this call
221
+ loop = asyncio.new_event_loop()
222
+ asyncio.set_event_loop(loop)
223
+ result = loop.run_until_complete(chat_with_penny(message, city, history))
224
+ loop.close()
225
+ return result
226
+ except Exception as e:
227
+ logger.error(f"Error in sync wrapper: {e}")
228
+ error_msg = f"Error: {str(e)}"
229
+ history.append((message, error_msg))
230
+ return history, ""
231
+
232
+
233
+ # ============================================================
234
+ # SERVICE STATUS DISPLAY
235
+ # ============================================================
236
+
237
+ def get_service_status() -> str:
238
+ """Display current service availability status."""
239
+ try:
240
+ services = get_service_availability()
241
+ status_lines = ["**πŸ”§ PENNY Service Status:**\n"]
242
+
243
+ service_names = {
244
+ "orchestrator": "🧠 Core Orchestrator",
245
+ "weather_service": "🌀️ Weather Service",
246
+ "event_database": "πŸ“… Event Database",
247
+ "resource_finder": "πŸ›οΈ Resource Finder"
248
+ }
249
+
250
+ for service_key, available in services.items():
251
+ icon = "βœ…" if available else "⚠️"
252
+ status = "Online" if available else "Limited"
253
+ name = service_names.get(service_key, service_key.replace('_', ' ').title())
254
+ status_lines.append(f"{icon} **{name}**: {status}")
255
+
256
+ return "\n".join(status_lines)
257
+ except Exception as e:
258
+ logger.error(f"Error getting service status: {e}")
259
+ return "**⚠️ Status:** Unable to check service availability"
260
+
261
+
262
+ # ============================================================
263
+ # GRADIO UI DEFINITION
264
+ # ============================================================
265
+
266
+ # Custom CSS for enhanced styling
267
+ custom_css = """
268
+ #chatbot {
269
+ height: 500px;
270
+ overflow-y: auto;
271
+ border-radius: 8px;
272
+ }
273
+ .gradio-container {
274
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
275
+ }
276
+ #status-panel {
277
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
278
+ padding: 15px;
279
+ border-radius: 8px;
280
+ margin: 10px 0;
281
+ }
282
+ footer {
283
+ display: none !important;
284
+ }
285
+ .message-user {
286
+ background-color: #e3f2fd !important;
287
+ }
288
+ .message-bot {
289
+ background-color: #fff3e0 !important;
290
+ }
291
+ """
292
+
293
+ # Build the Gradio interface
294
+ with gr.Blocks(
295
+ theme=gr.themes.Soft(primary_hue="amber", secondary_hue="blue"),
296
+ css=custom_css,
297
+ title="PENNY V2.2 - Civic Assistant"
298
+ ) as demo:
299
+
300
+ # Header
301
+ gr.Markdown(
302
+ """
303
+ # πŸ€– PENNY V2.2 - People's Engagement Network Navigator for You
304
+
305
+ **Your multilingual civic assistant connecting residents to local government services and community resources.**
306
+
307
+ ### πŸ’¬ Ask me about:
308
+ - 🌀️ **Weather conditions** and forecasts
309
+ - πŸ“… **Community events** and activities
310
+ - πŸ›οΈ **Local resources** (shelters, libraries, food banks, healthcare)
311
+ - πŸ‘₯ **Elected officials** and government contacts
312
+ - 🌍 **Translation** services (27+ languages)
313
+ - πŸ“„ **Document assistance** and form help
314
+ """
315
+ )
316
+
317
+ with gr.Row():
318
+ with gr.Column(scale=2):
319
+ # City selector
320
+ city_dropdown = gr.Dropdown(
321
+ choices=get_city_choices(),
322
+ value="Norfolk, VA",
323
+ label="πŸ“ Select Your City",
324
+ info="Choose your city for location-specific information",
325
+ interactive=True
326
+ )
327
+
328
+ # Chat interface
329
+ chatbot = gr.Chatbot(
330
+ label="πŸ’¬ Chat with PENNY",
331
+ elem_id="chatbot",
332
+ avatar_images=(None, "πŸ€–"),
333
+ show_label=True,
334
+ height=500,
335
+ bubble_full_width=False
336
+ )
337
+
338
+ # Input row
339
+ with gr.Row():
340
+ msg_input = gr.Textbox(
341
+ placeholder="Type your message here... (e.g., 'What's the weather today?')",
342
+ show_label=False,
343
+ scale=4,
344
+ container=False,
345
+ lines=1
346
+ )
347
+ submit_btn = gr.Button("Send πŸ“€", variant="primary", scale=1)
348
+
349
+ # Clear button
350
+ clear_btn = gr.Button("πŸ—‘οΈ Clear Chat", variant="secondary", size="sm")
351
+
352
+ # Example queries
353
+ gr.Examples(
354
+ examples=[
355
+ ["What's the weather in Norfolk today?"],
356
+ ["Any community events this weekend?"],
357
+ ["I need help finding a food bank"],
358
+ ["Who is my city council representative?"],
359
+ ["Show me local libraries"],
360
+ ["Translate 'hello' to Spanish"],
361
+ ["Help me understand this document"]
362
+ ],
363
+ inputs=msg_input,
364
+ label="πŸ’‘ Try asking:"
365
+ )
366
+
367
+ with gr.Column(scale=1):
368
+ # Service status panel
369
+ status_display = gr.Markdown(
370
+ value=get_service_status(),
371
+ label="System Status",
372
+ elem_id="status-panel"
373
+ )
374
+
375
+ # Refresh status button
376
+ refresh_btn = gr.Button("πŸ”„ Refresh Status", size="sm", variant="secondary")
377
+
378
+ gr.Markdown(
379
+ """
380
+ ### 🌟 Key Features
381
+
382
+ - βœ… **27+ Languages** supported
383
+ - βœ… **Real-time weather** via Azure Maps
384
+ - βœ… **Community events** database
385
+ - βœ… **Local resource** finder
386
+ - βœ… **Government contact** lookup
387
+ - βœ… **Document processing** help
388
+ - βœ… **Multilingual** support
389
+
390
+ ---
391
+
392
+ ### πŸ“ Supported Cities
393
+
394
+ - Atlanta, GA
395
+ - Birmingham, AL
396
+ - Chesterfield, VA
397
+ - El Paso, TX
398
+ - Norfolk, VA
399
+ - Providence, RI
400
+ - Seattle, WA
401
+
402
+ ---
403
+
404
+ ### πŸ†˜ Need Help?
405
+
406
+ PENNY can assist with:
407
+ - Finding emergency services
408
+ - Locating government offices
409
+ - Understanding civic processes
410
+ - Accessing community programs
411
+
412
+ ---
413
+
414
+ πŸ’› *PENNY is here to help connect you with civic resources!*
415
+ """
416
+ )
417
+
418
+ # Event handlers
419
+ submit_btn.click(
420
+ fn=chat_with_penny_sync,
421
+ inputs=[msg_input, city_dropdown, chatbot],
422
+ outputs=[chatbot, msg_input]
423
+ )
424
+
425
+ msg_input.submit(
426
+ fn=chat_with_penny_sync,
427
+ inputs=[msg_input, city_dropdown, chatbot],
428
+ outputs=[chatbot, msg_input]
429
+ )
430
+
431
+ clear_btn.click(
432
+ fn=lambda: ([], ""),
433
+ inputs=None,
434
+ outputs=[chatbot, msg_input]
435
+ )
436
+
437
+ refresh_btn.click(
438
+ fn=get_service_status,
439
+ inputs=None,
440
+ outputs=status_display
441
+ )
442
+
443
+ # Footer
444
+ gr.Markdown(
445
+ """
446
+ ---
447
+ **Built with:** Python β€’ FastAPI β€’ Gradio β€’ Azure ML β€’ Hugging Face Transformers
448
+
449
+ **Version:** 2.2 | **Last Updated:** November 2025
450
+
451
+ _PENNY is an open-source civic engagement platform designed to improve access to government services._
452
+ """
453
+ )
454
+
455
+
456
+ # ============================================================
457
+ # INITIALIZATION AND LAUNCH
458
+ # ============================================================
459
+
460
+ def initialize_penny():
461
+ """Initialize PENNY services at startup."""
462
+ logger.info("=" * 70)
463
+ logger.info("πŸš€ Initializing PENNY V2.2 Gradio Interface")
464
+ logger.info("=" * 70)
465
+
466
+ # Display service availability at startup
467
+ logger.info("\nπŸ“Š Service Availability Check:")
468
+ services = get_service_availability()
469
+
470
+ all_available = True
471
+ for service, available in services.items():
472
+ status = "βœ… Available" if available else "❌ Not loaded"
473
+ logger.info(f" {service.ljust(20)}: {status}")
474
+ if not available:
475
+ all_available = False
476
+
477
+ if all_available:
478
+ logger.info("\nβœ… All services loaded successfully!")
479
+ else:
480
+ logger.warning("\n⚠️ Some services are not available. PENNY will run with limited functionality.")
481
+
482
+ logger.info("\n" + "=" * 70)
483
+ logger.info("πŸ€– PENNY is ready to help residents!")
484
+ logger.info("=" * 70 + "\n")
485
+
486
+
487
+ if __name__ == "__main__":
488
+ # Initialize services
489
+ initialize_penny()
490
+
491
+ # Launch the Gradio app
492
+ demo.launch(
493
+ server_name="0.0.0.0",
494
+ server_port=7860,
495
+ share=False,
496
+ show_error=True
497
+ )