添加LLM大模型调用
This commit is contained in:
parent
23aa18fafe
commit
23d8ff6d37
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "渠道应用开发部知识库",
|
"name": "渠道应用开发部知识库",
|
||||||
"version": "1.3",
|
"version": "1.3.1",
|
||||||
"description": "通过关键词快速搜索内部文档",
|
"description": "通过关键词快速搜索内部文档",
|
||||||
"permissions": ["storage"],
|
"permissions": ["storage"],
|
||||||
"host_permissions": [
|
"host_permissions": [
|
||||||
|
|||||||
@ -34,9 +34,16 @@
|
|||||||
<div id="historyTags" class="tag-history"></div>
|
<div id="historyTags" class="tag-history"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- AnythingLLM结果区域 -->
|
||||||
|
<div id="llmResultContainer" class="llm-result-container hidden">
|
||||||
|
<div class="llm-title">智能助手答案</div>
|
||||||
|
<div id="llmResult"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ul id="results"></ul>
|
<ul id="results"></ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<script src="popup.js"></script>
|
<script src="popup.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
173
pages/popup.js
173
pages/popup.js
@ -6,6 +6,10 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
const historyBox = document.getElementById("historyTags");
|
const historyBox = document.getElementById("historyTags");
|
||||||
const historyWrapper = document.getElementById("historyWrapper");
|
const historyWrapper = document.getElementById("historyWrapper");
|
||||||
|
|
||||||
|
// AnythingLLM 相关节点
|
||||||
|
const llmResultContainer = document.getElementById("llmResultContainer");
|
||||||
|
const llmResultBox = document.getElementById("llmResult");
|
||||||
|
|
||||||
const userDropdown = document.getElementById("userDropdown");
|
const userDropdown = document.getElementById("userDropdown");
|
||||||
const logoutBtn = document.getElementById("logoutBtn");
|
const logoutBtn = document.getElementById("logoutBtn");
|
||||||
const userNameLabel = document.getElementById("userName");
|
const userNameLabel = document.getElementById("userName");
|
||||||
@ -18,9 +22,166 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
|
|
||||||
const TOKEN_EXPIRE_MS = 24 * 60 * 60 * 1000;
|
const TOKEN_EXPIRE_MS = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
// === AnythingLLM 配置 ===
|
||||||
|
const LLM_PASSWORD = "lukeye@6";
|
||||||
|
const LLM_TOKEN_API = "http://llm.wisdompulse.cn/api/request-token";
|
||||||
|
const LLM_WORKSPACES_API = "http://llm.wisdompulse.cn/api/workspaces";
|
||||||
|
const LLM_STREAM_API_BASE = "http://llm.wisdompulse.cn/api/workspace";
|
||||||
|
const LLM_TOKEN_EXPIRE_MS = 23 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
// 获取 LLM Token
|
||||||
|
async function getLLMToken(forceRefresh = false) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
chrome.storage.local.get(["llmToken", "llmTokenAt"], async (res) => {
|
||||||
|
const now = Date.now();
|
||||||
|
if (
|
||||||
|
!forceRefresh &&
|
||||||
|
res.llmToken &&
|
||||||
|
res.llmTokenAt &&
|
||||||
|
now - res.llmTokenAt < LLM_TOKEN_EXPIRE_MS
|
||||||
|
) {
|
||||||
|
console.log("LLM token 命中缓存,有效期内");
|
||||||
|
resolve(res.llmToken);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
console.log("开始请求 LLM token,POST", LLM_TOKEN_API, "参数:", { password: LLM_PASSWORD });
|
||||||
|
const response = await fetch(LLM_TOKEN_API, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ password: LLM_PASSWORD }),
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
console.log("LLM token 接口返回:", data);
|
||||||
|
if (data.token) {
|
||||||
|
chrome.storage.local.set({ llmToken: data.token, llmTokenAt: now });
|
||||||
|
console.log("LLM token 已缓存");
|
||||||
|
resolve(data.token);
|
||||||
|
} else {
|
||||||
|
console.log("LLM token 获取失败,响应内容:", data);
|
||||||
|
reject("LLM token获取失败");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("LLM token 请求异常:", e);
|
||||||
|
reject("LLM token接口异常");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 LLM workspaceSlug(修正)
|
||||||
|
async function getLLMWorkspaceSlug(token, forceRefresh = false) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
chrome.storage.local.get(["llmWorkspaceSlug", "llmWorkspaceAt"], async (res) => {
|
||||||
|
const now = Date.now();
|
||||||
|
if (
|
||||||
|
!forceRefresh &&
|
||||||
|
res.llmWorkspaceSlug &&
|
||||||
|
res.llmWorkspaceAt &&
|
||||||
|
now - res.llmWorkspaceAt < LLM_TOKEN_EXPIRE_MS
|
||||||
|
) {
|
||||||
|
console.log("LLM workspaceSlug 命中缓存,有效期内:", res.llmWorkspaceSlug);
|
||||||
|
resolve(res.llmWorkspaceSlug);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
console.log("开始请求 LLM 工作区列表,GET", LLM_WORKSPACES_API, "Authorization: Bearer", token);
|
||||||
|
const response = await fetch(LLM_WORKSPACES_API, {
|
||||||
|
method: "GET",
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
console.log("LLM 工作区接口返回:", data);
|
||||||
|
if (data.workspaces && Array.isArray(data.workspaces) && data.workspaces.length > 0 && data.workspaces[0].slug) {
|
||||||
|
chrome.storage.local.set({
|
||||||
|
llmWorkspaceSlug: data.workspaces[0].slug,
|
||||||
|
llmWorkspaceAt: now,
|
||||||
|
});
|
||||||
|
console.log("LLM workspaceSlug 已缓存:", data.workspaces[0].slug);
|
||||||
|
resolve(data.workspaces[0].slug);
|
||||||
|
} else {
|
||||||
|
console.log("未获取到有效的 LLM 工作区 slug,响应内容:", data);
|
||||||
|
reject("未获取到LLM工作区slug");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("LLM 工作区接口请求异常:", e);
|
||||||
|
reject("LLM工作区接口异常");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用 AnythingLLM stream-chat 并流式渲染
|
||||||
|
async function fetchLLMAnswer(question) {
|
||||||
|
llmResultBox.innerHTML = "";
|
||||||
|
llmResultContainer.classList.remove("hidden");
|
||||||
|
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: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": `Bearer ${llmToken}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ message: question, attachments: [] }),
|
||||||
|
});
|
||||||
|
if (!res.ok || !res.body) {
|
||||||
|
console.log("LLM stream-chat 请求失败,状态:", res.status);
|
||||||
|
llmResultBox.innerHTML = "<span style='color:red'>LLM接口请求失败</span>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const reader = res.body.getReader();
|
||||||
|
const decoder = new TextDecoder("utf-8");
|
||||||
|
let buffer = "";
|
||||||
|
let done = false;
|
||||||
|
let stop = false;
|
||||||
|
while (!done) {
|
||||||
|
const { value, done: readerDone } = await reader.read();
|
||||||
|
done = readerDone;
|
||||||
|
if (value) {
|
||||||
|
buffer += decoder.decode(value, { stream: true });
|
||||||
|
let lines = buffer.split("\n");
|
||||||
|
buffer = lines.pop();
|
||||||
|
for (let line of lines) {
|
||||||
|
if (line.startsWith("data: ")) {
|
||||||
|
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, "<br>");
|
||||||
|
llmResultBox.scrollTop = llmResultBox.scrollHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("LLM 片段解析异常:", e, "行内容:", line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stop) break;
|
||||||
|
}
|
||||||
|
console.log("LLM stream-chat 已完成。");
|
||||||
|
} catch (e) {
|
||||||
|
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() {
|
||||||
@ -48,13 +209,14 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function showLoading() {
|
function showLoading() {
|
||||||
resultBox.innerHTML = "<li>加载中...</li>";
|
resultBox.innerHTML = "<li>加载中...</li>";
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearResults() {
|
function clearResults() {
|
||||||
resultBox.innerHTML = "";
|
resultBox.innerHTML = "";
|
||||||
|
llmResultBox.innerHTML = "";
|
||||||
|
llmResultContainer.classList.add("hidden");
|
||||||
historyWrapper.classList.add("hidden");
|
historyWrapper.classList.add("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +343,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
renderPagination();
|
renderPagination();
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("搜索请求失败:", err);
|
console.log("搜索请求失败:", err);
|
||||||
resultBox.innerHTML = "<li>搜索失败,请检查网络或服务状态。</li>";
|
resultBox.innerHTML = "<li>搜索失败,请检查网络或服务状态。</li>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,6 +357,9 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
if (!val) return clearResults();
|
if (!val) return clearResults();
|
||||||
const keywords = val.split(/\s+/);
|
const keywords = val.split(/\s+/);
|
||||||
updateHistory(val);
|
updateHistory(val);
|
||||||
|
|
||||||
|
// 并发触发 AnythingLLM + ES
|
||||||
|
fetchLLMAnswer(val);
|
||||||
fetchResults(keywords, 1);
|
fetchResults(keywords, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,7 +368,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
searchBtn.addEventListener("click", triggerSearch);
|
searchBtn.addEventListener("click", triggerSearch);
|
||||||
input.addEventListener("keydown", (e) => {
|
input.addEventListener("keydown", (e) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
log("用户按下回车 ⏎");
|
log("用户按下回车");
|
||||||
triggerSearch();
|
triggerSearch();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -269,4 +269,28 @@ h1 {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.llm-result-container {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
max-height: 140px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid #e0e9f4;
|
||||||
|
border-radius: 7px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: #f8fbff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.llm-title {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2453b5;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#llmResult {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #233;
|
||||||
|
line-height: 1.7;
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user