Spaces:
Runtime error
Runtime error
File size: 4,419 Bytes
345a75b 6d3ab46 3d77ca7 345a75b 35c0011 345a75b 7da8a45 345a75b 8a5f1ff 345a75b 7da8a45 345a75b 7da8a45 3d77ca7 6d3ab46 3d77ca7 6d3ab46 345a75b 35c0011 345a75b 8a5f1ff 7da8a45 8a5f1ff 7da8a45 8a5f1ff 7da8a45 345a75b 8a5f1ff 345a75b 8a5f1ff 3d77ca7 35c0011 3d77ca7 35c0011 3d77ca7 35c0011 345a75b 3d77ca7 345a75b 6d3ab46 345a75b 8a5f1ff 3d77ca7 345a75b 3be26bc 3d77ca7 3be26bc 3d77ca7 345a75b 3d77ca7 3be26bc 345a75b 3d77ca7 35c0011 3d77ca7 8a5f1ff 3d77ca7 3be26bc 3d77ca7 8a5f1ff 3be26bc 35c0011 3d77ca7 8a5f1ff 3be26bc 3d77ca7 576e9a7 3d77ca7 8a5f1ff 3d77ca7 345a75b 8a5f1ff 3d77ca7 35c0011 8a5f1ff 3d77ca7 35c0011 3d77ca7 35c0011 3d77ca7 8a5f1ff 345a75b 3d77ca7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | package handler
import (
"context"
"encoding/base64"
"encoding/json"
"log"
"net/http"
"strings"
"time"
"github.com/alberdjuniawan/anput-api/middleware"
"github.com/alberdjuniawan/anput-api/repository"
"github.com/alberdjuniawan/anput-api/service"
"github.com/alberdjuniawan/anput-api/util"
chiMiddleware "github.com/go-chi/chi/v5/middleware"
"github.com/gorilla/websocket"
)
type WebSocketHandler struct {
AIService service.AIService
Repo repository.Repository
Upgrader websocket.Upgrader
}
func NewWebSocketHandler(aiService service.AIService, repo repository.Repository) *WebSocketHandler {
return &WebSocketHandler{
AIService: aiService,
Repo: repo,
Upgrader: websocket.Upgrader{
ReadBufferSize: 4096,
WriteBufferSize: 4096,
CheckOrigin: func(r *http.Request) bool { return true },
},
}
}
type wsResponse struct {
Type string `json:"type"`
Data interface{} `json:"data"`
}
func (h *WebSocketHandler) HandleStream(w http.ResponseWriter, r *http.Request) {
reqID := chiMiddleware.GetReqID(r.Context())
userIDVal := r.Context().Value(middleware.UserIDKey)
if userIDVal == nil {
util.WriteError(w, http.StatusUnauthorized, "Unauthorized", reqID)
return
}
userID := userIDVal.(string)
slug := r.URL.Query().Get("slug")
if slug == "" {
util.WriteError(w, http.StatusBadRequest, "Missing slug", reqID)
return
}
schemaData, err := h.Repo.GetSchemaBySlug(r.Context(), userID, slug)
if err != nil {
log.Printf("[%s] WS Schema Error: %v", reqID, err)
util.WriteError(w, http.StatusNotFound, "Schema not found", reqID)
return
}
conn, err := h.Upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("[%s] WS Upgrade Failed: %v", reqID, err)
return
}
defer conn.Close()
log.Printf("[%s] WS Connected", reqID)
var fullTranscriptBuilder strings.Builder
sentenceBuf := make([]byte, 0)
lastDataTime := time.Now()
const bytesPerSec = 32000
minChunkSize := bytesPerSec / 2
silenceThreshold := 700 * time.Millisecond
audioCh := make(chan []byte, 100)
stopCh := make(chan bool)
go func() {
defer close(audioCh)
for {
mt, p, err := conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("[%s] WS Read Error: %v", reqID, err)
}
stopCh <- true
return
}
if mt == websocket.BinaryMessage {
audioCh <- p
} else if mt == websocket.TextMessage {
var msg map[string]string
if json.Unmarshal(p, &msg) == nil && msg["action"] == "stop" {
stopCh <- true
return
}
if b, err := base64.StdEncoding.DecodeString(string(p)); err == nil {
audioCh <- b
}
}
}
}()
ticker := time.NewTicker(200 * time.Millisecond)
defer ticker.Stop()
for {
select {
case chunk := <-audioCh:
sentenceBuf = append(sentenceBuf, chunk...)
lastDataTime = time.Now()
case <-ticker.C:
if len(sentenceBuf) > minChunkSize && time.Since(lastDataTime) > silenceThreshold {
h.processChunk(conn, sentenceBuf, &fullTranscriptBuilder, reqID)
sentenceBuf = make([]byte, 0)
}
case <-stopCh:
if len(sentenceBuf) > 0 {
h.processChunk(conn, sentenceBuf, &fullTranscriptBuilder, reqID)
}
finalText := fullTranscriptBuilder.String()
log.Printf("[%s] Final Transcript: %s", reqID, finalText)
if len(strings.TrimSpace(finalText)) > 2 {
res, err := h.AIService.ExtractFromText(context.Background(), finalText, schemaData.Definition)
if err == nil {
conn.WriteJSON(wsResponse{
Type: "result",
Data: map[string]interface{}{
"parsed": res.Parsed,
"final_transcript": finalText,
},
})
} else {
log.Printf("[%s] Extraction Failed: %v", reqID, err)
conn.WriteJSON(wsResponse{Type: "error", Data: "Extraction failed: " + err.Error()})
}
}
log.Printf("[%s] WS Session Ended", reqID)
return
}
}
}
func (h *WebSocketHandler) processChunk(conn *websocket.Conn, audio []byte, sb *strings.Builder, reqID string) {
text, err := h.AIService.TranscribeChunk(context.Background(), audio)
if err == nil && len(strings.TrimSpace(text)) > 0 {
clean := strings.TrimSpace(text)
sb.WriteString(clean + " ")
conn.WriteJSON(wsResponse{
Type: "transcript",
Data: sb.String(),
})
} else if err != nil {
log.Printf("[%s] Transcribe Chunk Error: %v", reqID, err)
}
}
|