radames commited on
Commit
1123781
1 Parent(s): cb92d2b

adding new components

Browse files
README.md CHANGED
@@ -102,6 +102,12 @@ or with environment variables
102
  ```bash
103
  docker run -ti -e TIMEOUT=0 -e SAFETY_CHECKER=False -p 7860:7860 --gpus all lcm-live
104
  ```
 
 
 
 
 
 
105
 
106
  # Demo on Hugging Face
107
 
 
102
  ```bash
103
  docker run -ti -e TIMEOUT=0 -e SAFETY_CHECKER=False -p 7860:7860 --gpus all lcm-live
104
  ```
105
+ # Development Mode
106
+
107
+
108
+ ```bash
109
+ python run.py --reload
110
+ ```
111
 
112
  # Demo on Hugging Face
113
 
app_init.py CHANGED
@@ -140,6 +140,6 @@ def init_app(app: FastAPI, user_queue_map: UserQueueDict, args: Args, pipeline):
140
  @app.get("/settings")
141
  async def settings():
142
  params = pipeline.InputParams()
143
- return JSONResponse({"settings": params.dict()})
144
 
145
  app.mount("/", StaticFiles(directory="public", html=True), name="public")
 
140
  @app.get("/settings")
141
  async def settings():
142
  params = pipeline.InputParams()
143
+ return JSONResponse(params.model_json_schema())
144
 
145
  app.mount("/", StaticFiles(directory="public", html=True), name="public")
frontend/package-lock.json CHANGED
@@ -9,6 +9,7 @@
9
  "version": "0.0.1",
10
  "devDependencies": {
11
  "@sveltejs/adapter-auto": "^2.0.0",
 
12
  "@sveltejs/kit": "^1.20.4",
13
  "@typescript-eslint/eslint-plugin": "^6.0.0",
14
  "@typescript-eslint/parser": "^6.0.0",
@@ -614,6 +615,15 @@
614
  "@sveltejs/kit": "^1.0.0"
615
  }
616
  },
 
 
 
 
 
 
 
 
 
617
  "node_modules/@sveltejs/kit": {
618
  "version": "1.27.5",
619
  "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.27.5.tgz",
 
9
  "version": "0.0.1",
10
  "devDependencies": {
11
  "@sveltejs/adapter-auto": "^2.0.0",
12
+ "@sveltejs/adapter-static": "^2.0.3",
13
  "@sveltejs/kit": "^1.20.4",
14
  "@typescript-eslint/eslint-plugin": "^6.0.0",
15
  "@typescript-eslint/parser": "^6.0.0",
 
615
  "@sveltejs/kit": "^1.0.0"
616
  }
617
  },
618
+ "node_modules/@sveltejs/adapter-static": {
619
+ "version": "2.0.3",
620
+ "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-2.0.3.tgz",
621
+ "integrity": "sha512-VUqTfXsxYGugCpMqQv1U0LIdbR3S5nBkMMDmpjGVJyM6Q2jHVMFtdWJCkeHMySc6mZxJ+0eZK3T7IgmUCDrcUQ==",
622
+ "dev": true,
623
+ "peerDependencies": {
624
+ "@sveltejs/kit": "^1.5.0"
625
+ }
626
+ },
627
  "node_modules/@sveltejs/kit": {
628
  "version": "1.27.5",
629
  "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.27.5.tgz",
frontend/package.json CHANGED
@@ -13,6 +13,7 @@
13
  },
14
  "devDependencies": {
15
  "@sveltejs/adapter-auto": "^2.0.0",
 
16
  "@sveltejs/kit": "^1.20.4",
17
  "@typescript-eslint/eslint-plugin": "^6.0.0",
18
  "@typescript-eslint/parser": "^6.0.0",
 
13
  },
14
  "devDependencies": {
15
  "@sveltejs/adapter-auto": "^2.0.0",
16
+ "@sveltejs/adapter-static": "^2.0.3",
17
  "@sveltejs/kit": "^1.20.4",
18
  "@typescript-eslint/eslint-plugin": "^6.0.0",
19
  "@typescript-eslint/parser": "^6.0.0",
frontend/src/lib/components/Button.svelte ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <button class="button" on:click> <slot /> </button>
2
+
3
+ <style lang="postcss">
4
+ .button {
5
+ @apply rounded bg-gray-700 p-2 font-normal text-white hover:bg-gray-800 disabled:cursor-not-allowed disabled:bg-gray-300 dark:disabled:bg-gray-700 dark:disabled:text-black;
6
+ }
7
+ </style>
frontend/src/lib/components/ImagePlayer.svelte ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ </script>
3
+
4
+ <div class="relative overflow-hidden rounded-lg border border-slate-300">
5
+ <!-- svelte-ignore a11y-missing-attribute -->
6
+ <img
7
+ class="aspect-square w-full rounded-lg"
8
+ src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
9
+ />
10
+ <div class="absolute left-0 top-0 aspect-square w-1/4">
11
+ <div class="relative z-10 aspect-square w-full object-cover">
12
+ <slot />
13
+ </div>
14
+ <svg
15
+ xmlns="http://www.w3.org/2000/svg"
16
+ viewBox="0 0 448 448"
17
+ width="100"
18
+ class="absolute top-0 z-0 w-full p-4 opacity-20"
19
+ >
20
+ <path
21
+ fill="currentColor"
22
+ d="M224 256a128 128 0 1 0 0-256 128 128 0 1 0 0 256zm-45.7 48A178.3 178.3 0 0 0 0 482.3 29.7 29.7 0 0 0 29.7 512h388.6a29.7 29.7 0 0 0 29.7-29.7c0-98.5-79.8-178.3-178.3-178.3h-91.4z"
23
+ />
24
+ </svg>
25
+ </div>
26
+ </div>
frontend/src/lib/components/InputRange.svelte ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import type { FieldProps } from '$lib/types';
3
+ import { onMount } from 'svelte';
4
+ export let value = 8.0;
5
+ export let params: FieldProps;
6
+ onMount(() => {
7
+ value = Number(params?.default) ?? 8.0;
8
+ });
9
+ </script>
10
+
11
+ <div class="grid max-w-md grid-cols-4 items-center gap-3">
12
+ <label class="text-sm font-medium" for="guidance-scale">{params?.title}</label>
13
+ <input
14
+ class="col-span-2"
15
+ bind:value
16
+ type="range"
17
+ id="guidance-scale"
18
+ name="guidance-scale"
19
+ min={params?.min}
20
+ max={params?.max}
21
+ step={params?.step ?? 1}
22
+ />
23
+ <input
24
+ type="number"
25
+ step={params?.step ?? 1}
26
+ bind:value
27
+ class="rounded-md border border-gray-700 px-1 py-1 text-center text-xs font-light"
28
+ />
29
+ </div>
frontend/src/lib/components/PipelineOptions.svelte ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { createEventDispatcher } from 'svelte';
3
+ import type { FieldProps } from '$lib/types';
4
+ import { FieldType } from '$lib/types';
5
+ import InputRange from './InputRange.svelte';
6
+ import SeedInput from './SeedInput.svelte';
7
+ import TextArea from './TextArea.svelte';
8
+
9
+ export let pipelineParams: FieldProps[];
10
+ export let pipelineValues = {} as any;
11
+
12
+ $: advanceOptions = pipelineParams?.filter((e) => e?.hide == true);
13
+ $: featuredOptions = pipelineParams?.filter((e) => e?.hide !== true);
14
+ </script>
15
+
16
+ <div>
17
+ {#if featuredOptions}
18
+ {#each featuredOptions as params}
19
+ {#if params.field === FieldType.range}
20
+ <InputRange {params} bind:value={pipelineValues[params.title]}></InputRange>
21
+ {:else if params.field === FieldType.seed}
22
+ <SeedInput bind:value={pipelineValues[params.title]}></SeedInput>
23
+ {:else if params.field === FieldType.textarea}
24
+ <TextArea {params} bind:value={pipelineValues[params.title]}></TextArea>
25
+ {/if}
26
+ {/each}
27
+ {/if}
28
+ </div>
29
+
30
+ <details open>
31
+ <summary class="cursor-pointer font-medium">Advanced Options</summary>
32
+ <div class="flex flex-col gap-3 py-3">
33
+ {#if advanceOptions}
34
+ {#each advanceOptions as params}
35
+ {#if params.field === FieldType.range}
36
+ <InputRange {params} bind:value={pipelineValues[params.title]}></InputRange>
37
+ {:else if params.field === FieldType.seed}
38
+ <SeedInput bind:value={pipelineValues[params.title]}></SeedInput>
39
+ {:else if params.field === FieldType.textarea}
40
+ <TextArea {params} bind:value={pipelineValues[params.title]}></TextArea>
41
+ {/if}
42
+ {/each}
43
+ {/if}
44
+ </div>
45
+ </details>
frontend/src/lib/components/SeedInput.svelte ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import Button from './Button.svelte';
3
+ export let value = 299792458;
4
+
5
+ function randomize() {
6
+ value = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
7
+ }
8
+ </script>
9
+
10
+ <div class="grid max-w-md grid-cols-4 items-center gap-3">
11
+ <label class="text-sm font-medium" for="seed">Seed</label>
12
+ <input
13
+ bind:value
14
+ type="number"
15
+ id="seed"
16
+ name="seed"
17
+ class="col-span-2 rounded-md border border-gray-700 p-2 text-right font-light dark:text-black"
18
+ />
19
+ <Button on:click={randomize}>Randomize</Button>
20
+ </div>
frontend/src/lib/components/TextArea.svelte ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import type { FieldProps } from '$lib/types';
3
+ import { onMount } from 'svelte';
4
+ export let value: string;
5
+ export let params: FieldProps;
6
+ onMount(() => {
7
+ value = String(params?.default ?? '');
8
+ });
9
+ </script>
10
+
11
+ <div class="text-normal flex items-center rounded-md border border-gray-700 px-1 py-1">
12
+ <textarea
13
+ class="mx-1 w-full px-3 py-2 font-light outline-none dark:text-black"
14
+ title={params?.title}
15
+ placeholder="Add your prompt here..."
16
+ bind:value
17
+ ></textarea>
18
+ </div>
frontend/src/lib/components/VideoInput.svelte ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ <script lang="ts">
2
+ </script>
3
+
4
+ <video playsinline autoplay muted loop />
frontend/src/lib/types.ts CHANGED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const enum FieldType {
2
+ range = "range",
3
+ seed = "seed",
4
+ textarea = "textarea",
5
+ }
6
+
7
+ export interface FieldProps {
8
+ default: number | string;
9
+ max?: number;
10
+ min?: number;
11
+ title: string;
12
+ field: FieldType;
13
+ step?: number;
14
+ disabled?: boolean;
15
+ hide?: boolean;
16
+ }
frontend/src/routes/+page.svelte CHANGED
@@ -1,21 +1,34 @@
1
  <script lang="ts">
2
  import { onMount } from 'svelte';
3
  import { PUBLIC_BASE_URL } from '$env/static/public';
 
 
 
 
 
 
 
 
4
 
5
  onMount(() => {
6
  getSettings();
7
  });
 
8
  async function getSettings() {
9
  const settings = await fetch(`${PUBLIC_BASE_URL}/settings`).then((r) => r.json());
10
- console.log(settings);
 
 
 
 
 
11
  }
12
  </script>
13
 
14
  <div class="fixed right-2 top-2 max-w-xs rounded-lg p-4 text-center text-sm font-bold" id="error" />
15
- <main class="container mx-auto flex max-w-4xl flex-col gap-4 px-4 py-4">
16
- <article class="mx-auto max-w-xl text-center">
17
  <h1 class="text-3xl font-bold">Real-Time Latent Consistency Model</h1>
18
- <h2 class="mb-4 text-2xl font-bold">Image to Image</h2>
19
  <p class="text-sm">
20
  This demo showcases
21
  <a
@@ -40,121 +53,28 @@
40
  > and run it on your own GPU.
41
  </p>
42
  </article>
43
- <div>
44
- <h2 class="font-medium">Prompt</h2>
45
- <p class="text-sm text-gray-500">
46
- Change the prompt to generate different images, accepts <a
47
- href="https://github.com/damian0815/compel/blob/main/doc/syntax.md"
48
- target="_blank"
49
- class="text-blue-500 underline hover:no-underline">Compel</a
50
- > syntax.
51
- </p>
52
- <div class="text-normal flex items-center rounded-md border border-gray-700 px-1 py-1">
53
- <textarea
54
- type="text"
55
- id="prompt"
56
- class="mx-1 w-full px-3 py-2 font-light outline-none dark:text-black"
57
- title="Prompt, this is an example, feel free to modify"
58
- placeholder="Add your prompt here..."
59
- >Portrait of The Terminator with , glare pose, detailed, intricate, full of colour,
60
- cinematic lighting, trending on artstation, 8k, hyperrealistic, focused, extreme details,
61
- unreal engine 5, cinematic, masterpiece</textarea
62
- >
63
- </div>
64
- </div>
65
- <div class="">
66
- <details>
67
- <summary class="cursor-pointer font-medium">Advanced Options</summary>
68
- <div class="grid max-w-md grid-cols-3 items-center gap-3 py-3">
69
- <label class="text-sm font-medium" for="guidance-scale">Guidance Scale </label>
70
- <input
71
- type="range"
72
- id="guidance-scale"
73
- name="guidance-scale"
74
- min="1"
75
- max="30"
76
- step="0.001"
77
- value="8.0"
78
- oninput="this.nextElementSibling.value = Number(this.value).toFixed(2)"
79
- />
80
- <output
81
- class="w-[50px] rounded-md border border-gray-700 px-1 py-1 text-center text-xs font-light"
82
- >
83
- 8.0</output
84
- >
85
- <label class="text-sm font-medium" for="strength">Strength</label>
86
- <input
87
- type="range"
88
- id="strength"
89
- name="strength"
90
- min="0.20"
91
- max="1"
92
- step="0.001"
93
- value="0.50"
94
- oninput="this.nextElementSibling.value = Number(this.value).toFixed(2)"
95
- />
96
- <output
97
- class="w-[50px] rounded-md border border-gray-700 px-1 py-1 text-center text-xs font-light"
98
- >
99
- 0.5</output
100
- >
101
- <label class="text-sm font-medium" for="seed">Seed</label>
102
- <input
103
- type="number"
104
- id="seed"
105
- name="seed"
106
- value="299792458"
107
- class="rounded-md border border-gray-700 p-2 text-right font-light dark:text-black"
108
- />
109
- <button
110
- onclick="document.querySelector('#seed').value = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)"
111
- class="button"
112
- >
113
- Rand
114
- </button>
115
- </div>
116
- </details>
117
- </div>
118
  <div class="flex gap-3">
119
- <button id="start" class="button"> Start </button>
120
- <button id="stop" class="button"> Stop </button>
121
- <button id="snap" disabled class="button ml-auto"> Snapshot </button>
122
- </div>
123
- <div class="relative overflow-hidden rounded-lg border border-slate-300">
124
- <img
125
- id="player"
126
- class="aspect-square w-full rounded-lg"
127
- src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
128
- />
129
- <div class="absolute left-0 top-0 aspect-square w-1/4">
130
- <video
131
- id="webcam"
132
- class="relative z-10 aspect-square w-full object-cover"
133
- playsinline
134
- autoplay
135
- muted
136
- loop
137
- />
138
- <svg
139
- xmlns="http://www.w3.org/2000/svg"
140
- viewBox="0 0 448 448"
141
- width="100"
142
- class="absolute top-0 z-0 w-full p-4 opacity-20"
143
- >
144
- <path
145
- fill="currentColor"
146
- d="M224 256a128 128 0 1 0 0-256 128 128 0 1 0 0 256zm-45.7 48A178.3 178.3 0 0 0 0 482.3 29.7 29.7 0 0 0 29.7 512h388.6a29.7 29.7 0 0 0 29.7-29.7c0-98.5-79.8-178.3-178.3-178.3h-91.4z"
147
- />
148
- </svg>
149
- </div>
150
  </div>
 
 
 
 
151
  </main>
152
 
153
  <style lang="postcss">
154
  :global(html) {
155
  @apply text-black dark:bg-gray-900 dark:text-white;
156
  }
157
- .button {
158
- @apply rounded bg-gray-700 p-2 font-normal text-white hover:bg-gray-800 disabled:cursor-not-allowed disabled:bg-gray-300 dark:disabled:bg-gray-700 dark:disabled:text-black;
159
- }
160
  </style>
 
1
  <script lang="ts">
2
  import { onMount } from 'svelte';
3
  import { PUBLIC_BASE_URL } from '$env/static/public';
4
+ import type { FieldProps } from '$lib/types';
5
+ import ImagePlayer from '$lib/components/ImagePlayer.svelte';
6
+ import VideoInput from '$lib/components/VideoInput.svelte';
7
+ import Button from '$lib/components/Button.svelte';
8
+ import PipelineOptions from '$lib/components/PipelineOptions.svelte';
9
+
10
+ let pipelineParams: FieldProps[];
11
+ let pipelineValues = {};
12
 
13
  onMount(() => {
14
  getSettings();
15
  });
16
+
17
  async function getSettings() {
18
  const settings = await fetch(`${PUBLIC_BASE_URL}/settings`).then((r) => r.json());
19
+ pipelineParams = Object.values(settings.properties);
20
+ pipelineParams = pipelineParams.filter((e) => e?.disabled !== true);
21
+ }
22
+
23
+ $: {
24
+ console.log('PARENT', pipelineValues);
25
  }
26
  </script>
27
 
28
  <div class="fixed right-2 top-2 max-w-xs rounded-lg p-4 text-center text-sm font-bold" id="error" />
29
+ <main class="container mx-auto flex max-w-4xl flex-col gap-3 px-4 py-4">
30
+ <article class="flex- mx-auto max-w-xl text-center">
31
  <h1 class="text-3xl font-bold">Real-Time Latent Consistency Model</h1>
 
32
  <p class="text-sm">
33
  This demo showcases
34
  <a
 
53
  > and run it on your own GPU.
54
  </p>
55
  </article>
56
+ <h2 class="font-medium">Prompt</h2>
57
+ <p class="text-sm text-gray-500">
58
+ Change the prompt to generate different images, accepts <a
59
+ href="https://github.com/damian0815/compel/blob/main/doc/syntax.md"
60
+ target="_blank"
61
+ class="text-blue-500 underline hover:no-underline">Compel</a
62
+ > syntax.
63
+ </p>
64
+ <PipelineOptions {pipelineParams} bind:pipelineValues></PipelineOptions>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  <div class="flex gap-3">
66
+ <Button>Start</Button>
67
+ <Button>Stop</Button>
68
+ <Button>Snapshot</Button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  </div>
70
+
71
+ <ImagePlayer>
72
+ <VideoInput></VideoInput>
73
+ </ImagePlayer>
74
  </main>
75
 
76
  <style lang="postcss">
77
  :global(html) {
78
  @apply text-black dark:bg-gray-900 dark:text-white;
79
  }
 
 
 
80
  </style>
pipelines/txt2img.py CHANGED
@@ -9,23 +9,48 @@ except:
9
 
10
  import psutil
11
  from config import Args
12
- from pydantic import BaseModel
13
  from PIL import Image
14
  from typing import Callable
15
 
16
  base_model = "SimianLuo/LCM_Dreamshaper_v7"
17
  taesd_model = "madebyollin/taesd"
18
 
 
 
19
 
20
  class Pipeline:
21
  class InputParams(BaseModel):
22
- seed: int = 2159232
23
- prompt: str = ""
24
- guidance_scale: float = 8.0
25
- strength: float = 0.5
26
- steps: int = 4
27
- width: int = 512
28
- height: int = 512
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
  def __init__(self, args: Args, device: torch.device, torch_dtype: torch.dtype):
31
  if args.safety_checker:
 
9
 
10
  import psutil
11
  from config import Args
12
+ from pydantic import BaseModel, Field
13
  from PIL import Image
14
  from typing import Callable
15
 
16
  base_model = "SimianLuo/LCM_Dreamshaper_v7"
17
  taesd_model = "madebyollin/taesd"
18
 
19
+ default_prompt = "Portrait of The Terminator with , glare pose, detailed, intricate, full of colour, cinematic lighting, trending on artstation, 8k, hyperrealistic, focused, extreme details, unreal engine 5 cinematic, masterpiece"
20
+
21
 
22
  class Pipeline:
23
  class InputParams(BaseModel):
24
+ prompt: str = Field(
25
+ default_prompt,
26
+ title="Prompt",
27
+ field="textarea",
28
+ )
29
+ seed: int = Field(2159232, min=0, title="Seed", field="seed", hide=True)
30
+ strength: float = Field(
31
+ 0.5,
32
+ min=0,
33
+ max=1,
34
+ step=0.001,
35
+ title="Strength",
36
+ field="range",
37
+ hide=True,
38
+ )
39
+
40
+ steps: int = Field(4, min=2, max=15, title="Steps", field="range", hide=True)
41
+ width: int = Field(512, min=2, max=15, title="Width", disabled=True, hide=True)
42
+ height: int = Field(
43
+ 512, min=2, max=15, title="Height", disabled=True, hide=True
44
+ )
45
+ guidance_scale: float = Field(
46
+ 8.0,
47
+ min=1,
48
+ max=30,
49
+ step=0.001,
50
+ title="Guidance Scale",
51
+ field="range",
52
+ hide=True,
53
+ )
54
 
55
  def __init__(self, args: Args, device: torch.device, torch_dtype: torch.dtype):
56
  if args.safety_checker:
util.py CHANGED
@@ -1,5 +1,7 @@
1
  from importlib import import_module
2
  from types import ModuleType
 
 
3
 
4
 
5
  def get_pipeline_class(pipeline_name: str) -> ModuleType:
@@ -11,6 +13,6 @@ def get_pipeline_class(pipeline_name: str) -> ModuleType:
11
  pipeline_class = getattr(module, "Pipeline", None)
12
 
13
  if pipeline_class is None:
14
- raise ValueError(f"'Pipeline' class not found in module '{module_name}'.")
15
 
16
  return pipeline_class
 
1
  from importlib import import_module
2
  from types import ModuleType
3
+ from typing import Dict, Any
4
+ from pydantic import BaseModel as PydanticBaseModel, Field
5
 
6
 
7
  def get_pipeline_class(pipeline_name: str) -> ModuleType:
 
13
  pipeline_class = getattr(module, "Pipeline", None)
14
 
15
  if pipeline_class is None:
16
+ raise ValueError(f"'Pipeline' class not found in module '{pipeline_name}'.")
17
 
18
  return pipeline_class