@blotoutio/providers-evo-search-sdk 1.65.2 → 1.66.1
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.
- package/core.cjs.js +2 -0
- package/core.js +2 -0
- package/core.mjs +2 -0
- package/hooks.d.ts +8 -0
- package/index.cjs.js +279 -45
- package/index.js +279 -45
- package/index.mjs +279 -45
- package/package.json +1 -1
package/core.cjs.js
CHANGED
|
@@ -1244,6 +1244,7 @@ const init = (params) => {
|
|
|
1244
1244
|
const tag = ({ data }) => {
|
|
1245
1245
|
try {
|
|
1246
1246
|
return {
|
|
1247
|
+
sdkVersion: "1.66.1" || '',
|
|
1247
1248
|
searchQuery: data['searchQuery'],
|
|
1248
1249
|
searchResults: data['searchResults'],
|
|
1249
1250
|
timestamp: Date.now(),
|
|
@@ -1252,6 +1253,7 @@ const tag = ({ data }) => {
|
|
|
1252
1253
|
catch (err) {
|
|
1253
1254
|
console.error(`EvoSearch SDK: Tagging failed - ${err}`);
|
|
1254
1255
|
return {
|
|
1256
|
+
sdkVersion: "1.66.1" ,
|
|
1255
1257
|
timestamp: Date.now(),
|
|
1256
1258
|
};
|
|
1257
1259
|
}
|
package/core.js
CHANGED
|
@@ -1245,6 +1245,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
1245
1245
|
const tag = ({ data }) => {
|
|
1246
1246
|
try {
|
|
1247
1247
|
return {
|
|
1248
|
+
sdkVersion: "1.66.1" || '',
|
|
1248
1249
|
searchQuery: data['searchQuery'],
|
|
1249
1250
|
searchResults: data['searchResults'],
|
|
1250
1251
|
timestamp: Date.now(),
|
|
@@ -1253,6 +1254,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
1253
1254
|
catch (err) {
|
|
1254
1255
|
console.error(`EvoSearch SDK: Tagging failed - ${err}`);
|
|
1255
1256
|
return {
|
|
1257
|
+
sdkVersion: "1.66.1" ,
|
|
1256
1258
|
timestamp: Date.now(),
|
|
1257
1259
|
};
|
|
1258
1260
|
}
|
package/core.mjs
CHANGED
|
@@ -1242,6 +1242,7 @@ const init = (params) => {
|
|
|
1242
1242
|
const tag = ({ data }) => {
|
|
1243
1243
|
try {
|
|
1244
1244
|
return {
|
|
1245
|
+
sdkVersion: "1.66.1" || '',
|
|
1245
1246
|
searchQuery: data['searchQuery'],
|
|
1246
1247
|
searchResults: data['searchResults'],
|
|
1247
1248
|
timestamp: Date.now(),
|
|
@@ -1250,6 +1251,7 @@ const tag = ({ data }) => {
|
|
|
1250
1251
|
catch (err) {
|
|
1251
1252
|
console.error(`EvoSearch SDK: Tagging failed - ${err}`);
|
|
1252
1253
|
return {
|
|
1254
|
+
sdkVersion: "1.66.1" ,
|
|
1253
1255
|
timestamp: Date.now(),
|
|
1254
1256
|
};
|
|
1255
1257
|
}
|
package/hooks.d.ts
CHANGED
|
@@ -56,6 +56,12 @@ declare module '@blotoutio/providers-evo-search-sdk/hooks' {
|
|
|
56
56
|
llmSuggestions: string[]
|
|
57
57
|
products: AutocompleteAPIProduct[]
|
|
58
58
|
collections?: AutocompleteAPICollection[]
|
|
59
|
+
signals?: {
|
|
60
|
+
bm25: boolean
|
|
61
|
+
ann: boolean
|
|
62
|
+
rerank: boolean
|
|
63
|
+
degraded: boolean
|
|
64
|
+
}
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
export type RecentsAPIResponse = {
|
|
@@ -65,6 +71,8 @@ declare module '@blotoutio/providers-evo-search-sdk/hooks' {
|
|
|
65
71
|
export type AskConfigResponse = {
|
|
66
72
|
askAiEnabled: boolean
|
|
67
73
|
askStarters?: string[]
|
|
74
|
+
askNudge: 'always' | 'weak' | 'never'
|
|
75
|
+
askNudgeLabel: string
|
|
68
76
|
brandName?: string
|
|
69
77
|
brandVoice?: string
|
|
70
78
|
storeDescription?: string
|
package/index.cjs.js
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// When the Search tab offers Ask AI alongside its results. `always`: on every
|
|
4
|
+
// result set (the default — the tab pill alone doesn't read as an offer).
|
|
5
|
+
// `weak`: only when retrieval had no lexical anchor, i.e. the response's
|
|
6
|
+
// `signals.bm25` is false, so the products on screen are a semantic guess.
|
|
7
|
+
// `never`: the search results never mention Ask AI (the zero-result recovery
|
|
8
|
+
// CTA is unaffected — that one is a dead end, not a nudge).
|
|
9
|
+
// The tuple is the single source of truth: the API payload enum, the worker's
|
|
10
|
+
// write validator and the manifest coercion all derive from it, so adding a
|
|
11
|
+
// mode can't leave one layer rejecting it.
|
|
12
|
+
// Default on: the segmented tab pill alone doesn't read as an offer, which is
|
|
13
|
+
// the whole reason the nudge exists. Merchants dial it back to 'weak'/'never'.
|
|
14
|
+
const DEFAULT_ASK_NUDGE_MODE = 'always';
|
|
15
|
+
// Merchant-authored label for the nudge. `{query}` is substituted with the
|
|
16
|
+
// shopper's search terms; a label without the token renders verbatim, which is
|
|
17
|
+
// the escape hatch for merchants who don't want the query echoed back.
|
|
18
|
+
// Substitution happens through Lit text bindings, so the query is escaped —
|
|
19
|
+
// never interpolate this into markup.
|
|
20
|
+
const ASK_NUDGE_QUERY_TOKEN = '{query}';
|
|
21
|
+
const DEFAULT_ASK_NUDGE_LABEL = `Ask AI about ${ASK_NUDGE_QUERY_TOKEN}`;
|
|
22
|
+
|
|
3
23
|
/**
|
|
4
24
|
* Known ad-network click ID query parameters and the human-readable provider
|
|
5
25
|
* label each one maps to. Used by the CDN worker to resolve `inSessionTouch`
|
|
@@ -630,7 +650,7 @@ const setStoredSearchMode = (mode) => {
|
|
|
630
650
|
const searchSessionKeyName = 'evoSearchSession';
|
|
631
651
|
const SEARCH_SESSION_VERSION = 1;
|
|
632
652
|
const getStoredSearchSession = () => {
|
|
633
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
653
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
634
654
|
try {
|
|
635
655
|
const raw = sessionStorage.getItem(searchSessionKeyName);
|
|
636
656
|
if (!raw) {
|
|
@@ -652,10 +672,12 @@ const getStoredSearchSession = () => {
|
|
|
652
672
|
suggestions: (_a = parsed.suggestions) !== null && _a !== void 0 ? _a : [],
|
|
653
673
|
products: (_b = parsed.products) !== null && _b !== void 0 ? _b : [],
|
|
654
674
|
collections: (_c = parsed.collections) !== null && _c !== void 0 ? _c : [],
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
675
|
+
// Absent on pre-nudge snapshots — true matches the widget's own default.
|
|
676
|
+
hadLexicalMatch: (_d = parsed.hadLexicalMatch) !== null && _d !== void 0 ? _d : true,
|
|
677
|
+
askAnswer: (_e = parsed.askAnswer) !== null && _e !== void 0 ? _e : '',
|
|
678
|
+
askEvidence: (_f = parsed.askEvidence) !== null && _f !== void 0 ? _f : null,
|
|
679
|
+
askFollowUps: (_g = parsed.askFollowUps) !== null && _g !== void 0 ? _g : [],
|
|
680
|
+
askCitedTitles: (_h = parsed.askCitedTitles) !== null && _h !== void 0 ? _h : [],
|
|
659
681
|
};
|
|
660
682
|
}
|
|
661
683
|
catch {
|
|
@@ -1429,6 +1451,7 @@ const init = (params) => {
|
|
|
1429
1451
|
const tag = ({ data }) => {
|
|
1430
1452
|
try {
|
|
1431
1453
|
return {
|
|
1454
|
+
sdkVersion: "1.66.1" || '',
|
|
1432
1455
|
searchQuery: data['searchQuery'],
|
|
1433
1456
|
searchResults: data['searchResults'],
|
|
1434
1457
|
timestamp: Date.now(),
|
|
@@ -1437,6 +1460,7 @@ const tag = ({ data }) => {
|
|
|
1437
1460
|
catch (err) {
|
|
1438
1461
|
console.error(`EvoSearch SDK: Tagging failed - ${err}`);
|
|
1439
1462
|
return {
|
|
1463
|
+
sdkVersion: "1.66.1" ,
|
|
1440
1464
|
timestamp: Date.now(),
|
|
1441
1465
|
};
|
|
1442
1466
|
}
|
|
@@ -7566,12 +7590,12 @@ const stateBlockStyles = i$6 `
|
|
|
7566
7590
|
|
|
7567
7591
|
/* The AI rescue is the primary recovery on a dead-end search. */
|
|
7568
7592
|
.chip-ask {
|
|
7569
|
-
background: var(--evosearch-ask-accent, #
|
|
7593
|
+
background: var(--evosearch-ask-accent, #7c3aed);
|
|
7570
7594
|
color: #ffffff;
|
|
7571
7595
|
}
|
|
7572
7596
|
|
|
7573
7597
|
.chip-ask:hover {
|
|
7574
|
-
background: var(--evosearch-ask-accent, #
|
|
7598
|
+
background: var(--evosearch-ask-accent, #7c3aed);
|
|
7575
7599
|
filter: brightness(0.95);
|
|
7576
7600
|
}
|
|
7577
7601
|
|
|
@@ -7840,7 +7864,7 @@ const searchWidgetStyles = i$6 `
|
|
|
7840
7864
|
/* Ask mode swaps the active accent; everything inside the container reads
|
|
7841
7865
|
var(--evosearch-mode-accent) (custom props inherit to evs-box + cards). */
|
|
7842
7866
|
.search-widget-container[data-mode='ask'] {
|
|
7843
|
-
--evosearch-mode-accent: var(--evosearch-ask-accent, #
|
|
7867
|
+
--evosearch-mode-accent: var(--evosearch-ask-accent, #7c3aed);
|
|
7844
7868
|
}
|
|
7845
7869
|
|
|
7846
7870
|
/* The theme font reaches form controls (which don't inherit it by default). */
|
|
@@ -8090,6 +8114,12 @@ const searchWidgetStyles = i$6 `
|
|
|
8090
8114
|
max-height: 0;
|
|
8091
8115
|
opacity: 0;
|
|
8092
8116
|
pointer-events: none;
|
|
8117
|
+
/* The collapsed row is still a .body child, so the sibling rule above gives
|
|
8118
|
+
it a top margin AND another to whatever follows — 2.25em of dead space
|
|
8119
|
+
stacked around a zero-height element, which reads as a broken gap between
|
|
8120
|
+
the nudge and the results heading. Drop its own margin so the only gap
|
|
8121
|
+
left is the single one the next section is entitled to. */
|
|
8122
|
+
margin-top: 0;
|
|
8093
8123
|
}
|
|
8094
8124
|
|
|
8095
8125
|
/* inline-block (not flex) so the typed span and the bold completion stay on
|
|
@@ -8217,9 +8247,6 @@ const searchWidgetStyles = i$6 `
|
|
|
8217
8247
|
|
|
8218
8248
|
/* Segmented Results/AI toggle — replaces the "Search with AI" CTA once an
|
|
8219
8249
|
answer is in memory, letting the shopper flip between the two views. */
|
|
8220
|
-
/* Dual-tone segmented control: two equal halves with a thumb that slides
|
|
8221
|
-
between them (driven by the container's data-mode). The thumb is neutral
|
|
8222
|
-
under Search and gains an amethyst→indigo wash under Ask AI. */
|
|
8223
8250
|
/* Compact segmented control living inside the search bar. Two equal halves
|
|
8224
8251
|
(grid 1fr 1fr) sized to their content so the sliding thumb stays aligned
|
|
8225
8252
|
at 50%; flex:0 0 auto keeps it from stretching in the header row. */
|
|
@@ -8271,13 +8298,17 @@ const searchWidgetStyles = i$6 `
|
|
|
8271
8298
|
.search-widget-container[data-mode='ask'] .view-toggle::before {
|
|
8272
8299
|
transform: translateX(100%);
|
|
8273
8300
|
border-radius: 0 999px 999px 0;
|
|
8274
|
-
background:
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8301
|
+
background: color-mix(
|
|
8302
|
+
in srgb,
|
|
8303
|
+
var(--evosearch-ask-accent, #7c3aed) 10%,
|
|
8304
|
+
#ffffff
|
|
8305
|
+
);
|
|
8306
|
+
box-shadow: 0 1px 4px
|
|
8307
|
+
color-mix(
|
|
8308
|
+
in srgb,
|
|
8309
|
+
var(--evosearch-ask-accent, #7c3aed) 22%,
|
|
8310
|
+
transparent
|
|
8311
|
+
),
|
|
8281
8312
|
inset 0 -0.5px 0.5px rgba(10, 13, 18, 0.08);
|
|
8282
8313
|
}
|
|
8283
8314
|
|
|
@@ -8313,11 +8344,13 @@ const searchWidgetStyles = i$6 `
|
|
|
8313
8344
|
}
|
|
8314
8345
|
|
|
8315
8346
|
.view-toggle-search:focus-visible {
|
|
8316
|
-
box-shadow: 0 0 0 3px
|
|
8347
|
+
box-shadow: 0 0 0 3px
|
|
8348
|
+
color-mix(in srgb, var(--evosearch-accent, #e0590b) 30%, transparent);
|
|
8317
8349
|
}
|
|
8318
8350
|
|
|
8319
8351
|
.view-toggle-ai:focus-visible {
|
|
8320
|
-
box-shadow: 0 0 0 3px
|
|
8352
|
+
box-shadow: 0 0 0 3px
|
|
8353
|
+
color-mix(in srgb, var(--evosearch-ask-accent, #7c3aed) 30%, transparent);
|
|
8321
8354
|
}
|
|
8322
8355
|
|
|
8323
8356
|
.view-toggle-search:hover:not(.is-active) {
|
|
@@ -8325,16 +8358,19 @@ const searchWidgetStyles = i$6 `
|
|
|
8325
8358
|
}
|
|
8326
8359
|
|
|
8327
8360
|
.view-toggle-ai:hover:not(.is-active) {
|
|
8328
|
-
color: #
|
|
8361
|
+
color: var(--evosearch-ask-accent, #7c3aed);
|
|
8329
8362
|
}
|
|
8330
8363
|
|
|
8331
|
-
/*
|
|
8364
|
+
/* Each half carries its own accent when active: Search the brand accent, Ask
|
|
8365
|
+
the --evosearch-ask-accent that state-block, the ask-answer rule and the
|
|
8366
|
+
follow-up chips already use — so one colour means "AI" everywhere rather
|
|
8367
|
+
than the toggle inventing a second one. */
|
|
8332
8368
|
.view-toggle-search.is-active {
|
|
8333
|
-
color: var(--evosearch-accent, #
|
|
8369
|
+
color: var(--evosearch-accent, #e0590b);
|
|
8334
8370
|
}
|
|
8335
8371
|
|
|
8336
8372
|
.view-toggle-ai.is-active {
|
|
8337
|
-
color: #
|
|
8373
|
+
color: var(--evosearch-ask-accent, #7c3aed);
|
|
8338
8374
|
}
|
|
8339
8375
|
|
|
8340
8376
|
.view-toggle-btn bt-lucide-icon,
|
|
@@ -8343,7 +8379,7 @@ const searchWidgetStyles = i$6 `
|
|
|
8343
8379
|
}
|
|
8344
8380
|
|
|
8345
8381
|
.ask-answer {
|
|
8346
|
-
border-left: 3px solid var(--evosearch-mode-accent, #
|
|
8382
|
+
border-left: 3px solid var(--evosearch-mode-accent, #7c3aed);
|
|
8347
8383
|
padding: 0 0 0 0.75em;
|
|
8348
8384
|
line-height: 1.5;
|
|
8349
8385
|
min-height: 1.5em;
|
|
@@ -8399,8 +8435,8 @@ const searchWidgetStyles = i$6 `
|
|
|
8399
8435
|
|
|
8400
8436
|
.ask-starter-chip:hover {
|
|
8401
8437
|
background: #fcfdfb;
|
|
8402
|
-
border-color: var(--evosearch-ask-accent, #
|
|
8403
|
-
color: var(--evosearch-ask-accent, #
|
|
8438
|
+
border-color: var(--evosearch-ask-accent, #7c3aed);
|
|
8439
|
+
color: var(--evosearch-ask-accent, #7c3aed);
|
|
8404
8440
|
}
|
|
8405
8441
|
|
|
8406
8442
|
.ask-starter-chip:active {
|
|
@@ -8498,6 +8534,89 @@ const searchWidgetStyles = i$6 `
|
|
|
8498
8534
|
color: rgba(26, 28, 24, 0.6);
|
|
8499
8535
|
}
|
|
8500
8536
|
|
|
8537
|
+
/* The Ask AI offer: a filled band leading the results column, directly under
|
|
8538
|
+
the search bar. It is the first thing in .body, so it is read before the
|
|
8539
|
+
grid is scanned rather than after it disappoints — and it can never be
|
|
8540
|
+
stranded in whitespace under a short result row. */
|
|
8541
|
+
.ask-nudge {
|
|
8542
|
+
appearance: none;
|
|
8543
|
+
display: flex;
|
|
8544
|
+
align-items: center;
|
|
8545
|
+
gap: 0.5em;
|
|
8546
|
+
width: 100%;
|
|
8547
|
+
padding: 0.625em 0.875em;
|
|
8548
|
+
border: 1px solid
|
|
8549
|
+
color-mix(in srgb, var(--evosearch-ask-accent, #7c3aed) 28%, transparent);
|
|
8550
|
+
border-radius: 0.625em;
|
|
8551
|
+
background: color-mix(
|
|
8552
|
+
in srgb,
|
|
8553
|
+
var(--evosearch-ask-accent, #7c3aed) 6%,
|
|
8554
|
+
#ffffff
|
|
8555
|
+
);
|
|
8556
|
+
color: var(--evosearch-ask-accent, #7c3aed);
|
|
8557
|
+
font-size: 0.8125em;
|
|
8558
|
+
font-weight: 600;
|
|
8559
|
+
text-align: left;
|
|
8560
|
+
cursor: pointer;
|
|
8561
|
+
transition: background 0.15s ease, border-color 0.15s ease;
|
|
8562
|
+
}
|
|
8563
|
+
|
|
8564
|
+
.ask-nudge:hover {
|
|
8565
|
+
background: color-mix(
|
|
8566
|
+
in srgb,
|
|
8567
|
+
var(--evosearch-ask-accent, #7c3aed) 11%,
|
|
8568
|
+
#ffffff
|
|
8569
|
+
);
|
|
8570
|
+
border-color: color-mix(
|
|
8571
|
+
in srgb,
|
|
8572
|
+
var(--evosearch-ask-accent, #7c3aed) 42%,
|
|
8573
|
+
transparent
|
|
8574
|
+
);
|
|
8575
|
+
}
|
|
8576
|
+
|
|
8577
|
+
/* The label takes the slack so the trailing arrow sits hard right, and a
|
|
8578
|
+
merchant-authored label that outruns the band ellipsises instead of
|
|
8579
|
+
wrapping it onto a third line. */
|
|
8580
|
+
.ask-nudge-text {
|
|
8581
|
+
flex: 1 1 auto;
|
|
8582
|
+
min-width: 0;
|
|
8583
|
+
overflow: hidden;
|
|
8584
|
+
text-overflow: ellipsis;
|
|
8585
|
+
white-space: nowrap;
|
|
8586
|
+
}
|
|
8587
|
+
|
|
8588
|
+
@supports (background-clip: text) or (-webkit-background-clip: text) {
|
|
8589
|
+
.ask-nudge-text {
|
|
8590
|
+
background: linear-gradient(
|
|
8591
|
+
100deg,
|
|
8592
|
+
var(--evosearch-ask-accent, #7c3aed),
|
|
8593
|
+
var(--evosearch-ask-accent-alt, #6366f1)
|
|
8594
|
+
);
|
|
8595
|
+
-webkit-background-clip: text;
|
|
8596
|
+
background-clip: text;
|
|
8597
|
+
color: transparent;
|
|
8598
|
+
}
|
|
8599
|
+
}
|
|
8600
|
+
|
|
8601
|
+
/* Amethyst→indigo sweep on the label so the offer reads as generative rather
|
|
8602
|
+
than as a plain link. Guarded by @supports because the fallback for an
|
|
8603
|
+
unsupported background-clip is transparent text, i.e. an invisible CTA —
|
|
8604
|
+
outside the query the solid ask accent from .ask-nudge stands. The sparkle
|
|
8605
|
+
and arrow icons keep currentColor: gradient-filling icon strokes at 14px
|
|
8606
|
+
muddies them, and the solid accent still reads as the same family. */
|
|
8607
|
+
@supports (background-clip: text) or (-webkit-background-clip: text) {
|
|
8608
|
+
.ask-nudge > span {
|
|
8609
|
+
background: linear-gradient(
|
|
8610
|
+
100deg,
|
|
8611
|
+
var(--evosearch-ask-accent, #7c3aed),
|
|
8612
|
+
var(--evosearch-ask-accent-alt, #6366f1)
|
|
8613
|
+
);
|
|
8614
|
+
-webkit-background-clip: text;
|
|
8615
|
+
background-clip: text;
|
|
8616
|
+
color: transparent;
|
|
8617
|
+
}
|
|
8618
|
+
}
|
|
8619
|
+
|
|
8501
8620
|
.ask-followups {
|
|
8502
8621
|
display: flex;
|
|
8503
8622
|
flex-wrap: wrap;
|
|
@@ -8523,6 +8642,7 @@ const searchWidgetStyles = i$6 `
|
|
|
8523
8642
|
.chip:focus-visible,
|
|
8524
8643
|
.chip-remove:focus-visible,
|
|
8525
8644
|
.suggestion:focus-visible,
|
|
8645
|
+
.ask-nudge:focus-visible,
|
|
8526
8646
|
.view-toggle-btn:focus-visible,
|
|
8527
8647
|
.bar-dismiss:focus-visible,
|
|
8528
8648
|
.ask-starter-chip:focus-visible,
|
|
@@ -8539,7 +8659,7 @@ const searchWidgetStyles = i$6 `
|
|
|
8539
8659
|
gap: 0.45em;
|
|
8540
8660
|
border: 0;
|
|
8541
8661
|
border-radius: 999px;
|
|
8542
|
-
background: var(--evosearch-ask-accent, #
|
|
8662
|
+
background: var(--evosearch-ask-accent, #7c3aed);
|
|
8543
8663
|
color: #ffffff;
|
|
8544
8664
|
font-size: 0.8125em;
|
|
8545
8665
|
font-weight: 600;
|
|
@@ -8635,6 +8755,11 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
8635
8755
|
this.suggestions = [];
|
|
8636
8756
|
this.searchProducts = [];
|
|
8637
8757
|
this.searchCollections = [];
|
|
8758
|
+
// Whether the current results had a lexical (BM25) anchor. False means the
|
|
8759
|
+
// grid is a purely semantic guess, which is what `askNudge: 'weak'` targets.
|
|
8760
|
+
// Defaults true so a worker that omits `signals` is treated as a normal
|
|
8761
|
+
// match rather than silently nudging on every query.
|
|
8762
|
+
this.hadLexicalMatch = true;
|
|
8638
8763
|
this.recents = [];
|
|
8639
8764
|
this.loading = false;
|
|
8640
8765
|
this.hasError = false;
|
|
@@ -9217,7 +9342,7 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
9217
9342
|
}));
|
|
9218
9343
|
}
|
|
9219
9344
|
async performAutocomplete(query) {
|
|
9220
|
-
var _a;
|
|
9345
|
+
var _a, _b;
|
|
9221
9346
|
const generation = ++this.requestGeneration;
|
|
9222
9347
|
this.hasError = false;
|
|
9223
9348
|
try {
|
|
@@ -9237,6 +9362,7 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
9237
9362
|
this.suggestions = processed.suggestions;
|
|
9238
9363
|
this.searchProducts = processed.products;
|
|
9239
9364
|
this.searchCollections = (_a = response.collections) !== null && _a !== void 0 ? _a : [];
|
|
9365
|
+
this.hadLexicalMatch = ((_b = response.signals) === null || _b === void 0 ? void 0 : _b.bm25) !== false;
|
|
9240
9366
|
this.debouncedQuery = query;
|
|
9241
9367
|
this.loading = false;
|
|
9242
9368
|
if (this.finalizeOnRender) {
|
|
@@ -9376,6 +9502,7 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
9376
9502
|
this.showSuggestions = false;
|
|
9377
9503
|
this.searchProducts = [];
|
|
9378
9504
|
this.searchCollections = [];
|
|
9505
|
+
this.hadLexicalMatch = true;
|
|
9379
9506
|
this.activeSuggestionIndex = -1;
|
|
9380
9507
|
// Ask mode renders from a separate state set, so clearing the query alone
|
|
9381
9508
|
// leaves the previous answer/evidence on screen. Reset it too and abort any
|
|
@@ -9477,6 +9604,7 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
9477
9604
|
suggestions: this.suggestions,
|
|
9478
9605
|
products: this.searchProducts,
|
|
9479
9606
|
collections: this.searchCollections,
|
|
9607
|
+
hadLexicalMatch: this.hadLexicalMatch,
|
|
9480
9608
|
askAnswer: this.askAnswer,
|
|
9481
9609
|
askEvidence: this.askEvidence,
|
|
9482
9610
|
askFollowUps: this.askFollowUps,
|
|
@@ -9497,6 +9625,9 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
9497
9625
|
this.suggestions = snap.suggestions;
|
|
9498
9626
|
this.searchProducts = snap.products;
|
|
9499
9627
|
this.searchCollections = snap.collections;
|
|
9628
|
+
// Restored too, or a purely semantic result set would look like a lexical
|
|
9629
|
+
// match after a reopen and wrongly suppress the `weak` nudge.
|
|
9630
|
+
this.hadLexicalMatch = snap.hadLexicalMatch;
|
|
9500
9631
|
// The restored query was already a real search in its first life; don't let
|
|
9501
9632
|
// the settle/finalize path re-count it as a new one.
|
|
9502
9633
|
this.lastFinalizedQuery = snap.query.trim().toLowerCase();
|
|
@@ -9542,7 +9673,11 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
9542
9673
|
${this.renderMain()}
|
|
9543
9674
|
`;
|
|
9544
9675
|
}
|
|
9676
|
+
// The nudge leads the column — directly under the search bar, above the
|
|
9677
|
+
// typeahead chips and the grid — so it is seen before the results are
|
|
9678
|
+
// scanned rather than after they disappoint.
|
|
9545
9679
|
return b `
|
|
9680
|
+
${this.shouldShowAskNudge() ? this.renderAskNudge() : A}
|
|
9546
9681
|
${this.cachedRailSections.map((section) => this.renderColumnSection(section))}
|
|
9547
9682
|
${this.renderMain()}
|
|
9548
9683
|
`;
|
|
@@ -9933,25 +10068,12 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
9933
10068
|
height="14"
|
|
9934
10069
|
viewBox="0 0 24 24"
|
|
9935
10070
|
fill="none"
|
|
9936
|
-
stroke
|
|
10071
|
+
stroke="currentColor"
|
|
9937
10072
|
stroke-width="2.2"
|
|
9938
10073
|
stroke-linecap="round"
|
|
9939
10074
|
stroke-linejoin="round"
|
|
9940
10075
|
aria-hidden="true"
|
|
9941
10076
|
>
|
|
9942
|
-
<defs>
|
|
9943
|
-
<linearGradient
|
|
9944
|
-
id="aiGrad"
|
|
9945
|
-
x1="0"
|
|
9946
|
-
y1="0"
|
|
9947
|
-
x2="24"
|
|
9948
|
-
y2="24"
|
|
9949
|
-
gradientUnits="userSpaceOnUse"
|
|
9950
|
-
>
|
|
9951
|
-
<stop offset="0" stop-color="#9333EA"></stop>
|
|
9952
|
-
<stop offset="1" stop-color="#6366F1"></stop>
|
|
9953
|
-
</linearGradient>
|
|
9954
|
-
</defs>
|
|
9955
10077
|
<path
|
|
9956
10078
|
d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"
|
|
9957
10079
|
></path>
|
|
@@ -10009,6 +10131,80 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
10009
10131
|
</div>
|
|
10010
10132
|
`;
|
|
10011
10133
|
}
|
|
10134
|
+
// Whether the Ask AI offer belongs on screen right now. Lifted out of the
|
|
10135
|
+
// render so `renderColumn` can place the band above everything in the body
|
|
10136
|
+
// while still keeping the "real search results exist" conditions that only
|
|
10137
|
+
// renderMain used to know about.
|
|
10138
|
+
shouldShowAskNudge() {
|
|
10139
|
+
var _a, _b;
|
|
10140
|
+
if (!this.askActive || this.mode === 'ask') {
|
|
10141
|
+
return false;
|
|
10142
|
+
}
|
|
10143
|
+
const mode = (_b = (_a = this.uiPreferences) === null || _a === void 0 ? void 0 : _a.askNudge) !== null && _b !== void 0 ? _b : DEFAULT_ASK_NUDGE_MODE;
|
|
10144
|
+
if (mode === 'never' || (mode === 'weak' && this.hadLexicalMatch)) {
|
|
10145
|
+
return false;
|
|
10146
|
+
}
|
|
10147
|
+
// Never on the handpicked/empty state (no query to hand over), mid-load
|
|
10148
|
+
// (nothing to be unhappy with yet), on an error, or on the no-match state —
|
|
10149
|
+
// that one carries its own recovery CTA already. Collections count as
|
|
10150
|
+
// results: the same pair renderMain's `noMatches` tests, so a
|
|
10151
|
+
// collections-only match keeps the nudge rather than reading as empty.
|
|
10152
|
+
return (this.debouncedQuery.trim().length > 0 &&
|
|
10153
|
+
!this.loading &&
|
|
10154
|
+
!this.hasError &&
|
|
10155
|
+
(this.searchProducts.length > 0 || this.searchCollections.length > 0));
|
|
10156
|
+
}
|
|
10157
|
+
// Substitute the merchant's `{query}` token. Returned as separate strings so
|
|
10158
|
+
// Lit renders them as text bindings and escapes the shopper's query; never
|
|
10159
|
+
// build markup from this.
|
|
10160
|
+
askNudgeLabelParts(query) {
|
|
10161
|
+
var _a, _b;
|
|
10162
|
+
const label = ((_b = (_a = this.uiPreferences) === null || _a === void 0 ? void 0 : _a.askNudgeLabel) === null || _b === void 0 ? void 0 : _b.trim()) || DEFAULT_ASK_NUDGE_LABEL;
|
|
10163
|
+
return label.split(ASK_NUDGE_QUERY_TOKEN).join(`"${query}"`);
|
|
10164
|
+
}
|
|
10165
|
+
// Offer Ask AI above the results the shopper is about to scan. The tab pill
|
|
10166
|
+
// is too quiet to read as an offer, and the zero-result CTA never fires for
|
|
10167
|
+
// these queries by definition — this is the only place the offer lands while
|
|
10168
|
+
// the shopper still has the query in their head.
|
|
10169
|
+
renderAskNudge() {
|
|
10170
|
+
const query = this.debouncedQuery.trim();
|
|
10171
|
+
const label = this.askNudgeLabelParts(query);
|
|
10172
|
+
return b `
|
|
10173
|
+
<button
|
|
10174
|
+
type="button"
|
|
10175
|
+
class="ask-nudge"
|
|
10176
|
+
@click=${() => this.handleAskNudgeClick(query)}
|
|
10177
|
+
>
|
|
10178
|
+
<bt-lucide-icon
|
|
10179
|
+
name="sparkles"
|
|
10180
|
+
width="14"
|
|
10181
|
+
height="14"
|
|
10182
|
+
stroke="currentColor"
|
|
10183
|
+
stroke-width="1.5"
|
|
10184
|
+
></bt-lucide-icon>
|
|
10185
|
+
<span class="ask-nudge-text">${label}</span>
|
|
10186
|
+
<bt-lucide-icon
|
|
10187
|
+
name="arrowRight"
|
|
10188
|
+
width="14"
|
|
10189
|
+
height="14"
|
|
10190
|
+
stroke="currentColor"
|
|
10191
|
+
stroke-width="1.5"
|
|
10192
|
+
></bt-lucide-icon>
|
|
10193
|
+
</button>
|
|
10194
|
+
`;
|
|
10195
|
+
}
|
|
10196
|
+
// Same destination as the zero-result CTA: flip to Ask and run the query the
|
|
10197
|
+
// shopper already typed, so the words carry over verbatim.
|
|
10198
|
+
handleAskNudgeClick(query) {
|
|
10199
|
+
this.mode = 'ask';
|
|
10200
|
+
setStoredSearchMode('ask');
|
|
10201
|
+
// Reuse a held answer for this exact query, like the view toggle does —
|
|
10202
|
+
// re-submitting would open a second billed generation for the same words.
|
|
10203
|
+
const hasAnswer = this.askLoading || this.askAnswer.length > 0 || this.askEvidence !== null;
|
|
10204
|
+
if (!hasAnswer || this.lastAskedQuery !== query) {
|
|
10205
|
+
this.handleAskSubmit(query);
|
|
10206
|
+
}
|
|
10207
|
+
}
|
|
10012
10208
|
renderMain() {
|
|
10013
10209
|
var _a, _b, _c;
|
|
10014
10210
|
const askInitial = this.mode === 'ask' &&
|
|
@@ -10224,6 +10420,10 @@ __decorate([
|
|
|
10224
10420
|
r$1(),
|
|
10225
10421
|
__metadata("design:type", Array)
|
|
10226
10422
|
], SearchWidget.prototype, "searchCollections", void 0);
|
|
10423
|
+
__decorate([
|
|
10424
|
+
r$1(),
|
|
10425
|
+
__metadata("design:type", Object)
|
|
10426
|
+
], SearchWidget.prototype, "hadLexicalMatch", void 0);
|
|
10227
10427
|
__decorate([
|
|
10228
10428
|
r$1(),
|
|
10229
10429
|
__metadata("design:type", Array)
|
|
@@ -10283,7 +10483,15 @@ const evoSearchStyles = i$6 `
|
|
|
10283
10483
|
--evosearch-primary: #3a3d36;
|
|
10284
10484
|
--evosearch-secondary: #6b6f64;
|
|
10285
10485
|
--evosearch-accent: #e0590b;
|
|
10286
|
-
|
|
10486
|
+
/* Violet, not blue. The previous #2563eb read as a generic link colour, so
|
|
10487
|
+
every AI affordance looked like plain navigation. Violet is distinct from
|
|
10488
|
+
the brand accent AND from ordinary links, so "this is the AI" is legible
|
|
10489
|
+
without a second signal. Drives the mode toggle, the results nudge, the
|
|
10490
|
+
ask-answer rule, the follow-up chips and the state-block CTA. */
|
|
10491
|
+
--evosearch-ask-accent: #7c3aed;
|
|
10492
|
+
/* Paired end-stop for the gradient treatments (nudge label, toggle thumb).
|
|
10493
|
+
Same amethyst→indigo sweep the toggle icon used before it was unified. */
|
|
10494
|
+
--evosearch-ask-accent-alt: #6366f1;
|
|
10287
10495
|
--evosearch-mode-accent: var(--evosearch-accent);
|
|
10288
10496
|
--evosearch-white: #ffffff;
|
|
10289
10497
|
--evosearch-border: #d6d8cf;
|
|
@@ -10359,6 +10567,25 @@ class EvoSearch extends ElementWithStylesheet {
|
|
|
10359
10567
|
this.handpickedProducts = [];
|
|
10360
10568
|
this.featuredCollections = [];
|
|
10361
10569
|
this.askAiEnabled = false;
|
|
10570
|
+
// Host-page scroll locks (body-scroll-lock and friends) cancel every
|
|
10571
|
+
// document-level touchmove they didn't whitelist, which kills touch
|
|
10572
|
+
// scrolling inside the widget on iOS. Keep widget touches out of their
|
|
10573
|
+
// reach; never preventDefault, or we'd kill the scroll ourselves.
|
|
10574
|
+
// Known gap: a lock registered with { capture: true } runs before the
|
|
10575
|
+
// event reaches this host, so stopPropagation can't shield against it.
|
|
10576
|
+
this.touchmoveListener = (event) => event.stopPropagation();
|
|
10577
|
+
}
|
|
10578
|
+
connectedCallback() {
|
|
10579
|
+
super.connectedCallback();
|
|
10580
|
+
// Passive: this handler only stops propagation, and a blocking touchmove
|
|
10581
|
+
// listener would keep the widget's own scrolling off the compositor.
|
|
10582
|
+
this.addEventListener('touchmove', this.touchmoveListener, {
|
|
10583
|
+
passive: true,
|
|
10584
|
+
});
|
|
10585
|
+
}
|
|
10586
|
+
disconnectedCallback() {
|
|
10587
|
+
this.removeEventListener('touchmove', this.touchmoveListener);
|
|
10588
|
+
super.disconnectedCallback();
|
|
10362
10589
|
}
|
|
10363
10590
|
updated(changedProperties) {
|
|
10364
10591
|
var _a;
|
|
@@ -10558,7 +10785,7 @@ const tryFetchTrending = (target) => {
|
|
|
10558
10785
|
// Runtime Ask AI config. Unlike trending (fetched once per session), this is
|
|
10559
10786
|
// fetched on every open so a merchant's dashboard change takes effect without
|
|
10560
10787
|
// a reload — there is no session-fetched latch, only an in-flight guard to
|
|
10561
|
-
// dedupe concurrent opens. `askStarters`/`askAiEnabled` apply live.
|
|
10788
|
+
// dedupe concurrent opens. `askStarters`/`askAiEnabled`/`askNudge`(+label) apply live.
|
|
10562
10789
|
const tryFetchConfig = (target) => {
|
|
10563
10790
|
if (!target.api || configController) {
|
|
10564
10791
|
return;
|
|
@@ -10576,6 +10803,13 @@ const tryFetchConfig = (target) => {
|
|
|
10576
10803
|
...(data.askStarters !== undefined
|
|
10577
10804
|
? { askStarters: data.askStarters }
|
|
10578
10805
|
: {}),
|
|
10806
|
+
// Only overlay what the worker actually sent: during a deploy window
|
|
10807
|
+
// a new SDK can hit an old `/config` that omits these, and writing
|
|
10808
|
+
// undefined would drop the manifest-seeded value.
|
|
10809
|
+
...(data.askNudge !== undefined ? { askNudge: data.askNudge } : {}),
|
|
10810
|
+
...(data.askNudgeLabel !== undefined
|
|
10811
|
+
? { askNudgeLabel: data.askNudgeLabel }
|
|
10812
|
+
: {}),
|
|
10579
10813
|
};
|
|
10580
10814
|
}
|
|
10581
10815
|
})
|