Skip to content

Commit 3707706

Browse files
authored
Merge pull request #38 from AET-DevOps25/feature/improve-chat
Implement crud operations for chats
2 parents e05b5e5 + 338beec commit 3707706

File tree

27 files changed

+430
-142
lines changed

27 files changed

+430
-142
lines changed

client/package-lock.json

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"preview": "vite preview"
1111
},
1212
"dependencies": {
13+
"@tanstack/react-query": "^5.80.5",
1314
"lucide-react": "^0.344.0",
1415
"react": "^18.3.1",
1516
"react-dom": "^18.3.1",
@@ -24,6 +25,7 @@
2425
"@types/prop-types": "^15.7.13",
2526
"@types/react": "^18.3.12",
2627
"@types/react-dom": "^18.3.1",
28+
"@vitejs/plugin-react": "^4.3.3",
2729
"autoprefixer": "^10.4.20",
2830
"eslint": "^9.13.0",
2931
"eslint-plugin-react-hooks": "^5.0.0",
@@ -33,7 +35,6 @@
3335
"tailwindcss": "^3.4.16",
3436
"typescript": "~5.6.2",
3537
"typescript-eslint": "^8.11.0",
36-
"vite": "^6.3.5",
37-
"@vitejs/plugin-react": "^4.3.3"
38+
"vite": "^6.3.5"
3839
}
3940
}

client/src/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ function App() {
3838
};
3939
}, [isSidebarOpen]);
4040

41-
const handleNewConversation = () => {
42-
const newConversation = createNewConversation();
41+
const handleNewConversation = async () => {
42+
const newConversation = await createNewConversation();
4343
navigate(`/c/${newConversation.id}`);
4444
};
4545

client/src/components/MessageItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ interface MessageItemProps {
77
}
88

99
const MessageItem: React.FC<MessageItemProps> = ({ message }) => {
10-
const isUser = message.role === "user";
10+
const isUser = message.role === "USER";
1111

1212
return (
1313
<div
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { createContext, ReactNode, useContext } from "react";
2+
import { useAuth } from "../hooks/useAuth";
3+
4+
interface UserContextType {
5+
user: User | null;
6+
isLoading: boolean;
7+
}
8+
9+
const UserContext = createContext<UserContextType | null>(null);
10+
11+
export const UserContextProvider: React.FC<{ children: ReactNode }> = ({
12+
children,
13+
}) => {
14+
const { user, isLoading } = useAuth();
15+
return (
16+
<UserContext.Provider value={{ user, isLoading }}>
17+
{children}
18+
</UserContext.Provider>
19+
);
20+
};
21+
22+
export const useUser = (): UserContextType => {
23+
const context = useContext(UserContext);
24+
if (!context) {
25+
throw new Error("useUser must be used within a UserProvider");
26+
}
27+
return context;
28+
};
29+
export default UserContext;

client/src/hooks/useChat.tsx

Lines changed: 27 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,47 @@
11
import { useState, useCallback } from "react";
2-
import { generateId } from "../utils/helpers";
2+
import { useChats } from "./useChats";
3+
import { useChatUpdate } from "./useChatUpdate";
4+
import { useChatCreate } from "./useChatCreate";
5+
import { useChatDelete } from "./useChatDelete";
36

47
export default function useChat() {
5-
const [conversations, setConversations] = useState<Conversation[]>([
6-
{
7-
id: generateId(),
8-
title: "New conversation",
9-
messages: [],
10-
createdAt: new Date(),
11-
},
12-
]);
13-
const [activeConversationId, setActiveConversationId] = useState<string>(
14-
conversations[0].id
15-
);
8+
const { chats } = useChats();
9+
const [activeConversationId, setActiveConversationId] = useState<
10+
string | null
11+
>(null);
12+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
1613
const [isTyping, setIsTyping] = useState(false);
14+
const { addMessageToChat } = useChatUpdate({});
15+
const { createChat } = useChatCreate({});
16+
const { deleteChat } = useChatDelete({});
1717

1818
const activeConversation =
19-
conversations.find((c) => c.id === activeConversationId) ||
20-
conversations[0];
19+
chats.find((c) => c.id === activeConversationId) || null;
2120

2221
const addMessage = useCallback(
23-
(content: string, role: "user" | "assistant") => {
24-
const newMessage: Message = {
25-
id: generateId(),
26-
content,
27-
role,
28-
timestamp: new Date(),
29-
};
30-
31-
setConversations((prevConversations) =>
32-
prevConversations.map((conversation) =>
33-
conversation.id === activeConversationId
34-
? {
35-
...conversation,
36-
messages: [...conversation.messages, newMessage],
37-
title:
38-
conversation.messages.length === 0 && role === "user"
39-
? content.substring(0, 30) +
40-
(content.length > 30 ? "..." : "")
41-
: conversation.title,
42-
}
43-
: conversation
44-
)
45-
);
46-
47-
if (role === "user") {
48-
// Simulate bot typing
49-
setIsTyping(true);
50-
const typingTimeout = setTimeout(() => {
51-
const responses = [
52-
"I'm an AI assistant here to help you with your questions.",
53-
"That's an interesting question. Let me think about it.",
54-
"I can provide information on a wide range of topics.",
55-
"I'm designed to be helpful, harmless, and honest in my responses.",
56-
"I'm still learning, but I'll do my best to assist you.",
57-
];
58-
const randomResponse =
59-
responses[Math.floor(Math.random() * responses.length)];
60-
addMessage(randomResponse, "assistant");
61-
setIsTyping(false);
62-
}, 1500);
63-
64-
return () => clearTimeout(typingTimeout);
65-
}
22+
(content: string) => {
23+
addMessageToChat({
24+
message: content,
25+
conversationId: activeConversationId || "",
26+
});
6627
},
67-
[activeConversationId]
28+
[activeConversationId, addMessageToChat]
6829
);
6930

70-
const createNewConversation = useCallback(() => {
71-
const newConversation: Conversation = {
72-
id: generateId(),
73-
title: "New conversation",
74-
messages: [],
75-
createdAt: new Date(),
76-
};
77-
78-
setConversations((prev) => [...prev, newConversation]);
79-
setActiveConversationId(newConversation.id);
80-
return newConversation;
81-
}, []);
31+
const createNewConversation = useCallback(
32+
async (title?: string) => await createChat(title ?? "New Conversation"),
33+
[createChat]
34+
);
8235

8336
const deleteConversation = useCallback(
84-
(id: string) => {
85-
setConversations((prev) =>
86-
prev.filter((conversation) => conversation.id !== id)
87-
);
88-
89-
if (activeConversationId === id && conversations.length > 1) {
90-
// Set the active conversation to the most recent one that's not the deleted one
91-
const remainingConversations = conversations.filter((c) => c.id !== id);
92-
setActiveConversationId(remainingConversations[0].id);
93-
}
37+
(conversationId: string) => {
38+
deleteChat({ conversationId });
9439
},
95-
[activeConversationId, conversations]
40+
[deleteChat]
9641
);
9742

9843
return {
99-
conversations,
44+
conversations: chats,
10045
activeConversation,
10146
activeConversationId,
10247
setActiveConversationId,

client/src/hooks/useChatCreate.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useMutation, useQueryClient } from "@tanstack/react-query";
2+
import { useUser } from "../contexts/userContext";
3+
import { createConversation } from "../services/chatService";
4+
5+
interface UseChatCreateProps {
6+
onSuccess?: () => void;
7+
onError?: (error: Error) => void;
8+
}
9+
export const useChatCreate = ({ onSuccess, onError }: UseChatCreateProps) => {
10+
const { user } = useUser();
11+
const queryClient = useQueryClient();
12+
13+
const { mutateAsync, isError, error } = useMutation({
14+
mutationKey: ["chatCreate", user?.id],
15+
mutationFn: (title: string) => createConversation(user!.token, title),
16+
onSuccess: () => {
17+
queryClient.invalidateQueries({ queryKey: ["chats"] });
18+
if (onSuccess) {
19+
onSuccess();
20+
}
21+
},
22+
onError: (error: Error) => {
23+
console.error("Error creating chat:", error);
24+
if (onError) {
25+
onError(error);
26+
}
27+
},
28+
});
29+
30+
return { createChat: mutateAsync, isError, error };
31+
};

client/src/hooks/useChatDelete.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { useMutation, useQueryClient } from "@tanstack/react-query";
2+
import { useUser } from "../contexts/userContext";
3+
import { deleteConversation } from "../services/chatService";
4+
5+
interface UseChatDelete {
6+
onSuccess?: () => void;
7+
onError?: (error: Error) => void;
8+
}
9+
10+
export const useChatDelete = ({ onSuccess, onError }: UseChatDelete) => {
11+
const { user } = useUser();
12+
const queryClient = useQueryClient();
13+
const { mutate, isError, error } = useMutation({
14+
mutationKey: ["chatDelete", user?.id],
15+
mutationFn: ({ conversationId }: { conversationId: string }) =>
16+
deleteConversation(user!.token, conversationId),
17+
onSuccess: () => {
18+
queryClient.invalidateQueries({ queryKey: ["chats"] });
19+
if (onSuccess) {
20+
onSuccess();
21+
}
22+
},
23+
onError: (error: Error) => {
24+
console.error("Error deleting chat:", error);
25+
if (onError) {
26+
onError(error);
27+
}
28+
},
29+
});
30+
31+
return { deleteChat: mutate, isError, error };
32+
};

client/src/hooks/useChatUpdate.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { useMutation, useQueryClient } from "@tanstack/react-query";
2+
import { useUser } from "../contexts/userContext";
3+
import { addMessageToConversation } from "../services/chatService";
4+
5+
interface UseChatUpdateProps {
6+
onSuccess?: () => void;
7+
onError?: (error: Error) => void;
8+
}
9+
10+
export const useChatUpdate = ({ onSuccess, onError }: UseChatUpdateProps) => {
11+
const { user } = useUser();
12+
const queryClient = useQueryClient();
13+
const { mutate, isError, error } = useMutation({
14+
mutationKey: ["chatUpdate", user?.id],
15+
mutationFn: ({
16+
message,
17+
conversationId,
18+
}: {
19+
message: string;
20+
conversationId: string;
21+
}) => addMessageToConversation(user!.token, conversationId, message),
22+
onSuccess: () => {
23+
queryClient.invalidateQueries({ queryKey: ["chats"] });
24+
if (onSuccess) {
25+
onSuccess();
26+
}
27+
},
28+
onError: (error: Error) => {
29+
console.error("Error updating chat:", error);
30+
if (onError) {
31+
onError(error);
32+
}
33+
},
34+
});
35+
36+
return { addMessageToChat: mutate, isError, error };
37+
};

client/src/hooks/useChats.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { useQuery } from "@tanstack/react-query";
2+
import { getConversationsOfUser } from "../services/chatService";
3+
import { useUser } from "../contexts/userContext";
4+
5+
export const useChats = () => {
6+
const { user } = useUser();
7+
const { isLoading, isError, data, error } = useQuery({
8+
queryKey: ["chats"],
9+
queryFn: () => getConversationsOfUser(user!.token),
10+
enabled: !!user,
11+
});
12+
13+
return { isLoading, isError, chats: data ?? [], error };
14+
};

0 commit comments

Comments
 (0)