package api import ( "context" "encoding/json" "log/slog" "net/http" "github.com/gorilla/websocket" "project/internal/app" "project/internal/realtime" ) var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } type TTSProvider interface { Synthesize(ctx context.Context, text string) ([]byte, error) } type WSHandler struct { AppService *app.Service Hub *realtime.SessionHub TTS TTSProvider } func NewWSHandler(appService *app.Service, hub *realtime.SessionHub, tts TTSProvider) *WSHandler { return &WSHandler{AppService: appService, Hub: hub, TTS: tts} } func (h *WSHandler) HandleAudio(w http.ResponseWriter, r *http.Request) { sessionID := r.URL.Query().Get("session_id") if sessionID == "" { http.Error(w, "session_id required", http.StatusBadRequest) return } conn, err := upgrader.Upgrade(w, r, nil) if err != nil { slog.Error("ws upgrade failed", "err", err) return } defer conn.Close() h.Hub.Register(sessionID, conn) defer h.Hub.Unregister(sessionID) buf := realtime.NewAudioBuffer() for { msgType, data, err := conn.ReadMessage() if err != nil { break } switch msgType { case websocket.BinaryMessage: buf.Append(data) case websocket.TextMessage: var evt realtime.ClientEvent if err := json.Unmarshal(data, &evt); err != nil { continue } if evt.Type == realtime.EventEndUtterance { audio := buf.Flush() go h.processAudio(context.Background(), conn, sessionID, audio) } } } } func (h *WSHandler) processAudio(ctx context.Context, conn *websocket.Conn, sessionID string, audio []byte) { result, err := h.AppService.StreamAudioMessage(ctx, sessionID, audio, func(chunk string) { evt := realtime.ServerEvent{Type: realtime.EventTokenChunk, Payload: chunk} b, _ := json.Marshal(evt) _ = conn.WriteMessage(websocket.TextMessage, b) }) if err != nil { slog.Error("audio processing failed", "session_id", sessionID, "err", err) return } // kirim full reply untuk backward compat (transcript lengkap) evt := realtime.ServerEvent{Type: realtime.EventAssistantReply, Payload: result.Reply} b, _ := json.Marshal(evt) _ = conn.WriteMessage(websocket.TextMessage, b) // synthesize ke audio lalu kirim sebagai binary frame audioBytes, err := h.TTS.Synthesize(ctx, result.Reply) if err != nil { slog.Error("tts synthesis failed", "session_id", sessionID, "err", err) return } _ = conn.WriteMessage(websocket.BinaryMessage, audioBytes) }