Initial commit — AI-powered coding tutor (Professor)

Next.js 16, React 19, Monaco editor, Anthropic SDK, multi-provider AI,
Wandbox Python execution, iframe HTML preview, SQLite auth + session persistence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Aodhan Collins
2026-03-04 21:48:34 +00:00
commit f644937604
56 changed files with 14012 additions and 0 deletions

172
hooks/useAppState.ts Normal file
View File

@@ -0,0 +1,172 @@
import { useReducer } from 'react';
import type { AppState, AppAction } from '@/types';
const initialState: AppState = {
phase: 'selecting',
appMode: 'homework',
topic: null,
task: null,
code: '',
messages: [],
classroomMessages: [],
lessonContent: null,
executionResult: null,
streamingContent: '',
isStreaming: false,
error: null,
responseMode: { hintMode: true, strict: false },
};
function reducer(state: AppState, action: AppAction): AppState {
switch (action.type) {
case 'SELECT_TOPIC':
return {
...initialState,
phase: 'loading_task',
appMode: state.appMode,
topic: action.payload,
code: action.payload.starterCode,
isStreaming: true,
responseMode: state.responseMode,
};
case 'TASK_STREAM_START':
return { ...state, phase: 'loading_task', isStreaming: true, streamingContent: '' };
case 'TASK_STREAM_CHUNK':
return { ...state, streamingContent: state.streamingContent + action.payload };
case 'TASK_STREAM_DONE':
return {
...state,
phase: 'ready',
task: action.payload,
code: action.payload.starterCode || state.code,
isStreaming: false,
streamingContent: '',
messages: [],
};
case 'CODE_CHANGE':
return { ...state, code: action.payload };
case 'EXECUTE_START':
return { ...state, phase: 'executing', executionResult: null, error: null };
case 'EXECUTE_DONE':
return { ...state, phase: 'ready', executionResult: action.payload };
case 'REVIEW_START':
return {
...state,
phase: 'reviewing',
isStreaming: true,
streamingContent: '',
error: null,
};
case 'STREAM_CHUNK':
return { ...state, streamingContent: state.streamingContent + action.payload };
case 'STREAM_DONE':
return {
...state,
phase: 'ready',
isStreaming: false,
streamingContent: '',
messages: [...state.messages, action.payload],
};
case 'SEND_USER_MESSAGE':
return {
...state,
isStreaming: true,
streamingContent: '',
messages: [
...state.messages,
{
id: crypto.randomUUID(),
role: 'user',
content: action.payload,
timestamp: Date.now(),
type: 'chat',
},
],
};
case 'SET_APP_MODE':
return { ...state, appMode: action.payload };
case 'LESSON_STREAM_START':
return { ...state, isStreaming: true, streamingContent: '', error: null };
case 'LESSON_STREAM_DONE':
return {
...state,
isStreaming: false,
streamingContent: '',
lessonContent: action.payload,
};
case 'SEND_CLASSROOM_MESSAGE':
return {
...state,
isStreaming: true,
streamingContent: '',
classroomMessages: [
...state.classroomMessages,
{
id: crypto.randomUUID(),
role: 'user',
content: action.payload,
timestamp: Date.now(),
type: 'chat',
},
],
};
case 'CLASSROOM_MESSAGE_DONE':
return {
...state,
isStreaming: false,
streamingContent: '',
classroomMessages: [...state.classroomMessages, action.payload],
};
case 'SET_ERROR':
return { ...state, phase: 'ready', isStreaming: false, streamingContent: '', error: action.payload };
case 'CLEAR_ERROR':
return { ...state, error: null };
case 'SET_RESPONSE_MODE':
return { ...state, responseMode: { ...state.responseMode, ...action.payload } };
case 'RESET':
return {
...initialState,
appMode: state.appMode,
responseMode: state.responseMode,
};
case 'RESTORE_SESSION':
return {
...initialState,
phase: 'ready',
appMode: state.appMode,
topic: action.payload.topic,
task: action.payload.task,
code: action.payload.code,
messages: action.payload.messages,
executionResult: action.payload.executionResult,
responseMode: state.responseMode,
};
default:
return state;
}
}
export function useAppState() {
return useReducer(reducer, initialState);
}