File size: 16,968 Bytes
8a28a8d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
"""One shared, task-switchable UI instead of five duplicated forms."""

from __future__ import annotations

import gradio as gr

from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
from core.execution_plan import RUN_MODE_CHOICES
from core.runtime_config import CONFIG
from core.settings import (
    ARCHITECTURES_CONFIG,
    MODEL_DEFAULTS_CONFIG,
    MODEL_MAP_CHECKPOINT,
    MODEL_TYPE_MAP,
    RESOLUTION_MAP,
)
from ui.guidance import QUICK_PRESETS, model_hint, task_help
from .ui_components import (
    create_anima_controlnet_lllite_ui,
    create_boogu_edit_ui,
    create_category_filter_ui,
    create_conditioning_ui,
    create_controlnet_ui,
    create_diffsynth_controlnet_ui,
    create_embedding_ui,
    create_flux1_ipadapter_ui,
    create_hidream_o1_reference_ui,
    create_ipadapter_ui,
    create_joyai_reference_ui,
    create_krea2_controlnet_ui,
    create_krea2_identity_edit_ui,
    create_krea2_reference_edit_ui,
    create_lora_settings_ui,
    create_model_architecture_filter_ui,
    create_pid_ui,
    create_qwen_image_edit_ui,
    create_reference_image_ui,
    create_reference_latent_ui,
    create_sd3_ipadapter_ui,
    create_style_ui,
    create_vae_override_ui,
)


TASK_CHOICES = [
    ("✨ 文生图", "txt2img"),
    ("🖼️ 图生图", "img2img"),
    ("🖌️ 局部重绘", "inpaint"),
    ("↔️ 扩图", "outpaint"),
    ("🔎 高清修复", "hires_fix"),
]

PROMPT_EXAMPLES = [
    ["一位银发少女站在雨夜车站,半身构图,电影感侧光,细腻插画,高质量"],
    ["现代东方客厅,浅木色与米白配色,午后自然光,广角室内摄影"],
    ["未来城市夜景,湿润街道倒影,霓虹灯,低机位,强烈纵深,电影概念设计"],
]


def create_ui() -> dict:
    prefix = "studio"
    components: dict = {"_task_prefixes": [(prefix, None)]}
    default_values = MODEL_DEFAULTS_CONFIG.get("Default", {})
    default_model = next(iter(MODEL_MAP_CHECKPOINT), None)
    default_architecture = MODEL_TYPE_MAP.get(default_model, "SDXL")
    default_architecture_info = ARCHITECTURES_CONFIG.get("architectures", {}).get(
        default_architecture, {}
    )
    default_resolution_key = default_architecture_info.get("model_type", "sdxl")
    default_resolution_map = RESOLUTION_MAP.get(
        default_resolution_key, RESOLUTION_MAP.get("sdxl", {})
    )
    default_aspect = next(iter(default_resolution_map), "1:1 (Square)")
    default_width, default_height = default_resolution_map.get(default_aspect, (1024, 1024))

    components[f"task_type_{prefix}"] = gr.Radio(
        choices=TASK_CHOICES,
        value="txt2img",
        label="1. 选择任务",
        elem_id="task-switcher",
    )
    components[f"task_help_{prefix}"] = gr.Markdown(
        task_help("txt2img"), elem_classes="task-help"
    )

    with gr.Row(equal_height=False, elem_id="workspace-row"):
        with gr.Column(scale=7, min_width=360):
            with gr.Accordion("2. 选择模型", open=True):
                components[f"quick_preset_{prefix}"] = gr.Dropdown(
                    choices=QUICK_PRESETS,
                    value="__manual__",
                    label="快速方案",
                    info="只是帮你定位模型;仍可继续调整所有参数。",
                    interactive=True,
                )
                components.update(create_model_architecture_filter_ui(prefix))
                with gr.Row():
                    components.update(create_category_filter_ui(prefix))
                    components[f"base_model_{prefix}"] = gr.Dropdown(
                        label="具体模型",
                        choices=list(MODEL_MAP_CHECKPOINT.keys()),
                        value=default_model,
                        filterable=True,
                        allow_custom_value=False,
                        scale=3,
                        interactive=True,
                    )
                components[f"model_hint_{prefix}"] = gr.Markdown(
                    model_hint(default_model) if default_model else "暂无可用模型。",
                    elem_classes="model-hint",
                )

            with gr.Accordion("批量、多图与模型 PK(可选)", open=False):
                components[f"run_mode_{prefix}"] = gr.Dropdown(
                    choices=RUN_MODE_CHOICES,
                    value="single",
                    label="运行方式",
                    info="所有模式都顺序执行,不会同时把多个大模型塞进显存。",
                )
                components[f"pk_models_{prefix}"] = gr.Dropdown(
                    choices=list(MODEL_MAP_CHECKPOINT.keys()),
                    value=[],
                    multiselect=True,
                    max_choices=max(1, CONFIG.max_pk_models - 1),
                    label="额外对比模型",
                    info=f"当前模型会自动加入;最多合计 {CONFIG.max_pk_models} 个。",
                    filterable=True,
                    visible=False,
                )
                components[f"batch_images_{prefix}"] = gr.File(
                    file_count="multiple",
                    file_types=["image"],
                    type="filepath",
                    allow_reordering=True,
                    label="批量输入 / 多图参考",
                    visible=False,
                )
                with gr.Row():
                    components[f"reference_role_{prefix}"] = gr.Dropdown(
                        choices=[
                            ("自动匹配模型能力", "auto"),
                            ("人物 / 身份参考", "identity"),
                            ("风格参考", "style"),
                        ],
                        value="auto",
                        label="多图融合用途",
                        visible=False,
                    )
                    components[f"pk_model_defaults_{prefix}"] = gr.Checkbox(
                        value=True,
                        label="PK 使用各模型推荐采样参数",
                        info="Prompt、尺寸与种子保持一致;步数和 CFG 按模型推荐值。",
                        visible=False,
                    )
                gr.Markdown(
                    "**怎么选:** 多图独立是 A→A′、B→B′;多图×多模型会执行图片和模型的组合;"
                    "多图融合是把整组图片同时交给 Qwen/Mage-Flow/JoyAI/Boogu/Krea/Flux 等兼容模型。"
                )

            with gr.Group(elem_classes="prompt-card"):
                components[f"prompt_{prefix}"] = gr.Textbox(
                    label="3. 描述你想要的画面",
                    placeholder="主体 + 动作 + 场景 + 构图 + 风格 + 光线",
                    lines=4,
                    max_lines=8,
                    value=default_values.get("positive_prompt", ""),
                )
                with gr.Accordion("负面提示词(可选)", open=False):
                    components[f"neg_prompt_{prefix}"] = gr.Textbox(
                        label="不希望出现的内容",
                        placeholder="例如:低质量、模糊、多余手指、水印",
                        lines=2,
                        value=default_values.get("negative_prompt", ""),
                    )
                gr.Examples(
                    examples=PROMPT_EXAMPLES,
                    inputs=[components[f"prompt_{prefix}"]],
                    label="点一下填入示例",
                )

            with gr.Group(visible=False) as source_panel:
                components[f"source_image_{prefix}"] = gr.Image(
                    type="pil",
                    label="源图片",
                    sources=["upload", "clipboard"],
                    height=300,
                )
            components[f"source_panel_{prefix}"] = source_panel

            with gr.Group(visible=False) as inpaint_panel:
                gr.Markdown("涂抹需要替换的区域;未涂抹部分会尽量保留。")
                components[f"inpaint_image_dict_{prefix}"] = gr.ImageEditor(
                    type="pil",
                    label="图片与蒙版",
                    sources=["upload", "clipboard"],
                    height=420,
                )
                with gr.Row():
                    components[f"inpaint_denoise_{prefix}"] = gr.Slider(
                        0.0, 1.0, value=1.0, step=0.05, label="局部重绘幅度"
                    )
                    components[f"grow_mask_by_{prefix}"] = gr.Slider(
                        0, 64, value=6, step=1, label="蒙版外扩像素"
                    )
            components[f"inpaint_panel_{prefix}"] = inpaint_panel

            with gr.Group(visible=False) as img2img_panel:
                components[f"img2img_denoise_{prefix}"] = gr.Slider(
                    0.0,
                    1.0,
                    value=0.7,
                    step=0.01,
                    label="重绘幅度",
                    info="0 更接近原图,1 改动最大。",
                )
            components[f"img2img_panel_{prefix}"] = img2img_panel

            with gr.Group(visible=False) as outpaint_panel:
                with gr.Row():
                    components[f"left_{prefix}"] = gr.Slider(0, 512, 64, step=64, label="向左扩展")
                    components[f"right_{prefix}"] = gr.Slider(0, 512, 64, step=64, label="向右扩展")
                with gr.Row():
                    components[f"top_{prefix}"] = gr.Slider(0, 512, 64, step=64, label="向上扩展")
                    components[f"bottom_{prefix}"] = gr.Slider(0, 512, 64, step=64, label="向下扩展")
                components[f"feathering_{prefix}"] = gr.Slider(
                    0, 100, 10, step=1, label="接缝羽化 / 蒙版外扩"
                )
            components[f"outpaint_panel_{prefix}"] = outpaint_panel

            with gr.Group(visible=False) as hires_panel:
                with gr.Row():
                    components[f"hires_upscaler_{prefix}"] = gr.Dropdown(
                        choices=["nearest-exact", "bilinear", "area", "bicubic", "bislerp"],
                        value="nearest-exact",
                        label="放大算法",
                    )
                    components[f"hires_scale_by_{prefix}"] = gr.Slider(
                        1.0, 4.0, 1.5, step=0.1, label="放大倍数"
                    )
                components[f"hires_denoise_{prefix}"] = gr.Slider(
                    0.0, 1.0, 0.55, step=0.01, label="细节重绘幅度"
                )
            components[f"hires_panel_{prefix}"] = hires_panel

            with gr.Group() as size_panel:
                with gr.Row():
                    components[f"aspect_ratio_{prefix}"] = gr.Dropdown(
                        label="画幅",
                        choices=list(default_resolution_map) or [default_aspect],
                        value=default_aspect,
                        interactive=True,
                        scale=2,
                    )
                    components[f"width_{prefix}"] = gr.Number(
                        label="宽度", value=default_width, precision=0, interactive=True
                    )
                    components[f"height_{prefix}"] = gr.Number(
                        label="高度", value=default_height, precision=0, interactive=True
                    )
            components[f"size_panel_{prefix}"] = size_panel

            with gr.Accordion("高级采样与运行参数", open=False):
                with gr.Row():
                    components[f"sampler_{prefix}"] = gr.Dropdown(
                        choices=SAMPLER_CHOICES,
                        value=default_values.get("sampler_name", "euler"),
                        label="采样器",
                    )
                    components[f"scheduler_{prefix}"] = gr.Dropdown(
                        choices=SCHEDULER_CHOICES,
                        value=default_values.get("scheduler", "simple"),
                        label="调度器",
                    )
                with gr.Row():
                    components[f"steps_{prefix}"] = gr.Slider(
                        1, 100, default_values.get("steps", 8), step=1, label="采样步数"
                    )
                    components[f"cfg_{prefix}"] = gr.Slider(
                        1.0, 20.0, default_values.get("cfg", 1.0), step=0.1, label="CFG"
                    )
                with gr.Row():
                    components[f"seed_{prefix}"] = gr.Number(
                        label="随机种子(-1 为随机)", value=-1, precision=0
                    )
                    components[f"batch_size_{prefix}"] = gr.Slider(
                        1,
                        CONFIG.max_batch_size,
                        1,
                        step=1,
                        label="单次生成数量",
                        info="数量越大,显存与 ZeroGPU 配额消耗越高。",
                    )
                with gr.Row():
                    components[f"clip_skip_{prefix}"] = gr.Slider(
                        1, 2, 1, step=1, label="Clip Skip", visible=False
                    )
                    components[f"guidance_{prefix}"] = gr.Slider(
                        1.0, 10.0, 3.5, step=0.1, label="FLUX Guidance", visible=False
                    )
                    components[f"zero_gpu_{prefix}"] = gr.Dropdown(
                        choices=[("自动估算", 0), ("60 秒", 60), ("90 秒", 90), ("120 秒", 120)],
                        value=0,
                        label="ZeroGPU 运行时长",
                        info="通常保持自动;复杂高分辨率任务可手动提高。",
                    )
                with gr.Row():
                    components[f"auto_model_params_{prefix}"] = gr.Checkbox(
                        value=True,
                        label="切模型时自动应用推荐采样参数",
                        info="关闭后会保留你手调的步数、CFG、采样器和调度器。",
                        scale=3,
                    )
                    components[f"reset_model_params_{prefix}"] = gr.Button(
                        "恢复当前模型推荐值", size="sm", scale=1
                    )

        with gr.Column(scale=5, min_width=340, elem_id="result-column"):
            with gr.Row():
                components[f"run_{prefix}"] = gr.Button(
                    "开始生成", variant="primary", size="lg", scale=3
                )
                components[f"cancel_{prefix}"] = gr.Button(
                    "取消排队", variant="stop", size="lg", scale=1
                )
            components[f"result_{prefix}"] = gr.Gallery(
                label="生成结果",
                show_label=True,
                columns=2,
                object_fit="contain",
                height=720,
            )
            components[f"run_summary_{prefix}"] = gr.Markdown(
                "普通模式会在这里显示执行结果;PK/批量模式会逐项列出成功与失败。"
            )
            components[f"clear_result_{prefix}"] = gr.Button("清空结果", size="sm")
            gr.Markdown(
                "生成结果包含 Prompt、参数和完整 ComfyUI 工作流元数据,下载 PNG 后可继续复现。",
                elem_classes="result-note",
            )

    gr.Markdown("## 当前模型可用的扩展能力", elem_classes="advanced-title")
    gr.Markdown("下方只显示当前模型兼容的能力;切换模型后会自动更新。")
    components.update(create_lora_settings_ui(prefix))
    components.update(create_controlnet_ui(prefix))
    components.update(create_anima_controlnet_lllite_ui(prefix))
    components.update(create_diffsynth_controlnet_ui(prefix))
    components.update(create_krea2_controlnet_ui(prefix))
    components.update(create_ipadapter_ui(prefix))
    components.update(create_flux1_ipadapter_ui(prefix))
    components.update(create_sd3_ipadapter_ui(prefix))
    components.update(create_embedding_ui(prefix))
    components.update(create_style_ui(prefix))
    components.update(create_conditioning_ui(prefix))
    components.update(create_reference_latent_ui(prefix))
    components.update(create_hidream_o1_reference_ui(prefix))
    components.update(create_joyai_reference_ui(prefix))
    components.update(create_krea2_identity_edit_ui(prefix))
    components.update(create_krea2_reference_edit_ui(prefix))
    components.update(create_qwen_image_edit_ui(prefix))
    components.update(create_boogu_edit_ui(prefix))
    components.update(create_reference_image_ui(prefix))
    components.update(create_vae_override_ui(prefix))
    components.update(create_pid_ui(prefix))

    return components