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.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.urls.static import static
from django.conf import settings from django.conf import settings
from django.conf.urls.i18n import i18n_patterns from django.conf.urls.i18n import i18n_patterns

View File

@ -1,83 +1,45 @@
import re from openai import OpenAI
from nltk.tokenize import word_tokenize from inventory import models
from django.db.models import Q from car_inventory import settings
from inventory.models import Car # Import your car-related models
import nltk
# Download required NLTK resources
try:
nltk.download("punkt") def fetch_data(dealer):
except ImportError: try:
raise ImportError("Ensure nltk is installed by running 'pip install nltk'.") # 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): def get_gpt4_response(user_input, dealer):
""" dealer = dealer
Clean and tokenize user input. client = OpenAI(api_key=settings.OPENAI_API_KEY)
"""
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): if "سيارة في المخزون" in user_input.lower():
""" # cars = user_input.split("how many cars")[-1].strip()
Classify user intent based on tokens. car_data = fetch_data(dealer)
""" user_input += f" Relevant car data: {car_data}"
if any(word in tokens for word in ["hello", "hi", "hey"]): try:
return "greet" completion = client.chat.completions.create(
elif any(word in tokens for word in ["inventory", "cars", "check"]): model="gpt-4o",
return "inventory_check" messages=[
elif any(word in tokens for word in ["sell", "sold", "process"]): {
return "sell_process" "role": "system",
elif any(word in tokens for word in ["transfer", "branch", "display"]): "content": (
return "transfer_process" "You are an assistant specialized in car inventory management for the Haikal system. "
elif any(word in tokens for word in ["bye", "goodbye", "exit"]): "You can answer questions about the inventory, sales process, car transfers, invoices, "
return "bye" "and other application-specific functionalities. Always provide responses aligned "
elif any(word in tokens for word in ["price", "cost", "value"]): "with the Haikal system's structure and features."
return "car_price" )
else: },
return "unknown" {"role": "user", "content": user_input},
],
def get_dynamic_response(intent, tokens): )
""" return completion.choices[0].message.content.strip()
Generate dynamic responses by querying the database. except Exception as e:
""" return f"An error occurred while generating the response: {str(e)}"
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

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

View File

@ -9,28 +9,31 @@ class Command(BaseCommand):
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
client = OpenAI(api_key=settings.OPENAI_API_KEY) client = OpenAI(api_key=settings.OPENAI_API_KEY)
car_make = CarMake.objects.all() car_model = CarModel.objects.all()[0:1000]
total = car_make.count() total = car_model.count()
print(f'Translating {total} names...') 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: try:
completion = client.chat.completions.create( completion = client.chat.completions.create(
model="gpt-4", model="gpt-4o",
messages=[ messages=[
{ {
"role": "system", "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", "role": "user",
"content": car_make.name "content": car_model.name
} }
], ],
temperature=0.3, temperature=0.2,
) )
translation = completion.choices[0].message.content.strip() translation = completion.choices[0].message.content.strip()
car_make.arabic_name = translation car_model.arabic_name = translation
car_make.save() car_model.save()
print(f"[{index}/{total}] Translated '{car_make.name}' to '{translation}'") print(f"[{index}/{total}] Translated '{car_model.name}' to '{translation}'")
except Exception as e: 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 # 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>/', path('cars/inventory/<int:make_id>/<int:model_id>/<int:trim_id>/',
views.CarInventory.as_view(), views.CarInventory.as_view(),
name='car_inventory'), name='car_inventory'),

View File

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

View File

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

View File

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