feat: add custom nodes selectors
This commit is contained in:
		
							parent
							
								
									94fa92ed76
								
							
						
					
					
						commit
						6e3b7f0435
					
				
							
								
								
									
										
											BIN
										
									
								
								web/bun.lockb
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/bun.lockb
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							@ -47,6 +47,7 @@
 | 
				
			|||||||
    "@sindresorhus/slugify": "^2.2.1",
 | 
					    "@sindresorhus/slugify": "^2.2.1",
 | 
				
			||||||
    "@tailwindcss/typography": "^0.5.10",
 | 
					    "@tailwindcss/typography": "^0.5.10",
 | 
				
			||||||
    "@tanstack/react-table": "^8.10.7",
 | 
					    "@tanstack/react-table": "^8.10.7",
 | 
				
			||||||
 | 
					    "@tanstack/react-virtual": "beta",
 | 
				
			||||||
    "@types/jsonwebtoken": "^9.0.5",
 | 
					    "@types/jsonwebtoken": "^9.0.5",
 | 
				
			||||||
    "@types/react-highlight-words": "^0.16.7",
 | 
					    "@types/react-highlight-words": "^0.16.7",
 | 
				
			||||||
    "@types/swagger-ui-react": "^4.18.3",
 | 
					    "@types/swagger-ui-react": "^4.18.3",
 | 
				
			||||||
 | 
				
			|||||||
@ -17,6 +17,7 @@ import {
 | 
				
			|||||||
  TooltipContent,
 | 
					  TooltipContent,
 | 
				
			||||||
  TooltipTrigger,
 | 
					  TooltipTrigger,
 | 
				
			||||||
} from "@/components/ui/tooltip";
 | 
					} from "@/components/ui/tooltip";
 | 
				
			||||||
 | 
					import { cn } from "@/lib/utils";
 | 
				
			||||||
import * as React from "react";
 | 
					import * as React from "react";
 | 
				
			||||||
import { useState } from "react";
 | 
					import { useState } from "react";
 | 
				
			||||||
import type { UnknownKeysParam, ZodObject, ZodRawShape, z } from "zod";
 | 
					import type { UnknownKeysParam, ZodObject, ZodRawShape, z } from "zod";
 | 
				
			||||||
@ -30,6 +31,7 @@ export function InsertModal<
 | 
				
			|||||||
  disabled?: boolean;
 | 
					  disabled?: boolean;
 | 
				
			||||||
  title: string;
 | 
					  title: string;
 | 
				
			||||||
  description: string;
 | 
					  description: string;
 | 
				
			||||||
 | 
					  dialogClassName?: string;
 | 
				
			||||||
  serverAction: (data: z.infer<Z>) => Promise<unknown>;
 | 
					  serverAction: (data: z.infer<Z>) => Promise<unknown>;
 | 
				
			||||||
  formSchema: Z;
 | 
					  formSchema: Z;
 | 
				
			||||||
  fieldConfig?: FieldConfig<z.infer<Z>>;
 | 
					  fieldConfig?: FieldConfig<z.infer<Z>>;
 | 
				
			||||||
@ -70,7 +72,7 @@ export function InsertModal<
 | 
				
			|||||||
        </Button>
 | 
					        </Button>
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
      {/* </DialogTrigger> */}
 | 
					      {/* </DialogTrigger> */}
 | 
				
			||||||
      <DialogContent className="sm:max-w-[425px]">
 | 
					      <DialogContent className={cn("sm:max-w-[425px]", props.dialogClassName)}>
 | 
				
			||||||
        <DialogHeader>
 | 
					        <DialogHeader>
 | 
				
			||||||
          <DialogTitle>{props.title}</DialogTitle>
 | 
					          <DialogTitle>{props.title}</DialogTitle>
 | 
				
			||||||
          <DialogDescription>{props.description}</DialogDescription>
 | 
					          <DialogDescription>{props.description}</DialogDescription>
 | 
				
			||||||
@ -108,6 +110,7 @@ export function UpdateModal<
 | 
				
			|||||||
  setOpen: (open: boolean) => void;
 | 
					  setOpen: (open: boolean) => void;
 | 
				
			||||||
  title: string;
 | 
					  title: string;
 | 
				
			||||||
  description: string;
 | 
					  description: string;
 | 
				
			||||||
 | 
					  dialogClassName?: string;
 | 
				
			||||||
  data: z.infer<Z>;
 | 
					  data: z.infer<Z>;
 | 
				
			||||||
  serverAction: (
 | 
					  serverAction: (
 | 
				
			||||||
    data: z.infer<Z> & {
 | 
					    data: z.infer<Z> & {
 | 
				
			||||||
@ -130,7 +133,7 @@ export function UpdateModal<
 | 
				
			|||||||
      {/* <DialogTrigger asChild>
 | 
					      {/* <DialogTrigger asChild>
 | 
				
			||||||
        <DropdownMenuItem>{props.title}</DropdownMenuItem>
 | 
					        <DropdownMenuItem>{props.title}</DropdownMenuItem>
 | 
				
			||||||
      </DialogTrigger> */}
 | 
					      </DialogTrigger> */}
 | 
				
			||||||
      <DialogContent className="sm:max-w-[425px]">
 | 
					      <DialogContent className={cn("sm:max-w-[425px]", props.dialogClassName)}>
 | 
				
			||||||
        <DialogHeader>
 | 
					        <DialogHeader>
 | 
				
			||||||
          <DialogTitle>{props.title}</DialogTitle>
 | 
					          <DialogTitle>{props.title}</DialogTitle>
 | 
				
			||||||
          <DialogDescription>{props.description}</DialogDescription>
 | 
					          <DialogDescription>{props.description}</DialogDescription>
 | 
				
			||||||
 | 
				
			|||||||
@ -214,6 +214,7 @@ export const columns: ColumnDef<Machine>[] = [
 | 
				
			|||||||
          </DropdownMenuContent>
 | 
					          </DropdownMenuContent>
 | 
				
			||||||
          {machine.type === "comfy-deploy-serverless" ? (
 | 
					          {machine.type === "comfy-deploy-serverless" ? (
 | 
				
			||||||
            <UpdateModal
 | 
					            <UpdateModal
 | 
				
			||||||
 | 
					              dialogClassName="sm:max-w-[600px]"
 | 
				
			||||||
              data={machine}
 | 
					              data={machine}
 | 
				
			||||||
              open={open}
 | 
					              open={open}
 | 
				
			||||||
              setOpen={setOpen}
 | 
					              setOpen={setOpen}
 | 
				
			||||||
@ -303,6 +304,7 @@ export function MachineList({ data }: { data: Machine[] }) {
 | 
				
			|||||||
        />
 | 
					        />
 | 
				
			||||||
        <div className="ml-auto flex gap-2">
 | 
					        <div className="ml-auto flex gap-2">
 | 
				
			||||||
          <InsertModal
 | 
					          <InsertModal
 | 
				
			||||||
 | 
					            dialogClassName="sm:max-w-[600px]"
 | 
				
			||||||
            disabled={data.some(
 | 
					            disabled={data.some(
 | 
				
			||||||
              (machine) => machine.type === "comfy-deploy-serverless"
 | 
					              (machine) => machine.type === "comfy-deploy-serverless"
 | 
				
			||||||
            )}
 | 
					            )}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
"use client";
 | 
					"use client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { callServerPromise } from "./callServerPromise";
 | 
					import { callServerPromise } from "./callServerPromise";
 | 
				
			||||||
 | 
					import fetcher from "./fetcher";
 | 
				
			||||||
import { LoadingIcon } from "@/components/LoadingIcon";
 | 
					import { LoadingIcon } from "@/components/LoadingIcon";
 | 
				
			||||||
import AutoForm, { AutoFormSubmit } from "@/components/ui/auto-form";
 | 
					import AutoForm, { AutoFormSubmit } from "@/components/ui/auto-form";
 | 
				
			||||||
import { Badge } from "@/components/ui/badge";
 | 
					import { Badge } from "@/components/ui/badge";
 | 
				
			||||||
@ -361,14 +362,6 @@ export function getWorkflowVersionFromVersionIndex(
 | 
				
			|||||||
  return workflow_version;
 | 
					  return workflow_version;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async function fetcher<JSON = unknown>(
 | 
					 | 
				
			||||||
  input: RequestInfo,
 | 
					 | 
				
			||||||
  init?: RequestInit
 | 
					 | 
				
			||||||
): Promise<JSON> {
 | 
					 | 
				
			||||||
  const res = await fetch(input, init);
 | 
					 | 
				
			||||||
  return res.json();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function ViewWorkflowDetailsButton({
 | 
					export function ViewWorkflowDetailsButton({
 | 
				
			||||||
  workflow,
 | 
					  workflow,
 | 
				
			||||||
}: {
 | 
					}: {
 | 
				
			||||||
 | 
				
			|||||||
@ -112,7 +112,7 @@ export function ModelPickerView({
 | 
				
			|||||||
        </PopoverTrigger>
 | 
					        </PopoverTrigger>
 | 
				
			||||||
        <PopoverContent className="w-[375px] p-0" side="top">
 | 
					        <PopoverContent className="w-[375px] p-0" side="top">
 | 
				
			||||||
          <Command>
 | 
					          <Command>
 | 
				
			||||||
            <CommandInput placeholder="Search framework..." className="h-9" />
 | 
					            <CommandInput placeholder="Search models..." className="h-9" />
 | 
				
			||||||
            <CommandEmpty>No framework found.</CommandEmpty>
 | 
					            <CommandEmpty>No framework found.</CommandEmpty>
 | 
				
			||||||
            <CommandList className="pointer-events-auto">
 | 
					            <CommandList className="pointer-events-auto">
 | 
				
			||||||
              <CommandGroup>
 | 
					              <CommandGroup>
 | 
				
			||||||
@ -144,7 +144,7 @@ export function ModelPickerView({
 | 
				
			|||||||
        </PopoverContent>
 | 
					        </PopoverContent>
 | 
				
			||||||
      </Popover>
 | 
					      </Popover>
 | 
				
			||||||
      {field.value && (
 | 
					      {field.value && (
 | 
				
			||||||
        <ScrollArea className="w-full bg-gray-100 mx-auto max-w-[360px] rounded-lg mt-2">
 | 
					        <ScrollArea className="w-full bg-gray-100 mx-auto max-w-[500px] rounded-lg mt-2">
 | 
				
			||||||
          <div className="max-h-[200px]">
 | 
					          <div className="max-h-[200px]">
 | 
				
			||||||
            <pre className="p-2 rounded-md text-xs ">
 | 
					            <pre className="p-2 rounded-md text-xs ">
 | 
				
			||||||
              {JSON.stringify(field.value, null, 2)}
 | 
					              {JSON.stringify(field.value, null, 2)}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
"use client";
 | 
					"use client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
 | 
					import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
 | 
				
			||||||
 | 
					import fetcher from "@/components/fetcher";
 | 
				
			||||||
import { Button } from "@/components/ui/button";
 | 
					import { Button } from "@/components/ui/button";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  Command,
 | 
					  Command,
 | 
				
			||||||
@ -8,6 +9,7 @@ import {
 | 
				
			|||||||
  CommandGroup,
 | 
					  CommandGroup,
 | 
				
			||||||
  CommandInput,
 | 
					  CommandInput,
 | 
				
			||||||
  CommandItem,
 | 
					  CommandItem,
 | 
				
			||||||
 | 
					  CommandList,
 | 
				
			||||||
} from "@/components/ui/command";
 | 
					} from "@/components/ui/command";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  Popover,
 | 
					  Popover,
 | 
				
			||||||
@ -19,9 +21,30 @@ import { cn } from "@/lib/utils";
 | 
				
			|||||||
import { findAllDeployments } from "@/server/curdDeploments";
 | 
					import { findAllDeployments } from "@/server/curdDeploments";
 | 
				
			||||||
import { Check, ChevronsUpDown } from "lucide-react";
 | 
					import { Check, ChevronsUpDown } from "lucide-react";
 | 
				
			||||||
import * as React from "react";
 | 
					import * as React from "react";
 | 
				
			||||||
 | 
					import useSWR from "swr";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function SnapshotPickerView({
 | 
					export function SnapshotPickerView({
 | 
				
			||||||
  field,
 | 
					  field,
 | 
				
			||||||
 | 
					}: Pick<AutoFormInputComponentProps, "field">) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="flex gap-2 flex-col">
 | 
				
			||||||
 | 
					      <SnapshotPresetPicker field={field} />
 | 
				
			||||||
 | 
					      <CustomNodesSelector field={field} />
 | 
				
			||||||
 | 
					      {field.value && (
 | 
				
			||||||
 | 
					        <ScrollArea className="w-full bg-gray-100 mx-auto max-w-[500px] rounded-lg">
 | 
				
			||||||
 | 
					          <div className="max-h-[200px] w-full">
 | 
				
			||||||
 | 
					            <pre className="p-2 rounded-md text-xs w-full">
 | 
				
			||||||
 | 
					              {JSON.stringify(field.value, null, 2)}
 | 
				
			||||||
 | 
					            </pre>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </ScrollArea>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SnapshotPresetPicker({
 | 
				
			||||||
 | 
					  field,
 | 
				
			||||||
}: Pick<AutoFormInputComponentProps, "field">) {
 | 
					}: Pick<AutoFormInputComponentProps, "field">) {
 | 
				
			||||||
  const [open, setOpen] = React.useState(false);
 | 
					  const [open, setOpen] = React.useState(false);
 | 
				
			||||||
  const [selected, setSelected] = React.useState<string | null>(null);
 | 
					  const [selected, setSelected] = React.useState<string | null>(null);
 | 
				
			||||||
@ -59,67 +82,168 @@ export function SnapshotPickerView({
 | 
				
			|||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function findItem(value: string) {
 | 
					  function findItem(value: string) {
 | 
				
			||||||
    // console.log(frameworks);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return frameworks?.find((item) => item.id === value);
 | 
					    return frameworks?.find((item) => item.id === value);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="">
 | 
					    <Popover open={open} onOpenChange={setOpen}>
 | 
				
			||||||
      <Popover open={open} onOpenChange={setOpen}>
 | 
					      <PopoverTrigger asChild>
 | 
				
			||||||
        <PopoverTrigger asChild>
 | 
					        <Button
 | 
				
			||||||
          <Button
 | 
					          variant="outline"
 | 
				
			||||||
            variant="outline"
 | 
					          role="combobox"
 | 
				
			||||||
            role="combobox"
 | 
					          aria-expanded={open}
 | 
				
			||||||
            aria-expanded={open}
 | 
					          className="w-full justify-between flex"
 | 
				
			||||||
            className="w-full justify-between flex"
 | 
					        >
 | 
				
			||||||
          >
 | 
					          {selected ? findItem(selected)?.label : "Select snapshot..."}
 | 
				
			||||||
            {selected ? findItem(selected)?.label : "Select snapshot..."}
 | 
					          <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
 | 
				
			||||||
            <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
 | 
					        </Button>
 | 
				
			||||||
          </Button>
 | 
					      </PopoverTrigger>
 | 
				
			||||||
        </PopoverTrigger>
 | 
					      <PopoverContent className="w-[375px] p-0">
 | 
				
			||||||
        <PopoverContent className="w-[375px] p-0">
 | 
					        <Command>
 | 
				
			||||||
          <Command>
 | 
					          <CommandInput placeholder="Search framework..." className="h-9" />
 | 
				
			||||||
            <CommandInput placeholder="Search framework..." className="h-9" />
 | 
					          <CommandEmpty>No framework found.</CommandEmpty>
 | 
				
			||||||
            <CommandEmpty>No framework found.</CommandEmpty>
 | 
					          <CommandGroup>
 | 
				
			||||||
            <CommandGroup>
 | 
					            {frameworks?.map((framework) => (
 | 
				
			||||||
              {frameworks?.map((framework) => (
 | 
					              <CommandItem
 | 
				
			||||||
                <CommandItem
 | 
					                key={framework.id}
 | 
				
			||||||
                  key={framework.id}
 | 
					                value={framework.id}
 | 
				
			||||||
                  value={framework.id}
 | 
					                onSelect={(currentValue) => {
 | 
				
			||||||
                  onSelect={(currentValue) => {
 | 
					                  setSelected(currentValue);
 | 
				
			||||||
                    setSelected(currentValue);
 | 
					                  const json =
 | 
				
			||||||
                    const json =
 | 
					                    frameworks?.find((item) => item.id === currentValue)
 | 
				
			||||||
                      frameworks?.find((item) => item.id === currentValue)
 | 
					                      ?.value ?? null;
 | 
				
			||||||
                        ?.value ?? null;
 | 
					                  field.onChange(json ? JSON.parse(json) : null);
 | 
				
			||||||
                    field.onChange(json ? JSON.parse(json) : null);
 | 
					                  setOpen(false);
 | 
				
			||||||
                    setOpen(false);
 | 
					                }}
 | 
				
			||||||
                  }}
 | 
					              >
 | 
				
			||||||
                >
 | 
					                {framework.label}
 | 
				
			||||||
                  {framework.label}
 | 
					                <Check
 | 
				
			||||||
                  <Check
 | 
					                  className={cn(
 | 
				
			||||||
                    className={cn(
 | 
					                    "ml-auto h-4 w-4",
 | 
				
			||||||
                      "ml-auto h-4 w-4",
 | 
					                    field.value === framework.value
 | 
				
			||||||
                      field.value === framework.value
 | 
					                      ? "opacity-100"
 | 
				
			||||||
                        ? "opacity-100"
 | 
					                      : "opacity-0"
 | 
				
			||||||
                        : "opacity-0"
 | 
					                  )}
 | 
				
			||||||
                    )}
 | 
					                />
 | 
				
			||||||
                  />
 | 
					              </CommandItem>
 | 
				
			||||||
                </CommandItem>
 | 
					            ))}
 | 
				
			||||||
              ))}
 | 
					          </CommandGroup>
 | 
				
			||||||
            </CommandGroup>
 | 
					        </Command>
 | 
				
			||||||
          </Command>
 | 
					      </PopoverContent>
 | 
				
			||||||
        </PopoverContent>
 | 
					    </Popover>
 | 
				
			||||||
      </Popover>
 | 
					  );
 | 
				
			||||||
      {field.value && (
 | 
					}
 | 
				
			||||||
        <ScrollArea className="w-full bg-gray-100 mx-auto max-w-[360px] rounded-lg mt-2">
 | 
					
 | 
				
			||||||
          <div className="max-h-[200px]">
 | 
					type CustomNodeList = {
 | 
				
			||||||
            <pre className="p-2 rounded-md text-xs ">
 | 
					  custom_nodes: {
 | 
				
			||||||
              {JSON.stringify(field.value, null, 2)}
 | 
					    author: string;
 | 
				
			||||||
            </pre>
 | 
					    title: string;
 | 
				
			||||||
          </div>
 | 
					    reference: string;
 | 
				
			||||||
        </ScrollArea>
 | 
					    files: string[];
 | 
				
			||||||
      )}
 | 
					    install_type: string;
 | 
				
			||||||
    </div>
 | 
					    description: string;
 | 
				
			||||||
 | 
					  }[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function CustomNodesSelector({
 | 
				
			||||||
 | 
					  field,
 | 
				
			||||||
 | 
					}: Pick<AutoFormInputComponentProps, "field">) {
 | 
				
			||||||
 | 
					  const [open, setOpen] = React.useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const customNodeList =
 | 
				
			||||||
 | 
					    field.value.git_custom_nodes ??
 | 
				
			||||||
 | 
					    ({} as Record<
 | 
				
			||||||
 | 
					      string,
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        hash: string;
 | 
				
			||||||
 | 
					        disabled: boolean;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    >);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { data, error, isLoading } = useSWR<CustomNodeList>(
 | 
				
			||||||
 | 
					    "https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/custom-node-list.json",
 | 
				
			||||||
 | 
					    fetcher
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const keys = React.useMemo(
 | 
				
			||||||
 | 
					    () => Object.keys(customNodeList),
 | 
				
			||||||
 | 
					    [customNodeList, data]
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function findItem(value: string) {
 | 
				
			||||||
 | 
					    // console.log(keys, value.toLowerCase());
 | 
				
			||||||
 | 
					    const included = keys.includes(value.toLowerCase());
 | 
				
			||||||
 | 
					    return included;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Popover open={open} onOpenChange={setOpen}>
 | 
				
			||||||
 | 
					      <PopoverTrigger asChild>
 | 
				
			||||||
 | 
					        <Button
 | 
				
			||||||
 | 
					          variant="outline"
 | 
				
			||||||
 | 
					          role="combobox"
 | 
				
			||||||
 | 
					          aria-expanded={open}
 | 
				
			||||||
 | 
					          className="w-full justify-between flex"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          Select custom nodes... {keys.length} selected
 | 
				
			||||||
 | 
					          <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
 | 
				
			||||||
 | 
					        </Button>
 | 
				
			||||||
 | 
					      </PopoverTrigger>
 | 
				
			||||||
 | 
					      <PopoverContent className="w-[375px] p-0" side="bottom">
 | 
				
			||||||
 | 
					        <Command>
 | 
				
			||||||
 | 
					          <CommandInput placeholder="Search custom nodes..." className="h-9" />
 | 
				
			||||||
 | 
					          <CommandEmpty>No custom nodes found.</CommandEmpty>
 | 
				
			||||||
 | 
					          <CommandList>
 | 
				
			||||||
 | 
					            <CommandGroup>
 | 
				
			||||||
 | 
					              {data &&
 | 
				
			||||||
 | 
					                data.custom_nodes?.map((framework, index) => (
 | 
				
			||||||
 | 
					                  <CommandItem
 | 
				
			||||||
 | 
					                    key={index}
 | 
				
			||||||
 | 
					                    value={framework.reference}
 | 
				
			||||||
 | 
					                    onSelect={(currentValue) => {
 | 
				
			||||||
 | 
					                      let nodeList: Record<
 | 
				
			||||||
 | 
					                        string,
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                          hash: string;
 | 
				
			||||||
 | 
					                          disabled: boolean;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                      >;
 | 
				
			||||||
 | 
					                      const x = customNodeList;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                      if (x[currentValue]) {
 | 
				
			||||||
 | 
					                        const newNodeList = { ...x };
 | 
				
			||||||
 | 
					                        delete newNodeList[currentValue];
 | 
				
			||||||
 | 
					                        nodeList = newNodeList;
 | 
				
			||||||
 | 
					                      } else {
 | 
				
			||||||
 | 
					                        nodeList = {
 | 
				
			||||||
 | 
					                          [currentValue]: {
 | 
				
			||||||
 | 
					                            hash: "latest",
 | 
				
			||||||
 | 
					                            disabled: false,
 | 
				
			||||||
 | 
					                          },
 | 
				
			||||||
 | 
					                          ...x,
 | 
				
			||||||
 | 
					                        };
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
 | 
					                      field.onChange({
 | 
				
			||||||
 | 
					                        ...field.value,
 | 
				
			||||||
 | 
					                        git_custom_nodes: nodeList,
 | 
				
			||||||
 | 
					                      });
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    {framework.title}
 | 
				
			||||||
 | 
					                    <Check
 | 
				
			||||||
 | 
					                      className={cn(
 | 
				
			||||||
 | 
					                        "ml-auto h-4 w-4",
 | 
				
			||||||
 | 
					                        findItem(framework.reference)
 | 
				
			||||||
 | 
					                          ? "opacity-100"
 | 
				
			||||||
 | 
					                          : "opacity-0"
 | 
				
			||||||
 | 
					                      )}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                  </CommandItem>
 | 
				
			||||||
 | 
					                ))}
 | 
				
			||||||
 | 
					            </CommandGroup>
 | 
				
			||||||
 | 
					          </CommandList>
 | 
				
			||||||
 | 
					        </Command>
 | 
				
			||||||
 | 
					      </PopoverContent>
 | 
				
			||||||
 | 
					    </Popover>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										9
									
								
								web/src/components/fetcher.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								web/src/components/fetcher.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					"use client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default async function fetcher<JSON = unknown>(
 | 
				
			||||||
 | 
					  input: RequestInfo,
 | 
				
			||||||
 | 
					  init?: RequestInit
 | 
				
			||||||
 | 
					): Promise<JSON> {
 | 
				
			||||||
 | 
					  const res = await fetch(input, init);
 | 
				
			||||||
 | 
					  return res.json();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user