diff --git a/.DS_Store b/.DS_Store index 142e5f91..4e9a6eca 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 237f8bef..71c44dd4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ db.sqlite db.sqlite3 media car_inventory/settings.py +scripts/dsrpipe.py # Backup files # *.bak diff --git a/car_inventory/__pycache__/settings.cpython-311.pyc b/car_inventory/__pycache__/settings.cpython-311.pyc index eeca0380..f8667ea2 100644 Binary files a/car_inventory/__pycache__/settings.cpython-311.pyc and b/car_inventory/__pycache__/settings.cpython-311.pyc differ diff --git a/inventory/__pycache__/views.cpython-311.pyc b/inventory/__pycache__/views.cpython-311.pyc index 6defc2db..e919829b 100644 Binary files a/inventory/__pycache__/views.cpython-311.pyc and b/inventory/__pycache__/views.cpython-311.pyc differ diff --git a/inventory/management/commands/__pycache__/translate.cpython-311.pyc b/inventory/management/commands/__pycache__/translate.cpython-311.pyc index cda4a7bd..d578167b 100644 Binary files a/inventory/management/commands/__pycache__/translate.cpython-311.pyc and b/inventory/management/commands/__pycache__/translate.cpython-311.pyc differ diff --git a/inventory/management/commands/translate.py b/inventory/management/commands/translate.py index 7bcd1b31..0ac7bd94 100644 --- a/inventory/management/commands/translate.py +++ b/inventory/management/commands/translate.py @@ -1,6 +1,6 @@ from openai import OpenAI 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 @@ -9,28 +9,35 @@ class Command(BaseCommand): def handle(self, *args, **kwargs): client = OpenAI(api_key=settings.OPENAI_API_KEY) - car_option = CarOption.objects.all()[10300:] - total = car_option.count() + en_value = CarModel.objects.all() + + total = en_value.count() print(f'Translating {total} names...') - for index, car_option in enumerate(car_option, start=1): - try: - completion = client.chat.completions.create( - model="gpt-4o", - messages=[ - { - "role": "system", - "content": "You are an assistant that translates English to Arabic." - }, - { - "role": "user", - "content": car_option.name - } - ], - temperature=0.2, - ) - translation = completion.choices[0].message.content.strip() - car_option.arabic_name = translation - car_option.save() - print(f"[{index}/{total}] Translated '{car_option.name}' to '{translation}'") - except Exception as e: - print(f"Error translating '{car_option.name}': {e}") + for index, en_value in enumerate(en_value, start=1): + if not en_value.arabic_name: + try: + completion = client.chat.completions.create( + model="gpt-4o", + messages=[ + { + "role": "system", + "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", + "content": en_value.name + } + ], + temperature=0.2, + ) + translation = completion.choices[0].message.content.strip() + en_value.arabic_name = translation + en_value.save() + print(f"[{index}/{total}] .. Done") + except Exception as e: + print(f"Error translating '{en_value.name}': {e}") diff --git a/inventory/views.py b/inventory/views.py index 6740f3f3..44401c15 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -2675,7 +2675,7 @@ class PaymentRequest(LoginRequiredMixin, DetailView): return context -class EstimatePreviewView(LoginRequiredMixin, DetailView): +class EstimatePreviewView(DetailView): model = EstimateModel context_object_name = "estimate" template_name = "sales/estimates/estimate_preview.html" @@ -2697,30 +2697,31 @@ class EstimatePreviewView(LoginRequiredMixin, DetailView): @login_required def estimate_mark_as(request, 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") if mark: if mark == "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) estimate.mark_as_review() elif mark == "approved": 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) estimate.mark_as_approved() - messages.success(request, "Estimate approved successfully.") + messages.success(request, _("Estimate approved successfully.")) elif mark == "rejected": 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) estimate.mark_as_canceled() - messages.success(request, "Estimate canceled successfully.") + messages.success(request, _("Estimate canceled successfully.")) elif mark == "completed": 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) estimate.save() 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("message"), ) - messages.success(request, "Email sent successfully!") + messages.success(request, _("Email sent successfully!")) return redirect("lead_list") msg = f""" السلام عليكم @@ -3848,7 +3849,7 @@ def send_email_view(request, pk): # messages.error(request, "Estimate is not ready for review") # return redirect("estimate_detail", pk=estimate.pk) 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) send_email( @@ -3858,7 +3859,7 @@ def send_email_view(request, pk): request.POST.get("message"), ) estimate.mark_as_review() - messages.success(request, "Email sent successfully!") + messages.success(request, _("Email sent successfully!")) return redirect("estimate_detail", pk=estimate.pk) link = reverse_lazy("estimate_preview", kwargs={"pk": estimate.pk}) msg = f""" diff --git a/scripts/dsrpipe.py b/scripts/dsrpipe.py new file mode 100644 index 00000000..0526150c --- /dev/null +++ b/scripts/dsrpipe.py @@ -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 == "": + 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 "" + + if self.thinking == 0 and not delta.get("reasoning_content"): + self.thinking = 1 + return "\n\n\n" + + return "" diff --git a/templates/sales/estimates/estimate_detail.html b/templates/sales/estimates/estimate_detail.html index 125b9a13..a6ae6a38 100644 --- a/templates/sales/estimates/estimate_detail.html +++ b/templates/sales/estimates/estimate_detail.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% load i18n %} -{% block title %}{{ _("View Estimate") }}{% endblock title %} +{% block title %}{{ _("View Quotation") }}{% endblock title %} {% block content %} diff --git a/templates/sales/estimates/estimate_send.html b/templates/sales/estimates/estimate_send.html index 7091686a..4b9812b2 100644 --- a/templates/sales/estimates/estimate_send.html +++ b/templates/sales/estimates/estimate_send.html @@ -2,7 +2,7 @@ {% load crispy_forms_filters %} {% load i18n static %} -{% block title %}{{ _("Quotations") }}{% endblock title %} +{% block title %}{{ _("Quotation") }}{% endblock title %} {% block content %}
@@ -30,7 +30,5 @@
- - {% endblock content %} \ No newline at end of file diff --git a/templates/sales/estimates/payment_request_detail.html b/templates/sales/estimates/payment_request_detail.html index 753999bd..fab876d2 100644 --- a/templates/sales/estimates/payment_request_detail.html +++ b/templates/sales/estimates/payment_request_detail.html @@ -106,24 +106,26 @@ نوع السيارة اللون الخارجي اللون الداخلي + السعر + ملاحظات {% for car in cars %} - {{ car.year }} - {{ car.id_car_make.arabic_name }} - {{ car.id_car_model.arabic_name }} - {{ car.id_car_trim.arabic_name }} - {{ car.colors.first.exterior.arabic_name }} - {{ car.colors.first.interior.arabic_name }} - {{ car.finances.first.cost_price }} - {{ car. }} + {{ car.year }} - {{ car.id_car_make.name }} - {{ car.id_car_model.name }} - {{ car.id_car_trim.name }} + {{ car.colors.first.exterior.name }} + {{ car.colors.first.interior.name }} + {{ car.finances.selling_price }} + {{ car.get_specifications }} {% endfor %} -

حمولة المركبة () سنة الصنع () (جديد / مستعملة) كلم/ميل

+

حمولة المركبة )) سنة الصنع () (جديد / مستعملة) كلم/ميل

-

مستوى اقتصاد الوقود () رقم الشاسيه (في حال كانت السيارة مستعملة فقط) ()

+

مستوى اقتصاد الوقود )) رقم الهيكل (في حال كانت السيارة مستعملة فقط) ()

مواصفات أخرى:

diff --git a/templates/sales/sales_list.html b/templates/sales/sales_list.html index f905d369..97bef0e6 100644 --- a/templates/sales/sales_list.html +++ b/templates/sales/sales_list.html @@ -238,7 +238,9 @@
+ {% if is_paginated %} {% include 'partials/pagination.html' %} + {% endif %}
diff --git a/templates/vendors/vendors_list.html b/templates/vendors/vendors_list.html index e1cf3176..d39f408f 100644 --- a/templates/vendors/vendors_list.html +++ b/templates/vendors/vendors_list.html @@ -114,7 +114,6 @@
{% endif %}
-
{{ vendor.vendor_name }}

{{ vendor.vendor_name }}

{{ vendor.id}} @@ -147,43 +146,7 @@
{% if is_paginated %} - +{% include 'partials/pagination.html' %} {% endif %}