async function resolveJumpLinks(titles) { if (!titles || titles.length === 0) return {}; const config = await loadMergedConfig(); const filePathCfg = config.apiConfig.filePath; const publicBase = config.apiConfig.materialPublicUrl.url; const reqField = filePathCfg.requestParams[0].field; const respField = filePathCfg.responseParams[0].field; return new Promise((resolveOuter) => { chrome.storage.local.get(["token"], async (res) => { const token = res.token; if (!token) return resolveOuter({}); try { const body = {}; body[reqField] = titles; const resp = await fetch(filePathCfg.url, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": token }, body: JSON.stringify(body) }); const data = await resp.json(); const fileMap = data[respField] || {}; const map = {}; for (const title of Object.keys(fileMap)) { if (fileMap[title]) { const cleanPath = fileMap[title].replace(/\.md$/, ".html"); map[title] = `${publicBase}/${cleanPath}`; } } resolveOuter(map); } catch (e) { resolveOuter({}); } }); }); } async function renderSources(sourceList, sources, jumpMap) { sourceList.style.marginTop = "12px"; sourceList.style.borderTop = "1px solid #dbe3f3"; sourceList.style.paddingTop = "7px"; 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); // ul 包裹所有参考资料 const ul = document.createElement("ul"); ul.style.paddingLeft = "1.2em"; ul.style.margin = "0"; ul.style.listStyle = "disc"; ul.style.fontSize = "14px"; for (const src of sources) { const url = jumpMap[src.title]; const li = document.createElement("li"); li.style.marginBottom = "3px"; li.style.lineHeight = "1.7"; let scoreText = ""; if (typeof src.score === "number") { scoreText = `(相似度:${(src.score * 100).toFixed(2)}%)`; } 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"); li.appendChild(link); if (scoreText) { const scoreSpan = document.createElement("span"); scoreSpan.style.color = "#8990a1"; scoreSpan.style.fontSize = "12.5px"; scoreSpan.style.marginLeft = "6px"; scoreSpan.textContent = scoreText; li.appendChild(scoreSpan); } } else { li.textContent = (src.title || "未命名文档") + (scoreText ? " " + scoreText : ""); li.style.color = "#666"; } ul.appendChild(li); } sourceList.appendChild(ul); } // === 复制代码块功能 === function wrapHtmlWithCopyBtn(html) { const wrapper = document.createElement('div'); wrapper.innerHTML = html; wrapper.querySelectorAll('pre > code').forEach(codeEl => { const pre = codeEl.parentElement; if (pre.querySelector('.copy-btn')) return; const btn = document.createElement('button'); btn.className = 'copy-btn'; btn.innerText = '复制'; pre.appendChild(btn); pre.style.position = 'relative'; }); return wrapper.innerHTML; } function bindAllCopyButtons(container) { (container || document).querySelectorAll('.llm-md .copy-btn').forEach(btn => { if (btn.dataset.bind === "true") return; btn.dataset.bind = "true"; btn.addEventListener('click', function (e) { e.stopPropagation(); const codeEl = btn.parentElement.querySelector('code'); const codeText = codeEl ? (codeEl.innerText || codeEl.textContent) : ""; let success = false, error = null; try { const textarea = document.createElement("textarea"); textarea.value = codeText; textarea.setAttribute('readonly', ''); textarea.style.position = "absolute"; textarea.style.left = "-9999px"; document.body.appendChild(textarea); textarea.select(); success = document.execCommand('copy'); document.body.removeChild(textarea); if (success) { btn.innerText = '已复制!'; setTimeout(() => btn.innerText = '复制', 1200); } else { btn.innerText = '失败'; setTimeout(() => btn.innerText = '复制', 1200); } } catch (err) { error = err; btn.innerText = '失败'; setTimeout(() => btn.innerText = '复制', 1200); } if (!success) { alert("复制失败: " + (error ? error : "未知错误")); } }); }); } function insertCopyFullAnswerBtn(msgBox, answerStr) { if (msgBox.querySelector('.copy-full-answer-btn')) return; const copyBtn = document.createElement('button'); copyBtn.className = 'copy-full-answer-btn'; copyBtn.innerText = '复制全部回答'; copyBtn.style.margin = "12px 0 0 0"; copyBtn.style.display = "block"; copyBtn.style.background = "#eef1f9"; copyBtn.style.color = "#2d53a7"; copyBtn.style.border = "1px solid #dde4ef"; copyBtn.style.borderRadius = "6px"; copyBtn.style.padding = "5px 18px"; copyBtn.style.fontSize = "13.2px"; copyBtn.style.cursor = "pointer"; copyBtn.style.float = "right"; copyBtn.style.position = "relative"; copyBtn.style.right = "0"; copyBtn.style.transition = "background 0.13s"; copyBtn.onmouseenter = () => (copyBtn.style.background = "#dbeafe"); copyBtn.onmouseleave = () => (copyBtn.style.background = "#eef1f9"); copyBtn.onclick = () => { // 1. 回答正文 let answerText = (answerStr || "").trim(); // 2. 参考资料 markdown(遍历 ul/li/a) let refArr = []; const ul = msgBox.querySelector('.llm-sources ul'); if (ul) { ul.querySelectorAll('li').forEach(li => { let link = li.querySelector('a'); let title = link ? link.textContent : li.textContent; let url = link ? link.getAttribute('href') : ""; // 分值(span) let scoreSpan = li.querySelector('span'); let scoreText = scoreSpan ? scoreSpan.textContent : ""; refArr.push(`- [${title}](${url})${scoreText}`); }); } let refText = ""; if (refArr.length > 0) { refText = "\n\n参考资料:\n" + refArr.join("\n"); } const textToCopy = answerText + refText; try { const textarea = document.createElement("textarea"); textarea.value = textToCopy; textarea.setAttribute('readonly', ''); textarea.style.position = "absolute"; textarea.style.left = "-9999px"; document.body.appendChild(textarea); textarea.select(); document.execCommand('copy'); document.body.removeChild(textarea); copyBtn.innerText = "已复制!"; setTimeout(() => copyBtn.innerText = "复制全部回答", 1200); } catch (err) { copyBtn.innerText = "复制失败"; setTimeout(() => copyBtn.innerText = "复制全部回答", 1200); alert("复制失败:" + err); } }; msgBox.appendChild(copyBtn); } function promptLLMPasswordModal() { return new Promise((resolve) => { // 避免重复弹窗 if (document.getElementById("llm-password-modal")) return resolve(""); // 创建遮罩层 const mask = document.createElement("div"); mask.id = "llm-password-modal-mask"; mask.style = ` position: fixed; left:0; top:0; width:100vw; height:100vh; background:rgba(0,0,0,0.19); z-index:9998;`; // 创建弹窗 const modal = document.createElement("div"); modal.id = "llm-password-modal"; modal.style = ` position: fixed; top: 45%; left: 50%; transform: translate(-50%, -50%); background: #fff; border-radius: 10px; box-shadow: 0 8px 38px #0001; padding: 24px 32px 18px 32px; z-index:9999; min-width:260px; text-align:center;`; modal.innerHTML = `