239 lines
8.0 KiB
JavaScript
239 lines
8.0 KiB
JavaScript
(function () {
|
|
var page = document.querySelector(".knowledge-page");
|
|
if (!page) {
|
|
return;
|
|
}
|
|
|
|
var documentForm = document.getElementById("knowledgeDocumentForm");
|
|
var documentStatus = document.getElementById("knowledgeDocumentStatus");
|
|
var documentTable = document.getElementById("knowledgeDocumentTable");
|
|
var documentSearch = document.getElementById("knowledgeDocumentSearch");
|
|
var searchForm = document.getElementById("knowledgeSearchForm");
|
|
var queryInput = document.getElementById("knowledgeSearchQuery");
|
|
var results = document.getElementById("knowledgeSearchResults");
|
|
var sourceSearch = document.getElementById("knowledgeSourceSearch");
|
|
var sourceTable = document.getElementById("knowledgeSourceTable");
|
|
var documentFileInput = document.getElementById("knowledgeDocumentFile");
|
|
var uploadDropzone = document.getElementById("knowledgeUploadDropzone");
|
|
|
|
function csrfToken() {
|
|
var cookie = document.cookie.split("; ").find(function (item) {
|
|
return item.indexOf("csrftoken=") === 0;
|
|
});
|
|
return cookie ? decodeURIComponent(cookie.split("=")[1]) : "";
|
|
}
|
|
|
|
function escapeHtml(value) {
|
|
return String(value || "")
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """)
|
|
.replace(/'/g, "'");
|
|
}
|
|
|
|
async function patchDocument(row, payload) {
|
|
var response = await fetch(row.getAttribute("data-detail-url"), {
|
|
method: "PATCH",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-CSRFToken": csrfToken(),
|
|
},
|
|
body: JSON.stringify(payload),
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error("知识库材料更新失败。");
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
async function deleteDocument(row) {
|
|
var response = await fetch(row.getAttribute("data-detail-url"), {
|
|
method: "DELETE",
|
|
headers: { "X-CSRFToken": csrfToken() },
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error("知识库材料删除失败。");
|
|
}
|
|
}
|
|
|
|
async function indexDocument(row) {
|
|
var response = await fetch(row.getAttribute("data-index-url"), {
|
|
method: "POST",
|
|
headers: { "X-CSRFToken": csrfToken() },
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error("知识库材料解析入库失败。");
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
function renderResults(payload) {
|
|
if (!results) {
|
|
return;
|
|
}
|
|
if (payload.error_message) {
|
|
results.innerHTML = '<p class="knowledge-search-error">' + escapeHtml(payload.error_message) + "</p>";
|
|
return;
|
|
}
|
|
if (!payload.results || !payload.results.length) {
|
|
results.innerHTML = '<p class="panel-empty">未检索到依据片段。</p>';
|
|
return;
|
|
}
|
|
results.innerHTML = payload.results
|
|
.map(function (item, index) {
|
|
return [
|
|
'<article class="knowledge-result">',
|
|
"<header><strong>结果 " + (index + 1) + "</strong><span>" + escapeHtml(item.source || "法规材料") + "</span></header>",
|
|
"<p>" + escapeHtml(item.text || "").slice(0, 600) + "</p>",
|
|
item.score === null || item.score === undefined ? "" : "<em>score: " + escapeHtml(item.score) + "</em>",
|
|
"</article>",
|
|
].join("");
|
|
})
|
|
.join("");
|
|
}
|
|
|
|
if (documentForm) {
|
|
documentForm.addEventListener("submit", async function (event) {
|
|
event.preventDefault();
|
|
var formData = new FormData(documentForm);
|
|
if (documentStatus) {
|
|
documentStatus.textContent = "上传并解析入库中...";
|
|
}
|
|
try {
|
|
var response = await fetch(page.getAttribute("data-document-url"), {
|
|
method: "POST",
|
|
headers: { "X-CSRFToken": csrfToken() },
|
|
body: formData,
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error("新增材料失败。");
|
|
}
|
|
window.location.reload();
|
|
} catch (error) {
|
|
if (documentStatus) {
|
|
documentStatus.textContent = error.message || "新增材料失败。";
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
if (documentFileInput && documentStatus) {
|
|
documentFileInput.addEventListener("change", function () {
|
|
var file = documentFileInput.files && documentFileInput.files[0];
|
|
documentStatus.textContent = file
|
|
? "已选择:" + file.name
|
|
: "上传后会进入当前账号的全局知识库。";
|
|
});
|
|
}
|
|
|
|
if (uploadDropzone && documentFileInput) {
|
|
uploadDropzone.addEventListener("click", function () {
|
|
documentFileInput.click();
|
|
});
|
|
uploadDropzone.addEventListener("keydown", function (event) {
|
|
if (event.key === "Enter" || event.key === " ") {
|
|
event.preventDefault();
|
|
documentFileInput.click();
|
|
}
|
|
});
|
|
["dragenter", "dragover"].forEach(function (eventName) {
|
|
uploadDropzone.addEventListener(eventName, function (event) {
|
|
event.preventDefault();
|
|
uploadDropzone.classList.add("dragging");
|
|
});
|
|
});
|
|
["dragleave", "drop"].forEach(function (eventName) {
|
|
uploadDropzone.addEventListener(eventName, function (event) {
|
|
event.preventDefault();
|
|
uploadDropzone.classList.remove("dragging");
|
|
});
|
|
});
|
|
uploadDropzone.addEventListener("drop", function (event) {
|
|
var files = event.dataTransfer && event.dataTransfer.files;
|
|
if (!files || !files.length) {
|
|
return;
|
|
}
|
|
documentFileInput.files = files;
|
|
documentFileInput.dispatchEvent(new Event("change", { bubbles: true }));
|
|
});
|
|
}
|
|
|
|
if (documentTable) {
|
|
documentTable.addEventListener("click", async function (event) {
|
|
var button = event.target.closest("[data-kb-action]");
|
|
if (!button) {
|
|
return;
|
|
}
|
|
var row = button.closest("tr[data-document-id]");
|
|
if (!row) {
|
|
return;
|
|
}
|
|
var action = button.getAttribute("data-kb-action");
|
|
try {
|
|
if (action === "edit") {
|
|
var nameCell = row.querySelector(".attachment-name");
|
|
var nextName = window.prompt("请输入新的材料名称", nameCell ? nameCell.textContent.trim() : "");
|
|
if (nextName) {
|
|
await patchDocument(row, { display_name: nextName });
|
|
window.location.reload();
|
|
}
|
|
} else if (action === "toggle") {
|
|
await patchDocument(row, { is_active: button.textContent.trim() === "启用" });
|
|
window.location.reload();
|
|
} else if (action === "index") {
|
|
button.disabled = true;
|
|
button.textContent = "解析中";
|
|
await indexDocument(row);
|
|
window.location.reload();
|
|
} else if (action === "delete" && window.confirm("确认删除该知识库材料?")) {
|
|
await deleteDocument(row);
|
|
window.location.reload();
|
|
}
|
|
} catch (error) {
|
|
window.alert(error.message || "知识库材料操作失败。");
|
|
}
|
|
});
|
|
}
|
|
|
|
if (searchForm && queryInput) {
|
|
searchForm.addEventListener("submit", async function (event) {
|
|
event.preventDefault();
|
|
var query = queryInput.value.trim();
|
|
if (!query) {
|
|
renderResults({ error_message: "请输入检索问题。" });
|
|
return;
|
|
}
|
|
results.innerHTML = '<p class="panel-empty">检索中...</p>';
|
|
try {
|
|
var response = await fetch(page.getAttribute("data-search-url"), {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-CSRFToken": csrfToken(),
|
|
},
|
|
body: JSON.stringify({ query: query }),
|
|
});
|
|
renderResults(await response.json());
|
|
} catch (error) {
|
|
renderResults({ error_message: "检索失败,请稍后重试。" });
|
|
}
|
|
});
|
|
}
|
|
|
|
function bindTableSearch(input, table, selector) {
|
|
if (!input || !table) {
|
|
return;
|
|
}
|
|
input.addEventListener("input", function () {
|
|
var keyword = input.value.trim().toLowerCase();
|
|
table.querySelectorAll(selector).forEach(function (row) {
|
|
row.hidden = keyword && row.textContent.toLowerCase().indexOf(keyword) === -1;
|
|
});
|
|
});
|
|
}
|
|
|
|
bindTableSearch(documentSearch, documentTable, "tbody tr[data-document-id]");
|
|
bindTableSearch(sourceSearch, sourceTable, "tbody tr[data-source-name]");
|
|
})();
|