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 {