import "@mantine/core/styles.css"; import { navigate } from "vike/client/router"; import { AppShell, Burger, Group, Image, MantineProvider, NavLink, Title, } from "@mantine/core"; import { IconHome2, IconChevronRight, IconActivity, IconTrash, IconCircle, IconCircleFilled, IconTrashFilled, IconPlus, } from "@tabler/icons-react"; import { useDisclosure } from "@mantine/hooks"; import theme from "./theme.js"; import logoUrl from "../assets/logo.png"; import { useState } from "react"; import { TRPCProvider, useTRPC } from "../trpc/client.js"; import { usePageContext } from "vike-react/usePageContext"; import "./hover.css"; import { QueryClient, QueryClientProvider, useMutation, useQuery, useQueryClient, } from "@tanstack/react-query"; import { createTRPCClient, httpBatchLink, httpSubscriptionLink, splitLink, } from "@trpc/client"; import type { AppRouter } from "../trpc/router.js"; function makeQueryClient() { return new QueryClient({ defaultOptions: { queries: { // With SSR, we usually want to set some default staleTime // above 0 to avoid refetching immediately on the client staleTime: 60 * 1000, }, }, }); } let browserQueryClient: QueryClient | undefined = undefined; function getQueryClient() { if (typeof window === "undefined") { // Server: always make a new query client return makeQueryClient(); } // Browser: make a new query client if we don't already have one // This is very important, so we don't re-make a new client if React // suspends during the initial render. This may not be needed if we // have a suspense boundary BELOW the creation of the query client if (!browserQueryClient) browserQueryClient = makeQueryClient(); return browserQueryClient; } export default function LayoutDefault({ children, }: { children: React.ReactNode; }) { const pageContext = usePageContext(); const { urlPathname } = pageContext; const [opened, { toggle }] = useDisclosure(); const queryClient = getQueryClient(); const [trpc] = useState(() => createTRPCClient({ links: [ splitLink({ // uses the httpSubscriptionLink for subscriptions condition: (op) => op.type === "subscription", true: httpSubscriptionLink({ url: "/api/trpc", }), false: httpBatchLink({ url: "/api/trpc", methodOverride: "POST", }), }), ], }) ); return ( {" "} {" "} Token-Efficient Context Engineering {children} ); } function NavLinkChat() { const pageContext = usePageContext(); const { urlPathname } = pageContext; const trpc = useTRPC(); const queryClient = useQueryClient(); // const const startConversation = useMutation( trpc.chat.conversations.start.mutationOptions({ onSuccess: () => { queryClient.invalidateQueries({ queryKey: trpc.chat.conversations.fetchAll.queryKey(), }); }, }) ); const deleteConversation = useMutation( trpc.chat.conversations.deleteOne.mutationOptions({ onMutate: async ({ id: conversationIdToDelete }) => { /** Cancel affected queries that may be in-flight: */ await queryClient.cancelQueries({ queryKey: trpc.chat.conversations.fetchAll.queryKey(), }); /** Optimistically update the affected queries in react-query's cache: */ const previousConversations = await queryClient.getQueryData( trpc.chat.conversations.fetchAll.queryKey() ); if (!previousConversations) { return { previousConversations: [], newConversations: [], }; } const newConversations = previousConversations.filter( (c) => c.id !== conversationIdToDelete ); queryClient.setQueryData( trpc.chat.conversations.fetchAll.queryKey(), newConversations ); return { previousConversations, newConversations }; }, onSettled: async (data, variables, context) => { await queryClient.invalidateQueries({ queryKey: trpc.chat.conversations.fetchAll.queryKey(), }); }, onError: async (error, variables, context) => { console.error(error); if (!context) return; queryClient.setQueryData( trpc.chat.conversations.fetchAll.queryKey(), context.previousConversations ); }, }) ); const { data: conversations } = useQuery( trpc.chat.conversations.fetchAll.queryOptions() ); // const selectedConversationId = useStore( // (state) => state.selectedConversationId // ); const selectedConversationId = urlPathname.split("/chat/")[1]; async function handleDeleteConversation(conversationIdToDelete: string) { await deleteConversation.mutateAsync( { id: conversationIdToDelete }, { onSuccess: async (x, y, { newConversations }) => { /** If the selected conversation was deleted, navigate/select a * different conversation (creating a new one if necessary): */ if (conversationIdToDelete === selectedConversationId) { if (newConversations && newConversations.length > 0) { // Navigate to the first conversation const lastConversation = newConversations[newConversations.length - 1]; await navigate(`/chat/${lastConversation.id}`); } else { // No conversations left, create a new one const newConversation = await startConversation.mutateAsync(); if (!newConversation?.id) return; await navigate(`/chat/${newConversation.id}`); } } }, } ); } return ( Chats { e.preventDefault(); e.stopPropagation(); startConversation.mutateAsync().then((res) => { if (!res?.id) return; // addConversation(res); navigate(`/chat/${res.id}`); }); }} /> } leftSection={} rightSection={ } variant="subtle" active={urlPathname.startsWith("/chat")} defaultOpened={true} > {conversations?.map((conversation) => ( } rightSection={ <> { e.stopPropagation(); e.preventDefault(); handleDeleteConversation(conversation.id); }} className="show-by-default" /> { e.stopPropagation(); e.preventDefault(); handleDeleteConversation(conversation.id); }} className="show-on-hover border-on-hover" /> } variant="subtle" active={conversation.id === selectedConversationId} /> ))} ); }