Skip to content

Commit

Permalink
Merge branch 'main' into ayush/dev-417-2
Browse files Browse the repository at this point in the history
  • Loading branch information
hyusap committed Jan 8, 2025
2 parents 1eb7148 + 96c4345 commit 0af10cc
Show file tree
Hide file tree
Showing 24 changed files with 856 additions and 406 deletions.
168 changes: 80 additions & 88 deletions www/app/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { type Message } from '@/utils/types';
import { localStorageProvider } from '@/utils/swrCache';

import useAutoScroll from '@/hooks/autoscroll';
import MessageList from '@/components/MessageList';
import { MessageListRef } from '@/components/MessageList';

const Thoughts = dynamic(() => import('@/components/thoughts'), {
ssr: false,
Expand Down Expand Up @@ -56,6 +58,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 @@ -66,10 +79,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 @@ -80,9 +89,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 @@ -91,31 +103,21 @@ 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 @@ -139,6 +141,33 @@ export default function Chat({
const turnstileSiteKey = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY || '';
const turnstile = useTurnstile();

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') {
posthog?.identify(initialUserId, { email: initialEmail });
Expand Down Expand Up @@ -267,22 +296,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 All @@ -301,7 +314,7 @@ export default function Chat({
},
];
await mutateMessages(newMessages, { revalidate: false });
scrollToBottom();
messageListRef.current?.scrollToBottom();

await new Promise((resolve) => setTimeout(resolve, 1000));

Expand All @@ -315,7 +328,10 @@ 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 = '';
Expand All @@ -340,6 +356,7 @@ export default function Chat({
conversationId!,
thoughtText
);

const honchoContent = (await new Response(
honchoResponse
).json()) as HonchoResponse;
Expand Down Expand Up @@ -394,20 +411,20 @@ export default function Chat({
{ revalidate: false }
);

scrollToBottom();
messageListRef.current?.scrollToBottom();
}

responseReader.releaseLock();
responseReader = null;

await mutateMessages();
scrollToBottom();
messageListRef.current?.scrollToBottom();
setCanSend(true);
} catch (error) {
console.error('Chat error:', error);
setCanSend(true);
await mutateMessages();
scrollToBottom();
messageListRef.current?.scrollToBottom();
} finally {
// Cleanup
if (thoughtReader) {
Expand Down Expand Up @@ -449,19 +466,19 @@ 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 && (
<button
className={`absolute top-4 left-4 z-30 lg:hidden bg-neon-green text-black rounded-lg p-2 border border-black`}
className={`absolute top-3 left-4 z-30 lg:hidden bg-neon-green text-black rounded-lg p-2 border border-black`}
onClick={() => setIsSidebarOpen(true)}
>
<FiMenu size={24} />
</button>
)}
{!isSubscribed && (
<section className="h-16 lg:h-[72px] w-full bg-neon-green text-black text-center flex items-center justify-center flex-shrink-0">
<section className="h-[63px] w-full bg-neon-green text-black text-center flex items-center justify-center flex-shrink-0">
<p className="lg:ml-0 ml-12">
{freeMessages === 0
? "You've used all your free messages"
Expand All @@ -478,46 +495,19 @@ export default function Chat({
</section>
)}
<div className="flex flex-col flex-grow overflow-hidden bg-secondary">
<section
className="flex-grow overflow-y-auto px-4 lg:px-5 dark:text-white"
ref={messageContainerRef}
>
{messages ? (
[defaultMessage, ...messages].map((message, i) => (
<MessageBox
key={message.id || i}
isUser={message.isUser}
userId={userId}
message={message}
loading={messagesLoading}
conversationId={conversationId}
setThought={setThought}
isThoughtOpen={openThoughtMessageId === message.id}
setIsThoughtsOpen={(isOpen) =>
setIsThoughtsOpen(isOpen, message.id)
}
onReactionAdded={handleReactionAdded}
/>
))
) : (
<MessageBox
isUser={false}
message={{
content: '',
id: '',
isUser: false,
metadata: { reaction: null },
}}
loading={true}
setThought={setThought}
setIsThoughtsOpen={setIsThoughtsOpen}
onReactionAdded={handleReactionAdded}
userId={userId}
conversationId={conversationId}
/>
)}
</section>
<div className="p-3 lg:p-5">
<MessageList
ref={messageListRef}
messages={messages}
defaultMessage={defaultMessage}
userId={userId}
conversationId={conversationId}
messagesLoading={messagesLoading}
handleReactionAdded={handleReactionAdded}
setThoughtParent={setThought}
openThoughtMessageId={openThoughtMessageId}
setIsThoughtsOpen={setIsThoughtsOpen}
/>
<div className="p-3 pb-0 lg:p-5 lg:pb-0">
<form
id="send"
className="flex flex-col items-center gap-3 border-gray-300"
Expand All @@ -543,7 +533,9 @@ export default function Chat({
<textarea
ref={input}
placeholder={
canUseApp ? 'Type a message...' : 'Subscribe to send messages'
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
Expand Down
49 changes: 37 additions & 12 deletions www/app/actions/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,43 @@ export async function getThought(conversationId: string, messageId: string) {
const honchoUser = await getHonchoUser(user.id);

try {
const thoughts = await honcho.apps.users.sessions.metamessages.list(
honchoApp.id,
honchoUser.id,
conversationId,
{
message_id: messageId,
metamessage_type: 'thought',
filter: { type: 'assistant' },
}
);

return thoughts.items[0]?.content || null;
const [thoughts, dialectic] = await Promise.all([
honcho.apps.users.sessions.metamessages.list(
honchoApp.id,
honchoUser.id,
conversationId,
{
message_id: messageId,
metamessage_type: 'thought',
filter: { type: 'assistant' },
}
),
honcho.apps.users.sessions.metamessages.list(
honchoApp.id,
honchoUser.id,
conversationId,
{
message_id: messageId,
metamessage_type: 'honcho',
filter: { type: 'assistant' },
}
),
]);

const thoughtText = thoughts.items[0]?.content;
const dialecticText = dialectic.items[0]?.content;

if (!thoughtText) {
return null;
}

let completeThought = thoughtText;

if (dialecticText) {
completeThought += '\n\nDialectic Response:\n\n' + dialecticText;
}

return completeThought;
} catch (error) {
console.error('Error in getThought:', error);
throw new Error('Internal server error');
Expand Down
18 changes: 16 additions & 2 deletions www/app/api/chat/honcho/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,17 @@ export const runtime = 'nodejs';
export const maxDuration = 100;
export const dynamic = 'force-dynamic'; // always run dynamically

function parseHonchoContent(str: string) {
try {
const match = str.match(/<honcho>(.*?)<\/honcho>/s);
return match ? match[1].trim() : str;
} catch (error) {
return str;
}
}

export async function POST(req: NextRequest) {
const { message, conversationId } = await req.json();
const { message, thought, conversationId } = await req.json();

const userData = await getUserData();

Expand All @@ -17,12 +26,17 @@ export async function POST(req: NextRequest) {

const { appId, userId } = userData;

const query = `Given the following user message: <user>${message}</user> I had the following message: ${parseHonchoContent(thought)}`;

const dialecticQuery = await honcho.apps.users.sessions.chat(
appId,
userId,
conversationId,
{ queries: message }
{ queries: query }
);

console.log('dialecticQuery:', query);
console.log('dialecticQuery Response:', dialecticQuery);

return NextResponse.json({ content: dialecticQuery.content });
}
Loading

0 comments on commit 0af10cc

Please sign in to comment.