ClinDoc_CDSS / frontend /src /components /ChatCard.jsx
iyadh-bencheikh's picture
Update
764531e
// ChatCard.jsx
import { useState, useRef, useEffect } from 'react'
export default function ChatCard({ chatHistory, onSend, loading }) {
const [input, setInput] = useState('')
const [collapsed, setCollapsed] = useState(false)
const bottomRef = useRef(null)
// Auto-scroll to bottom on new message
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
}, [chatHistory])
function handleSend() {
const msg = input.trim()
if (!msg || loading) return
setInput('')
onSend(msg)
}
function handleKeyDown(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
handleSend()
}
}
return (
<div style={s.card}>
<div style={s.header} onClick={() => setCollapsed(c => !c)}>
<span style={s.title}>Chat</span>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span style={s.chevron}>{collapsed ? '▶' : '▼'}</span>
</div>
</div>
{!collapsed && (
<>
<div style={s.messages}>
{chatHistory.length === 0 && (
<div style={s.empty}>
Stellen Sie Fragen zum Patienten auf Basis dieses Dokuments.
</div>
)}
{chatHistory.map((msg, i) => (
<div key={i} style={s.msgWrapper}>
<div style={msg.role === 'user' ? s.labelUser : s.labelAi}>
{msg.role === 'user' ? 'Arzt' : 'System'}
</div>
<div style={msg.role === 'user' ? s.bubbleUser : s.bubbleAi}>
{msg.content}
</div>
</div>
))}
{/* Loading bubble */}
{loading && (
<div style={s.msgWrapper}>
<div style={s.labelAi}>System</div>
<div style={s.bubbleAi}>
<div style={s.typingDots}>
<span style={{ ...s.dot, animationDelay: '0ms' }} />
<span style={{ ...s.dot, animationDelay: '150ms' }} />
<span style={{ ...s.dot, animationDelay: '300ms' }} />
</div>
</div>
</div>
)}
<div ref={bottomRef} />
</div>
<div style={s.note}>
Die Antworten basieren ausschließlich auf Patientendokumente und extrahierten Daten. (In dieser Version ohne Abruf medizinischer Leitlinien oder Fachliteratur)
</div>
<div style={s.inputRow}>
<textarea
style={s.input}
placeholder="Frage zum Patienten stellen… (Enter zum Senden)"
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={handleKeyDown}
rows={2}
/>
<button
style={{ ...s.sendBtn, opacity: !input.trim() || loading ? 0.5 : 1 }}
disabled={!input.trim() || !!loading}
onClick={handleSend}
>
</button>
</div>
</>
)}
</div>
)
}
// Styles
const s = {
card: { borderRadius:10, border:'0.5px solid #E2E0D8', background:'#fff', overflow:'hidden' },
header: { display:'flex', alignItems:'center', justifyContent:'space-between', padding:'10px 14px', cursor:'pointer', userSelect:'none' },
title: { fontSize:13, fontWeight:500 },
hint: { fontSize:10, color:'#A09D94', fontStyle:'italic' },
chevron: { fontSize:10, color:'#A09D94' },
messages: { maxHeight:320, overflowY:'auto', padding:'12px 14px', display:'flex', flexDirection:'column', gap:10, borderTop:'0.5px solid #E2E0D8' },
empty: { fontSize:12, color:'#A09D94', textAlign:'center', padding:'16px 0' },
msgWrapper: { display:'flex', flexDirection:'column', gap:3 },
labelUser: { fontSize:10, color:'#A09D94', textTransform:'uppercase', letterSpacing:'0.04em' },
labelAi: { fontSize:10, color:'#A09D94', textTransform:'uppercase', letterSpacing:'0.04em' },
bubbleUser: { fontSize:12, lineHeight:1.6, padding:'8px 11px', borderRadius:8, background:'#EFF6FF', color:'#1E40AF', alignSelf:'flex-start', maxWidth:'90%' },
bubbleAi: { fontSize:12, lineHeight:1.6, padding:'8px 11px', borderRadius:8, background:'#F7F6F3', color:'#1A1916', border:'0.5px solid #E2E0D8', alignSelf:'flex-start', maxWidth:'90%' },
note: { fontSize:10, color:'#A09D94', fontStyle:'italic', padding:'6px 14px', borderTop:'0.5px solid #E2E0D8' },
inputRow: { display:'flex', gap:8, padding:'10px 14px', borderTop:'0.5px solid #E2E0D8', alignItems:'flex-end' },
input: { flex:1, fontFamily:'inherit', fontSize:12, padding:'7px 10px', borderRadius:6, border:'0.5px solid #C8C6BC', background:'#F7F6F3', color:'#1A1916', resize:'none', lineHeight:1.5, outline:'none' },
sendBtn: { fontFamily:'inherit', fontSize:13, padding:'7px 14px', borderRadius:6, border:'0.5px solid #1A1916', background:'#1A1916', color:'#fff', cursor:'pointer', flexShrink:0 },
typingDots: { display:'flex', gap:4, alignItems:'center', padding:'2px 0' },
dot: { width:6, height:6, borderRadius:'50%', background:'#A09D94', animation:'pulse 1s ease-in-out infinite' },
}