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 GitHub
parent a88b539402
commit 8fa2d7bb0e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 61 additions and 15 deletions

View file

@ -7,7 +7,7 @@ import (
// SplitSentences splits text into sentence-sized chunks suitable for TTS synthesis. // SplitSentences splits text into sentence-sized chunks suitable for TTS synthesis.
// It splits on sentence-ending punctuation (.!?\n) while avoiding false splits // 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. // the next sentence to prevent choppy playback.
func SplitSentences(text string) []string { func SplitSentences(text string) []string {
if text == "" { if text == "" {

View file

@ -677,10 +677,18 @@ func (c *DiscordChannel) listenVoiceControl(ctx context.Context) {
} }
func (c *DiscordChannel) playTTS(ctx context.Context, vc *discordgo.VoiceConnection, text string) { 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() { defer func() {
c.ttsMu.Lock() c.ttsMu.Lock()
c.cancelTTS = nil if c.cancelTTS == playbackCancel {
c.cancelTTS = nil
}
c.ttsMu.Unlock() c.ttsMu.Unlock()
}() }()
@ -699,12 +707,17 @@ func (c *DiscordChannel) playTTS(ctx context.Context, vc *discordgo.VoiceConnect
var prefetch chan ttResult 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() { defer func() {
if prefetch != nil { if prefetch != nil {
result := <-prefetch select {
if result.stream != nil { case result := <-prefetch:
result.stream.Close() if result.stream != nil {
result.stream.Close()
}
default:
// No prefetched result available to drain; avoid blocking on exit.
} }
} }
}() }()

View file

@ -17,27 +17,57 @@ func (c *DiscordChannel) handleVoiceCommand(s *discordgo.Session, m *discordgo.M
if m.Content == "!vc join" { if m.Content == "!vc join" {
vs, err := s.State.VoiceState(m.GuildID, m.Author.ID) vs, err := s.State.VoiceState(m.GuildID, m.Author.ID)
if err != nil || vs == nil { 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 return true
} }
logger.InfoCF("discord", "Joining voice channel", map[string]any{"channel": vs.ChannelID}) logger.InfoCF("discord", "Joining voice channel", map[string]any{"channel": vs.ChannelID})
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 { 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 return true
} }
go c.receiveVoice(vc, m.GuildID, m.ChannelID) 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 return true
} else if m.Content == "!vc leave" { } else if m.Content == "!vc leave" {
vc, exists := s.VoiceConnections[m.GuildID] vc, exists := s.VoiceConnections[m.GuildID]
if exists && vc != nil { if exists && vc != nil {
vc.Disconnect(c.ctx) if err := vc.Disconnect(c.ctx); err != nil {
s.ChannelMessageSend(m.ChannelID, "Left Voice Channel.") 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 { } 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 return true
} }

View file

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

View file

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