Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 5 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,15 @@ jobs:
run: |
npm ci
cd packages/backend && uv sync --frozen
df -lh

- uses: actions/cache@v5
id: cache-demo-data
id: cache-files
with:
path: packages/docs/demo-data
key: ${{ runner.os }}-${{ hashFiles('packages/docs/generate_demo_data.py') }}
path: ~/.cache/embedding_atlas
key: ${{ runner.os }}-embedding-atlas

- name: Generate demo data
if: steps.cache-demo-data.outputs.cache-hit != 'true'
run: cd packages/docs && uv run --project ../backend python generate_demo_data.py
- name: Generate cached demo data
run: cd packages/docs && npm run generate_cache

- name: Build WASM modules
run: ./scripts/build_wasm.sh
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ Embedding Atlas is a tool that provides interactive visualizations for large emb
Please visit <https://apple.github.io/embedding-atlas> for a demo and documentation.

<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/docs/assets/embedding-atlas-dark.png">
<img alt="screenshot of Embedding Atlas" src="./packages/docs/assets/embedding-atlas-light.png">
<source media="(prefers-color-scheme: dark)" srcset="./packages/docs/public/assets/embedding-atlas-dark.png">
<img alt="screenshot of Embedding Atlas" src="./packages/docs/public/assets/embedding-atlas-light.png">
</picture>

## Get started
Expand Down
22 changes: 12 additions & 10 deletions packages/backend/embedding_atlas/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"""Command line interface."""

import importlib
import logging
import pathlib
import socket
from pathlib import Path
Expand All @@ -17,7 +16,13 @@
from .data_source import DataSource
from .options import make_embedding_atlas_props
from .server import make_server
from .utils import Hasher, load_huggingface_data, load_pandas_data
from .utils import (
Hasher,
apply_logging_config,
load_huggingface_data,
load_pandas_data,
logger,
)
from .version import __version__


Expand Down Expand Up @@ -319,10 +324,7 @@ def main(
stop_words: str | None,
labels: str | None,
):
logging.basicConfig(
level=logging.INFO,
format="%(levelname)s: (%(name)s) %(message)s",
)
apply_logging_config()

if with_modules is not None:
import_modules(with_modules)
Expand Down Expand Up @@ -365,7 +367,7 @@ def main(
if vector is not None:
compute_vector_projection(
df,
vector,
vector=vector,
x=x_column,
y=y_column,
neighbors=new_neighbors_column,
Expand All @@ -385,7 +387,7 @@ def main(

compute_text_projection(
df,
text,
text=text,
x=x_column,
y=y_column,
neighbors=new_neighbors_column,
Expand All @@ -399,7 +401,7 @@ def main(
elif image is not None:
compute_image_projection(
df,
image,
image=image,
x=x_column,
y=y_column,
neighbors=new_neighbors_column,
Expand Down Expand Up @@ -460,7 +462,7 @@ def main(
if enable_auto_port:
new_port = find_available_port(port, max_attempts=10, host=host)
if new_port != port:
logging.info(f"Port {port} is not available, using {new_port}")
logger.info(f"Port {port} is not available, using {new_port}")
else:
new_port = port
uvicorn.run(app, port=new_port, host=host, access_log=False)
Expand Down
16 changes: 10 additions & 6 deletions packages/backend/embedding_atlas/projection.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,11 @@ def _run_umap(
metric=metric,
metric_kwds=None,
angular=False,
random_state=None,
random_state=umap_args.get("random_state"),
)

proj = umap.UMAP(**umap_args, precomputed_knn=knn)
kwargs = {k: v for k, v in umap_args.items() if k != "metric"}
proj = umap.UMAP(**kwargs, precomputed_knn=knn, metric=metric)
result: np.ndarray = proj.fit_transform(hidden_vectors) # type: ignore

return Projection(projection=result, knn_indices=knn[0], knn_distances=knn[1])
Expand Down Expand Up @@ -120,14 +121,14 @@ def _project_text_with_litellm(
model: str,
args: dict | None = None,
) -> np.ndarray:
from litellm import EmbeddingResponse, aembedding, embedding
from litellm import aembedding, embedding

default_args = {
"sync": False,
}
merged_args = {**default_args, **(args or {})}

def run_sync() -> list[EmbeddingResponse]:
def run_sync() -> list:
# Process batches synchronously
return [
embedding(
Expand All @@ -138,10 +139,10 @@ def run_sync() -> list[EmbeddingResponse]:
for i in range(0, len(texts), batch_size)
]

def run_async() -> list[EmbeddingResponse]:
def run_async() -> list:
import asyncio

async def run_async_coro() -> list[EmbeddingResponse]:
async def run_async_coro() -> list:
# Create coroutines for each batch for asynchronous processing
response_coroutines = [
aembedding(
Expand Down Expand Up @@ -330,6 +331,7 @@ def _find_text_projector_callback(name: str) -> TextProjectorCallback:

def compute_text_projection(
data_frame: pd.DataFrame,
*,
text: str,
x: str = "projection_x",
y: str = "projection_y",
Expand Down Expand Up @@ -417,6 +419,7 @@ def compute_text_projection(

def compute_vector_projection(
data_frame: pd.DataFrame,
*,
vector: str,
x: str = "projection_x",
y: str = "projection_y",
Expand Down Expand Up @@ -475,6 +478,7 @@ def compute_vector_projection(

def compute_image_projection(
data_frame: pd.DataFrame,
*,
image: str,
x: str = "projection_x",
y: str = "projection_y",
Expand Down
9 changes: 8 additions & 1 deletion packages/backend/embedding_atlas/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import pyarrow as pa
from platformdirs import user_cache_path

logger = logging.getLogger()
logger = logging.getLogger("embedding-atlas")


def load_pandas_data(url: str) -> pd.DataFrame:
Expand Down Expand Up @@ -106,6 +106,13 @@ def cache_path(*subfolders: str, mkdir=True) -> Path:
return p


def apply_logging_config():
logging.basicConfig(
level=logging.INFO,
format="%(levelname)s: (%(name)s) %(message)s",
)


class Hasher:
def __init__(self):
self.hash = hashlib.sha256()
Expand Down
4 changes: 3 additions & 1 deletion packages/docs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
.vitepress/dist
public/app
public/demo
demo-data
public/examples/app
public/examples/cache
.cache
19 changes: 11 additions & 8 deletions packages/docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,37 @@ import { defineConfig } from "vitepress";

import docComment from "./doc_comment.mjs";

const base = process.env["EMBEDDING_ATLAS_BASE"] ?? "/embedding-atlas/";

// https://vitepress.dev/reference/site-config
export default defineConfig({
base: process.env["EMBEDDING_ATLAS_BASE"] ?? "/embedding-atlas/",
base: base,
title: "Embedding Atlas",
description:
"Interactive visualizations for large-scale embeddings. Effortlessly explore, filter, and search through rich metadata.",
head: [
["link", { rel: "icon", href: "${base}favicon.svg", media: "(prefers-color-scheme: light)" }],
["link", { rel: "icon", href: "${base}favicon_dark.svg", media: "(prefers-color-scheme: dark)" }],
["meta", { property: "twitter:image", content: "${base}social.png" }],
["meta", { property: "og:image", content: "${base}social.png" }],
["link", { rel: "icon", href: `${base}assets/icon-light.png`, media: "(prefers-color-scheme: light)" }],
["link", { rel: "icon", href: `${base}assets/icon-dark.png`, media: "(prefers-color-scheme: dark)" }],
["meta", { property: "twitter:image", content: `${base}assets/social.png` }],
["meta", { property: "og:image", content: `${base}assets/social.png` }],
],
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
logo: {
light: "/favicon.svg",
dark: "/favicon_dark.svg",
light: "/assets/icon-light.png",
dark: "/assets/icon-dark.png",
},
search: {
provider: "local",
},
nav: [
{ text: "Home", link: "/" },
{ text: "Docs", link: "/overview", target: "_self" },
{ text: "Demo", link: "/demo/index.html", target: "_self" },
{ text: "Examples", link: "/examples", target: "_self" },
],
sidebar: [
{ text: "Overview", link: "/overview" },
{ text: "Examples", link: "/examples" },
{
text: "Use Embedding Atlas",
items: [
Expand Down
64 changes: 64 additions & 0 deletions packages/docs/.vitepress/theme/ExampleItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<script setup>
import { withBase } from "vitepress";
import { computed } from "vue";

const props = defineProps(["title", "details", "image", "data", "settings", "state"]);
const href = computed(
() =>
"/examples/app/#?" +
new URLSearchParams({
data: props.data,
...(props.settings != null ? { settings: props.settings } : {}),
...(props.state != null ? { state: props.state } : {}),
}).toString(),
);
</script>

<template>
<a :href="withBase(href)" target="_blank" class="example-item">
<img :src="withBase(image)" />
<div class="example-item-title">{{ props.title }}</div>
<div class="example-item-details">{{ props.details }}</div>
</a>
</template>

<style scoped>
.example-item {
display: flex;
flex-direction: column;
gap: 4px;
width: 336px;
padding: 8px;
line-height: 1em;
font-size: 14px;
border-radius: 8px;
color: var(--vp-c-indigo-1);
border: 1px solid var(--vp-c-gray-1);
background: var(--vp-c-gray-soft);
text-decoration: none !important;
transition: all 0.3s ease;
cursor: pointer;
}

.example-item:hover {
color: var(--vp-c-indigo-1);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border-color: var(--vp-c-brand-1);
}

.example-item img {
border-radius: 2px;
aspect-ratio: 16 / 9;
object-fit: cover;
}

.example-item-title {
margin-top: 2px;
}

.example-item-details {
font-size: 10px;
color: #999;
}
</style>
57 changes: 57 additions & 0 deletions packages/docs/.vitepress/theme/custom.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
:root {
--vp-nav-logo-height: 36px;
}

html.dark .light-only {
display: none !important;
}
Expand All @@ -10,6 +14,59 @@ html.dark .VPHero .image {
mix-blend-mode: screen;
}

.VPHero .image .VPImage {
max-width: none;
max-height: none;
width: 100%;
}

.VPHero .heading .text {
font-size: 20px;
}

@media (min-width: 640px) {
.VPHero .heading .text {
font-size: 32px;
}
}

@media (min-width: 960px) {
.VPHero .image .image-container {
justify-content: right;
transform: none;
position: relative;
}

.VPHero .image .image-bg {
display: none;
}

.VPHero .image .VPImage {
width: auto;
height: auto;
max-width: none;
max-height: none;
transform: none;
top: auto;
left: auto;
bottom: auto;
right: auto;

height: 100%;
position: absolute;
right: 0;
}

.VPHero .main {
background: linear-gradient(90deg, var(--vp-c-bg) 0%, var(--vp-c-bg) 85%, rgba(0, 0, 0, 0) 100%);
z-index: 1;
}

.VPHero .heading .text {
font-size: 36px;
}
}

h3.doc-property {
display: flex;
gap: 10px;
Expand Down
9 changes: 8 additions & 1 deletion packages/docs/.vitepress/theme/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,11 @@
import DefaultTheme from "vitepress/theme";
import "./custom.css";

export default DefaultTheme;
import ExampleItem from "./ExampleItem.vue";

export default {
extends: DefaultTheme,
enhanceApp(ctx) {
ctx.app.component("ExampleItem", ExampleItem);
},
};
Binary file removed packages/docs/assets/embedding-atlas-dark.png
Binary file not shown.
Binary file removed packages/docs/assets/embedding-atlas-light.png
Binary file not shown.
Loading