diff --git a/manifest.json b/manifest.json index 91a7d93..0c50c35 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "渠道应用开发部知识库", - "version": "1.3.1", + "version": "1.3.2", "description": "通过关键词快速搜索内部文档", "permissions": ["storage"], "host_permissions": [ diff --git a/pages/login.js b/pages/login.js index 5e513d6..d8134f7 100644 --- a/pages/login.js +++ b/pages/login.js @@ -22,10 +22,11 @@ function doLogin() { .then(res => res.json().then(data => ({ status: res.status, body: data }))) .then(({ status, body }) => { if (status === 200 && body.token) { + // 登录成功,保存登录时间 chrome.storage.local.set({ token: body.token, username: body.username || username, - loginAt: Date.now() + loginAt: Date.now() // 毫秒时间戳 }, () => { window.location.href = chrome.runtime.getURL("pages/popup.html"); }); diff --git a/pages/popup.html b/pages/popup.html index 1d0c4d7..5d7ff31 100644 --- a/pages/popup.html +++ b/pages/popup.html @@ -37,9 +37,14 @@ + diff --git a/pages/popup.js b/pages/popup.js index 0fb5664..5919fc9 100644 --- a/pages/popup.js +++ b/pages/popup.js @@ -8,7 +8,9 @@ document.addEventListener("DOMContentLoaded", async () => { // AnythingLLM 相关节点 const llmResultContainer = document.getElementById("llmResultContainer"); - const llmResultBox = document.getElementById("llmResult"); + 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"); @@ -20,7 +22,7 @@ document.addEventListener("DOMContentLoaded", async () => { let totalPages = 1; let lastKeywords = []; - const TOKEN_EXPIRE_MS = 24 * 60 * 60 * 1000; + const TOKEN_EXPIRE_MS = 7 * 24 * 60 * 60 * 1000; // 7天有效期 // === AnythingLLM 配置 === const LLM_PASSWORD = "lukeye@6"; @@ -69,7 +71,7 @@ document.addEventListener("DOMContentLoaded", async () => { }); } - // 获取 LLM workspaceSlug(修正) + // 获取 LLM workspaceSlug async function getLLMWorkspaceSlug(token, forceRefresh = false) { return new Promise((resolve, reject) => { chrome.storage.local.get(["llmWorkspaceSlug", "llmWorkspaceAt"], async (res) => { @@ -111,17 +113,36 @@ document.addEventListener("DOMContentLoaded", async () => { }); } - // 调用 AnythingLLM stream-chat 并流式渲染 + // 追加智能助手问答内容 + 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; + } + + // 支持多次提问、历史堆叠 + // 新增/修正流式渲染 assistant 的方式 async function fetchLLMAnswer(question) { - llmResultBox.innerHTML = ""; + if (!question || !question.trim()) return; + appendLLMMsg(question, "user"); llmResultContainer.classList.remove("hidden"); + + // 创建 assistant 消息卡片并持续写入 + const msgBox = document.createElement("div"); + msgBox.className = "llm-msg-item llm-assistant"; + msgBox.style.margin = "3px 0 7px 0"; + msgBox.innerHTML = `智能助手: `; + llmResultContent.appendChild(msgBox); + llmResultContent.scrollTop = llmResultContent.scrollHeight; + 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: { @@ -131,8 +152,7 @@ document.addEventListener("DOMContentLoaded", async () => { body: JSON.stringify({ message: question, attachments: [] }), }); if (!res.ok || !res.body) { - console.log("LLM stream-chat 请求失败,状态:", res.status); - llmResultBox.innerHTML = "LLM接口请求失败"; + msgBox.innerHTML += "LLM接口请求失败"; return; } const reader = res.body.getReader(); @@ -140,6 +160,7 @@ document.addEventListener("DOMContentLoaded", async () => { let buffer = ""; let done = false; let stop = false; + let answer = ""; while (!done) { const { value, done: readerDone } = await reader.read(); done = readerDone; @@ -152,30 +173,28 @@ document.addEventListener("DOMContentLoaded", async () => { 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; + answer += chunk.textResponse.replace(/\n/g, "
"); + msgBox.innerHTML = `智能助手: ${answer}`; + llmResultContent.scrollTop = llmResultContent.scrollHeight; } } - } catch (e) { - console.log("LLM 片段解析异常:", e, "行内容:", line); - } + } catch (e) {} } } } if (stop) break; } - console.log("LLM stream-chat 已完成。"); + if (!answer) { + msgBox.innerHTML += "无有效回答"; + } } catch (e) { - console.log("LLM stream-chat 全流程异常:", e); - llmResultBox.innerHTML = `LLM请求异常:${e}`; - llmResultContainer.classList.remove("hidden"); + msgBox.innerHTML += "LLM请求异常:" + e + ""; } } @@ -184,16 +203,17 @@ document.addEventListener("DOMContentLoaded", async () => { console.log("[v" + version + "]", ...args); } + // 长期免登录机制 async function loadToken() { return new Promise((resolve) => { chrome.storage.local.get(["token", "loginAt", "username"], (res) => { - log("加载本地存储 token:", res); + console.log("本地存储内容:", res); const now = Date.now(); - if (!res.token || !res.loginAt || (now - res.loginAt > TOKEN_EXPIRE_MS)) { - log("token 不存在或已过期"); + if (!res.token || !res.loginAt || isNaN(res.loginAt) || (now - res.loginAt > TOKEN_EXPIRE_MS)) { + console.log("token 不存在或已过期"); resolve(null); } else { - log("token 加载成功"); + console.log("token 加载成功"); resolve({ token: res.token, username: res.username || "用户" @@ -215,7 +235,7 @@ document.addEventListener("DOMContentLoaded", async () => { function clearResults() { resultBox.innerHTML = ""; - llmResultBox.innerHTML = ""; + llmResultContent.innerHTML = ""; llmResultContainer.classList.add("hidden"); historyWrapper.classList.add("hidden"); } @@ -385,6 +405,19 @@ document.addEventListener("DOMContentLoaded", async () => { 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(); @@ -398,3 +431,31 @@ document.addEventListener("DOMContentLoaded", async () => { 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 = ''; + }); +})(); diff --git a/pages/styles.css b/pages/styles.css index bd63db1..7e6a16c 100644 --- a/pages/styles.css +++ b/pages/styles.css @@ -280,12 +280,22 @@ h1 { } .llm-title { + background: linear-gradient(90deg, #2560de 90%, #418cfb 100%); + color: #fff; + font-size: 16px; font-weight: bold; - color: #2453b5; - margin-bottom: 4px; - font-size: 14px; + padding: 12px 12px 10px 12px; + margin: 0 -10px 6px -10px; + border-bottom: 1px solid #b3cdfb; + border-radius: 8px 8px 0 0; + position: sticky; + top: 0; + z-index: 2; + letter-spacing: 1px; + box-shadow: 0 2px 6px #2764bb11; } + #llmResult { font-size: 14px; color: #233; @@ -294,3 +304,91 @@ h1 { white-space: pre-wrap; } +.llm-result-container { + margin-bottom: 14px; + max-height: 300px; + min-height: 60px; + overflow: auto; + border: 1px solid #e0e9f4; + border-radius: 7px; + padding: 0 12px 8px 12px; + background: #f8fbff; + display: flex; + flex-direction: column; + resize: vertical; +} + +.llm-title { + position: sticky; + top: 0; + background: #f8fbff; + font-weight: bold; + color: #2453b5; + margin: 0 -12px 8px -12px; + padding: 8px 12px 6px 12px; + font-size: 14px; + border-bottom: 1px solid #e0e9f4; + z-index: 2; +} + +.llm-result-content { + flex: 1; + overflow-y: auto; + min-height: 44px; + font-size: 14px; + color: #233; + line-height: 1.7; + word-break: break-all; + white-space: pre-wrap; +} + +.llm-input-bar { + display: flex; + gap: 8px; + margin-top: 8px; + padding-bottom: 2px; + align-items: center; +} + +#llmInput { + flex: 1; + padding: 6px 10px; + border: 1px solid #c9dafc; + border-radius: 5px; + font-size: 14px; + outline: none; +} + +#llmSendBtn { + padding: 6px 14px; + background: #4179f6; + color: #fff; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + transition: background 0.2s; +} + +#llmSendBtn:hover { + background: #2350b8; +} + +.llm-msg-item.llm-assistant { + color: #2453b5; + background: #e7eefb; + border-radius: 5px; + margin: 3px 0 9px 0; + padding: 7px 8px; + word-break: break-all; + font-weight: 500; + border: 1px solid #b3cdfb; +} + +/* 只让“智能助手:”四字加粗/蓝色(需配合js输出span) */ +.llm-msg-item.llm-assistant .assistant-label { + color: #2560de; + font-weight: bold; + margin-right: 3px; +} +