Browse Source

fix: 声音克隆暂存

clone
leiyuee 2 weeks ago
parent
commit
d98ff726b5
  1. 24
      src/locales/en-US.json
  2. 24
      src/locales/zh-CN.json
  3. 24
      src/locales/zh-TW.json
  4. 152
      src/pages/Voice/components/speech/VoiceSpeechPanel.tsx
  5. 415
      src/pages/Voice/index.scss

24
src/locales/en-US.json

@ -900,6 +900,30 @@
"pages.voice.speech.clone.cardTitle": "Clone your own voice",
"pages.voice.speech.clone.cardDesc": "Tap to record",
"pages.voice.speech.cloneComingSoon": "Voice cloning is not available yet",
"pages.voice.speech.clone.modal.title": "Choose a scene and read aloud",
"pages.voice.speech.clone.modal.desc": "Read a short passage to clone your own voice",
"pages.voice.speech.clone.modal.sceneAria": "Choose reading scene",
"pages.voice.speech.clone.modal.scene.random": "Random",
"pages.voice.speech.clone.modal.scene.vlog": "Vlog",
"pages.voice.speech.clone.modal.scene.dubbing": "Dubbing",
"pages.voice.speech.clone.modal.scene.audiobook": "Audiobook",
"pages.voice.speech.clone.modal.scene.podcast": "Podcast",
"pages.voice.speech.clone.modal.scene.service": "Support",
"pages.voice.speech.clone.modal.scene.education": "Training",
"pages.voice.speech.clone.modal.readText.random": "The subway was especially crowded today, and I was pushed along with the crowd until I could barely lift my hands. When I finally reached my stop and squeezed out, I breathed a sigh of relief. As soon as I walked above ground, the fresh air made me feel awake again.",
"pages.voice.speech.clone.modal.readText.vlog": "I went out today without a plan and just walked along a familiar street. A new coffee shop by the road was quiet, sunlight resting on the windowsill, and suddenly this small moment felt worth recording.",
"pages.voice.speech.clone.modal.readText.dubbing": "Do not look back. The footsteps outside the door are getting closer. We only have one last chance: turn off the light, wait for them to pass, then run out together.",
"pages.voice.speech.clone.modal.readText.audiobook": "The morning mist had not yet lifted, and the mountain road stretched into the distance like a silver thread. The young traveler shouldered a bag and left the town they had known for years.",
"pages.voice.speech.clone.modal.readText.podcast": "Welcome to today's episode. We are talking about how to leave yourself a little quiet time in ordinary days that are already filled with schedules.",
"pages.voice.speech.clone.modal.readText.service": "Hello, I am happy to help. Please provide your order number or linked phone number, and I will check the current progress for you.",
"pages.voice.speech.clone.modal.readText.education": "Please pay attention. Today we are learning the basic property of fractions. When the numerator and denominator are multiplied or divided by the same number, the value of the fraction does not change.",
"pages.voice.speech.clone.modal.recordHint": "Starting recording means you have obtained voice authorization",
"pages.voice.speech.clone.modal.recordComingSoon": "Recording is not available yet",
"pages.voice.speech.clone.modal.agreementRequired": "Please agree to the User Terms and Privacy Policy",
"pages.voice.speech.clone.modal.testTitle": "Preview text",
"pages.voice.speech.clone.modal.testLang": "Chinese - Mandarin",
"pages.voice.speech.clone.modal.testText": "Hello, I am happy to provide voiceover service for you. Choose a voice you like, and let us begin a new audio creation journey together.",
"pages.voice.speech.clone.modal.cost": "15",
"pages.voice.speech.voiceLibrary": "Voice library",
"pages.voice.speech.resetParams": "Reset",
"pages.voice.speech.effectAdjust": "Voice tuning",

24
src/locales/zh-CN.json

@ -900,6 +900,30 @@
"pages.voice.speech.clone.cardTitle": "克隆你的专属声音",
"pages.voice.speech.clone.cardDesc": "点击录制",
"pages.voice.speech.cloneComingSoon": "声音克隆功能尚未接入",
"pages.voice.speech.clone.modal.title": "选择场景并朗读",
"pages.voice.speech.clone.modal.desc": "朗读一段文字,即可克隆你的专属声音",
"pages.voice.speech.clone.modal.sceneAria": "选择朗读场景",
"pages.voice.speech.clone.modal.scene.random": "随机",
"pages.voice.speech.clone.modal.scene.vlog": "Vlog独白",
"pages.voice.speech.clone.modal.scene.dubbing": "影视配音",
"pages.voice.speech.clone.modal.scene.audiobook": "有声读物",
"pages.voice.speech.clone.modal.scene.podcast": "电台博客",
"pages.voice.speech.clone.modal.scene.service": "智能客服",
"pages.voice.speech.clone.modal.scene.education": "教育培训",
"pages.voice.speech.clone.modal.readText.random": "今天的地铁特别挤,被人群推着走,连手都抬不起来。好不容易等到了自己的站,挤出来的时候松了一口气。走到地面上,外面的空气一吹,觉得整个人都清醒了。",
"pages.voice.speech.clone.modal.readText.vlog": "今天出门没有计划,只是沿着熟悉的街道慢慢走。路边新开的咖啡店很安静,阳光落在窗台上,我忽然觉得这样的片刻也很值得记录。",
"pages.voice.speech.clone.modal.readText.dubbing": "别回头,门外的脚步声越来越近了。我们只剩最后一次机会,把灯关掉,等他们走过去,再一起冲出去。",
"pages.voice.speech.clone.modal.readText.audiobook": "清晨的雾气还没有散去,山路像一条银色的线,蜿蜒伸向远方。少年背起行囊,第一次离开了生活多年的小镇。",
"pages.voice.speech.clone.modal.readText.podcast": "欢迎收听今天的节目。我们想聊一聊那些被日程填满的普通日子里,如何给自己留出一点真正安静的时间。",
"pages.voice.speech.clone.modal.readText.service": "您好,很高兴为您服务。请您提供订单编号或绑定手机号,我会尽快帮您查询当前的处理进度。",
"pages.voice.speech.clone.modal.readText.education": "同学们请注意,今天我们要学习的是分数的基本性质。只要分子和分母同时乘以或除以相同的数,分数大小就不会改变。",
"pages.voice.speech.clone.modal.recordHint": "开始录音即表示您已取得声音授权",
"pages.voice.speech.clone.modal.recordComingSoon": "录音功能尚未接入",
"pages.voice.speech.clone.modal.agreementRequired": "请先勾选并同意用户协议和隐私政策",
"pages.voice.speech.clone.modal.testTitle": "试听文本",
"pages.voice.speech.clone.modal.testLang": "中文-普通话",
"pages.voice.speech.clone.modal.testText": "您好,很高兴能为您提供配音服务。选择您感兴趣的音色,让我们一起开启声音创作的奇幻之旅吧。",
"pages.voice.speech.clone.modal.cost": "15",
"pages.voice.speech.voiceLibrary": "音色库",
"pages.voice.speech.resetParams": "参数重置",
"pages.voice.speech.effectAdjust": "音色效果调节",

24
src/locales/zh-TW.json

@ -878,6 +878,30 @@
"pages.voice.speech.clone.cardTitle": "克隆你的專屬聲音",
"pages.voice.speech.clone.cardDesc": "點擊錄製",
"pages.voice.speech.cloneComingSoon": "聲音克隆功能尚未接入",
"pages.voice.speech.clone.modal.title": "選擇場景並朗讀",
"pages.voice.speech.clone.modal.desc": "朗讀一段文字,即可克隆你的專屬聲音",
"pages.voice.speech.clone.modal.sceneAria": "選擇朗讀場景",
"pages.voice.speech.clone.modal.scene.random": "隨機",
"pages.voice.speech.clone.modal.scene.vlog": "Vlog獨白",
"pages.voice.speech.clone.modal.scene.dubbing": "影視配音",
"pages.voice.speech.clone.modal.scene.audiobook": "有聲讀物",
"pages.voice.speech.clone.modal.scene.podcast": "電台博客",
"pages.voice.speech.clone.modal.scene.service": "智能客服",
"pages.voice.speech.clone.modal.scene.education": "教育培訓",
"pages.voice.speech.clone.modal.readText.random": "今天的地鐵特別擠,被人群推著走,連手都抬不起來。好不容易等到了自己的站,擠出來的時候鬆了一口氣。走到地面上,外面的空氣一吹,覺得整個人都清醒了。",
"pages.voice.speech.clone.modal.readText.vlog": "今天出門沒有計劃,只是沿著熟悉的街道慢慢走。路邊新開的咖啡店很安靜,陽光落在窗台上,我忽然覺得這樣的片刻也很值得記錄。",
"pages.voice.speech.clone.modal.readText.dubbing": "別回頭,門外的腳步聲越來越近了。我們只剩最後一次機會,把燈關掉,等他們走過去,再一起衝出去。",
"pages.voice.speech.clone.modal.readText.audiobook": "清晨的霧氣還沒有散去,山路像一條銀色的線,蜿蜒伸向遠方。少年背起行囊,第一次離開了生活多年的小鎮。",
"pages.voice.speech.clone.modal.readText.podcast": "歡迎收聽今天的節目。我們想聊一聊那些被日程填滿的普通日子裡,如何給自己留出一點真正安靜的時間。",
"pages.voice.speech.clone.modal.readText.service": "您好,很高興為您服務。請您提供訂單編號或綁定手機號,我會盡快幫您查詢當前的處理進度。",
"pages.voice.speech.clone.modal.readText.education": "同學們請注意,今天我們要學習的是分數的基本性質。只要分子和分母同時乘以或除以相同的數,分數大小就不會改變。",
"pages.voice.speech.clone.modal.recordHint": "開始錄音即表示您已取得聲音授權",
"pages.voice.speech.clone.modal.recordComingSoon": "錄音功能尚未接入",
"pages.voice.speech.clone.modal.agreementRequired": "請先勾選並同意用戶協議和隱私政策",
"pages.voice.speech.clone.modal.testTitle": "試聽文本",
"pages.voice.speech.clone.modal.testLang": "中文-普通話",
"pages.voice.speech.clone.modal.testText": "您好,很高興能為您提供配音服務。選擇您感興趣的音色,讓我們一起開啟聲音創作的奇幻之旅吧。",
"pages.voice.speech.clone.modal.cost": "15",
"pages.voice.speech.voiceLibrary": "音色庫",
"pages.voice.speech.resetParams": "參數重置",
"pages.voice.speech.effectAdjust": "音色效果調節",

152
src/pages/Voice/components/speech/VoiceSpeechPanel.tsx

@ -26,6 +26,7 @@ import type { AssetAudioPlayerHandle } from "@/components/AssetAudioPlayer";
import { getVoiceLibraryItemCoverUrl, getVoiceLibraryItemDescription, getVoiceLibraryItemTitle } from "@/components/VoiceLibraryPicker";
import { openAssetDetailsDialog, openVoiceLibraryPickerDialog } from "@/dialog";
import { createAssetDetailNavigation } from "@/dialog/asset-details/navigation";
import { lockLayoutContentScroll, unlockLayoutContentScroll } from "@/layouts/layoutContentModalMount";
import { ensureSufficientPoints } from "@/layouts/globalPromptPanel/composerCommon";
import {
applyRangeParamValues,
@ -103,6 +104,18 @@ const SPEECH_EMOTION_OPTIONS = [
type SpeechEmotionKey = (typeof SPEECH_EMOTION_OPTIONS)[number];
const SPEECH_CLONE_SCENE_KEYS = [
"random",
"vlog",
"dubbing",
"audiobook",
"podcast",
"service",
"education",
] as const;
type SpeechCloneSceneKey = (typeof SPEECH_CLONE_SCENE_KEYS)[number];
// ---------------------------------------------------------------------------
// Modal tag helpers
// ---------------------------------------------------------------------------
@ -1572,6 +1585,140 @@ function SpeechShortcutTooltip({ line1, line2, children }: SpeechShortcutTooltip
);
}
type VoiceSpeechCloneModalProps = {
onClose: () => void;
};
function VoiceSpeechCloneModal({ onClose }: VoiceSpeechCloneModalProps) {
const { t } = useTranslation();
const [activeScene, setActiveScene] = useState<SpeechCloneSceneKey>("random");
const [agreedToCloneTerms, setAgreedToCloneTerms] = useState(true);
useEffect(() => {
lockLayoutContentScroll();
return () => unlockLayoutContentScroll();
}, []);
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") onClose();
};
window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown);
}, [onClose]);
const handleRecord = () => {
if (!agreedToCloneTerms) {
Message.error(t("pages.voice.speech.clone.modal.agreementRequired"));
return;
}
Message.info(t("pages.voice.speech.clone.modal.recordComingSoon"));
};
return createPortal(
<div className="voicePage__speechCloneModal" role="presentation">
<button type="button" className="voicePage__speechCloneModalMask" aria-label={t("dialog.common.closeAria")} onClick={onClose} />
<div
className="voicePage__speechCloneModalDialog"
role="dialog"
aria-modal="true"
aria-labelledby="voice-speech-clone-modal-title"
onClick={(event) => event.stopPropagation()}
>
<button type="button" className="voicePage__speechCloneModalClose" aria-label={t("dialog.common.closeAria")} onClick={onClose}>
<img src={new URL("@/assets/images/ic_close.png", import.meta.url).href} alt="" aria-hidden />
</button>
<section className="voicePage__speechCloneModalSection">
<h2 className="voicePage__speechCloneModalTitle" id="voice-speech-clone-modal-title">
{t("pages.voice.speech.clone.modal.title")}
</h2>
<p className="voicePage__speechCloneModalDesc">{t("pages.voice.speech.clone.modal.desc")}</p>
<div className="voicePage__speechCloneSceneList" role="tablist" aria-label={t("pages.voice.speech.clone.modal.sceneAria")}>
{SPEECH_CLONE_SCENE_KEYS.map((key) => (
<button
key={key}
type="button"
role="tab"
aria-selected={activeScene === key}
className={classNames("voicePage__speechCloneScene", activeScene === key && "voicePage__speechCloneScene_active")}
onClick={() => setActiveScene(key)}
>
{t(`pages.voice.speech.clone.modal.scene.${key}`)}
</button>
))}
</div>
<div className="voicePage__speechCloneReadBox">
<p className="voicePage__speechCloneReadText">{t(`pages.voice.speech.clone.modal.readText.${activeScene}`)}</p>
<button type="button" className="voicePage__speechCloneRecordBtn" onClick={handleRecord}>
<span className="voicePage__speechCloneRecordIcon" aria-hidden>
<span className="voicePage__speechCloneRecordMic" />
</span>
<span className="voicePage__speechCloneRecordText">{t("pages.voice.speech.clone.modal.recordHint")}</span>
</button>
</div>
</section>
<section className="voicePage__speechCloneTestBox">
<div className="voicePage__speechCloneTestHead">
<h3 className="voicePage__speechCloneTestTitle">{t("pages.voice.speech.clone.modal.testTitle")}</h3>
<button type="button" className="voicePage__speechCloneTestLang">
<span>{t("pages.voice.speech.clone.modal.testLang")}</span>
<img src={SPEECH_ASSETS.chevron} alt="" aria-hidden />
</button>
</div>
<p className="voicePage__speechCloneTestText">{t("pages.voice.speech.clone.modal.testText")}</p>
<div className="voicePage__speechCloneTestFooter">
<span>{t("pages.voice.charCount", { current: 0, max: SPEECH_PROMPT_MAX_LENGTH })}</span>
<button type="button" aria-label={t("pages.voice.clear")}>
<img src={SPEECH_ASSETS.clear} alt="" aria-hidden />
</button>
</div>
</section>
<footer className="voicePage__speechCloneModalFooter">
<label className="voicePage__speechCloneAgreement">
<input
type="checkbox"
className="voicePage__speechCloneAgreementInput"
checked={agreedToCloneTerms}
onChange={(event) => setAgreedToCloneTerms(event.target.checked)}
/>
<span className="voicePage__speechCloneAgreementText">
{t("pages.voice.speech.agreementPrefix")}
<button
type="button"
onClick={() => {
void openLatestPrivacyPolicyLink(PRIVACY_POLICY_KEY_USER_TERMS);
}}
>
{t("app.user_terms")}
</button>
{t("pages.voice.speech.agreementMiddle")}
<button
type="button"
onClick={() => {
void openLatestPrivacyPolicyLink(PRIVACY_POLICY_KEY_PRIVACY_POLICY);
}}
>
{t("app.privacy_policy")}
</button>
</span>
</label>
<button type="button" className="voicePage__speechCloneSubmit" disabled={!agreedToCloneTerms} onClick={handleRecord}>
<img src={SPEECH_ASSETS.generate} alt="" aria-hidden />
<span>{t("pages.voice.speech.clone.modal.cost")}</span>
</button>
</footer>
</div>
</div>,
document.body,
);
}
export function VoiceSpeechPanel() {
const { t } = useTranslation();
const speechModuleRef = useRef<HTMLDivElement>(null);
@ -1596,6 +1743,7 @@ export function VoiceSpeechPanel() {
const [speechLangMenuOpen, setSpeechLangMenuOpen] = useState(false);
const [selectedSpeechEmotion, setSelectedSpeechEmotion] = useState<SpeechEmotionKey | null>(null);
const [speechEmotionMenuOpen, setSpeechEmotionMenuOpen] = useState(false);
const [cloneModalOpen, setCloneModalOpen] = useState(false);
const promptCount = chatPrompt.length;
const modelParamFields = useMemo(() => {
@ -2128,7 +2276,7 @@ export function VoiceSpeechPanel() {
<button
type="button"
className="voicePage__speechCloneCard"
onClick={() => Message.info(t("pages.voice.speech.cloneComingSoon"))}
onClick={() => setCloneModalOpen(true)}
>
<img className="voicePage__speechCloneIcon" src={SPEECH_ASSETS.clone} alt="" aria-hidden />
<div className="voicePage__speechCloneText">
@ -2180,6 +2328,8 @@ export function VoiceSpeechPanel() {
<VoiceSpeechHistoryPanel refreshNonce={historyRefreshNonce} playerPortalRef={speechModuleRef} />
)}
</aside>
{cloneModalOpen ? <VoiceSpeechCloneModal onClose={() => setCloneModalOpen(false)} /> : null}
</div>
);
}

415
src/pages/Voice/index.scss

@ -2591,6 +2591,378 @@ $upload-crop-selection-rail-height: 3px;
color: #999999;
}
.voicePage__speechCloneModal {
position: fixed;
inset: 0;
z-index: 10040;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
}
.voicePage__speechCloneModalMask {
position: absolute;
inset: 0;
margin: 0;
padding: 0;
border: none;
background: rgba(0, 0, 0, 0.36);
cursor: pointer;
}
.voicePage__speechCloneModalDialog {
position: relative;
width: min(873px, calc(100vw - 48px));
max-height: calc(100vh - 48px);
padding: 20px;
border-radius: 9px;
background: #ffffff;
overflow-y: auto;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.18);
@include hide-scrollbar;
}
.voicePage__speechCloneModalClose {
position: absolute;
top: 14px;
right: 14px;
z-index: 1;
width: 28px;
height: 28px;
margin: 0;
padding: 0;
border: none;
border-radius: 9px;
background: #f9f9f9;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
img {
width: 12px;
height: 12px;
object-fit: contain;
}
}
.voicePage__speechCloneModalSection {
display: flex;
flex-direction: column;
}
.voicePage__speechCloneModalTitle {
margin: 0;
padding-right: 34px;
font-family: $font;
font-size: 16px;
font-weight: 500;
line-height: normal;
color: #333333;
}
.voicePage__speechCloneModalDesc {
margin: 12px 0 0;
font-family: $font;
font-size: 16px;
font-weight: 500;
line-height: normal;
color: #999999;
}
.voicePage__speechCloneSceneList {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
margin-top: 20px;
}
.voicePage__speechCloneScene {
min-width: 64px;
height: 30px;
margin: 0;
padding: 0 12px;
border: none;
border-radius: 9px;
background: #f9f9f9;
font-family: $font;
font-size: 12px;
font-weight: 500;
line-height: 18px;
color: #666666;
cursor: pointer;
}
.voicePage__speechCloneScene_active {
background: linear-gradient(90deg, #f0f0f0 0%, #d6ceef 100%);
color: #333333;
}
.voicePage__speechCloneReadBox {
position: relative;
min-height: 402px;
margin-top: 15px;
padding: 20px;
border-radius: 9px;
background: #f0f0f0;
overflow: hidden;
}
.voicePage__speechCloneReadText {
max-width: 710px;
margin: 0;
font-family: $font;
font-size: 16px;
font-weight: 500;
line-height: 1.6;
color: #333333;
}
.voicePage__speechCloneRecordBtn {
position: absolute;
left: 50%;
bottom: 26px;
transform: translateX(-50%);
width: min(470px, calc(100% - 40px));
min-height: 132px;
margin: 0;
padding: 22px 32px;
border: none;
border-radius: 100px;
background: #ffffff;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 14px;
cursor: pointer;
}
.voicePage__speechCloneRecordIcon {
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(180deg, #8a7fae 0%, #5c3bc7 100%);
display: inline-flex;
align-items: center;
justify-content: center;
}
.voicePage__speechCloneRecordMic {
position: relative;
width: 16px;
height: 28px;
border: 3px solid #ffffff;
border-radius: 10px;
&::before {
content: "";
position: absolute;
left: 50%;
bottom: -11px;
width: 28px;
height: 17px;
border: 3px solid #ffffff;
border-top: none;
border-radius: 0 0 16px 16px;
transform: translateX(-50%);
}
&::after {
content: "";
position: absolute;
left: 50%;
bottom: -18px;
width: 24px;
height: 3px;
border-radius: 2px;
background: #ffffff;
transform: translateX(-50%);
}
}
.voicePage__speechCloneRecordText {
font-family: $font;
font-size: 14px;
font-weight: 500;
line-height: normal;
color: #666666;
text-align: center;
}
.voicePage__speechCloneTestBox {
min-height: 239px;
margin-top: 20px;
padding: 24px 20px 18px;
border-radius: 24px;
background: #f9f9f9;
display: flex;
flex-direction: column;
}
.voicePage__speechCloneTestHead {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.voicePage__speechCloneTestTitle {
margin: 0;
font-family: $font;
font-size: 16px;
font-weight: 500;
line-height: normal;
color: #333333;
}
.voicePage__speechCloneTestLang {
display: inline-flex;
align-items: center;
gap: 6px;
height: 27px;
margin: 0;
padding: 0 6px;
border: none;
border-radius: 9px;
background: #f9f9f9;
font-family: $font;
font-size: 14px;
font-weight: 500;
line-height: 26px;
color: #666666;
cursor: pointer;
img {
width: 15px;
height: 15px;
transform: rotate(90deg);
object-fit: contain;
}
}
.voicePage__speechCloneTestText {
margin: 24px 0 0;
max-width: 530px;
font-family: $font;
font-size: 16px;
font-weight: 500;
line-height: 1.5;
color: #999999;
}
.voicePage__speechCloneTestFooter {
margin-top: auto;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10px;
font-family: $font;
font-size: 16px;
font-weight: 500;
line-height: normal;
color: #999999;
button {
width: 31px;
height: 31px;
margin: 0;
padding: 0;
border: none;
border-radius: 9px;
background: #ffffff;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
img {
width: 15px;
height: 15px;
object-fit: contain;
}
}
.voicePage__speechCloneModalFooter {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 20px;
margin-top: 15px;
}
.voicePage__speechCloneAgreement {
flex: 1 1 auto;
min-width: 0;
display: flex;
align-items: flex-start;
gap: 5px;
cursor: pointer;
}
.voicePage__speechCloneAgreementInput {
flex: 0 0 auto;
width: 15px;
height: 15px;
margin: 2px 0 0;
accent-color: #5c3bc7;
}
.voicePage__speechCloneAgreementText {
font-family: $font;
font-size: 12px;
font-weight: 400;
line-height: 22px;
color: #666666;
button {
margin: 0;
padding: 0;
border: none;
background: transparent;
cursor: pointer;
font: inherit;
color: #333333;
text-decoration: underline;
}
}
.voicePage__speechCloneSubmit {
flex: 0 0 auto;
width: 213px;
height: 45px;
margin: 50px 0 0;
padding: 0;
border: none;
border-radius: 100px;
background: #999999;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 10px;
font-family: $font;
font-size: 20px;
font-weight: 500;
line-height: normal;
color: #ffffff;
cursor: pointer;
img {
width: 25px;
height: 25px;
object-fit: contain;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
.voicePage__speechVoicePickerTrigger {
width: 100%;
}
@ -3852,3 +4224,46 @@ $upload-crop-selection-rail-height: 3px;
right: 14px;
}
}
@media (max-width: 700px) {
.voicePage__speechCloneModal {
align-items: flex-start;
padding: 16px;
}
.voicePage__speechCloneModalDialog {
width: calc(100vw - 32px);
max-height: calc(100vh - 32px);
padding: 16px;
}
.voicePage__speechCloneModalDesc,
.voicePage__speechCloneReadText,
.voicePage__speechCloneTestText {
font-size: 14px;
}
.voicePage__speechCloneReadBox {
min-height: 340px;
}
.voicePage__speechCloneRecordBtn {
min-height: 112px;
padding: 18px 22px;
}
.voicePage__speechCloneRecordIcon {
width: 52px;
height: 52px;
}
.voicePage__speechCloneModalFooter {
flex-direction: column;
align-items: stretch;
}
.voicePage__speechCloneSubmit {
width: 100%;
margin-top: 0;
}
}

Loading…
Cancel
Save