{ "kind": "open-flow-workflow", "version": 1, "name": "Generate a Product Image with Magic Hour", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_df2f41e507406698": { "name": "Start manually", "kind": "start", "inputDefs": [] }, "n_8d9e6dd8feee8c11": { "inputs": { "model": { "kind": "value", "value": "flux-2-klein" }, "image_count": { "kind": "value", "value": 1 }, "resolution": { "kind": "value", "value": "640px" }, "aspect_ratio": { "kind": "value", "value": "1:1" }, "style": { "kind": "value", "value": { "prompt": "YOUR_PRODUCT_IMAGE_PROMPT" } } }, "name": "Generate the image", "kind": "task", "task": { "inputs": [ { "description": "Give your image a custom name for easy identification.", "jsonSchema": { "type": "string", "description": "Give your image a custom name for easy identification." }, "nullable": true, "handle": "name", "value": null }, { "description": "Number of images to generate. Maximum varies by model.", "jsonSchema": { "type": "integer", "minimum": 1, "maximum": 16, "description": "Number of images to generate. Maximum varies by model." }, "nullable": false, "handle": "image_count" }, { "description": "The AI model to use for image generation. Each model has different capabilities and costs.\n\n**Models:**\n- `default` - Use the model we recommend, which will change over time. This is recommended unless you need a specific model. This is the default behavior.\n- `flux-2-klein` - from 5 credits/image\n - Supported resolutions: 640px, 1k, 2k\n - Available for tiers: free, creator, pro, business\n - Image count allowed: 1\n- `flux-schnell` - from 5 credits/image\n - Supported resolutions: 640px, 1k, 2k\n - Available for tiers: free, creator, pro, business\n - Image count allowed: 1, 2, 3, 4\n- `gpt-image-2` - from 50 credits/image\n - Supported resolutions: 640px, 1k, 2k, 4k\n - Available for tiers: creator, pro, business\n - Image count allowed: 1, 2, 3, 4\n- `gpt-image-2.5-flare` - from 100 credits/image\n - Supported resolutions: 640px, 1k, 2k, 4k\n - Available for tiers: creator, pro, business\n - Image count allowed: 1, 2, 3, 4\n- `krea-2` - from 10 credits/image\n - Supported resolutions: 640px, 1k\n - Available for tiers: free, creator, pro, business\n - Image count allowed: 1\n- `nano-banana` - from 50 credits/image\n - Supported resolutions: 640px, 1k\n - Available for tiers: creator, pro, business\n - Image count allowed: 1, 2, 3, 4\n- `nano-banana-2` - from 100 credits/image\n - Supported resolutions: 640px, 1k, 2k, 4k\n - Available for tiers: creator, pro, business\n - Image count allowed: 1, 4, 9, 16\n- `nano-banana-2-lite` - from 50 credits/image\n - Supported resolutions: 640px, 1k\n - Available for tiers: creator, pro, business\n - Image count allowed: 1, 2, 3, 4\n- `nano-banana-2.1` - from 50 credits/image\n - Supported resolutions: 640px, 1k, 2k, 4k\n - Available for tiers: creator, pro, business\n - Image count allowed: 1, 4, 9, 16\n- `nano-banana-pro` - from 150 credits/image\n - Supported resolutions: 1k, 2k, 4k\n - Available for tiers: creator, pro, business\n - Image count allowed: 1, 4, 9, 16\n- `qwen-image-2.1` - from 10 credits/image\n - Supported resolutions: 640px, 1k, 2k\n - Available for tiers: free, creator, pro, business\n - Image count allowed: 1, 2, 3, 4\n- `seedream-v4` - from 40 credits/image\n - Supported resolutions: 640px, 1k, 2k, 4k\n - Available for tiers: creator, pro, business\n - Image count allowed: 1, 2, 3, 4\n- `seedream-v5-pro` - from 75 credits/image\n - Supported resolutions: 640px, 1k, 2k\n - Available for tiers: creator, pro, business\n - Image count allowed: 1, 2, 3, 4\n- `z-image-turbo` - from 5 credits/image\n - Supported resolutions: 640px, 1k, 2k\n - Available for tiers: free, creator, pro, business\n - Image count allowed: 1, 2, 3, 4\n\n**Deprecated Enum Values:**\n- `seedream` - Use `seedream-v4` instead.\n", "jsonSchema": { "type": "string", "enum": [ "default", "z-image-turbo", "gpt-image-2", "qwen-image-2.1", "flux-2-klein", "nano-banana-2", "nano-banana-2.1", "nano-banana-pro", "krea-2", "seedream-v5-pro", "nano-banana-2-lite", "gpt-image-2.5-flare", "seedream-v4", "nano-banana", "flux-schnell", "seedream" ], "description": "The AI model to use for image generation. Each model has different capabilities and costs.\n\n**Models:**\n- `default` - Use the model we recommend, which will change over time. This is recommended unless you need a specific model. This is the default behavior.\n- `flux-2-klein` - from 5 credits/image\n - Supported resolutions: 640px, 1k, 2k\n - Available for tiers: free, creator, pro, business\n - Image count allowed: 1\n- `flux-schnell` - from 5 credits/image\n - Supported resolutions: 640px, 1k, 2k\n - Available for tiers: free, creator, pro, business\n - Image count allowed: 1, 2, 3, 4\n- `gpt-image-2` - from 50 credits/image\n - Supported resolutions: 640px, 1k, 2k, 4k\n - Available for tiers: creator, pro, business\n - Image count allowed: 1, 2, 3, 4\n- `gpt-image-2.5-flare` - from 100 credits/image\n - Supported resolutions: 640px, 1k, 2k, 4k\n - Available for tiers: creator, pro, business\n - Image count allowed: 1, 2, 3, 4\n- `krea-2` - from 10 credits/image\n - Supported resolutions: 640px, 1k\n - Available for tiers: free, creator, pro, business\n - Image count allowed: 1\n- `nano-banana` - from 50 credits/image\n - Supported resolutions: 640px, 1k\n - Available for tiers: creator, pro, business\n - Image count allowed: 1, 2, 3, 4\n- `nano-banana-2` - from 100 credits/image\n - Supported resolutions: 640px, 1k, 2k, 4k\n - Available for tiers: creator, pro, business\n - Image count allowed: 1, 4, 9, 16\n- `nano-banana-2-lite` - from 50 credits/image\n - Supported resolutions: 640px, 1k\n - Available for tiers: creator, pro, business\n - Image count allowed: 1, 2, 3, 4\n- `nano-banana-2.1` - from 50 credits/image\n - Supported resolutions: 640px, 1k, 2k, 4k\n - Available for tiers: creator, pro, business\n - Image count allowed: 1, 4, 9, 16\n- `nano-banana-pro` - from 150 credits/image\n - Supported resolutions: 1k, 2k, 4k\n - Available for tiers: creator, pro, business\n - Image count allowed: 1, 4, 9, 16\n- `qwen-image-2.1` - from 10 credits/image\n - Supported resolutions: 640px, 1k, 2k\n - Available for tiers: free, creator, pro, business\n - Image count allowed: 1, 2, 3, 4\n- `seedream-v4` - from 40 credits/image\n - Supported resolutions: 640px, 1k, 2k, 4k\n - Available for tiers: creator, pro, business\n - Image count allowed: 1, 2, 3, 4\n- `seedream-v5-pro` - from 75 credits/image\n - Supported resolutions: 640px, 1k, 2k\n - Available for tiers: creator, pro, business\n - Image count allowed: 1, 2, 3, 4\n- `z-image-turbo` - from 5 credits/image\n - Supported resolutions: 640px, 1k, 2k\n - Available for tiers: free, creator, pro, business\n - Image count allowed: 1, 2, 3, 4\n\n**Deprecated Enum Values:**\n- `seedream` - Use `seedream-v4` instead.\n" }, "nullable": true, "handle": "model", "value": null }, { "description": "The aspect ratio of the output image(s). If not specified, defaults to `1:1` (square).", "jsonSchema": { "type": "string", "enum": [ "1:1", "16:9", "9:16" ], "description": "The aspect ratio of the output image(s). If not specified, defaults to `1:1` (square)." }, "nullable": true, "handle": "aspect_ratio", "value": null }, { "description": "Maximum resolution (longest edge) for the output image.\n\n**Options:**\n- `640px` — up to 640px\n- `1k` — up to 1024px\n- `2k` — up to 2048px\n- `4k` — up to 4096px\n- `auto` — **Deprecated.** Mapped server-side from your subscription tier to the best matching resolution the model supports\n\n**Per-model support:**\n- `flux-2-klein` - 640px, 1k, 2k\n- `flux-schnell` - 640px, 1k, 2k\n- `gpt-image-2` - 640px, 1k, 2k, 4k\n- `gpt-image-2.5-flare` - 640px, 1k, 2k, 4k\n- `krea-2` - 640px, 1k\n- `nano-banana` - 640px, 1k\n- `nano-banana-2` - 640px, 1k, 2k, 4k\n- `nano-banana-2-lite` - 640px, 1k\n- `nano-banana-2.1` - 640px, 1k, 2k, 4k\n- `nano-banana-pro` - 1k, 2k, 4k\n- `qwen-image-2.1` - 640px, 1k, 2k\n- `seedream-v4` - 640px, 1k, 2k, 4k\n- `seedream-v5-pro` - 640px, 1k, 2k\n- `z-image-turbo` - 640px, 1k, 2k\n\nNote: Resolution availability depends on the model and your subscription tier.", "jsonSchema": { "type": "string", "enum": [ "auto", "640px", "1k", "2k", "4k" ], "description": "Maximum resolution (longest edge) for the output image.\n\n**Options:**\n- `640px` — up to 640px\n- `1k` — up to 1024px\n- `2k` — up to 2048px\n- `4k` — up to 4096px\n- `auto` — **Deprecated.** Mapped server-side from your subscription tier to the best matching resolution the model supports\n\n**Per-model support:**\n- `flux-2-klein` - 640px, 1k, 2k\n- `flux-schnell` - 640px, 1k, 2k\n- `gpt-image-2` - 640px, 1k, 2k, 4k\n- `gpt-image-2.5-flare` - 640px, 1k, 2k, 4k\n- `krea-2` - 640px, 1k\n- `nano-banana` - 640px, 1k\n- `nano-banana-2` - 640px, 1k, 2k, 4k\n- `nano-banana-2-lite` - 640px, 1k\n- `nano-banana-2.1` - 640px, 1k, 2k, 4k\n- `nano-banana-pro` - 1k, 2k, 4k\n- `qwen-image-2.1` - 640px, 1k, 2k\n- `seedream-v4` - 640px, 1k, 2k, 4k\n- `seedream-v5-pro` - 640px, 1k, 2k\n- `z-image-turbo` - 640px, 1k, 2k\n\nNote: Resolution availability depends on the model and your subscription tier." }, "nullable": true, "handle": "resolution", "value": null }, { "description": "The art style to use for image generation.", "jsonSchema": { "type": "object", "properties": { "prompt": { "type": "string", "minLength": 1, "description": "The prompt used for the image(s)." }, "tool": { "type": "string", "enum": [ "ai-anime-generator", "ai-art-generator", "ai-background-generator", "ai-character-generator", "ai-face-generator", "ai-fashion-generator", "ai-icon-generator", "ai-illustration-generator", "ai-interior-design-generator", "ai-landscape-generator", "ai-logo-generator", "ai-manga-generator", "ai-outfit-generator", "ai-pattern-generator", "ai-photo-generator", "ai-sketch-generator", "ai-tattoo-generator", "album-cover-generator", "animated-characters-generator", "architecture-generator", "book-cover-generator", "comic-book-generator", "dark-fantasy-ai", "disney-ai-generator", "dnd-ai-art-generator", "emoji-generator", "fantasy-map-generator", "graffiti-generator", "movie-poster-generator", "optical-illusion-generator", "pokemon-generator", "south-park-character-generator", "superhero-generator", "thumbnail-maker", "general" ], "description": "The art style to use for image generation. Defaults to 'general' if not provided." } }, "required": [ "prompt" ], "additionalProperties": true, "description": "The art style to use for image generation." }, "nullable": false, "handle": "style" } ], "outputs": [ { "description": "Success", "jsonSchema": { "type": "object", "properties": { "id": { "type": "string", "description": "Unique ID of the image. Use it with the [Get image Project API](https://docs.magichour.ai/api-reference/image-projects/get-image-details) to fetch status and downloads." }, "credits_charged": { "type": "integer", "description": "The amount of credits deducted from your account to generate the image. We charge credits right when the request is made. \n\nIf an error occurred while generating the image(s), credits will be refunded and this field will be updated to include the refund." } }, "required": [ "id", "credits_charged" ], "additionalProperties": true, "description": "Success" }, "nullable": false, "handle": "output" } ], "name": "Generate the image", "executor": { "kind": "connector", "action": "magichour.create_ai_image_generator", "outputMode": "value" } } }, "n_d3f193b1bdaf8cd5": { "inputs": { "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8d9e6dd8feee8c11", "output": "output" } ] } }, "name": "Read the project ID", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "properties": { "id": { "type": "string", "description": "Unique ID of the image. Use it with the [Get image Project API](https://docs.magichour.ai/api-reference/image-projects/get-image-details) to fetch status and downloads." }, "credits_charged": { "type": "integer", "description": "The amount of credits deducted from your account to generate the image. We charge credits right when the request is made. \n\nIf an error occurred while generating the image(s), credits will be refunded and this field will be updated to include the refund." } }, "required": [ "id", "credits_charged" ], "additionalProperties": true, "description": "Success" }, "nullable": false, "handle": "body" } ], "outputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "id" } ], "name": "Read the project ID", "moduleId": "n_4764ad6fc40be637", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_cdeb2f792aff4ca7": { "inputs": {}, "name": "Wait for the image", "kind": "wait", "inputDefinitions": [], "prompt": "Wait for the image", "autoContinue": { "kind": "after", "durationMs": 10000 } }, "n_f2232a9bd444aa27": { "inputs": { "id": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_d3f193b1bdaf8cd5", "output": "id" } ] } }, "name": "Check the image project", "kind": "task", "task": { "inputs": [ { "description": "Unique ID of the image project. This value is returned by all of the POST APIs that create an image.", "jsonSchema": { "type": "string", "description": "Unique ID of the image project. This value is returned by all of the POST APIs that create an image." }, "nullable": false, "handle": "id" } ], "outputs": [ { "description": "Success", "jsonSchema": { "type": "object", "properties": { "id": { "type": "string", "description": "Unique ID of the image. Use it with the [Get image Project API](https://docs.magichour.ai/api-reference/image-projects/get-image-details) to fetch status and downloads." }, "name": { "type": [ "string", "null" ], "description": "The name of the image." }, "status": { "type": "string", "enum": [ "draft", "queued", "rendering", "complete", "error", "canceled" ], "description": "The status of the image.\n\n- `draft` - the project was created but has not been submitted for rendering\n- `queued` - the job is waiting for an available server\n- `rendering` - the job is being processed; the `image.started` webhook event fires when rendering begins\n- `complete` - the job finished successfully; fires `image.completed`\n- `error` - the job failed during processing; fires `image.errored`\n- `canceled` - the job was manually canceled (for example from the Magic Hour web app)\n\n**Note:** `rendering`, `complete`, and `error` have matching webhook events; `canceled` does not - a canceled job emits no webhook event, so poll this endpoint to detect cancellation." }, "type": { "type": "string", "description": "The type of the image project. Possible values are FACE_EDITOR, AI_IMAGE_EDITOR, GENERATIVE_FILL, AI_SELFIE, AI_HEADSHOT, AI_INFLUENCER, AI_IMAGE, AI_MEME, CLOTHES_CHANGER, BACKGROUND_REMOVER, FACE_SWAP, IMAGE_UPSCALER, IMAGE_ENHANCER, AI_GIF, QR_CODE, PHOTO_EDITOR, PHOTO_COLORIZER, IMAGE_COLOR_GRADER, HEAD_SWAP, BODY_SWAP, STORYBOARD, IMAGE_EXPANDER" }, "created_at": { "type": "string", "format": "date-time", "description": "Created at." }, "enabled": { "type": "boolean", "description": "Whether this resource is active. If false, it is deleted." }, "credits_charged": { "type": "integer", "description": "The amount of credits deducted from your account to generate the image. We charge credits right when the request is made. \n\nIf an error occurred while generating the image(s), credits will be refunded and this field will be updated to include the refund." }, "downloads": { "type": "array", "items": { "type": "object", "properties": { "url": { "type": "string", "format": "uri", "description": "Url." }, "expires_at": { "type": "string", "format": "date-time", "description": "Expires at." } }, "required": [ "url", "expires_at" ], "additionalProperties": true, "description": "The download URL and expiration time of the project." }, "description": "Downloads." }, "error": { "type": [ "object", "null" ], "properties": { "message": { "type": "string", "description": "Details on the reason why a failure happened." }, "code": { "type": "string", "description": "An error code to indicate why a failure happened." } }, "required": [ "message", "code" ], "additionalProperties": true, "description": "The error reported while rendering the project." }, "image_count": { "type": "integer", "description": "Number of images generated" } }, "required": [ "id", "name", "status", "type", "created_at", "enabled", "credits_charged", "downloads", "error", "image_count" ], "additionalProperties": true, "description": "Success" }, "nullable": false, "handle": "output" } ], "name": "Check the image project", "executor": { "kind": "connector", "action": "magichour.get_image_projects", "outputMode": "value" } } }, "n_b855a3029a885886": { "inputs": { "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_f2232a9bd444aa27", "output": "output" } ] }, "projectId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_d3f193b1bdaf8cd5", "output": "id" } ] }, "previous": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b855a3029a885886", "output": "state" } ] } }, "name": "Check image status", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "properties": { "id": { "type": "string", "description": "Unique ID of the image. Use it with the [Get image Project API](https://docs.magichour.ai/api-reference/image-projects/get-image-details) to fetch status and downloads." }, "name": { "type": [ "string", "null" ], "description": "The name of the image." }, "status": { "type": "string", "enum": [ "draft", "queued", "rendering", "complete", "error", "canceled" ], "description": "The status of the image.\n\n- `draft` - the project was created but has not been submitted for rendering\n- `queued` - the job is waiting for an available server\n- `rendering` - the job is being processed; the `image.started` webhook event fires when rendering begins\n- `complete` - the job finished successfully; fires `image.completed`\n- `error` - the job failed during processing; fires `image.errored`\n- `canceled` - the job was manually canceled (for example from the Magic Hour web app)\n\n**Note:** `rendering`, `complete`, and `error` have matching webhook events; `canceled` does not - a canceled job emits no webhook event, so poll this endpoint to detect cancellation." }, "type": { "type": "string", "description": "The type of the image project. Possible values are FACE_EDITOR, AI_IMAGE_EDITOR, GENERATIVE_FILL, AI_SELFIE, AI_HEADSHOT, AI_INFLUENCER, AI_IMAGE, AI_MEME, CLOTHES_CHANGER, BACKGROUND_REMOVER, FACE_SWAP, IMAGE_UPSCALER, IMAGE_ENHANCER, AI_GIF, QR_CODE, PHOTO_EDITOR, PHOTO_COLORIZER, IMAGE_COLOR_GRADER, HEAD_SWAP, BODY_SWAP, STORYBOARD, IMAGE_EXPANDER" }, "created_at": { "type": "string", "format": "date-time", "description": "Created at." }, "enabled": { "type": "boolean", "description": "Whether this resource is active. If false, it is deleted." }, "credits_charged": { "type": "integer", "description": "The amount of credits deducted from your account to generate the image. We charge credits right when the request is made. \n\nIf an error occurred while generating the image(s), credits will be refunded and this field will be updated to include the refund." }, "downloads": { "type": "array", "items": { "type": "object", "properties": { "url": { "type": "string", "format": "uri", "description": "Url." }, "expires_at": { "type": "string", "format": "date-time", "description": "Expires at." } }, "required": [ "url", "expires_at" ], "additionalProperties": true, "description": "The download URL and expiration time of the project." }, "description": "Downloads." }, "error": { "type": [ "object", "null" ], "properties": { "message": { "type": "string", "description": "Details on the reason why a failure happened." }, "code": { "type": "string", "description": "An error code to indicate why a failure happened." } }, "required": [ "message", "code" ], "additionalProperties": true, "description": "The error reported while rendering the project." }, "image_count": { "type": "integer", "description": "Number of images generated" } }, "required": [ "id", "name", "status", "type", "created_at", "enabled", "credits_charged", "downloads", "error", "image_count" ], "additionalProperties": true, "description": "Success" }, "nullable": false, "handle": "body" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "projectId" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previous" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "done" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" } ], "name": "Check image status", "moduleId": "n_d3d6d753d1b2777d", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_1d801dee58425cdd": { "inputs": {}, "name": "Image complete?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "yes", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_b855a3029a885886", "output": "done" } }, "operator": "isTrue" } ] } ] } ] }, "n_5311b976f63bed0c": { "inputs": { "url": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b855a3029a885886", "output": "url" } ] }, "id": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_d3f193b1bdaf8cd5", "output": "id" } ] } }, "name": "Download the image", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "id" } ], "outputs": [ { "jsonSchema": { "contentMediaType": "oomol/artifact" }, "nullable": false, "handle": "image" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "id" } ], "name": "Download the image", "moduleId": "n_7f69e3450b2ec8eb", "capabilities": [ { "kind": "http", "hosts": [ "videos.magichour.ai" ] } ] } }, "n_dd982e897fe2cfeb": { "name": "Generated image", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "contentMediaType": "oomol/artifact" }, "nullable": false, "handle": "image" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "id" } ], "outputMappings": { "image": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_5311b976f63bed0c", "output": "image" } ] }, "url": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_5311b976f63bed0c", "output": "url" } ] }, "id": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_5311b976f63bed0c", "output": "id" } ] } } } }, "edges": [ { "source": "n_df2f41e507406698", "target": "n_8d9e6dd8feee8c11" }, { "source": "n_8d9e6dd8feee8c11", "target": "n_d3f193b1bdaf8cd5" }, { "source": "n_d3f193b1bdaf8cd5", "target": "n_cdeb2f792aff4ca7" }, { "source": "n_cdeb2f792aff4ca7", "target": "n_f2232a9bd444aa27", "sourceHandle": "continue" }, { "source": "n_f2232a9bd444aa27", "target": "n_b855a3029a885886" }, { "source": "n_b855a3029a885886", "target": "n_1d801dee58425cdd" }, { "source": "n_1d801dee58425cdd", "target": "n_5311b976f63bed0c", "sourceHandle": "yes" }, { "source": "n_1d801dee58425cdd", "target": "n_cdeb2f792aff4ca7", "sourceHandle": "otherwise" }, { "source": "n_5311b976f63bed0c", "target": "n_dd982e897fe2cfeb" } ] } }, "modules": { "n_4764ad6fc40be637": { "name": "Read the project ID", "imports": [], "source": "export default function created({body}){if(!body||typeof body.id!=='string'||!body.id)throw Error('Missing project ID');return{id:body.id}}" }, "n_d3d6d753d1b2777d": { "name": "Check image status", "imports": [], "source": "export default function status({body,projectId,previous}){const {id,status,downloads}=body??{};if(id!==projectId)throw Error('Project ID mismatch');const count=previous==null?1:previous.count+1;if(!Number.isInteger(count)||count>60)throw Error('Polling timed out for project '+projectId);if(status==='error'||status==='canceled')throw Error('Generation '+status+' for project '+projectId);if(status==='complete'){if(!Array.isArray(downloads)||!downloads[0]||typeof downloads[0].url!=='string')throw Error('Missing download for project '+projectId);return{state:{count},done:true,url:downloads[0].url}}if(!['draft','queued','rendering'].includes(status))throw Error('Unexpected status for project '+projectId);if(count===60)throw Error('Polling timed out for project '+projectId);return{state:{count},done:false,url:''}}\n" }, "n_7f69e3450b2ec8eb": { "name": "Download the image", "imports": [], "source": "import{Buffer}from'node:buffer';\nexport default async function download({url,id},context){if(typeof url!=='string'||!/^https:\\/\\//.test(url))throw Error('Invalid image URL for project '+id);const r=await context.fetch(url,{method:'GET'});if(!r.ok)throw Error('Download failed for project '+id);const b=Buffer.from(await r.arrayBuffer()),png=b.length>8&&b.subarray(0,8).equals(Buffer.from([137,80,78,71,13,10,26,10])),jpeg=b.length>3&&b[0]===255&&b[1]===216&&b[2]===255;if(!png&&!jpeg)throw Error('Invalid image for project '+id);const mediaType=png?'image/png':'image/jpeg',image=await context.artifact.put(b,{name:png?'image.png':'image.jpg',mediaType});return{image,url,id}}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }