driver = {
picocss;
autoAnimate(chat);
ai;
const control = htl.html`<div>
<div id="chat-history"></div>
<footer>
<form id="chat-input">
<label for="user-input" hidden>Enter your message:</label>
<textarea id="user-input" rows="5" cols="30" placeholder="Type your message here"></textarea>
<button type="submit">Send</button>
</form>
</footer>`;
const form = control.querySelector("#chat-input");
const input = control.querySelector("#user-input");
const history = control.querySelector("#chat-history");
const submitButton = control.querySelector("button");
autoAnimate(control);
autoAnimate(history);
if (chat.firstElementChild !== control) {
chat.firstElementChild.replaceWith(control);
}
input.onkeydown = (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
submitButton.click();
}
};
form.onsubmit = async (e) => {
e.preventDefault();
toggleForm(form);
const userText = input.value;
input.value = "";
const userMessageElement = makeChatHistoryElement("user", userText);
history.appendChild(userMessageElement);
(
userMessageElement.previousElementSibling || userMessageElement
).scrollIntoView({ behavior: "smooth" });
try {
// TODO: send the input text to the backend
//await Promises.delay(1000);
const completion = await ai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: getChatHistoryData()
});
console.log(completion);
const message = completion.data?.choices[0]?.message;
if (message) {
const responseElement = makeChatHistoryHtmlElement(
message.role,
message.content
);
console.log(responseElement.outerHTML);
await addChatHistoryHtmlMessage(responseElement);
}
} finally {
toggleForm(form);
input.focus();
(
userMessageElement.previousElementSibling || userMessageElement
).scrollIntoView({ behavior: "smooth" });
}
};
function makeChatHistoryElement(role, content) {
if (role === "user") content += "\n\n";
return htl.html`<p data-chatRole="${role}" data-chatContent=${content}>${(
content || ""
).trim()}</p>`;
}
function getChatHistoryData() {
return [...history.querySelectorAll("p[data-chatRole]")].map((element) => ({
role: element.getAttribute("data-chatRole"),
content: element.getAttribute("data-chatContent")
}));
}
async function addChatHistoryMessage(chatHistoryElement) {
// Clone chatHistoryElement
const clonedElement = chatHistoryElement.cloneNode(true);
// Split content into words and wrap each in a span
const content = clonedElement.getAttribute("data-chatContent") || "";
const words = content.trim().split(" ");
// Clear original content
clonedElement.textContent = "";
// Animate and append to history
autoAnimate(clonedElement);
history.appendChild(clonedElement);
// Append each word as a child span
for (const word of words) {
await Promises.delay(10);
const span = document.createElement("span");
span.textContent = word;
clonedElement.appendChild(span);
clonedElement.appendChild(document.createTextNode(" "));
}
}
function makeChatHistoryHtmlElement(role, content) {
if (role === "user") content += "\n\n";
return htl.html`<p data-chatRole="${role}" data-chatContent=${content}>${md`${
content || ""
}`}</p>`;
}
async function addChatHistoryHtmlMessage(chatHistoryElement) {
const clonedElement = chatHistoryElement.cloneNode(true);
clonedElement.innerHTML = "";
autoAnimate(clonedElement);
history.appendChild(clonedElement);
await appendChildrenWithDelay(chatHistoryElement, clonedElement);
}
async function appendChildrenWithDelay(sourceElement, targetElement) {
for (const child of sourceElement.children) {
await Promises.delay(10);
const clonedElement = child.cloneNode(true);
clonedElement.innerHTML = "";
autoAnimate(clonedElement);
targetElement.appendChild(clonedElement);
if (child.children.length > 0) {
await appendChildrenWithDelay(child, clonedElement);
}
}
}
}