Initial commit

This commit is contained in:
2026-03-18 18:26:24 +08:00
commit c784d07796
60 changed files with 3756 additions and 0 deletions
+124
View File
@@ -0,0 +1,124 @@
'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>
);
}