Edit from coplilot review
This commit is contained in:
parent
a3a1fdae86
commit
33109e862c
4 changed files with 18 additions and 52 deletions
|
|
@ -57,6 +57,7 @@ type AudioChunk struct {
|
||||||
SessionID string `json:"session_id"`
|
SessionID string `json:"session_id"`
|
||||||
SpeakerID string `json:"speaker_id"` // User ID or SSRC
|
SpeakerID string `json:"speaker_id"` // User ID or SSRC
|
||||||
ChatID string `json:"chat_id"` // Where to respond
|
ChatID string `json:"chat_id"` // Where to respond
|
||||||
|
Channel string `json:"channel"` // Source channel type (e.g. "discord")
|
||||||
Sequence uint64 `json:"sequence"`
|
Sequence uint64 `json:"sequence"`
|
||||||
Timestamp uint32 `json:"timestamp"`
|
Timestamp uint32 `json:"timestamp"`
|
||||||
SampleRate int `json:"sample_rate"`
|
SampleRate int `json:"sample_rate"`
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ func (c *DiscordChannel) Send(ctx context.Context, msg bus.OutboundMessage) erro
|
||||||
if c.cancelTTS != nil {
|
if c.cancelTTS != nil {
|
||||||
c.cancelTTS()
|
c.cancelTTS()
|
||||||
}
|
}
|
||||||
ttsCtx, ttsCancel := context.WithCancel(context.Background())
|
ttsCtx, ttsCancel := context.WithCancel(c.ctx)
|
||||||
c.cancelTTS = ttsCancel
|
c.cancelTTS = ttsCancel
|
||||||
c.ttsMu.Unlock()
|
c.ttsMu.Unlock()
|
||||||
|
|
||||||
|
|
@ -652,7 +652,10 @@ func (c *DiscordChannel) listenVoiceControl(ctx context.Context) {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case ctrl := <-c.bus.VoiceControlsChan():
|
case ctrl, ok := <-c.bus.VoiceControlsChan():
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
if ctrl.Type == "command" && ctrl.Action == "leave" {
|
if ctrl.Type == "command" && ctrl.Action == "leave" {
|
||||||
if strings.HasPrefix(ctrl.SessionID, "discord_vc_") {
|
if strings.HasPrefix(ctrl.SessionID, "discord_vc_") {
|
||||||
guildID := strings.TrimPrefix(ctrl.SessionID, "discord_vc_")
|
guildID := strings.TrimPrefix(ctrl.SessionID, "discord_vc_")
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package discord
|
package discord
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -9,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
|
|
||||||
|
"github.com/sipeed/picoclaw/pkg/audio"
|
||||||
"github.com/sipeed/picoclaw/pkg/bus"
|
"github.com/sipeed/picoclaw/pkg/bus"
|
||||||
"github.com/sipeed/picoclaw/pkg/logger"
|
"github.com/sipeed/picoclaw/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
@ -60,58 +60,14 @@ func streamOggOpusToDiscord(ctx context.Context, vc *discordgo.VoiceConnection,
|
||||||
vc.Speaking(true)
|
vc.Speaking(true)
|
||||||
defer vc.Speaking(false)
|
defer vc.Speaking(false)
|
||||||
|
|
||||||
var packet []byte
|
return audio.DecodeOggOpus(r, func(frame []byte) error {
|
||||||
header := make([]byte, 27)
|
|
||||||
|
|
||||||
for {
|
|
||||||
// Check for interruption
|
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
default:
|
case vc.OpusSend <- frame:
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
})
|
||||||
if _, err := io.ReadFull(r, header); err != nil {
|
|
||||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("failed to read ogg header: %w", err)
|
|
||||||
}
|
|
||||||
if string(header[:4]) != "OggS" {
|
|
||||||
return fmt.Errorf("invalid ogg magic string")
|
|
||||||
}
|
|
||||||
|
|
||||||
pageSegments := int(header[26])
|
|
||||||
segmentTable := make([]byte, pageSegments)
|
|
||||||
if _, err := io.ReadFull(r, segmentTable); err != nil {
|
|
||||||
return fmt.Errorf("failed to read segment table: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, lacing := range segmentTable {
|
|
||||||
segment := make([]byte, lacing)
|
|
||||||
if _, err := io.ReadFull(r, segment); err != nil {
|
|
||||||
return fmt.Errorf("failed to read segment data: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
packet = append(packet, segment...)
|
|
||||||
|
|
||||||
// If lacing is less than 255, the packet is complete
|
|
||||||
if lacing < 255 {
|
|
||||||
if len(packet) > 0 {
|
|
||||||
// Ignore Ogg Opus headers
|
|
||||||
if !bytes.HasPrefix(packet, []byte("OpusHead")) && !bytes.HasPrefix(packet, []byte("OpusTags")) {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
case vc.OpusSend <- packet:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Start new packet
|
|
||||||
packet = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DiscordChannel) receiveVoice(vc *discordgo.VoiceConnection, guildID string, chatID string) {
|
func (c *DiscordChannel) receiveVoice(vc *discordgo.VoiceConnection, guildID string, chatID string) {
|
||||||
|
|
@ -193,6 +149,7 @@ func (c *DiscordChannel) receiveVoice(vc *discordgo.VoiceConnection, guildID str
|
||||||
SessionID: sessionID,
|
SessionID: sessionID,
|
||||||
SpeakerID: fmt.Sprintf("%d", p.SSRC),
|
SpeakerID: fmt.Sprintf("%d", p.SSRC),
|
||||||
ChatID: chatID,
|
ChatID: chatID,
|
||||||
|
Channel: "discord",
|
||||||
Sequence: sequence,
|
Sequence: sequence,
|
||||||
Timestamp: p.Timestamp,
|
Timestamp: p.Timestamp,
|
||||||
SampleRate: 48000,
|
SampleRate: 48000,
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ type speechAccumulator struct {
|
||||||
chatID string
|
chatID string
|
||||||
speakerID string
|
speakerID string
|
||||||
sessionID string
|
sessionID string
|
||||||
|
channel string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *speechAccumulator) Push(chunk bus.AudioChunk) {
|
func (a *speechAccumulator) Push(chunk bus.AudioChunk) {
|
||||||
|
|
@ -117,6 +118,7 @@ func (a *Agent) handleChunk(chunk bus.AudioChunk) {
|
||||||
chatID: chunk.ChatID,
|
chatID: chunk.ChatID,
|
||||||
speakerID: chunk.SpeakerID,
|
speakerID: chunk.SpeakerID,
|
||||||
sessionID: chunk.SessionID,
|
sessionID: chunk.SessionID,
|
||||||
|
channel: chunk.Channel,
|
||||||
}
|
}
|
||||||
a.sessions[key] = acc
|
a.sessions[key] = acc
|
||||||
logger.DebugCF("voice-agent", "Started accumulating voice", map[string]any{"key": key, "file": filename})
|
logger.DebugCF("voice-agent", "Started accumulating voice", map[string]any{"key": key, "file": filename})
|
||||||
|
|
@ -185,7 +187,10 @@ func (a *Agent) processUtterance(ctx context.Context, acc *speechAccumulator) {
|
||||||
|
|
||||||
logger.InfoCF("voice-agent", "Transcription result", map[string]any{"text": res.Text, "duration": res.Duration})
|
logger.InfoCF("voice-agent", "Transcription result", map[string]any{"text": res.Text, "duration": res.Duration})
|
||||||
|
|
||||||
channelType := "discord"
|
channelType := acc.channel
|
||||||
|
if channelType == "" {
|
||||||
|
channelType = "discord" // fallback for legacy chunks
|
||||||
|
}
|
||||||
|
|
||||||
text := strings.ToLower(strings.TrimSpace(res.Text))
|
text := strings.ToLower(strings.TrimSpace(res.Text))
|
||||||
if strings.Contains(text, "leave the voice channel") || strings.Contains(text, "leave voice") ||
|
if strings.Contains(text, "leave the voice channel") || strings.Contains(text, "leave voice") ||
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue