From fc6f6d0883e1147bb79a162aa48990e72cab67f8 Mon Sep 17 00:00:00 2001 From: aalimsahin Date: Fri, 3 Jan 2025 03:08:01 +0300 Subject: [PATCH 1/2] refactor: modularize mutations --- client/src/Chat.tsx | 46 +++----------- client/src/api/index.ts | 2 + client/src/api/mutations/index.ts | 1 + .../src/api/mutations/sendMessageMutation.ts | 60 +++++++++++++++++++ client/src/api/routes.ts | 4 ++ client/src/api/types.ts | 13 ++++ client/src/components/app-sidebar.tsx | 3 +- 7 files changed, 90 insertions(+), 39 deletions(-) create mode 100644 client/src/api/index.ts create mode 100644 client/src/api/mutations/index.ts create mode 100644 client/src/api/mutations/sendMessageMutation.ts create mode 100644 client/src/api/routes.ts create mode 100644 client/src/api/types.ts diff --git a/client/src/Chat.tsx b/client/src/Chat.tsx index e1744c866e..73ba52dc1b 100644 --- a/client/src/Chat.tsx +++ b/client/src/Chat.tsx @@ -1,17 +1,11 @@ import { useRef, useState } from "react"; import { useParams } from "react-router-dom"; -import { useMutation } from "@tanstack/react-query"; import { Button } from "@/components/ui/button"; import { ImageIcon } from "lucide-react"; import { Input } from "@/components/ui/input"; +import type { TextResponse } from "@/api"; +import { useSendMessageMutation } from "@/api"; import "./App.css"; -import path from "path"; - -type TextResponse = { - text: string; - user: string; - attachments?: { url: string; contentType: string; title: string }[]; -}; export default function Chat() { const { agentId } = useParams(); @@ -19,33 +13,11 @@ export default function Chat() { const [messages, setMessages] = useState([]); const [selectedFile, setSelectedFile] = useState(null); const fileInputRef = useRef(null); - - const mutation = useMutation({ - mutationFn: async (text: string) => { - const formData = new FormData(); - formData.append("text", text); - formData.append("userId", "user"); - formData.append("roomId", `default-room-${agentId}`); - - if (selectedFile) { - formData.append("file", selectedFile); - } - - const res = await fetch(`/api/${agentId}/message`, { - method: "POST", - body: formData, - }); - return res.json() as Promise; - }, - onSuccess: (data) => { - setMessages((prev) => [...prev, ...data]); - setSelectedFile(null); - }, - }); + const { mutate: sendMessage, isPending } = useSendMessageMutation({ setMessages, setSelectedFile }); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - if (!input.trim() && !selectedFile) return; + if (!input.trim() && !selectedFile || !agentId) return; // Add user message immediately to state const userMessage: TextResponse = { @@ -55,7 +27,7 @@ export default function Chat() { }; setMessages((prev) => [...prev, userMessage]); - mutation.mutate(input); + sendMessage({ text: input, agentId, selectedFile }); setInput(""); }; @@ -133,19 +105,19 @@ export default function Chat() { onChange={(e) => setInput(e.target.value)} placeholder="Type a message..." className="flex-1" - disabled={mutation.isPending} + disabled={isPending} /> - {selectedFile && ( diff --git a/client/src/api/index.ts b/client/src/api/index.ts new file mode 100644 index 0000000000..0c2adeab02 --- /dev/null +++ b/client/src/api/index.ts @@ -0,0 +1,2 @@ +export * from "./mutations"; +export * from "./queries"; diff --git a/client/src/api/mutations/index.ts b/client/src/api/mutations/index.ts new file mode 100644 index 0000000000..ca9f0653dc --- /dev/null +++ b/client/src/api/mutations/index.ts @@ -0,0 +1 @@ +export * from "./sendMessageMutation"; diff --git a/client/src/api/mutations/sendMessageMutation.ts b/client/src/api/mutations/sendMessageMutation.ts new file mode 100644 index 0000000000..500e19d2e1 --- /dev/null +++ b/client/src/api/mutations/sendMessageMutation.ts @@ -0,0 +1,60 @@ +import type { CustomMutationResult } from "../types"; + +import { useMutation } from "@tanstack/react-query"; +import { ROUTES } from "../routes"; +import { SetStateAction } from "react"; + +export type TextResponse = { + text: string; + user: string; + attachments?: { url: string; contentType: string; title: string }[]; +}; + +type SendMessageMutationProps = { + text: string; + agentId: string; + selectedFile: File | null; +}; + +type Props = Required<{ + setMessages: (value: SetStateAction) => void; + setSelectedFile: (value: SetStateAction) => void; +}>; + +export const useSendMessageMutation = ({ + setMessages, + setSelectedFile, +}: Props): CustomMutationResult => { + const mutation = useMutation({ + mutationFn: async ({ + text, + agentId, + selectedFile, + }: SendMessageMutationProps) => { + const formData = new FormData(); + formData.append("text", text); + formData.append("userId", "user"); + formData.append("roomId", `default-room-${agentId}`); + + if (selectedFile) { + formData.append("file", selectedFile); + } + + const res = await fetch(ROUTES.sendMessage(agentId), { + method: "POST", + body: formData, + }); + + return res.json() as Promise; + }, + onSuccess: (data) => { + setMessages((prev) => [...prev, ...data]); + setSelectedFile(null); + }, + onError: (error) => { + console.error("[useSendMessageMutation]:", error); + }, + }); + + return mutation; +}; diff --git a/client/src/api/routes.ts b/client/src/api/routes.ts new file mode 100644 index 0000000000..1005a61a72 --- /dev/null +++ b/client/src/api/routes.ts @@ -0,0 +1,4 @@ +export const ROUTES = { + sendMessage: (agentId: string): string => `/api/${agentId}/message`, + getAgents: (): string => `/api/agents`, +}; diff --git a/client/src/api/types.ts b/client/src/api/types.ts new file mode 100644 index 0000000000..286daf64b5 --- /dev/null +++ b/client/src/api/types.ts @@ -0,0 +1,13 @@ +import type { UseMutationResult, UseQueryResult } from "@tanstack/react-query"; + +export type CustomMutationResult = UseMutationResult< + TData, + Error, + TArgs, + unknown +>; + +export type CustomQueryResult = Omit< + UseQueryResult, + "data" | "refetch" | "promise" +> & { data: TData; refetch: () => void; promise: unknown }; diff --git a/client/src/components/app-sidebar.tsx b/client/src/components/app-sidebar.tsx index 5245ad8feb..ac8d661c15 100644 --- a/client/src/components/app-sidebar.tsx +++ b/client/src/components/app-sidebar.tsx @@ -1,4 +1,4 @@ -import { Calendar, Home, Inbox, Search, Settings } from "lucide-react"; +import { Calendar, Inbox } from "lucide-react"; import { useParams } from "react-router-dom"; import { @@ -10,7 +10,6 @@ import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, - SidebarTrigger, } from "@/components/ui/sidebar"; // Menu items. From 4a8e9765290ef9217e55d024a7b0c7274a4f642a Mon Sep 17 00:00:00 2001 From: aalimsahin Date: Fri, 3 Jan 2025 03:08:34 +0300 Subject: [PATCH 2/2] refactor: modularize queries --- client/src/Agents.tsx | 16 ++------------ client/src/api/queries/index.ts | 1 + client/src/api/queries/queries.ts | 3 +++ client/src/api/queries/useGetAgentsQuery.ts | 23 +++++++++++++++++++++ 4 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 client/src/api/queries/index.ts create mode 100644 client/src/api/queries/queries.ts create mode 100644 client/src/api/queries/useGetAgentsQuery.ts diff --git a/client/src/Agents.tsx b/client/src/Agents.tsx index 06e2c56b49..90de2fca66 100644 --- a/client/src/Agents.tsx +++ b/client/src/Agents.tsx @@ -1,23 +1,11 @@ -import { useQuery } from "@tanstack/react-query"; import { Button } from "@/components/ui/button"; import { useNavigate } from "react-router-dom"; +import { useGetAgentsQuery } from "@/api"; import "./App.css"; -type Agent = { - id: string; - name: string; -}; - function Agents() { const navigate = useNavigate(); - const { data: agents, isLoading } = useQuery({ - queryKey: ["agents"], - queryFn: async () => { - const res = await fetch("/api/agents"); - const data = await res.json(); - return data.agents as Agent[]; - }, - }); + const { data: agents, isLoading } = useGetAgentsQuery() return (
diff --git a/client/src/api/queries/index.ts b/client/src/api/queries/index.ts new file mode 100644 index 0000000000..1b1c08c1e9 --- /dev/null +++ b/client/src/api/queries/index.ts @@ -0,0 +1 @@ +export * from "./useGetAgentsQuery"; diff --git a/client/src/api/queries/queries.ts b/client/src/api/queries/queries.ts new file mode 100644 index 0000000000..40253fe29d --- /dev/null +++ b/client/src/api/queries/queries.ts @@ -0,0 +1,3 @@ +export enum Queries { + AGENTS = "agents", +} diff --git a/client/src/api/queries/useGetAgentsQuery.ts b/client/src/api/queries/useGetAgentsQuery.ts new file mode 100644 index 0000000000..88f91ff7e7 --- /dev/null +++ b/client/src/api/queries/useGetAgentsQuery.ts @@ -0,0 +1,23 @@ +import { useQuery } from "@tanstack/react-query"; +import type { CustomQueryResult } from "../types"; +import { Queries } from "./queries"; +import { ROUTES } from "../routes"; + +export type Agent = { + id: string; + name: string; +}; + +export const useGetAgentsQuery = (): CustomQueryResult => { + return useQuery({ + queryKey: [Queries.AGENTS], + queryFn: async () => { + const res = await fetch(ROUTES.getAgents()); + const data = await res.json(); + return data.agents as Agent[]; + }, + retry: (failureCount) => failureCount < 3, + staleTime: 5 * 60 * 1000, // 5 minutes + refetchOnWindowFocus: false, + }); +};