dsh-image-gen 0.2.4 → 0.2.6
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 +1812 -492
- package/lib/client.js.map +1 -1
- package/lib/index.js +394 -52
- package/lib/types/client/gallery-store.d.ts +21 -3
- package/lib/types/client/gallery-view.d.ts +105 -10
- 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/image-route.d.ts +8 -1
- package/lib/types/index.d.ts +1 -1
- package/lib/types/shared.d.ts +33 -0
- package/lib/types/workspace-save.d.ts +21 -0
- package/package.json +7 -3
package/lib/client.js
CHANGED
|
@@ -10,6 +10,8 @@ window.__ModuleLoader__.load({
|
|
|
10
10
|
/** Values shared by the Host and browser Bundle faces. */
|
|
11
11
|
/** Browser route used by the generated-image card. */
|
|
12
12
|
const IMAGE_ROUTE = "/plugins/dsh-image-gen/image";
|
|
13
|
+
/** Browser route used for deleting generated images and workspace files. */
|
|
14
|
+
const DELETE_ROUTE = "/plugins/dsh-image-gen/delete";
|
|
13
15
|
/** Namespace persisted through DSH Settings. */
|
|
14
16
|
const IMAGE_GENERATION_NAMESPACE = "image-generation";
|
|
15
17
|
/** Default endpoints and base URLs. */
|
|
@@ -19,12 +21,61 @@ window.__ModuleLoader__.load({
|
|
|
19
21
|
const DEFAULT_DASHSCOPE_ENDPOINT = "https://dashscope.aliyuncs.com/api/v1";
|
|
20
22
|
const DEFAULT_COMFYUI_BASE_URL = "http://127.0.0.1:8188";
|
|
21
23
|
const DEFAULT_COMFYUI_TIMEOUT_MS = 3e5;
|
|
24
|
+
const DEFAULT_COMFYUI_WORKFLOW_LABEL = "API workflow";
|
|
25
|
+
/** Default model names. */
|
|
26
|
+
const DEFAULT_GOOGLE_MODEL = "gemini-3.1-flash-image";
|
|
27
|
+
const DEFAULT_OPENAI_MODEL = "gpt-image-2";
|
|
28
|
+
const DEFAULT_SEEDREAM_MODEL = "doubao-seedream-5-0-260128";
|
|
29
|
+
const DEFAULT_DASHSCOPE_MODEL = "qwen-image-3.0";
|
|
30
|
+
/** Named workflows, falling back to the legacy single-workflow fields when the list is empty. */
|
|
31
|
+
function resolveComfyUIWorkflows(source) {
|
|
32
|
+
const named = [];
|
|
33
|
+
for (const entry of source.comfyuiWorkflows ?? []) {
|
|
34
|
+
const name = typeof entry?.name === "string" ? entry.name.trim() : "";
|
|
35
|
+
const json = typeof entry?.json === "string" ? entry.json : "";
|
|
36
|
+
if (name.length > 0 && json.trim().length > 0) {
|
|
37
|
+
const presetPrompt = typeof entry.presetPrompt === "string" ? entry.presetPrompt.trim() : "";
|
|
38
|
+
named.push(presetPrompt.length > 0 ? {
|
|
39
|
+
name,
|
|
40
|
+
json,
|
|
41
|
+
presetPrompt
|
|
42
|
+
} : {
|
|
43
|
+
name,
|
|
44
|
+
json
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (named.length > 0) return named;
|
|
49
|
+
const legacyJson = typeof source.comfyuiWorkflowJson === "string" ? source.comfyuiWorkflowJson : "";
|
|
50
|
+
if (legacyJson.trim().length === 0) return [];
|
|
51
|
+
const legacyName = typeof source.comfyuiWorkflowName === "string" ? source.comfyuiWorkflowName.trim() : "";
|
|
52
|
+
return [{
|
|
53
|
+
name: legacyName.length > 0 ? legacyName : DEFAULT_COMFYUI_WORKFLOW_LABEL,
|
|
54
|
+
json: legacyJson
|
|
55
|
+
}];
|
|
56
|
+
}
|
|
57
|
+
/** The workflow ComfyUI calls use by default: the configured active name, else the first entry. */
|
|
58
|
+
function activeComfyUIWorkflow(source) {
|
|
59
|
+
const workflows = resolveComfyUIWorkflows(source);
|
|
60
|
+
if (workflows.length === 0) return void 0;
|
|
61
|
+
const activeName = typeof source.comfyuiActiveWorkflow === "string" ? source.comfyuiActiveWorkflow.trim() : "";
|
|
62
|
+
return workflows.find((workflow) => workflow.name === activeName) ?? workflows[0];
|
|
63
|
+
}
|
|
64
|
+
/** Derive a workflow label that does not collide with the given existing names. */
|
|
65
|
+
function uniqueComfyUIWorkflowName(name, existing) {
|
|
66
|
+
const base = name.trim().length > 0 ? name.trim() : DEFAULT_COMFYUI_WORKFLOW_LABEL;
|
|
67
|
+
if (!existing.includes(base)) return base;
|
|
68
|
+
for (let index = 2;; index += 1) {
|
|
69
|
+
const candidate = `${base} (${index})`;
|
|
70
|
+
if (!existing.includes(candidate)) return candidate;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
22
73
|
const DEFAULT_MODELS = {
|
|
23
|
-
google:
|
|
24
|
-
openai:
|
|
25
|
-
seedream:
|
|
26
|
-
dashscope:
|
|
27
|
-
comfyui:
|
|
74
|
+
google: DEFAULT_GOOGLE_MODEL,
|
|
75
|
+
openai: DEFAULT_OPENAI_MODEL,
|
|
76
|
+
seedream: DEFAULT_SEEDREAM_MODEL,
|
|
77
|
+
dashscope: DEFAULT_DASHSCOPE_MODEL,
|
|
78
|
+
comfyui: DEFAULT_COMFYUI_WORKFLOW_LABEL
|
|
28
79
|
};
|
|
29
80
|
const DEFAULT_BASE_URLS = {
|
|
30
81
|
google: DEFAULT_GOOGLE_ENDPOINT,
|
|
@@ -37,9 +88,16 @@ window.__ModuleLoader__.load({
|
|
|
37
88
|
//#region lib/types/comfyui-workflow.js
|
|
38
89
|
/** Pure ComfyUI API-workflow validation and placeholder injection. */
|
|
39
90
|
const COMFYUI_PROMPT_PLACEHOLDER = "{{prompt}}";
|
|
91
|
+
const COMFYUI_IMAGE_PLACEHOLDER = "{{image}}";
|
|
92
|
+
/** Legacy single-percent placeholders from early releases, still accepted. */
|
|
93
|
+
const LEGACY_PROMPT_PLACEHOLDER = "%prompt%";
|
|
94
|
+
const LEGACY_IMAGE_PLACEHOLDER = "%image%";
|
|
95
|
+
/** LoadImage-style input key that receives the uploaded source image name. */
|
|
96
|
+
const IMAGE_INPUT_KEY = "image";
|
|
40
97
|
/** Validate imported JSON without exposing workflow graph details to callers. */
|
|
41
98
|
function validateComfyUIWorkflowJson(workflowJson) {
|
|
42
|
-
parseWorkflow(workflowJson);
|
|
99
|
+
const imageInputs = countImagePlaceholders(parseWorkflow(workflowJson));
|
|
100
|
+
if (imageInputs > 1) throw new Error(`ComfyUI workflow must contain at most one ${COMFYUI_IMAGE_PLACEHOLDER} image input; found ${String(imageInputs)}`);
|
|
43
101
|
}
|
|
44
102
|
function parseWorkflow(workflowJson) {
|
|
45
103
|
if (workflowJson.trim().length === 0) throw new Error("Import a ComfyUI API workflow JSON file in Settings before generating");
|
|
@@ -54,11 +112,23 @@ window.__ModuleLoader__.load({
|
|
|
54
112
|
if (!Object.values(value).some((node) => {
|
|
55
113
|
const inputs = record$1(node)?.inputs;
|
|
56
114
|
return inputs !== void 0 && containsPromptPlaceholder(inputs);
|
|
57
|
-
})) throw new Error(`ComfyUI workflow must contain ${COMFYUI_PROMPT_PLACEHOLDER} in a text input`);
|
|
115
|
+
})) throw new Error(`ComfyUI workflow must contain ${COMFYUI_PROMPT_PLACEHOLDER} (or ${LEGACY_PROMPT_PLACEHOLDER}) in a text input`);
|
|
58
116
|
return value;
|
|
59
117
|
}
|
|
118
|
+
/** Count `inputs.image` fields that are exactly an image placeholder. */
|
|
119
|
+
function countImagePlaceholders(workflow) {
|
|
120
|
+
let count = 0;
|
|
121
|
+
for (const node of Object.values(workflow)) {
|
|
122
|
+
const inputs = record$1(record$1(node)?.inputs);
|
|
123
|
+
if (inputs !== void 0 && isImagePlaceholder(inputs[IMAGE_INPUT_KEY])) count += 1;
|
|
124
|
+
}
|
|
125
|
+
return count;
|
|
126
|
+
}
|
|
127
|
+
function isImagePlaceholder(value) {
|
|
128
|
+
return value === "{{image}}" || value === LEGACY_IMAGE_PLACEHOLDER;
|
|
129
|
+
}
|
|
60
130
|
function containsPromptPlaceholder(value) {
|
|
61
|
-
if (typeof value === "string") return value.includes(
|
|
131
|
+
if (typeof value === "string") return value.includes("{{prompt}}") || value.includes(LEGACY_PROMPT_PLACEHOLDER);
|
|
62
132
|
if (Array.isArray(value)) return value.some(containsPromptPlaceholder);
|
|
63
133
|
return isRecord(value) && Object.values(value).some(containsPromptPlaceholder);
|
|
64
134
|
}
|
|
@@ -142,19 +212,33 @@ window.__ModuleLoader__.load({
|
|
|
142
212
|
/**
|
|
143
213
|
* Save or update a gallery record by attachmentId.
|
|
144
214
|
* Skipped if the item was previously deleted (tombstoned).
|
|
215
|
+
* Preserves existing isFavorite, tags, and original createdAt on re-renders.
|
|
145
216
|
*/
|
|
146
217
|
async function saveGalleryItem(item) {
|
|
147
218
|
try {
|
|
148
219
|
const db = await getDB();
|
|
149
220
|
if ((await loadTombstones(db)).has(item.id)) return;
|
|
150
|
-
const record = {
|
|
151
|
-
...item,
|
|
152
|
-
createdAt: item.createdAt ?? Date.now()
|
|
153
|
-
};
|
|
154
221
|
await new Promise((resolve, reject) => {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
222
|
+
const store = db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME);
|
|
223
|
+
const getReq = store.get(item.id);
|
|
224
|
+
getReq.onsuccess = () => {
|
|
225
|
+
const existing = getReq.result;
|
|
226
|
+
const fav = item.isFavorite !== void 0 ? item.isFavorite : existing?.isFavorite;
|
|
227
|
+
const userTags = item.tags !== void 0 ? item.tags : existing?.tags;
|
|
228
|
+
const savedPath = item.savedTo !== void 0 ? item.savedTo : existing?.savedTo;
|
|
229
|
+
const record = {
|
|
230
|
+
...existing,
|
|
231
|
+
...item,
|
|
232
|
+
createdAt: existing?.createdAt ?? item.createdAt ?? Date.now(),
|
|
233
|
+
...fav !== void 0 ? { isFavorite: fav } : {},
|
|
234
|
+
...userTags !== void 0 ? { tags: userTags } : {},
|
|
235
|
+
...savedPath !== void 0 ? { savedTo: savedPath } : {}
|
|
236
|
+
};
|
|
237
|
+
const putReq = store.put(record);
|
|
238
|
+
putReq.onsuccess = () => resolve();
|
|
239
|
+
putReq.onerror = () => reject(putReq.error);
|
|
240
|
+
};
|
|
241
|
+
getReq.onerror = () => reject(getReq.error);
|
|
158
242
|
});
|
|
159
243
|
notifyListeners();
|
|
160
244
|
} catch (err) {
|
|
@@ -185,51 +269,537 @@ window.__ModuleLoader__.load({
|
|
|
185
269
|
}
|
|
186
270
|
}
|
|
187
271
|
/**
|
|
188
|
-
*
|
|
272
|
+
* Toggle favorite status of a gallery item.
|
|
273
|
+
* Returns the new favorite status (true if favorited, false if unfavorited).
|
|
189
274
|
*/
|
|
190
|
-
async function
|
|
275
|
+
async function toggleFavoriteGalleryItem(id) {
|
|
191
276
|
try {
|
|
192
277
|
const db = await getDB();
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
278
|
+
const newStatus = await new Promise((resolve, reject) => {
|
|
279
|
+
const store = db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME);
|
|
280
|
+
const getReq = store.get(id);
|
|
281
|
+
getReq.onsuccess = () => {
|
|
282
|
+
const item = getReq.result;
|
|
283
|
+
if (!item) {
|
|
284
|
+
resolve(false);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const nextFavorite = !item.isFavorite;
|
|
288
|
+
item.isFavorite = nextFavorite;
|
|
289
|
+
const putReq = store.put(item);
|
|
290
|
+
putReq.onsuccess = () => resolve(nextFavorite);
|
|
291
|
+
putReq.onerror = () => reject(putReq.error);
|
|
292
|
+
};
|
|
293
|
+
getReq.onerror = () => reject(getReq.error);
|
|
205
294
|
});
|
|
206
295
|
notifyListeners();
|
|
296
|
+
return newStatus;
|
|
207
297
|
} catch (err) {
|
|
208
|
-
console.warn("[dsh-image-gen] Failed to
|
|
298
|
+
console.warn("[dsh-image-gen] Failed to toggle favorite item in IndexedDB:", err);
|
|
299
|
+
return false;
|
|
209
300
|
}
|
|
210
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* Bulk delete multiple gallery records by IDs and record tombstones in a single transaction.
|
|
304
|
+
*/
|
|
305
|
+
async function bulkDeleteGalleryItems(ids) {
|
|
306
|
+
if (ids.length === 0) return;
|
|
307
|
+
const db = await getDB();
|
|
308
|
+
const tombstones = await loadTombstones(db);
|
|
309
|
+
const now = Date.now();
|
|
310
|
+
await new Promise((resolve, reject) => {
|
|
311
|
+
const tx = db.transaction([STORE_NAME, TOMBSTONE_STORE], "readwrite");
|
|
312
|
+
const store = tx.objectStore(STORE_NAME);
|
|
313
|
+
const tombstoneStore = tx.objectStore(TOMBSTONE_STORE);
|
|
314
|
+
for (const id of ids) {
|
|
315
|
+
store.delete(id);
|
|
316
|
+
tombstoneStore.put({
|
|
317
|
+
id,
|
|
318
|
+
deletedAt: now
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
tx.oncomplete = () => {
|
|
322
|
+
for (const id of ids) tombstones.add(id);
|
|
323
|
+
resolve();
|
|
324
|
+
};
|
|
325
|
+
tx.onerror = () => reject(tx.error ?? /* @__PURE__ */ new Error("IndexedDB transaction failed"));
|
|
326
|
+
tx.onabort = () => reject(tx.error ?? /* @__PURE__ */ new Error("IndexedDB transaction aborted"));
|
|
327
|
+
});
|
|
328
|
+
notifyListeners();
|
|
329
|
+
}
|
|
330
|
+
//#endregion
|
|
331
|
+
//#region node_modules/.pnpm/lucide-react@1.38.0_react@18.3.1/node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.mjs
|
|
332
|
+
/**
|
|
333
|
+
* @license lucide-react v1.38.0 - ISC
|
|
334
|
+
*
|
|
335
|
+
* This source code is licensed under the ISC license.
|
|
336
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
337
|
+
*/
|
|
338
|
+
const mergeClasses = (...classes) => classes.filter((className, index, array) => {
|
|
339
|
+
return Boolean(className) && className.trim() !== "" && array.indexOf(className) === index;
|
|
340
|
+
}).join(" ").trim();
|
|
341
|
+
//#endregion
|
|
342
|
+
//#region node_modules/.pnpm/lucide-react@1.38.0_react@18.3.1/node_modules/lucide-react/dist/esm/shared/src/utils/toKebabCase.mjs
|
|
343
|
+
/**
|
|
344
|
+
* @license lucide-react v1.38.0 - ISC
|
|
345
|
+
*
|
|
346
|
+
* This source code is licensed under the ISC license.
|
|
347
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
348
|
+
*/
|
|
349
|
+
const toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
350
|
+
//#endregion
|
|
351
|
+
//#region node_modules/.pnpm/lucide-react@1.38.0_react@18.3.1/node_modules/lucide-react/dist/esm/shared/src/utils/toCamelCase.mjs
|
|
352
|
+
/**
|
|
353
|
+
* @license lucide-react v1.38.0 - ISC
|
|
354
|
+
*
|
|
355
|
+
* This source code is licensed under the ISC license.
|
|
356
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
357
|
+
*/
|
|
358
|
+
const toCamelCase = (string) => string.replace(/^([A-Z])|[\s-_]+(\w)/g, (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase());
|
|
359
|
+
//#endregion
|
|
360
|
+
//#region node_modules/.pnpm/lucide-react@1.38.0_react@18.3.1/node_modules/lucide-react/dist/esm/shared/src/utils/toPascalCase.mjs
|
|
361
|
+
/**
|
|
362
|
+
* @license lucide-react v1.38.0 - ISC
|
|
363
|
+
*
|
|
364
|
+
* This source code is licensed under the ISC license.
|
|
365
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
366
|
+
*/
|
|
367
|
+
const toPascalCase = (string) => {
|
|
368
|
+
const camelCase = toCamelCase(string);
|
|
369
|
+
return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
|
|
370
|
+
};
|
|
371
|
+
//#endregion
|
|
372
|
+
//#region node_modules/.pnpm/lucide-react@1.38.0_react@18.3.1/node_modules/lucide-react/dist/esm/defaultAttributes.mjs
|
|
373
|
+
/**
|
|
374
|
+
* @license lucide-react v1.38.0 - ISC
|
|
375
|
+
*
|
|
376
|
+
* This source code is licensed under the ISC license.
|
|
377
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
378
|
+
*/
|
|
379
|
+
var defaultAttributes = {
|
|
380
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
381
|
+
width: 24,
|
|
382
|
+
height: 24,
|
|
383
|
+
viewBox: "0 0 24 24",
|
|
384
|
+
fill: "none",
|
|
385
|
+
stroke: "currentColor",
|
|
386
|
+
strokeWidth: 2,
|
|
387
|
+
strokeLinecap: "round",
|
|
388
|
+
strokeLinejoin: "round"
|
|
389
|
+
};
|
|
390
|
+
//#endregion
|
|
391
|
+
//#region node_modules/.pnpm/lucide-react@1.38.0_react@18.3.1/node_modules/lucide-react/dist/esm/shared/src/utils/hasA11yProp.mjs
|
|
392
|
+
/**
|
|
393
|
+
* @license lucide-react v1.38.0 - ISC
|
|
394
|
+
*
|
|
395
|
+
* This source code is licensed under the ISC license.
|
|
396
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
397
|
+
*/
|
|
398
|
+
const hasA11yProp = (props) => {
|
|
399
|
+
for (const prop in props) if (prop.startsWith("aria-") || prop === "role" || prop === "title") return true;
|
|
400
|
+
return false;
|
|
401
|
+
};
|
|
402
|
+
//#endregion
|
|
403
|
+
//#region node_modules/.pnpm/lucide-react@1.38.0_react@18.3.1/node_modules/lucide-react/dist/esm/context.mjs
|
|
404
|
+
/**
|
|
405
|
+
* @license lucide-react v1.38.0 - ISC
|
|
406
|
+
*
|
|
407
|
+
* This source code is licensed under the ISC license.
|
|
408
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
409
|
+
*/
|
|
410
|
+
const LucideContext = (0, react.createContext)({});
|
|
411
|
+
const useLucideContext = () => (0, react.useContext)(LucideContext);
|
|
412
|
+
//#endregion
|
|
413
|
+
//#region node_modules/.pnpm/lucide-react@1.38.0_react@18.3.1/node_modules/lucide-react/dist/esm/Icon.mjs
|
|
414
|
+
/**
|
|
415
|
+
* @license lucide-react v1.38.0 - ISC
|
|
416
|
+
*
|
|
417
|
+
* This source code is licensed under the ISC license.
|
|
418
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
419
|
+
*/
|
|
420
|
+
const Icon = (0, react.forwardRef)(({ color, size, strokeWidth, absoluteStrokeWidth, className = "", children, iconNode, ...rest }, ref) => {
|
|
421
|
+
const { size: contextSize = 24, strokeWidth: contextStrokeWidth = 2, absoluteStrokeWidth: contextAbsoluteStrokeWidth = false, color: contextColor = "currentColor", className: contextClass = "" } = useLucideContext() ?? {};
|
|
422
|
+
const calculatedStrokeWidth = absoluteStrokeWidth ?? contextAbsoluteStrokeWidth ? Number(strokeWidth ?? contextStrokeWidth) * 24 / Number(size ?? contextSize) : strokeWidth ?? contextStrokeWidth;
|
|
423
|
+
return (0, react.createElement)("svg", {
|
|
424
|
+
ref,
|
|
425
|
+
...defaultAttributes,
|
|
426
|
+
width: size ?? contextSize ?? defaultAttributes.width,
|
|
427
|
+
height: size ?? contextSize ?? defaultAttributes.height,
|
|
428
|
+
stroke: color ?? contextColor,
|
|
429
|
+
strokeWidth: calculatedStrokeWidth,
|
|
430
|
+
className: mergeClasses("lucide", contextClass, className),
|
|
431
|
+
...!children && !hasA11yProp(rest) && { "aria-hidden": "true" },
|
|
432
|
+
...rest
|
|
433
|
+
}, [...iconNode.map(([tag, attrs]) => (0, react.createElement)(tag, attrs)), ...Array.isArray(children) ? children : [children]]);
|
|
434
|
+
});
|
|
435
|
+
//#endregion
|
|
436
|
+
//#region node_modules/.pnpm/lucide-react@1.38.0_react@18.3.1/node_modules/lucide-react/dist/esm/createLucideIcon.mjs
|
|
437
|
+
/**
|
|
438
|
+
* @license lucide-react v1.38.0 - ISC
|
|
439
|
+
*
|
|
440
|
+
* This source code is licensed under the ISC license.
|
|
441
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
442
|
+
*/
|
|
443
|
+
const createLucideIcon = (iconName, iconNode) => {
|
|
444
|
+
const Component = (0, react.forwardRef)(({ className, ...props }, ref) => (0, react.createElement)(Icon, {
|
|
445
|
+
ref,
|
|
446
|
+
iconNode,
|
|
447
|
+
className: mergeClasses(`lucide-${toKebabCase(toPascalCase(iconName))}`, `lucide-${iconName}`, className),
|
|
448
|
+
...props
|
|
449
|
+
}));
|
|
450
|
+
Component.displayName = toPascalCase(iconName);
|
|
451
|
+
return Component;
|
|
452
|
+
};
|
|
453
|
+
/**
|
|
454
|
+
* @license lucide-react v1.38.0 - ISC
|
|
455
|
+
*
|
|
456
|
+
* This source code is licensed under the ISC license.
|
|
457
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
458
|
+
*/
|
|
459
|
+
const Check = createLucideIcon("check", [["path", {
|
|
460
|
+
d: "M20 6 9 17l-5-5",
|
|
461
|
+
key: "1gmf2c"
|
|
462
|
+
}]]);
|
|
463
|
+
/**
|
|
464
|
+
* @license lucide-react v1.38.0 - ISC
|
|
465
|
+
*
|
|
466
|
+
* This source code is licensed under the ISC license.
|
|
467
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
468
|
+
*/
|
|
469
|
+
const ChevronLeft = createLucideIcon("chevron-left", [["path", {
|
|
470
|
+
d: "m15 18-6-6 6-6",
|
|
471
|
+
key: "1wnfg3"
|
|
472
|
+
}]]);
|
|
473
|
+
/**
|
|
474
|
+
* @license lucide-react v1.38.0 - ISC
|
|
475
|
+
*
|
|
476
|
+
* This source code is licensed under the ISC license.
|
|
477
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
478
|
+
*/
|
|
479
|
+
const ChevronRight = createLucideIcon("chevron-right", [["path", {
|
|
480
|
+
d: "m9 18 6-6-6-6",
|
|
481
|
+
key: "mthhwq"
|
|
482
|
+
}]]);
|
|
483
|
+
/**
|
|
484
|
+
* @license lucide-react v1.38.0 - ISC
|
|
485
|
+
*
|
|
486
|
+
* This source code is licensed under the ISC license.
|
|
487
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
488
|
+
*/
|
|
489
|
+
const Copy = createLucideIcon("copy", [["rect", {
|
|
490
|
+
width: "14",
|
|
491
|
+
height: "14",
|
|
492
|
+
x: "8",
|
|
493
|
+
y: "8",
|
|
494
|
+
rx: "2",
|
|
495
|
+
ry: "2",
|
|
496
|
+
key: "17jyea"
|
|
497
|
+
}], ["path", {
|
|
498
|
+
d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",
|
|
499
|
+
key: "zix9uf"
|
|
500
|
+
}]]);
|
|
501
|
+
/**
|
|
502
|
+
* @license lucide-react v1.38.0 - ISC
|
|
503
|
+
*
|
|
504
|
+
* This source code is licensed under the ISC license.
|
|
505
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
506
|
+
*/
|
|
507
|
+
const Download = createLucideIcon("download", [
|
|
508
|
+
["path", {
|
|
509
|
+
d: "M12 15V3",
|
|
510
|
+
key: "m9g1x1"
|
|
511
|
+
}],
|
|
512
|
+
["path", {
|
|
513
|
+
d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",
|
|
514
|
+
key: "ih7n3h"
|
|
515
|
+
}],
|
|
516
|
+
["path", {
|
|
517
|
+
d: "m7 10 5 5 5-5",
|
|
518
|
+
key: "brsn70"
|
|
519
|
+
}]
|
|
520
|
+
]);
|
|
521
|
+
/**
|
|
522
|
+
* @license lucide-react v1.38.0 - ISC
|
|
523
|
+
*
|
|
524
|
+
* This source code is licensed under the ISC license.
|
|
525
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
526
|
+
*/
|
|
527
|
+
const FileText = createLucideIcon("file-text", [
|
|
528
|
+
["path", {
|
|
529
|
+
d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",
|
|
530
|
+
key: "1oefj6"
|
|
531
|
+
}],
|
|
532
|
+
["path", {
|
|
533
|
+
d: "M14 2v5a1 1 0 0 0 1 1h5",
|
|
534
|
+
key: "wfsgrz"
|
|
535
|
+
}],
|
|
536
|
+
["path", {
|
|
537
|
+
d: "M10 9H8",
|
|
538
|
+
key: "b1mrlr"
|
|
539
|
+
}],
|
|
540
|
+
["path", {
|
|
541
|
+
d: "M16 13H8",
|
|
542
|
+
key: "t4e002"
|
|
543
|
+
}],
|
|
544
|
+
["path", {
|
|
545
|
+
d: "M16 17H8",
|
|
546
|
+
key: "z1uh3a"
|
|
547
|
+
}]
|
|
548
|
+
]);
|
|
549
|
+
/**
|
|
550
|
+
* @license lucide-react v1.38.0 - ISC
|
|
551
|
+
*
|
|
552
|
+
* This source code is licensed under the ISC license.
|
|
553
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
554
|
+
*/
|
|
555
|
+
const GitCompare = createLucideIcon("git-compare", [
|
|
556
|
+
["circle", {
|
|
557
|
+
cx: "18",
|
|
558
|
+
cy: "18",
|
|
559
|
+
r: "3",
|
|
560
|
+
key: "1xkwt0"
|
|
561
|
+
}],
|
|
562
|
+
["circle", {
|
|
563
|
+
cx: "6",
|
|
564
|
+
cy: "6",
|
|
565
|
+
r: "3",
|
|
566
|
+
key: "1lh9wr"
|
|
567
|
+
}],
|
|
568
|
+
["path", {
|
|
569
|
+
d: "M13 6h3a2 2 0 0 1 2 2v7",
|
|
570
|
+
key: "1yeb86"
|
|
571
|
+
}],
|
|
572
|
+
["path", {
|
|
573
|
+
d: "M11 18H8a2 2 0 0 1-2-2V9",
|
|
574
|
+
key: "19pyzm"
|
|
575
|
+
}]
|
|
576
|
+
]);
|
|
577
|
+
/**
|
|
578
|
+
* @license lucide-react v1.38.0 - ISC
|
|
579
|
+
*
|
|
580
|
+
* This source code is licensed under the ISC license.
|
|
581
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
582
|
+
*/
|
|
583
|
+
const Heart = createLucideIcon("heart", [["path", {
|
|
584
|
+
d: "M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5",
|
|
585
|
+
key: "mvr1a0"
|
|
586
|
+
}]]);
|
|
587
|
+
/**
|
|
588
|
+
* @license lucide-react v1.38.0 - ISC
|
|
589
|
+
*
|
|
590
|
+
* This source code is licensed under the ISC license.
|
|
591
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
592
|
+
*/
|
|
593
|
+
const Image$1 = createLucideIcon("image", [
|
|
594
|
+
["rect", {
|
|
595
|
+
width: "18",
|
|
596
|
+
height: "18",
|
|
597
|
+
x: "3",
|
|
598
|
+
y: "3",
|
|
599
|
+
rx: "2",
|
|
600
|
+
ry: "2",
|
|
601
|
+
key: "1m3agn"
|
|
602
|
+
}],
|
|
603
|
+
["circle", {
|
|
604
|
+
cx: "9",
|
|
605
|
+
cy: "9",
|
|
606
|
+
r: "2",
|
|
607
|
+
key: "af1f0g"
|
|
608
|
+
}],
|
|
609
|
+
["path", {
|
|
610
|
+
d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21",
|
|
611
|
+
key: "1xmnt7"
|
|
612
|
+
}]
|
|
613
|
+
]);
|
|
614
|
+
/**
|
|
615
|
+
* @license lucide-react v1.38.0 - ISC
|
|
616
|
+
*
|
|
617
|
+
* This source code is licensed under the ISC license.
|
|
618
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
619
|
+
*/
|
|
620
|
+
const ListTodo = createLucideIcon("list-todo", [
|
|
621
|
+
["path", {
|
|
622
|
+
d: "M13 5h8",
|
|
623
|
+
key: "a7qcls"
|
|
624
|
+
}],
|
|
625
|
+
["path", {
|
|
626
|
+
d: "M13 12h8",
|
|
627
|
+
key: "h98zly"
|
|
628
|
+
}],
|
|
629
|
+
["path", {
|
|
630
|
+
d: "M13 19h8",
|
|
631
|
+
key: "c3s6r1"
|
|
632
|
+
}],
|
|
633
|
+
["path", {
|
|
634
|
+
d: "m3 17 2 2 4-4",
|
|
635
|
+
key: "1jhpwq"
|
|
636
|
+
}],
|
|
637
|
+
["rect", {
|
|
638
|
+
x: "3",
|
|
639
|
+
y: "4",
|
|
640
|
+
width: "6",
|
|
641
|
+
height: "6",
|
|
642
|
+
rx: "1",
|
|
643
|
+
key: "cif1o7"
|
|
644
|
+
}]
|
|
645
|
+
]);
|
|
646
|
+
/**
|
|
647
|
+
* @license lucide-react v1.38.0 - ISC
|
|
648
|
+
*
|
|
649
|
+
* This source code is licensed under the ISC license.
|
|
650
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
651
|
+
*/
|
|
652
|
+
const Search = createLucideIcon("search", [["path", {
|
|
653
|
+
d: "m21 21-4.34-4.34",
|
|
654
|
+
key: "14j7rj"
|
|
655
|
+
}], ["circle", {
|
|
656
|
+
cx: "11",
|
|
657
|
+
cy: "11",
|
|
658
|
+
r: "8",
|
|
659
|
+
key: "4ej97u"
|
|
660
|
+
}]]);
|
|
661
|
+
/**
|
|
662
|
+
* @license lucide-react v1.38.0 - ISC
|
|
663
|
+
*
|
|
664
|
+
* This source code is licensed under the ISC license.
|
|
665
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
666
|
+
*/
|
|
667
|
+
const SlidersHorizontal = createLucideIcon("sliders-horizontal", [
|
|
668
|
+
["path", {
|
|
669
|
+
d: "M10 5H3",
|
|
670
|
+
key: "1qgfaw"
|
|
671
|
+
}],
|
|
672
|
+
["path", {
|
|
673
|
+
d: "M12 19H3",
|
|
674
|
+
key: "yhmn1j"
|
|
675
|
+
}],
|
|
676
|
+
["path", {
|
|
677
|
+
d: "M14 3v4",
|
|
678
|
+
key: "1sua03"
|
|
679
|
+
}],
|
|
680
|
+
["path", {
|
|
681
|
+
d: "M16 17v4",
|
|
682
|
+
key: "1q0r14"
|
|
683
|
+
}],
|
|
684
|
+
["path", {
|
|
685
|
+
d: "M21 12h-9",
|
|
686
|
+
key: "1o4lsq"
|
|
687
|
+
}],
|
|
688
|
+
["path", {
|
|
689
|
+
d: "M21 19h-5",
|
|
690
|
+
key: "1rlt1p"
|
|
691
|
+
}],
|
|
692
|
+
["path", {
|
|
693
|
+
d: "M21 5h-7",
|
|
694
|
+
key: "1oszz2"
|
|
695
|
+
}],
|
|
696
|
+
["path", {
|
|
697
|
+
d: "M8 10v4",
|
|
698
|
+
key: "tgpxqk"
|
|
699
|
+
}],
|
|
700
|
+
["path", {
|
|
701
|
+
d: "M8 12H3",
|
|
702
|
+
key: "a7s4jb"
|
|
703
|
+
}]
|
|
704
|
+
]);
|
|
705
|
+
/**
|
|
706
|
+
* @license lucide-react v1.38.0 - ISC
|
|
707
|
+
*
|
|
708
|
+
* This source code is licensed under the ISC license.
|
|
709
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
710
|
+
*/
|
|
711
|
+
const Trash2 = createLucideIcon("trash-2", [
|
|
712
|
+
["path", {
|
|
713
|
+
d: "M10 11v6",
|
|
714
|
+
key: "nco0om"
|
|
715
|
+
}],
|
|
716
|
+
["path", {
|
|
717
|
+
d: "M14 11v6",
|
|
718
|
+
key: "outv1u"
|
|
719
|
+
}],
|
|
720
|
+
["path", {
|
|
721
|
+
d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",
|
|
722
|
+
key: "miytrc"
|
|
723
|
+
}],
|
|
724
|
+
["path", {
|
|
725
|
+
d: "M3 6h18",
|
|
726
|
+
key: "d0wm0j"
|
|
727
|
+
}],
|
|
728
|
+
["path", {
|
|
729
|
+
d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",
|
|
730
|
+
key: "e791ji"
|
|
731
|
+
}]
|
|
732
|
+
]);
|
|
733
|
+
/**
|
|
734
|
+
* @license lucide-react v1.38.0 - ISC
|
|
735
|
+
*
|
|
736
|
+
* This source code is licensed under the ISC license.
|
|
737
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
738
|
+
*/
|
|
739
|
+
const TriangleAlert = createLucideIcon("triangle-alert", [
|
|
740
|
+
["path", {
|
|
741
|
+
d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",
|
|
742
|
+
key: "wmoenq"
|
|
743
|
+
}],
|
|
744
|
+
["path", {
|
|
745
|
+
d: "M12 9v4",
|
|
746
|
+
key: "juzpu7"
|
|
747
|
+
}],
|
|
748
|
+
["path", {
|
|
749
|
+
d: "M12 17h.01",
|
|
750
|
+
key: "p32p05"
|
|
751
|
+
}]
|
|
752
|
+
]);
|
|
753
|
+
/**
|
|
754
|
+
* @license lucide-react v1.38.0 - ISC
|
|
755
|
+
*
|
|
756
|
+
* This source code is licensed under the ISC license.
|
|
757
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
758
|
+
*/
|
|
759
|
+
const X = createLucideIcon("x", [["path", {
|
|
760
|
+
d: "M18 6 6 18",
|
|
761
|
+
key: "1bl5f8"
|
|
762
|
+
}], ["path", {
|
|
763
|
+
d: "m6 6 12 12",
|
|
764
|
+
key: "d8bk6v"
|
|
765
|
+
}]]);
|
|
211
766
|
//#endregion
|
|
212
767
|
//#region lib/types/client/gallery-view.js
|
|
213
768
|
/**
|
|
214
|
-
* Native Workspace Gallery View Component for DSH `conversation.view` slot.
|
|
215
|
-
* Fully i18n-reactive (Chinese & English)
|
|
769
|
+
* Native Workspace Gallery & Studio View Component for DSH `conversation.view` slot.
|
|
770
|
+
* Fully i18n-reactive (Chinese & English) with modular tabs, multi-dimensional filters,
|
|
771
|
+
* responsive image grid, and placeholder routes.
|
|
216
772
|
*/
|
|
217
773
|
const DICT$1 = {
|
|
218
774
|
zh: {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
775
|
+
tabGallery: "图库",
|
|
776
|
+
tabStudio: "工作台",
|
|
777
|
+
tabFavorites: "收藏",
|
|
778
|
+
tabCompare: "对比",
|
|
779
|
+
tabTasks: "任务",
|
|
780
|
+
filterAllProviders: "全部提供商",
|
|
222
781
|
filterGoogle: "Google Gemini",
|
|
223
782
|
filterOpenAI: "OpenAI / 中转站",
|
|
224
783
|
filterSeedream: "字节 Seedream",
|
|
225
784
|
filterDashScope: "阿里 DashScope",
|
|
785
|
+
filterComfyUI: "本地 ComfyUI",
|
|
786
|
+
filterAllModels: "全部模型",
|
|
787
|
+
filterAllRatios: "全部比例",
|
|
788
|
+
searchPlaceholder: "搜索 Prompt、标签…",
|
|
789
|
+
sortNewest: "最新生成",
|
|
790
|
+
sortOldest: "最早生成",
|
|
791
|
+
totalCount: "共 {count} 张生成图片",
|
|
226
792
|
emptyTitle: "暂无生图记录",
|
|
227
793
|
emptyDesc: "在对话中让 Agent 生图后,生成的图片会自动收录到这里。",
|
|
794
|
+
favEmptyTitle: "暂无收藏图片",
|
|
795
|
+
favEmptyDesc: "在图库中点击卡片右下角的 ♡ 按钮,即可将喜爱的图片收录到这里。",
|
|
228
796
|
noMatchTitle: "未找到匹配结果",
|
|
229
|
-
noMatchDesc: "
|
|
797
|
+
noMatchDesc: "尝试更换搜索关键词或调整筛选条件。",
|
|
230
798
|
copiedPrompt: "已复制 Prompt",
|
|
231
799
|
copiedImage: "已复制图片",
|
|
232
800
|
copyFailed: "复制失败",
|
|
801
|
+
favoriteAdded: "已添加到收藏",
|
|
802
|
+
favoriteRemoved: "已取消收藏",
|
|
233
803
|
preview: "查看大图",
|
|
234
804
|
download: "下载图片",
|
|
235
805
|
copyImg: "复制图片",
|
|
@@ -239,23 +809,69 @@ window.__ModuleLoader__.load({
|
|
|
239
809
|
deleted: "已从画廊删除",
|
|
240
810
|
model: "模型",
|
|
241
811
|
prompt: "Prompt",
|
|
242
|
-
close: "关闭 (Esc)"
|
|
812
|
+
close: "关闭 (Esc)",
|
|
813
|
+
prevImage: "上一张 (←)",
|
|
814
|
+
nextImage: "下一张 (→)",
|
|
815
|
+
manage: "批量删除",
|
|
816
|
+
exitManage: "退出选择",
|
|
817
|
+
selectedCount: "已选 {n} 项",
|
|
818
|
+
selectAll: "全选",
|
|
819
|
+
invertSelect: "反选",
|
|
820
|
+
clearSelect: "清空",
|
|
821
|
+
batchDelete: "批量删除",
|
|
822
|
+
batchDeleteTitle: "确认批量删除选中的 {count} 张图片?",
|
|
823
|
+
batchDeleteTitleSingle: "确认从画廊中删除这张图片?",
|
|
824
|
+
batchDeleteDesc: "将从画廊历史记录中移除所选图片。",
|
|
825
|
+
deleteWorkspaceFilesOpt: "同时清理工作区磁盘物理文件(不可恢复)",
|
|
826
|
+
cancel: "取消",
|
|
827
|
+
confirmBatchDelete: "确认删除 ({count})",
|
|
828
|
+
confirmDeleteSingle: "确认删除",
|
|
829
|
+
batchDeletedToast: "已删除 {count} 张图片",
|
|
830
|
+
batchDeletedWithFilesToast: "已删除 {count} 张图片,并清理了 {files} 个工作区文件",
|
|
831
|
+
deleteFailedFileLocked: "已删除 {count} 张图片,但有 {files} 个文件因被系统占用未能删除",
|
|
832
|
+
deleteFailedDatabase: "删除失败:本地数据库操作异常",
|
|
833
|
+
justNow: "刚刚",
|
|
834
|
+
minutesAgo: "{n} 分钟前",
|
|
835
|
+
hoursAgo: "{n} 小时前",
|
|
836
|
+
daysAgo: "{n} 天前",
|
|
837
|
+
studioTitle: "AI 图像工作台 (Studio)",
|
|
838
|
+
studioDesc: "工作台模块正在紧锣密鼓开发中。在此你将体验大图精修、变体生成 (Variations)、参数重调并一键将生成结果无缝插回 DSH 正在进行的对话。",
|
|
839
|
+
studioTip: "💡 提示:目前你可以在“图库”中点击任意图片,在弹窗中进行查看、复制与下载。",
|
|
840
|
+
compareTitle: "多模型横向对比 (Compare)",
|
|
841
|
+
compareDesc: "支持单个 Prompt 一键同时调度 Gemini、Seedream、DashScope 及本地 ComfyUI 模型并排生成,直观横评画质与细节。",
|
|
842
|
+
tasksTitle: "异步任务队列 (Tasks)",
|
|
843
|
+
tasksDesc: "集中管理后台批量生图、多模型并发生成与本地 ComfyUI 耗时任务。支持状态追踪、失败重试与执行耗时分析。",
|
|
844
|
+
comingSoonBadge: "即将推出"
|
|
243
845
|
},
|
|
244
846
|
en: {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
847
|
+
tabGallery: "Gallery",
|
|
848
|
+
tabStudio: "Studio",
|
|
849
|
+
tabFavorites: "Favorites",
|
|
850
|
+
tabCompare: "Compare",
|
|
851
|
+
tabTasks: "Tasks",
|
|
852
|
+
filterAllProviders: "All Providers",
|
|
248
853
|
filterGoogle: "Google Gemini",
|
|
249
854
|
filterOpenAI: "OpenAI / Relay",
|
|
250
855
|
filterSeedream: "ByteDance Seedream",
|
|
251
856
|
filterDashScope: "Aliyun DashScope",
|
|
857
|
+
filterComfyUI: "Local ComfyUI",
|
|
858
|
+
filterAllModels: "All Models",
|
|
859
|
+
filterAllRatios: "All Ratios",
|
|
860
|
+
searchPlaceholder: "Search prompt, tags…",
|
|
861
|
+
sortNewest: "Newest first",
|
|
862
|
+
sortOldest: "Oldest first",
|
|
863
|
+
totalCount: "{count} images total",
|
|
252
864
|
emptyTitle: "No images generated yet",
|
|
253
865
|
emptyDesc: "Images generated during conversations will automatically appear here.",
|
|
866
|
+
favEmptyTitle: "No favorite images yet",
|
|
867
|
+
favEmptyDesc: "Click the ♡ button on any card in the gallery to collect your favorite images here.",
|
|
254
868
|
noMatchTitle: "No matching images",
|
|
255
|
-
noMatchDesc: "Try a different search keyword or
|
|
869
|
+
noMatchDesc: "Try a different search keyword or adjust filter criteria.",
|
|
256
870
|
copiedPrompt: "Prompt copied",
|
|
257
871
|
copiedImage: "Image copied",
|
|
258
872
|
copyFailed: "Copy failed",
|
|
873
|
+
favoriteAdded: "Added to favorites",
|
|
874
|
+
favoriteRemoved: "Removed from favorites",
|
|
259
875
|
preview: "Full Preview",
|
|
260
876
|
download: "Download",
|
|
261
877
|
copyImg: "Copy Image",
|
|
@@ -265,17 +881,123 @@ window.__ModuleLoader__.load({
|
|
|
265
881
|
deleted: "Deleted from gallery",
|
|
266
882
|
model: "Model",
|
|
267
883
|
prompt: "Prompt",
|
|
268
|
-
close: "Close (Esc)"
|
|
884
|
+
close: "Close (Esc)",
|
|
885
|
+
prevImage: "Previous (←)",
|
|
886
|
+
nextImage: "Next (→)",
|
|
887
|
+
manage: "Batch Delete",
|
|
888
|
+
exitManage: "Done",
|
|
889
|
+
selectedCount: "{n} selected",
|
|
890
|
+
selectAll: "Select All",
|
|
891
|
+
invertSelect: "Invert",
|
|
892
|
+
clearSelect: "Clear",
|
|
893
|
+
batchDelete: "Batch Delete",
|
|
894
|
+
batchDeleteTitle: "Delete {count} selected images?",
|
|
895
|
+
batchDeleteTitleSingle: "Delete this image from gallery?",
|
|
896
|
+
batchDeleteDesc: "These images will be removed from your gallery history.",
|
|
897
|
+
deleteWorkspaceFilesOpt: "Also delete files from workspace disk (cannot be undone)",
|
|
898
|
+
cancel: "Cancel",
|
|
899
|
+
confirmBatchDelete: "Delete ({count})",
|
|
900
|
+
confirmDeleteSingle: "Delete",
|
|
901
|
+
batchDeletedToast: "Deleted {count} images",
|
|
902
|
+
batchDeletedWithFilesToast: "Deleted {count} images and cleaned {files} workspace files",
|
|
903
|
+
deleteFailedFileLocked: "Deleted {count} images, but {files} files could not be deleted (busy/locked)",
|
|
904
|
+
deleteFailedDatabase: "Failed to delete: local database error",
|
|
905
|
+
justNow: "Just now",
|
|
906
|
+
minutesAgo: "{n}m ago",
|
|
907
|
+
hoursAgo: "{n}h ago",
|
|
908
|
+
daysAgo: "{n}d ago",
|
|
909
|
+
studioTitle: "AI Image Studio",
|
|
910
|
+
studioDesc: "Studio workbench is under active development. Fine-tune prompts, generate variations (2x/4x), and inject images directly into DSH chat.",
|
|
911
|
+
studioTip: "💡 Tip: You can currently click any image in the Gallery to preview, copy, or download it.",
|
|
912
|
+
compareTitle: "Model Comparison (Compare)",
|
|
913
|
+
compareDesc: "Side-by-side multi-model benchmarking coming soon. Test Gemini, Seedream, DashScope, and ComfyUI with a single prompt.",
|
|
914
|
+
tasksTitle: "Task Queue (Tasks)",
|
|
915
|
+
tasksDesc: "Centralized view for batch generation, asynchronous ComfyUI runs, live progress tracking, and retry controls.",
|
|
916
|
+
comingSoonBadge: "Coming Soon"
|
|
269
917
|
}
|
|
270
918
|
};
|
|
919
|
+
/** Format human-readable relative time */
|
|
920
|
+
function formatRelativeTime(timestamp, t) {
|
|
921
|
+
const diff = Date.now() - timestamp;
|
|
922
|
+
if (diff < 6e4) return t("justNow");
|
|
923
|
+
if (diff < 36e5) {
|
|
924
|
+
const mins = Math.max(1, Math.floor(diff / 6e4));
|
|
925
|
+
return t("minutesAgo", { n: String(mins) });
|
|
926
|
+
}
|
|
927
|
+
if (diff < 864e5) {
|
|
928
|
+
const hours = Math.floor(diff / 36e5);
|
|
929
|
+
return t("hoursAgo", { n: String(hours) });
|
|
930
|
+
}
|
|
931
|
+
if (diff < 2592e6) {
|
|
932
|
+
const days = Math.floor(diff / 864e5);
|
|
933
|
+
return t("daysAgo", { n: String(days) });
|
|
934
|
+
}
|
|
935
|
+
const d = new Date(timestamp);
|
|
936
|
+
return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`;
|
|
937
|
+
}
|
|
938
|
+
/** Extract standard aspect ratio for precise filtering */
|
|
939
|
+
function getItemRatio(item) {
|
|
940
|
+
if (item.aspectRatio && item.aspectRatio !== "custom") return item.aspectRatio;
|
|
941
|
+
if (item.output) {
|
|
942
|
+
const ratioMatch = item.output.match(/\b(1:1|16:9|9:16|4:3|3:4|3:2|2:3)\b/);
|
|
943
|
+
if (ratioMatch?.[1]) return ratioMatch[1];
|
|
944
|
+
const dimMatch = item.output.match(/(\d{3,4})\s*[×x*]\s*(\d{3,4})/);
|
|
945
|
+
if (dimMatch?.[1] && dimMatch[2]) {
|
|
946
|
+
const w = parseInt(dimMatch[1], 10);
|
|
947
|
+
const h = parseInt(dimMatch[2], 10);
|
|
948
|
+
if (w === h) return "1:1";
|
|
949
|
+
const approx = w / h;
|
|
950
|
+
if (Math.abs(approx - 16 / 9) < .05) return "16:9";
|
|
951
|
+
if (Math.abs(approx - 9 / 16) < .05) return "9:16";
|
|
952
|
+
if (Math.abs(approx - 4 / 3) < .05) return "4:3";
|
|
953
|
+
if (Math.abs(approx - 3 / 4) < .05) return "3:4";
|
|
954
|
+
if (Math.abs(approx - 3 / 2) < .05) return "3:2";
|
|
955
|
+
if (Math.abs(approx - 2 / 3) < .05) return "2:3";
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
return "1:1";
|
|
959
|
+
}
|
|
960
|
+
/** Format aspect ratio or dimensions for card metadata (e.g., 1024×1024 or 16:9) */
|
|
961
|
+
function formatCardMeta(item) {
|
|
962
|
+
if (item.output) {
|
|
963
|
+
const dimMatch = item.output.match(/(\d{3,4})\s*[×x*]\s*(\d{3,4})/);
|
|
964
|
+
if (dimMatch?.[1] && dimMatch[2]) return `${dimMatch[1]}×${dimMatch[2]}`;
|
|
965
|
+
}
|
|
966
|
+
if (item.aspectRatio && item.aspectRatio !== "custom") return item.aspectRatio;
|
|
967
|
+
if (item.output) {
|
|
968
|
+
const ratioMatch = item.output.match(/\b(1:1|16:9|9:16|4:3|3:4|3:2|2:3)\b/);
|
|
969
|
+
if (ratioMatch?.[1]) return ratioMatch[1];
|
|
970
|
+
}
|
|
971
|
+
if (item.imageSize) return item.imageSize;
|
|
972
|
+
return "1:1";
|
|
973
|
+
}
|
|
271
974
|
const GalleryViewTab = ({ locale }) => {
|
|
975
|
+
const [activeTab, setActiveTab] = (0, react.useState)("gallery");
|
|
272
976
|
const [items, setItems] = (0, react.useState)([]);
|
|
273
977
|
const [search, setSearch] = (0, react.useState)("");
|
|
274
978
|
const [selectedProvider, setSelectedProvider] = (0, react.useState)("all");
|
|
979
|
+
const [selectedRatio, setSelectedRatio] = (0, react.useState)("all");
|
|
980
|
+
const [sortBy, setSortBy] = (0, react.useState)("newest");
|
|
275
981
|
const [previewItem, setPreviewItem] = (0, react.useState)(null);
|
|
276
982
|
const [previewUrl, setPreviewUrl] = (0, react.useState)(null);
|
|
277
983
|
const [previewBlob, setPreviewBlob] = (0, react.useState)(null);
|
|
278
984
|
const [toast, setToast] = (0, react.useState)(null);
|
|
985
|
+
const [isManageMode, setIsManageMode] = (0, react.useState)(false);
|
|
986
|
+
const [selectedIds, setSelectedIds] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
987
|
+
const lastSelectedIndexRef = (0, react.useRef)(null);
|
|
988
|
+
const [showDeleteModal, setShowDeleteModal] = (0, react.useState)(false);
|
|
989
|
+
const [deleteWorkspaceFiles, setDeleteWorkspaceFiles] = (0, react.useState)(true);
|
|
990
|
+
const [isDeleting, setIsDeleting] = (0, react.useState)(false);
|
|
991
|
+
(0, react.useEffect)(() => {
|
|
992
|
+
setSelectedIds(/* @__PURE__ */ new Set());
|
|
993
|
+
lastSelectedIndexRef.current = null;
|
|
994
|
+
}, [
|
|
995
|
+
activeTab,
|
|
996
|
+
search,
|
|
997
|
+
selectedProvider,
|
|
998
|
+
selectedRatio,
|
|
999
|
+
sortBy
|
|
1000
|
+
]);
|
|
279
1001
|
const [lang, setLang] = (0, react.useState)(() => {
|
|
280
1002
|
return (locale?.getSnapshot?.()?.active)?.startsWith("en") ? "en" : "zh";
|
|
281
1003
|
});
|
|
@@ -322,108 +1044,431 @@ window.__ModuleLoader__.load({
|
|
|
322
1044
|
unsubscribe();
|
|
323
1045
|
};
|
|
324
1046
|
}, []);
|
|
325
|
-
(0, react.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (e.key === "Escape") {
|
|
329
|
-
setPreviewItem(null);
|
|
330
|
-
setPreviewUrl(null);
|
|
331
|
-
setPreviewBlob(null);
|
|
332
|
-
}
|
|
333
|
-
};
|
|
334
|
-
window.addEventListener("keydown", onKeyDown);
|
|
335
|
-
return () => window.removeEventListener("keydown", onKeyDown);
|
|
336
|
-
}, [previewItem]);
|
|
1047
|
+
const [previewLoading, setPreviewLoading] = (0, react.useState)(false);
|
|
1048
|
+
const blobCache = (0, react.useMemo)(() => /* @__PURE__ */ new Map(), []);
|
|
1049
|
+
const abortControllerRef = (0, react.useRef)(null);
|
|
337
1050
|
const filteredItems = (0, react.useMemo)(() => {
|
|
338
1051
|
return items.filter((item) => {
|
|
1052
|
+
if (activeTab === "favorites" && !item.isFavorite) return false;
|
|
339
1053
|
if (selectedProvider !== "all" && item.provider !== selectedProvider) return false;
|
|
1054
|
+
if (selectedRatio !== "all") {
|
|
1055
|
+
if (getItemRatio(item) !== selectedRatio) return false;
|
|
1056
|
+
}
|
|
340
1057
|
if (search.trim().length > 0) {
|
|
341
1058
|
const q = search.trim().toLowerCase();
|
|
342
1059
|
const matchPrompt = item.prompt?.toLowerCase().includes(q);
|
|
343
1060
|
const matchModel = item.model?.toLowerCase().includes(q);
|
|
344
|
-
|
|
1061
|
+
const matchProvider = item.provider?.toLowerCase().includes(q);
|
|
1062
|
+
const matchTags = item.tags?.some((t) => t.toLowerCase().includes(q));
|
|
1063
|
+
if (!matchPrompt && !matchModel && !matchProvider && !matchTags) return false;
|
|
345
1064
|
}
|
|
346
1065
|
return true;
|
|
1066
|
+
}).sort((a, b) => {
|
|
1067
|
+
if (sortBy === "oldest") return a.createdAt - b.createdAt;
|
|
1068
|
+
return b.createdAt - a.createdAt;
|
|
347
1069
|
});
|
|
348
1070
|
}, [
|
|
349
1071
|
items,
|
|
1072
|
+
activeTab,
|
|
1073
|
+
selectedProvider,
|
|
1074
|
+
selectedRatio,
|
|
350
1075
|
search,
|
|
351
|
-
|
|
1076
|
+
sortBy
|
|
352
1077
|
]);
|
|
1078
|
+
const currentPreviewIndex = (0, react.useMemo)(() => {
|
|
1079
|
+
if (!previewItem) return -1;
|
|
1080
|
+
return filteredItems.findIndex((i) => i.id === previewItem.id);
|
|
1081
|
+
}, [previewItem, filteredItems]);
|
|
1082
|
+
const hasPrev = currentPreviewIndex > 0;
|
|
1083
|
+
const hasNext = currentPreviewIndex >= 0 && currentPreviewIndex < filteredItems.length - 1;
|
|
1084
|
+
const openPreviewItem = (item, blob) => {
|
|
1085
|
+
if (abortControllerRef.current) {
|
|
1086
|
+
abortControllerRef.current.abort();
|
|
1087
|
+
abortControllerRef.current = null;
|
|
1088
|
+
}
|
|
1089
|
+
if (previewUrl) URL.revokeObjectURL(previewUrl);
|
|
1090
|
+
setPreviewItem(item);
|
|
1091
|
+
if (blob) {
|
|
1092
|
+
blobCache.set(item.id, blob);
|
|
1093
|
+
const url = URL.createObjectURL(blob);
|
|
1094
|
+
setPreviewBlob(blob);
|
|
1095
|
+
setPreviewUrl(url);
|
|
1096
|
+
setPreviewLoading(false);
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
const cached = blobCache.get(item.id);
|
|
1100
|
+
if (cached) {
|
|
1101
|
+
const url = URL.createObjectURL(cached);
|
|
1102
|
+
setPreviewBlob(cached);
|
|
1103
|
+
setPreviewUrl(url);
|
|
1104
|
+
setPreviewLoading(false);
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
setPreviewLoading(true);
|
|
1108
|
+
setPreviewBlob(null);
|
|
1109
|
+
setPreviewUrl(null);
|
|
1110
|
+
const controller = new AbortController();
|
|
1111
|
+
abortControllerRef.current = controller;
|
|
1112
|
+
fetch(IMAGE_ROUTE, {
|
|
1113
|
+
method: "POST",
|
|
1114
|
+
signal: controller.signal,
|
|
1115
|
+
headers: { "content-type": "application/json" },
|
|
1116
|
+
body: JSON.stringify({ attachment: item.attachment })
|
|
1117
|
+
}).then(async (res) => {
|
|
1118
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1119
|
+
return res.blob();
|
|
1120
|
+
}).then((fetchedBlob) => {
|
|
1121
|
+
if (controller.signal.aborted) return;
|
|
1122
|
+
blobCache.set(item.id, fetchedBlob);
|
|
1123
|
+
const url = URL.createObjectURL(fetchedBlob);
|
|
1124
|
+
setPreviewBlob(fetchedBlob);
|
|
1125
|
+
setPreviewUrl(url);
|
|
1126
|
+
setPreviewLoading(false);
|
|
1127
|
+
}).catch((err) => {
|
|
1128
|
+
if (controller.signal.aborted) return;
|
|
1129
|
+
setPreviewLoading(false);
|
|
1130
|
+
showToast(t("copyFailed"));
|
|
1131
|
+
});
|
|
1132
|
+
};
|
|
1133
|
+
const handleClosePreview = () => {
|
|
1134
|
+
if (abortControllerRef.current) {
|
|
1135
|
+
abortControllerRef.current.abort();
|
|
1136
|
+
abortControllerRef.current = null;
|
|
1137
|
+
}
|
|
1138
|
+
if (previewUrl) URL.revokeObjectURL(previewUrl);
|
|
1139
|
+
setPreviewItem(null);
|
|
1140
|
+
setPreviewUrl(null);
|
|
1141
|
+
setPreviewBlob(null);
|
|
1142
|
+
setPreviewLoading(false);
|
|
1143
|
+
};
|
|
1144
|
+
const goToPrev = () => {
|
|
1145
|
+
if (currentPreviewIndex > 0) {
|
|
1146
|
+
const prevItem = filteredItems[currentPreviewIndex - 1];
|
|
1147
|
+
if (prevItem) openPreviewItem(prevItem);
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
1150
|
+
const goToNext = () => {
|
|
1151
|
+
if (currentPreviewIndex >= 0 && currentPreviewIndex < filteredItems.length - 1) {
|
|
1152
|
+
const nextItem = filteredItems[currentPreviewIndex + 1];
|
|
1153
|
+
if (nextItem) openPreviewItem(nextItem);
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
(0, react.useEffect)(() => {
|
|
1157
|
+
return () => {
|
|
1158
|
+
if (abortControllerRef.current) abortControllerRef.current.abort();
|
|
1159
|
+
if (previewUrl) URL.revokeObjectURL(previewUrl);
|
|
1160
|
+
};
|
|
1161
|
+
}, [previewUrl]);
|
|
1162
|
+
(0, react.useEffect)(() => {
|
|
1163
|
+
const onKeyDown = (e) => {
|
|
1164
|
+
if (["INPUT", "TEXTAREA"].includes(e.target?.tagName)) return;
|
|
1165
|
+
if (showDeleteModal) {
|
|
1166
|
+
if (e.key === "Escape" && !isDeleting) {
|
|
1167
|
+
e.preventDefault();
|
|
1168
|
+
setShowDeleteModal(false);
|
|
1169
|
+
if (!isManageMode) setSelectedIds(/* @__PURE__ */ new Set());
|
|
1170
|
+
}
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
if (previewItem) {
|
|
1174
|
+
if (e.key === "Escape") handleClosePreview();
|
|
1175
|
+
else if (e.key === "ArrowLeft" || e.key === "Left") {
|
|
1176
|
+
e.preventDefault();
|
|
1177
|
+
goToPrev();
|
|
1178
|
+
} else if (e.key === "ArrowRight" || e.key === "Right") {
|
|
1179
|
+
e.preventDefault();
|
|
1180
|
+
goToNext();
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
};
|
|
1184
|
+
window.addEventListener("keydown", onKeyDown);
|
|
1185
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
1186
|
+
}, [
|
|
1187
|
+
showDeleteModal,
|
|
1188
|
+
isDeleting,
|
|
1189
|
+
isManageMode,
|
|
1190
|
+
previewItem,
|
|
1191
|
+
currentPreviewIndex,
|
|
1192
|
+
filteredItems
|
|
1193
|
+
]);
|
|
1194
|
+
const handleCardClick = (item, index, e, blob) => {
|
|
1195
|
+
if (e.shiftKey || isManageMode) {
|
|
1196
|
+
e.stopPropagation();
|
|
1197
|
+
if (!isManageMode) setIsManageMode(true);
|
|
1198
|
+
if (e.shiftKey && lastSelectedIndexRef.current !== null) {
|
|
1199
|
+
const from = Math.min(lastSelectedIndexRef.current, index);
|
|
1200
|
+
const to = Math.max(lastSelectedIndexRef.current, index);
|
|
1201
|
+
const rangeItems = filteredItems.slice(from, to + 1);
|
|
1202
|
+
setSelectedIds((prev) => {
|
|
1203
|
+
const next = new Set(prev);
|
|
1204
|
+
for (const r of rangeItems) next.add(r.id);
|
|
1205
|
+
return next;
|
|
1206
|
+
});
|
|
1207
|
+
} else {
|
|
1208
|
+
setSelectedIds((prev) => {
|
|
1209
|
+
const next = new Set(prev);
|
|
1210
|
+
if (next.has(item.id)) next.delete(item.id);
|
|
1211
|
+
else next.add(item.id);
|
|
1212
|
+
return next;
|
|
1213
|
+
});
|
|
1214
|
+
lastSelectedIndexRef.current = index;
|
|
1215
|
+
}
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
if (blob) openPreviewItem(item, blob);
|
|
1219
|
+
};
|
|
1220
|
+
const handleToggleSelect = (id, index, e) => {
|
|
1221
|
+
e.stopPropagation();
|
|
1222
|
+
if (!isManageMode) setIsManageMode(true);
|
|
1223
|
+
if (e.shiftKey && lastSelectedIndexRef.current !== null) {
|
|
1224
|
+
const from = Math.min(lastSelectedIndexRef.current, index);
|
|
1225
|
+
const to = Math.max(lastSelectedIndexRef.current, index);
|
|
1226
|
+
const rangeItems = filteredItems.slice(from, to + 1);
|
|
1227
|
+
setSelectedIds((prev) => {
|
|
1228
|
+
const next = new Set(prev);
|
|
1229
|
+
for (const r of rangeItems) next.add(r.id);
|
|
1230
|
+
return next;
|
|
1231
|
+
});
|
|
1232
|
+
} else {
|
|
1233
|
+
setSelectedIds((prev) => {
|
|
1234
|
+
const next = new Set(prev);
|
|
1235
|
+
if (next.has(id)) next.delete(id);
|
|
1236
|
+
else next.add(id);
|
|
1237
|
+
return next;
|
|
1238
|
+
});
|
|
1239
|
+
lastSelectedIndexRef.current = index;
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
const handleSelectAll = () => {
|
|
1243
|
+
setSelectedIds(new Set(filteredItems.map((i) => i.id)));
|
|
1244
|
+
};
|
|
1245
|
+
const handleInvertSelect = () => {
|
|
1246
|
+
setSelectedIds((prev) => {
|
|
1247
|
+
const next = /* @__PURE__ */ new Set();
|
|
1248
|
+
for (const item of filteredItems) if (!prev.has(item.id)) next.add(item.id);
|
|
1249
|
+
return next;
|
|
1250
|
+
});
|
|
1251
|
+
};
|
|
1252
|
+
const handleClearSelect = () => {
|
|
1253
|
+
setSelectedIds(/* @__PURE__ */ new Set());
|
|
1254
|
+
lastSelectedIndexRef.current = null;
|
|
1255
|
+
};
|
|
1256
|
+
const handleExitManage = () => {
|
|
1257
|
+
setIsManageMode(false);
|
|
1258
|
+
setSelectedIds(/* @__PURE__ */ new Set());
|
|
1259
|
+
lastSelectedIndexRef.current = null;
|
|
1260
|
+
};
|
|
1261
|
+
const requestSingleDelete = (item, e) => {
|
|
1262
|
+
if (e) e.stopPropagation();
|
|
1263
|
+
setSelectedIds(/* @__PURE__ */ new Set([item.id]));
|
|
1264
|
+
setShowDeleteModal(true);
|
|
1265
|
+
};
|
|
1266
|
+
const handleConfirmBatchDelete = async () => {
|
|
1267
|
+
if (selectedIds.size === 0 || isDeleting) return;
|
|
1268
|
+
setIsDeleting(true);
|
|
1269
|
+
const idsToDelete = Array.from(selectedIds);
|
|
1270
|
+
const itemsToDelete = items.filter((i) => selectedIds.has(i.id));
|
|
1271
|
+
const pathsToDelete = itemsToDelete.map((i) => i.savedTo).filter((p) => typeof p === "string" && p.trim().length > 0);
|
|
1272
|
+
const fallbackAttachmentIds = itemsToDelete.filter((i) => !i.savedTo || !i.savedTo.trim()).map((i) => i.id);
|
|
1273
|
+
let deletedFiles = 0;
|
|
1274
|
+
let failedFilesCount = 0;
|
|
1275
|
+
if (deleteWorkspaceFiles && (pathsToDelete.length > 0 || fallbackAttachmentIds.length > 0)) try {
|
|
1276
|
+
const res = await fetch(DELETE_ROUTE, {
|
|
1277
|
+
method: "POST",
|
|
1278
|
+
headers: { "content-type": "application/json" },
|
|
1279
|
+
body: JSON.stringify({
|
|
1280
|
+
paths: pathsToDelete,
|
|
1281
|
+
attachmentIds: fallbackAttachmentIds
|
|
1282
|
+
})
|
|
1283
|
+
});
|
|
1284
|
+
if (res.ok) {
|
|
1285
|
+
const data = await res.json();
|
|
1286
|
+
deletedFiles = data.deletedCount ?? 0;
|
|
1287
|
+
if (Array.isArray(data.failedFiles)) failedFilesCount = data.failedFiles.length;
|
|
1288
|
+
}
|
|
1289
|
+
} catch (err) {
|
|
1290
|
+
console.warn("[dsh-image-gen] Workspace file deletion failed:", err);
|
|
1291
|
+
}
|
|
1292
|
+
try {
|
|
1293
|
+
await bulkDeleteGalleryItems(idsToDelete);
|
|
1294
|
+
} catch (err) {
|
|
1295
|
+
setIsDeleting(false);
|
|
1296
|
+
showToast(t("deleteFailedDatabase"));
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
if (previewItem && selectedIds.has(previewItem.id)) handleClosePreview();
|
|
1300
|
+
setIsDeleting(false);
|
|
1301
|
+
setShowDeleteModal(false);
|
|
1302
|
+
if (isManageMode) handleExitManage();
|
|
1303
|
+
else {
|
|
1304
|
+
setSelectedIds(/* @__PURE__ */ new Set());
|
|
1305
|
+
lastSelectedIndexRef.current = null;
|
|
1306
|
+
}
|
|
1307
|
+
if (failedFilesCount > 0) showToast(t("deleteFailedFileLocked", {
|
|
1308
|
+
count: String(idsToDelete.length),
|
|
1309
|
+
files: String(failedFilesCount)
|
|
1310
|
+
}));
|
|
1311
|
+
else if (deletedFiles > 0) showToast(t("batchDeletedWithFilesToast", {
|
|
1312
|
+
count: String(idsToDelete.length),
|
|
1313
|
+
files: String(deletedFiles)
|
|
1314
|
+
}));
|
|
1315
|
+
else showToast(t("batchDeletedToast", { count: String(idsToDelete.length) }));
|
|
1316
|
+
};
|
|
353
1317
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
354
1318
|
className: "dsh-ig-gallery-page",
|
|
355
1319
|
children: [
|
|
356
1320
|
(0, react_jsx_runtime.jsxs)("header", {
|
|
357
|
-
className: "dsh-ig-
|
|
358
|
-
children: [
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
className:
|
|
362
|
-
|
|
1321
|
+
className: "dsh-ig-studio-tabs-bar",
|
|
1322
|
+
children: [
|
|
1323
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
1324
|
+
type: "button",
|
|
1325
|
+
className: `dsh-ig-studio-tab-btn ${activeTab === "gallery" ? "is-active" : ""}`,
|
|
1326
|
+
onClick: () => setActiveTab("gallery"),
|
|
1327
|
+
children: [(0, react_jsx_runtime.jsx)(Image$1, { size: 15 }), (0, react_jsx_runtime.jsx)("span", { children: t("tabGallery") })]
|
|
1328
|
+
}),
|
|
1329
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
1330
|
+
type: "button",
|
|
1331
|
+
className: `dsh-ig-studio-tab-btn ${activeTab === "studio" ? "is-active" : ""}`,
|
|
1332
|
+
onClick: () => setActiveTab("studio"),
|
|
1333
|
+
children: [(0, react_jsx_runtime.jsx)(SlidersHorizontal, { size: 15 }), (0, react_jsx_runtime.jsx)("span", { children: t("tabStudio") })]
|
|
1334
|
+
}),
|
|
1335
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
1336
|
+
type: "button",
|
|
1337
|
+
className: `dsh-ig-studio-tab-btn ${activeTab === "favorites" ? "is-active" : ""}`,
|
|
1338
|
+
onClick: () => setActiveTab("favorites"),
|
|
1339
|
+
children: [(0, react_jsx_runtime.jsx)(Heart, { size: 15 }), (0, react_jsx_runtime.jsx)("span", { children: t("tabFavorites") })]
|
|
1340
|
+
}),
|
|
1341
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
1342
|
+
type: "button",
|
|
1343
|
+
className: `dsh-ig-studio-tab-btn ${activeTab === "compare" ? "is-active" : ""}`,
|
|
1344
|
+
onClick: () => setActiveTab("compare"),
|
|
1345
|
+
children: [(0, react_jsx_runtime.jsx)(GitCompare, { size: 15 }), (0, react_jsx_runtime.jsx)("span", { children: t("tabCompare") })]
|
|
1346
|
+
}),
|
|
1347
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
1348
|
+
type: "button",
|
|
1349
|
+
className: `dsh-ig-studio-tab-btn ${activeTab === "tasks" ? "is-active" : ""}`,
|
|
1350
|
+
onClick: () => setActiveTab("tasks"),
|
|
1351
|
+
children: [(0, react_jsx_runtime.jsx)(ListTodo, { size: 15 }), (0, react_jsx_runtime.jsx)("span", { children: t("tabTasks") })]
|
|
363
1352
|
})
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
1353
|
+
]
|
|
1354
|
+
}),
|
|
1355
|
+
(activeTab === "gallery" || activeTab === "favorites") && (0, react_jsx_runtime.jsxs)("div", {
|
|
1356
|
+
className: "dsh-ig-studio-toolbar",
|
|
1357
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
1358
|
+
className: "dsh-ig-studio-toolbar-left",
|
|
1359
|
+
children: [
|
|
1360
|
+
(0, react_jsx_runtime.jsxs)("select", {
|
|
1361
|
+
className: "dsh-ig-studio-select",
|
|
1362
|
+
value: selectedProvider,
|
|
1363
|
+
onChange: (e) => setSelectedProvider(e.target.value),
|
|
1364
|
+
children: [
|
|
1365
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
1366
|
+
value: "all",
|
|
1367
|
+
children: t("filterAllProviders")
|
|
1368
|
+
}),
|
|
1369
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
1370
|
+
value: "google",
|
|
1371
|
+
children: t("filterGoogle")
|
|
1372
|
+
}),
|
|
1373
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
1374
|
+
value: "openai",
|
|
1375
|
+
children: t("filterOpenAI")
|
|
1376
|
+
}),
|
|
1377
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
1378
|
+
value: "seedream",
|
|
1379
|
+
children: t("filterSeedream")
|
|
1380
|
+
}),
|
|
1381
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
1382
|
+
value: "dashscope",
|
|
1383
|
+
children: t("filterDashScope")
|
|
1384
|
+
}),
|
|
1385
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
1386
|
+
value: "comfyui",
|
|
1387
|
+
children: t("filterComfyUI")
|
|
1388
|
+
})
|
|
1389
|
+
]
|
|
1390
|
+
}),
|
|
1391
|
+
(0, react_jsx_runtime.jsxs)("select", {
|
|
1392
|
+
className: "dsh-ig-studio-select",
|
|
1393
|
+
value: selectedRatio,
|
|
1394
|
+
onChange: (e) => setSelectedRatio(e.target.value),
|
|
1395
|
+
children: [
|
|
1396
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
1397
|
+
value: "all",
|
|
1398
|
+
children: t("filterAllRatios")
|
|
1399
|
+
}),
|
|
1400
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
1401
|
+
value: "1:1",
|
|
1402
|
+
children: "1:1"
|
|
1403
|
+
}),
|
|
1404
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
1405
|
+
value: "16:9",
|
|
1406
|
+
children: "16:9"
|
|
1407
|
+
}),
|
|
1408
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
1409
|
+
value: "9:16",
|
|
1410
|
+
children: "9:16"
|
|
1411
|
+
}),
|
|
1412
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
1413
|
+
value: "4:3",
|
|
1414
|
+
children: "4:3"
|
|
1415
|
+
}),
|
|
1416
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
1417
|
+
value: "3:4",
|
|
1418
|
+
children: "3:4"
|
|
1419
|
+
}),
|
|
1420
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
1421
|
+
value: "3:2",
|
|
1422
|
+
children: "3:2"
|
|
1423
|
+
}),
|
|
1424
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
1425
|
+
value: "2:3",
|
|
1426
|
+
children: "2:3"
|
|
1427
|
+
})
|
|
1428
|
+
]
|
|
1429
|
+
}),
|
|
1430
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1431
|
+
className: "dsh-ig-studio-search-wrap",
|
|
1432
|
+
children: [(0, react_jsx_runtime.jsx)(Search, {
|
|
1433
|
+
className: "dsh-ig-studio-search-icon",
|
|
1434
|
+
size: 14
|
|
1435
|
+
}), (0, react_jsx_runtime.jsx)("input", {
|
|
1436
|
+
type: "text",
|
|
1437
|
+
className: "dsh-ig-studio-search-input",
|
|
1438
|
+
placeholder: t("searchPlaceholder"),
|
|
1439
|
+
value: search,
|
|
1440
|
+
onChange: (e) => setSearch(e.target.value)
|
|
387
1441
|
})]
|
|
388
|
-
})
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
1442
|
+
})
|
|
1443
|
+
]
|
|
1444
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
1445
|
+
className: "dsh-ig-studio-toolbar-right",
|
|
1446
|
+
children: [(0, react_jsx_runtime.jsxs)("select", {
|
|
1447
|
+
className: "dsh-ig-studio-select dsh-ig-studio-select-sort",
|
|
1448
|
+
value: sortBy,
|
|
1449
|
+
onChange: (e) => setSortBy(e.target.value),
|
|
1450
|
+
children: [(0, react_jsx_runtime.jsx)("option", {
|
|
1451
|
+
value: "newest",
|
|
1452
|
+
children: t("sortNewest")
|
|
1453
|
+
}), (0, react_jsx_runtime.jsx)("option", {
|
|
1454
|
+
value: "oldest",
|
|
1455
|
+
children: t("sortOldest")
|
|
394
1456
|
})]
|
|
395
|
-
}), (0, react_jsx_runtime.jsxs)("
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
(
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
(0, react_jsx_runtime.jsx)("option", {
|
|
405
|
-
value: "google",
|
|
406
|
-
children: t("filterGoogle")
|
|
407
|
-
}),
|
|
408
|
-
(0, react_jsx_runtime.jsx)("option", {
|
|
409
|
-
value: "openai",
|
|
410
|
-
children: t("filterOpenAI")
|
|
411
|
-
}),
|
|
412
|
-
(0, react_jsx_runtime.jsx)("option", {
|
|
413
|
-
value: "seedream",
|
|
414
|
-
children: t("filterSeedream")
|
|
415
|
-
}),
|
|
416
|
-
(0, react_jsx_runtime.jsx)("option", {
|
|
417
|
-
value: "dashscope",
|
|
418
|
-
children: t("filterDashScope")
|
|
419
|
-
})
|
|
420
|
-
]
|
|
1457
|
+
}), (0, react_jsx_runtime.jsxs)("button", {
|
|
1458
|
+
type: "button",
|
|
1459
|
+
className: `dsh-ig-studio-btn dsh-ig-studio-btn-danger ${isManageMode ? "is-active" : ""}`,
|
|
1460
|
+
title: isManageMode ? t("exitManage") : t("manage"),
|
|
1461
|
+
onClick: () => {
|
|
1462
|
+
if (isManageMode) handleExitManage();
|
|
1463
|
+
else setIsManageMode(true);
|
|
1464
|
+
},
|
|
1465
|
+
children: [(0, react_jsx_runtime.jsx)(Trash2, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: isManageMode ? t("exitManage") : t("manage") })]
|
|
421
1466
|
})]
|
|
422
1467
|
})]
|
|
423
1468
|
}),
|
|
424
1469
|
(0, react_jsx_runtime.jsx)("div", {
|
|
425
1470
|
className: "dsh-ig-gallery-page-body",
|
|
426
|
-
children: items.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
|
|
1471
|
+
children: activeTab === "gallery" || activeTab === "favorites" ? items.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
|
|
427
1472
|
className: "dsh-ig-gallery-empty",
|
|
428
1473
|
children: [
|
|
429
1474
|
(0, react_jsx_runtime.jsx)("div", {
|
|
@@ -439,6 +1484,22 @@ window.__ModuleLoader__.load({
|
|
|
439
1484
|
children: t("emptyDesc")
|
|
440
1485
|
})
|
|
441
1486
|
]
|
|
1487
|
+
}) : activeTab === "favorites" && filteredItems.length === 0 && search === "" && selectedProvider === "all" && selectedRatio === "all" ? (0, react_jsx_runtime.jsxs)("div", {
|
|
1488
|
+
className: "dsh-ig-gallery-empty",
|
|
1489
|
+
children: [
|
|
1490
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
1491
|
+
className: "dsh-ig-gallery-empty-icon",
|
|
1492
|
+
children: "🤍"
|
|
1493
|
+
}),
|
|
1494
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
1495
|
+
className: "dsh-ig-gallery-empty-title",
|
|
1496
|
+
children: t("favEmptyTitle")
|
|
1497
|
+
}),
|
|
1498
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
1499
|
+
className: "dsh-ig-gallery-empty-desc",
|
|
1500
|
+
children: t("favEmptyDesc")
|
|
1501
|
+
})
|
|
1502
|
+
]
|
|
442
1503
|
}) : filteredItems.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
|
|
443
1504
|
className: "dsh-ig-gallery-empty",
|
|
444
1505
|
children: [
|
|
@@ -457,82 +1518,212 @@ window.__ModuleLoader__.load({
|
|
|
457
1518
|
]
|
|
458
1519
|
}) : (0, react_jsx_runtime.jsx)("div", {
|
|
459
1520
|
className: "dsh-ig-gallery-grid",
|
|
460
|
-
children: filteredItems.map((item) => (0, react_jsx_runtime.jsx)(GalleryCard, {
|
|
1521
|
+
children: filteredItems.map((item, idx) => (0, react_jsx_runtime.jsx)(GalleryCard, {
|
|
461
1522
|
item,
|
|
1523
|
+
index: idx,
|
|
1524
|
+
isManageMode,
|
|
1525
|
+
isSelected: selectedIds.has(item.id),
|
|
462
1526
|
t,
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
1527
|
+
onClick: (e, blob) => handleCardClick(item, idx, e, blob),
|
|
1528
|
+
onToggleSelect: (e) => handleToggleSelect(item.id, idx, e),
|
|
1529
|
+
onRequestDelete: () => requestSingleDelete(item),
|
|
1530
|
+
onPreview: (blob) => openPreviewItem(item, blob),
|
|
1531
|
+
onBlobLoaded: (b) => blobCache.set(item.id, b),
|
|
468
1532
|
onToast: showToast
|
|
469
1533
|
}, item.id))
|
|
1534
|
+
}) : activeTab === "studio" ? (0, react_jsx_runtime.jsx)(PlaceholderView, {
|
|
1535
|
+
icon: "🎨",
|
|
1536
|
+
title: t("studioTitle"),
|
|
1537
|
+
description: t("studioDesc"),
|
|
1538
|
+
tip: t("studioTip"),
|
|
1539
|
+
badge: t("comingSoonBadge")
|
|
1540
|
+
}) : activeTab === "compare" ? (0, react_jsx_runtime.jsx)(PlaceholderView, {
|
|
1541
|
+
icon: "🔀",
|
|
1542
|
+
title: t("compareTitle"),
|
|
1543
|
+
description: t("compareDesc"),
|
|
1544
|
+
badge: t("comingSoonBadge")
|
|
1545
|
+
}) : (0, react_jsx_runtime.jsx)(PlaceholderView, {
|
|
1546
|
+
icon: "📋",
|
|
1547
|
+
title: t("tasksTitle"),
|
|
1548
|
+
description: t("tasksDesc"),
|
|
1549
|
+
badge: t("comingSoonBadge")
|
|
470
1550
|
})
|
|
471
1551
|
}),
|
|
472
1552
|
toast && (0, react_jsx_runtime.jsx)("div", {
|
|
473
1553
|
className: "dsh-ig-gallery-page-toast",
|
|
474
1554
|
children: toast
|
|
475
1555
|
}),
|
|
476
|
-
|
|
477
|
-
className: "dsh-ig-
|
|
1556
|
+
(isManageMode || selectedIds.size > 0) && (0, react_jsx_runtime.jsxs)("div", {
|
|
1557
|
+
className: "dsh-ig-batch-bar",
|
|
1558
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
1559
|
+
className: "dsh-ig-batch-bar-left",
|
|
1560
|
+
children: [
|
|
1561
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1562
|
+
className: "dsh-ig-batch-counter",
|
|
1563
|
+
children: t("selectedCount", { n: String(selectedIds.size) })
|
|
1564
|
+
}),
|
|
1565
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1566
|
+
type: "button",
|
|
1567
|
+
className: "dsh-ig-batch-btn",
|
|
1568
|
+
onClick: handleSelectAll,
|
|
1569
|
+
children: t("selectAll")
|
|
1570
|
+
}),
|
|
1571
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1572
|
+
type: "button",
|
|
1573
|
+
className: "dsh-ig-batch-btn",
|
|
1574
|
+
onClick: handleInvertSelect,
|
|
1575
|
+
children: t("invertSelect")
|
|
1576
|
+
}),
|
|
1577
|
+
selectedIds.size > 0 && (0, react_jsx_runtime.jsx)("button", {
|
|
1578
|
+
type: "button",
|
|
1579
|
+
className: "dsh-ig-batch-btn",
|
|
1580
|
+
onClick: handleClearSelect,
|
|
1581
|
+
children: t("clearSelect")
|
|
1582
|
+
})
|
|
1583
|
+
]
|
|
1584
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
1585
|
+
className: "dsh-ig-batch-bar-right",
|
|
1586
|
+
children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
1587
|
+
type: "button",
|
|
1588
|
+
className: "dsh-ig-batch-btn dsh-ig-batch-btn-danger",
|
|
1589
|
+
disabled: selectedIds.size === 0,
|
|
1590
|
+
onClick: () => setShowDeleteModal(true),
|
|
1591
|
+
children: [(0, react_jsx_runtime.jsx)(Trash2, { size: 13 }), (0, react_jsx_runtime.jsxs)("span", { children: [t("batchDelete"), selectedIds.size > 0 ? ` (${selectedIds.size})` : ""] })]
|
|
1592
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
1593
|
+
type: "button",
|
|
1594
|
+
className: "dsh-ig-batch-btn dsh-ig-batch-btn-exit",
|
|
1595
|
+
onClick: handleExitManage,
|
|
1596
|
+
children: t("exitManage")
|
|
1597
|
+
})]
|
|
1598
|
+
})]
|
|
1599
|
+
}),
|
|
1600
|
+
showDeleteModal && (0, react_jsx_runtime.jsx)("div", {
|
|
1601
|
+
className: "dsh-ig-modal-backdrop",
|
|
478
1602
|
onClick: () => {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
1603
|
+
if (!isDeleting) {
|
|
1604
|
+
setShowDeleteModal(false);
|
|
1605
|
+
if (!isManageMode) setSelectedIds(/* @__PURE__ */ new Set());
|
|
1606
|
+
}
|
|
482
1607
|
},
|
|
1608
|
+
children: (0, react_jsx_runtime.jsxs)("div", {
|
|
1609
|
+
className: "dsh-ig-modal-box",
|
|
1610
|
+
onClick: (e) => e.stopPropagation(),
|
|
1611
|
+
children: [
|
|
1612
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1613
|
+
className: "dsh-ig-modal-header",
|
|
1614
|
+
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
1615
|
+
className: "dsh-ig-modal-icon-danger",
|
|
1616
|
+
children: (0, react_jsx_runtime.jsx)(TriangleAlert, { size: 20 })
|
|
1617
|
+
}), (0, react_jsx_runtime.jsx)("div", {
|
|
1618
|
+
className: "dsh-ig-modal-title",
|
|
1619
|
+
children: selectedIds.size === 1 ? t("batchDeleteTitleSingle") : t("batchDeleteTitle", { count: String(selectedIds.size) })
|
|
1620
|
+
})]
|
|
1621
|
+
}),
|
|
1622
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1623
|
+
className: "dsh-ig-modal-body",
|
|
1624
|
+
children: [(0, react_jsx_runtime.jsx)("p", {
|
|
1625
|
+
className: "dsh-ig-modal-desc",
|
|
1626
|
+
children: t("batchDeleteDesc")
|
|
1627
|
+
}), (0, react_jsx_runtime.jsxs)("label", {
|
|
1628
|
+
className: "dsh-ig-modal-checkbox-label",
|
|
1629
|
+
children: [(0, react_jsx_runtime.jsx)("input", {
|
|
1630
|
+
type: "checkbox",
|
|
1631
|
+
checked: deleteWorkspaceFiles,
|
|
1632
|
+
onChange: (e) => setDeleteWorkspaceFiles(e.target.checked),
|
|
1633
|
+
disabled: isDeleting
|
|
1634
|
+
}), (0, react_jsx_runtime.jsx)("span", { children: t("deleteWorkspaceFilesOpt") })]
|
|
1635
|
+
})]
|
|
1636
|
+
}),
|
|
1637
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1638
|
+
className: "dsh-ig-modal-footer",
|
|
1639
|
+
children: [(0, react_jsx_runtime.jsx)("button", {
|
|
1640
|
+
type: "button",
|
|
1641
|
+
className: "dsh-ig-modal-btn dsh-ig-modal-btn-cancel",
|
|
1642
|
+
onClick: () => {
|
|
1643
|
+
setShowDeleteModal(false);
|
|
1644
|
+
if (!isManageMode) setSelectedIds(/* @__PURE__ */ new Set());
|
|
1645
|
+
},
|
|
1646
|
+
disabled: isDeleting,
|
|
1647
|
+
children: t("cancel")
|
|
1648
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
1649
|
+
type: "button",
|
|
1650
|
+
className: "dsh-ig-modal-btn dsh-ig-modal-btn-danger",
|
|
1651
|
+
onClick: handleConfirmBatchDelete,
|
|
1652
|
+
disabled: isDeleting,
|
|
1653
|
+
children: isDeleting ? "..." : selectedIds.size === 1 ? t("confirmDeleteSingle") : t("confirmBatchDelete", { count: String(selectedIds.size) })
|
|
1654
|
+
})]
|
|
1655
|
+
})
|
|
1656
|
+
]
|
|
1657
|
+
})
|
|
1658
|
+
}),
|
|
1659
|
+
previewItem && (0, react_jsx_runtime.jsxs)("div", {
|
|
1660
|
+
className: "dsh-ig-lightbox-backdrop",
|
|
1661
|
+
onClick: handleClosePreview,
|
|
483
1662
|
children: [
|
|
484
1663
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
485
1664
|
className: "dsh-ig-lightbox-topbar",
|
|
486
1665
|
onClick: (e) => e.stopPropagation(),
|
|
487
1666
|
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
488
1667
|
className: "dsh-ig-lightbox-meta",
|
|
489
|
-
children: [
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
1668
|
+
children: [
|
|
1669
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1670
|
+
className: "dsh-ig-tag",
|
|
1671
|
+
children: previewItem.provider
|
|
1672
|
+
}),
|
|
1673
|
+
previewItem.model ? (0, react_jsx_runtime.jsx)("span", {
|
|
1674
|
+
className: "dsh-ig-tag dsh-ig-tag-model",
|
|
1675
|
+
children: previewItem.model
|
|
1676
|
+
}) : null,
|
|
1677
|
+
currentPreviewIndex >= 0 && (0, react_jsx_runtime.jsxs)("span", {
|
|
1678
|
+
className: "dsh-ig-lightbox-counter",
|
|
1679
|
+
children: [
|
|
1680
|
+
currentPreviewIndex + 1,
|
|
1681
|
+
" / ",
|
|
1682
|
+
filteredItems.length
|
|
1683
|
+
]
|
|
1684
|
+
})
|
|
1685
|
+
]
|
|
496
1686
|
}), (0, react_jsx_runtime.jsx)("button", {
|
|
497
1687
|
type: "button",
|
|
498
1688
|
className: "dsh-ig-lightbox-close-btn",
|
|
499
1689
|
title: t("close"),
|
|
500
|
-
onClick:
|
|
501
|
-
|
|
502
|
-
setPreviewUrl(null);
|
|
503
|
-
setPreviewBlob(null);
|
|
504
|
-
},
|
|
505
|
-
children: (0, react_jsx_runtime.jsxs)("svg", {
|
|
506
|
-
width: "18",
|
|
507
|
-
height: "18",
|
|
508
|
-
viewBox: "0 0 24 24",
|
|
509
|
-
fill: "none",
|
|
510
|
-
stroke: "currentColor",
|
|
511
|
-
strokeWidth: "2",
|
|
512
|
-
strokeLinecap: "round",
|
|
513
|
-
strokeLinejoin: "round",
|
|
514
|
-
children: [(0, react_jsx_runtime.jsx)("line", {
|
|
515
|
-
x1: "18",
|
|
516
|
-
y1: "6",
|
|
517
|
-
x2: "6",
|
|
518
|
-
y2: "18"
|
|
519
|
-
}), (0, react_jsx_runtime.jsx)("line", {
|
|
520
|
-
x1: "6",
|
|
521
|
-
y1: "6",
|
|
522
|
-
x2: "18",
|
|
523
|
-
y2: "18"
|
|
524
|
-
})]
|
|
525
|
-
})
|
|
1690
|
+
onClick: handleClosePreview,
|
|
1691
|
+
children: (0, react_jsx_runtime.jsx)(X, { size: 18 })
|
|
526
1692
|
})]
|
|
527
1693
|
}),
|
|
1694
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1695
|
+
type: "button",
|
|
1696
|
+
className: "dsh-ig-lightbox-nav-btn dsh-ig-lightbox-nav-prev",
|
|
1697
|
+
title: t("prevImage"),
|
|
1698
|
+
disabled: !hasPrev,
|
|
1699
|
+
onClick: (e) => {
|
|
1700
|
+
e.stopPropagation();
|
|
1701
|
+
goToPrev();
|
|
1702
|
+
},
|
|
1703
|
+
children: (0, react_jsx_runtime.jsx)(ChevronLeft, { size: 24 })
|
|
1704
|
+
}),
|
|
1705
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1706
|
+
type: "button",
|
|
1707
|
+
className: "dsh-ig-lightbox-nav-btn dsh-ig-lightbox-nav-next",
|
|
1708
|
+
title: t("nextImage"),
|
|
1709
|
+
disabled: !hasNext,
|
|
1710
|
+
onClick: (e) => {
|
|
1711
|
+
e.stopPropagation();
|
|
1712
|
+
goToNext();
|
|
1713
|
+
},
|
|
1714
|
+
children: (0, react_jsx_runtime.jsx)(ChevronRight, { size: 24 })
|
|
1715
|
+
}),
|
|
528
1716
|
(0, react_jsx_runtime.jsx)("div", {
|
|
529
1717
|
className: "dsh-ig-lightbox-img-wrap",
|
|
530
1718
|
onClick: (e) => e.stopPropagation(),
|
|
531
|
-
children: (0, react_jsx_runtime.jsx)("
|
|
1719
|
+
children: previewLoading ? (0, react_jsx_runtime.jsx)("div", {
|
|
1720
|
+
className: "dsh-ig-lightbox-loading",
|
|
1721
|
+
children: (0, react_jsx_runtime.jsx)("div", { className: "dsh-ig-lightbox-spinner" })
|
|
1722
|
+
}) : previewUrl ? (0, react_jsx_runtime.jsx)("img", {
|
|
532
1723
|
className: "dsh-ig-lightbox-img",
|
|
533
1724
|
src: previewUrl,
|
|
534
1725
|
alt: previewItem.prompt
|
|
535
|
-
})
|
|
1726
|
+
}) : null
|
|
536
1727
|
}),
|
|
537
1728
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
538
1729
|
className: "dsh-ig-lightbox-bottombar",
|
|
@@ -552,24 +1743,7 @@ window.__ModuleLoader__.load({
|
|
|
552
1743
|
await navigator.clipboard.writeText(previewItem.prompt);
|
|
553
1744
|
showToast(t("copiedPrompt"));
|
|
554
1745
|
},
|
|
555
|
-
children: [(0, react_jsx_runtime.
|
|
556
|
-
width: "14",
|
|
557
|
-
height: "14",
|
|
558
|
-
viewBox: "0 0 24 24",
|
|
559
|
-
fill: "none",
|
|
560
|
-
stroke: "currentColor",
|
|
561
|
-
strokeWidth: "2",
|
|
562
|
-
strokeLinecap: "round",
|
|
563
|
-
strokeLinejoin: "round",
|
|
564
|
-
children: [(0, react_jsx_runtime.jsx)("path", { d: "M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" }), (0, react_jsx_runtime.jsx)("rect", {
|
|
565
|
-
x: "8",
|
|
566
|
-
y: "2",
|
|
567
|
-
width: "8",
|
|
568
|
-
height: "4",
|
|
569
|
-
rx: "1",
|
|
570
|
-
ry: "1"
|
|
571
|
-
})]
|
|
572
|
-
}), (0, react_jsx_runtime.jsx)("span", { children: t("copyPpt") })]
|
|
1746
|
+
children: [(0, react_jsx_runtime.jsx)(FileText, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("copyPpt") })]
|
|
573
1747
|
}),
|
|
574
1748
|
(0, react_jsx_runtime.jsxs)("button", {
|
|
575
1749
|
type: "button",
|
|
@@ -580,30 +1754,14 @@ window.__ModuleLoader__.load({
|
|
|
580
1754
|
const ok = await copyImageBlob(previewBlob);
|
|
581
1755
|
showToast(ok ? t("copiedImage") : t("copyFailed"));
|
|
582
1756
|
},
|
|
583
|
-
children: [(0, react_jsx_runtime.
|
|
584
|
-
width: "14",
|
|
585
|
-
height: "14",
|
|
586
|
-
viewBox: "0 0 24 24",
|
|
587
|
-
fill: "none",
|
|
588
|
-
stroke: "currentColor",
|
|
589
|
-
strokeWidth: "2",
|
|
590
|
-
strokeLinecap: "round",
|
|
591
|
-
strokeLinejoin: "round",
|
|
592
|
-
children: [(0, react_jsx_runtime.jsx)("rect", {
|
|
593
|
-
x: "9",
|
|
594
|
-
y: "9",
|
|
595
|
-
width: "13",
|
|
596
|
-
height: "13",
|
|
597
|
-
rx: "2",
|
|
598
|
-
ry: "2"
|
|
599
|
-
}), (0, react_jsx_runtime.jsx)("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })]
|
|
600
|
-
}), (0, react_jsx_runtime.jsx)("span", { children: t("copyImg") })]
|
|
1757
|
+
children: [(0, react_jsx_runtime.jsx)(Copy, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("copyImg") })]
|
|
601
1758
|
}),
|
|
602
1759
|
(0, react_jsx_runtime.jsxs)("button", {
|
|
603
1760
|
type: "button",
|
|
604
1761
|
className: "dsh-ig-lightbox-btn",
|
|
605
1762
|
title: t("download"),
|
|
606
1763
|
onClick: () => {
|
|
1764
|
+
if (!previewUrl) return;
|
|
607
1765
|
const a = document.createElement("a");
|
|
608
1766
|
a.href = previewUrl;
|
|
609
1767
|
a.download = `dsh-${previewItem.provider}-${previewItem.id}.png`;
|
|
@@ -611,65 +1769,16 @@ window.__ModuleLoader__.load({
|
|
|
611
1769
|
a.click();
|
|
612
1770
|
document.body.removeChild(a);
|
|
613
1771
|
},
|
|
614
|
-
children: [(0, react_jsx_runtime.
|
|
615
|
-
width: "14",
|
|
616
|
-
height: "14",
|
|
617
|
-
viewBox: "0 0 24 24",
|
|
618
|
-
fill: "none",
|
|
619
|
-
stroke: "currentColor",
|
|
620
|
-
strokeWidth: "2",
|
|
621
|
-
strokeLinecap: "round",
|
|
622
|
-
strokeLinejoin: "round",
|
|
623
|
-
children: [
|
|
624
|
-
(0, react_jsx_runtime.jsx)("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
625
|
-
(0, react_jsx_runtime.jsx)("polyline", { points: "7 10 12 15 17 10" }),
|
|
626
|
-
(0, react_jsx_runtime.jsx)("line", {
|
|
627
|
-
x1: "12",
|
|
628
|
-
y1: "15",
|
|
629
|
-
x2: "12",
|
|
630
|
-
y2: "3"
|
|
631
|
-
})
|
|
632
|
-
]
|
|
633
|
-
}), (0, react_jsx_runtime.jsx)("span", { children: t("download") })]
|
|
1772
|
+
children: [(0, react_jsx_runtime.jsx)(Download, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("download") })]
|
|
634
1773
|
}),
|
|
635
1774
|
(0, react_jsx_runtime.jsxs)("button", {
|
|
636
1775
|
type: "button",
|
|
637
1776
|
className: "dsh-ig-lightbox-btn dsh-ig-lightbox-btn-danger",
|
|
638
1777
|
title: t("delete"),
|
|
639
|
-
onClick:
|
|
640
|
-
if (
|
|
641
|
-
await deleteGalleryItem(previewItem.id);
|
|
642
|
-
setPreviewItem(null);
|
|
643
|
-
setPreviewUrl(null);
|
|
644
|
-
setPreviewBlob(null);
|
|
645
|
-
showToast(t("deleted"));
|
|
1778
|
+
onClick: () => {
|
|
1779
|
+
if (previewItem) requestSingleDelete(previewItem);
|
|
646
1780
|
},
|
|
647
|
-
children: [(0, react_jsx_runtime.
|
|
648
|
-
width: "14",
|
|
649
|
-
height: "14",
|
|
650
|
-
viewBox: "0 0 24 24",
|
|
651
|
-
fill: "none",
|
|
652
|
-
stroke: "currentColor",
|
|
653
|
-
strokeWidth: "2",
|
|
654
|
-
strokeLinecap: "round",
|
|
655
|
-
strokeLinejoin: "round",
|
|
656
|
-
children: [
|
|
657
|
-
(0, react_jsx_runtime.jsx)("polyline", { points: "3 6 5 6 21 6" }),
|
|
658
|
-
(0, react_jsx_runtime.jsx)("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }),
|
|
659
|
-
(0, react_jsx_runtime.jsx)("line", {
|
|
660
|
-
x1: "10",
|
|
661
|
-
y1: "11",
|
|
662
|
-
x2: "10",
|
|
663
|
-
y2: "17"
|
|
664
|
-
}),
|
|
665
|
-
(0, react_jsx_runtime.jsx)("line", {
|
|
666
|
-
x1: "14",
|
|
667
|
-
y1: "11",
|
|
668
|
-
x2: "14",
|
|
669
|
-
y2: "17"
|
|
670
|
-
})
|
|
671
|
-
]
|
|
672
|
-
}), (0, react_jsx_runtime.jsx)("span", { children: t("delete") })]
|
|
1781
|
+
children: [(0, react_jsx_runtime.jsx)(Trash2, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("delete") })]
|
|
673
1782
|
})
|
|
674
1783
|
]
|
|
675
1784
|
})]
|
|
@@ -679,7 +1788,7 @@ window.__ModuleLoader__.load({
|
|
|
679
1788
|
]
|
|
680
1789
|
});
|
|
681
1790
|
};
|
|
682
|
-
const GalleryCard = ({ item, t, onPreview, onToast }) => {
|
|
1791
|
+
const GalleryCard = ({ item, index: _index, isManageMode, isSelected, t, onClick, onToggleSelect, onRequestDelete, onPreview: _onPreview, onBlobLoaded, onToast }) => {
|
|
683
1792
|
const [url, setUrl] = (0, react.useState)();
|
|
684
1793
|
const [blob, setBlob] = (0, react.useState)();
|
|
685
1794
|
const [loading, setLoading] = (0, react.useState)(true);
|
|
@@ -697,6 +1806,7 @@ window.__ModuleLoader__.load({
|
|
|
697
1806
|
const resBlob = await response.blob();
|
|
698
1807
|
if (controller.signal.aborted) return;
|
|
699
1808
|
setBlob(resBlob);
|
|
1809
|
+
onBlobLoaded?.(resBlob);
|
|
700
1810
|
objectUrl = URL.createObjectURL(resBlob);
|
|
701
1811
|
setUrl(objectUrl);
|
|
702
1812
|
setLoading(false);
|
|
@@ -735,14 +1845,27 @@ window.__ModuleLoader__.load({
|
|
|
735
1845
|
a.click();
|
|
736
1846
|
document.body.removeChild(a);
|
|
737
1847
|
};
|
|
1848
|
+
const toggleFavorite = async (e) => {
|
|
1849
|
+
e.stopPropagation();
|
|
1850
|
+
onToast(await toggleFavoriteGalleryItem(item.id) ? t("favoriteAdded") : t("favoriteRemoved"));
|
|
1851
|
+
};
|
|
1852
|
+
const badgeLabel = item.model || item.provider;
|
|
738
1853
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
739
|
-
className:
|
|
740
|
-
onClick: () =>
|
|
741
|
-
if (url && blob) onPreview(url, blob);
|
|
742
|
-
},
|
|
1854
|
+
className: `dsh-ig-gallery-card ${isSelected ? "is-selected" : ""} ${isManageMode ? "is-manage-mode" : ""}`,
|
|
1855
|
+
onClick: (e) => onClick(e, blob),
|
|
743
1856
|
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
744
1857
|
className: "dsh-ig-gallery-card-media",
|
|
745
1858
|
children: [
|
|
1859
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1860
|
+
type: "button",
|
|
1861
|
+
className: `dsh-ig-card-checkbox ${isSelected ? "is-checked" : ""}`,
|
|
1862
|
+
title: isSelected ? t("clearSelect") : t("manage"),
|
|
1863
|
+
onClick: onToggleSelect,
|
|
1864
|
+
children: isSelected && (0, react_jsx_runtime.jsx)(Check, {
|
|
1865
|
+
size: 11,
|
|
1866
|
+
strokeWidth: 3
|
|
1867
|
+
})
|
|
1868
|
+
}),
|
|
746
1869
|
loading && (0, react_jsx_runtime.jsx)("div", {
|
|
747
1870
|
className: "dsh-ig-gallery-card-loading",
|
|
748
1871
|
children: "..."
|
|
@@ -767,50 +1890,14 @@ window.__ModuleLoader__.load({
|
|
|
767
1890
|
onClick: (e) => {
|
|
768
1891
|
copyImage(e);
|
|
769
1892
|
},
|
|
770
|
-
children: (0, react_jsx_runtime.
|
|
771
|
-
width: "14",
|
|
772
|
-
height: "14",
|
|
773
|
-
viewBox: "0 0 24 24",
|
|
774
|
-
fill: "none",
|
|
775
|
-
stroke: "currentColor",
|
|
776
|
-
strokeWidth: "2",
|
|
777
|
-
strokeLinecap: "round",
|
|
778
|
-
strokeLinejoin: "round",
|
|
779
|
-
children: [(0, react_jsx_runtime.jsx)("rect", {
|
|
780
|
-
x: "9",
|
|
781
|
-
y: "9",
|
|
782
|
-
width: "13",
|
|
783
|
-
height: "13",
|
|
784
|
-
rx: "2",
|
|
785
|
-
ry: "2"
|
|
786
|
-
}), (0, react_jsx_runtime.jsx)("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })]
|
|
787
|
-
})
|
|
1893
|
+
children: (0, react_jsx_runtime.jsx)(Copy, { size: 13 })
|
|
788
1894
|
}),
|
|
789
1895
|
(0, react_jsx_runtime.jsx)("button", {
|
|
790
1896
|
type: "button",
|
|
791
1897
|
className: "dsh-ig-tool-btn",
|
|
792
1898
|
title: t("download"),
|
|
793
1899
|
onClick: downloadImage,
|
|
794
|
-
children: (0, react_jsx_runtime.
|
|
795
|
-
width: "14",
|
|
796
|
-
height: "14",
|
|
797
|
-
viewBox: "0 0 24 24",
|
|
798
|
-
fill: "none",
|
|
799
|
-
stroke: "currentColor",
|
|
800
|
-
strokeWidth: "2",
|
|
801
|
-
strokeLinecap: "round",
|
|
802
|
-
strokeLinejoin: "round",
|
|
803
|
-
children: [
|
|
804
|
-
(0, react_jsx_runtime.jsx)("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
805
|
-
(0, react_jsx_runtime.jsx)("polyline", { points: "7 10 12 15 17 10" }),
|
|
806
|
-
(0, react_jsx_runtime.jsx)("line", {
|
|
807
|
-
x1: "12",
|
|
808
|
-
y1: "15",
|
|
809
|
-
x2: "12",
|
|
810
|
-
y2: "3"
|
|
811
|
-
})
|
|
812
|
-
]
|
|
813
|
-
})
|
|
1900
|
+
children: (0, react_jsx_runtime.jsx)(Download, { size: 13 })
|
|
814
1901
|
}),
|
|
815
1902
|
(0, react_jsx_runtime.jsx)("button", {
|
|
816
1903
|
type: "button",
|
|
@@ -819,84 +1906,95 @@ window.__ModuleLoader__.load({
|
|
|
819
1906
|
onClick: (e) => {
|
|
820
1907
|
copyPrompt(e);
|
|
821
1908
|
},
|
|
822
|
-
children: (0, react_jsx_runtime.
|
|
823
|
-
width: "14",
|
|
824
|
-
height: "14",
|
|
825
|
-
viewBox: "0 0 24 24",
|
|
826
|
-
fill: "none",
|
|
827
|
-
stroke: "currentColor",
|
|
828
|
-
strokeWidth: "2",
|
|
829
|
-
strokeLinecap: "round",
|
|
830
|
-
strokeLinejoin: "round",
|
|
831
|
-
children: [(0, react_jsx_runtime.jsx)("path", { d: "M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" }), (0, react_jsx_runtime.jsx)("rect", {
|
|
832
|
-
x: "8",
|
|
833
|
-
y: "2",
|
|
834
|
-
width: "8",
|
|
835
|
-
height: "4",
|
|
836
|
-
rx: "1",
|
|
837
|
-
ry: "1"
|
|
838
|
-
})]
|
|
839
|
-
})
|
|
1909
|
+
children: (0, react_jsx_runtime.jsx)(FileText, { size: 13 })
|
|
840
1910
|
}),
|
|
841
1911
|
(0, react_jsx_runtime.jsx)("button", {
|
|
842
1912
|
type: "button",
|
|
843
1913
|
className: "dsh-ig-tool-btn dsh-ig-tool-btn-danger",
|
|
844
1914
|
title: t("delete"),
|
|
845
|
-
onClick:
|
|
1915
|
+
onClick: (e) => {
|
|
846
1916
|
e.stopPropagation();
|
|
847
|
-
|
|
848
|
-
await deleteGalleryItem(item.id);
|
|
849
|
-
onToast(t("deleted"));
|
|
1917
|
+
onRequestDelete();
|
|
850
1918
|
},
|
|
851
|
-
children: (0, react_jsx_runtime.
|
|
852
|
-
width: "14",
|
|
853
|
-
height: "14",
|
|
854
|
-
viewBox: "0 0 24 24",
|
|
855
|
-
fill: "none",
|
|
856
|
-
stroke: "currentColor",
|
|
857
|
-
strokeWidth: "2",
|
|
858
|
-
strokeLinecap: "round",
|
|
859
|
-
strokeLinejoin: "round",
|
|
860
|
-
children: [
|
|
861
|
-
(0, react_jsx_runtime.jsx)("polyline", { points: "3 6 5 6 21 6" }),
|
|
862
|
-
(0, react_jsx_runtime.jsx)("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }),
|
|
863
|
-
(0, react_jsx_runtime.jsx)("line", {
|
|
864
|
-
x1: "10",
|
|
865
|
-
y1: "11",
|
|
866
|
-
x2: "10",
|
|
867
|
-
y2: "17"
|
|
868
|
-
}),
|
|
869
|
-
(0, react_jsx_runtime.jsx)("line", {
|
|
870
|
-
x1: "14",
|
|
871
|
-
y1: "11",
|
|
872
|
-
x2: "14",
|
|
873
|
-
y2: "17"
|
|
874
|
-
})
|
|
875
|
-
]
|
|
876
|
-
})
|
|
1919
|
+
children: (0, react_jsx_runtime.jsx)(Trash2, { size: 13 })
|
|
877
1920
|
})
|
|
878
1921
|
]
|
|
879
1922
|
})
|
|
880
1923
|
]
|
|
881
1924
|
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
882
1925
|
className: "dsh-ig-gallery-card-meta",
|
|
883
|
-
children: [
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
})
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1926
|
+
children: [
|
|
1927
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
1928
|
+
className: "dsh-ig-card-badge-row",
|
|
1929
|
+
children: (0, react_jsx_runtime.jsx)("span", {
|
|
1930
|
+
className: "dsh-ig-card-badge",
|
|
1931
|
+
title: badgeLabel,
|
|
1932
|
+
children: badgeLabel
|
|
1933
|
+
})
|
|
1934
|
+
}),
|
|
1935
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
1936
|
+
className: "dsh-ig-gallery-card-prompt-line",
|
|
1937
|
+
title: item.prompt,
|
|
1938
|
+
children: item.prompt
|
|
1939
|
+
}),
|
|
1940
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1941
|
+
className: "dsh-ig-card-footer-row",
|
|
1942
|
+
children: [(0, react_jsx_runtime.jsxs)("span", {
|
|
1943
|
+
className: "dsh-ig-card-meta-text",
|
|
1944
|
+
children: [
|
|
1945
|
+
formatCardMeta(item),
|
|
1946
|
+
" | ",
|
|
1947
|
+
formatRelativeTime(item.createdAt, t)
|
|
1948
|
+
]
|
|
1949
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
1950
|
+
type: "button",
|
|
1951
|
+
className: `dsh-ig-card-fav-btn ${item.isFavorite ? "is-favorited" : ""}`,
|
|
1952
|
+
title: item.isFavorite ? t("favoriteRemoved") : t("favoriteAdded"),
|
|
1953
|
+
onClick: (e) => {
|
|
1954
|
+
toggleFavorite(e);
|
|
1955
|
+
},
|
|
1956
|
+
children: (0, react_jsx_runtime.jsx)(Heart, {
|
|
1957
|
+
size: 15,
|
|
1958
|
+
fill: item.isFavorite ? "currentColor" : "none"
|
|
1959
|
+
})
|
|
1960
|
+
})]
|
|
1961
|
+
})
|
|
1962
|
+
]
|
|
897
1963
|
})]
|
|
898
1964
|
});
|
|
899
1965
|
};
|
|
1966
|
+
const PlaceholderView = ({ icon, title, description, tip, badge }) => {
|
|
1967
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
1968
|
+
className: "dsh-ig-placeholder-view",
|
|
1969
|
+
children: (0, react_jsx_runtime.jsxs)("div", {
|
|
1970
|
+
className: "dsh-ig-placeholder-card",
|
|
1971
|
+
children: [
|
|
1972
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
1973
|
+
className: "dsh-ig-placeholder-icon",
|
|
1974
|
+
children: icon
|
|
1975
|
+
}),
|
|
1976
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1977
|
+
className: "dsh-ig-placeholder-header",
|
|
1978
|
+
children: [(0, react_jsx_runtime.jsx)("h2", {
|
|
1979
|
+
className: "dsh-ig-placeholder-title",
|
|
1980
|
+
children: title
|
|
1981
|
+
}), badge && (0, react_jsx_runtime.jsx)("span", {
|
|
1982
|
+
className: "dsh-ig-placeholder-badge",
|
|
1983
|
+
children: badge
|
|
1984
|
+
})]
|
|
1985
|
+
}),
|
|
1986
|
+
(0, react_jsx_runtime.jsx)("p", {
|
|
1987
|
+
className: "dsh-ig-placeholder-desc",
|
|
1988
|
+
children: description
|
|
1989
|
+
}),
|
|
1990
|
+
tip && (0, react_jsx_runtime.jsx)("div", {
|
|
1991
|
+
className: "dsh-ig-placeholder-tip",
|
|
1992
|
+
children: tip
|
|
1993
|
+
})
|
|
1994
|
+
]
|
|
1995
|
+
})
|
|
1996
|
+
});
|
|
1997
|
+
};
|
|
900
1998
|
async function copyImageBlob(blob) {
|
|
901
1999
|
try {
|
|
902
2000
|
if (blob.type === "image/png") {
|
|
@@ -943,80 +2041,93 @@ window.__ModuleLoader__.load({
|
|
|
943
2041
|
//#endregion
|
|
944
2042
|
//#region lib/types/client/image-result-node.js
|
|
945
2043
|
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
|
-
|
|
2044
|
+
/**
|
|
2045
|
+
* One image result row per Turn, anchored at its own tool/result position while
|
|
2046
|
+
* the Turn runs, then optionally re-anchored beside the final answer once the
|
|
2047
|
+
* Turn closes (only needed under the folding Compact transcript view).
|
|
2048
|
+
*/
|
|
2049
|
+
function createImageResultDefinition(options = {}) {
|
|
2050
|
+
return {
|
|
2051
|
+
kind: IMAGE_RESULT_NODE_KIND,
|
|
2052
|
+
target: "chat",
|
|
2053
|
+
match: (event) => {
|
|
2054
|
+
const turn = eventTurn(event);
|
|
2055
|
+
if (turn === void 0) return null;
|
|
2056
|
+
if (event.type === "turn/start") return {
|
|
2057
|
+
id: String(turn),
|
|
2058
|
+
role: "start"
|
|
2059
|
+
};
|
|
2060
|
+
if (event.type === "turn/end") return {
|
|
2061
|
+
id: String(turn),
|
|
2062
|
+
role: "update"
|
|
2063
|
+
};
|
|
2064
|
+
if (event.type === "assistant/message") return {
|
|
2065
|
+
id: String(turn),
|
|
2066
|
+
role: "update"
|
|
2067
|
+
};
|
|
2068
|
+
if (event.type === "tool/result" && imageResultFromMeta(event.data.meta) !== void 0) return {
|
|
2069
|
+
id: String(turn),
|
|
2070
|
+
role: "update"
|
|
2071
|
+
};
|
|
2072
|
+
return null;
|
|
2073
|
+
},
|
|
2074
|
+
start: (_context, match) => {
|
|
2075
|
+
const turn = eventTurn(match.event);
|
|
2076
|
+
if (match.event.type !== "turn/start" || turn === void 0) throw new Error("dsh-image-result start requires turn/start");
|
|
2077
|
+
return {
|
|
2078
|
+
turn,
|
|
2079
|
+
results: []
|
|
2080
|
+
};
|
|
2081
|
+
},
|
|
2082
|
+
update: (context, match) => {
|
|
2083
|
+
if (match.event.type === "turn/end") return {
|
|
2084
|
+
...context.state,
|
|
2085
|
+
endSeq: match.event.seq
|
|
2086
|
+
};
|
|
2087
|
+
if (match.event.type === "assistant/message") return {
|
|
2088
|
+
...context.state,
|
|
2089
|
+
answerSeq: match.event.seq
|
|
2090
|
+
};
|
|
2091
|
+
if (match.event.type !== "tool/result") return context.state;
|
|
2092
|
+
const result = imageResultFromMeta(match.event.data.meta);
|
|
2093
|
+
if (result === void 0) return context.state;
|
|
2094
|
+
if (context.state.results.some((candidate) => candidate.attachment.attachmentId === result.attachment.attachmentId)) return context.state;
|
|
2095
|
+
return {
|
|
2096
|
+
...context.state,
|
|
2097
|
+
results: [...context.state.results, {
|
|
2098
|
+
...result,
|
|
2099
|
+
seq: match.event.seq
|
|
2100
|
+
}]
|
|
2101
|
+
};
|
|
2102
|
+
},
|
|
2103
|
+
buildViewNode: (context) => {
|
|
2104
|
+
const state = context.state;
|
|
2105
|
+
const last = state?.results.at(-1);
|
|
2106
|
+
if (state === void 0 || last === void 0) return null;
|
|
2107
|
+
const location = context.matches.at(-1)?.location ?? context.start?.location ?? { kind: "unresolved" };
|
|
2108
|
+
return {
|
|
2109
|
+
key: context.key,
|
|
2110
|
+
kind: IMAGE_RESULT_NODE_KIND,
|
|
2111
|
+
id: context.id,
|
|
2112
|
+
target: "chat",
|
|
2113
|
+
anchorSeq: state.endSeq !== void 0 && compactTranscript(options) ? state.answerSeq ?? state.endSeq ?? last.seq : last.seq,
|
|
2114
|
+
location,
|
|
2115
|
+
visibility: "visible",
|
|
2116
|
+
data: {
|
|
2117
|
+
turn: state.turn,
|
|
2118
|
+
results: state.results.map(({ seq: _seq, ...result }) => result)
|
|
2119
|
+
}
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
function compactTranscript(options) {
|
|
2125
|
+
try {
|
|
2126
|
+
return options.isCompactTranscript?.() ?? true;
|
|
2127
|
+
} catch {
|
|
2128
|
+
return true;
|
|
1018
2129
|
}
|
|
1019
|
-
}
|
|
2130
|
+
}
|
|
1020
2131
|
/** Parse the plugin-owned durable presentation metadata from a Tool result event. */
|
|
1021
2132
|
function imageResultFromMeta(value) {
|
|
1022
2133
|
const meta = record(value);
|
|
@@ -1029,7 +2140,8 @@ window.__ModuleLoader__.load({
|
|
|
1029
2140
|
provider: stringValue(meta.provider, "google"),
|
|
1030
2141
|
model: stringValue(meta.model, ""),
|
|
1031
2142
|
output: stringValue(meta.output, ""),
|
|
1032
|
-
...typeof meta.savedTo === "string" ? { savedTo: meta.savedTo } : {}
|
|
2143
|
+
...typeof meta.savedTo === "string" ? { savedTo: meta.savedTo } : {},
|
|
2144
|
+
...typeof meta.seed === "number" ? { seed: meta.seed } : {}
|
|
1033
2145
|
};
|
|
1034
2146
|
}
|
|
1035
2147
|
function eventTurn(event) {
|
|
@@ -1096,13 +2208,18 @@ window.__ModuleLoader__.load({
|
|
|
1096
2208
|
endpointHintDashScope: "阿里云百炼 DashScope 官方接口地址。",
|
|
1097
2209
|
endpointHintComfyUI: "正在运行且 DSH Host 可以访问的 ComfyUI 地址,默认使用本机 8188 端口。",
|
|
1098
2210
|
model: "模型",
|
|
1099
|
-
workflow: "API Workflow",
|
|
2211
|
+
workflow: "API Workflow 工作流",
|
|
1100
2212
|
workflowImport: "导入 JSON 文件",
|
|
1101
|
-
workflowReplace: "替换 JSON 文件",
|
|
1102
2213
|
workflowMissing: "尚未导入工作流",
|
|
1103
2214
|
workflowImported: "已导入 {name}",
|
|
1104
|
-
workflowHint: "
|
|
2215
|
+
workflowHint: "从 ComfyUI 导出 API Format JSON,在提示词输入写入 {{prompt}},种子可用 {{seed}};图生图工作流在 LoadImage 的 image 输入写入 {{image}}(仅一次)。可导入多个工作流,Agent 也能在调用时按名称指定。",
|
|
1105
2216
|
workflowTooLarge: "工作流文件不能超过 5 MB。",
|
|
2217
|
+
workflowActiveTitle: "设为当前使用的工作流",
|
|
2218
|
+
workflowRemove: "删除",
|
|
2219
|
+
workflowPresetPlaceholder: "预设提示词,留空则只用对话内容",
|
|
2220
|
+
workflowPresetTitle: "预设提示词:每次调用此工作流时自动加在用户提示词前面。",
|
|
2221
|
+
workflowNameRequired: "工作流名称不能为空。",
|
|
2222
|
+
workflowDuplicateName: "工作流名称不能重复。",
|
|
1106
2223
|
timeout: "生成超时(秒)",
|
|
1107
2224
|
timeoutHint: "包括提交、等待和下载图片;默认 300 秒。",
|
|
1108
2225
|
saveToWorkspace: "保存到工作区",
|
|
@@ -1148,13 +2265,18 @@ window.__ModuleLoader__.load({
|
|
|
1148
2265
|
endpointHintDashScope: "Official Aliyun DashScope endpoint.",
|
|
1149
2266
|
endpointHintComfyUI: "A running ComfyUI server reachable by the DSH Host; the default points to port 8188 on this computer.",
|
|
1150
2267
|
model: "Model",
|
|
1151
|
-
workflow: "API
|
|
2268
|
+
workflow: "API Workflows",
|
|
1152
2269
|
workflowImport: "Import JSON file",
|
|
1153
|
-
workflowReplace: "Replace JSON file",
|
|
1154
2270
|
workflowMissing: "No workflow imported",
|
|
1155
2271
|
workflowImported: "Imported {name}",
|
|
1156
|
-
workflowHint: "Export an API Format JSON from ComfyUI and place {{prompt}} in its prompt input
|
|
2272
|
+
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
2273
|
workflowTooLarge: "Workflow files must be no larger than 5 MB.",
|
|
2274
|
+
workflowActiveTitle: "Make this the active workflow",
|
|
2275
|
+
workflowRemove: "Remove",
|
|
2276
|
+
workflowPresetPlaceholder: "Preset prompt (optional)",
|
|
2277
|
+
workflowPresetTitle: "Preset prompt: automatically prepended to the user prompt on every call of this workflow.",
|
|
2278
|
+
workflowNameRequired: "Workflow names cannot be empty.",
|
|
2279
|
+
workflowDuplicateName: "Workflow names must be unique.",
|
|
1158
2280
|
timeout: "Generation timeout (seconds)",
|
|
1159
2281
|
timeoutHint: "Covers submission, waiting, and image download; defaults to 300 seconds.",
|
|
1160
2282
|
saveToWorkspace: "Save to workspace",
|
|
@@ -1203,9 +2325,16 @@ window.__ModuleLoader__.load({
|
|
|
1203
2325
|
.dsh-ig-file-button:hover{background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
|
|
1204
2326
|
.dsh-ig-file-button:focus-within{outline:2px solid var(--dsw-alias-brand-primary,#4c78ff);outline-offset:2px}
|
|
1205
2327
|
.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}
|
|
2328
|
+
.dsh-ig-workflow-list{list-style:none;margin:0;padding:0;display:grid;gap:8px}
|
|
2329
|
+
.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}
|
|
2330
|
+
.dsh-ig-workflow-main{display:flex;align-items:center;gap:8px}
|
|
2331
|
+
.dsh-ig-workflow-active{display:inline-flex;align-items:center;cursor:pointer;flex:none}
|
|
2332
|
+
.dsh-ig-workflow-active input[type=radio]{width:15px;height:15px;accent-color:var(--dsw-alias-brand-primary,#4c78ff);margin:0;cursor:pointer}
|
|
2333
|
+
.dsh-ig-workflow-name{flex:1;min-width:0}
|
|
1206
2334
|
.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
2335
|
.dsh-ig-btn-reset:hover{background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
|
|
1208
2336
|
.dsh-ig-hint,.dsh-ig-status{margin:0;color:var(--dsw-alias-label-tertiary,#7b818b);font-size:12px;line-height:1.4}
|
|
2337
|
+
.dsh-ig-status-error{color:var(--dsw-alias-label-error,#d33);font-weight:500}
|
|
1209
2338
|
.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
2339
|
.dsh-ig-check-row{display:flex;align-items:center;gap:8px;cursor:pointer}
|
|
1211
2340
|
.dsh-ig-check-row input[type=checkbox]{width:15px;height:15px;accent-color:var(--dsw-alias-brand-primary,#4c78ff);margin:0}
|
|
@@ -1228,58 +2357,149 @@ window.__ModuleLoader__.load({
|
|
|
1228
2357
|
.dsh-ig-error{color:var(--dsw-alias-label-error,#d33);font-size:13px}
|
|
1229
2358
|
.dsh-ig-loading{color:var(--dsw-alias-label-tertiary,#7b818b);font-size:13px}
|
|
1230
2359
|
|
|
1231
|
-
/* Native Workspace Gallery View (Renders seamlessly inside DSH Session View) */
|
|
2360
|
+
/* Native Workspace Gallery & Studio View (Renders seamlessly inside DSH Session View) */
|
|
1232
2361
|
.dsh-ig-gallery-page{width:100%;height:100%;background:var(--dsw-alias-bg-layer-1,#ffffff);display:flex;flex-direction:column;overflow:hidden;flex:1}
|
|
1233
|
-
|
|
1234
|
-
.
|
|
1235
|
-
.dsh-ig-
|
|
1236
|
-
.dsh-ig-
|
|
1237
|
-
.dsh-ig-
|
|
1238
|
-
.dsh-ig-
|
|
1239
|
-
|
|
1240
|
-
.
|
|
1241
|
-
.dsh-ig-
|
|
1242
|
-
.dsh-ig-
|
|
1243
|
-
.dsh-ig-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
.dsh-ig-
|
|
1247
|
-
.dsh-ig-
|
|
2362
|
+
|
|
2363
|
+
/* 1. Top Navigation Tab Bar */
|
|
2364
|
+
.dsh-ig-studio-tabs-bar{display:flex;align-items:center;gap:6px;padding:6px 24px;border-bottom:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#ffffff);flex-shrink:0}
|
|
2365
|
+
.dsh-ig-studio-tab-btn{appearance:none;-webkit-appearance:none;border:0;background:transparent;display:inline-flex;align-items:center;gap:7px;padding:7px 14px;font-size:13px;font-weight:500;color:var(--dsw-alias-label-secondary,#64748b);cursor:pointer;border-radius:6px;transition:color .15s ease,background-color .15s ease}
|
|
2366
|
+
.dsh-ig-studio-tab-btn:hover{color:var(--dsw-alias-label-primary,#0f172a);background:var(--dsw-alias-bg-layer-2,#f1f5f9)}
|
|
2367
|
+
.dsh-ig-studio-tab-btn.is-active{color:var(--dsw-alias-brand-primary,#2563eb);font-weight:600;background:rgba(37,99,235,0.08)}
|
|
2368
|
+
|
|
2369
|
+
/* 2. Secondary Filter & Search Toolbar */
|
|
2370
|
+
.dsh-ig-studio-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 24px;background:var(--dsw-alias-bg-layer-1,#ffffff);border-bottom:1px solid var(--dsw-alias-border-l2,#f1f5f9);flex-shrink:0;flex-wrap:wrap}
|
|
2371
|
+
.dsh-ig-studio-toolbar-left{display:flex;align-items:center;gap:10px;flex-wrap:wrap;flex:1;min-width:0}
|
|
2372
|
+
.dsh-ig-studio-toolbar-right{display:flex;align-items:center;gap:10px;flex-shrink:0}
|
|
2373
|
+
|
|
2374
|
+
/* Modern Custom Select (Removes OS default arrows & ugly borders) */
|
|
2375
|
+
.dsh-ig-studio-select{appearance:none;-webkit-appearance:none;-moz-appearance:none;height:32px;line-height:30px;padding:0 28px 0 12px;font-size:12.5px;color:var(--dsw-alias-label-primary,#334155);background-color:var(--dsw-alias-bg-layer-2,#ffffff);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 9px center;border:1px solid var(--dsw-alias-border-l2,#e2e8f0);border-radius:6px;outline:none;cursor:pointer;box-sizing:border-box;transition:border-color .15s ease,box-shadow .15s ease,background-color .15s ease}
|
|
2376
|
+
.dsh-ig-studio-select:hover{border-color:var(--dsw-alias-border-l1,#cbd5e1);background-color:var(--dsw-alias-bg-layer-1,#f8fafc)}
|
|
2377
|
+
.dsh-ig-studio-select:focus{border-color:var(--dsw-alias-brand-primary,#3b82f6);box-shadow:0 0 0 2px rgba(59,130,246,0.15)}
|
|
2378
|
+
.dsh-ig-studio-select-sort{font-weight:500}
|
|
2379
|
+
|
|
2380
|
+
/* Unified Search Input */
|
|
2381
|
+
.dsh-ig-studio-search-wrap{position:relative;display:flex;align-items:center;min-width:190px;max-width:320px;flex:1}
|
|
2382
|
+
.dsh-ig-studio-search-icon{position:absolute;left:10px;color:var(--dsw-alias-label-tertiary,#94a3b8);pointer-events:none}
|
|
2383
|
+
.dsh-ig-studio-search-input{width:100%;height:32px;line-height:30px;padding:0 12px 0 32px;font-size:12.5px;border:1px solid var(--dsw-alias-border-l2,#e2e8f0);border-radius:6px;background-color:var(--dsw-alias-bg-layer-2,#ffffff);color:inherit;outline:none;box-sizing:border-box;transition:border-color .15s ease,box-shadow .15s ease}
|
|
2384
|
+
.dsh-ig-studio-search-input:hover{border-color:var(--dsw-alias-border-l1,#cbd5e1)}
|
|
2385
|
+
.dsh-ig-studio-search-input:focus{border-color:var(--dsw-alias-brand-primary,#3b82f6);box-shadow:0 0 0 2px rgba(59,130,246,0.15)}
|
|
2386
|
+
.dsh-ig-studio-search-input::placeholder{color:var(--dsw-alias-label-tertiary,#94a3b8)}
|
|
2387
|
+
|
|
2388
|
+
/* 3. Grid & Responsive Cards */
|
|
2389
|
+
.dsh-ig-gallery-page-body{flex:1;overflow-y:auto;padding:20px 24px}
|
|
2390
|
+
.dsh-ig-gallery-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:18px}
|
|
2391
|
+
.dsh-ig-gallery-card{background:var(--dsw-alias-bg-layer-2,#ffffff);border:1px solid var(--dsw-alias-border-l2,#e2e8f0);border-radius:10px;overflow:hidden;display:flex;flex-direction:column;cursor:pointer;transition:transform .18s ease,box-shadow .18s ease,border-color .18s ease}
|
|
2392
|
+
.dsh-ig-gallery-card:hover{transform:translateY(-2px);box-shadow:0 10px 20px -5px rgba(0,0,0,0.06),0 4px 6px -2px rgba(0,0,0,0.03);border-color:var(--dsw-alias-border-l1,#cbd5e1)}
|
|
2393
|
+
.dsh-ig-gallery-card-media{position:relative;width:100%;aspect-ratio:1/1;background:#f1f5f9;overflow:hidden;display:flex;align-items:center;justify-content:center}
|
|
2394
|
+
.dsh-ig-gallery-card-img{width:100%;height:100%;object-fit:cover;transition:transform .2s ease}
|
|
1248
2395
|
.dsh-ig-gallery-card:hover .dsh-ig-gallery-card-img{transform:scale(1.03)}
|
|
1249
|
-
.dsh-ig-gallery-card-loading{font-size:12px;color:#
|
|
2396
|
+
.dsh-ig-gallery-card-loading{font-size:12px;color:#94a3b8}
|
|
1250
2397
|
.dsh-ig-gallery-card-error{font-size:12px;color:#ef4444;padding:8px;text-align:center}
|
|
1251
2398
|
|
|
1252
|
-
/* Floating Action Toolbar
|
|
2399
|
+
/* Floating Action Toolbar on Card Hover */
|
|
1253
2400
|
.dsh-ig-gallery-card:hover .dsh-ig-card-toolbar{opacity:1;pointer-events:auto}
|
|
1254
|
-
.dsh-ig-card-toolbar{position:absolute;top:
|
|
2401
|
+
.dsh-ig-card-toolbar{position:absolute;top:6px;left:6px;display:flex;align-items:center;gap:3px;padding:3px 5px;border-radius:6px;background:rgba(15,23,42,0.72);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);opacity:0;pointer-events:none;transition:opacity .18s ease;z-index:10;line-height:1}
|
|
1255
2402
|
|
|
1256
|
-
|
|
1257
|
-
.dsh-ig-gallery-card-
|
|
1258
|
-
.dsh-ig-
|
|
1259
|
-
.dsh-ig-
|
|
1260
|
-
.dsh-ig-gallery-card-prompt{
|
|
1261
|
-
.dsh-ig-
|
|
1262
|
-
.dsh-ig-
|
|
1263
|
-
.dsh-ig-
|
|
2403
|
+
/* Card Bottom Metadata */
|
|
2404
|
+
.dsh-ig-gallery-card-meta{padding:10px 12px;display:flex;flex-direction:column;gap:5px;background:var(--dsw-alias-bg-layer-2,#ffffff);flex:1}
|
|
2405
|
+
.dsh-ig-card-badge-row{display:flex;align-items:center}
|
|
2406
|
+
.dsh-ig-card-badge{display:inline-block;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-bg-layer-3,#f1f5f9);color:var(--dsw-alias-label-secondary,#475569);font-size:11px;font-weight:500;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
2407
|
+
.dsh-ig-gallery-card-prompt-line{font-size:12.5px;font-weight:500;color:var(--dsw-alias-label-primary,#1e293b);line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
2408
|
+
.dsh-ig-card-footer-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:2px}
|
|
2409
|
+
.dsh-ig-card-meta-text{font-size:11px;color:var(--dsw-alias-label-tertiary,#94a3b8);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
2410
|
+
.dsh-ig-card-fav-btn{appearance:none;border:0;background:transparent;color:var(--dsw-alias-label-tertiary,#94a3b8);padding:2px;border-radius:4px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:color .15s,transform .15s;flex-shrink:0}
|
|
2411
|
+
.dsh-ig-card-fav-btn:hover{color:#ef4444;transform:scale(1.15)}
|
|
2412
|
+
.dsh-ig-card-fav-btn.is-favorited{color:#ef4444}
|
|
2413
|
+
|
|
2414
|
+
/* Placeholders for Upcoming Routes */
|
|
2415
|
+
.dsh-ig-placeholder-view{display:flex;align-items:center;justify-content:center;min-height:360px;height:100%;padding:24px}
|
|
2416
|
+
.dsh-ig-placeholder-card{max-width:500px;width:100%;text-align:center;padding:36px 28px;background:var(--dsw-alias-bg-layer-2,#ffffff);border:1px dashed var(--dsw-alias-border-l2,#e2e8f0);border-radius:14px;display:flex;flex-direction:column;align-items:center;gap:12px}
|
|
2417
|
+
.dsh-ig-placeholder-icon{font-size:40px;line-height:1}
|
|
2418
|
+
.dsh-ig-placeholder-header{display:flex;align-items:center;gap:8px;justify-content:center}
|
|
2419
|
+
.dsh-ig-placeholder-title{font-size:16px;font-weight:600;color:var(--dsw-alias-label-primary,inherit);margin:0}
|
|
2420
|
+
.dsh-ig-placeholder-badge{font-size:11px;font-weight:500;background:rgba(37,99,235,0.1);color:#2563eb;padding:2px 8px;border-radius:12px}
|
|
2421
|
+
.dsh-ig-placeholder-desc{font-size:13px;line-height:1.6;color:var(--dsw-alias-label-secondary,#64748b);margin:0}
|
|
2422
|
+
.dsh-ig-placeholder-tip{margin-top:6px;padding:8px 12px;font-size:12px;background:var(--dsw-alias-bg-layer-3,#f8fafc);border-radius:8px;color:var(--dsw-alias-label-tertiary,#64748b);text-align:left}
|
|
2423
|
+
|
|
2424
|
+
/* Empty State */
|
|
2425
|
+
.dsh-ig-gallery-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;min-height:360px;text-align:center;color:var(--dsw-alias-label-tertiary,#94a3b8)}
|
|
2426
|
+
.dsh-ig-gallery-empty-icon{font-size:44px;margin-bottom:10px}
|
|
2427
|
+
.dsh-ig-gallery-empty-title{font-size:15px;font-weight:600;color:var(--dsw-alias-label-primary,inherit);margin-bottom:4px}
|
|
1264
2428
|
.dsh-ig-gallery-empty-desc{font-size:13px;max-width:360px;line-height:1.5}
|
|
1265
2429
|
|
|
1266
2430
|
/* Pure Centered Lightbox */
|
|
1267
2431
|
.dsh-ig-lightbox-backdrop{position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.88);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;box-sizing:border-box;cursor:zoom-out;animation:dsh-ig-fade .15s ease-out}
|
|
1268
2432
|
.dsh-ig-lightbox-topbar{position:absolute;top:20px;left:24px;right:24px;display:flex;align-items:center;justify-content:space-between;z-index:10;pointer-events:none}
|
|
1269
2433
|
.dsh-ig-lightbox-meta{display:flex;align-items:center;gap:8px;pointer-events:auto}
|
|
2434
|
+
.dsh-ig-tag{display:inline-block;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-bg-layer-3,#edf0f3);color:var(--dsw-alias-label-secondary,inherit);font-weight:500;text-transform:uppercase;font-size:10px}
|
|
2435
|
+
.dsh-ig-tag-model{background:rgba(76,120,255,0.1);color:#4c78ff}
|
|
1270
2436
|
.dsh-ig-lightbox-close-btn{appearance:none;border:0;background:rgba(255,255,255,0.15);color:#fff;border-radius:50%;width:34px;height:34px;font-size:16px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background .15s;pointer-events:auto}
|
|
1271
2437
|
.dsh-ig-lightbox-close-btn:hover{background:rgba(255,255,255,0.3)}
|
|
1272
2438
|
.dsh-ig-lightbox-img-wrap{max-width:86vw;max-height:78vh;display:flex;align-items:center;justify-content:center;cursor:default}
|
|
1273
2439
|
.dsh-ig-lightbox-img{max-width:100%;max-height:78vh;object-fit:contain;border-radius:8px;box-shadow:0 24px 60px rgba(0,0,0,0.7);user-select:none}
|
|
1274
2440
|
.dsh-ig-lightbox-bottombar{position:absolute;bottom:24px;left:50%;transform:translateX(-50%);max-width:min(90vw,640px);background:rgba(20,22,26,0.85);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,0.15);border-radius:14px;padding:10px 16px;display:flex;flex-direction:column;gap:8px;color:#fff;box-shadow:0 16px 40px rgba(0,0,0,0.5);cursor:default}
|
|
1275
2441
|
.dsh-ig-lightbox-prompt-text{font-size:13px;line-height:1.4;color:rgba(255,255,255,0.92);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}
|
|
1276
|
-
.dsh-ig-lightbox-
|
|
2442
|
+
.dsh-ig-lightbox-counter{display:inline-flex;align-items:center;padding:2px 8px;border-radius:4px;background:rgba(255,255,255,0.15);color:#fff;font-size:11.5px;font-weight:500;font-variant-numeric:tabular-nums}
|
|
2443
|
+
.dsh-ig-lightbox-nav-btn{position:fixed;top:50%;transform:translateY(-50%);width:44px;height:44px;border-radius:50%;border:1px solid rgba(255,255,255,0.18);background:rgba(255,255,255,0.12);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:100;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);transition:background-color .15s ease,transform .15s ease,opacity .15s ease;outline:none}
|
|
2444
|
+
.dsh-ig-lightbox-nav-btn:hover:not(:disabled){background:rgba(255,255,255,0.28);transform:translateY(-50%) scale(1.08)}
|
|
2445
|
+
.dsh-ig-lightbox-nav-btn:disabled{opacity:0.2;cursor:not-allowed;pointer-events:none}
|
|
2446
|
+
.dsh-ig-lightbox-nav-prev{left:24px}
|
|
2447
|
+
.dsh-ig-lightbox-nav-next{right:24px}
|
|
2448
|
+
.dsh-ig-lightbox-loading{display:flex;align-items:center;justify-content:center;min-width:180px;min-height:180px}
|
|
2449
|
+
.dsh-ig-lightbox-spinner{width:36px;height:36px;border:3px solid rgba(255,255,255,0.2);border-top-color:#fff;border-radius:50%;animation:dsh-ig-spin .8s linear infinite}
|
|
2450
|
+
@keyframes dsh-ig-spin{to{transform:rotate(360deg)}}
|
|
1277
2451
|
.dsh-ig-lightbox-btn{appearance:none;border:1px solid rgba(255,255,255,0.18);background:rgba(255,255,255,0.08);color:#fff;border-radius:6px;padding:5px 10px;font-size:12px;cursor:pointer;display:inline-flex;align-items:center;gap:5px;transition:background .15s,border-color .15s,color .15s}
|
|
1278
2452
|
.dsh-ig-lightbox-btn:hover{background:rgba(255,255,255,0.22)}
|
|
1279
2453
|
.dsh-ig-lightbox-btn-danger{border-color:rgba(239,68,68,0.4);color:#fca5a5}
|
|
1280
2454
|
.dsh-ig-lightbox-btn-danger:hover{background:rgba(239,68,68,0.35)!important;color:#fff!important;border-color:rgba(239,68,68,0.7)!important}
|
|
1281
2455
|
.dsh-ig-gallery-page-toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.85);color:#fff;padding:6px 14px;border-radius:8px;font-size:13px;z-index:99999;animation:dsh-ig-fade .15s}
|
|
1282
2456
|
|
|
2457
|
+
/* Card selection and checkbox */
|
|
2458
|
+
.dsh-ig-gallery-card.is-selected{box-shadow:0 0 0 2px var(--dsw-alias-brand-primary,#2563eb);border-color:transparent}
|
|
2459
|
+
.dsh-ig-card-checkbox{position:absolute;top:8px;left:8px;width:22px;height:22px;border-radius:50%;border:1.5px solid rgba(255,255,255,0.7);background:rgba(0,0,0,0.35);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);color:#fff;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;z-index:5;opacity:0;transition:opacity .15s ease,background-color .15s ease,border-color .15s ease;padding:0;outline:none}
|
|
2460
|
+
.dsh-ig-gallery-card:hover .dsh-ig-card-checkbox,.dsh-ig-gallery-card.is-manage-mode .dsh-ig-card-checkbox,.dsh-ig-card-checkbox.is-checked{opacity:1}
|
|
2461
|
+
.dsh-ig-card-checkbox.is-checked{background:var(--dsw-alias-brand-primary,#2563eb);border-color:var(--dsw-alias-brand-primary,#2563eb)}
|
|
2462
|
+
|
|
2463
|
+
/* Studio button in toolbar */
|
|
2464
|
+
.dsh-ig-studio-btn{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 12px;box-sizing:border-box;border-radius:6px;border:1px solid var(--dsw-alias-border-subtle,rgba(0,0,0,0.12));background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,inherit);font-size:12.5px;font-weight:500;cursor:pointer;transition:border-color .15s,background .15s,color .15s}
|
|
2465
|
+
.dsh-ig-studio-btn:hover{background:var(--dsw-alias-bg-layer-3,#f3f4f6);border-color:var(--dsw-alias-border-default,rgba(0,0,0,0.2))}
|
|
2466
|
+
.dsh-ig-studio-btn.is-active{background:var(--dsw-alias-brand-primary,#2563eb);border-color:var(--dsw-alias-brand-primary,#2563eb);color:#fff}
|
|
2467
|
+
.dsh-ig-studio-btn-danger{color:#ef4444;border-color:rgba(239,68,68,0.35);background:rgba(239,68,68,0.06)}
|
|
2468
|
+
.dsh-ig-studio-btn-danger:hover{background:rgba(239,68,68,0.14);border-color:rgba(239,68,68,0.6);color:#dc2626}
|
|
2469
|
+
.dsh-ig-studio-btn-danger.is-active{background:#dc2626;border-color:#dc2626;color:#fff}
|
|
2470
|
+
|
|
2471
|
+
/* Floating Batch Action Bar */
|
|
2472
|
+
.dsh-ig-batch-bar{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);display:flex;align-items:center;gap:16px;background:rgba(20,24,32,0.92);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border:1px solid rgba(255,255,255,0.18);border-radius:40px;padding:8px 16px;box-shadow:0 16px 40px rgba(0,0,0,0.5);z-index:99990;animation:dsh-ig-slide-up .2s cubic-bezier(0.16,1,0.3,1);color:#fff}
|
|
2473
|
+
@keyframes dsh-ig-slide-up{from{transform:translate(-50%,20px);opacity:0}to{transform:translate(-50%,0);opacity:1}}
|
|
2474
|
+
.dsh-ig-batch-bar-left{display:flex;align-items:center;gap:10px}
|
|
2475
|
+
.dsh-ig-batch-bar-right{display:flex;align-items:center;gap:8px;border-left:1px solid rgba(255,255,255,0.15);padding-left:12px}
|
|
2476
|
+
.dsh-ig-batch-counter{font-size:13px;font-weight:600;color:rgba(255,255,255,0.95);margin-right:4px}
|
|
2477
|
+
.dsh-ig-batch-btn{appearance:none;border:1px solid rgba(255,255,255,0.15);background:rgba(255,255,255,0.08);color:#fff;border-radius:20px;padding:5px 12px;font-size:12px;cursor:pointer;display:inline-flex;align-items:center;gap:5px;transition:background .15s,border-color .15s,color .15s}
|
|
2478
|
+
.dsh-ig-batch-btn:hover:not(:disabled){background:rgba(255,255,255,0.2)}
|
|
2479
|
+
.dsh-ig-batch-btn:disabled{opacity:0.4;cursor:not-allowed}
|
|
2480
|
+
.dsh-ig-batch-btn-danger{background:rgba(239,68,68,0.2);border-color:rgba(239,68,68,0.5);color:#fca5a5}
|
|
2481
|
+
.dsh-ig-batch-btn-danger:hover:not(:disabled){background:rgba(239,68,68,0.4)!important;border-color:rgba(239,68,68,0.8)!important;color:#fff!important}
|
|
2482
|
+
.dsh-ig-batch-btn-exit{border-color:transparent;background:transparent;color:rgba(255,255,255,0.7)}
|
|
2483
|
+
.dsh-ig-batch-btn-exit:hover{background:rgba(255,255,255,0.1);color:#fff}
|
|
2484
|
+
|
|
2485
|
+
/* Batch Delete Confirmation Modal */
|
|
2486
|
+
.dsh-ig-modal-backdrop{position:fixed;inset:0;background:rgba(0,0,0,0.65);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);z-index:99995;display:flex;align-items:center;justify-content:center;padding:16px;animation:dsh-ig-fade .15s ease-out}
|
|
2487
|
+
.dsh-ig-modal-box{width:100%;max-width:440px;background:var(--dsw-alias-bg-layer-1,#1c1e24);border:1px solid var(--dsw-alias-border-subtle,rgba(255,255,255,0.12));border-radius:12px;padding:22px;box-sizing:border-box;box-shadow:0 20px 50px rgba(0,0,0,0.45);color:var(--dsw-alias-label-primary,#fff);animation:dsh-ig-scale-up .15s ease-out}
|
|
2488
|
+
@keyframes dsh-ig-scale-up{from{transform:scale(0.95);opacity:0}to{transform:scale(1);opacity:1}}
|
|
2489
|
+
.dsh-ig-modal-header{display:flex;align-items:flex-start;gap:12px;margin-bottom:12px}
|
|
2490
|
+
.dsh-ig-modal-icon-danger{width:36px;height:36px;border-radius:50%;background:rgba(239,68,68,0.12);color:#ef4444;display:flex;align-items:center;justify-content:center;flex-shrink:0}
|
|
2491
|
+
.dsh-ig-modal-title{font-size:16px;font-weight:600;line-height:1.4}
|
|
2492
|
+
.dsh-ig-modal-body{margin-bottom:20px;padding-left:48px}
|
|
2493
|
+
.dsh-ig-modal-desc{font-size:13.5px;color:var(--dsw-alias-label-secondary,rgba(255,255,255,0.7));margin:0 0 14px 0;line-height:1.5}
|
|
2494
|
+
.dsh-ig-modal-checkbox-label{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--dsw-alias-label-primary,inherit);cursor:pointer;user-select:none}
|
|
2495
|
+
.dsh-ig-modal-checkbox-label input{margin:0;cursor:pointer;width:15px;height:15px}
|
|
2496
|
+
.dsh-ig-modal-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px}
|
|
2497
|
+
.dsh-ig-modal-btn{height:34px;padding:0 14px;border-radius:6px;font-size:13px;font-weight:500;cursor:pointer;transition:background .15s,border-color .15s,color .15s;outline:none}
|
|
2498
|
+
.dsh-ig-modal-btn-cancel{background:transparent;border:1px solid var(--dsw-alias-border-subtle,rgba(255,255,255,0.2));color:var(--dsw-alias-label-primary,inherit)}
|
|
2499
|
+
.dsh-ig-modal-btn-cancel:hover{background:var(--dsw-alias-bg-layer-3,rgba(255,255,255,0.08))}
|
|
2500
|
+
.dsh-ig-modal-btn-danger{background:#dc2626;border:1px solid #dc2626;color:#fff}
|
|
2501
|
+
.dsh-ig-modal-btn-danger:hover{background:#b91c1c;border-color:#b91c1c}
|
|
2502
|
+
|
|
1283
2503
|
/* Hide floating chat composer when gallery page is active */
|
|
1284
2504
|
[data-conversation-scroll]:has(.dsh-ig-gallery-page) [data-composer-seat]{display:none!important}
|
|
1285
2505
|
`;
|
|
@@ -1294,6 +2514,8 @@ window.__ModuleLoader__.load({
|
|
|
1294
2514
|
/** Mount the settings card, generated-image card, and native conversation gallery view. */
|
|
1295
2515
|
function apply(ctx) {
|
|
1296
2516
|
const scope = ctx.settingsScope.bind({ namespace: IMAGE_GENERATION_NAMESPACE });
|
|
2517
|
+
const chatScope = ctx.settingsScope.bind({ namespace: "ui-chat" });
|
|
2518
|
+
const isCompactTranscript = () => chatScope.getSnapshot().value?.transcriptView !== "normal";
|
|
1297
2519
|
const locale = ctx.get("locale");
|
|
1298
2520
|
const promotion = { enabled: false };
|
|
1299
2521
|
ctx.effect(() => {
|
|
@@ -1311,7 +2533,7 @@ window.__ModuleLoader__.load({
|
|
|
1311
2533
|
if (uiConversation === void 0) throw new Error("dsh-image-gen: uiConversation has an incompatible interface");
|
|
1312
2534
|
promotion.enabled = true;
|
|
1313
2535
|
const ownerRegister = owner.slots.register.bind(owner.slots);
|
|
1314
|
-
owner.effect(() => uiConversation.events.register(
|
|
2536
|
+
owner.effect(() => uiConversation.events.register(createImageResultDefinition({ isCompactTranscript })), "dsh-image-gen: promoted image result node");
|
|
1315
2537
|
owner.slots.inject("conversation.chat.node", () => ownerRegister({
|
|
1316
2538
|
name: "conversation.chat.node",
|
|
1317
2539
|
key: IMAGE_RESULT_NODE_KIND,
|
|
@@ -1409,8 +2631,8 @@ window.__ModuleLoader__.load({
|
|
|
1409
2631
|
const [provider, setProvider] = (0, react.useState)("google");
|
|
1410
2632
|
const [model, setModel] = (0, react.useState)("");
|
|
1411
2633
|
const [baseURL, setBaseURL] = (0, react.useState)("");
|
|
1412
|
-
const [
|
|
1413
|
-
const [
|
|
2634
|
+
const [workflows, setWorkflows] = (0, react.useState)([]);
|
|
2635
|
+
const [activeWorkflow, setActiveWorkflow] = (0, react.useState)("");
|
|
1414
2636
|
const [timeoutSeconds, setTimeoutSeconds] = (0, react.useState)(DEFAULT_COMFYUI_TIMEOUT_MS / 1e3);
|
|
1415
2637
|
const [saveToWorkspace, setSaveToWorkspace] = (0, react.useState)(true);
|
|
1416
2638
|
const [workspaceFolder, setWorkspaceFolder] = (0, react.useState)("dsh-image-gen");
|
|
@@ -1418,6 +2640,15 @@ window.__ModuleLoader__.load({
|
|
|
1418
2640
|
const [configured, setConfigured] = (0, react.useState)();
|
|
1419
2641
|
const [saving, setSaving] = (0, react.useState)(false);
|
|
1420
2642
|
const [message, setMessage] = (0, react.useState)("");
|
|
2643
|
+
const [messageIsError, setMessageIsError] = (0, react.useState)(false);
|
|
2644
|
+
const reportMessage = (text) => {
|
|
2645
|
+
setMessage(text);
|
|
2646
|
+
setMessageIsError(false);
|
|
2647
|
+
};
|
|
2648
|
+
const reportError = (text) => {
|
|
2649
|
+
setMessage(text);
|
|
2650
|
+
setMessageIsError(true);
|
|
2651
|
+
};
|
|
1421
2652
|
(0, react.useEffect)(() => props.scope.subscribe(() => {
|
|
1422
2653
|
setSnapshot(props.scope.getSnapshot());
|
|
1423
2654
|
}), [props.scope]);
|
|
@@ -1444,8 +2675,8 @@ window.__ModuleLoader__.load({
|
|
|
1444
2675
|
setProvider(next);
|
|
1445
2676
|
setModel(modelOf(next, value));
|
|
1446
2677
|
setBaseURL(baseURLOf(next, value));
|
|
1447
|
-
|
|
1448
|
-
|
|
2678
|
+
setWorkflows(resolveComfyUIWorkflows(value ?? {}));
|
|
2679
|
+
setActiveWorkflow(activeComfyUIWorkflow(value ?? {})?.name ?? "");
|
|
1449
2680
|
setTimeoutSeconds(Math.max(1, Math.round((value?.comfyuiTimeoutMs ?? 3e5) / 1e3)));
|
|
1450
2681
|
setSaveToWorkspace(value?.saveToWorkspace ?? true);
|
|
1451
2682
|
setWorkspaceFolder(value?.workspaceFolder ?? "dsh-image-gen");
|
|
@@ -1473,10 +2704,22 @@ window.__ModuleLoader__.load({
|
|
|
1473
2704
|
try {
|
|
1474
2705
|
await props.scope.set("provider", provider);
|
|
1475
2706
|
if (provider === "comfyui") {
|
|
1476
|
-
|
|
2707
|
+
const entries = workflows.map((entry) => ({
|
|
2708
|
+
name: entry.name.trim(),
|
|
2709
|
+
json: entry.json,
|
|
2710
|
+
presetPrompt: (entry.presetPrompt ?? "").trim()
|
|
2711
|
+
}));
|
|
2712
|
+
for (const entry of entries) {
|
|
2713
|
+
if (entry.name.length === 0) throw new Error(t("workflowNameRequired"));
|
|
2714
|
+
validateComfyUIWorkflowJson(entry.json);
|
|
2715
|
+
}
|
|
2716
|
+
if (new Set(entries.map((entry) => entry.name)).size !== entries.length) throw new Error(t("workflowDuplicateName"));
|
|
2717
|
+
const activeEntry = entries.find((entry) => entry.name === activeWorkflow) ?? entries[0];
|
|
1477
2718
|
await props.scope.set("comfyuiBaseURL", baseURL);
|
|
1478
|
-
await props.scope.set("
|
|
1479
|
-
await props.scope.set("
|
|
2719
|
+
await props.scope.set("comfyuiWorkflows", entries);
|
|
2720
|
+
await props.scope.set("comfyuiActiveWorkflow", activeEntry === void 0 ? "" : activeEntry.name);
|
|
2721
|
+
await props.scope.set("comfyuiWorkflowJson", activeEntry === void 0 ? "" : activeEntry.json);
|
|
2722
|
+
await props.scope.set("comfyuiWorkflowName", activeEntry === void 0 ? "" : activeEntry.name);
|
|
1480
2723
|
await props.scope.set("comfyuiTimeoutMs", Math.max(1, Math.round(timeoutSeconds)) * 1e3);
|
|
1481
2724
|
} else {
|
|
1482
2725
|
await props.scope.set(provider === "google" ? "googleModel" : provider === "openai" ? "openaiModel" : provider === "seedream" ? "seedreamModel" : "dashscopeModel", model);
|
|
@@ -1492,31 +2735,58 @@ window.__ModuleLoader__.load({
|
|
|
1492
2735
|
setKey("");
|
|
1493
2736
|
setConfigured(true);
|
|
1494
2737
|
}
|
|
1495
|
-
|
|
2738
|
+
reportMessage(t("saved"));
|
|
1496
2739
|
} catch (cause) {
|
|
1497
|
-
|
|
2740
|
+
reportError(cause instanceof Error ? cause.message : String(cause));
|
|
1498
2741
|
} finally {
|
|
1499
2742
|
setSaving(false);
|
|
1500
2743
|
}
|
|
1501
2744
|
};
|
|
1502
2745
|
const keyStatus = configured === void 0 ? t("checkingKey") : configured ? t("keyConfigured") : t("keyNotConfigured");
|
|
1503
|
-
const workflowStatus =
|
|
2746
|
+
const workflowStatus = activeWorkflow.length > 0 ? t("workflowImported", { name: activeWorkflow }) : t("workflowMissing");
|
|
1504
2747
|
const importWorkflow = async (event) => {
|
|
1505
2748
|
const file = event.target.files?.[0];
|
|
1506
2749
|
event.target.value = "";
|
|
1507
2750
|
if (file === void 0) return;
|
|
1508
|
-
|
|
2751
|
+
reportMessage("");
|
|
1509
2752
|
try {
|
|
1510
2753
|
if (file.size > 5242880) throw new Error(t("workflowTooLarge"));
|
|
1511
2754
|
const json = await file.text();
|
|
1512
2755
|
validateComfyUIWorkflowJson(json);
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
2756
|
+
const name = uniqueComfyUIWorkflowName(file.name, workflows.map((entry) => entry.name));
|
|
2757
|
+
setWorkflows((current) => [...current, {
|
|
2758
|
+
name,
|
|
2759
|
+
json
|
|
2760
|
+
}]);
|
|
2761
|
+
setActiveWorkflow((current) => current.length > 0 ? current : name);
|
|
2762
|
+
reportMessage(t("workflowImported", { name }));
|
|
1516
2763
|
} catch (cause) {
|
|
1517
|
-
|
|
2764
|
+
reportError(cause instanceof Error ? cause.message : String(cause));
|
|
1518
2765
|
}
|
|
1519
2766
|
};
|
|
2767
|
+
/** Renaming the active entry keeps the active selection following its new name. */
|
|
2768
|
+
const renameWorkflow = (index, name) => {
|
|
2769
|
+
const previous = workflows[index];
|
|
2770
|
+
setWorkflows((current) => current.map((entry, position) => position === index ? {
|
|
2771
|
+
...entry,
|
|
2772
|
+
name
|
|
2773
|
+
} : entry));
|
|
2774
|
+
if (previous !== void 0 && previous.name === activeWorkflow) setActiveWorkflow(name);
|
|
2775
|
+
};
|
|
2776
|
+
/** Removing the active entry moves the selection to the first remaining workflow. */
|
|
2777
|
+
const removeWorkflow = (index) => {
|
|
2778
|
+
const previous = workflows[index];
|
|
2779
|
+
const next = workflows.filter((_entry, position) => position !== index);
|
|
2780
|
+
setWorkflows(next);
|
|
2781
|
+
if (previous !== void 0 && previous.name === activeWorkflow) setActiveWorkflow(next[0]?.name ?? "");
|
|
2782
|
+
};
|
|
2783
|
+
/** Editing one entry's preset leaves the rest of the entry untouched. */
|
|
2784
|
+
const setWorkflowPreset = (index, presetPrompt) => {
|
|
2785
|
+
setWorkflows((current) => current.map((entry, position) => position === index ? {
|
|
2786
|
+
...entry,
|
|
2787
|
+
presetPrompt
|
|
2788
|
+
} : entry));
|
|
2789
|
+
};
|
|
1520
2790
|
return (0, react_jsx_runtime.jsxs)("li", {
|
|
1521
2791
|
className: `dsh-ig-card ${open ? "dsh-ig-card-open" : ""}`,
|
|
1522
2792
|
children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
@@ -1689,13 +2959,60 @@ window.__ModuleLoader__.load({
|
|
|
1689
2959
|
onChange: (event) => {
|
|
1690
2960
|
importWorkflow(event);
|
|
1691
2961
|
}
|
|
1692
|
-
}),
|
|
1693
|
-
}), (0, react_jsx_runtime.jsx)("span", {
|
|
2962
|
+
}), t("workflowImport")]
|
|
2963
|
+
}), workflows.length === 0 ? (0, react_jsx_runtime.jsx)("span", {
|
|
1694
2964
|
className: "dsh-ig-file-name",
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
})]
|
|
2965
|
+
children: t("workflowMissing")
|
|
2966
|
+
}) : null]
|
|
1698
2967
|
}),
|
|
2968
|
+
workflows.length > 0 ? (0, react_jsx_runtime.jsx)("ul", {
|
|
2969
|
+
className: "dsh-ig-workflow-list",
|
|
2970
|
+
children: workflows.map((entry, index) => (0, react_jsx_runtime.jsxs)("li", {
|
|
2971
|
+
className: "dsh-ig-workflow-row",
|
|
2972
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
2973
|
+
className: "dsh-ig-workflow-main",
|
|
2974
|
+
children: [
|
|
2975
|
+
(0, react_jsx_runtime.jsx)("label", {
|
|
2976
|
+
className: "dsh-ig-workflow-active",
|
|
2977
|
+
title: t("workflowActiveTitle"),
|
|
2978
|
+
children: (0, react_jsx_runtime.jsx)("input", {
|
|
2979
|
+
type: "radio",
|
|
2980
|
+
name: "dsh-ig-active-workflow",
|
|
2981
|
+
"aria-label": t("workflowActiveTitle"),
|
|
2982
|
+
checked: entry.name === activeWorkflow,
|
|
2983
|
+
onChange: () => {
|
|
2984
|
+
setActiveWorkflow(entry.name);
|
|
2985
|
+
}
|
|
2986
|
+
})
|
|
2987
|
+
}),
|
|
2988
|
+
(0, react_jsx_runtime.jsx)("input", {
|
|
2989
|
+
className: "dsh-ig-input dsh-ig-workflow-name",
|
|
2990
|
+
value: entry.name,
|
|
2991
|
+
title: entry.name,
|
|
2992
|
+
onChange: (event) => {
|
|
2993
|
+
renameWorkflow(index, event.target.value);
|
|
2994
|
+
}
|
|
2995
|
+
}),
|
|
2996
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
2997
|
+
type: "button",
|
|
2998
|
+
className: "dsh-ig-btn-reset",
|
|
2999
|
+
onClick: () => {
|
|
3000
|
+
removeWorkflow(index);
|
|
3001
|
+
},
|
|
3002
|
+
children: t("workflowRemove")
|
|
3003
|
+
})
|
|
3004
|
+
]
|
|
3005
|
+
}), (0, react_jsx_runtime.jsx)("input", {
|
|
3006
|
+
className: "dsh-ig-input dsh-ig-workflow-preset",
|
|
3007
|
+
value: entry.presetPrompt ?? "",
|
|
3008
|
+
placeholder: t("workflowPresetPlaceholder"),
|
|
3009
|
+
title: t("workflowPresetTitle"),
|
|
3010
|
+
onChange: (event) => {
|
|
3011
|
+
setWorkflowPreset(index, event.target.value);
|
|
3012
|
+
}
|
|
3013
|
+
})]
|
|
3014
|
+
}, String(index)))
|
|
3015
|
+
}) : null,
|
|
1699
3016
|
(0, react_jsx_runtime.jsx)("span", {
|
|
1700
3017
|
className: "dsh-ig-hint",
|
|
1701
3018
|
children: t("workflowHint")
|
|
@@ -1769,13 +3086,13 @@ window.__ModuleLoader__.load({
|
|
|
1769
3086
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
1770
3087
|
className: "dsh-ig-actions",
|
|
1771
3088
|
children: [(0, react_jsx_runtime.jsx)("p", {
|
|
1772
|
-
className: "dsh-ig-status"
|
|
3089
|
+
className: `dsh-ig-status${messageIsError ? " dsh-ig-status-error" : ""}`,
|
|
1773
3090
|
role: "status",
|
|
1774
3091
|
children: message || (provider === "comfyui" ? workflowStatus : keyStatus)
|
|
1775
3092
|
}), (0, react_jsx_runtime.jsx)("button", {
|
|
1776
3093
|
className: "dsh-ig-save",
|
|
1777
3094
|
type: "submit",
|
|
1778
|
-
disabled: saving || !snapshot.writable || provider === "comfyui" &&
|
|
3095
|
+
disabled: saving || !snapshot.writable || provider === "comfyui" && workflows.length === 0,
|
|
1779
3096
|
children: saving ? t("saving") : t("save")
|
|
1780
3097
|
})]
|
|
1781
3098
|
})
|
|
@@ -1831,7 +3148,9 @@ window.__ModuleLoader__.load({
|
|
|
1831
3148
|
prompt: result.prompt,
|
|
1832
3149
|
provider: result.provider,
|
|
1833
3150
|
model: result.model,
|
|
1834
|
-
output: result.output
|
|
3151
|
+
output: result.output,
|
|
3152
|
+
...result.savedTo ? { savedTo: result.savedTo } : {},
|
|
3153
|
+
...result.seed !== void 0 ? { seed: result.seed } : {}
|
|
1835
3154
|
});
|
|
1836
3155
|
}, [result?.attachment.attachmentId]);
|
|
1837
3156
|
(0, react.useEffect)(() => {
|
|
@@ -2055,11 +3374,12 @@ window.__ModuleLoader__.load({
|
|
|
2055
3374
|
provider: typeof meta?.provider === "string" ? meta.provider : "google",
|
|
2056
3375
|
model: typeof meta?.model === "string" ? meta.model : "",
|
|
2057
3376
|
output: typeof meta?.output === "string" ? meta.output : "",
|
|
2058
|
-
...typeof meta?.savedTo === "string" ? { savedTo: meta.savedTo } : {}
|
|
3377
|
+
...typeof meta?.savedTo === "string" ? { savedTo: meta.savedTo } : {},
|
|
3378
|
+
...typeof meta?.seed === "number" ? { seed: meta.seed } : {}
|
|
2059
3379
|
};
|
|
2060
3380
|
}
|
|
2061
3381
|
function modelOf(provider, value) {
|
|
2062
|
-
const stored = provider === "google" ? value?.googleModel : provider === "openai" ? value?.openaiModel : provider === "seedream" ? value?.seedreamModel : provider === "dashscope" ? value?.dashscopeModel : value?.
|
|
3382
|
+
const stored = provider === "google" ? value?.googleModel : provider === "openai" ? value?.openaiModel : provider === "seedream" ? value?.seedreamModel : provider === "dashscope" ? value?.dashscopeModel : activeComfyUIWorkflow(value ?? {})?.name;
|
|
2063
3383
|
return typeof stored === "string" && stored.length > 0 ? stored : DEFAULT_MODELS[provider];
|
|
2064
3384
|
}
|
|
2065
3385
|
function baseURLOf(provider, value) {
|