emile 9 months ago
parent 909b7bdb39
commit 5b27f5284f

Binary file not shown.

@ -60,13 +60,19 @@ class OsitcomVisitor(WebsocketConsumer):
'visitors': visitors, 'visitors': visitors,
} }
if self.client_type == 'mobile_admin': if self.client_type == 'mobile_admin':
visitor_data = [] visitors_data = []
for visitor in visitors: for visitor in visitors:
visitor_data = model_to_dict(visitor) visitor_dict = model_to_dict(visitor)
visitor_logs = VisitorLog.objects.filter(visitor=visitor)
visitor_logs_data = [model_to_dict(log) for log in visitor_logs]
visitor_dict['visitor_logs'] = visitor_logs_data
visitors_data.append(visitor_dict)
self.send(text_data=json.dumps({ self.send(text_data=json.dumps({
'event_type': 'get_visitors', 'event_type': 'get_visitors',
'visitor_data': visitor_data, 'visitors_data': visitors_data,
}, cls=DjangoJSONEncoder)) }, cls=DjangoJSONEncoder))
@ -147,6 +153,7 @@ class OsitcomChatRooms(WebsocketConsumer):
chat_room = get_object_or_404(ChatRoom, id=event['chatroom_id']) chat_room = get_object_or_404(ChatRoom, id=event['chatroom_id'])
user = get_object_or_404(User, id=self.user_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() number_of_unread = ChatMessage.objects.filter(room=chat_room).exclude(member=user).exclude(chatmessageseen__member=user).count()
last_message = ChatMessage.objects.filter(room=chat_room).last()
context = { context = {
'chat_room': chat_room, 'chat_room': chat_room,
@ -163,6 +170,7 @@ class OsitcomChatRooms(WebsocketConsumer):
self.send(text_data=json.dumps({ self.send(text_data=json.dumps({
'event_type': 'new_update', 'event_type': 'new_update',
'chatroom_id': chat_room.id, 'chatroom_id': chat_room.id,
'user': last_message.member.id if last_message and last_message.member else None,
'html': html, 'html': html,
})) }))
@ -237,10 +245,6 @@ class OsitcomChatRoom(WebsocketConsumer):
) )
self.chat_room = chat_room self.chat_room = chat_room
self.group = f"{self.session_id}_{self.chat_room.id}" self.group = f"{self.session_id}_{self.chat_room.id}"
event = {
'type': 'start_conversation_handler',
'chat_room_id': chat_room.id
}
async_to_sync(self.channel_layer.group_discard)( async_to_sync(self.channel_layer.group_discard)(
self.group, self.channel_name self.group, self.channel_name
) )
@ -251,6 +255,11 @@ class OsitcomChatRoom(WebsocketConsumer):
self.group, event self.group, event
) )
event = {
'type': 'start_conversation_handler',
'chat_room_id': chat_room.id
}
if event_type == 'typing': if event_type == 'typing':
event = { event = {
'type': 'typing_handler', 'type': 'typing_handler',

@ -1,5 +1,19 @@
{% load static %} {% load static %}
<div class="w-full h-full flex flex-col justify-end"> <!-- RECONNECTING LOADER -->
<div id="roomLoader" class="w-full h-full absolute flex justify-center items-center bg-black bg-opacity-60 z-20 inset-0 rounded-r-md hidden">
<div class="flex flex-col justify-center items-center gap-2">
<p class="text-white">Reconneting</p>
<div class="w-fit flex gap-1 justify-start items-center">
<div class="h-2 w-2 bg-white rounded-full animate-typing shadow-md"></div>
<div class="h-2 w-2 bg-white rounded-full animate-typing shadow-md" style="animation-delay: 0.2s;"></div>
<div class="h-2 w-2 bg-white rounded-full animate-typing shadow-md" style="animation-delay: 0.4s;">
</div>
</div>
</div>
</div>
<div class="w-full h-full flex flex-col justify-between">
<!-- HEADER --> <!-- HEADER -->
<div class="w-full flex items-center gap-1 py-3 px-3 border-b border-gray-100 text-[17px] text-secondosiblue"> <div class="w-full flex items-center gap-1 py-3 px-3 border-b border-gray-100 text-[17px] text-secondosiblue">
<div class="w-[30px] h-[30px] rounded-full shadow-md text-white flex justify-center items-center bg-osiblue uppercase text-xs"> <div class="w-[30px] h-[30px] rounded-full shadow-md text-white flex justify-center items-center bg-osiblue uppercase text-xs">
@ -9,113 +23,113 @@
<p>{{chat_room.chatroomguest.visitor.name}}</p> <p>{{chat_room.chatroomguest.visitor.name}}</p>
</div> </div>
<!-- MESSAGES --> <div class="overflow-y-auto flex flex-col justify-end">
<div class="flex-1 overflow-y-auto p-3 flex flex-col gap-4" id="messages_container"> <!-- MESSAGES -->
{% for message in chat_room_messages %} <div class="overflow-y-auto p-3 flex flex-col gap-4" id="messages_container">
{% if message.member %} {% for message in chat_room_messages %}
{% if not message.chatmessageattachment %} {% if message.member %}
<div class="w-full flex justify-end"> {% if not message.chatmessageattachment %}
<div
class="max-w-[80%] px-4 py-3 rounded-l-3xl rounded-tr-3xl text-white shadow-md text-sm leading-6 bg-opacity-70 bg-osiblue">
<p style="white-space: pre-line; overflow-wrap: anywhere;">{{message.content}}</p>
</div>
</div>
{% else %}
{% if message.chatmessageattachment.is_image %}
<div class="w-full flex justify-end"> <div class="w-full flex justify-end">
<div class="max-w-[80%] p-4 rounded-l-3xl rounded-tr-3xl text-white shadow-md text-sm leading-6 bg-opacity-70 bg-osiblue"> <div
<img src="{{domain}}/{{message.chatmessageattachment.attachment}}" class="rounded-md"> class="max-w-[80%] px-4 py-3 rounded-l-3xl rounded-tr-3xl text-white shadow-md text-sm leading-6 bg-opacity-70 bg-osiblue">
<p style="white-space: pre-line; overflow-wrap: anywhere;">{{message.content}}</p>
</div> </div>
</div> </div>
{% else %} {% else %}
<div class="w-full flex justify-end"> {% if message.chatmessageattachment.is_image %}
<div class="max-w-[80%] p-4 rounded-l-3xl rounded-tr-3xl text-white shadow-md text-sm leading-6 bg-opacity-70 bg-osiblue"> <div class="w-full flex justify-end">
<div class="w-full flex items-center gap-1"> <div class="max-w-[80%] p-4 rounded-l-3xl rounded-tr-3xl text-white shadow-md text-sm leading-6 bg-opacity-70 bg-osiblue">
<div> <img src="{{domain}}/{{message.chatmessageattachment.attachment}}" class="rounded-md">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-5 text-white notFilledSvg"> </div>
<path d="M8 7L16 7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> </div>
<path d="M8 11L12 11" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> {% else %}
<path d="M13 21.5V21C13 18.1716 13 16.7574 13.8787 15.8787C14.7574 15 16.1716 15 19 15H19.5M20 13.3431V10C20 6.22876 20 4.34315 18.8284 3.17157C17.6569 2 15.7712 2 12 2C8.22877 2 6.34315 2 5.17157 3.17157C4 4.34314 4 6.22876 4 10L4 14.5442C4 17.7892 4 19.4117 4.88607 20.5107C5.06508 20.7327 5.26731 20.9349 5.48933 21.1139C6.58831 22 8.21082 22 11.4558 22C12.1614 22 12.5141 22 12.8372 21.886C12.9044 21.8623 12.9702 21.835 13.0345 21.8043C13.3436 21.6564 13.593 21.407 14.0919 20.9081L18.8284 16.1716C19.4065 15.5935 19.6955 15.3045 19.8478 14.9369C20 14.5694 20 14.1606 20 13.3431Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> <div class="w-full flex justify-end">
</svg> <div class="max-w-[80%] p-4 rounded-l-3xl rounded-tr-3xl text-white shadow-md text-sm leading-6 bg-opacity-70 bg-osiblue">
</div> <div class="w-full flex items-center gap-1">
<div class="flex flex-col"> <div>
<span class="text-xs">{{message.chatmessageattachment.file_name}}</span> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-5 text-white notFilledSvg">
<path d="M8 7L16 7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M8 11L12 11" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M13 21.5V21C13 18.1716 13 16.7574 13.8787 15.8787C14.7574 15 16.1716 15 19 15H19.5M20 13.3431V10C20 6.22876 20 4.34315 18.8284 3.17157C17.6569 2 15.7712 2 12 2C8.22877 2 6.34315 2 5.17157 3.17157C4 4.34314 4 6.22876 4 10L4 14.5442C4 17.7892 4 19.4117 4.88607 20.5107C5.06508 20.7327 5.26731 20.9349 5.48933 21.1139C6.58831 22 8.21082 22 11.4558 22C12.1614 22 12.5141 22 12.8372 21.886C12.9044 21.8623 12.9702 21.835 13.0345 21.8043C13.3436 21.6564 13.593 21.407 14.0919 20.9081L18.8284 16.1716C19.4065 15.5935 19.6955 15.3045 19.8478 14.9369C20 14.5694 20 14.1606 20 13.3431Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
</div>
<div class="flex flex-col">
<span class="text-xs">{{message.chatmessageattachment.file_name}}</span>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> {% endif %}
{% endif %} {% endif %}
{% endif %} {% else %}
{% else %} <div class="w-full flex items-end justify-start gap-2">
<div class="w-full flex items-end justify-start gap-2"> <div>
<div> <div
<div class="w-[25px] h-[25px] 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"> <img class="w-full h-full object-cover rounded-full" src="{{chat_room.chatroomguest.visitor.flag_image_url}}">
<img class="w-full h-full object-cover rounded-full" src="{{chat_room.chatroomguest.visitor.flag_image_url}}"> </div>
</div>
</div>
{% if not message.chatmessageattachment %}
<div
class="max-w-[80%] bg-gray-50 px-4 py-3 rounded-r-3xl rounded-tl-3xl text-secondosiblue text-sm leading-6 bg-opacity-50 shadow-md border border-gray-100">
<p style="white-space: pre-line; overflow-wrap: anywhere;">{{message.content}}</p>
</div> </div>
{% else %} {% if not message.chatmessageattachment %}
{% if message.chatmessageattachment.is_image %}
<div <div
class="max-w-[80%] bg-gray-50 p-4 rounded-r-3xl rounded-tl-3xl text-secondosiblue text-sm leading-6 bg-opacity-50 shadow-md border border-gray-100"> class="max-w-[80%] bg-gray-50 px-4 py-3 rounded-r-3xl rounded-tl-3xl text-secondosiblue text-sm leading-6 bg-opacity-50 shadow-md border border-gray-100">
<img src="{{domain}}/{{message.chatmessageattachment.attachment}}" class="rounded-md"> <p style="white-space: pre-line; overflow-wrap: anywhere;">{{message.content}}</p>
</div> </div>
{% else %} {% else %}
<div {% if message.chatmessageattachment.is_image %}
class="max-w-[80%] bg-gray-50 p-4 rounded-r-3xl rounded-tl-3xl text-secondosiblue text-sm leading-6 bg-opacity-50 shadow-md border border-gray-100"> <div
<div class="w-full flex items-center gap-1"> class="max-w-[80%] bg-gray-50 p-4 rounded-r-3xl rounded-tl-3xl text-secondosiblue text-sm leading-6 bg-opacity-50 shadow-md border border-gray-100">
<div> <img src="{{domain}}/{{message.chatmessageattachment.attachment}}" class="rounded-md">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-5 text-secondosiblue notFilledSvg"> </div>
<path d="M8 7L16 7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> {% else %}
<path d="M8 11L12 11" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> <div
<path d="M13 21.5V21C13 18.1716 13 16.7574 13.8787 15.8787C14.7574 15 16.1716 15 19 15H19.5M20 13.3431V10C20 6.22876 20 4.34315 18.8284 3.17157C17.6569 2 15.7712 2 12 2C8.22877 2 6.34315 2 5.17157 3.17157C4 4.34314 4 6.22876 4 10L4 14.5442C4 17.7892 4 19.4117 4.88607 20.5107C5.06508 20.7327 5.26731 20.9349 5.48933 21.1139C6.58831 22 8.21082 22 11.4558 22C12.1614 22 12.5141 22 12.8372 21.886C12.9044 21.8623 12.9702 21.835 13.0345 21.8043C13.3436 21.6564 13.593 21.407 14.0919 20.9081L18.8284 16.1716C19.4065 15.5935 19.6955 15.3045 19.8478 14.9369C20 14.5694 20 14.1606 20 13.3431Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> class="max-w-[80%] bg-gray-50 p-4 rounded-r-3xl rounded-tl-3xl text-secondosiblue text-sm leading-6 bg-opacity-50 shadow-md border border-gray-100">
</svg> <div class="w-full flex items-center gap-1">
</div> <div>
<div class="flex flex-col"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-5 text-secondosiblue notFilledSvg">
<span class="text-xs">{{message.chatmessageattachment.file_name}}</span> <path d="M8 7L16 7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M8 11L12 11" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M13 21.5V21C13 18.1716 13 16.7574 13.8787 15.8787C14.7574 15 16.1716 15 19 15H19.5M20 13.3431V10C20 6.22876 20 4.34315 18.8284 3.17157C17.6569 2 15.7712 2 12 2C8.22877 2 6.34315 2 5.17157 3.17157C4 4.34314 4 6.22876 4 10L4 14.5442C4 17.7892 4 19.4117 4.88607 20.5107C5.06508 20.7327 5.26731 20.9349 5.48933 21.1139C6.58831 22 8.21082 22 11.4558 22C12.1614 22 12.5141 22 12.8372 21.886C12.9044 21.8623 12.9702 21.835 13.0345 21.8043C13.3436 21.6564 13.593 21.407 14.0919 20.9081L18.8284 16.1716C19.4065 15.5935 19.6955 15.3045 19.8478 14.9369C20 14.5694 20 14.1606 20 13.3431Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
</div>
<div class="flex flex-col">
<span class="text-xs">{{message.chatmessageattachment.file_name}}</span>
</div>
</div> </div>
</div> </div>
</div> {% endif %}
{% endif %} {% endif %}
{% endif %} </div>
</div> {% endif %}
{% endif %} {% endfor %}
{% endfor %}
<audio id="notification-sound" src="{{domain}}/static/notifications/osichat-notification.mp3" preload="auto"></audio>
</div> </div>
<!-- INPUT FORM --> <!-- INPUT FORM -->
<form class="px-5 pb-5 bg-transparent relative" id="sendMessage"> <form class="px-5 pb-5 bg-transparent relative" id="sendMessage">
{% csrf_token %} {% csrf_token %}
<div class="w-full bg-white h-fit rounded-md border border-gray-200 flex items-center justify-between"> <div class="w-full bg-white h-fit rounded-md border border-gray-200 flex items-center justify-between">
<textarea name="message" id="dynamicTextarea" placeholder="Write your message..." <textarea name="message" id="dynamicTextarea" placeholder="Write your message..."
class="w-full outline-none p-3 resize-none h-[50px] max-h-[200px] duration-500"></textarea> class="w-full outline-none p-3 resize-none h-[50px] max-h-[200px] duration-500"></textarea>
<div class="h-full right-0 top-0 px-3 flex items-center gap-2 text-osiblue"> <div class="h-full right-0 top-0 px-3 flex items-center gap-2 text-osiblue">
<div class="relative"> <div class="relative">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-5 notFilledSvg cursor-pointer" id="svgFileUpload"> stroke="currentColor" class="w-5 notFilledSvg cursor-pointer" id="svgFileUpload">
<path stroke-linecap="round" stroke-linejoin="round" <path stroke-linecap="round" stroke-linejoin="round"
d="m18.375 12.739-7.693 7.693a4.5 4.5 0 0 1-6.364-6.364l10.94-10.94A3 3 0 1 1 19.5 7.372L8.552 18.32m.009-.01-.01.01m5.699-9.941-7.81 7.81a1.5 1.5 0 0 0 2.112 2.13" /> d="m18.375 12.739-7.693 7.693a4.5 4.5 0 0 1-6.364-6.364l10.94-10.94A3 3 0 1 1 19.5 7.372L8.552 18.32m.009-.01-.01.01m5.699-9.941-7.81 7.81a1.5 1.5 0 0 0 2.112 2.13" />
</svg> </svg>
<input type="file" id="fileupload" name="" placeholder="Select file" multiple class="hidden"> <input type="file" id="fileupload" name="" placeholder="Select file" multiple class="hidden">
<select id="filePathInput" name="filePath" multiple hidden></select> <select id="filePathInput" name="filePath" multiple hidden></select>
</div>
<button type="submit" id="submitMessageButton" class="hidden">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-5 notFilledSvg">
<path stroke-linecap="round" stroke-linejoin="round"
d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5" />
</svg>
</button>
</div> </div>
<button type="submit" id="submitMessageButton" class="hidden">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-5 notFilledSvg">
<path stroke-linecap="round" stroke-linejoin="round"
d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5" />
</svg>
</button>
</div> </div>
</div> </form>
</form> </div>
</div> </div>

@ -4,11 +4,11 @@
<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="widgetLeftSide" 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 relative rounded-l-md">
</div> </div>
<!-- RIGHT SIDE --> <!-- RIGHT SIDE -->
<div class="col-span-2 overflow-hidden" id="widgetRightSide"> <div class="col-span-2 overflow-hidden relative" id="widgetRightSide">
</div> </div>
</div> </div>

@ -2,8 +2,8 @@
<div class="w-full flex justify-between items-center"> <div class="w-full flex justify-between items-center">
<p class="text-secondosiblue font-poppinsBold">{{chat_room.chatroomguest.visitor.ip_address}}</p> <p class="text-secondosiblue font-poppinsBold">{{chat_room.chatroomguest.visitor.ip_address}}</p>
<div <div data-roomid={{chat_room.id}}
class="w-[20px] h-[20px] bg-osiblue rounded-full shadow-md flex justify-center items-center text-white text-[10px]"> class="unread w-[20px] h-[20px] bg-osiblue rounded-full shadow-md flex justify-center items-center text-white text-[10px] {% if chat_room.number_of_unread == 0 %} hidden {% endif %}">
<p>{{number_of_unread}}</p> <p>{{number_of_unread}}</p>
</div> </div>
</div> </div>

@ -1,49 +1,65 @@
<!-- HEADER --> <!-- RECONNECTING LOADER -->
<div class="grid grid-cols-2 sticky top-0 z-10"> <div id="roomsLoader" class="w-full h-full absolute flex justify-center items-center bg-black bg-opacity-60 z-20 inset-0 rounded-l-md hidden">
<div <div class="flex flex-col justify-center items-center gap-2">
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"> <p class="text-white">Reconneting</p>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" <div class="w-fit flex gap-1 justify-start items-center">
stroke="currentColor" class="w-5"> <div class="h-2 w-2 bg-white rounded-full animate-typing shadow-md"></div>
<path stroke-linecap="round" stroke-linejoin="round" <div class="h-2 w-2 bg-white rounded-full animate-typing shadow-md" style="animation-delay: 0.2s;"></div>
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" /> <div class="h-2 w-2 bg-white rounded-full animate-typing shadow-md" style="animation-delay: 0.4s;">
</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>
</div>
</div>
</div>
<div class="h-full flex flex-col text-secondosiblue" id="chatrooms"> <!-- HEADER -->
<!-- CHATS --> <div class="grid grid-cols-2 sticky top-0 z-10 rounded-l-md">
{% for chat_room in chat_rooms %} <div
<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"> 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">
<div class="w-full flex justify-between items-center"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
<p class="text-secondosiblue font-poppinsBold">{{chat_room.chatroomguest.visitor.ip_address}}</p> class="w-5">
<path stroke-linecap="round" stroke-linejoin="round"
<div 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" />
class="w-[20px] h-[20px] bg-osiblue rounded-full shadow-md flex justify-center items-center text-white text-[10px]"> </svg>
<p>{{chat_room.number_of_unread}}</p> <p>Chats</p>
</div> </div>
</div>
<p class="text-gray-500 text-xs">{{chat_room.chatmessage_set.all.last.content}}</p> <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">
<p class="text-gray-500 opacity-70 text-xs">{{chat_room.last_updated}}</p> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-5" fill="none">
</div> <path
{% endfor %} 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"
</div> 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 data-roomid={{chat_room.id}}
class="unread w-[20px] h-[20px] bg-osiblue rounded-full shadow-md flex justify-center items-center text-white text-[10px] {% if chat_room.number_of_unread == 0 %} hidden {% endif %}">
<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>
<audio id="notification-sound" src="{{domain}}/static/notifications/osichat-notification.mp3" preload="auto"></audio>

@ -714,6 +714,10 @@ video {
bottom: 4rem; bottom: 4rem;
} }
.bottom-2 {
bottom: 0.5rem;
}
.bottom-20 { .bottom-20 {
bottom: 5rem; bottom: 5rem;
} }
@ -722,10 +726,18 @@ video {
bottom: 6rem; bottom: 6rem;
} }
.bottom-28 {
bottom: 7rem;
}
.bottom-3 { .bottom-3 {
bottom: 0.75rem; bottom: 0.75rem;
} }
.bottom-32 {
bottom: 8rem;
}
.bottom-5 { .bottom-5 {
bottom: 1.25rem; bottom: 1.25rem;
} }
@ -842,18 +854,6 @@ 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;
} }
@ -1103,6 +1103,10 @@ video {
height: 210px; height: 210px;
} }
.h-\[22px\] {
height: 22px;
}
.h-\[250px\] { .h-\[250px\] {
height: 250px; height: 250px;
} }
@ -1200,10 +1204,6 @@ video {
height: 100vh; height: 100vh;
} }
.h-\[22px\] {
height: 22px;
}
.max-h-\[200px\] { .max-h-\[200px\] {
max-height: 200px; max-height: 200px;
} }
@ -1332,6 +1332,10 @@ video {
width: 220px; width: 220px;
} }
.w-\[22px\] {
width: 22px;
}
.w-\[240px\] { .w-\[240px\] {
width: 240px; width: 240px;
} }
@ -1449,10 +1453,6 @@ video {
width: 100%; width: 100%;
} }
.w-\[22px\] {
width: 22px;
}
.min-w-full { .min-w-full {
min-width: 100%; min-width: 100%;
} }
@ -1843,6 +1843,11 @@ video {
border-top-right-radius: 0px; border-top-right-radius: 0px;
} }
.rounded-l {
border-top-left-radius: 0.25rem;
border-bottom-left-radius: 0.25rem;
}
.rounded-bl-md { .rounded-bl-md {
border-bottom-left-radius: 0.375rem; border-bottom-left-radius: 0.375rem;
} }
@ -2196,6 +2201,10 @@ video {
--tw-bg-opacity: 0.8; --tw-bg-opacity: 0.8;
} }
.bg-opacity-60 {
--tw-bg-opacity: 0.6;
}
.bg-gradient-to-b { .bg-gradient-to-b {
background-image: linear-gradient(to bottom, var(--tw-gradient-stops)); background-image: linear-gradient(to bottom, var(--tw-gradient-stops));
} }
@ -3190,6 +3199,10 @@ video {
} }
} */ } */
.hover\:pointer-events-none:hover {
pointer-events: none;
}
.hover\:scale-105:hover { .hover\:scale-105:hover {
--tw-scale-x: 1.05; --tw-scale-x: 1.05;
--tw-scale-y: 1.05; --tw-scale-y: 1.05;
@ -3202,6 +3215,10 @@ video {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
} }
.hover\:overflow-hidden:hover {
overflow: hidden;
}
.hover\:bg-gray-100:hover { .hover\:bg-gray-100:hover {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(243 244 246 / var(--tw-bg-opacity)); background-color: rgb(243 244 246 / var(--tw-bg-opacity));

@ -2,99 +2,154 @@
function handleChatRoomClick(event) { function handleChatRoomClick(event) {
const sessionId = event.currentTarget.getAttribute('data-session'); const sessionId = event.currentTarget.getAttribute('data-session');
const chatId = event.currentTarget.getAttribute('data-roomid'); const chatId = event.currentTarget.getAttribute('data-roomid');
if (sessionId && chatId) { if (sessionId && chatId && chatId !== currentChatId) {
showLoader();
openConversation(sessionId, chatId); openConversation(sessionId, chatId);
} else { currentChatId = chatId;
console.error('Session ID not found for this chat room.');
} }
} }
document.querySelectorAll('.chat-room').forEach(div => { document.querySelectorAll('.chat-room').forEach(div => {
div.addEventListener('click', handleChatRoomClick); div.addEventListener('click', handleChatRoomClick);
}); });
function appendTextAreaScript(domain, conversationContainer) {
if (!document.querySelector(`script[src="${protocol}://${admin_chat_domain}/static/js/osichat-admin/textarea.js"]`)) { function markCurrentChatRead(chatid) {
const textareaScript = document.createElement('script'); const unreadElement = document.querySelector(`.unread[data-roomid='${chatid}']`);
textareaScript.type = 'text/javascript'; if (unreadElement) {
textareaScript.src = `${protocol}://${admin_chat_domain}/static/js/osichat-admin/textarea.js`; unreadElement.classList.add('hidden');
conversationContainer.appendChild(textareaScript);
} }
} }
// FUNCTIONS TO SHOW & HIDE THE LOADER
function showLoader() {
const roomLoader = document.getElementById('roomLoader');
if (roomLoader) {
roomLoader.classList.remove('hidden');
}
function openConversation(sessionid, chatid) { const widgetLeftSide = document.getElementById('widgetLeftSide');
if (osichatadminroomSocket && osichatadminroomSocket.readyState !== WebSocket.CLOSED) { //Close previous sockets widgetLeftSide.classList.remove('overflow-y-auto');
osichatadminroomSocket.close(); widgetLeftSide.classList.add('overflow-hidden');
} }
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) { function hideLoader() {
const data = JSON.parse(e.data); const roomLoader = document.getElementById('roomLoader');
const typingDiv = document.getElementById('typing'); if (roomLoader) {
const messagesDiv = document.getElementById('messages_container'); roomLoader.classList.add('hidden');
switch (data.event_type) { }
case 'load_chat':
handleLoadChatEvent(data, osichatadminroomSocket); const widgetLeftSide = document.getElementById('widgetLeftSide');
break; widgetLeftSide.classList.remove('overflow-hidden');
case 'typing': widgetLeftSide.classList.add('overflow-y-auto');
if(!typingDiv && data.user != userId){ }
messagesDiv.insertAdjacentHTML('beforeend', data.html);
} function appendTextAreaScript(conversationContainer) {
break; const textareaScript = document.createElement('script');
case 'stopped_typing': textareaScript.type = 'text/javascript';
textareaScript.src = `${protocol}://${admin_chat_domain}/static/js/osichat-admin/textarea.js`;
conversationContainer.appendChild(textareaScript);
}
function scrollBottom() {
const conversationContainer = document.getElementById('messages_container');
if (conversationContainer) {
conversationContainer.scrollTo({
top: conversationContainer.scrollHeight,
behavior: 'smooth'
});
}
}
function openConversation(sessionId, chatId) {
if (osichatadminroomSocket) {
osichatadminroomSocket.close();
}
osichatadminroomSocket = new WebSocket(`${admin_chat_ws_scheme}://${admin_chat_domain}/ws/osichat-admin/${sessionId}/${chatId}/`);
osichatadminroomSocket.onopen = function () {
scrollBottom();
hideLoader();
markCurrentChatRead(chatId)
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) {
let chatDiv = document.getElementById('widgetRightSide');
chatDiv.innerHTML = data.html;
appendTextAreaScript(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);
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 (typingDiv) { if (typingDiv) {
typingDiv.remove(); typingDiv.remove();
} }
break; }
case 'send_message': break;
osichatadminroomSocket.send(JSON.stringify({ 'event_type': 'update_read_messages', 'user_id': userId, 'chat_state': 'open' })); default:
messagesDiv.insertAdjacentHTML('beforeend', data.html); console.log('Unknown event type:', data.event_type);
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) { osichatadminroomSocket.onclose = function () {
typingDiv.remove(); console.log('WebSocket connection closed');
} if (currentChatId === chatId) {
break; setTimeout(() => {
} console.log('Attempting to reconnect to WebSocket...');
break; openConversation(sessionId, chatId);
default: }, 2000);
console.log('Unknown event type:', data.event_type); }
} };
};
osichatadminroomSocket.onerror = function (error) {
osichatadminroomSocket.onclose = function () { console.error('WebSocket error:', error);
console.log('WebSocket connection to osichat closed'); showLoader();
}; };
osichatadminroomSocket.onerror = function (error) {
console.error('WebSocket error:', error);
};
} }
})();
window.addEventListener('offline', () => {
showLoader();
if (osichatadminroomSocket) {
osichatadminroomSocket.close();
}
});
})();

@ -2,8 +2,24 @@ let admin_chat_ws_scheme = window.location.protocol === "https:" ? "wss" : "ws";
let protocol = window.location.protocol === "https:" ? "https" : "http"; let protocol = window.location.protocol === "https:" ? "https" : "http";
let admin_chat_domain = "osina.ositcom.com"; let admin_chat_domain = "osina.ositcom.com";
let userId = document.getElementById('userId').textContent.trim(); let userId = document.getElementById('userId').textContent.trim();
let osichatroomsSocket; let osichatadminroomSocket = null;
let osichatadminroomSocket; let currentChatId = null;
// FUNCTIONS TO SHOW & HIDE THE LOADER
function showLoader() {
const roomsLoader = document.getElementById('roomsLoader');
if (roomsLoader) {
roomsLoader.classList.remove('hidden');
}
}
function hideLoader() {
const roomsLoader = document.getElementById('roomsLoader');
if (roomsLoader) {
roomsLoader.classList.add('hidden');
}
}
function appendInnerConversationScript(div) { function appendInnerConversationScript(div) {
const innerConversationScript = document.createElement('script'); const innerConversationScript = document.createElement('script');
@ -12,11 +28,16 @@ function appendInnerConversationScript(div) {
div.appendChild(innerConversationScript); div.appendChild(innerConversationScript);
} }
function getRooms(){ function getRooms(){
osichatroomsSocket = new WebSocket(`${admin_chat_ws_scheme}://${admin_chat_domain}/ws/osichat/rooms/`); osichatroomsSocket = new WebSocket(`${admin_chat_ws_scheme}://${admin_chat_domain}/ws/osichat/rooms/`);
osichatroomsSocket.onopen = function () { osichatroomsSocket.onopen = function () {
console.log('WebSocket connection to rooms established'); console.log('WebSocket connection to rooms established');
hideLoader();
osichatroomsSocket.send(JSON.stringify({ osichatroomsSocket.send(JSON.stringify({
'event_type': 'set_client_type', 'event_type': 'set_client_type',
'client_type': 'website_admin', 'client_type': 'website_admin',
@ -39,11 +60,34 @@ function getRooms(){
if (chatRoomDiv) { if (chatRoomDiv) {
chatRoomDiv.remove(); // Remove the existing chat room div chatRoomDiv.remove(); // Remove the existing chat room div
} }
// Insert the new chat room HTML at the top of the container // Insert the new chat room HTML at the top of the container
const newChatRoomDiv = document.createElement('div'); const newChatRoomDiv = document.createElement('div');
newChatRoomDiv.innerHTML = data.html; newChatRoomDiv.innerHTML = data.html;
roomsContainer.insertAdjacentElement('afterbegin', newChatRoomDiv.firstElementChild); roomsContainer.insertAdjacentElement('afterbegin', newChatRoomDiv.firstElementChild);
if (parseInt(currentChatId) === parseInt(data.chatroom_id)) {
const unreadIndicator = roomsContainer.querySelector(`.unread[data-roomid='${data.chatroom_id}']`);
if (unreadIndicator) {
unreadIndicator.classList.add('hidden');
}
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();
}
} else {
const unreadIndicator = roomsContainer.querySelector(`.unread[data-roomid='${data.chatroom_id}']`);
if (unreadIndicator) {
unreadIndicator.classList.remove('hidden');
}
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();
}
}
appendInnerConversationScript(leftDiv); appendInnerConversationScript(leftDiv);
break; break;
@ -53,12 +97,25 @@ function getRooms(){
}; };
osichatroomsSocket.onclose = function () { osichatroomsSocket.onclose = function () {
console.log('WebSocket connection to rooms closed'); showLoader();
setTimeout(() => {
console.log('Attempting to reconnect to WebSocket...');
getRooms();
}, 2000);
}; };
osichatroomsSocket.onerror = function (error) { osichatroomsSocket.onerror = function (error) {
showLoader();
console.error('WebSocket error:', error); console.error('WebSocket error:', error);
}; };
window.addEventListener('offline', () => {
showLoader();
if (osichatroomsSocket) {
osichatroomsSocket.close();
}
});
} }
getRooms(); getRooms();

@ -1,12 +1,14 @@
(function() { (function() {
// FUNCTION TO ADJUST TEXTAREA HEIGHT AND SUBMIT BUTTON VISIBILITY // FUNCTION TO ADJUST TEXTAREA HEIGHT AND SUBMIT BUTTON VISIBILITY
function adjustTextAreaAndButton(textarea, submitButton) { function adjustTextAreaAndButton(textarea, submitButton) {
// Adjust the height of the textarea
if (textarea.value.trim() === '') { if (textarea.value.trim() === '') {
textarea.style.height = '50px'; textarea.style.height = '50px';
} else { } else {
textarea.style.height = textarea.scrollHeight + 'px'; textarea.style.height = textarea.scrollHeight + 'px';
} }
// Display and hide the submit button
if (textarea.value.trim() === '') { if (textarea.value.trim() === '') {
submitButton.classList.add('hidden'); submitButton.classList.add('hidden');
} else { } else {
@ -14,10 +16,21 @@
} }
} }
function scrollBottom() {
const conversationContainer = document.getElementById('messages_container');
if (conversationContainer) {
conversationContainer.scrollTo({
top: conversationContainer.scrollHeight,
behavior: 'smooth'
});
}
}
// INITIALIZE ELEMENTS // INITIALIZE ELEMENTS
const form = document.querySelector('#sendMessage'); const form = document.querySelector('#sendMessage');
const textarea = document.querySelector('#dynamicTextarea'); const textarea = document.querySelector('#dynamicTextarea');
const conversationContainer = document.getElementById('conversation');
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;
@ -54,8 +67,18 @@
submitButton.classList.add('hidden'); submitButton.classList.add('hidden');
setTimeout(() => { setTimeout(() => {
conversationContainer.scrollTop = conversationContainer.scrollHeight; scrollBottom();
}, 100); }, 100);
}); });
textarea.addEventListener('keydown', (event) => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
if (!submitButton.classList.contains('hidden')) {
submitButton.click();
}
scrollBottom();
}
});
})(); })();

@ -18,15 +18,19 @@
function scrollBottom() { function scrollBottom() {
const conversationContainer = document.getElementById('conversation'); const conversationContainer = document.getElementById('conversation');
conversationContainer.scrollTop = conversationContainer.scrollHeight;
if (conversationContainer) {
conversationContainer.scrollTo({
top: conversationContainer.scrollHeight,
behavior: 'smooth'
});
}
} }
// INITIALIZE ELEMENTS // INITIALIZE ELEMENTS
const form = document.querySelector('#sendMessage'); const form = document.querySelector('#sendMessage');
const textarea = document.querySelector('#dynamicTextarea'); const textarea = document.querySelector('#dynamicTextarea');
const conversationContainer = document.getElementById('conversation');
const submitButton = document.getElementById('submitMessageButton'); const submitButton = document.getElementById('submitMessageButton');
let typingTimeout; let typingTimeout;
let isTyping = false; let isTyping = false;

Loading…
Cancel
Save