feat: add opening directly from comfy deploy -> machines

This commit is contained in:
BennyKok 2024-01-23 09:33:53 +08:00
parent c0450b58d5
commit 52d6e07eeb
12 changed files with 808 additions and 405 deletions

View File

@ -9,6 +9,62 @@ const ext = {
init(app) { init(app) {
addButton(); 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() { registerCustomNodes() {
@ -191,7 +247,7 @@ function addButton() {
endpoint = endpoint.slice(0, -1); endpoint = endpoint.slice(0, -1);
} }
const apiRoute = endpoint + "/api/upload"; const apiRoute = endpoint + "/api/workflow";
// const userId = apiKey // const userId = apiKey
try { try {
const body = { const body = {
@ -280,14 +336,16 @@ export class InfoDialog extends ComfyDialog {
this.element.classList.add("comfy-normal-modal"); this.element.classList.add("comfy-normal-modal");
this.element.style.paddingBottom = "20px"; this.element.style.paddingBottom = "20px";
} }
button = undefined;
createButtons() { createButtons() {
return [ this.button = $el("button", {
$el("button", {
type: "button", type: "button",
textContent: "Close", textContent: "Close",
onclick: () => this.close(), onclick: () => this.close(),
}), });
]; return [this.button];
} }
close() { close() {
@ -317,6 +375,58 @@ export class InfoDialog extends ComfyDialog {
</div> </div>
`); `);
} }
loadingIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><g fill="none" stroke="#888888" stroke-linecap="round" stroke-width="2"><path stroke-dasharray="60" stroke-dashoffset="60" stroke-opacity=".3" d="M12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3Z"><animate fill="freeze" attributeName="stroke-dashoffset" dur="1.3s" values="60;0"/></path><path stroke-dasharray="15" stroke-dashoffset="15" d="M12 3C16.9706 3 21 7.02944 21 12"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.3s" values="15;0"/><animateTransform attributeName="transform" dur="1.5s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/></path></g></svg>`;
showLoading(title, message) {
this.show(`
<div style="width: 400px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
<h3 style="margin: 0px; display: flex; align-items: center; justify-content: center;">${title} ${this.loadingIcon}</h3>
<label>
${message}
</label>
</div>
`);
}
}
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 = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><g fill="none" stroke="#888888" stroke-linecap="round" stroke-width="2"><path stroke-dasharray="60" stroke-dashoffset="60" stroke-opacity=".3" d="M12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3Z"><animate fill="freeze" attributeName="stroke-dashoffset" dur="1.3s" values="60;0"/></path><path stroke-dasharray="15" stroke-dashoffset="15" d="M12 3C16.9706 3 21 7.02944 21 12"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.3s" values="15;0"/><animateTransform attributeName="transform" dur="1.5s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/></path></g></svg>`;
showLoading(title, message) {
this.show(`
<div style="width: 400px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
<h3 style="margin: 0px; display: flex; align-items: center; justify-content: center; gap: 4px;">${title} ${this.loadingIcon}</h3>
</div>
`);
}
} }
export class InputDialog extends InfoDialog { export class InputDialog extends InfoDialog {
@ -444,9 +554,15 @@ export class ConfirmDialog extends InfoDialog {
} }
export const inputDialog = new InputDialog(); export const inputDialog = new InputDialog();
export const loadingDialog = new LoadingDialog();
export const infoDialog = new InfoDialog(); export const infoDialog = new InfoDialog();
export const confirmDialog = new ConfirmDialog(); 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) { function getData(environment) {
const deployOption = const deployOption =
environment || localStorage.getItem("comfy_deploy_env") || "cloud"; 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 { export class ConfigDialog extends ComfyDialog {
container = null; container = null;
poll = null; poll = null;
@ -527,15 +654,12 @@ export class ConfigDialog extends ComfyDialog {
const endpoint = this.container.querySelector("#endpoint").value; const endpoint = this.container.querySelector("#endpoint").value;
const apiKey = api_key ?? this.container.querySelector("#apiKey").value; const apiKey = api_key ?? this.container.querySelector("#apiKey").value;
const data = { saveData({
endpoint, endpoint,
apiKey, apiKey,
displayName, displayName,
}; environment: deployOption,
localStorage.setItem( });
"comfy_deploy_env_data_" + deployOption,
JSON.stringify(data),
);
this.close(); this.close();
} }

View File

@ -8,7 +8,8 @@ import { handle } from "hono/vercel";
import { app } from "../../../../routes/app"; import { app } from "../../../../routes/app";
import { registerWorkflowUploadRoute } from "@/routes/registerWorkflowUploadRoute"; import { registerWorkflowUploadRoute } from "@/routes/registerWorkflowUploadRoute";
import { registerGetAuthResponse } from "@/routes/registerGetAuthResponse"; import { registerGetAuthResponse } from "@/routes/registerGetAuthResponse";
import { registerGetWorkflowRoute } from "@/routes/registerGetWorkflow";
import { cors } from "hono/cors";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
export const maxDuration = 300; // 5 minutes 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 token = c.req.raw.headers.get("Authorization")?.split(" ")?.[1]; // Assuming token is sent as "Bearer your_token"
const userData = token ? parseJWT(token) : undefined; const userData = token ? parseJWT(token) : undefined;
if (!userData || 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 the key has expiration, this is a temporary key and not in our db, so we can skip checking
if (userData.exp === undefined) { if (userData.exp === undefined) {
const revokedKey = await isKeyRevoked(token); 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); c.set("apiKeyTokenData", userData);
@ -36,9 +44,29 @@ async function checkAuth(c: Context, next: Next) {
await 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("/run", checkAuth);
app.use("/upload-url", 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 // create run endpoint
registerCreateRunRoute(app); registerCreateRunRoute(app);
@ -47,9 +75,12 @@ registerGetOutputRoute(app);
// file upload endpoint // file upload endpoint
registerUploadRoute(app); registerUploadRoute(app);
registerWorkflowUploadRoute(app); // Anon
registerGetAuthResponse(app); registerGetAuthResponse(app);
registerWorkflowUploadRoute(app);
registerGetWorkflowRoute(app);
// The OpenAPI documentation will be available at /doc // The OpenAPI documentation will be available at /doc
app.doc("/doc", { app.doc("/doc", {
openapi: "3.0.0", openapi: "3.0.0",
@ -76,3 +107,4 @@ const handler = handle(app);
export const GET = handler; export const GET = handler;
export const POST = handler; export const POST = handler;
export const OPTIONS = handler;

View File

@ -5,6 +5,7 @@ import {
CopyWorkflowVersion, CopyWorkflowVersion,
CreateDeploymentButton, CreateDeploymentButton,
MachineSelect, MachineSelect,
OpenEditButton,
RunWorkflowButton, RunWorkflowButton,
VersionSelect, VersionSelect,
ViewWorkflowDetailsButton, ViewWorkflowDetailsButton,
@ -48,6 +49,7 @@ export default async function Page({
<CreateShareButton workflow={workflow} machines={machines} /> <CreateShareButton workflow={workflow} machines={machines} />
<CopyWorkflowVersion workflow={workflow} /> <CopyWorkflowVersion workflow={workflow} />
<ViewWorkflowDetailsButton workflow={workflow} /> <ViewWorkflowDetailsButton workflow={workflow} />
<OpenEditButton workflow={workflow} machines={machines} />
</div> </div>
<VersionDetails workflow={workflow} /> <VersionDetails workflow={workflow} />

View File

@ -1,5 +1,5 @@
import { setInitialUserData } from "../../../lib/setInitialUserData"; import { setInitialUserData } from "../../../lib/setInitialUserData";
import { getAllUserWorkflow } from "../../../server/getAllUserWorkflow"; import { getAllUserWorkflow } from "../../../server/crudWorkflow";
import { WorkflowList } from "@/components/WorkflowList"; import { WorkflowList } from "@/components/WorkflowList";
import { db } from "@/db/db"; import { db } from "@/db/db";
import { usersTable } from "@/db/schema"; import { usersTable } from "@/db/schema";

View File

@ -41,7 +41,14 @@ import { checkStatus, createRun } from "@/server/createRun";
import { createDeployments } from "@/server/curdDeploments"; import { createDeployments } from "@/server/curdDeploments";
import type { getMachines } from "@/server/curdMachine"; import type { getMachines } from "@/server/curdMachine";
import type { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion"; 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 { parseAsInteger, useQueryState } from "next-usequerystate";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
@ -51,6 +58,8 @@ import { create } from "zustand";
import { workflowVersionInputsToZod } from "../lib/workflowVersionInputsToZod"; import { workflowVersionInputsToZod } from "../lib/workflowVersionInputsToZod";
import { callServerPromise } from "./callServerPromise"; import { callServerPromise } from "./callServerPromise";
import fetcher from "./fetcher"; import fetcher from "./fetcher";
import { ButtonAction } from "@/components/ButtonActionLoader";
import { editWorkflowOnMachine } from "@/server/editWorkflowOnMachine";
export function VersionSelect({ export function VersionSelect({
workflow, workflow,
@ -219,7 +228,7 @@ export function RunWorkflowButton({
const schema = useMemo(() => { const schema = useMemo(() => {
const workflow_version = getWorkflowVersionFromVersionIndex( const workflow_version = getWorkflowVersionFromVersionIndex(
workflow, workflow,
version version,
); );
if (!workflow_version) return null; if (!workflow_version) return null;
@ -233,7 +242,7 @@ export function RunWorkflowButton({
const val = Object.keys(values).length > 0 ? values : undefined; const val = Object.keys(values).length > 0 ? values : undefined;
const workflow_version_id = workflow?.versions.find( const workflow_version_id = workflow?.versions.find(
(x) => x.version === version (x) => x.version === version,
)?.id; )?.id;
console.log(workflow_version_id); console.log(workflow_version_id);
if (!workflow_version_id) return; if (!workflow_version_id) return;
@ -248,7 +257,7 @@ export function RunWorkflowButton({
machine_id: machine, machine_id: machine,
inputs: val, inputs: val,
runOrigin: "manual", runOrigin: "manual",
}) }),
); );
// console.log(res.json()); // console.log(res.json());
setIsLoading(false); setIsLoading(false);
@ -317,7 +326,7 @@ export function CreateDeploymentButton({
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const workflow_version_id = workflow?.versions.find( const workflow_version_id = workflow?.versions.find(
(x) => x.version === version (x) => x.version === version,
)?.id; )?.id;
return ( return (
<DropdownMenu> <DropdownMenu>
@ -337,8 +346,8 @@ export function CreateDeploymentButton({
workflow.id, workflow.id,
workflow_version_id, workflow_version_id,
machine, machine,
"production" "production",
) ),
); );
setIsLoading(false); setIsLoading(false);
}} }}
@ -355,8 +364,8 @@ export function CreateDeploymentButton({
workflow.id, workflow.id,
workflow_version_id, workflow_version_id,
machine, machine,
"staging" "staging",
) ),
); );
setIsLoading(false); setIsLoading(false);
}} }}
@ -368,6 +377,49 @@ export function CreateDeploymentButton({
); );
} }
export function OpenEditButton({
workflow,
machines,
}: {
workflow: Awaited<ReturnType<typeof findFirstTableWithVersion>>;
machines: Awaited<ReturnType<typeof getMachines>>;
}) {
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 && (
<Button
className="gap-2"
onClick={async () => {
setIsLoading(true);
const url = await callServerPromise(
editWorkflowOnMachine(workflow_version_id, machine),
);
if (url && typeof url !== "object") {
window.open(url, "_blank");
} else if (url && typeof url === "object" && url.error) {
console.error(url.error);
}
setIsLoading(false);
}}
// asChild
variant="outline"
>
Edit {isLoading ? <LoadingIcon /> : <Edit size={14} />}
</Button>
)
);
}
export function CopyWorkflowVersion({ export function CopyWorkflowVersion({
workflow, workflow,
}: { }: {
@ -378,7 +430,7 @@ export function CopyWorkflowVersion({
...parseAsInteger, ...parseAsInteger,
}); });
const workflow_version = workflow?.versions.find( const workflow_version = workflow?.versions.find(
(x) => x.version === version (x) => x.version === version,
); );
return ( return (
<DropdownMenu> <DropdownMenu>
@ -402,7 +454,7 @@ export function CopyWorkflowVersion({
}); });
navigator.clipboard.writeText( navigator.clipboard.writeText(
JSON.stringify(workflow_version?.workflow) JSON.stringify(workflow_version?.workflow),
); );
toast("Copied to clipboard"); toast("Copied to clipboard");
}} }}
@ -412,7 +464,7 @@ export function CopyWorkflowVersion({
<DropdownMenuItem <DropdownMenuItem
onClick={async () => { onClick={async () => {
navigator.clipboard.writeText( navigator.clipboard.writeText(
JSON.stringify(workflow_version?.workflow_api) JSON.stringify(workflow_version?.workflow_api),
); );
toast("Copied to clipboard"); toast("Copied to clipboard");
}} }}
@ -426,7 +478,7 @@ export function CopyWorkflowVersion({
export function getWorkflowVersionFromVersionIndex( export function getWorkflowVersionFromVersionIndex(
workflow: Awaited<ReturnType<typeof findFirstTableWithVersion>>, workflow: Awaited<ReturnType<typeof findFirstTableWithVersion>>,
version: number version: number,
) { ) {
const workflow_version = workflow?.versions.find((x) => x.version == version); const workflow_version = workflow?.versions.find((x) => x.version == version);
@ -452,7 +504,7 @@ export function ViewWorkflowDetailsButton({
isLoading: isNodesIndexLoading, isLoading: isNodesIndexLoading,
} = useSWR( } = useSWR(
"https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/extension-node-map.json", "https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/extension-node-map.json",
fetcher fetcher,
); );
const groupedByAuxName = useMemo(() => { const groupedByAuxName = useMemo(() => {
@ -462,7 +514,7 @@ export function ViewWorkflowDetailsButton({
const workflow_version = getWorkflowVersionFromVersionIndex( const workflow_version = getWorkflowVersionFromVersionIndex(
workflow, workflow,
version version,
); );
const api = workflow_version?.workflow_api; const api = workflow_version?.workflow_api;
@ -473,7 +525,7 @@ export function ViewWorkflowDetailsButton({
.map(([_, value]) => { .map(([_, value]) => {
const classType = value.class_type; const classType = value.class_type;
const classTypeData = Object.entries(data).find(([_, nodeArray]) => const classTypeData = Object.entries(data).find(([_, nodeArray]) =>
nodeArray[0].includes(classType) nodeArray[0].includes(classType),
); );
return classTypeData ? { node: value, classTypeData } : null; return classTypeData ? { node: value, classTypeData } : null;
}) })
@ -503,7 +555,7 @@ export function ViewWorkflowDetailsButton({
node: z.infer<typeof workflowAPINodeType>[]; node: z.infer<typeof workflowAPINodeType>[];
url: string; url: string;
} }
> >,
); );
// console.log(groupedByAuxName); // console.log(groupedByAuxName);
@ -544,7 +596,8 @@ export function ViewWorkflowDetailsButton({
<a <a
href={group.url} href={group.url}
target="_blank" target="_blank"
className="hover:underline" rel="noreferrer" className="hover:underline"
rel="noreferrer"
> >
{key} {key}
<ExternalLink <ExternalLink

View File

@ -9,7 +9,7 @@ import {
timestamp, timestamp,
uuid, uuid,
} from "drizzle-orm/pg-core"; } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import { z } from "zod"; import { z } from "zod";
export const dbSchema = pgSchema("comfyui_deploy"); export const dbSchema = pgSchema("comfyui_deploy");
@ -35,6 +35,8 @@ export const workflowTable = dbSchema.table("workflows", {
updated_at: timestamp("updated_at").defaultNow().notNull(), updated_at: timestamp("updated_at").defaultNow().notNull(),
}); });
export const workflowSchema = createSelectSchema(workflowTable);
export const workflowRelations = relations(workflowTable, ({ many, one }) => ({ export const workflowRelations = relations(workflowTable, ({ many, one }) => ({
user: one(usersTable, { user: one(usersTable, {
fields: [workflowTable.user_id], fields: [workflowTable.user_id],
@ -79,6 +81,7 @@ export const workflowVersionTable = dbSchema.table("workflow_versions", {
created_at: timestamp("created_at").defaultNow().notNull(), created_at: timestamp("created_at").defaultNow().notNull(),
updated_at: timestamp("updated_at").defaultNow().notNull(), updated_at: timestamp("updated_at").defaultNow().notNull(),
}); });
export const workflowVersionSchema = createSelectSchema(workflowVersionTable);
export const workflowVersionRelations = relations( export const workflowVersionRelations = relations(
workflowVersionTable, workflowVersionTable,

View File

@ -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,
},
);
}
});
};

View File

@ -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 type { App } from "@/routes/app";
import { authError } from "@/routes/authError"; import { authError } from "@/routes/authError";
import { import {
@ -6,10 +12,11 @@ import {
createNewWorkflowVersion, createNewWorkflowVersion,
} from "@/server/createNewWorkflow"; } from "@/server/createNewWorkflow";
import { z, createRoute } from "@hono/zod-openapi"; import { z, createRoute } from "@hono/zod-openapi";
import { and, eq } from "drizzle-orm";
const route = createRoute({ const route = createRoute({
method: "post", method: "post",
path: "/upload-workflow", path: "/workflow",
tags: ["comfyui"], tags: ["comfyui"],
summary: "Upload workflow from ComfyUI", summary: "Upload workflow from ComfyUI",
description: description:
@ -106,6 +113,30 @@ export const registerWorkflowUploadRoute = (app: App) => {
workflow_id = _workflow_id; workflow_id = _workflow_id;
version = _version; version = _version;
} else if (workflow_id) { } 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 // Case 2 update workflow
const { version: _version } = await createNewWorkflowVersion({ const { version: _version } = await createNewWorkflowVersion({
workflow_id: workflow_id, workflow_id: workflow_id,

View File

@ -4,8 +4,10 @@ import {
workflowTable, workflowTable,
workflowVersionTable, workflowVersionTable,
} from "@/db/schema"; } from "@/db/schema";
import { APIKeyUserType } from "@/server/APIKeyBodyRequest";
import { auth } from "@clerk/nextjs"; import { auth } from "@clerk/nextjs";
import { and, desc, eq, isNull } from "drizzle-orm"; import { and, desc, eq, isNull } from "drizzle-orm";
import { redirect } from "next/navigation";
export async function getAllUserWorkflow() { export async function getAllUserWorkflow() {
const { userId, orgId } = await auth(); const { userId, orgId } = await auth();
@ -51,3 +53,29 @@ export async function getAllUserWorkflow() {
return workflow; 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),
});
}

View File

@ -151,7 +151,8 @@ export const removePublicShareDeployment = withServerPromise(
eq(deploymentsTable.environment, "public-share"), eq(deploymentsTable.environment, "public-share"),
eq(deploymentsTable.id, deployment_id), eq(deploymentsTable.id, deployment_id),
), ),
).returning(); )
.returning();
// revalidatePath( // revalidatePath(
// `/workflows/${removed.workflow_id}` // `/workflows/${removed.workflow_id}`

View File

@ -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)}`;
},
);