Skip to content

Commit

Permalink
chore: working
Browse files Browse the repository at this point in the history
  • Loading branch information
tomwisecodes committed Nov 15, 2024
1 parent 1370379 commit 05b63cf
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 84 deletions.
8 changes: 6 additions & 2 deletions apps/nextjs/src/components/AppComponents/Chat/chat-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@ export const ChatMessagesDisplay = ({

return (
<>
{messages.map((message) => {
{messages.map((message, i) => {
// Check if the most recent message in the messages array is from the role user if so add a working on it message

const isLastMessage = i === messages.length - 1;
if (messages.length === 1) {
return (
<div
Expand All @@ -151,6 +151,7 @@ export const ChatMessagesDisplay = ({
lastModeration={lastModeration}
persistedModerations={persistedModerations}
ailaStreamingStatus={ailaStreamingStatus}
isLastMessage={isLastMessage}
separator={<span className="my-10 flex" />}
/>
<ChatMessage
Expand All @@ -165,6 +166,7 @@ export const ChatMessagesDisplay = ({
persistedModerations={[]}
separator={<span className="my-10 flex" />}
ailaStreamingStatus={ailaStreamingStatus}
isLastMessage={isLastMessage}
/>
</div>
);
Expand Down Expand Up @@ -195,6 +197,7 @@ export const ChatMessagesDisplay = ({
ailaStreamingStatus !== "Idle" ? [] : persistedModerations
}
separator={<span className="my-10 flex" />}
isLastMessage={isLastMessage}
/>
</div>
);
Expand All @@ -212,6 +215,7 @@ export const ChatMessagesDisplay = ({
content: "Working on it…",
}}
lastModeration={lastModeration}
isLastMessage={false}
persistedModerations={[]}
separator={<span className="my-10 flex" />}
/>
Expand Down
110 changes: 97 additions & 13 deletions apps/nextjs/src/components/AppComponents/Chat/chat-message/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Inspired by Chatbot-UI and modified to fit the needs of this project
// @see https://github.com/mckaywrigley/chatbot-ui/blob/main/components/Chat/ChatMessage.tsx
import type { ReactNode } from "react";
import { useState } from "react";
import { useCallback, useMemo, useState } from "react";

import type {
ActionDocument,
Expand All @@ -24,7 +24,10 @@ import type { Message } from "ai";

import { MemoizedReactMarkdownWithStyles } from "@/components/AppComponents/Chat/markdown";
import { useChatModeration } from "@/components/ContextProviders/ChatModerationContext";
import { useLessonChat } from "@/components/ContextProviders/ChatProvider";
import { Icon } from "@/components/Icon";
import { useLessonPlanTracking } from "@/lib/analytics/lessonPlanTrackingContext";
import useAnalytics from "@/lib/analytics/useAnalytics";
import { cn } from "@/lib/utils";

import type { ModerationModalHelpers } from "../../FeedbackForms/ModerationFeedbackModal";
Expand All @@ -40,13 +43,15 @@ export interface ChatMessageProps {
lastModeration?: PersistedModerationBase | null;
separator?: JSX.Element;
ailaStreamingStatus: AilaStreamingStatus;
isLastMessage: boolean;
}

export function ChatMessage({
message,
persistedModerations,
separator,
ailaStreamingStatus,
isLastMessage,
}: Readonly<ChatMessageProps>) {
const { moderationModalHelpers } = useChatModeration();

Expand Down Expand Up @@ -143,16 +148,11 @@ export function ChatMessage({
/>
<MessageTextWrapper>
{message.id !== "working-on-it-initial" &&
messageParts.map((part, index) => {
return (
<div className="w-full" key={index}>
<ChatMessagePart
part={part}
moderationModalHelpers={moderationModalHelpers}
inspect={inspect}
/>
</div>
);
handleTextAndInLineButton({
messageParts,
moderationModalHelpers,
inspect,
isLastMessage,
})}

{message.id === "working-on-it-initial" && (
Expand Down Expand Up @@ -225,12 +225,14 @@ export interface ChatMessagePartProps {
part: MessagePart;
inspect: boolean;
moderationModalHelpers: ModerationModalHelpers;
isLastMessage: boolean;
}

function ChatMessagePart({
part,
inspect,
moderationModalHelpers,
isLastMessage,
}: Readonly<ChatMessagePartProps>) {
const PartComponent = {
comment: CommentMessagePart,
Expand All @@ -247,6 +249,7 @@ function ChatMessagePart({
}[part.document.type] as React.ComponentType<{
part: typeof part.document;
moderationModalHelpers: ModerationModalHelpers;
isLastMessage: boolean;
}>;

if (!PartComponent) {
Expand All @@ -259,6 +262,7 @@ function ChatMessagePart({
<PartComponent
part={part.document}
moderationModalHelpers={moderationModalHelpers}
isLastMessage={isLastMessage}
/>

{
Expand Down Expand Up @@ -299,17 +303,72 @@ function ErrorMessagePart({
return <MemoizedReactMarkdownWithStyles markdown={markdown} />;
}

function TextMessagePart({ part }: Readonly<{ part: TextDocument }>) {
export const InLineButton = ({
text,
onClick,
}: {
text: string;
onClick: () => void;
}) => {
const handleClick = useCallback(() => {
onClick();
}, [onClick]);

return (
<button
onClick={handleClick}
className="my-6 w-fit rounded-lg border border-black border-opacity-30 bg-white p-7 text-blue"
>
{text}
</button>
);
};

function TextMessagePart({
part,
isLastMessage,
}: Readonly<{ part: TextDocument; isLastMessage: boolean }>) {
function containsRelevantList(text: string): boolean {
const relevancePhraseRegex =
/might\s*be\s*relevant|could\s*be\s*relevant|are\s*relevant/i; // Variations of "might be relevant"
const numberedListRegex = /\d+\.\s+[A-Za-z0-9]/; // Matches a numbered list like "1. Something"

return relevancePhraseRegex.test(text) && numberedListRegex.test(text);
}
const chat = useLessonChat();
const { trackEvent } = useAnalytics();
const lessonPlanTracking = useLessonPlanTracking();

const shouldTransformToButtons = containsRelevantList(part.value);
const { queueUserAction, ailaStreamingStatus } = chat;
const handleContinue = useCallback(async () => {
trackEvent("chat:continue");
lessonPlanTracking.onClickContinue();
queueUserAction("continue");
}, [queueUserAction, lessonPlanTracking, trackEvent]);

const shouldTransformToButtons = containsRelevantList(part.value);
// console.log("******isLastMessage", part.value, isLastMessage);
if (part.value.includes("Otherwise, tap")) {
return (
<div className="flex flex-col gap-6">
<MemoizedReactMarkdownWithStyles
markdown={part.value.split("Otherwise, tap")[0] ?? part.value}
shouldTransformToButtons={true}
/>
{ailaStreamingStatus === "Idle" && isLastMessage && (
<InLineButton
text={
part.value.includes("complete the lesson plan") ||
part.value.includes("final step")
? "Proceed to the final step"
: "Proceed to the next step"
}
onClick={() => handleContinue()}
/>
)}
</div>
);
}
return (
<MemoizedReactMarkdownWithStyles
markdown={part.value}
Expand Down Expand Up @@ -353,3 +412,28 @@ function PartInspector({ part }: Readonly<{ part: MessagePart }>) {
</div>
);
}

function handleTextAndInLineButton({
messageParts,
moderationModalHelpers,
inspect,
isLastMessage,
}: {
messageParts: MessagePart[];
moderationModalHelpers: ModerationModalHelpers;
inspect: boolean;
isLastMessage: boolean;
}) {
return messageParts.map((part, index) => {
return (
<div className="w-full" key={part.id || index}>
<ChatMessagePart
part={part}
moderationModalHelpers={moderationModalHelpers}
inspect={inspect}
isLastMessage={isLastMessage}
/>
</div>
);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ const QuickActionButtons = ({ isEmptyScreen }: QuickActionButtonsProps) => {
</ChatButton>
)}
</div>
{/* <ChatButton
<ChatButton
size="sm"
variant="primary"
disabled={!shouldAllowUserAction}
Expand All @@ -135,7 +135,7 @@ const QuickActionButtons = ({ isEmptyScreen }: QuickActionButtonsProps) => {
testId="chat-continue"
>
Continue
</ChatButton> */}
</ChatButton>
</div>
);
};
Expand Down
82 changes: 15 additions & 67 deletions apps/nextjs/src/components/AppComponents/Chat/markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import { Box, Flex } from "@radix-ui/themes";
import remarkGfm from "remark-gfm";

import { useLessonChat } from "@/components/ContextProviders/ChatProvider";
import { useLessonPlanTracking } from "@/lib/analytics/lessonPlanTrackingContext";
import useAnalytics from "@/lib/analytics/useAnalytics";
import { cn } from "@/lib/utils";

import { InLineButton } from "./chat-message";
import { CodeBlock } from "./ui/codeblock";

// Memoized Component
Expand All @@ -31,20 +34,22 @@ export const MemoizedReactMarkdownWithStyles = ({
lessonPlanSectionDescription?: string;
className?: string;
}) => {
// Memoize the chat to avoid re-renders on context updates
const chat = useLessonChat();
const { append } = chat;

// Memoize the markdown content to avoid recalculating if it hasn't changed
const memoizedMarkdown = useMemo(() => markdown, [markdown]);

// UseCallback to avoid inline function recreation
const handleAppend = useCallback(
(content: string) => {
console.log("Appending message:", content);
append({ content, role: "user" });

const chat = useLessonChat();
const { trackEvent } = useAnalytics();

const { queueUserAction, ailaStreamingStatus } = chat;
const handleContinue = useCallback(
async (string) => {
trackEvent(`chat:${string}`);

queueUserAction(string);
},
[append],
[queueUserAction, trackEvent],
);

return (
Expand Down Expand Up @@ -73,7 +78,7 @@ export const MemoizedReactMarkdownWithStyles = ({
<li className="m-0 p-0">
<InLineButton
text={String(children)}
onClick={() => handleAppend(String(children))}
onClick={() => handleContinue(String(children))}
/>
</li>
);
Expand All @@ -83,47 +88,6 @@ export const MemoizedReactMarkdownWithStyles = ({
);
},
p({ children }) {
const isStringChild = (child: any): child is string =>
typeof child === "string";

const textChildren = React.Children.toArray(children)
.filter(isStringChild)
.join("");

if (
textChildren.includes("proceed to the final step") ||
textChildren.includes("move on to the final step") ||
textChildren.includes("complete the lesson plan")
) {
const text = textChildren.split("Otherwise, tap")[0];
return (
<div className="flex flex-col gap-5">
<p className={cn("mb-7 last:mb-0", className)}>{text}</p>
<InLineButton
onClick={() => handleAppend("Proceed to the final step")}
text="Proceed to the final step"
/>
</div>
);
}

if (
textChildren.includes("proceed to the next step") ||
textChildren.includes("move on to the next step") ||
textChildren.includes("Otherwise, tap")
) {
const text = textChildren.split("Otherwise, tap")[0];
return (
<div className="flex flex-col gap-5">
<p className={cn("mb-7 last:mb-0", className)}>{text}</p>
<InLineButton
onClick={() => handleAppend("Proceed to the next step")}
text="Proceed to the next step"
/>
</div>
);
}

return <p className={cn("mb-7 last:mb-0", className)}>{children}</p>;
},

Expand Down Expand Up @@ -197,19 +161,3 @@ export const MemoizedReactMarkdownWithStyles = ({
</MemoizedReactMarkdown>
);
};
const InLineButton = memo(
({ text, onClick }: { text: string; onClick: () => void }) => {
const handleClick = useCallback(() => {
onClick();
}, [onClick]);

return (
<button
onClick={handleClick}
className="my-6 w-fit border-spacing-4 rounded-lg border border-black border-opacity-30 bg-white p-7 text-blue"
>
{text}
</button>
);
},
);

0 comments on commit 05b63cf

Please sign in to comment.