Skip to content

Commit 5bc19f2

Browse files
bivanalharadi-herwana-nus
authored andcommitted
feat(getHelp): improve chip and feedbacks
- we remove line-per-line feedbacks to enhance the students' learning experience - we add suggestion chips that might suggest the feedback given is wrong or inaccurate - fix the custom prompt sending when creating threads due to change in API - previously they require the role name to be user, now it's system
1 parent 35f3401 commit 5bc19f2

File tree

8 files changed

+41
-170
lines changed

8 files changed

+41
-170
lines changed

app/services/course/assessment/answer/live_feedback/thread_service.rb

+3-3
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ def initialize(user, course, question)
3333
end
3434

3535
def extend_thread_object_with_custom_prompt
36-
return unless @custom_prompt
36+
return if @custom_prompt.blank?
3737

38-
@thread_object.merge({
38+
@thread_object = @thread_object.merge({
3939
message: {
40-
role: 'user',
40+
role: 'system',
4141
content: (@custom_prompt.length >= 500) ? "#{@custom_prompt[0...495]}..." : @custom_prompt
4242
}
4343
})

client/app/bundles/course/assessment/submission/actions/answers/index.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -214,14 +214,12 @@ const handleFeedbackOKResponse = ({
214214
noFeedbackMessage,
215215
}) => {
216216
const overallContent = response.data?.data?.message.content ?? null;
217-
const feedbackFiles = response.data?.data?.message.files ?? [];
218217
const success = response.data?.success;
219-
if (success && (overallContent || feedbackFiles.length)) {
218+
if (success && overallContent) {
220219
dispatch(
221220
getLiveFeedbackFromCodaveri({
222221
answerId,
223222
overallContent,
224-
feedbackFiles,
225223
}),
226224
);
227225
} else {

client/app/bundles/course/assessment/submission/components/GetHelpChatPage/ConversationArea.tsx

+4-47
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { FC } from 'react';
2-
import { useFormContext, useWatch } from 'react-hook-form';
32
import { Typography } from '@mui/material';
43

54
import LoadingEllipsis from 'lib/components/core/LoadingEllipsis';
@@ -13,20 +12,11 @@ import { ChatSender } from '../../types';
1312
import MarkdownText from '../MarkdownText';
1413

1514
interface ConversationAreaProps {
16-
onFeedbackClick: (linenum: number, filename?: string) => void;
1715
answerId: number;
1816
}
1917

2018
const ConversationArea: FC<ConversationAreaProps> = (props) => {
21-
const { onFeedbackClick, answerId } = props;
22-
23-
const { control } = useFormContext();
24-
const currentAnswer = useWatch({ control });
25-
26-
const files = currentAnswer[answerId]
27-
? currentAnswer[answerId].files_attributes ||
28-
currentAnswer[`${answerId}`].files_attributes
29-
: [];
19+
const { answerId } = props;
3020

3121
const dispatch = useAppDispatch();
3222
const liveFeedbackChats = useAppSelector((state) =>
@@ -61,51 +51,18 @@ const ConversationArea: FC<ConversationAreaProps> = (props) => {
6151
>
6252
{liveFeedbackChats.chats.map((chat, index) => {
6353
const isStudent = chat.sender === ChatSender.student;
64-
const allMessages = [...chat.message];
65-
if (allMessages.length === 0) return null;
66-
67-
const firstMessage = allMessages[0];
68-
const nextMessages = allMessages.slice(1, allMessages.length);
54+
const message = chat.message;
6955

7056
return (
7157
<div
72-
key={`${firstMessage} ${chat.createdAt}`}
58+
key={`${message} ${chat.createdAt}`}
7359
className={`flex ${justifyPosition(isStudent, chat.isError)}`}
7460
id={`chat-${answerId}-${index}`}
7561
>
7662
<div
7763
className={`flex flex-col rounded-lg ${isStudent ? 'bg-blue-200' : 'bg-gray-200'} max-w-[70%] pt-3 pl-3 pr-3 pb-2 m-2 w-fit text-wrap break-words space-y-1`}
7864
>
79-
{chat.lineContent && chat.lineNumber && (
80-
<div
81-
className={`flex flex-col ${chat.lineNumber && 'cursor-pointer'} rounded-lg bg-gray-50 hover:bg-gray-100 p-3 w-full text-wrap break-words`}
82-
onClick={() => {
83-
if (chat.lineNumber) {
84-
onFeedbackClick(chat.lineNumber, chat.filename);
85-
}
86-
}}
87-
>
88-
<Typography
89-
className="flex flex-col whitespace-pre-wrap ml-1"
90-
variant="body2"
91-
>
92-
{files.length === 1
93-
? t(translations.lineNumber, {
94-
lineNumber: chat.lineNumber,
95-
})
96-
: t(translations.fileNameAndLineNumber, {
97-
filename: chat.filename ?? '',
98-
lineNumber: chat.lineNumber,
99-
})}
100-
</Typography>
101-
<MarkdownText content={`${chat.lineContent}`} />
102-
</div>
103-
)}
104-
105-
<MarkdownText content={`${firstMessage}`} />
106-
{nextMessages.map((nextMessage) => (
107-
<MarkdownText key={nextMessage} content={`${nextMessage}`} />
108-
))}
65+
<MarkdownText content={`${message}`} />
10966
{!chat.isError && (
11067
<Typography
11168
className="flex flex-col text-gray-400 text-right"

client/app/bundles/course/assessment/submission/components/GetHelpChatPage/index.tsx

+2-6
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@ import Header from './Header';
1313
import SuggestionChips from './SuggestionChips';
1414

1515
interface GetHelpChatPageProps {
16-
onFeedbackClick: (linenum: number, filename?: string) => void;
1716
answerId: number | null;
1817
questionId: number;
1918
}
2019

2120
const GetHelpChatPage: FC<GetHelpChatPageProps> = (props) => {
22-
const { onFeedbackClick, answerId, questionId } = props;
21+
const { answerId, questionId } = props;
2322

2423
const scrollableRef = useRef<HTMLDivElement>(null);
2524

@@ -69,10 +68,7 @@ const GetHelpChatPage: FC<GetHelpChatPageProps> = (props) => {
6968
<Divider />
7069

7170
<div ref={scrollableRef} className="flex-1 overflow-auto mt-1">
72-
<ConversationArea
73-
answerId={answerId}
74-
onFeedbackClick={onFeedbackClick}
75-
/>
71+
<ConversationArea answerId={answerId} />
7672
</div>
7773

7874
<div className="relative flex flex-row items-center">

client/app/bundles/course/assessment/submission/components/answers/Programming/index.jsx

+1-16
Original file line numberDiff line numberDiff line change
@@ -100,17 +100,6 @@ const Programming = (props) => {
100100

101101
const editorRef = useRef(null);
102102

103-
const focusEditorOnFeedbackLine = (linenum, filename) => {
104-
if (filename) {
105-
setDisplayFileName(filename);
106-
}
107-
108-
editorRef.current?.editor?.gotoLine(linenum, 0);
109-
editorRef.current?.editor?.selection?.setAnchor(linenum - 1, 0);
110-
editorRef.current?.editor?.selection?.moveCursorTo(linenum - 1, 0);
111-
editorRef.current?.editor?.focus();
112-
};
113-
114103
const feedbackFiles = useAppSelector(
115104
(state) =>
116105
state.assessments.submission.liveFeedback?.feedbackByQuestion?.[
@@ -154,11 +143,7 @@ const Programming = (props) => {
154143
</div>
155144
{isLiveFeedbackChatOpen && isAttempting && (
156145
<div className="absolute h-[100%] flex w-1/2 whitespace-nowrap right-0">
157-
<GetHelpChatPage
158-
answerId={answerId}
159-
onFeedbackClick={focusEditorOnFeedbackLine}
160-
questionId={question.id}
161-
/>
146+
<GetHelpChatPage answerId={answerId} questionId={question.id} />
162147
</div>
163148
)}
164149
</div>

client/app/bundles/course/assessment/submission/reducers/liveFeedbackChats/index.ts

+22-92
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ import {
1414
modifyLocalStorageValue,
1515
setLocalStorageValue,
1616
} from '../../localStorage/liveFeedbackChat/operations';
17-
import { suggestionsTranslations } from '../../suggestionTranslations';
17+
import {
18+
suggestionFixesTranslations,
19+
suggestionsTranslations,
20+
} from '../../suggestionTranslations';
1821
import {
1922
AnswerFile,
2023
ChatSender,
2124
ChatShape,
22-
FeedbackLine,
23-
FeedbackShape,
2425
LiveFeedbackChatData,
2526
Suggestion,
2627
} from '../../types';
@@ -38,9 +39,17 @@ const initialState: LiveFeedbackChatState = {
3839
liveFeedbackChatUrl: '',
3940
};
4041

41-
const sampleSuggestions = (): Suggestion[] => {
42+
const sampleSuggestions = (
43+
isIncludingSuggestionFixes: boolean,
44+
): Suggestion[] => {
4245
const suggestions = Object.values(suggestionsTranslations);
43-
const chosenSuggestions = shuffle(suggestions).slice(0, 3);
46+
const suggestionFixes = Object.values(suggestionFixesTranslations);
47+
48+
const chosenSuggestions = isIncludingSuggestionFixes
49+
? shuffle(suggestions)
50+
.slice(0, 2)
51+
.concat(shuffle(suggestionFixes).slice(0, 1))
52+
: shuffle(suggestions).slice(0, 3);
4453

4554
return chosenSuggestions.map((suggestion) => {
4655
return {
@@ -50,46 +59,6 @@ const sampleSuggestions = (): Suggestion[] => {
5059
});
5160
};
5261

53-
const sortAndCombineFeedbacks = (
54-
feedbackLines: {
55-
path: string;
56-
annotation: FeedbackLine;
57-
}[],
58-
): {
59-
path: string;
60-
line: number;
61-
content: string[];
62-
}[] => {
63-
const processedFeedbackLines: {
64-
path: string;
65-
line: number;
66-
content: string[];
67-
}[] = Object.values(
68-
feedbackLines.reduce((acc, current) => {
69-
if (!acc[(current.path, current.annotation.line)]) {
70-
acc[(current.path, current.annotation.line)] = {
71-
path: current.path,
72-
line: current.annotation.line,
73-
content: [current.annotation.content],
74-
};
75-
} else {
76-
acc[(current.path, current.annotation.line)].content = [
77-
...acc[(current.path, current.annotation.line)].content,
78-
current.annotation.content,
79-
];
80-
}
81-
82-
return acc;
83-
}, {}),
84-
);
85-
86-
processedFeedbackLines
87-
.sort((f1, f2) => f1.line - f2.line)
88-
.sort((f1, f2) => f1.path.localeCompare(f2.path));
89-
90-
return processedFeedbackLines;
91-
};
92-
9362
const defaultValue = (answerId: number): LiveFeedbackChatData => {
9463
return {
9564
id: answerId,
@@ -101,7 +70,7 @@ const defaultValue = (answerId: number): LiveFeedbackChatData => {
10170
isCurrentThreadExpired: false,
10271
chats: [],
10372
answerFiles: [],
104-
suggestions: sampleSuggestions(),
73+
suggestions: sampleSuggestions(false),
10574
};
10675
};
10776

@@ -230,9 +199,7 @@ export const liveFeedbackChatSlice = createSlice({
230199
...liveFeedbackChats.chats,
231200
{
232201
sender: ChatSender.student,
233-
lineNumber: null,
234-
lineContent: null,
235-
message: [message],
202+
message,
236203
createdAt: currentTime,
237204
isError: false,
238205
},
@@ -276,53 +243,18 @@ export const liveFeedbackChatSlice = createSlice({
276243
action: PayloadAction<{
277244
answerId: number;
278245
overallContent: string | null;
279-
feedbackFiles: FeedbackShape[];
280246
}>,
281247
) => {
282-
const { answerId, overallContent, feedbackFiles } = action.payload;
248+
const { answerId, overallContent } = action.payload;
283249
const liveFeedbackChats =
284250
state.liveFeedbackChatPerAnswer.entities[answerId];
285251

286252
if (liveFeedbackChats) {
287-
const feedbackLines = feedbackFiles.flatMap((file) =>
288-
file.annotations.map((annotation) => ({
289-
path: file.path,
290-
annotation,
291-
})),
292-
);
293-
294-
const sortedAndCombinedFeedbacks =
295-
sortAndCombineFeedbacks(feedbackLines);
296-
297-
const answerLines = liveFeedbackChats.answerFiles.reduce(
298-
(acc, current) => {
299-
if (!acc[current.filename]) {
300-
acc[current.filename] = current.content.split('\n');
301-
}
302-
return acc;
303-
},
304-
{},
305-
);
306-
307-
const newChats: ChatShape[] = sortedAndCombinedFeedbacks.map((line) => {
308-
return {
309-
sender: ChatSender.codaveri,
310-
filename: line.path,
311-
lineNumber: line.line,
312-
lineContent: answerLines[line.path][line.line - 1].trim() ?? null,
313-
message: line.content,
314-
createdAt: moment(new Date()).format(SHORT_TIME_FORMAT),
315-
isError: false,
316-
};
317-
});
318-
319253
const summaryChat: ChatShape[] = overallContent
320254
? [
321255
{
322256
sender: ChatSender.codaveri,
323-
lineNumber: null,
324-
lineContent: null,
325-
message: [overallContent],
257+
message: overallContent,
326258
createdAt: moment(new Date()).format(SHORT_TIME_FORMAT),
327259
isError: false,
328260
},
@@ -332,8 +264,8 @@ export const liveFeedbackChatSlice = createSlice({
332264
const changes: Partial<LiveFeedbackChatData> = {
333265
isRequestingLiveFeedback: false,
334266
pendingFeedbackToken: null,
335-
chats: [...liveFeedbackChats.chats, ...summaryChat, ...newChats],
336-
suggestions: sampleSuggestions(),
267+
chats: [...liveFeedbackChats.chats, ...summaryChat],
268+
suggestions: sampleSuggestions(true),
337269
};
338270

339271
liveFeedbackChatAdapter.updateOne(state.liveFeedbackChatPerAnswer, {
@@ -358,9 +290,7 @@ export const liveFeedbackChatSlice = createSlice({
358290
if (liveFeedbackChats) {
359291
const newChat: ChatShape = {
360292
sender: ChatSender.codaveri,
361-
lineNumber: null,
362-
lineContent: null,
363-
message: [errorMessage],
293+
message: errorMessage,
364294
createdAt: moment(new Date()).format(SHORT_TIME_FORMAT),
365295
isError: true,
366296
};
@@ -369,7 +299,7 @@ export const liveFeedbackChatSlice = createSlice({
369299
isRequestingLiveFeedback: false,
370300
pendingFeedbackToken: null,
371301
chats: [...liveFeedbackChats.chats, newChat],
372-
suggestions: sampleSuggestions(),
302+
suggestions: sampleSuggestions(true),
373303
};
374304

375305
liveFeedbackChatAdapter.updateOne(state.liveFeedbackChatPerAnswer, {

client/app/bundles/course/assessment/submission/suggestionTranslations.ts

+7
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,10 @@ export const suggestionsTranslations = defineMessages({
2222
defaultMessage: 'Where am I wrong?',
2323
},
2424
});
25+
26+
export const suggestionFixesTranslations = defineMessages({
27+
looksWrong: {
28+
id: 'course.assessment.submission.suggestions.looksWrong',
29+
defaultMessage: 'Your advice is wrong',
30+
},
31+
});

client/app/bundles/course/assessment/submission/types.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,7 @@ export enum ChatSender {
165165
export interface ChatShape {
166166
sender: ChatSender;
167167
filename?: string;
168-
lineNumber: number | null;
169-
lineContent: string | null;
170-
message: string[];
168+
message: string;
171169
createdAt: string;
172170
isError: boolean;
173171
}

0 commit comments

Comments
 (0)