Skip to content

Commit

Permalink
[FEAT] bots table template (#122)
Browse files Browse the repository at this point in the history
  • Loading branch information
NeonKirill authored Jan 4, 2025
1 parent fd5cdc1 commit 8361d59
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 40 deletions.
15 changes: 15 additions & 0 deletions chat_client/static/js/base_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
1 change: 0 additions & 1 deletion chat_client/static/js/builder_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
33 changes: 7 additions & 26 deletions chat_client/static/js/chat_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
1 change: 0 additions & 1 deletion chat_client/static/js/message_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
11 changes: 8 additions & 3 deletions chat_client/static/js/sio.js
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
});

Expand Down
128 changes: 128 additions & 0 deletions chat_client/static/js/submind_utils.js
Original file line number Diff line number Diff line change
@@ -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 = `
<td>${submind.service_name}</td>
<td class="text-center">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="toggle-${cid}-${submind.service_name}" ${submind.is_active === true ? 'checked' : ''}>
<label class="custom-control-label" for="toggle-${cid}-${submind.service_name}"></label>
</div>
</td>
`;

const checkbox = row.querySelector(`#toggle-${cid}-${submind.service_name}`);
checkbox.addEventListener('change', () => {
currentState[index].is_active = checkbox.checked;
updateButtonVisibility();
});
entriesContainer.appendChild(row);
});

cancelButton.onclick = () => {
currentState = structuredClone(initialSubmindsState);
currentState.forEach((submind, index) => {
const checkbox = document.getElementById(`toggle-${cid}-${submind.service_name}`);
checkbox.checked = (submind.is_active)? "checked" : '';
});
updateButtonVisibility();
};

submitButton.onclick = () => {
const modifiedSubminds = currentState.filter((current, index) => {
return current.is_active !== initialSubmindsState[index].is_active;
});

let subminds_to_remove = modifiedSubminds.filter(submind => !submind.is_active).map(submind => submind.service_name);
let subminds_to_add = modifiedSubminds.filter(submind => submind.is_active).map(submind => submind.service_name);

if (subminds_to_add.length !== 0 || subminds_to_remove.length !== 0){
socket.emit('broadcast', {
msg_type: "update_participating_subminds",
"cid": cid,
"subminds_to_invite": subminds_to_add,
"subminds_to_kick": subminds_to_remove,
});
}

const dropdownToggle = document.getElementById(`dropdownToggle-${cid}`);
if (dropdownToggle) dropdownToggle.click();

buttonsContainer.style.display = 'none';
};
}


function parseSubmindsState(data){
submindsState = data;

const cids = Object.keys(submindsState["subminds_per_cid"])
if (cids.length === 0){
setAllCountersToZero();
} else {
for (const cid of cids){
refreshSubmindsCount(cid);
}
}
}
1 change: 1 addition & 0 deletions chat_client/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
'date_utils.js',
'file_utils.js',
'language_utils.js',
'submind_utils.js',
'user_settings.js'
] %}
<script type="text/javascript" src="{{ url_for('js', path=js_module) }}"></script>
Expand Down
13 changes: 10 additions & 3 deletions chat_client/templates/components/conversation_skins/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@
{% block skin_body %}
<div class="card non-selectable" id="{cid}">
<div class="card-header"><span data-toggle="tooltip" title="{conversation_name}">{conversation_name_shrunk}</span>
<span class="ml-3" id="participants-list-{cid}" data-toggle="tooltip" title='Contributors of "{conversation_name}"'>
<i class="fa-solid fa-user" aria-hidden="true"></i> <span id="participants-count-{cid}">0</span>
</span>
<div class="dropdown ml-3" id="participants-list-{cid}" data-toggle="tooltip" title='Contributors of "{conversation_name}"' style="display: inline!important;">
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fa-solid fa-user" aria-hidden="true"></i> <span id="participants-count-{cid}">0</span>
</button>
<div class="dropdown-menu dropdown-menu-right text-right" id="bot-list-{cid}" onclick="renderActiveSubminds({cid})">
<a class="dropdown-item lang-dropdown-header" disabled>Select Bots</a>
<div class="dropdown-divider"></div>
<table class="table table-sm" id="{cid}-subminds-state-table"></table>
</div>
</div>
<div class="dropdown language ml-2" data-toggle="tooltip" title="Language you Write" style="display: inline!important;">
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" id="language-selected-{cid}-outcoming">
English <span class="flag-icon flag-icon-us" data-lang="en"></span>
Expand Down
30 changes: 27 additions & 3 deletions chat_client/templates/components/conversation_skins/prompts.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,33 @@
{% block skin_body %}
<div class="card non-selectable" id="{cid}">
<div class="card-header"><span data-toggle="tooltip" title="{conversation_name} (table mode)">{conversation_name_shrunk}</span>
<span class="ml-3" id="participants-list-{cid}" data-toggle="tooltip" title='Contributors of "{conversation_name}"'>
<i class="fa-solid fa-user" aria-hidden="true"></i> <span id="participants-count-{cid}">0</span>
</span>
<div class="dropdown ml-3" id="participants-list-{cid}" data-toggle="tooltip" title='Subminds of "{conversation_name}"' style="display: inline!important;" >
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownToggle-{cid}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" onclick="renderActiveSubminds('{cid}')">
<i class="fa-solid fa-robot" aria-hidden="true"></i> <span id="participants-count-{cid}">0</span>
</button>
<div class="dropdown-menu dropdown-menu-right text-right" id="bot-list-{cid}">
<div class="d-flex justify-content-center align-items-center" style="height: 100px;" id="{cid}-subminds-state-loading">
<div class="spinner-border spinner-border-sm text-info" role="status"></div>
<span class="ml-2">Loading...</span>
</div>
<!-- Table displaying subminds -->
<table class="table table-sm" id="{cid}-subminds-state-table" style="display: none;">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col" class="text-center">Connected</th>
</tr>
</thead>
<tbody id="{cid}-subminds-state-entries">
<!-- Dynamic rows will be added here -->
</tbody>
</table>
<div id="{cid}-subminds-buttons" style="display: none; text-align: center;">
<button class="btn btn-danger btn-sm" id="{cid}-reset-button">Cancel</button>
<button class="btn btn-success btn-sm" id="{cid}-submit-button">Submit</button>
</div>
</div>
</div>
<div class="dropdown language ml-2" data-toggle="tooltip" title="Language you Write" style="display: inline!important;">
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" id="language-selected-{cid}-outcoming">
<span class="flag-icon flag-icon-us" data-lang="en"></span>
Expand Down
20 changes: 17 additions & 3 deletions services/klatchat_observer/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

import requests
import socketio
from pika.exchange_type import ExchangeType

from socketio.exceptions import SocketIOError
from enum import Enum
Expand Down Expand Up @@ -338,6 +339,10 @@ def register_sio_handlers(self):
"revoke_submind_ban_from_conversation",
handler=self.request_revoke_submind_ban_from_conversation,
)
self._sio.on(
"update_participating_subminds",
handler=self.request_update_participating_subminds,
)
self._sio.on("auth_expired", handler=self._handle_auth_expired)

def connect_sio(self):
Expand Down Expand Up @@ -486,12 +491,12 @@ def _handle_neon_recipient(self, recipient_data: dict, msg_data: dict):
)

def _handle_chatbot_recipient(self, recipient_data: dict, msg_data: dict):
LOG.info(f"Emitting message to Chatbot Controller: {recipient_data}")
queue = "external_shout"
LOG.debug(f"Emitting message to Chatbot Controller: {recipient_data}")
queue = "klat_shout"
if requested_participants := recipient_data.get("context", {}).get(
"requested_participants"
):
msg_data["requested_participants"] = json.dumps(requested_participants)
msg_data["requested_participants"] = requested_participants
self.send_message(
request_data=msg_data,
vhost=self.get_vhost("chatbots"),
Expand Down Expand Up @@ -931,6 +936,15 @@ def request_revoke_submind_ban_from_conversation(self, data: dict):
expiration=3000,
)

def request_update_participating_subminds(self, data: dict):
LOG.info(f"Updating participating subminds: {data}")
self.send_message(
request_data=data,
vhost=self.get_vhost("chatbots"),
exchange="update_participating_subminds",
exchange_type=ExchangeType.fanout.value,
)

def _sio_emit(self, event: str, data: dict):
"""
Emit events to the Socket.IO server, ensuring reliability.
Expand Down

0 comments on commit 8361d59

Please sign in to comment.