Skip to content

ai-shifu/remark-flow

Repository files navigation

Remark Flow

A remark plugin library for parsing MarkdownFlow documents

MarkdownFlow (also known as MDFlow or markdown-flow) extends standard Markdown with AI to create personalized, interactive pages. Its tagline is "Write Once, Deliver Personally".

npm version License: MIT TypeScript

English | 简体中文

🚀 Quick Start

Install

npm install remark-flow
# or
yarn add remark-flow
# or
pnpm add remark-flow

Basic Usage

import { remark } from 'remark';
import remarkFlow from 'remark-flow';

const processor = remark().use(remarkFlow);

const markdown = `
# Welcome to Interactive Content!

Choose one option: ?[Option A | Option B | Option C]
Choose multiple skills: ?[%{{skills}} JavaScript||TypeScript||Python]
Enter your name: ?[%{{username}}...Please enter your name]
`;

const result = processor.processSync(markdown);
// Each ?[...] becomes a structured custom-variable node in the AST

Advanced Usage

import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkFlow from 'remark-flow';
import remarkStringify from 'remark-stringify';

const processor = unified()
  .use(remarkParse)
  .use(remarkFlow)
  .use(remarkStringify);

const result = processor.processSync(`
Select theme: ?[%{{theme}} Light//light | Dark//dark | ...custom]
Action: ?[Save Changes//save | Cancel//cancel]
`);

🧩 Supported Syntax Patterns

1. Simple Buttons

?[Submit]
?[Continue | Cancel]
?[Yes | No | Maybe]

Output: { buttonTexts: ["Yes", "No", "Maybe"], buttonValues: ["Yes", "No", "Maybe"], isMultiSelect: false }

2. Custom Button Values

?[Save Changes//save-action]
?[确定//confirm | 取消//cancel]

Output: { buttonTexts: ["Save Changes"], buttonValues: ["save-action"] }

3. Variable Text Input

?[%{{username}}...Enter your name]
?[%{{age}}...How old are you?]
?[%{{comment}}...]

Output: { variableName: "username", placeholder: "Enter your name" }

4. Variable Button Selection (Single-Select)

?[%{{theme}} Light | Dark]
?[%{{size}} Small//S | Medium//M | Large//L]

Output: { variableName: "theme", buttonTexts: ["Light", "Dark"], buttonValues: ["Light", "Dark"], isMultiSelect: false }

5. Variable Button Selection (Multi-Select)

?[%{{skills}} JavaScript||TypeScript||Python]
?[%{{lang}} JS//JavaScript||TS//TypeScript||PY//Python]

Output: { variableName: "skills", buttonTexts: ["JavaScript", "TypeScript", "Python"], buttonValues: ["JavaScript", "TypeScript", "Python"], isMultiSelect: true }

6. Combined: Buttons + Text Input

# Single-select with text input

?[%{{size}} Small//S | Medium//M | Large//L | ...custom size]

# Multi-select with text input

?[%{{tags}} React||Vue||Angular||...Other framework]

Output:

{
  variableName: "size",
  buttonTexts: ["Small", "Medium", "Large"],
  buttonValues: ["S", "M", "L"],
  placeholder: "custom size"
}

7. Separator Priority Rules

The first separator type encountered determines the parsing mode:

# Single-select mode (| appears first)

?[%{{option}} A | B||C] # Results in: ["A", "B||C"]

# Multi-select mode (|| appears first)

?[%{{option}} A||B | C] # Results in: ["A", "B | C"]

Key Points:

  • | = Single-select mode, || becomes part of button values
  • || = Multi-select mode, | becomes part of button values
  • First separator type wins and determines the entire parsing behavior

8. Unicode & International Support

?[%{{语言}} English//en | 中文//zh | 日本語//ja]
?[%{{用户名}}...请输入您的姓名]
?[👍 Good | 👎 Bad | 🤔 Unsure]

📖 API Reference

Plugin Exports

// Default export (recommended)
import remarkFlow from 'remark-flow';

// Named exports
import {
  remarkFlow, // Main plugin, functionally the same as the default export
  remarkInteraction, // The core plugin, which is also the default export
  remarkCustomVariable, // Variable-focused plugin
  createInteractionParser, // Parser factory
  InteractionType, // Type enums
} from 'remark-flow';

Output Format

All plugins transform ?[...] syntax into custom-variable AST nodes:

interface CustomVariableNode extends Node {
  type: 'custom-variable';
  data: {
    variableName?: string; // For %{{name}} syntax
    buttonTexts?: string[]; // Button display text
    buttonValues?: string[]; // Corresponding button values
    placeholder?: string; // Text input placeholder
  };
}

Parser API

import { createInteractionParser, InteractionType } from 'remark-flow';

const parser = createInteractionParser();

// Parse content and get detailed result
const result = parser.parse('?[%{{theme}} Light | Dark]');

// Parse and convert to remark-compatible format
const remarkData = parser.parseToRemarkFormat('?[%{{theme}} Light | Dark]');

🔗 Usage Examples

remark-flow can be used in two main ways:

  1. Standalone - Parse and transform syntax, then render with your own UI components
  2. With markdown-flow-ui - Use the pre-built React components for instant interactive UI

🎯 Standalone Usage (Custom Rendering)

When using remark-flow standalone, you parse the syntax and create your own UI components based on the AST nodes.

Basic AST Transformation

import { remark } from 'remark';
import { visit } from 'unist-util-visit';
import remarkFlow from 'remark-flow';
import type { Node } from 'unist';

const processor = remark().use(remarkFlow);

const markdown = `
# Choose Your Preferences

Select language: ?[%{{language}} JavaScript | Python | TypeScript | Go]
Enter your name: ?[%{{username}}...Your full name]
Action: ?[Save//save | Cancel//cancel]
`;

// Parse and examine the AST
const ast = processor.parse(markdown);
processor.runSync(ast);

// Find custom-variable nodes
visit(ast, 'custom-variable', (node: any) => {
  console.log('Found interaction:', node.data);
  // Output: { variableName: 'language', buttonTexts: ['JavaScript', 'Python', 'TypeScript', 'Go'], buttonValues: [...] }
});

Custom HTML Renderer

import { visit } from 'unist-util-visit';
import { remark } from 'remark';
import remarkHtml from 'remark-html';

function createCustomRenderer() {
  return (tree: Node) => {
    visit(tree, 'custom-variable', (node: any) => {
      const { variableName, buttonTexts, buttonValues, placeholder } =
        node.data;

      if (buttonTexts && buttonTexts.length > 0) {
        // Render as button group
        const buttonsHtml = buttonTexts
          .map((text, i) => {
            const value = buttonValues?.[i] || text;
            return `<button onclick="selectOption('${variableName}', '${value}')" class="interactive-btn">
              ${text}
            </button>`;
          })
          .join('');

        node.type = 'html';
        node.value = `
          <div class="button-group" data-variable="${variableName}">
            ${buttonsHtml}
          </div>
        `;
      } else if (placeholder) {
        // Render as text input
        node.type = 'html';
        node.value = `
          <div class="input-group">
            <label for="${variableName}">${placeholder}</label>
            <input
              id="${variableName}"
              name="${variableName}"
              placeholder="${placeholder}"
              class="interactive-input"
            />
          </div>
        `;
      }
    });
  };
}

// Use with remark processor
const processor = remark()
  .use(remarkFlow)
  .use(createCustomRenderer)
  .use(remarkHtml);

const result = processor.processSync(markdown);
console.log(result.toString()); // HTML with custom interactive elements

React Custom Components

import React from 'react';
import { remark } from 'remark';
import remarkReact from 'remark-react';
import remarkFlow from 'remark-flow';

// Custom React components for interactive elements
const InteractiveButton = ({ variableName, buttonTexts, buttonValues, onSelect }) => (
  <div className="flex gap-2">
    {buttonTexts.map((text, i) => (
      <button
        key={i}
        onClick={() => onSelect(variableName, buttonValues[i])}
        className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
      >
        {text}
      </button>
    ))}
  </div>
);

const InteractiveInput = ({ variableName, placeholder, onInput }) => (
  <div className="my-2">
    <input
      type="text"
      placeholder={placeholder}
      onChange={(e) => onInput(variableName, e.target.value)}
      className="border border-gray-300 rounded px-3 py-2 w-full"
    />
  </div>
);

// Usage in React component
function CustomMarkdownRenderer() {
  const handleInteraction = (variableName, value) => {
    console.log(`${variableName}: ${value}`);
    // Handle user interaction
  };

  const processor = remark()
    .use(remarkFlow)
    .use(remarkReact, {
      remarkReactComponents: {
        'custom-variable': ({ node }) => {
          const { variableName, buttonTexts, buttonValues, placeholder } = node.data;

          if (buttonTexts?.length > 0) {
            return (
              <InteractiveButton
                variableName={variableName}
                buttonTexts={buttonTexts}
                buttonValues={buttonValues}
                onSelect={handleInteraction}
              />
            );
          }

          if (placeholder) {
            return (
              <InteractiveInput
                variableName={variableName}
                placeholder={placeholder}
                onInput={handleInteraction}
              />
            );
          }

          return null;
        },
      },
    });

  const content = `
  # Interactive Form

  Choose language: ?[%{{lang}} English | 中文 | Español]
  Your name: ?[%{{name}}...Enter your name]
  Action: ?[Submit//submit | Reset//reset]
  `;

  return <div>{processor.processSync(content).result}</div>;
}

🎨 With markdown-flow-ui (Pre-built Components)

For a complete React component library with ready-to-use interactive components, use markdown-flow-ui.

Basic Integration

import { MarkdownFlow } from 'markdown-flow-ui';

function InteractiveChat() {
  const content = `
  # Welcome! 👋

  Select your preference: ?[%{{language}} JavaScript | Python | TypeScript]
  Enter your name: ?[%{{username}}...Your full name]
  Ready to start: ?[Let's Go!//start]
  `;

  return (
    <MarkdownFlow
      initialContentList={[{ content }]}
      onSend={(data) => {
        console.log('User interaction:', data);
        // Handle user interactions
      }}
      typingSpeed={30}
    />
  );
}

For advanced examples with streaming, multi-step forms, and more features, see:

📊 Comparison: Standalone vs markdown-flow-ui

Aspect Standalone Usage With markdown-flow-ui
Setup Complexity Medium - Need custom rendering Low - Pre-built components
Customization High - Full control over UI Medium - Theme/style customization
Bundle Size Smaller - Only remark plugin Larger - Full React component library
Framework Support Any (React, Vue, vanilla JS, etc.) React only
Advanced Features Manual implementation needed Built-in (streaming, typewriter, etc.)
Use Case Custom UI requirements, non-React Rapid prototyping, React projects

🌐 MarkdownFlow Ecosystem

remark-flow is part of the MarkdownFlow ecosystem for creating personalized, AI-driven interactive documents:

  • markdown-flow - The main repository containing homepage, documentation, and interactive playground
  • markdown-flow-agent-py - Python agent for transforming MarkdownFlow documents into personalized content
  • remark-flow - Remark plugin to parse and process MarkdownFlow syntax in React applications
  • markdown-flow-ui - React component library for rendering interactive MarkdownFlow documents

💖 Sponsors

AI-Shifu

AI-Shifu.com

AI-powered personalized learning platform

📄 License

MIT License - see LICENSE file for details.

🙏 Acknowledgments

📞 Support

About

Remark plugin to parse and process MarkdownFlow syntax in React applications

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 8