emile 9 months ago
parent ed5ff67bab
commit 411b2e3cca

Binary file not shown.

@ -2,7 +2,14 @@ from osichat.models import *
from rest_framework import serializers from rest_framework import serializers
class VisitorSerializer(serializers.ModelSerializer):
class Meta:
model = Visitor
fields = '__all__'
class ChatRoomGuestSerializer(serializers.ModelSerializer): class ChatRoomGuestSerializer(serializers.ModelSerializer):
visitor = VisitorSerializer()
class Meta: class Meta:
model = ChatRoomGuest model = ChatRoomGuest
fields = '__all__' fields = '__all__'

@ -49,10 +49,83 @@ class OsitcomVisitor(WebsocketConsumer):
class OsitcomChatRooms(WebsocketConsumer):
def connect(self):
async_to_sync(self.channel_layer.group_add)(
'ositcom_chats', self.channel_name
)
self.accept()
def disconnect(self, close_code):
async_to_sync(self.channel_layer.group_discard)(
'ositcom_chats', self.channel_name
)
def receive(self, text_data):
data = json.loads(text_data)
event_type = data.get('event_type')
if event_type == 'set_client_type':
self.client_type = data.get('client_type')
self.user_id = data.get('user_id')
self.get_chats_handler()
def get_chats_handler(self):
chat_rooms = ChatRoom.objects.annotate(last_update=Max('chatmessage__date_sent')).order_by('-last_update', '-date_created')
user = get_object_or_404(User, id=self.user_id)
for room in chat_rooms:
room.number_of_unread = room.unread_messages(user)
context = {
'chat_rooms': chat_rooms,
}
if self.client_type == 'mobile_admin':
chat_rooms_data = [model_to_dict(chat_room) for chat_room in chat_rooms]
self.send(text_data=json.dumps({
'event_type': 'get_chats',
'chat_rooms_data': chat_rooms_data,
}, cls=DjangoJSONEncoder))
else:
html = render_to_string("chat_templates/partials/rooms.html", context=context)
self.send(text_data=json.dumps({
'event_type': 'get_chats',
'html': html,
}))
def new_update_handler(self, event):
chat_room = get_object_or_404(ChatRoom, id=event['chatroom_id'])
user = get_object_or_404(User, id=self.user_id)
number_of_unread = ChatMessage.objects.filter(room=chat_room).exclude(member=user).exclude(chatmessageseen__member=user).count()
context = {
'chat_room': chat_room,
'number_of_unread': number_of_unread
}
if self.client_type == 'mobile_admin':
chat_room_data = model_to_dict(chat_room)
self.send(text_data=json.dumps({
'event_type': 'new_update',
'chat_room_data': chat_room_data,
}, cls=DjangoJSONEncoder))
else:
html = render_to_string("chat_templates/partials/new-chat-room.html", context=context)
self.send(text_data=json.dumps({
'event_type': 'new_update',
'chatroom_id': chat_room.id,
'html': html,
}))
class OsitcomChatRoom(WebsocketConsumer): class OsitcomChatRoom(WebsocketConsumer):
def connect(self): def connect(self):
self.domain = 'https://osina.ositcom.com' self.domain = 'http://192.168.1.111:8000'
self.session_id = self.scope['url_route']['kwargs']['session_id'] self.session_id = self.scope['url_route']['kwargs']['session_id']
self.visitor = Visitor.objects.filter(session_id=self.session_id).last() self.visitor = Visitor.objects.filter(session_id=self.session_id).last()
@ -201,7 +274,7 @@ class OsitcomChatRoom(WebsocketConsumer):
) )
number_of_unread = 0 number_of_unread = 0
else: else:
number_of_unread = ChatMessage.objects.filter(room=self.chat_room).exclude(member = member, chatmessageseen__member=member).count() number_of_unread = ChatMessage.objects.filter(room=self.chat_room).exclude(member = member).exclude(hatmessageseen__member=member).count()
latest_unread_message = ChatMessage.objects.filter(room=self.chat_room).exclude(chatmessageseen__member=member).last() latest_unread_message = ChatMessage.objects.filter(room=self.chat_room).exclude(chatmessageseen__member=member).last()
else: else:
member = None member = None
@ -334,6 +407,7 @@ class OsitcomChatRoom(WebsocketConsumer):
'html': html, 'html': html,
})) }))
def start_conversation_handler(self, event): def start_conversation_handler(self, event):
chat_room = get_object_or_404(ChatRoom, id=event['chat_room_id']) chat_room = get_object_or_404(ChatRoom, id=event['chat_room_id'])
context = { context = {
@ -353,6 +427,7 @@ class OsitcomChatRoom(WebsocketConsumer):
'html': html, 'html': html,
})) }))
def typing_handler(self, event): def typing_handler(self, event):
if event.get('typing_status') == 'typing': if event.get('typing_status') == 'typing':
if event.get('user_id'): if event.get('user_id'):
@ -362,19 +437,37 @@ class OsitcomChatRoom(WebsocketConsumer):
context = { context = {
'member': member, 'member': member,
'chat_room': self.chat_room,
'domain': self.domain 'domain': self.domain
} }
if self.client_type == 'mobile_admin':
html = render_to_string("partials/typing.html", context=context) member_data = None
self.send(text_data=json.dumps({ if member:
'event_type': 'typing', member_data = model_to_dict(member)
'html': html, self.send(text_data=json.dumps({
})) 'event_type': 'typing',
'member_data': member_data,
}, cls=DjangoJSONEncoder))
elif self.client_type == 'website_admin':
html = render_to_string("chat_templates/partials/typing.html", context=context)
self.send(text_data=json.dumps({
'event_type': 'typing',
'user': member.id if member else None,
'html': html,
}))
else:
html = render_to_string("partials/typing.html", context=context)
self.send(text_data=json.dumps({
'event_type': 'typing',
'user': member.id if member else None,
'html': html,
}))
else: else:
self.send(text_data=json.dumps({ self.send(text_data=json.dumps({
'event_type': 'stopped_typing', 'event_type': 'stopped_typing',
})) }))
def send_message_handler(self, event): def send_message_handler(self, event):
chat_message = get_object_or_404(ChatMessage, id=event['chat_message_id']) chat_message = get_object_or_404(ChatMessage, id=event['chat_message_id'])
context = { context = {
@ -391,6 +484,7 @@ class OsitcomChatRoom(WebsocketConsumer):
html = render_to_string("chat_templates/partials/message.html", context=context) html = render_to_string("chat_templates/partials/message.html", context=context)
self.send(text_data=json.dumps({ self.send(text_data=json.dumps({
'event_type': 'send_message', 'event_type': 'send_message',
'user': chat_message.member.id if chat_message.member else None,
'html': html, 'html': html,
})) }))
else: else:
@ -401,6 +495,7 @@ class OsitcomChatRoom(WebsocketConsumer):
'html': html, 'html': html,
})) }))
def uploaded_file_handler(self, event): def uploaded_file_handler(self, event):
message_attachment = get_object_or_404(ChatMessageAttachment, id=event['message_attachment_id']) message_attachment = get_object_or_404(ChatMessageAttachment, id=event['message_attachment_id'])
context = { context = {
@ -425,6 +520,7 @@ class OsitcomChatRoom(WebsocketConsumer):
'html': html, 'html': html,
})) }))
def update_read_messages_handler(self, event): def update_read_messages_handler(self, event):
latest_unread_message_id = event.get('latest_unread_message_id') latest_unread_message_id = event.get('latest_unread_message_id')
if latest_unread_message_id: if latest_unread_message_id:
@ -434,14 +530,21 @@ class OsitcomChatRoom(WebsocketConsumer):
'latest_unread_message': latest_unread_message, 'latest_unread_message': latest_unread_message,
'domain': self.domain 'domain': self.domain
} }
html = render_to_string("partials/unread-messages.html", context=context) if self.client_type == 'mobile_admin':
self.send(text_data=json.dumps({ self.send(text_data=json.dumps({
'event_type': 'update_read_messages', 'event_type': 'update_read_messages',
'html': html, 'number_of_unread': event['number_of_unread'],
})) },cls=DjangoJSONEncoder))
else:
html = render_to_string("partials/unread-messages.html", context=context)
self.send(text_data=json.dumps({
'event_type': 'update_read_messages',
'html': html,
}))
else: else:
latest_unread_message = None latest_unread_message = None
def end_chat_handler(self, event): def end_chat_handler(self, event):
if event['user_id']: if event['user_id']:
member = get_object_or_404(User, id=event['user_id']) member = get_object_or_404(User, id=event['user_id'])

@ -2,6 +2,9 @@ from django.db import models
from osinacore.models import * from osinacore.models import *
import mimetypes import mimetypes
import os import os
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
import json
# Create your models here. # Create your models here.
@ -46,6 +49,20 @@ class ChatRoom(models.Model):
return last_updated_time.strftime('%I:%M %p') return last_updated_time.strftime('%I:%M %p')
else: else:
return last_updated_time.strftime('%d-%m-%Y') return last_updated_time.strftime('%d-%m-%Y')
def unread_messages(self, user):
return ChatMessage.objects.filter(room=self).exclude(member=user).exclude(chatmessageseen__member=user).count()
def save(self, *args, **kwargs):
is_new = self.pk is None
super().save(*args, **kwargs)
channel_layer = get_channel_layer()
event = {
'type': 'new_update_handler',
'chatroom_id': self.id,
}
async_to_sync(channel_layer.group_send)("ositcom_chats", event)
class ChatRoomGuest(models.Model): class ChatRoomGuest(models.Model):
@ -80,6 +97,15 @@ class ChatMessage(models.Model):
member = models.ForeignKey(User, null=True, on_delete=models.SET_NULL, blank=True) member = models.ForeignKey(User, null=True, on_delete=models.SET_NULL, blank=True)
content = models.TextField(null=True, blank=True) content = models.TextField(null=True, blank=True)
date_sent = models.DateTimeField() date_sent = models.DateTimeField()
def save(self, *args, **kwargs):
is_new = self.pk is None
super().save(*args, **kwargs)
channel_layer = get_channel_layer()
event = {
'type': 'new_update_handler',
'chatroom_id': self.room.id,
}
async_to_sync(channel_layer.group_send)("ositcom_chats", event)
class ChatMessageAttachment(models.Model): class ChatMessageAttachment(models.Model):

@ -3,6 +3,7 @@ from .consumers import *
websocket_urlpatterns = [ websocket_urlpatterns = [
path("ws/osichat/visitors/", OsitcomVisitor.as_asgi()), path("ws/osichat/visitors/", OsitcomVisitor.as_asgi()),
path("ws/osichat/rooms/", OsitcomChatRooms.as_asgi()),
path("ws/osichat/<str:session_id>/", OsitcomChatRoom.as_asgi()), path("ws/osichat/<str:session_id>/", OsitcomChatRoom.as_asgi()),
path("ws/osichat-admin/<str:session_id>/<int:chat_id>/", OsitcomChatRoom.as_asgi()), path("ws/osichat-admin/<str:session_id>/<int:chat_id>/", OsitcomChatRoom.as_asgi()),

@ -4,6 +4,16 @@
<p id="chatRoomId" class="hidden">{{chat_room.id}}</p> <p id="chatRoomId" class="hidden">{{chat_room.id}}</p>
<div class="w-full h-full md:h-[450px] lg:h-[550px] bg-white rounded-b-none md:rounded-b-md flex flex-col justify-end"> <div class="w-full h-full md:h-[450px] lg:h-[550px] bg-white rounded-b-none md:rounded-b-md flex flex-col justify-end">
<div class="absolute right-0 bottom-32 bg-secondosiblue px-2 py-3 bg-white border border-gray-100 rounded-l-md z-10 flex flex-col gap-2 shadow-md hidden" id="newMessagesPopMessage">
<div class="w-[22px] h-[22px] rounded-full border border-secondosiblue flex justify-center items-center text-secondosiblue text-xs text-secondosiblue">
<p id="newMessagesCounter">1</p>
</div>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 text-secondosiblue">
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
</svg>
</div>
<div class="overflow-y-auto flex flex-col gap-5 px-5 pt-3" id="conversation"> <div class="overflow-y-auto flex flex-col gap-5 px-5 pt-3" id="conversation">
<!-- ROBOT --> <!-- ROBOT -->
<div class="w-full flex items-end gap-2"> <div class="w-full flex items-end gap-2">
@ -126,8 +136,6 @@
</div> </div>
<audio id="notification-sound" src="{{domain}}/static/notifications/osichat-notification.mp3" preload="auto"></audio> <audio id="notification-sound" src="{{domain}}/static/notifications/osichat-notification.mp3" preload="auto"></audio>
<div id="typing" class="hidden"></div>
</div> </div>
<form class="px-5 pt-3 bg-transparent relative" id="sendMessage"> <form class="px-5 pt-3 bg-transparent relative" id="sendMessage">

@ -1,12 +1,7 @@
<div class="mb-2 flex justify-start items-center gap-2"> <div class="mb-2 flex justify-start items-center gap-2" id="typing">
<div <div
class="w-[30px] h-[30px] rounded-full shadow-md text-white flex justify-center items-center bg-osiblue uppercase text-xs"> class="w-[25px] h-[25px] rounded-full shadow-md text-white flex justify-center items-center bg-osiblue uppercase text-xs">
{% if member.staffprofile.image %} <img class="w-full h-full rounded-full" src="{{domain}}{{member.staffprofile.image.url}}">
<img class="w-full h-full rounded-full"
src="{{domain}}{{member.staffprofile.image.url}}">
{% else %}
<p>{{member.first_name.0}}{{member.last_name.0}}</p>
{% endif %}
</div> </div>
<div <div
class="w-fit rounded-r-3xl rounded-tl-3xl px-3 py-3 bg-gray-50 border border-gray-100 shadow-md flex space-x-1 justify-start items-center"> class="w-fit rounded-r-3xl rounded-tl-3xl px-3 py-3 bg-gray-50 border border-gray-100 shadow-md flex space-x-1 justify-start items-center">

@ -9,8 +9,6 @@ from osichat.models import *
def utilities(request): def utilities(request):
# Combine protocol and domain
current_url = 'https://osina.ositcom.com'
notes = None notes = None
recent_note = None recent_note = None
@ -123,5 +121,5 @@ def utilities(request):
'closed_tickets': closed_tickets, 'closed_tickets': closed_tickets,
'today': today, 'today': today,
'latest_chat_rooms': latest_chat_rooms, 'latest_chat_rooms': latest_chat_rooms,
'current_url': current_url
} }

@ -89,22 +89,7 @@
<audio id="notification-sound" src="{{domain}}/static/notifications/osichat-notification.mp3" preload="auto"></audio> <audio id="notification-sound" src="{{domain}}/static/notifications/osichat-notification.mp3" preload="auto"></audio>
<!-- INCLUDE TYPING HERE -->
<!-- <div class="mb-2 flex justify-start items-center gap-2">
<div
class="w-[30px] h-[30px] rounded-full shadow-md text-white flex justify-center items-center bg-osiblue uppercase text-xs">
<p>nn</p>
</div>
<div
class="w-fit rounded-r-3xl rounded-tl-3xl px-2 py-2 bg-gray-50 border border-gray-100 shadow-md flex space-x-1 justify-start items-center">
<div class="h-2 w-2 bg-secondosiblue rounded-full animate-typing"></div>
<div class="h-2 w-2 bg-osiblue rounded-full animate-typing" style="animation-delay: 0.2s;"></div>
<div class="h-2 w-2 bg-fifthosiblue rounded-full animate-typing" style="animation-delay: 0.4s;">
</div>
</div>
</div> -->
<!-- END TYPING -->
</div> </div>
<!-- INPUT FORM --> <!-- INPUT FORM -->

@ -1,47 +1,14 @@
{% load static %} {% load static %}
<p id="userId" class="hidden">{{request.user.id}}</p>
<div id="chatWidget" class="hidden fixed bg-black bg-opacity-50 inset-0 z-50 h-[100vh]"> <div id="chatWidget" class="hidden fixed bg-black bg-opacity-50 inset-0 z-50 h-[100vh]">
<div <div
class="w-[950px] min-h-[500px] h-[80%] bg-white rounded-md border border-gray-200 shadow-xl z-20 right-9 bottom-24 grid grid-cols-3 fixed"> class="w-[950px] min-h-[500px] h-[80%] bg-white rounded-md border border-gray-200 shadow-xl z-20 right-9 bottom-24 grid grid-cols-3 fixed">
<!-- LEFT SIDE - CHATS --> <!-- LEFT SIDE - CHATS -->
<div id="osichat-conversations" class="h-full overflow-y-auto border-r border-gray-200 bg-gray-50"> <div id="widgetLeftSide" class="h-full overflow-y-auto border-r border-gray-200 bg-gray-50">
<!-- HEADER -->
<div class="grid grid-cols-2 sticky top-0 z-10">
<div
class="w-full flex items-center justify-center gap-1 py-3 px-3 border-b border-gray-100 bg-white rounded-md shadow-md cursor-pointer text-secondosiblue">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-5">
<path stroke-linecap="round" stroke-linejoin="round"
d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 0 1 .865-.501 48.172 48.172 0 0 0 3.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0 0 12 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018Z" />
</svg>
<p>Chats</p>
</div>
<div
class="w-full flex items-center justify-center gap-1 py-3 px-3 border-b border-gray-100 bg-gray-100 text-gray-400 cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-5" fill="none">
<path
d="M2.5 8.18677C2.60406 6.08705 2.91537 4.77792 3.84664 3.84664C4.77792 2.91537 6.08705 2.60406 8.18677 2.5M21.5 8.18677C21.3959 6.08705 21.0846 4.77792 20.1534 3.84664C19.2221 2.91537 17.9129 2.60406 15.8132 2.5M15.8132 21.5C17.9129 21.3959 19.2221 21.0846 20.1534 20.1534C21.0846 19.2221 21.3959 17.9129 21.5 15.8132M8.18676 21.5C6.08705 21.3959 4.77792 21.0846 3.84664 20.1534C2.91537 19.2221 2.60406 17.9129 2.5 15.8132"
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
<path
d="M19.6352 11.3178C19.8784 11.6224 20 11.7746 20 12C20 12.2254 19.8784 12.3776 19.6352 12.6822C18.5423 14.0504 15.7514 17 12 17C8.24862 17 5.45768 14.0504 4.36483 12.6822C4.12161 12.3776 4 12.2254 4 12C4 11.7746 4.12161 11.6224 4.36483 11.3178C5.45768 9.9496 8.24862 7 12 7C15.7514 7 18.5423 9.9496 19.6352 11.3178Z"
stroke="currentColor" stroke-width="1.5" />
<path
d="M14 12C14 10.8954 13.1046 10 12 10C10.8954 10 10 10.8954 10 12C10 13.1046 10.8954 14 12 14C13.1046 14 14 13.1046 14 12Z"
stroke="currentColor" stroke-width="1.5" />
</svg>
<p>Visitors</p>
</div>
</div>
<!-- CONVERSATIONS -->
{% include 'chat_templates/conversations.html' %}
</div> </div>
<!-- RIGHT SIDE -->
<!-- RIGHT SIDE - CONVERSATION --> <div class="col-span-2 overflow-hidden" id="widgetRightSide">
<div class="col-span-2 overflow-hidden" id="inner-conversation">
<!-- the messages are here -->
</div> </div>
</div> </div>
@ -70,5 +37,5 @@
<!---------------------- JS SCRIPTS --------------------> <!---------------------- JS SCRIPTS -------------------->
<script type="text/javascript" src='{% static "js/osichat-admin/conversation.js" %}'></script> <script type="text/javascript" src='{% static "js/osichat-admin/inner-conversation.js" %}'></script>
<script type="text/javascript" src='{% static "js/osichat-admin/chat-toggle.js" %}'></script> <script type="text/javascript" src='{% static "js/osichat-admin/chat-toggle.js" %}'></script>

@ -1,20 +0,0 @@
<p id="userId" class="hidden">{{request.user.id}}</p>
<div class="h-full flex flex-col text-secondosiblue">
<!-- CHATS -->
{% for chat in latest_chat_rooms %}
<div class="chat-room w-full flex flex-col gap-1 py-3 px-3 border-b border-gray-100 text-sm cursor-pointer" data-chatid ="{{chat.id}}" data-session="{{chat.chatroomguest.visitor.session_id}}">
<div class="w-full flex justify-between items-center">
<p class="text-secondosiblue font-poppinsBold">{{chat.chatroomguest.visitor.session_id}}</p>
<div
class="w-[20px] h-[20px] bg-osiblue rounded-full shadow-md flex justify-center items-center text-white text-[10px]">
<p>22</p>
</div>
</div>
<p class="text-gray-500 text-xs">{{chat.chatmessage_set.all.last.content}}</p>
<p class="text-gray-500 opacity-70 text-xs">{{chat.last_updated}}</p>
</div>
{% endfor %}
</div>

@ -0,0 +1,14 @@
<div data-roomid = {{chat_room.id}} data-session="{{chat_room.chatroomguest.visitor.session_id}}" class="chat-room w-full flex flex-col gap-1 py-3 px-3 border-b border-gray-100 text-sm cursor-pointer">
<div class="w-full flex justify-between items-center">
<p class="text-secondosiblue font-poppinsBold">{{chat_room.chatroomguest.visitor.ip_address}}</p>
<div
class="w-[20px] h-[20px] bg-osiblue rounded-full shadow-md flex justify-center items-center text-white text-[10px]">
<p>{{number_of_unread}}</p>
</div>
</div>
<p class="text-gray-500 text-xs">{{chat_room.chatmessage_set.all.last.content}}</p>
<p class="text-gray-500 opacity-70 text-xs">{{chat_room.last_updated}}</p>
</div>

@ -0,0 +1,49 @@
<!-- HEADER -->
<div class="grid grid-cols-2 sticky top-0 z-10">
<div
class="w-full flex items-center justify-center gap-1 py-3 px-3 border-b border-gray-100 bg-white rounded-md shadow-md cursor-pointer text-secondosiblue">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-5">
<path stroke-linecap="round" stroke-linejoin="round"
d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 0 1 .865-.501 48.172 48.172 0 0 0 3.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0 0 12 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018Z" />
</svg>
<p>Chats</p>
</div>
<div
class="w-full flex items-center justify-center gap-1 py-3 px-3 border-b border-gray-100 bg-gray-100 text-gray-400 cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-5" fill="none">
<path
d="M2.5 8.18677C2.60406 6.08705 2.91537 4.77792 3.84664 3.84664C4.77792 2.91537 6.08705 2.60406 8.18677 2.5M21.5 8.18677C21.3959 6.08705 21.0846 4.77792 20.1534 3.84664C19.2221 2.91537 17.9129 2.60406 15.8132 2.5M15.8132 21.5C17.9129 21.3959 19.2221 21.0846 20.1534 20.1534C21.0846 19.2221 21.3959 17.9129 21.5 15.8132M8.18676 21.5C6.08705 21.3959 4.77792 21.0846 3.84664 20.1534C2.91537 19.2221 2.60406 17.9129 2.5 15.8132"
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
<path
d="M19.6352 11.3178C19.8784 11.6224 20 11.7746 20 12C20 12.2254 19.8784 12.3776 19.6352 12.6822C18.5423 14.0504 15.7514 17 12 17C8.24862 17 5.45768 14.0504 4.36483 12.6822C4.12161 12.3776 4 12.2254 4 12C4 11.7746 4.12161 11.6224 4.36483 11.3178C5.45768 9.9496 8.24862 7 12 7C15.7514 7 18.5423 9.9496 19.6352 11.3178Z"
stroke="currentColor" stroke-width="1.5" />
<path
d="M14 12C14 10.8954 13.1046 10 12 10C10.8954 10 10 10.8954 10 12C10 13.1046 10.8954 14 12 14C13.1046 14 14 13.1046 14 12Z"
stroke="currentColor" stroke-width="1.5" />
</svg>
<p>Visitors</p>
</div>
</div>
<div class="h-full flex flex-col text-secondosiblue" id="chatrooms">
<!-- CHATS -->
{% for chat_room in chat_rooms %}
<div data-roomid = {{chat_room.id}} data-session="{{chat_room.chatroomguest.visitor.session_id}}" class="chat-room w-full flex flex-col gap-1 py-3 px-3 border-b border-gray-100 text-sm cursor-pointer">
<div class="w-full flex justify-between items-center">
<p class="text-secondosiblue font-poppinsBold">{{chat_room.chatroomguest.visitor.ip_address}}</p>
<div
class="w-[20px] h-[20px] bg-osiblue rounded-full shadow-md flex justify-center items-center text-white text-[10px]">
<p>{{chat_room.number_of_unread}}</p>
</div>
</div>
<p class="text-gray-500 text-xs">{{chat_room.chatmessage_set.all.last.content}}</p>
<p class="text-gray-500 opacity-70 text-xs">{{chat_room.last_updated}}</p>
</div>
{% endfor %}
</div>

@ -0,0 +1,13 @@
<div class="mb-2 flex justify-start items-center gap-2" id="typing">
<div
class="w-[25px] h-[25px] rounded-full shadow-md text-white flex justify-center items-center bg-osiblue uppercase text-xs">
<img class="w-full h-full rounded-full object-cover" src="{{chat_room.chatroomguest.visitor.flag_image_url}}">
</div>
<div
class="w-fit rounded-r-3xl rounded-tl-3xl px-3 py-3 bg-gray-50 border border-gray-100 shadow-md flex space-x-1 justify-start items-center">
<div class="h-1.5 w-1.5 bg-secondosiblue rounded-full animate-typing"></div>
<div class="h-1.5 w-1.5 bg-osiblue rounded-full animate-typing" style="animation-delay: 0.2s;"></div>
<div class="h-1.5 w-1.5 bg-fifthosiblue rounded-full animate-typing" style="animation-delay: 0.4s;">
</div>
</div>
</div>

@ -1226,6 +1226,9 @@
<!-- ONLINE CONSUMER SCRIPT --> <!-- ONLINE CONSUMER SCRIPT -->
<script type="text/javascript" src='{% static "js/online/online-consumer.js" %}'></script> <script type="text/javascript" src='{% static "js/online/online-consumer.js" %}'></script>
<!-- ROOMS -->
<script type="text/javascript" src='{% static "js/osichat-admin/rooms.js" %}'></script>
<!-- NEW STATUS CONSUMER --> <!-- NEW STATUS CONSUMER -->
<script type="text/javascript" src='{% static "js/status/new-status-consumer.js" %}'></script> <script type="text/javascript" src='{% static "js/status/new-status-consumer.js" %}'></script>
<script type="text/javascript" src='{% static "js/status/update-status-time-consumer.js" %}'></script> <script type="text/javascript" src='{% static "js/status/update-status-time-consumer.js" %}'></script>

@ -842,6 +842,18 @@ video {
top: 14px; top: 14px;
} }
.bottom-2 {
bottom: 0.5rem;
}
.bottom-28 {
bottom: 7rem;
}
.bottom-32 {
bottom: 8rem;
}
.z-10 { .z-10 {
z-index: 10; z-index: 10;
} }
@ -1039,10 +1051,6 @@ video {
height: 2rem; height: 2rem;
} }
.h-\[100\%\] {
height: 100%;
}
.h-\[1000px\] { .h-\[1000px\] {
height: 1000px; height: 1000px;
} }
@ -1192,6 +1200,10 @@ video {
height: 100vh; height: 100vh;
} }
.h-\[22px\] {
height: 22px;
}
.max-h-\[200px\] { .max-h-\[200px\] {
max-height: 200px; max-height: 200px;
} }
@ -1437,6 +1449,10 @@ video {
width: 100%; width: 100%;
} }
.w-\[22px\] {
width: 22px;
}
.min-w-full { .min-w-full {
min-width: 100%; min-width: 100%;
} }
@ -1575,10 +1591,6 @@ video {
cursor: default; cursor: default;
} }
.cursor-pointer {
cursor: pointer;
}
.cursor-none { .cursor-none {
cursor: none; cursor: none;
} }
@ -1587,6 +1599,10 @@ video {
cursor: not-allowed; cursor: not-allowed;
} }
.cursor-pointer {
cursor: pointer;
}
.resize-none { .resize-none {
resize: none; resize: none;
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 929 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 929 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 929 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 937 KiB

@ -1,87 +0,0 @@
const admin_chat_ws_scheme = window.location.protocol === "https:" ? "wss" : "ws";
const protocol = window.location.protocol === "https:" ? "https" : "http";
const admin_chat_domain = "osina.ositcom.com";
let chatWebSocket;
const userId = document.getElementById('userId').textContent.trim();
function handleChatRoomClick(event) {
const sessionId = event.currentTarget.getAttribute('data-session');
const chatId = event.currentTarget.getAttribute('data-chatid');
if (sessionId && chatId) {
openConversation(sessionId, chatId);
} else {
console.error('Session ID not found for this chat room.');
}
}
document.querySelectorAll('.chat-room').forEach(div => {
div.addEventListener('click', handleChatRoomClick);
});
function appendTextAreaScript(domain, conversationContainer) {
if (!document.querySelector(`script[src="${protocol}://${admin_chat_domain}/static/js/osichat-admin/textarea.js"]`)) {
const textareaScript = document.createElement('script');
textareaScript.type = 'text/javascript';
textareaScript.src = `${protocol}://${admin_chat_domain}/static/js/osichat-admin/textarea.js`;
conversationContainer.appendChild(textareaScript);
}
}
function openConversation(sessionid, chatid) {
if (chatWebSocket && chatWebSocket.readyState !== WebSocket.CLOSED) { //Close previous sockets
chatWebSocket.close();
}
chatWebSocket = new WebSocket(`${admin_chat_ws_scheme}://${admin_chat_domain}/ws/osichat-admin/${sessionid}/${chatid}/`);
chatWebSocket.onopen = function () {
console.log('WebSocket connection to osichat established');
chatWebSocket.send(JSON.stringify({ 'event_type': 'load_chat', 'client_type': 'website_admin' }));
};
function handleLoadChatEvent(data, chatWebSocket) {
let chatDiv = document.getElementById('inner-conversation');
chatDiv.innerHTML = data.html;
appendTextAreaScript(admin_chat_domain, chatDiv);
const sendMessageForm = document.querySelector('#sendMessage');
sendMessageForm.addEventListener('submit', function (event) {
event.preventDefault();
const message = event.target.elements.message.value;
const eventMessage = {
'event_type': 'send_message',
'message': message,
'user_id': userId
};
chatWebSocket.send(JSON.stringify(eventMessage));
event.target.reset();
});
}
chatWebSocket.onmessage = function (e) {
const data = JSON.parse(e.data);
switch (data.event_type) {
case 'load_chat':
handleLoadChatEvent(data, chatWebSocket);
break;
case 'send_message':
const messagesDiv = document.getElementById('messages_container');
messagesDiv.insertAdjacentHTML('beforeend', data.html);
if (!data.user) { // If it is sent by a guest play a notification sound for the guest
const notificationSound = document.getElementById('notification-sound');
notificationSound.play();
}
break;
default:
console.log('Unknown event type:', data.event_type);
}
};
chatWebSocket.onclose = function () {
console.log('WebSocket connection to osichat closed');
};
chatWebSocket.onerror = function (error) {
console.error('WebSocket error:', error);
};
}

@ -0,0 +1,100 @@
(function() {
function handleChatRoomClick(event) {
const sessionId = event.currentTarget.getAttribute('data-session');
const chatId = event.currentTarget.getAttribute('data-roomid');
if (sessionId && chatId) {
openConversation(sessionId, chatId);
} else {
console.error('Session ID not found for this chat room.');
}
}
document.querySelectorAll('.chat-room').forEach(div => {
div.addEventListener('click', handleChatRoomClick);
});
function appendTextAreaScript(domain, conversationContainer) {
if (!document.querySelector(`script[src="${protocol}://${admin_chat_domain}/static/js/osichat-admin/textarea.js"]`)) {
const textareaScript = document.createElement('script');
textareaScript.type = 'text/javascript';
textareaScript.src = `${protocol}://${admin_chat_domain}/static/js/osichat-admin/textarea.js`;
conversationContainer.appendChild(textareaScript);
}
}
function openConversation(sessionid, chatid) {
if (osichatadminroomSocket && osichatadminroomSocket.readyState !== WebSocket.CLOSED) { //Close previous sockets
osichatadminroomSocket.close();
}
osichatadminroomSocket = new WebSocket(`${admin_chat_ws_scheme}://${admin_chat_domain}/ws/osichat-admin/${sessionid}/${chatid}/`);
osichatadminroomSocket.onopen = function () {
console.log('WebSocket connection to osichat established');
osichatadminroomSocket.send(JSON.stringify({ 'event_type': 'load_chat', 'client_type': 'website_admin' }));
osichatadminroomSocket.send(JSON.stringify({ 'event_type': 'update_read_messages', 'user_id': userId, 'chat_state': 'open' }));
};
function handleLoadChatEvent(data, osichatadminroomSocket) {
let chatDiv = document.getElementById('widgetRightSide');
chatDiv.innerHTML = data.html;
appendTextAreaScript(admin_chat_domain, chatDiv);
const sendMessageForm = document.querySelector('#sendMessage');
sendMessageForm.addEventListener('submit', function (event) {
event.preventDefault();
const message = event.target.elements.message.value;
const eventMessage = {
'event_type': 'send_message',
'message': message,
'user_id': userId
};
osichatadminroomSocket.send(JSON.stringify(eventMessage));
event.target.reset();
});
}
osichatadminroomSocket.onmessage = function (e) {
const data = JSON.parse(e.data);
const typingDiv = document.getElementById('typing');
const messagesDiv = document.getElementById('messages_container');
switch (data.event_type) {
case 'load_chat':
handleLoadChatEvent(data, osichatadminroomSocket);
break;
case 'typing':
if(!typingDiv && data.user != userId){
messagesDiv.insertAdjacentHTML('beforeend', data.html);
}
break;
case 'stopped_typing':
if (typingDiv) {
typingDiv.remove();
}
break;
case 'send_message':
osichatadminroomSocket.send(JSON.stringify({ 'event_type': 'update_read_messages', 'user_id': userId, 'chat_state': 'open' }));
messagesDiv.insertAdjacentHTML('beforeend', data.html);
if (!data.user) { // If it is sent by a guest play a notification sound for the guest
const notificationSound = document.getElementById('notification-sound');
notificationSound.play();
if (typingDiv) {
typingDiv.remove();
}
break;
}
break;
default:
console.log('Unknown event type:', data.event_type);
}
};
osichatadminroomSocket.onclose = function () {
console.log('WebSocket connection to osichat closed');
};
osichatadminroomSocket.onerror = function (error) {
console.error('WebSocket error:', error);
};
}
})();

@ -0,0 +1,64 @@
let admin_chat_ws_scheme = window.location.protocol === "https:" ? "wss" : "ws";
let protocol = window.location.protocol === "https:" ? "https" : "http";
let admin_chat_domain = "192.168.1.111:8000";
let userId = document.getElementById('userId').textContent.trim();
let osichatroomsSocket;
let osichatadminroomSocket;
function appendInnerConversationScript(div) {
const innerConversationScript = document.createElement('script');
innerConversationScript.type = 'text/javascript';
innerConversationScript.src = `${protocol}://${admin_chat_domain}/static/js/osichat-admin/inner-conversation.js`;
div.appendChild(innerConversationScript);
}
function getRooms(){
osichatroomsSocket = new WebSocket(`${admin_chat_ws_scheme}://${admin_chat_domain}/ws/osichat/rooms/`);
osichatroomsSocket.onopen = function () {
console.log('WebSocket connection to rooms established');
osichatroomsSocket.send(JSON.stringify({
'event_type': 'set_client_type',
'client_type': 'website_admin',
'user_id': userId
}));
};
osichatroomsSocket.onmessage = function (e) {
const data = JSON.parse(e.data);
const leftDiv = document.getElementById('widgetLeftSide');
switch (data.event_type) {
case 'get_chats':
leftDiv.innerHTML = data.html;
appendInnerConversationScript(leftDiv);
break;
case 'new_update':
const roomsContainer = document.getElementById('chatrooms');
const chatRoomDiv = roomsContainer.querySelector(`.chat-room[data-roomid='${data.chatroom_id}']`);
if (chatRoomDiv) {
chatRoomDiv.remove(); // Remove the existing chat room div
}
// Insert the new chat room HTML at the top of the container
const newChatRoomDiv = document.createElement('div');
newChatRoomDiv.innerHTML = data.html;
roomsContainer.insertAdjacentElement('afterbegin', newChatRoomDiv.firstElementChild);
appendInnerConversationScript(leftDiv);
break;
default:
console.log('Unknown event type:', data.event_type);
}
};
osichatroomsSocket.onclose = function () {
console.log('WebSocket connection to rooms closed');
};
osichatroomsSocket.onerror = function (error) {
console.error('WebSocket error:', error);
};
}
getRooms();

@ -21,17 +21,20 @@
const submitButton = document.getElementById('submitMessageButton'); const submitButton = document.getElementById('submitMessageButton');
const typingUserId = document.getElementById('userId').textContent.trim(); const typingUserId = document.getElementById('userId').textContent.trim();
let typingTimeout; let typingTimeout;
let isTyping = false;
// EVENT LISTENERS // EVENT LISTENERS
textarea.addEventListener('input', function () { textarea.addEventListener('input', function () {
// Adjust textarea and button // Adjust textarea and button
adjustTextAreaAndButton(dynamicTextarea, submitButton); adjustTextAreaAndButton(dynamicTextarea, submitButton);
console.log(chatWebSocket); if (!isTyping){
chatWebSocket.send(JSON.stringify({ chatWebSocket.send(JSON.stringify({
'event_type': 'typing', 'event_type': 'typing',
'user_id': typingUserId, 'user_id': typingUserId,
'typing_status': 'typing' 'typing_status': 'typing'
})); }));
isTyping = true;
}
clearTimeout(typingTimeout); clearTimeout(typingTimeout);
typingTimeout = setTimeout(function() { typingTimeout = setTimeout(function() {
@ -40,6 +43,7 @@
'user_id': typingUserId, 'user_id': typingUserId,
'typing_status': 'stopped_typing' 'typing_status': 'stopped_typing'
})); }));
isTyping = false;
}, 3000); }, 3000);
}); });

@ -1,9 +1,10 @@
const chat_ws_scheme = window.location.protocol === "https:" ? "wss" : "ws"; const chat_ws_scheme = window.location.protocol === "https:" ? "wss" : "ws";
const protocol = window.location.protocol === "https:" ? "https" : "http"; const protocol = window.location.protocol === "https:" ? "https" : "http";
const domain = "osina.ositcom.com"; const domain = "192.168.1.111:8000";
let osichatSocket; let osichatSocket;
let isOpen = false; let isOpen = false;
let chatLoaded = false; let chatLoaded = false;
let newMessageCount = 0;
function scrollBottom() { function scrollBottom() {
@ -13,13 +14,31 @@ function scrollBottom() {
} }
} }
function showNewMessageNotification(count) {
const newMessagesPopMessage = document.getElementById('newMessagesPopMessage');
const newMessagesCounter = document.getElementById('newMessagesCounter');
if (newMessagesPopMessage && newMessagesCounter) {
newMessagesCounter.textContent = count;
newMessagesPopMessage.classList.remove('hidden');
console.log('popup displayed')
}
}
function hideNewMessageNotification() {
const newMessagesPopMessage = document.getElementById('newMessagesPopMessage');
if (newMessagesPopMessage) {
newMessagesPopMessage.classList.add('hidden');
}
}
// FUNCTION TO FETCH THE SESSION ID // FUNCTION TO FETCH THE SESSION ID
async function fetchSessionID() { async function fetchSessionID() {
let session_id = 'Unknown'; let session_id = 'Unknown';
while (session_id === 'Unknown') { while (session_id === 'Unknown') {
try { try {
const response = await fetch('https://ositcom.com/get-client-session/'); const response = await fetch('http://192.168.1.111:3000/get-client-session/');
const data = await response.json(); const data = await response.json();
if (data.session_id) { if (data.session_id) {
session_id = data.session_id; session_id = data.session_id;
@ -46,6 +65,8 @@ function hideLoader() {
} }
} }
// FUNCTION TO APPEND THE TEXTAREA SCRIPT // FUNCTION TO APPEND THE TEXTAREA SCRIPT
function appendTextAreaScript(domain, chatDiv) { function appendTextAreaScript(domain, chatDiv) {
const textareaScript = document.createElement('script'); const textareaScript = document.createElement('script');
@ -150,6 +171,7 @@ async function initializeChatWebSocket() {
osichatSocket.onmessage = function (e) { osichatSocket.onmessage = function (e) {
const data = JSON.parse(e.data); const data = JSON.parse(e.data);
const typingDiv = document.getElementById('typing'); const typingDiv = document.getElementById('typing');
const messagesDiv = document.getElementById('messages');
switch (data.event_type) { switch (data.event_type) {
case 'load_chat': case 'load_chat':
handleLoadChatEvent(data, osichatSocket); handleLoadChatEvent(data, osichatSocket);
@ -158,11 +180,14 @@ async function initializeChatWebSocket() {
handleLoadChatEvent(data, osichatSocket); handleLoadChatEvent(data, osichatSocket);
break; break;
case 'typing': case 'typing':
typingDiv.classList.remove('hidden'); if(!typingDiv && data.user){
typingDiv.innerHTML = data.html; messagesDiv.insertAdjacentHTML('beforeend', data.html);
}
break; break;
case 'stopped_typing': case 'stopped_typing':
typingDiv.classList.add('hidden'); if (typingDiv) {
typingDiv.remove();
}
break; break;
case 'send_message': case 'send_message':
if (isOpen) { // If chat widget isOpen (declared in chat-toggle.js) mark all messages as read by guest else just return number of unread messages if (isOpen) { // If chat widget isOpen (declared in chat-toggle.js) mark all messages as read by guest else just return number of unread messages
@ -170,11 +195,17 @@ async function initializeChatWebSocket() {
} else { } else {
osichatSocket.send(JSON.stringify({ 'event_type': 'update_read_messages', 'chat_state': 'closed' })); osichatSocket.send(JSON.stringify({ 'event_type': 'update_read_messages', 'chat_state': 'closed' }));
} }
const messagesDiv = document.getElementById('messages');
messagesDiv.insertAdjacentHTML('beforeend', data.html); messagesDiv.insertAdjacentHTML('beforeend', data.html);
if (data.user) { // If it is sent by an Osina user play a notification sound for the guest if (data.user) { // If it is sent by an Osina user play a notification sound for the guest
const notificationSound = document.getElementById('notification-sound'); const notificationSound = document.getElementById('notification-sound');
notificationSound.play(); notificationSound.play();
if (typingDiv) {
typingDiv.remove();
}
let count = newMessageCount + 1;
newMessageCount = count;
showNewMessageNotification(count);
} }
break; break;
case 'uploaded_file': case 'uploaded_file':

@ -28,10 +28,33 @@
const textarea = document.querySelector('#dynamicTextarea'); const textarea = document.querySelector('#dynamicTextarea');
const conversationContainer = document.getElementById('conversation'); const conversationContainer = document.getElementById('conversation');
const submitButton = document.getElementById('submitMessageButton'); const submitButton = document.getElementById('submitMessageButton');
let typingTimeout;
let isTyping = false;
// EVENT LISTENERS // EVENT LISTENERS
textarea.addEventListener('input', () => adjustTextAreaAndButton(textarea, submitButton)); textarea.addEventListener('input', function () {
// Adjust textarea and button
adjustTextAreaAndButton(dynamicTextarea, submitButton);
if (!isTyping) {
osichatSocket.send(JSON.stringify({
'event_type': 'typing',
'typing_status': 'typing'
}));
isTyping = true;
}
clearTimeout(typingTimeout);
typingTimeout = setTimeout(function() {
osichatSocket.send(JSON.stringify({
'event_type': 'typing',
'typing_status': 'stopped_typing'
}));
isTyping = false;
}, 3000);
});
form.addEventListener('submit', (event) => { form.addEventListener('submit', (event) => {
textarea.style.height = '50px'; textarea.style.height = '50px';

@ -1,5 +1,5 @@
(function () { (function () {
const imageDomain = "https://osina.ositcom.com"; const imageDomain = "http://192.168.1.111:8000";
// TO TRIGGER TEH FILE UPLOADER WHEN CLICKING ON THE UPLOAD FILE SVG // TO TRIGGER TEH FILE UPLOADER WHEN CLICKING ON THE UPLOAD FILE SVG
document.getElementById('svgFileUpload').addEventListener('click', function () { document.getElementById('svgFileUpload').addEventListener('click', function () {

@ -1,11 +1,11 @@
const visitors_ws_scheme = window.location.protocol === "https:" ? "wss" : "ws"; const visitors_ws_scheme = window.location.protocol === "https:" ? "wss" : "ws";
const my_domain = "osina.ositcom.com"; const my_domain = "192.168.1.111:8000";
async function fetchClientData() { async function fetchClientData() {
let clientData = { client_ip: 'Unknown', client_country: 'Unknown' }; let clientData = { client_ip: 'Unknown', client_country: 'Unknown' };
while (clientData.client_ip === 'Unknown') { while (clientData.client_ip === 'Unknown') {
try { try {
const response = await fetch('https://osina.ositcom.com/get-client-ip/'); const response = await fetch('http://192.168.1.111:8000/get-client-ip/');
const data = await response.json(); const data = await response.json();
if (data.ip) { if (data.ip) {
clientData = { clientData = {
@ -25,7 +25,7 @@ async function fetchVisitorsSession() {
let session_id = 'Unknown'; let session_id = 'Unknown';
while (session_id === 'Unknown') { while (session_id === 'Unknown') {
try { try {
const response = await fetch('https://ositcom.com/get-client-session/'); const response = await fetch('http://192.168.1.111:3000/get-client-session/');
const data = await response.json(); const data = await response.json();
if (data.session_id) { if (data.session_id) {
session_id = data.session_id; session_id = data.session_id;

Loading…
Cancel
Save