Files
ngo-toolkit/components/OutputPanel.tsx
T
2026-03-18 18:26:24 +08:00

125 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState } from 'react';
import BlurOverlay from './BlurOverlay';
import EditableOutput from './EditableOutput';
import ExportMenu from './ExportMenu';
type State = 'idle' | 'generating' | 'preview' | 'unlocked' | 'done';
interface OutputPanelProps {
tool: string;
sessionId: string | null;
preview: string | null;
fullOutput: string | null;
isPaid: boolean;
onPaymentRequest: () => void;
paymentLoading?: boolean;
}
export default function OutputPanel({
tool,
sessionId,
preview,
fullOutput,
isPaid,
onPaymentRequest,
paymentLoading,
}: OutputPanelProps) {
const [output, setOutput] = useState(fullOutput);
const [finalizing, setFinalizing] = useState(false);
const state: State = !sessionId
? 'idle'
: !preview
? 'generating'
: !isPaid
? 'preview'
: finalizing
? 'done'
: 'unlocked';
const displayOutput = output || fullOutput;
const handleFinalize = async () => {
setFinalizing(true);
try {
const res = await fetch(`/api/${tool}/finalize`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ session_id: sessionId }),
});
const data = await res.json();
if (data.finalized) setOutput(data.finalized);
} catch (e) {
console.error('Finalize failed:', e);
}
};
if (state === 'idle') {
return (
<div className="h-full flex items-center justify-center text-gray-400">
<div className="text-center">
<div className="text-5xl mb-4">📝</div>
<p>Fill in the form and click Generate to get started</p>
</div>
</div>
);
}
if (state === 'generating') {
return (
<div className="h-full flex items-center justify-center">
<div className="text-center">
<div className="animate-spin text-4xl mb-4"></div>
<p className="text-gray-600 font-medium">Generating your output...</p>
<p className="text-gray-400 text-sm mt-1">This may take 15-30 seconds</p>
</div>
</div>
);
}
return (
<div className="relative">
{state === 'preview' && (
<>
<div className="prose prose-sm max-w-none bg-gray-50 p-6 rounded-lg border whitespace-pre-wrap font-mono text-sm">
{preview}
<span className="text-gray-300">{'...'.repeat(20)}</span>
</div>
<BlurOverlay onUnlock={onPaymentRequest} loading={paymentLoading} />
</>
)}
{(state === 'unlocked' || state === 'done') && sessionId && displayOutput && (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="font-semibold text-dark">Your Output</h3>
<div className="flex gap-2">
{!finalizing && (
<button
onClick={handleFinalize}
className="px-4 py-2 bg-accent text-white rounded-lg text-sm hover:bg-accent/90"
>
🎯 Finalize
</button>
)}
<ExportMenu sessionId={sessionId} />
</div>
</div>
<EditableOutput
content={displayOutput}
sessionId={sessionId}
tool={tool}
isPaid={true}
improvementsUsed={0}
maxImprovements={5}
onImproved={setOutput}
/>
</div>
)}
</div>
);
}