Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>DriftShield — Human play</title> | |
| <style> | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; | |
| height: 100vh; | |
| overflow: hidden; | |
| color: #1a1a1a; | |
| } | |
| .container { display: flex; height: 100vh; } | |
| .left { | |
| width: 50%; | |
| background: #fff; | |
| border-right: 1px solid #e0e0e0; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .right { | |
| width: 50%; | |
| background: #f7f7f7; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .pane-header { | |
| padding: 16px 20px; | |
| border-bottom: 1px solid #e0e0e0; | |
| background: #f8f9fa; | |
| font-weight: 600; | |
| font-size: 15px; | |
| } | |
| .pane-content { flex: 1; padding: 20px; overflow-y: auto; } | |
| .card { | |
| background: #fff; | |
| border: 1px solid #e0e0e0; | |
| border-radius: 6px; | |
| padding: 16px; | |
| margin-bottom: 16px; | |
| } | |
| .card h3 { font-size: 13px; color: #555; margin-bottom: 10px; } | |
| label { display: block; font-weight: 500; font-size: 14px; margin-bottom: 6px; } | |
| .req { color: #c00; } | |
| textarea { | |
| width: 100%; | |
| min-height: 88px; | |
| padding: 10px 12px; | |
| border: 1px solid #ccc; | |
| border-radius: 4px; | |
| font-size: 14px; | |
| font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; | |
| resize: vertical; | |
| } | |
| textarea:focus { | |
| outline: none; | |
| border-color: #0d6efd; | |
| box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.2); | |
| } | |
| .hint { font-size: 12px; color: #666; margin-top: 6px; line-height: 1.4; } | |
| .row-btns { margin-top: 12px; display: flex; flex-wrap: wrap; gap: 10px; align-items: center; } | |
| .btn { | |
| border: none; | |
| padding: 10px 20px; | |
| border-radius: 4px; | |
| font-size: 14px; | |
| cursor: pointer; | |
| font-weight: 500; | |
| } | |
| .btn-primary { background: #0d6efd; color: #fff; } | |
| .btn-primary:hover { background: #0b5ed7; } | |
| .btn-primary:disabled { background: #6c757d; cursor: not-allowed; } | |
| .btn-secondary { background: #6c757d; color: #fff; } | |
| .btn-secondary:hover { background: #5c636a; } | |
| .state-row { display: flex; justify-content: space-between; margin-bottom: 8px; font-size: 14px; } | |
| .state-label { color: #666; } | |
| .state-value { font-family: ui-monospace, monospace; color: #111; } | |
| .obs-box, .hist-box { | |
| background: #fafafa; | |
| border: 1px solid #e8e8e8; | |
| border-radius: 4px; | |
| padding: 12px; | |
| min-height: 120px; | |
| max-height: 38vh; | |
| overflow: auto; | |
| font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; | |
| font-size: 12px; | |
| line-height: 1.45; | |
| white-space: pre-wrap; | |
| word-break: break-word; | |
| } | |
| .empty { color: #999; font-style: italic; } | |
| .log-item { border-bottom: 1px solid #eee; padding: 10px 0; } | |
| .log-item:last-child { border-bottom: none; } | |
| .log-meta { font-size: 11px; color: #888; margin-bottom: 4px; } | |
| .err { color: #b00020; font-size: 13px; margin-top: 8px; } | |
| a.toplink { font-size: 12px; color: #0d6efd; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="left"> | |
| <div class="pane-header">HumanAgent Interface</div> | |
| <div class="pane-content"> | |
| <div class="card"> | |
| <h3>Take action</h3> | |
| <label for="cmd">Command <span class="req">*</span></label> | |
| <textarea id="cmd" placeholder="Enter assistant message, or full JSON action…"></textarea> | |
| <p class="hint"> | |
| Plain text → <code>assistant_message</code> (empty <code>tool_calls</code>).<br /> | |
| JSON → <code>{"assistant_message":"…","tool_calls":[]}</code> or a single tool | |
| <code>{"name":"billing.get_invoice","args":{}}</code>. | |
| </p> | |
| <div class="row-btns"> | |
| <button type="button" class="btn btn-primary" id="btnStep">Step</button> | |
| <button type="button" class="btn btn-secondary" id="btnReset">Reset environment</button> | |
| <button type="button" class="btn btn-secondary" id="btnState">Get state</button> | |
| </div> | |
| <p class="err" id="err" style="display:none"></p> | |
| </div> | |
| <div class="card"> | |
| <h3>Current state</h3> | |
| <div class="state-row"><span class="state-label">Status</span><span class="state-value" id="stStatus">—</span></div> | |
| <div class="state-row"><span class="state-label">Episode ID</span><span class="state-value" id="stEp">—</span></div> | |
| <div class="state-row"><span class="state-label">Task ID</span><span class="state-value" id="stTask">—</span></div> | |
| <div class="state-row"><span class="state-label">Step count</span><span class="state-value" id="stSteps">—</span></div> | |
| </div> | |
| <p><a class="toplink" href="/web">Open full OpenEnv form (advanced)</a> · <a class="toplink" href="/">API JSON</a></p> | |
| </div> | |
| </div> | |
| <div class="right"> | |
| <div class="pane-header">State observer</div> | |
| <div class="pane-content"> | |
| <div class="card"> | |
| <h3>Current observation</h3> | |
| <div class="obs-box" id="obsBox"><span class="empty">No observation yet</span></div> | |
| </div> | |
| <div class="card"> | |
| <h3>Action history</h3> | |
| <div class="hist-box" id="histBox"><span class="empty">No actions taken yet</span></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| function parseAction(raw) { | |
| const s = (raw || "").trim(); | |
| if (!s) return { assistant_message: "", tool_calls: [] }; | |
| if (s.startsWith("{")) { | |
| try { | |
| const o = JSON.parse(s); | |
| if (o.assistant_message !== undefined || o.tool_calls !== undefined) { | |
| return { | |
| assistant_message: o.assistant_message != null ? String(o.assistant_message) : "", | |
| tool_calls: Array.isArray(o.tool_calls) ? o.tool_calls : [], | |
| }; | |
| } | |
| if (o.name !== undefined) { | |
| return { assistant_message: "", tool_calls: [o] }; | |
| } | |
| } catch (e) { | |
| return { assistant_message: s, tool_calls: [] }; | |
| } | |
| } | |
| return { assistant_message: s, tool_calls: [] }; | |
| } | |
| const errEl = document.getElementById("err"); | |
| function setErr(msg) { | |
| if (msg) { | |
| errEl.style.display = "block"; | |
| errEl.textContent = msg; | |
| } else { | |
| errEl.style.display = "none"; | |
| errEl.textContent = ""; | |
| } | |
| } | |
| const hist = []; | |
| function addHistory(line) { | |
| hist.push(line); | |
| const box = document.getElementById("histBox"); | |
| box.classList.remove("empty"); | |
| box.innerHTML = hist | |
| .map( | |
| (h) => | |
| `<div class="log-item"><div class="log-meta">${escapeHtml( | |
| h.t | |
| )}</div><div>${escapeHtml(h.a)}</div><div class="log-meta" style="margin-top:6px">reward: ${h.r != null ? h.r : "—"} · done: ${h.d}</div></div>` | |
| ) | |
| .join(""); | |
| } | |
| function escapeHtml(s) { | |
| if (s == null) return ""; | |
| return String(s) | |
| .replace(/&/g, "&") | |
| .replace(/</g, "<") | |
| .replace(/>/g, ">") | |
| .replace(/"/g, """); | |
| } | |
| function showObs(j) { | |
| const box = document.getElementById("obsBox"); | |
| const o = j && (j.observation != null ? j.observation : j); | |
| if (!o || (typeof o === "object" && !Object.keys(o).length)) { | |
| box.innerHTML = '<span class="empty">No observation yet</span>'; | |
| return; | |
| } | |
| box.classList.remove("empty"); | |
| const payload = { | |
| reward: j.reward, | |
| done: j.done, | |
| observation: o, | |
| }; | |
| box.textContent = JSON.stringify(payload, null, 2); | |
| } | |
| function updateStateFromServer(st) { | |
| if (!st) return; | |
| document.getElementById("stEp").textContent = st.episode_id || "—"; | |
| document.getElementById("stTask").textContent = st.task_id || "—"; | |
| document.getElementById("stSteps").textContent = | |
| st.step_count != null ? String(st.step_count) : "—"; | |
| } | |
| document.getElementById("btnReset").addEventListener("click", async () => { | |
| setErr(""); | |
| try { | |
| const r = await fetch("/web/reset", { method: "POST" }); | |
| const t = await r.text(); | |
| let j; | |
| try { | |
| j = JSON.parse(t); | |
| } catch (e) { | |
| setErr("Reset failed: " + t.slice(0, 200)); | |
| return; | |
| } | |
| if (!r.ok) { | |
| setErr("Reset HTTP " + r.status + " — " + t.slice(0, 300)); | |
| return; | |
| } | |
| hist.length = 0; | |
| document.getElementById("histBox").innerHTML = | |
| '<span class="empty">No actions taken yet</span>'; | |
| document.getElementById("stStatus").textContent = "Reset"; | |
| showObs(j); | |
| await refreshState(); | |
| } catch (e) { | |
| setErr(String(e)); | |
| } | |
| }); | |
| document.getElementById("btnStep").addEventListener("click", async () => { | |
| setErr(""); | |
| const action = parseAction(document.getElementById("cmd").value); | |
| try { | |
| const r = await fetch("/web/step", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ action }), | |
| }); | |
| const t = await r.text(); | |
| let j; | |
| try { | |
| j = JSON.parse(t); | |
| } catch (e) { | |
| setErr("Step failed: " + t.slice(0, 200)); | |
| return; | |
| } | |
| if (!r.ok) { | |
| setErr("Step HTTP " + r.status + " — " + t.slice(0, 300)); | |
| return; | |
| } | |
| document.getElementById("stStatus").textContent = j.done ? "Done" : "Stepped"; | |
| showObs(j); | |
| addHistory({ | |
| t: new Date().toISOString(), | |
| a: JSON.stringify(action), | |
| r: j.reward, | |
| d: !!j.done, | |
| }); | |
| await refreshState(); | |
| } catch (e) { | |
| setErr(String(e)); | |
| } | |
| }); | |
| document.getElementById("btnState").addEventListener("click", async () => { | |
| setErr(""); | |
| await refreshState(); | |
| }); | |
| async function refreshState() { | |
| try { | |
| const r = await fetch("/web/state"); | |
| const t = await r.text(); | |
| let st; | |
| try { | |
| st = JSON.parse(t); | |
| } catch (e) { | |
| setErr("Get state: bad JSON"); | |
| return; | |
| } | |
| if (r.ok) { | |
| updateStateFromServer(st); | |
| } | |
| } catch (e) { | |
| setErr(String(e)); | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> | |