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>
173 lines
4.3 KiB
TypeScript
173 lines
4.3 KiB
TypeScript
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);
|
|
}
|