refactor: 配置TTS参数支持试听功能
This commit is contained in:
parent
dead7e8da3
commit
68eb21b556
@ -973,6 +973,16 @@ class ApplicationSerializer(serializers.Serializer):
|
|||||||
**application.tts_model_params_setting)
|
**application.tts_model_params_setting)
|
||||||
return model.text_to_speech(text)
|
return model.text_to_speech(text)
|
||||||
|
|
||||||
|
def play_demo_text(self, form_data, with_valid=True):
|
||||||
|
text = '你好,这里是语音播放测试'
|
||||||
|
if with_valid:
|
||||||
|
self.is_valid(raise_exception=True)
|
||||||
|
application_id = self.data.get('application_id')
|
||||||
|
application = QuerySet(Application).filter(id=application_id).first()
|
||||||
|
if application.tts_model_enable:
|
||||||
|
model = get_model_instance_by_model_user_id(application.tts_model_id, application.user_id, **form_data)
|
||||||
|
return model.text_to_speech(text)
|
||||||
|
|
||||||
class ApplicationKeySerializerModel(serializers.ModelSerializer):
|
class ApplicationKeySerializerModel(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ApplicationApiKey
|
model = ApplicationApiKey
|
||||||
|
|||||||
@ -70,5 +70,7 @@ urlpatterns = [
|
|||||||
name='application/audio'),
|
name='application/audio'),
|
||||||
path('application/<str:application_id>/text_to_speech', views.Application.TextToSpeech.as_view(),
|
path('application/<str:application_id>/text_to_speech', views.Application.TextToSpeech.as_view(),
|
||||||
name='application/audio'),
|
name='application/audio'),
|
||||||
|
path('application/<str:application_id>/play_demo_text', views.Application.PlayDemoText.as_view(),
|
||||||
|
name='application/audio'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|||||||
@ -566,3 +566,19 @@ class Application(APIView):
|
|||||||
request.data.get('text'))
|
request.data.get('text'))
|
||||||
return HttpResponse(byte_data, status=200, headers={'Content-Type': 'audio/mp3',
|
return HttpResponse(byte_data, status=200, headers={'Content-Type': 'audio/mp3',
|
||||||
'Content-Disposition': 'attachment; filename="abc.mp3"'})
|
'Content-Disposition': 'attachment; filename="abc.mp3"'})
|
||||||
|
|
||||||
|
class PlayDemoText(APIView):
|
||||||
|
authentication_classes = [TokenAuth]
|
||||||
|
|
||||||
|
@action(methods=['POST'], detail=False)
|
||||||
|
@has_permissions(ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_ACCESS_TOKEN],
|
||||||
|
[lambda r, keywords: Permission(group=Group.APPLICATION,
|
||||||
|
operate=Operate.USE,
|
||||||
|
dynamic_tag=keywords.get(
|
||||||
|
'application_id'))],
|
||||||
|
compare=CompareConstants.AND))
|
||||||
|
def post(self, request: Request, application_id: str):
|
||||||
|
byte_data = ApplicationSerializer.Operate(
|
||||||
|
data={'application_id': application_id, 'user_id': request.user.id}).play_demo_text(request.data)
|
||||||
|
return HttpResponse(byte_data, status=200, headers={'Content-Type': 'audio/mp3',
|
||||||
|
'Content-Disposition': 'attachment; filename="abc.mp3"'})
|
||||||
|
|||||||
@ -44,3 +44,6 @@ class AliyunBaiLianTextToSpeech(MaxKBBaseModel, BaseTextToSpeech):
|
|||||||
print(audio)
|
print(audio)
|
||||||
raise Exception(audio)
|
raise Exception(audio)
|
||||||
return audio
|
return audio
|
||||||
|
|
||||||
|
def is_cache_model(self):
|
||||||
|
return False
|
||||||
|
|||||||
@ -56,3 +56,6 @@ class OpenAITextToSpeech(MaxKBBaseModel, BaseTextToSpeech):
|
|||||||
input=text,
|
input=text,
|
||||||
) as response:
|
) as response:
|
||||||
return response.read()
|
return response.read()
|
||||||
|
|
||||||
|
def is_cache_model(self):
|
||||||
|
return False
|
||||||
@ -102,6 +102,9 @@ class VolcanicEngineTextToSpeech(MaxKBBaseModel, BaseTextToSpeech):
|
|||||||
|
|
||||||
return asyncio.run(self.submit(request_json, text))
|
return asyncio.run(self.submit(request_json, text))
|
||||||
|
|
||||||
|
def is_cache_model(self):
|
||||||
|
return False
|
||||||
|
|
||||||
def token_auth(self):
|
def token_auth(self):
|
||||||
return {'Authorization': 'Bearer; {}'.format(self.volcanic_token)}
|
return {'Authorization': 'Bearer; {}'.format(self.volcanic_token)}
|
||||||
|
|
||||||
|
|||||||
@ -113,6 +113,9 @@ class XFSparkTextToSpeech(MaxKBBaseModel, BaseTextToSpeech):
|
|||||||
|
|
||||||
return asyncio.run(handle())
|
return asyncio.run(handle())
|
||||||
|
|
||||||
|
def is_cache_model(self):
|
||||||
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def handle_message(ws):
|
async def handle_message(ws):
|
||||||
audio_bytes: bytes = b''
|
audio_bytes: bytes = b''
|
||||||
|
|||||||
@ -58,3 +58,6 @@ class XInferenceTextToSpeech(MaxKBBaseModel, BaseTextToSpeech):
|
|||||||
input=text,
|
input=text,
|
||||||
) as response:
|
) as response:
|
||||||
return response.read()
|
return response.read()
|
||||||
|
|
||||||
|
def is_cache_model(self):
|
||||||
|
return False
|
||||||
@ -344,7 +344,7 @@ const postSpeechToText: (
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 语音转文本
|
* 文本转语音
|
||||||
*/
|
*/
|
||||||
const postTextToSpeech: (
|
const postTextToSpeech: (
|
||||||
application_id: String,
|
application_id: String,
|
||||||
@ -353,6 +353,17 @@ const postTextToSpeech: (
|
|||||||
) => Promise<Result<any>> = (application_id, data, loading) => {
|
) => Promise<Result<any>> = (application_id, data, loading) => {
|
||||||
return download(`${prefix}/${application_id}/text_to_speech`, 'post', data, undefined, loading)
|
return download(`${prefix}/${application_id}/text_to_speech`, 'post', data, undefined, loading)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 播放测试文本
|
||||||
|
*/
|
||||||
|
const playDemoText: (
|
||||||
|
application_id: String,
|
||||||
|
data: any,
|
||||||
|
loading?: Ref<boolean>
|
||||||
|
) => Promise<Result<any>> = (application_id, data, loading) => {
|
||||||
|
return download(`${prefix}/${application_id}/play_demo_text`, 'post', data, undefined, loading)
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 获取平台状态
|
* 获取平台状态
|
||||||
*/
|
*/
|
||||||
@ -430,5 +441,6 @@ export default {
|
|||||||
getPlatformConfig,
|
getPlatformConfig,
|
||||||
updatePlatformConfig,
|
updatePlatformConfig,
|
||||||
updatePlatformStatus,
|
updatePlatformStatus,
|
||||||
validatePassword
|
validatePassword,
|
||||||
|
playDemoText
|
||||||
}
|
}
|
||||||
|
|||||||
@ -533,7 +533,7 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<AIModeParamSettingDialog ref="AIModeParamSettingDialogRef" @refresh="refreshForm" />
|
<AIModeParamSettingDialog ref="AIModeParamSettingDialogRef" @refresh="refreshForm" />
|
||||||
<AIModeParamSettingDialog ref="TTSModeParamSettingDialogRef" @refresh="refreshTTSForm" />
|
<TTSModeParamSettingDialog ref="TTSModeParamSettingDialogRef" @refresh="refreshTTSForm" />
|
||||||
<ParamSettingDialog ref="ParamSettingDialogRef" @refresh="refreshParam" />
|
<ParamSettingDialog ref="ParamSettingDialogRef" @refresh="refreshParam" />
|
||||||
<AddDatasetDialog
|
<AddDatasetDialog
|
||||||
ref="AddDatasetDialogRef"
|
ref="AddDatasetDialogRef"
|
||||||
@ -573,6 +573,7 @@ import { relatedObject } from '@/utils/utils'
|
|||||||
import { MsgSuccess, MsgWarning } from '@/utils/message'
|
import { MsgSuccess, MsgWarning } from '@/utils/message'
|
||||||
import useStore from '@/stores'
|
import useStore from '@/stores'
|
||||||
import { t } from '@/locales'
|
import { t } from '@/locales'
|
||||||
|
import TTSModeParamSettingDialog from './component/TTSModeParamSettingDialog.vue'
|
||||||
|
|
||||||
const { model, application } = useStore()
|
const { model, application } = useStore()
|
||||||
|
|
||||||
@ -587,7 +588,7 @@ const defaultPrompt = t('views.application.prompt.defaultPrompt', {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const AIModeParamSettingDialogRef = ref<InstanceType<typeof AIModeParamSettingDialog>>()
|
const AIModeParamSettingDialogRef = ref<InstanceType<typeof AIModeParamSettingDialog>>()
|
||||||
const TTSModeParamSettingDialogRef = ref<InstanceType<typeof AIModeParamSettingDialog>>()
|
const TTSModeParamSettingDialogRef = ref<InstanceType<typeof TTSModeParamSettingDialog>>()
|
||||||
const ParamSettingDialogRef = ref<InstanceType<typeof ParamSettingDialog>>()
|
const ParamSettingDialogRef = ref<InstanceType<typeof ParamSettingDialog>>()
|
||||||
const createModelRef = ref<InstanceType<typeof CreateModelDialog>>()
|
const createModelRef = ref<InstanceType<typeof CreateModelDialog>>()
|
||||||
const selectProviderRef = ref<InstanceType<typeof SelectProviderDialog>>()
|
const selectProviderRef = ref<InstanceType<typeof SelectProviderDialog>>()
|
||||||
|
|||||||
156
ui/src/views/application/component/TTSModeParamSettingDialog.vue
Normal file
156
ui/src/views/application/component/TTSModeParamSettingDialog.vue
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
align-center
|
||||||
|
:title="$t('views.application.applicationForm.dialogues.paramSettings')"
|
||||||
|
class="aiMode-param-dialog"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
style="width: 550px"
|
||||||
|
append-to-body
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
>
|
||||||
|
<DynamicsForm
|
||||||
|
v-model="form_data"
|
||||||
|
:model="form_data"
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
:render_data="model_form_field"
|
||||||
|
ref="dynamicsFormRef"
|
||||||
|
>
|
||||||
|
</DynamicsForm>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex-between">
|
||||||
|
<span class="p-16">
|
||||||
|
<el-button @click="testPlay" :loading="playLoading">
|
||||||
|
<AppIcon iconName="app-video-play"></AppIcon>
|
||||||
|
试听
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
<span class="dialog-footer p-16">
|
||||||
|
<el-button @click.prevent="dialogVisible = false">
|
||||||
|
{{ $t('views.application.applicationForm.buttons.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" @click="submit" :loading="loading">
|
||||||
|
{{ $t('views.application.applicationForm.buttons.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
<!-- 先渲染,不然不能播放 -->
|
||||||
|
<audio ref="audioPlayer" controls hidden="hidden"></audio>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import type { FormField } from '@/components/dynamics-form/type'
|
||||||
|
import modelAPi from '@/api/model'
|
||||||
|
import applicationApi from '@/api/application'
|
||||||
|
import DynamicsForm from '@/components/dynamics-form/index.vue'
|
||||||
|
import { keys } from 'lodash'
|
||||||
|
import { app } from '@/main'
|
||||||
|
|
||||||
|
const {
|
||||||
|
params: { id }
|
||||||
|
} = app.config.globalProperties.$route as any
|
||||||
|
|
||||||
|
const model_form_field = ref<Array<FormField>>([])
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
const dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()
|
||||||
|
const form_data = ref<any>({})
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
const playLoading = ref(false)
|
||||||
|
const getApi = (model_id: string, application_id?: string) => {
|
||||||
|
return application_id
|
||||||
|
? applicationApi.getModelParamsForm(application_id, model_id, loading)
|
||||||
|
: modelAPi.getModelParamsForm(model_id, loading)
|
||||||
|
}
|
||||||
|
const open = (model_id: string, application_id?: string, model_setting_data?: any) => {
|
||||||
|
form_data.value = {}
|
||||||
|
const api = getApi(model_id, application_id)
|
||||||
|
api.then((ok) => {
|
||||||
|
model_form_field.value = ok.data
|
||||||
|
model_setting_data =
|
||||||
|
model_setting_data && keys(model_setting_data).length > 0
|
||||||
|
? model_setting_data
|
||||||
|
: ok.data
|
||||||
|
.map((item: any) => ({ [item.field]: item.default_value }))
|
||||||
|
.reduce((x, y) => ({ ...x, ...y }), {})
|
||||||
|
// 渲染动态表单
|
||||||
|
dynamicsFormRef.value?.render(model_form_field.value, model_setting_data)
|
||||||
|
})
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const reset_default = (model_id: string, application_id?: string) => {
|
||||||
|
const api = getApi(model_id, application_id)
|
||||||
|
api.then((ok) => {
|
||||||
|
model_form_field.value = ok.data
|
||||||
|
const model_setting_data = ok.data
|
||||||
|
.map((item) => ({ [item.field]: item.default_value }))
|
||||||
|
.reduce((x, y) => ({ ...x, ...y }), {})
|
||||||
|
|
||||||
|
emit('refresh', model_setting_data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
emit('refresh', form_data.value)
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const audioPlayer = ref<HTMLAudioElement | null>(null)
|
||||||
|
const testPlay = () => {
|
||||||
|
applicationApi
|
||||||
|
.playDemoText(id as string, form_data.value, playLoading)
|
||||||
|
.then((res: any) => {
|
||||||
|
// 创建 Blob 对象
|
||||||
|
const blob = new Blob([res], { type: 'audio/mp3' })
|
||||||
|
|
||||||
|
// 创建对象 URL
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
|
||||||
|
// 检查 audioPlayer 是否已经引用了 DOM 元素
|
||||||
|
if (audioPlayer.value instanceof HTMLAudioElement) {
|
||||||
|
audioPlayer.value.src = url
|
||||||
|
audioPlayer.value.play() // 自动播放音频
|
||||||
|
} else {
|
||||||
|
console.error('audioPlayer.value is not an instance of HTMLAudioElement')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('err: ', err)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
defineExpose({ open, reset_default })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.aiMode-param-dialog {
|
||||||
|
padding: 8px 8px 24px 8px;
|
||||||
|
|
||||||
|
.el-dialog__header {
|
||||||
|
padding: 16px 16px 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dialog__body {
|
||||||
|
padding: 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-max-height {
|
||||||
|
height: 550px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-slider {
|
||||||
|
.el-input-number.is-without-controls .el-input__wrapper {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -258,7 +258,7 @@
|
|||||||
|
|
||||||
<FieldFormDialog ref="FieldFormDialogRef" @refresh="refreshFieldList" />
|
<FieldFormDialog ref="FieldFormDialogRef" @refresh="refreshFieldList" />
|
||||||
</NodeContainer>
|
</NodeContainer>
|
||||||
<AIModeParamSettingDialog ref="TTSModeParamSettingDialogRef" @refresh="refreshTTSForm" />
|
<TTSModeParamSettingDialog ref="TTSModeParamSettingDialogRef" @refresh="refreshTTSForm" />
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { app } from '@/main'
|
import { app } from '@/main'
|
||||||
@ -273,7 +273,7 @@ import type { Provider } from '@/api/type/model'
|
|||||||
import FieldFormDialog from './component/FieldFormDialog.vue'
|
import FieldFormDialog from './component/FieldFormDialog.vue'
|
||||||
import { MsgError, MsgSuccess, MsgWarning } from '@/utils/message'
|
import { MsgError, MsgSuccess, MsgWarning } from '@/utils/message'
|
||||||
import { t } from '@/locales'
|
import { t } from '@/locales'
|
||||||
import AIModeParamSettingDialog from '@/views/application/component/AIModeParamSettingDialog.vue'
|
import TTSModeParamSettingDialog from '@/views/application/component/TTSModeParamSettingDialog.vue'
|
||||||
const { model } = useStore()
|
const { model } = useStore()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user