This commit is contained in:
gitea 2024-12-22 15:18:14 +00:00
commit faf5f14881
25 changed files with 102 additions and 148 deletions

View File

@ -1,5 +1,5 @@
from django.contrib import admin
from django.urls import path, include, re_path
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings
from django.conf.urls.i18n import i18n_patterns

View File

@ -1,83 +1,45 @@
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:
from openai import OpenAI
from inventory import models
from car_inventory import settings
nltk.download("punkt")
except ImportError:
raise ImportError("Ensure nltk is installed by running 'pip install nltk'.")
def fetch_data(dealer):
try:
# Annotate total cars by make, model, and trim
cars = models.Car.objects.filter(dealer=dealer).count()
print(cars)
if cars:
return f"إجمالي عدد السيارات في المخزون الخاص بـ {dealer.get_local_name}) هو {cars}"
# return f"The total cars in {dealer} inventory is ( {cars} )."
else:
return "No cars found in the inventory."
except Exception as e:
return f"An error occurred while fetching car data: {str(e)}"
# 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 get_gpt4_response(user_input, dealer):
dealer = dealer
client = OpenAI(api_key=settings.OPENAI_API_KEY)
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?"
if "سيارة في المخزون" in user_input.lower():
# cars = user_input.split("how many cars")[-1].strip()
car_data = fetch_data(dealer)
user_input += f" Relevant car data: {car_data}"
try:
completion = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": (
"You are an assistant specialized in car inventory management for the Haikal system. "
"You can answer questions about the inventory, sales process, car transfers, invoices, "
"and other application-specific functionalities. Always provide responses aligned "
"with the Haikal system's structure and features."
)
},
{"role": "user", "content": user_input},
],
)
return completion.choices[0].message.content.strip()
except Exception as e:
return f"An error occurred while generating the response: {str(e)}"

View File

@ -1,34 +1,25 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views import View
from django.shortcuts import render
from django.http import JsonResponse
from .chatbot_logic import get_response
from .chatbot_logic import get_gpt4_response
import json
class ChatbotView(View):
"""
Class-based view to handle chatbot template rendering (GET)
and GPT-4-based chatbot API responses (POST).
"""
class ChatbotView(LoginRequiredMixin, View):
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.
"""
dealer = request.user.dealer
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)
chatbot_response = get_gpt4_response(user_message, dealer)
return JsonResponse({"response": chatbot_response}, status=200)
except json.JSONDecodeError:

View File

@ -9,28 +9,31 @@ class Command(BaseCommand):
def handle(self, *args, **kwargs):
client = OpenAI(api_key=settings.OPENAI_API_KEY)
car_make = CarMake.objects.all()
total = car_make.count()
car_model = CarModel.objects.all()[0:1000]
total = car_model.count()
print(f'Translating {total} names...')
for index, car_make in enumerate(car_make, start=1):
for index, car_model in enumerate(car_model, start=1):
try:
completion = client.chat.completions.create(
model="gpt-4",
model="gpt-4o",
messages=[
{
"role": "system",
"content": "You are a translation assistant that translates English to Arabic."
"content": "You are an assistant that finds the Arabic Names for car models."
"Do not translate just write the arabic names."
"If the name is a number just write it as is."
"If you can't find the arabic name just write -"
},
{
"role": "user",
"content": car_make.name
"content": car_model.name
}
],
temperature=0.3,
temperature=0.2,
)
translation = completion.choices[0].message.content.strip()
car_make.arabic_name = translation
car_make.save()
print(f"[{index}/{total}] Translated '{car_make.name}' to '{translation}'")
car_model.arabic_name = translation
car_model.save()
print(f"[{index}/{total}] Translated '{car_model.name}' to '{translation}'")
except Exception as e:
print(f"Error translating '{car_make.name}': {e}")
print(f"Error translating '{car_model.name}': {e}")

View File

@ -47,6 +47,9 @@ urlpatterns = [
# Car URLs
path('cars/inventory/',
views.CarInventory.as_view(),
name='car_inventory_all'),
path('cars/inventory/<int:make_id>/<int:model_id>/<int:trim_id>/',
views.CarInventory.as_view(),
name='car_inventory'),

View File

@ -296,11 +296,11 @@ class CarInventory(LoginRequiredMixin, ListView):
ordering = ["receiving_date"]
def get_queryset(self, *args, **kwargs):
query = self.request.GET.get("q")
make_id = self.kwargs["make_id"]
model_id = self.kwargs["model_id"]
trim_id = self.kwargs["trim_id"]
query = self.request.GET.get('q')
make_id = self.kwargs['make_id']
model_id = self.kwargs['model_id']
trim_id = self.kwargs['trim_id']
cars = models.Car.objects.filter(
dealer=self.request.user.dealer.get_root_dealer,
id_car_make=make_id,
@ -355,32 +355,28 @@ def inventory_stats_view(request):
)
)
# Prepare the nested structure
inventory = {}
for car in cars:
# Make Level
make = car.id_car_make
if make.id_car_make not in inventory:
inventory[make.id_car_make] = {
"make_id": make.id_car_make,
"make_name": make.get_local_name(),
"total_cars": 0,
"models": {},
'make_id': make.id_car_make,
'make_name': make.get_local_name(),
'total_cars': 0,
'models': {}
}
inventory[make.id_car_make]["total_cars"] += 1
# Model Level
model = car.id_car_model
if model and model.id_car_model not in inventory[make.id_car_make]["models"]:
inventory[make.id_car_make]["models"][model.id_car_model] = {
"model_id": model.id_car_model,
"model_name": model.get_local_name(),
"total_cars": 0,
"trims": {},
if model and model.id_car_model not in inventory[make.id_car_make]['models']:
inventory[make.id_car_make]['models'][model.id_car_model] = {
'model_id': model.id_car_model,
'model_name': model.get_local_name(),
'total_cars': 0,
'trims': {}
}
inventory[make.id_car_make]["models"][model.id_car_model]["total_cars"] += 1
# Trim Level
trim = car.id_car_trim
if (
trim
@ -394,15 +390,14 @@ def inventory_stats_view(request):
trim.id_car_trim
]["total_cars"] += 1
# Convert to a list for easier template rendering
result = {
"total_cars": cars.count(),
"makes": [
{
"make_id": make_data["make_id"],
"make_name": make_data["make_name"],
"total_cars": make_data["total_cars"],
"models": [
'make_id': make_data['make_id'],
'make_name': make_data['make_name'],
'total_cars': make_data['total_cars'],
'models': [
{
"model_id": model_data["model_id"],
"model_name": model_data["model_name"],

BIN
static/.DS_Store vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

View File

@ -12,7 +12,6 @@
padding: 10px;
height: 200px;
overflow-y: scroll;
background-color: #f9f9f9;
}
</style>

View File

@ -346,7 +346,7 @@
<script>
document.addEventListener("DOMContentLoaded", function() {
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
@ -363,7 +363,7 @@ document.addEventListener("DOMContentLoaded", function() {
}
const csrfToken = getCookie('csrftoken');
const vinInput = document.getElementById('{{ form.vin.id_for_label }}');
const stockTypeSelect = document.getElementById('{{ form.stock_type.id_for_label }}');
const mileageInput = document.getElementById('{{ form.mileage.id_for_label }}');
@ -382,9 +382,9 @@ document.addEventListener("DOMContentLoaded", function() {
const serieBg = document.getElementById('serie-container');
const trimBg = document.getElementById('trim-container');
/*const saveCarBtn = document.getElementById('saveCarBtn');*/
const ajaxUrl = "{% url 'ajax_handler' %}";
const closeButton = document.querySelector(".btn-close");
const scanVinBtn = document.getElementById("scan-vin-btn");
const videoElement = document.getElementById('video');
@ -393,7 +393,7 @@ document.addEventListener("DOMContentLoaded", function() {
let codeReader;
codeReader = new ZXing.BrowserMultiFormatReader();
let currentStream = null;
function showLoading() {
Swal.fire({
title: "{% trans 'Please Wait' %}",
@ -424,7 +424,7 @@ function closeModal(){
console.error("Error closing scanner modal:", err);
}
}
async function decodeVin() {
const vinNumber = vinInput.value.trim();
if (vinNumber.length !== 17) {
@ -475,10 +475,10 @@ async function updateFields(vinData) {
}
/*checkFormCompletion();*/
}
// Start the scanner
async function startScanner() {
codeReader.decodeFromVideoDevice(null, videoElement, async(result, err) => {
async function startScanner() {
codeReader.decodeFromVideoDevice(null, videoElement, async(result, err) => {
let res = await result
if (result) {
vinInput.value = result.text;
@ -487,7 +487,7 @@ async function startScanner() {
}
}).catch(console.error);
}
function captureAndOCR() {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
@ -564,7 +564,7 @@ async function loadTrims(serie_id, model_id) {
specificationsContent.innerHTML = '';
const response = await fetch(`${ajaxUrl}?action=get_trims&serie_id=${serie_id}&model_id=${model_id}`, {
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRFToken': csrfToken
}
});
@ -586,7 +586,7 @@ async function loadSpecifications(trimId){
'X-CSRFToken': csrfToken
}
});
const data = await response.json();
const data = await response.json();
data.forEach((spec) => {
const parentDiv = document.createElement('div');
parentDiv.innerHTML = `<strong>${spec.parent_name}</strong>`;
@ -603,23 +603,23 @@ async function loadSpecifications(trimId){
resultDisplay.textContent = "";
startScanner();
});
fallbackButton.addEventListener('click', () => {
captureAndOCR();
});
serieSelect.addEventListener('change', () => {
const serie_id = serieSelect.value;
const model_id = modelSelect.value;
if (serie_id && model_id) loadTrims(serie_id, model_id);
});
trimSelect.addEventListener('change', () => {
const trimId = trimSelect.value;
const trimId = trimSelect.value;
showSpecificationButton.disabled = !trimId;
if (trimId) loadSpecifications(trimId);
});
closeButton.addEventListener('click', closeModal);
/*stockTypeSelect.addEventListener('change', checkFormCompletion);
mileageInput.addEventListener('input', checkFormCompletion);

View File

@ -20,6 +20,7 @@
{% for make in inventory.makes %}
<div class="accordion-item">
<p class="accordion-header" id="heading{{ make.make_id }}">
<button
class="accordion-button"
type="button"