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>
79 lines
2.5 KiB
TypeScript
79 lines
2.5 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import type { ExecutionResult } from '@/types';
|
|
|
|
interface Props {
|
|
result: ExecutionResult | null;
|
|
isLoading: boolean;
|
|
}
|
|
|
|
export default function OutputPanel({ result, isLoading }: Props) {
|
|
const [collapsed, setCollapsed] = useState(false);
|
|
|
|
if (!isLoading && !result) return null;
|
|
|
|
const hasError = result?.error || (result?.stderr && result.stderr.trim());
|
|
const hasOutput = result?.stdout && result.stdout.trim();
|
|
|
|
return (
|
|
<div className="border-t border-zinc-700 bg-zinc-900 text-sm font-mono">
|
|
{/* Header bar */}
|
|
<button
|
|
onClick={() => setCollapsed((c) => !c)}
|
|
className="flex w-full items-center gap-2 px-4 py-2 text-xs text-zinc-400 hover:text-zinc-200 transition-colors"
|
|
>
|
|
<span>{collapsed ? '▶' : '▼'}</span>
|
|
<span className="font-sans font-medium uppercase tracking-wider">Output</span>
|
|
{result && !isLoading && (
|
|
<span
|
|
className={`ml-auto rounded px-1.5 py-0.5 text-xs font-sans ${
|
|
result.exitCode === 0 && !result.error
|
|
? 'bg-green-900/50 text-green-400'
|
|
: 'bg-red-900/50 text-red-400'
|
|
}`}
|
|
>
|
|
{result.timedOut
|
|
? 'timed out'
|
|
: result.error
|
|
? 'error'
|
|
: `exit ${result.exitCode}`}
|
|
</span>
|
|
)}
|
|
</button>
|
|
|
|
{/* Content */}
|
|
{!collapsed && (
|
|
<div className="max-h-48 overflow-y-auto px-4 pb-4 space-y-2">
|
|
{isLoading && (
|
|
<div className="flex items-center gap-2 text-zinc-500">
|
|
<div className="h-3 w-3 animate-spin rounded-full border border-zinc-600 border-t-blue-400" />
|
|
<span>Running…</span>
|
|
</div>
|
|
)}
|
|
|
|
{result?.error && (
|
|
<pre className="whitespace-pre-wrap text-red-400">{result.error}</pre>
|
|
)}
|
|
|
|
{result?.timedOut && !result.error && (
|
|
<p className="text-yellow-400">Execution timed out after 15 seconds.</p>
|
|
)}
|
|
|
|
{hasOutput && (
|
|
<pre className="whitespace-pre-wrap text-zinc-200">{result!.stdout}</pre>
|
|
)}
|
|
|
|
{hasError && !result?.error && (
|
|
<pre className="whitespace-pre-wrap text-red-400">{result!.stderr}</pre>
|
|
)}
|
|
|
|
{result && !isLoading && !result.error && !hasOutput && !hasError && (
|
|
<span className="text-zinc-500 italic">No output</span>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|