-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
31 changed files
with
3,797 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -128,3 +128,6 @@ dist | |
.yarn/build-state.yml | ||
.yarn/install-state.gz | ||
.pnp.* | ||
|
||
py-embed/.venv | ||
py-embed/__pycache__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,56 @@ | ||
# ch-image-search-demo | ||
This repository contains an example React NextJS application that leverage Clickhouse vector Search capability to implement image similarity search. | ||
|
||
## Prerequisite | ||
- Clickhouse server | ||
- NodeJS LTS | ||
- Python 3.10+ | ||
|
||
## Architecture | ||
|
||
This application has two parts: | ||
1. Python embedding model | ||
2. ReactJS frontend | ||
|
||
## Python embedding model | ||
|
||
The Python application is used to generate embeddings for the images. | ||
|
||
### Setup | ||
|
||
Install the dependencies: | ||
|
||
```bash | ||
cd py-embed | ||
pip install -r requirements.txt | ||
``` | ||
|
||
Run the application: | ||
|
||
```bash | ||
uvicorn app:app --reload | ||
``` | ||
|
||
## ReactJS application | ||
|
||
The ReactJS application is used to stream images from Clickhouse and also perform image similarity search. | ||
|
||
### Setup | ||
|
||
Install the dependencies: | ||
|
||
```bash | ||
yarn install | ||
``` | ||
|
||
Run the application: | ||
|
||
```bash | ||
yarn dev | ||
``` | ||
|
||
Build the application: | ||
|
||
```bash | ||
yarn build | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": ["next/core-web-vitals", "next/typescript"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
/node_modules | ||
/.pnp | ||
.pnp.* | ||
.yarn/* | ||
!.yarn/patches | ||
!.yarn/plugins | ||
!.yarn/releases | ||
!.yarn/versions | ||
|
||
# testing | ||
/coverage | ||
|
||
# next.js | ||
/.next/ | ||
/out/ | ||
|
||
# production | ||
/build | ||
|
||
# misc | ||
.DS_Store | ||
*.pem | ||
|
||
# debug | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
|
||
# env files (can opt-in for committing if needed) | ||
.env* | ||
|
||
# vercel | ||
.vercel | ||
|
||
# typescript | ||
*.tsbuildinfo | ||
next-env.d.ts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { NextResponse } from 'next/server' | ||
import clickhouse from '@/utils/clickhouse' | ||
|
||
export async function POST(request: Request) { | ||
try { | ||
const { vectorData } = await request.json() | ||
const { searchParams } = new URL(request.url) | ||
const timestampMin = searchParams.get('timestamp_min') | ||
const timestampMax = searchParams.get('timestamp_max') | ||
|
||
// Convert the vector data to a string format that ClickHouse can understand | ||
const vectorString = `[${vectorData.join(',')}]` | ||
if (timestampMin != null && timestampMax != null) { | ||
const result = await clickhouse.query({ | ||
query: ` | ||
SELECT id, base64_data, L2Distance(image_embedding, ${vectorString}) as score | ||
FROM social_posts_with_images WHERE similarity >= 0.2 and timestamp > parseDateTimeBestEffort({timestampMin:String}) and timestamp < parseDateTimeBestEffort({timestampMax:String}) | ||
ORDER BY score ASC | ||
LIMIT 4 | ||
SETTINGS enable_analyzer = 1 | ||
`, | ||
format: 'JSONEachRow', | ||
query_params: { | ||
timestampMin: timestampMin, | ||
timestampMax: timestampMax | ||
} | ||
}) | ||
const data = await result.json() | ||
return NextResponse.json(data) | ||
} else { | ||
const result = await clickhouse.query({ | ||
query: ` | ||
SELECT id, base64_data, L2Distance(image_embedding, ${vectorString}) as score | ||
FROM social_posts_with_images WHERE similarity >= 0.2 | ||
ORDER BY score ASC | ||
LIMIT 4 | ||
SETTINGS enable_analyzer = 0 | ||
`, | ||
format: 'JSONEachRow', | ||
}) | ||
const data = await result.json() | ||
return NextResponse.json(data) | ||
} | ||
} catch (error) { | ||
console.error('Search error:', error) | ||
return NextResponse.json({ error: 'Failed to search images' }, { status: 500 }) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { NextResponse } from 'next/server' | ||
import clickhouse from '@/utils/clickhouse' | ||
|
||
interface ImageRow { | ||
url: string; | ||
timestamp: string; | ||
id: string; | ||
base64_data: string; | ||
} | ||
|
||
export async function GET(request: Request) { | ||
const { searchParams } = new URL(request.url) | ||
const defaultTimestamp = new Date('2024-11-15').toISOString().replace('T', ' ').split('.')[0] | ||
const timestamp = searchParams.get('timestamp') || defaultTimestamp | ||
try { | ||
const result = await clickhouse.query({ | ||
query: ` | ||
SELECT url, timestamp, id, base64_data | ||
FROM social_posts_with_images | ||
WHERE timestamp > {timestamp:String} and height > 100 and width > 200 | ||
ORDER BY timestamp ASC | ||
LIMIT 10 | ||
`, | ||
format: 'JSONEachRow', | ||
query_params: { | ||
timestamp: timestamp | ||
} | ||
}) | ||
|
||
const data = await result.json() | ||
const response: ImageRow[] = [] | ||
for (const row of data as ImageRow[] ) { | ||
response.push({ ...row }) | ||
} | ||
return NextResponse.json(response) | ||
} catch (error) { | ||
console.error(error) | ||
return NextResponse.json({ error: 'Failed to fetch images' }, { status: 500 }) | ||
} | ||
} |
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
@tailwind base; | ||
@tailwind components; | ||
@tailwind utilities; | ||
|
||
:root { | ||
--ch-yellow: #FFFF76; | ||
--ch-dark: #1A1A1A; | ||
--ch-darker: #141414; | ||
} | ||
|
||
@layer components { | ||
.no-scrollbar { | ||
-ms-overflow-style: none; | ||
scrollbar-width: none; | ||
} | ||
.no-scrollbar::-webkit-scrollbar { | ||
display: none; | ||
} | ||
} | ||
|
||
html, body { overflow: hidden; } | ||
|
||
|
||
body { | ||
background-color: var(--ch-dark); | ||
color: white; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import './globals.css' | ||
|
||
export default function RootLayout({ | ||
children, | ||
}: { | ||
children: React.ReactNode | ||
}) { | ||
return ( | ||
<html lang="en"> | ||
<body> | ||
<main className="container mx-auto p-4"> | ||
{children} | ||
</main> | ||
</body> | ||
</html> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
|
||
import Main from '@/components/Main' | ||
|
||
export default function Home() { | ||
|
||
|
||
|
||
return ( | ||
<div className="min-h-screen bg-[#1A1A1A] overflow-hidden"> | ||
<header className="bg-[#141414] p-4 border-b border-gray-800"> | ||
<h1 className="text-2xl font-bold text-white">Real time vector search</h1> | ||
</header> | ||
|
||
|
||
<Main /> | ||
</div> | ||
) | ||
} |
Oops, something went wrong.