feat: add comfy namespace and update comfy examples

This commit is contained in:
Vedat Baday 2024-06-11 19:44:28 +03:00 committed by GitHub
commit 4b3080c9a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 53 additions and 343 deletions

View File

@ -2,7 +2,6 @@
import * as fal from '@fal-ai/serverless-client'; import * as fal from '@fal-ai/serverless-client';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import getWorkflow from './workflow';
// @snippet:start(client.config) // @snippet:start(client.config)
fal.config({ fal.config({
@ -84,11 +83,13 @@ export default function ComfyImageToImagePage() {
setLoading(true); setLoading(true);
const start = Date.now(); const start = Date.now();
try { try {
const result: Result = await fal.subscribe('fal-ai/comfy-server', { const result: Result = await fal.subscribe(
input: getWorkflow({ 'comfy/fal-ai/image-to-image',
{
input: {
prompt: prompt, prompt: prompt,
loadimage_1: imageFile, loadimage_1: imageFile,
}), },
pollInterval: 3000, // Default is 1000 (every 1s) pollInterval: 3000, // Default is 1000 (every 1s)
logs: true, logs: true,
onQueueUpdate(update) { onQueueUpdate(update) {
@ -100,7 +101,8 @@ export default function ComfyImageToImagePage() {
setLogs((update.logs || []).map((log) => log.message)); setLogs((update.logs || []).map((log) => log.message));
} }
}, },
}); }
);
setResult(getImageURL(result)); setResult(getImageURL(result));
} catch (error: any) { } catch (error: any) {
setError(error); setError(error);

View File

@ -2,7 +2,6 @@
import * as fal from '@fal-ai/serverless-client'; import * as fal from '@fal-ai/serverless-client';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import getWorkflow from './workflow';
// @snippet:start(client.config) // @snippet:start(client.config)
fal.config({ fal.config({
@ -80,10 +79,12 @@ export default function ComfyImageToVideoPage() {
setLoading(true); setLoading(true);
const start = Date.now(); const start = Date.now();
try { try {
const result: Result = await fal.subscribe('fal-ai/comfy-server', { const result: Result = await fal.subscribe(
input: getWorkflow({ 'comfy/fal-ai/image-to-video',
{
input: {
loadimage_1: imageFile, loadimage_1: imageFile,
}), },
pollInterval: 3000, // Default is 1000 (every 1s) pollInterval: 3000, // Default is 1000 (every 1s)
logs: true, logs: true,
onQueueUpdate(update) { onQueueUpdate(update) {
@ -95,7 +96,8 @@ export default function ComfyImageToVideoPage() {
setLogs((update.logs || []).map((log) => log.message)); setLogs((update.logs || []).map((log) => log.message));
} }
}, },
}); }
);
setResult(getImageURL(result)); setResult(getImageURL(result));
} catch (error: any) { } catch (error: any) {
setError(error); setError(error);

View File

@ -1,102 +0,0 @@
// This workflow is generated with ComfyUI-fal
const WORKFLOW = {
prompt: {
'3': {
inputs: {
seed: 280823642470253,
steps: 20,
cfg: 8,
sampler_name: 'dpmpp_2m',
scheduler: 'normal',
denoise: 0.8700000000000001,
model: ['14', 0],
positive: ['6', 0],
negative: ['7', 0],
latent_image: ['12', 0],
},
class_type: 'KSampler',
},
'6': {
inputs: {
text: ['15', 0],
clip: ['14', 1],
},
class_type: 'CLIPTextEncode',
},
'7': {
inputs: {
text: 'watermark, text\n',
clip: ['14', 1],
},
class_type: 'CLIPTextEncode',
},
'8': {
inputs: {
samples: ['3', 0],
vae: ['14', 2],
},
class_type: 'VAEDecode',
},
'9': {
inputs: {
filename_prefix: 'ComfyUI',
images: ['8', 0],
},
class_type: 'SaveImage',
},
'10': {
inputs: {
image: 'example.png',
upload: 'image',
},
class_type: 'LoadImage',
},
'12': {
inputs: {
pixels: ['10', 0],
vae: ['14', 2],
},
class_type: 'VAEEncode',
},
'14': {
inputs: {
ckpt_name: 'v1-5-pruned-emaonly.ckpt',
},
class_type: 'CheckpointLoaderSimple',
},
'15': {
inputs: {
name: 'prompt',
value:
'photograph of victorian woman with wings, sky clouds, meadow grass\n',
},
class_type: 'StringInput_fal',
},
},
extra_data: {},
fal_inputs_dev_info: {
loadimage_1: {
key: ['10', 'inputs', 'image'],
class_type: 'LoadImage',
},
prompt: {
key: ['15', 'inputs', 'value'],
class_type: 'StringInput_fal',
},
},
fal_inputs: {
loadimage_1: 'example_url',
prompt:
'photograph of victorian woman with wings, sky clouds, meadow grass\n',
},
};
export default function getWorkflow(object: any) {
const newWorkflow = JSON.parse(JSON.stringify(WORKFLOW));
newWorkflow.fal_inputs = {
...newWorkflow.fal_inputs,
...object,
};
return newWorkflow;
}

View File

@ -1,91 +0,0 @@
const WORKFLOW = {
prompt: {
'3': {
inputs: {
seed: 351912937281939,
steps: 20,
cfg: 2.5,
sampler_name: 'euler',
scheduler: 'karras',
denoise: 1,
model: ['14', 0],
positive: ['12', 0],
negative: ['12', 1],
latent_image: ['12', 2],
},
class_type: 'KSampler',
},
'8': {
inputs: {
samples: ['3', 0],
vae: ['15', 2],
},
class_type: 'VAEDecode',
},
'10': {
inputs: {
filename_prefix: 'ComfyUI',
fps: 10,
lossless: false,
quality: 85,
method: 'default',
images: ['8', 0],
},
class_type: 'SaveAnimatedWEBP',
},
'12': {
inputs: {
width: 1024,
height: 576,
video_frames: 14,
motion_bucket_id: 127,
fps: 6,
augmentation_level: 0,
clip_vision: ['15', 1],
init_image: ['23', 0],
vae: ['15', 2],
},
class_type: 'SVD_img2vid_Conditioning',
},
'14': {
inputs: {
min_cfg: 1,
model: ['15', 0],
},
class_type: 'VideoLinearCFGGuidance',
},
'15': {
inputs: {
ckpt_name: 'svd.safetensors',
},
class_type: 'ImageOnlyCheckpointLoader',
},
'23': {
inputs: {
image: '18.png',
upload: 'image',
},
class_type: 'LoadImage',
},
},
extra_data: {},
fal_inputs_dev_info: {
loadimage_1: {
key: ['23', 'inputs', 'image'],
class_type: 'LoadImage',
},
},
fal_inputs: {
loadimage_1: 'example_url',
},
};
export default function getWorkflow(object: any) {
const newWorkflow = JSON.parse(JSON.stringify(WORKFLOW));
newWorkflow.fal_inputs = {
...newWorkflow.fal_inputs,
...object,
};
return newWorkflow;
}

View File

@ -16,19 +16,19 @@ export default function Index() {
</p> </p>
<div className="mt-12 grid grid-cols-1 gap-3 md:grid-cols-3 lg:grid-cols-3"> <div className="mt-12 grid grid-cols-1 gap-3 md:grid-cols-3 lg:grid-cols-3">
<button <button
onClick={() => router.push('/comfy/text_to_image')} onClick={() => router.push('/comfy/text-to-image')}
className="px-6 py-3 bg-blue-600 hover:bg-blue-500 text-white rounded-lg shadow-md transition-transform transform hover:-translate-y-1" className="px-6 py-3 bg-blue-600 hover:bg-blue-500 text-white rounded-lg shadow-md transition-transform transform hover:-translate-y-1"
> >
Text to Image Text to Image
</button> </button>
<button <button
onClick={() => router.push('/comfy/image_to_image')} onClick={() => router.push('/comfy/image-to-image')}
className="px-6 py-3 bg-blue-600 hover:bg-blue-500 text-white rounded-lg shadow-md transition-transform transform hover:-translate-y-1" className="px-6 py-3 bg-blue-600 hover:bg-blue-500 text-white rounded-lg shadow-md transition-transform transform hover:-translate-y-1"
> >
Image to Image Image to Image
</button> </button>
<button <button
onClick={() => router.push('/comfy/image_to_video')} onClick={() => router.push('/comfy/image-to-video')}
className="px-6 py-3 bg-blue-600 hover:bg-blue-500 text-white rounded-lg shadow-md transition-transform transform hover:-translate-y-1" className="px-6 py-3 bg-blue-600 hover:bg-blue-500 text-white rounded-lg shadow-md transition-transform transform hover:-translate-y-1"
> >
Image to Video Image to Video

View File

@ -2,7 +2,6 @@
import * as fal from '@fal-ai/serverless-client'; import * as fal from '@fal-ai/serverless-client';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import getWorkflow from './workflow';
// @snippet:start(client.config) // @snippet:start(client.config)
fal.config({ fal.config({
@ -83,10 +82,10 @@ export default function ComfyTextToImagePage() {
setLoading(true); setLoading(true);
const start = Date.now(); const start = Date.now();
try { try {
const result: Result = await fal.subscribe('fal-ai/comfy-server', { const result: Result = await fal.subscribe('comfy/fal-ai/text-to-image', {
input: getWorkflow({ input: {
prompt: prompt, prompt: prompt,
}), },
pollInterval: 3000, // Default is 1000 (every 1s) pollInterval: 3000, // Default is 1000 (every 1s)
logs: true, logs: true,
onQueueUpdate(update) { onQueueUpdate(update) {

View File

@ -1,103 +0,0 @@
// This workflow is generated with ComfyUI-fal
const WORKFLOW = {
prompt: {
'3': {
inputs: {
seed: 704126934460886,
steps: 20,
cfg: 8,
sampler_name: 'euler',
scheduler: 'normal',
denoise: 1,
model: ['4', 0],
positive: ['6', 0],
negative: ['7', 0],
latent_image: ['5', 0],
},
class_type: 'KSampler',
},
'4': {
inputs: {
ckpt_name: 'sd_xl_1.0.safetensors',
},
class_type: 'CheckpointLoaderSimple',
},
'5': {
inputs: {
width: 1024,
height: 1024,
batch_size: 1,
},
class_type: 'EmptyLatentImage',
},
'6': {
inputs: {
text: ['10', 0],
clip: ['4', 1],
},
class_type: 'CLIPTextEncode',
},
'7': {
inputs: {
text: ['11', 0],
clip: ['4', 1],
},
class_type: 'CLIPTextEncode',
},
'8': {
inputs: {
samples: ['3', 0],
vae: ['4', 2],
},
class_type: 'VAEDecode',
},
'9': {
inputs: {
filename_prefix: 'ComfyUI',
images: ['8', 0],
},
class_type: 'SaveImage',
},
'10': {
inputs: {
name: 'prompt',
value:
'beautiful scenery nature glass bottle landscape, , purple galaxy bottle,',
},
class_type: 'StringInput_fal',
},
'11': {
inputs: {
name: 'negative_prompt',
value: 'text, watermark',
},
class_type: 'StringInput_fal',
},
},
extra_data: {},
fal_inputs_dev_info: {
prompt: {
key: ['10', 'inputs', 'value'],
class_type: 'StringInput_fal',
},
negative_prompt: {
key: ['11', 'inputs', 'value'],
class_type: 'StringInput_fal',
},
},
fal_inputs: {
prompt:
'beautiful scenery nature glass bottle landscape, , purple galaxy bottle,',
negative_prompt: 'text, watermark',
},
};
export default function getWorkflow(object: any) {
const newWorkflow = JSON.parse(JSON.stringify(WORKFLOW));
newWorkflow.fal_inputs = {
...newWorkflow.fal_inputs,
...object,
};
return newWorkflow;
}

View File

@ -103,7 +103,7 @@ export const storageImpl: StorageSupport = {
}, },
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
transformInput: async (input: any) => { transformInput: async (input: any): Promise<any> => {
if (Array.isArray(input)) { if (Array.isArray(input)) {
return Promise.all(input.map((item) => storageImpl.transformInput(item))); return Promise.all(input.map((item) => storageImpl.transformInput(item)));
} else if ( } else if (
@ -119,9 +119,12 @@ export const storageImpl: StorageSupport = {
const url = await storageImpl.upload(blob as Blob); const url = await storageImpl.upload(blob as Blob);
return url; return url;
} else if (isPlainObject(input)) { } else if (isPlainObject(input)) {
const promises = Object.entries(input).map(async ([key, value]) => { const inputObject = input as Record<string, any>;
const promises = Object.entries(inputObject).map(
async ([key, value]): Promise<KeyValuePair> => {
return [key, await storageImpl.transformInput(value)]; return [key, await storageImpl.transformInput(value)];
}); }
);
const results = await Promise.all(promises); const results = await Promise.all(promises);
return Object.fromEntries(results); return Object.fromEntries(results);
} }

View File

@ -21,7 +21,7 @@ export function ensureAppIdFormat(id: string): string {
); );
} }
const APP_NAMESPACES = ['workflows'] as const; const APP_NAMESPACES = ['workflows', 'comfy'] as const;
type AppNamespace = (typeof APP_NAMESPACES)[number]; type AppNamespace = (typeof APP_NAMESPACES)[number];