initial
This commit is contained in:
206
components/AIChat.tsx
Normal file
206
components/AIChat.tsx
Normal file
@@ -0,0 +1,206 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { Card } from "./ui/card";
|
||||
import { Button } from "./ui/button";
|
||||
import { Input } from "./ui/input";
|
||||
import { Badge } from "./ui/badge";
|
||||
import { Send, Bot, User, Sparkles, FileText } from "lucide-react";
|
||||
|
||||
interface Message {
|
||||
id: string;
|
||||
role: "user" | "assistant";
|
||||
content: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export function AIChat() {
|
||||
const [messages, setMessages] = useState<Message[]>([
|
||||
{
|
||||
id: "1",
|
||||
role: "assistant",
|
||||
content: "Hello! I'm your Labwise AI assistant. I can help you with:\n\n• Chemical safety information\n• Protocol guidance\n• SDS lookups\n• EHS documentation questions\n• Emergency procedures\n\nWhat would you like to know?",
|
||||
timestamp: new Date()
|
||||
}
|
||||
]);
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const [isTyping, setIsTyping] = useState(false);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [messages, isTyping]);
|
||||
|
||||
const mockResponses: { [key: string]: string } = {
|
||||
"acetone": "Acetone (CAS 67-64-1) is a flammable liquid. Key safety considerations:\n\n• Store in flammables cabinet away from ignition sources\n• Use in well-ventilated areas or fume hood\n• Wear nitrile gloves and safety goggles\n• Keep away from oxidizing agents\n• Flash point: -20°C\n\nWould you like me to pull up the full SDS?",
|
||||
"disposal": "For chemical waste disposal:\n\n1. Segregate waste by compatibility class\n2. Label all waste containers with contents and hazards\n3. Use proper waste containers (no food/beverage containers)\n4. Contact EHS for pickup when containers are 80% full\n5. Never pour chemicals down the drain unless specifically approved\n\nWhat type of waste are you disposing of?",
|
||||
"ppe": "Personal Protective Equipment (PPE) requirements depend on the chemicals and procedures:\n\n**Minimum PPE:**\n• Safety glasses or goggles\n• Lab coat\n• Closed-toe shoes\n• Long pants\n\n**Additional PPE may include:**\n• Chemical-resistant gloves (nitrile, neoprene, etc.)\n• Face shield for splash hazards\n• Respirator for specific vapors\n\nWhat chemicals are you working with?",
|
||||
"default": "I can help you with that! Based on your question, here are some key points:\n\n• Always refer to the Safety Data Sheet (SDS) for specific chemical hazards\n• Ensure proper PPE is worn at all times\n• Work in well-ventilated areas or fume hoods when handling volatile chemicals\n• Follow your lab's standard operating procedures\n\nWould you like more specific information about a particular chemical or procedure?"
|
||||
};
|
||||
|
||||
const handleSend = () => {
|
||||
if (!inputValue.trim()) return;
|
||||
|
||||
const userMessage: Message = {
|
||||
id: Date.now().toString(),
|
||||
role: "user",
|
||||
content: inputValue,
|
||||
timestamp: new Date()
|
||||
};
|
||||
|
||||
setMessages(prev => [...prev, userMessage]);
|
||||
setInputValue("");
|
||||
setIsTyping(true);
|
||||
|
||||
setTimeout(() => {
|
||||
const lowercaseInput = inputValue.toLowerCase();
|
||||
let response = mockResponses.default;
|
||||
|
||||
if (lowercaseInput.includes("acetone")) {
|
||||
response = mockResponses.acetone;
|
||||
} else if (lowercaseInput.includes("disposal") || lowercaseInput.includes("waste")) {
|
||||
response = mockResponses.disposal;
|
||||
} else if (lowercaseInput.includes("ppe") || lowercaseInput.includes("protective equipment")) {
|
||||
response = mockResponses.ppe;
|
||||
}
|
||||
|
||||
const assistantMessage: Message = {
|
||||
id: (Date.now() + 1).toString(),
|
||||
role: "assistant",
|
||||
content: response,
|
||||
timestamp: new Date()
|
||||
};
|
||||
|
||||
setMessages(prev => [...prev, assistantMessage]);
|
||||
setIsTyping(false);
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
const quickQuestions = [
|
||||
"What PPE do I need for handling acids?",
|
||||
"How do I dispose of organic solvents?",
|
||||
"What are the hazards of acetone?",
|
||||
"Where can I find the SDS for benzene?"
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="p-8 h-full flex flex-col">
|
||||
<div className="max-w-5xl mx-auto w-full flex flex-col h-full">
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="p-2 bg-accent rounded-lg">
|
||||
<Sparkles className="w-6 h-6 text-primary" />
|
||||
</div>
|
||||
<h1 className="text-foreground">AI Safety Assistant</h1>
|
||||
</div>
|
||||
<p className="text-muted-foreground">Ask questions about chemical safety, protocols, and lab procedures</p>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col min-h-0">
|
||||
{/* Messages */}
|
||||
<Card className="flex-1 p-6 mb-4 overflow-y-auto">
|
||||
<div className="space-y-6">
|
||||
{messages.map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
className={`flex gap-4 ${
|
||||
message.role === "user" ? "flex-row-reverse" : ""
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0 ${
|
||||
message.role === "user"
|
||||
? "bg-secondary"
|
||||
: "bg-accent"
|
||||
}`}
|
||||
>
|
||||
{message.role === "user" ? (
|
||||
<User className="w-5 h-5 text-primary" />
|
||||
) : (
|
||||
<Bot className="w-5 h-5 text-primary" />
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`flex-1 max-w-2xl ${
|
||||
message.role === "user" ? "text-right" : ""
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`inline-block p-4 rounded-lg ${
|
||||
message.role === "user"
|
||||
? "bg-secondary text-foreground"
|
||||
: "bg-muted text-foreground"
|
||||
}`}
|
||||
>
|
||||
<p className="whitespace-pre-wrap">{message.content}</p>
|
||||
</div>
|
||||
<p className="text-muted-foreground mt-1 px-4">
|
||||
{message.timestamp.toLocaleTimeString()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{isTyping && (
|
||||
<div className="flex gap-4">
|
||||
<div className="w-10 h-10 rounded-full bg-accent flex items-center justify-center">
|
||||
<Bot className="w-5 h-5 text-primary" />
|
||||
</div>
|
||||
<div className="bg-muted p-4 rounded-lg">
|
||||
<div className="flex gap-2">
|
||||
<div className="w-2 h-2 bg-muted-foreground rounded-full animate-bounce" />
|
||||
<div className="w-2 h-2 bg-muted-foreground rounded-full animate-bounce [animation-delay:0.2s]" />
|
||||
<div className="w-2 h-2 bg-muted-foreground rounded-full animate-bounce [animation-delay:0.4s]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Quick Questions */}
|
||||
{messages.length === 1 && (
|
||||
<div className="mb-4">
|
||||
<p className="text-muted-foreground mb-3">Quick questions:</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{quickQuestions.map((question, idx) => (
|
||||
<Button
|
||||
key={idx}
|
||||
variant="outline"
|
||||
onClick={() => setInputValue(question)}
|
||||
className="text-left h-auto py-2"
|
||||
>
|
||||
{question}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Input */}
|
||||
<Card className="p-4">
|
||||
<div className="flex gap-3">
|
||||
<Input
|
||||
placeholder="Ask about chemical safety, protocols, or procedures..."
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onKeyPress={(e) => e.key === "Enter" && handleSend()}
|
||||
className="flex-1"
|
||||
/>
|
||||
<Button
|
||||
onClick={handleSend}
|
||||
disabled={!inputValue.trim() || isTyping}
|
||||
className="bg-primary hover:bg-primary/90"
|
||||
>
|
||||
<Send className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user