mirror of
https://github.com/jcreek/AutomatedAssessmentFeedbackAgent.git
synced 2026-07-12 18:43:49 +00:00
potential refactor - to be ignored
This commit is contained in:
@@ -1,58 +1,70 @@
|
|||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
import type { RequestEvent } from '@sveltejs/kit';
|
import type { RequestEvent } from '@sveltejs/kit';
|
||||||
import { gradeSubmissionWithAgent } from '$lib/server/ai';
|
import { client } from '$lib/agent/services/agentService';
|
||||||
|
import { buildGradingPrompt, buildGradingPromptWithSupportForHumanInTheLoop } from '$lib/server/ai';
|
||||||
|
import { error, json } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
interface GradingRequest {
|
||||||
|
submission: string;
|
||||||
|
task: string;
|
||||||
|
roomId?: string;
|
||||||
|
hitl?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export const POST: RequestHandler = async (event: RequestEvent) => {
|
export const POST: RequestHandler = async (event: RequestEvent) => {
|
||||||
try {
|
try {
|
||||||
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');
|
const task = data.get('task');
|
||||||
const roomId = data.get('roomId') as string | undefined;
|
const roomId = data.get('roomId') as string | undefined;
|
||||||
const hitl = data.get('hitl') === 'true';
|
const hitl = data.get('hitl') === 'true';
|
||||||
let submission = '';
|
let submission = '';
|
||||||
|
|
||||||
if (!task || typeof task !== 'string' || !task.trim()) {
|
if (!task || typeof task !== 'string' || !task.trim()) {
|
||||||
return new Response(JSON.stringify({ error: 'Task/assignment description is required.' }), {
|
throw error(400, '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;
|
||||||
if (!fileObj.name.endsWith('.txt')) {
|
if (!fileObj.name.endsWith('.txt')) {
|
||||||
return new Response(
|
throw error(400, 'Only .txt files are supported at this time.');
|
||||||
JSON.stringify({ error: 'Only .txt files are supported at this time.' }),
|
}
|
||||||
{ status: 400 }
|
const buffer = await fileObj.arrayBuffer();
|
||||||
);
|
submission = new TextDecoder('utf-8').decode(buffer);
|
||||||
}
|
} else if (typeof text === 'string' && text.trim().length > 0) {
|
||||||
const buffer = await fileObj.arrayBuffer();
|
submission = text.trim();
|
||||||
submission = new TextDecoder('utf-8').decode(buffer);
|
} else {
|
||||||
} else if (typeof text === 'string' && text.trim().length > 0) {
|
throw error(400, 'No valid input provided.');
|
||||||
submission = text.trim();
|
}
|
||||||
} else {
|
|
||||||
return new Response(JSON.stringify({ error: 'No valid input provided.' }), { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the AI grading agent with threading and fallback
|
// Create a new thread
|
||||||
const aiResult = await gradeSubmissionWithAgent(submission, task.trim(), roomId ?? '', hitl);
|
const thread = await client.agents.createThread();
|
||||||
|
|
||||||
|
// Add the initial message with the grading prompt
|
||||||
|
await client.agents.createMessage(thread.id, {
|
||||||
|
role: 'user',
|
||||||
|
content: hitl
|
||||||
|
? buildGradingPromptWithSupportForHumanInTheLoop(submission, task.trim())
|
||||||
|
: buildGradingPrompt(submission, task.trim())
|
||||||
|
});
|
||||||
|
|
||||||
return new Response(
|
// Start the run asynchronously
|
||||||
JSON.stringify({
|
const run = await client.agents.createRun(thread.id, (globalThis as any).agent.id, {
|
||||||
success: true,
|
parallelToolCalls: false
|
||||||
task: task.trim(),
|
});
|
||||||
feedback: `Suggested Grade: ${aiResult.grade}\nStrengths: ${aiResult.strengths}\nAreas for Improvement: ${aiResult.areasForImprovement}\nIndividualized Activity: ${aiResult.individualizedActivity}\nSpelling and Grammar: ${aiResult.spellingAndGrammar}\nReasoning: ${aiResult.reasoning}`,
|
|
||||||
...aiResult
|
// Return immediately with the thread ID for polling
|
||||||
}),
|
return json({
|
||||||
{
|
success: true,
|
||||||
status: 200,
|
threadId: thread.id,
|
||||||
headers: { 'Content-Type': 'application/json' }
|
runId: run.id,
|
||||||
}
|
status: 'started',
|
||||||
);
|
statusUrl: `/api/grade/status/${thread.id}`
|
||||||
} catch (err: any) {
|
});
|
||||||
return new Response(JSON.stringify({ error: err.message || 'Internal server error.' }), {
|
} catch (err: any) {
|
||||||
status: 500
|
console.error('Error starting grading operation:', err);
|
||||||
});
|
throw error(500, `Error starting grading operation: ${err.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { json, type RequestHandler } from '@sveltejs/kit';
|
||||||
|
import { client } from '$lib/agent/services/agentService';
|
||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
import type { ThreadRunOutput } from '@azure/ai-projects';
|
||||||
|
|
||||||
|
export const GET: RequestHandler = async ({ params }) => {
|
||||||
|
const { threadId } = params;
|
||||||
|
|
||||||
|
if (!threadId) {
|
||||||
|
throw error(400, 'Thread ID is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get the thread to check if it exists
|
||||||
|
const thread = await client.agents.getThread(threadId);
|
||||||
|
if (!thread) {
|
||||||
|
throw error(404, 'Thread not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the latest run for this thread
|
||||||
|
const runs = await client.agents.listRuns(threadId);
|
||||||
|
const latestRun = runs.data[0];
|
||||||
|
|
||||||
|
if (!latestRun) {
|
||||||
|
return json({
|
||||||
|
status: 'completed',
|
||||||
|
message: 'No runs found for this thread'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the run status
|
||||||
|
const run = await client.agents.getRun(threadId, latestRun.id);
|
||||||
|
|
||||||
|
// Get the latest messages to see if we have a result
|
||||||
|
const messages = await client.agents.listMessages(threadId);
|
||||||
|
const assistantMessage = messages.data.find((m: any) => m.role === 'assistant');
|
||||||
|
let result = null;
|
||||||
|
|
||||||
|
if (assistantMessage) {
|
||||||
|
const raw = assistantMessage.content
|
||||||
|
.filter((c: any) => c.type === 'text')
|
||||||
|
.map((c: any) => c.text.value)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
// Try to parse the JSON response
|
||||||
|
try {
|
||||||
|
const match = raw.match(/\{[\s\S]*\}$/);
|
||||||
|
const jsonText = match ? match[0] : raw;
|
||||||
|
result = JSON.parse(jsonText);
|
||||||
|
} catch (e) {
|
||||||
|
// If we can't parse JSON, return the raw text
|
||||||
|
result = { raw };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json({
|
||||||
|
status: run.status,
|
||||||
|
runId: run.id,
|
||||||
|
threadId,
|
||||||
|
result,
|
||||||
|
completed: ['completed', 'failed', 'cancelled', 'expired'].includes(run.status)
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('Error checking status:', err);
|
||||||
|
throw error(500, `Error checking status: ${err.message}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
+113
-47
@@ -39,17 +39,70 @@
|
|||||||
errorMsg = '';
|
errorMsg = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Poll for completion of the grading operation
|
||||||
|
interface PollResult {
|
||||||
|
completed: boolean;
|
||||||
|
result?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ToolEvent {
|
||||||
|
tool: string;
|
||||||
|
time: string;
|
||||||
|
// Add other properties as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pollForCompletion(threadId: string, runId: string, maxAttempts = 60, interval = 2000): Promise<PollResult> {
|
||||||
|
let attempts = 0;
|
||||||
|
|
||||||
|
const checkStatus = async (): Promise<PollResult> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/grade/status/${threadId}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json() as PollResult;
|
||||||
|
|
||||||
|
if (result.completed) {
|
||||||
|
// If we have a result, return it
|
||||||
|
if (result.result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// If no result but completed, throw an error
|
||||||
|
throw new Error('Operation completed but no result was returned');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not completed and we have attempts left, poll again
|
||||||
|
if (attempts < maxAttempts) {
|
||||||
|
attempts++;
|
||||||
|
await new Promise(resolve => setTimeout(resolve, interval));
|
||||||
|
return checkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max attempts reached
|
||||||
|
throw new Error('Operation timed out');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error polling status:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return checkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSubmit(event: Event) {
|
async function handleSubmit(event: Event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
submitting = true;
|
submitting = true;
|
||||||
successMsg = '';
|
successMsg = '';
|
||||||
errorMsg = '';
|
errorMsg = '';
|
||||||
const THINKING_EVENT = { tool: 'thinking', time: new Date().toISOString() };
|
const THINKING_EVENT = { tool: 'thinking', time: new Date().toISOString() };
|
||||||
function ensureThinkingStep(events) {
|
|
||||||
|
function ensureThinkingStep(events: ToolEvent[]): ToolEvent[] {
|
||||||
// Remove any existing 'thinking' event
|
// Remove any existing 'thinking' event
|
||||||
const filtered = events.filter((e) => e.tool !== 'thinking');
|
const filtered = events.filter((e: ToolEvent) => e.tool !== 'thinking');
|
||||||
return [THINKING_EVENT, ...filtered];
|
return [THINKING_EVENT, ...filtered];
|
||||||
}
|
}
|
||||||
|
|
||||||
toolEvents = [THINKING_EVENT];
|
toolEvents = [THINKING_EVENT];
|
||||||
|
|
||||||
// Generate a unique roomId for this submission
|
// Generate a unique roomId for this submission
|
||||||
@@ -69,6 +122,7 @@
|
|||||||
submitting = false;
|
submitting = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('task', taskDescription.trim());
|
formData.append('task', taskDescription.trim());
|
||||||
|
|
||||||
@@ -82,6 +136,7 @@
|
|||||||
|
|
||||||
errorMsg = '';
|
errorMsg = '';
|
||||||
try {
|
try {
|
||||||
|
// Start the grading operation
|
||||||
const response = await fetch('/api/grade', {
|
const response = await fetch('/api/grade', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
@@ -96,53 +151,64 @@
|
|||||||
submitting = false;
|
submitting = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (response.ok && result.success) {
|
|
||||||
if (result.grade === 'HUMAN_REVIEW_REQUIRED') {
|
|
||||||
humanReviewRequired = true;
|
|
||||||
aiReasoning = result.reasoning;
|
|
||||||
hitlContext = result.hitlContext || { threadId: result.threadId, runId: result.runId };
|
|
||||||
submitting = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
if (!response.ok || !result.success) {
|
||||||
// Save to localStorage history
|
throw new Error(result.error || 'Failed to start grading operation');
|
||||||
let prev = JSON.parse(localStorage.getItem('assessmentHistory') || '[]');
|
|
||||||
const entry = {
|
|
||||||
timestamp: Date.now(),
|
|
||||||
...result,
|
|
||||||
submission: result.submission || textInput || '',
|
|
||||||
task: taskDescription
|
|
||||||
};
|
|
||||||
prev.unshift(entry);
|
|
||||||
localStorage.setItem('assessmentHistory', JSON.stringify(prev.slice(0, 50)));
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
// Redirect to /results if HITL is not needed
|
|
||||||
window.location.assign('/results');
|
|
||||||
|
|
||||||
file = null;
|
|
||||||
textInput = '';
|
|
||||||
taskDescription = '';
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
console.error('Grade API error or non-success:', response, result);
|
|
||||||
if (result && result.error) {
|
|
||||||
errorMsg = `Sorry, something went wrong: ${result.error}`;
|
|
||||||
} else if (response.status === 413) {
|
|
||||||
errorMsg = 'The uploaded file is too large. Please upload a smaller file.';
|
|
||||||
} else if (response.status >= 500) {
|
|
||||||
errorMsg = 'The server encountered an error. Please try again later.';
|
|
||||||
} else {
|
|
||||||
errorMsg = 'An unknown error occurred. Please check your input and try again.';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
console.error('Error submitting grade:', err);
|
// Start polling for completion
|
||||||
errorMsg = 'Network or server error. Please check your connection and try again.';
|
try {
|
||||||
} finally {
|
const pollResult = await pollForCompletion(result.threadId, result.runId);
|
||||||
// Disabled to make the transition to the results page smoother
|
|
||||||
// submitting = false;
|
if (pollResult.result) {
|
||||||
|
// Handle the result
|
||||||
|
const finalResult = pollResult.result;
|
||||||
|
|
||||||
|
if (finalResult.grade === 'HUMAN_REVIEW_REQUIRED') {
|
||||||
|
humanReviewRequired = true;
|
||||||
|
aiReasoning = finalResult.reasoning;
|
||||||
|
hitlContext = finalResult.hitlContext || {
|
||||||
|
threadId: result.threadId,
|
||||||
|
runId: result.runId
|
||||||
|
};
|
||||||
|
submitting = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save to localStorage history
|
||||||
|
try {
|
||||||
|
let prev = JSON.parse(localStorage.getItem('assessmentHistory') || '[]');
|
||||||
|
const entry = {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
...finalResult,
|
||||||
|
submission: finalResult.submission || textInput || '',
|
||||||
|
task: taskDescription
|
||||||
|
};
|
||||||
|
prev.unshift(entry);
|
||||||
|
localStorage.setItem('assessmentHistory', JSON.stringify(prev.slice(0, 50)));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error saving to history:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect to results page
|
||||||
|
window.location.assign('/results');
|
||||||
|
|
||||||
|
// Reset form
|
||||||
|
file = null;
|
||||||
|
textInput = '';
|
||||||
|
taskDescription = '';
|
||||||
|
}
|
||||||
|
} catch (pollError: unknown) {
|
||||||
|
console.error('Error during polling:', pollError);
|
||||||
|
const errorMessage = pollError instanceof Error ? pollError.message : 'An unknown error occurred during polling';
|
||||||
|
errorMsg = `Error during grading: ${errorMessage}`;
|
||||||
|
submitting = false;
|
||||||
|
}
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error('Error starting grading operation:', err);
|
||||||
|
const errorMessage = err instanceof Error ? err.message : 'An unknown error occurred';
|
||||||
|
errorMsg = `Failed to start grading: ${errorMessage}`;
|
||||||
|
submitting = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user