feat: add drop down selection for storage
This commit is contained in:
parent
3b7db4480b
commit
757c587901
@ -15,7 +15,7 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import type { getAllUserModels as getAllUserModels } from "@/server/getAllUserModel";
|
import type { getAllUserModels } from "@/server/getAllUserModel";
|
||||||
import type {
|
import type {
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
ColumnFiltersState,
|
ColumnFiltersState,
|
||||||
@ -46,8 +46,10 @@ export const columns: ColumnDef<ModelItemList>[] = [
|
|||||||
id: "select",
|
id: "select",
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={table.getIsAllPageRowsSelected() ||
|
checked={
|
||||||
(table.getIsSomePageRowsSelected() && "indeterminate")}
|
table.getIsAllPageRowsSelected() ||
|
||||||
|
(table.getIsSomePageRowsSelected() && "indeterminate")
|
||||||
|
}
|
||||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||||
aria-label="Select all"
|
aria-label="Select all"
|
||||||
/>
|
/>
|
||||||
@ -79,12 +81,10 @@ export const columns: ColumnDef<ModelItemList>[] = [
|
|||||||
const model = row.original;
|
const model = row.original;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{
|
{/*<a
|
||||||
/*<a
|
|
||||||
className="hover:underline flex gap-2"
|
className="hover:underline flex gap-2"
|
||||||
href={`/storage/${model.id}`} // TODO
|
href={`/storage/${model.id}`} // TODO
|
||||||
>*/
|
>*/}
|
||||||
}
|
|
||||||
<span className="truncate max-w-[200px]">
|
<span className="truncate max-w-[200px]">
|
||||||
{row.original.model_name}
|
{row.original.model_name}
|
||||||
</span>
|
</span>
|
||||||
@ -110,9 +110,13 @@ export const columns: ColumnDef<ModelItemList>[] = [
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
variant={row.original.status === "failed"
|
variant={
|
||||||
? "red"
|
row.original.status === "failed"
|
||||||
: (row.original.status === "started" ? "yellow" : "green")}
|
? "red"
|
||||||
|
: row.original.status === "started"
|
||||||
|
? "yellow"
|
||||||
|
: "green"
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{row.original.status}
|
{row.original.status}
|
||||||
</Badge>
|
</Badge>
|
||||||
@ -184,10 +188,10 @@ export const columns: ColumnDef<ModelItemList>[] = [
|
|||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const model_type_map: Record<modelEnumType, any> = {
|
const model_type_map: Record<modelEnumType, any> = {
|
||||||
"checkpoint": "amber",
|
checkpoint: "amber",
|
||||||
"lora": "green",
|
lora: "green",
|
||||||
"embedding": "violet",
|
embedding: "violet",
|
||||||
"vae": "teal",
|
vae: "teal",
|
||||||
};
|
};
|
||||||
|
|
||||||
function getBadgeColor(modelType: modelEnumType) {
|
function getBadgeColor(modelType: modelEnumType) {
|
||||||
@ -257,9 +261,8 @@ export function ModelList({ data }: { data: ModelItemList[] }) {
|
|||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
const [columnVisibility, setColumnVisibility] = React.useState<
|
const [columnVisibility, setColumnVisibility] =
|
||||||
VisibilityState
|
React.useState<VisibilityState>({});
|
||||||
>({});
|
|
||||||
const [rowSelection, setRowSelection] = React.useState({});
|
const [rowSelection, setRowSelection] = React.useState({});
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
@ -286,10 +289,12 @@ export function ModelList({ data }: { data: ModelItemList[] }) {
|
|||||||
<div className="flex flex-row w-full items-center py-4">
|
<div className="flex flex-row w-full items-center py-4">
|
||||||
<Input
|
<Input
|
||||||
placeholder="Filter workflows..."
|
placeholder="Filter workflows..."
|
||||||
value={(table.getColumn("model_name")?.getFilterValue() as string) ??
|
value={
|
||||||
""}
|
(table.getColumn("model_name")?.getFilterValue() as string) ?? ""
|
||||||
|
}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
table.getColumn("model_name")?.setFilterValue(event.target.value)}
|
table.getColumn("model_name")?.setFilterValue(event.target.value)
|
||||||
|
}
|
||||||
className="max-w-sm"
|
className="max-w-sm"
|
||||||
/>
|
/>
|
||||||
<div className="ml-auto flex gap-2">
|
<div className="ml-auto flex gap-2">
|
||||||
@ -304,7 +309,7 @@ export function ModelList({ data }: { data: ModelItemList[] }) {
|
|||||||
formSchema={downloadUrlModelSchema}
|
formSchema={downloadUrlModelSchema}
|
||||||
fieldConfig={{
|
fieldConfig={{
|
||||||
url: {
|
url: {
|
||||||
fieldType: "fallback",
|
fieldType: "modelUrlPicker",
|
||||||
inputProps: { required: true },
|
inputProps: { required: true },
|
||||||
description: (
|
description: (
|
||||||
<>
|
<>
|
||||||
@ -313,6 +318,7 @@ export function ModelList({ data }: { data: ModelItemList[] }) {
|
|||||||
href="https://www.civitai.com/models"
|
href="https://www.civitai.com/models"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="underline text-blue-600 hover:text-blue-800 visited:text-purple-600"
|
className="underline text-blue-600 hover:text-blue-800 visited:text-purple-600"
|
||||||
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
civitai.com
|
civitai.com
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
@ -324,10 +330,8 @@ export function ModelList({ data }: { data: ModelItemList[] }) {
|
|||||||
fieldType: "select",
|
fieldType: "select",
|
||||||
inputProps: { required: true },
|
inputProps: { required: true },
|
||||||
description: (
|
description: (
|
||||||
<>
|
<>We'll figure this out if you pick a civitai model</>
|
||||||
We'll figure this out if you pick a civitai model
|
),
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -341,10 +345,12 @@ export function ModelList({ data }: { data: ModelItemList[] }) {
|
|||||||
{headerGroup.headers.map((header) => {
|
{headerGroup.headers.map((header) => {
|
||||||
return (
|
return (
|
||||||
<TableHead key={header.id}>
|
<TableHead key={header.id}>
|
||||||
{header.isPlaceholder ? null : flexRender(
|
{header.isPlaceholder
|
||||||
header.column.columnDef.header,
|
? null
|
||||||
header.getContext(),
|
: flexRender(
|
||||||
)}
|
header.column.columnDef.header,
|
||||||
|
header.getContext(),
|
||||||
|
)}
|
||||||
</TableHead>
|
</TableHead>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -352,34 +358,32 @@ export function ModelList({ data }: { data: ModelItemList[] }) {
|
|||||||
))}
|
))}
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{table.getRowModel().rows?.length
|
{table.getRowModel().rows?.length ? (
|
||||||
? (
|
table.getRowModel().rows.map((row) => (
|
||||||
table.getRowModel().rows.map((row) => (
|
<TableRow
|
||||||
<TableRow
|
key={row.id}
|
||||||
key={row.id}
|
data-state={row.getIsSelected() && "selected"}
|
||||||
data-state={row.getIsSelected() && "selected"}
|
>
|
||||||
>
|
{row.getVisibleCells().map((cell) => (
|
||||||
{row.getVisibleCells().map((cell) => (
|
<TableCell key={cell.id}>
|
||||||
<TableCell key={cell.id}>
|
{flexRender(
|
||||||
{flexRender(
|
cell.column.columnDef.cell,
|
||||||
cell.column.columnDef.cell,
|
cell.getContext(),
|
||||||
cell.getContext(),
|
)}
|
||||||
)}
|
</TableCell>
|
||||||
</TableCell>
|
))}
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))
|
|
||||||
)
|
|
||||||
: (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell
|
|
||||||
colSpan={columns.length}
|
|
||||||
className="h-24 text-center"
|
|
||||||
>
|
|
||||||
No results.
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)}
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
colSpan={columns.length}
|
||||||
|
className="h-24 text-center"
|
||||||
|
>
|
||||||
|
No results.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
72
web/src/components/custom-form/CivitaiModelRegistry.tsx
Normal file
72
web/src/components/custom-form/CivitaiModelRegistry.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
"use client";
|
||||||
|
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
||||||
|
import * as React from "react";
|
||||||
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { CivitalModelSchema, ModelListWrapper } from "./CivitalModelSchema";
|
||||||
|
import { getUrl, mapModelsList } from "./getUrl";
|
||||||
|
import { ModelSelector } from "./ModelSelector";
|
||||||
|
|
||||||
|
export function CivitaiModelRegistry({
|
||||||
|
field,
|
||||||
|
selectMultiple = true,
|
||||||
|
}: Pick<AutoFormInputComponentProps, "field"> & {
|
||||||
|
selectMultiple?: boolean;
|
||||||
|
}) {
|
||||||
|
const [modelList, setModelList] =
|
||||||
|
React.useState<z.infer<typeof ModelListWrapper>>();
|
||||||
|
|
||||||
|
const [loading, setLoading] = React.useState(false);
|
||||||
|
|
||||||
|
const handleSearch = useDebouncedCallback((search) => {
|
||||||
|
console.log(`Searching... ${search}`);
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
fetch(getUrl(search), {
|
||||||
|
signal: controller.signal,
|
||||||
|
})
|
||||||
|
.then((x) => x.json())
|
||||||
|
.then((a) => {
|
||||||
|
const list = CivitalModelSchema.parse(a);
|
||||||
|
console.log(a);
|
||||||
|
|
||||||
|
setModelList(mapModelsList(list));
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
controller.abort();
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
fetch(getUrl(), {
|
||||||
|
signal: controller.signal,
|
||||||
|
})
|
||||||
|
.then((x) => x.json())
|
||||||
|
.then((a) => {
|
||||||
|
const list = CivitalModelSchema.parse(a);
|
||||||
|
setModelList(mapModelsList(list));
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
controller.abort();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModelSelector
|
||||||
|
selectMultiple={selectMultiple}
|
||||||
|
field={field}
|
||||||
|
modelList={modelList}
|
||||||
|
label="Civitai"
|
||||||
|
onSearch={handleSearch}
|
||||||
|
shouldFilter={false}
|
||||||
|
isLoading={loading}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
94
web/src/components/custom-form/CivitalModelSchema.tsx
Normal file
94
web/src/components/custom-form/CivitalModelSchema.tsx
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
"use client";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export 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(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CivitalModelSchema = z.object({
|
||||||
|
items: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.number(),
|
||||||
|
name: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
creator: z
|
||||||
|
.object({
|
||||||
|
username: z.string().nullable(),
|
||||||
|
image: z.string().nullable().default(null),
|
||||||
|
})
|
||||||
|
.nullable(),
|
||||||
|
tags: z.array(z.string()),
|
||||||
|
modelVersions: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.number(),
|
||||||
|
modelId: z.number(),
|
||||||
|
name: z.string(),
|
||||||
|
createdAt: z.string(),
|
||||||
|
updatedAt: z.string(),
|
||||||
|
status: z.string(),
|
||||||
|
publishedAt: z.string(),
|
||||||
|
trainedWords: z.array(z.unknown()),
|
||||||
|
trainingStatus: z.string().nullable(),
|
||||||
|
trainingDetails: z.string().nullable(),
|
||||||
|
baseModel: z.string(),
|
||||||
|
baseModelType: z.string().nullable(),
|
||||||
|
earlyAccessTimeFrame: z.number(),
|
||||||
|
description: z.string().nullable(),
|
||||||
|
vaeId: z.number().nullable(),
|
||||||
|
stats: z.object({
|
||||||
|
downloadCount: z.number(),
|
||||||
|
ratingCount: z.number(),
|
||||||
|
rating: z.number(),
|
||||||
|
}),
|
||||||
|
files: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.number(),
|
||||||
|
sizeKB: z.number(),
|
||||||
|
name: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
downloadUrl: z.string(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
images: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.number(),
|
||||||
|
url: z.string(),
|
||||||
|
nsfw: z.string(),
|
||||||
|
width: z.number(),
|
||||||
|
height: z.number(),
|
||||||
|
hash: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
metadata: z.object({
|
||||||
|
hash: z.string(),
|
||||||
|
width: z.number(),
|
||||||
|
height: z.number(),
|
||||||
|
}),
|
||||||
|
meta: z.any(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
downloadUrl: z.string(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
metadata: z.object({
|
||||||
|
totalItems: z.number(),
|
||||||
|
currentPage: z.number(),
|
||||||
|
pageSize: z.number(),
|
||||||
|
totalPages: z.number(),
|
||||||
|
nextPage: z.string().optional(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
export const ModelList = z.array(Model);
|
||||||
|
|
||||||
|
export const ModelListWrapper = z.object({
|
||||||
|
models: ModelList,
|
||||||
|
});
|
@ -0,0 +1,43 @@
|
|||||||
|
"use client";
|
||||||
|
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
||||||
|
import * as React from "react";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { ModelListWrapper } from "./CivitalModelSchema";
|
||||||
|
import { ModelSelector } from "./ModelSelector";
|
||||||
|
|
||||||
|
export function ComfyUIManagerModelRegistry({
|
||||||
|
field,
|
||||||
|
selectMultiple = true,
|
||||||
|
}: Pick<AutoFormInputComponentProps, "field"> & {
|
||||||
|
selectMultiple?: boolean;
|
||||||
|
}) {
|
||||||
|
const [modelList, setModelList] =
|
||||||
|
React.useState<z.infer<typeof ModelListWrapper>>();
|
||||||
|
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModelSelector
|
||||||
|
selectMultiple={selectMultiple}
|
||||||
|
field={field}
|
||||||
|
modelList={modelList}
|
||||||
|
label="ComfyUI Manager"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -1,160 +1,17 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
||||||
import { LoadingIcon } from "@/components/LoadingIcon";
|
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionContent,
|
AccordionContent,
|
||||||
AccordionItem,
|
AccordionItem,
|
||||||
AccordionTrigger,
|
AccordionTrigger,
|
||||||
} from "@/components/ui/accordion";
|
} from "@/components/ui/accordion";
|
||||||
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 { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { Check, ChevronsUpDown } from "lucide-react";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useRef } from "react";
|
import { CivitaiModelRegistry } from "./CivitaiModelRegistry";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { ComfyUIManagerModelRegistry } from "./ComfyUIManagerModelRegistry";
|
||||||
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(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const CivitalModelSchema = z.object({
|
|
||||||
items: z.array(
|
|
||||||
z.object({
|
|
||||||
id: z.number(),
|
|
||||||
name: z.string(),
|
|
||||||
description: z.string(),
|
|
||||||
type: z.string(),
|
|
||||||
// poi: z.boolean(),
|
|
||||||
// nsfw: z.boolean(),
|
|
||||||
// allowNoCredit: z.boolean(),
|
|
||||||
// allowCommercialUse: z.string(),
|
|
||||||
// allowDerivatives: z.boolean(),
|
|
||||||
// allowDifferentLicense: z.boolean(),
|
|
||||||
// stats: z.object({
|
|
||||||
// downloadCount: z.number(),
|
|
||||||
// favoriteCount: z.number(),
|
|
||||||
// commentCount: z.number(),
|
|
||||||
// ratingCount: z.number(),
|
|
||||||
// rating: z.number(),
|
|
||||||
// tippedAmountCount: z.number(),
|
|
||||||
// }),
|
|
||||||
creator: z
|
|
||||||
.object({
|
|
||||||
username: z.string().nullable(),
|
|
||||||
image: z.string().nullable().default(null),
|
|
||||||
})
|
|
||||||
.nullable(),
|
|
||||||
tags: z.array(z.string()),
|
|
||||||
modelVersions: z.array(
|
|
||||||
z.object({
|
|
||||||
id: z.number(),
|
|
||||||
modelId: z.number(),
|
|
||||||
name: z.string(),
|
|
||||||
createdAt: z.string(),
|
|
||||||
updatedAt: z.string(),
|
|
||||||
status: z.string(),
|
|
||||||
publishedAt: z.string(),
|
|
||||||
trainedWords: z.array(z.unknown()),
|
|
||||||
trainingStatus: z.string().nullable(),
|
|
||||||
trainingDetails: z.string().nullable(),
|
|
||||||
baseModel: z.string(),
|
|
||||||
baseModelType: z.string().nullable(),
|
|
||||||
earlyAccessTimeFrame: z.number(),
|
|
||||||
description: z.string().nullable(),
|
|
||||||
vaeId: z.number().nullable(),
|
|
||||||
stats: z.object({
|
|
||||||
downloadCount: z.number(),
|
|
||||||
ratingCount: z.number(),
|
|
||||||
rating: z.number(),
|
|
||||||
}),
|
|
||||||
files: z.array(
|
|
||||||
z.object({
|
|
||||||
id: z.number(),
|
|
||||||
sizeKB: z.number(),
|
|
||||||
name: z.string(),
|
|
||||||
type: z.string(),
|
|
||||||
// metadata: z.object({
|
|
||||||
// fp: z.string().nullable().optional(),
|
|
||||||
// size: z.string().nullable().optional(),
|
|
||||||
// format: z.string().nullable().optional(),
|
|
||||||
// }),
|
|
||||||
// pickleScanResult: z.string(),
|
|
||||||
// pickleScanMessage: z.string(),
|
|
||||||
// virusScanResult: z.string(),
|
|
||||||
// virusScanMessage: z.string().nullable(),
|
|
||||||
// scannedAt: z.string(),
|
|
||||||
// hashes: z.object({
|
|
||||||
// AutoV1: z.string().nullable().optional(),
|
|
||||||
// AutoV2: z.string().nullable().optional(),
|
|
||||||
// SHA256: z.string().nullable().optional(),
|
|
||||||
// CRC32: z.string().nullable().optional(),
|
|
||||||
// BLAKE3: z.string().nullable().optional(),
|
|
||||||
// }),
|
|
||||||
downloadUrl: z.string(),
|
|
||||||
// primary: z.boolean().default(false),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
images: z.array(
|
|
||||||
z.object({
|
|
||||||
id: z.number(),
|
|
||||||
url: z.string(),
|
|
||||||
nsfw: z.string(),
|
|
||||||
width: z.number(),
|
|
||||||
height: z.number(),
|
|
||||||
hash: z.string(),
|
|
||||||
type: z.string(),
|
|
||||||
metadata: z.object({
|
|
||||||
hash: z.string(),
|
|
||||||
width: z.number(),
|
|
||||||
height: z.number(),
|
|
||||||
}),
|
|
||||||
meta: z.any(),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
downloadUrl: z.string(),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
metadata: z.object({
|
|
||||||
totalItems: z.number(),
|
|
||||||
currentPage: z.number(),
|
|
||||||
pageSize: z.number(),
|
|
||||||
totalPages: z.number(),
|
|
||||||
nextPage: z.string().optional(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const ModelList = z.array(Model);
|
|
||||||
|
|
||||||
export const ModelListWrapper = z.object({
|
|
||||||
models: ModelList,
|
|
||||||
});
|
|
||||||
|
|
||||||
export function ModelPickerView({
|
export function ModelPickerView({
|
||||||
field,
|
field,
|
||||||
@ -187,240 +44,3 @@ export function ModelPickerView({
|
|||||||
</Accordion>
|
</Accordion>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapType(type: string) {
|
|
||||||
switch (type) {
|
|
||||||
case "checkpoint":
|
|
||||||
return "checkpoints";
|
|
||||||
}
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapModelsList(
|
|
||||||
models: z.infer<typeof CivitalModelSchema>
|
|
||||||
): z.infer<typeof ModelListWrapper> {
|
|
||||||
return {
|
|
||||||
models: models.items.flatMap((item) => {
|
|
||||||
return item.modelVersions.map((v) => {
|
|
||||||
return {
|
|
||||||
name: `${item.name} ${v.name} (${v.files[0].name})`,
|
|
||||||
type: mapType(item.type.toLowerCase()),
|
|
||||||
base: v.baseModel,
|
|
||||||
save_path: "default",
|
|
||||||
description: item.description,
|
|
||||||
reference: "",
|
|
||||||
filename: v.files[0].name,
|
|
||||||
url: v.files[0].downloadUrl,
|
|
||||||
} as z.infer<typeof Model>;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getUrl(search?: string) {
|
|
||||||
const baseUrl = "https://civitai.com/api/v1/models";
|
|
||||||
const searchParams = {
|
|
||||||
limit: 5,
|
|
||||||
} as any;
|
|
||||||
searchParams["sort"] = "Most Downloaded";
|
|
||||||
|
|
||||||
if (search) {
|
|
||||||
searchParams["query"] = search;
|
|
||||||
} else {
|
|
||||||
// sort: "Highest Rated",
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = new URL(baseUrl);
|
|
||||||
Object.keys(searchParams).forEach((key) =>
|
|
||||||
url.searchParams.append(key, searchParams[key])
|
|
||||||
);
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CivitaiModelRegistry({
|
|
||||||
field,
|
|
||||||
}: Pick<AutoFormInputComponentProps, "field">) {
|
|
||||||
const [modelList, setModelList] =
|
|
||||||
React.useState<z.infer<typeof ModelListWrapper>>();
|
|
||||||
|
|
||||||
const [loading, setLoading] = React.useState(false);
|
|
||||||
|
|
||||||
const handleSearch = useDebouncedCallback((search) => {
|
|
||||||
console.log(`Searching... ${search}`);
|
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
const controller = new AbortController();
|
|
||||||
fetch(getUrl(search), {
|
|
||||||
signal: controller.signal,
|
|
||||||
})
|
|
||||||
.then((x) => x.json())
|
|
||||||
.then((a) => {
|
|
||||||
const list = CivitalModelSchema.parse(a);
|
|
||||||
console.log(a);
|
|
||||||
|
|
||||||
setModelList(mapModelsList(list));
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
controller.abort();
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const controller = new AbortController();
|
|
||||||
fetch(getUrl(), {
|
|
||||||
signal: controller.signal,
|
|
||||||
})
|
|
||||||
.then((x) => x.json())
|
|
||||||
.then((a) => {
|
|
||||||
const list = CivitalModelSchema.parse(a);
|
|
||||||
setModelList(mapModelsList(list));
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
controller.abort();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModelSelector
|
|
||||||
field={field}
|
|
||||||
modelList={modelList}
|
|
||||||
label="Civitai"
|
|
||||||
onSearch={handleSearch}
|
|
||||||
shouldFilter={false}
|
|
||||||
isLoading={loading}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ComfyUIManagerModelRegistry({
|
|
||||||
field,
|
|
||||||
}: Pick<AutoFormInputComponentProps, "field">) {
|
|
||||||
const [modelList, setModelList] =
|
|
||||||
React.useState<z.infer<typeof ModelListWrapper>>();
|
|
||||||
|
|
||||||
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();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModelSelector
|
|
||||||
field={field}
|
|
||||||
modelList={modelList}
|
|
||||||
label="ComfyUI Manager"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ModelSelector({
|
|
||||||
field,
|
|
||||||
modelList,
|
|
||||||
label,
|
|
||||||
onSearch,
|
|
||||||
shouldFilter = true,
|
|
||||||
isLoading,
|
|
||||||
}: Pick<AutoFormInputComponentProps, "field"> & {
|
|
||||||
modelList?: z.infer<typeof ModelListWrapper>;
|
|
||||||
label: string;
|
|
||||||
onSearch?: (search: string) => void;
|
|
||||||
shouldFilter?: boolean;
|
|
||||||
isLoading?: boolean;
|
|
||||||
}) {
|
|
||||||
const value = (field.value as z.infer<typeof ModelList>) ?? [];
|
|
||||||
const [open, setOpen] = React.useState(false);
|
|
||||||
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
Add from {label}
|
|
||||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-[375px] p-0" side="bottom">
|
|
||||||
<Command shouldFilter={shouldFilter}>
|
|
||||||
<CommandInput
|
|
||||||
placeholder="Search models..."
|
|
||||||
className="h-9"
|
|
||||||
onValueChange={onSearch}
|
|
||||||
>
|
|
||||||
{isLoading && <LoadingIcon />}
|
|
||||||
</CommandInput>
|
|
||||||
<CommandEmpty>No models found.</CommandEmpty>
|
|
||||||
<CommandList className="pointer-events-auto">
|
|
||||||
<CommandGroup>
|
|
||||||
{modelList?.models.map((model) => (
|
|
||||||
<CommandItem
|
|
||||||
key={model.url + model.name}
|
|
||||||
value={model.url}
|
|
||||||
onSelect={() => {
|
|
||||||
toggleModel(model);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{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>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
123
web/src/components/custom-form/ModelSelector.tsx
Normal file
123
web/src/components/custom-form/ModelSelector.tsx
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
"use client";
|
||||||
|
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
||||||
|
import { LoadingIcon } from "@/components/LoadingIcon";
|
||||||
|
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 { cn } from "@/lib/utils";
|
||||||
|
import { Check, ChevronsUpDown } from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { useRef } from "react";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { ModelListWrapper, Model, ModelList } from "./CivitalModelSchema";
|
||||||
|
|
||||||
|
export function ModelSelector({
|
||||||
|
field,
|
||||||
|
modelList,
|
||||||
|
label,
|
||||||
|
onSearch,
|
||||||
|
shouldFilter = true,
|
||||||
|
isLoading,
|
||||||
|
selectMultiple = true,
|
||||||
|
}: Pick<AutoFormInputComponentProps, "field"> & {
|
||||||
|
modelList?: z.infer<typeof ModelListWrapper>;
|
||||||
|
label: string;
|
||||||
|
onSearch?: (search: string) => void;
|
||||||
|
shouldFilter?: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
|
selectMultiple?: boolean;
|
||||||
|
}) {
|
||||||
|
const value = (field.value as z.infer<typeof ModelList>) ?? [];
|
||||||
|
const [open, setOpen] = React.useState(false);
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if (!selectMultiple) {
|
||||||
|
field.onChange([model]);
|
||||||
|
} else {
|
||||||
|
field.onChange([...prevSelectedModels, model]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
Add from {label}
|
||||||
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[375px] p-0" side="bottom">
|
||||||
|
<Command shouldFilter={shouldFilter}>
|
||||||
|
<CommandInput
|
||||||
|
placeholder="Search models..."
|
||||||
|
className="h-9"
|
||||||
|
onValueChange={onSearch}
|
||||||
|
>
|
||||||
|
{isLoading && <LoadingIcon />}
|
||||||
|
</CommandInput>
|
||||||
|
<CommandEmpty>No models found.</CommandEmpty>
|
||||||
|
<CommandList className="pointer-events-auto">
|
||||||
|
<CommandGroup>
|
||||||
|
{modelList?.models.map((model) => (
|
||||||
|
<CommandItem
|
||||||
|
key={model.url + model.name}
|
||||||
|
value={model.url}
|
||||||
|
onSelect={() => {
|
||||||
|
toggleModel(model);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
55
web/src/components/custom-form/getUrl.tsx
Normal file
55
web/src/components/custom-form/getUrl.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
"use client";
|
||||||
|
import { z } from "zod";
|
||||||
|
import {
|
||||||
|
CivitalModelSchema,
|
||||||
|
ModelListWrapper,
|
||||||
|
Model,
|
||||||
|
} from "./CivitalModelSchema";
|
||||||
|
|
||||||
|
function mapType(type: string) {
|
||||||
|
switch (type) {
|
||||||
|
case "checkpoint":
|
||||||
|
return "checkpoints";
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
export function mapModelsList(
|
||||||
|
models: z.infer<typeof CivitalModelSchema>,
|
||||||
|
): z.infer<typeof ModelListWrapper> {
|
||||||
|
return {
|
||||||
|
models: models.items.flatMap((item) => {
|
||||||
|
return item.modelVersions.map((v) => {
|
||||||
|
return {
|
||||||
|
name: `${item.name} ${v.name} (${v.files[0].name})`,
|
||||||
|
type: mapType(item.type.toLowerCase()),
|
||||||
|
base: v.baseModel,
|
||||||
|
save_path: "default",
|
||||||
|
description: item.description,
|
||||||
|
reference: "",
|
||||||
|
filename: v.files[0].name,
|
||||||
|
url: v.files[0].downloadUrl,
|
||||||
|
} as z.infer<typeof Model>;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function getUrl(search?: string) {
|
||||||
|
const baseUrl = "https://civitai.com/api/v1/models";
|
||||||
|
const searchParams = {
|
||||||
|
limit: 5,
|
||||||
|
} as any;
|
||||||
|
searchParams["sort"] = "Most Downloaded";
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
searchParams["query"] = search;
|
||||||
|
} else {
|
||||||
|
// sort: "Highest Rated",
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(baseUrl);
|
||||||
|
Object.keys(searchParams).forEach((key) =>
|
||||||
|
url.searchParams.append(key, searchParams[key]),
|
||||||
|
);
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
96
web/src/components/custom-form/model-picker-url-only.tsx
Normal file
96
web/src/components/custom-form/model-picker-url-only.tsx
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "../ui/form";
|
||||||
|
import { LoadingIcon } from "@/components/LoadingIcon";
|
||||||
|
// import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
|
||||||
|
import * as React from "react";
|
||||||
|
import { Suspense } from "react";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { CivitaiModelRegistry } from "./CivitaiModelRegistry";
|
||||||
|
import { ComfyUIManagerModelRegistry } from "./ComfyUIManagerModelRegistry";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { ModelList } from "@/components/custom-form/CivitalModelSchema";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export default function AutoFormModelsPickerUrl({
|
||||||
|
label,
|
||||||
|
isRequired,
|
||||||
|
field,
|
||||||
|
fieldConfigItem,
|
||||||
|
zodItem,
|
||||||
|
fieldProps,
|
||||||
|
}: AutoFormInputComponentProps) {
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
{fieldConfigItem.inputProps?.showLabel && (
|
||||||
|
<FormLabel>
|
||||||
|
{label}
|
||||||
|
{isRequired && <span className="text-destructive"> *</span>}
|
||||||
|
</FormLabel>
|
||||||
|
)}
|
||||||
|
<FormControl>
|
||||||
|
<Suspense fallback={<LoadingIcon />}>
|
||||||
|
<ModelPickerView field={field} fieldProps={fieldProps} />
|
||||||
|
</Suspense>
|
||||||
|
</FormControl>
|
||||||
|
{fieldConfigItem.description && (
|
||||||
|
<FormDescription>{fieldConfigItem.description}</FormDescription>
|
||||||
|
)}
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ModelPickerView({
|
||||||
|
field,
|
||||||
|
fieldProps,
|
||||||
|
}: Pick<AutoFormInputComponentProps, "field" | "fieldProps">) {
|
||||||
|
const customOverride = React.useMemo(() => {
|
||||||
|
const customOnChange = (value: z.infer<typeof ModelList>) => {
|
||||||
|
field.onChange(value[0]?.url);
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
...field,
|
||||||
|
onChange: customOnChange,
|
||||||
|
value: field.value
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
url: field.value,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
};
|
||||||
|
}, [field]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-2 flex-col px-1">
|
||||||
|
<ComfyUIManagerModelRegistry
|
||||||
|
field={customOverride}
|
||||||
|
selectMultiple={false}
|
||||||
|
/>
|
||||||
|
<CivitaiModelRegistry field={customOverride} selectMultiple={false} />
|
||||||
|
<Input
|
||||||
|
// className="min-h-[150px] max-h-[300px] p-2 rounded-lg text-xs w-full"
|
||||||
|
value={field.value ?? ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
field.onChange(e.target.value);
|
||||||
|
}}
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
@ -7,10 +9,19 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "../ui/form";
|
} from "../ui/form";
|
||||||
import { LoadingIcon } from "@/components/LoadingIcon";
|
import { LoadingIcon } from "@/components/LoadingIcon";
|
||||||
import { ModelPickerView } from "@/components/custom-form/ModelPickerView";
|
|
||||||
// import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
|
// import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { CivitaiModelRegistry } from "./CivitaiModelRegistry";
|
||||||
|
import { ComfyUIManagerModelRegistry } from "./ComfyUIManagerModelRegistry";
|
||||||
|
|
||||||
export default function AutoFormModelsPicker({
|
export default function AutoFormModelsPicker({
|
||||||
label,
|
label,
|
||||||
@ -39,3 +50,35 @@ export default function AutoFormModelsPicker({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ModelPickerView({
|
||||||
|
field,
|
||||||
|
}: Pick<AutoFormInputComponentProps, "field">) {
|
||||||
|
return (
|
||||||
|
<Accordion type="single" collapsible>
|
||||||
|
<AccordionItem value="item-1">
|
||||||
|
<AccordionTrigger className="text-sm">
|
||||||
|
Models (ComfyUI Manager & Civitai)
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<div className="flex gap-2 flex-col px-1">
|
||||||
|
<ComfyUIManagerModelRegistry field={field} />
|
||||||
|
<CivitaiModelRegistry field={field} />
|
||||||
|
{/* <span>{field.value.length} selected</span> */}
|
||||||
|
{field.value && (
|
||||||
|
<ScrollArea className="w-full bg-gray-100 mx-auto rounded-lg mt-2">
|
||||||
|
<Textarea
|
||||||
|
className="min-h-[150px] max-h-[300px] p-2 rounded-lg text-xs w-full"
|
||||||
|
value={JSON.stringify(field.value, null, 2)}
|
||||||
|
onChange={(e) => {
|
||||||
|
field.onChange(JSON.parse(e.target.value));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ScrollArea>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -9,6 +9,7 @@ import AutoFormSwitch from "./fields/switch";
|
|||||||
import AutoFormTextarea from "./fields/textarea";
|
import AutoFormTextarea from "./fields/textarea";
|
||||||
import AutoFormModelsPicker from "@/components/custom-form/model-picker";
|
import AutoFormModelsPicker from "@/components/custom-form/model-picker";
|
||||||
import AutoFormSnapshotPicker from "@/components/custom-form/snapshot-picker";
|
import AutoFormSnapshotPicker from "@/components/custom-form/snapshot-picker";
|
||||||
|
import AutoFormModelsPickerUrl from "@/components/custom-form/model-picker-url-only";
|
||||||
|
|
||||||
export const INPUT_COMPONENTS = {
|
export const INPUT_COMPONENTS = {
|
||||||
checkbox: AutoFormCheckbox,
|
checkbox: AutoFormCheckbox,
|
||||||
@ -24,6 +25,7 @@ export const INPUT_COMPONENTS = {
|
|||||||
snapshot: AutoFormSnapshotPicker,
|
snapshot: AutoFormSnapshotPicker,
|
||||||
models: AutoFormModelsPicker,
|
models: AutoFormModelsPicker,
|
||||||
gpuPicker: AutoFormGPUPicker,
|
gpuPicker: AutoFormGPUPicker,
|
||||||
|
modelUrlPicker: AutoFormModelsPickerUrl,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user