first commit

This commit is contained in:
Wind
2026-02-14 23:00:55 +09:00
commit ebd2be03a2
59 changed files with 2532 additions and 0 deletions

100
html/components/ChatBot.tsx Normal file
View File

@@ -0,0 +1,100 @@
import React, { useState, useRef, useEffect } from 'react';
import { chatWithExpert } from '../services/geminiService';
import { Message } from '../types';
const ChatBot: React.FC = () => {
const [isOpen, setIsOpen] = useState(false);
const [input, setInput] = useState('');
const [messages, setMessages] = useState<Message[]>([
{ role: 'model', text: "Welcome to Hanmo! I'm your Technical Assistant. How can I help you with our DCS, SCADA, or field instrument solutions today?" }
]);
const [isLoading, setIsLoading] = useState(false);
const scrollRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [messages, isOpen]);
const handleSend = async () => {
if (!input.trim() || isLoading) return;
const userMsg = input.trim();
setInput('');
setMessages(prev => [...prev, { role: 'user', text: userMsg }]);
setIsLoading(true);
const history = messages.map(m => ({ role: m.role, text: m.text }));
const responseText = await chatWithExpert(userMsg, history);
setMessages(prev => [...prev, { role: 'model', text: responseText }]);
setIsLoading(false);
};
return (
<div className="fixed bottom-6 right-6 z-[60]">
<button
onClick={() => setIsOpen(!isOpen)}
className="w-14 h-14 md:w-16 md:h-16 bg-blue-600 hover:bg-blue-700 text-white rounded-full shadow-2xl flex items-center justify-center transition-transform hover:scale-110 active:scale-95"
>
{isOpen ? <i className="fas fa-times text-2xl"></i> : <i className="fas fa-robot text-2xl"></i>}
</button>
{isOpen && (
<div className="absolute bottom-20 right-0 w-[calc(100vw-3rem)] md:w-96 h-[500px] max-h-[70vh] bg-white rounded-3xl shadow-2xl flex flex-col overflow-hidden border border-slate-200 animate-in fade-in slide-in-from-bottom-5">
<div className="p-5 bg-blue-600 text-white font-bold flex items-center justify-between">
<div className="flex items-center gap-3">
<i className="fas fa-microchip"></i>
<span>Tech Advisor</span>
</div>
<div className="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
</div>
<div ref={scrollRef} className="flex-1 overflow-y-auto p-5 space-y-4 bg-slate-50">
{messages.map((m, i) => (
<div key={i} className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-[85%] p-4 rounded-2xl text-sm leading-relaxed ${
m.role === 'user'
? 'bg-blue-600 text-white rounded-tr-none shadow-md'
: 'bg-white text-slate-700 shadow-sm border border-slate-100 rounded-tl-none'
}`}>
{m.text}
</div>
</div>
))}
{isLoading && (
<div className="flex justify-start">
<div className="bg-white p-4 rounded-2xl border border-slate-100 rounded-tl-none flex gap-1.5 shadow-sm">
<div className="w-1.5 h-1.5 bg-blue-400 rounded-full animate-bounce"></div>
<div className="w-1.5 h-1.5 bg-blue-400 rounded-full animate-bounce [animation-delay:0.2s]"></div>
<div className="w-1.5 h-1.5 bg-blue-400 rounded-full animate-bounce [animation-delay:0.4s]"></div>
</div>
</div>
)}
</div>
<div className="p-4 bg-white border-t border-slate-100 flex gap-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSend()}
placeholder="Ask a technical question..."
className="flex-1 bg-slate-100 text-sm p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-600/20"
/>
<button
onClick={handleSend}
className="bg-blue-600 hover:bg-blue-700 text-white w-12 h-12 rounded-xl flex items-center justify-center transition-colors"
>
<i className="fas fa-paper-plane"></i>
</button>
</div>
</div>
)}
</div>
);
};
export default ChatBot;