diff --git a/backend/open_webui/routers/images.py b/backend/open_webui/routers/images.py index e733201d4f..567745cfa0 100644 --- a/backend/open_webui/routers/images.py +++ b/backend/open_webui/routers/images.py @@ -455,6 +455,8 @@ class CreateImageForm(BaseModel): n: int = 1 steps: int | None = None negative_prompt: str | None = None + seed: int | None = None + extra_params: dict | None = None GenerateImageForm = CreateImageForm # Alias for backward compatibility @@ -749,6 +751,12 @@ async def image_generations( if form_data.negative_prompt is not None: data['negative_prompt'] = form_data.negative_prompt + if form_data.seed is not None: + data['seed'] = form_data.seed + + if form_data.extra_params: + data['extra_params'] = form_data.extra_params + form_data = ComfyUICreateImageForm( **{ 'workflow': ComfyUIWorkflow( diff --git a/backend/open_webui/utils/images/comfyui.py b/backend/open_webui/utils/images/comfyui.py index 0a904bae91..16fce080ab 100644 --- a/backend/open_webui/utils/images/comfyui.py +++ b/backend/open_webui/utils/images/comfyui.py @@ -152,6 +152,7 @@ class ComfyUICreateImageForm(BaseModel): steps: Optional[int] = None seed: Optional[int] = None + extra_params: Optional[dict] = None def _apply_workflow_nodes(workflow, nodes, model, payload): @@ -240,6 +241,9 @@ def _apply_workflow_nodes(workflow, nodes, model, payload): elif node.value is not None: # Custom static override workflow[node_id]['inputs'][node_key] = node.value + elif hasattr(payload, 'extra_params') and payload.extra_params and node_key in payload.extra_params: + # API-provided dynamic override + workflow[node_id]['inputs'][node_key] = payload.extra_params[node_key] # --- Generic static value passthrough -------------------------------- else: diff --git a/src/lib/components/admin/Settings/Images.svelte b/src/lib/components/admin/Settings/Images.svelte index 14d95c36eb..5654395be1 100644 --- a/src/lib/components/admin/Settings/Images.svelte +++ b/src/lib/components/admin/Settings/Images.svelte @@ -104,10 +104,17 @@ ); if (!nodeGroups.has(entryKey)) { + // Keys that map to payload.prompt in _apply_workflow_nodes are ambiguous + // when multiple nodes share the same key (e.g. positive vs negative + // CLIPTextEncode both have key 'text'). Leave node_ids empty for + // these so the admin explicitly picks which nodes receive the prompt. + const ambiguousKeys = new Set(['text', 'prompt', 'positive']); + const autoAssign = !ambiguousKeys.has(inputKey); + nodeGroups.set(entryKey, { type: semanticType, key: inputKey, - node_ids: [nodeId], + node_ids: autoAssign ? [nodeId] : [], class_type: node.class_type }); } @@ -255,10 +262,14 @@ const semanticType = `${node.class_type}::${inputKey}`; if (!nodeGroups.has(entryKey)) { + // Same ambiguous-key logic as parseAndPopulateWorkflowNodes + const ambiguousKeys = new Set(['text', 'prompt', 'positive']); + const autoAssign = !ambiguousKeys.has(inputKey); + nodeGroups.set(entryKey, { type: semanticType, key: inputKey, - node_ids: [nodeId], + node_ids: autoAssign ? [nodeId] : [], class_type: node.class_type }); } @@ -810,16 +821,27 @@