| import React, { useState, useEffect, useRef } from 'react'; |
| import { Send, Sparkles, MessageSquare, Compass, ShieldAlert } from 'lucide-react'; |
|
|
| export default function Chatbot({ metrics, scores }) { |
| const [messages, setMessages] = useState([]); |
| const [input, setInput] = useState(''); |
| const [loading, setLoading] = useState(false); |
| const messagesEndRef = useRef(null); |
|
|
| |
| useEffect(() => { |
| if (metrics) { |
| setMessages([ |
| { |
| role: 'assistant', |
| content: `สวัสดีครับ! ผมเป็นผู้ช่วยซินแสปัญญาประดิษฐ์และวิศวกรสิ่งแวดล้อม GIS ผมพร้อมช่วยวิเคราะห์ฮวงจุ้ยและโครงสร้างสิ่งแวดล้อมของ **${metrics.location_name}** (พิกัด: ${metrics.elevation_m}m, NDVI: ${metrics.ndvi}) ให้คุณแล้วครับ มีอะไรต้องการสอบถามเพิ่มเติมเกี่ยวกับการปรับสมดุล Yin-Yang, ปัญหา Sha Qi หรือแนวทางวิศวกรรมป้องกันในจุดนี้ไหมครับ?` |
| } |
| ]); |
| } |
| }, [metrics]); |
|
|
| const scrollToBottom = () => { |
| messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); |
| }; |
|
|
| useEffect(() => { |
| scrollToBottom(); |
| }, [messages, loading]); |
|
|
| const handleSend = async (e) => { |
| e.preventDefault(); |
| if (!input.trim() || loading) return; |
|
|
| const userMessage = input; |
| setInput(''); |
| setMessages((prev) => [...prev, { role: 'user', content: userMessage }]); |
| setLoading(true); |
|
|
| |
| const history = messages |
| .slice(-6) |
| .map((msg) => ({ role: msg.role, content: msg.content })); |
|
|
| try { |
| const response = await fetch('/api/chat', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| message: userMessage, |
| history: history, |
| metrics: metrics, |
| scores: scores |
| }) |
| }); |
|
|
| if (!response.ok) throw new Error('API server returned error'); |
| const data = await response.json(); |
| |
| setMessages((prev) => [...prev, { role: 'assistant', content: data.response }]); |
| } catch (err) { |
| console.error(err); |
| setMessages((prev) => [ |
| ...prev, |
| { role: 'assistant', content: 'ขออภัยครับ เกิดข้อผิดพลาดในการเชื่อมต่อระบบวิเคราะห์ AI กรุณาตรวจสอบว่าเซิร์ฟเวอร์ยังเปิดอยู่' } |
| ]); |
| } finally { |
| setLoading(false); |
| } |
| }; |
|
|
| return ( |
| <div className="glass-panel" style={{ |
| flex: 1, |
| display: 'flex', |
| flexDirection: 'column', |
| overflow: 'hidden', |
| border: '1px solid var(--border-gold-glass)' |
| }}> |
| {/* Chat Header */} |
| <div style={{ |
| padding: '12px 16px', |
| background: 'rgba(255, 255, 255, 0.02)', |
| borderBottom: '1px solid var(--border-glass)', |
| display: 'flex', |
| alignItems: 'center', |
| gap: '10px' |
| }}> |
| <div style={{ |
| width: '8px', |
| height: '8px', |
| borderRadius: '50%', |
| backgroundColor: '#50c878', |
| boxShadow: '0 0 8px #50c878' |
| }} /> |
| <MessageSquare size={14} color="var(--color-gold)" /> |
| <span style={{ fontSize: '13px', fontWeight: 'bold', color: 'var(--color-gold)' }}> |
| Typhoon Feng Shui Master AI |
| </span> |
| </div> |
| |
| {/* Messages Terminal Area */} |
| <div style={{ |
| flex: 1, |
| overflowY: 'auto', |
| padding: '16px', |
| display: 'flex', |
| flexDirection: 'column', |
| gap: '12px' |
| }}> |
| {messages.map((msg, index) => { |
| const isUser = msg.role === 'user'; |
| return ( |
| <div |
| key={index} |
| style={{ |
| display: 'flex', |
| justifyContent: isUser ? 'flex-end' : 'flex-start', |
| width: '100%' |
| }} |
| > |
| <div style={{ |
| maxWidth: '85%', |
| padding: '10px 14px', |
| borderRadius: isUser ? '12px 12px 0 12px' : '12px 12px 12px 0', |
| background: isUser ? 'rgba(212,175,55,0.08)' : 'rgba(255,255,255,0.03)', |
| border: isUser ? '1px solid rgba(212,175,55,0.15)' : '1px solid var(--border-glass)', |
| fontSize: '12.5px', |
| lineHeight: '1.5', |
| color: isUser ? 'var(--text-primary)' : '#e2e8f0', |
| whiteSpace: 'pre-wrap' |
| }}> |
| {!isUser && index === 0 && ( |
| <div style={{ fontSize: '10px', color: 'var(--color-jade-light)', display: 'flex', alignItems: 'center', gap: '4px', marginBottom: '6px', fontWeight: 'bold' }}> |
| <Sparkles size={10} /> CONTEXTUAL ANALYSIS |
| </div> |
| )} |
| {msg.content} |
| </div> |
| </div> |
| ); |
| })} |
| |
| {/* Loading Bubble */} |
| {loading && ( |
| <div style={{ display: 'flex', justifyContent: 'flex-start', width: '100%' }}> |
| <div className="shimmer" style={{ |
| maxWidth: '85%', |
| padding: '12px 24px', |
| borderRadius: '12px 12px 12px 0', |
| border: '1px solid var(--border-glass)', |
| display: 'flex', |
| alignItems: 'center', |
| gap: '8px' |
| }}> |
| <div style={{ display: 'flex', gap: '4px' }}> |
| <span className="pulse-gold-active" style={{ width: '6px', height: '6px', borderRadius: '50%', background: 'var(--color-gold)' }} /> |
| <span className="pulse-gold-active" style={{ width: '6px', height: '6px', borderRadius: '50%', background: 'var(--color-gold)', animationDelay: '0.2s' }} /> |
| <span className="pulse-gold-active" style={{ width: '6px', height: '6px', borderRadius: '50%', background: 'var(--color-gold)', animationDelay: '0.4s' }} /> |
| </div> |
| </div> |
| </div> |
| )} |
| <div ref={messagesEndRef} /> |
| </div> |
| |
| {/* Input Message Form */} |
| <form onSubmit={handleSend} style={{ |
| padding: '12px', |
| background: 'rgba(0,0,0,0.2)', |
| borderTop: '1px solid var(--border-glass)', |
| display: 'flex', |
| gap: '8px' |
| }}> |
| <input |
| type="text" |
| value={input} |
| onChange={(e) => setInput(e.target.value)} |
| placeholder="Ask about Sha Qi, Yin-Yang, or remedies for this spot..." |
| disabled={loading} |
| style={{ |
| flex: 1, |
| padding: '8px 12px', |
| backgroundColor: 'rgba(0,0,0,0.4)', |
| border: '1px solid var(--border-glass)', |
| borderRadius: '6px', |
| color: 'var(--text-primary)', |
| fontSize: '12.5px', |
| fontFamily: 'var(--font-sans)', |
| outline: 'none' |
| }} |
| /> |
| <button |
| type="submit" |
| disabled={loading || !input.trim()} |
| style={{ |
| padding: '8px 14px', |
| backgroundColor: 'var(--color-gold)', |
| color: 'var(--bg-obsidian)', |
| border: 'none', |
| borderRadius: '6px', |
| cursor: 'pointer', |
| display: 'flex', |
| alignItems: 'center', |
| justifyContent: 'center', |
| opacity: (loading || !input.trim()) ? 0.5 : 1, |
| transition: 'var(--transition-smooth)' |
| }} |
| > |
| <Send size={14} /> |
| </button> |
| </form> |
| </div> |
| ); |
| } |
|
|