wineater-bot 1.0.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/style.css ADDED
@@ -0,0 +1 @@
1
+ @font-face{font-family:Poppins;src:url(https://fonts.googleapis.com/css2?family=Poppins:wght@400&display=swap)}#chatbot-container{position:fixed;bottom:20px;right:20px;font-family:Poppins,sans-serif;z-index:1000}#chat-icon{width:60px;height:60px;background-color:#7e27ed;border-radius:50%;box-shadow:0 4px 8px #0003;display:flex;justify-content:center;align-items:center;cursor:pointer}#chat-icon img{width:80%;height:auto}#chat-window{width:350px;background-color:#fff;border-radius:8px;box-shadow:0 5px 15px #0000001a;position:absolute;bottom:80px;right:0;display:none}#chat-header{background-color:#7e27ed;color:#fff;padding:16px;border-top-left-radius:8px;border-top-right-radius:8px;display:flex;justify-content:space-between;align-items:center;position:relative}#close-btn{font-size:24px;cursor:pointer;padding:0 10px;line-height:24px}#message-container{max-height:300px;overflow-y:auto;padding:15px;background:#f7f7f7}#input-container{border-top:2px solid #ececec;background-color:#fff;padding:12px;display:flex;align-items:center;gap:10px}#input-container input{flex-grow:1;border:2px solid #B928EB;padding:10px;border-radius:4px;font-family:Poppins,sans-serif}#input-container button{padding:10px 20px;background-color:#5f82ff;border:none;color:#fff;border-radius:4px;font-family:Poppins,sans-serif;font-size:1rem;cursor:pointer;box-shadow:0 2px 5px #0003;transition:background-color .3s ease}#input-container button:hover{background-color:#2fc0bf;box-shadow:0 4px 8px #0000004d}.message{margin-bottom:8px;padding:12px;background-color:#e0e0e0;border-radius:16px;font-size:14px;line-height:1.4}.message.user{background-color:#b928eb;color:#fff;align-self:flex-end}.message.api{background-color:#e0e0e0;color:#000;align-self:flex-start}.wine-item{display:flex;align-items:center;border:1px solid #ddd;border-radius:8px;padding:10px;margin-top:10px;background:#fff}.wine-item img{width:80px;height:auto;border-radius:4px;margin-right:12px}.wine-item div{font-size:.9rem}#chat-window.visible{display:block}.loader{display:none;border:4px solid #f3f3f3;border-top:4px solid #3498db;border-radius:50%;width:40px;height:40px;animation:spin 2s linear infinite;margin:20px auto}.loader.visible{display:block}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.typing-indicator{display:flex;justify-content:center;align-items:center;height:0;visibility:hidden}.typing-indicator span{display:block;width:8px;height:8px;margin:0 4px;background-color:#3498db;border-radius:50%;opacity:.4;animation:typing 1.5s infinite}@keyframes typing{0%{transform:translateY(0)}20%{transform:translateY(-10px)}40%{transform:translateY(0)}60%{transform:translateY(-10px)}80%{transform:translateY(0)}to{transform:translateY(0)}}.typing-indicator span:nth-child(1){animation-delay:0s}.typing-indicator span:nth-child(2){animation-delay:.2s}.typing-indicator span:nth-child(3){animation-delay:.4s}.typing-indicator.visible{visibility:visible;height:50px}#message-container{overflow-y:auto;height:300px;padding:10px}
@@ -0,0 +1,120 @@
1
+ function a() {
2
+ document.getElementById("chat-window").classList.toggle("visible");
3
+ }
4
+ function c() {
5
+ document.getElementById("chat-window").classList.remove("visible");
6
+ }
7
+ function r() {
8
+ document.getElementById("chat-window").classList.add("visible");
9
+ }
10
+ document.addEventListener("DOMContentLoaded", () => {
11
+ setupCloseButton(), setupOutsideClick();
12
+ });
13
+ class l {
14
+ constructor(n) {
15
+ this.apiEndpoint = n || "https://api.wineater.com/dish/recommendations";
16
+ }
17
+ async getWineRecommendations(n) {
18
+ try {
19
+ const e = await fetch(this.apiEndpoint, {
20
+ method: "POST",
21
+ headers: {
22
+ "Content-Type": "application/json"
23
+ },
24
+ body: JSON.stringify({ text: n })
25
+ }), t = await e.json();
26
+ if (!e.ok)
27
+ throw new Error(`API call failed with status ${e.status}: ${t.message || "No additional error information"}`);
28
+ return t;
29
+ } catch (e) {
30
+ throw console.error("Fetching wine recommendations failed:", e.message), new Error(`Error in getWineRecommendations: ${e.message}`);
31
+ }
32
+ }
33
+ }
34
+ class h {
35
+ constructor() {
36
+ this.api = new l();
37
+ }
38
+ initialize() {
39
+ document.getElementById("send-button").addEventListener("click", () => this.handleUserInput()), document.getElementById("user-input").addEventListener("keypress", (t) => {
40
+ t.key === "Enter" && this.handleUserInput();
41
+ });
42
+ }
43
+ async handleUserInput() {
44
+ const n = document.getElementById("user-input"), e = n.value.trim();
45
+ if (e) {
46
+ this.displayMessage(e, "user"), n.value = "", this.showTypingIndicator();
47
+ try {
48
+ const t = await this.api.getWineRecommendations(e);
49
+ this.hideTypingIndicator(), t && t.length ? this.displayResults(t) : this.displayMessage("No results found.", "api");
50
+ } catch (t) {
51
+ this.hideTypingIndicator(), console.error("Error fetching wine recommendations:", t), this.displayMessage("Sorry, something went wrong.", "api");
52
+ }
53
+ }
54
+ }
55
+ showTypingIndicator() {
56
+ document.getElementById("typing-indicator").classList.add("visible");
57
+ }
58
+ hideTypingIndicator() {
59
+ document.getElementById("typing-indicator").classList.remove("visible");
60
+ }
61
+ displayMessage(n, e) {
62
+ const t = document.getElementById("message-container"), o = document.createElement("div");
63
+ o.textContent = n, o.className = "message " + e, t.appendChild(o), t.scrollTop = t.scrollHeight;
64
+ }
65
+ displayResults(n) {
66
+ const e = document.getElementById("message-container");
67
+ n.forEach((t) => {
68
+ const o = document.createElement("div");
69
+ o.className = "wine-item";
70
+ const s = document.createElement("img");
71
+ s.src = t.Image, o.appendChild(s);
72
+ const d = document.createElement("div");
73
+ d.innerHTML = `<strong>${t.Title}</strong><br>Price: ${t.Price}<br>Region: ${t.Region}<br>Type: ${t.Type}<br><a href="${t.Url}" target="_blank">More Info</a>`, o.appendChild(d), e.appendChild(o);
74
+ }), e.scrollTop = e.scrollHeight;
75
+ }
76
+ }
77
+ document.addEventListener("DOMContentLoaded", () => {
78
+ window.toggleChatWindow = a, window.closeChatWindow = c, window.openChatWindow = r;
79
+ });
80
+ function m() {
81
+ document.body.insertAdjacentHTML("beforeend", `
82
+ <div id="chatbot-container">
83
+ <div id="chat-icon" onclick="toggleChatWindow()">
84
+ <img src="/src/assets/images/chat-icon.svg" alt="Chat Icon">
85
+ </div>
86
+
87
+ <div id="chat-window" class="hidden">
88
+ <div id="chat-header">
89
+ <span>Wineater</span>
90
+ <div id="close-btn">&times;</div>
91
+ </div>
92
+ <div id="message-container"></div>
93
+ <div id="typing-indicator" class="typing-indicator hidden">
94
+ <span></span><span></span><span></span>
95
+ </div>
96
+ <div id="input-container">
97
+ <input type="text" id="user-input" placeholder="What's on your plate?">
98
+ <button id="send-button">Ask</button>
99
+ </div>
100
+ </div>
101
+ </div>
102
+ `);
103
+ }
104
+ function p() {
105
+ document.getElementById("close-btn").addEventListener("click", a);
106
+ }
107
+ function u() {
108
+ window.addEventListener("click", function(i) {
109
+ const n = document.getElementById("chat-window"), e = document.getElementById("chat-icon");
110
+ !n.contains(i.target) && !e.contains(i.target) && n.classList.remove("visible");
111
+ });
112
+ }
113
+ typeof window < "u" && window.addEventListener("DOMContentLoaded", () => {
114
+ m(), p(), u(), new h().initialize();
115
+ });
116
+ export {
117
+ c as closeChatWindow,
118
+ r as openChatWindow,
119
+ a as toggleChatWindow
120
+ };
@@ -0,0 +1,22 @@
1
+ (function(o,d){typeof exports=="object"&&typeof module<"u"?d(exports):typeof define=="function"&&define.amd?define(["exports"],d):(o=typeof globalThis<"u"?globalThis:o||self,d(o.WineaterChatbot={}))})(this,function(o){"use strict";function d(){document.getElementById("chat-window").classList.toggle("visible")}function a(){document.getElementById("chat-window").classList.remove("visible")}function c(){document.getElementById("chat-window").classList.add("visible")}document.addEventListener("DOMContentLoaded",()=>{setupCloseButton(),setupOutsideClick()});class h{constructor(n){this.apiEndpoint=n||"https://api.wineater.com/dish/recommendations"}async getWineRecommendations(n){try{const e=await fetch(this.apiEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({text:n})}),t=await e.json();if(!e.ok)throw new Error(`API call failed with status ${e.status}: ${t.message||"No additional error information"}`);return t}catch(e){throw console.error("Fetching wine recommendations failed:",e.message),new Error(`Error in getWineRecommendations: ${e.message}`)}}}class u{constructor(){this.api=new h}initialize(){document.getElementById("send-button").addEventListener("click",()=>this.handleUserInput()),document.getElementById("user-input").addEventListener("keypress",t=>{t.key==="Enter"&&this.handleUserInput()})}async handleUserInput(){const n=document.getElementById("user-input"),e=n.value.trim();if(e){this.displayMessage(e,"user"),n.value="",this.showTypingIndicator();try{const t=await this.api.getWineRecommendations(e);this.hideTypingIndicator(),t&&t.length?this.displayResults(t):this.displayMessage("No results found.","api")}catch(t){this.hideTypingIndicator(),console.error("Error fetching wine recommendations:",t),this.displayMessage("Sorry, something went wrong.","api")}}}showTypingIndicator(){document.getElementById("typing-indicator").classList.add("visible")}hideTypingIndicator(){document.getElementById("typing-indicator").classList.remove("visible")}displayMessage(n,e){const t=document.getElementById("message-container"),s=document.createElement("div");s.textContent=n,s.className="message "+e,t.appendChild(s),t.scrollTop=t.scrollHeight}displayResults(n){const e=document.getElementById("message-container");n.forEach(t=>{const s=document.createElement("div");s.className="wine-item";const r=document.createElement("img");r.src=t.Image,s.appendChild(r);const l=document.createElement("div");l.innerHTML=`<strong>${t.Title}</strong><br>Price: ${t.Price}<br>Region: ${t.Region}<br>Type: ${t.Type}<br><a href="${t.Url}" target="_blank">More Info</a>`,s.appendChild(l),e.appendChild(s)}),e.scrollTop=e.scrollHeight}}document.addEventListener("DOMContentLoaded",()=>{window.toggleChatWindow=d,window.closeChatWindow=a,window.openChatWindow=c});function p(){document.body.insertAdjacentHTML("beforeend",`
2
+ <div id="chatbot-container">
3
+ <div id="chat-icon" onclick="toggleChatWindow()">
4
+ <img src="/src/assets/images/chat-icon.svg" alt="Chat Icon">
5
+ </div>
6
+
7
+ <div id="chat-window" class="hidden">
8
+ <div id="chat-header">
9
+ <span>Wineater</span>
10
+ <div id="close-btn">&times;</div>
11
+ </div>
12
+ <div id="message-container"></div>
13
+ <div id="typing-indicator" class="typing-indicator hidden">
14
+ <span></span><span></span><span></span>
15
+ </div>
16
+ <div id="input-container">
17
+ <input type="text" id="user-input" placeholder="What's on your plate?">
18
+ <button id="send-button">Ask</button>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ `)}function m(){document.getElementById("close-btn").addEventListener("click",d)}function g(){window.addEventListener("click",function(i){const n=document.getElementById("chat-window"),e=document.getElementById("chat-icon");!n.contains(i.target)&&!e.contains(i.target)&&n.classList.remove("visible")})}typeof window<"u"&&window.addEventListener("DOMContentLoaded",()=>{p(),m(),g(),new u().initialize()}),o.closeChatWindow=a,o.openChatWindow=c,o.toggleChatWindow=d,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"})});
package/index.html ADDED
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Chatbot</title>
7
+ <link rel="stylesheet" href="/src/css/styles.css">
8
+ <script type="module" src="./src/index.js"></script>
9
+ </head>
10
+ <body>
11
+ </body>
12
+ </html>
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "wineater-bot",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "dist/index.js",
6
+ "module": "src/index.js",
7
+ "scripts": {
8
+ "dev": "vite",
9
+ "build": "vite build"
10
+ },
11
+ "keywords": [],
12
+ "author": "",
13
+ "license": "ISC",
14
+ "dependencies": {
15
+ "vite": "^5.2.9"
16
+ }
17
+ }
@@ -0,0 +1,4 @@
1
+ <svg width="601" height="601" viewBox="0 0 601 601" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="601" height="601" rx="300.5" fill="#7E27ED"/>
3
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M289.032 111.531C289.032 106.755 293.914 102.883 299.935 102.883C305.956 102.883 310.837 106.755 310.837 111.531V154.771C310.837 159.547 305.956 163.419 299.935 163.419C293.914 163.419 289.032 159.547 289.032 154.771V111.531ZM180.584 266.419C202.451 266.419 220.177 248.693 220.177 226.827C220.177 204.961 202.451 187.235 180.584 187.235C158.718 187.235 140.992 204.961 140.992 226.827C140.992 248.693 158.718 266.419 180.584 266.419ZM340.06 217.379C344.44 238.146 330.097 258.756 308.024 263.41C294.694 266.221 279.849 271.496 274.411 284.117L273.306 286.682C267.709 299.673 273.583 314.737 279.089 327.826C280.109 330.249 280.914 332.805 281.477 335.475C286.194 357.84 272.124 379.745 250.051 384.399C227.978 389.054 206.261 374.697 201.544 352.332C196.828 329.967 210.898 308.062 232.971 303.408C245.5 300.765 259.4 295.834 264.511 283.971L266.253 279.929C271.376 268.038 266.719 254.397 262.431 241.837L262.431 241.836C262.025 240.647 261.622 239.468 261.231 238.3C260.79 236.982 260.42 235.626 260.127 234.235C255.747 213.468 270.091 192.859 292.164 188.204C314.237 183.549 335.681 196.611 340.06 217.379ZM399.816 334.969C404.196 355.736 389.853 376.345 367.78 381C354.45 383.811 339.605 389.086 334.167 401.706L333.062 404.272C327.465 417.263 333.339 432.326 338.845 445.416C339.865 447.839 340.67 450.395 341.233 453.065C345.95 475.43 331.88 497.335 309.807 501.989C287.734 506.644 266.017 492.287 261.3 469.922C256.584 447.557 270.654 425.652 292.727 420.998C305.256 418.355 319.156 413.424 324.267 401.561L326.009 397.519C331.132 385.628 326.475 371.987 322.187 359.427C321.781 358.238 321.378 357.058 320.987 355.89C320.546 354.572 320.176 353.216 319.883 351.825C315.503 331.058 329.847 310.448 351.92 305.794C373.993 301.139 395.437 314.201 399.816 334.969ZM459.164 226.827C459.164 248.693 441.438 266.419 419.572 266.419C397.706 266.419 379.98 248.693 379.98 226.827C379.98 204.961 397.706 187.235 419.572 187.235C441.438 187.235 459.164 204.961 459.164 226.827Z" fill="white"/>
4
+ </svg>
@@ -0,0 +1,232 @@
1
+ @font-face {
2
+ font-family: 'Poppins';
3
+ src: url('https://fonts.googleapis.com/css2?family=Poppins:wght@400&display=swap');
4
+ }
5
+
6
+ /* Overall chatbot styles */
7
+ #chatbot-container {
8
+ position: fixed;
9
+ bottom: 20px;
10
+ right: 20px;
11
+ font-family: 'Poppins', sans-serif; /* Use Poppins as the main font */
12
+ z-index: 1000; /* Ensure chat is above other content */
13
+ }
14
+
15
+ /* Styling the chat icon */
16
+ #chat-icon {
17
+ width: 60px;
18
+ height: 60px;
19
+ background-color: #7E27ED; /* Purple background color */
20
+ border-radius: 50%;
21
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
22
+ display: flex;
23
+ justify-content: center;
24
+ align-items: center;
25
+ cursor: pointer;
26
+ }
27
+
28
+ #chat-icon img {
29
+ width: 80%; /* Adjust icon size as needed */
30
+ height: auto;
31
+ }
32
+
33
+ /* Chat window styles */
34
+ #chat-window {
35
+ width: 350px; /* Width of the chat window */
36
+ background-color: white;
37
+ border-radius: 8px;
38
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
39
+ position: absolute;
40
+ bottom: 80px; /* Positioning from the bottom */
41
+ right: 0;
42
+ display: none; /* Initially hidden */
43
+ }
44
+
45
+ /* Header of the chat window */
46
+ #chat-header {
47
+ background-color: #7E27ED;
48
+ color: white;
49
+ padding: 16px;
50
+ border-top-left-radius: 8px;
51
+ border-top-right-radius: 8px;
52
+ display: flex;
53
+ justify-content: space-between;
54
+ align-items: center;
55
+ position: relative; /* Needed for absolute positioning of the close button */
56
+ }
57
+
58
+
59
+ /* Style for the close button */
60
+ #close-btn {
61
+ font-size: 24px; /* Size of the close button */
62
+ cursor: pointer;
63
+ padding: 0 10px;
64
+ line-height: 24px;
65
+ }
66
+
67
+ /* Container for messages */
68
+ #message-container {
69
+ max-height: 300px;
70
+ overflow-y: auto;
71
+ padding: 15px;
72
+ background: #f7f7f7; /* Light grey background for the message area */
73
+ }
74
+
75
+ /* Input area */
76
+ #input-container {
77
+ border-top: 2px solid #ececec; /* Light border for input area */
78
+ background-color: white; /* White background for input area */
79
+ padding: 12px;
80
+ display: flex;
81
+ align-items: center; /* Align button and input vertically */
82
+ gap: 10px; /* Adds space between input and button */
83
+ }
84
+
85
+ #input-container input {
86
+ flex-grow: 1;
87
+ border: 2px solid #B928EB; /* Input border color based on style guide */
88
+ padding: 10px;
89
+ border-radius: 4px;
90
+ font-family: 'Poppins', sans-serif; /* Use Poppins font */
91
+ }
92
+
93
+ #input-container button {
94
+ padding: 10px 20px; /* Larger padding for a larger button */
95
+ background-color: #5F82FF; /* Button background color from style guide */
96
+ border: none;
97
+ color: white;
98
+ border-radius: 4px;
99
+ font-family: 'Poppins', sans-serif; /* Use Poppins font */
100
+ font-size: 1rem; /* Adjust font size as needed */
101
+ cursor: pointer;
102
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); /* Add subtle shadow for depth */
103
+ transition: background-color 0.3s ease; /* Smooth transition for background color */
104
+ }
105
+
106
+ #input-container button:hover {
107
+ background-color: #2FC0BF; /* Hover color based on style guide */
108
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); /* Darker shadow for hover state */
109
+ }
110
+
111
+ /* Message bubble styling */
112
+ .message {
113
+ margin-bottom: 8px;
114
+ padding: 12px;
115
+ background-color: #e0e0e0; /* Light grey for message bubbles */
116
+ border-radius: 16px;
117
+ font-size: 14px;
118
+ line-height: 1.4;
119
+ }
120
+
121
+ .message.user {
122
+ background-color: #B928EB; /* User message bubble color */
123
+ color: white;
124
+ align-self: flex-end;
125
+ }
126
+
127
+ .message.api {
128
+ background-color: #e0e0e0; /* API message bubble color */
129
+ color: black;
130
+ align-self: flex-start;
131
+ }
132
+
133
+ /* Wine item styling */
134
+ .wine-item {
135
+ display: flex;
136
+ align-items: center;
137
+ border: 1px solid #ddd;
138
+ border-radius: 8px;
139
+ padding: 10px;
140
+ margin-top: 10px;
141
+ background: white; /* White background for wine items */
142
+ }
143
+
144
+ .wine-item img {
145
+ width: 80px; /* Smaller wine image size */
146
+ height: auto;
147
+ border-radius: 4px; /* Rounded corners for images */
148
+ margin-right: 12px;
149
+ }
150
+
151
+ .wine-item div {
152
+ font-size: 0.9rem; /* Smaller font size for wine info */
153
+ }
154
+
155
+ /* Ensure the chat window is visible when the 'visible' class is added */
156
+ #chat-window.visible {
157
+ display: block;
158
+ }
159
+
160
+ /* Loader Styles */
161
+ .loader {
162
+ display: none; /* Loader is hidden by default */
163
+ border: 4px solid #f3f3f3; /* Light grey */
164
+ border-top: 4px solid #3498db; /* Blue, could be your primary color */
165
+ border-radius: 50%;
166
+ width: 40px;
167
+ height: 40px;
168
+ animation: spin 2s linear infinite;
169
+ margin: 20px auto; /* Centered horizontally */
170
+ }
171
+
172
+ .loader.visible {
173
+ display: block;
174
+ }
175
+
176
+ @keyframes spin {
177
+ 0% { transform: rotate(0deg); }
178
+ 100% { transform: rotate(360deg); }
179
+ }
180
+
181
+ /* Typing indicator styles */
182
+ .typing-indicator {
183
+ display: flex;
184
+ justify-content: center;
185
+ align-items: center;
186
+ height: 0; /* Adjust as needed */
187
+ visibility: hidden; /* Start off invisible */
188
+ }
189
+
190
+ .typing-indicator span {
191
+ display: block;
192
+ width: 8px;
193
+ height: 8px;
194
+ margin: 0 4px;
195
+ background-color: #3498db; /* Or use a color from your style guide */
196
+ border-radius: 50%;
197
+ opacity: 0.4;
198
+ animation: typing 1.5s infinite;
199
+ }
200
+
201
+ /* Animation keyframes */
202
+ @keyframes typing {
203
+ 0% { transform: translateY(0); }
204
+ 20% { transform: translateY(-10px); }
205
+ 40% { transform: translateY(0); }
206
+ 60% { transform: translateY(-10px); }
207
+ 80% { transform: translateY(0); }
208
+ 100% { transform: translateY(0); }
209
+ }
210
+
211
+ /* To stagger the animation for each dot */
212
+ .typing-indicator span:nth-child(1) {
213
+ animation-delay: 0s;
214
+ }
215
+ .typing-indicator span:nth-child(2) {
216
+ animation-delay: 0.2s;
217
+ }
218
+ .typing-indicator span:nth-child(3) {
219
+ animation-delay: 0.4s;
220
+ }
221
+
222
+ /* Show the typing indicator */
223
+ .typing-indicator.visible {
224
+ visibility: visible;
225
+ height: 50px;
226
+ }
227
+
228
+ #message-container {
229
+ overflow-y: auto;
230
+ height: 300px; /* adjust based on your UI needs */
231
+ padding: 10px;
232
+ }
package/src/index.js ADDED
@@ -0,0 +1,76 @@
1
+ // src/index.js
2
+
3
+ // Import your CSS here
4
+ import './css/styles.css';
5
+
6
+ // Import your JavaScript modules here
7
+ import { toggleChatWindow, closeChatWindow, openChatWindow } from './js/ui.js';
8
+ import { Chatbot } from './js/chatbot.js';
9
+ import './js/api.js';
10
+
11
+ document.addEventListener('DOMContentLoaded', () => {
12
+ // Attach any necessary initialization code here
13
+ // For example, expose functions to global scope if absolutely necessary
14
+ window.toggleChatWindow = toggleChatWindow;
15
+ window.closeChatWindow = closeChatWindow;
16
+ window.openChatWindow = openChatWindow;
17
+ });
18
+
19
+ export {
20
+ toggleChatWindow,
21
+ closeChatWindow,
22
+ openChatWindow
23
+ };
24
+
25
+ function createChatbotUI() {
26
+ const chatbotHTML = `
27
+ <div id="chatbot-container">
28
+ <div id="chat-icon" onclick="toggleChatWindow()">
29
+ <img src="/src/assets/images/chat-icon.svg" alt="Chat Icon">
30
+ </div>
31
+
32
+ <div id="chat-window" class="hidden">
33
+ <div id="chat-header">
34
+ <span>Wineater</span>
35
+ <div id="close-btn">&times;</div>
36
+ </div>
37
+ <div id="message-container"></div>
38
+ <div id="typing-indicator" class="typing-indicator hidden">
39
+ <span></span><span></span><span></span>
40
+ </div>
41
+ <div id="input-container">
42
+ <input type="text" id="user-input" placeholder="What's on your plate?">
43
+ <button id="send-button">Ask</button>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ `;
48
+
49
+ document.body.insertAdjacentHTML('beforeend', chatbotHTML);
50
+ }
51
+
52
+ function setupCloseButton() {
53
+ const closeButton = document.getElementById('close-btn');
54
+ closeButton.addEventListener('click', toggleChatWindow);
55
+ }
56
+
57
+ function setupOutsideClick() {
58
+ window.addEventListener('click', function(event) {
59
+ const chatWindow = document.getElementById('chat-window');
60
+ const chatIcon = document.getElementById('chat-icon');
61
+ if (!chatWindow.contains(event.target) && !chatIcon.contains(event.target)) {
62
+ chatWindow.classList.remove('visible');
63
+ }
64
+ });
65
+ }
66
+
67
+ // If you want to initialize your chatbot automatically when the script is loaded
68
+ if (typeof window !== 'undefined') {
69
+ window.addEventListener('DOMContentLoaded', () => {
70
+ createChatbotUI(); // Create the UI
71
+ setupCloseButton();
72
+ setupOutsideClick();
73
+ const myChatbot = new Chatbot();
74
+ myChatbot.initialize();
75
+ });
76
+ }
package/src/js/api.js ADDED
@@ -0,0 +1,33 @@
1
+ // api.js
2
+
3
+ export class Api {
4
+ constructor(apiEndpoint) {
5
+ // Allow users of the library to specify their own endpoint if needed
6
+ this.apiEndpoint = apiEndpoint || 'https://api.wineater.com/dish/recommendations';
7
+ }
8
+
9
+ async getWineRecommendations(text) {
10
+ try {
11
+ const response = await fetch(this.apiEndpoint, {
12
+ method: 'POST',
13
+ headers: {
14
+ 'Content-Type': 'application/json'
15
+ },
16
+ body: JSON.stringify({ text: text })
17
+ });
18
+
19
+ const data = await response.json();
20
+
21
+ if (!response.ok) {
22
+ // Providing more detailed error information
23
+ throw new Error(`API call failed with status ${response.status}: ${data.message || 'No additional error information'}`);
24
+ }
25
+
26
+ return data;
27
+ } catch (error) {
28
+ // Log and rethrow to allow consumer of the API to handle or display the error appropriately
29
+ console.error('Fetching wine recommendations failed:', error.message);
30
+ throw new Error(`Error in getWineRecommendations: ${error.message}`);
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,90 @@
1
+ // chatbot.js
2
+ import { Api } from './api.js';
3
+
4
+ export class Chatbot {
5
+ constructor() {
6
+ this.api = new Api(); // Instantiate the Api class
7
+ }
8
+
9
+ initialize() {
10
+ // Attach event listeners
11
+ const sendButton = document.getElementById('send-button');
12
+ sendButton.addEventListener('click', () => this.handleUserInput());
13
+
14
+ const userInputField = document.getElementById('user-input');
15
+ userInputField.addEventListener('keypress', (event) => {
16
+ if (event.key === 'Enter') {
17
+ this.handleUserInput();
18
+ }
19
+ });
20
+ }
21
+
22
+ async handleUserInput() {
23
+ const inputField = document.getElementById('user-input');
24
+ const userInput = inputField.value.trim();
25
+ if (!userInput) return;
26
+
27
+ this.displayMessage(userInput, 'user');
28
+ inputField.value = '';
29
+
30
+ // Show the loader
31
+ this.showTypingIndicator();
32
+
33
+ try {
34
+ const results = await this.api.getWineRecommendations(userInput);
35
+
36
+ // Hide the loader
37
+ this.hideTypingIndicator();
38
+
39
+ if (results && results.length) {
40
+ this.displayResults(results);
41
+ } else {
42
+ this.displayMessage('No results found.', 'api');
43
+ }
44
+ } catch (error) {
45
+ // Hide the loader
46
+ this.hideTypingIndicator();
47
+
48
+ console.error('Error fetching wine recommendations:', error);
49
+ this.displayMessage('Sorry, something went wrong.', 'api');
50
+ }
51
+ }
52
+
53
+ showTypingIndicator() {
54
+ const typingIndicator = document.getElementById('typing-indicator');
55
+ typingIndicator.classList.add('visible');
56
+ }
57
+
58
+ hideTypingIndicator() {
59
+ const typingIndicator = document.getElementById('typing-indicator');
60
+ typingIndicator.classList.remove('visible');
61
+ }
62
+
63
+ displayMessage(message, type) {
64
+ const messageContainer = document.getElementById('message-container');
65
+ const messageDiv = document.createElement('div');
66
+ messageDiv.textContent = message;
67
+ messageDiv.className = 'message ' + type;
68
+ messageContainer.appendChild(messageDiv);
69
+ messageContainer.scrollTop = messageContainer.scrollHeight;
70
+ }
71
+
72
+ displayResults(wines) {
73
+ const messageContainer = document.getElementById('message-container');
74
+ wines.forEach(wine => {
75
+ const wineDiv = document.createElement('div');
76
+ wineDiv.className = 'wine-item';
77
+
78
+ const image = document.createElement('img');
79
+ image.src = wine.Image;
80
+ wineDiv.appendChild(image);
81
+
82
+ const contentDiv = document.createElement('div');
83
+ contentDiv.innerHTML = `<strong>${wine.Title}</strong><br>Price: ${wine.Price}<br>Region: ${wine.Region}<br>Type: ${wine.Type}<br><a href="${wine.Url}" target="_blank">More Info</a>`;
84
+ wineDiv.appendChild(contentDiv);
85
+
86
+ messageContainer.appendChild(wineDiv);
87
+ });
88
+ messageContainer.scrollTop = messageContainer.scrollHeight;
89
+ }
90
+ }
package/src/js/ui.js ADDED
@@ -0,0 +1,20 @@
1
+ export function toggleChatWindow() {
2
+ const chatWindow = document.getElementById('chat-window');
3
+ chatWindow.classList.toggle('visible');
4
+ }
5
+
6
+ export function closeChatWindow() {
7
+ const chatWindow = document.getElementById('chat-window');
8
+ chatWindow.classList.remove('visible');
9
+ }
10
+
11
+ export function openChatWindow() {
12
+ const chatWindow = document.getElementById('chat-window');
13
+ chatWindow.classList.add('visible');
14
+ }
15
+
16
+ // Setup event listeners upon loading the module
17
+ document.addEventListener('DOMContentLoaded', () => {
18
+ setupCloseButton();
19
+ setupOutsideClick();
20
+ });
package/vite.config.js ADDED
@@ -0,0 +1,21 @@
1
+ import { defineConfig } from 'vite';
2
+
3
+ export default defineConfig({
4
+ server: {
5
+ port: 3000 // 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
+ });