Compare commits
No commits in common. "df177dff58058aed2c10031a37bc650e824b9dbd" and "23d8ff6d37699de64495dbeda891ed9985799450" have entirely different histories.
df177dff58
...
23d8ff6d37
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "渠道应用开发部知识库",
|
"name": "渠道应用开发部知识库",
|
||||||
"version": "1.3.2",
|
"version": "1.3.1",
|
||||||
"description": "通过关键词快速搜索内部文档",
|
"description": "通过关键词快速搜索内部文档",
|
||||||
"permissions": ["storage"],
|
"permissions": ["storage"],
|
||||||
"host_permissions": [
|
"host_permissions": [
|
||||||
|
|||||||
@ -22,11 +22,10 @@ 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");
|
||||||
});
|
});
|
||||||
|
|||||||
@ -37,14 +37,9 @@
|
|||||||
<!-- 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 class="llm-result-content" id="llmResultContent"></div>
|
<div id="llmResult"></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>
|
||||||
|
|
||||||
|
|||||||
139
pages/popup.js
139
pages/popup.js
@ -8,9 +8,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
|
|
||||||
// AnythingLLM 相关节点
|
// AnythingLLM 相关节点
|
||||||
const llmResultContainer = document.getElementById("llmResultContainer");
|
const llmResultContainer = document.getElementById("llmResultContainer");
|
||||||
const llmResultContent = document.getElementById("llmResultContent");
|
const llmResultBox = document.getElementById("llmResult");
|
||||||
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");
|
||||||
@ -22,7 +20,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
let totalPages = 1;
|
let totalPages = 1;
|
||||||
let lastKeywords = [];
|
let lastKeywords = [];
|
||||||
|
|
||||||
const TOKEN_EXPIRE_MS = 7 * 24 * 60 * 60 * 1000; // 7天有效期
|
const TOKEN_EXPIRE_MS = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
// === AnythingLLM 配置 ===
|
// === AnythingLLM 配置 ===
|
||||||
const LLM_PASSWORD = "lukeye@6";
|
const LLM_PASSWORD = "lukeye@6";
|
||||||
@ -71,7 +69,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) => {
|
||||||
@ -113,40 +111,17 @@ 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) {
|
||||||
if (!question || !question.trim()) return;
|
llmResultBox.innerHTML = "";
|
||||||
|
|
||||||
// 追加用户提问
|
|
||||||
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-chat,POST", url, "参数:", { message: question, attachments: [] });
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@ -156,7 +131,8 @@ 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) {
|
||||||
msgBox.innerHTML += "<span style='color:red;'>LLM接口请求失败</span>";
|
console.log("LLM stream-chat 请求失败,状态:", res.status);
|
||||||
|
llmResultBox.innerHTML = "<span style='color:red'>LLM接口请求失败</span>";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const reader = res.body.getReader();
|
const reader = res.body.getReader();
|
||||||
@ -164,10 +140,6 @@ 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;
|
||||||
@ -180,64 +152,48 @@ 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) {}
|
} catch (e) {
|
||||||
|
console.log("LLM 片段解析异常:", e, "行内容:", line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (stop) break;
|
if (stop) break;
|
||||||
}
|
}
|
||||||
if (!thinkStr && !answerStr) {
|
console.log("LLM stream-chat 已完成。");
|
||||||
msgBox.innerHTML += "无有效回答";
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
msgBox.innerHTML += "<span style='color:red;'>LLM请求异常:" + e + "</span>";
|
console.log("LLM stream-chat 全流程异常:", e);
|
||||||
|
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) => {
|
||||||
console.log("本地存储内容:", res);
|
log("加载本地存储 token:", res);
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (!res.token || !res.loginAt || isNaN(res.loginAt) || (now - res.loginAt > TOKEN_EXPIRE_MS)) {
|
if (!res.token || !res.loginAt || (now - res.loginAt > TOKEN_EXPIRE_MS)) {
|
||||||
console.log("token 不存在或已过期");
|
log("token 不存在或已过期");
|
||||||
resolve(null);
|
resolve(null);
|
||||||
} else {
|
} else {
|
||||||
console.log("token 加载成功");
|
log("token 加载成功");
|
||||||
resolve({
|
resolve({
|
||||||
token: res.token,
|
token: res.token,
|
||||||
username: res.username || "用户"
|
username: res.username || "用户"
|
||||||
@ -259,7 +215,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
|
|
||||||
function clearResults() {
|
function clearResults() {
|
||||||
resultBox.innerHTML = "";
|
resultBox.innerHTML = "";
|
||||||
llmResultContent.innerHTML = "";
|
llmResultBox.innerHTML = "";
|
||||||
llmResultContainer.classList.add("hidden");
|
llmResultContainer.classList.add("hidden");
|
||||||
historyWrapper.classList.add("hidden");
|
historyWrapper.classList.add("hidden");
|
||||||
}
|
}
|
||||||
@ -429,19 +385,6 @@ 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();
|
||||||
@ -455,31 +398,3 @@ 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 = '';
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|||||||
116
pages/styles.css
116
pages/styles.css
@ -280,22 +280,12 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.llm-title {
|
.llm-title {
|
||||||
background: linear-gradient(90deg, #2560de 90%, #418cfb 100%);
|
|
||||||
color: #fff;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding: 12px 12px 10px 12px;
|
color: #2453b5;
|
||||||
margin: 0 -10px 6px -10px;
|
margin-bottom: 4px;
|
||||||
border-bottom: 1px solid #b3cdfb;
|
font-size: 14px;
|
||||||
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;
|
||||||
@ -304,103 +294,3 @@ 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user