Skip to content

Commit

Permalink
Merge pull request #4 from weaviate-tutorials/new-client
Browse files Browse the repository at this point in the history
Adding new Client
  • Loading branch information
malgamves committed Jun 18, 2024
2 parents e6c9ac8 + d048e00 commit c366265
Show file tree
Hide file tree
Showing 28 changed files with 847 additions and 2,573 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
# misc
.DS_Store
*.pem
/public/audio
/public/image
/public/video


# debug
npm-debug.log*
Expand All @@ -27,6 +31,7 @@ yarn-error.log*

# local env files
.env*.local
.env

# vercel
.vercel
Expand Down
24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## This is a Multimodal Search demo built with [Weaviate](https://weaviate.io), [ImageBind](https://imagebind.metademolab.com/) and [Next.js](https://nextjs.org/)
## This is a Multimodal Search demo built with [Weaviate](https://weaviate.io), [ImageBind](https://imagebind.metademolab.com/)/ [Vertex AI](https://cloud.google.com/vertex-ai) and [Next.js](https://nextjs.org/)

👨🏾‍🍳 Blogpost - [Building Multimodal AI in TypeScript
](https://weaviate.io/blog/multimodal-search-in-typescript)
Expand All @@ -10,29 +10,41 @@
First, clone the project with the command below

```bash
git clone https://github.com/malgamves/next-multimodal-search-demo
git clone https://github.com/weaviate-tutorials/next-multimodal-search-demo
```

The repository lets us do three things
The repository lets you do three things

1. Run the Next.js Web App.
2. Run an instance of Weaviate.
2. Run an instance of Weaviate OR create a Weaviate Sandbox
3. Import images, audio and videos into your Weaviate database.



### 🏗️ Running Weaviate
### 🏗️ Running Weaviate (using ImageBind)
> Note that the first time you run it, Docker will download ~4.8GB multi2vec-bind Weaviate module, which contains the ImageBind model.
To start the Weaviate instance, run the following command, which will use the `docker-compose.yml` file.
```bash
docker compose up -d
```

### 🏗️ Create a Weaviate Instance (Using Vertex AI)

Create a Weaviate instance on Weaviate Cloud Services as described in [this guide](https://weaviate.io/developers/weaviate/quickstart#step-2-create-an-instance)

### 🦿 Create a `.env` file and add the following keys

- your Google Vertex API key as `GOOGLE_KEY` (you can get this in your [Vertex AI settings](https://console.cloud.google.com/apis/credentials))
- your Weaviate API key as `WEAVIATE_API_KEY` (you can get this in your [Weaviate dashboard](https://console.weaviate.cloud/dashboard) under sandbox details)
- your Weaviate host URL as `WEAVIATE_HOST_URL` (you can get this in your [Weaviate dashboard](https://console.weaviate.cloud/dashboard) under sandbox details)


### 📩 Importing Data
> Before you can import data, add any files to their respective media type in the `public/` folder.
With your data in the right folder, run `yarn install` to install all project dependencies and to import your data into Weaviate and initialise a collection, run:
With your data in the right folder, run `yarn install` to install all project dependencies and to import your data into Weaviate and initialize a collection, run:

```bash
yarn run import
```
Expand Down
68 changes: 21 additions & 47 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,8 @@
import Footer from '@/components/footer';
import Search from '../components/search'
import Link from 'next/link'
import Footer from '@/components/footer.tsx';
import Search from '../components/search.tsx'
import { vectorSearch } from '@/utils/action.ts';

import weaviate, {
WeaviateClient,
WeaviateObject,
} from "weaviate-ts-client";
import Navigation from '@/components/navigation';

const client: WeaviateClient = weaviate.client({
scheme: 'http',
host: 'localhost:8080',
});

interface BindMediaObject {
_additional: {
certainty: number;
id: string;
};
media: string;
name: string;
}
import Navigation from '@/components/navigation.tsx';

export default async function Home({
searchParams
Expand All @@ -29,8 +11,8 @@ export default async function Home({
search?: string;
}
}) {
const search = searchParams?.search || "";
const data = await searchDB(search);
const search = searchParams?.search || "people";
const data = await vectorSearch(search);

return (
<html lang="en">
Expand All @@ -44,34 +26,39 @@ export default async function Home({
<Search placeholder="Search for a word" />
<div className="relative flex grid grid-cols-1 gap-4 lg:grid-cols-4 lg:gap-8 p-20">

{data.map((result: BindMediaObject) => (
<div key={result?._additional.id} className="">
{data.objects.map((result) => (
<div key={result.uuid} className="">

<div className="h-40 w-50">
<p className=" w-16 h-6 mt-2 ml-2 block text-center whitespace-nowrap items-center justify-center rounded-lg translate-y-8 transform bg-white px-2.5 py-0.5 text-sm text-black">
{result.media}
<div className="flex justify-between">
<p className="w-16 h-6 mt-2 ml-2 block text-center whitespace-nowrap items-center justify-center rounded-lg translate-y-8 transform bg-white px-2.5 py-0.5 text-sm text-black">
{ result.properties.media }
</p>
<p className="w-24 h-6 mt-2 mr-2 block text-center whitespace-nowrap items-center justify-end rounded-lg translate-y-8 transform bg-white px-2.5 py-0.5 text-sm text-black">
dist: { result.metadata?.distance?.toString().slice(0,6) }
</p>
{result?.media == 'image' &&
</div>
{result?.properties.media == 'image' &&
<img
alt="Certainty: "
className='block object-cover w-full h-full rounded-lg'
src={
'/' + result.media + '/' + result.name
'/' + result.properties.media + '/' + result.properties.name
}
/>
}

{result?.media == 'audio' &&
{result?.properties.media == 'audio' &&
<audio controls src={
'/' + result.media + '/' + result.name
'/' + result.properties.media + '/' + result.properties.name
} className='block object-none w-full h-full rounded-lg'>
Your browser does not support the audio element.
</audio>
}

{result.media == 'video' &&
{result.properties.media == 'video' &&
<video controls src={
'/' + result.media + '/' + result.name
'/' + result.properties.media + '/' + result.properties.name
} className='block object-none w-full h-full rounded-lg'>
Your browser does not support the video element.
</video>
Expand All @@ -89,16 +76,3 @@ export default async function Home({
)
}


async function searchDB(search: string) {

const res = await client.graphql
.get()
.withClassName("BindExample")
.withFields("media name _additional{ certainty id }")
.withNearText({ concepts: [`${search}`] })
.withLimit(8)
.do();

return res.data.Get.BindExample;
}
2 changes: 0 additions & 2 deletions components/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export default function Search(
const handleSearch = useDebouncedCallback((term: string) => {
const params = new URLSearchParams(searchParams.toString())
if (term) {
// setSearchTerm(term)
params.set("search", term)
} else {
params.delete("search")
Expand All @@ -31,7 +30,6 @@ export default function Search(
placeholder={placeholder}
onChange={(e) => handleSearch(e.target.value)}
defaultValue={searchParams.get("search")?.toString()}
// value={searchTerm}
className="w-[400px] h-12 rounded-md bg-gray-200 p-2 shadow-sm sm:text-sm"
/>
</div>
Expand Down
35 changes: 22 additions & 13 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
version: '3.4'
---
services:
weaviate:
image: semitechnologies/weaviate:1.22.2
restart: on-failure:0
command:
- --host
- 0.0.0.0
- --port
- '8080'
- --scheme
- http
image: cr.weaviate.io/semitechnologies/weaviate:1.25.2
ports:
- "8080:8080"
- 8080:8080
- 50051:50051
restart: on-failure:0
environment:
QUERY_DEFAULTS_LIMIT: 25
AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true'
PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
DEFAULT_VECTORIZER_MODULE: 'multi2vec-bind'
ENABLE_MODULES: 'multi2vec-bind'
BIND_INFERENCE_API: 'http://multi2vec-bind:8080'
CLUSTER_HOSTNAME: 'node1'
BIND_INFERENCE_API: 'http://multi2vec-bind:8080'
QUERY_DEFAULTS_LIMIT: 25
AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true'
PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
DEFAULT_VECTORIZER_MODULE: 'multi2vec-bind'
ENABLE_MODULES: 'multi2vec-bind'
CLUSTER_HOSTNAME: 'node1'
multi2vec-bind:
image: semitechnologies/multi2vec-bind:imagebind
image: cr.weaviate.io/semitechnologies/multi2vec-bind:imagebind
environment:
ENABLE_CUDA: '0'
ENABLE_CUDA: '0'
...
19 changes: 11 additions & 8 deletions import/client.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import weaviate, { WeaviateClient } from 'weaviate-ts-client';
import weaviate, { type WeaviateClient } from 'weaviate-client';
import 'dotenv/config'

let client: WeaviateClient;

export const getWeaviateClient = () => {
export const getWeaviateClient = async () => {
if (!client) {
client = weaviate.client({
scheme: 'http',
host: 'localhost:8080',
});
client = await weaviate.connectToWeaviateCloud(process.env.WEAVIATE_HOST_URL || '',{
authCredentials: new weaviate.ApiKey(process.env.WEAVIATE_API_KEY || ''),
headers: {
'X-Palm-Api-Key': process.env.GOOGLE_KEY || ''
}
},
)
};

// client.misc.readyChecker().do().then(console.log)

return client;
}
67 changes: 26 additions & 41 deletions import/collection.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,61 @@
import { WeaviateClient } from 'weaviate-ts-client';
import { getWeaviateClient } from './client';
import weaviate, { type WeaviateClient } from 'weaviate-client';
import { getWeaviateClient } from './client.ts';

const client: WeaviateClient = getWeaviateClient();
const client: WeaviateClient = await getWeaviateClient();

const collectionExists = async (name: string) => {
return client.schema.exists(name);
return client.collections.exists(name);
}

export const createBindCollection = async (name: string) => {
if(await collectionExists(name)) {
export const createCollection = async (name: string) => {
if (await collectionExists(name)) {
console.log(`The collection [${name}] already exists. No need to create it.`);
return;
}

console.log(`Creating collection [${name}].`);

const bindSchema = {
class: name,
moduleConfig: {
'multi2vec-bind': {
// textFields: ['name'],
imageFields: ['image'],
audioFields: ['audio'],
videoFields: ['video'],
}
const newCollection = await client.collections.create({
name: name,
vectorizers: weaviate.configure.vectorizer.multi2VecPalm({
projectId: 'semi-random-dev',
location: 'us-central1',
imageFields: ['image'],
videoFields: ['video'],
},
),
properties: [
{
name: 'name',
dataType: ['text'],
moduleConfig: {
'multi2vec-bind': { skip: true },
},
dataType: 'text',
},
{
name: 'media',
dataType: ['text'],
moduleConfig: {
'multi2vec-bind': { skip: true },
},
dataType: 'text',
},
{
name: 'image',
dataType: ['blob'],
dataType: 'blob',
},
{
name: 'audio',
dataType: ['blob'],
dataType: 'blob',
},
{
name: 'video',
dataType: ['blob'],
dataType: 'blob',
}
],
vectorIndexType: 'hnsw',
vectorizer: 'multi2vec-bind'
}

const res = await client
.schema.classCreator()
.withClass(bindSchema)
.do();
})

console.log(JSON.stringify(res, null, 2));


console.log(JSON.stringify(newCollection, null, 2));
}

export const deleteCollection = async (name: string) => {
console.log(`Deleting collection ${name}...`);
await client.schema
.classDeleter()
.withClassName(name)
.do();
await client.collections.delete(name);

console.log(`Deleted collection ${name}.`);
}
}
Loading

0 comments on commit c366265

Please sign in to comment.