Skip to main content

Example: Library Plugin

A simpler plugin demonstrating core patterns.

Overview

The Library plugin manages documents with:

  • Document feed with search
  • Document viewer
  • Collection organization
  • AI-powered reading assistance

Features Demonstrated

FeatureImplementation
Feed shellList with filters
Semantic searchWeaviate vector search
Detail viewDocument reader
CollectionsGrouped organization

File Structure

apps/front/plugins/library/
├── manifest.ts
├── index.ts
└── components/
├── DocumentCard.tsx # Feed card
├── DocumentDetail.tsx # Document viewer
├── DocumentViewer.tsx # Reader component
└── LibraryWidget.tsx # Dashboard widget

packages/plugins/installed/library/
├── handler.py # Main handlers
└── search.py # Semantic search

Manifest

// apps/front/plugins/library/manifest.ts
import type { PluginManifest } from '@/lib/plugins/types';

export const manifest: PluginManifest = {
id: 'library',
name: 'Library',
description: 'Document management and reading',
icon: 'Library',
version: '1.0.0',
author: 'DebaterHub',

nav: {
label: 'Library',
icon: 'Library',
views: [
{
id: 'feed',
label: 'Documents',
path: '/feed?plugin=library.feed',
icon: 'FileText',
},
],
},

shells: {
feed: {
callable: 'list_documents',
card: 'DocumentCard',
detail: 'DocumentDetail',
filters: ['collection', 'type', 'date'],
searchable: true,
},
},

widget: {
component: 'LibraryWidget',
defaultSize: 'small',
refreshInterval: 300000,
},
};

Simple Card Component

// apps/front/plugins/library/components/DocumentCard.tsx
import { Card, CardContent } from '@/components/ui/card';
import { FileText, Clock } from 'lucide-react';
import { formatDistanceToNow } from 'date-fns';

interface DocumentCardProps {
item: {
id: string;
content: {
title: string;
type: string;
preview: string;
};
metadata: {
created_at: string;
word_count: number;
};
};
onClick: () => void;
}

export function DocumentCard({ item, onClick }: DocumentCardProps) {
return (
<Card
className="cursor-pointer hover:shadow-md transition-shadow"
onClick={onClick}
>
<CardContent className="p-4">
<div className="flex items-start gap-3">
<FileText className="h-8 w-8 text-blue-500 shrink-0" />
<div className="min-w-0">
<h3 className="font-medium truncate">{item.content.title}</h3>
<p className="text-sm text-muted-foreground line-clamp-2 mt-1">
{item.content.preview}
</p>
<div className="flex items-center gap-2 mt-2 text-xs text-muted-foreground">
<Clock className="h-3 w-3" />
{formatDistanceToNow(new Date(item.metadata.created_at), {
addSuffix: true,
})}
<span></span>
<span>{item.metadata.word_count} words</span>
</div>
</div>
</div>
</CardContent>
</Card>
);
}

Backend Handlers

# packages/plugins/installed/library/handler.py
from typing import Optional, List, Dict
from services.weaviate.plugin_repository import PluginRepository

repo = PluginRepository()
PLUGIN_ID = "library"

def list_documents(
user_id: str,
filters: Optional[Dict] = None,
search_query: Optional[str] = None,
) -> List[Dict]:
"""List documents, optionally filtered or searched."""
if search_query:
return repo.semantic_search(
plugin_id=PLUGIN_ID,
user_id=user_id,
query=search_query,
data_type="document",
limit=20,
)

return repo.list_by_user(
plugin_id=PLUGIN_ID,
user_id=user_id,
data_type="document",
filters=filters,
sort_by="created_at",
sort_order="desc",
)

def get_document(user_id: str, document_id: str) -> Optional[Dict]:
"""Get a single document with full content."""
return repo.get(plugin_id=PLUGIN_ID, item_id=document_id)

def get_widget_data(user_id: str) -> Dict:
"""Get summary for dashboard widget."""
docs = repo.list_by_user(
plugin_id=PLUGIN_ID,
user_id=user_id,
data_type="document",
limit=100,
)
return {
"totalDocuments": len(docs),
"recentDocuments": docs[:3],
}

Key Takeaways

  1. Keep it simple - Start with basic feed + detail pattern
  2. Use semantic search - Weaviate handles embeddings automatically
  3. Minimal backend - Let the PluginRepository do the heavy lifting
  4. Card design - Keep cards scannable with key info visible

Source Code

Full source available at:

  • Frontend: apps/front/plugins/library/
  • Backend: packages/plugins/installed/library/