Adding DB to Whatsaap AI
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
42334746bb
commit
f2180483f0
@ -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})
|
||||
|
@ -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):
|
||||
|
@ -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 %}
|
||||
|
34
pxy_whatsapp/migrations/0004_conversation_message.py
Normal file
34
pxy_whatsapp/migrations/0004_conversation_message.py
Normal 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')),
|
||||
],
|
||||
),
|
||||
]
|
@ -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]}…"
|
||||
|
||||
|
@ -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
15
tests/test_payload.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"entry": [{
|
||||
"changes": [{
|
||||
"value": {
|
||||
"metadata": { "phone_number_id": "1234567890" },
|
||||
"messages": [{
|
||||
"from": "5491123456789",
|
||||
"type": "text",
|
||||
"text": { "body": "Prueba desde curl" }
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user