- recipes/ai.py: Chat Completions with strict JSON schema (Structured Outputs), Pydantic re-validation, plausibility clamping, prompt treats input as data only - "Mit KI aufbereiten" on create/edit form returns an editable draft - Import page: photo/screenshot (vision, downscaled JPEG, not stored) and pasted text; drafts pre-fill the recipe form with a preview - Clear UI messages for timeout, rate limit, quota, auth, model and connection errors; without OPENAI_API_KEY AI features show a notice - Optional per-user and global daily AI limits (default unlimited) - Tests for response parsing, schema strictness, error mapping, views Co-Authored-By: Claude Opus 5.5 <[email protected]>
64 lines
2.2 KiB
JavaScript
64 lines
2.2 KiB
JavaScript
// Small progressive enhancements. Everything works without JavaScript.
|
|
document.addEventListener("click", async (event) => {
|
|
const button = event.target.closest("[data-copy]");
|
|
if (!button) return;
|
|
const input = document.querySelector(button.dataset.copy);
|
|
if (!input) return;
|
|
input.select();
|
|
try {
|
|
await navigator.clipboard.writeText(input.value);
|
|
button.textContent = "Kopiert ✓";
|
|
} catch {
|
|
button.textContent = "Bitte manuell kopieren";
|
|
}
|
|
});
|
|
|
|
// Serving calculator: recalculate as soon as the number changes.
|
|
document.addEventListener("change", (event) => {
|
|
const input = event.target.closest(".servings input[name=portionen]");
|
|
if (input && input.value) input.form.requestSubmit();
|
|
});
|
|
|
|
// Cooking mode: keep the screen on while the page is visible (Screen Wake Lock API).
|
|
(() => {
|
|
const page = document.querySelector("[data-wakelock]");
|
|
if (!page || !("wakeLock" in navigator)) return;
|
|
const status = page.querySelector(".wakelock-status");
|
|
let lock = null;
|
|
const acquire = async () => {
|
|
try {
|
|
lock = await navigator.wakeLock.request("screen");
|
|
if (status) status.hidden = false;
|
|
lock.addEventListener("release", () => { if (status) status.hidden = true; });
|
|
} catch {
|
|
// Not allowed right now (e.g. battery saver); the page still works.
|
|
}
|
|
};
|
|
document.addEventListener("visibilitychange", () => {
|
|
if (document.visibilityState === "visible") acquire();
|
|
});
|
|
acquire();
|
|
})();
|
|
|
|
// Long-running submits (AI): show progress on the clicked button and prevent double submits.
|
|
// Disabling happens after the browser has captured the form data (incl. the clicked button).
|
|
document.addEventListener("submit", (event) => {
|
|
const button = event.submitter;
|
|
if (!button || !button.dataset.busyText) return;
|
|
setTimeout(() => {
|
|
event.target.querySelectorAll("button[type=submit]").forEach((b) => {
|
|
b.dataset.label = b.dataset.label || b.textContent;
|
|
b.disabled = true;
|
|
});
|
|
button.textContent = button.dataset.busyText;
|
|
}, 0);
|
|
});
|
|
|
|
// Back/forward cache: restore buttons disabled by the handler above.
|
|
window.addEventListener("pageshow", () => {
|
|
document.querySelectorAll("button[data-label]").forEach((b) => {
|
|
b.disabled = false;
|
|
b.textContent = b.dataset.label;
|
|
});
|
|
});
|