feat(*): Require adding a description of the task when uploading

This commit is contained in:
Josh Creek
2025-04-16 23:26:21 +01:00
parent e8f1b9447b
commit e75fbb6694
3 changed files with 48 additions and 28 deletions
+13 -8
View File
@@ -5,8 +5,16 @@ const OPENAI_ENDPOINT = process.env['AZURE_OPENAI_ENDPOINT'];
const OPENAI_DEPLOYMENT = process.env['AZURE_OPENAI_DEPLOYMENT'] || 'gpt-4'; const OPENAI_DEPLOYMENT = process.env['AZURE_OPENAI_DEPLOYMENT'] || 'gpt-4';
// Prompt template for grading and feedback // Prompt template for grading and feedback
export function buildGradingPrompt(submission: string) { export function buildGradingPrompt(submission: string, task: string) {
return `You are an expert secondary school teacher and AI assessment agent. Assess the following student submission using the following steps: return `You are an expert secondary school teacher and AI assessment agent. Assess the following student submission in the context of the assignment/task provided.
TASK/ASSIGNMENT:
${task}
STUDENT SUBMISSION:
${submission}
Follow these steps:
1. Grade the work out of 10, using clear, objective criteria. 1. Grade the work out of 10, using clear, objective criteria.
2. Identify specific strengths, referencing the success criteria. 2. Identify specific strengths, referencing the success criteria.
3. Identify misconceptions or areas for improvement, using formative assessment language. 3. Identify misconceptions or areas for improvement, using formative assessment language.
@@ -18,9 +26,6 @@ export function buildGradingPrompt(submission: string) {
6. Suggest to the teacher one way to support this student in the next lesson. 6. Suggest to the teacher one way to support this student in the next lesson.
7. Show your reasoning step by step (chain-of-thought). 7. Show your reasoning step by step (chain-of-thought).
STUDENT SUBMISSION:
${submission}
RESPONSE FORMAT: RESPONSE FORMAT:
Grade: <number>/10 Grade: <number>/10
Strengths: <text> Strengths: <text>
@@ -31,12 +36,12 @@ Teacher Suggestion: <text>
Reasoning: <step-by-step explanation>`; Reasoning: <step-by-step explanation>`;
} }
export async function gradeWithOpenAI(submission: string): Promise<OpenAIResponse> { export async function gradeWithOpenAI(submission: string, task: string): Promise<OpenAIResponse> {
if (!OPENAI_API_KEY || !OPENAI_ENDPOINT) { if (!OPENAI_API_KEY || !OPENAI_ENDPOINT) {
// Fallback for local/demo mode // Fallback for local/demo mode
return { return {
grade: '7/10', grade: '7/10',
strengths: 'Clear argument and good evidence.', strengths: 'Clear argument and good evidence (in response to the task: ' + task + ").",
areas_for_improvement: 'Needs deeper analysis and more examples.', areas_for_improvement: 'Needs deeper analysis and more examples.',
individualized_activity: 'Write a paragraph expanding on your main point and provide two additional examples to support your argument.', individualized_activity: 'Write a paragraph expanding on your main point and provide two additional examples to support your argument.',
reflection_question: 'What was the most challenging part of this assignment for you, and why?', reflection_question: 'What was the most challenging part of this assignment for you, and why?',
@@ -45,7 +50,7 @@ export async function gradeWithOpenAI(submission: string): Promise<OpenAIRespons
}; };
} }
const prompt = buildGradingPrompt(submission); const prompt = buildGradingPrompt(submission, task);
const res = await fetch(`${OPENAI_ENDPOINT}/openai/deployments/${OPENAI_DEPLOYMENT}/chat/completions?api-version=2024-02-15-preview`, { const res = await fetch(`${OPENAI_ENDPOINT}/openai/deployments/${OPENAI_DEPLOYMENT}/chat/completions?api-version=2024-02-15-preview`, {
method: 'POST', method: 'POST',
+8 -2
View File
@@ -7,8 +7,13 @@ export const POST: RequestHandler = async (event: RequestEvent) => {
const data = await event.request.formData(); const data = await event.request.formData();
const file = data.get('file'); const file = data.get('file');
const text = data.get('text'); const text = data.get('text');
const task = data.get('task');
let submission = ''; let submission = '';
if (!task || typeof task !== 'string' || !task.trim()) {
return new Response(JSON.stringify({ error: 'Task/assignment description is required.' }), { status: 400 });
}
if (file && typeof file === 'object' && 'arrayBuffer' in file && 'name' in file) { if (file && typeof file === 'object' && 'arrayBuffer' in file && 'name' in file) {
// Only support .txt for now for hackathon speed // Only support .txt for now for hackathon speed
const fileObj = file as File; const fileObj = file as File;
@@ -24,11 +29,12 @@ export const POST: RequestHandler = async (event: RequestEvent) => {
} }
// Call the AI grading agent // Call the AI grading agent
const aiResult = await gradeWithOpenAI(submission); const aiResult = await gradeWithOpenAI(submission, task.trim());
return new Response(JSON.stringify({ return new Response(JSON.stringify({
success: true, success: true,
feedback: `Grade: ${aiResult.grade}\nStrengths: ${aiResult.strengths}\nWeaknesses: ${aiResult.weaknesses}\nIndividualized Activity: ${aiResult.individualized_activity}\nReasoning: ${aiResult.reasoning}`, task: task.trim(),
feedback: `Grade: ${aiResult.grade}\nStrengths: ${aiResult.strengths}\nAreas for Improvement: ${aiResult.areas_for_improvement}\nIndividualized Activity: ${aiResult.individualized_activity}\nReasoning: ${aiResult.reasoning}`,
...aiResult ...aiResult
}), { }), {
status: 200, status: 200,
+27 -18
View File
@@ -3,6 +3,7 @@ import { onMount } from 'svelte';
let file: File | null = null; let file: File | null = null;
let textInput = ''; let textInput = '';
let taskDescription = '';
let submitting = false; let submitting = false;
let successMsg = ''; let successMsg = '';
let errorMsg = ''; let errorMsg = '';
@@ -33,7 +34,13 @@ async function handleSubmit(event: Event) {
successMsg = ''; successMsg = '';
errorMsg = ''; errorMsg = '';
if (!taskDescription.trim()) {
errorMsg = 'Please provide a description of the task or assignment.';
submitting = false;
return;
}
const formData = new FormData(); const formData = new FormData();
formData.append('task', taskDescription.trim());
if (file) { if (file) {
formData.append('file', file); formData.append('file', file);
} else if (textInput.trim().length > 0) { } else if (textInput.trim().length > 0) {
@@ -52,6 +59,7 @@ async function handleSubmit(event: Event) {
window.location.assign('/results'); window.location.assign('/results');
file = null; file = null;
textInput = ''; textInput = '';
taskDescription = '';
return; return;
} else { } else {
errorMsg = result.error || 'An error occurred.'; errorMsg = result.error || 'An error occurred.';
@@ -68,43 +76,44 @@ async function handleSubmit(event: Event) {
<section class="max-w-xl mx-auto py-12"> <section class="max-w-xl mx-auto py-12">
<h1 class="text-2xl font-bold mb-4">Upload Student Submissions</h1> <h1 class="text-2xl font-bold mb-4">Upload Student Submissions</h1>
<p class="mb-6">Upload student assignments for instant AI-powered assessment and feedback.</p> <p class="mb-6">Upload student assignments for instant AI-powered assessment and feedback.</p>
<form on:submit|preventDefault={handleSubmit} class="space-y-6"> <form on:submit|preventDefault={handleSubmit} class="bg-white shadow rounded p-6 max-w-xl mx-auto mt-12">
<div> <h1 class="text-2xl font-bold mb-4">Upload Student Submission</h1>
<label for="file" class="block font-semibold mb-1">Upload a file (PDF, DOCX, TXT):</label> <div class="mb-4">
<label class="block font-semibold mb-1" for="taskDescription">Task/Assignment Description <span class="text-red-500">*</span></label>
<textarea id="taskDescription" rows="3" class="w-full border p-2 rounded" bind:value={taskDescription} required placeholder="Describe the assignment, question, or task the student was responding to..."></textarea>
</div>
<div class="mb-4">
<label class="block font-semibold mb-1" for="file">Upload a file (PDF, DOCX, TXT):</label>
<input <input
id="file" id="file"
type="file" type="file"
accept=".pdf,.doc,.docx,.txt" accept=".pdf,.doc,.docx,.txt"
class="block w-full border rounded p-2" class="block w-full border p-2 rounded"
on:change={handleFileChange} on:change={handleFileChange}
disabled={submitting} disabled={submitting}
/> />
<span class="text-gray-500 text-sm">Or paste the student's submission below.</span>
</div> </div>
<div class="text-center text-gray-500">or</div> <div class="mb-4">
<div> <label class="block font-semibold mb-1" for="textInput">Paste assignment text:</label>
<label for="textInput" class="block font-semibold mb-1">Paste assignment text:</label>
<textarea <textarea
id="textInput" id="textInput"
rows="6" rows="5"
class="block w-full border rounded p-2" class="w-full border p-2 rounded"
bind:value={textInput} bind:value={textInput}
on:input={handleTextChange} on:input={handleTextChange}
disabled={submitting} disabled={submitting}
placeholder="Paste or type student answer here..." placeholder="Paste or type student answer here..."
></textarea> ></textarea>
</div> </div>
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 w-full mt-4" disabled={submitting || (!file && textInput.trim().length === 0)}>
{submitting ? 'Uploading...' : 'Submit'}
</button>
{#if errorMsg} {#if errorMsg}
<div class="text-red-600 font-medium">{errorMsg}</div> <div class="mt-4 text-red-600">{errorMsg}</div>
{/if} {/if}
{#if successMsg} {#if successMsg}
<div class="text-green-600 font-medium">{successMsg}</div> <div class="mt-4 text-green-600">{successMsg}</div>
{/if} {/if}
<button
class="btn btn-primary mt-4 w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded disabled:opacity-60"
type="submit"
disabled={submitting || (!file && textInput.trim().length === 0)}
>
{submitting ? 'Uploading...' : 'Upload'}
</button>
</form> </form>
</section> </section>