Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,27 @@ public PredefinedQuestion toggleApproval(
return predefinedQuestionService.toggleApproval(predefinedQuestion);
}

@PatchMapping("/{predefinedQuestion}")
@Transactional
public PredefinedQuestion updateQuestion(
@PathVariable("predefinedQuestion") @Schema(type = "integer")
PredefinedQuestion predefinedQuestion,
@Valid @RequestBody PredefinedQuestion updatedQuestion)
throws UnexpectedNoAccessRightException {
authorizationService.assertAuthorization(predefinedQuestion.getNote());
return predefinedQuestionService.updateQuestion(predefinedQuestion, updatedQuestion);
}

@PostMapping("/{predefinedQuestion}/delete")
@Transactional
public void deleteQuestion(
@PathVariable("predefinedQuestion") @Schema(type = "integer")
PredefinedQuestion predefinedQuestion)
throws UnexpectedNoAccessRightException {
authorizationService.assertAuthorization(predefinedQuestion.getNote());
predefinedQuestionService.deleteQuestion(predefinedQuestion);
}

@GetMapping(value = "/{note}/export-question-generation", produces = "application/json")
public Map<String, Object> exportQuestionGeneration(
@PathVariable("note") @Schema(type = "integer") Note note)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ public PredefinedQuestion toggleApproval(PredefinedQuestion question) {
return question;
}

public PredefinedQuestion updateQuestion(
PredefinedQuestion question, PredefinedQuestion updatedQuestion) {
question.setMultipleChoicesQuestion(updatedQuestion.getMultipleChoicesQuestion());
question.setCorrectAnswerIndex(updatedQuestion.getCorrectAnswerIndex());
entityPersister.save(question);
return question;
}

public void deleteQuestion(PredefinedQuestion question) {
entityPersister.remove(question);
}

public QuestionContestResult contest(PredefinedQuestion predefinedQuestion) {
MCQWithAnswer mcqWithAnswer = predefinedQuestion.getMcqWithAnswer();
QuestionEvaluation questionContestResult =
Expand Down
161 changes: 149 additions & 12 deletions frontend/src/components/notes/QuestionManagement.vue
Original file line number Diff line number Diff line change
@@ -1,26 +1,163 @@
<template>
<PopButton btn-class="btn-primary">
<template #button_face>
Send this question for fine tuning the question generation model
</template>
<template #default="{ closer }">
<SuggestQuestionForFineTuning
:predefined-question="predefinedQuestion"
@close-dialog="closer()"
/>
</template>
</PopButton>
<div>
<div class="daisy-mb-4">
<h3 class="daisy-text-lg daisy-font-bold daisy-mb-2">Edit Question</h3>
<TextArea
:rows="2"
field="stem"
v-model="editingQuestion.multipleChoicesQuestion.f0__stem"
/><br />
<div
v-for="(_, index) in editingQuestion.multipleChoicesQuestion.f1__choices"
:key="index"
>
<TextArea
:field="'choice ' + index"
:rows="1"
v-model="editingQuestion.multipleChoicesQuestion.f1__choices[index]"
/>
<br />
</div>
<TextInput
rows="2"
field="correctChoiceIndex"
v-model="editingQuestion.correctAnswerIndex"
/><br />
<button
@click="addChoice"
:disabled="
editingQuestion.multipleChoicesQuestion.f1__choices.length >=
maximumNumberOfChoices
"
class="daisy-btn daisy-btn-sm daisy-btn-outline daisy-mr-2"
>
+
</button>
<button
@click="removeChoice"
:disabled="
editingQuestion.multipleChoicesQuestion.f1__choices.length <=
minimumNumberOfChoices
"
class="daisy-btn daisy-btn-sm daisy-btn-outline daisy-mr-2"
>
-
</button>
<button
@click="saveQuestion"
:disabled="!isValidQuestion"
class="daisy-btn daisy-btn-sm daisy-btn-primary daisy-mr-2"
>
Save
</button>
<button
@click="$emit('close-dialog')"
class="daisy-btn daisy-btn-sm daisy-btn-secondary"
>
Cancel
</button>
</div>
<div class="daisy-divider daisy-my-2"></div>
<PopButton btn-class="btn-primary">
<template #button_face>
Send this question for fine tuning the question generation model
</template>
<template #default="{ closer }">
<SuggestQuestionForFineTuning
:predefined-question="predefinedQuestion"
@close-dialog="closer()"
/>
</template>
</PopButton>
</div>
</template>

<script setup lang="ts">
import type { PropType } from "vue"
import { computed, ref, watch } from "vue"
import type { PredefinedQuestion } from "@generated/backend"
import { PredefinedQuestionController } from "@generated/backend/sdk.gen"
import { apiCallWithLoading } from "@/managedApi/clientSetup"
import isMCQWithAnswerValid from "@/models/isMCQWithAnswerValid"
import SuggestQuestionForFineTuning from "../ai/SuggestQuestionForFineTuning.vue"
import TextArea from "../form/TextArea.vue"
import TextInput from "../form/TextInput.vue"
import PopButton from "../commons/Popups/PopButton.vue"

defineProps({
const props = defineProps({
predefinedQuestion: {
type: Object as PropType<PredefinedQuestion>,
required: true,
},
})

const emit = defineEmits<{
(e: "close-dialog"): void
(e: "question-updated", question: PredefinedQuestion): void
}>()

const minimumNumberOfChoices = 2
const maximumNumberOfChoices = 10

const editingQuestion = ref<PredefinedQuestion>({
...props.predefinedQuestion,
multipleChoicesQuestion: {
...props.predefinedQuestion.multipleChoicesQuestion,
f1__choices: [
...(props.predefinedQuestion.multipleChoicesQuestion.f1__choices || []),
],
},
} as PredefinedQuestion)

watch(
() => props.predefinedQuestion,
(newQuestion) => {
editingQuestion.value = {
...newQuestion,
multipleChoicesQuestion: {
...newQuestion.multipleChoicesQuestion,
f1__choices: [...(newQuestion.multipleChoicesQuestion.f1__choices || [])],
},
} as PredefinedQuestion
},
{ deep: true }
)

const isValidQuestion = computed(() =>
isMCQWithAnswerValid(editingQuestion.value)
)

const addChoice = () => {
if (
editingQuestion.value.multipleChoicesQuestion.f1__choices.length <
maximumNumberOfChoices
) {
editingQuestion.value.multipleChoicesQuestion.f1__choices.push("")
}
}

const removeChoice = () => {
if (
editingQuestion.value.multipleChoicesQuestion.f1__choices.length >
minimumNumberOfChoices
) {
editingQuestion.value.multipleChoicesQuestion.f1__choices.pop()
}
}

const saveQuestion = async () => {
if (!props.predefinedQuestion.id) {
return
}
const { data: updated, error } = await apiCallWithLoading(() =>
PredefinedQuestionController.updateQuestion({
path: { predefinedQuestion: props.predefinedQuestion.id },
body: editingQuestion.value,
})
)
if (!error && updated) {
emit("question-updated", updated)
emit("close-dialog")
}
}
</script>
48 changes: 48 additions & 0 deletions frontend/src/components/notes/Questions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<th>B</th>
<th>C</th>
<th>D</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
Expand Down Expand Up @@ -64,6 +65,24 @@
{{ choice }}
</td>
</template>
<td>
<div class="daisy-flex daisy-gap-2">
<button
class="daisy-btn daisy-btn-ghost daisy-btn-sm"
title="Edit question"
@click="openedQuestion = question"
>
<SvgEdit />
</button>
<button
class="daisy-btn daisy-btn-ghost daisy-btn-sm"
title="Delete question"
@click="deleteQuestion(question)"
>
<SvgRemove />
</button>
</div>
</td>
</tr>
</tbody>
</table>
Expand All @@ -78,6 +97,8 @@
<template #body>
<QuestionManagement
:predefinedQuestion="openedQuestion"
@question-updated="handleQuestionUpdated"
@close-dialog="openedQuestion = undefined"
/>
</template>
</Modal>
Expand All @@ -99,6 +120,9 @@ import QuestionManagement from "./QuestionManagement.vue"
import QuestionExportDialog from "./QuestionExportDialog.vue"
import PopButton from "../commons/Popups/PopButton.vue"
import SvgExport from "../svgs/SvgExport.vue"
import SvgEdit from "../svgs/SvgEdit.vue"
import SvgRemove from "../svgs/SvgRemove.vue"
import usePopups from "../commons/Popups/usePopups"

const props = defineProps({
note: {
Expand All @@ -109,6 +133,7 @@ const props = defineProps({
const questions = ref<PredefinedQuestion[]>([])
const openedQuestion = ref<PredefinedQuestion | undefined>()
const showExportDialog = ref(false)
const { popups } = usePopups()

const fetchQuestions = async () => {
const { data: allQuestions, error } =
Expand All @@ -134,6 +159,29 @@ const toggleApproval = async (questionId?: number) => {
)
}
}
const deleteQuestion = async (question: PredefinedQuestion) => {
if (
await popups.confirm(
`Are you sure you want to delete this question: "${question.multipleChoicesQuestion.f0__stem}"?`
)
) {
const { error } = await apiCallWithLoading(() =>
PredefinedQuestionController.deleteQuestion({
path: { predefinedQuestion: question.id! },
})
)
if (!error) {
questions.value = questions.value.filter((q) => q.id !== question.id)
}
}
}
const handleQuestionUpdated = (updatedQuestion: PredefinedQuestion) => {
const index = questions.value.findIndex((q) => q.id === updatedQuestion.id)
if (index !== -1) {
questions.value[index] = updatedQuestion
}
openedQuestion.value = undefined
}
onMounted(() => {
fetchQuestions()
})
Expand Down
2 changes: 1 addition & 1 deletion generated/backend/index.ts

Large diffs are not rendered by default.

Loading