feat: validate tts form items

This commit is contained in:
jialin
2026-04-09 12:14:36 +08:00
committed by jialin
parent 78bf76e965
commit ae3148a86f
12 changed files with 107 additions and 90 deletions
+3 -1
View File
@@ -175,5 +175,7 @@ export default {
'Transcript of Reference Audio (for ICL mode)', 'Transcript of Reference Audio (for ICL mode)',
'playground.params.refAudio.vectorMode': 'playground.params.refAudio.vectorMode':
'Use Speaker Embedding Only (no ICL)', 'Use Speaker Embedding Only (no ICL)',
'playground.params.streamMode': 'Enable Streaming' 'playground.params.streamMode': 'Enable Streaming',
'playground.speech.rules.refAudio':
'Either the reference audio transcript or speaker embedding must be set.'
}; };
+4 -1
View File
@@ -178,7 +178,9 @@ export default {
'Transcript of Reference Audio (for ICL mode)', 'Transcript of Reference Audio (for ICL mode)',
'playground.params.refAudio.vectorMode': 'playground.params.refAudio.vectorMode':
'Use Speaker Embedding Only (no ICL)', 'Use Speaker Embedding Only (no ICL)',
'playground.params.streamMode': 'Enable Streaming' 'playground.params.streamMode': 'Enable Streaming',
'playground.speech.rules.refAudio':
'Either the reference audio transcript or speaker embedding must be set.'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -191,4 +193,5 @@ export default {
// 7. 'playground.params.duration': 'Duration (seconds)', // 7. 'playground.params.duration': 'Duration (seconds)',
// 8. 'playground.params.resolution': 'Resolution' // 8. 'playground.params.resolution': 'Resolution'
// 9. 'playground.params.streamMode': 'Enable Streaming' // 9. 'playground.params.streamMode': 'Enable Streaming'
// 10. 'playground.speech.rules.refAudio': 'Either the reference audio transcript or speaker embedding must be set.'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+5 -2
View File
@@ -172,7 +172,9 @@ export default {
'Transcript of Reference Audio (for ICL mode)', 'Transcript of Reference Audio (for ICL mode)',
'playground.params.refAudio.vectorMode': 'playground.params.refAudio.vectorMode':
'Use Speaker Embedding Only (no ICL)', 'Use Speaker Embedding Only (no ICL)',
'playground.params.streamMode': 'Enable Streaming' 'playground.params.streamMode': 'Enable Streaming',
'playground.speech.rules.refAudio':
'Either the reference audio transcript or speaker embedding must be set.'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -181,5 +183,6 @@ export default {
// 3. 'playground.uploadImage.url.button': 'Add Image from URL', // 3. 'playground.uploadImage.url.button': 'Add Image from URL',
// 4. 'playground.params.duration': 'Duration (seconds)', // 4. 'playground.params.duration': 'Duration (seconds)',
// 5. 'playground.params.resolution': 'Resolution', // 5. 'playground.params.resolution': 'Resolution',
// 6. 'playground.params.streamMode': 'Enable Streaming' // 6. 'playground.params.streamMode': 'Enable Streaming',
// 7. 'playground.speech.rules.refAudio': 'Either the reference audio transcript or speaker embedding must be set.'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+15 -10
View File
@@ -132,7 +132,8 @@ export default {
'Sınıflandırıcısız rehberlik ölçeği, görselin isteme bağlılığını etkiler.', 'Sınıflandırıcısız rehberlik ölçeği, görselin isteme bağlılığını etkiler.',
'playground.image.strength.tip': 'playground.image.strength.tip':
'Değer ne kadar yüksek olursa, orijinal görsel üzerindeki değişiklik o kadar büyük olur.', 'Değer ne kadar yüksek olursa, orijinal görsel üzerindeki değişiklik o kadar büyük olur.',
'playground.image.edit.tips': 'Yüklemek için görseli tıklayın veya bu alana sürükleyin', 'playground.image.edit.tips':
'Yüklemek için görseli tıklayın veya bu alana sürükleyin',
'playground.image.saveMask': 'Maskeyi Kaydet', 'playground.image.saveMask': 'Maskeyi Kaydet',
'playground.image.negativeMask': 'Maskeyi Ters Çevir', 'playground.image.negativeMask': 'Maskeyi Ters Çevir',
'playground.image.brushSize': 'Fırça Boyutu', 'playground.image.brushSize': 'Fırça Boyutu',
@@ -154,14 +155,13 @@ export default {
'playground.model.noavailable.tips2': 'playground.model.noavailable.tips2':
'Beklenen model görünmüyorsa, çalıştığından ve doğru kategoride olduğundan emin olun. Kategori yanlışsa, modelin ayarlarından manuel olarak düzeltebilirsiniz.', 'Beklenen model görünmüyorsa, çalıştığından ve doğru kategoride olduğundan emin olun. Kategori yanlışsa, modelin ayarlarından manuel olarak düzeltebilirsiniz.',
'playground.rerank.query.validate': 'Sorgu gereklidir.', 'playground.rerank.query.validate': 'Sorgu gereklidir.',
'playground.image.generate.error': 'playground.image.generate.error': 'Bir sorun oluştu. Görsel oluşturulamadı.',
'Bir sorun oluştu. Görsel oluşturulamadı.',
'playground.uploadfile.sizeError': 'playground.uploadfile.sizeError':
'Dosya boyutu sınırı aşıyor. İzin verilen maksimum: {size}.', 'Dosya boyutu sınırı aşıyor. İzin verilen maksimum: {size}.',
'playground.uploadImage.url.invalid': 'playground.uploadImage.url.invalid':
'Lütfen doğrudan bir görsel URL\'si girin (örn. https://.../image.png). İptal etmek için ESC tuşuna basın.', "Lütfen doğrudan bir görsel URL'si girin (örn. https://.../image.png). İptal etmek için ESC tuşuna basın.",
'playground.uploadImage.url.holder': 'Görsel URL\'si girin', 'playground.uploadImage.url.holder': "Görsel URL'si girin",
'playground.uploadImage.url.button': 'URL\'den Görsel Ekle', 'playground.uploadImage.url.button': "URL'den Görsel Ekle",
'playground.params.duration': 'Süre (saniye)', 'playground.params.duration': 'Süre (saniye)',
'playground.params.resolution': 'Çözünürlük', 'playground.params.resolution': 'Çözünürlük',
'playground.params.taskType': 'Görev Türü', 'playground.params.taskType': 'Görev Türü',
@@ -170,10 +170,15 @@ export default {
'playground.params.maxTokens': 'Maksimum Yeni Token', 'playground.params.maxTokens': 'Maksimum Yeni Token',
'playground.params.refAudio': 'Referans Ses', 'playground.params.refAudio': 'Referans Ses',
'playground.params.refAudio.tips': 'playground.params.refAudio.tips':
'Referans ses URL\'si girin veya bir ses dosyası yükleyin.', "Referans ses URL'si girin veya bir ses dosyası yükleyin.",
'playground.params.refAudio.text': 'playground.params.refAudio.text': 'Referans Ses Transkripti (ICL modu için)',
'Referans Ses Transkripti (ICL modu için)',
'playground.params.refAudio.vectorMode': 'playground.params.refAudio.vectorMode':
'Yalnızca Konuşmacı Gömme Kullan (ICL yok)', 'Yalnızca Konuşmacı Gömme Kullan (ICL yok)',
'playground.params.streamMode': 'Akışı Etkinleştir' 'playground.params.streamMode': 'Akışı Etkinleştir',
'playground.speech.rules.refAudio':
'Either the reference audio transcript or speaker embedding must be set.'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'playground.speech.rules.refAudio': 'Either the reference audio transcript or speaker embedding must be set.'
// ========== End of To-Do List ==========
+3 -1
View File
@@ -167,5 +167,7 @@ export default {
'playground.params.refAudio.text': '参考音频文本(用于上下文学习模式)', 'playground.params.refAudio.text': '参考音频文本(用于上下文学习模式)',
'playground.params.refAudio.vectorMode': 'playground.params.refAudio.vectorMode':
'仅使用说话人嵌入(不使用上下文学习)', '仅使用说话人嵌入(不使用上下文学习)',
'playground.params.streamMode': '启用流式输出' 'playground.params.streamMode': '启用流式输出',
'playground.speech.rules.refAudio':
'设置参考音频文本或仅使用说话人嵌入的任意一项。'
}; };
@@ -35,6 +35,7 @@ const ModelSelect: React.FC<{
]} ]}
> >
<SealSelect <SealSelect
required
description={intl.formatMessage({ description={intl.formatMessage({
id: 'playground.model.noavailable.tips2' id: 'playground.model.noavailable.tips2'
})} })}
@@ -1,4 +1,5 @@
import CheckboxField from '@/components/seal-form/checkbox-field'; import CheckboxField from '@/components/seal-form/checkbox-field';
import InputNumber from '@/components/seal-form/input-number';
import SealInput from '@/components/seal-form/seal-input'; import SealInput from '@/components/seal-form/seal-input';
import UploadAudio from '@/components/upload-audio'; import UploadAudio from '@/components/upload-audio';
import useAppUtils from '@/hooks/use-app-utils'; import useAppUtils from '@/hooks/use-app-utils';
@@ -6,7 +7,7 @@ import { convertFileToBase64 } from '@/utils/load-audio-file';
import { CloseCircleFilled } from '@ant-design/icons'; import { CloseCircleFilled } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Form } from 'antd'; import { Form } from 'antd';
import React from 'react'; import React, { useEffect } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { useFormContext } from '../../config/form-context'; import { useFormContext } from '../../config/form-context';
@@ -70,24 +71,74 @@ const TTSAdvanceConfig: React.FC = () => {
); );
}; };
const atLeastOneValidator = (relateField: string) => () => ({
validator(rule: any, value: string) {
const type = form.getFieldValue('task_type');
const relateFieldValue = form.getFieldValue(relateField);
if (type !== 'Base') return Promise.resolve();
if (value || relateFieldValue) return Promise.resolve();
return Promise.reject(
new Error(
intl.formatMessage({ id: 'playground.speech.rules.refAudio' })
)
);
}
});
useEffect(() => {
if (taskType === 'Base') {
form.setFieldsValue({
x_vector_only_mode: true
});
onValuesChange?.(
{ x_vector_only_mode: true },
{ ...form.getFieldsValue(), x_vector_only_mode: true }
);
}
}, [taskType]);
return ( return (
<> <>
<Form.Item
name="instructions"
rules={[
{
required: taskType === 'VoiceDesign',
message: getRuleMessage('input', 'playground.params.instructions')
}
]}
>
<SealInput.Input
allowClear
required={taskType === 'VoiceDesign'}
description={intl.formatMessage({
id: 'playground.params.instructions.tips'
})}
label={intl.formatMessage({ id: 'playground.params.instructions' })}
></SealInput.Input>
</Form.Item>
<Form.Item
name="max_new_tokens"
getValueProps={(value) => ({ value: value || null })}
>
<InputNumber
min={0}
step={1}
max={4096}
label={intl.formatMessage({ id: 'playground.params.maxTokens' })}
></InputNumber>
</Form.Item>
<Container> <Container>
<Form.Item <Form.Item
name="ref_audio" name="ref_audio"
getValueProps={(value) => ({ value: fileName ? fileName : value })} getValueProps={(value) => ({ value: fileName ? fileName : value })}
rules={[
{
required: taskType === 'Base',
message: getRuleMessage('input', 'playground.params.refAudio')
}
]}
dependencies={['task_type']} dependencies={['task_type']}
> >
<SealInput.Input <SealInput.Input
allowClear allowClear
readOnly={!!fileName} readOnly={!!fileName}
required={taskType === 'Base'}
suffix={ suffix={
<SuffixWrapper> <SuffixWrapper>
{fileName ? ( {fileName ? (
@@ -110,7 +161,12 @@ const TTSAdvanceConfig: React.FC = () => {
></SealInput.Input> ></SealInput.Input>
</Form.Item> </Form.Item>
</Container> </Container>
<Form.Item name="ref_text"> <Form.Item
name="ref_text"
style={{ marginBottom: 12 }}
dependencies={['task_type', 'x_vector_only_mode']}
rules={[atLeastOneValidator('x_vector_only_mode')]}
>
<SealInput.TextArea <SealInput.TextArea
allowClear allowClear
scaleSize={true} scaleSize={true}
@@ -118,9 +174,11 @@ const TTSAdvanceConfig: React.FC = () => {
></SealInput.TextArea> ></SealInput.TextArea>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
dependencies={['task_type', 'ref_text']}
style={{ marginBottom: 12 }}
name="x_vector_only_mode" name="x_vector_only_mode"
valuePropName="checked" valuePropName="checked"
style={{ marginBottom: 8 }} rules={[atLeastOneValidator('ref_text')]}
> >
<CheckboxField <CheckboxField
label={intl.formatMessage({ label={intl.formatMessage({
@@ -43,58 +43,5 @@ export const TTSAdvancedParamsConfig: ParamsSchema[] = [
required: false required: false
} }
] ]
},
{
type: 'Input',
name: 'instructions',
label: {
text: 'playground.params.instructions',
isLocalized: true
},
description: {
text: 'playground.params.instructions.tips',
isLocalized: true
},
attrs: {
allowClear: true
},
initAttrs: (meta: any) => {
return {
options: _.map(meta?.voices || [], (item: string) => ({
label: item,
value: item
}))
};
},
rules: [
{
required: false
}
]
},
{
type: 'InputNumber',
name: 'max_new_tokens',
label: {
text: 'playground.params.maxTokens',
isLocalized: true
},
attrs: {
step: 1,
min: 0,
max: 4096
},
formItemAttrs: {
getValueProps: (value: number) => {
return {
value: value || null
};
}
},
rules: [
{
required: false
}
]
} }
]; ];
+2 -6
View File
@@ -189,9 +189,6 @@ const GroundTTS: React.FC<MessageProps> = forwardRef((props, ref) => {
const submitMessage = async (current?: { role: string; content: string }) => { const submitMessage = async (current?: { role: string; content: string }) => {
try { try {
await formRef.current?.form.validateFields();
if (!parameters.model) return;
setLoading(true); setLoading(true);
setMessageId(); setMessageId();
setTokenResult(null); setTokenResult(null);
@@ -207,7 +204,6 @@ const GroundTTS: React.FC<MessageProps> = forwardRef((props, ref) => {
}; };
setParams(params); setParams(params);
console.log('submitMessage params:', streamTTS.isPlaying);
// Choose stream or non-stream based on parameters // Choose stream or non-stream based on parameters
if (parameters.stream) { if (parameters.stream) {
@@ -230,7 +226,6 @@ const GroundTTS: React.FC<MessageProps> = forwardRef((props, ref) => {
await nonStreamTTS.generate(params); await nonStreamTTS.generate(params);
} }
} catch (error: any) { } catch (error: any) {
console.log('error:', error);
setPlayingStream(false); setPlayingStream(false);
setTokenResult({ setTokenResult({
error: true, error: true,
@@ -248,7 +243,7 @@ const GroundTTS: React.FC<MessageProps> = forwardRef((props, ref) => {
}; };
const handleSendMessage = (message: Omit<MessageItem, 'uid'>) => { const handleSendMessage = (message: Omit<MessageItem, 'uid'>) => {
submitMessage(message); formRef.current?.form.submit();
}; };
const handleCloseViewCode = () => { const handleCloseViewCode = () => {
@@ -353,6 +348,7 @@ const GroundTTS: React.FC<MessageProps> = forwardRef((props, ref) => {
<TTSDataForm <TTSDataForm
ref={formRef} ref={formRef}
modelList={modelList} modelList={modelList}
onFinish={submitMessage}
updatateParams={updatateParams} updatateParams={updatateParams}
/> />
</RightContainer> </RightContainer>
+1 -1
View File
@@ -10,7 +10,7 @@
flex-direction: column; flex-direction: column;
position: relative; position: relative;
height: calc( height: calc(
100vh - @HEADER_HEIGHT - var(--page-content-padding) - 100vh - @HEADER_HEIGHT - 4px - var(--page-content-padding) -
var(--page-content-padding) - var(--page-header-height) var(--page-content-padding) - var(--page-header-height)
); );
width: calc(100% - 390px); width: calc(100% - 390px);
@@ -6,8 +6,8 @@
border-left: 1px solid var(--ant-color-split); border-left: 1px solid var(--ant-color-split);
transition: width 0.3s ease; transition: width 0.3s ease;
height: calc( height: calc(
100vh - @HEADER_HEIGHT - var(--page-content-padding) - 100vh - @HEADER_HEIGHT - 2px - var(--page-content-padding) -
var(--page-header-height) var(--page-content-padding) - var(--page-header-height)
); );
padding-top: 16px; padding-top: 16px;
display: flex; display: flex;
+3 -3
View File
@@ -41,7 +41,7 @@
overflow: hidden; overflow: hidden;
height: calc( height: calc(
100vh - var(--page-header-height) - var(--page-content-padding) - 100vh - var(--page-header-height) - var(--page-content-padding) -
@HEADER_HEIGHT var(--page-content-padding) - @HEADER_HEIGHT
); );
.collapse { .collapse {
@@ -74,8 +74,8 @@
.ant-divider { .ant-divider {
margin: 0; margin: 0;
height: calc( height: calc(
100vh - var(--page-header-height) - var(--page-content-padding) - 100vh - var(--page-header-height) - 2px - var(--page-content-padding) -
@HEADER_HEIGHT var(--page-content-padding) - @HEADER_HEIGHT
); );
} }
} }