Files
triqura-ecd/app/api/intent/classify/route.ts
colinislit 2170b23348 refactor: rename swift → cortex in code and documentation
Complete rename of "swift" terminology to "cortex" across the codebase:

Code Changes:
- Rename directories: lib/swift → lib/cortex, components/swift → components/cortex
- Rename API routes: /api/swift/* → /api/cortex/*
- Rename page routes: /epd/swift → /epd/cortex
- Rename store: swift-store.ts → cortex-store.ts

Type Renames:
- SwiftIntent → CortexIntent
- SwiftStore → CortexStore
- useSwiftStore → useCortexStore
- SwiftContext → CortexContext
- useSwiftVoice → useCortexVoice

Documentation Updates (docs/intent/):
- Safety Net → Nudge (Layer 3 rename)
- SafetySuggestion → NudgeSuggestion
- evaluateSafetyNet → evaluateNudge
- SWIFT_* feature flags → CORTEX_*
- All path references updated to match new structure

Files affected: 47 files, ~700 lines changed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 09:18:06 +01:00

173 lines
4.9 KiB
TypeScript

/**
* Intent Classify API
*
* POST /api/intent/classify
* Two-tier intent classification: local first, AI fallback if confidence < 0.8
*/
import { createClient } from '@/lib/auth/server';
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { classifyIntent, isHighConfidence } from '@/lib/cortex/intent-classifier';
import { classifyIntentWithAI } from '@/lib/cortex/intent-classifier-ai';
import { extractEntities } from '@/lib/cortex/entity-extractor';
import type { IntentClassificationResult } from '@/lib/cortex/types';
// Request schema
const ClassifyRequestSchema = z.object({
input: z.string().min(1, 'Input is verplicht').max(500, 'Input te lang (max 500 tekens)'),
forceAI: z.boolean().optional(), // For testing: force AI classification
});
// Response type
interface ClassifyResponse {
intent: IntentClassificationResult['intent'];
confidence: number;
entities: IntentClassificationResult['entities'];
source: 'local' | 'ai';
processingTimeMs: number;
localResult?: {
intent: string;
confidence: number;
matchedPattern?: string;
};
}
/**
* POST /api/intent/classify
*
* Classifies user input into an intent with entity extraction.
* Uses local regex patterns first, falls back to AI if confidence < 0.8.
*/
export async function POST(request: NextRequest) {
const startTime = performance.now();
try {
const body = await request.json();
// Validate input
const result = ClassifyRequestSchema.safeParse(body);
if (!result.success) {
return NextResponse.json(
{
error: 'Validatiefout',
details: result.error.issues.map((e) => ({
field: e.path.join('.'),
message: e.message,
})),
},
{ status: 400 }
);
}
const { input, forceAI } = result.data;
const supabase = await createClient();
// Check auth
const { data: authData } = await supabase.auth.getUser();
if (!authData?.user) {
return NextResponse.json({ error: 'Niet geautoriseerd' }, { status: 401 });
}
// Step 1: Local classification (fast, <50ms)
const localResult = classifyIntent(input);
// Step 2: Decide if we need AI fallback
const useAI = forceAI || !isHighConfidence(localResult);
let finalResult: ClassifyResponse;
if (useAI) {
// AI fallback for low confidence or forced
const aiResult = await classifyIntentWithAI(input);
finalResult = {
intent: aiResult.intent,
confidence: aiResult.confidence,
entities: aiResult.entities,
source: 'ai',
processingTimeMs: performance.now() - startTime,
localResult: {
intent: localResult.intent,
confidence: localResult.confidence,
matchedPattern: localResult.matchedPattern,
},
};
} else {
// High confidence local result - extract entities locally
const entities = extractEntities(input, localResult.intent);
finalResult = {
intent: localResult.intent,
confidence: localResult.confidence,
entities,
source: 'local',
processingTimeMs: performance.now() - startTime,
};
}
// Log classification event (non-blocking)
logClassificationEvent(supabase, input, finalResult).catch((err) => {
console.error('Failed to log classification event:', err);
});
return NextResponse.json(finalResult);
} catch (error) {
console.error('Error classifying intent:', error);
const errorMessage = error instanceof Error ? error.message : 'Onbekende fout';
if (errorMessage.includes('ANTHROPIC_API_KEY')) {
return NextResponse.json(
{ error: 'AI service niet geconfigureerd' },
{ status: 503 }
);
}
if (errorMessage.includes('Claude API')) {
return NextResponse.json(
{ error: 'AI service tijdelijk niet beschikbaar', details: errorMessage },
{ status: 503 }
);
}
return NextResponse.json(
{
error: 'Fout bij classificeren',
details: process.env.NODE_ENV === 'development' ? errorMessage : undefined,
},
{ status: 500 }
);
}
}
/**
* Log classification event to ai_events table
*/
async function logClassificationEvent(
supabase: Awaited<ReturnType<typeof createClient>>,
input: string,
result: ClassifyResponse
) {
try {
await supabase.from('ai_events').insert({
kind: 'intent_classify',
input_data: {
input: input.slice(0, 200), // Truncate for storage
inputLength: input.length,
},
output_data: {
intent: result.intent,
confidence: result.confidence,
source: result.source,
hasEntities: Object.keys(result.entities).length > 0,
localConfidence: result.localResult?.confidence,
},
duration_ms: Math.round(result.processingTimeMs),
});
} catch (error) {
// Don't throw, just log
console.error('Failed to log AI event:', error);
}
}