添加问答历史记录保存
This commit is contained in:
parent
e05f5f9c6e
commit
08ba30f1e3
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "渠道应用开发部知识库",
|
"name": "渠道应用开发部知识库",
|
||||||
"version": "2.0.15",
|
"version": "2.1.0",
|
||||||
"description": "通过关键词快速搜索内部文档",
|
"description": "通过关键词快速搜索内部文档",
|
||||||
"permissions": ["storage"],
|
"permissions": ["storage"],
|
||||||
"host_permissions": [
|
"host_permissions": [
|
||||||
|
|||||||
@ -51,8 +51,9 @@
|
|||||||
<div id="llmResultContainer" class="llm-result-container">
|
<div id="llmResultContainer" class="llm-result-container">
|
||||||
<div class="llm-result-content" id="llmResultContent"></div>
|
<div class="llm-result-content" id="llmResultContent"></div>
|
||||||
<div class="llm-input-bar">
|
<div class="llm-input-bar">
|
||||||
<input id="llmInput" type="text" placeholder="请输入您的问题..." />
|
<textarea id="llmInput" rows="1" placeholder="请输入您的问题..."></textarea>
|
||||||
<button id="llmSendBtn">发送</button>
|
<button id="llmSendBtn">发送</button>
|
||||||
|
<button id="clearLlmHistoryBtn" title="清空智能助手历史" style="margin-left:8px;">🗑️</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -14,10 +14,11 @@
|
|||||||
| 2025.06.19 | V2.0.8 | Luke.Ye | 对象存储上传优化,适配后端新接口 |
|
| 2025.06.19 | V2.0.8 | Luke.Ye | 对象存储上传优化,适配后端新接口 |
|
||||||
| 2025.06.20 | V2.0.9 | Luke.Ye | 上传记录页面支持删除历史记录(会同时清空对象存储和ES) |
|
| 2025.06.20 | V2.0.9 | Luke.Ye | 上传记录页面支持删除历史记录(会同时清空对象存储和ES) |
|
||||||
| 2025.06.22 | V2.0.10 | Luke.Ye | 搜索结果页分页展示优化 |
|
| 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.24 | V2.0.12 | Luke.Ye | 添加测试页面 |
|
||||||
| 2025.06.28 | V2.0.13 | Luke.Ye | 优化分页展示 |
|
| 2025.06.28 | V2.0.13 | Luke.Ye | 优化分页展示 |
|
||||||
| 2025.07.03 | V2.0.14 | Luke.Ye | test页面逻辑优化 |
|
| 2025.07.03 | V2.0.14 | Luke.Ye | test页面逻辑优化 |
|
||||||
| 2025.07.09 | V2.0.15 | Luke.Ye | 优化“智能助手”展示页面 |
|
| 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) {
|
async function renderSources(sourceList, sources, jumpMap) {
|
||||||
sourceList.style.marginTop = "12px";
|
sourceList.style.marginTop = "12px";
|
||||||
sourceList.style.borderTop = "1px solid #dbe3f3";
|
sourceList.style.borderTop = "1px solid #dbe3f3";
|
||||||
@ -271,7 +270,6 @@ function promptLLMPasswordModal() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function getLLMToken(forceRefresh = false) {
|
async function getLLMToken(forceRefresh = false) {
|
||||||
const config = await loadMergedConfig();
|
const config = await loadMergedConfig();
|
||||||
const auth = await loadUserToken();
|
const auth = await loadUserToken();
|
||||||
@ -333,14 +331,17 @@ async function getLLMToken(forceRefresh = false) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 放在全局(或函数外部,确保多次渲染只绑定一次即可)
|
// =========== 智能助手核心 ============
|
||||||
let isUserAtBottom = true;
|
let isUserAtBottom = true;
|
||||||
let scrollListenerInited = false;
|
let scrollListenerInited = false;
|
||||||
|
|
||||||
async function fetchLLMAnswer(question, container, contentDiv) {
|
async function fetchLLMAnswer(question, container, contentDiv) {
|
||||||
if (!question || !question.trim()) return;
|
if (!question || !question.trim()) return;
|
||||||
|
|
||||||
// 获取Token:关键是加try-catch!
|
// 历史:用户输入时追加
|
||||||
|
appendLlmHistory({ role: "user", text: question });
|
||||||
|
|
||||||
|
// 获取Token
|
||||||
let llmToken;
|
let llmToken;
|
||||||
try {
|
try {
|
||||||
llmToken = await getLLMToken();
|
llmToken = await getLLMToken();
|
||||||
@ -464,12 +465,12 @@ async function fetchLLMAnswer(question, container, contentDiv) {
|
|||||||
bindAllCopyButtons(msgBox);
|
bindAllCopyButtons(msgBox);
|
||||||
insertCopyFullAnswerBtn(msgBox, answerStr);
|
insertCopyFullAnswerBtn(msgBox, answerStr);
|
||||||
|
|
||||||
// === 核心优化:只在用户停留底部时才自动滚到底 ===
|
// 只在用户停留底部时才自动滚到底
|
||||||
if (resultContent && isUserAtBottom) {
|
if (resultContent && isUserAtBottom) {
|
||||||
resultContent.scrollTop = resultContent.scrollHeight;
|
resultContent.scrollTop = resultContent.scrollHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收尾渲染引用
|
// 收尾渲染引用 & 持久化历史
|
||||||
if (chunk.close === true) {
|
if (chunk.close === true) {
|
||||||
if (collectedSources.length > 0) {
|
if (collectedSources.length > 0) {
|
||||||
const titles = collectedSources.map(s => s.title).filter(Boolean);
|
const titles = collectedSources.map(s => s.title).filter(Boolean);
|
||||||
@ -481,6 +482,15 @@ async function fetchLLMAnswer(question, container, contentDiv) {
|
|||||||
await renderSources(sourceList, collectedSources, jumpMap);
|
await renderSources(sourceList, collectedSources, jumpMap);
|
||||||
insertCopyFullAnswerBtn(msgBox, answerStr);
|
insertCopyFullAnswerBtn(msgBox, answerStr);
|
||||||
}
|
}
|
||||||
|
// 历史追加 assistant 回答
|
||||||
|
appendLlmHistory({
|
||||||
|
role: "assistant",
|
||||||
|
text: answerStr,
|
||||||
|
sources: collectedSources,
|
||||||
|
jumpMap: jumpMap
|
||||||
|
});
|
||||||
|
// 自动滚动到底部
|
||||||
|
if (resultContent) resultContent.scrollTop = resultContent.scrollHeight;
|
||||||
return;
|
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);
|
resultBox.appendChild(pagination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function fetchResults(rawInput, page) {
|
async function fetchResults(rawInput, page) {
|
||||||
currentPage = page;
|
currentPage = page;
|
||||||
lastKeywords = rawInput;
|
lastKeywords = rawInput;
|
||||||
@ -350,20 +347,39 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 智能助手发送(带高度自适应)
|
||||||
llmSendBtn?.addEventListener("click", () => {
|
llmSendBtn?.addEventListener("click", () => {
|
||||||
const val = llmInput.value.trim();
|
const val = llmInput.value.trim();
|
||||||
if (!val) return;
|
if (!val) return;
|
||||||
llmInput.value = "";
|
llmInput.value = "";
|
||||||
fetchLLMAnswer(val, llmResultContainer, llmResultContent);
|
fetchLLMAnswer(val, llmResultContainer, llmResultContent);
|
||||||
});
|
if (llmInput.tagName.toLowerCase() === 'textarea') {
|
||||||
|
setTimeout(() => {
|
||||||
llmInput.addEventListener("keydown", e => {
|
llmInput.style.height = 'auto';
|
||||||
if (e.key === "Enter") {
|
}, 10);
|
||||||
e.preventDefault();
|
|
||||||
document.getElementById("llmSendBtn").click();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 智能助手输入框: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", () => {
|
openTabBtn.addEventListener("click", () => {
|
||||||
const val = input?.value?.trim();
|
const val = input?.value?.trim();
|
||||||
const encoded = encodeURIComponent(val || "");
|
const encoded = encodeURIComponent(val || "");
|
||||||
|
|||||||
@ -54,6 +54,14 @@
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
outline: none;
|
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 {
|
#llmSendBtn {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user