Skip to content

Commit

Permalink
Tidy-up web-component JavaScript files
Browse files Browse the repository at this point in the history
  • Loading branch information
KevinEtchells committed Jul 18, 2024
1 parent 988af7b commit c6c18f7
Show file tree
Hide file tree
Showing 19 changed files with 242 additions and 248 deletions.
19 changes: 6 additions & 13 deletions django_app/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@
"description": "Frontend dependencies for i.AI Redbox application",
"source": [
"src/styles.scss",
"src/js/documents.js",
"src/js/trusted-types.js",
"src/js/autosubmit.js",
"src/js/posthog.js",
"src/js/chats/feedback.js",
"src/js/chats/markdown.js",
"src/js/chats/message-input.js",
"src/js/chats/streaming.js",
"src/js/chats/chat-title.js"
"src/js/chats.js",
"src/js/citations.js",
"src/js/documents.js",
"src/js/main.js",
"src/js/posthog.js"
],
"staticFiles": [
{
Expand All @@ -34,10 +31,6 @@
{
"distDir": "./dist/js/libs",
"staticPath": "./src/js/libs"
},
{
"distDir": "./dist/js/libs",
"staticPath": "./node_modules/i.ai-design-system/dist/iai-design-system.js"
}
],
"scripts": {
Expand All @@ -47,7 +40,7 @@
"author": "",
"dependencies": {
"govuk-frontend": "^5.2.0",
"i.ai-design-system": "^0.3.12",
"i.ai-design-system": "^0.4.0",
"posthog-js": "^1.143.0"
},
"devDependencies": {
Expand Down
14 changes: 14 additions & 0 deletions django_app/frontend/src/js/chats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import "./web-components/chats/chat-controller.js";
import "./web-components/chats/chat-message.js";
import "./web-components/chats/chat-title.js";
import "./web-components/chats/document-selector.js";
import "./web-components/chats/feedback-buttons.js";
import "./web-components/markdown-converter.js";
import "./web-components/chats/message-input.js";
import "./web-components/chats/sources-list.js";

// Update URL when a new chat is created
document.addEventListener("chat-response-end", (evt) => {
const sessionId = /** @type{CustomEvent} */ (evt).detail.session_id;
window.history.pushState({}, "", `/chats/${sessionId}`);
});
28 changes: 0 additions & 28 deletions django_app/frontend/src/js/chats/markdown.js

This file was deleted.

1 change: 1 addition & 0 deletions django_app/frontend/src/js/citations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "./web-components/markdown-converter.js";
52 changes: 2 additions & 50 deletions django_app/frontend/src/js/documents.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,2 @@
// @ts-check


class FileStatus extends HTMLElement {

connectedCallback() {

const checkStatus = async () => {

// UPDATE THESE AS REQUIRED
const FILE_STATUS_ENDPOINT = '/file-status';
const CHECK_INTERVAL_MS = 5000;

// as we use different layouts for mobile and desktop - only request update if visible
if (!this.checkVisibility()) {
window.setTimeout(checkStatus, CHECK_INTERVAL_MS);
return;
}

const response = await fetch(`${FILE_STATUS_ENDPOINT}?id=${this.dataset.id}`);
const responseObj = await response.json();
this.textContent = responseObj.status;
if (responseObj.status.toLowerCase() === 'complete') {
const evt = new CustomEvent('doc-complete', { detail: this.closest('.iai-doc-list__item') });
document.body.dispatchEvent(evt);
} else {
window.setTimeout(checkStatus, CHECK_INTERVAL_MS);
}

};

checkStatus();

}

}
customElements.define('file-status', FileStatus);



/** So completed docs can be added to this list */
class DocList extends HTMLElement {
connectedCallback() {
document.body.addEventListener('doc-complete', (evt) => {
const completedDoc = /** @type{CustomEvent} */(evt).detail;
this.querySelector('tbody')?.appendChild(completedDoc);
});
}
}
customElements.define('doc-list', DocList);
import "./web-components/documents/doc-list.js";
import "./web-components/documents/file-status.js";
3 changes: 3 additions & 0 deletions django_app/frontend/src/js/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import "./trusted-types.js";
import "./libs/govuk-frontend.min.js";
import "../../node_modules/i.ai-design-system/dist/iai-design-system.js";
56 changes: 56 additions & 0 deletions django_app/frontend/src/js/web-components/chats/chat-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// @ts-check

class ChatController extends HTMLElement {
connectedCallback() {
const messageForm = this.closest("form");
const textArea = /** @type {HTMLInputElement | null} */ (
this.querySelector(".js-user-text")
);
const messageContainer = this.querySelector(".js-message-container");
const insertPosition = this.querySelector(".js-response-feedback");
const feedbackButtons = /** @type {HTMLElement | null} */ (
this.querySelector("feedback-buttons")
);
let selectedDocuments = [];

messageForm?.addEventListener("submit", (evt) => {
evt.preventDefault();
const userText = textArea?.value.trim();
if (!textArea || !userText) {
return;
}

let userMessage = document.createElement("chat-message");
userMessage.setAttribute("data-text", userText);
userMessage.setAttribute("data-role", "user");
messageContainer?.insertBefore(userMessage, insertPosition);

let aiMessage = /** @type {ChatMessage} */ (
document.createElement("chat-message")
);
aiMessage.setAttribute("data-role", "ai");
messageContainer?.insertBefore(aiMessage, insertPosition);
aiMessage.stream(
userText,
selectedDocuments,
this.dataset.sessionId,
this.dataset.streamUrl || "",
this
);
/** @type {HTMLElement | null} */ (
aiMessage.querySelector(".iai-chat-bubble")
)?.focus();

// reset UI
if (feedbackButtons) {
feedbackButtons.dataset.status = "";
}
textArea.value = "";
});

document.body.addEventListener("selected-docs-change", (evt) => {
selectedDocuments = /** @type{CustomEvent} */ (evt).detail;
});
}
}
customElements.define("chat-controller", ChatController);
Original file line number Diff line number Diff line change
@@ -1,59 +1,5 @@
// @ts-check

class SourcesList extends HTMLElement {
constructor() {
super();
this.sources = [];
}

/**
* Adds a source to the current message
* @param {string} fileName
* @param {string} url
*/
add = (fileName, url) => {
this.sources.push({
fileName: fileName,
url: url,
});

let html = `
<h3 class="iai-chat-bubble__sources-heading govuk-heading-s govuk-!-margin-bottom-1">Sources</h3>
<div class="iai-display-flex-from-desktop">
<ul class="govuk-list govuk-list--bullet govuk-!-margin-bottom-0">
`;
this.sources.forEach((source) => {
html += `
<li class="govuk-!-margin-bottom-0">
<a class="iai-chat-bubbles__sources-link govuk-link" href="${source.url}">${source.fileName}</a>
</li>
`;
});
html += `</div></ul>`;

this.innerHTML = html;
};

/**
* Shows to citations link/button
* @param {string} chatId
*/
showCitations = (chatId) => {
if (!chatId) {
return;
}
let link = document.createElement("a");
link.classList.add(
"iai-chat-bubble__citations-button",
"govuk-!-margin-left-2"
);
link.setAttribute("href", `/citations/${chatId}`);
link.textContent = "View information behind this answer";
this.querySelector(".iai-display-flex-from-desktop")?.appendChild(link);
};
}
customElements.define("sources-list", SourcesList);

class ChatMessage extends HTMLElement {
connectedCallback() {
const uuid = crypto.randomUUID();
Expand Down Expand Up @@ -229,94 +175,3 @@ class ChatMessage extends HTMLElement {
};
}
customElements.define("chat-message", ChatMessage);

class ChatController extends HTMLElement {
connectedCallback() {
const messageForm = this.closest("form");
const textArea = /** @type {HTMLInputElement | null} */ (
this.querySelector(".js-user-text")
);
const messageContainer = this.querySelector(".js-message-container");
const insertPosition = this.querySelector(".js-response-feedback");
const feedbackButtons = /** @type {HTMLElement | null} */ (
this.querySelector("feedback-buttons")
);
let selectedDocuments = [];

messageForm?.addEventListener("submit", (evt) => {
evt.preventDefault();
const userText = textArea?.value.trim();
if (!textArea || !userText) {
return;
}

let userMessage = document.createElement("chat-message");
userMessage.setAttribute("data-text", userText);
userMessage.setAttribute("data-role", "user");
messageContainer?.insertBefore(userMessage, insertPosition);

let aiMessage = /** @type {ChatMessage} */ (
document.createElement("chat-message")
);
aiMessage.setAttribute("data-role", "ai");
messageContainer?.insertBefore(aiMessage, insertPosition);
aiMessage.stream(
userText,
selectedDocuments,
this.dataset.sessionId,
this.dataset.streamUrl || "",
this
);
/** @type {HTMLElement | null} */ (
aiMessage.querySelector(".iai-chat-bubble")
)?.focus();

// reset UI
if (feedbackButtons) {
feedbackButtons.dataset.status = "";
}
textArea.value = "";
});

document.body.addEventListener("selected-docs-change", (evt) => {
selectedDocuments = /** @type{CustomEvent} */ (evt).detail;
});
}
}
customElements.define("chat-controller", ChatController);

class DocumentSelector extends HTMLElement {
connectedCallback() {
const documents = /** @type {NodeListOf<HTMLInputElement>} */ (
this.querySelectorAll('input[type="checkbox"]')
);

const getSelectedDocuments = () => {
let selectedDocuments = [];
documents.forEach((document) => {
if (document.checked) {
selectedDocuments.push(document.value);
}
});
const evt = new CustomEvent("selected-docs-change", {
detail: selectedDocuments,
});
document.body.dispatchEvent(evt);
};

// update on page load
getSelectedDocuments();

// update on any selection change
documents.forEach((document) => {
document.addEventListener("change", getSelectedDocuments);
});
}
}
customElements.define("document-selector", DocumentSelector);

// Update URL when a new chat is created
document.addEventListener("chat-response-end", (evt) => {
const sessionId = /** @type{CustomEvent} */ (evt).detail.session_id;
window.history.pushState({}, "", `/chats/${sessionId}`);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// @ts-check

class DocumentSelector extends HTMLElement {
connectedCallback() {
const documents = /** @type {NodeListOf<HTMLInputElement>} */ (
this.querySelectorAll('input[type="checkbox"]')
);

const getSelectedDocuments = () => {
let selectedDocuments = [];
documents.forEach((document) => {
if (document.checked) {
selectedDocuments.push(document.value);
}
});
const evt = new CustomEvent("selected-docs-change", {
detail: selectedDocuments,
});
document.body.dispatchEvent(evt);
};

// update on page load
getSelectedDocuments();

// update on any selection change
documents.forEach((document) => {
document.addEventListener("change", getSelectedDocuments);
});
}
}
customElements.define("document-selector", DocumentSelector);
Loading

0 comments on commit c6c18f7

Please sign in to comment.