Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Hua Audio 2026-03-23 15:44:14 +01:00 committed by Huaaudio
parent df3bd2515c
commit f57a8ba261
5 changed files with 60 additions and 16 deletions

View file

@ -7,7 +7,7 @@ import (
// SplitSentences splits text into sentence-sized chunks suitable for TTS synthesis.
// It splits on sentence-ending punctuation (.!?\n) while avoiding false splits
// on abbreviations and decimal numbers. Very short fragments are merged with
// on decimal numbers. Very short fragments are merged with
// the next sentence to prevent choppy playback.
func SplitSentences(text string) []string {
if text == "" {

View file

@ -678,10 +678,18 @@ func (c *DiscordChannel) listenVoiceControl(ctx context.Context) {
}
func (c *DiscordChannel) playTTS(ctx context.Context, vc *discordgo.VoiceConnection, text string) {
// Clear cancelTTS when playback finishes (normal or interrupted)
// Capture the cancel func associated with this playback (if any).
c.ttsMu.Lock()
playbackCancel := c.cancelTTS
c.ttsMu.Unlock()
// Clear cancelTTS when playback finishes (normal or interrupted),
// but only if it still refers to this playback's cancel func.
defer func() {
c.ttsMu.Lock()
c.cancelTTS = nil
if c.cancelTTS == playbackCancel {
c.cancelTTS = nil
}
c.ttsMu.Unlock()
}()
@ -700,12 +708,17 @@ func (c *DiscordChannel) playTTS(ctx context.Context, vc *discordgo.VoiceConnect
var prefetch chan ttResult
// Ensure any in-flight prefetch is drained on exit to prevent stream leaks
// Ensure any in-flight prefetch is drained on exit to prevent stream leaks,
// but avoid blocking indefinitely if the prefetch goroutine is stuck or never sends.
defer func() {
if prefetch != nil {
result := <-prefetch
if result.stream != nil {
result.stream.Close()
select {
case result := <-prefetch:
if result.stream != nil {
result.stream.Close()
}
default:
// No prefetched result available to drain; avoid blocking on exit.
}
}
}()

View file

@ -17,7 +17,12 @@ func (c *DiscordChannel) handleVoiceCommand(s *discordgo.Session, m *discordgo.M
if m.Content == "!vc join" {
vs, err := s.State.VoiceState(m.GuildID, m.Author.ID)
if err != nil || vs == nil {
s.ChannelMessageSend(m.ChannelID, "You need to be in a voice channel first!")
if _, sendErr := s.ChannelMessageSend(m.ChannelID, "You need to be in a voice channel first!"); sendErr != nil {
logger.InfoCF("discord", "Failed to send voice channel requirement message", map[string]any{
"channel": m.ChannelID,
"error": sendErr,
})
}
return true
}
@ -25,21 +30,45 @@ func (c *DiscordChannel) handleVoiceCommand(s *discordgo.Session, m *discordgo.M
vc, err := s.ChannelVoiceJoin(c.ctx, m.GuildID, vs.ChannelID, false, false)
vc, err := s.ChannelVoiceJoin(c.ctx, m.GuildID, vs.ChannelID, false, false)
if err != nil {
s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Failed to join voice channel: %v", err))
if _, sendErr := s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Failed to join voice channel: %v", err)); sendErr != nil {
logger.InfoCF("discord", "Failed to send voice join error message", map[string]any{
"channel": m.ChannelID,
"error": sendErr,
})
}
return true
}
go c.receiveVoice(vc, m.GuildID, m.ChannelID)
s.ChannelMessageSend(m.ChannelID, "Joined Voice Channel! Listening for audio...")
if _, sendErr := s.ChannelMessageSend(m.ChannelID, "Joined Voice Channel! Listening for audio..."); sendErr != nil {
logger.InfoCF("discord", "Failed to send voice join success message", map[string]any{
"channel": m.ChannelID,
"error": sendErr,
})
}
return true
} else if m.Content == "!vc leave" {
vc, exists := s.VoiceConnections[m.GuildID]
if exists && vc != nil {
vc.Disconnect(c.ctx)
vc.Disconnect(c.ctx)
s.ChannelMessageSend(m.ChannelID, "Left Voice Channel.")
if err := vc.Disconnect(c.ctx); err != nil {
logger.InfoCF("discord", "Failed to disconnect from voice channel", map[string]any{
"guild": m.GuildID,
"error": err,
})
}
if _, sendErr := s.ChannelMessageSend(m.ChannelID, "Left Voice Channel."); sendErr != nil {
logger.InfoCF("discord", "Failed to send voice leave success message", map[string]any{
"channel": m.ChannelID,
"error": sendErr,
})
}
} else {
s.ChannelMessageSend(m.ChannelID, "Not in a voice channel.")
if _, sendErr := s.ChannelMessageSend(m.ChannelID, "Not in a voice channel."); sendErr != nil {
logger.InfoCF("discord", "Failed to send voice not-in-channel message", map[string]any{
"channel": m.ChannelID,
"error": sendErr,
})
}
}
return true
}

View file

@ -81,6 +81,8 @@ func NewOpenAITTSProvider(apiKey string, apiBase string, proxyURL string) *OpenA
client.Transport = &http.Transport{
Proxy: http.ProxyURL(pURL),
}
} else {
logger.Warnf("NewOpenAITTSProvider: invalid proxy URL %q: %v; proceeding without proxy", proxyURL, err)
}
}

View file

@ -244,10 +244,10 @@ func (a *Agent) processUtterance(ctx context.Context, acc *speechAccumulator) {
SenderID: acc.speakerID,
ChatID: acc.chatID,
Content: res.Text + oralPrompt,
Content: res.Text + oralPrompt,
Peer: bus.Peer{Kind: "channel", ID: acc.chatID},
Metadata: map[string]string{
"is_voice": "true",
"is_voice": "true",
"oral_prompt": oralPrompt,
},
}); err != nil {
logger.ErrorCF("voice-agent", "Failed to publish inbound message", map[string]any{"error": err})