@supericons/mcp 0.4.9 → 0.4.11

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