| | <!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>HR Onboarding Environment</title> |
| | <style> |
| | * { margin: 0; padding: 0; box-sizing: border-box; } |
| | |
| | body { |
| | font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; |
| | background: #0f1117; |
| | color: #e0e0e0; |
| | min-height: 100vh; |
| | } |
| | |
| | .header { |
| | background: linear-gradient(135deg, #1a1f2e 0%, #0f1117 100%); |
| | border-bottom: 1px solid #2a2f3e; |
| | padding: 20px 32px; |
| | display: flex; |
| | align-items: center; |
| | gap: 16px; |
| | } |
| | |
| | .header-icon { |
| | font-size: 32px; |
| | } |
| | |
| | .header h1 { |
| | font-size: 22px; |
| | font-weight: 700; |
| | color: #fff; |
| | } |
| | |
| | .header p { |
| | font-size: 13px; |
| | color: #888; |
| | margin-top: 2px; |
| | } |
| | |
| | .header-badges { |
| | margin-left: auto; |
| | display: flex; |
| | gap: 8px; |
| | } |
| | |
| | .badge { |
| | background: #1e2433; |
| | border: 1px solid #2a3040; |
| | border-radius: 20px; |
| | padding: 4px 12px; |
| | font-size: 12px; |
| | color: #8899aa; |
| | } |
| | |
| | .badge b { color: #58a6ff; } |
| | |
| | .container { |
| | display: grid; |
| | grid-template-columns: 340px 1fr 320px; |
| | height: calc(100vh - 80px); |
| | gap: 0; |
| | } |
| | |
| | |
| | .panel-left { |
| | background: #13161f; |
| | border-right: 1px solid #1e2230; |
| | display: flex; |
| | flex-direction: column; |
| | overflow: hidden; |
| | } |
| | |
| | .panel-header { |
| | padding: 16px; |
| | border-bottom: 1px solid #1e2230; |
| | font-size: 13px; |
| | font-weight: 600; |
| | text-transform: uppercase; |
| | letter-spacing: 0.5px; |
| | color: #667; |
| | } |
| | |
| | .task-filters { |
| | padding: 12px 16px; |
| | display: flex; |
| | gap: 6px; |
| | flex-wrap: wrap; |
| | border-bottom: 1px solid #1e2230; |
| | } |
| | |
| | .filter-btn { |
| | background: #1a1f2e; |
| | border: 1px solid #2a2f3e; |
| | border-radius: 6px; |
| | padding: 4px 10px; |
| | font-size: 11px; |
| | color: #889; |
| | cursor: pointer; |
| | transition: all 0.15s; |
| | } |
| | |
| | .filter-btn:hover { border-color: #58a6ff; color: #58a6ff; } |
| | .filter-btn.active { background: #1c2d4a; border-color: #58a6ff; color: #58a6ff; } |
| | |
| | .task-list { |
| | flex: 1; |
| | overflow-y: auto; |
| | padding: 8px; |
| | } |
| | |
| | .task-item { |
| | padding: 10px 12px; |
| | border-radius: 8px; |
| | cursor: pointer; |
| | margin-bottom: 4px; |
| | transition: all 0.15s; |
| | } |
| | |
| | .task-item:hover { background: #1a1f2e; } |
| | .task-item.active { background: #1c2d4a; border-left: 3px solid #58a6ff; } |
| | |
| | .task-item .task-id { |
| | font-size: 11px; |
| | color: #556; |
| | font-family: monospace; |
| | } |
| | |
| | .task-item .task-title { |
| | font-size: 13px; |
| | color: #ccc; |
| | margin-top: 2px; |
| | line-height: 1.4; |
| | display: -webkit-box; |
| | -webkit-line-clamp: 2; |
| | -webkit-box-orient: vertical; |
| | overflow: hidden; |
| | } |
| | |
| | .task-item .task-meta { |
| | display: flex; |
| | gap: 6px; |
| | margin-top: 6px; |
| | } |
| | |
| | .task-tag { |
| | font-size: 10px; |
| | padding: 2px 6px; |
| | border-radius: 4px; |
| | font-weight: 600; |
| | text-transform: uppercase; |
| | } |
| | |
| | .tag-simple { background: #1a3a2a; color: #4ade80; } |
| | .tag-medium { background: #3a3a1a; color: #facc15; } |
| | .tag-complex { background: #3a1a1a; color: #f87171; } |
| | .tag-edge_case { background: #2a1a3a; color: #c084fc; } |
| | .tag-lookup { background: #1a2a3a; color: #60a5fa; } |
| | .tag-onboarding { background: #1a3a2a; color: #34d399; } |
| | .tag-offboarding { background: #3a2a1a; color: #fb923c; } |
| | .tag-cross_workflow { background: #2a1a3a; color: #a78bfa; } |
| | |
| | |
| | .panel-center { |
| | display: flex; |
| | flex-direction: column; |
| | overflow: hidden; |
| | } |
| | |
| | .task-instruction { |
| | padding: 20px 24px; |
| | background: #161a24; |
| | border-bottom: 1px solid #1e2230; |
| | } |
| | |
| | .task-instruction h3 { |
| | font-size: 14px; |
| | color: #58a6ff; |
| | margin-bottom: 8px; |
| | display: flex; |
| | align-items: center; |
| | gap: 8px; |
| | } |
| | |
| | .task-instruction p { |
| | font-size: 14px; |
| | line-height: 1.6; |
| | color: #d0d0d0; |
| | } |
| | |
| | .ideal-result { |
| | margin-top: 14px; |
| | background: #141a26; |
| | border: 1px solid #243049; |
| | border-left: 3px solid #58a6ff; |
| | border-radius: 8px; |
| | padding: 12px; |
| | } |
| | |
| | .ideal-result h4 { |
| | font-size: 12px; |
| | color: #9cc7ff; |
| | margin-bottom: 8px; |
| | text-transform: uppercase; |
| | letter-spacing: 0.4px; |
| | } |
| | |
| | .ideal-result .ideal-label { |
| | font-size: 11px; |
| | color: #7f8da3; |
| | margin: 6px 0 4px; |
| | } |
| | |
| | .ideal-result ul { |
| | margin: 0; |
| | padding-left: 18px; |
| | color: #c7d2e1; |
| | font-size: 12px; |
| | line-height: 1.45; |
| | } |
| | |
| | .ideal-result li { |
| | margin-bottom: 3px; |
| | } |
| | |
| | .step-indicator { |
| | display: flex; |
| | align-items: center; |
| | gap: 8px; |
| | margin-top: 12px; |
| | font-size: 12px; |
| | color: #667; |
| | } |
| | |
| | .step-bar { |
| | flex: 1; |
| | height: 4px; |
| | background: #1e2230; |
| | border-radius: 2px; |
| | overflow: hidden; |
| | } |
| | |
| | .step-bar-fill { |
| | height: 100%; |
| | background: #58a6ff; |
| | border-radius: 2px; |
| | transition: width 0.3s ease; |
| | } |
| | |
| | .action-log { |
| | flex: 1; |
| | overflow-y: auto; |
| | padding: 16px 24px; |
| | } |
| | |
| | .log-entry { |
| | margin-bottom: 16px; |
| | animation: fadeIn 0.3s ease; |
| | } |
| | |
| | @keyframes fadeIn { |
| | from { opacity: 0; transform: translateY(8px); } |
| | to { opacity: 1; transform: translateY(0); } |
| | } |
| | |
| | .log-step-label { |
| | font-size: 11px; |
| | color: #556; |
| | margin-bottom: 4px; |
| | font-family: monospace; |
| | } |
| | |
| | .log-action { |
| | background: #1a1f2e; |
| | border: 1px solid #252b3b; |
| | border-radius: 8px; |
| | padding: 12px; |
| | margin-bottom: 6px; |
| | } |
| | |
| | .log-action .tool-name { |
| | color: #58a6ff; |
| | font-family: monospace; |
| | font-size: 13px; |
| | font-weight: 600; |
| | } |
| | |
| | .log-action pre { |
| | margin-top: 6px; |
| | font-size: 12px; |
| | color: #8899aa; |
| | white-space: pre-wrap; |
| | word-break: break-word; |
| | font-family: 'JetBrains Mono', monospace; |
| | max-height: 120px; |
| | overflow-y: auto; |
| | } |
| | |
| | .log-result { |
| | background: #141820; |
| | border: 1px solid #1e2430; |
| | border-radius: 8px; |
| | padding: 12px; |
| | } |
| | |
| | .log-result.success { border-left: 3px solid #4ade80; } |
| | .log-result.error { border-left: 3px solid #f87171; } |
| | |
| | .log-result pre { |
| | font-size: 12px; |
| | color: #8899aa; |
| | white-space: pre-wrap; |
| | word-break: break-word; |
| | font-family: 'JetBrains Mono', monospace; |
| | max-height: 150px; |
| | overflow-y: auto; |
| | } |
| | |
| | |
| | .input-area { |
| | border-top: 1px solid #1e2230; |
| | padding: 16px 24px; |
| | background: #13161f; |
| | } |
| | |
| | .tool-select-row { |
| | display: flex; |
| | gap: 8px; |
| | margin-bottom: 10px; |
| | } |
| | |
| | .tool-select-row select { |
| | flex: 1; |
| | background: #1a1f2e; |
| | border: 1px solid #2a3040; |
| | border-radius: 8px; |
| | padding: 8px 12px; |
| | color: #d0d0d0; |
| | font-size: 13px; |
| | font-family: monospace; |
| | } |
| | |
| | .tool-select-row select:focus { outline: none; border-color: #58a6ff; } |
| | |
| | .params-input { |
| | width: 100%; |
| | background: #1a1f2e; |
| | border: 1px solid #2a3040; |
| | border-radius: 8px; |
| | padding: 10px 14px; |
| | color: #d0d0d0; |
| | font-size: 13px; |
| | font-family: 'JetBrains Mono', monospace; |
| | resize: vertical; |
| | min-height: 60px; |
| | } |
| | |
| | .params-input:focus { outline: none; border-color: #58a6ff; } |
| | |
| | .params-input::placeholder { color: #445; } |
| | |
| | .input-buttons { |
| | display: flex; |
| | gap: 8px; |
| | margin-top: 10px; |
| | } |
| | |
| | .btn { |
| | padding: 8px 20px; |
| | border-radius: 8px; |
| | font-size: 13px; |
| | font-weight: 600; |
| | cursor: pointer; |
| | border: none; |
| | transition: all 0.15s; |
| | } |
| | |
| | .btn-primary { |
| | background: #58a6ff; |
| | color: #000; |
| | } |
| | .btn-primary:hover { background: #79b8ff; } |
| | .btn-primary:disabled { background: #2a3a4a; color: #556; cursor: not-allowed; } |
| | |
| | .btn-secondary { |
| | background: #1e2433; |
| | color: #889; |
| | border: 1px solid #2a3040; |
| | } |
| | .btn-secondary:hover { border-color: #58a6ff; color: #58a6ff; } |
| | |
| | .btn-danger { |
| | background: #3a1a1a; |
| | color: #f87171; |
| | border: 1px solid #4a2020; |
| | } |
| | .btn-danger:hover { background: #4a2020; } |
| | |
| | |
| | .panel-right { |
| | background: #13161f; |
| | border-left: 1px solid #1e2230; |
| | display: flex; |
| | flex-direction: column; |
| | overflow: hidden; |
| | } |
| | |
| | .tools-section { |
| | flex: 1; |
| | overflow-y: auto; |
| | padding: 12px; |
| | } |
| | |
| | .tool-info { |
| | padding: 8px 10px; |
| | border-radius: 6px; |
| | cursor: pointer; |
| | font-size: 12px; |
| | font-family: monospace; |
| | color: #8899aa; |
| | transition: all 0.15s; |
| | } |
| | |
| | .tool-info:hover { background: #1a1f2e; color: #58a6ff; } |
| | |
| | .tool-info .tool-desc { |
| | font-family: 'Inter', sans-serif; |
| | font-size: 11px; |
| | color: #556; |
| | margin-top: 2px; |
| | display: none; |
| | } |
| | |
| | .tool-info:hover .tool-desc { display: block; } |
| | |
| | |
| | .eval-section { |
| | border-top: 1px solid #1e2230; |
| | padding: 16px; |
| | max-height: 50%; |
| | overflow-y: auto; |
| | } |
| | |
| | .eval-header { |
| | display: flex; |
| | align-items: center; |
| | justify-content: space-between; |
| | margin-bottom: 12px; |
| | } |
| | |
| | .eval-score { |
| | font-size: 28px; |
| | font-weight: 800; |
| | color: #58a6ff; |
| | } |
| | |
| | .eval-score.pass { color: #4ade80; } |
| | .eval-score.fail { color: #f87171; } |
| | .eval-score.partial { color: #facc15; } |
| | |
| | .eval-criteria { |
| | list-style: none; |
| | } |
| | |
| | .eval-criteria li { |
| | padding: 6px 0; |
| | font-size: 12px; |
| | display: flex; |
| | align-items: flex-start; |
| | gap: 8px; |
| | border-bottom: 1px solid #1a1f2a; |
| | } |
| | |
| | .eval-criteria .icon-pass { color: #4ade80; } |
| | .eval-criteria .icon-fail { color: #f87171; } |
| | |
| | .eval-criteria .criteria-desc { |
| | color: #889; |
| | font-size: 11px; |
| | } |
| | |
| | |
| | .welcome { |
| | display: flex; |
| | flex-direction: column; |
| | align-items: center; |
| | justify-content: center; |
| | height: 100%; |
| | text-align: center; |
| | padding: 40px; |
| | } |
| | |
| | .welcome h2 { |
| | font-size: 20px; |
| | color: #fff; |
| | margin-bottom: 8px; |
| | } |
| | |
| | .welcome p { |
| | color: #667; |
| | font-size: 14px; |
| | max-width: 400px; |
| | line-height: 1.6; |
| | } |
| | |
| | |
| | ::-webkit-scrollbar { width: 6px; } |
| | ::-webkit-scrollbar-track { background: transparent; } |
| | ::-webkit-scrollbar-thumb { background: #2a3040; border-radius: 3px; } |
| | ::-webkit-scrollbar-thumb:hover { background: #3a4050; } |
| | |
| | |
| | .spinner { |
| | display: inline-block; |
| | width: 14px; |
| | height: 14px; |
| | border: 2px solid #2a3040; |
| | border-top-color: #58a6ff; |
| | border-radius: 50%; |
| | animation: spin 0.6s linear infinite; |
| | } |
| | |
| | @keyframes spin { to { transform: rotate(360deg); } } |
| | |
| | @media (max-width: 1024px) { |
| | .container { |
| | grid-template-columns: 1fr; |
| | grid-template-rows: auto 1fr; |
| | } |
| | .panel-left, .panel-right { display: none; } |
| | } |
| | </style> |
| | </head> |
| | <body> |
| | <div class="header"> |
| | <div class="header-icon">🏢</div> |
| | <div> |
| | <h1>HR Onboarding & Offboarding Environment</h1> |
| | <p>OpenEnv RL Environment — Interactive Playground</p> |
| | </div> |
| | <div class="header-badges"> |
| | <span class="badge"><b>25</b> Tools</span> |
| | <span class="badge"><b>77</b> Tasks</span> |
| | <span class="badge"><b>200</b> Employees</span> |
| | <span class="badge"><b>15</b> Max Steps</span> |
| | </div> |
| | </div> |
| |
|
| | <div class="container"> |
| | |
| | <div class="panel-left"> |
| | <div class="panel-header">Tasks</div> |
| | <div class="task-filters"> |
| | <button class="filter-btn active" data-filter="all">All</button> |
| | <button class="filter-btn" data-filter="simple">Simple</button> |
| | <button class="filter-btn" data-filter="medium">Medium</button> |
| | <button class="filter-btn" data-filter="complex">Complex</button> |
| | <button class="filter-btn" data-filter="edge_case">Edge Case</button> |
| | </div> |
| | <div class="task-list" id="taskList"> |
| | <div style="padding: 20px; color: #556; font-size: 13px;">Loading tasks...</div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div class="panel-center"> |
| | <div class="task-instruction" id="taskInstruction"> |
| | <div class="welcome"> |
| | <h2>Select a task to begin</h2> |
| | <p>Pick a task from the left panel. You'll get an instruction, then call tools step by step to complete it. Your performance is scored by a rubric at the end.</p> |
| | </div> |
| | </div> |
| |
|
| | <div class="action-log" id="actionLog"></div> |
| |
|
| | <div class="input-area" id="inputArea" style="display: none;"> |
| | <div class="tool-select-row"> |
| | <select id="toolSelect"> |
| | <option value="">-- select a tool --</option> |
| | </select> |
| | </div> |
| | <textarea class="params-input" id="paramsInput" placeholder='{"emp_id": "emp_0001"}'></textarea> |
| | <div class="input-buttons"> |
| | <button class="btn btn-primary" id="btnStep" onclick="sendStep()">Send Tool Call</button> |
| | <button class="btn btn-secondary" id="btnDone" onclick="finishEpisode()">Finish & Evaluate</button> |
| | <button class="btn btn-danger" id="btnReset" onclick="resetCurrentTask()">Reset Task</button> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div class="panel-right"> |
| | <div class="panel-header">Available Tools</div> |
| | <div class="tools-section" id="toolsSection"></div> |
| | <div class="eval-section" id="evalSection" style="display: none;"> |
| | <div class="panel-header" style="padding: 0 0 8px 0; border: none;">Evaluation</div> |
| | <div class="eval-header"> |
| | <span class="eval-score" id="evalScore">--</span> |
| | <span id="evalLabel" style="font-size: 12px; color: #667;"></span> |
| | </div> |
| | <ul class="eval-criteria" id="evalCriteria"></ul> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | <script> |
| | const API_BASE = window.location.origin; |
| | let tasks = []; |
| | let currentTaskIdx = null; |
| | let currentStep = 0; |
| | let maxSteps = 15; |
| | let episodeDone = false; |
| | let availableTools = []; |
| | let toolDefs = []; |
| | |
| | |
| | async function init() { |
| | await loadTasks(); |
| | await loadToolDefs(); |
| | renderTaskList(); |
| | renderToolsPanel(); |
| | } |
| | |
| | async function loadTasks() { |
| | const res = await fetch(`${API_BASE}/api/tasks`); |
| | tasks = await res.json(); |
| | } |
| | |
| | async function loadToolDefs() { |
| | const res = await fetch(`${API_BASE}/api/tool_definitions`); |
| | toolDefs = await res.json(); |
| | } |
| | |
| | |
| | function renderTaskList(filter = 'all') { |
| | const list = document.getElementById('taskList'); |
| | const filtered = filter === 'all' ? tasks : tasks.filter(t => t.difficulty === filter); |
| | list.innerHTML = filtered.map((t, i) => ` |
| | <div class="task-item" data-idx="${t.index}" onclick="selectTask(${t.index})"> |
| | <div class="task-id">${t.task_id}</div> |
| | <div class="task-title">${t.instruction}</div> |
| | <div class="task-meta"> |
| | <span class="task-tag tag-${t.difficulty}">${t.difficulty}</span> |
| | <span class="task-tag tag-${t.category}">${t.category}</span> |
| | </div> |
| | </div> |
| | `).join(''); |
| | } |
| | |
| | |
| | document.querySelectorAll('.filter-btn').forEach(btn => { |
| | btn.addEventListener('click', () => { |
| | document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active')); |
| | btn.classList.add('active'); |
| | renderTaskList(btn.dataset.filter); |
| | }); |
| | }); |
| | |
| | |
| | async function selectTask(idx) { |
| | currentTaskIdx = idx; |
| | currentStep = 0; |
| | episodeDone = false; |
| | |
| | |
| | document.querySelectorAll('.task-item').forEach(el => el.classList.remove('active')); |
| | const active = document.querySelector(`.task-item[data-idx="${idx}"]`); |
| | if (active) active.classList.add('active'); |
| | |
| | |
| | const res = await fetch(`${API_BASE}/api/reset`, { |
| | method: 'POST', |
| | headers: { 'Content-Type': 'application/json' }, |
| | body: JSON.stringify({ task_idx: idx }), |
| | }); |
| | const data = await res.json(); |
| | maxSteps = data.max_steps || maxSteps; |
| | |
| | |
| | const instrEl = document.getElementById('taskInstruction'); |
| | const task = tasks.find(t => t.index === idx); |
| | instrEl.innerHTML = ` |
| | <h3> |
| | <span class="task-tag tag-${task.difficulty}">${task.difficulty}</span> |
| | <span class="task-tag tag-${task.category}">${task.category}</span> |
| | ${data.task_id} |
| | </h3> |
| | <p>${escapeHtml(data.instruction)}</p> |
| | ${renderIdealResult(task)} |
| | <div class="step-indicator"> |
| | <span>Step ${currentStep}/${maxSteps}</span> |
| | <div class="step-bar"><div class="step-bar-fill" style="width: 0%"></div></div> |
| | </div> |
| | `; |
| | |
| | |
| | document.getElementById('actionLog').innerHTML = ''; |
| | document.getElementById('inputArea').style.display = 'block'; |
| | document.getElementById('evalSection').style.display = 'none'; |
| | |
| | |
| | availableTools = data.available_tools || []; |
| | const sel = document.getElementById('toolSelect'); |
| | sel.innerHTML = '<option value="">-- select a tool --</option>' + |
| | availableTools.map(t => `<option value="${t}">${t}</option>`).join(''); |
| | |
| | updateButtons(); |
| | } |
| | |
| | async function resetCurrentTask() { |
| | if (currentTaskIdx !== null) { |
| | await selectTask(currentTaskIdx); |
| | } |
| | } |
| | |
| | |
| | async function sendStep() { |
| | const toolName = document.getElementById('toolSelect').value; |
| | if (!toolName) { alert('Select a tool first'); return; } |
| | |
| | let params = {}; |
| | const paramsText = document.getElementById('paramsInput').value.trim(); |
| | if (paramsText) { |
| | try { |
| | params = JSON.parse(paramsText); |
| | } catch (e) { |
| | alert('Invalid JSON in parameters: ' + e.message); |
| | return; |
| | } |
| | } |
| | |
| | document.getElementById('btnStep').disabled = true; |
| | document.getElementById('btnStep').innerHTML = '<span class="spinner"></span> Running...'; |
| | |
| | const res = await fetch(`${API_BASE}/api/step`, { |
| | method: 'POST', |
| | headers: { 'Content-Type': 'application/json' }, |
| | body: JSON.stringify({ tool_name: toolName, arguments: params }), |
| | }); |
| | const data = await res.json(); |
| | |
| | currentStep = data.step || currentStep + 1; |
| | episodeDone = data.done || false; |
| | |
| | |
| | addLogEntry(toolName, params, data.tool_result, data.done, data.reward); |
| | |
| | |
| | updateStepIndicator(); |
| | |
| | |
| | if (episodeDone && data.metadata && data.metadata.evaluation) { |
| | showEvaluation(data.metadata.evaluation); |
| | } |
| | |
| | |
| | document.getElementById('paramsInput').value = ''; |
| | updateButtons(); |
| | } |
| | |
| | async function finishEpisode() { |
| | |
| | if (!episodeDone) { |
| | |
| | const res = await fetch(`${API_BASE}/api/evaluate`, { |
| | method: 'POST', |
| | headers: { 'Content-Type': 'application/json' }, |
| | }); |
| | const data = await res.json(); |
| | episodeDone = true; |
| | showEvaluation(data); |
| | updateButtons(); |
| | } |
| | } |
| | |
| | |
| | function addLogEntry(toolName, params, result, done, reward) { |
| | const log = document.getElementById('actionLog'); |
| | const isSuccess = result && result.success !== false; |
| | const resultJson = JSON.stringify(result, null, 2); |
| | const paramsJson = JSON.stringify(params, null, 2); |
| | |
| | const entry = document.createElement('div'); |
| | entry.className = 'log-entry'; |
| | entry.innerHTML = ` |
| | <div class="log-step-label">Step ${currentStep}</div> |
| | <div class="log-action"> |
| | <span class="tool-name">${toolName}</span> |
| | <pre>${paramsJson}</pre> |
| | </div> |
| | <div class="log-result ${isSuccess ? 'success' : 'error'}"> |
| | <pre>${resultJson.length > 800 ? resultJson.substring(0, 800) + '\n...' : resultJson}</pre> |
| | </div> |
| | `; |
| | log.appendChild(entry); |
| | log.scrollTop = log.scrollHeight; |
| | } |
| | |
| | function renderIdealResult(task) { |
| | if (!task) return ''; |
| | |
| | const expectedTools = (task.expected_tools || []) |
| | .map(t => `<li><code>${escapeHtml(String(t))}</code></li>`) |
| | .join(''); |
| | |
| | const criteria = (task.rubric_criteria || []) |
| | .map(c => `<li>${escapeHtml(c.description || c.name || 'Criterion')}</li>`) |
| | .join(''); |
| | |
| | return ` |
| | <div class="ideal-result"> |
| | <h4>Ideal Result</h4> |
| | <div class="ideal-label">Expected tools:</div> |
| | <ul>${expectedTools || '<li>Not specified</li>'}</ul> |
| | <div class="ideal-label">Success criteria:</div> |
| | <ul>${criteria || '<li>Not specified</li>'}</ul> |
| | </div> |
| | `; |
| | } |
| | |
| | |
| | function showEvaluation(evalData) { |
| | const section = document.getElementById('evalSection'); |
| | section.style.display = 'block'; |
| | |
| | const score = evalData.score || 0; |
| | const scoreEl = document.getElementById('evalScore'); |
| | scoreEl.textContent = `${Math.round(score * 100)}%`; |
| | scoreEl.className = 'eval-score ' + (score >= 1 ? 'pass' : score >= 0.5 ? 'partial' : 'fail'); |
| | |
| | const labelEl = document.getElementById('evalLabel'); |
| | labelEl.textContent = evalData.passed ? 'ALL CRITERIA MET' : `${evalData.passed_count}/${evalData.total_criteria} criteria`; |
| | |
| | const criteria = document.getElementById('evalCriteria'); |
| | criteria.innerHTML = (evalData.criteria_results || []).map(c => ` |
| | <li> |
| | <span class="${c.passed ? 'icon-pass' : 'icon-fail'}">${c.passed ? '✓' : '✗'}</span> |
| | <div> |
| | <div style="color: ${c.passed ? '#4ade80' : '#f87171'}">${c.name}</div> |
| | <div class="criteria-desc">${c.description}</div> |
| | </div> |
| | </li> |
| | `).join(''); |
| | } |
| | |
| | |
| | function updateStepIndicator() { |
| | const pct = (currentStep / maxSteps) * 100; |
| | const indicator = document.querySelector('.step-indicator'); |
| | if (indicator) { |
| | indicator.querySelector('span').textContent = `Step ${currentStep}/${maxSteps}`; |
| | indicator.querySelector('.step-bar-fill').style.width = `${pct}%`; |
| | } |
| | } |
| | |
| | function updateButtons() { |
| | document.getElementById('btnStep').disabled = episodeDone; |
| | document.getElementById('btnStep').innerHTML = episodeDone ? 'Episode Done' : 'Send Tool Call'; |
| | } |
| | |
| | function renderToolsPanel() { |
| | const section = document.getElementById('toolsSection'); |
| | section.innerHTML = toolDefs.map(t => ` |
| | <div class="tool-info" onclick="selectTool('${t.name}')"> |
| | ${t.name} |
| | <div class="tool-desc">${t.description.substring(0, 80)}${t.description.length > 80 ? '...' : ''}</div> |
| | </div> |
| | `).join(''); |
| | } |
| | |
| | function escapeHtml(value) { |
| | return String(value) |
| | .replace(/&/g, '&') |
| | .replace(/</g, '<') |
| | .replace(/>/g, '>') |
| | .replace(/"/g, '"') |
| | .replace(/'/g, '''); |
| | } |
| | |
| | function selectTool(name) { |
| | document.getElementById('toolSelect').value = name; |
| | |
| | const tool = toolDefs.find(t => t.name === name); |
| | if (tool && tool.parameters && tool.parameters.properties) { |
| | const props = tool.parameters.properties; |
| | const required = tool.parameters.required || []; |
| | const hint = {}; |
| | for (const [key, val] of Object.entries(props)) { |
| | hint[key] = val.description || val.type; |
| | } |
| | document.getElementById('paramsInput').placeholder = JSON.stringify(hint, null, 2); |
| | } |
| | } |
| | |
| | init(); |
| | </script> |
| | </body> |
| | </html> |
| |
|