self-plan / static /index.html
lvwerra's picture
lvwerra HF Staff
Add FastAPI demo app for self-plan
63274c0
Raw
History Blame Contribute Delete
10.3 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>self-plan</title>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500&family=Inter:wght@300;400;500&display=swap');
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: "Inter", sans-serif;
font-size: 13px; font-weight: 300; line-height: 1.6;
color: #1a1a1a; background: #fafafa;
padding: 32px 40px; max-width: 960px; margin: 0 auto;
}
h1 {
font-family: "JetBrains Mono", monospace;
font-size: 15px; font-weight: 400; letter-spacing: 2px;
margin-bottom: 2px;
}
.meta {
font-family: "JetBrains Mono", monospace;
color: #999; font-size: 10px; letter-spacing: 0.5px;
margin-bottom: 24px;
}
.input-row {
display: flex; gap: 8px; margin-bottom: 24px;
}
.input-row input {
flex: 1; font-family: "Inter", sans-serif;
font-size: 13px; font-weight: 300;
padding: 8px 12px; border: 1px solid #ddd;
background: #fff; outline: none;
}
.input-row input:focus { border-color: #888; }
.btn {
font-family: "JetBrains Mono", monospace;
font-size: 11px; font-weight: 400;
padding: 8px 20px; border: 1px solid #1a1a1a;
background: #1a1a1a; color: #fff; cursor: pointer;
letter-spacing: 0.5px;
}
.btn:hover { background: #333; }
.btn:disabled { background: #ccc; border-color: #ccc; cursor: default; }
.btn-outline {
background: #fff; color: #1a1a1a; border: 1px solid #ccc;
}
.btn-outline:hover { border-color: #888; }
.section { margin-bottom: 20px; }
.section-label {
font-family: "JetBrains Mono", monospace;
font-size: 9px; font-weight: 400;
text-transform: uppercase; letter-spacing: 2px;
color: #999; margin-bottom: 6px;
}
.code-block {
font-family: "JetBrains Mono", monospace;
font-size: 11px; font-weight: 300; line-height: 1.7;
background: #f4f4f4; border: 1px solid #e0e0e0;
padding: 12px 16px; white-space: pre-wrap;
overflow-x: auto; color: #333;
}
.code-block .kw { color: #7a3e9d; }
.code-block .fn { color: #1a6; }
.code-block .str { color: #9a6700; }
.code-block .num { color: #0969a5; }
.code-block .cm { color: #999; font-style: italic; }
.code-block .dec { color: #7a3e9d; }
.log-block {
font-family: "JetBrains Mono", monospace;
font-size: 11px; font-weight: 300; line-height: 1.9;
}
.log-line { color: #555; }
.log-line .ts { color: #bbb; }
.log-line .agent-name { color: #2a7; font-weight: 400; }
.log-line .count { color: #888; }
.log-line .check { color: #2a7; }
.log-line.error { color: #c33; }
.log-line.done { color: #1a1a1a; font-weight: 400; }
.result-block {
font-size: 13px; font-weight: 300; line-height: 1.75;
color: #1a1a1a;
}
.result-block h1, .result-block h2, .result-block h3 {
font-family: "Inter", sans-serif;
font-weight: 500; margin: 16px 0 8px;
}
.result-block h1 { font-size: 18px; }
.result-block h2 { font-size: 15px; }
.result-block h3 { font-size: 13px; }
.result-block p { margin-bottom: 10px; }
.result-block ul, .result-block ol { margin: 8px 0 8px 20px; }
.result-block li { margin-bottom: 4px; }
.result-block code {
font-family: "JetBrains Mono", monospace;
font-size: 11px; background: #f0f0f0; padding: 1px 4px;
}
.result-block hr { border: none; border-top: 1px solid #eee; margin: 16px 0; }
.result-block a { color: #0969a5; }
.result-block strong { font-weight: 500; }
.confirm-row {
display: flex; gap: 8px; margin-top: 10px; margin-bottom: 20px;
}
.hidden { display: none; }
.divider { border-top: 1px solid #eee; margin: 20px 0; }
.spinner { display: inline-block; animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
</style>
</head>
<body>
<h1>SELF-PLAN</h1>
<div class="meta">multi-agent orchestration — plans that write themselves</div>
<div class="input-row">
<input id="task" type="text" placeholder="describe a task..." autofocus>
<button class="btn" id="run" onclick="generate()">RUN</button>
</div>
<div class="section hidden" id="s-plan">
<div class="section-label">plan</div>
<div class="code-block" id="plan-code"></div>
<div class="confirm-row" id="confirm-row">
<button class="btn" id="btn-execute" onclick="execute()">EXECUTE</button>
<button class="btn btn-outline" onclick="reset()">CANCEL</button>
</div>
</div>
<div class="section hidden" id="s-log">
<div class="section-label">log</div>
<div class="log-block" id="log-feed"></div>
</div>
<div class="divider hidden" id="s-divider"></div>
<div class="section hidden" id="s-result">
<div class="section-label">result</div>
<div class="result-block" id="result-text"></div>
</div>
<script>
const $ = s => document.getElementById(s);
const show = s => $(s).classList.remove("hidden");
const hide = s => $(s).classList.add("hidden");
let currentCode = "";
let currentTask = "";
function addLog(msg, cls = "") {
const div = document.createElement("div");
div.className = "log-line" + (cls ? " " + cls : "");
div.innerHTML = msg;
$("log-feed").appendChild(div);
div.scrollIntoView({behavior: "smooth", block: "end"});
}
function fmtLog(msg) {
return msg
.replace(/▶ \[(\d+)x\]\s*(\w+)/g, '▶ <span class="count">[$1x]</span> <span class="agent-name">$2</span>')
.replace(/▶\s*(\w+)/g, '▶ <span class="agent-name">$1</span>')
.replace(/✓ \[(\d+)x\]\s*(\w+)/g, '<span class="check">✓</span> <span class="count">[$1x]</span> <span class="agent-name">$2</span>')
.replace(/✓\s*(\w+)/g, '<span class="check">✓</span> <span class="agent-name">$1</span>')
.replace(/\[([0-9.]+s)\]/g, '<span class="ts">[$1]</span>');
}
function reset() {
hide("s-plan"); hide("s-log"); hide("s-divider"); hide("s-result");
$("run").disabled = false;
$("task").disabled = false;
currentCode = "";
}
async function generate() {
currentTask = $("task").value.trim();
if (!currentTask) return;
$("run").disabled = true;
$("task").disabled = true;
$("log-feed").innerHTML = "";
$("result-text").innerHTML = "";
hide("s-plan"); hide("s-log"); hide("s-divider"); hide("s-result");
show("s-log");
addLog('<span class="spinner">◑</span> generating plan...');
try {
const resp = await fetch("/generate?task=" + encodeURIComponent(currentTask));
const data = await resp.json();
currentCode = data.code;
$("log-feed").innerHTML = "";
addLog("plan generated");
$("plan-code").innerHTML = hlPython(currentCode);
show("s-plan");
} catch(e) {
addLog("error: " + e.message, "error");
$("run").disabled = false;
$("task").disabled = false;
}
}
function execute() {
$("btn-execute").disabled = true;
$("log-feed").innerHTML = "";
show("s-log");
const params = new URLSearchParams({task: currentTask, code: currentCode});
const es = new EventSource("/execute?" + params.toString());
es.addEventListener("log", e => {
const {msg} = JSON.parse(e.data);
const cls = msg.startsWith("done") ? "done" : "";
addLog(fmtLog(msg), cls);
});
es.addEventListener("result", e => {
const {text} = JSON.parse(e.data);
$("result-text").innerHTML = marked.parse(text);
show("s-divider");
show("s-result");
es.close();
$("run").disabled = false;
$("task").disabled = false;
$("btn-execute").disabled = false;
});
es.addEventListener("error", e => {
if (e.data) {
const {msg} = JSON.parse(e.data);
addLog(msg, "error");
}
es.close();
$("run").disabled = false;
$("task").disabled = false;
$("btn-execute").disabled = false;
});
es.onerror = () => {
es.close();
$("run").disabled = false;
$("task").disabled = false;
$("btn-execute").disabled = false;
};
}
$("task").addEventListener("keydown", e => {
if (e.key === "Enter") generate();
});
function hlPython(code) {
const KW = new Set(["def","class","return","if","elif","else","for","while",
"in","import","from","as","not","and","or","is","with","try","except",
"finally","raise","yield","break","continue","pass","True","False",
"None","lambda","global","async","await"]);
const tokens = [];
let i = 0;
while (i < code.length) {
if (code[i] === "#") {
let j = code.indexOf("\n", i); if (j === -1) j = code.length;
tokens.push({type:"cm", text:code.slice(i, j)}); i = j; continue;
}
if (code.slice(i,i+3) === '"""' || code.slice(i,i+3) === "'''") {
const q = code.slice(i,i+3); let j = code.indexOf(q, i+3);
j = j === -1 ? code.length : j+3;
tokens.push({type:"str", text:code.slice(i,j)}); i = j; continue;
}
if (code[i]==='"' || code[i]==="'" || (code[i]==='f' && i+1<code.length && (code[i+1]==='"'||code[i+1]==="'"))) {
let s = i; if (code[i]==='f') i++;
const q = code[i]; i++;
while (i < code.length && code[i] !== q) { if (code[i]==="\\") i++; i++; }
i++; tokens.push({type:"str", text:code.slice(s,i)}); continue;
}
if (/\d/.test(code[i]) && (i===0 || !/\w/.test(code[i-1]))) {
let j = i; while (j < code.length && /[\d.]/.test(code[j])) j++;
tokens.push({type:"num", text:code.slice(i,j)}); i = j; continue;
}
if (/[a-zA-Z_]/.test(code[i])) {
let j = i; while (j < code.length && /\w/.test(code[j])) j++;
const word = code.slice(i,j);
let k = j; while (k < code.length && code[k]===" ") k++;
if (KW.has(word)) tokens.push({type:"kw", text:word});
else if (code[k]==="(") tokens.push({type:"fn", text:word});
else tokens.push({type:"", text:word});
i = j; continue;
}
if (code[i]==="@" && (i===0 || code[i-1]==="\n")) {
let j = i+1; while (j < code.length && /\w/.test(code[j])) j++;
tokens.push({type:"dec", text:code.slice(i,j)}); i = j; continue;
}
tokens.push({type:"", text:code[i]}); i++;
}
return tokens.map(t => {
const esc = t.text.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
return t.type ? `<span class="${t.type}">${esc}</span>` : esc;
}).join("");
}
</script>
</body>
</html>