* 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>
125 lines
4.2 KiB
Go
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
|
|
}
|