VirtualLab/AutonomousResearcher
0
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();