Skip to content

Commit 9188c3d

Browse files
committed
feat: Enable natural language search for events and hackathons, and refine AI context generation and prompt formatting.
1 parent 970d292 commit 9188c3d

1 file changed

Lines changed: 94 additions & 42 deletions

File tree

app/api/ai/route.ts

Lines changed: 94 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -436,19 +436,45 @@ interface ContextData {
436436
};
437437
}
438438

439+
// Helper function to extract search terms from user message
440+
function extractSearchTerms(message: string): string[] {
441+
const stopWords = ['a', 'an', 'the', 'in', 'on', 'at', 'for', 'to', 'of', 'with', 'by', 'about', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'do', 'does', 'did', 'can', 'could', 'will', 'would', 'shall', 'should', 'may', 'might', 'must', 'show', 'me', 'tell', 'list', 'find', 'search', 'looking', 'any', 'some', 'all', 'what', 'where', 'when', 'who', 'why', 'how', 'event', 'events', 'hackathon', 'hackathons'];
442+
443+
const words = message.toLowerCase()
444+
.replace(/[^\w\s]/g, '') // Remove punctuation
445+
.split(/\s+/);
446+
447+
return words.filter(word => !stopWords.includes(word) && word.length > 2);
448+
}
449+
439450
// Database service functions
440-
async function getEvents(limit = 10) {
451+
async function getEvents(limit = 10, searchTerms: string[] = []) {
441452
try {
442453
const supabase = getSupabaseClient();
443-
const { data, error } = await supabase
454+
const today = new Date().toISOString();
455+
456+
let query = supabase
444457
.from('events')
445458
.select(`
446459
id, slug, title, description, excerpt, date, time, duration,
447460
location, locations, status, event_type, registration_deadline,
448461
capacity, registered, category, categories, tags, price, organizer
449462
`)
450463
.eq('status', 'live')
451-
.order('date', { ascending: false })
464+
.gte('date', today); // Only future events
465+
466+
// Apply search filters if terms exist
467+
if (searchTerms.length > 0) {
468+
const orConditions = searchTerms.map(term =>
469+
`title.ilike.%${term}%,description.ilike.%${term}%,location.ilike.%${term}%`
470+
).join(',');
471+
472+
// Note: This is a simple OR search. For more complex search, we might need textSearch or RPC
473+
query = query.or(orConditions);
474+
}
475+
476+
const { data, error } = await query
477+
.order('date', { ascending: true }) // Show nearest upcoming events first
452478
.limit(limit);
453479

454480
if (error) throw error;
@@ -459,10 +485,12 @@ async function getEvents(limit = 10) {
459485
}
460486
}
461487

462-
async function getHackathons(limit = 10) {
488+
async function getHackathons(limit = 10, searchTerms: string[] = []) {
463489
try {
464490
const supabase = getSupabaseClient();
465-
const { data, error } = await supabase
491+
const today = new Date().toISOString();
492+
493+
let query = supabase
466494
.from('hackathons')
467495
.select(`
468496
id, slug, title, description, excerpt, date, time, duration,
@@ -471,7 +499,19 @@ async function getHackathons(limit = 10) {
471499
price, organizer, prize, prize_details, team_size
472500
`)
473501
.eq('status', 'live')
474-
.order('date', { ascending: true })
502+
.gte('date', today); // Only future hackathons
503+
504+
// Apply search filters if terms exist
505+
if (searchTerms.length > 0) {
506+
const orConditions = searchTerms.map(term =>
507+
`title.ilike.%${term}%,description.ilike.%${term}%,location.ilike.%${term}%`
508+
).join(',');
509+
510+
query = query.or(orConditions);
511+
}
512+
513+
const { data, error } = await query
514+
.order('date', { ascending: true }) // Show nearest upcoming hackathons first
475515
.limit(limit);
476516

477517
if (error) throw error;
@@ -657,13 +697,18 @@ async function getContextualData(userMessage: string, context: string, userId?:
657697
return data;
658698
}
659699

700+
// Extract search terms for smarter data retrieval
701+
const searchTerms = extractSearchTerms(message);
702+
console.log('🔍 Extracted search terms:', searchTerms);
703+
660704
// Get specific data based on context and message content
661705
if (message.includes('event') || context === 'events') {
662-
data.events = await getEvents(5);
706+
data.events = await getEvents(5, searchTerms);
663707
}
664708

665709
if (message.includes('hackathon') || context === 'hackathons') {
666-
data.hackathons = await getHackathons(5);
710+
data.hackathons = await getHackathons(5, searchTerms);
711+
console.log(`🔍 Found ${data.hackathons.length} hackathons matching terms:`, searchTerms);
667712
}
668713

669714
if (message.includes('internship') || message.includes('job') || message.includes('opportunity') || context === 'opportunities') {
@@ -676,8 +721,16 @@ async function getContextualData(userMessage: string, context: string, userId?:
676721

677722
// If no specific context, get a bit of everything for comprehensive answers EXCEPT internships
678723
if (context === 'general' && Object.keys(data).length === 1) {
679-
data.events = await getEvents(3);
680-
data.hackathons = await getHackathons(3);
724+
// If we have specific search terms, try to find matching events/hackathons even in general context
725+
if (searchTerms.length > 0) {
726+
data.events = await getEvents(3, searchTerms);
727+
data.hackathons = await getHackathons(3, searchTerms);
728+
console.log(`🔍 General Search - Found ${data.events.length} events and ${data.hackathons.length} hackathons`);
729+
} else {
730+
data.events = await getEvents(3);
731+
data.hackathons = await getHackathons(3);
732+
}
733+
681734
data.blogs = await getBlogs(3);
682735
// Only include internships if the message specifically mentions them
683736
if (message.includes('internship') || message.includes('job') || message.includes('opportunity')) {
@@ -841,18 +894,28 @@ Would you like more details about either program or help choosing which one is r
841894
message.includes('about codeunia') ||
842895
(message.length < 30 && context === 'general');
843896

844-
const isProgrammingQuestion = message.includes('algorithm') ||
845-
message.includes('code') ||
897+
const isProgrammingQuestion = (
898+
message.includes('algorithm') ||
899+
message.includes('code ') || // Space to avoid matching 'codeunia'
900+
message.includes('coding') ||
846901
message.includes('programming') ||
847902
message.includes('sort') ||
848903
message.includes('function') ||
849-
message.includes('java') ||
904+
message.includes('java ') ||
850905
message.includes('python') ||
851906
message.includes('javascript') ||
852907
message.includes('data structure') ||
853-
message.includes('give me') ||
908+
message.includes('give me code') ||
854909
message.includes('how to') ||
855-
message.includes('explain');
910+
message.includes('explain')
911+
) && !(
912+
message.includes('hackathon') ||
913+
message.includes('event') ||
914+
message.includes('internship') ||
915+
message.includes('job') ||
916+
message.includes('opportunity') ||
917+
message.includes('register')
918+
);
856919

857920
// Check if user is asking for their name
858921
const isAskingName = message.includes('what is my name') ||
@@ -1001,53 +1064,42 @@ CODEUNIA DATA AVAILABLE:
10011064
`;
10021065
}
10031066

1004-
// Add events data with date analysis
1067+
// Add events data
10051068
if (contextData.events && contextData.events.length > 0) {
1006-
prompt += `\nEVENT DETAILS (analyze dates carefully - current date is ${formattedCurrentDate}):\n`;
1069+
prompt += `\nCURRENTLY ACTIVE EVENTS (${contextData.events.length} found):\n`;
10071070
contextData.events.forEach((event: Event) => {
1008-
const eventDate = new Date(event.date);
1009-
const todayStart = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate());
1010-
const status = eventDate >= todayStart ? 'CURRENT/UPCOMING' : 'COMPLETED';
1011-
10121071
prompt += `
1013-
EVENT: ${event.title} [${status}]
1072+
EVENT: ${event.title}
1073+
- ID: ${event.id}
10141074
- Description: ${event.description}
1015-
- Date: ${event.date} at ${event.time}
1016-
- Duration: ${event.duration}
1075+
- Date: ${event.date}
1076+
- Time: ${event.time}
10171077
- Location: ${event.location}
1018-
- Status: ${event.status}
1019-
- Type: ${Array.isArray(event.event_type) ? event.event_type.join(', ') : event.event_type}
1020-
- Registration Deadline: ${event.registration_deadline}
1021-
- Capacity: ${event.capacity} (${event.registered} registered)
1022-
- Category: ${event.category}
1023-
- Price: ${event.price}
1024-
- Organizer: ${event.organizer}
1078+
- Price: ${event.price || 'Free'}
1079+
- STATUS: ${event.status === 'live' ? 'CURRENT/UPCOMING' : 'COMPLETED'}
10251080
- Tags: ${Array.isArray(event.tags) ? event.tags.join(', ') : event.tags}
10261081
`;
10271082
});
10281083
}
10291084

1030-
// Add hackathons data with date analysis
1085+
// Add hackathons data
10311086
if (contextData.hackathons && contextData.hackathons.length > 0) {
1032-
prompt += `\nHACKATHON DETAILS (These are HACKATHONS, not events - analyze dates carefully - current date is ${formattedCurrentDate}):\n`;
1087+
prompt += `\nCURRENTLY ACTIVE HACKATHONS (${contextData.hackathons.length} found):\n`;
10331088
contextData.hackathons.forEach((hackathon: Hackathon) => {
1034-
const hackathonDate = new Date(hackathon.date);
1035-
const todayStart = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate());
1036-
const status = hackathonDate >= todayStart ? 'CURRENT/UPCOMING' : 'COMPLETED';
1037-
10381089
prompt += `
1039-
HACKATHON: ${hackathon.title} [${status}]
1040-
- Type: HACKATHON (not an event)
1090+
HACKATHON: ${hackathon.title}
1091+
- ID: ${hackathon.id}
10411092
- Description: ${hackathon.description}
1042-
- Start Date: ${hackathon.date}
1043-
- Duration: ${hackathon.duration || 'Not specified'}
1093+
- Date: ${hackathon.date}
10441094
- Theme/Category: ${hackathon.category || 'General'}
10451095
- Location: ${hackathon.location}
10461096
- Prize Pool: ${hackathon.prize || 'Not specified'}
10471097
- Registration Deadline: ${hackathon.registration_deadline} (last day to register)
1048-
- STATUS: ${status} - ${status === 'COMPLETED' ? 'This hackathon has ended' : 'This hackathon is current or upcoming'}
1098+
- STATUS: ${hackathon.status === 'live' ? 'CURRENT/UPCOMING' : 'COMPLETED'}
10491099
`;
10501100
});
1101+
} else if (message.includes('hackathon')) {
1102+
prompt += `\nNO ACTIVE HACKATHONS FOUND matching your search.\n`;
10511103
}
10521104

10531105
// Add internships data

0 commit comments

Comments
 (0)