mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Add dialog to report AI message feedback
This commit is contained in:
@@ -28,9 +28,10 @@ import { type MessageDescriptor } from '../../../Utils/i18n/MessageDescriptor.fl
|
||||
import Link from '../../../UI/Link';
|
||||
import { getHelpLink } from '../../../Utils/HelpLink';
|
||||
import Window from '../../../Utils/Window';
|
||||
import { DislikeFeedbackDialog } from './DislikeFeedbackDialog';
|
||||
|
||||
const TOO_MANY_MESSAGES_WARNING_COUNT = 9;
|
||||
const TOO_MANY_MESSAGES_ERROR_COUNT = 14;
|
||||
const TOO_MANY_USER_MESSAGES_WARNING_COUNT = 5;
|
||||
const TOO_MANY_USER_MESSAGES_ERROR_COUNT = 10;
|
||||
|
||||
type Props = {
|
||||
aiRequest: AiRequest | null,
|
||||
@@ -40,7 +41,8 @@ type Props = {
|
||||
onSendFeedback: (
|
||||
aiRequestId: string,
|
||||
messageIndex: number,
|
||||
feedback: 'like' | 'dislike'
|
||||
feedback: 'like' | 'dislike',
|
||||
reason?: string
|
||||
) => Promise<void>,
|
||||
hasOpenedProject: boolean,
|
||||
|
||||
@@ -107,6 +109,11 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
const scrollViewRef = React.useRef<ScrollViewInterface | null>(null);
|
||||
const [messageFeedbacks, setMessageFeedbacks] = React.useState({});
|
||||
const theme = React.useContext(GDevelopThemeContext);
|
||||
const [
|
||||
dislikeFeedbackDialogOpenedFor,
|
||||
setDislikeFeedbackDialogOpenedFor,
|
||||
] = React.useState(null);
|
||||
|
||||
const [newChatPlaceholder] = React.useState(() => {
|
||||
const newChatPlaceholders: Array<MessageDescriptor> = [
|
||||
t`How to add a leaderboard?`,
|
||||
@@ -281,6 +288,10 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
);
|
||||
}
|
||||
|
||||
const userMessagesCount = aiRequest.output.filter(
|
||||
message => message.role === 'user'
|
||||
).length;
|
||||
|
||||
return (
|
||||
<ColumnStackLayout
|
||||
expand
|
||||
@@ -350,11 +361,10 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
...messageFeedbacks,
|
||||
[feedbackKey]: 'dislike',
|
||||
});
|
||||
onSendFeedback(
|
||||
aiRequest.id,
|
||||
setDislikeFeedbackDialogOpenedFor({
|
||||
aiRequestId: aiRequest.id,
|
||||
messageIndex,
|
||||
'dislike'
|
||||
);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Dislike
|
||||
@@ -415,10 +425,10 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
</Line>
|
||||
) : null}
|
||||
</ScrollView>
|
||||
{aiRequest.output.length >= TOO_MANY_MESSAGES_WARNING_COUNT ? (
|
||||
{userMessagesCount >= TOO_MANY_USER_MESSAGES_WARNING_COUNT ? (
|
||||
<AlertMessage
|
||||
kind={
|
||||
aiRequest.output.length >= TOO_MANY_MESSAGES_ERROR_COUNT
|
||||
userMessagesCount >= TOO_MANY_USER_MESSAGES_ERROR_COUNT
|
||||
? 'error'
|
||||
: 'warning'
|
||||
}
|
||||
@@ -463,6 +473,20 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
{isMobile && errorOrQuotaOrCreditsExplanation}
|
||||
</ResponsiveLineStackLayout>
|
||||
</Column>
|
||||
{dislikeFeedbackDialogOpenedFor && (
|
||||
<DislikeFeedbackDialog
|
||||
open
|
||||
onClose={() => setDislikeFeedbackDialogOpenedFor(null)}
|
||||
onSendFeedback={(reason: string) => {
|
||||
onSendFeedback(
|
||||
dislikeFeedbackDialogOpenedFor.aiRequestId,
|
||||
dislikeFeedbackDialogOpenedFor.messageIndex,
|
||||
'dislike',
|
||||
reason
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ColumnStackLayout>
|
||||
);
|
||||
}
|
||||
|
@@ -0,0 +1,100 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { ColumnStackLayout } from '../../../UI/Layout';
|
||||
import Text from '../../../UI/Text';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Dialog, { DialogPrimaryButton } from '../../../UI/Dialog';
|
||||
import Radio from '@material-ui/core/Radio';
|
||||
import RadioGroup from '@material-ui/core/RadioGroup';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import FlatButton from '../../../UI/FlatButton';
|
||||
|
||||
type DislikeFeedbackDialogProps = {|
|
||||
open: boolean,
|
||||
onClose: () => void,
|
||||
onSendFeedback: (reason: string) => void,
|
||||
|};
|
||||
|
||||
export const DislikeFeedbackDialog = ({
|
||||
open,
|
||||
onClose,
|
||||
onSendFeedback,
|
||||
}: DislikeFeedbackDialogProps) => {
|
||||
const [selectedReason, setSelectedReason] = React.useState<?string>(null);
|
||||
|
||||
const handleChange = (event: { target: { value: string } }) => {
|
||||
setSelectedReason(event.target.value);
|
||||
};
|
||||
|
||||
const handleSendFeedback = () => {
|
||||
if (selectedReason) {
|
||||
onSendFeedback(selectedReason);
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title={<Trans>What could be improved?</Trans>}
|
||||
actions={[
|
||||
<FlatButton
|
||||
key="cancel"
|
||||
label={<Trans>Cancel</Trans>}
|
||||
onClick={onClose}
|
||||
/>,
|
||||
<DialogPrimaryButton
|
||||
key="send"
|
||||
primary
|
||||
label={<Trans>Send feedback</Trans>}
|
||||
onClick={handleSendFeedback}
|
||||
disabled={!selectedReason}
|
||||
/>,
|
||||
]}
|
||||
open={open}
|
||||
onRequestClose={onClose}
|
||||
maxWidth="sm"
|
||||
>
|
||||
<ColumnStackLayout noMargin>
|
||||
<Text>
|
||||
<Trans>
|
||||
Help us improve by telling us what was wrong with the answer:
|
||||
</Trans>
|
||||
</Text>
|
||||
<RadioGroup value={selectedReason || ''} onChange={handleChange}>
|
||||
<FormControlLabel
|
||||
value="not-in-my-language"
|
||||
control={<Radio color="secondary" />}
|
||||
label={<Trans>The answer is not in my language</Trans>}
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="non-existing-things"
|
||||
control={<Radio color="secondary" />}
|
||||
label={
|
||||
<Trans>Some things in the answer don't exist in GDevelop</Trans>
|
||||
}
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="not-as-good-as-it-could-be"
|
||||
control={<Radio color="secondary" />}
|
||||
label={<Trans>The answer is not as good as it could be</Trans>}
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="very-wrong-answer"
|
||||
control={<Radio color="secondary" />}
|
||||
label={<Trans>The answer is entirely wrong</Trans>}
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="out-of-scope"
|
||||
control={<Radio color="secondary" />}
|
||||
label={<Trans>The answer is out of scope for GDevelop</Trans>}
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="other"
|
||||
control={<Radio color="secondary" />}
|
||||
label={<Trans>Other reason</Trans>}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</ColumnStackLayout>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
@@ -244,7 +244,7 @@ export const AskAi = React.memo<Props>(
|
||||
);
|
||||
|
||||
const onSendFeedback = React.useCallback(
|
||||
async (aiRequestId, messageIndex, feedback) => {
|
||||
async (aiRequestId, messageIndex, feedback, reason) => {
|
||||
if (!profile) return;
|
||||
try {
|
||||
await retryIfFailed({ times: 2 }, () =>
|
||||
@@ -253,6 +253,7 @@ export const AskAi = React.memo<Props>(
|
||||
aiRequestId,
|
||||
messageIndex,
|
||||
feedback,
|
||||
reason,
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
|
@@ -287,11 +287,13 @@ export const sendAiRequestFeedback = async (
|
||||
aiRequestId,
|
||||
messageIndex,
|
||||
feedback,
|
||||
reason,
|
||||
}: {|
|
||||
userId: string,
|
||||
aiRequestId: string,
|
||||
messageIndex: number,
|
||||
feedback: 'like' | 'dislike',
|
||||
reason?: string,
|
||||
|}
|
||||
): Promise<AiRequest> => {
|
||||
const authorizationHeader = await getAuthorizationHeader();
|
||||
@@ -302,6 +304,7 @@ export const sendAiRequestFeedback = async (
|
||||
{
|
||||
messageIndex,
|
||||
feedback,
|
||||
reason,
|
||||
},
|
||||
{
|
||||
params: {
|
||||
|
@@ -231,36 +231,68 @@ const fakeOutputWithAiResponses = [
|
||||
|
||||
const fakeOutputWithMoreAiResponses = [
|
||||
...fakeOutputWithUserRequestOnly,
|
||||
...new Array(10).fill({
|
||||
type: 'message',
|
||||
status: 'completed',
|
||||
role: 'assistant',
|
||||
content: [
|
||||
...new Array(7)
|
||||
.fill([
|
||||
{
|
||||
type: 'output_text',
|
||||
type: 'message',
|
||||
status: 'completed',
|
||||
text: 'Some **answer** from the AI. Lorem ipsum AI.',
|
||||
annotations: [],
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
type: 'user_request',
|
||||
status: 'completed',
|
||||
text: 'Some follow up question. Lorem ipsum user.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
{
|
||||
type: 'message',
|
||||
status: 'completed',
|
||||
role: 'assistant',
|
||||
content: [
|
||||
{
|
||||
type: 'output_text',
|
||||
status: 'completed',
|
||||
text: 'Some **answer** from the AI. Lorem ipsum AI.',
|
||||
annotations: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
.flat(),
|
||||
];
|
||||
|
||||
const fakeOutputWithEvenMoreAiResponses = [
|
||||
...fakeOutputWithUserRequestOnly,
|
||||
...new Array(15).fill({
|
||||
type: 'message',
|
||||
status: 'completed',
|
||||
role: 'assistant',
|
||||
content: [
|
||||
...new Array(15)
|
||||
.fill([
|
||||
{
|
||||
type: 'output_text',
|
||||
type: 'message',
|
||||
status: 'completed',
|
||||
text: 'Some **answer** from the AI. Lorem ipsum AI.',
|
||||
annotations: [],
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
type: 'user_request',
|
||||
status: 'completed',
|
||||
text: 'Some follow up question. Lorem ipsum user.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
{
|
||||
type: 'message',
|
||||
status: 'completed',
|
||||
role: 'assistant',
|
||||
content: [
|
||||
{
|
||||
type: 'output_text',
|
||||
status: 'completed',
|
||||
text: 'Some **answer** from the AI. Lorem ipsum AI.',
|
||||
annotations: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
.flat(),
|
||||
];
|
||||
|
||||
export const ReadyAiRequest = () => (
|
||||
|
Reference in New Issue
Block a user