diff --git a/manifest.json b/manifest.json
index 91a7d93..0c50c35 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "渠道应用开发部知识库",
- "version": "1.3.1",
+ "version": "1.3.2",
"description": "通过关键词快速搜索内部文档",
"permissions": ["storage"],
"host_permissions": [
diff --git a/pages/login.js b/pages/login.js
index 5e513d6..d8134f7 100644
--- a/pages/login.js
+++ b/pages/login.js
@@ -22,10 +22,11 @@ function doLogin() {
.then(res => res.json().then(data => ({ status: res.status, body: data })))
.then(({ status, body }) => {
if (status === 200 && body.token) {
+ // 登录成功,保存登录时间
chrome.storage.local.set({
token: body.token,
username: body.username || username,
- loginAt: Date.now()
+ loginAt: Date.now() // 毫秒时间戳
}, () => {
window.location.href = chrome.runtime.getURL("pages/popup.html");
});
diff --git a/pages/popup.html b/pages/popup.html
index 1d0c4d7..5d7ff31 100644
--- a/pages/popup.html
+++ b/pages/popup.html
@@ -37,9 +37,14 @@
+
diff --git a/pages/popup.js b/pages/popup.js
index 0fb5664..5919fc9 100644
--- a/pages/popup.js
+++ b/pages/popup.js
@@ -8,7 +8,9 @@ document.addEventListener("DOMContentLoaded", async () => {
// AnythingLLM 相关节点
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 logoutBtn = document.getElementById("logoutBtn");
@@ -20,7 +22,7 @@ document.addEventListener("DOMContentLoaded", async () => {
let totalPages = 1;
let lastKeywords = [];
- const TOKEN_EXPIRE_MS = 24 * 60 * 60 * 1000;
+ const TOKEN_EXPIRE_MS = 7 * 24 * 60 * 60 * 1000; // 7天有效期
// === AnythingLLM 配置 ===
const LLM_PASSWORD = "lukeye@6";
@@ -69,7 +71,7 @@ document.addEventListener("DOMContentLoaded", async () => {
});
}
- // 获取 LLM workspaceSlug(修正)
+ // 获取 LLM workspaceSlug
async function getLLMWorkspaceSlug(token, forceRefresh = false) {
return new Promise((resolve, reject) => {
chrome.storage.local.get(["llmWorkspaceSlug", "llmWorkspaceAt"], async (res) => {
@@ -111,17 +113,36 @@ 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 = `${type === "user" ? "我:" : "智能助手:"} ${msg}`;
+ llmResultContent.appendChild(item);
+ llmResultContent.scrollTop = llmResultContent.scrollHeight;
+ }
+
+ // 支持多次提问、历史堆叠
+ // 新增/修正流式渲染 assistant 的方式
async function fetchLLMAnswer(question) {
- llmResultBox.innerHTML = "";
+ if (!question || !question.trim()) return;
+ appendLLMMsg(question, "user");
llmResultContainer.classList.remove("hidden");
+
+ // 创建 assistant 消息卡片并持续写入
+ const msgBox = document.createElement("div");
+ msgBox.className = "llm-msg-item llm-assistant";
+ msgBox.style.margin = "3px 0 7px 0";
+ msgBox.innerHTML = `智能助手: `;
+ llmResultContent.appendChild(msgBox);
+ llmResultContent.scrollTop = llmResultContent.scrollHeight;
+
try {
- console.log("开始获取 LLM token 和 workspaceSlug...");
const llmToken = await getLLMToken();
const workspaceSlug = await getLLMWorkspaceSlug(llmToken);
-
const url = `${LLM_STREAM_API_BASE}/${workspaceSlug}/stream-chat`;
- console.log("开始请求 LLM stream-chat,POST", url, "参数:", { message: question, attachments: [] });
+
const res = await fetch(url, {
method: "POST",
headers: {
@@ -131,8 +152,7 @@ document.addEventListener("DOMContentLoaded", async () => {
body: JSON.stringify({ message: question, attachments: [] }),
});
if (!res.ok || !res.body) {
- console.log("LLM stream-chat 请求失败,状态:", res.status);
- llmResultBox.innerHTML = "LLM接口请求失败";
+ msgBox.innerHTML += "LLM接口请求失败";
return;
}
const reader = res.body.getReader();
@@ -140,6 +160,7 @@ document.addEventListener("DOMContentLoaded", async () => {
let buffer = "";
let done = false;
let stop = false;
+ let answer = "";
while (!done) {
const { value, done: readerDone } = await reader.read();
done = readerDone;
@@ -152,30 +173,28 @@ document.addEventListener("DOMContentLoaded", async () => {
const data = line.replace("data: ", "");
try {
const chunk = JSON.parse(data);
- console.log("LLM 返回片段:", chunk);
if (chunk.type === "textResponseChunk") {
if (chunk.close) {
stop = true;
break;
}
if (chunk.textResponse) {
- llmResultBox.innerHTML += chunk.textResponse.replace(/\n/g, "
");
- llmResultBox.scrollTop = llmResultBox.scrollHeight;
+ answer += chunk.textResponse.replace(/\n/g, "
");
+ msgBox.innerHTML = `智能助手: ${answer}`;
+ llmResultContent.scrollTop = llmResultContent.scrollHeight;
}
}
- } catch (e) {
- console.log("LLM 片段解析异常:", e, "行内容:", line);
- }
+ } catch (e) {}
}
}
}
if (stop) break;
}
- console.log("LLM stream-chat 已完成。");
+ if (!answer) {
+ msgBox.innerHTML += "无有效回答";
+ }
} catch (e) {
- console.log("LLM stream-chat 全流程异常:", e);
- llmResultBox.innerHTML = `LLM请求异常:${e}`;
- llmResultContainer.classList.remove("hidden");
+ msgBox.innerHTML += "LLM请求异常:" + e + "";
}
}
@@ -184,16 +203,17 @@ document.addEventListener("DOMContentLoaded", async () => {
console.log("[v" + version + "]", ...args);
}
+ // 长期免登录机制
async function loadToken() {
return new Promise((resolve) => {
chrome.storage.local.get(["token", "loginAt", "username"], (res) => {
- log("加载本地存储 token:", res);
+ console.log("本地存储内容:", res);
const now = Date.now();
- if (!res.token || !res.loginAt || (now - res.loginAt > TOKEN_EXPIRE_MS)) {
- log("token 不存在或已过期");
+ if (!res.token || !res.loginAt || isNaN(res.loginAt) || (now - res.loginAt > TOKEN_EXPIRE_MS)) {
+ console.log("token 不存在或已过期");
resolve(null);
} else {
- log("token 加载成功");
+ console.log("token 加载成功");
resolve({
token: res.token,
username: res.username || "用户"
@@ -215,7 +235,7 @@ document.addEventListener("DOMContentLoaded", async () => {
function clearResults() {
resultBox.innerHTML = "";
- llmResultBox.innerHTML = "";
+ llmResultContent.innerHTML = "";
llmResultContainer.classList.add("hidden");
historyWrapper.classList.add("hidden");
}
@@ -385,6 +405,19 @@ document.addEventListener("DOMContentLoaded", async () => {
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();
@@ -398,3 +431,31 @@ document.addEventListener("DOMContentLoaded", async () => {
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 = '';
+ });
+})();
diff --git a/pages/styles.css b/pages/styles.css
index bd63db1..7e6a16c 100644
--- a/pages/styles.css
+++ b/pages/styles.css
@@ -280,12 +280,22 @@ h1 {
}
.llm-title {
+ background: linear-gradient(90deg, #2560de 90%, #418cfb 100%);
+ color: #fff;
+ font-size: 16px;
font-weight: bold;
- color: #2453b5;
- margin-bottom: 4px;
- font-size: 14px;
+ padding: 12px 12px 10px 12px;
+ margin: 0 -10px 6px -10px;
+ 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 {
font-size: 14px;
color: #233;
@@ -294,3 +304,91 @@ h1 {
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: #2453b5;
+ background: #e7eefb;
+ border-radius: 5px;
+ margin: 3px 0 9px 0;
+ padding: 7px 8px;
+ word-break: break-all;
+ font-weight: 500;
+ border: 1px solid #b3cdfb;
+}
+
+/* 只让“智能助手:”四字加粗/蓝色(需配合js输出span) */
+.llm-msg-item.llm-assistant .assistant-label {
+ color: #2560de;
+ font-weight: bold;
+ margin-right: 3px;
+}
+