import { IconButton } from "@/components/ui/button" import { useToggle } from "@uidotdev/usehooks" import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "./ui/dialog" import { Settings } from "lucide-react" import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import * as z from "zod" import { Button } from "@/components/ui/button" import { Separator } from "@/components/ui/separator" import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, } from "@/components/ui/form" import { Switch } from "./ui/switch" import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs" import { useEffect, useState } from "react" import { cn } from "@/lib/utils" import { useQuery } from "@tanstack/react-query" import { getServerConfig, switchModel, switchPluginModel } from "@/lib/api" import { ModelInfo, PluginName } from "@/lib/types" import { useStore } from "@/lib/states" import { ScrollArea } from "./ui/scroll-area" import { useToast } from "./ui/use-toast" import { AlertDialog, AlertDialogContent, AlertDialogHeader, } from "./ui/alert-dialog" import { MODEL_TYPE_DIFFUSERS_SD, MODEL_TYPE_DIFFUSERS_SDXL, MODEL_TYPE_DIFFUSERS_SDXL_INPAINT, MODEL_TYPE_DIFFUSERS_SD_INPAINT, MODEL_TYPE_INPAINT, MODEL_TYPE_OTHER, } from "@/lib/const" import useHotKey from "@/hooks/useHotkey" import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, } from "./ui/select" const formSchema = z.object({ enableFileManager: z.boolean(), inputDirectory: z.string(), outputDirectory: z.string(), enableDownloadMask: z.boolean(), enableManualInpainting: z.boolean(), enableUploadMask: z.boolean(), enableAutoExtractPrompt: z.boolean(), removeBGModel: z.string(), realesrganModel: z.string(), interactiveSegModel: z.string(), }) const TAB_GENERAL = "General" const TAB_MODEL = "Model" const TAB_PLUGINS = "Plugins" // const TAB_FILE_MANAGER = "File Manager" const TAB_NAMES = [TAB_MODEL, TAB_GENERAL, TAB_PLUGINS] export function SettingsDialog() { const [open, toggleOpen] = useToggle(false) const [tab, setTab] = useState(TAB_MODEL) const [ updateAppState, settings, updateSettings, fileManagerState, setAppModel, setServerConfig, ] = useStore((state) => [ state.updateAppState, state.settings, state.updateSettings, state.fileManagerState, state.setModel, state.setServerConfig, ]) const { toast } = useToast() const [model, setModel] = useState(settings.model) const [modelSwitchingTexts, setModelSwitchingTexts] = useState([]) const openModelSwitching = modelSwitchingTexts.length > 0 useEffect(() => { setModel(settings.model) }, [settings.model]) const { data: serverConfig, status, refetch, } = useQuery({ queryKey: ["serverConfig"], queryFn: getServerConfig, }) // 1. Define your form. const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { enableDownloadMask: settings.enableDownloadMask, enableManualInpainting: settings.enableManualInpainting, enableUploadMask: settings.enableUploadMask, enableAutoExtractPrompt: settings.enableAutoExtractPrompt, inputDirectory: fileManagerState.inputDirectory, outputDirectory: fileManagerState.outputDirectory, removeBGModel: serverConfig?.removeBGModel, realesrganModel: serverConfig?.realesrganModel, interactiveSegModel: serverConfig?.interactiveSegModel, }, }) useEffect(() => { if (serverConfig) { setServerConfig(serverConfig) form.setValue("removeBGModel", serverConfig.removeBGModel) form.setValue("realesrganModel", serverConfig.realesrganModel) form.setValue("interactiveSegModel", serverConfig.interactiveSegModel) } }, [form, serverConfig]) async function onSubmit(values: z.infer) { // Do something with the form values. ✅ This will be type-safe and validated. updateSettings({ enableDownloadMask: values.enableDownloadMask, enableManualInpainting: values.enableManualInpainting, enableUploadMask: values.enableUploadMask, enableAutoExtractPrompt: values.enableAutoExtractPrompt, }) // TODO: validate input/output Directory // updateFileManagerState({ // inputDirectory: values.inputDirectory, // outputDirectory: values.outputDirectory, // }) const shouldSwitchModel = model.name !== settings.model.name const shouldSwitchRemoveBGModel = serverConfig?.removeBGModel !== values.removeBGModel && removeBGEnabled const shouldSwitchRealesrganModel = serverConfig?.realesrganModel !== values.realesrganModel && realesrganEnabled const shouldSwitchInteractiveModel = serverConfig?.interactiveSegModel !== values.interactiveSegModel && interactiveSegEnabled const showModelSwitching = shouldSwitchModel || shouldSwitchRemoveBGModel || shouldSwitchRealesrganModel || shouldSwitchInteractiveModel if (showModelSwitching) { const newModelSwitchingTexts: string[] = [] if (shouldSwitchModel) { newModelSwitchingTexts.push( `Switching model from ${settings.model.name} to ${model.name}` ) } if (shouldSwitchRemoveBGModel) { newModelSwitchingTexts.push( `Switching RemoveBG model from ${serverConfig?.removeBGModel} to ${values.removeBGModel}` ) } if (shouldSwitchRealesrganModel) { newModelSwitchingTexts.push( `Switching RealESRGAN model from ${serverConfig?.realesrganModel} to ${values.realesrganModel}` ) } if (shouldSwitchInteractiveModel) { newModelSwitchingTexts.push( `Switching ${PluginName.InteractiveSeg} model from ${serverConfig?.interactiveSegModel} to ${values.interactiveSegModel}` ) } setModelSwitchingTexts(newModelSwitchingTexts) updateAppState({ disableShortCuts: true }) if (shouldSwitchModel) { try { const newModel = await switchModel(model.name) toast({ title: `Switch to ${newModel.name} success`, }) setAppModel(model) } catch (error: any) { toast({ variant: "destructive", title: `Switch to ${model.name} failed: ${error}`, }) setModel(settings.model) } } if (shouldSwitchRemoveBGModel) { try { const res = await switchPluginModel( PluginName.RemoveBG, values.removeBGModel ) if (res.status !== 200) { throw new Error(res.statusText) } } catch (error: any) { toast({ variant: "destructive", title: `Switch RemoveBG model to ${values.removeBGModel} failed: ${error}`, }) } } if (shouldSwitchRealesrganModel) { try { const res = await switchPluginModel( PluginName.RealESRGAN, values.realesrganModel ) if (res.status !== 200) { throw new Error(res.statusText) } } catch (error: any) { toast({ variant: "destructive", title: `Switch RealESRGAN model to ${values.realesrganModel} failed: ${error}`, }) } } if (shouldSwitchInteractiveModel) { try { const res = await switchPluginModel( PluginName.InteractiveSeg, values.interactiveSegModel ) if (res.status !== 200) { throw new Error(res.statusText) } } catch (error: any) { toast({ variant: "destructive", title: `Switch ${PluginName.InteractiveSeg} model to ${values.interactiveSegModel} failed: ${error}`, }) } } setModelSwitchingTexts([]) updateAppState({ disableShortCuts: false }) refetch() } } useHotKey( "s", () => { toggleOpen() if (open) { onSubmit(form.getValues()) } }, [open, form, model, serverConfig] ) if (status !== "success") { return <> } const modelInfos = serverConfig.modelInfos const plugins = serverConfig.plugins const removeBGEnabled = plugins.some( (plugin) => plugin.name === PluginName.RemoveBG ) const realesrganEnabled = plugins.some( (plugin) => plugin.name === PluginName.RealESRGAN ) const interactiveSegEnabled = plugins.some( (plugin) => plugin.name === PluginName.InteractiveSeg ) function onOpenChange(value: boolean) { toggleOpen() if (!value) { onSubmit(form.getValues()) } } function onModelSelect(info: ModelInfo) { setModel(info) } function renderModelList(model_types: string[]) { if (!modelInfos) { return
Please download model first
} return modelInfos .filter((info) => model_types.includes(info.model_type)) .map((info: ModelInfo) => { return (
onModelSelect(info)} className="px-2" >
{info.name}
) }) } function renderModelSettings() { let defaultTab = MODEL_TYPE_INPAINT for (let info of modelInfos) { if (model.name === info.name) { defaultTab = info.model_type if (defaultTab === MODEL_TYPE_DIFFUSERS_SDXL) { defaultTab = MODEL_TYPE_DIFFUSERS_SD } if (defaultTab === MODEL_TYPE_DIFFUSERS_SDXL_INPAINT) { defaultTab = MODEL_TYPE_DIFFUSERS_SD_INPAINT } break } } return (
Current Model
{model.name}
Available models
{/* */}
Inpaint Stable Diffusion Stable Diffusion Inpaint Other Diffusion {renderModelList([MODEL_TYPE_INPAINT])} {renderModelList([ MODEL_TYPE_DIFFUSERS_SD, MODEL_TYPE_DIFFUSERS_SDXL, ])} {renderModelList([ MODEL_TYPE_DIFFUSERS_SD_INPAINT, MODEL_TYPE_DIFFUSERS_SDXL_INPAINT, ])} {renderModelList([MODEL_TYPE_OTHER])}
) } function renderGeneralSettings() { return (
(
Enable manual inpainting For erase model, click a button to trigger inpainting after draw mask.
)} /> (
Enable download mask Also download the mask after save the inpainting result.
)} /> (
Enable auto extract prompt Automatically extract prompt/negativate prompt from the image meta.
)} /> {/* (
Enable upload mask Enable upload custom mask to perform inpainting.
)} /> */}
) } function renderPluginsSettings() { return (
(
Remove Background Remove background model
)} /> (
RealESRGAN RealESRGAN Model
)} /> (
Interactive Segmentation Interactive Segmentation Model
)} />
) } // function renderFileManagerSettings() { // return ( //
// ( // //
// Enable file manger // // Browser images // //
// // // //
// )} // /> // // ( // // Input directory // // // // // Browser images from this directory. // // // // )} // /> // ( // // Save directory // // // // // Result images will be saved to this directory. // // // // )} // /> //
// ) // } return ( <> {/* */}
Loading...
{modelSwitchingTexts ? (
{modelSwitchingTexts.map((text, index) => (
{text}
))}
) : ( <> )}
{/*
*/}
event.preventDefault()} onOpenAutoFocus={(event) => event.preventDefault()} // onPointerDownOutside={(event) => event.preventDefault()} > Settings
{TAB_NAMES.map((item) => ( ))}
{tab === TAB_MODEL ? renderModelSettings() : <>} {tab === TAB_GENERAL ? renderGeneralSettings() : <>} {tab === TAB_PLUGINS ? renderPluginsSettings() : <>} {/* {tab === TAB_FILE_MANAGER ? ( renderFileManagerSettings() ) : ( <> )} */}
) } export default SettingsDialog