dsh-comfyui 0.3.0-beta.2 → 0.3.0-beta.4
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.en.md +28 -12
- package/README.md +29 -13
- package/client/client.js +373 -60
- package/client/client.js.map +1 -1
- package/lib/comfyui.d.ts +33 -1
- package/lib/comfyui.js +38 -4
- package/lib/config.d.ts +12 -0
- package/lib/config.js +5 -0
- package/lib/index.js +7 -4
- package/lib/params.d.ts +29 -1
- package/lib/params.js +133 -10
- package/lib/proxy.js +16 -3
- package/lib/routes.js +277 -44
- package/lib/skill.js +5 -5
- package/lib/store.d.ts +15 -3
- package/lib/store.js +48 -12
- package/lib/tools.d.ts +8 -13
- package/lib/tools.js +23 -2
- package/package.json +5 -1
package/client/client.js
CHANGED
|
@@ -73,9 +73,16 @@ window.__ModuleLoader__.load({
|
|
|
73
73
|
wfUploaded: "已上传",
|
|
74
74
|
wfCopyName: "复制文件名",
|
|
75
75
|
wfLoadArea: "加载区",
|
|
76
|
-
wfLoadHint: "
|
|
77
|
-
wfLoadEmpty: "
|
|
76
|
+
wfLoadHint: "点击放入素材(加载区的素材按顺序填入工作流未指定的加载参数)",
|
|
77
|
+
wfLoadEmpty: "加载区为空,点击下方“添加加载区”放入素材",
|
|
78
78
|
wfLoadPicked: "已加载",
|
|
79
|
+
wfLoadAddSlot: "+ 添加加载区",
|
|
80
|
+
wfLoadAddMedia: "添加素材",
|
|
81
|
+
wfLoadRemoveSlot: "删除加载区",
|
|
82
|
+
wfLoadNone: "空",
|
|
83
|
+
wfLoadNoneHint: "清空该加载位(保留加载位,不放素材)",
|
|
84
|
+
wfLoadSlotLabel: "加载位",
|
|
85
|
+
wfLoadSummary: "加载位 {slots} 个 · 已放入 {loaded} 个素材,未指定的加载参数按顺序取用",
|
|
79
86
|
wfTabAll: "全部",
|
|
80
87
|
wfTabImported: "已导入",
|
|
81
88
|
wfTabGenerated: "已生成",
|
|
@@ -147,6 +154,14 @@ window.__ModuleLoader__.load({
|
|
|
147
154
|
assetBack: "返回",
|
|
148
155
|
assetEmpty: "还没有生成记录。运行工作流后,结果会出现在这里。",
|
|
149
156
|
assetGone: "源文件已被 ComfyUI 清理",
|
|
157
|
+
assetDelete: "删除资产",
|
|
158
|
+
assetDeleteTitle: "删除该资产?",
|
|
159
|
+
assetDeleteBody: "将从资产库移除这条记录,并删除 ComfyUI 输出目录里的 {count} 个文件。此操作不可恢复。",
|
|
160
|
+
assetDeleteNoDir: "未能定位 ComfyUI 输出目录,只会移除资产记录,磁盘文件保留(可在 cordis.yml 的 outputDir 里指定输出目录)。",
|
|
161
|
+
assetDeleteDone: "已删除记录,磁盘文件删除 {deleted} 个",
|
|
162
|
+
assetDeleteFailed: "删除失败:{message}",
|
|
163
|
+
confirmDelete: "确定删除",
|
|
164
|
+
cancel: "取消",
|
|
150
165
|
queueEmpty: "当前没有任务",
|
|
151
166
|
queueError: "无法读取队列(ComfyUI 未运行?)",
|
|
152
167
|
jobFilterLabel: "历史",
|
|
@@ -234,9 +249,16 @@ window.__ModuleLoader__.load({
|
|
|
234
249
|
wfUploaded: "Uploaded",
|
|
235
250
|
wfCopyName: "Copy file name",
|
|
236
251
|
wfLoadArea: "Load area",
|
|
237
|
-
wfLoadHint: "Click to
|
|
238
|
-
wfLoadEmpty: "
|
|
252
|
+
wfLoadHint: "Click to put media in this slot (slots fill the workflow unset loader parameters in order)",
|
|
253
|
+
wfLoadEmpty: "Load area is empty — use \"Add slot\" below to put media in it",
|
|
239
254
|
wfLoadPicked: "Loaded",
|
|
255
|
+
wfLoadAddSlot: "+ Add slot",
|
|
256
|
+
wfLoadAddMedia: "Add media",
|
|
257
|
+
wfLoadRemoveSlot: "Delete slot",
|
|
258
|
+
wfLoadNone: "None",
|
|
259
|
+
wfLoadNoneHint: "Empty this slot (the slot stays, with no media)",
|
|
260
|
+
wfLoadSlotLabel: "Slot",
|
|
261
|
+
wfLoadSummary: "{slots} slot(s) · {loaded} loaded; unset loader parameters take them in order",
|
|
240
262
|
wfTabAll: "All",
|
|
241
263
|
wfTabImported: "Imported",
|
|
242
264
|
wfTabGenerated: "Generated",
|
|
@@ -308,6 +330,14 @@ window.__ModuleLoader__.load({
|
|
|
308
330
|
assetBack: "Back",
|
|
309
331
|
assetEmpty: "Nothing generated yet. Results appear here after workflows run.",
|
|
310
332
|
assetGone: "Source file evicted by ComfyUI",
|
|
333
|
+
assetDelete: "Delete asset",
|
|
334
|
+
assetDeleteTitle: "Delete this asset?",
|
|
335
|
+
assetDeleteBody: "Removes the record from the asset library and deletes {count} file(s) from the ComfyUI output directory. This cannot be undone.",
|
|
336
|
+
assetDeleteNoDir: "The ComfyUI output directory could not be located, so only the record is removed and the files stay on disk (set outputDir in cordis.yml to enable file deletion).",
|
|
337
|
+
assetDeleteDone: "Record removed, {deleted} file(s) deleted",
|
|
338
|
+
assetDeleteFailed: "Delete failed: {message}",
|
|
339
|
+
confirmDelete: "Delete",
|
|
340
|
+
cancel: "Cancel",
|
|
311
341
|
queueEmpty: "No tasks right now",
|
|
312
342
|
queueError: "Cannot read the queue (ComfyUI not running?)",
|
|
313
343
|
jobFilterLabel: "History",
|
|
@@ -947,6 +977,33 @@ window.__ModuleLoader__.load({
|
|
|
947
977
|
return `/comfyui/media?file=${encodeURIComponent(preview.filename)}&subfolder=${encodeURIComponent(preview.subfolder ?? "")}&type=${encodeURIComponent(preview.type ?? "output")}`;
|
|
948
978
|
}
|
|
949
979
|
/** Opens a generated image in a large overlay with prev/next navigation. */
|
|
980
|
+
/** Whether a node input is fed by a link ([nodeId, slot]) rather than a widget value. */
|
|
981
|
+
function isLinkValue(value) {
|
|
982
|
+
return Array.isArray(value) && value.length === 2 && typeof value[0] === "string" && typeof value[1] === "number";
|
|
983
|
+
}
|
|
984
|
+
/** Short description of a node input's current value, for the advanced
|
|
985
|
+
* picker's dropdown. */
|
|
986
|
+
function inputValueKind(value) {
|
|
987
|
+
if (value === null) return "null";
|
|
988
|
+
if (Array.isArray(value)) return "array";
|
|
989
|
+
if (typeof value === "object") return "object";
|
|
990
|
+
return typeof value;
|
|
991
|
+
}
|
|
992
|
+
/** Read a parameter default as a boolean. Defaults saved before the editor
|
|
993
|
+
* had a checkbox are strings ("true"/"false"), so they are coerced here rather
|
|
994
|
+
* than silently reading as `true` for every non-empty string. */
|
|
995
|
+
function asBool(value) {
|
|
996
|
+
if (typeof value === "boolean") return value;
|
|
997
|
+
if (typeof value === "number") return value !== 0;
|
|
998
|
+
return /^(true|1|yes|on)$/i.test(value.trim());
|
|
999
|
+
}
|
|
1000
|
+
/** Tooltip for a number field: the declared type and bounds, when known. */
|
|
1001
|
+
function numberRangeHint(param) {
|
|
1002
|
+
const parts = [param.numberKind ?? "number"];
|
|
1003
|
+
if (param.min !== void 0 || param.max !== void 0) parts.push(`${param.min ?? "-∞"} ~ ${param.max ?? "∞"}`);
|
|
1004
|
+
if (param.step !== void 0) parts.push(`step ${param.step}`);
|
|
1005
|
+
return parts.join(" · ");
|
|
1006
|
+
}
|
|
950
1007
|
function newParamId() {
|
|
951
1008
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
952
1009
|
return `p${Date.now().toString(36)}${Math.floor(Math.random() * 1e6).toString(36)}`;
|
|
@@ -1263,6 +1320,7 @@ window.__ModuleLoader__.load({
|
|
|
1263
1320
|
const [recognizing, setRecognizing] = (0, react.useState)(false);
|
|
1264
1321
|
const [recognizeError, setRecognizeError] = (0, react.useState)(null);
|
|
1265
1322
|
const [uploading, setUploading] = (0, react.useState)({});
|
|
1323
|
+
const [numDraft, setNumDraft] = (0, react.useState)({});
|
|
1266
1324
|
const [hovered, setHovered] = (0, react.useState)(null);
|
|
1267
1325
|
const hoverRef = (0, react.useRef)(null);
|
|
1268
1326
|
const latestRef = (0, react.useRef)({
|
|
@@ -1335,7 +1393,7 @@ window.__ModuleLoader__.load({
|
|
|
1335
1393
|
};
|
|
1336
1394
|
const nodeEntries = Object.entries(workflow).map(([id, raw]) => [id, raw]);
|
|
1337
1395
|
const advNodeInfo = nodeEntries.find(([id]) => id === advNode)?.[1];
|
|
1338
|
-
const advInputs = advNodeInfo !== void 0 && advNodeInfo.inputs !== void 0 ? Object.entries(advNodeInfo.inputs).filter(([
|
|
1396
|
+
const advInputs = advNodeInfo !== void 0 && advNodeInfo.inputs !== void 0 ? Object.entries(advNodeInfo.inputs).filter(([, value]) => !isLinkValue(value)) : [];
|
|
1339
1397
|
const selectedInput = advInputs.find(([key]) => key === advInput);
|
|
1340
1398
|
/** Server-side auto-detection: prompt/size/steps/seed + dropdown options (object_info). */
|
|
1341
1399
|
const recognize = async () => {
|
|
@@ -1397,6 +1455,7 @@ window.__ModuleLoader__.load({
|
|
|
1397
1455
|
}
|
|
1398
1456
|
let options;
|
|
1399
1457
|
let upload;
|
|
1458
|
+
let numberSpec;
|
|
1400
1459
|
if (typeof advNodeInfo.class_type === "string") try {
|
|
1401
1460
|
const result = await postJson("/comfyui/workflows/input-options", {
|
|
1402
1461
|
classType: advNodeInfo.class_type,
|
|
@@ -1404,6 +1463,7 @@ window.__ModuleLoader__.load({
|
|
|
1404
1463
|
});
|
|
1405
1464
|
if (result.ok === true && Array.isArray(result.options) && result.options.length > 0) options = result.options;
|
|
1406
1465
|
upload = result.upload;
|
|
1466
|
+
numberSpec = result.number;
|
|
1407
1467
|
} catch {}
|
|
1408
1468
|
onChange([...params, {
|
|
1409
1469
|
id: newParamId(),
|
|
@@ -1413,6 +1473,10 @@ window.__ModuleLoader__.load({
|
|
|
1413
1473
|
nodeId: advNode,
|
|
1414
1474
|
inputKey: advInput,
|
|
1415
1475
|
default: typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : "",
|
|
1476
|
+
numberKind: type === "number" ? numberSpec?.kind : void 0,
|
|
1477
|
+
min: type === "number" ? numberSpec?.min : void 0,
|
|
1478
|
+
max: type === "number" ? numberSpec?.max : void 0,
|
|
1479
|
+
step: type === "number" ? numberSpec?.step : void 0,
|
|
1416
1480
|
options,
|
|
1417
1481
|
upload
|
|
1418
1482
|
}]);
|
|
@@ -1544,15 +1608,43 @@ window.__ModuleLoader__.load({
|
|
|
1544
1608
|
value: String(param.default),
|
|
1545
1609
|
placeholder: t("wfParamDefault"),
|
|
1546
1610
|
onChange: (event) => updateDefault(index, event.target.value)
|
|
1547
|
-
}) : (0, react.createElement)("input", {
|
|
1611
|
+
}) : param.type === "boolean" ? (0, react.createElement)("label", { className: "dsc-param-bool dsc-param-default" }, (0, react.createElement)("input", {
|
|
1612
|
+
type: "checkbox",
|
|
1613
|
+
checked: asBool(param.default),
|
|
1614
|
+
onChange: (event) => updateDefault(index, event.target.checked)
|
|
1615
|
+
}), (0, react.createElement)("span", null, asBool(param.default) ? "true" : "false")) : param.type === "number" ? (0, react.createElement)("input", {
|
|
1548
1616
|
className: "dsc-input dsc-input--sm dsc-param-default",
|
|
1549
|
-
|
|
1617
|
+
inputMode: param.numberKind === "int" ? "numeric" : "decimal",
|
|
1618
|
+
value: numDraft[param.id] ?? String(param.default),
|
|
1550
1619
|
placeholder: t("wfParamDefault"),
|
|
1620
|
+
title: numberRangeHint(param),
|
|
1551
1621
|
onChange: (event) => {
|
|
1552
1622
|
const raw = event.target.value;
|
|
1553
|
-
|
|
1623
|
+
setNumDraft((prev) => ({
|
|
1624
|
+
...prev,
|
|
1625
|
+
[param.id]: raw
|
|
1626
|
+
}));
|
|
1627
|
+
const parsed = Number(raw);
|
|
1628
|
+
if (raw.trim() !== "" && Number.isFinite(parsed)) updateDefault(index, parsed);
|
|
1629
|
+
},
|
|
1630
|
+
onBlur: () => {
|
|
1631
|
+
const raw = numDraft[param.id];
|
|
1632
|
+
setNumDraft((prev) => {
|
|
1633
|
+
const next = { ...prev };
|
|
1634
|
+
delete next[param.id];
|
|
1635
|
+
return next;
|
|
1636
|
+
});
|
|
1637
|
+
if (raw === void 0) return;
|
|
1638
|
+
const parsed = Number(raw);
|
|
1639
|
+
const value = raw.trim() === "" || !Number.isFinite(parsed) ? 0 : parsed;
|
|
1640
|
+
updateDefault(index, param.numberKind === "int" ? Math.round(value) : value);
|
|
1554
1641
|
}
|
|
1555
|
-
})
|
|
1642
|
+
}) : (0, react.createElement)("input", {
|
|
1643
|
+
className: "dsc-input dsc-input--sm dsc-param-default",
|
|
1644
|
+
value: String(param.default),
|
|
1645
|
+
placeholder: t("wfParamDefault"),
|
|
1646
|
+
onChange: (event) => updateDefault(index, event.target.value)
|
|
1647
|
+
}), (0, react.createElement)("div", { className: "dsc-param-meta" }, (0, react.createElement)("span", { className: "dsc-meta" }, `${param.type}${param.type === "number" ? `/${param.numberKind ?? "?"}` : ""} · #${param.nodeId}.${param.inputKey}`), param.type === "number" ? (0, react.createElement)("label", { className: "dsc-param-random" }, (0, react.createElement)("input", {
|
|
1556
1648
|
type: "checkbox",
|
|
1557
1649
|
checked: param.random === true,
|
|
1558
1650
|
onChange: (event) => update(index, { random: event.target.checked })
|
|
@@ -1582,10 +1674,10 @@ window.__ModuleLoader__.load({
|
|
|
1582
1674
|
className: "dsc-input dsc-input--sm",
|
|
1583
1675
|
value: advInput,
|
|
1584
1676
|
onChange: (event) => setAdvInput(event.target.value)
|
|
1585
|
-
}, (0, react.createElement)("option", { value: "" }, "—"), advInputs.
|
|
1677
|
+
}, (0, react.createElement)("option", { value: "" }, "—"), advInputs.map(([key, value]) => (0, react.createElement)("option", {
|
|
1586
1678
|
key,
|
|
1587
1679
|
value: key
|
|
1588
|
-
}, `${key} (${value
|
|
1680
|
+
}, `${key} (${inputValueKind(value)})`))) : null, (0, react.createElement)("input", {
|
|
1589
1681
|
className: "dsc-input dsc-input--sm",
|
|
1590
1682
|
value: advLabel,
|
|
1591
1683
|
placeholder: t("wfParamCustomLabel"),
|
|
@@ -1747,26 +1839,52 @@ window.__ModuleLoader__.load({
|
|
|
1747
1839
|
return (0, react.createElement)("div", { className: "dsc-err" }, `${t("error")}: ${message}`);
|
|
1748
1840
|
}
|
|
1749
1841
|
/** Load area at the bottom of the workflow library, modeled on the ComfyUI
|
|
1750
|
-
* LoadImage node
|
|
1751
|
-
*
|
|
1752
|
-
*
|
|
1753
|
-
*
|
|
1842
|
+
* LoadImage node — a list of **slots** rather than a single image.
|
|
1843
|
+
*
|
|
1844
|
+
* A lone slot stretches across the panel; from two slots on they share a
|
|
1845
|
+
* fixed width and wrap into rows. A slot is either filled with a file or
|
|
1846
|
+
* empty (added but not yet picked, or emptied through the picker's "空"
|
|
1847
|
+
* tile); the whole card is the pick button, and hovering reveals an × in the
|
|
1848
|
+
* corner that removes the slot. Filled slots feed the workflow's loader
|
|
1849
|
+
* parameters in order, so a run that takes two reference images picks up
|
|
1850
|
+
* slots 1 and 2 without the agent naming files. */
|
|
1754
1851
|
function LoadArea(props) {
|
|
1755
|
-
const [
|
|
1852
|
+
const [slots, setSlots] = (0, react.useState)([]);
|
|
1756
1853
|
const [files, setFiles] = (0, react.useState)([]);
|
|
1757
|
-
|
|
1854
|
+
/** Slot index the picker is currently filling; null = picker closed. */
|
|
1855
|
+
const [pickerFor, setPickerFor] = (0, react.useState)(null);
|
|
1758
1856
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
1759
1857
|
const [error, setError] = (0, react.useState)(null);
|
|
1760
1858
|
const refresh = (0, react.useCallback)(async () => {
|
|
1761
1859
|
const data = await fetch("/comfyui/loadarea").then((response) => response.json()).catch(() => void 0);
|
|
1762
1860
|
if (data?.ok === true) {
|
|
1763
|
-
|
|
1861
|
+
setSlots(data.slots ?? (data.current !== null && data.current !== void 0 ? [data.current] : []));
|
|
1764
1862
|
setFiles(data.files ?? []);
|
|
1765
1863
|
}
|
|
1766
1864
|
}, []);
|
|
1767
1865
|
(0, react.useEffect)(() => {
|
|
1768
1866
|
refresh();
|
|
1769
1867
|
}, [refresh]);
|
|
1868
|
+
const post = async (body) => {
|
|
1869
|
+
setBusy(true);
|
|
1870
|
+
setError(null);
|
|
1871
|
+
try {
|
|
1872
|
+
const response = await fetch("/comfyui/current-image", {
|
|
1873
|
+
method: "POST",
|
|
1874
|
+
headers: { "Content-Type": "application/json" },
|
|
1875
|
+
body: JSON.stringify(body)
|
|
1876
|
+
});
|
|
1877
|
+
const data = await response.json().catch(() => ({}));
|
|
1878
|
+
if (data.ok !== true) throw new Error(data.error ?? `HTTP ${response.status}`);
|
|
1879
|
+
await refresh();
|
|
1880
|
+
return true;
|
|
1881
|
+
} catch (cause) {
|
|
1882
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
1883
|
+
return false;
|
|
1884
|
+
} finally {
|
|
1885
|
+
setBusy(false);
|
|
1886
|
+
}
|
|
1887
|
+
};
|
|
1770
1888
|
const upload = async (file) => {
|
|
1771
1889
|
setBusy(true);
|
|
1772
1890
|
setError(null);
|
|
@@ -1784,38 +1902,92 @@ window.__ModuleLoader__.load({
|
|
|
1784
1902
|
}
|
|
1785
1903
|
};
|
|
1786
1904
|
const pick = async (file) => {
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
}).catch(() => void 0);
|
|
1796
|
-
setCurrent(file);
|
|
1797
|
-
setPickerOpen(false);
|
|
1798
|
-
props.onNotice(`${props.t("wfLoadPicked")}:${file.name}`);
|
|
1905
|
+
const index = pickerFor;
|
|
1906
|
+
setPickerFor(null);
|
|
1907
|
+
if (await post({
|
|
1908
|
+
name: file.name,
|
|
1909
|
+
kind: file.kind,
|
|
1910
|
+
source: file.source,
|
|
1911
|
+
...index !== null ? { index } : {}
|
|
1912
|
+
})) props.onNotice(`${props.t("wfLoadPicked")}:${file.name}`);
|
|
1799
1913
|
};
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
})
|
|
1914
|
+
/** "+ 添加加载区": opens a slot and goes straight to the picker for it, so
|
|
1915
|
+
* adding material is one gesture; closing the picker leaves the empty slot. */
|
|
1916
|
+
const addSlot = async () => {
|
|
1917
|
+
const index = slots.length;
|
|
1918
|
+
if (await post({ action: "addSlot" })) setPickerFor(index);
|
|
1919
|
+
};
|
|
1920
|
+
const loaded = slots.filter((slot) => slot !== null).length;
|
|
1921
|
+
return (0, react.createElement)("div", { className: "dsc-loadarea" }, (0, react.createElement)("div", { className: "dsc-loadarea-head" }, (0, react.createElement)("span", { className: "dsc-loadarea-title" }, props.t("wfLoadArea")), (0, react.createElement)("span", { className: "dsc-meta" }, slots.length === 0 ? props.t("wfLoadEmpty") : props.t("wfLoadSummary", {
|
|
1922
|
+
slots: slots.length,
|
|
1923
|
+
loaded
|
|
1924
|
+
}))), (0, react.createElement)("div", { className: "dsc-loadslots" }, slots.map((slot, index) => (0, react.createElement)(LoadSlot, {
|
|
1925
|
+
key: index,
|
|
1926
|
+
t: props.t,
|
|
1927
|
+
slot,
|
|
1928
|
+
index,
|
|
1929
|
+
busy,
|
|
1930
|
+
wide: slots.length === 1,
|
|
1931
|
+
onPick: () => setPickerFor(index),
|
|
1932
|
+
onRemove: () => {
|
|
1933
|
+
post({
|
|
1934
|
+
action: "removeSlot",
|
|
1935
|
+
index
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
}))), (0, react.createElement)("div", { className: "dsc-toolbar" }, (0, react.createElement)("button", {
|
|
1939
|
+
className: "dsc-btn",
|
|
1940
|
+
disabled: busy,
|
|
1941
|
+
onClick: () => {
|
|
1942
|
+
addSlot();
|
|
1943
|
+
}
|
|
1944
|
+
}, props.t("wfLoadAddSlot"))), pickerFor !== null ? (0, react.createElement)(LoadPicker, {
|
|
1811
1945
|
t: props.t,
|
|
1812
1946
|
files,
|
|
1813
|
-
current:
|
|
1947
|
+
current: (pickerFor < slots.length ? slots[pickerFor]?.name : void 0) ?? null,
|
|
1814
1948
|
onUpload: upload,
|
|
1815
1949
|
onPick: pick,
|
|
1816
|
-
|
|
1950
|
+
onClear: () => {
|
|
1951
|
+
const index = pickerFor;
|
|
1952
|
+
setPickerFor(null);
|
|
1953
|
+
post({
|
|
1954
|
+
action: "clear",
|
|
1955
|
+
index
|
|
1956
|
+
});
|
|
1957
|
+
},
|
|
1958
|
+
onClose: () => setPickerFor(null)
|
|
1817
1959
|
}) : null, error !== null ? (0, react.createElement)("div", { className: "dsc-hint dsc-hint--error" }, error) : null);
|
|
1818
1960
|
}
|
|
1961
|
+
/** One load-area slot card. The card is the pick button (empty slots show
|
|
1962
|
+
* "加载位 N / 添加素材"); the × in the corner, revealed on hover, removes the
|
|
1963
|
+
* slot. Emptying a slot without removing it is done from the picker's "空"
|
|
1964
|
+
* tile, so the card carries no button row of its own. */
|
|
1965
|
+
function LoadSlot(props) {
|
|
1966
|
+
const { t, slot, index } = props;
|
|
1967
|
+
const size = slot?.width !== void 0 && slot.height !== void 0 ? `(${slot.width}×${slot.height})` : "";
|
|
1968
|
+
return (0, react.createElement)("div", { className: props.wide ? "dsc-loadslot dsc-loadslot--wide" : "dsc-loadslot" }, (0, react.createElement)("button", {
|
|
1969
|
+
className: "dsc-loadslot-pick",
|
|
1970
|
+
onClick: props.onPick,
|
|
1971
|
+
disabled: props.busy,
|
|
1972
|
+
title: slot !== null ? slot.name : t("wfLoadHint")
|
|
1973
|
+
}, slot === null ? (0, react.createElement)("span", { className: "dsc-loadslot-empty" }, (0, react.createElement)("span", { className: "dsc-loadslot-index" }, `${t("wfLoadSlotLabel")} ${index + 1}`), (0, react.createElement)("span", { className: "dsc-loadslot-add" }, t("wfLoadAddMedia"))) : slot.kind === "video" ? (0, react.createElement)("video", {
|
|
1974
|
+
className: "dsc-loadslot-media",
|
|
1975
|
+
src: slot.url,
|
|
1976
|
+
preload: "metadata",
|
|
1977
|
+
muted: true
|
|
1978
|
+
}) : slot.kind === "audio" ? (0, react.createElement)("span", { className: "dsc-loadslot-empty" }, "♪") : (0, react.createElement)("img", {
|
|
1979
|
+
className: "dsc-loadslot-media",
|
|
1980
|
+
src: slot.url,
|
|
1981
|
+
alt: "",
|
|
1982
|
+
loading: "lazy"
|
|
1983
|
+
}), slot !== null ? (0, react.createElement)("span", { className: "dsc-loadslot-name" }, `${slot.name}${size}`) : null), (0, react.createElement)("button", {
|
|
1984
|
+
className: "dsc-loadslot-x",
|
|
1985
|
+
disabled: props.busy,
|
|
1986
|
+
title: t("wfLoadRemoveSlot"),
|
|
1987
|
+
"aria-label": t("wfLoadRemoveSlot"),
|
|
1988
|
+
onClick: props.onRemove
|
|
1989
|
+
}, "×"));
|
|
1990
|
+
}
|
|
1819
1991
|
/** The load-area picker dialog: tab bar (全部/已导入/已生成) with a
|
|
1820
1992
|
* paste/click upload zone on the right, and a masonry image grid below. */
|
|
1821
1993
|
function LoadPicker(props) {
|
|
@@ -1947,15 +2119,35 @@ window.__ModuleLoader__.load({
|
|
|
1947
2119
|
}, typeOptions.map((option) => (0, react.createElement)("option", {
|
|
1948
2120
|
key: option.id,
|
|
1949
2121
|
value: option.id
|
|
1950
|
-
}, option.label)))), filtered.length === 0 ? (0, react.createElement)("div", { className: "dsc-picker-empty" }, props.t("wfLoadNoFiles")) : (0, react.createElement)("div", { className: "dsc-picker-grid" }, columns.map((column, columnIndex) => (0, react.createElement)("div", {
|
|
2122
|
+
}, option.label)))), filtered.length === 0 && props.onClear === void 0 ? (0, react.createElement)("div", { className: "dsc-picker-empty" }, props.t("wfLoadNoFiles")) : (0, react.createElement)("div", { className: "dsc-picker-grid" }, columns.map((column, columnIndex) => (0, react.createElement)("div", {
|
|
1951
2123
|
key: columnIndex,
|
|
1952
2124
|
className: "dsc-picker-col"
|
|
1953
|
-
},
|
|
2125
|
+
}, columnIndex === 0 && props.onClear !== void 0 ? (0, react.createElement)("div", {
|
|
2126
|
+
className: `dsc-picker-card dsc-picker-card--none${props.current === null ? " dsc-picker-card--active" : ""}`,
|
|
2127
|
+
role: "button",
|
|
2128
|
+
tabIndex: 0,
|
|
2129
|
+
onClick: () => props.onClear?.(),
|
|
2130
|
+
onKeyDown: (event) => {
|
|
2131
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
2132
|
+
event.preventDefault();
|
|
2133
|
+
props.onClear?.();
|
|
2134
|
+
}
|
|
2135
|
+
},
|
|
2136
|
+
title: props.t("wfLoadNoneHint")
|
|
2137
|
+
}, (0, react.createElement)("span", { className: "dsc-picker-thumb dsc-picker-thumb--media" }, "∅"), (0, react.createElement)("span", { className: "dsc-picker-name" }, props.t("wfLoadNone"))) : null, column.map((file) => (0, react.createElement)("div", {
|
|
1954
2138
|
key: `${file.source}:${file.name}`,
|
|
1955
2139
|
className: `dsc-picker-card${file.name === props.current ? " dsc-picker-card--active" : ""}`,
|
|
2140
|
+
role: "button",
|
|
2141
|
+
tabIndex: 0,
|
|
1956
2142
|
onClick: () => {
|
|
1957
2143
|
props.onPick(file);
|
|
1958
2144
|
},
|
|
2145
|
+
onKeyDown: (event) => {
|
|
2146
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
2147
|
+
event.preventDefault();
|
|
2148
|
+
props.onPick(file);
|
|
2149
|
+
}
|
|
2150
|
+
},
|
|
1959
2151
|
title: file.name,
|
|
1960
2152
|
ref: (el) => {
|
|
1961
2153
|
if (el !== null) cardRefs.current.set(`${file.source}:${file.name}`, el);
|
|
@@ -1966,7 +2158,19 @@ window.__ModuleLoader__.load({
|
|
|
1966
2158
|
src: file.url,
|
|
1967
2159
|
alt: "",
|
|
1968
2160
|
loading: "lazy"
|
|
1969
|
-
}) :
|
|
2161
|
+
}) : file.kind === "video" ? (0, react.createElement)("video", {
|
|
2162
|
+
className: "dsc-picker-thumb dsc-picker-player",
|
|
2163
|
+
src: file.url,
|
|
2164
|
+
controls: true,
|
|
2165
|
+
preload: "metadata",
|
|
2166
|
+
onClick: (event) => event.stopPropagation()
|
|
2167
|
+
}) : (0, react.createElement)("audio", {
|
|
2168
|
+
className: "dsc-picker-player dsc-picker-player--audio",
|
|
2169
|
+
src: file.url,
|
|
2170
|
+
controls: true,
|
|
2171
|
+
preload: "metadata",
|
|
2172
|
+
onClick: (event) => event.stopPropagation()
|
|
2173
|
+
}), (0, react.createElement)("span", { className: "dsc-picker-name" }, file.name), file.width !== void 0 && file.height !== void 0 ? (0, react.createElement)("span", { className: "dsc-meta" }, `${file.width}×${file.height}`) : null))))), uploadFlash !== null ? (0, react.createElement)("div", { className: "dsc-picker-toast" }, uploadFlash) : null));
|
|
1970
2174
|
}
|
|
1971
2175
|
/** Tag editor: preset classification chips (图生图 etc.) toggle on/off, plus
|
|
1972
2176
|
* free-form custom tags added with Enter and removed by clicking. */
|
|
@@ -2467,6 +2671,10 @@ window.__ModuleLoader__.load({
|
|
|
2467
2671
|
const [assets, setAssets] = (0, react.useState)(null);
|
|
2468
2672
|
const [error, setError] = (0, react.useState)(null);
|
|
2469
2673
|
const [filter, setFilter] = (0, react.useState)(() => panelStore.getAssetFilter());
|
|
2674
|
+
/** Asset awaiting the delete confirmation, if any. */
|
|
2675
|
+
const [pendingDelete, setPendingDelete] = (0, react.useState)(null);
|
|
2676
|
+
const [deleting, setDeleting] = (0, react.useState)(false);
|
|
2677
|
+
const [notice, setNotice] = (0, react.useState)(null);
|
|
2470
2678
|
const load = async () => {
|
|
2471
2679
|
try {
|
|
2472
2680
|
const data = await getJson("/comfyui/assets");
|
|
@@ -2487,7 +2695,24 @@ window.__ModuleLoader__.load({
|
|
|
2487
2695
|
cancelled = true;
|
|
2488
2696
|
};
|
|
2489
2697
|
}, []);
|
|
2490
|
-
|
|
2698
|
+
/** Delete the record and (when the output directory is known) its files. */
|
|
2699
|
+
const confirmDelete = async (asset) => {
|
|
2700
|
+
setDeleting(true);
|
|
2701
|
+
try {
|
|
2702
|
+
const result = await postJson("/comfyui/assets/delete", { promptId: asset.promptId });
|
|
2703
|
+
if (result.ok !== true) throw new Error(result.error ?? "delete failed");
|
|
2704
|
+
setPendingDelete(null);
|
|
2705
|
+
setNotice(t("assetDeleteDone", { deleted: result.deleted ?? 0 }));
|
|
2706
|
+
if (Array.isArray(result.failures) && result.failures.length > 0) setError(result.failures.join("; "));
|
|
2707
|
+
await load();
|
|
2708
|
+
} catch (cause) {
|
|
2709
|
+
setError(t("assetDeleteFailed", { message: cause instanceof Error ? cause.message : String(cause) }));
|
|
2710
|
+
setPendingDelete(null);
|
|
2711
|
+
} finally {
|
|
2712
|
+
setDeleting(false);
|
|
2713
|
+
}
|
|
2714
|
+
};
|
|
2715
|
+
if (error !== null && assets === null) return (0, react.createElement)("div", null, (0, react.createElement)(ErrorNote, {
|
|
2491
2716
|
t,
|
|
2492
2717
|
message: error
|
|
2493
2718
|
}));
|
|
@@ -2513,20 +2738,64 @@ window.__ModuleLoader__.load({
|
|
|
2513
2738
|
}, name === "" ? "—" : name))), (0, react.createElement)("button", {
|
|
2514
2739
|
className: "dsc-btn",
|
|
2515
2740
|
onClick: () => void load()
|
|
2516
|
-
}, t("refresh"))),
|
|
2741
|
+
}, t("refresh"))), error !== null ? (0, react.createElement)(ErrorNote, {
|
|
2742
|
+
t,
|
|
2743
|
+
message: error
|
|
2744
|
+
}) : null, notice !== null ? (0, react.createElement)("div", { className: "dsc-ok" }, notice) : null, visible.length === 0 ? (0, react.createElement)("div", { className: "dsc-meta" }, t("assetEmpty")) : (0, react.createElement)("div", { className: "dsc-assets-grid" }, visible.map((asset) => (0, react.createElement)(AssetThumb, {
|
|
2517
2745
|
key: asset.promptId,
|
|
2518
2746
|
asset,
|
|
2519
2747
|
t,
|
|
2520
|
-
onClick: () => openAsset(asset)
|
|
2521
|
-
|
|
2748
|
+
onClick: () => openAsset(asset),
|
|
2749
|
+
onDelete: () => {
|
|
2750
|
+
setNotice(null);
|
|
2751
|
+
setPendingDelete(asset);
|
|
2752
|
+
}
|
|
2753
|
+
}))), pendingDelete !== null ? (0, react.createElement)(ConfirmDialog, {
|
|
2754
|
+
t,
|
|
2755
|
+
title: t("assetDeleteTitle"),
|
|
2756
|
+
body: t("assetDeleteBody", { count: pendingDelete.media.length }),
|
|
2757
|
+
detail: pendingDelete.media.map((item) => item.filename).join("、"),
|
|
2758
|
+
busy: deleting,
|
|
2759
|
+
onConfirm: () => {
|
|
2760
|
+
confirmDelete(pendingDelete);
|
|
2761
|
+
},
|
|
2762
|
+
onCancel: () => setPendingDelete(null)
|
|
2763
|
+
}) : null);
|
|
2764
|
+
}
|
|
2765
|
+
/** Modal confirmation for a destructive action (asset deletion). */
|
|
2766
|
+
function ConfirmDialog(props) {
|
|
2767
|
+
return (0, react.createElement)("div", {
|
|
2768
|
+
className: "dsc-picker-overlay",
|
|
2769
|
+
onClick: props.onCancel
|
|
2770
|
+
}, (0, react.createElement)("div", {
|
|
2771
|
+
className: "dsc-confirm",
|
|
2772
|
+
onClick: (event) => event.stopPropagation()
|
|
2773
|
+
}, (0, react.createElement)("div", { className: "dsc-confirm-title" }, props.title), (0, react.createElement)("div", { className: "dsc-confirm-body" }, props.body), props.detail !== void 0 && props.detail !== "" ? (0, react.createElement)("div", { className: "dsc-confirm-detail" }, props.detail) : null, (0, react.createElement)("div", { className: "dsc-confirm-actions" }, (0, react.createElement)("button", {
|
|
2774
|
+
className: "dsc-btn",
|
|
2775
|
+
disabled: props.busy,
|
|
2776
|
+
onClick: props.onCancel
|
|
2777
|
+
}, props.t("cancel")), (0, react.createElement)("button", {
|
|
2778
|
+
className: "dsc-btn dsc-btn--danger",
|
|
2779
|
+
disabled: props.busy,
|
|
2780
|
+
onClick: props.onConfirm
|
|
2781
|
+
}, props.t("confirmDelete")))));
|
|
2522
2782
|
}
|
|
2523
2783
|
function AssetThumb(props) {
|
|
2524
2784
|
const first = props.asset.media[0];
|
|
2525
2785
|
const [broken, setBroken] = (0, react.useState)(false);
|
|
2786
|
+
const trash = (0, react.createElement)("button", {
|
|
2787
|
+
className: "dsc-asset-trash",
|
|
2788
|
+
title: props.t("assetDelete"),
|
|
2789
|
+
"aria-label": props.t("assetDelete"),
|
|
2790
|
+
onClick: (event) => {
|
|
2791
|
+
event.stopPropagation();
|
|
2792
|
+
props.onDelete();
|
|
2793
|
+
}
|
|
2794
|
+
}, "🗑");
|
|
2526
2795
|
if (first === void 0 || broken) return (0, react.createElement)("div", {
|
|
2527
2796
|
className: "dsc-asset dsc-asset--empty",
|
|
2528
2797
|
onClick: props.onClick
|
|
2529
|
-
}, (0, react.createElement)("div", null, props.t("assetGone")), (0, react.createElement)("div", { className: "dsc-meta" }, props.asset.workflowName ?? "—"));
|
|
2798
|
+
}, (0, react.createElement)("div", null, props.t("assetGone")), (0, react.createElement)("div", { className: "dsc-meta" }, props.asset.workflowName ?? "—"), trash);
|
|
2530
2799
|
return (0, react.createElement)("div", {
|
|
2531
2800
|
className: "dsc-asset",
|
|
2532
2801
|
onClick: props.onClick
|
|
@@ -2540,7 +2809,7 @@ window.__ModuleLoader__.load({
|
|
|
2540
2809
|
alt: first.filename,
|
|
2541
2810
|
loading: "lazy",
|
|
2542
2811
|
onError: () => setBroken(true)
|
|
2543
|
-
}), (0, react.createElement)("div", { className: "dsc-asset-meta" }, props.asset.workflowName ?? "—"));
|
|
2812
|
+
}), (0, react.createElement)("div", { className: "dsc-asset-meta" }, props.asset.workflowName ?? "—"), trash);
|
|
2544
2813
|
}
|
|
2545
2814
|
/**
|
|
2546
2815
|
* Tab 3: the task center — active tasks (pending / generating) shown as
|
|
@@ -2878,7 +3147,17 @@ select.dsc-input option { background: var(--dsw-alias-bg-layer-1); color: var(--
|
|
|
2878
3147
|
.dsc-mode-label { font-size: 13px; }
|
|
2879
3148
|
/* --- assets tab --- */
|
|
2880
3149
|
.dsc-assets-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 8px; }
|
|
2881
|
-
.dsc-asset { border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; overflow: hidden; cursor: pointer; background: var(--dsw-alias-bg-layer-1); }
|
|
3150
|
+
.dsc-asset { position: relative; border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; overflow: hidden; cursor: pointer; background: var(--dsw-alias-bg-layer-1); }
|
|
3151
|
+
/* Destructive, so it only appears while the pointer is on the card. */
|
|
3152
|
+
.dsc-asset-trash {
|
|
3153
|
+
position: absolute; top: 4px; right: 4px; width: 26px; height: 26px; padding: 0; line-height: 1;
|
|
3154
|
+
display: flex; align-items: center; justify-content: center;
|
|
3155
|
+
border: 1px solid var(--dsw-alias-state-error-primary); border-radius: 6px;
|
|
3156
|
+
background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-state-error-primary);
|
|
3157
|
+
font-size: 14px; cursor: pointer; opacity: 0; transition: opacity 0.12s;
|
|
3158
|
+
}
|
|
3159
|
+
.dsc-asset:hover .dsc-asset-trash, .dsc-asset-trash:focus-visible { opacity: 1; }
|
|
3160
|
+
.dsc-asset-trash:hover { background: var(--dsw-alias-state-error-primary); color: #fff; }
|
|
2882
3161
|
.dsc-asset img, .dsc-asset video { width: 100%; height: 110px; object-fit: cover; display: block; background: #000; }
|
|
2883
3162
|
.dsc-asset--empty {
|
|
2884
3163
|
height: 110px; padding: 8px; box-sizing: border-box;
|
|
@@ -2886,6 +3165,14 @@ select.dsc-input option { background: var(--dsw-alias-bg-layer-1); color: var(--
|
|
|
2886
3165
|
font-size: 11px; color: var(--dsw-alias-label-secondary); text-align: center;
|
|
2887
3166
|
}
|
|
2888
3167
|
.dsc-asset-meta { padding: 3px 6px; font-size: 11px; color: var(--dsw-alias-label-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
3168
|
+
/* confirmation modal (asset deletion) */
|
|
3169
|
+
.dsc-confirm { width: min(360px, 88vw); display: flex; flex-direction: column; gap: 8px; padding: 14px 16px; border-radius: 12px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); box-shadow: 0 12px 40px rgba(0, 0, 0, 0.32); }
|
|
3170
|
+
.dsc-confirm-title { font-weight: 600; font-size: 14px; }
|
|
3171
|
+
.dsc-confirm-body { font-size: 13px; line-height: 1.6; color: var(--dsw-alias-label-secondary); }
|
|
3172
|
+
.dsc-confirm-detail { font-size: 12px; line-height: 1.5; color: var(--dsw-alias-label-tertiary); word-break: break-all; max-height: 96px; overflow-y: auto; }
|
|
3173
|
+
.dsc-confirm-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 4px; }
|
|
3174
|
+
.dsc-btn--danger { border-color: var(--dsw-alias-state-error-primary); color: var(--dsw-alias-state-error-primary); }
|
|
3175
|
+
.dsc-btn--danger:hover:not(:disabled) { background: var(--dsw-alias-state-error-primary); color: #fff; }
|
|
2889
3176
|
.dsc-asset-detail-media { display: flex; flex-direction: column; gap: 8px; margin-bottom: 8px; }
|
|
2890
3177
|
.dsc-asset-detail-media img, .dsc-asset-detail-media video { width: 100%; max-height: 320px; object-fit: contain; background: #000; border-radius: 8px; }
|
|
2891
3178
|
.dsc-asset-detail-file { display: flex; align-items: center; justify-content: space-between; gap: 8px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; padding: 6px 8px; }
|
|
@@ -2926,12 +3213,36 @@ select.dsc-input option { background: var(--dsw-alias-bg-layer-1); color: var(--
|
|
|
2926
3213
|
.dsc-loadarea { margin-top: 14px; border-top: 1px solid var(--dsw-alias-border-l1); padding-top: 10px; display: flex; flex-direction: column; gap: 8px; }
|
|
2927
3214
|
.dsc-loadarea-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
|
|
2928
3215
|
.dsc-loadarea-title { font-weight: 600; font-size: 12px; color: var(--dsw-alias-label-primary); }
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
.dsc-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
.dsc-
|
|
3216
|
+
/* load-area slots: slot 0 is the primary (big) source, the rest are thumbs.
|
|
3217
|
+
Each slot carries its own actions (add / clear media / delete slot). */
|
|
3218
|
+
.dsc-loadslots { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
3219
|
+
/* One slot stretches across the panel; from two on they share a fixed width
|
|
3220
|
+
and wrap into rows. The × sits in the corner and appears on hover. */
|
|
3221
|
+
.dsc-loadslot { position: relative; display: flex; flex-direction: column; gap: 4px; width: 128px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 10px; padding: 6px; background: var(--dsw-alias-bg-layer-1); }
|
|
3222
|
+
.dsc-loadslot--wide { width: 100%; }
|
|
3223
|
+
.dsc-loadslot-pick { display: flex; flex-direction: column; align-items: center; gap: 4px; border: none; border-radius: 8px; padding: 0; background: transparent; color: inherit; cursor: pointer; width: 100%; }
|
|
3224
|
+
.dsc-loadslot-media { width: 100%; height: 104px; object-fit: contain; border-radius: 6px; background: var(--dsw-alias-bg-layer-2); }
|
|
3225
|
+
.dsc-loadslot--wide .dsc-loadslot-media { height: 220px; }
|
|
3226
|
+
.dsc-loadslot-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; width: 100%; height: 104px; border-radius: 6px; border: 1px dashed var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-2); font-size: 12px; }
|
|
3227
|
+
.dsc-loadslot--wide .dsc-loadslot-empty { height: 120px; font-size: 13px; }
|
|
3228
|
+
.dsc-loadslot-name { width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; color: var(--dsw-alias-label-primary); text-align: center; }
|
|
3229
|
+
.dsc-loadslot-index { color: var(--dsw-alias-label-tertiary); }
|
|
3230
|
+
.dsc-loadslot-add { color: var(--dsw-alias-label-secondary); }
|
|
3231
|
+
.dsc-loadslot-pick:hover .dsc-loadslot-add { color: var(--dsw-alias-brand-primary); }
|
|
3232
|
+
.dsc-loadslot-x {
|
|
3233
|
+
position: absolute; top: 3px; right: 3px; width: 26px; height: 26px; padding: 0; line-height: 1;
|
|
3234
|
+
display: flex; align-items: center; justify-content: center;
|
|
3235
|
+
border: 1px solid var(--dsw-alias-border-l2); border-radius: 50%;
|
|
3236
|
+
background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-secondary);
|
|
3237
|
+
font-size: 18px; cursor: pointer; opacity: 0; transition: opacity 0.12s;
|
|
3238
|
+
}
|
|
3239
|
+
.dsc-loadslot:hover .dsc-loadslot-x, .dsc-loadslot-x:focus-visible { opacity: 1; }
|
|
3240
|
+
.dsc-loadslot-x:hover { color: var(--dsw-alias-state-error-primary); border-color: var(--dsw-alias-state-error-primary); }
|
|
3241
|
+
.dsc-picker-player { width: 100%; border-radius: 6px; background: var(--dsw-alias-bg-layer-1); }
|
|
3242
|
+
video.dsc-picker-player { max-height: 180px; }
|
|
3243
|
+
.dsc-picker-player--audio { height: 34px; }
|
|
3244
|
+
.dsc-loadslot-x:disabled { opacity: 0; cursor: default; }
|
|
3245
|
+
.dsc-picker-card--none .dsc-picker-thumb--media { font-size: 26px; color: var(--dsw-alias-label-tertiary); }
|
|
2935
3246
|
/* picker overlay: dims the page, dialog floats in the center */
|
|
2936
3247
|
.dsc-picker-overlay { position: fixed; inset: 0; z-index: 9990; background: rgba(6, 8, 12, 0.6); display: flex; align-items: center; justify-content: center; padding: 24px; }
|
|
2937
3248
|
.dsc-picker { position: relative; display: flex; flex-direction: column; width: min(860px, 92vw); height: min(560px, 84vh); background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l2); border-radius: 12px; box-shadow: 0 16px 56px rgba(0, 0, 0, 0.5); overflow: hidden; }
|
|
@@ -2950,7 +3261,8 @@ select.dsc-input option { background: var(--dsw-alias-bg-layer-1); color: var(--
|
|
|
2950
3261
|
.dsc-picker-card--active { border-color: var(--dsw-alias-brand-primary); box-shadow: 0 0 0 1px var(--dsw-alias-brand-primary); }
|
|
2951
3262
|
.dsc-picker-thumb { width: 100%; height: auto; object-fit: contain; border-radius: 6px; background: var(--dsw-alias-bg-layer-1); }
|
|
2952
3263
|
.dsc-picker-thumb--media { display: flex; align-items: center; justify-content: center; height: 100px; font-size: 22px; }
|
|
2953
|
-
.dsc-picker-name { font-size:
|
|
3264
|
+
.dsc-picker-name { font-size: 13px; line-height: 1.5; padding: 2px 0; color: var(--dsw-alias-label-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
3265
|
+
.dsc-picker-card:hover .dsc-picker-name { color: var(--dsw-alias-brand-primary); }
|
|
2954
3266
|
/* --- workflow tags: filter bar, chips, editor --- */
|
|
2955
3267
|
.dsc-tag-filter { display: flex; flex-wrap: wrap; gap: 6px; padding: 6px 0 2px; }
|
|
2956
3268
|
.dsc-tag-chip { border: 1px solid var(--dsw-alias-border-l1); border-radius: 999px; padding: 2px 10px; font-size: 12px; cursor: pointer; background: transparent; color: var(--dsw-alias-label-primary); }
|
|
@@ -3012,6 +3324,7 @@ select.dsc-input option { background: var(--dsw-alias-bg-layer-1); color: var(--
|
|
|
3012
3324
|
.dsc-param-default { min-width: 0; }
|
|
3013
3325
|
.dsc-param-meta { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
|
|
3014
3326
|
.dsc-param-random { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: var(--dsw-alias-label-secondary); cursor: pointer; }
|
|
3327
|
+
.dsc-param-bool { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--dsw-alias-label-primary); cursor: pointer; }
|
|
3015
3328
|
.dsc-param-advanced { display: flex; flex-direction: column; gap: 8px; border: 1px dashed var(--dsw-alias-border-l2); border-radius: 8px; padding: 8px; }
|
|
3016
3329
|
`;
|
|
3017
3330
|
/** Inject the stylesheet once (idempotent), owned by this plugin for HMR. */
|