From cea109d8ace9e71dc5c3162ae91714ccb7c1c7fa Mon Sep 17 00:00:00 2001 From: luke Date: Mon, 2 Jun 2025 01:47:35 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=85=8D=E7=BD=AE=E9=A1=B5?= =?UTF-8?q?=EF=BC=8C40%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/popup.html | 6 +- pages/settings.html | 152 ++++++++++++++++++++++++++++++++++++++++++++ scripts/config.js | 99 +++++++++++++++++++++++++++++ scripts/popup.js | 109 +++++++++++-------------------- scripts/settings.js | 125 ++++++++++++++++++++++++++++++++++++ 5 files changed, 417 insertions(+), 74 deletions(-) create mode 100644 pages/settings.html create mode 100644 scripts/config.js create mode 100644 scripts/settings.js diff --git a/pages/popup.html b/pages/popup.html index f3ea594..166eee8 100644 --- a/pages/popup.html +++ b/pages/popup.html @@ -1,3 +1,4 @@ + @@ -18,6 +19,7 @@
👤
@@ -55,12 +57,12 @@ - + + - diff --git a/pages/settings.html b/pages/settings.html new file mode 100644 index 0000000..cf2cacf --- /dev/null +++ b/pages/settings.html @@ -0,0 +1,152 @@ + + + + + 插件设置 + + + + + +

插件设置

+ +
+

通用设置

+
+ + +
超出有效期将要求重新登录
+
+
+ +
+

接口配置

+ +
+ +
+ + +
+ + + + + diff --git a/scripts/config.js b/scripts/config.js new file mode 100644 index 0000000..6c0e88b --- /dev/null +++ b/scripts/config.js @@ -0,0 +1,99 @@ +const defaultConfig = { + tokenExpireHours: 24, + apiConfig: { + esSearch: { + name: "ES 搜索接口", + url: "http://app.wisdompulse.cn/search", + requestParams: [ + { label: "关键词组", field: "keywordGroups" }, + { label: "页码", field: "page" }, + { label: "分页大小", field: "size" } + ], + responseParams: [ + { label: "结果字段", field: "results" }, + { label: "总条数字段", field: "total" }, + { label: "分页大小字段", field: "size" } + ] + }, + materialPublicUrl: { + name: "材料公开地址", + url: "http://share.wisdompulse.cn/public", + requestParams: [], + responseParams: [] + }, + login: { + name: "登录接口", + url: "http://app.wisdompulse.cn/api/v1/user/login", + requestParams: [ + { label: "用户名字段", field: "username" }, + { label: "密码字段", field: "password" } + ], + responseParams: [ + { label: "Token 字段", field: "token" }, + { label: "用户名字段", field: "username" } + ] + }, + filePath: { + name: "获取文件路径接口", + url: "http://app.wisdompulse.cn/search/file-path", + requestParams: [ + { label: "文件名字段", field: "filename" } + ], + responseParams: [ + { label: "路径字段", field: "filepath" } + ] + }, + llmToken: { + name: "获取 AnythingLLM Token 接口", + url: "http://llm.wisdompulse.cn/api/request-token", + requestParams: [ + { label: "用户名字段", field: "username" } + ], + responseParams: [ + { label: "Token 字段", field: "token" } + ] + }, + workspaces: { + name: "获取工作区接口", + url: "http://llm.wisdompulse.cn/api/workspaces", + requestParams: [], + responseParams: [ + { label: "工作区数组字段", field: "workspaces" } + ] + }, + llmChat: { + name: "指定工作区对话接口", + url: "http://llm.wisdompulse.cn/api/workspace/${workId}/stream-chat", + requestParams: [ + { label: "问题字段", field: "prompt" }, + { label: "历史字段", field: "messages" } + ], + responseParams: [] + } + } +}; + + +function loadMergedConfig() { + return new Promise((resolve) => { + chrome.storage.local.get(["customConfig"], (res) => { + const merged = JSON.parse(JSON.stringify(defaultConfig)); + if (res.customConfig && typeof res.customConfig === "object") { + merged.tokenExpireHours = res.customConfig.tokenExpireHours || merged.tokenExpireHours; + for (const key in merged.apiConfig) { + const override = res.customConfig.apiConfig?.[key]; + if (override) { + merged.apiConfig[key].url = override.url || merged.apiConfig[key].url; + merged.apiConfig[key].requestParams.forEach((param, i) => { + if (override.requestParams?.[i]?.field) param.field = override.requestParams[i].field; + }); + merged.apiConfig[key].responseParams.forEach((param, i) => { + if (override.responseParams?.[i]?.field) param.field = override.responseParams[i].field; + }); + } + } + } + resolve(merged); + }); + }); +} diff --git a/scripts/popup.js b/scripts/popup.js index 9ed6649..cc09bca 100644 --- a/scripts/popup.js +++ b/scripts/popup.js @@ -1,10 +1,8 @@ document.addEventListener("DOMContentLoaded", async () => { - log("DOMContentLoaded - 页面加载完成"); - const panel = document.getElementById("draggablePanel"); const header = document.querySelector(".header"); + if (window.innerWidth > 900 && window.innerHeight > 700) { - log("启用全屏模式"); document.body.classList.add("fullscreen"); if (panel) { panel.style.left = ""; @@ -12,7 +10,6 @@ document.addEventListener("DOMContentLoaded", async () => { panel.style.position = "fixed"; } } else if (panel && header) { - log("启用可拖拽模式"); let dragging = false, offsetX = 0, offsetY = 0; header.addEventListener("mousedown", (e) => { dragging = true; @@ -32,7 +29,6 @@ document.addEventListener("DOMContentLoaded", async () => { }); } - // ====== 元素选择 ====== const input = document.getElementById("searchInput"); const searchBtn = document.getElementById("searchBtn"); const clearBtn = document.getElementById("clearHistoryBtn"); @@ -48,22 +44,20 @@ document.addEventListener("DOMContentLoaded", async () => { 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 = () => { - log("切换到 ES 结果页"); tabEs.classList.add("active"); tabAi.classList.remove("active"); tabContentEs.classList.add("active"); tabContentAi.classList.remove("active"); }; + tabAi.onclick = () => { - log("切换到 LLM 结果页"); tabEs.classList.remove("active"); tabAi.classList.add("active"); tabContentEs.classList.remove("active"); @@ -73,35 +67,22 @@ document.addEventListener("DOMContentLoaded", async () => { }, 100); }; - function getQueryParam(name) { - const url = new URL(window.location.href); - return url.searchParams.get(name); - } - - const isNewTab = getQueryParam("from") === "newtab"; + 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) { - log("用户未认证,执行登出"); - return handleLogout(); - } + if (!auth) return handleLogout(); token = auth.token; userNameLabel.textContent = auth.username; - log("用户认证成功,用户名:", auth.username); async function triggerSearch() { const raw = input?.value?.trim(); - if (!token || !raw) { - log("取消搜索:token 丢失或关键词为空"); - return; - } - - log("执行 triggerSearch,关键词:", raw, "是否新标签页:", isNewTab); + if (!token || !raw) return; updateHistory(raw); renderHistoryTags(true); if (!isNewTab) { @@ -127,7 +108,6 @@ document.addEventListener("DOMContentLoaded", async () => { } function clearResults() { - log("清空结果和历史区域"); resultBox.innerHTML = ""; llmResultContent.innerHTML = ""; llmResultContainer.classList.add("hidden"); @@ -155,18 +135,20 @@ document.addEventListener("DOMContentLoaded", async () => { async function fetchResults(rawInput, page) { currentPage = page; lastKeywords = rawInput; - log("调用 fetchResults,关键词:", rawInput, "页码:", page); 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 = { - keywordGroups, - page: currentPage, - size: 10 + [kwField]: keywordGroups, + [pageField]: page, + [sizeField]: 10 }; try { - const response = await fetch("http://app.wisdompulse.cn/search", { + const response = await fetch(endpoint.url, { method: "POST", headers: { "Authorization": `${token}`, @@ -175,31 +157,30 @@ document.addEventListener("DOMContentLoaded", async () => { body: JSON.stringify(requestBody) }); - log("搜索接口响应状态:", response.status); - - if (response.status === 401) { - log("token 已失效,执行登出"); - return handleLogout(); - } - + 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 (!data.results || data.results.length === 0) { - log("无搜索结果"); + if (!results || results.length === 0) { resultBox.innerHTML = "
  • 未找到相关文档
  • "; return; } - totalPages = Math.ceil(data.total / data.size); + totalPages = Math.ceil(total / pageSize); + const fileBaseUrl = config.apiConfig.materialPublicUrl.url; - data.results.forEach(doc => { + results.forEach(doc => { const li = document.createElement("li"); - const path = doc.filepath.replace(/^import-data\//, "").replace(/\.md$/, ".html"); + const filename = doc.filename || doc.title || "未知文件"; + const filepath = doc.filepath?.replace(/^import-data\//, "").replace(/\.md$/, ".html") || ""; const link = document.createElement("a"); - link.href = `http://share.wisdompulse.cn/public/${path}`; + link.href = `${fileBaseUrl}/${filepath || filename}`; link.target = "_blank"; - link.textContent = doc.filename; + link.textContent = filename; const snippet = document.createElement("div"); snippet.className = "summary"; @@ -212,13 +193,11 @@ document.addEventListener("DOMContentLoaded", async () => { renderPagination(); } catch (err) { - log("搜索请求异常:", err); resultBox.innerHTML = "
  • 搜索失败,请检查网络或服务状态。
  • "; } } function updateHistory(keyword) { - log("更新搜索历史:", keyword); chrome.storage.local.get(["searchHistory"], (res) => { let history = res.searchHistory || []; history = [keyword, ...history.filter(item => item !== keyword)]; @@ -228,7 +207,6 @@ document.addEventListener("DOMContentLoaded", async () => { } function renderHistoryTags(show = false) { - log("渲染历史标签,是否显示:", show); chrome.storage.local.get(["searchHistory"], (res) => { const history = res.searchHistory || []; historyBox.innerHTML = ""; @@ -253,11 +231,7 @@ document.addEventListener("DOMContentLoaded", async () => { }); } - // ====== DOM 事件绑定 ====== - searchBtn.addEventListener("click", () => { - log("点击搜索按钮"); - triggerSearch(); - }); + searchBtn.addEventListener("click", triggerSearch); input.addEventListener("keydown", e => { if (e.key === "Enter") { e.preventDefault(); @@ -266,26 +240,20 @@ document.addEventListener("DOMContentLoaded", async () => { }); input.addEventListener("focus", () => renderHistoryTags(true)); input.addEventListener("input", () => { - if (!input.value.trim()) { - log("输入清空,清除结果"); - clearResults(); - } + if (!input.value.trim()) clearResults(); }); input.addEventListener("blur", () => setTimeout(() => historyWrapper.classList.add("hidden"), 200)); clearBtn.addEventListener("click", () => { - log("点击清空历史按钮"); chrome.storage.local.remove("searchHistory", () => renderHistoryTags()); }); - logoutBtn?.addEventListener("click", () => { - log("点击退出登录按钮"); - handleLogout(); - }); - + logoutBtn?.addEventListener("click", () => handleLogout()); userAvatar?.addEventListener("click", () => { - log("点击用户头像,切换菜单"); 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) { @@ -297,7 +265,6 @@ document.addEventListener("DOMContentLoaded", async () => { const val = llmInput.value.trim(); if (!val) return; llmInput.value = ""; - log("发送 LLM 问题:", val); fetchLLMAnswer(val, llmResultContainer, llmResultContent); }); @@ -308,18 +275,16 @@ document.addEventListener("DOMContentLoaded", async () => { } }); - openTabBtn.onclick = () => { + openTabBtn.addEventListener("click", () => { const val = input?.value?.trim(); const encoded = encodeURIComponent(val || ""); const url = chrome.runtime.getURL("pages/popup.html?keywords=" + encoded + "&from=newtab"); - log("点击新标签页按钮,跳转到:", url); - chrome.tabs?.create({ url }); - }; + chrome.tabs.create({ url }); + }); - const autoKeywords = getQueryParam("keywords"); + const autoKeywords = new URLSearchParams(location.search).get("keywords"); if (autoKeywords) { input.value = decodeURIComponent(autoKeywords); - log("检测到 URL 参数 keywords,自动搜索:", input.value); triggerSearch(); } diff --git a/scripts/settings.js b/scripts/settings.js new file mode 100644 index 0000000..b3444d7 --- /dev/null +++ b/scripts/settings.js @@ -0,0 +1,125 @@ +// scripts/settings.js + +function getMergedConfig(callback) { + chrome.storage.local.get(["customConfig"], (res) => { + const merged = JSON.parse(JSON.stringify(defaultConfig)); + if (res.customConfig && typeof res.customConfig === "object") { + merged.tokenExpireHours = res.customConfig.tokenExpireHours || merged.tokenExpireHours; + for (const key in merged.apiConfig) { + const override = res.customConfig.apiConfig?.[key]; + if (override) { + merged.apiConfig[key].url = override.url || merged.apiConfig[key].url; + + merged.apiConfig[key].requestParams.forEach((param, i) => { + if (override.requestParams?.[i]?.field) param.field = override.requestParams[i].field; + }); + + merged.apiConfig[key].responseParams.forEach((param, i) => { + if (override.responseParams?.[i]?.field) param.field = override.responseParams[i].field; + }); + } + } + } + callback(merged); + }); +} + +function renderConfig(config) { + document.getElementById("tokenExpireHours").value = config.tokenExpireHours; + const container = document.getElementById("endpointSection"); + container.innerHTML = ""; + + for (const [key, endpoint] of Object.entries(config.apiConfig)) { + const block = document.createElement("div"); + block.className = "api-block"; + + block.innerHTML += `
    【${endpoint.name}】
    `; + + const urlRow = document.createElement("div"); + urlRow.className = "form-row"; + urlRow.innerHTML = ` + + + `; + block.appendChild(urlRow); + + const reqGroup = document.createElement("div"); + reqGroup.className = "param-group"; + reqGroup.innerHTML = `

    请求参数

    `; + endpoint.requestParams.forEach((param, index) => { + const row = document.createElement("div"); + row.className = "form-row"; + row.innerHTML = ` + + + `; + reqGroup.appendChild(row); + }); + block.appendChild(reqGroup); + + const resGroup = document.createElement("div"); + resGroup.className = "param-group"; + resGroup.innerHTML = `

    响应字段

    `; + endpoint.responseParams.forEach((param, index) => { + const row = document.createElement("div"); + row.className = "form-row"; + row.innerHTML = ` + + + `; + resGroup.appendChild(row); + }); + block.appendChild(resGroup); + + container.appendChild(block); + } +} + +function collectUserConfig() { + const tokenExpireHours = Math.min(168, parseInt(document.getElementById("tokenExpireHours").value) || 24); + const apiConfig = {}; + + const inputs = document.querySelectorAll("input[data-key]"); + for (const input of inputs) { + const key = input.dataset.key; + const type = input.dataset.type; + const index = input.dataset.index; + + if (!apiConfig[key]) { + apiConfig[key] = { requestParams: [], responseParams: [] }; + } + + if (type === "url") { + apiConfig[key].url = input.value.trim(); + } else if (type === "requestParams") { + apiConfig[key].requestParams[index] = { field: input.value.trim() }; + } else if (type === "responseParams") { + apiConfig[key].responseParams[index] = { field: input.value.trim() }; + } + } + + return { tokenExpireHours, apiConfig }; +} + +document.getElementById("saveBtn").addEventListener("click", () => { + const newConfig = collectUserConfig(); + chrome.storage.local.set({ customConfig: newConfig }, () => { + alert("设置已保存!"); + }); +}); + +document.getElementById("resetBtn").addEventListener("click", () => { + chrome.storage.local.remove("customConfig", () => { + alert("已恢复默认设置,刷新页面以生效"); + location.reload(); + }); +}); + +document.addEventListener("DOMContentLoaded", () => { + getMergedConfig(renderConfig); + const backBtn = document.getElementById("backToPopup"); + backBtn.addEventListener("click", () => { + const popupUrl = chrome.runtime.getURL("pages/popup.html"); + chrome.tabs.create({ url: popupUrl }); + }); +});