143 lines
4.2 KiB
TypeScript
143 lines
4.2 KiB
TypeScript
import React from "react"
|
|
import { Button } from "@/components/ui"
|
|
import UploadZone from "./UploadZone"
|
|
import RecordArea from "./RecordArea"
|
|
import StepIndicator from "./StepIndicator"
|
|
import { MAX_VOICE_NAME_LENGTH, MAX_VOICE_DESC_LENGTH } from "../constants/cloneModal"
|
|
|
|
interface InputViewProps {
|
|
voiceName: string
|
|
voiceDescription: string
|
|
selectedFile: File | null
|
|
dragActive: boolean
|
|
isRecording: boolean
|
|
recordTime: number
|
|
recordedBlob: Blob | null
|
|
errorMessage: string
|
|
canSubmit: boolean
|
|
onVoiceNameChange: (value: string) => void
|
|
onVoiceDescChange: (value: string) => void
|
|
onDragActiveChange: (active: boolean) => void
|
|
onFileSelect: (file: File | null, error: string) => void
|
|
onRecordToggle: () => void
|
|
onClose: () => void
|
|
onSubmit: () => void
|
|
}
|
|
|
|
const INPUT_STEPS = ["上传/录制音频", "填写信息", "提交克隆"]
|
|
|
|
const InputView: React.FC<InputViewProps> = ({
|
|
voiceName,
|
|
voiceDescription,
|
|
selectedFile,
|
|
dragActive,
|
|
isRecording,
|
|
recordTime,
|
|
recordedBlob,
|
|
errorMessage,
|
|
canSubmit,
|
|
onVoiceNameChange,
|
|
onVoiceDescChange,
|
|
onDragActiveChange,
|
|
onFileSelect,
|
|
onRecordToggle,
|
|
onClose,
|
|
onSubmit,
|
|
}) => {
|
|
return (
|
|
<div className="xx-clonemodal-body">
|
|
{/* 步骤引导 */}
|
|
<StepIndicator currentStep={0} steps={INPUT_STEPS} />
|
|
|
|
{/* 音色名称 */}
|
|
<div className="xx-clonemodal-field">
|
|
<label className="xx-clonemodal-label">
|
|
音色名称 <span className="xx-clonemodal-required">*</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
className="xx-clonemodal-input"
|
|
value={voiceName}
|
|
onChange={(e) => onVoiceNameChange(e.target.value)}
|
|
placeholder="输入音色名称(2-20字符)"
|
|
maxLength={MAX_VOICE_NAME_LENGTH}
|
|
/>
|
|
<div className="xx-clonemodal-char-count">
|
|
{voiceName.length}/{MAX_VOICE_NAME_LENGTH}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 上传区域 */}
|
|
<div className="xx-clonemodal-field">
|
|
<label className="xx-clonemodal-label">上传音频</label>
|
|
<UploadZone
|
|
selectedFile={selectedFile}
|
|
dragActive={dragActive}
|
|
onDragActiveChange={onDragActiveChange}
|
|
onFileSelect={onFileSelect}
|
|
/>
|
|
</div>
|
|
|
|
{/* 或分隔 */}
|
|
<div className="xx-clonemodal-divider">
|
|
<div className="xx-clonemodal-divider-line" />
|
|
<span className="xx-clonemodal-divider-text">或</span>
|
|
<div className="xx-clonemodal-divider-line" />
|
|
</div>
|
|
|
|
{/* 录制区域 */}
|
|
<div className="xx-clonemodal-field">
|
|
<label className="xx-clonemodal-label">直接录制</label>
|
|
<RecordArea
|
|
isRecording={isRecording}
|
|
recordTime={recordTime}
|
|
recordedBlob={recordedBlob}
|
|
onRecordToggle={onRecordToggle}
|
|
/>
|
|
</div>
|
|
|
|
{/* 音色描述 */}
|
|
<div className="xx-clonemodal-field">
|
|
<label className="xx-clonemodal-label">音色描述</label>
|
|
<textarea
|
|
className="xx-clonemodal-textarea"
|
|
value={voiceDescription}
|
|
onChange={(e) => onVoiceDescChange(e.target.value)}
|
|
placeholder="可选,描述这个音色的特点(最多100字符)"
|
|
maxLength={MAX_VOICE_DESC_LENGTH}
|
|
rows={3}
|
|
/>
|
|
<div className="xx-clonemodal-char-count">
|
|
{voiceDescription.length}/{MAX_VOICE_DESC_LENGTH}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 错误提示 */}
|
|
{errorMessage && (
|
|
<div className="xx-clonemodal-error">
|
|
<span className="xx-clonemodal-error-icon">⚠️</span>
|
|
<span>{errorMessage}</span>
|
|
</div>
|
|
)}
|
|
|
|
{/* 提示 */}
|
|
<div className="xx-clonemodal-tip">
|
|
<span className="xx-clonemodal-tip-icon">💡</span>
|
|
<span>建议上传 10 秒 ~ 3 分钟的清晰语音,环境安静、语速均匀效果最佳</span>
|
|
</div>
|
|
|
|
{/* 底部按钮 */}
|
|
<div className="xx-clonemodal-footer">
|
|
<Button buttonType="ghost" onClick={onClose}>
|
|
取消
|
|
</Button>
|
|
<Button buttonType="primary" disabled={!canSubmit} onClick={onSubmit}>
|
|
🎤 开始克隆
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default InputView
|