@supericons/mcp 0.4.9 → 0.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,33 +1,33 @@
1
- import {
2
- buildPublicSemanticPayload,
3
- getSemanticRecordForIcon,
4
- scoreSemanticAlignment,
5
- } from './semantic-registry.js';
6
- import { existsSync, readFileSync } from 'node:fs';
7
- import { dirname, join } from 'node:path';
8
- import { fileURLToPath } from 'node:url';
9
- import {
10
- expandCjkQuery,
11
- normalizeCjkSearchText,
12
- } from './runtime/cjk-search-core.js';
13
- import {
14
- buildIntentQueryVariants,
15
- buildSearchIntentProfile,
16
- getIntentCandidateAdjustment,
17
- } from './runtime/search-intent-core.js';
18
-
19
- const __dirname = dirname(fileURLToPath(import.meta.url));
20
- const cjkTermsPath = join(__dirname, 'public', 'cjk-search-terms.json');
21
- const multilingualAliasesPath = join(__dirname, 'public', 'multilingual-search-aliases.json');
22
- const cjkSearchTerms = existsSync(cjkTermsPath)
23
- ? JSON.parse(readFileSync(cjkTermsPath, 'utf8')).terms || []
24
- : [];
25
- const multilingualSearchAliases = existsSync(multilingualAliasesPath)
26
- ? JSON.parse(readFileSync(multilingualAliasesPath, 'utf8')).aliases || []
27
- : [];
28
- const multilingualExpansionTerms = [...cjkSearchTerms, ...multilingualSearchAliases];
29
- const SLOT_SEARCH_CONCURRENCY = 2;
30
- const SLOT_QUERY_CONCURRENCY = 1;
1
+ import {
2
+ buildPublicSemanticPayload,
3
+ getSemanticRecordForIcon,
4
+ scoreSemanticAlignment,
5
+ } from './semantic-registry.js';
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ import { dirname, join } from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ import {
10
+ expandCjkQuery,
11
+ normalizeCjkSearchText,
12
+ } from './runtime/cjk-search-core.js';
13
+ import {
14
+ buildIntentQueryVariants,
15
+ buildSearchIntentProfile,
16
+ getIntentCandidateAdjustment,
17
+ } from './runtime/search-intent-core.js';
18
+
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ const cjkTermsPath = join(__dirname, 'public', 'cjk-search-terms.json');
21
+ const multilingualAliasesPath = join(__dirname, 'public', 'multilingual-search-aliases.json');
22
+ const cjkSearchTerms = existsSync(cjkTermsPath)
23
+ ? JSON.parse(readFileSync(cjkTermsPath, 'utf8')).terms || []
24
+ : [];
25
+ const multilingualSearchAliases = existsSync(multilingualAliasesPath)
26
+ ? JSON.parse(readFileSync(multilingualAliasesPath, 'utf8')).aliases || []
27
+ : [];
28
+ const multilingualExpansionTerms = [...cjkSearchTerms, ...multilingualSearchAliases];
29
+ const SLOT_SEARCH_CONCURRENCY = 2;
30
+ const SLOT_QUERY_CONCURRENCY = 1;
31
31
 
32
32
  const GENERIC_SLOT_WORDS = new Set([
33
33
  'action',
@@ -48,322 +48,322 @@ const GENERIC_SLOT_WORDS = new Set([
48
48
  'view',
49
49
  ]);
50
50
 
51
- const VARIANT_PENALTIES = Object.freeze([
52
- { token: 'circle', pattern: /circle/i, penalty: 12 },
53
- { token: 'square', pattern: /square/i, penalty: 12 },
54
- { token: 'dash', pattern: /dash/i, penalty: 5 },
55
- { token: 'badge', pattern: /badge/i, penalty: 4 },
56
- { token: 'brand', pattern: /\bbrand\b/i, penalty: 30 },
57
- { token: 'off', pattern: /\boff\b/i, penalty: 18 },
58
- { token: 'slash', pattern: /slash/i, penalty: 8 },
59
- { token: 'warning', pattern: /warning/i, penalty: 5 },
60
- { token: 'ai', pattern: /\bai\b/i, penalty: 18 },
61
- { token: 'add', pattern: /\badd\b/i, penalty: 12 },
62
- { token: 'plus', pattern: /\bplus\b/i, penalty: 18 },
63
- { token: 'edit', pattern: /\bedit\b/i, penalty: 12 },
64
- { token: 'remove', pattern: /\bremove\b/i, penalty: 12 },
65
- { token: 'delete', pattern: /\bdelete\b/i, penalty: 12 },
66
- { token: 'minus', pattern: /\bminus\b/i, penalty: 24 },
67
- { token: 'cancel', pattern: /\bcancel\b/i, penalty: 30 },
68
- { token: 'x', pattern: /\bx\b/i, penalty: 30 },
69
- { token: 'exclamation', pattern: /\bexclamation\b/i, penalty: 24 },
70
- { token: 'discount', pattern: /\bdiscount\b/i, penalty: 24 },
71
- { token: 'heart', pattern: /\bheart\b/i, penalty: 18 },
72
- { token: 'zap', pattern: /\bzap\b/i, penalty: 30 },
73
- { token: 'bolt', pattern: /\bbolt\b/i, penalty: 18 },
74
- { token: 'wifi', pattern: /\bwifi\b/i, penalty: 12 },
75
- { token: 'align', pattern: /\balign\b/i, penalty: 12 },
76
- { token: 'fruit', pattern: /\bfruit\b/i, penalty: 12 },
77
- { token: 'open', pattern: /\bopen\b/i, penalty: 28 },
78
- { token: 'unlock', pattern: /\bunlock(?:ed)?\b/i, penalty: 28 },
79
- { token: 'ban', pattern: /\bban\b/i, penalty: 24 },
80
- { token: 'blocked', pattern: /\bblocked\b/i, penalty: 24 },
81
- { token: 'rupee', pattern: /\brupee\b/i, penalty: 18 },
82
- { token: 'ruble', pattern: /\bruble\b/i, penalty: 18 },
83
- { token: 'franc', pattern: /\bfranc\b/i, penalty: 18 },
84
- { token: 'lira', pattern: /\blira\b/i, penalty: 18 },
85
- { token: 'bitcoin', pattern: /\bbitcoin\b/i, penalty: 18 },
86
- { token: 'dollar', pattern: /\bdollar\b/i, penalty: 18 },
87
- { token: 'cent', pattern: /\bcent\b/i, penalty: 18 },
88
- { token: 'yen', pattern: /\byen\b/i, penalty: 18 },
89
- { token: 'yuan', pattern: /\byuan\b/i, penalty: 18 },
90
- { token: 'euro', pattern: /\beuro\b/i, penalty: 18 },
91
- { token: 'pound', pattern: /\bpound\b/i, penalty: 18 },
92
- { token: 'down', pattern: /\bdown\b/i, penalty: 8 },
93
- { token: 'left', pattern: /\bleft\b/i, penalty: 8 },
94
- { token: 'up', pattern: /\bup\b/i, penalty: 8 },
95
- { token: 'corner', pattern: /\bcorner\b/i, penalty: 12 },
96
- { token: 'break', pattern: /\bbreak\b/i, penalty: 18 },
97
- { token: 'broken', pattern: /\bbroken\b/i, penalty: 18 },
98
- { token: 'locked', pattern: /\blocked\b/i, penalty: 18 },
99
- { token: 'orange', pattern: /\borange\b/i, penalty: 12 },
100
- ]);
101
-
102
- const VARIANT_TOKENS = new Set(VARIANT_PENALTIES.map((rule) => rule.token));
103
-
104
- const REQUESTED_VARIANT_ALIASES = Object.freeze({
105
- off: ['disabled', 'disable', 'muted', 'mute', 'off', 'broken'],
106
- brand: ['brand', 'logo'],
107
- open: ['open', 'unlock', 'unlocked'],
108
- unlock: ['open', 'unlock', 'unlocked'],
109
- ban: ['ban', 'banned', 'block', 'blocked'],
110
- blocked: ['ban', 'banned', 'block', 'blocked'],
111
- add: ['add', 'create', 'plus'],
112
- plus: ['add', 'create', 'plus'],
113
- edit: ['edit', 'editing', 'modify', 'pencil'],
114
- remove: ['remove', 'removed', 'delete', 'minus'],
115
- delete: ['delete', 'remove', 'trash'],
116
- minus: ['minus', 'remove', 'removed', 'delete'],
117
- cancel: ['cancel', 'canceled', 'cancelled', 'disabled', 'remove'],
118
- x: ['x', 'close', 'remove', 'delete', 'blocked', 'broken', 'off'],
119
- exclamation: ['alert', 'warning', 'exclamation'],
120
- discount: ['discount', 'coupon', 'coupons', 'promo', 'promotion', 'deal'],
121
- heart: ['heart', 'favorite', 'favourite', 'liked', 'wishlist'],
122
- ai: ['ai', 'smart', 'assistant', 'automation'],
123
- break: ['break', 'broken'],
124
- broken: ['break', 'broken'],
125
- locked: ['lock', 'locked', 'secure', 'security'],
126
- ruble: ['ruble', 'rouble', 'rub'],
127
- franc: ['franc', 'chf'],
128
- lira: ['lira'],
129
- bitcoin: ['bitcoin', 'btc'],
130
- dollar: ['dollar', 'usd'],
131
- yuan: ['yuan', 'cny'],
132
- });
133
-
134
- const DIRECT_LOCALIZED_INTENT_RULES = Object.freeze([
135
- {
136
- pattern: /通知|お知らせ|알림|notificaciones?|benachrichtigungen?|notifica(?:ç|c)[]o|notificações?/iu,
137
- terms: ['notification', 'notifications'],
138
- },
139
- {
140
- pattern: /关闭|關閉|オフ|꺼짐|끄기|desactivad[ao]s?|apagad[ao]s?|aus\b|deaktiviert|disabled|muted|mute|off/iu,
141
- terms: ['off', 'disabled'],
142
- },
143
- ]);
144
-
145
- const COMMON_SLOT_PREFERENCE_RULES = Object.freeze([
146
- {
147
- slotPatterns: [/\busers\b/i, /team/i, /members?/i],
148
- queryVariants: ['users', 'team', 'user group', 'people'],
149
- iconPreferences: [
150
- { pattern: /^users(?:_|-|$)|(?:_|-)users(?:_|-|$)|users-group|user-group|user_circle|user-circle/i, bonus: 66 },
151
- { pattern: /^user(?:_|-|$)|(?:_|-)user(?:_|-|$)/i, bonus: 18 },
152
- ],
153
- },
154
- {
155
- slotPatterns: [
156
- /profile/i,
157
- /account/i,
158
- /\buser\b/i,
159
- /\busers\b/i,
160
- /avatar/i,
161
- /\u8d26\u6237|\u5e10\u6237|\u5e33\u6236|\u500b\u4eba\u8cc7\u6599|\u4e2a\u4eba\u8d44\u6599|\u7528\u6237|\u4f7f\u7528\u8005/u,
162
- /\u30a2\u30ab\u30a6\u30f3\u30c8|\u30d7\u30ed\u30d5\u30a3\u30fc\u30eb|\u30e6\u30fc\u30b6\u30fc/u,
163
- /\uacc4\uc815|\ud504\ub85c\ud544|\uc0ac\uc6a9\uc790/u,
164
- /cuenta|perfil|usuario/i,
165
- /konto|profil|benutzer/i,
166
- /conta|perfil|usu[aá]rio|utilizador/i,
167
- /\u0627\u0644\u062d\u0633\u0627\u0628|\u0645\u0644\u0641\s+\u0634\u062e\u0635\u064a|\u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645/u,
168
- /\u0916\u093e\u0924\u093e|\u092a\u094d\u0930\u094b\u092b\u093c\u093e\u0907\u0932|\u092a\u094d\u0930\u094b\u092b\u093e\u0907\u0932|\u0909\u092a\u092f\u094b\u0917\u0915\u0930\u094d\u0924\u093e/u,
169
- /t[aà]i kho[aả]n|tai khoan|h[oồ] s[oơ]|ho so|ng[uư][oờ]i d[uù]ng|nguoi dung/i,
170
- /\u0e1a\u0e31\u0e0d\u0e0a\u0e35|\u0e42\u0e1b\u0e23\u0e44\u0e1f\u0e25\u0e4c|\u0e1c\u0e39\u0e49\u0e43\u0e0a\u0e49/u,
171
- ],
172
- queryVariants: ['user profile', 'account user', 'avatar person', 'user'],
173
- iconPreferences: [
174
- { pattern: /^user(?:_|-|$)|(?:_|-)user(?:_|-|$)|users|profile|avatar|circle-user|user-circle/i, bonus: 42 },
175
- { pattern: /person|contact/i, bonus: 12 },
176
- ],
177
- },
178
- {
179
- slotPatterns: [
180
- /\bhome\b/i,
181
- /\bmain\b/i,
182
- /\u9996\u9875|\u9996\u9801|\u4e3b\u9875|\u4e3b\u9801/u,
183
- /\u30db\u30fc\u30e0|\u30e1\u30a4\u30f3/u,
184
- /\ud648|\uba54\uc778/u,
185
- /inicio|principal/i,
186
- /startseite|hauptseite/i,
187
- /\u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629|\u0627\u0644\u0635\u0641\u062d\u0629\s+\u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629/u,
188
- /\u0e2b\u0e19\u0e49\u0e32\u0e2b\u0e25\u0e31\u0e01/u,
189
- ],
190
- queryVariants: ['home', 'house'],
191
- iconPreferences: [
192
- { pattern: /^home(?:_|-|$)|(?:_|-)home(?:_|-|$)|house/i, bonus: 40 },
193
- ],
194
- },
195
- {
196
- slotPatterns: [
197
- /alerts?/i,
198
- /notifications?/i,
199
- /bell/i,
200
- /\u901a\u77e5|\u63d0\u9192/u,
201
- /\u30a2\u30e9\u30fc\u30c8|\u304a\u77e5\u3089\u305b|\u901a\u77e5/u,
202
- /\uc54c\ub9bc/u,
203
- /notificaci[oó]n|notificaciones|alerta/i,
204
- /benachrichtigung|benachrichtigungen/i,
205
- /notifica(?:ç|c)[aã]o|notifica(?:ç|c)[oõ]es|notificacoes|alerta/i,
206
- /\u0627\u0644\u0625\u0634\u0639\u0627\u0631|\u0627\u0644\u0625\u0634\u0639\u0627\u0631\u0627\u062a/u,
207
- /\u0938\u0942\u091a\u0928\u093e|\u0938\u0942\u091a\u0928\u093e\u090f\u0901|\u0905\u0927\u093f\u0938\u0942\u091a\u0928\u093e/u,
208
- /th[oô]ng b[aá]o|thong bao|c[aả]nh b[aá]o|canh bao/i,
209
- /\u0e41\u0e08\u0e49\u0e07\u0e40\u0e15\u0e37\u0e2d\u0e19|\u0e01\u0e32\u0e23\u0e41\u0e08\u0e49\u0e07\u0e40\u0e15\u0e37\u0e2d\u0e19/u,
210
- ],
211
- queryVariants: ['notification', 'bell', 'alert', 'alarm'],
212
- iconPreferences: [
213
- { pattern: /notification|bell/i, bonus: 46 },
214
- { pattern: /alarm|alert/i, bonus: 18 },
215
- ],
216
- },
217
- {
218
- priority: 90,
219
- slotPatterns: [/notifications?\s+off/i, /disabled notifications?/i, /muted notifications?/i, /notification\s+off/i],
220
- queryVariants: ['bell slash', 'bell off', 'notification off', 'muted bell'],
221
- iconPreferences: [
222
- { pattern: /^bell[_-]?(off|slash)$|^bell[_-]?simple[_-]?slash$|notification[_-]?off|notifications?[_-]?off/i, bonus: 180 },
223
- { pattern: /bell|notification/i, bonus: 22 },
224
- ],
225
- },
226
- {
227
- slotPatterns: [
228
- /privacy/i,
229
- /security/i,
230
- /private/i,
231
- /safe/i,
232
- /protection/i,
233
- /\u9690\u79c1|\u96b1\u79c1|\u5b89\u5168/u,
234
- /\u30d7\u30e9\u30a4\u30d0\u30b7\u30fc|\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3|\u5b89\u5168/u,
235
- /\uac1c\uc778\uc815\ubcf4|\uac1c\uc778\s+\uc815\ubcf4|\ubcf4\uc548|\uc548\uc804/u,
236
- /privacidad|seguridad/i,
237
- /datenschutz|sicherheit/i,
238
- /privacidade|seguran[cç]a|seguranca/i,
239
- /\u0627\u0644\u062e\u0635\u0648\u0635\u064a\u0629|\u0627\u0644\u0623\u0645\u0627\u0646|\u0627\u0644\u0627\u0645\u0627\u0646|\u0627\u0644\u0623\u0645\u0646|\u0627\u0644\u0627\u0645\u0646/u,
240
- /\u0917\u094b\u092a\u0928\u0940\u092f\u0924\u093e|\u0938\u0941\u0930\u0915\u094d\u0937\u093e/u,
241
- /quy[eề]n ri[eê]ng t[uư]|quyen rieng tu|b[aả]o m[aậ]t|bao mat|an to[aà]n|an toan/i,
242
- /\u0e04\u0e27\u0e32\u0e21\u0e40\u0e1b\u0e47\u0e19\u0e2a\u0e48\u0e27\u0e19\u0e15\u0e31\u0e27|\u0e04\u0e27\u0e32\u0e21\u0e1b\u0e25\u0e2d\u0e14\u0e20\u0e31\u0e22/u,
243
- ],
244
- queryVariants: ['shield lock', 'lock', 'shield', 'privacy security', 'security'],
245
- iconPreferences: [
246
- { pattern: /^shield$|^shield[_-]?check$|shield-check|shield_check/i, bonus: 82 },
247
- { pattern: /shield.*lock|lock.*shield|shield/i, bonus: 58 },
248
- { pattern: /^lock$|^lock[_-]?keyhole$|(?:_|-)lock(?:_|-|$)|key|fingerprint/i, bonus: 34 },
249
- { pattern: /open|unlock|ban|minus|off|slash/i, bonus: -54 },
250
- ],
251
- },
252
- {
253
- priority: 120,
254
- slotPatterns: [/unlock/i, /open account/i, /unlocked account/i],
255
- queryVariants: ['lock open', 'unlock', 'lock keyhole open'],
256
- iconPreferences: [
257
- { pattern: /^lock[_-]?open$|^lock[_-]?keyhole[_-]?open$|^unlock(?:[_-]?keyhole)?$/i, bonus: 160 },
258
- { pattern: /lock.*open|open.*lock|unlock/i, bonus: 80 },
259
- { pattern: /^user(?:_|-|$)|(?:_|-)user(?:_|-|$)|file-user/i, bonus: -70 },
260
- ],
261
- },
262
- {
263
- priority: 120,
264
- slotPatterns: [/blocked user/i, /banned user/i, /user blocked/i, /user banned/i],
265
- queryVariants: ['user x', 'user minus', 'ban user', 'blocked user'],
266
- iconPreferences: [
267
- { pattern: /^user[_-]?x$|^user[_-]?minus$|user-round-x|user-round-minus|shield-ban|ban/i, bonus: 150 },
268
- { pattern: /^user(?:_|-|$)|(?:_|-)user(?:_|-|$)/i, bonus: 24 },
269
- { pattern: /^file-user$|^user-2$/i, bonus: -80 },
270
- ],
271
- },
272
- {
273
- slotPatterns: [
274
- /appearance/i,
275
- /theme/i,
276
- /color/i,
277
- /palette/i,
278
- /dark mode/i,
279
- /light mode/i,
280
- /\u5916\u89c2|\u5916\u89c0|\u4e3b\u9898|\u4e3b\u984c|\u989c\u8272|\u984f\u8272/u,
281
- /\u5916\u89b3|\u30c6\u30fc\u30de|\u8868\u793a|\u914d\u8272/u,
282
- /\uc678\uad00|\ud14c\ub9c8|\uc0c9\uc0c1|\ud654\uba74/u,
283
- /apariencia|tema|colou?r|modo/i,
284
- /erscheinungsbild|design|darstellung|thema/i,
285
- /apar[eê]ncia|tema|cor/i,
286
- /\u0627\u0644\u0645\u0638\u0647\u0631|\u0627\u0644\u0633\u0645\u0629|\u0627\u0644\u0648\u0636\u0639|\u0627\u0644\u0623\u0644\u0648\u0627\u0646/u,
287
- /\u0930\u0942\u092a|\u0925\u0940\u092e|\u0926\u093f\u0916\u093e\u0935\u091f|\u0930\u0902\u0917/u,
288
- /giao di[eệ]n|giao dien|ch[uủ] \u0111[eề]|chu de|m[aà]u|mau/i,
289
- /\u0e23\u0e39\u0e1b\u0e25\u0e31\u0e01\u0e29\u0e13\u0e4c|\u0e18\u0e35\u0e21|\u0e2a\u0e35|\u0e2b\u0e19\u0e49\u0e32\u0e15\u0e32/u,
290
- ],
291
- queryVariants: ['palette', 'moon', 'theme', 'sun moon', 'appearance'],
292
- iconPreferences: [
293
- { pattern: /^palette$|paint|brush|color|swatch/i, bonus: 54 },
294
- { pattern: /^moon$|sun-moon|sun|theme/i, bonus: 28 },
295
- ],
296
- },
297
- {
298
- slotPatterns: [
299
- /language/i,
300
- /locale/i,
301
- /translate/i,
302
- /translation/i,
303
- /\u8bed\u8a00/u,
304
- /\u8a9e\u8a00/u,
305
- /\u8a00\u8a9e/u,
306
- /\uc5b8\uc5b4/u,
307
- /idioma/i,
308
- /sprache/i,
309
- /l[ií]ngua/i,
310
- /langue/i,
311
- /\u0627\u0644\u0644\u063a\u0629|\u0644\u063a\u0629/u,
312
- /\u092d\u093e\u0937\u093e/u,
313
- /ng[oô]n ng[uữ]|ngon ngu/i,
314
- /\u0e20\u0e32\u0e29\u0e32/u,
315
- ],
316
- queryVariants: ['globe', 'languages', 'translate', 'language'],
317
- iconPreferences: [
318
- { pattern: /^globe$|^languages?$|^translate$|(?:_|-)(globe|languages?|translate)(?:_|-|$)/i, bonus: 70 },
319
- { pattern: /globe|language|translate/i, bonus: 28 },
320
- ],
321
- },
322
- {
323
- slotPatterns: [
324
- /language/i,
325
- /locale/i,
326
- /translate/i,
327
- /translation/i,
328
- /语言/u,
329
- /語言/u,
330
- /言語/u,
331
- /언어/u,
332
- /idioma/i,
333
- /sprache/i,
334
- /língua/i,
335
- /langue/i,
336
- /اللغة/u,
337
- /भाषा/u,
338
- /ngôn ngữ/i,
339
- /ภาษา/u,
340
- ],
341
- queryVariants: ['globe', 'languages', 'translate', 'language'],
342
- iconPreferences: [
343
- { pattern: /^globe$|^languages?$|^translate$|(?:_|-)(globe|languages?|translate)(?:_|-|$)/i, bonus: 70 },
344
- { pattern: /globe|language|translate/i, bonus: 28 },
345
- ],
346
- },
347
- {
348
- slotPatterns: [/create/i, /\badd\b/i, /\bplus\b/i, /compose/i, /new item/i],
349
- queryVariants: ['add', 'plus', 'create new', 'compose'],
350
- iconPreferences: [
351
- { pattern: /^(add|plus)(?:_|-|$)/i, bonus: 48 },
352
- { pattern: /(?:_|-)(add|plus)(?:_|-|$)/i, bonus: 18 },
353
- { pattern: /compose|edit|pencil/i, bonus: 8 },
354
- ],
355
- },
356
- {
357
- slotPatterns: [/archive/i],
358
- queryVariants: ['archive', 'archive box', 'box archive'],
359
- iconPreferences: [
360
- { pattern: /^archive(?:_|-|$)|(?:_|-)archive(?:_|-|$)/i, bonus: 40 },
361
- { pattern: /box|tray/i, bonus: 8 },
362
- ],
363
- },
364
- {
365
- slotPatterns: [/alerts?/i, /notifications?/i, /bell/i, /通知/u, /알림/u, /通知/u],
366
- queryVariants: ['notification', 'bell', 'alert', 'alarm'],
51
+ const VARIANT_PENALTIES = Object.freeze([
52
+ { token: 'circle', pattern: /circle/i, penalty: 12 },
53
+ { token: 'square', pattern: /square/i, penalty: 12 },
54
+ { token: 'dash', pattern: /dash/i, penalty: 5 },
55
+ { token: 'badge', pattern: /badge/i, penalty: 4 },
56
+ { token: 'brand', pattern: /\bbrand\b/i, penalty: 30 },
57
+ { token: 'off', pattern: /\boff\b/i, penalty: 18 },
58
+ { token: 'slash', pattern: /slash/i, penalty: 8 },
59
+ { token: 'warning', pattern: /warning/i, penalty: 5 },
60
+ { token: 'ai', pattern: /\bai\b/i, penalty: 18 },
61
+ { token: 'add', pattern: /\badd\b/i, penalty: 12 },
62
+ { token: 'plus', pattern: /\bplus\b/i, penalty: 18 },
63
+ { token: 'edit', pattern: /\bedit\b/i, penalty: 12 },
64
+ { token: 'remove', pattern: /\bremove\b/i, penalty: 12 },
65
+ { token: 'delete', pattern: /\bdelete\b/i, penalty: 12 },
66
+ { token: 'minus', pattern: /\bminus\b/i, penalty: 24 },
67
+ { token: 'cancel', pattern: /\bcancel\b/i, penalty: 30 },
68
+ { token: 'x', pattern: /\bx\b/i, penalty: 30 },
69
+ { token: 'exclamation', pattern: /\bexclamation\b/i, penalty: 24 },
70
+ { token: 'discount', pattern: /\bdiscount\b/i, penalty: 24 },
71
+ { token: 'heart', pattern: /\bheart\b/i, penalty: 18 },
72
+ { token: 'zap', pattern: /\bzap\b/i, penalty: 30 },
73
+ { token: 'bolt', pattern: /\bbolt\b/i, penalty: 18 },
74
+ { token: 'wifi', pattern: /\bwifi\b/i, penalty: 12 },
75
+ { token: 'align', pattern: /\balign\b/i, penalty: 12 },
76
+ { token: 'fruit', pattern: /\bfruit\b/i, penalty: 12 },
77
+ { token: 'open', pattern: /\bopen\b/i, penalty: 28 },
78
+ { token: 'unlock', pattern: /\bunlock(?:ed)?\b/i, penalty: 28 },
79
+ { token: 'ban', pattern: /\bban\b/i, penalty: 24 },
80
+ { token: 'blocked', pattern: /\bblocked\b/i, penalty: 24 },
81
+ { token: 'rupee', pattern: /\brupee\b/i, penalty: 18 },
82
+ { token: 'ruble', pattern: /\bruble\b/i, penalty: 18 },
83
+ { token: 'franc', pattern: /\bfranc\b/i, penalty: 18 },
84
+ { token: 'lira', pattern: /\blira\b/i, penalty: 18 },
85
+ { token: 'bitcoin', pattern: /\bbitcoin\b/i, penalty: 18 },
86
+ { token: 'dollar', pattern: /\bdollar\b/i, penalty: 18 },
87
+ { token: 'cent', pattern: /\bcent\b/i, penalty: 18 },
88
+ { token: 'yen', pattern: /\byen\b/i, penalty: 18 },
89
+ { token: 'yuan', pattern: /\byuan\b/i, penalty: 18 },
90
+ { token: 'euro', pattern: /\beuro\b/i, penalty: 18 },
91
+ { token: 'pound', pattern: /\bpound\b/i, penalty: 18 },
92
+ { token: 'down', pattern: /\bdown\b/i, penalty: 8 },
93
+ { token: 'left', pattern: /\bleft\b/i, penalty: 8 },
94
+ { token: 'up', pattern: /\bup\b/i, penalty: 8 },
95
+ { token: 'corner', pattern: /\bcorner\b/i, penalty: 12 },
96
+ { token: 'break', pattern: /\bbreak\b/i, penalty: 18 },
97
+ { token: 'broken', pattern: /\bbroken\b/i, penalty: 18 },
98
+ { token: 'locked', pattern: /\blocked\b/i, penalty: 18 },
99
+ { token: 'orange', pattern: /\borange\b/i, penalty: 12 },
100
+ ]);
101
+
102
+ const VARIANT_TOKENS = new Set(VARIANT_PENALTIES.map((rule) => rule.token));
103
+
104
+ const REQUESTED_VARIANT_ALIASES = Object.freeze({
105
+ off: ['disabled', 'disable', 'muted', 'mute', 'off', 'broken'],
106
+ brand: ['brand', 'logo'],
107
+ open: ['open', 'unlock', 'unlocked'],
108
+ unlock: ['open', 'unlock', 'unlocked'],
109
+ ban: ['ban', 'banned', 'block', 'blocked'],
110
+ blocked: ['ban', 'banned', 'block', 'blocked'],
111
+ add: ['add', 'create', 'plus'],
112
+ plus: ['add', 'create', 'plus'],
113
+ edit: ['edit', 'editing', 'modify', 'pencil'],
114
+ remove: ['remove', 'removed', 'delete', 'minus'],
115
+ delete: ['delete', 'remove', 'trash'],
116
+ minus: ['minus', 'remove', 'removed', 'delete'],
117
+ cancel: ['cancel', 'canceled', 'cancelled', 'disabled', 'remove'],
118
+ x: ['x', 'close', 'remove', 'delete', 'blocked', 'broken', 'off'],
119
+ exclamation: ['alert', 'warning', 'exclamation'],
120
+ discount: ['discount', 'coupon', 'coupons', 'promo', 'promotion', 'deal'],
121
+ heart: ['heart', 'favorite', 'favourite', 'liked', 'wishlist'],
122
+ ai: ['ai', 'smart', 'assistant', 'automation'],
123
+ break: ['break', 'broken'],
124
+ broken: ['break', 'broken'],
125
+ locked: ['lock', 'locked', 'secure', 'security'],
126
+ ruble: ['ruble', 'rouble', 'rub'],
127
+ franc: ['franc', 'chf'],
128
+ lira: ['lira'],
129
+ bitcoin: ['bitcoin', 'btc'],
130
+ dollar: ['dollar', 'usd'],
131
+ yuan: ['yuan', 'cny'],
132
+ });
133
+
134
+ const DIRECT_LOCALIZED_INTENT_RULES = Object.freeze([
135
+ {
136
+ pattern: /\u901a\u77e5|\u304a\u77e5\u3089\u305b|\uc54c\ub9bc|notificaciones?|benachrichtigungen?|notifica(?:\u00e7|c)[a\u00e3]o|notifica(?:\u00e7|c)[o\u00f5]es?|notificacoes?/iu,
137
+ terms: ['notification', 'notifications'],
138
+ },
139
+ {
140
+ pattern: /\u5173\u95ed|\u95dc\u9589|\u30aa\u30d5|\uaebc\uc9d0|\ub044\uae30|desactivad[ao]s?|apagad[ao]s?|aus\b|deaktiviert|disabled|muted|mute|off/iu,
141
+ terms: ['off', 'disabled'],
142
+ },
143
+ ]);
144
+
145
+ const COMMON_SLOT_PREFERENCE_RULES = Object.freeze([
146
+ {
147
+ slotPatterns: [/\busers\b/i, /team/i, /members?/i],
148
+ queryVariants: ['users', 'team', 'user group', 'people'],
149
+ iconPreferences: [
150
+ { pattern: /^users(?:_|-|$)|(?:_|-)users(?:_|-|$)|users-group|user-group|user_circle|user-circle/i, bonus: 66 },
151
+ { pattern: /^user(?:_|-|$)|(?:_|-)user(?:_|-|$)/i, bonus: 18 },
152
+ ],
153
+ },
154
+ {
155
+ slotPatterns: [
156
+ /profile/i,
157
+ /account/i,
158
+ /\buser\b/i,
159
+ /\busers\b/i,
160
+ /avatar/i,
161
+ /\u8d26\u6237|\u5e10\u6237|\u5e33\u6236|\u500b\u4eba\u8cc7\u6599|\u4e2a\u4eba\u8d44\u6599|\u7528\u6237|\u4f7f\u7528\u8005/u,
162
+ /\u30a2\u30ab\u30a6\u30f3\u30c8|\u30d7\u30ed\u30d5\u30a3\u30fc\u30eb|\u30e6\u30fc\u30b6\u30fc/u,
163
+ /\uacc4\uc815|\ud504\ub85c\ud544|\uc0ac\uc6a9\uc790/u,
164
+ /cuenta|perfil|usuario/i,
165
+ /konto|profil|benutzer/i,
166
+ /conta|perfil|usu[aá]rio|utilizador/i,
167
+ /\u0627\u0644\u062d\u0633\u0627\u0628|\u0645\u0644\u0641\s+\u0634\u062e\u0635\u064a|\u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645/u,
168
+ /\u0916\u093e\u0924\u093e|\u092a\u094d\u0930\u094b\u092b\u093c\u093e\u0907\u0932|\u092a\u094d\u0930\u094b\u092b\u093e\u0907\u0932|\u0909\u092a\u092f\u094b\u0917\u0915\u0930\u094d\u0924\u093e/u,
169
+ /t[aà]i kho[aả]n|tai khoan|h[oồ] s[oơ]|ho so|ng[uư][oờ]i d[uù]ng|nguoi dung/i,
170
+ /\u0e1a\u0e31\u0e0d\u0e0a\u0e35|\u0e42\u0e1b\u0e23\u0e44\u0e1f\u0e25\u0e4c|\u0e1c\u0e39\u0e49\u0e43\u0e0a\u0e49/u,
171
+ ],
172
+ queryVariants: ['user profile', 'account user', 'avatar person', 'user'],
173
+ iconPreferences: [
174
+ { pattern: /^user(?:_|-|$)|(?:_|-)user(?:_|-|$)|users|profile|avatar|circle-user|user-circle/i, bonus: 42 },
175
+ { pattern: /person|contact/i, bonus: 12 },
176
+ ],
177
+ },
178
+ {
179
+ slotPatterns: [
180
+ /\bhome\b/i,
181
+ /\bmain\b/i,
182
+ /\u9996\u9875|\u9996\u9801|\u4e3b\u9875|\u4e3b\u9801/u,
183
+ /\u30db\u30fc\u30e0|\u30e1\u30a4\u30f3/u,
184
+ /\ud648|\uba54\uc778/u,
185
+ /inicio|principal/i,
186
+ /startseite|hauptseite/i,
187
+ /\u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629|\u0627\u0644\u0635\u0641\u062d\u0629\s+\u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629/u,
188
+ /\u0e2b\u0e19\u0e49\u0e32\u0e2b\u0e25\u0e31\u0e01/u,
189
+ ],
190
+ queryVariants: ['home', 'house'],
191
+ iconPreferences: [
192
+ { pattern: /^home(?:_|-|$)|(?:_|-)home(?:_|-|$)|house/i, bonus: 40 },
193
+ ],
194
+ },
195
+ {
196
+ slotPatterns: [
197
+ /alerts?/i,
198
+ /notifications?/i,
199
+ /bell/i,
200
+ /\u901a\u77e5|\u63d0\u9192/u,
201
+ /\u30a2\u30e9\u30fc\u30c8|\u304a\u77e5\u3089\u305b|\u901a\u77e5/u,
202
+ /\uc54c\ub9bc/u,
203
+ /notificaci[oó]n|notificaciones|alerta/i,
204
+ /benachrichtigung|benachrichtigungen/i,
205
+ /notifica(?:ç|c)[aã]o|notifica(?:ç|c)[oõ]es|notificacoes|alerta/i,
206
+ /\u0627\u0644\u0625\u0634\u0639\u0627\u0631|\u0627\u0644\u0625\u0634\u0639\u0627\u0631\u0627\u062a/u,
207
+ /\u0938\u0942\u091a\u0928\u093e|\u0938\u0942\u091a\u0928\u093e\u090f\u0901|\u0905\u0927\u093f\u0938\u0942\u091a\u0928\u093e/u,
208
+ /th[oô]ng b[aá]o|thong bao|c[aả]nh b[aá]o|canh bao/i,
209
+ /\u0e41\u0e08\u0e49\u0e07\u0e40\u0e15\u0e37\u0e2d\u0e19|\u0e01\u0e32\u0e23\u0e41\u0e08\u0e49\u0e07\u0e40\u0e15\u0e37\u0e2d\u0e19/u,
210
+ ],
211
+ queryVariants: ['notification', 'bell', 'alert', 'alarm'],
212
+ iconPreferences: [
213
+ { pattern: /notification|bell/i, bonus: 46 },
214
+ { pattern: /alarm|alert/i, bonus: 18 },
215
+ ],
216
+ },
217
+ {
218
+ priority: 90,
219
+ slotPatterns: [/notifications?\s+off/i, /disabled notifications?/i, /muted notifications?/i, /notification\s+off/i],
220
+ queryVariants: ['bell slash', 'bell off', 'notification off', 'muted bell'],
221
+ iconPreferences: [
222
+ { pattern: /^bell[_-]?(off|slash)$|^bell[_-]?simple[_-]?slash$|notification[_-]?off|notifications?[_-]?off/i, bonus: 180 },
223
+ { pattern: /bell|notification/i, bonus: 22 },
224
+ ],
225
+ },
226
+ {
227
+ slotPatterns: [
228
+ /privacy/i,
229
+ /security/i,
230
+ /private/i,
231
+ /safe/i,
232
+ /protection/i,
233
+ /\u9690\u79c1|\u96b1\u79c1|\u5b89\u5168/u,
234
+ /\u30d7\u30e9\u30a4\u30d0\u30b7\u30fc|\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3|\u5b89\u5168/u,
235
+ /\uac1c\uc778\uc815\ubcf4|\uac1c\uc778\s+\uc815\ubcf4|\ubcf4\uc548|\uc548\uc804/u,
236
+ /privacidad|seguridad/i,
237
+ /datenschutz|sicherheit/i,
238
+ /privacidade|seguran[cç]a|seguranca/i,
239
+ /\u0627\u0644\u062e\u0635\u0648\u0635\u064a\u0629|\u0627\u0644\u0623\u0645\u0627\u0646|\u0627\u0644\u0627\u0645\u0627\u0646|\u0627\u0644\u0623\u0645\u0646|\u0627\u0644\u0627\u0645\u0646/u,
240
+ /\u0917\u094b\u092a\u0928\u0940\u092f\u0924\u093e|\u0938\u0941\u0930\u0915\u094d\u0937\u093e/u,
241
+ /quy[eề]n ri[eê]ng t[uư]|quyen rieng tu|b[aả]o m[aậ]t|bao mat|an to[aà]n|an toan/i,
242
+ /\u0e04\u0e27\u0e32\u0e21\u0e40\u0e1b\u0e47\u0e19\u0e2a\u0e48\u0e27\u0e19\u0e15\u0e31\u0e27|\u0e04\u0e27\u0e32\u0e21\u0e1b\u0e25\u0e2d\u0e14\u0e20\u0e31\u0e22/u,
243
+ ],
244
+ queryVariants: ['shield lock', 'lock', 'shield', 'privacy security', 'security'],
245
+ iconPreferences: [
246
+ { pattern: /^shield$|^shield[_-]?check$|shield-check|shield_check/i, bonus: 82 },
247
+ { pattern: /shield.*lock|lock.*shield|shield/i, bonus: 58 },
248
+ { pattern: /^lock$|^lock[_-]?keyhole$|(?:_|-)lock(?:_|-|$)|key|fingerprint/i, bonus: 34 },
249
+ { pattern: /open|unlock|ban|minus|off|slash/i, bonus: -54 },
250
+ ],
251
+ },
252
+ {
253
+ priority: 120,
254
+ slotPatterns: [/unlock/i, /open account/i, /unlocked account/i],
255
+ queryVariants: ['lock open', 'unlock', 'lock keyhole open'],
256
+ iconPreferences: [
257
+ { pattern: /^lock[_-]?open$|^lock[_-]?keyhole[_-]?open$|^unlock(?:[_-]?keyhole)?$/i, bonus: 160 },
258
+ { pattern: /lock.*open|open.*lock|unlock/i, bonus: 80 },
259
+ { pattern: /^user(?:_|-|$)|(?:_|-)user(?:_|-|$)|file-user/i, bonus: -70 },
260
+ ],
261
+ },
262
+ {
263
+ priority: 120,
264
+ slotPatterns: [/blocked user/i, /banned user/i, /user blocked/i, /user banned/i],
265
+ queryVariants: ['user x', 'user minus', 'ban user', 'blocked user'],
266
+ iconPreferences: [
267
+ { pattern: /^user[_-]?x$|^user[_-]?minus$|user-round-x|user-round-minus|shield-ban|ban/i, bonus: 150 },
268
+ { pattern: /^user(?:_|-|$)|(?:_|-)user(?:_|-|$)/i, bonus: 24 },
269
+ { pattern: /^file-user$|^user-2$/i, bonus: -80 },
270
+ ],
271
+ },
272
+ {
273
+ slotPatterns: [
274
+ /appearance/i,
275
+ /theme/i,
276
+ /color/i,
277
+ /palette/i,
278
+ /dark mode/i,
279
+ /light mode/i,
280
+ /\u5916\u89c2|\u5916\u89c0|\u4e3b\u9898|\u4e3b\u984c|\u989c\u8272|\u984f\u8272/u,
281
+ /\u5916\u89b3|\u30c6\u30fc\u30de|\u8868\u793a|\u914d\u8272/u,
282
+ /\uc678\uad00|\ud14c\ub9c8|\uc0c9\uc0c1|\ud654\uba74/u,
283
+ /apariencia|tema|colou?r|modo/i,
284
+ /erscheinungsbild|design|darstellung|thema/i,
285
+ /apar[eê]ncia|tema|cor/i,
286
+ /\u0627\u0644\u0645\u0638\u0647\u0631|\u0627\u0644\u0633\u0645\u0629|\u0627\u0644\u0648\u0636\u0639|\u0627\u0644\u0623\u0644\u0648\u0627\u0646/u,
287
+ /\u0930\u0942\u092a|\u0925\u0940\u092e|\u0926\u093f\u0916\u093e\u0935\u091f|\u0930\u0902\u0917/u,
288
+ /giao di[eệ]n|giao dien|ch[uủ] \u0111[eề]|chu de|m[aà]u|mau/i,
289
+ /\u0e23\u0e39\u0e1b\u0e25\u0e31\u0e01\u0e29\u0e13\u0e4c|\u0e18\u0e35\u0e21|\u0e2a\u0e35|\u0e2b\u0e19\u0e49\u0e32\u0e15\u0e32/u,
290
+ ],
291
+ queryVariants: ['palette', 'moon', 'theme', 'sun moon', 'appearance'],
292
+ iconPreferences: [
293
+ { pattern: /^palette$|paint|brush|color|swatch/i, bonus: 54 },
294
+ { pattern: /^moon$|sun-moon|sun|theme/i, bonus: 28 },
295
+ ],
296
+ },
297
+ {
298
+ slotPatterns: [
299
+ /language/i,
300
+ /locale/i,
301
+ /translate/i,
302
+ /translation/i,
303
+ /\u8bed\u8a00/u,
304
+ /\u8a9e\u8a00/u,
305
+ /\u8a00\u8a9e/u,
306
+ /\uc5b8\uc5b4/u,
307
+ /idioma/i,
308
+ /sprache/i,
309
+ /l[ií]ngua/i,
310
+ /langue/i,
311
+ /\u0627\u0644\u0644\u063a\u0629|\u0644\u063a\u0629/u,
312
+ /\u092d\u093e\u0937\u093e/u,
313
+ /ng[oô]n ng[uữ]|ngon ngu/i,
314
+ /\u0e20\u0e32\u0e29\u0e32/u,
315
+ ],
316
+ queryVariants: ['globe', 'languages', 'translate', 'language'],
317
+ iconPreferences: [
318
+ { pattern: /^globe$|^languages?$|^translate$|(?:_|-)(globe|languages?|translate)(?:_|-|$)/i, bonus: 70 },
319
+ { pattern: /globe|language|translate/i, bonus: 28 },
320
+ ],
321
+ },
322
+ {
323
+ slotPatterns: [
324
+ /language/i,
325
+ /locale/i,
326
+ /translate/i,
327
+ /translation/i,
328
+ /语言/u,
329
+ /語言/u,
330
+ /言語/u,
331
+ /언어/u,
332
+ /idioma/i,
333
+ /sprache/i,
334
+ /língua/i,
335
+ /langue/i,
336
+ /اللغة/u,
337
+ /भाषा/u,
338
+ /ngôn ngữ/i,
339
+ /ภาษา/u,
340
+ ],
341
+ queryVariants: ['globe', 'languages', 'translate', 'language'],
342
+ iconPreferences: [
343
+ { pattern: /^globe$|^languages?$|^translate$|(?:_|-)(globe|languages?|translate)(?:_|-|$)/i, bonus: 70 },
344
+ { pattern: /globe|language|translate/i, bonus: 28 },
345
+ ],
346
+ },
347
+ {
348
+ slotPatterns: [/create/i, /\badd\b/i, /\bplus\b/i, /compose/i, /new item/i],
349
+ queryVariants: ['add', 'plus', 'create new', 'compose'],
350
+ iconPreferences: [
351
+ { pattern: /^(add|plus)(?:_|-|$)/i, bonus: 48 },
352
+ { pattern: /(?:_|-)(add|plus)(?:_|-|$)/i, bonus: 18 },
353
+ { pattern: /compose|edit|pencil/i, bonus: 8 },
354
+ ],
355
+ },
356
+ {
357
+ slotPatterns: [/archive/i],
358
+ queryVariants: ['archive', 'archive box', 'box archive'],
359
+ iconPreferences: [
360
+ { pattern: /^archive(?:_|-|$)|(?:_|-)archive(?:_|-|$)/i, bonus: 40 },
361
+ { pattern: /box|tray/i, bonus: 8 },
362
+ ],
363
+ },
364
+ {
365
+ slotPatterns: [/alerts?/i, /notifications?/i, /bell/i, /通知/u, /알림/u, /通知/u],
366
+ queryVariants: ['notification', 'bell', 'alert', 'alarm'],
367
367
  iconPreferences: [
368
368
  { pattern: /notification|bell/i, bonus: 42 },
369
369
  { pattern: /alarm|alert/i, bonus: 16 },
@@ -377,33 +377,33 @@ const COMMON_SLOT_PREFERENCE_RULES = Object.freeze([
377
377
  { pattern: /person|contact/i, bonus: 10 },
378
378
  ],
379
379
  },
380
- {
381
- slotPatterns: [/model/i, /\bai\b/i, /\bml\b/i, /machine learning/i],
382
- queryVariants: ['brain circuit', 'brain cog', 'neural network', 'model'],
380
+ {
381
+ slotPatterns: [/model/i, /\bai\b/i, /\bml\b/i, /machine learning/i],
382
+ queryVariants: ['brain circuit', 'brain cog', 'neural network', 'model'],
383
383
  iconPreferences: [
384
384
  { pattern: /brain-circuit|brain_circuit/i, bonus: 44 },
385
- { pattern: /brain|circuit|nodes/i, bonus: 24 },
386
- ],
387
- },
388
- {
389
- priority: 120,
390
- slotPatterns: [/\bai search\b/i, /smart search/i, /semantic search/i, /assistant search/i],
391
- queryVariants: ['search ai', 'ai search', 'smart search'],
392
- iconPreferences: [
393
- { pattern: /^search.*ai|ai.*search|search[_-]?[23]?[_-]?ai/i, bonus: 150 },
394
- { pattern: /^search(?:_|-|$)|(?:_|-)search(?:_|-|$)/i, bonus: 34 },
395
- { pattern: /brain|robot|spark/i, bonus: 24 },
396
- ],
397
- },
398
- {
399
- priority: 90,
400
- slotPatterns: [/automation/i, /workflow/i, /automate/i, /smart action/i],
401
- queryVariants: ['automation', 'workflow', 'robot', 'refresh', 'sparkles'],
402
- iconPreferences: [
403
- { pattern: /workflow|automation|robot|sparkles?|refresh|settings|adjustments/i, bonus: 90 },
404
- { pattern: /hand|finger|train/i, bonus: -90 },
405
- ],
406
- },
385
+ { pattern: /brain|circuit|nodes/i, bonus: 24 },
386
+ ],
387
+ },
388
+ {
389
+ priority: 120,
390
+ slotPatterns: [/\bai search\b/i, /smart search/i, /semantic search/i, /assistant search/i],
391
+ queryVariants: ['search ai', 'ai search', 'smart search'],
392
+ iconPreferences: [
393
+ { pattern: /^search.*ai|ai.*search|search[_-]?[23]?[_-]?ai/i, bonus: 150 },
394
+ { pattern: /^search(?:_|-|$)|(?:_|-)search(?:_|-|$)/i, bonus: 34 },
395
+ { pattern: /brain|robot|spark/i, bonus: 24 },
396
+ ],
397
+ },
398
+ {
399
+ priority: 90,
400
+ slotPatterns: [/automation/i, /workflow/i, /automate/i, /smart action/i],
401
+ queryVariants: ['automation', 'workflow', 'robot', 'refresh', 'sparkles'],
402
+ iconPreferences: [
403
+ { pattern: /workflow|automation|robot|sparkles?|refresh|settings|adjustments/i, bonus: 90 },
404
+ { pattern: /hand|finger|train/i, bonus: -90 },
405
+ ],
406
+ },
407
407
  {
408
408
  slotPatterns: [/prompt/i],
409
409
  queryVariants: ['message text', 'text input', 'terminal', 'text cursor'],
@@ -444,430 +444,430 @@ const COMMON_SLOT_PREFERENCE_RULES = Object.freeze([
444
444
  { pattern: /chart|signal|radar/i, bonus: 16 },
445
445
  ],
446
446
  },
447
- {
448
- slotPatterns: [/billing/i, /payment/i, /invoice/i, /subscription/i],
449
- queryVariants: ['credit card', 'receipt', 'invoice', 'payment', 'wallet'],
450
- iconPreferences: [
451
- { pattern: /credit-card|receipt|wallet|invoice/i, bonus: 44 },
452
- { pattern: /card|banknote|currency|dollar/i, bonus: 18 },
453
- { pattern: /ruble|franc|lira|bitcoin|yuan/i, bonus: -70 },
454
- ],
455
- },
456
- {
457
- priority: 90,
458
- slotPatterns: [/storefront/i, /\bstore\b/i, /\bshop\b/i],
459
- queryVariants: ['store', 'shop', 'building store', 'shopping bag'],
460
- iconPreferences: [
461
- { pattern: /^store$|^storefront$|building-store|shop[_-]?line|store[_-]?\d?[_-]?line|shopping-bag/i, bonus: 120 },
462
- { pattern: /brand-appstore|restore|cancel|\bx\b|off|discount|heart|exclamation|minus|plus|search|share|star|question|bolt|code|copy|dollar|down|up|pin|pause/i, bonus: -120 },
463
- ],
464
- },
465
- {
466
- priority: 130,
467
- slotPatterns: [/(store|shop|storefront)\s+(off|disabled|closed|cancelled|canceled)/i, /(off|disabled|closed|cancelled|canceled)\s+(store|shop|storefront)/i],
468
- queryVariants: ['store off', 'shopping bag x', 'shopping cart off', 'store disabled'],
469
- iconPreferences: [
470
- { pattern: /(store|shop|shopping|bag|cart).*(off|\bx\b|cancel|disabled)|(off|\bx\b|cancel|disabled).*(store|shop|shopping|bag|cart)/i, bonus: 220 },
471
- { pattern: /^building-store$|^store$|^shopping-bag$/i, bonus: -70 },
472
- ],
473
- },
474
- {
475
- priority: 90,
476
- slotPatterns: [/checkout/i],
477
- queryVariants: ['shopping cart', 'credit card', 'payment', 'receipt checkout'],
478
- iconPreferences: [
479
- { pattern: /^shopping[_-]?cart$|shopping-cart$|credit-card|card-pay|payment|receipt/i, bonus: 140 },
480
- { pattern: /fork|knife|git|branch|merge|forklift|grill|cancel|\bx\b|off|discount|heart|exclamation|minus|plus|search|share|star|question|bolt|code|copy|dollar|down|up|pin|pause/i, bonus: -140 },
481
- ],
482
- },
483
- {
484
- priority: 120,
485
- slotPatterns: [/^cart$/i, /shopping cart/i, /\bbasket\b/i],
486
- queryVariants: ['shopping cart', 'cart', 'basket'],
487
- iconPreferences: [
488
- { pattern: /^shopping[_-]?cart$|^basket$/i, bonus: 220 },
489
- { pattern: /shopping-cart-(cog|x|off|discount|dollar|exclamation|heart|minus|pause|plus|question|share|star|bolt|copy|down|up|search|pin)/i, bonus: -260 },
490
- { pattern: /basket-(cog|x|off|discount|dollar|exclamation|heart|minus|pause|plus|question|share|star|bolt|copy|down|up|search|pin)/i, bonus: -180 },
491
- { pattern: /garden-cart/i, bonus: -180 },
492
- ],
493
- },
494
- {
495
- priority: 90,
496
- slotPatterns: [/customers?/i, /shoppers?/i, /buyers?/i],
497
- queryVariants: ['users', 'customers', 'user group'],
498
- iconPreferences: [
499
- { pattern: /^users(?:_|-|$)|(?:_|-)users(?:_|-|$)|users-group|user-group|user-circle|user_circle|^user(?:_|-|$)/i, bonus: 110 },
500
- { pattern: /ticket|plane|caret|cancel|\bx\b|off|discount|heart|exclamation|minus|plus|search|share|star|question|bolt|code|copy|dollar|down|up|pin|pause/i, bonus: -120 },
501
- ],
502
- },
503
- {
504
- priority: 90,
505
- slotPatterns: [/coupons?/i, /discounts?/i, /promo/i, /promotion/i],
506
- queryVariants: ['coupon', 'tag percent', 'discount', 'percentage'],
507
- iconPreferences: [
508
- { pattern: /coupon|ticket-percent|badge-percent|percent|percentage|tag|shopping-cart-discount|shopping-bag-discount|seal-percent/i, bonus: 120 },
509
- { pattern: /^percentage-\d+$|bean|candy|cannabis|off|slash|disabled|alert/i, bonus: -180 },
510
- ],
511
- },
512
- {
513
- priority: 130,
514
- slotPatterns: [/(cancel|cancelled|canceled|remove|delete)\s+orders?/i, /orders?\s+(cancel|cancelled|canceled|remove|delete)/i],
515
- queryVariants: ['shopping cart cancel', 'basket cancel', 'cancel order', 'order x'],
516
- iconPreferences: [
517
- { pattern: /cancel|\bx\b|remove|delete|minus|trash/i, bonus: 220 },
518
- { pattern: /shopping|cart|basket|package|receipt|clipboard|list/i, bonus: 20 },
519
- ],
520
- },
521
- {
522
- priority: 90,
523
- slotPatterns: [/orders?/i, /purchases?/i],
524
- queryVariants: ['package', 'receipt', 'clipboard list', 'shopping bag', 'ordered list'],
525
- iconPreferences: [
526
- { pattern: /^package$|packages|receipt|shopping-bag$|clipboard|list-ordered|file-invoice|file-text/i, bonus: 115 },
527
- { pattern: /border|sort|align|cancel|\bx\b|off|discount|heart|exclamation|minus|plus|search|share|star|question|bolt|code|copy|dollar|down|up|pin|pause/i, bonus: -130 },
528
- ],
529
- },
530
- {
531
- priority: 120,
532
- slotPatterns: [/shipping/i, /delivery/i, /fulfillment/i, /dispatch/i],
533
- queryVariants: ['truck delivery', 'delivery', 'shipping', 'package delivery'],
534
- iconPreferences: [
535
- { pattern: /^truck[_-]?delivery$/i, bonus: 260 },
536
- { pattern: /^truck$|package[_-]?export|package[_-]?import/i, bonus: 180 },
537
- { pattern: /^truck[_-]?return$/i, bonus: -120 },
538
- { pattern: /cloud-upload|upload|ship-off|cloud|arrow-up/i, bonus: -220 },
539
- ],
540
- },
541
- {
542
- priority: 120,
543
- slotPatterns: [/returns?/i, /refunds?/i, /reverse logistics/i],
544
- queryVariants: ['return', 'refund', 'truck return', 'receipt refund'],
545
- iconPreferences: [
546
- { pattern: /^truck[_-]?return$|receipt[_-]?refund|credit-card[_-]?refund|arrow-back|cash.*move.*back/i, bonus: 220 },
547
- { pattern: /player|chevron|stack|upload|cloud|ship-off/i, bonus: -160 },
548
- ],
549
- },
550
- {
551
- priority: 90,
552
- slotPatterns: [/products?/i, /catalog/i, /inventory/i],
553
- queryVariants: ['package', 'box', 'tag', 'shopping bag', 'products'],
554
- iconPreferences: [
555
- { pattern: /^package$|packages|package[_-]?\d?|^box$|boxes|tag$|shopping-bag$|warehouse|building-warehouse/i, bonus: 115 },
556
- { pattern: /brand-producthunt|brand-stocktwits|border|sort|cancel|\bx\b|off|discount|heart|exclamation|minus|plus|search|share|star|question|bolt|code|copy|dollar|down|up|pin|pause/i, bonus: -130 },
557
- ],
558
- },
559
- {
560
- slotPatterns: [/reports?/i, /analytics/i, /insights?/i],
561
- queryVariants: ['bar chart', 'file chart', 'analytics chart', 'report document'],
447
+ {
448
+ slotPatterns: [/billing/i, /payment/i, /invoice/i, /subscription/i],
449
+ queryVariants: ['credit card', 'receipt', 'invoice', 'payment', 'wallet'],
450
+ iconPreferences: [
451
+ { pattern: /credit-card|receipt|wallet|invoice/i, bonus: 44 },
452
+ { pattern: /card|banknote|currency|dollar/i, bonus: 18 },
453
+ { pattern: /ruble|franc|lira|bitcoin|yuan/i, bonus: -70 },
454
+ ],
455
+ },
456
+ {
457
+ priority: 90,
458
+ slotPatterns: [/storefront/i, /\bstore\b/i, /\bshop\b/i],
459
+ queryVariants: ['store', 'shop', 'building store', 'shopping bag'],
460
+ iconPreferences: [
461
+ { pattern: /^store$|^storefront$|building-store|shop[_-]?line|store[_-]?\d?[_-]?line|shopping-bag/i, bonus: 120 },
462
+ { pattern: /brand-appstore|restore|cancel|\bx\b|off|discount|heart|exclamation|minus|plus|search|share|star|question|bolt|code|copy|dollar|down|up|pin|pause/i, bonus: -120 },
463
+ ],
464
+ },
465
+ {
466
+ priority: 130,
467
+ slotPatterns: [/(store|shop|storefront)\s+(off|disabled|closed|cancelled|canceled)/i, /(off|disabled|closed|cancelled|canceled)\s+(store|shop|storefront)/i],
468
+ queryVariants: ['store off', 'shopping bag x', 'shopping cart off', 'store disabled'],
469
+ iconPreferences: [
470
+ { pattern: /(store|shop|shopping|bag|cart).*(off|\bx\b|cancel|disabled)|(off|\bx\b|cancel|disabled).*(store|shop|shopping|bag|cart)/i, bonus: 220 },
471
+ { pattern: /^building-store$|^store$|^shopping-bag$/i, bonus: -70 },
472
+ ],
473
+ },
474
+ {
475
+ priority: 90,
476
+ slotPatterns: [/checkout/i],
477
+ queryVariants: ['shopping cart', 'credit card', 'payment', 'receipt checkout'],
478
+ iconPreferences: [
479
+ { pattern: /^shopping[_-]?cart$|shopping-cart$|credit-card|card-pay|payment|receipt/i, bonus: 140 },
480
+ { pattern: /fork|knife|git|branch|merge|forklift|grill|cancel|\bx\b|off|discount|heart|exclamation|minus|plus|search|share|star|question|bolt|code|copy|dollar|down|up|pin|pause/i, bonus: -140 },
481
+ ],
482
+ },
483
+ {
484
+ priority: 120,
485
+ slotPatterns: [/^cart$/i, /shopping cart/i, /\bbasket\b/i],
486
+ queryVariants: ['shopping cart', 'cart', 'basket'],
487
+ iconPreferences: [
488
+ { pattern: /^shopping[_-]?cart$|^basket$/i, bonus: 220 },
489
+ { pattern: /shopping-cart-(cog|x|off|discount|dollar|exclamation|heart|minus|pause|plus|question|share|star|bolt|copy|down|up|search|pin)/i, bonus: -260 },
490
+ { pattern: /basket-(cog|x|off|discount|dollar|exclamation|heart|minus|pause|plus|question|share|star|bolt|copy|down|up|search|pin)/i, bonus: -180 },
491
+ { pattern: /garden-cart/i, bonus: -180 },
492
+ ],
493
+ },
494
+ {
495
+ priority: 90,
496
+ slotPatterns: [/customers?/i, /shoppers?/i, /buyers?/i],
497
+ queryVariants: ['users', 'customers', 'user group'],
498
+ iconPreferences: [
499
+ { pattern: /^users(?:_|-|$)|(?:_|-)users(?:_|-|$)|users-group|user-group|user-circle|user_circle|^user(?:_|-|$)/i, bonus: 110 },
500
+ { pattern: /ticket|plane|caret|cancel|\bx\b|off|discount|heart|exclamation|minus|plus|search|share|star|question|bolt|code|copy|dollar|down|up|pin|pause/i, bonus: -120 },
501
+ ],
502
+ },
503
+ {
504
+ priority: 90,
505
+ slotPatterns: [/coupons?/i, /discounts?/i, /promo/i, /promotion/i],
506
+ queryVariants: ['coupon', 'tag percent', 'discount', 'percentage'],
507
+ iconPreferences: [
508
+ { pattern: /coupon|ticket-percent|badge-percent|percent|percentage|tag|shopping-cart-discount|shopping-bag-discount|seal-percent/i, bonus: 120 },
509
+ { pattern: /^percentage-\d+$|bean|candy|cannabis|off|slash|disabled|alert/i, bonus: -180 },
510
+ ],
511
+ },
512
+ {
513
+ priority: 130,
514
+ slotPatterns: [/(cancel|cancelled|canceled|remove|delete)\s+orders?/i, /orders?\s+(cancel|cancelled|canceled|remove|delete)/i],
515
+ queryVariants: ['shopping cart cancel', 'basket cancel', 'cancel order', 'order x'],
516
+ iconPreferences: [
517
+ { pattern: /cancel|\bx\b|remove|delete|minus|trash/i, bonus: 220 },
518
+ { pattern: /shopping|cart|basket|package|receipt|clipboard|list/i, bonus: 20 },
519
+ ],
520
+ },
521
+ {
522
+ priority: 90,
523
+ slotPatterns: [/orders?/i, /purchases?/i],
524
+ queryVariants: ['package', 'receipt', 'clipboard list', 'shopping bag', 'ordered list'],
525
+ iconPreferences: [
526
+ { pattern: /^package$|packages|receipt|shopping-bag$|clipboard|list-ordered|file-invoice|file-text/i, bonus: 115 },
527
+ { pattern: /border|sort|align|cancel|\bx\b|off|discount|heart|exclamation|minus|plus|search|share|star|question|bolt|code|copy|dollar|down|up|pin|pause/i, bonus: -130 },
528
+ ],
529
+ },
530
+ {
531
+ priority: 120,
532
+ slotPatterns: [/shipping/i, /delivery/i, /fulfillment/i, /dispatch/i],
533
+ queryVariants: ['truck delivery', 'delivery', 'shipping', 'package delivery'],
534
+ iconPreferences: [
535
+ { pattern: /^truck[_-]?delivery$/i, bonus: 260 },
536
+ { pattern: /^truck$|package[_-]?export|package[_-]?import/i, bonus: 180 },
537
+ { pattern: /^truck[_-]?return$/i, bonus: -120 },
538
+ { pattern: /cloud-upload|upload|ship-off|cloud|arrow-up/i, bonus: -220 },
539
+ ],
540
+ },
541
+ {
542
+ priority: 120,
543
+ slotPatterns: [/returns?/i, /refunds?/i, /reverse logistics/i],
544
+ queryVariants: ['return', 'refund', 'truck return', 'receipt refund'],
545
+ iconPreferences: [
546
+ { pattern: /^truck[_-]?return$|receipt[_-]?refund|credit-card[_-]?refund|arrow-back|cash.*move.*back/i, bonus: 220 },
547
+ { pattern: /player|chevron|stack|upload|cloud|ship-off/i, bonus: -160 },
548
+ ],
549
+ },
550
+ {
551
+ priority: 90,
552
+ slotPatterns: [/products?/i, /catalog/i, /inventory/i],
553
+ queryVariants: ['package', 'box', 'tag', 'shopping bag', 'products'],
554
+ iconPreferences: [
555
+ { pattern: /^package$|packages|package[_-]?\d?|^box$|boxes|tag$|shopping-bag$|warehouse|building-warehouse/i, bonus: 115 },
556
+ { pattern: /brand-producthunt|brand-stocktwits|border|sort|cancel|\bx\b|off|discount|heart|exclamation|minus|plus|search|share|star|question|bolt|code|copy|dollar|down|up|pin|pause/i, bonus: -130 },
557
+ ],
558
+ },
559
+ {
560
+ slotPatterns: [/reports?/i, /analytics/i, /insights?/i],
561
+ queryVariants: ['bar chart', 'file chart', 'analytics chart', 'report document'],
562
562
  iconPreferences: [
563
563
  { pattern: /file-.*chart|chart-bar|bar-chart-3|bar-chart|chart-line|line-chart/i, bonus: 40 },
564
- { pattern: /chart|report|analytics/i, bonus: 16 },
565
- ],
566
- },
567
- {
568
- priority: 100,
569
- slotPatterns: [/^quotes?$/i, /blockquote/i, /quotation/i, /citation/i],
570
- queryVariants: ['quotes', 'quotation', 'quote', 'blockquote'],
571
- iconPreferences: [
572
- { pattern: /^quotes?$|quotation|blockquote/i, bonus: 180 },
573
- { pattern: /indent|text-align|receipt|ticket/i, bonus: -120 },
574
- ],
575
- },
576
- {
577
- slotPatterns: [/settings?/i, /preferences?/i, /configure/i],
578
- queryVariants: ['settings', 'cog', 'sliders'],
564
+ { pattern: /chart|report|analytics/i, bonus: 16 },
565
+ ],
566
+ },
567
+ {
568
+ priority: 100,
569
+ slotPatterns: [/^quotes?$/i, /blockquote/i, /quotation/i, /citation/i],
570
+ queryVariants: ['quotes', 'quotation', 'quote', 'blockquote'],
571
+ iconPreferences: [
572
+ { pattern: /^quotes?$|quotation|blockquote/i, bonus: 180 },
573
+ { pattern: /indent|text-align|receipt|ticket/i, bonus: -120 },
574
+ ],
575
+ },
576
+ {
577
+ slotPatterns: [/settings?/i, /preferences?/i, /configure/i],
578
+ queryVariants: ['settings', 'cog', 'sliders'],
579
579
  iconPreferences: [
580
580
  { pattern: /^settings$|^cog$|settings-2|sliders/i, bonus: 34 },
581
- { pattern: /settings|cog|adjustments/i, bonus: 16 },
582
- ],
583
- },
584
- {
585
- priority: 90,
586
- slotPatterns: [/permissions?/i, /access control/i, /roles?/i],
587
- queryVariants: ['user key', 'shield lock', 'key', 'settings permissions'],
588
- iconPreferences: [
589
- { pattern: /user-key|user-lock|user-check|key|lock|shield|adjustments|settings/i, bonus: 120 },
590
- { pattern: /free-rights|premium-rights|icons$/i, bonus: -100 },
591
- ],
592
- },
593
- {
594
- slotPatterns: [/database/i, /storage/i],
595
- queryVariants: ['database', 'server database', 'data storage'],
596
- iconPreferences: [
597
- { pattern: /^database$|database-stack/i, bonus: 36 },
598
- { pattern: /database|server/i, bonus: 16 },
599
- ],
600
- },
601
- {
602
- slotPatterns: [/search/i, /find/i, /lookup/i],
603
- queryVariants: ['search', 'find', 'magnifier', 'magnifying glass'],
604
- iconPreferences: [
605
- { pattern: /^search$/i, bonus: 140 },
606
- { pattern: /^search(?:_|-|$)|(?:_|-)search(?:_|-|$)/i, bonus: 64 },
607
- { pattern: /magnifier|magnifying/i, bonus: 36 },
608
- { pattern: /file-search|folder-search|scan-search|mail-search|calendar-search/i, bonus: -90 },
609
- ],
610
- },
611
- {
612
- priority: 115,
613
- slotPatterns: [/\b(add|new|create)\s+bookmark\b/i, /\bbookmark\s+(add|new|create)\b/i],
614
- queryVariants: ['bookmark add', 'add bookmark', 'bookmark plus'],
615
- iconPreferences: [
616
- { pattern: /^bookmark[_-]?(add|plus)(?:_|-|$)|(?:_|-)bookmark[_-]?(add|plus)(?:_|-|$)/i, bonus: 220 },
617
- { pattern: /^bookmarks?(?:_|-|$)|(?:_|-)bookmarks?(?:_|-|$)/i, bonus: 46 },
618
- { pattern: /^(add|plus)(?:_|-|$)/i, bonus: -80 },
619
- ],
620
- },
621
- {
622
- priority: 115,
623
- slotPatterns: [/\bedit\s+bookmark\b/i, /\bbookmark\s+edit\b/i],
624
- queryVariants: ['bookmark edit', 'edit bookmark', 'bookmark pencil'],
625
- iconPreferences: [
626
- { pattern: /^bookmark[_-]?edit(?:_|-|$)|(?:_|-)bookmark[_-]?edit(?:_|-|$)/i, bonus: 190 },
627
- { pattern: /^bookmarks?(?:_|-|$)|(?:_|-)bookmarks?(?:_|-|$)/i, bonus: 46 },
628
- { pattern: /^edit(?:_|-|$)|pencil/i, bonus: -70 },
629
- ],
630
- },
631
- {
632
- priority: 115,
633
- slotPatterns: [/\b(remove|delete)\s+bookmark\b/i, /\bbookmark\s+(remove|delete)\b/i],
634
- queryVariants: ['bookmark remove', 'remove bookmark', 'bookmark minus'],
635
- iconPreferences: [
636
- { pattern: /^bookmark[_-]?(remove|minus|x)(?:_|-|$)|(?:_|-)bookmark[_-]?(remove|minus|x)(?:_|-|$)/i, bonus: 190 },
637
- { pattern: /^bookmarks?(?:_|-|$)|(?:_|-)bookmarks?(?:_|-|$)/i, bonus: 46 },
638
- { pattern: /^(remove|delete|minus)(?:_|-|$)/i, bonus: -70 },
639
- ],
640
- },
641
- {
642
- slotPatterns: [/bookmark/i, /saved?/i, /save article/i],
643
- queryVariants: ['bookmark', 'saved', 'save'],
644
- iconPreferences: [
645
- { pattern: /^bookmarks?(?:_|-|$)|(?:_|-)bookmarks?(?:_|-|$)/i, bonus: 66 },
646
- { pattern: /save|favorite|star/i, bonus: 14 },
647
- ],
648
- },
649
- {
650
- slotPatterns: [/share/i, /send article/i, /forward/i],
651
- queryVariants: ['share', 'send', 'forward'],
652
- iconPreferences: [
653
- { pattern: /^share(?:_|-|$)|(?:_|-)share(?:_|-|$)/i, bonus: 58 },
654
- { pattern: /send|forward/i, bonus: 18 },
655
- ],
656
- },
657
- {
658
- priority: 110,
659
- slotPatterns: [/previous page/i, /previous/i, /\bback\b/i, /go back/i],
660
- queryVariants: ['arrow left', 'chevron left', 'back arrow', 'previous'],
661
- iconPreferences: [
662
- { pattern: /^arrow[_-]?left$|^chevron[_-]?left$|^caret[_-]?left$|arrow[_-]?back$|back[_-]?line|arrow[_-]?to[_-]?left|left(?:_|-|$)/i, bonus: 140 },
663
- { pattern: /skip-back|step-back/i, bonus: 48 },
664
- { pattern: /send-to-back|file|archive|audio|floppy|cash|banknote|brand|copy/i, bonus: -140 },
665
- ],
666
- },
667
- {
668
- priority: 90,
669
- slotPatterns: [/read more/i, /more link/i, /continue/i, /open article/i, /next page/i, /^next$/i],
670
- queryVariants: ['arrow right', 'move right', 'chevron right', 'read more', 'next'],
671
- iconPreferences: [
672
- { pattern: /^arrow[_-]?right$|^move[_-]?right$|arrow[_-]?to[_-]?right|chevron[_-]?right|right(?:_|-|$)/i, bonus: 90 },
673
- { pattern: /square|circle|corner|up|down|left|banknote|archive/i, bonus: -70 },
674
- ],
675
- },
676
- {
677
- slotPatterns: [/categor(?:y|ies)/i, /chips?/i, /filter/i, /topics?/i, /tags?/i],
678
- queryVariants: ['filter', 'category', 'tag', 'grid'],
679
- iconPreferences: [
680
- { pattern: /^filter(?:_|-|$)|(?:_|-)filter(?:_|-|$)/i, bonus: 56 },
681
- { pattern: /^tag(?:_|-|$)|(?:_|-)tag(?:_|-|$)|category|grid/i, bonus: 26 },
682
- ],
683
- },
684
- {
685
- slotPatterns: [/trending/i, /popular/i, /top stories/i, /hot/i],
686
- queryVariants: ['trending up', 'chart up', 'fire', 'popular'],
687
- iconPreferences: [
688
- { pattern: /^trending[_-]?up(?:_|-|$)|chart.*up|up.*chart/i, bonus: 62 },
689
- { pattern: /^fire(?:_|-|$)|flame|hot/i, bonus: 22 },
690
- ],
691
- },
692
- {
693
- slotPatterns: [/news/i, /article/i, /headline/i, /story/i, /publisher/i, /logo/i, /title/i],
694
- queryVariants: ['news', 'article', 'newspaper', 'headline'],
695
- iconPreferences: [
696
- { pattern: /^news(?:_|-|$)|(?:_|-)news(?:_|-|$)|newspaper|article/i, bonus: 66 },
697
- { pattern: /file|document|paper/i, bonus: 16 },
698
- ],
699
- },
700
- {
701
- slotPatterns: [/dashboard/i],
702
- queryVariants: ['dashboard', 'layout dashboard', 'grid dashboard'],
703
- iconPreferences: [
704
- { pattern: /^dashboard$|layout-dashboard|dashboard/i, bonus: 50 },
705
- { pattern: /grid|layout/i, bonus: 12 },
706
- ],
707
- },
708
- {
709
- slotPatterns: [/projects?/i],
710
- queryVariants: ['folder', 'folders', 'project folder'],
711
- iconPreferences: [
712
- { pattern: /^folders?$|(?:_|-)folders?(?:_|-|$)/i, bonus: 56 },
713
- { pattern: /briefcase|project/i, bonus: 12 },
714
- ],
715
- },
716
- {
717
- slotPatterns: [/tasks?/i, /todo/i, /to do/i, /checklist/i],
718
- queryVariants: ['list check', 'checklist', 'checkbox', 'task'],
719
- iconPreferences: [
720
- { pattern: /list-check|list_check|checkbox|checklist|clipboard-check/i, bonus: 56 },
721
- { pattern: /check|task/i, bonus: 16 },
722
- ],
723
- },
724
- {
725
- slotPatterns: [/team/i, /\busers\b/i, /members?/i],
726
- queryVariants: ['users', 'team', 'user group'],
727
- iconPreferences: [
728
- { pattern: /^users(?:_|-|$)|(?:_|-)users(?:_|-|$)|user-group|user_circle|user-circle/i, bonus: 64 },
729
- { pattern: /^user(?:_|-|$)|(?:_|-)user(?:_|-|$)/i, bonus: 18 },
730
- ],
731
- },
732
- {
733
- slotPatterns: [/calendar/i, /schedule/i, /events?/i],
734
- queryVariants: ['calendar', 'calendar event', 'schedule'],
735
- iconPreferences: [
736
- { pattern: /^calendar(?:_|-|$)|(?:_|-)calendar(?:_|-|$)/i, bonus: 54 },
737
- { pattern: /event|schedule/i, bonus: 18 },
738
- ],
739
- },
740
- {
741
- slotPatterns: [/\bbold\b/i],
742
- queryVariants: ['text b', 'bold', 'text bold'],
743
- iconPreferences: [
744
- { pattern: /^text[_-]?b$|text-bold|bold/i, bonus: 66 },
745
- ],
746
- },
747
- {
748
- slotPatterns: [/italic/i],
749
- queryVariants: ['text italic', 'italic'],
750
- iconPreferences: [
751
- { pattern: /^text[_-]?italic$|italic/i, bonus: 66 },
752
- ],
753
- },
754
- {
755
- priority: 130,
756
- slotPatterns: [/broken\s+link/i, /link\s+(broken|break|disabled|off)/i],
757
- queryVariants: ['broken link', 'link break', 'link slash'],
758
- iconPreferences: [
759
- { pattern: /link.*(break|broken|slash|off)|(break|broken|slash|off).*link/i, bonus: 180 },
760
- { pattern: /^link(?:_|-|$)|(?:_|-)link(?:_|-|$)/i, bonus: 10 },
761
- ],
762
- },
763
- {
764
- priority: 130,
765
- slotPatterns: [/(broken|disabled|off)\s+(image|photo|picture)/i, /(image|photo|picture)\s+(broken|disabled|off)/i],
766
- queryVariants: ['photo off', 'image off', 'broken image', 'image broken'],
767
- iconPreferences: [
768
- { pattern: /(image|photo|picture).*(broken|off|slash)|(broken|off|slash).*(image|photo|picture)/i, bonus: 240 },
769
- { pattern: /^image(?:_|-|$)|(?:_|-)image(?:_|-|$)|picture|photo/i, bonus: 10 },
770
- ],
771
- },
772
- {
773
- priority: 130,
774
- slotPatterns: [/(comments?|chat|discussion)\s+(off|disabled|muted|slash)/i, /(off|disabled|muted|slash)\s+(comments?|chat|discussion)/i],
775
- queryVariants: ['chat slash', 'comment off', 'comments off', 'message slash'],
776
- iconPreferences: [
777
- { pattern: /(chat|comment|message).*(slash|off|x)|(slash|off|x).*(chat|comment|message)/i, bonus: 180 },
778
- { pattern: /chat|comment|message/i, bonus: 10 },
779
- ],
780
- },
781
- {
782
- slotPatterns: [/\blink\b/i, /hyperlink/i],
783
- queryVariants: ['link', 'link simple', 'hyperlink'],
784
- iconPreferences: [
785
- { pattern: /^link(?:_|-|$)|(?:_|-)link(?:_|-|$)/i, bonus: 56 },
786
- { pattern: /chain/i, bonus: 14 },
787
- { pattern: /break|broken|slash|unlink|brand/i, bonus: -120 },
788
- ],
789
- },
790
- {
791
- slotPatterns: [/image/i, /photo/i, /picture/i],
792
- queryVariants: ['image', 'picture', 'photo'],
793
- iconPreferences: [
794
- { pattern: /^image(?:_|-|$)|(?:_|-)image(?:_|-|$)|picture|photo/i, bonus: 56 },
795
- { pattern: /broken|off|slash|brand/i, bonus: -120 },
796
- ],
797
- },
798
- {
799
- priority: 80,
800
- slotPatterns: [/comments?/i, /chat/i, /discussion/i],
801
- queryVariants: ['chat text', 'comments', 'message dots'],
802
- iconPreferences: [
803
- { pattern: /chat|comment|message/i, bonus: 76 },
804
- { pattern: /slash|off|x$|brand/i, bonus: -120 },
805
- ],
806
- },
807
- {
808
- slotPatterns: [/undo/i],
809
- queryVariants: ['undo', 'arrow counter clockwise', 'rotate left'],
810
- iconPreferences: [
811
- { pattern: /^undo$|arrow-counter-clockwise|arrow_counter_clockwise|rotate.*left/i, bonus: 66 },
812
- { pattern: /^redo$|^arrows?[_-]clockwise$|clock[_-]clockwise|arrow_clockwise|rotate.*right/i, bonus: -120 },
813
- ],
814
- },
815
- {
816
- slotPatterns: [/redo/i],
817
- queryVariants: ['redo', 'arrow clockwise', 'rotate right'],
818
- iconPreferences: [
819
- { pattern: /^redo$|arrow-clockwise|arrow_clockwise|rotate.*right/i, bonus: 66 },
820
- { pattern: /^undo$|^arrows?[_-]counter[_-]clockwise$|clock[_-]counter[_-]clockwise|arrow_counter_clockwise|rotate.*left/i, bonus: -120 },
821
- ],
822
- },
823
- ]);
824
-
825
- const SLOT_PREFERENCE_RULES = Object.freeze({
826
- lucide: [
827
- {
828
- slotPatterns: [/\b(add|new|create)\s+bookmark\b/i, /\bbookmark\s+(add|new|create)\b/i],
829
- iconPreferences: [
830
- { pattern: /^bookmark-plus$/i, bonus: 500 },
831
- { pattern: /^bookmark$/i, bonus: 40 },
832
- { pattern: /waves-ladder|map-pin/i, bonus: -220 },
833
- ],
834
- },
835
- {
836
- slotPatterns: [/users/i, /team/i],
837
- iconPreferences: [
838
- { pattern: /^users$/i, bonus: 28 },
839
- { pattern: /^users-2$/i, bonus: 20 },
840
- { pattern: /^user-2$/i, bonus: -12 },
841
- ],
842
- },
843
- {
844
- slotPatterns: [/database/i, /storage/i],
845
- iconPreferences: [
846
- { pattern: /^database$/i, bonus: 48 },
847
- { pattern: /^database-(backup|search)$/i, bonus: 28 },
848
- { pattern: /^database-zap$/i, bonus: -34 },
849
- ],
850
- },
851
- {
852
- slotPatterns: [/security/i, /privacy/i, /safe/i, /protection/i],
853
- iconPreferences: [
854
- { pattern: /^shield$/i, bonus: 80 },
855
- { pattern: /^shield-check$/i, bonus: 76 },
856
- { pattern: /^lock$/i, bonus: 52 },
857
- { pattern: /^lock-keyhole$/i, bonus: 48 },
858
- { pattern: /open|unlock|ban|minus|off|slash/i, bonus: -80 },
859
- ],
860
- },
861
- ],
862
- mingcute: [
581
+ { pattern: /settings|cog|adjustments/i, bonus: 16 },
582
+ ],
583
+ },
584
+ {
585
+ priority: 90,
586
+ slotPatterns: [/permissions?/i, /access control/i, /roles?/i],
587
+ queryVariants: ['user key', 'shield lock', 'key', 'settings permissions'],
588
+ iconPreferences: [
589
+ { pattern: /user-key|user-lock|user-check|key|lock|shield|adjustments|settings/i, bonus: 120 },
590
+ { pattern: /free-rights|premium-rights|icons$/i, bonus: -100 },
591
+ ],
592
+ },
593
+ {
594
+ slotPatterns: [/database/i, /storage/i],
595
+ queryVariants: ['database', 'server database', 'data storage'],
596
+ iconPreferences: [
597
+ { pattern: /^database$|database-stack/i, bonus: 36 },
598
+ { pattern: /database|server/i, bonus: 16 },
599
+ ],
600
+ },
601
+ {
602
+ slotPatterns: [/search/i, /find/i, /lookup/i],
603
+ queryVariants: ['search', 'find', 'magnifier', 'magnifying glass'],
604
+ iconPreferences: [
605
+ { pattern: /^search$/i, bonus: 140 },
606
+ { pattern: /^search(?:_|-|$)|(?:_|-)search(?:_|-|$)/i, bonus: 64 },
607
+ { pattern: /magnifier|magnifying/i, bonus: 36 },
608
+ { pattern: /file-search|folder-search|scan-search|mail-search|calendar-search/i, bonus: -90 },
609
+ ],
610
+ },
611
+ {
612
+ priority: 115,
613
+ slotPatterns: [/\b(add|new|create)\s+bookmark\b/i, /\bbookmark\s+(add|new|create)\b/i],
614
+ queryVariants: ['bookmark add', 'add bookmark', 'bookmark plus'],
615
+ iconPreferences: [
616
+ { pattern: /^bookmark[_-]?(add|plus)(?:_|-|$)|(?:_|-)bookmark[_-]?(add|plus)(?:_|-|$)/i, bonus: 220 },
617
+ { pattern: /^bookmarks?(?:_|-|$)|(?:_|-)bookmarks?(?:_|-|$)/i, bonus: 46 },
618
+ { pattern: /^(add|plus)(?:_|-|$)/i, bonus: -80 },
619
+ ],
620
+ },
621
+ {
622
+ priority: 115,
623
+ slotPatterns: [/\bedit\s+bookmark\b/i, /\bbookmark\s+edit\b/i],
624
+ queryVariants: ['bookmark edit', 'edit bookmark', 'bookmark pencil'],
625
+ iconPreferences: [
626
+ { pattern: /^bookmark[_-]?edit(?:_|-|$)|(?:_|-)bookmark[_-]?edit(?:_|-|$)/i, bonus: 190 },
627
+ { pattern: /^bookmarks?(?:_|-|$)|(?:_|-)bookmarks?(?:_|-|$)/i, bonus: 46 },
628
+ { pattern: /^edit(?:_|-|$)|pencil/i, bonus: -70 },
629
+ ],
630
+ },
631
+ {
632
+ priority: 115,
633
+ slotPatterns: [/\b(remove|delete)\s+bookmark\b/i, /\bbookmark\s+(remove|delete)\b/i],
634
+ queryVariants: ['bookmark remove', 'remove bookmark', 'bookmark minus'],
635
+ iconPreferences: [
636
+ { pattern: /^bookmark[_-]?(remove|minus|x)(?:_|-|$)|(?:_|-)bookmark[_-]?(remove|minus|x)(?:_|-|$)/i, bonus: 190 },
637
+ { pattern: /^bookmarks?(?:_|-|$)|(?:_|-)bookmarks?(?:_|-|$)/i, bonus: 46 },
638
+ { pattern: /^(remove|delete|minus)(?:_|-|$)/i, bonus: -70 },
639
+ ],
640
+ },
641
+ {
642
+ slotPatterns: [/bookmark/i, /saved?/i, /save article/i],
643
+ queryVariants: ['bookmark', 'saved', 'save'],
644
+ iconPreferences: [
645
+ { pattern: /^bookmarks?(?:_|-|$)|(?:_|-)bookmarks?(?:_|-|$)/i, bonus: 66 },
646
+ { pattern: /save|favorite|star/i, bonus: 14 },
647
+ ],
648
+ },
649
+ {
650
+ slotPatterns: [/share/i, /send article/i, /forward/i],
651
+ queryVariants: ['share', 'send', 'forward'],
652
+ iconPreferences: [
653
+ { pattern: /^share(?:_|-|$)|(?:_|-)share(?:_|-|$)/i, bonus: 58 },
654
+ { pattern: /send|forward/i, bonus: 18 },
655
+ ],
656
+ },
657
+ {
658
+ priority: 110,
659
+ slotPatterns: [/previous page/i, /previous/i, /\bback\b/i, /go back/i],
660
+ queryVariants: ['arrow left', 'chevron left', 'back arrow', 'previous'],
661
+ iconPreferences: [
662
+ { pattern: /^arrow[_-]?left$|^chevron[_-]?left$|^caret[_-]?left$|arrow[_-]?back$|back[_-]?line|arrow[_-]?to[_-]?left|left(?:_|-|$)/i, bonus: 140 },
663
+ { pattern: /skip-back|step-back/i, bonus: 48 },
664
+ { pattern: /send-to-back|file|archive|audio|floppy|cash|banknote|brand|copy/i, bonus: -140 },
665
+ ],
666
+ },
667
+ {
668
+ priority: 90,
669
+ slotPatterns: [/read more/i, /more link/i, /continue/i, /open article/i, /next page/i, /^next$/i],
670
+ queryVariants: ['arrow right', 'move right', 'chevron right', 'read more', 'next'],
671
+ iconPreferences: [
672
+ { pattern: /^arrow[_-]?right$|^move[_-]?right$|arrow[_-]?to[_-]?right|chevron[_-]?right|right(?:_|-|$)/i, bonus: 90 },
673
+ { pattern: /square|circle|corner|up|down|left|banknote|archive/i, bonus: -70 },
674
+ ],
675
+ },
676
+ {
677
+ slotPatterns: [/categor(?:y|ies)/i, /chips?/i, /filter/i, /topics?/i, /tags?/i],
678
+ queryVariants: ['filter', 'category', 'tag', 'grid'],
679
+ iconPreferences: [
680
+ { pattern: /^filter(?:_|-|$)|(?:_|-)filter(?:_|-|$)/i, bonus: 56 },
681
+ { pattern: /^tag(?:_|-|$)|(?:_|-)tag(?:_|-|$)|category|grid/i, bonus: 26 },
682
+ ],
683
+ },
684
+ {
685
+ slotPatterns: [/trending/i, /popular/i, /top stories/i, /hot/i],
686
+ queryVariants: ['trending up', 'chart up', 'fire', 'popular'],
687
+ iconPreferences: [
688
+ { pattern: /^trending[_-]?up(?:_|-|$)|chart.*up|up.*chart/i, bonus: 62 },
689
+ { pattern: /^fire(?:_|-|$)|flame|hot/i, bonus: 22 },
690
+ ],
691
+ },
692
+ {
693
+ slotPatterns: [/news/i, /article/i, /headline/i, /story/i, /publisher/i, /logo/i, /title/i],
694
+ queryVariants: ['news', 'article', 'newspaper', 'headline'],
695
+ iconPreferences: [
696
+ { pattern: /^news(?:_|-|$)|(?:_|-)news(?:_|-|$)|newspaper|article/i, bonus: 66 },
697
+ { pattern: /file|document|paper/i, bonus: 16 },
698
+ ],
699
+ },
700
+ {
701
+ slotPatterns: [/dashboard/i],
702
+ queryVariants: ['dashboard', 'layout dashboard', 'grid dashboard'],
703
+ iconPreferences: [
704
+ { pattern: /^dashboard$|layout-dashboard|dashboard/i, bonus: 50 },
705
+ { pattern: /grid|layout/i, bonus: 12 },
706
+ ],
707
+ },
708
+ {
709
+ slotPatterns: [/projects?/i],
710
+ queryVariants: ['folder', 'folders', 'project folder'],
711
+ iconPreferences: [
712
+ { pattern: /^folders?$|(?:_|-)folders?(?:_|-|$)/i, bonus: 56 },
713
+ { pattern: /briefcase|project/i, bonus: 12 },
714
+ ],
715
+ },
716
+ {
717
+ slotPatterns: [/tasks?/i, /todo/i, /to do/i, /checklist/i],
718
+ queryVariants: ['list check', 'checklist', 'checkbox', 'task'],
719
+ iconPreferences: [
720
+ { pattern: /list-check|list_check|checkbox|checklist|clipboard-check/i, bonus: 56 },
721
+ { pattern: /check|task/i, bonus: 16 },
722
+ ],
723
+ },
724
+ {
725
+ slotPatterns: [/team/i, /\busers\b/i, /members?/i],
726
+ queryVariants: ['users', 'team', 'user group'],
727
+ iconPreferences: [
728
+ { pattern: /^users(?:_|-|$)|(?:_|-)users(?:_|-|$)|user-group|user_circle|user-circle/i, bonus: 64 },
729
+ { pattern: /^user(?:_|-|$)|(?:_|-)user(?:_|-|$)/i, bonus: 18 },
730
+ ],
731
+ },
732
+ {
733
+ slotPatterns: [/calendar/i, /schedule/i, /events?/i],
734
+ queryVariants: ['calendar', 'calendar event', 'schedule'],
735
+ iconPreferences: [
736
+ { pattern: /^calendar(?:_|-|$)|(?:_|-)calendar(?:_|-|$)/i, bonus: 54 },
737
+ { pattern: /event|schedule/i, bonus: 18 },
738
+ ],
739
+ },
740
+ {
741
+ slotPatterns: [/\bbold\b/i],
742
+ queryVariants: ['text b', 'bold', 'text bold'],
743
+ iconPreferences: [
744
+ { pattern: /^text[_-]?b$|text-bold|bold/i, bonus: 66 },
745
+ ],
746
+ },
747
+ {
748
+ slotPatterns: [/italic/i],
749
+ queryVariants: ['text italic', 'italic'],
750
+ iconPreferences: [
751
+ { pattern: /^text[_-]?italic$|italic/i, bonus: 66 },
752
+ ],
753
+ },
754
+ {
755
+ priority: 130,
756
+ slotPatterns: [/broken\s+link/i, /link\s+(broken|break|disabled|off)/i],
757
+ queryVariants: ['broken link', 'link break', 'link slash'],
758
+ iconPreferences: [
759
+ { pattern: /link.*(break|broken|slash|off)|(break|broken|slash|off).*link/i, bonus: 180 },
760
+ { pattern: /^link(?:_|-|$)|(?:_|-)link(?:_|-|$)/i, bonus: 10 },
761
+ ],
762
+ },
763
+ {
764
+ priority: 130,
765
+ slotPatterns: [/(broken|disabled|off)\s+(image|photo|picture)/i, /(image|photo|picture)\s+(broken|disabled|off)/i],
766
+ queryVariants: ['photo off', 'image off', 'broken image', 'image broken'],
767
+ iconPreferences: [
768
+ { pattern: /(image|photo|picture).*(broken|off|slash)|(broken|off|slash).*(image|photo|picture)/i, bonus: 240 },
769
+ { pattern: /^image(?:_|-|$)|(?:_|-)image(?:_|-|$)|picture|photo/i, bonus: 10 },
770
+ ],
771
+ },
772
+ {
773
+ priority: 130,
774
+ slotPatterns: [/(comments?|chat|discussion)\s+(off|disabled|muted|slash)/i, /(off|disabled|muted|slash)\s+(comments?|chat|discussion)/i],
775
+ queryVariants: ['chat slash', 'comment off', 'comments off', 'message slash'],
776
+ iconPreferences: [
777
+ { pattern: /(chat|comment|message).*(slash|off|x)|(slash|off|x).*(chat|comment|message)/i, bonus: 180 },
778
+ { pattern: /chat|comment|message/i, bonus: 10 },
779
+ ],
780
+ },
781
+ {
782
+ slotPatterns: [/\blink\b/i, /hyperlink/i],
783
+ queryVariants: ['link', 'link simple', 'hyperlink'],
784
+ iconPreferences: [
785
+ { pattern: /^link(?:_|-|$)|(?:_|-)link(?:_|-|$)/i, bonus: 56 },
786
+ { pattern: /chain/i, bonus: 14 },
787
+ { pattern: /break|broken|slash|unlink|brand/i, bonus: -120 },
788
+ ],
789
+ },
790
+ {
791
+ slotPatterns: [/image/i, /photo/i, /picture/i],
792
+ queryVariants: ['image', 'picture', 'photo'],
793
+ iconPreferences: [
794
+ { pattern: /^image(?:_|-|$)|(?:_|-)image(?:_|-|$)|picture|photo/i, bonus: 56 },
795
+ { pattern: /broken|off|slash|brand/i, bonus: -120 },
796
+ ],
797
+ },
798
+ {
799
+ priority: 80,
800
+ slotPatterns: [/comments?/i, /chat/i, /discussion/i],
801
+ queryVariants: ['chat text', 'comments', 'message dots'],
802
+ iconPreferences: [
803
+ { pattern: /chat|comment|message/i, bonus: 76 },
804
+ { pattern: /slash|off|x$|brand/i, bonus: -120 },
805
+ ],
806
+ },
807
+ {
808
+ slotPatterns: [/undo/i],
809
+ queryVariants: ['undo', 'arrow counter clockwise', 'rotate left'],
810
+ iconPreferences: [
811
+ { pattern: /^undo$|arrow-counter-clockwise|arrow_counter_clockwise|rotate.*left/i, bonus: 66 },
812
+ { pattern: /^redo$|^arrows?[_-]clockwise$|clock[_-]clockwise|arrow_clockwise|rotate.*right/i, bonus: -120 },
813
+ ],
814
+ },
815
+ {
816
+ slotPatterns: [/redo/i],
817
+ queryVariants: ['redo', 'arrow clockwise', 'rotate right'],
818
+ iconPreferences: [
819
+ { pattern: /^redo$|arrow-clockwise|arrow_clockwise|rotate.*right/i, bonus: 66 },
820
+ { pattern: /^undo$|^arrows?[_-]counter[_-]clockwise$|clock[_-]counter[_-]clockwise|arrow_counter_clockwise|rotate.*left/i, bonus: -120 },
821
+ ],
822
+ },
823
+ ]);
824
+
825
+ const SLOT_PREFERENCE_RULES = Object.freeze({
826
+ lucide: [
863
827
  {
864
- slotPatterns: [/home/i],
865
- iconPreferences: [
866
- { pattern: /^home_3_line$/i, bonus: 160 },
867
- { pattern: /^home_2_line$/i, bonus: 8 },
868
- { pattern: /^home_1_line$/i, bonus: 4 },
869
- { pattern: /^home_wifi_line$/i, bonus: -24 },
870
- ],
828
+ slotPatterns: [/\b(add|new|create)\s+bookmark\b/i, /\bbookmark\s+(add|new|create)\b/i],
829
+ iconPreferences: [
830
+ { pattern: /^bookmark-plus$/i, bonus: 500 },
831
+ { pattern: /^bookmark$/i, bonus: 40 },
832
+ { pattern: /waves-ladder|map-pin/i, bonus: -220 },
833
+ ],
834
+ },
835
+ {
836
+ slotPatterns: [/users/i, /team/i],
837
+ iconPreferences: [
838
+ { pattern: /^users$/i, bonus: 28 },
839
+ { pattern: /^users-2$/i, bonus: 20 },
840
+ { pattern: /^user-2$/i, bonus: -12 },
841
+ ],
842
+ },
843
+ {
844
+ slotPatterns: [/database/i, /storage/i],
845
+ iconPreferences: [
846
+ { pattern: /^database$/i, bonus: 48 },
847
+ { pattern: /^database-(backup|search)$/i, bonus: 28 },
848
+ { pattern: /^database-zap$/i, bonus: -34 },
849
+ ],
850
+ },
851
+ {
852
+ slotPatterns: [/security/i, /privacy/i, /safe/i, /protection/i],
853
+ iconPreferences: [
854
+ { pattern: /^shield$/i, bonus: 80 },
855
+ { pattern: /^shield-check$/i, bonus: 76 },
856
+ { pattern: /^lock$/i, bonus: 52 },
857
+ { pattern: /^lock-keyhole$/i, bonus: 48 },
858
+ { pattern: /open|unlock|ban|minus|off|slash/i, bonus: -80 },
859
+ ],
860
+ },
861
+ ],
862
+ mingcute: [
863
+ {
864
+ slotPatterns: [/home/i],
865
+ iconPreferences: [
866
+ { pattern: /^home_3_line$/i, bonus: 160 },
867
+ { pattern: /^home_2_line$/i, bonus: 8 },
868
+ { pattern: /^home_1_line$/i, bonus: 4 },
869
+ { pattern: /^home_wifi_line$/i, bonus: -24 },
870
+ ],
871
871
  },
872
872
  {
873
873
  slotPatterns: [/create/i, /add/i, /plus/i, /compose/i],
@@ -877,335 +877,335 @@ const SLOT_PREFERENCE_RULES = Object.freeze({
877
877
  { pattern: /^add_circle_line$/i, bonus: 6 },
878
878
  ],
879
879
  },
880
- {
881
- slotPatterns: [/alerts?/i, /notification/i],
882
- iconPreferences: [
883
- { pattern: /^notification_line$/i, bonus: 36 },
884
- { pattern: /^notification_off_line$/i, bonus: -28 },
885
- ],
886
- },
887
- {
888
- slotPatterns: [/profile/i, /user/i, /account/i],
889
- iconPreferences: [
890
- { pattern: /^user_1_line$/i, bonus: 44 },
891
- { pattern: /^user_4_line$/i, bonus: -16 },
892
- ],
893
- },
894
- {
895
- slotPatterns: [/search/i],
896
- iconPreferences: [
897
- { pattern: /^search_line$/i, bonus: 70 },
898
- { pattern: /^search_[23]_line$/i, bonus: 18 },
899
- { pattern: /^search_.*_ai_line$/i, bonus: -70 },
900
- ],
901
- },
902
- {
903
- slotPatterns: [/bookmark/i, /saved?/i],
904
- iconPreferences: [
905
- { pattern: /^bookmark_line$/i, bonus: 34 },
906
- { pattern: /^bookmarks_line$/i, bonus: 28 },
907
- { pattern: /^bookmark_(add|edit|remove)_line$/i, bonus: -20 },
908
- ],
909
- },
910
- {
911
- slotPatterns: [/trending/i, /popular/i, /top stories/i],
912
- iconPreferences: [
913
- { pattern: /^trending_up_line$/i, bonus: 150 },
914
- { pattern: /^trending_down_line$/i, bonus: -30 },
915
- ],
916
- },
917
- {
918
- slotPatterns: [/read more/i, /continue/i, /open article/i],
919
- iconPreferences: [
920
- { pattern: /^arrow_right_line$/i, bonus: 72 },
921
- { pattern: /^arrow_to_right_line$/i, bonus: 40 },
922
- { pattern: /^align_arrow_right_line$/i, bonus: -30 },
923
- ],
924
- },
925
- {
926
- slotPatterns: [/categor(?:y|ies)/i, /chips?/i, /filter/i, /topics?/i],
927
- iconPreferences: [
928
- { pattern: /^filter_line$/i, bonus: 34 },
929
- { pattern: /^filter_[23]_line$/i, bonus: 22 },
930
- { pattern: /^tag_line$/i, bonus: 16 },
931
- ],
932
- },
933
- {
934
- slotPatterns: [/news/i, /article/i, /headline/i, /logo/i, /title/i],
935
- iconPreferences: [
936
- { pattern: /^news_line$/i, bonus: 76 },
937
- { pattern: /^news_2_line$/i, bonus: 46 },
938
- { pattern: /^appstore_line$/i, bonus: -44 },
939
- { pattern: /^apple_fruit_line$/i, bonus: -44 },
940
- ],
941
- },
942
- {
943
- slotPatterns: [/projects?/i],
944
- iconPreferences: [
945
- { pattern: /^folder_locked_line$/i, bonus: -70 },
946
- ],
947
- },
948
- ],
949
- });
950
-
951
- function normalizeText(value) {
952
- return String(value || '')
953
- .toLowerCase()
880
+ {
881
+ slotPatterns: [/alerts?/i, /notification/i],
882
+ iconPreferences: [
883
+ { pattern: /^notification_line$/i, bonus: 36 },
884
+ { pattern: /^notification_off_line$/i, bonus: -28 },
885
+ ],
886
+ },
887
+ {
888
+ slotPatterns: [/profile/i, /user/i, /account/i],
889
+ iconPreferences: [
890
+ { pattern: /^user_1_line$/i, bonus: 44 },
891
+ { pattern: /^user_4_line$/i, bonus: -16 },
892
+ ],
893
+ },
894
+ {
895
+ slotPatterns: [/search/i],
896
+ iconPreferences: [
897
+ { pattern: /^search_line$/i, bonus: 70 },
898
+ { pattern: /^search_[23]_line$/i, bonus: 18 },
899
+ { pattern: /^search_.*_ai_line$/i, bonus: -70 },
900
+ ],
901
+ },
902
+ {
903
+ slotPatterns: [/bookmark/i, /saved?/i],
904
+ iconPreferences: [
905
+ { pattern: /^bookmark_line$/i, bonus: 34 },
906
+ { pattern: /^bookmarks_line$/i, bonus: 28 },
907
+ { pattern: /^bookmark_(add|edit|remove)_line$/i, bonus: -20 },
908
+ ],
909
+ },
910
+ {
911
+ slotPatterns: [/trending/i, /popular/i, /top stories/i],
912
+ iconPreferences: [
913
+ { pattern: /^trending_up_line$/i, bonus: 150 },
914
+ { pattern: /^trending_down_line$/i, bonus: -30 },
915
+ ],
916
+ },
917
+ {
918
+ slotPatterns: [/read more/i, /continue/i, /open article/i],
919
+ iconPreferences: [
920
+ { pattern: /^arrow_right_line$/i, bonus: 72 },
921
+ { pattern: /^arrow_to_right_line$/i, bonus: 40 },
922
+ { pattern: /^align_arrow_right_line$/i, bonus: -30 },
923
+ ],
924
+ },
925
+ {
926
+ slotPatterns: [/categor(?:y|ies)/i, /chips?/i, /filter/i, /topics?/i],
927
+ iconPreferences: [
928
+ { pattern: /^filter_line$/i, bonus: 34 },
929
+ { pattern: /^filter_[23]_line$/i, bonus: 22 },
930
+ { pattern: /^tag_line$/i, bonus: 16 },
931
+ ],
932
+ },
933
+ {
934
+ slotPatterns: [/news/i, /article/i, /headline/i, /logo/i, /title/i],
935
+ iconPreferences: [
936
+ { pattern: /^news_line$/i, bonus: 76 },
937
+ { pattern: /^news_2_line$/i, bonus: 46 },
938
+ { pattern: /^appstore_line$/i, bonus: -44 },
939
+ { pattern: /^apple_fruit_line$/i, bonus: -44 },
940
+ ],
941
+ },
942
+ {
943
+ slotPatterns: [/projects?/i],
944
+ iconPreferences: [
945
+ { pattern: /^folder_locked_line$/i, bonus: -70 },
946
+ ],
947
+ },
948
+ ],
949
+ });
950
+
951
+ function normalizeText(value) {
952
+ return String(value || '')
953
+ .toLowerCase()
954
954
  .replace(/[_:]+/g, ' ')
955
955
  .replace(/[^a-z0-9\s-]/g, ' ')
956
956
  .replace(/-/g, ' ')
957
957
  .replace(/\s+/g, ' ')
958
- .trim();
959
- }
960
-
961
- function normalizeToken(token) {
962
- const value = String(token || '').toLowerCase();
963
- if (value.length > 4 && value.endsWith('ies')) return `${value.slice(0, -3)}y`;
964
- if (value.length > 3 && value.endsWith('es')) return value.slice(0, -2);
965
- if (value.length > 3 && value.endsWith('s')) return value.slice(0, -1);
966
- return value;
967
- }
968
-
969
- function tokenizeText(value) {
970
- const normalized = normalizeText(value);
971
- if (!normalized) return [];
972
- const tokens = normalized.split(' ');
973
- return dedupe([...tokens, ...tokens.map(normalizeToken)]);
974
- }
975
-
976
- function dedupe(values) {
977
- return [...new Set(values.filter(Boolean))];
978
- }
979
-
980
- function buildDirectLocalizedIntentTerms(value) {
981
- const text = String(value || '');
982
- return DIRECT_LOCALIZED_INTENT_RULES
983
- .filter((rule) => rule.pattern.test(text))
984
- .flatMap((rule) => rule.terms);
985
- }
986
-
987
- function buildRequestedTermSet(intentTerms = []) {
988
- return new Set(intentTerms.map(normalizeToken).filter(Boolean));
989
- }
990
-
991
- function isVariantTokenRequested(token, requestedTerms) {
992
- const normalizedToken = normalizeToken(token);
993
- if (requestedTerms.has(normalizedToken)) return true;
994
- const aliases = REQUESTED_VARIANT_ALIASES[normalizedToken] || [];
995
- return aliases.some((alias) => requestedTerms.has(normalizeToken(alias)));
996
- }
997
-
998
- function isIconVariantExplicitlyRequested(icon, intentTerms = []) {
999
- const requestedTerms = buildRequestedTermSet(intentTerms);
1000
- return tokenizeText(icon.id).some((token) => (
1001
- VARIANT_TOKENS.has(normalizeToken(token)) &&
1002
- isVariantTokenRequested(token, requestedTerms)
1003
- ));
1004
- }
1005
-
1006
- function buildLocalizedVariants(value, locale) {
1007
- if (!locale) return [];
1008
- const expanded = expandCjkQuery(value, {
1009
- locale,
1010
- terms: multilingualExpansionTerms,
1011
- });
1012
- const variants = expanded.matched.length > 0 ? expanded.variants.slice(1) : [];
1013
- const normalizedValue = normalizeCjkSearchText(value);
1014
- const containedMatches = [];
1015
-
1016
- if (normalizedValue) {
1017
- for (const record of multilingualExpansionTerms) {
1018
- if (record.locale !== locale || record.gate !== 'auto_accept') continue;
1019
-
1020
- const recordValues = [record.term, ...(record.variants || [])]
1021
- .map((term) => normalizeCjkSearchText(term))
1022
- .filter(Boolean);
1023
- const isContainedMatch = recordValues.some((term) => (
1024
- term.length >= 2
1025
- && (normalizedValue.includes(term) || term.includes(normalizedValue))
1026
- ));
1027
- if (!isContainedMatch) continue;
1028
-
1029
- const firstIndex = Math.min(
1030
- ...recordValues
1031
- .map((term) => normalizedValue.indexOf(term))
1032
- .filter((index) => index >= 0)
1033
- );
1034
- containedMatches.push({
1035
- index: Number.isFinite(firstIndex) ? firstIndex : Number.MAX_SAFE_INTEGER,
1036
- concepts: record.maps_to || [],
1037
- });
1038
- }
1039
- }
1040
-
1041
- containedMatches
1042
- .sort((left, right) => left.index - right.index)
1043
- .forEach((match) => {
1044
- for (const concept of match.concepts) {
1045
- const normalizedConcept = normalizeCjkSearchText(concept);
1046
- if (normalizedConcept) variants.push(normalizedConcept);
1047
- }
1048
- });
1049
-
1050
- return dedupe(variants);
1051
- }
1052
-
1053
- function buildSlotIntentTerms(task, slot, locale = null) {
1054
- const taskTokens = tokenizeText(task);
1055
- const slotTokens = tokenizeText(slot);
1056
- const usefulSlotTokens = slotTokens.filter((token) => !GENERIC_SLOT_WORDS.has(token));
1057
- const localizedSlotTokens = buildLocalizedVariants(slot, locale).flatMap(tokenizeText);
1058
- const localizedTaskTokens = buildLocalizedVariants(task, locale).flatMap(tokenizeText);
1059
- const directSlotTokens = buildDirectLocalizedIntentTerms(slot);
1060
-
1061
- const expanded = [...usefulSlotTokens];
1062
-
1063
- const usefulTaskTokens = taskTokens.filter((token) => !GENERIC_SLOT_WORDS.has(token));
1064
- expanded.push(...usefulTaskTokens);
1065
- expanded.push(...localizedSlotTokens);
1066
- expanded.push(...localizedTaskTokens);
1067
- expanded.push(...directSlotTokens);
1068
-
1069
- const variants = buildIntentQueryVariants(`${slot} ${task}`, {
1070
- baseQuery: slot,
1071
- maxVariants: 8,
958
+ .trim();
959
+ }
960
+
961
+ function normalizeToken(token) {
962
+ const value = String(token || '').toLowerCase();
963
+ if (value.length > 4 && value.endsWith('ies')) return `${value.slice(0, -3)}y`;
964
+ if (value.length > 3 && value.endsWith('es')) return value.slice(0, -2);
965
+ if (value.length > 3 && value.endsWith('s')) return value.slice(0, -1);
966
+ return value;
967
+ }
968
+
969
+ function tokenizeText(value) {
970
+ const normalized = normalizeText(value);
971
+ if (!normalized) return [];
972
+ const tokens = normalized.split(' ');
973
+ return dedupe([...tokens, ...tokens.map(normalizeToken)]);
974
+ }
975
+
976
+ function dedupe(values) {
977
+ return [...new Set(values.filter(Boolean))];
978
+ }
979
+
980
+ function buildDirectLocalizedIntentTerms(value) {
981
+ const text = String(value || '');
982
+ return DIRECT_LOCALIZED_INTENT_RULES
983
+ .filter((rule) => rule.pattern.test(text))
984
+ .flatMap((rule) => rule.terms);
985
+ }
986
+
987
+ function buildRequestedTermSet(intentTerms = []) {
988
+ return new Set(intentTerms.map(normalizeToken).filter(Boolean));
989
+ }
990
+
991
+ function isVariantTokenRequested(token, requestedTerms) {
992
+ const normalizedToken = normalizeToken(token);
993
+ if (requestedTerms.has(normalizedToken)) return true;
994
+ const aliases = REQUESTED_VARIANT_ALIASES[normalizedToken] || [];
995
+ return aliases.some((alias) => requestedTerms.has(normalizeToken(alias)));
996
+ }
997
+
998
+ function isIconVariantExplicitlyRequested(icon, intentTerms = []) {
999
+ const requestedTerms = buildRequestedTermSet(intentTerms);
1000
+ return tokenizeText(icon.id).some((token) => (
1001
+ VARIANT_TOKENS.has(normalizeToken(token)) &&
1002
+ isVariantTokenRequested(token, requestedTerms)
1003
+ ));
1004
+ }
1005
+
1006
+ function buildLocalizedVariants(value, locale) {
1007
+ if (!locale) return [];
1008
+ const expanded = expandCjkQuery(value, {
1009
+ locale,
1010
+ terms: multilingualExpansionTerms,
1011
+ });
1012
+ const variants = expanded.matched.length > 0 ? expanded.variants.slice(1) : [];
1013
+ const normalizedValue = normalizeCjkSearchText(value);
1014
+ const containedMatches = [];
1015
+
1016
+ if (normalizedValue) {
1017
+ for (const record of multilingualExpansionTerms) {
1018
+ if (record.locale !== locale || record.gate !== 'auto_accept') continue;
1019
+
1020
+ const recordValues = [record.term, ...(record.variants || [])]
1021
+ .map((term) => normalizeCjkSearchText(term))
1022
+ .filter(Boolean);
1023
+ const isContainedMatch = recordValues.some((term) => (
1024
+ term.length >= 2
1025
+ && (normalizedValue.includes(term) || term.includes(normalizedValue))
1026
+ ));
1027
+ if (!isContainedMatch) continue;
1028
+
1029
+ const firstIndex = Math.min(
1030
+ ...recordValues
1031
+ .map((term) => normalizedValue.indexOf(term))
1032
+ .filter((index) => index >= 0)
1033
+ );
1034
+ containedMatches.push({
1035
+ index: Number.isFinite(firstIndex) ? firstIndex : Number.MAX_SAFE_INTEGER,
1036
+ concepts: record.maps_to || [],
1037
+ });
1038
+ }
1039
+ }
1040
+
1041
+ containedMatches
1042
+ .sort((left, right) => left.index - right.index)
1043
+ .forEach((match) => {
1044
+ for (const concept of match.concepts) {
1045
+ const normalizedConcept = normalizeCjkSearchText(concept);
1046
+ if (normalizedConcept) variants.push(normalizedConcept);
1047
+ }
1048
+ });
1049
+
1050
+ return dedupe(variants);
1051
+ }
1052
+
1053
+ function buildSlotIntentTerms(task, slot, locale = null) {
1054
+ const taskTokens = tokenizeText(task);
1055
+ const slotTokens = tokenizeText(slot);
1056
+ const usefulSlotTokens = slotTokens.filter((token) => !GENERIC_SLOT_WORDS.has(token));
1057
+ const localizedSlotTokens = buildLocalizedVariants(slot, locale).flatMap(tokenizeText);
1058
+ const localizedTaskTokens = buildLocalizedVariants(task, locale).flatMap(tokenizeText);
1059
+ const directSlotTokens = buildDirectLocalizedIntentTerms(slot);
1060
+
1061
+ const expanded = [...usefulSlotTokens];
1062
+
1063
+ const usefulTaskTokens = taskTokens.filter((token) => !GENERIC_SLOT_WORDS.has(token));
1064
+ expanded.push(...usefulTaskTokens);
1065
+ expanded.push(...localizedSlotTokens);
1066
+ expanded.push(...localizedTaskTokens);
1067
+ expanded.push(...directSlotTokens);
1068
+
1069
+ const variants = buildIntentQueryVariants(`${slot} ${task}`, {
1070
+ baseQuery: slot,
1071
+ maxVariants: 8,
1072
1072
  });
1073
1073
  for (const variant of variants) {
1074
1074
  expanded.push(...tokenizeText(variant));
1075
1075
  }
1076
1076
 
1077
- const slotRuleTerms = dedupe([...usefulSlotTokens, ...localizedSlotTokens, ...directSlotTokens]);
1078
- for (const rule of getMatchingSlotRules(slot, slotRuleTerms)) {
1079
- for (const variant of rule.queryVariants || []) {
1080
- expanded.push(...tokenizeText(variant));
1081
- }
1077
+ const slotRuleTerms = dedupe([...usefulSlotTokens, ...localizedSlotTokens, ...directSlotTokens]);
1078
+ for (const rule of getMatchingSlotRules(slot, slotRuleTerms)) {
1079
+ for (const variant of rule.queryVariants || []) {
1080
+ expanded.push(...tokenizeText(variant));
1081
+ }
1082
+ }
1083
+
1084
+ return dedupe(expanded);
1085
+ }
1086
+
1087
+ function buildSlotQueryVariants(task, slot, locale = null) {
1088
+ const localizedSlotVariants = buildLocalizedVariants(slot, locale);
1089
+ const localizedVariants = [
1090
+ ...localizedSlotVariants,
1091
+ ...buildLocalizedVariants(`${slot} ${task}`, locale),
1092
+ ];
1093
+ const variants = buildIntentQueryVariants(`${slot} ${task}`, {
1094
+ baseQuery: slot,
1095
+ maxVariants: 8,
1096
+ });
1097
+ variants.unshift(...localizedVariants);
1098
+ const usefulSlotTokens = tokenizeText(slot).filter((token) => !GENERIC_SLOT_WORDS.has(token));
1099
+ variants.push(...usefulSlotTokens);
1100
+ const slotRuleTerms = [
1101
+ ...tokenizeText(`${slot} ${localizedSlotVariants.join(' ')}`),
1102
+ ...buildDirectLocalizedIntentTerms(slot),
1103
+ ]
1104
+ .filter((token) => !GENERIC_SLOT_WORDS.has(token));
1105
+ const ruleVariants = getMatchingSlotRules(slot, slotRuleTerms)
1106
+ .flatMap((rule) => rule.queryVariants || []);
1107
+ variants.unshift(...ruleVariants);
1108
+ return dedupe(variants).slice(0, 12);
1109
+ }
1110
+
1111
+ function scoreLexicalFit(icon, intentTerms, slotLabel, taskLabel = '') {
1112
+ const tokens = new Set([
1113
+ ...tokenizeText(icon.id),
1114
+ ...tokenizeText(icon.name),
1115
+ ...tokenizeText(`${icon.lib}:${icon.id}`),
1116
+ ]);
1117
+ const normalizedId = normalizeText(icon.id);
1118
+ const normalizedName = normalizeText(icon.name);
1119
+ const normalizedSlot = normalizeText(slotLabel);
1120
+ const slotTerms = tokenizeText(slotLabel).filter((token) => !GENERIC_SLOT_WORDS.has(token));
1121
+ const taskTerms = tokenizeText(taskLabel).filter((token) => !GENERIC_SLOT_WORDS.has(token));
1122
+
1123
+ let score = 0;
1124
+ for (const term of slotTerms) {
1125
+ if (tokens.has(term)) score += 22;
1126
+ else if (normalizedId.includes(term) || normalizedName.includes(term)) score += 14;
1127
+
1128
+ if (normalizedId === term || normalizedName === term) {
1129
+ score += 24;
1130
+ }
1131
+ }
1132
+
1133
+ for (const term of intentTerms) {
1134
+ if (tokens.has(term)) score += 12;
1135
+ else if (normalizedId.includes(term) || normalizedName.includes(term)) score += 7;
1136
+
1137
+ if (normalizedId === term || normalizedName === term) {
1138
+ score += 14;
1139
+ }
1140
+ }
1141
+
1142
+ for (const term of taskTerms) {
1143
+ if (tokens.has(term)) score += 3;
1082
1144
  }
1083
1145
 
1084
- return dedupe(expanded);
1085
- }
1086
-
1087
- function buildSlotQueryVariants(task, slot, locale = null) {
1088
- const localizedSlotVariants = buildLocalizedVariants(slot, locale);
1089
- const localizedVariants = [
1090
- ...localizedSlotVariants,
1091
- ...buildLocalizedVariants(`${slot} ${task}`, locale),
1092
- ];
1093
- const variants = buildIntentQueryVariants(`${slot} ${task}`, {
1094
- baseQuery: slot,
1095
- maxVariants: 8,
1096
- });
1097
- variants.unshift(...localizedVariants);
1098
- const usefulSlotTokens = tokenizeText(slot).filter((token) => !GENERIC_SLOT_WORDS.has(token));
1099
- variants.push(...usefulSlotTokens);
1100
- const slotRuleTerms = [
1101
- ...tokenizeText(`${slot} ${localizedSlotVariants.join(' ')}`),
1102
- ...buildDirectLocalizedIntentTerms(slot),
1103
- ]
1104
- .filter((token) => !GENERIC_SLOT_WORDS.has(token));
1105
- const ruleVariants = getMatchingSlotRules(slot, slotRuleTerms)
1106
- .flatMap((rule) => rule.queryVariants || []);
1107
- variants.unshift(...ruleVariants);
1108
- return dedupe(variants).slice(0, 12);
1109
- }
1110
-
1111
- function scoreLexicalFit(icon, intentTerms, slotLabel, taskLabel = '') {
1112
- const tokens = new Set([
1113
- ...tokenizeText(icon.id),
1114
- ...tokenizeText(icon.name),
1115
- ...tokenizeText(`${icon.lib}:${icon.id}`),
1116
- ]);
1117
- const normalizedId = normalizeText(icon.id);
1118
- const normalizedName = normalizeText(icon.name);
1119
- const normalizedSlot = normalizeText(slotLabel);
1120
- const slotTerms = tokenizeText(slotLabel).filter((token) => !GENERIC_SLOT_WORDS.has(token));
1121
- const taskTerms = tokenizeText(taskLabel).filter((token) => !GENERIC_SLOT_WORDS.has(token));
1122
-
1123
- let score = 0;
1124
- for (const term of slotTerms) {
1125
- if (tokens.has(term)) score += 22;
1126
- else if (normalizedId.includes(term) || normalizedName.includes(term)) score += 14;
1127
-
1128
- if (normalizedId === term || normalizedName === term) {
1129
- score += 24;
1130
- }
1131
- }
1132
-
1133
- for (const term of intentTerms) {
1134
- if (tokens.has(term)) score += 12;
1135
- else if (normalizedId.includes(term) || normalizedName.includes(term)) score += 7;
1136
-
1137
- if (normalizedId === term || normalizedName === term) {
1138
- score += 14;
1139
- }
1140
- }
1141
-
1142
- for (const term of taskTerms) {
1143
- if (tokens.has(term)) score += 3;
1144
- }
1145
-
1146
- if (normalizedSlot && (normalizedId === normalizedSlot || normalizedName === normalizedSlot)) {
1147
- score += 26;
1148
- }
1149
-
1150
- return score;
1151
- }
1152
-
1153
- function getVariantPenalty(icon, intentTerms = []) {
1154
- const normalizedId = normalizeText(icon.id);
1155
- const requestedTerms = buildRequestedTermSet(intentTerms);
1156
- let penalty = 0;
1157
- for (const rule of VARIANT_PENALTIES) {
1158
- if (!rule.pattern.test(normalizedId)) continue;
1159
- if (isVariantTokenRequested(rule.token, requestedTerms)) continue;
1160
- penalty += rule.penalty;
1161
- }
1162
- return penalty;
1163
- }
1164
-
1165
- function getBrandPenalty(icon, intentTerms = []) {
1166
- const requestedTerms = buildRequestedTermSet(intentTerms);
1167
- if (isVariantTokenRequested('brand', requestedTerms)) return 0;
1168
- return icon.lib === 'simpleicons' ? 80 : 0;
1169
- }
1170
-
1171
- function getMatchingSlotRules(slotLabel, intentTerms = []) {
1172
- const rawSlotText = String(slotLabel || '');
1173
- const slotText = normalizeText(slotLabel);
1174
- const intentText = normalizeText(intentTerms.join(' '));
1175
- return COMMON_SLOT_PREFERENCE_RULES
1176
- .filter((rule) => rule.slotPatterns.some((pattern) => (
1177
- pattern.test(slotText) ||
1178
- pattern.test(rawSlotText) ||
1179
- pattern.test(intentText)
1180
- )))
1181
- .sort((left, right) => (right.priority || 0) - (left.priority || 0));
1182
- }
1183
-
1184
- function scoreSlotPreferenceRules(icon, rules = [], intentTerms = []) {
1185
- let bonus = 0;
1186
- const explicitlyRequestedVariant = isIconVariantExplicitlyRequested(icon, intentTerms);
1187
-
1188
- for (const rule of rules) {
1189
- for (const preference of rule.iconPreferences) {
1190
- if (preference.pattern.test(icon.id)) {
1191
- if (preference.bonus < 0 && explicitlyRequestedVariant) continue;
1192
- bonus += preference.bonus;
1193
- }
1194
- }
1146
+ if (normalizedSlot && (normalizedId === normalizedSlot || normalizedName === normalizedSlot)) {
1147
+ score += 26;
1148
+ }
1149
+
1150
+ return score;
1151
+ }
1152
+
1153
+ function getVariantPenalty(icon, intentTerms = []) {
1154
+ const normalizedId = normalizeText(icon.id);
1155
+ const requestedTerms = buildRequestedTermSet(intentTerms);
1156
+ let penalty = 0;
1157
+ for (const rule of VARIANT_PENALTIES) {
1158
+ if (!rule.pattern.test(normalizedId)) continue;
1159
+ if (isVariantTokenRequested(rule.token, requestedTerms)) continue;
1160
+ penalty += rule.penalty;
1161
+ }
1162
+ return penalty;
1163
+ }
1164
+
1165
+ function getBrandPenalty(icon, intentTerms = []) {
1166
+ const requestedTerms = buildRequestedTermSet(intentTerms);
1167
+ if (isVariantTokenRequested('brand', requestedTerms)) return 0;
1168
+ return icon.lib === 'simpleicons' ? 80 : 0;
1169
+ }
1170
+
1171
+ function getMatchingSlotRules(slotLabel, intentTerms = []) {
1172
+ const rawSlotText = String(slotLabel || '');
1173
+ const slotText = normalizeText(slotLabel);
1174
+ const intentText = normalizeText(intentTerms.join(' '));
1175
+ return COMMON_SLOT_PREFERENCE_RULES
1176
+ .filter((rule) => rule.slotPatterns.some((pattern) => (
1177
+ pattern.test(slotText) ||
1178
+ pattern.test(rawSlotText) ||
1179
+ pattern.test(intentText)
1180
+ )))
1181
+ .sort((left, right) => (right.priority || 0) - (left.priority || 0));
1182
+ }
1183
+
1184
+ function scoreSlotPreferenceRules(icon, rules = [], intentTerms = []) {
1185
+ let bonus = 0;
1186
+ const explicitlyRequestedVariant = isIconVariantExplicitlyRequested(icon, intentTerms);
1187
+
1188
+ for (const rule of rules) {
1189
+ for (const preference of rule.iconPreferences) {
1190
+ if (preference.pattern.test(icon.id)) {
1191
+ if (preference.bonus < 0 && explicitlyRequestedVariant) continue;
1192
+ bonus += preference.bonus;
1193
+ }
1194
+ }
1195
1195
  }
1196
1196
 
1197
1197
  return bonus;
1198
1198
  }
1199
1199
 
1200
- function getSlotPreferenceBonus(icon, slotLabel, intentTerms, library, requestedVariantTerms = intentTerms) {
1201
- const slotText = `${slotLabel} ${requestedVariantTerms.join(' ')}`;
1202
- const commonRules = getMatchingSlotRules(slotLabel, requestedVariantTerms);
1203
- const libraryRules = (SLOT_PREFERENCE_RULES[library] || [])
1204
- .filter((rule) => rule.slotPatterns.some((pattern) => pattern.test(slotText)));
1205
-
1206
- return scoreSlotPreferenceRules(icon, commonRules, requestedVariantTerms) +
1207
- scoreSlotPreferenceRules(icon, libraryRules, requestedVariantTerms);
1208
- }
1200
+ function getSlotPreferenceBonus(icon, slotLabel, intentTerms, library, requestedVariantTerms = intentTerms) {
1201
+ const slotText = `${slotLabel} ${requestedVariantTerms.join(' ')}`;
1202
+ const commonRules = getMatchingSlotRules(slotLabel, requestedVariantTerms);
1203
+ const libraryRules = (SLOT_PREFERENCE_RULES[library] || [])
1204
+ .filter((rule) => rule.slotPatterns.some((pattern) => pattern.test(slotText)));
1205
+
1206
+ return scoreSlotPreferenceRules(icon, commonRules, requestedVariantTerms) +
1207
+ scoreSlotPreferenceRules(icon, libraryRules, requestedVariantTerms);
1208
+ }
1209
1209
 
1210
1210
  function summarizeSemanticFit(slotLabel, semanticRecord, intentTerms) {
1211
1211
  if (semanticRecord?.depicts && semanticRecord?.use_when) {
@@ -1223,7 +1223,7 @@ function summarizeSemanticFit(slotLabel, semanticRecord, intentTerms) {
1223
1223
  return `Best available match for ${slotLabel}.`;
1224
1224
  }
1225
1225
 
1226
- function buildWhySelected(slotLabel, semanticRecord, iconResult) {
1226
+ function buildWhySelected(slotLabel, semanticRecord, iconResult) {
1227
1227
  const label = semanticRecord?.label || iconResult.name;
1228
1228
  if (semanticRecord?.depicts) {
1229
1229
  return `${label} matches ${slotLabel} and visually reads as ${String(semanticRecord.depicts).toLowerCase()}.`;
@@ -1231,43 +1231,43 @@ function buildWhySelected(slotLabel, semanticRecord, iconResult) {
1231
1231
  if (semanticRecord?.use_when) {
1232
1232
  return `${label} matches ${slotLabel}. ${semanticRecord.use_when}`;
1233
1233
  }
1234
- return `${label} is the clearest match for ${slotLabel} from the current library.`;
1235
- }
1236
-
1237
- function buildCandidatePayload(
1238
- slotLabel,
1239
- iconResult,
1240
- semanticRecord,
1241
- intentTerms,
1242
- responseMode = 'plan',
1243
- includeSvg = false,
1244
- includeReason = true
1245
- ) {
1246
- const payload = {
1247
- id: iconResult.id,
1248
- library: iconResult.library,
1249
- name: iconResult.name,
1250
- style: iconResult.style || 'outline',
1251
- label: semanticRecord?.label || iconResult.semantic?.label || iconResult.name,
1252
- };
1253
-
1254
- if (includeReason) {
1255
- payload.semantic_fit = summarizeSemanticFit(slotLabel, semanticRecord, intentTerms);
1256
- payload.why_selected = buildWhySelected(slotLabel, semanticRecord, iconResult);
1257
- }
1258
-
1259
- if (includeSvg) {
1260
- payload.svg = iconResult.svg;
1261
- }
1262
-
1263
- if (responseMode === 'full') {
1264
- payload.semantic = buildPublicSemanticPayload(semanticRecord) || iconResult.semantic || null;
1265
- }
1266
-
1267
- return payload;
1268
- }
1269
-
1270
- async function mapWithConcurrency(items, limit, mapper) {
1234
+ return `${label} is the clearest match for ${slotLabel} from the current library.`;
1235
+ }
1236
+
1237
+ function buildCandidatePayload(
1238
+ slotLabel,
1239
+ iconResult,
1240
+ semanticRecord,
1241
+ intentTerms,
1242
+ responseMode = 'plan',
1243
+ includeSvg = false,
1244
+ includeReason = true
1245
+ ) {
1246
+ const payload = {
1247
+ id: iconResult.id,
1248
+ library: iconResult.library,
1249
+ name: iconResult.name,
1250
+ style: iconResult.style || 'outline',
1251
+ label: semanticRecord?.label || iconResult.semantic?.label || iconResult.name,
1252
+ };
1253
+
1254
+ if (includeReason) {
1255
+ payload.semantic_fit = summarizeSemanticFit(slotLabel, semanticRecord, intentTerms);
1256
+ payload.why_selected = buildWhySelected(slotLabel, semanticRecord, iconResult);
1257
+ }
1258
+
1259
+ if (includeSvg) {
1260
+ payload.svg = iconResult.svg;
1261
+ }
1262
+
1263
+ if (responseMode === 'full') {
1264
+ payload.semantic = buildPublicSemanticPayload(semanticRecord) || iconResult.semantic || null;
1265
+ }
1266
+
1267
+ return payload;
1268
+ }
1269
+
1270
+ async function mapWithConcurrency(items, limit, mapper) {
1271
1271
  const results = new Array(items.length);
1272
1272
  let nextIndex = 0;
1273
1273
 
@@ -1280,70 +1280,70 @@ async function mapWithConcurrency(items, limit, mapper) {
1280
1280
  }
1281
1281
 
1282
1282
  const workerCount = Math.min(Math.max(1, limit), items.length);
1283
- await Promise.all(Array.from({ length: workerCount }, () => worker()));
1284
- return results;
1285
- }
1286
-
1287
- function getConfidence(topScore, nextScore = 0) {
1288
- if (topScore >= 90 && topScore - nextScore >= 20) {
1289
- return { level: 'high', score: topScore };
1290
- }
1291
- if (topScore >= 45) {
1292
- return { level: 'medium', score: topScore };
1293
- }
1294
- return { level: 'low', score: topScore };
1295
- }
1296
-
1297
- function buildLowConfidenceHint(slotLabel, queriesUsed) {
1298
- return `Low confidence for ${slotLabel}. Try search_icons with: ${queriesUsed.slice(0, 3).join(', ')}.`;
1299
- }
1300
-
1301
- function normalizeResponseMode(responseMode) {
1302
- if (responseMode === 'assets' || responseMode === 'full') return responseMode;
1303
- return 'plan';
1304
- }
1305
-
1306
- function isNoisyAlternative(entry) {
1307
- return entry.variantPenalty >= 12 || entry.brandPenalty >= 12 || entry.slotPreferenceBonus < 0;
1308
- }
1309
-
1310
- export async function recommendIconsForTask({
1311
- task,
1312
- library,
1313
- style = 'any',
1314
- locale = null,
1315
- slots,
1316
- limitPerSlot = 3,
1317
- responseMode = 'plan',
1318
- searchIconsForQuery,
1319
- buildIconResult,
1320
- semanticMap,
1321
- }) {
1322
- const normalizedResponseMode = normalizeResponseMode(responseMode);
1323
- const scoredSlotResults = await mapWithConcurrency(slots, SLOT_SEARCH_CONCURRENCY, async (slotLabel) => {
1324
- const intentTerms = buildSlotIntentTerms(task, slotLabel, locale);
1325
- const requestedVariantTerms = dedupe([
1326
- ...tokenizeText(slotLabel),
1327
- ...buildLocalizedVariants(slotLabel, locale).flatMap(tokenizeText),
1328
- ...buildDirectLocalizedIntentTerms(slotLabel),
1329
- ]);
1330
- const queryVariants = buildSlotQueryVariants(task, slotLabel, locale).slice(0, locale ? 8 : 4);
1331
- const pooledIcons = [];
1332
- const seen = new Set();
1333
-
1334
- const resultGroups = await mapWithConcurrency(queryVariants, SLOT_QUERY_CONCURRENCY, async (queryVariant) => {
1335
- try {
1336
- return await searchIconsForQuery({
1337
- query: queryVariant,
1338
- library,
1339
- style,
1340
- limit: Math.max(limitPerSlot * 5, 10),
1341
- locale,
1342
- });
1343
- } catch {
1344
- return [];
1345
- }
1346
- });
1283
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
1284
+ return results;
1285
+ }
1286
+
1287
+ function getConfidence(topScore, nextScore = 0) {
1288
+ if (topScore >= 90 && topScore - nextScore >= 20) {
1289
+ return { level: 'high', score: topScore };
1290
+ }
1291
+ if (topScore >= 45) {
1292
+ return { level: 'medium', score: topScore };
1293
+ }
1294
+ return { level: 'low', score: topScore };
1295
+ }
1296
+
1297
+ function buildLowConfidenceHint(slotLabel, queriesUsed) {
1298
+ return `Low confidence for ${slotLabel}. Try search_icons with: ${queriesUsed.slice(0, 3).join(', ')}.`;
1299
+ }
1300
+
1301
+ function normalizeResponseMode(responseMode) {
1302
+ if (responseMode === 'assets' || responseMode === 'full') return responseMode;
1303
+ return 'plan';
1304
+ }
1305
+
1306
+ function isNoisyAlternative(entry) {
1307
+ return entry.variantPenalty >= 12 || entry.brandPenalty >= 12 || entry.slotPreferenceBonus < 0;
1308
+ }
1309
+
1310
+ export async function recommendIconsForTask({
1311
+ task,
1312
+ library,
1313
+ style = 'any',
1314
+ locale = null,
1315
+ slots,
1316
+ limitPerSlot = 3,
1317
+ responseMode = 'plan',
1318
+ searchIconsForQuery,
1319
+ buildIconResult,
1320
+ semanticMap,
1321
+ }) {
1322
+ const normalizedResponseMode = normalizeResponseMode(responseMode);
1323
+ const scoredSlotResults = await mapWithConcurrency(slots, SLOT_SEARCH_CONCURRENCY, async (slotLabel) => {
1324
+ const intentTerms = buildSlotIntentTerms(task, slotLabel, locale);
1325
+ const requestedVariantTerms = dedupe([
1326
+ ...tokenizeText(slotLabel),
1327
+ ...buildLocalizedVariants(slotLabel, locale).flatMap(tokenizeText),
1328
+ ...buildDirectLocalizedIntentTerms(slotLabel),
1329
+ ]);
1330
+ const queryVariants = buildSlotQueryVariants(task, slotLabel, locale).slice(0, locale ? 8 : 4);
1331
+ const pooledIcons = [];
1332
+ const seen = new Set();
1333
+
1334
+ const resultGroups = await mapWithConcurrency(queryVariants, SLOT_QUERY_CONCURRENCY, async (queryVariant) => {
1335
+ try {
1336
+ return await searchIconsForQuery({
1337
+ query: queryVariant,
1338
+ library,
1339
+ style,
1340
+ limit: Math.max(limitPerSlot * 5, 10),
1341
+ locale,
1342
+ });
1343
+ } catch {
1344
+ return [];
1345
+ }
1346
+ });
1347
1347
 
1348
1348
  for (const results of resultGroups) {
1349
1349
  for (const icon of results) {
@@ -1355,137 +1355,137 @@ export async function recommendIconsForTask({
1355
1355
  }
1356
1356
 
1357
1357
  const scored = pooledIcons
1358
- .map((icon, index) => {
1359
- const semanticRecord = getSemanticRecordForIcon(semanticMap, icon);
1360
- const semanticQuery = queryVariants.join(' ');
1361
- const semanticScore = semanticRecord ? scoreSemanticAlignment(semanticQuery, semanticRecord) * 3 : 0;
1362
- const lexicalScore = scoreLexicalFit(icon, intentTerms, slotLabel, task);
1363
- const semanticBonus = semanticRecord ? 6 : 0;
1364
- const variantPenalty = getVariantPenalty(icon, requestedVariantTerms);
1365
- const brandPenalty = getBrandPenalty(icon, requestedVariantTerms);
1366
- const slotPreferenceBonus = getSlotPreferenceBonus(icon, slotLabel, intentTerms, library, requestedVariantTerms);
1367
- const intentProfile = buildSearchIntentProfile(`${slotLabel} ${task}`);
1368
- const intentAdjustment = getIntentCandidateAdjustment(icon, intentProfile);
1369
-
1358
+ .map((icon, index) => {
1359
+ const semanticRecord = getSemanticRecordForIcon(semanticMap, icon);
1360
+ const semanticQuery = queryVariants.join(' ');
1361
+ const semanticScore = semanticRecord ? scoreSemanticAlignment(semanticQuery, semanticRecord) * 3 : 0;
1362
+ const lexicalScore = scoreLexicalFit(icon, intentTerms, slotLabel, task);
1363
+ const semanticBonus = semanticRecord ? 6 : 0;
1364
+ const variantPenalty = getVariantPenalty(icon, requestedVariantTerms);
1365
+ const brandPenalty = getBrandPenalty(icon, requestedVariantTerms);
1366
+ const slotPreferenceBonus = getSlotPreferenceBonus(icon, slotLabel, intentTerms, library, requestedVariantTerms);
1367
+ const intentProfile = buildSearchIntentProfile(`${slotLabel} ${task}`);
1368
+ const intentAdjustment = getIntentCandidateAdjustment(icon, intentProfile);
1369
+
1370
1370
  return {
1371
- icon,
1372
- index,
1373
- semanticRecord,
1374
- variantPenalty,
1375
- brandPenalty,
1376
- slotPreferenceBonus,
1377
- totalScore:
1371
+ icon,
1372
+ index,
1373
+ semanticRecord,
1374
+ variantPenalty,
1375
+ brandPenalty,
1376
+ slotPreferenceBonus,
1377
+ totalScore:
1378
1378
  semanticScore +
1379
1379
  lexicalScore +
1380
1380
  semanticBonus +
1381
- slotPreferenceBonus +
1382
- intentAdjustment.boost -
1383
- intentAdjustment.penalty -
1384
- variantPenalty -
1385
- brandPenalty,
1386
- };
1387
- })
1388
- .filter((entry) => entry.totalScore > 0)
1389
- .sort((left, right) => {
1390
- if (right.totalScore !== left.totalScore) return right.totalScore - left.totalScore;
1391
- if (right.slotPreferenceBonus !== left.slotPreferenceBonus) {
1392
- return right.slotPreferenceBonus - left.slotPreferenceBonus;
1393
- }
1394
- return left.index - right.index;
1395
- })
1396
- .slice(0, Math.max(limitPerSlot * 3, 8));
1397
-
1398
- return {
1399
- slot: slotLabel,
1400
- queries_used: queryVariants,
1401
- intentTerms,
1402
- requestedVariantTerms,
1403
- scored,
1404
- };
1405
- });
1406
-
1407
- const usedIconKeys = new Set();
1408
- const slotResults = [];
1409
- for (const slotResult of scoredSlotResults) {
1410
- const sorted = [...slotResult.scored].sort((left, right) => {
1411
- const leftKey = `${left.icon.lib}:${left.icon.id}`;
1412
- const rightKey = `${right.icon.lib}:${right.icon.id}`;
1413
- const leftDuplicatePenalty = usedIconKeys.has(leftKey) ? 80 : 0;
1414
- const rightDuplicatePenalty = usedIconKeys.has(rightKey) ? 80 : 0;
1415
- const leftScore = left.totalScore - leftDuplicatePenalty;
1416
- const rightScore = right.totalScore - rightDuplicatePenalty;
1417
-
1418
- if (rightScore !== leftScore) return rightScore - leftScore;
1419
- return left.index - right.index;
1420
- });
1421
- const selectedEntries = [];
1422
- const primaryEntry = sorted.find((entry) => (
1423
- !isNoisyAlternative(entry) &&
1424
- !usedIconKeys.has(`${entry.icon.lib}:${entry.icon.id}`)
1425
- )) || sorted.find((entry) => !isNoisyAlternative(entry)) || sorted[0];
1426
- if (primaryEntry) {
1427
- selectedEntries.push(primaryEntry);
1428
- }
1429
- for (const entry of sorted) {
1430
- if (selectedEntries.length >= limitPerSlot) break;
1431
- if (selectedEntries.includes(entry)) continue;
1432
- if (isNoisyAlternative(entry)) continue;
1433
- selectedEntries.push(entry);
1434
- }
1435
- const preparedCandidates = [];
1436
- const preparedEntries = [];
1437
- for (const [candidateIndex, entry] of selectedEntries.entries()) {
1438
- const iconResult = await buildIconResult(entry.icon, { style });
1439
- if (!iconResult?.svg) continue;
1440
- const includeSvg = normalizedResponseMode === 'full' || (normalizedResponseMode === 'assets' && candidateIndex === 0);
1441
- preparedCandidates.push(buildCandidatePayload(
1442
- slotResult.slot,
1443
- iconResult,
1444
- entry.semanticRecord,
1445
- slotResult.intentTerms,
1446
- normalizedResponseMode,
1447
- includeSvg,
1448
- normalizedResponseMode !== 'plan' || candidateIndex === 0
1449
- ));
1450
- preparedEntries.push(entry);
1451
- }
1452
- const chosen = preparedEntries[0] || primaryEntry || null;
1453
- if (chosen) {
1454
- usedIconKeys.add(`${chosen.icon.lib}:${chosen.icon.id}`);
1455
- }
1456
- const confidence = chosen
1457
- ? getConfidence(chosen.totalScore, sorted[1]?.totalScore || 0)
1458
- : { level: 'low', score: 0 };
1459
-
1460
- const slotPayload = {
1461
- slot: slotResult.slot,
1462
- confidence,
1463
- recommended: preparedCandidates[0] || null,
1464
- alternatives: preparedCandidates.slice(1),
1465
- };
1466
- if (confidence.level === 'low') {
1467
- slotPayload.guidance = buildLowConfidenceHint(slotResult.slot, slotResult.queries_used);
1468
- }
1469
- if (normalizedResponseMode !== 'plan') {
1470
- slotPayload.queries_used = slotResult.queries_used;
1471
- }
1472
- slotResults.push(slotPayload);
1473
- }
1474
-
1475
- const lowConfidenceSlots = slotResults
1476
- .filter((slot) => !slot.recommended || slot.confidence?.level === 'low')
1477
- .map((slot) => slot.slot);
1478
- const allSlotsResolved = slotResults.every((slot) => Boolean(slot.recommended));
1479
-
1480
- return {
1481
- task,
1482
- library: library || 'all',
1483
- style,
1484
- response_mode: normalizedResponseMode,
1485
- slot_count: slots.length,
1486
- all_slots_resolved: allSlotsResolved,
1487
- low_confidence_slots: lowConfidenceSlots,
1488
- fallback_recommended: !allSlotsResolved || lowConfidenceSlots.length > 0,
1489
- results: slotResults,
1490
- };
1491
- }
1381
+ slotPreferenceBonus +
1382
+ intentAdjustment.boost -
1383
+ intentAdjustment.penalty -
1384
+ variantPenalty -
1385
+ brandPenalty,
1386
+ };
1387
+ })
1388
+ .filter((entry) => entry.totalScore > 0)
1389
+ .sort((left, right) => {
1390
+ if (right.totalScore !== left.totalScore) return right.totalScore - left.totalScore;
1391
+ if (right.slotPreferenceBonus !== left.slotPreferenceBonus) {
1392
+ return right.slotPreferenceBonus - left.slotPreferenceBonus;
1393
+ }
1394
+ return left.index - right.index;
1395
+ })
1396
+ .slice(0, Math.max(limitPerSlot * 3, 8));
1397
+
1398
+ return {
1399
+ slot: slotLabel,
1400
+ queries_used: queryVariants,
1401
+ intentTerms,
1402
+ requestedVariantTerms,
1403
+ scored,
1404
+ };
1405
+ });
1406
+
1407
+ const usedIconKeys = new Set();
1408
+ const slotResults = [];
1409
+ for (const slotResult of scoredSlotResults) {
1410
+ const sorted = [...slotResult.scored].sort((left, right) => {
1411
+ const leftKey = `${left.icon.lib}:${left.icon.id}`;
1412
+ const rightKey = `${right.icon.lib}:${right.icon.id}`;
1413
+ const leftDuplicatePenalty = usedIconKeys.has(leftKey) ? 80 : 0;
1414
+ const rightDuplicatePenalty = usedIconKeys.has(rightKey) ? 80 : 0;
1415
+ const leftScore = left.totalScore - leftDuplicatePenalty;
1416
+ const rightScore = right.totalScore - rightDuplicatePenalty;
1417
+
1418
+ if (rightScore !== leftScore) return rightScore - leftScore;
1419
+ return left.index - right.index;
1420
+ });
1421
+ const selectedEntries = [];
1422
+ const primaryEntry = sorted.find((entry) => (
1423
+ !isNoisyAlternative(entry) &&
1424
+ !usedIconKeys.has(`${entry.icon.lib}:${entry.icon.id}`)
1425
+ )) || sorted.find((entry) => !isNoisyAlternative(entry)) || sorted[0];
1426
+ if (primaryEntry) {
1427
+ selectedEntries.push(primaryEntry);
1428
+ }
1429
+ for (const entry of sorted) {
1430
+ if (selectedEntries.length >= limitPerSlot) break;
1431
+ if (selectedEntries.includes(entry)) continue;
1432
+ if (isNoisyAlternative(entry)) continue;
1433
+ selectedEntries.push(entry);
1434
+ }
1435
+ const preparedCandidates = [];
1436
+ const preparedEntries = [];
1437
+ for (const [candidateIndex, entry] of selectedEntries.entries()) {
1438
+ const iconResult = await buildIconResult(entry.icon, { style });
1439
+ if (!iconResult?.svg) continue;
1440
+ const includeSvg = normalizedResponseMode === 'full' || (normalizedResponseMode === 'assets' && candidateIndex === 0);
1441
+ preparedCandidates.push(buildCandidatePayload(
1442
+ slotResult.slot,
1443
+ iconResult,
1444
+ entry.semanticRecord,
1445
+ slotResult.intentTerms,
1446
+ normalizedResponseMode,
1447
+ includeSvg,
1448
+ normalizedResponseMode !== 'plan' || candidateIndex === 0
1449
+ ));
1450
+ preparedEntries.push(entry);
1451
+ }
1452
+ const chosen = preparedEntries[0] || primaryEntry || null;
1453
+ if (chosen) {
1454
+ usedIconKeys.add(`${chosen.icon.lib}:${chosen.icon.id}`);
1455
+ }
1456
+ const confidence = chosen
1457
+ ? getConfidence(chosen.totalScore, sorted[1]?.totalScore || 0)
1458
+ : { level: 'low', score: 0 };
1459
+
1460
+ const slotPayload = {
1461
+ slot: slotResult.slot,
1462
+ confidence,
1463
+ recommended: preparedCandidates[0] || null,
1464
+ alternatives: preparedCandidates.slice(1),
1465
+ };
1466
+ if (confidence.level === 'low') {
1467
+ slotPayload.guidance = buildLowConfidenceHint(slotResult.slot, slotResult.queries_used);
1468
+ }
1469
+ if (normalizedResponseMode !== 'plan') {
1470
+ slotPayload.queries_used = slotResult.queries_used;
1471
+ }
1472
+ slotResults.push(slotPayload);
1473
+ }
1474
+
1475
+ const lowConfidenceSlots = slotResults
1476
+ .filter((slot) => !slot.recommended || slot.confidence?.level === 'low')
1477
+ .map((slot) => slot.slot);
1478
+ const allSlotsResolved = slotResults.every((slot) => Boolean(slot.recommended));
1479
+
1480
+ return {
1481
+ task,
1482
+ library: library || 'all',
1483
+ style,
1484
+ response_mode: normalizedResponseMode,
1485
+ slot_count: slots.length,
1486
+ all_slots_resolved: allSlotsResolved,
1487
+ low_confidence_slots: lowConfidenceSlots,
1488
+ fallback_recommended: !allSlotsResolved || lowConfidenceSlots.length > 0,
1489
+ results: slotResults,
1490
+ };
1491
+ }