package tts // 本文件把 `*Client` 变成一个**既有语音链的一级**(`mvoice.Provider`)。 // // 当前组装走的是 `Service`(包在整条链前面,失败回落),不是把本级插进链里 // (`multimodal/voice.New` 组装的各级列表不外露,插进去要先改那条链,超出本切片 // 范围)。提供这个适配是为了让"作为一级"这条路**现在就是通的**:一旦组装方式 // 改成显式列级(`voice.NewChain(...)`),本级可以原样进链,不必重写 HTTP。 // // 作为链的一级时**选角同样按 ctx 里的 personaId 现算**(`Config.Resolve`), // 没有"这一级的固定发音人"这种概念 —— 因此它也不会把角色定死。 import ( "context" mvoice "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/multimodal/voice" ) // Provider 是 emotionTTS 作为降级链一级的适配:只提供 TTS,STT 方向返回 nil // (它不做转写;链遇到 nil 会自动跳过这一级去找下一个能转写的)。 type Provider struct { client *Client } // NewProvider 用客户端构造一级。client 为 nil 时 Name 仍可用,TTS 返回错误。 func NewProvider(client *Client) *Provider { return &Provider{client: client} } // Name 返回稳定的级别名(降级信号里就显示这个)。 func (p *Provider) Name() string { return ProviderName } // STT 返回 nil:本一级不提供转写。 func (p *Provider) STT() mvoice.STT { return nil } // TTS 返回本一级的合成实现。 func (p *Provider) TTS() mvoice.TTS { return ttsAdapter{p.client} } // ttsAdapter 把 `Client.Speech` 适配成链要求的 `TTS` 形状(`Audio` 类型不同)。 type ttsAdapter struct{ client *Client } // TTS 按 ctx 里的 personaId 选发音人后合成;client 未接线时返回明确错误而不是 panic。 func (a ttsAdapter) TTS(ctx context.Context, text string) (mvoice.Audio, error) { if a.client == nil { return mvoice.Audio{}, mvoice.ErrNotConfigured } selection, err := a.client.Config().Resolve(PersonaIDFromContext(ctx)) if err != nil { return mvoice.Audio{}, err } audio, err := a.client.Speech(ctx, selection.Character, text) if err != nil { return mvoice.Audio{}, err } return mvoice.Audio{Format: audio.Format, Data: audio.Data}, nil }