From 9ceaeb2134e36828d23a9e8ff6640b4b32470bdf Mon Sep 17 00:00:00 2001 From: ss707494 Date: Fri, 8 Mar 2024 23:43:59 +0800 Subject: [PATCH 1/6] feat: meta all prompt expand --- .../AllPromptForm/AllPromptForm.tsx | 14 +++- .../components/FormItem/FormItemComponent.tsx | 1 + ui/src/gallery/components/FormItem/types.ts | 2 +- ui/src/gallery/components/MetaBox/MetaBox.tsx | 80 ++++++++++--------- ui/src/gallery/components/TopForm/TopForm.tsx | 4 +- 5 files changed, 58 insertions(+), 43 deletions(-) diff --git a/ui/src/gallery/components/AllPromptForm/AllPromptForm.tsx b/ui/src/gallery/components/AllPromptForm/AllPromptForm.tsx index 8ecbb8f..2c228c0 100644 --- a/ui/src/gallery/components/AllPromptForm/AllPromptForm.tsx +++ b/ui/src/gallery/components/AllPromptForm/AllPromptForm.tsx @@ -11,6 +11,7 @@ import { FormItemComponent } from "../FormItem/FormItemComponent.tsx"; import { MetaData } from "../../utils.ts"; import { FormItem } from "../FormItem/types.ts"; import { isInTopField, TopFieldType } from "../MetaBox/MetaBox.tsx"; +import { useEffect, useState } from "react"; export default function AllPromptForm({ metaData, @@ -24,17 +25,25 @@ export default function AllPromptForm({ updateTopField?: (field: TopFieldType) => void; }) { const prompt = metaData.prompt; + const [defaultIndex, setDefaultIndex] = useState([]); + useEffect(() => { + setDefaultIndex(Object.keys(prompt).map((_, i) => i)); + }, [prompt]); return ( - + setDefaultIndex(val as number[])} + allowMultiple + > {Object.keys(prompt).map((promptKey) => { const promptElement = prompt[promptKey]; const promptInputs = promptElement.inputs; const inputsKeyList = Object.keys(promptInputs).filter( (v) => - !Array.isArray(promptInputs[v]) && !isInTopField(topFields, { name: v, promptKey: promptKey, + classType: promptElement.class_type, }), ); if (inputsKeyList.length === 0) return null; @@ -59,6 +68,7 @@ export default function AllPromptForm({ isInTopField(topFields, { name: inputsKey, promptKey, + classType: promptElement.class_type, }) ) { return null; diff --git a/ui/src/gallery/components/FormItem/FormItemComponent.tsx b/ui/src/gallery/components/FormItem/FormItemComponent.tsx index 66c9e7c..61ef3db 100644 --- a/ui/src/gallery/components/FormItem/FormItemComponent.tsx +++ b/ui/src/gallery/components/FormItem/FormItemComponent.tsx @@ -88,6 +88,7 @@ export const FormItemComponent: FC = (props) => { isInTopField(props.topFields, { name: props.name, promptKey: props.promptKey, + classType: props.classType, }) ? ( ) : ( diff --git a/ui/src/gallery/components/FormItem/types.ts b/ui/src/gallery/components/FormItem/types.ts index c35bbbe..c55f1a1 100644 --- a/ui/src/gallery/components/FormItem/types.ts +++ b/ui/src/gallery/components/FormItem/types.ts @@ -20,7 +20,7 @@ export type FormItem = { topFields: TopFieldType[]; updateTopField?: (field: TopFieldType) => void; updateMetaData?: (data: { - promptKey: string; + promptKey: string | number; name: string; value: any; }) => void; diff --git a/ui/src/gallery/components/MetaBox/MetaBox.tsx b/ui/src/gallery/components/MetaBox/MetaBox.tsx index 766acda..66896a8 100644 --- a/ui/src/gallery/components/MetaBox/MetaBox.tsx +++ b/ui/src/gallery/components/MetaBox/MetaBox.tsx @@ -11,46 +11,50 @@ export type TopFieldType = { class_type?: string; name: string; }; + export const DEFAULT_TOP_FIELDS: TopFieldType[] = [ - { - promptKey: "4", - name: "ckpt_name", - }, - { - promptKey: "6", - name: "text", - }, - { - promptKey: "7", - name: "text", - }, - { - promptKey: "5", - name: "width", - }, - { - promptKey: "5", - name: "height", - }, - { - promptKey: "3", - name: "steps", - }, - { - promptKey: "3", - name: "sampler_name", - }, - { - promptKey: "3", - name: "cfg", - }, + // { + // promptKey: "4", + // name: "ckpt_name", + // }, + // { + // promptKey: "6", + // name: "text", + // }, + // { + // promptKey: "7", + // name: "text", + // }, + // { + // promptKey: "5", + // name: "width", + // }, + // { + // promptKey: "5", + // name: "height", + // }, + // { + // promptKey: "3", + // name: "steps", + // }, + // { + // promptKey: "3", + // name: "sampler_name", + // }, + // { + // promptKey: "3", + // name: "cfg", + // }, ]; export const isInTopField = ( topFields: TopFieldType[], - item: Pick, + item: Pick, ) => { return topFields?.some( - (top) => top.promptKey === item?.promptKey && top.name === item?.name, + (top) => + top.promptKey === item?.promptKey && + top.name === item?.name && + top.class_type === item.classType, ); }; @@ -60,16 +64,15 @@ export default function MetaBox({ metaData: MetaData; media: Media; }) { + const _metaData = JSON.parse(JSON.stringify(oriMetaData)); const [topFields, setTopFields] = useState(DEFAULT_TOP_FIELDS); - const [metaData, setMetaData] = useState( - JSON.parse(JSON.stringify(oriMetaData)), - ); + const [metaData, setMetaData] = useState(_metaData); const updateMetaData = ({ promptKey, name, value, }: { - promptKey: string; + promptKey: string | number; name: string; value: any; }) => { @@ -93,6 +96,7 @@ export default function MetaBox({ isInTopField(topFields, { name: field.name, promptKey: field?.promptKey, + classType: field?.class_type ?? "", }) ) { setTopFields((pre) => diff --git a/ui/src/gallery/components/TopForm/TopForm.tsx b/ui/src/gallery/components/TopForm/TopForm.tsx index 0d20eb1..18186d0 100644 --- a/ui/src/gallery/components/TopForm/TopForm.tsx +++ b/ui/src/gallery/components/TopForm/TopForm.tsx @@ -20,12 +20,12 @@ export default function TopForm({ <> {topFields?.length > 0 && ( - {topFields?.map((field) => { + {topFields?.map((field, i) => { if (!prompt?.[field.promptKey]?.inputs) return null; const promptValue = prompt?.[field.promptKey]?.inputs?.[field.name]; return ( Date: Fri, 8 Mar 2024 23:46:35 +0800 Subject: [PATCH 2/6] feat: meta all prompt expand --- ui/src/gallery/components/AllPromptForm/AllPromptForm.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/src/gallery/components/AllPromptForm/AllPromptForm.tsx b/ui/src/gallery/components/AllPromptForm/AllPromptForm.tsx index 2c228c0..fc9255f 100644 --- a/ui/src/gallery/components/AllPromptForm/AllPromptForm.tsx +++ b/ui/src/gallery/components/AllPromptForm/AllPromptForm.tsx @@ -40,6 +40,7 @@ export default function AllPromptForm({ const promptInputs = promptElement.inputs; const inputsKeyList = Object.keys(promptInputs).filter( (v) => + !Array.isArray(promptInputs[v]) && !isInTopField(topFields, { name: v, promptKey: promptKey, From e0c5da3911c3a5019a7b3211b12c61842f4ad9db Mon Sep 17 00:00:00 2001 From: ss707494 Date: Sat, 9 Mar 2024 09:32:40 +0800 Subject: [PATCH 3/6] feat: meta all prompt expand --- ui/src/gallery/components/MetaBox/MetaBox.tsx | 73 +++++++++++-------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/ui/src/gallery/components/MetaBox/MetaBox.tsx b/ui/src/gallery/components/MetaBox/MetaBox.tsx index 66896a8..74024b8 100644 --- a/ui/src/gallery/components/MetaBox/MetaBox.tsx +++ b/ui/src/gallery/components/MetaBox/MetaBox.tsx @@ -12,39 +12,48 @@ export type TopFieldType = { name: string; }; +// comfyui default workflow export const DEFAULT_TOP_FIELDS: TopFieldType[] = [ - // { - // promptKey: "4", - // name: "ckpt_name", - // }, - // { - // promptKey: "6", - // name: "text", - // }, - // { - // promptKey: "7", - // name: "text", - // }, - // { - // promptKey: "5", - // name: "width", - // }, - // { - // promptKey: "5", - // name: "height", - // }, - // { - // promptKey: "3", - // name: "steps", - // }, - // { - // promptKey: "3", - // name: "sampler_name", - // }, - // { - // promptKey: "3", - // name: "cfg", - // }, + { + promptKey: "4", + name: "ckpt_name", + class_type: "CheckpointLoaderSimple", + }, + { + promptKey: "6", + name: "text", + class_type: "CLIPTextEncode", + }, + { + promptKey: "7", + name: "text", + class_type: "CLIPTextEncode", + }, + { + promptKey: "5", + name: "width", + class_type: "EmptyLatentImage", + }, + { + promptKey: "5", + name: "height", + class_type: "EmptyLatentImage", + }, + { + promptKey: "3", + name: "steps", + class_type: "KSampler", + }, + { + promptKey: "3", + name: "sampler_name", + class_type: "KSampler", + }, + { + promptKey: "3", + name: "cfg", + class_type: "KSampler", + }, ]; export const isInTopField = ( topFields: TopFieldType[], From 46f007812ab44f821ba86ddb23d1ab51a89561ca Mon Sep 17 00:00:00 2001 From: yanle <421361608@qq.com> Date: Sun, 10 Mar 2024 12:49:09 +0800 Subject: [PATCH 4/6] feat: copy all files to the new directory when switching the save directory --- service/file_sync_service.py | 40 +++++++++++++++ ui/src/Api.ts | 22 +++++++++ ui/src/settings/SelectMyWorkflowsDir.tsx | 63 +++++++++++++++++------- 3 files changed, 108 insertions(+), 17 deletions(-) diff --git a/service/file_sync_service.py b/service/file_sync_service.py index c9a27e7..d999cde 100644 --- a/service/file_sync_service.py +++ b/service/file_sync_service.py @@ -242,3 +242,43 @@ def count_files_sync(reqJson): file_count += 1 return {"success": True, "count": file_count} + + +# Begin: Copy all files to the new directory when switching the save directory +@server.PromptServer.instance.routes.post('/workspace/copy_json_files') +async def copy_json_files(request): + reqJson = await request.json() + src_dir = reqJson['src'] + dest_dir = reqJson['dst'] + try: + for item in os.listdir(src_dir): + src_item_path = os.path.join(src_dir, item) + dest_item_path = os.path.join(dest_dir, item) + + if os.path.isdir(src_item_path): + dest_item_path = await get_unique_dir_name(dest_item_path) + await asyncio.to_thread(shutil.copytree, src_item_path, dest_item_path) + elif item.endswith('.json'): + dest_item_path = await get_unique_file_name(dest_item_path) + await asyncio.to_thread(shutil.copy2, src_item_path, dest_item_path) + + return web.json_response({"success": True}, content_type='application/json') + except Exception as e: + return web.json_response({"success": False, "error": str(e)}, content_type='application/json') + +async def get_unique_file_name(path): + base, extension = os.path.splitext(path) + counter = 1 + while os.path.exists(path): + path = f"{base}_{counter}{extension}" + counter += 1 + return path + +async def get_unique_dir_name(path): + original_path = path + counter = 1 + while os.path.exists(path): + path = f"{original_path}_{counter}" + counter += 1 + return path +# End: Copy all files to the new directory when switching the save directory \ No newline at end of file diff --git a/ui/src/Api.ts b/ui/src/Api.ts index 09edb5c..c803fbb 100644 --- a/ui/src/Api.ts +++ b/ui/src/Api.ts @@ -200,3 +200,25 @@ export async function cancelDownload(savePath: string) { console.error("Error move file:", error); } } + +export async function copyFlowsToNewDirectory( + sourceDir: string, + dstDir: string, +) { + try { + const response = await fetch("/workspace/copy_json_files", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + src: sourceDir, + dst: dstDir, + }), + }); + const result = await response.json(); + return result; + } catch (error) { + console.error("Error copy flows to new directory:", error); + } +} diff --git a/ui/src/settings/SelectMyWorkflowsDir.tsx b/ui/src/settings/SelectMyWorkflowsDir.tsx index 29716a9..3216d56 100644 --- a/ui/src/settings/SelectMyWorkflowsDir.tsx +++ b/ui/src/settings/SelectMyWorkflowsDir.tsx @@ -20,9 +20,13 @@ import { } from "@chakra-ui/react"; import { IconEdit } from "@tabler/icons-react"; import { useRef, useState, useEffect } from "react"; -import { fetchMyWorkflowsDir, getSystemDir } from "../Api"; +import { + copyFlowsToNewDirectory, + fetchMyWorkflowsDir, + getSystemDir, +} from "../Api"; import { userSettingsTable } from "../db-tables/WorkspaceDB"; -import { validateOrSaveAllJsonFileMyWorkflows } from "../utils"; +import { useDialog } from "../components/AlertDialogProvider"; export default function SelectMyWorkflowsDir() { const isWindows = navigator.userAgent.indexOf("Window") > 0; @@ -36,6 +40,7 @@ export default function SelectMyWorkflowsDir() { const [noPermission, setNoPermission] = useState(false); const manualEntryRef = useRef(null); const toast = useToast(); + const { showDialog } = useDialog(); useEffect(() => { fetchMyWorkflowsDir().then((dir) => setCurrentDirectory(dir ?? "")); @@ -83,7 +88,26 @@ export default function SelectMyWorkflowsDir() { setDirPathList([]); }; - const onSaveDirectory = async (manualEntry?: string) => { + const saveDirectory = async ( + newPath: string, + needCopy: boolean = false, + isManualEntry: boolean = false, + ) => { + if (needCopy) { + const sourcePath = await fetchMyWorkflowsDir(); + sourcePath && (await copyFlowsToNewDirectory(sourcePath, newPath)); + } + await userSettingsTable?.upsert({ + myWorkflowsDir: newPath, + }); + const getNewPath = + (await userSettingsTable?.getSetting("myWorkflowsDir")) ?? ""; + setCurrentDirectory(getNewPath); + isManualEntry && setNoPermission(false); + onCloseEditDirectory(); + }; + + const onSubmit = async (manualEntry?: string) => { if (manualEntry) { const { error = "" } = await getSystemDir(manualEntry); if (error.includes("Not a directory")) { @@ -100,16 +124,21 @@ export default function SelectMyWorkflowsDir() { } const newPath = manualEntry ?? `${isWindows ? "" : slash}${dirPathList.join(slash)}`; - await userSettingsTable?.upsert({ - myWorkflowsDir: newPath, - }); - const getNewPath = - (await userSettingsTable?.getSetting("myWorkflowsDir")) ?? ""; - setCurrentDirectory(getNewPath); - manualEntry && setNoPermission(false); - onCloseEditDirectory(); - // to update /my_workflows files in disk to new location - validateOrSaveAllJsonFileMyWorkflows(); + + showDialog("Do you want to copy all workflows to new directory?", [ + { + label: "Yes", + onClick: () => { + saveDirectory(newPath, true, !!manualEntry); + }, + }, + { + label: "No", + onClick: () => { + saveDirectory(newPath, false, !!manualEntry); + }, + }, + ]); }; const onReset = async () => { @@ -150,7 +179,7 @@ export default function SelectMyWorkflowsDir() { size="sm" onClick={() => { manualEntryRef?.current?.value && - onSaveDirectory(manualEntryRef?.current?.value); + onSubmit(manualEntryRef?.current?.value); }} > Save @@ -220,7 +249,7 @@ export default function SelectMyWorkflowsDir() { {dirPathList.map((dir, index) => ( - <> + {index > 0 && slash} - + ))} { - onSaveDirectory(); + onSubmit(); }} > Save From 5d675e1c89bb0448c7a4e00ba0be5536fbb37f13 Mon Sep 17 00:00:00 2001 From: Weixuan Fu Date: Sun, 10 Mar 2024 23:06:39 +0800 Subject: [PATCH 5/6] fix bulk add throw error --- ui/src/db-tables/WorkspaceDB.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/src/db-tables/WorkspaceDB.ts b/ui/src/db-tables/WorkspaceDB.ts index 8e30ddd..2dc0575 100644 --- a/ui/src/db-tables/WorkspaceDB.ts +++ b/ui/src/db-tables/WorkspaceDB.ts @@ -71,7 +71,7 @@ export async function backfillIndexdb() { const backfillWorkflows = async () => { try { const all = await workflowsTable?.getRecords(); - all && (await indexdb.workflows.bulkAdd(Object.values(all))); + all && (await indexdb.workflows.bulkPut(Object.values(all))); } catch (error) { console.error(error); } @@ -79,7 +79,7 @@ export async function backfillIndexdb() { const backfillFolders = async () => { try { const all = await foldersTable?.getRecords(); - all && (await indexdb.folders.bulkAdd(Object.values(all))); + all && (await indexdb.folders.bulkPut(Object.values(all))); } catch (error) { console.error(error); } @@ -87,7 +87,7 @@ export async function backfillIndexdb() { const backfillMedia = async () => { try { const all = await mediaTable?.getRecords(); - all && (await indexdb.media.bulkAdd(Object.values(all))); + all && (await indexdb.media.bulkPut(Object.values(all))); } catch (error) { console.error(error); } @@ -95,7 +95,7 @@ export async function backfillIndexdb() { const backfillChangelogs = async () => { try { const all = await changelogsTable?.getRecords(); - all && (await indexdb.changelogs.bulkAdd(Object.values(all))); + all && (await indexdb.changelogs.bulkPut(Object.values(all))); } catch (error) { console.error(error); } @@ -103,7 +103,7 @@ export async function backfillIndexdb() { const backfillTags = async () => { try { const all = await tagsTable?.getRecords(); - all && (await indexdb.tags.bulkAdd(Object.values(all))); + all && (await indexdb.tags.bulkPut(Object.values(all))); } catch (error) { console.error(error); } @@ -129,7 +129,7 @@ export async function backfillIndexdb() { const backfillWorkflowVersions = async () => { try { const all = await workflowVersionsTable?.getRecords(); - all && (await indexdb.workflowVersions.bulkAdd(Object.values(all))); + all && (await indexdb.workflowVersions.bulkPut(Object.values(all))); } catch (error) { console.error(error); } From c1cae9a9d2ba923155dd29c820ba4b8ced537b05 Mon Sep 17 00:00:00 2001 From: Weixuan Fu Date: Mon, 11 Mar 2024 00:19:48 +0800 Subject: [PATCH 6/6] non auto dismiss twoway sync dialog --- ui/src/App.tsx | 4 +++- ui/src/components/AlertDialogProvider.tsx | 12 +++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 009f337..0342196 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -226,6 +226,9 @@ export default function App() { colorScheme: "red", }, ], + { + closeOnOverlayClick: false, + }, ); } }); @@ -482,7 +485,6 @@ export default function App() { }, }, ], - true, ); } else { // autosave workflow if enabled diff --git a/ui/src/components/AlertDialogProvider.tsx b/ui/src/components/AlertDialogProvider.tsx index c5ef867..3b0caa4 100644 --- a/ui/src/components/AlertDialogProvider.tsx +++ b/ui/src/components/AlertDialogProvider.tsx @@ -28,7 +28,10 @@ interface DialogContextType { showDialog: ( message: string | React.ReactNode, buttons?: (DialogButton | null)[], - hideCloseIcon?: boolean, + options?: { + hideCloseIcon?: boolean; + closeOnOverlayClick?: boolean; + }, ) => void; } @@ -49,14 +52,16 @@ export const AlertDialogProvider: React.FC<{ children: ReactNode }> = ({ const [message, setMessage] = useState(""); const [buttons, setButtons] = useState<(DialogButton | null)[]>([]); const [hideCloseIcon, setHideCloseIcon] = useState(false); + const [closeOnOverlayClick, setCloseOnOverlayClick] = useState(true); const cancelRef = React.useRef(null); const showDialog: DialogContextType["showDialog"] = useCallback( - (message, buttons, hideCloseIcon = false) => { + (message, buttons, options) => { setMessage(message); setButtons(buttons ?? []); setIsOpen(true); - setHideCloseIcon(hideCloseIcon); + setHideCloseIcon(options?.hideCloseIcon ?? false); + setCloseOnOverlayClick(options?.closeOnOverlayClick ?? true); }, [], ); @@ -74,6 +79,7 @@ export const AlertDialogProvider: React.FC<{ children: ReactNode }> = ({ leastDestructiveRef={cancelRef} onClose={handleClose} size={"xl"} + closeOnOverlayClick={closeOnOverlayClick} >