Skip to content

Commit

Permalink
Merge pull request #1713 from aalimsahin/refactor/client-react-query
Browse files Browse the repository at this point in the history
refactor: client api
  • Loading branch information
shakkernerd authored Jan 4, 2025
2 parents b79cd60 + 6d7012b commit 395c2d6
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 51 deletions.
16 changes: 2 additions & 14 deletions client/src/Agents.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="min-h-screen flex flex-col items-center justify-center p-4">
Expand Down
45 changes: 9 additions & 36 deletions client/src/Chat.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useMutation } from "@tanstack/react-query";
import type { TextResponse } from "@/api";
import { useSendMessageMutation } from "@/api";
import { ImageIcon } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { useParams } from "react-router-dom";
import "./App.css";

type TextResponse = {
text: string;
user: string;
attachments?: { url: string; contentType: string; title: string }[];
};

export default function Chat() {
const { agentId } = useParams();
const [input, setInput] = useState("");
const [messages, setMessages] = useState<TextResponse[]>([]);
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
const { mutate: sendMessage, isPending } = useSendMessageMutation({ setMessages, setSelectedFile });

const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
Expand All @@ -28,32 +24,9 @@ export default function Chat() {
scrollToBottom();
}, [messages]);

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<TextResponse[]>;
},
onSuccess: (data) => {
setMessages((prev) => [...prev, ...data]);
setSelectedFile(null);
},
});

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 = {
Expand All @@ -63,7 +36,7 @@ export default function Chat() {
};
setMessages((prev) => [...prev, userMessage]);

mutation.mutate(input);
sendMessage({ text: input, agentId, selectedFile });
setInput("");
};

Expand Down Expand Up @@ -142,19 +115,19 @@ export default function Chat() {
onChange={(e) => setInput(e.target.value)}
placeholder="Type a message..."
className="flex-1"
disabled={mutation.isPending}
disabled={isPending}
/>
<Button
type="button"
variant="outline"
size="icon"
onClick={handleFileSelect}
disabled={mutation.isPending}
disabled={isPending}
>
<ImageIcon className="h-4 w-4" />
</Button>
<Button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? "..." : "Send"}
<Button type="submit" disabled={isPending}>
{isPending ? "..." : "Send"}
</Button>
</form>
{selectedFile && (
Expand Down
2 changes: 2 additions & 0 deletions client/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./mutations";
export * from "./queries";
1 change: 1 addition & 0 deletions client/src/api/mutations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./sendMessageMutation";
60 changes: 60 additions & 0 deletions client/src/api/mutations/sendMessageMutation.ts
Original file line number Diff line number Diff line change
@@ -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<TextResponse[]>) => void;
setSelectedFile: (value: SetStateAction<File | null>) => void;
}>;

export const useSendMessageMutation = ({
setMessages,
setSelectedFile,
}: Props): CustomMutationResult<TextResponse[], SendMessageMutationProps> => {
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<TextResponse[]>;
},
onSuccess: (data) => {
setMessages((prev) => [...prev, ...data]);
setSelectedFile(null);
},
onError: (error) => {
console.error("[useSendMessageMutation]:", error);
},
});

return mutation;
};
1 change: 1 addition & 0 deletions client/src/api/queries/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./useGetAgentsQuery";
3 changes: 3 additions & 0 deletions client/src/api/queries/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum Queries {
AGENTS = "agents",
}
23 changes: 23 additions & 0 deletions client/src/api/queries/useGetAgentsQuery.ts
Original file line number Diff line number Diff line change
@@ -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<Agent[] | undefined> => {
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,
});
};
4 changes: 4 additions & 0 deletions client/src/api/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const ROUTES = {
sendMessage: (agentId: string): string => `/api/${agentId}/message`,
getAgents: (): string => `/api/agents`,
};
13 changes: 13 additions & 0 deletions client/src/api/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { UseMutationResult, UseQueryResult } from "@tanstack/react-query";

export type CustomMutationResult<TData, TArgs = unknown> = UseMutationResult<
TData,
Error,
TArgs,
unknown
>;

export type CustomQueryResult<TData, TArgs = unknown> = Omit<
UseQueryResult<TData, TArgs>,
"data" | "refetch" | "promise"
> & { data: TData; refetch: () => void; promise: unknown };
2 changes: 1 addition & 1 deletion client/src/components/app-sidebar.tsx
Original file line number Diff line number Diff line change
@@ -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 { ThemeToggle } from "@/components/theme-toggle";

Expand Down

0 comments on commit 395c2d6

Please sign in to comment.