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,9 +330,7 @@ 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