This commit is contained in:
@@ -255,6 +255,19 @@
|
||||
- backend debug broadcasts
|
||||
|
||||
## Recent Change Log
|
||||
- Date: `2026-03-16`
|
||||
- What changed:
|
||||
- Expanded search breadth moderately by increasing base query count, collector query budgets, per-source caps, enrichment scope, and final visible result target while keeping Gemini review cap at `16`.
|
||||
- Reworked recommendation action metadata so Google Video now advertises `Direct Download` as the primary CTA, while Envato and Artgrid advertise `Open Source`.
|
||||
- Changed default modal media priority so Artgrid now prefers preview video ahead of thumbnail when both are available, and Google Video now defaults to a webpage-like thumbnail mode instead of embed-first.
|
||||
- Added visible-count style debug summary fields to support checking whether the widened search budget actually increases user-facing choice.
|
||||
- Why it changed:
|
||||
- The UI was much healthier, but the remaining request from the user was to widen the pool of selectable results without undoing the recent quality gains, and to align modal CTA semantics with what each source can actually do.
|
||||
- How it was verified:
|
||||
- `go test ./...`
|
||||
- What is still risky or incomplete:
|
||||
- The widened search budget may increase latency on worse SearXNG days, so the frontend/UI half of this batch still needs to land before the full user-facing behavior is validated.
|
||||
|
||||
- Date: `2026-03-16`
|
||||
- What changed:
|
||||
- Rewired the result modal to consume backend media metadata instead of hard-coded source branches.
|
||||
|
||||
@@ -82,6 +82,7 @@ type PreviewResponse struct {
|
||||
|
||||
type searchDebugSummary struct {
|
||||
Total int `json:"total"`
|
||||
VisibleCount int `json:"visibleCount,omitempty"`
|
||||
BySource map[string]int `json:"bySource"`
|
||||
WithPreview int `json:"withPreview"`
|
||||
WithThumbnail int `json:"withThumbnail"`
|
||||
@@ -477,16 +478,16 @@ func (a *App) searchMedia(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
merged := services.MergeRecommendations(recommended, scored, 20)
|
||||
merged := services.MergeRecommendations(recommended, scored, 16)
|
||||
if geminiErr != nil {
|
||||
merged = services.BackfillRecommendations(
|
||||
merged,
|
||||
scored,
|
||||
12,
|
||||
16,
|
||||
"Gemini 배치 일부가 실패해 미리보기 가능한 상위 후보를 보강했습니다.",
|
||||
)
|
||||
}
|
||||
merged = services.RandomizeTopRecommendations(merged, 8)
|
||||
merged = services.RandomizeTopRecommendations(merged, 6)
|
||||
for idx := range merged {
|
||||
merged[idx] = services.DecorateRecommendationMedia(merged[idx])
|
||||
}
|
||||
@@ -665,6 +666,7 @@ func summarizeSearchResults(results []services.SearchResult, duration time.Durat
|
||||
}
|
||||
return searchDebugSummary{
|
||||
Total: len(results),
|
||||
VisibleCount: len(results),
|
||||
BySource: bySource,
|
||||
WithPreview: withPreview,
|
||||
WithThumbnail: withThumbnail,
|
||||
@@ -717,6 +719,7 @@ func summarizeRecommendationResults(results []services.AIRecommendation, duratio
|
||||
}
|
||||
return searchDebugSummary{
|
||||
Total: len(results),
|
||||
VisibleCount: len(results),
|
||||
BySource: bySource,
|
||||
WithPreview: withPreview,
|
||||
WithThumbnail: withThumbnail,
|
||||
|
||||
+21
-12
@@ -88,7 +88,7 @@ func (s *SearchService) SearchMediaWithDeadline(queries []string, enabledPlatfor
|
||||
results := make([]SearchResult, 0, 90)
|
||||
var lastErr error
|
||||
|
||||
baseQueries := limitQueries(queries, 6)
|
||||
baseQueries := limitQueries(queries, 8)
|
||||
shuffleStrings(baseQueries)
|
||||
primaryQueries := baseQueries[:minInt(len(baseQueries), 3)]
|
||||
runSearchPass := func(bases []string, onlyMissing bool) {
|
||||
@@ -190,7 +190,7 @@ func (s *SearchService) EnrichResults(results []SearchResult) []SearchResult {
|
||||
}
|
||||
|
||||
func (s *SearchService) EnrichResultsWithDeadline(results []SearchResult, deadline time.Time) []SearchResult {
|
||||
limit := minInt(len(results), 14)
|
||||
limit := minInt(len(results), 18)
|
||||
if limit == 0 {
|
||||
return results
|
||||
}
|
||||
@@ -722,13 +722,10 @@ func defaultMediaMode(source, link, previewURL, thumbnailURL string) (string, st
|
||||
embedURL := buildEmbedURL(source, link)
|
||||
switch source {
|
||||
case "Google Video":
|
||||
if embedURL != "" {
|
||||
return "embed", embedURL, ""
|
||||
}
|
||||
if hasUsableThumbnail(thumbnailURL) {
|
||||
return "thumbnail", "", "missing_google_embed"
|
||||
return "thumbnail", embedURL, "webpage_like_preview_preferred"
|
||||
}
|
||||
return "none", "", "missing_google_embed"
|
||||
return "none", embedURL, "webpage_like_preview_preferred"
|
||||
case "Envato":
|
||||
if strings.TrimSpace(previewURL) != "" {
|
||||
return "preview_video", embedURL, "provider_embed_blocked"
|
||||
@@ -741,12 +738,12 @@ func defaultMediaMode(source, link, previewURL, thumbnailURL string) (string, st
|
||||
}
|
||||
return "none", "", "provider_embed_blocked"
|
||||
case "Artgrid":
|
||||
if hasUsableThumbnail(thumbnailURL) {
|
||||
return "thumbnail", embedURL, "provider_preview_unavailable"
|
||||
}
|
||||
if strings.TrimSpace(previewURL) != "" {
|
||||
return "preview_video", embedURL, "provider_preview_unavailable"
|
||||
}
|
||||
if hasUsableThumbnail(thumbnailURL) {
|
||||
return "thumbnail", embedURL, "provider_preview_unavailable"
|
||||
}
|
||||
if embedURL != "" {
|
||||
return "embed", embedURL, ""
|
||||
}
|
||||
@@ -774,6 +771,18 @@ func DecorateRecommendationMedia(item AIRecommendation) AIRecommendation {
|
||||
if item.MediaMode == "thumbnail" && !hasUsableThumbnail(item.ThumbnailURL) && strings.TrimSpace(item.PreviewVideoURL) != "" {
|
||||
item.MediaMode = "preview_video"
|
||||
}
|
||||
switch item.Source {
|
||||
case "Google Video":
|
||||
item.ActionType = "download"
|
||||
item.ActionLabel = "Direct Download"
|
||||
item.SecondaryActionLabel = "Open Source"
|
||||
case "Envato", "Artgrid":
|
||||
item.ActionType = "open_source"
|
||||
item.ActionLabel = "Open Source"
|
||||
default:
|
||||
item.ActionType = "open_source"
|
||||
item.ActionLabel = "Open Source"
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
@@ -1380,9 +1389,9 @@ func limitCollectorQueries(collector string, queries []string, onlyMissing bool)
|
||||
limit := 2
|
||||
switch collector {
|
||||
case "Envato", "Artgrid":
|
||||
limit = 3
|
||||
limit = 4
|
||||
case "Google Video":
|
||||
limit = 2
|
||||
limit = 3
|
||||
}
|
||||
if onlyMissing {
|
||||
limit--
|
||||
|
||||
@@ -144,13 +144,13 @@ func TestLimitCollectorQueriesUsesSmallerBudgetForMissingPass(t *testing.T) {
|
||||
queries := []string{"a", "b", "c", "d"}
|
||||
|
||||
got := limitCollectorQueries("Artgrid", queries, true)
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("expected 2 queries for missing-pass Artgrid collector, got %d", len(got))
|
||||
if len(got) != 3 {
|
||||
t.Fatalf("expected 3 queries for missing-pass Artgrid collector, got %d", len(got))
|
||||
}
|
||||
|
||||
got = limitCollectorQueries("Google Video", queries, false)
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("expected 2 queries for Google Video collector, got %d", len(got))
|
||||
if len(got) != 3 {
|
||||
t.Fatalf("expected 3 queries for Google Video collector, got %d", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,9 @@ type AIRecommendation struct {
|
||||
MediaMode string `json:"mediaMode,omitempty"`
|
||||
EmbedURL string `json:"embedUrl,omitempty"`
|
||||
PreviewBlockedReason string `json:"previewBlockedReason,omitempty"`
|
||||
ActionLabel string `json:"actionLabel,omitempty"`
|
||||
ActionType string `json:"actionType,omitempty"`
|
||||
SecondaryActionLabel string `json:"secondaryActionLabel,omitempty"`
|
||||
}
|
||||
|
||||
type QueryExpansion struct {
|
||||
|
||||
@@ -118,15 +118,19 @@ func TestGeminiExpansionCacheRoundTrip(t *testing.T) {
|
||||
|
||||
func TestDecorateRecommendationMediaUsesEmbedForGoogleVideo(t *testing.T) {
|
||||
item := DecorateRecommendationMedia(AIRecommendation{
|
||||
Source: "Google Video",
|
||||
Link: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
||||
Source: "Google Video",
|
||||
Link: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
||||
ThumbnailURL: "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg",
|
||||
})
|
||||
if item.MediaMode != "embed" {
|
||||
t.Fatalf("expected embed media mode, got %q", item.MediaMode)
|
||||
if item.MediaMode != "thumbnail" {
|
||||
t.Fatalf("expected thumbnail media mode, got %q", item.MediaMode)
|
||||
}
|
||||
if item.EmbedURL == "" || !strings.Contains(item.EmbedURL, "youtube-nocookie.com/embed/") {
|
||||
t.Fatalf("unexpected embed url: %q", item.EmbedURL)
|
||||
}
|
||||
if item.ActionType != "download" || item.ActionLabel != "Direct Download" {
|
||||
t.Fatalf("unexpected Google Video actions: %#v", item)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRankSearchResultsPrefersUsableVisuals(t *testing.T) {
|
||||
|
||||
@@ -427,9 +427,9 @@ func MergeRecommendations(recommended []AIRecommendation, ranked []SearchResult,
|
||||
merged = append(merged, DecorateRecommendationMedia(item))
|
||||
}
|
||||
|
||||
if len(merged) < min(12, limit) {
|
||||
if len(merged) < min(16, limit) {
|
||||
for _, item := range ranked {
|
||||
if len(merged) >= min(12, limit) || item.Link == "" || seen[item.Link] {
|
||||
if len(merged) >= min(16, limit) || item.Link == "" || seen[item.Link] {
|
||||
continue
|
||||
}
|
||||
if fillerCount >= maxFiller {
|
||||
|
||||
@@ -15,7 +15,7 @@ type searchCollector interface {
|
||||
type envatoCollector struct{}
|
||||
|
||||
func (envatoCollector) Name() string { return "Envato" }
|
||||
func (envatoCollector) MaxResults() int { return 10 }
|
||||
func (envatoCollector) MaxResults() int { return 12 }
|
||||
func (envatoCollector) Enabled(enabledPlatforms map[string]bool) bool {
|
||||
return len(enabledPlatforms) == 0 || enabledPlatforms["envato"]
|
||||
}
|
||||
@@ -31,7 +31,7 @@ func (envatoCollector) Enrich(searcher *SearchService, result SearchResult) Sear
|
||||
type artgridCollector struct{}
|
||||
|
||||
func (artgridCollector) Name() string { return "Artgrid" }
|
||||
func (artgridCollector) MaxResults() int { return 10 }
|
||||
func (artgridCollector) MaxResults() int { return 12 }
|
||||
func (artgridCollector) Enabled(enabledPlatforms map[string]bool) bool {
|
||||
return len(enabledPlatforms) == 0 || enabledPlatforms["artgrid"]
|
||||
}
|
||||
@@ -47,7 +47,7 @@ func (artgridCollector) Enrich(searcher *SearchService, result SearchResult) Sea
|
||||
type googleVideoCollector struct{}
|
||||
|
||||
func (googleVideoCollector) Name() string { return "Google Video" }
|
||||
func (googleVideoCollector) MaxResults() int { return 6 }
|
||||
func (googleVideoCollector) MaxResults() int { return 8 }
|
||||
func (googleVideoCollector) Enabled(enabledPlatforms map[string]bool) bool {
|
||||
return len(enabledPlatforms) == 0 || enabledPlatforms["google video"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user