update FE #2
			
				
			
		
		
		
	| 
						 | 
				
			
			@ -1,167 +1,169 @@
 | 
			
		|||
import { useCallback, useEffect, useRef } from "react"
 | 
			
		||||
import { useCallback, useEffect, useRef } from "react";
 | 
			
		||||
 | 
			
		||||
import useInputImage from "@/hooks/useInputImage"
 | 
			
		||||
import { keepGUIAlive } from "@/lib/utils"
 | 
			
		||||
import { getServerConfig } from "@/lib/api"
 | 
			
		||||
import Header from "@/components/Header"
 | 
			
		||||
import Workspace from "@/components/Workspace"
 | 
			
		||||
import FileSelect from "@/components/FileSelect"
 | 
			
		||||
import { Toaster } from "./components/ui/toaster"
 | 
			
		||||
import { useStore } from "./lib/states"
 | 
			
		||||
import { useWindowSize } from "react-use"
 | 
			
		||||
import useInputImage from "@/hooks/useInputImage";
 | 
			
		||||
import { keepGUIAlive } from "@/lib/utils";
 | 
			
		||||
import { getServerConfig } from "@/lib/api";
 | 
			
		||||
import Header from "@/components/Header";
 | 
			
		||||
import Workspace from "@/components/Workspace";
 | 
			
		||||
import FileSelect from "@/components/FileSelect";
 | 
			
		||||
import { Toaster } from "./components/ui/toaster";
 | 
			
		||||
import { useStore } from "./lib/states";
 | 
			
		||||
import { useWindowSize } from "react-use";
 | 
			
		||||
 | 
			
		||||
const SUPPORTED_FILE_TYPE = [
 | 
			
		||||
  "image/jpeg",
 | 
			
		||||
  "image/png",
 | 
			
		||||
  "image/webp",
 | 
			
		||||
  "image/bmp",
 | 
			
		||||
  "image/tiff",
 | 
			
		||||
]
 | 
			
		||||
	"image/jpeg",
 | 
			
		||||
	"image/png",
 | 
			
		||||
	"image/webp",
 | 
			
		||||
	"image/bmp",
 | 
			
		||||
	"image/tiff",
 | 
			
		||||
];
 | 
			
		||||
function Home() {
 | 
			
		||||
  const [file, updateAppState, setServerConfig, setFile] = useStore((state) => [
 | 
			
		||||
    state.file,
 | 
			
		||||
    state.updateAppState,
 | 
			
		||||
    state.setServerConfig,
 | 
			
		||||
    state.setFile,
 | 
			
		||||
  ])
 | 
			
		||||
	const [file, updateAppState, setServerConfig, setFile] = useStore(
 | 
			
		||||
		(state) => [
 | 
			
		||||
			state.file,
 | 
			
		||||
			state.updateAppState,
 | 
			
		||||
			state.setServerConfig,
 | 
			
		||||
			state.setFile,
 | 
			
		||||
		],
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
  const userInputImage = useInputImage()
 | 
			
		||||
	const userInputImage = useInputImage();
 | 
			
		||||
 | 
			
		||||
  const windowSize = useWindowSize()
 | 
			
		||||
	const windowSize = useWindowSize();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (userInputImage) {
 | 
			
		||||
      setFile(userInputImage)
 | 
			
		||||
    }
 | 
			
		||||
  }, [userInputImage, setFile])
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		if (userInputImage) {
 | 
			
		||||
			setFile(userInputImage);
 | 
			
		||||
		}
 | 
			
		||||
	}, [userInputImage, setFile]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    updateAppState({ windowSize })
 | 
			
		||||
  }, [windowSize])
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		updateAppState({ windowSize });
 | 
			
		||||
	}, [windowSize]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const fetchServerConfig = async () => {
 | 
			
		||||
      const serverConfig = await getServerConfig()
 | 
			
		||||
      setServerConfig(serverConfig)
 | 
			
		||||
      if (serverConfig.isDesktop) {
 | 
			
		||||
        // Keeping GUI Window Open
 | 
			
		||||
        keepGUIAlive()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    fetchServerConfig()
 | 
			
		||||
  }, [])
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		const fetchServerConfig = async () => {
 | 
			
		||||
			const serverConfig = await getServerConfig();
 | 
			
		||||
			setServerConfig(serverConfig);
 | 
			
		||||
			if (serverConfig.isDesktop) {
 | 
			
		||||
				// Keeping GUI Window Open
 | 
			
		||||
				keepGUIAlive();
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
		fetchServerConfig();
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
  const dragCounter = useRef(0)
 | 
			
		||||
	const dragCounter = useRef(0);
 | 
			
		||||
 | 
			
		||||
  const handleDrag = useCallback((event: any) => {
 | 
			
		||||
    event.preventDefault()
 | 
			
		||||
    event.stopPropagation()
 | 
			
		||||
  }, [])
 | 
			
		||||
	const handleDrag = useCallback((event: any) => {
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		event.stopPropagation();
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
  const handleDragIn = useCallback((event: any) => {
 | 
			
		||||
    event.preventDefault()
 | 
			
		||||
    event.stopPropagation()
 | 
			
		||||
    dragCounter.current += 1
 | 
			
		||||
  }, [])
 | 
			
		||||
	const handleDragIn = useCallback((event: any) => {
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		event.stopPropagation();
 | 
			
		||||
		dragCounter.current += 1;
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
  const handleDragOut = useCallback((event: any) => {
 | 
			
		||||
    event.preventDefault()
 | 
			
		||||
    event.stopPropagation()
 | 
			
		||||
    dragCounter.current -= 1
 | 
			
		||||
    if (dragCounter.current > 0) return
 | 
			
		||||
  }, [])
 | 
			
		||||
	const handleDragOut = useCallback((event: any) => {
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		event.stopPropagation();
 | 
			
		||||
		dragCounter.current -= 1;
 | 
			
		||||
		if (dragCounter.current > 0) return;
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
  const handleDrop = useCallback((event: any) => {
 | 
			
		||||
    event.preventDefault()
 | 
			
		||||
    event.stopPropagation()
 | 
			
		||||
    if (event.dataTransfer.files && event.dataTransfer.files.length > 0) {
 | 
			
		||||
      if (event.dataTransfer.files.length > 1) {
 | 
			
		||||
        // setToastState({
 | 
			
		||||
        //   open: true,
 | 
			
		||||
        //   desc: "Please drag and drop only one file",
 | 
			
		||||
        //   state: "error",
 | 
			
		||||
        //   duration: 3000,
 | 
			
		||||
        // })
 | 
			
		||||
      } else {
 | 
			
		||||
        const dragFile = event.dataTransfer.files[0]
 | 
			
		||||
        const fileType = dragFile.type
 | 
			
		||||
        if (SUPPORTED_FILE_TYPE.includes(fileType)) {
 | 
			
		||||
          setFile(dragFile)
 | 
			
		||||
        } else {
 | 
			
		||||
          // setToastState({
 | 
			
		||||
          //   open: true,
 | 
			
		||||
          //   desc: "Please drag and drop an image file",
 | 
			
		||||
          //   state: "error",
 | 
			
		||||
          //   duration: 3000,
 | 
			
		||||
          // })
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      event.dataTransfer.clearData()
 | 
			
		||||
    }
 | 
			
		||||
  }, [])
 | 
			
		||||
	const handleDrop = useCallback((event: any) => {
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		event.stopPropagation();
 | 
			
		||||
		if (event.dataTransfer.files && event.dataTransfer.files.length > 0) {
 | 
			
		||||
			if (event.dataTransfer.files.length > 1) {
 | 
			
		||||
				// setToastState({
 | 
			
		||||
				//   open: true,
 | 
			
		||||
				//   desc: "Please drag and drop only one file",
 | 
			
		||||
				//   state: "error",
 | 
			
		||||
				//   duration: 3000,
 | 
			
		||||
				// })
 | 
			
		||||
			} else {
 | 
			
		||||
				const dragFile = event.dataTransfer.files[0];
 | 
			
		||||
				const fileType = dragFile.type;
 | 
			
		||||
				if (SUPPORTED_FILE_TYPE.includes(fileType)) {
 | 
			
		||||
					setFile(dragFile);
 | 
			
		||||
				} else {
 | 
			
		||||
					// setToastState({
 | 
			
		||||
					//   open: true,
 | 
			
		||||
					//   desc: "Please drag and drop an image file",
 | 
			
		||||
					//   state: "error",
 | 
			
		||||
					//   duration: 3000,
 | 
			
		||||
					// })
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			event.dataTransfer.clearData();
 | 
			
		||||
		}
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
  const onPaste = useCallback((event: any) => {
 | 
			
		||||
    // TODO: when sd side panel open, ctrl+v not work
 | 
			
		||||
    // https://htmldom.dev/paste-an-image-from-the-clipboard/
 | 
			
		||||
    if (!event.clipboardData) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    const clipboardItems = event.clipboardData.items
 | 
			
		||||
    const items: DataTransferItem[] = [].slice
 | 
			
		||||
      .call(clipboardItems)
 | 
			
		||||
      .filter((item: DataTransferItem) => {
 | 
			
		||||
        // Filter the image items only
 | 
			
		||||
        return item.type.indexOf("image") !== -1
 | 
			
		||||
      })
 | 
			
		||||
	const onPaste = useCallback((event: any) => {
 | 
			
		||||
		// TODO: when sd side panel open, ctrl+v not work
 | 
			
		||||
		// https://htmldom.dev/paste-an-image-from-the-clipboard/
 | 
			
		||||
		if (!event.clipboardData) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		const clipboardItems = event.clipboardData.items;
 | 
			
		||||
		const items: DataTransferItem[] = [].slice
 | 
			
		||||
			.call(clipboardItems)
 | 
			
		||||
			.filter((item: DataTransferItem) => {
 | 
			
		||||
				// Filter the image items only
 | 
			
		||||
				return item.type.indexOf("image") !== -1;
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
    if (items.length === 0) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
		if (items.length === 0) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
    event.preventDefault()
 | 
			
		||||
    event.stopPropagation()
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		event.stopPropagation();
 | 
			
		||||
 | 
			
		||||
    // TODO: add confirm dialog
 | 
			
		||||
		// TODO: add confirm dialog
 | 
			
		||||
 | 
			
		||||
    const item = items[0]
 | 
			
		||||
    // Get the blob of image
 | 
			
		||||
    const blob = item.getAsFile()
 | 
			
		||||
    if (blob) {
 | 
			
		||||
      setFile(blob)
 | 
			
		||||
    }
 | 
			
		||||
  }, [])
 | 
			
		||||
		const item = items[0];
 | 
			
		||||
		// Get the blob of image
 | 
			
		||||
		const blob = item.getAsFile();
 | 
			
		||||
		if (blob) {
 | 
			
		||||
			setFile(blob);
 | 
			
		||||
		}
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    window.addEventListener("dragenter", handleDragIn)
 | 
			
		||||
    window.addEventListener("dragleave", handleDragOut)
 | 
			
		||||
    window.addEventListener("dragover", handleDrag)
 | 
			
		||||
    window.addEventListener("drop", handleDrop)
 | 
			
		||||
    window.addEventListener("paste", onPaste)
 | 
			
		||||
    return function cleanUp() {
 | 
			
		||||
      window.removeEventListener("dragenter", handleDragIn)
 | 
			
		||||
      window.removeEventListener("dragleave", handleDragOut)
 | 
			
		||||
      window.removeEventListener("dragover", handleDrag)
 | 
			
		||||
      window.removeEventListener("drop", handleDrop)
 | 
			
		||||
      window.removeEventListener("paste", onPaste)
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		window.addEventListener("dragenter", handleDragIn);
 | 
			
		||||
		window.addEventListener("dragleave", handleDragOut);
 | 
			
		||||
		window.addEventListener("dragover", handleDrag);
 | 
			
		||||
		window.addEventListener("drop", handleDrop);
 | 
			
		||||
		window.addEventListener("paste", onPaste);
 | 
			
		||||
		return function cleanUp() {
 | 
			
		||||
			window.removeEventListener("dragenter", handleDragIn);
 | 
			
		||||
			window.removeEventListener("dragleave", handleDragOut);
 | 
			
		||||
			window.removeEventListener("dragover", handleDrag);
 | 
			
		||||
			window.removeEventListener("drop", handleDrop);
 | 
			
		||||
			window.removeEventListener("paste", onPaste);
 | 
			
		||||
		};
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <main className="flex min-h-screen flex-col items-center justify-between w-full bg-[radial-gradient(circle_at_1px_1px,_#8e8e8e8e_1px,_transparent_0)] [background-size:20px_20px] bg-repeat">
 | 
			
		||||
      <Toaster />
 | 
			
		||||
      <Header />
 | 
			
		||||
      <Workspace />
 | 
			
		||||
      {!file ? (
 | 
			
		||||
        <FileSelect
 | 
			
		||||
          onSelection={async (f) => {
 | 
			
		||||
            setFile(f)
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      ) : (
 | 
			
		||||
        <></>
 | 
			
		||||
      )}
 | 
			
		||||
    </main>
 | 
			
		||||
  )
 | 
			
		||||
	return (
 | 
			
		||||
		<main className="flex min-h-screen flex-col items-center justify-between w-full bg-[radial-gradient(circle_at_1px_1px,_#8e8e8e8e_1px,_transparent_0)] [background-size:20px_20px] bg-repeat">
 | 
			
		||||
			<Toaster />
 | 
			
		||||
			<Header />
 | 
			
		||||
			<Workspace />
 | 
			
		||||
			{!file ? (
 | 
			
		||||
				<FileSelect
 | 
			
		||||
					onSelection={async (f) => {
 | 
			
		||||
						setFile(f);
 | 
			
		||||
					}}
 | 
			
		||||
				/>
 | 
			
		||||
			) : (
 | 
			
		||||
				<></>
 | 
			
		||||
			)}
 | 
			
		||||
		</main>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Home
 | 
			
		||||
export default Home;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -1,71 +1,71 @@
 | 
			
		|||
import { useState } from "react"
 | 
			
		||||
import useResolution from "@/hooks/useResolution"
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
import useResolution from "@/hooks/useResolution";
 | 
			
		||||
 | 
			
		||||
type FileSelectProps = {
 | 
			
		||||
  onSelection: (file: File) => void
 | 
			
		||||
}
 | 
			
		||||
	onSelection: (file: File) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function FileSelect(props: FileSelectProps) {
 | 
			
		||||
  const { onSelection } = props
 | 
			
		||||
	const { onSelection } = props;
 | 
			
		||||
 | 
			
		||||
  const [uploadElemId] = useState(`file-upload-${Math.random().toString()}`)
 | 
			
		||||
	const [uploadElemId] = useState(`file-upload-${Math.random().toString()}`);
 | 
			
		||||
 | 
			
		||||
  const resolution = useResolution()
 | 
			
		||||
	const resolution = useResolution();
 | 
			
		||||
 | 
			
		||||
  function onFileSelected(file: File) {
 | 
			
		||||
    if (!file) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    // Skip non-image files
 | 
			
		||||
    const isImage = file.type.match("image.*")
 | 
			
		||||
    if (!isImage) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
      // Check if file is larger than 20mb
 | 
			
		||||
      if (file.size > 20 * 1024 * 1024) {
 | 
			
		||||
        throw new Error("file too large")
 | 
			
		||||
      }
 | 
			
		||||
      onSelection(file)
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      // eslint-disable-next-line
 | 
			
		||||
      alert(`error: ${(e as any).message}`)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
	function onFileSelected(file: File) {
 | 
			
		||||
		if (!file) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		// Skip non-image files
 | 
			
		||||
		const isImage = file.type.match("image.*");
 | 
			
		||||
		if (!isImage) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		try {
 | 
			
		||||
			// Check if file is larger than 20mb
 | 
			
		||||
			if (file.size > 20 * 1024 * 1024) {
 | 
			
		||||
				throw new Error("file too large");
 | 
			
		||||
			}
 | 
			
		||||
			onSelection(file);
 | 
			
		||||
		} catch (e) {
 | 
			
		||||
			// eslint-disable-next-line
 | 
			
		||||
			alert(`error: ${(e as any).message}`);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="absolute flex w-screen h-screen justify-center items-center pointer-events-none">
 | 
			
		||||
      <label
 | 
			
		||||
        htmlFor={uploadElemId}
 | 
			
		||||
        className="grid bg-background border-[2px] border-[dashed] rounded-lg min-w-[600px] hover:bg-primary hover:text-primary-foreground pointer-events-auto"
 | 
			
		||||
      >
 | 
			
		||||
        <div
 | 
			
		||||
          className="grid p-16 w-full h-full"
 | 
			
		||||
          onDragOver={(ev) => {
 | 
			
		||||
            ev.stopPropagation()
 | 
			
		||||
            ev.preventDefault()
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <input
 | 
			
		||||
            className="hidden"
 | 
			
		||||
            id={uploadElemId}
 | 
			
		||||
            name={uploadElemId}
 | 
			
		||||
            type="file"
 | 
			
		||||
            onChange={(ev) => {
 | 
			
		||||
              const file = ev.currentTarget.files?.[0]
 | 
			
		||||
              if (file) {
 | 
			
		||||
                onFileSelected(file)
 | 
			
		||||
              }
 | 
			
		||||
            }}
 | 
			
		||||
            accept="image/png, image/jpeg"
 | 
			
		||||
          />
 | 
			
		||||
          <p className="text-center">
 | 
			
		||||
            {resolution === "desktop"
 | 
			
		||||
              ? "Click here or drag an image file"
 | 
			
		||||
              : "Tap here to load your picture"}
 | 
			
		||||
          </p>
 | 
			
		||||
        </div>
 | 
			
		||||
      </label>
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
	return (
 | 
			
		||||
		<div className="absolute flex w-screen h-screen justify-center items-center pointer-events-none">
 | 
			
		||||
			<label
 | 
			
		||||
				htmlFor={uploadElemId}
 | 
			
		||||
				className="grid bg-background border-[2px] border-[dashed] rounded-lg min-w-[600px] hover:bg-primary hover:text-primary-foreground pointer-events-auto"
 | 
			
		||||
			>
 | 
			
		||||
				<div
 | 
			
		||||
					className="grid p-16 w-full h-full"
 | 
			
		||||
					onDragOver={(ev) => {
 | 
			
		||||
						ev.stopPropagation();
 | 
			
		||||
						ev.preventDefault();
 | 
			
		||||
					}}
 | 
			
		||||
				>
 | 
			
		||||
					<input
 | 
			
		||||
						className="hidden"
 | 
			
		||||
						id={uploadElemId}
 | 
			
		||||
						name={uploadElemId}
 | 
			
		||||
						type="file"
 | 
			
		||||
						onChange={(ev) => {
 | 
			
		||||
							const file = ev.currentTarget.files?.[0];
 | 
			
		||||
							if (file) {
 | 
			
		||||
								onFileSelected(file);
 | 
			
		||||
							}
 | 
			
		||||
						}}
 | 
			
		||||
						accept="image/png, image/jpeg"
 | 
			
		||||
					/>
 | 
			
		||||
					<p className="text-center">
 | 
			
		||||
						{resolution === "desktop"
 | 
			
		||||
							? "Click here or drag an image file"
 | 
			
		||||
							: "Tap here to load your picture"}
 | 
			
		||||
					</p>
 | 
			
		||||
				</div>
 | 
			
		||||
			</label>
 | 
			
		||||
		</div>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,198 +1,73 @@
 | 
			
		|||
import { PlayIcon } from "@radix-ui/react-icons"
 | 
			
		||||
import { useState } from "react"
 | 
			
		||||
import { IconButton, ImageUploadButton } from "@/components/ui/button"
 | 
			
		||||
import Shortcuts from "@/components/Shortcuts"
 | 
			
		||||
import { useImage } from "@/hooks/useImage"
 | 
			
		||||
import { IconButton, ImageUploadButton } from "@/components/ui/button";
 | 
			
		||||
 | 
			
		||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"
 | 
			
		||||
import PromptInput from "./PromptInput"
 | 
			
		||||
import { RotateCw, Image, Upload } from "lucide-react"
 | 
			
		||||
import FileManager from "./FileManager"
 | 
			
		||||
import { getMediaFile } from "@/lib/api"
 | 
			
		||||
import { useStore } from "@/lib/states"
 | 
			
		||||
import SettingsDialog from "./Settings"
 | 
			
		||||
import { cn, fileToImage } from "@/lib/utils"
 | 
			
		||||
import Coffee from "./Coffee"
 | 
			
		||||
import { useToast } from "./ui/use-toast"
 | 
			
		||||
import PromptInput from "./PromptInput";
 | 
			
		||||
import { RotateCw, Image } from "lucide-react";
 | 
			
		||||
import { useStore } from "@/lib/states";
 | 
			
		||||
 | 
			
		||||
const Header = () => {
 | 
			
		||||
  const [
 | 
			
		||||
    file,
 | 
			
		||||
    customMask,
 | 
			
		||||
    isInpainting,
 | 
			
		||||
    serverConfig,
 | 
			
		||||
    runMannually,
 | 
			
		||||
    enableUploadMask,
 | 
			
		||||
    model,
 | 
			
		||||
    setFile,
 | 
			
		||||
    setCustomFile,
 | 
			
		||||
    runInpainting,
 | 
			
		||||
    showPrevMask,
 | 
			
		||||
    hidePrevMask,
 | 
			
		||||
    imageHeight,
 | 
			
		||||
    imageWidth,
 | 
			
		||||
  ] = useStore((state) => [
 | 
			
		||||
    state.file,
 | 
			
		||||
    state.customMask,
 | 
			
		||||
    state.isInpainting,
 | 
			
		||||
    state.serverConfig,
 | 
			
		||||
    state.runMannually(),
 | 
			
		||||
    state.settings.enableUploadMask,
 | 
			
		||||
    state.settings.model,
 | 
			
		||||
    state.setFile,
 | 
			
		||||
    state.setCustomFile,
 | 
			
		||||
    state.runInpainting,
 | 
			
		||||
    state.showPrevMask,
 | 
			
		||||
    state.hidePrevMask,
 | 
			
		||||
    state.imageHeight,
 | 
			
		||||
    state.imageWidth,
 | 
			
		||||
  ])
 | 
			
		||||
	const [
 | 
			
		||||
		file,
 | 
			
		||||
		isInpainting,
 | 
			
		||||
		model,
 | 
			
		||||
		setFile,
 | 
			
		||||
		runInpainting,
 | 
			
		||||
		showPrevMask,
 | 
			
		||||
		hidePrevMask,
 | 
			
		||||
	] = useStore((state) => [
 | 
			
		||||
		state.file,
 | 
			
		||||
		state.isInpainting,
 | 
			
		||||
		state.settings.model,
 | 
			
		||||
		state.setFile,
 | 
			
		||||
		state.runInpainting,
 | 
			
		||||
		state.showPrevMask,
 | 
			
		||||
		state.hidePrevMask,
 | 
			
		||||
	]);
 | 
			
		||||
 | 
			
		||||
  const { toast } = useToast()
 | 
			
		||||
  const [maskImage, maskImageLoaded] = useImage(customMask)
 | 
			
		||||
  const [openMaskPopover, setOpenMaskPopover] = useState(false)
 | 
			
		||||
	const handleRerunLastMask = () => {
 | 
			
		||||
		runInpainting();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
  const handleRerunLastMask = () => {
 | 
			
		||||
    runInpainting()
 | 
			
		||||
  }
 | 
			
		||||
	const onRerunMouseEnter = () => {
 | 
			
		||||
		showPrevMask();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
  const onRerunMouseEnter = () => {
 | 
			
		||||
    showPrevMask()
 | 
			
		||||
  }
 | 
			
		||||
	const onRerunMouseLeave = () => {
 | 
			
		||||
		hidePrevMask();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
  const onRerunMouseLeave = () => {
 | 
			
		||||
    hidePrevMask()
 | 
			
		||||
  }
 | 
			
		||||
	return (
 | 
			
		||||
		<header className="h-[60px] px-6 py-4 absolute top-[0] flex justify-between items-center w-full z-20 border-b backdrop-filter backdrop-blur-md bg-background/70">
 | 
			
		||||
			<div className="flex items-center gap-1">
 | 
			
		||||
				<ImageUploadButton
 | 
			
		||||
					disabled={isInpainting}
 | 
			
		||||
					tooltip="Upload image"
 | 
			
		||||
					onFileUpload={(file) => {
 | 
			
		||||
						setFile(file);
 | 
			
		||||
					}}
 | 
			
		||||
				>
 | 
			
		||||
					<Image />
 | 
			
		||||
				</ImageUploadButton>
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <header className="h-[60px] px-6 py-4 absolute top-[0] flex justify-between items-center w-full z-20 border-b backdrop-filter backdrop-blur-md bg-background/70">
 | 
			
		||||
      <div className="flex items-center gap-1">
 | 
			
		||||
        {serverConfig.enableFileManager ? (
 | 
			
		||||
          <FileManager
 | 
			
		||||
            photoWidth={512}
 | 
			
		||||
            onPhotoClick={async (tab: string, filename: string) => {
 | 
			
		||||
              try {
 | 
			
		||||
                const newFile = await getMediaFile(tab, filename)
 | 
			
		||||
                setFile(newFile)
 | 
			
		||||
              } catch (e: any) {
 | 
			
		||||
                toast({
 | 
			
		||||
                  variant: "destructive",
 | 
			
		||||
                  description: e.message ? e.message : e.toString(),
 | 
			
		||||
                })
 | 
			
		||||
                return
 | 
			
		||||
              }
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        ) : (
 | 
			
		||||
          <></>
 | 
			
		||||
        )}
 | 
			
		||||
				{file && !model.need_prompt ? (
 | 
			
		||||
					<IconButton
 | 
			
		||||
						disabled={isInpainting}
 | 
			
		||||
						tooltip="Rerun previous mask"
 | 
			
		||||
						onClick={handleRerunLastMask}
 | 
			
		||||
						onMouseEnter={onRerunMouseEnter}
 | 
			
		||||
						onMouseLeave={onRerunMouseLeave}
 | 
			
		||||
					>
 | 
			
		||||
						<RotateCw />
 | 
			
		||||
					</IconButton>
 | 
			
		||||
				) : (
 | 
			
		||||
					<></>
 | 
			
		||||
				)}
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
        <ImageUploadButton
 | 
			
		||||
          disabled={isInpainting}
 | 
			
		||||
          tooltip="Upload image"
 | 
			
		||||
          onFileUpload={(file) => {
 | 
			
		||||
            setFile(file)
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <Image />
 | 
			
		||||
        </ImageUploadButton>
 | 
			
		||||
			{model.need_prompt ? <PromptInput /> : <></>}
 | 
			
		||||
 | 
			
		||||
        <div
 | 
			
		||||
          className={cn([
 | 
			
		||||
            "flex items-center gap-1",
 | 
			
		||||
            file && enableUploadMask ? "visible" : "hidden",
 | 
			
		||||
          ])}
 | 
			
		||||
        >
 | 
			
		||||
          <ImageUploadButton
 | 
			
		||||
            disabled={isInpainting}
 | 
			
		||||
            tooltip="Upload custom mask"
 | 
			
		||||
            onFileUpload={async (file) => {
 | 
			
		||||
              let newCustomMask: HTMLImageElement | null = null
 | 
			
		||||
              try {
 | 
			
		||||
                newCustomMask = await fileToImage(file)
 | 
			
		||||
              } catch (e: any) {
 | 
			
		||||
                toast({
 | 
			
		||||
                  variant: "destructive",
 | 
			
		||||
                  description: e.message ? e.message : e.toString(),
 | 
			
		||||
                })
 | 
			
		||||
                return
 | 
			
		||||
              }
 | 
			
		||||
              if (
 | 
			
		||||
                newCustomMask.naturalHeight !== imageHeight ||
 | 
			
		||||
                newCustomMask.naturalWidth !== imageWidth
 | 
			
		||||
              ) {
 | 
			
		||||
                toast({
 | 
			
		||||
                  variant: "destructive",
 | 
			
		||||
                  description: `The size of the mask must same as image: ${imageWidth}x${imageHeight}`,
 | 
			
		||||
                })
 | 
			
		||||
                return
 | 
			
		||||
              }
 | 
			
		||||
			<div className="flex gap-1"></div>
 | 
			
		||||
		</header>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
              setCustomFile(file)
 | 
			
		||||
              if (!runMannually) {
 | 
			
		||||
                runInpainting()
 | 
			
		||||
              }
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            <Upload />
 | 
			
		||||
          </ImageUploadButton>
 | 
			
		||||
 | 
			
		||||
          {customMask ? (
 | 
			
		||||
            <Popover open={openMaskPopover}>
 | 
			
		||||
              <PopoverTrigger
 | 
			
		||||
                className="btn-primary side-panel-trigger"
 | 
			
		||||
                onMouseEnter={() => setOpenMaskPopover(true)}
 | 
			
		||||
                onMouseLeave={() => setOpenMaskPopover(false)}
 | 
			
		||||
                style={{
 | 
			
		||||
                  visibility: customMask ? "visible" : "hidden",
 | 
			
		||||
                  outline: "none",
 | 
			
		||||
                }}
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                  if (customMask) {
 | 
			
		||||
                  }
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
                <IconButton tooltip="Run custom mask">
 | 
			
		||||
                  <PlayIcon />
 | 
			
		||||
                </IconButton>
 | 
			
		||||
              </PopoverTrigger>
 | 
			
		||||
              <PopoverContent>
 | 
			
		||||
                {maskImageLoaded ? (
 | 
			
		||||
                  <img src={maskImage.src} alt="Custom mask" />
 | 
			
		||||
                ) : (
 | 
			
		||||
                  <></>
 | 
			
		||||
                )}
 | 
			
		||||
              </PopoverContent>
 | 
			
		||||
            </Popover>
 | 
			
		||||
          ) : (
 | 
			
		||||
            <></>
 | 
			
		||||
          )}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        {file && !model.need_prompt ? (
 | 
			
		||||
          <IconButton
 | 
			
		||||
            disabled={isInpainting}
 | 
			
		||||
            tooltip="Rerun previous mask"
 | 
			
		||||
            onClick={handleRerunLastMask}
 | 
			
		||||
            onMouseEnter={onRerunMouseEnter}
 | 
			
		||||
            onMouseLeave={onRerunMouseLeave}
 | 
			
		||||
          >
 | 
			
		||||
            <RotateCw />
 | 
			
		||||
          </IconButton>
 | 
			
		||||
        ) : (
 | 
			
		||||
          <></>
 | 
			
		||||
        )}
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      {model.need_prompt ? <PromptInput /> : <></>}
 | 
			
		||||
 | 
			
		||||
      <div className="flex gap-1">
 | 
			
		||||
        <Coffee />
 | 
			
		||||
        <Shortcuts />
 | 
			
		||||
        {serverConfig.disableModelSwitch ? <></> : <SettingsDialog />}
 | 
			
		||||
      </div>
 | 
			
		||||
    </header>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Header
 | 
			
		||||
export default Header;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,39 +1,31 @@
 | 
			
		|||
import { useEffect } from "react"
 | 
			
		||||
import Editor from "./Editor"
 | 
			
		||||
import { currentModel } from "@/lib/api"
 | 
			
		||||
import { useStore } from "@/lib/states"
 | 
			
		||||
import ImageSize from "./ImageSize"
 | 
			
		||||
import Plugins from "./Plugins"
 | 
			
		||||
import { InteractiveSeg } from "./InteractiveSeg"
 | 
			
		||||
import SidePanel from "./SidePanel"
 | 
			
		||||
import DiffusionProgress from "./DiffusionProgress"
 | 
			
		||||
import { useEffect } from "react";
 | 
			
		||||
import Editor from "./Editor";
 | 
			
		||||
import { currentModel } from "@/lib/api";
 | 
			
		||||
import { useStore } from "@/lib/states";
 | 
			
		||||
import ImageSize from "./ImageSize";
 | 
			
		||||
 | 
			
		||||
const Workspace = () => {
 | 
			
		||||
  const [file, updateSettings] = useStore((state) => [
 | 
			
		||||
    state.file,
 | 
			
		||||
    state.updateSettings,
 | 
			
		||||
  ])
 | 
			
		||||
	const [file, updateSettings] = useStore((state) => [
 | 
			
		||||
		state.file,
 | 
			
		||||
		state.updateSettings,
 | 
			
		||||
	]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const fetchCurrentModel = async () => {
 | 
			
		||||
      const model = await currentModel()
 | 
			
		||||
      updateSettings({ model })
 | 
			
		||||
    }
 | 
			
		||||
    fetchCurrentModel()
 | 
			
		||||
  }, [])
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		const fetchCurrentModel = async () => {
 | 
			
		||||
			const model = await currentModel();
 | 
			
		||||
			updateSettings({ model });
 | 
			
		||||
		};
 | 
			
		||||
		fetchCurrentModel();
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <div className="flex gap-3 absolute top-[68px] left-[24px] items-center">
 | 
			
		||||
        <Plugins />
 | 
			
		||||
        <ImageSize />
 | 
			
		||||
      </div>
 | 
			
		||||
      <InteractiveSeg />
 | 
			
		||||
      <DiffusionProgress />
 | 
			
		||||
      <SidePanel />
 | 
			
		||||
      {file ? <Editor file={file} /> : <></>}
 | 
			
		||||
    </>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
	return (
 | 
			
		||||
		<>
 | 
			
		||||
			<div className="flex gap-3 absolute top-[68px] left-[24px] items-center">
 | 
			
		||||
				<ImageSize />
 | 
			
		||||
			</div>
 | 
			
		||||
			{file ? <Editor file={file} /> : <></>}
 | 
			
		||||
		</>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default Workspace
 | 
			
		||||
export default Workspace;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,231 +1,256 @@
 | 
			
		|||
import {
 | 
			
		||||
  Filename,
 | 
			
		||||
  GenInfo,
 | 
			
		||||
  ModelInfo,
 | 
			
		||||
  PowerPaintTask,
 | 
			
		||||
  Rect,
 | 
			
		||||
  ServerConfig,
 | 
			
		||||
	Filename,
 | 
			
		||||
	GenInfo,
 | 
			
		||||
	ModelInfo,
 | 
			
		||||
	PowerPaintTask,
 | 
			
		||||
	Rect,
 | 
			
		||||
	ServerConfig,
 | 
			
		||||
} from "@/lib/types";
 | 
			
		||||
import { Settings } from "@/lib/states";
 | 
			
		||||
import { convertToBase64, srcToFile } from "@/lib/utils";
 | 
			
		||||
import axios from "axios";
 | 
			
		||||
 | 
			
		||||
export const API_ENDPOINT = import.meta.env.DEV
 | 
			
		||||
  ? import.meta.env.VITE_BACKEND + "/api/v1"
 | 
			
		||||
  : "/api/v1";
 | 
			
		||||
	? import.meta.env.VITE_BACKEND + "/api/v1"
 | 
			
		||||
	: "/api/v1";
 | 
			
		||||
 | 
			
		||||
const api = axios.create({
 | 
			
		||||
  baseURL: API_ENDPOINT,
 | 
			
		||||
	baseURL: API_ENDPOINT,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default async function inpaint(
 | 
			
		||||
  imageFile: File,
 | 
			
		||||
  settings: Settings,
 | 
			
		||||
  croperRect: Rect,
 | 
			
		||||
  extenderState: Rect,
 | 
			
		||||
  mask: File | Blob,
 | 
			
		||||
  paintByExampleImage: File | null = null
 | 
			
		||||
	imageFile: File,
 | 
			
		||||
	settings: Settings,
 | 
			
		||||
	croperRect: Rect,
 | 
			
		||||
	extenderState: Rect,
 | 
			
		||||
	mask: File | Blob,
 | 
			
		||||
	paintByExampleImage: File | null = null,
 | 
			
		||||
) {
 | 
			
		||||
  const imageBase64 = await convertToBase64(imageFile);
 | 
			
		||||
  const maskBase64 = await convertToBase64(mask);
 | 
			
		||||
  const exampleImageBase64 = paintByExampleImage
 | 
			
		||||
    ? await convertToBase64(paintByExampleImage)
 | 
			
		||||
    : null;
 | 
			
		||||
	const imageBase64 = await convertToBase64(imageFile);
 | 
			
		||||
	const maskBase64 = await convertToBase64(mask);
 | 
			
		||||
	const exampleImageBase64 = paintByExampleImage
 | 
			
		||||
		? await convertToBase64(paintByExampleImage)
 | 
			
		||||
		: null;
 | 
			
		||||
 | 
			
		||||
  const res = await fetch(`${API_ENDPOINT}/inpaint`, {
 | 
			
		||||
    method: "POST",
 | 
			
		||||
    headers: {
 | 
			
		||||
      "Content-Type": "application/json",
 | 
			
		||||
    },
 | 
			
		||||
    body: JSON.stringify({
 | 
			
		||||
      image: imageBase64,
 | 
			
		||||
      mask: maskBase64,
 | 
			
		||||
      ldm_steps: settings.ldmSteps,
 | 
			
		||||
      ldm_sampler: settings.ldmSampler,
 | 
			
		||||
      zits_wireframe: settings.zitsWireframe,
 | 
			
		||||
      cv2_flag: settings.cv2Flag,
 | 
			
		||||
      cv2_radius: settings.cv2Radius,
 | 
			
		||||
      hd_strategy: "Crop",
 | 
			
		||||
      hd_strategy_crop_triger_size: 640,
 | 
			
		||||
      hd_strategy_crop_margin: 128,
 | 
			
		||||
      hd_trategy_resize_imit: 2048,
 | 
			
		||||
      prompt: settings.prompt,
 | 
			
		||||
      negative_prompt: settings.negativePrompt,
 | 
			
		||||
      use_croper: settings.showCropper,
 | 
			
		||||
      croper_x: croperRect.x,
 | 
			
		||||
      croper_y: croperRect.y,
 | 
			
		||||
      croper_height: croperRect.height,
 | 
			
		||||
      croper_width: croperRect.width,
 | 
			
		||||
      use_extender: settings.showExtender,
 | 
			
		||||
      extender_x: extenderState.x,
 | 
			
		||||
      extender_y: extenderState.y,
 | 
			
		||||
      extender_height: extenderState.height,
 | 
			
		||||
      extender_width: extenderState.width,
 | 
			
		||||
      sd_mask_blur: settings.sdMaskBlur,
 | 
			
		||||
      sd_strength: settings.sdStrength,
 | 
			
		||||
      sd_steps: settings.sdSteps,
 | 
			
		||||
      sd_guidance_scale: settings.sdGuidanceScale,
 | 
			
		||||
      sd_sampler: settings.sdSampler,
 | 
			
		||||
      sd_seed: settings.seedFixed ? settings.seed : -1,
 | 
			
		||||
      sd_match_histograms: settings.sdMatchHistograms,
 | 
			
		||||
      sd_freeu: settings.enableFreeu,
 | 
			
		||||
      sd_freeu_config: settings.freeuConfig,
 | 
			
		||||
      sd_lcm_lora: settings.enableLCMLora,
 | 
			
		||||
      paint_by_example_example_image: exampleImageBase64,
 | 
			
		||||
      p2p_image_guidance_scale: settings.p2pImageGuidanceScale,
 | 
			
		||||
      enable_controlnet: settings.enableControlnet,
 | 
			
		||||
      controlnet_conditioning_scale: settings.controlnetConditioningScale,
 | 
			
		||||
      controlnet_method: settings.controlnetMethod
 | 
			
		||||
        ? settings.controlnetMethod
 | 
			
		||||
        : "",
 | 
			
		||||
      powerpaint_task: settings.showExtender
 | 
			
		||||
        ? PowerPaintTask.outpainting
 | 
			
		||||
        : settings.powerpaintTask,
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
  if (res.ok) {
 | 
			
		||||
    const blob = await res.blob();
 | 
			
		||||
    return {
 | 
			
		||||
      blob: URL.createObjectURL(blob),
 | 
			
		||||
      seed: res.headers.get("X-Seed"),
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  const errors = await res.json();
 | 
			
		||||
  throw new Error(`Something went wrong: ${errors.errors}`);
 | 
			
		||||
	const res = await fetch(`${API_ENDPOINT}/inpaint`, {
 | 
			
		||||
		method: "POST",
 | 
			
		||||
		headers: {
 | 
			
		||||
			"Content-Type": "application/json",
 | 
			
		||||
		},
 | 
			
		||||
		body: JSON.stringify({
 | 
			
		||||
			image: imageBase64,
 | 
			
		||||
			mask: maskBase64,
 | 
			
		||||
			ldm_steps: settings.ldmSteps,
 | 
			
		||||
			ldm_sampler: settings.ldmSampler,
 | 
			
		||||
			zits_wireframe: settings.zitsWireframe,
 | 
			
		||||
			cv2_flag: settings.cv2Flag,
 | 
			
		||||
			cv2_radius: settings.cv2Radius,
 | 
			
		||||
			hd_strategy: "Crop",
 | 
			
		||||
			hd_strategy_crop_triger_size: 640,
 | 
			
		||||
			hd_strategy_crop_margin: 128,
 | 
			
		||||
			hd_trategy_resize_imit: 2048,
 | 
			
		||||
			prompt: settings.prompt,
 | 
			
		||||
			negative_prompt: settings.negativePrompt,
 | 
			
		||||
			use_croper: settings.showCropper,
 | 
			
		||||
			croper_x: croperRect.x,
 | 
			
		||||
			croper_y: croperRect.y,
 | 
			
		||||
			croper_height: croperRect.height,
 | 
			
		||||
			croper_width: croperRect.width,
 | 
			
		||||
			use_extender: settings.showExtender,
 | 
			
		||||
			extender_x: extenderState.x,
 | 
			
		||||
			extender_y: extenderState.y,
 | 
			
		||||
			extender_height: extenderState.height,
 | 
			
		||||
			extender_width: extenderState.width,
 | 
			
		||||
			sd_mask_blur: settings.sdMaskBlur,
 | 
			
		||||
			sd_strength: settings.sdStrength,
 | 
			
		||||
			sd_steps: settings.sdSteps,
 | 
			
		||||
			sd_guidance_scale: settings.sdGuidanceScale,
 | 
			
		||||
			sd_sampler: settings.sdSampler,
 | 
			
		||||
			sd_seed: settings.seedFixed ? settings.seed : -1,
 | 
			
		||||
			sd_match_histograms: settings.sdMatchHistograms,
 | 
			
		||||
			sd_freeu: settings.enableFreeu,
 | 
			
		||||
			sd_freeu_config: settings.freeuConfig,
 | 
			
		||||
			sd_lcm_lora: settings.enableLCMLora,
 | 
			
		||||
			paint_by_example_example_image: exampleImageBase64,
 | 
			
		||||
			p2p_image_guidance_scale: settings.p2pImageGuidanceScale,
 | 
			
		||||
			enable_controlnet: settings.enableControlnet,
 | 
			
		||||
			controlnet_conditioning_scale: settings.controlnetConditioningScale,
 | 
			
		||||
			controlnet_method: settings.controlnetMethod
 | 
			
		||||
				? settings.controlnetMethod
 | 
			
		||||
				: "",
 | 
			
		||||
			powerpaint_task: settings.showExtender
 | 
			
		||||
				? PowerPaintTask.outpainting
 | 
			
		||||
				: settings.powerpaintTask,
 | 
			
		||||
		}),
 | 
			
		||||
	});
 | 
			
		||||
	if (res.ok) {
 | 
			
		||||
		const blob = await res.blob();
 | 
			
		||||
		return {
 | 
			
		||||
			blob: URL.createObjectURL(blob),
 | 
			
		||||
			seed: res.headers.get("X-Seed"),
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
	const errors = await res.json();
 | 
			
		||||
	throw new Error(`Something went wrong: ${errors.errors}`);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getServerConfig(): Promise<ServerConfig> {
 | 
			
		||||
  const res = await api.get(`/server-config`);
 | 
			
		||||
  return res.data;
 | 
			
		||||
	const res = await api.get(`/server-config`);
 | 
			
		||||
	return res.data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function switchModel(name: string): Promise<ModelInfo> {
 | 
			
		||||
  const res = await api.post(`/model`, { name });
 | 
			
		||||
  return res.data;
 | 
			
		||||
	const res = await api.post(`/model`, { name });
 | 
			
		||||
	return res.data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function switchPluginModel(
 | 
			
		||||
  plugin_name: string,
 | 
			
		||||
  model_name: string
 | 
			
		||||
	plugin_name: string,
 | 
			
		||||
	model_name: string,
 | 
			
		||||
) {
 | 
			
		||||
  return api.post(`/switch_plugin_model`, { plugin_name, model_name });
 | 
			
		||||
	return api.post(`/switch_plugin_model`, { plugin_name, model_name });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function currentModel(): Promise<ModelInfo> {
 | 
			
		||||
  const res = await api.get("/model");
 | 
			
		||||
  return res.data;
 | 
			
		||||
	const res = await api.get("/model");
 | 
			
		||||
	return res.data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function runPlugin(
 | 
			
		||||
  genMask: boolean,
 | 
			
		||||
  name: string,
 | 
			
		||||
  imageFile: File,
 | 
			
		||||
  upscale?: number,
 | 
			
		||||
  clicks?: number[][]
 | 
			
		||||
	genMask: boolean,
 | 
			
		||||
	name: string,
 | 
			
		||||
	imageFile: File,
 | 
			
		||||
	upscale?: number,
 | 
			
		||||
	clicks?: number[][],
 | 
			
		||||
) {
 | 
			
		||||
  const imageBase64 = await convertToBase64(imageFile);
 | 
			
		||||
  const p = genMask ? "run_plugin_gen_mask" : "run_plugin_gen_image";
 | 
			
		||||
  const res = await fetch(`${API_ENDPOINT}/${p}`, {
 | 
			
		||||
    method: "POST",
 | 
			
		||||
    headers: {
 | 
			
		||||
      "Content-Type": "application/json",
 | 
			
		||||
    },
 | 
			
		||||
    body: JSON.stringify({
 | 
			
		||||
      name,
 | 
			
		||||
      image: imageBase64,
 | 
			
		||||
      upscale,
 | 
			
		||||
      clicks,
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
  if (res.ok) {
 | 
			
		||||
    const blob = await res.blob();
 | 
			
		||||
    return { blob: URL.createObjectURL(blob) };
 | 
			
		||||
  }
 | 
			
		||||
  const errMsg = await res.json();
 | 
			
		||||
  throw new Error(errMsg);
 | 
			
		||||
	const imageBase64 = await convertToBase64(imageFile);
 | 
			
		||||
	const p = genMask ? "run_plugin_gen_mask" : "run_plugin_gen_image";
 | 
			
		||||
	const res = await fetch(`${API_ENDPOINT}/${p}`, {
 | 
			
		||||
		method: "POST",
 | 
			
		||||
		headers: {
 | 
			
		||||
			"Content-Type": "application/json",
 | 
			
		||||
		},
 | 
			
		||||
		body: JSON.stringify({
 | 
			
		||||
			name,
 | 
			
		||||
			image: imageBase64,
 | 
			
		||||
			upscale,
 | 
			
		||||
			clicks,
 | 
			
		||||
		}),
 | 
			
		||||
	});
 | 
			
		||||
	if (res.ok) {
 | 
			
		||||
		const blob = await res.blob();
 | 
			
		||||
		return { blob: URL.createObjectURL(blob) };
 | 
			
		||||
	}
 | 
			
		||||
	const errMsg = await res.json();
 | 
			
		||||
	throw new Error(errMsg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getMediaFile(tab: string, filename: string) {
 | 
			
		||||
  const res = await fetch(
 | 
			
		||||
    `${API_ENDPOINT}/media_file?tab=${tab}&filename=${encodeURIComponent(
 | 
			
		||||
      filename
 | 
			
		||||
    )}`,
 | 
			
		||||
    {
 | 
			
		||||
      method: "GET",
 | 
			
		||||
    }
 | 
			
		||||
  );
 | 
			
		||||
  if (res.ok) {
 | 
			
		||||
    const blob = await res.blob();
 | 
			
		||||
    const file = new File([blob], filename, {
 | 
			
		||||
      type: res.headers.get("Content-Type") ?? "image/png",
 | 
			
		||||
    });
 | 
			
		||||
    return file;
 | 
			
		||||
  }
 | 
			
		||||
  const errMsg = await res.json();
 | 
			
		||||
  throw new Error(errMsg.errors);
 | 
			
		||||
	const res = await fetch(
 | 
			
		||||
		`${API_ENDPOINT}/media_file?tab=${tab}&filename=${encodeURIComponent(
 | 
			
		||||
			filename,
 | 
			
		||||
		)}`,
 | 
			
		||||
		{
 | 
			
		||||
			method: "GET",
 | 
			
		||||
		},
 | 
			
		||||
	);
 | 
			
		||||
	if (res.ok) {
 | 
			
		||||
		const blob = await res.blob();
 | 
			
		||||
		const file = new File([blob], filename, {
 | 
			
		||||
			type: res.headers.get("Content-Type") ?? "image/png",
 | 
			
		||||
		});
 | 
			
		||||
		return file;
 | 
			
		||||
	}
 | 
			
		||||
	const errMsg = await res.json();
 | 
			
		||||
	throw new Error(errMsg.errors);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getMedias(tab: string): Promise<Filename[]> {
 | 
			
		||||
  const res = await api.get(`medias`, { params: { tab } });
 | 
			
		||||
  return res.data;
 | 
			
		||||
	const res = await api.get(`medias`, { params: { tab } });
 | 
			
		||||
	return res.data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function downloadToOutput(
 | 
			
		||||
  image: HTMLImageElement,
 | 
			
		||||
  filename: string,
 | 
			
		||||
  mimeType: string
 | 
			
		||||
	image: HTMLImageElement,
 | 
			
		||||
	filename: string,
 | 
			
		||||
	mimeType: string,
 | 
			
		||||
) {
 | 
			
		||||
  const file = await srcToFile(image.src, filename, mimeType);
 | 
			
		||||
  const fd = new FormData();
 | 
			
		||||
  fd.append("file", file);
 | 
			
		||||
	const file = await srcToFile(image.src, filename, mimeType);
 | 
			
		||||
	const fd = new FormData();
 | 
			
		||||
	fd.append("file", file);
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    const res = await fetch(`${API_ENDPOINT}/save_image`, {
 | 
			
		||||
      method: "POST",
 | 
			
		||||
      body: fd,
 | 
			
		||||
    });
 | 
			
		||||
    if (!res.ok) {
 | 
			
		||||
      const errMsg = await res.text();
 | 
			
		||||
      throw new Error(errMsg);
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    throw new Error(`Something went wrong: ${error}`);
 | 
			
		||||
  }
 | 
			
		||||
	try {
 | 
			
		||||
		const res = await fetch(`${API_ENDPOINT}/save_image`, {
 | 
			
		||||
			method: "POST",
 | 
			
		||||
			body: fd,
 | 
			
		||||
		});
 | 
			
		||||
		if (!res.ok) {
 | 
			
		||||
			const errMsg = await res.text();
 | 
			
		||||
			throw new Error(errMsg);
 | 
			
		||||
		}
 | 
			
		||||
	} catch (error) {
 | 
			
		||||
		throw new Error(`Something went wrong: ${error}`);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getGenInfo(file: File): Promise<GenInfo> {
 | 
			
		||||
  const fd = new FormData();
 | 
			
		||||
  fd.append("file", file);
 | 
			
		||||
  const res = await api.post(`/gen-info`, fd);
 | 
			
		||||
  return res.data;
 | 
			
		||||
	const fd = new FormData();
 | 
			
		||||
	fd.append("file", file);
 | 
			
		||||
	const res = await api.post(`/gen-info`, fd);
 | 
			
		||||
	return res.data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getSamplers(): Promise<string[]> {
 | 
			
		||||
  const res = await api.post("/samplers");
 | 
			
		||||
  return res.data;
 | 
			
		||||
	const res = await api.post("/samplers");
 | 
			
		||||
	return res.data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function postAdjustMask(
 | 
			
		||||
  mask: File | Blob,
 | 
			
		||||
  operate: "expand" | "shrink" | "reverse",
 | 
			
		||||
  kernel_size: number
 | 
			
		||||
	mask: File | Blob,
 | 
			
		||||
	operate: "expand" | "shrink" | "reverse",
 | 
			
		||||
	kernel_size: number,
 | 
			
		||||
) {
 | 
			
		||||
  const maskBase64 = await convertToBase64(mask);
 | 
			
		||||
  const res = await fetch(`${API_ENDPOINT}/adjust_mask`, {
 | 
			
		||||
    method: "POST",
 | 
			
		||||
    headers: {
 | 
			
		||||
      "Content-Type": "application/json",
 | 
			
		||||
    },
 | 
			
		||||
    body: JSON.stringify({
 | 
			
		||||
      mask: maskBase64,
 | 
			
		||||
      operate: operate,
 | 
			
		||||
      kernel_size: kernel_size,
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
  if (res.ok) {
 | 
			
		||||
    const blob = await res.blob();
 | 
			
		||||
    return blob;
 | 
			
		||||
  }
 | 
			
		||||
  const errMsg = await res.json();
 | 
			
		||||
  throw new Error(errMsg);
 | 
			
		||||
	const maskBase64 = await convertToBase64(mask);
 | 
			
		||||
	const res = await fetch(`${API_ENDPOINT}/adjust_mask`, {
 | 
			
		||||
		method: "POST",
 | 
			
		||||
		headers: {
 | 
			
		||||
			"Content-Type": "application/json",
 | 
			
		||||
		},
 | 
			
		||||
		body: JSON.stringify({
 | 
			
		||||
			mask: maskBase64,
 | 
			
		||||
			operate: operate,
 | 
			
		||||
			kernel_size: kernel_size,
 | 
			
		||||
		}),
 | 
			
		||||
	});
 | 
			
		||||
	if (res.ok) {
 | 
			
		||||
		const blob = await res.blob();
 | 
			
		||||
		return blob;
 | 
			
		||||
	}
 | 
			
		||||
	const errMsg = await res.json();
 | 
			
		||||
	throw new Error(errMsg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function submitMask(imageFile: File, mask: File | Blob) {
 | 
			
		||||
	const imageBase64 = await convertToBase64(imageFile);
 | 
			
		||||
	const maskBase64 = await convertToBase64(mask);
 | 
			
		||||
 | 
			
		||||
	const res = await fetch(`${API_ENDPOINT}/submit-mask`, {
 | 
			
		||||
		method: "POST",
 | 
			
		||||
		headers: {
 | 
			
		||||
			"Content-Type": "application/json",
 | 
			
		||||
		},
 | 
			
		||||
		body: JSON.stringify({
 | 
			
		||||
			image: imageBase64,
 | 
			
		||||
			mask: maskBase64,
 | 
			
		||||
		}),
 | 
			
		||||
	});
 | 
			
		||||
	if (res.ok) {
 | 
			
		||||
		const blob = await res.blob();
 | 
			
		||||
		return {
 | 
			
		||||
			blob: URL.createObjectURL(blob),
 | 
			
		||||
			seed: res.headers.get("X-Seed"),
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
	// const errors = await res.json();
 | 
			
		||||
	throw new Error(`Submit successfull.`);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
		Reference in New Issue