Skip to content

feat(markdown): render AI translation results with formatting#1161

Open
leozanee wants to merge 2 commits intotisfeng:devfrom
leozanee:dev
Open

feat(markdown): render AI translation results with formatting#1161
leozanee wants to merge 2 commits intotisfeng:devfrom
leozanee:dev

Conversation

@leozanee
Copy link
Copy Markdown

Summary

Render Markdown emitted by streaming AI/LLM services (OpenAI, Claude Code,
Custom OpenAI, Gemini, etc.) into formatted NSAttributedString in result
cells, instead of showing the raw ## / ** markers.

Scope

  • New MarkdownRenderer (Swift, no deps): block + inline pass with
    streaming-safety (unterminated ** / fences don't crash)
  • MarkdownLabel extends EZLabel, falls back to plain-text path when
    disabled — non-AI services and dark mode keep working unchanged
  • MarkdownToggleButton per-card override icon with 0.2s fade transition
  • Settings → General → Display global toggle (default ON)
  • Localized en / zh-Hans / zh-Hant / sk

Non-goals

  • Window resize UX is unchanged (separate concern)
  • Tables, footnotes, HTML embedded in markdown not supported (rare in AI output)

Test plan

  • MarkdownRendererTests (16 cases: blocks, inline, partial input, perf)
  • Manual: query AI service, verify headings / bold / lists render
  • Manual: per-card toggle flips one card without affecting siblings
  • Manual: Google / Bing / DeepL / Youdao show no toggle button, render unchanged
  • Build and run on macOS 13+

Render Markdown source from streaming AI/LLM services as styled
NSAttributedString in result cells: headings, emphasis, blockquotes,
lists, fenced/inline code, and links.

Plain-text services (Google, Bing, DeepL, Youdao) keep their existing
appearance. Add a global toggle in Settings → Display and a per-card
icon button with fade transition for inline override.
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello leozanee, Thank you for your first PR contribution 🎉 leozanee

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 43b9d20b83

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +33 to +34

let source = text
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Guard nil text before reading in Markdown label

This override reads text directly and then calls isEmpty, but EZLabel invokes updateDisplayedText during superclass setup (via its property setters) before text is guaranteed to be initialized. In that initialization path, the Obj-C backing ivar can still be nil, which can trap when bridged to Swift here and crash when creating streaming result cells. Please coalesce to an empty string before checking emptiness.

Useful? React with 👍 / 👎.

Comment on lines +252 to +254
if char == "*" || char == "_",
let close = findClose(of: String(char), in: scalars, after: index + 1),
close > index + 1 {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid parsing in-word underscores as italic markers

The inline emphasis branch treats _ like * without word-boundary checks, so identifiers such as foo_bar_baz are rendered as foobarbaz with the middle segment italicized. This alters literal content that frequently appears in AI output (paths, variable names, filenames) and can make results inaccurate even when the source text is correct.

Useful? React with 👍 / 👎.

- Mark `EZLabel.text` as nullable to reflect the runtime contract: the
  ivar is nil during super init when font / line-spacing setters trigger
  `updateDisplayedText`, which previously would trap Swift bridging.
- Require word boundaries around `_` in italic emphasis so identifiers
  like `foo_bar_baz` keep literal underscores instead of italicizing
  the middle segment. `*` is unchanged.
- Cover both with new MarkdownRenderer tests.
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.

1 participant