优化markdown格式展示

This commit is contained in:
luke 2025-06-01 22:35:17 +08:00
parent 5d879e490f
commit d3c8841d01
5 changed files with 372 additions and 50 deletions

View File

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

View File

@ -55,12 +55,12 @@
</div> </div>
</div> </div>
<!-- JS模块加载顺序非常重要请保持此顺序 --> <!-- JS模块加载顺序非常重要请保持此顺序 -->
<script src="../scripts/utils.js"></script> <script src="../scripts/utils.js"></script>
<script src="../scripts/auth.js"></script> <script src="../scripts/auth.js"></script>
<script src="../scripts/llm.js"></script> <script src="../scripts/llm.js"></script>
<script src="../scripts/popup.js"></script> <script src="../scripts/popup.js"></script>
<!-- 新增 marked.js CDN -->
<script src="../scripts/markdown-min.js"></script>
</body> </body>
</html> </html>

View File

@ -3,7 +3,6 @@ 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) { async function resolveJumpLinks(titles) {
if (!titles || titles.length === 0) return {}; if (!titles || titles.length === 0) return {};
@ -43,50 +42,6 @@ async function resolveJumpLinks(titles) {
}); });
} }
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) => {
@ -152,6 +107,71 @@ async function getLLMWorkspaceSlug(token, forceRefresh = false) {
}); });
} }
// 仅插入按钮,不绑定事件
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();
// 只复制紧邻 code 元素的内容
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) {
log("[复制代码] 成功,内容:", codeText);
} else {
console.error("[复制代码] 失败", error, "内容:", codeText);
alert("复制失败: " + (error ? error : "未知错误"));
}
});
});
}
// fetchLLMAnswer支持流式markdown、代码块复制按钮
async function fetchLLMAnswer(question, container, contentDiv) { async function fetchLLMAnswer(question, container, contentDiv) {
if (!question || !question.trim()) return; if (!question || !question.trim()) return;
@ -225,12 +245,23 @@ async function fetchLLMAnswer(question, container, contentDiv) {
if (inThink) { if (inThink) {
thinkStr += content.replace(/\n/g, "<br>"); thinkStr += content.replace(/\n/g, "<br>");
} else { } else {
answerStr += content.replace(/\n/g, "<br>"); answerStr += content; // 直接累计 markdown 原文
}
// 渲染 markdown代码块加复制按钮
let html = "";
try {
html = window.marked ? marked.parse(answerStr) : answerStr.replace(/\n/g, "<br>");
} catch (e) {
html = answerStr.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><div class="llm-md">${wrapHtmlWithCopyBtn(html)}</div>`;
// 【关键】渲染后绑定复制事件
bindAllCopyButtons(msgBox);
contentDiv.scrollTop = contentDiv.scrollHeight; contentDiv.scrollTop = contentDiv.scrollHeight;
} }
} }
@ -251,3 +282,70 @@ async function fetchLLMAnswer(question, container, contentDiv) {
msgBox.innerHTML += `<span style='color:red;'>LLM异常${e}</span>`; msgBox.innerHTML += `<span style='color:red;'>LLM异常${e}</span>`;
} }
} }
async function renderSources(sourceList, sources) {
const titles = sources.map(s => s.title).filter(Boolean);
const jumpMap = await resolveJumpLinks(titles);
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);
}

69
scripts/markdown-min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -156,3 +156,158 @@ body.fullscreen #tabContentAi.tab-content.active {
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
} }
.llm-md {
font-size: 14.2px;
line-height: 1.72;
color: #222d3b;
background: #fcfcfe;
border-radius: 7px;
box-shadow: 0 1.5px 6px #e6ebfa33;
border: 1px solid #e7eaf5;
padding: 18px 18px 13px 18px;
margin: 9px 0 7px 0;
overflow-x: auto;
}
/* 段落、标题、块状元素间距 */
.llm-md p,
.llm-md ul, .llm-md ol, .llm-md pre, .llm-md blockquote, .llm-md table {
margin-top: 0;
margin-bottom: 0;
}
.llm-md h1, .llm-md h2, .llm-md h3, .llm-md h4, .llm-md h5, .llm-md h6 {
margin-top: 1.2em;
margin-bottom: 0.6em;
font-weight: bold;
color: #2560de;
line-height: 1.22;
}
.llm-md h1 { font-size: 1.22em; border-bottom: 2px solid #e7eaf5; padding-bottom: 3px; }
.llm-md h2 { font-size: 1.13em; border-bottom: 1px solid #eceff5; padding-bottom: 2px;}
.llm-md h3 { font-size: 1.07em; }
.llm-md h4, .llm-md h5, .llm-md h6 { font-size: 1em; }
.llm-md > * + * {
margin-top: 0.74em; /* 块元素之间舒适的间隔 */
}
.llm-md ul, .llm-md ol {
padding-left: 1.45em;
margin-left: 0;
}
.llm-md li {
margin-bottom: 0.16em;
padding: 0;
line-height: 1.62;
}
.llm-md blockquote {
background: #f6f9fd;
border-left: 4px solid #a4c0f0;
padding: 8px 17px;
margin: 9px 0 11px 0;
color: #395fa9;
font-size: 13.6px;
border-radius: 6px;
}
.llm-md pre {
background: #f5f7fa;
color: #26324b;
border-radius: 8px;
padding: 15px 19px 15px 17px;
font-size: 13.5px;
margin: 8px 0 11px 0;
overflow-x: auto;
box-shadow: 0 2px 8px #e0eaf422;
position: relative;
border: 1px solid #e3e8f2;
font-family: 'JetBrains Mono', 'Fira Mono', 'Consolas', 'monospace';
}
.llm-md pre code {
background: none;
color: inherit;
padding: 0;
font-size: inherit;
font-family: inherit;
white-space: pre;
}
.llm-md code {
background: #f7f6f4;
color: #a72543;
border-radius: 3.5px;
padding: 2.5px 6px;
font-size: 92%;
font-family: 'JetBrains Mono', 'Fira Mono', 'Consolas', 'monospace';
}
/* 复制按钮右上角美观悬浮 */
.llm-md .copy-btn {
position: absolute;
top: 10px;
right: 14px;
background: #eef1f9;
color: #3661b2;
border: 1px solid #dde4ef;
border-radius: 5px;
font-size: 12px;
padding: 2px 13px 2px 13px;
cursor: pointer;
opacity: 0.76;
z-index: 10;
transition: background 0.16s, opacity 0.16s;
box-shadow: 0 1px 3px #ccd4f522;
font-weight: 500;
height: 22px;
line-height: 1.5;
user-select: none;
}
.llm-md .copy-btn:hover {
background: #e1eaff;
opacity: 1;
color: #184d99;
}
.llm-md pre:hover .copy-btn {
opacity: 1;
}
.llm-md table {
border-collapse: collapse;
margin: 12px 0;
width: 100%;
background: #fff;
box-shadow: 0 1px 5px #e3eaf5;
font-size: 13px;
}
.llm-md th, .llm-md td {
border: 1px solid #e5e9f2;
padding: 8px 13px;
text-align: left;
}
.llm-md th {
background: #f5f7fc;
color: #2c4e9c;
font-weight: 600;
}
.llm-md hr {
border: none;
border-top: 1.5px dashed #d1d7e6;
margin: 18px 0 15px 0;
}
.llm-md a {
color: #2560de;
text-decoration: underline dotted;
word-break: break-all;
transition: color 0.16s;
}
.llm-md a:hover {
color: #183b8e;
background: #e8f1ff;
}