@@ -402,6 +402,7 @@ export default function SettingsPage() {
402402 ? staticVoiceOptions . map ( v => ( {
403403 id : v . id ,
404404 name : v . label ,
405+ description : v . description ,
405406 previewUrl : '' ,
406407 category : 'premade'
407408 } ) )
@@ -413,8 +414,13 @@ export default function SettingsPage() {
413414 ?? fallbackVoices . find ( v => v . id === voiceId ) ?. name
414415 ?? voiceId )
415416 : null
417+ const currentVoiceDescription = voiceId
418+ ? ( voiceOptions . find ( v => v . id === voiceId ) ?. description
419+ ?? staticVoiceOptions . find ( v => v . id === voiceId ) ?. description )
420+ : undefined
421+ const staticVoiceCatalog = voiceBackend === 'gemini-live' || voiceBackend === 'qwen-realtime'
416422
417- const showVoicePreview = voiceBackend === 'elevenlabs'
423+ const voicePreviewEnabled = voiceBackend === 'elevenlabs'
418424 const showVoiceBackendChooser = configuredVoiceBackends . length > 1
419425 const currentVoiceBackendLabel = voiceBackend ? VOICE_BACKEND_LABELS [ voiceBackend ] : null
420426
@@ -1138,15 +1144,29 @@ export default function SettingsPage() {
11381144 aria-haspopup = "listbox"
11391145 >
11401146 < span className = "text-[var(--app-fg)]" > { t ( 'settings.voice.voice' ) } </ span >
1141- < span className = "flex items-center gap-1 text-[var(--app-hint)]" >
1142- < span > { currentVoiceName ?? t ( 'settings.voice.voiceDefault' ) } </ span >
1143- < ChevronDownIcon className = { `transition-transform ${ isVoicePickerOpen ? 'rotate-180' : '' } ` } />
1147+ < span className = "flex min-w-0 items-center gap-1 text-[var(--app-hint)]" >
1148+ < span className = "min-w-0 text-right" >
1149+ < span className = "block truncate" >
1150+ { currentVoiceName ?? t ( 'settings.voice.voiceDefault' ) }
1151+ </ span >
1152+ { currentVoiceDescription && (
1153+ < span className = "block text-xs leading-snug text-[var(--app-hint)]" >
1154+ { currentVoiceDescription }
1155+ </ span >
1156+ ) }
1157+ </ span >
1158+ < ChevronDownIcon className = { `shrink-0 transition-transform ${ isVoicePickerOpen ? 'rotate-180' : '' } ` } />
11441159 </ span >
11451160 </ button >
1161+ { staticVoiceCatalog && (
1162+ < p className = "px-3 pb-2 text-xs text-[var(--app-hint)]" >
1163+ { t ( 'settings.voice.staticCatalogHint' ) }
1164+ </ p >
1165+ ) }
11461166
11471167 { isVoicePickerOpen && (
11481168 < div
1149- className = "absolute right-3 top-full mt-1 min-w-[220px] max-h-[300px ] overflow-y-auto rounded-lg border border-[var(--app-border)] bg-[var(--app-bg)] shadow-lg z-50 "
1169+ className = "absolute right-3 top-full z-50 mt-1 max-h-[300px] min-w-[260px ] overflow-y-auto rounded-lg border border-[var(--app-border)] bg-[var(--app-bg)] shadow-lg"
11501170 role = "listbox"
11511171 aria-label = { t ( 'settings.voice.voice' ) }
11521172 >
@@ -1176,7 +1196,7 @@ export default function SettingsPage() {
11761196 key = { voice . id }
11771197 role = "option"
11781198 aria-selected = { isSelected }
1179- className = { `flex items-center w-full text-base transition-colors ${
1199+ className = { `flex w-full items-start text-base transition-colors ${
11801200 isSelected
11811201 ? 'text-[var(--app-link)] bg-[var(--app-subtle-bg)]'
11821202 : 'text-[var(--app-fg)] hover:bg-[var(--app-subtle-bg)]'
@@ -1185,32 +1205,43 @@ export default function SettingsPage() {
11851205 < button
11861206 type = "button"
11871207 onClick = { ( ) => handleVoiceChange ( voice . id ) }
1188- className = "flex flex-1 items-center justify-between px-3 py-2 text-left min-w-0 "
1208+ className = "flex min-w-0 flex-1 items-start justify-between px-3 py-2.5 text-left"
11891209 >
1190- < span className = "truncate" >
1191- { voice . name }
1192- { voice . category === 'cloned' && (
1193- < span className = "ml-2 text-xs text-[var(--app-hint)]" > clone</ span >
1210+ < span className = "min-w-0 pr-2" >
1211+ < span className = "block font-medium leading-snug" >
1212+ { voice . name }
1213+ { voice . category === 'cloned' && (
1214+ < span className = "ml-2 text-xs font-normal text-[var(--app-hint)]" > clone</ span >
1215+ ) }
1216+ </ span >
1217+ { voice . description && (
1218+ < span className = "mt-0.5 block text-xs leading-snug text-[var(--app-hint)]" >
1219+ { voice . description }
1220+ </ span >
11941221 ) }
11951222 </ span >
11961223 { isSelected && < span className = "ml-2 shrink-0" > < CheckIcon /> </ span > }
11971224 </ button >
1198- { showVoicePreview && (
1199- < button
1200- type = "button"
1201- onClick = { ( e ) => handleVoicePreview ( voice . previewUrl , voice . id , e ) }
1202- aria-label = { isPlaying ? 'Stop preview' : 'Preview voice' }
1203- title = { voice . previewUrl ? ( isPlaying ? 'Stop preview' : 'Preview voice' ) : 'Preview unavailable without an ElevenLabs API key' }
1204- disabled = { ! voice . previewUrl }
1205- className = { `flex h-full shrink-0 items-center px-3 py-2 ${
1206- voice . previewUrl
1207- ? 'text-[var(--app-hint)] hover:text-[var(--app-fg)]'
1208- : 'text-[var(--app-divider)] cursor-not-allowed'
1209- } `}
1210- >
1211- { isPlaying ? < StopIcon /> : < PlayIcon /> }
1212- </ button >
1213- ) }
1225+ < button
1226+ type = "button"
1227+ onClick = { ( e ) => handleVoicePreview ( voice . previewUrl , voice . id , e ) }
1228+ aria-label = { isPlaying ? 'Stop preview' : 'Preview voice' }
1229+ title = {
1230+ voicePreviewEnabled && voice . previewUrl
1231+ ? ( isPlaying ? 'Stop preview' : 'Preview voice' )
1232+ : voicePreviewEnabled
1233+ ? t ( 'settings.voice.preview.unavailable' )
1234+ : t ( 'settings.voice.preview.elevenlabsOnly' )
1235+ }
1236+ disabled = { ! voicePreviewEnabled || ! voice . previewUrl }
1237+ className = { `flex shrink-0 items-center self-center px-3 py-2 ${
1238+ voicePreviewEnabled && voice . previewUrl
1239+ ? 'text-[var(--app-hint)] hover:text-[var(--app-fg)]'
1240+ : 'text-[var(--app-divider)] cursor-not-allowed'
1241+ } `}
1242+ >
1243+ { isPlaying ? < StopIcon /> : < PlayIcon /> }
1244+ </ button >
12141245 </ div >
12151246 )
12161247 } ) }
0 commit comments