import { useState, useRef, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Card, CardFooter, CardHeader } from '@/components/ui/card';
import { Bot, X, Send, Sparkles } from 'lucide-react';
import { apiRequest } from '@/lib/queryClient';
import { cn } from '@/lib/utils';
interface Message {
role: 'user' | 'assistant';
content: string;
}
// Typing indicator with animated dots - v2
function TypingIndicator() {
return (
);
}
export function AiChat() {
const { t } = useTranslation();
const queryClient = useQueryClient();
const [isOpen, setIsOpen] = useState(false);
const [input, setInput] = useState('');
const [messages, setMessages] = useState([]);
const [isWaitingForResponse, setIsWaitingForResponse] = useState(false);
const scrollRef = useRef(null);
const mutation = useMutation({
mutationFn: async (msgs: Message[]) => {
const res = await apiRequest("POST", "/api/ai/chat", { messages: msgs });
if (!res.ok) {
const err = await res.json();
throw new Error(err.error || "Failed");
}
return res.json();
},
onSuccess: (data) => {
setMessages(prev => [...prev, data]);
setIsWaitingForResponse(false);
// Refresh tasks and labels in case AI created/modified them
queryClient.invalidateQueries({ queryKey: ['/api/tasks'] });
queryClient.invalidateQueries({ queryKey: ['/api/labels'] });
queryClient.invalidateQueries({ queryKey: ['/api/user'] });
},
onError: (err: Error) => {
setMessages(prev => [...prev, { role: 'assistant', content: t('ai.chat.error', 'Something went wrong') + ": " + err.message }]);
setIsWaitingForResponse(false);
}
});
// Use our own loading state OR mutation.isPending for the indicator
const isLoading = isWaitingForResponse || mutation.isPending;
useEffect(() => {
if (scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [messages, isOpen, isLoading]);
const handleSubmit = (e?: React.FormEvent) => {
e?.preventDefault();
if (!input.trim() || isLoading) return;
const newMsgs: Message[] = [...messages, { role: 'user', content: input }];
setMessages(newMsgs);
setInput('');
setIsWaitingForResponse(true);
mutation.mutate(newMsgs);
};
if (!isOpen) {
return (
);
}
return (
{t('ai.chat.title', 'AI Assistant')}
{isLoading && (
{t('ai.chat.thinking', 'Thinking')}...
)}
{messages.length === 0 && (
{t('ai.chat.welcome', 'Hi! I can help you manage tasks. Try: "Create a task to buy groceries tomorrow"')}
)}
{messages.map((msg, i) => (
))}
{isLoading && (
{t('ai.chat.thinking', 'Thinking')}...
)}
);
}