diff --git a/backend/services/cse.go b/backend/services/cse.go index fd05aa1..50be200 100644 --- a/backend/services/cse.go +++ b/backend/services/cse.go @@ -48,31 +48,34 @@ func (s *SearchService) SearchMedia(queries []string) ([]SearchResult, error) { } sources := []struct { - name string - categories string - engine string - siteFilter string - match func(string) bool + name string + categories string + engine string + queryBuilder func(string) string + match func(string) bool }{ { name: "Google Video", categories: "videos", engine: s.GoogleVideoEngine, - match: func(string) bool { return true }, + queryBuilder: func(query string) string { + return query + }, + match: func(string) bool { return true }, }, { - name: "Envato", - categories: "general", - engine: s.WebEngine, - siteFilter: "site:elements.envato.com OR site:envato.com OR site:videohive.net", - match: isEnvatoURL, + name: "Envato", + categories: "general", + engine: s.WebEngine, + queryBuilder: buildEnvatoQuery, + match: isEnvatoURL, }, { - name: "Artgrid", - categories: "general", - engine: s.WebEngine, - siteFilter: "site:artgrid.io", - match: func(link string) bool { return strings.Contains(strings.ToLower(link), "artgrid.io") }, + name: "Artgrid", + categories: "general", + engine: s.WebEngine, + queryBuilder: buildArtgridQuery, + match: func(link string) bool { return strings.Contains(strings.ToLower(link), "artgrid.io") }, }, } @@ -86,8 +89,8 @@ func (s *SearchService) SearchMedia(queries []string) ([]SearchResult, error) { } for _, source := range sources { searchQuery := query - if source.siteFilter != "" { - searchQuery = query + " " + source.siteFilter + if source.queryBuilder != nil { + searchQuery = source.queryBuilder(query) } items, err := s.search(searchQuery, source.categories, source.engine, source.name) @@ -112,6 +115,9 @@ func (s *SearchService) SearchMedia(queries []string) ([]SearchResult, error) { if source.match != nil && !source.match(item.Link) { continue } + if !isRenderableLink(item.Link, item.Source) { + continue + } seen[item.Link] = true results = append(results, item) } @@ -224,6 +230,34 @@ func isEnvatoURL(link string) bool { return strings.Contains(lower, "envato") || strings.Contains(lower, "videohive.net") } +func isRenderableLink(link, source string) bool { + parsed, err := url.Parse(link) + if err != nil { + return false + } + path := strings.Trim(parsed.Path, "/") + if path == "" { + return false + } + lower := strings.ToLower(link) + switch source { + case "Envato": + return strings.Contains(lower, "/item/") || strings.Contains(lower, "/stock-video/") || strings.Contains(lower, "/video-templates/") + case "Artgrid": + return strings.Contains(lower, "artgrid.io") && len(strings.Split(path, "/")) >= 2 + default: + return true + } +} + +func buildEnvatoQuery(query string) string { + return fmt.Sprintf(`%s ("stock video" OR footage OR "video template" OR cinematic) (site:elements.envato.com/stock-video OR site:elements.envato.com/video-templates OR site:videohive.net/item)`, query) +} + +func buildArtgridQuery(query string) string { + return fmt.Sprintf(`%s ("stock footage" OR "b-roll" OR cinematic OR editorial) (site:artgrid.io)`, query) +} + func deriveThumbnail(link string) string { if videoID := extractYouTubeID(link); videoID != "" { return "https://i.ytimg.com/vi/" + videoID + "/hqdefault.jpg" diff --git a/backend/services/gemini.go b/backend/services/gemini.go index 6171de9..e940fc8 100644 --- a/backend/services/gemini.go +++ b/backend/services/gemini.go @@ -43,6 +43,13 @@ func (g *GeminiService) ExpandQuery(query string) ([]string, error) { } body := map[string]any{ + "systemInstruction": map[string]any{ + "parts": []map[string]string{ + { + "text": "You are a JSON-only API. Output valid JSON only. Never add prose, labels, markdown, or explanations before or after the JSON.", + }, + }, + }, "contents": []map[string]any{ { "parts": []map[string]string{ @@ -85,6 +92,13 @@ User query: ` + query, jsonText, err := extractJSONObject(rawText) if err != nil { strictBody := map[string]any{ + "systemInstruction": map[string]any{ + "parts": []map[string]string{ + { + "text": "You are a strict JSON emitter. Output one valid JSON object only. Do not write any other text.", + }, + }, + }, "contents": []map[string]any{ { "parts": []map[string]string{ diff --git a/frontend/index.html b/frontend/index.html index 77a3927..97bcb0e 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -70,7 +70,7 @@