Add AI features with secure environment variable configuration
- OpenAI API integration for event description generation - Environment variable configuration for API key - Premium feature access controls and usage tracking 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
268
src/lib/ai.ts
Normal file
268
src/lib/ai.ts
Normal file
@@ -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<EventDescriptionResponse> {
|
||||
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<string[]> {
|
||||
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}`);
|
||||
}
|
||||
Reference in New Issue
Block a user