Files
Cursor Agentandhaijd b7bd830da7 feat: 用 Ragflow 搭建鲜境方案客服
咨询台接 Ragflow 检索与示例知识库,支持追问、判定、引用和留资。

Co-authored-by: haijd <haijd@163.com>
2026-09-20 01:08:11 +00:00

145 lines
5.1 KiB
JavaScript

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();