feat: 用 Ragflow 搭建鲜境方案客服

咨询台接 Ragflow 检索与示例知识库,支持追问、判定、引用和留资。

Co-authored-by: haijd <haijd@163.com>
This commit is contained in:
Cursor Agent
2026-09-20 01:08:11 +00:00
co-authored by haijd
parent 9c6afcb61a
commit b7bd830da7
20 changed files with 925 additions and 258 deletions
+84 -13
View File
@@ -6,7 +6,7 @@ const schemeEl = document.getElementById("scheme");
const metaEl = document.getElementById("meta");
const form = document.getElementById("form");
const input = document.getElementById("input");
const resetBtn = document.getElementById("reset");
const statusEl = document.getElementById("status");
function addBubble(role, text) {
const div = document.createElement("div");
@@ -21,7 +21,7 @@ async function ensureSession() {
const res = await fetch("/api/sessions", { method: "POST" });
const data = await res.json();
sessionId = data.session_id;
metaEl.textContent = `session: ${sessionId.slice(0, 8)}…`;
metaEl.textContent = `会话 ${sessionId.slice(0, 8)}`;
return sessionId;
}
@@ -34,20 +34,18 @@ async function sendMessage(message) {
body: JSON.stringify({ session_id: sessionId, message }),
});
if (!res.ok) {
addBubble("sys", `错误:${res.status}`);
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 = `session: ${sessionId.slice(0, 8)}… · phase: ${data.phase}`;
schemeEl.textContent = data.scheme ? JSON.stringify(data.scheme, null, 2) : "还没到出方案这一步";
metaEl.textContent = `会话 ${sessionId.slice(0, 8)} · ${data.phase}`;
}
form.addEventListener("submit", async (e) => {
e.preventDefault();
form.addEventListener("submit", async (event) => {
event.preventDefault();
const text = input.value.trim();
if (!text) return;
input.value = "";
@@ -60,14 +58,87 @@ form.addEventListener("submit", async (e) => {
}
});
resetBtn.addEventListener("click", () => {
document.getElementById("reset").addEventListener("click", () => {
sessionId = null;
logEl.innerHTML = "";
slotsEl.textContent = "{}";
schemeEl.textContent = "尚未生成";
schemeEl.textContent = "还没到出方案这一步";
metaEl.textContent = "";
addBubble("bot", "新会话已开始。可以直接说场景,例如:「产线检测,偏向 B 类」。");
addBubble("bot", "新会话。可以直接说:「产线检测,偏向 B 类」。");
input.focus();
});
addBubble("bot", "你好,我是鲜境方案引擎原型。先收集几个关键信息,再给出产品组合草稿。\n试试:「我们在户外做监测,指标还不确定」。");
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();