Streaming Mindmap Rendering by ssshooter/mind-elixir-core
npx skills add https://github.com/ssshooter/mind-elixir-core --skill 'Streaming Mindmap Rendering'本技能将指导您使用 mind-elixir 实现一个流式思维导图渲染器。这项技术允许您在 AI 模型生成数据或从流中获取数据时,实时显示一个不断增长的思维导图。
mind-elixir 库首先,确保已安装 mind-elixir。
npm install mind-elixir
为 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,
})
// 初始空状态或加载状态
meRef.current.init(data || { nodeData: { topic: '加载中...', id: 'root' } })
return () => {
// 必要时进行清理
}
}, [])
// 更新效果
useEffect(() => {
if (meRef.current && data) {
// 用新数据刷新图表
meRef.current.refresh(data)
}
}, [data])
return <div ref={elRef} style={{ height: '500px', width: '100%' }} />
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
本技能的核心是高效处理数据流并解析可能不完整的数据。
Mind Elixir 支持两种主要格式:
- 根节点
- 子节点 1
- 子节点 1-1
- 子节点 1-2
- 子节点 1-3
- }:2 前两个节点的摘要
- 子节点 2
- 子节点 2-1 [^id1]
- 子节点 2-2 [^id2]
- 子节点 2-3 {color: "#e87a90"}
- > [^id1] <-双向链接-> [^id2]
- 子节点 3
- 子节点 3-1 [^id3]
- 子节点 3-2 [^id4]
- 子节点 3-3 [^id5]
- > [^id3] >-单向链接-> [^id4]
- > [^id3] <-单向链接-< [^id5]
- 子节点 4
- 子节点 4-1 [^id6]
- 子节点 4-2 [^id7]
- 子节点 4-3 [^id8]
- } 所有先前节点的摘要
- 子节点 4-4
- > [^id1] <-链接位置不受限制,只要在渲染时能找到 id-> [^id8]
使用 mind-elixir/plaintextConverter(或自定义解析器)将文本转换为 Mind Elixir JSON 格式。
import { plaintextToMindElixir } from 'mind-elixir/plaintextConverter'
// 如果您的流包含 Markdown 代码块,用于清理的辅助函数
function cleanStreamContent(content: string): string {
return content
.replace(/^```[\w]*\n?/gm, '')
.replace(/```$/gm, '')
.trim()
}
// 在父组件中的状态钩子
const [mindmapData, setMindmapData] = useState<MindElixirData | null>(null)
const accumulatedText = useRef('')
const lastRenderTime = useRef(0)
// 流式处理函数(通用示例)
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
// 限制更新频率以避免 UI 冻结
const now = Date.now()
if (now - lastRenderTime.current > 500) {
// 500ms 节流
updateMindmap()
lastRenderTime.current = now
}
}
// 最终更新
updateMindmap()
}
function updateMindmap() {
try {
const cleanText = cleanStreamContent(accumulatedText.current)
const data = plaintextToMindElixir(cleanText)
setMindmapData(data) // 这会触发 MindmapRenderer 中的 useEffect
} catch (e) {
// 忽略因数据块不完整导致的解析错误
console.warn('忽略部分解析错误')
}
}
// 滚动到最后一个节点(在 MindmapRenderer 的更新效果中)
const lastNode = findLastNode(data.nodeData) // 实现遍历以找到最后一个节点
if (lastNode?.id) {
const nodeEle = meRef.current.findEle(lastNode.id)
if (nodeEle) meRef.current.scrollIntoView(nodeEle)
}
当使用 LLM 生成思维导图时,指示模型使用纯文本格式。
每周安装次数
–
代码仓库
GitHub 星标数
3.0K
首次出现
–
安全审计
This skill guides you through implementing a streaming mindmap renderer using mind-elixir. This technique allows you to display a mindmap that grows in real-time as data is generated by an AI model or fetched from a stream.
mind-elixir libraryFirst, ensure you have mind-elixir installed.
npm install mind-elixir
Create a wrapper component for mind-elixir to handle the lifecycle and updates.
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%' }} />
}
The core of this skill is efficiently handling the stream and parsing potentially incomplete data.
Mind Elixir supports two main formats:
- 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]
Use mind-elixir/plaintextConverter (or a custom parser) to convert text to the Mind Elixir JSON format.
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')
}
}
Throttling : Do not re-parse and re-render on every single byte. Use a throttle (e.g., 200-500ms).
Stable Root : Ensure the parsing logic maintains a stable root ID if possible, to prevent the whole graph from flashing.
Scroll to Last : To follow the generation, you can programmatically scroll to the last added node.
// 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) }
When generating mindmaps with LLMs, instruct the model to use the plaintext format.
Weekly Installs
–
Repository
GitHub Stars
3.0K
First Seen
–
Security Audits
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
109,600 周安装