class AIChatChat extends HTMLElement { #root = null; #controls = null; #input = null; #output = null; #progress = null; #history = []; constructor() { super(); this.#root = this.attachShadow({mode: 'open'}); this.#root.innerHTML = `
`; this.#root.appendChild(this.getStyle()); this.#input = this.#root.querySelector('textarea'); this.#output = this.#root.querySelector('.output'); this.#progress = this.#root.querySelector('progress'); this.#controls = this.#root.querySelector('.controls'); const form = this.#root.querySelector('form'); form.addEventListener('submit', this.onSubmit.bind(this)); const restart = this.#root.querySelector('.delete-history'); restart.addEventListener('click', this.deleteHistory.bind(this)); this.#input.addEventListener('keydown', (event) => { if (event.key === 'Enter' && !event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey) { event.preventDefault(); form.dispatchEvent(new Event('submit')); } }); } /** * Called when the DOM has been connected * * We initialize the attribute based states here */ connectedCallback() { this.#input.placeholder = this.getAttribute('placeholder') || 'Your question...'; this.displayMessage(this.getAttribute('hello') || 'Hello, how can I help you?', {}); // make title attributes translatable for (const elem of this.#root.querySelectorAll('[title]')) { elem.title = this.getAttribute('title-'+elem.title) || elem.title; } this.restoreHistory(); this.stopProgress(); // initializes the visibility states } /** * Define the web component's internal styles * * @returns {HTMLStyleElement} */ getStyle() { const style = document.createElement('style'); style.textContent = ` :host { --color-human: #ebd8b2; --color-ai: #c6dbf2; --color-link: #4881bf; display: flex; flex-direction: column; height: 100%; justify-content: space-between; } * { box-sizing: border-box; font-family: sans-serif; } form { clear: both; margin-bottom: 1em; } .controls { width: 100%; position: relative; } .controls button { padding: 0; background: none; border: none; cursor: pointer; display: flex; width: 2.5em; position: absolute; bottom: 2px; z-index: 1; } .controls button.delete-history { left: 5px; } .controls button.send { right: 5px; } .controls button svg { flex-grow: 1; flex-shrink: 1; fill: var(--color-link); } .controls textarea { width: 100%; padding: 0.25em 3em; font-size: 1.2em; height: 4em; border: none; resize: vertical; } .controls textarea:focus { outline: none; } progress{ width: 100%; } progress { color: var(--color-link); accent-color: var(--color-link); } a { color: var(--color-link); } .output > div { border-radius: 0.25em; clear: both; padding: 0.5em 1em; position: relative; margin-bottom: 1em; } .output > div::before { content: ""; width: 0px; height: 0px; position: absolute; top: 0; } .output > div.user { background-color: var(--color-human); float: right; margin-right: 2em; border-top-right-radius: 0; } .output > div.user::before { right: -1em; border-left: 0.5em solid var(--color-human); border-right: 0.5em solid transparent; border-top: 0.5em solid var(--color-human); border-bottom: 0.5em solid transparent; } .output > div.ai { background-color: var(--color-ai); float: left; margin-left: 2em; border-top-left-radius: 0; } .output > div.ai::before { left: -1em; border-left: 0.5em solid transparent; border-right: 0.5em solid var(--color-ai); border-top: 0.5em solid var(--color-ai); border-bottom: 0.5em solid transparent; } `; return style; } /** * Save history to session storage */ saveHistory() { sessionStorage.setItem('ai-chat-history', JSON.stringify(this.#history)); } /** * Load the history from session storage and display it */ restoreHistory() { const history = sessionStorage.getItem('ai-chat-history'); if (history) { this.#history = JSON.parse(history); this.#history.forEach(row => { this.displayMessage(row[0]); this.displayMessage(row[1], row[2]); }); } } deleteHistory() { sessionStorage.removeItem('ai-chat-history'); this.#history = []; this.#output.innerHTML = ''; this.connectedCallback(); // re-initialize } /** * Submit the given question to the server * * @param event * @returns {Promise