diff --git a/scripts/config.js b/scripts/config.js
index 991c1eb..7d32bc4 100644
--- a/scripts/config.js
+++ b/scripts/config.js
@@ -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: []
diff --git a/scripts/llm.js b/scripts/llm.js
index f98b55d..b28025e 100644
--- a/scripts/llm.js
+++ b/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 = `我: ${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("")) {
inThink = true;
content = content.replace("", "");
@@ -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, "
");
@@ -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 += `LLM异常:${e}`;
+ console.error("LLM问答流式接口异常", e);
}
}