Initial commit: AI Employees for Hire platform
This commit is contained in:
commit
7bd79fa3b5
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
|
@ -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(),
|
||||
})
|
||||
}
|
||||
|
|
@ -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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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"]
|
||||
}
|
||||
Loading…
Reference in New Issue