Files
text-adventure-llm/web/static/app.js
Aodhan Collins 912b205699 Initial commit.
Basic docker deployment with Local LLM integration and simple game state.
2025-08-17 19:31:33 +01:00

115 lines
3.1 KiB
JavaScript

(() => {
const el = {
statusDot: document.getElementById("statusDot"),
messages: document.getElementById("messages"),
form: document.getElementById("chatForm"),
input: document.getElementById("messageInput"),
sendBtn: document.getElementById("sendBtn"),
tplUser: document.getElementById("msg-user"),
tplAssistant: document.getElementById("msg-assistant"),
};
const state = {
sending: false,
};
function setStatus(ok) {
el.statusDot.classList.toggle("ok", !!ok);
el.statusDot.classList.toggle("err", !ok);
}
async function healthCheck() {
try {
const res = await fetch("/api/health", { cache: "no-store" });
setStatus(res.ok);
} catch {
setStatus(false);
}
}
function appendMessage(role, text) {
const tpl = role === "user" ? el.tplUser : el.tplAssistant;
const node = tpl.content.cloneNode(true);
const bubble = node.querySelector(".bubble");
bubble.textContent = text;
el.messages.appendChild(node);
el.messages.scrollTop = el.messages.scrollHeight;
}
function setSending(sending) {
state.sending = sending;
el.input.disabled = sending;
el.sendBtn.disabled = sending;
el.sendBtn.textContent = sending ? "Sending..." : "Send";
}
async function sendMessage(text) {
setSending(true);
try {
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "same-origin",
body: JSON.stringify({ message: text }),
});
if (!res.ok) {
let detail = "";
try {
const data = await res.json();
detail = data.detail || res.statusText;
} catch {
detail = res.statusText;
}
throw new Error(detail || `HTTP ${res.status}`);
}
const data = await res.json();
appendMessage("assistant", data.reply ?? "");
setStatus(true);
} catch (err) {
appendMessage("assistant", `Error: ${err.message || err}`);
setStatus(false);
} finally {
setSending(false);
}
}
el.form.addEventListener("submit", async (e) => {
e.preventDefault();
const text = (el.input.value || "").trim();
if (!text || state.sending) return;
appendMessage("user", text);
el.input.value = "";
await sendMessage(text);
});
// Submit on Enter, allow Shift+Enter for newline (if we switch to textarea later)
el.input.addEventListener("keydown", (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
el.form.requestSubmit();
}
});
// Initial status check
healthCheck();
// Initialize session and show start message if configured
(async function initSession() {
try {
const res = await fetch("/api/session", { credentials: "same-origin" });
if (res.ok) {
const data = await res.json();
if (data.start_message) {
appendMessage("assistant", data.start_message);
}
}
} catch (_) {
// no-op
}
})();
// Periodic health check
setInterval(healthCheck, 15000);
})();