fix(recipes): steps shown as raw JSON when AI uses instruction/appliance_function objects

- _stepStr: parse JSON-string steps; handle s.instruction key (backward-compat with already-saved recipes)
- _stepAppliance: new helper to extract appliance_function hint; returns null for 'Nessuno'/'None'
- renderRecipe steps list: shows appliance badge inline after step text when present
- CSS: .recipe-step-appliance badge (green chip, dark-mode variant)
- Prompt (both generateRecipe + generateRecipeStream): rule 9/10 explicitly forbids step objects;
  appliance info must be embedded in the step text string directly
This commit is contained in:
dadaloop82
2026-05-25 10:01:10 +00:00
parent eddb622c85
commit 52afdd6bfa
3 changed files with 42 additions and 5 deletions
+25 -4
View File
@@ -13273,7 +13273,8 @@ function renderRecipe(r) {
// Steps
html += `<h3>${t('recipes.steps_title')}</h3><ol>`;
(r.steps || []).forEach(step => {
html += `<li>${_stepStr(step)}</li>`;
const appliance = _stepAppliance(step);
html += `<li>${_stepStr(step)}${appliance ? ` <span class="recipe-step-appliance">${appliance}</span>` : ''}</li>`;
});
html += '</ol>';
@@ -13291,9 +13292,29 @@ let _cookingStep = 0;
let _cookingTTS = true;
let _cookingVisited = new Set(); // indices of steps already seen
// Safely extract step text regardless of whether it's a string or an object
// (Gemini sometimes returns [{text:"..."}, ...] instead of ["...", ...])
const _stepStr = s => String((s !== null && typeof s === 'object') ? (s.text ?? s.description ?? s.step ?? '') : (s ?? '')).replace(/^Passo\s*\d+\s*[:.]\s*/i, '');
// Safely extract step text regardless of whether it's a string or an object.
// Also handles JSON-encoded step objects emitted by older AI generations
// (e.g. {"instruction":"…","appliance_function":"…"}).
const _stepStr = s => {
if (typeof s === 'string' && s.trimStart().startsWith('{')) {
try { s = JSON.parse(s); } catch(e) {}
}
const text = (s !== null && typeof s === 'object')
? (s.instruction ?? s.text ?? s.description ?? s.step ?? '')
: (s ?? '');
return String(text).replace(/^Passo\s*\d+\s*[:.]\s*/i, '').replace(/^Step\s*\d+\s*[:.]\s*/i, '');
};
// Returns the appliance/function hint for a step, or null if absent/Nessuno.
const _stepAppliance = s => {
if (typeof s === 'string' && s.trimStart().startsWith('{')) {
try { s = JSON.parse(s); } catch(e) {}
}
if (s !== null && typeof s === 'object' && s.appliance_function) {
const a = s.appliance_function.trim();
if (a && a.toLowerCase() !== 'nessuno' && a.toLowerCase() !== 'none') return a;
}
return null;
};
let _cookingWheelBound = false;
let _cookingWheelTouchStartY = null;