ux improved
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { llmNluV3 } from "./openai.js";
|
||||
import { retrieveCandidates } from "./catalogRetrieval.js";
|
||||
import { safeNextState } from "./fsm.js";
|
||||
import { handleRecommend } from "./recommendations.js";
|
||||
|
||||
function unitAskFor(displayUnit) {
|
||||
if (displayUnit === "unit") return "¿Cuántas unidades querés?";
|
||||
@@ -19,6 +20,12 @@ function inferDefaultUnit({ name, categories }) {
|
||||
const cats = Array.isArray(categories) ? categories : [];
|
||||
const hay = (re) =>
|
||||
cats.some((c) => re.test(String(c?.name || "")) || re.test(String(c?.slug || ""))) || re.test(n);
|
||||
if (hay(/\b(chimichurri|provoleta|queso|pan|salsa|aderezo|condimento|especia|especias)\b/i)) {
|
||||
return "unit";
|
||||
}
|
||||
if (hay(/\b(proveedur[ií]a|almac[eé]n|almacen|sal\s+pimienta|aderezos)\b/i)) {
|
||||
return "unit";
|
||||
}
|
||||
if (hay(/\b(vino|vinos|bebida|bebidas|cerveza|cervezas|gaseosa|gaseosas|whisky|ron|gin|vodka|fernet)\b/i)) {
|
||||
return "unit";
|
||||
}
|
||||
@@ -165,6 +172,25 @@ function resolveQuantity({ quantity, unit, displayUnit }) {
|
||||
|
||||
function buildPendingItemFromCandidate(candidate) {
|
||||
const displayUnit = inferDefaultUnit({ name: candidate.name, categories: candidate.categories });
|
||||
// #region agent log
|
||||
fetch("http://127.0.0.1:7242/ingest/86c7b1cd-c414-4eae-852c-08e57e562b3b", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
sessionId: "debug-session",
|
||||
runId: "pre-fix",
|
||||
hypothesisId: "H14",
|
||||
location: "turnEngineV3.js:171",
|
||||
message: "pending_item_display_unit",
|
||||
data: {
|
||||
name: candidate?.name || null,
|
||||
categories: Array.isArray(candidate?.categories) ? candidate.categories.map((c) => c?.name || c) : [],
|
||||
display_unit: displayUnit,
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
}),
|
||||
}).catch(() => {});
|
||||
// #endregion
|
||||
return {
|
||||
product_id: Number(candidate.woo_product_id),
|
||||
variation_id: null,
|
||||
@@ -192,6 +218,173 @@ function hasAddress(ctx) {
|
||||
return Boolean(ctx?.delivery_address?.text || ctx?.address?.text || ctx?.address_text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Procesa múltiples items mencionados en un solo mensaje.
|
||||
* Para cada item: busca en catálogo, si hay match fuerte + cantidad -> agrega al carrito.
|
||||
* Si hay ambigüedad, para y crea pending_clarification para el primer item ambiguo.
|
||||
*/
|
||||
async function processMultiItems({
|
||||
tenantId,
|
||||
items,
|
||||
prev_state,
|
||||
prev_context,
|
||||
audit,
|
||||
}) {
|
||||
const prev = prev_context && typeof prev_context === "object" ? prev_context : {};
|
||||
const actions = [];
|
||||
const context_patch = {};
|
||||
const addedItems = [];
|
||||
const addedLabels = [];
|
||||
let prevItems = Array.isArray(prev?.order_basket?.items) ? [...prev.order_basket.items] : [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
const { candidates, audit: catAudit } = await retrieveCandidates({
|
||||
tenantId,
|
||||
query: item.product_query,
|
||||
limit: 12,
|
||||
});
|
||||
audit.catalog_multi = audit.catalog_multi || [];
|
||||
audit.catalog_multi.push({ query: item.product_query, count: candidates?.length || 0 });
|
||||
|
||||
if (!candidates.length) {
|
||||
// No encontrado, seguimos con los demás
|
||||
continue;
|
||||
}
|
||||
|
||||
const best = candidates[0];
|
||||
const second = candidates[1];
|
||||
const strong = candidates.length === 1 || (best?._score >= 0.9 && (!second || best._score - (second?._score || 0) >= 0.2));
|
||||
|
||||
if (!strong) {
|
||||
// Ambigüedad: crear pending_clarification para este item y guardar los restantes
|
||||
const { question, pending } = buildPagedOptions({ candidates });
|
||||
context_patch.pending_clarification = pending;
|
||||
context_patch.pending_item = null;
|
||||
// Guardar cantidad pendiente para este item
|
||||
if (item.quantity != null) {
|
||||
context_patch.pending_quantity = item.quantity;
|
||||
context_patch.pending_unit = item.unit;
|
||||
}
|
||||
// Guardar items restantes para procesar después
|
||||
const remainingItems = items.slice(i + 1);
|
||||
if (remainingItems.length > 0) {
|
||||
context_patch.pending_multi_items = remainingItems;
|
||||
}
|
||||
actions.push({ type: "show_options", payload: { count: pending.options?.length || 0 } });
|
||||
|
||||
// Si ya agregamos algunos items, incluirlos en el contexto
|
||||
if (addedItems.length > 0) {
|
||||
context_patch.order_basket = { items: prevItems };
|
||||
}
|
||||
|
||||
const { next_state, validation: v } = safeNextState(prev_state, { ...prev, ...context_patch }, { did_show_options: true });
|
||||
|
||||
let reply = question;
|
||||
if (addedLabels.length > 0) {
|
||||
reply = `Anoté ${addedLabels.join(", ")}. Ahora, para "${item.product_query}":\n\n${question}`;
|
||||
}
|
||||
|
||||
return {
|
||||
plan: {
|
||||
reply,
|
||||
next_state,
|
||||
intent: "add_to_cart",
|
||||
missing_fields: ["product_selection"],
|
||||
order_action: "none",
|
||||
basket_resolved: { items: addedItems },
|
||||
},
|
||||
decision: { actions, context_patch, audit: { ...audit, fsm: v } },
|
||||
};
|
||||
}
|
||||
|
||||
// Match fuerte, verificar cantidad
|
||||
const pendingItem = buildPendingItemFromCandidate(best);
|
||||
const qty = resolveQuantity({
|
||||
quantity: item.quantity,
|
||||
unit: item.unit,
|
||||
displayUnit: pendingItem.display_unit,
|
||||
});
|
||||
|
||||
if (!qty?.quantity) {
|
||||
// Sin cantidad: crear pending_item para este y guardar restantes
|
||||
context_patch.pending_item = pendingItem;
|
||||
const remainingItems = items.slice(i + 1);
|
||||
if (remainingItems.length > 0) {
|
||||
context_patch.pending_multi_items = remainingItems;
|
||||
}
|
||||
if (addedItems.length > 0) {
|
||||
context_patch.order_basket = { items: prevItems };
|
||||
}
|
||||
|
||||
const { next_state, validation: v } = safeNextState(prev_state, { ...prev, ...context_patch }, {});
|
||||
|
||||
let reply = unitAskFor(pendingItem.display_unit === "unit" ? "unit" : "kg");
|
||||
if (addedLabels.length > 0) {
|
||||
reply = `Anoté ${addedLabels.join(", ")}. Para ${pendingItem.name}, ${reply.toLowerCase()}`;
|
||||
}
|
||||
|
||||
return {
|
||||
plan: {
|
||||
reply,
|
||||
next_state,
|
||||
intent: "add_to_cart",
|
||||
missing_fields: ["quantity"],
|
||||
order_action: "none",
|
||||
basket_resolved: { items: addedItems },
|
||||
},
|
||||
decision: { actions, context_patch, audit: { ...audit, fsm: v } },
|
||||
};
|
||||
}
|
||||
|
||||
// Todo completo: agregar al carrito
|
||||
const cartItem = {
|
||||
product_id: pendingItem.product_id,
|
||||
variation_id: pendingItem.variation_id,
|
||||
quantity: qty.quantity,
|
||||
unit: qty.unit,
|
||||
label: pendingItem.name,
|
||||
};
|
||||
prevItems.push(cartItem);
|
||||
addedItems.push(cartItem);
|
||||
actions.push({ type: "add_to_cart", payload: cartItem });
|
||||
|
||||
const display = qty.display_unit === "kg"
|
||||
? `${qty.display_quantity}kg de ${pendingItem.name}`
|
||||
: qty.display_unit === "unit"
|
||||
? `${qty.display_quantity} ${pendingItem.name}`
|
||||
: `${qty.display_quantity}g de ${pendingItem.name}`;
|
||||
addedLabels.push(display);
|
||||
}
|
||||
|
||||
// Todos los items procesados exitosamente
|
||||
if (addedItems.length > 0) {
|
||||
context_patch.order_basket = { items: prevItems };
|
||||
context_patch.pending_item = null;
|
||||
context_patch.pending_clarification = null;
|
||||
context_patch.pending_quantity = null;
|
||||
context_patch.pending_unit = null;
|
||||
context_patch.pending_multi_items = null;
|
||||
|
||||
const { next_state, validation: v } = safeNextState(prev_state, { ...prev, ...context_patch }, { pending_item_completed: true });
|
||||
|
||||
return {
|
||||
plan: {
|
||||
reply: `Perfecto, anoto ${addedLabels.join(" y ")}. ¿Algo más?`,
|
||||
next_state,
|
||||
intent: "add_to_cart",
|
||||
missing_fields: [],
|
||||
order_action: "none",
|
||||
basket_resolved: { items: addedItems },
|
||||
},
|
||||
decision: { actions, context_patch, audit: { ...audit, fsm: v } },
|
||||
};
|
||||
}
|
||||
|
||||
// Ningún item encontrado
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function runTurnV3({
|
||||
tenantId,
|
||||
chat_id,
|
||||
@@ -206,6 +399,12 @@ export async function runTurnV3({
|
||||
const context_patch = {};
|
||||
const audit = {};
|
||||
|
||||
// Observabilidad (NO se envía al LLM)
|
||||
audit.trace = {
|
||||
tenantId: tenantId || null,
|
||||
chat_id: chat_id || null,
|
||||
};
|
||||
|
||||
const last_shown_options = Array.isArray(prev?.pending_clarification?.options)
|
||||
? prev.pending_clarification.options.map((o) => ({ idx: o.idx, type: o.type, name: o.name, woo_product_id: o.woo_product_id || null }))
|
||||
: [];
|
||||
@@ -221,13 +420,100 @@ export async function runTurnV3({
|
||||
last_shown_options,
|
||||
locale: tenant_config?.locale || "es-AR",
|
||||
};
|
||||
// #region agent log
|
||||
fetch("http://127.0.0.1:7242/ingest/86c7b1cd-c414-4eae-852c-08e57e562b3b", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
sessionId: "debug-session",
|
||||
runId: "pre-fix",
|
||||
hypothesisId: "H6",
|
||||
location: "turnEngineV3.js:231",
|
||||
message: "nlu_input_built",
|
||||
data: {
|
||||
text_len: String(nluInput.last_user_message || "").length,
|
||||
state: nluInput.conversation_state || null,
|
||||
memory_len: String(nluInput.memory_summary || "").length,
|
||||
pending_clarification: Boolean(nluInput.pending_context?.pending_clarification),
|
||||
pending_item: Boolean(nluInput.pending_context?.pending_item),
|
||||
last_shown_options: Array.isArray(nluInput.last_shown_options)
|
||||
? nluInput.last_shown_options.length
|
||||
: null,
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
}),
|
||||
}).catch(() => {});
|
||||
// #endregion
|
||||
|
||||
const { nlu, raw_text, model, usage, validation } = await llmNluV3({ input: nluInput });
|
||||
audit.nlu = { raw_text, model, usage, validation, parsed: nlu };
|
||||
// #region agent log
|
||||
fetch("http://127.0.0.1:7242/ingest/86c7b1cd-c414-4eae-852c-08e57e562b3b", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
sessionId: "debug-session",
|
||||
runId: "pre-fix",
|
||||
hypothesisId: "H5",
|
||||
location: "turnEngineV3.js:235",
|
||||
message: "nlu_result",
|
||||
data: {
|
||||
intent: nlu?.intent || null,
|
||||
needsCatalog: Boolean(nlu?.needs?.catalog_lookup),
|
||||
has_pending_clarification: Boolean(prev?.pending_clarification?.candidates?.length),
|
||||
has_pending_item: Boolean(prev?.pending_item?.product_id),
|
||||
nlu_valid: validation?.ok ?? null,
|
||||
raw_len: typeof raw_text === "string" ? raw_text.length : null,
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
}),
|
||||
}).catch(() => {});
|
||||
// #endregion
|
||||
|
||||
// 0) Procesar multi-items si hay varios productos en un mensaje
|
||||
// Solo si no hay pending_clarification ni pending_item (flujo limpio)
|
||||
if (
|
||||
Array.isArray(nlu?.entities?.items) &&
|
||||
nlu.entities.items.length > 0 &&
|
||||
!prev?.pending_clarification?.candidates?.length &&
|
||||
!prev?.pending_item?.product_id
|
||||
) {
|
||||
const multiResult = await processMultiItems({
|
||||
tenantId,
|
||||
items: nlu.entities.items,
|
||||
prev_state,
|
||||
prev_context: prev,
|
||||
audit,
|
||||
});
|
||||
if (multiResult) {
|
||||
return multiResult;
|
||||
}
|
||||
// Si multiResult es null, ningún item fue encontrado, seguir con flujo normal
|
||||
}
|
||||
|
||||
// 1) Resolver pending_clarification primero
|
||||
if (prev?.pending_clarification?.candidates?.length) {
|
||||
const resolved = resolvePendingSelection({ text, nlu, pending: prev.pending_clarification });
|
||||
// #region agent log
|
||||
fetch("http://127.0.0.1:7242/ingest/86c7b1cd-c414-4eae-852c-08e57e562b3b", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
sessionId: "debug-session",
|
||||
runId: "pre-fix",
|
||||
hypothesisId: "H12",
|
||||
location: "turnEngineV3.js:239",
|
||||
message: "pending_clarification_resolved",
|
||||
data: {
|
||||
kind: resolved?.kind || null,
|
||||
selection_type: nlu?.entities?.selection?.type || null,
|
||||
selection_value: nlu?.entities?.selection?.value || null,
|
||||
text_len: String(text || "").length,
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
}),
|
||||
}).catch(() => {});
|
||||
// #endregion
|
||||
if (resolved.kind === "more") {
|
||||
const nextPending = resolved.pending || prev.pending_clarification;
|
||||
const reply = resolved.question || buildPagedOptions({ candidates: nextPending.candidates }).question;
|
||||
@@ -249,9 +535,10 @@ export async function runTurnV3({
|
||||
}
|
||||
if (resolved.kind === "chosen" && resolved.chosen) {
|
||||
const pendingItem = buildPendingItemFromCandidate(resolved.chosen);
|
||||
// Usar cantidad guardada como fallback si el NLU actual no la tiene
|
||||
const qty = resolveQuantity({
|
||||
quantity: nlu?.entities?.quantity,
|
||||
unit: nlu?.entities?.unit,
|
||||
quantity: nlu?.entities?.quantity ?? prev?.pending_quantity,
|
||||
unit: nlu?.entities?.unit ?? prev?.pending_unit,
|
||||
displayUnit: pendingItem.display_unit,
|
||||
});
|
||||
if (qty?.quantity) {
|
||||
@@ -266,7 +553,34 @@ export async function runTurnV3({
|
||||
context_patch.order_basket = { items: [...prevItems, item] };
|
||||
context_patch.pending_item = null;
|
||||
context_patch.pending_clarification = null;
|
||||
context_patch.pending_quantity = null;
|
||||
context_patch.pending_unit = null;
|
||||
actions.push({ type: "add_to_cart", payload: item });
|
||||
|
||||
// Procesar pending_multi_items si hay
|
||||
const pendingMulti = Array.isArray(prev?.pending_multi_items) ? prev.pending_multi_items : [];
|
||||
if (pendingMulti.length > 0) {
|
||||
context_patch.pending_multi_items = null;
|
||||
const multiResult = await processMultiItems({
|
||||
tenantId,
|
||||
items: pendingMulti,
|
||||
prev_state,
|
||||
prev_context: { ...prev, ...context_patch },
|
||||
audit,
|
||||
});
|
||||
if (multiResult) {
|
||||
// Combinar resultados
|
||||
const display = qty.display_unit === "kg"
|
||||
? `${qty.display_quantity}kg de ${pendingItem.name}`
|
||||
: qty.display_unit === "unit"
|
||||
? `${qty.display_quantity} ${pendingItem.name}`
|
||||
: `${qty.display_quantity}g de ${pendingItem.name}`;
|
||||
multiResult.plan.reply = `Anoté ${display}. ${multiResult.plan.reply}`;
|
||||
multiResult.decision.actions = [...actions, ...multiResult.decision.actions];
|
||||
return multiResult;
|
||||
}
|
||||
}
|
||||
|
||||
const { next_state, validation: v } = safeNextState(prev_state, { ...prev, ...context_patch }, { pending_item_completed: true });
|
||||
const display = qty.display_unit === "kg"
|
||||
? `${qty.display_quantity}kg`
|
||||
@@ -287,6 +601,7 @@ export async function runTurnV3({
|
||||
}
|
||||
context_patch.pending_item = pendingItem;
|
||||
context_patch.pending_clarification = null;
|
||||
// Preservar pending_quantity si había, se usará cuando el usuario dé cantidad
|
||||
const { next_state, validation: v } = safeNextState(prev_state, { ...prev, ...context_patch }, {});
|
||||
return {
|
||||
plan: {
|
||||
@@ -320,11 +635,32 @@ export async function runTurnV3({
|
||||
// 2) Si hay pending_item, esperamos cantidad
|
||||
if (prev?.pending_item?.product_id) {
|
||||
const pendingItem = prev.pending_item;
|
||||
// Usar cantidad guardada como fallback si el NLU actual no la tiene
|
||||
const qty = resolveQuantity({
|
||||
quantity: nlu?.entities?.quantity,
|
||||
unit: nlu?.entities?.unit,
|
||||
quantity: nlu?.entities?.quantity ?? prev?.pending_quantity,
|
||||
unit: nlu?.entities?.unit ?? prev?.pending_unit,
|
||||
displayUnit: pendingItem.display_unit || "kg",
|
||||
});
|
||||
// #region agent log
|
||||
fetch("http://127.0.0.1:7242/ingest/86c7b1cd-c414-4eae-852c-08e57e562b3b", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
sessionId: "debug-session",
|
||||
runId: "pre-fix",
|
||||
hypothesisId: "H12",
|
||||
location: "turnEngineV3.js:332",
|
||||
message: "pending_item_quantity",
|
||||
data: {
|
||||
quantity_in: nlu?.entities?.quantity ?? null,
|
||||
unit_in: nlu?.entities?.unit ?? null,
|
||||
qty_resolved: qty?.quantity ?? null,
|
||||
text: String(text || "").slice(0, 20),
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
}),
|
||||
}).catch(() => {});
|
||||
// #endregion
|
||||
if (qty?.quantity) {
|
||||
const item = {
|
||||
product_id: Number(pendingItem.product_id),
|
||||
@@ -336,7 +672,34 @@ export async function runTurnV3({
|
||||
const prevItems = Array.isArray(prev?.order_basket?.items) ? prev.order_basket.items : [];
|
||||
context_patch.order_basket = { items: [...prevItems, item] };
|
||||
context_patch.pending_item = null;
|
||||
context_patch.pending_quantity = null;
|
||||
context_patch.pending_unit = null;
|
||||
actions.push({ type: "add_to_cart", payload: item });
|
||||
|
||||
// Procesar pending_multi_items si hay
|
||||
const pendingMulti = Array.isArray(prev?.pending_multi_items) ? prev.pending_multi_items : [];
|
||||
if (pendingMulti.length > 0) {
|
||||
context_patch.pending_multi_items = null;
|
||||
const multiResult = await processMultiItems({
|
||||
tenantId,
|
||||
items: pendingMulti,
|
||||
prev_state,
|
||||
prev_context: { ...prev, ...context_patch },
|
||||
audit,
|
||||
});
|
||||
if (multiResult) {
|
||||
// Combinar resultados
|
||||
const display = qty.display_unit === "kg"
|
||||
? `${qty.display_quantity}kg de ${item.label}`
|
||||
: qty.display_unit === "unit"
|
||||
? `${qty.display_quantity} ${item.label}`
|
||||
: `${qty.display_quantity}g de ${item.label}`;
|
||||
multiResult.plan.reply = `Anoté ${display}. ${multiResult.plan.reply}`;
|
||||
multiResult.decision.actions = [...actions, ...multiResult.decision.actions];
|
||||
return multiResult;
|
||||
}
|
||||
}
|
||||
|
||||
const { next_state, validation: v } = safeNextState(prev_state, { ...prev, ...context_patch }, { pending_item_completed: true });
|
||||
const display = qty.display_unit === "kg"
|
||||
? `${qty.display_quantity}kg`
|
||||
@@ -371,8 +734,71 @@ export async function runTurnV3({
|
||||
|
||||
// 3) Intento normal
|
||||
const intent = nlu?.intent || "other";
|
||||
const productQuery = String(nlu?.entities?.product_query || "").trim();
|
||||
let productQuery = String(nlu?.entities?.product_query || "").trim();
|
||||
const needsCatalog = Boolean(nlu?.needs?.catalog_lookup);
|
||||
const lastBasketItem = Array.isArray(prev?.order_basket?.items) ? prev.order_basket.items.slice(-1)[0] : null;
|
||||
const fallbackQuery =
|
||||
!productQuery && intent === "browse"
|
||||
? (prev?.pending_item?.name || lastBasketItem?.label || lastBasketItem?.name || null)
|
||||
: null;
|
||||
if (fallbackQuery) {
|
||||
productQuery = String(fallbackQuery).trim();
|
||||
// #region agent log
|
||||
fetch("http://127.0.0.1:7242/ingest/86c7b1cd-c414-4eae-852c-08e57e562b3b", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
sessionId: "debug-session",
|
||||
runId: "pre-fix",
|
||||
hypothesisId: "H13",
|
||||
location: "turnEngineV3.js:390",
|
||||
message: "browse_fallback_query",
|
||||
data: {
|
||||
fallback: productQuery,
|
||||
has_basket: Boolean(lastBasketItem),
|
||||
has_pending_item: Boolean(prev?.pending_item?.name),
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
}),
|
||||
}).catch(() => {});
|
||||
// #endregion
|
||||
}
|
||||
|
||||
if (intent === "recommend") {
|
||||
const basketItems = Array.isArray(prev?.order_basket?.items) ? prev.order_basket.items : [];
|
||||
const rec = await handleRecommend({
|
||||
tenantId,
|
||||
text,
|
||||
prev_context: prev,
|
||||
basket_items: basketItems,
|
||||
});
|
||||
if (rec?.actions?.length) actions.push(...rec.actions);
|
||||
if (rec?.context_patch) Object.assign(context_patch, rec.context_patch);
|
||||
if (rec?.audit) audit.recommend = rec.audit;
|
||||
const didShowOptions = actions.some((a) => a?.type === "show_options");
|
||||
const { next_state, validation: v } = safeNextState(
|
||||
prev_state,
|
||||
{ ...prev, ...context_patch },
|
||||
{ did_show_options: didShowOptions, is_browsing: didShowOptions }
|
||||
);
|
||||
const missing_fields = [];
|
||||
if (rec?.asked_slot) missing_fields.push(rec.asked_slot);
|
||||
if (didShowOptions) missing_fields.push("product_selection");
|
||||
if (!rec?.asked_slot && !didShowOptions && !basketItems.length && !rec?.candidates?.length) {
|
||||
missing_fields.push("recommend_base");
|
||||
}
|
||||
return {
|
||||
plan: {
|
||||
reply: rec?.reply || "¿Qué te gustaría que te recomiende?",
|
||||
next_state,
|
||||
intent: "recommend",
|
||||
missing_fields,
|
||||
order_action: "none",
|
||||
basket_resolved: { items: basketItems },
|
||||
},
|
||||
decision: { actions, context_patch, audit: { ...audit, fsm: v } },
|
||||
};
|
||||
}
|
||||
|
||||
if (intent === "greeting") {
|
||||
const { next_state, validation: v } = safeNextState(prev_state, { ...prev }, {});
|
||||
@@ -484,6 +910,11 @@ export async function runTurnV3({
|
||||
const { question, pending } = buildPagedOptions({ candidates });
|
||||
context_patch.pending_clarification = pending;
|
||||
context_patch.pending_item = null;
|
||||
// Guardar cantidad pendiente para usarla después de la selección
|
||||
if (nlu?.entities?.quantity != null) {
|
||||
context_patch.pending_quantity = nlu.entities.quantity;
|
||||
context_patch.pending_unit = nlu.entities.unit;
|
||||
}
|
||||
actions.push({ type: "show_options", payload: { count: pending.options?.length || 0 } });
|
||||
const { next_state, validation: v } = safeNextState(prev_state, { ...prev, ...context_patch }, { did_show_options: true });
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user