Team Ai
Apppublic

VirtualLab/AutonomousResearcher

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
scripts.js257 linesDownload Raw Back to app
1let ws = new WebSocket("wss://" + window.location.host + "/ws");2 3// Global specs (update as needed)4const MODEL_USED = "Gemini2Pro";  // you can update this dynamically if you wish5const CALL_INTERVAL = 240;        // seconds (from CONFIG.check_interval)6let nextCallTimestamp = Date.now() + CALL_INTERVAL * 1000;7let remainingTime = CALL_INTERVAL;8 9let isStreaming = false;10let wsMessageQueue = [];11 12// Add this function near the top of scripts.js or after your global specs declarations13async function fetchMeta() {14  try {15    const res = await fetch("/meta");16    if (res.ok) {17      const data = await res.json();18      // Update the key and next call info in the specs table19      document.getElementById("key-spec").innerText = "Key: " + data.key_num;20      document.getElementById("interval-spec").innerText = "Next call: " + data.remaining + " sec";21    }22  } catch (err) {23    console.error("Error fetching meta:", err);24  }25}26 27// Update specifications div when DOM loads28document.addEventListener("DOMContentLoaded", () => {29  document.getElementById("model-spec").innerText = "Model: " + MODEL_USED;30  document.getElementById("interval-spec").innerText = "Call interval: " + remainingTime + " sec";31});32 33ws.onopen = function() {34  console.log("WebSocket connection established");35};36 37ws.onmessage = function(event) {38  let messageText = event.data;39  console.log("Received message:", messageText);40  if (isStreaming) {41    wsMessageQueue.push(messageText);42  } else {43    processWsMessage(messageText);44  }45};46 47ws.onerror = function(error) {48  console.error("WebSocket error:", error);49};50 51ws.onclose = function() {52  console.log("WebSocket connection closed");53};54 55function processWsQueue() {56  if (wsMessageQueue.length > 0 && !isStreaming) {57    let nextMessage = wsMessageQueue.shift();58    processWsMessage(nextMessage);59  }60}61 62function processWsMessage(messageText) {63  let chatDiv = document.getElementById("chat");64  console.log("Processing message:", messageText);65  66  let logEntries = messageText.split("Log entry. Timestamp#:").filter(entry => entry.trim() !== "");67  let lastIdx = logEntries.length - 1;68 69  logEntries.forEach((entry, idx) => {70    // Extract the timestamp and content71    let [timestamp, content] = entry.split(" Content#:", 2);72 73    // Determine type and set icon source accordingly74    let iconSrc = "";75    if (content.startsWith("COMMAND:")) {76      iconSrc = "/static/command.png";77    } else if (content.startsWith("TERMINAL:")) {78      iconSrc = "/static/terminal.png";79    } else if (content.startsWith("AI_TEXT:")) {80      iconSrc = "/static/agent.png";81    } else {82      iconSrc = "/static/agent.png";83    }84 85    // Build the formatted output differently for commands86    let formattedHTML = "";87    if (content.startsWith("COMMAND:")) {88      // Remove the 'COMMAND:' prefix from the raw content89      let commandText = content.replace("COMMAND:", "").trim();90      formattedHTML = `<span class="command-prefix">Agent@Terminal:$</span> <span class="command-text">${commandText}</span>`;91    } else {92      formattedHTML = content.replace("AI_TEXT:", "").replace("TERMINAL:", "").trim();93    }94 95    // Create the overall message container and assign a class based on type96    let messageDiv = document.createElement("div");97    if (content.startsWith("TERMINAL:")) {98      messageDiv.className = "message terminal";99    } else if (content.startsWith("COMMAND:")) {100      messageDiv.className = "message command";101    } else {102      messageDiv.className = "message ai";103    }104 105    // Create and add the timestamp element (outside and above the bubble)106    let timestampDiv = document.createElement("div");107    timestampDiv.className = "timestamp";108    timestampDiv.textContent = timestamp;109 110    // Create a wrapper that holds the icon and the bubble side-by-side111    let bubbleWrapper = document.createElement("div");112    bubbleWrapper.className = "bubble-wrapper";113 114    // Create the icon element (placed outside the bubble)115    let iconImg = document.createElement("img");116    iconImg.className = "icon";117    iconImg.src = iconSrc;118    iconImg.alt = "";119 120    // Create the bubble element for the text121    let bubbleDiv = document.createElement("div");122    bubbleDiv.className = "bubble";123 124    // Append the icon and bubble to the wrapper, then the timestamp and wrapper to the message125    bubbleWrapper.appendChild(iconImg);126    bubbleWrapper.appendChild(bubbleDiv);127    messageDiv.appendChild(timestampDiv);128    messageDiv.appendChild(bubbleWrapper);129 130    // Add the message to the chat container131    chatDiv.appendChild(messageDiv);132 133    // If this is the last entry and it is NOT a command (so that we do streaming)134    if (idx === lastIdx && !content.startsWith("COMMAND:")) {135      isStreaming = true;136      let textIndex = 0;137      // For streaming simulation use innerText (streaming HTML is trickier)138      bubbleDiv.textContent = "";139      let intervalId = setInterval(() => {140        bubbleDiv.textContent += formattedHTML[textIndex];141        textIndex++;142        if (textIndex >= formattedHTML.length) {143          clearInterval(intervalId);144          isStreaming = false;145          processWsQueue();146        }147      }, 30);148    } else {149      // Otherwise, show immediately. For command messages, use innerHTML so the prefix is styled.150      bubbleDiv.innerHTML = formattedHTML;151    }152  });153 154  window.scrollTo(0, document.body.scrollHeight);155}156 157// File Tree functionality158function buildTreeHtml(tree) {159  let html = '<div class="tree-element">';160  if (tree.type === "directory") {161    // Folder header with toggle indicator162    html += `<div class="tree-folder">163      <span class="folder-toggle">-</span>164      <span class="folder-name">${tree.name}</span>165    </div>`;166    // Folder children container167    if (tree.children && tree.children.length > 0) {168      html += `<div class="folder-children">`;169      tree.children.forEach(child => {170        html += buildTreeHtml(child);171      });172      html += `</div>`;173    }174  } else {175    // File element: file name and buttons in one row176    html += `<div class="tree-file">177      <span class="file-name">${tree.name}</span>178      <span class="file-buttons">179        <button class="view-btn" data-path="${tree.relative_path}">👁</button>180        <button class="download-btn" data-path="${tree.relative_path}">⬇</button>181      </span>182    </div>`;183  }184  html += '</div>';185  return html;186}187 188function loadFileTree() {189  fetch("/directory")190    .then(res => res.json())191    .then(tree => {192      const fileTreeDiv = document.getElementById("file-tree");193      // Build tree starting from children (if needed, adjust accordingly)194      fileTreeDiv.innerHTML = buildTreeHtml(tree);195      196      // Add folder toggle functionality197      document.querySelectorAll(".tree-folder").forEach(folder => {198        folder.addEventListener("click", (e) => {199          e.stopPropagation();200          const childrenDiv = folder.nextElementSibling;201          if (childrenDiv) {202            if (childrenDiv.style.display === "none") {203              childrenDiv.style.display = "block";204              folder.querySelector(".folder-toggle").innerText = "-";205            } else {206              childrenDiv.style.display = "none";207              folder.querySelector(".folder-toggle").innerText = "+";208            }209          }210        });211      });212 213      // Add click listeners for view buttons214      document.querySelectorAll("button.view-btn").forEach(btn => {215        btn.addEventListener("click", (e) => {216          e.stopPropagation();217          const filePath = btn.getAttribute("data-path");218          window.open(`/file?path=${encodeURIComponent(filePath)}`, "_blank");219        });220      });221      // Add click listeners for download buttons222      document.querySelectorAll("button.download-btn").forEach(btn => {223        btn.addEventListener("click", (e) => {224          e.stopPropagation();225          const filePath = btn.getAttribute("data-path");226          window.open(`/download?path=${encodeURIComponent(filePath)}`, "_blank");227        });228      });229    })230    .catch(err => console.error("Error loading file tree:", err));231}232 233loadFileTree();234setInterval(loadFileTree, 30000);235 236// Function to update the remaining time in the specs table237function updateRemainingTime() {238  const remaining = Math.max(0, Math.ceil((nextCallTimestamp - Date.now()) / 1000));239  document.getElementById("interval-spec").innerText = "Call interval: " + remaining + " sec";240  if (remaining <= 0) {241    // Reset the next call timestamp when the interval elapses.242    nextCallTimestamp = Date.now() + CALL_INTERVAL * 1000;243  }244}245 246// Update every 10 seconds247setInterval(updateRemainingTime, 10000);248 249// Also update immediately on DOMContentLoaded:250document.addEventListener("DOMContentLoaded", () => {251  document.getElementById("model-spec").innerText = "Model: " + MODEL_USED;252  updateRemainingTime();253});254 255// Update meta every 10 seconds256setInterval(fetchMeta, 10000);257fetchMeta();