CHAINR / frontend /src /hooks /useChat.js
chainr-ai's picture
Upload 8536 files
4888678 verified
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();
// Get conversations
const {
data: conversations = [],
isLoading: conversationsLoading,
refetch: refetchConversations
} = useQuery('conversations', () => conversationAPI.getConversations().then(res => res.data.conversations));
// Get messages for current conversation
const {
data: messagesData,
isLoading: messagesLoading,
refetch: refetchMessages
} = useQuery(
['messages', currentConversationId],
() => chatAPI.getMessages(currentConversationId).then(res => res.data.messages),
{
enabled: !!currentConversationId,
onSuccess: (data) => {
setMessages(data);
}
}
);
// Create conversation mutation
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');
}
}
);
// Send message mutation
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');
}
}
);
// Delete message mutation
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');
}
}
);
// Update conversation mutation
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');
}
}
);
// Delete conversation mutation
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');
}
}
);
// Socket event handlers
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]);
// Helper functions
const createConversation = (title = 'New Chat') => {
createConversationMutation.mutate(title);
};
const sendMessage = (content) => {
if (!currentConversationId) {
// Create new conversation first
const title = content.length > 50 ? content.substring(0, 50) + '...' : content;
createConversationMutation.mutate(title);
// The message will be sent after conversation is created
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 {
// Data
conversations,
messages,
currentConversationId,
isTyping,
// Loading states
conversationsLoading,
messagesLoading,
sendingMessage: sendMessageMutation.isLoading,
creatingConversation: createConversationMutation.isLoading,
// Actions
createConversation,
sendMessage,
deleteMessage,
updateConversation,
deleteConversation,
switchConversation,
emitTyping,
// Utilities
refetchConversations,
refetchMessages
};
};