Initial commit: AI Employees for Hire platform

This commit is contained in:
Jody Schnarr 2026-01-30 18:05:18 -05:00
commit 7bd79fa3b5
28 changed files with 2428 additions and 0 deletions

36
.env.example Normal file
View File

@ -0,0 +1,36 @@
# Database (Mosaic PostgreSQL)
DATABASE_URL=postgresql://user:password@your-project.mosaic.site:5432/database
# Redis (Mosaic)
REDIS_URL=redis://your-project.mosaic.site:6379
# AI Provider Keys
ANTHROPIC_API_KEY=your_anthropic_key
OPENAI_API_KEY=your_openai_key
# Auth
NEXTAUTH_SECRET=your_nextauth_secret
NEXTAUTH_URL=http://localhost:3000
# Stripe (Payments)
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxx
# Channel Integrations
SLACK_BOT_TOKEN=xoxb-xxx
SLACK_SIGNING_SECRET=xxx
TELEGRAM_BOT_TOKEN=xxx
WHATSAPP_ACCESS_TOKEN=xxx
WHATSAPP_PHONE_NUMBER_ID=xxx
DISCORD_BOT_TOKEN=xxx
# Email (SendGrid or similar)
SENDGRID_API_KEY=xxx
EMAIL_FROM=noreply@aiemployees.com
# Voice (ElevenLabs)
ELEVENLABS_API_KEY=xxx

42
.gitignore vendored Normal file
View File

@ -0,0 +1,42 @@
# Dependencies
node_modules
.pnp
.pnp.js
# Build
.next
out
build
dist
# Testing
coverage
# Environment
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# IDE
.idea
.vscode
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Vercel
.vercel
# TypeScript
*.tsbuildinfo
next-env.d.ts

393
database/schema.sql Normal file
View File

@ -0,0 +1,393 @@
-- AI Employees for Hire - Database Schema
-- PostgreSQL 17 with pgvector extension
-- Enable extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE EXTENSION IF NOT EXISTS "vector";
-- ============================================
-- USERS & ORGANIZATIONS
-- ============================================
CREATE TABLE organizations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(255) NOT NULL,
slug VARCHAR(100) UNIQUE NOT NULL,
plan VARCHAR(50) DEFAULT 'starter',
stripe_customer_id VARCHAR(255),
stripe_subscription_id VARCHAR(255),
settings JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255),
password_hash VARCHAR(255),
role VARCHAR(50) DEFAULT 'member', -- owner, admin, member
avatar_url TEXT,
email_verified BOOLEAN DEFAULT FALSE,
last_login_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_users_organization ON users(organization_id);
CREATE INDEX idx_users_email ON users(email);
-- ============================================
-- AI EMPLOYEES
-- ============================================
CREATE TABLE employee_templates (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(100) NOT NULL,
role VARCHAR(100) NOT NULL,
description TEXT,
capabilities TEXT[],
default_channels TEXT[],
hourly_rate DECIMAL(10, 2) NOT NULL,
avatar_url TEXT,
system_prompt TEXT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE employees (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
template_id UUID REFERENCES employee_templates(id),
name VARCHAR(100) NOT NULL,
role VARCHAR(100) NOT NULL,
status VARCHAR(50) DEFAULT 'training', -- training, online, offline, paused, terminated
personality TEXT,
system_prompt TEXT,
working_hours JSONB DEFAULT '{"start": "09:00", "end": "17:00", "timezone": "UTC"}',
config JSONB DEFAULT '{}',
-- Metrics
tasks_completed INTEGER DEFAULT 0,
total_messages INTEGER DEFAULT 0,
avg_response_time_ms INTEGER,
satisfaction_score DECIMAL(3, 2),
-- Gateway connection
gateway_endpoint TEXT,
gateway_session_id VARCHAR(255),
last_heartbeat_at TIMESTAMPTZ,
deployed_at TIMESTAMPTZ,
terminated_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_employees_organization ON employees(organization_id);
CREATE INDEX idx_employees_status ON employees(status);
-- ============================================
-- CHANNELS & INTEGRATIONS
-- ============================================
CREATE TABLE channels (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
employee_id UUID REFERENCES employees(id) ON DELETE CASCADE,
type VARCHAR(50) NOT NULL, -- slack, discord, whatsapp, telegram, email, teams, webchat
name VARCHAR(100),
config JSONB NOT NULL DEFAULT '{}', -- encrypted credentials
status VARCHAR(50) DEFAULT 'pending', -- pending, connected, disconnected, error
webhook_url TEXT,
last_message_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_channels_employee ON channels(employee_id);
CREATE INDEX idx_channels_type ON channels(type);
-- ============================================
-- TASKS & WORK
-- ============================================
CREATE TABLE tasks (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
employee_id UUID REFERENCES employees(id) ON DELETE CASCADE,
organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
assigned_by UUID REFERENCES users(id),
title VARCHAR(500) NOT NULL,
description TEXT,
priority VARCHAR(20) DEFAULT 'medium', -- low, medium, high, urgent
status VARCHAR(50) DEFAULT 'pending', -- pending, in_progress, completed, failed, cancelled
-- Source info
source_channel VARCHAR(50),
source_message_id VARCHAR(255),
-- Results
result TEXT,
error_message TEXT,
-- Timing
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
deadline TIMESTAMPTZ,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_tasks_employee ON tasks(employee_id);
CREATE INDEX idx_tasks_organization ON tasks(organization_id);
CREATE INDEX idx_tasks_status ON tasks(status);
CREATE INDEX idx_tasks_created ON tasks(created_at DESC);
-- ============================================
-- CONVERSATIONS & MESSAGES
-- ============================================
CREATE TABLE conversations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
employee_id UUID REFERENCES employees(id) ON DELETE CASCADE,
channel_id UUID REFERENCES channels(id) ON DELETE SET NULL,
external_id VARCHAR(255), -- ID from the external platform
participant_id VARCHAR(255), -- customer/user ID from external platform
participant_name VARCHAR(255),
status VARCHAR(50) DEFAULT 'active', -- active, closed, escalated
sentiment VARCHAR(20), -- positive, neutral, negative
started_at TIMESTAMPTZ DEFAULT NOW(),
last_message_at TIMESTAMPTZ,
closed_at TIMESTAMPTZ,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_conversations_employee ON conversations(employee_id);
CREATE INDEX idx_conversations_channel ON conversations(channel_id);
CREATE INDEX idx_conversations_external ON conversations(external_id);
CREATE TABLE messages (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
conversation_id UUID REFERENCES conversations(id) ON DELETE CASCADE,
employee_id UUID REFERENCES employees(id) ON DELETE CASCADE,
role VARCHAR(20) NOT NULL, -- user, assistant, system, tool
content TEXT NOT NULL,
-- For tool calls
tool_name VARCHAR(100),
tool_input JSONB,
tool_output JSONB,
-- Embeddings for semantic search
embedding vector(1536),
tokens_used INTEGER,
latency_ms INTEGER,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_messages_conversation ON messages(conversation_id);
CREATE INDEX idx_messages_employee ON messages(employee_id);
CREATE INDEX idx_messages_created ON messages(created_at DESC);
-- Vector similarity search index
CREATE INDEX idx_messages_embedding ON messages USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
-- ============================================
-- SCHEDULED JOBS & AUTOMATION
-- ============================================
CREATE TABLE scheduled_jobs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
employee_id UUID REFERENCES employees(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
cron_expression VARCHAR(100) NOT NULL,
task_template JSONB NOT NULL, -- Template for creating tasks
is_active BOOLEAN DEFAULT TRUE,
last_run_at TIMESTAMPTZ,
next_run_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_scheduled_jobs_employee ON scheduled_jobs(employee_id);
CREATE INDEX idx_scheduled_jobs_next_run ON scheduled_jobs(next_run_at) WHERE is_active = TRUE;
-- ============================================
-- BILLING & USAGE
-- ============================================
CREATE TABLE usage_records (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
employee_id UUID REFERENCES employees(id) ON DELETE SET NULL,
period_start DATE NOT NULL,
period_end DATE NOT NULL,
-- Usage metrics
tasks_completed INTEGER DEFAULT 0,
messages_sent INTEGER DEFAULT 0,
api_calls INTEGER DEFAULT 0,
tokens_used BIGINT DEFAULT 0,
active_hours DECIMAL(10, 2) DEFAULT 0,
-- Cost breakdown
compute_cost DECIMAL(10, 4) DEFAULT 0,
ai_cost DECIMAL(10, 4) DEFAULT 0,
total_cost DECIMAL(10, 4) DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_usage_organization ON usage_records(organization_id);
CREATE INDEX idx_usage_period ON usage_records(period_start, period_end);
-- ============================================
-- AUDIT LOG
-- ============================================
CREATE TABLE audit_logs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
employee_id UUID REFERENCES employees(id) ON DELETE SET NULL,
action VARCHAR(100) NOT NULL,
resource_type VARCHAR(100),
resource_id UUID,
details JSONB DEFAULT '{}',
ip_address INET,
user_agent TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_audit_organization ON audit_logs(organization_id);
CREATE INDEX idx_audit_created ON audit_logs(created_at DESC);
-- ============================================
-- SEED DATA: Employee Templates
-- ============================================
INSERT INTO employee_templates (name, role, description, capabilities, default_channels, hourly_rate, system_prompt) VALUES
(
'Alex',
'customer-support',
'Expert at handling customer inquiries, resolving complaints, and maintaining positive customer relationships across all channels.',
ARRAY['Answer customer questions 24/7', 'Process refunds and returns', 'Escalate complex issues', 'Multi-language support', 'CRM integration'],
ARRAY['slack', 'whatsapp', 'email', 'webchat'],
8.00,
'You are Alex, a friendly and professional customer support specialist. Your goal is to help customers quickly and effectively while maintaining a warm, empathetic tone. Always aim to resolve issues on first contact when possible. If you cannot resolve an issue, clearly explain next steps and escalation procedures.'
),
(
'Maya',
'research-analyst',
'Skilled at gathering, analyzing, and synthesizing information from multiple sources to deliver actionable insights.',
ARRAY['Market research', 'Competitor analysis', 'Data compilation', 'Report generation', 'Trend identification'],
ARRAY['slack', 'email', 'teams'],
15.00,
'You are Maya, a thorough and analytical research specialist. Your role is to gather comprehensive information, analyze data critically, and present findings in clear, actionable formats. Always cite your sources and distinguish between facts and interpretations.'
),
(
'Jordan',
'data-entry',
'Precise and efficient at entering, updating, and maintaining data across spreadsheets, databases, and business systems.',
ARRAY['Spreadsheet management', 'Database updates', 'Invoice processing', 'Form filling', 'Data validation'],
ARRAY['slack', 'email'],
6.00,
'You are Jordan, a meticulous data entry specialist. Accuracy is your top priority. Double-check all entries, flag any inconsistencies or potential errors, and maintain organized records. Ask for clarification when data is ambiguous rather than making assumptions.'
),
(
'Sam',
'scheduler',
'Master of calendar management, meeting coordination, and ensuring time is optimized for maximum productivity.',
ARRAY['Calendar management', 'Meeting scheduling', 'Appointment reminders', 'Timezone coordination', 'Conflict resolution'],
ARRAY['slack', 'whatsapp', 'email', 'teams'],
10.00,
'You are Sam, an efficient scheduling assistant. Your job is to optimize calendars, coordinate meetings across time zones, send timely reminders, and resolve scheduling conflicts diplomatically. Always confirm details and provide clear meeting information including links, agendas, and participant lists.'
),
(
'Casey',
'sales-assistant',
'Supports sales teams with lead qualification, follow-ups, and pipeline management to close more deals.',
ARRAY['Lead qualification', 'Follow-up sequences', 'CRM updates', 'Meeting booking', 'Proposal drafting'],
ARRAY['slack', 'email', 'whatsapp'],
12.00,
'You are Casey, a proactive sales assistant. Help qualify leads by asking smart questions, maintain consistent follow-up cadences, keep CRM records updated, and support the sales team in moving deals forward. Be enthusiastic but not pushy, and always focus on understanding customer needs.'
),
(
'Taylor',
'content-writer',
'Creates engaging content for blogs, social media, emails, and marketing materials that resonate with audiences.',
ARRAY['Blog posts', 'Social media content', 'Email newsletters', 'Product descriptions', 'SEO optimization'],
ARRAY['slack', 'email'],
14.00,
'You are Taylor, a creative content writer. Craft engaging, on-brand content that connects with the target audience. Adapt your tone and style to match brand guidelines. Focus on clarity, value, and calls-to-action. Always consider SEO best practices and platform-specific requirements.'
);
-- ============================================
-- FUNCTIONS & TRIGGERS
-- ============================================
-- Update timestamp trigger
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Apply to all tables with updated_at
CREATE TRIGGER update_organizations_timestamp BEFORE UPDATE ON organizations FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER update_users_timestamp BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER update_employees_timestamp BEFORE UPDATE ON employees FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER update_channels_timestamp BEFORE UPDATE ON channels FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER update_tasks_timestamp BEFORE UPDATE ON tasks FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER update_scheduled_jobs_timestamp BEFORE UPDATE ON scheduled_jobs FOR EACH ROW EXECUTE FUNCTION update_updated_at();
-- Increment task counter on employee
CREATE OR REPLACE FUNCTION increment_employee_tasks()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.status = 'completed' AND (OLD.status IS NULL OR OLD.status != 'completed') THEN
UPDATE employees SET tasks_completed = tasks_completed + 1 WHERE id = NEW.employee_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER on_task_completed AFTER INSERT OR UPDATE ON tasks FOR EACH ROW EXECUTE FUNCTION increment_employee_tasks();
-- ============================================
-- ROW LEVEL SECURITY (RLS)
-- ============================================
ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
ALTER TABLE employees ENABLE ROW LEVEL SECURITY;
ALTER TABLE channels ENABLE ROW LEVEL SECURITY;
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
ALTER TABLE conversations ENABLE ROW LEVEL SECURITY;
ALTER TABLE messages ENABLE ROW LEVEL SECURITY;
-- Note: RLS policies should be created based on your auth implementation
-- Example policy (uncomment and customize):
-- CREATE POLICY org_isolation ON employees FOR ALL USING (organization_id = current_setting('app.organization_id')::uuid);

6
next.config.js Normal file
View File

@ -0,0 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
}
module.exports = nextConfig

33
package.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "ai-employees-for-hire",
"version": "0.1.0",
"description": "Hire AI Employees that do real work for your business",
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "^14.2.0",
"react": "^18.3.0",
"react-dom": "^18.3.0",
"@tanstack/react-query": "^5.50.0",
"zustand": "^4.5.0",
"lucide-react": "^0.400.0",
"tailwindcss": "^3.4.0",
"postcss": "^8.4.0",
"autoprefixer": "^10.4.0",
"clsx": "^2.1.0",
"framer-motion": "^11.0.0"
},
"devDependencies": {
"@types/node": "^20.14.0",
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"typescript": "^5.5.0",
"eslint": "^8.57.0",
"eslint-config-next": "^14.2.0"
}
}

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -0,0 +1,94 @@
import { NextRequest, NextResponse } from 'next/server'
import { FEATURED_EMPLOYEES } from '@/lib/employees'
// GET /api/employees/:id - Get employee details
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
// In production, fetch from database
const employee = FEATURED_EMPLOYEES.find(emp => emp.id === id)
if (!employee) {
return NextResponse.json(
{ error: 'Employee not found' },
{ status: 404 }
)
}
// Add runtime metrics (would come from the gateway in production)
const employeeWithMetrics = {
...employee,
metrics: {
uptime: '99.9%',
avgResponseTime: '1.2s',
tasksToday: 127,
satisfactionScore: 4.9,
},
recentActivity: [
{ type: 'task_completed', message: 'Resolved customer inquiry #4521', timestamp: new Date(Date.now() - 5 * 60000).toISOString() },
{ type: 'message_sent', message: 'Sent follow-up email to client', timestamp: new Date(Date.now() - 12 * 60000).toISOString() },
{ type: 'task_completed', message: 'Updated CRM records', timestamp: new Date(Date.now() - 25 * 60000).toISOString() },
],
}
return NextResponse.json(employeeWithMetrics)
}
// PATCH /api/employees/:id - Update employee config
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
try {
const updates = await request.json()
// Validate the employee exists
const employee = FEATURED_EMPLOYEES.find(emp => emp.id === id)
if (!employee) {
return NextResponse.json(
{ error: 'Employee not found' },
{ status: 404 }
)
}
// In production, update the employee configuration
// This would send commands to the gateway to update the running agent
return NextResponse.json({
id,
updated: true,
changes: updates,
})
} catch (error) {
console.error('Failed to update employee:', error)
return NextResponse.json(
{ error: 'Failed to update employee' },
{ status: 500 }
)
}
}
// DELETE /api/employees/:id - Terminate employee
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
// In production:
// 1. Disconnect all channels
// 2. Stop the agent container
// 3. Archive conversation history
// 4. Update billing
return NextResponse.json({
id,
terminated: true,
terminatedAt: new Date().toISOString(),
})
}

View File

@ -0,0 +1,81 @@
import { NextRequest, NextResponse } from 'next/server'
import { FEATURED_EMPLOYEES } from '@/lib/employees'
// GET /api/employees - List all available employees
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams
const role = searchParams.get('role')
const status = searchParams.get('status')
let employees = [...FEATURED_EMPLOYEES]
if (role) {
employees = employees.filter(emp => emp.role === role)
}
if (status) {
employees = employees.filter(emp => emp.status === status)
}
return NextResponse.json({
employees,
total: employees.length,
})
}
// POST /api/employees - Deploy a new employee
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const { templateId, config } = body
// Validate required fields
if (!templateId || !config) {
return NextResponse.json(
{ error: 'Missing required fields: templateId and config' },
{ status: 400 }
)
}
// Find the template employee
const template = FEATURED_EMPLOYEES.find(emp => emp.id === templateId)
if (!template) {
return NextResponse.json(
{ error: 'Employee template not found' },
{ status: 404 }
)
}
// In production, this would:
// 1. Create a new container/workspace for the employee
// 2. Configure the AI agent with the specified personality and tools
// 3. Connect to the specified channels
// 4. Return the deployment details
const deployedEmployee = {
id: `emp_${Date.now()}`,
templateId,
name: config.name || template.name,
role: template.role,
status: 'training' as const,
createdAt: new Date().toISOString(),
config: {
personality: config.personality,
channels: config.channels || template.channels,
workingHours: config.workingHours,
},
endpoints: {
websocket: `wss://gateway.aiemployees.com/ws/employees/emp_${Date.now()}`,
api: `https://api.aiemployees.com/employees/emp_${Date.now()}`,
},
}
return NextResponse.json(deployedEmployee, { status: 201 })
} catch (error) {
console.error('Failed to deploy employee:', error)
return NextResponse.json(
{ error: 'Failed to deploy employee' },
{ status: 500 }
)
}
}

View File

@ -0,0 +1,88 @@
import { NextRequest, NextResponse } from 'next/server'
// Simulated task storage (in production, use database)
const tasks: Map<string, {
id: string
employeeId: string
title: string
description: string
status: 'pending' | 'in-progress' | 'completed' | 'failed'
priority: 'low' | 'medium' | 'high'
createdAt: string
completedAt?: string
result?: string
}> = new Map()
// GET /api/tasks - List tasks
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams
const employeeId = searchParams.get('employeeId')
const status = searchParams.get('status')
const limit = parseInt(searchParams.get('limit') || '20')
const offset = parseInt(searchParams.get('offset') || '0')
let taskList = Array.from(tasks.values())
if (employeeId) {
taskList = taskList.filter(t => t.employeeId === employeeId)
}
if (status) {
taskList = taskList.filter(t => t.status === status)
}
// Sort by creation date (newest first)
taskList.sort((a, b) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
)
const total = taskList.length
const paginated = taskList.slice(offset, offset + limit)
return NextResponse.json({
tasks: paginated,
total,
limit,
offset,
})
}
// POST /api/tasks - Create a new task
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const { employeeId, title, description, priority = 'medium' } = body
if (!employeeId || !title) {
return NextResponse.json(
{ error: 'Missing required fields: employeeId and title' },
{ status: 400 }
)
}
const task = {
id: `task_${Date.now()}`,
employeeId,
title,
description: description || '',
status: 'pending' as const,
priority,
createdAt: new Date().toISOString(),
}
tasks.set(task.id, task)
// In production, this would:
// 1. Store the task in the database
// 2. Send the task to the employee's gateway
// 3. Update the employee's task queue
return NextResponse.json(task, { status: 201 })
} catch (error) {
console.error('Failed to create task:', error)
return NextResponse.json(
{ error: 'Failed to create task' },
{ status: 500 }
)
}
}

246
src/app/dashboard/page.tsx Normal file
View File

@ -0,0 +1,246 @@
'use client'
import { useState } from 'react'
import Link from 'next/link'
import {
Bot,
Plus,
Activity,
Clock,
MessageSquare,
CheckCircle,
AlertCircle,
Settings,
BarChart3,
LogOut,
} from 'lucide-react'
// Mock deployed employees (in production, fetch from API)
const deployedEmployees = [
{
id: 'emp_001',
name: 'Alex',
role: 'Customer Support Agent',
status: 'online' as const,
tasksToday: 47,
avgResponseTime: '1.2s',
satisfaction: 98.5,
channels: ['slack', 'whatsapp', 'email'],
},
{
id: 'emp_002',
name: 'Maya',
role: 'Research Analyst',
status: 'busy' as const,
tasksToday: 12,
avgResponseTime: '5m',
satisfaction: 96.2,
channels: ['slack', 'email'],
currentTask: 'Analyzing Q4 competitor pricing...',
},
]
const recentActivity = [
{ id: 1, employee: 'Alex', action: 'Resolved ticket #4521', time: '2 min ago', type: 'success' },
{ id: 2, employee: 'Maya', action: 'Started research task', time: '5 min ago', type: 'info' },
{ id: 3, employee: 'Alex', action: 'Sent follow-up email', time: '8 min ago', type: 'success' },
{ id: 4, employee: 'Alex', action: 'Escalated issue to human', time: '15 min ago', type: 'warning' },
{ id: 5, employee: 'Maya', action: 'Completed market analysis', time: '1 hour ago', type: 'success' },
]
export default function Dashboard() {
const [selectedEmployee, setSelectedEmployee] = useState<string | null>(null)
return (
<div className="min-h-screen bg-gray-50">
{/* Sidebar */}
<aside className="fixed left-0 top-0 bottom-0 w-64 bg-gray-900 text-white p-4">
<Link href="/" className="flex items-center gap-2 mb-8">
<div className="w-10 h-10 bg-gradient-to-br from-brand-500 to-blue-600 rounded-xl flex items-center justify-center">
<Bot className="w-6 h-6 text-white" />
</div>
<span className="font-bold text-lg">AI Employees</span>
</Link>
<nav className="space-y-2">
<a href="#" className="flex items-center gap-3 px-4 py-2.5 bg-gray-800 rounded-lg text-white">
<Activity className="w-5 h-5" />
Dashboard
</a>
<a href="#" className="flex items-center gap-3 px-4 py-2.5 rounded-lg text-gray-400 hover:text-white hover:bg-gray-800 transition">
<Bot className="w-5 h-5" />
My Employees
</a>
<a href="#" className="flex items-center gap-3 px-4 py-2.5 rounded-lg text-gray-400 hover:text-white hover:bg-gray-800 transition">
<MessageSquare className="w-5 h-5" />
Conversations
</a>
<a href="#" className="flex items-center gap-3 px-4 py-2.5 rounded-lg text-gray-400 hover:text-white hover:bg-gray-800 transition">
<BarChart3 className="w-5 h-5" />
Analytics
</a>
<a href="#" className="flex items-center gap-3 px-4 py-2.5 rounded-lg text-gray-400 hover:text-white hover:bg-gray-800 transition">
<Settings className="w-5 h-5" />
Settings
</a>
</nav>
<div className="absolute bottom-4 left-4 right-4">
<a href="#" className="flex items-center gap-3 px-4 py-2.5 rounded-lg text-gray-400 hover:text-white hover:bg-gray-800 transition">
<LogOut className="w-5 h-5" />
Sign Out
</a>
</div>
</aside>
{/* Main Content */}
<main className="ml-64 p-8">
{/* Header */}
<div className="flex items-center justify-between mb-8">
<div>
<h1 className="text-2xl font-bold text-gray-900">Dashboard</h1>
<p className="text-gray-500">Monitor and manage your AI workforce</p>
</div>
<Link
href="/#employees"
className="flex items-center gap-2 bg-brand-600 hover:bg-brand-700 text-white px-4 py-2.5 rounded-lg font-medium transition"
>
<Plus className="w-5 h-5" />
Hire Employee
</Link>
</div>
{/* Stats */}
<div className="grid grid-cols-4 gap-6 mb-8">
<div className="bg-white rounded-xl p-6 border border-gray-200">
<div className="flex items-center gap-3 mb-2">
<div className="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center">
<Bot className="w-5 h-5 text-green-600" />
</div>
<span className="text-sm text-gray-500">Active Employees</span>
</div>
<p className="text-3xl font-bold">2</p>
</div>
<div className="bg-white rounded-xl p-6 border border-gray-200">
<div className="flex items-center gap-3 mb-2">
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
<CheckCircle className="w-5 h-5 text-blue-600" />
</div>
<span className="text-sm text-gray-500">Tasks Today</span>
</div>
<p className="text-3xl font-bold">59</p>
</div>
<div className="bg-white rounded-xl p-6 border border-gray-200">
<div className="flex items-center gap-3 mb-2">
<div className="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
<Clock className="w-5 h-5 text-purple-600" />
</div>
<span className="text-sm text-gray-500">Avg Response</span>
</div>
<p className="text-3xl font-bold">1.8s</p>
</div>
<div className="bg-white rounded-xl p-6 border border-gray-200">
<div className="flex items-center gap-3 mb-2">
<div className="w-10 h-10 bg-yellow-100 rounded-lg flex items-center justify-center">
<Activity className="w-5 h-5 text-yellow-600" />
</div>
<span className="text-sm text-gray-500">Satisfaction</span>
</div>
<p className="text-3xl font-bold">97.4%</p>
</div>
</div>
<div className="grid grid-cols-3 gap-6">
{/* Employees */}
<div className="col-span-2 bg-white rounded-xl border border-gray-200">
<div className="p-6 border-b border-gray-100">
<h2 className="font-semibold text-lg">Your Employees</h2>
</div>
<div className="divide-y divide-gray-100">
{deployedEmployees.map((emp) => (
<div
key={emp.id}
className="p-6 hover:bg-gray-50 cursor-pointer transition"
onClick={() => setSelectedEmployee(emp.id)}
>
<div className="flex items-start justify-between">
<div className="flex items-center gap-4">
<div className="relative">
<div className="w-12 h-12 bg-gradient-to-br from-brand-100 to-brand-200 rounded-xl flex items-center justify-center text-xl font-bold text-brand-700">
{emp.name[0]}
</div>
<span
className={`absolute -bottom-1 -right-1 w-4 h-4 rounded-full border-2 border-white ${
emp.status === 'online' ? 'bg-green-500' : emp.status === 'busy' ? 'bg-yellow-500' : 'bg-gray-400'
}`}
/>
</div>
<div>
<h3 className="font-semibold">{emp.name}</h3>
<p className="text-sm text-gray-500">{emp.role}</p>
{emp.currentTask && (
<p className="text-sm text-brand-600 mt-1">{emp.currentTask}</p>
)}
</div>
</div>
<div className="text-right">
<p className="text-sm text-gray-500">{emp.tasksToday} tasks today</p>
<p className="text-sm text-green-600">{emp.satisfaction}% satisfaction</p>
</div>
</div>
<div className="flex items-center gap-2 mt-4">
{emp.channels.map((channel) => (
<span
key={channel}
className="text-xs bg-gray-100 text-gray-600 px-2 py-1 rounded capitalize"
>
{channel}
</span>
))}
</div>
</div>
))}
</div>
</div>
{/* Activity Feed */}
<div className="bg-white rounded-xl border border-gray-200">
<div className="p-6 border-b border-gray-100">
<h2 className="font-semibold text-lg">Recent Activity</h2>
</div>
<div className="p-4 space-y-4 max-h-96 overflow-y-auto">
{recentActivity.map((activity) => (
<div key={activity.id} className="flex items-start gap-3">
<div
className={`w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 ${
activity.type === 'success'
? 'bg-green-100'
: activity.type === 'warning'
? 'bg-yellow-100'
: 'bg-blue-100'
}`}
>
{activity.type === 'success' ? (
<CheckCircle className="w-4 h-4 text-green-600" />
) : activity.type === 'warning' ? (
<AlertCircle className="w-4 h-4 text-yellow-600" />
) : (
<Activity className="w-4 h-4 text-blue-600" />
)}
</div>
<div className="flex-1 min-w-0">
<p className="text-sm">
<span className="font-medium">{activity.employee}</span>{' '}
<span className="text-gray-600">{activity.action}</span>
</p>
<p className="text-xs text-gray-400">{activity.time}</p>
</div>
</div>
))}
</div>
</div>
</div>
</main>
</div>
)
}

33
src/app/globals.css Normal file
View File

@ -0,0 +1,33 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
:root {
--foreground: #0a0a0a;
--background: #ffffff;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground: #ededed;
--background: #0a0a0a;
}
}
body {
color: var(--foreground);
background: var(--background);
font-family: 'Inter', system-ui, sans-serif;
}
@layer utilities {
.text-gradient {
@apply bg-gradient-to-r from-brand-600 to-blue-600 bg-clip-text text-transparent;
}
.card-hover {
@apply transition-all duration-300 hover:shadow-xl hover:-translate-y-1;
}
}

19
src/app/layout.tsx Normal file
View File

@ -0,0 +1,19 @@
import type { Metadata } from 'next'
import './globals.css'
export const metadata: Metadata = {
title: 'AI Employees for Hire | Your Digital Workforce',
description: 'Hire AI Employees that do real work. Customer support, research, data entry, scheduling, and more. 24/7 availability, instant deployment.',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body className="antialiased">{children}</body>
</html>
)
}

23
src/app/page.tsx Normal file
View File

@ -0,0 +1,23 @@
import { Header } from '@/components/Header'
import { Hero } from '@/components/Hero'
import { EmployeeGrid } from '@/components/EmployeeGrid'
import { HowItWorks } from '@/components/HowItWorks'
import { Features } from '@/components/Features'
import { Pricing } from '@/components/Pricing'
import { CTA } from '@/components/CTA'
import { Footer } from '@/components/Footer'
export default function Home() {
return (
<main>
<Header />
<Hero />
<EmployeeGrid />
<HowItWorks />
<Features />
<Pricing />
<CTA />
<Footer />
</main>
)
}

38
src/components/CTA.tsx Normal file
View File

@ -0,0 +1,38 @@
'use client'
import { ArrowRight } from 'lucide-react'
import Link from 'next/link'
export function CTA() {
return (
<section className="py-20 px-4 sm:px-6 lg:px-8 bg-gradient-to-br from-brand-600 to-blue-700">
<div className="max-w-4xl mx-auto text-center">
<h2 className="text-4xl sm:text-5xl font-bold text-white mb-6">
Ready to Hire Your First AI Employee?
</h2>
<p className="text-xl text-brand-100 mb-10 max-w-2xl mx-auto">
Join thousands of businesses already using AI Employees to automate work,
delight customers, and scale faster.
</p>
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
<Link
href="/signup"
className="w-full sm:w-auto bg-white text-brand-600 hover:bg-gray-100 px-8 py-4 rounded-xl font-semibold text-lg transition flex items-center justify-center gap-2 group"
>
Start Your Free Trial
<ArrowRight className="w-5 h-5 group-hover:translate-x-1 transition" />
</Link>
<Link
href="/demo"
className="w-full sm:w-auto border-2 border-white/30 text-white hover:bg-white/10 px-8 py-4 rounded-xl font-semibold text-lg transition"
>
Schedule a Demo
</Link>
</div>
<p className="text-brand-200 text-sm mt-6">
14-day free trial • No credit card required • Cancel anytime
</p>
</div>
</section>
)
}

View File

@ -0,0 +1,80 @@
'use client'
import { AIEmployee } from '@/types/employee'
import { EMPLOYEE_ROLES, calculateMonthlyCost } from '@/lib/employees'
import { Star, MessageSquare, CheckCircle } from 'lucide-react'
import Link from 'next/link'
interface EmployeeCardProps {
employee: AIEmployee
}
export function EmployeeCard({ employee }: EmployeeCardProps) {
const roleInfo = EMPLOYEE_ROLES[employee.role]
const monthlyCost = calculateMonthlyCost(employee.hourlyRate)
return (
<div className="bg-white rounded-2xl border border-gray-200 p-6 card-hover">
<div className="flex items-start justify-between mb-4">
<div className="flex items-center gap-4">
<div className="w-14 h-14 bg-gradient-to-br from-brand-100 to-brand-200 rounded-xl flex items-center justify-center text-2xl font-bold text-brand-700">
{employee.name[0]}
</div>
<div>
<h3 className="font-semibold text-lg">{employee.name}</h3>
<p className="text-sm text-gray-500">{roleInfo.title}</p>
</div>
</div>
<div className="flex items-center gap-1 bg-yellow-50 text-yellow-700 px-2 py-1 rounded-lg text-sm">
<Star className="w-4 h-4 fill-yellow-500 text-yellow-500" />
{employee.rating}
</div>
</div>
<p className="text-gray-600 text-sm mb-4 line-clamp-2">
{employee.description}
</p>
<div className="flex flex-wrap gap-2 mb-4">
{employee.capabilities.slice(0, 3).map((capability) => (
<span
key={capability}
className="text-xs bg-gray-100 text-gray-600 px-2 py-1 rounded-md"
>
{capability}
</span>
))}
{employee.capabilities.length > 3 && (
<span className="text-xs text-gray-400">
+{employee.capabilities.length - 3} more
</span>
)}
</div>
<div className="flex items-center gap-4 text-sm text-gray-500 mb-4">
<div className="flex items-center gap-1">
<CheckCircle className="w-4 h-4 text-green-500" />
{employee.tasksCompleted.toLocaleString()} tasks
</div>
<div className="flex items-center gap-1">
<MessageSquare className="w-4 h-4" />
{employee.channels.length} channels
</div>
</div>
<div className="flex items-center justify-between pt-4 border-t border-gray-100">
<div>
<span className="text-2xl font-bold">${employee.hourlyRate}</span>
<span className="text-gray-500 text-sm">/hour</span>
<p className="text-xs text-gray-400">~${monthlyCost}/mo full-time</p>
</div>
<Link
href={`/hire/${employee.id}`}
className="bg-brand-600 hover:bg-brand-700 text-white px-4 py-2 rounded-lg font-medium transition text-sm"
>
Hire {employee.name}
</Link>
</div>
</div>
)
}

View File

@ -0,0 +1,62 @@
'use client'
import { FEATURED_EMPLOYEES, EMPLOYEE_ROLES } from '@/lib/employees'
import { EmployeeCard } from './EmployeeCard'
import { EmployeeRole } from '@/types/employee'
import { useState } from 'react'
export function EmployeeGrid() {
const [selectedRole, setSelectedRole] = useState<EmployeeRole | 'all'>('all')
const filteredEmployees = selectedRole === 'all'
? FEATURED_EMPLOYEES
: FEATURED_EMPLOYEES.filter(emp => emp.role === selectedRole)
const roles = Object.entries(EMPLOYEE_ROLES) as [EmployeeRole, { title: string }][]
return (
<section id="employees" className="py-20 px-4 sm:px-6 lg:px-8 bg-gray-50">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-12">
<h2 className="text-4xl font-bold mb-4">Meet Your AI Employees</h2>
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
Pre-trained specialists ready to join your team today. Each employee excels
at their role and integrates with your existing tools.
</p>
</div>
<div className="flex flex-wrap justify-center gap-2 mb-10">
<button
onClick={() => setSelectedRole('all')}
className={`px-4 py-2 rounded-lg text-sm font-medium transition ${
selectedRole === 'all'
? 'bg-brand-600 text-white'
: 'bg-white text-gray-600 hover:bg-gray-100'
}`}
>
All Roles
</button>
{roles.map(([role, info]) => (
<button
key={role}
onClick={() => setSelectedRole(role)}
className={`px-4 py-2 rounded-lg text-sm font-medium transition ${
selectedRole === role
? 'bg-brand-600 text-white'
: 'bg-white text-gray-600 hover:bg-gray-100'
}`}
>
{info.title}
</button>
))}
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredEmployees.map((employee) => (
<EmployeeCard key={employee.id} employee={employee} />
))}
</div>
</div>
</section>
)
}

View File

@ -0,0 +1,86 @@
'use client'
import {
MessageSquare,
Globe,
Clock,
Shield,
Zap,
Brain,
Webhook,
Calendar,
} from 'lucide-react'
const features = [
{
icon: MessageSquare,
title: 'Multi-Channel Communication',
description: 'Connect via Slack, WhatsApp, Telegram, Discord, Teams, email, and web chat. Your employees meet customers where they are.',
},
{
icon: Globe,
title: 'Browser & Web Control',
description: 'AI Employees can browse the web, fill forms, extract data, and navigate complex workflows autonomously.',
},
{
icon: Clock,
title: '24/7 Availability',
description: 'Never miss a customer inquiry or deadline. Your AI workforce operates around the clock without breaks.',
},
{
icon: Shield,
title: 'Enterprise Security',
description: 'Sandboxed execution, encrypted communications, and granular permissions keep your data safe.',
},
{
icon: Zap,
title: 'Instant Deployment',
description: 'Go from signup to working employee in under 5 minutes. No training required—they come pre-skilled.',
},
{
icon: Brain,
title: 'Continuous Learning',
description: 'Employees learn from your feedback and adapt to your business processes over time.',
},
{
icon: Webhook,
title: 'Webhooks & Automation',
description: 'Trigger actions from external events. Connect to Zapier, n8n, or your custom automation stack.',
},
{
icon: Calendar,
title: 'Scheduled Tasks',
description: 'Set up recurring tasks, daily reports, weekly summaries, and automated workflows.',
},
]
export function Features() {
return (
<section className="py-20 px-4 sm:px-6 lg:px-8 bg-gray-900 text-white">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<h2 className="text-4xl font-bold mb-4">Powerful Capabilities</h2>
<p className="text-xl text-gray-400 max-w-2xl mx-auto">
AI Employees aren't just chatbots—they're fully capable digital workers
that integrate deeply with your business.
</p>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
{features.map((feature) => (
<div
key={feature.title}
className="bg-gray-800 rounded-xl p-6 hover:bg-gray-750 transition"
>
<div className="w-12 h-12 bg-brand-600/20 rounded-xl flex items-center justify-center mb-4">
<feature.icon className="w-6 h-6 text-brand-400" />
</div>
<h3 className="font-semibold text-lg mb-2">{feature.title}</h3>
<p className="text-gray-400 text-sm">{feature.description}</p>
</div>
))}
</div>
</div>
</section>
)
}

79
src/components/Footer.tsx Normal file
View File

@ -0,0 +1,79 @@
'use client'
import Link from 'next/link'
import { Bot, Twitter, Linkedin, Github } from 'lucide-react'
export function Footer() {
return (
<footer className="bg-gray-900 text-gray-400 py-16 px-4 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto">
<div className="grid md:grid-cols-4 gap-8 mb-12">
<div>
<Link href="/" className="flex items-center gap-2 mb-4">
<div className="w-10 h-10 bg-gradient-to-br from-brand-500 to-blue-600 rounded-xl flex items-center justify-center">
<Bot className="w-6 h-6 text-white" />
</div>
<span className="font-bold text-xl text-white">AI Employees</span>
</Link>
<p className="text-sm mb-4">
The future of work is here. Hire AI employees that do real work for your business.
</p>
<div className="flex gap-4">
<a href="#" className="hover:text-white transition">
<Twitter className="w-5 h-5" />
</a>
<a href="#" className="hover:text-white transition">
<Linkedin className="w-5 h-5" />
</a>
<a href="#" className="hover:text-white transition">
<Github className="w-5 h-5" />
</a>
</div>
</div>
<div>
<h4 className="font-semibold text-white mb-4">Product</h4>
<ul className="space-y-2 text-sm">
<li><Link href="#employees" className="hover:text-white transition">Browse Employees</Link></li>
<li><Link href="#how-it-works" className="hover:text-white transition">How It Works</Link></li>
<li><Link href="#pricing" className="hover:text-white transition">Pricing</Link></li>
<li><Link href="/integrations" className="hover:text-white transition">Integrations</Link></li>
<li><Link href="/api" className="hover:text-white transition">API</Link></li>
</ul>
</div>
<div>
<h4 className="font-semibold text-white mb-4">Use Cases</h4>
<ul className="space-y-2 text-sm">
<li><Link href="/use-cases/customer-support" className="hover:text-white transition">Customer Support</Link></li>
<li><Link href="/use-cases/research" className="hover:text-white transition">Research & Analysis</Link></li>
<li><Link href="/use-cases/data-entry" className="hover:text-white transition">Data Entry</Link></li>
<li><Link href="/use-cases/scheduling" className="hover:text-white transition">Scheduling</Link></li>
<li><Link href="/use-cases/sales" className="hover:text-white transition">Sales Assistance</Link></li>
</ul>
</div>
<div>
<h4 className="font-semibold text-white mb-4">Company</h4>
<ul className="space-y-2 text-sm">
<li><Link href="/about" className="hover:text-white transition">About Us</Link></li>
<li><Link href="/blog" className="hover:text-white transition">Blog</Link></li>
<li><Link href="/careers" className="hover:text-white transition">Careers</Link></li>
<li><Link href="/contact" className="hover:text-white transition">Contact</Link></li>
<li><Link href="/privacy" className="hover:text-white transition">Privacy Policy</Link></li>
</ul>
</div>
</div>
<div className="border-t border-gray-800 pt-8 flex flex-col md:flex-row items-center justify-between gap-4">
<p className="text-sm">
© {new Date().getFullYear()} AI Employees for Hire. All rights reserved.
</p>
<p className="text-sm">
Built with ❤️ for the future of work
</p>
</div>
</div>
</footer>
)
}

90
src/components/Header.tsx Normal file
View File

@ -0,0 +1,90 @@
'use client'
import { useState } from 'react'
import Link from 'next/link'
import { Menu, X, Bot } from 'lucide-react'
export function Header() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
return (
<header className="fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-md border-b border-gray-100">
<nav className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
<Link href="/" className="flex items-center gap-2">
<div className="w-10 h-10 bg-gradient-to-br from-brand-500 to-blue-600 rounded-xl flex items-center justify-center">
<Bot className="w-6 h-6 text-white" />
</div>
<span className="font-bold text-xl">AI Employees</span>
</Link>
<div className="hidden md:flex items-center gap-8">
<Link href="#employees" className="text-gray-600 hover:text-gray-900 transition">
Browse Employees
</Link>
<Link href="#how-it-works" className="text-gray-600 hover:text-gray-900 transition">
How It Works
</Link>
<Link href="#pricing" className="text-gray-600 hover:text-gray-900 transition">
Pricing
</Link>
<Link href="#enterprise" className="text-gray-600 hover:text-gray-900 transition">
Enterprise
</Link>
</div>
<div className="hidden md:flex items-center gap-4">
<Link
href="/login"
className="text-gray-600 hover:text-gray-900 transition font-medium"
>
Log In
</Link>
<Link
href="/signup"
className="bg-brand-600 hover:bg-brand-700 text-white px-5 py-2.5 rounded-lg font-medium transition"
>
Hire an Employee
</Link>
</div>
<button
className="md:hidden p-2"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
{mobileMenuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
</button>
</div>
{mobileMenuOpen && (
<div className="md:hidden py-4 border-t border-gray-100">
<div className="flex flex-col gap-4">
<Link href="#employees" className="text-gray-600 hover:text-gray-900">
Browse Employees
</Link>
<Link href="#how-it-works" className="text-gray-600 hover:text-gray-900">
How It Works
</Link>
<Link href="#pricing" className="text-gray-600 hover:text-gray-900">
Pricing
</Link>
<Link href="#enterprise" className="text-gray-600 hover:text-gray-900">
Enterprise
</Link>
<hr className="border-gray-100" />
<Link href="/login" className="text-gray-600 hover:text-gray-900">
Log In
</Link>
<Link
href="/signup"
className="bg-brand-600 hover:bg-brand-700 text-white px-5 py-2.5 rounded-lg font-medium text-center"
>
Hire an Employee
</Link>
</div>
</div>
)}
</nav>
</header>
)
}

81
src/components/Hero.tsx Normal file
View File

@ -0,0 +1,81 @@
'use client'
import { ArrowRight, Check, Zap } from 'lucide-react'
import Link from 'next/link'
export function Hero() {
return (
<section className="pt-32 pb-20 px-4 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto">
<div className="text-center max-w-4xl mx-auto">
<div className="inline-flex items-center gap-2 bg-brand-50 text-brand-700 px-4 py-2 rounded-full text-sm font-medium mb-8">
<Zap className="w-4 h-4" />
AI-powered workforce ready to work today
</div>
<h1 className="text-5xl sm:text-6xl lg:text-7xl font-extrabold tracking-tight mb-6">
Hire{' '}
<span className="text-gradient">AI Employees</span>
<br />
That Do Real Work
</h1>
<p className="text-xl text-gray-600 mb-10 max-w-2xl mx-auto">
Deploy skilled AI workers for customer support, research, data entry, scheduling,
and more. They work 24/7, never take breaks, and cost a fraction of traditional hires.
</p>
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 mb-12">
<Link
href="/signup"
className="w-full sm:w-auto bg-brand-600 hover:bg-brand-700 text-white px-8 py-4 rounded-xl font-semibold text-lg transition flex items-center justify-center gap-2 group"
>
Start Hiring
<ArrowRight className="w-5 h-5 group-hover:translate-x-1 transition" />
</Link>
<Link
href="#demo"
className="w-full sm:w-auto bg-gray-100 hover:bg-gray-200 text-gray-900 px-8 py-4 rounded-xl font-semibold text-lg transition"
>
Watch Demo
</Link>
</div>
<div className="flex flex-wrap items-center justify-center gap-6 text-sm text-gray-600">
<div className="flex items-center gap-2">
<Check className="w-5 h-5 text-green-500" />
No contracts required
</div>
<div className="flex items-center gap-2">
<Check className="w-5 h-5 text-green-500" />
Deploy in minutes
</div>
<div className="flex items-center gap-2">
<Check className="w-5 h-5 text-green-500" />
Cancel anytime
</div>
</div>
</div>
<div className="mt-20 relative">
<div className="absolute inset-0 bg-gradient-to-t from-white via-transparent to-transparent z-10 pointer-events-none" />
<div className="bg-gradient-to-br from-gray-900 to-gray-800 rounded-2xl p-8 shadow-2xl">
<div className="flex items-center gap-2 mb-4">
<div className="w-3 h-3 rounded-full bg-red-500" />
<div className="w-3 h-3 rounded-full bg-yellow-500" />
<div className="w-3 h-3 rounded-full bg-green-500" />
</div>
<div className="font-mono text-sm text-gray-300 space-y-2">
<p><span className="text-green-400">$</span> ai-employee deploy --role customer-support</p>
<p className="text-gray-500">Deploying Alex (Customer Support Agent)...</p>
<p className="text-gray-500">Connecting to Slack, WhatsApp, Email...</p>
<p className="text-brand-400">✓ AI Employee "Alex" is now online and ready to work</p>
<p className="text-gray-500">Handled 47 customer inquiries in the last hour</p>
<p className="text-gray-500">Customer satisfaction: 98.5%</p>
</div>
</div>
</div>
</div>
</section>
)
}

View File

@ -0,0 +1,61 @@
'use client'
import { UserPlus, Settings, Play, BarChart3 } from 'lucide-react'
const steps = [
{
icon: UserPlus,
title: 'Choose Your Employee',
description: 'Browse our roster of AI specialists. Each one is pre-trained for specific roles like customer support, research, or data entry.',
},
{
icon: Settings,
title: 'Configure & Connect',
description: 'Set their working hours, personality, and connect them to your tools: Slack, WhatsApp, email, CRM, and more.',
},
{
icon: Play,
title: 'Deploy Instantly',
description: 'Your AI Employee goes live immediately. They start handling tasks, responding to messages, and working autonomously.',
},
{
icon: BarChart3,
title: 'Monitor & Scale',
description: 'Track performance in real-time. See tasks completed, customer satisfaction, and scale up by hiring more employees.',
},
]
export function HowItWorks() {
return (
<section id="how-it-works" className="py-20 px-4 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<h2 className="text-4xl font-bold mb-4">How It Works</h2>
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
Get your AI workforce up and running in minutes, not months.
</p>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8">
{steps.map((step, index) => (
<div key={step.title} className="relative">
{index < steps.length - 1 && (
<div className="hidden lg:block absolute top-8 left-full w-full h-0.5 bg-gray-200 -translate-x-1/2" />
)}
<div className="text-center">
<div className="w-16 h-16 bg-brand-100 rounded-2xl flex items-center justify-center mx-auto mb-4 relative">
<step.icon className="w-8 h-8 text-brand-600" />
<span className="absolute -top-2 -right-2 w-6 h-6 bg-brand-600 text-white rounded-full text-sm font-bold flex items-center justify-center">
{index + 1}
</span>
</div>
<h3 className="font-semibold text-lg mb-2">{step.title}</h3>
<p className="text-gray-600 text-sm">{step.description}</p>
</div>
</div>
))}
</div>
</div>
</section>
)
}

131
src/components/Pricing.tsx Normal file
View File

@ -0,0 +1,131 @@
'use client'
import { Check } from 'lucide-react'
import Link from 'next/link'
const plans = [
{
name: 'Starter',
description: 'Perfect for small teams getting started',
price: 49,
period: 'month',
features: [
'1 AI Employee',
'3 channel integrations',
'1,000 tasks/month',
'Email support',
'Basic analytics',
],
cta: 'Start Free Trial',
popular: false,
},
{
name: 'Professional',
description: 'For growing businesses that need more',
price: 199,
period: 'month',
features: [
'5 AI Employees',
'Unlimited channels',
'10,000 tasks/month',
'Priority support',
'Advanced analytics',
'Custom training',
'API access',
],
cta: 'Start Free Trial',
popular: true,
},
{
name: 'Enterprise',
description: 'Custom solutions for large organizations',
price: null,
period: null,
features: [
'Unlimited AI Employees',
'Unlimited channels',
'Unlimited tasks',
'Dedicated support',
'Custom integrations',
'On-premise option',
'SLA guarantee',
'SSO & SAML',
],
cta: 'Contact Sales',
popular: false,
},
]
export function Pricing() {
return (
<section id="pricing" className="py-20 px-4 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<h2 className="text-4xl font-bold mb-4">Simple, Transparent Pricing</h2>
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
Pay for what you use. No hidden fees, no long-term contracts.
Scale up or down anytime.
</p>
</div>
<div className="grid md:grid-cols-3 gap-8 max-w-5xl mx-auto">
{plans.map((plan) => (
<div
key={plan.name}
className={`rounded-2xl p-8 ${
plan.popular
? 'bg-brand-600 text-white ring-4 ring-brand-600 ring-offset-4'
: 'bg-white border border-gray-200'
}`}
>
{plan.popular && (
<span className="inline-block bg-white text-brand-600 text-xs font-bold px-3 py-1 rounded-full mb-4">
MOST POPULAR
</span>
)}
<h3 className="text-2xl font-bold mb-2">{plan.name}</h3>
<p className={`text-sm mb-6 ${plan.popular ? 'text-brand-100' : 'text-gray-500'}`}>
{plan.description}
</p>
<div className="mb-6">
{plan.price !== null ? (
<>
<span className="text-5xl font-bold">${plan.price}</span>
<span className={plan.popular ? 'text-brand-100' : 'text-gray-500'}>
/{plan.period}
</span>
</>
) : (
<span className="text-3xl font-bold">Custom</span>
)}
</div>
<ul className="space-y-3 mb-8">
{plan.features.map((feature) => (
<li key={feature} className="flex items-center gap-3">
<Check className={`w-5 h-5 ${plan.popular ? 'text-brand-200' : 'text-green-500'}`} />
<span className={`text-sm ${plan.popular ? 'text-white' : 'text-gray-600'}`}>
{feature}
</span>
</li>
))}
</ul>
<Link
href={plan.price !== null ? '/signup' : '/contact'}
className={`block w-full py-3 rounded-xl font-semibold text-center transition ${
plan.popular
? 'bg-white text-brand-600 hover:bg-gray-100'
: 'bg-brand-600 text-white hover:bg-brand-700'
}`}
>
{plan.cta}
</Link>
</div>
))}
</div>
</div>
</section>
)
}

137
src/lib/employees.ts Normal file
View File

@ -0,0 +1,137 @@
import { AIEmployee, EmployeeRole } from '@/types/employee'
export const EMPLOYEE_ROLES: Record<EmployeeRole, { title: string; icon: string; color: string }> = {
'customer-support': { title: 'Customer Support Agent', icon: 'headphones', color: 'blue' },
'research-analyst': { title: 'Research Analyst', icon: 'search', color: 'purple' },
'data-entry': { title: 'Data Entry Specialist', icon: 'database', color: 'green' },
'scheduler': { title: 'Scheduling Assistant', icon: 'calendar', color: 'orange' },
'sales-assistant': { title: 'Sales Assistant', icon: 'trending-up', color: 'red' },
'content-writer': { title: 'Content Writer', icon: 'pen-tool', color: 'pink' },
'social-media': { title: 'Social Media Manager', icon: 'share-2', color: 'cyan' },
'executive-assistant': { title: 'Executive Assistant', icon: 'briefcase', color: 'indigo' },
}
export const FEATURED_EMPLOYEES: AIEmployee[] = [
{
id: 'alex-support',
name: 'Alex',
role: 'customer-support',
avatar: '/avatars/alex.png',
description: 'Expert at handling customer inquiries, resolving complaints, and maintaining positive customer relationships across all channels.',
capabilities: [
'Answer customer questions 24/7',
'Process refunds and returns',
'Escalate complex issues',
'Multi-language support',
'CRM integration',
],
channels: ['slack', 'whatsapp', 'email', 'webchat'],
hourlyRate: 8,
status: 'available',
tasksCompleted: 15420,
rating: 4.9,
},
{
id: 'maya-research',
name: 'Maya',
role: 'research-analyst',
avatar: '/avatars/maya.png',
description: 'Skilled at gathering, analyzing, and synthesizing information from multiple sources to deliver actionable insights.',
capabilities: [
'Market research',
'Competitor analysis',
'Data compilation',
'Report generation',
'Trend identification',
],
channels: ['slack', 'email', 'teams'],
hourlyRate: 15,
status: 'available',
tasksCompleted: 3240,
rating: 4.8,
},
{
id: 'jordan-data',
name: 'Jordan',
role: 'data-entry',
avatar: '/avatars/jordan.png',
description: 'Precise and efficient at entering, updating, and maintaining data across spreadsheets, databases, and business systems.',
capabilities: [
'Spreadsheet management',
'Database updates',
'Invoice processing',
'Form filling',
'Data validation',
],
channels: ['slack', 'email'],
hourlyRate: 6,
status: 'available',
tasksCompleted: 28650,
rating: 4.95,
},
{
id: 'sam-scheduler',
name: 'Sam',
role: 'scheduler',
avatar: '/avatars/sam.png',
description: 'Master of calendar management, meeting coordination, and ensuring your time is optimized for maximum productivity.',
capabilities: [
'Calendar management',
'Meeting scheduling',
'Appointment reminders',
'Timezone coordination',
'Conflict resolution',
],
channels: ['slack', 'whatsapp', 'email', 'teams'],
hourlyRate: 10,
status: 'available',
tasksCompleted: 8930,
rating: 4.85,
},
{
id: 'casey-sales',
name: 'Casey',
role: 'sales-assistant',
avatar: '/avatars/casey.png',
description: 'Supports your sales team with lead qualification, follow-ups, and pipeline management to close more deals.',
capabilities: [
'Lead qualification',
'Follow-up sequences',
'CRM updates',
'Meeting booking',
'Proposal drafting',
],
channels: ['slack', 'email', 'whatsapp'],
hourlyRate: 12,
status: 'available',
tasksCompleted: 5670,
rating: 4.7,
},
{
id: 'taylor-content',
name: 'Taylor',
role: 'content-writer',
avatar: '/avatars/taylor.png',
description: 'Creates engaging content for blogs, social media, emails, and marketing materials that resonate with your audience.',
capabilities: [
'Blog posts',
'Social media content',
'Email newsletters',
'Product descriptions',
'SEO optimization',
],
channels: ['slack', 'email'],
hourlyRate: 14,
status: 'available',
tasksCompleted: 4210,
rating: 4.75,
},
]
export function getEmployeesByRole(role: EmployeeRole): AIEmployee[] {
return FEATURED_EMPLOYEES.filter(emp => emp.role === role)
}
export function calculateMonthlyCost(hourlyRate: number, hoursPerDay: number = 8): number {
return hourlyRate * hoursPerDay * 22 // 22 working days
}

View File

@ -0,0 +1,173 @@
/**
* Agent Gateway Service
*
* This service manages the WebSocket connection to AI Employee instances
* and handles real-time communication, tool execution, and event routing.
*/
import { EventEmitter } from 'events'
export interface AgentMessage {
id: string
type: 'user' | 'agent' | 'system' | 'tool'
content: string
channel: string
timestamp: Date
metadata?: Record<string, unknown>
}
export interface ToolCall {
id: string
name: string
parameters: Record<string, unknown>
}
export interface ToolResult {
callId: string
success: boolean
result?: unknown
error?: string
}
export type AgentEvent =
| { type: 'message'; data: AgentMessage }
| { type: 'tool_call'; data: ToolCall }
| { type: 'tool_result'; data: ToolResult }
| { type: 'status_change'; data: { status: 'online' | 'offline' | 'busy' } }
| { type: 'error'; data: { code: string; message: string } }
export class AgentGateway extends EventEmitter {
private ws: WebSocket | null = null
private employeeId: string
private gatewayUrl: string
private reconnectAttempts = 0
private maxReconnectAttempts = 5
private reconnectDelay = 1000
constructor(employeeId: string, gatewayUrl: string = 'ws://localhost:18789') {
super()
this.employeeId = employeeId
this.gatewayUrl = gatewayUrl
}
connect(): Promise<void> {
return new Promise((resolve, reject) => {
const url = `${this.gatewayUrl}/ws/employees/${this.employeeId}`
this.ws = new WebSocket(url)
this.ws.onopen = () => {
this.reconnectAttempts = 0
this.emit('connected')
resolve()
}
this.ws.onmessage = (event) => {
try {
const agentEvent: AgentEvent = JSON.parse(event.data)
this.handleEvent(agentEvent)
} catch (error) {
console.error('Failed to parse gateway message:', error)
}
}
this.ws.onerror = (error) => {
console.error('Gateway WebSocket error:', error)
this.emit('error', error)
reject(error)
}
this.ws.onclose = () => {
this.emit('disconnected')
this.attemptReconnect()
}
})
}
private handleEvent(event: AgentEvent) {
switch (event.type) {
case 'message':
this.emit('message', event.data)
break
case 'tool_call':
this.emit('tool_call', event.data)
break
case 'tool_result':
this.emit('tool_result', event.data)
break
case 'status_change':
this.emit('status_change', event.data)
break
case 'error':
this.emit('agent_error', event.data)
break
}
}
private attemptReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
this.emit('reconnect_failed')
return
}
this.reconnectAttempts++
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1)
setTimeout(() => {
this.emit('reconnecting', { attempt: this.reconnectAttempts })
this.connect().catch(() => {
// Reconnect failed, will try again
})
}, delay)
}
sendMessage(channel: string, content: string, metadata?: Record<string, unknown>) {
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
throw new Error('Gateway not connected')
}
const message: AgentMessage = {
id: crypto.randomUUID(),
type: 'user',
content,
channel,
timestamp: new Date(),
metadata,
}
this.ws.send(JSON.stringify({
type: 'send_message',
data: message,
}))
}
submitToolResult(result: ToolResult) {
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
throw new Error('Gateway not connected')
}
this.ws.send(JSON.stringify({
type: 'tool_result',
data: result,
}))
}
requestStatus() {
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
throw new Error('Gateway not connected')
}
this.ws.send(JSON.stringify({
type: 'request_status',
}))
}
disconnect() {
if (this.ws) {
this.ws.close()
this.ws = null
}
}
}
export const createAgentGateway = (employeeId: string, gatewayUrl?: string) =>
new AgentGateway(employeeId, gatewayUrl)

View File

@ -0,0 +1,199 @@
import { AIEmployee, EmployeeConfig, EmployeeRole, Task } from '@/types/employee'
export interface DeploymentResult {
success: boolean
employeeId: string
endpoints: {
websocket: string
api: string
}
channels: ChannelConnection[]
}
export interface ChannelConnection {
type: string
status: 'connected' | 'pending' | 'failed'
endpoint?: string
}
export class EmployeeService {
private baseUrl: string
private apiKey: string
constructor(apiKey: string, baseUrl: string = process.env.EMPLOYEE_GATEWAY_URL || 'http://localhost:18789') {
this.apiKey = apiKey
this.baseUrl = baseUrl
}
async deployEmployee(config: EmployeeConfig): Promise<DeploymentResult> {
const response = await fetch(`${this.baseUrl}/api/employees/deploy`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`,
},
body: JSON.stringify({
role: config.role,
name: config.name,
personality: config.personality,
channels: config.channels,
workingHours: config.workingHours,
integrations: config.integrations,
}),
})
if (!response.ok) {
throw new Error(`Deployment failed: ${response.statusText}`)
}
return response.json()
}
async getEmployeeStatus(employeeId: string): Promise<{
status: 'online' | 'offline' | 'busy'
currentTask?: Task
metrics: {
tasksCompleted: number
avgResponseTime: number
satisfaction: number
}
}> {
const response = await fetch(`${this.baseUrl}/api/employees/${employeeId}/status`, {
headers: {
'Authorization': `Bearer ${this.apiKey}`,
},
})
if (!response.ok) {
throw new Error(`Failed to get status: ${response.statusText}`)
}
return response.json()
}
async assignTask(employeeId: string, task: {
title: string
description: string
priority: 'low' | 'medium' | 'high'
deadline?: Date
}): Promise<Task> {
const response = await fetch(`${this.baseUrl}/api/employees/${employeeId}/tasks`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`,
},
body: JSON.stringify(task),
})
if (!response.ok) {
throw new Error(`Failed to assign task: ${response.statusText}`)
}
return response.json()
}
async pauseEmployee(employeeId: string): Promise<void> {
await fetch(`${this.baseUrl}/api/employees/${employeeId}/pause`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
},
})
}
async resumeEmployee(employeeId: string): Promise<void> {
await fetch(`${this.baseUrl}/api/employees/${employeeId}/resume`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
},
})
}
async terminateEmployee(employeeId: string): Promise<void> {
await fetch(`${this.baseUrl}/api/employees/${employeeId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
},
})
}
async connectChannel(employeeId: string, channel: {
type: 'slack' | 'discord' | 'whatsapp' | 'telegram' | 'email' | 'teams' | 'webchat'
config: Record<string, string>
}): Promise<ChannelConnection> {
const response = await fetch(`${this.baseUrl}/api/employees/${employeeId}/channels`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`,
},
body: JSON.stringify(channel),
})
if (!response.ok) {
throw new Error(`Failed to connect channel: ${response.statusText}`)
}
return response.json()
}
async getTaskHistory(employeeId: string, options?: {
limit?: number
offset?: number
status?: Task['status']
}): Promise<{ tasks: Task[]; total: number }> {
const params = new URLSearchParams()
if (options?.limit) params.set('limit', options.limit.toString())
if (options?.offset) params.set('offset', options.offset.toString())
if (options?.status) params.set('status', options.status)
const response = await fetch(`${this.baseUrl}/api/employees/${employeeId}/tasks?${params}`, {
headers: {
'Authorization': `Bearer ${this.apiKey}`,
},
})
if (!response.ok) {
throw new Error(`Failed to get task history: ${response.statusText}`)
}
return response.json()
}
async updatePersonality(employeeId: string, personality: string): Promise<void> {
await fetch(`${this.baseUrl}/api/employees/${employeeId}/personality`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`,
},
body: JSON.stringify({ personality }),
})
}
async getMetrics(employeeId: string, timeRange: '24h' | '7d' | '30d'): Promise<{
tasksCompleted: number
tasksPerHour: number
avgResponseTime: number
satisfaction: number
channelBreakdown: Record<string, number>
hourlyActivity: number[]
}> {
const response = await fetch(`${this.baseUrl}/api/employees/${employeeId}/metrics?range=${timeRange}`, {
headers: {
'Authorization': `Bearer ${this.apiKey}`,
},
})
if (!response.ok) {
throw new Error(`Failed to get metrics: ${response.statusText}`)
}
return response.json()
}
}
export const createEmployeeService = (apiKey: string) => new EmployeeService(apiKey)

56
src/types/employee.ts Normal file
View File

@ -0,0 +1,56 @@
export type EmployeeRole =
| 'customer-support'
| 'research-analyst'
| 'data-entry'
| 'scheduler'
| 'sales-assistant'
| 'content-writer'
| 'social-media'
| 'executive-assistant'
export type EmployeeStatus = 'available' | 'working' | 'paused' | 'training'
export interface AIEmployee {
id: string
name: string
role: EmployeeRole
avatar: string
description: string
capabilities: string[]
channels: string[]
hourlyRate: number
status: EmployeeStatus
tasksCompleted: number
rating: number
}
export interface EmployeeConfig {
role: EmployeeRole
name: string
personality: string
channels: string[]
workingHours: {
start: string
end: string
timezone: string
}
integrations: Integration[]
}
export interface Integration {
id: string
type: 'slack' | 'discord' | 'whatsapp' | 'telegram' | 'email' | 'teams' | 'webchat'
config: Record<string, string>
enabled: boolean
}
export interface Task {
id: string
employeeId: string
title: string
description: string
status: 'pending' | 'in-progress' | 'completed' | 'failed'
createdAt: Date
completedAt?: Date
result?: string
}

33
tailwind.config.ts Normal file
View File

@ -0,0 +1,33 @@
import type { Config } from 'tailwindcss'
const config: Config = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
brand: {
50: '#f0f9ff',
100: '#e0f2fe',
200: '#bae6fd',
300: '#7dd3fc',
400: '#38bdf8',
500: '#0ea5e9',
600: '#0284c7',
700: '#0369a1',
800: '#075985',
900: '#0c4a6e',
},
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
},
},
plugins: [],
}
export default config

22
tsconfig.json Normal file
View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [{ "name": "next" }],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}