from django.shortcuts import render, get_object_or_404 from django.contrib.auth.decorators import login_required from .models import Business, Service, Call, Booking, Contact from django.db.models import Count from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt import json from twilio.twiml.voice_response import VoiceResponse, Gather from django.urls import reverse from ai.local_ai_api import LocalAIApi from .booking import create_booking from datetime import datetime #@login_required def dashboard(request): """Render the main admin dashboard.""" businesses = Business.objects.annotate( service_count=Count('services', distinct=True), call_count=Count('calls', distinct=True), booking_count=Count('services__bookings', distinct=True) ) recent_calls = Call.objects.order_by('-start_time')[:10] upcoming_bookings = Booking.objects.filter(start_time__gte=datetime.now()).order_by('start_time')[:10] context = { 'businesses': businesses, 'recent_calls': recent_calls, 'upcoming_bookings': upcoming_bookings, 'page_title': 'Dashboard - Xfront' } return render(request, 'core/index.html', context) def business_detail(request, business_id): """Display a detailed view of a business.""" business = get_object_or_404(Business, pk=business_id) services = business.services.all() calls = business.calls.all().order_by('-start_time')[:10] bookings = Booking.objects.filter(service__business=business).order_by('-start_time')[:10] context = { 'business': business, 'services': services, 'calls': calls, 'bookings': bookings, 'page_title': f'{business.business_name} - Details' } return render(request, 'core/business_detail.html', context) @csrf_exempt def inbound_call_webhook(request): """Handle inbound calls from Twilio.""" if request.method == 'POST': from_number = request.POST.get('From') to_number = request.POST.get('To') try: business = Business.objects.get(phone_number=to_number) except Business.DoesNotExist: response = VoiceResponse() response.say("The number you have called is not associated with a business.") return HttpResponse(str(response), content_type='text/xml') contact, _ = Contact.objects.get_or_create(phone_number=from_number) call = Call.objects.create( business=business, contact=contact, ) response = VoiceResponse() response.say(f"Hello and welcome to {business.business_name}. Please wait while we connect you to our AI assistant.") response.redirect(reverse('ai_call_handler', args=[call.id])) return HttpResponse(str(response), content_type='text/xml') return HttpResponse(status=400) tools = [ { "type": "function", "function": { "name": "create_booking", "description": "Creates a booking for a given service at a specific time.", "parameters": { "type": "object", "properties": { "service_name": { "type": "string", "description": "The name of the service to book.", }, "booking_time_str": { "type": "string", "description": "The desired time for the booking in ISO 8601 format.", }, }, "required": ["service_name", "booking_time_str"], }, }, } ] @csrf_exempt def ai_call_handler(request, call_id): """Handle the AI-powered conversation.""" call = get_object_or_404(Call, pk=call_id) response = VoiceResponse() if 'SpeechResult' in request.POST: user_speech = request.POST['SpeechResult'] call.conversation_history.append({'role': 'user', 'content': user_speech}) services = Service.objects.filter(business=call.business) services_prompt = "\n".join([f"- {s.name}: {s.description}" for s in services]) # Get AI response ai_response = LocalAIApi.create_response( { "input": [ { "role": "system", "content": f"You are a friendly and helpful AI assistant for {call.business.business_name}. Available services are:\n{services_prompt}" }, *call.conversation_history ], "tools": tools, "tool_choice": "auto", }, { "poll_interval": 5, "poll_timeout": 300, }, ) if ai_response.get("success") and ai_response.get("data", {}).get("output", [{}])[0].get("content", [{}])[0].get("type") == "tool_calls": tool_calls = ai_response["data"]["output"][0]["content"][0]["tool_calls"] call.conversation_history.append({"role": "assistant", "content": None, "tool_calls": tool_calls}) for tool_call in tool_calls: function_name = tool_call['function']['name'] if function_name == 'create_booking': args = json.loads(tool_call['function']['arguments']) result = create_booking( contact_phone_number=call.contact.phone_number, service_name=args.get("service_name"), booking_time_str=args.get("booking_time_str"), ) call.conversation_history.append({"role": "tool", "tool_call_id": tool_call['id'], "name": function_name, "content": result}) # Get AI response after tool call ai_response = LocalAIApi.create_response( { "input": [ { "role": "system", "content": f"You are a friendly and helpful AI assistant for {call.business.business_name}. Available services are:\n{services_prompt}" }, *call.conversation_history ], "tools": tools, "tool_choice": "auto", }, { "poll_interval": 5, "poll_timeout": 300, }, ) ai_text = LocalAIApi.extract_text(ai_response) else: ai_text = LocalAIApi.extract_text(ai_response) if not ai_text: ai_text = "I am sorry, I am having trouble understanding. Please try again." call.conversation_history.append({'role': 'assistant', 'content': ai_text}) response.say(ai_text) else: # First turn of the conversation response.say("How can I help you today?") gather = Gather(input='speech', action=reverse('ai_call_handler', args=[call.id]), speechTimeout='auto') response.append(gather) call.save() return HttpResponse(str(response), content_type='text/xml')