openmaic-classroom

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

OpenMAIC — Multi-Agent Interactive Classroom

OpenMAIC — 多Agent交互式课堂

Skill by ara.so — Daily 2026 Skills collection.
OpenMAIC (Open Multi-Agent Interactive Classroom) is a Next.js 16 / React 19 / TypeScript platform that converts any topic or document into a full interactive lesson. A multi-agent pipeline (LangGraph 1.1) generates slides, quizzes, HTML simulations, and project-based learning activities delivered by AI teachers and AI classmates with voice (TTS) and whiteboard support.

ara.so开发的Skill — 属于Daily 2026 Skills合集。
OpenMAIC(开源多Agent交互式课堂)是基于Next.js 16 / React 19 / TypeScript构建的平台,可将任意主题或文档转换为完整的交互式课程。其多Agent流水线(LangGraph 1.1)可生成幻灯片、测验、HTML模拟场景和项目式学习活动,由AI教师和AI同学通过语音(TTS)和白板功能进行授课。

Project Stack

技术栈

LayerTechnology
FrameworkNext.js 16 (App Router)
UIReact 19, Tailwind CSS 4
Agent orchestrationLangGraph 1.1
LanguageTypeScript 5
Package managerpnpm >= 10
RuntimeNode.js >= 20

层级技术
框架Next.js 16(App Router)
UIReact 19, Tailwind CSS 4
Agent编排LangGraph 1.1
编程语言TypeScript 5
包管理器pnpm >= 10
运行时Node.js >= 20

Installation

安装步骤

bash
git clone https://github.com/THU-MAIC/OpenMAIC.git
cd OpenMAIC
pnpm install
bash
git clone https://github.com/THU-MAIC/OpenMAIC.git
cd OpenMAIC
pnpm install

Environment Configuration

环境配置

bash
cp .env.example .env.local
Edit
.env.local
— at minimum one LLM provider key is required:
env
undefined
bash
cp .env.example .env.local
编辑
.env.local
文件 — 至少需要配置一个LLM提供商的密钥:
env
undefined

LLM Providers (configure at least one)

LLM Providers (configure at least one)

OPENAI_API_KEY=$OPENAI_API_KEY ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY GOOGLE_API_KEY=$GOOGLE_API_KEY
OPENAI_API_KEY=$OPENAI_API_KEY ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY GOOGLE_API_KEY=$GOOGLE_API_KEY

Recommended default model (Gemini 3 Flash = best speed/quality balance)

Recommended default model (Gemini 3 Flash = best speed/quality balance)

DEFAULT_MODEL=google:gemini-3-flash-preview
DEFAULT_MODEL=google:gemini-3-flash-preview

Optional: MinerU for advanced PDF/table/formula parsing

Optional: MinerU for advanced PDF/table/formula parsing

PDF_MINERU_BASE_URL=https://mineru.net PDF_MINERU_API_KEY=$MINERU_API_KEY
PDF_MINERU_BASE_URL=https://mineru.net PDF_MINERU_API_KEY=$MINERU_API_KEY

Optional: access code for hosted mode

Optional: access code for hosted mode

ACCESS_CODE=$OPENMAIC_ACCESS_CODE
undefined
ACCESS_CODE=$OPENMAIC_ACCESS_CODE
undefined

Provider Config via YAML (alternative to env vars)

基于YAML的提供商配置(环境变量的替代方案)

Create
server-providers.yml
in the project root:
yaml
providers:
  openai:
    apiKey: $OPENAI_API_KEY
  anthropic:
    apiKey: $ANTHROPIC_API_KEY
  google:
    apiKey: $GOOGLE_API_KEY
  deepseek:
    apiKey: $DEEPSEEK_API_KEY
  # Any OpenAI-compatible endpoint
  custom:
    baseURL: https://your-proxy.example.com/v1
    apiKey: $CUSTOM_API_KEY

在项目根目录创建
server-providers.yml
文件:
yaml
providers:
  openai:
    apiKey: $OPENAI_API_KEY
  anthropic:
    apiKey: $ANTHROPIC_API_KEY
  google:
    apiKey: $GOOGLE_API_KEY
  deepseek:
    apiKey: $DEEPSEEK_API_KEY
  # Any OpenAI-compatible endpoint
  custom:
    baseURL: https://your-proxy.example.com/v1
    apiKey: $CUSTOM_API_KEY

Running the App

运行应用

bash
undefined
bash
undefined

Development

开发环境

pnpm dev
pnpm dev

Production build

生产环境构建

pnpm build && pnpm start
pnpm build && pnpm start

Type checking

类型检查

pnpm tsc --noEmit
pnpm tsc --noEmit

Linting

代码检查

pnpm lint

---
pnpm lint

---

Docker Deployment

Docker部署

bash
cp .env.example .env.local
bash
cp .env.example .env.local

Edit .env.local with your API keys

编辑.env.local文件配置你的API密钥

docker compose up --build
docker compose up --build

---

---

Vercel Deployment

Vercel部署

bash
undefined
bash
undefined

Fork the repo, then import at https://vercel.com/new

Fork该仓库,然后在https://vercel.com/new导入

Set env vars in Vercel dashboard:

在Vercel控制台设置环境变量:

OPENAI_API_KEY or ANTHROPIC_API_KEY or GOOGLE_API_KEY

OPENAI_API_KEY 或 ANTHROPIC_API_KEY 或 GOOGLE_API_KEY

DEFAULT_MODEL (optional, e.g. google:gemini-3-flash-preview)

DEFAULT_MODEL(可选,例如google:gemini-3-flash-preview)


One-click deploy button is available in the README; it pre-fills env var descriptions automatically.

---

README中提供了一键部署按钮,可自动预填充环境变量说明。

---

Lesson Generation Pipeline

课程生成流程

OpenMAIC uses a two-stage pipeline:
StageDescription
OutlineAI analyzes topic/document and produces a structured lesson outline
ScenesEach outline item is expanded into a typed scene:
slides
,
quiz
,
interactive
, or
pbl
OpenMAIC采用两阶段流水线:
阶段描述
大纲生成AI分析主题/文档,生成结构化课程大纲
场景生成大纲中的每个条目会被扩展为特定类型的场景:
slides
(幻灯片)、
quiz
(测验)、
interactive
(交互式模拟)或
pbl
(项目式学习)

Scene Types

场景类型

TypeDescription
slides
AI teacher lectures with TTS narration, spotlight, laser pointer
quiz
Single/multiple choice or short-answer with AI grading
interactive
HTML-based simulation (physics, flowcharts, etc.)
pbl
Project-Based Learning — choose a role, collaborate with agents

类型描述
slides
AI教师通过TTS旁白、高亮、激光笔进行授课
quiz
单选题/多选题/简答题,由AI自动评分
interactive
基于HTML的模拟场景(物理实验、流程图等)
pbl
项目式学习 — 选择角色,与Agent协作完成项目

API Usage — Generating a Classroom

API使用 — 生成课堂

REST: Start Generation Job

REST:启动生成任务

typescript
// POST /api/generate
const response = await fetch('/api/generate', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    topic: 'Quantum Entanglement',
    // Optional: attach document content
    document: markdownString,
    // Optional: model override
    model: 'google:gemini-3-flash-preview',
  }),
});

const { jobId } = await response.json();
typescript
// POST /api/generate
const response = await fetch('/api/generate', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    topic: 'Quantum Entanglement',
    // Optional: attach document content
    document: markdownString,
    // Optional: model override
    model: 'google:gemini-3-flash-preview',
  }),
});

const { jobId } = await response.json();

REST: Poll Job Status

REST:轮询任务状态

typescript
// GET /api/generate/status?jobId=<jobId>
const poll = async (jobId: string) => {
  while (true) {
    const res = await fetch(`/api/generate/status?jobId=${jobId}`);
    const data = await res.json();

    if (data.status === 'completed') {
      console.log('Classroom URL:', data.classroomUrl);
      break;
    }
    if (data.status === 'failed') {
      throw new Error(data.error);
    }
    // status === 'pending' | 'running'
    await new Promise(r => setTimeout(r, 3000));
  }
};
typescript
// GET /api/generate/status?jobId=<jobId>
const poll = async (jobId: string) => {
  while (true) {
    const res = await fetch(`/api/generate/status?jobId=${jobId}`);
    const data = await res.json();

    if (data.status === 'completed') {
      console.log('Classroom URL:', data.classroomUrl);
      break;
    }
    if (data.status === 'failed') {
      throw new Error(data.error);
    }
    // status === 'pending' | 'running'
    await new Promise(r => setTimeout(r, 3000));
  }
};

REST: Export Slides

REST:导出幻灯片

typescript
// GET /api/export/pptx?classroomId=<id>
const exportPptx = async (classroomId: string) => {
  const res = await fetch(`/api/export/pptx?classroomId=${classroomId}`);
  const blob = await res.blob();
  const url = URL.createObjectURL(blob);
  // trigger download
  const a = document.createElement('a');
  a.href = url;
  a.download = 'lesson.pptx';
  a.click();
};

// GET /api/export/html?classroomId=<id>
const exportHtml = async (classroomId: string) => {
  const res = await fetch(`/api/export/html?classroomId=${classroomId}`);
  const html = await res.text();
  return html;
};

typescript
// GET /api/export/pptx?classroomId=<id>
const exportPptx = async (classroomId: string) => {
  const res = await fetch(`/api/export/pptx?classroomId=${classroomId}`);
  const blob = await res.blob();
  const url = URL.createObjectURL(blob);
  // trigger download
  const a = document.createElement('a');
  a.href = url;
  a.download = 'lesson.pptx';
  a.click();
};

// GET /api/export/html?classroomId=<id>
const exportHtml = async (classroomId: string) => {
  const res = await fetch(`/api/export/html?classroomId=${classroomId}`);
  const html = await res.text();
  return html;
};

OpenClaw Integration

OpenClaw集成

OpenMAIC ships a skill for OpenClaw, enabling classroom generation from Feishu, Slack, Discord, Telegram, etc.
OpenMAIC附带了OpenClaw的Skill,支持从飞书、Slack、Discord、Telegram等平台生成课堂。

Install the Skill

安装Skill

bash
undefined
bash
undefined

Via ClawHub (recommended)

推荐通过ClawHub安装

clawhub install openmaic
clawhub install openmaic

Manual install

手动安装

mkdir -p ~/.openclaw/skills cp -R /path/to/OpenMAIC/skills/openmaic ~/.openclaw/skills/openmaic
undefined
mkdir -p ~/.openclaw/skills cp -R /path/to/OpenMAIC/skills/openmaic ~/.openclaw/skills/openmaic
undefined

Configure OpenClaw

配置OpenClaw

Edit
~/.openclaw/openclaw.json
:
jsonc
{
  "skills": {
    "entries": {
      "openmaic": {
        "config": {
          // Hosted mode — get access code from https://open.maic.chat/
          "accessCode": "$OPENMAIC_ACCESS_CODE",

          // Self-hosted mode — local repo + server URL
          "repoDir": "/path/to/OpenMAIC",
          "url": "http://localhost:3000"
        }
      }
    }
  }
}
编辑
~/.openclaw/openclaw.json
文件:
jsonc
{
  "skills": {
    "entries": {
      "openmaic": {
        "config": {
          // 托管模式 — 从https://open.maic.chat/获取访问码
          "accessCode": "$OPENMAIC_ACCESS_CODE",

          // 自托管模式 — 本地仓库+服务器地址
          "repoDir": "/path/to/OpenMAIC",
          "url": "http://localhost:3000"
        }
      }
    }
  }
}

OpenClaw Skill Lifecycle

OpenClaw Skill生命周期

PhaseWhat Happens
CloneDetect existing checkout or clone fresh
StartupChoose
pnpm dev
,
pnpm build && pnpm start
, or Docker
Provider KeysGuide user to edit
.env.local
GenerationSubmit async job, poll, return classroom link

阶段操作内容
克隆检测现有仓库或克隆新仓库
启动选择
pnpm dev
pnpm build && pnpm start
或Docker方式
提供商密钥引导用户编辑
.env.local
文件
生成任务提交异步任务,轮询状态,返回课堂链接

Custom Scene Development Pattern

自定义场景开发模式

Scenes are typed React components. To add a new scene type:
typescript
// types/scene.ts
export type SceneType = 'slides' | 'quiz' | 'interactive' | 'pbl' | 'custom';

export interface CustomScene {
  type: 'custom';
  title: string;
  content: string;
  // your fields
  metadata: Record<string, unknown>;
}
typescript
// components/scenes/CustomScene.tsx
'use client';

import { type CustomScene } from '@/types/scene';

interface Props {
  scene: CustomScene;
  onComplete: () => void;
}

export function CustomSceneComponent({ scene, onComplete }: Props) {
  return (
    <div className="flex flex-col gap-4 p-6">
      <h2 className="text-2xl font-bold">{scene.title}</h2>
      <div dangerouslySetInnerHTML={{ __html: scene.content }} />
      <button
        className="mt-4 rounded-lg bg-blue-600 px-6 py-2 text-white"
        onClick={onComplete}
      >
        Continue
      </button>
    </div>
  );
}

场景是带类型的React组件。要添加新的场景类型:
typescript
// types/scene.ts
export type SceneType = 'slides' | 'quiz' | 'interactive' | 'pbl' | 'custom';

export interface CustomScene {
  type: 'custom';
  title: string;
  content: string;
  // your fields
  metadata: Record<string, unknown>;
}
typescript
// components/scenes/CustomScene.tsx
'use client';

import { type CustomScene } from '@/types/scene';

interface Props {
  scene: CustomScene;
  onComplete: () => void;
}

export function CustomSceneComponent({ scene, onComplete }: Props) {
  return (
    <div className="flex flex-col gap-4 p-6">
      <h2 className="text-2xl font-bold">{scene.title}</h2>
      <div dangerouslySetInnerHTML={{ __html: scene.content }} />
      <button
        className="mt-4 rounded-lg bg-blue-600 px-6 py-2 text-white"
        onClick={onComplete}
      >
        Continue
      </button>
    </div>
  );
}

Multi-Agent Interaction Modes

多Agent交互模式

ModeTriggerDescription
Classroom DiscussionAutomaticAgents proactively start discussions; user can jump in or get called on
Roundtable DebateScene configMultiple agent personas debate a topic with whiteboard illustrations
Q&AUser asks questionAI teacher responds with slides, diagrams, or whiteboard drawings
WhiteboardDuring any sceneAgents draw equations, flowcharts, or concept diagrams in real time

模式触发方式描述
课堂讨论自动触发Agent主动发起讨论,用户可加入或被邀请参与
圆桌辩论场景配置触发多个不同角色的Agent围绕主题辩论,同时在白板上绘图说明
问答环节用户提问触发AI教师通过幻灯片、图表或白板绘图进行解答
白板协作任意场景中触发Agent实时绘制公式、流程图或概念图

MinerU Advanced Document Parsing

MinerU高级文档解析

For complex PDFs with tables, formulas, or scanned images:
env
undefined
对于包含表格、公式或扫描件的复杂PDF:
env
undefined

Use MinerU hosted API

使用MinerU托管API

PDF_MINERU_BASE_URL=https://mineru.net PDF_MINERU_API_KEY=$MINERU_API_KEY
PDF_MINERU_BASE_URL=https://mineru.net PDF_MINERU_API_KEY=$MINERU_API_KEY

Or self-hosted MinerU instance (Docker)

或自托管MinerU实例(Docker)

PDF_MINERU_BASE_URL=http://localhost:8888

Without MinerU, OpenMAIC falls back to standard PDF text extraction.

---
PDF_MINERU_BASE_URL=http://localhost:8888

如果未启用MinerU,OpenMAIC会回退到标准PDF文本提取功能。

---

Supported LLM Providers & Model Strings

支持的LLM提供商及模型字符串

typescript
// Model string format: "provider:model-name"
const models = {
  // Google (recommended)
  geminiFlash: 'google:gemini-3-flash-preview',   // best speed/quality
  geminiPro: 'google:gemini-3.1-pro',             // highest quality

  // OpenAI
  gpt4o: 'openai:gpt-4o',
  gpt4oMini: 'openai:gpt-4o-mini',

  // Anthropic
  claude4Sonnet: 'anthropic:claude-sonnet-4-5',
  claude4Haiku: 'anthropic:claude-haiku-4-5',

  // DeepSeek
  deepseekChat: 'deepseek:deepseek-chat',

  // OpenAI-compatible (custom base URL)
  custom: 'custom:your-model-name',
};

typescript
// Model string format: "provider:model-name"
const models = {
  // Google (recommended)
  geminiFlash: 'google:gemini-3-flash-preview',   // best speed/quality
  geminiPro: 'google:gemini-3.1-pro',             // highest quality

  // OpenAI
  gpt4o: 'openai:gpt-4o',
  gpt4oMini: 'openai:gpt-4o-mini',

  // Anthropic
  claude4Sonnet: 'anthropic:claude-sonnet-4-5',
  claude4Haiku: 'anthropic:claude-haiku-4-5',

  // DeepSeek
  deepseekChat: 'deepseek:deepseek-chat',

  // OpenAI-compatible (custom base URL)
  custom: 'custom:your-model-name',
};

Export Formats

导出格式

FormatEndpointNotes
PowerPoint
.pptx
GET /api/export/pptx?classroomId=
Fully editable slides
Interactive
.html
GET /api/export/html?classroomId=
Self-contained HTML page

格式接口说明
PowerPoint
.pptx
GET /api/export/pptx?classroomId=
可完全编辑的幻灯片
交互式
.html
GET /api/export/html?classroomId=
独立HTML页面

Common Patterns

常见使用模式

Generate a Classroom from a Document String

从文档字符串生成课堂

typescript
const generateFromDocument = async (markdownContent: string, topic: string) => {
  const res = await fetch('/api/generate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      topic,
      document: markdownContent,
      model: process.env.DEFAULT_MODEL ?? 'google:gemini-3-flash-preview',
    }),
  });

  const { jobId } = await res.json();

  // Poll until done
  let classroomUrl: string | null = null;
  while (!classroomUrl) {
    await new Promise(r => setTimeout(r, 4000));
    const status = await fetch(`/api/generate/status?jobId=${jobId}`).then(r => r.json());
    if (status.status === 'completed') classroomUrl = status.classroomUrl;
    if (status.status === 'failed') throw new Error(status.error);
  }

  return classroomUrl;
};
typescript
const generateFromDocument = async (markdownContent: string, topic: string) => {
  const res = await fetch('/api/generate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      topic,
      document: markdownContent,
      model: process.env.DEFAULT_MODEL ?? 'google:gemini-3-flash-preview',
    }),
  });

  const { jobId } = await res.json();

  // 轮询直到完成
  let classroomUrl: string | null = null;
  while (!classroomUrl) {
    await new Promise(r => setTimeout(r, 4000));
    const status = await fetch(`/api/generate/status?jobId=${jobId}`).then(r => r.json());
    if (status.status === 'completed') classroomUrl = status.classroomUrl;
    if (status.status === 'failed') throw new Error(status.error);
  }

  return classroomUrl;
};

Check Provider Health

检查提供商健康状态

typescript
// GET /api/providers
const checkProviders = async () => {
  const res = await fetch('/api/providers');
  const { providers } = await res.json();
  // providers: Array<{ name: string; available: boolean; models: string[] }>
  return providers.filter((p: { available: boolean }) => p.available);
};

typescript
// GET /api/providers
const checkProviders = async () => {
  const res = await fetch('/api/providers');
  const { providers } = await res.json();
  // providers: Array<{ name: string; available: boolean; models: string[] }>
  return providers.filter((p: { available: boolean }) => p.available);
};

Troubleshooting

故障排查

ProblemSolution
No LLM provider configured
Set at least one of
OPENAI_API_KEY
,
ANTHROPIC_API_KEY
, or
GOOGLE_API_KEY
in
.env.local
Generation hangs at outline stageCheck API key quota; try switching to
google:gemini-3-flash-preview
for higher rate limits
TTS not workingTTS requires a browser with Web Speech API support; check browser console for errors
PDF parsing produces garbled textEnable MinerU by setting
PDF_MINERU_BASE_URL
in
.env.local
Vercel timeout during generationIncrease function timeout in
vercel.json
; generation is async so the API should return a
jobId
immediately
Docker build failsEnsure
DOCKER_BUILDKIT=1
and that
.env.local
exists before running
docker compose up --build
OpenClaw skill not foundRun
clawhub install openmaic
or manually copy
skills/openmaic
to
~/.openclaw/skills/
pnpm install
fails on Node < 20
Upgrade Node.js to >= 20 (
nvm use 20
)
Port 3000 already in useSet
PORT=3001
in
.env.local
or run
PORT=3001 pnpm dev

问题解决方案
No LLM provider configured
.env.local
中至少设置
OPENAI_API_KEY
ANTHROPIC_API_KEY
GOOGLE_API_KEY
中的一个
大纲生成阶段卡住检查API密钥配额;尝试切换到
google:gemini-3-flash-preview
以获得更高的调用限制
TTS无法工作TTS需要浏览器支持Web Speech API;检查浏览器控制台的错误信息
PDF解析结果乱码通过在
.env.local
中设置
PDF_MINERU_BASE_URL
启用MinerU
Vercel部署时生成任务超时
vercel.json
中增加函数超时时间;生成是异步操作,API应立即返回
jobId
Docker构建失败确保
DOCKER_BUILDKIT=1
,且在运行
docker compose up --build
前已创建
.env.local
文件
OpenClaw Skill找不到运行
clawhub install openmaic
或手动将
skills/openmaic
复制到
~/.openclaw/skills/
目录
Node < 20时
pnpm install
失败
将Node.js升级到>=20版本(
nvm use 20
端口3000已被占用
.env.local
中设置
PORT=3001
或运行
PORT=3001 pnpm dev

Key File Structure

核心文件结构

OpenMAIC/
├── app/                    # Next.js App Router pages & API routes
│   ├── api/
│   │   ├── generate/       # POST lesson generation, GET status
│   │   ├── export/         # pptx / html export endpoints
│   │   └── providers/      # LLM provider health check
│   └── classroom/          # Classroom viewer pages
├── components/
│   ├── scenes/             # Slide, Quiz, Interactive, PBL components
│   ├── whiteboard/         # Real-time whiteboard rendering
│   └── agents/             # Agent avatar & TTS components
├── lib/
│   ├── agents/             # LangGraph agent graph definitions
│   ├── providers/          # LLM provider abstractions
│   └── generation/         # Outline + scene generation pipeline
├── skills/
│   └── openmaic/           # OpenClaw skill definition
├── server-providers.yml    # Optional YAML provider config
├── .env.example            # Environment variable template
└── docker-compose.yml      # Docker deployment config
OpenMAIC/
├── app/                    # Next.js App Router页面及API路由
│   ├── api/
│   │   ├── generate/       # 课程生成POST接口、状态查询GET接口
│   │   ├── export/         # pptx/html导出接口
│   │   └── providers/      # LLM提供商健康检查接口
│   └── classroom/          # 课堂查看页面
├── components/
│   ├── scenes/             # 幻灯片、测验、交互式模拟、项目式学习组件
│   ├── whiteboard/         # 实时白板渲染组件
│   └── agents/             # Agent头像及TTS组件
├── lib/
│   ├── agents/             # LangGraph Agent图定义
│   ├── providers/          # LLM提供商抽象层
│   └── generation/         # 大纲+场景生成流水线
├── skills/
│   └── openmaic/           # OpenClaw Skill定义
├── server-providers.yml    # 可选的YAML提供商配置文件
├── .env.example            # 环境变量模板
└── docker-compose.yml      # Docker部署配置