From 106aec46a887ec5c97eb5a92f4079522a43d9ab0 Mon Sep 17 00:00:00 2001 From: James Pine Date: Thu, 16 Apr 2026 03:09:55 -0700 Subject: [PATCH] feat(generate): restore instruct toggle in floating generate box (Qwen CustomVoice) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before 0.4 every engine was a cloning model, so the instruct UI in the floating generate box applied the same way everywhere. Commit 3187344 hid the instruct toggle because the mix of new engines landing in 0.4 made it unclear which ones honored the kwarg. With Qwen CustomVoice now shipping as the only engine actually tuned for instruct-style control, bring the button back — conditionally, and only for that engine. Changes: • FloatingGenerateBox: SlidersHorizontal toggle button appears left of Generate when the box is expanded AND engine is qwen_custom_voice. Clicking it reveals an additive instruct textarea below the main text field (not a modal swap like the old version). State persists across engine switches so the toggle remembers its last position. • GenerationForm: narrow the instruct FormField's conditional from `qwen || qwen_custom_voice` to just `qwen_custom_voice`. • useGenerationForm: narrow supportsInstruct for the same reason. Base Qwen3-TTS accepts the kwarg but the model itself doesn't honor it — only CustomVoice was trained for instruction-based style control. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Generation/FloatingGenerateBox.tsx | 83 ++++++++++++++++++- .../components/Generation/GenerationForm.tsx | 10 ++- app/src/lib/hooks/useGenerationForm.ts | 4 +- 3 files changed, 91 insertions(+), 6 deletions(-) diff --git a/app/src/components/Generation/FloatingGenerateBox.tsx b/app/src/components/Generation/FloatingGenerateBox.tsx index 956c6547..d977cc1a 100644 --- a/app/src/components/Generation/FloatingGenerateBox.tsx +++ b/app/src/components/Generation/FloatingGenerateBox.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@tanstack/react-query'; import { useMatchRoute } from '@tanstack/react-router'; import { AnimatePresence, motion } from 'framer-motion'; -import { Loader2, Sparkles } from 'lucide-react'; +import { Loader2, SlidersHorizontal, Sparkles } from 'lucide-react'; import { useEffect, useRef, useState } from 'react'; import { Button } from '@/components/ui/button'; import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form'; @@ -40,6 +40,7 @@ export function FloatingGenerateBox({ const { data: selectedProfile } = useProfile(selectedProfileId || ''); const { data: profiles } = useProfiles(); const [isExpanded, setIsExpanded] = useState(false); + const [isInstructExpanded, setIsInstructExpanded] = useState(false); const [selectedPresetId, setSelectedPresetId] = useState(null); const containerRef = useRef(null); const textareaRef = useRef(null); @@ -125,7 +126,14 @@ export function FloatingGenerateBox({ }, [watchedEngine, setSelectedEngine]); // Sync generation form language, engine, and effects with selected profile - type EngineValue = 'qwen' | 'luxtts' | 'chatterbox' | 'chatterbox_turbo' | 'tada' | 'kokoro' | 'qwen_custom_voice'; + type EngineValue = + | 'qwen' + | 'luxtts' + | 'chatterbox' + | 'chatterbox_turbo' + | 'tada' + | 'kokoro' + | 'qwen_custom_voice'; useEffect(() => { if (selectedProfile?.language) { form.setValue('language', selectedProfile.language as LanguageCode); @@ -346,9 +354,80 @@ export function FloatingGenerateBox({ : 'Generate speech'} + + {/* Instruct toggle — only for Qwen CustomVoice, which actually honors the kwarg */} + + {isExpanded && form.watch('engine') === 'qwen_custom_voice' && ( + +
+ + + Delivery instructions (tone, emotion, pace) + +
+
+ )} +
+ {/* Additive instruct textarea — shown below main text when toggle is on and engine supports it */} + + {isInstructExpanded && form.watch('engine') === 'qwen_custom_voice' && ( + + ( + + +