FessのRAGチャット機能で、LLMからの応答をMarkdownとしてレンダリングできるようにしました。これにより、見出しやテーブル、コードブロックなどが整形された状態で表示されるようになります。また、ストリーミング中の途中の応答も適切にMarkdownとして表示されます。
背景
これまでFessのチャット画面では、LLMからの応答をプレーンテキストとして表示していました。LLMの応答にはMarkdown形式で見出しやリスト、コードブロックなどが含まれることが多いため、そのまま表示すると読みにくい状態でした。特にストリーミング応答中は、Markdownのソースがそのまま見えてしまう問題がありました。
変更内容
marked.jsとDOMPurifyの導入
クライアントサイドでMarkdownをHTMLに変換するために、marked.js(v17.0.4)を導入しました。また、XSS対策としてDOMPurifyを組み合わせて使用しています。
サニタイズポリシーは、サーバーサイドのMarkdownRenderer(OWASPサニタイザー)と同等の設定にしています。
- 許可するHTMLタグを限定(見出し、リスト、テーブル、コードブロックなど)
- リンクには
rel="nofollow"を自動付与 class属性はcode、pre、span、div要素のみに制限data-*属性は不許可- URIは
https?://のみ許可
ストリーミング対応
ストリーミング応答では、受信中のテキストに対してrenderMarkdown()を呼び出すことで、途中の応答もMarkdownとしてレンダリングされます。従来の.text()による表示を.html(renderMarkdown())に変更し、リアルタイムにMarkdown変換を行っています。
CSSスタイルの追加
レンダリングされたMarkdown要素に対して、チャットUIに適したスタイルを追加しました。
- 見出し(h1〜h6)のフォントサイズとマージン
- テーブルのボーダーとパディング
- 引用ブロックの左ボーダー
- リンクの色とホバースタイル
フォールバック
marked.jsやDOMPurifyの読み込みに失敗した場合は、従来どおりHTMLエスケープしたプレーンテキストとして表示するフォールバック処理を実装しています。