--- /dev/null
+import React, { useEffect, useRef, useState } from 'react';
+import './chatbot.css';
+
+export function ChatbotToggle() {
+ const [theme, setTheme] = useState(() => (typeof document !== 'undefined' ? (document.body.getAttribute('data-bs-theme') || 'light') : 'light'));
+ const [open, setOpen] = useState(false);
+ const [hoverSend, setHoverSend] = useState(false);
+ const [hoverFab, setHoverFab] = useState(false);
+ const [text, setText] = useState('');
+ const [messages, setMessages] = useState([
+ { id: 1, role: 'assistant', content: 'Hello! How can I assist you today?' }
+ ]);
+ const endRef = useRef(null);
+
+ useEffect(() => {
+ if (endRef.current) {
+ endRef.current.scrollIntoView({ behavior: 'smooth' });
+ }
+ }, [messages, open]);
+
+ function pushMessage(role, content) {
+ setMessages((prev) => [...prev, { id: prev.length + 1, role, content }]);
+ }
+
+ function onKeyDown(e) {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ onSend();
+ }
+ }
+
+ function onSend(e) {
+ if (e) e.preventDefault();
+ if (!text.trim()) return;
+ const userText = text.trim();
+ setText('');
+ pushMessage('user', userText);
+ pushMessage('assistant', 'LLM endpoint is not connected.');
+ }
+
+ useEffect(() => {
+ const body = document.body;
+ const observer = new MutationObserver((mutations) => {
+ for (const m of mutations) {
+ if (m.attributeName === 'data-bs-theme') {
+ setTheme(body.getAttribute('data-bs-theme') || 'light');
+ }
+ }
+ });
+ observer.observe(body, { attributes: true });
+ const onStorage = (e) => {
+ if (e.key === 'theme') {
+ setTheme(e.newValue || 'light');
+ }
+ };
+ window.addEventListener('storage', onStorage);
+ return () => {
+ observer.disconnect();
+ window.removeEventListener('storage', onStorage);
+ };
+ }, []);
+
+ const isDark = theme === 'dark';
+
+ const textColor = isDark ? '#f8fafc' : '#0f172a';
+
+ return (
+ <>
+ {open && (
+ <div className={`chatbot-container ${open ? 'open' : ''}`}>
+ <div className='chatbot-panel'>
+ <div className='chatbot-header'>
+ <div className='chatbot-title'>
+ <span>Assistant</span>
+ </div>
+ <button className='chatbot-icon' aria-label='Close' onClick={() => setOpen(false)}>
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
+ </button>
+ </div>
+
+ <div className='chatbot-messages'>
+ {messages.map((m) => (
+ <div key={m.id} className={`chatbot-row ${m.role}`}>
+ <div className={`chatbot-bubble ${m.role}`}>{m.content}</div>
+ </div>
+ ))}
+ <div ref={endRef} />
+ </div>
+
+ <div className='chatbot-composer'>
+ <textarea
+ className='chatbot-textarea'
+ placeholder='Type your message...'
+ value={text}
+ onChange={(e) => setText(e.target.value)}
+ onKeyDown={onKeyDown}
+ />
+ <button type='button' onClick={onSend} className='chatbot-send'>
+ Send
+ </button>
+ </div>
+ <div className='chatbot-hint'>Press Enter to send, Shift+Enter for a new line</div>
+ </div>
+ </div>
+ )}
+
+ {!open && (
+ <button onClick={() => setOpen(true)} aria-label='Open Chatbot' className='chatbot-fab'>
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
+ <path d="M21 15a4 4 0 0 1-4 4H8l-4 4V7a4 4 0 0 1 4-4h9a4 4 0 0 1 4 4z" />
+ </svg>
+ </button>
+ )}
+ </>
+ );
+}
+
--- /dev/null
+.chatbot-container {
+ position: fixed;
+ right: 24px;
+ bottom: 16px;
+ width: 520px;
+ max-width: 94vw;
+ height: min(72vh, 700px);
+ z-index: 1030;
+ transform: translateY(16px) scale(0.98);
+ opacity: 0;
+ transition: opacity 220ms ease, transform 220ms ease, bottom 220ms ease;
+}
+
+.chatbot-container.open {
+ bottom: 24px;
+ transform: translateY(0) scale(1);
+ opacity: 1;
+}
+
+.chatbot-panel {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ padding: 16px;
+ border-radius: 16px;
+ background: #ffffff;
+ border: 1px solid #e6e8eb;
+}
+
+.chatbot-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 12px;
+ color: #0f172a;
+}
+
+.chatbot-icon {
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ padding: 0;
+ line-height: 0;
+}
+
+.chatbot-title {
+ display: flex;
+ align-items: baseline;
+ gap: 8px;
+ font-weight: 700;
+ font-size: 16px;
+}
+
+.chatbot-messages {
+ flex: 1;
+ overflow: auto;
+ padding: 8px;
+ border-radius: 12px;
+ background: #f8fafc;
+ border: 1px solid #e2e8f0;
+}
+
+.chatbot-row {
+ display: flex;
+ margin-bottom: 8px;
+}
+
+.chatbot-row.user {
+ justify-content: flex-end;
+}
+
+.chatbot-row.assistant {
+ justify-content: flex-start;
+}
+
+.chatbot-bubble {
+ max-width: 78%;
+ padding: 10px 12px;
+ border-radius: 14px;
+ word-break: break-word;
+ white-space: pre-wrap;
+}
+
+.chatbot-bubble.user {
+ background: linear-gradient(135deg, #4f46e5 0%, #06b6d4 100%);
+ color: #ffffff;
+ border: none;
+ box-shadow: 0 10px 24px rgba(6, 182, 212, 0.25);
+ border-radius: 14px 14px 4px 14px;
+}
+
+.chatbot-bubble.assistant {
+ background: #ffffff;
+ color: #0f172a;
+ border: 1px solid #e2e8f0;
+ box-shadow: 0 8px 18px rgba(0, 0, 0, 0.06);
+ border-radius: 14px 14px 14px 4px;
+}
+
+.chatbot-composer {
+ display: flex;
+ gap: 10px;
+ align-items: flex-end;
+ margin-top: 12px;
+}
+
+.chatbot-textarea {
+ flex: 1;
+ min-height: 60px;
+ max-height: 160px;
+ resize: vertical;
+ padding: 10px 12px;
+ border-radius: 12px;
+ border: 1px solid #e5e7eb;
+ background: #ffffff;
+ color: #0f172a;
+}
+
+.chatbot-send {
+ border: none;
+ background: linear-gradient(135deg, #4f46e5 0%, #06b6d4 100%);
+ color: #fff;
+ padding: 10px 16px;
+ border-radius: 12px;
+ font-weight: 700;
+ cursor: pointer;
+}
+
+.chatbot-hint {
+ font-size: 12px;
+ margin-top: 4px;
+ color: #94a3b8;
+}
+
+.chatbot-fab {
+ position: fixed;
+ right: 24px;
+ bottom: 24px;
+ width: 76px;
+ height: 76px;
+ border-radius: 50%;
+ border: none;
+ cursor: pointer;
+ z-index: 1030;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: linear-gradient(135deg, #4f46e5 0%, #06b6d4 100%);
+ color: #fff;
+}
+
+body[data-bs-theme='dark'] .chatbot-panel {
+ background: #0f172a;
+ border: 1px solid #334155;
+}
+
+body[data-bs-theme='dark'] .chatbot-header {
+ color: #f8fafc;
+}
+
+body[data-bs-theme='dark'] .chatbot-messages {
+ background: #0b1220;
+ border-color: #475569;
+}
+
+body[data-bs-theme='dark'] .chatbot-bubble.assistant {
+ background: #1f2937;
+ color: #f1f5f9;
+ border-color: #475569;
+}
+
+body[data-bs-theme='dark'] .chatbot-textarea {
+ background: #0b1220;
+ color: #f8fafc;
+ border-color: #475569;
+}
+
+body[data-bs-theme='dark'] .chatbot-fab {
+ background: linear-gradient(135deg, #1f2937 0%, #334155 100%);
+}
+
+