summaryrefslogtreecommitdiff
path: root/internal/image
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-07-15 21:12:18 +0300
committerPaul Buetow <paul@buetow.org>2025-07-15 21:12:18 +0300
commit9c77f2a7bef485fa137f123cbf55b42cacb2b285 (patch)
treed4ee67a4d3de0c17fc86e465f24480b768e80d13 /internal/image
parentb2e1d035ab8d32f6c3351ae5f0a514e569294ef0 (diff)
feat: add OpenAI gpt-4o-mini-tts support with voice instructions
- Add support for OpenAI's new gpt-4o-mini-tts model with customizable voice instructions - Add OpenAIInstruction field to audio configuration for natural language voice control - Update CLI with --openai-instruction flag for runtime voice customization - Enhanced cache key generation to include voice instructions - Update default model to gpt-4o-mini-tts with Bulgarian-optimized instructions - Add support for new voices: ash, ballad, coral, sage, verse - Improve error handling for models requiring special API access - Update documentation with examples and model information - Create .totalrecall.yaml.example with comprehensive configuration options Note: The gpt-4o-mini-tts model requires special API access and may not be available to all accounts yet. πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'internal/image')
-rw-r--r--internal/image/openai.go63
-rw-r--r--internal/image/translate.go9
2 files changed, 58 insertions, 14 deletions
diff --git a/internal/image/openai.go b/internal/image/openai.go
index a5a3e31..839c735 100644
--- a/internal/image/openai.go
+++ b/internal/image/openai.go
@@ -48,10 +48,10 @@ func NewOpenAIClient(config *OpenAIConfig) *OpenAIClient {
// Set defaults
if config.Model == "" {
- config.Model = "dall-e-2"
+ config.Model = "dall-e-3"
}
if config.Size == "" {
- config.Size = "512x512"
+ config.Size = "1024x1024"
}
if config.Quality == "" {
config.Quality = "standard"
@@ -97,6 +97,7 @@ func (c *OpenAIClient) Search(ctx context.Context, opts *SearchOptions) ([]Searc
cacheFile := c.getCacheFilePath(opts.Query)
if info, err := os.Stat(cacheFile); err == nil && info.Size() > 0 {
// Return cached result
+ fmt.Printf("Using cached image for '%s'\n", opts.Query)
result := SearchResult{
ID: c.generateImageID(opts.Query),
URL: cacheFile,
@@ -112,11 +113,20 @@ func (c *OpenAIClient) Search(ctx context.Context, opts *SearchOptions) ([]Searc
}
// Translate Bulgarian word to English for better results
- translatedWord := translateBulgarianToEnglish(opts.Query)
+ translatedWord, err := c.translateBulgarianToEnglish(ctx, opts.Query)
+ if err != nil {
+ // If translation fails, fall back to using the original word
+ fmt.Printf("Translation failed: %v, using original word\n", err)
+ translatedWord = opts.Query
+ }
// Create educational prompt
prompt := c.createEducationalPrompt(opts.Query, translatedWord)
+ // Log the prompt to stdout for debugging
+ fmt.Printf("OpenAI Image Generation Prompt: %s\n", prompt)
+ fmt.Printf("OpenAI Image Generation: Using model '%s' with size '%s'\n", c.model, c.size)
+
// Create the image generation request
req := openai.ImageRequest{
Prompt: prompt,
@@ -220,22 +230,47 @@ func (c *OpenAIClient) Name() string {
// createEducationalPrompt generates a prompt optimized for language learning
func (c *OpenAIClient) createEducationalPrompt(bulgarianWord, englishTranslation string) string {
- // Create a prompt that generates clear, educational images
- // suitable for language learning flashcards
+ // Create a simple, clear prompt for educational images
return fmt.Sprintf(
- "A simple, clear, photorealistic educational image showing %s, "+
- "suitable for language learning flashcards. "+
- "The image should be easily recognizable, with good lighting, "+
- "plain background, and focused on a single clear subject. "+
+ "Generate a simple, clear image of: %s. "+
+ "This is for the Bulgarian word '%s' which means %s. "+
+ "The image should be educational and suitable for language learning flashcards. "+
+ "Requirements: single main subject, plain background, clear and recognizable. "+
"No text, labels, or writing in the image.",
- englishTranslation,
+ englishTranslation, bulgarianWord, englishTranslation,
)
}
-// translateBulgarianToEnglish translates a Bulgarian word to English
-func translateBulgarianToEnglish(word string) string {
- // Use the existing translation function from translate.go
- return translateBulgarianQuery(word)
+// translateBulgarianToEnglish translates a Bulgarian word to English using OpenAI
+func (c *OpenAIClient) translateBulgarianToEnglish(ctx context.Context, word string) (string, error) {
+ // Use OpenAI chat completion to translate
+ fmt.Printf("OpenAI Translation: Using model 'gpt-4o-mini' to translate '%s'\n", word)
+
+ req := openai.ChatCompletionRequest{
+ Model: openai.GPT4oMini,
+ Messages: []openai.ChatCompletionMessage{
+ {
+ Role: openai.ChatMessageRoleUser,
+ Content: fmt.Sprintf("Translate the Bulgarian word '%s' to English. Respond with only the English translation, nothing else.", word),
+ },
+ },
+ Temperature: 0.3, // Lower temperature for more consistent translations
+ MaxTokens: 50,
+ }
+
+ resp, err := c.client.CreateChatCompletion(ctx, req)
+ if err != nil {
+ return "", fmt.Errorf("translation failed: %w", err)
+ }
+
+ if len(resp.Choices) == 0 || resp.Choices[0].Message.Content == "" {
+ return "", fmt.Errorf("no translation received")
+ }
+
+ translation := strings.TrimSpace(resp.Choices[0].Message.Content)
+ fmt.Printf("Translated '%s' to '%s'\n", word, translation)
+
+ return translation, nil
}
// getCacheFilePath generates a cache file path for the given word
diff --git a/internal/image/translate.go b/internal/image/translate.go
index 03d5875..38d16f9 100644
--- a/internal/image/translate.go
+++ b/internal/image/translate.go
@@ -8,6 +8,15 @@ func translateBulgarianQuery(query string) string {
// Common Bulgarian words for flashcard creation
translations := map[string]string{
"ябълка": "apple",
+ "ΠΌΠ°Π»ΠΈΠ½ΠΊΠ°": "raspberry",
+ "ягода": "strawberry",
+ "Ρ‡Π΅Ρ€Π΅ΡˆΠ°": "cherry",
+ "ΠΊΡ€ΡƒΡˆΠ°": "pear",
+ "праскова": "peach",
+ "Π³Ρ€ΠΎΠ·Π΄Π΅": "grapes",
+ "Π±Π°Π½Π°Π½": "banana",
+ "ΠΏΠΎΡ€Ρ‚ΠΎΠΊΠ°Π»": "orange",
+ "Π»ΠΈΠΌΠΎΠ½": "lemon",
"ΠΊΠΎΡ‚ΠΊΠ°": "cat",
"ΠΊΡƒΡ‡Π΅": "dog",
"хляб": "bread",