Files
gochat/backend/internal/handler/api/v1/assignable_agent_handler.go
T
Rogeeandrogee 83a8ab315a H-16: align takeover with channel AI workflow (#2)
* feat(conversations): complete manual AI takeover

* fix(conversations): align AI takeover flow with channel AI

* fix(conversations): close takeover review gaps

---------

Co-authored-by: Rogee <rogee@ipao.vip>
2026-08-12 22:57:06 +08:00

125 lines
4.2 KiB
Go

package v1
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/gochat/gochat/internal/service"
"github.com/gochat/gochat/pkg/response"
)
// AssignableAgentHandler handles endpoints for finding agents assignable to conversations.
// Reference: Chatwoot app/controllers/api/v1/accounts/assignable_agents_controller.rb
// Two routes:
// 1. GET /api/v1/accounts/:account_id/assignable_agents (with inbox_ids[] query param)
// 2. GET /api/v1/accounts/:account_id/inboxes/:inbox_id/assignable_agents (single inbox)
type AssignableAgentHandler struct {
svc *service.AssignableAgentService
}
// NewAssignableAgentHandler creates a new AssignableAgentHandler.
func NewAssignableAgentHandler(svc *service.AssignableAgentService) *AssignableAgentHandler {
return &AssignableAgentHandler{svc: svc}
}
// List returns agents that can be assigned to conversations in the specified inbox(es).
// Supports two modes:
// 1. Standalone resource: GET /api/v1/accounts/:account_id/assignable_agents?inbox_ids[]=1&inbox_ids[]=2
// (Ref: Chatwoot resources :assignable_agents, only: [:index] — takes inbox_ids[] query param)
// 2. Inbox member action: GET /api/v1/accounts/:account_id/inboxes/:inbox_id/assignable_agents
// (Ref: Chatwoot get :assignable_agents, on: :member on inboxes)
//
// 返回带workload信息的agent列表,按workload升序排序(最少conversations优先)。
func (h *AssignableAgentHandler) List(c *gin.Context) {
if h.svc == nil {
response.AbortWithStatusError(c, http.StatusUnprocessableEntity, response.ErrInternal, "assignable agent service unavailable")
return
}
accountID := getAccountID(c)
if accountID == 0 {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id")
return
}
// Determine inbox IDs from either path param or query param
inboxIDs := []uint{}
// Mode 2: inbox_id from URL path (GET /accounts/:account_id/inboxes/:inbox_id/assignable_agents)
if inboxIDStr := c.Param("inbox_id"); inboxIDStr != "" {
inboxID, parseErr := strconv.ParseUint(inboxIDStr, 10, 32)
if parseErr != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid inbox_id")
return
}
inboxIDs = append(inboxIDs, uint(inboxID))
}
// Mode 1 & 2: additional inbox IDs from query param inbox_ids[]
if queryInboxIDs, exists := c.GetQueryArray("inbox_ids[]"); exists {
for _, qid := range queryInboxIDs {
id, parseErr := strconv.ParseUint(qid, 10, 32)
if parseErr == nil {
// Avoid duplicate if path inbox_id matches query inbox_id
isDup := false
if len(inboxIDs) > 0 && uint(id) == inboxIDs[0] {
isDup = true
}
if !isDup {
inboxIDs = append(inboxIDs, uint(id))
}
}
}
}
agents, svcErr := h.svc.GetAssignableAgents(c.Request.Context(), accountID, inboxIDs)
if svcErr != nil {
response.AbortWithStatusError(c, http.StatusUnprocessableEntity, response.ErrInternal, "failed to list assignable agents")
return
}
payload := serializeAssignableAgents(accountID, agents)
c.JSON(http.StatusOK, gin.H{"payload": payload})
}
func serializeAssignableAgents(accountID uint, agents []service.AssignableAgentDTO, includeAgentBots ...bool) []map[string]any {
includeType := len(includeAgentBots) > 0 && includeAgentBots[0]
payload := make([]map[string]any, 0, len(agents))
for i := range agents {
agent := agents[i]
availableName := agent.DisplayName
if availableName == "" {
availableName = agent.Name
}
provider := agent.Provider
if provider == "" {
provider = "email"
}
customRoleID := any(nil)
if agent.CustomRoleID != 0 {
customRoleID = agent.CustomRoleID
}
item := map[string]any{
"id": agent.ID,
"account_id": accountID,
"availability_status": agent.AvailabilityStatus,
"auto_offline": agent.AutoOffline,
"confirmed": agent.Confirmed,
"email": agent.Email,
"provider": provider,
"available_name": availableName,
"name": agent.Name,
"role": agent.Role,
"thumbnail": agent.AvatarURL,
"custom_role_id": customRoleID,
}
if includeType {
item["assignee_type"] = "User"
}
payload = append(payload, item)
}
return payload
}