wineater-bot 3.9.4 → 4.0.0-beta.3

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.
Files changed (41) hide show
  1. package/.prettierignore +9 -0
  2. package/.prettierrc +16 -0
  3. package/README.md +86 -0
  4. package/dist/wineater-chatbot.css +1 -0
  5. package/dist/wineater-chatbot.mjs +6493 -5129
  6. package/dist/wineater-chatbot.umd.js +40 -985
  7. package/docs/SVELTE.md +146 -0
  8. package/e2e/demo.test.ts +6 -0
  9. package/eslint.config.js +41 -0
  10. package/index.html +11 -3
  11. package/package.json +30 -5
  12. package/playwright.config.ts +9 -0
  13. package/pnpm-workspace.yaml +2 -0
  14. package/src/App.svelte +394 -0
  15. package/src/App.svelte.test.ts +13 -0
  16. package/src/components/Footer.svelte +131 -0
  17. package/src/components/Header.svelte +92 -0
  18. package/src/components/Loader.svelte +62 -0
  19. package/src/components/Search.svelte +188 -0
  20. package/src/components/WinePopup.svelte +192 -0
  21. package/src/components/WineTile.svelte +331 -0
  22. package/src/demo.spec.ts +13 -0
  23. package/src/index.js +4 -36
  24. package/src/services/analytics/analytics.ts +103 -0
  25. package/src/{js/widget/widget-events.js → services/analytics/constants.ts} +0 -2
  26. package/src/services/api/api.ts +112 -0
  27. package/src/services/api/deserialize.ts +102 -0
  28. package/src/services/i18n/i18n.ts +134 -0
  29. package/src/services/i18n/translations.json +521 -0
  30. package/src/services/store/app-store.svelte.ts +84 -0
  31. package/tsconfig.json +23 -0
  32. package/vite.config.js +69 -19
  33. package/vitest-setup-client.ts +2 -0
  34. package/src/js/analytics.js +0 -117
  35. package/src/js/api.js +0 -138
  36. package/src/js/i18n.js +0 -703
  37. package/src/js/widget/widget-language.js +0 -107
  38. package/src/js/widget/widget-search.js +0 -68
  39. package/src/js/widget/widget-styles.js +0 -855
  40. package/src/js/widget/widget-ui.js +0 -186
  41. package/src/js/widget.js +0 -241
@@ -0,0 +1,84 @@
1
+ import { wineaterApi } from "../api/api";
2
+ import type { CurrencyCode, LanguageCode } from "../i18n/i18n";
3
+
4
+ export type StoreConfig = {
5
+ id: string;
6
+ lang: LanguageCode;
7
+ available_languages: LanguageCode[];
8
+ open_ai_key: string | null;
9
+ currency: CurrencyCode;
10
+ [key: string]: any;
11
+ };
12
+
13
+ export type AppState = {
14
+ storeConfig: StoreConfig | null;
15
+ currentLanguage: LanguageCode;
16
+ isConfigLoaded: boolean;
17
+ };
18
+
19
+ let state = $state<AppState>({
20
+ storeConfig: null,
21
+ currentLanguage: 'en',
22
+ isConfigLoaded: false,
23
+ });
24
+
25
+ export const appStore = {
26
+ getLanguageFromURL(): LanguageCode | null {
27
+ const params = new URLSearchParams(window.location.search);
28
+ const lang = params.get('lang');
29
+ return lang as LanguageCode | null;
30
+ },
31
+ // app language -- the one used in the UI
32
+ getAppLanguage(): LanguageCode {
33
+ return state?.currentLanguage ?? 'en';
34
+ },
35
+ // store default language from API
36
+ getStoreLanguage(): LanguageCode | null {
37
+ return state.storeConfig?.lang ?? null;
38
+ },
39
+ getApiKey(): string | null {
40
+ return state.storeConfig?.open_ai_key ?? null;
41
+ },
42
+ getStoreId(): string | null {
43
+ return state.storeConfig?.id ?? null;
44
+ },
45
+ setStoreLanguage(lang: LanguageCode) {
46
+ if (state.storeConfig && state.storeConfig.available_languages.includes(lang)) {
47
+ state.currentLanguage = lang;
48
+ const params = new URLSearchParams(window.location.search);
49
+ params.set('lang', lang);
50
+ const newUrl = `${window.location.pathname}?${params.toString()}`;
51
+ window.history.replaceState({}, '', newUrl);
52
+ } else {
53
+ throw new Error(`Language ${lang} is not available in store configuration.`);
54
+ }
55
+ },
56
+ getSupportedLanguages(): LanguageCode[] {
57
+ return state.storeConfig?.available_languages ?? ['en'];
58
+ },
59
+ getCurrency(): CurrencyCode {
60
+ return state.storeConfig?.currency ?? 'USD';
61
+ },
62
+ async initializeStore() {
63
+ try {
64
+ const config = await wineaterApi.getStoreConfig();
65
+ state.storeConfig = config;
66
+ state.currentLanguage = config.lang as LanguageCode;
67
+ const urlLang = this.getLanguageFromURL();
68
+ if (urlLang && config.available_languages.includes(urlLang)) {
69
+ this.setStoreLanguage(urlLang);
70
+ } else {
71
+ // fallback to store default language
72
+ this.setStoreLanguage(config.lang as LanguageCode);
73
+ }
74
+
75
+ state.isConfigLoaded = true;
76
+ } catch (error) {
77
+ console.error('Failed to initialize store:', error);
78
+ state.isConfigLoaded = false;
79
+ }
80
+ },
81
+ isInitialized(): boolean {
82
+ return state.isConfigLoaded;
83
+ },
84
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "ESNext",
4
+ "target": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "allowImportingTsExtensions": true,
7
+ "allowArbitraryExtensions": true,
8
+ "resolveJsonModule": true,
9
+ "allowSyntheticDefaultImports": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "strict": false,
13
+ "forceConsistentCasingInFileNames": true,
14
+ "isolatedModules": true,
15
+ "noEmit": true
16
+ },
17
+ "include": [
18
+ "src/**/*"
19
+ ],
20
+ "exclude": [
21
+ "node_modules"
22
+ ]
23
+ }
package/vite.config.js CHANGED
@@ -1,21 +1,71 @@
1
- import { defineConfig } from 'vite';
1
+ import devtoolsJson from 'vite-plugin-devtools-json';
2
+ import { defineConfig } from 'vitest/config';
3
+ import { svelte } from '@sveltejs/vite-plugin-svelte';
2
4
 
3
5
  export default defineConfig({
4
- server: {
5
- port: 3001 // Customize the port if necessary
6
- },
7
- build: {
8
- lib: {
9
- entry: 'src/index.js',
10
- name: 'WineaterChatbot',
11
- fileName: 'wineater-chatbot'
12
- },
13
- // Make sure to externalize dependencies that shouldn't be bundled into your library
14
- rollupOptions: {
15
- external: [],
16
- output: {
17
- // Provide globals here if needed
18
- }
19
- }
20
- }
21
- });
6
+ server: {
7
+ port: 3001 // Customize the port if necessary
8
+ },
9
+ build: {
10
+ lib: {
11
+ entry: 'src/index.js',
12
+ name: 'WineaterChatbot',
13
+ fileName: () => 'wineater-chatbot' // base name only
14
+ },
15
+ rollupOptions: {
16
+ output: [
17
+ {
18
+ format: 'es',
19
+ entryFileNames: 'wineater-chatbot.mjs',
20
+ assetFileNames: (assetInfo) => {
21
+ if (assetInfo.name && assetInfo.name.endsWith('.css')) {
22
+ return 'wineater-chatbot.css'; // your desired name
23
+ }
24
+ return assetInfo.name || '[name].[ext]';
25
+ },
26
+ },
27
+ {
28
+ format: 'umd',
29
+ name: 'WineaterChatbot',
30
+ entryFileNames: 'wineater-chatbot.umd.js',
31
+ assetFileNames: (assetInfo) => {
32
+ if (assetInfo.name && assetInfo.name.endsWith('.css')) {
33
+ return 'wineater-chatbot.css';
34
+ }
35
+ return assetInfo.name || '[name].[ext]';
36
+ },
37
+ },
38
+ ],
39
+ },
40
+ },
41
+ test: {
42
+ expect: { requireAssertions: true },
43
+ projects: [
44
+ {
45
+ extends: './vite.config.js',
46
+ test: {
47
+ name: 'client',
48
+ environment: 'browser',
49
+ browser: {
50
+ enabled: true,
51
+ provider: 'playwright',
52
+ instances: [{ browser: 'chromium' }]
53
+ },
54
+ include: ['src/**/*.svelte.{test,spec}.{js,ts}'],
55
+ exclude: ['src/lib/server/**'],
56
+ setupFiles: ['./vitest-setup-client.ts']
57
+ }
58
+ },
59
+ {
60
+ extends: './vite.config.js',
61
+ test: {
62
+ name: 'server',
63
+ environment: 'node',
64
+ include: ['src/**/*.{test,spec}.{js,ts}'],
65
+ exclude: ['src/**/*.svelte.{test,spec}.{js,ts}']
66
+ }
67
+ }
68
+ ]
69
+ },
70
+ plugins: [devtoolsJson(), svelte()]
71
+ });
@@ -0,0 +1,2 @@
1
+ /// <reference types="@vitest/browser/matchers" />
2
+ /// <reference types="@vitest/browser/providers/playwright" />
@@ -1,117 +0,0 @@
1
- // analytics.js
2
-
3
- // Function to generate a UUID
4
- function generateUUID() {
5
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
6
- const r = Math.random() * 16 | 0,
7
- v = c === 'x' ? r : (r & 0x3 | 0x8);
8
- return v.toString(16);
9
- });
10
- }
11
-
12
- // Function to set a cookie
13
- function setCookie(name, value, days) {
14
- console.log(name, days);
15
- let expires = "";
16
- if (days) {
17
- const date = new Date();
18
- date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
19
- expires = "; expires=" + date.toUTCString();
20
- }
21
- document.cookie = name + "=" + (value || "") + expires + "; path=/; secure; samesite=strict";
22
- }
23
-
24
- // Function to get a cookie by name
25
- function getCookie(name) {
26
- const nameEQ = name + "=";
27
- const ca = document.cookie.split(';');
28
- for (let i = 0; i < ca.length; i++) {
29
- let c = ca[i];
30
- while (c.charAt(0) === ' ') c = c.substring(1, c.length);
31
- if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
32
- }
33
- return null;
34
- }
35
-
36
- function deleteCookie(name) {
37
- document.cookie = name + '=; Max-Age=-99999999;';
38
- }
39
-
40
- // Function to set a unique user identifier cookie
41
- function setUniqueUserCookie() {
42
- const cookieName = 'uniqueUserID';
43
- let userId = getCookie(cookieName);
44
-
45
- if (!userId) {
46
- userId = generateUUID();
47
- setCookie(cookieName, userId, 365); // Set cookie to expire in 1 year
48
- }
49
- return userId;
50
- }
51
-
52
- function trackWidgetEvent(event_type, store_id, data = {}, meta = {}) {
53
- const endpoint = 'https://admin-api.wineater.com/api/widget-events';
54
-
55
- // Ensure we have a unique user ID
56
- let user_id = getCookie('uniqueUserID');
57
- if (!user_id) {
58
- user_id = setUniqueUserCookie();
59
- }
60
-
61
- // Get device info
62
- const device_info = {
63
- user_agent: navigator.userAgent,
64
- screen_width: window.screen.width,
65
- screen_height: window.screen.height,
66
- language: navigator.language,
67
- platform: navigator.platform
68
- };
69
-
70
- // Get current timestamp
71
- const event_time = new Date().toISOString();
72
-
73
- // Generate session ID if not exists
74
- let session_id = getCookie('sessionID');
75
- if (!session_id) {
76
- session_id = generateUUID();
77
- setCookie('sessionID', session_id, 1); // Session cookie expires in 1 day
78
- }
79
-
80
- const payload = {
81
- event_type,
82
- store_id,
83
- session_id,
84
- event_time,
85
- data,
86
- meta: {
87
- ...meta,
88
- user_id
89
- },
90
- device_info,
91
- region: navigator.language.split('-')[1] || null,
92
- language: navigator.language.split('-')[0] || 'en'
93
- };
94
-
95
- return fetch(endpoint, {
96
- method: 'POST',
97
- headers: {
98
- 'Content-Type': 'application/json'
99
- },
100
- body: JSON.stringify(payload)
101
- })
102
- .then(response => {
103
- if (!response.ok) {
104
- return response.json().then(err => {
105
- throw new Error(err.message || 'Failed to track widget event');
106
- });
107
- }
108
- return response.json();
109
- })
110
- .catch(error => {
111
- console.error('Widget event tracking error:', error);
112
- throw error;
113
- });
114
- }
115
-
116
- export { setCookie, getCookie, setUniqueUserCookie, deleteCookie, trackWidgetEvent };
117
-
package/src/js/api.js DELETED
@@ -1,138 +0,0 @@
1
- // api.js
2
-
3
- const stores = [];
4
- const DEMO_TOKEN='HeUw6IXuhvz6tRuXdPEGEbj3SGQ3Q2YtEc1Z8jd41Kg9LtyPb4Epu0to31SYIadG';
5
-
6
- export class Api {
7
- constructor(apiEndpoint) {
8
- // Allow users of the library to specify their own endpoint if needed
9
- // this.baseURL = 'http://localhost:3000/';
10
- this.baseURL = 'https://wineater-backend.vercel.app/';
11
- this.store = stores.find(value => window.location.href.includes(value));
12
- this.token = window?.wineaterData?.CLIENT_TOKEN || DEMO_TOKEN;
13
- }
14
-
15
- async getStoreConfig(storeName) {
16
- console.log(storeName);
17
- try {
18
- const response = await fetch(`${this.baseURL}stores/store`, {
19
- method: 'POST',
20
- headers: {
21
- 'Content-Type': 'application/json',
22
- 'Authorization': `Bearer ${this.token}`
23
- },
24
- body: JSON.stringify({ store: storeName })
25
- });
26
- console.log(response);
27
- if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
28
- return await response.json();
29
- } catch (error) {
30
- console.error('Error fetching store configuration:', error);
31
- throw error;
32
- }
33
- }
34
-
35
- async getWineRecommendations(text) {
36
- console.log(window.location.href);
37
- try {
38
- const response = await fetch(`${this.baseURL}dish/recommendations`, {
39
- method: 'POST',
40
- headers: {
41
- 'Content-Type': 'application/json',
42
- 'Authorization': `Bearer ${this.token}`
43
- },
44
- body: JSON.stringify({ text: text, store: this.store ? (this.store.toString().charAt(0).toUpperCase() + this.store.slice(1)) : undefined})
45
- });
46
-
47
- const data = await response.json();
48
-
49
- if (!response.ok) {
50
- // Providing more detailed error information
51
- throw new Error(`API call failed with status ${response.status}: ${data.message || 'No additional error information'}`);
52
- }
53
-
54
- return data;
55
- } catch (error) {
56
- // Log and rethrow to allow consumer of the API to handle or display the error appropriately
57
- console.error('Fetching wine recommendations failed:', error.message);
58
- throw new Error(`Error in getWineRecommendations: ${error.message}`);
59
- }
60
- }
61
-
62
- async getWineRecommendationsSearch(text) {
63
- console.log(window.location.href);
64
- try {
65
- const response = await fetch(`${this.baseURL}dish/recommendations/search`, {
66
- method: 'POST',
67
- headers: {
68
- 'Content-Type': 'application/json',
69
- 'Authorization': `Bearer ${this.token}`
70
- },
71
- body: JSON.stringify({ text: text})
72
- });
73
-
74
- const data = await response.json();
75
-
76
- if (!response.ok) {
77
- // Providing more detailed error information
78
- throw new Error(`API call failed with status ${response.status}: ${data.message || 'No additional error information'}`);
79
- }
80
-
81
- return data;
82
- } catch (error) {
83
- // Log and rethrow to allow consumer of the API to handle or display the error appropriately
84
- console.error('Fetching wine recommendations failed:', error.message);
85
- throw new Error(`Error in getWineRecommendations: ${error.message}`);
86
- }
87
- }
88
-
89
- async getPossibleDishesWithMeta(metaTitle, metaDescription) {
90
- try {
91
- const response = await fetch(`${this.baseURL}dish/possible_dishes`, {
92
- method: 'POST',
93
- headers: {
94
- 'Authorization': `Bearer ${this.token}`,
95
- 'Content-Type': 'application/json'
96
- },
97
- body: JSON.stringify({
98
- title: metaTitle,
99
- description: metaDescription
100
- })
101
- });
102
- const data = await response.json();
103
- console.log(data);
104
- if (!response.ok) {
105
- throw new Error(`API call failed with status ${response.status}: ${data.message || 'No additional error information'}`);
106
- }
107
- return data; // Return the list of dishes
108
- } catch (error) {
109
- console.error('Error fetching possible dishes:', error);
110
- throw error;
111
- }
112
- }
113
- async getInstantRecommendations() {
114
- console.log('Fetching instant recommendations...');
115
- try {
116
- const response = await fetch(`${this.baseURL}dish/recommendations/instant`, {
117
- method: 'POST',
118
- headers: {
119
- 'Content-Type': 'application/json',
120
- 'Authorization': `Bearer ${this.token}`
121
- },
122
- body: JSON.stringify({ store: this.store})
123
- });
124
-
125
- const data = await response.json();
126
-
127
- if (!response.ok) {
128
- throw new Error(`API call failed with status ${response.status}: ${data.message || 'No additional error information'}`);
129
- }
130
-
131
- console.log('Instant recommendations:', data);
132
- return data;
133
- } catch (error) {
134
- console.error('Fetching instant recommendations failed:', error.message);
135
- throw new Error(`Error in getInstantRecommendations: ${error.message}`);
136
- }
137
- }
138
- }