Files
professor/components/editor/EditorToolbar.tsx
Aodhan Collins f644937604 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>
2026-03-04 21:48:34 +00:00

164 lines
5.3 KiB
TypeScript

'use client';
import type { ResponseMode } from '@/types';
interface Props {
language: string;
providerLabel: string;
isExecuting: boolean;
isStreaming: boolean;
onRun: () => void;
onSubmit: () => void;
onReset: () => void;
onOpenSettings: () => void;
savedIndicator: boolean;
authUser: { id: string; email: string } | null;
onShowAuth: () => void;
onLogout: () => void;
responseMode: ResponseMode;
onToggleHintMode: () => void;
onToggleStrict: () => void;
}
export default function EditorToolbar({
language,
providerLabel,
isExecuting,
isStreaming,
onRun,
onSubmit,
onReset,
onOpenSettings,
savedIndicator,
authUser,
onShowAuth,
onLogout,
responseMode,
onToggleHintMode,
onToggleStrict,
}: Props) {
const busy = isExecuting || isStreaming;
return (
<div className="flex items-center gap-2 border-b border-zinc-700 bg-zinc-900 px-4 py-2">
{/* Language badge */}
<span className="rounded bg-zinc-700 px-2 py-0.5 text-xs font-mono text-zinc-300">
{language}
</span>
{/* Hint mode toggle */}
<button
onClick={onToggleHintMode}
title={responseMode.hintMode ? 'Hints on — click to disable' : 'Hints off — click to enable'}
className={`flex items-center gap-1 rounded px-2 py-1 text-xs transition-colors ${
responseMode.hintMode
? 'bg-yellow-500/20 text-yellow-300 hover:bg-yellow-500/30'
: 'text-zinc-500 hover:bg-zinc-700 hover:text-zinc-300'
}`}
>
<svg className="h-3.5 w-3.5" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2a7 7 0 0 1 7 7c0 2.73-1.56 5.1-3.85 6.33L15 17H9l-.15-1.67C6.56 14.1 5 11.73 5 9a7 7 0 0 1 7-7zm-1 16h2v1a1 1 0 0 1-2 0v-1zm0 3h2v.5a1 1 0 0 1-2 0V21z" />
</svg>
Hints
</button>
{/* Strict / Lenient pill */}
<button
onClick={onToggleStrict}
title={responseMode.strict ? 'Strict — exact approach required. Click for Lenient.' : 'Lenient — equivalent solutions accepted. Click for Strict.'}
className={`rounded px-2 py-1 text-xs font-medium transition-colors ${
responseMode.strict
? 'bg-red-500/20 text-red-300 hover:bg-red-500/30'
: 'bg-zinc-700 text-zinc-400 hover:bg-zinc-600 hover:text-zinc-200'
}`}
>
{responseMode.strict ? 'Strict' : 'Lenient'}
</button>
<div className="ml-auto flex items-center gap-2">
{/* Saved indicator */}
<span
className={`text-xs text-zinc-500 transition-opacity duration-300 ${savedIndicator ? 'opacity-100' : 'opacity-0'}`}
aria-live="polite"
>
Saved
</span>
{/* Auth area */}
{authUser ? (
<>
<span className="max-w-[120px] truncate text-xs text-zinc-500" title={authUser.email}>
{authUser.email}
</span>
<button
onClick={onLogout}
className="rounded px-2 py-1 text-xs text-zinc-500 hover:bg-zinc-700 hover:text-zinc-300 transition-colors"
>
Sign out
</button>
</>
) : (
<button
onClick={onShowAuth}
className="rounded px-2 py-1 text-xs text-zinc-400 hover:bg-zinc-700 hover:text-zinc-200 transition-colors"
>
Sign in
</button>
)}
{/* Provider settings */}
<button
onClick={onOpenSettings}
title="AI provider settings"
className="flex items-center gap-1.5 rounded px-2 py-1.5 text-xs text-zinc-500 hover:bg-zinc-700 hover:text-zinc-300 transition-colors"
>
<svg className="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
<circle cx="12" cy="12" r="3" />
<path d="M19.07 4.93a10 10 0 0 1 0 14.14M4.93 4.93a10 10 0 0 0 0 14.14" />
<path d="M12 2v2M12 20v2M2 12h2M20 12h2" />
</svg>
<span className="max-w-[80px] truncate">{providerLabel}</span>
</button>
<button
onClick={onReset}
title="Pick a new topic"
className="rounded px-3 py-1.5 text-xs text-zinc-400 hover:bg-zinc-700 hover:text-zinc-200 transition-colors"
>
Change topic
</button>
<button
onClick={onRun}
disabled={busy}
className="flex items-center gap-1.5 rounded bg-zinc-700 px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-600 disabled:cursor-not-allowed disabled:opacity-50 transition-colors"
>
{isExecuting ? (
<>
<div className="h-3 w-3 animate-spin rounded-full border border-zinc-500 border-t-blue-400" />
Running
</>
) : (
<> Run</>
)}
</button>
<button
onClick={onSubmit}
disabled={busy}
className="flex items-center gap-1.5 rounded bg-blue-600 px-3 py-1.5 text-xs text-white hover:bg-blue-500 disabled:cursor-not-allowed disabled:opacity-50 transition-colors"
>
{isStreaming ? (
<>
<div className="h-3 w-3 animate-spin rounded-full border border-blue-300 border-t-white" />
Reviewing
</>
) : (
'Submit for Review'
)}
</button>
</div>
</div>
);
}