2025-06-20 01:32:24 +08:00

321 lines
12 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 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 loadToken();
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";
if (currentPage > 1) {
const prev = document.createElement("button");
prev.textContent = "上一页";
prev.onclick = () => fetchResults(lastKeywords, currentPage - 1);
pagination.appendChild(prev);
}
if (currentPage < totalPages) {
const next = document.createElement("button");
next.textContent = "下一页";
next.onclick = () => fetchResults(lastKeywords, currentPage + 1);
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 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>
`;
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") });
});
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");
}