| from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError | from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError | ||||
| from core.model_runtime.errors.invoke import InvokeError | from core.model_runtime.errors.invoke import InvokeError | ||||
| from libs.login import login_required | from libs.login import login_required | ||||
| from models.model import AppModelConfig | |||||
| from services.audio_service import AudioService | from services.audio_service import AudioService | ||||
| from services.errors.audio import ( | from services.errors.audio import ( | ||||
| AudioTooLargeServiceError, | AudioTooLargeServiceError, | ||||
| app_id = str(app_id) | app_id = str(app_id) | ||||
| app_model = _get_app(app_id, None) | app_model = _get_app(app_id, None) | ||||
| app_model_config: AppModelConfig = app_model.app_model_config | |||||
| if not app_model_config.text_to_speech_dict['enabled']: | |||||
| raise AppUnavailableError() | |||||
| try: | try: | ||||
| response = AudioService.transcript_tts( | response = AudioService.transcript_tts( | ||||
| tenant_id=app_model.tenant_id, | tenant_id=app_model.tenant_id, | ||||
| class TextModesApi(Resource): | class TextModesApi(Resource): | ||||
| def get(self, app_id: str): | def get(self, app_id: str): | ||||
| app_model = _get_app(str(app_id)) | app_model = _get_app(str(app_id)) | ||||
| app_model_config: AppModelConfig = app_model.app_model_config | |||||
| if not app_model_config.text_to_speech_dict['enabled']: | |||||
| raise AppUnavailableError() | |||||
| try: | try: | ||||
| parser = reqparse.RequestParser() | parser = reqparse.RequestParser() | 
| model: tts-1 | |||||
| model: tts-1-hd | |||||
| model_type: tts | model_type: tts | ||||
| model_properties: | model_properties: | ||||
| default_voice: 'alloy' | default_voice: 'alloy' | ||||
| voices: | voices: | ||||
| - mode: 'alloy' | - mode: 'alloy' | ||||
| name: 'Alloy' | name: 'Alloy' | ||||
| language: ['zh-CN', 'en-US'] | |||||
| language: ['zh-Hans', 'en-US'] | |||||
| - mode: 'echo' | - mode: 'echo' | ||||
| name: 'Echo' | name: 'Echo' | ||||
| language: ['zh-CN', 'en-US'] | |||||
| language: ['zh-Hans', 'en-US'] | |||||
| - mode: 'fable' | - mode: 'fable' | ||||
| name: 'Fable' | name: 'Fable' | ||||
| language: ['zh-CN', 'en-US'] | |||||
| language: ['zh-Hans', 'en-US'] | |||||
| - mode: 'onyx' | - mode: 'onyx' | ||||
| name: 'Onyx' | name: 'Onyx' | ||||
| language: ['zh-CN', 'en-US'] | |||||
| language: ['zh-Hans', 'en-US'] | |||||
| - mode: 'nova' | - mode: 'nova' | ||||
| name: 'Nova' | name: 'Nova' | ||||
| language: ['zh-CN', 'en-US'] | |||||
| language: ['zh-Hans', 'en-US'] | |||||
| - mode: 'shimmer' | - mode: 'shimmer' | ||||
| name: 'Shimmer' | name: 'Shimmer' | ||||
| language: ['zh-CN', 'en-US'] | |||||
| language: ['zh-Hans', 'en-US'] | |||||
| word_limit: 120 | word_limit: 120 | ||||
| audio_type: 'mp3' | audio_type: 'mp3' | ||||
| max_workers: 5 | max_workers: 5 | 
| voices: | voices: | ||||
| - mode: 'alloy' | - mode: 'alloy' | ||||
| name: 'Alloy' | name: 'Alloy' | ||||
| language: ['zh-CN', 'en-US'] | |||||
| language: ['zh-Hans', 'en-US'] | |||||
| - mode: 'echo' | - mode: 'echo' | ||||
| name: 'Echo' | name: 'Echo' | ||||
| language: ['zh-CN', 'en-US'] | |||||
| language: ['zh-Hans', 'en-US'] | |||||
| - mode: 'fable' | - mode: 'fable' | ||||
| name: 'Fable' | name: 'Fable' | ||||
| language: ['zh-CN', 'en-US'] | |||||
| language: ['zh-Hans', 'en-US'] | |||||
| - mode: 'onyx' | - mode: 'onyx' | ||||
| name: 'Onyx' | name: 'Onyx' | ||||
| language: ['zh-CN', 'en-US'] | |||||
| language: ['zh-Hans', 'en-US'] | |||||
| - mode: 'nova' | - mode: 'nova' | ||||
| name: 'Nova' | name: 'Nova' | ||||
| language: ['zh-CN', 'en-US'] | |||||
| language: ['zh-Hans', 'en-US'] | |||||
| - mode: 'shimmer' | - mode: 'shimmer' | ||||
| name: 'Shimmer' | name: 'Shimmer' | ||||
| language: ['zh-CN', 'en-US'] | |||||
| language: ['zh-Hans', 'en-US'] | |||||
| word_limit: 120 | word_limit: 120 | ||||
| audio_type: 'mp3' | audio_type: 'mp3' | ||||
| max_workers: 5 | max_workers: 5 | 
| voices: | voices: | ||||
| - mode: "sambert-zhinan-v1" | - mode: "sambert-zhinan-v1" | ||||
| name: "知楠(广告男声)" | name: "知楠(广告男声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhiqi-v1" | - mode: "sambert-zhiqi-v1" | ||||
| name: "知琪(温柔女声)" | name: "知琪(温柔女声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhichu-v1" | - mode: "sambert-zhichu-v1" | ||||
| name: "知厨(新闻播报)" | name: "知厨(新闻播报)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhide-v1" | - mode: "sambert-zhide-v1" | ||||
| name: "知德(新闻男声)" | name: "知德(新闻男声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhijia-v1" | - mode: "sambert-zhijia-v1" | ||||
| name: "知佳(标准女声)" | name: "知佳(标准女声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhiru-v1" | - mode: "sambert-zhiru-v1" | ||||
| name: "知茹(新闻女声)" | name: "知茹(新闻女声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhiqian-v1" | - mode: "sambert-zhiqian-v1" | ||||
| name: "知倩(配音解说、新闻播报)" | name: "知倩(配音解说、新闻播报)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhixiang-v1" | - mode: "sambert-zhixiang-v1" | ||||
| name: "知祥(配音解说)" | name: "知祥(配音解说)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhiwei-v1" | - mode: "sambert-zhiwei-v1" | ||||
| name: "知薇(萝莉女声)" | name: "知薇(萝莉女声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhihao-v1" | - mode: "sambert-zhihao-v1" | ||||
| name: "知浩(咨询男声)" | name: "知浩(咨询男声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhijing-v1" | - mode: "sambert-zhijing-v1" | ||||
| name: "知婧(严厉女声)" | name: "知婧(严厉女声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhiming-v1" | - mode: "sambert-zhiming-v1" | ||||
| name: "知茗(诙谐男声)" | name: "知茗(诙谐男声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhimo-v1" | - mode: "sambert-zhimo-v1" | ||||
| name: "知墨(情感男声)" | name: "知墨(情感男声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhina-v1" | - mode: "sambert-zhina-v1" | ||||
| name: "知娜(浙普女声)" | name: "知娜(浙普女声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhishu-v1" | - mode: "sambert-zhishu-v1" | ||||
| name: "知树(资讯男声)" | name: "知树(资讯男声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhistella-v1" | - mode: "sambert-zhistella-v1" | ||||
| name: "知莎(知性女声)" | name: "知莎(知性女声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhiting-v1" | - mode: "sambert-zhiting-v1" | ||||
| name: "知婷(电台女声)" | name: "知婷(电台女声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhixiao-v1" | - mode: "sambert-zhixiao-v1" | ||||
| name: "知笑(资讯女声)" | name: "知笑(资讯女声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhiya-v1" | - mode: "sambert-zhiya-v1" | ||||
| name: "知雅(严厉女声)" | name: "知雅(严厉女声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhiye-v1" | - mode: "sambert-zhiye-v1" | ||||
| name: "知晔(青年男声)" | name: "知晔(青年男声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhiying-v1" | - mode: "sambert-zhiying-v1" | ||||
| name: "知颖(软萌童声)" | name: "知颖(软萌童声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhiyuan-v1" | - mode: "sambert-zhiyuan-v1" | ||||
| name: "知媛(知心姐姐)" | name: "知媛(知心姐姐)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhigui-v1" | - mode: "sambert-zhigui-v1" | ||||
| name: "知柜(直播女声)" | name: "知柜(直播女声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhishuo-v1" | - mode: "sambert-zhishuo-v1" | ||||
| name: "知硕(自然男声)" | name: "知硕(自然男声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhimiao-emo-v1" | - mode: "sambert-zhimiao-emo-v1" | ||||
| name: "知妙(多种情感女声)" | name: "知妙(多种情感女声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhimao-v1" | - mode: "sambert-zhimao-v1" | ||||
| name: "知猫(直播女声)" | name: "知猫(直播女声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhilun-v1" | - mode: "sambert-zhilun-v1" | ||||
| name: "知伦(悬疑解说)" | name: "知伦(悬疑解说)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhifei-v1" | - mode: "sambert-zhifei-v1" | ||||
| name: "知飞(激昂解说)" | name: "知飞(激昂解说)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-zhida-v1" | - mode: "sambert-zhida-v1" | ||||
| name: "知达(标准男声)" | name: "知达(标准男声)" | ||||
| language: [ "zh-CN", "en-US" ] | |||||
| language: [ "zh-Hans", "en-US" ] | |||||
| - mode: "sambert-camila-v1" | - mode: "sambert-camila-v1" | ||||
| name: "Camila(西班牙语女声)" | name: "Camila(西班牙语女声)" | ||||
| language: [ "es-ES" ] | language: [ "es-ES" ] | 
| const appId = (matched?.length && matched[1]) ? matched[1] : '' | const appId = (matched?.length && matched[1]) ? matched[1] : '' | ||||
| const LanguageItems = [ | const LanguageItems = [ | ||||
| { value: 'zh-CN', name: '中文' }, | |||||
| { value: 'en-US', name: '英语' }, | |||||
| { value: 'de-DE', name: '德语' }, | |||||
| { value: 'fr-FR', name: '法语' }, | |||||
| { value: 'es-ES', name: '西班牙语' }, | |||||
| { value: 'it-IT', name: '意大利语' }, | |||||
| { value: 'th-TH', name: '泰语' }, | |||||
| { value: 'id-ID', name: '印尼语' }, | |||||
| { value: 'zh-Hans', name: 'Chinese' }, | |||||
| { value: 'en-US', name: 'English' }, | |||||
| { value: 'de-DE', name: 'German' }, | |||||
| { value: 'fr-FR', name: 'French' }, | |||||
| { value: 'es-ES', name: 'Spanish' }, | |||||
| { value: 'it-IT', name: 'Italian' }, | |||||
| { value: 'th-TH', name: 'Thai' }, | |||||
| { value: 'id-ID', name: 'Indonesian' }, | |||||
| ] | ] | ||||
| const { | const { | ||||
| textToSpeechConfig, | textToSpeechConfig, | ||||
| const languageItem = LanguageItems.find(item => item.value === textToSpeechConfig.language) | const languageItem = LanguageItems.find(item => item.value === textToSpeechConfig.language) | ||||
| const localLanguagePlaceholder = languageItem?.name || t('common.placeholder.select') | const localLanguagePlaceholder = languageItem?.name || t('common.placeholder.select') | ||||
| const voiceItems = useSWR({ url: `/apps/${appId}/text-to-audio/voices?language=${languageItem ? languageItem.value : 'zh-CN'}` }, fetchAppVoices).data | |||||
| const voiceItems = useSWR({ url: `/apps/${appId}/text-to-audio/voices?language=${languageItem ? languageItem.value : 'en-US'}` }, fetchAppVoices).data | |||||
| const voiceItem = voiceItems?.find(item => item.value === textToSpeechConfig.voice) | const voiceItem = voiceItems?.find(item => item.value === textToSpeechConfig.voice) | ||||
| const localVoicePlaceholder = voiceItem?.name || t('common.placeholder.select') | const localVoicePlaceholder = voiceItem?.name || t('common.placeholder.select') | ||||