wineater-bot 4.22.0 → 4.23.0
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/dist/wineater-chatbot.mjs +13 -13
- package/dist/wineater-chatbot.umd.js +13 -13
- package/package.json +1 -1
- package/src/App.svelte +18 -4
- package/src/services/api/api.ts +16 -4
package/package.json
CHANGED
package/src/App.svelte
CHANGED
|
@@ -31,7 +31,14 @@
|
|
|
31
31
|
preconnectFontHosts();
|
|
32
32
|
|
|
33
33
|
const INSTANT_CACHE_MAX_AGE_MS = 6 * 60 * 60 * 1000;
|
|
34
|
-
|
|
34
|
+
// A personal start grid follows the taste profile, which changes with every
|
|
35
|
+
// click — keep it for 1 h only; the general grid stays cached for 6 h.
|
|
36
|
+
const PERSONAL_INSTANT_CACHE_MAX_AGE_MS = 60 * 60 * 1000;
|
|
37
|
+
// The cache key carries the profile version from the last response
|
|
38
|
+
// (X-Wineater-Profile), remembered separately: a new profile → new key.
|
|
39
|
+
const instantProfileKey = `${cacheScope}:instant-profile`;
|
|
40
|
+
const instantCacheKey = (profileHash: string | null) =>
|
|
41
|
+
`${cacheScope}:instant${profileHash ? `:${profileHash}` : ''}`;
|
|
35
42
|
|
|
36
43
|
const widgetType = window?.wineaterData?.WIDGET_TYPE || 'store';
|
|
37
44
|
const countryCode = window?.wineaterData?.COUNTRY_CODE || null;
|
|
@@ -132,7 +139,11 @@
|
|
|
132
139
|
// response only refreshes the cache for the next visit, so the grid the
|
|
133
140
|
// visitor is already looking at never swaps under their cursor.
|
|
134
141
|
async function loadInstantRecommendations(configReady: Promise<void>) {
|
|
135
|
-
const
|
|
142
|
+
const lastProfile = readCache<string>(instantProfileKey, 30 * 24 * 60 * 60 * 1000);
|
|
143
|
+
const cached = readCache<Wine[]>(
|
|
144
|
+
instantCacheKey(lastProfile),
|
|
145
|
+
lastProfile ? PERSONAL_INSTANT_CACHE_MAX_AGE_MS : INSTANT_CACHE_MAX_AGE_MS
|
|
146
|
+
);
|
|
136
147
|
const hasCached = !!cached?.length;
|
|
137
148
|
if (hasCached) {
|
|
138
149
|
results = cached;
|
|
@@ -140,8 +151,11 @@
|
|
|
140
151
|
isLoading = true;
|
|
141
152
|
}
|
|
142
153
|
try {
|
|
143
|
-
const fresh = await wineaterApi.getInstantRecommendations();
|
|
144
|
-
if (fresh.length > 0)
|
|
154
|
+
const { wines: fresh, profileHash } = await wineaterApi.getInstantRecommendations();
|
|
155
|
+
if (fresh.length > 0) {
|
|
156
|
+
writeCache(instantCacheKey(profileHash), fresh);
|
|
157
|
+
writeCache(instantProfileKey, profileHash);
|
|
158
|
+
}
|
|
145
159
|
if (!hasCached && !userActed) results = fresh;
|
|
146
160
|
} catch (error) {
|
|
147
161
|
console.error('[wineater] instant recommendations error:', error);
|
package/src/services/api/api.ts
CHANGED
|
@@ -68,6 +68,12 @@ class WineaterApi {
|
|
|
68
68
|
// One place for every backend call: JSON body, auth, anonymous uid header,
|
|
69
69
|
// uniform error. `keepalive` lets the last feedback batch out-live the page.
|
|
70
70
|
async #request<T>(endpoint: string, body?: unknown, options: { keepalive?: boolean; method?: string } = {}): Promise<T> {
|
|
71
|
+
return (await this.#send<T>(endpoint, body, options)).data;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Same as #request, but also hands back the response headers (instant/v2
|
|
75
|
+
// returns the taste-profile version in X-Wineater-Profile).
|
|
76
|
+
async #send<T>(endpoint: string, body?: unknown, options: { keepalive?: boolean; method?: string } = {}): Promise<{ data: T; headers: Headers }> {
|
|
71
77
|
const init = (): RequestInit => ({
|
|
72
78
|
method: options.method ?? 'POST',
|
|
73
79
|
headers: this.#headers(),
|
|
@@ -91,16 +97,22 @@ class WineaterApi {
|
|
|
91
97
|
if (!response.ok) {
|
|
92
98
|
throw new Error(`API call failed with status ${response.status}: ${data?.message || 'No additional error information'}`);
|
|
93
99
|
}
|
|
94
|
-
return data as T;
|
|
100
|
+
return { data: data as T, headers: response.headers };
|
|
95
101
|
}
|
|
96
102
|
|
|
97
|
-
|
|
103
|
+
// `profileHash` is set when the backend served the visitor's personal start
|
|
104
|
+
// grid (anonymous taste profile); null for the general grid. The widget keys
|
|
105
|
+
// its instant cache by it, so a recomputed profile is picked up next visit.
|
|
106
|
+
async getInstantRecommendations(): Promise<{ wines: Wine[]; profileHash: string | null }> {
|
|
98
107
|
try {
|
|
99
108
|
const endpoint = this.#apiVersion === '2'
|
|
100
109
|
? 'dish/recommendations/instant/v2'
|
|
101
110
|
: 'dish/recommendations/instant';
|
|
102
|
-
const data = await this.#
|
|
103
|
-
return
|
|
111
|
+
const { data, headers } = await this.#send<any[]>(endpoint, {});
|
|
112
|
+
return {
|
|
113
|
+
wines: data.filter(Boolean).map(deserializeInstantRecommendation),
|
|
114
|
+
profileHash: headers.get('X-Wineater-Profile') || null,
|
|
115
|
+
};
|
|
104
116
|
} catch (error) {
|
|
105
117
|
throw new Error(`Error in getInstantRecommendations: ${error.message}`);
|
|
106
118
|
}
|