diff --git a/manifest.json b/manifest.json index c29ed71..e81e3fc 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "渠道应用开发部知识库", - "version": "2.0.15", + "version": "2.1.0", "description": "通过关键词快速搜索内部文档", "permissions": ["storage"], "host_permissions": [ diff --git a/pages/popup.html b/pages/popup.html index 0150997..3eaf774 100644 --- a/pages/popup.html +++ b/pages/popup.html @@ -51,8 +51,9 @@
- + +
diff --git a/readme.md b/readme.md index f5b2fdb..4018b97 100644 --- a/readme.md +++ b/readme.md @@ -14,10 +14,11 @@ | 2025.06.19 | V2.0.8 | Luke.Ye | 对象存储上传优化,适配后端新接口 | | 2025.06.20 | V2.0.9 | Luke.Ye | 上传记录页面支持删除历史记录(会同时清空对象存储和ES) | | 2025.06.22 | V2.0.10 | Luke.Ye | 搜索结果页分页展示优化 | -| 2025.06.23 | V2.0.11 | Luke.Ye | 准备做llm接口迁移 | +| 2025.06.23 | V2.0.11 | Luke.Ye | 完成llm接口迁移 | | 2025.06.24 | V2.0.12 | Luke.Ye | 添加测试页面 | | 2025.06.28 | V2.0.13 | Luke.Ye | 优化分页展示 | | 2025.07.03 | V2.0.14 | Luke.Ye | test页面逻辑优化 | | 2025.07.09 | V2.0.15 | Luke.Ye | 优化“智能助手”展示页面 | +| 2025.07.10 | V2.1.0 | Luke.Ye | 完成llm问答历史记录保存 | diff --git a/scripts/llm.js b/scripts/llm.js index 4bf6a8d..a4cc9d0 100644 --- a/scripts/llm.js +++ b/scripts/llm.js @@ -40,7 +40,6 @@ async function resolveJumpLinks(titles) { }); } - async function renderSources(sourceList, sources, jumpMap) { sourceList.style.marginTop = "12px"; sourceList.style.borderTop = "1px solid #dbe3f3"; @@ -271,7 +270,6 @@ function promptLLMPasswordModal() { }); } - async function getLLMToken(forceRefresh = false) { const config = await loadMergedConfig(); const auth = await loadUserToken(); @@ -333,14 +331,17 @@ async function getLLMToken(forceRefresh = false) { }); } -// 放在全局(或函数外部,确保多次渲染只绑定一次即可) +// =========== 智能助手核心 ============ let isUserAtBottom = true; let scrollListenerInited = false; async function fetchLLMAnswer(question, container, contentDiv) { if (!question || !question.trim()) return; - // 获取Token:关键是加try-catch! + // 历史:用户输入时追加 + appendLlmHistory({ role: "user", text: question }); + + // 获取Token let llmToken; try { llmToken = await getLLMToken(); @@ -464,12 +465,12 @@ async function fetchLLMAnswer(question, container, contentDiv) { bindAllCopyButtons(msgBox); insertCopyFullAnswerBtn(msgBox, answerStr); - // === 核心优化:只在用户停留底部时才自动滚到底 === + // 只在用户停留底部时才自动滚到底 if (resultContent && isUserAtBottom) { resultContent.scrollTop = resultContent.scrollHeight; } - // 收尾渲染引用 + // 收尾渲染引用 & 持久化历史 if (chunk.close === true) { if (collectedSources.length > 0) { const titles = collectedSources.map(s => s.title).filter(Boolean); @@ -481,6 +482,15 @@ async function fetchLLMAnswer(question, container, contentDiv) { await renderSources(sourceList, collectedSources, jumpMap); insertCopyFullAnswerBtn(msgBox, answerStr); } + // 历史追加 assistant 回答 + appendLlmHistory({ + role: "assistant", + text: answerStr, + sources: collectedSources, + jumpMap: jumpMap + }); + // 自动滚动到底部 + if (resultContent) resultContent.scrollTop = resultContent.scrollHeight; return; } } @@ -492,5 +502,109 @@ async function fetchLLMAnswer(question, container, contentDiv) { } } +// 历史加载 +async function renderLlmHistory(contentDiv) { + chrome.storage.local.get(["llmHistory"], (res) => { + const history = res.llmHistory || []; + contentDiv.innerHTML = ""; + for (const item of history) { + if (item.role === 'user') { + const userItem = document.createElement("div"); + userItem.className = "llm-msg-item llm-user"; + userItem.innerHTML = `我: ${item.text}`; + contentDiv.appendChild(userItem); + } else if (item.role === 'assistant') { + const msgBox = document.createElement("div"); + msgBox.className = "llm-msg-item llm-assistant"; + let html = ""; + try { + html = window.marked ? wrapHtmlWithCopyBtn(marked.parse(item.text)) : item.text.replace(/\n/g, "
"); + } catch (e) { + html = item.text.replace(/\n/g, "
"); + } + msgBox.innerHTML = `智能助手:
${html}
`; + bindAllCopyButtons(msgBox); + insertCopyFullAnswerBtn(msgBox, item.text); + + // 渲染参考资料 + if (item.sources && item.sources.length) { + const sourceList = document.createElement("div"); + sourceList.className = "llm-sources"; + msgBox.appendChild(sourceList); + renderSources(sourceList, item.sources, item.jumpMap || {}); + insertCopyFullAnswerBtn(msgBox, item.text); + } + contentDiv.appendChild(msgBox); + } + } + // 自动滚到底 + contentDiv.scrollTop = contentDiv.scrollHeight; + }); +} + +// 对话历史追加 +function appendLlmHistory(item) { + chrome.storage.local.get(["llmHistory"], (res) => { + const history = res.llmHistory || []; + history.push(item); + chrome.storage.local.set({ llmHistory: history }); + }); +} + +// 清空历史按钮绑定 +document.addEventListener("DOMContentLoaded", () => { + const clearBtn = document.getElementById("clearLlmHistoryBtn"); + if (clearBtn) { + clearBtn.onclick = () => { + if (confirm("确定要清空智能助手历史记录吗?")) { + chrome.storage.local.set({ llmHistory: [] }, () => { + const contentDiv = document.getElementById("llmResultContent"); + if (contentDiv) contentDiv.innerHTML = ""; + }); + } + } + } + + // textarea 自动高度适应 + const input = document.getElementById("llmInput"); + const sendBtn = document.getElementById("llmSendBtn"); + + if (input && input.tagName.toLowerCase() === 'textarea') { + function autoResizeTextarea(el) { + el.style.height = 'auto'; + el.style.height = el.scrollHeight + 'px'; + } + input.addEventListener("input", function() { + autoResizeTextarea(input); + }); + autoResizeTextarea(input); + + // 发送后清空并适应高度 + sendBtn.addEventListener("click", () => { + setTimeout(() => { + input.value = ""; + autoResizeTextarea(input); + }, 10); // 让你的业务代码先执行(如提取 input.value 后再清空) + }); + + // 支持Enter发送,Ctrl+Enter/Shift+Enter换行 + input.addEventListener("keydown", function(e) { + if (e.key === "Enter" && !e.ctrlKey && !e.shiftKey) { + e.preventDefault(); + sendBtn.click(); + } + // 其他情况浏览器原生处理 + }); + } +}); +// 智能助手 tab 被激活时刷新历史 +function tryRenderLlmHistoryOnTabSwitch() { + const tabAi = document.getElementById("tabAi"); + tabAi && tabAi.addEventListener("click", () => { + const contentDiv = document.getElementById("llmResultContent"); + renderLlmHistory(contentDiv); + }); +} +tryRenderLlmHistoryOnTabSwitch(); diff --git a/scripts/popup.js b/scripts/popup.js index 04c9dc4..0022a27 100644 --- a/scripts/popup.js +++ b/scripts/popup.js @@ -196,9 +196,6 @@ document.addEventListener("DOMContentLoaded", async () => { resultBox.appendChild(pagination); } - - - async function fetchResults(rawInput, page) { currentPage = page; lastKeywords = rawInput; @@ -350,20 +347,39 @@ document.addEventListener("DOMContentLoaded", async () => { } }); + // 智能助手发送(带高度自适应) 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(); + if (llmInput.tagName.toLowerCase() === 'textarea') { + setTimeout(() => { + llmInput.style.height = 'auto'; + }, 10); } }); + // 智能助手输入框:Enter发送,Ctrl/Shift+Enter换行 + if (llmInput && llmInput.tagName.toLowerCase() === 'textarea') { + function autoResizeTextarea(el) { + el.style.height = 'auto'; + el.style.height = el.scrollHeight + 'px'; + } + llmInput.addEventListener("input", function() { + autoResizeTextarea(llmInput); + }); + autoResizeTextarea(llmInput); + + llmInput.addEventListener("keydown", function(e) { + if (e.key === "Enter" && !e.ctrlKey && !e.shiftKey) { + e.preventDefault(); + llmSendBtn.click(); + } + // 其余情况(含 Ctrl/Shift+Enter),浏览器原生换行 + }); + } + openTabBtn.addEventListener("click", () => { const val = input?.value?.trim(); const encoded = encodeURIComponent(val || ""); diff --git a/styles/styles-llm.css b/styles/styles-llm.css index cb193de..9fcf677 100644 --- a/styles/styles-llm.css +++ b/styles/styles-llm.css @@ -54,6 +54,14 @@ border-radius: 5px; font-size: 14px; outline: none; + min-height: 34px; + max-height: 120px; + line-height: 1.6; + resize: none; /* 禁止用户直接拖动 */ + box-sizing: border-box; + background: #fff; + transition: border 0.18s; + overflow-y: auto; } #llmSendBtn {