Refactor spacing and layout in forms for improved UI consistency

This commit is contained in:
2026-01-26 16:56:29 +08:00
parent 783e0dc984
commit 23e72f80e5
6 changed files with 51 additions and 39 deletions

View File

@@ -28,8 +28,8 @@ export function ParamInput<T extends FieldValues>({
max, max,
}: ParamInputProps<T>) { }: ParamInputProps<T>) {
return ( return (
<div className="space-y-2"> <div className="space-y-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-1.5">
<Label htmlFor={name}>{label}</Label> <Label htmlFor={name}>{label}</Label>
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>

View File

@@ -1,5 +1,6 @@
import { memo, useMemo } from 'react' import { memo, useMemo } from 'react'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Shuffle } from 'lucide-react'
interface Preset { interface Preset {
label: string label: string
@@ -20,17 +21,34 @@ const PresetSelectorInner = <T extends Preset>({ presets, onSelect }: PresetSele
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => onSelect(preset)} onClick={() => onSelect(preset)}
className="text-xs md:text-sm px-2.5 md:px-3 h-7 md:h-8" className="text-xs md:text-sm px-2 h-6 md:h-7"
> >
{preset.label} {preset.label}
</Button> </Button>
)) ))
}, [presets, onSelect]) }, [presets, onSelect])
const handleRandomSelect = () => {
const randomIndex = Math.floor(Math.random() * presets.length)
onSelect(presets[randomIndex])
}
return ( return (
<div className="flex flex-wrap gap-1.5 md:gap-2 mt-1.5 md:mt-2"> <div className="flex items-center gap-2 mt-1">
<div className="flex flex-wrap gap-1 flex-1">
{presetButtons} {presetButtons}
</div> </div>
<Button
type="button"
variant="ghost"
size="sm"
onClick={handleRandomSelect}
className="h-6 md:h-7 px-2 flex-shrink-0"
title="随机选择"
>
<Shuffle className="h-3.5 w-3.5" />
</Button>
</div>
) )
} }

View File

@@ -116,8 +116,8 @@ const CustomVoiceForm = forwardRef<CustomVoiceFormHandle>((_props, ref) => {
}, [currentJob?.id, currentJob?.audio_url]) }, [currentJob?.id, currentJob?.audio_url])
return ( return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4 md:space-y-6"> <form onSubmit={handleSubmit(onSubmit)} className="space-y-3">
<div className="space-y-1.5 md:space-y-2"> <div className="space-y-1">
<Label htmlFor="language"></Label> <Label htmlFor="language"></Label>
<Select <Select
value={watch('language')} value={watch('language')}
@@ -139,7 +139,7 @@ const CustomVoiceForm = forwardRef<CustomVoiceFormHandle>((_props, ref) => {
)} )}
</div> </div>
<div className="space-y-1.5 md:space-y-2"> <div className="space-y-1">
<Label htmlFor="speaker"></Label> <Label htmlFor="speaker"></Label>
<Select <Select
value={watch('speaker')} value={watch('speaker')}
@@ -161,26 +161,24 @@ const CustomVoiceForm = forwardRef<CustomVoiceFormHandle>((_props, ref) => {
)} )}
</div> </div>
<div className="space-y-1.5 md:space-y-2"> <div className="space-y-1">
<Label htmlFor="text"></Label> <Label htmlFor="text"></Label>
<Textarea <Textarea
{...register('text')} {...register('text')}
placeholder="输入要合成的文本..." placeholder="输入要合成的文本..."
rows={2} className="min-h-[40px] md:min-h-[60px]"
className="min-h-[60px] md:min-h-[96px]"
/> />
{errors.text && ( {errors.text && (
<p className="text-sm text-destructive">{errors.text.message}</p> <p className="text-sm text-destructive">{errors.text.message}</p>
)} )}
</div> </div>
<div className="space-y-1.5 md:space-y-2"> <div className="space-y-1">
<Label htmlFor="instruct"></Label> <Label htmlFor="instruct"></Label>
<Textarea <Textarea
{...register('instruct')} {...register('instruct')}
placeholder="例如:温柔体贴,语速平缓,充满关怀" placeholder="例如:温柔体贴,语速平缓,充满关怀"
rows={2} className="min-h-[40px] md:min-h-[60px]"
className="min-h-[60px] md:min-h-[80px]"
/> />
<PresetSelector <PresetSelector
presets={PRESET_INSTRUCTS} presets={PRESET_INSTRUCTS}
@@ -198,12 +196,12 @@ const CustomVoiceForm = forwardRef<CustomVoiceFormHandle>((_props, ref) => {
<Collapsible open={advancedOpen} onOpenChange={setAdvancedOpen}> <Collapsible open={advancedOpen} onOpenChange={setAdvancedOpen}>
<CollapsibleTrigger asChild> <CollapsibleTrigger asChild>
<Button type="button" variant="ghost" className="w-full"> <Button type="button" variant="ghost" className="w-full py-1.5">
<ChevronDown className="ml-2 h-4 w-4" /> <ChevronDown className="ml-2 h-4 w-4" />
</Button> </Button>
</CollapsibleTrigger> </CollapsibleTrigger>
<CollapsibleContent className="space-y-3 md:space-y-4 pt-3 md:pt-4"> <CollapsibleContent className="space-y-2 pt-2">
<ParamInput <ParamInput
name="max_new_tokens" name="max_new_tokens"
label={ADVANCED_PARAMS_INFO.max_new_tokens.label} label={ADVANCED_PARAMS_INFO.max_new_tokens.label}

View File

@@ -101,14 +101,13 @@ function VoiceCloneForm() {
}, [currentJob?.id, currentJob?.audio_url]) }, [currentJob?.id, currentJob?.audio_url])
return ( return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4 md:space-y-6"> <form onSubmit={handleSubmit(onSubmit)} className="space-y-3">
<div className="space-y-1.5 md:space-y-2"> <div className="space-y-1">
<Label htmlFor="ref_text">稿</Label> <Label htmlFor="ref_text">稿</Label>
<Textarea <Textarea
{...register('ref_text')} {...register('ref_text')}
placeholder="参考音频对应的文本..." placeholder="参考音频对应的文本..."
rows={2} className="min-h-[40px] md:min-h-[60px]"
className="min-h-[60px] md:min-h-[80px]"
/> />
<PresetSelector <PresetSelector
presets={PRESET_REF_TEXTS} presets={PRESET_REF_TEXTS}
@@ -119,7 +118,7 @@ function VoiceCloneForm() {
)} )}
</div> </div>
<div className="space-y-1.5 md:space-y-2"> <div className="space-y-1">
<Label htmlFor="ref_audio"></Label> <Label htmlFor="ref_audio"></Label>
<Controller <Controller
name="ref_audio" name="ref_audio"
@@ -134,7 +133,7 @@ function VoiceCloneForm() {
/> />
</div> </div>
<div className="space-y-1.5 md:space-y-2"> <div className="space-y-1">
<Label htmlFor="language"></Label> <Label htmlFor="language"></Label>
<Select <Select
value={watch('language')} value={watch('language')}
@@ -153,13 +152,12 @@ function VoiceCloneForm() {
</Select> </Select>
</div> </div>
<div className="space-y-1.5 md:space-y-2"> <div className="space-y-1">
<Label htmlFor="text"></Label> <Label htmlFor="text"></Label>
<Textarea <Textarea
{...register('text')} {...register('text')}
placeholder="输入要合成的文本..." placeholder="输入要合成的文本..."
rows={2} className="min-h-[40px] md:min-h-[60px]"
className="min-h-[60px] md:min-h-[96px]"
/> />
<PresetSelector <PresetSelector
presets={PRESET_REF_TEXTS} presets={PRESET_REF_TEXTS}
@@ -170,7 +168,7 @@ function VoiceCloneForm() {
)} )}
</div> </div>
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-3">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Controller <Controller
name="x_vector_only_mode" name="x_vector_only_mode"
@@ -208,12 +206,12 @@ function VoiceCloneForm() {
<Collapsible open={advancedOpen} onOpenChange={setAdvancedOpen}> <Collapsible open={advancedOpen} onOpenChange={setAdvancedOpen}>
<CollapsibleTrigger asChild> <CollapsibleTrigger asChild>
<Button type="button" variant="ghost" className="w-full"> <Button type="button" variant="ghost" className="w-full py-1.5">
<ChevronDown className="ml-2 h-4 w-4" /> <ChevronDown className="ml-2 h-4 w-4" />
</Button> </Button>
</CollapsibleTrigger> </CollapsibleTrigger>
<CollapsibleContent className="space-y-3 md:space-y-4 pt-3 md:pt-4"> <CollapsibleContent className="space-y-2 pt-2">
<ParamInput <ParamInput
name="max_new_tokens" name="max_new_tokens"
label={ADVANCED_PARAMS_INFO.max_new_tokens.label} label={ADVANCED_PARAMS_INFO.max_new_tokens.label}

View File

@@ -107,8 +107,8 @@ const VoiceDesignForm = forwardRef<VoiceDesignFormHandle>((_props, ref) => {
}, [currentJob?.id, currentJob?.audio_url]) }, [currentJob?.id, currentJob?.audio_url])
return ( return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4 md:space-y-6"> <form onSubmit={handleSubmit(onSubmit)} className="space-y-3">
<div className="space-y-1.5 md:space-y-2"> <div className="space-y-1">
<Label htmlFor="language"></Label> <Label htmlFor="language"></Label>
<Select <Select
value={watch('language')} value={watch('language')}
@@ -130,26 +130,24 @@ const VoiceDesignForm = forwardRef<VoiceDesignFormHandle>((_props, ref) => {
)} )}
</div> </div>
<div className="space-y-1.5 md:space-y-2"> <div className="space-y-1">
<Label htmlFor="text"></Label> <Label htmlFor="text"></Label>
<Textarea <Textarea
{...register('text')} {...register('text')}
placeholder="输入要合成的文本..." placeholder="输入要合成的文本..."
rows={2} className="min-h-[40px] md:min-h-[60px]"
className="min-h-[60px] md:min-h-[96px]"
/> />
{errors.text && ( {errors.text && (
<p className="text-sm text-destructive">{errors.text.message}</p> <p className="text-sm text-destructive">{errors.text.message}</p>
)} )}
</div> </div>
<div className="space-y-1.5 md:space-y-2"> <div className="space-y-1">
<Label htmlFor="instruct"></Label> <Label htmlFor="instruct"></Label>
<Textarea <Textarea
{...register('instruct')} {...register('instruct')}
placeholder="例如:成熟男性,低沉磁性,充满权威感" placeholder="例如:成熟男性,低沉磁性,充满权威感"
rows={2} className="min-h-[40px] md:min-h-[60px]"
className="min-h-[60px] md:min-h-[80px]"
/> />
<PresetSelector <PresetSelector
presets={PRESET_VOICE_DESIGNS} presets={PRESET_VOICE_DESIGNS}
@@ -167,12 +165,12 @@ const VoiceDesignForm = forwardRef<VoiceDesignFormHandle>((_props, ref) => {
<Collapsible open={advancedOpen} onOpenChange={setAdvancedOpen}> <Collapsible open={advancedOpen} onOpenChange={setAdvancedOpen}>
<CollapsibleTrigger asChild> <CollapsibleTrigger asChild>
<Button type="button" variant="ghost" className="w-full"> <Button type="button" variant="ghost" className="w-full py-1.5">
<ChevronDown className="ml-2 h-4 w-4" /> <ChevronDown className="ml-2 h-4 w-4" />
</Button> </Button>
</CollapsibleTrigger> </CollapsibleTrigger>
<CollapsibleContent className="space-y-3 md:space-y-4 pt-3 md:pt-4"> <CollapsibleContent className="space-y-2 pt-2">
<ParamInput <ParamInput
name="max_new_tokens" name="max_new_tokens"
label={ADVANCED_PARAMS_INFO.max_new_tokens.label} label={ADVANCED_PARAMS_INFO.max_new_tokens.label}

View File

@@ -71,7 +71,7 @@ function Home() {
</Tabs> </Tabs>
</CardHeader> </CardHeader>
<CardContent className="pt-4 md:pt-6 px-3 md:px-6"> <CardContent className="pt-0 px-3 md:px-6">
<Tabs value={currentTab}> <Tabs value={currentTab}>
<TabsContent value="custom-voice" className="mt-0"> <TabsContent value="custom-voice" className="mt-0">
<Suspense fallback={<FormSkeleton />}> <Suspense fallback={<FormSkeleton />}>