Spaces:
Sleeping
Sleeping
| "use client"; | |
| import { | |
| Accordion, | |
| ActionIcon, | |
| LoadingOverlay, | |
| SegmentedControl, | |
| Textarea, | |
| } from "@mantine/core"; | |
| import { useForm } from "@mantine/form"; | |
| import { | |
| IconArrowDownRight, | |
| IconMinus, | |
| IconPlus, | |
| IconRotateClockwise, | |
| } from "@tabler/icons-react"; | |
| import React, { useCallback, useEffect, useRef } from "react"; | |
| import useDebounce from "../hooks/debounce"; | |
| import FieldSet from "./FieldSet"; | |
| import Label from "./Label"; | |
| import SelectInput from "./SelectInput"; | |
| import SlideInput from "./SliderInput"; | |
| import TextInput from "./TextInput"; | |
| export const Controls = React.memo(({ remoteParams, onData }) => { | |
| const init = useRef(false); | |
| const form = useForm({ | |
| initialValues: { | |
| prompt: "", | |
| negative_prompt: "", | |
| steps: 1, | |
| guidance: 0.1, | |
| seed: 45678, | |
| resolution: "720", | |
| }, | |
| }); | |
| const debounce = useDebounce(); | |
| const handleChange = useCallback((v) => onData(v), [onData]); | |
| useEffect(() => { | |
| if (!remoteParams) return; | |
| if (!init.current) { | |
| form.setValues(remoteParams); | |
| init.current = true; | |
| } | |
| }, [form, remoteParams]); | |
| useEffect(() => { | |
| if (!form) return; | |
| form.isDirty() && debounce(() => handleChange(form.values), 500); | |
| }, [form, debounce, handleChange]); | |
| return ( | |
| <div className="relative flex flex-col gap-4"> | |
| {!remoteParams && ( | |
| <LoadingOverlay | |
| visible={true} | |
| zIndex={1000} | |
| overlayProps={{ blur: 2 }} | |
| loaderProps={{ | |
| children: ( | |
| <div className="animate-pulse font-medium"> | |
| Waiting for bot to join... | |
| </div> | |
| ), | |
| }} | |
| className="h-screen" | |
| /> | |
| )} | |
| <Accordion | |
| defaultValue="prompt" | |
| classNames={{ | |
| chevron: "w-[20px] text-indigo-500", | |
| label: "font-medium text-zinc-900", | |
| control: "hover:bg-zinc-50", | |
| item: "border-zinc-200", | |
| }} | |
| chevron={<IconArrowDownRight size={20} stroke={2} className="block" />} | |
| > | |
| <Accordion.Item value="prompt" key="prompt"> | |
| <Accordion.Control value="prompt">Prompt</Accordion.Control> | |
| <Accordion.Panel> | |
| <FieldSet> | |
| <div className="flex flex-col"> | |
| <div className="flex flex-row items-center gap-2 mb-2"> | |
| <IconPlus size={20} stroke={1} className="text-emerald-500" /> | |
| <span className="font-mono uppercase text-xs tracking-wider text-emerald-600"> | |
| Positive Prompt | |
| </span> | |
| </div> | |
| <Textarea | |
| autosize | |
| className="rounded-md" | |
| minRows={3} | |
| defaultValue={form.getInputProps("prompt").value} | |
| onChange={(e) => form.setFieldValue("prompt", e.target.value)} | |
| classNames={{ | |
| input: | |
| "p-3 font-mono text-emerald-800 bg-emerald-600/[0.07] border-emerald-500/30 focus:border-emerald-500 focus:ring-1 focus:ring-inset focus:ring-emerald-500", | |
| }} | |
| /> | |
| </div> | |
| <div className="flex flex-col"> | |
| <div className="flex flex-row items-center gap-2 mb-2"> | |
| <IconMinus size={20} stroke={1} className="text-rose-500" /> | |
| <span className="font-mono uppercase text-xs tracking-wider text-rose-600"> | |
| Negative Prompt | |
| </span> | |
| </div> | |
| <Textarea | |
| autosize | |
| className="rounded-md" | |
| minRows={3} | |
| defaultValue={form.getInputProps("negative_prompt").value} | |
| onChange={(e) => | |
| form.setFieldValue("negative_prompt", e.target.value) | |
| } | |
| classNames={{ | |
| input: | |
| "p-3 font-mono text-rose-800 bg-rose-600/[0.07] border-rose-500/30 focus:border-rose-500 focus:ring-1 focus:ring-inset focus:ring-rose-500", | |
| }} | |
| /> | |
| </div> | |
| </FieldSet> | |
| </Accordion.Panel> | |
| </Accordion.Item> | |
| <Accordion.Item value="camera" key="camera"> | |
| <Accordion.Control value="camera">Camera</Accordion.Control> | |
| <Accordion.Panel> | |
| <FieldSet> | |
| <SelectInput label="Device" /> | |
| <div> | |
| <Label>Resolution</Label> | |
| <SegmentedControl | |
| fullWidth | |
| size="sm" | |
| radius="xl" | |
| defaultValue={"720"} | |
| data={[ | |
| { label: "480P", value: "480" }, | |
| { label: "720P", value: "720" }, | |
| { label: "1080P", value: "1080" }, | |
| ]} | |
| onChange={(v) => form.setFieldValue("resolution", v)} | |
| /> | |
| </div> | |
| </FieldSet> | |
| </Accordion.Panel> | |
| </Accordion.Item> | |
| <Accordion.Item value="diffusion" key="diffusion"> | |
| <Accordion.Control value="inference">Diffusion</Accordion.Control> | |
| <Accordion.Panel> | |
| <div className="flex flex-col gap-4"> | |
| <TextInput | |
| label="Seed" | |
| type="number" | |
| fieldProps={form.getInputProps("seed")} | |
| > | |
| <ActionIcon | |
| radius="sm" | |
| variant="subtle" | |
| onClick={() => | |
| form.setFieldValue( | |
| "seed", | |
| Math.floor(Math.random() * 100000001) | |
| ) | |
| } | |
| > | |
| <IconRotateClockwise | |
| style={{ width: "70%", height: "70%" }} | |
| stroke={2} | |
| /> | |
| </ActionIcon> | |
| </TextInput> | |
| <SlideInput | |
| label="Steps" | |
| fieldProps={form.getInputProps("steps")} | |
| step={1} | |
| min={1} | |
| max={15} | |
| marks={[ | |
| { value: 1, label: "Low" }, | |
| { value: 15, label: "High" }, | |
| ]} | |
| /> | |
| <SlideInput | |
| label="Guidance" | |
| fieldProps={form.getInputProps("guidance")} | |
| step={0.001} | |
| min={0} | |
| max={30} | |
| marks={[ | |
| { value: 0, label: "None" }, | |
| { value: 2.5, label: "Low" }, | |
| { value: 7.5, label: "Normal" }, | |
| { value: 12.5, label: "Strict" }, | |
| { value: 17.5, label: "Very Strict" }, | |
| { value: 30, label: "Max" }, | |
| ]} | |
| /> | |
| </div> | |
| </Accordion.Panel> | |
| </Accordion.Item> | |
| <Accordion.Item value="cn" key="cn"> | |
| <Accordion.Control value="cn">Control Net</Accordion.Control> | |
| <Accordion.Panel>Control net</Accordion.Panel> | |
| </Accordion.Item> | |
| </Accordion> | |
| </div> | |
| ); | |
| }); | |
| Controls.displayName = "Controls"; | |
| export default Controls; | |