- Updated comments and documentation to reflect the new branding of the chat API and related components. - Renamed functions and variables to align with the Swift Assistent terminology. - Enhanced UI components with animations using Framer Motion for a smoother user experience. - Removed outdated architecture documentation related to the Medical Scribe system. This change is part of the transition to the Swift Assistent branding, ensuring consistency across the application.
110 lines
2.7 KiB
TypeScript
110 lines
2.7 KiB
TypeScript
/**
|
|
* Swift Chat API Client
|
|
*
|
|
* Client-side helper voor het aanroepen van de Swift chat API met streaming support.
|
|
*
|
|
* Epic: E3 (Chat API & Swift Assistent)
|
|
* Story: E3.S1 (Chat API endpoint skeleton)
|
|
*/
|
|
|
|
import type { ChatMessage } from '@/stores/swift-store';
|
|
|
|
export interface ChatContext {
|
|
activePatient?: {
|
|
id: string;
|
|
first_name: string;
|
|
last_name: string;
|
|
} | null;
|
|
shift: 'nacht' | 'ochtend' | 'middag' | 'avond';
|
|
}
|
|
|
|
export interface StreamEvent {
|
|
type: 'content' | 'done' | 'error';
|
|
text?: string;
|
|
error?: string;
|
|
}
|
|
|
|
/**
|
|
* Send a chat message and receive streaming response via SSE
|
|
*/
|
|
export async function sendChatMessage(
|
|
message: string,
|
|
messages: ChatMessage[],
|
|
context?: ChatContext,
|
|
onChunk?: (text: string) => void,
|
|
onDone?: () => void,
|
|
onError?: (error: string) => void
|
|
): Promise<void> {
|
|
try {
|
|
const response = await fetch('/api/swift/chat', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
message,
|
|
messages,
|
|
context,
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ error: response.statusText }));
|
|
throw new Error(errorData.error || 'API request failed');
|
|
}
|
|
|
|
if (!response.body) {
|
|
throw new Error('Response body is null');
|
|
}
|
|
|
|
// Read SSE stream
|
|
const reader = response.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
|
|
let buffer = '';
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
|
|
if (done) {
|
|
break;
|
|
}
|
|
|
|
// Decode chunk and add to buffer
|
|
buffer += decoder.decode(value, { stream: true });
|
|
|
|
// Process complete SSE events (separated by \n\n)
|
|
const events = buffer.split('\n\n');
|
|
|
|
// Keep the last incomplete event in the buffer
|
|
buffer = events.pop() || '';
|
|
|
|
// Process complete events
|
|
for (const eventStr of events) {
|
|
if (!eventStr.trim()) continue;
|
|
|
|
// Parse SSE event (format: "data: {...}")
|
|
const dataMatch = eventStr.match(/^data: (.+)$/);
|
|
if (!dataMatch) continue;
|
|
|
|
try {
|
|
const event: StreamEvent = JSON.parse(dataMatch[1]);
|
|
|
|
if (event.type === 'content' && event.text) {
|
|
onChunk?.(event.text);
|
|
} else if (event.type === 'done') {
|
|
onDone?.();
|
|
} else if (event.type === 'error') {
|
|
onError?.(event.error || 'Unknown error');
|
|
}
|
|
} catch (parseError) {
|
|
console.error('Failed to parse SSE event:', parseError);
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Chat API error:', error);
|
|
onError?.(error instanceof Error ? error.message : 'Unknown error');
|
|
}
|
|
}
|