adding chat pot

This commit is contained in:
Flatlogic Bot 2026-01-24 16:05:41 +00:00
parent 290e13038f
commit f5e53da1b1
13 changed files with 629 additions and 3 deletions

Binary file not shown.

Binary file not shown.

View File

@ -21,6 +21,9 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
<!-- Chatbot Widget CSS -->
<link rel="stylesheet" href="{% static 'css/chatbot.css' %}?v={{ deployment_timestamp }}">
{% block head %}{% endblock %}
<style>
body { font-family: 'Outfit', sans-serif; background-color: #f8f9fa; }
@ -185,8 +188,40 @@
</div>
</footer>
<!-- Chatbot Widget HTML -->
<div id="chatbot-widget">
<button id="chatbot-button" title="{% trans 'Chat with MASAR AI' %}">
<i class="fa-solid fa-robot"></i>
</button>
<div id="chatbot-window">
<div id="chatbot-header">
<h5>MASAR AI</h5>
<button id="chatbot-close">&times;</button>
</div>
<div id="chatbot-messages">
<div class="chat-message bot">
{% trans "Hello! I am MASAR, your AI assistant. How can I help you today?" %}
</div>
</div>
<div id="chatbot-input-container">
<input type="text" id="chatbot-input" placeholder="{% trans 'Type a message...' %}">
<button id="chatbot-send">
<i class="fa-solid fa-paper-plane"></i>
</button>
</div>
<div class="px-3 pb-2 text-center">
<a href="https://wa.me/{{ app_settings.contact_phone|default:'' }}" target="_blank" class="small text-muted text-decoration-none">
<i class="fa-brands fa-whatsapp text-success"></i> {% trans "Talk to a Human" %}
</a>
</div>
</div>
</div>
<!-- Bootstrap 5 JS Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Chatbot Widget JS -->
<script src="{% static 'js/chatbot.js' %}?v={{ deployment_timestamp }}"></script>
</body>
</html>

View File

@ -33,4 +33,5 @@ urlpatterns = [
path("admin/financials/", views.admin_financials, name="admin_financials"),
path("admin/refund/<str:receipt_number>/", views.issue_refund, name="issue_refund"),
path("admin/settings/", views.admin_app_settings, name="admin_app_settings"),
path("api/chat/", views.chat_api, name="chat_api"),
]

View File

@ -7,7 +7,7 @@ from django.utils import timezone
from django.urls import reverse
from .models import (
Profile, Truck, Shipment, Bid, Message, OTPCode, Country, City,
AppSetting, Banner, HomeSection, Transaction, ContactMessage, Testimonial
AppSetting, Banner, HomeSection, Transaction, ContactMessage, Testimonial, WhatsAppConfig
)
from .forms import (
TruckForm, ShipmentForm, BidForm, UserRegistrationForm,
@ -23,8 +23,10 @@ from django.contrib.auth.forms import AuthenticationForm
from django.core.mail import send_mail
from django.conf import settings
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
from django.http import HttpResponse, JsonResponse
from django.contrib.sites.shortcuts import get_current_site
from django.utils.translation import get_language
from ai.local_ai_api import LocalAIApi
import json
import logging
import os
@ -778,4 +780,52 @@ def contact(request):
return render(request, 'core/contact.html', {
'form': form,
'app_settings': app_settings
})
})
@csrf_exempt
def chat_api(request):
if request.method != 'POST':
return JsonResponse({'success': False, 'error': 'Method not allowed'}, status=405)
try:
data = json.loads(request.body)
user_message = data.get('message', '')
if not user_message:
return JsonResponse({'success': False, 'error': 'Message is empty'})
# Maintain conversation history in session
history = request.session.get('chat_history', [])
# System prompt to define the bot's personality
system_prompt = {
"role": "system",
"content": "You are MASAR, the AI assistant for MASAR CARGO, a logistics and trucking marketplace in Oman. "
"You help shippers find trucks and truck owners find shipments. "
"Answer questions politely and concisely. If the user asks for something you can't do, "
"suggest contacting support via WhatsApp. "
"Current language: " + (get_language() or 'en')
}
messages_input = [system_prompt] + history + [{"role": "user", "content": user_message}]
response = LocalAIApi.create_response({
"input": messages_input
})
if response.get("success"):
ai_reply = LocalAIApi.extract_text(response)
# Update history
history.append({"role": "user", "content": user_message})
history.append({"role": "assistant", "content": ai_reply})
# Keep only last 10 messages for context efficiency
request.session['chat_history'] = history[-10:]
return JsonResponse({'success': True, 'reply': ai_reply})
else:
logger.error(f"AI Error: {response.get('error')}")
return JsonResponse({'success': False, 'error': 'AI failed to respond'})
except Exception as e:
logger.error(f"Chat API Error: {str(e)}")
return JsonResponse({'success': False, 'error': str(e)})

Binary file not shown.

View File

@ -2093,3 +2093,15 @@ msgstr "حدث خطأ أثناء تحديث شاحنتك. يرجى التحقق
#~ msgid "Contact for Renewal"
#~ msgstr "الاتصال للتجديد"
msgid "Chat with MASAR AI"
msgstr "تحدث مع مسار الذكي"
msgid "Hello! I am MASAR, your AI assistant. How can I help you today?"
msgstr "أهلاً بك! أنا مسار، مساعدك الذكي. كيف يمكنني مساعدتك اليوم؟"
msgid "Type a message..."
msgstr "اكتب رسالة..."
msgid "Talk to a Human"
msgstr "تحدث مع شخص حقيقي"

165
static/css/chatbot.css Normal file
View File

@ -0,0 +1,165 @@
/* Chatbot Floating Widget Styles */
#chatbot-widget {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1000;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
#chatbot-button {
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, #007bff, #0056b3);
color: white;
border: none;
cursor: pointer;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
#chatbot-button:hover {
transform: scale(1.1);
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
}
#chatbot-button svg {
width: 30px;
height: 30px;
}
#chatbot-window {
position: absolute;
bottom: 80px;
right: 0;
width: 350px;
height: 500px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
box-shadow: 0 10px 30px rgba(0,0,0,0.15);
display: none;
flex-direction: column;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.3);
}
#chatbot-window.active {
display: flex;
}
#chatbot-header {
background: linear-gradient(135deg, #007bff, #0056b3);
color: white;
padding: 15px 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
#chatbot-header h5 {
margin: 0;
font-size: 1.1rem;
font-weight: 600;
}
#chatbot-close {
background: none;
border: none;
color: white;
cursor: pointer;
font-size: 1.5rem;
line-height: 1;
}
#chatbot-messages {
flex: 1;
padding: 15px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 10px;
}
.chat-message {
max-width: 80%;
padding: 10px 15px;
border-radius: 15px;
font-size: 0.9rem;
line-height: 1.4;
}
.chat-message.bot {
align-self: flex-start;
background: #f0f2f5;
color: #333;
border-bottom-left-radius: 5px;
}
.chat-message.user {
align-self: flex-end;
background: #007bff;
color: white;
border-bottom-right-radius: 5px;
}
#chatbot-input-container {
padding: 15px;
border-top: 1px solid #eee;
display: flex;
gap: 10px;
}
#chatbot-input {
flex: 1;
border: 1px solid #ddd;
border-radius: 20px;
padding: 8px 15px;
outline: none;
font-size: 0.9rem;
}
#chatbot-send {
background: #007bff;
color: white;
border: none;
border-radius: 50%;
width: 35px;
height: 35px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
/* RTL Support */
[dir="rtl"] #chatbot-widget {
right: auto;
left: 20px;
}
[dir="rtl"] #chatbot-window {
right: auto;
left: 0;
}
[dir="rtl"] .chat-message.bot {
border-bottom-left-radius: 15px;
border-bottom-right-radius: 5px;
}
[dir="rtl"] .chat-message.user {
border-bottom-right-radius: 15px;
border-bottom-left-radius: 5px;
}
.typing-indicator {
font-style: italic;
font-size: 0.8rem;
color: #888;
margin-bottom: 5px;
}

99
static/js/chatbot.js Normal file
View File

@ -0,0 +1,99 @@
document.addEventListener('DOMContentLoaded', function() {
const chatbotButton = document.getElementById('chatbot-button');
const chatbotWindow = document.getElementById('chatbot-window');
const chatbotClose = document.getElementById('chatbot-close');
const chatbotInput = document.getElementById('chatbot-input');
const chatbotSend = document.getElementById('chatbot-send');
const chatbotMessages = document.getElementById('chatbot-messages');
if (!chatbotButton) return;
// Toggle window
chatbotButton.addEventListener('click', () => {
chatbotWindow.classList.toggle('active');
if (chatbotWindow.classList.contains('active')) {
chatbotInput.focus();
}
});
chatbotClose.addEventListener('click', () => {
chatbotWindow.classList.remove('active');
});
// Send message function
async function sendMessage() {
const message = chatbotInput.value.trim();
if (!message) return;
// Add user message to UI
appendMessage('user', message);
chatbotInput.value = '';
// Add typing indicator
const typingId = 'typing-' + Date.now();
const typingDiv = document.createElement('div');
typingDiv.id = typingId;
typingDiv.className = 'typing-indicator';
typingDiv.innerText = document.documentElement.lang === 'ar' ? 'جاري الكتابة...' : 'Typing...';
chatbotMessages.appendChild(typingDiv);
chatbotMessages.scrollTop = chatbotMessages.scrollHeight;
try {
const response = await fetch('/api/chat/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken')
},
body: JSON.stringify({ message: message })
});
const data = await response.json();
// Remove typing indicator
const typingElement = document.getElementById(typingId);
if (typingElement) typingElement.remove();
if (data.success) {
appendMessage('bot', data.reply);
} else {
appendMessage('bot', 'Sorry, I encountered an error. Please try again later.');
}
} catch (error) {
console.error('Error:', error);
const typingElement = document.getElementById(typingId);
if (typingElement) typingElement.remove();
appendMessage('bot', 'Network error. Please check your connection.');
}
}
function appendMessage(role, text) {
const msgDiv = document.createElement('div');
msgDiv.className = `chat-message ${role}`;
msgDiv.innerText = text;
chatbotMessages.appendChild(msgDiv);
chatbotMessages.scrollTop = chatbotMessages.scrollHeight;
}
// Event listeners for sending
chatbotSend.addEventListener('click', sendMessage);
chatbotInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') sendMessage();
});
// CSRF helper
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;
}
});

165
staticfiles/css/chatbot.css Normal file
View File

@ -0,0 +1,165 @@
/* Chatbot Floating Widget Styles */
#chatbot-widget {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1000;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
#chatbot-button {
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, #007bff, #0056b3);
color: white;
border: none;
cursor: pointer;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
#chatbot-button:hover {
transform: scale(1.1);
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
}
#chatbot-button svg {
width: 30px;
height: 30px;
}
#chatbot-window {
position: absolute;
bottom: 80px;
right: 0;
width: 350px;
height: 500px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
box-shadow: 0 10px 30px rgba(0,0,0,0.15);
display: none;
flex-direction: column;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.3);
}
#chatbot-window.active {
display: flex;
}
#chatbot-header {
background: linear-gradient(135deg, #007bff, #0056b3);
color: white;
padding: 15px 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
#chatbot-header h5 {
margin: 0;
font-size: 1.1rem;
font-weight: 600;
}
#chatbot-close {
background: none;
border: none;
color: white;
cursor: pointer;
font-size: 1.5rem;
line-height: 1;
}
#chatbot-messages {
flex: 1;
padding: 15px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 10px;
}
.chat-message {
max-width: 80%;
padding: 10px 15px;
border-radius: 15px;
font-size: 0.9rem;
line-height: 1.4;
}
.chat-message.bot {
align-self: flex-start;
background: #f0f2f5;
color: #333;
border-bottom-left-radius: 5px;
}
.chat-message.user {
align-self: flex-end;
background: #007bff;
color: white;
border-bottom-right-radius: 5px;
}
#chatbot-input-container {
padding: 15px;
border-top: 1px solid #eee;
display: flex;
gap: 10px;
}
#chatbot-input {
flex: 1;
border: 1px solid #ddd;
border-radius: 20px;
padding: 8px 15px;
outline: none;
font-size: 0.9rem;
}
#chatbot-send {
background: #007bff;
color: white;
border: none;
border-radius: 50%;
width: 35px;
height: 35px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
/* RTL Support */
[dir="rtl"] #chatbot-widget {
right: auto;
left: 20px;
}
[dir="rtl"] #chatbot-window {
right: auto;
left: 0;
}
[dir="rtl"] .chat-message.bot {
border-bottom-left-radius: 15px;
border-bottom-right-radius: 5px;
}
[dir="rtl"] .chat-message.user {
border-bottom-right-radius: 15px;
border-bottom-left-radius: 5px;
}
.typing-indicator {
font-style: italic;
font-size: 0.8rem;
color: #888;
margin-bottom: 5px;
}

99
staticfiles/js/chatbot.js Normal file
View File

@ -0,0 +1,99 @@
document.addEventListener('DOMContentLoaded', function() {
const chatbotButton = document.getElementById('chatbot-button');
const chatbotWindow = document.getElementById('chatbot-window');
const chatbotClose = document.getElementById('chatbot-close');
const chatbotInput = document.getElementById('chatbot-input');
const chatbotSend = document.getElementById('chatbot-send');
const chatbotMessages = document.getElementById('chatbot-messages');
if (!chatbotButton) return;
// Toggle window
chatbotButton.addEventListener('click', () => {
chatbotWindow.classList.toggle('active');
if (chatbotWindow.classList.contains('active')) {
chatbotInput.focus();
}
});
chatbotClose.addEventListener('click', () => {
chatbotWindow.classList.remove('active');
});
// Send message function
async function sendMessage() {
const message = chatbotInput.value.trim();
if (!message) return;
// Add user message to UI
appendMessage('user', message);
chatbotInput.value = '';
// Add typing indicator
const typingId = 'typing-' + Date.now();
const typingDiv = document.createElement('div');
typingDiv.id = typingId;
typingDiv.className = 'typing-indicator';
typingDiv.innerText = document.documentElement.lang === 'ar' ? 'جاري الكتابة...' : 'Typing...';
chatbotMessages.appendChild(typingDiv);
chatbotMessages.scrollTop = chatbotMessages.scrollHeight;
try {
const response = await fetch('/api/chat/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken')
},
body: JSON.stringify({ message: message })
});
const data = await response.json();
// Remove typing indicator
const typingElement = document.getElementById(typingId);
if (typingElement) typingElement.remove();
if (data.success) {
appendMessage('bot', data.reply);
} else {
appendMessage('bot', 'Sorry, I encountered an error. Please try again later.');
}
} catch (error) {
console.error('Error:', error);
const typingElement = document.getElementById(typingId);
if (typingElement) typingElement.remove();
appendMessage('bot', 'Network error. Please check your connection.');
}
}
function appendMessage(role, text) {
const msgDiv = document.createElement('div');
msgDiv.className = `chat-message ${role}`;
msgDiv.innerText = text;
chatbotMessages.appendChild(msgDiv);
chatbotMessages.scrollTop = chatbotMessages.scrollHeight;
}
// Event listeners for sending
chatbotSend.addEventListener('click', sendMessage);
chatbotInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') sendMessage();
});
// CSRF helper
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;
}
});