Compare commits

..

2 Commits

Author SHA1 Message Date
df177dff58 大模型调用优化 2025-05-28 16:56:54 +08:00
8c311bce87 大模型调用优化 2025-05-28 16:42:34 +08:00
5 changed files with 234 additions and 33 deletions

View File

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

View File

@ -22,10 +22,11 @@ function doLogin() {
.then(res => res.json().then(data => ({ status: res.status, body: data }))) .then(res => res.json().then(data => ({ status: res.status, body: data })))
.then(({ status, body }) => { .then(({ status, body }) => {
if (status === 200 && body.token) { if (status === 200 && body.token) {
// 登录成功,保存登录时间
chrome.storage.local.set({ chrome.storage.local.set({
token: body.token, token: body.token,
username: body.username || username, username: body.username || username,
loginAt: Date.now() loginAt: Date.now() // 毫秒时间戳
}, () => { }, () => {
window.location.href = chrome.runtime.getURL("pages/popup.html"); window.location.href = chrome.runtime.getURL("pages/popup.html");
}); });

View File

@ -37,8 +37,13 @@
<!-- AnythingLLM结果区域 --> <!-- AnythingLLM结果区域 -->
<div id="llmResultContainer" class="llm-result-container hidden"> <div id="llmResultContainer" class="llm-result-container hidden">
<div class="llm-title">智能助手答案</div> <div class="llm-title">智能助手答案</div>
<div id="llmResult"></div> <div class="llm-result-content" id="llmResultContent"></div>
<div class="llm-input-bar">
<input id="llmInput" type="text" placeholder="请输入您的问题..." />
<button id="llmSendBtn">发送</button>
</div> </div>
</div>
<ul id="results"></ul> <ul id="results"></ul>
</div> </div>

View File

@ -8,7 +8,9 @@ document.addEventListener("DOMContentLoaded", async () => {
// AnythingLLM 相关节点 // AnythingLLM 相关节点
const llmResultContainer = document.getElementById("llmResultContainer"); const llmResultContainer = document.getElementById("llmResultContainer");
const llmResultBox = document.getElementById("llmResult"); const llmResultContent = document.getElementById("llmResultContent");
const llmInput = document.getElementById("llmInput");
const llmSendBtn = document.getElementById("llmSendBtn");
const userDropdown = document.getElementById("userDropdown"); const userDropdown = document.getElementById("userDropdown");
const logoutBtn = document.getElementById("logoutBtn"); const logoutBtn = document.getElementById("logoutBtn");
@ -20,7 +22,7 @@ document.addEventListener("DOMContentLoaded", async () => {
let totalPages = 1; let totalPages = 1;
let lastKeywords = []; let lastKeywords = [];
const TOKEN_EXPIRE_MS = 24 * 60 * 60 * 1000; const TOKEN_EXPIRE_MS = 7 * 24 * 60 * 60 * 1000; // 7天有效期
// === AnythingLLM 配置 === // === AnythingLLM 配置 ===
const LLM_PASSWORD = "lukeye@6"; const LLM_PASSWORD = "lukeye@6";
@ -69,7 +71,7 @@ document.addEventListener("DOMContentLoaded", async () => {
}); });
} }
// 获取 LLM workspaceSlug(修正) // 获取 LLM workspaceSlug
async function getLLMWorkspaceSlug(token, forceRefresh = false) { async function getLLMWorkspaceSlug(token, forceRefresh = false) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
chrome.storage.local.get(["llmWorkspaceSlug", "llmWorkspaceAt"], async (res) => { chrome.storage.local.get(["llmWorkspaceSlug", "llmWorkspaceAt"], async (res) => {
@ -111,17 +113,40 @@ document.addEventListener("DOMContentLoaded", async () => {
}); });
} }
// 调用 AnythingLLM stream-chat 并流式渲染 // 追加智能助手问答内容
function appendLLMMsg(msg, type) {
const item = document.createElement("div");
item.className = "llm-msg-item " + (type === "user" ? "llm-user" : "llm-assistant");
item.style.margin = "6px 0";
item.innerHTML = `<span style="color:${type === "user" ? "#2a63c8" : "#2453b5"};">${type === "user" ? "我:" : "智能助手:"}</span> ${msg}`;
llmResultContent.appendChild(item);
llmResultContent.scrollTop = llmResultContent.scrollHeight;
}
// 流式渲染智能助手
async function fetchLLMAnswer(question) { async function fetchLLMAnswer(question) {
llmResultBox.innerHTML = ""; if (!question || !question.trim()) return;
// 追加用户提问
const userItem = document.createElement("div");
userItem.className = "llm-msg-item llm-user";
userItem.innerHTML = `<span style="color:#2a63c8;font-weight:bold;">我:</span> ${question}`;
llmResultContent.appendChild(userItem);
llmResultContainer.classList.remove("hidden"); llmResultContainer.classList.remove("hidden");
llmResultContent.scrollTop = llmResultContent.scrollHeight;
// 创建 assistant 消息卡片
const msgBox = document.createElement("div");
msgBox.className = "llm-msg-item llm-assistant";
msgBox.style.margin = "3px 0 7px 0";
llmResultContent.appendChild(msgBox);
llmResultContent.scrollTop = llmResultContent.scrollHeight;
try { try {
console.log("开始获取 LLM token 和 workspaceSlug...");
const llmToken = await getLLMToken(); const llmToken = await getLLMToken();
const workspaceSlug = await getLLMWorkspaceSlug(llmToken); const workspaceSlug = await getLLMWorkspaceSlug(llmToken);
const url = `${LLM_STREAM_API_BASE}/${workspaceSlug}/stream-chat`; const url = `${LLM_STREAM_API_BASE}/${workspaceSlug}/stream-chat`;
console.log("开始请求 LLM stream-chatPOST", url, "参数:", { message: question, attachments: [] });
const res = await fetch(url, { const res = await fetch(url, {
method: "POST", method: "POST",
headers: { headers: {
@ -131,8 +156,7 @@ document.addEventListener("DOMContentLoaded", async () => {
body: JSON.stringify({ message: question, attachments: [] }), body: JSON.stringify({ message: question, attachments: [] }),
}); });
if (!res.ok || !res.body) { if (!res.ok || !res.body) {
console.log("LLM stream-chat 请求失败,状态:", res.status); msgBox.innerHTML += "<span style='color:red;'>LLM接口请求失败</span>";
llmResultBox.innerHTML = "<span style='color:red'>LLM接口请求失败</span>";
return; return;
} }
const reader = res.body.getReader(); const reader = res.body.getReader();
@ -140,6 +164,10 @@ document.addEventListener("DOMContentLoaded", async () => {
let buffer = ""; let buffer = "";
let done = false; let done = false;
let stop = false; let stop = false;
let inThink = false;
let thinkStr = "";
let answerStr = "";
while (!done) { while (!done) {
const { value, done: readerDone } = await reader.read(); const { value, done: readerDone } = await reader.read();
done = readerDone; done = readerDone;
@ -152,48 +180,64 @@ document.addEventListener("DOMContentLoaded", async () => {
const data = line.replace("data: ", ""); const data = line.replace("data: ", "");
try { try {
const chunk = JSON.parse(data); const chunk = JSON.parse(data);
console.log("LLM 返回片段:", chunk);
if (chunk.type === "textResponseChunk") { if (chunk.type === "textResponseChunk") {
let content = chunk.textResponse || "";
if (content.includes("<think>")) {
inThink = true;
content = content.replace("<think>", "");
}
if (content.includes("</think>")) {
inThink = false;
content = content.replace("</think>", "");
}
if (inThink) {
thinkStr += content.replace(/\n/g, "<br>");
} else if (chunk.textResponse) {
answerStr += content.replace(/\n/g, "<br>");
}
// 实时渲染,思考内容和正式回答分区块
msgBox.innerHTML =
(thinkStr
? `<div class="llm-think">模型思考过程:${thinkStr}</div>`
: "") +
`<span class="assistant-label">智能助手:</span>${answerStr}`;
llmResultContent.scrollTop = llmResultContent.scrollHeight;
if (chunk.close) { if (chunk.close) {
stop = true; stop = true;
break; break;
} }
if (chunk.textResponse) {
llmResultBox.innerHTML += chunk.textResponse.replace(/\n/g, "<br>");
llmResultBox.scrollTop = llmResultBox.scrollHeight;
}
}
} catch (e) {
console.log("LLM 片段解析异常:", e, "行内容:", line);
} }
} catch (e) {}
} }
} }
} }
if (stop) break; if (stop) break;
} }
console.log("LLM stream-chat 已完成。"); if (!thinkStr && !answerStr) {
msgBox.innerHTML += "无有效回答";
}
} catch (e) { } catch (e) {
console.log("LLM stream-chat 全流程异常:", e); msgBox.innerHTML += "<span style='color:red;'>LLM请求异常" + e + "</span>";
llmResultBox.innerHTML = `<span style='color:red'>LLM请求异常${e}</span>`;
llmResultContainer.classList.remove("hidden");
} }
} }
function log(...args) { function log(...args) {
const version = chrome?.runtime?.getManifest?.().version || "dev"; const version = chrome?.runtime?.getManifest?.().version || "dev";
console.log("[v" + version + "]", ...args); console.log("[v" + version + "]", ...args);
} }
// 长期免登录机制
async function loadToken() { async function loadToken() {
return new Promise((resolve) => { return new Promise((resolve) => {
chrome.storage.local.get(["token", "loginAt", "username"], (res) => { chrome.storage.local.get(["token", "loginAt", "username"], (res) => {
log("加载本地存储 token:", res); console.log("本地存储内容:", res);
const now = Date.now(); const now = Date.now();
if (!res.token || !res.loginAt || (now - res.loginAt > TOKEN_EXPIRE_MS)) { if (!res.token || !res.loginAt || isNaN(res.loginAt) || (now - res.loginAt > TOKEN_EXPIRE_MS)) {
log("token 不存在或已过期"); console.log("token 不存在或已过期");
resolve(null); resolve(null);
} else { } else {
log("token 加载成功"); console.log("token 加载成功");
resolve({ resolve({
token: res.token, token: res.token,
username: res.username || "用户" username: res.username || "用户"
@ -215,7 +259,7 @@ document.addEventListener("DOMContentLoaded", async () => {
function clearResults() { function clearResults() {
resultBox.innerHTML = ""; resultBox.innerHTML = "";
llmResultBox.innerHTML = ""; llmResultContent.innerHTML = "";
llmResultContainer.classList.add("hidden"); llmResultContainer.classList.add("hidden");
historyWrapper.classList.add("hidden"); historyWrapper.classList.add("hidden");
} }
@ -385,6 +429,19 @@ document.addEventListener("DOMContentLoaded", async () => {
userDropdown.classList.add("hidden"); userDropdown.classList.add("hidden");
} }
}); });
// 多轮智能助手输入/回车发送
llmSendBtn.addEventListener("click", () => {
const val = llmInput.value.trim();
if (!val) return;
llmInput.value = "";
fetchLLMAnswer(val);
});
llmInput.addEventListener("keydown", function(e) {
if (e.key === "Enter") {
llmSendBtn.click();
}
});
} }
const auth = await loadToken(); const auth = await loadToken();
@ -398,3 +455,31 @@ document.addEventListener("DOMContentLoaded", async () => {
bindEvents(); bindEvents();
} }
}); });
// 拖动+缩放
(function() {
const panel = document.getElementById('draggablePanel');
const header = document.querySelector('.header');
let dragging = false, offsetX = 0, offsetY = 0;
header.addEventListener('mousedown', function(e) {
dragging = true;
offsetX = e.clientX - panel.offsetLeft;
offsetY = e.clientY - panel.offsetTop;
document.body.style.userSelect = 'none';
});
document.addEventListener('mousemove', function(e) {
if (dragging) {
let left = Math.max(0, e.clientX - offsetX);
let top = Math.max(0, e.clientY - offsetY);
panel.style.left = left + 'px';
panel.style.top = top + 'px';
}
});
document.addEventListener('mouseup', function() {
dragging = false;
document.body.style.userSelect = '';
});
})();

View File

@ -280,12 +280,22 @@ h1 {
} }
.llm-title { .llm-title {
background: linear-gradient(90deg, #2560de 90%, #418cfb 100%);
color: #fff;
font-size: 16px;
font-weight: bold; font-weight: bold;
color: #2453b5; padding: 12px 12px 10px 12px;
margin-bottom: 4px; margin: 0 -10px 6px -10px;
font-size: 14px; border-bottom: 1px solid #b3cdfb;
border-radius: 8px 8px 0 0;
position: sticky;
top: 0;
z-index: 2;
letter-spacing: 1px;
box-shadow: 0 2px 6px #2764bb11;
} }
#llmResult { #llmResult {
font-size: 14px; font-size: 14px;
color: #233; color: #233;
@ -294,3 +304,103 @@ h1 {
white-space: pre-wrap; white-space: pre-wrap;
} }
.llm-result-container {
margin-bottom: 14px;
max-height: 300px;
min-height: 60px;
overflow: auto;
border: 1px solid #e0e9f4;
border-radius: 7px;
padding: 0 12px 8px 12px;
background: #f8fbff;
display: flex;
flex-direction: column;
resize: vertical;
}
.llm-title {
position: sticky;
top: 0;
background: #f8fbff;
font-weight: bold;
color: #2453b5;
margin: 0 -12px 8px -12px;
padding: 8px 12px 6px 12px;
font-size: 14px;
border-bottom: 1px solid #e0e9f4;
z-index: 2;
}
.llm-result-content {
flex: 1;
overflow-y: auto;
min-height: 44px;
font-size: 14px;
color: #233;
line-height: 1.7;
word-break: break-all;
white-space: pre-wrap;
}
.llm-input-bar {
display: flex;
gap: 8px;
margin-top: 8px;
padding-bottom: 2px;
align-items: center;
}
#llmInput {
flex: 1;
padding: 6px 10px;
border: 1px solid #c9dafc;
border-radius: 5px;
font-size: 14px;
outline: none;
}
#llmSendBtn {
padding: 6px 14px;
background: #4179f6;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: background 0.2s;
}
#llmSendBtn:hover {
background: #2350b8;
}
.llm-msg-item.llm-assistant {
color: #233; /* 回答文本深灰色 */
background: #e7eefb;
border-radius: 5px;
margin: 3px 0 9px 0;
padding: 7px 8px;
word-break: break-all;
font-weight: 500;
border: 1px solid #b3cdfb;
}
/* 只让“智能助手:”四字蓝色,其它不变 */
.assistant-label {
color: #2560de;
font-weight: bold;
margin-right: 3px;
}
.llm-think {
background: #fffbe6;
color: #8d7a24;
font-size: 13px;
font-style: italic;
border-left: 3px solid #ffe16a;
border-radius: 5px;
margin-bottom: 6px;
padding: 7px 10px 7px 12px;
white-space: pre-wrap;
}