Skip to content

Commit

Permalink
New encourage message UI (#3130)
Browse files Browse the repository at this point in the history
  • Loading branch information
notmd and andreaskoepf authored May 12, 2023
1 parent 9739bc2 commit 895b662
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 78 deletions.
4 changes: 3 additions & 1 deletion website/public/locales/en/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,7 @@
"save_preset": "Save this preset",
"preset_exists_error": "A preset with this name already exists",
"preset_name_placeholder": "Enter name",
"feedback_message": "Thoughts? Let us know!"
"feedback_message": "How did I do? Your feedback will make me better!",
"feedback_action_great": "Good",
"feedback_action_poor": "Could be better"
}
10 changes: 8 additions & 2 deletions website/src/components/Chat/ChatConversation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export const ChatConversation = memo(function ChatConversation({ chatId, getConf
const [streamedResponse, setResponse] = useState<string | null>(null);
const [queueInfo, setQueueInfo] = useState<QueueInfo | null>(null);
const [isSending, setIsSending] = useBoolean();
const [showEncourageMessage, setShowEncourageMessage] = useBoolean(false);

const toast = useToast();

const { isLoading: isLoadingMessages } = useSWR<ChatItem>(chatId ? API_ROUTES.GET_CHAT(chatId) : null, get, {
Expand Down Expand Up @@ -110,15 +112,17 @@ export const ChatConversation = memo(function ChatConversation({ chatId, getConf
setQueueInfo(null);
setResponse(null);
setIsSending.off();
setShowEncourageMessage.on();
},
[getConfigValues, setIsSending, toast]
[getConfigValues, setIsSending, setShowEncourageMessage, toast]
);
const sendPrompterMessage = useCallback(async () => {
const content = inputRef.current?.value.trim();
if (!content || isSending) {
return;
}
setIsSending.on();
setShowEncourageMessage.off();

// TODO: maybe at some point we won't need to access the rendered HTML directly, but use react state
const parentId = document.getElementById(LAST_ASSISTANT_MESSAGE_ID)?.dataset.id ?? null;
Expand Down Expand Up @@ -164,7 +168,7 @@ export const ChatConversation = memo(function ChatConversation({ chatId, getConf
inputRef.current!.value = "";
// after creating the prompters message, handle the assistant's case
await createAndFetchAssistantMessage({ parentId: prompter_message.id, chatId });
}, [setIsSending, chatId, messages, createAndFetchAssistantMessage, toast, isSending]);
}, [isSending, setIsSending, setShowEncourageMessage, chatId, messages, createAndFetchAssistantMessage, toast]);

const sendVote = useMessageVote();

Expand Down Expand Up @@ -284,6 +288,8 @@ export const ChatConversation = memo(function ChatConversation({ chatId, getConf
isSending={isSending}
retryingParentId={retryingParentId}
onEditPromtp={handleEditPrompt}
showEncourageMessage={showEncourageMessage}
onEncourageMessageClose={setShowEncourageMessage.off}
></ChatConversationTree>
{isSending && streamedResponse && <PendingMessageEntry isAssistant content={streamedResponse} />}
<div ref={messagesEndRef} style={{ height: 0 }}></div>
Expand Down
1 change: 1 addition & 0 deletions website/src/components/Chat/ChatConversationTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const TreeChildren = ({
message={currentTree}
{...props}
canRetry={isLeaf}
showEncourageMessage={props.showEncourageMessage && isLeaf}
// TODO refacor away from this dirty hack
id={isLeaf && currentTree.role === "assistant" ? LAST_ASSISTANT_MESSAGE_ID : undefined}
data-id={currentTree.id}
Expand Down
150 changes: 81 additions & 69 deletions website/src/components/Chat/ChatMessageEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { InferenceMessage } from "src/types/Chat";
import { BaseMessageEntry } from "../Messages/BaseMessageEntry";
import { BaseMessageEmojiButton } from "../Messages/MessageEmojiButton";
import { MessageInlineEmojiRow } from "../Messages/MessageInlineEmojiRow";
import { WorkParametersDisplay } from "./WorkParameters";
import { EncourageMessage } from "./EncourageMessage";
import { WorkParametersDisplay } from "./WorkParameters";

export type EditPromptParams = { parentId: string; chatId: string; content: string };

Expand All @@ -36,6 +36,8 @@ export type ChatMessageEntryProps = {
canRetry?: boolean;
id?: string;
"data-id"?: string;
showEncourageMessage: boolean;
onEncourageMessageClose: () => void;
};

export const ChatMessageEntry = memo(function ChatMessageEntry({
Expand All @@ -46,6 +48,8 @@ export const ChatMessageEntry = memo(function ChatMessageEntry({
pagingSlot,
onEditPromtp,
canRetry,
showEncourageMessage,
onEncourageMessageClose,
...props
}: ChatMessageEntryProps) {
const { t } = useTranslation("common");
Expand Down Expand Up @@ -114,74 +118,82 @@ export const ChatMessageEntry = memo(function ChatMessageEntry({
const { onCopy, hasCopied } = useClipboard(message.content);

return (
<PendingMessageEntry
ref={ref}
{...props}
isAssistant={isAssistant}
usedPlugin={used_plugin}
content={isEditing ? "" : content!}
>
{!isAssistant && parentId !== null && (
<Box position="absolute" top={{ base: "4", md: 0 }} style={{ insetInlineEnd: `0.5rem` }}>
{isEditing ? (
<MessageInlineEmojiRow spacing="0">
<BaseMessageEmojiButton emoji={Check} onClick={handleEditSubmit}></BaseMessageEmojiButton>
<BaseMessageEmojiButton emoji={X} onClick={setIsEditing.off}></BaseMessageEmojiButton>
</MessageInlineEmojiRow>
) : (
<BaseMessageEmojiButton emoji={Edit} onClick={setIsEditing.on}></BaseMessageEmojiButton>
)}
</Box>
)}
{isEditing && (
<Box mx={{ md: "-15px" }} mt={{ md: 2 }}>
<Textarea
defaultValue={content || ""}
ref={inputRef}
onKeyDown={handleKeydown}
bg="gray.100"
borderRadius="xl"
_dark={{
bg: "gray.800",
}}
autoFocus
></Textarea>
</Box>
)}
{!isEditing && (
<Flex justifyContent={pagingSlot ? "space-between" : "end"} mt="1">
{pagingSlot}
{isAssistant && (
<MessageInlineEmojiRow>
{(state === "pending" || state === "in_progress") && (
<CircularProgress isIndeterminate size="20px" title={state} />
)}
{(state === "aborted_by_worker" || state === "cancelled" || state === "timeout") && (
<>
<Icon as={XCircle} color="red" />
<Text color="red">{`Error: ${state}`}</Text>
{onRetry && !isSending && <Button onClick={handleRetry}>{t("retry")}</Button>}
</>
)}
{state === "complete" && (
<>
<EncourageMessage />
{canRetry && <BaseMessageEmojiButton emoji={RotateCcw} onClick={handleRetry} label={t("retry")} />}
{!hasCopied ? (
<BaseMessageEmojiButton emoji={Copy} onClick={onCopy} label={t("copy")} />
) : (
<BaseMessageEmojiButton emoji={Check} />
)}
<BaseMessageEmojiButton emoji={ThumbsUp} checked={score === 1} onClick={handleThumbsUp} />
<BaseMessageEmojiButton emoji={ThumbsDown} checked={score === -1} onClick={handleThumbsDown} />
</>
)}
</MessageInlineEmojiRow>
)}
</Flex>
<>
<PendingMessageEntry
ref={ref}
{...props}
isAssistant={isAssistant}
usedPlugin={used_plugin}
content={isEditing ? "" : content!}
>
{!isAssistant && parentId !== null && (
<Box position="absolute" top={{ base: "4", md: 0 }} style={{ insetInlineEnd: `0.5rem` }}>
{isEditing ? (
<MessageInlineEmojiRow spacing="0">
<BaseMessageEmojiButton emoji={Check} onClick={handleEditSubmit}></BaseMessageEmojiButton>
<BaseMessageEmojiButton emoji={X} onClick={setIsEditing.off}></BaseMessageEmojiButton>
</MessageInlineEmojiRow>
) : (
<BaseMessageEmojiButton emoji={Edit} onClick={setIsEditing.on}></BaseMessageEmojiButton>
)}
</Box>
)}
{isEditing && (
<Box mx={{ md: "-15px" }} mt={{ md: 2 }}>
<Textarea
defaultValue={content || ""}
ref={inputRef}
onKeyDown={handleKeydown}
bg="gray.100"
borderRadius="xl"
_dark={{
bg: "gray.800",
}}
autoFocus
></Textarea>
</Box>
)}
{!isEditing && (
<Flex justifyContent={pagingSlot ? "space-between" : "end"} mt="1">
{pagingSlot}
{isAssistant && (
<MessageInlineEmojiRow>
{(state === "pending" || state === "in_progress") && (
<CircularProgress isIndeterminate size="20px" title={state} />
)}
{(state === "aborted_by_worker" || state === "cancelled" || state === "timeout") && (
<>
<Icon as={XCircle} color="red" />
<Text color="red">{`Error: ${state}`}</Text>
{onRetry && !isSending && <Button onClick={handleRetry}>{t("retry")}</Button>}
</>
)}
{state === "complete" && (
<>
{canRetry && <BaseMessageEmojiButton emoji={RotateCcw} onClick={handleRetry} label={t("retry")} />}
{!hasCopied ? (
<BaseMessageEmojiButton emoji={Copy} onClick={onCopy} label={t("copy")} />
) : (
<BaseMessageEmojiButton emoji={Check} />
)}
<BaseMessageEmojiButton emoji={ThumbsUp} checked={score === 1} onClick={handleThumbsUp} />
<BaseMessageEmojiButton emoji={ThumbsDown} checked={score === -1} onClick={handleThumbsDown} />
</>
)}
</MessageInlineEmojiRow>
)}
</Flex>
)}
{work_parameters && <WorkParametersDisplay parameters={work_parameters} />}
</PendingMessageEntry>
{state === "complete" && isAssistant && showEncourageMessage && (
<EncourageMessage
onThumbsUp={handleThumbsUp}
onThumbsDown={handleThumbsDown}
onClose={onEncourageMessageClose}
/>
)}
{work_parameters && <WorkParametersDisplay parameters={work_parameters} />}
</PendingMessageEntry>
</>
);
});

Expand All @@ -194,7 +206,7 @@ type PendingMessageEntryProps = {
usedPlugin?: object;
};

const messageEntryContainerProps = {
export const messageEntryContainerProps = {
maxWidth: { base: "3xl", "2xl": "4xl" },
w: "full",
};
Expand Down
67 changes: 61 additions & 6 deletions website/src/components/Chat/EncourageMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,66 @@
import { Badge } from "@chakra-ui/react";
import { Box, Button, ButtonProps, Card, Flex } from "@chakra-ui/react";
import { ThumbsDown, ThumbsUp, X } from "lucide-react";
import { useTranslation } from "next-i18next";
// kept brief so that it doesnt distract/interfere with user experience
export const EncourageMessage = () => {

import { messageEntryContainerProps } from "./ChatMessageEntry";

export const EncourageMessage = ({
onClose,
onThumbsDown,
onThumbsUp,
}: {
onThumbsUp: () => void;
onThumbsDown: () => void;
onClose: () => void;
}) => {
const { t } = useTranslation("chat");

return (
<Badge fontWeight="normal" background="gray.300" color="black">
{t("feedback_message")}
</Badge>
<Box {...messageEntryContainerProps}>
<Card
fontWeight="normal"
p="4"
flexDirection="row"
gap="2"
alignItems="center"
justifyContent={{ base: "center", lg: "space-between" }}
flexWrap="wrap"
textAlign="center"
w="fit-content"
ms={{
md: "38px",
}}
borderRadius="2xl"
>
{t("feedback_message")}
<Flex>
<FeedBackButton
leftIcon={<ThumbsUp size="20px" />}
onClick={() => {
onThumbsUp();
onClose();
}}
>
{t("feedback_action_great")}
</FeedBackButton>
<FeedBackButton
leftIcon={<ThumbsDown size="20px" />}
onClick={() => {
onThumbsDown();
onClose();
}}
>
{t("feedback_action_poor")}
</FeedBackButton>
<FeedBackButton p="2" onClick={onClose}>
<X size="20px" />
</FeedBackButton>
</Flex>
</Card>
</Box>
);
};

const FeedBackButton = (props: ButtonProps) => {
return <Button variant="ghost" py="2" px="3" borderRadius="xl" {...props}></Button>;
};

0 comments on commit 895b662

Please sign in to comment.