let apiKey;
let serpApiKey;
let ws = null;
let taskId = null;
let imageBaseDir = "../images/";
let agentProfileImages = {};
let agentLists = [];
const apiHost = ((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + window.location.pathname.replace('demo.html', '') + "api";
// Helpers
const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay));
window.onload = async () => {
await connect();
}
const getApiKey = document.getElementById('openai-api-key');
const getApiKeySerp = document.getElementById('serp-api-key');
getApiKey.addEventListener('input', () => {
if (apiKey !== '' || apiKey !== undefined) {
apiKey = getApiKey.value;
}
});
getApiKeySerp.addEventListener('input', () => {
if (serpApiKey !== '' || serpApiKey !== undefined) {
serpApiKey = getApiKeySerp.value;
}
});
function applyStyles(element) {
element.style.backgroundColor = '#add8e6a8';
element.style.padding = '12px';
element.style.borderRadius = '1.2rem';
element.style.marginTop = '30px !important';
}
function resetStyles(element) {
element.style.backgroundColor = '';
element.style.padding = '';
element.style.borderRadius = '';
element.style.marginTop = '';
}
function isEmpty(e) {
if (e === undefined || e === null) {
return true;
}
return false;
}
function profileImage(url) {
const profileImage = document.createElement('img');
profileImage.src = url;
profileImage.height = 50;
profileImage.width = 50;
profileImage.className = 'agent-avatar';
return profileImage;
}
function cleanResponseContent(content) {
if (isEmpty(content)) { return; }
// const contentWithoutHeadersOne = content.replace(/#\s+/g, '');
const contentWithoutHeaders = content.replace(/##\s+/g, '');
const cleanedContent = contentWithoutHeaders.replace(/"([^"]*)": ""\s*,?|{\s*}/g, '');
const formattedContent = cleanedContent.replace(/\n/g, '
');
const codeBlockRegex = /```([\s\S]*?)```/g;
const formattedWithCodeBlocks = formattedContent.replace(codeBlockRegex, '
$1
');
const codeSnippetRegex = /`([^`]+)`/g;
const formattedFinal = formattedWithCodeBlocks.replace(codeSnippetRegex, '$1
');
return formattedFinal;
}
const imageFilenames = Array.from({ length: 20 }, (_, index) => `${index + 1}.jpg`);
let currentImageIndex = 0;
// Render agents on left col
function renderAgent(agent) {
const agentName = agent.task_message.role;
if (agentLists.indexOf(agentName) > -1) {
return;
}
const agentsList = document.getElementById('agentsList');
const listItem = document.createElement('li');
const agentNameDiv = document.createElement('p');
agentNameDiv.className = 'px-3 agents mt-3';
agentsList.className = 'mt-4 py-3';
listItem.className = 'list-group-item d-flex align-items-center';
agentNameDiv.textContent = agentName;
let agentProfileImage = '';
if (agentProfileImages.hasOwnProperty(agentName)) {
agentProfileImage = agentProfileImages[agentName];
} else {
agentProfileImage = currentImageIndex < imageFilenames.length
? `${imageBaseDir}${imageFilenames[currentImageIndex]}`
: `${imageBaseDir}default.png`;
currentImageIndex = (currentImageIndex + 1) % imageFilenames.length;
agentProfileImages[agentName] = agentProfileImage;
}
const agentImage = profileImage(agentProfileImage);
listItem.prepend(agentImage);
listItem.appendChild(agentNameDiv);
agentsList.appendChild(listItem);
agentLists.push(agentName);
}
// Render Expert agents on left col
function renderExpertAgent(expertName) {
if (agentLists.indexOf(expertName) > -1) {
return;
}
const agentsList = document.getElementById('agentsList');
const listItem = document.createElement('li');
const agentNameDiv = document.createElement('p');
agentNameDiv.className = 'px-3 agents mt-3';
agentsList.className = 'mt-4 py-3';
listItem.className = 'list-group-item d-flex align-items-center';
agentNameDiv.textContent = expertName;
const agentProfileImage = currentImageIndex < imageFilenames.length
? `${imageBaseDir}${imageFilenames[currentImageIndex]}`
: `${imageBaseDir}default.png`;
currentImageIndex = (currentImageIndex + 1) % imageFilenames.length;
agentProfileImages[expertName] = agentProfileImage;
const agentImage = profileImage(agentProfileImage);
listItem.prepend(agentImage);
listItem.appendChild(agentNameDiv);
agentsList.appendChild(listItem);
agentLists.push(expertName);
}
// Scroll to msg of clicked agent/step
let previousScrolledDiv = null; // track previously scrolled divs
function scrollToMessageDiv(agentRole) {
const chatView = document.getElementById('chatView');
const chatMessages = chatView.getElementsByClassName('chat-agent');
for (const msgDiv of chatMessages) {
if (msgDiv.getAttribute('data-role') === agentRole) {
if (previousScrolledDiv) {
resetStyles(previousScrolledDiv);
}
applyStyles(msgDiv);
msgDiv.scrollIntoView({ behavior: 'smooth' });
previousScrolledDiv = msgDiv;
break;
}
}
}
// Render Lead agents responses
async function renderLeadAgentsResponses(responseData) {
// console.info(responseData);
const chatView = document.getElementById('chatView');
// Get all lead agents
const chatMessages = responseData.filter((msg) => msg.task_message.role === "Question/Task" || msg.task_message.role === "Manager" || msg.task_message.role === "Agents Observer" || msg.task_message.role === "Plan Observer" || msg.task_message.role !== "" || msg.task_message.content !== "");
// Sort chat messages based on timestamp
chatMessages.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
for (const msg of chatMessages) {
const agentDiv = createAgentChatDiv(msg.task_message);
chatView.appendChild(agentDiv);
chatView.scrollTop = chatView.scrollHeight;
// showCallingNextAgentMessage().stop();
}
// showCallingNextAgentMessage();
chatView.scrollTop = chatView.scrollHeight;
}
// render task agents message
async function renderTaskAgentsResponse(responseData) {
const chatView = document.getElementById('chatView');
const namedAgents = responseData.filter((msg) => msg.task_message.role !== "Question/Task" && msg.task_message.role !== "Manager" && msg.task_message.role !== "Agents Observer" && msg.task_message.role !== "Plan Observer" && msg.task_message.role !== "" && msg.task_message.content !== "");
// Render work agents responses
for (const agent of namedAgents) {
const agentChatMessages = responseData.filter((msg) => msg.task_message.role === agent.role);
for (const msg of agentChatMessages) {
const agentDiv = createAgentChatDiv(msg.task_message);
chatView.appendChild(agentDiv);
chatView.scrollTop = chatView.scrollHeight;
}
}
}
// Invite and render work agents
async function inviteTaskAgents(responseData) {
// console.info(responseData);
const chatView = document.getElementById('chatView');
const invitees = responseData.filter((msg) => msg.task_message.role === "Revised Role List");
for (const agent of invitees) {
const inviteMessageContainer = document.createElement('div');
inviteMessageContainer.className = 'text-center invite-message fs';
chatView.appendChild(inviteMessageContainer);
for (const contentItem of agent.task_message.content) {
const inviteMessage = document.createElement('p');
inviteMessage.innerText = `Invited ${contentItem.name} to group chat`;
inviteMessageContainer.appendChild(inviteMessage);
chatView.scrollTop = chatView.scrollHeight;
renderExpertAgent(contentItem.name);
await sleep(1000);
}
}
}
let renderedLeadAgents = {};
async function renderLeadAgentsOnce(responseData) {
// console.info(responseData);
const leadAgents = responseData.filter((msg) => msg.task_message.role === "Question/Task" || msg.task_message.role === "Manager" || msg.task_message.role === "Agents Observer" || msg.task_message.role === "Plan Observer" || msg.task_message.role !== "" || msg.task_message.content !== "");
for (const agent of leadAgents) {
const agentRole = agent.task_message.role;
if (!renderedLeadAgents.hasOwnProperty(agentRole)) {
renderedLeadAgents[agentRole] = true;
renderAgent(agent);
}
}
}
// Agent chat response divs
let renderedAgentMessage = {};
function createAgentChatDiv(msg) {
const agentDiv = document.createElement('div');
agentDiv.className = 'chat-agent d-flex';
agentDiv.setAttribute('data-role', msg.role);
const agentNameDiv = document.createElement('p');
agentNameDiv.className = 'agent-name';
const agentRole = msg.role;
let agentCount = renderedAgentMessage.hasOwnProperty(agentRole) ? renderedAgentMessage[agentRole] + 1 : 1;
renderedAgentMessage[agentRole] = agentCount;
if (agentCount > 1) {
agentNameDiv.textContent = `${agentRole}`;
} else {
agentNameDiv.textContent = agentRole;
}
const responseMessages = document.createElement('p');
responseMessages.className = 'chat-bubble ms-2 px-3 pb-3';
let agentProfileImage = ''
if (agentProfileImages.hasOwnProperty(agentNameDiv.textContent)) {
agentProfileImage = agentProfileImages[agentNameDiv.textContent];
} else {
agentProfileImage = currentImageIndex < imageFilenames.length
? `${imageBaseDir}${imageFilenames[currentImageIndex]}`
: `${imageBaseDir}default.png`;
currentImageIndex = (currentImageIndex + 1) % imageFilenames.length;
agentProfileImages[agentNameDiv.textContent] = agentProfileImage;
}
let messageText = cleanResponseContent(msg.content);
if(msg.role === 'Question/Task') {
messageText = `Task "${messageText}" is submitted.
Waiting for agents to cooperate to complete the task.`;
}
// console.log(messageText);
if (msg.role === 'Question/Task' || msg.role === 'Manager' || msg.role === 'Agents Observer' || msg.role === 'Plan Observer') {
responseMessages.style.backgroundColor = 'rgb(170, 253, 217)';
}
if (!isEmpty(messageText) && messageText.length > 300) {
const truncatedText = messageText.substring(0, 500);
const fullText = messageText;
const showMoreButton = document.createElement('button');
showMoreButton.className = 'read-more mt-2 btn btn-success';
showMoreButton.textContent = 'Show More';
showMoreButton.addEventListener('click', function() {
showFullText(this);
});
responseMessages.innerHTML = `${truncatedText}`;
responseMessages.appendChild(showMoreButton);
responseMessages.prepend(agentNameDiv);
responseMessages.dataset.fullText = fullText;
} else {
responseMessages.innerHTML = messageText;
}
chatView.scrollTop = chatView.scrollHeight;
const agentImage = profileImage(agentProfileImage);
agentDiv.prepend(agentImage);
responseMessages.prepend(agentNameDiv);
agentDiv.appendChild(responseMessages);
return agentDiv;
}
let callingMessageCount = 0; // keep track of calling messages
function showCallingNextAgentMessage() {
const chatView = document.getElementById('chatView');
const callingMessage = document.createElement('p');
callingMessage.className = 'calling-message fs';
callingMessage.textContent = 'Calling next agent';
callingMessage.dataset.messageId = callingMessageCount;
chatView.appendChild(callingMessage);
callingMessageCount++;
const animationFrames = ['Calling', 'Calling next', 'Calling next agent..', 'Calling next agent...'];
let frameIndex = 0;
const updateMessage = () => {
callingMessage.textContent = animationFrames[frameIndex];
frameIndex = (frameIndex + 1) % animationFrames.length;
};
const interval = setInterval(updateMessage, 500);
return {
stop: () => {
clearInterval(interval);
chatView.removeChild(callingMessage);
}
};
}
function showFullText(element) {
const parentMessage = element.parentElement;
const fullText = parentMessage.dataset.fullText;
const fullTextParagraph = document.createElement('p');
const agentNameDiv = parentMessage.querySelector('.agent-name');
fullTextParagraph.innerHTML = fullText;
const foldButton = document.createElement('button');
foldButton.className = 'read-more mt-2 btn btn-success';
foldButton.textContent = 'Show less';
foldButton.addEventListener('click', function() {
hideFullText(this);
});
parentMessage.innerHTML = '';
parentMessage.prepend(agentNameDiv);
parentMessage.appendChild(fullTextParagraph);
parentMessage.appendChild(foldButton);
}
function hideFullText(element) {
const parentMessage = element.parentElement;
const truncatedText = parentMessage.dataset.fullText.substring(0, 500);
const agentNameDiv = parentMessage.querySelector('.agent-name');
const truncatedTextParagraph = document.createElement('p');
truncatedTextParagraph.innerHTML = truncatedText;
const showMoreButton = document.createElement('button');
showMoreButton.className = 'read-more mt-2 btn btn-success';
showMoreButton.textContent = 'Show More';
showMoreButton.addEventListener('click', function() {
showFullText(this);
});
parentMessage.innerHTML = '';
parentMessage.prepend(agentNameDiv);
parentMessage.appendChild(truncatedTextParagraph);
parentMessage.appendChild(showMoreButton);
}
// Render tasks in the right column
async function displayTasks(responseData) {
// console.info(responseData);
const taskView = document.getElementById('taskView');
// tasks and their progress
responseData.forEach((task) => {
if (task.task_message.role === 'Question/Task') {
const taskItem = document.createElement('div');
taskItem.className = 'alert alert-primary fw-bold';
taskItem.textContent = task.task_message.content;
taskView.appendChild(taskItem);
}
});
}
function styleSelectedStep(item) {
const allListItems = document.querySelectorAll('.task-step');
allListItems.forEach((listItem) => {
listItem.style.border = "none";
listItem.style.backgroundColor = "#fff";
});
item.style.backgroundColor = "#add8e6a8";
item.style.borderRadius = "1.3rem";
}
// Render task(s) and progress
let renderedTasks = {};
let taskStepCount = 0;
async function displayTaskSteps(responseData) {
// console.info(responseData);
const taskView = document.getElementById('taskView');
let completedStages = 0;
// Progress bar
const progressBar = document.createElement('div');
progressBar.className = 'progress-bar';
for (let i = 0; i < responseData.length; i++) {
const step = responseData[i];
if (step.task_message.role) {
const agentRole = step.task_message.role;
if (renderedTasks.hasOwnProperty(agentRole)) {
renderedTasks[agentRole]++;
} else {
renderedTasks[agentRole] = 1;
}
const stepItem = document.createElement('div');
stepItem.className = 'task-step';
stepItem.addEventListener('click', () => {
scrollToMessageDiv(step.task_message.role);
styleSelectedStep(stepItem);
});
// Expert details
const expertDetails = document.createElement('div');
expertDetails.className = 'expert-details d-flex';
// Use the taskStepCount to set the step number
const stepNumber = document.createElement('div');
stepNumber.className = 'step-number';
stepNumber.textContent = ++taskStepCount; // Increment and assign
const expertNameContent = renderedTasks[agentRole] > 1
? `${agentRole} ${renderedTasks[agentRole]}`
: agentRole;
expertDetails.innerHTML = `
${expertNameContent}
Pending...
`;
expertDetails.insertBefore(stepNumber, expertDetails.firstChild);
stepItem.appendChild(expertDetails);
stepItem.appendChild(progressBar);
taskView.appendChild(stepItem);
}
}
// Animate progress bar
const expertName = document.querySelectorAll('.expert-name');
const status = document.querySelectorAll('.status');
const stepNumberElements = taskView.querySelectorAll('.step-number');
for (let i = 0; i <= completedStages; i++) {
stepNumberElements[i].classList.add('step-number-completed');
expertName[i].classList.add('completed');
status[i].textContent = 'Completed task(s)';
status[i].classList.add('completed');
// await new Promise((resolve) => setTimeout(resolve, 1000));
if (i < stepNumberElements.length - 1) {
completedStages++;
}
}
progressBar.classList.add('completed-stage');
}
// Disable buttons if no input or api keys
const sendButton = document.getElementById('sendButton');
const sendIput = document.getElementById('inputMessage');
sendButton.disabled = true;
sendButton.style.color = '#9d9d9d';
function sendBtn() {
if (sendIput.value.trim() !== '') {
sendButton.disabled = false;
sendButton.style.color = '#079f49';
} else {
sendButton.disabled = true;
sendButton.style.color = '#9d9d9d';
}
}
sendIput.addEventListener('input', sendBtn);
sendBtn();
// handle sending messages in the chat
async function sendMessage(exampleMessage) {
if(taskId != null) {
alert("Previous task is not completed. Please wait for the result or click STOP to end.");
return;
}
await clearChat();
const apiKey = document.getElementById('openai-api-key').value;
const serpApiKey = document.getElementById('serp-api-key').value;
const exampleMessagesID = document.getElementById("example-messages");
const intro = document.querySelector('.intro');
const sentMessage = document.getElementById('inputMessage').value;
const inputMessage = sentMessage || exampleMessage;
if ((!apiKey || apiKey.trim() === '' || apiKey === undefined) && (!serpApiKey || serpApiKey.trim() === '' || serpApiKey === undefined)) {
var colLeft = document.querySelector('.col-left');
colLeft.classList.toggle('show');
alert('Please enter both API keys');
return;
} else if (inputMessage === '') {
alert('Please enter a message');
} else {
localStorage.setItem("llm_api_key", apiKey.trim());
localStorage.setItem("serpapi_key", serpApiKey.trim());
// Get the input message and render into chat
const chatView = document.getElementById('chatView');
const messageDiv = document.createElement('div');
const sentMesage = document.createElement('p');
sentMesage.className = 'chat-bubble me-2 p-3';
sentMesage.textContent = inputMessage;
const senderImage = document.createElement('img');
senderImage.src = '../images/default.jpg';
senderImage.height = 50;
senderImage.width = 50;
senderImage.className = 'agent-avatar';
messageDiv.className = 'chat-user d-flex mb-2 mt-5 justify-content-end';
messageDiv.appendChild(sentMesage);
messageDiv.appendChild(senderImage);
chatView.appendChild(messageDiv);
ws.send(JSON.stringify({
"action": "run_task",
"data": {
"llm_api_key": apiKey,
"serpapi_key": serpApiKey,
"idea": inputMessage
}
}));
// Clear the input field
document.getElementById('inputMessage').value = '';
intro.style.display = 'none';
exampleMessagesID.style.display = 'none';
// show Stop button
interruptButton.textContent = 'Stop';
interruptButton.style.color = 'black';
interruptButton.style.display = '';
document.getElementById('calling-next-agent').style.display = 'block';
}
}
async function clearChat() {
// agent list
const agentsList = document.getElementById('agentsList');
agentsList.innerHTML = '';
agentsList.className = 'list-group list-group-flush';
// chat view
const chatUsers = document.getElementsByClassName('chat-user');
for(var i = chatUsers.length-1; i >=0; i--){
chatUsers[i].remove();
}
const chatAgents = document.getElementsByClassName('chat-agent');
for(var i = chatAgents.length-1; i >= 0; i--) {
chatAgents[i].remove();
}
const invitedExperts = document.getElementsByClassName('invite-message');
for(var i = invitedExperts.length-1; i >= 0; i--) {
invitedExperts[i].remove();
}
document.getElementById('calling-next-agent').style.display = 'none';
// progress
document.getElementById('taskView').innerHTML = '';
// examples
document.getElementById('example-messages').style.display = '';
// global variables
currentImageIndex = 0;
previousScrolledDiv = null;
renderedAgentMessage = {};
renderedLeadAgents = {};
callingMessageCount = 0;
renderedTasks = {};
taskStepCount = 0;
agentProfileImages = {};
agentLists = [];
// button state, task_id, agentList
interruptButton.textContent = 'Stop';
interruptButton.style.color = 'black';
interruptButton.style.display = 'none';
clearButton.style.display = 'none';
taskId = null;
// ws connect
if (ws == null || ws.readyState != 1) {
await connect();
await sleep(800);
}
}
// Add event listener to send button
document.getElementById('sendButton').addEventListener('click', sendMessage);
document.getElementById('inputMessage').addEventListener('keydown', function(e){
if(e.key == "Enter"){
sendMessage();
}
})
document.addEventListener("DOMContentLoaded", function () {
const exampleInputs = document.querySelectorAll(".example-input");
exampleInputs.forEach(function (example) {
// if (ws = null){ example.disabled = true;}
example.addEventListener("click", function () {
const inputValue = example.getAttribute("data-input");
sendMessage(inputValue);
});
});
});
const interruptButton = document.getElementById('interruptButton');
interruptButton.addEventListener('click', async () => {
if (taskId && ws) {
ws.send(JSON.stringify({
"action": "interrupt",
"data": {
"task_id": taskId
}
}));
} else {
interruptButton.style.color = 'red';
if(interruptButton.textContent == 'Stop'){
interruptButton.textContent = 'Stopped';
}
const callingMessages = document.querySelectorAll("calling-message");
callingMessages.forEach((callingMessage) => {
callingMessage.style.display = "none !important";
})
clearButton.style.display = '';
document.getElementById('calling-next-agent').style.display = 'none';
}
});
const clearButton = document.getElementById('clearButton');
clearButton.addEventListener('click', async () => {
await clearChat();
});
// Websocket connection
async function connect() {
ws = new WebSocket(apiHost);
ws.onopen = function (e) {
console.log('ws opened');
if (ws.readyState == 1) {
console.log('ws connected');
}
};
ws.onmessage = async function (e) {
console.log(e['data'])
var response = JSON.parse(e['data']);
if (response["action"] == "run_task") {
// console.log(response);
// nothing to do
if (response['msg'] == 'ok') {
taskId = response['data']['task_id'];
// console.log(response["data"])
let responseData = [response["data"]];
// data rendering fxns
await renderLeadAgentsResponses(responseData).then(inviteTaskAgents(responseData));
await renderLeadAgentsOnce(responseData);
await renderTaskAgentsResponse(responseData)
await displayTasks(responseData);
await displayTaskSteps(responseData);
} else if (response['msg'] == 'finished') {
console.log("task: " + taskId + " finished.");
taskId = null;
interruptButton.style.display = '';
interruptButton.style.color = 'red';
if(interruptButton.textContent == 'Stop') {
interruptButton.textContent = 'Finished';
}
clearButton.style.display = '';
document.getElementById('calling-next-agent').style.display = 'none';
} else {
// errors
console.log("task error:" + response['msg']);
interruptButton.click();
alert("An error occurred in the task, please check the logs.");
}
}
else if (response['action'] == "interrupt") {
if (response['msg'] == 'ok') {
console.log("task: " + taskId + " interrupted.");
interruptButton.style.color = 'red';
if(interruptButton.textContent == 'Stop'){
interruptButton.textContent = 'Stopped';
}
const callingMessages = document.querySelectorAll("calling-message");
callingMessages.forEach((callingMessage) => {
callingMessage.style.display = "none !important";
})
clearButton.style.display = '';
document.getElementById('calling-next-agent').style.display = 'none';
}
}
}
ws.onerror = function (err) {
console.info('ws error: ' + err);
ws = null;
}
ws.onclose = function (e) {
console.info('ws close: ' + e);
ws = null;
};
}
document.addEventListener('DOMContentLoaded', function () {
var toggleLeftBtn = document.getElementById('toggleLeft');
var toggleRightBtn = document.getElementById('toggleRight');
var colLeft = document.querySelector('.col-left');
var colRight = document.querySelector('.col-right');
toggleLeftBtn.addEventListener('click', function () {
colLeft.classList.toggle('show');
colLeft.style.transition = 'all 0.3s ease-in-out !important';
colRight.classList.remove('show');
});
toggleRightBtn.addEventListener('click', function () {
colRight.classList.toggle('show');
colLeft.classList.remove('show');
});
let apiKey = localStorage.getItem("llm_api_key");
let serpApiKey = localStorage.getItem("serpapi_key");
if(apiKey) {
document.getElementById('openai-api-key').value = apiKey;
localStorage.removeItem('llm_api_key');
}
if(serpApiKey) {
document.getElementById('serp-api-key').value = serpApiKey;
localStorage.removeItem('serpapi_key');
}
setInterval(callingNextAgent, 100);
});
function callingNextAgent() {
node = document.getElementById('calling-next-agent');
let text = node.getAttribute('data-content');
var i = parseInt(node.getAttribute('data-index'));
if(i == 0) {
node.textContent = '';
}
if (i < text.length) {
node.textContent += text.charAt(i);
i = (i+1) % text.length;
node.setAttribute('data-index', i.toString());
}
}