Skip to content

feat: 支持可配置的聊天 HTML/CSS 内联预览 - #1041

Merged
xintaofei merged 7 commits into
spacering-net:mainfrom
ana52070:feat/html-codeblock-preview-657
Oct 27, 2026
Merged

xintaofei merged 7 commits into
spacering-net:mainfrom
ana52070:feat/html-codeblock-preview-657

Conversation

@ana52070

Copy link
Copy Markdown
Contributor

原问题

AI 在回复中输出 HTML 代码块时,用户只能阅读源码,无法直接查看卡片、对比矩阵等排版效果。自包含的 HTML/CSS 片段与 Markdown 正文混排时,也缺少对应的预览能力。

Closes #657

改动

  • 在「设置 → 外观」增加「聊天 HTML 渲染」开关,默认关闭,持久保存并即时生效。
  • 开启后,在 AI 回复正文中直接预览 html / htm 代码块以及顶层自包含 HTML/CSS 片段,支持与 Markdown 混排,并可切换查看源码。
  • 复用现有 HTML 预览基础设施,适配主题、自动高度和长内容展开/收起。
  • 对流式回复保留已完成片段,仅继续解析生成中的尾部;不完整片段显示生成状态,避免反复挂载已完成的预览。
  • HTML 在沙箱 iframe 中渲染,剥离模型脚本、事件处理器和主动内容,通过 CSP 限制外部资源。只保留内部测高脚本,不执行模型生成的 JavaScript。
  • 增加 10 种语言的设置文案。普通代码块、用户消息、公式和 Mermaid 保持原有渲染路径。

测试

  • 本地相关回归:41 个测试文件、524 项通过,覆盖设置持久化与同步、HTML 识别、源码保留、安全过滤及流式稳定性。
  • 本地 tsc --noEmit、修改文件的 ESLint、Prettier、git diff --check 均通过;生产构建成功,静态导出 37 页。
  • Windows 桌面端人工测试通过。
  • fork 第二轮 CI:前端及 Linux、Windows 全部通过;前端全量 725 个测试文件、10701 项测试通过,静态导出构建成功。4 项 macOS 任务因 GitHub ARM64 hosted runner 容量不足、未分配到 runner 而取消,未执行,完整 CI 未全绿。
  • 首轮 CI中 macOS desktop 的测试和 clippy 已通过,macOS server / CLI 同样未分配到 runner。

截图

图一:设置开关

设置中的聊天 HTML 渲染开关

图二:对话内预览

Markdown 与 HTML 卡片、图表及对比矩阵混排

ana52070 and others added 7 commits October 26, 2026 20:29
The chat preview's sanitizer removed `href` but kept SVG's `xlink:href`
and `<set>`/`<animate>` elements that write a link target, so a click
could still navigate the sandboxed frame to an outside page, where CSP no
longer applies. Any attribute named `href` or `*:href` is now dropped
unless it points into the document itself (`#id`), which also keeps SVG
`<use>` and gradient references and in-page anchors working, and
animations that target a link are removed.
The theme tokens, the size reporter and the theme reader moved from the
Codex visualize card to html-preview-frame.ts without their comments,
which stayed behind in the card above unrelated declarations.
With the setting on, every assistant reply was drawn in Streamdown's
streaming mode even after it finished, so finished replies lost
per-paragraph text direction and left-to-right code, reference-style
links stayed literal text, and json-render cards stayed in their loading
state. Finished replies render statically again; a preview that mounts
anew when its reply stops streaming comes back from a cache of the built
document, its measured height and the view the reader picked.

Only self-contained HTML becomes a preview: a fragment's root has to
close inside the run of HTML blocks it starts. Tags that wrap Markdown (a
<details> around a list) or never close before Markdown are left to
Markdown instead of swallowing the rest of the reply into a code block,
and a synthetic closing fence always ends its line.

While streaming, a fragment is final only once the block after it is
known, so a sibling root on the next line still joins it and the
finished output matches processing the whole text at once. A closed HTML
fence always previews, and an agent-written codeg-html-pending fence
shows as code once the reply is done.

Streaming no longer re-parses the whole reply on every delta: text with
no line that can open HTML skips the parse, the parse covers block
structure only, everything before the last block is sealed, and an open
fence at the end is only watched for its closing line.

The preview nonce comes from the utils randomUUID, which has a fallback
for plain-HTTP origins where crypto.randomUUID is missing, and over code
the Code/Preview switch sits clear of the code block's copy and download
buttons.
A preview that mounted from the frame cache relied on the cache for its
document; once 32 newer previews pushed its entry out, its next render
fell back to "Generating HTML…" for good. Previews on screen now pin their
entries, and eviction passes over pinned ones.
Settling text at block boundaries also cut the Markdown around fragments
into pieces for the math-delimiter rewrite, so a `\[ … \]` across a blank
line streamed differently from the same reply processed at once. Only
the structure is settled now: output is kept up to the last settled
fragment, and the Markdown after it is kept raw and transformed whole.

The settled state also outlived edits that were not appends, keeping
decisions made for text that had changed; any such edit now starts over.
@xintaofei

Copy link
Copy Markdown
Collaborator

codeg work task 434 is done — #1041 (12 files, +694/-120).

@xintaofei
xintaofei merged commit 9c53c17 into spacering-net:main Oct 27, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

【需求】能不能增加一下,ai输出html代码块的时候能够预览,类似Gemini那些在线chat

2 participants