From 7e7d34bfc0aec2aa8dff4cefa720e4a9b42d95d8 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Wed, 8 Apr 2026 09:50:33 +0300 Subject: refactor(gui): extract AudioConfigResolver, VoiceSelector, ParallelRunner Split orchestrator audio resolution, voice selection, and parallel material generation into dedicated types in separate files. GenerationOrchestrator wires AudioConfigResolver and VoiceSelector from NewGenerationOrchestrator and delegates GenerateMaterials to ParallelRunner; public behaviour unchanged. Made-with: Cursor --- internal/gui/parallel_runner.go | 130 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 internal/gui/parallel_runner.go (limited to 'internal/gui/parallel_runner.go') diff --git a/internal/gui/parallel_runner.go b/internal/gui/parallel_runner.go new file mode 100644 index 0000000..2567304 --- /dev/null +++ b/internal/gui/parallel_runner.go @@ -0,0 +1,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) + } +} -- cgit v1.2.3