Files
MelodyMuse/src/components/cards/VideoPromptsCard.tsx
T

125 lines
4.0 KiB
TypeScript

import { useState } from "react";
import { ResultCard } from "../ResultCard";
import { CopyButton } from "../CopyButton";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faVideo, faUndo } from "@fortawesome/free-solid-svg-icons";
import { useAutoHeight } from "../../lib/useAutoHeight";
import {
NEGATIVE_VIDEO_PROMPT,
type VideoPrompt,
type VideoPromptType,
} from "../../lib/types";
interface VideoPromptsCardProps {
prompts: VideoPrompt[];
originalPrompts: VideoPrompt[];
onChange: (index: number, next: string) => void;
onRegenerate: () => void;
regenerating: boolean;
}
const TABS: VideoPromptType[] = ["Abstract", "Cinematic", "Hybrid"];
export function VideoPromptsCard({
prompts,
originalPrompts,
onChange,
onRegenerate,
regenerating,
}: VideoPromptsCardProps) {
const [tab, setTab] = useState<VideoPromptType>("Abstract");
const idx = prompts.findIndex((p) => p.type === tab);
const current = idx >= 0 ? prompts[idx] : undefined;
const originalCurrent = originalPrompts.find((p) => p.type === tab);
const dirty = current?.prompt !== originalCurrent?.prompt;
const ref = useAutoHeight(current?.prompt ?? "", 120);
return (
<ResultCard
index={3}
icon={<FontAwesomeIcon icon={faVideo} className="text-rose-400" />}
title="Video Prompts"
onRegenerate={onRegenerate}
regenerating={regenerating}
headerExtra={
current ? (
<>
{dirty && originalCurrent && (
<button
type="button"
onClick={() => onChange(idx, originalCurrent.prompt)}
className="btn-ghost"
aria-label="Revert video prompt to last generated version"
title="Revert to last generated"
>
<FontAwesomeIcon icon={faUndo} className="w-3.5 h-3.5" />
<span>Revert</span>
</button>
)}
<CopyButton value={current.prompt} label="Copy" />
</>
) : null
}
>
<div className="flex items-center gap-1 mb-4 p-1 rounded-lg bg-bg-hover/40 border border-border w-fit">
{TABS.map((t) => {
const isActive = t === tab;
return (
<button
key={t}
type="button"
onClick={() => setTab(t)}
className={
isActive
? "px-3 py-1.5 rounded-md text-sm font-medium bg-accent-primary text-white transition-colors"
: "px-3 py-1.5 rounded-md text-sm font-medium text-fg-muted hover:text-fg hover:bg-bg-hover transition-colors"
}
role="tab"
aria-selected={isActive}
>
{t}
</button>
);
})}
</div>
{current ? (
<div className="space-y-3">
<div>
<div className="flex items-center justify-between mb-2">
<label className="text-sm font-medium text-fg">Main prompt</label>
</div>
<textarea
ref={ref}
value={current.prompt}
onChange={(e) => onChange(idx, e.target.value)}
rows={5}
className="textarea text-sm leading-relaxed scrollbar-thin"
style={{ minHeight: 120 }}
/>
</div>
<p className="text-sm italic text-fg-muted">
Best for:{" "}
<span className="not-italic text-fg">
{current.tool_recommendation}
</span>
</p>
<div className="mt-1 p-3 rounded-lg bg-bg-hover/40 border border-border">
<p className="text-[11px] uppercase tracking-wider text-fg-muted mb-1">
Negative
</p>
<p className="text-xs text-fg-muted leading-relaxed">
{NEGATIVE_VIDEO_PROMPT}
</p>
</div>
</div>
) : (
<p className="text-sm text-fg-muted">No prompt for this tab yet.</p>
)}
</ResultCard>
);
}