Skip to content

Commit

Permalink
Dev/ben 431 (#183)
Browse files Browse the repository at this point in the history
* swr local storage cache

* IndexedDB SWR cache with invalidation.

* Fix build.

* Refactor.

* Update providers.tsx

* Use localstorage cache.

* Remove dep

* Update pnpm-lock.yaml

* Adds unload event listener to handle clearing cache on signout.

---------

Co-authored-by: hyusap <[email protected]>
  • Loading branch information
bLopata and hyusap authored Dec 13, 2024
1 parent bb5edef commit 7c70c4a
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 36 deletions.
51 changes: 24 additions & 27 deletions www/app/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ import { getFreeMessageCount, useFreeTrial } from '@/utils/supabase/actions';
import { getConversations, createConversation } from './actions/conversations';
import { getMessages, addOrRemoveReaction } from './actions/messages';
import { type Message } from '@/utils/types';
import { localStorageProvider } from '@/utils/swrCache';

import useAutoScroll from '@/hooks/autoscroll';

const Thoughts = dynamic(() => import('@/components/thoughts'), {
ssr: false,
});
// import Thoughts from '@/components/thoughts';

const MessageBox = dynamic(() => import('@/components/messagebox'), {
ssr: false,
});
Expand Down Expand Up @@ -90,7 +91,6 @@ interface HonchoResponse {
content: string;
}

// Near the top of the file, add the default message constant
const defaultMessage: Message = {
content: `I'm your Aristotelian learning companion — here to help you follow your curiosity in whatever direction you like. My engineering makes me extremely receptive to your needs and interests. You can reply normally, and I'll always respond!\n\nIf I&apos;m off track, just say so!\n\nNeed to leave or just done chatting? Let me know! I'm conversational by design so I'll say goodbye 😊.`,
isUser: false,
Expand Down Expand Up @@ -150,29 +150,20 @@ export default function Chat({
return getConversations();
};

const { data: conversations, mutate: mutateConversations } = useSWR(
userId,
conversationsFetcher,
{
fallbackData: initialConversations,
onSuccess: async (conversations) => {
if (conversations.length) {
if (
!conversationId ||
!conversations.find((c) => c.conversationId === conversationId)
) {
setConversationId(conversations[0].conversationId);
}
setCanSend(true);
} else {
const newConvo = await createConversation();
setConversationId(newConvo?.conversationId);
await mutateConversations();
}
},
revalidateOnFocus: false,
}
);
const conversationsKey = useMemo(() => userId, [userId]);

const { data: conversations, mutate: mutateConversations } = useSWR(
conversationsKey,
conversationsFetcher,
{
fallbackData: initialConversations,
provider: localStorageProvider,
revalidateOnFocus: false,
dedupingInterval: 60000,
revalidateIfStale: false,
revalidateOnMount: false,
}
);

const messagesFetcher = async (conversationId: string) => {
if (!userId) return Promise.resolve([]);
Expand All @@ -182,23 +173,29 @@ export default function Chat({
return getMessages(conversationId);
};

const messagesKey = useMemo(() =>
conversationId ? ['messages', conversationId] : null,
[conversationId]
);

const {
data: messages,
mutate: mutateMessages,
isLoading: messagesLoading,
} = useSWR(
conversationId ? ['messages', conversationId] : null,
messagesKey,
() => messagesFetcher(conversationId!),
{
fallbackData: initialMessages,
provider: localStorageProvider,
revalidateOnFocus: false,
revalidateOnReconnect: false,
dedupingInterval: 60000,
onSuccess: (data) => {
if (conversationId?.startsWith('temp-')) {
mutateMessages([], false);
}
},
}
}
);

Expand Down
12 changes: 7 additions & 5 deletions www/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import './globals.css';
import 'react-loading-skeleton/dist/skeleton.css';
import type { Metadata } from 'next';
import { Roboto_Mono } from 'next/font/google';
import { PHProvider, PostHogPageview } from './providers';
import { PHProvider, PostHogPageview, SWRProvider } from './providers';
import { Suspense } from 'react';
import { Header } from '@/components/header';
import { ThemeProvider } from 'next-themes';
Expand Down Expand Up @@ -64,10 +64,12 @@ export default function RootLayout({
<PostHogPageview />
</Suspense>
<PHProvider>
<div className="h-screen flex flex-col">
<Header />
<main className="flex-1 overflow-y-auto">{children}</main>
</div>
<SWRProvider>
<div className="h-screen flex flex-col">
<Header />
<main className="flex-1 overflow-y-auto">{children}</main>
</div>
</SWRProvider>
</PHProvider>
</ThemeProvider>
<SpeedInsights />
Expand Down
15 changes: 14 additions & 1 deletion www/app/providers.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// app/providers.tsx
'use client';
import posthog from 'posthog-js';
import { PostHogProvider } from 'posthog-js/react';
import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect } from 'react';
import { SWRConfig } from 'swr';
import { localStorageProvider } from '@/utils/swrCache';

const posthogKey: string = process.env.NEXT_PUBLIC_POSTHOG_KEY || '';
const posthogHost: string = process.env.NEXT_PUBLIC_POSTHOG_HOST || '';
Expand Down Expand Up @@ -40,3 +41,15 @@ export function PostHogPageview(): JSX.Element {
export function PHProvider({ children }: { children: React.ReactNode }) {
return <PostHogProvider client={posthog}>{children}</PostHogProvider>;
}

export function SWRProvider({ children }: { children: React.ReactNode }) {
return (
<SWRConfig
value={{
provider: localStorageProvider
}}
>
{children}
</SWRConfig>
);
}
11 changes: 8 additions & 3 deletions www/components/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,21 @@ import { usePostHog } from 'posthog-js/react';
import Swal from 'sweetalert2';
import { ConversationTab } from './conversationtab';
import { useState } from 'react';
import useSWR, { KeyedMutator } from 'swr';
import useSWR, { KeyedMutator, useSWRConfig } from 'swr';
import { FaUser } from 'react-icons/fa';
import {
createConversation,
deleteConversation,
updateConversation,
} from '@/app/actions/conversations';
import { type Conversation, type Message } from '@/utils/types';
import { clearSWRCache } from '@/utils/swrCache';

const departureMono = localFont({
src: '../fonts/DepartureMono-Regular.woff2',
});


export default function Sidebar({
conversations,
mutateConversations,
Expand All @@ -41,6 +43,7 @@ export default function Sidebar({
const supabase = createClient();
const [isMenuOpen, setIsMenuOpen] = useState(false);
const router = useRouter();
const { mutate } = useSWRConfig();

async function editConversation(cur: Conversation) {
const { value: newName } = await Swal.fire({
Expand Down Expand Up @@ -260,9 +263,11 @@ export default function Sidebar({
</button>
<button
className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 w-full text-left"
onClick={async () => {
onClick={async () => {
clearSWRCache();
mutate(() => true, undefined, { revalidate: false });
await supabase.auth.signOut();
location.reload();
window.location.href = '/';
}}
>
Sign Out
Expand Down
35 changes: 35 additions & 0 deletions www/utils/swrCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Cache, State } from 'swr';

export let unloadListener: ((ev: BeforeUnloadEvent) => void) | null = null;

export function localStorageProvider(): Cache {
if (typeof window !== 'undefined') {
const map: Map<string, State> = new Map(
JSON.parse(localStorage.getItem('app-cache') || '[]')
);

// Remove any existing listener
if (unloadListener) {
window.removeEventListener('beforeunload', unloadListener);
}

unloadListener = () => {
localStorage.setItem('app-cache', JSON.stringify(Array.from(map.entries())));
};
window.addEventListener('beforeunload', unloadListener);

return map;
}
return new Map();
}

export const clearSWRCache = () => {
if (typeof window !== 'undefined') {
// Remove the listener before clearing cache
if (unloadListener) {
window.removeEventListener('beforeunload', unloadListener);
unloadListener = null;
}
localStorage.removeItem('app-cache');
}
};

0 comments on commit 7c70c4a

Please sign in to comment.