package inbox import ( "errors" "gorm.io/gorm" "senlinai-agent/backend/internal/domain" ) type CaptureInput struct { ProjectID uint UserID uint SourceType string Title string Body string } type Suggestion struct { Kind string `json:"kind"` Title string `json:"title"` Body string `json:"body"` } type Analyzer interface { Analyze(item domain.InboxItem, userID uint) ([]Suggestion, error) } type StaticAnalyzer struct { Suggestions []Suggestion } func (a StaticAnalyzer) Analyze(item domain.InboxItem, userID uint) ([]Suggestion, error) { return a.Suggestions, nil } type Service struct { db *gorm.DB analyzer Analyzer } func NewService(database *gorm.DB, analyzer Analyzer) *Service { return &Service{db: database, analyzer: analyzer} } func (s *Service) Capture(input CaptureInput) (*domain.InboxItem, error) { if input.ProjectID == 0 || input.UserID == 0 { return nil, errors.New("project and user are required") } if input.SourceType == "" { return nil, errors.New("source type is required") } item := &domain.InboxItem{ ProjectID: input.ProjectID, CreatedBy: input.UserID, SourceType: input.SourceType, Title: input.Title, Body: input.Body, Status: "open", } return item, s.db.Create(item).Error } func (s *Service) Analyze(itemID uint, userID uint) ([]Suggestion, error) { var item domain.InboxItem if err := s.db.First(&item, itemID).Error; err != nil { return nil, err } if s.analyzer == nil { return []Suggestion{}, nil } return s.analyzer.Analyze(item, userID) } func (s *Service) Confirm(itemID uint, selected []Suggestion) error { return s.db.Transaction(func(tx *gorm.DB) error { var item domain.InboxItem if err := tx.First(&item, itemID).Error; err != nil { return err } sourceInboxItemID := item.ID for _, suggestion := range selected { switch suggestion.Kind { case "task": if err := tx.Create(&domain.Task{ProjectID: item.ProjectID, CreatedBy: item.CreatedBy, SourceInboxItemID: &sourceInboxItemID, Title: suggestion.Title, Description: suggestion.Body}).Error; err != nil { return err } case "note": if err := tx.Create(&domain.Note{ProjectID: item.ProjectID, CreatedBy: item.CreatedBy, SourceInboxItemID: &sourceInboxItemID, Title: suggestion.Title, Markdown: suggestion.Body}).Error; err != nil { return err } case "source": if err := tx.Create(&domain.Source{ProjectID: item.ProjectID, CreatedBy: item.CreatedBy, SourceInboxItemID: &sourceInboxItemID, Kind: "link", Title: suggestion.Title, ContentText: suggestion.Body}).Error; err != nil { return err } default: return errors.New("unsupported suggestion kind") } } return tx.Model(&item).Update("status", "processed").Error }) }