summaryrefslogtreecommitdiff
path: root/internal/comic/generator.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/comic/generator.go')
-rw-r--r--internal/comic/generator.go141
1 files changed, 141 insertions, 0 deletions
diff --git a/internal/comic/generator.go b/internal/comic/generator.go
new file mode 100644
index 0000000..34c60c5
--- /dev/null
+++ b/internal/comic/generator.go
@@ -0,0 +1,141 @@
+package comic
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "strings"
+
+ "codeberg.org/snonux/comicforge/internal/provider"
+)
+
+// GeneratorConfig configures story generation.
+type GeneratorConfig struct {
+ TextProvider provider.TextProvider
+ Prompts PromptRenderer
+ Language string
+ Script string
+ Theme string
+ Genres []string
+}
+
+// Generator produces the comic story text, bible, title, and panel script.
+type Generator struct {
+ textProvider provider.TextProvider
+ prompts PromptRenderer
+ language string
+ script string
+ theme string
+ genres []string
+ initErr error
+}
+
+var _ = (*Generator)(nil)
+
+// NewGenerator creates a new story generator.
+func NewGenerator(cfg *GeneratorConfig) *Generator {
+ g := &Generator{
+ language: "Bulgarian",
+ script: "Cyrillic",
+ genres: defaultStoryGenres,
+ }
+ if cfg == nil {
+ g.initErr = errors.New("generator config is required")
+ return g
+ }
+ g.textProvider = cfg.TextProvider
+ g.prompts = cfg.Prompts
+ g.language = orDefault(cfg.Language, g.language)
+ g.script = orDefault(cfg.Script, g.script)
+ g.theme = cfg.Theme
+ if len(cfg.Genres) > 0 {
+ g.genres = append([]string(nil), cfg.Genres...)
+ }
+ if g.textProvider == nil {
+ g.initErr = fmt.Errorf("%w: text provider", ErrMissingProvider)
+ }
+ if g.prompts == nil {
+ g.initErr = errors.Join(g.initErr, fmt.Errorf("%w: prompt renderer", ErrMissingProvider))
+ }
+ return g
+}
+
+// Generate produces only the story text.
+func (g *Generator) Generate(ctx context.Context, entries []WordEntry) (string, error) {
+ if err := g.ready(); err != nil {
+ return "", err
+ }
+ prompt, err := g.renderStoryPrompt(storyPromptTemplate, entries)
+ if err != nil {
+ return "", err
+ }
+ ctx, cancel := withTimeout(ctx, storyTimeout)
+ defer cancel()
+ text, err := g.textProvider.GenerateText(ctx, prompt)
+ if err != nil {
+ return "", fmt.Errorf("generate story: %w", err)
+ }
+ text = strings.TrimSpace(text)
+ if text == "" {
+ return "", fmt.Errorf("no story content returned")
+ }
+ return text, nil
+}
+
+// GenerateFull produces the combined story, bible, title, and panel script.
+func (g *Generator) GenerateFull(ctx context.Context, entries []WordEntry) (GenerateResult, error) {
+ if err := g.ready(); err != nil {
+ return GenerateResult{}, err
+ }
+ prompt, err := g.renderStoryPrompt(storyFullPromptTemplate, entries)
+ if err != nil {
+ return GenerateResult{}, err
+ }
+ ctx, cancel := withTimeout(ctx, storyTimeout)
+ defer cancel()
+ text, err := g.textProvider.GenerateText(ctx, prompt)
+ if err != nil {
+ return GenerateResult{}, fmt.Errorf("generate story bundle: %w", err)
+ }
+ text = strings.TrimSpace(text)
+ if text == "" {
+ return GenerateResult{}, fmt.Errorf("no content returned")
+ }
+ return parseGenerateResult(text), nil
+}
+
+func (g *Generator) ready() error {
+ if g == nil {
+ return errors.New("generator is nil")
+ }
+ if g.initErr != nil {
+ return g.initErr
+ }
+ return nil
+}
+
+func (g *Generator) renderStoryPrompt(templateName string, entries []WordEntry) (string, error) {
+ genre := resolveGenre(g.theme, g.genres)
+ data := map[string]any{
+ "Language": g.language,
+ "Script": g.script,
+ "Genre": genre,
+ "Words": buildWordList(entries, ""),
+ "StoryBibleSeparator": storyBibleSeparator,
+ "StoryTitleSeparator": storyTitleSeparator,
+ "StoryPanelSeparator": storyPanelSeparator,
+ }
+
+ systemPrompt, err := g.prompts.RenderPrompt(storySystemPromptTemplate, map[string]any{
+ "Language": g.language,
+ "Script": g.script,
+ })
+ if err != nil {
+ return "", err
+ }
+ userPrompt, err := g.prompts.RenderPrompt(templateName, data)
+ if err != nil {
+ return "", err
+ }
+ return systemPrompt + "\n\n" + userPrompt, nil
+}