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

> Discover how OpenMAIC transforms documents into interactive classroom experiences. This technical guide explores its six-stage pipeline for dynamic AI-powered content.

- Repository: [MAIC/OpenMAIC](https://github.com/THU-MAIC/OpenMAIC)
- Tags: how-to-guide
- Published: 2026-09-12

---

**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

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
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

```typescript
// 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`](https://github.com/THU-MAIC/OpenMAIC/blob/main/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`](https://github.com/THU-MAIC/OpenMAIC/blob/main/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.