diff --git a/internal/api/messages_handler.go b/internal/api/messages_handler.go index 4418311..2b559f4 100644 --- a/internal/api/messages_handler.go +++ b/internal/api/messages_handler.go @@ -5,6 +5,7 @@ import ( "log/slog" "net/http" "strconv" + "strings" "time" "github.com/go-chi/chi/v5" @@ -399,9 +400,60 @@ func (h *MessagesHandler) SearchMessages(w http.ResponseWriter, r *http.Request) limit = 20 } + // Parse advanced filter parameters + channelFilter := r.URL.Query().Get("channel") + agentFilter := r.URL.Query().Get("agent") + afterFilter := r.URL.Query().Get("after") + beforeFilter := r.URL.Query().Get("before") + var allMessages []*messaging.Message for _, agent := range ownedAgents { - opts := messaging.SearchOptions{Limit: limit} + opts := messaging.SearchOptions{ + Limit: limit, + After: afterFilter, + Before: beforeFilter, + } + + // Apply channel filters (comma-separated, prefix with - to exclude) + if channelFilter != "" { + channels := strings.Split(channelFilter, ",") + var includeChannels []string + var excludeChannels []string + for _, ch := range channels { + ch = strings.TrimSpace(ch) + if ch == "" { + continue + } + if strings.HasPrefix(ch, "-") { + excludeChannels = append(excludeChannels, strings.TrimPrefix(ch, "-")) + } else { + includeChannels = append(includeChannels, ch) + } + } + opts.Channels = includeChannels + opts.ExcludeChannels = excludeChannels + } + + // Apply agent filters (comma-separated, prefix with - to exclude) + if agentFilter != "" { + agentNames := strings.Split(agentFilter, ",") + var includeAgents []string + var excludeAgents []string + for _, a := range agentNames { + a = strings.TrimSpace(a) + if a == "" { + continue + } + if strings.HasPrefix(a, "-") { + excludeAgents = append(excludeAgents, strings.TrimPrefix(a, "-")) + } else { + includeAgents = append(includeAgents, a) + } + } + opts.Agents = includeAgents + opts.ExcludeAgents = excludeAgents + } + result, err := h.msgService.SearchMessages(r.Context(), agent.Name, query, opts) if err != nil { continue diff --git a/internal/messaging/options.go b/internal/messaging/options.go index ebfc626..2ec494b 100644 --- a/internal/messaging/options.go +++ b/internal/messaging/options.go @@ -25,14 +25,18 @@ type ReadOptions struct { // SearchOptions configures message search behavior. type SearchOptions struct { - FromAgent string `json:"from_agent,omitempty"` - ToAgent string `json:"to_agent,omitempty"` - ChannelID *int64 `json:"channel_id,omitempty"` - MinPriority int `json:"min_priority,omitempty"` - Status string `json:"status,omitempty"` - Limit int `json:"limit,omitempty"` - Offset int `json:"offset,omitempty"` - After string `json:"after,omitempty"` - Before string `json:"before,omitempty"` - Channel string `json:"channel,omitempty"` + FromAgent string `json:"from_agent,omitempty"` + ToAgent string `json:"to_agent,omitempty"` + ChannelID *int64 `json:"channel_id,omitempty"` + MinPriority int `json:"min_priority,omitempty"` + Status string `json:"status,omitempty"` + Limit int `json:"limit,omitempty"` + Offset int `json:"offset,omitempty"` + After string `json:"after,omitempty"` + Before string `json:"before,omitempty"` + Channel string `json:"channel,omitempty"` + Channels []string `json:"channels,omitempty"` // include messages in these channels + ExcludeChannels []string `json:"exclude_channels,omitempty"` // exclude messages in these channels + Agents []string `json:"agents,omitempty"` // include messages from/to these agents + ExcludeAgents []string `json:"exclude_agents,omitempty"` // exclude messages from/to these agents } diff --git a/internal/messaging/store.go b/internal/messaging/store.go index b575a1b..3bdb0ed 100644 --- a/internal/messaging/store.go +++ b/internal/messaging/store.go @@ -492,6 +492,57 @@ func (s *SQLiteMessageStore) buildSearchConditions(agentName, query string, opts args = append(args, opts.Channel) } + if len(opts.Channels) > 0 { + placeholders := make([]string, len(opts.Channels)) + for i, ch := range opts.Channels { + placeholders[i] = "?" + args = append(args, ch) + } + conditions = append(conditions, fmt.Sprintf("m.channel_id IN (SELECT id FROM channels WHERE LOWER(name) IN (%s))", strings.Join(placeholders, ","))) + } + + if len(opts.ExcludeChannels) > 0 { + placeholders := make([]string, len(opts.ExcludeChannels)) + for i, ch := range opts.ExcludeChannels { + placeholders[i] = "?" + args = append(args, ch) + } + conditions = append(conditions, fmt.Sprintf("(m.channel_id IS NULL OR m.channel_id NOT IN (SELECT id FROM channels WHERE LOWER(name) IN (%s)))", strings.Join(placeholders, ","))) + } + + if len(opts.Agents) > 0 { + placeholders := make([]string, len(opts.Agents)) + for i, a := range opts.Agents { + placeholders[i] = "?" + args = append(args, a) + } + inClause := strings.Join(placeholders, ",") + // Clone placeholders for the second IN clause + placeholders2 := make([]string, len(opts.Agents)) + for i, a := range opts.Agents { + placeholders2[i] = "?" + args = append(args, a) + } + inClause2 := strings.Join(placeholders2, ",") + conditions = append(conditions, fmt.Sprintf("(m.from_agent IN (%s) OR m.to_agent IN (%s))", inClause, inClause2)) + } + + if len(opts.ExcludeAgents) > 0 { + placeholders := make([]string, len(opts.ExcludeAgents)) + for i, a := range opts.ExcludeAgents { + placeholders[i] = "?" + args = append(args, a) + } + inClause := strings.Join(placeholders, ",") + placeholders2 := make([]string, len(opts.ExcludeAgents)) + for i, a := range opts.ExcludeAgents { + placeholders2[i] = "?" + args = append(args, a) + } + inClause2 := strings.Join(placeholders2, ",") + conditions = append(conditions, fmt.Sprintf("m.from_agent NOT IN (%s) AND (m.to_agent = '' OR m.to_agent NOT IN (%s))", inClause, inClause2)) + } + if opts.After != "" { if t, err := time.Parse(time.RFC3339, opts.After); err == nil { conditions = append(conditions, "m.created_at >= ?") diff --git a/internal/web/dist/index.html b/internal/web/dist/index.html index 2bccb06..6b78884 100644 --- a/internal/web/dist/index.html +++ b/internal/web/dist/index.html @@ -8,29 +8,29 @@ - - - - - - - - + + + + + + + +
{msg.body.slice(0, 200)}
- + ++ {searchTotal} result{searchTotal !== 1 ? 's' : ''} for "{$page.url.searchParams.get('q')}" +
+No messages found
+Try different keywords or adjust your filters
+