package handlers import ( "net/http" "time" "github.com/google/uuid" "git.threesix.ai/jordan/persona-community-2/pkg/app" "git.threesix.ai/jordan/persona-community-2/pkg/auth" "git.threesix.ai/jordan/persona-community-2/pkg/httpresponse" "git.threesix.ai/jordan/persona-community-2/pkg/logging" "git.threesix.ai/jordan/persona-community-2/pkg/queue" "git.threesix.ai/jordan/persona-community-2/pkg/realtime" ) // Chat handles HTTP requests for chat messaging with AI responses. // User messages are broadcast immediately via SSE. // AI responses are enqueued and processed by the worker with streaming chunks. type Chat struct { queue queue.Producer sseHub *realtime.SSEHub logger *logging.Logger } // NewChat creates a new Chat handler. func NewChat(q queue.Producer, hub *realtime.SSEHub, logger *logging.Logger) *Chat { return &Chat{ queue: q, sseHub: hub, logger: logger.WithComponent("ChatHandler"), } } // SendMessageRequest is the request body for sending a chat message. type SendMessageRequest struct { Content string `json:"content" validate:"required,min=1,max=5000"` } // SendMessage broadcasts a chat message to a channel via SSE // and enqueues an AI response job for the worker. func (h *Chat) SendMessage(w http.ResponseWriter, r *http.Request) error { var req SendMessageRequest if err := app.BindAndValidate(r, &req); err != nil { return err } // Get user info userID := "anonymous" userName := "Anonymous" if user := auth.GetUser(r.Context()); user != nil { userID = user.ID if name, ok := user.Metadata["name"].(string); ok && name != "" { userName = name } else if user.Email != "" { userName = user.Email } } msgID := uuid.New().String() now := time.Now().UTC() // Broadcast user message to channel:general immediately (synchronous — users // see their own messages instantly without waiting for the queue) h.sseHub.SendToChannel("channel:general", &realtime.SSEEvent{ Type: "chat", Timestamp: now, JobID: msgID, Message: req.Content, Result: map[string]any{ "id": msgID, "content": req.Content, "userId": userID, "userName": userName, "timestamp": now.Format(time.RFC3339), }, }) // Enqueue AI response job — worker streams chunks via Redis → SSE if _, err := h.queue.Enqueue(r.Context(), "ai_chat_response", map[string]any{ "content": req.Content, "userID": userID, "channel": "channel:general", }); err != nil { h.logger.Error("failed to enqueue AI chat response", "error", err) // Don't fail the request — user message was already delivered } httpresponse.OK(w, r, map[string]string{ "id": msgID, "status": "sent", }) return nil }