# Indexed Document Card

Indexed Document Card [#indexed-document-card]

The **Indexed Document Card** is a consistent visual unit for a single document indexed into a VectorDB. It shows a truncated filename (full name in a native tooltip), the chunk count, an optional page count and file size, and a delete control that reveals on hover/focus and shows a spinner while the removal is in flight.

It is presentational: the page/chunk counts come from your app's ingest state — for example the length of [`useSemanticChunk()`](https://localmode.dev/docs/react) output plus a PDF page count — not a single hook, so the prop contract is explicit and any RAG / knowledge-base app fits.

**When to use it:** render the list of indexed files in a RAG sidebar or knowledge-base manager, with per-document delete. Pairs with RAG ingest (the `MultiStepPipelineTracker` from the conversation family).

Preview [#preview]

```tsx
'use client';

import * as React from 'react';
import { IndexedDocumentCard } from '@/components/indexed-document-card';

interface DemoDoc {
  id: string;
  filename: string;
  pageCount?: number;
  chunkCount: number;
  sizeBytes: number;
}

const INITIAL_DOCS: DemoDoc[] = [
  {
    id: '1',
    filename: 'annual-report-2024-final-with-appendices.pdf',
    pageCount: 42,
    chunkCount: 128,
    sizeBytes: 2_400_000,
  },
  {
    id: '2',
    filename: 'customers.csv',
    chunkCount: 64,
    sizeBytes: 312_000,
  },
  { id: '3', filename: 'meeting-notes.md', pageCount: 1, chunkCount: 6, sizeBytes: 4_200 },
];

/**
 * Demo for the IndexedDocumentCard component, used by the docs live preview.
 * Hover a card to reveal its delete control; deleting shows a brief loading
 * state before removing the row. Fully presentational — no model download.
 */
export default function IndexedDocumentCardDemo() {
  const [docs, setDocs] = React.useState<DemoDoc[]>(INITIAL_DOCS);
  const [deletingId, setDeletingId] = React.useState<string | null>(null);

  const remove = (id: string) => {
    setDeletingId(id);
    // Simulate the async VectorDB delete.
    setTimeout(() => {
      setDocs((prev) => prev.filter((d) => d.id !== id));
      setDeletingId(null);
    }, 700);
  };

  return (
    <div className="w-full max-w-md space-y-2">
      {docs.map((doc) => (
        <IndexedDocumentCard
          key={doc.id}
          filename={doc.filename}
          pageCount={doc.pageCount}
          chunkCount={doc.chunkCount}
          sizeBytes={doc.sizeBytes}
          deleting={deletingId === doc.id}
          onDelete={() => remove(doc.id)}
        />
      ))}
      {docs.length === 0 && (
        <p className="text-sm text-muted-foreground">No indexed documents.</p>
      )}
    </div>
  );
}
```

Installation [#installation]

```bash
npx shadcn@latest add @localmode/ui/data-documents/indexed-document-card
```

Dependencies [#dependencies]

* **Data source:** renders the plain filename, counts, and size props you pass — works with any backend; recommended LocalMode producer: `useSemanticChunk` output length for the chunk count (optional).
* `lucide-react` — the document / delete / spinner icons
* `clsx` + `tailwind-merge` — via the shared `cn()` util (installed automatically as a registry dependency)

Files installed [#files-installed]

* `indexed-document-card.tsx` — the component
* `lib/utils.ts` — the `cn()` helper (if not already present)

Props [#props]

**IndexedDocumentCard**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `filename` | `string` | — | **Required.** Display name of the indexed document (e.g. `"report.pdf"`). |
| `chunkCount` | `number` | — | **Required.** Number of chunks the document was split into and stored in the VectorDB. Comes from your ingest state / `useSemanticChunk` output length. |
| `pageCount` | `number` | — | Number of pages, if known (e.g. from PDF extraction). Omit to hide. |
| `sizeBytes` | `number` | — | Original file size in bytes. Omit to hide. |
| `onDelete` | `function` | — | Called when the user activates the delete control. Wire it to your VectorDB delete; while it runs, pass `deleting` to show the loading state. |
| `deleting` | `boolean` | `false` | Show the delete control in a loading state and disable it (the removal is in flight). |

Examples [#examples]

Render an indexed document [#render-an-indexed-document]

```tsx
import { IndexedDocumentCard } from '@/components/indexed-document-card';

<IndexedDocumentCard
  filename="annual-report-2024.pdf"
  pageCount={42}
  chunkCount={128}
  sizeBytes={2_400_000}
  onDelete={() => removeFromVectorDB(doc.id)}
/>;
```

Wire delete to your VectorDB [#wire-delete-to-your-vectordb]

```tsx
const [removingId, setRemovingId] = useState<string | null>(null);

{docs.map((doc) => (
  <IndexedDocumentCard
    key={doc.id}
    filename={doc.filename}
    chunkCount={doc.chunks}
    pageCount={doc.pages}
    sizeBytes={doc.size}
    deleting={removingId === doc.id}
    onDelete={async () => {
      setRemovingId(doc.id);
      await db.delete(doc.id);
      setRemovingId(null);
    }}
  />
))}
```

From `useSemanticChunk` output [#from-usesemanticchunk-output]

```tsx
const { data: chunks } = useSemanticChunk({ model });

<IndexedDocumentCard
  filename={file.name}
  chunkCount={chunks?.length ?? 0}
  sizeBytes={file.size}
/>;
```

Customization [#customization]

The card is styled with shadcn/ui CSS-variable utilities (`border-border`, `bg-card`, `bg-muted`, `text-muted-foreground`, `hover:bg-destructive/10`), so it inherits your theme. The delete control hides until the card is hovered or the button is focused (and stays visible while `deleting`) via the `group/doc` hover scope — adjust that in the copied file if you want the control always visible.

The truncated filename uses a native `title` tooltip to keep the component dependency-free. Because you own the file, you can extend `IndexedDocumentCardProps` with extra metadata (e.g. indexed-at time, source type) and render it in the stats line.