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>
This commit is contained in:
167
src/lib/llm/intent-detector.ts
Normal file
167
src/lib/llm/intent-detector.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* 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';
|
||||
}
|
||||
Reference in New Issue
Block a user