// Ricetta visione del Maker (#46): componente Alpine per i task camera_measure / dxf_compare. // Carica la ricetta VisionSuite v2 + i file allegati verso /maker/api/vision/tasks//recipe, // mostra cartella, file e uscite numeriche; notifica le uscite al task editor con un evento. // Uso: x-data="visionRecipe(task.id, csrfToken, msgs)". Dipende da Alpine.js. function visionRecipe(taskId, csrf, msgs) { return { taskId: taskId, recipeDir: null, files: [], outputs: [], errors: [], // errori 422 elencati in chiaro loading: false, busy: false, recipeFile: null, attachments: [], msgs: msgs || {}, async init() { // All'apertura: ricetta corrente (GET); il 404/errore non blocca l'editor this.loading = true; try { const resp = await fetch(`/maker/api/vision/tasks/${this.taskId}/recipe`); if (resp.ok) this._apply(await resp.json()); } catch (e) { console.error('visionRecipe init', e); } this.loading = false; }, // Aggiorna lo stato e avvisa il task editor (select vision_output delle quote) _apply(data) { this.recipeDir = data.recipe_dir || null; this.files = data.files || []; this.outputs = data.outputs || []; window.dispatchEvent(new CustomEvent('vision-outputs', { detail: { taskId: this.taskId, outputs: this.outputs } })); }, // Il 422 porta `errors` (lista) oppure `detail` (stringa o lista di voci) _errorsFrom(data) { if (Array.isArray(data.errors) && data.errors.length) return data.errors.map(String); const d = data.detail; if (Array.isArray(d)) return d.map(x => (typeof x === 'string' ? x : (x.msg || JSON.stringify(x)))); if (d && typeof d === 'object') { if (Array.isArray(d.errors)) return d.errors.map(String); return [JSON.stringify(d)]; } return [d || this.msgs.genericError || 'Error']; }, async upload() { if (!this.recipeFile) return; this.busy = true; this.errors = []; try { const fd = new FormData(); fd.append('recipe', this.recipeFile); for (const f of this.attachments) fd.append('files', f); const resp = await fetch(`/maker/api/vision/tasks/${this.taskId}/recipe`, { method: 'POST', headers: { 'X-CSRFToken': typeof csrf === 'function' ? csrf() : csrf }, body: fd }); const data = await resp.json(); if (!resp.ok || data.error) { this.errors = this._errorsFrom(data); } else { this._apply(data); this.recipeFile = null; this.attachments = []; } } catch (e) { this.errors = [this.msgs.genericError || String(e)]; } this.busy = false; }, onRecipe(ev) { this.recipeFile = ev.target.files[0] || null; }, onAttachments(ev) { this.attachments = Array.from(ev.target.files || []); } }; }