/** * Intent Detector * * Classifies user messages as "venting" or "insight" based on keyword patterns. * Uses a combination of keyword-based heuristics for fast classification. * * Venting Indicators: * - Negative emotion words (frustrated, stuck, hate, broke) * - Problem-focused language (doesn't work, failing, error) * - Uncertainty or confusion (don't understand, why does) * - Time spent struggling (hours, days, all day) * * Insight Indicators: * - Positive realization words (get, understand, clicked, realized) * - Solution-focused language (figured out, solved, fixed) * - Teaching/explaining intent (so the trick is, here's what) * - Completion or success (finally, working, done) */ export type Intent = 'venting' | 'insight'; // Venting keyword patterns (more specific to avoid false positives) const VENTING_KEYWORDS = [ 'frustrated', 'stuck', 'hate', 'broke', 'broken', "don't understand", 'doesnt understand', 'confused', 'failing', 'error', "won't work", 'wont work', 'cant figure', "can't figure", 'struggling', 'difficult', 'hard', 'annoying', // Question words (only when at start or with ?) 'why', 'how do', 'help', ]; // Insight keyword patterns (more specific to avoid false positives) const INSIGHT_KEYWORDS = [ 'finally get', // More specific than just "get" 'get it', // Also add "get it" pattern 'get it now', // "I get it now" 'understand', 'clicked', 'realized', 'figured it out', // Common phrase: "I figured it out" 'figured out', // Alternative: "I figured out the bug" 'solved', 'fixed', 'fixed it', // "I fixed it" 'now working', // More specific than just "working" "it's working", // "It's working now" 'its working', 'done', 'finally', 'solution', 'found the solution', // "I found the solution" 'trick is', 'trick was', // "The trick was to..." "here's what", 'heres what', 'learned', 'key is', 'answer', 'accomplished', 'makes sense', // "This makes sense" 'makes sense now', ]; /** * Classifies the intent of a user message as "venting" or "insight". * @param input - The user's message text * @returns The classified intent ('venting' | 'insight') */ export function classifyIntent(input: string): Intent { if (!input || input.trim().length === 0) { return 'venting'; // Default to venting for empty input } const normalizedInput = input.toLowerCase().trim(); // Count insight indicators (positive patterns) FIRST let insightScore = 0; for (const keyword of INSIGHT_KEYWORDS) { if (normalizedInput.includes(keyword)) { insightScore += 1; } } // Count venting indicators (negative patterns) let ventingScore = 0; for (const keyword of VENTING_KEYWORDS) { if (normalizedInput.includes(keyword)) { ventingScore += 1; } } // Strong insight patterns (solution language, teaching) take ULTIMATE precedence // This check happens AFTER scoring but BEFORE time-struggling check const strongInsightPatterns = [ 'figured it out', // Common phrase: "I figured it out" 'figured out', // Alternative: "I figured out the bug" 'solution is', 'trick is', 'key is', "here's what", 'heres what', ]; const hasStrongInsightPattern = strongInsightPatterns.some(pattern => normalizedInput.includes(pattern) ); // Strong insight patterns override everything else // "figured out but it took forever" - still insight because they accomplished it if (hasStrongInsightPattern) { return 'insight'; } // Questions are typically venting (seeking help) const isQuestion = normalizedInput.includes('?') || normalizedInput.startsWith('why ') || normalizedInput.startsWith('how ') || normalizedInput.startsWith('what ') || normalizedInput.startsWith('when ') || normalizedInput.startsWith('where '); if (isQuestion && insightScore === 0) { return 'venting'; } // Special case: time spent struggling is venting UNLESS there's strong insight pattern const timeStrugglingPatterns = [ 'all day', 'for hours', ' hours ', 'days', 'forever', ]; const hasTimeStrugglingPattern = timeStrugglingPatterns.some(pattern => normalizedInput.includes(pattern) ); if (hasTimeStrugglingPattern && !hasStrongInsightPattern) { return 'venting'; } // Decision logic: insight if insight score is strictly higher than venting score // Default to venting for tie or when scores are equal (safer assumption) // Also default to venting when there are no clear indicators (both scores are 0) if (insightScore > ventingScore && insightScore > 0) { return 'insight'; } return 'venting'; }