From d97741966a6c97b5db7df62873bd2ee9d99f0565 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sun, 19 Apr 2026 23:13:06 +0300 Subject: y4: wire ComicForge CLI providers and config --- internal/text/gemini.go | 110 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 internal/text/gemini.go (limited to 'internal/text') diff --git a/internal/text/gemini.go b/internal/text/gemini.go new file mode 100644 index 0000000..94a8c16 --- /dev/null +++ b/internal/text/gemini.go @@ -0,0 +1,110 @@ +package text + +import ( + "context" + "fmt" + "strings" + + "google.golang.org/genai" + + "codeberg.org/snonux/comicforge/internal/httpctx" + "codeberg.org/snonux/comicforge/internal/provider" +) + +const ( + // DefaultModel is the Gemini text model used for story generation. + DefaultModel = "gemini-2.5-flash" +) + +// GeminiConfig holds the settings needed to build a Gemini-backed text provider. +type GeminiConfig struct { + APIKey string + Model string +} + +// GeminiProvider implements TextProvider for Google Gemini text generation. +type GeminiProvider struct { + client *genai.Client + model string + err error +} + +var _ provider.TextProvider = (*GeminiProvider)(nil) + +var newGeminiClient = httpctx.NewGenAIClient +var geminiGenerateText = func(ctx context.Context, p *GeminiProvider, prompt string) (string, error) { + return p.generateText(ctx, prompt) +} + +// NewGeminiProvider creates a Gemini text provider. +func NewGeminiProvider(cfg *GeminiConfig) *GeminiProvider { + g := &GeminiProvider{model: DefaultModel} + if cfg == nil { + g.err = fmt.Errorf("text config is required") + return g + } + g.model = defaultOr(cfg.Model, DefaultModel) + if strings.TrimSpace(cfg.APIKey) == "" { + g.err = fmt.Errorf("Google API key is required for text generation") + return g + } + client, err := newGeminiClient(context.Background(), &genai.ClientConfig{ + APIKey: cfg.APIKey, + Backend: genai.BackendGeminiAPI, + }) + if err != nil { + g.err = fmt.Errorf("create Gemini client: %w", err) + return g + } + g.client = client + return g +} + +// Name returns the provider name. +func (g *GeminiProvider) Name() string { return provider.Gemini } + +// IsAvailable reports whether the provider was initialized successfully. +func (g *GeminiProvider) IsAvailable() error { + if g == nil { + return fmt.Errorf("text provider is nil") + } + return g.err +} + +// GenerateText generates a text response for the provided prompt. +func (g *GeminiProvider) GenerateText(ctx context.Context, prompt string) (string, error) { + if g == nil { + return "", fmt.Errorf("text provider is nil") + } + if ctx == nil { + ctx = context.Background() + } + if g.err != nil { + return "", g.err + } + if strings.TrimSpace(prompt) == "" { + return "", fmt.Errorf("prompt is required") + } + return geminiGenerateText(ctx, g, prompt) +} + +func (g *GeminiProvider) generateText(ctx context.Context, prompt string) (string, error) { + resp, err := g.client.Models.GenerateContent(ctx, g.model, []*genai.Content{ + genai.NewContentFromText(prompt, genai.RoleUser), + }, nil) + if err != nil { + return "", fmt.Errorf("generate text: %w", err) + } + text := strings.TrimSpace(resp.Text()) + if text == "" { + return "", fmt.Errorf("no text content returned") + } + return text, nil +} + +func defaultOr(value, fallback string) string { + if strings.TrimSpace(value) != "" { + return value + } + return fallback +} -- cgit v1.2.3