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
|
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
}
// 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)
}
}
|