diff --git a/apps/api/package.json b/apps/api/package.json index 49f6c71a..9468d629 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -23,7 +23,6 @@ "generate:master-key": "node scripts/generate-master-key.js" }, "devDependencies": { - "@cloudflare/puppeteer": "^1.0.2", "@cloudflare/vitest-pool-workers": "^0.8.58", "@cloudflare/workers-types": "^4.20250726.0", "@eslint/js": "^9.26.0", diff --git a/apps/api/src/cron.ts b/apps/api/src/cron.ts index a0314066..458af3b4 100644 --- a/apps/api/src/cron.ts +++ b/apps/api/src/cron.ts @@ -73,7 +73,6 @@ async function executeWorkflow( userId: "cron_trigger", organizationId: workflowInfo.organizationId, status: "executing" as ExecutionStatusType, - visibility: "private", nodeExecutions, createdAt: new Date(), updatedAt: new Date(), diff --git a/apps/api/src/db/queries.ts b/apps/api/src/db/queries.ts index cdaf3e20..a5d098bc 100644 --- a/apps/api/src/db/queries.ts +++ b/apps/api/src/db/queries.ts @@ -86,7 +86,6 @@ export type SaveExecutionRecord = { organizationId: string; status: ExecutionStatusType; nodeExecutions: NodeExecution[]; - visibility: "public" | "private"; error?: string; createdAt?: Date; updatedAt?: Date; @@ -481,7 +480,6 @@ export async function saveExecution( status: record.status as WorkflowExecutionStatus, nodeExecutions, error: record.error, - visibility: record.visibility, startedAt: record.startedAt, endedAt: record.endedAt, }; @@ -914,25 +912,6 @@ export async function listExecutions( return results.map((item) => item.executions); } -/** - * Get a public execution by ID - * - * @param db Database instance - * @param id Execution ID - * @returns Execution record or undefined if not found or not public - */ -export async function getPublicExecution( - db: ReturnType, - id: string -): Promise { - const [execution] = await db - .select() - .from(executions) - .where(and(eq(executions.id, id), eq(executions.visibility, "public"))); - - return execution; -} - /** * Get workflow names by their IDs * @@ -977,113 +956,6 @@ export async function getWorkflowName( return workflow?.name; } -/** - * Update execution visibility to public - * - * @param db Database instance - * @param executionId Execution ID - * @param organizationId Organization ID - * @returns The updated execution record - */ -export async function updateExecutionToPublic( - db: ReturnType, - executionId: string, - organizationId: string -): Promise { - const [execution] = await db - .update(executions) - .set({ visibility: "public", updatedAt: new Date() }) - .where( - and( - eq(executions.id, executionId), - eq(executions.organizationId, organizationId) - ) - ) - .returning(); - - return execution; -} - -/** - * Update execution visibility to private - * - * @param db Database instance - * @param executionId Execution ID - * @param organizationId Organization ID - * @returns The updated execution record - */ -export async function updateExecutionToPrivate( - db: ReturnType, - executionId: string, - organizationId: string -): Promise { - const [execution] = await db - .update(executions) - .set({ visibility: "private", updatedAt: new Date() }) - .where( - and( - eq(executions.id, executionId), - eq(executions.organizationId, organizationId) - ) - ) - .returning(); - - return execution; -} - -/** - * Update execution OG image generation status - * - * @param db Database instance - * @param executionId Execution ID - * @returns The updated execution record - */ -export async function updateExecutionOgImageStatus( - db: ReturnType, - executionId: string -): Promise { - const [execution] = await db - .update(executions) - .set({ ogImageGenerated: true, updatedAt: new Date() }) - .where(eq(executions.id, executionId)) - .returning(); - - return execution; -} - -/** - * Get execution with visibility check - * - * @param db Database instance - * @param executionId Execution ID - * @param organizationId Organization ID - * @returns The execution record with visibility status - */ -export async function getExecutionWithVisibility( - db: ReturnType, - executionId: string, - organizationId: string -): Promise< - | { id: string; organizationId: string; ogImageGenerated: boolean | null } - | undefined -> { - const [execution] = await db - .select({ - id: executions.id, - organizationId: executions.organizationId, - ogImageGenerated: executions.ogImageGenerated, - }) - .from(executions) - .where( - and( - eq(executions.id, executionId), - eq(executions.organizationId, organizationId) - ) - ); - - return execution; -} - /** * Get a cron trigger. * diff --git a/apps/api/src/db/schema/index.ts b/apps/api/src/db/schema/index.ts index 5f0c0062..1f308b6d 100644 --- a/apps/api/src/db/schema/index.ts +++ b/apps/api/src/db/schema/index.ts @@ -284,16 +284,10 @@ export const executions = sqliteTable( .$type() .notNull(), error: text("error"), - visibility: text("visibility", { enum: ["public", "private"] }) - .notNull() - .default("private"), startedAt: integer("started_at", { mode: "timestamp" }), endedAt: integer("ended_at", { mode: "timestamp" }), createdAt: createCreatedAt(), updatedAt: createUpdatedAt(), - ogImageGenerated: integer("og_image_generated", { - mode: "boolean", - }).default(false), }, (table) => [ index("executions_workflow_id_idx").on(table.workflowId), @@ -303,7 +297,6 @@ export const executions = sqliteTable( index("executions_created_at_idx").on(table.createdAt), index("executions_started_at_idx").on(table.startedAt), index("executions_ended_at_idx").on(table.endedAt), - index("executions_visibility_idx").on(table.visibility), // Composite indexes for common query patterns index("executions_organization_id_status_idx").on( table.organizationId, diff --git a/apps/api/src/email.ts b/apps/api/src/email.ts index e3d76546..b3139172 100644 --- a/apps/api/src/email.ts +++ b/apps/api/src/email.ts @@ -186,7 +186,6 @@ export async function handleIncomingEmail( userId: "email", organizationId: workflow.organizationId, status: ExecutionStatus.EXECUTING, - visibility: "private", nodeExecutions, createdAt: new Date(), updatedAt: new Date(), diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 3a1dc23c..c260d8f5 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -16,7 +16,6 @@ import llmsRoutes from "./routes/llms"; import objectRoutes from "./routes/objects"; import organizationRoutes from "./routes/organizations"; import profileRoutes from "./routes/profile"; -import publicRoutes from "./routes/public"; import robotsRoutes from "./routes/robots"; import secretRoutes from "./routes/secrets"; import typeRoutes from "./routes/types"; @@ -55,7 +54,6 @@ app.route("/robots.txt", robotsRoutes); app.route("/llms.txt", llmsRoutes); // Public routes -app.route("/public", publicRoutes); app.route("/types", typeRoutes); app.route("/:organizationIdOrHandle/api-keys", apiKeyRoutes); diff --git a/apps/api/src/routes/deployments.ts b/apps/api/src/routes/deployments.ts index c2eb3450..56ee2214 100644 --- a/apps/api/src/routes/deployments.ts +++ b/apps/api/src/routes/deployments.ts @@ -364,7 +364,6 @@ deploymentRoutes.post( userId, organizationId, status: executingStatus, - visibility: "private", nodeExecutions, createdAt: new Date(), updatedAt: new Date(), diff --git a/apps/api/src/routes/executions.ts b/apps/api/src/routes/executions.ts index ef3c2ce9..512d0b67 100644 --- a/apps/api/src/routes/executions.ts +++ b/apps/api/src/routes/executions.ts @@ -2,7 +2,6 @@ import { GetExecutionResponse, ListExecutionsRequest, ListExecutionsResponse, - UpdateExecutionVisibilityResponse, WorkflowExecution, WorkflowExecutionStatus, } from "@dafthunk/types"; @@ -13,15 +12,10 @@ import { ApiContext } from "../context"; import { createDatabase, getExecution, - getExecutionWithVisibility, getWorkflowName, getWorkflowNames, listExecutions, - updateExecutionOgImageStatus, - updateExecutionToPrivate, - updateExecutionToPublic, } from "../db"; -import { generateExecutionOgImage } from "../utils/og-image-generator"; const executionRoutes = new Hono(); @@ -53,7 +47,6 @@ executionRoutes.get("/:id", apiKeyOrJwtMiddleware, async (c) => { status: execution.status as WorkflowExecutionStatus, nodeExecutions: executionData.nodeExecutions || [], error: execution.error || undefined, - visibility: execution.visibility, startedAt: execution.startedAt ?? executionData.startedAt, endedAt: execution.endedAt ?? executionData.endedAt, }; @@ -102,7 +95,6 @@ executionRoutes.get("/", jwtMiddleware, async (c) => { status: execution.status as WorkflowExecutionStatus, nodeExecutions: executionData.nodeExecutions || [], error: execution.error || undefined, - visibility: execution.visibility, startedAt: execution.startedAt ?? executionData.startedAt, endedAt: execution.endedAt ?? executionData.endedAt, }; @@ -112,82 +104,4 @@ executionRoutes.get("/", jwtMiddleware, async (c) => { return c.json(response); }); -executionRoutes.patch("/:id/share/public", jwtMiddleware, async (c) => { - const executionId = c.req.param("id"); - const db = createDatabase(c.env.DB); - const organizationId = c.get("organizationId")!; - - try { - const execution = await getExecutionWithVisibility( - db, - executionId, - organizationId - ); - - if (!execution) { - return c.json({ error: "Execution not found" }, 404); - } - - await updateExecutionToPublic(db, executionId, organizationId); - - if (!execution.ogImageGenerated && c.env.BROWSER) { - try { - await generateExecutionOgImage({ - env: c.env, - executionId: executionId, - organizationId: organizationId, - }); - - await updateExecutionOgImageStatus(db, executionId); - console.log( - `Execution ${executionId} updated with OG image generation status.` - ); - } catch (error) { - console.error( - `OG image generation step failed for execution ${executionId}, but execution is now public. Error: ${error}` - ); - } - } - - const response: UpdateExecutionVisibilityResponse = { - success: true, - message: "Execution set to public", - }; - return c.json(response); - } catch (error) { - console.error("Error setting execution to public:", error); - return c.json({ error: "Failed to set execution to public" }, 500); - } -}); - -executionRoutes.patch("/:id/share/private", jwtMiddleware, async (c) => { - const id = c.req.param("id"); - const db = createDatabase(c.env.DB); - - const organizationId = c.get("organizationId")!; - - try { - const execution = await getExecution(db, id, organizationId); - - if (!execution) { - return c.json({ error: "Execution not found" }, 404); - } - - // The check execution.organizationId !== orgId is implicitly handled by getExecutionById - // if it correctly filters by organizationId. If not, this check might be needed here. - // However, getExecutionById already takes orgId, so it should be fine. - - await updateExecutionToPrivate(db, id, organizationId); - - const response: UpdateExecutionVisibilityResponse = { - success: true, - message: "Execution set to private", - }; - return c.json(response); - } catch (error) { - console.error("Error setting execution to private:", error); - return c.json({ error: "Failed to set execution to private" }, 500); - } -}); - export default executionRoutes; diff --git a/apps/api/src/routes/objects.ts b/apps/api/src/routes/objects.ts index 3d5399f6..ad7f6a76 100644 --- a/apps/api/src/routes/objects.ts +++ b/apps/api/src/routes/objects.ts @@ -48,7 +48,6 @@ objectRoutes.get("/", apiKeyOrJwtMiddleware, async (c) => { const db = createDatabase(c.env.DB); const [execution] = await db .select({ - visibility: executionsTable.visibility, organizationId: executionsTable.organizationId, }) .from(executionsTable) @@ -58,17 +57,13 @@ objectRoutes.get("/", apiKeyOrJwtMiddleware, async (c) => { return c.text("Object not found or linked to invalid execution", 404); } - if (execution.visibility === "private") { - // For private executions, the execution's organization must match the requesting organization - if (execution.organizationId !== requestingOrganizationId) { - return c.text( - "Forbidden: You do not have access to this object via its execution", - 403 - ); - } + // For private executions, the execution's organization must match the requesting organization + if (execution.organizationId !== requestingOrganizationId) { + return c.text( + "Forbidden: You do not have access to this object via its execution", + 403 + ); } - // If execution is public, access is allowed for any authenticated entity (JWT user or API key). - // No further check against requestingOrganizationId is needed here for public executions. } else { // Object not linked to an execution, check its own organizationId if (metadata?.organizationId !== requestingOrganizationId) { diff --git a/apps/api/src/routes/public.ts b/apps/api/src/routes/public.ts deleted file mode 100644 index 510dd0b7..00000000 --- a/apps/api/src/routes/public.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { - GetPublicExecutionResponse, - ObjectReference, - PublicExecutionWithStructure, - Workflow, - WorkflowExecution, - WorkflowExecutionStatus, -} from "@dafthunk/types"; -import { eq } from "drizzle-orm"; -import { Hono } from "hono"; - -import { ApiContext } from "../context"; -import { createDatabase } from "../db"; -import { - executions as executionsTable, - getPublicExecution, - getWorkflow, -} from "../db"; -import { ObjectStore } from "../runtime/object-store"; - -const publicRoutes = new Hono(); - -// Public Objects Route -publicRoutes.get("/objects", async (c) => { - const objectId = c.req.query("id"); - const mimeType = c.req.query("mimeType"); - - if (!objectId || !mimeType) { - return c.text("Missing required parameters: id and mimeType", 400); - } - - try { - const objectStore = new ObjectStore(c.env.RESSOURCES); - const reference: ObjectReference = { id: objectId, mimeType }; - const result = await objectStore.readObject(reference); - - if (!result) { - return c.text("Object not found", 404); - } - - const { data, metadata } = result; - - if (!metadata?.executionId) { - return c.text("Object is not linked to an execution", 404); - } - - const db = createDatabase(c.env.DB); - const [execution] = await db - .select({ - visibility: executionsTable.visibility, - organizationId: executionsTable.organizationId, - }) - .from(executionsTable) - .where(eq(executionsTable.id, metadata.executionId)); - - if (!execution) { - return c.text("Object not found or linked to invalid execution", 404); - } - - if (execution.visibility !== "public") { - return c.text( - "Forbidden: You do not have access to this object via its execution", - 403 - ); - } - - return c.body(data, { - headers: { - "content-type": mimeType, - "Cache-Control": "public, max-age=31536000", - }, - }); - } catch (error) { - console.error("Object retrieval error:", error); - if (error instanceof Error && error.message.startsWith("Forbidden")) { - return c.text(error.message, 403); - } - return c.text("Object not found or error retrieving object", 404); - } -}); - -// Public Executions Route -publicRoutes.get("/executions/:id", async (c) => { - const id = c.req.param("id"); - const db = createDatabase(c.env.DB); - - try { - const executionRecord = await getPublicExecution(db, id); - - if (!executionRecord) { - return c.json({ error: "Execution not found or not public" }, 404); - } - - let workflowNodes: Workflow["nodes"] = []; - let workflowEdges: Workflow["edges"] = []; - let workflowName = "Unknown Workflow"; - - const workflowRecord = await getWorkflow( - db, - executionRecord.workflowId, - executionRecord.organizationId - ); - - if (workflowRecord) { - const workflowDataFromDb = workflowRecord.data as Workflow; - workflowNodes = workflowDataFromDb.nodes || []; - workflowEdges = workflowDataFromDb.edges || []; - workflowName = workflowRecord.name || workflowName; - } - - const executionData = executionRecord.data as WorkflowExecution; - const responseExecution: PublicExecutionWithStructure = { - id: executionRecord.id, - workflowId: executionRecord.workflowId, - workflowName: workflowName, - deploymentId: executionRecord.deploymentId ?? undefined, - status: executionRecord.status as WorkflowExecutionStatus, - nodeExecutions: executionData.nodeExecutions || [], - error: executionRecord.error || undefined, - visibility: executionRecord.visibility, - startedAt: executionRecord.startedAt ?? executionData.startedAt, - endedAt: executionRecord.endedAt ?? executionData.endedAt, - nodes: workflowNodes, - edges: workflowEdges, - }; - - const response: GetPublicExecutionResponse = { - execution: responseExecution, - }; - return c.json(response); - } catch (error) { - console.error("Error retrieving public execution:", error); - return c.json({ error: "Failed to retrieve public execution" }, 500); - } -}); - -// Public Images Route -publicRoutes.get("/images/:key", async (c) => { - const key = c.req.param("key"); - - try { - const object = await c.env.RESSOURCES.get("images/" + key); - const mimeType = object?.httpMetadata?.contentType; - - if (!object || !mimeType) { - return c.text("Image not found", 404); - } - - const image = new Uint8Array(await object.arrayBuffer()); - - return c.body(image, { - headers: { - "content-type": mimeType, - "Cache-Control": "public, max-age=31536000", - }, - }); - } catch (error) { - console.error("Object retrieval error:", error); - if (error instanceof Error && error.message.startsWith("Forbidden")) { - return c.text(error.message, 403); - } - return c.text("Object not found or error retrieving object", 404); - } -}); - -export default publicRoutes; diff --git a/apps/api/src/routes/workflows.ts b/apps/api/src/routes/workflows.ts index 5e0a38b9..e5683536 100644 --- a/apps/api/src/routes/workflows.ts +++ b/apps/api/src/routes/workflows.ts @@ -681,7 +681,6 @@ workflowRoutes.post( userId, organizationId: organizationId, status: ExecutionStatus.EXECUTING, - visibility: "private", nodeExecutions, createdAt: new Date(), updatedAt: new Date(), @@ -741,7 +740,6 @@ workflowRoutes.post( status: ExecutionStatus.CANCELLED, nodeExecutions: execution.data.nodeExecutions || [], error: execution.error ?? "Execution cancelled by user", - visibility: execution.visibility, updatedAt: now, endedAt: now, startedAt: execution.startedAt ?? undefined, @@ -767,7 +765,6 @@ workflowRoutes.post( status: ExecutionStatus.CANCELLED, nodeExecutions: execution.data.nodeExecutions || [], error: execution.error ?? "Execution cancelled by user", - visibility: execution.visibility, updatedAt: now, endedAt: now, startedAt: execution.startedAt ?? undefined, diff --git a/apps/api/src/runtime/runtime.ts b/apps/api/src/runtime/runtime.ts index 6de4bbe8..bb43f6c7 100644 --- a/apps/api/src/runtime/runtime.ts +++ b/apps/api/src/runtime/runtime.ts @@ -204,7 +204,6 @@ export class Runtime extends WorkflowEntrypoint { deploymentId: event.payload.deploymentId, status: "submitted", nodeExecutions: [], - visibility: "private", startedAt: undefined, endedAt: undefined, } as WorkflowExecution; @@ -1244,7 +1243,6 @@ export class Runtime extends WorkflowEntrypoint { status: executionStatus as ExecutionStatusType, nodeExecutions: nodeExecutionList, error: errorMsg, - visibility: "private", updatedAt: new Date(), startedAt, endedAt, @@ -1260,7 +1258,6 @@ export class Runtime extends WorkflowEntrypoint { status: executionStatus, nodeExecutions: nodeExecutionList, error: errorMsg, - visibility: "private", startedAt, endedAt, }; diff --git a/apps/api/src/utils/og-image-generator.ts b/apps/api/src/utils/og-image-generator.ts deleted file mode 100644 index 2de7f828..00000000 --- a/apps/api/src/utils/og-image-generator.ts +++ /dev/null @@ -1,66 +0,0 @@ -import puppeteer from "@cloudflare/puppeteer"; - -import { type Bindings } from "../context"; - -const OG_IMAGE_WIDTH = 1200; -const OG_IMAGE_HEIGHT = 630; - -interface OgImageGeneratorParams { - env: Pick; - executionId: string; - organizationId: string; - sharedExecutionPath?: string; -} - -/** - * Generates an OG image for a given execution, saves it to R2, and returns the R2 key. - * Throws an error if any step fails. - */ -export async function generateExecutionOgImage({ - env, - executionId, - organizationId, - sharedExecutionPath = "/public/executions/", -}: OgImageGeneratorParams): Promise { - let browser = null; - console.log( - `[ogImageGenerator] Starting OG image generation for execution ID: ${executionId}` - ); - - try { - browser = await puppeteer.launch(env.BROWSER as any); - const page = await browser.newPage(); - - const targetUrl = `${env.WEB_HOST}${sharedExecutionPath}${executionId}?fullscreen&theme=light`; - - await page.setViewport({ width: OG_IMAGE_WIDTH, height: OG_IMAGE_HEIGHT }); - await page.goto(targetUrl, { waitUntil: "networkidle0" }); - - const screenshotBuffer = await page.screenshot({ - type: "jpeg", - quality: 80, - }); - - const key = `images/og-execution-${executionId}.jpeg`; - await env.RESSOURCES.put(key, screenshotBuffer, { - httpMetadata: { - contentType: "image/jpeg", - cacheControl: "public, max-age=31536000", - }, - customMetadata: { - executionId, - organizationId, - }, - }); - } catch (error) { - console.error( - `[ogImageGenerator] Error during OG image generation for ${executionId}: ${error}` - ); - // Re-throw the error to be handled by the caller - throw error; - } finally { - if (browser) { - await browser.close(); - } - } -} diff --git a/apps/api/test/setup.ts b/apps/api/test/setup.ts index 8ce2b6eb..6fa911b9 100644 --- a/apps/api/test/setup.ts +++ b/apps/api/test/setup.ts @@ -2023,7 +2023,6 @@ vi.mock("@turf/turf", () => ({ // LineString-Polygon intersection if (type1 === "LineString" && type2 === "Polygon") { const lineCoords = coords1; - const _polygonCoords = coords2[0]; // Outer ring // Check if any line segment intersects with polygon for (let i = 0; i < lineCoords.length - 1; i++) { diff --git a/apps/web/public/demo/audio.mp3 b/apps/web/public/demo/audio.mp3 new file mode 100644 index 00000000..29c8495a Binary files /dev/null and b/apps/web/public/demo/audio.mp3 differ diff --git a/apps/web/public/demo/image1.jpg b/apps/web/public/demo/image1.jpg new file mode 100644 index 00000000..ebfdab18 Binary files /dev/null and b/apps/web/public/demo/image1.jpg differ diff --git a/apps/web/public/demo/image2.png b/apps/web/public/demo/image2.png new file mode 100644 index 00000000..1089e2d6 Binary files /dev/null and b/apps/web/public/demo/image2.png differ diff --git a/apps/web/src/components/demo-execution.tsx b/apps/web/src/components/demo-execution.tsx new file mode 100644 index 00000000..b9bf8839 --- /dev/null +++ b/apps/web/src/components/demo-execution.tsx @@ -0,0 +1,54 @@ +import { memo, useMemo } from "react"; + +import { WorkflowBuilder } from "@/components/workflow/workflow-builder"; +import { + createDemoObjectUrl, + demoEdges, + demoExecution, + demoNodes, +} from "@/data/homepage-demo"; + +export const DemoExecution = memo(() => { + // Pre-populate outputs from execution data since readonly mode skips dynamic updates + const processedNodes = useMemo(() => { + const execMap = new Map( + demoExecution.nodeExecutions.map((n) => [n.nodeId, n]) + ); + return demoNodes.map((node) => { + const nodeExec = execMap.get(node.id); + if (!nodeExec) return node; + + const updatedOutputs = node.data.outputs.map((output) => ({ + ...output, + value: nodeExec.outputs?.[output.id] || nodeExec.outputs?.[output.name], + })); + + return { + ...node, + data: { + ...node.data, + outputs: updatedOutputs, + executionState: nodeExec.status || "completed", + error: nodeExec.error, + }, + }; + }); + }, []); + + return ( +
+ +
+ ); +}); + +DemoExecution.displayName = "DemoExecution"; diff --git a/apps/web/src/components/executions/execution-info-card.tsx b/apps/web/src/components/executions/execution-info-card.tsx index 047a49f4..2140d22c 100644 --- a/apps/web/src/components/executions/execution-info-card.tsx +++ b/apps/web/src/components/executions/execution-info-card.tsx @@ -2,8 +2,6 @@ import { WorkflowExecutionStatus } from "@dafthunk/types"; import { format } from "date-fns"; import AlertCircle from "lucide-react/icons/alert-circle"; import Clock from "lucide-react/icons/clock"; -import Eye from "lucide-react/icons/eye"; -import EyeOff from "lucide-react/icons/eye-off"; import IdCard from "lucide-react/icons/id-card"; import Workflow from "lucide-react/icons/workflow"; import { Link } from "react-router"; @@ -16,15 +14,12 @@ import { CardTitle, } from "@/components/ui/card"; import { useOrgUrl } from "@/hooks/use-org-url"; -import { cn } from "@/utils/utils"; -import { Badge } from "../ui/badge"; import { ExecutionStatusBadge } from "./execution-status-badge"; interface ExecutionInfoCardProps { id: string; status: WorkflowExecutionStatus; - visibility: "public" | "private"; startedAt?: Date; endedAt?: Date; workflowId: string; @@ -38,7 +33,6 @@ interface ExecutionInfoCardProps { export function ExecutionInfoCard({ id, status, - visibility, startedAt, endedAt, workflowId, @@ -82,24 +76,6 @@ export function ExecutionInfoCard({ {description}
- {visibility && ( - - {visibility === "public" ? ( - - ) : ( - - )} - {visibility} - - )}
diff --git a/apps/web/src/components/ui/loading-button.tsx b/apps/web/src/components/ui/loading-button.tsx deleted file mode 100644 index e0fa7411..00000000 --- a/apps/web/src/components/ui/loading-button.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { cn } from "@/utils/utils"; - -import { Button, ButtonProps } from "./button"; -import { Spinner } from "./spinner"; - -interface LoadingButtonProps extends ButtonProps { - isLoading: boolean; - icon?: React.ReactNode; -} - -export function LoadingButton({ - children, - isLoading, - icon, - ...props -}: LoadingButtonProps) { - return ( - - ); -} diff --git a/apps/web/src/data/homepage-demo.ts b/apps/web/src/data/homepage-demo.ts new file mode 100644 index 00000000..f4e9bc36 --- /dev/null +++ b/apps/web/src/data/homepage-demo.ts @@ -0,0 +1,534 @@ +import type { ObjectReference, WorkflowExecution } from "@dafthunk/types"; +import type { + Edge as ReactFlowEdge, + Node as ReactFlowNode, +} from "@xyflow/react"; + +import type { WorkflowNodeType } from "@/components/workflow/workflow-types"; + +export const demoNodes: ReactFlowNode[] = [ + { + id: "canvas-doodle-1750948989536", + type: "workflowNode", + position: { x: -179.4695714341764, y: -323.6257684843936 }, + data: { + name: "Canvas Doodle", + icon: "pencil", + executionState: "completed", + inputs: [ + { + id: "value", + name: "value", + type: "image", + description: "value", + hidden: true, + value: { + id: "0197acb7-9bdf-76c8-934f-85f298c49c15", + mimeType: "image/png", + }, + }, + { + id: "width", + name: "width", + type: "number", + description: "width", + hidden: true, + }, + { + id: "height", + name: "height", + type: "number", + description: "height", + hidden: true, + }, + { + id: "strokeColor", + name: "strokeColor", + type: "string", + description: "strokeColor", + hidden: true, + }, + { + id: "strokeWidth", + name: "strokeWidth", + type: "number", + description: "strokeWidth", + hidden: true, + }, + ], + outputs: [ + { id: "image", name: "image", type: "image", description: "image" }, + ], + }, + }, + { + id: "uform-gen2-qwen-500m-1750948994683", + type: "workflowNode", + position: { x: 84.47390019675802, y: -79.01905703435875 }, + data: { + name: "UForm Gen2 Qwen 500M", + icon: "image", + executionState: "completed", + inputs: [ + { id: "image", name: "image", type: "image", description: "image" }, + { + id: "prompt", + name: "prompt", + type: "string", + description: "prompt", + hidden: true, + }, + { + id: "max_tokens", + name: "max_tokens", + type: "number", + description: "max_tokens", + hidden: true, + }, + { + id: "top_p", + name: "top_p", + type: "number", + description: "top_p", + hidden: true, + }, + { + id: "top_k", + name: "top_k", + type: "number", + description: "top_k", + hidden: true, + }, + { + id: "repetition_penalty", + name: "repetition_penalty", + type: "number", + description: "repetition_penalty", + hidden: true, + }, + ], + outputs: [ + { + id: "description", + name: "description", + type: "string", + description: "description", + }, + ], + }, + }, + { + id: "stable-diffusion-xl-base-1-0-1750949003301", + type: "workflowNode", + position: { x: 894.3937777338549, y: -455.22969227837785 }, + data: { + name: "Stable Diffusion XL Base", + icon: "image", + executionState: "completed", + inputs: [ + { id: "prompt", name: "prompt", type: "string", description: "prompt" }, + { + id: "negative_prompt", + name: "negative_prompt", + type: "string", + description: "negative_prompt", + hidden: false, + }, + { + id: "width", + name: "width", + type: "number", + description: "width", + hidden: true, + }, + { + id: "height", + name: "height", + type: "number", + description: "height", + hidden: true, + }, + { + id: "num_steps", + name: "num_steps", + type: "number", + description: "num_steps", + hidden: true, + }, + { + id: "guidance", + name: "guidance", + type: "number", + description: "guidance", + hidden: true, + }, + { + id: "seed", + name: "seed", + type: "number", + description: "seed", + hidden: true, + }, + ], + outputs: [ + { id: "image", name: "image", type: "image", description: "image" }, + ], + }, + }, + { + id: "single-variable-string-template-1750949013100", + type: "workflowNode", + position: { x: 372.2932063530624, y: -477.8462229058754 }, + data: { + name: "Single Variable String Template", + icon: "quote", + executionState: "completed", + inputs: [ + { + id: "template", + name: "template", + type: "string", + description: "template", + }, + { + id: "variable", + name: "variable", + type: "string", + description: "variable", + }, + ], + outputs: [ + { id: "result", name: "result", type: "string", description: "result" }, + ], + }, + }, + { + id: "text-area-1750949028851", + type: "workflowNode", + position: { x: 74.84288256585423, y: -519.449283356264 }, + data: { + name: "Text Area", + icon: "text", + executionState: "completed", + inputs: [ + { + id: "value", + name: "value", + type: "string", + description: "value", + hidden: true, + value: + "cinematic photo ${variable} . 35mm photograph, film, bokeh, professional, 4k, highly detailed", + }, + { + id: "placeholder", + name: "placeholder", + type: "string", + description: "placeholder", + hidden: true, + }, + { + id: "rows", + name: "rows", + type: "number", + description: "rows", + hidden: true, + }, + ], + outputs: [ + { id: "value", name: "value", type: "string", description: "value" }, + ], + }, + }, + { + id: "text-area-1750949047338", + type: "workflowNode", + position: { x: 633.9627748375746, y: -379.8193303419201 }, + data: { + name: "Text Area", + icon: "text", + executionState: "completed", + inputs: [ + { + id: "value", + name: "value", + type: "string", + description: "value", + hidden: true, + value: + "drawing, painting, crayon, sketch, graphite, impressionist, noisy, blurry, soft, deformed, ugly", + }, + { + id: "placeholder", + name: "placeholder", + type: "string", + description: "placeholder", + hidden: true, + }, + { + id: "rows", + name: "rows", + type: "number", + description: "rows", + hidden: true, + }, + ], + outputs: [ + { id: "value", name: "value", type: "string", description: "value" }, + ], + }, + }, + { + id: "melotts-1750949090680", + type: "workflowNode", + position: { x: 633.7652315068437, y: 59.99048095410136 }, + data: { + name: "MeloTTS", + icon: "mic", + executionState: "completed", + inputs: [ + { id: "prompt", name: "prompt", type: "string", description: "prompt" }, + { id: "lang", name: "lang", type: "string", description: "lang" }, + ], + outputs: [ + { id: "audio", name: "audio", type: "audio", description: "audio" }, + ], + }, + }, + { + id: "bart-large-cnn-1750949100668", + type: "workflowNode", + position: { x: 372.08548414825697, y: -36.8981771182756 }, + data: { + name: "BART Large CNN", + icon: "sparkles", + executionState: "completed", + inputs: [ + { + id: "inputText", + name: "inputText", + type: "string", + description: "inputText", + }, + { + id: "maxLength", + name: "maxLength", + type: "number", + description: "maxLength", + hidden: true, + }, + ], + outputs: [ + { + id: "summary", + name: "summary", + type: "string", + description: "summary", + }, + ], + }, + }, + { + id: "m2m100-1-2b-1750949147154", + type: "workflowNode", + position: { x: 890.7351714776828, y: -75.6100661108988 }, + data: { + name: "M2M100 1.2B", + icon: "sparkles", + executionState: "completed", + inputs: [ + { id: "text", name: "text", type: "string", description: "text" }, + { + id: "sourceLang", + name: "sourceLang", + type: "string", + description: "sourceLang", + value: "en", + }, + { + id: "targetLang", + name: "targetLang", + type: "string", + description: "targetLang", + value: "fr", + }, + ], + outputs: [ + { + id: "translatedText", + name: "translatedText", + type: "string", + description: "translatedText", + }, + ], + }, + }, +]; + +export const demoEdges: ReactFlowEdge[] = [ + { + id: "edge-1", + source: "canvas-doodle-1750948989536", + target: "uform-gen2-qwen-500m-1750948994683", + sourceHandle: "image", + targetHandle: "image", + type: "workflowEdge", + }, + { + id: "edge-2", + source: "single-variable-string-template-1750949013100", + target: "stable-diffusion-xl-base-1-0-1750949003301", + sourceHandle: "result", + targetHandle: "prompt", + type: "workflowEdge", + }, + { + id: "edge-3", + source: "uform-gen2-qwen-500m-1750948994683", + target: "single-variable-string-template-1750949013100", + sourceHandle: "description", + targetHandle: "variable", + type: "workflowEdge", + }, + { + id: "edge-4", + source: "text-area-1750949028851", + target: "single-variable-string-template-1750949013100", + sourceHandle: "value", + targetHandle: "template", + type: "workflowEdge", + }, + { + id: "edge-5", + source: "text-area-1750949047338", + target: "stable-diffusion-xl-base-1-0-1750949003301", + sourceHandle: "value", + targetHandle: "negative_prompt", + type: "workflowEdge", + }, + { + id: "edge-6", + source: "uform-gen2-qwen-500m-1750948994683", + target: "bart-large-cnn-1750949100668", + sourceHandle: "description", + targetHandle: "inputText", + type: "workflowEdge", + }, + { + id: "edge-7", + source: "bart-large-cnn-1750949100668", + target: "melotts-1750949090680", + sourceHandle: "summary", + targetHandle: "prompt", + type: "workflowEdge", + }, + { + id: "edge-8", + source: "bart-large-cnn-1750949100668", + target: "m2m100-1-2b-1750949147154", + sourceHandle: "summary", + targetHandle: "text", + type: "workflowEdge", + }, +]; + +export const demoExecution: WorkflowExecution = { + id: "61c6a061-669e-42a9-92d9-5d65413f941a", + workflowId: "0197acb1-39b1-7047-8eb7-d8288d1131f0", + status: "completed", + nodeExecutions: [ + { + nodeId: "canvas-doodle-1750948989536", + status: "completed", + outputs: { + image: { + id: "0197ad31-bb02-71b9-a5db-32fa2af51f4a", + mimeType: "image/png", + }, + }, + }, + { + nodeId: "uform-gen2-qwen-500m-1750948994683", + status: "completed", + outputs: { + description: + "A small island with a green palm tree is depicted in the center of the image, surrounded by blue water. Above the island, an orange sun with a yellow center and six rays is drawn, casting a warm glow on the scene. The image is a simple yet captivating depiction of a tropical paradise.", + }, + }, + { + nodeId: "stable-diffusion-xl-base-1-0-1750949003301", + status: "completed", + outputs: { + image: { + id: "0197ad31-f434-776a-8ee3-94fec7cbc4d4", + mimeType: "image/jpeg", + }, + }, + }, + { + nodeId: "single-variable-string-template-1750949013100", + status: "completed", + outputs: { + result: + "cinematic photo A small island with a green palm tree is depicted in the center of the image, surrounded by blue water. Above the island, an orange sun with a yellow center and six rays is drawn, casting a warm glow on the scene. The image is a simple yet captivating depiction of a tropical paradise. . 35mm photograph, film, bokeh, professional, 4k, highly detailed", + }, + }, + { + nodeId: "text-area-1750949028851", + status: "completed", + outputs: { + value: + "cinematic photo ${variable} . 35mm photograph, film, bokeh, professional, 4k, highly detailed", + }, + }, + { + nodeId: "text-area-1750949047338", + status: "completed", + outputs: { + value: + "drawing, painting, crayon, sketch, graphite, impressionist, noisy, blurry, soft, deformed, ugly", + }, + }, + { + nodeId: "melotts-1750949090680", + status: "completed", + outputs: { + audio: { + id: "0197ad31-ff39-731c-ae5d-1040b03bdc2d", + mimeType: "audio/mpeg", + }, + }, + }, + { + nodeId: "bart-large-cnn-1750949100668", + status: "completed", + outputs: { + summary: + "A small island with a green palm tree is depicted in the center of the image, surrounded by blue water. Above the island, an orange sun with a yellow center and six rays is drawn, casting a warm glow on the scene.", + }, + }, + { + nodeId: "m2m100-1-2b-1750949147154", + status: "completed", + outputs: { + translatedText: + "Une petite île avec un palmier vert est représentée au centre de l'image, entourée d'eau bleue. Au-dessus de l'île, un soleil orange avec un centre jaune et six rayons est dessiné, jetant une chaleur chaleureuse sur la scène.", + }, + }, + ], + startedAt: new Date("2025-06-26T17:03:26.000Z"), + endedAt: new Date("2025-06-26T17:03:46.000Z"), +}; + +export const createDemoObjectUrl = (ref: ObjectReference): string => { + switch (ref.id) { + case "0197ad31-bb02-71b9-a5db-32fa2af51f4a": + return "/demo/image2.png"; + case "0197ad31-f434-776a-8ee3-94fec7cbc4d4": + return "/demo/image1.jpg"; + case "0197ad31-ff39-731c-ae5d-1040b03bdc2d": + return "/demo/audio.mp3"; + default: + return "#"; + } +}; diff --git a/apps/web/src/pages/docs/api-page.tsx b/apps/web/src/pages/docs/api-page.tsx index 9328f02c..31f48ffe 100644 --- a/apps/web/src/pages/docs/api-page.tsx +++ b/apps/web/src/pages/docs/api-page.tsx @@ -160,7 +160,6 @@ export function DocsApiPage() { } } ], - "visibility": "private", "startedAt": "2024-01-15T10:30:00.000Z", "endedAt": "2024-01-15T10:31:23.000Z" } diff --git a/apps/web/src/pages/execution-detail-page.tsx b/apps/web/src/pages/execution-detail-page.tsx index e6168593..0a758768 100644 --- a/apps/web/src/pages/execution-detail-page.tsx +++ b/apps/web/src/pages/execution-detail-page.tsx @@ -1,18 +1,12 @@ import type { NodeExecution, WorkflowExecution } from "@dafthunk/types"; -import Eye from "lucide-react/icons/eye"; -import EyeOff from "lucide-react/icons/eye-off"; -import Share2 from "lucide-react/icons/share-2"; import { useEffect, useMemo, useState } from "react"; -import { Link, useParams } from "react-router"; +import { useParams } from "react-router"; import { toast } from "sonner"; -import { useAuth } from "@/components/auth-context"; import { ExecutionInfoCard } from "@/components/executions/execution-info-card"; import { InsetError } from "@/components/inset-error"; import { InsetLoading } from "@/components/inset-loading"; import { InsetLayout } from "@/components/layouts/inset-layout"; -import { Button } from "@/components/ui/button"; -import { LoadingButton } from "@/components/ui/loading-button"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { WorkflowBuilder } from "@/components/workflow/workflow-builder"; import type { @@ -22,11 +16,7 @@ import type { import { useOrgUrl } from "@/hooks/use-org-url"; import { usePageBreadcrumbs } from "@/hooks/use-page"; import { useDeploymentVersion } from "@/services/deployment-service"; -import { - setExecutionPrivate, - setExecutionPublic, - useExecution, -} from "@/services/execution-service"; +import { useExecution } from "@/services/execution-service"; import { useObjectService } from "@/services/object-service"; import { convertToReactFlowEdges, @@ -37,14 +27,12 @@ import { export function ExecutionDetailPage() { const { executionId } = useParams<{ executionId: string }>(); const { setBreadcrumbs } = usePageBreadcrumbs([]); - const { organization } = useAuth(); const { getOrgUrl } = useOrgUrl(); const { execution, executionError: executionDetailsError, isExecutionLoading: isExecutionDetailsLoading, - mutateExecution: mutateExecutionDetails, } = useExecution(executionId || null); const { createObjectUrl } = useObjectService(); @@ -96,8 +84,6 @@ export function ExecutionDetailPage() { const [reactFlowNodes, setReactFlowNodes] = useState([]); const [reactFlowEdges, setReactFlowEdges] = useState([]); - const [isVisibilityUpdating, setIsVisibilityUpdating] = useState(false); - useEffect(() => { if (executionId) { setBreadcrumbs([ @@ -182,39 +168,6 @@ export function ExecutionDetailPage() { [reactFlowEdges] ); - const handleToggleVisibility = async () => { - if (!execution || !executionId || !organization?.handle) return; - - setIsVisibilityUpdating(true); - const newVisibility = - execution.visibility === "public" ? "private" : "public"; - - try { - let success = false; - - if (newVisibility === "public") { - success = await setExecutionPublic(executionId, organization.handle); - } else { - success = await setExecutionPrivate(executionId, organization.handle); - } - - if (success) { - toast.success(`Execution successfully made ${newVisibility}.`); - mutateExecutionDetails(); - } else { - toast.error(`Failed to update visibility.`); - } - } catch (error) { - toast.error( - `Failed to update visibility: ${ - error instanceof Error ? error.message : String(error) - }` - ); - } - - setIsVisibilityUpdating(false); - }; - if (isExecutionDetailsLoading || isStructureOverallLoading) { return ; } else if (executionDetailsError) { @@ -245,45 +198,12 @@ export function ExecutionDetailPage() { Status Visualization - {execution && ( -
- {execution.visibility === "public" && executionId && ( - - )} - - ) : ( - - ) - } - > - {execution.visibility === "public" - ? "Make Private" - : "Make Public"} - -
- )} + {execution &&
} string @@ -54,33 +50,7 @@ export const createColumns = ( return ; }, }, - { - accessorKey: "visibility", - header: "Visibility", - cell: ({ row }) => { - const visibility = row.getValue( - "visibility" - ) as WorkflowExecution["visibility"]; - return ( - - {visibility === "public" ? ( - - ) : ( - - )} - {visibility} - - ); - }, - }, + { accessorKey: "startedAt", header: "Started At", diff --git a/apps/web/src/pages/home-page.tsx b/apps/web/src/pages/home-page.tsx index 9887821c..b488455e 100644 --- a/apps/web/src/pages/home-page.tsx +++ b/apps/web/src/pages/home-page.tsx @@ -9,9 +9,9 @@ import Workflow from "lucide-react/icons/workflow"; import { Link, Navigate } from "react-router"; import { useAuth } from "@/components/auth-context"; +import { DemoExecution } from "@/components/demo-execution"; import { HomeFooter } from "@/components/layouts/home-footer"; import { HomeHeader } from "@/components/layouts/home-header"; -import { useTheme } from "@/components/theme-provider"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardHeader, CardTitle } from "@/components/ui/card"; @@ -67,11 +67,8 @@ const features = [ export function HomePage() { const { user, isAuthenticated } = useAuth(); - const { theme } = useTheme(); const { getOrgUrl } = useOrgUrl(); - const homepagePublicExecutionUrl = `${import.meta.env.VITE_WEBSITE_URL}/public/executions/${import.meta.env.VITE_HOMEPAGE_PUBLIC_EXECUTION_ID}?fullscreen`; - if (isAuthenticated && user) { return ; } @@ -132,11 +129,8 @@ export function HomePage() { -
-