From 5ecb603d872c994c9b3d6652736787abc098b0e0 Mon Sep 17 00:00:00 2001
From: Adam Howard <91115+codeincontext@users.noreply.github.com>
Date: Mon, 2 Dec 2024 20:17:17 +0100
Subject: [PATCH] test: add chat panel stories
---
.../Chat/chat-left-hand-side.tsx | 7 +-
.../AppComponents/Chat/chat-panel.stories.tsx | 178 ++++++++++++++++++
.../AppComponents/Chat/chat-panel.tsx | 19 +-
.../AppComponents/Chat/prompt-form.tsx | 25 +--
.../analytics/lessonPlanTrackingContext.tsx | 2 +-
apps/nextjs/src/lib/hooks/use-sidebar.tsx | 4 +-
6 files changed, 206 insertions(+), 29 deletions(-)
create mode 100644 apps/nextjs/src/components/AppComponents/Chat/chat-panel.stories.tsx
diff --git a/apps/nextjs/src/components/AppComponents/Chat/chat-left-hand-side.tsx b/apps/nextjs/src/components/AppComponents/Chat/chat-left-hand-side.tsx
index ba3fab32e..321b4ad2e 100644
--- a/apps/nextjs/src/components/AppComponents/Chat/chat-left-hand-side.tsx
+++ b/apps/nextjs/src/components/AppComponents/Chat/chat-left-hand-side.tsx
@@ -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";
@@ -52,10 +53,8 @@ const ChatLeftHandSide = ({
)}
-
+
+
);
diff --git a/apps/nextjs/src/components/AppComponents/Chat/chat-panel.stories.tsx b/apps/nextjs/src/components/AppComponents/Chat/chat-panel.stories.tsx
new file mode 100644
index 000000000..c2cff638c
--- /dev/null
+++ b/apps/nextjs/src/components/AppComponents/Chat/chat-panel.stories.tsx
@@ -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 }) => (
+
+
+
+);
+
+const LessonPlanTrackingContextDecorator: Story["decorators"] = (Story) => (
+ {},
+ onClickRetry: () => {},
+ onClickStartFromExample: () => {},
+ onClickStartFromFreeText: () => {},
+ onStreamFinished: () => {},
+ onSubmitText: () => {},
+ }}
+ >
+
+
+);
+
+const SidebarContextDecorator: Story["decorators"] = (Story) => (
+ {},
+ isLoading: false,
+ isSidebarOpen: false,
+ }}
+ >
+
+
+);
+
+const meta: Meta = {
+ title: "Components/Chat/ChatPanel",
+ component: ChatPanel,
+ tags: ["autodocs"],
+ decorators: [
+ ChatDecorator,
+ LessonPlanTrackingContextDecorator,
+ SidebarContextDecorator,
+ ],
+ args: {
+ isDemoLocked: false,
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+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",
+ },
+ },
+};
diff --git a/apps/nextjs/src/components/AppComponents/Chat/chat-panel.tsx b/apps/nextjs/src/components/AppComponents/Chat/chat-panel.tsx
index fca32ed91..867d85fa4 100644
--- a/apps/nextjs/src/components/AppComponents/Chat/chat-panel.tsx
+++ b/apps/nextjs/src/components/AppComponents/Chat/chat-panel.tsx
@@ -1,6 +1,5 @@
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";
@@ -8,7 +7,6 @@ import useAnalytics from "@/lib/analytics/useAnalytics";
import ChatPanelDisclaimer from "./chat-panel-disclaimer";
interface ChatPanelProps {
- isEmptyScreen: boolean;
isDemoLocked: boolean;
}
@@ -18,14 +16,12 @@ function LockedPromptForm() {
);
}
-export function ChatPanel({
- isEmptyScreen,
- isDemoLocked,
-}: Readonly) {
+export function ChatPanel({ isDemoLocked }: Readonly) {
const chat = useLessonChat();
const {
id,
isLoading,
+ messages,
input,
setInput,
append,
@@ -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 (
-
-
+
{!isDemoLocked && (
{
@@ -57,7 +54,7 @@ export function ChatPanel({
input={input}
setInput={setInput}
ailaStreamingStatus={ailaStreamingStatus}
- isEmptyScreen={isEmptyScreen}
+ hasMessages={hasMessages}
queueUserAction={queueUserAction}
queuedUserAction={queuedUserAction}
/>
@@ -73,7 +70,7 @@ export function ChatPanel({
const chatBoxWrap = cva(["mx-auto w-full "], {
variants: {
- isEmptyScreen: {
+ hasMessages: {
false: "max-w-2xl ",
true: "",
},
diff --git a/apps/nextjs/src/components/AppComponents/Chat/prompt-form.tsx b/apps/nextjs/src/components/AppComponents/Chat/prompt-form.tsx
index 04e600d87..b2369b0ca 100644
--- a/apps/nextjs/src/components/AppComponents/Chat/prompt-form.tsx
+++ b/apps/nextjs/src/components/AppComponents/Chat/prompt-form.tsx
@@ -17,8 +17,7 @@ import type { AilaStreamingStatus } from "./Chat/hooks/useAilaStreamingStatus";
export interface PromptFormProps
extends Pick {
onSubmit: (value: string) => void;
- isEmptyScreen: boolean;
- placeholder?: string;
+ hasMessages: boolean;
ailaStreamingStatus: AilaStreamingStatus;
queuedUserAction?: string | null;
queueUserAction?: (action: string) => void;
@@ -29,8 +28,7 @@ export function PromptForm({
onSubmit,
input,
setInput,
- isEmptyScreen,
- placeholder,
+ hasMessages,
queuedUserAction,
queueUserAction,
}: Readonly) {
@@ -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"
@@ -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";
}
diff --git a/apps/nextjs/src/lib/analytics/lessonPlanTrackingContext.tsx b/apps/nextjs/src/lib/analytics/lessonPlanTrackingContext.tsx
index 2f9e9a3af..d664640d4 100644
--- a/apps/nextjs/src/lib/analytics/lessonPlanTrackingContext.tsx
+++ b/apps/nextjs/src/lib/analytics/lessonPlanTrackingContext.tsx
@@ -30,7 +30,7 @@ type LessonPlanTrackingContext = {
onClickStartFromFreeText: (text: string) => void;
};
-const lessonPlanTrackingContext =
+export const lessonPlanTrackingContext =
createContext(null);
export type LessonPlanTrackingProviderProps = Readonly<{
diff --git a/apps/nextjs/src/lib/hooks/use-sidebar.tsx b/apps/nextjs/src/lib/hooks/use-sidebar.tsx
index b95c386ad..eaba67064 100644
--- a/apps/nextjs/src/lib/hooks/use-sidebar.tsx
+++ b/apps/nextjs/src/lib/hooks/use-sidebar.tsx
@@ -17,7 +17,9 @@ export interface SidebarContext {
isLoading: boolean;
}
-const SidebarContext = createContext(undefined);
+export const SidebarContext = createContext(
+ undefined,
+);
export function useSidebar() {
const context = useContext(SidebarContext);