# ComicForge Architecture
This document describes the architecture of **ComicForge**, a Go CLI application that generates AI-powered comic books from vocabulary word lists or direct prompts.
## Table of Contents
1. [High-Level Overview](#high-level-overview)
2. [System Architecture](#system-architecture)
3. [Package Structure](#package-structure)
4. [Data Flow](#data-flow)
5. [Provider Architecture](#provider-architecture)
6. [Configuration System](#configuration-system)
7. [Comic Generation Pipeline](#comic-generation-pipeline)
8. [Key Design Patterns](#key-design-patterns)
---
## High-Level Overview
ComicForge is a command-line tool that orchestrates multiple AI providers to produce complete comic books. Given a vocabulary file containing words in a target language, it:
1. Generates a story incorporating those words
2. Creates a character bible for visual consistency
3. Renders comic pages (cover, story pages, gallery, back cover)
4. Assembles a PDF
5. Optionally generates cinematic audio narration
---
## System Architecture
```mermaid
graph TB
subgraph CLI["Command Layer"]
CMD["cmd/comicforge/main.go
Minimal bootstrap"]
CLI_GO["cmd/comicforge/cli.go
Cobra flags & wiring"]
end
subgraph Internal["Internal Packages"]
CFG["internal/config
Configuration loading"]
PRV["internal/provider
Provider interfaces"]
COM["internal/comic
Generation pipeline"]
IMG["internal/image
Image providers"]
TXT["internal/text
Text providers"]
TTS["internal/tts
TTS providers"]
VOC["internal/vocab
Vocabulary parsing"]
HTP["internal/httpctx
HTTP/GenAI clients"]
end
subgraph External["External Services"]
GEMINI["Google Gemini API
(Text, Image, TTS)"]
FS["Local Filesystem
(Output, PDFs, Audio)"]
end
CMD --> CLI_GO
CLI_GO --> CFG
CLI_GO --> COM
CLI_GO --> PRV
PRV --> TXT
PRV --> IMG
PRV --> TTS
COM --> TXT
COM --> IMG
COM --> TTS
COM --> VOC
TXT --> HTP
IMG --> HTP
TTS --> HTP
HTP --> GEMINI
COM --> FS
```
---
## Package Structure
```mermaid
graph LR
subgraph Entry["Entry Point"]
M[main.go]
C[cli.go]
end
subgraph Config["Configuration"]
CFG[config.go]
HOME[home.go]
end
subgraph Providers["Provider Layer"]
P[provider.go]
TR[text/registry.go]
IR[image/registry.go]
TTR[tts/registry.go]
TG[text/gemini.go]
IG[image/gemini.go]
TTG[tts/gemini.go]
end
subgraph Pipeline["Comic Pipeline"]
RUN[runner.go]
GEN[generator.go]
ART[artist.go]
NAR[narrator.go]
TYP[types.go]
end
subgraph Support["Supporting Packages"]
VOC[vocab/reader.go]
HTP[httpctx/httpctx.go]
CIR[apicircuit/apicircuit.go]
end
M --> C
C --> CFG
C --> RUN
C --> TR
C --> IR
C --> TTR
RUN --> GEN
RUN --> ART
RUN --> NAR
RUN --> VOC
GEN --> TG
ART --> IG
ART --> TG
NAR --> TTG
NAR --> TG
TR --> P
IR --> P
TTR --> P
TG --> HTP
IG --> HTP
TTG --> HTP
```
---
## Data Flow
### Vocabulary Mode (Full Comic)
```mermaid
sequenceDiagram
participant User
participant CLI as cli.go
participant Run as Runner
participant Gen as Generator
participant Art as Artist
participant Nar as Narrator
participant TP as TextProvider
participant IP as ImageProvider
participant TTS as TTSProvider
participant FS as Filesystem
User->>CLI: comicforge --vocab words.txt
CLI->>Run: NewRunner(cfg)
Run->>Gen: NewGenerator()
Run->>Art: NewArtist()
Run->>Nar: NewNarrator()
Run->>Gen: GenerateFull(entries)
Gen->>TP: GenerateText(storyPrompt)
TP-->>Gen: story + bible + title + panelScript
Gen-->>Run: GenerateResult
Run->>Art: DrawComicPages(story, bible, title, entries, panelScript)
loop For each page
Art->>Art: Render prompt template
Art->>IP: GenerateImageWithReferences(prompt, refs)
IP-->>Art: image.png
Art->>FS: Write page file
end
Art-->>Run: []pagePaths
Run->>FS: Save story.txt
Run->>FS: Save vocabulary.txt
Run->>FS: Save theme.txt
Run->>FS: Assemble PDF
opt Narration enabled
Run->>Nar: Narrate(storyText)
Nar->>TP: GenerateText(intro prompt)
Nar->>TTS: GenerateAudio(chunks)
Nar->>FS: Save narration.mp3
end
Run-->>CLI: success
CLI-->>User: Done
```
### Prompt Mode (Single Image)
```mermaid
sequenceDiagram
participant User
participant CLI as cli.go
participant Run as Runner
participant Art as Artist
participant TP as TextProvider
participant IP as ImageProvider
participant FS as Filesystem
User->>CLI: comicforge --prompt "dragon in space"
CLI->>Run: NewRunner(cfg)
Run->>Art: NewArtist()
Run->>Run: Generate title slug via TP
Run->>Art: generatePromptImage(prompt)
Art->>Art: Render manual prompt template
Art->>IP: GenerateImage(renderedPrompt)
IP-->>Art: image.png
Art->>FS: Write prompt.png
Run-->>CLI: success
CLI-->>User: Done
```
---
## Provider Architecture
ComicForge uses a **registry-based provider system** that abstracts over different AI backends. Currently, only **Google Gemini** is fully implemented; OpenAI is stubbed for future extension.
```mermaid
classDiagram
class TextProvider {
<>
+Name() string
+IsAvailable() error
+GenerateText(ctx, prompt) (string, error)
}
class ImageProvider {
<>
+Name() string
+IsAvailable() error
+GenerateImage(ctx, prompt, outputFile) error
}
class AspectRatioImageProvider {
<>
+GenerateImageWithAspectRatio(ctx, prompt, outputFile, aspectRatio) error
}
class TTSProvider {
<>
+Name() string
+IsAvailable() error
+GenerateAudio(ctx, text, outputFile) error
}
class GeminiTextProvider {
+client *genai.Client
+model string
+GenerateText(ctx, prompt) (string, error)
}
class GeminiImageProvider {
+client *genai.Client
+config *GeminiConfig
+GenerateImage(ctx, prompt, outputFile) error
+GenerateImageWithReferences(ctx, prompt, outputFile, refs) error
}
class GeminiTTSProvider {
+client *genai.Client
+model string
+voice string
+GenerateAudio(ctx, text, outputFile) error
}
class Registry~T, C~ {
+Register(name, factory)
+Resolve(name) Factory
+New(name, cfg) T
}
TextProvider <|.. GeminiTextProvider
ImageProvider <|.. GeminiImageProvider
ImageProvider <|-- AspectRatioImageProvider
AspectRatioImageProvider <|.. GeminiImageProvider
TTSProvider <|.. GeminiTTSProvider
Registry --> TextProvider
Registry --> ImageProvider
Registry --> TTSProvider
```
### Provider Registry Flow
```mermaid
graph LR
A[CLI Flags
--text-provider gemini] --> B[Config.Normalize]
B --> C[Registry.Resolve"gemini"]
C --> D[Factory Function]
D --> E[GeminiProvider]
E --> F[IsAvailable Check]
F --> G[Inject into Runner]
```
---
## Configuration System
Configuration is loaded via **Viper** with a three-layer priority:
1. **Defaults** (code-defined)
2. **Config file** (`config.yaml`)
3. **Environment variables** (`COMICFORGE_*`)
4. **CLI flags** (highest priority)
```mermaid
graph TD
A[DefaultConfig] --> B[Viper SetDefaults]
C[config.yaml] --> D[Viper ReadInConfig]
E[COMICFORGE_API_GOOGLE_API_KEY] --> F[Viper AutomaticEnv]
G[CLI Flags
--text-model, --style] --> H[Apply Overrides]
B --> I[Loaded Config]
D --> I
F --> I
H --> I
I --> J[Validate Providers]
J --> K[Return *Config]
```
The `Config` struct implements multiple small interfaces so each package only depends on what it needs:
```mermaid
graph TD
CFG[Config] --> TC[TextConfig]
CFG --> IC[ImageConfig]
CFG --> TTC[TTSConfig]
CFG --> TXTC[text.Config]
CFG --> IMGC[image.Config]
CFG --> TTSC[tts.Config]
TC --> TXT[internal/text]
IC --> IMG[internal/image]
TTC --> TTS[internal/tts]
```
---
## Comic Generation Pipeline
The pipeline is divided into three specialized stages:
```mermaid
graph LR
subgraph Stage1["Stage 1: Story Generation"]
direction TB
VOC[Vocabulary Words] --> GEN
GEN[Generator] --> STORY[Story Text]
GEN --> BIBLE[Character Bible]
GEN --> TITLE[Comic Title]
GEN --> PANEL[Panel Script]
end
subgraph Stage2["Stage 2: Visual Art"]
direction TB
STORY --> ART[Artist]
BIBLE --> ART
PANEL --> ART
ART --> COVER[Cover Page]
ART --> PAGES[Story Pages]
ART --> GALLERY[Gallery Pages]
ART --> BACK[Back Cover]
end
subgraph Stage3["Stage 3: Assembly"]
direction TB
COVER --> PDF[PDF Assembly]
PAGES --> PDF
GALLERY --> PDF
BACK --> PDF
STORY --> NAR[Narrator]
NAR --> AUDIO[MP3 Narration]
end
```
### Reference Image Chaining
To maintain visual consistency across comic pages, the Artist passes previous page images as **reference images** to the image provider:
```mermaid
graph LR
P1[Page 1] -->|ref| P2[Page 2]
P2 -->|ref| P3[Page 3]
P3 -->|ref| P4[...]
style P1 fill:#e1f5fe
style P2 fill:#e1f5fe
style P3 fill:#e1f5fe
```
The `appendRef` function keeps only the **first and most recent** reference images to avoid exceeding API limits.
---
## Key Design Patterns
### Dependency Injection
The CLI uses a `commandDeps` struct to inject provider factories, making the entire command layer testable without real API calls:
```go
type commandDeps struct {
loadConfig func(string) (*config.Config, error)
newTextProvider func(*config.Config) (provider.TextProvider, error)
newImageProvider func(*config.Config) (provider.ImageProvider, error)
newTTSProvider func(*config.Config, string) (provider.TTSProvider, error)
newRunner func(*comic.RunnerConfig) comic.StoryRunner
}
```
### Template-Driven Prompts
All LLM prompts are **Go text/template files** rendered with structured data. Templates live in `prompts/` and can be overridden at runtime via `--prompts-dir`:
```mermaid
graph LR
DATA[Template Data
Language, Style, Bible] --> RENDER[config.RenderPrompt]
TPL[Template File
story_full_prompt.md] --> RENDER
RENDER --> PROMPT[Final Prompt]
PROMPT --> LLM[Gemini API]
```
### Circuit Breaker
The `internal/apicircuit` package provides circuit breaker protection for API calls to prevent cascading failures when AI providers are unavailable.
### Defensive Defaults
Every component uses the **functional options / config struct** pattern with sensible defaults:
```mermaid
graph TD
A[User Config] --> B{Field Set?}
B -->|Yes| C[Use User Value]
B -->|No| D[Use Default Value]
C --> E[Initialized Component]
D --> E
```
---
## File Output Structure
When running with `--output .` and a comic titled "Space Adventure":
```
comics/
├── assets/
│ └── space-adventure/
│ ├── space-adventure_cover.png
│ ├── space-adventure_page_1.png
│ ├── space-adventure_page_2.png
│ ├── space-adventure_gallery_1.png
│ ├── space-adventure_back.png
│ ├── space-adventure_story.txt
│ ├── space-adventure_comic_vocabulary.txt
│ ├── space-adventure_theme.txt
│ └── space-adventure_narration.mp3
├── PDF/
│ └── space-adventure.pdf
└── gallery/
├── space-adventure_gallery_1.png
└── ...
```
---
## Technology Stack
| Component | Technology |
|-----------|------------|
| Language | Go 1.23+ |
| CLI Framework | Cobra |
| Configuration | Viper |
| AI Provider SDK | `google.golang.org/genai` |
| PDF Generation | Custom assembly via `internal/comic/pdf.go` |
| Audio Processing | `ffmpeg` (external dependency) |
| Testing | Go standard testing + test tables |
| Module Path | `codeberg.org/snonux/comicforge` |
---
## Future Extension Points
- **New AI Providers**: Implement `TextProvider`/`ImageProvider`/`TTSProvider` and register in the respective registries
- **New Style Modes**: Add to `StyleConfig` and update `pickStyle()` in `types.go`
- **Custom Output Formats**: The `assemblePDF` field in `Runner` is injectable for alternative exporters
- **Additional Languages**: Extend `localization.go` with new script/language mappings