package services import ( "sort" "strings" ) type GeminiBatchStats struct { CandidateCap int `json:"candidateCap"` Requested int `json:"requested"` Batches int `json:"batches"` Succeeded int `json:"succeeded"` Failed int `json:"failed"` RecommendedCount int `json:"recommendedCount"` Errors []string `json:"errors,omitempty"` } func RankSearchResults(query string, results []SearchResult) []SearchResult { queryTerms := strings.Fields(strings.ToLower(query)) positiveTerms := []string{ "b-roll", "b roll", "stock", "stock footage", "footage", "cinematic", "editorial", "establishing", "4k", "hd", "drone", "ambient", "scene", "urban", "cityscape", } negativeTerms := []string{ "shocking", "amazing", "crazy", "must watch", "reaction", "gossip", "celebrity", "thumbnail", "meme", "prank", "drama", "breaking", "viral", "tutorial", "how to", "review", "walkthrough", "course", "lesson", "podcast", "interview", "premiere pro", "after effects", "explained", "breakdown", "vlog", } type scoredResult struct { item SearchResult score int } scored := make([]scoredResult, 0, len(results)) for _, result := range results { score := 0 text := strings.ToLower(result.Title + " " + result.Snippet + " " + result.Source) for _, term := range queryTerms { if strings.Contains(text, term) { score += 3 } } for _, term := range positiveTerms { if strings.Contains(text, term) { score += 2 } } for _, term := range negativeTerms { if strings.Contains(text, term) { score -= 4 } } if result.ThumbnailURL != "" { score += 2 } if result.PreviewVideoURL != "" { score += 3 } switch result.Source { case "Google Video": score -= 1 case "Envato": score += 7 case "Artgrid": score += 7 } scored = append(scored, scoredResult{item: result, score: score}) } sort.SliceStable(scored, func(i, j int) bool { return scored[i].score > scored[j].score }) ranked := make([]SearchResult, 0, len(scored)) for _, item := range scored { ranked = append(ranked, item.item) } return ranked } func GeminiCandidateLimit(total int) int { switch { case total <= 12: return total case total <= 16: return 12 default: return 16 } } func EvaluateAllCandidatesWithGemini(service *GeminiService, query string, ranked []SearchResult) ([]AIRecommendation, GeminiBatchStats) { const chunkSize = 8 limit := GeminiCandidateLimit(len(ranked)) stats := GeminiBatchStats{ CandidateCap: limit, Requested: min(limit, len(ranked)), } merged := make([]AIRecommendation, 0, len(ranked)) seen := map[string]bool{} for start := 0; start < limit; start += chunkSize { end := start + chunkSize if end > limit { end = limit } batch := ranked[start:end] stats.Batches++ recommended, err := service.Recommend(query, batch) if err != nil { stats.Failed++ if len(stats.Errors) < 5 { stats.Errors = append(stats.Errors, err.Error()) } continue } stats.Succeeded++ for _, item := range recommended { if item.Link == "" || seen[item.Link] { continue } seen[item.Link] = true merged = append(merged, item) } } stats.RecommendedCount = len(merged) return merged, stats } func MergeRecommendations(recommended []AIRecommendation, ranked []SearchResult, limit int) []AIRecommendation { merged := make([]AIRecommendation, 0, min(limit, len(ranked))) seen := map[string]bool{} for _, item := range recommended { if item.Link == "" || seen[item.Link] { continue } seen[item.Link] = true merged = append(merged, item) } for _, item := range ranked { if len(merged) >= limit || item.Link == "" || seen[item.Link] { continue } seen[item.Link] = true merged = append(merged, AIRecommendation{ Title: item.Title, Link: item.Link, Snippet: item.Snippet, ThumbnailURL: item.ThumbnailURL, PreviewVideoURL: item.PreviewVideoURL, Source: item.Source, Reason: "Keyword-ranked result added without extra Gemini vision tokens.", Recommended: true, }) } return merged }