Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
a88b539402
commit
8fa2d7bb0e
5 changed files with 61 additions and 15 deletions
|
|
@ -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 == "" {
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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})
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue