jjmandog commited on
Commit
2662b83
Β·
verified Β·
1 Parent(s): 5dc0181

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +296 -530
app.py CHANGED
@@ -1,34 +1,24 @@
1
  #!/usr/bin/env python3
2
  """
3
- πŸš— JAY'S MOBILE WASH - COMPLETE 3-FILE AI SYSTEM
4
- iPhone Forwarding + AI Assistant + Dashboard - ALL IN ONE!
5
  """
6
 
7
  import os
8
  import json
9
  import requests
10
  import logging
11
- from datetime import datetime, timedelta
12
- from flask import Flask, request, jsonify, render_template_string
13
- from flask_cors import CORS
14
- from flask_socketio import SocketIO, emit
15
  import threading
16
- from threading import Lock
17
- from collections import defaultdict
18
  import time
 
 
 
 
19
 
20
  # Load environment variables
21
  from dotenv import load_dotenv
22
  load_dotenv()
23
 
24
- # Initialize Flask app
25
- app = Flask(__name__)
26
- app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'jays-mobile-wash-2025')
27
-
28
- # Enable CORS and SocketIO
29
- CORS(app)
30
- socketio = SocketIO(app, cors_allowed_origins="*")
31
-
32
  # Configuration
33
  SIGNALWIRE_PROJECT_ID = os.environ.get('SIGNALWIRE_PROJECT_ID', '')
34
  SIGNALWIRE_AUTH_TOKEN = os.environ.get('SIGNALWIRE_AUTH_TOKEN', '')
@@ -69,9 +59,11 @@ try:
69
  )
70
  print("βœ… SignalWire initialized")
71
  else:
72
- print("⚠️ SignalWire credentials not found")
73
  except ImportError:
74
  print("⚠️ SignalWire library not available")
 
 
75
 
76
  # Logging setup
77
  logging.basicConfig(level=logging.INFO)
@@ -90,7 +82,9 @@ class SystemState:
90
  'active_calls': {},
91
  'active_sms': {},
92
  'start_time': datetime.now(),
93
- 'status': 'healthy'
 
 
94
  }
95
 
96
  def get(self, key):
@@ -108,6 +102,18 @@ class SystemState:
108
  def get_all(self):
109
  with self._lock:
110
  return self._data.copy()
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
  # Initialize system state
113
  system_state = SystemState()
@@ -152,11 +158,6 @@ class SimpleAI:
152
  def generate_response(self, user_input, context=None):
153
  """Generate AI response"""
154
 
155
- # Check cache first
156
- cache_key = f"{user_input[:50]}_{context.get('forwarded', False) if context else False}"
157
- if cache_key in self.response_cache:
158
- return self.response_cache[cache_key]
159
-
160
  intent = self.detect_intent(user_input)
161
  sentiment = self.analyze_sentiment(user_input)
162
 
@@ -196,8 +197,6 @@ class SimpleAI:
196
  except Exception as e:
197
  logger.warning(f"DeepSeek failed: {e}")
198
 
199
- # Cache and return
200
- self.response_cache[cache_key] = response
201
  return response
202
 
203
  def get_deepseek_response(self, prompt, context=None):
@@ -247,386 +246,14 @@ BUSINESS INFO:
247
  # Initialize AI processor
248
  ai = SimpleAI()
249
 
250
- # ===== INLINE HTML TEMPLATE =====
251
- DASHBOARD_HTML = """
252
- <!DOCTYPE html>
253
- <html lang="en">
254
- <head>
255
- <meta charset="UTF-8">
256
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
257
- <title>{{ business_name }} - AI Dashboard</title>
258
- <style>
259
- * { margin: 0; padding: 0; box-sizing: border-box; }
260
- body {
261
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
262
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
263
- color: white;
264
- min-height: 100vh;
265
- overflow-x: hidden;
266
- }
267
- .container { max-width: 1400px; margin: 0 auto; padding: 20px; }
268
- .header {
269
- text-align: center;
270
- margin-bottom: 40px;
271
- background: rgba(255,255,255,0.1);
272
- padding: 30px;
273
- border-radius: 20px;
274
- backdrop-filter: blur(10px);
275
- }
276
- .header h1 {
277
- color: #fff;
278
- font-size: 3em;
279
- text-shadow: 0 0 30px rgba(255,255,255,0.5);
280
- margin-bottom: 10px;
281
- }
282
- .header p {
283
- font-size: 1.2em;
284
- opacity: 0.9;
285
- color: #e0e0e0;
286
- }
287
- .stats-grid {
288
- display: grid;
289
- grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
290
- gap: 25px;
291
- margin-bottom: 40px;
292
- }
293
- .stat-card {
294
- background: linear-gradient(145deg, rgba(255,255,255,0.15), rgba(255,255,255,0.05));
295
- border: 1px solid rgba(255,255,255,0.2);
296
- border-radius: 20px;
297
- padding: 30px;
298
- text-align: center;
299
- backdrop-filter: blur(15px);
300
- transition: all 0.3s ease;
301
- position: relative;
302
- overflow: hidden;
303
- }
304
- .stat-card:hover {
305
- transform: translateY(-5px);
306
- box-shadow: 0 15px 35px rgba(0,0,0,0.3);
307
- }
308
- .stat-card::before {
309
- content: '';
310
- position: absolute;
311
- top: 0;
312
- left: -100%;
313
- width: 100%;
314
- height: 100%;
315
- background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
316
- transition: left 0.5s;
317
- }
318
- .stat-card:hover::before {
319
- left: 100%;
320
- }
321
- .stat-number {
322
- font-size: 2.5em;
323
- font-weight: bold;
324
- color: #4facfe;
325
- margin-bottom: 10px;
326
- text-shadow: 0 0 20px rgba(79, 172, 254, 0.5);
327
- }
328
- .stat-label {
329
- color: #ffffff;
330
- font-size: 1.1em;
331
- opacity: 0.9;
332
- }
333
- .status-indicator {
334
- display: inline-block;
335
- width: 14px;
336
- height: 14px;
337
- border-radius: 50%;
338
- margin-right: 10px;
339
- position: relative;
340
- }
341
- .status-healthy {
342
- background: #00ff88;
343
- animation: pulse 2s infinite;
344
- }
345
- .status-warning { background: #ffaa00; }
346
- .status-error { background: #ff4757; }
347
- .config-section {
348
- background: linear-gradient(145deg, rgba(255,255,255,0.1), rgba(255,255,255,0.05));
349
- border: 1px solid rgba(255,255,255,0.15);
350
- border-radius: 15px;
351
- padding: 30px;
352
- margin-bottom: 30px;
353
- backdrop-filter: blur(10px);
354
- }
355
- .config-section h3 {
356
- color: #4facfe;
357
- margin-bottom: 20px;
358
- font-size: 1.4em;
359
- display: flex;
360
- align-items: center;
361
- }
362
- .config-item {
363
- display: flex;
364
- justify-content: space-between;
365
- margin: 15px 0;
366
- padding: 10px 0;
367
- border-bottom: 1px solid rgba(255,255,255,0.1);
368
- }
369
- .config-item:last-child {
370
- border-bottom: none;
371
- }
372
- .services-grid {
373
- display: grid;
374
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
375
- gap: 20px;
376
- }
377
- .service-card {
378
- background: linear-gradient(145deg, rgba(79, 172, 254, 0.2), rgba(79, 172, 254, 0.1));
379
- border: 1px solid rgba(79, 172, 254, 0.3);
380
- border-radius: 15px;
381
- padding: 20px;
382
- text-align: center;
383
- transition: all 0.3s ease;
384
- }
385
- .service-card:hover {
386
- transform: scale(1.05);
387
- box-shadow: 0 10px 25px rgba(79, 172, 254, 0.3);
388
- }
389
- .service-name {
390
- font-weight: bold;
391
- margin-bottom: 10px;
392
- color: #ffffff;
393
- }
394
- .service-description {
395
- font-size: 0.9em;
396
- opacity: 0.8;
397
- margin-bottom: 15px;
398
- color: #e0e0e0;
399
- }
400
- .price {
401
- font-size: 1.8em;
402
- font-weight: bold;
403
- color: #00ff88;
404
- text-shadow: 0 0 15px rgba(0, 255, 136, 0.5);
405
- }
406
- .live-indicator {
407
- background: #ff4757;
408
- color: white;
409
- padding: 5px 15px;
410
- border-radius: 20px;
411
- font-size: 0.8em;
412
- animation: blink 1.5s infinite;
413
- margin-left: 10px;
414
- }
415
- .footer {
416
- text-align: center;
417
- margin-top: 50px;
418
- padding: 20px;
419
- color: rgba(255,255,255,0.7);
420
- border-top: 1px solid rgba(255,255,255,0.1);
421
- }
422
- @keyframes pulse {
423
- 0% {
424
- box-shadow: 0 0 0 0 rgba(0, 255, 136, 0.7);
425
- transform: scale(1);
426
- }
427
- 70% {
428
- box-shadow: 0 0 0 10px rgba(0, 255, 136, 0);
429
- transform: scale(1.1);
430
- }
431
- 100% {
432
- box-shadow: 0 0 0 0 rgba(0, 255, 136, 0);
433
- transform: scale(1);
434
- }
435
- }
436
- @keyframes blink {
437
- 0%, 50% { opacity: 1; }
438
- 51%, 100% { opacity: 0.3; }
439
- }
440
- .emoji { font-size: 1.2em; margin-right: 10px; }
441
- .webhook-info {
442
- background: rgba(255, 193, 7, 0.1);
443
- border: 1px solid rgba(255, 193, 7, 0.3);
444
- border-radius: 10px;
445
- padding: 15px;
446
- margin-top: 20px;
447
- }
448
- .webhook-url {
449
- font-family: 'Courier New', monospace;
450
- background: rgba(0,0,0,0.3);
451
- padding: 8px 12px;
452
- border-radius: 5px;
453
- color: #4facfe;
454
- word-break: break-all;
455
- }
456
- </style>
457
- <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
458
- </head>
459
- <body>
460
- <div class="container">
461
- <div class="header">
462
- <h1>πŸš— {{ business_name }}</h1>
463
- <p>iPhone Forwarding AI Assistant System</p>
464
- <span class="live-indicator">πŸ”΄ LIVE</span>
465
- </div>
466
-
467
- <div class="stats-grid">
468
- <div class="stat-card">
469
- <div class="stat-number" id="calls-today">{{ stats.calls_today }}</div>
470
- <div class="stat-label"><span class="emoji">πŸ“ž</span>Calls Today</div>
471
- </div>
472
- <div class="stat-card">
473
- <div class="stat-number" id="sms-today">{{ stats.sms_today }}</div>
474
- <div class="stat-label"><span class="emoji">πŸ“±</span>SMS Today</div>
475
- </div>
476
- <div class="stat-card">
477
- <div class="stat-number" id="calls-forwarded">{{ stats.calls_forwarded }}</div>
478
- <div class="stat-label"><span class="emoji">πŸ”„</span>Calls Forwarded</div>
479
- </div>
480
- <div class="stat-card">
481
- <div class="stat-number" id="ai-responses">{{ stats.ai_responses }}</div>
482
- <div class="stat-label"><span class="emoji">πŸ€–</span>AI Responses</div>
483
- </div>
484
- <div class="stat-card">
485
- <div class="stat-number" id="revenue-today">${{ "%.2f"|format(stats.revenue_today) }}</div>
486
- <div class="stat-label"><span class="emoji">πŸ’°</span>Revenue Today</div>
487
- </div>
488
- <div class="stat-card">
489
- <div class="stat-number" id="uptime">{{ stats.uptime_hours }}h</div>
490
- <div class="stat-label"><span class="emoji">⏱️</span>System Uptime</div>
491
- </div>
492
- </div>
493
-
494
- <div class="config-section">
495
- <h3><span class="emoji">πŸ“ž</span>iPhone Forwarding Configuration</h3>
496
- <div class="config-item">
497
- <span><strong>Jay's iPhone:</strong></span>
498
- <span>{{ jay_phone }}</span>
499
- </div>
500
- <div class="config-item">
501
- <span><strong>AI Assistant Number:</strong></span>
502
- <span>{{ ai_phone }}</span>
503
- </div>
504
- <div class="config-item">
505
- <span><strong>Forwarding Status:</strong></span>
506
- <span>
507
- <span class="status-indicator {{ 'status-healthy' if forwarding_enabled else 'status-error' }}"></span>
508
- {{ 'Enabled' if forwarding_enabled else 'Disabled' }}
509
- </span>
510
- </div>
511
- <div class="config-item">
512
- <span><strong>Forwarding Delay:</strong></span>
513
- <span>{{ forwarding_delay }} seconds</span>
514
- </div>
515
- <div class="webhook-info">
516
- <strong>πŸ“‘ SignalWire Webhook URLs:</strong><br>
517
- <div style="margin-top: 10px;">
518
- <strong>Voice Webhook:</strong><br>
519
- <div class="webhook-url">{{ request.url_root }}voice/incoming</div>
520
- </div>
521
- <div style="margin-top: 10px;">
522
- <strong>SMS Webhook:</strong><br>
523
- <div class="webhook-url">{{ request.url_root }}sms/incoming</div>
524
- </div>
525
- </div>
526
- </div>
527
-
528
- <div class="config-section">
529
- <h3><span class="emoji">πŸ”§</span>System Status</h3>
530
- <div class="config-item">
531
- <span><strong>SignalWire:</strong></span>
532
- <span>
533
- <span class="status-indicator {{ 'status-healthy' if signalwire_configured else 'status-error' }}"></span>
534
- {{ 'Connected' if signalwire_configured else 'Not Configured' }}
535
- </span>
536
- </div>
537
- <div class="config-item">
538
- <span><strong>DeepSeek AI:</strong></span>
539
- <span>
540
- <span class="status-indicator {{ 'status-healthy' if deepseek_configured else 'status-warning' }}"></span>
541
- {{ 'Connected' if deepseek_configured else 'Fallback Mode' }}
542
- </span>
543
- </div>
544
- <div class="config-item">
545
- <span><strong>Active Calls:</strong></span>
546
- <span id="active-calls">{{ stats.active_calls }}</span>
547
- </div>
548
- <div class="config-item">
549
- <span><strong>Active SMS:</strong></span>
550
- <span id="active-sms">{{ stats.active_sms }}</span>
551
- </div>
552
- </div>
553
-
554
- <div class="config-section">
555
- <h3><span class="emoji">πŸ’Ό</span>Services & Pricing</h3>
556
- <div class="services-grid">
557
- {% for service_id, service in services.items() %}
558
- <div class="service-card">
559
- <div class="service-name">{{ service.name }}</div>
560
- <div class="service-description">{{ service.description }}</div>
561
- <div class="price">${{ "%.0f"|format(service.price) }}</div>
562
- </div>
563
- {% endfor %}
564
- </div>
565
- </div>
566
-
567
- <div class="footer">
568
- <p>πŸ”„ Real-time updates every 30 seconds | Built with ❀️ for {{ business_name }}</p>
569
- <p style="margin-top: 10px; font-size: 0.9em;">πŸš€ 3-File iPhone Forwarding AI System | Powered by DeepSeek & SignalWire</p>
570
- </div>
571
- </div>
572
 
573
- <script>
574
- // Socket.IO connection for real-time updates
575
- const socket = io();
576
-
577
- socket.on('connect', function() {
578
- console.log('πŸ”— Connected to AI system');
579
- });
580
-
581
- socket.on('stats_broadcast', function(data) {
582
- console.log('πŸ“Š Stats update received');
583
- document.getElementById('calls-today').textContent = data.calls_today || 0;
584
- document.getElementById('sms-today').textContent = data.sms_today || 0;
585
- document.getElementById('calls-forwarded').textContent = data.calls_forwarded || 0;
586
- document.getElementById('ai-responses').textContent = data.ai_responses || 0;
587
- document.getElementById('active-calls').textContent = Object.keys(data.active_calls || {}).length;
588
- document.getElementById('active-sms').textContent = Object.keys(data.active_sms || {}).length;
589
- });
590
-
591
- // Auto-refresh stats every 30 seconds
592
- setInterval(function() {
593
- fetch('/api/stats')
594
- .then(response => response.json())
595
- .then(data => {
596
- document.getElementById('calls-today').textContent = data.calls_today;
597
- document.getElementById('sms-today').textContent = data.sms_today;
598
- document.getElementById('calls-forwarded').textContent = data.calls_forwarded;
599
- document.getElementById('ai-responses').textContent = data.ai_responses;
600
- document.getElementById('active-calls').textContent = data.active_calls;
601
- document.getElementById('active-sms').textContent = data.active_sms;
602
- })
603
- .catch(error => console.error('⚠️ Stats update failed:', error));
604
- }, 30000);
605
-
606
- // Add some visual feedback
607
- document.addEventListener('DOMContentLoaded', function() {
608
- console.log('πŸš— Jay\'s Mobile Wash AI Dashboard Loaded');
609
-
610
- // Add click effects to stat cards
611
- document.querySelectorAll('.stat-card').forEach(card => {
612
- card.addEventListener('click', function() {
613
- this.style.transform = 'scale(0.95)';
614
- setTimeout(() => {
615
- this.style.transform = '';
616
- }, 150);
617
- });
618
- });
619
- });
620
- </script>
621
- </body>
622
- </html>
623
- """
624
-
625
- # ===== VOICE WEBHOOKS =====
626
 
627
- @app.route('/voice/incoming', methods=['POST'])
628
  def handle_voice():
629
- """Handle incoming voice calls with iPhone forwarding"""
630
  try:
631
  if not signalwire_client or not VoiceResponse:
632
  return "Service unavailable", 503
@@ -639,20 +266,20 @@ def handle_voice():
639
  # Detect forwarding
640
  is_forwarded = bool(forwarded_from) or (to_number == SIGNALWIRE_PHONE)
641
 
642
- logger.info(f"πŸ“ž Call: {from_number} β†’ {to_number}, Forwarded: {is_forwarded}")
 
 
 
 
 
 
 
643
 
644
  # Update stats
645
  system_state.increment('calls_today')
646
  if is_forwarded:
647
  system_state.increment('calls_forwarded')
648
 
649
- # Store active call
650
- system_state.get_all()['active_calls'][call_sid] = {
651
- 'from': from_number,
652
- 'forwarded': is_forwarded,
653
- 'start': datetime.now().isoformat()
654
- }
655
-
656
  response = VoiceResponse()
657
 
658
  # Greeting
@@ -684,7 +311,7 @@ def handle_voice():
684
  response.dial(JAY_PHONE, timeout=30)
685
  return str(response)
686
 
687
- @app.route('/voice/process', methods=['POST'])
688
  def process_speech():
689
  """Process customer speech"""
690
  try:
@@ -693,8 +320,6 @@ def process_speech():
693
  speech_result = request.form.get('SpeechResult', '')
694
  confidence = float(request.form.get('Confidence', '0.0'))
695
 
696
- logger.info(f"🎀 Speech: {speech_result} (conf: {confidence:.2f})")
697
-
698
  if not speech_result or confidence < 0.4:
699
  response = VoiceResponse()
700
  response.say("I didn't understand clearly. Let me connect you with Jay.", voice='alice')
@@ -738,9 +363,7 @@ def process_speech():
738
  response.dial(JAY_PHONE, timeout=30)
739
  return str(response)
740
 
741
- # ===== SMS WEBHOOKS =====
742
-
743
- @app.route('/sms/incoming', methods=['POST'])
744
  def handle_sms():
745
  """Handle incoming SMS"""
746
  try:
@@ -748,7 +371,14 @@ def handle_sms():
748
  from_number = request.form.get('From')
749
  message_body = request.form.get('Body', '').strip()
750
 
751
- logger.info(f"πŸ“± SMS from {from_number}: {message_body[:50]}...")
 
 
 
 
 
 
 
752
 
753
  system_state.increment('sms_today')
754
 
@@ -765,13 +395,12 @@ def handle_sms():
765
  'jay' in message_body.lower()):
766
 
767
  response_text = f"I'll have Jay respond to you personally. For immediate assistance, call {JAY_PHONE}."
768
- # TODO: Notify Jay
769
  return send_sms(response_text, from_number)
770
 
771
  # Generate AI response
772
  ai_response = ai.generate_response(message_body, {'sms': True})
773
 
774
- # Optimize for SMS (160 char limit)
775
  if len(ai_response) > 320:
776
  ai_response = ai_response[:317] + "..."
777
 
@@ -792,71 +421,22 @@ def send_sms(message, to_number):
792
  from_=SIGNALWIRE_PHONE,
793
  to=to_number
794
  )
795
- logger.info(f"πŸ“€ SMS sent to {to_number}")
 
 
 
 
 
 
 
 
796
 
797
  return str(MessagingResponse()) if MessagingResponse else ""
798
  except Exception as e:
799
  logger.error(f"SMS send error: {e}")
800
  return ""
801
 
802
- # ===== WEB INTERFACE =====
803
-
804
- @app.route('/')
805
- def dashboard():
806
- """Main dashboard with inline HTML"""
807
- try:
808
- stats = system_state.get_all()
809
- uptime = datetime.now() - stats['start_time']
810
-
811
- dashboard_data = {
812
- 'business_name': BUSINESS_NAME,
813
- 'jay_phone': JAY_PHONE,
814
- 'ai_phone': SIGNALWIRE_PHONE,
815
- 'forwarding_enabled': FORWARDING_ENABLED,
816
- 'forwarding_delay': FORWARDING_DELAY,
817
- 'stats': {
818
- 'calls_today': stats['calls_today'],
819
- 'sms_today': stats['sms_today'],
820
- 'calls_forwarded': stats['calls_forwarded'],
821
- 'ai_responses': stats['ai_responses'],
822
- 'revenue_today': stats['revenue_today'],
823
- 'uptime_hours': int(uptime.total_seconds() / 3600),
824
- 'active_calls': len(stats['active_calls']),
825
- 'active_sms': len(stats['active_sms']),
826
- 'status': stats['status']
827
- },
828
- 'services': SERVICES,
829
- 'signalwire_configured': bool(signalwire_client),
830
- 'deepseek_configured': bool(DEEPSEEK_API_KEY),
831
- 'request': request
832
- }
833
-
834
- return render_template_string(DASHBOARD_HTML, **dashboard_data)
835
-
836
- except Exception as e:
837
- logger.error(f"Dashboard error: {e}")
838
- return f"<h1>Dashboard Error: {e}</h1>", 500
839
-
840
- @app.route('/api/stats')
841
- def api_stats():
842
- """API endpoint for real-time stats"""
843
- stats = system_state.get_all()
844
- uptime = datetime.now() - stats['start_time']
845
-
846
- return jsonify({
847
- 'calls_today': stats['calls_today'],
848
- 'sms_today': stats['sms_today'],
849
- 'calls_forwarded': stats['calls_forwarded'],
850
- 'ai_responses': stats['ai_responses'],
851
- 'revenue_today': stats['revenue_today'],
852
- 'uptime_seconds': int(uptime.total_seconds()),
853
- 'active_calls': len(stats['active_calls']),
854
- 'active_sms': len(stats['active_sms']),
855
- 'status': stats['status'],
856
- 'timestamp': datetime.now().isoformat()
857
- })
858
-
859
- @app.route('/health')
860
  def health_check():
861
  """Health check endpoint"""
862
  health = {
@@ -866,55 +446,244 @@ def health_check():
866
  'uptime': int((datetime.now() - system_state.get_all()['start_time']).total_seconds()),
867
  'timestamp': datetime.now().isoformat()
868
  }
869
-
870
- status_code = 200 if health['signalwire'] else 503
871
- return jsonify(health), status_code
872
-
873
- # ===== SOCKETIO EVENTS =====
874
-
875
- @socketio.on('connect')
876
- def handle_connect():
877
- emit('status', {'message': 'Connected to Jay\'s Mobile Wash AI'})
878
- logger.info("πŸ”— Client connected")
879
 
880
- @socketio.on('request_stats')
881
- def handle_stats_request():
 
882
  stats = system_state.get_all()
883
- emit('stats_update', stats)
884
-
885
- # ===== BACKGROUND TASKS =====
886
-
887
- def broadcast_stats():
888
- """Broadcast stats every 30 seconds"""
889
- while True:
890
- try:
891
- stats = system_state.get_all()
892
- socketio.emit('stats_broadcast', stats)
893
- time.sleep(30)
894
- except Exception as e:
895
- logger.error(f"Broadcast error: {e}")
896
- time.sleep(60)
897
-
898
- # Start background thread
899
- stats_thread = threading.Thread(target=broadcast_stats, daemon=True)
900
- stats_thread.start()
901
-
902
- # ===== ERROR HANDLERS =====
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
903
 
904
- @app.errorhandler(404)
905
- def not_found(error):
906
- return jsonify({'error': 'Not found'}), 404
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
907
 
908
- @app.errorhandler(500)
909
- def internal_error(error):
910
- logger.error(f"Internal error: {error}")
911
- return jsonify({'error': 'Internal server error'}), 500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
912
 
913
- # ===== STARTUP =====
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
914
 
915
- if __name__ == '__main__':
 
916
  print("\n" + "="*60)
917
- print("πŸš— JAY'S MOBILE WASH AI SYSTEM - 3 FILE VERSION")
918
  print("="*60)
919
  print(f"πŸ“ž Jay's Phone: {JAY_PHONE}")
920
  print(f"πŸ€– AI Phone: {SIGNALWIRE_PHONE}")
@@ -922,16 +691,13 @@ if __name__ == '__main__':
922
  print(f"🌐 SignalWire: {'βœ… Connected' if signalwire_client else '❌ Not configured'}")
923
  print(f"🧠 DeepSeek: {'βœ… Connected' if DEEPSEEK_API_KEY else '❌ Not configured'}")
924
  print("="*60)
925
- print("πŸš€ Starting server on http://0.0.0.0:7860")
926
- print("πŸ“Š Dashboard: http://localhost:7860")
927
- print("πŸ“‘ Health Check: http://localhost:7860/health")
928
  print("="*60)
929
 
930
- # Run the app
931
- socketio.run(
932
- app,
933
- host='0.0.0.0',
934
- port=7860,
935
- debug=False,
936
- use_reloader=False
937
  )
 
1
  #!/usr/bin/env python3
2
  """
3
+ πŸš— JAY'S MOBILE WASH - HUGGINGFACE SPACES COMPATIBLE
4
+ iPhone Forwarding + AI Assistant - Optimized for HuggingFace deployment
5
  """
6
 
7
  import os
8
  import json
9
  import requests
10
  import logging
 
 
 
 
11
  import threading
 
 
12
  import time
13
+ from datetime import datetime, timedelta
14
+ from collections import defaultdict
15
+ from threading import Lock
16
+ import gradio as gr
17
 
18
  # Load environment variables
19
  from dotenv import load_dotenv
20
  load_dotenv()
21
 
 
 
 
 
 
 
 
 
22
  # Configuration
23
  SIGNALWIRE_PROJECT_ID = os.environ.get('SIGNALWIRE_PROJECT_ID', '')
24
  SIGNALWIRE_AUTH_TOKEN = os.environ.get('SIGNALWIRE_AUTH_TOKEN', '')
 
59
  )
60
  print("βœ… SignalWire initialized")
61
  else:
62
+ print("⚠️ SignalWire credentials not configured")
63
  except ImportError:
64
  print("⚠️ SignalWire library not available")
65
+ except Exception as e:
66
+ print(f"⚠️ SignalWire error: {e}")
67
 
68
  # Logging setup
69
  logging.basicConfig(level=logging.INFO)
 
82
  'active_calls': {},
83
  'active_sms': {},
84
  'start_time': datetime.now(),
85
+ 'status': 'healthy',
86
+ 'call_log': [],
87
+ 'sms_log': []
88
  }
89
 
90
  def get(self, key):
 
102
  def get_all(self):
103
  with self._lock:
104
  return self._data.copy()
105
+
106
+ def add_call_log(self, entry):
107
+ with self._lock:
108
+ self._data['call_log'].insert(0, entry)
109
+ if len(self._data['call_log']) > 50:
110
+ self._data['call_log'] = self._data['call_log'][:50]
111
+
112
+ def add_sms_log(self, entry):
113
+ with self._lock:
114
+ self._data['sms_log'].insert(0, entry)
115
+ if len(self._data['sms_log']) > 50:
116
+ self._data['sms_log'] = self._data['sms_log'][:50]
117
 
118
  # Initialize system state
119
  system_state = SystemState()
 
158
  def generate_response(self, user_input, context=None):
159
  """Generate AI response"""
160
 
 
 
 
 
 
161
  intent = self.detect_intent(user_input)
162
  sentiment = self.analyze_sentiment(user_input)
163
 
 
197
  except Exception as e:
198
  logger.warning(f"DeepSeek failed: {e}")
199
 
 
 
200
  return response
201
 
202
  def get_deepseek_response(self, prompt, context=None):
 
246
  # Initialize AI processor
247
  ai = SimpleAI()
248
 
249
+ # Flask app for webhooks (runs in background)
250
+ from flask import Flask, request
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
 
252
+ flask_app = Flask(__name__)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
 
254
+ @flask_app.route('/voice/incoming', methods=['POST'])
255
  def handle_voice():
256
+ """Handle incoming voice calls"""
257
  try:
258
  if not signalwire_client or not VoiceResponse:
259
  return "Service unavailable", 503
 
266
  # Detect forwarding
267
  is_forwarded = bool(forwarded_from) or (to_number == SIGNALWIRE_PHONE)
268
 
269
+ # Log the call
270
+ call_entry = {
271
+ 'time': datetime.now().strftime('%H:%M:%S'),
272
+ 'from': from_number,
273
+ 'type': 'Forwarded Call' if is_forwarded else 'Direct Call',
274
+ 'status': 'Incoming'
275
+ }
276
+ system_state.add_call_log(call_entry)
277
 
278
  # Update stats
279
  system_state.increment('calls_today')
280
  if is_forwarded:
281
  system_state.increment('calls_forwarded')
282
 
 
 
 
 
 
 
 
283
  response = VoiceResponse()
284
 
285
  # Greeting
 
311
  response.dial(JAY_PHONE, timeout=30)
312
  return str(response)
313
 
314
+ @flask_app.route('/voice/process', methods=['POST'])
315
  def process_speech():
316
  """Process customer speech"""
317
  try:
 
320
  speech_result = request.form.get('SpeechResult', '')
321
  confidence = float(request.form.get('Confidence', '0.0'))
322
 
 
 
323
  if not speech_result or confidence < 0.4:
324
  response = VoiceResponse()
325
  response.say("I didn't understand clearly. Let me connect you with Jay.", voice='alice')
 
363
  response.dial(JAY_PHONE, timeout=30)
364
  return str(response)
365
 
366
+ @flask_app.route('/sms/incoming', methods=['POST'])
 
 
367
  def handle_sms():
368
  """Handle incoming SMS"""
369
  try:
 
371
  from_number = request.form.get('From')
372
  message_body = request.form.get('Body', '').strip()
373
 
374
+ # Log the SMS
375
+ sms_entry = {
376
+ 'time': datetime.now().strftime('%H:%M:%S'),
377
+ 'from': from_number,
378
+ 'message': message_body[:50] + '...' if len(message_body) > 50 else message_body,
379
+ 'status': 'Received'
380
+ }
381
+ system_state.add_sms_log(sms_entry)
382
 
383
  system_state.increment('sms_today')
384
 
 
395
  'jay' in message_body.lower()):
396
 
397
  response_text = f"I'll have Jay respond to you personally. For immediate assistance, call {JAY_PHONE}."
 
398
  return send_sms(response_text, from_number)
399
 
400
  # Generate AI response
401
  ai_response = ai.generate_response(message_body, {'sms': True})
402
 
403
+ # Optimize for SMS
404
  if len(ai_response) > 320:
405
  ai_response = ai_response[:317] + "..."
406
 
 
421
  from_=SIGNALWIRE_PHONE,
422
  to=to_number
423
  )
424
+
425
+ # Log sent SMS
426
+ sms_entry = {
427
+ 'time': datetime.now().strftime('%H:%M:%S'),
428
+ 'from': 'AI Assistant',
429
+ 'message': message[:50] + '...' if len(message) > 50 else message,
430
+ 'status': 'Sent'
431
+ }
432
+ system_state.add_sms_log(sms_entry)
433
 
434
  return str(MessagingResponse()) if MessagingResponse else ""
435
  except Exception as e:
436
  logger.error(f"SMS send error: {e}")
437
  return ""
438
 
439
+ @flask_app.route('/health')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440
  def health_check():
441
  """Health check endpoint"""
442
  health = {
 
446
  'uptime': int((datetime.now() - system_state.get_all()['start_time']).total_seconds()),
447
  'timestamp': datetime.now().isoformat()
448
  }
449
+ return health
 
 
 
 
 
 
 
 
 
450
 
451
+ # Gradio Interface Functions
452
+ def get_dashboard_data():
453
+ """Get current dashboard data"""
454
  stats = system_state.get_all()
455
+ uptime = datetime.now() - stats['start_time']
456
+
457
+ dashboard_html = f"""
458
+ <div style="font-family: Arial, sans-serif; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; color: white;">
459
+ <h1 style="text-align: center; margin-bottom: 30px;">πŸš— {BUSINESS_NAME} - AI Dashboard</h1>
460
+
461
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 30px;">
462
+ <div style="background: rgba(255,255,255,0.15); padding: 20px; border-radius: 10px; text-align: center;">
463
+ <h2 style="color: #4facfe; font-size: 2em; margin: 0;">{stats['calls_today']}</h2>
464
+ <p style="margin: 5px 0 0 0;">πŸ“ž Calls Today</p>
465
+ </div>
466
+ <div style="background: rgba(255,255,255,0.15); padding: 20px; border-radius: 10px; text-align: center;">
467
+ <h2 style="color: #4facfe; font-size: 2em; margin: 0;">{stats['sms_today']}</h2>
468
+ <p style="margin: 5px 0 0 0;">πŸ“± SMS Today</p>
469
+ </div>
470
+ <div style="background: rgba(255,255,255,0.15); padding: 20px; border-radius: 10px; text-align: center;">
471
+ <h2 style="color: #4facfe; font-size: 2em; margin: 0;">{stats['calls_forwarded']}</h2>
472
+ <p style="margin: 5px 0 0 0;">πŸ”„ Calls Forwarded</p>
473
+ </div>
474
+ <div style="background: rgba(255,255,255,0.15); padding: 20px; border-radius: 10px; text-align: center;">
475
+ <h2 style="color: #4facfe; font-size: 2em; margin: 0;">{stats['ai_responses']}</h2>
476
+ <p style="margin: 5px 0 0 0;">πŸ€– AI Responses</p>
477
+ </div>
478
+ </div>
479
+
480
+ <div style="background: rgba(255,255,255,0.1); padding: 20px; border-radius: 10px; margin-bottom: 20px;">
481
+ <h3 style="color: #4facfe; margin-bottom: 15px;">πŸ“ž iPhone Forwarding Status</h3>
482
+ <p><strong>Jay's iPhone:</strong> {JAY_PHONE}</p>
483
+ <p><strong>AI Assistant Number:</strong> {SIGNALWIRE_PHONE}</p>
484
+ <p><strong>Forwarding:</strong> {'βœ… Enabled' if FORWARDING_ENABLED else '❌ Disabled'}</p>
485
+ <p><strong>Delay:</strong> {FORWARDING_DELAY} seconds</p>
486
+ <p><strong>SignalWire:</strong> {'βœ… Connected' if signalwire_client else '❌ Not configured'}</p>
487
+ <p><strong>DeepSeek AI:</strong> {'βœ… Connected' if DEEPSEEK_API_KEY else '⚠️ Fallback mode'}</p>
488
+ </div>
489
+
490
+ <div style="background: rgba(255,255,255,0.1); padding: 20px; border-radius: 10px;">
491
+ <h3 style="color: #4facfe; margin-bottom: 15px;">πŸ’Ό Services & Pricing</h3>
492
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">
493
+ """
494
+
495
+ for service_id, service in SERVICES.items():
496
+ dashboard_html += f"""
497
+ <div style="background: rgba(79, 172, 254, 0.2); padding: 15px; border-radius: 8px; text-align: center;">
498
+ <h4 style="margin: 0 0 5px 0; color: white;">{service['name']}</h4>
499
+ <p style="margin: 0 0 10px 0; font-size: 0.9em; opacity: 0.8;">{service['description']}</p>
500
+ <p style="margin: 0; font-size: 1.5em; font-weight: bold; color: #00ff88;">${service['price']:.0f}</p>
501
+ </div>
502
+ """
503
+
504
+ dashboard_html += """
505
+ </div>
506
+ </div>
507
+ </div>
508
+ """
509
+
510
+ return dashboard_html
511
 
512
+ def get_call_log():
513
+ """Get recent call log"""
514
+ stats = system_state.get_all()
515
+ call_log = stats.get('call_log', [])
516
+
517
+ if not call_log:
518
+ return "No calls logged yet."
519
+
520
+ log_html = """
521
+ <div style="font-family: Arial, sans-serif;">
522
+ <h3>πŸ“ž Recent Calls</h3>
523
+ <div style="max-height: 300px; overflow-y: auto;">
524
+ """
525
+
526
+ for call in call_log[:10]: # Show last 10 calls
527
+ log_html += f"""
528
+ <div style="background: #f5f5f5; padding: 10px; margin: 5px 0; border-radius: 5px; border-left: 4px solid #4facfe;">
529
+ <strong>{call['time']}</strong> | {call['from']} | {call['type']} | {call['status']}
530
+ </div>
531
+ """
532
+
533
+ log_html += "</div></div>"
534
+ return log_html
535
 
536
+ def get_sms_log():
537
+ """Get recent SMS log"""
538
+ stats = system_state.get_all()
539
+ sms_log = stats.get('sms_log', [])
540
+
541
+ if not sms_log:
542
+ return "No SMS logged yet."
543
+
544
+ log_html = """
545
+ <div style="font-family: Arial, sans-serif;">
546
+ <h3>πŸ“± Recent SMS</h3>
547
+ <div style="max-height: 300px; overflow-y: auto;">
548
+ """
549
+
550
+ for sms in sms_log[:10]: # Show last 10 SMS
551
+ log_html += f"""
552
+ <div style="background: #f5f5f5; padding: 10px; margin: 5px 0; border-radius: 5px; border-left: 4px solid #00ff88;">
553
+ <strong>{sms['time']}</strong> | {sms['from']}<br>
554
+ <em>{sms['message']}</em> | {sms['status']}
555
+ </div>
556
+ """
557
+
558
+ log_html += "</div></div>"
559
+ return log_html
560
 
561
+ def test_ai_response(message):
562
+ """Test AI response generation"""
563
+ if not message.strip():
564
+ return "Please enter a message to test."
565
+
566
+ try:
567
+ intent = ai.detect_intent(message)
568
+ sentiment = ai.analyze_sentiment(message)
569
+ response = ai.generate_response(message, {'test': True})
570
+
571
+ result = f"""
572
+ <div style="font-family: Arial, sans-serif; padding: 15px; background: #f9f9f9; border-radius: 10px;">
573
+ <h3>AI Response Test</h3>
574
+ <p><strong>Customer Message:</strong> {message}</p>
575
+ <p><strong>Detected Intent:</strong> {intent}</p>
576
+ <p><strong>Sentiment:</strong> {sentiment['label']} ({sentiment['score']:.2f})</p>
577
+ <div style="background: #e3f2fd; padding: 10px; border-radius: 5px; margin-top: 10px;">
578
+ <strong>AI Response:</strong><br>
579
+ {response}
580
+ </div>
581
+ </div>
582
+ """
583
+ return result
584
+ except Exception as e:
585
+ return f"Error testing AI response: {e}"
586
+
587
+ # Start Flask app in background thread
588
+ def run_flask():
589
+ flask_app.run(host='0.0.0.0', port=7860, debug=False)
590
+
591
+ flask_thread = threading.Thread(target=run_flask, daemon=True)
592
+ flask_thread.start()
593
+
594
+ # Create Gradio Interface
595
+ with gr.Blocks(
596
+ title=f"{BUSINESS_NAME} - AI Dashboard",
597
+ theme=gr.themes.Soft(),
598
+ css="""
599
+ .gradio-container {
600
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
601
+ }
602
+ """
603
+ ) as interface:
604
+
605
+ gr.Markdown(f"""
606
+ # πŸš— {BUSINESS_NAME} - iPhone Forwarding AI System
607
+
608
+ **Real-time dashboard for your AI assistant with iPhone call forwarding**
609
+
610
+ πŸ“ž **Jay's iPhone:** {JAY_PHONE}
611
+ πŸ€– **AI Assistant:** {SIGNALWIRE_PHONE}
612
+ πŸ”„ **Forwarding:** {'βœ… Enabled' if FORWARDING_ENABLED else '❌ Disabled'}
613
+ """)
614
+
615
+ with gr.Tabs():
616
+ with gr.Tab("πŸ“Š Dashboard"):
617
+ dashboard_display = gr.HTML(value=get_dashboard_data())
618
+ refresh_btn = gr.Button("πŸ”„ Refresh Dashboard", variant="primary")
619
+ refresh_btn.click(fn=get_dashboard_data, outputs=dashboard_display)
620
+
621
+ with gr.Tab("πŸ“ž Call Log"):
622
+ call_log_display = gr.HTML(value=get_call_log())
623
+ refresh_calls_btn = gr.Button("πŸ”„ Refresh Calls", variant="secondary")
624
+ refresh_calls_btn.click(fn=get_call_log, outputs=call_log_display)
625
+
626
+ with gr.Tab("πŸ“± SMS Log"):
627
+ sms_log_display = gr.HTML(value=get_sms_log())
628
+ refresh_sms_btn = gr.Button("πŸ”„ Refresh SMS", variant="secondary")
629
+ refresh_sms_btn.click(fn=get_sms_log, outputs=sms_log_display)
630
+
631
+ with gr.Tab("πŸ§ͺ Test AI"):
632
+ with gr.Row():
633
+ with gr.Column():
634
+ test_message = gr.Textbox(
635
+ label="Test Message",
636
+ placeholder="Enter a customer message to test AI response...",
637
+ lines=3
638
+ )
639
+ test_btn = gr.Button("πŸ€– Test AI Response", variant="primary")
640
+
641
+ with gr.Column():
642
+ test_result = gr.HTML(label="AI Response")
643
+
644
+ test_btn.click(fn=test_ai_response, inputs=test_message, outputs=test_result)
645
+
646
+ with gr.Tab("ℹ️ Setup Info"):
647
+ gr.Markdown(f"""
648
+ ## πŸ”§ Webhook Configuration
649
+
650
+ **Set these URLs in your SignalWire dashboard:**
651
+
652
+ - **Voice Webhook:** `https://YOUR-SPACE-NAME.hf.space/voice/incoming`
653
+ - **SMS Webhook:** `https://YOUR-SPACE-NAME.hf.space/sms/incoming`
654
+
655
+ ## πŸ“± iPhone Setup
656
+
657
+ **On Jay's iPhone:**
658
+ 1. Go to **Settings** β†’ **Phone** β†’ **Call Forwarding**
659
+ 2. Turn **ON** Call Forwarding
660
+ 3. Set forward number to: `{SIGNALWIRE_PHONE}`
661
+ 4. Choose: Forward when **unanswered** after {FORWARDING_DELAY} seconds
662
+
663
+ ## 🎯 How It Works
664
+
665
+ 1. Customer calls Jay's iPhone: `{JAY_PHONE}`
666
+ 2. iPhone rings for {FORWARDING_DELAY} seconds
667
+ 3. If no answer β†’ forwards to AI: `{SIGNALWIRE_PHONE}`
668
+ 4. AI handles call with forwarding awareness
669
+ 5. Can escalate back to Jay if needed
670
+
671
+ ## πŸ“Š System Status
672
+
673
+ - **SignalWire:** {'βœ… Connected' if signalwire_client else '❌ Not configured'}
674
+ - **DeepSeek AI:** {'βœ… Connected' if DEEPSEEK_API_KEY else '⚠️ Using fallback responses'}
675
+ - **Health Check:** [/health](./health)
676
+ """)
677
+
678
+ # Auto-refresh every 30 seconds
679
+ interface.load(fn=get_dashboard_data, outputs=dashboard_display, every=30)
680
+ interface.load(fn=get_call_log, outputs=call_log_display, every=30)
681
+ interface.load(fn=get_sms_log, outputs=sms_log_display, every=30)
682
 
683
+ # Launch the interface
684
+ if __name__ == "__main__":
685
  print("\n" + "="*60)
686
+ print("πŸš— JAY'S MOBILE WASH AI SYSTEM")
687
  print("="*60)
688
  print(f"πŸ“ž Jay's Phone: {JAY_PHONE}")
689
  print(f"πŸ€– AI Phone: {SIGNALWIRE_PHONE}")
 
691
  print(f"🌐 SignalWire: {'βœ… Connected' if signalwire_client else '❌ Not configured'}")
692
  print(f"🧠 DeepSeek: {'βœ… Connected' if DEEPSEEK_API_KEY else '❌ Not configured'}")
693
  print("="*60)
694
+ print("πŸš€ Starting Gradio interface...")
695
+ print("πŸ“ž Flask webhooks running on port 7860")
 
696
  print("="*60)
697
 
698
+ interface.launch(
699
+ server_name="0.0.0.0",
700
+ server_port=7860,
701
+ share=False,
702
+ show_error=True
 
 
703
  )