123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- import EmptyData from "@/components/empty-data";
- import CardListItem from "@/components/list/card-list-item/index";
- import CardList from "@/components/list/card-list/index";
- import { IVoicePlayerBar } from "@/components/voice-player-bar/index";
- import WemetaRadio from "@/components/wemeta-radio/index";
- import { CloneVoiceStatus } from "@/consts/enum";
- import IconWave from "@/images/icon-wave-20.png";
- import { voiceCloneConfirm } from "@/service/character";
- import { useAppStore } from "@/store/appStore";
- import { useCharacterStore } from "@/store/characterStore";
- import { ICharacter, TEntityVoiceCloneRecord } from "@/types";
- import { formatDateFull, getCloneVoiceIdentifier } from "@/utils/index";
- import { Image, View } from "@tarojs/components";
- import { useEffect, useRef, useState } from "react";
- import PopupContainer from "./components/popup-container";
- import PopupRecorder, { ECloneStatus } from "./components/popup-recorder/index";
- import PopupTryout from "./components/popup-tryout/index";
- import style from "./index.module.less";
- interface Props {
- value?: ICharacter;
- setValue?: (value: ICharacter) => void;
- profileId: string;
- onPlay?: (voice: any) => void;
- }
- export default ({ profileId, onPlay }: Props) => {
- const playerRef = useRef<IVoicePlayerBar>(null);
- const intervalRef = useRef<NodeJS.Timeout | null>(null);
- const [show, setShow] = useState(false);
- const [popupType, setPopupType] = useState<"clone" | "try" | "reclone">(
- "clone",
- );
- const [voiceName, setVoiceName] = useState("");
- const [voiceIndex, setVoiceIndex] = useState(-1);
- const { saveCharacter, fetchCharacter, fetchVoiceCloneHistory } =
- useCharacterStore();
- const character = useCharacterStore((state) => state.character);
- const voiceList = useCharacterStore((state) => state.voiceList);
- const appConfig = useAppStore((state) => state.appConfig);
- const createVoiceNameText = (voiceName: string) => {
- const i = voiceList.findIndex((item) => item.voiceName === voiceName);
- console.log(voiceName, i);
- if (i === -1) return "";
- return `克隆声音 0${i + 1}`;
- };
- // 克隆列表操作
- const renderRightColumn = (item?: TEntityVoiceCloneRecord) => {
- if (item?.status == CloneVoiceStatus.CloneVoiceStatusExpired) {
- return <WemetaRadio disabled></WemetaRadio>;
- }
- if (item?.status == CloneVoiceStatus.CloneVoiceStatusSuccess) {
- const notSystemVoice = (character?.voice !== character?.defaultSystemVoice)
- const isSameVoice = (item.voiceName === character?.voice)
- return (
- <WemetaRadio
- checked={ isSameVoice && notSystemVoice}
- ></WemetaRadio>
- );
- }
- if (item?.status == CloneVoiceStatus.CloneVoiceStatusUnconfirmed) {
- return (
- <View className="text-14 bg-primary text-black leading-22 px-12 py-6 rounded-28 active:pressed-button">
- 试听
- </View>
- );
- }
- return <></>;
- };
- // 克隆状态
- const renderCloneStatus = (item: TEntityVoiceCloneRecord) => {
- if (item.status === CloneVoiceStatus.CloneVoiceStatusSuccess) {
- return (
- <View className={`text-12 leading-20 text-gray-45`}>
- {item.createdAt && formatDateFull(new Date(item.createdAt))}
- </View>
- );
- }
- if (item.status === CloneVoiceStatus.CloneVoiceStatusUnconfirmed) {
- return <View className={`text-12 leading-20 text-orange`}>试听确认</View>;
- }
- if (item.status == CloneVoiceStatus.CloneVoiceStatusExpired) {
- return <View className={`text-12 leading-20 text-orange`}>已过期</View>;
- }
- return <View>当前时间</View>;
- };
- const renderCloneList = () => {
- if (!voiceList.length) {
- return <EmptyData></EmptyData>;
- }
- return (
- <CardList>
- {voiceList.map((item, _index) => {
- return (
- <CardListItem
- rightRenderer={() => {
- return renderRightColumn(item);
- }}
- onClick={() => handleSelect(item, _index)}
- >
- <View className="flex items-center gap-16">
- <View className={style.listIcon}>
- <Image src={IconWave} className={style.iconImage}></Image>
- </View>
- <View className="flex flex-col gap-4">
- <View className={style.ListItemText}>
- {/* deprecated getCloneVoiceIdentifier */}
- {item.voiceAlias ?? getCloneVoiceIdentifier(_index + 1)}
- </View>
- {renderCloneStatus(item)}
- </View>
- </View>
- </CardListItem>
- );
- })}
- </CardList>
- );
- };
- const handleSelect = (item: TEntityVoiceCloneRecord, index: number) => {
- setVoiceIndex(index);
- if (item.status == CloneVoiceStatus.CloneVoiceStatusSuccess) {
- if (item.voiceName) {
- setVoiceName(item.voiceName);
- onPlay &&
- onPlay({
- voiceName: item.voiceName,
- voiceAlias: item.voiceAlias,
- voiceIndex: index,
- });
- }
- saveCharacter({
- profileId: profileId,
- voice: item.voiceName,
- });
- }
- if (item.status == CloneVoiceStatus.CloneVoiceStatusUnconfirmed) {
- // 未确认,弹出试听框
- setPopupType("try");
- setShow(true);
- }
- };
- // 克隆按钮状态
- const handleCloneStatus = (status: ECloneStatus) => {
- console.log(status);
- };
- const fetchVoiceList = async () => {
- if (profileId) {
- const r = await fetchVoiceCloneHistory(profileId);
- const result = r.find((item) => item.status === "pending");
- if (result) {
- intervalRef.current = setTimeout(() => fetchVoiceList(), 3000);
- } else {
- stopTimer();
- }
- }
- };
- const handleSureAction = async () => {
- setShow(false);
- await voiceCloneConfirm(voiceList[voiceIndex].voiceName!);
- fetchVoiceList();
- };
- const handleRecloneAction = () => {
- setPopupType("reclone");
- setShow(true);
- };
- // 声音录制完成
- const onRecordEnd = (r: string) => {
- console.log("onRecordEnd:", r);
- fetchVoiceList();
- };
- const stopTimer = () => {
- if (intervalRef.current !== null) {
- clearTimeout(intervalRef.current);
- intervalRef.current = null;
- }
- };
- const calcRemainCloneNum = () => {
- if (appConfig?.maxCloneNum) {
- const clonedNum = voiceList.filter(
- (item) =>
- item.status === CloneVoiceStatus.CloneVoiceStatusSuccess ||
- item.status === CloneVoiceStatus.CloneVoiceStatusUnconfirmed,
- ).length;
- const remainNum = appConfig.maxCloneNum - clonedNum;
- return remainNum < 0 ? 0 : remainNum;
- }
- return voiceList.length;
- };
- const initPage = async (profileId: string) => {
- await fetchCharacter(profileId);
- fetchVoiceCloneHistory(profileId);
- character?.voice && setVoiceName(character.voice);
- };
- useEffect(() => {
- profileId && initPage(profileId);
- }, [profileId]);
- // 清除定时器
- useEffect(() => {
- return () => {
- stopTimer();
- };
- }, []);
- return (
- <View className={`flex flex-col`}>
- <View className="flex-1">
- {/* <VoicePlayerBar
- ref={playerRef}
- voiceName={voiceName}
- voiceNameText={createVoiceNameText(voiceName)}
- /> */}
- {renderCloneList()}
- </View>
- <PopupContainer show={show} setShow={setShow}>
- {(popupType == "clone" || popupType == "reclone") && (
- <PopupRecorder
- onRecordEnd={onRecordEnd}
- show={show}
- setShow={setShow}
- setCloneStatus={(status) => handleCloneStatus(status)}
- voiceName={
- popupType == "reclone" ? voiceList[voiceIndex].voiceName! : ""
- }
- ></PopupRecorder>
- )}
- {popupType == "try" && (
- <PopupTryout
- show={show}
- onSure={handleSureAction}
- onReclone={handleRecloneAction}
- voiceName={voiceList[voiceIndex].voiceName!}
- showName={getCloneVoiceIdentifier(voiceIndex + 1)}
- ></PopupTryout>
- )}
- </PopupContainer>
- <View
- className={style.addButton}
- onClick={() => {
- setPopupType("clone");
- setShow(true);
- }}
- >
- <View className="button-rounded-big font-medium">
- <View>添加克隆声音</View>
- <View className="font-normal text-12 leading-0">
- (剩{calcRemainCloneNum()}次)
- </View>
- </View>
- </View>
- </View>
- );
- };
|