113 lines
4.1 KiB
JavaScript
113 lines
4.1 KiB
JavaScript
function parseIndexSelection(text) {
|
|
const t = String(text || "").toLowerCase();
|
|
const m = /\b(\d{1,2})\b/.exec(t);
|
|
if (m) return parseInt(m[1], 10);
|
|
if (/\bprimera\b|\bprimero\b/.test(t)) return 1;
|
|
if (/\bsegunda\b|\bsegundo\b/.test(t)) return 2;
|
|
if (/\btercera\b|\btercero\b/.test(t)) return 3;
|
|
if (/\bcuarta\b|\bcuarto\b/.test(t)) return 4;
|
|
if (/\bquinta\b|\bquinto\b/.test(t)) return 5;
|
|
if (/\bsexta\b|\bsexto\b/.test(t)) return 6;
|
|
if (/\bs[eé]ptima\b|\bs[eé]ptimo\b/.test(t)) return 7;
|
|
if (/\boctava\b|\boctavo\b/.test(t)) return 8;
|
|
if (/\bnovena\b|\bnoveno\b/.test(t)) return 9;
|
|
if (/\bd[eé]cima\b|\bd[eé]cimo\b/.test(t)) return 10;
|
|
return null;
|
|
}
|
|
|
|
function isShowMoreRequest(text) {
|
|
const t = String(text || "").toLowerCase();
|
|
return (
|
|
/\bmostr(a|ame)\s+m[aá]s\b/.test(t) ||
|
|
/\bmas\s+opciones\b/.test(t) ||
|
|
(/\bm[aá]s\b/.test(t) && /\b(opciones|productos|variedades|tipos)\b/.test(t)) ||
|
|
/\bsiguiente(s)?\b/.test(t)
|
|
);
|
|
}
|
|
|
|
function normalizeText(s) {
|
|
return String(s || "")
|
|
.toLowerCase()
|
|
.replace(/[¿?¡!.,;:()"]/g, " ")
|
|
.replace(/\s+/g, " ")
|
|
.trim();
|
|
}
|
|
|
|
function scoreTextMatch(query, candidateName) {
|
|
const qt = new Set(normalizeText(query).split(" ").filter(Boolean));
|
|
const nt = new Set(normalizeText(candidateName).split(" ").filter(Boolean));
|
|
let hits = 0;
|
|
for (const w of qt) if (nt.has(w)) hits++;
|
|
return hits / Math.max(qt.size, 1);
|
|
}
|
|
|
|
export function buildPagedOptions({ candidates, candidateOffset = 0, baseIdx = 1, pageSize = 9 }) {
|
|
const cands = (candidates || []).filter((c) => c && c.woo_product_id && c.name);
|
|
const off = Math.max(0, parseInt(candidateOffset, 10) || 0);
|
|
const size = Math.max(1, Math.min(20, parseInt(pageSize, 10) || 9));
|
|
const slice = cands.slice(off, off + size);
|
|
const options = slice.map((c, i) => ({
|
|
idx: baseIdx + i,
|
|
type: "product",
|
|
woo_product_id: c.woo_product_id,
|
|
name: c.name,
|
|
}));
|
|
const hasMore = off + size < cands.length;
|
|
if (hasMore) options.push({ idx: baseIdx + size, type: "more", name: "Mostrame más…" });
|
|
const list = options
|
|
.map((o) => (o.type === "more" ? `- ${o.idx}) ${o.name}` : `- ${o.idx}) ${o.name}`))
|
|
.join("\n");
|
|
const question = `¿Cuál de estos querés?\n${list}\n\nRespondé con el número.`;
|
|
const pending = {
|
|
candidates: cands,
|
|
options,
|
|
candidate_offset: off,
|
|
page_size: size,
|
|
base_idx: baseIdx,
|
|
has_more: hasMore,
|
|
next_candidate_offset: off + size,
|
|
next_base_idx: baseIdx + size + (hasMore ? 1 : 0),
|
|
};
|
|
return { question, pending, options, hasMore };
|
|
}
|
|
|
|
export function resolvePendingSelection({ text, nlu, pending }) {
|
|
if (!pending?.candidates?.length) return { kind: "none" };
|
|
|
|
if (isShowMoreRequest(text)) {
|
|
const { question, pending: nextPending } = buildPagedOptions({
|
|
candidates: pending.candidates,
|
|
candidateOffset: pending.next_candidate_offset ?? ((pending.candidate_offset || 0) + (pending.page_size || 9)),
|
|
baseIdx: pending.next_base_idx ?? ((pending.base_idx || 1) + (pending.page_size || 9) + 1),
|
|
pageSize: pending.page_size || 9,
|
|
});
|
|
return { kind: "more", question, pending: nextPending };
|
|
}
|
|
|
|
const idx =
|
|
(nlu?.entities?.selection?.type === "index" ? parseInt(String(nlu.entities.selection.value), 10) : null) ??
|
|
parseIndexSelection(text);
|
|
if (idx && Array.isArray(pending.options)) {
|
|
const opt = pending.options.find((o) => o.idx === idx);
|
|
if (opt?.type === "more") return { kind: "more", question: null, pending };
|
|
if (opt?.woo_product_id) {
|
|
const chosen = pending.candidates.find((c) => c.woo_product_id === opt.woo_product_id) || null;
|
|
if (chosen) return { kind: "chosen", chosen };
|
|
}
|
|
}
|
|
|
|
const selText = nlu?.entities?.selection?.type === "text" ? String(nlu.entities.selection.value || "").trim() : null;
|
|
const q = selText || nlu?.entities?.product_query || null;
|
|
if (q) {
|
|
const scored = pending.candidates
|
|
.map((c) => ({ c, s: scoreTextMatch(q, c?.name) }))
|
|
.sort((a, b) => b.s - a.s);
|
|
if (scored[0]?.s >= 0.6 && (!scored[1] || scored[0].s - scored[1].s >= 0.2)) {
|
|
return { kind: "chosen", chosen: scored[0].c };
|
|
}
|
|
}
|
|
|
|
return { kind: "ask" };
|
|
}
|
|
|