yoi/tickets/tui-assistant-markdown.md
2026-05-05 18:30:25 +09:00

4.7 KiB
Raw Blame History

TUI: Assistant 応答の Markdown スタイル表示

背景

LLM の出力は実質 Markdown だが、TUI は Block::AssistantText { text }push_padded_lines で 1 行ずつ素のテキストとして Style(MessageKind::Assistant) に流しているだけで、**強調** / `code` / # 見出し / - list 等の記号がそのまま見える状態になっている crates/tui/src/ui.rs:592-595, 640-648)。スタイルが付かないため、 構造のあるアシスタント応答は読みにくい。

ratatui 0.30 の Vec<Line<'static>> で表現できる範囲のスタイル付けで 十分目的を満たせる。既存の wrap_line_intocrates/tui/src/ui.rs:473-)が span 単位のラップを既に実装しているため、Markdown レンダラは スタイル付きの Vec<Line> を返すだけでよく、ラップスクロールoverview 畳み込みの仕組みを変える必要はない。

方針

  • pulldown-cmarktui クレートの依存に追加し、Event ストリームを 既存の MessageKind / ratatui::style::Style 体系へ畳み込む小さな 自前レンダラを crates/tui/src/markdown.rs に置く。
  • レンダラの公開面は render(text: &str, base: Style) -> Vec<Line<'static>> 程度の 1 関数。Block::AssistantTextMode::Detail / Mode::Normal 描画から呼ぶ。Mode::Overview は現行通り 1 行畳み込みMarkdown 記号 含めて表示しても情報量はほぼ同じなので素のテキストでよい)。
  • ストリーミング中の不完全要素(未閉鎖の ** や開きっぱなしのフェンス) は CommonMark の流儀テキスト扱いEOF で閉じる)に任せる。挙動が 破綻する場合だけ末尾要素を素のテキストにフォールバックする小さな 後処理を入れる余地を残す。
  • tui-markdown クレートは採用しない。syntect 依存でビルドが肥大する 割にカスタマイズが効かず、本クレートの色味(MessageKind パレット)との整合を握りにくいため。

対応する Markdown 要素

最小限の "対応できる範囲" を以下に限定する。CommonMark + GFM の一部。

  • 強調: **bold** / *italic* / ~~strike~~GFM
  • インラインコード: `code`
  • フェンスコードブロック: ```lang / ```(言語タグは無視、 ブロック全体を等幅・低彩度の背景/前景で塗る)
  • 見出し: H1〜H4H5/H6 は H4 と同等)
  • 箇条書きリスト: - / * / +、ネスト可(深さ分インデント)
  • 順序リスト: 1. / 1)、ネスト可(番号は元の値で表示)
  • 引用: > ...(ネスト可)
  • 水平線: --- / ***
  • リンク: [text](url)text をリンク色で着色URL は表示しない)

範囲外

  • GFM table
  • 画像 ![alt](src)(テキストとしても表示しない)
  • HTML パススルー(タグはそのまま生テキストで出る)
  • 数式($...$ / $$...$$
  • コードブロックの syntax highlighting
  • リンクのターミナルクリックOSC 8URL の自動表示
  • Thinking 本文 / SystemMessage への適用 (同じ markdown::render を後で差せばよい。本チケットは Block::AssistantText のみ)
  • ライブストリーム最中の "途中要素のフォールバック" の作り込み CommonMark のデフォルト挙動で破綻が見えたら別チケット)

完了条件

  • アシスタント応答に含まれる上記要素が、それぞれ視認可能な スタイルで描画される。
  • ストリーミング中、テキストが追記されるたびに描画が更新され、 フェンスコードブロックの開きが先に着いて中身が後から流れる ようなケースでも、テキスト全体の見た目が大きく崩れない。
  • Mode::Detail / Mode::Normal で Markdown スタイルが、 Mode::Overview では従来通りの 1 行畳み込みが出る。
  • 既存の wrap_line_into によるラップ・右パディング・スクロール が引き続き機能する(行幅計算が乱れない)。

影響範囲

  • crates/tui/Cargo.toml: pulldown-cmark を追加(cargo add 経由)。
  • crates/tui/src/markdown.rs: 新設。render(&str, Style) -> Vec<Line<'static>>
  • crates/tui/src/ui.rs: Block::AssistantText 分岐で Markdown レンダラを呼ぶ。Mode::Overview は現行のまま。
  • crates/tui/src/main.rs または lib.rs: 新モジュールの宣言。

Review