llm返回值包含参考文档

This commit is contained in:
luke 2025-06-01 16:33:32 +08:00
parent 4954fee1e9
commit b5a15d0750
2 changed files with 109 additions and 2 deletions

View File

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

View File

@ -1,9 +1,92 @@
// scripts/llm.js
const LLM_TOKEN_API = "http://llm.wisdompulse.cn/api/request-token"; const LLM_TOKEN_API = "http://llm.wisdompulse.cn/api/request-token";
const LLM_WORKSPACES_API = "http://llm.wisdompulse.cn/api/workspaces"; const LLM_WORKSPACES_API = "http://llm.wisdompulse.cn/api/workspaces";
const LLM_STREAM_API_BASE = "http://llm.wisdompulse.cn/api/workspace"; const LLM_STREAM_API_BASE = "http://llm.wisdompulse.cn/api/workspace";
const LLM_TOKEN_EXPIRE_MS = 23 * 60 * 60 * 1000; const LLM_TOKEN_EXPIRE_MS = 23 * 60 * 60 * 1000;
async function resolveJumpLinks(titles) {
if (!titles || titles.length === 0) return {};
return new Promise((resolveOuter) => {
chrome.storage.local.get(["token"], async (res) => {
const token = res.token;
if (!token) {
console.warn("resolveJumpLinks 缺少 token跳过");
return resolveOuter({});
}
try {
const res = await fetch("http://app.wisdompulse.cn/search/file-path", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": token
},
body: JSON.stringify({ fileNames: titles })
});
const data = await res.json();
const map = {};
for (const title of Object.keys(data)) {
if (data[title]) {
const cleanPath = data[title].replace(/\.md$/, ".html");
map[title] = `http://share.wisdompulse.cn/public/${cleanPath}`;
}
}
resolveOuter(map);
} catch (e) {
console.warn("resolveJumpLinks 接口异常:", e);
resolveOuter({});
}
});
});
}
async function renderSources(sourceList, sources) {
const titles = sources.map(s => s.title).filter(Boolean);
const jumpMap = await resolveJumpLinks(titles);
sourceList.style.marginTop = "16px";
sourceList.style.borderTop = "1px solid #dbe3f3";
sourceList.style.paddingTop = "10px";
const titleBar = document.createElement("div");
titleBar.textContent = "📎 参考资料";
titleBar.style.fontWeight = "bold";
titleBar.style.marginBottom = "6px";
titleBar.style.fontSize = "13.5px";
titleBar.style.color = "#0d2fec";
sourceList.appendChild(titleBar);
for (const src of sources) {
const url = jumpMap[src.title];
const line = document.createElement("div");
line.style.marginTop = "4px";
if (url) {
const link = document.createElement("a");
link.href = url;
link.textContent = src.title || "未命名文档";
link.target = "_blank";
link.style.color = "#2560de";
link.style.textDecoration = "none";
link.onmouseenter = () => (link.style.textDecoration = "underline");
link.onmouseleave = () => (link.style.textDecoration = "none");
line.appendChild(link);
} else {
line.textContent = src.title || "未命名文档";
line.style.color = "#666";
}
sourceList.appendChild(line);
}
}
async function getLLMPassword() { async function getLLMPassword() {
return new Promise((resolve) => { return new Promise((resolve) => {
chrome.storage.local.get(["llmPassword"], (res) => { chrome.storage.local.get(["llmPassword"], (res) => {
@ -83,6 +166,8 @@ async function fetchLLMAnswer(question, container, contentDiv) {
contentDiv.appendChild(msgBox); contentDiv.appendChild(msgBox);
contentDiv.scrollTop = contentDiv.scrollHeight; contentDiv.scrollTop = contentDiv.scrollHeight;
let collectedSources = [];
try { try {
const token = await getLLMToken(); const token = await getLLMToken();
const workspace = await getLLMWorkspaceSlug(token); const workspace = await getLLMWorkspaceSlug(token);
@ -118,6 +203,16 @@ async function fetchLLMAnswer(question, container, contentDiv) {
if (line.startsWith("data: ")) { if (line.startsWith("data: ")) {
const data = line.slice(6); const data = line.slice(6);
const chunk = JSON.parse(data); const chunk = JSON.parse(data);
// 收集 sources去重按 id
if (chunk.sources && Array.isArray(chunk.sources)) {
for (const src of chunk.sources) {
if (!collectedSources.find(s => s.id === src.id)) {
collectedSources.push(src);
}
}
}
let content = chunk.textResponse || ""; let content = chunk.textResponse || "";
if (content.includes("<think>")) { if (content.includes("<think>")) {
inThink = true; inThink = true;
@ -132,6 +227,7 @@ async function fetchLLMAnswer(question, container, contentDiv) {
} else { } else {
answerStr += content.replace(/\n/g, "<br>"); answerStr += content.replace(/\n/g, "<br>");
} }
msgBox.innerHTML = msgBox.innerHTML =
(thinkStr ? `<div class="llm-think">${thinkStr}</div>` : "") + (thinkStr ? `<div class="llm-think">${thinkStr}</div>` : "") +
`<span class="assistant-label">智能助手:</span>${answerStr}`; `<span class="assistant-label">智能助手:</span>${answerStr}`;
@ -140,6 +236,17 @@ async function fetchLLMAnswer(question, container, contentDiv) {
} }
} }
} }
if (collectedSources.length > 0) {
const sourceList = document.createElement("div");
sourceList.className = "llm-sources";
sourceList.style.marginTop = "10px";
sourceList.style.fontSize = "13px";
sourceList.style.color = "#555";
msgBox.appendChild(sourceList);
await renderSources(sourceList, collectedSources);
}
} catch (e) { } catch (e) {
msgBox.innerHTML += `<span style='color:red;'>LLM异常${e}</span>`; msgBox.innerHTML += `<span style='color:red;'>LLM异常${e}</span>`;
} }