ワンクリックで
frontend-markdown-rendering
当需要在前端把最终 assistant 文本渲染为 Markdown,同时保留工具过程为纯文本/JSON,并支持表格、代码块、图片、KaTeX 公式、Mermaid 图和 workspace 本地图片安全访问时使用。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
当需要在前端把最终 assistant 文本渲染为 Markdown,同时保留工具过程为纯文本/JSON,并支持表格、代码块、图片、KaTeX 公式、Mermaid 图和 workspace 本地图片安全访问时使用。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
将 arXiv、旧会议、旧期刊或自定义 LaTeX 论文工程迁移到目标会议/期刊投稿模板;用于模板 A 到模板 B 的 LaTeX class/style/bibliography/单双栏转换、全匿名投稿检查、页数约束、图表公式排版、PDF 可视化检查、文本/图片/表格/公式守恒、模板标准文件守恒和最终压页。
当需要部署或训练 LLM/VLM 时使用;覆盖 vLLM OpenAI-compatible 服务、多模态输入限制、Qwen3.5 工具调用、thinking/reasoning 控制、CUDA Graph 策略,以及 ms-swift SFT/DPO/GRPO full training、Megatron 长序列训练、messages loss/loss_scale、数据校验、显存排错和训练检查。
当需要在 lab cluster 1 / PJLAB 上使用开发机、rlaunch worker 或 rjob 任务时使用;覆盖交互 SSH、安全边界、路径规范、代理、CPU/GPU 分区、训练/部署、服务访问和排错,并要求使用原始 rlaunch/rjob 命令。
Use when installing, configuring, running, embedding, deploying, or debugging InternScience ResearchHarness as a lightweight tool-using LLM agent runtime, including CLI runs, local frontend UI, OpenAI-compatible API server, Python API, tool selection, workspaces, traces, compaction, tests, and read-only source inspection.
Use when configuring or debugging the context-overlay OpenAI-compatible proxy for deterministic prompt/context injection, prompt patching, rule matching, request routing, rejection rules, skill_dir retrieval, streaming forwarding, and local or tunneled proxy validation.
当需要用 structai.read_pdf 将 PDF 文档解析成本地 Markdown、抽取图片资源,并处理 MinerU 解析缓存或代理重试问题时使用。
| name | frontend-markdown-rendering |
| description | 当需要在前端把最终 assistant 文本渲染为 Markdown,同时保留工具过程为纯文本/JSON,并支持表格、代码块、图片、KaTeX 公式、Mermaid 图和 workspace 本地图片安全访问时使用。 |
推荐库:
markedDOMPurifyKaTeXMermaid/api/workspace-file 路由前端可以直接使用 CDN:
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.2.6/dist/purify.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked@15.0.12/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/auto-render.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mermaid@11.12.0/dist/mermaid.min.js"></script>
如果页面需要 KaTeX 样式,同时引入 CSS:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css">
如果使用 Mermaid,初始化一次:
mermaid.initialize({
startOnLoad: false,
securityLevel: 'strict',
theme: 'default'
});
marked、DOMPurify、KaTeX、Mermaid 行为会随版本变化;渲染异常时先确认页面实际加载版本。require.resolve(...) 定位源码入口。node_modules;需要 patch 时写项目侧 wrapper/helper,并保留最小复现。console.log('marked', marked?.version || marked);
console.log('DOMPurify', DOMPurify?.version || DOMPurify);
console.log('katex', katex?.version || katex);
console.log('mermaid', mermaid?.version || mermaid);
node - <<'JS'
for (const name of ['marked', 'dompurify', 'katex', 'mermaid']) {
try {
console.log(name, require.resolve(name));
} catch (error) {
console.log(name, 'not installed locally; check CDN URL instead');
}
}
JS
只在最终结果消息上启用 Markdown:
function shouldRenderMarkdown(message) {
return (
message.role === 'assistant' &&
message.termination === 'result' &&
!message.tool_calls &&
!message.tool_call_id
);
}
不要对这些内容做 Markdown 渲染:
推荐流程:
text
-> 如果整个输出被 ```markdown ... ``` 包住,则去掉最外层 fence
-> 保护公式片段
-> marked.parse(..., { gfm: true })
-> 重写本地 workspace 图片路径
-> DOMPurify.sanitize(...)
-> 恢复公式片段
-> 插入 .markdown-body
-> KaTeX 渲染公式
-> Mermaid 渲染图
核心函数骨架:
function stripOuterMarkdownFence(text) {
const trimmed = text.trim();
const match = trimmed.match(/^```(?:markdown|md)?\s*\n([\s\S]*?)\n```$/i);
return match ? match[1] : text;
}
function renderAssistantMarkdown(text, options) {
const source = stripOuterMarkdownFence(text);
const protectedMath = protectMathSegments(source);
let html = marked.parse(protectedMath.text, {
gfm: true,
breaks: false
});
html = rewriteWorkspaceImageSrc(html, options);
html = DOMPurify.sanitize(html, {
ADD_TAGS: ['math'],
ADD_ATTR: ['target', 'rel']
});
html = restoreMathSegments(html, protectedMath.segments);
const body = document.createElement('div');
body.className = 'markdown-body';
body.innerHTML = html;
renderMath(body);
renderMermaidBlocks(body);
return body;
}
支持这些公式分隔符:
$$...$$ # 段间 / block formula
\[...\] # 段间 / block formula
\(...\) # 句中 / inline formula
默认句中公式优先使用 \(...\)。不主动支持单 $...$,因为它容易和普通文本、金额、shell 变量冲突。
如果产品必须兼容用户输入的单 $...$,只能做受限支持:
\frac、\sum、\alpha、_、^、=、<、>、\cdot 等数学信号。$...$。pre、code、textarea、script、style 内的 $...$。\(...\)、\[...\] 和 $$...$$。公式要先从 Markdown 文本中临时替换成 token,等 marked + DOMPurify 后再恢复。否则 marked 可能改坏反斜杠或公式结构。
公式保护骨架:
function protectMathSegments(text) {
const segments = [];
const pattern = /(\$\$[\s\S]*?\$\$|\\\[[\s\S]*?\\\]|\\\([\s\S]*?\\\))/g;
const replaced = text.replace(pattern, (match) => {
const token = `@@MATH_${segments.length}@@`;
segments.push(match);
return token;
});
return { text: replaced, segments };
}
function restoreMathSegments(html, segments) {
return html.replace(/@@MATH_(\d+)@@/g, (_, index) => segments[Number(index)] || '');
}
KaTeX 渲染:
function renderMath(body) {
renderMathInElement(body, {
delimiters: [
{ left: '$$', right: '$$', display: true },
{ left: '\\[', right: '\\]', display: true },
{ left: '\\(', right: '\\)', display: false }
],
ignoredTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'],
throwOnError: false
});
}
Markdown 中写:
```mermaid
graph TD
A --> B
```
marked 会生成 pre code.language-mermaid。前端找到这些 block 后调用 mermaid.render,再把原 code block 替换成 SVG 容器。
let mermaidCounter = 0;
async function renderMermaidBlocks(body) {
const blocks = Array.from(body.querySelectorAll('pre code.language-mermaid'));
for (const code of blocks) {
const source = code.textContent || '';
const wrapper = document.createElement('div');
wrapper.className = 'mermaid-chart';
try {
const id = `mermaid-${Date.now()}-${mermaidCounter++}`;
const result = await mermaid.render(id, source);
wrapper.innerHTML = result.svg;
code.closest('pre').replaceWith(wrapper);
} catch (error) {
wrapper.textContent = source;
wrapper.classList.add('mermaid-chart--error');
code.closest('pre').replaceWith(wrapper);
}
}
}
在线图片和 data URL 可以保留:


本地 workspace 图片应被前端改写:

改写为:
/api/workspace-file?token=<token>&path=assets/result.png
前端改写逻辑:
function rewriteWorkspaceImageSrc(html, options) {
const root = document.createElement('div');
root.innerHTML = html;
root.querySelectorAll('img').forEach((img) => {
const src = img.getAttribute('src') || '';
if (isExternalOrDataUrl(src)) return;
if (!options.workspaceToken) return;
const url = new URL('/api/workspace-file', window.location.origin);
url.searchParams.set('token', options.workspaceToken);
url.searchParams.set('path', src);
img.setAttribute('src', url.toString());
});
return root.innerHTML;
}
function isExternalOrDataUrl(src) {
return /^(https?:|data:image\/)/i.test(src);
}
后端 /api/workspace-file 必须做三件事:
.png、.jpg、.jpeg、.gif、.webp、.bmp、.svg。后端伪代码:
ALLOWED_IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg"}
def workspace_file(token: str, path: str):
workspace = lookup_workspace_by_token(token)
if workspace is None:
raise NotFound()
target = safe_join(workspace, path)
if not target.is_file():
raise NotFound()
if target.suffix.lower() not in ALLOWED_IMAGE_EXTS:
raise Forbidden()
return send_file(target)
Markdown 容器:
.markdown-body {
overflow-wrap: anywhere;
line-height: 1.62;
}
图片:
.markdown-body img {
display: block;
width: auto;
max-width: 100%;
height: auto;
object-fit: contain;
}
表格:
.markdown-body table {
width: 100%;
border-collapse: collapse;
}
.markdown-body th,
.markdown-body td {
padding: 8px 10px;
border: 1px solid rgba(0, 0, 0, 0.12);
vertical-align: top;
}
.markdown-body table {
display: block;
overflow-x: auto;
}
Mermaid:
.mermaid-chart {
max-width: 100%;
overflow-x: auto;
}
.mermaid-chart svg {
max-width: 100%;
height: auto;
}
代码块:
.markdown-body pre {
max-width: 100%;
overflow-x: auto;
white-space: pre;
}
\(...\)、\[...\]、$$...$$ 能正常渲染,单 $...$ 不作为默认支持。pre code.language-mermaid 能被替换成 SVG。/api/workspace-file 显示。../ 逃出工作目录。marked 前不保护公式,导致反斜杠和换行被改坏。$...$ 后把金额、shell 变量或普通美元符号误渲染成公式。marked.parse 结果,不经过 DOMPurify。<img src>,导致浏览器无法访问或产生路径泄露。