dsh-comfyui-rp 0.2.1 → 0.2.3
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.
- package/README.md +1 -1
- package/docs/CHANGELOG.md +18 -0
- package/lib/api.js +7 -1
- package/lib/client.js +252 -21
- package/lib/comfy.js +24 -1
- package/lib/jobs.js +6 -0
- package/lib/meta.js +1 -1
- package/lib/store.js +7 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ dsh plugin --profile web add git+https://github.com/bmhty123/dsh-comfyui-rp.git
|
|
|
40
40
|
|
|
41
41
|
## 能做什么
|
|
42
42
|
|
|
43
|
-
- **自动出图**:捕获正文 → 解析提示词 → 出图 →
|
|
43
|
+
- **自动出图**:捕获正文 → 解析提示词 → 出图 → 归档,全自动;投递和出图可分开开关。纯黑图会自动换种子重生成,进行中可以随时点「停止自动补图」。
|
|
44
44
|
- **投递**:正文来源和提示词来源分开,可指到另一个会话甚至另一台 DSH;附加消息放正文最上面或最下面,投递成功后自动留空。
|
|
45
45
|
- **看图**:右侧栏面板、全屏、悬浮球、悬浮图片框。翻页、下载、收藏、刷新、删除、圆角;自己拖过 / 缩过之后,切图不再改回初始大小。
|
|
46
46
|
- **跟图**:滚动正文自动切图,连续图按段落比例拆位置,可切光标跟图、手动偏移、清空定位。
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# 更新记录
|
|
2
2
|
|
|
3
|
+
## 0.2.3
|
|
4
|
+
|
|
5
|
+
- **自动补图可以随时停下**:补图进行中,图片框左下菜单的按钮会变成「终止」(之前因为补图是内部任务,菜单里根本看不到它),点一下清空队列并中止正在跑的那一单;全屏和工作台的「终止」也一样。右侧栏顶部还有一条「停止自动补图」。下一次新任务开始时自动复位。
|
|
6
|
+
- **黑图自动替换开关**:工作流页 →「图片输出相关」→「额外设置」里新增开关,关掉后不再检测纯黑图,待补队列也会清空。
|
|
7
|
+
- **纯黑判定更严,修掉误判**:以前只要求「暗像素 ≥ 99%」就会判黑,夜景、黑底小主体这类图会被误伤。现在必须同时满足:暗像素 ≥ 99.5%、平均亮度 ≤ 8、亮点(亮度 > 110)占比 < 0.1%,并且不透明像素要占到一半以上(透明底的小图不算黑)。
|
|
8
|
+
- **固定种子可手填**:工作流页「每次换随机种子」下面多了一个「固定种子」输入框。
|
|
9
|
+
- 勾着自动时它是灰的,显示**上一轮实际用的种子**(方便看到满意的一张后直接固定下来);
|
|
10
|
+
- 取消勾选后解锁,填的整数会立刻存进工作流,出图时覆盖每一处字面量 seed;取消的那一刻会把上一轮的种子自动带进来。
|
|
11
|
+
- 这些开关改完直接生效,不用再点「保存」。
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## 0.2.2
|
|
15
|
+
|
|
16
|
+
- **LoRA 强度可以填负数**:工作台「连续图」的档位、LoRA 页每条的强度都允许 `-5 ~ 5`(反相 LoRA 用)。
|
|
17
|
+
- 这两个输入框在输入过程中不再被强制夹取:打「-」「-.5」这种中间态会先留在文本框里,**失焦或按回车**才夹到范围内并写回。
|
|
18
|
+
- 工作台的分辨率宽高也是同样规则(这条从 0.2.1 起就是)。
|
|
19
|
+
- 连续图说明里补了一句「强度可以填负数(反相),范围 -5 ~ 5」。
|
|
20
|
+
|
|
3
21
|
## 0.2.1 — 首个公开发布
|
|
4
22
|
|
|
5
23
|
第一个放到 npm / 插件市场里的版本,功能面就是 README 里描述的这一整套:
|
package/lib/api.js
CHANGED
|
@@ -358,7 +358,7 @@ export function createApi({ store, jobs, hub, getConfig, ctx, sc, routePath, ver
|
|
|
358
358
|
workflows: compact ? [] : store.listWorkflows(),
|
|
359
359
|
activeWorkflowId: cfg.workflowId,
|
|
360
360
|
activeWorkflow: activeWf
|
|
361
|
-
? { id: activeWf.id, name: activeWf.name, size: activeWf.size || null, sizeMode: activeWf.sizeMode || 'manual', randomSeed: activeWf.randomSeed !== false, nodeCount: Object.keys(activeWf.json || {}).length }
|
|
361
|
+
? { id: activeWf.id, name: activeWf.name, size: activeWf.size || null, sizeMode: activeWf.sizeMode || 'manual', randomSeed: activeWf.randomSeed !== false, blackRetry: activeWf.blackRetry !== false, nodeCount: Object.keys(activeWf.json || {}).length }
|
|
362
362
|
: null,
|
|
363
363
|
jobs: jobsList,
|
|
364
364
|
images: imageItems,
|
|
@@ -604,6 +604,9 @@ export function createApi({ store, jobs, hub, getConfig, ctx, sc, routePath, ver
|
|
|
604
604
|
size: wf.size || null,
|
|
605
605
|
sizeMode: wf.sizeMode || 'manual',
|
|
606
606
|
randomSeed: wf.randomSeed !== false,
|
|
607
|
+
seed: wf.seed === null || wf.seed === undefined ? null : Number(wf.seed),
|
|
608
|
+
lastSeed: wf.lastSeed === null || wf.lastSeed === undefined ? null : Number(wf.lastSeed),
|
|
609
|
+
blackRetry: wf.blackRetry !== false,
|
|
607
610
|
pruneOutputs: wf.pruneOutputs !== false,
|
|
608
611
|
onlyFirst: wf.onlyFirst === true,
|
|
609
612
|
loraState: wf.loraState || null,
|
|
@@ -662,12 +665,15 @@ export function createApi({ store, jobs, hub, getConfig, ctx, sc, routePath, ver
|
|
|
662
665
|
const sizeMode = body.sizeMode || wf?.sizeMode || 'manual';
|
|
663
666
|
const loraState = body.loraState !== undefined ? body.loraState : resolveLoraState(wf || {});
|
|
664
667
|
const loraScan = scanLoraStacks(json);
|
|
668
|
+
// 固定种子:干跑也要按工作流页填的那个值算,否则「干跑」和真出一张的结果对不上。
|
|
669
|
+
const seed = body.seed !== undefined ? body.seed : (wf && wf.seed !== undefined ? wf.seed : null);
|
|
665
670
|
const { workflow, report } = applyOverrides(json, {
|
|
666
671
|
positive: String(body.positive ?? ''),
|
|
667
672
|
negative: String(body.negative ?? ''),
|
|
668
673
|
mapping,
|
|
669
674
|
tokenEmpty: 'blank',
|
|
670
675
|
randomSeed: body.randomSeed === true,
|
|
676
|
+
seed,
|
|
671
677
|
loraState,
|
|
672
678
|
size,
|
|
673
679
|
sizeSource: sizeMode,
|
package/lib/client.js
CHANGED
|
@@ -221,6 +221,30 @@ window.__ModuleLoader__.load({
|
|
|
221
221
|
|
|
222
222
|
function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, Number(v) || 0)); }
|
|
223
223
|
|
|
224
|
+
/**
|
|
225
|
+
* 自动补图的全局状态:逻辑在 FloatBall 里跑(它一直在,负责检测纯黑图),
|
|
226
|
+
* 但「停止」按钮要能出现在右侧栏面板里,所以两边共享这一份。
|
|
227
|
+
* pending = 队列里的 + 正在跑的;stop = FloatBall 注册过来的停止函数。
|
|
228
|
+
*/
|
|
229
|
+
var BLACK_UI = { pending: 0, stop: null, subs: new Set() };
|
|
230
|
+
function setBlackPendingGlobal(n) {
|
|
231
|
+
var value = Math.max(0, Number(n) || 0);
|
|
232
|
+
if (BLACK_UI.pending === value) return;
|
|
233
|
+
BLACK_UI.pending = value;
|
|
234
|
+
BLACK_UI.subs.forEach(function (fn) { try { fn(value); } catch (e) { /* ignore */ } });
|
|
235
|
+
}
|
|
236
|
+
function useBlackPending() {
|
|
237
|
+
var [pending, setPending] = useState(BLACK_UI.pending);
|
|
238
|
+
useEffect(function () {
|
|
239
|
+
var fn = function (n) { setPending(n); };
|
|
240
|
+
BLACK_UI.subs.add(fn);
|
|
241
|
+
setPending(BLACK_UI.pending);
|
|
242
|
+
return function () { BLACK_UI.subs.delete(fn); };
|
|
243
|
+
}, []);
|
|
244
|
+
return pending;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
224
248
|
function loadTheme() {
|
|
225
249
|
try { return localStorage.getItem(LS_THEME) === "light" ? "light" : "dark"; } catch (e) { return "dark"; }
|
|
226
250
|
}
|
|
@@ -504,6 +528,8 @@ window.__ModuleLoader__.load({
|
|
|
504
528
|
:root{--ac-bg:#151517;--ac-bg2:#1b1b1d;--ac-card:#1b1b1d;--ac-raised:#2a2a2c;--ac-line:#303033;--ac-line2:#3a3a3d;--ac-text:#ececee;--ac-text2:#c6c6ca;--ac-muted:#a8a8ad;--ac-dim:#8a8a90;--ac-accent:#b3bfd0;--ac-accent2:#dbe1ea;--ac-ok:#6fbf8f;--ac-warn:#d1ac72;--ac-err:#d18a8a;--ac-fs:12.5px;--ac-fs-s:11.5px;--ac-fs-l:11px;}
|
|
505
529
|
.ac-root{--ac-bg:#151517;--ac-bg2:#1b1b1d;--ac-card:#1b1b1d;--ac-raised:#2a2a2c;--ac-line:#303033;--ac-line2:#3a3a3d;--ac-text:#ececee;--ac-text2:#c6c6ca;--ac-muted:#a8a8ad;--ac-dim:#8a8a90;--ac-accent:#b3bfd0;--ac-accent2:#dbe1ea;--ac-ok:#6fbf8f;--ac-warn:#d1ac72;--ac-err:#d18a8a;--ac-fs:12.5px;--ac-fs-s:11.5px;--ac-fs-l:11px;box-sizing:border-box;height:100%;min-height:0;display:flex;flex-direction:column;background:var(--ac-bg);color:var(--ac-text);font-size:var(--ac-fs);line-height:1.5;font-family:var(--ac-font,inherit);color-scheme:dark;}
|
|
506
530
|
.ac-root *{box-sizing:border-box;}
|
|
531
|
+
.ac-blackbar{display:flex;align-items:center;gap:8px;padding:7px 10px;background:rgba(96,72,30,.5);border-bottom:1px solid var(--ac-line);color:var(--ac-text);font-size:var(--ac-fs-s);flex:0 0 auto;}
|
|
532
|
+
.ac-blackbar .ac-blackbar-txt{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
507
533
|
.ac-ballroot,.ac-ballroot *,.ac-overlay,.ac-overlay *{box-sizing:border-box;}
|
|
508
534
|
.ac-root ::selection{background:rgba(130,150,180,.35);}
|
|
509
535
|
.ac-root ::-webkit-scrollbar{width:8px;height:8px;}
|
|
@@ -1758,7 +1784,10 @@ window.__ModuleLoader__.load({
|
|
|
1758
1784
|
var shown = view || img || null;
|
|
1759
1785
|
if (shown && shown.id) lastFrameView = shown;
|
|
1760
1786
|
}, [view && view.id, view && view.url, img && img.id, img && img.url]);
|
|
1761
|
-
|
|
1787
|
+
// 黑图补图是 internal 任务,不在 visibleJobs 里,所以「有没有在跑」要单看这个计数:
|
|
1788
|
+
// 补图进行中也要能把菜单里的「请求生成」换成「终止」。
|
|
1789
|
+
var blackActive = useBlackPending() > 0;
|
|
1790
|
+
var live = blackActive || !!(job && (job.status === "delivering" || job.status === "parsing" || job.status === "running" || job.status === "queued"));
|
|
1762
1791
|
// anchor / turn 也进 key:旧图回填锚点后 id 不变,但跟随逻辑必须重建,
|
|
1763
1792
|
// 否则监听器里还是“没有锚点”的旧 imgs。
|
|
1764
1793
|
var imgKey = imgs.map(function (x) {
|
|
@@ -3693,14 +3722,45 @@ window.__ModuleLoader__.load({
|
|
|
3693
3722
|
});
|
|
3694
3723
|
}, "重新生成");
|
|
3695
3724
|
};
|
|
3725
|
+
/**
|
|
3726
|
+
* 纯黑判定(纯函数,便于自检):从缩放后的 RGBA 采样里算,三条同时满足才算黑:
|
|
3727
|
+
* ① 暗像素(亮度 < 14)占比 ≥ 99.5%
|
|
3728
|
+
* ② 平均亮度 ≤ 8
|
|
3729
|
+
* ③ 亮点(亮度 > 110)占比 < 0.1%
|
|
3730
|
+
* 只满足 ① 会误伤夜景 / 黑底小主体——之前就是这么误判的。
|
|
3731
|
+
* 另外要求不透明像素覆盖 ≥ 50%:透明底的小图不是“黑图”,是透明图。
|
|
3732
|
+
*/
|
|
3733
|
+
function judgeNearlyBlack(d) {
|
|
3734
|
+
var pixels = d.length / 4;
|
|
3735
|
+
var opaque = 0;
|
|
3736
|
+
var dark = 0;
|
|
3737
|
+
var bright = 0;
|
|
3738
|
+
var lumSum = 0;
|
|
3739
|
+
for (var i = 0; i < d.length; i += 4) {
|
|
3740
|
+
if (d[i + 3] < 16) continue; // 透明像素不算
|
|
3741
|
+
opaque += 1;
|
|
3742
|
+
var lum = 0.2126 * d[i] + 0.7152 * d[i + 1] + 0.0722 * d[i + 2];
|
|
3743
|
+
lumSum += lum;
|
|
3744
|
+
if (lum < 14) dark += 1;
|
|
3745
|
+
else if (lum > 110) bright += 1;
|
|
3746
|
+
}
|
|
3747
|
+
var darkRatio = opaque ? dark / opaque : 0;
|
|
3748
|
+
var brightRatio = opaque ? bright / opaque : 0;
|
|
3749
|
+
var meanLum = opaque ? lumSum / opaque : 0;
|
|
3750
|
+
var coverage = pixels ? opaque / pixels : 0;
|
|
3751
|
+
var black = opaque >= 8 && coverage >= 0.5
|
|
3752
|
+
&& darkRatio >= 0.995 && meanLum <= 8 && brightRatio < 0.001;
|
|
3753
|
+
return { black: black, darkRatio: darkRatio, brightRatio: brightRatio, meanLum: meanLum, coverage: coverage, opaque: opaque };
|
|
3754
|
+
}
|
|
3696
3755
|
var isNearlyBlack = function (url) {
|
|
3697
3756
|
return new Promise(function (resolve) {
|
|
3698
3757
|
if (!url || typeof document === "undefined" || typeof Image === "undefined") return resolve(false);
|
|
3699
3758
|
var probe = new Image();
|
|
3700
3759
|
probe.onload = function () {
|
|
3701
3760
|
try {
|
|
3702
|
-
|
|
3703
|
-
var
|
|
3761
|
+
if (!probe.naturalWidth || !probe.naturalHeight) return resolve(false);
|
|
3762
|
+
var w = Math.max(1, Math.min(256, probe.naturalWidth));
|
|
3763
|
+
var h = Math.max(1, Math.min(256, probe.naturalHeight));
|
|
3704
3764
|
var canvas = document.createElement("canvas");
|
|
3705
3765
|
canvas.width = w;
|
|
3706
3766
|
canvas.height = h;
|
|
@@ -3708,14 +3768,7 @@ window.__ModuleLoader__.load({
|
|
|
3708
3768
|
if (!g) return resolve(false);
|
|
3709
3769
|
g.drawImage(probe, 0, 0, w, h);
|
|
3710
3770
|
var d = g.getImageData(0, 0, w, h).data;
|
|
3711
|
-
|
|
3712
|
-
var black = 0;
|
|
3713
|
-
for (var i = 0; i < d.length; i += 4) {
|
|
3714
|
-
if (d[i + 3] < 16) continue; // 透明像素不算黑
|
|
3715
|
-
total += 1;
|
|
3716
|
-
if (d[i] < 16 && d[i + 1] < 16 && d[i + 2] < 16) black += 1;
|
|
3717
|
-
}
|
|
3718
|
-
resolve(total > 0 && black / total >= 0.99);
|
|
3771
|
+
resolve(judgeNearlyBlack(d).black);
|
|
3719
3772
|
} catch (e) { resolve(false); }
|
|
3720
3773
|
};
|
|
3721
3774
|
probe.onerror = function () { resolve(false); };
|
|
@@ -3723,6 +3776,27 @@ window.__ModuleLoader__.load({
|
|
|
3723
3776
|
});
|
|
3724
3777
|
};
|
|
3725
3778
|
var blackQueueRef = useRef([]);
|
|
3779
|
+
// 自动补图可以随时叫停:队列清空 + 正在跑的那单 abort,并且这一轮不再自动补。
|
|
3780
|
+
// blackStopRef 在下一轮新任务完成时复位,避免「点过一次停止后就再也不补」。
|
|
3781
|
+
var blackStopRef = useRef(false);
|
|
3782
|
+
var blackJobRef = useRef("");
|
|
3783
|
+
var syncBlackPending = function () {
|
|
3784
|
+
setBlackPendingGlobal(blackQueueRef.current.length + (blackBusyRef.current ? 1 : 0));
|
|
3785
|
+
};
|
|
3786
|
+
var stopBlack = function () {
|
|
3787
|
+
blackStopRef.current = true;
|
|
3788
|
+
blackQueueRef.current = [];
|
|
3789
|
+
var jid = blackJobRef.current;
|
|
3790
|
+
blackJobRef.current = "";
|
|
3791
|
+
syncBlackPending();
|
|
3792
|
+
showToast("已停止自动补图", "ok");
|
|
3793
|
+
if (jid) api.abort(jid).catch(function () { /* 任务可能已经结束了 */ });
|
|
3794
|
+
};
|
|
3795
|
+
// 面板上的「停止自动补图」通过这个入口回调过来。
|
|
3796
|
+
useEffect(function () {
|
|
3797
|
+
BLACK_UI.stop = stopBlack;
|
|
3798
|
+
return function () { if (BLACK_UI.stop === stopBlack) BLACK_UI.stop = null; };
|
|
3799
|
+
}, []);
|
|
3726
3800
|
/** 同一张图(同一套提示词)跨多次自动换种子仍用同一个 key,用来限制最多补几次。 */
|
|
3727
3801
|
var blackSlotKey = function (sourceJob, im) {
|
|
3728
3802
|
var sets = Array.isArray(sourceJob && sourceJob.sets) ? sourceJob.sets : [];
|
|
@@ -3738,16 +3812,20 @@ window.__ModuleLoader__.load({
|
|
|
3738
3812
|
};
|
|
3739
3813
|
var enqueueBlack = function (im, sourceJob, attempt, key, label) {
|
|
3740
3814
|
if (!im || !im.id || !im.url) return;
|
|
3815
|
+
if (blackStopRef.current) return;
|
|
3741
3816
|
blackQueueRef.current.push({
|
|
3742
3817
|
id: String(im.id), jobId: im.jobId || (sourceJob && sourceJob.id) || "",
|
|
3743
3818
|
setIndex: im.setIndex, url: im.url, attempt: attempt || 1, key: key || "", label: label || "",
|
|
3744
3819
|
});
|
|
3820
|
+
syncBlackPending();
|
|
3745
3821
|
};
|
|
3746
3822
|
var drainBlackQueue = function () {
|
|
3747
3823
|
if (blackBusyRef.current) return;
|
|
3748
3824
|
var next = blackQueueRef.current.shift();
|
|
3749
|
-
if (!next) return;
|
|
3825
|
+
if (!next) { syncBlackPending(); return; }
|
|
3826
|
+
if (blackStopRef.current) { blackQueueRef.current = []; syncBlackPending(); return; }
|
|
3750
3827
|
blackBusyRef.current = true;
|
|
3828
|
+
syncBlackPending();
|
|
3751
3829
|
isNearlyBlack(next.url).then(function (black) {
|
|
3752
3830
|
if (!black) return null;
|
|
3753
3831
|
if (next.attempt === 1) {
|
|
@@ -3767,17 +3845,32 @@ window.__ModuleLoader__.load({
|
|
|
3767
3845
|
mode: "auto",
|
|
3768
3846
|
sessionId: data && data.activeSessionId || "",
|
|
3769
3847
|
sessionTitle: data && data.activeSessionTitle || "",
|
|
3848
|
+
}).then(function (r) {
|
|
3849
|
+
blackJobRef.current = r && r.job && r.job.id ? String(r.job.id) : "";
|
|
3850
|
+
return r;
|
|
3770
3851
|
});
|
|
3771
3852
|
}).catch(function () { /* 黑图重生成失败不阻塞其它图 */ }).then(function () {
|
|
3772
3853
|
blackBusyRef.current = false;
|
|
3854
|
+
blackJobRef.current = "";
|
|
3855
|
+
syncBlackPending();
|
|
3773
3856
|
drainBlackQueue();
|
|
3774
3857
|
});
|
|
3775
3858
|
};
|
|
3776
3859
|
var checkBlackImages = function (sourceJob) {
|
|
3777
3860
|
if (!sourceJob || sourceJob.status !== "done") return;
|
|
3861
|
+
// 工作流页「图片输出相关 → 黑图自动替换」关掉就整个不检测(连已在队列里的也清掉)。
|
|
3862
|
+
if (data && data.activeWorkflow && data.activeWorkflow.blackRetry === false) {
|
|
3863
|
+
if (blackQueueRef.current.length || blackBusyRef.current) {
|
|
3864
|
+
blackQueueRef.current = [];
|
|
3865
|
+
syncBlackPending();
|
|
3866
|
+
}
|
|
3867
|
+
return;
|
|
3868
|
+
}
|
|
3778
3869
|
// 只处理刚完成的这一轮;翻旧记录 / 重启页面不追溯历史图。
|
|
3779
3870
|
var finished = Date.parse(sourceJob.finishedAt || "");
|
|
3780
3871
|
if (!Number.isFinite(finished) || Date.now() - finished > 120000) return;
|
|
3872
|
+
// 新一轮(非自动补图)开始时解除上一次的「停止」,否则停过一次就再也不会补了。
|
|
3873
|
+
if (sourceJob.regenMode !== "auto") blackStopRef.current = false;
|
|
3781
3874
|
|
|
3782
3875
|
if (sourceJob.regenMode === "auto") {
|
|
3783
3876
|
// 自动补图后继续检查:仍然黑就再换种子,最多 BLACK_MAX_RETRIES 次。
|
|
@@ -3821,6 +3914,12 @@ window.__ModuleLoader__.load({
|
|
|
3821
3914
|
drainBlackQueue();
|
|
3822
3915
|
};
|
|
3823
3916
|
var stopJob = function () {
|
|
3917
|
+
// 图片框左下菜单的「终止」就是真正的终止:黑图补图是 internal 任务,
|
|
3918
|
+
// 一起停掉(清队列 + 中止正在跑的那单),否则点了终止它还在后台接着补。
|
|
3919
|
+
if (blackQueueRef.current.length || blackBusyRef.current) {
|
|
3920
|
+
stopBlack();
|
|
3921
|
+
return;
|
|
3922
|
+
}
|
|
3824
3923
|
if (!job || !job.id) return;
|
|
3825
3924
|
act(function () {
|
|
3826
3925
|
return api.abort(job.id).then(function () { showToast("已终止", "ok"); });
|
|
@@ -4547,6 +4646,8 @@ window.__ModuleLoader__.load({
|
|
|
4547
4646
|
});
|
|
4548
4647
|
var [toasts, setToasts] = useState([]);
|
|
4549
4648
|
var [conn, setConn] = useState(true);
|
|
4649
|
+
// 自动补图是 FloatBall 在跑,这里只订阅「还有几个在补」用来显示停止条。
|
|
4650
|
+
var blackPending = useBlackPending();
|
|
4550
4651
|
var [viewer, setViewer] = useState(null);
|
|
4551
4652
|
// 切到右侧栏其它标签时 BridgePanel 会卸载;memory 让回来时还在同一张图。
|
|
4552
4653
|
var bootSession = props && typeof props.sessionId === "string" ? props.sessionId : "";
|
|
@@ -5441,6 +5542,8 @@ window.__ModuleLoader__.load({
|
|
|
5441
5542
|
}).catch(function (e) { showToast("重新生成失败:" + (e.message || e), "err"); });
|
|
5442
5543
|
};
|
|
5443
5544
|
var fullStop = function () {
|
|
5545
|
+
// 全屏里的「终止」也一样:先把自动补图停掉,再中止当前任务。
|
|
5546
|
+
if (BLACK_UI.stop) BLACK_UI.stop();
|
|
5444
5547
|
if (!liveJob || !liveJob.id) return;
|
|
5445
5548
|
Promise.resolve().then(function () { return api.abort(liveJob.id); }).then(function () { showToast("已终止", "ok"); }).catch(function (e) { showToast("终止失败:" + (e.message || e), "err"); });
|
|
5446
5549
|
};
|
|
@@ -5809,6 +5912,11 @@ window.__ModuleLoader__.load({
|
|
|
5809
5912
|
};
|
|
5810
5913
|
|
|
5811
5914
|
return h("div", { className: cls("ac-root", fullImage && "fullimg", theme === "light" && "light") },
|
|
5915
|
+
blackPending > 0 ? h("div", { className: "ac-blackbar" },
|
|
5916
|
+
h("span", { className: "ac-blackbar-txt" },
|
|
5917
|
+
"检测到纯黑图,正在自动重新生成" + (blackPending > 1 ? "(队列 " + blackPending + " 个)" : "")),
|
|
5918
|
+
h(Btn, { small: true, variant: "danger", onClick: function () { if (BLACK_UI.stop) BLACK_UI.stop(); } }, "停止自动补图"),
|
|
5919
|
+
) : null,
|
|
5812
5920
|
fullImage
|
|
5813
5921
|
? h("div", {
|
|
5814
5922
|
className: cls("ac-fullview", square && "square"),
|
|
@@ -6561,6 +6669,8 @@ window.__ModuleLoader__.load({
|
|
|
6561
6669
|
})();
|
|
6562
6670
|
var [contCount, setContCount] = useState(initialStrengths.count);
|
|
6563
6671
|
var [contStrengths, setContStrengths] = useState(initialStrengths.values);
|
|
6672
|
+
// 连续图档位的输入原文:允许先打「-」「-.5」,离开输入框才兜底。
|
|
6673
|
+
var [contText, setContText] = useState({});
|
|
6564
6674
|
var cfgTimer = useRef(null);
|
|
6565
6675
|
var wfSkip = useRef(!!draft);
|
|
6566
6676
|
|
|
@@ -6786,12 +6896,37 @@ window.__ModuleLoader__.load({
|
|
|
6786
6896
|
setContCount(n);
|
|
6787
6897
|
saveContinuous({ count: n, strengths: contStrengths }, true);
|
|
6788
6898
|
};
|
|
6899
|
+
// 档位允许负数(反相 LoRA):输入期间保留原文(「-」「-.5」这种中间态),
|
|
6900
|
+
// 失焦 / 回车才夹到 -5..5 并落库。夹取范围和 lib/config.js 的服务端归一化一致。
|
|
6789
6901
|
var setStrength = function (i, value) {
|
|
6790
6902
|
var next = contStrengths.slice();
|
|
6791
|
-
next[i] = clamp(value,
|
|
6903
|
+
next[i] = Math.round(clamp(value, -5, 5) * 100) / 100;
|
|
6792
6904
|
setContStrengths(next);
|
|
6793
6905
|
saveContinuous({ strengths: next }, false);
|
|
6794
6906
|
};
|
|
6907
|
+
var editContStrength = function (i, raw) {
|
|
6908
|
+
setContText(function (old) {
|
|
6909
|
+
var n = Object.assign({}, old);
|
|
6910
|
+
n[i] = String(raw);
|
|
6911
|
+
return n;
|
|
6912
|
+
});
|
|
6913
|
+
var v = Number(raw);
|
|
6914
|
+
if (String(raw).trim() !== "" && isFinite(v)) {
|
|
6915
|
+
var next = contStrengths.slice();
|
|
6916
|
+
next[i] = v;
|
|
6917
|
+
setContStrengths(next);
|
|
6918
|
+
saveContinuous({ strengths: next }, false);
|
|
6919
|
+
}
|
|
6920
|
+
};
|
|
6921
|
+
var commitContStrength = function (i, raw) {
|
|
6922
|
+
setContText(function (old) {
|
|
6923
|
+
var n = Object.assign({}, old);
|
|
6924
|
+
delete n[i];
|
|
6925
|
+
return n;
|
|
6926
|
+
});
|
|
6927
|
+
if (String(raw === undefined || raw === null ? "" : raw).trim() === "") return;
|
|
6928
|
+
setStrength(i, raw);
|
|
6929
|
+
};
|
|
6795
6930
|
|
|
6796
6931
|
var loraOptions = [];
|
|
6797
6932
|
(loraStacks || []).forEach(function (st) {
|
|
@@ -6816,7 +6951,7 @@ window.__ModuleLoader__.load({
|
|
|
6816
6951
|
h("div", { className: "ac-wfbar" },
|
|
6817
6952
|
h("span", { className: "ac-wfname", title: wf ? wf.name : "未选择" }, "工作流:" + (wf ? wf.name : "未选择")),
|
|
6818
6953
|
h("span", { style: { flex: 1 } }),
|
|
6819
|
-
job && job.cancellable ? h(Btn, { small: true, variant: "danger", onClick: function () { api.abort(job.id).then(function () { props.toast("已终止", "ok"); }).catch(function (e) { props.toast("终止失败:" + (e.message || e), "err"); }); } }, "终止") : null,
|
|
6954
|
+
job && job.cancellable ? h(Btn, { small: true, variant: "danger", onClick: function () { if (BLACK_UI.stop) BLACK_UI.stop(); api.abort(job.id).then(function () { props.toast("已终止", "ok"); }).catch(function (e) { props.toast("终止失败:" + (e.message || e), "err"); }); } }, "终止") : null,
|
|
6820
6955
|
h(Btn, {
|
|
6821
6956
|
variant: "primary", className: "ac-genbtn",
|
|
6822
6957
|
disabled: busy || !sets.length || !sets.some(function (s) { return s.positive; }) || !!(job && job.cancellable),
|
|
@@ -6939,11 +7074,15 @@ window.__ModuleLoader__.load({
|
|
|
6939
7074
|
h("div", { className: "ac-strengthrow" },
|
|
6940
7075
|
contStrengths.slice(0, contCount).map(function (v, i) {
|
|
6941
7076
|
return h(Field, { key: i, label: "档位" + (i + 1) }, h(NumInput, {
|
|
6942
|
-
step: "0.05",
|
|
7077
|
+
step: "0.05", min: "-5", max: "5",
|
|
7078
|
+
value: contText[i] !== undefined ? contText[i] : String(v),
|
|
7079
|
+
onChange: function (e) { editContStrength(i, e.target.value); },
|
|
7080
|
+
onBlur: function (e) { commitContStrength(i, e.target.value); },
|
|
7081
|
+
onKeyDown: function (e) { if (e.key === "Enter") e.currentTarget.blur(); },
|
|
6943
7082
|
}));
|
|
6944
7083
|
}),
|
|
6945
7084
|
),
|
|
6946
|
-
h("div", { className: "ac-hint" }, "支持 LoRA 堆、单 LoRA、Power / Stacker / Tag
|
|
7085
|
+
h("div", { className: "ac-hint" }, "支持 LoRA 堆、单 LoRA、Power / Stacker / Tag 节点;仅影响最后一组提示词,按设置张数用不同强度连续生图。强度可以填负数(反相),范围 -5 ~ 5。"),
|
|
6947
7086
|
),
|
|
6948
7087
|
),
|
|
6949
7088
|
|
|
@@ -7332,6 +7471,21 @@ window.__ModuleLoader__.load({
|
|
|
7332
7471
|
var [dry, setDry] = useState(props.initialDry || null);
|
|
7333
7472
|
var [jsonOpen, setJsonOpen] = useState(props.initialJsonOpen === true);
|
|
7334
7473
|
var fileRef = useRef(null);
|
|
7474
|
+
// 「固定种子」文本框:null = 不在编辑(显示自动 / 已保存的值),字符串 = 正在输入的原文。
|
|
7475
|
+
var [seedDraft, setSeedDraft] = useState(null);
|
|
7476
|
+
var seedTimer = useRef(null);
|
|
7477
|
+
useEffect(function () {
|
|
7478
|
+
return function () { if (seedTimer.current) clearTimeout(seedTimer.current); };
|
|
7479
|
+
}, []);
|
|
7480
|
+
// 种子这类开关不走「保存」按钮也会立刻落库,否则改完忘了保存,出图还是老种子。
|
|
7481
|
+
var persistWf = function (p) {
|
|
7482
|
+
patch(p);
|
|
7483
|
+
if (!wf || !wf.id) return;
|
|
7484
|
+
if (seedTimer.current) clearTimeout(seedTimer.current);
|
|
7485
|
+
seedTimer.current = setTimeout(function () {
|
|
7486
|
+
api.updateWorkflow(wf.id, p).catch(function () { /* 落库失败不阻塞界面 */ });
|
|
7487
|
+
}, 400);
|
|
7488
|
+
};
|
|
7335
7489
|
|
|
7336
7490
|
var loadWf = function (wid) {
|
|
7337
7491
|
if (!wid) { setWf(null); setScan(null); setJsonText(""); return Promise.resolve(null); }
|
|
@@ -7394,6 +7548,7 @@ window.__ModuleLoader__.load({
|
|
|
7394
7548
|
size: wf ? wf.size : null,
|
|
7395
7549
|
sizeMode: wf ? wf.sizeMode : "manual",
|
|
7396
7550
|
randomSeed: wf ? wf.randomSeed : true,
|
|
7551
|
+
seed: wf && wf.seed !== undefined && wf.seed !== null && Number.isFinite(Number(wf.seed)) ? Math.round(Number(wf.seed)) : null,
|
|
7397
7552
|
pruneOutputs: wf ? wf.pruneOutputs : true,
|
|
7398
7553
|
onlyFirst: true,
|
|
7399
7554
|
};
|
|
@@ -7424,7 +7579,7 @@ window.__ModuleLoader__.load({
|
|
|
7424
7579
|
doSave(undefined, name);
|
|
7425
7580
|
};
|
|
7426
7581
|
var create = function () {
|
|
7427
|
-
setWf({ id: "", name: "新工作流", json: {}, mapping: {}, mappingSeeded: false, mappingVersion: 0, outputNodes: [], outputSelectionVersion: 2, size: null, sizeMode: "manual", randomSeed: true, pruneOutputs: true, onlyFirst: false, loraState: null });
|
|
7582
|
+
setWf({ id: "", name: "新工作流", json: {}, mapping: {}, mappingSeeded: false, mappingVersion: 0, outputNodes: [], outputSelectionVersion: 2, size: null, sizeMode: "manual", randomSeed: true, seed: null, lastSeed: null, pruneOutputs: true, onlyFirst: false, loraState: null });
|
|
7428
7583
|
setId("");
|
|
7429
7584
|
setJsonText("{}");
|
|
7430
7585
|
setScan(null);
|
|
@@ -7448,6 +7603,32 @@ window.__ModuleLoader__.load({
|
|
|
7448
7603
|
var patch = function (p) {
|
|
7449
7604
|
setWf(function (old) { return old ? Object.assign({}, old, p) : old; });
|
|
7450
7605
|
};
|
|
7606
|
+
// 固定种子:自动模式只读,显示上一轮实际用的种子;取消自动后可以手填,
|
|
7607
|
+
// 填的数字会随保存写进工作流,出图时覆盖每一处字面量 seed。
|
|
7608
|
+
var seedAuto = !wf || wf.randomSeed !== false;
|
|
7609
|
+
var seedDisplay = seedAuto
|
|
7610
|
+
? (wf && wf.lastSeed !== null && wf.lastSeed !== undefined ? String(wf.lastSeed) : "")
|
|
7611
|
+
: (wf && wf.seed !== null && wf.seed !== undefined ? String(wf.seed) : "");
|
|
7612
|
+
var editSeed = function (value) {
|
|
7613
|
+
setSeedDraft(value);
|
|
7614
|
+
var n = Number(value);
|
|
7615
|
+
if (String(value).trim() !== "" && isFinite(n) && n >= 0) patch({ seed: Math.round(n) });
|
|
7616
|
+
};
|
|
7617
|
+
var commitSeed = function (value) {
|
|
7618
|
+
setSeedDraft(null);
|
|
7619
|
+
var raw = String(value === undefined || value === null ? "" : value).trim();
|
|
7620
|
+
if (raw === "") { persistWf({ seed: null }); return; }
|
|
7621
|
+
var n = Number(raw);
|
|
7622
|
+
if (!isFinite(n)) { persistWf({ seed: null }); return; }
|
|
7623
|
+
persistWf({ seed: Math.max(0, Math.min(9007199254740991, Math.round(n))) });
|
|
7624
|
+
};
|
|
7625
|
+
var toggleRandomSeed = function (on) {
|
|
7626
|
+
if (on) { persistWf({ randomSeed: true }); return; }
|
|
7627
|
+
// 取消自动时顺手把「上一轮用的种子」带过来,省得再手敲一遍。
|
|
7628
|
+
var carry = wf.seed !== null && wf.seed !== undefined ? wf.seed
|
|
7629
|
+
: (wf.lastSeed !== null && wf.lastSeed !== undefined ? wf.lastSeed : null);
|
|
7630
|
+
persistWf({ randomSeed: false, seed: carry });
|
|
7631
|
+
};
|
|
7451
7632
|
var mapping = (wf && wf.mapping) || {};
|
|
7452
7633
|
var setMapping = function (nodeId, role) {
|
|
7453
7634
|
var next = Object.assign({}, mapping);
|
|
@@ -7756,11 +7937,31 @@ window.__ModuleLoader__.load({
|
|
|
7756
7937
|
h("div", { className: "ac-subtitle", style: { marginTop: 12 } }, "额外设置"),
|
|
7757
7938
|
h("div", { className: "ac-form" },
|
|
7758
7939
|
h("div", { className: "ac-row" },
|
|
7759
|
-
h(Switch, { checked: wf.randomSeed !== false, onChange: function (e) {
|
|
7940
|
+
h(Switch, { checked: wf.randomSeed !== false, onChange: function (e) { toggleRandomSeed(e.target.checked); } }, "每次换随机种子"),
|
|
7941
|
+
),
|
|
7942
|
+
h("div", { className: "ac-row" },
|
|
7943
|
+
h(Field, { label: "固定种子" }, h(TextInput, {
|
|
7944
|
+
value: seedDraft !== null ? seedDraft : seedDisplay,
|
|
7945
|
+
disabled: seedAuto,
|
|
7946
|
+
placeholder: seedAuto ? "自动(显示上一轮用的种子)" : "填一个整数,每轮都用它",
|
|
7947
|
+
title: seedAuto
|
|
7948
|
+
? "自动换种子时只读:显示上一轮实际用的种子;取消上面勾选即可编辑"
|
|
7949
|
+
: "取消自动后,这里填的数字会写进工作流,每轮都用它",
|
|
7950
|
+
onChange: function (e) { editSeed(e.target.value); },
|
|
7951
|
+
onBlur: function (e) { commitSeed(e.target.value); },
|
|
7952
|
+
onKeyDown: function (e) { if (e.key === "Enter") e.currentTarget.blur(); },
|
|
7953
|
+
})),
|
|
7954
|
+
h("span", { className: "ac-hint" }, seedAuto ? "自动:每轮随机" : "固定:每轮都用这个种子"),
|
|
7760
7955
|
),
|
|
7761
7956
|
h("div", { className: "ac-row" },
|
|
7762
7957
|
h(Switch, { checked: wf.pruneOutputs !== false, onChange: function (e) { patch({ pruneOutputs: e.target.checked }); } }, "提交前裁剪输出节点(只提交上面勾选的)"),
|
|
7763
7958
|
),
|
|
7959
|
+
h("div", { className: "ac-row" },
|
|
7960
|
+
h(Switch, { checked: wf.blackRetry !== false, onChange: function (e) { persistWf({ blackRetry: e.target.checked }); } }, "黑图自动替换"),
|
|
7961
|
+
h("span", { className: "ac-hint" }, wf.blackRetry !== false
|
|
7962
|
+
? "出图后检测到纯黑图就自动换种子重生成;进行中可在图片框菜单点「终止」"
|
|
7963
|
+
: "已关闭:纯黑图不再自动替换"),
|
|
7964
|
+
),
|
|
7764
7965
|
),
|
|
7765
7966
|
),
|
|
7766
7967
|
),
|
|
@@ -7783,6 +7984,9 @@ window.__ModuleLoader__.load({
|
|
|
7783
7984
|
var dropRef = useRef(null);
|
|
7784
7985
|
// 点 LoRA 名字就地改名:输入期间只改本地草稿,回车 / 失焦才写进 loraState。
|
|
7785
7986
|
var [nameEdit, setNameEdit] = useState(null);
|
|
7987
|
+
// 强度输入:输入期间只改文本框(允许「-」「-.5」这种中间态),失焦 / 回车才夹到 -5..5 写回。
|
|
7988
|
+
// 以前每按一次键就 Number(),"-" 会变成 NaN 被弹回去,负数根本打不进去。
|
|
7989
|
+
var [numText, setNumText] = useState({});
|
|
7786
7990
|
var nameEditRef = useRef(null);
|
|
7787
7991
|
var applyTimer = useRef(null);
|
|
7788
7992
|
// 所有临时覆盖串行发送:保存到工作流时先等已发出的覆盖落地,
|
|
@@ -7850,6 +8054,29 @@ window.__ModuleLoader__.load({
|
|
|
7850
8054
|
};
|
|
7851
8055
|
if (immediate) send(); else applyTimer.current = setTimeout(send, 260);
|
|
7852
8056
|
};
|
|
8057
|
+
var numKey = function (stackId, idx) { return stackId + ":" + idx; };
|
|
8058
|
+
// 强度:输入时先别夹、也别丢原文;失焦 / 回车才落到 -5..5。
|
|
8059
|
+
var editStrength = function (stackId, idx, raw) {
|
|
8060
|
+
setNumText(function (old) {
|
|
8061
|
+
var n = Object.assign({}, old);
|
|
8062
|
+
n[numKey(stackId, idx)] = String(raw);
|
|
8063
|
+
return n;
|
|
8064
|
+
});
|
|
8065
|
+
var v = Number(raw);
|
|
8066
|
+
if (String(raw).trim() !== "" && isFinite(v)) {
|
|
8067
|
+
updateEntry(stackId, idx, { strength: v, clipStrength: v });
|
|
8068
|
+
}
|
|
8069
|
+
};
|
|
8070
|
+
var commitStrength = function (stackId, idx, raw) {
|
|
8071
|
+
setNumText(function (old) {
|
|
8072
|
+
var n = Object.assign({}, old);
|
|
8073
|
+
delete n[numKey(stackId, idx)];
|
|
8074
|
+
return n;
|
|
8075
|
+
});
|
|
8076
|
+
if (String(raw === undefined || raw === null ? "" : raw).trim() === "") return;
|
|
8077
|
+
var v = Math.round(clamp(raw, -5, 5) * 100) / 100;
|
|
8078
|
+
updateEntry(stackId, idx, { strength: v, clipStrength: v });
|
|
8079
|
+
};
|
|
7853
8080
|
var updateEntry = function (stackId, idx, patch, immediate) {
|
|
7854
8081
|
if (!data) return;
|
|
7855
8082
|
var next = JSON.parse(JSON.stringify(data));
|
|
@@ -8106,9 +8333,13 @@ window.__ModuleLoader__.load({
|
|
|
8106
8333
|
onClick: function () { if (!taken) beginNameEdit(st.nodeId, i, e.label || e.name); },
|
|
8107
8334
|
}, e.label || e.name),
|
|
8108
8335
|
h("input", {
|
|
8109
|
-
type: "number", step: "0.05",
|
|
8110
|
-
|
|
8111
|
-
|
|
8336
|
+
type: "number", step: "0.05", min: "-5", max: "5",
|
|
8337
|
+
value: numText[numKey(st.nodeId, i)] !== undefined ? numText[numKey(st.nodeId, i)] : e.strength,
|
|
8338
|
+
disabled: taken,
|
|
8339
|
+
title: taken ? "连续图接管中:强度按连续图档位,这里不生效" : "强度,可用负数;范围 -5 ~ 5",
|
|
8340
|
+
onChange: function (ev) { editStrength(st.nodeId, i, ev.target.value); },
|
|
8341
|
+
onBlur: function (ev) { commitStrength(st.nodeId, i, ev.target.value); },
|
|
8342
|
+
onKeyDown: function (ev) { if (ev.key === "Enter") ev.currentTarget.blur(); },
|
|
8112
8343
|
}),
|
|
8113
8344
|
canSort ? h("button", {
|
|
8114
8345
|
className: "ac-btn sm", disabled: taken,
|
package/lib/comfy.js
CHANGED
|
@@ -631,7 +631,7 @@ function countOccurrences(hay, needle) {
|
|
|
631
631
|
*/
|
|
632
632
|
export function applyOverrides(
|
|
633
633
|
workflow,
|
|
634
|
-
{ positive = '', negative = '', mapping = {}, tokenEmpty = 'blank', randomSeed = true, loraState = null, size = null, sizeSource = '' } = {},
|
|
634
|
+
{ positive = '', negative = '', mapping = {}, tokenEmpty = 'blank', randomSeed = true, seed = null, loraState = null, size = null, sizeSource = '' } = {},
|
|
635
635
|
) {
|
|
636
636
|
let wf = structuredClone(workflow);
|
|
637
637
|
const report = {
|
|
@@ -978,6 +978,29 @@ export function applyOverrides(
|
|
|
978
978
|
});
|
|
979
979
|
}
|
|
980
980
|
}
|
|
981
|
+
} else if (seed !== null && seed !== undefined && Number.isFinite(Number(seed))) {
|
|
982
|
+
// 固定种子:把每一处字面量 seed 都写成用户填的这个值(工作台「固定种子」文本框)。
|
|
983
|
+
// 只动和上面同一类节点,连线出来的种子不碰。
|
|
984
|
+
const fixed = Math.round(Number(seed));
|
|
985
|
+
for (const [nodeId, node] of Object.entries(wf)) {
|
|
986
|
+
if (!node || typeof node !== 'object' || !node.inputs) continue;
|
|
987
|
+
if (!SEED_NODE_RE.test(String(node.class_type || ''))) continue;
|
|
988
|
+
for (const key of Object.keys(node.inputs)) {
|
|
989
|
+
if (key !== 'seed' && key !== 'noise_seed' && key !== 'rand_seed') continue;
|
|
990
|
+
const before = node.inputs[key];
|
|
991
|
+
if (typeof before !== 'number' || !Number.isFinite(before)) continue;
|
|
992
|
+
node.inputs[key] = fixed;
|
|
993
|
+
report.seeds.push({
|
|
994
|
+
nodeId,
|
|
995
|
+
classType: node.class_type,
|
|
996
|
+
title: node?._meta?.title || node?.title || '',
|
|
997
|
+
inputKey: key,
|
|
998
|
+
before,
|
|
999
|
+
after: fixed,
|
|
1000
|
+
fixed: true,
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
981
1004
|
}
|
|
982
1005
|
|
|
983
1006
|
/* ── 第 4 步:LoRA 开关 ──
|
package/lib/jobs.js
CHANGED
|
@@ -578,6 +578,7 @@ export function createJobsEngine({ ctx, sc, store, hub, getConfig, getActiveSess
|
|
|
578
578
|
mapping: wf.mapping || {},
|
|
579
579
|
tokenEmpty: 'blank',
|
|
580
580
|
randomSeed: job?.forceRandomSeed === true || wf.randomSeed !== false,
|
|
581
|
+
seed: wf.seed === null || wf.seed === undefined ? null : Number(wf.seed),
|
|
581
582
|
loraState: set.continuousShot
|
|
582
583
|
? withLoraStrength(wf, resolveLoraState(wf), set.continuousShot)
|
|
583
584
|
: resolveLoraState(wf),
|
|
@@ -743,6 +744,11 @@ export function createJobsEngine({ ctx, sc, store, hub, getConfig, getActiveSess
|
|
|
743
744
|
const wfN = prepared.workflow;
|
|
744
745
|
if (index === 0) job.overrideReport = prepared.report;
|
|
745
746
|
const usedSeeds = seedsOfWorkflow(wfN);
|
|
747
|
+
// 把这一轮实际用的种子记到工作流上:工作流页「固定种子」那一栏在自动模式下
|
|
748
|
+
// 显示的就是它,用户看到满意的一张可以直接取消自动、把这颗种子固定下来。
|
|
749
|
+
if (index === 0 && wf.id && usedSeeds.length) {
|
|
750
|
+
try { store.patchWorkflow(wf.id, { lastSeed: usedSeeds[0].value }); } catch { /* 记录失败不影响出图 */ }
|
|
751
|
+
}
|
|
746
752
|
if (runSets.length > 1) log('info', `第 ${index + 1}/${runSets.length} 套:排队提交`, { jobId: job.id });
|
|
747
753
|
const { promptId } = await queuePrompt(cfg.comfy.baseUrl, wfN, CLIENT_ID);
|
|
748
754
|
job.comfyPromptId = promptId;
|
package/lib/meta.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export const PACKAGE_NAME = 'dsh-comfyui-rp';
|
|
8
8
|
export const PLUGIN_NAME = 'dsh-comfyui-rp';
|
|
9
|
-
export const VERSION = '0.2.
|
|
9
|
+
export const VERSION = '0.2.3';
|
|
10
10
|
|
|
11
11
|
/** 对外路由前缀。不能带尾斜杠(dsh-host-webserver 的 prefix 匹配规则)。 */
|
|
12
12
|
export const ROUTE_PATH = '/__airp_comfy';
|
package/lib/store.js
CHANGED
|
@@ -483,6 +483,9 @@ export function createStore(dataDir) {
|
|
|
483
483
|
size: null,
|
|
484
484
|
sizeMode: 'manual',
|
|
485
485
|
randomSeed: true,
|
|
486
|
+
seed: null,
|
|
487
|
+
lastSeed: null,
|
|
488
|
+
blackRetry: true,
|
|
486
489
|
pruneOutputs: true,
|
|
487
490
|
onlyFirst: false,
|
|
488
491
|
loraState: null,
|
|
@@ -553,6 +556,9 @@ export function createStore(dataDir) {
|
|
|
553
556
|
size: null,
|
|
554
557
|
sizeMode: 'manual',
|
|
555
558
|
randomSeed: true,
|
|
559
|
+
seed: null,
|
|
560
|
+
lastSeed: null,
|
|
561
|
+
blackRetry: true,
|
|
556
562
|
pruneOutputs: true,
|
|
557
563
|
onlyFirst: false,
|
|
558
564
|
loraState: null,
|
|
@@ -577,7 +583,7 @@ export function createStore(dataDir) {
|
|
|
577
583
|
const w = api.getWorkflow(id);
|
|
578
584
|
if (!w) return null;
|
|
579
585
|
const allow = ['name', 'json', 'mapping', 'mappingSeeded', 'mappingVersion', 'outputNodes', 'outputSelectionVersion', 'tokenEmpty', 'size', 'sizeMode',
|
|
580
|
-
'randomSeed', 'pruneOutputs', 'onlyFirst', 'loraState'];
|
|
586
|
+
'randomSeed', 'seed', 'lastSeed', 'pruneOutputs', 'onlyFirst', 'loraState', 'blackRetry'];
|
|
581
587
|
for (const k of allow) {
|
|
582
588
|
if (patch && Object.prototype.hasOwnProperty.call(patch, k)) w[k] = k === 'tokenEmpty' ? 'blank' : patch[k];
|
|
583
589
|
}
|
package/package.json
CHANGED