Expand search breadth and modal action metadata
build-push / docker (push) Has been cancelled

This commit is contained in:
AI Assistant
2026-03-16 17:07:53 +09:00
parent 794aec1496
commit 19425c9503
8 changed files with 60 additions and 28 deletions
+6 -3
View File
@@ -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
View File
@@ -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--
+4 -4
View File
@@ -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))
}
}
+3
View File
@@ -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 {
+8 -4
View File
@@ -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) {
+2 -2
View File
@@ -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 {
+3 -3
View File
@@ -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"]
}