Duas formas de encontrar um ícone entre 280.000

Duas formas de encontrar um ícone entre 280.000

Por que precisávamos de uma busca

A Biblioteca de ícones do Jinero reúne 280.101 ícones SVG open source gratuitos em 237 pacotes — Lucide, Heroicons, Tabler, Material Symbols, Phosphor, IconPark, Bootstrap Icons e dezenas de outros. Você pode filtrá-los, visualizar qualquer ícone em tamanho real, copiá-lo como SVG, exportá-lo como PNG em oito tamanhos, reunir uma seleção e baixá-la como um único sprite, fonte de ícones ou ZIP com o Gerador de fonte de ícones / sprites. O mesmo catálogo alimenta a nossa API pública, o plugin para Figma e o servidor MCP.

Essa escala é justamente o problema.

Um pacote de 300 ícones não precisa de busca — basta rolar a página. Com 280.000, o catálogo só é tão bom quanto a porta de entrada. E essa “porta de entrada” acaba sendo dois problemas completamente diferentes que dividem o mesmo campo de busca.

O primeiro problema é a velocidade. Você digita arrow e espera ver resultados antes de terminar a palavra. Essa é uma questão de banco de dados.

O segundo problema é o vocabulário. Você precisa de um ícone para “o usuário está saindo da conta”, e o ícone que você quer se chama sign-out-alt em um pacote, logout em outro, exit_to_app em um terceiro e door-open em um quarto. Você não conhece nenhum desses nomes. Isso não é, de forma alguma, uma questão de banco de dados.

Resolvemos os dois separadamente, e a parte interessante é por que não dava para resolvê-los juntos.


Método 1 — busca por palavra-chave, e por que não é só LIKE

A busca padrão é instantânea e roda a cada tecla digitada. Ela precisa ser uma consulta ao banco de dados — nada mais responde em dezenas de milissegundos com 280 mil linhas.

A versão ingênua é WHERE name LIKE '%arrow%'. E ela é inútil: não consegue ordenar por relevância, não lida com duas palavras e varre a tabela inteira.

O que realmente roda é uma consulta FULLTEXT booleana do MySQL em uma coluna dedicada, search_keywords. Cada token com três caracteres ou mais vira uma correspondência obrigatória por prefixo:

"shopping cart"  →  +shopping* +cart*

Tokens com menos de três caracteres recorrem a um LIKE no nome, porque o FULLTEXT os ignora. Limitamos a consulta a oito tokens — acima disso, a pessoa não está buscando, está colando texto.

Medições em produção:

Consulta Tempo Resultados
delete 37 ms 2.143
sign out 41 ms 420
shopping cart 37 ms 1.746

A coluna que faz o trabalho de verdade

search_keywords não é o nome do ícone. É um blob enriquecido com sinônimos, montado no momento da importação, e é nele que mora a maior parte da qualidade.

Mantemos 313 grupos de sinônimos, cada um reunindo um conceito em todas as palavras que os designers realmente usam para ele:

delete → trash → remove → bin → erase
edit   → modify → pencil → pen → write
add    → plus → create → new → insert

Digite delete e você recebe ícones chamados trash, bin e remove, porque, na importação, todas essas palavras foram gravadas no mesmo blob pesquisável. Isso não custa nada na hora da consulta — o trabalho foi feito uma vez, offline.

Há um caso especial que vale mencionar, porque ele quebrou de um jeito que não esperávamos. Os ícones de bandeiras são nomeados pelo código ISO: Jp, Ua, De. Ninguém busca “Jp” — as pessoas buscam “Japan”. Então, para os pacotes de bandeiras, incorporamos às palavras-chave o nome do país e os seus nomes alternativos.

Mas só para os pacotes de bandeiras. A primeira versão fazia isso para tudo, e o resultado foi absurdo: os códigos ISO in, it, is e no são palavras comuns em inglês, então buscar “no entry” passou a retornar bandeiras da Noruega e da Itália. A correção não foi uma correspondência mais esperta — foi restringir o mapa de países a kind=flag e deixar o resto do catálogo em paz.

Onde a busca por palavra-chave para

Peça a ela algo que você consegue descrever, mas não sabe nomear:

"something went wrong"   →   nothing found

Não é um resultado ruim. É nenhum resultado. Entre 280.000 ícones, não há nenhum cujas palavras-chave contenham essa frase, e nunca haverá, porque ela descreve uma situação, e não uma imagem.

Esse é o muro. Não dá para contorná-lo com sinônimos — você precisaria de um grupo de sinônimos para cada pensamento que uma pessoa pode ter. É aqui que começa o segundo método.


Método 2 — buscar pela aparência do ícone

A segunda busca não lê nomes. Ela olha para a imagem.

A ideia é a mesma por trás do nosso Identificador de fontes: transformar a coisa em um vetor de números e depois comparar números. Mas, enquanto o identificador compara imagem com imagem, aqui precisamos comparar texto com imagem — a sua frase com um desenho.

É exatamente isso que um modelo de visão e linguagem faz. Ele projeta imagens e textos em um único espaço compartilhado, de modo que “uma lixeira com tampa” e o desenho real de uma lixeira ficam próximos.

O modelo que não treinamos

Treinamos o nosso próprio modelo para o Identificador de fontes, e ele superou todas as opções prontas. Então o primeiro instinto aqui foi o mesmo: treinar um modelo contrastivo com os nossos próprios ícones.

Começamos e depois paramos.

Fontes e ícones são problemas diferentes. As fontes diferem por detalhes finíssimos — a forma de um terminal, o eixo de um bojo —, e nenhum modelo de uso geral é treinado para se importar com isso. Os ícones são o oposto: são imagens de coisas comuns. Uma lixeira, uma seta, um carrinho de compras. Os modelos de visão e linguagem de uso geral já viram milhões delas.

Por isso usamos o SigLIP2 (so400m-patch16-384) sem modificações. Treinar o nosso próprio custaria semanas para chegar a um resultado pior que o dele. A resposta honesta de engenharia foi não construir a coisa.

É o reflexo invertido da história do identificador de fontes, e as duas decisões vieram da mesma pergunta: o modelo geral é realmente ruim nisso? Para fontes, era. Para ícones, não é.

O que roda em produção

Cada ícone do índice passou pelo SigLIP uma vez, offline, gerando um vetor de 1.152 números:

siglip_index.npz     259,301 × 1152, float16     586 MB
service RAM          ~1.2 GB

Um contêiner separado, sempre ligado, mantém o modelo e o índice inteiro na memória. Uma consulta gera o embedding do seu texto — essa é a única chamada ao modelo — e depois é só um produto escalar contra um quarto de milhão de linhas.

Endpoint Tempo
/search (texto → ícone) 257–343 ms
/similar (ícone → ícone) 35 ms

A busca por texto é uma ordem de grandeza mais lenta porque codificar a sua frase é a parte cara; comparar os vetores depois disso é quase de graça. É também por isso que “Semelhantes” é tão rápido: o vetor já existe, então não há nenhuma chamada ao modelo.

É por isso que a busca profunda é um botão, e não o padrão. 300 ms são aceitáveis quando você clica de propósito; enquanto você digita, seriam inviáveis. A busca por palavra-chave continua instantânea, e a profunda é opcional.

Não importa em que idioma você pergunta

Esta é a parte que surpreende as pessoas, e não é um recurso que construímos. Ela vem de graça com o modelo, porque o SigLIP aprendeu texto e imagens juntos, em muitos idiomas.

A mesma pergunta em três idiomas, com resultados reais de produção:

Consulta Tempo Principais resultados
throw it away 257 ms Reuse · Garbage Throw · Litter In Bin Sign · Bin
викинути у смітник 302 ms Garbage Throw · Litter In Bin Sign · Bin
ゴミ箱に捨てる 343 ms Delete · Delete Sweep · Trash · Trashcan

Nenhum desses ícones tem uma palavra em ucraniano ou japonês em qualquer parte dos metadados. O catálogo é inteiramente em inglês. O modelo não está comparando palavras — está ligando o significado à imagem.

As duas buscas não se substituem — elas se combinam

A busca profunda não descarta os resultados por palavra-chave. Ela executa as duas e une os resultados: primeiro os acertos semânticos, depois os de palavra-chave, com os filtros aplicados preservando essa ordem e um cache de cinco minutos para que a paginação não execute o modelo de novo.

E, se o serviço de busca estiver fora do ar, o seletor volta silenciosamente para a busca por palavra-chave. Uma busca mais lenta é um recurso; uma busca quebrada é uma pane.


Encontrar semelhantes — o mesmo índice, na direção oposta

A visualização de detalhes de cada ícone tem um botão Semelhantes. Ele pega o vetor desse ícone — que já está no índice — e retorna os vizinhos mais próximos entre todos os 280.000, em 35 ms. É útil para encontrar um estilo que combine ou uma versão mais bem desenhada do ícone que você encontrou.

Ele também nos ensinou algo que vale dizer com todas as letras, porque é a limitação mais clara de todo o sistema.

Peça ícones semelhantes a uma lixeira cheia e você recebe:

Pail · Accumulation Precipitation · Glas Water · Specific Gravity

Um balde. Um recipiente com água. Eles não têm relação semântica com apagar coisas — mas são, absoluta e inegavelmente, parecidos na aparência. Uma lixeira e um balde têm a mesma forma.

É isso que significa similaridade de imagem para imagem, e vale deixar claro: “Semelhantes” encontra ícones que se parecem, não ícones que significam a mesma coisa. Quando você está procurando um estilo visual que combine, é exatamente o certo. Quando você espera “mais ícones de excluir”, não é.


O que ela ainda erra

A busca profunda não é mágica, e o modo de falha é específico: ela é boa com coisas e ruim com situações.

"something went wrong"  →  Compass South West · Compass West Arrow · Roadmap · Phone Off

A busca por palavra-chave não retorna nada aqui; a profunda retorna quatro respostas erradas cheias de confiança. Qual das duas é pior é uma questão genuína de UX. Um resultado vazio diz para você reformular a busca. Um resultado errado diz que o catálogo não tem o que você quer — e isso é mentira.

Objetos concretos funcionam: a bin with a lid, bicycle, wifi off. Abstrações se perdem: estados de erro, emoções, “com aparência profissional”.

Há também uma propriedade estrutural que vale entender, porque ela explica a diferença ocasional entre as duas buscas.

Um índice vetorial descreve o recurso como ele era quando o índice foi construído, não como ele é agora. A busca por palavra-chave lê o banco de dados, então um pacote recém-importado pode ser encontrado no momento em que chega. A busca profunda lê um arquivo de vetores pré-calculados, então os ícones novos só entram nela na próxima reconstrução. Essa é a troca por responder em 300 ms em vez de 300 segundos.

Aprendemos da pior forma o quanto isso é literal. Em certo momento, 616 ícones do IconPark estavam sendo renderizados como formas pretas sólidas — um bug de máscara nos SVGs de origem. Os desenhos foram corrigidos, e quem navega pela biblioteca os vê corretamente. Mas um embedding calculado a partir da renderização quebrada codifica exatamente isso: um retângulo preto. A imagem e a descrição que o modelo guardou dela são dois artefatos diferentes, e corrigir um não corrige o outro. É exatamente por isso que gerar os embeddings de novo depois que um pacote muda faz parte da rotina de importação.


Onde cada uma está integrada

Não em todo lugar, de propósito.

A API pública, o plugin para Figma e o servidor MCP usam apenas a busca por palavra-chave. Eles são voltados para máquinas: uma integração quer uma resposta rápida, previsível e barata, e geralmente já sabe a palavra que está procurando. A busca semântica custa ~300 ms e um modelo sempre ligado ocupando memória; vamos ativá-la ali quando alguém realmente precisar, com os seus próprios limites de uso.

A busca profunda e o botão Semelhantes ficam na biblioteca web, onde uma pessoa está olhando para uma grade e pensando “não isso — algo mais parecido com isto”.


A conclusão

Duas buscas, porque são duas perguntas.

Quando você sabe a palavra, o banco de dados vence — 37 ms, com ordenação por relevância, e roda a cada tecla digitada. A maioria das buscas é assim. O trabalho que a torna boa aconteceu offline, em uma tabela de sinônimos, não na hora da consulta.

Quando você só sabe o significado, o modelo vence — e responde em um idioma para o qual o catálogo nunca foi traduzido.

A decisão de engenharia com a qual nos sentimos mais seguros é aquela em que não criamos modelo nenhum. Para fontes, treinar o nosso próprio superou tudo o que estava disponível. Para ícones, o melhor modelo disponível já era melhor do que o que teríamos construído. Saber em qual situação você está é a maior parte do trabalho.


237 visualizações 0 comentários

Comentários 0

Entre para ser o primeiro a comentar.