Files
ai-agent/internal/services/conversation_interrupt_service.go
T
mlogclub 34051a4631 feat: Enhance AI Agent and Channel Management
- Updated labels in the AI Agents dashboard for clarity, changing "流程状态" to "Playbook 状态" and "未发布流程" to "未发布 Playbook".
- Introduced AI Agent rollout percentage management in channel editing, allowing users to set and rollback rollout percentages.
- Added new API endpoints for rolling back AI Agent rollout and fetching agent run metrics.
- Implemented new UI components for displaying agent run details, including status, duration, and input/output tokens.
- Enhanced type definitions for AdminChannel and AIAgent to include rollout percentages and runtime modes.
- Updated navigation to include a section for agent runs.
- Added new translations for agent run features in both English and Chinese.
2026-07-25 12:04:06 +08:00

196 lines
6.2 KiB
Go

package services
import (
"encoding/base64"
"strings"
"time"
"agent-desk/internal/models"
"agent-desk/internal/repositories"
"github.com/mlogclub/simple/sqls"
)
var ConversationInterruptService = newConversationInterruptService()
func newConversationInterruptService() *conversationInterruptService {
return &conversationInterruptService{}
}
type conversationInterruptService struct{}
func (s *conversationInterruptService) Get(id int64) *models.ConversationInterrupt {
return repositories.ConversationInterruptRepository.Get(sqls.DB(), id)
}
func (s *conversationInterruptService) GetByCheckPointID(checkPointID string) *models.ConversationInterrupt {
checkPointID = strings.TrimSpace(checkPointID)
if checkPointID == "" {
return nil
}
return repositories.ConversationInterruptRepository.GetByCheckPointID(sqls.DB(), checkPointID)
}
func (s *conversationInterruptService) FindLatestPendingByConversationID(conversationID int64) *models.ConversationInterrupt {
if conversationID <= 0 {
return nil
}
return repositories.ConversationInterruptRepository.FindLatestPendingByConversationID(sqls.DB(), conversationID)
}
func (s *conversationInterruptService) SaveCheckpoint(checkPointID string, data []byte) error {
checkPointID = strings.TrimSpace(checkPointID)
if checkPointID == "" {
return nil
}
now := time.Now()
item := &models.ConversationInterrupt{
CheckPointID: checkPointID,
CheckPointData: base64.StdEncoding.EncodeToString(data),
Status: "checkpointed",
CreatedAt: now,
UpdatedAt: now,
}
item = s.mergeForCheckpointUpdate(s.GetByCheckPointID(checkPointID), item)
return repositories.ConversationInterruptRepository.UpsertByCheckPointID(sqls.DB(), item)
}
func (s *conversationInterruptService) LoadCheckpoint(checkPointID string) ([]byte, bool, error) {
item := s.GetByCheckPointID(checkPointID)
if item == nil || strings.TrimSpace(item.CheckPointData) == "" {
return nil, false, nil
}
data, err := base64.StdEncoding.DecodeString(item.CheckPointData)
if err != nil {
return nil, false, err
}
return data, true, nil
}
func (s *conversationInterruptService) CreateOrUpdatePending(item *models.ConversationInterrupt) error {
if item == nil {
return nil
}
now := time.Now()
if item.CreatedAt.IsZero() {
item.CreatedAt = now
}
item.UpdatedAt = now
item.Status = strings.TrimSpace(item.Status)
if item.Status == "" {
item.Status = "pending"
}
item = s.mergeForPendingUpdate(s.GetByCheckPointID(item.CheckPointID), item)
return repositories.ConversationInterruptRepository.UpsertByCheckPointID(sqls.DB(), item)
}
func (s *conversationInterruptService) mergeForCheckpointUpdate(current, next *models.ConversationInterrupt) *models.ConversationInterrupt {
if next == nil {
return nil
}
if current == nil {
return next
}
merged := *current
merged.ConversationID = current.ConversationID
merged.AIAgentID = current.AIAgentID
merged.AgentRunID = current.AgentRunID
merged.AgentStepID = current.AgentStepID
merged.SourceMessageID = current.SourceMessageID
merged.LastResumeMessageID = current.LastResumeMessageID
merged.WorkflowRunID = current.WorkflowRunID
merged.WorkflowNodeID = current.WorkflowNodeID
merged.InterruptID = current.InterruptID
merged.InterruptType = current.InterruptType
merged.Status = current.Status
merged.PromptText = current.PromptText
merged.RequestData = current.RequestData
merged.ResumeCount = current.ResumeCount
merged.ExpiresAt = current.ExpiresAt
merged.CheckPointData = next.CheckPointData
merged.UpdatedAt = next.UpdatedAt
return &merged
}
func (s *conversationInterruptService) mergeForPendingUpdate(current, next *models.ConversationInterrupt) *models.ConversationInterrupt {
if next == nil {
return nil
}
if current == nil {
return next
}
merged := *current
merged.ConversationID = next.ConversationID
merged.AIAgentID = next.AIAgentID
merged.AgentRunID = next.AgentRunID
merged.AgentStepID = next.AgentStepID
merged.SourceMessageID = next.SourceMessageID
merged.WorkflowRunID = next.WorkflowRunID
merged.WorkflowNodeID = next.WorkflowNodeID
merged.InterruptID = next.InterruptID
merged.InterruptType = next.InterruptType
merged.Status = next.Status
merged.PromptText = next.PromptText
merged.RequestData = next.RequestData
merged.UpdatedAt = next.UpdatedAt
return &merged
}
func (s *conversationInterruptService) MarkResolved(id int64, lastResumeMessageID int64) error {
current := s.Get(id)
nextCount := 1
if current != nil {
nextCount = current.ResumeCount + 1
}
return repositories.ConversationInterruptRepository.Updates(sqls.DB(), id, map[string]any{
"status": "resolved",
"last_resume_message_id": lastResumeMessageID,
"resume_count": nextCount,
"updated_at": time.Now(),
})
}
func (s *conversationInterruptService) MarkCancelled(id int64, lastResumeMessageID int64) error {
current := s.Get(id)
nextCount := 1
if current != nil {
nextCount = current.ResumeCount + 1
}
return repositories.ConversationInterruptRepository.Updates(sqls.DB(), id, map[string]any{
"status": "cancelled",
"last_resume_message_id": lastResumeMessageID,
"resume_count": nextCount,
"updated_at": time.Now(),
})
}
func (s *conversationInterruptService) MarkExpired(id int64, lastResumeMessageID int64) error {
current := s.Get(id)
nextCount := 1
if current != nil {
nextCount = current.ResumeCount + 1
}
return repositories.ConversationInterruptRepository.Updates(sqls.DB(), id, map[string]any{
"status": "expired",
"last_resume_message_id": lastResumeMessageID,
"resume_count": nextCount,
"updated_at": time.Now(),
})
}
func (s *conversationInterruptService) MarkPendingAgain(id int64, interruptID, promptText string, lastResumeMessageID int64) error {
current := s.Get(id)
nextCount := 1
if current != nil {
nextCount = current.ResumeCount + 1
}
return repositories.ConversationInterruptRepository.Updates(sqls.DB(), id, map[string]any{
"status": "pending",
"interrupt_id": strings.TrimSpace(interruptID),
"prompt_text": strings.TrimSpace(promptText),
"last_resume_message_id": lastResumeMessageID,
"resume_count": nextCount,
"updated_at": time.Now(),
})
}