123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377 |
- <script>
- import { onMount, tick } from 'svelte';
- import { get } from 'svelte/store';
- import Nav from 'labs/packages/global-navbar/src/Nav.svelte';
- import SideBar from '$lib/SideBar.svelte';
- import '$lib/global.css';
- import '@xterm/xterm/css/xterm.css'
- import '@fortawesome/fontawesome-free/css/all.min.css'
- import { networkInterface, startLogin } from '$lib/network.js'
- import { cpuActivity, diskActivity, cpuPercentage, diskLatency } from '$lib/activities.js'
- import { introMessage, errorMessage, unexpectedErrorMessage } from '$lib/messages.js'
- import { displayConfig, handleToolImpl } from '$lib/anthropic.js'
- import { tryPlausible } from '$lib/plausible.js'
- export let configObj = null;
- export let processCallback = null;
- export let cacheId = null;
- export let cpuActivityEvents = [];
- export let diskLatencies = [];
- export let activityEventsInterval = 0;
- var term = null;
- var cx = null;
- var fitAddon = null;
- var cxReadFunc = null;
- var blockCache = null;
- var processCount = 0;
- var curVT = 0;
- var sideBarPinned = false;
- function writeData(buf, vt)
- {
- if(vt != 1)
- return;
- term.write(new Uint8Array(buf));
- }
- function readData(str)
- {
- if(cxReadFunc == null)
- return;
- for(var i=0;i<str.length;i++)
- cxReadFunc(str.charCodeAt(i));
- }
- function printMessage(msg)
- {
- for(var i=0;i<msg.length;i++)
- term.write(msg[i] + "\n");
- }
- function expireEvents(list, curTime, limitTime)
- {
- while(list.length > 1)
- {
- if(list[1].t < limitTime)
- {
- list.shift();
- }
- else
- {
- break;
- }
- }
- }
- function cleanupEvents()
- {
- var curTime = Date.now();
- var limitTime = curTime - 10000;
- expireEvents(cpuActivityEvents, curTime, limitTime);
- computeCpuActivity(curTime, limitTime);
- if(cpuActivityEvents.length == 0)
- {
- clearInterval(activityEventsInterval);
- activityEventsInterval = 0;
- }
- }
- function computeCpuActivity(curTime, limitTime)
- {
- var totalActiveTime = 0;
- var lastActiveTime = limitTime;
- var lastWasActive = false;
- for(var i=0;i<cpuActivityEvents.length;i++)
- {
- var e = cpuActivityEvents[i];
- // NOTE: The first event could be before the limit,
- // we need at least one event to correctly mark
- // active time when there is long time under load
- var eTime = e.t;
- if(eTime < limitTime)
- eTime = limitTime;
- if(e.state == "ready")
- {
- // Inactive state, add the time from lastActiveTime
- totalActiveTime += (eTime - lastActiveTime);
- lastWasActive = false;
- }
- else
- {
- // Active state
- lastActiveTime = eTime;
- lastWasActive = true;
- }
- }
- // Add the last interval if needed
- if(lastWasActive)
- {
- totalActiveTime += (curTime - lastActiveTime);
- }
- cpuPercentage.set(Math.ceil((totalActiveTime / 10000) * 100));
- }
- function hddCallback(state)
- {
- diskActivity.set(state != "ready");
- }
- function latencyCallback(latency)
- {
- diskLatencies.push(latency);
- if(diskLatencies.length > 30)
- diskLatencies.shift();
- // Average the latency over at most 30 blocks
- var total = 0;
- for(var i=0;i<diskLatencies.length;i++)
- total += diskLatencies[i];
- var avg = total / diskLatencies.length;
- diskLatency.set(Math.ceil(avg));
- }
- function cpuCallback(state)
- {
- cpuActivity.set(state != "ready");
- var curTime = Date.now();
- var limitTime = curTime - 10000;
- expireEvents(cpuActivityEvents, curTime, limitTime);
- cpuActivityEvents.push({t: curTime, state: state});
- computeCpuActivity(curTime, limitTime);
- // Start an interval timer to cleanup old samples when no further activity is received
- if(activityEventsInterval != 0)
- clearInterval(activityEventsInterval);
- activityEventsInterval = setInterval(cleanupEvents, 2000);
- }
- function computeXTermFontSize()
- {
- return parseInt(getComputedStyle(document.body).fontSize);
- }
- function setScreenSize(display)
- {
- var internalMult = 1.0;
- var displayWidth = display.offsetWidth;
- var displayHeight = display.offsetHeight;
- var minWidth = 1024;
- var minHeight = 768;
- if(displayWidth < minWidth)
- internalMult = minWidth / displayWidth;
- if(displayHeight < minHeight)
- internalMult = Math.max(internalMult, minHeight / displayHeight);
- var internalWidth = Math.floor(displayWidth * internalMult);
- var internalHeight = Math.floor(displayHeight * internalMult);
- cx.setKmsCanvas(display, internalWidth, internalHeight);
- // Compute the size to be used for AI screenshots
- var screenshotMult = 1.0;
- var maxWidth = 1024;
- var maxHeight = 768;
- if(internalWidth > maxWidth)
- screenshotMult = maxWidth / internalWidth;
- if(internalHeight > maxHeight)
- screenshotMult = Math.min(screenshotMult, maxHeight / internalHeight);
- var screenshotWidth = Math.floor(internalWidth * screenshotMult);
- var screenshotHeight = Math.floor(internalHeight * screenshotMult);
- // Track the state of the mouse as requested by the AI, to avoid losing the position due to user movement
- displayConfig.set({width: screenshotWidth, height: screenshotHeight, mouseMult: internalMult * screenshotMult});
- }
- var curInnerWidth = 0;
- var curInnerHeight = 0;
- function handleResize()
- {
- // Avoid spurious resize events caused by the soft keyboard
- if(curInnerWidth == window.innerWidth && curInnerHeight == window.innerHeight)
- return;
- curInnerWidth = window.innerWidth;
- curInnerHeight = window.innerHeight;
- triggerResize();
- }
- function triggerResize()
- {
- term.options.fontSize = computeXTermFontSize();
- fitAddon.fit();
- const display = document.getElementById("display");
- if(display)
- setScreenSize(display);
- }
- async function initTerminal()
- {
- const { Terminal } = await import('@xterm/xterm');
- const { FitAddon } = await import('@xterm/addon-fit');
- const { WebLinksAddon } = await import('@xterm/addon-web-links');
- term = new Terminal({cursorBlink:true, convertEol:true, fontFamily:"monospace", fontWeight: 400, fontWeightBold: 700, fontSize: computeXTermFontSize()});
- fitAddon = new FitAddon();
- term.loadAddon(fitAddon);
- var linkAddon = new WebLinksAddon();
- term.loadAddon(linkAddon);
- const consoleDiv = document.getElementById("console");
- term.open(consoleDiv);
- term.scrollToTop();
- fitAddon.fit();
- window.addEventListener("resize", handleResize);
- term.focus();
- term.onData(readData);
- // Avoid undesired default DnD handling
- function preventDefaults (e) {
- e.preventDefault()
- e.stopPropagation()
- }
- consoleDiv.addEventListener("dragover", preventDefaults, false);
- consoleDiv.addEventListener("dragenter", preventDefaults, false);
- consoleDiv.addEventListener("dragleave", preventDefaults, false);
- consoleDiv.addEventListener("drop", preventDefaults, false);
- curInnerWidth = window.innerWidth;
- curInnerHeight = window.innerHeight;
- if(configObj.printIntro)
- printMessage(introMessage);
- try
- {
- await initCheerpX();
- }
- catch(e)
- {
- printMessage(unexpectedErrorMessage);
- printMessage([e.toString()]);
- return;
- }
- }
- function handleActivateConsole(vt)
- {
- if(curVT == vt)
- return;
- curVT = vt;
- if(vt != 7)
- return;
- // Raise the display to the foreground
- const display = document.getElementById("display");
- display.parentElement.style.zIndex = 5;
- tryPlausible("Display activated");
- }
- function handleProcessCreated()
- {
- processCount++;
- if(processCallback)
- processCallback(processCount);
- }
- async function initCheerpX()
- {
- const CheerpX = await import('@leaningtech/cheerpx');
- var blockDevice = null;
- switch(configObj.diskImageType)
- {
- case "cloud":
- try
- {
- blockDevice = await CheerpX.CloudDevice.create(configObj.diskImageUrl);
- }
- catch(e)
- {
- // Report the failure and try again with plain HTTP
- var wssProtocol = "wss:";
- if(configObj.diskImageUrl.startsWith(wssProtocol))
- {
- // WebSocket protocol failed, try agin using plain HTTP
- tryPlausible("WS Disk failure");
- blockDevice = await CheerpX.CloudDevice.create("https:" + configObj.diskImageUrl.substr(wssProtocol.length));
- }
- else
- {
- // No other recovery option
- throw e;
- }
- }
- break;
- case "bytes":
- blockDevice = await CheerpX.HttpBytesDevice.create(configObj.diskImageUrl);
- break;
- case "github":
- blockDevice = await CheerpX.GitHubDevice.create(configObj.diskImageUrl);
- break;
- default:
- throw new Error("Unrecognized device type");
- }
- blockCache = await CheerpX.IDBDevice.create(cacheId);
- var overlayDevice = await CheerpX.OverlayDevice.create(blockDevice, blockCache);
- var webDevice = await CheerpX.WebDevice.create("");
- var documentsDevice = await CheerpX.WebDevice.create("documents");
- var dataDevice = await CheerpX.DataDevice.create();
- var mountPoints = [
- // The root filesystem, as an Ext2 image
- {type:"ext2", dev:overlayDevice, path:"/"},
- // Access to files on the Web server, relative to the current page
- {type:"dir", dev:webDevice, path:"/web"},
- // Access to read-only data coming from JavaScript
- {type:"dir", dev:dataDevice, path:"/data"},
- // Automatically created device files
- {type:"devs", path:"/dev"},
- // Pseudo-terminals
- {type:"devpts", path:"/dev/pts"},
- // The Linux 'proc' filesystem which provides information about running processes
- {type:"proc", path:"/proc"},
- // The Linux 'sysfs' filesystem which is used to enumerate emulated devices
- {type:"sys", path:"/sys"},
- // Convenient access to sample documents in the user directory
- {type:"dir", dev:documentsDevice, path:"/home/user/documents"}
- ];
- try
- {
- cx = await CheerpX.Linux.create({mounts: mountPoints, networkInterface: networkInterface});
- }
- catch(e)
- {
- printMessage(errorMessage);
- printMessage([e.toString()]);
- return;
- }
- cx.registerCallback("cpuActivity", cpuCallback);
- cx.registerCallback("diskActivity", hddCallback);
- cx.registerCallback("diskLatency", latencyCallback);
- cx.registerCallback("processCreated", handleProcessCreated);
- term.scrollToBottom();
- cxReadFunc = cx.setCustomConsole(writeData, term.cols, term.rows);
- const display = document.getElementById("display");
- if(display)
- {
- setScreenSize(display);
- cx.setActivateConsole(handleActivateConsole);
- }
- // Run the command in a loop, in case the user exits
- while (true)
- {
- await cx.run(configObj.cmd, configObj.args, configObj.opts);
- }
- }
- onMount(initTerminal);
- async function handleConnect()
- {
- const w = window.open("login.html", "_blank");
- await cx.networkLogin();
- w.location.href = await startLogin();
- }
- async function handleReset()
- {
- // Be robust before initialization
- if(blockCache == null)
- return;
- await blockCache.reset();
- location.reload();
- }
- async function handleTool(tool)
- {
- return await handleToolImpl(tool, term);
- }
- async function handleSidebarPinChange(event)
- {
- sideBarPinned = event.detail;
- // Make sure the pinning state of reflected in the layout
- await tick();
- // Adjust the layout based on the new sidebar state
- triggerResize();
- }
- </script>
- <main class="relative w-full h-full">
- <Nav />
- <div class="absolute top-10 bottom-0 left-0 right-0">
- <SideBar on:connect={handleConnect} on:reset={handleReset} handleTool={!configObj.needsDisplay || curVT == 7 ? handleTool : null} on:sidebarPinChange={handleSidebarPinChange}>
- <slot></slot>
- </SideBar>
- {#if configObj.needsDisplay}
- <div class="absolute top-0 bottom-0 {sideBarPinned ? 'left-[23.5rem]' : 'left-14'} right-0">
- <canvas class="w-full h-full cursor-none" id="display"></canvas>
- </div>
- {/if}
- <div class="absolute top-0 bottom-0 {sideBarPinned ? 'left-[23.5rem]' : 'left-14'} right-0 p-1 scrollbar" id="console">
- </div>
- </div>
- </main>
|