From dd6aebf56615f539a926bcb98df7255e600e57c2 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 24 Jun 2025 18:17:51 +0800 Subject: [PATCH] fix --- scripts/config.js | 12 +---- scripts/llm.js | 114 +++++++++++++++++++++++++++------------------- 2 files changed, 69 insertions(+), 57 deletions(-) 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); } }