diff --git a/TODO.md b/TODO.md index 75bd53f..92c885b 100644 --- a/TODO.md +++ b/TODO.md @@ -50,6 +50,20 @@ - Improved result rendering: - search cards now show source snippet/description separately from AI reason to reduce confusion between asset metadata and Gemini commentary +## Current Session Update (2026-03-13, Regression Fix) +- A regression was found after search optimization: + - Envato and Artgrid disappeared entirely for some real searches while Google Video still returned results +- Root cause: + - the first optimization reduced query-variant breadth too aggressively + - the first 3 query variants were not enough to recover Envato/Artgrid in some real SearXNG result sets +- Fix applied: + - search now runs in two stages + - stage 1 searches only the first few variants for speed + - stage 2 searches additional variants only for sources that still returned zero results +- Intent: + - keep the anti-timeout optimization + - recover Envato/Artgrid recall when the early pass is too narrow + ## Local Self-Test Workflow - Primary command: - `bash scripts/selftest.sh` diff --git a/backend/services/cse.go b/backend/services/cse.go index dff937b..7b74180 100644 --- a/backend/services/cse.go +++ b/backend/services/cse.go @@ -92,47 +92,58 @@ func (s *SearchService) SearchMedia(queries []string, enabledPlatforms map[strin results := make([]SearchResult, 0, 90) var lastErr error - baseQueries := limitQueries(queries, 3) - for _, base := range baseQueries { - base = strings.TrimSpace(base) - if base == "" { - continue - } - for _, source := range sources { - if len(enabledPlatforms) > 0 && !enabledPlatforms[strings.ToLower(source.name)] { + baseQueries := limitQueries(queries, 6) + primaryQueries := baseQueries[:minInt(len(baseQueries), 3)] + runSearchPass := func(bases []string, onlyMissing bool) { + for _, base := range bases { + base = strings.TrimSpace(base) + if base == "" { continue } - if sourceCounts[source.name] >= source.maxResults { - continue - } - for _, searchQuery := range source.build(base) { - if sourceCounts[source.name] >= source.maxResults { - break - } - items, err := s.search(searchQuery, source.categories, source.engine, source.name) - if err != nil { - lastErr = err - items, err = s.search(searchQuery, source.categories, "", source.name) - } - if err != nil { - lastErr = err + for _, source := range sources { + if len(enabledPlatforms) > 0 && !enabledPlatforms[strings.ToLower(source.name)] { continue } - for _, item := range items { - if item.Link == "" || seen[item.Link] || !source.accept(item) { - continue - } - seen[item.Link] = true - results = append(results, item) - sourceCounts[source.name]++ + if sourceCounts[source.name] >= source.maxResults { + continue + } + if onlyMissing && sourceCounts[source.name] > 0 { + continue + } + for _, searchQuery := range source.build(base) { if sourceCounts[source.name] >= source.maxResults { break } + items, err := s.search(searchQuery, source.categories, source.engine, source.name) + if err != nil { + lastErr = err + items, err = s.search(searchQuery, source.categories, "", source.name) + } + if err != nil { + lastErr = err + continue + } + for _, item := range items { + if item.Link == "" || seen[item.Link] || !source.accept(item) { + continue + } + seen[item.Link] = true + results = append(results, item) + sourceCounts[source.name]++ + if sourceCounts[source.name] >= source.maxResults { + break + } + } } } } } + runSearchPass(primaryQueries, false) + if len(baseQueries) > len(primaryQueries) { + runSearchPass(baseQueries[len(primaryQueries):], true) + } + if len(results) == 0 && lastErr != nil { return nil, lastErr }