Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ const getDefaultFormData = (): Partial<CreateExerciseFormType> => ({
instructions: "",
language: "",
stdin: "",
selectedExistingDataFiles: []
selectedExistingDataFiles: [],
// CodeTailor support
enableCodeTailor: false,
parsonspersonalize: "",
parsonsexample: ""
});

export const ActiveCodeExercise: FC<ExerciseComponentProps> = ({
Expand Down Expand Up @@ -90,7 +94,12 @@ export const ActiveCodeExercise: FC<ExerciseComponentProps> = ({
data.suffix_code || "",
data.name || "",
data.stdin || "",
selectedDatafilesInfo
selectedDatafilesInfo,
{
enableCodeTailor: data.enableCodeTailor,
parsonspersonalize: data.parsonspersonalize,
parsonsexample: data.parsonsexample
}
);
},
[allDatafiles]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { Dropdown } from "primereact/dropdown";
import { InputSwitch } from "primereact/inputswitch";
import { InputText } from "primereact/inputtext";
import { Tooltip } from "primereact/tooltip";
import { FC } from "react";

import { CreateExerciseFormType } from "@/types/exercises";
Expand All @@ -6,20 +10,99 @@ import {
BaseExerciseSettings,
BaseExerciseSettingsContent
} from "../../shared/BaseExerciseSettingsContent";
import styles from "../../shared/styles/CreateExerciseSettings.module.css";

interface ActiveCodeExerciseSettingsProps {
formData: Partial<CreateExerciseFormType>;
onChange: (settings: Partial<CreateExerciseFormType>) => void;
}

const PERSONALIZATION_LEVELS = [
{ label: "Solution", value: "solution-level" },
{ label: "Solution & Block", value: "block-and-solution" }
];

export const ActiveCodeExerciseSettings: FC<ActiveCodeExerciseSettingsProps> = ({
formData,
onChange
}) => {
const handleCodeTailorToggle = (enabled: boolean) => {
onChange({
enableCodeTailor: enabled,
// Reset personalization level when disabling
parsonspersonalize: enabled ? "solution-level" : "",
// Reset parsons example when disabling
parsonsexample: enabled ? formData.parsonsexample : ""
});
};

const handlePersonalizationChange = (value: "solution-level" | "block-and-solution") => {
onChange({ parsonspersonalize: value });
};

const handleParsonsExampleChange = (value: string) => {
onChange({ parsonsexample: value });
};

const codeTailorFields = (
<div className={styles.codeTailorSection}>
<div className={styles.formField}>
<div className="flex align-items-center gap-2">
<InputSwitch
id="enableCodeTailor"
checked={formData.enableCodeTailor ?? false}
onChange={(e) => handleCodeTailorToggle(e.value)}
/>
<label htmlFor="enableCodeTailor" className="font-medium">
Personalized Parsons Support (CodeTailor)
</label>
<i
className="pi pi-info-circle codetailor-info-icon"
data-pr-tooltip="CodeTailor provides personalized Parsons puzzles as adaptive support for students struggling with coding exercises."
data-pr-position="right"
/>
<Tooltip target=".codetailor-info-icon" />
</div>
</div>

{formData.enableCodeTailor && (
<div className={styles.codeTailorOptions}>
<div className={styles.formField}>
<span className="p-float-label">
<Dropdown
id="parsonspersonalize"
value={formData.parsonspersonalize || "solution-level"}
options={PERSONALIZATION_LEVELS}
optionLabel="label"
className="w-full"
onChange={(e) => handlePersonalizationChange(e.value)}
/>
<label htmlFor="parsonspersonalize">Personalization Level</label>
</span>
</div>

<div className={styles.formField}>
<span className="p-float-label">
<InputText
id="parsonsexample"
value={formData.parsonsexample || ""}
className="w-full"
onChange={(e) => handleParsonsExampleChange(e.target.value)}
placeholder="Enter a ParsonProb Question id(optional)"
/>
<label htmlFor="parsonsexample">Backup Example Solution</label>
</span>
</div>
</div>
)}
</div>
);

return (
<BaseExerciseSettingsContent<BaseExerciseSettings>
initialData={formData}
onSettingsChange={onChange}
additionalFields={codeTailorFields}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,34 @@
gap: 0.75rem;
}
}

/* CodeTailor Section Styles */
.codeTailorSection {
padding-top: 1.5rem;
border-top: 1px solid #e2e8f0;
}

.codeTailorOptions {
margin-top: 1rem;
padding: 1rem;
padding-top: 1.5rem;
background: #f8fafc;
border-radius: 8px;
border: 1px solid #e2e8f0;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
}

.fieldHint {
font-size: 0.75rem;
color: #64748b;
margin-top: 0.25rem;
}

@media screen and (max-width: 768px) {
.codeTailorOptions {
grid-template-columns: 1fr;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ export type QuestionJSON = Partial<{
dataLimitBasecourse: boolean;
stdin: string;
selectedExistingDataFiles: SelectedDataFile[];
// CodeTailor support
enableCodeTailor: boolean;
parsonspersonalize: "solution-level" | "block-and-solution" | "";
parsonsexample: string;
}>;

export type CreateExerciseFormType = Omit<Exercise, "question_json"> & QuestionJSON;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ export interface DataFileInfo {
filename?: string;
}

export interface CodeTailorOptions {
enableCodeTailor?: boolean;
parsonspersonalize?: "solution-level" | "block-and-solution" | "";
parsonsexample?: string;
}

export const generateActiveCodePreview = (
instructions: string,
language: string,
Expand All @@ -13,7 +19,8 @@ export const generateActiveCodePreview = (
suffix_code: string,
name: string,
stdin?: string,
selectedDataFiles?: DataFileInfo[]
selectedDataFiles?: DataFileInfo[],
codeTailorOptions?: CodeTailorOptions
): string => {
const safeId = sanitizeId(name);

Expand All @@ -24,6 +31,15 @@ export const generateActiveCodePreview = (
selectedDataFiles && selectedDataFiles.length > 0 ? selectedDataFiles.map((df) => df.acid) : [];
const datafileAttr = filenames.length > 0 ? ` data-datafile="${filenames.join(",")}"` : "";

// CodeTailor attributes
let codeTailorAttrs = "";
if (codeTailorOptions?.enableCodeTailor && codeTailorOptions?.parsonspersonalize) {
codeTailorAttrs += ` data-parsonspersonalize="${codeTailorOptions.parsonspersonalize}"`;
// If parsonsexample is provided, use it; otherwise default to LLM-example
const parsonsExampleValue = codeTailorOptions.parsonsexample?.trim() || "LLM-example";
codeTailorAttrs += ` data-parsonsexample="${parsonsExampleValue}"`;
}

return `
<div class="runestone explainer ac_section ">
<div data-component="activecode" id="${safeId}" data-question_label="${name}">
Expand All @@ -36,7 +52,7 @@ export const generateActiveCodePreview = (
data-timelimit=25000 data-codelens="true"
data-audio=''
data-wasm=/_static
${stdinAttr}${datafileAttr}
${stdinAttr}${datafileAttr}${codeTailorAttrs}
style="visibility: hidden;">
${prefix_code}
^^^^
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ export const buildQuestionJson = (data: CreateExerciseFormType) => {
instructions: data.instructions,
language: data.language,
stdin: data.stdin,
selectedExistingDataFiles: data.selectedExistingDataFiles
selectedExistingDataFiles: data.selectedExistingDataFiles,
// CodeTailor support
enableCodeTailor: data.enableCodeTailor,
parsonspersonalize: data.parsonspersonalize,
parsonsexample: data.parsonsexample
}),
...(data.question_type === "shortanswer" && {
attachment: data.attachment,
Expand Down Expand Up @@ -82,7 +86,11 @@ export const getDefaultQuestionJson = (languageOptions: TableDropdownOption[]) =
right: [{ id: "x", label: "" }],
correctAnswers: [["a", "x"]],
feedback: "Incorrect. Please try again.",
blocks: [{ id: `block-${Date.now()}`, content: "", indent: 0 }]
blocks: [{ id: `block-${Date.now()}`, content: "", indent: 0 }],
// CodeTailor support
enableCodeTailor: false,
parsonspersonalize: "",
parsonsexample: ""
});

export const mergeQuestionJsonWithDefaults = (
Expand All @@ -103,6 +111,12 @@ export const mergeQuestionJsonWithDefaults = (
blocks: questionJson?.blocks ?? defaultQuestionJson.blocks,
language: questionJson?.language ?? defaultQuestionJson.language,
instructions: questionJson?.instructions ?? defaultQuestionJson.instructions,
stdin: questionJson?.stdin ?? defaultQuestionJson.stdin
stdin: questionJson?.stdin ?? defaultQuestionJson.stdin,
// CodeTailor support
enableCodeTailor: questionJson?.enableCodeTailor ?? defaultQuestionJson.enableCodeTailor,
parsonspersonalize:
questionJson?.parsonspersonalize ??
(defaultQuestionJson.parsonspersonalize as "" | "solution-level" | "block-and-solution"),
parsonsexample: questionJson?.parsonsexample ?? defaultQuestionJson.parsonsexample
};
};