mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-08-16 20:25:55 +00:00
Baseline groundwork for TTS expansion plan. Introduces a capabilities system that the follow-up plan (phase-01..04) builds on. Backend: - `audio.ParamSchema` / `ProviderCapabilities` types + per-provider capabilities.go for edge, elevenlabs, minimax, openai, gemini. - Gemini TTS provider (client, models, voices, wav encoder, multi-speaker via SpeakerVoice, audio-tag aware prompts). - TTSOptions gains `Params map[string]any` (read-only) + `Speakers`. - `VoiceListProvider` interface decouples HTTP voice handler from provider-specific impls. - `nested_keys.go` resolves dot-separated param paths for nested provider bodies (voice_settings.stability etc.). - Characterization + defaults-invariant tests per provider lock nil-params byte-equivalence before new params land. - `/v1/tts/capabilities` HTTP endpoint + integration coverage. - Dual-read tests (PG + SQLite) for tts_config. Frontend (web + desktop): - `DynamicParamForm` with depends-on evaluation, split into fields/logic modules. Slider primitive added. - `AudioTagPicker` + `MultiSpeakerEditor` for Gemini. - `voice-picker` refactored toward portal UX; combobox tightened. - tts-capabilities API client + typed hooks. - i18n catalogs (en/vi/zh) expanded; parity tests guard key drift. - TTS page reorganised (voice-model-section removed; playground + credentials + provider-setup split cleanly). Docs: codebase-summary, project-changelog, tts-provider-capabilities.
71 lines
2.5 KiB
Go
71 lines
2.5 KiB
Go
package http
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/audio"
|
|
"github.com/nextlevelbuilder/goclaw/internal/audio/edge"
|
|
"github.com/nextlevelbuilder/goclaw/internal/audio/elevenlabs"
|
|
"github.com/nextlevelbuilder/goclaw/internal/audio/gemini"
|
|
"github.com/nextlevelbuilder/goclaw/internal/audio/minimax"
|
|
"github.com/nextlevelbuilder/goclaw/internal/audio/openai"
|
|
"github.com/nextlevelbuilder/goclaw/internal/permissions"
|
|
)
|
|
|
|
// ttsCapabilitiesResponse is the JSON envelope for GET /v1/tts/capabilities.
|
|
type ttsCapabilitiesResponse struct {
|
|
Providers []audio.ProviderCapabilities `json:"providers"`
|
|
}
|
|
|
|
// builtinTTSCapabilities returns the static capability catalog for every known
|
|
// TTS provider — independent of which providers happen to be registered in the
|
|
// manager at runtime. Frontend needs this so users can browse + configure a
|
|
// provider before saving credentials. Each provider's Capabilities() reads no
|
|
// receiver state, so calling on a zero-value Provider is safe.
|
|
func builtinTTSCapabilities() []audio.ProviderCapabilities {
|
|
return []audio.ProviderCapabilities{
|
|
(&openai.Provider{}).Capabilities(),
|
|
(&elevenlabs.TTSProvider{}).Capabilities(),
|
|
(&edge.Provider{}).Capabilities(),
|
|
(&minimax.Provider{}).Capabilities(),
|
|
(&gemini.Provider{}).Capabilities(),
|
|
}
|
|
}
|
|
|
|
// handleCapabilities serves GET /v1/tts/capabilities.
|
|
// Returns catalog-level metadata for all known TTS providers — registered
|
|
// providers in the manager take precedence; unregistered ones fall back to
|
|
// the static builtin catalog so users can configure a fresh provider.
|
|
// Response is tenant-agnostic.
|
|
func (h *TTSHandler) handleCapabilities(w http.ResponseWriter, r *http.Request) {
|
|
h.mu.RLock()
|
|
mgr := h.manager
|
|
h.mu.RUnlock()
|
|
|
|
var registered []audio.ProviderCapabilities
|
|
if mgr != nil {
|
|
registered = mgr.ListCapabilities()
|
|
}
|
|
|
|
seen := make(map[string]bool, len(registered))
|
|
caps := make([]audio.ProviderCapabilities, 0, 5)
|
|
for _, c := range registered {
|
|
caps = append(caps, c)
|
|
seen[c.Provider] = true
|
|
}
|
|
for _, c := range builtinTTSCapabilities() {
|
|
if !seen[c.Provider] {
|
|
caps = append(caps, c)
|
|
}
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, ttsCapabilitiesResponse{Providers: caps})
|
|
}
|
|
|
|
// registerCapabilitiesRoute wires GET /v1/tts/capabilities onto mux.
|
|
// Called from TTSHandler.RegisterRoutes to keep all TTS routes co-located.
|
|
func (h *TTSHandler) registerCapabilitiesRoute(mux *http.ServeMux) {
|
|
mux.HandleFunc("GET /v1/tts/capabilities",
|
|
requireAuth(permissions.RoleOperator, h.handleCapabilities))
|
|
}
|