document.addEventListener("DOMContentLoaded", async () => { const input = document.getElementById("searchInput"); const searchBtn = document.getElementById("searchBtn"); const clearHistoryBtn = document.getElementById("clearHistoryBtn"); const resultBox = document.getElementById("results"); const historyBox = document.getElementById("historyTags"); const historyWrapper = document.getElementById("historyWrapper"); // AnythingLLM 相关节点 const llmResultContainer = document.getElementById("llmResultContainer"); const llmResultContent = document.getElementById("llmResultContent"); const llmInput = document.getElementById("llmInput"); const llmSendBtn = document.getElementById("llmSendBtn"); const userDropdown = document.getElementById("userDropdown"); const logoutBtn = document.getElementById("logoutBtn"); const userNameLabel = document.getElementById("userName"); const userAvatar = document.getElementById("userAvatar"); let token = null; let currentPage = 1; let totalPages = 1; let lastKeywords = []; const TOKEN_EXPIRE_MS = 7 * 24 * 60 * 60 * 1000; // 7天有效期 // === AnythingLLM 配置 === const LLM_TOKEN_API = "http://llm.wisdompulse.cn/api/request-token"; const LLM_WORKSPACES_API = "http://llm.wisdompulse.cn/api/workspaces"; const LLM_STREAM_API_BASE = "http://llm.wisdompulse.cn/api/workspace"; const LLM_TOKEN_EXPIRE_MS = 23 * 60 * 60 * 1000; // 新增tab切换功能 const tabEs = document.getElementById("tabEs"); const tabAi = document.getElementById("tabAi"); const tabContentEs = document.getElementById("tabContentEs"); const tabContentAi = document.getElementById("tabContentAi"); tabEs.onclick = function() { tabEs.classList.add("active"); tabAi.classList.remove("active"); tabContentEs.classList.add("active"); tabContentAi.classList.remove("active"); }; tabAi.onclick = function() { tabEs.classList.remove("active"); tabAi.classList.add("active"); tabContentEs.classList.remove("active"); tabContentAi.classList.add("active"); // 自动滚动到最底部 setTimeout(() => { const llmContent = document.getElementById("llmResultContent"); if (llmContent) llmContent.scrollTop = llmContent.scrollHeight; }, 100); }; // 动态获取 AnythingLLM 密码 async function getLLMPassword() { return new Promise((resolve) => { chrome.storage.local.get(["llmPassword"], (res) => { resolve(res.llmPassword || ""); }); }); } // 获取 LLM Token async function getLLMToken(forceRefresh = false) { return new Promise((resolve, reject) => { chrome.storage.local.get(["llmToken", "llmTokenAt"], async (res) => { const now = Date.now(); if ( !forceRefresh && res.llmToken && res.llmTokenAt && now - res.llmTokenAt < LLM_TOKEN_EXPIRE_MS ) { console.log("LLM token 命中缓存,有效期内"); resolve(res.llmToken); } else { try { const LLM_PASSWORD = await getLLMPassword(); console.log("开始请求 LLM token,POST", LLM_TOKEN_API, "参数:", { password: "******" }); const response = await fetch(LLM_TOKEN_API, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ password: LLM_PASSWORD }), }); const data = await response.json(); console.log("LLM token 接口返回:", data); if (data.token) { chrome.storage.local.set({ llmToken: data.token, llmTokenAt: now }); console.log("LLM token 已缓存"); resolve(data.token); } else { console.log("LLM token 获取失败,响应内容:", data); reject("LLM token获取失败"); } } catch (e) { console.log("LLM token 请求异常:", e); reject("LLM token接口异常"); } } }); }); } // 获取 LLM workspaceSlug async function getLLMWorkspaceSlug(token, forceRefresh = false) { return new Promise((resolve, reject) => { chrome.storage.local.get(["llmWorkspaceSlug", "llmWorkspaceAt"], async (res) => { const now = Date.now(); if ( !forceRefresh && res.llmWorkspaceSlug && res.llmWorkspaceAt && now - res.llmWorkspaceAt < LLM_TOKEN_EXPIRE_MS ) { console.log("LLM workspaceSlug 命中缓存,有效期内:", res.llmWorkspaceSlug); resolve(res.llmWorkspaceSlug); } else { try { console.log("开始请求 LLM 工作区列表,GET", LLM_WORKSPACES_API, "Authorization: Bearer", token); const response = await fetch(LLM_WORKSPACES_API, { method: "GET", headers: { Authorization: `Bearer ${token}` }, }); const data = await response.json(); console.log("LLM 工作区接口返回:", data); if (data.workspaces && Array.isArray(data.workspaces) && data.workspaces.length > 0 && data.workspaces[0].slug) { chrome.storage.local.set({ llmWorkspaceSlug: data.workspaces[0].slug, llmWorkspaceAt: now, }); console.log("LLM workspaceSlug 已缓存:", data.workspaces[0].slug); resolve(data.workspaces[0].slug); } else { console.log("未获取到有效的 LLM 工作区 slug,响应内容:", data); reject("未获取到LLM工作区slug"); } } catch (e) { console.log("LLM 工作区接口请求异常:", e); reject("LLM工作区接口异常"); } } }); }); } // 追加智能助手问答内容 function appendLLMMsg(msg, type) { const item = document.createElement("div"); item.className = "llm-msg-item " + (type === "user" ? "llm-user" : "llm-assistant"); item.style.margin = "6px 0"; item.innerHTML = `${type === "user" ? "我:" : "智能助手:"} ${msg}`; llmResultContent.appendChild(item); llmResultContent.scrollTop = llmResultContent.scrollHeight; } // 流式渲染智能助手 async function fetchLLMAnswer(question) { if (!question || !question.trim()) return; // 追加用户提问 const userItem = document.createElement("div"); userItem.className = "llm-msg-item llm-user"; userItem.innerHTML = `我: ${question}`; llmResultContent.appendChild(userItem); llmResultContainer.classList.remove("hidden"); llmResultContent.scrollTop = llmResultContent.scrollHeight; // 创建 assistant 消息卡片 const msgBox = document.createElement("div"); msgBox.className = "llm-msg-item llm-assistant"; msgBox.style.margin = "3px 0 7px 0"; llmResultContent.appendChild(msgBox); llmResultContent.scrollTop = llmResultContent.scrollHeight; try { const llmToken = await getLLMToken(); const workspaceSlug = await getLLMWorkspaceSlug(llmToken); const url = `${LLM_STREAM_API_BASE}/${workspaceSlug}/stream-chat`; const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${llmToken}`, }, body: JSON.stringify({ message: question, attachments: [] }), }); if (!res.ok || !res.body) { msgBox.innerHTML += "LLM接口请求失败"; return; } const reader = res.body.getReader(); const decoder = new TextDecoder("utf-8"); let buffer = ""; let done = false; let stop = false; let inThink = false; let thinkStr = ""; let answerStr = ""; while (!done) { const { value, done: readerDone } = await reader.read(); done = readerDone; if (value) { buffer += decoder.decode(value, { stream: true }); let lines = buffer.split("\n"); buffer = lines.pop(); for (let line of lines) { if (line.startsWith("data: ")) { const data = line.replace("data: ", ""); try { const chunk = JSON.parse(data); if (chunk.type === "textResponseChunk") { let content = chunk.textResponse || ""; if (content.includes("")) { inThink = true; content = content.replace("", ""); } if (content.includes("")) { inThink = false; content = content.replace("", ""); } if (inThink) { thinkStr += content.replace(/\n/g, "
"); } else if (chunk.textResponse) { answerStr += content.replace(/\n/g, "
"); } // 实时渲染,思考内容和正式回答分区块 msgBox.innerHTML = (thinkStr ? `
模型思考过程:${thinkStr}
` : "") + `智能助手:${answerStr}`; llmResultContent.scrollTop = llmResultContent.scrollHeight; if (chunk.close) { stop = true; break; } } } catch (e) {} } } } if (stop) break; } if (!thinkStr && !answerStr) { msgBox.innerHTML += "无有效回答"; } } catch (e) { msgBox.innerHTML += "LLM请求异常:" + e + ""; } } function log(...args) { const version = chrome?.runtime?.getManifest?.().version || "dev"; console.log("[v" + version + "]", ...args); } // 长期免登录机制 async function loadToken() { return new Promise((resolve) => { chrome.storage.local.get(["token", "loginAt", "username"], (res) => { console.log("本地存储内容:", res); const now = Date.now(); if (!res.token || !res.loginAt || isNaN(res.loginAt) || (now - res.loginAt > TOKEN_EXPIRE_MS)) { console.log("token 不存在或已过期"); resolve(null); } else { console.log("token 加载成功"); resolve({ token: res.token, username: res.username || "用户" }); } }); }); } function handleLogout() { chrome.storage.local.remove(["token", "loginAt", "username"], () => { window.location.href = chrome.runtime.getURL("pages/login.html"); }); } function showLoading() { resultBox.innerHTML = "
  • 加载中...
  • "; } 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 prevBtn = document.createElement("button"); prevBtn.textContent = "上一页"; prevBtn.onclick = () => fetchResults(lastKeywords, currentPage - 1); pagination.appendChild(prevBtn); } if (currentPage < totalPages) { const nextBtn = document.createElement("button"); nextBtn.textContent = "下一页"; nextBtn.onclick = () => fetchResults(lastKeywords, currentPage + 1); pagination.appendChild(nextBtn); } resultBox.appendChild(pagination); } 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() { chrome.storage.local.get(["searchHistory"], (res) => { const history = res.searchHistory || []; historyBox.innerHTML = ""; if (history.length === 0) { 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"); }); } async function fetchResults(keywords, page) { log("执行搜索:", keywords, "page:", page); if (!token) { log("token 未初始化,跳转 login"); handleLogout(); return; } currentPage = page; lastKeywords = keywords; const keywordGroups = keywords.map(k => [k]); const requestBody = { keywordGroups, page: currentPage, size: 10 }; showLoading(); try { const response = await fetch("http://app.wisdompulse.cn/search", { method: "POST", headers: { "Authorization": `${token}`, "Content-Type": "application/json" }, body: JSON.stringify(requestBody) }); log("响应状态:", response.status); if (response.status === 401) { log("后端返回 401,token 无效,登出"); handleLogout(); return; } const data = await response.json(); resultBox.innerHTML = ""; if (!data.results || data.results.length === 0) { resultBox.innerHTML = "
  • 未找到相关文档
  • "; return; } totalPages = Math.ceil(data.total / data.size); data.results.forEach(doc => { const li = document.createElement("li"); const cleanPath = doc.filepath.replace(/^import-data\//, "").replace(/\.md$/, ".html"); const link = document.createElement("a"); link.href = `http://share.wisdompulse.cn/public/${cleanPath}`; link.target = "_blank"; link.textContent = doc.filename; const snippet = document.createElement("div"); snippet.className = "summary"; snippet.innerHTML = doc.summary?.slice(0, 300) + "..."; li.appendChild(link); li.appendChild(snippet); resultBox.appendChild(li); }); renderPagination(); } catch (err) { console.log("搜索请求失败:", err); resultBox.innerHTML = "
  • 搜索失败,请检查网络或服务状态。
  • "; } } function triggerSearch() { if (!token) { log("triggerSearch 中 token 丢失,取消搜索"); return; } const val = input.value.trim(); if (!val) return clearResults(); const keywords = val.split(/\s+/); updateHistory(val); // 并发触发 AnythingLLM + ES fetchLLMAnswer(val); fetchResults(keywords, 1); } function bindEvents() { log("绑定事件成功"); searchBtn.addEventListener("click", triggerSearch); input.addEventListener("keydown", (e) => { if (e.key === "Enter") { log("用户按下回车"); triggerSearch(); } }); input.addEventListener("focus", renderHistoryTags); input.addEventListener("input", () => !input.value.trim() && clearResults()); input.addEventListener("blur", () => setTimeout(() => historyWrapper.classList.add("hidden"), 200)); clearHistoryBtn.addEventListener("click", () => { chrome.storage.local.remove("searchHistory", renderHistoryTags); }); logoutBtn.addEventListener("click", handleLogout); userAvatar.addEventListener("click", () => userDropdown.classList.toggle("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); }); llmInput.addEventListener("keydown", function(e) { if (e.key === "Enter") { llmSendBtn.click(); } }); } const auth = await loadToken(); if (!auth) { log("未通过认证,执行登出"); handleLogout(); } else { token = auth.token; userNameLabel.textContent = auth.username; log("用户认证通过,用户名:", auth.username); bindEvents(); } }); // 拖动+缩放 (function() { const panel = document.getElementById('draggablePanel'); const header = document.querySelector('.header'); let dragging = false, offsetX = 0, offsetY = 0; header.addEventListener('mousedown', function(e) { dragging = true; offsetX = e.clientX - panel.offsetLeft; offsetY = e.clientY - panel.offsetTop; document.body.style.userSelect = 'none'; }); document.addEventListener('mousemove', function(e) { if (dragging) { let left = Math.max(0, e.clientX - offsetX); let top = Math.max(0, e.clientY - offsetY); panel.style.left = left + 'px'; panel.style.top = top + 'px'; } }); document.addEventListener('mouseup', function() { dragging = false; document.body.style.userSelect = ''; }); })();