交互原型大版本
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
/**
|
||||
* 高级控制面板(Phase 2 占位)
|
||||
*
|
||||
* 对应 INFOLAYER.md 3.3 节「高级控制」:提供给高级用户的参数调节抽屉。
|
||||
* 当前所有参数均为占位,不会真实下发到后端。
|
||||
*/
|
||||
export function AdvancedControls() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [cfg, setCfg] = useState(7.5);
|
||||
const [steps, setSteps] = useState(28);
|
||||
const [sampler, setSampler] = useState("DPM++ 2M Karras");
|
||||
const [seed, setSeed] = useState("");
|
||||
const [styleWeight, setStyleWeight] = useState(0.8);
|
||||
const [charWeight, setCharWeight] = useState(0.85);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setOpen(!open)}
|
||||
className="h-7 px-2 rounded-lg text-xs flex items-center gap-1.5
|
||||
border border-[var(--border)] bg-[var(--bg-tertiary)]
|
||||
text-[var(--text-secondary)] hover:text-[var(--text-primary)]
|
||||
hover:border-[var(--accent)]/40 cursor-pointer transition-all"
|
||||
title="高级控制(占位)"
|
||||
>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
|
||||
</svg>
|
||||
高级
|
||||
<span className="phase-chip">P2</span>
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
{open && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 8 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 8 }}
|
||||
className="absolute bottom-full mb-2 right-0 w-80 max-w-[calc(100vw-24px)]
|
||||
rounded-xl surface-2 border border-[var(--border)] shadow-xl
|
||||
backdrop-blur-xl z-40 p-3"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="typo-strong text-xs">高级控制(占位)</span>
|
||||
<button
|
||||
onClick={() => setOpen(false)}
|
||||
className="text-xs text-[var(--text-secondary)] hover:text-[var(--text-primary)] cursor-pointer"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<Slider label="CFG Scale" value={cfg} min={1} max={20} step={0.5} onChange={setCfg} />
|
||||
<Slider label="Steps" value={steps} min={10} max={60} step={1} onChange={setSteps} />
|
||||
<Slider label="风格强度" value={styleWeight} min={0} max={1.5} step={0.05} onChange={setStyleWeight} />
|
||||
<Slider label="角色一致性" value={charWeight} min={0} max={1.5} step={0.05} onChange={setCharWeight} />
|
||||
|
||||
<div>
|
||||
<label className="block typo-micro mb-1" style={{ textTransform: "none" }}>采样器</label>
|
||||
<select
|
||||
value={sampler}
|
||||
onChange={(e) => setSampler(e.target.value)}
|
||||
className="w-full px-2 py-1.5 text-xs rounded-md bg-[var(--bg-primary)]
|
||||
border border-[var(--border)] focus:border-[var(--accent)]/40 outline-none"
|
||||
>
|
||||
{["DPM++ 2M Karras", "Euler a", "DDIM", "UniPC"].map((s) => (
|
||||
<option key={s} value={s}>{s}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block typo-micro mb-1" style={{ textTransform: "none" }}>Seed</label>
|
||||
<div className="flex gap-1.5">
|
||||
<input
|
||||
value={seed}
|
||||
onChange={(e) => setSeed(e.target.value)}
|
||||
placeholder="留空为随机"
|
||||
className="flex-1 px-2 py-1.5 text-xs rounded-md bg-[var(--bg-primary)]
|
||||
border border-[var(--border)] focus:border-[var(--accent)]/40 outline-none font-mono"
|
||||
/>
|
||||
<button
|
||||
onClick={() => setSeed(Math.floor(Math.random() * 9_999_999).toString())}
|
||||
className="px-2 py-1.5 text-xs rounded-md border border-[var(--border)]
|
||||
text-[var(--text-secondary)] hover:text-[var(--text-primary)]
|
||||
cursor-pointer"
|
||||
>
|
||||
🎲
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 pt-3 border-t border-[var(--border)] text-[10px] text-[var(--text-secondary)]">
|
||||
参数为占位,真实生效链路待 Phase 2 交付。
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Slider({
|
||||
label, value, min, max, step, onChange,
|
||||
}: { label: string; value: number; min: number; max: number; step: number; onChange: (v: number) => void }) {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<label className="typo-micro" style={{ textTransform: "none" }}>{label}</label>
|
||||
<span className="text-[11px] text-[var(--accent)] font-mono">{value}</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
value={value}
|
||||
onChange={(e) => onChange(parseFloat(e.target.value))}
|
||||
className="w-full accent-[var(--accent)]"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user