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)',
'playground.params.refAudio.vectorMode':
'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)',
'playground.params.refAudio.vectorMode':
'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) ==========
@@ -191,4 +193,5 @@ export default {
// 7. 'playground.params.duration': 'Duration (seconds)',
// 8. 'playground.params.resolution': 'Resolution'
// 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 ==========
+5 -2
View File
@@ -172,7 +172,9 @@ export default {
'Transcript of Reference Audio (for ICL mode)',
'playground.params.refAudio.vectorMode':
'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) ==========
@@ -181,5 +183,6 @@ export default {
// 3. 'playground.uploadImage.url.button': 'Add Image from URL',
// 4. 'playground.params.duration': 'Duration (seconds)',
// 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 ==========
+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.',
'playground.image.strength.tip':
'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.negativeMask': 'Maskeyi Ters Çevir',
'playground.image.brushSize': 'Fırça Boyutu',
@@ -154,14 +155,13 @@ export default {
'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.',
'playground.rerank.query.validate': 'Sorgu gereklidir.',
'playground.image.generate.error':
'Bir sorun oluştu. Görsel oluşturulamadı.',
'playground.image.generate.error': 'Bir sorun oluştu. Görsel oluşturulamadı.',
'playground.uploadfile.sizeError':
'Dosya boyutu sınırı aşıyor. İzin verilen maksimum: {size}.',
'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.',
'playground.uploadImage.url.holder': 'Görsel URL\'si girin',
'playground.uploadImage.url.button': 'URL\'den Görsel Ekle',
"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.button': "URL'den Görsel Ekle",
'playground.params.duration': 'Süre (saniye)',
'playground.params.resolution': 'Çözünürlük',
'playground.params.taskType': 'Görev Türü',
@@ -170,10 +170,15 @@ export default {
'playground.params.maxTokens': 'Maksimum Yeni Token',
'playground.params.refAudio': 'Referans Ses',
'playground.params.refAudio.tips':
'Referans ses URL\'si girin veya bir ses dosyası yükleyin.',
'playground.params.refAudio.text':
'Referans Ses Transkripti (ICL modu için)',
"Referans ses URL'si girin veya bir ses dosyası yükleyin.",
'playground.params.refAudio.text': 'Referans Ses Transkripti (ICL modu için)',
'playground.params.refAudio.vectorMode':
'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.vectorMode':
'仅使用说话人嵌入(不使用上下文学习)',
'playground.params.streamMode': '启用流式输出'
'playground.params.streamMode': '启用流式输出',
'playground.speech.rules.refAudio':
'设置参考音频文本或仅使用说话人嵌入的任意一项。'
};
@@ -35,6 +35,7 @@ const ModelSelect: React.FC<{
]}
>
<SealSelect
required
description={intl.formatMessage({
id: 'playground.model.noavailable.tips2'
})}
@@ -1,4 +1,5 @@
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 UploadAudio from '@/components/upload-audio';
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 { useIntl } from '@umijs/max';
import { Form } from 'antd';
import React from 'react';
import React, { useEffect } from 'react';
import styled from 'styled-components';
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 (
<>
<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>
<Form.Item
name="ref_audio"
getValueProps={(value) => ({ value: fileName ? fileName : value })}
rules={[
{
required: taskType === 'Base',
message: getRuleMessage('input', 'playground.params.refAudio')
}
]}
dependencies={['task_type']}
>
<SealInput.Input
allowClear
readOnly={!!fileName}
required={taskType === 'Base'}
suffix={
<SuffixWrapper>
{fileName ? (
@@ -110,7 +161,12 @@ const TTSAdvanceConfig: React.FC = () => {
></SealInput.Input>
</Form.Item>
</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
allowClear
scaleSize={true}
@@ -118,9 +174,11 @@ const TTSAdvanceConfig: React.FC = () => {
></SealInput.TextArea>
</Form.Item>
<Form.Item
dependencies={['task_type', 'ref_text']}
style={{ marginBottom: 12 }}
name="x_vector_only_mode"
valuePropName="checked"
style={{ marginBottom: 8 }}
rules={[atLeastOneValidator('ref_text')]}
>
<CheckboxField
label={intl.formatMessage({
@@ -43,58 +43,5 @@ export const TTSAdvancedParamsConfig: ParamsSchema[] = [
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 }) => {
try {
await formRef.current?.form.validateFields();
if (!parameters.model) return;
setLoading(true);
setMessageId();
setTokenResult(null);
@@ -207,7 +204,6 @@ const GroundTTS: React.FC<MessageProps> = forwardRef((props, ref) => {
};
setParams(params);
console.log('submitMessage params:', streamTTS.isPlaying);
// Choose stream or non-stream based on parameters
if (parameters.stream) {
@@ -230,7 +226,6 @@ const GroundTTS: React.FC<MessageProps> = forwardRef((props, ref) => {
await nonStreamTTS.generate(params);
}
} catch (error: any) {
console.log('error:', error);
setPlayingStream(false);
setTokenResult({
error: true,
@@ -248,7 +243,7 @@ const GroundTTS: React.FC<MessageProps> = forwardRef((props, ref) => {
};
const handleSendMessage = (message: Omit<MessageItem, 'uid'>) => {
submitMessage(message);
formRef.current?.form.submit();
};
const handleCloseViewCode = () => {
@@ -353,6 +348,7 @@ const GroundTTS: React.FC<MessageProps> = forwardRef((props, ref) => {
<TTSDataForm
ref={formRef}
modelList={modelList}
onFinish={submitMessage}
updatateParams={updatateParams}
/>
</RightContainer>
+1 -1
View File
@@ -10,7 +10,7 @@
flex-direction: column;
position: relative;
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)
);
width: calc(100% - 390px);
@@ -6,8 +6,8 @@
border-left: 1px solid var(--ant-color-split);
transition: width 0.3s ease;
height: calc(
100vh - @HEADER_HEIGHT - var(--page-content-padding) -
var(--page-header-height)
100vh - @HEADER_HEIGHT - 2px - var(--page-content-padding) -
var(--page-content-padding) - var(--page-header-height)
);
padding-top: 16px;
display: flex;
+3 -3
View File
@@ -41,7 +41,7 @@
overflow: hidden;
height: calc(
100vh - var(--page-header-height) - var(--page-content-padding) -
@HEADER_HEIGHT
var(--page-content-padding) - @HEADER_HEIGHT
);
.collapse {
@@ -74,8 +74,8 @@
.ant-divider {
margin: 0;
height: calc(
100vh - var(--page-header-height) - var(--page-content-padding) -
@HEADER_HEIGHT
100vh - var(--page-header-height) - 2px - var(--page-content-padding) -
var(--page-content-padding) - @HEADER_HEIGHT
);
}
}