#!/usr/bin/env python3 """ ๐Ÿš— JAY'S MOBILE WASH - GRADIO + FASTAPI VERSION iPhone Forwarding + AI Assistant - HuggingFace Spaces Optimized """ import os import json import requests import logging import threading import time from datetime import datetime, timedelta from collections import defaultdict from threading import Lock import gradio as gr from fastapi import FastAPI, Request, Form from fastapi.responses import PlainTextResponse import uvicorn # Load environment variables from dotenv import load_dotenv load_dotenv() # Configuration SIGNALWIRE_PROJECT_ID = os.environ.get('SIGNALWIRE_PROJECT_ID', '') SIGNALWIRE_AUTH_TOKEN = os.environ.get('SIGNALWIRE_AUTH_TOKEN', '') SIGNALWIRE_SPACE_URL = os.environ.get('SIGNALWIRE_SPACE_URL', '') DEEPSEEK_API_KEY = os.environ.get('DEEPSEEK_API_KEY', '') # Business settings BUSINESS_NAME = "Jay's Mobile Wash" JAY_PHONE = os.environ.get('JAY_PHONE', '+15622289429') SIGNALWIRE_PHONE = os.environ.get('SIGNALWIRE_PHONE', '+17149278841') FORWARDING_ENABLED = True FORWARDING_DELAY = 20 # Services and pricing SERVICES = { 'basic_wash': {'price': 25.00, 'name': 'Basic Wash', 'description': 'Exterior wash and dry'}, 'premium_wash': {'price': 45.00, 'name': 'Premium Wash', 'description': 'Wash, wax, interior vacuum'}, 'full_detail': {'price': 85.00, 'name': 'Full Detail', 'description': 'Complete interior & exterior'}, 'ceramic_coating': {'price': 150.00, 'name': 'Ceramic Coating', 'description': 'Premium protection'}, 'headlight_restoration': {'price': 35.00, 'name': 'Headlight Restoration', 'description': 'Clear headlight restoration'} } # Initialize SignalWire signalwire_client = None VoiceResponse = None MessagingResponse = None try: from signalwire.rest import Client from signalwire.voice_response import VoiceResponse from signalwire.messaging_response import MessagingResponse if SIGNALWIRE_PROJECT_ID and SIGNALWIRE_AUTH_TOKEN and SIGNALWIRE_SPACE_URL: signalwire_client = Client( SIGNALWIRE_PROJECT_ID, SIGNALWIRE_AUTH_TOKEN, signalwire_space_url=SIGNALWIRE_SPACE_URL ) print("โœ… SignalWire initialized") else: print("โš ๏ธ SignalWire credentials not configured") except ImportError: print("โš ๏ธ SignalWire library not available") except Exception as e: print(f"โš ๏ธ SignalWire error: {e}") # Logging setup logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Thread-safe system state class SystemState: def __init__(self): self._lock = Lock() self._data = { 'calls_today': 0, 'sms_today': 0, 'calls_forwarded': 0, 'ai_responses': 0, 'revenue_today': 0.0, 'active_calls': {}, 'active_sms': {}, 'start_time': datetime.now(), 'status': 'healthy', 'call_log': [], 'sms_log': [] } def get(self, key): with self._lock: return self._data.get(key, 0) def set(self, key, value): with self._lock: self._data[key] = value def increment(self, key): with self._lock: self._data[key] = self._data.get(key, 0) + 1 def get_all(self): with self._lock: return self._data.copy() def add_call_log(self, entry): with self._lock: self._data['call_log'].insert(0, entry) if len(self._data['call_log']) > 50: self._data['call_log'] = self._data['call_log'][:50] def add_sms_log(self, entry): with self._lock: self._data['sms_log'].insert(0, entry) if len(self._data['sms_log']) > 50: self._data['sms_log'] = self._data['sms_log'][:50] # Initialize system state system_state = SystemState() # Simple AI processor class SimpleAI: def __init__(self): self.response_cache = {} def analyze_sentiment(self, text): """Simple sentiment analysis""" try: from textblob import TextBlob blob = TextBlob(text) polarity = blob.sentiment.polarity return { 'score': polarity, 'label': 'positive' if polarity > 0.1 else 'negative' if polarity < -0.1 else 'neutral' } except: return {'score': 0.0, 'label': 'neutral'} def detect_intent(self, text): """Detect customer intent""" text_lower = text.lower() if any(word in text_lower for word in ['price', 'cost', 'how much', 'pricing']): return 'pricing' elif any(word in text_lower for word in ['book', 'schedule', 'appointment', 'when']): return 'booking' elif any(word in text_lower for word in ['service', 'wash', 'detail', 'clean']): return 'services' elif any(word in text_lower for word in ['location', 'where', 'area', 'address']): return 'location' elif any(word in text_lower for word in ['urgent', 'emergency', 'asap', 'now']): return 'urgent' elif any(word in text_lower for word in ['jay', 'owner', 'human', 'person']): return 'human_request' else: return 'general' def generate_response(self, user_input, context=None): """Generate AI response""" intent = self.detect_intent(user_input) sentiment = self.analyze_sentiment(user_input) # Add forwarding acknowledgment if needed forwarding_ack = "" if context and context.get('forwarded'): forwarding_ack = "Thank you for your patience while the call was connected. " # Generate response based on intent if intent == 'pricing': response = f"{forwarding_ack}I'd be happy to help with pricing! Our basic wash is $25, premium wash is $45, and full detail is $85. Which service interests you most?" elif intent == 'booking': response = f"{forwarding_ack}I can help you schedule an appointment! We're available Monday-Saturday 8AM-6PM, Sunday 10AM-4PM. What day works best for you?" elif intent == 'services': response = f"{forwarding_ack}We offer mobile car detailing services: Basic wash ($25), Premium wash with wax ($45), Full detail ($85), and ceramic coating ($150). Which would you like to know more about?" elif intent == 'location': response = f"{forwarding_ack}We provide mobile service within a 15-mile radius of Los Angeles. We come to your location! Where are you located?" elif intent == 'urgent': response = f"{forwarding_ack}I understand this is urgent. Let me connect you with Jay directly right away for immediate assistance." elif intent == 'human_request': response = f"{forwarding_ack}Let me connect you with Jay personally. He'll be right with you!" else: response = f"{forwarding_ack}Hi! Thanks for contacting Jay's Mobile Wash! I can help with pricing, scheduling, or questions about our mobile car detailing services. How can I assist you today?" # Try DeepSeek if available for better responses if DEEPSEEK_API_KEY and intent not in ['urgent', 'human_request']: try: deepseek_response = self.get_deepseek_response(user_input, context) if deepseek_response: response = deepseek_response except Exception as e: logger.warning(f"DeepSeek failed: {e}") return response def get_deepseek_response(self, prompt, context=None): """Get response from DeepSeek API""" try: headers = { "Authorization": f"Bearer {DEEPSEEK_API_KEY}", "Content-Type": "application/json" } system_prompt = f"""You are Jay's Mobile Wash AI assistant. Be friendly and professional. BUSINESS INFO: - Services: Basic wash ($25), Premium wash ($45), Full detail ($85), Ceramic coating ($150), Headlight restoration ($35) - Hours: Mon-Sat 8AM-6PM, Sun 10AM-4PM - Service area: 15 mile radius from Los Angeles - Owner: Jay, Phone: {JAY_PHONE} {"IMPORTANT: Customer's call was forwarded from Jay's phone. Be empathetic about any wait time." if context and context.get('forwarded') else ""}""" data = { "model": "deepseek-chat", "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": prompt} ], "max_tokens": 150, "temperature": 0.7 } response = requests.post( "https://api.deepseek.com/v1/chat/completions", headers=headers, json=data, timeout=10 ) if response.status_code == 200: result = response.json() return result['choices'][0]['message']['content'].strip() except Exception as e: logger.error(f"DeepSeek error: {e}") return None # Initialize AI processor ai = SimpleAI() # FastAPI app for webhooks fastapi_app = FastAPI() @fastapi_app.post("/voice/incoming") async def handle_voice(request: Request): """Handle incoming voice calls""" try: form = await request.form() if not signalwire_client or not VoiceResponse: return PlainTextResponse("Service unavailable", status_code=503) call_sid = form.get('CallSid') from_number = form.get('From') to_number = form.get('To') forwarded_from = form.get('ForwardedFrom') # Detect forwarding is_forwarded = bool(forwarded_from) or (to_number == SIGNALWIRE_PHONE) # Log the call call_entry = { 'time': datetime.now().strftime('%H:%M:%S'), 'from': from_number, 'type': 'Forwarded Call' if is_forwarded else 'Direct Call', 'status': 'Incoming' } system_state.add_call_log(call_entry) # Update stats system_state.increment('calls_today') if is_forwarded: system_state.increment('calls_forwarded') response = VoiceResponse() # Greeting if is_forwarded: greeting = "Hi! Thanks for calling Jay's Mobile Wash. I'm Jay's AI assistant ready to help while Jay is with another customer. How can I assist you?" else: greeting = "Hi! Welcome to Jay's Mobile Wash! I'm your AI assistant ready to help with pricing, scheduling, or any questions. How can I help you?" response.say(greeting, voice='alice') # Gather speech response.gather( input='speech', timeout=10, action=f'/voice/process?call_sid={call_sid}&forwarded={is_forwarded}', speech_timeout=3 ) # Fallback response.say("I didn't catch that. Let me connect you with Jay.", voice='alice') response.dial(JAY_PHONE, timeout=30) return PlainTextResponse(str(response), media_type="application/xml") except Exception as e: logger.error(f"Voice error: {e}") response = VoiceResponse() response.say("Technical issue. Connecting you with Jay.", voice='alice') response.dial(JAY_PHONE, timeout=30) return PlainTextResponse(str(response), media_type="application/xml") @fastapi_app.post("/voice/process") async def process_speech(request: Request): """Process customer speech""" try: form = await request.form() query_params = request.query_params call_sid = query_params.get('call_sid') is_forwarded = query_params.get('forwarded') == 'True' speech_result = form.get('SpeechResult', '') confidence = float(form.get('Confidence', '0.0')) if not speech_result or confidence < 0.4: response = VoiceResponse() response.say("I didn't understand clearly. Let me connect you with Jay.", voice='alice') response.dial(JAY_PHONE, timeout=30) return PlainTextResponse(str(response), media_type="application/xml") # Analyze intent intent = ai.detect_intent(speech_result) # Check for escalation if intent in ['urgent', 'human_request'] or 'jay' in speech_result.lower(): response = VoiceResponse() response.say("Let me connect you with Jay right away.", voice='alice') response.dial(JAY_PHONE, timeout=30) return PlainTextResponse(str(response), media_type="application/xml") # Generate AI response context = {'forwarded': is_forwarded, 'call_sid': call_sid} ai_response = ai.generate_response(speech_result, context) system_state.increment('ai_responses') response = VoiceResponse() response.say(ai_response, voice='alice') # Continue conversation response.gather( input='speech', timeout=8, action=f'/voice/process?call_sid={call_sid}&forwarded={is_forwarded}', speech_timeout=3 ) response.say("Thank you for calling Jay's Mobile Wash! Have a great day!", voice='alice') return PlainTextResponse(str(response), media_type="application/xml") except Exception as e: logger.error(f"Speech processing error: {e}") response = VoiceResponse() response.say("Technical issue. Connecting with Jay.", voice='alice') response.dial(JAY_PHONE, timeout=30) return PlainTextResponse(str(response), media_type="application/xml") @fastapi_app.post("/sms/incoming") async def handle_sms(request: Request): """Handle incoming SMS""" try: form = await request.form() message_sid = form.get('MessageSid') from_number = form.get('From') message_body = form.get('Body', '').strip() # Log the SMS sms_entry = { 'time': datetime.now().strftime('%H:%M:%S'), 'from': from_number, 'message': message_body[:50] + '...' if len(message_body) > 50 else message_body, 'status': 'Received' } system_state.add_sms_log(sms_entry) system_state.increment('sms_today') if not message_body: await send_sms("Hi! How can I help you with Jay's Mobile Wash today?", from_number) return PlainTextResponse(str(MessagingResponse()) if MessagingResponse else "", media_type="application/xml") # Analyze message intent = ai.detect_intent(message_body) sentiment = ai.analyze_sentiment(message_body) # Check for escalation if (intent in ['urgent', 'human_request'] or sentiment['score'] < -0.6 or 'jay' in message_body.lower()): response_text = f"I'll have Jay respond to you personally. For immediate assistance, call {JAY_PHONE}." await send_sms(response_text, from_number) return PlainTextResponse(str(MessagingResponse()) if MessagingResponse else "", media_type="application/xml") # Generate AI response ai_response = ai.generate_response(message_body, {'sms': True}) # Optimize for SMS if len(ai_response) > 320: ai_response = ai_response[:317] + "..." system_state.increment('ai_responses') await send_sms(ai_response, from_number) return PlainTextResponse(str(MessagingResponse()) if MessagingResponse else "", media_type="application/xml") except Exception as e: logger.error(f"SMS error: {e}") await send_sms("Thanks for your message! Jay will get back to you soon.", from_number) return PlainTextResponse(str(MessagingResponse()) if MessagingResponse else "", media_type="application/xml") async def send_sms(message, to_number): """Send SMS response""" try: if signalwire_client: signalwire_client.messages.create( body=message, from_=SIGNALWIRE_PHONE, to=to_number ) # Log sent SMS sms_entry = { 'time': datetime.now().strftime('%H:%M:%S'), 'from': 'AI Assistant', 'message': message[:50] + '...' if len(message) > 50 else message, 'status': 'Sent' } system_state.add_sms_log(sms_entry) except Exception as e: logger.error(f"SMS send error: {e}") @fastapi_app.get("/health") async def health_check(): """Health check endpoint""" health = { 'status': 'healthy', 'signalwire': bool(signalwire_client), 'deepseek': bool(DEEPSEEK_API_KEY), 'uptime': int((datetime.now() - system_state.get_all()['start_time']).total_seconds()), 'timestamp': datetime.now().isoformat() } return health # Gradio Interface Functions def get_dashboard_data(): """Get current dashboard data""" stats = system_state.get_all() uptime = datetime.now() - stats['start_time'] dashboard_html = f"""

๐Ÿš— {BUSINESS_NAME} - AI Dashboard

{stats['calls_today']}

๐Ÿ“ž Calls Today

{stats['sms_today']}

๐Ÿ“ฑ SMS Today

{stats['calls_forwarded']}

๐Ÿ”„ Calls Forwarded

{stats['ai_responses']}

๐Ÿค– AI Responses

๐Ÿ“ž iPhone Forwarding Status

Jay's iPhone: {JAY_PHONE}

AI Assistant Number: {SIGNALWIRE_PHONE}

Forwarding: {'โœ… Enabled' if FORWARDING_ENABLED else 'โŒ Disabled'}

Delay: {FORWARDING_DELAY} seconds

SignalWire: {'โœ… Connected' if signalwire_client else 'โŒ Not configured'}

DeepSeek AI: {'โœ… Connected' if DEEPSEEK_API_KEY else 'โš ๏ธ Fallback mode'}

๐Ÿ’ผ Services & Pricing

""" for service_id, service in SERVICES.items(): dashboard_html += f"""

{service['name']}

{service['description']}

${service['price']:.0f}

""" dashboard_html += """
""" return dashboard_html def get_call_log(): """Get recent call log""" stats = system_state.get_all() call_log = stats.get('call_log', []) if not call_log: return "No calls logged yet." log_html = """

๐Ÿ“ž Recent Calls

""" for call in call_log[:10]: # Show last 10 calls log_html += f"""
{call['time']} | {call['from']} | {call['type']} | {call['status']}
""" log_html += "
" return log_html def get_sms_log(): """Get recent SMS log""" stats = system_state.get_all() sms_log = stats.get('sms_log', []) if not sms_log: return "No SMS logged yet." log_html = """

๐Ÿ“ฑ Recent SMS

""" for sms in sms_log[:10]: # Show last 10 SMS log_html += f"""
{sms['time']} | {sms['from']}
{sms['message']} | {sms['status']}
""" log_html += "
" return log_html def test_ai_response(message): """Test AI response generation""" if not message.strip(): return "Please enter a message to test." try: intent = ai.detect_intent(message) sentiment = ai.analyze_sentiment(message) response = ai.generate_response(message, {'test': True}) result = f"""

AI Response Test

Customer Message: {message}

Detected Intent: {intent}

Sentiment: {sentiment['label']} ({sentiment['score']:.2f})

AI Response:
{response}
""" return result except Exception as e: return f"Error testing AI response: {e}" # Start FastAPI in background thread def run_fastapi(): uvicorn.run(fastapi_app, host="0.0.0.0", port=8000, log_level="info") fastapi_thread = threading.Thread(target=run_fastapi, daemon=True) fastapi_thread.start() # Create Gradio Interface with gr.Blocks( title=f"{BUSINESS_NAME} - AI Dashboard", theme=gr.themes.Soft(), css=""" .gradio-container { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } """ ) as interface: gr.Markdown(f""" # ๐Ÿš— {BUSINESS_NAME} - iPhone Forwarding AI System **Real-time dashboard for your AI assistant with iPhone call forwarding** ๐Ÿ“ž **Jay's iPhone:** {JAY_PHONE} ๐Ÿค– **AI Assistant:** {SIGNALWIRE_PHONE} ๐Ÿ”„ **Forwarding:** {'โœ… Enabled' if FORWARDING_ENABLED else 'โŒ Disabled'} """) with gr.Tabs(): with gr.Tab("๐Ÿ“Š Dashboard"): dashboard_display = gr.HTML(value=get_dashboard_data()) refresh_btn = gr.Button("๐Ÿ”„ Refresh Dashboard", variant="primary") refresh_btn.click(fn=get_dashboard_data, outputs=dashboard_display) with gr.Tab("๐Ÿ“ž Call Log"): call_log_display = gr.HTML(value=get_call_log()) refresh_calls_btn = gr.Button("๐Ÿ”„ Refresh Calls", variant="secondary") refresh_calls_btn.click(fn=get_call_log, outputs=call_log_display) with gr.Tab("๐Ÿ“ฑ SMS Log"): sms_log_display = gr.HTML(value=get_sms_log()) refresh_sms_btn = gr.Button("๐Ÿ”„ Refresh SMS", variant="secondary") refresh_sms_btn.click(fn=get_sms_log, outputs=sms_log_display) with gr.Tab("๐Ÿงช Test AI"): with gr.Row(): with gr.Column(): test_message = gr.Textbox( label="Test Message", placeholder="Enter a customer message to test AI response...", lines=3 ) test_btn = gr.Button("๐Ÿค– Test AI Response", variant="primary") with gr.Column(): test_result = gr.HTML(label="AI Response") test_btn.click(fn=test_ai_response, inputs=test_message, outputs=test_result) with gr.Tab("โ„น๏ธ Setup Info"): gr.Markdown(f""" ## ๐Ÿ”ง Webhook Configuration **Set these URLs in your SignalWire dashboard:** - **Voice Webhook:** `https://YOUR-SPACE-NAME.hf.space/voice/incoming` - **SMS Webhook:** `https://YOUR-SPACE-NAME.hf.space/sms/incoming` ## ๐Ÿ“ฑ iPhone Setup **On Jay's iPhone:** 1. Go to **Settings** โ†’ **Phone** โ†’ **Call Forwarding** 2. Turn **ON** Call Forwarding 3. Set forward number to: `{SIGNALWIRE_PHONE}` 4. Choose: Forward when **unanswered** after {FORWARDING_DELAY} seconds ## ๐ŸŽฏ How It Works 1. Customer calls Jay's iPhone: `{JAY_PHONE}` 2. iPhone rings for {FORWARDING_DELAY} seconds 3. If no answer โ†’ forwards to AI: `{SIGNALWIRE_PHONE}` 4. AI handles call with forwarding awareness 5. Can escalate back to Jay if needed ## ๐Ÿ“Š System Status - **SignalWire:** {'โœ… Connected' if signalwire_client else 'โŒ Not configured'} - **DeepSeek AI:** {'โœ… Connected' if DEEPSEEK_API_KEY else 'โš ๏ธ Using fallback responses'} - **FastAPI Webhooks:** โœ… Running on port 8000 - **Health Check:** [/health](./health) """) # Auto-refresh every 30 seconds interface.load(fn=get_dashboard_data, outputs=dashboard_display, every=30) interface.load(fn=get_call_log, outputs=call_log_display, every=30) interface.load(fn=get_sms_log, outputs=sms_log_display, every=30) # Launch the interface if __name__ == "__main__": print("\n" + "="*60) print("๐Ÿš— JAY'S MOBILE WASH AI SYSTEM") print("="*60) print(f"๐Ÿ“ž Jay's Phone: {JAY_PHONE}") print(f"๐Ÿค– AI Phone: {SIGNALWIRE_PHONE}") print(f"๐Ÿ”„ Forwarding: {'โœ… Enabled' if FORWARDING_ENABLED else 'โŒ Disabled'}") print(f"๐ŸŒ SignalWire: {'โœ… Connected' if signalwire_client else 'โŒ Not configured'}") print(f"๐Ÿง  DeepSeek: {'โœ… Connected' if DEEPSEEK_API_KEY else 'โŒ Not configured'}") print("="*60) print("๐Ÿš€ Starting Gradio interface on port 7860...") print("๐Ÿ“ž FastAPI webhooks running on port 8000") print("="*60) interface.launch( server_name="0.0.0.0", server_port=7860, share=False, show_error=True )