Add brewing section, plan chat, roaster learning, API tokens, Swagger docs, and full backup
Test and deploy / test-and-deploy (push) Successful in 1m6s
Test and deploy / test-and-deploy (push) Successful in 1m6s
Brewing: - roasted_beans + brews tables; /beans (bean management with LLM URL prefill) and /brews (silhouette brewer picker across immersion/ percolation/espresso, recipe fields, auto ratio, 0-10 rating, tasting notes); bean remaining weight derived from logged brew doses - Green inventory lot form also prefills from a product URL Navigation/UX: - Side nav is now generated from one definition in nav.js, grouped Roasting / Brewing / account, consistent on every page LLM: - 'Ask the LLM' chat drawer on the planner (stateless /api/plan-chat) grounded in the plan, computed ledger, learned pace, and a new roaster-behavior profile aggregated from uploaded .alogs (/api/roaster-profile: TP lag, phase RoR, median milestone temps) - The profile also feeds roast reviews and the planner curve's fallback milestone temps API platform: - User-generated bearer tokens (rpt_…) with account-page management; token requests skip CSRF; hand-authored OpenAPI 3 spec at /api/openapi.json rendered by self-hosted Swagger UI at /api-docs - Full-database backup export/import (admin) + per-user data export Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
5efaeb63c9
commit
38c7d01e03
@@ -250,6 +250,38 @@ document.getElementById("lot-form").addEventListener("submit", (event) => {
|
||||
});
|
||||
});
|
||||
|
||||
// The LLM prefill endpoint returns raw `extracted` page facts — map the green-relevant ones
|
||||
// into the lot form (only overwriting what the page actually stated).
|
||||
document.getElementById("lot-prefill-btn").addEventListener("click", (event) =>
|
||||
guarded(event.currentTarget, async () => {
|
||||
const url = document.getElementById("lot-prefill-url").value.trim();
|
||||
const note = document.getElementById("lot-prefill-note");
|
||||
if (!url) return;
|
||||
note.classList.remove("hidden");
|
||||
note.textContent = "Reading the page with the LLM…";
|
||||
try {
|
||||
const body = await api("/api/prefill", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ url }),
|
||||
});
|
||||
const x = body.extracted ?? {};
|
||||
const form = document.getElementById("lot-form");
|
||||
if (x.origin) form.origin.value = x.origin;
|
||||
if (x.cultivar) form.variety.value = x.cultivar;
|
||||
if (x.process) form.process.value = x.process;
|
||||
if (x.producer) form.producer.value = x.producer;
|
||||
if (x.moisturePct) form.moisturePct.value = x.moisturePct;
|
||||
const filled = ["origin", "cultivar", "process", "producer", "moisturePct"]
|
||||
.filter((k) => x[k]).length;
|
||||
note.textContent = filled
|
||||
? `Prefilled ${filled} field${filled === 1 ? "" : "s"} — check them, then save.`
|
||||
: "The page didn't state anything usable — fill the form by hand.";
|
||||
} catch (error) {
|
||||
note.textContent = `Prefill failed: ${error.message}`;
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
document.getElementById("btn-logout").addEventListener("click", async () => {
|
||||
await protectedFetch("/api/auth/logout", { method: "POST" });
|
||||
location.assign("/");
|
||||
|
||||
Reference in New Issue
Block a user