package v1 import ( "net/http" "strconv" "github.com/gin-gonic/gin" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/service" applogger "github.com/gochat/gochat/pkg/logger" "github.com/gochat/gochat/pkg/pagination" "github.com/gochat/gochat/pkg/response" ) // CaptainAssistantHandler handles CaptainAssistant REST API endpoints. // Reference: Chatwoot enterprise/app/controllers/api/v1/captain/assistants_controller.rb type CaptainAssistantHandler struct { svc *service.CaptainAssistantService } // NewCaptainAssistantHandler creates a new CaptainAssistantHandler. func NewCaptainAssistantHandler(svc *service.CaptainAssistantService) *CaptainAssistantHandler { return &CaptainAssistantHandler{svc: svc} } // Create creates a new captain assistant. // POST /api/v1/accounts/:account_id/captain_assistants func (h *CaptainAssistantHandler) Create(c *gin.Context) { accountID, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } var req service.CreateAssistantRequest if err := c.ShouldBindJSON(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } assistant, err := h.svc.Create(c.Request.Context(), uint(accountID), &req) if err != nil { applogger.L().Errorf("Create captain assistant: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to create assistant") return } response.Created(c, assistant) } // Get retrieves a captain assistant by ID. // GET /api/v1/accounts/:account_id/captain_assistants/:id func (h *CaptainAssistantHandler) Get(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid id") return } assistant, err := h.svc.Get(c.Request.Context(), uint(id)) if err != nil { applogger.L().Errorf("Get captain assistant: %v", err) response.AbortWithStatusError(c, http.StatusNotFound, response.ErrNotFound, "assistant not found") return } response.OK(c, assistant) } // Update updates an existing captain assistant. // PUT /api/v1/accounts/:account_id/captain_assistants/:id func (h *CaptainAssistantHandler) Update(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid id") return } var req service.UpdateAssistantRequest if err := c.ShouldBindJSON(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } assistant, err := h.svc.Update(c.Request.Context(), uint(id), &req) if err != nil { applogger.L().Errorf("Update captain assistant: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to update assistant") return } response.OK(c, assistant) } // Delete deletes a captain assistant. // DELETE /api/v1/accounts/:account_id/captain_assistants/:id func (h *CaptainAssistantHandler) Delete(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid id") return } if err := h.svc.Delete(c.Request.Context(), uint(id)); err != nil { applogger.L().Errorf("Delete captain assistant: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to delete assistant") return } response.NoContent(c) } // List retrieves captain assistants for an account. // GET /api/v1/accounts/:account_id/captain_assistants func (h *CaptainAssistantHandler) List(c *gin.Context) { accountID, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } p := pagination.Parse(c) assistants, count, err := h.svc.List(c.Request.Context(), uint(accountID), p.Offset, p.PerPage) if err != nil { applogger.L().Errorf("List captain assistants: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to list assistants") return } response.OKWithMeta(c, assistants, p.Page, p.PerPage, count) } // GetConfig retrieves the assistant's JSONB config. // GET /api/v1/accounts/:account_id/captain_assistants/:id/config func (h *CaptainAssistantHandler) GetConfig(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid id") return } cfg, err := h.svc.GetConfig(c.Request.Context(), uint(id)) if err != nil { applogger.L().Errorf("GetConfig captain assistant: %v", err) response.AbortWithStatusError(c, http.StatusNotFound, response.ErrNotFound, "assistant not found") return } response.OK(c, cfg) } // SetConfig updates the assistant's JSONB config. // PUT /api/v1/accounts/:account_id/captain_assistants/:id/config func (h *CaptainAssistantHandler) SetConfig(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid id") return } var cfg model.AssistantConfig if err := c.ShouldBindJSON(&cfg); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } if err := h.svc.SetConfig(c.Request.Context(), uint(id), &cfg); err != nil { applogger.L().Errorf("SetConfig captain assistant: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to update config") return } response.OK(c, cfg) } // AssociateInbox binds an assistant to an inbox. // POST /api/v1/accounts/:account_id/captain_assistants/:id/inboxes func (h *CaptainAssistantHandler) AssociateInbox(c *gin.Context) { assistantID, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid assistant id") return } accountID, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } var req struct { InboxID uint `json:"inbox_id" validate:"required"` } if err := c.ShouldBindJSON(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } if err := h.svc.AssociateInbox(c.Request.Context(), uint(assistantID), req.InboxID, uint(accountID)); err != nil { applogger.L().Errorf("AssociateInbox: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to associate inbox") return } response.OK(c, gin.H{"associated": true}) } // DissociateInbox unbinds an assistant from an inbox. // DELETE /api/v1/accounts/:account_id/captain_assistants/:id/inboxes/:inbox_id func (h *CaptainAssistantHandler) DissociateInbox(c *gin.Context) { assistantID, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid assistant id") return } inboxID, err := strconv.ParseUint(c.Param("inbox_id"), 10, 64) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid inbox_id") return } if err := h.svc.DissociateInbox(c.Request.Context(), uint(assistantID), uint(inboxID)); err != nil { applogger.L().Errorf("DissociateInbox: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to dissociate inbox") return } response.OK(c, gin.H{"dissociated": true}) } // GenerateResponse generates an AI response via RAG. // POST /api/v1/accounts/:account_id/captain_assistants/:id/generate_response func (h *CaptainAssistantHandler) GenerateResponse(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid id") return } var req struct { Query string `json:"query" validate:"required"` } if err := c.ShouldBindJSON(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } result, err := h.svc.GenerateResponse(c.Request.Context(), uint(id), req.Query) if err != nil { applogger.L().Errorf("GenerateResponse: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to generate response") return } response.OK(c, gin.H{"response": result}) }