- {/* Method and Endpoint */}
-
-
- POST
-
-
- http://localhost:3000/api/workflows/{workflowId}/run
-
+ {/* Right Column: Documentation Card List */}
+
+ {/* API Endpoint Card */}
+
+
+ API Endpoint
+
+
+
+ POST
+
+
+ https://api.magica.com/api/v1/runs
+
+
- {/* Code Blocks */}
-
- {/* Selector Header */}
-
-
- {["curl", "javascript", "python"].map((lang) => (
-
- ))}
+ {/* Response Format Card */}
+
+
+ Response Format
+
+
+
+ The start endpoint returns a{" "}
+
+ runId
+
+ . Poll{" "}
+
+ GET /v1/runs/{"{"}runId{"}"}
+ {" "}
+ to check status.
+
+
+
-
-
- {/* Snip content */}
-
- {apiSnippets[selectedApiLang]}
-
- {/* Payload Schema info */}
-
-
- Request Body Params
+ {/* Polling Format Card */}
+
+
+ Polling Format
-
-
-
-
- | Field |
- Type |
- Required |
- Description |
-
-
-
-
- |
- selectedNodeIds
- |
- string[] |
- No |
-
- Subset of nodes IDs to trigger. If omitted, triggers
- the full pipeline execution.
- |
-
-
-
+
+
+ Poll{" "}
+
+ GET /v1/runs/{"{"}runId{"}"}
+ {" "}
+ until status is a terminal value:
+
+
+ {/* Status badges row */}
+
+
+ Queued
+
+
+ Running
+
+
+ Completed
+
+
+ Canceled
+
+
+ Failed
+
+
+
+ {/* GET request box */}
+
+
+ GET
+
+
+ /v1/runs/{"{"}runId{"}"}
+ {inDetails ? "?inDetails=true" : ""}
+
+
+
+ {/* Toggle Switch */}
+
+
+
+
+ inDetails
+
+
+ {inDetails
+ ? "true - all node runs"
+ : "false - only workflow status"}
+
+
+
+
+ {/* Sample Response Section */}
+
+
+ Sample response:
+
+
+
+
+
@@ -1079,56 +1593,56 @@ else:
zoomable
/>
-
-
{/* Floating Workflow Navigator Widget matching Screen 1 & 2 */}
-
- {(() => {
- const sortedNodes = [...nodes].sort(
- (a, b) => a.position.x - b.position.x,
- );
- if (sortedNodes.length === 0) {
- return (
-
- No Nodes
-
+ {activeTab !== "api" && (
+
+ {(() => {
+ const sortedNodes = [...nodes].sort(
+ (a, b) => a.position.x - b.position.x,
);
- }
- return sortedNodes.map((node) => {
- // Determine height based on node type
- let height = 12;
- if (node.type === "REQUEST_INPUTS") height = 10;
- else if (node.type === "RESPONSE") height = 14;
- else if (node.type === "GEMINI") height = 28;
- else if (node.type === "CROP_IMAGE") height = 20;
- else height = 18;
-
- return (
-
+ if (sortedNodes.length === 0) {
+ return (
+
+ No Nodes
+
+ );
+ }
+ return sortedNodes.map((node) => {
+ // Determine height based on node type
+ let height = 12;
+ if (node.type === "REQUEST_INPUTS") height = 10;
+ else if (node.type === "RESPONSE") height = 14;
+ else if (node.type === "GEMINI") height = 28;
+ else if (node.type === "CROP_IMAGE") height = 20;
+ else height = 18;
+
+ return (
+
+ )}
);
diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx
index 1900d40..e92ae4a 100644
--- a/app/dashboard/page.tsx
+++ b/app/dashboard/page.tsx
@@ -74,7 +74,7 @@ export default function DashboardPage() {
const res = await fetch("/api/workflows", { method: "POST", body: "{}" });
if (res.ok) {
const wf = await res.json();
- router.push(`/workflow/${wf.id}`);
+ router.push(`/app/workflows/${wf.id}`);
}
};
@@ -196,7 +196,7 @@ export default function DashboardPage() {
router.push(`/workflow/${wf.id}`)}
+ onClick={() => router.push(`/app/workflows/${wf.id}`)}
>
Open
diff --git a/app/globals.css b/app/globals.css
index 7ad0381..bbf79fd 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -7,9 +7,12 @@
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
- --font-sans: var(--font-inter), var(--font-geist-sans);
+ --font-sans:
+ "Google Sans Flex", "Google Sans", var(--font-inter), var(--font-geist-sans);
+ --font-static-sans: var(--font-inter), var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
- --font-heading: var(--font-inter), var(--font-geist-sans);
+ --font-heading:
+ "Google Sans Flex", "Google Sans", var(--font-inter), var(--font-geist-sans);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
@@ -93,7 +96,7 @@
}
:root {
- --background: oklch(1 0 0);
+ --background: #f9f9f9;
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
@@ -117,7 +120,7 @@
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--radius: 0.625rem;
- --sidebar: oklch(0.985 0 0);
+ --sidebar: #f0f0f0;
--sidebar-foreground: oklch(0.4 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
@@ -178,11 +181,13 @@
}
/* Hide scrollbar utility */
-.hide-scrollbar {
+.hide-scrollbar,
+.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
-.hide-scrollbar::-webkit-scrollbar {
+.hide-scrollbar::-webkit-scrollbar,
+.no-scrollbar::-webkit-scrollbar {
display: none;
}
diff --git a/app/layout.tsx b/app/layout.tsx
index 802a9c8..eca9de6 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -2,6 +2,20 @@ import { ClerkProvider } from "@clerk/nextjs";
import type { Metadata } from "next";
import { Geist, Geist_Mono, Inter } from "next/font/google";
import { TooltipProvider } from "@/components/ui/tooltip";
+
+// Google Sans Flex (variable — flex everywhere except PromoBanner)
+import "@fontsource/google-sans-flex/400.css";
+import "@fontsource/google-sans-flex/500.css";
+import "@fontsource/google-sans-flex/600.css";
+import "@fontsource/google-sans-flex/700.css";
+import "@fontsource/google-sans-flex/800.css";
+
+// Google Sans (static — for PromoBanner)
+import "@fontsource/google-sans/400.css";
+import "@fontsource/google-sans/500.css";
+import "@fontsource/google-sans/600.css";
+import "@fontsource/google-sans/700.css";
+
import "./globals.css";
const inter = Inter({
@@ -34,11 +48,11 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
-
-
+
+
{children}
diff --git a/bun.lock b/bun.lock
index e6adf22..394feb6 100644
--- a/bun.lock
+++ b/bun.lock
@@ -6,11 +6,15 @@
"dependencies": {
"@base-ui/react": "^1.6.0",
"@clerk/nextjs": "^7.5.8",
+ "@fontsource/google-sans": "^5.2.1",
+ "@fontsource/google-sans-flex": "^5.2.4",
"@google/generative-ai": "0.24.1",
"@prisma/adapter-pg": "^7.8.0",
"@prisma/client": "^7.8.0",
"@t3-oss/env-nextjs": "^0.13.11",
"@trigger.dev/sdk": "4.4.6",
+ "@uppy/core": "^5.2.0",
+ "@uppy/transloadit": "^5.5.1",
"@xyflow/react": "12.11.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -159,6 +163,10 @@
"@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="],
+ "@fontsource/google-sans": ["@fontsource/google-sans@5.2.1", "", {}, "sha512-FteXH18YS0288Bcrjefwyyp7hLwxd8yQBEcmjU/P0BED9c8n8kxwgna3gtMtYyFQx/wIkpyrzj8RaYQyl/4quw=="],
+
+ "@fontsource/google-sans-flex": ["@fontsource/google-sans-flex@5.2.4", "", {}, "sha512-S7WKovlLsdcmC7DdkYRYNMul1EAmj6PQ9ZYTLgihazDBzYAyRti3MbaZeOqYhBl1kAY6kyck70+wAyZFEtz8TQ=="],
+
"@google-cloud/precise-date": ["@google-cloud/precise-date@4.0.0", "", {}, "sha512-1TUx3KdaU3cN7nfCdNf+UVqA/PSX29Cjcox3fZZBtINlRrXVTmUkQnCKv2MbBUbCopbK4olAT1IHl76uZyCiVA=="],
"@google/generative-ai": ["@google/generative-ai@0.24.1", "", {}, "sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q=="],
@@ -413,8 +421,12 @@
"@transloadit/abbr": ["@transloadit/abbr@1.0.0", "", {}, "sha512-Hg5xdbpsDfUiUc62fIAF6L86+o52pY37/eKCOKqvJCJfSJ0ET6AG5FBIg6o2tTiFF7si5lhGIHONs0/IAEwc2Q=="],
+ "@transloadit/prettier-bytes": ["@transloadit/prettier-bytes@0.3.5", "", {}, "sha512-xF4A3d/ZyX2LJWeQZREZQw+qFX4TGQ8bGVP97OLRt6sPO6T0TNHBFTuRHOJh7RNmYOBmQ9MHxpolD9bXihpuVA=="],
+
"@transloadit/sev-logger": ["@transloadit/sev-logger@0.1.9", "", { "dependencies": { "@transloadit/abbr": "^1.0.0" } }, "sha512-TALqS5mOo+5TmwNdtRfsfOhtjhfCuXllVffNoiGEpewmbwsxBfrTdcE/9/Ayst9rfT0HST0KU1l71lxltLMEwg=="],
+ "@transloadit/types": ["@transloadit/types@4.3.3", "", {}, "sha512-SWPS65ZkCzTfIZErICXcugUkFvjVEtDHi9CZtI2IZ6/7UyG0llqdQbrge0F10PVFX2/20m3lyxsJVPKhQzAKlQ=="],
+
"@transloadit/utils": ["@transloadit/utils@4.3.0", "", {}, "sha512-fjfowdo+RCh1VQe2aH/dfgRUWNT3Vq9863jYCH4neWA4pyyyl+Y4zA11fKmncs+NlGLsQlbkiQYCeTFwVa+6Uw=="],
"@trigger.dev/build": ["@trigger.dev/build@4.4.6", "", { "dependencies": { "@prisma/config": "^6.10.0", "@trigger.dev/core": "4.4.6", "mlly": "^1.7.1", "pkg-types": "^1.1.3", "resolve": "^1.22.8", "tinyglobby": "^0.2.2", "tsconfck": "3.1.3" } }, "sha512-eHPPaeuFe9GZDndQzP4QUlxocyIJWYSx0FMx1GEiAnEVKwXWUqiW72DRFH7cr9v7IQnI9YbAWRuWvyMPHSVLwg=="],
@@ -453,8 +465,22 @@
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
+ "@types/retry": ["@types/retry@0.12.2", "", {}, "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow=="],
+
"@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="],
+ "@uppy/companion-client": ["@uppy/companion-client@5.1.1", "", { "dependencies": { "@uppy/utils": "^7.1.1", "namespace-emitter": "^2.0.1", "p-retry": "^6.1.0" }, "peerDependencies": { "@uppy/core": "^5.1.1" } }, "sha512-DzrOWTbIZHvtgAFXBMYHk2wD27NjpBSVhY2tEiEIUhPd2CxbFRZjHM/N3HOt3VwZEAP471QWFLlJRWPcIY3A2Q=="],
+
+ "@uppy/core": ["@uppy/core@5.2.0", "", { "dependencies": { "@transloadit/prettier-bytes": "^0.3.4", "@uppy/store-default": "^5.0.0", "@uppy/utils": "^7.1.4", "lodash": "^4.17.21", "mime-match": "^1.0.2", "namespace-emitter": "^2.0.1", "nanoid": "^5.0.9", "preact": "^10.5.13" } }, "sha512-uvfNyz4cnaplt7LYJmEZHuqOuav0tKp4a9WKJIaH6iIj7XiqYvS2J5SEByexAlUFlzefOAyjzj4Ja2dd/8aMrw=="],
+
+ "@uppy/store-default": ["@uppy/store-default@5.0.0", "", {}, "sha512-hQtCSQ1yGiaval/wVYUWquYGDJ+bpQ7e4FhUUAsRQz1x1K+o7NBtjfp63O9I4Ks1WRoKunpkarZ+as09l02cPw=="],
+
+ "@uppy/transloadit": ["@uppy/transloadit@5.5.1", "", { "dependencies": { "@transloadit/types": "^4.1.3", "@uppy/tus": "^5.1.1", "@uppy/utils": "^7.2.0", "component-emitter": "^2.0.0" }, "peerDependencies": { "@uppy/core": "^5.2.0" } }, "sha512-nzgZ/u90hMzbsCj/44pVepUXtbOvpIbWAHLRLbSTUbQUfwR6o8o5s59IFmFUQ3uLYdunW4DDaOkEdNb7hRLmEw=="],
+
+ "@uppy/tus": ["@uppy/tus@5.1.1", "", { "dependencies": { "@uppy/companion-client": "^5.1.1", "@uppy/utils": "^7.1.5", "tus-js-client": "^4.2.3" }, "peerDependencies": { "@uppy/core": "^5.2.0" } }, "sha512-316kLQfO5H/uUJIMhBYhBrTpeN0Q+d6ykW3pomCvdTkFGCvg20rF3oH/owE3lf2UZZN7ZqBk+wHO0WlQePoklg=="],
+
+ "@uppy/utils": ["@uppy/utils@7.2.0", "", { "dependencies": { "lodash": "^4.17.23", "preact": "^10.26.10" } }, "sha512-6lC246qszMv6bTyl/+QyHwrudgeguWkA94ME1wHn+a6uRAvmtAEaUManIfGqTJfoKvWAiCJqdJPl5xRJjhAloQ=="],
+
"@xyflow/react": ["@xyflow/react@12.11.0", "", { "dependencies": { "@xyflow/system": "0.0.77", "classcat": "^5.0.3", "zustand": "^4.4.0" }, "peerDependencies": { "@types/react": ">=17", "@types/react-dom": ">=17", "react": ">=17", "react-dom": ">=17" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-na4IO33FSs2OS72hASgZDmTYwFAkef7Z74uBUVrong3ARmQQHfnRUVaCFn1kTt5LbS6pK03TbYjCPGLjLFfziA=="],
"@xyflow/system": ["@xyflow/system@0.0.77", "", { "dependencies": { "@types/d3-drag": "^3.0.7", "@types/d3-interpolate": "^3.0.4", "@types/d3-selection": "^3.0.10", "@types/d3-transition": "^3.0.8", "@types/d3-zoom": "^3.0.8", "d3-drag": "^3.0.0", "d3-interpolate": "^3.0.1", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" } }, "sha512-qCDCMCQAAgUu8yHnhloHG9F5mwPX5E+Wl8McpYIOPSSXfzFJJoZcwOcsDiAjitVKIg2de1WmJbCHfpcvxprsgg=="],
@@ -559,6 +585,8 @@
"commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
+ "component-emitter": ["component-emitter@2.0.0", "", {}, "sha512-4m5s3Me2xxlVKG9PkZpQqHQR7bgpnN7joDMJ4yvVkVXngjoITG76IaZmzmywSeRTeTpc6N6r3H3+KyUurV8OYw=="],
+
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"conf": ["conf@10.2.0", "", { "dependencies": { "ajv": "^8.6.3", "ajv-formats": "^2.1.1", "atomically": "^1.7.0", "debounce-fn": "^4.0.0", "dot-prop": "^6.0.1", "env-paths": "^2.2.1", "json-schema-typed": "^7.0.3", "onetime": "^5.1.2", "pkg-up": "^3.1.0", "semver": "^7.3.5" } }, "sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg=="],
@@ -843,6 +871,8 @@
"is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="],
+ "is-network-error": ["is-network-error@1.3.2", "", {}, "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA=="],
+
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-obj": ["is-obj@3.0.0", "", {}, "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ=="],
@@ -969,6 +999,8 @@
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
+ "mime-match": ["mime-match@1.0.2", "", { "dependencies": { "wildcard": "^1.1.0" } }, "sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg=="],
+
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="],
@@ -993,7 +1025,9 @@
"named-placeholders": ["named-placeholders@1.1.6", "", { "dependencies": { "lru.min": "^1.1.0" } }, "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w=="],
- "nanoid": ["nanoid@3.3.15", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="],
+ "namespace-emitter": ["namespace-emitter@2.0.1", "", {}, "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g=="],
+
+ "nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="],
"negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
@@ -1043,6 +1077,8 @@
"p-queue": ["p-queue@9.3.0", "", { "dependencies": { "eventemitter3": "^5.0.4", "p-timeout": "^7.0.0" } }, "sha512-7NED7xhQ74Ngp4JP/2e0VZHp7vSWfJfqeiR92jPgxsz6m0Se4P03YoTKa9dDXyZ3r6P616gUXttrB6nnHYKang=="],
+ "p-retry": ["p-retry@6.2.1", "", { "dependencies": { "@types/retry": "0.12.2", "is-network-error": "^1.0.0", "retry": "^0.13.1" } }, "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ=="],
+
"p-timeout": ["p-timeout@7.0.1", "", {}, "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg=="],
"p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
@@ -1111,6 +1147,8 @@
"powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="],
+ "preact": ["preact@10.29.3", "", {}, "sha512-D9NL1GAnJZhc3RndVs4gDdxEeU9TcHgywMrhhOsnpdlvFjdbx0gAsLUnH6JEhlJH5giL7Tx5biWPUSEXE/HPzw=="],
+
"pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="],
"prisma": ["prisma@7.8.0", "", { "dependencies": { "@prisma/config": "7.8.0", "@prisma/dev": "0.24.3", "@prisma/engines": "7.8.0", "@prisma/studio-core": "0.27.3", "mysql2": "3.15.3", "postgres": "3.4.7" }, "peerDependencies": { "better-sqlite3": ">=9.0.0", "typescript": ">=5.4.0" }, "optionalPeers": ["better-sqlite3", "typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-yfN4yrw7HV9kEJhoy1+jgah0jafEIQsf7uWouSsM8MvJtlubsk+kM7AIBWZ8+GJl74Yj3c+nbYqBkMOxtsZ3Lw=="],
@@ -1341,6 +1379,8 @@
"which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
+ "wildcard": ["wildcard@1.1.2", "", {}, "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng=="],
+
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="],
@@ -1469,8 +1509,12 @@
"nypm/tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="],
+ "p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
+
"pg-types/postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
+ "postcss/nanoid": ["nanoid@3.3.15", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="],
+
"prisma/@prisma/config": ["@prisma/config@7.8.0", "", { "dependencies": { "c12": "3.3.4", "deepmerge-ts": "7.1.5", "effect": "3.20.0", "empathic": "2.0.0" } }, "sha512-HFESzd9rx2ZQxlK+TL7tu1HPvCqrHiL6LCxYykI2c34mvaUuIVVl3lYuicJD/MNnzgPnyeBEMlK4WTomJCV5jw=="],
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
@@ -1537,6 +1581,8 @@
"express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
+ "next/postcss/nanoid": ["nanoid@3.3.15", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="],
+
"prisma/@prisma/config/c12": ["c12@3.3.4", "", { "dependencies": { "chokidar": "^5.0.0", "confbox": "^0.2.4", "defu": "^6.1.6", "dotenv": "^17.3.1", "exsolve": "^1.0.8", "giget": "^3.2.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.1.0", "pkg-types": "^2.3.0", "rc9": "^3.0.1" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA=="],
"prisma/@prisma/config/effect": ["effect@3.20.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw=="],
diff --git a/components/NodePropertiesPanel.tsx b/components/NodePropertiesPanel.tsx
index b619665..48975ec 100644
--- a/components/NodePropertiesPanel.tsx
+++ b/components/NodePropertiesPanel.tsx
@@ -1,7 +1,7 @@
"use client";
import type { Node } from "@xyflow/react";
-import { Plus, Trash2, X } from "lucide-react";
+import { Loader2, Plus, Trash2, X } from "lucide-react";
import { useState } from "react";
import { useCanvasStore } from "@/lib/stores";
@@ -12,6 +12,30 @@ interface Props {
type Field = { name: string; type: string; value?: string };
+async function uploadToTransloadit(file: File): Promise {
+ const res = await fetch("/api/upload/transloadit");
+ if (!res.ok) throw new Error("Failed to get upload params");
+ const { params, signature } = await res.json();
+
+ const form = new FormData();
+ form.append("params", params);
+ form.append("signature", signature);
+ form.append("my_file", file);
+
+ const assemblyRes = await fetch(
+ "https://api2.transloadit.com/assemblies?wait=true",
+ {
+ method: "POST",
+ body: form,
+ },
+ );
+ if (!assemblyRes.ok) throw new Error("Upload failed");
+
+ const result = await assemblyRes.json();
+ const uploaded = result?.results?.[":original"]?.[0];
+ return uploaded?.ssl_url ?? uploaded?.url ?? "";
+}
+
function RequestInputsForm({
config,
onChange,
@@ -22,6 +46,7 @@ function RequestInputsForm({
const fields = (config.fields as Field[]) ?? [];
const [newName, setNewName] = useState("");
const [newType, setNewType] = useState("text");
+ const [uploadingIdx, setUploadingIdx] = useState(null);
const addField = () => {
if (!newName.trim()) return;
@@ -87,20 +112,34 @@ function RequestInputsForm({
accept="image/*"
className="hidden"
id={`upload-${i}`}
- onChange={(e) => {
+ onChange={async (e) => {
const file = e.target.files?.[0];
- if (file) {
- const url = URL.createObjectURL(file);
- updateField(i, "value", url);
+ if (!file) return;
+ setUploadingIdx(i);
+ try {
+ const url = await uploadToTransloadit(file);
+ if (url) updateField(i, "value", url);
+ } catch (err) {
+ console.log("[UPLOAD] Transloadit error:", err);
+ } finally {
+ setUploadingIdx(null);
}
}}
/>
) : (
diff --git a/components/app-sidebar.tsx b/components/app-sidebar.tsx
index 1645370..8cb01d7 100644
--- a/components/app-sidebar.tsx
+++ b/components/app-sidebar.tsx
@@ -1,23 +1,25 @@
"use client";
-import { useClerk, useUser } from "@clerk/nextjs";
+import { UserButton } from "@clerk/nextjs";
import {
- BarChart3,
BookOpen,
+ Boxes,
ChevronDown,
- FolderClosed,
+ ChevronUp,
+ FolderOpen,
Gift,
- MessageSquare,
+ Library,
+ MessageSquareMore,
+ PanelLeft,
Plus,
Search,
Settings,
- Users,
- Workflow,
+ Share2,
} from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
+import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
Sidebar,
@@ -29,7 +31,6 @@ import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
- SidebarSeparator,
SidebarTrigger,
useSidebar,
} from "@/components/ui/sidebar";
@@ -42,31 +43,25 @@ import {
const mainNav = [
{ href: "#", label: "New task", icon: Plus },
{ href: "#", label: "Search Task", icon: Search },
- { href: "#", label: "Task", icon: MessageSquare },
- { href: "#", label: "Projects", icon: FolderClosed },
- { href: "#", label: "Library", icon: BarChart3 },
- { href: "/app/flow", label: "Flow", icon: Workflow },
- { href: "#", label: "Tools", icon: Users },
+ { href: "#", label: "Task", icon: MessageSquareMore },
+ { href: "#", label: "Projects", icon: FolderOpen },
+ { href: "#", label: "Library", icon: Library },
+ { href: "/app/flow", label: "Flow", icon: Share2 },
+ { href: "#", label: "Tools", icon: Boxes },
{ href: "#", label: "API / MCP", icon: BookOpen },
];
export function AppSidebar() {
const pathname = usePathname();
- const { user } = useUser();
- const { signOut } = useClerk();
const { state, toggleSidebar } = useSidebar();
const collapsed = state === "collapsed";
-
- const initials =
- user?.firstName?.[0] ?? user?.emailAddresses?.[0]?.emailAddress?.[0] ?? "?";
- const displayName =
- [user?.firstName, user?.lastName].filter(Boolean).join(" ") || "User";
+ const [showSettingsOffer, setShowSettingsOffer] = useState(true);
return (
{/* Header: Logo + Collapse Trigger */}
-
-
+
+
{collapsed ? (
}
>
@@ -83,8 +78,11 @@ export function AppSidebar() {
alt="Magica"
width={24}
height={24}
- className="invert dark:invert-0"
+ className="invert dark:invert-0 transition-opacity duration-150 group-hover/logo:opacity-0"
/>
+
Open sidebar
@@ -95,16 +93,33 @@ export function AppSidebar() {
) : (
<>
-
+
-
+
+ }
+ />
+
+ Close sidebar
+
+ ⌘.
+
+
+
>
)}
@@ -114,7 +129,7 @@ export function AppSidebar() {
-
+
{mainNav.map((item) => {
const Icon = item.icon;
const isActive =
@@ -125,7 +140,7 @@ export function AppSidebar() {
isActive={isActive}
tooltip={item.label}
render={}
- className="h-[46px] text-[15.5px] px-4 gap-4 rounded-xl [&_svg]:size-[19.5px] font-semibold text-gray-700 data-active:text-gray-900 data-active:bg-gray-100/70 transition-all"
+ className="h-11 text-[16px] px-4 gap-4 rounded-xl [&_svg]:size-[22px] [&_svg]:text-[#3D3D41] font-normal text-gray-700 data-active:text-gray-950 data-active:bg-[#DCDCE0] transition-all group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:px-0"
>
{item.label}
@@ -139,85 +154,94 @@ export function AppSidebar() {
{/* No tasks yet placeholder */}
{!collapsed && (
-
-
+
+
No tasks yet
)}
- {/* Footer */}
-
- {/* Settings */}
-
-
- {collapsed ? (
- }
- className="h-[46px] text-[15.5px] px-4 gap-4 rounded-xl [&_svg]:size-[19.5px] font-semibold text-gray-700 hover:text-gray-900 transition-all"
- >
-
-
- ) : (
-
-
- Settings
-
- )}
-
-
-
-
-
- {/* Claim Offer */}
-
-
- {collapsed ? (
-
+ {/* Settings + Claim Offer */}
+ {(!collapsed && showSettingsOffer) || collapsed ? (
+ <>
+
+
+ {collapsed ? (
+
+ ) : (
+
+
+ Settings
+
+ )}
+
+
+
+
+ {collapsed ? (
+
+
+
+ ) : (
+
+
+
+
+ )}
+
+
+ >
+ ) : (
+ !collapsed && (
+
+
+
+ )
+ )}
- {/* User Profile */}
-
-
- signOut({ redirectUrl: "/" })}
- className="h-13.5 text-[15.5px] [&_svg]:size-[19.5px] gap-4 font-semibold text-gray-750 hover:text-gray-950 transition-all rounded-xl"
- >
-
-
- {initials}
-
-
-
- {displayName}
-
-
-
-
-
-
+ {/* User Profile — Clerk UserButton */}
+
+
+
);
diff --git a/components/nodes/NodeShell.tsx b/components/nodes/NodeShell.tsx
index 72a8afb..d502234 100644
--- a/components/nodes/NodeShell.tsx
+++ b/components/nodes/NodeShell.tsx
@@ -1,9 +1,9 @@
"use client";
-import type { ReactNode } from "react";
import { Play } from "lucide-react";
+import { type ReactNode, useCallback, useRef } from "react";
+import { useCanvasStore, useExecutionStore } from "@/lib/stores";
import { cn } from "@/lib/utils";
-import { useExecutionStore } from "@/lib/stores";
interface NodeShellProps {
id: string;
@@ -67,13 +67,70 @@ export function NodeShell({
const nodeStatus = useExecutionStore((s) => s.nodeStatuses[id]);
const isRunning = nodeStatus === "running";
const c = colorConfig[color];
+ const pollRef = useRef | null>(null);
+
+ const handleRun = useCallback(async () => {
+ const workflowId =
+ window.location.pathname.match(/\/workflows\/([^/]+)/)?.[1];
+ if (!workflowId) return;
+
+ const startRun = useExecutionStore.getState().startRun;
+ const updateNodeStatus = useExecutionStore.getState().updateNodeStatus;
+ const setNodeResult = useExecutionStore.getState().setNodeResult;
+ const setNodeError = useExecutionStore.getState().setNodeError;
+ const completeRun = useExecutionStore.getState().completeRun;
+
+ try {
+ const res = await fetch(`/api/workflows/${workflowId}/run`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ selectedNodeIds: [id] }),
+ });
+ if (!res.ok) return;
+
+ const run = await res.json();
+ startRun(run.id, [id]);
+
+ pollRef.current = setInterval(async () => {
+ try {
+ const statusRes = await fetch(
+ `/api/workflows/${workflowId}/runs/${run.id}`,
+ );
+ if (!statusRes.ok) {
+ if (pollRef.current) clearInterval(pollRef.current);
+ completeRun(run.id);
+ return;
+ }
+ const data = await statusRes.json();
+ if (["SUCCESS", "FAILED", "PARTIAL"].includes(data.status)) {
+ if (pollRef.current) clearInterval(pollRef.current);
+ for (const nr of data.nodeResults ?? []) {
+ updateNodeStatus(run.id, nr.nodeId, nr.status.toLowerCase());
+ if (nr.output != null) {
+ setNodeResult(run.id, nr.nodeId, nr.output);
+ }
+ if (nr.error != null) {
+ setNodeError(run.id, nr.nodeId, nr.error);
+ }
+ }
+ completeRun(run.id);
+ }
+ } catch {
+ if (pollRef.current) clearInterval(pollRef.current);
+ completeRun(run.id);
+ }
+ }, 2000);
+ } catch (err) {
+ console.log("[NODE RUN] error:", err);
+ }
+ }, [id]);
return (
)}
-
+
- Run
-
+ {isRunning ? "..." : "Run"}
+
diff --git a/components/nodes/ResponseNode.tsx b/components/nodes/ResponseNode.tsx
index 1421d76..f707a94 100644
--- a/components/nodes/ResponseNode.tsx
+++ b/components/nodes/ResponseNode.tsx
@@ -55,9 +55,10 @@ function ResponseNode({ id, data, selected }: NodeProps) {
value &&
typeof value === "object" &&
"response" in (value as Record)
- ? String(
- (value as Record).response,
- ).slice(0, 80)
+ ? String((value as Record).response).slice(
+ 0,
+ 80,
+ )
: typeof value === "string"
? value.slice(0, 80)
: "Output received";
diff --git a/components/promo-banner.tsx b/components/promo-banner.tsx
index d6d5c46..237af8a 100644
--- a/components/promo-banner.tsx
+++ b/components/promo-banner.tsx
@@ -27,13 +27,12 @@ export function PromoBanner() {
const pad = (n: number) => String(n).padStart(2, "0");
return (
-
+
- Pay once, get a{" "}
- LIFETIME deal
- forever — for only $499
+ Pay once, get a LIFETIME deal forever
+ — for only $499
-
+