Files
OPEN_CODEX_API/src/components/prompts/PromptGeneratingScreen.tsx
T
2026-03-18 01:07:27 -03:00

249 lines
8.8 KiB
TypeScript

import { forwardRef, useEffect, useRef, useState } from "react";
import {
Bot,
Sparkles,
FileText,
Wand2,
SlidersHorizontal,
ListChecks,
MessageSquareText,
} from "lucide-react";
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { Terminal, AnimatedStep } from "@/components/ui/terminal";
import { TypingAnimation } from "@/components/ui/typing-animation";
import { AnimatedBeam } from "@/components/ui/animated-beam";
import { cn } from "@/lib/utils";
const PROMPT_CREATE_STEPS = [
"Interpretando instruções e contexto...",
"Definindo objetivo, público e tom...",
"Organizando estrutura do prompt (seções e regras)...",
"Especificando formato de saída e restrições...",
"Adicionando critérios de qualidade e validações...",
"Incluindo exemplos e casos de borda...",
"Revisando clareza e consistência...",
"Finalizando prompt...",
] as const;
const PROMPT_REFINE_STEPS = [
"Lendo o prompt atual...",
"Identificando ambiguidades e pontos fracos...",
"Removendo redundâncias e ruído...",
"Aprimorando instruções e critérios de sucesso...",
"Ajustando tom, voz e consistência...",
"Fortalecendo formato de saída e validações...",
"Adicionando exemplos e casos de borda...",
"Consolidando melhorias e finalizando...",
] as const;
const STEP_INTERVAL_MS = 1500;
export type PromptGeneratingMode = "create" | "refine";
export interface PromptGeneratingScreenProps {
mode: PromptGeneratingMode;
progress?: number;
message?: string;
}
function visibleLineCount(progress: number, stepCount: number): number {
if (progress <= 0) return 1;
const phase = (progress / 100) * stepCount;
const stepInCycle = Math.floor(phase) % stepCount;
return Math.min(stepInCycle + 1, stepCount);
}
const BeamCircle = forwardRef<
HTMLDivElement,
{ className?: string; children?: React.ReactNode }
>(({ className, children }, ref) => (
<div
ref={ref}
className={cn(
"z-10 flex size-12 items-center justify-center rounded-full border-2 border-slate-300 bg-slate-100 text-slate-700 shadow-sm",
className
)}
>
{children}
</div>
));
BeamCircle.displayName = "BeamCircle";
export function PromptGeneratingScreen({
mode,
progress,
message = "Assim que finalizar, o modal será fechado automaticamente.",
}: PromptGeneratingScreenProps) {
const steps = mode === "refine" ? PROMPT_REFINE_STEPS : PROMPT_CREATE_STEPS;
const stepCount = steps.length;
const containerRef = useRef<HTMLDivElement>(null);
const sparklesRef = useRef<HTMLDivElement>(null);
const fileRef = useRef<HTMLDivElement>(null);
const slidersRef = useRef<HTMLDivElement>(null);
const centerRef = useRef<HTMLDivElement>(null);
const wandRef = useRef<HTMLDivElement>(null);
const checklistRef = useRef<HTMLDivElement>(null);
const chatRef = useRef<HTMLDivElement>(null);
const [loopStep, setLoopStep] = useState(0);
useEffect(() => {
if (progress !== undefined) return;
const id = setInterval(() => {
setLoopStep((s) => (s + 1) % stepCount);
}, STEP_INTERVAL_MS);
return () => clearInterval(id);
}, [progress, stepCount]);
const visibleCount =
progress !== undefined
? visibleLineCount(progress, stepCount)
: loopStep + 1;
return (
<Dialog open>
<DialogContent
hideClose
overlayClassName="bg-black/50"
className="max-w-2xl gap-0 overflow-hidden bg-white p-0 sm:rounded-xl"
>
<div className="flex flex-col gap-4 p-5">
<div className="flex items-center gap-3 rounded-t-lg border border-b-0 border-border bg-zinc-100 px-3 py-2.5">
<div className="flex gap-1.5">
<span className="size-3 rounded-full bg-[#ff5f57]" aria-hidden />
<span className="size-3 rounded-full bg-[#febc2e]" aria-hidden />
<span className="size-3 rounded-full bg-[#28c840]" aria-hidden />
</div>
<span className="font-mono text-xs text-muted-foreground">
codex prompt {mode === "refine" ? "melhorando" : "gerando"}
</span>
</div>
<Terminal className="min-h-[200px] space-y-2 rounded-t-none border-t-0 bg-white p-4 font-mono text-sm">
<AnimatedStep delay={0} className="text-muted-foreground">
{`> codex prompt --${mode === "refine" ? "refine" : "create"}`}
</AnimatedStep>
{steps.slice(0, visibleCount).map((text, i) => (
<div key={`${visibleCount}-${i}-${text}`} className="flex items-center gap-0">
{i < visibleCount - 1 ? (
<span className="text-sky-600 dark:text-sky-400">
{text}
</span>
) : (
<TypingAnimation
key={`typing-${visibleCount}`}
startOnView={false}
showCursor
cursorStyle="block"
className="text-sky-600 dark:text-sky-400"
>
{`${text}`}
</TypingAnimation>
)}
</div>
))}
</Terminal>
{message && (
<p className="text-center text-sm text-muted-foreground">
{message}
</p>
)}
<div className="rounded-xl border border-border bg-white p-6">
<div
ref={containerRef}
className="relative flex h-[260px] w-full items-center justify-center overflow-hidden"
>
<div className="flex size-full max-h-[220px] max-w-lg flex-col items-stretch justify-between">
<div className="flex flex-row items-center justify-between">
<BeamCircle ref={sparklesRef}>
<Sparkles className="size-6 text-sky-500" />
</BeamCircle>
<BeamCircle ref={fileRef}>
<FileText className="size-6 text-slate-600" />
</BeamCircle>
</div>
<div className="flex flex-row items-center justify-between">
<BeamCircle ref={slidersRef}>
<SlidersHorizontal className="size-6 text-indigo-500" />
</BeamCircle>
<BeamCircle ref={centerRef} className="size-14 border-slate-400 bg-slate-200">
<Bot className="size-8 text-slate-700" />
</BeamCircle>
<BeamCircle ref={wandRef}>
<Wand2 className="size-6 text-amber-500" />
</BeamCircle>
</div>
<div className="flex flex-row items-center justify-between">
<BeamCircle ref={checklistRef}>
<ListChecks className="size-6 text-slate-600" />
</BeamCircle>
<BeamCircle ref={chatRef}>
<MessageSquareText className="size-6 text-slate-600" />
</BeamCircle>
</div>
</div>
<AnimatedBeam
containerRef={containerRef}
fromRef={sparklesRef}
toRef={centerRef}
curvature={-75}
endYOffset={-10}
gradientStartColor="#0ea5e9"
gradientStopColor="#64748b"
/>
<AnimatedBeam
containerRef={containerRef}
fromRef={fileRef}
toRef={centerRef}
curvature={-75}
endYOffset={-10}
reverse
gradientStartColor="#94a3b8"
gradientStopColor="#64748b"
/>
<AnimatedBeam
containerRef={containerRef}
fromRef={slidersRef}
toRef={centerRef}
gradientStartColor="#6366f1"
gradientStopColor="#64748b"
/>
<AnimatedBeam
containerRef={containerRef}
fromRef={wandRef}
toRef={centerRef}
reverse
gradientStartColor="#f59e0b"
gradientStopColor="#64748b"
/>
<AnimatedBeam
containerRef={containerRef}
fromRef={checklistRef}
toRef={centerRef}
curvature={75}
endYOffset={10}
gradientStartColor="#94a3b8"
gradientStopColor="#64748b"
/>
<AnimatedBeam
containerRef={containerRef}
fromRef={chatRef}
toRef={centerRef}
curvature={75}
endYOffset={10}
reverse
gradientStartColor="#94a3b8"
gradientStopColor="#64748b"
/>
</div>
</div>
</div>
</DialogContent>
</Dialog>
);
}