Add admin LLM model setting; rename Pi to LLM in the UI; drop table Review column
Test and deploy / test-and-deploy (push) Successful in 49s

- New server/llm.js consolidates ModelRuntime + model selection: admin
  setting (app_settings.llm_model) > LLM_MODEL/PREFILL_MODEL env > first
  available; used by both prefill and roast evaluation
- GET/PUT /api/admin/llm lists configured models and stores the choice
  (validated against the list; empty = auto; audited); admin page gains
  an LLM section with a model picker
- All user-facing 'Pi agent' wording is now 'LLM'; no_model error message
  no longer references the pi CLI
- /roasts table: Review column removed (review lives in the detail view)

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Shane Maynard
2026-08-08 22:12:42 -04:00
co-authored by Claude Fable 5
parent eb82263ead
commit 5efaeb63c9
14 changed files with 344 additions and 87 deletions
+6 -28
View File
@@ -24,17 +24,10 @@ const GRADE_LABEL = {
"needs-work": "Needs work",
};
function reviewLabel(roast) {
if (roast.evaluationStatus === "pending") return "Reviewing…";
if (roast.evaluationStatus === "failed") return "Review failed";
if (roast.evaluationGrade) return GRADE_LABEL[roast.evaluationGrade] ?? roast.evaluationGrade;
return "—";
}
function renderTable() {
const body = document.getElementById("roasts-body");
if (!roasts.length) {
body.innerHTML = `<tr><td colspan="7" class="empty-state">No finished roasts uploaded yet. Upload an Artisan .alog above, or from the planner's Reference curve drawer to attach it to a plan.</td></tr>`;
body.innerHTML = `<tr><td colspan="6" class="empty-state">No finished roasts uploaded yet. Upload an Artisan .alog above, or from the planner's Reference curve drawer to attach it to a plan.</td></tr>`;
return;
}
body.replaceChildren(
@@ -66,22 +59,7 @@ function renderTable() {
lossCell.textContent =
roast.roast?.weightLossPct == null ? "—" : `${roast.roast.weightLossPct}%`;
const reviewCell = document.createElement("td");
const chip = document.createElement("span");
chip.textContent = reviewLabel(roast);
if (roast.evaluationStatus === "failed") chip.style.color = "#a8371a";
if (roast.evaluationStatus === "pending") chip.style.fontStyle = "italic";
reviewCell.append(chip);
if (roast.evaluationSummary) {
const summary = document.createElement("div");
summary.className = "muted";
summary.style.fontSize = "11.5px";
summary.style.maxWidth = "320px";
summary.textContent = roast.evaluationSummary;
reviewCell.append(summary);
}
tr.append(roastCell, planCell, fcCell, dropCell, dtrCell, lossCell, reviewCell);
tr.append(roastCell, planCell, fcCell, dropCell, dtrCell, lossCell);
tr.addEventListener("click", () => openDetail(roast.id));
return tr;
}),
@@ -100,7 +78,7 @@ async function loadRoasts() {
}
// While any review is still pending, refresh every few seconds so the table/detail fill in as
// the Pi agent finishes; stops by itself once nothing is pending.
// the LLM finishes; stops by itself once nothing is pending.
function schedulePoll() {
clearTimeout(pollTimer);
if (!roasts.some((roast) => roast.evaluationStatus === "pending")) return;
@@ -280,11 +258,11 @@ function evaluationList(title, items) {
function renderEvaluation(detail) {
const box = document.getElementById("detail-evaluation");
box.replaceChildren();
const heading = el("h3", {}, "Pi agent review");
const heading = el("h3", {}, "LLM review");
heading.style.margin = "0 0 6px";
box.append(heading);
if (detail.evaluationStatus === "pending") {
box.append(el("p", { className: "field-note" }, "The Pi agent is reviewing this roast — this page refreshes automatically."));
box.append(el("p", { className: "field-note" }, "The LLM is reviewing this roast — this page refreshes automatically."));
return;
}
if (detail.evaluationStatus === "failed") {
@@ -370,7 +348,7 @@ async function uploadFiles(files) {
}
}
status.textContent = uploaded
? `Uploaded ${uploaded} roast${uploaded === 1 ? "" : "s"} — the Pi agent review runs in the background.`
? `Uploaded ${uploaded} roast${uploaded === 1 ? "" : "s"} — the LLM review runs in the background.`
: "";
status.hidden = !status.textContent;
await loadRoasts();