Improve source-specific search queries and preview layout
build-push / docker (push) Successful in 4m14s

This commit is contained in:
AI Assistant
2026-03-13 11:39:51 +09:00
parent c7261edc7d
commit 1fc06fb785
3 changed files with 71 additions and 23 deletions
+52 -18
View File
@@ -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"
+14
View File
@@ -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{
+5 -5
View File
@@ -70,7 +70,7 @@
</main>
<div id="previewModal" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/80 px-4">
<div class="w-full max-w-3xl rounded-3xl border border-white/10 bg-zinc-950 p-5 shadow-2xl">
<div class="w-full max-w-4xl rounded-3xl border border-white/10 bg-zinc-950 p-5 shadow-2xl">
<div class="flex items-start justify-between gap-4">
<div>
<p class="text-xs uppercase tracking-[0.3em] text-zinc-500">Download Preview</p>
@@ -78,12 +78,12 @@
</div>
<button id="closePreviewModal" class="rounded-full border border-white/10 px-3 py-2 text-xs uppercase tracking-[0.2em] text-zinc-300">Close</button>
</div>
<div class="mt-5 grid gap-5 md:grid-cols-[1.2fr_0.8fr]">
<div id="previewMediaFrame" class="flex min-h-[320px] items-center justify-center overflow-hidden rounded-3xl border border-white/10 bg-black/30 p-2">
<div class="mt-5 grid gap-5 xl:grid-cols-[minmax(0,1.15fr)_360px]">
<div id="previewMediaFrame" class="min-w-0 flex min-h-[320px] items-center justify-center overflow-hidden rounded-3xl border border-white/10 bg-black/30 p-2">
<video id="previewVideo" class="hidden max-h-[60vh] w-full bg-black object-contain" controls playsinline></video>
<img id="previewThumbnail" class="max-h-[60vh] w-full object-contain" alt="" />
</div>
<div class="space-y-4">
<div class="min-w-0 space-y-4">
<div class="rounded-2xl border border-white/10 bg-white/[0.03] p-4">
<div class="flex items-center justify-between text-sm text-zinc-400">
<span>Detected duration</span>
@@ -136,6 +136,6 @@
</a>
</template>
<script src="/app.js?v=20260313b" defer></script>
<script src="/app.js?v=20260313c" defer></script>
</body>
</html>