Spaces:
Sleeping
Sleeping
| // 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' }, | |
| } |