From efbc330e2de3cc0eb652a65eda7f7a0ff84e81cf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Konrad=20V=C3=B6lkel?= <konrad.voelkel@hhu.de>
Date: Fri, 28 Feb 2025 09:03:59 +0100
Subject: [PATCH] another fix

---
 chatbox.html               |   6 +--
 index.js                   |  57 +++++++++++++++------
 modules/feedback.js        |  19 ++++---
 modules/ui.js              | 100 +++++++++++++++++++++++++++----------
 modules/workerInterface.js |  18 +++++--
 styles.css                 |  84 +++++++++++++++++++++++++++----
 worker.js                  |   2 +-
 7 files changed, 217 insertions(+), 69 deletions(-)

diff --git a/chatbox.html b/chatbox.html
index 8ae6a06..143d69b 100644
--- a/chatbox.html
+++ b/chatbox.html
@@ -15,12 +15,12 @@
 
 <body>
   <div id="chatbox">
-    <div id="chatbox-title">HHU Search Helper</div>
+    <div id="chatbox-title">Ask hAInrich</div>
     <div id="messages"></div>
     <div id="messageInfo"></div>
     <div id="input-container">
-      <input type="text" id="user-input" placeholder="Try searching: 'minimum admission grade' or 'I'm from outside the EU'">
-      <button onclick="saveMessage()">Send</button>
+      <input type="text" id="user-input" placeholder="Try asking: 'minimum admission grade' or 'I'm from outside the EU'">
+      <button onclick="sendAsk()">Ask</button>
     </div>
   </div>
 
diff --git a/index.js b/index.js
index bf1a78e..171c923 100644
--- a/index.js
+++ b/index.js
@@ -1,4 +1,4 @@
-import { sendMessage, copyToClipboard } from './modules/ui.js';
+import { sendMessage } from './modules/ui.js';
 import { tokenizeText } from './modules/semanticSearch.js';
 import { extractFAQfromHTML } from './modules/faqData.js';
 import { loadEmbedding, removeOutdatedEntries } from './modules/embeddings.js'
@@ -38,10 +38,10 @@ localStorage.setItem('smallEmbedding', JSON.stringify(tokenEmbeddingStack));
 const inputElement = document.getElementById('user-input');
 const resultsIframe = parent.document.getElementById('resultsIframe');
 
-let content = "";
-
+let topResult = null;
 inputElement.addEventListener('input', function (event) {
-  window.focus();
+  
+  
   const query = event.target.value.trim();
   
   const results = resultsIframe.contentDocument || resultsIframe.contentWindow.document;
@@ -55,11 +55,13 @@ inputElement.addEventListener('input', function (event) {
     }, 500);
     return;
   }
-  let resultLinks;
+
   resultsIframe.style.display = 'block';
   resultsContainer.innerHTML = '';
+
   
   processQuery(query, worker, function (outputs) {
+
     window.focus();
     outputs.forEach(async output => {
   
@@ -87,37 +89,60 @@ inputElement.addEventListener('input', function (event) {
       resultLink.appendChild(bestTokenDiv);
 
       window.focus();
+      /** open the answer field in chat view for selected panel */
+      console.log("inside", output.question);
       resultLink.onclick = function () {
-        window.focus();
-        copyToClipboard(output.question);
-
+        linkAndClick(output.question);
       };
       
       resultsContainer.appendChild(resultLink);
     });
-  });
-});
-
 
+    if (outputs.length > 0) {
+      topResult = outputs[0];
+    }
 
+  });
+});
 /** search for query on enter press and put it in chat */
 document.getElementById('user-input').addEventListener('keypress', function (event) {
   if (event.key === 'Enter') {
     event.preventDefault();
-    sendMessage(worker);
-    
+    if (topResult) {
+      linkAndClick(topResult.question);
+    }
   }
 });
 
 
-worker.onmessage = (events) => handleWorker(events);
+export function linkAndClick(output) {
+  saveMessage();
+
+  setTimeout(() => {
+    const encodedQuestion = encodeURIComponent(output);
+    const panels = document.querySelectorAll(
+      `.info-panel[data-question="${encodedQuestion}"]`
+    );
+    if (panels.length > 0) {
+      panels[panels.length - 1].click();
+    }
+  }, 250);
+}
 
+worker.onmessage = (events) => handleWorker(events);
 
-/** show results in chat window if "send" is clicked */
+/** show results in chat window if "ask" is clicked */
 function saveMessage(){
   return sendMessage(worker);
 }
-window.saveMessage = saveMessage;
+
+function sendAsk(){
+  if (topResult) {
+    linkAndClick(topResult.question);
+  }
+}
+
+window.sendAsk = sendAsk;
 
 /** give worker work */
 worker.postMessage({ act: 'initialize', textData, embeddingStack, tokenEmbeddingStack });
diff --git a/modules/feedback.js b/modules/feedback.js
index 44cf2d4..a7b54eb 100644
--- a/modules/feedback.js
+++ b/modules/feedback.js
@@ -2,7 +2,7 @@
 
 
 /** open mail client of user with prewritten message and stats */
-export function submitFeedback(query, bestToken, feedback) {
+export function submitFeedback(query, bestAnswers, feedback) {
 
   /** temporary mail adress, adjust if needed */
   const feedbackAdress = 'huepfen-richtlinie.7r@icloud.com';
@@ -10,22 +10,27 @@ export function submitFeedback(query, bestToken, feedback) {
   let subject = '';
   let message = '';
 
+  const bestAnswersAsString = Object.entries(bestAnswers)
+  .map(([question, token]) => `> Question: ${question} \n > Answer: ${token}\n`)
+  .join("\n\n");
+
 
   if (feedback === 'up') {
     subject = 'Positive Feedback: FAQ Search Feature';
     message = `Hello,
-    \nI used your FAQ search feature and was pleased with the result. Details below:
-    \nQuery: ${query}\nBest Tokens: ${bestToken}\nFeedback: ${feedback}\nTimestamp: ${timestamp}
-    \nI also have some additional suggestions: 
+    \nI used your FAQ search feature and was pleased with the result. 
+    \nI also have some additional suggestions: ...
+    \nBest Tokens:\n\n${bestAnswersAsString}\nFeedback: ${feedback}\nTimestamp: ${timestamp}\nQuery: ${query}
     \nKeep it up!
     \nBest regards`;
 
   } else if (feedback === 'down') {
     subject = 'Negative Feedback: FAQ Search Feature';
     message = `Hello,
-    \nI tried used FAQ search feature, and unfortunately, I was not satisfied with this result. Detail below:
-    \nQuery: ${query}\nBest Tokens: ${bestToken}\nFeedback: ${feedback}\nTimestamp: ${timestamp}
-    \nI also have some additional suggestions: 
+    \nI tried used FAQ search feature, and unfortunately, I was not satisfied with this result. 
+    \nI also have some additional suggestions: ...
+    Detail below:
+    \nBest Tokens: ${bestAnswersAsString}\nFeedback: ${feedback}\nTimestamp: ${timestamp}\nQuery: ${query}
     \nI hope this feedback lets you improve the experience.
     \nBest regards`;
   }
diff --git a/modules/ui.js b/modules/ui.js
index a1ca671..42907bc 100644
--- a/modules/ui.js
+++ b/modules/ui.js
@@ -1,21 +1,17 @@
 import { submitFeedback } from "./feedback.js";
 
 /** handle what needs to be displayed in chat container */
-export function appendMessage(content, className, linkId = null, akkordeonId = null, bestToken = null, query = null) {
+export function appendMessage(content, className, linkId = null, akkordeonId = null, bestToken = null, query = null, answer = null) {
   const messageContainer = document.createElement('div');
   
   if (className === 'user-message') {
-
     const messageDiv = document.createElement('div');
     messageDiv.textContent = content;
     messageDiv.className = className;
     messageContainer.appendChild(messageDiv);
 
-    
-
   } else {
 
-    /** drawing what chat response looks like */
     messageContainer.className = 'bot-message-container';
 
     const numberBox = document.createElement('div');
@@ -29,18 +25,42 @@ export function appendMessage(content, className, linkId = null, akkordeonId = n
     const panelDiv = document.createElement('div');
     panelDiv.textContent = '➜';
     panelDiv.className = 'info-panel';
-    /** now the user can copy the relevant question to clipboard and use the browser search */
-    panelDiv.onclick = function () {
+
+    panelDiv.setAttribute('data-question', encodeURIComponent(content));
+    
+    messageDiv.onclick = function () {
       copyToClipboard(content);
     };
     
-    messageContainer.appendChild(numberBox);
-    messageContainer.appendChild(messageDiv);
-    messageContainer.appendChild(panelDiv);
-  }
-  
-  
+    panelDiv.onclick = function (event) {
+
+      event.stopPropagation();
+
+      let nextElem = messageContainer.nextElementSibling;
+      if (nextElem && nextElem.classList.contains('answer-container')) {
+        nextElem.remove();
+        panelDiv.classList.remove('active');
+        return;
+      }
+      
+      let highlightToken = answer.replace(bestToken, `<span class="highlight">${bestToken}</span>`);
+      
+      const answerContainer = document.createElement('div');
+      answerContainer.className = 'answer-container';
+      
+      answerContainer.innerHTML = highlightToken;
+      
+      messageContainer.parentNode.insertBefore(answerContainer, messageContainer.nextSibling);
+      panelDiv.classList.add('active');
+    }
+
+
+  messageContainer.appendChild(numberBox);
+  messageContainer.appendChild(messageDiv);
+  messageContainer.appendChild(panelDiv);
   
+  }
+
   const messagesDiv = document.getElementById('messages');
   messagesDiv.appendChild(messageContainer);
   messagesDiv.scrollTop = messagesDiv.scrollHeight;
@@ -52,6 +72,19 @@ export function sendMessage(worker) {
 
   const userInput = document.getElementById('user-input');
   const message = userInput.value.trim();
+  
+  const resultsIframe = parent.document.getElementById('resultsIframe');
+  const results = resultsIframe.contentDocument || resultsIframe.contentWindow.document;
+  const resultsContainer = results.getElementById('results');
+  
+  /** hide results when message is sent */
+  resultsContainer.innerHTML = '';
+  setTimeout(() => {
+      resultsIframe.style.display = 'none';
+    }, 500);
+    
+  resultsIframe.style.display = 'block';
+  resultsContainer.innerHTML = '';
 
 
   if (message !== "") {
@@ -77,21 +110,34 @@ export function copyToClipboard(text) {
 }
 
 /** show user that clipboard action was successful */
-export function copySuccsess() {
+let currentConfirm = null;
+let showTimeoutId = null;
+let hideTimeoutId = null;
+let removeTimeoutId = null;
+
+function copySuccsess() {
   
-  const confirm = document.createElement('div');
-  confirm.innerText = 'Copied to clipboard!';
-  confirm.classList.add('confirm');
-  document.body.appendChild(confirm);
+  if (currentConfirm) {
+    clearTimeout(showTimeoutId);
+    clearTimeout(hideTimeoutId);
+    clearTimeout(removeTimeoutId);
+    currentConfirm.remove();
+  }
 
-  setTimeout(() => {
-    confirm.classList.add('visible');
+  currentConfirm = document.createElement('div');
+  currentConfirm.innerText = 'Question copied to clipboard!';
+  currentConfirm.classList.add('confirm');
+  document.body.appendChild(currentConfirm);
+
+  showTimeoutId = setTimeout(() => {
+    currentConfirm.classList.add('visible');
   }, 10);
 
-  setTimeout(() => {
-    confirm.classList.remove('visible');
-    setTimeout(() => {
-      confirm.remove();
+  hideTimeoutId = setTimeout(() => {
+    currentConfirm.classList.remove('visible');
+    removeTimeoutId = setTimeout(() => {
+      currentConfirm.remove();
+      currentConfirm = null;
     }, 500);
   }, 2000);
 }
@@ -132,13 +178,13 @@ export function drawUserInfo() {
   const messagesDiv = document.getElementById('messages');
   const infoDiv = document.createElement('div');
 
-  infoDiv.className = 'bot-message';
-  infoDiv.innerHTML = "These question panels might be useful to you:<br>";
+  infoDiv.className = 'bot-intro';
+  infoDiv.innerHTML = "This might be useful:<br>";
 
   messagesDiv.appendChild(infoDiv);
 }
 
-/** new message to inform user of the limited usability */
+/** new message to inform user of the limited usability; out for now*/
 export function discloseLimitations() {
 
   const messagesDiv = document.getElementById('messages');
diff --git a/modules/workerInterface.js b/modules/workerInterface.js
index 1cb6abd..147917c 100644
--- a/modules/workerInterface.js
+++ b/modules/workerInterface.js
@@ -23,10 +23,17 @@ export function handleWorker(events){
         case 'searchResults':
 
           const bestTokens = results.map(result => result.bestToken);
+          const questions = results.map(result => result.question);
+          const bestAnswer = {};
 
-          drawFeedbackButtons(query, bestTokens);
+          for (let i = 0; i < questions.length; i++) {
+            bestAnswer[questions[i]] = bestTokens[i];
+          }
+    
+          drawFeedbackButtons(query, bestAnswer);
           drawUserInfo();
 
+
           /** pass our three results to ui so they can be displayed and worked with in chat */
           results.forEach(result => {
             appendMessage(
@@ -35,11 +42,12 @@ export function handleWorker(events){
               result.buttonId,
               result.akkordeonId,
               result.bestToken,
-              query
+              query,
+              result.answer
             );
           });
 
-          discloseLimitations();
+          /**discloseLimitations();*/
 
           break;
     
@@ -75,12 +83,12 @@ function exportAsJson(data, fileName) {
 
   const jsonString = JSON.stringify(data, null, 2); 
   const blob = new Blob([jsonString], { type: 'application/json' }); 
-  const url = URL.createObjectURL(blob);
+  const url = URL.createbestAnswerectURL(blob);
   
   const link = document.createElement('a');
   link.href = url;
   link.download = fileName;
   
   link.click();
-  URL.revokeObjectURL(url);
+  URL.revokebestAnswerectURL(url);
 }
\ No newline at end of file
diff --git a/styles.css b/styles.css
index 255f9a1..9ec13c6 100644
--- a/styles.css
+++ b/styles.css
@@ -9,9 +9,9 @@
             width: 100%;
             max-height: 400px;
             overflow-x: hidden;
-            overflow-y: auto;
             display: flex;
             flex-direction: column;
+            overflow-y: hidden;
         }
         
 
@@ -73,8 +73,6 @@
         }
 
         
-
-
         .user-message {
             align-self: flex-end;
             background-color: #007bff;
@@ -87,18 +85,41 @@
             text-align: center;
         }
         
-        .bot-message {
+        
+
+        .answer-container {
+            white-space: pre-wrap;
             align-self: flex-start;
             background-color: #003865;
             color: white;
-            margin-left: 10px;
+            margin-left: 52px;
             margin: 10px 2;
             max-width: 60%;
             word-wrap: break-word;
-            text-align: middle;
+            cursor: default;
+            opacity: 0;
+            animation: showUp 0.3s forwards;
+
+        }
+
+        .highlight {
+            background-color: rgb(59, 136, 172);
+            color: white; /* Adjust as needed for contrast */
+        }
+          
+        @keyframes showUp {
+            from {
+              opacity: 0;
+              transform: translateY(-10px);
+            }
+            to {
+              opacity: 1;
+              transform: translateY(0);
+            }
         }
 
 
+
         .info-for-user {
             position: relative;
             margin-right: auto;
@@ -112,21 +133,57 @@
             display: inline-flex;
             align-items: center;
             height: auto;
-            position: relative;
             gap: 20px;
             padding: 50px;
             border-radius: 4px;
             font-size: 14px;
             max-width: 50%;   
-
             vertical-align: top;
             margin-right: 20px;
             
         }
 
+        .bot-message {
+            align-self: flex-start;
+            background-color: #003865;
+            color: white;
+            margin-left: 10px;
+            margin: 10px 2;
+            max-width: 60%;
+            word-wrap: break-word;
+            text-align: middle;
+            z-index: 100;
+            cursor: pointer;
+            
+        }
+
+        .bot-intro {
+            align-self: flex-start;
+            background-color: #003865;
+            color: white;
+            margin-left: 10px;
+            margin: 10px 2;
+            max-width: 60%;
+            word-wrap: break-word;
+            text-align: middle;
+            
+        }
+
+
+
         .bot-message-container {
              display: flex;
              align-items: stretch;
+             
+             
+        }
+
+        .bot-message:hover {
+            background-color: #002144;
+        }
+
+        .bot-message:active {
+            transform: scale(1.03);
         }
 
 
@@ -148,7 +205,7 @@
         }
 
         .info-panel::before {
-            content: "Copy to Clipboard";
+            content: "show full answer";
             position: absolute;
             background-color: rgba(0, 0, 0, 0.8);
             color: white;
@@ -163,13 +220,20 @@
             left: 50px;
             max-width: 300px;
         }
+
+        .info-panel.active::before {
+            content: "hide answer";
+        }
         
         .info-panel:hover::before {
             opacity: 1;
             visibility: visible;
         }
 
-
+        .info-panel:active {
+            transform: scale(1.03);
+        }
+        
         .number-box {
             background-color: #aeb5bc;
             color: white;
diff --git a/worker.js b/worker.js
index 08d5f91..9930d39 100644
--- a/worker.js
+++ b/worker.js
@@ -56,7 +56,7 @@ async function getResultsWithTokens(faqResults, query) {
   return await Promise.all(
     faqResults.map(async (result) => {
       const { bestToken } = await findBestToken(query, result.answer);
-      return { ...result, bestToken };
+      return { ...result, bestToken};
     })
   );
 }
-- 
GitLab