feat(swift): voeg chat orchestration toe (E5)
Epic 5 compleet: Chat orchestration voor Swift Agenda Planning. Alle agenda intents worden nu correct gerouteerd naar AgendaBlock met user-friendly error handling en fallback opties. E5.S1 - Action Routing (2 SP) - routeIntentToArtifact() functie in action-parser - Maps agenda intents naar juiste AgendaBlock mode - Confidence threshold enforcement (< 0.7 → fallback) - Required entity validation (patient voor create, identifier voor reschedule) - Command-input gebruikt nieuwe routing ipv legacy openBlock - Migratie naar modern artifact systeem (openArtifact) E5.S2 - Chat Prompt Update (2 SP) - 4 agenda intents toegevoegd aan Swift chat system prompt - agenda_query: afspraken opvragen op datumrange - create_appointment: nieuwe afspraak maken (required: patient, datetime) - cancel_appointment: afspraak annuleren (disambiguation support) - reschedule_appointment: afspraak verzetten (required: identifier) - Entity extraction rules gedocumenteerd (dateRange, datetime, identifier) - 4 complete voorbeelden met JSON action format - Clarification questions voor incomplete data - Prompt size: ~325 → ~525 regels (+60%) E5.S3 - Error States (2 SP) - AgendaErrorState component voor full-page errors - AgendaErrorAlert component voor inline form errors - getUserFriendlyMessage() vertaalt technical → user-friendly Dutch - Auto-redirect bij auth errors (401 → /login) - Fallback link naar /epd/agenda in alle error states - Context-aware messaging (query/create/cancel/reschedule) - Retry functionaliteit voor recoverable errors - Dev-only technical details collapsible Error message mapping: - 401 → "Je sessie is verlopen. Log opnieuw in." + auto-redirect - 403 → "Je hebt geen toegang tot deze afspraak." - 404 → "De gevraagde afspraak kon niet worden gevonden." - 500 → "Er ging iets mis op de server. Probeer het opnieuw." - Network → "Geen internetverbinding. Controleer je netwerkverbinding." - Timeout → "De aanvraag duurde te lang. Probeer het opnieuw." Components updated: - command-input: gebruikt routeIntentToArtifact + openArtifact - agenda-create-form: gebruikt AgendaErrorAlert met fallback link - chat/route: uitgebreide system prompt met agenda sectie Nieuwe files: - lib/swift/action-parser.ts: routeIntentToArtifact() functie - components/swift/artifacts/blocks/agenda-error-state.tsx (225 regels) - docs/swift/implementation-e5-s1-action-routing.md - docs/swift/implementation-e5-s2-chat-prompt.md - docs/swift/implementation-e5-s3-error-states.md Documentatie: - Bouwplan bijgewerkt: Epic 5 → Done - 3 implementation docs met API specs en testing scenarios - Error handling best practices gedocumenteerd Progress: 48 SP / 51 SP (94%) - Epic 6 (QA) remaining 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,16 @@ import type { ChatAction, SwiftIntent, BlockType } from '@/stores/swift-store';
|
||||
// Validation schema for action objects
|
||||
const ActionSchema = z.object({
|
||||
type: z.literal('action'),
|
||||
intent: z.enum(['dagnotitie', 'zoeken', 'overdracht', 'unknown']),
|
||||
intent: z.enum([
|
||||
'dagnotitie',
|
||||
'zoeken',
|
||||
'overdracht',
|
||||
'agenda_query',
|
||||
'create_appointment',
|
||||
'cancel_appointment',
|
||||
'reschedule_appointment',
|
||||
'unknown',
|
||||
]),
|
||||
entities: z.object({
|
||||
patientName: z.string().optional(),
|
||||
patientId: z.string().optional(),
|
||||
@@ -27,7 +36,17 @@ const ActionSchema = z.object({
|
||||
confidence: z.number().min(0).max(1),
|
||||
artifact: z
|
||||
.object({
|
||||
type: z.enum(['dagnotitie', 'zoeken', 'overdracht', 'fallback', 'patient-dashboard']),
|
||||
type: z.enum([
|
||||
'dagnotitie',
|
||||
'zoeken',
|
||||
'overdracht',
|
||||
'agenda_query',
|
||||
'create_appointment',
|
||||
'cancel_appointment',
|
||||
'reschedule_appointment',
|
||||
'fallback',
|
||||
'patient-dashboard',
|
||||
]),
|
||||
prefill: z.record(z.string(), z.any()),
|
||||
})
|
||||
.optional(),
|
||||
@@ -148,5 +167,124 @@ export function validateArtifactType(intent: SwiftIntent, artifactType?: BlockTy
|
||||
// Intent should match artifact type (except for 'unknown' and 'fallback')
|
||||
if (intent === 'unknown') return artifactType === 'fallback';
|
||||
|
||||
// For agenda intents, all map to agenda block types
|
||||
const agendaIntents: SwiftIntent[] = [
|
||||
'agenda_query',
|
||||
'create_appointment',
|
||||
'cancel_appointment',
|
||||
'reschedule_appointment',
|
||||
];
|
||||
if (agendaIntents.includes(intent)) {
|
||||
return agendaIntents.includes(artifactType as SwiftIntent);
|
||||
}
|
||||
|
||||
return intent === artifactType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Route intent to appropriate artifact configuration
|
||||
*
|
||||
* Maps intents (especially agenda intents) to the correct artifact type with prefill data.
|
||||
* Implements Epic 5.S1 routing logic.
|
||||
*
|
||||
* @param intent - The classified intent
|
||||
* @param entities - Extracted entities from user input
|
||||
* @param confidence - Intent classification confidence (0-1)
|
||||
* @returns Artifact configuration or null if confidence too low or required data missing
|
||||
*/
|
||||
export function routeIntentToArtifact(
|
||||
intent: SwiftIntent,
|
||||
entities: Record<string, any>,
|
||||
confidence: number
|
||||
): { type: BlockType; prefill: Record<string, any>; title: string } | null {
|
||||
// Confidence threshold: return null if too low
|
||||
// This triggers fallback/clarification question in UI
|
||||
if (confidence < 0.7) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Route agenda intents to AgendaBlock with appropriate configuration
|
||||
switch (intent) {
|
||||
case 'agenda_query':
|
||||
return {
|
||||
type: 'agenda_query',
|
||||
title: 'Agenda',
|
||||
prefill: {
|
||||
dateRange: entities.dateRange,
|
||||
},
|
||||
};
|
||||
|
||||
case 'create_appointment':
|
||||
// Require patient for create
|
||||
if (!entities.patientName && !entities.patientId) {
|
||||
return null; // Missing required entity - trigger clarification
|
||||
}
|
||||
return {
|
||||
type: 'create_appointment',
|
||||
title: 'Nieuwe afspraak',
|
||||
prefill: {
|
||||
patientName: entities.patientName,
|
||||
patientId: entities.patientId,
|
||||
datetime: entities.datetime,
|
||||
appointmentType: entities.appointmentType,
|
||||
location: entities.location,
|
||||
},
|
||||
};
|
||||
|
||||
case 'cancel_appointment':
|
||||
return {
|
||||
type: 'cancel_appointment',
|
||||
title: 'Afspraak annuleren',
|
||||
prefill: {
|
||||
identifier: entities.identifier,
|
||||
},
|
||||
};
|
||||
|
||||
case 'reschedule_appointment':
|
||||
// Require identifier to know which appointment
|
||||
if (!entities.identifier) {
|
||||
return null; // Missing required entity - trigger clarification
|
||||
}
|
||||
return {
|
||||
type: 'reschedule_appointment',
|
||||
title: 'Afspraak verzetten',
|
||||
prefill: {
|
||||
identifier: entities.identifier,
|
||||
newDatetime: entities.newDatetime,
|
||||
},
|
||||
};
|
||||
|
||||
// Non-agenda intents - direct mapping
|
||||
case 'dagnotitie':
|
||||
return {
|
||||
type: 'dagnotitie',
|
||||
title: 'Dagnotitie',
|
||||
prefill: entities,
|
||||
};
|
||||
|
||||
case 'zoeken':
|
||||
return {
|
||||
type: 'zoeken',
|
||||
title: 'Patiënt zoeken',
|
||||
prefill: entities,
|
||||
};
|
||||
|
||||
case 'overdracht':
|
||||
return {
|
||||
type: 'overdracht',
|
||||
title: 'Overdracht',
|
||||
prefill: entities,
|
||||
};
|
||||
|
||||
case 'unknown':
|
||||
// Unknown intent - show fallback picker
|
||||
return {
|
||||
type: 'fallback',
|
||||
title: 'Keuze maken',
|
||||
prefill: entities,
|
||||
};
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user