Files
Krzak.org/ai-chat.js
2026-02-24 18:26:38 +01:00

193 lines
5.2 KiB
JavaScript

(() => {
const introMessage =
"Witaj, jestem twoim wirtualnym agentem Krzak.org, w czym mogę Ci pomóc?";
const baseReplies = [
"Niestety, nie umiem jeszcze tego zrobić",
"Nie jestem pewien o co Ci chodzi",
];
const offerFrom = [
"pedałów",
"pajaca",
"noży",
"gwoździ",
"przypraw",
"słoika",
"słoików",
"tabletek",
"spreju",
"narzędzi",
"samolotu",
"ostrzałki",
"szafki",
"pojemnika",
"pojemników",
"ładowarki",
"ładowarek",
"wieszaków",
"wieszaka",
"półki",
"półek",
"ryby",
"migdałów",
"baterii",
"kościanych guzików",
];
const offerTo = [
"gwoździ",
"roweru",
"krojenia cebuli",
"pedałów",
"latania",
"łatania",
"podatków",
"sprzątania",
"wyrywania zębów",
"zmywarki",
"śmieci",
"krojenia chleba",
"krojenia ogórków",
"krojenia pomidorów",
"krojenia cebuli",
"krojenia ogórków",
"krojenia pomidorów",
"jedzenia",
"picia",
"kuchni",
"książek",
"ryb",
"migdałów",
"komputera",
"internetu",
"koszul",
];
const chatRoot = document.createElement("div");
chatRoot.className = "ai-chat";
chatRoot.innerHTML = `
<button class="ai-chat__fab" type="button" aria-expanded="false" aria-controls="ai-chat-panel">AI chat</button>
<section class="ai-chat__panel" id="ai-chat-panel" aria-hidden="true">
<header class="ai-chat__header">
<strong>Agent Krzak.org</strong>
<button class="ai-chat__close" type="button" aria-label="Close chat">x</button>
</header>
<div class="ai-chat__messages"></div>
<form class="ai-chat__form">
<input class="ai-chat__input" type="text" placeholder="Napisz wiadomość..." autocomplete="off" />
<button class="ai-chat__send" type="submit">Wyslij</button>
</form>
</section>
`;
document.body.appendChild(chatRoot);
const fab = chatRoot.querySelector(".ai-chat__fab");
const panel = chatRoot.querySelector(".ai-chat__panel");
const closeBtn = chatRoot.querySelector(".ai-chat__close");
const messages = chatRoot.querySelector(".ai-chat__messages");
const form = chatRoot.querySelector(".ai-chat__form");
const input = chatRoot.querySelector(".ai-chat__input");
const sendBtn = chatRoot.querySelector(".ai-chat__send");
const backToTop = document.getElementById("back-to-top");
const viewport = window.visualViewport;
function addMessage(text, fromUser) {
const bubble = document.createElement("p");
bubble.className = fromUser
? "ai-chat__bubble ai-chat__bubble--user"
: "ai-chat__bubble";
bubble.textContent = text;
messages.appendChild(bubble);
messages.scrollTop = messages.scrollHeight;
}
function openChat() {
panel.classList.add("is-open");
panel.setAttribute("aria-hidden", "false");
fab.setAttribute("aria-expanded", "true");
if (!messages.hasChildNodes()) {
addMessage(introMessage, false);
}
input.focus();
syncKeyboardOffset();
}
function closeChat() {
panel.classList.remove("is-open");
panel.setAttribute("aria-hidden", "true");
fab.setAttribute("aria-expanded", "false");
syncKeyboardOffset();
}
function randomItem(items) {
return items[Math.floor(Math.random() * items.length)];
}
function buildReply() {
return `${randomItem(baseReplies)}, ale mogę Ci zaoferować kupno ${randomItem(offerFrom)} do ${randomItem(offerTo)}.`;
}
function syncChatPosition() {
if (!backToTop) {
chatRoot.classList.remove("ai-chat--raised");
return;
}
chatRoot.classList.toggle(
"ai-chat--raised",
backToTop.classList.contains("visible"),
);
}
function syncKeyboardOffset() {
if (!viewport) {
chatRoot.style.setProperty("--ai-chat-keyboard-offset", "0px");
return;
}
const offset = Math.max(
0,
Math.round(window.innerHeight - viewport.height - viewport.offsetTop),
);
chatRoot.style.setProperty("--ai-chat-keyboard-offset", `${offset}px`);
}
fab.addEventListener("click", () => {
if (panel.classList.contains("is-open")) {
closeChat();
return;
}
openChat();
});
closeBtn.addEventListener("click", closeChat);
sendBtn.addEventListener("pointerdown", (event) => {
// Keep focus on the input so mobile keyboards stay open after sending.
event.preventDefault();
});
form.addEventListener("submit", (event) => {
event.preventDefault();
const text = input.value.trim();
if (!text) {
return;
}
addMessage(text, true);
addMessage(buildReply(), false);
input.value = "";
input.focus({ preventScroll: true });
});
if (backToTop) {
const observer = new MutationObserver(syncChatPosition);
observer.observe(backToTop, {
attributes: true,
attributeFilter: ["class"],
});
window.addEventListener("resize", syncChatPosition);
}
if (viewport) {
viewport.addEventListener("resize", syncKeyboardOffset);
viewport.addEventListener("scroll", syncKeyboardOffset);
}
window.addEventListener("resize", syncKeyboardOffset);
syncChatPosition();
syncKeyboardOffset();
})();