188 lines
7.0 KiB
Python
188 lines
7.0 KiB
Python
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')
|