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:
@@ -13,6 +13,9 @@ import type {
|
||||
UpdateTaskRequest,
|
||||
CreateChainRequest,
|
||||
UpdateChainRequest,
|
||||
SubmitRequest,
|
||||
TestSubmissionResult,
|
||||
APIResponse,
|
||||
} from '../../types/challenge'
|
||||
|
||||
export const api = createApi({
|
||||
@@ -141,6 +144,21 @@ export const api = createApi({
|
||||
transformResponse: (response: { body: ChallengeSubmission[] }) => response.body,
|
||||
providesTags: ['Submission'],
|
||||
}),
|
||||
|
||||
// Test submission (LLM check without creating a real submission)
|
||||
testSubmission: builder.mutation<TestSubmissionResult, SubmitRequest>({
|
||||
query: ({ userId, taskId, result, isTest = true }) => ({
|
||||
url: '/challenge/submit',
|
||||
method: 'POST',
|
||||
body: {
|
||||
userId,
|
||||
taskId,
|
||||
result,
|
||||
isTest,
|
||||
},
|
||||
}),
|
||||
transformResponse: (response: APIResponse<TestSubmissionResult>) => response.data,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -159,5 +177,6 @@ export const {
|
||||
useGetSystemStatsV2Query,
|
||||
useGetUserStatsQuery,
|
||||
useGetUserSubmissionsQuery,
|
||||
useTestSubmissionMutation,
|
||||
} = api
|
||||
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -226,3 +226,19 @@ export interface SystemStatsV2 {
|
||||
chainsDetailed: ChainDetailed[]
|
||||
}
|
||||
|
||||
// ========== Submissions / Checking ==========
|
||||
|
||||
export interface SubmitRequest {
|
||||
userId: string
|
||||
taskId: string
|
||||
result: string
|
||||
// Флаг тестового режима: проверка без создания Submission и очереди
|
||||
isTest?: boolean
|
||||
}
|
||||
|
||||
export interface TestSubmissionResult {
|
||||
isTest: true
|
||||
status: Exclude<SubmissionStatus, 'pending' | 'in_progress'>
|
||||
feedback?: string
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user