> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inworld.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Generate Images with MiniMax

This template creates AI-generated 4-panel comics using the Inworld Agent Runtime and MiniMax Image Generation API.

Key concepts demonstrated:

* Custom nodes extending the `CustomNode` class for specialized processing
* LLM-powered story generation
* External API integration (MiniMax) for image generation
* Parallel processing for efficiency
* Error handling and retry logic

<Card title="Watch the Demo">
  <iframe style={{ aspectRatio: '16 / 9', width: '100%', height: 'auto' }} src="https://www.youtube.com/embed/QJufvjcC85c" title="Inworld Agent Runtime Demo - Generating Images with MiniMax" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen />
</Card>

<Note>
  **Architecture**

  * **Backend:** Inworld Agent Runtime + Express.js + Minimax API
  * **Frontend:** Static HTML + JavaScript
  * **Communication:** HTTP
</Note>

## Prerequisites

* Node.js v20 or higher: [Download here](https://nodejs.org/en/download)
* Inworld API key (required): [Sign up here](https://platform.inworld.ai/signup) or see [quickstart guide](/node/authentication#getting-an-api-key)
* MiniMax API key (required for image generation): [Get your API key](https://minmax.io)

## Run the Template

1. Clone the comic generator [GitHub repo](https://github.com/inworld-ai/comic-generator-node).

2. Set up your API keys by creating a `.env` file in the project's root directory with your required API keys:
   ```env .env theme={"system"}
   # Required, Inworld Agent Runtime Base64 API key
   INWORLD_API_KEY=<your_inworld_api_key_here>

   # Required, MiniMax API key for image generation
   MINIMAX_API_KEY=<your_minimax_api_key_here>
   ```
   * Set `INWORLD_API_KEY` to your Base64 [Runtime API key](/node/authentication#runtime-api-key).
   * Set `MINIMAX_API_KEY` to your [Minimax API key](https://www.minimax.io/platform/user-center/basic-information/interface-key) for image generation.

3. Install dependencies and start the server:

   <CodeGroup>
     ```shell Yarn theme={"system"}
     yarn && yarn start
     ```

     ```shell npm theme={"system"}
     npm install && npm start
     ```
   </CodeGroup>

   The server will start on port 3003 (or the port specified in your PORT environment variable).

4. Open your browser and navigate to `http://localhost:3003` to access the comic generation interface.

5. Create a comic by filling in the form:
   * **Character 1 Description**: e.g., "A brave knight with shiny armor"
   * **Character 2 Description**: e.g., "A wise old wizard with a long beard"
   * **Art Style**: e.g., "anime manga style" or "cartoon style"
   * **Theme** (optional): e.g., "medieval adventure"

6. Click "Generate Comic" and wait for the processing to complete. You can check the status or view recent comics through the interface.
   <img src="https://mintcdn.com/inworldai/au0dXjR_jWXAG2ts/img/nodejs/runtime/image-generation.png?fit=max&auto=format&n=au0dXjR_jWXAG2ts&q=85&s=4bfe0906b9c77dd95a5983369985d82c" alt="" width="1201" height="1189" data-path="img/nodejs/runtime/image-generation.png" />

7. Check out your captured traces in [Portal](https://platform.inworld.ai/)!

## Understanding the Template

The core functionality of the comic generator is contained in `comic_server.ts`, which orchestrates a graph-based processing pipeline using custom nodes and built-in Inworld Agent Runtime components.

The template creates a processing pipeline that transforms user input through multiple AI processing stages to generate complete 4-panel comics.

### 1) Custom Node Implementation

The template defines custom nodes by extending the `CustomNode` class for specialized comic generation operations:

```typescript comic_story_node.ts theme={"system"}
import { CustomNode, ProcessContext } from '@inworld/runtime/graph';
import { GraphTypes } from '@inworld/runtime/common';

// Custom node for generating story prompts
export class ComicStoryGeneratorNode extends CustomNode {
  process(context: ProcessContext, input: ComicStoryInput): GraphTypes.LLMChatRequest {
    const prompt = `You are a comic book writer. Create a 4-panel comic story with the following characters and specifications:
    CHARACTER 1: ${input.character1Description}
    CHARACTER 2: ${input.character2Description}
    ART STYLE: ${input.artStyle}
    ${input.theme ? `THEME/SETTING: ${input.theme}` : ''}
    Create exactly 4 panels for a short comic strip...`;
    
    return new GraphTypes.LLMChatRequest({
      messages: [{ role: 'user', content: prompt }]
    });
  }
}

// Custom node for parsing LLM responses
class ComicResponseParserNode extends CustomNode {
  process(context: ProcessContext, input: GraphTypes.Content) {
    return parseComicStoryResponse(input.content);
  }
}

// Custom node for image generation
export class ComicImageGeneratorNode extends CustomNode {
  async process(context: ProcessContext, input: ComicStoryOutput): Promise<ComicImageOutput> {
    // Generate images for all 4 panels using MiniMax API
    // ... image generation logic
  }
}
```

### 2) Node Creation

After defining custom node classes, the graph creates instances of both custom and built-in nodes:

```typescript comic_server.ts theme={"system"}
// Create custom node instances
const storyGeneratorNode = new ComicStoryGeneratorNode();
const responseParserNode = new ComicResponseParserNode();
const imageGeneratorNode = new ComicImageGeneratorNode();

// Create built-in LLM node
const llmChatNode = new RemoteLLMChatNode({
  provider: 'openai',
  modelName: 'gpt-5-mini',
  stream: false,
});
```

### 3) Core Graph Construction

With all nodes created, the comic generation pipeline is assembled using `GraphBuilder` by connecting each processing stage:

```typescript comic_server.ts theme={"system"}
const graphBuilder = new GraphBuilder({ 
  id: 'comic_generator',
  apiKey: process.env.INWORLD_API_KEY!
});

// Add all nodes to the graph
graphBuilder
  .addNode(storyGeneratorNode)
  .addNode(llmChatNode)
  .addNode(responseParserNode)
  .addNode(imageGeneratorNode);

// Connect the nodes to create the processing pipeline
graphBuilder
  .addEdge(storyGeneratorNode, llmChatNode)
  .addEdge(llmChatNode, responseParserNode)
  .addEdge(responseParserNode, imageGeneratorNode)
  .setStartNode(storyGeneratorNode)
  .setEndNode(imageGeneratorNode);

const comicGeneratorGraph = graphBuilder.build();
```

This creates the complete flow: **User Input → Story Generation → LLM Processing → Response Parsing → Image Generation → Final Comic**

### 4) Graph Execution

Once the graph is built, the server uses it to process comic generation requests asynchronously. When a user submits a comic request through the REST API, the following execution flow begins:

```typescript comic_server.ts theme={"system"}
async function generateComic(request: ComicRequest) {
  const input: ComicStoryInput = {
    character1Description: request.character1Description,
    character2Description: request.character2Description,
    artStyle: request.artStyle,
    theme: request.theme,
  };

  // Start graph execution
  const executionId = uuidv4();
  const outputStream = await comicGeneratorGraph!.start(input, executionId);
  
  // Process the output stream (see Response Handling)
  await processGraphOutput(outputStream, request);
  
  // Clean up resources
  comicGeneratorGraph!.closeExecution(outputStream);
}
```

### 5) Response Handling

As the graph processes through each stage, the application monitors the output stream and manages the comic generation lifecycle. This enables real-time status tracking and provides users with immediate feedback:

```typescript comic_server.ts theme={"system"}
async function processGraphOutput(outputStream: any, request: ComicRequest) {
  // Iterate through graph execution results
  for await (const result of outputStream) {
    // Extract the final comic with generated images
    request.result = result.data as ComicImageOutput;
    request.status = 'completed';
    console.log(`✅ Comic generation completed for request ${request.id}`);
    break;
  }
}

// REST API endpoint for client polling
app.get('/api/comic-status/:requestId', (req, res) => {
  const request = requests[req.params.requestId];
  
  if (!request) {
    return res.status(404).json({ error: 'Request not found' });
  }

  // Return current status and results if available
  const response = {
    requestId: request.id,
    status: request.status,
    ...(request.status === 'completed' && { result: request.result })
  };

  return res.json(response);
});
```

This demonstrates the power of the Inworld Agent Runtime's graph architecture - you can create sophisticated AI processing pipelines by combining custom nodes with built-in components, handling complex workflows with proper resource management and error handling.
