import { useState, useRef, useCallback, useEffect } from "react" import type { ModalPhase } from "../types/cloneModal" import { MIN_VOICE_NAME_LENGTH, MAX_VOICE_NAME_LENGTH } from "../constants/cloneModal" import useAudioRecorder from "./useAudioRecorder" /** * 克隆弹窗表单状态 Hook * 管理表单字段、录音、文件选择、验证逻辑 */ export function useCloneFormState({ open, onClose }: { open: boolean; onClose: () => void }) { const [phase, setPhase] = useState("input") const [voiceName, setVoiceName] = useState("") const [voiceDescription, setVoiceDescription] = useState("") const [selectedFile, setSelectedFile] = useState(null) const [dragActive, setDragActive] = useState(false) const [errorMessage, setErrorMessage] = useState("") const { isRecording, recordTime, recordedBlob, toggleRecording, resetRecording } = useAudioRecorder() /** 默认音色名称计数器 */ const cloneCounterRef = useRef(1) const getNextDefaultName = useCallback((): string => { const name = `我的声音 ${cloneCounterRef.current}` cloneCounterRef.current += 1 return name }, []) const hasAudio = selectedFile !== null || recordedBlob !== null const canSubmit = voiceName.trim().length >= MIN_VOICE_NAME_LENGTH && voiceName.trim().length <= MAX_VOICE_NAME_LENGTH && hasAudio const isProcessing = phase === "uploading" || phase === "cloning" /** 重置弹窗状态 */ const resetState = useCallback(() => { setPhase("input") setVoiceName(getNextDefaultName()) setVoiceDescription("") setSelectedFile(null) setDragActive(false) setErrorMessage("") resetRecording() }, [getNextDefaultName, resetRecording]) /** 关闭弹窗 */ const handleClose = useCallback(() => { resetState() onClose() }, [resetState, onClose]) /** 弹窗打开时重置状态 */ useEffect(() => { if (open) { resetState() } }, [open, resetState]) /** 选择文件(来自上传或拖拽) */ const handleFileSelect = useCallback( (file: File | null, error: string) => { if (error) { setErrorMessage(error) setSelectedFile(null) } else { setErrorMessage("") setSelectedFile(file) // 清除录音 resetRecording() } }, [resetRecording], ) /** 录音切换 */ const handleRecordToggle = useCallback(() => { setErrorMessage("") if (isRecording) { toggleRecording() } else { // 开始录制前清除已选文件 setSelectedFile(null) toggleRecording() } }, [isRecording, toggleRecording]) /** 表单验证 */ const validateForm = useCallback((): string | null => { const name = voiceName.trim() if (!name) { return "请输入音色名称" } if (name.length < MIN_VOICE_NAME_LENGTH || name.length > MAX_VOICE_NAME_LENGTH) { return `音色名称需在 ${MIN_VOICE_NAME_LENGTH}-${MAX_VOICE_NAME_LENGTH} 个字符之间` } if (!hasAudio) { return "请上传音频文件或录制一段声音" } return null }, [voiceName, hasAudio]) return { // 状态 phase, setPhase, voiceName, setVoiceName, voiceDescription, setVoiceDescription, selectedFile, dragActive, setDragActive, errorMessage, setErrorMessage, // 录音 isRecording, recordTime, recordedBlob, // 计算属性 hasAudio, canSubmit, isProcessing, // handlers handleFileSelect, handleRecordToggle, handleClose, validateForm, resetState, } }