mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-05-18 20:08:19 +00:00
- Comprehensive docs: embed snippet, key management, API endpoints, chat/booking/contact flows, lead dedup, reCAPTCHA, branding, deploy checklist, troubleshooting - Widget Preact source archived in packages/widget-src/ (was only on local machine, not tracked in any repo) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
95 lines
3.6 KiB
TypeScript
95 lines
3.6 KiB
TypeScript
import { useState, useRef, useEffect } from 'preact/hooks';
|
|
import { streamChat } from './api';
|
|
import type { ChatMessage } from './types';
|
|
|
|
const QUICK_ACTIONS = [
|
|
'Doctor availability',
|
|
'Clinic timings',
|
|
'Book appointment',
|
|
'Health packages',
|
|
];
|
|
|
|
export const Chat = () => {
|
|
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
|
const [input, setInput] = useState('');
|
|
const [loading, setLoading] = useState(false);
|
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
if (scrollRef.current) {
|
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
}
|
|
}, [messages]);
|
|
|
|
const sendMessage = async (text: string) => {
|
|
if (!text.trim() || loading) return;
|
|
|
|
const userMsg: ChatMessage = { role: 'user', content: text.trim() };
|
|
const updated = [...messages, userMsg];
|
|
setMessages(updated);
|
|
setInput('');
|
|
setLoading(true);
|
|
|
|
try {
|
|
const stream = await streamChat(updated);
|
|
const reader = stream.getReader();
|
|
const decoder = new TextDecoder();
|
|
let assistantText = '';
|
|
|
|
setMessages([...updated, { role: 'assistant', content: '' }]);
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
assistantText += decoder.decode(value, { stream: true });
|
|
setMessages([...updated, { role: 'assistant', content: assistantText }]);
|
|
}
|
|
} catch {
|
|
setMessages([...updated, { role: 'assistant', content: 'Sorry, I encountered an error. Please try again.' }]);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
<div class="chat-messages" ref={scrollRef}>
|
|
{messages.length === 0 && (
|
|
<div style={{ textAlign: 'center', padding: '20px 0' }}>
|
|
<div style={{ fontSize: '24px', marginBottom: '8px' }}>👋</div>
|
|
<div style={{ fontSize: '14px', fontWeight: 600, color: '#1f2937', marginBottom: '4px' }}>
|
|
How can we help you?
|
|
</div>
|
|
<div style={{ fontSize: '12px', color: '#6b7280', marginBottom: '16px' }}>
|
|
Ask about doctors, clinics, packages, or book an appointment.
|
|
</div>
|
|
<div class="quick-actions">
|
|
{QUICK_ACTIONS.map(q => (
|
|
<button key={q} class="quick-action" onClick={() => sendMessage(q)}>{q}</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
{messages.map((msg, i) => (
|
|
<div key={i} class={`chat-msg ${msg.role}`}>
|
|
<div class="chat-bubble">{msg.content || '...'}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div class="chat-input-row">
|
|
<input
|
|
class="widget-input chat-input"
|
|
placeholder="Type a message..."
|
|
value={input}
|
|
onInput={(e: any) => setInput(e.target.value)}
|
|
onKeyDown={(e: any) => e.key === 'Enter' && sendMessage(input)}
|
|
disabled={loading}
|
|
/>
|
|
<button class="chat-send" onClick={() => sendMessage(input)} disabled={loading || !input.trim()}>
|
|
↑
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|