summaryrefslogtreecommitdiff
path: root/docs/ARCHITECTURE.md
blob: 41e34dce412c0c491742ff16b04b3f4e80039724 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
# 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<br/>Minimal bootstrap"]
        CLI_GO["cmd/comicforge/cli.go<br/>Cobra flags & wiring"]
    end

    subgraph Internal["Internal Packages"]
        CFG["internal/config<br/>Configuration loading"]
        PRV["internal/provider<br/>Provider interfaces"]
        COM["internal/comic<br/>Generation pipeline"]
        IMG["internal/image<br/>Image providers"]
        TXT["internal/text<br/>Text providers"]
        TTS["internal/tts<br/>TTS providers"]
        VOC["internal/vocab<br/>Vocabulary parsing"]
        HTP["internal/httpctx<br/>HTTP/GenAI clients"]
    end

    subgraph External["External Services"]
        GEMINI["Google Gemini API<br/>(Text, Image, TTS)"]
        FS["Local Filesystem<br/>(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 {
        <<interface>>
        +Name() string
        +IsAvailable() error
        +GenerateText(ctx, prompt) (string, error)
    }

    class ImageProvider {
        <<interface>>
        +Name() string
        +IsAvailable() error
        +GenerateImage(ctx, prompt, outputFile) error
    }

    class AspectRatioImageProvider {
        <<interface>>
        +GenerateImageWithAspectRatio(ctx, prompt, outputFile, aspectRatio) error
    }

    class TTSProvider {
        <<interface>>
        +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<br/>--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<br/>--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<br/>Language, Style, Bible] --> RENDER[config.RenderPrompt]
    TPL[Template File<br/>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