Adding DB to Whatsaap AI
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Ekaropolus 2025-05-19 23:04:20 -06:00
parent 42334746bb
commit f2180483f0
7 changed files with 221 additions and 10 deletions

View File

@ -31,7 +31,7 @@ apps_dispatch_plan = AppsView.as_view(template_name="pxy_dashboard/apps/apps-dis
# Operation Physical & Social Digital Twin
apps_urban_digital_twin = AppsView.as_view(template_name="pxy_dashboard/apps/apps-urban-digital-twin.html")
apps_whatsapp_bot = AppsView.as_view(template_name="pxy_dashboard/apps/apps-whatsapp-bot.html")
#apps_whatsapp_bot = AppsView.as_view(template_name="pxy_dashboard/apps/apps-whatsapp-bot.html")
apps_telegram_bot = AppsView.as_view(template_name="pxy_dashboard/apps/apps-telegram-bot.html")
apps_facebook_pages_bot = AppsView.as_view(template_name="pxy_dashboard/apps/apps-facebook-pages-bot.html")
apps_feedback_loop = AppsView.as_view(template_name="pxy_dashboard/apps/apps-feedback-loop.html")
@ -268,3 +268,14 @@ def dispatch_plan_view(request):
"selected_subdivision": selected_subdivision,
"selected_route": selected_route,
})
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
import requests
@login_required
def apps_whatsapp_bot(request):
stats = request.user.has_perm("pxy_whatsapp.view_whatsappstats") and \
requests.get(request.build_absolute_uri("/whatsapp/stats/"), cookies=request.COOKIES).json()
return render(request, "pxy_dashboard/apps/apps-whatsapp-bot.html", {"stats": stats})

View File

@ -22,6 +22,17 @@ EXEMPT_URLS += [re.compile(expr) for expr in [
r"^media/",
]]
# ————— aquí añadimos los webhooks de WhatsApp —————
# Como path_info.lstrip("/") será "whatsapp/webhook/" y "whatsapp/webhook/verify/"
EXEMPT_URLS += [
"pxy_whatsapp/webhook/",
"pxy_whatsapp/webhook/verify/",
]
EXEMPT_URLS += [
re.compile(r"^pxy_whatsapp/webhook/?$"),
re.compile(r"^pxy_whatsapp/webhook/verify/?$"),
]
class LoginRequiredMiddleware(MiddlewareMixin):
def process_request(self, request):

View File

@ -1,5 +1,55 @@
{% extends "pxy_dashboard/partials/base.html" %}
{% block content %}
<h2>Whatsapp Bot</h2>
<p>This is a placeholder page for <code>apps-whatsapp-bot.html</code></p>
{% include "pxy_dashboard/partials/dashboard/kpi_row.html" %}
<div class="row mb-4">
<div class="col">
<div class="card text-center">
<div class="card-body">
<h3>{{ stats.total_conversations }}</h3>
<p>Conversaciones totales</p>
</div>
</div>
</div>
<div class="col">
<div class="card text-center">
<div class="card-body">
<h3>{{ stats.messages_in }}</h3>
<p>Mensajes recibidos (24h)</p>
</div>
</div>
</div>
<div class="col">
<div class="card text-center">
<div class="card-body">
<h3>{{ stats.messages_out }}</h3>
<p>Mensajes enviados (24h)</p>
</div>
</div>
</div>
<div class="col">
<div class="card text-center">
<div class="card-body">
<h3>{{ stats.avg_response_time|floatformat:0 }} ms</h3>
<p>Tiempo de respuesta promedio</p>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header"><h5>Actividad por hora</h5></div>
<div class="card-body">
<div id="chart-whatsapp-activity" class="apex-charts"></div>
</div>
</div>
{% block extra_js %}
<script src="{% static 'dashboard/vendor/apexcharts/apexcharts.min.js' %}"></script>
<script>
// Aquí harías un fetch("/whatsapp/activity/") para datos por hora
// y luego construyes un gráfico de barras/apexcharts como en Route Optimization
</script>
{% endblock %}
{% endblock %}

View File

@ -0,0 +1,34 @@
# Generated by Django 5.0.3 on 2025-05-20 03:14
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pxy_whatsapp', '0003_whatsappbot_assistant'),
]
operations = [
migrations.CreateModel(
name='Conversation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('user_number', models.CharField(max_length=32)),
('started_at', models.DateTimeField(auto_now_add=True)),
('bot', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='conversations', to='pxy_whatsapp.whatsappbot')),
],
),
migrations.CreateModel(
name='Message',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('direction', models.CharField(choices=[('in', 'In'), ('out', 'Out')], max_length=4)),
('content', models.TextField()),
('timestamp', models.DateTimeField(auto_now_add=True)),
('response_time_ms', models.IntegerField(blank=True, null=True)),
('conversation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='pxy_whatsapp.conversation')),
],
),
]

View File

@ -16,3 +16,35 @@ class WhatsAppBot(models.Model):
def __str__(self):
return self.name
class Conversation(models.Model):
bot = models.ForeignKey(
'WhatsAppBot',
on_delete=models.CASCADE,
related_name='conversations'
)
user_number = models.CharField(max_length=32)
started_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.user_number} @ {self.started_at:%Y-%m-%d %H:%M}"
class Message(models.Model):
DIRECTION_CHOICES = [
('in', 'In'),
('out', 'Out'),
]
conversation = models.ForeignKey(
Conversation,
on_delete=models.CASCADE,
related_name='messages'
)
direction = models.CharField(max_length=4, choices=DIRECTION_CHOICES)
content = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
response_time_ms = models.IntegerField(null=True, blank=True)
def __str__(self):
return f"[{self.direction}] {self.content[:30]}"

View File

@ -8,6 +8,9 @@ from django.views.decorators.http import require_http_methods
from django.shortcuts import get_object_or_404
from pxy_openai.assistants import OpenAIAssistant
from .models import WhatsAppBot
from .models import WhatsAppBot, Conversation, Message
from django.utils import timezone
logger = logging.getLogger(__name__)
@ -81,19 +84,54 @@ def webhook(request):
sender_number = message["from"]
logger.info(f"Received phone_number_id from webhook payload: {phone_number_id}")
# Fetch the appropriate bot configuration
bot = get_object_or_404(WhatsAppBot, phone_number_id=phone_number_id, is_active=True)
# Initialize the assistant and get a response
# 1) Fetch the active bot
bot = get_object_or_404(
WhatsAppBot,
phone_number_id=phone_number_id,
is_active=True
)
# 2) Get or create Conversation
conv, _ = Conversation.objects.get_or_create(
bot=bot,
user_number=sender_number,
defaults={'started_at': timezone.now()}
)
# 3) Save inbound message
Message.objects.create(
conversation=conv,
direction="in",
content=user_message
)
# 4) Generate assistant response and measure time
assistant = OpenAIAssistant(name=bot.assistant.name)
start = timezone.now()
try:
bot_response = assistant.handle_message(user_message)
except Exception as e:
bot_response = f"Assistant error: {e}"
logger.error(bot_response)
end = timezone.now()
# Send the response back to the user
send_whatsapp_message(bot.phone_number_id, sender_number, bot_response, bot.graph_api_token)
# 5) Send the response back to the user
send_whatsapp_message(
phone_number_id,
sender_number,
bot_response,
bot.graph_api_token
)
# 6) Save outbound message with response time
resp_ms = int((end - start).total_seconds() * 1000)
Message.objects.create(
conversation=conv,
direction="out",
content=bot_response,
response_time_ms=resp_ms
)
except Exception as e:
logger.error(f"Error processing webhook: {e}")
@ -103,6 +141,7 @@ def webhook(request):
return HttpResponse("Method Not Allowed", status=405)
# Webhook Verification Endpoint
@require_http_methods(["GET"])
def webhook_verification(request):
@ -126,3 +165,22 @@ def root(request):
A root endpoint for basic connectivity testing.
"""
return HttpResponse("<pre>Nothing to see here.\nCheckout README.md to start.</pre>", content_type="text/html")
from django.contrib.auth.decorators import login_required
from django.db.models import Avg
from .models import Conversation, Message
@login_required
def whatsapp_stats(request):
from django.utils import timezone
since = timezone.now() - timezone.timedelta(days=1)
total_convos = Conversation.objects.count()
msgs_in = Message.objects.filter(direction="in", timestamp__gte=since).count()
msgs_out = Message.objects.filter(direction="out", timestamp__gte=since).count()
avg_rt = Message.objects.filter(direction="out", response_time_ms__isnull=False).aggregate(Avg("response_time_ms"))
return JsonResponse({
"total_conversations": total_convos,
"messages_in": msgs_in,
"messages_out": msgs_out,
"avg_response_time": avg_rt["response_time_ms__avg"] or 0,
})

15
tests/test_payload.json Normal file
View File

@ -0,0 +1,15 @@
{
"entry": [{
"changes": [{
"value": {
"metadata": { "phone_number_id": "1234567890" },
"messages": [{
"from": "5491123456789",
"type": "text",
"text": { "body": "Prueba desde curl" }
}]
}
}]
}]
}