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------现成大模型接口直接调,省了部署和运维。

相关推荐
通问AI1 小时前
Apple Intelligence 国行备案深度技术解析:阿里千问如何被集成到苹果端侧AI架构
人工智能·架构
视***间1 小时前
算力赋能零售与创意新生态:视程空间Pandora,解锁线下场景智能化无限可能
人工智能·边缘计算·智慧零售·ai算力·视程空间·创意开发
冬奇Lab2 小时前
MCP 系列(08):企业治理——Registry、路由与可观测性
人工智能·llm·mcp
冬奇Lab2 小时前
每日一个开源项目(第161篇):Open Interpreter - 开源 AI 编程 Agent,用 Rust 重写,支持 Kimi、Qwen、DeepSeek
人工智能·开源·资讯
renhongxia12 小时前
世界模型,是“空中楼阁”还是AGI的“最后一块拼图”?
运维·服务器·数据库·人工智能·算法·agi
天上路人2 小时前
AU-48双麦多功能语音处理模组:100dB AEC + 波束定向拾音,会议与监控全场景覆盖
人工智能·ai降噪·语音处理·ai语音·麦克风·回音消除
甲维斯2 小时前
Codex定制皮肤,Windows一键切换已经搞定!
人工智能
逐米时代2 小时前
AI智能体与制造执行系统(MES)的数据打通
人工智能
dunge20263 小时前
2026年7月最新ChatGPT Plus / Pro 与 Codex:当 AI Agent 最新5.6版本来袭,必须理解事务、幂等与补偿
开发语言·人工智能·python
研究员子楚3 小时前
GEO行业发展标准体系白皮书V2.0-第10卷 · 全球篇:跨国标准协同与全球品牌语义治理框架
数据库·人工智能·microsoft·架构·geo