File size: 10,238 Bytes
921d377 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | import React, { useMemo, useState } from "react";
import { ChevronDown, ChevronRight, RefreshCw, Settings2, Sliders, X, Lock } from "lucide-react";
export const CREATOR_STUDIO_PARAM_DEFAULTS = {
enabled: false,
steps: 30,
cfgScale: 3.5,
creativity: 0.85,
useCustomNegativePrompt: false,
customNegativePrompt: "",
lockSeed: false,
seed: Math.floor(Math.random() * 2147483647),
};
export function CreatorStudioSettings({ value, onChange, floating = false, show = true, onRequestClose, }) {
const [advancedMode, setAdvancedMode] = useState(true);
const presetDefaults = useMemo(() => CREATOR_STUDIO_PARAM_DEFAULTS, []);
const reset = () => onChange({ ...presetDefaults, enabled: value.enabled });
const panelBody = (<div className="p-5 space-y-6 max-h-[70vh] overflow-y-auto">
{/* Enable toggle */}
<div className="flex items-center justify-between p-3 rounded-xl bg-white/5 border border-white/10">
<div>
<div className="text-sm font-medium text-white/90">Enable Parameters</div>
<div className="text-xs text-white/40">Override generation defaults for images & videos</div>
</div>
<button type="button" onClick={() => onChange({ ...value, enabled: !value.enabled })} className={`w-10 h-5 rounded-full transition-colors relative ${value.enabled ? "bg-purple-500" : "bg-white/20"}`} aria-label="Enable Parameters">
<div className={`absolute top-0.5 w-4 h-4 rounded-full bg-white transition-transform ${value.enabled ? "translate-x-5" : "translate-x-0.5"}`}/>
</button>
</div>
{/* Advanced Controls accordion */}
<button type="button" onClick={() => setAdvancedMode(!advancedMode)} className={`w-full flex items-center justify-between p-3 rounded-xl border transition-colors ${advancedMode
? "bg-purple-500/20 border-purple-500/40 text-purple-300"
: "bg-white/5 border-white/10 text-white/60 hover:border-white/20"}`}>
<span className="flex items-center gap-2 font-medium text-sm">
<Sliders size={16}/>
Advanced Controls
</span>
{advancedMode ? <ChevronDown size={16}/> : <ChevronRight size={16}/>}
</button>
{advancedMode && (<div className="space-y-4 animate-in fade-in slide-in-from-top-2 duration-200">
{/* Steps */}
<div className={`space-y-2 ${!value.enabled ? "opacity-50 pointer-events-none" : ""}`}>
<div className="flex justify-between text-xs">
<span className="uppercase tracking-wider text-white/40 font-semibold">Steps</span>
<span className="text-white/60">{value.steps}</span>
</div>
<input type="range" min={10} max={50} value={value.steps} onChange={(e) => onChange({ ...value, steps: Number(e.target.value) })} className="w-full h-1.5 bg-white/10 rounded-full appearance-none cursor-pointer
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3
[&::-webkit-slider-thumb]:bg-purple-400 [&::-webkit-slider-thumb]:rounded-full"/>
</div>
{/* CFG Scale */}
<div className={`space-y-2 ${!value.enabled ? "opacity-50 pointer-events-none" : ""}`}>
<div className="flex justify-between text-xs">
<span className="uppercase tracking-wider text-white/40 font-semibold">CFG Scale</span>
<span className="text-white/60">{value.cfgScale.toFixed(1)}</span>
</div>
<input type="range" min={1} max={15} step={0.5} value={value.cfgScale} onChange={(e) => onChange({ ...value, cfgScale: Number(e.target.value) })} className="w-full h-1.5 bg-white/10 rounded-full appearance-none cursor-pointer
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3
[&::-webkit-slider-thumb]:bg-purple-400 [&::-webkit-slider-thumb]:rounded-full"/>
</div>
{/* Creativity */}
<div className={`space-y-2 ${!value.enabled ? "opacity-50 pointer-events-none" : ""}`}>
<div className="flex justify-between text-xs">
<span className="uppercase tracking-wider text-white/40 font-semibold">Creativity</span>
<span className="text-white/60">{value.creativity.toFixed(2)}</span>
</div>
<div className="flex justify-between text-[10px] text-white/30 mb-1">
<span>More Faithful</span>
<span>More Creative</span>
</div>
<input type="range" min={0.1} max={1.0} step={0.05} value={value.creativity} onChange={(e) => onChange({ ...value, creativity: Number(e.target.value) })} className="w-full h-1.5 bg-white/10 rounded-full appearance-none cursor-pointer
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3
[&::-webkit-slider-thumb]:bg-purple-400 [&::-webkit-slider-thumb]:rounded-full"/>
</div>
{/* Custom Negative Prompt */}
<div className={`space-y-2 ${!value.enabled ? "opacity-50 pointer-events-none" : ""}`}>
<div className="flex items-center justify-between p-3 rounded-xl bg-white/5 border border-white/10">
<span className="text-sm text-white/80">Custom Negative Prompt</span>
<button type="button" onClick={() => onChange({ ...value, useCustomNegativePrompt: !value.useCustomNegativePrompt })} className={`w-10 h-5 rounded-full transition-colors relative ${value.useCustomNegativePrompt ? "bg-purple-500" : "bg-white/20"}`} aria-label="Custom Negative Prompt">
<div className={`absolute top-0.5 w-4 h-4 rounded-full bg-white transition-transform ${value.useCustomNegativePrompt ? "translate-x-5" : "translate-x-0.5"}`}/>
</button>
</div>
{value.useCustomNegativePrompt && (<div className="space-y-2">
<textarea value={value.customNegativePrompt} onChange={(e) => onChange({ ...value, customNegativePrompt: e.target.value })} placeholder="text, watermark, logo, low quality, blurry, flicker, jitter, deformed" className="w-full rounded-xl bg-white/5 border border-white/10 px-3 py-2 text-sm text-white
focus:border-purple-500/50 focus:outline-none resize-none h-20"/>
<p className="text-[10px] text-white/40">Separate terms with commas. Leave empty to use scene/model default.</p>
</div>)}
</div>
{/* Lock Seed */}
<div className={`space-y-2 ${!value.enabled ? "opacity-50 pointer-events-none" : ""}`}>
<div className="flex items-center justify-between p-3 rounded-xl bg-white/5 border border-white/10">
<span className="text-sm text-white/80 flex items-center gap-2">
<Lock size={14} className="text-white/60"/>
Lock Seed
</span>
<button type="button" onClick={() => {
const nextLock = !value.lockSeed;
onChange({
...value,
lockSeed: nextLock,
seed: nextLock ? value.seed || Math.floor(Math.random() * 2147483647) : value.seed,
});
}} className={`w-10 h-5 rounded-full transition-colors relative ${value.lockSeed ? "bg-purple-500" : "bg-white/20"}`} aria-label="Lock Seed">
<div className={`absolute top-0.5 w-4 h-4 rounded-full bg-white transition-transform ${value.lockSeed ? "translate-x-5" : "translate-x-0.5"}`}/>
</button>
</div>
{value.lockSeed && (<input type="number" value={value.seed} onChange={(e) => onChange({ ...value, seed: Number(e.target.value) })} className="w-full rounded-xl bg-white/5 border border-white/10 px-3 py-2 text-sm text-white font-mono
focus:border-purple-500/50 focus:outline-none" placeholder="Seed value"/>)}
</div>
</div>)}
<div className="p-4 rounded-xl bg-purple-900/10 border border-purple-500/20 text-xs text-purple-200/70 leading-relaxed">
<span className="font-bold text-purple-400 block mb-1">PRO TIP</span>
Enable Parameters to override defaults. Use Lock Seed to regenerate with the same composition.
</div>
</div>);
// Inline mode (inside Project Settings modal)
if (!floating) {
return (<div className="bg-black/30 border border-white/10 rounded-2xl overflow-hidden">
<div className="p-5 border-b border-white/10 flex items-center justify-between">
<h3 className="text-sm font-bold text-white flex items-center gap-2">
<Settings2 size={16}/>
PARAMETERS
</h3>
<button type="button" onClick={reset} className="text-white/50 hover:text-purple-400 transition-colors flex items-center gap-1 text-xs" title="Reset to recommended defaults">
<RefreshCw size={14}/>
Reset
</button>
</div>
{panelBody}
</div>);
}
// Floating panel mode (top-right), like your snippet
if (!show)
return null;
return (<div className="absolute top-20 right-6 z-30 bg-black/95 border border-white/10 rounded-2xl shadow-2xl w-80 backdrop-blur-xl overflow-hidden">
<div className="p-5 border-b border-white/10 flex items-center justify-between">
<h3 className="text-sm font-bold text-white flex items-center gap-2">
<Settings2 size={16}/>
PARAMETERS
</h3>
<div className="flex items-center gap-2">
<button type="button" onClick={reset} className="text-white/50 hover:text-purple-400 transition-colors flex items-center gap-1 text-xs" title="Reset to recommended defaults">
<RefreshCw size={14}/>
Reset
</button>
{onRequestClose && (<button type="button" onClick={onRequestClose} className="text-white/50 hover:text-white">
<X size={16}/>
</button>)}
</div>
</div>
{panelBody}
</div>);
}
export default CreatorStudioSettings;
|