AI Vibe Craft
← Назад к AI Vibe News

Редакция 20 июня 2026 г.

Разборы

Mind map из Markdown-списка: как Open MindMap стыкует потоковый вывод LLM с живой SVG-картой

Mind map из Markdown-списка: как Open MindMap стыкует потоковый вывод LLM с живой SVG-картой.

Потоковый ответ модели можно вести прямо в интерфейс mind map — если единственным источником правды остаётся обычный Markdown-список с отступами, без отдельного конвейера постобработки. разбор Open MindMap Разработчик Open MindMap (@amery2010) в свежем build-log на Dev.to показывает, почему такой формат превращает LLM streaming в побочный эффект архитектуры, а не отдельную «AI-фичу» в коде компонента.

Почему потоковый вывод LLM «выпадает почти бесплатно» из формата данных

Open MindMap — React-компонент, где карта хранится не в проприетарном бинарнике и не в WYSIWYG-хранилище, а в plain indented Markdown-like list. Автор прямо связывает это с заголовком поста: indented list превращается в живую SVG mind map, и «LLM streaming fall out for free».

Для AI-assisted UX смысл простой: модель естественно выдаёт иерархию списком с отступами — токен за токеном. Частичный буфер при этом остаётся валидным Markdown: неполный список — это просто список покороче. Компоненту не нужно знать, что за строкой стоит LLM: любой вызывающий код, который на каждом тике передаёт растущую строку через setMarkdown, получает тот же эффект «растущей» карты.

Числовых замеров latency или стоимости streaming в материале нет — только качественное обещание «almost free» через дизайн, а не через бенчмарк.

Архитектура text → tree → layout → SVG для diff-friendly AI-генерации

Архитектуру автор описывает одной цепочкой:

text  →  tree  →  layout  →  SVG

Каждый этап — pure и однонаправленный, и каждый round-trips обратно в text. Симметрия parse ↔ serialize важна для diff-friendly карт, программной генерации и того же streaming: изменение в git — одна строка diff, а не непрозрачная бинарная дельта.

Модель данных MindMapData держит id, text, опционально children, remark, taskStatus и plugin-поля (tags, anchorId, crossLinks, collapsed и др.). Координаты, ширины и цвета в модели не живут — позиция и стиль выводятся при layout и render. Для vibe coding это удобная граница: LLM и человек правят текст; визуал пересчитывается.

Рендер — pure SVG (не canvas, без внешней graph/layout library): чёткий zoom, CSS-темизация через ~30 переменных, бесплатный export. Tradeoff — много DOM-узлов на больших деревьях; folding смягчает, но полноценная виртуализация SVG-дерева автором названа отдельным проектом.

Демо: mindmap.u14.app. Код — github.com/u14app/mindmap, npm-пакет @xiangfa/mindmap, лицензия Apache-2.0.

Парсер O(n) и плагины: неполный LLM-вывод не ломает дерево

Парсер — однопроходный stack по глубине отступа; маркеры -, *, + снимаются. Сложность O(n) по числу строк, без backtracking. Несколько top-level строк через пустую строку дают forest of trees на одном canvas.

После структурного parse работают два слоя синтаксиса. Line-level markers — теги вроде #framework, task [x]/[ ], remark >, anchor {#launch}, cross-link -> {#launch}. Inline formatting — lazy parseInlineMarkdown только при рендере узла (off-screen и collapsed не токенизируются).

Семь встроенных плагинов (tags, folding, cross-links, LaTeX, dotted lines, multi-line, frontmatter) включены по умолчанию и tree-shakeable; плагин = пара hooks на parse и render. Round-trip API: parseMarkdownList / toMarkdownList и мультикорневой вариант — встроенный text editor переключает visual map ↔ raw Markdown без lossy conversion.

Для потокового сценария критично второе свойство парсера: он cheap и total — не падает на неполном вводе. Полустрока становится узлом с коротким text до следующего тика; не нужна state machine «дождаться complete element».

setMarkdown на каждом чанке: паттерн потребителя LLM-stream

Типичный потребитель потока в посте выглядит так:

let buffer = "";
for await (const chunk of llmStream) {
  buffer += chunk;
  mindMapRef.current?.setMarkdown(buffer); // re-parse partial text every tick
}

На каждом тике — O(n) re-parse всего buffer и свежее дерево в React. Звучит расточительно, но автор опирается на два механизма «гладкости» без цифр на бумаге:

  1. Парсер дешёвый и толерантный к обрывам — UI не мигает ошибками валидации на середине слова.
  2. Diff между тиками маленький: reconciliation и SVG re-layout трогают только изменившееся; карта растёт, а не перерисовывается целиком.

Встроенная AI input bar шлёт запросы на любой OpenAI-compatible endpoint; streaming для неё не special-cased — тот же setMarkdown(growing string). Отсюда и тезис: компонент «не обязан знать, что существует LLM», а интеграция с агентом или IDE сводится к подаче растущего текста.

Оговорка безопасности из поста: AI bar отправляет API key из браузера — ок для local prototyping; в production автор рекомендует proxy, чтобы ключ оставался server-side.

Экспорт, layout-caveats и production-ограничения AI-бара

Export покрывает SVG (walk rendered tree + inline <style>), PNG через SVG → canvas high DPI, Markdown через toMarkdownList / exportToOutline. Для AI-воркфлоу полезно, что и визуал, и текст остаются производными одного текстового источника.

Автор честно перечисляет caveats, которые не отменяют streaming, но влияют на production:

  • измерение ширины текста и поздние web fonts → layout bugs после first paint;
  • большие деревья → потолок DOM-узлов;
  • cross-links — второй слой рёбер, routing без пересечений «genuinely unsolved here»;
  • browser-side API keys → нужен proxy в реальных деплоях.

Итог для разработчика AI-интерфейсов: Open MindMap — не привязка к конкретному вендору LLM, а архитектурный паттерн plain-text source of truth + incremental re-parse. Потоковая mind map из модели становится естественным следствием формата, если вы готовы платить пересчётом дерева на каждом чанке — без обещанных в посте количественных бенчмарков, зато с открытым кодом и демо для проверки на своих промптах.

Источники