Skip to content

Commit

Permalink
[ENH] fix browser usage of JS client & update browser example to work…
Browse files Browse the repository at this point in the history
… with latest version of Chroma (#2050)
  • Loading branch information
codetheweb authored Apr 24, 2024
1 parent ed0d578 commit 0056c13
Show file tree
Hide file tree
Showing 11 changed files with 2,709 additions and 1,657 deletions.
19 changes: 9 additions & 10 deletions clients/js/examples/browser/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
## Demo in browser

Update your settings to add `localhost:3000` to `chroma_server_cors_allow_origins`.
First, update Chroma's config to allow the `localhost:3000` origin for CORS.

For example in `docker-compose.yml`
For example, you could start Chroma with

```bash
CHROMA_SERVER_CORS_ALLOW_ORIGINS='["http://localhost:3000"]' chroma run
```
environment:
- CHROMA_DB_IMPL=clickhouse
- CLICKHOUSE_HOST=clickhouse
- CLICKHOUSE_PORT=8123
- CHROMA_SERVER_CORS_ALLOW_ORIGINS=["http://localhost:3000"]
```

1. `yarn dev`
2. visit `localhost:3000`
Then, in this folder:

1. `pnpm i`
2. `pnpm dev`
3. visit `localhost:3000`
53 changes: 0 additions & 53 deletions clients/js/examples/browser/app.ts

This file was deleted.

231 changes: 231 additions & 0 deletions clients/js/examples/browser/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import React, { useCallback, useEffect, useState } from "react";
import { ChromaClient } from "../../src/ChromaClient";
import { Collection } from "../../src/Collection";

const SAMPLE_DOCUMENTS = [
"apple",
"strawberry",
"pineapple",
"scooter",
"car",
"train",
];

const hashString = async (message: string) => {
const encoder = new TextEncoder();
const data = encoder.encode(message);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
};

const chroma = new ChromaClient({ path: "http://localhost:8000" });

const useCollection = () => {
const [collection, setCollection] = useState<Collection>();

useEffect(() => {
chroma
.getOrCreateCollection({ name: "demo-collection" })
.then((collection) => setCollection(collection));
}, []);

return collection;
};

const useDocuments = (query?: string) => {
const collection = useCollection();
const [isLoading, setIsLoading] = useState(false);
const [documents, setDocuments] = useState<
{ document: string; relativeDistance?: number }[]
>([]);

const revalidate = useCallback(
async (abortSignal?: AbortSignal) => {
setIsLoading(true);
try {
if (query) {
collection?.query({ queryTexts: query }).then((results) => {
if (abortSignal?.aborted) {
return;
}

const maxDistance = Math.max(...(results.distances?.[0] ?? []));
setDocuments(
results.documents[0].map((document, i) => {
const distance = results.distances?.[0][i] ?? 0;
const relativeDistance = distance / maxDistance;

return {
document: document!,
relativeDistance,
};
}),
);
});
} else {
collection?.get({}).then((results) => {
if (abortSignal?.aborted) {
return;
}

setDocuments(
results.documents.map((document) => ({
document: document!,
})),
);
});
}
} finally {
setIsLoading(false);
}
},
[collection, query],
);

useEffect(() => {
const abort = new AbortController();
revalidate(abort.signal);
return () => abort.abort();
}, [revalidate]);

return { documents, revalidate, isLoading };
};

export function App() {
const [query, setQuery] = useState("");
const [isMutating, setIsMutating] = useState(false);
const collection = useCollection();

const trimmedQuery = query.trim();

const { documents, revalidate, isLoading } = useDocuments(
trimmedQuery === "" ? undefined : trimmedQuery,
);

const handleDocumentAdd = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();

if (!collection) {
return;
}

const currentTarget = event.currentTarget;

const document = new FormData(currentTarget).get("document")!.toString();

setIsMutating(true);
try {
await collection.upsert({
ids: [await hashString(document)],
documents: [document],
});

await revalidate();
currentTarget.reset();
} finally {
setIsMutating(false);
}
};

const handleLoadSampleData = async (event: React.FormEvent) => {
event.preventDefault();

if (!collection) {
return;
}

setIsMutating(true);
try {
await collection.upsert({
ids: await Promise.all(
SAMPLE_DOCUMENTS.map(async (d) => hashString(d)),
),
documents: SAMPLE_DOCUMENTS,
});

await revalidate();
} finally {
setIsMutating(false);
}
};

return (
<div>
<h1>Chroma Browser Demo</h1>

<p>
This assumes that you have a locally running Chroma instance at port{" "}
<code>8000</code> and that CORS is allowed for Vite's dev server. To
start Chroma, you can use this command:
</p>

<pre>
CHROMA_SERVER_CORS_ALLOW_ORIGINS='["http://localhost:3000"]' chroma run
</pre>

<form onSubmit={handleDocumentAdd}>
<h3>Add documents</h3>

<textarea placeholder="foo" name="document" disabled={isMutating} />
<div style={{ display: "flex", justifyContent: "space-between" }}>
<button disabled={isMutating}>Create</button>
<button
disabled={isMutating}
type="button"
onClick={handleLoadSampleData}
>
Load sample data
</button>
</div>
</form>

<div>
<h3>Query</h3>

<p>
Try loading the sample dataset, then search for "fruit" or "vehicle"
and note how the results are ordered.
</p>

<label>Search</label>
<input
placeholder="fruit"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>

{isLoading && <progress style={{ marginLeft: "1rem" }} />}

<h4>Results</h4>

<p>
When you search for a query, the (xx) number is the distance of the
document's vector from the vector of your query. A lower number means
it's closer and thus more relevant to your query.
</p>

{documents.length > 0 || trimmedQuery !== "" ? (
<label>
{trimmedQuery === ""
? "(All documents)"
: `Documents filtered by "${trimmedQuery}"`}
</label>
) : (
<label>(No documents have been created)</label>
)}

<ul>
{documents.map(({ document, relativeDistance }) => (
<li key={document}>
{document}{" "}
{relativeDistance
? `(${Math.round(relativeDistance * 100)})`
: ""}
</li>
))}
</ul>
</div>
</div>
);
}
31 changes: 9 additions & 22 deletions clients/js/examples/browser/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,20 @@
<head>
<meta charset="utf-8" />
<title>Demo App</title>
<script type="module" src="app.ts"></script>
<link
rel="stylesheet"
href="https://unpkg.com/sakura.css/css/sakura.css"
href="https://unpkg.com/[email protected]/normalize.css"
type="text/css"
/>
<link
rel="stylesheet"
href="https://unpkg.com/[email protected]/css/sakura.css"
type="text/css"
/>
</head>
<body>
<h3>Page intentionally left blank</h3>
<!-- <div>
<h3>List Collections</h3>
<div id="list-collections-result">Fetching data from server</div>
</div>
<div>
<h3>Collection Count</h3>
<div id="collection-count">Fetching data from server</div>
</div>

<div>
<h3>Collection Get</h3>
<div id="collection-get">Fetching data from server</div>
</div>
<div>
<h3>Collection Query</h3>
<div id="collection-query">Fetching data from server</div>
</div> -->
<body>
<div id="app"></div>
<script type="module" src="index.tsx"></script>
</body>
</html>
7 changes: 7 additions & 0 deletions clients/js/examples/browser/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react";
import { createRoot } from "react-dom/client";
import { App } from "./app";

const container = document.getElementById("app");
const root = createRoot(container);
root.render(<App />);
15 changes: 10 additions & 5 deletions clients/js/examples/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@
"author": "",
"license": "Apache-2.0",
"devDependencies": {
"parcel": "^2.6.0",
"process": "^0.11.10"
"@types/react": "^18.2.79",
"@vitejs/plugin-react": "^4.2.1",
"chromadb-default-embed": "^2.13.2",
"buffer": "^5.5.0||^6.0.0",
"process": "^0.11.10",
"vite": "^5.2.10"
},
"dependencies": {
"chromadb": "file:../.."
"chromadb": "link:../..",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"scripts": {
"dev": "parcel ./index.html --port 3000 --no-cache",
"start": "parcel ./index.html --port 3000 --no-cache"
"dev": "vite"
}
}
Loading

0 comments on commit 0056c13

Please sign in to comment.