Skip to content

Commit

Permalink
test: add chat panel stories
Browse files Browse the repository at this point in the history
  • Loading branch information
codeincontext committed Dec 2, 2024
1 parent 5f749f4 commit 5ecb603
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";

import { Flex } from "@radix-ui/themes";

import { ButtonScrollToBottom } from "@/components/AppComponents/Chat/button-scroll-to-bottom";
import { useLessonChat } from "@/components/ContextProviders/ChatProvider";
import type { DemoContextProps } from "@/components/ContextProviders/Demo";

Expand Down Expand Up @@ -52,10 +53,8 @@ const ChatLeftHandSide = ({
<QuickActionButtons isEmptyScreen={!!messages.length} />
)}
</div>
<ChatPanel
isEmptyScreen={!!messages.length}
isDemoLocked={isDemoLocked}
/>
<ButtonScrollToBottom />
<ChatPanel isDemoLocked={isDemoLocked} />
<span className="absolute right-0 top-[-70px] z-10 hidden h-[calc(100vh+100px)] w-3 bg-black sm:block" />
</Flex>
);
Expand Down
178 changes: 178 additions & 0 deletions apps/nextjs/src/components/AppComponents/Chat/chat-panel.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import type { Meta, StoryObj } from "@storybook/react";

import {
ChatContext,
type ChatContextProps,
} from "@/components/ContextProviders/ChatProvider";
import { lessonPlanTrackingContext } from "@/lib/analytics/lessonPlanTrackingContext";
import { SidebarContext } from "@/lib/hooks/use-sidebar";

import { ChatPanel } from "./chat-panel";

const DummyMessage = {};

const ChatDecorator: Story["decorators"] = (Story, { parameters }) => (
<ChatContext.Provider
value={
{
messages: [DummyMessage],
...parameters.chatContext,
} as unknown as ChatContextProps
}
>
<Story />
</ChatContext.Provider>
);

const LessonPlanTrackingContextDecorator: Story["decorators"] = (Story) => (
<lessonPlanTrackingContext.Provider
value={{
onClickContinue: () => {},
onClickRetry: () => {},
onClickStartFromExample: () => {},
onClickStartFromFreeText: () => {},
onStreamFinished: () => {},
onSubmitText: () => {},
}}
>
<Story />
</lessonPlanTrackingContext.Provider>
);

const SidebarContextDecorator: Story["decorators"] = (Story) => (
<SidebarContext.Provider
value={{
toggleSidebar: () => {},
isLoading: false,
isSidebarOpen: false,
}}
>
<Story />
</SidebarContext.Provider>
);

const meta: Meta<typeof ChatPanel> = {
title: "Components/Chat/ChatPanel",
component: ChatPanel,
tags: ["autodocs"],
decorators: [
ChatDecorator,
LessonPlanTrackingContextDecorator,
SidebarContextDecorator,
],
args: {
isDemoLocked: false,
},
};

export default meta;
type Story = StoryObj<typeof ChatPanel>;

export const NoMessages: Story = {
args: {},
parameters: {
chatContext: {
messages: [],
},
},
};

export const DemoLocked: Story = {
args: {
isDemoLocked: true,
},
};

export const Idle: Story = {
args: {},
parameters: {
chatContext: {
ailaStreamingStatus: "Idle",
},
},
};

export const IdleWithQueuedUserAction: Story = {
args: {},
parameters: {
chatContext: {
queuedUserAction: "regenerate",
ailaStreamingStatus: "Idle",
},
},
};

export const Loading: Story = {
args: {},
parameters: {
chatContext: {
ailaStreamingStatus: "Loading",
},
},
};

export const RequestMade: Story = {
args: {},
parameters: {
chatContext: {
ailaStreamingStatus: "RequestMade",
},
},
};

export const StreamingLessonPlan: Story = {
args: {},
parameters: {
chatContext: {
ailaStreamingStatus: "StreamingLessonPlan",
},
},
};

export const StreamingChatResponse: Story = {
args: {},
parameters: {
chatContext: {
ailaStreamingStatus: "StreamingChatResponse",
},
},
};

export const StreamingWithQueuedUserAction: Story = {
args: {},
parameters: {
chatContext: {
queuedUserAction: "regenerate",
ailaStreamingStatus: "StreamingLessonPlan",
},
},
};

export const Moderating: Story = {
args: {},
parameters: {
chatContext: {
ailaStreamingStatus: "Moderating",
},
},
};

export const ModeratingWithRegenerateUserAction: Story = {
args: {},
parameters: {
chatContext: {
queuedUserAction: "regenerate",
ailaStreamingStatus: "Moderating",
},
},
};

export const CustomQueuedUserAction: Story = {
args: {},
parameters: {
chatContext: {
queuedUserAction: "Increase the reading age of that section",
ailaStreamingStatus: "Moderating",
},
},
};
19 changes: 8 additions & 11 deletions apps/nextjs/src/components/AppComponents/Chat/chat-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { cva } from "class-variance-authority";

import { ButtonScrollToBottom } from "@/components/AppComponents/Chat/button-scroll-to-bottom";
import { PromptForm } from "@/components/AppComponents/Chat/prompt-form";
import { useLessonChat } from "@/components/ContextProviders/ChatProvider";
import useAnalytics from "@/lib/analytics/useAnalytics";

import ChatPanelDisclaimer from "./chat-panel-disclaimer";

interface ChatPanelProps {
isEmptyScreen: boolean;
isDemoLocked: boolean;
}

Expand All @@ -18,14 +16,12 @@ function LockedPromptForm() {
);
}

export function ChatPanel({
isEmptyScreen,
isDemoLocked,
}: Readonly<ChatPanelProps>) {
export function ChatPanel({ isDemoLocked }: Readonly<ChatPanelProps>) {
const chat = useLessonChat();
const {
id,
isLoading,
messages,
input,
setInput,
append,
Expand All @@ -34,12 +30,13 @@ export function ChatPanel({
queuedUserAction,
} = chat;

const hasMessages = !!messages.length;

const { trackEvent } = useAnalytics();
const containerClass = `grid w-full grid-cols-1 ${isEmptyScreen ? "sm:grid-cols-1" : ""} peer-[[data-state=open]]:group-[]:lg:pl-[250px] peer-[[data-state=open]]:group-[]:xl:pl-[300px]`;
const containerClass = `grid w-full grid-cols-1 ${hasMessages ? "sm:grid-cols-1" : ""} peer-[[data-state=open]]:group-[]:lg:pl-[250px] peer-[[data-state=open]]:group-[]:xl:pl-[300px]`;
return (
<div className={containerClass}>
<ButtonScrollToBottom />
<div className={chatBoxWrap({ isEmptyScreen })}>
<div className={chatBoxWrap({ hasMessages })}>
{!isDemoLocked && (
<PromptForm
onSubmit={async (value) => {
Expand All @@ -57,7 +54,7 @@ export function ChatPanel({
input={input}
setInput={setInput}
ailaStreamingStatus={ailaStreamingStatus}
isEmptyScreen={isEmptyScreen}
hasMessages={hasMessages}
queueUserAction={queueUserAction}
queuedUserAction={queuedUserAction}
/>
Expand All @@ -73,7 +70,7 @@ export function ChatPanel({

const chatBoxWrap = cva(["mx-auto w-full "], {
variants: {
isEmptyScreen: {
hasMessages: {
false: "max-w-2xl ",
true: "",
},
Expand Down
25 changes: 13 additions & 12 deletions apps/nextjs/src/components/AppComponents/Chat/prompt-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ import type { AilaStreamingStatus } from "./Chat/hooks/useAilaStreamingStatus";
export interface PromptFormProps
extends Pick<UseChatHelpers, "input" | "setInput"> {
onSubmit: (value: string) => void;
isEmptyScreen: boolean;
placeholder?: string;
hasMessages: boolean;
ailaStreamingStatus: AilaStreamingStatus;
queuedUserAction?: string | null;
queueUserAction?: (action: string) => void;
Expand All @@ -29,8 +28,7 @@ export function PromptForm({
onSubmit,
input,
setInput,
isEmptyScreen,
placeholder,
hasMessages,
queuedUserAction,
queueUserAction,
}: Readonly<PromptFormProps>) {
Expand Down Expand Up @@ -93,8 +91,8 @@ export function PromptForm({
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder={handlePlaceholder(
isEmptyScreen,
queuedUserAction ?? placeholder,
hasMessages,
queuedUserAction ?? undefined,
)}
spellCheck={false}
className="min-h-[60px] w-full resize-none bg-transparent px-10 py-[1.3rem] text-base focus-within:outline-none"
Expand All @@ -119,11 +117,14 @@ export function PromptForm({
);
}

function handlePlaceholder(isEmptyScreen: boolean, placeholder?: string) {
if (placeholder && !["continue", "regenerate"].includes(placeholder)) {
return placeholder;
function handlePlaceholder(hasMessages: boolean, queuedUserAction?: string) {
if (
queuedUserAction &&
!["continue", "regenerate"].includes(queuedUserAction)
) {
return queuedUserAction;
}
return !isEmptyScreen
? "Type a subject, key stage and title"
: "Type your response here";
return hasMessages
? "Type your response here"
: "Type a subject, key stage and title";
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type LessonPlanTrackingContext = {
onClickStartFromFreeText: (text: string) => void;
};

const lessonPlanTrackingContext =
export const lessonPlanTrackingContext =
createContext<LessonPlanTrackingContext | null>(null);

export type LessonPlanTrackingProviderProps = Readonly<{
Expand Down
4 changes: 3 additions & 1 deletion apps/nextjs/src/lib/hooks/use-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export interface SidebarContext {
isLoading: boolean;
}

const SidebarContext = createContext<SidebarContext | undefined>(undefined);
export const SidebarContext = createContext<SidebarContext | undefined>(
undefined,
);

export function useSidebar() {
const context = useContext(SidebarContext);
Expand Down

0 comments on commit 5ecb603

Please sign in to comment.