chat suggestions, SEo integration, and more
This commit is contained in:
@@ -12,7 +12,10 @@ interface SuggestionCategory {
|
||||
questions: string[]
|
||||
}
|
||||
|
||||
const SUGGESTION_CATEGORIES: SuggestionCategory[] = [
|
||||
/**
|
||||
* Documentation suggestions - shown when not in a patient dossier
|
||||
*/
|
||||
const DOC_SUGGESTION_CATEGORIES: SuggestionCategory[] = [
|
||||
{
|
||||
id: 'clienten',
|
||||
label: 'Cliënten & Dossiers',
|
||||
@@ -45,19 +48,63 @@ const SUGGESTION_CATEGORIES: SuggestionCategory[] = [
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* Client suggestions - shown when in a patient dossier
|
||||
*/
|
||||
const CLIENT_SUGGESTION_CATEGORIES: SuggestionCategory[] = [
|
||||
{
|
||||
id: 'rapportages',
|
||||
label: 'Rapportages',
|
||||
icon: '📝',
|
||||
questions: [
|
||||
'Geef een samenvatting van de rapportages',
|
||||
'Wat is er de laatste tijd genoteerd?',
|
||||
'Zijn er behandeladviezen?',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'intake',
|
||||
label: 'Intake & Behandeling',
|
||||
icon: '🏥',
|
||||
questions: [
|
||||
'Wat is het behandeladvies?',
|
||||
'Op welke afdeling loopt de intake?',
|
||||
'Is de intake afgerond?',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'screening',
|
||||
label: 'Screening',
|
||||
icon: '📋',
|
||||
questions: [
|
||||
'Wat was de hulpvraag?',
|
||||
'Wat is de screeningbeslissing?',
|
||||
'Is de cliënt geschikt bevonden?',
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
interface ChatSuggestionsProps {
|
||||
onSelect: (question: string) => void
|
||||
disabled?: boolean
|
||||
mode?: 'client' | 'documentation'
|
||||
}
|
||||
|
||||
/**
|
||||
* Two-step suggestion selector:
|
||||
* 1. Show categories
|
||||
* 2. After selecting category, show questions
|
||||
*
|
||||
* Supports two modes:
|
||||
* - 'documentation': Questions about how to use the EPD system
|
||||
* - 'client': Questions about the active patient (rapportages, intake, screening)
|
||||
*/
|
||||
export function ChatSuggestions({ onSelect, disabled = false }: ChatSuggestionsProps) {
|
||||
export function ChatSuggestions({ onSelect, disabled = false, mode = 'documentation' }: ChatSuggestionsProps) {
|
||||
const [selectedCategory, setSelectedCategory] = useState<SuggestionCategory | null>(null)
|
||||
|
||||
// Select the appropriate categories based on mode
|
||||
const categories = mode === 'client' ? CLIENT_SUGGESTION_CATEGORIES : DOC_SUGGESTION_CATEGORIES
|
||||
|
||||
const handleQuestionSelect = (question: string) => {
|
||||
onSelect(question)
|
||||
setSelectedCategory(null)
|
||||
@@ -106,9 +153,11 @@ export function ChatSuggestions({ onSelect, disabled = false }: ChatSuggestionsP
|
||||
// Show category selection
|
||||
return (
|
||||
<div className="px-4 pb-3">
|
||||
<p className="text-xs text-slate-500 mb-2">Kies een onderwerp:</p>
|
||||
<p className="text-xs text-slate-500 mb-2">
|
||||
{mode === 'client' ? 'Vragen over deze cliënt:' : 'Kies een onderwerp:'}
|
||||
</p>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
{SUGGESTION_CATEGORIES.map((category) => (
|
||||
{categories.map((category) => (
|
||||
<button
|
||||
key={category.id}
|
||||
type="button"
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Sparkles, X } from 'lucide-react'
|
||||
import { Sparkles, X, FileText } from 'lucide-react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
import { usePatientContext } from '@/app/epd/components/patient-context'
|
||||
|
||||
import { ChatInput } from './chat-input'
|
||||
import { ChatMessages } from './chat-messages'
|
||||
@@ -11,6 +12,18 @@ import { ChatSuggestions } from './chat-suggestions'
|
||||
import { RateLimitMessage } from './rate-limit-message'
|
||||
import { useDocsChat } from './use-docs-chat'
|
||||
|
||||
/**
|
||||
* Helper to format patient name from FHIR structure
|
||||
*/
|
||||
function formatPatientName(patient: { name?: Array<{ given?: string[]; family?: string; prefix?: string[] }> } | null): string | undefined {
|
||||
if (!patient?.name?.[0]) return undefined
|
||||
const name = patient.name[0]
|
||||
const given = name.given?.join(' ') || ''
|
||||
const prefix = name.prefix?.join(' ') || ''
|
||||
const family = name.family || ''
|
||||
return `${given} ${prefix ? prefix + ' ' : ''}${family}`.trim() || undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Floating chat widget for documentation assistant
|
||||
*
|
||||
@@ -20,7 +33,27 @@ import { useDocsChat } from './use-docs-chat'
|
||||
*/
|
||||
export function DocsChatWidget() {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const { messages, isLoading, isStreaming, error, isRateLimited, rateLimitResetTime, sendMessage, clearError, clearRateLimit } = useDocsChat()
|
||||
|
||||
// Get patient context for client-aware chat
|
||||
const { patient } = usePatientContext()
|
||||
const patientName = formatPatientName(patient)
|
||||
|
||||
const {
|
||||
messages,
|
||||
isLoading,
|
||||
isStreaming,
|
||||
error,
|
||||
isRateLimited,
|
||||
rateLimitResetTime,
|
||||
sendMessage,
|
||||
clearError,
|
||||
clearRateLimit,
|
||||
hasClientContext,
|
||||
clientName,
|
||||
} = useDocsChat({
|
||||
clientId: patient?.id,
|
||||
clientName: patientName,
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -70,7 +103,7 @@ export function DocsChatWidget() {
|
||||
EPD Assistent
|
||||
</h2>
|
||||
<p className="text-xs text-slate-500">
|
||||
Stel vragen over het EPD
|
||||
{hasClientContext ? 'Stel vragen over de client of het EPD' : 'Stel vragen over het EPD'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,6 +123,16 @@ export function DocsChatWidget() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Client indicator - shown when in patient dossier */}
|
||||
{hasClientContext && clientName && (
|
||||
<div className="px-4 py-2 bg-blue-50 border-b border-blue-100 flex items-center gap-2">
|
||||
<FileText className="w-4 h-4 text-blue-600" />
|
||||
<span className="text-sm text-blue-700">
|
||||
Dossier: <span className="font-medium">{clientName}</span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error banner */}
|
||||
{error && (
|
||||
<div className="px-4 py-2 bg-red-50 border-b border-red-100 flex items-center justify-between">
|
||||
@@ -120,6 +163,7 @@ export function DocsChatWidget() {
|
||||
<ChatSuggestions
|
||||
onSelect={sendMessage}
|
||||
disabled={isLoading || isStreaming}
|
||||
mode={hasClientContext ? 'client' : 'documentation'}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -23,6 +23,14 @@ interface UseDocsChatState {
|
||||
rateLimitResetTime: number | null // timestamp when rate limit resets
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook options
|
||||
*/
|
||||
interface UseDocsChatOptions {
|
||||
clientId?: string // UUID van actieve patiënt
|
||||
clientName?: string // Naam van actieve patiënt (voor display)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook return type
|
||||
*/
|
||||
@@ -31,6 +39,8 @@ interface UseDocsChatReturn extends UseDocsChatState {
|
||||
clearMessages: () => void
|
||||
clearError: () => void
|
||||
clearRateLimit: () => void
|
||||
hasClientContext: boolean
|
||||
clientName: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,12 +68,20 @@ const WELCOME_MESSAGE: ChatMessage = {
|
||||
* - Streaming responses from Claude API
|
||||
* - Loading and error states
|
||||
* - Welcome message on init
|
||||
* - Client-aware: sends clientId for patient-specific questions
|
||||
*
|
||||
* @example
|
||||
* // Documentation-only mode
|
||||
* const { messages, isLoading, sendMessage } = useDocsChat()
|
||||
* await sendMessage("Hoe maak ik een intake aan?")
|
||||
*
|
||||
* // Client-aware mode
|
||||
* const { messages, sendMessage, hasClientContext } = useDocsChat({
|
||||
* clientId: patient?.id,
|
||||
* clientName: "Jan de Vries"
|
||||
* })
|
||||
*/
|
||||
export function useDocsChat(): UseDocsChatReturn {
|
||||
export function useDocsChat(options?: UseDocsChatOptions): UseDocsChatReturn {
|
||||
const { clientId, clientName } = options ?? {}
|
||||
const [state, setState] = useState<UseDocsChatState>({
|
||||
messages: [WELCOME_MESSAGE],
|
||||
isLoading: false,
|
||||
@@ -111,6 +129,7 @@ export function useDocsChat(): UseDocsChatReturn {
|
||||
body: JSON.stringify({
|
||||
messages: history,
|
||||
userMessage: trimmedContent,
|
||||
clientId, // Include clientId if available for patient-specific questions
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -203,7 +222,7 @@ export function useDocsChat(): UseDocsChatReturn {
|
||||
),
|
||||
}))
|
||||
}
|
||||
}, [state.messages])
|
||||
}, [state.messages, clientId])
|
||||
|
||||
const clearMessages = useCallback(() => {
|
||||
setState({
|
||||
@@ -230,5 +249,7 @@ export function useDocsChat(): UseDocsChatReturn {
|
||||
clearMessages,
|
||||
clearError,
|
||||
clearRateLimit,
|
||||
hasClientContext: !!clientId,
|
||||
clientName: clientName ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user