添加问答历史记录保存
This commit is contained in:
parent
e05f5f9c6e
commit
08ba30f1e3
@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "渠道应用开发部知识库",
|
||||
"version": "2.0.15",
|
||||
"version": "2.1.0",
|
||||
"description": "通过关键词快速搜索内部文档",
|
||||
"permissions": ["storage"],
|
||||
"host_permissions": [
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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问答历史记录保存 |
|
||||
|
||||
|
||||
|
||||
126
scripts/llm.js
126
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 = `<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();
|
||||
|
||||
@ -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 || "");
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user