Switch search backend to SearXNG
build-push / docker (push) Has been cancelled

This commit is contained in:
AI Assistant
2026-03-13 10:10:13 +09:00
parent 6734887fc6
commit ee316de7ab
8 changed files with 466 additions and 199 deletions
+110 -20
View File
@@ -11,6 +11,7 @@ import (
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
"sync"
"time"
@@ -65,14 +66,14 @@ func (h *Hub) Remove(conn *websocket.Conn) {
}
type PreviewResponse struct {
Title string `json:"title"`
Thumbnail string `json:"thumbnail"`
Title string `json:"title"`
Thumbnail string `json:"thumbnail"`
PreviewStreamURL string `json:"previewStreamUrl"`
Duration string `json:"duration"`
DurationSeconds int `json:"durationSeconds"`
StartDefault string `json:"startDefault"`
EndDefault string `json:"endDefault"`
Qualities []map[string]any `json:"qualities"`
Duration string `json:"duration"`
DurationSeconds int `json:"durationSeconds"`
StartDefault string `json:"startDefault"`
EndDefault string `json:"endDefault"`
Qualities []map[string]any `json:"qualities"`
}
func RegisterRoutes(router *gin.Engine, app *App) {
@@ -141,11 +142,11 @@ func (a *App) uploadFile(c *gin.Context) {
func (a *App) startDownload(c *gin.Context) {
var req struct {
URL string `json:"url"`
Start string `json:"start"`
End string `json:"end"`
URL string `json:"url"`
Start string `json:"start"`
End string `json:"end"`
Quality string `json:"quality"`
Force bool `json:"force"`
Force bool `json:"force"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
@@ -192,7 +193,7 @@ func (a *App) previewDownload(c *gin.Context) {
return
}
cmd := exec.Command("python3", a.WorkerScript, "--mode", "probe", "--url", req.URL, "--output", filepath.Join(a.DownloadsDir, "probe.tmp"))
cmd := exec.Command("python3", a.WorkerScript, "--mode", "probe", "--url", req.URL)
output, err := cmd.CombinedOutput()
if err != nil {
c.JSON(http.StatusBadGateway, gin.H{"error": strings.TrimSpace(string(output))})
@@ -258,34 +259,53 @@ func (a *App) searchMedia(c *gin.Context) {
return
}
results, err := a.SearchService.SearchMedia(req.Query)
queryVariants, expandErr := a.GeminiService.ExpandQuery(req.Query)
if len(queryVariants) == 0 {
queryVariants = []string{req.Query}
}
results, err := a.SearchService.SearchMedia(queryVariants)
if err != nil {
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
return
}
if len(results) == 0 {
c.JSON(http.StatusOK, gin.H{"results": []services.AIRecommendation{}, "warning": "Vertex AI Search returned no renderable results. Check your website indexing fields and thumbnails."})
warning := "SearXNG returned no renderable results."
if expandErr != nil {
warning += " Query expansion fallback was used."
}
c.JSON(http.StatusOK, gin.H{"results": []services.AIRecommendation{}, "warning": warning})
return
}
recommended, err := a.GeminiService.Recommend(req.Query, results)
scored := rankSearchResults(req.Query, results)
shortlist := scored[:min(len(scored), 10)]
recommended, err := a.GeminiService.Recommend(req.Query, shortlist)
if err != nil {
fallback := make([]services.AIRecommendation, 0, min(4, len(results)))
for _, result := range results[:min(4, len(results))] {
fallback := make([]services.AIRecommendation, 0, min(20, len(scored)))
for _, result := range scored[:min(20, len(scored))] {
fallback = append(fallback, services.AIRecommendation{
Title: result.Title,
Link: result.Link,
ThumbnailURL: result.ThumbnailURL,
Source: result.Source,
Reason: "Gemini recommendation failed, showing raw search result.",
Reason: "Keyword-ranked result added without extra Gemini vision tokens.",
Recommended: true,
})
}
c.JSON(http.StatusOK, gin.H{"results": fallback, "warning": err.Error()})
warning := err.Error()
if expandErr != nil {
warning = warning + " Query expansion fallback was used."
}
c.JSON(http.StatusOK, gin.H{"results": fallback, "warning": warning, "queries": queryVariants})
return
}
c.JSON(http.StatusOK, gin.H{"results": recommended})
response := gin.H{"results": mergeRecommendations(recommended, scored, 20), "queries": queryVariants}
if expandErr != nil {
response["warning"] = "Gemini query expansion failed, using the original query only."
}
c.JSON(http.StatusOK, response)
}
func normalizeFilename(name string) string {
@@ -321,6 +341,76 @@ func min(a, b int) int {
return b
}
func rankSearchResults(query string, results []services.SearchResult) []services.SearchResult {
queryTerms := strings.Fields(strings.ToLower(query))
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
}
}
if result.ThumbnailURL != "" {
score += 2
}
switch result.Source {
case "Google Video":
score += 3
case "Envato":
score += 2
case "Artgrid":
score += 2
}
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,
ThumbnailURL: item.ThumbnailURL,
Source: item.Source,
Reason: "Keyword-ranked result added without extra Gemini vision tokens.",
Recommended: true,
})
}
return merged
}
func EnsurePaths(downloadsDir, workerScript string) error {
if err := os.MkdirAll(downloadsDir, 0o755); err != nil {
return err