From 6355e61eb8437c629c40de9573878a64985fd178 Mon Sep 17 00:00:00 2001 From: "Charlie.Wei" Date: Wed, 24 Jan 2024 01:05:37 +0800 Subject: [PATCH] tts models support (#2033) Co-authored-by: luowei Co-authored-by: crazywoola <427733928@qq.com> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: Yeuoly <45712896+Yeuoly@users.noreply.github.com> --- api/Dockerfile | 2 +- api/app.py | 1 + api/controllers/console/app/audio.py | 49 +++- api/controllers/console/explore/audio.py | 48 +++- api/controllers/console/explore/parameter.py | 2 + api/controllers/service_api/app/app.py | 2 + api/controllers/service_api/app/audio.py | 54 +++- api/controllers/web/app.py | 2 + api/controllers/web/audio.py | 43 +++- api/core/application_manager.py | 6 + api/core/entities/application_entities.py | 2 +- api/core/model_manager.py | 26 +- .../model_runtime/entities/model_entities.py | 10 +- .../model_providers/__base/tts_model.py | 42 ++++ .../model_providers/openai/openai.yaml | 1 + .../model_providers/openai/tts/__init__.py | 0 .../model_providers/openai/tts/tts-1-hd.yaml | 7 + .../model_providers/openai/tts/tts-1.yaml | 7 + .../model_providers/openai/tts/tts.py | 235 ++++++++++++++++++ api/fields/app_fields.py | 1 + api/migrations/versions/b24be59fbb04_.py | 32 +++ api/models/model.py | 10 + api/requirements.txt | 1 + api/services/account_service.py | 14 +- api/services/app_model_config_service.py | 16 ++ api/services/audio_service.py | 34 ++- api/services/errors/audio.py | 7 +- web/app/components/app/chat/answer/index.tsx | 11 +- web/app/components/app/chat/index.tsx | 3 + web/app/components/app/chat/style.module.css | 2 + .../citations-and-attributions-preview@2x.png | Bin 0 -> 20827 bytes .../conversation-opener-preview@2x.png | Bin 0 -> 14409 bytes .../more-like-this-preview@2x.png | Bin 0 -> 19839 bytes .../next-question-suggestion-preview@2x.png | Bin 0 -> 28325 bytes .../opening-suggestion-preview@2x.png | Bin 0 -> 24140 bytes .../speech-to-text-preview@2x.png | Bin 0 -> 16929 bytes .../text-to-audio-preview-assistant@2x.png | Bin 0 -> 23500 bytes .../text-to-audio-preview-completion@2x.png | Bin 0 -> 18462 bytes .../feature-item/style.module.css | 7 +- .../config/feature/choose-feature/index.tsx | 29 ++- .../config/feature/use-feature.tsx | 8 + .../app/configuration/config/index.tsx | 27 +- .../app/configuration/debug/index.tsx | 10 + .../features/chat-group/index.tsx | 8 + .../chat-group/speech-to-text/index.tsx | 4 +- .../chat-group/text-to-speech/index.tsx | 25 ++ .../experience-enchance-group/index.tsx | 27 +- .../components/app/configuration/index.tsx | 15 ++ web/app/components/app/log/list.tsx | 2 + .../app/text-generate/item/index.tsx | 17 +- .../app/text-generate/saved-items/index.tsx | 13 + web/app/components/base/audio-btn/index.tsx | 110 ++++++++ .../base/audio-btn/style.module.css | 16 ++ .../vender/line/mediaAndDevices/speaker.svg | 15 ++ .../vender/solid/mediaAndDevices/speaker.svg | 15 ++ .../vender/line/mediaAndDevices/Speaker.json | 112 +++++++++ .../vender/line/mediaAndDevices/Speaker.tsx | 16 ++ .../src/vender/line/mediaAndDevices/index.ts | 1 + .../vender/solid/mediaAndDevices/Speaker.json | 112 +++++++++ .../vender/solid/mediaAndDevices/Speaker.tsx | 16 ++ .../src/vender/solid/mediaAndDevices/index.ts | 1 + .../develop/secret-key/assets/pause.svg | 10 + .../develop/secret-key/assets/play.svg | 11 + .../develop/secret-key/assets/stop.svg | 11 + .../develop/template/template.en.mdx | 78 ++++-- .../develop/template/template.zh.mdx | 76 ++++-- .../develop/template/template_chat.en.mdx | 94 +++++-- .../develop/template/template_chat.zh.mdx | 82 ++++-- .../model-provider-page/declarations.ts | 2 + .../model-provider-page/hooks.ts | 3 +- .../model-provider-page/index.tsx | 4 +- .../system-model-selector/index.tsx | 40 ++- web/app/components/share/chat/index.tsx | 19 +- web/app/components/share/chatbot/index.tsx | 8 +- .../share/text-generation/index.tsx | 17 +- .../share/text-generation/result/index.tsx | 3 + web/context/debug-configuration.ts | 8 + web/i18n/lang/app-api.en.ts | 3 + web/i18n/lang/app-api.zh.ts | 5 +- web/i18n/lang/app-debug.en.ts | 5 + web/i18n/lang/app-debug.zh.ts | 5 + web/i18n/lang/common.en.ts | 4 + web/i18n/lang/common.zh.ts | 4 + web/models/debug.ts | 3 + web/service/share.ts | 4 + web/types/app.ts | 3 + 86 files changed, 1645 insertions(+), 133 deletions(-) create mode 100644 api/core/model_runtime/model_providers/__base/tts_model.py create mode 100644 api/core/model_runtime/model_providers/openai/tts/__init__.py create mode 100644 api/core/model_runtime/model_providers/openai/tts/tts-1-hd.yaml create mode 100644 api/core/model_runtime/model_providers/openai/tts/tts-1.yaml create mode 100644 api/core/model_runtime/model_providers/openai/tts/tts.py create mode 100644 api/migrations/versions/b24be59fbb04_.py create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/citations-and-attributions-preview@2x.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/conversation-opener-preview@2x.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/more-like-this-preview@2x.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/next-question-suggestion-preview@2x.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/opening-suggestion-preview@2x.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/speech-to-text-preview@2x.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/text-to-audio-preview-assistant@2x.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/text-to-audio-preview-completion@2x.png create mode 100644 web/app/components/app/configuration/features/chat-group/text-to-speech/index.tsx create mode 100644 web/app/components/base/audio-btn/index.tsx create mode 100644 web/app/components/base/audio-btn/style.module.css create mode 100644 web/app/components/base/icons/assets/vender/line/mediaAndDevices/speaker.svg create mode 100644 web/app/components/base/icons/assets/vender/solid/mediaAndDevices/speaker.svg create mode 100644 web/app/components/base/icons/src/vender/line/mediaAndDevices/Speaker.json create mode 100644 web/app/components/base/icons/src/vender/line/mediaAndDevices/Speaker.tsx create mode 100644 web/app/components/base/icons/src/vender/solid/mediaAndDevices/Speaker.json create mode 100644 web/app/components/base/icons/src/vender/solid/mediaAndDevices/Speaker.tsx create mode 100644 web/app/components/develop/secret-key/assets/pause.svg create mode 100644 web/app/components/develop/secret-key/assets/play.svg create mode 100644 web/app/components/develop/secret-key/assets/stop.svg diff --git a/api/Dockerfile b/api/Dockerfile index d060055941..ae4f7e82bb 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -30,7 +30,7 @@ ENV TZ UTC WORKDIR /app/api RUN apt-get update \ - && apt-get install -y --no-install-recommends bash curl wget vim nodejs \ + && apt-get install -y --no-install-recommends bash curl wget vim nodejs ffmpeg \ && apt-get autoremove \ && rm -rf /var/lib/apt/lists/* diff --git a/api/app.py b/api/app.py index e46cb84bb8..caf4e8f459 100644 --- a/api/app.py +++ b/api/app.py @@ -124,6 +124,7 @@ def load_user_from_request(request_from_flask_login): else: return None + @login_manager.unauthorized_handler def unauthorized_handler(): """Handle unauthorized requests.""" diff --git a/api/controllers/console/app/audio.py b/api/controllers/console/app/audio.py index ed8b36c00c..7eef2abc32 100644 --- a/api/controllers/console/app/audio.py +++ b/api/controllers/console/app/audio.py @@ -32,9 +32,10 @@ class ChatMessageAudioApi(Resource): file = request.files['file'] try: - response = AudioService.transcript( + response = AudioService.transcript_asr( tenant_id=app_model.tenant_id, file=file, + promot=app_model.app_model_config.pre_prompt ) return response @@ -62,6 +63,48 @@ class ChatMessageAudioApi(Resource): except Exception as e: logging.exception("internal server error.") raise InternalServerError() - -api.add_resource(ChatMessageAudioApi, '/apps//audio-to-text') \ No newline at end of file + +class ChatMessageTextApi(Resource): + @setup_required + @login_required + @account_initialization_required + def post(self, app_id): + app_id = str(app_id) + app_model = _get_app(app_id, None) + try: + response = AudioService.transcript_tts( + tenant_id=app_model.tenant_id, + text=request.form['text'], + streaming=False + ) + + return {'data': response.data.decode('latin1')} + except services.errors.app_model_config.AppModelConfigBrokenError: + logging.exception("App model config broken.") + raise AppUnavailableError() + except NoAudioUploadedServiceError: + raise NoAudioUploadedError() + except AudioTooLargeServiceError as e: + raise AudioTooLargeError(str(e)) + except UnsupportedAudioTypeServiceError: + raise UnsupportedAudioTypeError() + except ProviderNotSupportSpeechToTextServiceError: + raise ProviderNotSupportSpeechToTextError() + except ProviderTokenNotInitError as ex: + raise ProviderNotInitializeError(ex.description) + except QuotaExceededError: + raise ProviderQuotaExceededError() + except ModelCurrentlyNotSupportError: + raise ProviderModelCurrentlyNotSupportError() + except InvokeError as e: + raise CompletionRequestError(e.description) + except ValueError as e: + raise e + except Exception as e: + logging.exception("internal server error.") + raise InternalServerError() + + +api.add_resource(ChatMessageAudioApi, '/apps//audio-to-text') +api.add_resource(ChatMessageTextApi, '/apps//text-to-audio') diff --git a/api/controllers/console/explore/audio.py b/api/controllers/console/explore/audio.py index 00ae66e663..651cdf16b5 100644 --- a/api/controllers/console/explore/audio.py +++ b/api/controllers/console/explore/audio.py @@ -29,7 +29,7 @@ class ChatAudioApi(InstalledAppResource): file = request.files['file'] try: - response = AudioService.transcript( + response = AudioService.transcript_asr( tenant_id=app_model.tenant_id, file=file, ) @@ -59,6 +59,48 @@ class ChatAudioApi(InstalledAppResource): except Exception as e: logging.exception("internal server error.") raise InternalServerError() - -api.add_resource(ChatAudioApi, '/installed-apps//audio-to-text', endpoint='installed_app_audio') \ No newline at end of file + +class ChatTextApi(InstalledAppResource): + def post(self, installed_app): + app_model = installed_app.app + app_model_config: AppModelConfig = app_model.app_model_config + + if not app_model_config.text_to_speech_dict['enabled']: + raise AppUnavailableError() + + try: + response = AudioService.transcript_tts( + tenant_id=app_model.tenant_id, + text=request.form['text'], + streaming=False + ) + return {'data': response.data.decode('latin1')} + except services.errors.app_model_config.AppModelConfigBrokenError: + logging.exception("App model config broken.") + raise AppUnavailableError() + except NoAudioUploadedServiceError: + raise NoAudioUploadedError() + except AudioTooLargeServiceError as e: + raise AudioTooLargeError(str(e)) + except UnsupportedAudioTypeServiceError: + raise UnsupportedAudioTypeError() + except ProviderNotSupportSpeechToTextServiceError: + raise ProviderNotSupportSpeechToTextError() + except ProviderTokenNotInitError as ex: + raise ProviderNotInitializeError(ex.description) + except QuotaExceededError: + raise ProviderQuotaExceededError() + except ModelCurrentlyNotSupportError: + raise ProviderModelCurrentlyNotSupportError() + except InvokeError as e: + raise CompletionRequestError(e.description) + except ValueError as e: + raise e + except Exception as e: + logging.exception("internal server error.") + raise InternalServerError() + + +api.add_resource(ChatAudioApi, '/installed-apps//audio-to-text', endpoint='installed_app_audio') +api.add_resource(ChatTextApi, '/installed-apps//text-to-audio', endpoint='installed_app_text') diff --git a/api/controllers/console/explore/parameter.py b/api/controllers/console/explore/parameter.py index 0a76f9d58a..6bdfa34095 100644 --- a/api/controllers/console/explore/parameter.py +++ b/api/controllers/console/explore/parameter.py @@ -31,6 +31,7 @@ class AppParameterApi(InstalledAppResource): 'suggested_questions': fields.Raw, 'suggested_questions_after_answer': fields.Raw, 'speech_to_text': fields.Raw, + 'text_to_speech': fields.Raw, 'retriever_resource': fields.Raw, 'annotation_reply': fields.Raw, 'more_like_this': fields.Raw, @@ -51,6 +52,7 @@ class AppParameterApi(InstalledAppResource): 'suggested_questions': app_model_config.suggested_questions_list, 'suggested_questions_after_answer': app_model_config.suggested_questions_after_answer_dict, 'speech_to_text': app_model_config.speech_to_text_dict, + 'text_to_speech': app_model_config.text_to_speech_dict, 'retriever_resource': app_model_config.retriever_resource_dict, 'annotation_reply': app_model_config.annotation_reply_dict, 'more_like_this': app_model_config.more_like_this_dict, diff --git a/api/controllers/service_api/app/app.py b/api/controllers/service_api/app/app.py index 0be38d3083..aa4323a146 100644 --- a/api/controllers/service_api/app/app.py +++ b/api/controllers/service_api/app/app.py @@ -33,6 +33,7 @@ class AppParameterApi(AppApiResource): 'suggested_questions': fields.Raw, 'suggested_questions_after_answer': fields.Raw, 'speech_to_text': fields.Raw, + 'text_to_speech': fields.Raw, 'retriever_resource': fields.Raw, 'annotation_reply': fields.Raw, 'more_like_this': fields.Raw, @@ -52,6 +53,7 @@ class AppParameterApi(AppApiResource): 'suggested_questions': app_model_config.suggested_questions_list, 'suggested_questions_after_answer': app_model_config.suggested_questions_after_answer_dict, 'speech_to_text': app_model_config.speech_to_text_dict, + 'text_to_speech': app_model_config.text_to_speech_dict, 'retriever_resource': app_model_config.retriever_resource_dict, 'annotation_reply': app_model_config.annotation_reply_dict, 'more_like_this': app_model_config.more_like_this_dict, diff --git a/api/controllers/service_api/app/audio.py b/api/controllers/service_api/app/audio.py index 17e9abdb55..3e642b69d3 100644 --- a/api/controllers/service_api/app/audio.py +++ b/api/controllers/service_api/app/audio.py @@ -10,6 +10,7 @@ from controllers.service_api.wraps import AppApiResource from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from core.model_runtime.errors.invoke import InvokeError from flask import request +from flask_restful import reqparse from models.model import App, AppModelConfig from services.audio_service import AudioService from services.errors.audio import (AudioTooLargeServiceError, NoAudioUploadedServiceError, @@ -22,14 +23,15 @@ class AudioApi(AppApiResource): app_model_config: AppModelConfig = app_model.app_model_config if not app_model_config.speech_to_text_dict['enabled']: - raise AppUnavailableError() + raise AppUnavailableError() file = request.files['file'] try: - response = AudioService.transcript( + response = AudioService.transcript_asr( tenant_id=app_model.tenant_id, file=file, + end_user=end_user ) return response @@ -57,5 +59,49 @@ class AudioApi(AppApiResource): except Exception as e: logging.exception("internal server error.") raise InternalServerError() - -api.add_resource(AudioApi, '/audio-to-text') \ No newline at end of file + + +class TextApi(AppApiResource): + def post(self, app_model: App, end_user): + parser = reqparse.RequestParser() + parser.add_argument('text', type=str, required=True, nullable=False, location='json') + parser.add_argument('user', type=str, required=True, nullable=False, location='json') + args = parser.parse_args() + + try: + response = AudioService.transcript_tts( + tenant_id=app_model.tenant_id, + text=args['text'], + end_user=args['user'], + streaming=False + ) + + return response + except services.errors.app_model_config.AppModelConfigBrokenError: + logging.exception("App model config broken.") + raise AppUnavailableError() + except NoAudioUploadedServiceError: + raise NoAudioUploadedError() + except AudioTooLargeServiceError as e: + raise AudioTooLargeError(str(e)) + except UnsupportedAudioTypeServiceError: + raise UnsupportedAudioTypeError() + except ProviderNotSupportSpeechToTextServiceError: + raise ProviderNotSupportSpeechToTextError() + except ProviderTokenNotInitError as ex: + raise ProviderNotInitializeError(ex.description) + except QuotaExceededError: + raise ProviderQuotaExceededError() + except ModelCurrentlyNotSupportError: + raise ProviderModelCurrentlyNotSupportError() + except InvokeError as e: + raise CompletionRequestError(e.description) + except ValueError as e: + raise e + except Exception as e: + logging.exception("internal server error.") + raise InternalServerError() + + +api.add_resource(AudioApi, '/audio-to-text') +api.add_resource(TextApi, '/text-to-audio') diff --git a/api/controllers/web/app.py b/api/controllers/web/app.py index a51259f2e4..913f7fbee1 100644 --- a/api/controllers/web/app.py +++ b/api/controllers/web/app.py @@ -32,6 +32,7 @@ class AppParameterApi(WebApiResource): 'suggested_questions': fields.Raw, 'suggested_questions_after_answer': fields.Raw, 'speech_to_text': fields.Raw, + 'text_to_speech': fields.Raw, 'retriever_resource': fields.Raw, 'annotation_reply': fields.Raw, 'more_like_this': fields.Raw, @@ -51,6 +52,7 @@ class AppParameterApi(WebApiResource): 'suggested_questions': app_model_config.suggested_questions_list, 'suggested_questions_after_answer': app_model_config.suggested_questions_after_answer_dict, 'speech_to_text': app_model_config.speech_to_text_dict, + 'text_to_speech': app_model_config.text_to_speech_dict, 'retriever_resource': app_model_config.retriever_resource_dict, 'annotation_reply': app_model_config.annotation_reply_dict, 'more_like_this': app_model_config.more_like_this_dict, diff --git a/api/controllers/web/audio.py b/api/controllers/web/audio.py index edbe9b71b8..310374a256 100644 --- a/api/controllers/web/audio.py +++ b/api/controllers/web/audio.py @@ -28,7 +28,7 @@ class AudioApi(WebApiResource): file = request.files['file'] try: - response = AudioService.transcript( + response = AudioService.transcript_asr( tenant_id=app_model.tenant_id, file=file, ) @@ -59,4 +59,43 @@ class AudioApi(WebApiResource): logging.exception("internal server error.") raise InternalServerError() -api.add_resource(AudioApi, '/audio-to-text') \ No newline at end of file + +class TextApi(WebApiResource): + def post(self, app_model: App, end_user): + try: + response = AudioService.transcript_tts( + tenant_id=app_model.tenant_id, + text=request.form['text'], + end_user=end_user.external_user_id, + streaming=False + ) + + return {'data': response.data.decode('latin1')} + except services.errors.app_model_config.AppModelConfigBrokenError: + logging.exception("App model config broken.") + raise AppUnavailableError() + except NoAudioUploadedServiceError: + raise NoAudioUploadedError() + except AudioTooLargeServiceError as e: + raise AudioTooLargeError(str(e)) + except UnsupportedAudioTypeServiceError: + raise UnsupportedAudioTypeError() + except ProviderNotSupportSpeechToTextServiceError: + raise ProviderNotSupportSpeechToTextError() + except ProviderTokenNotInitError as ex: + raise ProviderNotInitializeError(ex.description) + except QuotaExceededError: + raise ProviderQuotaExceededError() + except ModelCurrentlyNotSupportError: + raise ProviderModelCurrentlyNotSupportError() + except InvokeError as e: + raise CompletionRequestError(e.description) + except ValueError as e: + raise e + except Exception as e: + logging.exception("internal server error.") + raise InternalServerError() + + +api.add_resource(AudioApi, '/audio-to-text') +api.add_resource(TextApi, '/text-to-audio') diff --git a/api/core/application_manager.py b/api/core/application_manager.py index 96ed0a41cb..86c0bc0d0d 100644 --- a/api/core/application_manager.py +++ b/api/core/application_manager.py @@ -555,6 +555,12 @@ class ApplicationManager: if 'enabled' in speech_to_text_dict and speech_to_text_dict['enabled']: properties['speech_to_text'] = True + # text to speech + text_to_speech_dict = copy_app_model_config_dict.get('text_to_speech') + if text_to_speech_dict: + if 'enabled' in text_to_speech_dict and text_to_speech_dict['enabled']: + properties['text_to_speech'] = True + # sensitive word avoidance sensitive_word_avoidance_dict = copy_app_model_config_dict.get('sensitive_word_avoidance') if sensitive_word_avoidance_dict: diff --git a/api/core/entities/application_entities.py b/api/core/entities/application_entities.py index 95a9d90f97..56172188e4 100644 --- a/api/core/entities/application_entities.py +++ b/api/core/entities/application_entities.py @@ -219,6 +219,7 @@ class AppOrchestrationConfigEntity(BaseModel): show_retrieve_source: bool = False more_like_this: bool = False speech_to_text: bool = False + text_to_speech: bool = False sensitive_word_avoidance: Optional[SensitiveWordAvoidanceEntity] = None @@ -283,7 +284,6 @@ class ApplicationGenerateEntity(BaseModel): query: Optional[str] = None files: list[FileObj] = [] user_id: str - # extras stream: bool invoke_from: InvokeFrom diff --git a/api/core/model_manager.py b/api/core/model_manager.py index e75f624f2e..20316510dd 100644 --- a/api/core/model_manager.py +++ b/api/core/model_manager.py @@ -12,6 +12,7 @@ from core.model_runtime.model_providers.__base.large_language_model import Large from core.model_runtime.model_providers.__base.moderation_model import ModerationModel from core.model_runtime.model_providers.__base.rerank_model import RerankModel from core.model_runtime.model_providers.__base.speech2text_model import Speech2TextModel +from core.model_runtime.model_providers.__base.tts_model import TTSModel from core.model_runtime.model_providers.__base.text_embedding_model import TextEmbeddingModel from core.provider_manager import ProviderManager @@ -144,7 +145,7 @@ class ModelInstance: user=user ) - def invoke_speech2text(self, file: IO[bytes], user: Optional[str] = None, **params) \ + def invoke_speech2text(self, file: IO[bytes], user: Optional[str] = None) \ -> str: """ Invoke large language model @@ -161,8 +162,29 @@ class ModelInstance: model=self.model, credentials=self.credentials, file=file, + user=user + ) + + def invoke_tts(self, content_text: str, streaming: bool, user: Optional[str] = None) \ + -> str: + """ + Invoke large language model + + :param content_text: text content to be translated + :param user: unique user id + :param streaming: output is streaming + :return: text for given audio file + """ + if not isinstance(self.model_type_instance, TTSModel): + raise Exception(f"Model type instance is not TTSModel") + + self.model_type_instance = cast(TTSModel, self.model_type_instance) + return self.model_type_instance.invoke( + model=self.model, + credentials=self.credentials, + content_text=content_text, user=user, - **params + streaming=streaming ) diff --git a/api/core/model_runtime/entities/model_entities.py b/api/core/model_runtime/entities/model_entities.py index 8ad9bd7206..23c492cedb 100644 --- a/api/core/model_runtime/entities/model_entities.py +++ b/api/core/model_runtime/entities/model_entities.py @@ -15,7 +15,7 @@ class ModelType(Enum): RERANK = "rerank" SPEECH2TEXT = "speech2text" MODERATION = "moderation" - # TTS = "tts" + TTS = "tts" # TEXT2IMG = "text2img" @classmethod @@ -33,6 +33,8 @@ class ModelType(Enum): return cls.RERANK elif origin_model_type == 'speech2text' or origin_model_type == cls.SPEECH2TEXT.value: return cls.SPEECH2TEXT + elif origin_model_type == 'tts' or origin_model_type == cls.TTS.value: + return cls.TTS elif origin_model_type == cls.MODERATION.value: return cls.MODERATION else: @@ -52,6 +54,8 @@ class ModelType(Enum): return 'reranking' elif self == self.SPEECH2TEXT: return 'speech2text' + elif self == self.TTS: + return 'tts' elif self == self.MODERATION: return 'moderation' else: @@ -120,6 +124,10 @@ class ModelPropertyKey(Enum): FILE_UPLOAD_LIMIT = "file_upload_limit" SUPPORTED_FILE_EXTENSIONS = "supported_file_extensions" MAX_CHARACTERS_PER_CHUNK = "max_characters_per_chunk" + DEFAULT_VOICE = "default_voice" + WORD_LIMIT = "word_limit" + AUDOI_TYPE = "audio_type" + MAX_WORKERS = "max_workers" class ProviderModel(BaseModel): diff --git a/api/core/model_runtime/model_providers/__base/tts_model.py b/api/core/model_runtime/model_providers/__base/tts_model.py new file mode 100644 index 0000000000..c3f3b65fa4 --- /dev/null +++ b/api/core/model_runtime/model_providers/__base/tts_model.py @@ -0,0 +1,42 @@ +from abc import abstractmethod +from typing import Optional + +from core.model_runtime.entities.model_entities import ModelType +from core.model_runtime.model_providers.__base.ai_model import AIModel + + +class TTSModel(AIModel): + """ + Model class for ttstext model. + """ + model_type: ModelType = ModelType.TTS + + def invoke(self, model: str, credentials: dict, content_text: str, streaming: bool, user: Optional[str] = None): + """ + Invoke large language model + + :param model: model name + :param credentials: model credentials + :param content_text: text content to be translated + :param streaming: output is streaming + :param user: unique user id + :return: translated audio file + """ + try: + return self._invoke(model=model, credentials=credentials, user=user, streaming=streaming, content_text=content_text) + except Exception as e: + raise self._transform_invoke_error(e) + + @abstractmethod + def _invoke(self, model: str, credentials: dict, content_text: str, streaming: bool, user: Optional[str] = None): + """ + Invoke large language model + + :param model: model name + :param credentials: model credentials + :param content_text: text content to be translated + :param streaming: output is streaming + :param user: unique user id + :return: translated audio file + """ + raise NotImplementedError diff --git a/api/core/model_runtime/model_providers/openai/openai.yaml b/api/core/model_runtime/model_providers/openai/openai.yaml index 02587576bf..3af99e107e 100644 --- a/api/core/model_runtime/model_providers/openai/openai.yaml +++ b/api/core/model_runtime/model_providers/openai/openai.yaml @@ -20,6 +20,7 @@ supported_model_types: - text-embedding - speech2text - moderation + - tts configurate_methods: - predefined-model - customizable-model diff --git a/api/core/model_runtime/model_providers/openai/tts/__init__.py b/api/core/model_runtime/model_providers/openai/tts/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/model_runtime/model_providers/openai/tts/tts-1-hd.yaml b/api/core/model_runtime/model_providers/openai/tts/tts-1-hd.yaml new file mode 100644 index 0000000000..aa7ed537a4 --- /dev/null +++ b/api/core/model_runtime/model_providers/openai/tts/tts-1-hd.yaml @@ -0,0 +1,7 @@ +model: tts-1-hd +model_type: tts +model_properties: + default_voice: 'alloy' + word_limit: 120 + audio_type: 'mp3' + max_workers: 5 diff --git a/api/core/model_runtime/model_providers/openai/tts/tts-1.yaml b/api/core/model_runtime/model_providers/openai/tts/tts-1.yaml new file mode 100644 index 0000000000..96f54a7340 --- /dev/null +++ b/api/core/model_runtime/model_providers/openai/tts/tts-1.yaml @@ -0,0 +1,7 @@ +model: tts-1 +model_type: tts +model_properties: + default_voice: 'alloy' + word_limit: 120 + audio_type: 'mp3' + max_workers: 5 diff --git a/api/core/model_runtime/model_providers/openai/tts/tts.py b/api/core/model_runtime/model_providers/openai/tts/tts.py new file mode 100644 index 0000000000..64e748ea28 --- /dev/null +++ b/api/core/model_runtime/model_providers/openai/tts/tts.py @@ -0,0 +1,235 @@ +import uuid +import hashlib +import subprocess +from io import BytesIO +from typing import Optional +from functools import reduce +from pydub import AudioSegment + +from core.model_runtime.entities.model_entities import ModelPropertyKey +from core.model_runtime.errors.validate import CredentialsValidateFailedError +from core.model_runtime.errors.invoke import InvokeBadRequestError +from core.model_runtime.model_providers.__base.tts_model import TTSModel +from core.model_runtime.model_providers.openai._common import _CommonOpenAI + +from typing_extensions import Literal +from flask import Response, stream_with_context +from openai import OpenAI +import concurrent.futures + + +class OpenAIText2SpeechModel(_CommonOpenAI, TTSModel): + """ + Model class for OpenAI Speech to text model. + """ + def _invoke(self, model: str, credentials: dict, content_text: str, streaming: bool, user: Optional[str] = None) -> any: + """ + _invoke text2speech model + + :param model: model name + :param credentials: model credentials + :param content_text: text content to be translated + :param streaming: output is streaming + :param user: unique user id + :return: text translated to audio file + """ + self._is_ffmpeg_installed() + audio_type = self._get_model_audio_type(model, credentials) + if streaming: + return Response(stream_with_context(self._tts_invoke_streaming(model=model, + credentials=credentials, + content_text=content_text, + user=user)), + status=200, mimetype=f'audio/{audio_type}') + else: + return self._tts_invoke(model=model, credentials=credentials, content_text=content_text, user=user) + + def validate_credentials(self, model: str, credentials: dict, user: Optional[str] = None) -> None: + """ + validate credentials text2speech model + + :param model: model name + :param credentials: model credentials + :param user: unique user id + :return: text translated to audio file + """ + try: + self._tts_invoke( + model=model, + credentials=credentials, + content_text='Hello world!', + user=user + ) + except Exception as ex: + raise CredentialsValidateFailedError(str(ex)) + + def _tts_invoke(self, model: str, credentials: dict, content_text: str, user: Optional[str] = None) -> any: + """ + _tts_invoke text2speech model + + :param model: model name + :param credentials: model credentials + :param content_text: text content to be translated + :param user: unique user id + :return: text translated to audio file + """ + audio_type = self._get_model_audio_type(model, credentials) + word_limit = self._get_model_word_limit(model, credentials) + max_workers = self._get_model_workers_limit(model, credentials) + + try: + sentences = list(self._split_text_into_sentences(text=content_text, limit=word_limit)) + audio_bytes_list = list() + + # Create a thread pool and map the function to the list of sentences + with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: + futures = [executor.submit(self._process_sentence, sentence, model, credentials) for sentence + in sentences] + for future in futures: + try: + audio_bytes_list.append(future.result()) + except Exception as ex: + raise InvokeBadRequestError(str(ex)) + + audio_segments = [AudioSegment.from_file(BytesIO(audio_bytes), format=audio_type) for audio_bytes in + audio_bytes_list if audio_bytes] + combined_segment = reduce(lambda x, y: x + y, audio_segments) + buffer: BytesIO = BytesIO() + combined_segment.export(buffer, format=audio_type) + buffer.seek(0) + return Response(buffer.read(), status=200, mimetype=f"audio/{audio_type}") + except Exception as ex: + raise InvokeBadRequestError(str(ex)) + + # Todo: To improve the streaming function + def _tts_invoke_streaming(self, model: str, credentials: dict, content_text: str, user: Optional[str] = None) -> any: + """ + _tts_invoke_streaming text2speech model + + :param model: model name + :param credentials: model credentials + :param content_text: text content to be translated + :param user: unique user id + :return: text translated to audio file + """ + # transform credentials to kwargs for model instance + credentials_kwargs = self._to_credential_kwargs(credentials) + voice_name = self._get_model_voice(model, credentials) + word_limit = self._get_model_word_limit(model, credentials) + audio_type = self._get_model_audio_type(model, credentials) + tts_file_id = self._get_file_name(content_text) + file_path = f'storage/generate_files/{audio_type}/{tts_file_id}.{audio_type}' + try: + client = OpenAI(**credentials_kwargs) + sentences = list(self._split_text_into_sentences(text=content_text, limit=word_limit)) + for sentence in sentences: + response = client.audio.speech.create(model=model, voice=voice_name, input=sentence.strip()) + response.stream_to_file(file_path) + except Exception as ex: + raise InvokeBadRequestError(str(ex)) + + def _get_model_voice(self, model: str, credentials: dict) -> Literal["alloy", "echo", "fable", "onyx", "nova", "shimmer"]: + """ + Get voice for given tts model + + :param model: model name + :param credentials: model credentials + :return: voice + """ + model_schema = self.get_model_schema(model, credentials) + + if model_schema and ModelPropertyKey.DEFAULT_VOICE in model_schema.model_properties: + return model_schema.model_properties[ModelPropertyKey.DEFAULT_VOICE] + + def _get_model_audio_type(self, model: str, credentials: dict) -> str: + """ + Get audio type for given tts model + + :param model: model name + :param credentials: model credentials + :return: voice + """ + model_schema = self.get_model_schema(model, credentials) + + if model_schema and ModelPropertyKey.AUDOI_TYPE in model_schema.model_properties: + return model_schema.model_properties[ModelPropertyKey.AUDOI_TYPE] + + def _get_model_word_limit(self, model: str, credentials: dict) -> int: + """ + Get audio type for given tts model + :return: audio type + """ + model_schema = self.get_model_schema(model, credentials) + + if model_schema and ModelPropertyKey.WORD_LIMIT in model_schema.model_properties: + return model_schema.model_properties[ModelPropertyKey.WORD_LIMIT] + + def _get_model_workers_limit(self, model: str, credentials: dict) -> int: + """ + Get audio max workers for given tts model + :return: audio type + """ + model_schema = self.get_model_schema(model, credentials) + + if model_schema and ModelPropertyKey.MAX_WORKERS in model_schema.model_properties: + return model_schema.model_properties[ModelPropertyKey.MAX_WORKERS] + + @staticmethod + def _split_text_into_sentences(text: str, limit: int, delimiters=None): + if delimiters is None: + delimiters = set('。!?;\n') + + buf = [] + word_count = 0 + for char in text: + buf.append(char) + if char in delimiters: + if word_count >= limit: + yield ''.join(buf) + buf = [] + word_count = 0 + else: + word_count += 1 + else: + word_count += 1 + + if buf: + yield ''.join(buf) + + @staticmethod + def _get_file_name(file_content: str) -> str: + hash_object = hashlib.sha256(file_content.encode()) + hex_digest = hash_object.hexdigest() + + namespace_uuid = uuid.UUID('a5da6ef9-b303-596f-8e88-bf8fa40f4b31') + unique_uuid = uuid.uuid5(namespace_uuid, hex_digest) + return str(unique_uuid) + + def _process_sentence(self, sentence: str, model: str, credentials: dict): + """ + _tts_invoke openai text2speech model api + + :param model: model name + :param credentials: model credentials + :param sentence: text content to be translated + :return: text translated to audio file + """ + # transform credentials to kwargs for model instance + credentials_kwargs = self._to_credential_kwargs(credentials) + voice_name = self._get_model_voice(model, credentials) + + client = OpenAI(**credentials_kwargs) + response = client.audio.speech.create(model=model, voice=voice_name, input=sentence.strip()) + if isinstance(response.read(), bytes): + return response.read() + + @staticmethod + def _is_ffmpeg_installed(): + try: + output = subprocess.check_output("ffmpeg -version", shell=True) + if "ffmpeg version" in output.decode("utf-8"): + return True + else: + raise InvokeBadRequestError("ffmpeg is not installed") + except Exception: + raise InvokeBadRequestError("ffmpeg is not installed") diff --git a/api/fields/app_fields.py b/api/fields/app_fields.py index 9030b2fe4d..63e8f5b16a 100644 --- a/api/fields/app_fields.py +++ b/api/fields/app_fields.py @@ -19,6 +19,7 @@ model_config_fields = { 'suggested_questions': fields.Raw(attribute='suggested_questions_list'), 'suggested_questions_after_answer': fields.Raw(attribute='suggested_questions_after_answer_dict'), 'speech_to_text': fields.Raw(attribute='speech_to_text_dict'), + 'text_to_speech': fields.Raw(attribute='text_to_speech_dict'), 'retriever_resource': fields.Raw(attribute='retriever_resource_dict'), 'annotation_reply': fields.Raw(attribute='annotation_reply_dict'), 'more_like_this': fields.Raw(attribute='more_like_this_dict'), diff --git a/api/migrations/versions/b24be59fbb04_.py b/api/migrations/versions/b24be59fbb04_.py new file mode 100644 index 0000000000..e19ea09e86 --- /dev/null +++ b/api/migrations/versions/b24be59fbb04_.py @@ -0,0 +1,32 @@ +"""empty message + +Revision ID: b24be59fbb04 +Revises: 187385f442fc +Create Date: 2024-01-17 01:31:12.670556 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b24be59fbb04' +down_revision = 'de95f5c77138' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('app_model_configs', schema=None) as batch_op: + batch_op.add_column(sa.Column('text_to_speech', sa.Text(), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('app_model_configs', schema=None) as batch_op: + batch_op.drop_column('text_to_speech') + + # ### end Alembic commands ### diff --git a/api/models/model.py b/api/models/model.py index f317113e8d..badaac9b57 100644 --- a/api/models/model.py +++ b/api/models/model.py @@ -146,6 +146,7 @@ class AppModelConfig(db.Model): suggested_questions = db.Column(db.Text) suggested_questions_after_answer = db.Column(db.Text) speech_to_text = db.Column(db.Text) + text_to_speech = db.Column(db.Text) more_like_this = db.Column(db.Text) model = db.Column(db.Text) user_input_form = db.Column(db.Text) @@ -184,6 +185,11 @@ class AppModelConfig(db.Model): return json.loads(self.speech_to_text) if self.speech_to_text \ else {"enabled": False} + @property + def text_to_speech_dict(self) -> dict: + return json.loads(self.text_to_speech) if self.text_to_speech \ + else {"enabled": False} + @property def retriever_resource_dict(self) -> dict: return json.loads(self.retriever_resource) if self.retriever_resource \ @@ -263,6 +269,7 @@ class AppModelConfig(db.Model): "suggested_questions": self.suggested_questions_list, "suggested_questions_after_answer": self.suggested_questions_after_answer_dict, "speech_to_text": self.speech_to_text_dict, + "text_to_speech": self.text_to_speech_dict, "retriever_resource": self.retriever_resource_dict, "annotation_reply": self.annotation_reply_dict, "more_like_this": self.more_like_this_dict, @@ -289,6 +296,8 @@ class AppModelConfig(db.Model): self.suggested_questions_after_answer = json.dumps(model_config['suggested_questions_after_answer']) self.speech_to_text = json.dumps(model_config['speech_to_text']) \ if model_config.get('speech_to_text') else None + self.text_to_speech = json.dumps(model_config['text_to_speech']) \ + if model_config.get('text_to_speech') else None self.more_like_this = json.dumps(model_config['more_like_this']) self.sensitive_word_avoidance = json.dumps(model_config['sensitive_word_avoidance']) \ if model_config.get('sensitive_word_avoidance') else None @@ -323,6 +332,7 @@ class AppModelConfig(db.Model): suggested_questions=self.suggested_questions, suggested_questions_after_answer=self.suggested_questions_after_answer, speech_to_text=self.speech_to_text, + text_to_speech=self.text_to_speech, more_like_this=self.more_like_this, sensitive_word_avoidance=self.sensitive_word_avoidance, external_data_tools=self.external_data_tools, diff --git a/api/requirements.txt b/api/requirements.txt index 639941e39b..08ceb1b830 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -65,3 +65,4 @@ httpx[socks]~=0.24.1 pydub~=0.25.1 matplotlib~=3.8.2 yfinance~=0.2.35 +pydub~=0.25.1 \ No newline at end of file diff --git a/api/services/account_service.py b/api/services/account_service.py index 076edf3786..e14f46dfee 100644 --- a/api/services/account_service.py +++ b/api/services/account_service.py @@ -86,13 +86,13 @@ class AccountService: db.session.commit() return account - + @staticmethod def get_account_jwt_token(account): payload = { "user_id": account.id, "exp": datetime.utcnow() + timedelta(days=30), - "iss": current_app.config['EDITION'], + "iss": current_app.config['EDITION'], "sub": 'Console API Passport', } @@ -345,7 +345,7 @@ class TenantService: } if action not in ['add', 'remove', 'update']: raise InvalidActionError("Invalid action.") - + if member: if operator.id == member.id: raise CannotOperateSelfError("Cannot operate self.") @@ -546,10 +546,10 @@ class RegisterService: return None return { - 'account': account, - 'data': invitation_data, - 'tenant': tenant, - } + 'account': account, + 'data': invitation_data, + 'tenant': tenant, + } @classmethod def _get_invitation_by_token(cls, token: str, workspace_id: str, email: str) -> Optional[Dict[str, str]]: diff --git a/api/services/app_model_config_service.py b/api/services/app_model_config_service.py index f4e697f356..9c367a429e 100644 --- a/api/services/app_model_config_service.py +++ b/api/services/app_model_config_service.py @@ -95,6 +95,21 @@ class AppModelConfigService: if not isinstance(config["speech_to_text"]["enabled"], bool): raise ValueError("enabled in speech_to_text must be of boolean type") + # text_to_speech + if 'text_to_speech' not in config or not config["text_to_speech"]: + config["text_to_speech"] = { + "enabled": False + } + + if not isinstance(config["text_to_speech"], dict): + raise ValueError("text_to_speech must be of dict type") + + if "enabled" not in config["text_to_speech"] or not config["text_to_speech"]["enabled"]: + config["text_to_speech"]["enabled"] = False + + if not isinstance(config["text_to_speech"]["enabled"], bool): + raise ValueError("enabled in text_to_speech must be of boolean type") + # return retriever resource if 'retriever_resource' not in config or not config["retriever_resource"]: config["retriever_resource"] = { @@ -317,6 +332,7 @@ class AppModelConfigService: "suggested_questions": config["suggested_questions"], "suggested_questions_after_answer": config["suggested_questions_after_answer"], "speech_to_text": config["speech_to_text"], + "text_to_speech": config["text_to_speech"], "retriever_resource": config["retriever_resource"], "more_like_this": config["more_like_this"], "sensitive_word_avoidance": config["sensitive_word_avoidance"], diff --git a/api/services/audio_service.py b/api/services/audio_service.py index 8d9a1e3b89..44aac41880 100644 --- a/api/services/audio_service.py +++ b/api/services/audio_service.py @@ -1,22 +1,26 @@ import io +from typing import Optional from core.model_manager import ModelManager from core.model_runtime.entities.model_entities import ModelType -from services.errors.audio import (AudioTooLargeServiceError, NoAudioUploadedServiceError, - ProviderNotSupportSpeechToTextServiceError, UnsupportedAudioTypeServiceError) +from services.errors.audio import (AudioTooLargeServiceError, + NoAudioUploadedServiceError, + ProviderNotSupportTextToSpeechServiceError, + ProviderNotSupportSpeechToTextServiceError, + UnsupportedAudioTypeServiceError) from werkzeug.datastructures import FileStorage FILE_SIZE = 15 FILE_SIZE_LIMIT = FILE_SIZE * 1024 * 1024 -ALLOWED_EXTENSIONS = ['mp3', 'mp4', 'mpeg', 'mpga', 'm4a', 'wav', 'webm'] +ALLOWED_EXTENSIONS = ['mp3', 'mp4', 'mpeg', 'mpga', 'm4a', 'wav', 'webm', 'amr'] class AudioService: @classmethod - def transcript(cls, tenant_id: str, file: FileStorage): + def transcript_asr(cls, tenant_id: str, file: FileStorage, end_user: Optional[str] = None): if file is None: raise NoAudioUploadedServiceError() - + extension = file.mimetype if extension not in [f'audio/{ext}' for ext in ALLOWED_EXTENSIONS]: raise UnsupportedAudioTypeServiceError() @@ -33,8 +37,26 @@ class AudioService: tenant_id=tenant_id, model_type=ModelType.SPEECH2TEXT ) + if model_instance is None: + raise ProviderNotSupportSpeechToTextServiceError() buffer = io.BytesIO(file_content) buffer.name = 'temp.mp3' - return {"text": model_instance.invoke_speech2text(buffer)} + return {"text": model_instance.invoke_speech2text(file=buffer, user=end_user)} + + @classmethod + def transcript_tts(cls, tenant_id: str, text: str, streaming: bool, end_user: Optional[str] = None): + model_manager = ModelManager() + model_instance = model_manager.get_default_model_instance( + tenant_id=tenant_id, + model_type=ModelType.TTS + ) + if model_instance is None: + raise ProviderNotSupportTextToSpeechServiceError() + + try: + audio_response = model_instance.invoke_tts(content_text=text.strip(), user=end_user, streaming=streaming) + return audio_response + except Exception as e: + raise e diff --git a/api/services/errors/audio.py b/api/services/errors/audio.py index 8c6508936d..091ce36588 100644 --- a/api/services/errors/audio.py +++ b/api/services/errors/audio.py @@ -9,5 +9,10 @@ class AudioTooLargeServiceError(Exception): class UnsupportedAudioTypeServiceError(Exception): pass + class ProviderNotSupportSpeechToTextServiceError(Exception): - pass \ No newline at end of file + pass + + +class ProviderNotSupportTextToSpeechServiceError(Exception): + pass diff --git a/web/app/components/app/chat/answer/index.tsx b/web/app/components/app/chat/answer/index.tsx index da2bf65e15..7c13a17f66 100644 --- a/web/app/components/app/chat/answer/index.tsx +++ b/web/app/components/app/chat/answer/index.tsx @@ -13,6 +13,7 @@ import MoreInfo from '../more-info' import CopyBtn from '../copy-btn' import Thought from '../thought' import Citation from '../citation' +import AudioBtn from '@/app/components/base/audio-btn' import { randomString } from '@/utils' import type { MessageRating } from '@/models/log' import Tooltip from '@/app/components/base/tooltip' @@ -53,6 +54,7 @@ export type IAnswerProps = { dataSets?: DataSet[] isShowCitation?: boolean isShowCitationHitInfo?: boolean + isShowTextToSpeech?: boolean // Annotation props supportAnnotation?: boolean appId?: string @@ -75,6 +77,7 @@ const Answer: FC = ({ citation, isShowCitation, isShowCitationHitInfo = false, + isShowTextToSpeech, supportAnnotation, appId, question, @@ -322,7 +325,13 @@ const Answer: FC = ({ className={cn(s.copyBtn, 'mr-1')} /> )} - {(supportAnnotation && !item.isOpeningStatement) && ( + {!item.isOpeningStatement && isShowTextToSpeech && ( + + )} + {(!item.isOpeningStatement && supportAnnotation) && ( = ({ isShowSuggestion, suggestionList, isShowSpeechToText, + isShowTextToSpeech, isShowCitation, answerIcon, isShowConfigElem, @@ -222,6 +224,7 @@ const Chat: FC = ({ dataSets={dataSets} isShowCitation={isShowCitation} isShowCitationHitInfo={isShowCitationHitInfo} + isShowTextToSpeech={isShowTextToSpeech} supportAnnotation={supportAnnotation} appId={appId} question={chatList[index - 1]?.content} diff --git a/web/app/components/app/chat/style.module.css b/web/app/components/app/chat/style.module.css index 46a4672625..0e0d67e35a 100644 --- a/web/app/components/app/chat/style.module.css +++ b/web/app/components/app/chat/style.module.css @@ -39,6 +39,7 @@ } .copyBtn, +.playBtn, .annotationBtn { display: none; } @@ -65,6 +66,7 @@ } .answerWrap:hover .copyBtn, +.answerWrap:hover .playBtn, .answerWrap:hover .annotationBtn { display: block; } diff --git a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/citations-and-attributions-preview@2x.png b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/citations-and-attributions-preview@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ef066204ca42478c976a0136385221a9464fb9b6 GIT binary patch literal 20827 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 S6Vpuq0000bQ7k~|VfI^{kel1jKD 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 VA{u6Wnt$HVkd;!BEE6{j{4XB~qQ?LL literal 0 HcmV?d00001 diff --git a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/more-like-this-preview@2x.png b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/more-like-this-preview@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..62671c5889edba480d8454d5e767db9482772b33 GIT binary patch literal 19839 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 HCj9>a)KP2< literal 0 HcmV?d00001 diff --git a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/speech-to-text-preview@2x.png b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/speech-to-text-preview@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..68df3983dc7df3a163fdec8041b7e9ebfc2c3b56 GIT binary patch literal 16929 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 z%0Da`F>*A)5Do&+T<-59+Ae@5)8~CeEI&Pj=r1f!L|)bkLtE>9k4p;N)Q!^nyG7&_ z;HJVBbu&1td7)54=Ihkhx_Ngh*Qa)$uDl?Qu760+1ZTyqbYdh}`{F#G9R2M4%dN zOUB61=lZJmVp{r0;F|Y$%RvDAz4hr|ui?1=B8oC{13sNLeB^kCU{~_-^3qG6f0TTU zNT6=r!)_kHsP0pIm~`berutR3p6BcF@dnOGl|oH~Y8@3;oK@J(aIeND4|JVC8i;Ur zhYCYN(xjoT7l14y4J^^=+o67Qw+5thm&a;TT z9JvA$3CnZk5WW@M6|C+F=MRjLgN*k)2`-C?o z$sIE2SKKE}GDS1+Bqk^#hsw&>gUFRU^>m4;yc34*y2PMA#q5a<^2LSNI7x~m`Q&vx=x;NtkT4gT zN`vXA)06zs$b%p~93janj~u_w++ex-u$SpKFt3*p9|f11@-k_dq06^@*&RP@trwRA z0}~DePXdVl+AVov;*D1he9rJ;jqRQ5m5PX(Z~|)*|J)3en`j~ht`#m?%wt81Ivg(E zUK&FIGpI~h(z07KMqya6cP|y5tgQh#Q6^wQkC>d5=cn@dz!`~f{8|x(4kXCUr<0V3i(jLAIN5?C-{Z* z+@HeG|6G~yz?lsEPd%r`+A0#HZFDrMl}y9g8w4OgLMVW#v>~4qMkHIHLu>~6uDk4X zFzCPZ(6ut$Em>>Hn1)0w9CksOttu*wJ;F__?9&tc1_cuARzV_$7GK2!TXr+}bG5Qk z)o@1WLmZlT$#DJWy`smvQoqf-(WbMr-Bos%De%Io!ANz-vtD4x#jXU|9^O#!ZnB11 zISnOul{#IRG3z=TlOb0AH)T6YcIV4$;SDtzZJ|aNqJm zB4i{5{oq_#3dZrN$n9VuFOr`!-tTkX^D)RwF*PI|c!KYg8513-Cg`7h+yk|%6p}&e zIrsyNPzRgNNbviUYj0Y08XH-YHdD_!DxEb0bdKAPvXp4?ci4#lfVJuIDJ`jS@Fu3& z^Z0PJ#wZdPUcXAg-`;A|5pk^8_AR!c`{Lt@m_}l#6*5 zw9bItjFDPM)Rqvl<##(Stp+)qel9f0sF+rUQU>SN{0&)|)!*U$_t|pqUV$O}mC~If zrZ{0BE5jEZ*6;dM(e3K(7h?^nqelYt7_7%>1oT0D7Of9RQ;x7}7n}kn^k;I{UnyB>u7| z&2dLsmTY{W1@auUHaHb2`u1-8?}*JO?xIBQP*4NGfVA=ojUEsQf(#Oqm=OZ%FoeOR zm51oaC%QN6kkKc5VSrWkfpAOF`2z zDa?iIezb#_d1x2!yPock<`TN=Uz5ib91()S^d4?zwk3K<3eUDSziAatkF~r(@*N%< zt9NVVW2)$ygV+54RuUj2_E@GLzXvcd&N5i}c@GL}L-&5;yV6+NOT3n=+3{M;oayW* zCZ(6>RQozh^(8wSER-35m9M}XRG;9Cj5TZT6M^BRQZdxS`t7rF%RBlgYYK*yj$wF@ z&Rd2rllj*yjIpBk>%|5Ha&yV9kv=He6+R*d#ej@z6NT&x;|{V@v;D8sm7PIz3{{#a z$R1V8EhN_EW$gIBJFlo_zX0GS{*Ff_;`cHr8KJ)<~$stN?Ka7we4_=qeU>@ll zq(-$0qTarKY&k3R$pAqs){!V(=@3=6U(uYAVfcT|1BbRY#S0*S_!#Sf*Ry8;h$^vR z`eOFxP_n$_h`g?QigLa5XCq-|U+Dy5dS|JcRFc5Nih^_$tS&>W!#^*B&GGFRINk<( z*`qkH`bEVDnjdN0(}}TeM{ny_N)kXEsli1kSaeO+T~NRPR>Jrd4zV`bT_5dpG`qkq zjM3hr(`21s-QN6Fksf3NX=QGRUM5*F6C@x6nWE+exHX&}%3?8}+)zP|5zm3G=>`T*f4TT7!d+9vH!BnHX%;ZH5j}Y63OH zV%O`s1lgrikbx59>Nen7rFr!D?Ri$Hq>$9Cq{OUq=73u^ODjhw`bvtFJg{bS<<$$s zHOVKyo{Bo!*{|U7U+f7!USAuM$qd2=%N+;1=!}|EKEqfC&%1JZI_TOIfOxIQ^Hv!W zi76I`Y}0|&j9g}59ac#ufn739pFN=Xv{d81^u0G}pu7gEW)LRmRPnmPEZ1}Ut@3?G zl>%VzW&x$*1#f6b%n~z@oMIpb*;-E>N&0?K=Wf#wvQvEhBK(2Hm>^nw4b|fCRfFCD z{-5Qldjlw73Fb(P8b<=-4;kVFSnBi_P&K^o(hS_z@x#%KP!{BH8_wILQuxRxVq#Y| z>9JC}rde}-(}NLZJg=2A9++m~Wjb0z?OxZ4G2zc0OT!rHly<7x@cthR&`S0gl4l2R zEd0I5ek*C#A6*3qczgoTSwv9YFeJbFhfR6PvEk2(#l0i@X!@mUks|Ew3yF(IlA;2Ku2?ArRY z=LgM`V;e8(j@=%$zNRmtfr)`~f=-)(n6OR5P;#+gdgUdWZ0(Qy=a#?Fmn!JQ5?TJJ z{SN7)@?pJQ)DZ#fjgamfdkKP|m>}eVjenYvn4pK&@Y>eq5)&a|Lt(R6(4Lt5*>-eA zcmj0aV8oUXxTbMKLm}Hfe|68GFW72Gdl@QvkFe(triP@g<>ptmVI@%#D(U0IT|NH><>HcSBt7)Isd>Rb_Myu zCT?KF8&ATs*B#lR07rw;j|@MZ=dDlFe3$66^?UYcmlN-n!(z3Zm*B@*FSw2q!kcnf zFnXE_rGE=bsb%o+VUcE4Tx&$^j#-zeXlx>qY(a)ql(JW$fJB@~5c>J zHo40!UF>n*_I$-i^HxQ7uld+zHq^=51oN$1S@FmA!W;j?!7;V{w@MyuIQl$E`=;Ba|OLicz3K-1 zU;Z1!zLgz^%u0Z4JsfVIJTce06l6z@O4<6ZGr)CcNHFPlyJr%ULw2&}uR??I@Dn|K zecl9Rp~6ajQ`lf>oQ1<0@5oZn=$F+Ce%!P1iFc>J#8enmSEH09Cpwq(4D&|M`z`~% zx;k3SP3Hb$HthmE8tFHD9nuZ}7p{`5i_3e0Gd;t9x06Sc@AVMpw_97lY7D>>kYl^W zpnNP9{3LELL4c|RRtmH^C3z9N^+P*D^JQZ+sIFMi3+K-;bHXsp?AvzhWmu6A!W4cc%o~MhXKRacj+fn zozQy%%eMTolPd$)X9N0=+ftin`6L69pDbl6N$!ApVa7?BaxOCWb+n*|l63LYycbV0 z-PT#uLS13SAaJ-$GNyAU(_96pR|edRPKAXpz%2<1ipPRcriH^~dBd~#cwCvn&3R=k zUHY@lm3J}Y)Av>R>uHGM)W^H4a=qFbV7Mkg58O>LOQY6>p zNcK0TU5QZEw+3c06$@&P?6wRn*zLccTep9yqqCqmZQt_sNSoSRX;jToSG(i*c)n{| z0`;X{%@1~O*1tPPH~4QG@?FLgE5dlAI5vcb=g!yC7)tu7e#NuVZk+R!Q2!csW}~%i zr&IgMdkx~ANsEYZ(oYOowk&WBt=N7|I;NTD8WZ8U^O@6WP5dQizecz5=DI?V>!}KM$Mi1OgL~*Lo_h*N_m>a!87>uO9xK!KQNUL7)pFZqAQO)YG)S;;LAC9kuI043 z>0I_^GJP#rhqYONva?ug90tXVCLH9?aQb=1&y7^f)Xn6Ent~cKO_7YsuG)#dM~GiS zr)RIp8(po4s?X3+(Lj0~5 z^k_01T2sAaD519WNf^COGbKhlM6A?N(EKa4&mHPmdAr&23yJSnjQlO+1;v zOrGQ2o2)5V2wzlGV-h^AYdk)S;=~vKQMpT{xIE3Q)OrPa>NdKTi5FlU_diSz$G;TB z3|WC|Xl24~jN&!)9HrK3+hX9@Dbbjf7#j+ar^b_`BKB_%%&vL#Ph$RZk|^27IEH9a z_j$eSMBYGtqt_?b(M{493UaQd9q1ut6%l5S{dVjw4$EoP$A*NoXniQ&u@j-&pm#Cx zu6_G%yfb|7BIEYg`KHv_rax=x6k4g6%|Yo3S?-v~wo&w6uekptCSt_J-p$XwzWrbjkT=Aycy1^7lQRv=GTMn-zUuCFjYU zu$Db+oTD@~(jo2vF=nd)#dAd*5*b;go(`7&X8h3#O*Dv%mHhmD{9!GrSF1i&h7|y4 z0FLK-@6NE`7v?!6jtZ9~KY#Q;hY!G)?+?(b#J07miOXi^F0N+&5D;G}o>@EkEH4Ch zHLBOk4f=o7xjl;DvWb^mbsF$;-%&~?J+5>W%`0Tmqy2H&rjdKN;$%%5!ATRKNUvyv zRMGr&dGFs3=`r1k3l6_W%0}}%7BX3O@F5@_*mmU<%iwY_?+^T~yY8@RsPZu$4c6i<4WipICm{mbmCt0jgj;u1yi@`uf3xsL+t{>e%* z`uNA7BFK*dAeLwcV$|7Y#daJ-xNV9?fnuT}CLsz|*v-M@#UtWnK&gN@gRg!UX9gR7 zh&7qNV44+$NY=X z;RXZJ2eO-r^6aBNsXO>)+dE3G)E$$91FKa7&%B>P=9G)(e&~!}Hy&3iQs}6<6Ctym<0_TlWV5_3e`E%))L$ym= zAdc|p?47Rtgl7ws1%8iC-^%Ski|t-;(gWQXPf@0+`EXy=-`=+6L7QTspU({U_cvE2 z)W|4-Y;0`Gf*-BXk4qPs519V4U|BzR8$BkZgXq>^{J#0joq;^6E=A+lPh*r#M}A-7 zNBM)cJJLGOOH25hTA3PBM-`6`avcv9FVvg9u1O5vDl;>hz+0bLzhlsbMblvJkq1VT z+=B7Cjcat;wZcN8nXs+*d1afdSI*KMnRL{047^O4#_#ca$;<$n9ojD$&s7)B5Ie!RV83Am{GoG;&MOj9ZVvx|82rNO8W($5|) zj%%>lHP|YyBHtaWSrm`rFT?}g^lFs86O={^xxM?ik(hf9xw-0^VcjK5ly@TNimLHJ zXE^Z*{NI0Y_maJcvy*@6YeDYIqs6S9rc)5y_QL1@i}lw~szCzmbMKppi9JxZuZiNQ! z!u?O#kE1(y$_dbuFYR&yv)+=VR!4o~XdUA?r3^Nhp!&0Y8TV7J9Gb%2O-hs&w93DC zU3{?Nct{W*6_(BgnThc2LX)@O+(ew(NHt;4E!cl<`Nl;YJ*~S_8SpLJL>u4BAajDB z+t-5U9KY8a+@pEa%{7Umgu-x0K=q9uwK<`KqC>2?m^l&VpxU#Q0*f!(SuwF@aunp6SzBnSL6VL`4J z6vtW)=pA-3psoYcq_R!-syCDgNLB7eT6AX4F+1 zeZSrOSy6Sywl53(i_u;2zT5$+dpOXV;}7q2m_~hB^}VoJg7UO$^ghtPNpJqpBDg(k zhOJD@hv$>>(Z5{zvMoD$MU2~=yx-vrO(MZAyA}P6qYoUJSLFEZRTA2`=KGT1>)*;lkVw%x z|8xHp6#G+^!qo~d#T&(2fIpHhEBMJbesJQl>GhK2;KGxQ4@L#ofCJ?=b4>m&-t9a7 zKAIzv_Y>>_QX=1zf~s?2WyWtBs>G=Vgklbdb47-C`tHrBN#b7ut@8(RK-@kGY{*RYlI;@$~ zaFc?IF`fX+jeQSryGjnGH)-hVSY=epAA$fJE@W1mR6Uu_FY^)~`7Ysfl0CIG$lslC z6^jX#t?m6_F=4z?OW>jxbnOoy`ghPO~e13+zdcic^_e;w~ntsrc4Vw)hEgA zH006|M6WK4IJz$_{<6Xtc=}YT0({pLA_!laPg14s3vnM3v)smvB+VZY_|$A04Gvk) ze6*F|#>C?}L_Eg{AMh`h7K6vF^zG$k;uym53*mb@ct&+XK5f?BL$8f?*~UW5m0N=t zR3VCr$X;A13f hj$0{O9*@4D$yxLhqs^_L|9fZ%qM#;UCTkk>zW|xPJ=p*N literal 0 HcmV?d00001 diff --git a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/text-to-audio-preview-assistant@2x.png b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/text-to-audio-preview-assistant@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..91396e72c75b34a9707de3d258d41f3d321f2508 GIT binary patch literal 23500 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

SDUaRr+>9aIL|AS=u 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 literal 0 HcmV?d00001 diff --git a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/style.module.css b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/style.module.css index 63139bda56..20f0534744 100644 --- a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/style.module.css +++ b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/style.module.css @@ -29,6 +29,11 @@ background-image: url(./preview-imgs/speech-to-text.svg); } +.textToSpeechPreview { + @apply shadow-lg rounded-lg; + background-image: url(./preview-imgs/text-to-audio-preview-assistant@2x.png); +} + .citationPreview { background-image: url(./preview-imgs/citation.svg); -} \ No newline at end of file +} diff --git a/web/app/components/app/configuration/config/feature/choose-feature/index.tsx b/web/app/components/app/configuration/config/feature/choose-feature/index.tsx index 0a9814b73d..8364f9529d 100644 --- a/web/app/components/app/configuration/config/feature/choose-feature/index.tsx +++ b/web/app/components/app/configuration/config/feature/choose-feature/index.tsx @@ -7,7 +7,7 @@ import MoreLikeThisIcon from '../../../base/icons/more-like-this-icon' import FeatureItem from './feature-item' import Modal from '@/app/components/base/modal' import SuggestedQuestionsAfterAnswerIcon from '@/app/components/app/configuration/base/icons/suggested-questions-after-answer-icon' -import { Microphone01 } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' +import { Microphone01, Speaker } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' import { Citations } from '@/app/components/base/icons/src/vender/solid/editor' import { FileSearch02 } from '@/app/components/base/icons/src/vender/solid/files' import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication' @@ -16,6 +16,7 @@ type IConfig = { moreLikeThis: boolean suggestedQuestionsAfterAnswer: boolean speechToText: boolean + textToSpeech: boolean citation: boolean moderation: boolean annotation: boolean @@ -27,6 +28,7 @@ export type IChooseFeatureProps = { config: IConfig isChatApp: boolean onChange: (key: string, value: boolean) => void + showTextToSpeechItem?: boolean showSpeechToTextItem?: boolean } @@ -42,6 +44,7 @@ const ChooseFeature: FC = ({ isChatApp, config, onChange, + showTextToSpeechItem, showSpeechToTextItem, }) => { const { t } = useTranslation() @@ -78,6 +81,18 @@ const ChooseFeature: FC = ({ value={config.suggestedQuestionsAfterAnswer} onChange={value => onChange('suggestedQuestionsAfterAnswer', value)} /> + { + showTextToSpeechItem && ( + } + previewImgClassName='textToSpeechPreview' + title={t('appDebug.feature.textToSpeech.title')} + description={t('appDebug.feature.textToSpeech.description')} + value={config.textToSpeech} + onChange={value => onChange('textToSpeech', value)} + /> + ) + } { showSpeechToTextItem && ( = ({ value={config.moreLikeThis} onChange={value => onChange('moreLikeThis', value)} /> + { + showTextToSpeechItem && ( + } + previewImgClassName='textToSpeechPreview' + title={t('appDebug.feature.textToSpeech.title')} + description={t('appDebug.feature.textToSpeech.description')} + value={config.textToSpeech} + onChange={value => onChange('textToSpeech', value)} + /> + ) + } )} diff --git a/web/app/components/app/configuration/config/feature/use-feature.tsx b/web/app/components/app/configuration/config/feature/use-feature.tsx index 5ec0d8af02..190c50eab5 100644 --- a/web/app/components/app/configuration/config/feature/use-feature.tsx +++ b/web/app/components/app/configuration/config/feature/use-feature.tsx @@ -9,6 +9,8 @@ function useFeature({ setSuggestedQuestionsAfterAnswer, speechToText, setSpeechToText, + textToSpeech, + setTextToSpeech, citation, setCitation, annotation, @@ -24,6 +26,8 @@ function useFeature({ setSuggestedQuestionsAfterAnswer: (suggestedQuestionsAfterAnswer: boolean) => void speechToText: boolean setSpeechToText: (speechToText: boolean) => void + textToSpeech: boolean + setTextToSpeech: (textToSpeech: boolean) => void citation: boolean setCitation: (citation: boolean) => void annotation: boolean @@ -48,6 +52,7 @@ function useFeature({ moreLikeThis, suggestedQuestionsAfterAnswer, speechToText, + textToSpeech, citation, annotation, moderation, @@ -69,6 +74,9 @@ function useFeature({ case 'speechToText': setSpeechToText(value) break + case 'textToSpeech': + setTextToSpeech(value) + break case 'citation': setCitation(value) break diff --git a/web/app/components/app/configuration/config/index.tsx b/web/app/components/app/configuration/config/index.tsx index f1eb9fd703..e9d7d3eef5 100644 --- a/web/app/components/app/configuration/config/index.tsx +++ b/web/app/components/app/configuration/config/index.tsx @@ -19,7 +19,7 @@ import AdvancedModeWaring from '@/app/components/app/configuration/prompt-mode/a import ConfigContext from '@/context/debug-configuration' import ConfigPrompt from '@/app/components/app/configuration/config-prompt' import ConfigVar from '@/app/components/app/configuration/config-var' -import { type CitationConfig, type ModelConfig, type ModerationConfig, type MoreLikeThisConfig, PromptMode, type PromptVariable, type SpeechToTextConfig, type SuggestedQuestionsAfterAnswerConfig } from '@/models/debug' +import { type CitationConfig, type ModelConfig, type ModerationConfig, type MoreLikeThisConfig, PromptMode, type PromptVariable, type SpeechToTextConfig, type SuggestedQuestionsAfterAnswerConfig, type TextToSpeechConfig } from '@/models/debug' import { AppType, ModelModeType } from '@/types/app' import { useModalContext } from '@/context/modal-context' import ConfigParamModal from '@/app/components/app/configuration/toolbox/annotation/config-param-modal' @@ -51,6 +51,8 @@ const Config: FC = () => { setSuggestedQuestionsAfterAnswerConfig, speechToTextConfig, setSpeechToTextConfig, + textToSpeechConfig, + setTextToSpeechConfig, citationConfig, setCitationConfig, annotationConfig, @@ -60,6 +62,7 @@ const Config: FC = () => { } = useContext(ConfigContext) const isChatApp = mode === AppType.chat const { data: speech2textDefaultModel } = useDefaultModel(4) + const { data: text2speechDefaultModel } = useDefaultModel(5) const { setShowModerationSettingModal } = useModalContext() const promptTemplate = modelConfig.configs.prompt_template @@ -111,6 +114,12 @@ const Config: FC = () => { draft.enabled = value })) }, + textToSpeech: textToSpeechConfig.enabled, + setTextToSpeech: (value) => { + setTextToSpeechConfig(produce(textToSpeechConfig, (draft: TextToSpeechConfig) => { + draft.enabled = value + })) + }, citation: citationConfig.enabled, setCitation: (value) => { setCitationConfig(produce(citationConfig, (draft: CitationConfig) => { @@ -173,7 +182,7 @@ const Config: FC = () => { setAnnotationConfig, }) - const hasChatConfig = isChatApp && (featureConfig.openingStatement || featureConfig.suggestedQuestionsAfterAnswer || (featureConfig.speechToText && !!speech2textDefaultModel) || featureConfig.citation) + const hasChatConfig = isChatApp && (featureConfig.openingStatement || featureConfig.suggestedQuestionsAfterAnswer || (featureConfig.speechToText && !!speech2textDefaultModel) || (featureConfig.textToSpeech && !!text2speechDefaultModel) || featureConfig.citation) const hasToolbox = moderationConfig.enabled || featureConfig.annotation const wrapRef = useRef(null) @@ -207,6 +216,7 @@ const Config: FC = () => { config={featureConfig} onChange={handleFeatureChange} showSpeechToTextItem={!!speech2textDefaultModel} + showTextToSpeechItem={!!text2speechDefaultModel} /> )} @@ -255,16 +265,21 @@ const Config: FC = () => { } } isShowSuggestedQuestionsAfterAnswer={featureConfig.suggestedQuestionsAfterAnswer} + isShowTextToSpeech={featureConfig.textToSpeech && !!text2speechDefaultModel} isShowSpeechText={featureConfig.speechToText && !!speech2textDefaultModel} isShowCitation={featureConfig.citation} /> ) } - {/* TextnGeneration config */} - {moreLikeThisConfig.enabled && ( - - )} + {/* TextnGeneration config */}{ + !hasChatConfig && ( + + ) + } {/* Toolbox */} { diff --git a/web/app/components/app/configuration/debug/index.tsx b/web/app/components/app/configuration/debug/index.tsx index 733f596333..8a8d089f37 100644 --- a/web/app/components/app/configuration/debug/index.tsx +++ b/web/app/components/app/configuration/debug/index.tsx @@ -56,6 +56,7 @@ const Debug: FC = ({ suggestedQuestions, suggestedQuestionsAfterAnswerConfig, speechToTextConfig, + textToSpeechConfig, citationConfig, moderationConfig, moreLikeThisConfig, @@ -73,6 +74,7 @@ const Debug: FC = ({ annotationConfig, } = useContext(ConfigContext) const { data: speech2textDefaultModel } = useDefaultModel(4) + const { data: text2speechDefaultModel } = useDefaultModel(5) const [chatList, setChatList, getChatList] = useGetState([]) const chatListDomRef = useRef(null) const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig) @@ -233,6 +235,9 @@ const Debug: FC = ({ setChatList(newListWithAnswer) } const postModelConfig: BackendModelConfig = { + text_to_speech: { + enabled: false, + }, pre_prompt: !isAdvancedMode ? modelConfig.configs.prompt_template : '', prompt_type: promptMode, chat_prompt_config: {}, @@ -514,6 +519,9 @@ const Debug: FC = ({ const contextVar = modelConfig.configs.prompt_variables.find(item => item.is_context_var)?.key const postModelConfig: BackendModelConfig = { + text_to_speech: { + enabled: false, + }, pre_prompt: !isAdvancedMode ? modelConfig.configs.prompt_template : '', prompt_type: promptMode, chat_prompt_config: {}, @@ -657,6 +665,7 @@ const Debug: FC = ({ isShowSuggestion={doShowSuggestion} suggestionList={suggestQuestions} isShowSpeechToText={speechToTextConfig.enabled && !!speech2textDefaultModel} + isShowTextToSpeech={textToSpeechConfig.enabled && !!text2speechDefaultModel} isShowCitation={citationConfig.enabled} isShowCitationHitInfo isShowPromptLog @@ -682,6 +691,7 @@ const Debug: FC = ({ className="mt-2" content={completionRes} isLoading={!completionRes && isResponsing} + isShowTextToSpeech={textToSpeechConfig.enabled && !!text2speechDefaultModel} isResponsing={isResponsing} isInstalledApp={false} messageId={messageId} diff --git a/web/app/components/app/configuration/features/chat-group/index.tsx b/web/app/components/app/configuration/features/chat-group/index.tsx index 9b61f2f082..fd3cfa3a68 100644 --- a/web/app/components/app/configuration/features/chat-group/index.tsx +++ b/web/app/components/app/configuration/features/chat-group/index.tsx @@ -7,6 +7,7 @@ import type { IOpeningStatementProps } from './opening-statement' import OpeningStatement from './opening-statement' import SuggestedQuestionsAfterAnswer from './suggested-questions-after-answer' import SpeechToText from './speech-to-text' +import TextToSpeech from './text-to-speech' import Citation from './citation' /* * Include @@ -19,6 +20,7 @@ type ChatGroupProps = { openingStatementConfig: IOpeningStatementProps isShowSuggestedQuestionsAfterAnswer: boolean isShowSpeechText: boolean + isShowTextToSpeech: boolean isShowCitation: boolean } const ChatGroup: FC = ({ @@ -26,6 +28,7 @@ const ChatGroup: FC = ({ openingStatementConfig, isShowSuggestedQuestionsAfterAnswer, isShowSpeechText, + isShowTextToSpeech, isShowCitation, }) => { const { t } = useTranslation() @@ -40,6 +43,11 @@ const ChatGroup: FC = ({ {isShowSuggestedQuestionsAfterAnswer && ( )} + { + isShowTextToSpeech && ( + + ) + } { isShowSpeechText && ( diff --git a/web/app/components/app/configuration/features/chat-group/speech-to-text/index.tsx b/web/app/components/app/configuration/features/chat-group/speech-to-text/index.tsx index 913765bce3..e452b38971 100644 --- a/web/app/components/app/configuration/features/chat-group/speech-to-text/index.tsx +++ b/web/app/components/app/configuration/features/chat-group/speech-to-text/index.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next' import Panel from '@/app/components/app/configuration/base/feature-panel' import { Microphone01 } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' -const SuggestedQuestionsAfterAnswer: FC = () => { +const SpeechToTextConfig: FC = () => { const { t } = useTranslation() return ( @@ -22,4 +22,4 @@ const SuggestedQuestionsAfterAnswer: FC = () => { /> ) } -export default React.memo(SuggestedQuestionsAfterAnswer) +export default React.memo(SpeechToTextConfig) diff --git a/web/app/components/app/configuration/features/chat-group/text-to-speech/index.tsx b/web/app/components/app/configuration/features/chat-group/text-to-speech/index.tsx new file mode 100644 index 0000000000..d3f5562df7 --- /dev/null +++ b/web/app/components/app/configuration/features/chat-group/text-to-speech/index.tsx @@ -0,0 +1,25 @@ +'use client' +import React, { type FC } from 'react' +import { useTranslation } from 'react-i18next' +import Panel from '@/app/components/app/configuration/base/feature-panel' +import { Speaker } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' + +const TextToSpeech: FC = () => { + const { t } = useTranslation() + + return ( + +

{t('appDebug.feature.textToSpeech.title')}
+ + } + headerIcon={} + headerRight={ +
{t('appDebug.feature.textToSpeech.resDes')}
+ } + noBodySpacing + /> + ) +} +export default React.memo(TextToSpeech) diff --git a/web/app/components/app/configuration/features/experience-enchance-group/index.tsx b/web/app/components/app/configuration/features/experience-enchance-group/index.tsx index ee3f3b9ff6..6902a17468 100644 --- a/web/app/components/app/configuration/features/experience-enchance-group/index.tsx +++ b/web/app/components/app/configuration/features/experience-enchance-group/index.tsx @@ -3,19 +3,40 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' import GroupName from '../../base/group-name' +import TextToSpeech from '../chat-group/text-to-speech' import MoreLikeThis from './more-like-this' /* * Include * 1. More like this */ -const ExperienceEnchanceGroup: FC = () => { + +type ExperienceGroupProps = { + isShowTextToSpeech: boolean + isShowMoreLike: boolean +} + +const ExperienceEnchanceGroup: FC = ({ + isShowTextToSpeech, + isShowMoreLike, +}) => { const { t } = useTranslation() return (
- - + +
+ { + isShowMoreLike && ( + + ) + } + { + isShowTextToSpeech && ( + + ) + } +
) } diff --git a/web/app/components/app/configuration/index.tsx b/web/app/components/app/configuration/index.tsx index fb1e31e083..8b8bdd7c3c 100644 --- a/web/app/components/app/configuration/index.tsx +++ b/web/app/components/app/configuration/index.tsx @@ -91,6 +91,9 @@ const Configuration: FC = () => { const [speechToTextConfig, setSpeechToTextConfig] = useState({ enabled: false, }) + const [textToSpeechConfig, setTextToSpeechConfig] = useState({ + enabled: false, + }) const [citationConfig, setCitationConfig] = useState({ enabled: false, }) @@ -140,6 +143,7 @@ const Configuration: FC = () => { more_like_this: null, suggested_questions_after_answer: null, speech_to_text: null, + text_to_speech: null, retriever_resource: null, sensitive_word_avoidance: null, dataSets: [], @@ -232,6 +236,9 @@ const Configuration: FC = () => { setSpeechToTextConfig(modelConfig.speech_to_text || { enabled: false, }) + setTextToSpeechConfig(modelConfig.text_to_speech || { + enabled: false, + }) setCitationConfig(modelConfig.retriever_resource || { enabled: false, }) @@ -396,6 +403,9 @@ const Configuration: FC = () => { if (modelConfig.speech_to_text) setSpeechToTextConfig(modelConfig.speech_to_text) + if (modelConfig.text_to_speech) + setTextToSpeechConfig(modelConfig.text_to_speech) + if (modelConfig.retriever_resource) setCitationConfig(modelConfig.retriever_resource) @@ -444,6 +454,7 @@ const Configuration: FC = () => { more_like_this: modelConfig.more_like_this, suggested_questions_after_answer: modelConfig.suggested_questions_after_answer, speech_to_text: modelConfig.speech_to_text, + text_to_speech: modelConfig.text_to_speech, retriever_resource: modelConfig.retriever_resource, sensitive_word_avoidance: modelConfig.sensitive_word_avoidance, external_data_tools: modelConfig.external_data_tools, @@ -559,6 +570,7 @@ const Configuration: FC = () => { more_like_this: moreLikeThisConfig, suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig, speech_to_text: speechToTextConfig, + text_to_speech: textToSpeechConfig, retriever_resource: citationConfig, sensitive_word_avoidance: moderationConfig, agent_mode: { @@ -593,6 +605,7 @@ const Configuration: FC = () => { draft.more_like_this = moreLikeThisConfig draft.suggested_questions_after_answer = suggestedQuestionsAfterAnswerConfig draft.speech_to_text = speechToTextConfig + draft.text_to_speech = textToSpeechConfig draft.retriever_resource = citationConfig draft.dataSets = dataSets }) @@ -662,6 +675,8 @@ const Configuration: FC = () => { setSuggestedQuestionsAfterAnswerConfig, speechToTextConfig, setSpeechToTextConfig, + textToSpeechConfig, + setTextToSpeechConfig, citationConfig, setCitationConfig, annotationConfig, diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index 6e3ba82982..0346d4d0ce 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -297,6 +297,7 @@ function DetailPanel item.from_source === 'admin')} onFeedback={feedback => onFeedback(detail.message.id, feedback)} supportAnnotation + isShowTextToSpeech appId={appDetail?.id} varList={varList} /> @@ -310,6 +311,7 @@ function DetailPanel diff --git a/web/app/components/app/text-generate/item/index.tsx b/web/app/components/app/text-generate/item/index.tsx index d8967d2992..cd3e7cd331 100644 --- a/web/app/components/app/text-generate/item/index.tsx +++ b/web/app/components/app/text-generate/item/index.tsx @@ -12,6 +12,7 @@ import PromptLog from '@/app/components/app/chat/log' import { Markdown } from '@/app/components/base/markdown' import Loading from '@/app/components/base/loading' import Toast from '@/app/components/base/toast' +import AudioBtn from '@/app/components/base/audio-btn' import type { Feedbacktype } from '@/app/components/app/chat/type' import { fetchMoreLikeThis, updateFeedback } from '@/service/share' import { Clipboard, File02 } from '@/app/components/base/icons/src/vender/line/files' @@ -45,6 +46,7 @@ export type IGenerationItemProps = { controlClearMoreLikeThis?: number supportFeedback?: boolean supportAnnotation?: boolean + isShowTextToSpeech?: boolean appId?: string varList?: { label: string; value: string | number | object }[] } @@ -90,6 +92,7 @@ const GenerationItem: FC = ({ controlClearMoreLikeThis, supportFeedback, supportAnnotation, + isShowTextToSpeech, appId, varList, }) => { @@ -124,6 +127,7 @@ const GenerationItem: FC = ({ isLoading: isQuerying, feedback: childFeedback, onSave, + isShowTextToSpeech, isMobile, isInstalledApp, installedAppId, @@ -366,8 +370,17 @@ const GenerationItem: FC = ({
{ratingContent}
- ) - } + )} + + {isShowTextToSpeech && ( + <> +
+ + + )}
{content?.length} {t('common.unit.char')}
diff --git a/web/app/components/app/text-generate/saved-items/index.tsx b/web/app/components/app/text-generate/saved-items/index.tsx index e2cc0e8396..f63ebdfbc0 100644 --- a/web/app/components/app/text-generate/saved-items/index.tsx +++ b/web/app/components/app/text-generate/saved-items/index.tsx @@ -9,9 +9,11 @@ import type { SavedMessage } from '@/models/debug' import { Markdown } from '@/app/components/base/markdown' import { SimpleBtn, copyIcon } from '@/app/components/app/text-generate/item' import Toast from '@/app/components/base/toast' +import AudioBtn from '@/app/components/base/audio-btn' export type ISavedItemsProps = { className?: string + isShowTextToSpeech?: boolean list: SavedMessage[] onRemove: (id: string) => void onStartCreateContent: () => void @@ -25,6 +27,7 @@ const removeIcon = ( const SavedItems: FC = ({ className, + isShowTextToSpeech, list, onRemove, onStartCreateContent, @@ -69,6 +72,16 @@ const SavedItems: FC = ({ {removeIcon}
{t('common.operation.remove')}
+ + {isShowTextToSpeech && ( + <> +
+ + + )}
{answer?.length} {t('common.unit.char')}
diff --git a/web/app/components/base/audio-btn/index.tsx b/web/app/components/base/audio-btn/index.tsx new file mode 100644 index 0000000000..f64993669a --- /dev/null +++ b/web/app/components/base/audio-btn/index.tsx @@ -0,0 +1,110 @@ +'use client' +import { useRef, useState } from 'react' +import { t } from 'i18next' +import { useParams, usePathname } from 'next/navigation' +import s from './style.module.css' +import Tooltip from '@/app/components/base/tooltip' +import { randomString } from '@/utils' +import { textToAudio } from '@/service/share' + +type AudioBtnProps = { + value: string + className?: string +} + +const AudioBtn = ({ + value, + className, +}: AudioBtnProps) => { + const audioRef = useRef(null) + const [isPlaying, setIsPlaying] = useState(false) + const [isPause, setPause] = useState(false) + const [hasEnded, setHasEnded] = useState(false) + const selector = useRef(`play-tooltip-${randomString(4)}`) + const params = useParams() + const pathname = usePathname() + const removeCodeBlocks = (inputText: any) => { + const codeBlockRegex = /```[\s\S]*?```/g + return inputText.replace(codeBlockRegex, '') + } + + const playAudio = async () => { + const formData = new FormData() + if (value !== '') { + formData.append('text', removeCodeBlocks(value)) + + let url = '/universal-chat/text-to-audio' + let isPublic = false + + if (params.token) { + url = '/text-to-audio' + isPublic = true + } + else if (params.appId) { + if (pathname.search('explore/installed') > -1) + url = `/installed-apps/${params.appId}/text-to-audio` + else + url = `/apps/${params.appId}/text-to-audio` + } + + try { + const audioResponse = await textToAudio(url, isPublic, formData) + const blob_bytes = Buffer.from(audioResponse.data, 'latin1') + const blob = new Blob([blob_bytes], { type: 'audio/wav' }) + const audioUrl = URL.createObjectURL(blob) + const audio = new Audio(audioUrl) + audioRef.current = audio + audio.play().then(() => { + setIsPlaying(true) + }).catch(() => { + setIsPlaying(false) + URL.revokeObjectURL(audioUrl) + }) + audio.onended = () => setHasEnded(true) + } + catch (error) { + setIsPlaying(false) + console.error('Error playing audio:', error) + } + } + } + + const togglePlayPause = () => { + if (audioRef.current) { + if (isPlaying) { + setPause(true) + audioRef.current.pause() + } + else if (!hasEnded) { + setPause(false) + audioRef.current.play() + } + else if (!isPlaying) { + playAudio().then() + } + setIsPlaying(prevIsPlaying => !prevIsPlaying) + } + else { + playAudio().then() + } + } + + return ( +
+ +
+
+
+
+
+ ) +} + +export default AudioBtn diff --git a/web/app/components/base/audio-btn/style.module.css b/web/app/components/base/audio-btn/style.module.css new file mode 100644 index 0000000000..7c05003b04 --- /dev/null +++ b/web/app/components/base/audio-btn/style.module.css @@ -0,0 +1,16 @@ +.playIcon { + background-image: url(~@/app/components/develop/secret-key/assets/play.svg); + background-position: center; + background-repeat: no-repeat; +} +.pauseIcon { + background-image: url(~@/app/components/develop/secret-key/assets/pause.svg); + background-position: center; + background-repeat: no-repeat; +} + +.stopIcon { + background-position: center; + background-repeat: no-repeat; + background-image: url(~@/app/components/develop/secret-key/assets/stop.svg); +} \ No newline at end of file diff --git a/web/app/components/base/icons/assets/vender/line/mediaAndDevices/speaker.svg b/web/app/components/base/icons/assets/vender/line/mediaAndDevices/speaker.svg new file mode 100644 index 0000000000..f769c7e830 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/mediaAndDevices/speaker.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/web/app/components/base/icons/assets/vender/solid/mediaAndDevices/speaker.svg b/web/app/components/base/icons/assets/vender/solid/mediaAndDevices/speaker.svg new file mode 100644 index 0000000000..f769c7e830 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/solid/mediaAndDevices/speaker.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/web/app/components/base/icons/src/vender/line/mediaAndDevices/Speaker.json b/web/app/components/base/icons/src/vender/line/mediaAndDevices/Speaker.json new file mode 100644 index 0000000000..3e5cbe171b --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/mediaAndDevices/Speaker.json @@ -0,0 +1,112 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "16", + "height": "16", + "viewBox": "0 0 16 16", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "clip-path": "url(#clip0_109_6694)" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M0 2.86666C0 2.05664 0.656649 1.39999 1.46667 1.39999H5.86667C6.67668 1.39999 7.33333 2.05664 7.33333 2.86666C7.33333 3.27167 7.00501 3.59999 6.6 3.59999C6.19499 3.59999 5.86667 3.27167 5.86667 2.86666H4.4V7.99999C4.80501 7.99999 5.13333 8.32831 5.13333 8.73332C5.13333 9.13833 4.80501 9.46666 4.4 9.46666H2.93333C2.52832 9.46666 2.2 9.13833 2.2 8.73332C2.2 8.32831 2.52832 7.99999 2.93333 7.99999V2.86666H1.46667C1.46667 3.27167 1.13834 3.59999 0.733333 3.59999C0.328324 3.59999 0 3.27167 0 2.86666Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M13.8205 0.782296C13.7434 0.62811 13.5233 0.62811 13.4462 0.782296C12.9664 1.74206 12.8754 1.83302 11.9156 2.3129C11.7615 2.39 11.7615 2.61003 11.9156 2.68712C12.8754 3.167 12.9664 3.25797 13.4462 4.21773C13.5233 4.37191 13.7434 4.37191 13.8205 4.21773C14.3003 3.25797 14.3913 3.167 15.3511 2.68712C15.5053 2.61003 15.5053 2.39 15.3511 2.3129C14.3913 1.83302 14.3003 1.74206 13.8205 0.782296Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M9.79394 2.25319C9.71404 2.09337 9.48596 2.09337 9.40605 2.25319C9.04994 2.96543 8.96544 3.04993 8.2532 3.40605C8.09338 3.48595 8.09338 3.71402 8.2532 3.79393C8.96544 4.15005 9.04994 4.23455 9.40606 4.94679C9.48596 5.10661 9.71404 5.10661 9.79394 4.94679C10.1501 4.23455 10.2346 4.15005 10.9468 3.79393C11.1066 3.71402 11.1066 3.48595 10.9468 3.40605C10.2346 3.04993 10.1501 2.96543 9.79394 2.25319Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M2.75377 11.049C2.67668 10.8948 2.45665 10.8948 2.37956 11.049C1.89969 12.0087 1.80872 12.0997 0.848971 12.5796C0.694788 12.6566 0.694787 12.8767 0.848971 12.9538C1.80872 13.4336 1.89969 13.5246 2.37956 14.4844C2.45665 14.6385 2.67668 14.6385 2.75377 14.4844C3.23365 13.5246 3.32461 13.4336 4.28436 12.9538C4.43855 12.8767 4.43855 12.6566 4.28436 12.5796C3.32461 12.0997 3.23365 12.0087 2.75377 11.049Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M14.6741 8.65106C14.8886 8.50146 15.1837 8.55405 15.3333 8.76853C15.7614 9.38226 16.0125 10.1292 16.0125 10.9333C16.0125 11.7375 15.7614 12.4844 15.3333 13.0981C15.1837 13.3126 14.8886 13.3652 14.6741 13.2156C14.4596 13.066 14.407 12.7708 14.5567 12.5564C14.8775 12.0964 15.0656 11.5375 15.0656 10.9333C15.0656 10.3291 14.8775 9.77025 14.5567 9.31028C14.407 9.09581 14.4596 8.80066 14.6741 8.65106Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M12.5674 6.53771C12.794 6.51987 13.0155 6.61161 13.1632 6.78449C13.2954 6.93929 13.3164 7.12549 13.3244 7.21587C13.3334 7.31718 13.3334 7.44301 13.3333 7.57103C13.3333 7.57691 13.3333 7.58278 13.3333 7.58866L13.3333 14.3C13.3334 14.428 13.3334 14.5539 13.3244 14.6552C13.3164 14.7455 13.2954 14.9317 13.1632 15.0865C13.0155 15.2594 12.794 15.3512 12.5674 15.3333C12.3644 15.3173 12.2179 15.2005 12.1484 15.1423C12.0704 15.077 11.9814 14.988 11.8909 14.8975L10.3795 13.3861C10.3357 13.3423 10.3137 13.3205 10.2971 13.3053L10.2958 13.3041L10.2941 13.3041C10.2716 13.303 10.2407 13.3029 10.1787 13.3029L9.34101 13.3029C9.22151 13.3029 9.10513 13.3029 9.00657 13.2949C8.89833 13.286 8.77062 13.2652 8.6421 13.1997C8.46392 13.1089 8.31906 12.964 8.22827 12.7859C8.16279 12.6574 8.14192 12.5296 8.13308 12.4214C8.12503 12.3228 8.12504 12.2065 8.12505 12.087V9.79916C8.12505 9.79413 8.12505 9.78909 8.12505 9.78406C8.12504 9.66456 8.12503 9.54819 8.13308 9.44963C8.14192 9.34139 8.16279 9.21368 8.22827 9.08517C8.31906 8.90699 8.46392 8.76212 8.6421 8.67133C8.77062 8.60585 8.89833 8.58498 9.00657 8.57614C9.10512 8.56809 9.2215 8.5681 9.341 8.56812C9.34603 8.56812 9.35106 8.56812 9.3561 8.56812H10.1787C10.2407 8.56812 10.2716 8.56801 10.2941 8.56698L10.2958 8.5669L10.2971 8.56575C10.3137 8.55058 10.3357 8.52877 10.3795 8.48491L11.8784 6.98602C11.8826 6.98186 11.8867 6.97771 11.8909 6.97355C11.9814 6.88302 12.0704 6.79403 12.1484 6.72874C12.2179 6.67049 12.3644 6.55368 12.5674 6.53771Z", + "fill": "currentColor" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "defs", + "attributes": {}, + "children": [ + { + "type": "element", + "name": "clipPath", + "attributes": { + "id": "clip0_109_6694" + }, + "children": [ + { + "type": "element", + "name": "rect", + "attributes": { + "width": "16", + "height": "16", + "fill": "white" + }, + "children": [] + } + ] + } + ] + } + ] + }, + "name": "Speaker" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/mediaAndDevices/Speaker.tsx b/web/app/components/base/icons/src/vender/line/mediaAndDevices/Speaker.tsx new file mode 100644 index 0000000000..a33b9ebcfd --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/mediaAndDevices/Speaker.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Speaker.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'Speaker' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/line/mediaAndDevices/index.ts b/web/app/components/base/icons/src/vender/line/mediaAndDevices/index.ts index 6e90c11609..ba693b054e 100644 --- a/web/app/components/base/icons/src/vender/line/mediaAndDevices/index.ts +++ b/web/app/components/base/icons/src/vender/line/mediaAndDevices/index.ts @@ -1,2 +1,3 @@ export { default as Microphone01 } from './Microphone01' export { default as SlidersH } from './SlidersH' +export { default as Speaker } from './Speaker' diff --git a/web/app/components/base/icons/src/vender/solid/mediaAndDevices/Speaker.json b/web/app/components/base/icons/src/vender/solid/mediaAndDevices/Speaker.json new file mode 100644 index 0000000000..3e5cbe171b --- /dev/null +++ b/web/app/components/base/icons/src/vender/solid/mediaAndDevices/Speaker.json @@ -0,0 +1,112 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "16", + "height": "16", + "viewBox": "0 0 16 16", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "clip-path": "url(#clip0_109_6694)" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M0 2.86666C0 2.05664 0.656649 1.39999 1.46667 1.39999H5.86667C6.67668 1.39999 7.33333 2.05664 7.33333 2.86666C7.33333 3.27167 7.00501 3.59999 6.6 3.59999C6.19499 3.59999 5.86667 3.27167 5.86667 2.86666H4.4V7.99999C4.80501 7.99999 5.13333 8.32831 5.13333 8.73332C5.13333 9.13833 4.80501 9.46666 4.4 9.46666H2.93333C2.52832 9.46666 2.2 9.13833 2.2 8.73332C2.2 8.32831 2.52832 7.99999 2.93333 7.99999V2.86666H1.46667C1.46667 3.27167 1.13834 3.59999 0.733333 3.59999C0.328324 3.59999 0 3.27167 0 2.86666Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M13.8205 0.782296C13.7434 0.62811 13.5233 0.62811 13.4462 0.782296C12.9664 1.74206 12.8754 1.83302 11.9156 2.3129C11.7615 2.39 11.7615 2.61003 11.9156 2.68712C12.8754 3.167 12.9664 3.25797 13.4462 4.21773C13.5233 4.37191 13.7434 4.37191 13.8205 4.21773C14.3003 3.25797 14.3913 3.167 15.3511 2.68712C15.5053 2.61003 15.5053 2.39 15.3511 2.3129C14.3913 1.83302 14.3003 1.74206 13.8205 0.782296Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M9.79394 2.25319C9.71404 2.09337 9.48596 2.09337 9.40605 2.25319C9.04994 2.96543 8.96544 3.04993 8.2532 3.40605C8.09338 3.48595 8.09338 3.71402 8.2532 3.79393C8.96544 4.15005 9.04994 4.23455 9.40606 4.94679C9.48596 5.10661 9.71404 5.10661 9.79394 4.94679C10.1501 4.23455 10.2346 4.15005 10.9468 3.79393C11.1066 3.71402 11.1066 3.48595 10.9468 3.40605C10.2346 3.04993 10.1501 2.96543 9.79394 2.25319Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M2.75377 11.049C2.67668 10.8948 2.45665 10.8948 2.37956 11.049C1.89969 12.0087 1.80872 12.0997 0.848971 12.5796C0.694788 12.6566 0.694787 12.8767 0.848971 12.9538C1.80872 13.4336 1.89969 13.5246 2.37956 14.4844C2.45665 14.6385 2.67668 14.6385 2.75377 14.4844C3.23365 13.5246 3.32461 13.4336 4.28436 12.9538C4.43855 12.8767 4.43855 12.6566 4.28436 12.5796C3.32461 12.0997 3.23365 12.0087 2.75377 11.049Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M14.6741 8.65106C14.8886 8.50146 15.1837 8.55405 15.3333 8.76853C15.7614 9.38226 16.0125 10.1292 16.0125 10.9333C16.0125 11.7375 15.7614 12.4844 15.3333 13.0981C15.1837 13.3126 14.8886 13.3652 14.6741 13.2156C14.4596 13.066 14.407 12.7708 14.5567 12.5564C14.8775 12.0964 15.0656 11.5375 15.0656 10.9333C15.0656 10.3291 14.8775 9.77025 14.5567 9.31028C14.407 9.09581 14.4596 8.80066 14.6741 8.65106Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M12.5674 6.53771C12.794 6.51987 13.0155 6.61161 13.1632 6.78449C13.2954 6.93929 13.3164 7.12549 13.3244 7.21587C13.3334 7.31718 13.3334 7.44301 13.3333 7.57103C13.3333 7.57691 13.3333 7.58278 13.3333 7.58866L13.3333 14.3C13.3334 14.428 13.3334 14.5539 13.3244 14.6552C13.3164 14.7455 13.2954 14.9317 13.1632 15.0865C13.0155 15.2594 12.794 15.3512 12.5674 15.3333C12.3644 15.3173 12.2179 15.2005 12.1484 15.1423C12.0704 15.077 11.9814 14.988 11.8909 14.8975L10.3795 13.3861C10.3357 13.3423 10.3137 13.3205 10.2971 13.3053L10.2958 13.3041L10.2941 13.3041C10.2716 13.303 10.2407 13.3029 10.1787 13.3029L9.34101 13.3029C9.22151 13.3029 9.10513 13.3029 9.00657 13.2949C8.89833 13.286 8.77062 13.2652 8.6421 13.1997C8.46392 13.1089 8.31906 12.964 8.22827 12.7859C8.16279 12.6574 8.14192 12.5296 8.13308 12.4214C8.12503 12.3228 8.12504 12.2065 8.12505 12.087V9.79916C8.12505 9.79413 8.12505 9.78909 8.12505 9.78406C8.12504 9.66456 8.12503 9.54819 8.13308 9.44963C8.14192 9.34139 8.16279 9.21368 8.22827 9.08517C8.31906 8.90699 8.46392 8.76212 8.6421 8.67133C8.77062 8.60585 8.89833 8.58498 9.00657 8.57614C9.10512 8.56809 9.2215 8.5681 9.341 8.56812C9.34603 8.56812 9.35106 8.56812 9.3561 8.56812H10.1787C10.2407 8.56812 10.2716 8.56801 10.2941 8.56698L10.2958 8.5669L10.2971 8.56575C10.3137 8.55058 10.3357 8.52877 10.3795 8.48491L11.8784 6.98602C11.8826 6.98186 11.8867 6.97771 11.8909 6.97355C11.9814 6.88302 12.0704 6.79403 12.1484 6.72874C12.2179 6.67049 12.3644 6.55368 12.5674 6.53771Z", + "fill": "currentColor" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "defs", + "attributes": {}, + "children": [ + { + "type": "element", + "name": "clipPath", + "attributes": { + "id": "clip0_109_6694" + }, + "children": [ + { + "type": "element", + "name": "rect", + "attributes": { + "width": "16", + "height": "16", + "fill": "white" + }, + "children": [] + } + ] + } + ] + } + ] + }, + "name": "Speaker" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/solid/mediaAndDevices/Speaker.tsx b/web/app/components/base/icons/src/vender/solid/mediaAndDevices/Speaker.tsx new file mode 100644 index 0000000000..a33b9ebcfd --- /dev/null +++ b/web/app/components/base/icons/src/vender/solid/mediaAndDevices/Speaker.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Speaker.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'Speaker' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/solid/mediaAndDevices/index.ts b/web/app/components/base/icons/src/vender/solid/mediaAndDevices/index.ts index 12538b9aaf..37d5b3a2d9 100644 --- a/web/app/components/base/icons/src/vender/solid/mediaAndDevices/index.ts +++ b/web/app/components/base/icons/src/vender/solid/mediaAndDevices/index.ts @@ -4,4 +4,5 @@ export { default as MagicWand } from './MagicWand' export { default as Microphone01 } from './Microphone01' export { default as Robot } from './Robot' export { default as Sliders02 } from './Sliders02' +export { default as Speaker } from './Speaker' export { default as StopCircle } from './StopCircle' diff --git a/web/app/components/develop/secret-key/assets/pause.svg b/web/app/components/develop/secret-key/assets/pause.svg new file mode 100644 index 0000000000..a204b179d2 --- /dev/null +++ b/web/app/components/develop/secret-key/assets/pause.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/web/app/components/develop/secret-key/assets/play.svg b/web/app/components/develop/secret-key/assets/play.svg new file mode 100644 index 0000000000..0ab33af6c6 --- /dev/null +++ b/web/app/components/develop/secret-key/assets/play.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/web/app/components/develop/secret-key/assets/stop.svg b/web/app/components/develop/secret-key/assets/stop.svg new file mode 100644 index 0000000000..b423e98ce2 --- /dev/null +++ b/web/app/components/develop/secret-key/assets/stop.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/web/app/components/develop/template/template.en.mdx b/web/app/components/develop/template/template.en.mdx index 28d92e98f8..2ac9aecdea 100644 --- a/web/app/components/develop/template/template.en.mdx +++ b/web/app/components/develop/template/template.en.mdx @@ -6,7 +6,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from The text generation application offers non-session support and is ideal for translation, article writing, summarization AI, and more.
- ### Base URL + ### Base URL ```javascript ``` @@ -14,10 +14,10 @@ The text generation application offers non-session support and is ideal for tran ### Authentication - The Service API uses `API-Key` authentication. + The Service API uses `API-Key` authentication. **Strongly recommend storing your API Key on the server-side, not shared or stored on the client-side, to avoid possible API-Key leakage that can lead to serious consequences.** - For all API requests, include your API Key in the `Authorization` HTTP Header, as shown below: + For all API requests, include your API Key in the `Authorization` HTTP Header, as shown below: ```javascript @@ -46,18 +46,18 @@ The text generation application offers non-session support and is ideal for tran User Input/Question content - Allows the entry of various variable values defined by the App. + Allows the entry of various variable values defined by the App. The `inputs` parameter contains multiple key/value pairs, with each key corresponding to a specific variable and each value being the specific value for that variable. The text generation application requires at least one key/value pair to be inputted. The mode of response return, supporting: - `streaming` Streaming mode (recommended), implements a typewriter-like output through SSE ([Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)). - - `blocking` Blocking mode, returns result after execution is complete. (Requests may be interrupted if the process is long) + - `blocking` Blocking mode, returns result after execution is complete. (Requests may be interrupted if the process is long) Due to Cloudflare restrictions, the request will be interrupted without a return after 100 seconds. - User identifier, used to define the identity of the end-user for retrieval and statistics. + User identifier, used to define the identity of the end-user for retrieval and statistics. Should be uniquely defined by the developer within the application. @@ -71,9 +71,9 @@ The text generation application offers non-session support and is ideal for tran - `upload_file_id` (string) Uploaded file ID, which must be obtained by uploading through the File Upload API in advance (when the transfer method is `local_file`) - + ### Response - When `response_mode` is `blocking`, return a CompletionResponse object. + When `response_mode` is `blocking`, return a CompletionResponse object. When `response_mode` is `streaming`, return a ChunkCompletionResponse stream. ### ChatCompletionResponse @@ -205,7 +205,7 @@ The text generation application offers non-session support and is ideal for tran Upload a file (currently only images are supported) for use when sending messages, enabling multimodal understanding of images and text. - Supports png, jpg, jpeg, webp, gif formats. + Supports png, jpg, jpeg, webp, gif formats. Uploaded files are for use by the current end-user only. ### Request Body @@ -214,7 +214,7 @@ The text generation application offers non-session support and is ideal for tran The file to be uploaded. - `user` (string) Required User identifier, defined by the developer's rules, must be unique within the application. - + ### Response After a successful upload, the server will return the file's ID and related information. - `id` (uuid) ID @@ -236,7 +236,7 @@ The text generation application offers non-session support and is ideal for tran - 503, `s3_permission_denied`, no permission to upload files to S3 - 503, `s3_file_too_large`, file exceeds S3 size limit - 500, internal server error - + @@ -256,12 +256,12 @@ The text generation application offers non-session support and is ideal for tran ```json {{ title: 'Response' }} { - "id": "72fa9618-8f89-4a37-9b33-7e1178a24a67", + "id": "72fa9618-8f89-4a37-9b33-7e1178a24a67", "name": "example.png", "size": 1024, "extension": "png", "mime_type": "image/png", - "created_by": "6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13", + "created_by": "6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13", "created_at": 1577836800, } ``` @@ -292,8 +292,8 @@ The text generation application offers non-session support and is ideal for tran ```bash {{ title: 'cURL' }} curl -X POST 'https://cloud.dify.ai/v1/chat-messages/:task_id/stop' \ - -H 'Authorization: Bearer {api_key}' \ - -H 'Content-Type: application/json' \ + -H 'Authorization: Bearer {api_key}' \ + -H 'Content-Type: application/json' \ --data-raw '{ "user": "abc-123" }' @@ -484,3 +484,51 @@ The text generation application offers non-session support and is ideal for tran + +--- + + + + + Text to speech, only supports openai model. + + ### Request Body + + + + Speech generated content。 + + + The user identifier, defined by the developer, must ensure uniqueness within the app. + + + Whether to enable streaming output, true、false。 + + + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.appDetail.api_base_url}/text-to-audio' \ + --header 'Authorization: Bearer ENTER-YOUR-SECRET-KEY' \ + --form 'file=Hello Dify;user=abc-123;streaming=false' + ``` + + + + + ```json {{ title: 'headers' }} + { + "Content-Type": "audio/wav" + } + ``` + + + \ No newline at end of file diff --git a/web/app/components/develop/template/template.zh.mdx b/web/app/components/develop/template/template.zh.mdx index 4748ec41dd..2c5322c922 100644 --- a/web/app/components/develop/template/template.zh.mdx +++ b/web/app/components/develop/template/template.zh.mdx @@ -14,9 +14,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' ### 鉴权 - - Dify Service API 使用 `API-Key` 进行鉴权。 - **强烈建议开发者把 `API-Key` 放在后端存储,而非分享或者放在客户端存储,以免 `API-Key` 泄露,导致财产损失。** + + Dify Service API 使用 `API-Key` 进行鉴权。 + **强烈建议开发者把 `API-Key` 放在后端存储,而非分享或者放在客户端存储,以免 `API-Key` 泄露,导致财产损失。** 所有 API 请求都应在 **`Authorization`** HTTP Header 中包含您的 `API-Key`,如下所示: @@ -46,16 +46,16 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' (选填)允许传入 App 定义的各变量值。 - inputs 参数包含了多组键值对(Key/Value pairs),每组的键对应一个特定变量,每组的值则是该变量的具体值。 + inputs 参数包含了多组键值对(Key/Value pairs),每组的键对应一个特定变量,每组的值则是该变量的具体值。 文本生成型应用要求至少传入一组键值对。 - `streaming` 流式模式(推荐)。基于 SSE(**[Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)**)实现类似打字机输出方式的流式返回。 - - `blocking` 阻塞模式,等待执行完毕后返回结果。(请求若流程较长可能会被中断)。 + - `blocking` 阻塞模式,等待执行完毕后返回结果。(请求若流程较长可能会被中断)。 由于 Cloudflare 限制,请求会在 100 秒超时无返回后中断。 - 用户标识,用于定义终端用户的身份,方便检索、统计。 + 用户标识,用于定义终端用户的身份,方便检索、统计。 由开发者定义规则,需保证用户标识在应用内唯一。 @@ -74,9 +74,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' ### Response - 当 `response_mode` 为 `blocking` 时,返回 ChatCompletionResponse object。 + 当 `response_mode` 为 `blocking` 时,返回 ChatCompletionResponse object。 当 `response_mode` 为 `streaming`时,返回 ChunkChatCompletionResponse object 流式序列。 - + ### ChatCompletionResponse 返回完整的 App 结果,`Content-Type` 为 `application/json`。 - `message_id` (string) 消息唯一 ID @@ -184,7 +184,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' 上传文件(目前仅支持图片)并在发送消息时使用,可实现图文多模态理解。 - 支持 png, jpg, jpeg, webp, gif 格式。 + 支持 png, jpg, jpeg, webp, gif 格式。 上传的文件仅供当前终端用户使用。 ### Request Body @@ -234,12 +234,12 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' ```json {{ title: 'Response' }} { - "id": "72fa9618-8f89-4a37-9b33-7e1178a24a67", + "id": "72fa9618-8f89-4a37-9b33-7e1178a24a67", "name": "example.png", "size": 1024, "extension": "png", "mime_type": "image/png", - "created_by": 123, + "created_by": 123, "created_at": 1577836800, } ``` @@ -258,7 +258,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' 仅支持流式模式。 ### Path - `task_id` (string) 任务 ID,可在流式返回 Chunk 中获取 - + ### Request Body - `user` (string) Required 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。 @@ -378,7 +378,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' - `annotation_reply` (object) 标记回复 - `enabled` (bool) 是否开启 - `user_input_form` (array[object]) 用户输入表单配置 - - `text-input` (object) 文本输入控件 + - `text-input` (object) 文本输入控件 - `label` (string) 控件展示标签名 - `variable` (string) 控件 ID - `required` (bool) 是否必填 @@ -388,7 +388,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' - `variable` (string) 控件 ID - `required` (bool) 是否必填 - `default` (string) 默认值 - - `select` (object) 下拉控件 + - `select` (object) 下拉控件 - `label` (string) 控件展示标签名 - `variable` (string) 控件 ID - `required` (bool) 是否必填 @@ -447,3 +447,51 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' + +--- + + + + + 文字转语音,仅支持 openai 模型。 + + ### Request Body + + + + 语音生成内容。 + + + 用户标识,由开发者定义规则,需保证用户标识在应用内唯一。 + + + 是否启用流式输出true、false。 + + + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.appDetail.api_base_url}/text-to-audio' \ + --header 'Authorization: Bearer ENTER-YOUR-SECRET-KEY' \ + --form 'file=你好Dify;user=abc-123;streaming=false' + ``` + + + + + ```json {{ title: 'headers' }} + { + "Content-Type": "audio/wav" + } + ``` + + + diff --git a/web/app/components/develop/template/template_chat.en.mdx b/web/app/components/develop/template/template_chat.en.mdx index 3a0171fb51..f6532510c7 100644 --- a/web/app/components/develop/template/template_chat.en.mdx +++ b/web/app/components/develop/template/template_chat.en.mdx @@ -6,7 +6,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from Chat applications support session persistence, allowing previous chat history to be used as context for responses. This can be applicable for chatbots, customer service AI, etc.
- ### Base URL + ### Base URL ```javascript ``` @@ -14,10 +14,10 @@ Chat applications support session persistence, allowing previous chat history to ### Authentication - The Service API uses `API-Key` authentication. + The Service API uses `API-Key` authentication. **Strongly recommend storing your API Key on the server-side, not shared or stored on the client-side, to avoid possible API-Key leakage that can lead to serious consequences.** - For all API requests, include your API Key in the `Authorization`HTTP Header, as shown below: + For all API requests, include your API Key in the `Authorization`HTTP Header, as shown below: ```javascript @@ -46,18 +46,18 @@ Chat applications support session persistence, allowing previous chat history to User Input/Question content - Allows the entry of various variable values defined by the App. + Allows the entry of various variable values defined by the App. The `inputs` parameter contains multiple key/value pairs, with each key corresponding to a specific variable and each value being the specific value for that variable. The mode of response return, supporting: - `streaming` Streaming mode (recommended), implements a typewriter-like output through SSE ([Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)). - - `blocking` Blocking mode, returns result after execution is complete. (Requests may be interrupted if the process is long) - Due to Cloudflare restrictions, the request will be interrupted without a return after 100 seconds. + - `blocking` Blocking mode, returns result after execution is complete. (Requests may be interrupted if the process is long) + Due to Cloudflare restrictions, the request will be interrupted without a return after 100 seconds. Note: blocking mode is not supported in Agent Assistant mode - User identifier, used to define the identity of the end-user for retrieval and statistics. + User identifier, used to define the identity of the end-user for retrieval and statistics. Should be uniquely defined by the developer within the application. @@ -75,9 +75,9 @@ Chat applications support session persistence, allowing previous chat history to Can achieve async title generation by calling the conversation rename API and setting `auto_generate` to true. - + ### Response - When response_mode is blocking, return a CompletionResponse object. + When response_mode is blocking, return a CompletionResponse object. When response_mode is streaming, return a ChunkCompletionResponse stream. ### ChatCompletionResponse @@ -122,7 +122,7 @@ Chat applications support session persistence, allowing previous chat history to - `tool` (string) A list of tools represents which tools are called,split by ; - `tool_input` (string) Input of tools in JSON format. Like: `{"dalle3": {"prompt": "a cute cat"}}`. - `created_at` (int) Creation timestamp, e.g., 1705395332 - - `message_files` (array[string]) Refer to message_file event + - `message_files` (array[string]) Refer to message_file event - `file_id` (string) File ID - `conversation_id` (string) Conversation ID - `event: message_file` Message file event, a new file has created by tool @@ -260,7 +260,7 @@ Chat applications support session persistence, allowing previous chat history to Upload a file (currently only images are supported) for use when sending messages, enabling multimodal understanding of images and text. - Supports png, jpg, jpeg, webp, gif formats. + Supports png, jpg, jpeg, webp, gif formats. Uploaded files are for use by the current end-user only. ### Request Body @@ -269,7 +269,7 @@ Chat applications support session persistence, allowing previous chat history to The file to be uploaded. - `user` (string) Required User identifier, defined by the developer's rules, must be unique within the application. - + ### Response After a successful upload, the server will return the file's ID and related information. - `id` (uuid) ID @@ -291,7 +291,7 @@ Chat applications support session persistence, allowing previous chat history to - 503, `s3_permission_denied`, no permission to upload files to S3 - 503, `s3_file_too_large`, file exceeds S3 size limit - 500, internal server error - + @@ -311,12 +311,12 @@ Chat applications support session persistence, allowing previous chat history to ```json {{ title: 'Response' }} { - "id": "72fa9618-8f89-4a37-9b33-7e1178a24a67", + "id": "72fa9618-8f89-4a37-9b33-7e1178a24a67", "name": "example.png", "size": 1024, "extension": "png", "mime_type": "image/png", - "created_by": "6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13", + "created_by": "6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13", "created_at": 1577836800, } ``` @@ -347,8 +347,8 @@ Chat applications support session persistence, allowing previous chat history to ```bash {{ title: 'cURL' }} curl -X POST 'https://cloud.dify.ai/v1/chat-messages/:task_id/stop' \ - -H 'Authorization: Bearer {api_key}' \ - -H 'Content-Type: application/json' \ + -H 'Authorization: Bearer {api_key}' \ + -H 'Content-Type: application/json' \ --data-raw '{ "user": "abc-123" }' @@ -444,7 +444,7 @@ Chat applications support session persistence, allowing previous chat history to Conversation ID - User identifier, used to define the identity of the end-user for retrieval and statistics. + User identifier, used to define the identity of the end-user for retrieval and statistics. Should be uniquely defined by the developer within the application. @@ -475,7 +475,7 @@ Chat applications support session persistence, allowing previous chat history to - `tool` (string) A list of tools represents which tools are called,split by ; - `tool_input` (string) Input of tools in JSON format. Like: `{"dalle3": {"prompt": "a cute cat"}}`. - `created_at` (int) Creation timestamp, e.g., 1705395332 - - `message_files` (array[string]) Refer to message_file event + - `message_files` (array[string]) Refer to message_file event - `file_id` (string) File ID - `answer` (string) Response message content - `created_at` (timestamp) Creation timestamp, e.g., 1705395332 @@ -609,7 +609,7 @@ Chat applications support session persistence, allowing previous chat history to - User identifier, used to define the identity of the end-user for retrieval and statistics. + User identifier, used to define the identity of the end-user for retrieval and statistics. Should be uniquely defined by the developer within the application. @@ -800,8 +800,8 @@ Chat applications support session persistence, allowing previous chat history to - Audio file. - Supported formats: `['mp3', 'mp4', 'mpeg', 'mpga', 'm4a', 'wav', 'webm']` + Audio file. + Supported formats: `['mp3', 'mp4', 'mpeg', 'mpga', 'm4a', 'wav', 'webm']` File size limit: 15MB @@ -837,6 +837,54 @@ Chat applications support session persistence, allowing previous chat history to --- + + + + Text to speech, only supports openai model. + + ### Request Body + + + + Speech generated content。 + + + The user identifier, defined by the developer, must ensure uniqueness within the app. + + + Whether to enable streaming output, true、false。 + + + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.appDetail.api_base_url}/text-to-audio' \ + --header 'Authorization: Bearer ENTER-YOUR-SECRET-KEY' \ + --form 'file=Hello Dify;user=abc-123;streaming=false' + ``` + + + + + ```json {{ title: 'headers' }} + { + "Content-Type": "audio/wav" + } + ``` + + + + +--- + ### Response - `tool_icons`(object[string]) tool icons - - `tool_name` (string) + - `tool_name` (string) - `icon` (object|string) - (object) icon object - `background` (string) background color in hex format diff --git a/web/app/components/develop/template/template_chat.zh.mdx b/web/app/components/develop/template/template_chat.zh.mdx index 9c4e3c7c3c..b87a89b825 100644 --- a/web/app/components/develop/template/template_chat.zh.mdx +++ b/web/app/components/develop/template/template_chat.zh.mdx @@ -14,8 +14,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' ### 鉴权 - Service API 使用 `API-Key` 进行鉴权。 - **强烈建议开发者把 `API-Key` 放在后端存储,而非分享或者放在客户端存储,以免 `API-Key` 泄露,导致财产损失。** + Service API 使用 `API-Key` 进行鉴权。 + **强烈建议开发者把 `API-Key` 放在后端存储,而非分享或者放在客户端存储,以免 `API-Key` 泄露,导致财产损失。** 所有 API 请求都应在 **`Authorization`** HTTP Header 中包含您的 `API-Key`,如下所示: @@ -44,14 +44,14 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' 用户输入/提问内容。 - (选填)允许传入 App 定义的各变量值。 + (选填)允许传入 App 定义的各变量值。 inputs 参数包含了多组键值对(Key/Value pairs),每组的键对应一个特定变量,每组的值则是该变量的具体值。 - `streaming` 流式模式(推荐)。基于 SSE(**[Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)**)实现类似打字机输出方式的流式返回。 - - `blocking` 阻塞模式,等待执行完毕后返回结果。(请求若流程较长可能会被中断)。 - 由于 Cloudflare 限制,请求会在 100 秒超时无返回后中断。 + - `blocking` 阻塞模式,等待执行完毕后返回结果。(请求若流程较长可能会被中断)。 + 由于 Cloudflare 限制,请求会在 100 秒超时无返回后中断。 注:Agent模式下不允许blocking。 @@ -77,9 +77,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' ### Response - 当 `response_mode` 为 `blocking` 时,返回 ChatCompletionResponse object。 + 当 `response_mode` 为 `blocking` 时,返回 ChatCompletionResponse object。 当 `response_mode` 为 `streaming`时,返回 ChunkChatCompletionResponse object 流式序列。 - + ### ChatCompletionResponse 返回完整的 App 结果,`Content-Type` 为 `application/json`。 @@ -147,7 +147,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' - `conversation_id` (string) 会话 ID - `answer` (string) 替换内容(直接替换 LLM 所有回复文本) - `created_at` (int) 创建时间戳,如:1705395332 - - `event: error` + - `event: error` 流式输出过程中出现的异常会以 stream event 形式输出,收到异常事件后即结束。 - `task_id` (string) 任务 ID,用于请求跟踪和下方的停止响应接口 - `message_id` (string) 消息唯一 ID @@ -278,7 +278,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' 上传文件(目前仅支持图片)并在发送消息时使用,可实现图文多模态理解。 - 支持 png, jpg, jpeg, webp, gif 格式。 + 支持 png, jpg, jpeg, webp, gif 格式。 上传的文件仅供当前终端用户使用。 ### Request Body @@ -328,12 +328,12 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' ```json {{ title: 'Response' }} { - "id": "72fa9618-8f89-4a37-9b33-7e1178a24a67", + "id": "72fa9618-8f89-4a37-9b33-7e1178a24a67", "name": "example.png", "size": 1024, "extension": "png", "mime_type": "image/png", - "created_by": 123, + "created_by": 123, "created_at": 1577836800, } ``` @@ -352,7 +352,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' 仅支持流式模式。 ### Path - `task_id` (string) 任务 ID,可在流式返回 Chunk 中获取 - + ### Request Body - `user` (string) Required 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。 @@ -772,7 +772,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' ### Response - - `result` (string) 固定返回 success + - `result` (string) 固定返回 success @@ -874,7 +874,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' 语音文件。 - 支持格式:`['mp3', 'mp4', 'mpeg', 'mpga', 'm4a', 'wav', 'webm']` + 支持格式:`['mp3', 'mp4', 'mpeg', 'mpga', 'm4a', 'wav', 'webm']` 文件大小限制:15MB @@ -909,6 +909,54 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' --- + + + + 文字转语音,仅支持 openai 模型。 + + ### Request Body + + + + 语音生成内容。 + + + 用户标识,由开发者定义规则,需保证用户标识在应用内唯一。 + + + 是否启用流式输出true、false。 + + + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.appDetail.api_base_url}/text-to-audio' \ + --header 'Authorization: Bearer ENTER-YOUR-SECRET-KEY' \ + --form 'file=你好Dify;user=abc-123;streaming=false' + ``` + + + + + ```json {{ title: 'headers' }} + { + "Content-Type": "audio/wav" + } + ``` + + + + +--- + ### Response - `tool_icons`(object[string]) 工具图标 - - `工具名称` (string) + - `工具名称` (string) - `icon` (object|string) - (object) 图标 - `background` (string) hex格式的背景色 diff --git a/web/app/components/header/account-setting/model-provider-page/declarations.ts b/web/app/components/header/account-setting/model-provider-page/declarations.ts index e8cb079a14..d8eaf5f625 100644 --- a/web/app/components/header/account-setting/model-provider-page/declarations.ts +++ b/web/app/components/header/account-setting/model-provider-page/declarations.ts @@ -26,6 +26,7 @@ export enum ModelTypeEnum { rerank = 'rerank', speech2text = 'speech2text', moderation = 'moderation', + tts = 'tts', } export const MODEL_TYPE_TEXT = { @@ -34,6 +35,7 @@ export const MODEL_TYPE_TEXT = { [ModelTypeEnum.rerank]: 'Rerank', [ModelTypeEnum.speech2text]: 'Speech2text', [ModelTypeEnum.moderation]: 'Moderation', + [ModelTypeEnum.tts]: 'TTS', } export enum ConfigurateMethodEnum { diff --git a/web/app/components/header/account-setting/model-provider-page/hooks.ts b/web/app/components/header/account-setting/model-provider-page/hooks.ts index 064076751d..1cc0e0ae13 100644 --- a/web/app/components/header/account-setting/model-provider-page/hooks.ts +++ b/web/app/components/header/account-setting/model-provider-page/hooks.ts @@ -100,12 +100,13 @@ export const useProviderCrenditialsFormSchemasValue = ( return value } -export type ModelTypeIndex = 1 | 2 | 3 | 4 +export type ModelTypeIndex = 1 | 2 | 3 | 4 | 5 export const MODEL_TYPE_MAPS = { 1: ModelTypeEnum.textGeneration, 2: ModelTypeEnum.textEmbedding, 3: ModelTypeEnum.rerank, 4: ModelTypeEnum.speech2text, + 5: ModelTypeEnum.tts, } export const useModelList = (type: ModelTypeIndex) => { diff --git a/web/app/components/header/account-setting/model-provider-page/index.tsx b/web/app/components/header/account-setting/model-provider-page/index.tsx index b5c7d59013..6cb672673b 100644 --- a/web/app/components/header/account-setting/model-provider-page/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/index.tsx @@ -30,9 +30,10 @@ const ModelProviderPage = () => { const { data: embeddingsDefaultModel } = useDefaultModel(2) const { data: rerankDefaultModel } = useDefaultModel(3) const { data: speech2textDefaultModel } = useDefaultModel(4) + const { data: ttsDefaultModel } = useDefaultModel(5) const { modelProviders: providers } = useProviderContext() const { setShowModelModal } = useModalContext() - const defaultModelNotConfigured = !textGenerationDefaultModel && !embeddingsDefaultModel && !speech2textDefaultModel && !rerankDefaultModel + const defaultModelNotConfigured = !textGenerationDefaultModel && !embeddingsDefaultModel && !speech2textDefaultModel && !rerankDefaultModel && !ttsDefaultModel const [configedProviders, notConfigedProviders] = useMemo(() => { const configedProviders: ModelProvider[] = [] const notConfigedProviders: ModelProvider[] = [] @@ -104,6 +105,7 @@ const ModelProviderPage = () => { embeddingsDefaultModel={embeddingsDefaultModel} rerankDefaultModel={rerankDefaultModel} speech2textDefaultModel={speech2textDefaultModel} + ttsDefaultModel={ttsDefaultModel} />
{ diff --git a/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx b/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx index d382562bd4..4215bbfdec 100644 --- a/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx @@ -29,12 +29,14 @@ type SystemModelSelectorProps = { embeddingsDefaultModel: DefaultModelResponse | undefined rerankDefaultModel: DefaultModelResponse | undefined speech2textDefaultModel: DefaultModelResponse | undefined + ttsDefaultModel: DefaultModelResponse | undefined } const SystemModel: FC = ({ textGenerationDefaultModel, embeddingsDefaultModel, rerankDefaultModel, speech2textDefaultModel, + ttsDefaultModel, }) => { const { t } = useTranslation() const { notify } = useToastContext() @@ -43,11 +45,13 @@ const SystemModel: FC = ({ const { data: embeddingModelList } = useModelList(2) const { data: rerankModelList } = useModelList(3) const { data: speech2textModelList } = useModelList(4) + const { data: ttsModelList } = useModelList(5) const [changedModelTypes, setChangedModelTypes] = useState([]) const [currentTextGenerationDefaultModel, changeCurrentTextGenerationDefaultModel] = useSystemDefaultModelAndModelList(textGenerationDefaultModel, textGenerationModelList) const [currentEmbeddingsDefaultModel, changeCurrentEmbeddingsDefaultModel] = useSystemDefaultModelAndModelList(embeddingsDefaultModel, embeddingModelList) const [currentRerankDefaultModel, changeCurrentRerankDefaultModel] = useSystemDefaultModelAndModelList(rerankDefaultModel, rerankModelList) const [currentSpeech2textDefaultModel, changeCurrentSpeech2textDefaultModel] = useSystemDefaultModelAndModelList(speech2textDefaultModel, speech2textModelList) + const [currentTTSDefaultModel, changeCurrentTTSDefaultModel] = useSystemDefaultModelAndModelList(ttsDefaultModel, ttsModelList) const [open, setOpen] = useState(false) const getCurrentDefaultModelByModelType = (modelType: ModelTypeEnum) => { @@ -59,6 +63,8 @@ const SystemModel: FC = ({ return currentRerankDefaultModel else if (modelType === ModelTypeEnum.speech2text) return currentSpeech2textDefaultModel + else if (modelType === ModelTypeEnum.tts) + return currentTTSDefaultModel return undefined } @@ -71,6 +77,8 @@ const SystemModel: FC = ({ changeCurrentRerankDefaultModel(model) else if (modelType === ModelTypeEnum.speech2text) changeCurrentSpeech2textDefaultModel(model) + else if (modelType === ModelTypeEnum.tts) + changeCurrentTTSDefaultModel(model) if (!changedModelTypes.includes(modelType)) setChangedModelTypes([...changedModelTypes, modelType]) @@ -79,7 +87,7 @@ const SystemModel: FC = ({ const res = await updateDefaultModel({ url: '/workspaces/current/default-model', body: { - model_settings: [ModelTypeEnum.textGeneration, ModelTypeEnum.textEmbedding, ModelTypeEnum.rerank, ModelTypeEnum.speech2text].map((modelType) => { + model_settings: [ModelTypeEnum.textGeneration, ModelTypeEnum.textEmbedding, ModelTypeEnum.rerank, ModelTypeEnum.speech2text, ModelTypeEnum.tts].map((modelType) => { return { model_type: modelType, provider: getCurrentDefaultModelByModelType(modelType)?.provider, @@ -101,6 +109,8 @@ const SystemModel: FC = ({ updateModelList(modelType) else if (modelType === ModelTypeEnum.speech2text) updateModelList(modelType) + else if (modelType === ModelTypeEnum.tts) + updateModelList(modelType) }) } } @@ -136,7 +146,7 @@ const SystemModel: FC = ({
{t('common.modelProvider.systemReasoningModel.tip')}
} > - +
@@ -156,7 +166,7 @@ const SystemModel: FC = ({
{t('common.modelProvider.embeddingModel.tip')}
} > - +
@@ -176,7 +186,7 @@ const SystemModel: FC = ({
{t('common.modelProvider.rerankModel.tip')}
} > - +
@@ -196,7 +206,7 @@ const SystemModel: FC = ({
{t('common.modelProvider.speechToTextModel.tip')}
} > - +
@@ -207,6 +217,26 @@ const SystemModel: FC = ({ />
+
+
+ {t('common.modelProvider.ttsModel.key')} + {t('common.modelProvider.ttsModel.tip')}
+ } + > + + +
+
+ handleChangeDefaultModel(ModelTypeEnum.tts, model)} + /> +
+