Files
brachnha-insight/src/lib/llm/intent-detector.ts
Max 3fbbb1a93b Initial commit: Brachnha Insight project setup
- Next.js 14+ with App Router and TypeScript
- Tailwind CSS and ShadCN UI styling
- Zustand state management
- Dexie.js for IndexedDB (local-first data)
- Auth.js v5 for authentication
- BMAD framework integration

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-26 12:28:43 +07:00

168 lines
4.6 KiB
TypeScript

/**
* 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';
}