Build coffee history and experiment workflows with Ask Bean Bro
Test and deploy / test-and-deploy (push) Successful in 1m21s
Test and deploy / test-and-deploy (push) Successful in 1m21s
This commit is contained in:
1 parent
1fd53091ac
commit
8c21750356
28 files changed
+3848
-384
No files matched your search
+25
-12
@@ -125,7 +125,7 @@ async function loadRoasts() {
|
||||
}
|
||||
|
||||
// While any review is still pending, refresh every few seconds so the table/detail fill in as
|
||||
// the LLM finishes; stops by itself once nothing is pending.
|
||||
// Bean Bro finishes; stops by itself once nothing is pending.
|
||||
function schedulePoll() {
|
||||
clearTimeout(pollTimer);
|
||||
if (!roasts.some((roast) => roast.evaluationStatus === "pending")) return;
|
||||
@@ -419,7 +419,7 @@ function evaluationList(title, items) {
|
||||
function renderEvaluation(detail) {
|
||||
const box = document.getElementById("detail-evaluation");
|
||||
box.replaceChildren();
|
||||
const heading = el("h3", {}, "LLM review");
|
||||
const heading = el("h3", {}, "Bean Bro review");
|
||||
heading.style.margin = "0 0 6px";
|
||||
box.append(heading);
|
||||
if (detail.evaluationStatus === "pending") {
|
||||
@@ -427,7 +427,7 @@ function renderEvaluation(detail) {
|
||||
el(
|
||||
"p",
|
||||
{ className: "field-note" },
|
||||
"The LLM is reviewing this roast — this page refreshes automatically.",
|
||||
"Bean Bro is reviewing this roast — this page refreshes automatically.",
|
||||
),
|
||||
);
|
||||
return;
|
||||
@@ -448,7 +448,7 @@ function renderEvaluation(detail) {
|
||||
el(
|
||||
"p",
|
||||
{ className: "field-note" },
|
||||
"No review recorded for this roast.",
|
||||
detail.roast?.manual ? "Manual milestones recorded. Add tasting feedback below; these points do not provide a continuous RoR trace." : "No review recorded for this roast.",
|
||||
),
|
||||
);
|
||||
return;
|
||||
@@ -532,6 +532,12 @@ async function openDetail(id, { keepScroll = false } = {}) {
|
||||
}),
|
||||
);
|
||||
planSelect.value = detail.roastPlanId ?? "";
|
||||
if (!detail.roastPlanId) {
|
||||
const words=String(detail.roast?.title||detail.filename).toLowerCase().split(/[^a-z0-9]+/).filter(w=>w.length>3);
|
||||
const ranked=planList.map(p=>({p,score:words.filter(w=>String(p.coffee||p.title).toLowerCase().includes(w)).length})).filter(r=>r.score>=2).sort((a,b)=>b.score-a.score);
|
||||
if(ranked.length){const suggested=ranked[0].p;const button=el("button",{type:"button",className:"ghost-btn small"},`Suggested match: ${suggested.title} — attach`);button.addEventListener("click",()=>{planSelect.value=suggested.id;planSelect.dispatchEvent(new Event("change"));});planWrap.append(button);}
|
||||
}
|
||||
|
||||
planSelect.addEventListener("change", async () => {
|
||||
try {
|
||||
await api(`/api/roasts/${detail.id}`, {
|
||||
@@ -635,6 +641,8 @@ async function openDetail(id, { keepScroll = false } = {}) {
|
||||
}
|
||||
document.getElementById("detail-download").href =
|
||||
`/api/roasts/${encodeURIComponent(id)}/download`;
|
||||
document.getElementById("detail-download-updated").hidden=Boolean(detail.roast?.manual);
|
||||
document.getElementById("detail-download").textContent=detail.roast?.manual?"Download manual record":"Download original .alog";
|
||||
document.getElementById("detail-download-updated").href =
|
||||
`/api/roasts/${encodeURIComponent(id)}/download?variant=updated`;
|
||||
// The 4s pending-evaluation poll re-runs openDetail; never let that background refresh
|
||||
@@ -665,7 +673,7 @@ let comparisonDirty = false;
|
||||
function renderComparison(detail) {
|
||||
comparisonDirty = false;
|
||||
document.getElementById("comparison-plan-note").textContent = detail.plan
|
||||
? "* Illustrative plan temperature; actual readings may use a different probe reference."
|
||||
? `${detail.hasPlanSnapshot?"Saved target snapshot":"Current linked plan (no historical snapshot)"}. * Illustrative temperature; actual readings may use a different probe reference.`
|
||||
: "Attach a plan above to see its targets alongside your observations.";
|
||||
const rows = buildComparison(detail);
|
||||
document.getElementById("comparison-body").replaceChildren(
|
||||
@@ -762,7 +770,7 @@ document
|
||||
|
||||
// ---- after the roast (moved here from the plan worksheet) -------------------
|
||||
|
||||
const AFTER_FIELDS = [
|
||||
const AFTER_FIELDS = ["tasteBalance", "sweetness", "favorite", "experimentResult",
|
||||
"greenIn",
|
||||
"out",
|
||||
"weightLossPct",
|
||||
@@ -786,6 +794,7 @@ document.getElementById("after-form").addEventListener("input", () => {
|
||||
function renderAfterForm(detail) {
|
||||
const form = document.getElementById("after-form");
|
||||
const after = detail.after ?? {};
|
||||
document.getElementById("experiment-expectation").textContent = detail.plan?.experiment?.expected ? "Expected result: " + detail.plan.experiment.expected : "Record one change and what you expect to taste in the plan’s experiment section.";
|
||||
// Prefill measurable values straight from the .alog so the user only types what the
|
||||
// machine couldn't know (colour, cup notes, the one change).
|
||||
const auto = {
|
||||
@@ -808,18 +817,20 @@ document
|
||||
const after = {};
|
||||
for (const field of AFTER_FIELDS) after[field] = form[field].value;
|
||||
const button = document.getElementById("after-save");
|
||||
button.disabled = true;
|
||||
if(button.disabled)return;
|
||||
const submitButtons=[...form.querySelectorAll('button[type="submit"]')];
|
||||
submitButtons.forEach(b=>b.disabled=true);
|
||||
try {
|
||||
await api(`/api/roasts/${openId}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ after }),
|
||||
});
|
||||
afterFormDirty = false;
|
||||
showToast("After-roast saved — included in the updated .alog.");
|
||||
showToast("Tasting and roast notes saved.");
|
||||
} catch (error) {
|
||||
showToast(error.message, "fail");
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
submitButtons.forEach(b=>b.disabled=false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -879,27 +890,29 @@ function closeDetail() {
|
||||
async function uploadFiles(files) {
|
||||
const status = document.getElementById("upload-status");
|
||||
status.hidden = false;
|
||||
let uploaded = 0;
|
||||
let uploaded = 0, newestId = null;
|
||||
for (const file of files) {
|
||||
status.textContent = `Uploading ${file.name}…`;
|
||||
try {
|
||||
await api("/api/roasts", {
|
||||
const uploadedRoast = await api("/api/roasts", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
filename: file.name,
|
||||
content: await file.text(),
|
||||
}),
|
||||
});
|
||||
newestId = uploadedRoast.roast.id;
|
||||
uploaded++;
|
||||
} catch (error) {
|
||||
showToast(`${file.name}: ${error.message}`, "fail");
|
||||
}
|
||||
}
|
||||
status.textContent = uploaded
|
||||
? `Uploaded ${uploaded} roast${uploaded === 1 ? "" : "s"} — the LLM review runs in the background.`
|
||||
? `Uploaded ${uploaded} roast${uploaded === 1 ? "" : "s"} — Bean Bro review runs in the background.`
|
||||
: "";
|
||||
status.hidden = !status.textContent;
|
||||
await loadRoasts();
|
||||
if (newestId) await openDetail(newestId);
|
||||
}
|
||||
|
||||
document
|
||||
|
||||
Reference in new issue
Block a user