Skip to content

Commit 895b662

Browse files
notmdandreaskoepf
andauthored
New encourage message UI (#3130)
- Only show for the last assistant message Mobile UI ![image](https://github.com/LAION-AI/Open-Assistant/assets/33456881/ca2d534d-b6ad-4ff3-bd1b-93769c7b6327) Desktop UI ![image](https://github.com/LAION-AI/Open-Assistant/assets/33456881/612435fc-0dd3-4d23-b2fa-ff0f4d1990fe) --------- Co-authored-by: Andreas Koepf <[email protected]>
1 parent 9739bc2 commit 895b662

File tree

5 files changed

+154
-78
lines changed

5 files changed

+154
-78
lines changed

website/public/locales/en/chat.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,7 @@
4646
"save_preset": "Save this preset",
4747
"preset_exists_error": "A preset with this name already exists",
4848
"preset_name_placeholder": "Enter name",
49-
"feedback_message": "Thoughts? Let us know!"
49+
"feedback_message": "How did I do? Your feedback will make me better!",
50+
"feedback_action_great": "Good",
51+
"feedback_action_poor": "Could be better"
5052
}

website/src/components/Chat/ChatConversation.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export const ChatConversation = memo(function ChatConversation({ chatId, getConf
3939
const [streamedResponse, setResponse] = useState<string | null>(null);
4040
const [queueInfo, setQueueInfo] = useState<QueueInfo | null>(null);
4141
const [isSending, setIsSending] = useBoolean();
42+
const [showEncourageMessage, setShowEncourageMessage] = useBoolean(false);
43+
4244
const toast = useToast();
4345

4446
const { isLoading: isLoadingMessages } = useSWR<ChatItem>(chatId ? API_ROUTES.GET_CHAT(chatId) : null, get, {
@@ -110,15 +112,17 @@ export const ChatConversation = memo(function ChatConversation({ chatId, getConf
110112
setQueueInfo(null);
111113
setResponse(null);
112114
setIsSending.off();
115+
setShowEncourageMessage.on();
113116
},
114-
[getConfigValues, setIsSending, toast]
117+
[getConfigValues, setIsSending, setShowEncourageMessage, toast]
115118
);
116119
const sendPrompterMessage = useCallback(async () => {
117120
const content = inputRef.current?.value.trim();
118121
if (!content || isSending) {
119122
return;
120123
}
121124
setIsSending.on();
125+
setShowEncourageMessage.off();
122126

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

169173
const sendVote = useMessageVote();
170174

@@ -284,6 +288,8 @@ export const ChatConversation = memo(function ChatConversation({ chatId, getConf
284288
isSending={isSending}
285289
retryingParentId={retryingParentId}
286290
onEditPromtp={handleEditPrompt}
291+
showEncourageMessage={showEncourageMessage}
292+
onEncourageMessageClose={setShowEncourageMessage.off}
287293
></ChatConversationTree>
288294
{isSending && streamedResponse && <PendingMessageEntry isAssistant content={streamedResponse} />}
289295
<div ref={messagesEndRef} style={{ height: 0 }}></div>

website/src/components/Chat/ChatConversationTree.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ const TreeChildren = ({
7676
message={currentTree}
7777
{...props}
7878
canRetry={isLeaf}
79+
showEncourageMessage={props.showEncourageMessage && isLeaf}
7980
// TODO refacor away from this dirty hack
8081
id={isLeaf && currentTree.role === "assistant" ? LAST_ASSISTANT_MESSAGE_ID : undefined}
8182
data-id={currentTree.id}

website/src/components/Chat/ChatMessageEntry.tsx

Lines changed: 81 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import { InferenceMessage } from "src/types/Chat";
2121
import { BaseMessageEntry } from "../Messages/BaseMessageEntry";
2222
import { BaseMessageEmojiButton } from "../Messages/MessageEmojiButton";
2323
import { MessageInlineEmojiRow } from "../Messages/MessageInlineEmojiRow";
24-
import { WorkParametersDisplay } from "./WorkParameters";
2524
import { EncourageMessage } from "./EncourageMessage";
25+
import { WorkParametersDisplay } from "./WorkParameters";
2626

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

@@ -36,6 +36,8 @@ export type ChatMessageEntryProps = {
3636
canRetry?: boolean;
3737
id?: string;
3838
"data-id"?: string;
39+
showEncourageMessage: boolean;
40+
onEncourageMessageClose: () => void;
3941
};
4042

4143
export const ChatMessageEntry = memo(function ChatMessageEntry({
@@ -46,6 +48,8 @@ export const ChatMessageEntry = memo(function ChatMessageEntry({
4648
pagingSlot,
4749
onEditPromtp,
4850
canRetry,
51+
showEncourageMessage,
52+
onEncourageMessageClose,
4953
...props
5054
}: ChatMessageEntryProps) {
5155
const { t } = useTranslation("common");
@@ -114,74 +118,82 @@ export const ChatMessageEntry = memo(function ChatMessageEntry({
114118
const { onCopy, hasCopied } = useClipboard(message.content);
115119

116120
return (
117-
<PendingMessageEntry
118-
ref={ref}
119-
{...props}
120-
isAssistant={isAssistant}
121-
usedPlugin={used_plugin}
122-
content={isEditing ? "" : content!}
123-
>
124-
{!isAssistant && parentId !== null && (
125-
<Box position="absolute" top={{ base: "4", md: 0 }} style={{ insetInlineEnd: `0.5rem` }}>
126-
{isEditing ? (
127-
<MessageInlineEmojiRow spacing="0">
128-
<BaseMessageEmojiButton emoji={Check} onClick={handleEditSubmit}></BaseMessageEmojiButton>
129-
<BaseMessageEmojiButton emoji={X} onClick={setIsEditing.off}></BaseMessageEmojiButton>
130-
</MessageInlineEmojiRow>
131-
) : (
132-
<BaseMessageEmojiButton emoji={Edit} onClick={setIsEditing.on}></BaseMessageEmojiButton>
133-
)}
134-
</Box>
135-
)}
136-
{isEditing && (
137-
<Box mx={{ md: "-15px" }} mt={{ md: 2 }}>
138-
<Textarea
139-
defaultValue={content || ""}
140-
ref={inputRef}
141-
onKeyDown={handleKeydown}
142-
bg="gray.100"
143-
borderRadius="xl"
144-
_dark={{
145-
bg: "gray.800",
146-
}}
147-
autoFocus
148-
></Textarea>
149-
</Box>
150-
)}
151-
{!isEditing && (
152-
<Flex justifyContent={pagingSlot ? "space-between" : "end"} mt="1">
153-
{pagingSlot}
154-
{isAssistant && (
155-
<MessageInlineEmojiRow>
156-
{(state === "pending" || state === "in_progress") && (
157-
<CircularProgress isIndeterminate size="20px" title={state} />
158-
)}
159-
{(state === "aborted_by_worker" || state === "cancelled" || state === "timeout") && (
160-
<>
161-
<Icon as={XCircle} color="red" />
162-
<Text color="red">{`Error: ${state}`}</Text>
163-
{onRetry && !isSending && <Button onClick={handleRetry}>{t("retry")}</Button>}
164-
</>
165-
)}
166-
{state === "complete" && (
167-
<>
168-
<EncourageMessage />
169-
{canRetry && <BaseMessageEmojiButton emoji={RotateCcw} onClick={handleRetry} label={t("retry")} />}
170-
{!hasCopied ? (
171-
<BaseMessageEmojiButton emoji={Copy} onClick={onCopy} label={t("copy")} />
172-
) : (
173-
<BaseMessageEmojiButton emoji={Check} />
174-
)}
175-
<BaseMessageEmojiButton emoji={ThumbsUp} checked={score === 1} onClick={handleThumbsUp} />
176-
<BaseMessageEmojiButton emoji={ThumbsDown} checked={score === -1} onClick={handleThumbsDown} />
177-
</>
178-
)}
179-
</MessageInlineEmojiRow>
180-
)}
181-
</Flex>
121+
<>
122+
<PendingMessageEntry
123+
ref={ref}
124+
{...props}
125+
isAssistant={isAssistant}
126+
usedPlugin={used_plugin}
127+
content={isEditing ? "" : content!}
128+
>
129+
{!isAssistant && parentId !== null && (
130+
<Box position="absolute" top={{ base: "4", md: 0 }} style={{ insetInlineEnd: `0.5rem` }}>
131+
{isEditing ? (
132+
<MessageInlineEmojiRow spacing="0">
133+
<BaseMessageEmojiButton emoji={Check} onClick={handleEditSubmit}></BaseMessageEmojiButton>
134+
<BaseMessageEmojiButton emoji={X} onClick={setIsEditing.off}></BaseMessageEmojiButton>
135+
</MessageInlineEmojiRow>
136+
) : (
137+
<BaseMessageEmojiButton emoji={Edit} onClick={setIsEditing.on}></BaseMessageEmojiButton>
138+
)}
139+
</Box>
140+
)}
141+
{isEditing && (
142+
<Box mx={{ md: "-15px" }} mt={{ md: 2 }}>
143+
<Textarea
144+
defaultValue={content || ""}
145+
ref={inputRef}
146+
onKeyDown={handleKeydown}
147+
bg="gray.100"
148+
borderRadius="xl"
149+
_dark={{
150+
bg: "gray.800",
151+
}}
152+
autoFocus
153+
></Textarea>
154+
</Box>
155+
)}
156+
{!isEditing && (
157+
<Flex justifyContent={pagingSlot ? "space-between" : "end"} mt="1">
158+
{pagingSlot}
159+
{isAssistant && (
160+
<MessageInlineEmojiRow>
161+
{(state === "pending" || state === "in_progress") && (
162+
<CircularProgress isIndeterminate size="20px" title={state} />
163+
)}
164+
{(state === "aborted_by_worker" || state === "cancelled" || state === "timeout") && (
165+
<>
166+
<Icon as={XCircle} color="red" />
167+
<Text color="red">{`Error: ${state}`}</Text>
168+
{onRetry && !isSending && <Button onClick={handleRetry}>{t("retry")}</Button>}
169+
</>
170+
)}
171+
{state === "complete" && (
172+
<>
173+
{canRetry && <BaseMessageEmojiButton emoji={RotateCcw} onClick={handleRetry} label={t("retry")} />}
174+
{!hasCopied ? (
175+
<BaseMessageEmojiButton emoji={Copy} onClick={onCopy} label={t("copy")} />
176+
) : (
177+
<BaseMessageEmojiButton emoji={Check} />
178+
)}
179+
<BaseMessageEmojiButton emoji={ThumbsUp} checked={score === 1} onClick={handleThumbsUp} />
180+
<BaseMessageEmojiButton emoji={ThumbsDown} checked={score === -1} onClick={handleThumbsDown} />
181+
</>
182+
)}
183+
</MessageInlineEmojiRow>
184+
)}
185+
</Flex>
186+
)}
187+
{work_parameters && <WorkParametersDisplay parameters={work_parameters} />}
188+
</PendingMessageEntry>
189+
{state === "complete" && isAssistant && showEncourageMessage && (
190+
<EncourageMessage
191+
onThumbsUp={handleThumbsUp}
192+
onThumbsDown={handleThumbsDown}
193+
onClose={onEncourageMessageClose}
194+
/>
182195
)}
183-
{work_parameters && <WorkParametersDisplay parameters={work_parameters} />}
184-
</PendingMessageEntry>
196+
</>
185197
);
186198
});
187199

@@ -194,7 +206,7 @@ type PendingMessageEntryProps = {
194206
usedPlugin?: object;
195207
};
196208

197-
const messageEntryContainerProps = {
209+
export const messageEntryContainerProps = {
198210
maxWidth: { base: "3xl", "2xl": "4xl" },
199211
w: "full",
200212
};
Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,66 @@
1-
import { Badge } from "@chakra-ui/react";
1+
import { Box, Button, ButtonProps, Card, Flex } from "@chakra-ui/react";
2+
import { ThumbsDown, ThumbsUp, X } from "lucide-react";
23
import { useTranslation } from "next-i18next";
3-
// kept brief so that it doesnt distract/interfere with user experience
4-
export const EncourageMessage = () => {
4+
5+
import { messageEntryContainerProps } from "./ChatMessageEntry";
6+
7+
export const EncourageMessage = ({
8+
onClose,
9+
onThumbsDown,
10+
onThumbsUp,
11+
}: {
12+
onThumbsUp: () => void;
13+
onThumbsDown: () => void;
14+
onClose: () => void;
15+
}) => {
516
const { t } = useTranslation("chat");
17+
618
return (
7-
<Badge fontWeight="normal" background="gray.300" color="black">
8-
{t("feedback_message")}
9-
</Badge>
19+
<Box {...messageEntryContainerProps}>
20+
<Card
21+
fontWeight="normal"
22+
p="4"
23+
flexDirection="row"
24+
gap="2"
25+
alignItems="center"
26+
justifyContent={{ base: "center", lg: "space-between" }}
27+
flexWrap="wrap"
28+
textAlign="center"
29+
w="fit-content"
30+
ms={{
31+
md: "38px",
32+
}}
33+
borderRadius="2xl"
34+
>
35+
{t("feedback_message")}
36+
<Flex>
37+
<FeedBackButton
38+
leftIcon={<ThumbsUp size="20px" />}
39+
onClick={() => {
40+
onThumbsUp();
41+
onClose();
42+
}}
43+
>
44+
{t("feedback_action_great")}
45+
</FeedBackButton>
46+
<FeedBackButton
47+
leftIcon={<ThumbsDown size="20px" />}
48+
onClick={() => {
49+
onThumbsDown();
50+
onClose();
51+
}}
52+
>
53+
{t("feedback_action_poor")}
54+
</FeedBackButton>
55+
<FeedBackButton p="2" onClick={onClose}>
56+
<X size="20px" />
57+
</FeedBackButton>
58+
</Flex>
59+
</Card>
60+
</Box>
1061
);
1162
};
63+
64+
const FeedBackButton = (props: ButtonProps) => {
65+
return <Button variant="ghost" py="2" px="3" borderRadius="xl" {...props}></Button>;
66+
};

0 commit comments

Comments
 (0)