fix
This commit is contained in:
parent
ddd94b3681
commit
dd6aebf566
@ -61,19 +61,11 @@ const defaultConfig = {
|
||||
{ label: "Token 字段", field: "token" }
|
||||
]
|
||||
},
|
||||
workspaces: {
|
||||
name: "获取工作区接口",
|
||||
url: "http://llm.wisdompulse.cn/api/workspaces",
|
||||
requestParams: [],
|
||||
responseParams: [
|
||||
{ label: "工作区数组字段", field: "workspaces" }
|
||||
]
|
||||
},
|
||||
llmChat: {
|
||||
name: "指定工作区对话接口",
|
||||
url: "http://llm.wisdompulse.cn/api/workspace/${workId}/stream-chat",
|
||||
url: "http://app-test.wisdompulse.cn/api/v1/llm/ask",
|
||||
requestParams: [
|
||||
{ label: "问题字段", field: "message" },
|
||||
{ label: "问题字段", field: "question" },
|
||||
{ label: "历史字段", field: "attachments" }
|
||||
],
|
||||
responseParams: []
|
||||
|
||||
114
scripts/llm.js
114
scripts/llm.js
@ -226,7 +226,7 @@ function insertCopyFullAnswerBtn(msgBox, answerStr) {
|
||||
function promptLLMPasswordModal() {
|
||||
return new Promise((resolve) => {
|
||||
// 避免重复弹窗
|
||||
if (document.getElementById("llm-password-modal")) return;
|
||||
if (document.getElementById("llm-password-modal")) return resolve("");
|
||||
|
||||
// 创建遮罩层
|
||||
const mask = document.createElement("div");
|
||||
@ -275,45 +275,53 @@ function promptLLMPasswordModal() {
|
||||
async function getLLMToken(forceRefresh = false) {
|
||||
const config = await loadMergedConfig();
|
||||
const tokenCfg = config.apiConfig.llmToken;
|
||||
const expireMs = config.llmTokenExpireHours * 60 * 60 * 1000;
|
||||
|
||||
// 检查缓存
|
||||
return new Promise((resolve, reject) => {
|
||||
chrome.storage.local.get(["llmToken", "llmTokenAt"], async (res) => {
|
||||
const now = Date.now();
|
||||
const mergedConfig = await loadMergedConfig();
|
||||
const expireMs = mergedConfig.llmTokenExpireHours * 60 * 60 * 1000;
|
||||
if (!forceRefresh && res.llmToken && res.llmTokenAt && now - res.llmTokenAt < expireMs) {
|
||||
log("已从缓存中获取到llmToken. expireMs: " + expireMs);
|
||||
resolve(res.llmToken);
|
||||
} else {
|
||||
try {
|
||||
// 这里不再用 getLLMPassword(),而是直接弹窗
|
||||
let pwd = await promptLLMPasswordModal();
|
||||
if (!pwd) {
|
||||
reject("未输入密码,无法获取 LLM Token");
|
||||
return;
|
||||
}
|
||||
// 可选:此处不自动存储 llmPassword,提升安全性
|
||||
const body = {};
|
||||
body[tokenCfg.requestParams[0].field] = pwd;
|
||||
|
||||
const response = await fetch(tokenCfg.url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const tokenField = tokenCfg.responseParams.find(p => p.label.includes("token")).field;
|
||||
if (data[tokenField]) {
|
||||
chrome.storage.local.set({ llmToken: data[tokenField], llmTokenAt: now });
|
||||
log("请求llmToken成功!");
|
||||
resolve(data[tokenField]);
|
||||
} else {
|
||||
log("LLM token获取失败!");
|
||||
reject("LLM token获取失败");
|
||||
}
|
||||
} catch (e) {
|
||||
reject("LLM token接口异常");
|
||||
try {
|
||||
const now = Date.now();
|
||||
if (!forceRefresh && res.llmToken && res.llmTokenAt && now - res.llmTokenAt < expireMs) {
|
||||
return resolve(res.llmToken);
|
||||
}
|
||||
// 弹窗输入密码
|
||||
let pwd = await promptLLMPasswordModal();
|
||||
if (!pwd) return reject("未输入密码,无法获取 LLM Token");
|
||||
|
||||
// 构造请求
|
||||
const body = {};
|
||||
body[tokenCfg.requestParams[0].field] = pwd;
|
||||
let response, text, data;
|
||||
try {
|
||||
response = await fetch(tokenCfg.url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "test"
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
text = await response.text();
|
||||
} catch (e) {
|
||||
return reject("LLM token接口网络异常: " + e);
|
||||
}
|
||||
try {
|
||||
data = JSON.parse(text);
|
||||
} catch (e) {
|
||||
return reject("LLM token接口返回非JSON: " + text);
|
||||
}
|
||||
|
||||
const tokenParam = tokenCfg.responseParams.find(p => p.label && p.label.includes("token"));
|
||||
const tokenField = tokenParam ? tokenParam.field : "token";
|
||||
if (data[tokenField]) {
|
||||
chrome.storage.local.set({ llmToken: data[tokenField], llmTokenAt: now });
|
||||
return resolve(data[tokenField]);
|
||||
} else {
|
||||
return reject("LLM token获取失败,响应内容: " + text);
|
||||
}
|
||||
} catch (e) {
|
||||
return reject("getLLMToken代码异常: " + e);
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -322,15 +330,25 @@ async function getLLMToken(forceRefresh = false) {
|
||||
async function fetchLLMAnswer(question, container, contentDiv) {
|
||||
if (!question || !question.trim()) return;
|
||||
|
||||
// 获取Token:关键是加try-catch!
|
||||
let llmToken;
|
||||
try {
|
||||
llmToken = await getLLMToken();
|
||||
} catch (e) {
|
||||
alert("LLM Token 获取失败:" + e);
|
||||
return;
|
||||
}
|
||||
|
||||
const config = await loadMergedConfig();
|
||||
const llmToken = await getLLMToken();
|
||||
const auth = await loadUserToken();
|
||||
if (!auth) {
|
||||
return handleLogout(auth);
|
||||
handleLogout(auth);
|
||||
return;
|
||||
}
|
||||
const userToken = auth.token;
|
||||
const url = "http://app-test.wisdompulse.cn/api/v1/llm/ask";
|
||||
const url = config.apiConfig.llmChat.url;
|
||||
|
||||
// ... 你的渲染用户输入的代码 ...
|
||||
const userItem = document.createElement("div");
|
||||
userItem.className = "llm-msg-item llm-user";
|
||||
userItem.innerHTML = `<span style="color:#2a63c8;font-weight:bold;">我:</span> ${question}`;
|
||||
@ -344,6 +362,7 @@ async function fetchLLMAnswer(question, container, contentDiv) {
|
||||
|
||||
let collectedSources = [];
|
||||
let jumpMap = {};
|
||||
// LLM 问答接口流式处理
|
||||
try {
|
||||
const body = { llmToken, question };
|
||||
|
||||
@ -351,6 +370,7 @@ async function fetchLLMAnswer(question, container, contentDiv) {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `${userToken}`,
|
||||
"Accept": "text/event-stream",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
@ -372,7 +392,7 @@ async function fetchLLMAnswer(question, container, contentDiv) {
|
||||
if (value) {
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split("\n\n");
|
||||
buffer = lines.pop(); // 留给下次读未完整数据
|
||||
buffer = lines.pop(); // 留给下次未完整数据
|
||||
|
||||
for (let line of lines) {
|
||||
line = line.trim();
|
||||
@ -380,7 +400,6 @@ async function fetchLLMAnswer(question, container, contentDiv) {
|
||||
|
||||
const chunkStr = line.slice(5).trim();
|
||||
if (!chunkStr) continue;
|
||||
|
||||
let chunk;
|
||||
try {
|
||||
chunk = JSON.parse(chunkStr);
|
||||
@ -389,13 +408,13 @@ async function fetchLLMAnswer(question, container, contentDiv) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 收集 sources(只更新,不渲染)
|
||||
// 收集 sources
|
||||
if (chunk.sources) {
|
||||
collectedSources = chunk.sources;
|
||||
}
|
||||
|
||||
// 累积内容
|
||||
let content = chunk.textResponse || "";
|
||||
// 只要有 textResponse 就累加
|
||||
let content = chunk.textResponse ?? "";
|
||||
if (content.includes("<think>")) {
|
||||
inThink = true;
|
||||
content = content.replace("<think>", "");
|
||||
@ -411,7 +430,7 @@ async function fetchLLMAnswer(question, container, contentDiv) {
|
||||
answerStr += content;
|
||||
}
|
||||
|
||||
// 渲染回答(markdown 支持)
|
||||
// 每次都要渲染(不管内容是否为空)
|
||||
let html = "";
|
||||
try {
|
||||
html = window.marked ? wrapHtmlWithCopyBtn(marked.parse(answerStr)) : answerStr.replace(/\n/g, "<br>");
|
||||
@ -427,7 +446,7 @@ async function fetchLLMAnswer(question, container, contentDiv) {
|
||||
insertCopyFullAnswerBtn(msgBox, answerStr);
|
||||
contentDiv.scrollTop = contentDiv.scrollHeight;
|
||||
|
||||
// 关闭信号触发最终引用渲染
|
||||
// 收尾渲染引用
|
||||
if (chunk.close === true) {
|
||||
if (collectedSources.length > 0) {
|
||||
const titles = collectedSources.map(s => s.title).filter(Boolean);
|
||||
@ -446,6 +465,7 @@ async function fetchLLMAnswer(question, container, contentDiv) {
|
||||
}
|
||||
} catch (e) {
|
||||
msgBox.innerHTML += `<span style='color:red;'>LLM异常:${e}</span>`;
|
||||
console.error("LLM问答流式接口异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user