diff --git a/web-plugin/index.js b/web-plugin/index.js
index 47e71d3..c960293 100644
--- a/web-plugin/index.js
+++ b/web-plugin/index.js
@@ -9,6 +9,62 @@ const ext = {
init(app) {
addButton();
+
+ const queryParams = new URLSearchParams(window.location.search);
+ const workflow_version_id = queryParams.get("workflow_version_id");
+ const auth_token = queryParams.get("auth_token");
+ const org_display = queryParams.get("org_display");
+ const origin = queryParams.get("origin");
+ if (!workflow_version_id) {
+ console.error("No workflow_version_id provided in query parameters.");
+ } else {
+ const data = getData();
+ let endpoint = data.endpoint;
+ let apiKey = data.apiKey;
+
+ // If there is auth token override it
+ if (auth_token) {
+ apiKey = auth_token;
+ endpoint = origin;
+ saveData({
+ displayName: org_display,
+ endpoint: origin,
+ apiKey: auth_token,
+ displayName: org_display,
+ });
+ }
+
+ loadingDialog.showLoading(
+ "Loading workflow from " + org_display,
+ "Please wait...",
+ );
+ fetch(endpoint + "/api/workflow-version/" + workflow_version_id, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: "Bearer " + apiKey,
+ },
+ })
+ .then(async (res) => {
+ const data = await res.json();
+ const { workflow, error } = data;
+ if (error) {
+ infoDialog.showMessage("Unable to load this workflow", error);
+ return;
+ }
+ /** @type {LGraph} */
+ app.loadGraphData(workflow);
+ })
+ .catch((e) => infoDialog.showMessage("Error", e.message))
+ .finally(() => {
+ loadingDialog.close();
+ window.history.replaceState(
+ {},
+ document.title,
+ window.location.pathname,
+ );
+ });
+ }
},
registerCustomNodes() {
@@ -191,7 +247,7 @@ function addButton() {
endpoint = endpoint.slice(0, -1);
}
- const apiRoute = endpoint + "/api/upload";
+ const apiRoute = endpoint + "/api/workflow";
// const userId = apiKey
try {
const body = {
@@ -280,14 +336,16 @@ export class InfoDialog extends ComfyDialog {
this.element.classList.add("comfy-normal-modal");
this.element.style.paddingBottom = "20px";
}
+
+ button = undefined;
+
createButtons() {
- return [
- $el("button", {
- type: "button",
- textContent: "Close",
- onclick: () => this.close(),
- }),
- ];
+ this.button = $el("button", {
+ type: "button",
+ textContent: "Close",
+ onclick: () => this.close(),
+ });
+ return [this.button];
}
close() {
@@ -317,6 +375,58 @@ export class InfoDialog extends ComfyDialog {
`);
}
+
+ loadingIcon = ``;
+
+ showLoading(title, message) {
+ this.show(`
+
+
${title} ${this.loadingIcon}
+
+
+ `);
+ }
+}
+
+export class LoadingDialog extends ComfyDialog {
+ constructor() {
+ super();
+ this.element.classList.add("comfy-normal-modal");
+ // this.element.style.paddingBottom = "20px";
+ }
+
+ createButtons() {
+ return [];
+ }
+
+ close() {
+ this.element.style.display = "none";
+ }
+
+ show(html) {
+ this.textElement.style["white-space"] = "normal";
+ this.textElement.style.color = "white";
+ this.textElement.style.marginTop = "0px";
+ if (typeof html === "string") {
+ this.textElement.innerHTML = html;
+ } else {
+ this.textElement.replaceChildren(html);
+ }
+ this.element.style.display = "flex";
+ this.element.style.zIndex = 1001;
+ }
+
+ loadingIcon = ``;
+
+ showLoading(title, message) {
+ this.show(`
+
+
${title} ${this.loadingIcon}
+
+ `);
+ }
}
export class InputDialog extends InfoDialog {
@@ -444,9 +554,15 @@ export class ConfirmDialog extends InfoDialog {
}
export const inputDialog = new InputDialog();
+export const loadingDialog = new LoadingDialog();
export const infoDialog = new InfoDialog();
export const confirmDialog = new ConfirmDialog();
+/**
+ * Retrieves deployment data from local storage or defaults.
+ * @param {string} [environment] - The environment to get the data for.
+ * @returns {{endpoint: string, apiKey: string, displayName: string, environment?: string}} The deployment data.
+ */
function getData(environment) {
const deployOption =
environment || localStorage.getItem("comfy_deploy_env") || "cloud";
@@ -469,6 +585,17 @@ function getData(environment) {
};
}
+/**
+ * Retrieves deployment data from local storage or defaults.
+ * @param {{endpoint: string, apiKey: string, displayName: string, environment?: string}} [data] - The environment to get the data for.
+ */
+function saveData(data) {
+ localStorage.setItem(
+ "comfy_deploy_env_data_" + data.environment,
+ JSON.stringify(data),
+ );
+}
+
export class ConfigDialog extends ComfyDialog {
container = null;
poll = null;
@@ -527,15 +654,12 @@ export class ConfigDialog extends ComfyDialog {
const endpoint = this.container.querySelector("#endpoint").value;
const apiKey = api_key ?? this.container.querySelector("#apiKey").value;
- const data = {
+ saveData({
endpoint,
apiKey,
displayName,
- };
- localStorage.setItem(
- "comfy_deploy_env_data_" + deployOption,
- JSON.stringify(data),
- );
+ environment: deployOption,
+ });
this.close();
}
diff --git a/web/src/app/(app)/api/[[...routes]]/route.ts b/web/src/app/(app)/api/[[...routes]]/route.ts
index e8c7cdd..8e27050 100644
--- a/web/src/app/(app)/api/[[...routes]]/route.ts
+++ b/web/src/app/(app)/api/[[...routes]]/route.ts
@@ -8,7 +8,8 @@ import { handle } from "hono/vercel";
import { app } from "../../../../routes/app";
import { registerWorkflowUploadRoute } from "@/routes/registerWorkflowUploadRoute";
import { registerGetAuthResponse } from "@/routes/registerGetAuthResponse";
-
+import { registerGetWorkflowRoute } from "@/routes/registerGetWorkflow";
+import { cors } from "hono/cors";
export const dynamic = "force-dynamic";
export const maxDuration = 300; // 5 minutes
@@ -18,17 +19,24 @@ declare module "hono" {
}
}
-async function checkAuth(c: Context, next: Next) {
+async function checkAuth(c: Context, next: Next, headers?: HeadersInit) {
const token = c.req.raw.headers.get("Authorization")?.split(" ")?.[1]; // Assuming token is sent as "Bearer your_token"
const userData = token ? parseJWT(token) : undefined;
if (!userData || token === undefined) {
- return c.text("Invalid or expired token", 401);
+ return c.text("Invalid or expired token", {
+ status: 401,
+ headers: headers,
+ });
}
// If the key has expiration, this is a temporary key and not in our db, so we can skip checking
if (userData.exp === undefined) {
const revokedKey = await isKeyRevoked(token);
- if (revokedKey) return c.text("Revoked token", 401);
+ if (revokedKey)
+ return c.text("Revoked token", {
+ status: 401,
+ headers: headers,
+ });
}
c.set("apiKeyTokenData", userData);
@@ -36,9 +44,29 @@ async function checkAuth(c: Context, next: Next) {
await next();
}
+async function checkAuthCORS(c: Context, next: Next) {
+ return checkAuth(c, next, {
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
+ });
+}
+
app.use("/run", checkAuth);
app.use("/upload-url", checkAuth);
-app.use("/upload-workflow", checkAuth);
+
+const corsHandler = cors({
+ origin: "*",
+ allowHeaders: ["Authorization", "Content-Type"],
+ allowMethods: ["POST", "GET", "OPTIONS"],
+ exposeHeaders: ["Content-Length"],
+ maxAge: 600,
+ credentials: true,
+});
+
+// CORS Check
+app.use("/workflow", corsHandler, checkAuth);
+app.use("/workflow-version/*", corsHandler, checkAuth);
// create run endpoint
registerCreateRunRoute(app);
@@ -47,9 +75,12 @@ registerGetOutputRoute(app);
// file upload endpoint
registerUploadRoute(app);
-registerWorkflowUploadRoute(app);
+// Anon
registerGetAuthResponse(app);
+registerWorkflowUploadRoute(app);
+registerGetWorkflowRoute(app);
+
// The OpenAPI documentation will be available at /doc
app.doc("/doc", {
openapi: "3.0.0",
@@ -76,3 +107,4 @@ const handler = handle(app);
export const GET = handler;
export const POST = handler;
+export const OPTIONS = handler;
diff --git a/web/src/app/(app)/workflows/[workflow_id]/@workflow/page.tsx b/web/src/app/(app)/workflows/[workflow_id]/@workflow/page.tsx
index 620b2b4..778d4b2 100644
--- a/web/src/app/(app)/workflows/[workflow_id]/@workflow/page.tsx
+++ b/web/src/app/(app)/workflows/[workflow_id]/@workflow/page.tsx
@@ -2,12 +2,13 @@ import { CreateShareButton } from "@/components/CreateShareButton";
import { MachinesWSMain } from "@/components/MachinesWS";
import { VersionDetails } from "@/components/VersionDetails";
import {
- CopyWorkflowVersion,
- CreateDeploymentButton,
- MachineSelect,
- RunWorkflowButton,
- VersionSelect,
- ViewWorkflowDetailsButton,
+ CopyWorkflowVersion,
+ CreateDeploymentButton,
+ MachineSelect,
+ OpenEditButton,
+ RunWorkflowButton,
+ VersionSelect,
+ ViewWorkflowDetailsButton,
} from "@/components/VersionSelect";
import {
Card,
@@ -48,6 +49,7 @@ export default async function Page({
+
diff --git a/web/src/app/(app)/workflows/page.tsx b/web/src/app/(app)/workflows/page.tsx
index d1499d2..afdd310 100644
--- a/web/src/app/(app)/workflows/page.tsx
+++ b/web/src/app/(app)/workflows/page.tsx
@@ -1,5 +1,5 @@
import { setInitialUserData } from "../../../lib/setInitialUserData";
-import { getAllUserWorkflow } from "../../../server/getAllUserWorkflow";
+import { getAllUserWorkflow } from "../../../server/crudWorkflow";
import { WorkflowList } from "@/components/WorkflowList";
import { db } from "@/db/db";
import { usersTable } from "@/db/schema";
diff --git a/web/src/components/DeploymentDisplay.tsx b/web/src/components/DeploymentDisplay.tsx
index 87be9a2..257545f 100644
--- a/web/src/components/DeploymentDisplay.tsx
+++ b/web/src/components/DeploymentDisplay.tsx
@@ -1,11 +1,11 @@
import { CodeBlock } from "@/components/CodeBlock";
import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
} from "@/components/ui/dialog";
import { ScrollArea } from "@/components/ui/scroll-area";
import { TableRow } from "@/components/ui/table";
@@ -83,145 +83,145 @@ const run = await client.getRun(run_id);
`;
export function DeploymentDisplay({
- deployment,
- domain,
+ deployment,
+ domain,
}: {
- deployment: Awaited>[0];
- domain: string;
+ deployment: Awaited>[0];
+ domain: string;
}) {
- const workflowInput = getInputsFromWorkflow(deployment.version);
+ const workflowInput = getInputsFromWorkflow(deployment.version);
- if (deployment.environment === "public-share") {
- return ;
- }
+ if (deployment.environment === "public-share") {
+ return ;
+ }
- return (
-
- );
+ return (
+
+ );
}
function formatCode(
- codeTemplate: string,
- deployment: Awaited>[0],
- domain: string,
- inputs?: ReturnType,
- inputsTabs?: number,
+ codeTemplate: string,
+ deployment: Awaited>[0],
+ domain: string,
+ inputs?: ReturnType,
+ inputsTabs?: number,
) {
- if (inputs && inputs.length > 0) {
- codeTemplate = codeTemplate.replace(
- "inputs: {}",
- `inputs: ${JSON.stringify(
- Object.fromEntries(
- inputs.map((x) => {
- return [x?.input_id, ""];
- }),
- ),
- null,
- 2,
- )
- .split("\n")
- .map((line, index) => (index === 0 ? line : ` ${line}`)) // Add two spaces indentation except for the first line
- .join("\n")}`,
- );
- } else {
- codeTemplate = codeTemplate.replace(
- `
+ if (inputs && inputs.length > 0) {
+ codeTemplate = codeTemplate.replace(
+ "inputs: {}",
+ `inputs: ${JSON.stringify(
+ Object.fromEntries(
+ inputs.map((x) => {
+ return [x?.input_id, ""];
+ }),
+ ),
+ null,
+ 2,
+ )
+ .split("\n")
+ .map((line, index) => (index === 0 ? line : ` ${line}`)) // Add two spaces indentation except for the first line
+ .join("\n")}`,
+ );
+ } else {
+ codeTemplate = codeTemplate.replace(
+ `
inputs: {}`,
- "",
- );
- }
- return codeTemplate
- .replace("", `${domain ?? "http://localhost:3000"}/api/run`)
- .replace("", deployment.id)
- .replace("", domain ?? "http://localhost:3000");
+ "",
+ );
+ }
+ return codeTemplate
+ .replace("", `${domain ?? "http://localhost:3000"}/api/run`)
+ .replace("", deployment.id)
+ .replace("", domain ?? "http://localhost:3000");
}
diff --git a/web/src/components/VersionSelect.tsx b/web/src/components/VersionSelect.tsx
index 96edf18..1ac739b 100644
--- a/web/src/components/VersionSelect.tsx
+++ b/web/src/components/VersionSelect.tsx
@@ -41,7 +41,14 @@ import { checkStatus, createRun } from "@/server/createRun";
import { createDeployments } from "@/server/curdDeploments";
import type { getMachines } from "@/server/curdMachine";
import type { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion";
-import { Copy, ExternalLink, Info, MoreVertical, Play } from "lucide-react";
+import {
+ Copy,
+ Edit,
+ ExternalLink,
+ Info,
+ MoreVertical,
+ Play,
+} from "lucide-react";
import { parseAsInteger, useQueryState } from "next-usequerystate";
import { useEffect, useMemo, useState } from "react";
import { toast } from "sonner";
@@ -51,6 +58,8 @@ import { create } from "zustand";
import { workflowVersionInputsToZod } from "../lib/workflowVersionInputsToZod";
import { callServerPromise } from "./callServerPromise";
import fetcher from "./fetcher";
+import { ButtonAction } from "@/components/ButtonActionLoader";
+import { editWorkflowOnMachine } from "@/server/editWorkflowOnMachine";
export function VersionSelect({
workflow,
@@ -116,13 +125,13 @@ export function MachineSelect({
}
export function useSelectedMachine(
- machines: Awaited>,
+ machines: Awaited>,
) {
- const a = useQueryState("machine", {
- defaultValue: machines?.[0]?.id ?? "",
- });
+ const a = useQueryState("machine", {
+ defaultValue: machines?.[0]?.id ?? "",
+ });
- return a;
+ return a;
}
type PublicRunStore = {
@@ -219,7 +228,7 @@ export function RunWorkflowButton({
const schema = useMemo(() => {
const workflow_version = getWorkflowVersionFromVersionIndex(
workflow,
- version
+ version,
);
if (!workflow_version) return null;
@@ -233,7 +242,7 @@ export function RunWorkflowButton({
const val = Object.keys(values).length > 0 ? values : undefined;
const workflow_version_id = workflow?.versions.find(
- (x) => x.version === version
+ (x) => x.version === version,
)?.id;
console.log(workflow_version_id);
if (!workflow_version_id) return;
@@ -248,7 +257,7 @@ export function RunWorkflowButton({
machine_id: machine,
inputs: val,
runOrigin: "manual",
- })
+ }),
);
// console.log(res.json());
setIsLoading(false);
@@ -317,7 +326,7 @@ export function CreateDeploymentButton({
const [isLoading, setIsLoading] = useState(false);
const workflow_version_id = workflow?.versions.find(
- (x) => x.version === version
+ (x) => x.version === version,
)?.id;
return (
@@ -337,8 +346,8 @@ export function CreateDeploymentButton({
workflow.id,
workflow_version_id,
machine,
- "production"
- )
+ "production",
+ ),
);
setIsLoading(false);
}}
@@ -355,8 +364,8 @@ export function CreateDeploymentButton({
workflow.id,
workflow_version_id,
machine,
- "staging"
- )
+ "staging",
+ ),
);
setIsLoading(false);
}}
@@ -368,6 +377,49 @@ export function CreateDeploymentButton({
);
}
+export function OpenEditButton({
+ workflow,
+ machines,
+}: {
+ workflow: Awaited>;
+ machines: Awaited>;
+}) {
+ const [version] = useQueryState("version", {
+ defaultValue: workflow?.versions[0].version ?? 1,
+ ...parseAsInteger,
+ });
+ const [machine] = useSelectedMachine(machines);
+ const workflow_version_id = workflow?.versions.find(
+ (x) => x.version == version,
+ )?.id;
+ const [isLoading, setIsLoading] = useState(false);
+
+ return (
+ workflow_version_id &&
+ machine && (
+
+ )
+ );
+}
+
export function CopyWorkflowVersion({
workflow,
}: {
@@ -378,7 +430,7 @@ export function CopyWorkflowVersion({
...parseAsInteger,
});
const workflow_version = workflow?.versions.find(
- (x) => x.version === version
+ (x) => x.version === version,
);
return (
@@ -402,7 +454,7 @@ export function CopyWorkflowVersion({
});
navigator.clipboard.writeText(
- JSON.stringify(workflow_version?.workflow)
+ JSON.stringify(workflow_version?.workflow),
);
toast("Copied to clipboard");
}}
@@ -412,7 +464,7 @@ export function CopyWorkflowVersion({
{
navigator.clipboard.writeText(
- JSON.stringify(workflow_version?.workflow_api)
+ JSON.stringify(workflow_version?.workflow_api),
);
toast("Copied to clipboard");
}}
@@ -426,7 +478,7 @@ export function CopyWorkflowVersion({
export function getWorkflowVersionFromVersionIndex(
workflow: Awaited>,
- version: number
+ version: number,
) {
const workflow_version = workflow?.versions.find((x) => x.version == version);
@@ -452,7 +504,7 @@ export function ViewWorkflowDetailsButton({
isLoading: isNodesIndexLoading,
} = useSWR(
"https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/extension-node-map.json",
- fetcher
+ fetcher,
);
const groupedByAuxName = useMemo(() => {
@@ -462,7 +514,7 @@ export function ViewWorkflowDetailsButton({
const workflow_version = getWorkflowVersionFromVersionIndex(
workflow,
- version
+ version,
);
const api = workflow_version?.workflow_api;
@@ -473,7 +525,7 @@ export function ViewWorkflowDetailsButton({
.map(([_, value]) => {
const classType = value.class_type;
const classTypeData = Object.entries(data).find(([_, nodeArray]) =>
- nodeArray[0].includes(classType)
+ nodeArray[0].includes(classType),
);
return classTypeData ? { node: value, classTypeData } : null;
})
@@ -503,7 +555,7 @@ export function ViewWorkflowDetailsButton({
node: z.infer[];
url: string;
}
- >
+ >,
);
// console.log(groupedByAuxName);
@@ -544,7 +596,8 @@ export function ViewWorkflowDetailsButton({
{key}
({
user: one(usersTable, {
fields: [workflowTable.user_id],
@@ -79,6 +81,7 @@ export const workflowVersionTable = dbSchema.table("workflow_versions", {
created_at: timestamp("created_at").defaultNow().notNull(),
updated_at: timestamp("updated_at").defaultNow().notNull(),
});
+export const workflowVersionSchema = createSelectSchema(workflowVersionTable);
export const workflowVersionRelations = relations(
workflowVersionTable,
diff --git a/web/src/routes/registerGetWorkflow.ts b/web/src/routes/registerGetWorkflow.ts
new file mode 100644
index 0000000..be17571
--- /dev/null
+++ b/web/src/routes/registerGetWorkflow.ts
@@ -0,0 +1,86 @@
+import { workflowVersionSchema } from "@/db/schema";
+import type { App } from "@/routes/app";
+import { authError } from "@/routes/authError";
+import { getWorkflowVersion } from "@/server/crudWorkflow";
+import { z, createRoute } from "@hono/zod-openapi";
+
+const route = createRoute({
+ method: "get",
+ path: "/workflow-version/:version_id",
+ tags: ["comfyui"],
+ summary: "Get comfyui workflow",
+ description: "Use this to retrieve comfyui workflow by id",
+ request: {
+ params: z.object({
+ version_id: z.string(),
+ }),
+ },
+ responses: {
+ 200: {
+ content: {
+ "application/json": {
+ schema: workflowVersionSchema,
+ },
+ },
+ description: "Retrieve the output",
+ },
+ 500: {
+ content: {
+ "application/json": {
+ schema: z.object({
+ error: z.string(),
+ }),
+ },
+ },
+ description: "Error when uploading the workflow",
+ },
+ ...authError,
+ },
+});
+
+export const registerGetWorkflowRoute = (app: App) => {
+ return app.openapi(route, async (c) => {
+ const { version_id } = c.req.valid("param");
+ const apiUser = c.get("apiKeyTokenData")!;
+
+ if (!apiUser.user_id)
+ return c.json(
+ {
+ error: "Invalid user_id",
+ },
+ {
+ status: 500,
+ },
+ );
+
+ try {
+ const workflow_version = await getWorkflowVersion(apiUser, version_id);
+ if (workflow_version) {
+ return c.json(workflow_version, {
+ status: 200,
+ });
+ } else {
+ return c.json(
+ {
+ error: "No version found",
+ },
+ {
+ status: 500,
+ },
+ );
+ }
+ } catch (error: unknown) {
+ const errorMessage =
+ error instanceof Error ? error.message : "Unknown error";
+ return c.json(
+ {
+ error: errorMessage,
+ },
+ {
+ statusText: "Invalid request",
+ status: 500,
+ },
+ );
+ }
+ });
+};
diff --git a/web/src/routes/registerWorkflowUploadRoute.ts b/web/src/routes/registerWorkflowUploadRoute.ts
index b6fe96d..b851ae2 100644
--- a/web/src/routes/registerWorkflowUploadRoute.ts
+++ b/web/src/routes/registerWorkflowUploadRoute.ts
@@ -1,4 +1,10 @@
-import { snapshotType, workflowAPIType, workflowType } from "@/db/schema";
+import { db } from "@/db/db";
+import {
+ snapshotType,
+ workflowAPIType,
+ workflowTable,
+ workflowType,
+} from "@/db/schema";
import type { App } from "@/routes/app";
import { authError } from "@/routes/authError";
import {
@@ -6,10 +12,11 @@ import {
createNewWorkflowVersion,
} from "@/server/createNewWorkflow";
import { z, createRoute } from "@hono/zod-openapi";
+import { and, eq } from "drizzle-orm";
const route = createRoute({
method: "post",
- path: "/upload-workflow",
+ path: "/workflow",
tags: ["comfyui"],
summary: "Upload workflow from ComfyUI",
description:
@@ -106,6 +113,30 @@ export const registerWorkflowUploadRoute = (app: App) => {
workflow_id = _workflow_id;
version = _version;
} else if (workflow_id) {
+ const workflow = await db
+ .select()
+ .from(workflowTable)
+ .where(
+ and(
+ eq(workflowTable.id, workflow_id),
+ eq(workflowTable.user_id, user_id),
+ eq(workflowTable.org_id, org_id),
+ ),
+ );
+
+ if (workflow.length === 0) {
+ return c.json(
+ {
+ error: "Invalid workflow_id",
+ },
+ {
+ status: 500,
+ statusText: "Invalid workflow_id",
+ headers: corsHeaders,
+ },
+ );
+ }
+
// Case 2 update workflow
const { version: _version } = await createNewWorkflowVersion({
workflow_id: workflow_id,
diff --git a/web/src/server/getAllUserWorkflow.tsx b/web/src/server/crudWorkflow.tsx
similarity index 61%
rename from web/src/server/getAllUserWorkflow.tsx
rename to web/src/server/crudWorkflow.tsx
index b6400f4..32fb56b 100644
--- a/web/src/server/getAllUserWorkflow.tsx
+++ b/web/src/server/crudWorkflow.tsx
@@ -4,8 +4,10 @@ import {
workflowTable,
workflowVersionTable,
} from "@/db/schema";
+import { APIKeyUserType } from "@/server/APIKeyBodyRequest";
import { auth } from "@clerk/nextjs";
import { and, desc, eq, isNull } from "drizzle-orm";
+import { redirect } from "next/navigation";
export async function getAllUserWorkflow() {
const { userId, orgId } = await auth();
@@ -51,3 +53,29 @@ export async function getAllUserWorkflow() {
return workflow;
}
+
+export async function getWorkflowVersion(
+ apiUser: APIKeyUserType,
+ version_id: string,
+) {
+ const { org_id, user_id } = apiUser;
+
+ if (!user_id) {
+ throw new Error("No user id");
+ }
+
+ const parentWorkflow = await db.query.workflowTable.findFirst({
+ where:
+ org_id != undefined
+ ? eq(workflowTable.org_id, org_id)
+ : and(eq(workflowTable.user_id, user_id), isNull(workflowTable.org_id)),
+ });
+
+ if (!parentWorkflow) {
+ throw new Error("No workflow found");
+ }
+
+ return db.query.workflowVersionTable.findFirst({
+ where: eq(workflowVersionTable.id, version_id),
+ });
+}
diff --git a/web/src/server/curdDeploments.ts b/web/src/server/curdDeploments.ts
index 9ea5190..a3ef2b0 100644
--- a/web/src/server/curdDeploments.ts
+++ b/web/src/server/curdDeploments.ts
@@ -16,270 +16,271 @@ import "server-only";
import { validate as isValidUUID } from "uuid";
import type { z } from "zod";
export async function createDeployments(
- workflow_id: string,
- version_id: string,
- machine_id: string,
- environment: DeploymentType["environment"],
+ workflow_id: string,
+ version_id: string,
+ machine_id: string,
+ environment: DeploymentType["environment"],
) {
- const { userId, orgId } = auth();
- if (!userId) throw new Error("No user id");
+ const { userId, orgId } = auth();
+ if (!userId) throw new Error("No user id");
- if (!machine_id) {
- throw new Error("No machine id provided");
- }
+ if (!machine_id) {
+ throw new Error("No machine id provided");
+ }
- // Same environment and same workflow
- const existingDeployment = await db.query.deploymentsTable.findFirst({
- where: and(
- eq(deploymentsTable.workflow_id, workflow_id),
- eq(deploymentsTable.environment, environment),
- ),
- });
+ // Same environment and same workflow
+ const existingDeployment = await db.query.deploymentsTable.findFirst({
+ where: and(
+ eq(deploymentsTable.workflow_id, workflow_id),
+ eq(deploymentsTable.environment, environment),
+ ),
+ });
- if (existingDeployment) {
- await db
- .update(deploymentsTable)
- .set({
- workflow_id,
- workflow_version_id: version_id,
- machine_id,
- org_id: orgId,
- })
- .where(eq(deploymentsTable.id, existingDeployment.id));
- } else {
- const workflow = await db.query.workflowTable.findFirst({
- where: eq(workflowTable.id, workflow_id),
- with: {
- user: {
- columns: {
- name: true,
- },
- },
- },
- });
+ if (existingDeployment) {
+ await db
+ .update(deploymentsTable)
+ .set({
+ workflow_id,
+ workflow_version_id: version_id,
+ machine_id,
+ org_id: orgId,
+ })
+ .where(eq(deploymentsTable.id, existingDeployment.id));
+ } else {
+ const workflow = await db.query.workflowTable.findFirst({
+ where: eq(workflowTable.id, workflow_id),
+ with: {
+ user: {
+ columns: {
+ name: true,
+ },
+ },
+ },
+ });
- if (!workflow) throw new Error("No workflow found");
+ if (!workflow) throw new Error("No workflow found");
- const userName = workflow.org_id
- ? await clerkClient.organizations
- .getOrganization({
- organizationId: workflow.org_id,
- })
- .then((x) => x.name)
- : workflow.user.name;
+ const userName = workflow.org_id
+ ? await clerkClient.organizations
+ .getOrganization({
+ organizationId: workflow.org_id,
+ })
+ .then((x) => x.name)
+ : workflow.user.name;
- await db.insert(deploymentsTable).values({
- user_id: userId,
- workflow_id,
- workflow_version_id: version_id,
- machine_id,
- environment,
- org_id: orgId,
- share_slug: slugify(`${userName} ${workflow.name}`),
- });
- }
- revalidatePath(`/${workflow_id}`);
- return {
- message: `Successfully created deployment for ${environment}`,
- };
+ await db.insert(deploymentsTable).values({
+ user_id: userId,
+ workflow_id,
+ workflow_version_id: version_id,
+ machine_id,
+ environment,
+ org_id: orgId,
+ share_slug: slugify(`${userName} ${workflow.name}`),
+ });
+ }
+ revalidatePath(`/${workflow_id}`);
+ return {
+ message: `Successfully created deployment for ${environment}`,
+ };
}
export async function findAllDeployments() {
- const { userId, orgId } = auth();
- if (!userId) throw new Error("No user id");
+ const { userId, orgId } = auth();
+ if (!userId) throw new Error("No user id");
- const deployments = await db.query.workflowTable.findMany({
- where: and(
- orgId
- ? eq(workflowTable.org_id, orgId)
- : and(eq(workflowTable.user_id, userId), isNull(workflowTable.org_id)),
- ),
- columns: {
- name: true,
- },
- with: {
- deployments: {
- columns: {
- environment: true,
- },
- with: {
- version: {
- columns: {
- id: true,
- snapshot: true,
- },
- },
- },
- },
- },
- });
+ const deployments = await db.query.workflowTable.findMany({
+ where: and(
+ orgId
+ ? eq(workflowTable.org_id, orgId)
+ : and(eq(workflowTable.user_id, userId), isNull(workflowTable.org_id)),
+ ),
+ columns: {
+ name: true,
+ },
+ with: {
+ deployments: {
+ columns: {
+ environment: true,
+ },
+ with: {
+ version: {
+ columns: {
+ id: true,
+ snapshot: true,
+ },
+ },
+ },
+ },
+ },
+ });
- return deployments;
+ return deployments;
}
export async function findSharedDeployment(workflow_id: string) {
- const deploymentData = await db.query.deploymentsTable.findFirst({
- where: and(
- eq(deploymentsTable.environment, "public-share"),
- isValidUUID(workflow_id)
- ? eq(deploymentsTable.id, workflow_id)
- : eq(deploymentsTable.share_slug, workflow_id),
- ),
- with: {
- user: true,
- machine: true,
- workflow: {
- columns: {
- name: true,
- org_id: true,
- user_id: true,
- },
- },
- version: true,
- },
- });
+ const deploymentData = await db.query.deploymentsTable.findFirst({
+ where: and(
+ eq(deploymentsTable.environment, "public-share"),
+ isValidUUID(workflow_id)
+ ? eq(deploymentsTable.id, workflow_id)
+ : eq(deploymentsTable.share_slug, workflow_id),
+ ),
+ with: {
+ user: true,
+ machine: true,
+ workflow: {
+ columns: {
+ name: true,
+ org_id: true,
+ user_id: true,
+ },
+ },
+ version: true,
+ },
+ });
- return deploymentData;
+ return deploymentData;
}
export const removePublicShareDeployment = withServerPromise(
- async (deployment_id: string) => {
- const [removed] = await db
- .delete(deploymentsTable)
- .where(
- and(
- eq(deploymentsTable.environment, "public-share"),
- eq(deploymentsTable.id, deployment_id),
- ),
- ).returning();
-
+ async (deployment_id: string) => {
+ const [removed] = await db
+ .delete(deploymentsTable)
+ .where(
+ and(
+ eq(deploymentsTable.environment, "public-share"),
+ eq(deploymentsTable.id, deployment_id),
+ ),
+ )
+ .returning();
+
// revalidatePath(
// `/workflows/${removed.workflow_id}`
// )
- },
+ },
);
export const cloneWorkflow = withServerPromise(
- async (deployment_id: string) => {
- const deployment = await db.query.deploymentsTable.findFirst({
- where: and(
- eq(deploymentsTable.environment, "public-share"),
- eq(deploymentsTable.id, deployment_id),
- ),
- with: {
- version: true,
- workflow: true,
- },
- });
+ async (deployment_id: string) => {
+ const deployment = await db.query.deploymentsTable.findFirst({
+ where: and(
+ eq(deploymentsTable.environment, "public-share"),
+ eq(deploymentsTable.id, deployment_id),
+ ),
+ with: {
+ version: true,
+ workflow: true,
+ },
+ });
- if (!deployment) throw new Error("No deployment found");
+ if (!deployment) throw new Error("No deployment found");
- const { userId, orgId } = auth();
+ const { userId, orgId } = auth();
- if (!userId) throw new Error("No user id");
+ if (!userId) throw new Error("No user id");
- await createNewWorkflow({
- user_id: userId,
- org_id: orgId,
- workflow_name: `${deployment.workflow.name} (Cloned)`,
- workflowData: {
- workflow: deployment.version.workflow,
- workflow_api: deployment?.version.workflow_api,
- snapshot: deployment?.version.snapshot,
- },
- });
+ await createNewWorkflow({
+ user_id: userId,
+ org_id: orgId,
+ workflow_name: `${deployment.workflow.name} (Cloned)`,
+ workflowData: {
+ workflow: deployment.version.workflow,
+ workflow_api: deployment?.version.workflow_api,
+ snapshot: deployment?.version.snapshot,
+ },
+ });
- redirect(`/workflows/${deployment.workflow.id}`);
+ redirect(`/workflows/${deployment.workflow.id}`);
- return {
- message: "Successfully cloned workflow",
- };
- },
+ return {
+ message: "Successfully cloned workflow",
+ };
+ },
);
export const cloneMachine = withServerPromise(async (deployment_id: string) => {
- const deployment = await db.query.deploymentsTable.findFirst({
- where: and(
- eq(deploymentsTable.environment, "public-share"),
- eq(deploymentsTable.id, deployment_id),
- ),
- with: {
- machine: true,
- },
- });
+ const deployment = await db.query.deploymentsTable.findFirst({
+ where: and(
+ eq(deploymentsTable.environment, "public-share"),
+ eq(deploymentsTable.id, deployment_id),
+ ),
+ with: {
+ machine: true,
+ },
+ });
- if (!deployment) throw new Error("No deployment found");
- if (deployment.machine.type !== "comfy-deploy-serverless")
- throw new Error("Can only clone comfy-deploy-serverlesss");
+ if (!deployment) throw new Error("No deployment found");
+ if (deployment.machine.type !== "comfy-deploy-serverless")
+ throw new Error("Can only clone comfy-deploy-serverlesss");
- const { userId, orgId } = auth();
+ const { userId, orgId } = auth();
- if (!userId) throw new Error("No user id");
+ if (!userId) throw new Error("No user id");
- await addCustomMachine({
- gpu: deployment.machine.gpu,
- models: deployment.machine.models,
- snapshot: deployment.machine.snapshot,
- name: `${deployment.machine.name} (Cloned)`,
- type: "comfy-deploy-serverless",
- });
+ await addCustomMachine({
+ gpu: deployment.machine.gpu,
+ models: deployment.machine.models,
+ snapshot: deployment.machine.snapshot,
+ name: `${deployment.machine.name} (Cloned)`,
+ type: "comfy-deploy-serverless",
+ });
- return {
- message: "Successfully cloned workflow",
- };
+ return {
+ message: "Successfully cloned workflow",
+ };
});
export async function findUserShareDeployment(share_id: string) {
- const { userId, orgId } = auth();
+ const { userId, orgId } = auth();
- if (!userId) throw new Error("No user id");
+ if (!userId) throw new Error("No user id");
- const [deployment] = await db
- .select()
- .from(deploymentsTable)
- .where(
- and(
- isValidUUID(share_id)
- ? eq(deploymentsTable.id, share_id)
- : eq(deploymentsTable.share_slug, share_id),
- eq(deploymentsTable.environment, "public-share"),
- orgId
- ? eq(deploymentsTable.org_id, orgId)
- : and(
- eq(deploymentsTable.user_id, userId),
- isNull(deploymentsTable.org_id),
- ),
- ),
- );
+ const [deployment] = await db
+ .select()
+ .from(deploymentsTable)
+ .where(
+ and(
+ isValidUUID(share_id)
+ ? eq(deploymentsTable.id, share_id)
+ : eq(deploymentsTable.share_slug, share_id),
+ eq(deploymentsTable.environment, "public-share"),
+ orgId
+ ? eq(deploymentsTable.org_id, orgId)
+ : and(
+ eq(deploymentsTable.user_id, userId),
+ isNull(deploymentsTable.org_id),
+ ),
+ ),
+ );
- if (!deployment) throw new Error("No deployment found");
+ if (!deployment) throw new Error("No deployment found");
- return deployment;
+ return deployment;
}
export const updateSharePageInfo = withServerPromise(
- async ({
- id,
- ...data
- }: z.infer & {
- id: string;
- }) => {
- const { userId } = auth();
- if (!userId) return { error: "No user id" };
+ async ({
+ id,
+ ...data
+ }: z.infer & {
+ id: string;
+ }) => {
+ const { userId } = auth();
+ if (!userId) return { error: "No user id" };
- console.log(data);
+ console.log(data);
- const [deployment] = await db
- .update(deploymentsTable)
- .set(data)
- .where(
- and(
- eq(deploymentsTable.environment, "public-share"),
- eq(deploymentsTable.id, id),
- ),
- )
- .returning();
+ const [deployment] = await db
+ .update(deploymentsTable)
+ .set(data)
+ .where(
+ and(
+ eq(deploymentsTable.environment, "public-share"),
+ eq(deploymentsTable.id, id),
+ ),
+ )
+ .returning();
- return { message: "Info Updated" };
- },
+ return { message: "Info Updated" };
+ },
);
diff --git a/web/src/server/editWorkflowOnMachine.tsx b/web/src/server/editWorkflowOnMachine.tsx
new file mode 100644
index 0000000..ff7bb5a
--- /dev/null
+++ b/web/src/server/editWorkflowOnMachine.tsx
@@ -0,0 +1,43 @@
+"use server";
+
+import { getMachineById } from "@/server/curdMachine";
+import { auth } from "@clerk/nextjs";
+import jwt from "jsonwebtoken";
+import { getOrgOrUserDisplayName } from "@/server/getOrgOrUserDisplayName";
+import { withServerPromise } from "@/server/withServerPromise";
+import "server-only";
+import { headers } from "next/headers";
+
+export const editWorkflowOnMachine = withServerPromise(
+ async (workflow_version_id: string, machine_id: string) => {
+ const { userId, orgId } = auth();
+
+ const headersList = headers();
+ const host = headersList.get("host") || "";
+ const protocol = headersList.get("x-forwarded-proto") || "";
+ const domain = `${protocol}://${host}`;
+
+ if (!userId) {
+ throw new Error("No user id");
+ }
+
+ const machine = await getMachineById(machine_id);
+
+ const expireTime = "1w";
+ const token = jwt.sign(
+ { user_id: userId, org_id: orgId },
+ process.env.JWT_SECRET!,
+ {
+ expiresIn: expireTime,
+ },
+ );
+
+ const userName = await getOrgOrUserDisplayName(orgId, userId);
+
+ return `${
+ machine.endpoint
+ }?workflow_version_id=${workflow_version_id}&auth_token=${token}&org_display=${encodeURIComponent(
+ userName,
+ )}&origin=${encodeURIComponent(domain)}`;
+ },
+);