This commit is contained in:
Marwan Alwali 2024-12-19 20:04:22 +03:00
parent 2b74f92ab6
commit 152518ebdc
21 changed files with 2781 additions and 1878 deletions

View File

@ -61,6 +61,7 @@ INSTALLED_APPS = [
'django_ledger',
'djmoney',
'sslserver',
'haikalbot',
]

View File

@ -13,6 +13,7 @@ urlpatterns = [
path('api/', include('api.urls')),
path('dj-rest-auth/', include('dj_rest_auth.urls')),
]
urlpatterns += i18n_patterns(
path('admin/', admin.site.urls),
@ -21,7 +22,7 @@ urlpatterns += i18n_patterns(
path('prometheus/', include('django_prometheus.urls')),
path('', include('inventory.urls')),
path('ledger/', include('django_ledger.urls', namespace='django_ledger')),
path("haikalbot/", include("haikalbot.urls")),
)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

0
haikalbot/__init__.py Normal file
View File

3
haikalbot/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
haikalbot/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class HaikalbotConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'haikalbot'

View File

@ -0,0 +1,83 @@
import re
from nltk.tokenize import word_tokenize
from django.db.models import Q
from inventory.models import Car # Import your car-related models
import nltk
# Download required NLTK resources
try:
nltk.download("punkt")
except ImportError:
raise ImportError("Ensure nltk is installed by running 'pip install nltk'.")
# Static responses for predefined intents
RESPONSES = {
"greet": ["Hello! How can I assist you today with Haikal Car Inventory?"],
"inventory_check": ["You can check the car inventory in the Cars section. Do you want to search for a specific car?"],
"car_status": ["If a car is sold, it will either be marked as SOLD or removed from the inventory, as per your preferences."],
"sell_process": ["To sell a car, the process involves creating a Sell Order, adding the customer, confirming payment, generating an invoice, and finally delivering the car."],
"transfer_process": ["Dealers can transfer cars to other branches or dealers for display or sale. This is handled through Sell Orders as well."],
"bye": ["Goodbye! If you need further assistance, just ask!"],
}
def clean_input(user_input):
"""
Clean and tokenize user input.
"""
user_input = user_input.lower()
user_input = re.sub(r"[^\w\s]", "", user_input) # Remove punctuation
return word_tokenize(user_input)
def classify_input(tokens):
"""
Classify user intent based on tokens.
"""
if any(word in tokens for word in ["hello", "hi", "hey"]):
return "greet"
elif any(word in tokens for word in ["inventory", "cars", "check"]):
return "inventory_check"
elif any(word in tokens for word in ["sell", "sold", "process"]):
return "sell_process"
elif any(word in tokens for word in ["transfer", "branch", "display"]):
return "transfer_process"
elif any(word in tokens for word in ["bye", "goodbye", "exit"]):
return "bye"
elif any(word in tokens for word in ["price", "cost", "value"]):
return "car_price"
else:
return "unknown"
def get_dynamic_response(intent, tokens):
"""
Generate dynamic responses by querying the database.
"""
if intent == "car_price":
# Extract car name from tokens
car_name = " ".join([word.capitalize() for word in tokens])
try:
car = Car.objects.filter(Q(make__icontains=car_name) | Q(model__icontains=car_name)).first()
if car:
return f"The price of {car_name} is {car.finance.selling_price}."
return f"Sorry, no car matching '{car_name}' was found in the inventory."
except Exception as e:
return f"An error occurred while retrieving the car price: {str(e)}"
return None
def get_response(user_input):
"""
Generate a response based on the user's input.
"""
tokens = clean_input(user_input)
intent = classify_input(tokens)
# Check for a dynamic response
dynamic_response = get_dynamic_response(intent, tokens)
if dynamic_response:
return dynamic_response
# Return a static response if available
if intent in RESPONSES:
return RESPONSES[intent][0]
# Default response for unknown intents
return "I'm sorry, I didn't understand that. Could you rephrase your question about the Haikal system?"

View File

@ -0,0 +1,26 @@
# Generated by Django 5.1.4 on 2024-12-18 16:49
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('inventory', '0030_alter_carfinance_options_and_more'),
]
operations = [
migrations.CreateModel(
name='ChatLog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('user_message', models.TextField()),
('chatbot_response', models.TextField()),
('timestamp', models.DateTimeField(auto_now_add=True)),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chatlogs', to='inventory.dealer')),
],
),
]

View File

12
haikalbot/models.py Normal file
View File

@ -0,0 +1,12 @@
from django.db import models
from inventory.models import Dealer
class ChatLog(models.Model):
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name='chatlogs')
user_message = models.TextField()
chatbot_response = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.user_message

3
haikalbot/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

7
haikalbot/urls.py Normal file
View File

@ -0,0 +1,7 @@
from django.urls import path
from . import views
urlpatterns = [
path("", views.ChatbotView.as_view(), name="chatbot"),
]

35
haikalbot/views.py Normal file
View File

@ -0,0 +1,35 @@
from django.views import View
from django.shortcuts import render
from django.http import JsonResponse
from .chatbot_logic import get_response
import json
class ChatbotView(View):
"""
Class-based view to handle chatbot template rendering (GET)
and GPT-4-based chatbot API responses (POST).
"""
def get(self, request):
"""
Render the chatbot interface template.
"""
return render(request, "haikalbot/chatbot.html")
def post(self, request):
"""
Handle chatbot API requests and return responses as JSON.
"""
try:
# Parse JSON payload from the request body
data = json.loads(request.body)
user_message = data.get("message", "").strip()
if not user_message:
return JsonResponse({"error": "Message cannot be empty."}, status=400)
# Get GPT-4 response
chatbot_response = get_response(user_message)
return JsonResponse({"response": chatbot_response}, status=200)
except json.JSONDecodeError:
return JsonResponse({"error": "Invalid JSON format."}, status=400)

View File

@ -962,5 +962,3 @@ class RepresentativeDeleteView(LoginRequiredMixin, DeleteView):
template_name = 'representatives/representative_confirm_delete.html'
success_url = reverse_lazy('representative_list')
success_message = "Representative deleted successfully."

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +1,30 @@
alabaster==1.0.0
annotated-types==0.7.0
anyio==4.6.2.post1
anyio==4.7.0
arabic-reshaper==3.0.0
asgiref==3.8.1
astroid==3.3.5
astroid==3.3.6
attrs==23.2.0
autopep8==2.3.1
babel==2.16.0
certifi==2024.8.30
beautifulsoup4==4.12.3
bleach==6.2.0
blinker==1.9.0
certifi==2024.12.14
cffi==1.17.1
chardet==5.2.0
charset-normalizer==3.4.0
click==8.1.7
colorama==0.4.6
commonmark==0.9.1
crispy-bootstrap5==2024.10
cryptography==44.0.0
desert==2020.11.18
dill==0.3.9
distro==1.9.0
dj-rest-auth==7.0.0
dj-shop-cart==7.1.1
Django
Django==5.1.4
django-allauth==65.3.0
django-autoslug==1.9.9
django-bootstrap5==24.3
@ -29,83 +37,133 @@ django-filter==24.3
django-formtools==2.5.1
django-ledger==0.7.0
django-money==3.5.3
django-nine==0.2.7
django-nonefield==0.4
django-phonenumber-field==8.0.0
django-prometheus==2.3.1
django-sekizai==4.1.0
django-silk==5.3.1
django-silk==5.3.2
django-sslserver==0.22
django-tables2==2.7.0
django-treebeard==4.7.1
django-view-breadcrumbs==2.5.1
djangocms-admin-style==3.3.1
djangorestframework==3.15.2
djangorestframework-simplejwt==5.3.1
djangoviz==0.1.1
docutils==0.21.2
easy-thumbnails==2.10
et_xmlfile==2.0.0
Faker==33.1.0
Flask==3.1.0
gprof2dot==2024.6.6
graphqlclient==0.2.4
h11==0.14.0
httpcore==1.0.7
httpx==0.28.0
httpx==0.28.1
idna==3.10
imagesize==1.4.1
iso4217==1.12.20240625
isodate==0.7.2
isort==5.13.2
itsdangerous==2.2.0
Jinja2==3.1.4
jiter==0.8.0
jiter==0.8.2
joblib==1.4.2
ledger==1.0.1
lxml==5.3.0
Markdown==3.7
MarkupSafe==3.0.2
marshmallow==3.23.2
mccabe==0.7.0
newrelic==10.3.1
MouseInfo==0.1.3
mypy-extensions==1.0.0
newrelic==10.4.0
nltk==3.9.1
numpy==2.2.0
oauthlib==3.2.2
ofxtools==0.9.5
openai==1.56.2
openai==1.58.1
openpyxl==3.1.5
outcome==1.3.0.post0
packaging==24.2
pandas==2.2.3
pdfkit==1.0.0
phonenumbers==8.13.51
phonenumbers==8.13.52
pillow==11.0.0
platformdirs==4.3.6
prometheus_client==0.21.1
psycopg==3.2.3
psycopg-binary==3.2.3
psycopg-c==3.2.3
py-moneyed==3.0
PyAutoGUI==0.9.54
pycodestyle==2.12.1
pycparser==2.22
pydantic==2.10.3
pydantic_core==2.27.1
pydantic==2.10.4
pydantic_core==2.27.2
pydotplus==2.0.2
PyGetWindow==0.0.9
Pygments==2.18.0
PyJWT==2.10.1
pylint==3.3.2
PyMsgBox==1.0.9
PyMySQL==1.1.1
pyobjc-core==10.3.2
pyobjc-framework-Cocoa==10.3.2
pyobjc-framework-Quartz==10.3.2
pyparsing==3.2.0
pyperclip==1.9.0
pypng==0.20220715.0
PyRect==0.2.0
PyScreeze==1.0.1
pyserial==3.5
PySocks==1.7.1
python-bidi==0.6.3
python-dateutil==2.9.0.post0
python-openid==2.2.5
python3-saml==1.16.0
pytweening==1.2.0
pytz==2024.2
pyvin==0.0.2
pywa==2.4.0
pywhat==5.1.0
pywhatkit==5.4
qrcode==8.0
regex==2024.11.6
reportlab==4.2.5
requests==2.32.3
requests-oauthlib==2.0.0
six==1.16.0
rich==10.16.2
rubicon-objc==0.4.9
scikit-learn==1.6.0
scipy==1.14.1
selenium==4.27.1
six==1.17.0
sniffio==1.3.1
snowballstemmer==2.2.0
sqlparse==0.5.2
sortedcontainers==2.4.0
soupsieve==2.6
SQLAlchemy==2.0.36
sqlparse==0.5.3
tablib==3.7.0
threadpoolctl==3.5.0
tomlkit==0.13.2
tqdm==4.67.1
trio==0.27.0
trio-websocket==0.11.1
typing-inspect==0.9.0
typing_extensions==4.12.2
tzdata==2024.2
Unidecode==1.3.8
upgrade-requirements==1.7.0
urllib3==2.2.3
vin==0.6.2
vininfo==1.8.0
vishap==0.1.5
vpic-api==0.7.4
webencodings==0.5.1
websocket-client==1.8.0
Werkzeug==3.1.3
wikipedia==1.4.0
wsproto==1.2.0
xmlsec==1.3.14

View File

@ -0,0 +1,90 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}{{ _("HaikalBot") }}{% endblock title %}
{% block content %}
<style>
#chatbox {
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
height: 200px;
overflow-y: scroll;
background-color: #f9f9f9;
}
</style>
<div class="container p-2">
<div class="card shadow-sm rounded shadow">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">{% trans 'HaikalBot' %}</h4>
</div>
<div class="card-body">
<div id="chatbox">
<p><b>{% trans 'HaikalBot' %}:</b> {% trans 'Hello! How can I assist you today?' %}</p>
</div>
<label for="userMessage"></label>
<input class="form-control form-control-sm"
type="text" id="userMessage"
placeholder="{% trans 'Type your message here...' %}" />
<button class="btn btn-sm btn-success m-2" onclick="sendMessage()">{% trans 'Send' %}</button>
</div>
</div>
<!-- Script to send to api -->
<script>
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
cookie = cookie.trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrfToken = getCookie('csrftoken');
async function sendMessage() {
const userMessage = document.getElementById("userMessage").value;
if (!userMessage.trim()) {
alert("Please enter a message.");
return;
}
const response = await fetch("", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": csrfToken,
},
body: JSON.stringify({ message: userMessage }),
});
if (response) {
const data = await response.json();
const chatbox = document.getElementById("chatbox");
chatbox.innerHTML += `<p><b>{% trans 'You' %}:</b> ${userMessage}</p>`;
chatbox.innerHTML += `<p><b>{% trans 'HaikalBot' %}:</b> ${data.response}</p>`;
document.getElementById("userMessage").value = "";
chatbox.scrollTop = chatbox.scrollHeight;
} else {
alert("An error occurred.");
}
}
</script>
</div>
{% endblock content %}

67
whatsapp_bot.py Normal file
View File

@ -0,0 +1,67 @@
import re
import arabic_reshaper
from bidi.algorithm import get_display
from pywa import WhatsApp
# Initialize the WhatsApp bot
bot = WhatsApp(
phone_id="00966535521547",
token="c446eba6-fcfe-437c-923a-a554c25578dd"
)
# Pre-defined responses in English and Arabic
responses = {
"greet": {
"en": "Hello! How can I assist you with Haikal Car Inventory?",
"ar": get_display(arabic_reshaper.reshape("مرحباً! كيف يمكنني مساعدتك في نظام هيكل لإدارة السيارات؟"))
},
"sell_process": {
"en": "To sell a car: Create a Sell Order, add a customer, confirm payment, generate an invoice, and deliver the car.",
"ar": get_display(arabic_reshaper.reshape("لبيع السيارة: قم بإنشاء طلب بيع، إضافة العميل، تأكيد الدفع، إنشاء الفاتورة، وتسليم السيارة."))
},
"inventory_check": {
"en": "You can check available cars in the inventory. Do you want details on any specific car?",
"ar": get_display(arabic_reshaper.reshape("يمكنك التحقق من السيارات المتوفرة في المخزون. هل تريد تفاصيل عن سيارة معينة؟"))
},
"bye": {
"en": "Goodbye! Let me know if you need further help.",
"ar": get_display(arabic_reshaper.reshape("وداعاً! أخبرني إذا كنت بحاجة إلى مزيد من المساعدة."))
},
"unknown": {
"en": "I'm sorry, I didn't understand that. Can you rephrase?",
"ar": get_display(arabic_reshaper.reshape("عذراً، لم أفهم ذلك. هل يمكنك إعادة صياغة السؤال؟"))
}
}
# Function to classify user input
def classify_message(message):
message = message.lower()
if re.search(r"hello|hi|مرحب|السلام", message):
return "greet"
elif re.search(r"sell|بيع", message):
return "sell_process"
elif re.search(r"inventory|cars|المخزون|السيارات", message):
return "inventory_check"
elif re.search(r"bye|وداع|إلى اللقاء", message):
return "bye"
else:
return "unknown"
# Send response based on the intent
@bot.on_message()
def handle_message(message):
user_input = message.body.strip()
intent = classify_message(user_input)
# Detect language and respond
if re.search("[\u0600-\u06FF]", user_input): # Arabic characters
response = responses[intent]["ar"]
else: # Default to English
response = responses[intent]["en"]
message.reply(response)
# Start the bot
if __name__ == "__main__":
print("Starting the Haikal Car Inventory WhatsApp Bot...")
bot.run()