ReplyFlow / app /static /js /chat.js
nvikou's picture
Initial commit
582520a verified
Raw History Blame Contribute Delete
4.56 kB
(() => {
const messagesEl = document.getElementById("messages");
const composer = document.getElementById("composer");
const input = document.getElementById("message-input");
const convIdEl = document.getElementById("conv-id");
const slotsBox = document.getElementById("slots-box");
let conversationId = null;
let busy = false;
function addBubble(role, text) {
const div = document.createElement("div");
div.className = `bubble ${role}`;
div.textContent = text;
messagesEl.appendChild(div);
messagesEl.scrollTop = messagesEl.scrollHeight;
}
function renderLead(lead) {
document.getElementById("lead-name").textContent =
(lead && lead.name) || "—";
document.getElementById("lead-need").textContent =
(lead && lead.need) || "—";
document.getElementById("lead-budget").textContent =
(lead && lead.budget) || "—";
document.getElementById("lead-contact").textContent =
(lead && (lead.contact_channel || lead.email || lead.phone)) ||
"—";
}
function renderSlots(slots) {
slotsBox.innerHTML = "";
if (!slots || !slots.length) {
slotsBox.innerHTML =
'<div style="color:var(--muted);font-size:0.9rem">—</div>';
return;
}
slots.forEach((slot) => {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "slot-btn";
btn.textContent = slot.label || slot.start;
btn.addEventListener("click", () => bookSlot(slot));
slotsBox.appendChild(btn);
});
}
async function bookSlot(slot) {
if (!conversationId) return;
const res = await fetch("/api/appointments/book", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
conversation_id: conversationId,
slot_start: slot.start,
slot_end: slot.end,
}),
});
const data = await res.json();
if (!res.ok) {
addBubble("assistant", data.detail || "Booking failed");
return;
}
addBubble(
"assistant",
data.message || "Appointment booked"
);
renderSlots([]);
}
async function loadHealth() {
try {
const res = await fetch("/api/health");
const data = await res.json();
document.getElementById("h-openai").textContent = data.openai
? "on"
: "demo";
document.getElementById("h-calcom").textContent = data.calcom
? "on"
: "demo";
document.getElementById("h-whatsapp").textContent = data.whatsapp
? "on"
: "demo";
document.getElementById("h-chunks").textContent =
data.knowledge_chunks;
} catch (_) {
/* ignore */
}
}
async function startChat() {
messagesEl.innerHTML = "";
const res = await fetch(
"/api/chat/start?ui_language=en",
{ method: "POST" }
);
const data = await res.json();
conversationId = data.conversation_id;
convIdEl.textContent = `#${conversationId}`;
addBubble("assistant", data.greeting);
renderLead(null);
renderSlots([]);
}
composer.addEventListener("submit", async (event) => {
event.preventDefault();
if (busy || !conversationId) return;
const text = input.value.trim();
if (!text) return;
input.value = "";
addBubble("user", text);
busy = true;
try {
const res = await fetch("/api/chat/message", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
conversation_id: conversationId,
message: text,
ui_language: "en",
}),
});
const data = await res.json();
addBubble("assistant", data.reply);
renderLead(data.lead);
renderSlots(data.suggested_slots);
} catch (err) {
addBubble("assistant", "Network error. Please retry.");
} finally {
busy = false;
}
});
loadHealth();
startChat();
})();