| | import { useState, useEffect } from 'react';
|
| | import { useQuery, useMutation, useQueryClient } from 'react-query';
|
| | import { conversationAPI, chatAPI } from '../services/api';
|
| | import { useSocket } from './useSocket';
|
| | import toast from 'react-hot-toast';
|
| |
|
| | export const useChat = () => {
|
| | const [currentConversationId, setCurrentConversationId] = useState(null);
|
| | const [messages, setMessages] = useState([]);
|
| | const [isTyping, setIsTyping] = useState(false);
|
| | const queryClient = useQueryClient();
|
| | const socket = useSocket();
|
| |
|
| |
|
| | const {
|
| | data: conversations = [],
|
| | isLoading: conversationsLoading,
|
| | refetch: refetchConversations
|
| | } = useQuery('conversations', () => conversationAPI.getConversations().then(res => res.data.conversations));
|
| |
|
| |
|
| | const {
|
| | data: messagesData,
|
| | isLoading: messagesLoading,
|
| | refetch: refetchMessages
|
| | } = useQuery(
|
| | ['messages', currentConversationId],
|
| | () => chatAPI.getMessages(currentConversationId).then(res => res.data.messages),
|
| | {
|
| | enabled: !!currentConversationId,
|
| | onSuccess: (data) => {
|
| | setMessages(data);
|
| | }
|
| | }
|
| | );
|
| |
|
| |
|
| | const createConversationMutation = useMutation(
|
| | (title) => conversationAPI.createConversation(title),
|
| | {
|
| | onSuccess: (response) => {
|
| | const newConversation = response.data.conversation;
|
| | setCurrentConversationId(newConversation._id);
|
| | queryClient.invalidateQueries('conversations');
|
| | toast.success('New conversation created');
|
| | },
|
| | onError: (error) => {
|
| | toast.error(error.response?.data?.error || 'Failed to create conversation');
|
| | }
|
| | }
|
| | );
|
| |
|
| |
|
| | const sendMessageMutation = useMutation(
|
| | ({ conversationId, content }) => chatAPI.sendMessage(conversationId, content),
|
| | {
|
| | onSuccess: (response) => {
|
| | const { userMessage, assistantMessage } = response.data;
|
| | setMessages(prev => [...prev, userMessage, assistantMessage]);
|
| | queryClient.invalidateQueries('conversations');
|
| | },
|
| | onError: (error) => {
|
| | toast.error(error.response?.data?.error || 'Failed to send message');
|
| | }
|
| | }
|
| | );
|
| |
|
| |
|
| | const deleteMessageMutation = useMutation(
|
| | (messageId) => chatAPI.deleteMessage(messageId),
|
| | {
|
| | onSuccess: () => {
|
| | refetchMessages();
|
| | queryClient.invalidateQueries('conversations');
|
| | toast.success('Message deleted');
|
| | },
|
| | onError: (error) => {
|
| | toast.error(error.response?.data?.error || 'Failed to delete message');
|
| | }
|
| | }
|
| | );
|
| |
|
| |
|
| | const updateConversationMutation = useMutation(
|
| | ({ id, updates }) => conversationAPI.updateConversation(id, updates),
|
| | {
|
| | onSuccess: () => {
|
| | queryClient.invalidateQueries('conversations');
|
| | toast.success('Conversation updated');
|
| | },
|
| | onError: (error) => {
|
| | toast.error(error.response?.data?.error || 'Failed to update conversation');
|
| | }
|
| | }
|
| | );
|
| |
|
| |
|
| | const deleteConversationMutation = useMutation(
|
| | (id) => conversationAPI.deleteConversation(id),
|
| | {
|
| | onSuccess: () => {
|
| | if (currentConversationId === id) {
|
| | setCurrentConversationId(null);
|
| | setMessages([]);
|
| | }
|
| | queryClient.invalidateQueries('conversations');
|
| | toast.success('Conversation deleted');
|
| | },
|
| | onError: (error) => {
|
| | toast.error(error.response?.data?.error || 'Failed to delete conversation');
|
| | }
|
| | }
|
| | );
|
| |
|
| |
|
| | useEffect(() => {
|
| | if (!socket) return;
|
| |
|
| | const handleNewMessage = (data) => {
|
| | const { userMessage, assistantMessage } = data;
|
| | setMessages(prev => [...prev, userMessage, assistantMessage]);
|
| | queryClient.invalidateQueries('conversations');
|
| | };
|
| |
|
| | const handleUserTyping = (data) => {
|
| | if (data.conversationId === currentConversationId) {
|
| | setIsTyping(data.isTyping);
|
| | }
|
| | };
|
| |
|
| | socket.on('newMessage', handleNewMessage);
|
| | socket.on('userTyping', handleUserTyping);
|
| |
|
| | return () => {
|
| | socket.off('newMessage', handleNewMessage);
|
| | socket.off('userTyping', handleUserTyping);
|
| | };
|
| | }, [socket, currentConversationId, queryClient]);
|
| |
|
| |
|
| | const createConversation = (title = 'New Chat') => {
|
| | createConversationMutation.mutate(title);
|
| | };
|
| |
|
| | const sendMessage = (content) => {
|
| | if (!currentConversationId) {
|
| |
|
| | const title = content.length > 50 ? content.substring(0, 50) + '...' : content;
|
| | createConversationMutation.mutate(title);
|
| |
|
| | return;
|
| | }
|
| |
|
| | sendMessageMutation.mutate({
|
| | conversationId: currentConversationId,
|
| | content
|
| | });
|
| | };
|
| |
|
| | const deleteMessage = (messageId) => {
|
| | deleteMessageMutation.mutate(messageId);
|
| | };
|
| |
|
| | const updateConversation = (id, updates) => {
|
| | updateConversationMutation.mutate({ id, updates });
|
| | };
|
| |
|
| | const deleteConversation = (id) => {
|
| | deleteConversationMutation.mutate(id);
|
| | };
|
| |
|
| | const switchConversation = (conversationId) => {
|
| | setCurrentConversationId(conversationId);
|
| | setMessages([]);
|
| | };
|
| |
|
| | const emitTyping = (isTyping) => {
|
| | if (socket && currentConversationId) {
|
| | socket.emit('typing', {
|
| | conversationId: currentConversationId,
|
| | isTyping
|
| | });
|
| | }
|
| | };
|
| |
|
| | return {
|
| |
|
| | conversations,
|
| | messages,
|
| | currentConversationId,
|
| | isTyping,
|
| |
|
| |
|
| | conversationsLoading,
|
| | messagesLoading,
|
| | sendingMessage: sendMessageMutation.isLoading,
|
| | creatingConversation: createConversationMutation.isLoading,
|
| |
|
| |
|
| | createConversation,
|
| | sendMessage,
|
| | deleteMessage,
|
| | updateConversation,
|
| | deleteConversation,
|
| | switchConversation,
|
| | emitTyping,
|
| |
|
| |
|
| | refetchConversations,
|
| | refetchMessages
|
| | };
|
| | }; |