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
2 changes: 2 additions & 0 deletions embedding_cluster/server/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
class CollectionInfo(BaseModel):
name: str
count: int
model_name: str | None = None
model_type: str | None = None


class CollectionDetail(BaseModel):
Expand Down
17 changes: 13 additions & 4 deletions embedding_cluster/server/routes/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,19 @@ def _get_chromadb_client() -> ClientAPI:
async def list_collections() -> list[CollectionInfo]:
client = _get_chromadb_client()
collection_names = client.list_collections()
return [
CollectionInfo(name=c, count=client.get_collection(c).count())
for c in collection_names
]
results: list[CollectionInfo] = []
for name in collection_names:
collection = client.get_collection(name)
metadata = collection.metadata or {}
results.append(
CollectionInfo(
name=name,
count=collection.count(),
model_name=metadata.get("model_name"),
model_type=metadata.get("model_type"),
)
)
return results


@router.get("/{name}", response_model=CollectionDetail)
Expand Down
12 changes: 6 additions & 6 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { BrowserRouter, Link, NavLink, Route, Routes } from 'react-router-dom'
import CollectionsPage from './pages/CollectionsPage'
import HomePage from './pages/HomePage'
import IndexPage from './pages/IndexPage'
import PlotPage from './pages/PlotPage'

Expand All @@ -25,14 +25,14 @@ function NavBar() {
</Link>
<div className="ml-10 flex items-baseline space-x-4">
<NavLink to="/" end className={linkClass}>
Home
</NavLink>
<NavLink to="/index" className={linkClass}>
Index
</NavLink>
<NavLink to="/plot" className={linkClass}>
Plot
</NavLink>
<NavLink to="/collections" className={linkClass}>
Collections
</NavLink>
</div>
</div>
</div>
Expand All @@ -49,9 +49,9 @@ export default function App() {
<NavBar />
<main>
<Routes>
<Route path="/" element={<IndexPage />} />
<Route path="/" element={<HomePage />} />
<Route path="/index" element={<IndexPage />} />
<Route path="/plot" element={<PlotPage />} />
<Route path="/collections" element={<CollectionsPage />} />
</Routes>
</main>
</div>
Expand Down
79 changes: 0 additions & 79 deletions frontend/src/components/collections/CollectionList.tsx

This file was deleted.

122 changes: 122 additions & 0 deletions frontend/src/components/home/CollectionCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { fetchCollection, deleteCollection } from '../../api/collections'
import type { CollectionInfo } from '../../types'

interface CollectionCardProps {
collection: CollectionInfo
}

export default function CollectionCard({ collection }: CollectionCardProps) {
const [isExpanded, setIsExpanded] = useState(false)
const navigate = useNavigate()
const queryClient = useQueryClient()

const { data: detail, isLoading: isDetailLoading } = useQuery({
queryKey: ['collection', collection.name],
queryFn: () => fetchCollection(collection.name),
enabled: isExpanded,
})

const deleteMutation = useMutation({
mutationFn: deleteCollection,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['collections'] })
},
})

const handleDelete = () => {
if (
window.confirm(
`Are you sure you want to delete collection "${collection.name}"?`,
)
) {
deleteMutation.mutate(collection.name)
}
}

return (
<div className="bg-white rounded-lg shadow-md border border-gray-200 hover:shadow-lg transition-shadow">
<div className="p-6">
<h3 className="text-lg font-semibold text-gray-900 truncate">
{collection.name}
</h3>
<p className="text-2xl font-bold text-gray-700 mt-2">
{collection.count.toLocaleString()}
<span className="text-sm font-normal text-gray-500 ml-1">items</span>
</p>
{(collection.model_type || collection.model_name) && (
<div className="mt-2 flex flex-wrap gap-2">
{collection.model_type && (
<span className="inline-flex items-center rounded-full bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-600/20">
{collection.model_type}
</span>
)}
{collection.model_name && (
<span className="inline-flex items-center rounded-full bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 truncate max-w-full">
{collection.model_name}
</span>
)}
</div>
)}
</div>

{isExpanded && (
<div className="border-t border-gray-100 px-6 py-4 bg-gray-50">
{isDetailLoading ? (
<div className="flex items-center text-sm text-gray-500">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-gray-600 mr-2"></div>
Loading details...
</div>
) : detail ? (
<div>
<p className="text-sm font-medium text-gray-700 mb-2">
Metadata Fields
</p>
{detail.metadata_fields.length > 0 ? (
<div className="flex flex-wrap gap-1">
{detail.metadata_fields.map((field) => (
<span
key={field}
className="inline-flex items-center rounded bg-white px-2 py-0.5 text-xs text-gray-700 border border-gray-200"
>
{field}
</span>
))}
</div>
) : (
<p className="text-sm text-gray-400">No metadata fields</p>
)}
</div>
) : null}
</div>
)}

<div className="border-t border-gray-100 px-6 py-3 flex items-center justify-between">
<div className="flex items-center space-x-3">
<button
onClick={() => navigate(`/plot?collection=${collection.name}`)}
className="text-sm font-medium text-blue-600 hover:text-blue-800 transition-colors"
>
Visualize
</button>
<span className="text-gray-300">|</span>
<button
onClick={() => setIsExpanded(!isExpanded)}
className="text-sm font-medium text-gray-600 hover:text-gray-800 transition-colors"
>
{isExpanded ? 'Collapse' : 'Inspect'}
</button>
</div>
<button
onClick={handleDelete}
className="text-sm font-medium text-red-600 hover:text-red-800 transition-colors"
disabled={deleteMutation.isPending}
>
{deleteMutation.isPending ? 'Deleting...' : 'Delete'}
</button>
</div>
</div>
)
}
23 changes: 23 additions & 0 deletions frontend/src/components/home/CollectionGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { CollectionInfo } from '../../types'
import CollectionCard from './CollectionCard'

interface CollectionGridProps {
collections: CollectionInfo[]
}

export default function CollectionGrid({
collections,
}: CollectionGridProps) {
return (
<div>
<h2 className="text-xl font-semibold text-gray-900 mb-4">
Collections
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{collections.map((collection) => (
<CollectionCard key={collection.name} collection={collection} />
))}
</div>
</div>
)
}
32 changes: 32 additions & 0 deletions frontend/src/components/home/EmptyState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Link } from 'react-router-dom'

export default function EmptyState() {
return (
<div className="text-center py-20 bg-gray-50 rounded-lg border-2 border-dashed border-gray-300">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-16 w-16 mx-auto mb-4 text-gray-400 opacity-50"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1}
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
/>
</svg>
<p className="text-gray-500 text-xl">No collections yet</p>
<p className="text-gray-400 mt-2">
Get started by indexing your first CSV
</p>
<Link
to="/index"
className="mt-6 inline-flex items-center rounded-md border border-transparent bg-blue-600 px-6 py-3 text-base font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors"
>
Index New Data
</Link>
</div>
)
}
26 changes: 26 additions & 0 deletions frontend/src/components/home/StatsBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
interface StatsBarProps {
collectionCount: number
totalItems: number
}

export default function StatsBar({
collectionCount,
totalItems,
}: StatsBarProps) {
return (
<div className="grid grid-cols-2 gap-4 mb-8">
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<p className="text-sm font-medium text-gray-500">Collections</p>
<p className="text-3xl font-bold text-gray-900 mt-1">
{collectionCount.toLocaleString()}
</p>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<p className="text-sm font-medium text-gray-500">Total Items</p>
<p className="text-3xl font-bold text-gray-900 mt-1">
{totalItems.toLocaleString()}
</p>
</div>
</div>
)
}
Loading