mcpField
S
Streaming Mindmap Rendering

Streaming Mindmap Rendering

github.com/ssshooter開発3.2k72 回インストール1 週間前に更新
リポジトリ

Streaming Mindmap Rendering について

Streaming Mindmap Renderingは、mind-elixirでマインドマップをリアルタイムに描画する実装を案内するスキルです。フロントエンド開発者向けに、ストリームのテキストを解析し、生成または取得したデータに合わせてマップを更新する方法を説明します。

主な機能

  1. 01ストリームからのマップ逐次描画
  2. 02未完了のプレーンテキスト解析
  3. 03mind-elixirコンバーターとの連携
  4. 04解析と描画のスロットリング
  5. 05最新ノードへのスクロール

活用シーン

  1. 01AI生成中のマップの表示
  2. 02受信したテキストストリームからのマップ描画

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/ssshooter/mind-elixir-core --skill streaming-mindmap-rendering

インストール後、Claude Code で/streaming-mindmap-renderingを入力して読み込まれたか確認します。

詳細

ストリーミングでのマインドマップ描画

このスキルでは、mind-elixir を使ってストリーミング対応のマインドマップ描画機能を実装する方法を説明します。この手法を使うと、AI モデルが生成したデータやストリームから取得したデータに合わせて、リアルタイムで成長するマインドマップを表示できます。

前提条件

  • React(または任意のフロントエンドフレームワーク。例では React を使用します)
  • mind-elixir ライブラリ

1. 依存関係のインストール

まず、mind-elixir がインストールされていることを確認します。

npm install mind-elixir

2. コンポーネントの構造

ライフサイクルと更新を管理するために、mind-elixir 用のラッパーコンポーネントを作成します。

import MindElixir, { type MindElixirData, type MindElixirInstance } from 'mind-elixir'
import { useEffect, useRef } from 'react'

export function MindmapRenderer({ data }: { data: MindElixirData | null }) {
  const elRef = useRef<HTMLDivElement>(null)
  const meRef = useRef<MindElixirInstance | null>(null)

  useEffect(() => {
    if (!elRef.current) return

    meRef.current = new MindElixir({
      el: elRef.current,
      direction: MindElixir.RIGHT,
    })

    // Initial empty state or loading state
    meRef.current.init(data || { nodeData: { topic: 'Loading...', id: 'root' } })

    return () => {
      // Cleanup if necessary
    }
  }, [])

  // Update effect
  useEffect(() => {
    if (meRef.current && data) {
      // Refresh the graph with new data
      meRef.current.refresh(data)
    }
  }, [data])

  return <div ref={elRef} style={{ height: '500px', width: '100%' }} />
}

3. ストリーミングと解析のロジック

このスキルの要となるのは、ストリームを効率的に処理し、不完全な可能性があるデータを解析することです。

データ形式

Mind Elixir は、主に次の 2 つの形式をサポートしています。

  1. JSON(ネイティブ):階層型のツリー構造です。JSON は完成するまで無効なため、ストリーミングには適していません。
  2. プレーンテキスト(ストリーミングに推奨):インデントベース、または Markdown のリスト形式のテキストです。部分的な状態でも解析しやすくなっています。
プレーンテキスト形式の例
- Root Node
  - Child Node 1
    - Child Node 1-1
    - Child Node 1-2
    - Child Node 1-3
    - }:2 Summary of first two nodes
  - Child Node 2
    - Child Node 2-1 [^id1]
    - Child Node 2-2 [^id2]
    - Child Node 2-3 {color: "#e87a90"}
    - > [^id1] <-Bidirectional Link-> [^id2]
  - Child Node 3
    - Child Node 3-1 [^id3]
    - Child Node 3-2 [^id4]
    - Child Node 3-3 [^id5]
    - > [^id3] >-Unidirectional Link-> [^id4]
    - > [^id3] <-Unidirectional Link-< [^id5]
  - Child Node 4
    - Child Node 4-1 [^id6]
    - Child Node 4-2 [^id7]
    - Child Node 4-3 [^id8]
    - } Summary of all previous nodes
    - Child Node 4-4
  - > [^id1] <-Link position is not restricted, as long as the id can be found during rendering-> [^id8]
解析の実装

mind-elixir/plaintextConverter(またはカスタムパーサー)を使って、テキストを Mind Elixir の JSON 形式に変換します。

import { plaintextToMindElixir } from 'mind-elixir/plaintextConverter'

// Helper to clean Markdown code blocks if your stream includes them
function cleanStreamContent(content: string): string {
  return content
    .replace(/^```[\w]*\n?/gm, '')
    .replace(/```$/gm, '')
    .trim()
}

// State hooks in your parent component
const [mindmapData, setMindmapData] = useState<MindElixirData | null>(null)
const accumulatedText = useRef('')
const lastRenderTime = useRef(0)

// Streaming function (Generic Example)
async function startStreaming(url: string) {
  const response = await fetch(url)
  const reader = response.body?.getReader()
  const decoder = new TextDecoder()

  if (!reader) return

  while (true) {
    const { done, value } = await reader.read()
    if (done) break

    const chunk = decoder.decode(value)
    accumulatedText.current += chunk

    // Throttle updates to avoid freezing the UI
    const now = Date.now()
    if (now - lastRenderTime.current > 500) {
      // 500ms throttle
      updateMindmap()
      lastRenderTime.current = now
    }
  }

  // Final update
  updateMindmap()
}

function updateMindmap() {
  try {
    const cleanText = cleanStreamContent(accumulatedText.current)
    const data = plaintextToMindElixir(cleanText)
    setMindmapData(data) // This triggers the useEffect in MindmapRenderer
  } catch (e) {
    // Ignore parse errors from incomplete chunks
    console.warn('Partial parse error ignored')
  }
}

4. 最適化のヒント

  • スロットリング:1 バイトごとに再解析・再描画しないでください。スロットル(例:200~500ms)を使用します。
  • 安定したルート:可能であれば、解析ロジックで安定したルート ID を維持し、グラフ全体がちらつくのを防ぎます。
  • 最後のノードまでスクロール:生成の進行を追えるように、最後に追加されたノードまでプログラムからスクロールできます。
// Scroll to last node (inside MindmapRenderer update effect)
const lastNode = findLastNode(data.nodeData) // Implement traversal to find last node
if (lastNode?.id) {
  const nodeEle = meRef.current.findEle(lastNode.id)
  if (nodeEle) meRef.current.scrollIntoView(nodeEle)
}

5. AI プロンプトとの統合

LLM でマインドマップを生成する際は、プレーンテキスト形式を使うようモデルに指示します。

コメント