feat(web): seed control with -1=random + randomize button
Browse files
web/src/components/ParamsPanel.tsx
CHANGED
|
@@ -13,6 +13,39 @@ function renderControl(
|
|
| 13 |
) {
|
| 14 |
const id = `param-${s.name}`;
|
| 15 |
const current: unknown = values[s.name] ?? s.default;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
if (s.type === "float" || s.type === "int") {
|
| 17 |
const n = typeof current === "number" ? current : Number(current);
|
| 18 |
return (
|
|
|
|
| 13 |
) {
|
| 14 |
const id = `param-${s.name}`;
|
| 15 |
const current: unknown = values[s.name] ?? s.default;
|
| 16 |
+
if (s.name === "seed") {
|
| 17 |
+
const v = (values[s.name] ?? s.default) as number;
|
| 18 |
+
return (
|
| 19 |
+
<div key={s.name} className="space-y-1.5">
|
| 20 |
+
<label htmlFor={id} className="label-mono">{s.label}</label>
|
| 21 |
+
<div className="flex items-center gap-3">
|
| 22 |
+
<input
|
| 23 |
+
id={id}
|
| 24 |
+
aria-label={s.label}
|
| 25 |
+
type="number"
|
| 26 |
+
min={s.min}
|
| 27 |
+
step={s.step ?? 1}
|
| 28 |
+
value={v}
|
| 29 |
+
onChange={(e) => set(s.name, Number(e.target.value))}
|
| 30 |
+
className="field-input !w-44 font-mono text-[12px] py-1"
|
| 31 |
+
/>
|
| 32 |
+
<button
|
| 33 |
+
type="button"
|
| 34 |
+
onClick={() => set(s.name, -1)}
|
| 35 |
+
className="label-mono hover:text-foreground transition-colors"
|
| 36 |
+
>
|
| 37 |
+
↻ random
|
| 38 |
+
</button>
|
| 39 |
+
{v === -1 && (
|
| 40 |
+
<span className="label-mono text-muted-foreground">(random per generate)</span>
|
| 41 |
+
)}
|
| 42 |
+
</div>
|
| 43 |
+
{s.help && (
|
| 44 |
+
<p className="text-[11px] text-muted-foreground/80 italic">{s.help}</p>
|
| 45 |
+
)}
|
| 46 |
+
</div>
|
| 47 |
+
);
|
| 48 |
+
}
|
| 49 |
if (s.type === "float" || s.type === "int") {
|
| 50 |
const n = typeof current === "number" ? current : Number(current);
|
| 51 |
return (
|
web/src/test/ParamsPanel.test.tsx
CHANGED
|
@@ -58,3 +58,26 @@ describe("ParamsPanel groups", () => {
|
|
| 58 |
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ top_p: 0.6 }));
|
| 59 |
});
|
| 60 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ top_p: 0.6 }));
|
| 59 |
});
|
| 60 |
});
|
| 61 |
+
|
| 62 |
+
describe("ParamsPanel seed control", () => {
|
| 63 |
+
it("renders an int input plus a randomize button for seed", () => {
|
| 64 |
+
const specs: ParamSpec[] = [
|
| 65 |
+
{ name: "seed", label: "Seed", type: "int", default: -1, min: -1, step: 1, group: "advanced" },
|
| 66 |
+
];
|
| 67 |
+
render(<ParamsPanel specs={specs} values={{}} onChange={() => {}} />);
|
| 68 |
+
fireEvent.click(screen.getByText(/advanced/i));
|
| 69 |
+
expect(screen.getByLabelText(/^seed$/i)).toHaveAttribute("type", "number");
|
| 70 |
+
expect(screen.getByRole("button", { name: /random/i })).toBeInTheDocument();
|
| 71 |
+
});
|
| 72 |
+
|
| 73 |
+
it("clicking randomize sets seed to -1 via onChange", () => {
|
| 74 |
+
const specs: ParamSpec[] = [
|
| 75 |
+
{ name: "seed", label: "Seed", type: "int", default: -1, min: -1, step: 1, group: "advanced" },
|
| 76 |
+
];
|
| 77 |
+
const onChange = vi.fn();
|
| 78 |
+
render(<ParamsPanel specs={specs} values={{ seed: 42 }} onChange={onChange} />);
|
| 79 |
+
fireEvent.click(screen.getByText(/advanced/i));
|
| 80 |
+
fireEvent.click(screen.getByRole("button", { name: /random/i }));
|
| 81 |
+
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ seed: -1 }));
|
| 82 |
+
});
|
| 83 |
+
});
|