feat: grade exercise with AI (#296)

* working on the UI of the grading

* working on the grading function of the radio and checkboxes

* ai grading fully implemented with dummy functions for generating scores

* working on the openai response

* working on the openai response

* finished with the ai_grading

* finished with the ai_grading

* creating new migration file

* working on reviews

* updated the translations

* improvements

---------

Co-authored-by: Rotimi Best <[email protected]>
This commit is contained in:
Em_Dev
2024-05-13 19:35:06 +01:00
committed by GitHub
co-authored by Rotimi Best
parent fc8b8e1898
commit e243564113
37 changed files with 688 additions and 150 deletions
@@ -6,6 +6,7 @@
import HtmlRender from '$lib/components/HTMLRender/HTMLRender.svelte';
import Grade from '$lib/components/Question/Grade.svelte';
import { t } from '$lib/utils/functions/translations';
import ReasonBox from '../ReasonBox.svelte';
export let title = '';
export let index = 1;
@@ -22,6 +23,12 @@
export let grade: number | undefined;
export let gradeMax = 0;
export let disableGrading = false;
export let isGradeWithAI = false;
export let reason;
export let isLoading = false;
export let hideGrading = false;
let gradeWithAI = false;
function getVal(form, name) {
let values = [];
@@ -59,6 +66,17 @@
return '';
}
function acceptGrade() {
gradeWithAI = false;
}
function rejectGrade() {
gradeWithAI = false;
grade = 0;
}
$: {
gradeWithAI = isGradeWithAI;
}
</script>
<form on:submit|preventDefault={handleFormSubmit}>
@@ -73,7 +91,9 @@
</span>
</svelte:fragment>
</HtmlRender>
<Grade {gradeMax} bind:grade {disableGrading} />
{#if !hideGrading}
<Grade {gradeMax} bind:grade {disableGrading} />
{/if}
</div>
{#if code}
@@ -99,6 +119,10 @@
</button>
{/each}
</div>
{#if gradeWithAI}
<ReasonBox {reason} {isLoading} {acceptGrade} {rejectGrade} />
{/if}
{#if !isPreview}
<div class="mt-3 flex items-center justify-between w-full">
<PrimaryButton
@@ -1,23 +1,30 @@
<script lang="ts">
import TextField from '$lib/components/Form/TextField.svelte';
import { t } from '$lib/utils/functions/translations';
import { snackbar } from '../Snackbar/store';
export let gradeMax = 0;
export let disableGrading = false;
export let grade: number | undefined;
export let grade: number | undefined = 0;
$: if (grade && grade > gradeMax) {
snackbar.error('grade cant be more than max value');
grade = gradeMax;
}
</script>
{#if typeof grade !== 'undefined'}
<div class="flex items-center">
<TextField
placeholder={$t('course.navItem.lessons.exercises.new_exercise_modal.points')}
bind:value={grade}
type="number"
className="w-12"
isDisabled={disableGrading}
/>
<p class="dark:text-white ml-2 text-lg flex items-center">
<span class="mr-1">/</span> <span>{gradeMax}</span>
</p>
</div>
{/if}
<div class="flex items-center font-semibold">
<TextField
placeholder={$t('course.navItem.lessons.exercises.new_exercise_modal.points')}
bind:value={grade}
max={gradeMax}
type="number"
className="w-12"
inputClassName="h-10"
isDisabled={disableGrading}
/>
<p class="dark:text-white ml-2 text-base flex items-center font-semibold">
<span class="mr-1">/</span> <span>{gradeMax}</span>
</p>
</div>
@@ -6,6 +6,7 @@
import HtmlRender from '$lib/components/HTMLRender/HTMLRender.svelte';
import Grade from '$lib/components/Question/Grade.svelte';
import { t } from '$lib/utils/functions/translations';
import ReasonBox from '../ReasonBox.svelte';
export let title = '';
export let index = 1;
@@ -24,6 +25,12 @@
export let disableGrading = false;
export let labelClassName = '';
export let disableOptContainerMargin = false;
export let isGradeWithAI = false;
export let reason;
export let isLoading = false;
export let hideGrading = false;
let gradeWithAI = false;
function getRadioVal(form, name) {
let val;
@@ -61,6 +68,18 @@
return '';
}
function acceptGrade() {
gradeWithAI = false;
}
function rejectGrade() {
gradeWithAI = false;
grade = 0;
}
$: {
gradeWithAI = isGradeWithAI;
}
</script>
<form on:submit|preventDefault={handleFormSubmit}>
@@ -73,14 +92,16 @@
</span>
</svelte:fragment>
</HtmlRender>
<Grade {gradeMax} bind:grade {disableGrading} />
{#if !hideGrading}
<Grade {gradeMax} bind:grade {disableGrading} />
{/if}
</div>
{#if code}
<CodeSnippet {code} />
{/if}
<div class={!disableOptContainerMargin && 'ml-4'}>
<div class={disableOptContainerMargin ? '' : 'ml-4'}>
{#each options as option}
<button
class="cursor-pointer text-left my-2 border-2 border-gray-300 rounded-md hover:bg-gray-200 dark:hover:bg-neutral-800 w-full {getValidationClassName(
@@ -99,6 +120,9 @@
</button>
{/each}
</div>
{#if gradeWithAI}
<ReasonBox {reason} {isLoading} {acceptGrade} {rejectGrade} />
{/if}
{#if !isPreview}
<div class="mt-3 flex items-center justify-between w-full">
@@ -0,0 +1,53 @@
<script>
import { VARIANTS } from '../PrimaryButton/constants';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { t } from '$lib/utils/functions/translations';
import { SkeletonPlaceholder, SkeletonText } from 'carbon-components-svelte';
export let reason = '';
export let isLoading = false;
export let rejectGrade = () => {};
export let acceptGrade = () => {};
</script>
<div class="border rounded-md">
{#if isLoading}
<div class="flex w-full px-2 py-4">
<div class="w-full flex flex-row items-start space-x-4">
<img src="/ai.svg" alt="alt" class="animate-pulse" />
<div class="w-full">
<SkeletonText width="100%" paragraph lines={3} />
</div>
</div>
<div class="flex space-x-2">
<SkeletonPlaceholder style="height: 2rem; width: 5rem;" />
<SkeletonPlaceholder style="height: 2rem; width: 5rem;" />
</div>
</div>
{:else}
<div class="flex items-start justify-between px-2 py-4">
<div class="flex items-center space-x-4">
<img src="/ai.svg" alt="alt" />
<p class="font-normal text-sm">
{reason}
</p>
</div>
<div class="flex space-x-2">
<PrimaryButton
variant={VARIANTS.CONTAINED_SUCCESS}
label={$t('course.navItem.submissions.grading_modal.accept_grade')}
className="rounded-none py-1 px-2"
disablePadding={true}
onClick={acceptGrade}
/>
<PrimaryButton
variant={VARIANTS.CONTAINED_DANGER}
label={$t('course.navItem.submissions.grading_modal.reject_grade')}
className="rounded-none py-1 px-2"
disablePadding={true}
onClick={rejectGrade}
/>
</div>
</div>
{/if}
</div>
@@ -6,6 +6,7 @@
import HtmlRender from '$lib/components/HTMLRender/HTMLRender.svelte';
import Grade from '$lib/components/Question/Grade.svelte';
import { t } from '$lib/utils/functions/translations';
import ReasonBox from '../ReasonBox.svelte';
export let title = '';
export let index = 1;
@@ -21,6 +22,12 @@
export let grade: number | undefined;
export let gradeMax = 0;
export let disableGrading = false;
export let isGradeWithAI = false;
export let reason;
export let isLoading = false;
export let hideGrading = false;
let gradeWithAI = false;
function handleFormSubmit(event) {
if (isPreview) return;
@@ -33,6 +40,18 @@
event.preventDefault();
onPrevious();
}
function acceptGrade() {
gradeWithAI = false;
}
function rejectGrade() {
gradeWithAI = false;
grade = 0;
}
$: {
gradeWithAI = isGradeWithAI;
}
</script>
<form on:submit|preventDefault={handleFormSubmit}>
@@ -45,7 +64,9 @@
</span>
</svelte:fragment>
</HtmlRender>
<Grade {gradeMax} bind:grade {disableGrading} />
{#if !hideGrading}
<Grade {gradeMax} bind:grade {disableGrading} />
{/if}
</div>
{#if code}
@@ -54,9 +75,12 @@
<div class="ml-4">
{#if disabled}
<div class="bg-gray-200 dark:bg-gray-500 p-5 rounded-md mb-3">
<div class="bg-gray-200 dark:bg-gray-500 py-3 px-5 rounded-md mb-3">
{defaultValue}
</div>
{#if gradeWithAI}
<ReasonBox {reason} {isLoading} {acceptGrade} {rejectGrade} />
{/if}
{:else}
<TextArea
bind:value={defaultValue}