357 lines
13 KiB
JavaScript
357 lines
13 KiB
JavaScript
// popup.js
|
||
document.addEventListener("DOMContentLoaded", async () => {
|
||
const panel = document.getElementById("draggablePanel");
|
||
const header = document.querySelector(".header");
|
||
|
||
if (window.innerWidth > 900 && window.innerHeight > 700) {
|
||
document.body.classList.add("fullscreen");
|
||
if (panel) {
|
||
panel.style.left = "";
|
||
panel.style.top = "";
|
||
panel.style.position = "fixed";
|
||
}
|
||
} else if (panel && header) {
|
||
let dragging = false, offsetX = 0, offsetY = 0;
|
||
header.addEventListener("mousedown", (e) => {
|
||
dragging = true;
|
||
offsetX = e.clientX - panel.offsetLeft;
|
||
offsetY = e.clientY - panel.offsetTop;
|
||
document.body.style.userSelect = "none";
|
||
});
|
||
document.addEventListener("mousemove", (e) => {
|
||
if (dragging) {
|
||
panel.style.left = Math.max(0, e.clientX - offsetX) + "px";
|
||
panel.style.top = Math.max(0, e.clientY - offsetY) + "px";
|
||
}
|
||
});
|
||
document.addEventListener("mouseup", () => {
|
||
dragging = false;
|
||
document.body.style.userSelect = "";
|
||
});
|
||
}
|
||
|
||
const input = document.getElementById("searchInput");
|
||
const searchBtn = document.getElementById("searchBtn");
|
||
const clearBtn = document.getElementById("clearHistoryBtn");
|
||
const historyBox = document.getElementById("historyTags");
|
||
const historyWrapper = document.getElementById("historyWrapper");
|
||
const resultBox = document.getElementById("results");
|
||
const llmResultContainer = document.getElementById("llmResultContainer");
|
||
const llmResultContent = document.getElementById("llmResultContent");
|
||
const llmInput = document.getElementById("llmInput");
|
||
const llmSendBtn = document.getElementById("llmSendBtn");
|
||
const logoutBtn = document.getElementById("logoutBtn");
|
||
const userNameLabel = document.getElementById("userName");
|
||
const userAvatar = document.getElementById("userAvatar");
|
||
const userDropdown = document.getElementById("userDropdown");
|
||
const openTabBtn = document.getElementById("openTabBtn");
|
||
const settingsBtn = document.getElementById("settingsBtn");
|
||
const testBtn = document.getElementById("testBtn");
|
||
const openUploaderMenu = document.getElementById("openUploaderMenu");
|
||
const tabEs = document.getElementById("tabEs");
|
||
const tabAi = document.getElementById("tabAi");
|
||
const tabContentEs = document.getElementById("tabContentEs");
|
||
const tabContentAi = document.getElementById("tabContentAi");
|
||
|
||
tabEs.onclick = () => {
|
||
tabEs.classList.add("active");
|
||
tabAi.classList.remove("active");
|
||
tabContentEs.classList.add("active");
|
||
tabContentAi.classList.remove("active");
|
||
};
|
||
|
||
tabAi.onclick = () => {
|
||
tabEs.classList.remove("active");
|
||
tabAi.classList.add("active");
|
||
tabContentEs.classList.remove("active");
|
||
tabContentAi.classList.add("active");
|
||
setTimeout(() => {
|
||
llmResultContent.scrollTop = llmResultContent.scrollHeight;
|
||
}, 100);
|
||
};
|
||
|
||
const isNewTab = new URLSearchParams(window.location.search).get("from") === "newtab";
|
||
|
||
let token = null;
|
||
let currentPage = 1;
|
||
let totalPages = 1;
|
||
let lastKeywords = [];
|
||
let config = await loadMergedConfig();
|
||
|
||
const auth = await loadUserToken();
|
||
if (!auth) return handleLogout(auth);
|
||
token = auth.token;
|
||
userNameLabel.textContent = auth.username;
|
||
|
||
async function triggerSearch() {
|
||
const raw = input?.value?.trim();
|
||
if (!token || !raw) return;
|
||
updateHistory(raw);
|
||
renderHistoryTags(true);
|
||
fetchResults(raw, 1);
|
||
}
|
||
|
||
function parseKeywordGroups(inputStr) {
|
||
return inputStr
|
||
.trim()
|
||
.split("||")
|
||
.map(group => {
|
||
group = group.trim();
|
||
if (group.startsWith("(") && group.endsWith(")")) group = group.slice(1, -1);
|
||
return group.split(/\s+/).filter(Boolean);
|
||
})
|
||
.filter(g => g.length > 0);
|
||
}
|
||
|
||
function showLoading() {
|
||
resultBox.innerHTML = "<li>加载中...</li>";
|
||
}
|
||
|
||
function clearResults() {
|
||
resultBox.innerHTML = "";
|
||
llmResultContent.innerHTML = "";
|
||
llmResultContainer.classList.add("hidden");
|
||
historyWrapper.classList.add("hidden");
|
||
}
|
||
|
||
function renderPagination() {
|
||
const pagination = document.createElement("div");
|
||
pagination.className = "pagination";
|
||
|
||
// 上一页按钮(始终显示)
|
||
const prev = document.createElement("button");
|
||
prev.textContent = "上一页";
|
||
if (currentPage > 1) {
|
||
prev.onclick = () => fetchResults(lastKeywords, currentPage - 1);
|
||
} else {
|
||
prev.disabled = true;
|
||
}
|
||
pagination.appendChild(prev);
|
||
|
||
// 显示页码:前后最多各2个,加当前页(共5个)
|
||
const maxPages = 5;
|
||
let start = Math.max(1, currentPage - 2);
|
||
let end = Math.min(totalPages, start + maxPages - 1);
|
||
if (end - start < maxPages - 1) {
|
||
start = Math.max(1, end - maxPages + 1);
|
||
}
|
||
|
||
for (let i = start; i <= end; i++) {
|
||
const pageBtn = document.createElement("button");
|
||
pageBtn.textContent = i;
|
||
if (i === currentPage) {
|
||
pageBtn.className = "active-page";
|
||
} else {
|
||
pageBtn.onclick = () => fetchResults(lastKeywords, i);
|
||
}
|
||
pagination.appendChild(pageBtn);
|
||
}
|
||
|
||
// 下一页按钮(始终显示)
|
||
const next = document.createElement("button");
|
||
next.textContent = "下一页";
|
||
if (currentPage < totalPages) {
|
||
next.onclick = () => fetchResults(lastKeywords, currentPage + 1);
|
||
} else {
|
||
next.disabled = true;
|
||
}
|
||
pagination.appendChild(next);
|
||
|
||
resultBox.appendChild(pagination);
|
||
}
|
||
|
||
|
||
|
||
async function fetchResults(rawInput, page) {
|
||
currentPage = page;
|
||
lastKeywords = rawInput;
|
||
showLoading();
|
||
|
||
const endpoint = config.apiConfig.esSearch;
|
||
const [kwField, pageField, sizeField] = endpoint.requestParams.map(p => p.field);
|
||
const [resultsField, totalField, sizeField2] = endpoint.responseParams.map(p => p.field);
|
||
const keywordGroups = parseKeywordGroups(rawInput);
|
||
const requestBody = {
|
||
[kwField]: keywordGroups,
|
||
[pageField]: page,
|
||
[sizeField]: 10
|
||
};
|
||
|
||
try {
|
||
const response = await fetch(endpoint.url, {
|
||
method: "POST",
|
||
headers: {
|
||
"Authorization": `${token}`,
|
||
"Content-Type": "application/json"
|
||
},
|
||
body: JSON.stringify(requestBody)
|
||
});
|
||
log("已使用前端接口配置:" + JSON.stringify(endpoint));
|
||
log("请求后端接口参数:" + JSON.stringify(requestBody));
|
||
|
||
if (response.status === 401) return handleLogout();
|
||
const data = await response.json();
|
||
const results = data[resultsField];
|
||
const total = data[totalField];
|
||
const pageSize = data[sizeField2] || 10;
|
||
|
||
resultBox.innerHTML = "";
|
||
|
||
if (!results || results.length === 0) {
|
||
resultBox.innerHTML = "<li>未找到相关文档</li>";
|
||
return;
|
||
}
|
||
|
||
totalPages = Math.ceil(total / pageSize);
|
||
const fileBaseUrl = config.apiConfig.materialPublicUrl.url;
|
||
|
||
results.forEach(doc => {
|
||
const li = document.createElement("li");
|
||
const filename = doc.filename || doc.title || "未知文件";
|
||
const filepath = doc.filepath?.replace(/^import-data\//, "").replace(/\.md$/, ".html") || "";
|
||
const mtime = doc.mtime ? formatLocalTime(doc.mtime) : ""; // 格式化时间
|
||
const uploader = doc.uploader ? doc.uploader : "管理员";
|
||
const link = document.createElement("a");
|
||
link.href = doc.url ? doc.url : `${fileBaseUrl}/${filepath || filename}`;
|
||
link.target = "_blank";
|
||
link.textContent = filename;
|
||
|
||
const snippet = document.createElement("div");
|
||
snippet.className = "summary";
|
||
snippet.innerHTML = doc.summary?.slice(0, 300) + "...";
|
||
|
||
// 元信息行
|
||
const metaRow = document.createElement("div");
|
||
metaRow.className = "es-meta-row";
|
||
metaRow.innerHTML = `
|
||
<span class="es-filepath" title="${filepath}">${filepath}</span>
|
||
<span class="es-updatetime">${mtime ? "更新时间:" + mtime : ""}</span>
|
||
<span class="es-uploader">${mtime ? "上传人:" + uploader : ""}</span>
|
||
`;
|
||
|
||
li.appendChild(link);
|
||
li.appendChild(snippet);
|
||
li.appendChild(metaRow);
|
||
resultBox.appendChild(li);
|
||
});
|
||
|
||
renderPagination();
|
||
} catch (err) {
|
||
resultBox.innerHTML = "<li>搜索失败,请检查网络或服务状态。</li>";
|
||
}
|
||
}
|
||
|
||
function updateHistory(keyword) {
|
||
chrome.storage.local.get(["searchHistory"], (res) => {
|
||
let history = res.searchHistory || [];
|
||
history = [keyword, ...history.filter(item => item !== keyword)];
|
||
if (history.length > 5) history = history.slice(0, 5);
|
||
chrome.storage.local.set({ searchHistory: history });
|
||
});
|
||
}
|
||
|
||
function renderHistoryTags(show = false) {
|
||
chrome.storage.local.get(["searchHistory"], (res) => {
|
||
const history = res.searchHistory || [];
|
||
historyBox.innerHTML = "";
|
||
|
||
if (history.length === 0 || !show) {
|
||
historyWrapper.classList.add("hidden");
|
||
return;
|
||
}
|
||
|
||
history.forEach(tagText => {
|
||
const tag = document.createElement("span");
|
||
tag.className = "tag";
|
||
tag.textContent = tagText;
|
||
tag.onclick = () => {
|
||
input.value = tagText;
|
||
triggerSearch();
|
||
};
|
||
historyBox.appendChild(tag);
|
||
});
|
||
|
||
historyWrapper.classList.remove("hidden");
|
||
});
|
||
}
|
||
|
||
searchBtn.addEventListener("click", triggerSearch);
|
||
input.addEventListener("keydown", e => {
|
||
if (e.key === "Enter") {
|
||
e.preventDefault();
|
||
triggerSearch();
|
||
}
|
||
});
|
||
input.addEventListener("focus", () => renderHistoryTags(true));
|
||
input.addEventListener("input", () => {
|
||
if (!input.value.trim()) clearResults();
|
||
});
|
||
input.addEventListener("blur", () => setTimeout(() => historyWrapper.classList.add("hidden"), 200));
|
||
clearBtn.addEventListener("click", () => {
|
||
chrome.storage.local.remove("searchHistory", () => renderHistoryTags());
|
||
});
|
||
|
||
logoutBtn?.addEventListener("click", () => handleLogout(auth));
|
||
userAvatar?.addEventListener("click", () => {
|
||
userDropdown.classList.toggle("hidden");
|
||
});
|
||
settingsBtn?.addEventListener("click", () => {
|
||
chrome.tabs.create({ url: chrome.runtime.getURL("pages/settings.html") });
|
||
});
|
||
testBtn?.addEventListener("click", () => {
|
||
chrome.tabs.create({ url: chrome.runtime.getURL("pages/test.html") });
|
||
});
|
||
openUploaderMenu?.addEventListener("click", () => {
|
||
const uploaderUrl = chrome.runtime ? chrome.runtime.getURL("pages/uploader.html") : "pages/uploader.html";
|
||
window.open(uploaderUrl, "_blank");
|
||
userDropdown.classList.add("hidden");
|
||
});
|
||
|
||
window.addEventListener("click", (e) => {
|
||
if (!userDropdown.contains(e.target) && e.target !== userAvatar) {
|
||
userDropdown.classList.add("hidden");
|
||
}
|
||
});
|
||
|
||
llmSendBtn?.addEventListener("click", () => {
|
||
const val = llmInput.value.trim();
|
||
if (!val) return;
|
||
llmInput.value = "";
|
||
fetchLLMAnswer(val, llmResultContainer, llmResultContent);
|
||
});
|
||
|
||
llmInput.addEventListener("keydown", e => {
|
||
if (e.key === "Enter") {
|
||
e.preventDefault();
|
||
document.getElementById("llmSendBtn").click();
|
||
}
|
||
});
|
||
|
||
openTabBtn.addEventListener("click", () => {
|
||
const val = input?.value?.trim();
|
||
const encoded = encodeURIComponent(val || "");
|
||
const url = chrome.runtime.getURL("pages/popup.html?keywords=" + encoded + "&from=newtab");
|
||
chrome.tabs.create({ url });
|
||
});
|
||
|
||
const autoKeywords = new URLSearchParams(location.search).get("keywords");
|
||
if (autoKeywords) {
|
||
input.value = decodeURIComponent(autoKeywords);
|
||
triggerSearch();
|
||
}
|
||
|
||
renderHistoryTags();
|
||
});
|
||
|
||
// 时间戳格式化辅助
|
||
function formatLocalTime(ts) {
|
||
if (!ts) return "";
|
||
const date = new Date(Number(ts));
|
||
if (isNaN(date.getTime())) return "";
|
||
return date.getFullYear() + "-" +
|
||
String(date.getMonth() + 1).padStart(2, "0") + "-" +
|
||
String(date.getDate()).padStart(2, "0") + " " +
|
||
String(date.getHours()).padStart(2, "0") + ":" +
|
||
String(date.getMinutes()).padStart(2, "0");
|
||
}
|