const state = { tasks: [], selectedTask: "easy", sessionId: "default", observation: null, done: false, grade: null, logs: [], latest: null, showRawLatest: false, }; const refs = { taskSelect: document.getElementById("taskSelect"), resetBtn: document.getElementById("resetBtn"), autoBtn: document.getElementById("autoBtn"), aiBtn: document.getElementById("aiBtn"), finalizeBtn: document.getElementById("finalizeBtn"), suggestBtn: document.getElementById("suggestBtn"), runStepBtn: document.getElementById("runStepBtn"), clearLogBtn: document.getElementById("clearLogBtn"), actionForm: document.getElementById("actionForm"), actionType: document.getElementById("actionType"), passengerId: document.getElementById("passengerId"), flightId: document.getElementById("flightId"), sessionBadge: document.getElementById("sessionBadge"), phaseBadge: document.getElementById("phaseBadge"), scoreBadge: document.getElementById("scoreBadge"), taskMeta: document.getElementById("taskMeta"), budgetRemaining: document.getElementById("budgetRemaining"), budgetSpent: document.getElementById("budgetSpent"), progressValue: document.getElementById("progressValue"), invalidValue: document.getElementById("invalidValue"), stepCountBadge: document.getElementById("stepCountBadge"), pendingList: document.getElementById("pendingList"), flightsList: document.getElementById("flightsList"), latestResult: document.getElementById("latestResult"), latestRaw: document.getElementById("latestRaw"), toggleRawBtn: document.getElementById("toggleRawBtn"), logList: document.getElementById("logList"), }; function money(value) { return `$${Number(value || 0).toFixed(2)}`; } function safeJson(data) { return JSON.stringify(data, null, 2); } function escapeHtml(value) { return String(value) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function latestRow(label, value) { return `
${escapeHtml(label)} ${escapeHtml(value)}
`; } function buildLatestSummary() { const data = state.latest; if (!data) return '
No actions yet.
'; if (data.error) return `
Error
${escapeHtml(data.error)}
`; if (data.event === "reset") { return `
Session reset
${latestRow("Task", data.task)}${latestRow("Session", data.session_id)}`; } const summary = []; summary.push(`
${data.done ? "Complete" : "Step Applied"}
`); const obs = data.observation || {}; summary.push(latestRow("Reward", Number(data.reward?.value || 0).toFixed(4))); summary.push(latestRow("Budget Spent", money(obs.budget_spent))); summary.push(latestRow("Remaining", money(obs.budget_remaining))); if (data.final_score !== undefined) { summary.push(latestRow("Final Score", Number(data.final_score).toFixed(4))); } return summary.join(""); } async function api(path, options = {}) { const response = await fetch(path, { headers: { "Content-Type": "application/json" }, ...options, }); const payload = await response.json(); if (!response.ok) throw new Error(payload.detail || "API Error"); return payload; } function renderAll() { const obs = state.observation; if (!obs) return; refs.sessionBadge.textContent = `Session: ${state.sessionId}`; refs.phaseBadge.textContent = `State: ${state.done ? "Done" : "Active"}`; refs.scoreBadge.textContent = `Grade: ${state.grade == null ? "-" : Number(state.grade).toFixed(4)}`; refs.budgetRemaining.textContent = money(obs.budget_remaining); refs.budgetSpent.textContent = money(obs.budget_spent); refs.progressValue.textContent = `${obs.processed_count}/${obs.total_passengers}`; refs.invalidValue.textContent = obs.invalid_actions || 0; refs.stepCountBadge.textContent = `step ${obs.step_count || 0}`; // Render Lists refs.pendingList.innerHTML = (obs.pending_passengers || []).map(p => `
${p.id} - ${p.priority_tier}
${p.cabin_class} | ${p.original_flight}
`).join(""); refs.flightsList.innerHTML = (obs.available_flights || []).map(f => `
${f.id} ${f.is_partner ? "(Partner)" : ""}
T+${f.departure_hrs}h | E:${f.economy_seats} B:${f.business_seats}
`).join(""); refs.latestResult.innerHTML = buildLatestSummary(); // Update Dropdowns const passOpts = obs.pending_passengers.map(p => ``).join(""); refs.passengerId.innerHTML = '' + passOpts; const flightOpts = obs.available_flights.map(f => ``).join(""); refs.flightId.innerHTML = '' + flightOpts; } async function resetSession() { const result = await api("/reset", { method: "POST", body: JSON.stringify({ task: refs.taskSelect.value }), }); state.sessionId = result.session_id; state.observation = result.observation; state.done = false; state.grade = null; state.logs = []; state.latest = { event: "reset", task: result.task_key, session_id: result.session_id }; renderAll(); } async function runStep(action) { const result = await api("/step", { method: "POST", body: JSON.stringify({ session_id: state.sessionId, action }), }); processResult(result, action); } async function runAIStep(recursive = false) { if (state.done) return; refs.aiBtn.disabled = true; refs.aiBtn.textContent = "AI Thinking..."; try { const result = await api(`/auto_step?session_id=${encodeURIComponent(state.sessionId)}`, { method: "POST" }); processResult(result, { action_type: "AI_INFERENCE" }); if (recursive && !state.done) { setTimeout(() => runAIStep(true), 500); } } catch (err) { showError(err); } finally { if (!recursive || state.done) { refs.aiBtn.disabled = false; refs.aiBtn.textContent = "AI Auto-Play"; } } } function processResult(result, action) { state.observation = result.observation; state.done = !!result.done; state.latest = result; state.logs.push({ action, reward: result.reward?.value || 0 }); if (result.final_score !== undefined) state.grade = result.final_score; renderAll(); } function showError(err) { state.latest = { error: err.message }; renderAll(); } async function loadTasks() { const result = await api("/tasks"); state.tasks = result.tasks; refs.taskSelect.innerHTML = state.tasks.map(t => ``).join(""); } function bindEvents() { refs.resetBtn.onclick = resetSession; refs.aiBtn.onclick = () => runAIStep(true); refs.runStepBtn.onclick = (e) => { e.preventDefault(); const action = { action_type: refs.actionType.value, passenger_id: refs.passengerId.value, flight_id: refs.flightId.value }; runStep(action); }; } async function init() { bindEvents(); await loadTasks(); await resetSession(); } init();