feat(busca): pin-number queries in every language, pin answer card
CI / Lint e auditorias (push) Successful in 46s
CI / Trivy (dependências e segredos) (push) Successful in 10s
CI / Imagem Docker (linux/arm64) (push) Successful in 3m50s
Release / Plano (reaproveitar a imagem?) (push) Successful in 5s
Release / ci (push) Skipped
Release / Publica a imagem (linux/arm64) (push) Successful in 23s
Release / Cria a release no Gitea (push) Successful in 5s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Sonnet 5.5 committed 2026-11-02 00:43:23 -03:00
1 parent fe1802f818
commit 71c5708726
5 files changed
+208 -15

No files matched your search

+60 -6
View File
@@ -16,7 +16,7 @@ const STOP = new Set([
"para", "pra", "pro", "com", "sem", "que", "qual", "quais", "como", "e", "ou", "ver", "sobre", "eh",
"preciso", "quero", "onde", "fica", "ficam", "esta", "estao", "meu", "minha", "usar", "uso",
]);
const INTENCAO_PINOS = new Set(["pino", "pinos", "pinagem", "pinout", "pin", "pins", "gpios"]);
const INTENCAO_PINOS = new Set(["pino", "pinos", "pinagem", "pinout", "pin", "pins", "gpios", "pines", "broche", "broches", "brochage", "pinbelegung"]);
const INTENCAO_DADOS = new Set([
"dados", "especificacao", "especificacoes", "spec", "specs", "informacao", "informacoes", "info",
"quanto", "quantos", "quantas", "capacidade", "limite", "limites", "tem", "possui", "suporta",
@@ -137,9 +137,17 @@ function casarTermo(termo, livres) {
return null;
}
// "pino 32", "pin 32", "broche 32", "引脚 32", "32号引脚": número do pino físico do diagrama.
const PINO_FISICO = /\b(?:pino|pinos|pin|pins|pines|broche|broches)\s*(?:n[ºo°.]?\s*|numero\s*|number\s*|nummer\s*)?(\d{1,3})\b(?![.,]\d)/g;
const PINO_FISICO_ZH = /第?(\d{1,3})\s*(?:号|脚)?\s*(?:引脚|管脚|针脚)|(?:引脚|管脚|针脚)\s*第?(\d{1,3})(?![.,\d])/g;
export function interpretar(indice, consulta) {
const bruto = normalizar(consulta)
const pinNums = [];
const semPinos = normalizar(consulta)
.replace(/gpio\s*-?\s*(\d+)/g, "gpio$1")
.replace(PINO_FISICO, (_, n) => (pinNums.push(n), " "))
.replace(PINO_FISICO_ZH, (_, a, b) => (pinNums.push(a ?? b), " "));
const bruto = semPinos
.replace(/([\u3400-\u9fff])([a-z0-9])/g, "$1 $2")
.replace(/([a-z0-9])([\u3400-\u9fff])/g, "$1 $2")
.replace(/[^a-z0-9.+\-_ \u3400-\u9fff]/g, " ");
@@ -231,7 +239,7 @@ export function interpretar(indice, consulta) {
cjkUsados.forEach((i) => usados.add(i));
const restantes = tokens.filter((_, i) => !usados.has(i)).flatMap((t) => (CJK.test(t) ? segmentar(t) : [t]));
return { tokens, chips, topicos, gpios, funcoes, restantes, querPinos, querDados };
return { tokens, chips, topicos, gpios, funcoes, restantes, querPinos, querDados, pinNums: [...new Set(pinNums)].slice(0, 3) };
}
const rotuloPino = (p) => {
@@ -253,6 +261,27 @@ function pinosDoTopico(serie, topico) {
return { forte, fraco };
}
const corta = (s, max = 110) => (s.length > max ? `${s.slice(0, max - 1).trimEnd()}…` : s);
/** Cartão com tudo o que o diagrama diz sobre um pino (nome, funções, categorias, avisos). */
function cartaoPino(I, chave, serie, p, titulo) {
const T = I.t;
const linhas = [
{ k: T.kPino, v: String(p.n) },
{ k: T.kNome, v: rotuloPino(p) },
];
if (p.f?.length) linhas.push({ k: T.kFuncoes, v: corta(p.f.join(" · ")) });
if (p.c?.length) linhas.push({ k: T.kCategorias, v: p.c.join(", ") });
(p.a ?? []).slice(0, 3).forEach((a) => linhas.push({ k: T.kAviso, v: corta(a) }));
return {
tipo: "resposta",
titulo,
descricao: T.pinoDesc,
linhas,
href: hrefPinos(I, chave, [p], fmt(T.motivoPino, { pino: rotuloPino(p) })),
};
}
const hrefPinos = (I, chave, pinos, motivo) =>
`${I.rotas.series}/${chave}?pinos=${pinos.map((p) => p.n).join(",")}&motivo=${encodeURIComponent(motivo)}#connections`;
@@ -307,7 +336,19 @@ function respostas(indice, it) {
(it.querPinos ? [cartaoPinos, cartaoSpec] : [cartaoSpec, cartaoPinos]).filter(Boolean).forEach((c) => saida.push(c));
}
if (!it.topicos.length && !it.gpios.length && !it.funcoes.length) {
// "pino 32 s3": o pino físico 32 do diagrama e, se for outro, o GPIO32.
for (const n of it.pinNums) {
const fisico = serie.pinos.find((p) => String(p.n) === n);
const gpio = (indice.porFuncao.get(`gpio${n}`) ?? []).find((a) => a.chave === chave)?.pino;
if (fisico) saida.push(cartaoPino(I, chave, serie, fisico, fmt(T.pinoFisico, { n, serie: serie.nome })));
if (gpio && gpio !== fisico) saida.push(cartaoPino(I, chave, serie, gpio, fmt(T.pinoNo, { pino: rotuloPino(gpio), serie: serie.nome })));
if (!fisico && !gpio) {
const max = Math.max(0, ...serie.pinos.map((p) => Number(p.n) || 0));
saida.push({ tipo: "pino", titulo: fmt(T.pinoInexistente, { n, serie: serie.nome }), descricao: fmt(T.pinoInexistenteDesc, { max }), href: `${I.rotas.series}/${chave}#connections` });
}
}
if (!it.topicos.length && !it.gpios.length && !it.funcoes.length && !it.pinNums.length) {
if (it.querPinos && serie.pinos.length) {
saida.push({
tipo: "pino",
@@ -337,6 +378,18 @@ function respostas(indice, it) {
}
}
// Pino físico sem chip: o que há nele em cada série.
if (!alvos.length) {
for (const n of it.pinNums) {
const linhas = [];
for (const chave of indice.chaves) {
const p = indice.series[chave].pinos.find((x) => String(x.n) === n);
if (p) linhas.push({ k: indice.series[chave].nome, v: corta(rotuloPino(p), 46) });
}
if (linhas.length) saida.push({ tipo: "resposta", titulo: fmt(T.pinoEmCada, { n }), descricao: fmt(T.pinoEmCadaDesc, { n }), linhas, href: I.rotas.series });
}
}
// GPIO26 e nomes de função: "gpio26 s3", "mtck"
const buscas = [...it.gpios, ...it.funcoes];
for (const termo of buscas) {
@@ -387,6 +440,7 @@ export function buscar(indice, consulta) {
const interpretacao = {
chips: it.chips.map((c) => ({ chave: c, nome: indice.series[c]?.nome ?? c })),
topicos: it.topicos.map((t) => ({ id: t.id, nome: indice.topicosLoc?.[t.id]?.nome ?? t.nome })),
pinos: it.pinNums.map((n) => fmt((BUSCA_I18N[indice.locale] ?? BUSCA_I18N.pt).t.chipPino, { n })),
};
const smart = respostas(indice, it).map((r, i) => ({ id: `r${i}`, keywords: "", ...r }));
@@ -436,11 +490,11 @@ export function buscar(indice, consulta) {
const sugestoes = [];
if (it.topicos.length && !it.chips.length) {
for (const c of indice.chaves.slice(0, 12)) sugestoes.push({ rotulo: indice.series[c].nome, anexar: c });
} else if (it.chips.length && !it.topicos.length && !it.gpios.length) {
} else if (it.chips.length && !it.topicos.length && !it.gpios.length && !it.pinNums.length) {
for (const nome of EXEMPLOS_TOPICOS) sugestoes.push({ rotulo: nome, anexar: nome });
}
return { resultados: [...smart, ...texto], interpretacao, sugestoes, totalRespostas: smart.length };
}
export const EXEMPLOS_DE_BUSCA = ["flash s3", "pinos usb c6", "deep sleep p4", "gpio0 esp32", "secure boot c3", "psram"];
export const EXEMPLOS_DE_BUSCA = ["flash s3", "pinos usb c6", "pino 32 esp32-s3", "gpio0 esp32", "secure boot c3", "psram"];