let sessionId = null; const logEl = document.getElementById("log"); const slotsEl = document.getElementById("slots"); const schemeEl = document.getElementById("scheme"); const metaEl = document.getElementById("meta"); const form = document.getElementById("form"); const input = document.getElementById("input"); const statusEl = document.getElementById("status"); function addBubble(role, text) { const div = document.createElement("div"); div.className = `bubble ${role}`; div.textContent = text; logEl.appendChild(div); logEl.scrollTop = logEl.scrollHeight; } async function ensureSession() { if (sessionId) return sessionId; const res = await fetch("/api/sessions", { method: "POST" }); const data = await res.json(); sessionId = data.session_id; metaEl.textContent = `会话 ${sessionId.slice(0, 8)}`; return sessionId; } async function sendMessage(message) { await ensureSession(); addBubble("user", message); const res = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ session_id: sessionId, message }), }); if (!res.ok) { addBubble("bot", `请求失败:${res.status}`); return; } const data = await res.json(); addBubble("bot", data.reply); slotsEl.textContent = JSON.stringify(data.slots || {}, null, 2); schemeEl.textContent = data.scheme ? JSON.stringify(data.scheme, null, 2) : "还没到出方案这一步"; metaEl.textContent = `会话 ${sessionId.slice(0, 8)} · ${data.phase}`; } form.addEventListener("submit", async (event) => { event.preventDefault(); const text = input.value.trim(); if (!text) return; input.value = ""; form.querySelector("button").disabled = true; try { await sendMessage(text); } finally { form.querySelector("button").disabled = false; input.focus(); } }); document.getElementById("reset").addEventListener("click", () => { sessionId = null; logEl.innerHTML = ""; slotsEl.textContent = "{}"; schemeEl.textContent = "还没到出方案这一步"; metaEl.textContent = ""; addBubble("bot", "新会话。可以直接说:「产线检测,偏向 B 类」。"); input.focus(); }); document.getElementById("handoff").addEventListener("submit", async (event) => { event.preventDefault(); const msg = document.getElementById("handoff-msg"); if (!sessionId) { msg.textContent = "请先开始一段咨询。"; return; } const body = Object.fromEntries(new FormData(event.target).entries()); const res = await fetch("/api/handoff", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ session_id: sessionId, ...body }), }); const data = await res.json(); msg.textContent = res.ok ? `已记录,编号 ${data.lead_id}` : "提交失败"; if (res.ok) loadLeads(); }); document.querySelectorAll(".tab").forEach((button) => { button.addEventListener("click", () => { document.querySelectorAll(".tab").forEach((item) => item.classList.remove("active")); document.querySelectorAll(".pane").forEach((item) => item.classList.remove("active")); button.classList.add("active"); document.getElementById(button.dataset.tab).classList.add("active"); if (button.dataset.tab === "knowledge") loadKnowledge(); if (button.dataset.tab === "leads") loadLeads(); }); }); async function loadHealth() { try { const data = await (await fetch("/api/health")).json(); const ragOk = data.ragflow && data.ragflow.ok && data.dataset_ids_configured; statusEl.textContent = ragOk ? "Ragflow 已接入" : "Ragflow 未就绪,出方案时会标明检索失败"; statusEl.className = `status ${ragOk ? "ok" : "bad"}`; if (data.ragflow_web) document.getElementById("ragflow-link").href = data.ragflow_web; } catch { statusEl.textContent = "咨询服务未连通"; statusEl.className = "status bad"; } } async function loadKnowledge() { const box = document.getElementById("ragflow-status"); const files = document.getElementById("files"); const status = await (await fetch("/api/ragflow/status")).json(); box.textContent = JSON.stringify(status, null, 2); const list = await (await fetch("/api/knowledge/files")).json(); files.innerHTML = ""; (list.files || []).forEach((file) => { const li = document.createElement("li"); li.textContent = `${file.name} · ${file.bytes} 字节`; files.appendChild(li); }); } async function loadLeads() { const box = document.getElementById("lead-list"); const data = await (await fetch("/api/leads")).json(); if (!data.leads || !data.leads.length) { box.textContent = "暂无"; return; } box.innerHTML = ""; data.leads.forEach((lead) => { const div = document.createElement("div"); div.className = "lead"; div.textContent = `#${lead.id} ${lead.name} ${lead.phone} · ${lead.note || "无备注"} · ${lead.created_at}`; box.appendChild(div); }); } addBubble("bot", "你好,我是鲜境方案客服。先问清技术路线和场景,再给出产品组合草稿,并附上知识库依据。\n试试:「户外监测,指标还不确定」。"); loadHealth();