This commit is contained in:
+4
-158
@@ -11,7 +11,6 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -87,16 +86,6 @@ type searchDebugSummary struct {
|
||||
GeminiCandidateCap int `json:"geminiCandidateCap,omitempty"`
|
||||
}
|
||||
|
||||
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 RegisterRoutes(router *gin.Engine, app *App) {
|
||||
router.GET("/healthz", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
||||
@@ -329,10 +318,10 @@ func (a *App) searchMedia(c *gin.Context) {
|
||||
if len(queryVariants) > 0 {
|
||||
rankQuery = strings.Join(queryVariants[:min(len(queryVariants), 3)], " ")
|
||||
}
|
||||
scored := rankSearchResults(rankQuery, results)
|
||||
a.debug("search ranked summary", summarizeSearchResults(scored, time.Since(started), geminiCandidateLimit(len(scored)), ""))
|
||||
scored := services.RankSearchResults(rankQuery, results)
|
||||
a.debug("search ranked summary", summarizeSearchResults(scored, time.Since(started), services.GeminiCandidateLimit(len(scored)), ""))
|
||||
a.Hub.Broadcast("progress", gin.H{"type": "search", "status": "analyzing top candidate visuals with Gemini Vision", "progress": 75})
|
||||
recommended, geminiStats := evaluateAllCandidatesWithGemini(a.GeminiService, req.Query, scored)
|
||||
recommended, geminiStats := services.EvaluateAllCandidatesWithGemini(a.GeminiService, req.Query, scored)
|
||||
a.debug("search gemini evaluation", geminiStats)
|
||||
err = nil
|
||||
if len(recommended) == 0 {
|
||||
@@ -359,7 +348,7 @@ func (a *App) searchMedia(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
merged := mergeRecommendations(recommended, scored, 20)
|
||||
merged := services.MergeRecommendations(recommended, scored, 20)
|
||||
a.debug("search complete summary", summarizeRecommendationResults(merged, time.Since(started), ""))
|
||||
response := gin.H{"results": merged, "queries": queryVariants}
|
||||
a.Hub.Broadcast("progress", gin.H{"type": "search", "status": "search complete", "progress": 100})
|
||||
@@ -438,149 +427,6 @@ func selectedPlatformLabel(platforms map[string]bool) string {
|
||||
return strings.Join(labels, ", ")
|
||||
}
|
||||
|
||||
func evaluateAllCandidatesWithGemini(service *services.GeminiService, query string, ranked []services.SearchResult) ([]services.AIRecommendation, geminiBatchStats) {
|
||||
const chunkSize = 8
|
||||
limit := geminiCandidateLimit(len(ranked))
|
||||
stats := geminiBatchStats{
|
||||
CandidateCap: limit,
|
||||
Requested: min(limit, len(ranked)),
|
||||
}
|
||||
merged := make([]services.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 rankSearchResults(query string, results []services.SearchResult) []services.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 services.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([]services.SearchResult, 0, len(scored))
|
||||
for _, item := range scored {
|
||||
ranked = append(ranked, item.item)
|
||||
}
|
||||
return ranked
|
||||
}
|
||||
|
||||
func mergeRecommendations(recommended []services.AIRecommendation, ranked []services.SearchResult, limit int) []services.AIRecommendation {
|
||||
merged := make([]services.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, services.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
|
||||
}
|
||||
|
||||
func geminiCandidateLimit(total int) int {
|
||||
switch {
|
||||
case total <= 8:
|
||||
return total
|
||||
case total <= 16:
|
||||
return 12
|
||||
default:
|
||||
return 16
|
||||
}
|
||||
}
|
||||
|
||||
func summarizeSearchResults(results []services.SearchResult, duration time.Duration, geminiCap int, warning string) searchDebugSummary {
|
||||
bySource := map[string]int{}
|
||||
withPreview := 0
|
||||
|
||||
Reference in New Issue
Block a user