Merge branch 'main' of http://10.10.1.120:3000/tenhal_admin/haikal
This commit is contained in:
commit
faf5f14881
Binary file not shown.
Binary file not shown.
@ -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
|
||||
|
||||
@ -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)}"
|
||||
|
||||
@ -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:
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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}")
|
||||
|
||||
@ -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'),
|
||||
|
||||
@ -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
BIN
static/.DS_Store
vendored
Binary file not shown.
BIN
static/images/.DS_Store
vendored
BIN
static/images/.DS_Store
vendored
Binary file not shown.
BIN
static/images/car_make/.DS_Store
vendored
BIN
static/images/car_make/.DS_Store
vendored
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 |
BIN
static/images/logos/.DS_Store
vendored
BIN
static/images/logos/.DS_Store
vendored
Binary file not shown.
@ -12,7 +12,6 @@
|
||||
padding: 10px;
|
||||
height: 200px;
|
||||
overflow-y: scroll;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user