fix(images): stop auto-assigning prompt to negative prompt nodes, expose seed and extra_params via API

- Don't auto-assign node_ids for ambiguous text/prompt/positive keys in workflow auto-detection so admins control which CLIPTextEncode nodes receive the prompt vs negative prompt
- Restore editable key input fields for workflow nodes
- Add seed field to CreateImageForm and pass it through to ComfyUI payload
- Add extra_params dict to allow API callers to set arbitrary workflow parameters (e.g. cfg, sampler_name, scheduler, denoise)
This commit is contained in:
silentoplayz 2026-06-09 07:48:23 -04:00
parent 283520d128
commit c3eaed3c5f
3 changed files with 55 additions and 10 deletions

View file

@ -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(

View file

@ -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:

View file

@ -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 @@
<div class="flex w-full flex-col">
<div class="shrink-0">
<div
class="line-clamp-1 text-gray-400 dark:text-gray-500"
class="capitalize line-clamp-1 w-20 text-gray-400 dark:text-gray-500"
title={node.type}
>
<span class="font-medium">{node.class_type}</span><span
class="opacity-60">::{node.key}</span
>
{node.class_type}
</div>
</div>
<div class="mt-0.5 flex items-center">
<div class="">
<Tooltip content={$i18n.t('Input Key (e.g. text, unet_name, steps)')}>
<input
class="{inputClass} w-24"
placeholder={$i18n.t('Key')}
bind:value={node.key}
required
/>
</Tooltip>
</div>
<div class="px-2 text-gray-400 dark:text-gray-500">:</div>
<div class="w-full">
<Tooltip
content={$i18n.t('Comma separated Node Ids (e.g. 1 or 1,2)')}
@ -1113,16 +1135,27 @@
<div class="flex w-full flex-col">
<div class="shrink-0">
<div
class="line-clamp-1 text-gray-400 dark:text-gray-500"
class="capitalize line-clamp-1 w-20 text-gray-400 dark:text-gray-500"
title={node.type}
>
<span class="font-medium">{node.class_type}</span><span
class="opacity-60">::{node.key}</span
>
{node.class_type}
</div>
</div>
<div class="mt-0.5 flex items-center">
<div class="">
<Tooltip content={$i18n.t('Input Key (e.g. text, unet_name, steps)')}>
<input
class="{inputClass} w-24"
placeholder={$i18n.t('Key')}
bind:value={node.key}
required
/>
</Tooltip>
</div>
<div class="px-2 text-gray-400 dark:text-gray-500">:</div>
<div class="w-full">
<Tooltip
content={$i18n.t('Comma separated Node Ids (e.g. 1 or 1,2)')}