302 lines
11 KiB
JavaScript
302 lines
11 KiB
JavaScript
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 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 = `${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") });
|
|
});
|
|
|
|
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();
|
|
});
|