This commit is contained in:
@@ -259,13 +259,16 @@ func (a *App) searchMedia(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.Hub.Broadcast("progress", gin.H{"type": "search", "status": "expanding query with Gemini", "progress": 10})
|
||||||
queryVariants, expandErr := a.GeminiService.ExpandQuery(req.Query)
|
queryVariants, expandErr := a.GeminiService.ExpandQuery(req.Query)
|
||||||
if len(queryVariants) == 0 {
|
if len(queryVariants) == 0 {
|
||||||
queryVariants = []string{req.Query}
|
queryVariants = []string{req.Query}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.Hub.Broadcast("progress", gin.H{"type": "search", "status": "searching Google Video, Envato, and Artgrid", "progress": 35})
|
||||||
results, err := a.SearchService.SearchMedia(queryVariants)
|
results, err := a.SearchService.SearchMedia(queryVariants)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
a.Hub.Broadcast("progress", gin.H{"type": "search", "status": "search failed", "progress": 100, "message": err.Error()})
|
||||||
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
|
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -274,12 +277,15 @@ func (a *App) searchMedia(c *gin.Context) {
|
|||||||
if expandErr != nil {
|
if expandErr != nil {
|
||||||
warning += " Query expansion failed: " + expandErr.Error()
|
warning += " Query expansion failed: " + expandErr.Error()
|
||||||
}
|
}
|
||||||
|
a.Hub.Broadcast("progress", gin.H{"type": "search", "status": "no renderable search results", "progress": 100, "message": warning})
|
||||||
c.JSON(http.StatusOK, gin.H{"results": []services.AIRecommendation{}, "warning": warning})
|
c.JSON(http.StatusOK, gin.H{"results": []services.AIRecommendation{}, "warning": warning})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.Hub.Broadcast("progress", gin.H{"type": "search", "status": "ranking thumbnail candidates", "progress": 55})
|
||||||
scored := rankSearchResults(req.Query, results)
|
scored := rankSearchResults(req.Query, results)
|
||||||
shortlist := scored[:min(len(scored), 10)]
|
shortlist := scored[:min(len(scored), 10)]
|
||||||
|
a.Hub.Broadcast("progress", gin.H{"type": "search", "status": "analyzing shortlisted thumbnails with Gemini Vision", "progress": 75})
|
||||||
recommended, err := a.GeminiService.Recommend(req.Query, shortlist)
|
recommended, err := a.GeminiService.Recommend(req.Query, shortlist)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fallback := make([]services.AIRecommendation, 0, min(20, len(scored)))
|
fallback := make([]services.AIRecommendation, 0, min(20, len(scored)))
|
||||||
@@ -297,6 +303,7 @@ func (a *App) searchMedia(c *gin.Context) {
|
|||||||
if expandErr != nil {
|
if expandErr != nil {
|
||||||
warning = warning + " Query expansion failed: " + expandErr.Error()
|
warning = warning + " Query expansion failed: " + expandErr.Error()
|
||||||
}
|
}
|
||||||
|
a.Hub.Broadcast("progress", gin.H{"type": "search", "status": "Gemini Vision fallback to ranked results", "progress": 90, "message": warning})
|
||||||
c.JSON(http.StatusOK, gin.H{"results": fallback, "warning": warning, "queries": queryVariants})
|
c.JSON(http.StatusOK, gin.H{"results": fallback, "warning": warning, "queries": queryVariants})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -305,6 +312,7 @@ func (a *App) searchMedia(c *gin.Context) {
|
|||||||
if expandErr != nil {
|
if expandErr != nil {
|
||||||
response["warning"] = "Gemini query expansion failed: " + expandErr.Error() + ". Using the original query only."
|
response["warning"] = "Gemini query expansion failed: " + expandErr.Error() + ". Using the original query only."
|
||||||
}
|
}
|
||||||
|
a.Hub.Broadcast("progress", gin.H{"type": "search", "status": "search complete", "progress": 100})
|
||||||
c.JSON(http.StatusOK, response)
|
c.JSON(http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-2
@@ -13,6 +13,7 @@ const downloadUrl = document.getElementById("downloadUrl");
|
|||||||
const downloadResult = document.getElementById("downloadResult");
|
const downloadResult = document.getElementById("downloadResult");
|
||||||
const cardTemplate = document.getElementById("searchCardTemplate");
|
const cardTemplate = document.getElementById("searchCardTemplate");
|
||||||
const previewModal = document.getElementById("previewModal");
|
const previewModal = document.getElementById("previewModal");
|
||||||
|
const previewMediaFrame = document.getElementById("previewMediaFrame");
|
||||||
const previewTitle = document.getElementById("previewTitle");
|
const previewTitle = document.getElementById("previewTitle");
|
||||||
const previewVideo = document.getElementById("previewVideo");
|
const previewVideo = document.getElementById("previewVideo");
|
||||||
const previewThumbnail = document.getElementById("previewThumbnail");
|
const previewThumbnail = document.getElementById("previewThumbnail");
|
||||||
@@ -80,7 +81,8 @@ function connectWS() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = payload.data;
|
const data = payload.data;
|
||||||
setStatus(`${data.type || "task"}: ${data.status}`, Number(data.progress ?? 0));
|
const label = data.status || `${data.type || "task"} in progress`;
|
||||||
|
setStatus(label, Number(data.progress ?? 0));
|
||||||
if (data.type === "upload" && data.status === "completed") {
|
if (data.type === "upload" && data.status === "completed") {
|
||||||
uploadResult.textContent = `${data.filename} saved successfully`;
|
uploadResult.textContent = `${data.filename} saved successfully`;
|
||||||
}
|
}
|
||||||
@@ -128,7 +130,7 @@ function renderResults(results) {
|
|||||||
|
|
||||||
searchForm.addEventListener("submit", async (event) => {
|
searchForm.addEventListener("submit", async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setStatus("searching", 20);
|
setStatus("preparing search", 5);
|
||||||
searchWarning.classList.add("hidden");
|
searchWarning.classList.add("hidden");
|
||||||
try {
|
try {
|
||||||
const data = await api("/api/search", {
|
const data = await api("/api/search", {
|
||||||
@@ -169,6 +171,7 @@ function openPreviewModal(preview) {
|
|||||||
previewVideo.pause();
|
previewVideo.pause();
|
||||||
previewVideo.removeAttribute("src");
|
previewVideo.removeAttribute("src");
|
||||||
previewVideo.load();
|
previewVideo.load();
|
||||||
|
previewMediaFrame.style.aspectRatio = "";
|
||||||
if (preview.previewStreamUrl) {
|
if (preview.previewStreamUrl) {
|
||||||
previewVideo.src = preview.previewStreamUrl;
|
previewVideo.src = preview.previewStreamUrl;
|
||||||
previewVideo.classList.remove("hidden");
|
previewVideo.classList.remove("hidden");
|
||||||
@@ -199,6 +202,7 @@ function closeModal() {
|
|||||||
previewVideo.pause();
|
previewVideo.pause();
|
||||||
previewVideo.removeAttribute("src");
|
previewVideo.removeAttribute("src");
|
||||||
previewVideo.load();
|
previewVideo.load();
|
||||||
|
previewMediaFrame.style.aspectRatio = "";
|
||||||
previewModal.classList.add("hidden");
|
previewModal.classList.add("hidden");
|
||||||
previewModal.classList.remove("flex");
|
previewModal.classList.remove("flex");
|
||||||
startRange.value = "0";
|
startRange.value = "0";
|
||||||
@@ -298,6 +302,16 @@ setEndFromPreview.addEventListener("click", () => {
|
|||||||
endRange.value = String(Math.floor(previewVideo.currentTime || 0));
|
endRange.value = String(Math.floor(previewVideo.currentTime || 0));
|
||||||
syncRanges();
|
syncRanges();
|
||||||
});
|
});
|
||||||
|
previewVideo.addEventListener("loadedmetadata", () => {
|
||||||
|
if (previewVideo.videoWidth > 0 && previewVideo.videoHeight > 0) {
|
||||||
|
previewMediaFrame.style.aspectRatio = `${previewVideo.videoWidth} / ${previewVideo.videoHeight}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
previewThumbnail.addEventListener("load", () => {
|
||||||
|
if (!previewVideo.src && previewThumbnail.naturalWidth > 0 && previewThumbnail.naturalHeight > 0) {
|
||||||
|
previewMediaFrame.style.aspectRatio = `${previewThumbnail.naturalWidth} / ${previewThumbnail.naturalHeight}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
connectWS();
|
connectWS();
|
||||||
setStatus("idle", 0);
|
setStatus("idle", 0);
|
||||||
|
|||||||
+3
-3
@@ -79,9 +79,9 @@
|
|||||||
<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>
|
<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>
|
||||||
<div class="mt-5 grid gap-5 md:grid-cols-[1.2fr_0.8fr]">
|
<div class="mt-5 grid gap-5 md:grid-cols-[1.2fr_0.8fr]">
|
||||||
<div class="overflow-hidden rounded-3xl border border-white/10 bg-black/30">
|
<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">
|
||||||
<video id="previewVideo" class="hidden aspect-video h-full w-full bg-black object-cover" controls playsinline></video>
|
<video id="previewVideo" class="hidden max-h-[60vh] w-full bg-black object-contain" controls playsinline></video>
|
||||||
<img id="previewThumbnail" class="aspect-video h-full w-full object-cover" alt="" />
|
<img id="previewThumbnail" class="max-h-[60vh] w-full object-contain" alt="" />
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="rounded-2xl border border-white/10 bg-white/[0.03] p-4">
|
<div class="rounded-2xl border border-white/10 bg-white/[0.03] p-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user