From 815c804bb6c183633d728356cf17dba2ffae55eb Mon Sep 17 00:00:00 2001 From: AdithaBuwaneka Date: Tue, 4 Nov 2025 17:31:06 +0530 Subject: [PATCH 1/8] Add knowledge base and vector service for CodeRush 2025 - Implemented a comprehensive knowledge base in `knowledgeBase.ts` containing event details, team requirements, registration rules, submission requirements, and more. - Developed a vector service in `vectorService.ts` utilizing Pinecone and Gemini embeddings for semantic search capabilities. - Added functions for initializing the Pinecone index, generating embeddings, populating the vector database, and searching for relevant documents. - Ensured lazy initialization of Pinecone and Gemini clients for efficient resource management. --- .claude/settings.local.json | 9 + package-lock.json | 20 + package.json | 2 + scripts/check-api-keys.ts | 52 ++ scripts/check-api-rate-limit.ts | 153 +++++ scripts/debug-joke.ts | 11 + scripts/final-comprehensive-test.ts | 197 ++++++ scripts/setup-pinecone.ts | 66 ++ scripts/test-all-questions.ts | 88 +++ scripts/test-api-key.ts | 41 ++ scripts/test-complete-system.ts | 438 +++++++++++++ scripts/test-content-filter.ts | 65 ++ scripts/test-conversational-flow.ts | 166 +++++ scripts/test-event-details-query.ts | 85 +++ scripts/test-fixes.ts | 80 +++ scripts/test-greeting-detection.ts | 94 +++ scripts/test-intent-classification.ts | 139 ++++ scripts/test-intent-during-registration.ts | 56 ++ scripts/test-location-specific.ts | 107 ++++ scripts/test-location.ts | 50 ++ scripts/test-network.ts | 73 +++ scripts/test-question-handling.ts | 160 +++++ scripts/test-rag-comprehensive.ts | 307 +++++++++ scripts/test-rag-live.ts | 119 ++++ scripts/test-rag.ts | 82 +++ scripts/test-real-world.ts | 153 +++++ scripts/test-registration-data-detection.ts | 75 +++ scripts/test-registration-flow.ts | 49 ++ scripts/test-registration-questions.ts | 114 ++++ scripts/test-remaining-issues.ts | 53 ++ scripts/test-search.ts | 29 + scripts/test-tech-questions.ts | 47 ++ src/app/api/chat/route.ts | 404 +++++++++++- src/components/ChatBox.tsx | 107 +++- src/lib/geminiService.ts | 668 ++++++++++++++++++++ src/lib/knowledgeBase.ts | 657 +++++++++++++++++++ src/lib/stateMachine.ts | 4 +- src/lib/vectorService.ts | 202 ++++++ 38 files changed, 5179 insertions(+), 43 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 scripts/check-api-keys.ts create mode 100644 scripts/check-api-rate-limit.ts create mode 100644 scripts/debug-joke.ts create mode 100644 scripts/final-comprehensive-test.ts create mode 100644 scripts/setup-pinecone.ts create mode 100644 scripts/test-all-questions.ts create mode 100644 scripts/test-api-key.ts create mode 100644 scripts/test-complete-system.ts create mode 100644 scripts/test-content-filter.ts create mode 100644 scripts/test-conversational-flow.ts create mode 100644 scripts/test-event-details-query.ts create mode 100644 scripts/test-fixes.ts create mode 100644 scripts/test-greeting-detection.ts create mode 100644 scripts/test-intent-classification.ts create mode 100644 scripts/test-intent-during-registration.ts create mode 100644 scripts/test-location-specific.ts create mode 100644 scripts/test-location.ts create mode 100644 scripts/test-network.ts create mode 100644 scripts/test-question-handling.ts create mode 100644 scripts/test-rag-comprehensive.ts create mode 100644 scripts/test-rag-live.ts create mode 100644 scripts/test-rag.ts create mode 100644 scripts/test-real-world.ts create mode 100644 scripts/test-registration-data-detection.ts create mode 100644 scripts/test-registration-flow.ts create mode 100644 scripts/test-registration-questions.ts create mode 100644 scripts/test-remaining-issues.ts create mode 100644 scripts/test-search.ts create mode 100644 scripts/test-tech-questions.ts create mode 100644 src/lib/geminiService.ts create mode 100644 src/lib/knowledgeBase.ts create mode 100644 src/lib/vectorService.ts diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..fccd125 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(cat:*)" + ], + "deny": [], + "ask": [] + } +} diff --git a/package-lock.json b/package-lock.json index 97e71af..8ef6454 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "coderush2025", "version": "0.1.0", "dependencies": { + "@google/generative-ai": "^0.24.1", + "@pinecone-database/pinecone": "^6.1.2", "@react-three/drei": "^10.7.6", "@react-three/fiber": "^9.3.0", "@types/three": "^0.180.0", @@ -1411,6 +1413,15 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@google/generative-ai": { + "version": "0.24.1", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.24.1.tgz", + "integrity": "sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -2180,6 +2191,15 @@ "node": ">=12.4.0" } }, + "node_modules/@pinecone-database/pinecone": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@pinecone-database/pinecone/-/pinecone-6.1.2.tgz", + "integrity": "sha512-ydIlbtgIIHFgBL08sPzua5ckmOgtjgDz8xg21CnP1fqnnEgDmOlnfd10MRKU+fvFRhDlh4Md37SwZDr0d4cBqg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", diff --git a/package.json b/package.json index e34449c..93d3921 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "setup-admin": "tsx scripts/setup-admin.ts" }, "dependencies": { + "@google/generative-ai": "^0.24.1", + "@pinecone-database/pinecone": "^6.1.2", "@react-three/drei": "^10.7.6", "@react-three/fiber": "^9.3.0", "@types/three": "^0.180.0", diff --git a/scripts/check-api-keys.ts b/scripts/check-api-keys.ts new file mode 100644 index 0000000..0c01fb1 --- /dev/null +++ b/scripts/check-api-keys.ts @@ -0,0 +1,52 @@ +/** + * Check API Keys Configuration + * Verifies that Gemini and Pinecone API keys are properly loaded + */ + +console.log('๐Ÿ”‘ Checking API Keys Configuration\n'); +console.log('='.repeat(60)); + +// Check Gemini API Key +const geminiKey = process.env.GOOGLE_GEMINI_API_KEY; +console.log('\n๐Ÿ“Š Gemini API Key:'); +if (geminiKey) { + console.log(`โœ… Configured (${geminiKey.substring(0, 20)}...)`); +} else { + console.log('โŒ NOT configured'); + console.log('โš ๏ธ Set GOOGLE_GEMINI_API_KEY in .env.local'); +} + +// Check Pinecone API Key +const pineconeKey = process.env.PINECONE_API_KEY; +console.log('\n๐Ÿ“Š Pinecone API Key:'); +if (pineconeKey) { + console.log(`โœ… Configured (${pineconeKey.substring(0, 20)}...)`); +} else { + console.log('โŒ NOT configured'); + console.log('โš ๏ธ Set PINECONE_API_KEY in .env.local'); +} + +// Check MongoDB URI +const mongoUri = process.env.MONGODB_URI; +console.log('\n๐Ÿ“Š MongoDB URI:'); +if (mongoUri) { + console.log('โœ… Configured'); +} else { + console.log('โŒ NOT configured'); +} + +console.log('\n' + '='.repeat(60)); + +if (geminiKey && pineconeKey) { + console.log('\nโœ… All API keys are configured!'); + console.log('\n๐Ÿ“ Next steps:'); + console.log('1. If you just added/changed keys, restart your Next.js dev server'); + console.log('2. Run: npm run dev'); + console.log('3. Test the chat assistant in your browser'); +} else { + console.log('\nโŒ Some API keys are missing!'); + console.log('\n๐Ÿ“ Fix:'); + console.log('1. Check that .env.local exists in project root'); + console.log('2. Add missing keys to .env.local'); + console.log('3. Restart Next.js dev server: npm run dev'); +} diff --git a/scripts/check-api-rate-limit.ts b/scripts/check-api-rate-limit.ts new file mode 100644 index 0000000..94675eb --- /dev/null +++ b/scripts/check-api-rate-limit.ts @@ -0,0 +1,153 @@ +/** + * Check Gemini API Rate Limit Status + * This script tests your API key and shows current limits + */ + +import dotenv from 'dotenv'; +import path from 'path'; + +// Load environment variables +dotenv.config({ path: path.join(process.cwd(), '.env.local') }); +dotenv.config({ path: path.join(process.cwd(), '.env') }); + +import { GoogleGenerativeAI } from '@google/generative-ai'; + +async function checkRateLimit() { + console.log('๐Ÿ” Checking Gemini API Rate Limit Status...\n'); + console.log('=' .repeat(70)); + + // Check if API key exists + const apiKey = process.env.GOOGLE_GEMINI_API_KEY; + if (!apiKey) { + console.log('โŒ ERROR: GOOGLE_GEMINI_API_KEY not found in .env.local'); + console.log(' Please add your API key to .env.local'); + return; + } + + console.log('โœ… API Key found:', apiKey.substring(0, 10) + '...' + apiKey.substring(apiKey.length - 4)); + console.log(''); + + // Test different models + const models = [ + 'gemini-2.0-flash-exp', // Current model (10 RPM) + 'gemini-1.5-flash', // Alternative (15 RPM) + 'gemini-1.5-pro' // Pro version (2 RPM) + ]; + + const genAI = new GoogleGenerativeAI(apiKey); + + console.log('๐Ÿ“Š Testing API access with different models:\n'); + + for (const modelName of models) { + try { + console.log(`Testing: ${modelName}...`); + + const model = genAI.getGenerativeModel({ model: modelName }); + const startTime = Date.now(); + + const result = await model.generateContent('Say "Hello" in one word'); + const response = result.response.text(); + + const endTime = Date.now(); + const duration = endTime - startTime; + + console.log(` โœ… SUCCESS`); + console.log(` Response: "${response.trim()}"`); + console.log(` Latency: ${duration}ms`); + console.log(''); + + } catch (error: any) { + if (error.status === 429) { + console.log(` โŒ RATE LIMIT EXCEEDED`); + console.log(` Status: ${error.status} ${error.statusText}`); + + // Extract rate limit info from error + if (error.errorDetails) { + const quotaFailure = error.errorDetails.find((d: any) => + d['@type'] === 'type.googleapis.com/google.rpc.QuotaFailure' + ); + + if (quotaFailure?.violations) { + quotaFailure.violations.forEach((v: any) => { + console.log(` Quota Metric: ${v.quotaMetric}`); + console.log(` Quota Value: ${v.quotaValue} requests per minute`); + }); + } + + const retryInfo = error.errorDetails.find((d: any) => + d['@type'] === 'type.googleapis.com/google.rpc.RetryInfo' + ); + + if (retryInfo?.retryDelay) { + console.log(` Retry After: ${retryInfo.retryDelay}`); + } + } + console.log(''); + } else if (error.status === 404) { + console.log(` โš ๏ธ MODEL NOT AVAILABLE (may require different tier)`); + console.log(''); + } else { + console.log(` โŒ ERROR: ${error.message}`); + console.log(''); + } + } + + // Wait between tests to avoid rate limit + await new Promise(resolve => setTimeout(resolve, 1000)); + } + + console.log('=' .repeat(70)); + console.log('\n๐Ÿ“‹ Rate Limit Information:\n'); + + console.log('Free Tier Limits (per minute):'); + console.log(' โ€ข gemini-2.0-flash-exp: 10 RPM (current model)'); + console.log(' โ€ข gemini-1.5-flash: 15 RPM (recommended alternative)'); + console.log(' โ€ข gemini-1.5-pro: 2 RPM (more powerful but slower)'); + console.log(''); + + console.log('๐Ÿ’ก Tips:'); + console.log(' 1. For testing: Add 6-second delays between requests'); + console.log(' 2. For production: Current limits are fine (users space out questions)'); + console.log(' 3. If you hit limits: System falls back to keyword search automatically'); + console.log(''); + + // Test rapid requests to see limit + console.log('๐Ÿงช Testing rapid requests to find your actual limit...\n'); + console.log('Sending 5 quick requests in succession...'); + + const testModel = genAI.getGenerativeModel({ model: 'gemini-2.0-flash-exp' }); + let successCount = 0; + let failCount = 0; + + for (let i = 1; i <= 5; i++) { + try { + const result = await testModel.generateContent(`Count: ${i}`); + console.log(` ${i}. โœ… Success - ${result.response.text().trim()}`); + successCount++; + await new Promise(resolve => setTimeout(resolve, 500)); + } catch (error: any) { + if (error.status === 429) { + console.log(` ${i}. โŒ Rate Limited - Hit quota at request #${i}`); + failCount++; + break; + } + } + } + + console.log(''); + console.log('๐Ÿ“Š Rapid Test Results:'); + console.log(` Successful: ${successCount}/5`); + console.log(` Rate Limited: ${failCount > 0 ? 'Yes' : 'No'}`); + + if (failCount > 0) { + console.log(` \n โš ๏ธ You're currently at or near your rate limit.`); + console.log(` Wait ~60 seconds before running more tests.`); + } else { + console.log(` \n โœ… You have quota available for more requests.`); + } + + console.log('\n' + '=' .repeat(70)); + console.log('\nโœ… Rate limit check complete!'); +} + +checkRateLimit().catch(console.error); diff --git a/scripts/debug-joke.ts b/scripts/debug-joke.ts new file mode 100644 index 0000000..89d0fc1 --- /dev/null +++ b/scripts/debug-joke.ts @@ -0,0 +1,11 @@ +import { isSpamOrOffTopic } from '../src/lib/geminiService'; + +const message = 'tell me a joke'; +const result = isSpamOrOffTopic(message); + +console.log(`Message: "${message}"`); +console.log(`Is spam/off-topic: ${result}`); +console.log(`Expected: true`); + +// Check if 'joke' is in the message +console.log(`\nContains 'joke': ${message.toLowerCase().includes('joke')}`); diff --git a/scripts/final-comprehensive-test.ts b/scripts/final-comprehensive-test.ts new file mode 100644 index 0000000..5f245e4 --- /dev/null +++ b/scripts/final-comprehensive-test.ts @@ -0,0 +1,197 @@ +/** + * Final Comprehensive Test - Check Everything + * Tests all possible user questions to find any gaps + */ + +import dotenv from 'dotenv'; +import path from 'path'; + +dotenv.config({ path: path.join(process.cwd(), '.env.local') }); + +import { answerQuestionWithRAG, isSpamOrOffTopic } from '../src/lib/geminiService'; + +const comprehensiveQuestions = [ + // === EVENT BASICS === + { category: "Event Basics", q: "when is coderush" }, + { category: "Event Basics", q: "what time does it start" }, + { category: "Event Basics", q: "where is it" }, + { category: "Event Basics", q: "how long is the event" }, + { category: "Event Basics", q: "what is coderush" }, + { category: "Event Basics", q: "when is awards ceremony" }, + + // === LOCATION & VENUE === + { category: "Location", q: "send me location" }, + { category: "Location", q: "how do i get there" }, + { category: "Location", q: "map link please" }, + { category: "Location", q: "what is the address" }, + + // === TEAM === + { category: "Team", q: "how many members" }, + { category: "Team", q: "can we have 3 people" }, + { category: "Team", q: "can batch 23 and 24 mix" }, + { category: "Team", q: "can i be in two teams" }, + { category: "Team", q: "what if someone drops out" }, + + // === REGISTRATION === + { category: "Registration", q: "how to register" }, + { category: "Registration", q: "can i edit registration" }, + { category: "Registration", q: "index number format" }, + { category: "Registration", q: "i made a mistake" }, + { category: "Registration", q: "registration deadline" }, + + // === TECHNOLOGIES === + { category: "Tech", q: "can we use react" }, + { category: "Tech", q: "can we use nextjs" }, + { category: "Tech", q: "can we use python" }, + { category: "Tech", q: "what languages are allowed" }, + { category: "Tech", q: "can we use tailwind css" }, + { category: "Tech", q: "can we use bootstrap" }, + + // === AI TOOLS === + { category: "AI Tools", q: "can we use chatgpt" }, + { category: "AI Tools", q: "can we use copilot" }, + { category: "AI Tools", q: "are ai tools allowed" }, + { category: "AI Tools", q: "can we use claude" }, + + // === SUBMISSION === + { category: "Submission", q: "how to submit" }, + { category: "Submission", q: "submission deadline" }, + { category: "Submission", q: "what to submit" }, + { category: "Submission", q: "do we need video" }, + { category: "Submission", q: "github public or private" }, + { category: "Submission", q: "can we submit late" }, + + // === PROJECT/SCENARIO === + { category: "Scenario", q: "what should we build" }, + { category: "Scenario", q: "is there a theme" }, + { category: "Scenario", q: "what are scenario questions" }, + { category: "Scenario", q: "give me example scenario" }, + { category: "Scenario", q: "what problems will we solve" }, + + // === RULES & REQUIREMENTS === + { category: "Rules", q: "what are the rules" }, + { category: "Rules", q: "any restrictions" }, + { category: "Rules", q: "can we start coding before" }, + { category: "Rules", q: "what to bring" }, + { category: "Rules", q: "is food provided" }, + { category: "Rules", q: "can we leave early" }, + + // === PRIZES & WINNERS === + { category: "Prizes", q: "what are the prizes" }, + { category: "Prizes", q: "how many winners" }, + { category: "Prizes", q: "prize money" }, + + // === CONTACT & SUPPORT === + { category: "Contact", q: "who are the organizers" }, + { category: "Contact", q: "contact number" }, + { category: "Contact", q: "organizer email" }, + { category: "Contact", q: "i need help" }, + { category: "Contact", q: "i have a problem" }, + + // === TECHNICAL DETAILS === + { category: "Technical", q: "can we use apis" }, + { category: "Technical", q: "can we use databases" }, + { category: "Technical", q: "hosting provided" }, + { category: "Technical", q: "internet available" }, + { category: "Technical", q: "power outlets available" }, + + // === ELIGIBILITY === + { category: "Eligibility", q: "who can participate" }, + { category: "Eligibility", q: "only IT students" }, + { category: "Eligibility", q: "need experience" }, + { category: "Eligibility", q: "first year can join" }, + + // === SHOULD BE BLOCKED === + { category: "BLOCK", q: "what is react", shouldBlock: true }, + { category: "BLOCK", q: "teach me python", shouldBlock: true }, + { category: "BLOCK", q: "what is sex", shouldBlock: true }, + { category: "BLOCK", q: "fuck you", shouldBlock: true }, + { category: "BLOCK", q: "weather today", shouldBlock: true }, +]; + +async function runFinalTest() { + console.log('๐ŸŽฏ FINAL COMPREHENSIVE TEST - Checking Everything!\n'); + console.log('='.repeat(100)); + + let answered = 0; + let blocked = 0; + let noAnswer = 0; + let errors = 0; + const missingTopics: string[] = []; + + for (const test of comprehensiveQuestions) { + console.log(`\n[${test.category}] "${test.q}"`); + console.log('-'.repeat(100)); + + try { + // Check if should be blocked + const isBlocked = isSpamOrOffTopic(test.q); + + if (test.shouldBlock) { + if (isBlocked) { + console.log('โœ… CORRECTLY BLOCKED'); + blocked++; + } else { + console.log('โŒ SHOULD BE BLOCKED BUT WASN\'T!'); + errors++; + } + continue; + } + + if (isBlocked) { + console.log('โŒ WRONGLY BLOCKED - Valid question!'); + errors++; + missingTopics.push(`${test.category}: ${test.q}`); + continue; + } + + // Get answer + const answer = await answerQuestionWithRAG(test.q, { state: 'IDLE' }); + + // Check quality + if (answer.includes('having trouble') || answer.includes('only help with CodeRush') || answer.length < 30) { + console.log('โš ๏ธ NO CLEAR ANSWER'); + console.log(`Response: ${answer.substring(0, 100)}...`); + noAnswer++; + missingTopics.push(`${test.category}: ${test.q}`); + } else { + console.log('โœ… ANSWERED'); + console.log(`Response: ${answer.substring(0, 150)}...`); + answered++; + } + + // Small delay + await new Promise(resolve => setTimeout(resolve, 1500)); + + } catch (error: any) { + console.log('โŒ ERROR:', error.message); + errors++; + } + } + + console.log('\n' + '='.repeat(100)); + console.log('\n๐Ÿ“Š FINAL RESULTS:'); + console.log(` โœ… Questions Answered: ${answered}`); + console.log(` ๐Ÿšซ Correctly Blocked: ${blocked}`); + console.log(` โš ๏ธ No Clear Answer: ${noAnswer}`); + console.log(` โŒ Errors: ${errors}`); + console.log(` ๐Ÿ“ Total Tests: ${comprehensiveQuestions.length}`); + + const successRate = (answered + blocked) / comprehensiveQuestions.length * 100; + console.log(`\n ๐ŸŽฏ Success Rate: ${successRate.toFixed(1)}%`); + + if (missingTopics.length > 0) { + console.log('\nโš ๏ธ MISSING OR UNCLEAR TOPICS:'); + missingTopics.forEach(topic => console.log(` - ${topic}`)); + } + + if (successRate >= 95) { + console.log('\nโœ… EXCELLENT! Bot is production ready! ๐Ÿš€'); + } else if (successRate >= 85) { + console.log('\n๐Ÿ‘ GOOD! Some topics need clarification.'); + } else { + console.log('\nโš ๏ธ NEEDS IMPROVEMENT - Several topics missing.'); + } +} + +runFinalTest().catch(console.error); diff --git a/scripts/setup-pinecone.ts b/scripts/setup-pinecone.ts new file mode 100644 index 0000000..4c96117 --- /dev/null +++ b/scripts/setup-pinecone.ts @@ -0,0 +1,66 @@ +/** + * Setup Script: Populate Pinecone Vector Database + * Run once: npx tsx scripts/setup-pinecone.ts + */ + +// Load environment variables +import dotenv from 'dotenv'; +import path from 'path'; + +// Load .env.local first, then .env +dotenv.config({ path: path.join(process.cwd(), '.env.local') }); +dotenv.config({ path: path.join(process.cwd(), '.env') }); + +import { initializePineconeIndex, populateVectorDatabase } from '../src/lib/vectorService'; + +async function setup() { + console.log('๐Ÿš€ Starting Pinecone setup...\n'); + + // Check if API key is configured + if (!process.env.PINECONE_API_KEY || process.env.PINECONE_API_KEY === 'your-key-here-if-you-want-it') { + console.log('โš ๏ธ Pinecone API key not configured in .env'); + console.log('๐Ÿ“ To use Pinecone:'); + console.log(' 1. Get free API key from: https://www.pinecone.io/'); + console.log(' 2. Add to .env: PINECONE_API_KEY=your-actual-key'); + console.log(' 3. Run this script again\n'); + console.log('โœ… System will use keyword search (works great!)'); + process.exit(0); + } + + try { + // Step 1: Initialize index + console.log('๐Ÿ“Š Step 1: Initializing Pinecone index...'); + const indexCreated = await initializePineconeIndex(); + + if (!indexCreated) { + console.error('โŒ Failed to initialize Pinecone index'); + process.exit(1); + } + + console.log('โœ… Index initialized successfully\n'); + + // Step 2: Populate with knowledge base + console.log('๐Ÿ“š Step 2: Populating vector database...'); + console.log(' (This may take 1-2 minutes)\n'); + + const populated = await populateVectorDatabase(); + + if (!populated) { + console.error('โŒ Failed to populate vector database'); + process.exit(1); + } + + console.log('\nโœ… SUCCESS! Pinecone is ready to use!'); + console.log('\n๐ŸŽฏ Your chat assistant will now use:'); + console.log(' - Semantic search (Pinecone)'); + console.log(' - Better synonym understanding'); + console.log(' - Improved accuracy\n'); + + } catch (error) { + console.error('โŒ Setup failed:', error); + console.log('\n๐Ÿ’ก Tip: System will fall back to keyword search automatically'); + process.exit(1); + } +} + +setup(); diff --git a/scripts/test-all-questions.ts b/scripts/test-all-questions.ts new file mode 100644 index 0000000..5ca9221 --- /dev/null +++ b/scripts/test-all-questions.ts @@ -0,0 +1,88 @@ +/** + * Test Script: Test all types of questions + */ + +import { searchByKeyword } from '../src/lib/knowledgeBase'; + +const testQueries = [ + // Event date & location + "when is the event", + "event date", + "what time", + "where is it", + "location please", + "venue", + "map link", + "how to get there", + "address", + "directions", + + // Team registration + "how to register", + "register my team", + "registration process", + "sign up", + "how many members", + "team size", + "batch requirements", + "can we join", + "eligibility", + + // Submission requirements + "what to submit", + "submission format", + "deliverables", + "deadline", + "when to submit", + "github link", + "demo video", + + // Technologies & tools + "which tech", + "can we use react", + "frameworks allowed", + "technology stack", + "programming languages", + "any restrictions", + + // General/vague questions + "tell me more", + "details", + "explain", + "info", + "about this", + "what is this" +]; + +console.log('๐Ÿ” Testing all question patterns...\n'); +console.log('=' .repeat(60)); + +let successCount = 0; +let failCount = 0; + +testQueries.forEach(query => { + const results = searchByKeyword(query, 3); + const status = results.length > 0 ? 'โœ…' : 'โŒ'; + + if (results.length > 0) { + successCount++; + } else { + failCount++; + } + + console.log(`${status} "${query}" โ†’ ${results.length} results`); + + if (results.length === 0) { + console.log(` โš ๏ธ NO RESULTS - needs fixing!`); + } +}); + +console.log('=' .repeat(60)); +console.log(`\n๐Ÿ“Š Results: ${successCount}/${testQueries.length} queries working`); +console.log(`โŒ Failed: ${failCount}`); + +if (failCount > 0) { + console.log('\nโš ๏ธ Some queries need better keyword matching!'); +} else { + console.log('\nโœ… All queries working perfectly!'); +} diff --git a/scripts/test-api-key.ts b/scripts/test-api-key.ts new file mode 100644 index 0000000..2405f45 --- /dev/null +++ b/scripts/test-api-key.ts @@ -0,0 +1,41 @@ +/** + * Simple API Key Test + */ + +import dotenv from 'dotenv'; +import path from 'path'; + +dotenv.config({ path: path.join(process.cwd(), '.env.local') }); + +import { GoogleGenerativeAI } from '@google/generative-ai'; + +async function testAPIKey() { + const apiKey = process.env.GOOGLE_GEMINI_API_KEY; + + console.log('๐Ÿ”‘ API Key loaded:', apiKey ? `${apiKey.substring(0, 20)}...` : 'NOT FOUND'); + + if (!apiKey) { + console.error('โŒ No API key found in .env.local'); + return; + } + + try { + console.log('\n๐Ÿ“ก Testing API connection...'); + const genAI = new GoogleGenerativeAI(apiKey); + + console.log('๐Ÿ“ค Testing gemini-2.0-flash-exp...'); + const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash-exp' }); + const result = await model.generateContent('Say "Hello World" in one word'); + const response = result.response.text(); + + console.log('โœ… SUCCESS! Gemini 2.0 Flash is working!'); + console.log('๐Ÿ“จ Response:', response); + } catch (error: any) { + console.error('โŒ ERROR:', error.message); + if (error.status) { + console.error('Status:', error.status); + } + } +} + +testAPIKey(); diff --git a/scripts/test-complete-system.ts b/scripts/test-complete-system.ts new file mode 100644 index 0000000..76242b1 --- /dev/null +++ b/scripts/test-complete-system.ts @@ -0,0 +1,438 @@ +/** + * Comprehensive System Test + * Tests the complete registration flow with RAG-powered chat assistant + */ + +import { answerQuestionWithRAG, classifyIntent, isSpamOrOffTopic, checkQuestionRateLimit } from '../src/lib/geminiService'; +import { searchVectorDatabase, isPineconeAvailable, generateEmbedding } from '../src/lib/vectorService'; +import { searchByKeyword, knowledgeBase } from '../src/lib/knowledgeBase'; + +// ANSI color codes for terminal output +const colors = { + reset: '\x1b[0m', + bright: '\x1b[1m', + dim: '\x1b[2m', + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + cyan: '\x1b[36m', + magenta: '\x1b[35m', +}; + +function log(message: string, color: string = colors.reset) { + console.log(`${color}${message}${colors.reset}`); +} + +function logSection(title: string) { + console.log('\n' + '='.repeat(60)); + log(title, colors.cyan + colors.bright); + console.log('='.repeat(60)); +} + +function logTest(testName: string) { + log(`\n๐Ÿงช Test: ${testName}`, colors.blue); +} + +function logSuccess(message: string) { + log(`โœ… ${message}`, colors.green); +} + +function logError(message: string) { + log(`โŒ ${message}`, colors.red); +} + +function logWarning(message: string) { + log(`โš ๏ธ ${message}`, colors.yellow); +} + +function logInfo(message: string) { + log(`โ„น๏ธ ${message}`, colors.cyan); +} + +/** + * Test 1: Knowledge Base Integrity + */ +async function testKnowledgeBase() { + logSection('TEST 1: KNOWLEDGE BASE INTEGRITY'); + + try { + logTest('Verifying knowledge base structure'); + + // Check document count + if (knowledgeBase.length === 0) { + throw new Error('Knowledge base is empty'); + } + logSuccess(`Knowledge base contains ${knowledgeBase.length} documents`); + + // Check required fields + const invalidDocs = knowledgeBase.filter(doc => + !doc.id || !doc.category || !doc.question || !doc.answer || !doc.keywords || doc.priority === undefined + ); + + if (invalidDocs.length > 0) { + throw new Error(`Found ${invalidDocs.length} invalid documents`); + } + logSuccess('All documents have required fields'); + + // Check categories + const categories = [...new Set(knowledgeBase.map(doc => doc.category))]; + logInfo(`Categories: ${categories.join(', ')}`); + logSuccess(`Found ${categories.length} categories`); + + // Test keyword search + logTest('Testing keyword search functionality'); + const venueResults = searchByKeyword('venue location', 3); + if (venueResults.length === 0) { + throw new Error('Keyword search returned no results for "venue location"'); + } + logSuccess(`Keyword search found ${venueResults.length} relevant documents`); + logInfo(`Top result: "${venueResults[0].question}"`); + + // Test search for registration info + const regResults = searchByKeyword('team name rules', 3); + if (regResults.length === 0) { + throw new Error('Keyword search returned no results for "team name rules"'); + } + logSuccess(`Found ${regResults.length} registration-related documents`); + + logSuccess('โœ“ Knowledge Base tests passed'); + return true; + } catch (error) { + logError(`Knowledge Base test failed: ${error instanceof Error ? error.message : String(error)}`); + return false; + } +} + +/** + * Test 2: Pinecone Vector Database + */ +async function testVectorDatabase() { + logSection('TEST 2: PINECONE VECTOR DATABASE'); + + try { + // Check if Pinecone is configured + logTest('Checking Pinecone configuration'); + if (!process.env.PINECONE_API_KEY) { + logWarning('PINECONE_API_KEY not found in environment'); + logInfo('Vector search will use fallback keyword search'); + return true; // Not a failure, just a warning + } + logSuccess('Pinecone API key configured'); + + // Check if Pinecone index is available + logTest('Checking Pinecone index availability'); + const isAvailable = await isPineconeAvailable(); + + if (!isAvailable) { + logWarning('Pinecone index not available'); + logInfo('Run "npm run setup-pinecone" to initialize the vector database'); + logInfo('System will use fallback keyword search'); + return true; // Not a failure + } + logSuccess('Pinecone index is available'); + + // Test embedding generation + logTest('Testing embedding generation with Gemini'); + const testText = 'When is CodeRush 2025?'; + const embedding = await generateEmbedding(testText); + + if (!embedding || embedding.length !== 768) { + throw new Error(`Invalid embedding dimensions: expected 768, got ${embedding?.length || 0}`); + } + logSuccess(`Generated embedding with correct dimensions (768)`); + + // Test vector search + logTest('Testing semantic vector search'); + const searchResults = await searchVectorDatabase('event location venue', 3); + + if (searchResults.length === 0) { + throw new Error('Vector search returned no results'); + } + logSuccess(`Vector search found ${searchResults.length} relevant documents`); + logInfo(`Top result: "${searchResults[0].question}"`); + + // Test semantic understanding + logTest('Testing semantic understanding'); + const semanticResults = await searchVectorDatabase('where is the competition held?', 3); + if (semanticResults.length > 0) { + logSuccess('Successfully found venue information using semantic search'); + logInfo(`Matched: "${semanticResults[0].question}"`); + } + + logSuccess('โœ“ Vector Database tests passed'); + return true; + } catch (error) { + logError(`Vector Database test failed: ${error instanceof Error ? error.message : String(error)}`); + return false; + } +} + +/** + * Test 3: Gemini AI Integration + */ +async function testGeminiIntegration() { + logSection('TEST 3: GEMINI AI INTEGRATION'); + + try { + // Check API key + logTest('Checking Gemini API configuration'); + if (!process.env.GOOGLE_GEMINI_API_KEY) { + throw new Error('GOOGLE_GEMINI_API_KEY not found in environment'); + } + logSuccess('Gemini API key configured'); + + // Test intent classification + logTest('Testing intent classification'); + + const questionIntent = await classifyIntent('What is the event date?', 'IDLE'); + if (questionIntent !== 'QUESTION') { + throw new Error(`Expected QUESTION intent, got ${questionIntent}`); + } + logSuccess('Correctly classified question intent'); + + const registrationIntent = await classifyIntent('TeamRocket', 'IDLE'); + if (registrationIntent !== 'REGISTRATION') { + logWarning(`Classification: "${registrationIntent}" - may vary based on context`); + } else { + logSuccess('Correctly classified registration intent'); + } + + const greetingIntent = await classifyIntent('hello', 'IDLE'); + if (greetingIntent !== 'GREETING') { + throw new Error(`Expected GREETING intent, got ${greetingIntent}`); + } + logSuccess('Correctly classified greeting intent'); + + // Test spam detection + logTest('Testing spam and off-topic detection'); + + if (!isSpamOrOffTopic('When is CodeRush 2025?')) { + logSuccess('Valid question not marked as spam'); + } else { + throw new Error('Valid question incorrectly marked as spam'); + } + + if (isSpamOrOffTopic('buy cheap products now')) { + logSuccess('Spam detected correctly'); + } else { + logWarning('Spam detection may need tuning'); + } + + if (isSpamOrOffTopic('what is react')) { + logSuccess('Off-topic question detected'); + } else { + logWarning('Off-topic detection may need tuning'); + } + + // Test rate limiting + logTest('Testing rate limiting'); + const testSessionId = 'test-session-123'; + + for (let i = 0; i < 5; i++) { + if (!checkQuestionRateLimit(testSessionId)) { + throw new Error('Rate limit triggered too early'); + } + } + logSuccess('Rate limiting allows reasonable number of requests'); + + // Test RAG Q&A + logTest('Testing RAG-powered question answering'); + const answer1 = await answerQuestionWithRAG('When is CodeRush 2025?'); + + if (!answer1 || answer1.length < 10) { + throw new Error('RAG returned empty or invalid answer'); + } + logSuccess('RAG successfully answered event date question'); + logInfo(`Answer: ${answer1.substring(0, 100)}...`); + + const answer2 = await answerQuestionWithRAG('Where is the venue?'); + if (!answer2 || answer2.length < 10) { + throw new Error('RAG returned empty or invalid answer'); + } + logSuccess('RAG successfully answered venue question'); + logInfo(`Answer: ${answer2.substring(0, 100)}...`); + + const answer3 = await answerQuestionWithRAG('What are the team requirements?'); + if (!answer3 || answer3.length < 10) { + throw new Error('RAG returned empty or invalid answer'); + } + logSuccess('RAG successfully answered team requirements question'); + logInfo(`Answer: ${answer3.substring(0, 100)}...`); + + logSuccess('โœ“ Gemini AI Integration tests passed'); + return true; + } catch (error) { + logError(`Gemini Integration test failed: ${error instanceof Error ? error.message : String(error)}`); + if (error instanceof Error && error.message.includes('API key')) { + logInfo('Make sure GOOGLE_GEMINI_API_KEY is set in your .env.local file'); + } + return false; + } +} + +/** + * Test 4: Registration State Machine + */ +async function testRegistrationFlow() { + logSection('TEST 4: REGISTRATION STATE MACHINE'); + + try { + logTest('Verifying registration states'); + + // Import state machine + const { states, validators, MEMBER_COUNT } = await import('../src/lib/stateMachine'); + + // Check required states + const requiredStates = ['BATCH_SELECTION', 'MEMBER_DETAILS', 'CONFIRMATION', 'DONE']; + for (const state of requiredStates) { + if (!states[state]) { + throw new Error(`Missing required state: ${state}`); + } + } + logSuccess(`All required states present: ${requiredStates.join(', ')}`); + + // Test validators + logTest('Testing input validators'); + + if (!validators.batch('23') || !validators.batch('24')) { + throw new Error('Batch validator failed for valid input'); + } + if (validators.batch('25') || validators.batch('22')) { + throw new Error('Batch validator accepted invalid input'); + } + logSuccess('Batch validator working correctly'); + + if (!validators.index('234001T') || !validators.index('244001A')) { + throw new Error('Index validator failed for valid input'); + } + if (validators.index('12345') || validators.index('234001t')) { + throw new Error('Index validator accepted invalid input'); + } + logSuccess('Index validator working correctly'); + + if (!validators.email('test@example.com') || !validators.email('user.name+tag@example.co.uk')) { + throw new Error('Email validator failed for valid input'); + } + if (validators.email('invalid.email') || validators.email('@example.com')) { + throw new Error('Email validator accepted invalid input'); + } + logSuccess('Email validator working correctly'); + + // Check member count + if (MEMBER_COUNT !== 4) { + throw new Error(`Expected MEMBER_COUNT = 4, got ${MEMBER_COUNT}`); + } + logSuccess(`Team size correctly set to ${MEMBER_COUNT} members`); + + logSuccess('โœ“ Registration State Machine tests passed'); + return true; + } catch (error) { + logError(`Registration Flow test failed: ${error instanceof Error ? error.message : String(error)}`); + return false; + } +} + +/** + * Test 5: Environment Configuration + */ +async function testEnvironmentConfig() { + logSection('TEST 5: ENVIRONMENT CONFIGURATION'); + + try { + logTest('Checking required environment variables'); + + const requiredVars = [ + { name: 'MONGODB_URI', critical: true }, + { name: 'GOOGLE_GEMINI_API_KEY', critical: true }, + { name: 'PINECONE_API_KEY', critical: false }, + ]; + + let allCriticalPresent = true; + + for (const envVar of requiredVars) { + if (process.env[envVar.name]) { + logSuccess(`${envVar.name} is configured`); + } else { + if (envVar.critical) { + logError(`${envVar.name} is MISSING (CRITICAL)`); + allCriticalPresent = false; + } else { + logWarning(`${envVar.name} is not configured (optional)`); + } + } + } + + if (!allCriticalPresent) { + throw new Error('Critical environment variables are missing'); + } + + logSuccess('โœ“ Environment Configuration tests passed'); + return true; + } catch (error) { + logError(`Environment Config test failed: ${error instanceof Error ? error.message : String(error)}`); + logInfo('Create a .env.local file with required variables'); + return false; + } +} + +/** + * Main test runner + */ +async function runAllTests() { + log('\n' + 'โ–ˆ'.repeat(60), colors.magenta); + log(' CODERUSH 2025 - COMPREHENSIVE SYSTEM TEST', colors.magenta + colors.bright); + log(' RAG-Powered Registration System', colors.magenta); + log('โ–ˆ'.repeat(60) + '\n', colors.magenta); + + const results = { + knowledgeBase: false, + vectorDatabase: false, + geminiIntegration: false, + registrationFlow: false, + environmentConfig: false, + }; + + // Run tests sequentially + results.environmentConfig = await testEnvironmentConfig(); + results.knowledgeBase = await testKnowledgeBase(); + results.vectorDatabase = await testVectorDatabase(); + results.geminiIntegration = await testGeminiIntegration(); + results.registrationFlow = await testRegistrationFlow(); + + // Summary + logSection('TEST SUMMARY'); + + const testNames = Object.keys(results) as Array; + const passed = testNames.filter(test => results[test]).length; + const total = testNames.length; + + console.log(''); + testNames.forEach(test => { + const status = results[test] ? 'โœ… PASS' : 'โŒ FAIL'; + const color = results[test] ? colors.green : colors.red; + log(`${status} - ${test}`, color); + }); + + console.log('\n' + '='.repeat(60)); + const summaryColor = passed === total ? colors.green : passed > total / 2 ? colors.yellow : colors.red; + log(`RESULT: ${passed}/${total} tests passed`, summaryColor + colors.bright); + console.log('='.repeat(60) + '\n'); + + if (passed === total) { + log('๐ŸŽ‰ All systems operational! Registration system is ready.', colors.green + colors.bright); + } else if (passed > total / 2) { + log('โš ๏ธ Some issues detected. Review warnings above.', colors.yellow + colors.bright); + } else { + log('โŒ Critical issues detected. Please fix errors before deployment.', colors.red + colors.bright); + } + + // Exit with appropriate code + process.exit(passed === total ? 0 : 1); +} + +// Run tests +runAllTests().catch(error => { + logError(`Fatal error: ${error instanceof Error ? error.message : String(error)}`); + process.exit(1); +}); diff --git a/scripts/test-content-filter.ts b/scripts/test-content-filter.ts new file mode 100644 index 0000000..7281325 --- /dev/null +++ b/scripts/test-content-filter.ts @@ -0,0 +1,65 @@ +/** + * Test Content Filtering + */ + +import dotenv from 'dotenv'; +import path from 'path'; + +dotenv.config({ path: path.join(process.cwd(), '.env.local') }); + +import { isSpamOrOffTopic } from '../src/lib/geminiService'; + +const testCases = [ + // Inappropriate content + { question: "what is sex", shouldBlock: true, category: "Inappropriate" }, + { question: "show me porn", shouldBlock: true, category: "Inappropriate" }, + { question: "how to hack", shouldBlock: true, category: "Inappropriate" }, + { question: "dating girls", shouldBlock: true, category: "Inappropriate" }, + { question: "buy drugs", shouldBlock: true, category: "Inappropriate" }, + + // Programming questions + { question: "What is React?", shouldBlock: true, category: "Programming" }, + { question: "What is Python?", shouldBlock: true, category: "Programming" }, + { question: "teach me coding", shouldBlock: true, category: "Programming" }, + + // Off-topic + { question: "What's the weather?", shouldBlock: true, category: "Off-topic" }, + { question: "Tell me a joke", shouldBlock: true, category: "Off-topic" }, + { question: "How old are you?", shouldBlock: true, category: "Off-topic" }, + + // Valid CodeRush questions (should NOT block) + { question: "When is CodeRush?", shouldBlock: false, category: "Valid" }, + { question: "How many team members?", shouldBlock: false, category: "Valid" }, + { question: "What's the submission format?", shouldBlock: false, category: "Valid" }, + { question: "Can I edit my registration?", shouldBlock: false, category: "Valid" }, +]; + +console.log('๐Ÿงช Testing Content Filter...\n'); +console.log('='.repeat(80)); + +let passed = 0; +let failed = 0; + +for (const test of testCases) { + const isBlocked = isSpamOrOffTopic(test.question); + const result = isBlocked === test.shouldBlock ? 'โœ… PASS' : 'โŒ FAIL'; + + if (isBlocked === test.shouldBlock) { + passed++; + } else { + failed++; + } + + console.log(`\n${result} [${test.category}]`); + console.log(`Question: "${test.question}"`); + console.log(`Expected: ${test.shouldBlock ? 'BLOCK' : 'ALLOW'} | Got: ${isBlocked ? 'BLOCKED' : 'ALLOWED'}`); +} + +console.log('\n' + '='.repeat(80)); +console.log(`\n๐Ÿ“Š Results: ${passed} passed, ${failed} failed out of ${testCases.length} tests`); + +if (failed === 0) { + console.log('โœ… All tests passed! Content filter is working correctly.'); +} else { + console.log('โŒ Some tests failed. Please review the filter logic.'); +} diff --git a/scripts/test-conversational-flow.ts b/scripts/test-conversational-flow.ts new file mode 100644 index 0000000..3ae8965 --- /dev/null +++ b/scripts/test-conversational-flow.ts @@ -0,0 +1,166 @@ +/** + * Test: Conversational Flow + * Tests friendly conversational messages and responses + */ + +import { classifyIntent, getConversationalResponse, isSpamOrOffTopic } from '../src/lib/geminiService'; + +console.log('๐Ÿงช Testing Conversational Flow\n'); +console.log('='.repeat(60)); + +// Test 1: Intent Classification +console.log('\n๐Ÿ“Š Test 1: Intent Classification for Conversational Messages\n'); + +const intentTests = [ + // Conversational messages + { message: 'how are you', state: 'IDLE', expected: 'CONVERSATIONAL' }, + { message: 'thank you', state: 'IDLE', expected: 'CONVERSATIONAL' }, + { message: 'thanks', state: 'IDLE', expected: 'CONVERSATIONAL' }, + { message: 'ok', state: 'IDLE', expected: 'CONVERSATIONAL' }, + { message: 'cool', state: 'IDLE', expected: 'CONVERSATIONAL' }, + { message: 'bye', state: 'IDLE', expected: 'CONVERSATIONAL' }, + { message: 'lol', state: 'IDLE', expected: 'CONVERSATIONAL' }, + { message: 'who are you', state: 'IDLE', expected: 'CONVERSATIONAL' }, + { message: 'are you a bot', state: 'IDLE', expected: 'CONVERSATIONAL' }, + { message: 'awesome', state: 'IDLE', expected: 'CONVERSATIONAL' }, + { message: 'perfect', state: 'IDLE', expected: 'CONVERSATIONAL' }, + + // Should still work during registration + { message: 'how are you', state: 'MEMBER_DETAILS', expected: 'CONVERSATIONAL' }, + { message: 'thanks', state: 'TEAM_BATCH', expected: 'CONVERSATIONAL' }, + + // Not conversational + { message: 'what is the event', state: 'IDLE', expected: 'QUESTION' }, + { message: 'TeamAlpha', state: 'IDLE', expected: 'REGISTRATION' }, +]; + +async function testIntentClassification() { + let passed = 0; + let failed = 0; + + for (const test of intentTests) { + const result = await classifyIntent(test.message, test.state); + if (result === test.expected) { + console.log(`โœ… "${test.message}" โ†’ ${result}`); + passed++; + } else { + console.log(`โŒ "${test.message}" โ†’ Expected: ${test.expected}, Got: ${result}`); + failed++; + } + } + + console.log(`\n๐Ÿ“Š Intent Classification: ${passed}/${intentTests.length} passed\n`); + return { passed, failed }; +} + +// Test 2: Conversational Responses +console.log('๐Ÿ“Š Test 2: Conversational Response Quality\n'); + +const responseTests = [ + { message: 'how are you', shouldInclude: ['doing great', 'CodeRush'] }, + { message: 'thank you', shouldInclude: ['welcome', 'help'] }, + { message: 'thanks', shouldInclude: ['welcome', 'help'] }, + { message: 'ok', shouldInclude: ['Awesome', 'team name'] }, + { message: 'cool', shouldInclude: ['Awesome', 'team name'] }, + { message: 'bye', shouldInclude: ['See you', 'CodeRush'] }, + { message: 'lol', shouldInclude: ['glad', 'help'] }, + { message: 'who are you', shouldInclude: ['assistant', 'help'] }, + { message: 'are you a bot', shouldInclude: ['assistant', 'help'] }, +]; + +function testConversationalResponses() { + let passed = 0; + let failed = 0; + + for (const test of responseTests) { + const response = getConversationalResponse(test.message); + const allIncluded = test.shouldInclude.every(phrase => + response.toLowerCase().includes(phrase.toLowerCase()) + ); + + if (allIncluded) { + console.log(`โœ… "${test.message}"`); + console.log(` Response: ${response.substring(0, 80)}...`); + passed++; + } else { + console.log(`โŒ "${test.message}"`); + console.log(` Missing: ${test.shouldInclude.filter(phrase => !response.toLowerCase().includes(phrase.toLowerCase())).join(', ')}`); + failed++; + } + } + + console.log(`\n๐Ÿ“Š Response Quality: ${passed}/${responseTests.length} passed\n`); + return { passed, failed }; +} + +// Test 3: Spam Detection (should NOT flag conversational) +console.log('๐Ÿ“Š Test 3: Spam Detection for Conversational Messages\n'); + +const spamTests = [ + // Should NOT be flagged as spam (conversational) + { message: 'how are you', expected: false, description: 'Conversational - how are you' }, + { message: 'thank you', expected: false, description: 'Conversational - thank you' }, + { message: 'who are you', expected: false, description: 'Conversational - who are you' }, + { message: 'cool', expected: false, description: 'Conversational - cool' }, + { message: 'bye', expected: false, description: 'Conversational - bye' }, + + // Should be flagged as spam/off-topic + { message: 'what is the weather', expected: true, description: 'Off-topic - weather' }, + { message: 'tell me a joke', expected: true, description: 'Off-topic - joke' }, + { message: 'what is react', expected: true, description: 'Off-topic - programming tutorial' }, +]; + +function testSpamDetection() { + let passed = 0; + let failed = 0; + + for (const test of spamTests) { + const result = isSpamOrOffTopic(test.message); + if (result === test.expected) { + console.log(`โœ… ${test.description}`); + passed++; + } else { + console.log(`โŒ ${test.description} - Expected: ${test.expected}, Got: ${result}`); + failed++; + } + } + + console.log(`\n๐Ÿ“Š Spam Detection: ${passed}/${spamTests.length} passed\n`); + return { passed, failed }; +} + +// Run all tests +async function runAllTests() { + const results = { + intentClassification: await testIntentClassification(), + conversationalResponses: testConversationalResponses(), + spamDetection: testSpamDetection() + }; + + console.log('='.repeat(60)); + console.log('๐Ÿ“Š FINAL SUMMARY\n'); + + const totalTests = intentTests.length + responseTests.length + spamTests.length; + const totalPassed = results.intentClassification.passed + + results.conversationalResponses.passed + + results.spamDetection.passed; + const totalFailed = results.intentClassification.failed + + results.conversationalResponses.failed + + results.spamDetection.failed; + + console.log(`Total Tests: ${totalTests}`); + console.log(`โœ… Passed: ${totalPassed}`); + console.log(`โŒ Failed: ${totalFailed}`); + console.log('='.repeat(60)); + + if (totalFailed === 0) { + console.log('\n๐ŸŽ‰ All conversational flow tests passed!'); + } else { + console.log('\nโš ๏ธ Some tests failed. Review output above.'); + } +} + +runAllTests().catch(error => { + console.error('Test execution error:', error); + process.exit(1); +}); diff --git a/scripts/test-event-details-query.ts b/scripts/test-event-details-query.ts new file mode 100644 index 0000000..4914db2 --- /dev/null +++ b/scripts/test-event-details-query.ts @@ -0,0 +1,85 @@ +/** + * Test: Event Details Query + * Test if "what is event details" query works with Pinecone + */ + +import { answerQuestionWithRAG } from '../src/lib/geminiService'; +import { searchVectorDatabase, isPineconeAvailable } from '../src/lib/vectorService'; +import { searchByKeyword } from '../src/lib/knowledgeBase'; + +async function testEventDetailsQuery() { + console.log('๐Ÿงช Testing Event Details Query\n'); + console.log('='.repeat(60)); + + // Test 1: Check if Pinecone is available + console.log('\n๐Ÿ“Š Test 1: Checking Pinecone availability...'); + const pineconeAvailable = await isPineconeAvailable(); + console.log(`Pinecone available: ${pineconeAvailable ? 'โœ… Yes' : 'โŒ No'}`); + + // Test 2: Test vector search + if (pineconeAvailable) { + console.log('\n๐Ÿ“Š Test 2: Testing vector search for "what is event details"...'); + try { + const vectorResults = await searchVectorDatabase('what is event details', 3); + console.log(`Found ${vectorResults.length} results from vector search:`); + vectorResults.forEach((doc, idx) => { + console.log(`\n${idx + 1}. ${doc.question}`); + console.log(` Category: ${doc.category}`); + console.log(` Priority: ${doc.priority}`); + console.log(` Answer: ${doc.answer.substring(0, 150)}...`); + }); + } catch (error) { + console.error('โŒ Vector search failed:', error); + } + } + + // Test 3: Test keyword search (fallback) + console.log('\n๐Ÿ“Š Test 3: Testing keyword search for "what is event details"...'); + const keywordResults = searchByKeyword('what is event details', 3); + console.log(`Found ${keywordResults.length} results from keyword search:`); + keywordResults.forEach((doc, idx) => { + console.log(`\n${idx + 1}. ${doc.question}`); + console.log(` Category: ${doc.category}`); + console.log(` Priority: ${doc.priority}`); + console.log(` Answer: ${doc.answer.substring(0, 150)}...`); + }); + + // Test 4: Full RAG test + console.log('\n๐Ÿ“Š Test 4: Testing full RAG with "what is event details"...'); + try { + const answer = await answerQuestionWithRAG('what is event details'); + console.log('\nโœ… RAG Response:'); + console.log('โ”€'.repeat(60)); + console.log(answer); + console.log('โ”€'.repeat(60)); + } catch (error) { + console.error('โŒ RAG test failed:', error); + } + + // Test 5: Alternative phrasings + console.log('\n๐Ÿ“Š Test 5: Testing alternative phrasings...'); + const queries = [ + 'tell me about the event', + 'what is coderush', + 'event information', + 'about the competition' + ]; + + for (const query of queries) { + console.log(`\n๐Ÿ” Query: "${query}"`); + try { + const answer = await answerQuestionWithRAG(query); + console.log(`โœ… Response: ${answer.substring(0, 200)}...`); + } catch (error) { + console.error(`โŒ Failed: ${error}`); + } + } + + console.log('\n' + '='.repeat(60)); + console.log('โœ… Test completed!'); +} + +testEventDetailsQuery().catch(error => { + console.error('Test execution error:', error); + process.exit(1); +}); diff --git a/scripts/test-fixes.ts b/scripts/test-fixes.ts new file mode 100644 index 0000000..e0a4946 --- /dev/null +++ b/scripts/test-fixes.ts @@ -0,0 +1,80 @@ +/** + * Test the 5 Fixed Issues + * Verify that all previously failing questions now work + */ + +import dotenv from 'dotenv'; +import path from 'path'; + +dotenv.config({ path: path.join(process.cwd(), '.env.local') }); + +import { answerQuestionWithRAG, isSpamOrOffTopic } from '../src/lib/geminiService'; + +const fixedQuestions = [ + { issue: "Map link was blocked", question: "map link please" }, + { issue: "Registration mistake - no clear answer", question: "i made a mistake" }, + { issue: "Pre-event coding - no clear answer", question: "can we start coding before" }, + { issue: "Food provided - was blocked", question: "is food provided" }, + { issue: "Help/support - vague answer", question: "i need help" } +]; + +async function testFixes() { + console.log('๐Ÿงช Testing 5 Fixed Issues...\n'); + console.log('='.repeat(100)); + + let passed = 0; + let failed = 0; + + for (const test of fixedQuestions) { + console.log(`\n๐Ÿ” Issue: ${test.issue}`); + console.log(`๐Ÿ“ Q: "${test.question}"`); + console.log('-'.repeat(100)); + + try { + // Check if blocked + const isBlocked = isSpamOrOffTopic(test.question); + + if (isBlocked) { + console.log('โŒ STILL BLOCKED - Should not be blocked!'); + failed++; + continue; + } + + // Get answer + const answer = await answerQuestionWithRAG(test.question, { state: 'IDLE' }); + + // Check quality + if (answer.includes('having trouble') || answer.includes('only help with CodeRush') || answer.length < 30) { + console.log('โŒ STILL NO CLEAR ANSWER'); + console.log(`Response: ${answer}`); + failed++; + } else { + console.log('โœ… FIXED! Clear answer provided'); + console.log(`Response: ${answer}`); + passed++; + } + + // Delay to avoid rate limits + await new Promise(resolve => setTimeout(resolve, 2000)); + + } catch (error: any) { + console.log('โŒ ERROR:', error.message); + failed++; + } + } + + console.log('\n' + '='.repeat(100)); + console.log('\n๐Ÿ“Š RESULTS:'); + console.log(` โœ… Fixed: ${passed}/5`); + console.log(` โŒ Still Broken: ${failed}/5`); + + if (passed === 5) { + console.log('\n๐ŸŽ‰ ALL ISSUES FIXED! Ready for production! ๐Ÿš€'); + } else if (passed >= 3) { + console.log('\n๐Ÿ‘ Most issues fixed, but need to check remaining failures'); + } else { + console.log('\nโš ๏ธ Need more work on these fixes'); + } +} + +testFixes().catch(console.error); diff --git a/scripts/test-greeting-detection.ts b/scripts/test-greeting-detection.ts new file mode 100644 index 0000000..be1ca0c --- /dev/null +++ b/scripts/test-greeting-detection.ts @@ -0,0 +1,94 @@ +/** + * Test: Greeting Detection + * Ensures greeting variations are properly detected + */ + +import { classifyIntent } from '../src/lib/geminiService'; + +console.log('๐Ÿงช Testing Greeting Detection\n'); + +const testCases = [ + // Standard greetings + { message: 'hi', state: 'IDLE', expected: 'GREETING', description: 'Standard "hi"' }, + { message: 'hello', state: 'IDLE', expected: 'GREETING', description: 'Standard "hello"' }, + { message: 'hey', state: 'IDLE', expected: 'GREETING', description: 'Standard "hey"' }, + { message: 'sup', state: 'IDLE', expected: 'GREETING', description: 'Standard "sup"' }, + { message: 'yo', state: 'IDLE', expected: 'GREETING', description: 'Standard "yo"' }, + + // Greeting variations with repeated letters + { message: 'hii', state: 'IDLE', expected: 'GREETING', description: 'Greeting variation "hii"' }, + { message: 'hiii', state: 'IDLE', expected: 'GREETING', description: 'Greeting variation "hiii"' }, + { message: 'hiiii', state: 'IDLE', expected: 'GREETING', description: 'Greeting variation "hiiii"' }, + { message: 'hiiiii', state: 'IDLE', expected: 'GREETING', description: 'Greeting variation "hiiiii"' }, + { message: 'hellooo', state: 'IDLE', expected: 'GREETING', description: 'Greeting variation "hellooo"' }, + { message: 'helloo', state: 'IDLE', expected: 'GREETING', description: 'Greeting variation "helloo"' }, + { message: 'heyyy', state: 'IDLE', expected: 'GREETING', description: 'Greeting variation "heyyy"' }, + { message: 'heyy', state: 'IDLE', expected: 'GREETING', description: 'Greeting variation "heyy"' }, + { message: 'supp', state: 'IDLE', expected: 'GREETING', description: 'Greeting variation "supp"' }, + { message: 'yoo', state: 'IDLE', expected: 'GREETING', description: 'Greeting variation "yoo"' }, + + // Greetings with text after + { message: 'hi there', state: 'IDLE', expected: 'GREETING', description: 'Greeting with text "hi there"' }, + { message: 'hello world', state: 'IDLE', expected: 'GREETING', description: 'Greeting with text "hello world"' }, + { message: 'hey buddy', state: 'IDLE', expected: 'GREETING', description: 'Greeting with text "hey buddy"' }, + + // NOT greetings (should be team names or questions) + { message: 'high', state: 'IDLE', expected: 'REGISTRATION', description: 'Not greeting "high" (team name)' }, + { message: 'hill', state: 'IDLE', expected: 'REGISTRATION', description: 'Not greeting "hill" (team name)' }, + { message: 'TeamAlpha', state: 'IDLE', expected: 'REGISTRATION', description: 'Team name "TeamAlpha"' }, + + // Greetings should not trigger during registration + { message: 'hi', state: 'MEMBER_DETAILS', expected: 'REGISTRATION', description: '"hi" during registration (should be name)' }, + { message: 'hello', state: 'TEAM_BATCH', expected: 'REGISTRATION', description: '"hello" during batch selection (should be data)' }, +]; + +async function runTests() { + let passed = 0; + let failed = 0; + const failures: string[] = []; + + for (const test of testCases) { + try { + const result = await classifyIntent(test.message, test.state); + const success = result === test.expected; + + if (success) { + console.log(`โœ… PASS: ${test.description}`); + console.log(` Input: "${test.message}" | State: ${test.state} | Result: ${result}\n`); + passed++; + } else { + console.log(`โŒ FAIL: ${test.description}`); + console.log(` Input: "${test.message}" | State: ${test.state}`); + console.log(` Expected: ${test.expected} | Got: ${result}\n`); + failures.push(`${test.description} - Expected ${test.expected}, got ${result}`); + failed++; + } + } catch (error) { + console.log(`โŒ ERROR: ${test.description}`); + console.log(` ${error}\n`); + failures.push(`${test.description} - Error: ${error}`); + failed++; + } + } + + console.log('\n' + '='.repeat(60)); + console.log('๐Ÿ“Š Test Summary:'); + console.log(`Total Tests: ${testCases.length}`); + console.log(`โœ… Passed: ${passed}`); + console.log(`โŒ Failed: ${failed}`); + console.log('='.repeat(60)); + + if (failures.length > 0) { + console.log('\nโŒ Failed Tests:'); + failures.forEach((failure, idx) => { + console.log(`${idx + 1}. ${failure}`); + }); + } else { + console.log('\n๐ŸŽ‰ All tests passed!'); + } +} + +runTests().catch(error => { + console.error('Test execution error:', error); + process.exit(1); +}); diff --git a/scripts/test-intent-classification.ts b/scripts/test-intent-classification.ts new file mode 100644 index 0000000..b51da0d --- /dev/null +++ b/scripts/test-intent-classification.ts @@ -0,0 +1,139 @@ +/** + * Test Intent Classification Fix + * Tests that event questions are properly classified + */ + +import dotenv from 'dotenv'; +import path from 'path'; + +// Load environment variables +dotenv.config({ path: path.join(process.cwd(), '.env.local') }); +dotenv.config({ path: path.join(process.cwd(), '.env') }); + +import { classifyIntent } from '../src/lib/geminiService'; + +interface TestCase { + message: string; + expectedIntent: 'QUESTION' | 'CONVERSATIONAL' | 'GREETING' | 'REGISTRATION'; + reason: string; +} + +const testCases: TestCase[] = [ + // These should be QUESTIONS (the problematic ones from user) + { + message: "what is the event", + expectedIntent: "QUESTION", + reason: "Asking about the event - should trigger RAG" + }, + { + message: "what is this competition", + expectedIntent: "QUESTION", + reason: "Asking about competition - should trigger RAG" + }, + { + message: "what is buildathon", + expectedIntent: "QUESTION", + reason: "Asking about buildathon - should trigger RAG" + }, + { + message: "explain this event more details", + expectedIntent: "QUESTION", + reason: "Asking for event details - should trigger RAG" + }, + { + message: "what is coderush", + expectedIntent: "QUESTION", + reason: "Asking about coderush - should trigger RAG" + }, + + // These should still be CONVERSATIONAL (not about event) + { + message: "what are you", + expectedIntent: "CONVERSATIONAL", + reason: "Asking about the bot itself - generic response" + }, + { + message: "who are you", + expectedIntent: "CONVERSATIONAL", + reason: "Asking about the bot - generic response" + }, + { + message: "thanks", + expectedIntent: "CONVERSATIONAL", + reason: "Gratitude - generic response" + }, + + // These should be QUESTIONS (event-related) + { + message: "when is the event", + expectedIntent: "QUESTION", + reason: "Date question - should trigger RAG" + }, + { + message: "where is the venue", + expectedIntent: "QUESTION", + reason: "Location question - should trigger RAG" + }, + { + message: "how to register", + expectedIntent: "QUESTION", + reason: "Registration question - should trigger RAG" + }, + + // Greetings + { + message: "hello", + expectedIntent: "GREETING", + reason: "Simple greeting - greeting response" + }, + { + message: "hi there", + expectedIntent: "GREETING", + reason: "Greeting with extra - greeting response" + } +]; + +async function runTests() { + console.log('๐Ÿงช Testing Intent Classification Fix\n'); + console.log('=' .repeat(70)); + + let passed = 0; + let failed = 0; + const failures: { message: string; expected: string; got: string }[] = []; + + for (const test of testCases) { + const result = await classifyIntent(test.message, 'IDLE'); + + if (result === test.expectedIntent) { + console.log(`โœ… "${test.message}"`); + console.log(` โ†’ ${result} (correct)`); + passed++; + } else { + console.log(`โŒ "${test.message}"`); + console.log(` โ†’ Expected: ${test.expectedIntent}, Got: ${result}`); + console.log(` โ†’ Reason: ${test.reason}`); + failed++; + failures.push({ + message: test.message, + expected: test.expectedIntent, + got: result + }); + } + console.log(''); + } + + console.log('=' .repeat(70)); + console.log(`\n๐Ÿ“Š Results: ${passed}/${testCases.length} passed (${Math.round(passed/testCases.length * 100)}%)`); + + if (failures.length > 0) { + console.log('\nโŒ Failed Tests:'); + failures.forEach((f, i) => { + console.log(`${i + 1}. "${f.message}"`); + console.log(` Expected: ${f.expected}, Got: ${f.got}\n`); + }); + } else { + console.log('\n๐ŸŽ‰ All tests passed! Intent classification is working correctly!'); + } +} + +runTests().catch(console.error); diff --git a/scripts/test-intent-during-registration.ts b/scripts/test-intent-during-registration.ts new file mode 100644 index 0000000..8fad4b9 --- /dev/null +++ b/scripts/test-intent-during-registration.ts @@ -0,0 +1,56 @@ +/** + * Test Intent Classification During Active Registration + */ + +import dotenv from 'dotenv'; +import path from 'path'; + +dotenv.config({ path: path.join(process.cwd(), '.env.local') }); +dotenv.config({ path: path.join(process.cwd(), '.env') }); + +import { classifyIntent } from '../src/lib/geminiService'; + +const testCases = [ + { message: "provide guidelines", expectedIntent: "QUESTION" }, + { message: "provide guidlines", expectedIntent: "QUESTION" }, + { message: "give me information", expectedIntent: "QUESTION" }, + { message: "tell me about event", expectedIntent: "QUESTION" }, + { message: "show me rules", expectedIntent: "QUESTION" }, + { message: "what are the guidelines", expectedIntent: "QUESTION" }, + { message: "23", expectedIntent: "REGISTRATION" }, + { message: "24", expectedIntent: "REGISTRATION" }, + { message: "John Doe", expectedIntent: "REGISTRATION" } +]; + +async function runTests() { + console.log('๐Ÿงช Testing Intent Classification During BATCH_SELECTION\n'); + console.log('=' .repeat(70)); + + let passed = 0; + let failed = 0; + + for (const test of testCases) { + const result = await classifyIntent(test.message, 'BATCH_SELECTION'); + const status = result === test.expectedIntent ? 'โœ…' : 'โŒ'; + + console.log(`${status} "${test.message}"`); + console.log(` Expected: ${test.expectedIntent}, Got: ${result}`); + + if (result === test.expectedIntent) { + passed++; + } else { + failed++; + } + } + + console.log('\n' + '=' .repeat(70)); + console.log(`\n๐Ÿ“Š Results: ${passed}/${testCases.length} passed`); + + if (failed === 0) { + console.log('๐ŸŽ‰ All tests passed!'); + } else { + console.log(`โŒ ${failed} tests failed`); + } +} + +runTests().catch(console.error); diff --git a/scripts/test-location-specific.ts b/scripts/test-location-specific.ts new file mode 100644 index 0000000..4f4b0b0 --- /dev/null +++ b/scripts/test-location-specific.ts @@ -0,0 +1,107 @@ +/** + * Specific test for location/venue/map link queries + */ + +import * as dotenv from 'dotenv'; +import * as path from 'path'; +dotenv.config({ path: path.resolve(process.cwd(), '.env.local') }); + +import { answerQuestionWithRAG } from '../src/lib/geminiService'; +import { searchVectorDatabase, isPineconeAvailable } from '../src/lib/vectorService'; +import { searchByKeyword } from '../src/lib/knowledgeBase'; + +const colors = { + reset: '\x1b[0m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + cyan: '\x1b[36m', + red: '\x1b[31m', + bright: '\x1b[1m', +}; + +function log(message: string, color: string = colors.reset) { + console.log(`${color}${message}${colors.reset}`); +} + +async function testLocationQueries() { + log('\n' + 'โ•'.repeat(70), colors.cyan); + log(' LOCATION/MAP LINK TEST', colors.cyan + colors.bright); + log('โ•'.repeat(70) + '\n', colors.cyan); + + const locationQueries = [ + 'Where is CodeRush?', + 'Where is the venue?', + 'What is the location?', + 'Where is the competition?', + 'where is the event?', + 'location of coderush', + 'venue location', + 'map link', + 'send me the map', + 'how do I get to the venue?', + 'address of the event', + 'what is the location of competition', // typo version + 'where is the compititon', // typo version + ]; + + const pineconeAvailable = await isPineconeAvailable(); + log(`Vector Search: ${pineconeAvailable ? 'โœ… Active' : 'โš ๏ธ Fallback'}\n`, + pineconeAvailable ? colors.green : colors.yellow); + + for (let i = 0; i < locationQueries.length; i++) { + const query = locationQueries[i]; + + log(`${'โ”€'.repeat(70)}`, colors.blue); + log(`Test ${i + 1}/${locationQueries.length}: "${query}"`, colors.blue + colors.bright); + log('โ”€'.repeat(70), colors.blue); + + try { + // Check what documents are retrieved + if (pineconeAvailable) { + const vectorResults = await searchVectorDatabase(query, 3); + log(` Vector Search Results (${vectorResults.length} docs):`, colors.cyan); + vectorResults.forEach((doc, idx) => { + log(` ${idx + 1}. "${doc.question}" (category: ${doc.category})`, colors.cyan); + }); + } else { + const keywordResults = searchByKeyword(query, 3); + log(` Keyword Search Results (${keywordResults.length} docs):`, colors.cyan); + keywordResults.forEach((doc, idx) => { + log(` ${idx + 1}. "${doc.question}" (category: ${doc.category})`, colors.cyan); + }); + } + + // Get RAG answer + const answer = await answerQuestionWithRAG(query); + + // Check if answer contains map link + const hasMapLink = answer.includes('maps.app.goo.gl') || + answer.includes('maps.google.com') || + answer.includes('https://'); + + log(`\n Answer (${answer.length} chars):`, colors.green + colors.bright); + log(` ${answer}`, colors.green); + + if (hasMapLink) { + log(`\n โœ… MAP LINK INCLUDED`, colors.green + colors.bright); + } else { + log(`\n โŒ MAP LINK MISSING`, colors.red + colors.bright); + } + + log(''); + + } catch (error) { + log(` โŒ Error: ${error instanceof Error ? error.message : String(error)}`, colors.red); + } + } + + log('\n' + 'โ•'.repeat(70), colors.cyan); + log(' TEST COMPLETE', colors.cyan + colors.bright); + log('โ•'.repeat(70) + '\n', colors.cyan); +} + +testLocationQueries().catch(error => { + console.error('Fatal error:', error); + process.exit(1); +}); diff --git a/scripts/test-location.ts b/scripts/test-location.ts new file mode 100644 index 0000000..79cbadd --- /dev/null +++ b/scripts/test-location.ts @@ -0,0 +1,50 @@ +/** + * Test location-related queries + */ + +import dotenv from 'dotenv'; +import path from 'path'; + +// Load environment variables +dotenv.config({ path: path.join(process.cwd(), '.env.local') }); +dotenv.config({ path: path.join(process.cwd(), '.env') }); + +import { answerQuestionWithRAG, classifyIntent } from '../src/lib/geminiService'; + +async function testLocationQueries() { + console.log('๐Ÿงช Testing Location Queries...\n'); + console.log('='.repeat(80) + '\n'); + + const locationQueries = [ + "what is the location of competition", + "what is the location of compititon", // With typo + "where is the venue", + "where is the event", + "send me the map link", + "how do I get to the venue", + "where is coderush held", + "venue location", + "event location", + "map" + ]; + + for (const query of locationQueries) { + console.log(`๐Ÿ“ Question: "${query}"`); + console.log('-'.repeat(80)); + + try { + const intent = await classifyIntent(query, 'IDLE'); + console.log(`๐Ÿง  Intent: ${intent}`); + + const answer = await answerQuestionWithRAG(query, { state: 'IDLE' }); + console.log(`โœ… Answer:\n${answer}\n`); + } catch (error) { + console.error(`โŒ Error: ${error}\n`); + } + } + + console.log('='.repeat(80)); + console.log('โœ… Testing complete!'); +} + +testLocationQueries(); diff --git a/scripts/test-network.ts b/scripts/test-network.ts new file mode 100644 index 0000000..7ffa856 --- /dev/null +++ b/scripts/test-network.ts @@ -0,0 +1,73 @@ +/** + * Test network connectivity with SSL bypass (FOR TESTING ONLY) + * WARNING: This disables SSL verification - use only in development! + */ + +import dotenv from 'dotenv'; +import path from 'path'; +import https from 'https'; + +// Load environment variables +dotenv.config({ path: path.join(process.cwd(), '.env.local') }); +dotenv.config({ path: path.join(process.cwd(), '.env') }); + +// TEMPORARY: Bypass SSL verification (INSECURE - DEV ONLY!) +process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + +console.log('โš ๏ธ WARNING: SSL verification is DISABLED (development only!)'); +console.log('๐Ÿงช Testing network connectivity...\n'); + +async function testConnections() { + // Test 1: Check Gemini API + console.log('1๏ธโƒฃ Testing Gemini API connection...'); + try { + const { GoogleGenerativeAI } = await import('@google/generative-ai'); + const genAI = new GoogleGenerativeAI(process.env.GOOGLE_GEMINI_API_KEY || ''); + const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash-exp' }); + const result = await model.generateContent('Hello'); + console.log(' โœ… Gemini API: Connected\n'); + } catch (error) { + console.log(' โŒ Gemini API: Failed'); + console.log(' Error:', error instanceof Error ? error.message : String(error)); + console.log(''); + } + + // Test 2: Check Pinecone API + console.log('2๏ธโƒฃ Testing Pinecone API connection...'); + try { + const { Pinecone } = await import('@pinecone-database/pinecone'); + const pc = new Pinecone({ apiKey: process.env.PINECONE_API_KEY || '' }); + const indexes = await pc.listIndexes(); + console.log(' โœ… Pinecone API: Connected'); + console.log(' Indexes found:', indexes.indexes?.length || 0); + console.log(''); + } catch (error) { + console.log(' โŒ Pinecone API: Failed'); + console.log(' Error:', error instanceof Error ? error.message : String(error)); + console.log(''); + } + + // Test 3: General internet connectivity + console.log('3๏ธโƒฃ Testing general internet...'); + try { + await fetch('https://www.google.com', { + method: 'HEAD', + // @ts-ignore + agent: new https.Agent({ rejectUnauthorized: false }) + }); + console.log(' โœ… Internet: Connected\n'); + } catch (error) { + console.log(' โŒ Internet: Failed'); + console.log(' Error:', error instanceof Error ? error.message : String(error)); + console.log(''); + } +} + +testConnections().then(() => { + console.log('โ•'.repeat(60)); + console.log('โœ… Network test complete!'); + console.log('\n๐Ÿ’ก If tests failed:'); + console.log(' 1. Switch to mobile hotspot or home WiFi'); + console.log(' 2. Check if university proxy is blocking external APIs'); + console.log(' 3. Use VPN if available'); +}); diff --git a/scripts/test-question-handling.ts b/scripts/test-question-handling.ts new file mode 100644 index 0000000..da3f94c --- /dev/null +++ b/scripts/test-question-handling.ts @@ -0,0 +1,160 @@ +/** + * Test: Question Handling During Registration + * Ensures questions don't interfere with the registration process + */ + +import { classifyIntent, isSpamOrOffTopic } from '../src/lib/geminiService'; + +console.log('๐Ÿงช Testing Question Handling During Registration\n'); + +// Test cases for intent classification during different registration states +const testCases = [ + // IDLE state - should detect questions + { message: 'what is react', state: 'IDLE', expected: 'QUESTION', description: 'Off-topic question in IDLE' }, + { message: 'what is event details', state: 'IDLE', expected: 'QUESTION', description: 'Valid event question in IDLE' }, + { message: 'where is the venue', state: 'IDLE', expected: 'QUESTION', description: 'Venue question in IDLE' }, + { message: 'when is coderush', state: 'IDLE', expected: 'QUESTION', description: 'Date question in IDLE' }, + { message: 'tell me about the event', state: 'IDLE', expected: 'QUESTION', description: 'General event question in IDLE' }, + { message: 'TeamAlpha', state: 'IDLE', expected: 'REGISTRATION', description: 'Team name in IDLE' }, + + // TEAM_BATCH state - should prioritize registration + { message: '23', state: 'TEAM_BATCH', expected: 'REGISTRATION', description: 'Batch number during registration' }, + { message: 'what is the format?', state: 'TEAM_BATCH', expected: 'QUESTION', description: 'Format question during batch input' }, + { message: 'can you help me?', state: 'TEAM_BATCH', expected: 'QUESTION', description: 'Help request during registration' }, + + // MEMBER_DETAILS state - should prioritize registration data + { message: 'John Doe', state: 'MEMBER_DETAILS', expected: 'REGISTRATION', description: 'Name during member details' }, + { message: '234001T', state: 'MEMBER_DETAILS', expected: 'REGISTRATION', description: 'Index during member details' }, + { message: 'john@example.com', state: 'MEMBER_DETAILS', expected: 'REGISTRATION', description: 'Email during member details' }, + { message: 'what is index format?', state: 'MEMBER_DETAILS', expected: 'QUESTION', description: 'Format question during member input' }, + { message: 'where is the venue?', state: 'MEMBER_DETAILS', expected: 'QUESTION', description: 'Venue question during member input' }, + { message: 'how many members?', state: 'MEMBER_DETAILS', expected: 'QUESTION', description: 'Team size question during member input' }, + { message: 'what format should I use', state: 'MEMBER_DETAILS', expected: 'QUESTION', description: 'Format help during member input' }, + { message: 'tell me the example', state: 'MEMBER_DETAILS', expected: 'QUESTION', description: 'Example request during member input' }, + + // CONFIRM state - should detect questions + { message: 'yes', state: 'CONFIRM', expected: 'REGISTRATION', description: 'Confirmation yes' }, + { message: 'no', state: 'CONFIRM', expected: 'REGISTRATION', description: 'Confirmation no' }, + { message: 'can I edit later?', state: 'CONFIRM', expected: 'QUESTION', description: 'Edit question during confirm' }, + + // DONE state - should detect questions + { message: 'what is the venue?', state: 'DONE', expected: 'QUESTION', description: 'Question after registration complete' }, + { message: 'when is the event?', state: 'DONE', expected: 'QUESTION', description: 'Date question after registration' }, + + // Edge cases + { message: 'what', state: 'MEMBER_DETAILS', expected: 'QUESTION', description: 'Single question word during registration' }, + { message: 'help', state: 'MEMBER_DETAILS', expected: 'QUESTION', description: 'Help keyword during registration' }, + { message: '', state: 'IDLE', expected: 'QUESTION', description: 'Empty message' }, +]; + +async function runTests() { + let passed = 0; + let failed = 0; + const failures: string[] = []; + + for (const test of testCases) { + try { + const result = await classifyIntent(test.message, test.state); + const success = result === test.expected; + + if (success) { + console.log(`โœ… PASS: ${test.description}`); + console.log(` Input: "${test.message}" | State: ${test.state} | Result: ${result}\n`); + passed++; + } else { + console.log(`โŒ FAIL: ${test.description}`); + console.log(` Input: "${test.message}" | State: ${test.state}`); + console.log(` Expected: ${test.expected} | Got: ${result}\n`); + failures.push(`${test.description} - Expected ${test.expected}, got ${result}`); + failed++; + } + } catch (error) { + console.log(`โŒ ERROR: ${test.description}`); + console.log(` ${error}\n`); + failures.push(`${test.description} - Error: ${error}`); + failed++; + } + } + + console.log('\n' + '='.repeat(60)); + console.log('๐Ÿ“Š Test Summary:'); + console.log(`Total Tests: ${testCases.length}`); + console.log(`โœ… Passed: ${passed}`); + console.log(`โŒ Failed: ${failed}`); + console.log('='.repeat(60)); + + if (failures.length > 0) { + console.log('\nโŒ Failed Tests:'); + failures.forEach((failure, idx) => { + console.log(`${idx + 1}. ${failure}`); + }); + } +} + +// Test spam/off-topic detection +console.log('\n๐Ÿ” Testing Spam and Off-Topic Detection\n'); + +const spamTests = [ + { message: 'what is react', expected: true, description: 'React question (off-topic programming tutorial)' }, + { message: 'what is event details', expected: false, description: 'Valid event question' }, + { message: 'where is the venue', expected: false, description: 'Valid venue question' }, + { message: 'aaaaaaaa', expected: true, description: 'Repeated characters (spam)' }, + { message: 'buy now discount', expected: true, description: 'Spam keywords' }, + { message: 'fuck you', expected: true, description: 'Inappropriate language' }, + { message: 'what is the weather', expected: true, description: 'Off-topic weather question' }, + { message: 'tell me a joke', expected: true, description: 'Off-topic joke request' }, + { message: 'what is python', expected: true, description: 'Programming tutorial request' }, + { message: 'how to code in javascript', expected: true, description: 'Programming learning request' }, + { message: 'what tech stack can we use?', expected: false, description: 'Valid tech stack question' }, + { message: 'can we use react?', expected: false, description: 'Valid framework question' }, + { message: 'is food provided?', expected: false, description: 'Valid event logistics question' }, + { message: '234001T', expected: false, description: 'Valid index number' }, + { message: 'john@example.com', expected: false, description: 'Valid email' }, + { message: 'TeamAlpha', expected: false, description: 'Valid team name' }, +]; + +function testSpamDetection() { + let passed = 0; + let failed = 0; + const failures: string[] = []; + + for (const test of spamTests) { + const result = isSpamOrOffTopic(test.message); + const success = result === test.expected; + + if (success) { + console.log(`โœ… PASS: ${test.description}`); + console.log(` Input: "${test.message}" | Spam: ${result}\n`); + passed++; + } else { + console.log(`โŒ FAIL: ${test.description}`); + console.log(` Input: "${test.message}"`); + console.log(` Expected: ${test.expected} | Got: ${result}\n`); + failures.push(`${test.description} - Expected ${test.expected}, got ${result}`); + failed++; + } + } + + console.log('\n' + '='.repeat(60)); + console.log('๐Ÿ“Š Spam Detection Summary:'); + console.log(`Total Tests: ${spamTests.length}`); + console.log(`โœ… Passed: ${passed}`); + console.log(`โŒ Failed: ${failed}`); + console.log('='.repeat(60)); + + if (failures.length > 0) { + console.log('\nโŒ Failed Tests:'); + failures.forEach((failure, idx) => { + console.log(`${idx + 1}. ${failure}`); + }); + } +} + +// Run all tests +runTests().then(() => { + console.log('\n'); + testSpamDetection(); +}).catch(error => { + console.error('Test execution error:', error); + process.exit(1); +}); diff --git a/scripts/test-rag-comprehensive.ts b/scripts/test-rag-comprehensive.ts new file mode 100644 index 0000000..207d5bb --- /dev/null +++ b/scripts/test-rag-comprehensive.ts @@ -0,0 +1,307 @@ +/** + * Comprehensive RAG Test Script + * Tests the full AI assistant with Gemini + RAG + */ + +import dotenv from 'dotenv'; +import path from 'path'; + +// Load environment variables +dotenv.config({ path: path.join(process.cwd(), '.env.local') }); +dotenv.config({ path: path.join(process.cwd(), '.env') }); + +import { answerQuestionWithRAG } from '../src/lib/geminiService'; + +interface TestCase { + category: string; + question: string; + expectedTopics: string[]; // Keywords that should appear in the answer +} + +const testCases: TestCase[] = [ + // Event Date & Location Questions + { + category: "Event Date & Time", + question: "when is the event", + expectedTopics: ["november", "15", "2025", "8", "6"] + }, + { + category: "Event Date & Time", + question: "what time does it start", + expectedTopics: ["8", "am", "morning"] + }, + { + category: "Event Date & Time", + question: "how long is the buildathon", + expectedTopics: ["10", "hour", "8", "6"] + }, + + // Venue & Location Questions + { + category: "Venue & Location", + question: "where is the venue", + expectedTopics: ["moratuwa", "faculty", "it", "maps"] + }, + { + category: "Venue & Location", + question: "send me the map link", + expectedTopics: ["maps.app.goo.gl", "WsNriCAdxhJadHaK7"] + }, + { + category: "Venue & Location", + question: "how do i get there", + expectedTopics: ["maps", "directions", "moratuwa"] + }, + + // Registration Questions + { + category: "Registration", + question: "how to register", + expectedTopics: ["chat", "team name", "batch", "members"] + }, + { + category: "Registration", + question: "what's the registration process", + expectedTopics: ["step", "team name", "batch", "members"] + }, + { + category: "Registration", + question: "can i edit after registration", + expectedTopics: ["yes", "edit", "reset"] + }, + { + category: "Registration", + question: "what happens if i click reset", + expectedTopics: ["reset", "new", "lose", "access"] + }, + + // Team Questions + { + category: "Team Requirements", + question: "how many members", + expectedTopics: ["4", "four", "members"] + }, + { + category: "Team Requirements", + question: "what batches can join", + expectedTopics: ["23", "24", "batch"] + }, + { + category: "Team Requirements", + question: "can we mix batches", + expectedTopics: ["same", "batch", "cannot"] + }, + { + category: "Team Requirements", + question: "can one person be in multiple teams", + expectedTopics: ["no", "once", "cannot"] + }, + + // Submission Questions + { + category: "Submission", + question: "what do we need to submit", + expectedTopics: ["github", "drive", "video", "report"] + }, + { + category: "Submission", + question: "when is the deadline", + expectedTopics: ["6", "pm", "november", "15"] + }, + { + category: "Submission", + question: "what should be in the github repo", + expectedTopics: ["code", "readme", "source"] + }, + { + category: "Submission", + question: "file naming format", + expectedTopics: ["team", "demo", "mp4", "report", "pdf"] + }, + + // Technology Questions + { + category: "Technology", + question: "can we use react", + expectedTopics: ["yes", "any", "framework"] + }, + { + category: "Technology", + question: "are there tech restrictions", + expectedTopics: ["no", "any", "technology"] + }, + { + category: "Technology", + question: "can we use ai tools", + expectedTopics: ["yes", "chatgpt", "copilot", "ai"] + }, + { + category: "Technology", + question: "which programming languages are allowed", + expectedTopics: ["any", "python", "javascript"] + }, + + // General/Vague Questions + { + category: "General", + question: "tell me about coderush", + expectedTopics: ["buildathon", "university", "moratuwa", "10", "hour"] + }, + { + category: "General", + question: "explain this event", + expectedTopics: ["buildathon", "coding", "competition"] + }, + { + category: "General", + question: "what is this about", + expectedTopics: ["coderush", "event", "buildathon"] + }, + + // New Features - Judging & Equipment + { + category: "Judging", + question: "how will projects be judged", + expectedTopics: ["innovation", "technical", "criteria"] + }, + { + category: "Equipment", + question: "what should i bring", + expectedTopics: ["laptop", "charger"] + }, + { + category: "Equipment", + question: "is there wifi", + expectedTopics: ["yes", "wifi", "internet"] + }, + + // Edge Cases & Conversational + { + category: "Conversational", + question: "hey what's up", + expectedTopics: ["coderush", "help", "register"] + }, + { + category: "Conversational", + question: "i have a question", + expectedTopics: ["help", "ask"] + }, + + // Specific Detail Questions + { + category: "Specific Details", + question: "team name rules", + expectedTopics: ["3", "10", "characters"] + }, + { + category: "Specific Details", + question: "index number format", + expectedTopics: ["6", "digits", "letter", "23", "24"] + }, + { + category: "Specific Details", + question: "can we start coding before the event", + expectedTopics: ["no", "8", "am", "fairness"] + }, + { + category: "Specific Details", + question: "is food provided", + expectedTopics: ["yes", "refreshment"] + } +]; + +async function runTests() { + console.log('๐Ÿงช Starting Comprehensive RAG Tests\n'); + console.log('=' .repeat(80)); + + let passed = 0; + let failed = 0; + const failedTests: { question: string; reason: string }[] = []; + + for (let i = 0; i < testCases.length; i++) { + const test = testCases[i]; + const testNum = i + 1; + + try { + console.log(`\n[${testNum}/${testCases.length}] Testing: "${test.question}"`); + console.log(`Category: ${test.category}`); + + const answer = await answerQuestionWithRAG(test.question); + + // Check if answer contains expected topics + const lowerAnswer = answer.toLowerCase(); + const foundTopics = test.expectedTopics.filter(topic => + lowerAnswer.includes(topic.toLowerCase()) + ); + + const allTopicsFound = foundTopics.length >= Math.ceil(test.expectedTopics.length * 0.6); + + if (allTopicsFound) { + console.log(`โœ… PASS - Found ${foundTopics.length}/${test.expectedTopics.length} expected topics`); + console.log(`๐Ÿ“ Answer preview: ${answer.substring(0, 150)}...`); + passed++; + } else { + console.log(`โŒ FAIL - Only found ${foundTopics.length}/${test.expectedTopics.length} expected topics`); + console.log(` Expected: ${test.expectedTopics.join(', ')}`); + console.log(` Found: ${foundTopics.join(', ')}`); + console.log(`๐Ÿ“ Answer: ${answer}`); + failed++; + failedTests.push({ + question: test.question, + reason: `Missing topics: ${test.expectedTopics.filter(t => !foundTopics.includes(t)).join(', ')}` + }); + } + + // Small delay to avoid rate limits + await new Promise(resolve => setTimeout(resolve, 500)); + + } catch (error) { + console.log(`โŒ ERROR - ${error instanceof Error ? error.message : 'Unknown error'}`); + failed++; + failedTests.push({ + question: test.question, + reason: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` + }); + } + } + + console.log('\n' + '=' .repeat(80)); + console.log('\n๐Ÿ“Š Test Results Summary'); + console.log('=' .repeat(80)); + console.log(`โœ… Passed: ${passed}/${testCases.length} (${Math.round(passed/testCases.length * 100)}%)`); + console.log(`โŒ Failed: ${failed}/${testCases.length} (${Math.round(failed/testCases.length * 100)}%)`); + + if (failedTests.length > 0) { + console.log('\nโŒ Failed Tests Details:'); + console.log('=' .repeat(80)); + failedTests.forEach((test, i) => { + console.log(`${i + 1}. Question: "${test.question}"`); + console.log(` Reason: ${test.reason}\n`); + }); + } + + // Category breakdown + console.log('\n๐Ÿ“‹ Results by Category:'); + console.log('=' .repeat(80)); + const categories = [...new Set(testCases.map(t => t.category))]; + categories.forEach(cat => { + const catTests = testCases.filter(t => t.category === cat); + const catPassed = catTests.filter((t, i) => { + const globalIndex = testCases.indexOf(t); + return globalIndex < passed + failed && !failedTests.some(f => f.question === t.question); + }).length; + console.log(`${cat}: ${catPassed}/${catTests.length}`); + }); + + if (passed === testCases.length) { + console.log('\n๐ŸŽ‰ All tests passed! The RAG system is working perfectly! ๐ŸŽ‰'); + } else if (passed / testCases.length >= 0.9) { + console.log('\nโœ… Excellent! Over 90% tests passed. Minor improvements needed.'); + } else if (passed / testCases.length >= 0.75) { + console.log('\nโš ๏ธ Good progress. Some improvements needed for better accuracy.'); + } else { + console.log('\nโŒ Needs improvement. Review failed tests and update knowledge base.'); + } +} + +runTests().catch(console.error); diff --git a/scripts/test-rag-live.ts b/scripts/test-rag-live.ts new file mode 100644 index 0000000..e410638 --- /dev/null +++ b/scripts/test-rag-live.ts @@ -0,0 +1,119 @@ +/** + * Live RAG System Test + * Tests the RAG system with real questions + */ + +// Load environment variables from .env.local +import * as dotenv from 'dotenv'; +import * as path from 'path'; +import { fileURLToPath } from 'url'; + +// Load .env.local +dotenv.config({ path: path.resolve(process.cwd(), '.env.local') }); + +import { answerQuestionWithRAG, classifyIntent, isSpamOrOffTopic } from '../src/lib/geminiService'; +import { searchVectorDatabase, isPineconeAvailable } from '../src/lib/vectorService'; +import { searchByKeyword } from '../src/lib/knowledgeBase'; + +const colors = { + reset: '\x1b[0m', + bright: '\x1b[1m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + cyan: '\x1b[36m', + magenta: '\x1b[35m', +}; + +function log(message: string, color: string = colors.reset) { + console.log(`${color}${message}${colors.reset}`); +} + +async function testRAGSystem() { + log('\n' + 'โ–ˆ'.repeat(70), colors.magenta); + log(' LIVE RAG SYSTEM TEST - CODERUSH 2025', colors.magenta + colors.bright); + log('โ–ˆ'.repeat(70) + '\n', colors.magenta); + + // Check environment + log('๐Ÿ”ง Environment Check:', colors.cyan + colors.bright); + log(` GEMINI API KEY: ${process.env.GOOGLE_GEMINI_API_KEY ? 'โœ… Configured' : 'โŒ Missing'}`, + process.env.GOOGLE_GEMINI_API_KEY ? colors.green : colors.yellow); + log(` PINECONE API KEY: ${process.env.PINECONE_API_KEY ? 'โœ… Configured' : 'โš ๏ธ Optional'}`, + process.env.PINECONE_API_KEY ? colors.green : colors.yellow); + + // Check Pinecone + const pineconeAvailable = await isPineconeAvailable(); + log(` Vector Search: ${pineconeAvailable ? 'โœ… Active (Semantic)' : 'โš ๏ธ Fallback (Keyword)'}`, + pineconeAvailable ? colors.green : colors.yellow); + + console.log(''); + + // Test questions + const testQuestions = [ + { q: 'When is CodeRush 2025?', expectedCategory: 'event' }, + { q: 'Where is the venue?', expectedCategory: 'event' }, + { q: 'what is the location of compititon', expectedCategory: 'event' }, // Typo test + { q: 'How many members per team?', expectedCategory: 'team' }, + { q: 'What are the team name rules?', expectedCategory: 'registration' }, + { q: 'Can we use React and Next.js?', expectedCategory: 'technical' }, + { q: 'Who are the organizers?', expectedCategory: 'contact' }, + ]; + + log('๐Ÿ“ Testing RAG System with Real Questions:', colors.cyan + colors.bright); + console.log(''); + + for (let i = 0; i < testQuestions.length; i++) { + const { q, expectedCategory } = testQuestions[i]; + + log(`\n${'โ”€'.repeat(70)}`, colors.blue); + log(`Question ${i + 1}: "${q}"`, colors.blue + colors.bright); + log('โ”€'.repeat(70), colors.blue); + + try { + // Check intent classification + const intent = await classifyIntent(q, 'IDLE'); + log(` Intent: ${intent}`, colors.cyan); + + // Check spam detection + const isSpam = isSpamOrOffTopic(q); + log(` Spam/Off-topic: ${isSpam ? 'โŒ Yes' : 'โœ… No'}`, isSpam ? colors.yellow : colors.green); + + // Search for relevant documents + if (pineconeAvailable) { + const vectorResults = await searchVectorDatabase(q, 3); + log(` Vector Search: Found ${vectorResults.length} documents`, colors.cyan); + if (vectorResults.length > 0) { + log(` Top Match: "${vectorResults[0].question}"`, colors.cyan); + } + } else { + const keywordResults = searchByKeyword(q, 3); + log(` Keyword Search: Found ${keywordResults.length} documents`, colors.cyan); + if (keywordResults.length > 0) { + log(` Top Match: "${keywordResults[0].question}"`, colors.cyan); + } + } + + // Get RAG answer + if (process.env.GOOGLE_GEMINI_API_KEY) { + const answer = await answerQuestionWithRAG(q); + log(`\n Answer:`, colors.green + colors.bright); + log(` ${answer.substring(0, 200)}${answer.length > 200 ? '...' : ''}`, colors.green); + } else { + log(` โš ๏ธ Skipping RAG answer (no API key)`, colors.yellow); + } + + } catch (error) { + log(` โŒ Error: ${error instanceof Error ? error.message : String(error)}`, colors.yellow); + } + } + + log('\n' + 'โ•'.repeat(70), colors.cyan); + log('โœ… RAG System Test Complete!', colors.green + colors.bright); + log('โ•'.repeat(70) + '\n', colors.cyan); +} + +// Run test +testRAGSystem().catch(error => { + console.error('Fatal error:', error); + process.exit(1); +}); diff --git a/scripts/test-rag.ts b/scripts/test-rag.ts new file mode 100644 index 0000000..46fb768 --- /dev/null +++ b/scripts/test-rag.ts @@ -0,0 +1,82 @@ +/** + * Test RAG System Responses + * Run: npx tsx scripts/test-rag.ts + */ + +// Load environment variables +import dotenv from 'dotenv'; +import path from 'path'; + +dotenv.config({ path: path.join(process.cwd(), '.env.local') }); +dotenv.config({ path: path.join(process.cwd(), '.env') }); + +import { answerQuestionWithRAG, classifyIntent, isSpamOrOffTopic } from '../src/lib/geminiService'; + +const testQuestions = [ + // Valid CodeRush questions + "When is CodeRush?", + "How many team members?", + "What's the submission format?", + "Can I edit my registration?", + "What is the index number format?", + "When is the deadline?", + + // Spam/Off-topic (should be blocked) + "What is React?", + "What is Python?", + "Teach me coding", + "What's the weather?", + + // Edge cases + "format?", + "How many people per team?", + "When do we submit our work?", +]; + +async function testRAG() { + console.log('๐Ÿงช Testing RAG System...\n'); + console.log('='.repeat(80)); + + for (const question of testQuestions) { + console.log(`\n๐Ÿ“ Question: "${question}"`); + console.log('-'.repeat(80)); + + // Test spam detection + const isSpam = isSpamOrOffTopic(question); + if (isSpam) { + console.log('๐Ÿšซ Status: BLOCKED (Spam/Off-topic)'); + console.log('โœ… Response: "I can only help with CodeRush 2025..."'); + continue; + } + + // Test intent classification + const intent = await classifyIntent(question, 'IDLE'); + console.log(`๐Ÿง  Intent: ${intent}`); + + if (intent === 'QUESTION') { + try { + // Test answer generation + const answer = await answerQuestionWithRAG(question, { state: 'IDLE' }); + console.log(`โœ… Answer:\n${answer}`); + + // Check formatting + if (answer.length < 20) { + console.warn('โš ๏ธ Warning: Answer seems too short'); + } + if (answer.length > 500) { + console.warn('โš ๏ธ Warning: Answer seems too long'); + } + if (!answer.includes('CodeRush') && !answer.includes('team') && !answer.includes('registration')) { + console.warn('โš ๏ธ Warning: Answer may not be relevant'); + } + } catch (error) { + console.error('โŒ Error:', error); + } + } + } + + console.log('\n' + '='.repeat(80)); + console.log('โœ… Testing complete!'); +} + +testRAG().catch(console.error); diff --git a/scripts/test-real-world.ts b/scripts/test-real-world.ts new file mode 100644 index 0000000..6048404 --- /dev/null +++ b/scripts/test-real-world.ts @@ -0,0 +1,153 @@ +/** + * Real-World User Questions Test + * Tests all possible situations users might encounter + */ + +import dotenv from 'dotenv'; +import path from 'path'; + +dotenv.config({ path: path.join(process.cwd(), '.env.local') }); +dotenv.config({ path: path.join(process.cwd(), '.env') }); + +import { answerQuestionWithRAG, classifyIntent, isSpamOrOffTopic } from '../src/lib/geminiService'; + +const realWorldQuestions = [ + // === VALID EVENT QUESTIONS === + { category: "Event Info", question: "when is the event" }, + { category: "Event Info", question: "what time does it start" }, + { category: "Event Info", question: "where is the venue" }, + { category: "Event Info", question: "how long is the event" }, + { category: "Event Info", question: "what should i bring" }, + + // === TEAM QUESTIONS === + { category: "Team", question: "how many members in a team" }, + { category: "Team", question: "can i have 3 members" }, + { category: "Team", question: "can batch 23 and 24 mix" }, + { category: "Team", question: "what if my teammate drops out" }, + { category: "Team", question: "can i change team name later" }, + + // === REGISTRATION QUESTIONS === + { category: "Registration", question: "how do i register" }, + { category: "Registration", question: "can i edit my registration" }, + { category: "Registration", question: "what is index number format" }, + { category: "Registration", question: "i made a mistake in email" }, + { category: "Registration", question: "can i register multiple teams" }, + + // === SUBMISSION QUESTIONS === + { category: "Submission", question: "how do i submit my project" }, + { category: "Submission", question: "what is the deadline" }, + { category: "Submission", question: "can i submit late" }, + { category: "Submission", question: "do i need a video demo" }, + { category: "Submission", question: "github must be public?" }, + + // === TECHNICAL QUESTIONS === + { category: "Technical", question: "what technologies can we use" }, + { category: "Technical", question: "can we use AI tools" }, + { category: "Technical", question: "do you provide hosting" }, + { category: "Technical", question: "can we use external APIs" }, + + // === PRIZES/RULES === + { category: "Prizes", question: "what are the prizes" }, + { category: "Prizes", question: "how many winners" }, + { category: "Rules", question: "can we start coding before event" }, + { category: "Rules", question: "are there any restrictions" }, + + // === SUPPORT/HELP === + { category: "Support", question: "i have a problem" }, + { category: "Support", question: "who do i contact for help" }, + { category: "Support", question: "what if i get stuck" }, + + // === CASUAL/TYPOS (Should still work) === + { category: "Casual", question: "hey when is this thing" }, + { category: "Casual", question: "how many ppl per team" }, + { category: "Casual", question: "whats the format" }, + { category: "Casual", question: "can i edit stufff" }, // typo + + // === INAPPROPRIATE (Should be BLOCKED) === + { category: "BLOCK", question: "what is sex" }, + { category: "BLOCK", question: "how to hack" }, + { category: "BLOCK", question: "fuck you" }, + { category: "BLOCK", question: "show me porn" }, + + // === OFF-TOPIC (Should be BLOCKED) === + { category: "BLOCK", question: "what is react" }, + { category: "BLOCK", question: "teach me python" }, + { category: "BLOCK", question: "what's the weather" }, + { category: "BLOCK", question: "tell me a joke" }, + { category: "BLOCK", question: "who are you" }, +]; + +async function testRealWorld() { + console.log('๐ŸŒ Testing Real-World User Questions...\n'); + console.log('='.repeat(100)); + + let validAnswered = 0; + let blockedCorrectly = 0; + let total = 0; + + for (const test of realWorldQuestions) { + total++; + console.log(`\n[${test.category}] "${test.question}"`); + console.log('-'.repeat(100)); + + // Check if should be blocked + const isBlocked = isSpamOrOffTopic(test.question); + + if (test.category === 'BLOCK') { + if (isBlocked) { + console.log('โœ… CORRECTLY BLOCKED - Friendly redirect message shown'); + blockedCorrectly++; + } else { + console.log('โŒ SHOULD BE BLOCKED but was allowed!'); + } + continue; + } + + // If it's a valid question + if (isBlocked) { + console.log('โŒ WRONGLY BLOCKED - Valid question was blocked!'); + continue; + } + + // Classify intent + const intent = await classifyIntent(test.question, 'IDLE'); + console.log(`๐Ÿง  Intent: ${intent}`); + + if (intent === 'QUESTION') { + try { + // Get answer + const answer = await answerQuestionWithRAG(test.question, { state: 'IDLE' }); + console.log(`โœ… Answer:\n${answer}`); + + // Check if answer is helpful + if (answer.length > 20 && !answer.includes('having trouble')) { + validAnswered++; + } + } catch (error: any) { + console.log(`โŒ Error: ${error.message}`); + } + } + + // Small delay to avoid rate limits + await new Promise(resolve => setTimeout(resolve, 1000)); + } + + console.log('\n' + '='.repeat(100)); + console.log(`\n๐Ÿ“Š RESULTS:`); + console.log(` Valid Questions Answered: ${validAnswered}`); + console.log(` Inappropriate Blocked: ${blockedCorrectly}`); + console.log(` Total Tests: ${total}`); + + const successRate = (validAnswered + blockedCorrectly) / total * 100; + console.log(`\n ๐ŸŽฏ Success Rate: ${successRate.toFixed(1)}%`); + + if (successRate >= 90) { + console.log('\nโœ… EXCELLENT! Chatbot is handling real-world questions very well!'); + } else if (successRate >= 75) { + console.log('\nโš ๏ธ GOOD but needs improvement'); + } else { + console.log('\nโŒ NEEDS WORK - Many questions not handled properly'); + } +} + +testRealWorld().catch(console.error); diff --git a/scripts/test-registration-data-detection.ts b/scripts/test-registration-data-detection.ts new file mode 100644 index 0000000..d76b42b --- /dev/null +++ b/scripts/test-registration-data-detection.ts @@ -0,0 +1,75 @@ +/** + * Test Registration Data Detection Logic + */ + +const testMessages = [ + "provide guidelines", + "provide guidlines", // typo + "give me information", + "tell me about event", + "23", + "24", + "John Doe", + "test@email.com", + "234001T" +]; + +function testDetection(message: string) { + const lowerMsg = message.toLowerCase().trim(); + + const questionWords = ['what', 'when', 'where', 'how', 'why', 'can', 'is', 'are', 'do', 'does', 'will', 'should', 'which', 'who']; + const helpKeywords = ['help', 'format', 'example', 'explain', 'tell me', 'show me']; + const conversationalPhrases = ['i want', 'i need', 'i ask', 'give me', 'send me', 'show me', 'tell me', 'no no', 'wait', 'actually']; + const eventKeywords = ['venue', 'location', 'address', 'place', 'map', 'event', 'coderush', 'buildathon', 'hackathon', 'competition', 'guidelines', 'guideline', 'rules', 'information', 'details']; + + const startsWithQuestionWord = questionWords.some(word => lowerMsg.startsWith(word + ' ') || lowerMsg.startsWith(word + "'")); + const containsQuestionWord = questionWords.some(word => lowerMsg.includes(' ' + word + ' ') || lowerMsg.includes(' ' + word + '?') || lowerMsg.endsWith(' ' + word)); + const containsHelpKeyword = helpKeywords.some(word => lowerMsg.includes(word)); + const containsConversationalPhrase = conversationalPhrases.some(phrase => lowerMsg.includes(phrase)); + const containsEventKeyword = eventKeywords.some(keyword => lowerMsg.includes(keyword)); + const hasQuestionMark = message.includes('?'); + + const looksLikeRegistrationData = + /@/.test(message) || // Email + /^\d{6}[A-Z]$/i.test(message.trim()) || // Index number + /^(23|24)$/.test(message.trim()) || // Batch + /^(yes|no)$/i.test(message.trim()) || // Confirmation + // Name/data (not a question) - must not contain question/help/conversational/event keywords + (message.length >= 2 && message.length <= 100 && + !hasQuestionMark && + !startsWithQuestionWord && + !containsQuestionWord && + !containsHelpKeyword && + !containsConversationalPhrase && + !containsEventKeyword && + !/^(provide|tell|give|show|explain|describe)/i.test(lowerMsg)); // Not asking for information + + return { + message, + looksLikeRegistrationData, + checks: { + startsWithQuestionWord, + containsQuestionWord, + containsHelpKeyword, + containsConversationalPhrase, + containsEventKeyword, + hasQuestionMark, + startsWithCommand: /^(provide|tell|give|show|explain|describe)/i.test(lowerMsg) + } + }; +} + +console.log('๐Ÿงช Testing Registration Data Detection\n'); +console.log('=' .repeat(80)); + +testMessages.forEach(msg => { + const result = testDetection(msg); + const status = result.looksLikeRegistrationData ? 'โŒ REGISTRATION DATA' : 'โœ… QUESTION/OTHER'; + + console.log(`\nMessage: "${msg}"`); + console.log(`Result: ${status}`); + console.log('Checks:', result.checks); +}); + +console.log('\n' + '=' .repeat(80)); +console.log('\nโœ… Test complete!'); diff --git a/scripts/test-registration-flow.ts b/scripts/test-registration-flow.ts new file mode 100644 index 0000000..cf029ec --- /dev/null +++ b/scripts/test-registration-flow.ts @@ -0,0 +1,49 @@ +/** + * Test Registration Flow Understanding + * Verify users understand the registration process clearly + */ + +import dotenv from 'dotenv'; +import path from 'path'; + +dotenv.config({ path: path.join(process.cwd(), '.env.local') }); + +import { answerQuestionWithRAG } from '../src/lib/geminiService'; + +const flowQuestions = [ + { q: "can i edit my details before submitting registration" }, + { q: "can i update details during registration" }, + { q: "what if page refreshes during registration" }, + { q: "can i edit my registration after submitting" }, + { q: "can i update my team details after successful registration" }, + { q: "what happens if i click reset after registration" }, + { q: "reset button delete my registration" }, + { q: "can i change details after clicking reset" }, + { q: "how to update team details after registration" }, + { q: "registration deadline date" } +]; + +async function testFlow() { + console.log('๐Ÿงช Testing Registration Flow Understanding...\n'); + console.log('='.repeat(100)); + + for (const test of flowQuestions) { + console.log(`\n๐Ÿ“ Q: "${test.q}"`); + console.log('-'.repeat(100)); + + try { + const answer = await answerQuestionWithRAG(test.q, { state: 'IDLE' }); + console.log(`โœ… A: ${answer}`); + + await new Promise(resolve => setTimeout(resolve, 2000)); + + } catch (error: any) { + console.log('โŒ ERROR:', error.message); + } + } + + console.log('\n' + '='.repeat(100)); + console.log('โœ… Testing complete!'); +} + +testFlow().catch(console.error); diff --git a/scripts/test-registration-questions.ts b/scripts/test-registration-questions.ts new file mode 100644 index 0000000..b7665f0 --- /dev/null +++ b/scripts/test-registration-questions.ts @@ -0,0 +1,114 @@ +/** + * Test Registration Process Questions + * Verify user-friendly responses for registration + */ + +import dotenv from 'dotenv'; +import path from 'path'; + +dotenv.config({ path: path.join(process.cwd(), '.env.local') }); + +import { answerQuestionWithRAG, isSpamOrOffTopic } from '../src/lib/geminiService'; + +const registrationQuestions = [ + // Basic registration questions + { q: "how do i register" }, + { q: "how to register my team" }, + { q: "registration process" }, + + // Team name questions + { q: "what are team name rules" }, + { q: "can i use numbers in team name" }, + { q: "team name format" }, + + // Index number questions + { q: "index number format" }, + { q: "what is index number format" }, + { q: "example of index number" }, + + // Email questions + { q: "can we use same email" }, + { q: "unique email required" }, + + // Editing questions + { q: "can i edit my registration" }, + { q: "how to change team details" }, + { q: "i made a mistake in registration" }, + + // Reset button questions + { q: "what happens if i click reset" }, + { q: "reset button delete my details" }, + { q: "can i reset after registration" }, + + // Deadline questions + { q: "registration deadline" }, + { q: "when does registration close" }, + { q: "last date to register" }, + + // Confirmation + { q: "will i get confirmation email" }, + { q: "how do i know registration is successful" } +]; + +async function testRegistrationQuestions() { + console.log('๐Ÿงช Testing Registration Questions...\n'); + console.log('='.repeat(100)); + + let passed = 0; + let failed = 0; + + for (const test of registrationQuestions) { + console.log(`\n๐Ÿ“ Q: "${test.q}"`); + console.log('-'.repeat(100)); + + try { + // Check if blocked + const isBlocked = isSpamOrOffTopic(test.q); + + if (isBlocked) { + console.log('โŒ WRONGLY BLOCKED!'); + failed++; + continue; + } + + // Get answer + const answer = await answerQuestionWithRAG(test.q, { state: 'IDLE' }); + + // Check quality + if (answer.includes('having trouble') || answer.includes('only help with CodeRush') || answer.length < 30) { + console.log('โŒ NO CLEAR ANSWER'); + console.log(`Response: ${answer}`); + failed++; + } else { + console.log('โœ… USER-FRIENDLY ANSWER'); + console.log(`Response: ${answer}`); + passed++; + } + + // Delay to avoid rate limits + await new Promise(resolve => setTimeout(resolve, 2000)); + + } catch (error: any) { + console.log('โŒ ERROR:', error.message); + failed++; + } + } + + console.log('\n' + '='.repeat(100)); + console.log('\n๐Ÿ“Š RESULTS:'); + console.log(` โœ… Answered: ${passed}/${registrationQuestions.length}`); + console.log(` โŒ Failed: ${failed}/${registrationQuestions.length}`); + + const successRate = passed / registrationQuestions.length * 100; + console.log(`\n ๐ŸŽฏ Success Rate: ${successRate.toFixed(1)}%`); + + if (successRate >= 95) { + console.log('\nโœ… EXCELLENT! Registration questions are user-friendly! ๐Ÿš€'); + } else if (successRate >= 85) { + console.log('\n๐Ÿ‘ GOOD! Most registration questions work well.'); + } else { + console.log('\nโš ๏ธ NEEDS IMPROVEMENT - Some registration questions unclear.'); + } +} + +testRegistrationQuestions().catch(console.error); diff --git a/scripts/test-remaining-issues.ts b/scripts/test-remaining-issues.ts new file mode 100644 index 0000000..776a72e --- /dev/null +++ b/scripts/test-remaining-issues.ts @@ -0,0 +1,53 @@ +/** + * Test Remaining Issues with Better Phrasing + */ + +import dotenv from 'dotenv'; +import path from 'path'; + +dotenv.config({ path: path.join(process.cwd(), '.env.local') }); + +import { answerQuestionWithRAG, isSpamOrOffTopic } from '../src/lib/geminiService'; + +const tests = [ + // Registration mistake variations + { q: "i made a mistake in registration" }, + { q: "i entered wrong email" }, + { q: "can i edit my registration" }, + { q: "how to fix registration mistake" }, + + // Help/support variations + { q: "who can i contact for help" }, + { q: "i have a problem with registration" }, + { q: "how to get support" }, + { q: "contact organizers" } +]; + +async function testRemaining() { + console.log('๐Ÿงช Testing Remaining Issues with Better Phrasing...\n'); + console.log('='.repeat(100)); + + for (const test of tests) { + console.log(`\n๐Ÿ“ Q: "${test.q}"`); + console.log('-'.repeat(100)); + + try { + const isBlocked = isSpamOrOffTopic(test.q); + + if (isBlocked) { + console.log('โŒ BLOCKED'); + continue; + } + + const answer = await answerQuestionWithRAG(test.q, { state: 'IDLE' }); + console.log(`โœ… A: ${answer}`); + + await new Promise(resolve => setTimeout(resolve, 2000)); + + } catch (error: any) { + console.log('โŒ ERROR:', error.message); + } + } +} + +testRemaining().catch(console.error); diff --git a/scripts/test-search.ts b/scripts/test-search.ts new file mode 100644 index 0000000..b0645df --- /dev/null +++ b/scripts/test-search.ts @@ -0,0 +1,29 @@ +/** + * Test Script: Debug keyword search + */ + +import { searchByKeyword } from '../src/lib/knowledgeBase'; + +const testQueries = [ + "explain this event", + "what is coderush", + "event details", + "tell me about event" +]; + +console.log('๐Ÿ” Testing keyword search...\n'); + +testQueries.forEach(query => { + console.log(`Query: "${query}"`); + const results = searchByKeyword(query, 3); + console.log(`Results: ${results.length}`); + + if (results.length > 0) { + results.forEach((doc, i) => { + console.log(` ${i + 1}. ${doc.id} - ${doc.question.substring(0, 60)}...`); + }); + } else { + console.log(' โŒ No results found'); + } + console.log(''); +}); diff --git a/scripts/test-tech-questions.ts b/scripts/test-tech-questions.ts new file mode 100644 index 0000000..6df631e --- /dev/null +++ b/scripts/test-tech-questions.ts @@ -0,0 +1,47 @@ +/** + * Test Technology-Related Questions + */ + +import dotenv from 'dotenv'; +import path from 'path'; + +dotenv.config({ path: path.join(process.cwd(), '.env.local') }); + +import { answerQuestionWithRAG } from '../src/lib/geminiService'; + +const techQuestions = [ + "can we use react", + "can we use next.js", + "can we use html css js", + "what technologies can we use", + "are there any restrictions on frameworks", + "can we use external apis", + "what are scenario based questions", + "will there be a problem statement", + "what should we build" +]; + +async function testTech() { + console.log('๐Ÿงช Testing Technology Questions...\n'); + console.log('='.repeat(80)); + + for (const question of techQuestions) { + console.log(`\n๐Ÿ“ Q: "${question}"`); + console.log('-'.repeat(80)); + + try { + const answer = await answerQuestionWithRAG(question, { state: 'IDLE' }); + console.log(`โœ… A: ${answer}`); + } catch (error: any) { + console.log(`โŒ Error: ${error.message}`); + } + + // Delay to avoid rate limits + await new Promise(resolve => setTimeout(resolve, 2000)); + } + + console.log('\n' + '='.repeat(80)); + console.log('โœ… Testing complete!'); +} + +testTech().catch(console.error); diff --git a/src/app/api/chat/route.ts b/src/app/api/chat/route.ts index 0adff22..38c56a2 100644 --- a/src/app/api/chat/route.ts +++ b/src/app/api/chat/route.ts @@ -6,6 +6,7 @@ import { appendToGoogleSheets } from "@/lib/googleSheets"; import { sendRegistrationEmail } from "@/lib/emailService"; import { Member } from "@/types/registration"; import { globalRateLimiter, getDuplicateErrorMessage, isDuplicateKeyError } from "@/lib/concurrencyHelpers"; +import { answerQuestionWithRAG, classifyIntent, getRegistrationReminder, checkQuestionRateLimit, isSpamOrOffTopic, getConversationalResponse } from "@/lib/geminiService"; type ReqBody = { sessionId: string; @@ -54,9 +55,220 @@ export async function POST(req: Request) { }, { status: 429 }); } - // find or create session + // ============================================================================ + // RAG-POWERED Q&A SYSTEM (NEW - DOESN'T AFFECT REGISTRATION FLOW) + // ============================================================================ + + // Find or create session console.log("๐Ÿ” Looking for registration with sessionId:", sessionId); let reg = await Registration.findOne({ sessionId }); + + // Check if user is asking a question (only if Gemini API is configured) + if (process.env.GOOGLE_GEMINI_API_KEY && message) { + // Classify user intent + const currentState = reg?.state || 'IDLE'; + + // CRITICAL: Skip RAG for specific registration states and data patterns + // This ensures registration data is NEVER intercepted by the AI system + const isInActiveRegistration = currentState !== 'IDLE' && currentState !== 'DONE'; + + // Detect if message looks like registration data (not a question) + const lowerMsg = message.toLowerCase().trim(); + + // Skip RAG for "edit" command when registration is DONE + if (currentState === 'DONE' && lowerMsg.includes('edit')) { + console.log('โšก Skipping RAG - edit command detected for DONE registration'); + // Fall through to registration logic below + } else { + const questionWords = ['what', 'when', 'where', 'how', 'why', 'can', 'is', 'are', 'do', 'does', 'will', 'should', 'which', 'who']; + const helpKeywords = ['help', 'format', 'example', 'explain', 'tell me', 'show me']; + const conversationalPhrases = ['i want', 'i need', 'i ask', 'give me', 'send me', 'show me', 'tell me', 'no no', 'wait', 'actually']; + const eventKeywords = ['venue', 'location', 'address', 'place', 'map', 'event', 'coderush', 'buildathon', 'hackathon', 'competition', 'guidelines', 'guideline', 'rules', 'information', 'details']; + + const startsWithQuestionWord = questionWords.some(word => lowerMsg.startsWith(word + ' ') || lowerMsg.startsWith(word + "'")); + const containsQuestionWord = questionWords.some(word => lowerMsg.includes(' ' + word + ' ') || lowerMsg.includes(' ' + word + '?') || lowerMsg.endsWith(' ' + word)); + const containsHelpKeyword = helpKeywords.some(word => lowerMsg.includes(word)); + const containsConversationalPhrase = conversationalPhrases.some(phrase => lowerMsg.includes(phrase)); + const containsEventKeyword = eventKeywords.some(keyword => lowerMsg.includes(keyword)); + const hasQuestionMark = message.includes('?'); + + const looksLikeRegistrationData = + /@/.test(message) || // Email + /^\d{6}[A-Z]$/i.test(message.trim()) || // Index number + /^(23|24)$/.test(message.trim()) || // Batch + /^(yes|no)$/i.test(message.trim()) || // Confirmation + // Name/data (not a question) - must not contain question/help/conversational/event keywords + (message.length >= 2 && message.length <= 100 && + !hasQuestionMark && + !startsWithQuestionWord && + !containsQuestionWord && + !containsHelpKeyword && + !containsConversationalPhrase && + !containsEventKeyword && + !/^(provide|tell|give|show|explain|describe)/i.test(lowerMsg)); // Not asking for information + + // If in active registration and looks like data, SKIP RAG entirely + if (isInActiveRegistration && looksLikeRegistrationData) { + console.log('โšก Skipping RAG - active registration detected with data pattern'); + console.log(` Message: "${message}"`); + // Fall through to registration logic below + } else { + // Only run intent classification if not clearly registration data + console.log(`๐Ÿ” Message doesn't look like registration data: "${message}"`); + console.log(` State: ${currentState}, In active registration: ${isInActiveRegistration}`); + const intent = await classifyIntent(message, currentState); + console.log(`๐Ÿง  Intent classified as: ${intent} (State: ${currentState})`); + + // Handle questions with RAG + if (intent === 'QUESTION') { + console.log('๐Ÿ“– Triggering RAG for question...'); + + // Check spam/off-topic + if (isSpamOrOffTopic(message)) { + let reply = "Hey! ๐Ÿ‘‹ I'm here to help with CodeRush 2025 - the buildathon event! Ask me about:\n" + + "โ€ข Event details & schedule\n" + + "โ€ข Team registration\n" + + "โ€ข Submission guidelines\n" + + "โ€ข Requirements & rules"; + + // Add registration reminder if user is mid-registration + if (reg && currentState !== 'IDLE' && currentState !== 'DONE') { + if (currentState === 'MEMBER_DETAILS') { + reply += '\n' + getRegistrationReminder({ + state: currentState, + currentMember: reg.currentMember, + tempMember: reg.tempMember, + teamBatch: reg.teamBatch + }); + } else if (currentState === 'BATCH_SELECTION') { + reply += '\n๐Ÿ“ Continue registration: Please select your team batch (23 or 24)'; + } else if (currentState === 'CONFIRMATION') { + reply += '\n๐Ÿ“ Continue registration: Type "yes" to confirm or "no" to edit your details'; + } + } else { + reply += "\n\n๐Ÿš€ Ready to register? Just type your team name to begin!"; + } + + return NextResponse.json({ reply }); + } + + // Check question rate limit + if (!checkQuestionRateLimit(sessionId)) { + return NextResponse.json({ + reply: "Whoa, lots of questions! ๐Ÿ˜„ Let's finish your registration first, then I'm happy to answer more. Ready to continue?" + }); + } + + try { + // Answer question using RAG + const answer = await answerQuestionWithRAG(message, { + state: currentState, + teamName: reg?.teamName, + currentMember: reg?.currentMember + }); + + // Add registration reminder if user is mid-registration + let reminder = ''; + if (reg && currentState !== 'IDLE' && currentState !== 'DONE') { + if (currentState === 'MEMBER_DETAILS') { + reminder = getRegistrationReminder({ + state: currentState, + currentMember: reg.currentMember, + tempMember: reg.tempMember, + teamBatch: reg.teamBatch + }); + } else if (currentState === 'BATCH_SELECTION') { + reminder = '\n\n๐Ÿ“ Continue registration: Please select your team batch (23 or 24)'; + } else if (currentState === 'CONFIRMATION') { + reminder = '\n\n๐Ÿ“ Continue registration: Type "yes" to confirm or "no" to edit your details'; + } + } else if (!reg || currentState === 'IDLE') { + // User hasn't started registration yet - prompt them to register + reminder = '\n\n๐Ÿš€ Ready to register? Just type your team name to begin!'; + } + + console.log('โœ… Question answered with RAG'); + return NextResponse.json({ + reply: answer + reminder + }); + } catch (ragError) { + console.error('โŒ RAG error:', ragError); + // Return fallback message instead of falling through to registration logic + const fallbackMessage = "I'm having trouble answering right now. Here's what I can help with:\n\n" + + "โ€ข CodeRush 2025 event info\n" + + "โ€ข Team registration\n" + + "โ€ข Submission requirements\n\n"; + + let reminder = ''; + if (reg && currentState !== 'IDLE' && currentState !== 'DONE') { + if (currentState === 'MEMBER_DETAILS') { + reminder = getRegistrationReminder({ + state: currentState, + currentMember: reg.currentMember, + tempMember: reg.tempMember, + teamBatch: reg.teamBatch + }); + } else if (currentState === 'BATCH_SELECTION') { + reminder = '\n\n๐Ÿ“ Continue registration: Please select your team batch (23 or 24)'; + } else if (currentState === 'CONFIRMATION') { + reminder = '\n\n๐Ÿ“ Continue registration: Type "yes" to confirm or "no" to edit your details'; + } + } + + return NextResponse.json({ + reply: fallbackMessage + reminder + }); + } + } + + // Handle greetings (only when IDLE) + if (intent === 'GREETING' && currentState === 'IDLE') { + return NextResponse.json({ + reply: "Hey there! ๐Ÿ‘‹ Welcome to CodeRush 2025! ๐Ÿš€\n\n" + + "I'm your registration assistant! I can help you:\n" + + "โ€ข Register your team (right here in chat!)\n" + + "โ€ข Answer event questions\n" + + "โ€ข Provide guidelines & rules\n\n" + + "Ready to register? Type your team name to begin! ๐Ÿ˜Š\n" + + "Or ask me anything about CodeRush 2025!" + }); + } + + // Handle conversational messages (friendly chat) + if (intent === 'CONVERSATIONAL') { + const response = getConversationalResponse(message); + + // Add registration reminder if user is mid-registration + let reminder = ''; + if (reg && currentState !== 'IDLE' && currentState !== 'DONE') { + if (currentState === 'MEMBER_DETAILS') { + reminder = getRegistrationReminder({ + state: currentState, + currentMember: reg.currentMember, + tempMember: reg.tempMember, + teamBatch: reg.teamBatch + }); + } else if (currentState === 'BATCH_SELECTION') { + reminder = '\n\n๐Ÿ“ Continue registration: Please select your team batch (23 or 24)'; + } else if (currentState === 'CONFIRMATION') { + reminder = '\n\n๐Ÿ“ Continue registration: Type "yes" to confirm or "no" to edit your details'; + } + } else if (!reg || currentState === 'IDLE') { + // User hasn't started registration yet - prompt them to register + reminder = '\n\n๐Ÿš€ Ready to register? Just type your team name to begin!'; + } + + return NextResponse.json({ + reply: response + reminder + }); + } + } + } + } + + // ============================================================================ + // EXISTING REGISTRATION FLOW (UNCHANGED) + // ============================================================================ if (!reg) { console.log("โœจ Creating new registration with team name"); @@ -94,6 +306,33 @@ export async function POST(req: Request) { return NextResponse.json({ reply: "โŒ This team name is reserved and cannot be used. Please choose another name." }); } + // Validation 7: Detect repeated characters (e.g., "aaaa", "1111") + // Check if more than 70% of the name is the same character + const charCounts = new Map(); + for (const char of trimmedTeamName.toLowerCase()) { + if (char !== ' ') { // Ignore spaces + charCounts.set(char, (charCounts.get(char) || 0) + 1); + } + } + const maxCharCount = Math.max(...Array.from(charCounts.values())); + const nonSpaceLength = trimmedTeamName.replace(/\s/g, '').length; + if (maxCharCount / nonSpaceLength > 0.7) { + return NextResponse.json({ reply: "โŒ Team name looks suspicious (too many repeated characters). Please enter a real team name." }); + } + + // Validation 8: Detect test-like patterns + const testPatterns = ['demo', 'sample', 'testing', 'temp', 'temporary', 'example', 'fake', 'dummy', 'asdf', 'qwerty', '1234', 'abcd']; + const lowerTeamName = trimmedTeamName.toLowerCase(); + if (testPatterns.some(pattern => lowerTeamName.includes(pattern))) { + return NextResponse.json({ reply: "โŒ Team name appears to be a test or placeholder. Please enter your actual team name." }); + } + + // Validation 9: Detect question words and event-related terms + const questionWords = ['what', 'where', 'when', 'who', 'why', 'how', 'which', 'prize', 'money', 'venue', 'location', 'date', 'time', 'event', 'registration', 'register', 'submit', 'guideline', 'rule']; + if (questionWords.some(word => lowerTeamName.includes(word))) { + return NextResponse.json({ reply: "โŒ That doesn't look like a valid team name. Please enter your actual team name (3-10 characters)." }); + } + // Check team count (only count completed registrations) const teamCount = await Registration.countDocuments({ state: "DONE" }); if (teamCount >= MAX_TEAMS) { @@ -121,7 +360,7 @@ export async function POST(req: Request) { console.log("๐Ÿ’พ New registration saved with team name:", reg.teamName); return NextResponse.json({ - reply: `Team name saved as "${reg.teamName}". ๐ŸŽ‰\n\nSelect your team's batch (all 4 members must be from the same batch):`, + reply: `Awesome team name! ๐ŸŽ‰ "${reg.teamName}" is registered!\n\nNow, which batch is your team from?\n(Remember: All 4 members must be from the same batch!)`, buttons: [ { text: "Batch 23", value: "23" }, { text: "Batch 24", value: "24" } @@ -357,6 +596,15 @@ export async function POST(req: Request) { if (!message) return NextResponse.json({ reply: `${memberLabel} โ€” Full name:` }); const trimmedMessage = message.trim(); + const lowerMessage = trimmedMessage.toLowerCase(); + + // Detect unhelpful responses + const unhelpfulResponses = ['i dont know', 'i don\'t know', 'idk', 'dont know', 'don\'t know', 'skip', 'pass', 'next', 'later', 'unknown', 'not sure', 'no idea']; + if (unhelpfulResponses.some(phrase => lowerMessage === phrase || lowerMessage.includes(phrase))) { + return NextResponse.json({ + reply: `๐Ÿ“ Please provide the actual full name for ${memberLabel}. This is required for registration!\n\n${memberLabel} โ€” Full name:` + }); + } // Validate that name is not just numbers (prevent batch numbers being used as names) if (/^\d+$/.test(trimmedMessage)) { @@ -372,14 +620,64 @@ export async function POST(req: Request) { }); } + // Detect repeated characters (e.g., "aaaa", "1111") + const charCounts = new Map(); + for (const char of trimmedMessage.toLowerCase()) { + if (char !== ' ') { // Ignore spaces + charCounts.set(char, (charCounts.get(char) || 0) + 1); + } + } + const maxCharCount = Math.max(...Array.from(charCounts.values())); + const nonSpaceLength = trimmedMessage.replace(/\s/g, '').length; + if (maxCharCount / nonSpaceLength > 0.7) { + return NextResponse.json({ + reply: `โŒ Name looks suspicious (too many repeated characters). Please enter the actual full name.\n\n${memberLabel} โ€” Full name:` + }); + } + + // Detect test-like patterns and question-like inputs + const testPatterns = ['test', 'demo', 'sample', 'testing', 'temp', 'example', 'fake', 'dummy', 'asdf', 'qwerty', 'abc', 'xyz']; + if (testPatterns.some(pattern => lowerMessage.includes(pattern))) { + return NextResponse.json({ + reply: `โŒ Name appears to be a test or placeholder. Please enter the actual full name.\n\n${memberLabel} โ€” Full name:` + }); + } + + // Detect question words that indicate user is asking a question instead of providing name + const questionWords = ['what', 'where', 'when', 'who', 'why', 'how', 'which', 'prize', 'money', 'venue', 'location', 'date', 'time', 'event', 'registration']; + if (questionWords.some(word => lowerMessage.includes(word))) { + return NextResponse.json({ + reply: `โŒ That doesn't look like a person's name. Please enter the actual full name for ${memberLabel}.\n\n${memberLabel} โ€” Full name:` + }); + } + reg.tempMember = { fullName: trimmedMessage, batch: reg.teamBatch }; await reg.save(); - return NextResponse.json({ reply: `${memberLabel} โ€” Index number (must start with ${reg.teamBatch}):` }); + const emoji = reg.currentMember === 1 ? "๐Ÿ‘‘" : "๐Ÿ‘ค"; + return NextResponse.json({ reply: `${emoji} ${memberLabel}: ${trimmedMessage}\n๐Ÿ“ What's ${trimmedMessage}'s index number?\n(Format: ${reg.teamBatch}****X - Example: ${reg.teamBatch}4001T)` }); } // indexNumber (validate against team batch) if (reg.tempMember && !reg.tempMember.indexNumber) { const trimmedMessage = message.trim().toUpperCase(); + const lowerMessage = message.trim().toLowerCase(); + + // Detect unhelpful responses + const unhelpfulResponses = ['i dont know', 'i don\'t know', 'idk', 'dont know', 'don\'t know', 'skip', 'pass', 'next', 'later', 'unknown', 'not sure', 'no idea', 'no index', 'none']; + if (unhelpfulResponses.some(phrase => lowerMessage === phrase || lowerMessage.includes(phrase))) { + return NextResponse.json({ + reply: `๐Ÿ“ Please provide the actual index number for ${memberLabel}. This is required for registration!\n\n${memberLabel} โ€” Index number:\n(Format: ${reg.teamBatch}****X - Example: ${reg.teamBatch}4001T)` + }); + } + + // Detect question words + const questionWords = ['what', 'where', 'when', 'who', 'why', 'how', 'which', 'prize', 'money', 'venue', 'location', 'date', 'time', 'event', 'registration']; + if (questionWords.some(word => lowerMessage.includes(word))) { + return NextResponse.json({ + reply: `โŒ That doesn't look like an index number. Please enter the actual index number for ${memberLabel}.\n\n${memberLabel} โ€” Index number:\n(Format: ${reg.teamBatch}****X - Example: ${reg.teamBatch}4001T)` + }); + } + if (!validators.index(trimmedMessage)) { return NextResponse.json({ reply: "โŒ Invalid index number. Must be 6 digits followed by a capital letter (e.g., 234001T)." }); } @@ -392,6 +690,24 @@ export async function POST(req: Request) { }); } + // Detect suspicious patterns (repeated digits or sequential patterns) + const middleFourDigits = trimmedMessage.substring(2, 6); + + // Check for repeated digits (e.g., "1111", "4444") + if (/^(.)\1{3}$/.test(middleFourDigits)) { + return NextResponse.json({ + reply: `โŒ The index number looks suspicious (repeated digits). Please enter your actual UoM index number:\n(Format: ${reg.teamBatch}****X - Example: ${reg.teamBatch}4001T)` + }); + } + + // Check for sequential patterns (e.g., "1234", "5678") + const isSequential = ['0123', '1234', '2345', '3456', '4567', '5678', '6789'].includes(middleFourDigits); + if (isSequential) { + return NextResponse.json({ + reply: `โŒ The index number looks suspicious (sequential pattern). Please enter your actual UoM index number:\n(Format: ${reg.teamBatch}****X - Example: ${reg.teamBatch}4001T)` + }); + } + // Check if index number already exists in current team const indexExistsInTeam = reg.members.some(m => m.indexNumber === trimmedMessage); if (indexExistsInTeam) { @@ -422,12 +738,49 @@ export async function POST(req: Request) { // Mark the field as modified for mongoose reg.markModified('tempMember'); await reg.save(); - return NextResponse.json({ reply: `${memberLabel} โ€” Email:` }); + const emoji = reg.currentMember === 1 ? "๐Ÿ‘‘" : "๐Ÿ‘ค"; + const memberName = reg.tempMember?.fullName || memberLabel; + return NextResponse.json({ reply: `${emoji} ${memberLabel}: ${memberName}\n๐Ÿ“ง What's ${memberName}'s email address?` }); } // email if (reg.tempMember && reg.tempMember.indexNumber && !reg.tempMember.email) { const trimmedMessage = message.trim(); + const lowerEmail = trimmedMessage.toLowerCase(); + + // Detect unhelpful responses + const unhelpfulResponses = ['i dont know', 'i don\'t know', 'idk', 'dont know', 'don\'t know', 'skip', 'pass', 'next', 'later', 'unknown', 'not sure', 'no idea', 'no email', 'none']; + if (unhelpfulResponses.some(phrase => lowerEmail === phrase || lowerEmail.includes(phrase))) { + return NextResponse.json({ + reply: `๐Ÿ“ Please provide the actual email address for ${memberLabel}. This is required for registration!\n\n${memberLabel} โ€” Email:` + }); + } + + // Detect question words + const questionWords = ['what', 'where', 'when', 'who', 'why', 'how', 'which', 'prize', 'money', 'venue', 'location', 'date', 'time', 'event', 'registration']; + if (questionWords.some(word => lowerEmail.includes(word))) { + return NextResponse.json({ + reply: `โŒ That doesn't look like an email address. Please enter the actual email address for ${memberLabel}.\n\n${memberLabel} โ€” Email:` + }); + } + + // Check for common email typos + const commonTypos = ['gamil.com', 'gmai.com', 'gmial.com', 'yahooo.com', 'yaho.com', 'hotmial.com', 'outlok.com']; + const typoFound = commonTypos.find(typo => lowerEmail.includes(typo)); + if (typoFound) { + const suggestions: Record = { + 'gamil.com': 'gmail.com', + 'gmai.com': 'gmail.com', + 'gmial.com': 'gmail.com', + 'yahooo.com': 'yahoo.com', + 'yaho.com': 'yahoo.com', + 'hotmial.com': 'hotmail.com', + 'outlok.com': 'outlook.com' + }; + return NextResponse.json({ + reply: `โŒ Possible typo detected! Did you mean "${suggestions[typoFound]}" instead of "${typoFound}"?\n\nPlease re-enter the correct email:` + }); + } // Check for invalid characters like backslashes if (trimmedMessage.includes('\\') || trimmedMessage.includes('/')) { @@ -475,23 +828,34 @@ export async function POST(req: Request) { await reg.save(); if ((reg.members || []).length < MEMBER_COUNT) { + const justAddedMember = reg.members[reg.members.length - 1]; reg.currentMember = (reg.currentMember || 1) + 1; await reg.save(); const nextMemberLabel = `Member ${reg.currentMember}`; + const progress = `(${reg.members.length}/${MEMBER_COUNT} members added)`; return NextResponse.json({ - reply: `${nextMemberLabel} โ€” Full name:` + reply: `โœ… ${justAddedMember.fullName} added successfully! ${progress}\n\n๐Ÿ‘ค ${nextMemberLabel}\nWhat's ${nextMemberLabel}'s full name?` }); } else { reg.state = "CONFIRMATION"; await reg.save(); const summaryLines = [ - `Team: ${reg.teamName}`, - `Batch: ${reg.teamBatch}`, - `Members:`, - ...(reg.members || []).map((m, i: number) => `${i + 1}. ${m.fullName} โ€” ${m.indexNumber} โ€” ${m.email}`), + `๐ŸŽ‰ Awesome! All team members added! (4/4)\n`, + `๐Ÿ“‹ Team Registration Summary`, + `โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”`, + `๐Ÿ† Team: ${reg.teamName}`, + `๐Ÿ“š Batch: ${reg.teamBatch}`, + `\n๐Ÿ‘ฅ Team Members:`, + ...(reg.members || []).map((m, i: number) => `${i === 0 ? '๐Ÿ‘‘' : '๐Ÿ‘ค'} ${i + 1}. ${m.fullName}\n ๐Ÿ“ ${m.indexNumber}\n ๐Ÿ“ง ${m.email}`), ]; const summary = summaryLines.join("\n"); - return NextResponse.json({ reply: `${summary}\n\n${states.CONFIRMATION.prompt}` }); + return NextResponse.json({ + reply: `${summary}\n\n${states.CONFIRMATION.prompt}`, + buttons: [ + { text: "Yes", value: "yes" }, + { text: "No", value: "no" } + ] + }); } } @@ -521,6 +885,16 @@ export async function POST(req: Request) { // BATCH_SELECTION handling if (reg.state === "BATCH_SELECTION") { const trimmedMessage = message.trim(); + + // Check if this is a question instead of batch selection + const lowerMsg = trimmedMessage.toLowerCase(); + const questionWords = ['what', 'when', 'where', 'how', 'why', 'can', 'is', 'are', 'do', 'does', 'will', 'should', 'which', 'who', 'tell', 'give', 'show', 'explain']; + const isLikelyQuestion = questionWords.some(word => lowerMsg.includes(word)) || lowerMsg.includes('?'); + + // If it looks like a question, let it go through the question handler above + // The question handler should have already caught it, but if we're here, + // it means the message passed through - so validate batch + if (!validators.batch(trimmedMessage)) { return NextResponse.json({ reply: "โŒ Invalid batch. Please select Batch 23 or Batch 24:", @@ -538,7 +912,7 @@ export async function POST(req: Request) { await reg.save(); return NextResponse.json({ - reply: `Team batch saved as Batch ${reg.teamBatch}.\n\nTeam Leader (Member 1) โ€” Full name:` + reply: `Perfect! Batch ${reg.teamBatch} selected! โœ…\n\nNow let's add your team members! ๐Ÿ‘ฅ\n\n๐Ÿ‘‘ Team Leader (Member 1)\nWhat's the team leader's full name?` }); } @@ -650,7 +1024,13 @@ export async function POST(req: Request) { ...(reg.members || []).map((m, i: number) => `${i + 1}. ${m.fullName} โ€” ${m.indexNumber} โ€” ${m.email}`), ]; const summary = summaryLines.join("\n"); - return NextResponse.json({ reply: `${summary}\n\n${states.CONFIRMATION.prompt}` }); + return NextResponse.json({ + reply: `${summary}\n\n${states.CONFIRMATION.prompt}`, + buttons: [ + { text: "Yes", value: "yes" }, + { text: "No", value: "no" } + ] + }); } // finalize on DONE diff --git a/src/components/ChatBox.tsx b/src/components/ChatBox.tsx index ab91bc2..8afaed9 100644 --- a/src/components/ChatBox.tsx +++ b/src/components/ChatBox.tsx @@ -24,18 +24,43 @@ interface Toast { type: 'error' | 'warning' | 'success'; } -// Helper function to render text with bold quotes +// Helper function to render text with bold quotes and clickable links const renderTextWithBoldQuotes = (text: string) => { + // First, split by quoted text const parts = text.split(/("([^"]+)")/g); + return parts.map((part, index) => { // Skip empty strings and the captured group content (every 3rd element starting from index 2) if (!part || (index > 0 && (index - 2) % 3 === 0)) { return null; } + + // Handle quoted text (make it bold) if (part.startsWith('"') && part.endsWith('"')) { return {part.slice(1, -1)}; } - return part; + + // Handle URLs in non-quoted text + const urlRegex = /(https?:\/\/[^\s]+)/g; + const textParts = part.split(urlRegex); + + return textParts.map((textPart, subIndex) => { + if (urlRegex.test(textPart)) { + return ( + + {textPart} + + ); + } + return {textPart}; + }); }).filter(Boolean); }; @@ -89,12 +114,7 @@ export default function ChatBot() { useEffect(() => { const loadSession = async () => { if (typeof window !== "undefined" && sessionId) { - // Show default message immediately for faster perceived loading - setMessages([{ - role: "bot", - content: "๐Ÿ‘‹ Hi! I'll register your team for CodeRush 2025. What's your team name?" - }]); - setIsLoadingSession(false); + setIsLoadingSession(true); try { // Run cleanup in the background (non-blocking) @@ -118,18 +138,41 @@ export default function ChatBot() { const data = await response.json(); if (data.success && data.exists) { - // If there's an existing session, update the message + // If there's an existing session, show "Welcome back" directly console.log("โœ… Restored session in state:", data.state); setMessages([{ role: "bot", content: data.message, buttons: data.buttons }]); + } else { + // Only show welcome message if no existing session + setMessages([{ + role: "bot", + content: "๐Ÿ‘‹ Welcome to CodeRush 2025! ๐Ÿš€\n\n" + + "I'm your registration assistant! I can help you:\n" + + "โ€ข Register your team (right here in chat!)\n" + + "โ€ข Answer event questions\n" + + "โ€ข Provide guidelines & rules\n\n" + + "Ready to register? Type your team name to begin! ๐Ÿ˜Š\n" + + "Or ask me anything about CodeRush 2025!" + }]); } - // If no existing session (data.exists === false), keep the default message already shown + setIsLoadingSession(false); } catch (error) { console.error("Session load error:", error); - // Keep the default message already shown + // Show default message on error + setMessages([{ + role: "bot", + content: "๐Ÿ‘‹ Welcome to CodeRush 2025! ๐Ÿš€\n\n" + + "I'm your registration assistant! I can help you:\n" + + "โ€ข Register your team (right here in chat!)\n" + + "โ€ข Answer event questions\n" + + "โ€ข Provide guidelines & rules\n\n" + + "Ready to register? Type your team name to begin! ๐Ÿ˜Š\n" + + "Or ask me anything about CodeRush 2025!" + }]); + setIsLoadingSession(false); } } }; @@ -147,33 +190,36 @@ export default function ChatBot() { }, 5000); }; - const resetSession = async () => { + const resetSession = () => { if (typeof window !== "undefined") { // Save current scroll position const currentScrollY = window.scrollY; - // Force delete the registration from MongoDB before resetting - try { - await fetch("/api/cleanup", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ sessionId, forceDelete: true }), - }); - console.log("๐Ÿ—‘๏ธ Force deleted registration for session:", sessionId); - } catch (error) { - console.error("Cleanup error during reset:", error); - // Continue with reset even if cleanup fails - } + // Fire-and-forget cleanup (don't wait for it to complete - instant reset) + fetch("/api/cleanup", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ sessionId, forceDelete: true }), + }) + .then(() => console.log("๐Ÿ—‘๏ธ Force deleted registration for session:", sessionId)) + .catch(error => console.error("Cleanup error during reset:", error)); + // Instant reset - don't wait for cleanup localStorage.removeItem("regSessionId"); const newId = uuidv4(); localStorage.setItem("regSessionId", newId); setSessionId(newId); setMessages([{ role: "bot", - content: "๐Ÿ‘‹ Hi! I'll register your team for CodeRush 2025. What's your team name?" + content: "๐Ÿ‘‹ Welcome to CodeRush 2025! ๐Ÿš€\n\n" + + "I'm your registration assistant! I can help you:\n" + + "โ€ข Register your team (right here in chat!)\n" + + "โ€ข Answer event questions\n" + + "โ€ข Provide guidelines & rules\n\n" + + "Ready to register? Type your team name to begin! ๐Ÿ˜Š\n" + + "Or ask me anything about CodeRush 2025!" }]); setInput(""); @@ -187,17 +233,20 @@ export default function ChatBot() { const handleButtonClick = async (value: string) => { // Check if this is the reset trigger if (value === "RESET") { - await resetSession(); + resetSession(); // No await - instant reset return; } // Check if this is the edit form trigger - if (value === "OPEN_EDIT_FORM") { + if (value === "OPEN_EDIT_FORM" || value === "edit") { // Find the last message with registration data const lastMessageWithData = [...messages].reverse().find(m => m.registrationData); if (lastMessageWithData && lastMessageWithData.registrationData) { setEditData(lastMessageWithData.registrationData); setShowEditModal(true); + } else { + // If no registration data in messages, request it from the server + await sendMessageToAPI("edit"); } return; } diff --git a/src/lib/geminiService.ts b/src/lib/geminiService.ts new file mode 100644 index 0000000..d60ad50 --- /dev/null +++ b/src/lib/geminiService.ts @@ -0,0 +1,668 @@ +/** + * Gemini AI Service with RAG + * Uses Gemini 2.0 Flash for intelligent Q&A + */ + +import { GoogleGenerativeAI } from '@google/generative-ai'; +import { searchVectorDatabase, isPineconeAvailable } from './vectorService'; +import { searchByKeyword, type KnowledgeDocument } from './knowledgeBase'; + +// Lazy initialization to avoid caching issues +let genAI: GoogleGenerativeAI | null = null; + +function getGenAI(): GoogleGenerativeAI { + if (!genAI) { + genAI = new GoogleGenerativeAI(process.env.GOOGLE_GEMINI_API_KEY || ''); + } + return genAI; +} + +const MODEL_NAME = 'gemini-2.0-flash-exp'; // Gemini 2.0 Flash (experimental) + +// System prompt for the AI +const SYSTEM_PROMPT = `You are a friendly and enthusiastic assistant for CodeRush 2025, a buildathon event at the University of Moratuwa - Faculty of IT. + +**Your Personality:** +- Be warm, friendly, and conversational (like talking to a helpful friend) +- Show enthusiasm about the event with emojis when appropriate +- Be encouraging and supportive +- Use simple, casual language (avoid being too formal or robotic) +- Make users feel welcome and excited about CodeRush! + +**Response Style:** +- Keep answers short and sweet (2-3 sentences max) +- Use emojis occasionally to add friendliness (but don't overdo it) +- Break up longer info with bullet points or line breaks for readability +- End with encouraging phrases like "Good luck!", "See you there!", "Excited to have you!", etc. +- Use "you" and "your" to make it personal + +**Important Rules:** +1. You MUST answer ALL questions about CodeRush 2025 - including questions about the event, venue, location, dates, registration, teams, technologies, submission, prizes, rules, organizers, etc. +2. Questions about the "venue", "location", "competition", "buildathon", "hackathon", "event", "map", "address", "directions" are ALL valid CodeRush 2025 questions - answer them using the context provided! +3. ONLY use information from the provided context - DO NOT make up or hallucinate information +4. **CRITICAL**: Registration happens IN THIS CHAT, NOT on a form or registration page! When users ask "how to register", tell them to type their team name in the chat to start +5. **CRITICAL**: When users ask about location, venue, address, map, or directions, you MUST ALWAYS include the Google Maps link: https://maps.app.goo.gl/WsNriCAdxhJadHaK7 (even if it's not explicitly in the context) +6. NEVER mention "registration page", "form", "fill out", or "Report an Issue button" - registration happens in chat +7. If the context doesn't contain specific details, still provide what you know from the context +8. NEVER refuse to answer questions that mention CodeRush, event, location, venue, registration, or any buildathon-related terms +9. Always maintain a helpful, positive, and friendly tone + +**Examples of Friendly Responses:** +- Instead of: "The event is on November 15, 2025." + Say: "CodeRush 2025 is happening on November 15! ๐ŸŽ‰ Mark your calendar for an exciting day of coding from 8 AM to 6 PM!" + +- Instead of: "Teams must have 4 members." + Say: "You'll need exactly 4 teammates for this buildathon! Make sure everyone's from the same batch (23 or 24). Start gathering your dream team! ๐Ÿ‘ฅ" + +- Instead of: "Submit your project on time." + Say: "Don't forget to submit by 6 PM on event day! โฐ You got this!" + +- For ANY location/venue/map question: + Say: "CodeRush 2025 is at the Faculty of IT, University of Moratuwa! ๐Ÿ“ Here's the Google Maps link: https://maps.app.goo.gl/WsNriCAdxhJadHaK7 See you there!" + +- For registration questions ("how to register", "how do I register", etc.): + Say: "You can register right here in this chat! ๐Ÿš€ Just type your team name to begin! I'll guide you through the rest - super easy!" + +**Context Format:** +You will receive relevant information from our knowledge base. Use this context to answer the user's question in a friendly, conversational way.`; + +/** + * Answer question using RAG (Retrieval Augmented Generation) + */ +export async function answerQuestionWithRAG( + question: string, + registrationContext?: { + state: string; + teamName?: string; + currentMember?: number; + } +): Promise { + try { + // Validate inputs + if (!question || question.trim().length === 0) { + console.warn('โš ๏ธ Empty question provided to RAG'); + return "Please ask me a question about CodeRush 2025! I'm here to help! ๐Ÿ˜Š"; + } + + // Handle very short/incomplete questions + const trimmedQuestion = question.trim().toLowerCase(); + const singleQuestionWords = ['what', 'when', 'where', 'how', 'why', 'who']; + if (singleQuestionWords.includes(trimmedQuestion)) { + return "I'd love to help! Could you be more specific? Try asking:\n\n" + + "โ€ข When is CodeRush 2025?\n" + + "โ€ข Where is the venue?\n" + + "โ€ข What are the rules?\n" + + "โ€ข How do I register?"; + } + + // Step 1: Retrieve relevant documents + let relevantDocs: KnowledgeDocument[]; + + try { + const pineconeAvailable = await isPineconeAvailable(); + + if (pineconeAvailable) { + // Use vector search for semantic matching + relevantDocs = await searchVectorDatabase(question, 3); + console.log('โœ… Using vector search'); + } else { + // Fallback to keyword search + relevantDocs = searchByKeyword(question, 3); + console.log('โš ๏ธ Using keyword search (Pinecone unavailable)'); + } + } catch (retrievalError) { + console.error('โŒ Error retrieving documents:', retrievalError); + // Fallback to keyword search on any error + relevantDocs = searchByKeyword(question, 3); + console.log('โš ๏ธ Using keyword search (fallback)'); + } + + // Validate we got documents + console.log('๐Ÿ“š Retrieved documents count:', relevantDocs?.length || 0); + if (relevantDocs && relevantDocs.length > 0) { + console.log('๐Ÿ“„ First doc:', relevantDocs[0].question); + } + + // Failsafe: If no docs found, try again with broader search for common topics + if (!relevantDocs || relevantDocs.length === 0) { + console.warn('โš ๏ธ No relevant documents found, trying broader search...'); + const lowerQ = question.toLowerCase(); + + // Try specific keyword searches for common questions + // Event date & time questions + if (lowerQ.includes('date') || lowerQ.includes('when') || lowerQ.includes('time') || + lowerQ.includes('schedule') || lowerQ.includes('day')) { + relevantDocs = searchByKeyword('date time when schedule', 3); + console.log('๐Ÿ”„ Retry with date/time keywords:', relevantDocs.length); + } + // Location & venue questions + else if (lowerQ.includes('venue') || lowerQ.includes('location') || lowerQ.includes('where') || + lowerQ.includes('place') || lowerQ.includes('address') || lowerQ.includes('map') || + lowerQ.includes('direction')) { + relevantDocs = searchByKeyword('venue location address map', 3); + console.log('๐Ÿ”„ Retry with location keywords:', relevantDocs.length); + } + // Registration questions + else if (lowerQ.includes('register') || lowerQ.includes('registration') || + lowerQ.includes('sign up') || lowerQ.includes('join') || lowerQ.includes('enroll')) { + relevantDocs = searchByKeyword('register registration how to join', 3); + console.log('๐Ÿ”„ Retry with registration keywords:', relevantDocs.length); + } + // Team questions + else if (lowerQ.includes('team') || lowerQ.includes('member') || lowerQ.includes('batch') || + lowerQ.includes('size') || lowerQ.includes('how many')) { + relevantDocs = searchByKeyword('team members batch size', 3); + console.log('๐Ÿ”„ Retry with team keywords:', relevantDocs.length); + } + // Submission questions + else if (lowerQ.includes('submit') || lowerQ.includes('submission') || lowerQ.includes('deliverable') || + lowerQ.includes('deadline') || lowerQ.includes('github') || lowerQ.includes('demo')) { + relevantDocs = searchByKeyword('submit submission deadline deliverables', 3); + console.log('๐Ÿ”„ Retry with submission keywords:', relevantDocs.length); + } + // Technology & tools questions + else if (lowerQ.includes('tech') || lowerQ.includes('framework') || lowerQ.includes('language') || + lowerQ.includes('tool') || lowerQ.includes('react') || lowerQ.includes('python') || + lowerQ.includes('allowed') || lowerQ.includes('restriction') || lowerQ.includes('can we use')) { + relevantDocs = searchByKeyword('technology framework language tools allowed', 3); + console.log('๐Ÿ”„ Retry with tech keywords:', relevantDocs.length); + } + // Guidelines & rules questions + else if (lowerQ.includes('guideline') || lowerQ.includes('rule') || lowerQ.includes('requirement') || + lowerQ.includes('regulation') || lowerQ.includes('format')) { + relevantDocs = searchByKeyword('guidelines rules requirements format', 3); + console.log('๐Ÿ”„ Retry with guidelines keywords:', relevantDocs.length); + } + // General/vague questions (tell me, explain, what, etc.) + else if (lowerQ.includes('tell') || lowerQ.includes('explain') || lowerQ.includes('describe') || + lowerQ.includes('what') || lowerQ.includes('about') || lowerQ.includes('detail') || + lowerQ.includes('info') || lowerQ.includes('event') || lowerQ.includes('coderush')) { + relevantDocs = searchByKeyword('event overview details about coderush', 3); + console.log('๐Ÿ”„ Retry with general event keywords:', relevantDocs.length); + } + // Catch-all: Use keyword search on the original query + else { + relevantDocs = searchByKeyword(question, 3); + console.log('๐Ÿ”„ Retry with keyword search on original query:', relevantDocs.length); + } + } + + // If still no documents, return fallback message + if (!relevantDocs || relevantDocs.length === 0) { + console.warn('โš ๏ธ No relevant documents found for question:', question); + return "I couldn't find specific information about that. Try asking about:\n\n" + + "โ€ข Event date & location\n" + + "โ€ข Team registration\n" + + "โ€ข Submission requirements\n" + + "โ€ข Technologies & tools"; + } + + // Step 2: Build context from retrieved documents + const context = relevantDocs + .map((doc, idx) => `[${idx + 1}] ${doc.question}\n${doc.answer}`) + .join('\n\n'); + + // Step 3: Add registration context if available + let registrationInfo = ''; + if (registrationContext && registrationContext.state !== 'IDLE') { + if (registrationContext.state === 'MEMBER_DETAILS') { + const memberLabel = registrationContext.currentMember === 1 + ? 'Team Leader' + : `Member ${registrationContext.currentMember}`; + registrationInfo = `\n\n**User Status:** Currently registering their team. Waiting for ${memberLabel} details.`; + } else if (registrationContext.state === 'DONE') { + registrationInfo = `\n\n**User Status:** Already completed registration for team "${registrationContext.teamName}".`; + } + } + + // Step 4: Generate answer with Gemini + try { + const ai = getGenAI(); + const model = ai.getGenerativeModel({ + model: MODEL_NAME, + systemInstruction: SYSTEM_PROMPT + }); + + const prompt = `**Context from Knowledge Base:** +${context} +${registrationInfo} + +**User Question:** ${question} + +**Instructions:** +- This question is about CodeRush 2025. Answer it using the context above. +- The context provided is relevant and pre-filtered - trust it and use it to answer. +- Be concise (2-4 sentences) and friendly. +- Questions about venue, location, event, competition, buildathon are all valid - answer them!`; + + const result = await model.generateContent(prompt); + const response = result.response.text(); + + if (!response || response.trim().length === 0) { + console.warn('โš ๏ธ Empty response from Gemini'); + throw new Error('Empty response from model'); + } + + return response.trim(); + } catch (geminiError) { + console.error('โŒ Error generating response with Gemini:', geminiError); + + // If Gemini fails, return the first document's answer directly + if (relevantDocs.length > 0) { + return relevantDocs[0].answer; + } + + throw geminiError; // Re-throw to be caught by outer catch + } + } catch (error) { + console.error('โŒ Error in Gemini RAG:', error); + + // Fallback response + return "Oops! ๐Ÿ˜… I'm having a little trouble right now. Here's what I can help with:\n\n" + + "โ€ข CodeRush 2025 event info\n" + + "โ€ข Registration questions\n" + + "โ€ข Team requirements\n" + + "โ€ข Submission guidelines\n\n" + + "Try asking again, or continue with your registration!"; + } +} + +/** + * Classify if message is a question or registration data + */ +export async function classifyIntent( + message: string, + registrationState: string +): Promise<'QUESTION' | 'REGISTRATION' | 'GREETING' | 'CONVERSATIONAL'> { + const lowerMsg = message.toLowerCase().trim(); + + // Greeting detection (only when IDLE) + const greetings = ['hi', 'hello', 'hey', 'sup', 'yo']; + + // Check for exact greetings or greetings with spaces + if (registrationState === 'IDLE' && ( + greetings.includes(lowerMsg) || + lowerMsg.startsWith('hi ') || + lowerMsg.startsWith('hello ') || + lowerMsg.startsWith('hey ') + )) { + return 'GREETING'; + } + + // Check for greeting variations (e.g., "hiiiii", "hellooo", "heyyy") + if (registrationState === 'IDLE') { + // Pattern: greeting word with repeated letters + const greetingPatterns = [ + /^h+i+$/, // hi, hii, hiii, hiiii, etc. + /^h+e+l+o+$/, // helo, hello, hellooo, etc. + /^h+e+y+$/, // hey, heyy, heyyy, etc. + /^s+u+p+$/, // sup, supp, suppp, etc. + /^y+o+$/ // yo, yoo, yooo, etc. + ]; + + if (greetingPatterns.some(pattern => pattern.test(lowerMsg))) { + return 'GREETING'; + } + } + + // Conversational/Social phrases (friendly chat) + const conversationalPhrases = [ + // Asking about bot + 'how are you', 'how r u', 'how are u', 'whats up', 'what\'s up', 'wassup', + 'how do you do', 'how is it going', 'how\'s it going', 'hows it going', + + // Thanks/Gratitude + 'thank you', 'thanks', 'thankyou', 'thank u', 'thx', 'ty', 'thanks a lot', + 'appreciate it', 'cheers', 'cool', 'awesome', 'great', 'perfect', 'nice', + + // Acknowledgments + 'ok', 'okay', 'alright', 'sure', 'got it', 'understood', 'i see', 'cool', + 'nice', 'good', 'fine', 'kk', 'k', 'okie', 'okey', + + // Goodbyes + 'bye', 'goodbye', 'see you', 'see ya', 'cya', 'later', 'bye bye', + + // Expressions + 'lol', 'haha', 'hehe', 'wow', 'omg', 'oh', 'ah', 'oh okay', 'oh ok', + + // Questions about bot (but NOT about event/competition) + 'who are you', 'what are you', 'are you a bot', 'are you human', 'your name', + 'who you are', 'what you are', 'who is this' + ]; + + // Check if message is conversational + const isConversational = conversationalPhrases.some(phrase => { + // Exact match + if (lowerMsg === phrase) return true; + // Starts with phrase (followed by space or punctuation) + if (lowerMsg.startsWith(phrase + ' ') || lowerMsg.startsWith(phrase + '?') || + lowerMsg.startsWith(phrase + '!') || lowerMsg.startsWith(phrase + '.')) return true; + // Ends with phrase (preceded by space or is standalone with punctuation) + if (lowerMsg.endsWith(' ' + phrase)) return true; + // Just the phrase with punctuation at end + if (lowerMsg === phrase + '?' || lowerMsg === phrase + '!' || lowerMsg === phrase + '.') return true; + return false; + }); + + // BUT: If message contains event-related keywords, it's a QUESTION not conversational + const eventKeywords = [ + 'event', 'coderush', 'buildathon', 'hackathon', 'competition', 'compet', + 'venue', 'location', 'date', 'time', 'when', 'where', 'register', + 'team', 'submission', 'prize', 'judging', 'batch', 'member' + ]; + + const hasEventKeyword = eventKeywords.some(keyword => lowerMsg.includes(keyword)); + + // Override conversational if asking about event + if (isConversational && hasEventKeyword) { + // It's a question about the event, not just chatting + return 'QUESTION'; + } + + if (isConversational) { + return 'CONVERSATIONAL'; + } + + // Data format detection (high confidence = registration data) + const looksLikeEmail = /@/.test(message) && /\.[a-z]{2,}$/i.test(message); + const looksLikeIndex = /^\d{6}[A-Z]$/i.test(message.trim()); + const looksLikeBatch = /^(23|24)$/.test(message.trim()); + const looksLikeConfirmation = /^(yes|no)$/i.test(message.trim()); + + // If clearly data format, return registration immediately + if (looksLikeEmail || looksLikeIndex || looksLikeBatch || looksLikeConfirmation) { + return 'REGISTRATION'; + } + + // PRIORITY: If in active registration flow (not IDLE, not DONE), default to REGISTRATION + if (registrationState !== 'IDLE' && registrationState !== 'DONE') { + // Quick pattern checks for questions + const questionWords = ['what', 'when', 'where', 'how', 'why', 'can i', 'is it', 'are there', 'do i', 'does it']; + const startsWithQuestion = questionWords.some(word => lowerMsg.startsWith(word)); + const hasQuestionMark = lowerMsg.includes('?'); + + // Help/question indicators + const helpWords = ['help', 'format', 'example', 'explain', 'tell me', 'show me']; + const needsHelp = helpWords.some(word => lowerMsg.includes(word)); + + // Command words (provide, give, tell, show, etc.) + const commandWords = ['provide', 'give', 'tell', 'show', 'explain', 'describe', 'send']; + const startsWithCommand = commandWords.some(word => lowerMsg.startsWith(word)); + + // Event-related keywords + const eventKeywords = ['guidelines', 'guideline', 'rules', 'information', 'details', 'event', 'venue', 'location', 'date', 'time']; + const hasEventKeyword = eventKeywords.some(keyword => lowerMsg.includes(keyword)); + + // Single word questions (very short messages that are clearly questions) + const singleWordQuestions = ['what', 'when', 'where', 'how', 'why', 'help']; + const isSingleWordQuestion = singleWordQuestions.includes(lowerMsg); + + // Treat as question if: + // - Clearly a question format + // - Starts with command word (provide, give, tell, etc.) + // - Contains event keywords (guidelines, rules, etc.) + if (isSingleWordQuestion || hasQuestionMark || (startsWithQuestion && message.length > 10) || + needsHelp || startsWithCommand || (hasEventKeyword && message.length > 3)) { + return 'QUESTION'; + } + + // During registration, assume everything else is registration data + return 'REGISTRATION'; + } + + // When IDLE (no registration in progress) + const questionWords = ['what', 'when', 'where', 'how', 'why', 'can', 'is', 'are', 'do', 'does', 'will', 'should']; + const startsWithQuestion = questionWords.some(word => lowerMsg.startsWith(word)); + const hasQuestionMark = lowerMsg.includes('?'); + const helpWords = ['help', 'format', 'example', 'explain', 'tell me', 'show me']; + const needsHelp = helpWords.some(word => lowerMsg.includes(word)); + + // If has question indicators, it's a question + if (hasQuestionMark || startsWithQuestion || needsHelp) { + return 'QUESTION'; + } + + // If IDLE and looks like a team name (3-10 chars, letters/numbers), treat as registration + if (registrationState === 'IDLE' && message.length >= 3 && message.length <= 10 && /^[a-zA-Z0-9\s\-_]+$/.test(message)) { + return 'REGISTRATION'; + } + + // Default for IDLE: treat as question for better UX + return 'QUESTION'; +} + +/** + * Generate registration reminder based on current state + */ +export function getRegistrationReminder(registrationState: { + state: string; + currentMember?: number; + tempMember?: { + fullName?: string; + indexNumber?: string; + email?: string; + }; + teamBatch?: string; +}): string { + if (registrationState.state !== 'MEMBER_DETAILS') { + return ''; + } + + const memberLabel = registrationState.currentMember === 1 + ? 'Team Leader' + : `Member ${registrationState.currentMember}`; + + const tempMember = registrationState.tempMember; + + if (!tempMember || !tempMember.fullName) { + return `\n\n๐Ÿ“ Continue registration:\n${memberLabel} โ€” Full name:`; + } + + if (!tempMember.indexNumber) { + return `\n\n๐Ÿ“ Continue registration:\n${memberLabel} โ€” Index number (must start with ${registrationState.teamBatch}):`; + } + + if (!tempMember.email) { + return `\n\n๐Ÿ“ Continue registration:\n${memberLabel} โ€” Email:`; + } + + return `\n\n๐Ÿ“ Continue registration:\n${memberLabel} โ€” Please provide the next detail.`; +} + +/** + * Rate limiter for questions + */ +const questionCounts = new Map(); +const MAX_QUESTIONS_PER_MINUTE = 10; +const RATE_LIMIT_WINDOW = 60000; // 1 minute + +export function checkQuestionRateLimit(sessionId: string): boolean { + const now = Date.now(); + const record = questionCounts.get(sessionId); + + if (!record || now > record.resetTime) { + // Reset or initialize + questionCounts.set(sessionId, { + count: 1, + resetTime: now + RATE_LIMIT_WINDOW + }); + return true; + } + + if (record.count >= MAX_QUESTIONS_PER_MINUTE) { + return false; // Rate limit exceeded + } + + record.count++; + return true; +} + +/** + * Generate friendly conversational response + */ +export function getConversationalResponse(message: string): string { + const lowerMsg = message.toLowerCase().trim(); + + // How are you / What's up + if (lowerMsg.includes('how are you') || lowerMsg.includes('how r u') || lowerMsg.includes('how are u') || + lowerMsg.includes('whats up') || lowerMsg.includes('what\'s up') || lowerMsg.includes('wassup') || + lowerMsg.includes('how is it going') || lowerMsg.includes('how\'s it going')) { + return "I'm doing great, thanks for asking! ๐Ÿ˜Š I'm super excited to help you with CodeRush 2025! " + + "Whether you need event info or want to register your team, I'm here for you. What can I help with?"; + } + + // Thanks / Gratitude + if (lowerMsg.includes('thank') || lowerMsg.includes('thx') || lowerMsg.includes('ty') || + lowerMsg.includes('appreciate') || lowerMsg.includes('cheers')) { + return "You're very welcome! ๐Ÿ˜Š Happy to help! If you have more questions about CodeRush 2025 or need " + + "help with registration, just ask!"; + } + + // Acknowledgments (ok, cool, nice, etc.) + if (lowerMsg === 'ok' || lowerMsg === 'okay' || lowerMsg === 'alright' || lowerMsg === 'sure' || + lowerMsg === 'cool' || lowerMsg === 'nice' || lowerMsg === 'great' || lowerMsg === 'awesome' || + lowerMsg === 'perfect' || lowerMsg === 'good' || lowerMsg === 'fine' || lowerMsg === 'kk' || lowerMsg === 'k') { + return "Awesome! ๐Ÿ‘ Ready to get started? Share your team name to begin registration, or ask me anything about CodeRush 2025!"; + } + + // Goodbye + if (lowerMsg.includes('bye') || lowerMsg.includes('see you') || lowerMsg.includes('see ya') || + lowerMsg.includes('cya') || lowerMsg.includes('later')) { + return "See you at CodeRush 2025! ๐Ÿ‘‹ Feel free to come back anytime if you need help with registration or have questions. Good luck! ๐Ÿš€"; + } + + // Expressions (lol, haha, wow, omg) + if (lowerMsg === 'lol' || lowerMsg === 'haha' || lowerMsg === 'hehe' || lowerMsg === 'wow' || + lowerMsg === 'omg' || lowerMsg === 'oh' || lowerMsg === 'ah') { + return "๐Ÿ˜„ Glad you're having a good time! Need any help with CodeRush 2025 registration or event details?"; + } + + // Questions about the bot + if (lowerMsg.includes('who are you') || lowerMsg.includes('what are you') || + lowerMsg.includes('who you are') || lowerMsg.includes('what you are') || + lowerMsg.includes('who is this') || lowerMsg.includes('what is this') || + lowerMsg.includes('are you a bot') || lowerMsg.includes('are you human') || lowerMsg.includes('your name')) { + return "I'm your friendly CodeRush 2025 registration assistant! ๐Ÿค– I'm here to help you with everything about the buildathon - " + + "from event details to team registration right here in this chat. Think of me as your personal guide! What would you like to know?"; + } + + // Default conversational response + return "๐Ÿ˜Š I'm here to help with CodeRush 2025! Feel free to:\n\n" + + "โ€ข Ask about the event (date, venue, rules)\n" + + "โ€ข Register your team\n" + + "โ€ข Get info about submissions\n\n" + + "What can I help you with?"; +} + +/** + * Check if question is spam or off-topic + */ +export function isSpamOrOffTopic(message: string): boolean { + const lowerMsg = message.toLowerCase(); + + // Spam patterns + const spamPatterns = [ + /^(.)\1{4,}$/i, // Repeated characters (aaaaa) + /buy|sell|purchase|discount|offer/i, + /click here|visit website/i, // Removed "link" to allow "map link" + /^\d+$/ // Only numbers (but allow index numbers to pass through via other checks) + ]; + + // Inappropriate/Adult content + const inappropriateKeywords = [ + 'sex', 'porn', 'xxx', 'nude', 'naked', 'adult content', + 'fuck', 'shit', 'damn', 'bitch', 'ass', 'dick', 'cock', + 'pussy', 'vagina', 'penis', 'boobs', 'breast', + 'dating', 'hookup', 'sexy', 'hot girls', 'hot boys', + 'drugs', 'weed', 'cannabis', 'cocaine', 'alcohol', 'beer', 'drunk', + 'violence', 'kill', 'murder', 'weapon', 'gun', 'bomb', + 'hate', 'racist', 'discrimination' + ]; + + // Off-topic keywords (too general to be CodeRush-related) + const offTopicKeywords = [ + // Entertainment & News + 'weather', 'movie', 'song', 'game', 'cricket', 'football', 'sports', + 'recipe', 'diet', 'workout', 'netflix', 'music', 'celebrity', + 'fashion', 'shopping', 'restaurant', 'travel', 'vacation', + + // Programming tutorials (not CodeRush-specific) + 'what is react', 'what is python', 'what is javascript', 'what is java', + 'what is node', 'what is angular', 'what is vue', 'what is django', + 'how to code', 'learn programming', 'teach me', 'tutorial', + 'what is html', 'what is css', 'what is sql', 'what is database', + + // General tech help (not event-related) + 'install', 'download', 'setup node', 'install python', 'error in code', + 'debug', 'fix my code', 'syntax error', 'compile error', + + // Random/Unrelated + 'joke', 'story', 'poem', 'translate', 'define', 'meaning of', + 'horoscope', 'lottery', 'astrology', 'fortune' + ]; + + // Conversational phrases that should NOT be flagged as off-topic + const allowedConversational = [ + 'how are you', 'how r u', 'how are u', + 'thank you', 'thanks', 'thankyou', 'thank u', 'thx', 'ty', + 'bye', 'goodbye', 'see you', + 'ok', 'okay', 'cool', 'nice', 'awesome', 'great', 'perfect', + 'who are you', 'what are you', 'are you a bot', 'your name', 'how old are you', + 'lol', 'haha' + ]; + + // Don't flag conversational phrases (exact match or starts/ends with) + const isAllowedConversational = allowedConversational.some(phrase => { + // Exact match + if (lowerMsg === phrase) return true; + // Starts with phrase followed by space or punctuation + if (lowerMsg.startsWith(phrase + ' ') || lowerMsg.startsWith(phrase + '?') || lowerMsg.startsWith(phrase + '!')) return true; + // Ends with phrase preceded by space + if (lowerMsg.endsWith(' ' + phrase) || lowerMsg.endsWith('?' + phrase) || lowerMsg.endsWith('!' + phrase)) return true; + return false; + }); + + if (isAllowedConversational) { + return false; + } + + // Check spam patterns + if (spamPatterns.some(pattern => pattern.test(message))) { + return true; + } + + // Check inappropriate content (highest priority) + if (inappropriateKeywords.some(keyword => lowerMsg.includes(keyword))) { + return true; + } + + // Check off-topic keywords + if (offTopicKeywords.some(keyword => lowerMsg.includes(keyword))) { + return true; + } + + // Programming language detection (block programming help questions) + const programmingQuestions = [ + /^(what|tell me|explain) (is|about) (react|python|javascript|java|node|angular|vue|django)/i, + /how (to|do i) (code|program|learn)/i, + /(teach|show) me (how to|programming|coding)/i, + /^(help|fix|debug) (my|this) code/i + ]; + + if (programmingQuestions.some(pattern => pattern.test(lowerMsg))) { + return true; + } + + // Inappropriate question patterns + const inappropriatePatterns = [ + /^what is (sex|porn|drugs)/i, + /how to (hack|cheat|steal)/i, + /(dating|hookup|meet) (girls|boys|people)/i + ]; + + if (inappropriatePatterns.some(pattern => pattern.test(lowerMsg))) { + return true; + } + + return false; +} diff --git a/src/lib/knowledgeBase.ts b/src/lib/knowledgeBase.ts new file mode 100644 index 0000000..fd8a7ca --- /dev/null +++ b/src/lib/knowledgeBase.ts @@ -0,0 +1,657 @@ +/** + * Knowledge Base for CodeRush 2025 + * Contains all platform information for RAG system + */ + +export interface KnowledgeDocument { + id: string; + category: string; + question: string; + answer: string; + keywords: string[]; + priority: number; // Higher = more important +} + +export const knowledgeBase: KnowledgeDocument[] = [ + // Event Information + { + id: "event-overview", + category: "event", + question: "What is CodeRush 2025? Tell me about the event details.", + answer: "CodeRush 2025 is a 10-hour buildathon happening on November 15, 2025 (8 AM - 6 PM) at the University of Moratuwa - Faculty of IT! ๐Ÿš€ It's an exciting coding competition where teams of 4 students work on real-world scenario-based problems. You can use ANY technology or framework you want! Registration is open now (max 100 teams, first-come first-served). Awards ceremony is on November 25th. Here's the venue: https://maps.app.goo.gl/WsNriCAdxhJadHaK7", + keywords: ["event details", "about event", "what is", "overview", "tell me about", "coderush", "event info", "details", "information", "about", "explain", "describe", "this event", "the event"], + priority: 10 + }, + { + id: "competition-guidelines", + category: "event", + question: "What are the competition guidelines and rules?", + answer: "Here are the key guidelines for CodeRush 2025:\n\n๐Ÿ‘ฅ Teams: Exactly 4 members from same batch (23 or 24)\nโฐ Duration: 10 hours (8 AM - 6 PM, November 15, 2025)\n๐Ÿ’ป Tech: Use ANY technology/framework you want!\n๐Ÿ“ Submission: GitHub repo (public) + Google Drive folder with demo video & report\n๐Ÿšซ Rules: No pre-written code, must start fresh on event day\n๐ŸŽฏ Topic: Real-world scenario-based problem\n\nYou got this! ๐Ÿš€", + keywords: ["guidelines", "rules", "requirements", "regulations", "competition rules", "event rules", "what are rules", "competition guidelines"], + priority: 10 + }, + { + id: "event-date", + category: "event", + question: "When is CodeRush 2025?", + answer: "CodeRush 2025 is on November 15, 2025, from 8:00 AM to 6:00 PM. It's a 10-hour buildathon challenge.", + keywords: ["date", "when", "time", "schedule", "day", "november"], + priority: 10 + }, + { + id: "event-venue", + category: "event", + question: "Where is CodeRush 2025 held?", + answer: "CodeRush 2025 is held at the University of Moratuwa - Faculty of Information Technology. ๐Ÿ“ You can find the exact location here: https://maps.app.goo.gl/WsNriCAdxhJadHaK7", + keywords: ["where", "venue", "location", "place", "university", "moratuwa", "address", "map", "directions", "competition", "buildathon", "hackathon", "event"], + priority: 9 + }, + { + id: "venue-directions", + category: "event", + question: "How do I get to the venue?", + answer: "The event is at the Faculty of IT, University of Moratuwa. Here's the Google Maps link for directions: https://maps.app.goo.gl/WsNriCAdxhJadHaK7 ๐Ÿ“ Make sure to arrive by 8:00 AM on November 15, 2025!", + keywords: ["directions", "how to get", "map", "location", "navigate", "find venue", "competition", "buildathon", "event"], + priority: 8 + }, + { + id: "venue-map-link", + category: "event", + question: "Can you send me the map link?", + answer: "Sure! Here's the Google Maps link to the venue (Faculty of IT, University of Moratuwa): https://maps.app.goo.gl/WsNriCAdxhJadHaK7 ๐Ÿ“ Save it and see you there on November 15, 2025!", + keywords: ["map link", "google maps", "send map", "share location", "gps", "coordinates", "competition", "buildathon", "event"], + priority: 9 + }, + { + id: "event-duration", + category: "event", + question: "How long is the buildathon?", + answer: "The buildathon is a 10-hour challenge, running from 8:00 AM to 6:00 PM on November 15, 2025.", + keywords: ["duration", "how long", "hours", "time limit"], + priority: 8 + }, + { + id: "event-awards", + category: "event", + question: "When is the awards ceremony?", + answer: "The awards ceremony will be held on November 25, 2025, at 8:00 AM. Winners will be announced and prizes distributed.", + keywords: ["awards", "ceremony", "prizes", "winners", "announcement"], + priority: 8 + }, + + // Team Requirements + { + id: "team-size", + category: "team", + question: "How many members per team?", + answer: "Each team must have exactly 4 members. All members must be from the same batch (either Batch 23 or Batch 24).", + keywords: ["team size", "members", "how many", "people", "participants", "4", "how many people", "four members", "group size", "4 people"], + priority: 10 + }, + { + id: "team-batch", + category: "team", + question: "What batches can participate?", + answer: "Teams can be from Batch 23 or Batch 24. All 4 members of a team must be from the SAME batch. You cannot mix batches.", + keywords: ["batch", "23", "24", "year", "which batch"], + priority: 10 + }, + { + id: "team-eligibility", + category: "team", + question: "Who can participate?", + answer: "All students from the University of Moratuwa - Faculty of Information Technology from Batch 23 or Batch 24 can participate. Teams must have 4 members from the same batch.", + keywords: ["eligibility", "who", "can participate", "students", "requirements"], + priority: 9 + }, + { + id: "team-limit", + category: "team", + question: "How many teams can register?", + answer: "Maximum 100 teams can register for CodeRush 2025. Registration is first-come, first-served, so register early!", + keywords: ["limit", "maximum", "how many teams", "capacity", "100"], + priority: 8 + }, + + // Registration Rules + { + id: "team-name-rules", + category: "registration", + question: "What are the team name rules?", + answer: "Choose a cool team name! ๐Ÿ˜Ž It should be 3-10 characters and can include letters, numbers, spaces, hyphens (-), or underscores (_). Make it unique and creative! Just avoid using only numbers. Examples: Team42, Code_Ninjas, Rush-2025", + keywords: ["team name", "name rules", "characters", "format", "naming"], + priority: 10 + }, + { + id: "index-format", + category: "registration", + question: "What is the index number format?", + answer: "Your index number should look like this: 234001T (6 digits + 1 capital letter). ๐Ÿ“ Make sure it starts with your batch (23 or 24)! For example, Batch 23 students use 23****X and Batch 24 use 24****X. Each teammate needs their own unique index number!", + keywords: ["index", "format", "number", "student id", "234001T", "example"], + priority: 10 + }, + { + id: "email-rules", + category: "registration", + question: "Can team members use the same email?", + answer: "Nope! Each team member needs their own email address. ๐Ÿ“ง Make sure everyone uses a different email - this helps us send confirmation messages to everyone on your team! You can use personal emails or university emails.", + keywords: ["email", "unique", "same email", "duplicate"], + priority: 9 + }, + { + id: "duplicate-members", + category: "registration", + question: "Can a person be in multiple teams?", + answer: "No. Each index number and email can only be registered once. A person cannot be part of multiple teams.", + keywords: ["duplicate", "multiple teams", "same person", "join"], + priority: 9 + }, + + // Registration Process + { + id: "registration-steps", + category: "registration", + question: "How do I register my team?", + answer: "You can register right here in this chat! ๐Ÿš€ Super easy - I'll guide you through it:\n\n1๏ธโƒฃ Share your team name (3-10 characters)\n2๏ธโƒฃ Select your batch (23 or 24)\n3๏ธโƒฃ Add all 4 teammates (name, index, email for each)\n4๏ธโƒฃ Review and confirm!\n\nReady to start? Just type your team name now! ๐Ÿ˜Š", + keywords: ["how to register", "process", "steps", "procedure", "how do i register", "register team", "start registration", "registration process"], + priority: 10 + }, + { + id: "edit-registration", + category: "registration", + question: "Can I edit my registration after submitting?", + answer: "Yes! You can edit your registration details after submitting. โœ… Just don't click the Reset button, or you'll lose access to edit! If you need help, contact the organizers:\n๐Ÿ“ง Ishan Hansaka: silvahih.22@uom.lk, 0775437008\n๐Ÿ“ง Kenuka Karunakaran: karunakarank.22@uom.lk, 0767508136\n๐Ÿ“ง Nimesh Madhusanka: madhusankanan.22@uom.lk, 0788722847", + keywords: ["edit", "change", "update", "modify", "after registration", "edit details", "change details"], + priority: 9 + }, + { + id: "confirmation-email", + category: "registration", + question: "Will I receive a confirmation email?", + answer: "Yes! ๐Ÿ“ฌ Once you submit your registration, the team leader will get a confirmation email with all your team details and next steps for CodeRush 2025. Keep an eye on your inbox! If you don't see it, check your spam folder too.", + keywords: ["email", "confirmation", "receive", "sent"], + priority: 8 + }, + { + id: "registration-deadline", + category: "registration", + question: "When does registration close?", + answer: "Registration closes on November 14, 2025 at 11:59 PM or when we reach 100 teams (whichever comes first!). โฐ It's first-come, first-served, so don't wait! Register your team ASAP to secure your spot!", + keywords: ["deadline", "close", "last date", "when", "november 14", "last day", "cut off", "expires", "until when", "registration ends"], + priority: 8 + }, + { + id: "reset-button-warning", + category: "registration", + question: "What happens if I click the Reset button?", + answer: "The Reset button starts a completely new registration session! ๐Ÿ”„\n\nโœ… Use Reset if: You want to register a DIFFERENT team or start completely over\nโŒ Don't use Reset if: You've completed registration and want to edit your current team - you'll LOSE access to edit!\n\nโš ๏ธ After Reset: Your submitted registration stays in the database, but you can't access it through this chat anymore. To edit an existing registration, use the Edit button instead of Reset!", + keywords: ["reset", "reset button", "fresh start", "start over", "warning", "after registration", "lose access"], + priority: 10 + }, + { + id: "registration-page-refresh", + category: "registration", + question: "What happens if the page refreshes during registration?", + answer: "Don't worry! ๐Ÿ˜Š If the page refreshes while you're registering in the chat (before final submission), your progress is saved. You can continue from where you left off and still update any details before submitting!", + keywords: ["page refresh", "during registration", "reload", "lost data", "before submission"], + priority: 9 + }, + { + id: "edit-during-registration", + category: "registration", + question: "Can I go back to edit details during registration?", + answer: "While you're registering in the chat (halfway through), you CANNOT go back to edit details you already entered. โš ๏ธ You must continue forward. BUT once you complete all the information (before final confirmation), you CAN review and update ALL details! After submission, you can still edit (just don't click Reset!).", + keywords: ["edit during", "go back", "halfway", "during registration", "cannot change", "must continue"], + priority: 9 + }, + { + id: "edit-before-submission", + category: "registration", + question: "Can I update my details after filling all information but before submitting?", + answer: "Yes! ๐Ÿ˜Š Once you've completed filling in all the information (all 4 members' details), you can review and update ANY details before final confirmation. Make sure everything is perfect! After you submit, you can still edit (just don't click Reset!).", + keywords: ["edit before submit", "review before submit", "can change", "update before submit", "all info filled"], + priority: 9 + }, + + // Submission Requirements + { + id: "submission-format", + category: "submission", + question: "What do we need to submit?", + answer: "You need to submit 2 separate things:\n\n1๏ธโƒฃ GitHub Repository Link (public repo with your code)\n2๏ธโƒฃ Google Drive Folder Link containing:\n ๐Ÿ“น Demo video: [YourTeamName]_demo.mp4\n ๐Ÿ“„ Report: [YourTeamName]_report.pdf\n\nExample: If your team is \"Team42\", files should be named:\nโ€ข Team42_demo.mp4\nโ€ข Team42_report.pdf\n\nMake sure the Drive folder is set to 'Anyone with the link can view'!", + keywords: ["submit", "submission", "deliverables", "what to submit", "requirements", "format"], + priority: 10 + }, + { + id: "submission-deadline", + category: "submission", + question: "When is the submission deadline?", + answer: "All submissions must be completed by 6:00 PM on November 15, 2025 (same day as the buildathon). You have 10 hours from 8 AM to 6 PM to build and submit.", + keywords: ["deadline", "when to submit", "due date", "time"], + priority: 10 + }, + { + id: "tech-stack", + category: "submission", + question: "What tech stack can we use?", + answer: "You can use ANY technology, framework, or programming language! Whether it's HTML, CSS, JavaScript, React, Next.js, Vue, Angular, Node.js, Python, Django, Flask, PHP, Java, or anything else - there are NO restrictions. Pick what you're comfortable with and build something awesome!", + keywords: ["tech stack", "technology", "framework", "language", "tools", "programming", "react", "nextjs", "vue", "angular", "html", "css", "javascript", "python", "django", "node"], + priority: 9 + }, + { + id: "project-theme", + category: "submission", + question: "Is there a specific theme or problem statement?", + answer: "YES! On the event day, you'll receive scenario-based questions or real-world problem statements. The specific scenarios will be announced at the start of the buildathon on November 15, 2025. You'll then build solutions to solve these scenarios using any technology you want!", + keywords: ["theme", "problem", "topic", "challenge", "what to build", "scenario", "question", "problem statement"], + priority: 8 + }, + { + id: "scenario-questions", + category: "event", + question: "What are scenario-based questions?", + answer: "On the event day, teams will receive real-world scenario-based problems or challenges that they need to solve. These scenarios will be announced at 8 AM on November 15th. You'll have 10 hours to build a solution using any technology or framework you choose!", + keywords: ["scenario", "questions", "problems", "challenges", "what to build", "event day"], + priority: 9 + }, + { + id: "scenario-examples", + category: "event", + question: "Can you give me an example of a scenario-based question?", + answer: "Sure! Here are some examples of what scenario-based questions might look like: 1) Build a student attendance tracking system for universities, 2) Create a food delivery app for local restaurants, 3) Develop a healthcare appointment booking platform, 4) Build a real-time pollution monitoring dashboard. The actual scenarios will be announced on event day, but they'll be similar real-world problems that need creative tech solutions!", + keywords: ["example", "sample", "scenario", "what kind", "like what", "problem statement example"], + priority: 9 + }, + { + id: "frameworks-allowed", + category: "technical", + question: "Can we use React, Next.js, or other frameworks?", + answer: "YES! You can use ANY framework you want - React, Next.js, Vue, Angular, Django, Flask, Express, or any other framework. There are absolutely NO restrictions on what technologies, frameworks, or libraries you use!", + keywords: ["react", "nextjs", "next.js", "vue", "angular", "framework", "library", "use", "allowed"], + priority: 10 + }, + { + id: "any-technology", + category: "technical", + question: "Are there any restrictions on technologies?", + answer: "NO restrictions at all! Use HTML, CSS, JavaScript, TypeScript, Python, Java, PHP, Ruby, or ANY programming language. Use any framework like React, Next.js, Vue, Angular, Django, Laravel - whatever you're comfortable with! You have complete freedom to choose your tech stack.", + keywords: ["restrictions", "allowed", "can we use", "technology", "language", "framework", "html", "css", "js", "python"], + priority: 10 + }, + { + id: "external-apis", + category: "technical", + question: "Can we use external APIs and libraries?", + answer: "Absolutely! You're free to use any external APIs, third-party libraries, npm packages, pip packages, or any tools you need. There are no limitations - use whatever helps you build the best solution!", + keywords: ["api", "external", "library", "package", "npm", "third party", "tools"], + priority: 9 + }, + { + id: "ai-tools-allowed", + category: "technical", + question: "Can we use AI tools like ChatGPT, GitHub Copilot, or Claude?", + answer: "YES! You can absolutely use AI tools during CodeRush 2025! Feel free to use ChatGPT, GitHub Copilot, Claude, Gemini, or any other AI assistant to help you code, debug, brainstorm, or solve problems. There are NO restrictions - use whatever tools help you build the best solution! ๐Ÿค–โœจ", + keywords: ["ai", "ai tools", "chatgpt", "copilot", "github copilot", "claude", "gemini", "artificial intelligence", "machine learning", "gpt"], + priority: 10 + }, + + // Organizers & Contact + { + id: "organizers", + category: "contact", + question: "Who are the organizers of CodeRush 2025?", + answer: "CodeRush 2025 is organized by three students from the Faculty of IT, University of Moratuwa: 1) Ishan Hansaka (silvahih.22@uom.lk, 0775437008), 2) Kenuka Karunakaran (karunakarank.22@uom.lk, 0767508136), and 3) Nimesh Madhusanka (madhusankanan.22@uom.lk, 0788722847). Feel free to reach out to them if you have any questions!", + keywords: ["organizers", "who organized", "contact", "team", "organiser"], + priority: 8 + }, + { + id: "contact-organizers", + category: "contact", + question: "How can I contact the organizers?", + answer: "You can contact the organizers via email or phone: Ishan Hansaka (silvahih.22@uom.lk, 0775437008), Kenuka Karunakaran (karunakarank.22@uom.lk, 0767508136), or Nimesh Madhusanka (madhusankanan.22@uom.lk, 0788722847). Feel free to reach out anytime!", + keywords: ["contact", "email", "phone", "reach out", "get in touch", "call"], + priority: 8 + }, + + // Event Logistics + { + id: "food-refreshments", + category: "event", + question: "Is food provided at the event?", + answer: "Yes! We'll provide refreshments during CodeRush 2025 to keep you energized throughout the day! ๐Ÿ•โ˜• You can focus on coding while we take care of keeping you fueled!", + keywords: ["food", "refreshments", "snacks", "drinks", "meals", "lunch", "breakfast", "provided", "eating", "hungry", "catering", "diet"], + priority: 8 + }, + { + id: "pre-event-coding", + category: "rules", + question: "Can we start coding before the event?", + answer: "No, you cannot start coding before the event! โฐ The problem statements will be revealed at 8 AM on November 15th, and that's when coding begins. Everyone starts at the same time for fairness!", + keywords: ["pre-event", "before event", "start early", "code before", "preparation"], + priority: 9 + }, + { + id: "registration-mistake", + category: "registration", + question: "I made a mistake in my registration, what should I do?", + answer: "No worries! You can edit your registration to fix any mistakes (team name, batch, member details, etc.). Just don't click the Reset button! If you need help, contact the organizers:\n๐Ÿ“ง Ishan: silvahih.22@uom.lk, 0775437008\n๐Ÿ“ง Kenuka: karunakarank.22@uom.lk, 0767508136\n๐Ÿ“ง Nimesh: madhusankanan.22@uom.lk, 0788722847", + keywords: ["mistake", "error", "wrong info", "fix registration", "incorrect", "typo"], + priority: 9 + }, + { + id: "need-help-support", + category: "support", + question: "I need help, who can I contact?", + answer: "We're here to help! ๐Ÿ˜Š You can contact the organizers directly: Ishan Hansaka (silvahih.22@uom.lk, 0775437008), Kenuka Karunakaran (karunakarank.22@uom.lk, 0767508136), or Nimesh Madhusanka (madhusankanan.22@uom.lk, 0788722847). Feel free to reach out anytime!", + keywords: ["need help", "help me", "support", "assistance", "stuck", "problem"], + priority: 9 + }, + + // Common Questions + { + id: "prizes", + category: "event", + question: "What are the prizes?", + answer: "Prizes will be awarded to winning teams at the awards ceremony on November 25, 2025. Specific prize details will be announced during the event.", + keywords: ["prize", "reward", "win", "award", "money"], + priority: 7 + }, + { + id: "experience-level", + category: "team", + question: "Do we need prior experience?", + answer: "No specific experience is required! CodeRush welcomes all skill levels. It's a great opportunity to learn, collaborate, and build something amazing with your team.", + keywords: ["experience", "beginner", "skill", "level", "qualified"], + priority: 7 + }, + { + id: "team-formation", + category: "team", + question: "Can we form teams at the event?", + answer: "No, you must register your complete team of 4 members before the event. Teams cannot be formed or changed on the event day.", + keywords: ["form team", "find team", "team formation", "partners"], + priority: 8 + }, + { + id: "offline-online", + category: "event", + question: "Is CodeRush online or offline?", + answer: "CodeRush 2025 is an in-person event held at the University of Moratuwa - Faculty of IT. All teams must attend physically.", + keywords: ["online", "offline", "physical", "virtual", "in-person", "remote", "zoom", "location", "attendance", "attend"], + priority: 8 + }, + { + id: "contact-support", + category: "support", + question: "How do I get help or report issues?", + answer: "If you face any issues during registration or have questions, just ask me here! I'm here to help you through the entire process. You can also contact the organizers directly if needed!", + keywords: ["help", "support", "issue", "problem", "contact"], + priority: 7 + }, + { + id: "reset-registration", + category: "registration", + question: "How do I start a new registration?", + answer: "Click the ๐Ÿ”„ Reset button at the top of the chat to clear your current session and start a new registration for a different team.", + keywords: ["reset", "new registration", "start over", "clear"], + priority: 7 + }, + { + id: "view-teams", + category: "registration", + question: "Can I see registered teams?", + answer: "Yes! You can view all registered teams on the main registration page. Just look for the 'View Registered Teams' button to see all teams that have successfully registered.", + keywords: ["view teams", "see teams", "registered", "list"], + priority: 6 + }, + + // Submission Page + { + id: "submission-page", + category: "submission", + question: "How do I submit my project?", + answer: "Visit the Submission page and fill in 3 fields:\n\n1๏ธโƒฃ Team Name (must match your registration)\n2๏ธโƒฃ GitHub Repository Link (must be public)\n3๏ธโƒฃ Google Drive Folder Link (containing video + report)\n\nThe Drive folder should be named '[YourTeamName]' and contain '[YourTeamName]_demo.mp4' and '[YourTeamName]_report.pdf'. Set it to 'Anyone with the link can view'!", + keywords: ["how to submit", "submission page", "submit project", "where to submit", "submission process"], + priority: 10 + }, + { + id: "submission-github", + category: "submission", + question: "Does GitHub repo need to be public?", + answer: "Yes! Your GitHub repository must be set to public visibility so judges can access and review your code.", + keywords: ["github", "public", "repository", "visibility", "private"], + priority: 9 + }, + { + id: "submission-drive-folder", + category: "submission", + question: "What should be in the Google Drive folder?", + answer: "Your Google Drive folder must contain: 1) Demo video showing your solution, and 2) Project report document. The folder must be named with your team name and set to public view (Anyone with the link can view).", + keywords: ["drive folder", "contents", "video", "report", "document"], + priority: 9 + }, + { + id: "submission-video", + category: "submission", + question: "What should the demo video include?", + answer: "The demo video should showcase your solution, demonstrate key features, and explain how it solves the problem. Keep it clear and concise.", + keywords: ["demo video", "video content", "showcase", "demonstration"], + priority: 7 + }, + { + id: "submission-report", + category: "submission", + question: "What should be in the project report?", + answer: "The project report should include: problem statement, your solution approach, technologies used, key features, challenges faced, and future improvements.", + keywords: ["report", "documentation", "document", "write"], + priority: 7 + }, + + // Report Issue Page + { + id: "report-issue", + category: "support", + question: "How do I report a problem or issue?", + answer: "You can report issues directly to the organizers! Contact them via: Ishan Hansaka (silvahih.22@uom.lk, 0775437008), Kenuka Karunakaran (karunakarank.22@uom.lk, 0767508136), or Nimesh Madhusanka (madhusankanan.22@uom.lk, 0788722847). They'll review and respond to your concern quickly!", + keywords: ["report issue", "problem", "help", "support", "contact"], + priority: 8 + }, + { + id: "issue-types", + category: "support", + question: "What types of issues can I report?", + answer: "You can report registration problems (unable to register, errors, duplicate entries), technical issues (website bugs, submission problems), or other concerns related to CodeRush 2025.", + keywords: ["issue types", "problems", "categories", "what to report"], + priority: 7 + }, + { + id: "issue-response-time", + category: "support", + question: "How long does it take to get help?", + answer: "The organizing team reviews reported issues regularly. For urgent matters during registration or the event, expect a response within a few hours. For general queries, typically within 24 hours.", + keywords: ["response time", "how long", "wait", "reply"], + priority: 6 + }, + + // Registration Page Guidelines + { + id: "registration-page-chat", + category: "registration", + question: "How does the registration chatbot work?", + answer: "I'm your registration assistant! I guide you step-by-step through the registration process right here in the chat. You can ask me questions anytime. I'll collect your team name, batch, and details for all 4 members, then let you review and confirm before submitting. Let's get started! ๐Ÿš€", + keywords: ["chatbot", "how it works", "assistant", "registration bot"], + priority: 8 + }, + { + id: "registration-pause", + category: "registration", + question: "Can I pause and resume registration later?", + answer: "Yes! Your registration progress is automatically saved. If you close the page or refresh, you can resume from where you left off using the same browser session.", + keywords: ["pause", "resume", "save", "continue later"], + priority: 7 + }, + { + id: "multiple-registrations", + category: "registration", + question: "Can I register multiple teams?", + answer: "No. Each person can only be part of one team. Index numbers and emails can only be registered once. However, you can use the Reset button to start a new registration if you made a mistake.", + keywords: ["multiple teams", "more than one", "two teams"], + priority: 7 + }, + + // New Important Documents + { + id: "judging-criteria", + category: "event", + question: "How will projects be judged? What are the judging criteria?", + answer: "Projects will be evaluated on several key factors: 1๏ธโƒฃ Innovation & Creativity - How unique is your solution? 2๏ธโƒฃ Technical Implementation - Code quality and tech choices 3๏ธโƒฃ Problem-Solving Approach - How well does it solve the scenario? 4๏ธโƒฃ User Experience - Is it user-friendly? 5๏ธโƒฃ Completeness & Polish - Is it finished and well-presented? 6๏ธโƒฃ Presentation Quality - Demo video and report. Focus on building a solid, complete solution! ๐Ÿ†", + keywords: ["judging", "criteria", "evaluate", "scoring", "how judged", "grading", "assessment", "evaluation", "judges", "rating"], + priority: 9 + }, + { + id: "equipment-requirements", + category: "event", + question: "What should I bring to the event? What equipment do I need?", + answer: "Bring your laptop and charger - that's the most important! ๐Ÿ’ป๐Ÿ”Œ Make sure your laptop is fully charged and has all your development tools installed (IDEs, frameworks, etc.). WiFi will be provided at the venue, so you'll have internet access throughout the day!", + keywords: ["bring", "laptop", "equipment", "what to bring", "requirements", "charger", "devices", "need", "wifi", "internet"], + priority: 9 + }, + { + id: "wifi-internet", + category: "event", + question: "Is WiFi or internet provided at the venue?", + answer: "Yes! WiFi will be available at the venue throughout the event. You'll have internet access to download packages, search documentation, use APIs, and access online resources during the buildathon! ๐Ÿ“ถ", + keywords: ["wifi", "internet", "connection", "network", "online", "access"], + priority: 8 + }, + { + id: "github-repo-requirements", + category: "submission", + question: "What should be in the GitHub repository?", + answer: "Your GitHub repo should contain: 1) All your source code 2) README.md with setup instructions 3) Project dependencies (package.json, requirements.txt, etc.) 4) Any configuration files needed to run the project. Make sure the repo is PUBLIC and well-organized! The judges will review your code quality and implementation. ๐Ÿ’ป", + keywords: ["github", "repository", "repo", "code", "what to include", "readme", "source code"], + priority: 9 + } +]; + +/** + * Get documents by category + */ +export function getDocumentsByCategory(category: string): KnowledgeDocument[] { + return knowledgeBase.filter(doc => doc.category === category); +} + +/** + * Get all categories + */ +export function getCategories(): string[] { + return [...new Set(knowledgeBase.map(doc => doc.category))]; +} + +/** + * Search documents by keyword (simple fallback) + */ +export function searchByKeyword(query: string, limit = 5): KnowledgeDocument[] { + const lowerQuery = query.toLowerCase(); + + // Common typos and alternate terms + const synonymMap: Record = { + 'explain': ['details', 'information', 'about', 'overview', 'tell'], + 'describe': ['details', 'information', 'about', 'overview', 'tell'], + 'tell': ['explain', 'describe', 'information', 'details', 'about'], + 'details': ['information', 'info', 'about', 'overview', 'explain'], + 'information': ['details', 'info', 'about', 'overview', 'explain'], + 'overview': ['details', 'information', 'about', 'explain'], + 'location': ['venue', 'place', 'address', 'where', 'map', 'directions'], + 'venue': ['location', 'place', 'where', 'map', 'directions'], + 'competition': ['event', 'buildathon', 'hackathon', 'coderush'], + 'compition': ['competition', 'event'], // Common typo + 'competion': ['competition', 'event'], // Common typo + 'compititon': ['competition', 'event'], // Common typo + 'event': ['competition', 'buildathon', 'hackathon', 'coderush'], + 'buildathon': ['event', 'competition', 'hackathon'], + 'hackathon': ['event', 'competition', 'buildathon'], + 'coderush': ['event', 'competition', 'buildathon'], + }; + + // Clean punctuation and split into words + let words = lowerQuery + .replace(/[^\w\s]/g, ' ') // Replace punctuation with spaces + .split(/\s+/) // Split by whitespace + .filter(word => word.length > 0); // Remove empty strings + + // Add synonyms for better matching + const expandedWords = [...words]; + words.forEach(word => { + if (synonymMap[word]) { + expandedWords.push(...synonymMap[word]); + } + }); + words = [...new Set(expandedWords)]; // Remove duplicates + + // If no valid words, return empty + if (words.length === 0) { + return []; + } + + const scored = knowledgeBase.map(doc => { + let score = 0; + + // Check keywords (exact and partial matches) + doc.keywords.forEach(keyword => { + const keywordLower = keyword.toLowerCase(); + words.forEach(word => { + if (word.length >= 3) { // Only match words 3+ chars to avoid noise + if (keywordLower === word) { + score += 5; // Exact keyword match - high score + } else if (keywordLower.includes(word) || word.includes(keywordLower)) { + score += 3; // Partial match + } + } + }); + }); + + // Multi-word phrase bonus (e.g., "event details", "about event") + const docText = `${doc.question} ${doc.answer} ${doc.keywords.join(' ')}`.toLowerCase(); + if (words.length >= 2) { + // Check if multiple query words appear together + let phraseMatches = 0; + for (let i = 0; i < words.length - 1; i++) { + const phrase = `${words[i]} ${words[i + 1]}`; + if (docText.includes(phrase)) { + phraseMatches++; + score += 8; // High bonus for phrase matches + } + } + // Extra bonus if the full query appears + if (lowerQuery.length > 10 && docText.includes(lowerQuery)) { + score += 15; + } + } + + // Check question + words.forEach(word => { + if (word.length >= 3 && doc.question.toLowerCase().includes(word)) { + score += 2; + } + }); + + // Check answer + words.forEach(word => { + if (word.length >= 3 && doc.answer.toLowerCase().includes(word)) { + score += 1; + } + }); + + // Add priority bonus + score += doc.priority * 0.1; + + return { doc, score }; + }); + + return scored + .filter(item => item.score > 0) + .sort((a, b) => b.score - a.score) + .slice(0, limit) + .map(item => item.doc); +} diff --git a/src/lib/stateMachine.ts b/src/lib/stateMachine.ts index 030881a..57989ce 100644 --- a/src/lib/stateMachine.ts +++ b/src/lib/stateMachine.ts @@ -25,13 +25,13 @@ export const states: Record = { }, CONFIRMATION: { - prompt: "Here is your summary. Confirm submission? (yes/no)", + prompt: "โœจ Does everything look correct?\n\nReview your details carefully! You can update them later if needed (just don't click Reset!).\n\nReady to submit? (yes/no)", validate: (input) => ["yes", "no"].includes(input.toLowerCase()), next: "DONE", }, DONE: { - prompt: "๐ŸŽ‰ Registration Successful! Your team has been registered for CodeRush 2025. Check your email for confirmation details.", + prompt: "๐ŸŽŠ Congratulations! Your team is officially registered for CodeRush 2025! ๐ŸŽ‰\n\nโœ… Confirmation email sent to your team leader\n๐Ÿ“… Event Date: November 15, 2025\nโฐ Time: 8 AM - 6 PM\n\nSee you there! Get ready to build something amazing! ๐Ÿš€", }, }; diff --git a/src/lib/vectorService.ts b/src/lib/vectorService.ts new file mode 100644 index 0000000..3c904c6 --- /dev/null +++ b/src/lib/vectorService.ts @@ -0,0 +1,202 @@ +/** + * Vector Service using Pinecone and Gemini Embeddings + * Handles vector storage and semantic search for RAG + */ + +import { Pinecone } from '@pinecone-database/pinecone'; +import { GoogleGenerativeAI } from '@google/generative-ai'; +import { knowledgeBase, type KnowledgeDocument } from './knowledgeBase'; + +// Initialize clients lazily +let pinecone: Pinecone | null = null; +let genAI: GoogleGenerativeAI | null = null; + +function getPinecone(): Pinecone { + if (!pinecone) { + pinecone = new Pinecone({ + apiKey: process.env.PINECONE_API_KEY || '', + }); + } + return pinecone; +} + +function getGenAI(): GoogleGenerativeAI { + if (!genAI) { + genAI = new GoogleGenerativeAI(process.env.GOOGLE_GEMINI_API_KEY || ''); + } + return genAI; +} + +const INDEX_NAME = 'coderush-knowledge'; +const EMBEDDING_MODEL = 'text-embedding-004'; // Gemini embedding model +const DIMENSION = 768; // Gemini embedding dimensions + +/** + * Initialize Pinecone index (call once during setup) + */ +export async function initializePineconeIndex() { + try { + const pc = getPinecone(); + + // Check if index exists + const indexList = await pc.listIndexes(); + const indexExists = indexList.indexes?.some(idx => idx.name === INDEX_NAME); + + if (!indexExists) { + console.log('Creating Pinecone index...'); + await pc.createIndex({ + name: INDEX_NAME, + dimension: DIMENSION, + metric: 'cosine', + spec: { + serverless: { + cloud: 'aws', + region: 'us-east-1' + } + } + }); + console.log('โœ… Pinecone index created successfully'); + + // Wait for index to be ready + await new Promise(resolve => setTimeout(resolve, 10000)); + } else { + console.log('โœ… Pinecone index already exists'); + } + + return true; + } catch (error) { + console.error('โŒ Error initializing Pinecone:', error); + return false; + } +} + +/** + * Generate embedding using Gemini + */ +export async function generateEmbedding(text: string): Promise { + try { + const ai = getGenAI(); + const model = ai.getGenerativeModel({ model: EMBEDDING_MODEL }); + const result = await model.embedContent(text); + return result.embedding.values; + } catch (error) { + console.error('Error generating embedding:', error); + throw error; + } +} + +/** + * Populate Pinecone with knowledge base (run once) + */ +export async function populateVectorDatabase() { + try { + console.log('๐Ÿ“Š Starting vector database population...'); + + const pc = getPinecone(); + const index = pc.index(INDEX_NAME); + + // Process in batches to avoid rate limits + const batchSize = 10; + for (let i = 0; i < knowledgeBase.length; i += batchSize) { + const batch = knowledgeBase.slice(i, i + batchSize); + + const vectors = await Promise.all( + batch.map(async (doc) => { + // Combine question and answer for better semantic search + const text = `${doc.question} ${doc.answer}`; + const embedding = await generateEmbedding(text); + + return { + id: doc.id, + values: embedding, + metadata: { + question: doc.question, + answer: doc.answer, + category: doc.category, + keywords: doc.keywords.join(', '), + priority: doc.priority + } + }; + }) + ); + + await index.upsert(vectors); + console.log(`โœ… Upserted batch ${Math.floor(i / batchSize) + 1}`); + + // Small delay to avoid rate limits + await new Promise(resolve => setTimeout(resolve, 1000)); + } + + console.log('โœ… Vector database populated successfully'); + return true; + } catch (error) { + console.error('โŒ Error populating vector database:', error); + return false; + } +} + +/** + * Search vector database for relevant documents + */ +export async function searchVectorDatabase( + query: string, + topK = 3 +): Promise { + try { + // Generate query embedding + const queryEmbedding = await generateEmbedding(query); + + // Search Pinecone + const pc = getPinecone(); + const index = pc.index(INDEX_NAME); + const searchResults = await index.query({ + vector: queryEmbedding, + topK, + includeMetadata: true + }); + + // Convert results back to KnowledgeDocument format + const documents: KnowledgeDocument[] = searchResults.matches + .filter(match => match.score && match.score > 0.5) // Similarity threshold + .map(match => ({ + id: match.id, + question: (match.metadata?.question as string) || '', + answer: (match.metadata?.answer as string) || '', + category: (match.metadata?.category as string) || '', + keywords: ((match.metadata?.keywords as string) || '').split(', '), + priority: (match.metadata?.priority as number) || 5 + })); + + return documents; + } catch (error) { + console.error('โŒ Error searching vector database:', error); + // Fallback to keyword search + const { searchByKeyword } = await import('./knowledgeBase'); + return searchByKeyword(query, topK); + } +} + +/** + * Check if Pinecone is available and configured + */ +export async function isPineconeAvailable(): Promise { + try { + if (!process.env.PINECONE_API_KEY) { + console.log('โš ๏ธ Pinecone API key not configured'); + return false; + } + + const pc = getPinecone(); + const indexList = await pc.listIndexes(); + const available = indexList.indexes?.some(idx => idx.name === INDEX_NAME) || false; + + if (!available) { + console.log('โš ๏ธ Pinecone index not found'); + } + + return available; + } catch (error) { + console.error('โš ๏ธ Pinecone connection error:', error); + return false; + } +} From 463ed803aad2135ee8ac2bb25c0d8469d9e657b4 Mon Sep 17 00:00:00 2001 From: AdithaBuwaneka Date: Tue, 4 Nov 2025 17:36:09 +0530 Subject: [PATCH 2/8] fix: improve error handling in API rate limit check --- scripts/check-api-rate-limit.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/scripts/check-api-rate-limit.ts b/scripts/check-api-rate-limit.ts index 94675eb..ea902e8 100644 --- a/scripts/check-api-rate-limit.ts +++ b/scripts/check-api-rate-limit.ts @@ -56,25 +56,27 @@ async function checkRateLimit() { console.log(` Latency: ${duration}ms`); console.log(''); - } catch (error: any) { - if (error.status === 429) { + } catch (error) { + const err = error as { status?: number; statusText?: string; message?: string; errorDetails?: Array<{ '@type': string; violations?: Array<{ quotaMetric: string; quotaValue: string }>; retryDelay?: string }> }; + + if (err.status === 429) { console.log(` โŒ RATE LIMIT EXCEEDED`); - console.log(` Status: ${error.status} ${error.statusText}`); + console.log(` Status: ${err.status} ${err.statusText}`); // Extract rate limit info from error - if (error.errorDetails) { - const quotaFailure = error.errorDetails.find((d: any) => + if (err.errorDetails) { + const quotaFailure = err.errorDetails.find((d) => d['@type'] === 'type.googleapis.com/google.rpc.QuotaFailure' ); if (quotaFailure?.violations) { - quotaFailure.violations.forEach((v: any) => { + quotaFailure.violations.forEach((v) => { console.log(` Quota Metric: ${v.quotaMetric}`); console.log(` Quota Value: ${v.quotaValue} requests per minute`); }); } - const retryInfo = error.errorDetails.find((d: any) => + const retryInfo = err.errorDetails.find((d) => d['@type'] === 'type.googleapis.com/google.rpc.RetryInfo' ); @@ -83,11 +85,11 @@ async function checkRateLimit() { } } console.log(''); - } else if (error.status === 404) { + } else if (err.status === 404) { console.log(` โš ๏ธ MODEL NOT AVAILABLE (may require different tier)`); console.log(''); } else { - console.log(` โŒ ERROR: ${error.message}`); + console.log(` โŒ ERROR: ${err.message}`); console.log(''); } } From 69dba8a899a2e19eaca206d77cdc6f4949210520 Mon Sep 17 00:00:00 2001 From: AdithaBuwaneka Date: Tue, 4 Nov 2025 17:38:20 +0530 Subject: [PATCH 3/8] fix: enhance error handling in test scripts for better clarity --- scripts/final-comprehensive-test.ts | 5 +++-- scripts/test-api-key.ts | 9 +++++---- scripts/test-fixes.ts | 5 +++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/scripts/final-comprehensive-test.ts b/scripts/final-comprehensive-test.ts index 5f245e4..93eab38 100644 --- a/scripts/final-comprehensive-test.ts +++ b/scripts/final-comprehensive-test.ts @@ -163,8 +163,9 @@ async function runFinalTest() { // Small delay await new Promise(resolve => setTimeout(resolve, 1500)); - } catch (error: any) { - console.log('โŒ ERROR:', error.message); + } catch (error) { + const err = error as { message?: string }; + console.log('โŒ ERROR:', err.message); errors++; } } diff --git a/scripts/test-api-key.ts b/scripts/test-api-key.ts index 2405f45..75d13be 100644 --- a/scripts/test-api-key.ts +++ b/scripts/test-api-key.ts @@ -30,10 +30,11 @@ async function testAPIKey() { console.log('โœ… SUCCESS! Gemini 2.0 Flash is working!'); console.log('๐Ÿ“จ Response:', response); - } catch (error: any) { - console.error('โŒ ERROR:', error.message); - if (error.status) { - console.error('Status:', error.status); + } catch (error) { + const err = error as { message?: string; status?: number }; + console.error('โŒ ERROR:', err.message); + if (err.status) { + console.error('Status:', err.status); } } } diff --git a/scripts/test-fixes.ts b/scripts/test-fixes.ts index e0a4946..db9b505 100644 --- a/scripts/test-fixes.ts +++ b/scripts/test-fixes.ts @@ -57,8 +57,9 @@ async function testFixes() { // Delay to avoid rate limits await new Promise(resolve => setTimeout(resolve, 2000)); - } catch (error: any) { - console.log('โŒ ERROR:', error.message); + } catch (error) { + const err = error as { message?: string }; + console.log('โŒ ERROR:', err.message); failed++; } } From 4c1fa8996e3eb08e505cb59c474523c0e8d8a979 Mon Sep 17 00:00:00 2001 From: AdithaBuwaneka Date: Tue, 4 Nov 2025 17:38:42 +0530 Subject: [PATCH 4/8] fix: improve error handling in test scripts for clearer error messages --- scripts/test-real-world.ts | 5 +++-- scripts/test-registration-flow.ts | 5 +++-- scripts/test-registration-questions.ts | 5 +++-- scripts/test-remaining-issues.ts | 5 +++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/scripts/test-real-world.ts b/scripts/test-real-world.ts index 6048404..4b2e4d4 100644 --- a/scripts/test-real-world.ts +++ b/scripts/test-real-world.ts @@ -123,8 +123,9 @@ async function testRealWorld() { if (answer.length > 20 && !answer.includes('having trouble')) { validAnswered++; } - } catch (error: any) { - console.log(`โŒ Error: ${error.message}`); + } catch (error) { + const err = error as { message?: string }; + console.log(`โŒ Error: ${err.message}`); } } diff --git a/scripts/test-registration-flow.ts b/scripts/test-registration-flow.ts index cf029ec..67ee004 100644 --- a/scripts/test-registration-flow.ts +++ b/scripts/test-registration-flow.ts @@ -37,8 +37,9 @@ async function testFlow() { await new Promise(resolve => setTimeout(resolve, 2000)); - } catch (error: any) { - console.log('โŒ ERROR:', error.message); + } catch (error) { + const err = error as { message?: string }; + console.log('โŒ ERROR:', err.message); } } diff --git a/scripts/test-registration-questions.ts b/scripts/test-registration-questions.ts index b7665f0..704720a 100644 --- a/scripts/test-registration-questions.ts +++ b/scripts/test-registration-questions.ts @@ -88,8 +88,9 @@ async function testRegistrationQuestions() { // Delay to avoid rate limits await new Promise(resolve => setTimeout(resolve, 2000)); - } catch (error: any) { - console.log('โŒ ERROR:', error.message); + } catch (error) { + const err = error as { message?: string }; + console.log('โŒ ERROR:', err.message); failed++; } } diff --git a/scripts/test-remaining-issues.ts b/scripts/test-remaining-issues.ts index 776a72e..def11ae 100644 --- a/scripts/test-remaining-issues.ts +++ b/scripts/test-remaining-issues.ts @@ -44,8 +44,9 @@ async function testRemaining() { await new Promise(resolve => setTimeout(resolve, 2000)); - } catch (error: any) { - console.log('โŒ ERROR:', error.message); + } catch (error) { + const err = error as { message?: string }; + console.log('โŒ ERROR:', err.message); } } } From c154ffe7ed7f3d622d6f1210ace5c3b3393dc6bf Mon Sep 17 00:00:00 2001 From: AdithaBuwaneka Date: Tue, 4 Nov 2025 17:39:11 +0530 Subject: [PATCH 5/8] fix: improve error handling in test script for clearer error messages --- scripts/test-tech-questions.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/test-tech-questions.ts b/scripts/test-tech-questions.ts index 6df631e..f89322f 100644 --- a/scripts/test-tech-questions.ts +++ b/scripts/test-tech-questions.ts @@ -32,8 +32,9 @@ async function testTech() { try { const answer = await answerQuestionWithRAG(question, { state: 'IDLE' }); console.log(`โœ… A: ${answer}`); - } catch (error: any) { - console.log(`โŒ Error: ${error.message}`); + } catch (error) { + const err = error as { message?: string }; + console.log(`โŒ Error: ${err.message}`); } // Delay to avoid rate limits From 1fd665b0a370e00463d43a85c866fa65819e3bba Mon Sep 17 00:00:00 2001 From: AdithaBuwaneka Date: Tue, 4 Nov 2025 17:39:34 +0530 Subject: [PATCH 6/8] fix: enhance error handling for API rate limit and improve type safety in network tests --- scripts/check-api-rate-limit.ts | 5 +++-- scripts/test-network.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/check-api-rate-limit.ts b/scripts/check-api-rate-limit.ts index ea902e8..ed6bf7d 100644 --- a/scripts/check-api-rate-limit.ts +++ b/scripts/check-api-rate-limit.ts @@ -127,8 +127,9 @@ async function checkRateLimit() { console.log(` ${i}. โœ… Success - ${result.response.text().trim()}`); successCount++; await new Promise(resolve => setTimeout(resolve, 500)); - } catch (error: any) { - if (error.status === 429) { + } catch (error) { + const err = error as { status?: number }; + if (err.status === 429) { console.log(` ${i}. โŒ Rate Limited - Hit quota at request #${i}`); failCount++; break; diff --git a/scripts/test-network.ts b/scripts/test-network.ts index 7ffa856..77aa762 100644 --- a/scripts/test-network.ts +++ b/scripts/test-network.ts @@ -52,7 +52,7 @@ async function testConnections() { try { await fetch('https://www.google.com', { method: 'HEAD', - // @ts-ignore + // @ts-expect-error - Node-specific agent option not in standard fetch types agent: new https.Agent({ rejectUnauthorized: false }) }); console.log(' โœ… Internet: Connected\n'); From 40e1eeecc52d3b77b1a5f20c794f89441e933c22 Mon Sep 17 00:00:00 2001 From: AdithaBuwaneka Date: Tue, 4 Nov 2025 17:39:54 +0530 Subject: [PATCH 7/8] fix: remove unnecessary variable assignment in Gemini API connection test --- scripts/test-network.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test-network.ts b/scripts/test-network.ts index 77aa762..7d3dfed 100644 --- a/scripts/test-network.ts +++ b/scripts/test-network.ts @@ -24,7 +24,7 @@ async function testConnections() { const { GoogleGenerativeAI } = await import('@google/generative-ai'); const genAI = new GoogleGenerativeAI(process.env.GOOGLE_GEMINI_API_KEY || ''); const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash-exp' }); - const result = await model.generateContent('Hello'); + await model.generateContent('Hello'); console.log(' โœ… Gemini API: Connected\n'); } catch (error) { console.log(' โŒ Gemini API: Failed'); From d58b47ee14cc83c1349e1acda7bcd099f3977d8c Mon Sep 17 00:00:00 2001 From: AdithaBuwaneka Date: Tue, 4 Nov 2025 17:40:19 +0530 Subject: [PATCH 8/8] fix: remove unused import in live RAG system test script --- scripts/test-rag-comprehensive.ts | 2 +- scripts/test-rag-live.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/test-rag-comprehensive.ts b/scripts/test-rag-comprehensive.ts index 207d5bb..4638443 100644 --- a/scripts/test-rag-comprehensive.ts +++ b/scripts/test-rag-comprehensive.ts @@ -286,7 +286,7 @@ async function runTests() { const categories = [...new Set(testCases.map(t => t.category))]; categories.forEach(cat => { const catTests = testCases.filter(t => t.category === cat); - const catPassed = catTests.filter((t, i) => { + const catPassed = catTests.filter((t) => { const globalIndex = testCases.indexOf(t); return globalIndex < passed + failed && !failedTests.some(f => f.question === t.question); }).length; diff --git a/scripts/test-rag-live.ts b/scripts/test-rag-live.ts index e410638..6085ecd 100644 --- a/scripts/test-rag-live.ts +++ b/scripts/test-rag-live.ts @@ -6,7 +6,6 @@ // Load environment variables from .env.local import * as dotenv from 'dotenv'; import * as path from 'path'; -import { fileURLToPath } from 'url'; // Load .env.local dotenv.config({ path: path.resolve(process.cwd(), '.env.local') }); @@ -63,7 +62,7 @@ async function testRAGSystem() { console.log(''); for (let i = 0; i < testQuestions.length; i++) { - const { q, expectedCategory } = testQuestions[i]; + const { q } = testQuestions[i]; log(`\n${'โ”€'.repeat(70)}`, colors.blue); log(`Question ${i + 1}: "${q}"`, colors.blue + colors.bright);