Add in-app result viewer and expand Gemini review
build-push / docker (push) Successful in 4m52s

This commit is contained in:
AI Assistant
2026-03-16 10:12:12 +09:00
parent 9637b761bd
commit b43886e950
6 changed files with 414 additions and 306 deletions
+51 -18
View File
@@ -3,6 +3,7 @@ package services
import (
"sort"
"strings"
"sync"
)
type GeminiBatchStats struct {
@@ -80,42 +81,63 @@ func RankSearchResults(query string, results []SearchResult) []SearchResult {
}
func GeminiCandidateLimit(total int) int {
switch {
case total <= 12:
return total
case total <= 16:
return 12
default:
return 16
}
return total
}
func EvaluateAllCandidatesWithGemini(service *GeminiService, query string, ranked []SearchResult) ([]AIRecommendation, GeminiBatchStats) {
const chunkSize = 8
const maxConcurrentBatches = 2
limit := GeminiCandidateLimit(len(ranked))
stats := GeminiBatchStats{
CandidateCap: limit,
Requested: min(limit, len(ranked)),
}
merged := make([]AIRecommendation, 0, len(ranked))
seen := map[string]bool{}
type batchResult struct {
index int
recommendations []AIRecommendation
err error
}
batches := make([][]SearchResult, 0, (limit+chunkSize-1)/chunkSize)
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 {
batches = append(batches, ranked[start:end])
}
stats.Batches = len(batches)
results := make([]batchResult, len(batches))
var wg sync.WaitGroup
sem := make(chan struct{}, maxConcurrentBatches)
for idx, batch := range batches {
wg.Add(1)
go func(batchIndex int, candidates []SearchResult) {
defer wg.Done()
sem <- struct{}{}
defer func() { <-sem }()
recommended, err := service.Recommend(query, candidates)
results[batchIndex] = batchResult{
index: batchIndex,
recommendations: recommended,
err: err,
}
}(idx, batch)
}
wg.Wait()
merged := make([]AIRecommendation, 0, len(ranked))
seen := map[string]bool{}
for _, batch := range results {
if batch.err != nil {
stats.Failed++
if len(stats.Errors) < 5 {
stats.Errors = append(stats.Errors, err.Error())
stats.Errors = append(stats.Errors, batch.err.Error())
}
continue
}
stats.Succeeded++
for _, item := range recommended {
for _, item := range batch.recommendations {
if item.Link == "" || seen[item.Link] {
continue
}
@@ -132,6 +154,9 @@ func MergeRecommendations(recommended []AIRecommendation, ranked []SearchResult,
seen := map[string]bool{}
for _, item := range recommended {
if !item.Recommended {
continue
}
if item.Link == "" || seen[item.Link] {
continue
}
@@ -139,6 +164,14 @@ func MergeRecommendations(recommended []AIRecommendation, ranked []SearchResult,
merged = append(merged, item)
}
for _, item := range recommended {
if item.Recommended || item.Link == "" || seen[item.Link] || len(merged) >= limit {
continue
}
seen[item.Link] = true
merged = append(merged, item)
}
for _, item := range ranked {
if len(merged) >= limit || item.Link == "" || seen[item.Link] {
continue
@@ -151,8 +184,8 @@ func MergeRecommendations(recommended []AIRecommendation, ranked []SearchResult,
ThumbnailURL: item.ThumbnailURL,
PreviewVideoURL: item.PreviewVideoURL,
Source: item.Source,
Reason: "Keyword-ranked result added without extra Gemini vision tokens.",
Recommended: true,
Reason: "Gemini Vision 응답이 부족해 키워드 기준으로 보강된 결과입니다.",
Recommended: false,
})
}
return merged