dhnkhr's picture
Production-ready: Clean code with Groq API integration, LoRA model support, and FastAPI app
9753ee2
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("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#39;");
}
function latestRow(label, value) {
return `
<div class="latest-row">
<span class="latest-label">${escapeHtml(label)}</span>
<span class="latest-value">${escapeHtml(value)}</span>
</div>
`;
}
function buildLatestSummary() {
const data = state.latest;
if (!data) return '<div class="latest-empty">No actions yet.</div>';
if (data.error) return `<div class="latest-status latest-status-error">Error</div><div class="latest-note">${escapeHtml(data.error)}</div>`;
if (data.event === "reset") {
return `<div class="latest-status latest-status-reset">Session reset</div>${latestRow("Task", data.task)}${latestRow("Session", data.session_id)}`;
}
const summary = [];
summary.push(`<div class="latest-status ${data.done ? "latest-status-done" : "latest-status-active"}">${data.done ? "Complete" : "Step Applied"}</div>`);
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 => `
<div class="card card-passenger">
<strong>${p.id}</strong> - ${p.priority_tier}<br>
<small>${p.cabin_class} | ${p.original_flight}</small>
</div>
`).join("");
refs.flightsList.innerHTML = (obs.available_flights || []).map(f => `
<div class="card card-flight">
<strong>${f.id}</strong> ${f.is_partner ? "(Partner)" : ""}<br>
<small>T+${f.departure_hrs}h | E:${f.economy_seats} B:${f.business_seats}</small>
</div>
`).join("");
refs.latestResult.innerHTML = buildLatestSummary();
// Update Dropdowns
const passOpts = obs.pending_passengers.map(p => `<option value="${p.id}">${p.id}</option>`).join("");
refs.passengerId.innerHTML = '<option value="">Select Passenger</option>' + passOpts;
const flightOpts = obs.available_flights.map(f => `<option value="${f.id}">${f.id}</option>`).join("");
refs.flightId.innerHTML = '<option value="">Select Flight</option>' + 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 => `<option value="${t.task_key}">${t.task_key.toUpperCase()}</option>`).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();