-
Notifications
You must be signed in to change notification settings - Fork 3.2k
feat(audio): add custom OpenAI-compatible TTS/ASR provider support (#357) #409
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
Merged
Merged
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
687840d
feat(audio): extend TTSProviderId/ASRProviderId to support custom pro…
wyuc 2626785
feat(audio): update provider registry helpers for custom provider sup…
wyuc 3c0b4e7
feat(audio): route custom providers to OpenAI-compatible implementations
wyuc 46a0cf4
feat(audio): add custom TTS/ASR provider CRUD to settings store
wyuc 91e94b4
feat(audio): add dialog for creating custom audio providers
wyuc 99caa0e
feat(audio): add custom provider UI with voice management and delete
wyuc 70db8ff
feat(i18n): add custom audio provider translation strings
wyuc aab12f8
fix(audio): resolve remaining type errors for custom provider support
wyuc fd53ac2
fix(audio): allow custom provider IDs to reach switch default branch
wyuc 35f4145
feat(audio): add default model field to dialog and polish voice list UI
wyuc 9ef827c
fix(audio): fallback to customDefaultBaseUrl when testing custom TTS …
wyuc e81525c
fix(audio): auto-select first voice when adding to custom TTS provider
wyuc 93b1759
fix(audio): fallback to customDefaultBaseUrl in all TTS call sites
wyuc a770303
fix(audio): include custom provider voices in agent voice picker
wyuc 9f87c2b
fix(audio): fallback to customDefaultBaseUrl in all ASR call sites
wyuc 6edb356
feat(audio): add model list management for custom ASR providers
wyuc a3f3b67
fix(audio): seed customModels from defaultModel and fix ASR model label
wyuc 7224c3e
fix(audio): separate ASR model management from creation dialog
wyuc db8739b
fix(audio): simplify ASR model management — first model is default
wyuc 773e81a
fix(audio): add loading state and detailed errors to ASR test
wyuc cb5e5a0
fix(i18n): use correct key for processing label in ASR test button
wyuc 53b5dba
fix(audio): polish custom provider UX and type safety
wyuc 2834e3a
fix(test): add isCustomTTSProvider/isCustomASRProvider to audio types…
wyuc 30b35bc
fix(audio): add customDefaultBaseUrl fallback in all TTS/ASR call sites
wyuc 773a180
style: format generation-preview/page.tsx
wyuc 403952f
fix: remove unused ttsSpeedRange variable in media-popover
wyuc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| 'use client'; | ||
|
|
||
| import { useState } from 'react'; | ||
| import { Dialog, DialogContent, DialogTitle, DialogDescription } from '@/components/ui/dialog'; | ||
| import { Button } from '@/components/ui/button'; | ||
| import { Input } from '@/components/ui/input'; | ||
| import { Label } from '@/components/ui/label'; | ||
| import { Checkbox } from '@/components/ui/checkbox'; | ||
| import { Plus } from 'lucide-react'; | ||
| import { useI18n } from '@/lib/hooks/use-i18n'; | ||
|
|
||
| export interface NewAudioProviderData { | ||
| name: string; | ||
| baseUrl: string; | ||
| defaultModel: string; | ||
| requiresApiKey: boolean; | ||
| } | ||
|
|
||
| interface AddAudioProviderDialogProps { | ||
| open: boolean; | ||
| onOpenChange: (open: boolean) => void; | ||
| onAdd: (data: NewAudioProviderData) => void; | ||
| type: 'tts' | 'asr'; | ||
| } | ||
|
|
||
| export function AddAudioProviderDialog({ | ||
| open, | ||
| onOpenChange, | ||
| onAdd, | ||
| type, | ||
| }: AddAudioProviderDialogProps) { | ||
| const { t } = useI18n(); | ||
|
|
||
| const [name, setName] = useState(''); | ||
| const [baseUrl, setBaseUrl] = useState(''); | ||
| const [defaultModel, setDefaultModel] = useState(''); | ||
| const [requiresApiKey, setRequiresApiKey] = useState(false); | ||
|
|
||
| // Reset form when dialog closes | ||
| const [prevOpen, setPrevOpen] = useState(open); | ||
| if (open !== prevOpen) { | ||
| setPrevOpen(open); | ||
| if (!open) { | ||
| setName(''); | ||
| setBaseUrl(''); | ||
| setDefaultModel(''); | ||
| setRequiresApiKey(false); | ||
| } | ||
| } | ||
|
|
||
| const handleAdd = () => { | ||
| if (!name.trim() || !baseUrl.trim()) return; | ||
| onAdd({ | ||
| name: name.trim(), | ||
| baseUrl: baseUrl.trim(), | ||
| defaultModel: defaultModel.trim(), | ||
| requiresApiKey, | ||
| }); | ||
| onOpenChange(false); | ||
| }; | ||
|
|
||
| const titleKey = | ||
| type === 'tts' ? 'settings.addCustomTTSProvider' : 'settings.addCustomASRProvider'; | ||
|
|
||
| return ( | ||
| <Dialog open={open} onOpenChange={onOpenChange}> | ||
| <DialogContent className="sm:max-w-[450px]"> | ||
| <DialogTitle className="sr-only">{t(titleKey)}</DialogTitle> | ||
| <DialogDescription className="sr-only"> | ||
| {t('settings.addCustomAudioProviderDescription')} | ||
| </DialogDescription> | ||
| <div className="space-y-4"> | ||
| <div className="pb-3 border-b"> | ||
| <h2 className="text-lg font-semibold">{t(titleKey)}</h2> | ||
| <p className="text-xs text-muted-foreground mt-1"> | ||
| {t('settings.addCustomAudioProviderDescription')} | ||
| </p> | ||
| </div> | ||
|
|
||
| <div className="space-y-2"> | ||
| <Label>{t('settings.providerName')}</Label> | ||
| <Input | ||
| placeholder={type === 'tts' ? 'My Local TTS' : 'My Local ASR'} | ||
| value={name} | ||
| onChange={(e) => setName(e.target.value)} | ||
| /> | ||
| </div> | ||
|
|
||
| <div className="space-y-2"> | ||
| <Label>{t('settings.defaultBaseUrl')}</Label> | ||
| <Input | ||
| type="url" | ||
| placeholder="http://localhost:8000/v1" | ||
| value={baseUrl} | ||
| onChange={(e) => setBaseUrl(e.target.value)} | ||
| /> | ||
| </div> | ||
|
|
||
| {/* Default Model — TTS only (ASR models are managed in provider settings) */} | ||
| {type === 'tts' && ( | ||
| <div className="space-y-2"> | ||
| <Label>{t('settings.defaultModel')}</Label> | ||
| <Input | ||
| placeholder="tts-1" | ||
| value={defaultModel} | ||
| onChange={(e) => setDefaultModel(e.target.value)} | ||
| /> | ||
| <p className="text-xs text-muted-foreground">{t('settings.defaultModelHint')}</p> | ||
| </div> | ||
| )} | ||
|
|
||
| <div className="flex items-center space-x-2"> | ||
| <Checkbox | ||
| id="audio-requires-api-key" | ||
| checked={requiresApiKey} | ||
| onCheckedChange={(checked) => setRequiresApiKey(checked as boolean)} | ||
| /> | ||
| <label htmlFor="audio-requires-api-key" className="text-sm cursor-pointer"> | ||
| {t('settings.requiresApiKey')} | ||
| </label> | ||
| </div> | ||
|
|
||
| <div className="flex items-center justify-end gap-2 pt-3 border-t"> | ||
| <Button variant="outline" size="sm" onClick={() => onOpenChange(false)}> | ||
| {t('settings.cancelEdit')} | ||
| </Button> | ||
| <Button | ||
| size="sm" | ||
| onClick={handleAdd} | ||
| disabled={!name.trim() || !baseUrl.trim()} | ||
| className="gap-1.5" | ||
| > | ||
| <Plus className="h-3.5 w-3.5" /> | ||
| {t('settings.addProviderButton')} | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| </DialogContent> | ||
| </Dialog> | ||
| ); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.