AI回答代码块高亮加一键复制

需求很常见:AI 返回的 Markdown 里有大段代码,得高亮,还得在每个代码块右上角放个「复制」按钮。我用 markdown-it + highlight.js 搞定,踩了几个坑,记一下。

整体思路

三步走:

  1. markdown-it 把 Markdown 转 HTML,配置里挂上 highlight.js 做语法高亮。
  2. 用一个 renderer.rules.fence 重写,给每个代码块包一层带「复制」按钮的容器。
  3. 复制按钮用事件委托统一监听,别给每个按钮单独绑。

高亮配置

javascript 复制代码
import MarkdownIt from 'markdown-it';
import hljs from 'highlight.js';
import 'highlight.js/styles/github-dark.css';

const md = new MarkdownIt({
  highlight(code, lang) {
    if (lang && hljs.getLanguage(lang)) {
      try {
        return hljs.highlight(code, { language: lang }).value;
      } catch (_) {}
    }
    return md.utils.escapeHtml(code); // 兜底转义,防 XSS
  },
});

那个兜底的 escapeHtml 别省。AI 有时会返回它自己都不认识的语言标记,或者干脆没标语言,这时如果不转义直接塞进 DOM,万一代码里有 <img onerror=...> 就出事了。

给代码块包按钮

重写 fence 规则,在原始渲染结果外面套一层:

ini 复制代码
const defaultFence = md.renderer.rules.fence;
md.renderer.rules.fence = (tokens, idx, options, env, self) => {
  const raw = tokens[idx].content;
  const html = defaultFence(tokens, idx, options, env, self);
  // 把原始代码存进 data 属性,复制时直接取
  const encoded = encodeURIComponent(raw);
  return `
    <div class="code-block">
      <button class="copy-btn" data-code="${encoded}">复制</button>
      ${html}
    </div>`;
};

把原始代码 encodeURIComponent 后塞进 data-code,复制时拿这个,而不是去读高亮后那堆 <span>textContent。我一开始偷懒读 textContent,结果发现高亮 DOM 里有时会混进多余空白和不可见字符,复制出来的代码缩进对不上,干脆改回存原始串。

一键复制 + 事件委托

流式场景下代码块是一个个动态冒出来的,逐个 addEventListener 既麻烦又容易内存泄漏。直接在容器上委托:

ini 复制代码
container.addEventListener('click', async (e) => {
  const btn = e.target.closest('.copy-btn');
  if (!btn) return;
  const code = decodeURIComponent(btn.dataset.code);
  try {
    await navigator.clipboard.writeText(code);
    btn.textContent = '已复制';
    setTimeout(() => (btn.textContent = '复制'), 1500);
  } catch {
    btn.textContent = '复制失败';
  }
});

两个真实的坑

第一,navigator.clipboard 只在 HTTPS 或 localhost 下可用。内网 IP + HTTP 直接 undefined,本地联调时我对着控制台一脸懵,后来才想起来要么上 HTTPS,要么退回老掉牙的 document.execCommand('copy') 兜底。这个 API 现在虽然废弃了,但作为 HTTP 环境的备胎还真不能少。

第二,流式输出时代码块还没闭合就被渲染。模型刚吐到代码块一半,还没来,`markdown-it` 会把后面所有内容都当成代码。我的处理是流式过程中检测未闭合的 数量是奇数,就临时补一个,等流结束再用完整文本重渲一次。有点 hack,但比让用户看半天「假代码块」强。

highlight.js 全量包挺大的,按需引入语言能省不少体积,但配置略繁,小项目我一般直接全量,懒。

收尾

前端这层渲染壳是我手搓的,对话背后的智能体是在一个能拖拽编排的平台上零代码搭的。模型 API 我也没自建,用的讯飞这种 MaaS------现成大模型接口直接调,省了部署和运维。

相关推荐
AIDANHANG7 小时前
最小可售:不是最小可炫
人工智能
鲲穹AI种草7 小时前
图片水印处理工具记录:多款 AI 去水印工具能力边界整理
人工智能·图片水印处理工具
企鹅的企7 小时前
2027北京AI健康科技与智慧医疗展官方精准邀约进行中
人工智能·科技
高德诚7 小时前
探索桌面智能的无限可能:小小舵机,撑起 AI 仿生交互的完整未来
人工智能
2601_956319887 小时前
2026年零基础学量化:从看懂示例到写清条件和动作
人工智能·python
ArkAPI7 小时前
Claude Code 连发安全修复:AI 编程 Agent 的权限,正在成为新的“安全事故高发区”
人工智能·大模型·api·codex·ai工具·claude code·arkapi
Thecozzy7 小时前
大厂 AI Coding 面试大招
人工智能·面试·职场和发展
维核科技7 小时前
推理加速实战:投机解码与KV Cache优化让大模型“快起来“
人工智能
数智化管理手记7 小时前
海量数据如何沉淀有效数据资产?数据标准化治理方案如何搭建?
java·大数据·人工智能