Skip to content

Merge dev into main. Integrate latest bug fixes and improvements. #1631

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Servers/UI/OJS.Servers.Ui/ClientApp/src/common/labels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export const TITLE = 'Title';
// Users Mentors
export const QUOTA_RESET_TIME = 'Quota Reset Time';
export const REQUESTS_MADE = 'Requests Made';
export const TOTAL_REQUESTS_MADE = 'Total Requests Made';
export const QUOTA_LIMIT = 'Quota Limit';

// Mentor Prompt Templates
Expand Down
1 change: 1 addition & 0 deletions Servers/UI/OJS.Servers.Ui/ClientApp/src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ interface IEnumType {
}

interface IAdministrationFilterColumn {
field: string;
columnName: string;
columnType: FilterColumnTypeEnum;
enumValues?: Array<string> | null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export const GET_ALL_ENDPOINT = 'GetAll';

export const EXCEL_RESULTS_ENDPOINT = 'GetExcelFile';

// Query Parameters
export const OPEN_CREATE_PARAM = 'openCreate';
export const CATEGORY_ID_PARAM = 'categoryId';

// Paths
export const NEW_ADMINISTRATION_PATH = 'administration';
export const CONTESTS_PATH = 'contests';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ interface IContestEditProps {
setParentSuccessMessage: Function;
onDeleteSuccess? : Function;
skipGettingContest?: boolean;
initialCategoryId?: number;
}

const NAME_PROP = 'name';
Expand All @@ -97,6 +98,7 @@ const ContestEdit = (props:IContestEditProps) => {
setParentSuccessMessage,
onDeleteSuccess,
skipGettingContest = false,
initialCategoryId,
} = props;

const navigate = useNavigate();
Expand All @@ -109,7 +111,7 @@ const ContestEdit = (props:IContestEditProps) => {
const [ contest, setContest ] = useState<IContestAdministration>({
allowedIps: '',
allowParallelSubmissionsInTasks: false,
categoryId: 0,
categoryId: initialCategoryId || 0,
categoryName: '',
contestPassword: null,
description: null,
Expand Down Expand Up @@ -588,6 +590,7 @@ const ContestEdit = (props:IContestEditProps) => {
{option.name}
</MenuItem>
)}
disabled={!isEditMode && initialCategoryId !== undefined}
/>
</FormControl>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ const ProblemsInContestView = (props:IProblemsInContestViewProps) => {
notFilterableGridColumnDef={
returnProblemsNonFilterableColumns(
onEditClick,
setSuccessMessage,
openCopyModal,
openRetestModal,
retakeData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const LoginForm = () => {
const { isDarkMode, getColorClassName, themeColors } = useTheme();
const [ userName, setUsername ] = useState<string>('');
const [ password, setPassword ] = useState<string>('');
const [ rememberMe, setRememberMe ] = useState<boolean>(false);
const [ rememberMe, setRememberMe ] = useState<boolean>(true);
const [ loginErrorMessage, setLoginErrorMessage ] = useState<string>('');
const [ usernameFormError, setUsernameFormError ] = useState('');
const [ passwordFormError, setPasswordFormError ] = useState('');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
display: flex;
align-items: center;
gap: 1rem;
width: 100%;

@media (width <= 768px) {
gap: 0.75rem;
Expand All @@ -57,6 +58,33 @@
}
}

.closeButtonContainer {
margin-left: auto;
}

.closeButton {
position: absolute !important;
top: 8px !important;
right: 8px !important;
min-width: 40px !important;
min-height: 40px !important;
padding: 8px !important;
color: colors.$white-color !important;
z-index: 10 !important;

&:hover {
background-color: rgb(255 255 255 / 10%) !important;
}

@media (width <= 768px) {
min-width: 32px !important;
min-height: 32px !important;
padding: 4px !important;
top: 4px !important;
right: 4px !important;
}
}

.titleTextContainer {
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -295,6 +323,7 @@
gap: 1rem;
max-height: 50px;
font-size: 24px !important;
position: relative; /* For absolute positioning of close button */

@media (width <= 768px) {
font-size: 20px !important;
Expand All @@ -309,6 +338,30 @@
}
}

.systemMessageButton {
background-color: rgb(255 255 255 / 20%) !important;
color: colors.$white-color !important;
font-size: 12px !important;
padding: 4px 8px !important;
min-width: auto !important;
border-radius: 4px !important;
text-transform: none !important;
margin-left: 8px !important;

&:hover {
background-color: rgb(255 255 255 / 30%) !important;
}

&:disabled {
color: rgb(255 255 255 / 50%) !important;
}

@media (width <= 768px) {
font-size: 10px !important;
padding: 2px 6px !important;
}
}

.dialogContent {
flex-grow: 1;
padding: spacings.$sp-16 !important;
Expand Down Expand Up @@ -421,6 +474,63 @@
color: colors.$dark-base-color-500 !important;
}

.systemMessageContainer {
width: 100%;
margin-bottom: spacings.$sp-16;
}

.systemMessage {
background-color: colors.$dark-base-color-100;
color: colors.$light-base-color-100 !important;
width: 100%;
max-width: 100%;
padding: spacings.$sp-16;
border-radius: 8px;
border-left: 4px solid colors.$primary-blue;

.markdownContent {
h1, h2, h3, h4, h5, h6 {
color: colors.$primary-blue;
}

code {
background-color: colors.$dark-base-color-400;
}

pre {
background-color: colors.$dark-base-color-400;
padding: spacings.$sp-16;
margin: spacings.$sp-12 0;
}

ul, ol {
padding-left: spacings.$sp-32;
}
}

@media (width <= 768px) {
padding: spacings.$sp-12;

.markdownContent {
pre {
padding: spacings.$sp-12;
margin: spacings.$sp-8 0;
}
}
}

@media (width <= 480px) {
padding: spacings.$sp-8;

.markdownContent {
pre {
padding: spacings.$sp-8;
margin: spacings.$sp-4 0;
}
}
}
}

.dialogActions {
padding: spacings.$sp-16 !important;
gap: spacings.$sp-8;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { IoMdSend } from 'react-icons/io';
import { IoMdClose, IoMdSend } from 'react-icons/io';
import ReactMarkdown from 'react-markdown';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
Expand All @@ -12,10 +12,11 @@ import { ChatMessageRole } from 'src/common/enums';
import { IMentorConversationMessage } from 'src/common/types';
import useTheme from 'src/hooks/use-theme';
import { addMessages } from 'src/redux/features/mentorSlice';
import { useStartConversationMutation } from 'src/redux/services/mentorService';
import { useLazyGetSystemMessageQuery, useStartConversationMutation } from 'src/redux/services/mentorService';
import { useAppDispatch, useAppSelector } from 'src/redux/store';
import concatClassNames from 'src/utils/class-names';
import { getMentorConversationDate } from 'src/utils/dates';
import { getCompositeKey } from 'src/utils/id-generator';

import mentorAvatar from '../../assets/mentor.png';

Expand All @@ -41,8 +42,8 @@ const Mentor = (props: IMentorProps) => {
const [ showBubble, setShowBubble ] = useState(true);
const [ inputMessage, setInputMessage ] = useState('');

const conversationData = useAppSelector((state) => problemId !== undefined && state.mentor?.conversationsByProblemId
? state.mentor.conversationsByProblemId[problemId]
const conversationData = useAppSelector((state) => problemId && user?.id
? state.mentor?.conversationsByProblemId?.[getCompositeKey(user.id, problemId)]
: undefined);

// Local state for UI purposes
Expand All @@ -58,6 +59,8 @@ const Mentor = (props: IMentorProps) => {
isLoading,
} ] = useStartConversationMutation({ fixedCacheKey: `problem-${problemId}` });

const [ getSystemMessage, { isLoading: isLoadingSystemMessage } ] = useLazyGetSystemMessageQuery();

const isInputLengthExceeded = useMemo(
() => inputMessage.length > (conversationResponseData?.maxUserInputLength ?? 4096),
[ conversationResponseData, inputMessage ],
Expand Down Expand Up @@ -146,6 +149,7 @@ const Mentor = (props: IMentorProps) => {
}

dispatch(addMessages({
userId: user.id,
problemId,
messages,
setConversationDate: localConversationDate === null,
Expand Down Expand Up @@ -187,6 +191,25 @@ const Mentor = (props: IMentorProps) => {
setShowBubble(false);
};

const handleCheckSystemMessage = async () => {
if (problemId && problemName && contestId && contestName && categoryName && submissionTypeName &&
!localConversationMessages.some((m) => m.role === ChatMessageRole.System)) {
const { data } = await getSystemMessage({
userId: user?.id || '',
problemId,
problemName,
contestId,
contestName,
categoryName,
submissionTypeName,
});

if (data) {
setLocalConversationMessages([ ...localConversationMessages, data ]);
}
}
};

if (!isMentorAllowed) {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <></>;
Expand Down Expand Up @@ -230,6 +253,12 @@ const Mentor = (props: IMentorProps) => {
disableAutoFocus
>
<DialogTitle className={styles.dialogTitle}>
<Button
onClick={handleToggleChat}
className={styles.closeButton}
>
<IoMdClose />
</Button>
<div className={styles.mentorTitleContainer}>
<div className={styles.mentorTitleAvatar}>
<img src={mentorAvatar} alt="Mentor Avatar" />
Expand All @@ -240,6 +269,17 @@ const Mentor = (props: IMentorProps) => {
<span className={styles.problemNameText}>{problemName}</span>
)}
</div>
{user?.isAdmin && (
<Button
onClick={handleCheckSystemMessage}
className={styles.systemMessageButton}
disabled={isLoadingSystemMessage}
>
{isLoadingSystemMessage
? 'Loading...'
: 'Check System Message'}
</Button>
)}
</div>
</DialogTitle>

Expand All @@ -256,17 +296,27 @@ const Mentor = (props: IMentorProps) => {
{localConversationDate !== null && getMentorConversationDate(localConversationDate)}
</div>
{localConversationMessages.map((message) => (
<div className={styles.messageContainer} key={message.sequenceNumber}>
{(message.role === ChatMessageRole.Assistant || message.role === ChatMessageRole.Information) && (
<div className={styles.mentorMessageAvatar}>
<img src={mentorAvatar} alt="Mentor Avatar" />
</div>
<div
className={`${styles.messageContainer} ${
message.role === ChatMessageRole.System
? styles.systemMessageContainer
: ''
}`}
key={message.sequenceNumber}
>
{(message.role === ChatMessageRole.Assistant ||
message.role === ChatMessageRole.Information) && (
<div className={styles.mentorMessageAvatar}>
<img src={mentorAvatar} alt="Mentor Avatar" />
</div>
)}
<div
className={`${styles.message} ${
message.role === ChatMessageRole.User
? styles.userMessage
: styles.mentorMessage
: message.role === ChatMessageRole.System
? styles.systemMessage
: styles.mentorMessage
}`}
>
<ReactMarkdown className={styles.markdownContent}>
Expand Down
Loading