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

[FEAT] bots table template #122

Merged
merged 19 commits into from
Jan 4, 2025
Merged
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
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
Loading
Loading