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:
172
hooks/useAppState.ts
Normal file
172
hooks/useAppState.ts
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user