This commit is contained in:
Marwan Alwali 2025-02-13 11:11:32 +03:00
parent 0733ea3669
commit 709b3359f3
13 changed files with 201 additions and 87 deletions

BIN
.DS_Store vendored

Binary file not shown.

1
.gitignore vendored
View File

@ -8,6 +8,7 @@ db.sqlite
db.sqlite3 db.sqlite3
media media
car_inventory/settings.py car_inventory/settings.py
scripts/dsrpipe.py
# Backup files # # Backup files #
*.bak *.bak

View File

@ -1,6 +1,6 @@
from openai import OpenAI from openai import OpenAI
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from inventory.models import CarSerie, CarModel, CarMake, CarTrim, CarOption, CarSpecificationValue from inventory.models import CarSerie, CarModel, CarMake, CarTrim, CarOption, CarSpecification
from django.conf import settings from django.conf import settings
@ -9,28 +9,35 @@ 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_option = CarOption.objects.all()[10300:] en_value = CarModel.objects.all()
total = car_option.count()
total = en_value.count()
print(f'Translating {total} names...') print(f'Translating {total} names...')
for index, car_option in enumerate(car_option, start=1): for index, en_value in enumerate(en_value, start=1):
if not en_value.arabic_name:
try: try:
completion = client.chat.completions.create( completion = client.chat.completions.create(
model="gpt-4o", model="gpt-4o",
messages=[ messages=[
{ {
"role": "system", "role": "system",
"content": "You are an assistant that translates English to Arabic." "content": (
"You are an assistant that translates English to Arabic."
"You are an assistant specialized in cars and automotive terms."
"If the model name is a number just write it as is"
"You can get the arabic names for makes, models, series, trims, options, and specifications."
)
}, },
{ {
"role": "user", "role": "user",
"content": car_option.name "content": en_value.name
} }
], ],
temperature=0.2, temperature=0.2,
) )
translation = completion.choices[0].message.content.strip() translation = completion.choices[0].message.content.strip()
car_option.arabic_name = translation en_value.arabic_name = translation
car_option.save() en_value.save()
print(f"[{index}/{total}] Translated '{car_option.name}' to '{translation}'") print(f"[{index}/{total}] .. Done")
except Exception as e: except Exception as e:
print(f"Error translating '{car_option.name}': {e}") print(f"Error translating '{en_value.name}': {e}")

View File

@ -2675,7 +2675,7 @@ class PaymentRequest(LoginRequiredMixin, DetailView):
return context return context
class EstimatePreviewView(LoginRequiredMixin, DetailView): class EstimatePreviewView(DetailView):
model = EstimateModel model = EstimateModel
context_object_name = "estimate" context_object_name = "estimate"
template_name = "sales/estimates/estimate_preview.html" template_name = "sales/estimates/estimate_preview.html"
@ -2697,30 +2697,31 @@ class EstimatePreviewView(LoginRequiredMixin, DetailView):
@login_required @login_required
def estimate_mark_as(request, pk): def estimate_mark_as(request, pk):
estimate = get_object_or_404(EstimateModel, pk=pk) estimate = get_object_or_404(EstimateModel, pk=pk)
entity = estimate.entity dealer = get_user_type(request.user)
entity = dealer.entity
mark = request.GET.get("mark") mark = request.GET.get("mark")
if mark: if mark:
if mark == "review": if mark == "review":
if not estimate.can_review(): if not estimate.can_review():
messages.error(request, "Estimate is not ready for review") messages.error(request, _("Estimate is not ready for review"))
return redirect("estimate_detail", pk=estimate.pk) return redirect("estimate_detail", pk=estimate.pk)
estimate.mark_as_review() estimate.mark_as_review()
elif mark == "approved": elif mark == "approved":
if not estimate.can_approve(): if not estimate.can_approve():
messages.error(request, "Estimate is not ready for approval") messages.error(request, _("Estimate is not ready for approval"))
return redirect("estimate_detail", pk=estimate.pk) return redirect("estimate_detail", pk=estimate.pk)
estimate.mark_as_approved() estimate.mark_as_approved()
messages.success(request, "Estimate approved successfully.") messages.success(request, _("Estimate approved successfully."))
elif mark == "rejected": elif mark == "rejected":
if not estimate.can_cancel(): if not estimate.can_cancel():
messages.error(request, "Estimate is not ready for rejection") messages.error(request, _("Estimate is not ready for rejection"))
return redirect("estimate_detail", pk=estimate.pk) return redirect("estimate_detail", pk=estimate.pk)
estimate.mark_as_canceled() estimate.mark_as_canceled()
messages.success(request, "Estimate canceled successfully.") messages.success(request, _("Estimate canceled successfully."))
elif mark == "completed": elif mark == "completed":
if not estimate.can_complete(): if not estimate.can_complete():
messages.error(request, "Estimate is not ready for completion") messages.error(request, _("Estimate is not ready for completion"))
return redirect("estimate_detail", pk=estimate.pk) return redirect("estimate_detail", pk=estimate.pk)
estimate.save() estimate.save()
messages.success(request, "Estimate marked as " + mark.upper()) messages.success(request, "Estimate marked as " + mark.upper())
@ -3257,7 +3258,7 @@ def send_lead_email(request, pk):
request.POST.get("subject"), request.POST.get("subject"),
request.POST.get("message"), request.POST.get("message"),
) )
messages.success(request, "Email sent successfully!") messages.success(request, _("Email sent successfully!"))
return redirect("lead_list") return redirect("lead_list")
msg = f""" msg = f"""
السلام عليكم السلام عليكم
@ -3848,7 +3849,7 @@ def send_email_view(request, pk):
# messages.error(request, "Estimate is not ready for review") # messages.error(request, "Estimate is not ready for review")
# return redirect("estimate_detail", pk=estimate.pk) # return redirect("estimate_detail", pk=estimate.pk)
if not estimate.get_itemtxs_data()[0]: if not estimate.get_itemtxs_data()[0]:
messages.error(request, "Estimate has no items") messages.error(request, _("Estimate has no items"))
return redirect("estimate_detail", pk=estimate.pk) return redirect("estimate_detail", pk=estimate.pk)
send_email( send_email(
@ -3858,7 +3859,7 @@ def send_email_view(request, pk):
request.POST.get("message"), request.POST.get("message"),
) )
estimate.mark_as_review() estimate.mark_as_review()
messages.success(request, "Email sent successfully!") messages.success(request, _("Email sent successfully!"))
return redirect("estimate_detail", pk=estimate.pk) return redirect("estimate_detail", pk=estimate.pk)
link = reverse_lazy("estimate_preview", kwargs={"pk": estimate.pk}) link = reverse_lazy("estimate_preview", kwargs={"pk": estimate.pk})
msg = f""" msg = f"""

140
scripts/dsrpipe.py Normal file
View File

@ -0,0 +1,140 @@
"""
title: Deepseek R1 Reasoner and Chat with Realtime Thinking Preview
authors: Ethan Copping
author_url: https://github.com/CoppingEthan
version: 0.3.0
required_open_webui_version: 0.5.5
license: MIT
# Acknowledgments
Code used from MCode-Team & Zgccrui
"""
import json
import httpx
from typing import AsyncGenerator, Callable, Awaitable
from pydantic import BaseModel, Field
class Pipe:
class Valves(BaseModel):
DEEPSEEK_API_BASE_URL: str = Field(
default="https://api.deepseek.com/v1", description="Base API endpoint URL"
)
DEEPSEEK_API_KEY: str = Field(
default="", description="Authentication key for API access"
)
def __init__(self):
self.valves = self.Valves()
self.thinking = -1
self._emitter = None
self.data_prefix = "data: "
def pipes(self):
try:
headers = {"Authorization": f"Bearer {self.valves.DEEPSEEK_API_KEY}"}
resp = httpx.get(
f"{self.valves.DEEPSEEK_API_BASE_URL}/models",
headers=headers,
timeout=10,
)
if resp.status_code == 200:
return [
{"id": m["id"], "name": m["id"]}
for m in resp.json().get("data", [])
]
except Exception:
pass
return [
{"id": "deepseek-chat", "name": "deepseek-chat"},
{"id": "deepseek-reasoner", "name": "deepseek-reasoner"},
]
async def pipe(
self, body: dict, __event_emitter__: Callable[[dict], Awaitable[None]] = None
) -> AsyncGenerator[str, None]:
self.thinking = -1
self._emitter = __event_emitter__
if not self.valves.DEEPSEEK_API_KEY:
yield json.dumps({"error": "Missing API credentials"})
return
req_headers = {
"Authorization": f"Bearer {self.valves.DEEPSEEK_API_KEY}",
"Content-Type": "application/json",
}
try:
request_data = body.copy()
model_id = request_data["model"].split(".", 1)[-1]
request_data["model"] = model_id
is_reasoner = "reasoner" in model_id.lower()
messages = request_data["messages"]
for i in reversed(range(1, len(messages))):
if messages[i - 1]["role"] == messages[i]["role"]:
alt_role = (
"user" if messages[i]["role"] == "assistant" else "assistant"
)
messages.insert(
i, {"role": alt_role, "content": "[Unfinished thinking]"}
)
async with httpx.AsyncClient(http2=True) as client:
async with client.stream(
"POST",
f"{self.valves.DEEPSEEK_API_BASE_URL}/chat/completions",
json=request_data,
headers=req_headers,
timeout=20,
) as resp:
if resp.status_code != 200:
error_content = (await resp.aread()).decode()[:200]
yield json.dumps(
{"error": f"API error {resp.status_code}: {error_content}"}
)
return
async for line in resp.aiter_lines():
if not line.startswith(self.data_prefix):
continue
stream_data = json.loads(line[6:])
choice = stream_data.get("choices", [{}])[0]
if choice.get("finish_reason"):
return
delta = choice.get("delta", {})
if is_reasoner:
state_marker = self._handle_state(delta)
if state_marker:
yield state_marker
if state_marker == "<think>":
yield "\n"
content = delta.get("reasoning_content", "") or delta.get(
"content", ""
)
if content:
yield content
else:
content = delta.get("content", "")
if content:
yield content
except Exception as e:
yield json.dumps({"error": f"{type(e).__name__}: {str(e)}"})
def _handle_state(self, delta: dict) -> str:
if self.thinking == -1 and delta.get("reasoning_content"):
self.thinking = 0
return "<think>"
if self.thinking == 0 and not delta.get("reasoning_content"):
self.thinking = 1
return "\n</think>\n\n"
return ""

View File

@ -1,7 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n %} {% load i18n %}
{% block title %}{{ _("View Estimate") }}{% endblock title %} {% block title %}{{ _("View Quotation") }}{% endblock title %}
{% block content %} {% block content %}
<div class="modal fade" id="confirmModal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true"> <div class="modal fade" id="confirmModal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
@ -21,8 +21,8 @@
<form id="confirmForm" method="POST" action="{% url 'estimate_mark_as' estimate.pk %}?mark=approved" class="form"> <form id="confirmForm" method="POST" action="{% url 'estimate_mark_as' estimate.pk %}?mark=approved" class="form">
{% csrf_token %} {% csrf_token %}
<div class="container-fluid m-0 p-0"> <div class="container-fluid m-0 p-0">
<button type="button" class="btn btn-danger btn-sm w-100" data-bs-dismiss="modal">{% trans 'No' %}</button> <button type="button" class="btn btn-danger btn-sm" data-bs-dismiss="modal">{% trans 'No' %}</button>
<button type="submit" class="btn btn-success btn-sm w-100">{% trans "Yes" %}</button> <button type="submit" class="btn btn-success btn-sm">{% trans "Yes" %}</button>
</div> </div>
</form> </form>
</div> </div>

View File

@ -2,7 +2,7 @@
{% load crispy_forms_filters %} {% load crispy_forms_filters %}
{% load i18n static %} {% load i18n static %}
{% block title %}{{ _("Quotations") }}{% endblock title %} {% block title %}{{ _("Quotation") }}{% endblock title %}
{% block content %} {% block content %}
<div class="card email-content"> <div class="card email-content">
@ -30,7 +30,5 @@
</form> </form>
</div> </div>
</div> </div>
</div>
</div>
{% endblock content %} {% endblock content %}

View File

@ -106,24 +106,26 @@
<th>نوع السيارة</th> <th>نوع السيارة</th>
<th>اللون الخارجي</th> <th>اللون الخارجي</th>
<th>اللون الداخلي</th> <th>اللون الداخلي</th>
<th>السعر</th>
<th>ملاحظات</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for car in cars %} {% for car in cars %}
<tr> <tr>
<td>{{ car.year }} - {{ car.id_car_make.arabic_name }} - {{ car.id_car_model.arabic_name }} - {{ car.id_car_trim.arabic_name }}</td> <td>{{ car.year }} - {{ car.id_car_make.name }} - {{ car.id_car_model.name }} - {{ car.id_car_trim.name }}</td>
<td>{{ car.colors.first.exterior.arabic_name }}</td> <td>{{ car.colors.first.exterior.name }}</td>
<td>{{ car.colors.first.interior.arabic_name }}</td> <td>{{ car.colors.first.interior.name }}</td>
<td>{{ car.finances.first.cost_price }}</td> <td>{{ car.finances.selling_price }}</td>
<td>{{ car. }}</td> <td>{{ car.get_specifications }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<p>حمولة المركبة (<input type="text">) سنة الصنع (<input type="text">) (جديد / مستعملة) كلم/ميل</p> <p>حمولة المركبة )<input type="text">) سنة الصنع (<input type="text">) (جديد / مستعملة) كلم/ميل</p>
<p>مستوى اقتصاد الوقود (<input type="text">) رقم الشاسيه (في حال كانت السيارة مستعملة فقط) (<input type="text">)</p> <p>مستوى اقتصاد الوقود )<input type="text">) رقم الهيكل (في حال كانت السيارة مستعملة فقط) (<input type="text">)</p>
<p>مواصفات أخرى:</p> <p>مواصفات أخرى:</p>
<input type="text"> <input type="text">

View File

@ -238,7 +238,9 @@
<div class="d-flex flex-wrap align-items-center justify-content-between py-3 pe-0 fs-9 border-bottom border-translucent"> <div class="d-flex flex-wrap align-items-center justify-content-between py-3 pe-0 fs-9 border-bottom border-translucent">
<div class="d-flex"> <div class="d-flex">
{% if is_paginated %}
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
{% endif %}
</div> </div>
</div> </div>

View File

@ -114,7 +114,6 @@
<div class="avatar avatar-xl me-3"><img class="rounded-circle" src="{% static 'images/icons/picture.svg' %}" alt="" /> <div class="avatar avatar-xl me-3"><img class="rounded-circle" src="{% static 'images/icons/picture.svg' %}" alt="" />
{% endif %} {% endif %}
</div> </div>
<div><a class="fs-8 fw-bold" href="">{{ vendor.vendor_name }}</a> <div><a class="fs-8 fw-bold" href="">{{ vendor.vendor_name }}</a>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<p class="mb-0 text-body-highlight fw-semibold fs-9 me-2">{{ vendor.vendor_name }}</p><span class="badge badge-phoenix badge-phoenix-primary">{{ vendor.id}}</span> <p class="mb-0 text-body-highlight fw-semibold fs-9 me-2">{{ vendor.vendor_name }}</p><span class="badge badge-phoenix badge-phoenix-primary">{{ vendor.id}}</span>
@ -147,43 +146,7 @@
<div class="row align-items-center justify-content-end py-4 pe-0 fs-9"> <div class="row align-items-center justify-content-end py-4 pe-0 fs-9">
<!-- Optional: Pagination --> <!-- Optional: Pagination -->
{% if is_paginated %} {% if is_paginated %}
<nav aria-label="Page navigation"> {% include 'partials/pagination.html' %}
<ul class="pagination mb-0">
{% if page_obj.has_previous %}
<li class="page-item py-0">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">
<span aria-hidden="true"><span class="fas fa-chevron-left"></span></span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true"><span class="fas fa-chevron-left"></span></span>
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active"><a class="page-link" href="?page={{ num }}">{{ num }}</a></li>
{% else %}
<li class="page-item"><a class="page-link" href="?page={{ num }}">{{ num }}</a></li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="Next">
<span aria-hidden="true"><span class="fas fa-chevron-right"></span></span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#" aria-label="Next">
<span aria-hidden="true"><span class="fas fa-chevron-right"></span></span>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %} {% endif %}
</div> </div>
</div> </div>