添加LLM大模型调用
This commit is contained in:
parent
23aa18fafe
commit
23d8ff6d37
@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "渠道应用开发部知识库",
|
||||
"version": "1.3",
|
||||
"version": "1.3.1",
|
||||
"description": "通过关键词快速搜索内部文档",
|
||||
"permissions": ["storage"],
|
||||
"host_permissions": [
|
||||
|
||||
@ -34,9 +34,16 @@
|
||||
<div id="historyTags" class="tag-history"></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>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
173
pages/popup.js
173
pages/popup.js
@ -6,6 +6,10 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
const historyBox = document.getElementById("historyTags");
|
||||
const historyWrapper = document.getElementById("historyWrapper");
|
||||
|
||||
// AnythingLLM 相关节点
|
||||
const llmResultContainer = document.getElementById("llmResultContainer");
|
||||
const llmResultBox = document.getElementById("llmResult");
|
||||
|
||||
const userDropdown = document.getElementById("userDropdown");
|
||||
const logoutBtn = document.getElementById("logoutBtn");
|
||||
const userNameLabel = document.getElementById("userName");
|
||||
@ -18,9 +22,166 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
|
||||
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) {
|
||||
const version = chrome?.runtime?.getManifest?.().version || "dev";
|
||||
console.log(`[v${version}]`, ...args);
|
||||
console.log("[v" + version + "]", ...args);
|
||||
}
|
||||
|
||||
async function loadToken() {
|
||||
@ -48,13 +209,14 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function showLoading() {
|
||||
resultBox.innerHTML = "<li>加载中...</li>";
|
||||
}
|
||||
|
||||
function clearResults() {
|
||||
resultBox.innerHTML = "";
|
||||
llmResultBox.innerHTML = "";
|
||||
llmResultContainer.classList.add("hidden");
|
||||
historyWrapper.classList.add("hidden");
|
||||
}
|
||||
|
||||
@ -181,7 +343,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
renderPagination();
|
||||
|
||||
} catch (err) {
|
||||
console.error("搜索请求失败:", err);
|
||||
console.log("搜索请求失败:", err);
|
||||
resultBox.innerHTML = "<li>搜索失败,请检查网络或服务状态。</li>";
|
||||
}
|
||||
}
|
||||
@ -195,6 +357,9 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
if (!val) return clearResults();
|
||||
const keywords = val.split(/\s+/);
|
||||
updateHistory(val);
|
||||
|
||||
// 并发触发 AnythingLLM + ES
|
||||
fetchLLMAnswer(val);
|
||||
fetchResults(keywords, 1);
|
||||
}
|
||||
|
||||
@ -203,7 +368,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
searchBtn.addEventListener("click", triggerSearch);
|
||||
input.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") {
|
||||
log("用户按下回车 ⏎");
|
||||
log("用户按下回车");
|
||||
triggerSearch();
|
||||
}
|
||||
});
|
||||
|
||||
@ -269,4 +269,28 @@ h1 {
|
||||
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