From 71598854b774db1c0bb74c4cfe85117134ca2524 Mon Sep 17 00:00:00 2001 From: localai-org-maint-bot <306269227+localai-org-maint-bot@users.noreply.github.com> Date: Wed, 19 Aug 2026 22:15:29 +0000 Subject: [PATCH] feat(audio): list available TTS voices Clients cannot discover the named voices that an installed TTS model accepts without consulting backend-specific documentation. Expose voice metadata through the audio API and let custom model configs declare their own catalog. Assisted-by: Codex:gpt-5 --- core/config/backend_capabilities.go | 48 ++++++++- core/config/backend_capabilities_test.go | 22 +++++ core/config/meta/registry.go | 7 ++ core/config/model_config.go | 11 +++ core/http/auth/features.go | 2 + .../endpoints/localai/api_instructions.go | 2 +- core/http/endpoints/localai/tts_voices.go | 99 +++++++++++++++++++ .../http/endpoints/localai/tts_voices_test.go | 66 +++++++++++++ core/http/routes/localai.go | 4 + core/http/routes/openai.go | 2 + docs/content/features/text-to-audio.md | 31 ++++++ swagger/docs.go | 73 ++++++++++++++ swagger/swagger.json | 73 ++++++++++++++ swagger/swagger.yaml | 48 +++++++++ 14 files changed, 486 insertions(+), 2 deletions(-) create mode 100644 core/http/endpoints/localai/tts_voices.go create mode 100644 core/http/endpoints/localai/tts_voices_test.go diff --git a/core/config/backend_capabilities.go b/core/config/backend_capabilities.go index c0693d0486fd..51c8d92360d6 100644 --- a/core/config/backend_capabilities.go +++ b/core/config/backend_capabilities.go @@ -244,6 +244,8 @@ type BackendCapability struct { // contract. Model variants that share a backend may narrow this further; // use VoiceCloningForModel for UI/API decisions. VoiceCloning *VoiceCloningCapability + // TTSVoices lists named voices built into the backend. + TTSVoices []TTSVoice // Description is a human-readable summary of the backend. Description string } @@ -263,6 +265,22 @@ func referenceVoiceCloning() *VoiceCloningCapability { } } +// TTSVoicesForModel returns model-specific metadata or the backend's built-in +// catalog. The returned slice is safe for callers to modify. +func TTSVoicesForModel(cfg *ModelConfig) []TTSVoice { + if cfg == nil { + return nil + } + if len(cfg.TTSConfig.Voices) > 0 { + return slices.Clone(cfg.TTSConfig.Voices) + } + capability := GetBackendCapability(cfg.Backend) + if capability == nil { + return nil + } + return slices.Clone(capability.TTSVoices) +} + // BackendCapabilities maps each backend name (as used in model configs and gallery // entries) to its verified capabilities. This is the single source of truth for // what each backend supports. @@ -580,7 +598,35 @@ var BackendCapabilities = map[string]BackendCapability{ PossibleUsecases: []string{UsecaseTTS}, DefaultUsecases: []string{UsecaseTTS}, VoiceCloning: referenceVoiceCloning(), - Description: "Pocket TTS — lightweight text-to-speech", + TTSVoices: []TTSVoice{ + {Name: "juergen", Language: "de_DE", Gender: "male"}, + {Name: "alba", Language: "en_US", Gender: "female"}, + {Name: "bill_boerst", Language: "en_US", Gender: "male"}, + {Name: "charles", Language: "en_US", Gender: "male"}, + {Name: "george", Language: "en_US", Gender: "male"}, + {Name: "javert", Language: "en_US", Gender: "male"}, + {Name: "jean", Language: "en_US", Gender: "male"}, + {Name: "marius", Language: "en_US", Gender: "male"}, + {Name: "michael", Language: "en_US", Gender: "male"}, + {Name: "paul", Language: "en_US", Gender: "male"}, + {Name: "peter_yearsley", Language: "en_US", Gender: "male"}, + {Name: "stuart_bell", Language: "en_US", Gender: "male"}, + {Name: "anna", Language: "en_US", Gender: "female"}, + {Name: "azelma", Language: "en_US", Gender: "female"}, + {Name: "caro_davy", Language: "en_US", Gender: "female"}, + {Name: "cosette", Language: "en_US", Gender: "female"}, + {Name: "eponine", Language: "en_US", Gender: "female"}, + {Name: "eve", Language: "en_US", Gender: "female"}, + {Name: "fantine", Language: "en_US", Gender: "female"}, + {Name: "jane", Language: "en_US", Gender: "female"}, + {Name: "mary", Language: "en_US", Gender: "female"}, + {Name: "vera", Language: "en_US", Gender: "female"}, + {Name: "lola", Language: "es_ES", Gender: "female"}, + {Name: "estelle", Language: "fr_FR", Gender: "female"}, + {Name: "giovanni", Language: "it_IT", Gender: "male"}, + {Name: "rafael", Language: "pt_PT", Gender: "male"}, + }, + Description: "Pocket TTS — lightweight text-to-speech", }, "qwen-tts": { GRPCMethods: []GRPCMethod{MethodTTS}, diff --git a/core/config/backend_capabilities_test.go b/core/config/backend_capabilities_test.go index eeb6c3135c02..192dd5bb9679 100644 --- a/core/config/backend_capabilities_test.go +++ b/core/config/backend_capabilities_test.go @@ -291,6 +291,28 @@ var _ = Describe("VoiceCloningForModel", func() { ) }) +var _ = Describe("TTSVoicesForModel", func() { + It("returns the built-in Pocket TTS voice catalog", func() { + voices := TTSVoicesForModel(&ModelConfig{Name: "pocket", Backend: "pocket-tts"}) + Expect(voices).To(ContainElement(TTSVoice{Name: "alba", Language: "en_US", Gender: "female"})) + Expect(voices).To(ContainElement(TTSVoice{Name: "giovanni", Language: "it_IT", Gender: "male"})) + }) + + It("resolves the catalog for pinned backend variants", func() { + voices := TTSVoicesForModel(&ModelConfig{Name: "pocket", Backend: "cuda12-pocket-tts"}) + Expect(voices).To(ContainElement(TTSVoice{Name: "alba", Language: "en_US", Gender: "female"})) + }) + + It("prefers model-specific voice metadata", func() { + configured := []TTSVoice{{Name: "custom", Language: "en_GB"}} + voices := TTSVoicesForModel(&ModelConfig{ + Backend: "pocket-tts", + TTSConfig: TTSConfig{Voices: configured}, + }) + Expect(voices).To(Equal(configured)) + }) +}) + // llama.cpp serves Qwen3-TTS as well as the text LLMs it is known for, so the // backend has to advertise TTS. That advertisement is what makes narrowing // mandatory: the per-backend switch in VoiceCloningForModel ends in a diff --git a/core/config/meta/registry.go b/core/config/meta/registry.go index fbf40fa8b194..d33746cddf10 100644 --- a/core/config/meta/registry.go +++ b/core/config/meta/registry.go @@ -878,6 +878,13 @@ func DefaultRegistry() map[string]FieldMetaOverride { Component: "input", Order: 91, }, + "tts.voices": { + Section: "tts", + Label: "Named Voices", + Description: "Named voices that this model accepts. Each entry requires a name and can include language and gender metadata.", + Component: "json-editor", + Order: 92, + }, // --- Diffusers --- "diffusers.pipeline_type": { diff --git a/core/config/model_config.go b/core/config/model_config.go index 1cc7bc903edb..1b6ec23e4367 100644 --- a/core/config/model_config.go +++ b/core/config/model_config.go @@ -39,6 +39,17 @@ type TTSConfig struct { // A pointer preserves the distinction between an explicit false and the // default automatic behavior. VoiceCloning *bool `yaml:"voice_cloning,omitempty" json:"voice_cloning,omitempty"` + + // Voices describes named voices accepted by this model. Backends with a + // built-in catalog supply defaults when this list is empty. + Voices []TTSVoice `yaml:"voices,omitempty" json:"voices,omitempty"` +} + +// TTSVoice describes one named voice accepted by a text-to-speech model. +type TTSVoice struct { + Name string `yaml:"name" json:"name"` + Language string `yaml:"language,omitempty" json:"language,omitempty"` + Gender string `yaml:"gender,omitempty" json:"gender,omitempty"` } // @Description ModelConfig represents a model configuration diff --git a/core/http/auth/features.go b/core/http/auth/features.go index d83c9b25d268..ca7d024dfb24 100644 --- a/core/http/auth/features.go +++ b/core/http/auth/features.go @@ -58,6 +58,8 @@ var RouteFeatureRegistry = []RouteFeature{ {"POST", "/v1/audio/speech", FeatureAudioSpeech}, {"POST", "/audio/speech", FeatureAudioSpeech}, {"POST", "/tts", FeatureAudioSpeech}, + {"GET", "/v1/audio/voices", FeatureAudioSpeech}, + {"GET", "/audio/voices", FeatureAudioSpeech}, {"POST", "/v1/text-to-speech/:voice-id", FeatureAudioSpeech}, {"GET", "/api/voice-profiles", FeatureAudioSpeech}, {"GET", "/api/voice-profiles/:id/audio", FeatureAudioSpeech}, diff --git a/core/http/endpoints/localai/api_instructions.go b/core/http/endpoints/localai/api_instructions.go index d4574d2e08d2..daec9b6bf80b 100644 --- a/core/http/endpoints/localai/api_instructions.go +++ b/core/http/endpoints/localai/api_instructions.go @@ -40,7 +40,7 @@ var instructionDefs = []instructionDef{ Name: "audio", Description: "Text-to-speech, voice activity detection, transcription, speaker diarization, sound classification, and sound generation", Tags: []string{"audio"}, - Intro: "Diarization (/v1/audio/diarization) returns speaker-labelled time segments. Backends with native ASR-diarization (vibevoice-cpp) can also emit per-segment text via include_text=true; backends with a dedicated pipeline (sherpa-onnx + pyannote) emit segmentation only. Response formats: json (default), verbose_json (adds speakers summary + text), rttm (NIST format). Sound classification (/v1/audio/classification) returns scored AudioSet sound-event tags (audio tagging via the ced backend); top_k and threshold control the returned set.", + Intro: "GET /v1/audio/voices lists named voices for installed TTS models and accepts an optional model filter. Diarization (/v1/audio/diarization) returns speaker-labelled time segments. Backends with native ASR-diarization (vibevoice-cpp) can also emit per-segment text via include_text=true; backends with a dedicated pipeline (sherpa-onnx + pyannote) emit segmentation only. Response formats: json (default), verbose_json (adds speakers summary + text), rttm (NIST format). Sound classification (/v1/audio/classification) returns scored AudioSet sound-event tags (audio tagging via the ced backend); top_k and threshold control the returned set.", }, { Name: "voice-library", diff --git a/core/http/endpoints/localai/tts_voices.go b/core/http/endpoints/localai/tts_voices.go new file mode 100644 index 000000000000..af18865a2f68 --- /dev/null +++ b/core/http/endpoints/localai/tts_voices.go @@ -0,0 +1,99 @@ +package localai + +import ( + "net/http" + + "github.com/labstack/echo/v4" + "github.com/mudler/LocalAI/core/config" + "github.com/mudler/LocalAI/core/http/auth" + "github.com/mudler/LocalAI/core/schema" + "gorm.io/gorm" +) + +// TTSModelVoices groups named voices by installed model. +type TTSModelVoices struct { + Model string `json:"model"` + Voices []config.TTSVoice `json:"voices"` +} + +// TTSVoicesResponse is returned by the TTS voice discovery endpoint. +type TTSVoicesResponse struct { + Data []TTSModelVoices `json:"data"` +} + +// TTSVoicesEndpoint lists named voices advertised by installed model configs. +// +// @Summary List text-to-speech voices +// @Description List named voices and their language and gender metadata. Use the optional model query parameter to filter the response. +// @Tags audio +// @Produce json +// @Param model query string false "Installed model name" +// @Success 200 {object} TTSVoicesResponse +// @Failure 404 {object} schema.ErrorResponse +// @Router /v1/audio/voices [get] +func TTSVoicesEndpoint(loader *config.ModelConfigLoader, databases ...*gorm.DB) echo.HandlerFunc { + var authDB *gorm.DB + if len(databases) > 0 { + authDB = databases[0] + } + return func(c echo.Context) error { + allowed, err := ttsVoiceModelAllowlist(c, authDB) + if err != nil { + return c.JSON(http.StatusInternalServerError, schema.ErrorResponse{Error: &schema.APIError{ + Code: http.StatusInternalServerError, Message: "failed to check permissions", Type: "server_error", + }}) + } + modelName := c.QueryParam("model") + if modelName != "" { + cfg, ok := loader.GetModelConfig(modelName) + if !ok || (allowed != nil && !allowed[modelName]) { + return c.JSON(http.StatusNotFound, schema.ErrorResponse{Error: &schema.APIError{ + Code: http.StatusNotFound, Message: "model not found", Type: "not_found", + }}) + } + return c.JSON(http.StatusOK, TTSVoicesResponse{Data: []TTSModelVoices{{ + Model: cfg.Name, Voices: ttsVoicesForConfig(loader, &cfg), + }}}) + } + + response := TTSVoicesResponse{Data: []TTSModelVoices{}} + for _, cfg := range loader.GetAllModelsConfigs() { + if allowed != nil && !allowed[cfg.Name] { + continue + } + voices := ttsVoicesForConfig(loader, &cfg) + if len(voices) == 0 { + continue + } + response.Data = append(response.Data, TTSModelVoices{Model: cfg.Name, Voices: voices}) + } + return c.JSON(http.StatusOK, response) + } +} + +func ttsVoicesForConfig(loader *config.ModelConfigLoader, cfg *config.ModelConfig) []config.TTSVoice { + resolved, isAlias, err := loader.ResolveAlias(cfg) + if err == nil && isAlias { + return config.TTSVoicesForModel(resolved) + } + return config.TTSVoicesForModel(cfg) +} + +func ttsVoiceModelAllowlist(c echo.Context, db *gorm.DB) (map[string]bool, error) { + if db == nil { + return nil, nil + } + user := auth.GetUser(c) + if user == nil || user.Role == auth.RoleAdmin { + return nil, nil + } + permissions, err := auth.GetCachedUserPermissions(c, db, user.ID) + if err != nil || !permissions.AllowedModels.Enabled { + return nil, err + } + allowed := make(map[string]bool, len(permissions.AllowedModels.Models)) + for _, model := range permissions.AllowedModels.Models { + allowed[model] = true + } + return allowed, nil +} diff --git a/core/http/endpoints/localai/tts_voices_test.go b/core/http/endpoints/localai/tts_voices_test.go new file mode 100644 index 000000000000..2896b71dc783 --- /dev/null +++ b/core/http/endpoints/localai/tts_voices_test.go @@ -0,0 +1,66 @@ +package localai_test + +import ( + "net/http" + "net/http/httptest" + "os" + "path/filepath" + + "github.com/labstack/echo/v4" + "github.com/mudler/LocalAI/core/config" + . "github.com/mudler/LocalAI/core/http/endpoints/localai" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("TTSVoicesEndpoint", func() { + var loader *config.ModelConfigLoader + + BeforeEach(func() { + dir, err := os.MkdirTemp("", "localai-tts-voices-test") + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(os.RemoveAll, dir) + Expect(os.WriteFile(filepath.Join(dir, "pocket.yaml"), []byte("name: pocket\nbackend: pocket-tts\n"), 0o600)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(dir, "custom.yaml"), []byte("name: custom\nbackend: custom\nknown_usecases: [tts]\ntts:\n voices:\n - name: narrator\n language: en_GB\n"), 0o600)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(dir, "pocket-alias.yaml"), []byte("name: pocket-alias\nalias: pocket\n"), 0o600)).To(Succeed()) + loader = config.NewModelConfigLoader(dir) + Expect(loader.LoadModelConfigsFromPath(dir)).To(Succeed()) + }) + + It("returns the target catalog under an alias name", func() { + e := echo.New() + e.GET("/v1/audio/voices", TTSVoicesEndpoint(loader)) + rec := httptest.NewRecorder() + e.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/v1/audio/voices?model=pocket-alias", nil)) + Expect(rec.Code).To(Equal(http.StatusOK)) + Expect(rec.Body.String()).To(ContainSubstring(`"model":"pocket-alias"`)) + Expect(rec.Body.String()).To(ContainSubstring(`"name":"alba"`)) + }) + + It("lists voice metadata for installed TTS models", func() { + e := echo.New() + e.GET("/v1/audio/voices", TTSVoicesEndpoint(loader)) + rec := httptest.NewRecorder() + e.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/v1/audio/voices", nil)) + Expect(rec.Code).To(Equal(http.StatusOK)) + Expect(rec.Body.String()).To(ContainSubstring(`"model":"custom"`)) + Expect(rec.Body.String()).To(ContainSubstring(`"name":"narrator"`)) + Expect(rec.Body.String()).To(ContainSubstring(`"model":"pocket"`)) + Expect(rec.Body.String()).To(ContainSubstring(`"name":"alba"`)) + }) + + It("filters by model and rejects an unknown model", func() { + e := echo.New() + e.GET("/v1/audio/voices", TTSVoicesEndpoint(loader)) + + found := httptest.NewRecorder() + e.ServeHTTP(found, httptest.NewRequest(http.MethodGet, "/v1/audio/voices?model=pocket", nil)) + Expect(found.Code).To(Equal(http.StatusOK)) + Expect(found.Body.String()).To(ContainSubstring(`"model":"pocket"`)) + Expect(found.Body.String()).NotTo(ContainSubstring(`"model":"custom"`)) + + missing := httptest.NewRecorder() + e.ServeHTTP(missing, httptest.NewRequest(http.MethodGet, "/v1/audio/voices?model=missing", nil)) + Expect(missing.Code).To(Equal(http.StatusNotFound)) + }) +}) diff --git a/core/http/routes/localai.go b/core/http/routes/localai.go index 1da4683db85c..c54653d66b70 100644 --- a/core/http/routes/localai.go +++ b/core/http/routes/localai.go @@ -328,6 +328,7 @@ func RegisterLocalAIRoutes(router *echo.Echo, "vram_estimate": "/api/models/vram-estimate", "model_load_status": "/api/models/:id/load-status", "tts": "/tts", + "tts_voices": "/v1/audio/voices", "voice_profiles": "/api/voice-profiles", "transcription": "/v1/audio/transcriptions", "image_generation": "/v1/images/generations", @@ -345,6 +346,7 @@ func RegisterLocalAIRoutes(router *echo.Echo, "transcription": "/v1/audio/transcriptions", "diarization": "/v1/audio/diarization", "sound_classification": "/v1/audio/classification", + "tts_voices": "/v1/audio/voices", "image_generation": "/v1/images/generations", }, "config_management": map[string]string{ @@ -366,6 +368,7 @@ func RegisterLocalAIRoutes(router *echo.Echo, }, "ai_functions": map[string]string{ "tts": "/tts", + "tts_voices": "/v1/audio/voices", "voice_profiles": "/api/voice-profiles", "vad": "/vad", "video": "/video", @@ -414,6 +417,7 @@ func RegisterLocalAIRoutes(router *echo.Echo, "p2p": appConfig.P2PToken != "", "tracing": true, "voice_profiles": true, + "tts_voices": true, }, }) }) diff --git a/core/http/routes/openai.go b/core/http/routes/openai.go index 6a9012626861..16b74804af30 100644 --- a/core/http/routes/openai.go +++ b/core/http/routes/openai.go @@ -238,6 +238,8 @@ func RegisterOpenAIRoutes(app *echo.Echo, app.POST("/v1/audio/speech", audioSpeechHandler, audioSpeechMiddleware...) app.POST("/audio/speech", audioSpeechHandler, audioSpeechMiddleware...) + app.GET("/v1/audio/voices", localai.TTSVoicesEndpoint(application.ModelConfigLoader(), application.AuthDB())) + app.GET("/audio/voices", localai.TTSVoicesEndpoint(application.ModelConfigLoader(), application.AuthDB())) // images imageHandler := openai.ImageEndpoint(application.ModelConfigLoader(), application.ModelLoader(), application.ApplicationConfig()) diff --git a/docs/content/features/text-to-audio.md b/docs/content/features/text-to-audio.md index 8ff355a73f1f..169257c26031 100644 --- a/docs/content/features/text-to-audio.md +++ b/docs/content/features/text-to-audio.md @@ -30,6 +30,37 @@ curl http://localhost:8080/tts -H "Content-Type: application/json" -d '{ Returns an `audio/wav` file. +## List available voices + +Use `GET /v1/audio/voices` to list named voices for installed TTS models: + +```bash +curl http://localhost:8080/v1/audio/voices +``` + +Add the `model` query parameter to return one installed model: + +```bash +curl 'http://localhost:8080/v1/audio/voices?model=pocket-tts' +``` + +Each voice can include `language` and `gender` metadata. LocalAI supplies the +built-in Pocket TTS catalog. Other models can declare their catalog in YAML: + +```yaml +name: custom-tts +backend: custom +known_usecases: [tts] +tts: + voices: + - name: narrator + language: en_GB + gender: female +``` + +LocalAI returns `404` when the requested model is not installed. Models without +voice metadata do not appear in the unfiltered response. + ## Voice Library Administrators can manage reusable voice-cloning references from **Operate → Voice Library** in the LocalAI WebUI. The library replaces per-model filesystem and YAML setup for supported cloning backends: diff --git a/swagger/docs.go b/swagger/docs.go index b399f73e8ac3..aa8bac8b7785 100644 --- a/swagger/docs.go +++ b/swagger/docs.go @@ -2908,6 +2908,40 @@ const docTemplate = `{ } } }, + "/v1/audio/voices": { + "get": { + "description": "List named voices and their language and gender metadata. Use the optional model query parameter to filter the response.", + "produces": [ + "application/json" + ], + "tags": [ + "audio" + ], + "summary": "List text-to-speech voices", + "parameters": [ + { + "type": "string", + "description": "Installed model name", + "name": "model", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/localai.TTSVoicesResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/schema.ErrorResponse" + } + } + } + } + }, "/v1/chat/completions": { "post": { "tags": [ @@ -4071,6 +4105,20 @@ const docTemplate = `{ } } }, + "config.TTSVoice": { + "type": "object", + "properties": { + "gender": { + "type": "string" + }, + "language": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, "functions.Function": { "type": "object", "properties": { @@ -4600,6 +4648,31 @@ const docTemplate = `{ } } }, + "localai.TTSModelVoices": { + "type": "object", + "properties": { + "model": { + "type": "string" + }, + "voices": { + "type": "array", + "items": { + "$ref": "#/definitions/config.TTSVoice" + } + } + } + }, + "localai.TTSVoicesResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/localai.TTSModelVoices" + } + } + } + }, "localai.UpdateMaxReplicasPerModelRequest": { "type": "object", "properties": { diff --git a/swagger/swagger.json b/swagger/swagger.json index b04ebca6d597..1e6c75e99988 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -2905,6 +2905,40 @@ } } }, + "/v1/audio/voices": { + "get": { + "description": "List named voices and their language and gender metadata. Use the optional model query parameter to filter the response.", + "produces": [ + "application/json" + ], + "tags": [ + "audio" + ], + "summary": "List text-to-speech voices", + "parameters": [ + { + "type": "string", + "description": "Installed model name", + "name": "model", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/localai.TTSVoicesResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/schema.ErrorResponse" + } + } + } + } + }, "/v1/chat/completions": { "post": { "tags": [ @@ -4068,6 +4102,20 @@ } } }, + "config.TTSVoice": { + "type": "object", + "properties": { + "gender": { + "type": "string" + }, + "language": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, "functions.Function": { "type": "object", "properties": { @@ -4597,6 +4645,31 @@ } } }, + "localai.TTSModelVoices": { + "type": "object", + "properties": { + "model": { + "type": "string" + }, + "voices": { + "type": "array", + "items": { + "$ref": "#/definitions/config.TTSVoice" + } + } + } + }, + "localai.TTSVoicesResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/localai.TTSModelVoices" + } + } + } + }, "localai.UpdateMaxReplicasPerModelRequest": { "type": "object", "properties": { diff --git a/swagger/swagger.yaml b/swagger/swagger.yaml index f0e40bbe050f..180c1026bc4f 100644 --- a/swagger/swagger.yaml +++ b/swagger/swagger.yaml @@ -33,6 +33,15 @@ definitions: description: NotBefore is an RFC3339 timestamp. Empty disables the time check. type: string type: object + config.TTSVoice: + properties: + gender: + type: string + language: + type: string + name: + type: string + type: object functions.Function: properties: description: @@ -427,6 +436,22 @@ definitions: success: type: boolean type: object + localai.TTSModelVoices: + properties: + model: + type: string + voices: + items: + $ref: '#/definitions/config.TTSVoice' + type: array + type: object + localai.TTSVoicesResponse: + properties: + data: + items: + $ref: '#/definitions/localai.TTSModelVoices' + type: array + type: object localai.UpdateMaxReplicasPerModelRequest: properties: value: @@ -4961,6 +4986,29 @@ paths: summary: Transcribes audio into the input language. tags: - audio + /v1/audio/voices: + get: + description: List named voices and their language and gender metadata. Use the + optional model query parameter to filter the response. + parameters: + - description: Installed model name + in: query + name: model + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/localai.TTSVoicesResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/schema.ErrorResponse' + summary: List text-to-speech voices + tags: + - audio /v1/chat/completions: post: parameters: