diff --git a/pkg/asr/groq_transcriber.go b/pkg/asr/groq_transcriber.go index ca6a5eb5b..a17850cb0 100644 --- a/pkg/asr/groq_transcriber.go +++ b/pkg/asr/groq_transcriber.go @@ -16,6 +16,12 @@ import ( "github.com/sipeed/picoclaw/pkg/utils" ) +type Transcriber interface { + Name() string + Transcribe(ctx context.Context, audioFilePath string) (*TranscriptionResponse, error) + TranscribeData(ctx context.Context, data []byte, filename string) (*TranscriptionResponse, error) +} + type GroqTranscriber struct { apiKey string apiBase string @@ -35,45 +41,24 @@ func NewGroqTranscriber(apiKey string) *GroqTranscriber { } } -func (t *GroqTranscriber) Transcribe(ctx context.Context, audioFilePath string) (*TranscriptionResponse, error) { - logger.InfoCF("voice", "Starting transcription", map[string]any{"audio_file": audioFilePath}) - - audioFile, err := os.Open(audioFilePath) - if err != nil { - logger.ErrorCF("voice", "Failed to open audio file", map[string]any{"path": audioFilePath, "error": err}) - return nil, fmt.Errorf("failed to open audio file: %w", err) - } - defer audioFile.Close() - - fileInfo, err := audioFile.Stat() - if err != nil { - logger.ErrorCF("voice", "Failed to get file info", map[string]any{"path": audioFilePath, "error": err}) - return nil, fmt.Errorf("failed to get file info: %w", err) - } - - logger.DebugCF("voice", "Audio file details", map[string]any{ - "size_bytes": fileInfo.Size(), - "file_name": filepath.Base(audioFilePath), - }) +func (t *GroqTranscriber) TranscribeData(ctx context.Context, data []byte, filename string) (*TranscriptionResponse, error) { + logger.InfoCF("voice", "Starting memory transcription", map[string]any{"filename": filename, "bytes": len(data)}) var requestBody bytes.Buffer writer := multipart.NewWriter(&requestBody) - part, err := writer.CreateFormFile("file", filepath.Base(audioFilePath)) + part, err := writer.CreateFormFile("file", filename) if err != nil { logger.ErrorCF("voice", "Failed to create form file", map[string]any{"error": err}) return nil, fmt.Errorf("failed to create form file: %w", err) } - copied, err := io.Copy(part, audioFile) - if err != nil { + if _, err := io.Copy(part, bytes.NewReader(data)); err != nil { logger.ErrorCF("voice", "Failed to copy file content", map[string]any{"error": err}) return nil, fmt.Errorf("failed to copy file content: %w", err) } - logger.DebugCF("voice", "File copied to request", map[string]any{"bytes_copied": copied}) - - if err = writer.WriteField("model", "whisper-large-v3"); err != nil { + if err = writer.WriteField("model", "whisper-large-v3-turbo"); err != nil { logger.ErrorCF("voice", "Failed to write model field", map[string]any{"error": err}) return nil, fmt.Errorf("failed to write model field: %w", err) } @@ -88,20 +73,52 @@ func (t *GroqTranscriber) Transcribe(ctx context.Context, audioFilePath string) return nil, fmt.Errorf("failed to close multipart writer: %w", err) } + return t.doRequest(ctx, &requestBody, writer.FormDataContentType(), int64(len(data))) +} + +func (t *GroqTranscriber) Transcribe(ctx context.Context, audioFilePath string) (*TranscriptionResponse, error) { + logger.InfoCF("voice", "Starting transcription", map[string]any{"audio_file": audioFilePath}) + + audioFile, err := os.Open(audioFilePath) + if err != nil { + return nil, fmt.Errorf("failed to open audio file: %w", err) + } + defer audioFile.Close() + + fileInfo, err := audioFile.Stat() + if err != nil { + return nil, err + } + + var requestBody bytes.Buffer + writer := multipart.NewWriter(&requestBody) + part, err := writer.CreateFormFile("file", filepath.Base(audioFilePath)) + if err != nil { + return nil, err + } + io.Copy(part, audioFile) + writer.WriteField("model", "whisper-large-v3") + writer.WriteField("response_format", "json") + writer.Close() + + return t.doRequest(ctx, &requestBody, writer.FormDataContentType(), fileInfo.Size()) +} + +func (t *GroqTranscriber) doRequest(ctx context.Context, requestBody *bytes.Buffer, contentType string, fileSize int64) (*TranscriptionResponse, error) { url := t.apiBase + "/audio/transcriptions" - req, err := http.NewRequestWithContext(ctx, "POST", url, &requestBody) + req, err := http.NewRequestWithContext(ctx, "POST", url, requestBody) if err != nil { logger.ErrorCF("voice", "Failed to create request", map[string]any{"error": err}) return nil, fmt.Errorf("failed to create request: %w", err) } - req.Header.Set("Content-Type", writer.FormDataContentType()) + req.Header.Set("Content-Type", contentType) req.Header.Set("Authorization", "Bearer "+t.apiKey) logger.DebugCF("voice", "Sending transcription request to Groq API", map[string]any{ "url": url, "request_size_bytes": requestBody.Len(), - "file_size_bytes": fileInfo.Size(), + "file_size_bytes": fileSize, }) resp, err := t.httpClient.Do(req) diff --git a/pkg/channels/discord/discord.go b/pkg/channels/discord/discord.go index 38d365678..1b0355503 100644 --- a/pkg/channels/discord/discord.go +++ b/pkg/channels/discord/discord.go @@ -83,6 +83,7 @@ func NewDiscordChannel(cfg config.DiscordConfig, bus *bus.MessageBus) (*DiscordC ctx: context.Background(), typingStop: make(map[string]chan struct{}), bus: bus, + bus: bus, }, nil }