diff --git a/app/actions/search.ts b/app/actions/search.ts
index fc7d478..9b0a3c7 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_REST_URL!,
+ token: process.env.UPSTASH_SEARCH_REST_TOKEN!,
+});
const index = upstash.index("images");
type SearchResponse =
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/app/api/upload/index-image.ts b/app/api/upload/index-image.ts
index 2e18531..6357e00 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_REST_URL!,
+ token: process.env.UPSTASH_SEARCH_REST_TOKEN!,
+});
export const indexImage = async (blob: PutBlobResult, text: string) => {
"use step";
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..d308be9 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 = () => (
@@ -29,14 +29,14 @@ const ImagesSkeleton = () => (
const Home = () => (
-
+
);
diff --git a/components/deploy.tsx b/components/deploy.tsx
deleted file mode 100644
index 5e946c7..0000000
--- a/components/deploy.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import Image from "next/image";
-
-export const DeployButton = () => {
- const url = new URL("https://vercel.com/new/clone");
-
- // Demo
- url.searchParams.set(
- "demo-description",
- "A free, open-source template for building natural language image search on the AI Cloud."
- );
- 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/");
-
- // 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");
-
- // Integrations
- url.searchParams.set(
- "products",
- JSON.stringify([
- {
- type: "integration",
- protocol: "storage",
- productSlug: "upstash-search",
- integrationSlug: "upstash",
- },
- { type: "blob" },
- ])
- );
- url.searchParams.set("skippable-integrations", "0");
-
- return (
-
-
-
- );
-};
diff --git a/components/header.tsx b/components/header.tsx
index d13256e..0566a2c 100644
--- a/components/header.tsx
+++ b/components/header.tsx
@@ -1,20 +1,17 @@
import { CheckCircle2Icon, ImageUpIcon } from "lucide-react";
-import { DeployButton } from "./deploy";
-import { Button } from "./ui/button";
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".
@@ -85,17 +82,5 @@ export const Header = () => (
-
);
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) => (
diff --git a/components/results.client.tsx b/components/results.client.tsx
index ea1c4ec..169499d 100644
--- a/components/results.client.tsx
+++ b/components/results.client.tsx
@@ -109,6 +109,7 @@ export const ResultsClient = ({ defaultData }: ResultsClientProps) => {
variant="ghost"
>
+
Clear search results
)}
{
{isPending ? (
+ Searching
) : (
diff --git a/components/ui/sonner.tsx b/components/ui/sonner.tsx
index 957524e..93665cf 100644
--- a/components/ui/sonner.tsx
+++ b/components/ui/sonner.tsx
@@ -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 (
{
}
{...props}
/>
- )
+ );
}
-export { Toaster }
+export { Toaster };
diff --git a/components/upload-button.tsx b/components/upload-button.tsx
index b5c7c19..7ab008e 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,13 +231,14 @@ export const UploadButton = () => {
/>
inputRef.current?.click()}
size="icon"
type="button"
variant="ghost"
>
+ Upload images
>
);
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)