添加问答历史记录保存

This commit is contained in:
Luke.Ye 2025-07-10 09:56:17 +08:00
parent e05f5f9c6e
commit 08ba30f1e3
6 changed files with 158 additions and 18 deletions

View File

@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "渠道应用开发部知识库",
"version": "2.0.15",
"version": "2.1.0",
"description": "通过关键词快速搜索内部文档",
"permissions": ["storage"],
"host_permissions": [

View File

@ -51,8 +51,9 @@
<div id="llmResultContainer" class="llm-result-container">
<div class="llm-result-content" id="llmResultContent"></div>
<div class="llm-input-bar">
<input id="llmInput" type="text" placeholder="请输入您的问题..." />
<textarea id="llmInput" rows="1" placeholder="请输入您的问题..."></textarea>
<button id="llmSendBtn">发送</button>
<button id="clearLlmHistoryBtn" title="清空智能助手历史" style="margin-left:8px;">🗑️</button>
</div>
</div>
</div>

View File

@ -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问答历史记录保存 |

View File

@ -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 = `<span style="color:#2a63c8;font-weight:bold;">我:</span> ${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, "<br>");
} catch (e) {
html = item.text.replace(/\n/g, "<br>");
}
msgBox.innerHTML = `<span class="assistant-label">智能助手:</span><div class="llm-md">${html}</div>`;
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();

View File

@ -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 || "");

View File

@ -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 {