const $ = (selector) => document.querySelector(selector); const escapeHtml = (unsafe) => { return unsafe .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }; const setLoader = (isLoading) => { const button = $("#submit"); const loader = $("#submit-loader"); if (isLoading) { button.style.display = "none"; loader.style.display = "block"; } else { button.style.display = "block"; loader.style.display = "none"; } }; const setAnswer = (answer, files = []) => { if (answer) { $("#answer").textContent = answer; } else { $("#answer").innerHTML = createSpinner(); } const filesDiv = $("#response-files"); filesDiv.innerHTML = ""; files.forEach((file) => { const a = document.createElement("a"); a.classList.add("icon-link"); a.href = file; a.textContent = file.split("/").pop(); a.setAttribute("download", ""); filesDiv.appendChild(a); }); }; class EvalApi { constructor({ onComplete, onError, onSettle, onLLMEnd, onToolEnd }) { this.executionId = null; this.pollInterval = null; this.onComplete = (answer, files, info) => { onComplete(answer, files, info); onSettle(); }; this.onError = (error) => { onError(error); onSettle(); }; this.onLLMEnd = (info) => { onLLMEnd(info); }; this.onToolEnd = (info) => { onToolEnd(info); }; } async uploadFiles(rawfiles) { const files = []; if (rawfiles.length > 0) { const formData = new FormData(); for (let i = 0; i < rawfiles.length; i++) { formData.append("files", rawfiles[i]); } const respone = await fetch("/upload", { method: "POST", body: formData, }); const { urls } = await respone.json(); files.push(...urls); } return files; } async execute(prompt, session, files) { try { const response = await fetch("/api/execute/async", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ prompt, session, files, }), }); if (response.status !== 200) { throw new Error(await response.text()); } const { id: executionId } = await response.json(); this.executionId = executionId; this.pollInterval = setInterval(this.poll.bind(this), 1000); } catch (e) { clearInterval(this.pollInterval); this.onError(e); } } async poll() { try { const response = await fetch(`/api/execute/async/${this.executionId}`, { method: "GET", }); if (response.status !== 200) { throw new Error(await response.text()); } const { status, result, info } = await response.json(); switch (status) { case "PENDING": break; case "FAILURE": throw new Error("Execution failed"); case "LLM_END": this.onLLMEnd(info); break; case "TOOL_END": this.onToolEnd(info); break; case "SUCCESS": clearInterval(this.pollInterval); this.onComplete(result.answer, result.files, info); break; } } catch (e) { clearInterval(this.pollInterval); this.onError(e); } } } const submit = async () => { setAnswer(""); setLoader(true); const actions = $("#actions"); actions.innerHTML = ""; let currentActionIndex = 0; const onInfo = (info) => { if (currentActionIndex >= info.index) { return; } currentActionIndex = info.index; const w = document.createElement("div"); w.innerHTML = createActionCard( info.index, info.action, escapeHtml(info.action_input || ""), info.what_i_did, info.plan, escapeHtml(info.observation || "") ); actions.appendChild(w); }; const api = new EvalApi({ onSettle: () => setLoader(false), onError: (error) => setAnswer(`Error: ${error.message}`, []), onComplete: (answer, files, info) => { setAnswer(answer, files); onInfo(info); }, onLLMEnd: onInfo, onToolEnd: onInfo, }); const prompt = $("#prompt").value; const session = $("#session").value; const files = await api.uploadFiles($("#files").files); await api.execute(prompt, session, files); }; const setRandomSessionId = () => { const sessionId = Math.random().toString(36).substring(2, 15); $("#session").value = sessionId; }; const createSpinner = () => `
Input | ${input} |
---|---|
What I Did | ${whatIdid} |
Plan | |||
---|---|---|---|
${p.replace("[ ]", "")} | ` : "" } ${ p.startsWith("[x]") ? `${p.replace("[x]", "")} | ` : "" }
Observation | |
---|---|
${observation}
|