Skip to content

Commit

Permalink
Merge branch 'main' into vineeth/dev-544
Browse files Browse the repository at this point in the history
  • Loading branch information
VVoruganti committed Dec 27, 2024
2 parents 4c56c7b + 6120bb8 commit 97e796c
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 105 deletions.
101 changes: 52 additions & 49 deletions www/app/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ async function fetchStream(
});

if (!response.ok) {
if (response.status === 402) {
Swal.fire({
title: 'Subscription Required',
text: 'You have no active subscription. Subscribe to continue using Bloom!',
icon: 'warning',
confirmButtonColor: '#3085d6',
confirmButtonText: 'Subscribe',
showCancelButton: false,
});
throw new Error(`Subscription is required to chat: ${response.status}`);
}
const errorText = await response.text();
console.error(`Stream error for ${type}:`, {
status: response.status,
Expand All @@ -67,10 +78,6 @@ async function fetchStream(
throw new Error(`Failed to fetch ${type} stream: ${response.status}`);
}

if (!response.body) {
throw new Error(`No response body for ${type} stream`);
}

return response.body;
} catch (error) {
console.error(`Error in fetchStream (${type}):`, error);
Expand All @@ -81,9 +88,12 @@ async function fetchStream(
interface ChatProps {
initialUserId: string;
initialEmail: string | undefined;
initialIsSubscribed: boolean;
initialFreeMessages: number;
initialConversations: any[];
initialChatAccess: {
isSubscribed: boolean;
freeMessages: number;
canChat: boolean;
};
initialMessages: any[];
initialConversationId: string | null | undefined;
}
Expand All @@ -92,31 +102,17 @@ interface HonchoResponse {
content: string;
}

const defaultMessage: Message = {
content: `I’m Bloom, your Aristotelian learning companion, here to guide your intellectual journey.
The more we chat, the more I learn about you as a person. That helps me adapt to your interests and needs.
What’s on your mind? Let’s dive in. 🌱`,
isUser: false,
id: '',
metadata: {},
};

export default function Chat({
initialUserId,
initialEmail,
initialIsSubscribed,
initialFreeMessages,
initialConversations,
initialMessages,
initialConversationId,
initialChatAccess,
}: ChatProps) {
const [userId] = useState(initialUserId);
const [isSubscribed, setIsSubscribed] = useState(initialIsSubscribed);
const [freeMessages, setFreeMessages] = useState(initialFreeMessages);
const [isSubscribed, setIsSubscribed] = useState(initialChatAccess.isSubscribed);
const [freeMessages, setFreeMessages] = useState(initialChatAccess.freeMessages);
const [conversationId, setConversationId] = useState<string | undefined>(
initialConversationId || undefined
);
Expand All @@ -137,6 +133,26 @@ export default function Chat({
const [, scrollToBottom] = useAutoScroll(messageContainerRef);

const messageListRef = useRef<MessageListRef>(null);
const firstChat = useMemo(() => {
return !initialConversations?.length ||
(initialConversations.length === 1 && !initialMessages?.length) ||
initialChatAccess.freeMessages === 50;
}, [initialConversations?.length, initialMessages?.length, initialChatAccess.freeMessages]);

// Since this message is just rendered in the UI, this naive check may result in edge cases where the incorrect message is shown.
// (Ex. will show on all chats after creating a new session or messaging Bloom, even the first chat).
// Also, clearing chats will revert the message to the initial description.
const defaultMessage: Message = {
content:
`${firstChat ? 'I\'m Bloom, your Aristotelian learning companion,' : 'Welcome back! I\'m'} here to guide your intellectual journey.
The more we chat, the more I learn about you as a person. That helps me adapt to your interests and needs.
What\'s on your mind? Let\'s dive in. 🌱`,
isUser: false,
id: '',
metadata: {},
};

useEffect(() => {
if (typeof window !== 'undefined') {
Expand Down Expand Up @@ -193,7 +209,7 @@ export default function Chat({
revalidateOnFocus: false,
dedupingInterval: 60000,
revalidateIfStale: false,
revalidateOnMount: true,
revalidateOnMount: true
}
);

Expand Down Expand Up @@ -266,22 +282,6 @@ export default function Chat({

if (input.current) input.current.value = '';

// Check free message allotment upfront if not subscribed
if (!isSubscribed) {
const currentCount = await getFreeMessageCount(userId);
if (currentCount <= 0) {
Swal.fire({
title: 'Free Messages Depleted',
text: 'You have used all your free messages. Subscribe to continue using Bloom!',
icon: 'warning',
confirmButtonColor: '#3085d6',
confirmButtonText: 'Subscribe',
showCancelButton: true,
});
return;
}
}

setCanSend(false);

const newMessages = [
Expand Down Expand Up @@ -314,8 +314,11 @@ export default function Chat({
messageToSend,
conversationId!
);
if (!thoughtStream) throw new Error('Failed to get thought stream');


if (!thoughtStream) {
throw new Error('Failed to get thought stream');
}

thoughtReader = thoughtStream.getReader();
let thoughtText = '';
setThought('');
Expand All @@ -339,10 +342,11 @@ export default function Chat({
conversationId!,
thoughtText
);

const honchoContent = (await new Response(
honchoResponse
).json()) as HonchoResponse;

const pureThought = thoughtText;

thoughtText +=
Expand Down Expand Up @@ -448,7 +452,7 @@ export default function Chat({
setConversationId={setConversationId}
isSidebarOpen={isSidebarOpen}
toggleSidebar={() => setIsSidebarOpen(!isSidebarOpen)}
isSubscribed={canUseApp}
canUseApp={canUseApp}
/>
<div className="flex-1 flex flex-col flex-grow overflow-hidden">
{!isSidebarOpen && (
Expand Down Expand Up @@ -506,11 +510,10 @@ export default function Chat({
placeholder={
canUseApp ? 'Type a message...' : 'Subscribe to send messages'
}
className={`flex-1 px-3 py-1 lg:px-5 lg:py-3 bg-accent text-gray-400 rounded-2xl border-2 resize-none outline-none focus:outline-none ${
canSend && canUseApp
? 'border-green-200 focus:border-green-200'
: 'border-red-200 focus:border-red-200 opacity-50'
}`}
className={`flex-1 px-3 py-1 lg:px-5 lg:py-3 bg-accent text-gray-400 rounded-2xl border-2 resize-none outline-none focus:outline-none ${canSend && canUseApp
? 'border-green-200 focus:border-green-200'
: 'border-red-200 focus:border-red-200 opacity-50'
}`}
rows={1}
disabled={!canUseApp}
onKeyDown={(e) => {
Expand Down
20 changes: 17 additions & 3 deletions www/app/api/chat/thought/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
} from '@/utils/ai';
import { honcho } from '@/utils/honcho';
import { thoughtPrompt } from '@/utils/prompts/thought';
import { createClient } from '@/utils/supabase/server';
import { getChatAccessWithUser } from '@/utils/supabase/actions';
import { NextRequest, NextResponse } from 'next/server';

export const runtime = 'nodejs';
Expand All @@ -19,13 +21,25 @@ interface ThoughtCallProps {
}

export async function POST(req: NextRequest) {
const supabase = createClient();
const honchoUserData = await getUserData();
const { message, conversationId } = (await req.json()) as ThoughtCallProps;

const userData = await getUserData();
if (!userData) {
const {
data: { user: supabaseUser },
} = await supabase.auth.getUser();

if (!honchoUserData || !supabaseUser) {
return new NextResponse('Unauthorized', { status: 401 });
}
const { appId, userId } = userData;

const { canChat } = await getChatAccessWithUser(supabaseUser.id);

if (!canChat) {
return new NextResponse('Subscription required', { status: 402 });
}

const { appId, userId } = honchoUserData;

const messageIter = await honcho.apps.users.sessions.messages.list(
appId,
Expand Down
30 changes: 11 additions & 19 deletions www/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { getSubscription } from '@/utils/supabase/queries';
import { getChatAccessWithUser } from '@/utils/supabase/actions';
import { createClient } from '@/utils/supabase/server';
import { redirect } from 'next/navigation';
import Chat from './Chat';
import { getFreeMessageCount } from '@/utils/supabase/actions';
import { getConversations } from './actions/conversations';
import { getMessages } from './actions/messages';
import { type Message } from '@/utils/types';
Expand All @@ -22,20 +21,14 @@ export default async function Home() {
}

// Get initial subscription state
let isSubscribed = false;
let freeMessages = 0;

if (process.env.NEXT_PUBLIC_STRIPE_ENABLED === 'false') {
isSubscribed = true;
} else {
const sub = await getSubscription(supabase);
// Only consider active paid subscriptions, not trials
isSubscribed = !!(sub && sub.status === 'active' && !sub.trial_end);

if (!isSubscribed) {
freeMessages = await getFreeMessageCount(user.id);
}
}
const realChatAccess = await getChatAccessWithUser(user.id);
const isDevMode = process.env.NEXT_PUBLIC_STRIPE_ENABLED === 'false';

const chatAccess = {
isSubscribed: isDevMode ? true : realChatAccess.isSubscribed,
freeMessages: realChatAccess.freeMessages,
canChat: isDevMode ? true : (realChatAccess.isSubscribed || realChatAccess.freeMessages > 0)
};

// Get initial conversations
const conversations = await getConversations();
Expand All @@ -45,7 +38,7 @@ export default async function Home() {
let initialConversationId: string | undefined = undefined;
if (conversations.length > 0) {
initialConversationId = conversations[0].conversationId;
initialMessages = await getMessages(initialConversationId);
initialMessages = await getMessages(initialConversationId!);
}

return (
Expand All @@ -54,11 +47,10 @@ export default async function Home() {
<Chat
initialUserId={user.id}
initialEmail={user.email}
initialIsSubscribed={isSubscribed}
initialFreeMessages={freeMessages}
initialConversations={conversations}
initialMessages={initialMessages}
initialConversationId={initialConversationId}
initialChatAccess={chatAccess}
/>
</>
);
Expand Down
8 changes: 6 additions & 2 deletions www/components/markdownWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,11 @@ const CodeBlock = memo(
}
);

const MarkdownWrapper = memo(({ text }: { text: string }) => {
// Memoize components configuration
interface MarkdownWrapperProps {
text: string;
}

const MarkdownWrapper = memo(({ text }: MarkdownWrapperProps) => {
const components = useMemo(
() => ({
ol: ({
Expand Down Expand Up @@ -115,6 +118,7 @@ const MarkdownWrapper = memo(({ text }: { text: string }) => {

if (!text) return <Typing />;


return (
<Suspense fallback={<div className="animate-pulse bg-gray-100 h-32" />}>
<ReactMarkdown
Expand Down
6 changes: 3 additions & 3 deletions www/components/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ export default function Sidebar({
setConversationId,
isSidebarOpen,
toggleSidebar,
isSubscribed,
canUseApp,
}: {
conversations: Conversation[];
mutateConversations: KeyedMutator<Conversation[]>;
conversationId: string | undefined;
setConversationId: (id: typeof conversationId) => void;
isSidebarOpen: boolean;
toggleSidebar: () => void;
isSubscribed: boolean;
canUseApp: boolean;
}) {
const postHog = usePostHog();
const supabase = createClient();
Expand Down Expand Up @@ -178,7 +178,7 @@ export default function Sidebar({
<button
className="bg-neon-green text-black rounded-lg px-4 py-2 w-full lg:w-full h-10"
onClick={addChat}
disabled={!isSubscribed}
disabled={!canUseApp}
>
New Chat
</button>
Expand Down
25 changes: 19 additions & 6 deletions www/utils/supabase/actions.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
'use server'; // This directive marks all exports as server actions

import {
createOrRetrieveFreeTrialSubscription,
decrementFreeMessages,
} from '@/utils/supabase/admin';
import { createFreeTrialSubscription, decrementFreeMessages } from './admin';
import { createClient } from './server';
import { getChatAccess, getSubscription } from './queries';

export async function getChatAccessWithUser(userId: string) {
const subscription = await createOrRetrieveFreeTrialSubscription(userId);
return getChatAccess(subscription);
}

export async function createOrRetrieveFreeTrialSubscription(userId: string) {
const supabase = createClient();
const existingSub = await getSubscription(supabase);
if (existingSub) return existingSub;

// If no subscription exists, create one with admin privileges
return createFreeTrialSubscription(userId);
}

export async function getFreeMessageCount(userId: string) {
const subscription = await createOrRetrieveFreeTrialSubscription(userId);
return (subscription.metadata as { freeMessages: number })?.freeMessages ?? 0;
return (subscription?.metadata as { freeMessages: number })?.freeMessages ?? 0;
}

export async function useFreeTrial(userId: string) {
return decrementFreeMessages(userId);
}
}
Loading

0 comments on commit 97e796c

Please sign in to comment.