How OpenMAIC Transforms Documents into Interactive Classroom Experiences: A Complete Technical Guide

OpenMAIC converts static teaching materials into dynamic, interactive classrooms through a six-stage pipeline that imports PPTX/PDF files, stores them as structured stage documents, generates AI-powered content and media, and renders them as interactive React components.

The THU-MAIC/OpenMAIC repository provides a modular framework that transforms documents into interactive classroom experiences by chaining specialized subsystems under the @openmaic/* monorepo namespace. This architecture enables educators to upload raw materials like PowerPoint presentations or PDFs and automatically receive fully interactive lessons with embedded quizzes, multimedia, and live-editable components. Understanding this transformation requires examining the specific technical stages from import to rendering, as implemented in the source code.

The Six-Stage Transformation Pipeline

OpenMAIC processes documents through a deterministic pipeline that normalizes, persists, generates, and renders educational content. Each stage is encapsulated in a dedicated package within the monorepo.

Step 1: Document Import and Normalization

The importer subsystem (packages/@openmaic/importer) handles the ingestion of raw teaching materials. According to the Importer README, the system parses uploaded files (PPTX, PDF, or plain text) and normalizes them into the MAIC slide DSL (Slide[]).

The public API exposes two primary entry points: importPptx() for PowerPoint files and parse() for PDF documents. During import, the system optionally uploads embedded media to a user-provided OSS (Object Storage Service), replacing binary blobs with URLs to ensure the resulting slide objects remain JSON-safe.

Step 2: Structured Storage as Stage Documents

Once normalized, the slide objects persist in the document store (@openmaic/storage). The system creates a stage document (MaicDocument) that contains the outline, scenes, assets, and runtime metadata.

The storage contracts, defined in packages/@openmaic/storage/docs/, guarantee JSON-safe payloads and support multiple back-ends including server-side Postgres and browser-based IndexedDB. The DocumentStore.putStage() method writes the document using the stageId as an immutable identifier for all subsequent operations.

Step 3: AI-Powered Content Generation

The generation engine (@openmaic/generation) drives a multi-stage LLM pipeline through the server-side /api/generate-classroom endpoint. As documented in the Generation README, the pipeline executes three distinct phases:

  • Outline creation → scene planning
  • Content generation (text, quizzes, actions)
  • Media generation (images, video, TTS)

All prompts live in packages/@openmaic/generation/templates/ and execute according to the two-stage generation strategy: first producing an outline, then generating each scene independently. This enables parallel media generation and fine-grained retry logic.

Step 4: Interactive React Rendering

The renderer (@openmaic/renderer) converts the persisted MaicDocument into an interactive HTML canvas. It ingests the slide DSL and produces a React component tree supporting drag-and-drop interactions, embedded quizzes, widgets, and video playback.

When slides reference generated media (images, video, TTS) that are still processing, the renderer injects skeleton placeholders until the generation service returns the final URLs. These placeholders store as generation references (elementId) within the document schema.

Step 5: Runtime Agent Utilities

During live classroom sessions, the agent-runtime layer (skills/agent-runtime/*) provides utilities to manipulate the stored document. The Stage DSL SKILL documentation defines three core operations:

  • read_stage: Retrieves current document state
  • patch_stage: Applies targeted edits to scenes or content
  • grep_stage: Queries document contents

These utilities enable real-time modifications during active lessons while maintaining the integrity of the stageId-based document structure.

Step 6: End-to-End Integration

The complete transformation flow follows this sequence: user uploads a document → importPptx() → DocumentStore.putStage → generation job (/api/generate-classroom) → poll until complete → renderer displays the interactive lesson. This pipeline appears in the repository's top-level README under the generation pipeline diagram.

Key Architectural Principles

The transformation pipeline relies on several architectural patterns that ensure scalability and extensibility.

Modular SDK Design with @openmaic/* Packages

Each concern (import, storage, generation, rendering) lives in its own npm package (@openmaic/importer, @openmaic/storage, @openmaic/generation, @openmaic/renderer). This modularity lets developers embed only necessary components—for example, a custom LMS can call importPptx → generateSceneContent → renderSlide without importing the entire framework.

Contract-First Storage Design

HTTP contracts for documents, assets, and KV stores are defined in packages/@openmaic/storage/docs/. The server implements these contracts in @openmaic/storage/server, ensuring front-end and back-end synchronization regardless of persistence layer changes. This contract-first approach guarantees that switching from Postgres to IndexedDB requires no client-side code modifications.

Two-Stage Generation Strategy

The generation pipeline employs a two-stage flow: outline production followed by independent scene generation. This architecture enables parallel processing of media generation tasks and provides fine-grained retry logic at the scene level rather than regenerating entire documents. The stageId serves as the immutable capability for all subsequent reads and patches throughout this process.

Async Media Placeholders

When a slide references a generated asset (image, video, or TTS), the system stores a generation reference (elementId) rather than blocking the render pipeline. The React renderer displays skeleton UI until the asynchronous generation service returns the final asset URL, ensuring immediate interactivity even while media processes in the background.

Implementing the Transformation Pipeline

The following code examples demonstrate the end-to-end implementation using the published @openmaic/* packages. Each snippet corresponds to a specific stage in the transformation pipeline.

Importing Documents

// packages/@openmaic/importer implementation
import { importPptx } from '@openmaic/importer';
import type { Slide } from '@openmaic/importer';

async function loadSlides(file: File | Buffer): Promise<Slide[]> {
  // importPptx returns a fully-normalised Slide[] ready for the renderer
  return await importPptx(file, {
    // optional: upload media to your own OSS and return URLs
    upload: async (blob) => {
      const url = await myOssUpload(blob);
      return url;               // the Slide will contain the URL instead of base64
    },
  });
}

Persisting Stage Documents

// packages/@openmaic/storage implementation
import { DocumentStore } from '@openmaic/storage/document/pg';
import { MaicDocument } from '@openmaic/dsl';

async function createStage(slides: Slide[]) {
  const stageId = `stage-${Date.now()}`;
  const doc: MaicDocument = {
    id: stageId,
    name: 'My New Lesson',
    dslVersion: 'v1',
    outline: { /* generated later */ },
    scenes: slides.map((s, i) => ({
      id: `scene-${i}`,
      content: { canvas: s },   // canvas holds the Slide DSL
    })),
    // runtime, assets, etc. are empty for now
  };
  await DocumentStore.putStage(doc);
  return stageId;
}

Triggering Generation Jobs

// Server-side endpoint invocation
import fetch from 'node-fetch';

async function startGeneration(stageId: string) {
  const resp = await fetch(`${process.env.OPENMAIC_URL}/api/generate-classroom`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ stageId, enableImageGeneration: true, enableTTS: true }),
  });
  const { jobId } = await resp.json();
  return jobId;
}

Polling for Completion

async function pollJob(jobId: string) {
  while (true) {
    const r = await fetch(`${process.env.OPENMAIC_URL}/api/job/${jobId}`);
    const { status, result } = await r.json();
    if (status === 'completed') return result.stageId;
    if (status === 'failed') throw new Error('Generation failed');
    await new Promise((res) => setTimeout(res, 60000)); // 60s back-off
  }
}

Rendering the Interactive Classroom

// React component implementation
import { MaicRenderer } from '@openmaic/renderer';
import { DocumentStore } from '@openmaic/storage/document/pg';

function Lesson({ stageId }: { stageId: string }) {
  const [doc, setDoc] = React.useState(null);
  React.useEffect(() => {
    DocumentStore.getStage(stageId).then(setDoc);
  }, [stageId]);

  return doc ? <MaicRenderer document={doc} /> : <p>Loading…</p>;
}

Summary

  • OpenMAIC transforms documents through six distinct stages: Import, Storage, Generation, Rendering, Runtime, and End-to-End Integration.
  • The importer (@openmaic/importer) normalizes PPTX/PDF files into Slide[] DSL using importPptx() or parse().
  • Storage (@openmaic/storage) persists content as MaicDocument stage documents with stageId as the immutable reference.
  • Generation (@openmaic/generation) executes a two-stage LLM pipeline via /api/generate-classroom with templates in packages/@openmaic/generation/templates/.
  • Rendering (@openmaic/renderer) converts DSL to interactive React components, using skeleton placeholders for async media.
  • Runtime utilities (skills/agent-runtime/stage-dsl/SKILL.md) provide read_stage, patch_stage, and grep_stage for live classroom manipulation.
  • The architecture supports extensibility through environment-based provider configuration (provider-keys.md) allowing custom LLM and media model integration.

Frequently Asked Questions

How does OpenMAIC handle different document formats like PDF and PowerPoint?

OpenMAIC abstracts format differences through the @openmaic/importer package. The system exposes importPptx() for PowerPoint files and parse() for PDFs, both returning a normalized Slide[] DSL that downstream components consume uniformly. This normalization ensures that the storage, generation, and rendering layers remain agnostic to the original file format.

What is the two-stage generation strategy in OpenMAIC?

The two-stage generation strategy first produces a high-level outline, then generates each scene independently based on that outline. This approach enables parallel processing of media generation tasks and allows fine-grained retry logic at the scene level rather than regenerating entire documents. The strategy is implemented in the /api/generate-classroom endpoint and documented in the generation pipeline diagrams.

How does OpenMAIC manage asynchronous media generation?

When content requires generated images, video, or TTS, the system stores generation references (elementId) in the document schema rather than blocking the render pipeline. The React renderer (@openmaic/renderer) displays skeleton UI placeholders until the asynchronous generation service returns final asset URLs. This ensures immediate interactivity while media processes in the background.

What storage options does OpenMAIC support for stage documents?

The @openmaic/storage package implements a contract-first design supporting both server-side Postgres and browser-based IndexedDB back-ends. The DocumentStore class provides putStage() and getStage() methods that work identically across back-ends, ensuring that switching storage technologies requires no changes to client code or the generation pipeline.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →