Add test submission feature for LLM checks without creating submissions; update API and UI components to support new functionality, enhancing task evaluation for teachers and challenge authors. Update localization for test check messages in English and Russian.
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
||||
useGetTaskQuery,
|
||||
useCreateTaskMutation,
|
||||
useUpdateTaskMutation,
|
||||
useTestSubmissionMutation,
|
||||
} from '../../__data__/api/api'
|
||||
import { URLs } from '../../__data__/urls'
|
||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||
@@ -35,11 +36,15 @@ export const TaskFormPage: React.FC = () => {
|
||||
})
|
||||
const [createTask, { isLoading: isCreating }] = useCreateTaskMutation()
|
||||
const [updateTask, { isLoading: isUpdating }] = useUpdateTaskMutation()
|
||||
const [testSubmission, { isLoading: isTesting }] = useTestSubmissionMutation()
|
||||
|
||||
const [title, setTitle] = useState('')
|
||||
const [description, setDescription] = useState('')
|
||||
const [hiddenInstructions, setHiddenInstructions] = useState('')
|
||||
const [showDescPreview, setShowDescPreview] = useState(false)
|
||||
const [testAnswer, setTestAnswer] = useState('')
|
||||
const [testStatus, setTestStatus] = useState<string | null>(null)
|
||||
const [testFeedback, setTestFeedback] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (task) {
|
||||
@@ -106,6 +111,58 @@ export const TaskFormPage: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleTestSubmit = async () => {
|
||||
if (!task || !id) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!testAnswer.trim()) {
|
||||
toaster.create({
|
||||
title: t('challenge.admin.common.validation.error'),
|
||||
description: t('challenge.admin.tasks.test.validation.fill.answer'),
|
||||
type: 'error',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
setTestStatus(null)
|
||||
setTestFeedback(null)
|
||||
|
||||
try {
|
||||
const dummyUserId = task.creator?.sub || task.id
|
||||
|
||||
const result = await testSubmission({
|
||||
userId: dummyUserId,
|
||||
taskId: task.id,
|
||||
result: testAnswer.trim(),
|
||||
isTest: true,
|
||||
}).unwrap()
|
||||
|
||||
setTestStatus(result.status)
|
||||
setTestFeedback(result.feedback ?? null)
|
||||
|
||||
toaster.create({
|
||||
title: t('challenge.admin.common.success'),
|
||||
description: t('challenge.admin.tasks.test.success'),
|
||||
type: 'success',
|
||||
})
|
||||
} catch (err: unknown) {
|
||||
const isForbidden =
|
||||
err &&
|
||||
typeof err === 'object' &&
|
||||
'status' in err &&
|
||||
(err as { status?: number }).status === 403
|
||||
|
||||
toaster.create({
|
||||
title: t('challenge.admin.common.error'),
|
||||
description: isForbidden
|
||||
? t('challenge.admin.tasks.test.forbidden')
|
||||
: t('challenge.admin.tasks.test.error'),
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (isEdit && isLoadingTask) {
|
||||
return <LoadingSpinner message={t('challenge.admin.tasks.loading')} />
|
||||
}
|
||||
@@ -309,6 +366,57 @@ export const TaskFormPage: React.FC = () => {
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Test submission (LLM check) */}
|
||||
{isEdit && task && (
|
||||
<Box p={4} bg="teal.50" borderRadius="md" borderWidth="1px" borderColor="teal.200">
|
||||
<Text fontWeight="bold" mb={2} color="teal.900">
|
||||
{t('challenge.admin.tasks.test.title')}
|
||||
</Text>
|
||||
<Text fontSize="sm" mb={3} color="teal.800">
|
||||
{t('challenge.admin.tasks.test.description')}
|
||||
</Text>
|
||||
<Field.Root>
|
||||
<Field.Label>{t('challenge.admin.tasks.test.field.answer')}</Field.Label>
|
||||
<Textarea
|
||||
value={testAnswer}
|
||||
onChange={(e) => setTestAnswer(e.target.value)}
|
||||
placeholder={t('challenge.admin.tasks.test.field.answer.placeholder')}
|
||||
rows={6}
|
||||
fontFamily="monospace"
|
||||
disabled={isTesting}
|
||||
/>
|
||||
<Field.HelperText>
|
||||
{t('challenge.admin.tasks.test.field.answer.helper')}
|
||||
</Field.HelperText>
|
||||
</Field.Root>
|
||||
|
||||
<HStack mt={3} align="flex-start" justify="space-between" gap={4}>
|
||||
<Button
|
||||
onClick={handleTestSubmit}
|
||||
colorPalette="teal"
|
||||
disabled={isTesting || !testAnswer.trim()}
|
||||
>
|
||||
{t('challenge.admin.tasks.test.button.run')}
|
||||
</Button>
|
||||
|
||||
{(testStatus || testFeedback) && (
|
||||
<Box flex="1" p={3} bg="white" borderRadius="md" borderWidth="1px" borderColor="teal.100">
|
||||
{testStatus && (
|
||||
<Text fontSize="sm" fontWeight="medium" mb={1}>
|
||||
{t(`challenge.admin.tasks.test.status.${testStatus}`)}
|
||||
</Text>
|
||||
)}
|
||||
{testFeedback && (
|
||||
<Text fontSize="sm" whiteSpace="pre-wrap">
|
||||
{testFeedback}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</HStack>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<HStack gap={3} justify="flex-end">
|
||||
<Button variant="outline" onClick={() => navigate(URLs.tasks)} disabled={isLoading}>
|
||||
|
||||
Reference in New Issue
Block a user