Два способи знайти іконку серед 280 000

Два способи знайти іконку серед 280 000

Навіщо взагалі знадобився пошук

Бібліотека іконок Jinero містить 280 101 безкоштовну open-source SVG-іконку у 237 наборах — Lucide, Heroicons, Tabler, Material Symbols, Phosphor, IconPark, Bootstrap Icons і ще десятки інших. Їх можна фільтрувати, переглядати будь-яку іконку в повному розмірі, копіювати як SVG, експортувати в PNG восьми розмірів, збирати добірку й вивантажувати її одним спрайтом, іконковим шрифтом або ZIP-архівом через Генератор іконкового шрифту / спрайтів. Той самий каталог живить наш публічний API, плагін для Figma і MCP-сервер.

Саме в цьому масштабі й уся проблема.

Набору з 300 іконок пошук не потрібен — його просто гортають. А коли іконок 280 000, каталог вартий рівно стільки, наскільки легко в ньому знайти потрібне. І цей «шлях усередину» виявляється двома зовсім різними задачами, що ховаються за одним полем введення.

Перша задача — швидкість. Ви вводите arrow і чекаєте результатів ще до того, як допишете слово. Це питання до бази даних.

Друга задача — словник. Вам потрібна іконка для «користувач виходить із системи», а та, що вам підійде, в одному наборі називається sign-out-alt, в іншому — logout, у третьому — exit_to_app, а в четвертому — door-open. Жодної з цих назв ви не знаєте. І це вже зовсім не питання до бази даних.

Ми розв'язали їх окремо, і найцікавіше тут — чому їх не можна було розв'язати разом.


Спосіб 1 — пошук за ключовими словами, і чому це не просто LIKE

Типовий пошук миттєвий і спрацьовує на кожне натискання клавіші. Тут потрібен саме запит до бази даних — ніщо інше не відповідає за десятки мілісекунд на 280 тисячах рядків.

Наївний варіант — WHERE name LIKE '%arrow%'. І він же марний: не вміє ранжувати, не справляється з двома словами й сканує всю таблицю.

Насправді працює запит MySQL boolean FULLTEXT до окремого стовпця search_keywords. Кожен токен від трьох символів стає обов'язковим збігом за префіксом:

"shopping cart"  →  +shopping* +cart*

Токени, коротші за три символи, обробляються через LIKE за назвою, бо FULLTEXT їх ігнорує. Запит обмежено вісьмома токенами — далі людина вже не шукає, а вставляє текст.

Виміри на продакшні:

Запит Час Результати
delete 37 мс 2 143
sign out 41 мс 420
shopping cart 37 мс 1 746

Стовпець, що виконує справжню роботу

search_keywords — це не назва іконки. Це збагачений синонімами текстовий блок, який формується під час імпорту, і саме в ньому криється більша частина якості.

У нас 313 груп синонімів, і кожна об'єднує поняття з усіма словами, якими його насправді називають дизайнери:

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

Введіть delete — і отримаєте іконки з назвами trash, bin і remove, бо під час імпорту всі ці слова записано в той самий блок для пошуку. Під час запиту це нічого не коштує — робота виконана один раз, офлайн.

Окремий випадок варто згадати, бо він зламався так, як ми не очікували. Іконки прапорів названо за ISO-кодами: Jp, Ua, De. Ніхто не шукає «Jp» — шукають «Japan». Тож для наборів прапорів ми вшиваємо в ключові слова назву країни та її альтернативні назви.

Але лише для наборів прапорів. Перша версія робила це для всього каталогу, і результат був абсурдний: ISO-коди in, it, is і no — звичайні англійські слова, тож на запит «no entry» почали вилазити норвезький та італійський прапори. Виправлення полягало не в розумнішому зіставленні, а в тому, щоб обмежити мапу країн умовою kind=flag і не чіпати решту каталогу.

Де пошук за ключовими словами безсилий

Попросіть у нього те, що можете описати, але не можете назвати:

"something went wrong"   →   nothing found

Не поганий результат. Жодного результату. Серед 280 000 іконок немає жодної, у ключових словах якої була б ця фраза, і ніколи не буде, бо вона описує ситуацію, а не зображення.

Це стіна. Синонімами її не обійти — знадобилася б група синонімів для кожної думки, яка може спасти людині на гадку. Тут і починається другий спосіб.


Спосіб 2 — пошук за тим, як виглядає іконка

Другий пошук не читає назв. Він дивиться на зображення.

Ідея та сама, що й у нашому «Розпізнаванні шрифтів»: перетворити об'єкт на вектор чисел і порівнювати числа. Але якщо розпізнавач порівнює зображення із зображенням, то тут треба порівнювати текст із зображенням — ваше речення з малюнком.

Саме це й робить візуально-мовна (vision-language) модель. Вона переводить зображення й текст в один спільний простір, тож «смітник із кришкою» і справжній малюнок смітника опиняються поруч.

Модель, яку ми не навчали

Для «Розпізнавання шрифтів» ми навчили власну модель, і вона обійшла всі готові варіанти. Тож перший порив тут був такий самий: навчити контрастивну модель на власних іконках.

Ми почали — і зупинилися.

Шрифти й іконки — різні задачі. Гарнітури відрізняються тонкими, як волосина, деталями — формою закінчення штриха, віссю овалу, — і жодну модель загального призначення не вчили на це зважати. З іконками навпаки: це зображення звичайних речей. Смітник, стрілка, кошик для покупок. Візуально-мовні моделі загального призначення бачили мільйони таких.

Тож ми використовуємо SigLIP2 (so400m-patch16-384) без змін. Навчання власної моделі забрало б тижні — і привело б нас до результату, гіршого за неї. Чесна інженерна відповідь — не будувати цього взагалі.

Це дзеркальне відображення історії розпізнавача шрифтів, і обидва рішення виросли з одного запитання: чи справді загальна модель із цим погано справляється? Зі шрифтами — так. З іконками — ні.

Що працює на продакшні

Кожну іконку в індексі один раз, офлайн, пропущено через SigLIP — і отримано вектор із 1152 чисел:

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

Окремий постійно ввімкнений контейнер тримає в пам'яті модель і весь індекс. Запит перетворює ваш текст на ембединг — це єдиний виклик моделі, — а далі лише скалярний добуток із чвертю мільйона рядків.

Ендпоінт Час
/search (текст → іконка) 257–343 мс
/similar (іконка → іконка) 35 мс

Текстовий пошук на порядок повільніший, бо найдорожче — закодувати ваше речення; порівняти вектори після цього майже нічого не коштує. Через це ж і «Схожі» працюють так швидко: вектор уже є, тож модель узагалі не викликається.

Тому глибокий пошук — це кнопка, а не типовий режим. 300 мс — нормально, коли ви натискаєте її свідомо, але для пошуку під час введення це непридатно. Пошук за ключовими словами лишається миттєвим, а глибокий ви вмикаєте самі.

Йому байдуже, якою мовою ви питаєте

Саме це найбільше дивує людей, і цю функцію ми не створювали. Вона йде в комплекті з моделлю, бо SigLIP вчився на текстах і зображеннях разом, багатьма мовами.

Те саме запитання трьома мовами, реальні результати з продакшну:

Запит Час Перші результати
throw it away 257 мс Reuse · Garbage Throw · Litter In Bin Sign · Bin
викинути у смітник 302 мс Garbage Throw · Litter In Bin Sign · Bin
ゴミ箱に捨てる 343 мс Delete · Delete Sweep · Trash · Trashcan

У жодної з цих іконок немає ні українського, ні японського слова в метаданих. Каталог повністю англомовний. Модель зіставляє не слова, а зміст із зображенням.

Два пошуки не замінюють один одного — вони поєднуються

Глибокий пошук не відкидає результатів за ключовими словами. Він запускає обидва пошуки й об'єднує результати: спершу семантичні збіги, потім збіги за ключовими словами; фільтри застосовуються зі збереженням цього порядку, а результат кешується на п'ять хвилин, щоб пагінація не запускала модель повторно.

А якщо сервіс пошуку недоступний, перемикач тихо повертається до пошуку за ключовими словами. Повільніший пошук — це особливість, а зламаний — уже аварія.


Пошук схожих — той самий індекс, інший напрямок

У детальному перегляді кожної іконки є кнопка «Схожі». Вона бере вектор цієї іконки — він уже є в індексі — і за 35 мс повертає її найближчих сусідів серед усіх 280 000. Це зручно, щоб знайти іконки в тому самому стилі або краще намальовану версію знайденої іконки.

А ще вона навчила нас дечого, що варто сказати прямо, бо це найочевидніше обмеження всієї системи.

Попросіть іконки, схожі на повний смітник, і отримаєте:

Pail · Accumulation Precipitation · Glas Water · Specific Gravity

Відро. Посудина з водою. За змістом вони ніяк не пов'язані з видаленням — але вони абсолютно, беззаперечно схожі на вигляд. Смітник і відро мають однакову форму.

Ось що означає схожість «зображення — зображення», і про це варто сказати чітко: «Схожі» знаходить іконки, схожі на вигляд, а не за змістом. Коли ви підбираєте візуальний стиль, це саме те, що треба. Коли ж чекаєте «ще іконок для видалення» — ні.


У чому він досі помиляється

Глибокий пошук — не магія, і слабке місце в нього цілком конкретне: він добре знаходить речі й погано — ситуації.

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

Пошук за ключовими словами тут нічого не повертає; глибокий повертає чотири впевнені хибні відповіді. Що з цього гірше — справжнє питання UX. Порожній результат підказує переформулювати запит. Хибний результат каже, що в каталозі немає того, що вам потрібно, — а це неправда.

Конкретні об'єкти працюють: a bin with a lid, bicycle, wifi off. Абстракції «пливуть»: стани помилок, емоції, «професійний вигляд».

Є ще одна структурна особливість, яку варто розуміти, бо вона пояснює час від часу помітну розбіжність між двома пошуками.

Векторний індекс описує ресурс таким, яким той був на момент побудови індексу, а не таким, яким він є зараз. Пошук за ключовими словами читає базу даних, тож щойно імпортований набір можна знайти одразу після появи. Глибокий пошук читає файл із заздалегідь обчисленими векторами, тож нові іконки потрапляють у нього під час наступної перебудови. Така ціна відповіді за 300 мс замість 300 секунд.

Наскільки буквально це працює, ми дізналися на власному досвіді. Якось 616 іконок IconPark рендерилися суцільними чорними фігурами — через баг із маскою-вирізом (knockout) у вихідних SVG. Малюнки виправили, і кожен, хто переглядає бібліотеку, бачить їх правильно. Але ембединг, обчислений зі зламаного рендеру, кодує саме це: чорний прямокутник. Зображення та його опис — два різні артефакти, і виправлення одного не виправляє іншого. Саме тому повторне обчислення ембедингів після змін у наборі — частина стандартної процедури імпорту.


Де який пошук підключено

Не всюди — і це свідомо.

Публічний API, плагін для Figma і MCP-сервер шукають лише за ключовими словами. Вони орієнтовані на машини: інтеграції потрібна швидка, передбачувана й дешева відповідь, і зазвичай вона вже знає слово, яке шукає. Семантичний пошук коштує ~300 мс і постійно завантажену в пам'ять модель; ми ввімкнемо його там, коли комусь він справді знадобиться, — з окремими лімітами запитів.

Глибокий пошук і «Схожі» живуть у вебверсії бібліотеки, де людина дивиться на сітку іконок і думає: «не те — щось більше схоже на оце».


Висновок

Два пошуки — бо є два запитання.

Коли ви знаєте слово, перемагає база даних — 37 мс, з ранжуванням, і спрацьовує на кожне натискання клавіші. Більшість пошуків саме такі. Те, що робить цей пошук якісним, відбулося офлайн, у таблиці синонімів, а не під час запиту.

Коли ви знаєте лише зміст, перемагає модель — і вона відповідає мовою, якою каталог ніколи не перекладали.

Інженерне рішення, у якому ми впевнені найбільше, — те, де ми не написали жодної моделі. Для шрифтів власне навчання обійшло все доступне. Для іконок найкраща доступна модель уже була кращою за те, що ми збудували б самі. Зрозуміти, в якій із цих ситуацій ви опинилися, — це більша частина роботи.


242 перегляди 0 коментарів

Коментарі 0

Увійдіть, щоб залишити перший коментар.