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
|
# Operation – Physical & Social Digital Twin
|
||||||
apps_urban_digital_twin = AppsView.as_view(template_name="pxy_dashboard/apps/apps-urban-digital-twin.html")
|
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_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_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")
|
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_subdivision": selected_subdivision,
|
||||||
"selected_route": selected_route,
|
"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/",
|
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):
|
class LoginRequiredMiddleware(MiddlewareMixin):
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
|
@ -1,5 +1,55 @@
|
|||||||
{% extends "pxy_dashboard/partials/base.html" %}
|
{% extends "pxy_dashboard/partials/base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Whatsapp Bot</h2>
|
{% include "pxy_dashboard/partials/dashboard/kpi_row.html" %}
|
||||||
<p>This is a placeholder page for <code>apps-whatsapp-bot.html</code></p>
|
|
||||||
|
<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 %}
|
{% 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):
|
def __str__(self):
|
||||||
return self.name
|
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 django.shortcuts import get_object_or_404
|
||||||
from pxy_openai.assistants import OpenAIAssistant
|
from pxy_openai.assistants import OpenAIAssistant
|
||||||
from .models import WhatsAppBot
|
from .models import WhatsAppBot
|
||||||
|
from .models import WhatsAppBot, Conversation, Message
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -81,19 +84,54 @@ def webhook(request):
|
|||||||
sender_number = message["from"]
|
sender_number = message["from"]
|
||||||
|
|
||||||
logger.info(f"Received phone_number_id from webhook payload: {phone_number_id}")
|
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)
|
assistant = OpenAIAssistant(name=bot.assistant.name)
|
||||||
|
start = timezone.now()
|
||||||
try:
|
try:
|
||||||
bot_response = assistant.handle_message(user_message)
|
bot_response = assistant.handle_message(user_message)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
bot_response = f"Assistant error: {e}"
|
bot_response = f"Assistant error: {e}"
|
||||||
logger.error(bot_response)
|
logger.error(bot_response)
|
||||||
|
end = timezone.now()
|
||||||
|
|
||||||
# Send the response back to the user
|
# 5) Send the response back to the user
|
||||||
send_whatsapp_message(bot.phone_number_id, sender_number, bot_response, bot.graph_api_token)
|
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:
|
except Exception as e:
|
||||||
logger.error(f"Error processing webhook: {e}")
|
logger.error(f"Error processing webhook: {e}")
|
||||||
@ -103,6 +141,7 @@ def webhook(request):
|
|||||||
return HttpResponse("Method Not Allowed", status=405)
|
return HttpResponse("Method Not Allowed", status=405)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Webhook Verification Endpoint
|
# Webhook Verification Endpoint
|
||||||
@require_http_methods(["GET"])
|
@require_http_methods(["GET"])
|
||||||
def webhook_verification(request):
|
def webhook_verification(request):
|
||||||
@ -126,3 +165,22 @@ def root(request):
|
|||||||
A root endpoint for basic connectivity testing.
|
A root endpoint for basic connectivity testing.
|
||||||
"""
|
"""
|
||||||
return HttpResponse("<pre>Nothing to see here.\nCheckout README.md to start.</pre>", content_type="text/html")
|
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