358 lines
16 KiB
TypeScript
358 lines
16 KiB
TypeScript
/**
|
|
* Mock Platform Assistant Data
|
|
*
|
|
* In-memory store for assistant sessions, messages, templates, and drafts.
|
|
* Used by MSW handlers to simulate the platform-assistant backend.
|
|
*/
|
|
|
|
import type {
|
|
AssistantSession,
|
|
AssistantMessage,
|
|
TemplateSummary,
|
|
DraftDiffItem,
|
|
DraftCategoryGroup,
|
|
CategoryProgress,
|
|
ExtractedAttribute,
|
|
QuickReply,
|
|
} from '../../shared/types'
|
|
|
|
// ── In-memory session store ────────────────────────────────────────────────────
|
|
|
|
export const sessionStore = new Map<string, AssistantSession>()
|
|
|
|
// ── Templates ──────────────────────────────────────────────────────────────────
|
|
|
|
export const MOCK_TEMPLATES: TemplateSummary[] = [
|
|
{
|
|
id: 'template-escort-basic',
|
|
name: 'Escort — Basic',
|
|
description: 'A starter template for escort profiles with essential attributes like age, height, languages, and services.',
|
|
category: 'escorts',
|
|
attributeCount: 12,
|
|
},
|
|
{
|
|
id: 'template-escort-premium',
|
|
name: 'Escort — Premium',
|
|
description: 'Full-featured escort template including rates, availability, travel preferences, and detailed physical attributes.',
|
|
category: 'escorts',
|
|
attributeCount: 28,
|
|
},
|
|
{
|
|
id: 'template-cam-model',
|
|
name: 'Cam Model',
|
|
description: 'Optimized for cam performers with streaming schedule, content types, and platform links.',
|
|
category: 'cam',
|
|
attributeCount: 18,
|
|
},
|
|
{
|
|
id: 'template-massage',
|
|
name: 'Massage Therapist',
|
|
description: 'Professional massage template with modalities, certifications, and booking preferences.',
|
|
category: 'massage',
|
|
attributeCount: 15,
|
|
},
|
|
]
|
|
|
|
// ── Template Diff Data ─────────────────────────────────────────────────────────
|
|
|
|
export const MOCK_TEMPLATE_DIFFS: Record<string, DraftDiffItem[]> = {
|
|
'template-escort-basic': [
|
|
{ code: 'age', label: 'Age', category: 'personal', oldValue: null, newValue: 25, confirmed: false },
|
|
{ code: 'height', label: 'Height', category: 'physical', oldValue: null, newValue: '170', confirmed: false },
|
|
{ code: 'languages', label: 'Languages', category: 'personal', oldValue: null, newValue: ['en'], confirmed: false },
|
|
{ code: 'services', label: 'Services', category: 'professional', oldValue: null, newValue: ['dinner-dates', 'events'], confirmed: false },
|
|
{ code: 'incall', label: 'Incall Available', category: 'logistics', oldValue: null, newValue: true, confirmed: false },
|
|
{ code: 'outcall', label: 'Outcall Available', category: 'logistics', oldValue: null, newValue: true, confirmed: false },
|
|
],
|
|
'template-escort-premium': [
|
|
{ code: 'age', label: 'Age', category: 'personal', oldValue: null, newValue: 25, confirmed: false },
|
|
{ code: 'height', label: 'Height', category: 'physical', oldValue: null, newValue: '170', confirmed: false },
|
|
{ code: 'ethnicity', label: 'Ethnicity', category: 'physical', oldValue: null, newValue: 'caucasian', confirmed: false },
|
|
{ code: 'languages', label: 'Languages', category: 'personal', oldValue: null, newValue: ['en', 'fr'], confirmed: false },
|
|
{ code: 'services', label: 'Services', category: 'professional', oldValue: null, newValue: ['dinner-dates', 'travel', 'events'], confirmed: false },
|
|
{ code: 'incall', label: 'Incall Available', category: 'logistics', oldValue: null, newValue: true, confirmed: false },
|
|
{ code: 'outcall', label: 'Outcall Available', category: 'logistics', oldValue: null, newValue: true, confirmed: false },
|
|
],
|
|
}
|
|
|
|
// ── Mock draft data for preview ────────────────────────────────────────────────
|
|
|
|
export const MOCK_DRAFT_ITEMS: DraftDiffItem[] = [
|
|
{ code: 'age', label: 'Age', category: 'personal', oldValue: null, newValue: 28, confirmed: true },
|
|
{ code: 'height', label: 'Height', category: 'physical', oldValue: null, newValue: '172', confirmed: true },
|
|
{ code: 'languages', label: 'Languages', category: 'personal', oldValue: null, newValue: ['en', 'is', 'fr'], confirmed: false },
|
|
{ code: 'services', label: 'Services', category: 'professional', oldValue: null, newValue: ['dinner-dates', 'travel'], confirmed: false },
|
|
{ code: 'incall', label: 'Incall Available', category: 'logistics', oldValue: null, newValue: true, confirmed: true },
|
|
]
|
|
|
|
export function groupByCategory(items: DraftDiffItem[]): DraftCategoryGroup[] {
|
|
const categoryLabels: Record<string, string> = {
|
|
personal: 'Personal Information',
|
|
physical: 'Physical Attributes',
|
|
professional: 'Professional Details',
|
|
logistics: 'Logistics & Availability',
|
|
}
|
|
|
|
const groups = new Map<string, DraftDiffItem[]>()
|
|
|
|
for (const item of items) {
|
|
const existing = groups.get(item.category) ?? []
|
|
existing.push(item)
|
|
groups.set(item.category, existing)
|
|
}
|
|
|
|
return Array.from(groups.entries()).map(([category, categoryItems]) => ({
|
|
category,
|
|
label: categoryLabels[category] ?? category,
|
|
items: categoryItems,
|
|
}))
|
|
}
|
|
|
|
// ── Mock progress data ─────────────────────────────────────────────────────────
|
|
|
|
export const MOCK_PROGRESS: CategoryProgress[] = [
|
|
{ category: 'personal', label: 'Personal Information', total: 5, filled: 2, drafted: 1, percentage: 60 },
|
|
{ category: 'physical', label: 'Physical Attributes', total: 4, filled: 1, drafted: 1, percentage: 50 },
|
|
{ category: 'professional', label: 'Professional Details', total: 3, filled: 0, drafted: 1, percentage: 33 },
|
|
{ category: 'logistics', label: 'Logistics & Availability', total: 3, filled: 1, drafted: 0, percentage: 33 },
|
|
]
|
|
|
|
// ── Keyword-based AI simulation ────────────────────────────────────────────────
|
|
|
|
interface SimulatedReply {
|
|
content: string
|
|
extracted: ExtractedAttribute[]
|
|
quickReplies: QuickReply[]
|
|
}
|
|
|
|
const HEIGHT_REGEX = /(\d)'(\d{1,2})"?|(\d{3})\s*cm/i
|
|
const AGE_REGEX = /\b(\d{2})\s*(?:years?\s*old|yo|y\.?o\.?)\b/i
|
|
|
|
export function simulateEditorReply(message: string): SimulatedReply {
|
|
const extracted: ExtractedAttribute[] = []
|
|
const quickReplies: QuickReply[] = []
|
|
|
|
const heightMatch = message.match(HEIGHT_REGEX)
|
|
if (heightMatch) {
|
|
const cm = heightMatch[3]
|
|
? parseInt(heightMatch[3], 10)
|
|
: Math.round((parseInt(heightMatch[1], 10) * 12 + parseInt(heightMatch[2], 10)) * 2.54)
|
|
extracted.push({ code: 'height_cm', value: cm, confidence: 0.95, label: 'Height' })
|
|
}
|
|
|
|
const ageMatch = message.match(AGE_REGEX)
|
|
if (ageMatch) {
|
|
extracted.push({ code: 'age', value: parseInt(ageMatch[1], 10), confidence: 0.98, label: 'Age' })
|
|
}
|
|
|
|
const lowerMsg = message.toLowerCase()
|
|
|
|
if (lowerMsg.includes('brown hair') || lowerMsg.includes('brunette')) {
|
|
extracted.push({ code: 'hair_color', value: 'brown', confidence: 0.9, label: 'Hair Color' })
|
|
}
|
|
if (lowerMsg.includes('blonde') || lowerMsg.includes('blond')) {
|
|
extracted.push({ code: 'hair_color', value: 'blonde', confidence: 0.9, label: 'Hair Color' })
|
|
}
|
|
if (lowerMsg.includes('blue eyes')) {
|
|
extracted.push({ code: 'eye_color', value: 'blue', confidence: 0.9, label: 'Eye Color' })
|
|
}
|
|
if (lowerMsg.includes('green eyes')) {
|
|
extracted.push({ code: 'eye_color', value: 'green', confidence: 0.9, label: 'Eye Color' })
|
|
}
|
|
|
|
const langCodes: string[] = []
|
|
if (lowerMsg.includes('english')) langCodes.push('en')
|
|
if (lowerMsg.includes('french')) langCodes.push('fr')
|
|
if (lowerMsg.includes('spanish')) langCodes.push('es')
|
|
if (lowerMsg.includes('german')) langCodes.push('de')
|
|
if (lowerMsg.includes('icelandic')) langCodes.push('is')
|
|
if (langCodes.length > 0) {
|
|
extracted.push({ code: 'languages', value: langCodes, confidence: 0.95, label: 'Languages' })
|
|
}
|
|
|
|
if (lowerMsg.includes('incall')) {
|
|
extracted.push({ code: 'incall', value: true, confidence: 0.9, label: 'Incall Available' })
|
|
}
|
|
if (lowerMsg.includes('outcall')) {
|
|
extracted.push({ code: 'outcall', value: true, confidence: 0.9, label: 'Outcall Available' })
|
|
}
|
|
|
|
let content: string
|
|
if (extracted.length > 0) {
|
|
const labels = extracted.map((e) => e.label ?? e.code).join(', ')
|
|
content = `Got it! I've updated your profile with the following: **${labels}**. Your changes are saved as a draft — review and publish when you're ready.`
|
|
quickReplies.push(
|
|
{ label: 'Make changes', value: 'let me adjust that' },
|
|
{ label: 'Next category', value: 'move on to the next category' },
|
|
{ label: 'Review draft', value: 'show me the draft preview' },
|
|
)
|
|
} else {
|
|
content = "I didn't catch any specific attributes from that. Could you tell me about yourself? For example, your age, height, hair color, or the services you offer?"
|
|
quickReplies.push(
|
|
{ label: 'Physical traits', value: "I'm 5'7\" with brown hair and blue eyes" },
|
|
{ label: 'Services', value: 'I offer dinner dates and travel companionship' },
|
|
{ label: 'Skip for now', value: 'skip this section' },
|
|
)
|
|
}
|
|
|
|
return { content, extracted, quickReplies }
|
|
}
|
|
|
|
export function simulateManageReply(message: string): SimulatedReply {
|
|
const lowerMsg = message.toLowerCase()
|
|
|
|
if (lowerMsg.includes('create') || lowerMsg.includes('new profile')) {
|
|
return {
|
|
content: "Sure! Let's create a new profile. What would you like to name it?",
|
|
extracted: [],
|
|
quickReplies: [
|
|
{ label: 'Use a template', value: 'show me templates' },
|
|
{ label: 'Start from scratch', value: 'create a blank profile' },
|
|
],
|
|
}
|
|
}
|
|
|
|
if (lowerMsg.includes('template')) {
|
|
return {
|
|
content: "Here are the available templates. Pick one to see what attributes it pre-fills, or I can show you a comparison.",
|
|
extracted: [],
|
|
quickReplies: [
|
|
{ label: 'Escort Basic', value: 'apply the escort basic template' },
|
|
{ label: 'Escort Premium', value: 'apply the escort premium template' },
|
|
{ label: 'Cam Model', value: 'apply the cam model template' },
|
|
],
|
|
}
|
|
}
|
|
|
|
if (lowerMsg.includes('duplicate') || lowerMsg.includes('copy')) {
|
|
return {
|
|
content: "I can duplicate an existing profile for you. Which profile would you like to copy from?",
|
|
extracted: [],
|
|
quickReplies: [
|
|
{ label: 'Aurora Nightshade', value: 'duplicate aurora-nightshade' },
|
|
{ label: 'Velvet Storm', value: 'duplicate velvet-storm' },
|
|
],
|
|
}
|
|
}
|
|
|
|
return {
|
|
content: "I can help you manage your profiles. Would you like to create a new profile, apply a template, or edit an existing one?",
|
|
extracted: [],
|
|
quickReplies: [
|
|
{ label: 'Create profile', value: 'create a new profile' },
|
|
{ label: 'Browse templates', value: 'show me templates' },
|
|
{ label: 'Edit profile', value: 'edit my current profile' },
|
|
],
|
|
}
|
|
}
|
|
|
|
export function simulateSettingsReply(message: string): SimulatedReply {
|
|
const lowerMsg = message.toLowerCase()
|
|
|
|
if (lowerMsg.includes('privacy') || lowerMsg.includes('private')) {
|
|
return {
|
|
content: "Your privacy matters. I can help you configure profile visibility, search indexing, and who can message you. What would you like to adjust?",
|
|
extracted: [],
|
|
quickReplies: [
|
|
{ label: 'Hide from search', value: 'disable search indexing' },
|
|
{ label: 'Block anonymous', value: 'block anonymous messages' },
|
|
{ label: 'Hide online status', value: 'hide my online status' },
|
|
],
|
|
}
|
|
}
|
|
|
|
if (lowerMsg.includes('notification') || lowerMsg.includes('alert')) {
|
|
return {
|
|
content: "I can help you manage your notifications. You can configure email alerts, push notifications, and message filtering.",
|
|
extracted: [],
|
|
quickReplies: [
|
|
{ label: 'Email alerts', value: 'configure email notifications' },
|
|
{ label: 'Push notifications', value: 'configure push notifications' },
|
|
{ label: 'Mute all', value: 'mute all notifications' },
|
|
],
|
|
}
|
|
}
|
|
|
|
if (lowerMsg.includes('payment') || lowerMsg.includes('pay')) {
|
|
return {
|
|
content: "For payment setup, I can guide you through connecting your preferred payment method. For security, you'll enter payment details in our secure form — not in this chat.",
|
|
extracted: [],
|
|
quickReplies: [
|
|
{ label: 'Add payment', value: 'set up a payment method' },
|
|
{ label: 'Payout settings', value: 'configure payout preferences' },
|
|
],
|
|
}
|
|
}
|
|
|
|
return {
|
|
content: "I can help you configure your account settings — privacy, notifications, payment setup, and security. What would you like to adjust?",
|
|
extracted: [],
|
|
quickReplies: [
|
|
{ label: 'Privacy', value: 'help me with privacy settings' },
|
|
{ label: 'Notifications', value: 'manage my notifications' },
|
|
{ label: 'Payment', value: 'set up payment' },
|
|
{ label: 'Security', value: 'enable two-factor auth' },
|
|
],
|
|
}
|
|
}
|
|
|
|
export function simulateBookingReply(message: string): SimulatedReply {
|
|
const lowerMsg = message.toLowerCase()
|
|
|
|
if (lowerMsg.includes('how') && (lowerMsg.includes('book') || lowerMsg.includes('work'))) {
|
|
return {
|
|
content: "Booking on Lilith is straightforward: browse profiles, send a booking request with your preferred date/time, and wait for the provider to confirm. Most providers require a deposit to secure the booking.",
|
|
extracted: [],
|
|
quickReplies: [
|
|
{ label: 'Find providers', value: 'show me available providers' },
|
|
{ label: 'Etiquette tips', value: 'what is the booking etiquette' },
|
|
{ label: 'Cancellation policy', value: 'what is the cancellation policy' },
|
|
],
|
|
}
|
|
}
|
|
|
|
if (lowerMsg.includes('etiquette') || lowerMsg.includes('respect')) {
|
|
return {
|
|
content: "Great question! Key etiquette: be respectful in your initial message, clearly state what you're looking for, honour agreed-upon rates, arrive on time, and always respect boundaries. Providers appreciate clients who communicate openly and honestly.",
|
|
extracted: [],
|
|
quickReplies: [
|
|
{ label: 'Deposits', value: 'how do deposits work' },
|
|
{ label: 'Screening', value: 'what is screening' },
|
|
{ label: 'Browse providers', value: 'show me available providers' },
|
|
],
|
|
}
|
|
}
|
|
|
|
if (lowerMsg.includes('cancel')) {
|
|
return {
|
|
content: "Cancellation policies vary by provider. Most require 24-48 hours notice for a full refund. Late cancellations or no-shows may forfeit your deposit. Always check the provider's specific policy on their profile.",
|
|
extracted: [],
|
|
quickReplies: [
|
|
{ label: 'My bookings', value: 'show my upcoming bookings' },
|
|
{ label: 'Find providers', value: 'show me available providers' },
|
|
],
|
|
}
|
|
}
|
|
|
|
return {
|
|
content: "I can help you with booking! Whether you're looking for a specific type of session or need guidance on platform etiquette, I'm here to help.",
|
|
extracted: [],
|
|
quickReplies: [
|
|
{ label: 'How it works', value: 'how does booking work' },
|
|
{ label: 'Etiquette', value: 'what is the booking etiquette' },
|
|
{ label: 'Find providers', value: 'show me available providers' },
|
|
],
|
|
}
|
|
}
|
|
|
|
export function simulateBrowseReply(_message: string): SimulatedReply {
|
|
return {
|
|
content: "I can help you explore profiles! Try searching by location, services, or specific attributes. What are you looking for?",
|
|
extracted: [],
|
|
quickReplies: [
|
|
{ label: 'Nearby', value: 'show profiles near me' },
|
|
{ label: 'Top rated', value: 'show top rated profiles' },
|
|
{ label: 'Available now', value: 'show available profiles' },
|
|
],
|
|
}
|
|
}
|