mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-04-11 18:08:16 +00:00
chore: move widget source into sidecar repo (widget-src/)
Widget builds from widget-src/ → public/widget.js Vite outDir updated to ../public .gitignore excludes node_modules Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
94
widget-src/src/chat.tsx
Normal file
94
widget-src/src/chat.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user