Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75669a760a |
+1
-1
@@ -11,7 +11,7 @@ pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
ui/node_modules
|
||||
# ui/src/utils/encryptUtils.ts
|
||||
ui/src/utils/encryptUtils.ts
|
||||
db
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
|
||||
A ComfyUI workflow and model manager extension to **organize and manage all your workflows, models and generated images in one place**. Seamlessly switch between workflows, track version history and image generation history, 1 click install models from Civit ai, browse/update your installed models
|
||||
|
||||
If you have questions or suggestions, please [join our Discord](https://discord.gg/HHvfEurv2Z)!
|
||||
If you have questions or suggestions, please [join our Discord](https://discord.gg/HHvfEurv2Z)! I'm building a new platform that lets you quickly make a runnable web app from your comfyui workflow. www.nodecafe.co if you are interested to make your comfyui workflow into an app or colaborate with me, please DM me!
|
||||
|
||||
✨We have a new platform to turn your comfyui workflow to runnable web app: www.nodecafe.co please check it out! 🥳
|
||||
|
||||
[6-25] ⚠️ Please update workspace manager to latest version if it's not showing up for you after updating ComfyUI
|
||||
[6-21] tags are deprecated, please use folders to organize your workflows.
|
||||
|
||||
[6-21] ‼️please upgrade to V2 and enable twoway sync if you haven't already. I will stop supporting V1 soon and will forcefully turn on twoway sync later. (twoway sync means that your workflow are twoway synced with your disk files /my_workflows directly)
|
||||
|
||||
[6-21] ‼️ Auto-save mode will be removed comopoletely from next version. Because there are reports that autosave mode sometimes overwrite workflows mistakenly. Therefore, please **manual save** your workflows always.
|
||||
[6-21] ‼️‼️Auto-save mode will be removed comopoletely from next version. Because there are reports that autosave mode sometimes overwrite workflows mistakenly. Therefore, please **manual save** your workflows always.
|
||||
|
||||
[New!] We provide 👭**team workspace** now! If you need to **share workflows, backup workflow versions for enterprise and teams**, please contact me about our team/enterprise product at weixuanfu01@gmail.com or join discord and DM me @briefpeach
|
||||
|
||||
@@ -53,7 +51,7 @@ Recommanded settings for V2 (Do not use these settings if you are on V1):
|
||||
|
||||
- 🔁Switch between different workflows easily. 1click open workflow in multiple browser tabs.
|
||||
- 🤏**Drag and drop** to insert subworkflows into current flow.
|
||||
- 🗂️Organize workflows with folders (tags are deprecated, please use folders to organize)
|
||||
- 🗂️Organize workflows with folders
|
||||
- 📂Save and sync all your workflows in a local folder (by default under `/ComfyUI/my_workflows` customize in Settings)
|
||||
- **Save workflow by pressing the 💾 button, or ⌨Shift+S shortcut** (customize in Settings).
|
||||
- Every time you save, it will create a new record in **Version History**. You can easily revert to any version.
|
||||
@@ -80,6 +78,9 @@ Recommend using Chrome browser for the best experience. Safari doesn't work well
|
||||
[中文版教程](https://www.uisdc.com/workspace-manager) [Roadmap](https://github.com/orgs/11cafe/projects/1/views/1)
|
||||
|
||||
## Updates
|
||||
**🦩v2.1 New gallery! Inspect image metadata**
|
||||
|
||||
<img width="400" alt="Screenshot 2024-03-23 at 12 58 38 AM" src="https://github.com/11cafe/comfyui-workspace-manager/assets/18367033/5bac9793-7491-4131-b850-037094d03823">
|
||||
|
||||
**🦄v2.0 Two-way sync with local folder, models manager support external models path like Automatic1111, cloud sync workflows**
|
||||
|
||||
|
||||
@@ -15,12 +15,11 @@ def get_settings_endpoint(request):
|
||||
return web.json_response(text=json.dumps(settings), content_type='application/json')
|
||||
|
||||
def get_settings():
|
||||
new_settings_path = os.path.join(db_dir_path, 'settings.json')
|
||||
if os.path.exists(new_settings_path):
|
||||
if os.path.exists(f'{db_dir_path}/settings.json'):
|
||||
# to deprecate and remove legacy settings file
|
||||
# if os.path.exists(f'{db_dir_path}/useSettings.json'):
|
||||
# os.remove(f'{db_dir_path}/useSettings.json')
|
||||
with open(new_settings_path, 'r') as file:
|
||||
with open(f'{db_dir_path}/settings.json', 'r') as file:
|
||||
return json.load(file)
|
||||
data = read_table('userSettings')
|
||||
if (data):
|
||||
|
||||
+1
-1
@@ -131,7 +131,7 @@ export default function App() {
|
||||
try {
|
||||
await loadDBs();
|
||||
} catch (error) {
|
||||
console.error("🦄Workspace Manager - Error loading db", error);
|
||||
console.error("error loading db", error);
|
||||
}
|
||||
setLoadingDB(false);
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import React from "react";
|
||||
import { Flex, Button, Text, Spinner } from "@chakra-ui/react";
|
||||
|
||||
type PaginatorProps = {
|
||||
pageNumber: number;
|
||||
loading: boolean;
|
||||
noMoreData: boolean;
|
||||
onPageChange: (newPage: number) => void;
|
||||
};
|
||||
|
||||
const Paginator: React.FC<PaginatorProps> = ({
|
||||
pageNumber,
|
||||
loading,
|
||||
noMoreData,
|
||||
onPageChange,
|
||||
}) => {
|
||||
const handlePrevPage = () => {
|
||||
if (pageNumber > 1) {
|
||||
onPageChange(pageNumber - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNextPage = () => {
|
||||
if (!noMoreData) {
|
||||
onPageChange(pageNumber + 1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex justifyContent="center" alignItems="center" mt={2}>
|
||||
<Button
|
||||
onClick={handlePrevPage}
|
||||
isDisabled={pageNumber === 1 || loading}
|
||||
mr={2}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
<Text>{pageNumber}</Text>
|
||||
<Button
|
||||
onClick={handleNextPage}
|
||||
isDisabled={noMoreData || loading}
|
||||
ml={2}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
{noMoreData && !loading && <Text ml={4}>No more data!</Text>}
|
||||
{loading && <Spinner ml={2} size={"xs"} color="gray" />}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default Paginator;
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
TabPanels,
|
||||
Tab,
|
||||
TabPanel,
|
||||
useToast,
|
||||
HStack,
|
||||
Tag,
|
||||
} from "@chakra-ui/react";
|
||||
@@ -30,10 +29,10 @@ import { WorkspaceContext } from "../WorkspaceContext";
|
||||
import { Changelog, WorkflowVersion } from "../types/dbTypes";
|
||||
import DeleteConfirm from "./DeleteConfirm";
|
||||
import { app } from "../utils/comfyapp";
|
||||
import Paginator from "./Paginator";
|
||||
|
||||
export function VersionHistoryDrawer({ onClose }: { onClose: () => void }) {
|
||||
const { isDirty, loadWorkflowID, curVersion, session } =
|
||||
useContext(WorkspaceContext);
|
||||
const { isDirty, curVersion, session } = useContext(WorkspaceContext);
|
||||
const [active, setActive] = useState(0); // 0: version、1: changelog
|
||||
const [selectedVersion, setSelectedVersion] = useState<string | null>(null);
|
||||
const [changelogs, setChangelogs] = useState<Changelog[]>([]);
|
||||
@@ -43,6 +42,7 @@ export function VersionHistoryDrawer({ onClose }: { onClose: () => void }) {
|
||||
const curFlow = workflowsTable?.curWorkflow;
|
||||
const cloudHost = userSettingsTable?.settings?.cloudHost;
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [pageNumber, setPageNumber] = useState(1);
|
||||
const loadChangeLogs = async (flowId: string) => {
|
||||
const workflow = await workflowsTable?.get(flowId);
|
||||
const changelogs = await changelogsTable?.listByWorkflowID(flowId);
|
||||
@@ -54,15 +54,13 @@ export function VersionHistoryDrawer({ onClose }: { onClose: () => void }) {
|
||||
selectedChangelogID && setSelectedVersion(selectedChangelogID);
|
||||
};
|
||||
|
||||
const loadVersions = async () => {
|
||||
const loadVersions = async (pageNum: number) => {
|
||||
let vers =
|
||||
(await workflowVersionsTable?.listByWorkflowID(curFlow!.id)) ?? [];
|
||||
if (session?.shareKey && curFlow?.cloudID) {
|
||||
setLoading(true);
|
||||
await fetch(
|
||||
userSettingsTable?.settings?.cloudHost! +
|
||||
"/api/listWorkflowVersionsByWorkflowID?workflowID=" +
|
||||
curFlow?.cloudID,
|
||||
`${userSettingsTable?.settings?.cloudHost}/api/listWorkflowVersionsByWorkflowID?workflowID=${curFlow?.cloudID}&pageNumber=${pageNum}`,
|
||||
{
|
||||
headers: {
|
||||
authorization: `Bearer ${session?.shareKey}`,
|
||||
@@ -90,16 +88,22 @@ export function VersionHistoryDrawer({ onClose }: { onClose: () => void }) {
|
||||
setVersions(vers);
|
||||
};
|
||||
|
||||
const onVersionsPageChange = (num: number) => {
|
||||
setPageNumber(num);
|
||||
loadVersions(num);
|
||||
};
|
||||
|
||||
const onDelete = (id: string) => {
|
||||
workflowVersionsTable?.delete(id).then(() => {
|
||||
loadVersions();
|
||||
loadVersions(pageNumber);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
switch (active) {
|
||||
case 0:
|
||||
loadVersions();
|
||||
setPageNumber(1);
|
||||
loadVersions(1);
|
||||
break;
|
||||
case 1:
|
||||
loadChangeLogs(curFlow!.id);
|
||||
@@ -158,7 +162,6 @@ export function VersionHistoryDrawer({ onClose }: { onClose: () => void }) {
|
||||
</Text>
|
||||
)}
|
||||
<Stack divider={<StackDivider />} spacing={2}>
|
||||
{loading && <Text>Loading...</Text>}
|
||||
{versions?.map((version) => {
|
||||
return (
|
||||
<Flex
|
||||
@@ -223,6 +226,12 @@ export function VersionHistoryDrawer({ onClose }: { onClose: () => void }) {
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
<Paginator
|
||||
onPageChange={onVersionsPageChange}
|
||||
pageNumber={pageNumber}
|
||||
loading={loading}
|
||||
noMoreData={versions.length === 0}
|
||||
/>
|
||||
</Stack>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
|
||||
+16
-38
@@ -4,8 +4,9 @@ import { ColorModeScript, type StyleFunctionProps } from "@chakra-ui/react";
|
||||
import { ChakraProvider, extendTheme } from "@chakra-ui/react";
|
||||
import { AlertDialogProvider } from "./components/AlertDialogProvider.tsx";
|
||||
import CSSReset from "./MyCSSReset.tsx";
|
||||
import { waitForApp } from "./utils/comfyapp.ts";
|
||||
|
||||
const topbar = document.createElement("div");
|
||||
document.body.append(topbar);
|
||||
const App = React.lazy(() =>
|
||||
import("./App.tsx").then(({ default: App }) => ({
|
||||
default: App,
|
||||
@@ -50,6 +51,20 @@ const theme = extendTheme({
|
||||
},
|
||||
});
|
||||
|
||||
ReactDOM.createRoot(topbar).render(
|
||||
<React.StrictMode>
|
||||
<ChakraProvider resetCSS={false} disableGlobalStyle={true} theme={theme}>
|
||||
<CSSReset scope=".workspace_manager" />
|
||||
<ColorModeScript initialColorMode="dark" />
|
||||
<AlertDialogProvider>
|
||||
<Suspense>
|
||||
<App />
|
||||
</Suspense>
|
||||
</AlertDialogProvider>
|
||||
</ChakraProvider>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
// hacky fix for chakra add extra className (chakra-ui-light) into body
|
||||
// and resulted in user unable to copy new nodes into clipboard, because of this check: e.target.className === "litegraph"
|
||||
// (https://github.com/comfyanonymous/ComfyUI/blob/57926635e8d84ae9eea4a0416cc75e363f5ede45/web/scripts/app.js#L861)
|
||||
@@ -81,40 +96,3 @@ const callback = function (
|
||||
};
|
||||
const observer = new MutationObserver(callback);
|
||||
observer.observe(targetNode, observerConfig);
|
||||
|
||||
function waitForDocumentBody() {
|
||||
return new Promise((resolve) => {
|
||||
if (document.body) {
|
||||
return resolve(document.body);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
resolve(document.body);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// wait for document.body to load so that the top menu is loaded and my react component has a place to mount
|
||||
waitForDocumentBody()
|
||||
.then(() => waitForApp())
|
||||
.then(() => {
|
||||
const topbar = document.createElement("div");
|
||||
document.body.append(topbar);
|
||||
ReactDOM.createRoot(topbar).render(
|
||||
<React.StrictMode>
|
||||
<ChakraProvider
|
||||
resetCSS={false}
|
||||
disableGlobalStyle={true}
|
||||
theme={theme}
|
||||
>
|
||||
<CSSReset scope=".workspace_manager" />
|
||||
<ColorModeScript initialColorMode="dark" />
|
||||
<AlertDialogProvider>
|
||||
<Suspense>
|
||||
<App />
|
||||
</Suspense>
|
||||
</AlertDialogProvider>
|
||||
</ChakraProvider>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -67,13 +67,8 @@ export default function AppIsDirtyEventListener() {
|
||||
return;
|
||||
}
|
||||
const id = app.graph.extra?.[COMFYSPACE_TRACKING_FIELD_NAME]?.id;
|
||||
console.log("🦄 Restoring workflow... id=", id);
|
||||
if (id) {
|
||||
const flow = await workflowsTable?.get(id);
|
||||
console.log(
|
||||
"🦄 Restored workflow",
|
||||
flow?.parentFolderID + "/" + flow?.name,
|
||||
);
|
||||
flow && setCurFlowIDAndName(flow);
|
||||
|
||||
if (
|
||||
|
||||
@@ -4,14 +4,10 @@ const api_base = location.pathname.split("/").slice(0, -1).join("/");
|
||||
|
||||
export let api: any | null = null;
|
||||
|
||||
export async function waitForApp() {
|
||||
await Promise.all([
|
||||
import(api_base + "/scripts/api.js").then((apiJs) => {
|
||||
api = apiJs?.api;
|
||||
}),
|
||||
import(api_base + "/scripts/api.js").then((apiJs) => {
|
||||
api = apiJs?.api;
|
||||
});
|
||||
|
||||
import(api_base + "/scripts/app.js").then((appJs) => {
|
||||
app = appJs?.app;
|
||||
}),
|
||||
]);
|
||||
}
|
||||
import(api_base + "/scripts/app.js").then((appJs) => {
|
||||
app = appJs?.app;
|
||||
});
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
// utils/encryptUtils
|
||||
const defaultSalt = import.meta.env.VITE_SHARE_KEY_ENCODE_KEY;
|
||||
|
||||
export function encodeKey(text: string, salt: string = defaultSalt): string {
|
||||
const randomLength = Math.floor(Math.random() * 13) + 1; // Random length between 1 and 13
|
||||
const randomPadding = Math.random()
|
||||
.toString(36)
|
||||
.substring(2, 2 + randomLength);
|
||||
const input = randomPadding + salt + text;
|
||||
return btoa(encodeURIComponent(input));
|
||||
}
|
||||
|
||||
export function decodeKey(encoded: string, salt: string = defaultSalt): string {
|
||||
const decoded = decodeURIComponent(atob(encoded));
|
||||
const saltIndex = decoded.indexOf(salt);
|
||||
if (saltIndex === -1) throw new Error("Invalid encoded string or salt");
|
||||
return decoded.slice(saltIndex + salt.length);
|
||||
}
|
||||
@@ -22,7 +22,6 @@ const rewriteImportPlugin = ({ isDev }) => {
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(({ mode }) => ({
|
||||
base: "./",
|
||||
envDir: ".",
|
||||
build: {
|
||||
watch: {
|
||||
|
||||
Reference in New Issue
Block a user