Skip to main content

Example: Jobs Plugin

A complex plugin demonstrating advanced features.

Overview

The Jobs plugin provides career management with:

  • Job feed with AI scoring
  • Application tracker (Kanban board)
  • User preferences for filtering
  • Email import from Gmail
  • Resume builder

Features Demonstrated

FeatureImplementation
Multiple viewsFeed, Kanban, Preferences
AI integrationJob scoring, resume tailoring
External APIsGmail import
Complex stateKanban drag-and-drop
Settings formsMulti-tab preferences

File Structure

apps/front/plugins/jobs/
├── manifest.ts
├── index.ts
└── components/
├── JobCard.tsx # Feed card
├── JobDetail.tsx # Job detail drawer
├── ApplicationTracker.tsx # Kanban board
├── ApplicationCard.tsx # Kanban card
├── JobPreferencesForm.tsx # 5-tab preferences
├── DashboardWidget.tsx # Dashboard widget
└── resume/
└── ResumePreview.tsx # Resume renderer

packages/plugins/installed/jobs/
├── handler.py # Main handlers
├── scorer.py # AI scoring logic
├── resume_tailor.py # Resume tailoring
└── email_extractor.py # Gmail job extraction

Manifest Highlights

// Multiple shell configurations
shells: {
feed: {
callable: 'list_jobs',
card: 'JobCard',
detail: 'JobDetail',
filters: ['status', 'score', 'location', 'date'],
},
tracker: {
callable: 'get_applications',
component: 'ApplicationTracker',
},
},

// Custom views
customViews: {
preferences: {
component: 'JobPreferencesForm',
title: 'Job Preferences',
},
resume: {
component: 'ResumePreview',
title: 'Resume Builder',
},
},

AI Scoring

Jobs are scored based on user preferences:

# packages/plugins/installed/jobs/scorer.py

def score_job(job: dict, preferences: dict) -> float:
"""Score a job 0-100 based on user preferences."""
score = 50 # Base score

# Hard filters (must match)
if not matches_location(job, preferences.get('locations', [])):
return 0

if not matches_salary(job, preferences.get('min_salary')):
return 0

# Soft factors (adjust score)
score += calculate_skill_match(job, preferences.get('skills', []))
score += calculate_company_fit(job, preferences.get('company_prefs', {}))

return min(100, max(0, score))

Kanban Implementation

// Drag-and-drop with react-beautiful-dnd
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';

const columns = ['applied', 'screening', 'interview', 'offer', 'rejected'];

function ApplicationTracker({ applications, onMove }) {
const handleDragEnd = (result) => {
if (!result.destination) return;

onMove(
result.draggableId,
result.destination.droppableId // new status
);
};

return (
<DragDropContext onDragEnd={handleDragEnd}>
{columns.map((status) => (
<Droppable key={status} droppableId={status}>
{/* Column content */}
</Droppable>
))}
</DragDropContext>
);
}

Key Takeaways

  1. Multiple views - Use different shells/customViews for different UIs
  2. AI integration - Call LLM APIs from backend handlers
  3. Complex state - Keep complex UI state in React, simple data in Weaviate
  4. External APIs - Handle OAuth in backend, expose clean APIs to frontend

Source Code

Full source available at:

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