// ChatPane — mirrors ui/src/components/chat/* with user/ask/result/progress bubbles function Bubble({ role, children, footer }) { const isUser = role === 'user'; const style = { maxWidth: '85%', padding: '10px 14px', borderRadius: 16, fontSize: 13, lineHeight: 1.55, whiteSpace: 'pre-wrap', wordBreak: 'break-word', }; if (isUser) { Object.assign(style, { background: '#f1f5f9', color: '#0f172a', borderBottomRightRadius: 4, alignSelf: 'flex-end' }); } else if (role === 'ask') { Object.assign(style, { background: '#fef9c3', color: '#854d0e', border: '1px solid #fde68a', borderBottomLeftRadius: 4 }); } else if (role === 'result') { Object.assign(style, { background: '#ecfdf5', color: '#065f46', border: '1px solid #a7f3d0', borderBottomLeftRadius: 4 }); } else { Object.assign(style, { background: '#fff', color: '#0f172a', border: '1px solid #e2e8f0', borderBottomLeftRadius: 4 }); } return (
{children}
{footer &&
{footer}
}
); } function ProgressBubble({ text }) { return (
{text}
); } function ChatHeader({ task, onOpenDetail, detailOpen }) { return (
TASK #{task.id}
{task.title}
); } function Composer({ onSend, disabled }) { const [text, setText] = React.useState(''); const [sending, setSending] = React.useState(false); const [error, setError] = React.useState(null); const send = async () => { if (!text.trim() || disabled || sending) return; setError(null); setSending(true); try { await Promise.resolve(onSend(text.trim())); setText(''); } catch (e) { setError(e?.message || '送信に失敗しました'); } finally { setSending(false); } }; return (
{error && (
⚠ {error}
)}