<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Atrium Chat</title>
<!-- Turnstile MUST run on a real hostname (not about:/srcdoc) -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" defer></script>
<style>
:root { color-scheme: dark; }
body { margin:0; background:#062015; color:#fff; font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif; }
#atrium-chat { max-width:720px; margin:0 auto; padding:24px; }
#messages { height:360px; overflow:auto; border:1px solid rgba(255,255,255,0.15); border-radius:16px; padding:16px; background:rgba(0,0,0,0.15); }
.row { margin:10px 0; white-space:pre-wrap; }
.role { opacity:0.7; font-size:12px; margin-bottom:4px; }
.bubble { padding:10px 12px; border-radius:14px; display:inline-block; max-width:90%; background:rgba(255,255,255,0.06); }
.bubble.you { background:rgba(255,255,255,0.10); }
.controls { display:flex; gap:10px; margin-top:12px; }
input { flex:1; padding:12px 14px; border-radius:14px; border:1px solid rgba(255,255,255,0.2); background:rgba(0,0,0,0.2); color:#fff; outline:none; }
button { padding:12px 16px; border-radius:14px; border:1px solid rgba(255,255,255,0.2); background:rgba(255,255,255,0.12); color:#fff; cursor:pointer; }
#status { margin-top:10px; font-size:12px; color:rgba(255,255,255,0.7); }
#turnstileBox { margin-top:14px; }
.pill { display:inline-block; padding:6px 10px; border-radius:999px; background:rgba(255,255,255,0.10); font-size:12px; margin-top:8px; }
</style>
</head>
<body>
<div id="atrium-chat">
<div id="messages"></div>
<div id="turnstileBox">
<div
class="cf-turnstile"
data-sitekey="0x4AAAAAACU6JJ9FKE1zu7Qi"
data-theme="dark"
data-callback="onTurnstileSuccess"
data-expired-callback="onTurnstileExpired"
data-error-callback="onTurnstileError">
</div>
<div id="turnstileStatus" class="pill">Turnstile: waiting…</div>
</div>
<div class="controls">
<input id="input" placeholder="Ask Atrium…" />
<button id="send">Send</button>
</div>
<div id="status"></div>
</div>
<script>
// =========================
// CONFIG
// =========================
const CHAT_URL = "https://chat.atrium.travel/chat";
// If your worker requires it, set it. Otherwise leave empty.
// WARNING: putting private keys in frontend is NOT secure.
const CHAT_API_KEY = ""; // e.g. "atrium_api_..." (only if truly public/low-risk)
const TURNSTILE_SITE_KEY = "0x4AAAAAACU6JJ9FKE1zu7Qi";
// =========================
// UI HELPERS
// =========================
const messagesEl = document.getElementById("messages");
const inputEl = document.getElementById("input");
const sendEl = document.getElementById("send");
const statusEl = document.getElementById("status");
const turnstileStatusEl = document.getElementById("turnstileStatus");
const envStatusEl = document.getElementById("envStatus");
function escapeHtml(s) {
return (s || "").replace(/[&<>"']/g, (c) => ({
"&":"&","<":"<",">":">",'"':""","'":"'"
}[c]));
}
function addMessage(role, text) {
const row = document.createElement("div");
row.className = "row";
row.innerHTML = `
<div class="role">${escapeHtml(role)}</div>
<div class="bubble ${role === "You" ? "you" : ""}">${escapeHtml(text)}</div>
`;
messagesEl.appendChild(row);
messagesEl.scrollTop = messagesEl.scrollHeight;
}
// =========================
// TURNSTILE
// =========================
let turnstileToken = null;
function onTurnstileSuccess(token) {
turnstileToken = token;
document.getElementById("turnstileStatus").textContent = "Turnstile: verified ✅";
}
function onTurnstileExpired() {
turnstileToken = null;
document.getElementById("turnstileStatus").textContent = "Turnstile: expired";
}
function onTurnstileError() {
turnstileToken = null;
document.getElementById("turnstileStatus").textContent = "Turnstile: error";
}
// =========================
// CHAT SEND
// =========================
async function send() {
const msg = inputEl.value.trim();
if (!msg) return;
if (!turnstileToken) {
statusEl.textContent = "Blocked: missing Turnstile token";
addMessage("Atrium", "Please complete the verification above.");
return;
}
inputEl.value = "";
addMessage("You", msg);
statusEl.textContent = "Sending…";
const headers = { "content-type": "application/json" };
// Optional API key (only if your worker requires it)
if (CHAT_API_KEY) headers["x-api-key"] = CHAT_API_KEY;
// Send token both ways (body + header) to match whatever your Worker expects
headers["cf-turnstile-response"] = turnstileToken;
const payload = {
message: msg,
state: {},
debug: 1,
turnstile: turnstileToken,
turnstileToken: turnstileToken,
cf_turnstile_response: turnstileToken
};
try {
const r = await fetch(CHAT_URL, {
method: "POST",
headers,
body: JSON.stringify(payload)
});
const text = await r.text();
let data;
try { data = JSON.parse(text); } catch { data = { raw: text }; }
if (!r.ok) {
statusEl.textContent = `Error ${r.status}`;
addMessage("Atrium (error)", JSON.stringify(data, null, 2));
return;
}
statusEl.textContent = "OK";
const reply =
data.reply ||
data.message ||
data.text ||
(data.messages && data.messages[data.messages.length - 1] && (data.messages[data.messages.length - 1].text || data.messages[data.messages.length - 1].content)) ||
JSON.stringify(data, null, 2);
addMessage("Atrium", typeof reply === "string" ? reply : JSON.stringify(reply, null, 2));
} catch (e) {
statusEl.textContent = "Network error";
addMessage("Atrium (error)", String(e));
}
}
sendEl.addEventListener("click", send);
inputEl.addEventListener("keydown", (e) => { if (e.key === "Enter") send(); });
addMessage("Atrium", "Welcome. Complete verification above, then ask for a city + category.");
// Boot
</script>
</body>
</html>