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 llmResultBox = document.getElementById("llmResult"); 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 = 24 * 60 * 60 * 1000; // === AnythingLLM 配置 === const LLM_PASSWORD = "lukeye@6"; 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; // 获取 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 { console.log("开始请求 LLM token,POST", LLM_TOKEN_API, "参数:", { password: LLM_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工作区接口异常"); } } }); }); } // 调用 AnythingLLM stream-chat 并流式渲染 async function fetchLLMAnswer(question) { llmResultBox.innerHTML = ""; llmResultContainer.classList.remove("hidden"); try { console.log("开始获取 LLM token 和 workspaceSlug..."); const llmToken = await getLLMToken(); const workspaceSlug = await getLLMWorkspaceSlug(llmToken); const url = `${LLM_STREAM_API_BASE}/${workspaceSlug}/stream-chat`; console.log("开始请求 LLM stream-chat,POST", url, "参数:", { message: question, attachments: [] }); 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) { console.log("LLM stream-chat 请求失败,状态:", res.status); llmResultBox.innerHTML = "LLM接口请求失败"; return; } const reader = res.body.getReader(); const decoder = new TextDecoder("utf-8"); let buffer = ""; let done = false; let stop = false; 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); console.log("LLM 返回片段:", chunk); if (chunk.type === "textResponseChunk") { if (chunk.close) { stop = true; break; } if (chunk.textResponse) { llmResultBox.innerHTML += chunk.textResponse.replace(/\n/g, "
"); llmResultBox.scrollTop = llmResultBox.scrollHeight; } } } catch (e) { console.log("LLM 片段解析异常:", e, "行内容:", line); } } } } if (stop) break; } console.log("LLM stream-chat 已完成。"); } catch (e) { console.log("LLM stream-chat 全流程异常:", e); llmResultBox.innerHTML = `LLM请求异常:${e}`; llmResultContainer.classList.remove("hidden"); } } 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) => { log("加载本地存储 token:", res); const now = Date.now(); if (!res.token || !res.loginAt || (now - res.loginAt > TOKEN_EXPIRE_MS)) { log("token 不存在或已过期"); resolve(null); } else { 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 = ""; llmResultBox.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"); } }); } const auth = await loadToken(); if (!auth) { log("未通过认证,执行登出"); handleLogout(); } else { token = auth.token; userNameLabel.textContent = auth.username; log("用户认证通过,用户名:", auth.username); bindEvents(); } });