Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display proctored conversation in table mode by default #100

Draft
wants to merge 2 commits into
base: alpha
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 0 additions & 38 deletions .github/workflows/build_nano_assets.yml

This file was deleted.

3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ sandbox.py
# Generated Migration Files
passed_migrations

# Nano generated files
*Nano

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
42 changes: 23 additions & 19 deletions chat_client/static/js/chat_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ const sendMessage = async (inputElem, cid, repliedMessageId=null, isAudio='0', i

/**
* Builds new conversation HTML from provided data and attaches it to the list of displayed conversations
* @param conversationData: JS Object containing conversation data of type:
* @param conversationData - JS Object containing conversation data of type:
* {
* '_id': 'id of conversation',
* 'conversation_name': 'title of the conversation',
Expand All @@ -243,21 +243,20 @@ const sendMessage = async (inputElem, cid, repliedMessageId=null, isAudio='0', i
* 'created_on': 'creation time of the message'
* }, ... (num of user messages returned)]
* }
* @param conversationParentID: ID of conversation parent
* @param remember: to store this conversation into localStorage (defaults to true)
* @param skin: Conversation skin to build
*
* @param skin - Conversation skin to build
* @param remember - to store this conversation into localStorage (defaults to true)
* @param conversationParentID - ID of conversation parent
* @return id of the built conversation
*/
async function buildConversation(conversationData={}, skin = CONVERSATION_SKINS.BASE, remember=true,conversationParentID = 'conversationsBody'){
async function buildConversation(conversationData, skin, remember=true,conversationParentID = 'conversationsBody'){
const idField = '_id';
const cid = conversationData[idField];
if (!cid){
console.error(`Failed to extract id field="${idField}" from conversation data - ${conversationData}`);
return -1;
}
if(remember){
await addNewCID(cid, skin);
await addNewConversationToAlignmentStore(cid, skin);
}
const newConversationHTML = await buildConversationHTML(conversationData, skin);
const conversationsBody = document.getElementById(conversationParentID);
Expand Down Expand Up @@ -369,10 +368,13 @@ async function buildConversation(conversationData={}, skin = CONVERSATION_SKINS.
* @param alertParent: parent of error alert (optional)
* @returns {Promise<{}>} promise resolving conversation data returned
*/
async function getConversationDataByInput(input="", skin=CONVERSATION_SKINS.BASE, oldestMessageTS=null, maxResults=20, alertParent=null){
async function getConversationDataByInput(input="", forceSkin=null, oldestMessageTS=null, maxResults=20, alertParent=null){
let conversationData = {};
if(input){
let query_url = `chat_api/search/${input.toString()}?limit_chat_history=${maxResults}&skin=${skin}`;
let query_url = `chat_api/search/${input.toString()}?limit_chat_history=${maxResults}`;
if (forceSkin){
query_url += `&skin=${forceSkin}`
}
if(oldestMessageTS){
query_url += `&creation_time_from=${oldestMessageTS}`;
}
Expand Down Expand Up @@ -434,7 +436,7 @@ const getMinifySettingsTable = () => {
* @param cid: conversation id to add
* @param skin: conversation skin to add
*/
async function addNewCID(cid, skin){
async function addNewConversationToAlignmentStore(cid, skin){
return await getChatAlignmentTable().put({'cid': cid, 'skin': skin, 'added_on': getCurrentTimestamp()}, [cid]);
}

Expand Down Expand Up @@ -504,15 +506,16 @@ const chatAlignmentRestoredEvent = new CustomEvent("chatAlignmentRestored", { "d
async function restoreChatAlignment(keyName=conversationAlignmentKey){
let cachedItems = await retrieveItemsLayout();
if (cachedItems.length === 0){
cachedItems = [{'cid': '1', 'added_on': getCurrentTimestamp(), 'skin': CONVERSATION_SKINS.BASE}]
await addNewCID('1', CONVERSATION_SKINS.BASE);
cachedItems = [{'cid': '1', 'added_on': getCurrentTimestamp()}]
}
for (const item of cachedItems) {
await getConversationDataByInput(item.cid, item.skin).then(async conversationData=>{
await getConversationDataByInput(item.cid).then(async conversationData=>{
if(conversationData && Object.keys(conversationData).length > 0) {
await buildConversation(conversationData, item.skin, false);
await buildConversation(conversationData, conversationData.skin, true);
}else{
if (item.cid !== '1') {
if (item.cid === '1'){
displayAlert(document.getElementById('conversationsBody'), 'Default Conversation is missing!', 'danger', 'noRestoreConversationAlert');
}else{
displayAlert(document.getElementById('conversationsBody'), 'No matching conversation found', 'danger', 'noRestoreConversationAlert', {'type': alertBehaviors.AUTO_EXPIRE});
}
await removeConversation(item.cid);
Expand Down Expand Up @@ -652,10 +655,10 @@ function setChatState(cid, state='active', state_msg = ''){
* @param alertParentID: id of the element to display alert in
* @param conversationParentID: parent Node ID of the conversation
*/
async function displayConversation(searchStr, skin=CONVERSATION_SKINS.BASE, alertParentID = null, conversationParentID='conversationsBody'){
async function displayConversation(searchStr, forceSkin=null, alertParentID = null, conversationParentID='conversationsBody'){
if (searchStr !== "") {
const alertParent = document.getElementById(alertParentID);
await getConversationDataByInput(searchStr, skin, null, 20, alertParent).then(async conversationData => {
await getConversationDataByInput(searchStr, forceSkin, null, 20, alertParent).then(async conversationData => {
let responseOk = false;
if (!conversationData || Object.keys(conversationData).length === 0){
displayAlert(
Expand All @@ -669,6 +672,7 @@ async function displayConversation(searchStr, skin=CONVERSATION_SKINS.BASE, aler
else if (isDisplayed(conversationData['_id'])) {
displayAlert(alertParent, 'Chat is already displayed', 'danger');
} else {
const skin = conversationData.skin
await buildConversation(conversationData, skin, true, conversationParentID);
if (skin === CONVERSATION_SKINS.BASE) {
for (const inputType of ['incoming', 'outcoming']) {
Expand Down Expand Up @@ -707,7 +711,7 @@ async function createNewConversation(conversationName, isPrivate=false, conversa
const responseJson = await response.json();
let responseOk = false;
if (response.ok) {
await buildConversation(responseJson);
await buildConversation(responseJson, CONVERSATION_SKINS.BASE);
responseOk = true;
} else {
displayAlert('newConversationModalBody',
Expand All @@ -730,7 +734,7 @@ document.addEventListener('DOMContentLoaded', (e)=>{
});
addBySearch.addEventListener('click', async (e) => {
e.preventDefault();
displayConversation(conversationSearchInput.value, CONVERSATION_SKINS.BASE, 'importConversationModalBody').then(responseOk=> {
displayConversation(conversationSearchInput.value, null, 'importConversationModalBody').then(responseOk=> {
conversationSearchInput.value = "";
if(responseOk) {
importConversationModal.modal('hide');
Expand Down
14 changes: 6 additions & 8 deletions chat_client/static/js/message_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ function getFirstMessageFromCID(firstChild){
* @param cid: target conversation id
* @param skin: target conversation skin
*/
async function addOldMessages(cid, skin=CONVERSATION_SKINS.BASE) {
async function addOldMessages(cid, skin) {
const messageContainer = getMessageListContainer( cid );
if (messageContainer.children.length > 0) {
for (let i = 0; i < messageContainer.children.length; i++) {
Expand All @@ -183,7 +183,7 @@ async function addOldMessages(cid, skin=CONVERSATION_SKINS.BASE) {
console.debug( `!!message_id=${message["message_id"]} is already displayed` )
}
}
await initMessages( conversationData, skin );
await initMessages( conversationData );
}
} ).then( _ => {
firstMessageItem.scrollIntoView( {behavior: "smooth"} );
Expand Down Expand Up @@ -234,9 +234,8 @@ const getUserMessages = (conversationData, forceType='plain') => {
/**
* Initializes listener for loading old message on scrolling conversation box
* @param conversationData: Conversation Data object to fetch
* @param skin: conversation skin to apply
*/
function initLoadOldMessages(conversationData, skin) {
function initLoadOldMessages(conversationData) {
const cid = conversationData['_id'];
const messageList = getMessageListContainer(cid);
const messageListParent = messageList.parentElement;
Expand All @@ -248,7 +247,7 @@ function initLoadOldMessages(conversationData, skin) {
!conversationState[cid]['all_messages_displayed'] &&
conversationState[cid]['scrollY'] === 0) {
setChatState(cid, 'updating', 'Loading messages...')
await addOldMessages(cid, skin);
await addOldMessages(cid, conversationData.skin);
for(const inputType of ['incoming', 'outcoming']){
await requestTranslation(cid, null, null, inputType);
}
Expand Down Expand Up @@ -335,14 +334,13 @@ async function initPagination(conversationData) {
* 'created_on': 'creation time of the message'
* }, ... (num of user messages returned)]
* }
* @param skin - target conversation skin to consider
*/
async function initMessages(conversationData, skin = CONVERSATION_SKINS.BASE){
async function initMessages(conversationData){
initProfileDisplay(conversationData);
attachReplies(conversationData);
addAttachments(conversationData);
addCommunicationChannelTransformCallback(conversationData);
initLoadOldMessages(conversationData, skin);
initLoadOldMessages(conversationData);
await initPagination(conversationData);
}

Expand Down
2 changes: 1 addition & 1 deletion chat_client/static/nano_builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class NanoBuilder {
const chatData = options['CHAT_DATA'];
const nanoChatsLoaded = new CustomEvent('nanoChatsLoaded')
Array.from(chatData).forEach(async chat => {
await displayConversation(chat['CID'], CONVERSATION_SKINS.BASE, chat['PARENT_ID'], chat['PARENT_ID'])
await displayConversation(chat['CID'], null, chat['PARENT_ID'], chat['PARENT_ID'])
});
console.log('all chats loaded')
document.dispatchEvent(nanoChatsLoaded);
Expand Down
17 changes: 16 additions & 1 deletion chat_server/blueprints/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@
from fastapi import APIRouter, Form, Depends
from fastapi.responses import JSONResponse

from chat_server.constants.conversations import ConversationSkins
from chat_server.server_utils.api_dependencies.validators.users import (
get_authorized_user,
)
from chat_server.server_utils.cache_utils import SubmindsState
from chat_server.server_utils.conversation_utils import build_message_json
from chat_server.server_utils.api_dependencies.extractors import CurrentUserData
from chat_server.server_utils.api_dependencies.models import GetConversationModel
Expand Down Expand Up @@ -125,9 +127,21 @@ async def get_matching_conversation(
else:
query_filter = None

if not model.skin:
is_proctored_conversation = SubmindsState.is_proctored_conversation(
cid=conversation_data["_id"]
)
skin = (
ConversationSkins.PROMPTS
if is_proctored_conversation
else ConversationSkins.BASE
)
else:
skin = model.skin

message_data = (
fetch_message_data(
skin=model.skin,
skin=skin,
conversation_data=conversation_data,
limit=model.limit_chat_history,
creation_time_filter=query_filter,
Expand All @@ -138,6 +152,7 @@ async def get_matching_conversation(
build_message_json(raw_message=message_data[i], skin=model.skin)
for i in range(len(message_data))
]
conversation_data["skin"] = skin

return conversation_data

Expand Down
4 changes: 1 addition & 3 deletions chat_server/server_utils/api_dependencies/models/chats.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,4 @@ class GetConversationModel(BaseModel):
search_str: str = Field(Path(), examples=["1"])
limit_chat_history: int | None = Field(Query(default=100), examples=[100])
creation_time_from: str | None = Field(Query(default=None), examples=[int(time())])
skin: str = Field(
Query(default=ConversationSkins.BASE), examples=[ConversationSkins.BASE]
)
skin: str | None = Field(Query(default=None), examples=[ConversationSkins.BASE])
35 changes: 34 additions & 1 deletion chat_server/server_utils/cache_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from typing import Type
from cachetools import Cache


# TODO: consider storing cached values in Redis (Kirill)


class CacheFactory:
Expand All @@ -35,7 +39,7 @@ class CacheFactory:
__active_caches = {}

@classmethod
def get(cls, name: str, cache_type: Type = None, **kwargs):
def get(cls, name: str, cache_type: Type[Cache] = None, **kwargs) -> Cache:
"""
Get cache instance based on name and type

Expand All @@ -50,3 +54,32 @@ def get(cls, name: str, cache_type: Type = None, **kwargs):
else:
raise KeyError(f"Missing cache instance under {name}")
return cls.__active_caches[name]


class SubmindsState:
items = {}

@classmethod
def update(cls, data: dict):
cls.items["proctored_conversations"] = cls._get_proctored_conversations(data)

@classmethod
def _get_proctored_conversations(cls, data):
proctored_conversations = []
for cid, subminds in data.get("subminds_per_cid", {}).items():
for submind in subminds:
if (
cls._is_proctor(submind["submind_id"])
and submind["status"] != "banned"
):
proctored_conversations.append(cid)
break
return proctored_conversations

@classmethod
def _is_proctor(cls, submind_id: str) -> bool:
return submind_id.startswith("proctor")

@classmethod
def is_proctored_conversation(cls, cid: str) -> bool:
return cid in cls.items.get("proctored_conversations", [])
19 changes: 8 additions & 11 deletions chat_server/sio/handlers/user_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@
from utils.database_utils.mongo_utils.queries.wrapper import MongoDocumentsAPI
from utils.logging_utils import LOG
from ..server import sio
from ..utils import emit_error, login_required
from ..utils import emit_error
from ...server_config import server_config
from ...server_utils.enums import UserRoles
from ...server_utils.cache_utils import SubmindsState
from ...services.popularity_counter import PopularityCounter


Expand Down Expand Up @@ -176,16 +176,13 @@ async def user_message(sid, data):


@sio.event
@login_required(min_required_role=UserRoles.ADMIN)
async def broadcast(sid, data):
"""Forwards received broadcast message from client"""
msg_type = data.pop("msg_type", None)
msg_receivers = data.pop("to", None)
if msg_type:
async def subminds_state_received(sid, data):
"""Handles received subminds state"""
if data:
SubmindsState.update(data=data)
await sio.emit(
msg_type,
"subminds_state_updated",
data=data,
to=msg_receivers,
)
else:
LOG.error(f'data={data} skipped - no "msg_type" provided')
LOG.error(f"Empty data skipped for subminds_state_received")
7 changes: 4 additions & 3 deletions services/klatchat_observer/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,9 @@ def connect_sio(self):
"""
Method for establishing connection with Socket IO server
"""
self._sio = socketio.Client()
self._sio = socketio.Client(
request_timeout=15, reconnection_delay=0.5, reconnection_delay_max=2
)
self._sio.connect(
url=self.sio_url,
namespaces=["/"],
Expand Down Expand Up @@ -769,8 +771,7 @@ def on_tts_response(self, body: dict):
def on_subminds_state(self, body: dict):
"""Handles receiving subminds state message"""
LOG.debug(f"Received submind state: {body}")
body["msg_type"] = "subminds_state"
self.sio.emit("broadcast", data=body)
self.sio.emit("subminds_state_received", data=body)

@create_mq_callback()
def on_get_configured_personas(self, body: dict):
Expand Down
Loading