deedrop1140 commited on
Commit
a5cfa35
·
verified ·
1 Parent(s): f6d6416

Update static/script.js

Browse files
Files changed (1) hide show
  1. static/script.js +213 -54
static/script.js CHANGED
@@ -1,3 +1,7 @@
 
 
 
 
1
  const chatForm = document.getElementById("chat-form");
2
  const userInput = document.getElementById("user-input");
3
  const chatBox = document.getElementById("chat-box");
@@ -5,93 +9,248 @@ const orderIdEl = document.getElementById("order_id");
5
  const categoryEl = document.getElementById("category");
6
  const sentimentEl = document.getElementById("sentiment");
7
  const negativeBox = document.getElementById("negative-box");
 
8
 
9
  let sentimentChart = null;
10
 
11
- // 🧠 Append User Message
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  function appendUser(text) {
13
- chatBox.innerHTML += `<p class="user-msg">🧑 You: ${text}</p>`;
14
- chatBox.scrollTop = chatBox.scrollHeight;
 
 
 
 
15
  }
16
 
17
- // 🤖 Append Bot Message
18
  function appendBot(text) {
19
- chatBox.innerHTML += `<p class="bot-msg">🤖 Bot: ${text}</p>`;
20
- chatBox.scrollTop = chatBox.scrollHeight;
 
 
 
 
21
  }
22
 
23
- // Send chat message
24
- chatForm.addEventListener("submit", async (e) => {
 
25
  e.preventDefault();
 
 
26
 
27
- const text = userInput.value.trim();
28
- const order_id = orderIdEl.value.trim();
29
  if (!text) return;
30
 
31
  appendUser(text);
32
- userInput.value = "";
33
 
34
  try {
35
- const res = await fetch("/chat", {
36
- method: "POST",
37
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
38
- body: new URLSearchParams({ user_input: text, order_id })
39
- });
 
40
 
41
- const data = await res.json();
 
 
 
42
 
43
- if (data.error) {
44
- appendBot("Sorry, could not process your message.");
45
- return;
46
- }
47
 
48
- appendBot(data.reply);
 
49
 
50
- categoryEl.textContent = data.category || "—";
51
- sentimentEl.textContent = `${data.sentiment || "—"} (${data.confidence || 0}%)`;
52
-
53
- await updateAnalytics();
54
- await updateNegatives();
55
  } catch (err) {
56
- console.error(err);
57
- appendBot("Network error — try again.");
58
  }
59
- });
 
60
 
61
- // 📊 Update Analytics Charts
62
  async function updateAnalytics() {
63
- const res = await fetch("/analytics");
64
- const data = await res.json();
65
 
66
- const ctx = document.getElementById("sentimentChart");
 
 
 
 
67
 
68
  if (sentimentChart) sentimentChart.destroy();
69
 
70
- sentimentChart = new Chart(ctx, {
71
- type: "pie",
72
- data: {
73
- labels: data.labels,
74
- datasets: [{
75
- data: data.values,
76
- backgroundColor: ["#2ecc71", "#e74c3c", "#f1c40f"]
77
- }]
78
- }
 
 
 
 
79
  });
 
 
 
 
80
  }
81
 
82
- // ⚠️ Update Negative Messages Box
83
- async function updateNegatives() {
84
- if (!negativeBox) return; // Safety check
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
- const res = await fetch("/admin/negatives-data");
87
  const data = await res.json();
88
 
89
- negativeBox.innerHTML = data.length === 0
90
- ? "<p>No negative messages yet.</p>"
91
- : data.map(msg =>
92
- `<p class="neg-item">⚠️ <b>${msg.username}</b>: ${msg.message}</p>`
93
- ).join("");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  }
95
 
96
- // auto-refresh negative messages
97
- setInterval(updateNegatives, 8000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // static/script.js
2
+ // Full client-side script for chat, analytics, and admin negatives panel.
3
+
4
+ // Element refs
5
  const chatForm = document.getElementById("chat-form");
6
  const userInput = document.getElementById("user-input");
7
  const chatBox = document.getElementById("chat-box");
 
9
  const categoryEl = document.getElementById("category");
10
  const sentimentEl = document.getElementById("sentiment");
11
  const negativeBox = document.getElementById("negative-box");
12
+ const sentimentCanvas = document.getElementById("sentimentChart");
13
 
14
  let sentimentChart = null;
15
 
16
+ // ---------- Helper: robust JSON fetch (handles non-JSON and error pages) ----------
17
+ async function fetchJson(url, options = {}) {
18
+ const res = await fetch(url, {
19
+ credentials: "same-origin", // send cookies for session-protected endpoints
20
+ ...options,
21
+ });
22
+
23
+ // If not OK, read body text for diagnostics and throw
24
+ if (!res.ok) {
25
+ const text = await res.text().catch(() => "");
26
+ const err = new Error(`HTTP ${res.status} ${res.statusText}`);
27
+ err.status = res.status;
28
+ err.body = text;
29
+ throw err;
30
+ }
31
+
32
+ // Ensure response is JSON
33
+ const contentType = res.headers.get("content-type") || "";
34
+ if (!contentType.includes("application/json")) {
35
+ const text = await res.text().catch(() => "");
36
+ const err = new Error(`Expected JSON but got ${contentType}`);
37
+ err.status = res.status;
38
+ err.body = text;
39
+ throw err;
40
+ }
41
+
42
+ return res.json();
43
+ }
44
+
45
+ // ---------- UI helpers ----------
46
  function appendUser(text) {
47
+ if (!chatBox) return;
48
+ const p = document.createElement("p");
49
+ p.className = "user-msg";
50
+ p.innerHTML = `🧑 You: ${escapeHtml(text)}`;
51
+ chatBox.appendChild(p);
52
+ chatBox.scrollTop = chatBox.scrollHeight;
53
  }
54
 
 
55
  function appendBot(text) {
56
+ if (!chatBox) return;
57
+ const p = document.createElement("p");
58
+ p.className = "bot-msg";
59
+ p.innerHTML = `🤖 Bot: ${escapeHtml(text)}`;
60
+ chatBox.appendChild(p);
61
+ chatBox.scrollTop = chatBox.scrollHeight;
62
  }
63
 
64
+ // ---------- Chat submit handler ----------
65
+ if (chatForm) {
66
+ chatForm.addEventListener("submit", async (e) => {
67
  e.preventDefault();
68
+ const text = (userInput && userInput.value || "").trim();
69
+ const order_id = (orderIdEl && orderIdEl.value) ? orderIdEl.value.trim() : "";
70
 
 
 
71
  if (!text) return;
72
 
73
  appendUser(text);
74
+ if (userInput) userInput.value = "";
75
 
76
  try {
77
+ // send form-encoded request (matches Flask form handlers)
78
+ const data = await fetchJson("/chat", {
79
+ method: "POST",
80
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
81
+ body: new URLSearchParams({ user_input: text, order_id }).toString(),
82
+ });
83
 
84
+ if (data.error) {
85
+ appendBot(data.message || "Sorry, could not process your message.");
86
+ return;
87
+ }
88
 
89
+ appendBot(data.reply || "No reply.");
 
 
 
90
 
91
+ if (categoryEl) categoryEl.textContent = data.category || "—";
92
+ if (sentimentEl) sentimentEl.textContent = `${data.sentiment || "—"} (${data.confidence || 0}%)`;
93
 
94
+ // update analytics + negatives after successful chat
95
+ await updateAnalytics();
96
+ await updateNegatives();
 
 
97
  } catch (err) {
98
+ console.error("Chat submit error:", err);
99
+ appendBot("Network or server error — try again.");
100
  }
101
+ });
102
+ }
103
 
104
+ // ---------- Analytics (Chart.js) ----------
105
  async function updateAnalytics() {
106
+ if (!sentimentCanvas) return;
 
107
 
108
+ try {
109
+ const data = await fetchJson("/analytics");
110
+
111
+ const labels = Array.isArray(data.labels) ? data.labels : (data.labels || []);
112
+ const values = Array.isArray(data.values) ? data.values : (data.values || []);
113
 
114
  if (sentimentChart) sentimentChart.destroy();
115
 
116
+ sentimentChart = new Chart(sentimentCanvas, {
117
+ type: "pie",
118
+ data: {
119
+ labels: labels,
120
+ datasets: [{
121
+ data: values,
122
+ backgroundColor: ["#2ecc71", "#e74c3c", "#f1c40f"],
123
+ }],
124
+ },
125
+ options: {
126
+ responsive: true,
127
+ maintainAspectRatio: false,
128
+ },
129
  });
130
+ } catch (err) {
131
+ console.error("updateAnalytics failed:", err);
132
+ // silent fail; UI will show previous chart or none
133
+ }
134
  }
135
 
136
+ // ---------- Negatives loader (robust) ----------
137
+ async function updateNegatives(page = 1, perPage = 50) {
138
+ if (!negativeBox) return;
139
+
140
+ // loading UI
141
+ negativeBox.innerHTML = '<p class="loading">Loading recent negatives…</p>';
142
+
143
+ try {
144
+ const res = await fetch(`/admin/negatives-data?page=${page}&per_page=${perPage}`, {
145
+ credentials: "same-origin",
146
+ method: "GET",
147
+ });
148
+
149
+ // handle auth errors gracefully
150
+ if (res.status === 401 || res.status === 403) {
151
+ const text = await res.text().catch(() => "");
152
+ let info = null;
153
+ try { info = JSON.parse(text); } catch (e) { info = null; }
154
+
155
+ negativeBox.innerHTML = `
156
+ <div style="color:salmon">
157
+ <p><strong>Admin access required</strong></p>
158
+ <p>${escapeHtml(info && (info.message || info.error) ? (info.message || info.error) : 'Please log in as an admin to view this data.')}</p>
159
+ <p><a href="/login">Log in</a> and reload the page.</p>
160
+ </div>
161
+ `;
162
+ console.warn("Negatives access denied:", res.status, text);
163
+ return;
164
+ }
165
+
166
+ if (!res.ok) {
167
+ const body = await res.text().catch(() => "");
168
+ negativeBox.innerHTML = `<pre style="color:salmon">Failed to load negatives (status ${res.status}).\n${escapeHtml(body.slice ? body.slice(0,2000) : String(body))}</pre>`;
169
+ console.error("Negatives non-ok response:", res.status, body);
170
+ return;
171
+ }
172
+
173
+ const contentType = res.headers.get("content-type") || "";
174
+ if (!contentType.includes("application/json")) {
175
+ const body = await res.text().catch(() => "");
176
+ negativeBox.innerHTML = `<pre style="color:salmon">Expected JSON but server returned ${contentType}:\n${escapeHtml(body.slice ? body.slice(0,2000) : String(body))}</pre>`;
177
+ console.error("Negatives unexpected content-type:", contentType, body);
178
+ return;
179
+ }
180
 
 
181
  const data = await res.json();
182
 
183
+ // Accept multiple JSON shapes:
184
+ // - array: [ { username, message, ... }, ... ]
185
+ // - object: { total, page, results: [ ... ] }
186
+ // - object error: { error, message }
187
+ let items = [];
188
+ if (Array.isArray(data)) {
189
+ items = data;
190
+ } else if (data && Array.isArray(data.results)) {
191
+ items = data.results;
192
+ } else if (data && data.error) {
193
+ negativeBox.innerHTML = `<pre style="color:salmon">${escapeHtml(JSON.stringify(data, null, 2))}</pre>`;
194
+ console.warn("Negatives returned error object:", data);
195
+ return;
196
+ } else {
197
+ negativeBox.innerHTML = `<pre style="color:salmon">Unexpected response shape:\n${escapeHtml(JSON.stringify(data).slice(0,2000))}</pre>`;
198
+ console.error("Negatives unexpected JSON:", data);
199
+ return;
200
+ }
201
+
202
+ if (!items.length) {
203
+ negativeBox.innerHTML = "<p>No negative messages yet.</p>";
204
+ return;
205
+ }
206
+
207
+ // render items safely (escape HTML)
208
+ negativeBox.innerHTML = items.map(msg => {
209
+ const username = escapeHtml(msg.username || "Unknown");
210
+ const created = escapeHtml(msg.created_at || "");
211
+ const category = escapeHtml(msg.category || "");
212
+ const message = escapeHtml(msg.message || "");
213
+ return `
214
+ <div class="neg-item">
215
+ <div class="neg-meta"><strong>${username}</strong> ${created ? `— <small>${created}</small>` : ""} ${category ? `| <em>${category}</em>` : ""}</div>
216
+ <div class="neg-message">${message}</div>
217
+ </div>
218
+ `;
219
+ }).join("");
220
+
221
+ } catch (err) {
222
+ console.error("updateNegatives error:", err);
223
+ negativeBox.innerHTML = `<pre style="color:salmon">Error loading negatives: ${escapeHtml(err.message || String(err))}</pre>`;
224
+ }
225
  }
226
 
227
+ // ---------- Utilities ----------
228
+ function escapeHtml(str) {
229
+ return String(str || "").replace(/[&<>"'`=\/]/g, function (s) {
230
+ return {
231
+ "&": "&amp;",
232
+ "<": "&lt;",
233
+ ">": "&gt;",
234
+ '"': "&quot;",
235
+ "'": "&#39;",
236
+ "/": "&#x2F;",
237
+ "`": "&#x60;",
238
+ "=": "&#x3D;"
239
+ }[s];
240
+ });
241
+ }
242
+
243
+ // ---------- Startup: initial loads & polling ----------
244
+ document.addEventListener("DOMContentLoaded", () => {
245
+ // initial analytics load
246
+ updateAnalytics().catch(() => {});
247
+
248
+ // if negativeBox exists, load and set interval
249
+ if (negativeBox) {
250
+ updateNegatives().catch(() => {});
251
+ // poll every 8 seconds
252
+ setInterval(() => {
253
+ updateNegatives().catch(() => {});
254
+ }, 8000);
255
+ }
256
+ });