forked from GCA-Classroom/08-prj-loreal-chatbot
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
165 lines (127 loc) · 5.87 KB
/
Copy pathscript.js
File metadata and controls
165 lines (127 loc) · 5.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/* DOM elements */
const chatForm = document.getElementById("chatForm");
const userInput = document.getElementById("userInput");
const chatWindow = document.getElementById("chatWindow");
const sendBtn = document.getElementById("sendBtn");
const statusMessage = document.getElementById("statusMessage");
// All chatbot requests go through the class Cloudflare Worker.
const WORKER_URL = "https://winter-bread-6a37.yho5.workers.dev/";
// This prompt keeps the assistant focused on L’Oréal and beauty topics.
const SYSTEM_PROMPT = `You are the L’Oréal Beauty Assistant. You help users learn about L’Oréal products, product categories, skincare, makeup, haircare, fragrances, beauty routines, and general beauty recommendations.
Only answer questions related to L’Oréal, beauty, cosmetics, skincare, haircare, makeup, fragrances, product selection, product usage, or beauty routines.
When the user asks an unrelated question, politely refuse and redirect them to a L’Oréal or beauty-related topic.
Do not pretend to diagnose medical conditions. For serious skin reactions, allergies, persistent irritation, hair loss, or other medical concerns, recommend consulting a qualified healthcare professional.
Do not guarantee results. Clearly explain that product results can vary by person.
Ask brief follow-up questions when useful, such as the user’s skin type, hair type, concern, desired result, product preference, or budget.
Keep answers friendly, clear, practical, and reasonably concise.
Do not claim that a product definitely exists unless the information is available in the conversation or returned by the model. Avoid inventing exact prices, ingredients, shades, product availability, or medical benefits.`;
const WELCOME_MESSAGE =
"Hello! I’m your L’Oréal Beauty Assistant. I can help you explore skincare, makeup, haircare, fragrances, and personalized beauty routines. What would you like help with today?";
const FRIENDLY_ERROR_MESSAGE =
"Sorry, I could not reach the beauty assistant right now. Please try again in a moment.";
// Keep the system prompt plus the most recent user/assistant messages.
const MAX_HISTORY_MESSAGES = 16;
const conversationHistory = [{ role: "system", content: SYSTEM_PROMPT }];
// Show the welcome message as soon as the page loads.
addMessage("assistant", WELCOME_MESSAGE);
/* Handle form submit from the Send button or Enter key. */
chatForm.addEventListener("submit", async (event) => {
event.preventDefault();
await sendMessage();
});
async function sendMessage() {
const message = userInput.value.trim();
if (!message) {
statusMessage.textContent = "Please type a beauty question before sending.";
userInput.focus();
return;
}
statusMessage.textContent = "";
addMessage("user", message);
conversationHistory.push({ role: "user", content: message });
trimConversationHistory();
userInput.value = "";
setLoadingState(true);
const typingMessage = addMessage(
"assistant",
"L’Oréal Beauty Assistant is typing…",
true,
);
try {
const response = await fetch(WORKER_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messages: conversationHistory }),
});
let data;
try {
data = await response.json();
} catch (jsonError) {
console.error("The Worker did not return valid JSON.", jsonError);
throw new Error("Invalid JSON response from Worker");
}
if (!response.ok) {
console.error("Worker returned an error response:", response.status, data);
throw new Error(`Worker error ${response.status}`);
}
const assistantReply = extractAssistantReply(data);
if (!assistantReply) {
console.error("Could not find assistant text in Worker response:", data);
throw new Error("Empty assistant response");
}
typingMessage.remove();
addMessage("assistant", assistantReply);
conversationHistory.push({ role: "assistant", content: assistantReply });
trimConversationHistory();
} catch (error) {
console.error("Chat request failed:", error);
typingMessage.remove();
addMessage("assistant", FRIENDLY_ERROR_MESSAGE);
statusMessage.textContent = FRIENDLY_ERROR_MESSAGE;
} finally {
setLoadingState(false);
userInput.focus();
}
}
function addMessage(role, text, isTyping = false) {
const message = document.createElement("div");
message.className = `msg ${role === "user" ? "user" : "ai"}`;
if (isTyping) {
message.classList.add("typing");
}
const label = document.createElement("span");
label.className = "msg-label";
label.textContent = role === "user" ? "You" : "L’Oréal Beauty Assistant";
const bubble = document.createElement("div");
bubble.className = "msg-bubble";
bubble.textContent = text;
message.appendChild(label);
message.appendChild(bubble);
chatWindow.appendChild(message);
chatWindow.scrollTop = chatWindow.scrollHeight;
return message;
}
function setLoadingState(isLoading) {
sendBtn.disabled = isLoading;
userInput.disabled = isLoading;
statusMessage.textContent = isLoading ? "Waiting for the beauty assistant…" : "";
}
function trimConversationHistory() {
const systemPrompt = conversationHistory[0];
const recentMessages = conversationHistory.slice(1).slice(-MAX_HISTORY_MESSAGES);
conversationHistory.length = 0;
conversationHistory.push(systemPrompt, ...recentMessages);
}
function extractAssistantReply(data) {
// The provided Worker returns the OpenAI chat completions shape.
const primaryReply = data?.choices?.[0]?.message?.content;
if (typeof primaryReply === "string" && primaryReply.trim()) {
return primaryReply.trim();
}
// Safe fallbacks for other simple Worker response shapes.
const fallbackReply = data?.response || data?.message || data?.reply || data?.content;
if (typeof fallbackReply === "string" && fallbackReply.trim()) {
return fallbackReply.trim();
}
return "";
}