diffusers-workflow 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diffusers_workflow-0.4.0.dist-info/METADATA +318 -0
- diffusers_workflow-0.4.0.dist-info/RECORD +260 -0
- diffusers_workflow-0.4.0.dist-info/WHEEL +5 -0
- diffusers_workflow-0.4.0.dist-info/entry_points.txt +7 -0
- diffusers_workflow-0.4.0.dist-info/licenses/LICENSE +201 -0
- diffusers_workflow-0.4.0.dist-info/top_level.txt +2 -0
- dw/__init__.py +440 -0
- dw/adapter_compatibility.py +226 -0
- dw/arguments.py +1231 -0
- dw/assessment_rules.py +159 -0
- dw/assets.py +130 -0
- dw/cache_blocks.json +16 -0
- dw/cache_blocks.py +146 -0
- dw/community_pipelines/pipeline_flux_rf_inversion.py +1184 -0
- dw/content_types.py +150 -0
- dw/dissolve_frame_errors.py +121 -0
- dw/docs/ACCELERATION.md +352 -0
- dw/docs/AGENT_LOOP.md +95 -0
- dw/docs/DEPENDENCIES.md +91 -0
- dw/docs/IP_ADAPTER.md +109 -0
- dw/docs/LORAS.md +131 -0
- dw/docs/MCP.md +517 -0
- dw/docs/PROMPT_WEIGHTING.md +78 -0
- dw/docs/QUANTIZATION.md +230 -0
- dw/docs/RECIPES_24GB.md +201 -0
- dw/docs/RELEASING.md +195 -0
- dw/docs/REMOTE.md +140 -0
- dw/docs/REPL_COMMANDS.md +121 -0
- dw/docs/REPL_WORKER_GUIDE.md +51 -0
- dw/docs/SECURITY.md +272 -0
- dw/docs/SECURITY_QUICKREF.md +112 -0
- dw/docs/SERVER.md +679 -0
- dw/docs/TASKS.md +1741 -0
- dw/docs/TESTING.md +71 -0
- dw/docs/WORKFLOW_GUIDE.md +2038 -0
- dw/docs/WORKSPACES.md +316 -0
- dw/download_watch.py +335 -0
- dw/elision.py +306 -0
- dw/events.py +275 -0
- dw/for_each.py +409 -0
- dw/host_memory.py +258 -0
- dw/host_memory_projection.py +230 -0
- dw/hub_cache.py +432 -0
- dw/introspection.py +1228 -0
- dw/kernel_availability.py +208 -0
- dw/locations.py +599 -0
- dw/log_setup.py +45 -0
- dw/loudness.py +82 -0
- dw/media_audio.py +217 -0
- dw/media_frames.py +367 -0
- dw/media_info.py +297 -0
- dw/pipeline_processors/chain.py +821 -0
- dw/pipeline_processors/config_objects.py +237 -0
- dw/pipeline_processors/pipeline.py +2297 -0
- dw/pipeline_processors/remote.py +46 -0
- dw/plan.py +920 -0
- dw/previous_results.py +411 -0
- dw/probe_paths.py +59 -0
- dw/prompt_schema.json +48 -0
- dw/prompt_weighting.py +378 -0
- dw/prompts.py +159 -0
- dw/realize.py +250 -0
- dw/reference_limits.py +215 -0
- dw/reference_names.py +125 -0
- dw/repl.py +338 -0
- dw/repl_commands.py +836 -0
- dw/repl_worker.py +159 -0
- dw/result.py +1720 -0
- dw/result_fps.py +82 -0
- dw/run.py +162 -0
- dw/runs.py +768 -0
- dw/scalar_result_validation.py +97 -0
- dw/schema.py +283 -0
- dw/security.py +1038 -0
- dw/select_validation.py +115 -0
- dw/serve.py +277 -0
- dw/server/__init__.py +2 -0
- dw/server/app.py +4586 -0
- dw/server/assess.py +132 -0
- dw/server/catalog_shape.py +487 -0
- dw/server/enhancers.py +129 -0
- dw/server/exports.py +480 -0
- dw/server/guides.py +257 -0
- dw/server/jobs.py +1561 -0
- dw/server/mcp_mount.py +95 -0
- dw/server/netinfo.py +124 -0
- dw/server/observed_cost.py +379 -0
- dw/server/sysinfo.py +71 -0
- dw/server/ui/assets/abap-08VXUWAP.js +1 -0
- dw/server/ui/assets/apex-BWPQTe0t.js +1 -0
- dw/server/ui/assets/azcli-Bc_sGQ0U.js +1 -0
- dw/server/ui/assets/bat-i0X4ZdIN.js +1 -0
- dw/server/ui/assets/bicep-B5-_aFwp.js +2 -0
- dw/server/ui/assets/cameligo-DMUM7wLl.js +1 -0
- dw/server/ui/assets/clojure-Cm7r79vr.js +1 -0
- dw/server/ui/assets/codicon-Brq4_Ui5.ttf +0 -0
- dw/server/ui/assets/coffee-Ba7i2nA0.js +1 -0
- dw/server/ui/assets/cpp-C7h46wYY.js +1 -0
- dw/server/ui/assets/csharp-BKxtCVv1.js +1 -0
- dw/server/ui/assets/csp-bTuwJoIa.js +1 -0
- dw/server/ui/assets/css-DIMkf-bt.js +3 -0
- dw/server/ui/assets/css.worker-B3ciXF_0.js +93 -0
- dw/server/ui/assets/cssMode-CPznxfY8.js +1 -0
- dw/server/ui/assets/cypher-CVaqCwHa.js +1 -0
- dw/server/ui/assets/dart-onAF5SnQ.js +1 -0
- dw/server/ui/assets/dockerfile-DZFCIeNp.js +1 -0
- dw/server/ui/assets/ecl-D05T4iGw.js +1 -0
- dw/server/ui/assets/editor-jjEx9u7D.css +1 -0
- dw/server/ui/assets/editor.api-CpWcotrd.js +847 -0
- dw/server/ui/assets/editor.worker-q-txB4vs.js +30 -0
- dw/server/ui/assets/elixir-6RTg0lbw.js +1 -0
- dw/server/ui/assets/flow9-C5_-GSwl.js +1 -0
- dw/server/ui/assets/freemarker2-CXtRM8N4.js +3 -0
- dw/server/ui/assets/fsharp-C8Ef5oNN.js +1 -0
- dw/server/ui/assets/go-C-y9NEjX.js +1 -0
- dw/server/ui/assets/graphql-fmXr3nnJ.js +1 -0
- dw/server/ui/assets/handlebars-N7x-6NMY.js +1 -0
- dw/server/ui/assets/hcl-CpzslTdj.js +1 -0
- dw/server/ui/assets/html-PhsdjHSr.js +1 -0
- dw/server/ui/assets/html.worker-C93Ht9o9.js +506 -0
- dw/server/ui/assets/htmlMode-Dgj0SEok.js +1 -0
- dw/server/ui/assets/index-3Vw6WAPW.css +1 -0
- dw/server/ui/assets/index-DgrYhQd9.js +43 -0
- dw/server/ui/assets/ini-sBoK_t0W.js +1 -0
- dw/server/ui/assets/java-BEtHBSE6.js +1 -0
- dw/server/ui/assets/javascript-BJqN9Qhv.js +1 -0
- dw/server/ui/assets/json.worker-B2V3pomh.js +62 -0
- dw/server/ui/assets/jsonMode-DbM4SWSv.js +7 -0
- dw/server/ui/assets/julia-Bri6UV-V.js +1 -0
- dw/server/ui/assets/kotlin-BOotOW0E.js +1 -0
- dw/server/ui/assets/less-B9JPFI3C.js +2 -0
- dw/server/ui/assets/lexon-CfSJPG6W.js +1 -0
- dw/server/ui/assets/liquid-BWr8lEc4.js +1 -0
- dw/server/ui/assets/lspLanguageFeatures-C1iGuDyZ.js +4 -0
- dw/server/ui/assets/lua-CsQS60Ue.js +1 -0
- dw/server/ui/assets/m3-D-oSqn_W.js +1 -0
- dw/server/ui/assets/markdown-Cimd5fb3.js +1 -0
- dw/server/ui/assets/mdx-DAdMi_0p.js +1 -0
- dw/server/ui/assets/mips-CIPQ_RoX.js +1 -0
- dw/server/ui/assets/monaco--ixms01u.css +1 -0
- dw/server/ui/assets/monaco-BGCeEqaw.js +56 -0
- dw/server/ui/assets/msdax-DauUninz.js +1 -0
- dw/server/ui/assets/mysql-SOo6toE5.js +1 -0
- dw/server/ui/assets/objective-c-FvmIjYaQ.js +1 -0
- dw/server/ui/assets/pascal-DrH0SRf2.js +1 -0
- dw/server/ui/assets/pascaligo-D-ptJ9y-.js +1 -0
- dw/server/ui/assets/perl-oz_6vUea.js +1 -0
- dw/server/ui/assets/pgsql-DTj74zXo.js +1 -0
- dw/server/ui/assets/php-nr791fC2.js +1 -0
- dw/server/ui/assets/pla-CopQ2nXW.js +1 -0
- dw/server/ui/assets/postiats-43DmfD33.js +1 -0
- dw/server/ui/assets/powerquery-D3hlyOfw.js +1 -0
- dw/server/ui/assets/powershell-DmHpPYUd.js +1 -0
- dw/server/ui/assets/protobuf-C531GsRP.js +2 -0
- dw/server/ui/assets/pug-Z5eAx3Zn.js +1 -0
- dw/server/ui/assets/python-Bcn70HdC.js +1 -0
- dw/server/ui/assets/qsharp-DkqhCAOL.js +1 -0
- dw/server/ui/assets/r-BwWrilGY.js +1 -0
- dw/server/ui/assets/razor-D1HmNnby.js +1 -0
- dw/server/ui/assets/redis-ClamHrr6.js +1 -0
- dw/server/ui/assets/redshift-DT7zqm-g.js +1 -0
- dw/server/ui/assets/restructuredtext-BYgofb2h.js +1 -0
- dw/server/ui/assets/ruby-DezsRK8O.js +1 -0
- dw/server/ui/assets/rust-DdL9SqIa.js +1 -0
- dw/server/ui/assets/sb-CcwsVR0C.js +1 -0
- dw/server/ui/assets/scala-DHpiXF5c.js +1 -0
- dw/server/ui/assets/scheme-BeGwcela.js +1 -0
- dw/server/ui/assets/scss-gp-XZpBa.js +3 -0
- dw/server/ui/assets/shell-CC2rA5mh.js +1 -0
- dw/server/ui/assets/solidity-BEEn4gHE.js +1 -0
- dw/server/ui/assets/sophia-CRfGWb83.js +1 -0
- dw/server/ui/assets/sparql-D_Lu-MrJ.js +1 -0
- dw/server/ui/assets/sql-NEE52Syq.js +1 -0
- dw/server/ui/assets/st-DbInun42.js +1 -0
- dw/server/ui/assets/swift-Bxkupp3x.js +1 -0
- dw/server/ui/assets/systemverilog-Bz4Y3fRF.js +1 -0
- dw/server/ui/assets/tcl-DISqw1ZD.js +1 -0
- dw/server/ui/assets/ts.worker-D7T1-Ig5.js +67738 -0
- dw/server/ui/assets/tsMode-D6u0XmOW.js +11 -0
- dw/server/ui/assets/twig-De2hgUGE.js +1 -0
- dw/server/ui/assets/typescript-BU6v-LMV.js +1 -0
- dw/server/ui/assets/typespec-B8J7ngcE.js +1 -0
- dw/server/ui/assets/vb-DV3o63ZY.js +1 -0
- dw/server/ui/assets/wgsl-DpFanUEy.js +298 -0
- dw/server/ui/assets/workers-Cn7cTUKr.js +1 -0
- dw/server/ui/assets/xml--0LP2Lwk.js +1 -0
- dw/server/ui/assets/yaml-mpBg9jnt.js +1 -0
- dw/server/ui/index.html +17 -0
- dw/server/updater.py +192 -0
- dw/settings.py +98 -0
- dw/shot_span_preflight.py +116 -0
- dw/shots.py +359 -0
- dw/slice_preflight.py +148 -0
- dw/step.py +187 -0
- dw/step_cache.py +442 -0
- dw/subfolders.py +107 -0
- dw/task_domains.py +307 -0
- dw/tasks/assess.py +826 -0
- dw/tasks/audio_transcription.py +88 -0
- dw/tasks/audio_utils.py +1862 -0
- dw/tasks/background_remover.py +43 -0
- dw/tasks/borders.py +113 -0
- dw/tasks/compose_text.py +74 -0
- dw/tasks/concat_videos.py +300 -0
- dw/tasks/depth_estimator.py +54 -0
- dw/tasks/diffusion_upscale.py +109 -0
- dw/tasks/dissolve_videos.py +342 -0
- dw/tasks/format_messages.py +24 -0
- dw/tasks/gather.py +173 -0
- dw/tasks/grade.py +97 -0
- dw/tasks/image_to_text.py +43 -0
- dw/tasks/image_utils.py +764 -0
- dw/tasks/interpolate_frames.py +252 -0
- dw/tasks/judge.py +68 -0
- dw/tasks/model_cache.py +55 -0
- dw/tasks/pair_audio.py +268 -0
- dw/tasks/qr_code.py +19 -0
- dw/tasks/restore_faces.py +175 -0
- dw/tasks/rife_model.py +192 -0
- dw/tasks/segment.py +121 -0
- dw/tasks/select.py +111 -0
- dw/tasks/speech_generation.py +228 -0
- dw/tasks/stabilize.py +129 -0
- dw/tasks/task.py +920 -0
- dw/tasks/tensor_image.py +57 -0
- dw/tasks/text_generation.py +169 -0
- dw/tasks/text_sections.py +80 -0
- dw/tasks/upscale.py +203 -0
- dw/tasks/video_utils.py +624 -0
- dw/tasks/zoe_depth.py +71 -0
- dw/teacache.py +381 -0
- dw/teacache_models.json +99 -0
- dw/test.py +29 -0
- dw/type_helpers.py +231 -0
- dw/validate.py +68 -0
- dw/variable_constraints.py +444 -0
- dw/variables.py +443 -0
- dw/video_extensions.py +141 -0
- dw/vram_estimate.py +116 -0
- dw/worker.py +764 -0
- dw/workflow.py +2007 -0
- dw/workflow_schema.json +1346 -0
- dw/workflow_sources.py +383 -0
- dw/workflows/h3_context_ir.json +57 -0
- dw/workflows/test.json +31 -0
- dw/workspace.py +730 -0
- dw_mcp/__init__.py +6 -0
- dw_mcp/__main__.py +133 -0
- dw_mcp/assets.py +336 -0
- dw_mcp/authoring.py +114 -0
- dw_mcp/catalog.py +360 -0
- dw_mcp/client.py +486 -0
- dw_mcp/diagnose.py +371 -0
- dw_mcp/exports.py +84 -0
- dw_mcp/guides.py +35 -0
- dw_mcp/media.py +638 -0
- dw_mcp/models.py +97 -0
- dw_mcp/prompts.py +104 -0
- dw_mcp/server.py +1343 -0
- dw_mcp/workspaces.py +212 -0
dw/workflow_sources.py
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
"""Where workflows are read from, and the one place they are written to.
|
|
2
|
+
|
|
3
|
+
A workflow directory used to be a single directory that was both the library
|
|
4
|
+
and the place saves landed. With the repository's own workflows/ as that
|
|
5
|
+
directory - the default when a checkout is the workspace - every save from
|
|
6
|
+
the editor or an MCP client wrote into the example corpus.
|
|
7
|
+
|
|
8
|
+
A search path separates the two. Reads resolve front to back; writes only
|
|
9
|
+
ever go to the front:
|
|
10
|
+
|
|
11
|
+
<workspace>/workflows/ the user's own, writable
|
|
12
|
+
<examples dirs> read-only, --examples-dir
|
|
13
|
+
|
|
14
|
+
A name found in an earlier source shadows the same name in a later one, so a
|
|
15
|
+
workspace copy of an example is the one that runs. Saving over a read-only
|
|
16
|
+
workflow is not an error and not an overwrite: it writes a copy into the
|
|
17
|
+
writable source, which is what "open an example, change it, save" should do.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import logging
|
|
21
|
+
import os
|
|
22
|
+
|
|
23
|
+
from .security import PathTraversalError, SecurityError, contained, validate_path
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger("dw")
|
|
26
|
+
|
|
27
|
+
# What a source is, for a client deciding whether to offer save or delete
|
|
28
|
+
WORKSPACE_ORIGIN = "workspace"
|
|
29
|
+
EXAMPLES_ORIGIN = "examples"
|
|
30
|
+
BUILTIN_ORIGIN = "builtin"
|
|
31
|
+
# Not a workflow source - the asset library shared by every workspace under
|
|
32
|
+
# one root reports itself this way, so a client can tell a shared asset from
|
|
33
|
+
# one of its own
|
|
34
|
+
COMMON_ORIGIN = "common"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def builtin_root():
|
|
38
|
+
"""The packaged workflows that ship inside dw/ - what 'builtin:' names."""
|
|
39
|
+
return os.path.join(os.path.dirname(os.path.abspath(__file__)), "workflows")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def catalog_root(directory):
|
|
43
|
+
"""The nearest ancestor of `directory` literally named 'workflows', else
|
|
44
|
+
the directory itself.
|
|
45
|
+
|
|
46
|
+
The root a run with no workflow_dir of its own confines a relative
|
|
47
|
+
sub-workflow reference to, so a template under templates/ can still climb
|
|
48
|
+
to a sibling models/ without leaving the catalog. `catalog_root_dir`
|
|
49
|
+
(dw/workflow.py) is this rule asked for a file rather than a directory.
|
|
50
|
+
"""
|
|
51
|
+
directory = os.path.normpath(os.path.abspath(directory))
|
|
52
|
+
parts = directory.split(os.sep)
|
|
53
|
+
try:
|
|
54
|
+
index = len(parts) - 1 - parts[::-1].index("workflows")
|
|
55
|
+
except ValueError:
|
|
56
|
+
return directory
|
|
57
|
+
|
|
58
|
+
return os.sep.join(parts[: index + 1])
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class WorkflowSource:
|
|
62
|
+
"""One root on the search path."""
|
|
63
|
+
|
|
64
|
+
def __init__(self, root, origin, writable):
|
|
65
|
+
self.root = os.path.abspath(os.path.expanduser(str(root)))
|
|
66
|
+
self.origin = origin
|
|
67
|
+
self.writable = writable
|
|
68
|
+
|
|
69
|
+
def contains(self, path):
|
|
70
|
+
"""Whether a path resolves inside this root - the containment check
|
|
71
|
+
the security layer already implements, asked as a question rather
|
|
72
|
+
than raised as an error."""
|
|
73
|
+
try:
|
|
74
|
+
validate_path(path, self.root)
|
|
75
|
+
return True
|
|
76
|
+
except SecurityError:
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
def names(self):
|
|
80
|
+
"""Workflow names under this root, as relative paths without .json."""
|
|
81
|
+
return workflow_names(self.root)
|
|
82
|
+
|
|
83
|
+
def to_dict(self):
|
|
84
|
+
return {"root": self.root, "origin": self.origin, "writable": self.writable}
|
|
85
|
+
|
|
86
|
+
def __repr__(self):
|
|
87
|
+
return f"WorkflowSource({self.root!r}, {self.origin}, writable={self.writable})"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def workflow_names(root):
|
|
91
|
+
"""Workflow names under a root, as relative paths without .json.
|
|
92
|
+
|
|
93
|
+
A file symlink resolving outside the root is not a name here: a listing
|
|
94
|
+
opens every file it names, and reads by name already refuse the link."""
|
|
95
|
+
names = []
|
|
96
|
+
if not os.path.isdir(root):
|
|
97
|
+
return names
|
|
98
|
+
for directory, _dirs, files in os.walk(root):
|
|
99
|
+
for file_name in files:
|
|
100
|
+
if file_name.endswith(".json"):
|
|
101
|
+
path = os.path.join(directory, file_name)
|
|
102
|
+
if not contained(path, root):
|
|
103
|
+
continue
|
|
104
|
+
relative = os.path.relpath(path, root)
|
|
105
|
+
names.append(relative[: -len(".json")].replace(os.sep, "/"))
|
|
106
|
+
return sorted(names)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def workflow_sources(workflow_dir, examples_dirs=None, include_builtin=False):
|
|
110
|
+
"""The search path: the writable directory first, then read-only roots.
|
|
111
|
+
|
|
112
|
+
A read-only root that is the writable one - a checkout whose workflows/
|
|
113
|
+
is both the workspace library and the examples - appears once, writable,
|
|
114
|
+
rather than twice with two different answers about whether it can be
|
|
115
|
+
saved to.
|
|
116
|
+
|
|
117
|
+
The packaged workflows are off the path by default. They are the pieces
|
|
118
|
+
a 'builtin:' sub-workflow step names, resolved by the engine where that
|
|
119
|
+
step is read (dw/workflow.py) - not workflows anyone browses or runs on
|
|
120
|
+
their own, and listing them would put a handful of fragments in front of
|
|
121
|
+
every user who never asked for them.
|
|
122
|
+
"""
|
|
123
|
+
sources = [WorkflowSource(workflow_dir, WORKSPACE_ORIGIN, True)]
|
|
124
|
+
candidates = [(directory, EXAMPLES_ORIGIN) for directory in examples_dirs or []]
|
|
125
|
+
if include_builtin:
|
|
126
|
+
candidates.append((builtin_root(), BUILTIN_ORIGIN))
|
|
127
|
+
|
|
128
|
+
seen = {sources[0].root}
|
|
129
|
+
for root, origin in candidates:
|
|
130
|
+
source = WorkflowSource(root, origin, False)
|
|
131
|
+
if source.root in seen:
|
|
132
|
+
continue
|
|
133
|
+
seen.add(source.root)
|
|
134
|
+
sources.append(source)
|
|
135
|
+
return sources
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def writable_source(sources):
|
|
139
|
+
"""The source saves go to: the front of the path."""
|
|
140
|
+
for source in sources:
|
|
141
|
+
if source.writable:
|
|
142
|
+
return source
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def source_for_path(sources, path):
|
|
147
|
+
"""Which source a resolved path belongs to, or None if it is outside
|
|
148
|
+
every root - which is what makes a path a workflow rather than an
|
|
149
|
+
arbitrary file."""
|
|
150
|
+
for source in sources:
|
|
151
|
+
if source.contains(path):
|
|
152
|
+
return source
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def resolve_in_source(source, name, allow_create=False):
|
|
157
|
+
"""The on-disk path a name has in one source, or None when the name does
|
|
158
|
+
not resolve inside it. Containment is the security layer's, so a name
|
|
159
|
+
that tries to traverse simply does not resolve."""
|
|
160
|
+
if not name.endswith(".json"):
|
|
161
|
+
name = f"{name}.json"
|
|
162
|
+
try:
|
|
163
|
+
return validate_path(
|
|
164
|
+
os.path.join(source.root, name), source.root, allow_create=allow_create
|
|
165
|
+
)
|
|
166
|
+
except SecurityError:
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def find_workflow(sources, name):
|
|
171
|
+
"""The first source that has this name, as (path, source).
|
|
172
|
+
|
|
173
|
+
Front to back, so a workspace copy shadows the example it was copied
|
|
174
|
+
from. (None, None) when no source has it.
|
|
175
|
+
"""
|
|
176
|
+
for source in sources:
|
|
177
|
+
path = resolve_in_source(source, name)
|
|
178
|
+
if path and os.path.isfile(path):
|
|
179
|
+
return path, source
|
|
180
|
+
return None, None
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def suggest_workflow_names(sources, name, limit=3):
|
|
184
|
+
"""Catalog names an unresolved `name` might have meant, for an error
|
|
185
|
+
message rather than a second round trip.
|
|
186
|
+
|
|
187
|
+
The catalog is organised in directories (`templates/minimax/dialogue-short`)
|
|
188
|
+
and a caller - a skill, an earlier turn - often has only the trailing
|
|
189
|
+
name (`dialogue-short`). Preferred answer: every catalog entry `name` is
|
|
190
|
+
a unique path suffix of, since that is unambiguous; failing that, a
|
|
191
|
+
close spelling match (`difflib`), for a typo rather than a shortened
|
|
192
|
+
path. Empty when neither finds anything worth naming.
|
|
193
|
+
"""
|
|
194
|
+
import difflib
|
|
195
|
+
|
|
196
|
+
stripped = name[: -len(".json")] if name.endswith(".json") else name
|
|
197
|
+
catalog_names = list(listing(sources).keys())
|
|
198
|
+
suffix_matches = [
|
|
199
|
+
candidate
|
|
200
|
+
for candidate in catalog_names
|
|
201
|
+
if candidate == stripped or candidate.endswith(f"/{stripped}")
|
|
202
|
+
]
|
|
203
|
+
if suffix_matches:
|
|
204
|
+
return suffix_matches[:limit]
|
|
205
|
+
|
|
206
|
+
# A typo is measured against the catalog entry's own name, not against
|
|
207
|
+
# its directory prefix - "dialog-short" scores 0.92 against
|
|
208
|
+
# "dialogue-short" and 0.55 against "templates/minimax/dialogue-short",
|
|
209
|
+
# so comparing full paths lets a real typo miss the cutoff (#397). The
|
|
210
|
+
# query's own prefix is stripped the same way, so "sub/Basik" is
|
|
211
|
+
# measured as "Basik" against "Basic" rather than against "sub/Basic".
|
|
212
|
+
query_basename = stripped.rsplit("/", 1)[-1]
|
|
213
|
+
by_basename = {}
|
|
214
|
+
for candidate in catalog_names:
|
|
215
|
+
by_basename.setdefault(candidate.rsplit("/", 1)[-1], []).append(candidate)
|
|
216
|
+
close_bases = difflib.get_close_matches(
|
|
217
|
+
query_basename, list(by_basename.keys()), n=limit, cutoff=0.6
|
|
218
|
+
)
|
|
219
|
+
matches = []
|
|
220
|
+
for base in close_bases:
|
|
221
|
+
for candidate in by_basename[base]:
|
|
222
|
+
if candidate not in matches:
|
|
223
|
+
matches.append(candidate)
|
|
224
|
+
return matches[:limit]
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def listing(sources):
|
|
228
|
+
"""Every name the search path offers, each with the source it comes
|
|
229
|
+
from - a name in an earlier source shadowing the same name later."""
|
|
230
|
+
found = {}
|
|
231
|
+
for source in sources:
|
|
232
|
+
for name in source.names():
|
|
233
|
+
found.setdefault(name, source)
|
|
234
|
+
return dict(sorted(found.items()))
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def fallback_roots(primary=None):
|
|
238
|
+
"""The read-only workflow roots a sub-workflow name is resolved against
|
|
239
|
+
after the directory the run is confined to.
|
|
240
|
+
|
|
241
|
+
Pinned in DW_WORKFLOW_PATH by dw.serve, the same way the prompt and
|
|
242
|
+
asset libraries are, so the worker resolves a composed template exactly
|
|
243
|
+
as the API would.
|
|
244
|
+
"""
|
|
245
|
+
from .workspace import WORKFLOWS_SUBDIR, library_fallbacks
|
|
246
|
+
|
|
247
|
+
return library_fallbacks(WORKFLOWS_SUBDIR, primary)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _candidate_names(name):
|
|
251
|
+
"""A name as written, and with .json appended when it has no extension -
|
|
252
|
+
the catalog reports names without it, and run_workflow's workflow_path
|
|
253
|
+
takes either (#90)."""
|
|
254
|
+
names = [name]
|
|
255
|
+
if not name.endswith(".json"):
|
|
256
|
+
names.append(f"{name}.json")
|
|
257
|
+
return names
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class SubWorkflowNotFound(Exception):
|
|
261
|
+
"""A sub-workflow step's path names nothing on the search path."""
|
|
262
|
+
|
|
263
|
+
def __init__(self, path, tried):
|
|
264
|
+
self.path = path
|
|
265
|
+
# In order, each candidate once - two roots can resolve one name to
|
|
266
|
+
# the same file, and saying so twice reads as two failures
|
|
267
|
+
self.tried = list(dict.fromkeys(tried))
|
|
268
|
+
detail = "\n ".join(self.tried)
|
|
269
|
+
super().__init__(
|
|
270
|
+
f"Sub-workflow '{path}' could not be resolved. It is read as a "
|
|
271
|
+
"catalog name from list_workflows (with or without .json), or a "
|
|
272
|
+
"path relative to the workflow that names it. Looked in:"
|
|
273
|
+
f"\n {detail}"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _describe_roots(roots):
|
|
278
|
+
"""Roots this resolution consulted, for a refusal message - never empty
|
|
279
|
+
text, since a run with none configured still owes an answer."""
|
|
280
|
+
return ", ".join(roots) if roots else "(no workflow sources configured)"
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def resolve_sub_workflow(path, base_dir, confine_to):
|
|
284
|
+
"""Where a sub-workflow step's `path` resolves to, and the root the
|
|
285
|
+
child is confined to, as (path, root).
|
|
286
|
+
|
|
287
|
+
Order, first hit wins:
|
|
288
|
+
|
|
289
|
+
1. relative to the directory of the workflow that names it - which is
|
|
290
|
+
how a template reaches '../models/x.json', and stays first so an
|
|
291
|
+
existing composition keeps meaning what it did
|
|
292
|
+
2. the same, with '.json' supplied
|
|
293
|
+
3. the run's own workflow root (a workspace's workflows/), by catalog
|
|
294
|
+
name, with or without '.json'
|
|
295
|
+
4. each read-only root on the search path, the same way - which is
|
|
296
|
+
what lets a stored template be composed rather than copied (#90)
|
|
297
|
+
|
|
298
|
+
An absolute path is taken as written and confined to whichever root
|
|
299
|
+
holds it, so the sandbox still refuses one that belongs to no source.
|
|
300
|
+
|
|
301
|
+
A relative path that climbs out of the root it will be handed back with is
|
|
302
|
+
a PathTraversalError rather than a SubWorkflowNotFound - a refusal, not a
|
|
303
|
+
name that was absent. Otherwise raises SubWorkflowNotFound, naming every
|
|
304
|
+
candidate it looked at.
|
|
305
|
+
"""
|
|
306
|
+
roots = []
|
|
307
|
+
if confine_to:
|
|
308
|
+
roots.append(os.path.abspath(os.path.expanduser(str(confine_to))))
|
|
309
|
+
for root in fallback_roots(roots[0] if roots else None):
|
|
310
|
+
if root not in roots:
|
|
311
|
+
roots.append(root)
|
|
312
|
+
|
|
313
|
+
tried = []
|
|
314
|
+
if os.path.isabs(path):
|
|
315
|
+
candidate = os.path.normpath(path)
|
|
316
|
+
tried.append(candidate)
|
|
317
|
+
for root in roots:
|
|
318
|
+
source = WorkflowSource(root, EXAMPLES_ORIGIN, False)
|
|
319
|
+
if source.contains(candidate) and os.path.isfile(candidate):
|
|
320
|
+
return candidate, root
|
|
321
|
+
if confine_to:
|
|
322
|
+
# A real confinement boundary was named and nothing on the
|
|
323
|
+
# search path holds this candidate - refuse here, naming every
|
|
324
|
+
# root this resolution consulted, rather than handing an
|
|
325
|
+
# unqualified path back to validate_workflow_path for a refusal
|
|
326
|
+
# that names only the rejected path and not where it looked
|
|
327
|
+
# (#422)
|
|
328
|
+
raise PathTraversalError(
|
|
329
|
+
f"Path outside every workflow source: {candidate}. Looked in: "
|
|
330
|
+
+ _describe_roots(roots)
|
|
331
|
+
)
|
|
332
|
+
# Unconfined (a bare CLI run naming no workflow_dir) - hand it back
|
|
333
|
+
# as before and let validate_workflow_path's base=None passthrough
|
|
334
|
+
# decide, since there is no boundary to report roots for
|
|
335
|
+
return candidate, confine_to
|
|
336
|
+
|
|
337
|
+
if base_dir:
|
|
338
|
+
# The root this candidate would be handed back with: the caller's
|
|
339
|
+
# confinement when it named one, else the catalog root the run itself
|
|
340
|
+
# would confine to. Containment is checked before the stat, so a name
|
|
341
|
+
# that climbs out of the catalog is never even looked at - which is
|
|
342
|
+
# what the callers' own validate_workflow_path caught a step too late,
|
|
343
|
+
# leaving this `os.path.isfile` the dw/path-injection query's only
|
|
344
|
+
# unguarded sink. A climb out is refused rather than reported as
|
|
345
|
+
# absent, the distinction the search path below keeps: the
|
|
346
|
+
# PathTraversalError propagates to the caller
|
|
347
|
+
root = confine_to or catalog_root(base_dir)
|
|
348
|
+
search_roots = [root] + [r for r in roots if r != root]
|
|
349
|
+
for name in _candidate_names(path):
|
|
350
|
+
# normpath first: validate_path refuses a '..' outright, so a
|
|
351
|
+
# climb that stays inside the root has to be collapsed before it
|
|
352
|
+
# is judged. Its return value is what gets stat'ed - and being
|
|
353
|
+
# the validator's own, it is contained by construction
|
|
354
|
+
try:
|
|
355
|
+
candidate = validate_path(
|
|
356
|
+
os.path.normpath(os.path.join(base_dir, name)),
|
|
357
|
+
root,
|
|
358
|
+
allow_create=True,
|
|
359
|
+
)
|
|
360
|
+
except PathTraversalError as e:
|
|
361
|
+
# Same refusal, naming the search path rather than only the
|
|
362
|
+
# resolved (rejected) path (#422)
|
|
363
|
+
raise PathTraversalError(
|
|
364
|
+
f"{e} Looked in: {_describe_roots(search_roots)}"
|
|
365
|
+
) from e
|
|
366
|
+
tried.append(candidate)
|
|
367
|
+
if os.path.isfile(candidate):
|
|
368
|
+
return candidate, confine_to
|
|
369
|
+
|
|
370
|
+
for root in roots:
|
|
371
|
+
source = WorkflowSource(root, EXAMPLES_ORIGIN, False)
|
|
372
|
+
# allow_create, because what is being asked is where the name would
|
|
373
|
+
# land rather than whether something is there - None means the name
|
|
374
|
+
# traverses out of the root, and the file check is the next line
|
|
375
|
+
candidate = resolve_in_source(source, path, allow_create=True)
|
|
376
|
+
if candidate is None:
|
|
377
|
+
tried.append(f"{os.path.join(root, path)} (outside the root)")
|
|
378
|
+
continue
|
|
379
|
+
tried.append(candidate)
|
|
380
|
+
if os.path.isfile(candidate):
|
|
381
|
+
return candidate, root
|
|
382
|
+
|
|
383
|
+
raise SubWorkflowNotFound(path, tried)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "h3_context_ir",
|
|
3
|
+
"description": "Rewrites a user's video idea into a MiniMax-H3 Context-IR prompt. Ref2VA has no instruction block above its six sections, so a Ref2VA caller should pass keep_preamble false; the other tasks put a required instruction line there and keep it.",
|
|
4
|
+
"variables": {
|
|
5
|
+
"prompt": "Task: T2VA. Duration: 5.17 seconds. Idea: a red fox trotting through a snowy pine forest at dawn.",
|
|
6
|
+
"image": null,
|
|
7
|
+
"system_prompt": "You rewrite a user's video idea into a MiniMax-H3 Context-IR prompt, the format MiniMax's two prompt-writing guides define (docs/VIDEO_PROMPT_WRITING_GUIDE_base_en.md and docs/VIDEO_PROMPT_WRITING_GUIDE_ref_en.md in the MiniMaxAI/MiniMax-H3 model repository; this text follows them and is checked against them). Output only the rewritten prompt - no preamble, no commentary, no markdown fences, no explanation of what you did.\n\nThe user message states the task (T2VA, I2VA, FL2VA, L2VA or Ref2VA), the duration in seconds, and the idea. It may also state a continuity mode - \"Continuity: standalone\" or \"Continuity: continuation\" - which is standalone when absent. Structure the output as an optional instruction block, one blank line, then the three core fields.\n\nINSTRUCTION BLOCK, by task:\n- T2VA: none. Begin directly with the core fields.\n- I2VA: exactly \"For the target video, at 0.00 seconds into the target video, <Picture 1> (from [Shot 1]) is fully referenced.\"\n- FL2VA: exactly \"How the reference pictures align with the target video - Picture 1 (from Shot 1) aligns with the 0.00-second mark of the target video; Picture 2 (from Shot N) aligns with the S.SS-second mark of the target video.\" where S.SS is the duration to two decimal places and N is the index of the final shot.\n- L2VA: exactly \"How the reference pictures align with the target video - <Picture 1> (from [Shot N]) aligns with the S.SS-second mark of the target video.\"\n- Ref2VA: none of the above. Ref2VA has its own six-section layout, given below.\n\nCORE FIELDS for T2VA, I2VA, FL2VA and L2VA, in this order, separated by blank lines:\nintegrated_multimodal_description: the timeline - visual style, initial composition, subject appearance and position, scene and key props, actions and reactions, shot changes, spoken language and synchronized diegetic sound. This field is always written in full, whatever the brief says about sound or music; a silent brief still gets its complete visual timeline here.\noverall_soundscape: ambient sound, physical action sounds and non-verbal human sounds across the whole video. If the brief says there is no sound, this field is exactly one line reading \"overall_soundscape: N/A\" - not a description of silence.\nnon_diegetic_music: background score the audience hears and the characters cannot. If the brief says there is no music, this field is exactly one line reading \"non_diegetic_music: N/A\". Never describe silence in words and never invent audio a silent brief did not ask for.\n\nREF2VA uses six sections instead, in this order, separated by blank lines:\nsubject_definitions: one line per referenced item. <Subject N> is reusable visible content - a person, animal, object, scene, costume, style, action. <Picture N> is a reference image acting as a keyframe or composition anchor; an image that only defines a subject is cited inside that subject's line instead of getting one of its own. <Video N> is a whole-video relationship - an edit source, a continuation point, or a temporal-structure reference. <Audio N> is an audio asset that is copied or referenced; when it fixes a speaker's voice, name that speaker's ID in its line (\"<Audio 1> is the voice-timbre reference for <Subject 1> (S1).\"). One subject may draw on several assets (\"whose appearance comes from <Picture 1> and whose walking motion comes from <Video 1>\"), and an image that plans shots is a storyboard reference (\"<Picture 3> is a storyboard reference for [Shot 1] and [Shot 2], defining their viewpoint, subject placement, and shot order.\"). A label keeps the same meaning in every later section. The references appear in the order the request passes them, and each label type is numbered independently within its own category: the first video is <Video 1> whether or not pictures precede it, and a video reference does not by itself produce an <Audio N> - an audio label exists only for an audio asset the request passes.\nsummary: a square-bracketed task-type prefix followed by one short paragraph on the target video and its main reference relationships. The prefix combines, with \" + \" and without repeats, only the types the references actually play: \"keyframe completion\" (an image is a concrete first, last or key frame), \"reference generation\" (an image, video or audio guides a character, scene, style, action, camera, or storyboard without being a concrete frame or an edited source), \"video editing\" (a source video is directly modified), \"video continuation\" (new content continues or extends a source video), \"audio reuse\" (all or part of an audio signal is copied into the soundtrack), \"audio reference\" (only an audio's style, timbre, content, beat or continuity is followed). A video that only lends camera, cuts or rhythm is reference generation, not editing or continuation. A video edit opens with \"The target video is an edited version of <Video N>.\"\nretention_analysis: one line per referenced item, stating its relationship marker and what that means for it. Visible content (<Subject N>, <Picture N>, <Video N>) uses exactly one of: fully_preserved (its defined role is kept whole), partially_preserved (still used, some defined characteristics changed or only partly kept), attribute_transfer (its characteristics move onto a different identifiable subject), weak_reference (only broad similarity of style, category, composition or atmosphere). Audio (<Audio N>) uses exactly one of: fully_copy (the whole signal is the whole final soundtrack), partially_copy (part of the timeline or selected layers are copied, or sounds are added or replaced around it), reference (nothing copied; timbre, rhythm, music style, dialogue content or texture followed), weak_reference (only broad similarity of category or atmosphere). Write the entry as \"<Subject 1> (appears in [Shot 1], [Shot 3]): fully_preserved - ...\", \"<Picture 2> ([Shot 1] first frame): fully_preserved - ...\", \"<Audio 1>: partially_copy - ...\". Choose the marker within the role subject_definitions gave the label; new actions, backgrounds or events in the target video are not losses of fidelity.\ndetailed_description: one or two sentences establishing the overall style before [Shot 1] (\"The target video is in a realistic photographic style with soft window light.\"), then the same shot-by-shot timeline the other tasks put in integrated_multimodal_description, as detailed and explicit as possible - composition, appearance, environment and lighting, actions and state changes, camera movement, sound, and the points where referenced content actually takes effect. A generation task runs 350-500 words here, spread across the shots by their information load; dialogue-dense content fits the whole spoken timeline first. Never reduce it to a plot summary or a list of reference relationships.\noverall_soundscape: as above.\nnon_diegetic_music: as above.\n\nCONTINUITY MODES. These are this workflow engine's convention for writing one segment of a chained take and are not part of Context-IR; the user message states one when it applies, and standalone is assumed when it does not. They decide how the clip opens:\n- standalone, the default: the clip is a complete piece. [Shot 1] establishes the scene, and when a reference image is supplied [Shot 1] opens on that image - its framing, lighting and style.\n- continuation: the clip is one segment cut out of a longer unbroken take, and a <Video N> reference holds the frames immediately before it. Write one continuous shot - no cuts, and no later timestamped shots. [Shot 1] opens already in progress, on the framing, shot size, camera position, pose and lighting that <Video N> ends on, and carries them on from there. A supplied reference image constrains subject identity, wardrobe, set and palette alone and leaves the opening composition to <Video N>; the summary prefix includes \"video continuation\", and in retention_analysis the video reference is fully_preserved (it is the continuation point) and the subject the image defines is fully_preserved in its identity role. The clip begins mid-action and ends mid-action: it establishes nothing and resolves nothing. The stated duration is this segment's own, not that of the finished video it belongs to.\n\n\nRULES for the shot-by-shot body, whichever field carries it:\n- Open [Shot 1] with the overall style and initial composition, which in continuation mode is the one <Video N> ends on (in Ref2VA the style sentence sits just before [Shot 1] instead of inside it). Styles include Cinematic, live-action, 2D-animated, 3D CG, claymation, watercolor, vintage film. Derive it from the reference image when there is one, otherwise from the user's text.\n- Do not timestamp the first shot. Every later shot opens with a strictly increasing cut time inside the duration: \"[Shot 2] At 00:03.500, the camera cuts to ...\". A cut must introduce new information; when only framing or angle changes, use camera motion instead. FL2VA favours a single continuous shot unless the user asks otherwise, and continuation mode allows no cut at all.\n- Write camera motion as natural English inside the shot, as motion type plus optional amplitude and speed. Motion types: Zoom In, Zoom Out, Push In, Pull Out, Pan Left, Pan Right, Truck Left, Truck Right, Tilt Up, Tilt Down, Pedestal Up, Pedestal Down, Arc Shot, Tracking Shot, Static Shot, Shake Slightly, Shake Strongly, POV, Roll Clockwise, Roll Counterclockwise. Amplitude: \"with small amplitude\", \"with large amplitude\". Speed: \"at slow speed\", \"at fast speed\". Omit amplitude and speed when they are medium and normal.\n- Give every subject who speaks, sings or vocalises off-screen a stable ID - (S1), (S2), and (S1,S2) for unison - kept across shots. Characters who never vocalise get no ID. The first time a speaker appears, establish identity: character type, age, gender, on- or off-screen, pitch, timbre, speaking rate, accent.\n- Put spoken and sung content inside <d>[Language] ...</d> and nothing else. Identity, action and delivery stay outside the tag. Preserve the user's wording and punctuation verbatim; never translate or rewrite supplied lines. Invent dialogue only when the user asked for speech without supplying any.\n- Dialogue fidelity: write [unclear] for a span that cannot be made out; standardise punctuation and end every <d> block with terminal punctuation before </d>; never write speaker IDs such as (Sx) inside retention_analysis, which describes references, not speech; when an <Audio N> is referenced for a speaker's timbre only, do not carry its original words over - the dialogue is what the brief supplies.\n- Voiceover uses the exact phrase \"says in an off-screen voiceover\", and the text right after every voiceover <d> block states that the character's lips remain completely closed.\n- Use <scenetrans> at both connecting points of a line that continues across a cut, and <cutoff> when speech is truncated by the end of the video.\n- Put text actually visible on screen in double quotation marks, verbatim and untranslated.\n- When a reference image is supplied, the description must match what is actually in it - subject, setting, lighting and style - and in standalone mode [Shot 1] must open on that image's framing rather than on an invented scene; in continuation mode the opening framing is <Video N>'s and the image constrains the rest. Never describe content the image contradicts.\n- Every detail must be something visible or audible, in plain words. No negative phrasing - H3 is guidance-distilled and has no negative prompt, so say what is there, never what is not.",
|
|
8
|
+
"model_name": "Qwen/Qwen3-4B-Instruct-2507",
|
|
9
|
+
"max_new_tokens": 1600,
|
|
10
|
+
"device": "cpu",
|
|
11
|
+
"sections": [
|
|
12
|
+
"subject_definitions",
|
|
13
|
+
"summary",
|
|
14
|
+
"retention_analysis",
|
|
15
|
+
"detailed_description",
|
|
16
|
+
"integrated_multimodal_description",
|
|
17
|
+
"overall_soundscape",
|
|
18
|
+
"non_diegetic_music"
|
|
19
|
+
],
|
|
20
|
+
"keep_preamble": true
|
|
21
|
+
},
|
|
22
|
+
"steps": [
|
|
23
|
+
{
|
|
24
|
+
"name": "context_ir",
|
|
25
|
+
"task": {
|
|
26
|
+
"command": "text_generation",
|
|
27
|
+
"arguments": {
|
|
28
|
+
"prompt": "variable:prompt",
|
|
29
|
+
"system_prompt": "variable:system_prompt",
|
|
30
|
+
"model_name": "variable:model_name",
|
|
31
|
+
"max_new_tokens": "variable:max_new_tokens",
|
|
32
|
+
"device": "variable:device",
|
|
33
|
+
"image": "variable:image"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"result": {
|
|
37
|
+
"content_type": "text/plain",
|
|
38
|
+
"save": false
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "trim",
|
|
43
|
+
"task": {
|
|
44
|
+
"command": "extract_sections",
|
|
45
|
+
"arguments": {
|
|
46
|
+
"text": "previous_result:context_ir",
|
|
47
|
+
"sections": "variable:sections",
|
|
48
|
+
"keep_preamble": "variable:keep_preamble"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"result": {
|
|
52
|
+
"content_type": "text/plain",
|
|
53
|
+
"save": false
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
dw/workflows/test.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"variables": {
|
|
3
|
+
"prompt": "an apple",
|
|
4
|
+
"num_images_per_prompt": 1
|
|
5
|
+
},
|
|
6
|
+
"id": "test_job",
|
|
7
|
+
"steps": [
|
|
8
|
+
{
|
|
9
|
+
"name": "main",
|
|
10
|
+
"pipeline": {
|
|
11
|
+
"configuration": {
|
|
12
|
+
"component_type": "StableDiffusionPipeline"
|
|
13
|
+
},
|
|
14
|
+
"from_pretrained_arguments": {
|
|
15
|
+
"model_name": "stable-diffusion-v1-5/stable-diffusion-v1-5",
|
|
16
|
+
"torch_dtype": "torch.float16"
|
|
17
|
+
},
|
|
18
|
+
"arguments": {
|
|
19
|
+
"prompt": "variable:prompt",
|
|
20
|
+
"num_inference_steps": 25,
|
|
21
|
+
"num_images_per_prompt": "variable:num_images_per_prompt"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"result": {
|
|
25
|
+
"content_type": "image/jpeg",
|
|
26
|
+
"embed_metadata": true,
|
|
27
|
+
"file_base_name": "test_image"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|