From 33d9a4190c9214eca8b38f54d78f70a9a8dfa98f Mon Sep 17 00:00:00 2001 From: v0 Date: Thu, 5 Feb 2026 12:44:53 +0000 Subject: [PATCH 01/21] fix: update environment variables for Upstash Search Align code with correct Upstash Search token and URL variables. --- app/actions/search.ts | 5 ++++- app/api/upload/index-image.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/actions/search.ts b/app/actions/search.ts index fc7d478..ff7b48d 100644 --- a/app/actions/search.ts +++ b/app/actions/search.ts @@ -5,7 +5,10 @@ import { Search } from "@upstash/search"; import type { PutBlobResult } from "@vercel/blob"; -const upstash = Search.fromEnv(); +const upstash = new Search({ + url: process.env.UPSTASH_SEARCH_URL!, + token: process.env.UPSTASH_SEARCH_TOKEN!, +}); const index = upstash.index("images"); type SearchResponse = diff --git a/app/api/upload/index-image.ts b/app/api/upload/index-image.ts index 2e18531..1965985 100644 --- a/app/api/upload/index-image.ts +++ b/app/api/upload/index-image.ts @@ -4,7 +4,10 @@ import { Search } from "@upstash/search"; import type { PutBlobResult } from "@vercel/blob"; import { FatalError, getStepMetadata, RetryableError } from "workflow"; -const upstash = Search.fromEnv(); +const upstash = new Search({ + url: process.env.UPSTASH_SEARCH_URL!, + token: process.env.UPSTASH_SEARCH_TOKEN!, +}); export const indexImage = async (blob: PutBlobResult, text: string) => { "use step"; From ffb097dc38719b61d8818d860a9bdcea6ac39fdb Mon Sep 17 00:00:00 2001 From: v0 Date: Thu, 5 Feb 2026 13:02:09 +0000 Subject: [PATCH 02/21] fix: switch to Upstash Vector API Update code to use @upstash/vector instead of @upstash/search --- app/actions/search.ts | 18 +++++++++++------- app/api/upload/index-image.ts | 15 +++++++-------- package.json | 1 + pnpm-lock.yaml | 3 +++ 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/app/actions/search.ts b/app/actions/search.ts index ff7b48d..eace519 100644 --- a/app/actions/search.ts +++ b/app/actions/search.ts @@ -2,14 +2,13 @@ "use server"; -import { Search } from "@upstash/search"; +import { Index } from "@upstash/vector"; import type { PutBlobResult } from "@vercel/blob"; -const upstash = new Search({ - url: process.env.UPSTASH_SEARCH_URL!, - token: process.env.UPSTASH_SEARCH_TOKEN!, +const index = new Index({ + url: process.env.UPSTASH_VECTOR_REST_URL!, + token: process.env.UPSTASH_VECTOR_REST_TOKEN!, }); -const index = upstash.index("images"); type SearchResponse = | { @@ -31,11 +30,16 @@ export const search = async ( try { console.log("Searching index for query:", query); - const results = await index.search({ query }); + // Upstash Vector with embedding model handles text-to-vector conversion + const results = await index.query({ + data: query, + topK: 20, + includeMetadata: true, + }); console.log("Results:", results); const data = results - .sort((a, b) => b.score - a.score) + .sort((a, b) => (b.score ?? 0) - (a.score ?? 0)) .map((result) => result.metadata) .filter(Boolean) as unknown as PutBlobResult[]; diff --git a/app/api/upload/index-image.ts b/app/api/upload/index-image.ts index 1965985..528f1c1 100644 --- a/app/api/upload/index-image.ts +++ b/app/api/upload/index-image.ts @@ -1,12 +1,12 @@ /** biome-ignore-all lint/suspicious/noConsole: "Handy for debugging" */ -import { Search } from "@upstash/search"; +import { Index } from "@upstash/vector"; import type { PutBlobResult } from "@vercel/blob"; import { FatalError, getStepMetadata, RetryableError } from "workflow"; -const upstash = new Search({ - url: process.env.UPSTASH_SEARCH_URL!, - token: process.env.UPSTASH_SEARCH_TOKEN!, +const index = new Index({ + url: process.env.UPSTASH_VECTOR_REST_URL!, + token: process.env.UPSTASH_VECTOR_REST_TOKEN!, }); export const indexImage = async (blob: PutBlobResult, text: string) => { @@ -20,12 +20,11 @@ export const indexImage = async (blob: PutBlobResult, text: string) => { ); try { - const index = upstash.index("images"); - - // Store blob metadata in Upstash along with the description + // Store blob metadata in Upstash Vector along with the description + // The index will automatically generate embeddings from the text const result = await index.upsert({ id: blob.pathname, - content: { text }, + data: text, metadata: { ...blob }, }); diff --git a/package.json b/package.json index 36ef0b1..f2b90c8 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "@hookform/resolvers": "^5.2.2", "@upstash/search": "^0.1.5", + "@upstash/vector": "^1.2.2", "@vercel/analytics": "^1.5.0", "@vercel/blob": "^2.0.0", "ai": "^5.0.77", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9478fb0..ea89f40 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@upstash/search': specifier: ^0.1.5 version: 0.1.5 + '@upstash/vector': + specifier: ^1.2.2 + version: 1.2.2 '@vercel/analytics': specifier: ^1.5.0 version: 1.5.0(next@16.0.10(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0) From 486a6fe22380e12acc688d9e5406a58ca20519b2 Mon Sep 17 00:00:00 2001 From: v0 Date: Thu, 5 Feb 2026 13:04:48 +0000 Subject: [PATCH 03/21] revert to Upstash Search integration --- app/actions/search.ts | 18 +++++++----------- app/api/upload/index-image.ts | 15 ++++++++------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/app/actions/search.ts b/app/actions/search.ts index eace519..ff7b48d 100644 --- a/app/actions/search.ts +++ b/app/actions/search.ts @@ -2,13 +2,14 @@ "use server"; -import { Index } from "@upstash/vector"; +import { Search } from "@upstash/search"; import type { PutBlobResult } from "@vercel/blob"; -const index = new Index({ - url: process.env.UPSTASH_VECTOR_REST_URL!, - token: process.env.UPSTASH_VECTOR_REST_TOKEN!, +const upstash = new Search({ + url: process.env.UPSTASH_SEARCH_URL!, + token: process.env.UPSTASH_SEARCH_TOKEN!, }); +const index = upstash.index("images"); type SearchResponse = | { @@ -30,16 +31,11 @@ export const search = async ( try { console.log("Searching index for query:", query); - // Upstash Vector with embedding model handles text-to-vector conversion - const results = await index.query({ - data: query, - topK: 20, - includeMetadata: true, - }); + const results = await index.search({ query }); console.log("Results:", results); const data = results - .sort((a, b) => (b.score ?? 0) - (a.score ?? 0)) + .sort((a, b) => b.score - a.score) .map((result) => result.metadata) .filter(Boolean) as unknown as PutBlobResult[]; diff --git a/app/api/upload/index-image.ts b/app/api/upload/index-image.ts index 528f1c1..1965985 100644 --- a/app/api/upload/index-image.ts +++ b/app/api/upload/index-image.ts @@ -1,12 +1,12 @@ /** biome-ignore-all lint/suspicious/noConsole: "Handy for debugging" */ -import { Index } from "@upstash/vector"; +import { Search } from "@upstash/search"; import type { PutBlobResult } from "@vercel/blob"; import { FatalError, getStepMetadata, RetryableError } from "workflow"; -const index = new Index({ - url: process.env.UPSTASH_VECTOR_REST_URL!, - token: process.env.UPSTASH_VECTOR_REST_TOKEN!, +const upstash = new Search({ + url: process.env.UPSTASH_SEARCH_URL!, + token: process.env.UPSTASH_SEARCH_TOKEN!, }); export const indexImage = async (blob: PutBlobResult, text: string) => { @@ -20,11 +20,12 @@ export const indexImage = async (blob: PutBlobResult, text: string) => { ); try { - // Store blob metadata in Upstash Vector along with the description - // The index will automatically generate embeddings from the text + const index = upstash.index("images"); + + // Store blob metadata in Upstash along with the description const result = await index.upsert({ id: blob.pathname, - data: text, + content: { text }, metadata: { ...blob }, }); From 2963ee21d255f3d8ef903be3e86a65e27290cae1 Mon Sep 17 00:00:00 2001 From: v0 Date: Thu, 5 Feb 2026 13:07:10 +0000 Subject: [PATCH 04/21] fix: correct Upstash Search environment variable names Update files to use correct Upstash Search URL and token variables. --- app/actions/search.ts | 4 ++-- app/api/upload/index-image.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/actions/search.ts b/app/actions/search.ts index ff7b48d..9b0a3c7 100644 --- a/app/actions/search.ts +++ b/app/actions/search.ts @@ -6,8 +6,8 @@ import { Search } from "@upstash/search"; import type { PutBlobResult } from "@vercel/blob"; const upstash = new Search({ - url: process.env.UPSTASH_SEARCH_URL!, - token: process.env.UPSTASH_SEARCH_TOKEN!, + url: process.env.UPSTASH_SEARCH_REST_URL!, + token: process.env.UPSTASH_SEARCH_REST_TOKEN!, }); const index = upstash.index("images"); diff --git a/app/api/upload/index-image.ts b/app/api/upload/index-image.ts index 1965985..6357e00 100644 --- a/app/api/upload/index-image.ts +++ b/app/api/upload/index-image.ts @@ -5,8 +5,8 @@ import type { PutBlobResult } from "@vercel/blob"; import { FatalError, getStepMetadata, RetryableError } from "workflow"; const upstash = new Search({ - url: process.env.UPSTASH_SEARCH_URL!, - token: process.env.UPSTASH_SEARCH_TOKEN!, + url: process.env.UPSTASH_SEARCH_REST_URL!, + token: process.env.UPSTASH_SEARCH_REST_TOKEN!, }); export const indexImage = async (blob: PutBlobResult, text: string) => { From 8d3dd30db302b348126b14f1aa61300fd689f4c0 Mon Sep 17 00:00:00 2001 From: v0 Date: Thu, 5 Feb 2026 17:13:09 +0000 Subject: [PATCH 05/21] fix: add debug logs to search component error handling Improve error tracking in search results state handling. --- components/results.client.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/results.client.tsx b/components/results.client.tsx index ea1c4ec..e1efd9a 100644 --- a/components/results.client.tsx +++ b/components/results.client.tsx @@ -30,8 +30,12 @@ export const ResultsClient = ({ defaultData }: ResultsClientProps) => { const [state, formAction, isPending] = useActionState(search, { data: [] }); useEffect(() => { + console.log("[v0] Search state changed:", state); if ("error" in state) { + console.log("[v0] Search error:", state.error); toast.error(state.error); + } else if ("data" in state) { + console.log("[v0] Search results count:", state.data?.length); } }, [state]); From 235cebc66840fa597df82f660dfb3b6f3342e928 Mon Sep 17 00:00:00 2001 From: v0 Date: Thu, 5 Feb 2026 17:17:11 +0000 Subject: [PATCH 06/21] feat: enhance AI description prompt for better search results Improve AI prompt to include detailed descriptions for better search accuracy. --- app/api/upload/generate-description.ts | 12 +++++++++++- components/results.client.tsx | 4 ---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/api/upload/generate-description.ts b/app/api/upload/generate-description.ts index 630346b..1d30e10 100644 --- a/app/api/upload/generate-description.ts +++ b/app/api/upload/generate-description.ts @@ -23,7 +23,17 @@ export const generateDescription = async (blob: PutBlobResult) => { const { text } = await generateText({ model: "xai/grok-2-vision", - system: "Describe the image in detail.", + system: `You are an image description expert. Describe the image in detail for a searchable image database. + +Include the following when relevant: +- People: gender, approximate age, clothing (colors, types), accessories, hair, expressions +- Objects: what they are, colors, sizes, brands if visible +- Setting: indoor/outdoor, location type, time of day +- Actions: what is happening, poses, activities +- Colors: mention prominent colors explicitly +- Text: any visible text or signs + +Be specific and use common search terms. For example, say "man in blue t-shirt" not just "person wearing clothes".`, messages: [ { role: "user", diff --git a/components/results.client.tsx b/components/results.client.tsx index e1efd9a..ea1c4ec 100644 --- a/components/results.client.tsx +++ b/components/results.client.tsx @@ -30,12 +30,8 @@ export const ResultsClient = ({ defaultData }: ResultsClientProps) => { const [state, formAction, isPending] = useActionState(search, { data: [] }); useEffect(() => { - console.log("[v0] Search state changed:", state); if ("error" in state) { - console.log("[v0] Search error:", state.error); toast.error(state.error); - } else if ("data" in state) { - console.log("[v0] Search results count:", state.data?.length); } }, [state]); From 3c44aca276772abdf196e4ee8cbc5b29c8a765eb Mon Sep 17 00:00:00 2001 From: v0 Date: Thu, 5 Feb 2026 17:23:47 +0000 Subject: [PATCH 07/21] fix: improve search relevance and filtering Enable reranking, set score threshold, and limit results. --- app/actions/search.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/actions/search.ts b/app/actions/search.ts index 9b0a3c7..e2cbf31 100644 --- a/app/actions/search.ts +++ b/app/actions/search.ts @@ -31,10 +31,18 @@ export const search = async ( try { console.log("Searching index for query:", query); - const results = await index.search({ query }); + const results = await index.search({ + query, + limit: 50, + reranking: true, + }); console.log("Results:", results); + + // Filter results by minimum score threshold (0.3) to ensure relevance + const MINIMUM_SCORE_THRESHOLD = 0.3; const data = results + .filter((result) => result.score >= MINIMUM_SCORE_THRESHOLD) .sort((a, b) => b.score - a.score) .map((result) => result.metadata) .filter(Boolean) as unknown as PutBlobResult[]; From 1d3e63e365534460380ef166d4103307c999ec73 Mon Sep 17 00:00:00 2001 From: v0 Date: Thu, 5 Feb 2026 17:24:42 +0000 Subject: [PATCH 08/21] fix: resolve aspect ratio warning for Image component Add CSS classes to maintain aspect ratio and update sizes attribute for responsive layout. --- components/preview.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/preview.tsx b/components/preview.tsx index 09ea3c0..d3b36d1 100644 --- a/components/preview.tsx +++ b/components/preview.tsx @@ -9,10 +9,10 @@ export const Preview = ({ url, priority }: PreviewProps) => (
{url} From 69f542110da8795001e52b41d316987bc6575926 Mon Sep 17 00:00:00 2001 From: v0 Date: Thu, 5 Feb 2026 17:34:17 +0000 Subject: [PATCH 09/21] fix: lower search score threshold and add debug logging Reduce threshold from 0.3 to 0.01 and log returned scores. --- app/actions/search.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/actions/search.ts b/app/actions/search.ts index e2cbf31..af9acd4 100644 --- a/app/actions/search.ts +++ b/app/actions/search.ts @@ -37,15 +37,22 @@ export const search = async ( reranking: true, }); - console.log("Results:", results); + console.log("[v0] Raw search results with scores:", results.map(r => ({ + id: r.id, + score: r.score, + content: r.content?.substring(0, 100) + }))); - // Filter results by minimum score threshold (0.3) to ensure relevance - const MINIMUM_SCORE_THRESHOLD = 0.3; + // Filter results by minimum score threshold to ensure relevance + // Using a low threshold (0.01) since Upstash scores can vary widely + const MINIMUM_SCORE_THRESHOLD = 0.01; const data = results .filter((result) => result.score >= MINIMUM_SCORE_THRESHOLD) .sort((a, b) => b.score - a.score) .map((result) => result.metadata) .filter(Boolean) as unknown as PutBlobResult[]; + + console.log("[v0] Filtered results count:", data.length); console.log("Images found:", data); return { data }; From 5f6c7ba857a74882924c28dba2b3ca2cce64f8e9 Mon Sep 17 00:00:00 2001 From: v0 Date: Thu, 5 Feb 2026 17:36:28 +0000 Subject: [PATCH 10/21] fix: add type check before substring call Ensure content is a string before using substring method. --- app/actions/search.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/actions/search.ts b/app/actions/search.ts index af9acd4..0eb5acb 100644 --- a/app/actions/search.ts +++ b/app/actions/search.ts @@ -40,7 +40,7 @@ export const search = async ( console.log("[v0] Raw search results with scores:", results.map(r => ({ id: r.id, score: r.score, - content: r.content?.substring(0, 100) + content: typeof r.content === 'string' ? r.content.substring(0, 100) : r.content }))); // Filter results by minimum score threshold to ensure relevance From d9ce5fabeefe0048f84ace52f5ba6db0df61566c Mon Sep 17 00:00:00 2001 From: v0 Date: Thu, 5 Feb 2026 17:38:45 +0000 Subject: [PATCH 11/21] fix: fix TypeScript error with content conversion Safely convert content to string before substring call. --- app/actions/search.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/actions/search.ts b/app/actions/search.ts index 0eb5acb..a12ec3a 100644 --- a/app/actions/search.ts +++ b/app/actions/search.ts @@ -40,7 +40,7 @@ export const search = async ( console.log("[v0] Raw search results with scores:", results.map(r => ({ id: r.id, score: r.score, - content: typeof r.content === 'string' ? r.content.substring(0, 100) : r.content + content: String(r.content || '').substring(0, 100) }))); // Filter results by minimum score threshold to ensure relevance From 5230c331781b5264d00155eab7be8972738c1fc0 Mon Sep 17 00:00:00 2001 From: v0 Date: Thu, 5 Feb 2026 17:42:16 +0000 Subject: [PATCH 12/21] fix: improve image search relevance and filtering Switch to dynamic threshold based on top result's score. --- app/actions/search.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/app/actions/search.ts b/app/actions/search.ts index a12ec3a..efbb11e 100644 --- a/app/actions/search.ts +++ b/app/actions/search.ts @@ -37,18 +37,23 @@ export const search = async ( reranking: true, }); - console.log("[v0] Raw search results with scores:", results.map(r => ({ + // Sort by score first + const sortedResults = results.sort((a, b) => b.score - a.score); + + // Get the top score and filter results relative to it + // Only include results within 50% of the top score to ensure relevance + const topScore = sortedResults[0]?.score ?? 0; + const dynamicThreshold = topScore * 0.5; + + console.log("[v0] Top score:", topScore, "Dynamic threshold:", dynamicThreshold); + console.log("[v0] All results:", sortedResults.map(r => ({ id: r.id, score: r.score, - content: String(r.content || '').substring(0, 100) + included: r.score >= dynamicThreshold }))); - // Filter results by minimum score threshold to ensure relevance - // Using a low threshold (0.01) since Upstash scores can vary widely - const MINIMUM_SCORE_THRESHOLD = 0.01; - const data = results - .filter((result) => result.score >= MINIMUM_SCORE_THRESHOLD) - .sort((a, b) => b.score - a.score) + const data = sortedResults + .filter((result) => result.score >= dynamicThreshold) .map((result) => result.metadata) .filter(Boolean) as unknown as PutBlobResult[]; From 8d9c6f6aadbff507c7218e39e1496e393cf0d0f5 Mon Sep 17 00:00:00 2001 From: v0 Date: Thu, 5 Feb 2026 17:46:47 +0000 Subject: [PATCH 13/21] fix: improve search accuracy with absolute minimum score Implement absolute minimum score and relative threshold for better search relevance. --- app/actions/search.ts | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/app/actions/search.ts b/app/actions/search.ts index efbb11e..80a80b6 100644 --- a/app/actions/search.ts +++ b/app/actions/search.ts @@ -30,30 +30,37 @@ export const search = async ( } try { - console.log("Searching index for query:", query); + console.log("[v0] Searching index for query:", query); const results = await index.search({ query, limit: 50, reranking: true, }); - // Sort by score first + // Sort by score (highest first) const sortedResults = results.sort((a, b) => b.score - a.score); - // Get the top score and filter results relative to it - // Only include results within 50% of the top score to ensure relevance + // Log all results with their scores and content for debugging + console.log("[v0] Search results:"); + sortedResults.forEach((r, i) => { + const contentPreview = typeof r.content === 'object' && r.content !== null && 'text' in r.content + ? String((r.content as { text: string }).text).substring(0, 150) + : String(r.content || '').substring(0, 150); + console.log(`[v0] ${i + 1}. Score: ${r.score.toFixed(4)} | ID: ${r.id}`); + console.log(`[v0] Content: ${contentPreview}...`); + }); + + // Use absolute minimum threshold - scores below 0.1 are likely not relevant + // Also use relative threshold - only include if within 60% of top score + const ABSOLUTE_MIN = 0.1; const topScore = sortedResults[0]?.score ?? 0; - const dynamicThreshold = topScore * 0.5; + const relativeThreshold = topScore * 0.6; + const threshold = Math.max(ABSOLUTE_MIN, relativeThreshold); - console.log("[v0] Top score:", topScore, "Dynamic threshold:", dynamicThreshold); - console.log("[v0] All results:", sortedResults.map(r => ({ - id: r.id, - score: r.score, - included: r.score >= dynamicThreshold - }))); + console.log(`[v0] Top score: ${topScore}, Relative threshold: ${relativeThreshold}, Final threshold: ${threshold}`); const data = sortedResults - .filter((result) => result.score >= dynamicThreshold) + .filter((result) => result.score >= threshold) .map((result) => result.metadata) .filter(Boolean) as unknown as PutBlobResult[]; From b3a2b6ee1d7c4cfa4bd55dc9eab38a16ac3912f8 Mon Sep 17 00:00:00 2001 From: v0 Date: Thu, 5 Feb 2026 17:48:45 +0000 Subject: [PATCH 14/21] revert: restore simple search logic without threshold filtering Return to original search method without threshold filtering. --- app/actions/search.ts | 37 +++++-------------------------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/app/actions/search.ts b/app/actions/search.ts index 80a80b6..9b0a3c7 100644 --- a/app/actions/search.ts +++ b/app/actions/search.ts @@ -30,41 +30,14 @@ export const search = async ( } try { - console.log("[v0] Searching index for query:", query); - const results = await index.search({ - query, - limit: 50, - reranking: true, - }); + console.log("Searching index for query:", query); + const results = await index.search({ query }); - // Sort by score (highest first) - const sortedResults = results.sort((a, b) => b.score - a.score); - - // Log all results with their scores and content for debugging - console.log("[v0] Search results:"); - sortedResults.forEach((r, i) => { - const contentPreview = typeof r.content === 'object' && r.content !== null && 'text' in r.content - ? String((r.content as { text: string }).text).substring(0, 150) - : String(r.content || '').substring(0, 150); - console.log(`[v0] ${i + 1}. Score: ${r.score.toFixed(4)} | ID: ${r.id}`); - console.log(`[v0] Content: ${contentPreview}...`); - }); - - // Use absolute minimum threshold - scores below 0.1 are likely not relevant - // Also use relative threshold - only include if within 60% of top score - const ABSOLUTE_MIN = 0.1; - const topScore = sortedResults[0]?.score ?? 0; - const relativeThreshold = topScore * 0.6; - const threshold = Math.max(ABSOLUTE_MIN, relativeThreshold); - - console.log(`[v0] Top score: ${topScore}, Relative threshold: ${relativeThreshold}, Final threshold: ${threshold}`); - - const data = sortedResults - .filter((result) => result.score >= threshold) + console.log("Results:", results); + const data = results + .sort((a, b) => b.score - a.score) .map((result) => result.metadata) .filter(Boolean) as unknown as PutBlobResult[]; - - console.log("[v0] Filtered results count:", data.length); console.log("Images found:", data); return { data }; From e3ed11c76c0bf25da798cb30cd29cc460623e70f Mon Sep 17 00:00:00 2001 From: v0 Date: Thu, 5 Feb 2026 17:54:22 +0000 Subject: [PATCH 15/21] feat: rebrand from vectr.store to Picsearch Update branding to "Picsearch" with custom copy and remove demo mode --- app/layout.tsx | 4 ++-- app/page.tsx | 4 ++-- components/deploy.tsx | 12 +++--------- components/header.tsx | 7 +++---- components/upload-button.tsx | 10 +--------- 5 files changed, 11 insertions(+), 26 deletions(-) diff --git a/app/layout.tsx b/app/layout.tsx index 6f05284..351f5fd 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -21,8 +21,8 @@ const mono = Geist_Mono({ }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Picsearch", + description: "Search your photos using natural language", }; type RootLayoutProps = { diff --git a/app/page.tsx b/app/page.tsx index ba80e4b..0d0f477 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -5,8 +5,8 @@ import { Results } from "@/components/results"; import { UploadedImagesProvider } from "@/components/uploaded-images-provider"; export const metadata: Metadata = { - title: "vectr", - description: "vectr", + title: "Picsearch", + description: "Search your photos using natural language", }; const ImagesSkeleton = () => ( diff --git a/components/deploy.tsx b/components/deploy.tsx index 5e946c7..2ce25fd 100644 --- a/components/deploy.tsx +++ b/components/deploy.tsx @@ -6,19 +6,13 @@ export const DeployButton = () => { // Demo url.searchParams.set( "demo-description", - "A free, open-source template for building natural language image search on the AI Cloud." + "Search your photos using natural language. Just describe what you're looking for." ); - url.searchParams.set("demo-image", "https://vectr.store/opengraph-image.png"); - url.searchParams.set("demo-title", "vectr.store"); - url.searchParams.set("demo-url", "https://vectr.store/"); + url.searchParams.set("demo-title", "Picsearch"); // Marketplace url.searchParams.set("from", "templates"); - url.searchParams.set("project-name", "Vectr"); - - // Repository - url.searchParams.set("repository-name", "vectr"); - url.searchParams.set("repository-url", "https://github.com/vercel/vectr"); + url.searchParams.set("project-name", "Picsearch"); // Integrations url.searchParams.set( diff --git a/components/header.tsx b/components/header.tsx index d13256e..7e43b57 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -7,14 +7,13 @@ export const Header = () => (
-

vectr.store

+

Picsearch

- A free, open-source template for building natural language image search - on the AI Cloud. + Search your photos using natural language. Just describe what you're looking for.

- Try searching for "water" or "desert". + Try searching for "dog" or "blue shirt".

    diff --git a/components/upload-button.tsx b/components/upload-button.tsx index b5c7c19..62c2d78 100644 --- a/components/upload-button.tsx +++ b/components/upload-button.tsx @@ -12,9 +12,6 @@ export const UploadButton = () => { const inputRef = useRef(null); const abortControllerRef = useRef(null); const [isUploading, setIsUploading] = useState(false); - const isDemo = - typeof window !== "undefined" && - window.location.hostname.includes("vectr.store"); const cancelUpload = () => { if (abortControllerRef.current) { @@ -31,11 +28,6 @@ export const UploadButton = () => { return; } - if (isDemo) { - toast.error("Uploads are disabled in demo mode"); - return; - } - // Check file sizes const maxSize = 4.5 * 1024 * 1024; // 4.5MB const oversizedFiles = files.filter((file) => file.size > maxSize); @@ -239,7 +231,7 @@ export const UploadButton = () => { />
-
); From b8cd7fc2e8544cbd0198ee53b81c0f42289d828c Mon Sep 17 00:00:00 2001 From: v0 Date: Thu, 5 Feb 2026 18:28:56 +0000 Subject: [PATCH 17/21] fix: resolve accessibility issues in search bar Add `aria-label` to icon-only buttons and set main landmark. --- app/page.tsx | 4 ++-- components/results.client.tsx | 3 ++- components/upload-button.tsx | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 0d0f477..d308be9 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -29,14 +29,14 @@ const ImagesSkeleton = () => ( const Home = () => ( -
+
}> -
+
); diff --git a/components/results.client.tsx b/components/results.client.tsx index ea1c4ec..81b2b4a 100644 --- a/components/results.client.tsx +++ b/components/results.client.tsx @@ -101,6 +101,7 @@ export const ResultsClient = ({ defaultData }: ResultsClientProps) => { > {"data" in state && state.data.length > 0 && ( ) : ( diff --git a/components/upload-button.tsx b/components/upload-button.tsx index 62c2d78..7753a82 100644 --- a/components/upload-button.tsx +++ b/components/upload-button.tsx @@ -230,6 +230,7 @@ export const UploadButton = () => { type="file" /> )} { required /> {isPending ? ( - ) : ( diff --git a/components/upload-button.tsx b/components/upload-button.tsx index 7753a82..7ab008e 100644 --- a/components/upload-button.tsx +++ b/components/upload-button.tsx @@ -230,7 +230,6 @@ export const UploadButton = () => { type="file" /> ); From f7fdd34ae8cd898e6a83cd3f14092e8fb41036e1 Mon Sep 17 00:00:00 2001 From: v0 Date: Thu, 5 Feb 2026 18:33:20 +0000 Subject: [PATCH 19/21] fix: resolve Sonner Toaster accessibility issue Remove useTheme and hardcode theme to fix hydration mismatch. --- components/ui/sonner.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/components/ui/sonner.tsx b/components/ui/sonner.tsx index 957524e..1d60ab2 100644 --- a/components/ui/sonner.tsx +++ b/components/ui/sonner.tsx @@ -1,14 +1,11 @@ "use client" -import { useTheme } from "next-themes" import { Toaster as Sonner, ToasterProps } from "sonner" const Toaster = ({ ...props }: ToasterProps) => { - const { theme = "system" } = useTheme() - return ( Date: Thu, 5 Feb 2026 18:34:10 +0000 Subject: [PATCH 20/21] fix: resolve Turbopack HMR stale cache issue Force module refresh to clear old `next-themes` import cache. --- components/ui/sonner.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/ui/sonner.tsx b/components/ui/sonner.tsx index 1d60ab2..64ff637 100644 --- a/components/ui/sonner.tsx +++ b/components/ui/sonner.tsx @@ -1,6 +1,6 @@ -"use client" +"use client"; -import { Toaster as Sonner, ToasterProps } from "sonner" +import { Toaster as Sonner, type ToasterProps } from "sonner"; const Toaster = ({ ...props }: ToasterProps) => { return ( From 2c8220298996411ad10c886326be6c438e5ec865 Mon Sep 17 00:00:00 2001 From: v0 Date: Thu, 5 Feb 2026 18:35:02 +0000 Subject: [PATCH 21/21] fix: invalidate Turbopack cache for sonner.tsx module Recreate sonner.tsx to clear stale module reference. --- components/ui/sonner.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/ui/sonner.tsx b/components/ui/sonner.tsx index 64ff637..93665cf 100644 --- a/components/ui/sonner.tsx +++ b/components/ui/sonner.tsx @@ -2,7 +2,7 @@ import { Toaster as Sonner, type ToasterProps } from "sonner"; -const Toaster = ({ ...props }: ToasterProps) => { +function Toaster(props: ToasterProps) { return ( { } {...props} /> - ) + ); } -export { Toaster } +export { Toaster };