dsh-image-gen 0.2.3 → 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 +458 -70
- 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 +70 -0
- package/lib/types/client/index.d.ts +16 -2
- 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");
|
|
@@ -52,20 +108,32 @@ window.__ModuleLoader__.load({
|
|
|
52
108
|
}
|
|
53
109
|
if (!isRecord(value) || Object.keys(value).length === 0) throw new Error("ComfyUI workflow must be a non-empty API-format JSON object");
|
|
54
110
|
if (!Object.values(value).some((node) => {
|
|
55
|
-
const inputs = record(node)?.inputs;
|
|
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
|
}
|
|
65
133
|
function isRecord(value) {
|
|
66
134
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
67
135
|
}
|
|
68
|
-
function record(value) {
|
|
136
|
+
function record$1(value) {
|
|
69
137
|
return isRecord(value) ? value : void 0;
|
|
70
138
|
}
|
|
71
139
|
//#endregion
|
|
@@ -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
|
})]
|
|
@@ -941,6 +1015,146 @@ window.__ModuleLoader__.load({
|
|
|
941
1015
|
if (fromBlockContent?.type === "image") return fromBlockContent.attachment;
|
|
942
1016
|
}
|
|
943
1017
|
//#endregion
|
|
1018
|
+
//#region lib/types/client/image-result-node.js
|
|
1019
|
+
const IMAGE_RESULT_NODE_KIND = "dsh-image-result";
|
|
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;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
/** Parse the plugin-owned durable presentation metadata from a Tool result event. */
|
|
1108
|
+
function imageResultFromMeta(value) {
|
|
1109
|
+
const meta = record(value);
|
|
1110
|
+
if (meta?.kind !== "dsh-image-gen") return void 0;
|
|
1111
|
+
const attachment = imageAttachment(meta.attachment);
|
|
1112
|
+
if (attachment === void 0) return void 0;
|
|
1113
|
+
return {
|
|
1114
|
+
attachment,
|
|
1115
|
+
prompt: stringValue(meta.prompt, "Generated Image"),
|
|
1116
|
+
provider: stringValue(meta.provider, "google"),
|
|
1117
|
+
model: stringValue(meta.model, ""),
|
|
1118
|
+
output: stringValue(meta.output, ""),
|
|
1119
|
+
...typeof meta.savedTo === "string" ? { savedTo: meta.savedTo } : {},
|
|
1120
|
+
...typeof meta.seed === "number" ? { seed: meta.seed } : {}
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
function eventTurn(event) {
|
|
1124
|
+
const turn = event.data.turn;
|
|
1125
|
+
return typeof turn === "number" && Number.isSafeInteger(turn) && turn >= 0 ? turn : void 0;
|
|
1126
|
+
}
|
|
1127
|
+
function imageAttachment(value) {
|
|
1128
|
+
const candidate = record(value);
|
|
1129
|
+
if (candidate === void 0 || typeof candidate.attachmentId !== "string" || !isImageMediaType(candidate.mediaType) || !positiveInteger(candidate.bytes) || !positiveInteger(candidate.width) || !positiveInteger(candidate.height)) return void 0;
|
|
1130
|
+
const original = record(candidate.originalDimensions);
|
|
1131
|
+
if (candidate.originalDimensions !== void 0 && (original === void 0 || !positiveInteger(original.width) || !positiveInteger(original.height))) return void 0;
|
|
1132
|
+
return {
|
|
1133
|
+
attachmentId: candidate.attachmentId,
|
|
1134
|
+
mediaType: candidate.mediaType,
|
|
1135
|
+
bytes: candidate.bytes,
|
|
1136
|
+
width: candidate.width,
|
|
1137
|
+
height: candidate.height,
|
|
1138
|
+
...typeof candidate.name === "string" ? { name: candidate.name } : {},
|
|
1139
|
+
...original === void 0 ? {} : { originalDimensions: {
|
|
1140
|
+
width: original.width,
|
|
1141
|
+
height: original.height
|
|
1142
|
+
} }
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
function isImageMediaType(value) {
|
|
1146
|
+
return value === "image/png" || value === "image/jpeg" || value === "image/webp" || value === "image/gif";
|
|
1147
|
+
}
|
|
1148
|
+
function positiveInteger(value) {
|
|
1149
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
1150
|
+
}
|
|
1151
|
+
function stringValue(value, fallback) {
|
|
1152
|
+
return typeof value === "string" ? value : fallback;
|
|
1153
|
+
}
|
|
1154
|
+
function record(value) {
|
|
1155
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
1156
|
+
}
|
|
1157
|
+
//#endregion
|
|
944
1158
|
//#region lib/types/client/index.js
|
|
945
1159
|
const KEY_REF = {
|
|
946
1160
|
google: "GEMINI_API_KEY",
|
|
@@ -970,13 +1184,18 @@ window.__ModuleLoader__.load({
|
|
|
970
1184
|
endpointHintDashScope: "阿里云百炼 DashScope 官方接口地址。",
|
|
971
1185
|
endpointHintComfyUI: "正在运行且 DSH Host 可以访问的 ComfyUI 地址,默认使用本机 8188 端口。",
|
|
972
1186
|
model: "模型",
|
|
973
|
-
workflow: "API Workflow",
|
|
1187
|
+
workflow: "API Workflow 工作流",
|
|
974
1188
|
workflowImport: "导入 JSON 文件",
|
|
975
|
-
workflowReplace: "替换 JSON 文件",
|
|
976
1189
|
workflowMissing: "尚未导入工作流",
|
|
977
1190
|
workflowImported: "已导入 {name}",
|
|
978
|
-
workflowHint: "
|
|
1191
|
+
workflowHint: "从 ComfyUI 导出 API Format JSON,在提示词输入写入 {{prompt}},种子可用 {{seed}};图生图工作流在 LoadImage 的 image 输入写入 {{image}}(仅一次)。可导入多个工作流,Agent 也能在调用时按名称指定。",
|
|
979
1192
|
workflowTooLarge: "工作流文件不能超过 5 MB。",
|
|
1193
|
+
workflowActiveTitle: "设为当前使用的工作流",
|
|
1194
|
+
workflowRemove: "删除",
|
|
1195
|
+
workflowPresetPlaceholder: "预设提示词,留空则只用对话内容",
|
|
1196
|
+
workflowPresetTitle: "预设提示词:每次调用此工作流时自动加在用户提示词前面。",
|
|
1197
|
+
workflowNameRequired: "工作流名称不能为空。",
|
|
1198
|
+
workflowDuplicateName: "工作流名称不能重复。",
|
|
980
1199
|
timeout: "生成超时(秒)",
|
|
981
1200
|
timeoutHint: "包括提交、等待和下载图片;默认 300 秒。",
|
|
982
1201
|
saveToWorkspace: "保存到工作区",
|
|
@@ -994,6 +1213,7 @@ window.__ModuleLoader__.load({
|
|
|
994
1213
|
loading: "正在加载图片…",
|
|
995
1214
|
loadFailed: "图片读取失败 ({status})",
|
|
996
1215
|
generatedTitle: "已生成图片",
|
|
1216
|
+
resultShown: "图片结果已显示在对话中",
|
|
997
1217
|
copyImg: "复制图片",
|
|
998
1218
|
download: "下载图片",
|
|
999
1219
|
openNewTab: "新标签页打开",
|
|
@@ -1021,13 +1241,18 @@ window.__ModuleLoader__.load({
|
|
|
1021
1241
|
endpointHintDashScope: "Official Aliyun DashScope endpoint.",
|
|
1022
1242
|
endpointHintComfyUI: "A running ComfyUI server reachable by the DSH Host; the default points to port 8188 on this computer.",
|
|
1023
1243
|
model: "Model",
|
|
1024
|
-
workflow: "API
|
|
1244
|
+
workflow: "API Workflows",
|
|
1025
1245
|
workflowImport: "Import JSON file",
|
|
1026
|
-
workflowReplace: "Replace JSON file",
|
|
1027
1246
|
workflowMissing: "No workflow imported",
|
|
1028
1247
|
workflowImported: "Imported {name}",
|
|
1029
|
-
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.",
|
|
1030
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.",
|
|
1031
1256
|
timeout: "Generation timeout (seconds)",
|
|
1032
1257
|
timeoutHint: "Covers submission, waiting, and image download; defaults to 300 seconds.",
|
|
1033
1258
|
saveToWorkspace: "Save to workspace",
|
|
@@ -1045,6 +1270,7 @@ window.__ModuleLoader__.load({
|
|
|
1045
1270
|
loading: "Loading image…",
|
|
1046
1271
|
loadFailed: "Failed to load image ({status})",
|
|
1047
1272
|
generatedTitle: "Generated image",
|
|
1273
|
+
resultShown: "Image result is shown in the conversation",
|
|
1048
1274
|
copyImg: "Copy Image",
|
|
1049
1275
|
download: "Download Image",
|
|
1050
1276
|
openNewTab: "Open in new tab",
|
|
@@ -1075,9 +1301,16 @@ window.__ModuleLoader__.load({
|
|
|
1075
1301
|
.dsh-ig-file-button:hover{background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
|
|
1076
1302
|
.dsh-ig-file-button:focus-within{outline:2px solid var(--dsw-alias-brand-primary,#4c78ff);outline-offset:2px}
|
|
1077
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}
|
|
1078
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}
|
|
1079
1311
|
.dsh-ig-btn-reset:hover{background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
|
|
1080
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}
|
|
1081
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)}
|
|
1082
1315
|
.dsh-ig-check-row{display:flex;align-items:center;gap:8px;cursor:pointer}
|
|
1083
1316
|
.dsh-ig-check-row input[type=checkbox]{width:15px;height:15px;accent-color:var(--dsw-alias-brand-primary,#4c78ff);margin:0}
|
|
@@ -1086,6 +1319,7 @@ window.__ModuleLoader__.load({
|
|
|
1086
1319
|
.dsh-ig-save:disabled{opacity:.4;cursor:default}
|
|
1087
1320
|
|
|
1088
1321
|
.dsh-ig-result{display:grid;gap:10px;max-width:520px}
|
|
1322
|
+
.dsh-ig-promoted-results{display:grid;gap:16px}
|
|
1089
1323
|
.dsh-ig-result-title{font-size:14px;font-weight:600}
|
|
1090
1324
|
.dsh-ig-container{position:relative;display:inline-block;width:fit-content;max-width:100%;justify-self:start;border-radius:12px;overflow:hidden;line-height:0}
|
|
1091
1325
|
.dsh-ig-container:hover .dsh-ig-toolbar{opacity:1;pointer-events:auto}
|
|
@@ -1165,7 +1399,10 @@ window.__ModuleLoader__.load({
|
|
|
1165
1399
|
/** Mount the settings card, generated-image card, and native conversation gallery view. */
|
|
1166
1400
|
function apply(ctx) {
|
|
1167
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";
|
|
1168
1404
|
const locale = ctx.get("locale");
|
|
1405
|
+
const promotion = { enabled: false };
|
|
1169
1406
|
ctx.effect(() => {
|
|
1170
1407
|
const style = document.createElement("style");
|
|
1171
1408
|
style.dataset.plugin = "dsh-image-gen";
|
|
@@ -1176,6 +1413,18 @@ window.__ModuleLoader__.load({
|
|
|
1176
1413
|
};
|
|
1177
1414
|
}, "dsh-image-gen: styles");
|
|
1178
1415
|
const register = ctx.slots.register.bind(ctx.slots);
|
|
1416
|
+
ctx.inject(["uiConversation"], (owner) => {
|
|
1417
|
+
const uiConversation = asModernUiConversation(owner.get("uiConversation"));
|
|
1418
|
+
if (uiConversation === void 0) throw new Error("dsh-image-gen: uiConversation has an incompatible interface");
|
|
1419
|
+
promotion.enabled = true;
|
|
1420
|
+
const ownerRegister = owner.slots.register.bind(owner.slots);
|
|
1421
|
+
owner.effect(() => uiConversation.events.register(createImageResultDefinition({ isCompactTranscript })), "dsh-image-gen: promoted image result node");
|
|
1422
|
+
owner.slots.inject("conversation.chat.node", () => ownerRegister({
|
|
1423
|
+
name: "conversation.chat.node",
|
|
1424
|
+
key: IMAGE_RESULT_NODE_KIND,
|
|
1425
|
+
inject: () => ({ locale })
|
|
1426
|
+
}, PromotedImageResultNode));
|
|
1427
|
+
});
|
|
1179
1428
|
const injectSettingsItem = (owner, credentials) => {
|
|
1180
1429
|
const ownerRegister = owner.slots.register.bind(owner.slots);
|
|
1181
1430
|
owner.slots.inject("settings.plugin.item", () => ownerRegister({
|
|
@@ -1200,12 +1449,18 @@ window.__ModuleLoader__.load({
|
|
|
1200
1449
|
ctx.slots.inject("tool.call.toolview", () => register({
|
|
1201
1450
|
name: "tool.call.toolview",
|
|
1202
1451
|
key: "generate_image",
|
|
1203
|
-
inject: () => ({
|
|
1452
|
+
inject: () => ({
|
|
1453
|
+
locale,
|
|
1454
|
+
promoted: promotion.enabled
|
|
1455
|
+
})
|
|
1204
1456
|
}, GeneratedImageCard));
|
|
1205
1457
|
ctx.slots.inject("tool.call.toolview", () => register({
|
|
1206
1458
|
name: "tool.call.toolview",
|
|
1207
1459
|
key: "edit_image",
|
|
1208
|
-
inject: () => ({
|
|
1460
|
+
inject: () => ({
|
|
1461
|
+
locale,
|
|
1462
|
+
promoted: promotion.enabled
|
|
1463
|
+
})
|
|
1209
1464
|
}, GeneratedImageCard));
|
|
1210
1465
|
ctx.slots.inject("conversation.view", () => register({
|
|
1211
1466
|
name: "conversation.view",
|
|
@@ -1217,6 +1472,12 @@ window.__ModuleLoader__.load({
|
|
|
1217
1472
|
inject: () => ({ locale })
|
|
1218
1473
|
}, GalleryViewTab));
|
|
1219
1474
|
}
|
|
1475
|
+
function asModernUiConversation(value) {
|
|
1476
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
1477
|
+
const events = value.events;
|
|
1478
|
+
if (events === null || typeof events !== "object") return void 0;
|
|
1479
|
+
return typeof events.register === "function" ? value : void 0;
|
|
1480
|
+
}
|
|
1220
1481
|
function asCredentialsRemote(value) {
|
|
1221
1482
|
if (value === null || typeof value !== "object") return void 0;
|
|
1222
1483
|
const candidate = value;
|
|
@@ -1255,8 +1516,8 @@ window.__ModuleLoader__.load({
|
|
|
1255
1516
|
const [provider, setProvider] = (0, react.useState)("google");
|
|
1256
1517
|
const [model, setModel] = (0, react.useState)("");
|
|
1257
1518
|
const [baseURL, setBaseURL] = (0, react.useState)("");
|
|
1258
|
-
const [
|
|
1259
|
-
const [
|
|
1519
|
+
const [workflows, setWorkflows] = (0, react.useState)([]);
|
|
1520
|
+
const [activeWorkflow, setActiveWorkflow] = (0, react.useState)("");
|
|
1260
1521
|
const [timeoutSeconds, setTimeoutSeconds] = (0, react.useState)(DEFAULT_COMFYUI_TIMEOUT_MS / 1e3);
|
|
1261
1522
|
const [saveToWorkspace, setSaveToWorkspace] = (0, react.useState)(true);
|
|
1262
1523
|
const [workspaceFolder, setWorkspaceFolder] = (0, react.useState)("dsh-image-gen");
|
|
@@ -1264,6 +1525,15 @@ window.__ModuleLoader__.load({
|
|
|
1264
1525
|
const [configured, setConfigured] = (0, react.useState)();
|
|
1265
1526
|
const [saving, setSaving] = (0, react.useState)(false);
|
|
1266
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
|
+
};
|
|
1267
1537
|
(0, react.useEffect)(() => props.scope.subscribe(() => {
|
|
1268
1538
|
setSnapshot(props.scope.getSnapshot());
|
|
1269
1539
|
}), [props.scope]);
|
|
@@ -1290,8 +1560,8 @@ window.__ModuleLoader__.load({
|
|
|
1290
1560
|
setProvider(next);
|
|
1291
1561
|
setModel(modelOf(next, value));
|
|
1292
1562
|
setBaseURL(baseURLOf(next, value));
|
|
1293
|
-
|
|
1294
|
-
|
|
1563
|
+
setWorkflows(resolveComfyUIWorkflows(value ?? {}));
|
|
1564
|
+
setActiveWorkflow(activeComfyUIWorkflow(value ?? {})?.name ?? "");
|
|
1295
1565
|
setTimeoutSeconds(Math.max(1, Math.round((value?.comfyuiTimeoutMs ?? 3e5) / 1e3)));
|
|
1296
1566
|
setSaveToWorkspace(value?.saveToWorkspace ?? true);
|
|
1297
1567
|
setWorkspaceFolder(value?.workspaceFolder ?? "dsh-image-gen");
|
|
@@ -1319,10 +1589,22 @@ window.__ModuleLoader__.load({
|
|
|
1319
1589
|
try {
|
|
1320
1590
|
await props.scope.set("provider", provider);
|
|
1321
1591
|
if (provider === "comfyui") {
|
|
1322
|
-
|
|
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];
|
|
1323
1603
|
await props.scope.set("comfyuiBaseURL", baseURL);
|
|
1324
|
-
await props.scope.set("
|
|
1325
|
-
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);
|
|
1326
1608
|
await props.scope.set("comfyuiTimeoutMs", Math.max(1, Math.round(timeoutSeconds)) * 1e3);
|
|
1327
1609
|
} else {
|
|
1328
1610
|
await props.scope.set(provider === "google" ? "googleModel" : provider === "openai" ? "openaiModel" : provider === "seedream" ? "seedreamModel" : "dashscopeModel", model);
|
|
@@ -1338,31 +1620,58 @@ window.__ModuleLoader__.load({
|
|
|
1338
1620
|
setKey("");
|
|
1339
1621
|
setConfigured(true);
|
|
1340
1622
|
}
|
|
1341
|
-
|
|
1623
|
+
reportMessage(t("saved"));
|
|
1342
1624
|
} catch (cause) {
|
|
1343
|
-
|
|
1625
|
+
reportError(cause instanceof Error ? cause.message : String(cause));
|
|
1344
1626
|
} finally {
|
|
1345
1627
|
setSaving(false);
|
|
1346
1628
|
}
|
|
1347
1629
|
};
|
|
1348
1630
|
const keyStatus = configured === void 0 ? t("checkingKey") : configured ? t("keyConfigured") : t("keyNotConfigured");
|
|
1349
|
-
const workflowStatus =
|
|
1631
|
+
const workflowStatus = activeWorkflow.length > 0 ? t("workflowImported", { name: activeWorkflow }) : t("workflowMissing");
|
|
1350
1632
|
const importWorkflow = async (event) => {
|
|
1351
1633
|
const file = event.target.files?.[0];
|
|
1352
1634
|
event.target.value = "";
|
|
1353
1635
|
if (file === void 0) return;
|
|
1354
|
-
|
|
1636
|
+
reportMessage("");
|
|
1355
1637
|
try {
|
|
1356
1638
|
if (file.size > 5242880) throw new Error(t("workflowTooLarge"));
|
|
1357
1639
|
const json = await file.text();
|
|
1358
1640
|
validateComfyUIWorkflowJson(json);
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
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 }));
|
|
1362
1648
|
} catch (cause) {
|
|
1363
|
-
|
|
1649
|
+
reportError(cause instanceof Error ? cause.message : String(cause));
|
|
1364
1650
|
}
|
|
1365
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
|
+
};
|
|
1366
1675
|
return (0, react_jsx_runtime.jsxs)("li", {
|
|
1367
1676
|
className: `dsh-ig-card ${open ? "dsh-ig-card-open" : ""}`,
|
|
1368
1677
|
children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
@@ -1535,13 +1844,60 @@ window.__ModuleLoader__.load({
|
|
|
1535
1844
|
onChange: (event) => {
|
|
1536
1845
|
importWorkflow(event);
|
|
1537
1846
|
}
|
|
1538
|
-
}),
|
|
1539
|
-
}), (0, react_jsx_runtime.jsx)("span", {
|
|
1847
|
+
}), t("workflowImport")]
|
|
1848
|
+
}), workflows.length === 0 ? (0, react_jsx_runtime.jsx)("span", {
|
|
1540
1849
|
className: "dsh-ig-file-name",
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
})]
|
|
1850
|
+
children: t("workflowMissing")
|
|
1851
|
+
}) : null]
|
|
1544
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,
|
|
1545
1901
|
(0, react_jsx_runtime.jsx)("span", {
|
|
1546
1902
|
className: "dsh-ig-hint",
|
|
1547
1903
|
children: t("workflowHint")
|
|
@@ -1615,13 +1971,13 @@ window.__ModuleLoader__.load({
|
|
|
1615
1971
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
1616
1972
|
className: "dsh-ig-actions",
|
|
1617
1973
|
children: [(0, react_jsx_runtime.jsx)("p", {
|
|
1618
|
-
className: "dsh-ig-status"
|
|
1974
|
+
className: `dsh-ig-status${messageIsError ? " dsh-ig-status-error" : ""}`,
|
|
1619
1975
|
role: "status",
|
|
1620
1976
|
children: message || (provider === "comfyui" ? workflowStatus : keyStatus)
|
|
1621
1977
|
}), (0, react_jsx_runtime.jsx)("button", {
|
|
1622
1978
|
className: "dsh-ig-save",
|
|
1623
1979
|
type: "submit",
|
|
1624
|
-
disabled: saving || !snapshot.writable || provider === "comfyui" &&
|
|
1980
|
+
disabled: saving || !snapshot.writable || provider === "comfyui" && workflows.length === 0,
|
|
1625
1981
|
children: saving ? t("saving") : t("save")
|
|
1626
1982
|
})]
|
|
1627
1983
|
})
|
|
@@ -1629,43 +1985,58 @@ window.__ModuleLoader__.load({
|
|
|
1629
1985
|
}) : null]
|
|
1630
1986
|
});
|
|
1631
1987
|
}
|
|
1632
|
-
/**
|
|
1988
|
+
/** Keep the legacy Tool row for old DSH and hand modern results to the independent Chat node. */
|
|
1633
1989
|
function GeneratedImageCard(props) {
|
|
1634
|
-
const
|
|
1635
|
-
|
|
1990
|
+
const result = imageResultFromBlock(props.block);
|
|
1991
|
+
if (props.promoted && result !== void 0) return (0, react_jsx_runtime.jsx)(PromotedResultNotice, { locale: props.locale });
|
|
1992
|
+
return (0, react_jsx_runtime.jsx)(ImageResultCard, {
|
|
1993
|
+
result,
|
|
1994
|
+
locale: props.locale
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1997
|
+
/** Render modern image artifacts as final conversation output instead of Tool process content. */
|
|
1998
|
+
function PromotedImageResultNode(props) {
|
|
1999
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
2000
|
+
className: "dsh-ig-promoted-results",
|
|
2001
|
+
children: props.node.data.results.map((result) => (0, react_jsx_runtime.jsx)(ImageResultCard, {
|
|
2002
|
+
result,
|
|
2003
|
+
locale: props.locale
|
|
2004
|
+
}, result.attachment.attachmentId))
|
|
2005
|
+
});
|
|
2006
|
+
}
|
|
2007
|
+
function PromotedResultNotice({ locale }) {
|
|
2008
|
+
const lang = usePluginLanguage(locale);
|
|
2009
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
2010
|
+
className: "dsh-ig-loading",
|
|
2011
|
+
children: DICT[lang].resultShown
|
|
2012
|
+
});
|
|
2013
|
+
}
|
|
2014
|
+
function ImageResultCard({ result, locale }) {
|
|
2015
|
+
const attachment = result?.attachment;
|
|
2016
|
+
const savedTo = result?.savedTo;
|
|
1636
2017
|
const [url, setUrl] = (0, react.useState)();
|
|
1637
2018
|
const [blob, setBlob] = (0, react.useState)();
|
|
1638
2019
|
const [error, setError] = (0, react.useState)();
|
|
1639
2020
|
const [previewOpen, setPreviewOpen] = (0, react.useState)(false);
|
|
1640
2021
|
const [toast, setToast] = (0, react.useState)();
|
|
1641
|
-
const
|
|
1642
|
-
(0, react.useEffect)(() => {
|
|
1643
|
-
return props.locale?.subscribe?.(() => {
|
|
1644
|
-
setLang(props.locale?.getSnapshot?.()?.active?.startsWith("en") ? "en" : "zh");
|
|
1645
|
-
});
|
|
1646
|
-
}, [props.locale]);
|
|
2022
|
+
const lang = usePluginLanguage(locale);
|
|
1647
2023
|
const t = (keyName, params) => {
|
|
1648
2024
|
let text = (lang === "en" ? DICT.en : DICT.zh)[keyName] || DICT.zh[keyName] || keyName;
|
|
1649
2025
|
if (params) for (const [k, v] of Object.entries(params)) text = text.replace(`{${k}}`, v);
|
|
1650
2026
|
return text;
|
|
1651
2027
|
};
|
|
1652
2028
|
(0, react.useEffect)(() => {
|
|
1653
|
-
if (
|
|
1654
|
-
const blockAny = props.block;
|
|
1655
|
-
const meta = blockAny.meta ?? blockAny.resultView?.meta;
|
|
1656
|
-
const prompt = typeof meta?.prompt === "string" ? meta.prompt : blockAny.call?.args?.prompt ?? "Generated Image";
|
|
1657
|
-
const provider = typeof meta?.provider === "string" ? meta.provider : "google";
|
|
1658
|
-
const model = typeof meta?.model === "string" ? meta.model : "";
|
|
1659
|
-
const output = typeof meta?.output === "string" ? meta.output : "";
|
|
2029
|
+
if (result === void 0) return;
|
|
1660
2030
|
saveGalleryItem({
|
|
1661
|
-
id: attachment.attachmentId,
|
|
1662
|
-
attachment,
|
|
1663
|
-
prompt,
|
|
1664
|
-
provider,
|
|
1665
|
-
model,
|
|
1666
|
-
output
|
|
2031
|
+
id: result.attachment.attachmentId,
|
|
2032
|
+
attachment: result.attachment,
|
|
2033
|
+
prompt: result.prompt,
|
|
2034
|
+
provider: result.provider,
|
|
2035
|
+
model: result.model,
|
|
2036
|
+
output: result.output,
|
|
2037
|
+
...result.seed !== void 0 ? { seed: result.seed } : {}
|
|
1667
2038
|
});
|
|
1668
|
-
}, [attachment
|
|
2039
|
+
}, [result?.attachment.attachmentId]);
|
|
1669
2040
|
(0, react.useEffect)(() => {
|
|
1670
2041
|
if (!previewOpen) return;
|
|
1671
2042
|
const onKeyDown = (e) => {
|
|
@@ -1869,23 +2240,40 @@ window.__ModuleLoader__.load({
|
|
|
1869
2240
|
]
|
|
1870
2241
|
});
|
|
1871
2242
|
}
|
|
2243
|
+
function usePluginLanguage(locale) {
|
|
2244
|
+
const [lang, setLang] = (0, react.useState)(() => locale?.getSnapshot?.()?.active?.startsWith("en") ? "en" : "zh");
|
|
2245
|
+
(0, react.useEffect)(() => locale?.subscribe?.(() => {
|
|
2246
|
+
setLang(locale?.getSnapshot?.()?.active?.startsWith("en") ? "en" : "zh");
|
|
2247
|
+
}), [locale]);
|
|
2248
|
+
return lang;
|
|
2249
|
+
}
|
|
2250
|
+
function imageResultFromBlock(block) {
|
|
2251
|
+
const attachment = imageRef(block);
|
|
2252
|
+
if (attachment === void 0) return void 0;
|
|
2253
|
+
const blockValue = block;
|
|
2254
|
+
const meta = blockValue.meta ?? blockValue.resultView?.meta;
|
|
2255
|
+
return {
|
|
2256
|
+
attachment,
|
|
2257
|
+
prompt: typeof meta?.prompt === "string" ? meta.prompt : blockValue.call?.args?.prompt ?? "Generated Image",
|
|
2258
|
+
provider: typeof meta?.provider === "string" ? meta.provider : "google",
|
|
2259
|
+
model: typeof meta?.model === "string" ? meta.model : "",
|
|
2260
|
+
output: typeof meta?.output === "string" ? meta.output : "",
|
|
2261
|
+
...typeof meta?.savedTo === "string" ? { savedTo: meta.savedTo } : {},
|
|
2262
|
+
...typeof meta?.seed === "number" ? { seed: meta.seed } : {}
|
|
2263
|
+
};
|
|
2264
|
+
}
|
|
1872
2265
|
function modelOf(provider, value) {
|
|
1873
|
-
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;
|
|
1874
2267
|
return typeof stored === "string" && stored.length > 0 ? stored : DEFAULT_MODELS[provider];
|
|
1875
2268
|
}
|
|
1876
2269
|
function baseURLOf(provider, value) {
|
|
1877
2270
|
const stored = provider === "google" ? value?.googleEndpoint : provider === "openai" ? value?.openaiBaseURL : provider === "seedream" ? value?.seedreamBaseURL : provider === "dashscope" ? value?.dashscopeEndpoint : value?.comfyuiBaseURL;
|
|
1878
2271
|
return typeof stored === "string" && stored.length > 0 ? stored : DEFAULT_BASE_URLS[provider];
|
|
1879
2272
|
}
|
|
1880
|
-
/** The workspace file path a completed image call saved, when the result meta carries one. */
|
|
1881
|
-
function imageSavedTo(block) {
|
|
1882
|
-
if (!("kind" in block)) return void 0;
|
|
1883
|
-
const meta = block.meta ?? block.resultView?.meta;
|
|
1884
|
-
return typeof meta?.savedTo === "string" ? meta.savedTo : void 0;
|
|
1885
|
-
}
|
|
1886
2273
|
//#endregion
|
|
1887
2274
|
exports.GeneratedImageCard = GeneratedImageCard;
|
|
1888
2275
|
exports.ImageGenerationSettingsCard = ImageGenerationSettingsCard;
|
|
2276
|
+
exports.PromotedImageResultNode = PromotedImageResultNode;
|
|
1889
2277
|
exports.apply = apply;
|
|
1890
2278
|
exports.inject = inject;
|
|
1891
2279
|
return module.exports;
|