diff --git a/chat_client/static/js/base_utils.js b/chat_client/static/js/base_utils.js index 6ba06307..e902f587 100644 --- a/chat_client/static/js/base_utils.js +++ b/chat_client/static/js/base_utils.js @@ -178,6 +178,21 @@ function deleteElement(elem){ if (elem && elem?.parentElement) return elem.parentElement.removeChild(elem); } +/** + * Generic checker for value emptiness + * @param value - provided data to check + */ +function isEmpty(value){ + return ( + // null or undefined + value == null || + // has length and it's zero + (value.hasOwnProperty('length') && value.length === 0) || + // is an Object and has no keys + (value.constructor === Object && Object.keys(value).length === 0) + ); +} + const MIMES = [ ["xml","application/xml"], ["bin","application/vnd.ms-excel.sheet.binary.macroEnabled.main"], diff --git a/chat_client/static/js/builder_utils.js b/chat_client/static/js/builder_utils.js index af25f3b8..d182a676 100644 --- a/chat_client/static/js/builder_utils.js +++ b/chat_client/static/js/builder_utils.js @@ -313,7 +313,6 @@ async function buildConversationHTML(conversationData = {}, skin = CONVERSATION_ message['cid'] = cid; chatFlowHTML += await messageHTMLFromData(message, skin); // if (skin === CONVERSATION_SKINS.BASE) { - addConversationParticipant(cid, message['user_nickname']); // } } }else{ diff --git a/chat_client/static/js/chat_utils.js b/chat_client/static/js/chat_utils.js index 1d691ca0..ecab158d 100644 --- a/chat_client/static/js/chat_utils.js +++ b/chat_client/static/js/chat_utils.js @@ -20,42 +20,24 @@ let conversationState = {}; const clearStateCache = (cid) => { delete conversationState[cid]; } - /** - * Gets participants data listed under conversation id - * @param cid - target conversation id - * @return {*} participants data object + * Sets all participants counters to zero */ -const getParticipants = (cid) => { - return setDefault(setDefault(conversationState, cid, {}), 'participants', {}); +const setAllCountersToZero = () => { + const countNodes = document.querySelectorAll('[id^="participants-count-"]'); + countNodes.forEach(node => node.innerText = 0); } + /** * Sets participants count for conversation view * @param cid - desired conversation id */ -const displayParticipantsCount = (cid) => { +const refreshSubmindsCount = (cid) => { const participantsCountNode = document.getElementById(`participants-count-${cid}`); - participantsCountNode.innerText = Object.keys(getParticipants(cid)).length; + if (participantsCountNode && !isEmpty(submindsState)) participantsCountNode.innerText = submindsState["subminds_per_cid"][cid].length; } -/** - * Adds new conversation participant - * @param cid - target conversation id - * @param nickname - nickname to add - * @param updateCount - to update participants count - */ -const addConversationParticipant = (cid, nickname, updateCount = false) => { - const conversationParticipants = getParticipants(cid); - if(!conversationParticipants.hasOwnProperty(nickname)){ - conversationParticipants[nickname] = {'num_messages': 1}; - }else{ - conversationParticipants[nickname]['num_messages']++; - } - if(updateCount){ - displayParticipantsCount(cid); - } -} /** * Saves attached files to the server @@ -306,7 +288,6 @@ async function buildConversation(conversationData, skin, remember=true,conversat } }); await addRecorder(conversationData); - displayParticipantsCount(conversationData['_id']); await initLanguageSelectors(conversationData['_id']); if (skin === CONVERSATION_SKINS.BASE) { diff --git a/chat_client/static/js/message_utils.js b/chat_client/static/js/message_utils.js index 94aea0f4..01d15937 100644 --- a/chat_client/static/js/message_utils.js +++ b/chat_client/static/js/message_utils.js @@ -68,7 +68,6 @@ async function addNewMessage(cid, userID=null, messageID=null, messageText, time resolveMessageAttachments(cid, messageID, attachments); resolveUserReply(messageID, repliedMessageID); addProfileDisplay(userID, cid, messageID, 'plain'); - addConversationParticipant(cid, userData['nickname'], true); scrollOnNewMessage(messageList); return messageID; } diff --git a/chat_client/static/js/sio.js b/chat_client/static/js/sio.js index 8d2059a0..b12374c3 100644 --- a/chat_client/static/js/sio.js +++ b/chat_client/static/js/sio.js @@ -89,17 +89,22 @@ function initSIO(){ }); socket.on('translation_response', async (data) => { - console.log('translation_response: ', data) + console.debug('translation_response: ', data) await applyTranslations(data); }); + socket.on('subminds_state', async (data) => { + console.debug('subminds_state: ', data) + parseSubmindsState(data); + }); + socket.on('incoming_tts', (data)=> { - console.log('received incoming stt audio'); + console.debug('received incoming stt audio'); playTTS(data['cid'], data['lang'], data['audio_data']); }); socket.on('incoming_stt', (data)=>{ - console.log('received incoming stt response'); + console.debug('received incoming stt response'); showSTT(data['message_id'], data['lang'], data['message_text']); }); diff --git a/chat_client/static/js/submind_utils.js b/chat_client/static/js/submind_utils.js new file mode 100644 index 00000000..4380c32c --- /dev/null +++ b/chat_client/static/js/submind_utils.js @@ -0,0 +1,128 @@ +let submindsState; + +function renderActiveSubminds(cid) { + if (!submindsState) { + console.log(`Subminds for CID ${cid} not yet loaded.`); + return; + } + const loadingSpinner = document.getElementById(`${cid}-subminds-state-loading`); + if (loadingSpinner) { + loadingSpinner.classList.remove('d-flex'); + loadingSpinner.style.display = 'none'; + } + + const dropdownMenu = document.getElementById(`bot-list-${cid}`); + dropdownMenu.addEventListener('click', (event) => { + event.stopPropagation(); + }); + + const table = document.getElementById(`${cid}-subminds-state-table`); + const entriesContainer = document.getElementById(`${cid}-subminds-state-entries`); + const buttonsContainer = document.getElementById(`${cid}-subminds-buttons`); + buttonsContainer.style.display = 'none'; + const cancelButton = document.getElementById(`${cid}-reset-button`); + const submitButton = document.getElementById(`${cid}-submit-button`); + + const { subminds_per_cid: submindsPerCID, connected_subminds: connectedSubminds } = submindsState; + + const activeSubminds = submindsPerCID?.[cid]?.filter(submind => submind.status === 'active') || []; + const activeSubmindServices = new Set(activeSubminds.map(submind => submind.submind_id.slice(0, submind.submind_id.lastIndexOf('-')))) + + const banned_subminds = submindsPerCID?.[cid]?.filter(submind => submind.status === 'banned') || []; + const bannedSubmindIds = new Set(banned_subminds.map(submind => submind.submind_id)); + + const initialSubmindsState = []; + const processedServiceNames = []; + for (let [submindID, submindData] of Object.entries(connectedSubminds || {})){ + const serviceName = submindData.service_name; + const botType = submindData.bot_type; + if (botType === "submind" && !bannedSubmindIds.has(submindID) && !processedServiceNames.includes(serviceName)){ + processedServiceNames.push(serviceName) + initialSubmindsState.push( + { + service_name: serviceName, + is_active: activeSubmindServices.has(serviceName) + } + ) + } + } + initialSubmindsState.sort((a, b) => { + return b.is_active - a.is_active; + }) + + let currentState = structuredClone(initialSubmindsState); + + const updateButtonVisibility = () => { + const hasChanges = initialSubmindsState.some((submind, index) => submind.is_active !== currentState[index].is_active); + buttonsContainer.style.display = hasChanges ? 'block' : 'none'; + }; + + table.style.display = ''; + entriesContainer.innerHTML = ''; + + initialSubmindsState.forEach((submind, index) => { + const row = document.createElement('tr'); + row.innerHTML = ` +