Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
33d9a41
fix: update environment variables for Upstash Search
v0[bot] Feb 5, 2026
99dbcdf
Merge pull request #1 from savoo12/upstash-token-error
vercel[bot] Feb 5, 2026
ffb097d
fix: switch to Upstash Vector API
v0[bot] Feb 5, 2026
486a6fe
revert to Upstash Search integration
v0[bot] Feb 5, 2026
2963ee2
fix: correct Upstash Search environment variable names
v0[bot] Feb 5, 2026
8d3dd30
fix: add debug logs to search component error handling
v0[bot] Feb 5, 2026
235cebc
feat: enhance AI description prompt for better search results
v0[bot] Feb 5, 2026
3c44aca
fix: improve search relevance and filtering
v0[bot] Feb 5, 2026
1d3e63e
fix: resolve aspect ratio warning for Image component
v0[bot] Feb 5, 2026
69f5421
fix: lower search score threshold and add debug logging
v0[bot] Feb 5, 2026
5f6c7ba
fix: add type check before substring call
v0[bot] Feb 5, 2026
d9ce5fa
fix: fix TypeScript error with content conversion
v0[bot] Feb 5, 2026
5230c33
fix: improve image search relevance and filtering
v0[bot] Feb 5, 2026
8d9c6f6
fix: improve search accuracy with absolute minimum score
v0[bot] Feb 5, 2026
b3a2b6e
revert: restore simple search logic without threshold filtering
v0[bot] Feb 5, 2026
e3ed11c
feat: rebrand from vectr.store to Picsearch
v0[bot] Feb 5, 2026
4e172d1
chore: remove unused header buttons and deploy file
v0[bot] Feb 5, 2026
b8cd7fc
fix: resolve accessibility issues in search bar
v0[bot] Feb 5, 2026
ac24484
fix: resolve hydration mismatch with icon buttons
v0[bot] Feb 5, 2026
f7fdd34
fix: resolve Sonner Toaster accessibility issue
v0[bot] Feb 5, 2026
89ea954
fix: resolve Turbopack HMR stale cache issue
v0[bot] Feb 5, 2026
2c82202
fix: invalidate Turbopack cache for sonner.tsx module
v0[bot] Feb 5, 2026
faec189
Merge pull request #3 from savoo12/image-search-results
vercel[bot] Feb 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion app/actions/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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_REST_URL!,
token: process.env.UPSTASH_SEARCH_REST_TOKEN!,
});
const index = upstash.index("images");

type SearchResponse =
Expand Down
12 changes: 11 additions & 1 deletion app/api/upload/generate-description.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
5 changes: 4 additions & 1 deletion app/api/upload/index-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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_REST_URL!,
token: process.env.UPSTASH_SEARCH_REST_TOKEN!,
});

export const indexImage = async (blob: PutBlobResult, text: string) => {
"use step";
Expand Down
4 changes: 2 additions & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
8 changes: 4 additions & 4 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => (
Expand All @@ -29,14 +29,14 @@ const ImagesSkeleton = () => (

const Home = () => (
<UploadedImagesProvider>
<div className="container relative mx-auto grid items-start gap-12 px-4 py-8 sm:gap-16 lg:grid-cols-[300px_1fr]">
<main className="container relative mx-auto grid items-start gap-12 px-4 py-8 sm:gap-16 lg:grid-cols-[300px_1fr]">
<div className="lg:sticky lg:top-8">
<Header />
</div>
<Suspense fallback={<ImagesSkeleton />}>
<Results />
</Suspense>
</div>
</main>
</UploadedImagesProvider>
);

Expand Down
49 changes: 0 additions & 49 deletions components/deploy.tsx

This file was deleted.

21 changes: 3 additions & 18 deletions components/header.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import { CheckCircle2Icon, ImageUpIcon } from "lucide-react";
import { DeployButton } from "./deploy";
import { Button } from "./ui/button";

export const Header = () => (
<div className="flex flex-col gap-8 sm:gap-12">
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<ImageUpIcon className="size-4" />
<h1 className="font-semibold tracking-tight">vectr.store</h1>
<h1 className="font-semibold tracking-tight">Picsearch</h1>
</div>
<p className="text-balance text-muted-foreground">
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.
</p>
<p className="text-muted-foreground text-sm italic">
Try searching for "water" or "desert".
Try searching for "dog" or "blue shirt".
</p>
</div>
<ul className="flex flex-col gap-2 text-muted-foreground sm:gap-4">
Expand Down Expand Up @@ -85,17 +82,5 @@ export const Header = () => (
</p>
</li>
</ul>
<div className="flex gap-2">
<DeployButton />
<Button asChild size="sm" variant="outline">
<a
href="https://github.com/vercel/vectr"
rel="noopener noreferrer"
target="_blank"
>
Source code
</a>
</Button>
</div>
</div>
);
4 changes: 2 additions & 2 deletions components/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ export const Preview = ({ url, priority }: PreviewProps) => (
<div className="mb-4 rounded-xl bg-card p-2 shadow-xl">
<Image
alt={url}
className="rounded-md"
className="h-auto w-full rounded-md"
height={630}
priority={priority}
sizes="630px"
sizes="(max-width: 640px) 100vw, (max-width: 768px) 50vw, 33vw"
src={url}
width={630}
/>
Expand Down
2 changes: 2 additions & 0 deletions components/results.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export const ResultsClient = ({ defaultData }: ResultsClientProps) => {
variant="ghost"
>
<ArrowLeftIcon className="size-4" />
<span className="sr-only">Clear search results</span>
</Button>
)}
<Input
Expand All @@ -122,6 +123,7 @@ export const ResultsClient = ({ defaultData }: ResultsClientProps) => {
{isPending ? (
<Button className="shrink-0" disabled size="icon" variant="ghost">
<Loader2Icon className="size-4 animate-spin" />
<span className="sr-only">Searching</span>
</Button>
) : (
<UploadButton />
Expand Down
15 changes: 6 additions & 9 deletions components/ui/sonner.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
"use client"
"use client";

import { useTheme } from "next-themes"
import { Toaster as Sonner, ToasterProps } from "sonner"

const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme()
import { Toaster as Sonner, type ToasterProps } from "sonner";

function Toaster(props: ToasterProps) {
return (
<Sonner
theme={theme as ToasterProps["theme"]}
theme="light"
className="toaster group"
style={
{
Expand All @@ -19,7 +16,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
}
{...props}
/>
)
);
}

export { Toaster }
export { Toaster };
11 changes: 2 additions & 9 deletions components/upload-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ export const UploadButton = () => {
const inputRef = useRef<HTMLInputElement>(null);
const abortControllerRef = useRef<AbortController | null>(null);
const [isUploading, setIsUploading] = useState(false);
const isDemo =
typeof window !== "undefined" &&
window.location.hostname.includes("vectr.store");

const cancelUpload = () => {
if (abortControllerRef.current) {
Expand All @@ -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);
Expand Down Expand Up @@ -239,13 +231,14 @@ export const UploadButton = () => {
/>
<Button
className="shrink-0 rounded-full disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50"
disabled={isUploading || isDemo}
disabled={isUploading}
onClick={() => inputRef.current?.click()}
size="icon"
type="button"
variant="ghost"
>
<ImageUpIcon className="size-4" />
<span className="sr-only">Upload images</span>
</Button>
</>
);
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.