2 Commits
8 changed files with 140 additions and 126 deletions
+6
View File
@@ -52,6 +52,9 @@ def file_handle(name, fileList, file_path, metaInfoOnly):
createTime, updateTime = getFileCreateTime(file_path)
workspace_info = json_data.get('extra', {}).get('workspace_info', {})
workflow_id = workspace_info.get('id', str(uuid.uuid4()))
saveLock = workspace_info.get('saveLock', False)
cloudID = workspace_info.get('cloudID', None)
coverMediaPath = workspace_info.get('coverMediaPath', None)
# Update JSON data with new ID if needed and write back to file
if 'id' not in workspace_info:
if 'extra' not in json_data:
@@ -66,6 +69,9 @@ def file_handle(name, fileList, file_path, metaInfoOnly):
'name': name,
'type': "workflow",
'id': workflow_id,
'saveLock': saveLock,
'cloudID': cloudID,
'coverMediaPath': coverMediaPath,
'createTime': createTime,
'updateTime': updateTime
}
-1
View File
@@ -337,7 +337,6 @@ export default function App() {
json: workflow.json,
name: newFlowName || workflow.name,
parentFolderID: workflow.parentFolderID,
tags: workflow.tags,
});
flow && (await loadWorkflowID(flow.id));
};
+32 -16
View File
@@ -10,7 +10,7 @@ import {
export namespace TwowaySyncAPI {
export async function moveWorkflow(
workflow: Workflow,
curRelPath: string,
newParentFolderRelPath: string,
) {
try {
@@ -20,7 +20,7 @@ export namespace TwowaySyncAPI {
"Content-Type": "application/json",
},
body: JSON.stringify({
path: getWorkflowRelPath(workflow),
path: curRelPath,
newParentPath: newParentFolderRelPath,
}),
});
@@ -62,7 +62,7 @@ export namespace TwowaySyncAPI {
}
export async function renameWorkflow(
{ parentFolderID, name }: Pick<Workflow, "name" | "parentFolderID">,
{ relPath, id }: { relPath: string; id: string },
newName: string,
) {
try {
@@ -72,7 +72,7 @@ export namespace TwowaySyncAPI {
"Content-Type": "application/json",
},
body: JSON.stringify({
path: joinRelPath(parentFolderID ?? "", name + ".json"),
path: relPath,
newName: newName,
}),
});
@@ -103,6 +103,11 @@ export namespace TwowaySyncAPI {
flow.extra[COMFYSPACE_TRACKING_FIELD_NAME] = {};
}
flow.extra[COMFYSPACE_TRACKING_FIELD_NAME].id = workflow.id;
flow.extra[COMFYSPACE_TRACKING_FIELD_NAME].saveLock = workflow.saveLock;
flow.extra[COMFYSPACE_TRACKING_FIELD_NAME].cloudID = workflow.cloudID;
flow.extra[COMFYSPACE_TRACKING_FIELD_NAME].coverMediaPath =
workflow.coverMediaPath;
const response = await fetchApi("/workspace/file/save", {
method: "POST",
headers: {
@@ -143,12 +148,8 @@ export namespace TwowaySyncAPI {
console.error("Error deleting file:", error);
}
}
export async function createWorkflow({
parentFolderID,
name,
json,
id,
}: Workflow) {
export async function createWorkflow(workflow: Workflow) {
const { parentFolderID, name, json, id } = workflow;
let jsonObj: any = JSON.parse(json);
if (jsonObj.extra == null) {
jsonObj.extra = {};
@@ -157,6 +158,10 @@ export namespace TwowaySyncAPI {
jsonObj.extra[COMFYSPACE_TRACKING_FIELD_NAME] = {};
}
jsonObj.extra[COMFYSPACE_TRACKING_FIELD_NAME].id = id; // to avoid overwriting deps
jsonObj.extra[COMFYSPACE_TRACKING_FIELD_NAME].saveLock = workflow.saveLock;
jsonObj.extra[COMFYSPACE_TRACKING_FIELD_NAME].cloudID = workflow.cloudID;
jsonObj.extra[COMFYSPACE_TRACKING_FIELD_NAME].coverMediaPath =
workflow.coverMediaPath;
const input: { parentFolderPath: string; name: string; json: string } = {
parentFolderPath: sanitizeRelPath(parentFolderID ?? ""),
name: name,
@@ -188,18 +193,26 @@ export namespace TwowaySyncAPI {
}
}
export async function getFile({
parentFolderID,
name,
relPath,
id,
}: {
parentFolderID: string | null;
relPath: string;
id: string;
name: string;
}): Promise<{
json?: Object;
json?: {
extra: {
[COMFYSPACE_TRACKING_FIELD_NAME]: {
id: string;
saveLock: boolean;
cloudID: string | null;
coverMediaPath: string | null;
};
};
};
createTime?: number;
updateTime?: number;
error?: string;
}> {
const relPath = joinRelPath(parentFolderID ?? "", name + ".json");
try {
const response = await fetchApi("/workspace/get_workflow_file", {
method: "POST",
@@ -236,6 +249,9 @@ export type ScanLocalFile = {
name: string;
id: string;
json: string;
saveLock: boolean;
cloudID: string | null;
coverMediaPath: string | null;
createTime: number;
updateTime: number;
lastOpenedTime?: number;
+76 -71
View File
@@ -21,6 +21,7 @@ import { COMFYSPACE_TRACKING_FIELD_NAME } from "../const";
export class WorkflowsTable extends TableBase<Workflow> {
static readonly TABLE_NAME = "workflows";
private _curWorkflow: Workflow | null = null;
workflowIDPathMap: Record<string, string> = {};
public get curWorkflow() {
return this._curWorkflow;
}
@@ -60,20 +61,8 @@ export class WorkflowsTable extends TableBase<Workflow> {
return true;
}
public async listByIndex(
index: keyof Workflow,
value: string,
): Promise<Workflow[]> {
const list = await super.listByIndex(index, value);
const twoWaySyncEnabled = await userSettingsTable?.getSetting("twoWaySync");
if (!twoWaySyncEnabled) {
return list;
}
// for two way sync we need to varify if it really exists in disk
const results = await Promise.all(
list.map(async (f) => await this.get(f.id)),
);
return results.filter((f) => f != null) as Workflow[];
public async listByIndex(): Promise<Workflow[]> {
throw new Error("Method not allowed.");
}
async generateUniqueName(name?: string, parentFolderID?: string) {
@@ -131,8 +120,6 @@ export class WorkflowsTable extends TableBase<Workflow> {
};
//add to IndexDB
await indexdb.workflows.add(newWorkflow);
// add to disk file db
this.saveDiskDB();
// add to my_workflows/
const twoWaySyncEnabled = await userSettingsTable?.getSetting("twoWaySync");
if (twoWaySyncEnabled) {
@@ -143,20 +130,10 @@ export class WorkflowsTable extends TableBase<Workflow> {
}
// disallow TableBase.update()
async update(
_id: string,
_change: Partial<Workflow>,
): Promise<Workflow | null> {
async update(): Promise<Workflow | null> {
throw new Error("Method not allowed.");
}
public async updateTopFields(
id: string,
change: Pick<Partial<Workflow>, "topFieldsConfig">,
): Promise<Workflow | null> {
return this._update(id, change);
}
public async updateMetaInfo(
id: string,
change: Omit<Partial<Workflow>, "id" | "name" | "parentFolderID" | "json">,
@@ -167,26 +144,33 @@ export class WorkflowsTable extends TableBase<Workflow> {
id: string,
change: Partial<Workflow>,
): Promise<Workflow | null> {
//update indexdb
await indexdb.workflows.update(id, change);
const newWorkflow = (await indexdb.workflows.get(id)) ?? null;
const cur = await this.get(id);
if (!cur) {
return null;
}
const newWorkflow = {
...cur,
...change,
};
await TwowaySyncAPI.saveWorkflow(newWorkflow);
//update curWorkflow RAM
if (this._curWorkflow && this._curWorkflow.id === id) {
this._curWorkflow = newWorkflow;
}
this.saveDiskDB();
return newWorkflow;
}
async updateFolder(id: string, change: Pick<Workflow, "parentFolderID">) {
const twoWaySyncEnabled = await userSettingsTable?.getSetting("twoWaySync");
const oldWorkflow = await this.get(id);
const newWorkflow = await this.updateMetaInfo(id, change as any);
if (!newWorkflow || !oldWorkflow) {
return;
}
if (twoWaySyncEnabled) {
await TwowaySyncAPI.moveWorkflow(oldWorkflow, change.parentFolderID!);
const relPath = this.workflowIDPathMap[id];
if (!relPath) {
alert("Error: workflow not found. Cannot move workflow.");
return;
}
await TwowaySyncAPI.moveWorkflow(relPath, change.parentFolderID!);
}
}
public async genLastManualSavedJson(id: string) {
@@ -194,25 +178,31 @@ export class WorkflowsTable extends TableBase<Workflow> {
return changelog?.json;
}
public async updateName(id: string, change: Pick<Workflow, "name">) {
const before = await this.get(id);
const path = this.workflowIDPathMap[id];
if (!path) {
alert("Error: workflow not found. Cannot rename workflow.");
return;
}
const twoWaySyncEnabled = await userSettingsTable?.getSetting("twoWaySync");
if (twoWaySyncEnabled) {
before &&
(await TwowaySyncAPI.renameWorkflow(before, change.name + ".json"));
const success = await TwowaySyncAPI.renameWorkflow(
{
relPath: path,
id: id,
},
change.name + ".json",
);
if (success) {
this.workflowIDPathMap[id] =
`${path.split("/").slice(0, -1).join("/")}/${change.name}.json`
.split("/")
.filter((part) => part !== "")
.join("/");
}
}
return await this.updateMetaInfo(id, change as any);
}
public async updateFlow(id: string, input: Pick<Workflow, "json">) {
const before = await this.get(id, false);
if (before == null) {
return;
}
const beforeStr = JSON.stringify(before.json);
const afterStr = JSON.stringify(input.json);
if (beforeStr === afterStr) {
// no change detected
return;
}
const after = await this._update(id, {
updateTime: Date.now(),
json: input.json,
@@ -275,17 +265,10 @@ export class WorkflowsTable extends TableBase<Workflow> {
}
}
}
try {
await indexdb.workflows.bulkAdd(newWorkflows);
this.saveDiskDB();
} catch (e) {
console.error("batchCreateFlows error", e);
}
}
public async deleteFlow(id: string) {
const workflow = await this.get(id);
if (workflow) {
const twoWaySyncEnabled =
await userSettingsTable?.getSetting("twoWaySync");
@@ -293,9 +276,6 @@ export class WorkflowsTable extends TableBase<Workflow> {
await TwowaySyncAPI.deleteWorkflow(workflow);
}
}
//add to IndexDB
await indexdb.workflows.delete(id);
this.saveDiskDB();
}
public async batchDeleteFlow(ids: string[]) {
@@ -308,8 +288,6 @@ export class WorkflowsTable extends TableBase<Workflow> {
await TwowaySyncAPI.deleteWorkflow(flow);
}
}
await indexdb.workflows.bulkDelete(ids);
this.saveDiskDB();
}
public async listFolderContent(
@@ -339,28 +317,55 @@ export class WorkflowsTable extends TableBase<Workflow> {
public async get(
id: string,
fetchJsonContentOnDisk = true, //if false, only get the meta info from indexdb
): Promise<Workflow | undefined> {
): Promise<
| {
id: string;
name: string;
parentFolderID?: string | null;
saveLock?: boolean;
cloudID?: string | null;
coverMediaPath?: string | null;
json: string;
updateTime: number;
createTime: number;
}
| undefined
> {
const twoWaySyncEnabled = await userSettingsTable?.getSetting("twoWaySync");
const wf = await indexdb.workflows.get(id);
if (!twoWaySyncEnabled || !fetchJsonContentOnDisk) {
return wf;
if (!twoWaySyncEnabled) {
return await indexdb.workflows.get(id);
}
// two way sync mode
if (wf == null) {
const rel = this.workflowIDPathMap[id];
if (!rel) {
return undefined;
}
const data = await TwowaySyncAPI.getFile({
parentFolderID: wf.parentFolderID ?? null,
name: wf.name,
id: wf.id,
relPath: rel,
id: id,
});
if (!data.json) {
return undefined;
}
const workspaceInfo = data.json?.extra?.[COMFYSPACE_TRACKING_FIELD_NAME];
if (!workspaceInfo || workspaceInfo.id !== id) {
return undefined;
}
return {
...wf,
id: workspaceInfo.id,
name:
rel
.split("/")
.pop()
?.replace(/\.json$/, "") ?? "Untitled Flow",
parentFolderID: rel.split("/").slice(0, -1).join("/"),
saveLock: workspaceInfo.saveLock,
cloudID: workspaceInfo.cloudID,
coverMediaPath: workspaceInfo.coverMediaPath,
json: JSON.stringify(data.json),
updateTime: data.updateTime ?? Date.now(),
createTime: data.createTime ?? Date.now(),
};
}
@@ -63,18 +63,6 @@ export default function MetadataForm({ media }: { media: Media | null }) {
const [topFields, setTopFields] = useState<TopFieldType[]>([]);
useEffect(() => {
(async () => {
if (curFlowID) {
const topFieldsConfig = (await workflowsTable?.get(curFlowID))
?.topFieldsConfig;
if (topFieldsConfig) {
setTopFields(topFieldsConfig);
setShowAllInputs(false);
}
}
})();
}, [curFlowID]);
const updateTopField = (field: TopFieldType) => {
if (
isInTopField(topFields, {
@@ -87,19 +75,9 @@ export default function MetadataForm({ media }: { media: Media | null }) {
(v) => v.name !== field.name || v.promptKey !== field.promptKey,
);
setTopFields(topFieldsConfig);
if (curFlowID) {
workflowsTable?.updateTopFields(curFlowID, {
topFieldsConfig,
});
}
} else {
const topFieldsConfig = [...topFields, field];
setTopFields(topFieldsConfig);
if (curFlowID) {
workflowsTable?.updateTopFields(curFlowID, {
topFieldsConfig,
});
}
}
};
@@ -32,6 +32,7 @@ export default function ServerEventListener() {
name: version.name,
authorID: version.authorID,
json: version.json,
api_prompt: null,
});
newversion && loadWorkflowID(existingWorkflow.id, newversion.id);
}
@@ -56,6 +57,7 @@ export default function ServerEventListener() {
name: version.name,
json: version.json,
authorID: version.authorID,
api_prompt: null,
createTime: Date.now(),
});
await loadWorkflowID(newWorkflow.id, newverison?.id);
+6 -4
View File
@@ -11,14 +11,16 @@ export interface Workflow extends SortableItem {
id: string;
json: string;
name: string;
coverMediaPath?: string | null;
saveLock?: boolean;
cloudID?: string | null;
createTime: number;
// will be deprecated
privacy?: WorkflowPrivacy;
tags?: string[];
parentFolderID?: string | null; //TODO remove undefined, use null only
coverMediaPath?: string;
cloudID?: string; // TODO will be deprecated
cloudOrigin?: string;
saveLock?: boolean;
latestImage?: string;
topFieldsConfig?: TopFieldType[];
}
@@ -65,7 +67,7 @@ export type WorkflowVersion = {
workflowID: string;
json: string;
createTime: number;
api_prompt: string;
api_prompt: string | null;
cloudID?: string;
cloudOrigin?: string;
authorID?: string;
+18 -12
View File
@@ -4,7 +4,7 @@ import {
ScanLocalFolder,
scanLocalFiles,
} from "../apis/TwowaySyncApi";
import { isFolder } from "../db-tables/WorkspaceDB";
import { isFolder, workflowsTable } from "../db-tables/WorkspaceDB";
import { indexdb } from "../db-tables/indexdb";
import { Folder, Workflow } from "../types/dbTypes";
import { sortFileItem } from "../utils";
@@ -34,27 +34,33 @@ export async function scanMyWorkflowsDir(
return folder;
} else {
// is workflow
// const existingWorkflow = (await indexdb.workflows
// ?.get(file.id)
// .catch((err) => {
// console.error("Error getting workflow from indexdb", err);
// return null;
// })) ?? {
// createTime: Date.now(),
// updateTime: Date.now(),
// };
const existingWorkflow = (await indexdb.workflows
?.get(file.id)
.catch((err) => {
console.error("Error getting workflow from indexdb", err);
return null;
})) ?? {
createTime: Date.now(),
updateTime: Date.now(),
};
if (workflowsTable) {
workflowsTable.workflowIDPathMap[file.id] = relPath;
}
const newWorkflow: Workflow = {
...existingWorkflow,
// ...existingWorkflow,
id: file.id,
json: file.json ?? "{}",
cloudID: file.cloudID,
coverMediaPath: file.coverMediaPath,
saveLock: file.saveLock,
name: fileName.replace(/\.json$/, ""),
parentFolderID: parentRelPath,
createTime: file.createTime,
// setting updateTime will result latestVersionChcek() always fail if in
// workflow.get() not pull updateTime from file
// updateTime: file.updateTime,
updateTime: file.updateTime,
};
return newWorkflow;
}