import { useIsMobile } from "@/hooks/useScreen"; import { cn } from "@/utils"; import { MessageSquareQuote, SendIcon } from "lucide-react"; import React, { useEffect, useRef, useState } from "react"; import furinaAvatar from "@/assets/furina-avatar.webp"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { api } from "@/api"; import { ThreeDots } from "react-loader-spinner"; import Markdown from "react-markdown"; const ChatWindow = () => { const containerRef = useRef(null); const moveWindowStateRef = useRef({ isMoving: false, x: 0, y: 0, startX: 0, startY: 0, }); const isMobile = useIsMobile(); const queryClient = useQueryClient(); const [isOpen, setOpen] = useState(false); const { data: messages } = useQuery({ queryKey: ["chats"], queryFn: () => api("/chats"), }); const sendMessage = useMutation({ mutationFn: async (message: string) => { return api("/chats", { method: "POST", body: JSON.stringify({ message }), headers: { "Content-Type": "application/json" }, }); }, onMutate: async (data) => { await queryClient.cancelQueries({ queryKey: ["chats"] }); const prevData = queryClient.getQueryData(["chats"]); // optimistic update queryClient.setQueryData(["chats"], (prev: any) => [ { id: "-1", role: "user", content: data }, ...prev, ]); return { prevData }; }, onError: (_err, _data, ctx) => { queryClient.setQueryData(["chats"], ctx?.prevData); }, onSettled: () => { queryClient.invalidateQueries({ queryKey: ["chats"] }); const msgEl = document.querySelector('[name="message"]') as | HTMLInputElement | undefined; setTimeout(() => msgEl?.focus(), 100); }, }); const onMouseDown = (e: React.MouseEvent) => { if (isMobile) { if (isOpen) { setOpen(false); } return; } if (moveWindowStateRef.current.isMoving) { return; } e.preventDefault(); e.stopPropagation(); moveWindowStateRef.current.isMoving = true; moveWindowStateRef.current.startX = e.clientX - moveWindowStateRef.current.x; moveWindowStateRef.current.startY = e.clientY - moveWindowStateRef.current.y; }; const onMouseMove = (e: MouseEvent) => { const container = containerRef.current; if (isMobile || !moveWindowStateRef.current.isMoving || !container) { return; } const x = e.clientX - moveWindowStateRef.current.startX; const y = e.clientY - moveWindowStateRef.current.startY; moveWindowStateRef.current.x = x; moveWindowStateRef.current.y = y; container.style.transform = `translate(${x}px, ${y}px)`; }; const onMouseUp = () => { moveWindowStateRef.current.isMoving = false; }; const onSubmit = (e: React.FormEvent) => { e.preventDefault(); if (sendMessage.isPending) { return; } const form = e.target as HTMLFormElement; const data = new FormData(form); const message = data.get("message") as string; if (!message?.length) { return; } sendMessage.mutate(message, { onSuccess: () => form.reset(), }); }; useEffect(() => { document.addEventListener("mousemove", onMouseMove); document.addEventListener("mouseup", onMouseUp); document.addEventListener("pointermove", onMouseMove); document.addEventListener("pointerup", onMouseUp); return () => { document.removeEventListener("mousemove", onMouseMove); document.removeEventListener("mouseup", onMouseUp); document.removeEventListener("pointermove", onMouseMove); document.removeEventListener("pointerup", onMouseUp); }; }, []); return ( <>
{sendMessage.isPending && ( } /> )} {sendMessage.isError && (

{getSendChatErrorMessage(sendMessage.error)}

)} {messages?.map((msg: any) => { return ( {msg.content}} /> ); })}
); }; type MessageProps = { isMe?: boolean; role?: string; name: string; children?: React.ReactNode; }; const Message = ({ isMe, role, name, children }: MessageProps) => { return (
{role === "model" && (
)}

{name}

{children}
); }; const getSendChatErrorMessage = (error: Error) => { if (error?.message?.includes("FinishReasonSafety")) { return "Your message probably detected with blocked words, please try again."; } return "An error occured. Please try again."; }; export default ChatWindow;