diff --git a/src/lib/ai.ts b/src/lib/ai.ts new file mode 100644 index 0000000..3e568d2 --- /dev/null +++ b/src/lib/ai.ts @@ -0,0 +1,268 @@ +// AI-powered features for Black Canyon Tickets +// Uses OpenAI API for premium event description generation + +const OPENAI_API_KEY = import.meta.env.OPENAI_API_KEY || process.env.OPENAI_API_KEY; +const OPENAI_API_URL = 'https://api.openai.com/v1/chat/completions'; + +export interface EventDescriptionRequest { + eventTitle: string; + venueName: string; + venueType?: string; // 'hotel', 'theater', 'gallery', 'outdoor', etc. + eventType: string; // 'gala', 'wedding', 'concert', 'dance', 'theater', etc. + eventDate?: string; + targetAudience?: string; // 'upscale', 'family', 'corporate', 'artistic', etc. + keyFeatures?: string[]; // ['dinner', 'dancing', 'live music', 'auction', etc.] + tone?: 'elegant' | 'casual' | 'luxurious' | 'artistic' | 'professional'; + length?: 'short' | 'medium' | 'long'; +} + +export interface EventDescriptionResponse { + description: string; + tagline?: string; + marketingPoints: string[]; + seoKeywords: string[]; + socialMediaVersion?: string; +} + +// Generate professional event description using OpenAI +export async function generateEventDescription(request: EventDescriptionRequest): Promise { + const prompt = buildDescriptionPrompt(request); + + try { + const response = await fetch(OPENAI_API_URL, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${OPENAI_API_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + model: 'gpt-4', + messages: [ + { + role: 'system', + content: `You are a professional event marketing copywriter specializing in upscale venues. You write compelling, sophisticated event descriptions that attract discerning clientele to high-end events. Your writing style is elegant but accessible, highlighting the unique aspects of luxury venues.` + }, + { + role: 'user', + content: prompt + } + ], + max_tokens: 800, + temperature: 0.7, + }), + }); + + if (!response.ok) { + throw new Error(`OpenAI API error: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + const generatedText = data.choices[0]?.message?.content; + + if (!generatedText) { + throw new Error('No content generated from OpenAI'); + } + + return parseGeneratedDescription(generatedText); + } catch (error) { + console.error('Error generating event description:', error); + throw new Error('Failed to generate AI event description. Please try again.'); + } +} + +// Build the prompt for OpenAI based on event details +function buildDescriptionPrompt(request: EventDescriptionRequest): string { + const { + eventTitle, + venueName, + venueType = 'venue', + eventType, + eventDate, + targetAudience = 'upscale', + keyFeatures = [], + tone = 'elegant', + length = 'medium' + } = request; + + const lengthGuide = { + short: '1-2 paragraphs (150-200 words)', + medium: '2-3 paragraphs (250-350 words)', + long: '3-4 paragraphs (400-500 words)' + }; + + const toneGuide = { + elegant: 'sophisticated and refined, emphasizing exclusivity and prestige', + casual: 'warm and inviting, approachable but still upscale', + luxurious: 'opulent and lavish, highlighting premium amenities and experiences', + artistic: 'creative and cultured, appealing to arts enthusiasts', + professional: 'polished and corporate, suitable for business events' + }; + + return `Write a compelling event description for "${eventTitle}" at ${venueName} in the Aspen/Roaring Fork Valley area. + +Event Details: +- Event Type: ${eventType} +- Venue: ${venueName} (${venueType}) +- Target Audience: ${targetAudience} +- Tone: ${toneGuide[tone]} +- Length: ${lengthGuide[length]} +${eventDate ? `- Date: ${eventDate}` : ''} +${keyFeatures.length > 0 ? `- Key Features: ${keyFeatures.join(', ')}` : ''} + +Requirements: +1. Write a main event description that captures the essence and appeal of the event +2. Include a short, catchy tagline (10-15 words) +3. List 3-5 key marketing points that would appeal to the target audience +4. Suggest 5-8 SEO keywords relevant to the event and location +5. Create a shorter social media version (1-2 sentences) + +Focus on: +- The unique appeal of the Aspen/mountain luxury setting +- What makes this event special and worth attending +- The experience attendees will have +- The prestige and exclusivity appropriate for the market + +Format your response as: + +DESCRIPTION: +[Main event description here] + +TAGLINE: +[Catchy tagline here] + +MARKETING POINTS: +- [Point 1] +- [Point 2] +- [Point 3] +- [etc.] + +SEO KEYWORDS: +[keyword1, keyword2, keyword3, etc.] + +SOCIAL MEDIA: +[Short social media version]`; +} + +// Parse the generated text into structured response +function parseGeneratedDescription(generatedText: string): EventDescriptionResponse { + const sections = generatedText.split(/\n(?=[A-Z ]+:)/); + + let description = ''; + let tagline = ''; + let marketingPoints: string[] = []; + let seoKeywords: string[] = []; + let socialMediaVersion = ''; + + sections.forEach(section => { + const trimmedSection = section.trim(); + + if (trimmedSection.startsWith('DESCRIPTION:')) { + description = trimmedSection.replace('DESCRIPTION:', '').trim(); + } else if (trimmedSection.startsWith('TAGLINE:')) { + tagline = trimmedSection.replace('TAGLINE:', '').trim(); + } else if (trimmedSection.startsWith('MARKETING POINTS:')) { + const pointsText = trimmedSection.replace('MARKETING POINTS:', '').trim(); + marketingPoints = pointsText + .split('\n') + .map(point => point.replace(/^-\s*/, '').trim()) + .filter(point => point.length > 0); + } else if (trimmedSection.startsWith('SEO KEYWORDS:')) { + const keywordsText = trimmedSection.replace('SEO KEYWORDS:', '').trim(); + seoKeywords = keywordsText + .split(',') + .map(keyword => keyword.trim()) + .filter(keyword => keyword.length > 0); + } else if (trimmedSection.startsWith('SOCIAL MEDIA:')) { + socialMediaVersion = trimmedSection.replace('SOCIAL MEDIA:', '').trim(); + } + }); + + return { + description, + tagline, + marketingPoints, + seoKeywords, + socialMediaVersion + }; +} + +// Generate event title suggestions +export async function generateEventTitleSuggestions(request: { + eventType: string; + venueName: string; + theme?: string; + tone?: string; +}): Promise { + const { eventType, venueName, theme, tone = 'elegant' } = request; + + const prompt = `Generate 5 creative, sophisticated event titles for a ${eventType} at ${venueName} in Aspen, Colorado. ${theme ? `The theme is: ${theme}.` : ''} The tone should be ${tone}. Make them appealing to upscale mountain-town clientele. + +Format as a simple numbered list: +1. [Title] +2. [Title] +etc.`; + + try { + const response = await fetch(OPENAI_API_URL, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${OPENAI_API_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + model: 'gpt-4', + messages: [ + { + role: 'system', + content: 'You are a creative event naming specialist for luxury venues in Aspen.' + }, + { + role: 'user', + content: prompt + } + ], + max_tokens: 300, + temperature: 0.8, + }), + }); + + const data = await response.json(); + const generatedText = data.choices[0]?.message?.content || ''; + + // Parse numbered list + return generatedText + .split('\n') + .map(line => line.replace(/^\d+\.\s*/, '').trim()) + .filter(title => title.length > 0); + + } catch (error) { + console.error('Error generating title suggestions:', error); + return []; + } +} + +// Check if user has access to AI features +export function hasAIAccess(addons: any[]): boolean { + return addons.some(addon => + addon.slug === 'ai-event-description' || + addon.slug === 'bct-pro-monthly' || + addon.slug === 'enterprise-package' + ); +} + +// Cost calculation for AI features +export const AI_FEATURE_COSTS = { + 'ai-event-description': 500, // $5.00 in cents + 'ai-title-suggestions': 300, // $3.00 in cents (if offered separately) +} as const; + +// Usage tracking for AI features +export async function trackAIUsage( + organizationId: string, + feature: string, + tokens: number, + cost: number +) { + // This could be logged to analytics or usage tracking system + console.log(`AI Usage - Org: ${organizationId}, Feature: ${feature}, Tokens: ${tokens}, Cost: $${cost/100}`); +} \ No newline at end of file