added basic embed checks

This commit is contained in:
Nate Kelley 2025-10-01 14:52:11 -06:00
parent ac19b16362
commit 88e3e1e6c5
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
9 changed files with 130 additions and 96 deletions

View File

@ -0,0 +1,9 @@
import { useMatch, useMatches } from '@tanstack/react-router';
export const useIsEmbed = () => {
const match = useMatch({
from: '/embed',
});
console.log(match);
return !!match?.id;
};

View File

@ -1,10 +1,21 @@
import { getRouteApi, type RouteContext, useParams, useSearch } from '@tanstack/react-router'; import type { AssetType } from '@buster/server-shared/assets';
import {
type StaticDataRouteOption,
useMatches,
useParams,
useSearch,
} from '@tanstack/react-router';
import findLast from 'lodash/findLast';
const assetRouteApi = getRouteApi('/app/_app/_asset'); export const useSelectedAssetType = (): NonNullable<StaticDataRouteOption['assetType']> => {
const matches = useMatches();
const stableCtxSelector = (ctx: RouteContext) => ctx.assetType; const lastMatch = findLast(matches, (match) => match.staticData?.assetType);
export const useSelectedAssetType = () => { if (typeof lastMatch === 'number') {
const data = assetRouteApi.useRouteContext({ select: stableCtxSelector }); return 'chat';
}
// @ts-expect-error - lastMatch is not undefined
const data = lastMatch?.staticData?.assetType as StaticDataRouteOption['assetType'];
const { messageId } = useParams({ const { messageId } = useParams({
strict: false, strict: false,
}); });

View File

@ -5,21 +5,23 @@ import { ChatHeader } from './ChatHeader';
export const CHAT_CONTAINER_ID = 'chat-container-content'; export const CHAT_CONTAINER_ID = 'chat-container-content';
export const ChatContainer = React.memo(({ chatId }: { chatId: string | undefined }) => { export const ChatContainer = React.memo(
return ( ({ chatId, isEmbed }: { chatId: string | undefined; isEmbed: boolean }) => {
<AppPageLayout return (
headerSizeVariant="default" <AppPageLayout
header={<ChatHeader />} headerSizeVariant="default"
headerBorderVariant="ghost" header={<ChatHeader isEmbed={isEmbed} />}
headerClassName="bg-page-background" headerBorderVariant="ghost"
mainClassName="bg-page-background" headerClassName="bg-page-background"
scrollable mainClassName="bg-page-background"
id={CHAT_CONTAINER_ID} scrollable
className="flex h-full w-full min-w-[295px] flex-col" id={CHAT_CONTAINER_ID}
> className="flex h-full w-full min-w-[295px] flex-col"
<ChatContent chatId={chatId} /> >
</AppPageLayout> <ChatContent chatId={chatId} isEmbed={isEmbed} />
); </AppPageLayout>
}); );
}
);
ChatContainer.displayName = 'ChatContainer'; ChatContainer.displayName = 'ChatContainer';

View File

@ -12,61 +12,65 @@ import { FollowUpChatInput } from './FollowupChatInput';
const autoClass = 'mx-auto max-w-[600px] w-full'; const autoClass = 'mx-auto max-w-[600px] w-full';
export const ChatContent: React.FC<{ chatId: string | undefined }> = React.memo(({ chatId }) => { export const ChatContent: React.FC<{ chatId: string | undefined; isEmbed: boolean }> = React.memo(
const chatMessageIds = useGetChatMessageIds(chatId); ({ chatId, isEmbed }) => {
const containerRef = useRef<HTMLElement>(null); const chatMessageIds = useGetChatMessageIds(chatId);
const containerRef = useRef<HTMLElement>(null);
const { isAutoScrollEnabled, isMountedAutoScrollObserver, scrollToBottom, enableAutoScroll } = const { isAutoScrollEnabled, isMountedAutoScrollObserver, scrollToBottom, enableAutoScroll } =
useAutoScroll(containerRef, { useAutoScroll(containerRef, {
observeSubTree: true, observeSubTree: true,
enabled: false, enabled: false,
});
useMount(() => {
const container = document
.getElementById(CHAT_CONTAINER_ID)
?.querySelector(`.${SCROLL_AREA_VIEWPORT_CLASS}`) as HTMLElement;
if (!container) return;
containerRef.current = container;
enableAutoScroll();
}); });
useMount(() => { const showScrollToBottomButton = isMountedAutoScrollObserver && containerRef.current;
const container = document
.getElementById(CHAT_CONTAINER_ID)
?.querySelector(`.${SCROLL_AREA_VIEWPORT_CLASS}`) as HTMLElement;
if (!container) return;
containerRef.current = container;
enableAutoScroll();
});
const showScrollToBottomButton = isMountedAutoScrollObserver && containerRef.current; return (
<>
<div
className={cn(
'mb-48 flex h-full w-full flex-col',
!isMountedAutoScrollObserver && 'invisible'
)}
>
<ClientOnly>
{chatMessageIds?.map((messageId, index) => (
<div key={messageId} className={autoClass}>
<ChatMessageBlock
key={messageId}
messageId={messageId}
chatId={chatId || ''}
messageIndex={index}
/>
</div>
))}
</ClientOnly>
</div>
return ( {!isEmbed && (
<> <ChatInputWrapper>
<div {showScrollToBottomButton && (
className={cn( <ScrollToBottomButton
'mb-48 flex h-full w-full flex-col', isAutoScrollEnabled={isAutoScrollEnabled}
!isMountedAutoScrollObserver && 'invisible' scrollToBottom={scrollToBottom}
)} className={'absolute -top-10'}
>
<ClientOnly>
{chatMessageIds?.map((messageId, index) => (
<div key={messageId} className={autoClass}>
<ChatMessageBlock
key={messageId}
messageId={messageId}
chatId={chatId || ''}
messageIndex={index}
/> />
</div> )}
))} </ChatInputWrapper>
</ClientOnly>
</div>
<ChatInputWrapper>
{showScrollToBottomButton && (
<ScrollToBottomButton
isAutoScrollEnabled={isAutoScrollEnabled}
scrollToBottom={scrollToBottom}
className={'absolute -top-10'}
/>
)} )}
</ChatInputWrapper> </>
</> );
); }
}); );
ChatContent.displayName = 'ChatContent'; ChatContent.displayName = 'ChatContent';

View File

@ -89,18 +89,20 @@ export const ChatMessageOptions: React.FC<{
/> />
</AppTooltip> </AppTooltip>
)} )}
<AppTooltip title="Report message"> {canEditChat && (
<Button <AppTooltip title="Report message">
variant="ghost" <Button
prefix={feedback === 'negative' ? <ThumbsDownFilled /> : <ThumbsDown />} variant="ghost"
onClick={() => prefix={feedback === 'negative' ? <ThumbsDownFilled /> : <ThumbsDown />}
updateChatMessageFeedback({ onClick={() =>
message_id: messageId, updateChatMessageFeedback({
feedback: feedback === 'negative' ? null : 'negative', message_id: messageId,
}) feedback: feedback === 'negative' ? null : 'negative',
} })
/> }
</AppTooltip> />
</AppTooltip>
)}
{postProcessingMessage && ( {postProcessingMessage && (
<AppTooltip title="View assumptions"> <AppTooltip title="View assumptions">

View File

@ -4,7 +4,7 @@ import { ChatHeaderTitle } from '@/components/features/chat/ChatHeaderTitle';
import { useGetActiveChatTitle, useIsStreamingMessage } from '@/context/Chats'; import { useGetActiveChatTitle, useIsStreamingMessage } from '@/context/Chats';
import { useGetChatId } from '@/context/Chats/useGetChatId'; import { useGetChatId } from '@/context/Chats/useGetChatId';
export const ChatHeader: React.FC = React.memo(() => { export const ChatHeader: React.FC<{ isEmbed: boolean }> = React.memo(({ isEmbed }) => {
const chatId = useGetChatId(); const chatId = useGetChatId();
const chatTitle = useGetActiveChatTitle(); const chatTitle = useGetActiveChatTitle();
const isStreamingMessage = useIsStreamingMessage(); const isStreamingMessage = useIsStreamingMessage();
@ -16,7 +16,7 @@ export const ChatHeader: React.FC = React.memo(() => {
chatId={chatId || ''} chatId={chatId || ''}
isStreamingMessage={isStreamingMessage} isStreamingMessage={isStreamingMessage}
/> />
<ChatHeaderOptions /> {!isEmbed && <ChatHeaderOptions />}
</> </>
); );
}); });

View File

@ -5,6 +5,7 @@ import {
type AppSplitterRef, type AppSplitterRef,
type LayoutSize, type LayoutSize,
} from '@/components/ui/layouts/AppSplitter'; } from '@/components/ui/layouts/AppSplitter';
import { useIsEmbed } from '@/context/BusterAssets/useIsEmbed';
import { useGetCurrentMessageId, useIsStreamingMessage } from '@/context/Chats'; import { useGetCurrentMessageId, useIsStreamingMessage } from '@/context/Chats';
import { useGetChatId } from '@/context/Chats/useGetChatId'; import { useGetChatId } from '@/context/Chats/useGetChatId';
import type { LayoutMode } from '@/layouts/ChatLayout/config'; import type { LayoutMode } from '@/layouts/ChatLayout/config';
@ -38,6 +39,7 @@ export const ChatLayout: React.FC<ChatSplitterProps> = ({
const selectedAssetId = useSelectedAssetId(); const selectedAssetId = useSelectedAssetId();
const currentMessageId = useGetCurrentMessageId() || ''; const currentMessageId = useGetCurrentMessageId() || '';
const chatId = useGetChatId(); const chatId = useGetChatId();
const isEmbed = useIsEmbed();
const isStreamingMessage = useIsStreamingMessage(); const isStreamingMessage = useIsStreamingMessage();
const leftPanelMinSize = selectedAssetId ? DEFAULT_CHAT_OPTION_SIDEBAR_SIZE : '0px'; const leftPanelMinSize = selectedAssetId ? DEFAULT_CHAT_OPTION_SIDEBAR_SIZE : '0px';
@ -57,7 +59,7 @@ export const ChatLayout: React.FC<ChatSplitterProps> = ({
return ( return (
<AppSplitter <AppSplitter
ref={appSplitterRef} ref={appSplitterRef}
leftChildren={renderLeftPanel && <ChatContainer chatId={chatId} />} leftChildren={renderLeftPanel && <ChatContainer chatId={chatId} isEmbed={isEmbed} />}
rightChildren={renderRightPanel && children} rightChildren={renderRightPanel && children}
autoSaveId={autoSaveId} autoSaveId={autoSaveId}
defaultLayout={defaultLayout} defaultLayout={defaultLayout}

View File

@ -5,6 +5,7 @@ import { Text } from '@/components/ui/typography';
import { getSupabaseSession } from '@/integrations/supabase/getSupabaseUserClient'; import { getSupabaseSession } from '@/integrations/supabase/getSupabaseUserClient';
import { signInWithAnonymousUser } from '@/integrations/supabase/signIn'; import { signInWithAnonymousUser } from '@/integrations/supabase/signIn';
import { AppAssetCheckLayout } from '@/layouts/AppAssetCheckLayout'; import { AppAssetCheckLayout } from '@/layouts/AppAssetCheckLayout';
import { cn } from '@/lib/classMerge';
export const Route = createFileRoute('/embed')({ export const Route = createFileRoute('/embed')({
beforeLoad: async ({ context, matches }) => { beforeLoad: async ({ context, matches }) => {
@ -36,18 +37,15 @@ function RouteComponent() {
); );
} }
if (assetType === 'chat') { const isChat = assetType === 'chat';
return (
<div className="flex h-full w-full items-center justify-center">
<Text className="text-lg">
Sharing a chat is not supported yet... But it is on our roadmap!
</Text>
</div>
);
}
return ( return (
<main className="h-full w-full bg-page-background overflow-y-auto"> <main
className={cn(
'h-full w-full bg-page-background overflow-y-auto',
isChat && 'overflow-y-hidden bg-background-secondary'
)}
>
<AppAssetCheckLayout assetType={assetType}> <AppAssetCheckLayout assetType={assetType}>
<Outlet /> <Outlet />
</AppAssetCheckLayout> </AppAssetCheckLayout>

View File

@ -1,10 +1,16 @@
import { createFileRoute } from '@tanstack/react-router'; import { createFileRoute, Outlet } from '@tanstack/react-router';
import * as chatLayoutServerContext from '@/context/BusterAssets/chat-server/chatLayoutServer'; import * as chatLayoutServerContext from '@/context/BusterAssets/chat-server/chatLayoutServer';
export const Route = createFileRoute('/embed/chat/$chatId')({ export const Route = createFileRoute('/embed/chat/$chatId')({
...chatLayoutServerContext, ...chatLayoutServerContext,
ssr: false, ssr: false,
component: () => { component: () => {
return <div>Hello "/embed/chat/$chatId"!</div>; return (
<div className="h-full w-full p-2 max-h-[100vh] ">
<div className="h-full w-full border rounded bg-background">
{chatLayoutServerContext.component()}
</div>
</div>
);
}, },
}); });