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. 03集成 mind-elixir 转换器
  4. 04节流解析与渲染
  5. 05滚动到最新节点

适用场景

  1. 01实时展示 AI 生成的导图
  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 支持两种主要格式:

  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. 优化技巧

  • 节流:不要对每个字节都重新解析和渲染。使用节流(例如,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 提示词集成

使用大模型生成思维导图时,指示模型使用纯文本格式。

评论

支持 Markdown