dsh-image-gen 0.2.4 → 0.2.5
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 +2 -2
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/lib/client.js +318 -114
- package/lib/client.js.map +1 -1
- package/lib/index.js +238 -41
- package/lib/types/client/gallery-store.d.ts +2 -0
- package/lib/types/client/gallery-view.d.ts +2 -0
- package/lib/types/client/image-result-node.d.ts +18 -1
- package/lib/types/client/index.d.ts +3 -1
- package/lib/types/comfyui-workflow.d.ts +13 -2
- package/lib/types/comfyui.d.ts +22 -5
- package/lib/types/config.d.ts +15 -6
- package/lib/types/shared.d.ts +31 -0
- package/package.json +3 -2
package/lib/client.js
CHANGED
|
@@ -19,12 +19,61 @@ window.__ModuleLoader__.load({
|
|
|
19
19
|
const DEFAULT_DASHSCOPE_ENDPOINT = "https://dashscope.aliyuncs.com/api/v1";
|
|
20
20
|
const DEFAULT_COMFYUI_BASE_URL = "http://127.0.0.1:8188";
|
|
21
21
|
const DEFAULT_COMFYUI_TIMEOUT_MS = 3e5;
|
|
22
|
+
const DEFAULT_COMFYUI_WORKFLOW_LABEL = "API workflow";
|
|
23
|
+
/** Default model names. */
|
|
24
|
+
const DEFAULT_GOOGLE_MODEL = "gemini-3.1-flash-image";
|
|
25
|
+
const DEFAULT_OPENAI_MODEL = "gpt-image-2";
|
|
26
|
+
const DEFAULT_SEEDREAM_MODEL = "doubao-seedream-5-0-260128";
|
|
27
|
+
const DEFAULT_DASHSCOPE_MODEL = "qwen-image-3.0";
|
|
28
|
+
/** Named workflows, falling back to the legacy single-workflow fields when the list is empty. */
|
|
29
|
+
function resolveComfyUIWorkflows(source) {
|
|
30
|
+
const named = [];
|
|
31
|
+
for (const entry of source.comfyuiWorkflows ?? []) {
|
|
32
|
+
const name = typeof entry?.name === "string" ? entry.name.trim() : "";
|
|
33
|
+
const json = typeof entry?.json === "string" ? entry.json : "";
|
|
34
|
+
if (name.length > 0 && json.trim().length > 0) {
|
|
35
|
+
const presetPrompt = typeof entry.presetPrompt === "string" ? entry.presetPrompt.trim() : "";
|
|
36
|
+
named.push(presetPrompt.length > 0 ? {
|
|
37
|
+
name,
|
|
38
|
+
json,
|
|
39
|
+
presetPrompt
|
|
40
|
+
} : {
|
|
41
|
+
name,
|
|
42
|
+
json
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (named.length > 0) return named;
|
|
47
|
+
const legacyJson = typeof source.comfyuiWorkflowJson === "string" ? source.comfyuiWorkflowJson : "";
|
|
48
|
+
if (legacyJson.trim().length === 0) return [];
|
|
49
|
+
const legacyName = typeof source.comfyuiWorkflowName === "string" ? source.comfyuiWorkflowName.trim() : "";
|
|
50
|
+
return [{
|
|
51
|
+
name: legacyName.length > 0 ? legacyName : DEFAULT_COMFYUI_WORKFLOW_LABEL,
|
|
52
|
+
json: legacyJson
|
|
53
|
+
}];
|
|
54
|
+
}
|
|
55
|
+
/** The workflow ComfyUI calls use by default: the configured active name, else the first entry. */
|
|
56
|
+
function activeComfyUIWorkflow(source) {
|
|
57
|
+
const workflows = resolveComfyUIWorkflows(source);
|
|
58
|
+
if (workflows.length === 0) return void 0;
|
|
59
|
+
const activeName = typeof source.comfyuiActiveWorkflow === "string" ? source.comfyuiActiveWorkflow.trim() : "";
|
|
60
|
+
return workflows.find((workflow) => workflow.name === activeName) ?? workflows[0];
|
|
61
|
+
}
|
|
62
|
+
/** Derive a workflow label that does not collide with the given existing names. */
|
|
63
|
+
function uniqueComfyUIWorkflowName(name, existing) {
|
|
64
|
+
const base = name.trim().length > 0 ? name.trim() : DEFAULT_COMFYUI_WORKFLOW_LABEL;
|
|
65
|
+
if (!existing.includes(base)) return base;
|
|
66
|
+
for (let index = 2;; index += 1) {
|
|
67
|
+
const candidate = `${base} (${index})`;
|
|
68
|
+
if (!existing.includes(candidate)) return candidate;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
22
71
|
const DEFAULT_MODELS = {
|
|
23
|
-
google:
|
|
24
|
-
openai:
|
|
25
|
-
seedream:
|
|
26
|
-
dashscope:
|
|
27
|
-
comfyui:
|
|
72
|
+
google: DEFAULT_GOOGLE_MODEL,
|
|
73
|
+
openai: DEFAULT_OPENAI_MODEL,
|
|
74
|
+
seedream: DEFAULT_SEEDREAM_MODEL,
|
|
75
|
+
dashscope: DEFAULT_DASHSCOPE_MODEL,
|
|
76
|
+
comfyui: DEFAULT_COMFYUI_WORKFLOW_LABEL
|
|
28
77
|
};
|
|
29
78
|
const DEFAULT_BASE_URLS = {
|
|
30
79
|
google: DEFAULT_GOOGLE_ENDPOINT,
|
|
@@ -37,9 +86,16 @@ window.__ModuleLoader__.load({
|
|
|
37
86
|
//#region lib/types/comfyui-workflow.js
|
|
38
87
|
/** Pure ComfyUI API-workflow validation and placeholder injection. */
|
|
39
88
|
const COMFYUI_PROMPT_PLACEHOLDER = "{{prompt}}";
|
|
89
|
+
const COMFYUI_IMAGE_PLACEHOLDER = "{{image}}";
|
|
90
|
+
/** Legacy single-percent placeholders from early releases, still accepted. */
|
|
91
|
+
const LEGACY_PROMPT_PLACEHOLDER = "%prompt%";
|
|
92
|
+
const LEGACY_IMAGE_PLACEHOLDER = "%image%";
|
|
93
|
+
/** LoadImage-style input key that receives the uploaded source image name. */
|
|
94
|
+
const IMAGE_INPUT_KEY = "image";
|
|
40
95
|
/** Validate imported JSON without exposing workflow graph details to callers. */
|
|
41
96
|
function validateComfyUIWorkflowJson(workflowJson) {
|
|
42
|
-
parseWorkflow(workflowJson);
|
|
97
|
+
const imageInputs = countImagePlaceholders(parseWorkflow(workflowJson));
|
|
98
|
+
if (imageInputs > 1) throw new Error(`ComfyUI workflow must contain at most one ${COMFYUI_IMAGE_PLACEHOLDER} image input; found ${String(imageInputs)}`);
|
|
43
99
|
}
|
|
44
100
|
function parseWorkflow(workflowJson) {
|
|
45
101
|
if (workflowJson.trim().length === 0) throw new Error("Import a ComfyUI API workflow JSON file in Settings before generating");
|
|
@@ -54,11 +110,23 @@ window.__ModuleLoader__.load({
|
|
|
54
110
|
if (!Object.values(value).some((node) => {
|
|
55
111
|
const inputs = record$1(node)?.inputs;
|
|
56
112
|
return inputs !== void 0 && containsPromptPlaceholder(inputs);
|
|
57
|
-
})) throw new Error(`ComfyUI workflow must contain ${COMFYUI_PROMPT_PLACEHOLDER} in a text input`);
|
|
113
|
+
})) throw new Error(`ComfyUI workflow must contain ${COMFYUI_PROMPT_PLACEHOLDER} (or ${LEGACY_PROMPT_PLACEHOLDER}) in a text input`);
|
|
58
114
|
return value;
|
|
59
115
|
}
|
|
116
|
+
/** Count `inputs.image` fields that are exactly an image placeholder. */
|
|
117
|
+
function countImagePlaceholders(workflow) {
|
|
118
|
+
let count = 0;
|
|
119
|
+
for (const node of Object.values(workflow)) {
|
|
120
|
+
const inputs = record$1(record$1(node)?.inputs);
|
|
121
|
+
if (inputs !== void 0 && isImagePlaceholder(inputs[IMAGE_INPUT_KEY])) count += 1;
|
|
122
|
+
}
|
|
123
|
+
return count;
|
|
124
|
+
}
|
|
125
|
+
function isImagePlaceholder(value) {
|
|
126
|
+
return value === "{{image}}" || value === LEGACY_IMAGE_PLACEHOLDER;
|
|
127
|
+
}
|
|
60
128
|
function containsPromptPlaceholder(value) {
|
|
61
|
-
if (typeof value === "string") return value.includes(
|
|
129
|
+
if (typeof value === "string") return value.includes("{{prompt}}") || value.includes(LEGACY_PROMPT_PLACEHOLDER);
|
|
62
130
|
if (Array.isArray(value)) return value.some(containsPromptPlaceholder);
|
|
63
131
|
return isRecord(value) && Object.values(value).some(containsPromptPlaceholder);
|
|
64
132
|
}
|
|
@@ -223,6 +291,7 @@ window.__ModuleLoader__.load({
|
|
|
223
291
|
filterOpenAI: "OpenAI / 中转站",
|
|
224
292
|
filterSeedream: "字节 Seedream",
|
|
225
293
|
filterDashScope: "阿里 DashScope",
|
|
294
|
+
filterComfyUI: "本地 ComfyUI",
|
|
226
295
|
emptyTitle: "暂无生图记录",
|
|
227
296
|
emptyDesc: "在对话中让 Agent 生图后,生成的图片会自动收录到这里。",
|
|
228
297
|
noMatchTitle: "未找到匹配结果",
|
|
@@ -249,6 +318,7 @@ window.__ModuleLoader__.load({
|
|
|
249
318
|
filterOpenAI: "OpenAI / Relay",
|
|
250
319
|
filterSeedream: "ByteDance Seedream",
|
|
251
320
|
filterDashScope: "Aliyun DashScope",
|
|
321
|
+
filterComfyUI: "Local ComfyUI",
|
|
252
322
|
emptyTitle: "No images generated yet",
|
|
253
323
|
emptyDesc: "Images generated during conversations will automatically appear here.",
|
|
254
324
|
noMatchTitle: "No matching images",
|
|
@@ -416,6 +486,10 @@ window.__ModuleLoader__.load({
|
|
|
416
486
|
(0, react_jsx_runtime.jsx)("option", {
|
|
417
487
|
value: "dashscope",
|
|
418
488
|
children: t("filterDashScope")
|
|
489
|
+
}),
|
|
490
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
491
|
+
value: "comfyui",
|
|
492
|
+
children: t("filterComfyUI")
|
|
419
493
|
})
|
|
420
494
|
]
|
|
421
495
|
})]
|
|
@@ -943,80 +1017,93 @@ window.__ModuleLoader__.load({
|
|
|
943
1017
|
//#endregion
|
|
944
1018
|
//#region lib/types/client/image-result-node.js
|
|
945
1019
|
const IMAGE_RESULT_NODE_KIND = "dsh-image-result";
|
|
946
|
-
/**
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1020
|
+
/**
|
|
1021
|
+
* One image result row per Turn, anchored at its own tool/result position while
|
|
1022
|
+
* the Turn runs, then optionally re-anchored beside the final answer once the
|
|
1023
|
+
* Turn closes (only needed under the folding Compact transcript view).
|
|
1024
|
+
*/
|
|
1025
|
+
function createImageResultDefinition(options = {}) {
|
|
1026
|
+
return {
|
|
1027
|
+
kind: IMAGE_RESULT_NODE_KIND,
|
|
1028
|
+
target: "chat",
|
|
1029
|
+
match: (event) => {
|
|
1030
|
+
const turn = eventTurn(event);
|
|
1031
|
+
if (turn === void 0) return null;
|
|
1032
|
+
if (event.type === "turn/start") return {
|
|
1033
|
+
id: String(turn),
|
|
1034
|
+
role: "start"
|
|
1035
|
+
};
|
|
1036
|
+
if (event.type === "turn/end") return {
|
|
1037
|
+
id: String(turn),
|
|
1038
|
+
role: "update"
|
|
1039
|
+
};
|
|
1040
|
+
if (event.type === "assistant/message") return {
|
|
1041
|
+
id: String(turn),
|
|
1042
|
+
role: "update"
|
|
1043
|
+
};
|
|
1044
|
+
if (event.type === "tool/result" && imageResultFromMeta(event.data.meta) !== void 0) return {
|
|
1045
|
+
id: String(turn),
|
|
1046
|
+
role: "update"
|
|
1047
|
+
};
|
|
1048
|
+
return null;
|
|
1049
|
+
},
|
|
1050
|
+
start: (_context, match) => {
|
|
1051
|
+
const turn = eventTurn(match.event);
|
|
1052
|
+
if (match.event.type !== "turn/start" || turn === void 0) throw new Error("dsh-image-result start requires turn/start");
|
|
1053
|
+
return {
|
|
1054
|
+
turn,
|
|
1055
|
+
results: []
|
|
1056
|
+
};
|
|
1057
|
+
},
|
|
1058
|
+
update: (context, match) => {
|
|
1059
|
+
if (match.event.type === "turn/end") return {
|
|
1060
|
+
...context.state,
|
|
1061
|
+
endSeq: match.event.seq
|
|
1062
|
+
};
|
|
1063
|
+
if (match.event.type === "assistant/message") return {
|
|
1064
|
+
...context.state,
|
|
1065
|
+
answerSeq: match.event.seq
|
|
1066
|
+
};
|
|
1067
|
+
if (match.event.type !== "tool/result") return context.state;
|
|
1068
|
+
const result = imageResultFromMeta(match.event.data.meta);
|
|
1069
|
+
if (result === void 0) return context.state;
|
|
1070
|
+
if (context.state.results.some((candidate) => candidate.attachment.attachmentId === result.attachment.attachmentId)) return context.state;
|
|
1071
|
+
return {
|
|
1072
|
+
...context.state,
|
|
1073
|
+
results: [...context.state.results, {
|
|
1074
|
+
...result,
|
|
1075
|
+
seq: match.event.seq
|
|
1076
|
+
}]
|
|
1077
|
+
};
|
|
1078
|
+
},
|
|
1079
|
+
buildViewNode: (context) => {
|
|
1080
|
+
const state = context.state;
|
|
1081
|
+
const last = state?.results.at(-1);
|
|
1082
|
+
if (state === void 0 || last === void 0) return null;
|
|
1083
|
+
const location = context.matches.at(-1)?.location ?? context.start?.location ?? { kind: "unresolved" };
|
|
1084
|
+
return {
|
|
1085
|
+
key: context.key,
|
|
1086
|
+
kind: IMAGE_RESULT_NODE_KIND,
|
|
1087
|
+
id: context.id,
|
|
1088
|
+
target: "chat",
|
|
1089
|
+
anchorSeq: state.endSeq !== void 0 && compactTranscript(options) ? state.answerSeq ?? state.endSeq ?? last.seq : last.seq,
|
|
1090
|
+
location,
|
|
1091
|
+
visibility: "visible",
|
|
1092
|
+
data: {
|
|
1093
|
+
turn: state.turn,
|
|
1094
|
+
results: state.results.map(({ seq: _seq, ...result }) => result)
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
function compactTranscript(options) {
|
|
1101
|
+
try {
|
|
1102
|
+
return options.isCompactTranscript?.() ?? true;
|
|
1103
|
+
} catch {
|
|
1104
|
+
return true;
|
|
1018
1105
|
}
|
|
1019
|
-
}
|
|
1106
|
+
}
|
|
1020
1107
|
/** Parse the plugin-owned durable presentation metadata from a Tool result event. */
|
|
1021
1108
|
function imageResultFromMeta(value) {
|
|
1022
1109
|
const meta = record(value);
|
|
@@ -1029,7 +1116,8 @@ window.__ModuleLoader__.load({
|
|
|
1029
1116
|
provider: stringValue(meta.provider, "google"),
|
|
1030
1117
|
model: stringValue(meta.model, ""),
|
|
1031
1118
|
output: stringValue(meta.output, ""),
|
|
1032
|
-
...typeof meta.savedTo === "string" ? { savedTo: meta.savedTo } : {}
|
|
1119
|
+
...typeof meta.savedTo === "string" ? { savedTo: meta.savedTo } : {},
|
|
1120
|
+
...typeof meta.seed === "number" ? { seed: meta.seed } : {}
|
|
1033
1121
|
};
|
|
1034
1122
|
}
|
|
1035
1123
|
function eventTurn(event) {
|
|
@@ -1096,13 +1184,18 @@ window.__ModuleLoader__.load({
|
|
|
1096
1184
|
endpointHintDashScope: "阿里云百炼 DashScope 官方接口地址。",
|
|
1097
1185
|
endpointHintComfyUI: "正在运行且 DSH Host 可以访问的 ComfyUI 地址,默认使用本机 8188 端口。",
|
|
1098
1186
|
model: "模型",
|
|
1099
|
-
workflow: "API Workflow",
|
|
1187
|
+
workflow: "API Workflow 工作流",
|
|
1100
1188
|
workflowImport: "导入 JSON 文件",
|
|
1101
|
-
workflowReplace: "替换 JSON 文件",
|
|
1102
1189
|
workflowMissing: "尚未导入工作流",
|
|
1103
1190
|
workflowImported: "已导入 {name}",
|
|
1104
|
-
workflowHint: "
|
|
1191
|
+
workflowHint: "从 ComfyUI 导出 API Format JSON,在提示词输入写入 {{prompt}},种子可用 {{seed}};图生图工作流在 LoadImage 的 image 输入写入 {{image}}(仅一次)。可导入多个工作流,Agent 也能在调用时按名称指定。",
|
|
1105
1192
|
workflowTooLarge: "工作流文件不能超过 5 MB。",
|
|
1193
|
+
workflowActiveTitle: "设为当前使用的工作流",
|
|
1194
|
+
workflowRemove: "删除",
|
|
1195
|
+
workflowPresetPlaceholder: "预设提示词,留空则只用对话内容",
|
|
1196
|
+
workflowPresetTitle: "预设提示词:每次调用此工作流时自动加在用户提示词前面。",
|
|
1197
|
+
workflowNameRequired: "工作流名称不能为空。",
|
|
1198
|
+
workflowDuplicateName: "工作流名称不能重复。",
|
|
1106
1199
|
timeout: "生成超时(秒)",
|
|
1107
1200
|
timeoutHint: "包括提交、等待和下载图片;默认 300 秒。",
|
|
1108
1201
|
saveToWorkspace: "保存到工作区",
|
|
@@ -1148,13 +1241,18 @@ window.__ModuleLoader__.load({
|
|
|
1148
1241
|
endpointHintDashScope: "Official Aliyun DashScope endpoint.",
|
|
1149
1242
|
endpointHintComfyUI: "A running ComfyUI server reachable by the DSH Host; the default points to port 8188 on this computer.",
|
|
1150
1243
|
model: "Model",
|
|
1151
|
-
workflow: "API
|
|
1244
|
+
workflow: "API Workflows",
|
|
1152
1245
|
workflowImport: "Import JSON file",
|
|
1153
|
-
workflowReplace: "Replace JSON file",
|
|
1154
1246
|
workflowMissing: "No workflow imported",
|
|
1155
1247
|
workflowImported: "Imported {name}",
|
|
1156
|
-
workflowHint: "Export an API Format JSON from ComfyUI and place {{prompt}} in its prompt input
|
|
1248
|
+
workflowHint: "Export an API Format JSON from ComfyUI and place {{prompt}} in its prompt input; {{seed}} is available for a random seed. For image editing put {{image}} (exactly once) in the LoadImage image input. Import as many workflows as you need; the Agent can also pick one by name.",
|
|
1157
1249
|
workflowTooLarge: "Workflow files must be no larger than 5 MB.",
|
|
1250
|
+
workflowActiveTitle: "Make this the active workflow",
|
|
1251
|
+
workflowRemove: "Remove",
|
|
1252
|
+
workflowPresetPlaceholder: "Preset prompt (optional)",
|
|
1253
|
+
workflowPresetTitle: "Preset prompt: automatically prepended to the user prompt on every call of this workflow.",
|
|
1254
|
+
workflowNameRequired: "Workflow names cannot be empty.",
|
|
1255
|
+
workflowDuplicateName: "Workflow names must be unique.",
|
|
1158
1256
|
timeout: "Generation timeout (seconds)",
|
|
1159
1257
|
timeoutHint: "Covers submission, waiting, and image download; defaults to 300 seconds.",
|
|
1160
1258
|
saveToWorkspace: "Save to workspace",
|
|
@@ -1203,9 +1301,16 @@ window.__ModuleLoader__.load({
|
|
|
1203
1301
|
.dsh-ig-file-button:hover{background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
|
|
1204
1302
|
.dsh-ig-file-button:focus-within{outline:2px solid var(--dsw-alias-brand-primary,#4c78ff);outline-offset:2px}
|
|
1205
1303
|
.dsh-ig-file-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-secondary,inherit);font-size:12px}
|
|
1304
|
+
.dsh-ig-workflow-list{list-style:none;margin:0;padding:0;display:grid;gap:8px}
|
|
1305
|
+
.dsh-ig-workflow-row{display:flex;flex-direction:column;gap:6px;padding:8px;border:1px solid var(--dsw-alias-border-l2,#eee);border-radius:8px}
|
|
1306
|
+
.dsh-ig-workflow-main{display:flex;align-items:center;gap:8px}
|
|
1307
|
+
.dsh-ig-workflow-active{display:inline-flex;align-items:center;cursor:pointer;flex:none}
|
|
1308
|
+
.dsh-ig-workflow-active input[type=radio]{width:15px;height:15px;accent-color:var(--dsw-alias-brand-primary,#4c78ff);margin:0;cursor:pointer}
|
|
1309
|
+
.dsh-ig-workflow-name{flex:1;min-width:0}
|
|
1206
1310
|
.dsh-ig-btn-reset{appearance:none;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);border-radius:8px;padding:7px 12px;background:var(--dsw-alias-bg-layer-3,#f9fafb);color:var(--dsw-alias-label-secondary,inherit);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap;transition:background .15s,border-color .15s}
|
|
1207
1311
|
.dsh-ig-btn-reset:hover{background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
|
|
1208
1312
|
.dsh-ig-hint,.dsh-ig-status{margin:0;color:var(--dsw-alias-label-tertiary,#7b818b);font-size:12px;line-height:1.4}
|
|
1313
|
+
.dsh-ig-status-error{color:var(--dsw-alias-label-error,#d33);font-weight:500}
|
|
1209
1314
|
.dsh-ig-actions{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:16px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2,#eee)}
|
|
1210
1315
|
.dsh-ig-check-row{display:flex;align-items:center;gap:8px;cursor:pointer}
|
|
1211
1316
|
.dsh-ig-check-row input[type=checkbox]{width:15px;height:15px;accent-color:var(--dsw-alias-brand-primary,#4c78ff);margin:0}
|
|
@@ -1294,6 +1399,8 @@ window.__ModuleLoader__.load({
|
|
|
1294
1399
|
/** Mount the settings card, generated-image card, and native conversation gallery view. */
|
|
1295
1400
|
function apply(ctx) {
|
|
1296
1401
|
const scope = ctx.settingsScope.bind({ namespace: IMAGE_GENERATION_NAMESPACE });
|
|
1402
|
+
const chatScope = ctx.settingsScope.bind({ namespace: "ui-chat" });
|
|
1403
|
+
const isCompactTranscript = () => chatScope.getSnapshot().value?.transcriptView !== "normal";
|
|
1297
1404
|
const locale = ctx.get("locale");
|
|
1298
1405
|
const promotion = { enabled: false };
|
|
1299
1406
|
ctx.effect(() => {
|
|
@@ -1311,7 +1418,7 @@ window.__ModuleLoader__.load({
|
|
|
1311
1418
|
if (uiConversation === void 0) throw new Error("dsh-image-gen: uiConversation has an incompatible interface");
|
|
1312
1419
|
promotion.enabled = true;
|
|
1313
1420
|
const ownerRegister = owner.slots.register.bind(owner.slots);
|
|
1314
|
-
owner.effect(() => uiConversation.events.register(
|
|
1421
|
+
owner.effect(() => uiConversation.events.register(createImageResultDefinition({ isCompactTranscript })), "dsh-image-gen: promoted image result node");
|
|
1315
1422
|
owner.slots.inject("conversation.chat.node", () => ownerRegister({
|
|
1316
1423
|
name: "conversation.chat.node",
|
|
1317
1424
|
key: IMAGE_RESULT_NODE_KIND,
|
|
@@ -1409,8 +1516,8 @@ window.__ModuleLoader__.load({
|
|
|
1409
1516
|
const [provider, setProvider] = (0, react.useState)("google");
|
|
1410
1517
|
const [model, setModel] = (0, react.useState)("");
|
|
1411
1518
|
const [baseURL, setBaseURL] = (0, react.useState)("");
|
|
1412
|
-
const [
|
|
1413
|
-
const [
|
|
1519
|
+
const [workflows, setWorkflows] = (0, react.useState)([]);
|
|
1520
|
+
const [activeWorkflow, setActiveWorkflow] = (0, react.useState)("");
|
|
1414
1521
|
const [timeoutSeconds, setTimeoutSeconds] = (0, react.useState)(DEFAULT_COMFYUI_TIMEOUT_MS / 1e3);
|
|
1415
1522
|
const [saveToWorkspace, setSaveToWorkspace] = (0, react.useState)(true);
|
|
1416
1523
|
const [workspaceFolder, setWorkspaceFolder] = (0, react.useState)("dsh-image-gen");
|
|
@@ -1418,6 +1525,15 @@ window.__ModuleLoader__.load({
|
|
|
1418
1525
|
const [configured, setConfigured] = (0, react.useState)();
|
|
1419
1526
|
const [saving, setSaving] = (0, react.useState)(false);
|
|
1420
1527
|
const [message, setMessage] = (0, react.useState)("");
|
|
1528
|
+
const [messageIsError, setMessageIsError] = (0, react.useState)(false);
|
|
1529
|
+
const reportMessage = (text) => {
|
|
1530
|
+
setMessage(text);
|
|
1531
|
+
setMessageIsError(false);
|
|
1532
|
+
};
|
|
1533
|
+
const reportError = (text) => {
|
|
1534
|
+
setMessage(text);
|
|
1535
|
+
setMessageIsError(true);
|
|
1536
|
+
};
|
|
1421
1537
|
(0, react.useEffect)(() => props.scope.subscribe(() => {
|
|
1422
1538
|
setSnapshot(props.scope.getSnapshot());
|
|
1423
1539
|
}), [props.scope]);
|
|
@@ -1444,8 +1560,8 @@ window.__ModuleLoader__.load({
|
|
|
1444
1560
|
setProvider(next);
|
|
1445
1561
|
setModel(modelOf(next, value));
|
|
1446
1562
|
setBaseURL(baseURLOf(next, value));
|
|
1447
|
-
|
|
1448
|
-
|
|
1563
|
+
setWorkflows(resolveComfyUIWorkflows(value ?? {}));
|
|
1564
|
+
setActiveWorkflow(activeComfyUIWorkflow(value ?? {})?.name ?? "");
|
|
1449
1565
|
setTimeoutSeconds(Math.max(1, Math.round((value?.comfyuiTimeoutMs ?? 3e5) / 1e3)));
|
|
1450
1566
|
setSaveToWorkspace(value?.saveToWorkspace ?? true);
|
|
1451
1567
|
setWorkspaceFolder(value?.workspaceFolder ?? "dsh-image-gen");
|
|
@@ -1473,10 +1589,22 @@ window.__ModuleLoader__.load({
|
|
|
1473
1589
|
try {
|
|
1474
1590
|
await props.scope.set("provider", provider);
|
|
1475
1591
|
if (provider === "comfyui") {
|
|
1476
|
-
|
|
1592
|
+
const entries = workflows.map((entry) => ({
|
|
1593
|
+
name: entry.name.trim(),
|
|
1594
|
+
json: entry.json,
|
|
1595
|
+
presetPrompt: (entry.presetPrompt ?? "").trim()
|
|
1596
|
+
}));
|
|
1597
|
+
for (const entry of entries) {
|
|
1598
|
+
if (entry.name.length === 0) throw new Error(t("workflowNameRequired"));
|
|
1599
|
+
validateComfyUIWorkflowJson(entry.json);
|
|
1600
|
+
}
|
|
1601
|
+
if (new Set(entries.map((entry) => entry.name)).size !== entries.length) throw new Error(t("workflowDuplicateName"));
|
|
1602
|
+
const activeEntry = entries.find((entry) => entry.name === activeWorkflow) ?? entries[0];
|
|
1477
1603
|
await props.scope.set("comfyuiBaseURL", baseURL);
|
|
1478
|
-
await props.scope.set("
|
|
1479
|
-
await props.scope.set("
|
|
1604
|
+
await props.scope.set("comfyuiWorkflows", entries);
|
|
1605
|
+
await props.scope.set("comfyuiActiveWorkflow", activeEntry === void 0 ? "" : activeEntry.name);
|
|
1606
|
+
await props.scope.set("comfyuiWorkflowJson", activeEntry === void 0 ? "" : activeEntry.json);
|
|
1607
|
+
await props.scope.set("comfyuiWorkflowName", activeEntry === void 0 ? "" : activeEntry.name);
|
|
1480
1608
|
await props.scope.set("comfyuiTimeoutMs", Math.max(1, Math.round(timeoutSeconds)) * 1e3);
|
|
1481
1609
|
} else {
|
|
1482
1610
|
await props.scope.set(provider === "google" ? "googleModel" : provider === "openai" ? "openaiModel" : provider === "seedream" ? "seedreamModel" : "dashscopeModel", model);
|
|
@@ -1492,31 +1620,58 @@ window.__ModuleLoader__.load({
|
|
|
1492
1620
|
setKey("");
|
|
1493
1621
|
setConfigured(true);
|
|
1494
1622
|
}
|
|
1495
|
-
|
|
1623
|
+
reportMessage(t("saved"));
|
|
1496
1624
|
} catch (cause) {
|
|
1497
|
-
|
|
1625
|
+
reportError(cause instanceof Error ? cause.message : String(cause));
|
|
1498
1626
|
} finally {
|
|
1499
1627
|
setSaving(false);
|
|
1500
1628
|
}
|
|
1501
1629
|
};
|
|
1502
1630
|
const keyStatus = configured === void 0 ? t("checkingKey") : configured ? t("keyConfigured") : t("keyNotConfigured");
|
|
1503
|
-
const workflowStatus =
|
|
1631
|
+
const workflowStatus = activeWorkflow.length > 0 ? t("workflowImported", { name: activeWorkflow }) : t("workflowMissing");
|
|
1504
1632
|
const importWorkflow = async (event) => {
|
|
1505
1633
|
const file = event.target.files?.[0];
|
|
1506
1634
|
event.target.value = "";
|
|
1507
1635
|
if (file === void 0) return;
|
|
1508
|
-
|
|
1636
|
+
reportMessage("");
|
|
1509
1637
|
try {
|
|
1510
1638
|
if (file.size > 5242880) throw new Error(t("workflowTooLarge"));
|
|
1511
1639
|
const json = await file.text();
|
|
1512
1640
|
validateComfyUIWorkflowJson(json);
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1641
|
+
const name = uniqueComfyUIWorkflowName(file.name, workflows.map((entry) => entry.name));
|
|
1642
|
+
setWorkflows((current) => [...current, {
|
|
1643
|
+
name,
|
|
1644
|
+
json
|
|
1645
|
+
}]);
|
|
1646
|
+
setActiveWorkflow((current) => current.length > 0 ? current : name);
|
|
1647
|
+
reportMessage(t("workflowImported", { name }));
|
|
1516
1648
|
} catch (cause) {
|
|
1517
|
-
|
|
1649
|
+
reportError(cause instanceof Error ? cause.message : String(cause));
|
|
1518
1650
|
}
|
|
1519
1651
|
};
|
|
1652
|
+
/** Renaming the active entry keeps the active selection following its new name. */
|
|
1653
|
+
const renameWorkflow = (index, name) => {
|
|
1654
|
+
const previous = workflows[index];
|
|
1655
|
+
setWorkflows((current) => current.map((entry, position) => position === index ? {
|
|
1656
|
+
...entry,
|
|
1657
|
+
name
|
|
1658
|
+
} : entry));
|
|
1659
|
+
if (previous !== void 0 && previous.name === activeWorkflow) setActiveWorkflow(name);
|
|
1660
|
+
};
|
|
1661
|
+
/** Removing the active entry moves the selection to the first remaining workflow. */
|
|
1662
|
+
const removeWorkflow = (index) => {
|
|
1663
|
+
const previous = workflows[index];
|
|
1664
|
+
const next = workflows.filter((_entry, position) => position !== index);
|
|
1665
|
+
setWorkflows(next);
|
|
1666
|
+
if (previous !== void 0 && previous.name === activeWorkflow) setActiveWorkflow(next[0]?.name ?? "");
|
|
1667
|
+
};
|
|
1668
|
+
/** Editing one entry's preset leaves the rest of the entry untouched. */
|
|
1669
|
+
const setWorkflowPreset = (index, presetPrompt) => {
|
|
1670
|
+
setWorkflows((current) => current.map((entry, position) => position === index ? {
|
|
1671
|
+
...entry,
|
|
1672
|
+
presetPrompt
|
|
1673
|
+
} : entry));
|
|
1674
|
+
};
|
|
1520
1675
|
return (0, react_jsx_runtime.jsxs)("li", {
|
|
1521
1676
|
className: `dsh-ig-card ${open ? "dsh-ig-card-open" : ""}`,
|
|
1522
1677
|
children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
@@ -1689,13 +1844,60 @@ window.__ModuleLoader__.load({
|
|
|
1689
1844
|
onChange: (event) => {
|
|
1690
1845
|
importWorkflow(event);
|
|
1691
1846
|
}
|
|
1692
|
-
}),
|
|
1693
|
-
}), (0, react_jsx_runtime.jsx)("span", {
|
|
1847
|
+
}), t("workflowImport")]
|
|
1848
|
+
}), workflows.length === 0 ? (0, react_jsx_runtime.jsx)("span", {
|
|
1694
1849
|
className: "dsh-ig-file-name",
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
})]
|
|
1850
|
+
children: t("workflowMissing")
|
|
1851
|
+
}) : null]
|
|
1698
1852
|
}),
|
|
1853
|
+
workflows.length > 0 ? (0, react_jsx_runtime.jsx)("ul", {
|
|
1854
|
+
className: "dsh-ig-workflow-list",
|
|
1855
|
+
children: workflows.map((entry, index) => (0, react_jsx_runtime.jsxs)("li", {
|
|
1856
|
+
className: "dsh-ig-workflow-row",
|
|
1857
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
1858
|
+
className: "dsh-ig-workflow-main",
|
|
1859
|
+
children: [
|
|
1860
|
+
(0, react_jsx_runtime.jsx)("label", {
|
|
1861
|
+
className: "dsh-ig-workflow-active",
|
|
1862
|
+
title: t("workflowActiveTitle"),
|
|
1863
|
+
children: (0, react_jsx_runtime.jsx)("input", {
|
|
1864
|
+
type: "radio",
|
|
1865
|
+
name: "dsh-ig-active-workflow",
|
|
1866
|
+
"aria-label": t("workflowActiveTitle"),
|
|
1867
|
+
checked: entry.name === activeWorkflow,
|
|
1868
|
+
onChange: () => {
|
|
1869
|
+
setActiveWorkflow(entry.name);
|
|
1870
|
+
}
|
|
1871
|
+
})
|
|
1872
|
+
}),
|
|
1873
|
+
(0, react_jsx_runtime.jsx)("input", {
|
|
1874
|
+
className: "dsh-ig-input dsh-ig-workflow-name",
|
|
1875
|
+
value: entry.name,
|
|
1876
|
+
title: entry.name,
|
|
1877
|
+
onChange: (event) => {
|
|
1878
|
+
renameWorkflow(index, event.target.value);
|
|
1879
|
+
}
|
|
1880
|
+
}),
|
|
1881
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1882
|
+
type: "button",
|
|
1883
|
+
className: "dsh-ig-btn-reset",
|
|
1884
|
+
onClick: () => {
|
|
1885
|
+
removeWorkflow(index);
|
|
1886
|
+
},
|
|
1887
|
+
children: t("workflowRemove")
|
|
1888
|
+
})
|
|
1889
|
+
]
|
|
1890
|
+
}), (0, react_jsx_runtime.jsx)("input", {
|
|
1891
|
+
className: "dsh-ig-input dsh-ig-workflow-preset",
|
|
1892
|
+
value: entry.presetPrompt ?? "",
|
|
1893
|
+
placeholder: t("workflowPresetPlaceholder"),
|
|
1894
|
+
title: t("workflowPresetTitle"),
|
|
1895
|
+
onChange: (event) => {
|
|
1896
|
+
setWorkflowPreset(index, event.target.value);
|
|
1897
|
+
}
|
|
1898
|
+
})]
|
|
1899
|
+
}, String(index)))
|
|
1900
|
+
}) : null,
|
|
1699
1901
|
(0, react_jsx_runtime.jsx)("span", {
|
|
1700
1902
|
className: "dsh-ig-hint",
|
|
1701
1903
|
children: t("workflowHint")
|
|
@@ -1769,13 +1971,13 @@ window.__ModuleLoader__.load({
|
|
|
1769
1971
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
1770
1972
|
className: "dsh-ig-actions",
|
|
1771
1973
|
children: [(0, react_jsx_runtime.jsx)("p", {
|
|
1772
|
-
className: "dsh-ig-status"
|
|
1974
|
+
className: `dsh-ig-status${messageIsError ? " dsh-ig-status-error" : ""}`,
|
|
1773
1975
|
role: "status",
|
|
1774
1976
|
children: message || (provider === "comfyui" ? workflowStatus : keyStatus)
|
|
1775
1977
|
}), (0, react_jsx_runtime.jsx)("button", {
|
|
1776
1978
|
className: "dsh-ig-save",
|
|
1777
1979
|
type: "submit",
|
|
1778
|
-
disabled: saving || !snapshot.writable || provider === "comfyui" &&
|
|
1980
|
+
disabled: saving || !snapshot.writable || provider === "comfyui" && workflows.length === 0,
|
|
1779
1981
|
children: saving ? t("saving") : t("save")
|
|
1780
1982
|
})]
|
|
1781
1983
|
})
|
|
@@ -1831,7 +2033,8 @@ window.__ModuleLoader__.load({
|
|
|
1831
2033
|
prompt: result.prompt,
|
|
1832
2034
|
provider: result.provider,
|
|
1833
2035
|
model: result.model,
|
|
1834
|
-
output: result.output
|
|
2036
|
+
output: result.output,
|
|
2037
|
+
...result.seed !== void 0 ? { seed: result.seed } : {}
|
|
1835
2038
|
});
|
|
1836
2039
|
}, [result?.attachment.attachmentId]);
|
|
1837
2040
|
(0, react.useEffect)(() => {
|
|
@@ -2055,11 +2258,12 @@ window.__ModuleLoader__.load({
|
|
|
2055
2258
|
provider: typeof meta?.provider === "string" ? meta.provider : "google",
|
|
2056
2259
|
model: typeof meta?.model === "string" ? meta.model : "",
|
|
2057
2260
|
output: typeof meta?.output === "string" ? meta.output : "",
|
|
2058
|
-
...typeof meta?.savedTo === "string" ? { savedTo: meta.savedTo } : {}
|
|
2261
|
+
...typeof meta?.savedTo === "string" ? { savedTo: meta.savedTo } : {},
|
|
2262
|
+
...typeof meta?.seed === "number" ? { seed: meta.seed } : {}
|
|
2059
2263
|
};
|
|
2060
2264
|
}
|
|
2061
2265
|
function modelOf(provider, value) {
|
|
2062
|
-
const stored = provider === "google" ? value?.googleModel : provider === "openai" ? value?.openaiModel : provider === "seedream" ? value?.seedreamModel : provider === "dashscope" ? value?.dashscopeModel : value?.
|
|
2266
|
+
const stored = provider === "google" ? value?.googleModel : provider === "openai" ? value?.openaiModel : provider === "seedream" ? value?.seedreamModel : provider === "dashscope" ? value?.dashscopeModel : activeComfyUIWorkflow(value ?? {})?.name;
|
|
2063
2267
|
return typeof stored === "string" && stored.length > 0 ? stored : DEFAULT_MODELS[provider];
|
|
2064
2268
|
}
|
|
2065
2269
|
function baseURLOf(provider, value) {
|