-
Notifications
You must be signed in to change notification settings - Fork 28
voice agents refactoring #207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
7a00c1d
c5d0a04
2acffea
98b0597
b460cf2
a27956a
04c9ba7
95e7a11
1e6741f
8d35f5a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,7 +56,9 @@ const createVoiceAgentSchema = z.object({ | |
| tts_config_id: z.string().min(1, 'TTS configuration is required'), | ||
| stt_config_id: z.string().min(1, 'STT configuration is required'), | ||
| telephony_config_id: z.string().min(1, 'Telephony configuration is required'), | ||
| tts_voice_id: z.string().min(1, 'TTS Voice ID is required'), | ||
| tts_voice_ids: z.record(z.string(), z.string()).refine((val) => Object.keys(val).length > 0, { | ||
| message: 'At least one voice ID is required', | ||
| }), | ||
| system_prompt: z.string().min(1, 'System prompt is required'), | ||
| welcome_message: z.string().min(1, 'Welcome message is required'), | ||
| conversation_config: z.string().optional(), | ||
|
|
@@ -83,6 +85,7 @@ const CreateVoiceAgentDialog: React.FC<CreateVoiceAgentDialogProps> = ({ isOpen, | |
| const [creating, setCreating] = useState(false); | ||
| const [ttsParameters, setTtsParameters] = useState<Record<string, unknown>>({}); | ||
| const [sttParameters, setSttParameters] = useState<Record<string, unknown>>({}); | ||
| const [voiceIdState, setVoiceIdState] = useState<Record<string, string>>({ en: '' }); | ||
|
|
||
| // Fetch configs for dropdowns | ||
| const { data: llmConfigs = [] } = useGetLLMConfigs(appId); | ||
|
|
@@ -99,7 +102,7 @@ const CreateVoiceAgentDialog: React.FC<CreateVoiceAgentDialogProps> = ({ isOpen, | |
| tts_config_id: '', | ||
| stt_config_id: '', | ||
| telephony_config_id: '', | ||
| tts_voice_id: '', | ||
| tts_voice_ids: { en: '' }, | ||
| system_prompt: '', | ||
| welcome_message: '', | ||
| conversation_config: '{}', | ||
|
|
@@ -114,6 +117,7 @@ const CreateVoiceAgentDialog: React.FC<CreateVoiceAgentDialogProps> = ({ isOpen, | |
| // Watch config selections to determine providers | ||
| const watchedTtsConfigId = form.watch('tts_config_id'); | ||
| const watchedSttConfigId = form.watch('stt_config_id'); | ||
| const watchedSupportedLanguages = form.watch('supported_languages'); | ||
|
|
||
| // Get selected providers | ||
| const selectedTtsProvider = ttsConfigs.find((c) => c.id === watchedTtsConfigId)?.provider; | ||
|
|
@@ -132,6 +136,20 @@ const CreateVoiceAgentDialog: React.FC<CreateVoiceAgentDialogProps> = ({ isOpen, | |
| } | ||
| }, [selectedSttProvider, isOpen]); | ||
|
|
||
| // Sync voice ID state with language changes | ||
| useEffect(() => { | ||
| if (isOpen && watchedSupportedLanguages) { | ||
| setVoiceIdState((prev) => { | ||
| const newState: Record<string, string> = {}; | ||
| // Preserve existing voice IDs for languages still selected | ||
| watchedSupportedLanguages.forEach((lang) => { | ||
| newState[lang] = prev[lang] || ''; | ||
| }); | ||
| return newState; | ||
| }); | ||
| } | ||
| }, [watchedSupportedLanguages, isOpen]); | ||
|
Comment on lines
+139
to
+151
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Form field not updated when languages are removed. The Proposed fix to sync form state with voiceIdState useEffect(() => {
if (isOpen && watchedSupportedLanguages) {
setVoiceIdState((prev) => {
const newState: Record<string, string> = {};
// Preserve existing voice IDs for languages still selected
watchedSupportedLanguages.forEach((lang) => {
newState[lang] = prev[lang] || '';
});
+ // Sync form state to remove voice IDs for deselected languages
+ form.setValue('tts_voice_ids', newState);
return newState;
});
}
- }, [watchedSupportedLanguages, isOpen]);
+ }, [watchedSupportedLanguages, isOpen, form]);🤖 Prompt for AI Agents |
||
|
|
||
| // Reset form when dialog closes | ||
| useEffect(() => { | ||
| if (!isOpen) { | ||
|
|
@@ -142,7 +160,7 @@ const CreateVoiceAgentDialog: React.FC<CreateVoiceAgentDialogProps> = ({ isOpen, | |
| tts_config_id: '', | ||
| stt_config_id: '', | ||
| telephony_config_id: '', | ||
| tts_voice_id: '', | ||
| tts_voice_ids: { en: '' }, | ||
| system_prompt: '', | ||
| welcome_message: '', | ||
| conversation_config: '{}', | ||
|
|
@@ -154,6 +172,7 @@ const CreateVoiceAgentDialog: React.FC<CreateVoiceAgentDialogProps> = ({ isOpen, | |
| }); | ||
| setTtsParameters({}); | ||
| setSttParameters({}); | ||
| setVoiceIdState({ en: '' }); | ||
| } | ||
| }, [isOpen, form]); | ||
|
|
||
|
|
@@ -232,7 +251,7 @@ const CreateVoiceAgentDialog: React.FC<CreateVoiceAgentDialogProps> = ({ isOpen, | |
| tts_config_id: data.tts_config_id.trim(), | ||
| stt_config_id: data.stt_config_id.trim(), | ||
| telephony_config_id: data.telephony_config_id.trim(), | ||
| tts_voice_id: data.tts_voice_id.trim(), | ||
| tts_voice_ids: data.tts_voice_ids, | ||
| tts_parameters: Object.keys(builtTtsParameters).length > 0 ? builtTtsParameters : null, | ||
| stt_parameters: Object.keys(builtSttParameters).length > 0 ? builtSttParameters : null, | ||
| system_prompt: data.system_prompt.trim(), | ||
|
|
@@ -710,17 +729,31 @@ const CreateVoiceAgentDialog: React.FC<CreateVoiceAgentDialogProps> = ({ isOpen, | |
| <h4 className="text-sm font-medium">TTS Voice Settings</h4> | ||
| <FormField | ||
| control={form.control} | ||
| name="tts_voice_id" | ||
| name="tts_voice_ids" | ||
| render={({ field }) => ( | ||
| <FormItem> | ||
| <FormLabel> | ||
| TTS Voice ID<span className="text-red-500">*</span> | ||
| TTS Voice IDs<span className="text-red-500">*</span> | ||
| </FormLabel> | ||
| <FormControl> | ||
| <Input placeholder="e.g., alloy, echo, fable (OpenAI) or voice ID (ElevenLabs)" {...field} /> | ||
| </FormControl> | ||
| <div className="space-y-3"> | ||
| {watchedSupportedLanguages.map((langCode) => ( | ||
| <div key={langCode} className="flex items-center gap-3"> | ||
| <Label className="w-24 text-sm font-medium">{getLanguageDisplayName(langCode)}:</Label> | ||
| <Input | ||
| placeholder={`Voice ID for ${getLanguageDisplayName(langCode)}`} | ||
| value={voiceIdState[langCode] || ''} | ||
| onChange={(e) => { | ||
| const newState = { ...voiceIdState, [langCode]: e.target.value }; | ||
| setVoiceIdState(newState); | ||
| field.onChange(newState); | ||
| }} | ||
| className="flex-1" | ||
| /> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| <FormDescription> | ||
| Provider-specific voice identifier (e.g., for Deepgram: aura-2-helena-en) | ||
| Provider-specific voice identifiers per language (e.g., "aura-2-helena-en" for Deepgram) | ||
| </FormDescription> | ||
| <FormMessage /> | ||
| </FormItem> | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -51,7 +51,7 @@ const updateVoiceAgentSchema = z.object({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tts_config_id: z.string().min(1, 'TTS configuration is required'), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stt_config_id: z.string().min(1, 'STT configuration is required'), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| telephony_config_id: z.string().min(1, 'Telephony configuration is required'), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tts_voice_id: z.string().min(1, 'TTS Voice ID is required'), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tts_voice_ids: z.record(z.string(), z.string()).optional(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| system_prompt: z.string().min(1, 'System prompt is required'), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| welcome_message: z.string().min(1, 'Welcome message is required'), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| conversation_config: z.string().optional(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -92,6 +92,7 @@ const EditVoiceAgentDialog: React.FC<EditVoiceAgentDialogProps> = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // State for TTS/STT parameters (managed separately from form) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [ttsParameters, setTtsParameters] = useState<Record<string, unknown>>({}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [sttParameters, setSttParameters] = useState<Record<string, unknown>>({}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [voiceIdState, setVoiceIdState] = useState<Record<string, string>>(agent.tts_voice_ids || { en: '' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const form = useForm<UpdateVoiceAgentInput>({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resolver: zodResolver(updateVoiceAgentSchema), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -102,7 +103,7 @@ const EditVoiceAgentDialog: React.FC<EditVoiceAgentDialogProps> = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tts_config_id: agent.tts_config_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stt_config_id: agent.stt_config_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| telephony_config_id: agent.telephony_config_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tts_voice_id: agent.tts_voice_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tts_voice_ids: agent.tts_voice_ids, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| system_prompt: agent.system_prompt, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| welcome_message: agent.welcome_message, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| conversation_config: agent.conversation_config ? JSON.stringify(agent.conversation_config, null, 2) : '{}', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -117,6 +118,7 @@ const EditVoiceAgentDialog: React.FC<EditVoiceAgentDialogProps> = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Watch for config changes to determine providers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const watchedTtsConfigId = form.watch('tts_config_id'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const watchedSttConfigId = form.watch('stt_config_id'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const watchedSupportedLanguages = form.watch('supported_languages'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const selectedTtsProvider = ttsConfigs.find((c) => c.id === watchedTtsConfigId)?.provider; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const selectedSttProvider = sttConfigs.find((c) => c.id === watchedSttConfigId)?.provider; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -134,7 +136,7 @@ const EditVoiceAgentDialog: React.FC<EditVoiceAgentDialogProps> = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tts_config_id: agent.tts_config_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stt_config_id: agent.stt_config_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| telephony_config_id: agent.telephony_config_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tts_voice_id: agent.tts_voice_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tts_voice_ids: agent.tts_voice_ids, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| system_prompt: agent.system_prompt, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| welcome_message: agent.welcome_message, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| conversation_config: agent.conversation_config ? JSON.stringify(agent.conversation_config, null, 2) : '{}', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -144,6 +146,7 @@ const EditVoiceAgentDialog: React.FC<EditVoiceAgentDialogProps> = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| supported_languages: agent.supported_languages || ['en'], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| default_language: agent.default_language || 'en', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setVoiceIdState(agent.tts_voice_ids || { en: '' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [isOpen, agent, form]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -161,6 +164,20 @@ const EditVoiceAgentDialog: React.FC<EditVoiceAgentDialogProps> = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [isOpen, agent.stt_parameters]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Sync voice ID state with language changes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isOpen && watchedSupportedLanguages) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setVoiceIdState((prev) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const newState: Record<string, string> = {}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Preserve existing voice IDs for languages still selected | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| watchedSupportedLanguages.forEach((lang) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| newState[lang] = prev[lang] || ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return newState; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [watchedSupportedLanguages, isOpen]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+167
to
+179
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -name "EditVoiceAgentDialog.tsx" -type fRepository: rootflo/wavefront Length of output: 140 🏁 Script executed: cat -n ./wavefront/client/src/pages/apps/[appId]/voice-agents/EditVoiceAgentDialog.tsxRepository: rootflo/wavefront Length of output: 50373 🏁 Script executed: sed -n '167,300p' ./wavefront/client/src/pages/apps/[appId]/voice-agents/EditVoiceAgentDialog.tsxRepository: rootflo/wavefront Length of output: 5033 🏁 Script executed: sed -n '775,806p' ./wavefront/client/src/pages/apps/[appId]/voice-agents/EditVoiceAgentDialog.tsxRepository: rootflo/wavefront Length of output: 1739 🏁 Script executed: sed -n '286,288p' ./wavefront/client/src/pages/apps/[appId]/voice-agents/EditVoiceAgentDialog.tsxRepository: rootflo/wavefront Length of output: 212 🏁 Script executed: sed -n '54,54p' ./wavefront/client/src/pages/apps/[appId]/voice-agents/EditVoiceAgentDialog.tsxRepository: rootflo/wavefront Length of output: 122 🏁 Script executed: sed -n '95,180p' ./wavefront/client/src/pages/apps/[appId]/voice-agents/EditVoiceAgentDialog.tsxRepository: rootflo/wavefront Length of output: 3672 🏁 Script executed: rg "tts_voice_ids" ./wavefront/client/src/pages/apps/[appId]/voice-agents/EditVoiceAgentDialog.tsxRepository: rootflo/wavefront Length of output: 562 Sync form field when supported languages change. The effect updates 🛠️ Suggested fix useEffect(() => {
if (isOpen && watchedSupportedLanguages) {
setVoiceIdState((prev) => {
const newState: Record<string, string> = {};
// Preserve existing voice IDs for languages still selected
watchedSupportedLanguages.forEach((lang) => {
newState[lang] = prev[lang] || '';
});
+ form.setValue('tts_voice_ids', newState, { shouldDirty: true });
return newState;
});
}
- }, [watchedSupportedLanguages, isOpen]);
+ }, [watchedSupportedLanguages, isOpen, form]);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Helper functions to update parameters | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const setTtsParameter = (key: string, value: unknown) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTtsParameters((prev) => ({ ...prev, [key]: value })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -266,8 +283,8 @@ const EditVoiceAgentDialog: React.FC<EditVoiceAgentDialogProps> = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requestData.telephony_config_id = data.telephony_config_id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (data.tts_voice_id.trim() !== agent.tts_voice_id) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requestData.tts_voice_id = data.tts_voice_id.trim(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (JSON.stringify(data.tts_voice_ids) !== JSON.stringify(agent.tts_voice_ids)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requestData.tts_voice_ids = data.tts_voice_ids; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if TTS parameters changed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -757,17 +774,31 @@ const EditVoiceAgentDialog: React.FC<EditVoiceAgentDialogProps> = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <h4 className="text-sm font-medium">TTS Voice Settings</h4> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormField | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| control={form.control} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name="tts_voice_id" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name="tts_voice_ids" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| render={({ field }) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormItem> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormLabel> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TTS Voice ID<span className="text-red-500">*</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TTS Voice IDs<span className="text-red-500">*</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </FormLabel> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormControl> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Input placeholder="e.g., alloy, echo, fable (OpenAI) or voice ID (ElevenLabs)" {...field} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </FormControl> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="space-y-3"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {watchedSupportedLanguages.map((langCode) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div key={langCode} className="flex items-center gap-3"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Label className="w-24 text-sm font-medium">{getLanguageDisplayName(langCode)}:</Label> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Input | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| placeholder={`Voice ID for ${getLanguageDisplayName(langCode)}`} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value={voiceIdState[langCode] || ''} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={(e) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const newState = { ...voiceIdState, [langCode]: e.target.value }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setVoiceIdState(newState); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| field.onChange(newState); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="flex-1" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormDescription> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Provider-specific voice identifier (e.g., for Deepgram: aura-2-helena-en) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Provider-specific voice identifiers per language (e.g., "aura-2-helena-en" for Deepgram) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </FormDescription> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormMessage /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </FormItem> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -16,8 +16,6 @@ | |||||||||
| from pipecat.runner.types import WebSocketRunnerArguments | ||||||||||
| from pipecat.runner.utils import parse_telephony_websocket | ||||||||||
|
|
||||||||||
| # from pipecat.audio.turn.smart_turn.local_smart_turn_v3 import LocalSmartTurnAnalyzerV3 | ||||||||||
| # from pipecat.audio.turn.smart_turn.base_smart_turn import SmartTurnParams | ||||||||||
| from pipecat.serializers.twilio import TwilioFrameSerializer | ||||||||||
| from pipecat.audio.vad.silero import SileroVADAnalyzer | ||||||||||
| from pipecat.audio.vad.vad_analyzer import VADParams | ||||||||||
|
|
@@ -119,6 +117,8 @@ async def inbound_webhook( | |||||||||
|
|
||||||||||
| # Pass parameters to WebSocket stream | ||||||||||
| stream.parameter(name='voice_agent_id', value=agent_id) | ||||||||||
| stream.parameter(name='customer_number', value=From) | ||||||||||
| stream.parameter(name='agent_number', value=To) | ||||||||||
|
|
||||||||||
| connect.append(stream) | ||||||||||
| response.append(connect) | ||||||||||
|
|
@@ -134,6 +134,8 @@ async def inbound_webhook( | |||||||||
|
|
||||||||||
| @webhook_router.post('/twiml') | ||||||||||
| async def twiml_endpoint( | ||||||||||
| From: str = Form(...), | ||||||||||
| To: str = Form(...), | ||||||||||
| voice_agent_id: str = Query(...), | ||||||||||
| welcome_message_audio_url: str = Query(default=''), | ||||||||||
| ): | ||||||||||
|
|
@@ -181,6 +183,8 @@ async def twiml_endpoint( | |||||||||
|
|
||||||||||
| # Pass parameters to WebSocket stream | ||||||||||
| stream.parameter(name='voice_agent_id', value=voice_agent_id) | ||||||||||
| stream.parameter(name='customer_number', value=To) | ||||||||||
| stream.parameter(name='agent_number', value=From) | ||||||||||
|
Comment on lines
+186
to
+187
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: In the
In Twilio webhooks, 🐛 Fix the swapped parameters # Pass parameters to WebSocket stream
stream.parameter(name='voice_agent_id', value=voice_agent_id)
- stream.parameter(name='customer_number', value=To)
- stream.parameter(name='agent_number', value=From)
+ stream.parameter(name='customer_number', value=From)
+ stream.parameter(name='agent_number', value=To)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| connect.append(stream) | ||||||||||
| response.append(connect) | ||||||||||
|
|
@@ -223,6 +227,8 @@ async def websocket_endpoint( | |||||||||
| # Extract parameters from stream | ||||||||||
| body_data = call_data.get('body', {}) | ||||||||||
| voice_agent_id = body_data.get('voice_agent_id') | ||||||||||
| customer_number = body_data.get('customer_number') | ||||||||||
| # agent_number = body_data.get('agent_number') | ||||||||||
|
Comment on lines
+230
to
+231
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing validation for
🛠️ Suggested fix voice_agent_id = body_data.get('voice_agent_id')
customer_number = body_data.get('customer_number')
# agent_number = body_data.get('agent_number')
if not voice_agent_id:
logger.error('voice_agent_id not found in stream parameters')
await websocket.close(code=1008, reason='Missing voice_agent_id')
return
+ if not customer_number:
+ logger.warning('customer_number not found in stream parameters, using empty string')
+ customer_number = ''
+
logger.info(f'Voice agent ID: {voice_agent_id}')Also applies to: 293-293 🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| if not voice_agent_id: | ||||||||||
| logger.error('voice_agent_id not found in stream parameters') | ||||||||||
|
|
@@ -263,13 +269,12 @@ async def websocket_endpoint( | |||||||||
| vad_analyzer=SileroVADAnalyzer( | ||||||||||
| params=VADParams( | ||||||||||
| confidence=0.7, # Default is 0.7, can lower to 0.4-0.5 for faster detection | ||||||||||
| start_secs=0.15, # Default is 0.2, keep it | ||||||||||
| stop_secs=0.8, # KEY: Lower from default 0.8 for faster cutoff (should be 0.2 for smart turn detection) | ||||||||||
| start_secs=0.2, # Default is 0.2, keep it | ||||||||||
| stop_secs=0.2, # KEY: Lower from default 0.8 for faster cutoff (should be 0.2 for smart turn detection) | ||||||||||
| min_volume=0.6, # Default is 0.6, adjust based on your audio quality | ||||||||||
| ), | ||||||||||
| ), # Voice Activity Detection | ||||||||||
| serializer=serializer, | ||||||||||
| # turn_analyzer=LocalSmartTurnAnalyzerV3(params=SmartTurnParams()), | ||||||||||
| ), | ||||||||||
| ) | ||||||||||
|
|
||||||||||
|
|
@@ -282,6 +287,7 @@ async def websocket_endpoint( | |||||||||
| tts_config=configs['tts_config'], | ||||||||||
| stt_config=configs['stt_config'], | ||||||||||
| tools=configs['tools'], | ||||||||||
| customer_number=customer_number, | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| except Exception as e: | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Zod schema doesn't validate that voice IDs are non-empty strings.
The current schema only checks that at least one key exists in the record, but allows empty string values like
{ en: '' }. This could lead to agents being created with empty voice IDs, which would fail at runtime.Proposed fix to validate non-empty voice IDs
📝 Committable suggestion
🤖 Prompt for AI Agents