feat(swift): voeg chat API met Claude streaming toe (E3.S1-S2)
E3.S1 - Chat API endpoint skeleton (3 SP)
- /api/swift/chat route met SSE setup
- Zod validation (message, messages, context)
- Authentication check (session required)
- Rate limiting (20 requests per minute)
- Request body schema voor chat messages en context
E3.S2 - Streaming response logic (5 SP)
- Claude API integration (Anthropic Sonnet 4)
- Real-time streaming via Server-Sent Events
- Event parsing (content_block_delta, message_stop, error)
- Simple system prompt met context injection
- Error handling voor API failures
API Route Features:
- Model: claude-sonnet-4-20250514
- Max tokens: 2048, Temperature: 0.7
- Conversation history: max 20 messages
- Rate limiting per user (20 req/min)
- Request cancellation support
- Context: activePatient + shift
SSE Event Format:
- content_block_delta → {"type":"content","text":"..."}
- message_stop → {"type":"done"}
- error → {"type":"error","error":"..."}
Client Helper (lib/swift/chat-api.ts):
- sendChatMessage() function voor frontend
- SSE stream parsing met TextDecoder
- Callbacks: onChunk, onDone, onError
- Error handling en retry logic
ChatPanel Integration:
- Real-time streaming met updateLastMessage()
- isStreaming state voor UI feedback
- Context injection (patient, shift)
- Error messages in chat
System Prompt (Simple):
- Nederlandse medische assistent voor Swift GGZ EPD
- Context-aware (patiënt + dienst)
- Vriendelijk en professioneel
- Medical scribe functionaliteit komt in E3.S3
Files Created:
- app/api/swift/chat/route.ts (279 regels)
- lib/swift/chat-api.ts (113 regels)
Files Updated:
- components/swift/chat/chat-panel.tsx (+52 regels) - Streaming integration
Epic 3 Progress: 2/6 stories compleet (8 SP / 21 SP = 38%)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,10 +14,17 @@ import { ArrowDown } from 'lucide-react';
|
||||
import { ChatMessage } from './chat-message';
|
||||
import { ChatInput, ChatInputHandle } from './chat-input';
|
||||
import { useSwiftStore } from '@/stores/swift-store';
|
||||
import { sendChatMessage } from '@/lib/swift/chat-api';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export function ChatPanel() {
|
||||
const chatMessages = useSwiftStore((s) => s.chatMessages);
|
||||
const addChatMessage = useSwiftStore((s) => s.addChatMessage);
|
||||
const updateLastMessage = useSwiftStore((s) => s.updateLastMessage);
|
||||
const setStreaming = useSwiftStore((s) => s.setStreaming);
|
||||
const isStreaming = useSwiftStore((s) => s.isStreaming);
|
||||
const activePatient = useSwiftStore((s) => s.activePatient);
|
||||
const shift = useSwiftStore((s) => s.shift);
|
||||
|
||||
// Refs for scrolling
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -137,9 +144,48 @@ export function ChatPanel() {
|
||||
{/* Chat input */}
|
||||
<ChatInput
|
||||
ref={chatInputRef}
|
||||
onSend={(message) => {
|
||||
// Handle send (AI response will be added in E3)
|
||||
console.log('User sent:', message);
|
||||
disabled={isStreaming}
|
||||
onSend={async (message) => {
|
||||
// E3.S1: Test streaming API with mock response
|
||||
setStreaming(true);
|
||||
|
||||
// Add empty assistant message that will be filled by streaming
|
||||
addChatMessage({
|
||||
type: 'assistant',
|
||||
content: '',
|
||||
});
|
||||
|
||||
let accumulatedContent = '';
|
||||
|
||||
await sendChatMessage(
|
||||
message,
|
||||
chatMessages,
|
||||
{
|
||||
activePatient: activePatient ? {
|
||||
id: activePatient.id,
|
||||
first_name: activePatient.name_given?.[0] || '',
|
||||
last_name: activePatient.name_family || '',
|
||||
} : null,
|
||||
shift,
|
||||
},
|
||||
(chunk) => {
|
||||
// On each chunk, append to accumulated content and update last message
|
||||
accumulatedContent += chunk;
|
||||
updateLastMessage(accumulatedContent);
|
||||
},
|
||||
() => {
|
||||
// On done
|
||||
setStreaming(false);
|
||||
},
|
||||
(error) => {
|
||||
// On error
|
||||
setStreaming(false);
|
||||
addChatMessage({
|
||||
type: 'error',
|
||||
content: `Er ging iets mis: ${error}`,
|
||||
});
|
||||
}
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user