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:
102
components/chat/ChatPane.tsx
Normal file
102
components/chat/ChatPane.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
'use client';
|
||||
|
||||
import type { Dispatch } from 'react';
|
||||
import type { AppState, AppAction, AppMode } from '@/types';
|
||||
import TaskCard from './TaskCard';
|
||||
import MessageList from './MessageList';
|
||||
import ChatInput from './ChatInput';
|
||||
|
||||
interface Props {
|
||||
state: AppState;
|
||||
dispatch: Dispatch<AppAction>;
|
||||
onSendMessage: (text: string) => void;
|
||||
onSendClassroomMessage: (text: string) => void;
|
||||
onSubmit: () => void;
|
||||
onSetAppMode: (mode: AppMode) => void;
|
||||
}
|
||||
|
||||
export default function ChatPane({ state, onSendMessage, onSendClassroomMessage, onSetAppMode }: Props) {
|
||||
const isClassroom = state.appMode === 'classroom';
|
||||
const isTaskLoading = state.phase === 'loading_task';
|
||||
|
||||
// In classroom mode "busy" only covers classroom chat replies, not lesson generation
|
||||
const isBusy = isClassroom
|
||||
? state.isStreaming && state.lessonContent !== null
|
||||
: state.isStreaming || state.phase === 'executing';
|
||||
|
||||
const messages = isClassroom ? state.classroomMessages : state.messages;
|
||||
const handleSend = isClassroom ? onSendClassroomMessage : onSendMessage;
|
||||
|
||||
const showChatStreaming = isClassroom
|
||||
? state.isStreaming && state.lessonContent !== null
|
||||
: state.isStreaming && state.phase !== 'loading_task';
|
||||
const chatStreamingContent = showChatStreaming ? state.streamingContent : '';
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-zinc-900">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-2 border-b border-zinc-700 px-4 py-2">
|
||||
<div className="h-2 w-2 rounded-full bg-blue-400" />
|
||||
<span className="text-sm font-semibold text-zinc-200">Professor</span>
|
||||
|
||||
{isBusy && (
|
||||
<div className="flex items-center gap-1.5 text-xs text-zinc-500">
|
||||
<div className="h-2 w-2 animate-pulse rounded-full bg-blue-400" />
|
||||
Thinking…
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mode toggle */}
|
||||
<div className="ml-auto flex overflow-hidden rounded-lg border border-zinc-700">
|
||||
<button
|
||||
onClick={() => onSetAppMode('classroom')}
|
||||
className={`px-3 py-1 text-xs transition-colors ${
|
||||
isClassroom ? 'bg-zinc-700 text-zinc-100' : 'text-zinc-500 hover:bg-zinc-800 hover:text-zinc-300'
|
||||
}`}
|
||||
>
|
||||
Classroom
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onSetAppMode('homework')}
|
||||
className={`border-l border-zinc-700 px-3 py-1 text-xs transition-colors ${
|
||||
!isClassroom ? 'bg-zinc-700 text-zinc-100' : 'text-zinc-500 hover:bg-zinc-800 hover:text-zinc-300'
|
||||
}`}
|
||||
>
|
||||
Homework
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Task card — homework only */}
|
||||
{!isClassroom && (
|
||||
<TaskCard
|
||||
task={state.task}
|
||||
isLoading={isTaskLoading}
|
||||
streamingContent={isTaskLoading ? state.streamingContent : ''}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Error banner */}
|
||||
{state.error && (
|
||||
<div className="mx-3 mt-3 rounded-lg border border-red-800 bg-red-900/30 px-4 py-2.5 text-sm text-red-300">
|
||||
{state.error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Messages */}
|
||||
<MessageList
|
||||
messages={messages}
|
||||
isStreaming={showChatStreaming}
|
||||
streamingContent={chatStreamingContent}
|
||||
emptyText={
|
||||
isClassroom
|
||||
? 'Ask me anything about this topic.'
|
||||
: 'Run your code or submit it for review — or ask me anything about the task.'
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Input */}
|
||||
<ChatInput isDisabled={state.isStreaming} onSend={handleSend} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user