From 1e9f216ade817fcb5eee4637b15c9b893e7003d6 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Mon, 26 Jan 2026 07:35:15 +0000 Subject: [PATCH] Autosave: 20260126-073515 --- ai/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 404 bytes ai/__pycache__/local_ai_api.cpython-311.pyc | Bin 0 -> 19874 bytes core/__pycache__/admin.cpython-311.pyc | Bin 10997 -> 10949 bytes core/__pycache__/models.cpython-311.pyc | Bin 23115 -> 23169 bytes core/__pycache__/urls.cpython-311.pyc | Bin 3888 -> 4185 bytes core/__pycache__/views.cpython-311.pyc | Bin 26341 -> 34918 bytes core/admin.py | 1 - .../0018_alter_otpverification_purpose.py | 18 ++ ...er_otpverification_purpose.cpython-311.pyc | Bin 0 -> 1008 bytes core/models.py | 3 +- core/templates/admin/dashboard.html | 2 +- core/templates/base.html | 33 +++ core/templates/core/driver_dashboard.html | 188 +++++++++++--- core/templates/core/index.html | 85 +++++++ core/templates/core/login.html | 233 ++++++++++++++++-- core/templates/core/shipper_dashboard.html | 155 +++++++++++- core/urls.py | 5 + core/views.py | 193 ++++++++++++++- locale/ar/LC_MESSAGES/django.mo | Bin 23944 -> 24360 bytes locale/ar/LC_MESSAGES/django.po | 16 +- static/css/custom.css | 60 ++++- static/js/chat.js | 118 +++++++++ update_base.py | 50 ++++ update_base_v2.py | 36 +++ 24 files changed, 1123 insertions(+), 73 deletions(-) create mode 100644 ai/__pycache__/__init__.cpython-311.pyc create mode 100644 ai/__pycache__/local_ai_api.cpython-311.pyc create mode 100644 core/migrations/0018_alter_otpverification_purpose.py create mode 100644 core/migrations/__pycache__/0018_alter_otpverification_purpose.cpython-311.pyc create mode 100644 static/js/chat.js create mode 100644 update_base.py create mode 100644 update_base_v2.py diff --git a/ai/__pycache__/__init__.cpython-311.pyc b/ai/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9beeae7341d797e2eac5af07416a24e9ee781f92 GIT binary patch literal 404 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 I)39d(0Bbmg-T(jq delta 658 zcmX>a`ZbhqIWI340}#w$EzeYynaC%>ST<38nIl6AZ;ouPT$CImLkdd@PmX-9Oq2|e z&5**EqY$M4rWKMgF8ctKnp{PU@Bvl%H#=*;*6@3r5L68)xa_$ zKp9b>j58=zkfk$OfLVU>M@A8D9gw?0 zV1eG`iEJWDsjR7L%a|A#Rs%5vFfxENz<3a4OsO1`4VXnSb#ta_5!OBV7!!up$-gkg zlu~(8H3=K1lFFN^jjk+32HDl|`jb1D-^lP6r{<-^XO!gT#HZ#aX69%z-r^`uEh)*& zOE2C$frX2aQE2mQ)}@S$Qj;Cor!tCfzRtddg;8~L4^Iapqx@!3K3^6_pUufa0ZfcR zo41J6F*8POHkR;VVHBO*Ak)W~yP02>otd$1v$#SFBjc3Ga}=*LT5m2<`pw8VcXNS? z7$f7t$?dA%j0T%8tFkfb38Z8t=B4NBCFd8V>Vce7Bnk}WTikHrytE=QkO&J%a`IQT d`COZTTt*-+Hk>?9{e^(e1qKwfxkbZ~2>?pomP-Ht diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index c173f305be8947c2b943f168eda8e574d01d8cbc..987996dd8f4cd5bd231dd964b99ee1198175cbe7 100644 GIT binary patch delta 471 zcmX@Tg|TrfBkyuvUM>b8*u_eX3!Lw{KYAk_ZDkTetKr!Emj{OwK>6=myz+oB`6Wda{M9 ztKT3c zb@Cg}yvdy2u8cn>`+IjX{+WE!yM~cva+psXs}NB7|Ky!MMWWn5RuLD7*bCCf2V(I~ zcJQ@h-IUm7tUyxNOO@11g1^@(MgpvRN delta 436 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 delta 5674 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 diff --git a/core/admin.py b/core/admin.py index 63389ef..0ea02f2 100644 --- a/core/admin.py +++ b/core/admin.py @@ -8,7 +8,6 @@ from django.shortcuts import render from django.utils.html import format_html from django.contrib import messages from .whatsapp_utils import send_whatsapp_message_detailed -from django.core.mail import send_html_email from django.conf import settings from .mail import send_html_email import logging diff --git a/core/migrations/0018_alter_otpverification_purpose.py b/core/migrations/0018_alter_otpverification_purpose.py new file mode 100644 index 0000000..a4ff2d6 --- /dev/null +++ b/core/migrations/0018_alter_otpverification_purpose.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2026-01-26 06:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0017_driverrating'), + ] + + operations = [ + migrations.AlterField( + model_name='otpverification', + name='purpose', + field=models.CharField(choices=[('profile_update', 'Profile Update'), ('password_reset', 'Password Reset'), ('registration', 'Registration'), ('login', 'Login')], default='profile_update', max_length=20), + ), + ] diff --git a/core/migrations/__pycache__/0018_alter_otpverification_purpose.cpython-311.pyc b/core/migrations/__pycache__/0018_alter_otpverification_purpose.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..60072e6547a9f33ac62339356228dd9f6b91c5a4 GIT binary patch literal 1008 zcmZuvK~K~`6rO3j-ECb!RuU5v710oy7>Xx75EEmf#vm~m)a+$aI&ZsOyKSAef}A*b z@W_q32aS>c;nf2NGFMOBO5nuFH{D$nu+x6?zIktEzWJu##>Q-d&rf?jbTmSKD>8a& z!{BHUgB`*NrxEGXHtlO|jS`JKC0zSNxDK?Zk0yP^=*KB*>)d!noZ5j(JDRlnVIaIb zOyW$M{e(l5`J{ z!!TQmk|2y_ZAo?F5}x>A3c=B3-R~x$51FiU=y*kxqsQI`iy#j2F6QmZWfpsVkVb{I zP6!cSb}ZRo)i`6)W(?~JJOYzV#@-iRq-qu8kV4EM_Cv^ox>#vHigFN7)GESI1J>iN zH{jAvQW#$FcXEVDaLAHsob=pdkShJ1mWv>xRol=TNpw2HcckwOFCN)cP5w`8U%Xbb{ K;`l$=YTrK|j}i0$ literal 0 HcmV?d00001 diff --git a/core/models.py b/core/models.py index 2f2c56f..8d068a8 100644 --- a/core/models.py +++ b/core/models.py @@ -213,6 +213,7 @@ class OTPVerification(models.Model): ('profile_update', _('Profile Update')), ('password_reset', _('Password Reset')), ('registration', _('Registration')), + ('login', _('Login')), ) user = models.ForeignKey(User, on_delete=models.CASCADE) code = models.CharField(max_length=6) @@ -269,4 +270,4 @@ class DriverRating(models.Model): class Meta: verbose_name = _('Driver Rating') - verbose_name_plural = _('Driver Ratings') + verbose_name_plural = _('Driver Ratings') \ No newline at end of file diff --git a/core/templates/admin/dashboard.html b/core/templates/admin/dashboard.html index fe1ab63..8630e68 100644 --- a/core/templates/admin/dashboard.html +++ b/core/templates/admin/dashboard.html @@ -180,7 +180,7 @@ {% trans "Total Revenue" %}
💰
-
{{ stats.total_revenue|floatform:2 }} OMR
+
{{ stats.total_revenue|floatformat:3 }} OMR
diff --git a/core/templates/base.html b/core/templates/base.html index aedc38a..1eff527 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -245,5 +245,38 @@ + + + +
+
+
+ +
MasarX AI
+
+ +
+
+
+
+ {% trans "Hello! How can I help you with your shipments today?" %} +
+
+
+
+
+ + +
+
+
+ + \ No newline at end of file diff --git a/core/templates/core/driver_dashboard.html b/core/templates/core/driver_dashboard.html index f932197..40ea4ec 100644 --- a/core/templates/core/driver_dashboard.html +++ b/core/templates/core/driver_dashboard.html @@ -20,37 +20,132 @@
- {% if available_parcels %} -
- {% for parcel in available_parcels %} -
-
-
-
{{ parcel.description|truncatechars:30 }}
-

{% trans "Pickup" %}: {{ parcel.pickup_address }}

-

{% trans "Delivery" %}: {{ parcel.delivery_address }}

- -
- {% trans "Weight" %}: {{ parcel.weight }} kg -
- - -
- {% trans "Shipper's Offer (Bid)" %} -
{{ parcel.price }} OMR
-
+ + +
+
{% trans "Browse Shipments" %}
+
+ + +
+
-
- {% csrf_token %} - -
+ {% if available_parcels %} + + +
+ {% for parcel in available_parcels %} +
+
+
+
{{ parcel.description|truncatechars:30 }}
+

{% trans "Pickup" %}: {{ parcel.pickup_governate.name }} / {{ parcel.pickup_city.name }}

+

{% trans "Delivery" %}: {{ parcel.delivery_governate.name }} / {{ parcel.delivery_city.name }}

+ +
+ {% trans "Weight" %}: {{ parcel.weight }} kg +
+ + +
+ {% trans "Shipper's Offer (Bid)" %} +
{{ parcel.price }} OMR
+
+ +
+ {% csrf_token %} + +
+
+ {% endfor %}
- {% endfor %} -
+ + +
+
+ {% for parcel in available_parcels %} +
+
+
+
+
{{ parcel.description|truncatechars:80 }}
+
+ + + {% trans "From" %}: {{ parcel.pickup_governate.name }} / {{ parcel.pickup_city.name }} + + + + {% trans "To" %}: {{ parcel.delivery_governate.name }} / {{ parcel.delivery_city.name }} + + + {{ parcel.weight }} kg + +
+
+
+
+
{{ parcel.price }} OMR
+
+ {% csrf_token %} + +
+
+
+
+
+
+ {% endfor %} +
+
+ + + {% if available_parcels.has_other_pages %} + + {% endif %} + {% else %} -

{% trans "No shipments available at the moment." %}

+

{% trans "No shipments available at the moment." %}

{% endif %}
@@ -67,7 +162,7 @@ {{ parcel.get_status_display }}
{{ parcel.description|truncatechars:30 }}
-

{% trans "To" %}: {{ parcel.delivery_address }}

+

{% trans "To" %}: {{ parcel.delivery_governate.name }} / {{ parcel.delivery_city.name }}

{% trans "Receiver" %}: {{ parcel.receiver_name }}

@@ -113,8 +208,8 @@ {{ parcel.created_at|date:"Y-m-d" }} #{{ parcel.tracking_number }} - {{ parcel.pickup_city.name|default:parcel.pickup_address }} - {{ parcel.delivery_city.name|default:parcel.delivery_address }} + {{ parcel.pickup_governate.name }} / {{ parcel.pickup_city.name }} + {{ parcel.delivery_governate.name }} / {{ parcel.delivery_city.name }} {{ parcel.price }} OMR @@ -136,4 +231,39 @@
+ + {% endblock %} \ No newline at end of file diff --git a/core/templates/core/index.html b/core/templates/core/index.html index cb52783..c204a19 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -86,6 +86,91 @@
+ +
+
+
+ +
+

+ {% trans "Top Rated Drivers" %} +

+
+ {% for driver in top_drivers %} +
+
+ {% if driver.profile_picture %} + + {% else %} +
+ {{ driver.user.first_name|first|upper }} +
+ {% endif %} + + {{ forloop.counter }} + +
+
+
{{ driver.user.first_name }} {{ driver.user.last_name|first }}.
+ {% trans "Driver" %} +
+
+
+ {{ driver.avg_rating|floatformat:1 }} +
+ ({{ driver.rating_count }} {% trans "reviews" %}) +
+
+ {% empty %} +
+ {% trans "No ratings yet. Be the first!" %} +
+ {% endfor %} +
+
+ + +
+

+ {% trans "Top Shippers" %} +

+
+ {% for shipper in top_shippers %} +
+
+ {% if shipper.profile_picture %} + + {% else %} +
+ {{ shipper.user.first_name|first|upper }} +
+ {% endif %} + + {{ forloop.counter }} + +
+
+
{{ shipper.user.first_name }} {{ shipper.user.last_name|first }}.
+ {% trans "Shipper" %} +
+
+
+ {{ shipper.shipment_count }} +
+ {% trans "Shipments" %} +
+
+ {% empty %} +
+ {% trans "No shipments yet. Start shipping now!" %} +
+ {% endfor %} +
+
+
+
+
+ {% if testimonials %}
diff --git a/core/templates/core/login.html b/core/templates/core/login.html index 765521e..09f4d96 100644 --- a/core/templates/core/login.html +++ b/core/templates/core/login.html @@ -22,34 +22,81 @@

{% trans "Please login to your account" %}

- - {% csrf_token %} - {% for field in form %} -
- - {{ field }} - {% if field.errors %} -
{{ field.errors }}
- {% endif %} -
- {% endfor %} - -
-
- -
- - {% trans "Forgot Password?" %} - + + + +
+ +
+ + {% csrf_token %} + {% for field in form %} +
+ + {{ field }} + {% if field.errors %} +
{{ field.errors }}
+ {% endif %} +
+ {% endfor %} + + + + +
- - -
- {% trans "Don't have an account?" %} - {% trans "Register" %} + +
+
+
+ + +
+
+ +
+ +
+
+
+ + +
+
+ +
+ +
+
- +
+ +
+ {% trans "Don't have an account?" %} + {% trans "Register" %} +
+
@@ -58,6 +105,15 @@
-{% endblock %} + + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/shipper_dashboard.html b/core/templates/core/shipper_dashboard.html index 0d706f0..6d7df9a 100644 --- a/core/templates/core/shipper_dashboard.html +++ b/core/templates/core/shipper_dashboard.html @@ -20,10 +20,26 @@
- +
+ + +
+
{% trans "Current Shipments" %}
+
+ + +
+
+ {% if active_parcels %} -
+ + +
{% for parcel in active_parcels %}
@@ -35,8 +51,8 @@
{{ parcel.description|truncatechars:30 }}
-

{% trans "From" %}: {{ parcel.pickup_address }}

-

{% trans "To" %}: {{ parcel.delivery_address }}

+

{% trans "From" %}: {{ parcel.pickup_governate.name }} / {{ parcel.pickup_city.name }}

+

{% trans "To" %}: {{ parcel.delivery_governate.name }} / {{ parcel.delivery_city.name }}

{{ parcel.price }} OMR @@ -61,6 +77,100 @@
{% endfor %}
+ + +
+
+ {% for parcel in active_parcels %} +
+
+
+
+
+
{{ parcel.description|truncatechars:60 }}
+ #{{ parcel.tracking_number }} +
+
+ + + {% trans "From" %}: {{ parcel.pickup_governate.name }} / {{ parcel.pickup_city.name }} + + + + {% trans "To" %}: {{ parcel.delivery_governate.name }} / {{ parcel.delivery_city.name }} + + + + {% if parcel.carrier %}{{ parcel.carrier.get_full_name|default:parcel.carrier.username }}{% else %}{% trans "Waiting" %}{% endif %} + +
+
+
+
+
+ {{ parcel.price }} OMR + + {{ parcel.get_status_display }} + +
+ + {% if parcel.payment_status == 'pending' and payments_enabled %} + + {% trans "Pay Now" %} + + {% else %} + + {{ parcel.get_payment_status_display }} + + {% endif %} +
+
+
+
+
+ {% endfor %} +
+
+ + + {% if active_parcels.has_other_pages %} + + {% endif %} + {% else %}

{% trans "You have no active shipments." %}

@@ -135,4 +245,39 @@
-{% endblock %} + + +{% endblock %} \ No newline at end of file diff --git a/core/urls.py b/core/urls.py index 2cda543..4f4230f 100644 --- a/core/urls.py +++ b/core/urls.py @@ -43,6 +43,7 @@ urlpatterns = [ path('article/1/', views.article_detail, name='article_detail'), path('ajax/get-governates/', views.get_governates, name='get_governates'), path('ajax/get-cities/', views.get_cities, name='get_cities'), + path('ajax/chatbot/', views.chatbot, name='chatbot'), path('privacy-policy/', views.privacy_policy, name='privacy_policy'), path('terms-conditions/', views.terms_conditions, name='terms_conditions'), path('contact/', views.contact, name='contact'), @@ -50,4 +51,8 @@ urlpatterns = [ path('profile/', views.profile_view, name='profile'), path('profile/edit/', views.edit_profile, name='edit_profile'), path('profile/verify-otp/', views.verify_otp_view, name='verify_otp'), + + # OTP Login + path('login/request-otp/', views.request_login_otp, name='request_login_otp'), + path('login/verify-otp/', views.verify_login_otp, name='verify_login_otp'), ] \ No newline at end of file diff --git a/core/views.py b/core/views.py index 0160bae..f67acf2 100644 --- a/core/views.py +++ b/core/views.py @@ -13,6 +13,9 @@ from django.urls import reverse from .payment_utils import ThawaniPay from django.conf import settings from django.core.mail import send_mail +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from django.views.decorators.http import require_POST +from django.db.models import Avg, Count import random import string from .whatsapp_utils import ( @@ -23,6 +26,8 @@ from .whatsapp_utils import ( send_whatsapp_message ) from .mail import send_contact_message, send_html_email +import json +from ai.local_ai_api import LocalAIApi def index(request): tracking_id = request.GET.get('tracking_id') @@ -36,11 +41,24 @@ def index(request): testimonials = Testimonial.objects.filter(is_active=True) + # Top 5 Drivers (by Average Rating) + top_drivers = Profile.objects.filter(role='car_owner').annotate( + avg_rating=Avg('user__received_ratings__rating'), + rating_count=Count('user__received_ratings') + ).filter(rating_count__gt=0).order_by('-avg_rating')[:5] + + # Top 5 Shippers (by Shipment Count) + top_shippers = Profile.objects.filter(role='shipper').annotate( + shipment_count=Count('user__sent_parcels') + ).order_by('-shipment_count')[:5] + return render(request, 'core/index.html', { 'parcel': parcel, 'error': error, 'tracking_id': tracking_id, - 'testimonials': testimonials + 'testimonials': testimonials, + 'top_drivers': top_drivers, + 'top_shippers': top_shippers }) def register(request): @@ -58,14 +76,16 @@ def register(request): # Send OTP method = form.cleaned_data.get('verification_method', 'email') + otp_msg = _("Your Masar Verification Code is %(code)s") % {'code': code} + if method == 'whatsapp': phone = user.profile.phone_number - send_whatsapp_message(phone, f"Your verification code is: {code}") + send_whatsapp_message(phone, otp_msg) messages.info(request, _("Verification code sent to WhatsApp.")) else: send_html_email( subject=_('Verification Code'), - message=f'Your verification code is: {code}', + message=otp_msg, recipient_list=[user.email], title=_('Welcome to Masar!'), request=request @@ -124,9 +144,20 @@ def dashboard(request): if profile.role == 'shipper': all_parcels = Parcel.objects.filter(shipper=request.user).order_by('-created_at') - active_parcels = all_parcels.exclude(status__in=['delivered', 'cancelled']) + active_parcels_list = all_parcels.exclude(status__in=['delivered', 'cancelled']) history_parcels = all_parcels.filter(status__in=['delivered', 'cancelled']) + # Pagination for Active Shipments + page = request.GET.get('page', 1) + paginator = Paginator(active_parcels_list, 9) # Show 9 parcels per page + + try: + active_parcels = paginator.page(page) + except PageNotAnInteger: + active_parcels = paginator.page(1) + except EmptyPage: + active_parcels = paginator.page(paginator.num_pages) + platform_profile = PlatformProfile.objects.first() payments_enabled = platform_profile.enable_payment if platform_profile else True @@ -142,9 +173,20 @@ def dashboard(request): payments_enabled = platform_profile.enable_payment if platform_profile else True if payments_enabled: - available_parcels = Parcel.objects.filter(status='pending', payment_status='paid').order_by('-created_at') + available_parcels_list = Parcel.objects.filter(status='pending', payment_status='paid').order_by('-created_at') else: - available_parcels = Parcel.objects.filter(status='pending').order_by('-created_at') + available_parcels_list = Parcel.objects.filter(status='pending').order_by('-created_at') + + # Pagination for Available Shipments + page = request.GET.get('page', 1) + paginator = Paginator(available_parcels_list, 9) # Show 9 parcels per page + + try: + available_parcels = paginator.page(page) + except PageNotAnInteger: + available_parcels = paginator.page(1) + except EmptyPage: + available_parcels = paginator.page(paginator.num_pages) # Active: Picked up or In Transit my_parcels = Parcel.objects.filter(carrier=request.user).exclude(status__in=['delivered', 'cancelled']).order_by('-created_at') @@ -360,10 +402,12 @@ def edit_profile(request): # 4. Send OTP method = data.get('otp_method', 'email') + otp_msg = _("Your Masar Update Code is %(code)s") % {'code': code} + if method == 'whatsapp': # Use current phone if available, else new phone phone = request.user.profile.phone_number or data['phone_number'] - send_whatsapp_message(phone, f"Your verification code is: {code}") + send_whatsapp_message(phone, otp_msg) messages.info(request, _("Verification code sent to WhatsApp.")) else: # Default to email @@ -371,7 +415,7 @@ def edit_profile(request): target_email = data['email'] send_html_email( subject=_('Verification Code'), - message=f'Your verification code is: {code}', + message=otp_msg, recipient_list=[target_email], title=_('Profile Update Verification'), request=request @@ -473,3 +517,136 @@ def rate_driver(request, parcel_id): 'form': form, 'parcel': parcel }) + +@require_POST +def request_login_otp(request): + identifier = request.POST.get('identifier') + + if not identifier: + return JsonResponse({'success': False, 'message': _('Please enter an email or phone number.')}) + + # Clean identifier + identifier = identifier.strip() + + user = None + method = 'email' + + # Try to find user by email + user = User.objects.filter(email__iexact=identifier).first() + + # If not found, try by phone number + if not user: + profile = Profile.objects.filter(phone_number=identifier).first() + if profile: + user = profile.user + method = 'whatsapp' + else: + # Fallback: maybe they entered a phone without country code or with? + # For now, simplistic search + pass + + if not user: + # Don't reveal if user exists or not for security, but for UX on this project we can be a bit more helpful + return JsonResponse({'success': False, 'message': _('User not found with this email or phone number.')}) + + if not user.is_active: + return JsonResponse({'success': False, 'message': _('Account is inactive. Please verify registration first.')}) + + # Generate OTP + code = ''.join(random.choices(string.digits, k=6)) + OTPVerification.objects.create(user=user, code=code, purpose='login') + + # Send OTP + otp_msg = _("Your Masar Login Code is %(code)s. Do not share this code.") % {'code': code} + + try: + if method == 'whatsapp': + phone = user.profile.phone_number + send_whatsapp_message(phone, otp_msg) + message_sent = _("OTP sent to your WhatsApp.") + else: + send_html_email( + subject=_('Login OTP'), + message=otp_msg, + recipient_list=[user.email], + title=_('Login Verification'), + request=request + ) + message_sent = _("OTP sent to your email.") + + return JsonResponse({'success': True, 'message': message_sent, 'user_id': user.id}) + except Exception as e: + return JsonResponse({'success': False, 'message': _('Failed to send OTP. Please try again.')}) + +@require_POST +def verify_login_otp(request): + user_id = request.POST.get('user_id') + code = request.POST.get('code') + + if not user_id or not code: + return JsonResponse({'success': False, 'message': _('Invalid request.')}) + + try: + user = User.objects.get(id=user_id) + otp = OTPVerification.objects.filter( + user=user, + code=code, + purpose='login', + is_verified=False + ).latest('created_at') + + if otp.is_valid(): + # Cleanup + otp.is_verified = True + otp.save() + + # Login + login(request, user) + + return JsonResponse({'success': True, 'redirect_url': reverse('dashboard')}) + else: + return JsonResponse({'success': False, 'message': _('Invalid or expired OTP.')}) + + except (User.DoesNotExist, OTPVerification.DoesNotExist): + return JsonResponse({'success': False, 'message': _('Invalid OTP.')}) + +@require_POST +def chatbot(request): + try: + data = json.loads(request.body) + user_message = data.get("message", "") + language = data.get("language", "en") + + if not user_message: + return JsonResponse({"success": False, "error": "Empty message"}) + + system_prompt = ( + "You are MasarX AI, a helpful and professional assistant for the Masar logistics platform. " + "The platform connects shippers with drivers for small parcel deliveries. " + "Answer the user's questions about shipping, tracking, becoming a driver, or general support. " + "If the user speaks Arabic, reply in Arabic. If English, reply in English. " + "Keep responses concise and helpful." + ) + + if language == "ar": + system_prompt += " The user is currently browsing in Arabic." + else: + system_prompt += " The user is currently browsing in English." + + response = LocalAIApi.create_response({ + "input": [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_message}, + ] + }) + + if response.get("success"): + text = LocalAIApi.extract_text(response) + return JsonResponse({"success": True, "response": text}) + else: + return JsonResponse({"success": False, "error": response.get("error", "AI Error")}) + + except json.JSONDecodeError: + return JsonResponse({"success": False, "error": "Invalid JSON"}) + except Exception as e: + return JsonResponse({"success": False, "error": str(e)}) diff --git a/locale/ar/LC_MESSAGES/django.mo b/locale/ar/LC_MESSAGES/django.mo index 4e3a69382be69bca59f8d214e24ca646f2f23209..84a3de1f4cb9ecac7d408f1329a68630c1583ab3 100644 GIT binary patch delta 6864 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 Z^Dbr-%MHW0sp3v5(zz;DzmvH?=zlt~FNXjC delta 6500 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 fdLEx chatInput.focus(), 100); + } else { + chatWidget.classList.add('d-none'); + } + } + + chatToggle.addEventListener('click', toggleChat); + chatClose.addEventListener('click', toggleChat); + + // Send Message + chatForm.addEventListener('submit', function(e) { + e.preventDefault(); + const message = chatInput.value.trim(); + if (!message) return; + + // Add User Message + addMessage(message, 'user'); + chatInput.value = ''; + + // Show Typing Indicator + const typingId = addTypingIndicator(); + + // Send to Backend + fetch('/ajax/chatbot/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': getCookie('csrftoken') + }, + body: JSON.stringify({ + message: message, + language: document.documentElement.lang || 'en' + }) + }) + .then(response => response.json()) + .then(data => { + removeMessage(typingId); + if (data.success) { + addMessage(data.response, 'bot'); + } else { + addMessage('Sorry, I encountered an error.', 'bot'); + } + }) + .catch(error => { + removeMessage(typingId); + addMessage('Sorry, connection error.', 'bot'); + console.error('Error:', error); + }); + }); + + function addMessage(text, sender) { + const div = document.createElement('div'); + div.className = `d-flex mb-3 ${sender === 'user' ? 'justify-content-end' : 'justify-content-start'}`; + + const bubble = document.createElement('div'); + bubble.className = `p-3 rounded-3 shadow-sm ${sender === 'user' ? 'bg-primary text-white' : 'bg-light text-dark'}`; + bubble.style.maxWidth = '80%'; + bubble.style.wordWrap = 'break-word'; + // Convert newlines to
for basic formatting + bubble.innerHTML = text.replace(/\n/g, '
'); + + div.appendChild(bubble); + chatMessages.appendChild(div); + chatMessages.scrollTop = chatMessages.scrollHeight; + return div.id; + } + + function addTypingIndicator() { + const id = 'typing-' + Date.now(); + const div = document.createElement('div'); + div.id = id; + div.className = 'd-flex mb-3 justify-content-start'; + div.innerHTML = "" + + "
" + + "
" + + " " + + "
" + + "
" + ; + chatMessages.appendChild(div); + chatMessages.scrollTop = chatMessages.scrollHeight; + return id; + } + + function removeMessage(id) { + const el = document.getElementById(id); + if (el) el.remove(); + } + + function getCookie(name) { + let cookieValue = null; + if (document.cookie && document.cookie !== '') { + const cookies = document.cookie.split(';'); + for (let i = 0; i < cookies.length; i++) { + const cookie = cookies[i].trim(); + if (cookie.substring(0, name.length + 1) === (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } +}); diff --git a/update_base.py b/update_base.py new file mode 100644 index 0000000..15994fc --- /dev/null +++ b/update_base.py @@ -0,0 +1,50 @@ +import os + +with open("core/templates/base.html", "r") as f: + content = f.read() + +widget_html = """ + + + +
+
+
+ +
MasarX AI
+
+ +
+ +
+
+
+ {% trans "Hello! How can I help you with your shipments today?" %} +
+
+
+ +
+
+ + +
+
+
+ + +" + +if "masar-chat-toggle" not in content: + new_content = content.replace("", widget_html + "\n") + with open("core/templates/base.html", "w") as f: + f.write(new_content) + print("Updated base.html") +else: + print("Widget already exists") diff --git a/update_base_v2.py b/update_base_v2.py new file mode 100644 index 0000000..d418f08 --- /dev/null +++ b/update_base_v2.py @@ -0,0 +1,36 @@ + + + +
+
+
+ +
MasarX AI
+
+ +
+ +
+
+
+ {% trans "Hello! How can I help you with your shipments today?" %} +
+
+
+ +
+
+ + +
+
+
+ + + \ No newline at end of file