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