import { Fragment, type ReactNode } from "react"; import { parseContent } from "../lib/parseContent"; import { parseInlineMarkup, type InlineNode } from "../lib/inlineMarkup"; /** 正文渲染口径(两种界面形态各一套,别混用): * · 缺省 / `lineBreaks:false` = **chat 气泡正文** —— `` 是气泡分隔符,在上一层 * (`components/chat.tsx` 的 `splitParagraphs`)就切成独立气泡了;这里绝不把 `` * 变成换行(等于把「多条消息」偷偷还原成「一条消息里的换行」,与主人口径冲突)。 * · `lineBreaks:true` = **非 chat 的单卡正文**(工作 / 代码 / 文档 / 首页 / 视觉 / 语音 / * 智能体 / 设置)—— 那些界面没有「多条气泡」的概念,`` 只换行、不切分。 */ export interface RenderOptions { lineBreaks?: boolean; } /** 非 chat **单卡正文**的统一渲染口径(工作 / 代码 / 文档 / 首页 / 视觉 / 语音 / 智能体 / * 设置,以及这些模式里的用户气泡):`` ⇒ 换行。抽成一处常量,避免各卡片各写一份 * 而在日后漂移(chat 气泡**不**用它 —— 那里 `` 是气泡分隔符)。 */ export const CARD_RENDER: RenderOptions = { lineBreaks: true }; /** 渲染助手/用户消息正文。`` / `` 块在这里**不**进入气泡 —— 它们由 * `splitBubble` 剥出、以气泡外的注记呈现(单卡正文里则整块略去);剩下的文本段落交给 * 行内白名单渲染。 * * 🔴 这里**只**做行内(`b`/`strong`/`i`/`em`/`code`/`u`)的事:段落 `` 与消息断隔 * `` 都在上一层(`components/chat.tsx` 的 `splitParagraphs`)就已切成独立气泡。 * * 🔴 安全:文本一律交给 React 当**文本子节点**渲染(默认转义),全程没有 * `innerHTML` / `dangerouslySetInnerHTML`,也没有字符串拼 HTML —— 白名单外的标签 * (`
` 与消息断隔 * `` 都在上一层(`components/chat.tsx` 的 `splitParagraphs`)就已切成独立气泡。 * * 🔴 安全:文本一律交给 React 当**文本子节点**渲染(默认转义),全程没有 * `innerHTML` / `dangerouslySetInnerHTML`,也没有字符串拼 HTML —— 白名单外的标签 * (`