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
|
package gui
import (
"context"
"fmt"
"os"
"path/filepath"
)
// GenerateResult holds the outcome of a parallel generation run.
type GenerateResult struct {
AudioFile string
AudioFileBack string
ImageFile string
PhoneticInfo string
}
// audioGenResult is an internal channel payload for audio goroutines.
type audioGenResult struct {
file string
fileBack string
err error
}
// imageGenResult is an internal channel payload for image goroutines.
type imageGenResult struct {
file string
err error
}
// phoneticGenResult is an internal channel payload for phonetic goroutines.
type phoneticGenResult struct {
info string
err error
}
// ParallelRunner coordinates parallel audio, image, and phonetics work for
// material generation. It is stateless; the orchestrator supplies all behaviour
// via the first method argument.
type ParallelRunner struct{}
// GenerateMaterials generates audio, image, and phonetics in parallel for a
// word. translation is the existing translation (may be empty). isBgBg flags
// bg-bg card type. imagePrompt is an optional custom prompt; imageTranslation
// is the translation hint for image prompts.
// The promptUI callback is called on the generating goroutine when the image
// prompt becomes known so callers can update the UI.
// Returns a GenerateResult or an error if any mandatory step fails.
func (ParallelRunner) GenerateMaterials(
o *GenerationOrchestrator,
ctx context.Context,
word, translation, cardDir string,
isBgBg bool,
imagePrompt string,
promptUI func(prompt string),
) (GenerateResult, error) {
audioChan := make(chan audioGenResult, 1)
imageChan := make(chan imageGenResult, 1)
phoneticChan := make(chan phoneticGenResult, 1)
// 1. Audio generation
go func() {
var audioFile, audioFileBack string
var err error
if isBgBg && translation != "" {
audioFile, audioFileBack, err = o.GenerateAudioBgBg(ctx, word, translation, cardDir)
} else {
audioFile, err = o.GenerateAudio(ctx, word, cardDir)
}
audioChan <- audioGenResult{file: audioFile, fileBack: audioFileBack, err: err}
}()
// 2. Image generation (includes scene description from the AI)
go func() {
imageFile, err := o.generateImagesWithPromptAndNotify(ctx, word, imagePrompt, translation, cardDir, promptUI)
imageChan <- imageGenResult{file: imageFile, err: err}
}()
// 3. Phonetic information fetching
go func() {
phoneticInfo, err := o.GetPhoneticInfo(word)
if err != nil {
fmt.Printf("Warning: Failed to get phonetic info: %v\n", err)
phoneticInfo = "Failed to fetch phonetic information"
} else {
fmt.Printf("Successfully fetched phonetic info for '%s': %s\n", word, phoneticInfo)
}
savePhoneticIfValid(phoneticInfo, cardDir, word)
phoneticChan <- phoneticGenResult{info: phoneticInfo}
}()
// Collect results.
audioRes := <-audioChan
if audioRes.err != nil {
// Drain remaining channels to avoid goroutine leaks.
<-imageChan
<-phoneticChan
return GenerateResult{}, fmt.Errorf("audio generation failed: %w", audioRes.err)
}
imageRes := <-imageChan
if imageRes.err != nil {
<-phoneticChan
return GenerateResult{}, fmt.Errorf("image download failed: %w", imageRes.err)
}
phoneticRes := <-phoneticChan
return GenerateResult{
AudioFile: audioRes.file,
AudioFileBack: audioRes.fileBack,
ImageFile: imageRes.file,
PhoneticInfo: phoneticRes.info,
}, nil
}
// savePhoneticIfValid saves phonetic info to disk when the info is valid.
func savePhoneticIfValid(phoneticInfo, cardDir, word string) {
if phoneticInfo == "" || phoneticInfo == "Failed to fetch phonetic information" {
return
}
phoneticFile := filepath.Join(cardDir, "phonetic.txt")
if err := os.WriteFile(phoneticFile, []byte(phoneticInfo), 0644); err != nil {
fmt.Printf("Warning: Failed to save phonetic info for '%s': %v\n", word, err)
}
}
|