feat: add millon js, add models picker dialog, update builder

This commit is contained in:
BennyKok
2024-01-07 17:22:28 +08:00
parent 3c4bce630e
commit 01a9c1a1d6
33 changed files with 1693 additions and 190 deletions
@@ -0,0 +1,157 @@
"use client";
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils";
import { Check, ChevronsUpDown } from "lucide-react";
import * as React from "react";
import { useRef } from "react";
import { z } from "zod";
const Model = z.object({
name: z.string(),
type: z.string(),
base: z.string(),
save_path: z.string(),
description: z.string(),
reference: z.string(),
filename: z.string(),
url: z.string(),
});
const ModelList = z.array(Model);
export const ModelListWrapper = z.object({
models: ModelList,
});
export function ModelPickerView({
field,
}: Pick<AutoFormInputComponentProps, "field">) {
const value = (field.value as z.infer<typeof ModelList>) ?? [];
const [open, setOpen] = React.useState(false);
const [modelList, setModelList] =
React.useState<z.infer<typeof ModelListWrapper>>();
// const [selectedModels, setSelectedModels] = React.useState<
// z.infer<typeof ModelList>
// >(field.value ?? []);
React.useEffect(() => {
const controller = new AbortController();
fetch(
"https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/model-list.json",
{
signal: controller.signal,
}
)
.then((x) => x.json())
.then((a) => {
setModelList(ModelListWrapper.parse(a));
});
return () => {
controller.abort();
};
}, []);
function toggleModel(model: z.infer<typeof Model>) {
const prevSelectedModels = value;
if (
prevSelectedModels.some(
(selectedModel) =>
selectedModel.url + selectedModel.name === model.url + model.name
)
) {
field.onChange(
prevSelectedModels.filter(
(selectedModel) =>
selectedModel.url + selectedModel.name !== model.url + model.name
)
);
} else {
field.onChange([...prevSelectedModels, model]);
}
}
// React.useEffect(() => {
// field.onChange(selectedModels);
// }, [selectedModels]);
const containerRef = useRef<HTMLDivElement>(null);
return (
<div className="" ref={containerRef}>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-full justify-between flex"
>
Select models... ({value.length} selected)
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[375px] p-0" side="top">
<Command>
<CommandInput placeholder="Search framework..." className="h-9" />
<CommandEmpty>No framework found.</CommandEmpty>
<CommandList className="pointer-events-auto">
<CommandGroup>
{modelList?.models.map((model) => (
<CommandItem
key={model.url + model.name}
value={model.url}
onSelect={() => {
toggleModel(model);
// Update field.onChange to pass the array of selected models
}}
>
{model.name}
<Check
className={cn(
"ml-auto h-4 w-4",
value.some(
(selectedModel) => selectedModel.url === model.url
)
? "opacity-100"
: "opacity-0"
)}
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
{field.value && (
<ScrollArea className="w-full bg-gray-100 mx-auto max-w-[360px] rounded-lg mt-2">
<div className="max-h-[200px]">
<pre className="p-2 rounded-md text-xs ">
{JSON.stringify(field.value, null, 2)}
</pre>
</div>
</ScrollArea>
)}
</div>
);
}
@@ -0,0 +1,125 @@
"use client";
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils";
import { findAllDeployments } from "@/server/curdDeploments";
import { Check, ChevronsUpDown } from "lucide-react";
import * as React from "react";
export function SnapshotPickerView({
field,
}: Pick<AutoFormInputComponentProps, "field">) {
const [open, setOpen] = React.useState(false);
const [selected, setSelected] = React.useState<string | null>(null);
const [frameworks, setFramework] = React.useState<
{
id: string;
label: string;
value: string;
}[]
>();
React.useEffect(() => {
findAllDeployments().then((a) => {
console.log(a);
const frameworks = a
.map((item) => {
if (
item.deployments.length == 0 ||
item.deployments[0].version.snapshot == null
)
return null;
return {
id: item.deployments[0].version.id,
label: `${item.name} - ${item.deployments[0].environment}`,
value: JSON.stringify(item.deployments[0].version.snapshot),
};
})
.filter((item): item is NonNullable<typeof item> => item != null);
setFramework(frameworks);
});
}, []);
function findItem(value: string) {
// console.log(frameworks);
return frameworks?.find((item) => item.id === value);
}
return (
<div className="">
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-full justify-between flex"
>
{selected ? findItem(selected)?.label : "Select snapshot..."}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[375px] p-0">
<Command>
<CommandInput placeholder="Search framework..." className="h-9" />
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
{frameworks?.map((framework) => (
<CommandItem
key={framework.id}
value={framework.id}
onSelect={(currentValue) => {
setSelected(currentValue);
const json =
frameworks?.find((item) => item.id === currentValue)
?.value ?? null;
field.onChange(json ? JSON.parse(json) : null);
setOpen(false);
}}
>
{framework.label}
<Check
className={cn(
"ml-auto h-4 w-4",
field.value === framework.value
? "opacity-100"
: "opacity-0"
)}
/>
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
{field.value && (
<ScrollArea className="w-full bg-gray-100 mx-auto max-w-[360px] rounded-lg mt-2">
<div className="max-h-[200px]">
<pre className="p-2 rounded-md text-xs ">
{JSON.stringify(field.value, null, 2)}
</pre>
</div>
</ScrollArea>
)}
</div>
);
}
@@ -0,0 +1,39 @@
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
import {
FormControl,
FormDescription,
FormItem,
FormLabel,
FormMessage,
} from "../ui/form";
import { LoadingIcon } from "@/components/LoadingIcon";
import { ModelPickerView } from "@/components/custom-form/ModelPickerView";
// import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
import * as React from "react";
import { Suspense } from "react";
export default function AutoFormModelsPicker({
label,
isRequired,
field,
fieldConfigItem,
zodItem,
}: AutoFormInputComponentProps) {
return (
<FormItem>
<FormLabel>
{label}
{isRequired && <span className="text-destructive"> *</span>}
</FormLabel>
<FormControl>
<Suspense fallback={<LoadingIcon />}>
<ModelPickerView field={field} />
</Suspense>
</FormControl>
{fieldConfigItem.description && (
<FormDescription>{fieldConfigItem.description}</FormDescription>
)}
<FormMessage />
</FormItem>
);
}
@@ -0,0 +1,39 @@
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
import {
FormControl,
FormDescription,
FormItem,
FormLabel,
FormMessage,
} from "../ui/form";
import { SnapshotPickerView } from "./SnapshotPickerView";
import { LoadingIcon } from "@/components/LoadingIcon";
// import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
import * as React from "react";
import { Suspense } from "react";
export default function AutoFormSnapshotPicker({
label,
isRequired,
field,
fieldConfigItem,
zodItem,
}: AutoFormInputComponentProps) {
return (
<FormItem>
<FormLabel>
{label}
{isRequired && <span className="text-destructive"> *</span>}
</FormLabel>
<FormControl>
<Suspense fallback={<LoadingIcon />}>
<SnapshotPickerView field={field} />
</Suspense>
</FormControl>
{fieldConfigItem.description && (
<FormDescription>{fieldConfigItem.description}</FormDescription>
)}
<FormMessage />
</FormItem>
);
}