dsh-image-gen 0.2.6 → 0.3.1
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/lib/client.js +3084 -314
- package/lib/client.js.map +1 -1
- package/lib/index.js +906 -256
- package/lib/types/client/browser-image-utils.d.ts +10 -0
- package/lib/types/client/conversation-image-revisions.d.ts +25 -0
- package/lib/types/client/conversation-regenerate.d.ts +11 -0
- package/lib/types/client/gallery-store.d.ts +19 -0
- package/lib/types/client/gallery-view.d.ts +27 -5
- package/lib/types/client/image-cache.d.ts +26 -0
- package/lib/types/client/studio-style.d.ts +1 -0
- package/lib/types/client/studio-view.d.ts +30 -0
- package/lib/types/image-route.d.ts +18 -2
- package/lib/types/index.d.ts +2 -1
- package/lib/types/shared.d.ts +84 -0
- package/lib/types/studio-route.d.ts +12 -0
- package/lib/types/studio.d.ts +9 -0
- package/lib/types/workspace-save.d.ts +10 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -7,13 +7,23 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
8
|
let react = require("react");
|
|
9
9
|
//#region lib/types/shared.js
|
|
10
|
-
/** Values shared by the Host and browser Bundle faces. */
|
|
11
10
|
/** Browser route used by the generated-image card. */
|
|
12
11
|
const IMAGE_ROUTE = "/plugins/dsh-image-gen/image";
|
|
13
12
|
/** Browser route used for deleting generated images and workspace files. */
|
|
14
13
|
const DELETE_ROUTE = "/plugins/dsh-image-gen/delete";
|
|
14
|
+
/** Same-origin route used by the browser image workbench. */
|
|
15
|
+
const STUDIO_ROUTE = "/plugins/dsh-image-gen/studio";
|
|
16
|
+
/** Browser route used for saving generated images to workspace on demand. */
|
|
17
|
+
const SAVE_WORKSPACE_ROUTE = "/plugins/dsh-image-gen/save-workspace";
|
|
15
18
|
/** Namespace persisted through DSH Settings. */
|
|
16
19
|
const IMAGE_GENERATION_NAMESPACE = "image-generation";
|
|
20
|
+
/** Providers supported by the first browser workbench release. */
|
|
21
|
+
const CLOUD_IMAGE_PROVIDERS = [
|
|
22
|
+
"google",
|
|
23
|
+
"openai",
|
|
24
|
+
"seedream",
|
|
25
|
+
"dashscope"
|
|
26
|
+
];
|
|
17
27
|
/** Default endpoints and base URLs. */
|
|
18
28
|
const DEFAULT_GOOGLE_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/interactions";
|
|
19
29
|
const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
@@ -300,6 +310,29 @@ window.__ModuleLoader__.load({
|
|
|
300
310
|
}
|
|
301
311
|
}
|
|
302
312
|
/**
|
|
313
|
+
* Delete a single gallery record by ID and record a tombstone.
|
|
314
|
+
*/
|
|
315
|
+
async function deleteGalleryItem(id) {
|
|
316
|
+
const db = await getDB();
|
|
317
|
+
const tombstones = await loadTombstones(db);
|
|
318
|
+
await new Promise((resolve, reject) => {
|
|
319
|
+
const tx = db.transaction([STORE_NAME, TOMBSTONE_STORE], "readwrite");
|
|
320
|
+
const store = tx.objectStore(STORE_NAME);
|
|
321
|
+
const tombstoneStore = tx.objectStore(TOMBSTONE_STORE);
|
|
322
|
+
store.delete(id);
|
|
323
|
+
tombstoneStore.put({
|
|
324
|
+
id,
|
|
325
|
+
deletedAt: Date.now()
|
|
326
|
+
});
|
|
327
|
+
tx.oncomplete = () => {
|
|
328
|
+
tombstones.add(id);
|
|
329
|
+
resolve();
|
|
330
|
+
};
|
|
331
|
+
tx.onerror = () => reject(tx.error);
|
|
332
|
+
});
|
|
333
|
+
notifyListeners();
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
303
336
|
* Bulk delete multiple gallery records by IDs and record tombstones in a single transaction.
|
|
304
337
|
*/
|
|
305
338
|
async function bulkDeleteGalleryItems(ids) {
|
|
@@ -327,6 +360,30 @@ window.__ModuleLoader__.load({
|
|
|
327
360
|
});
|
|
328
361
|
notifyListeners();
|
|
329
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* Standardize path format across Windows and POSIX (lowercase, forward slashes, no trailing slash).
|
|
365
|
+
*/
|
|
366
|
+
function normalizeWorkspacePath(rawPath) {
|
|
367
|
+
return rawPath.replace(/\\/g, "/").toLowerCase().replace(/\/+$/, "");
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Determine if a gallery item belongs to the given workspace context.
|
|
371
|
+
* Compares workspaceId, sessionId membership, workspacePath, and savedTo file path prefix.
|
|
372
|
+
*/
|
|
373
|
+
function isItemInWorkspace(item, workspace) {
|
|
374
|
+
if (!workspace || !workspace.workspaceId && !workspace.path && (!workspace.sessionIds || workspace.sessionIds.length === 0)) return true;
|
|
375
|
+
if (item.workspaceId && workspace.workspaceId && item.workspaceId === workspace.workspaceId) return true;
|
|
376
|
+
if (item.sessionId && Array.isArray(workspace.sessionIds) && workspace.sessionIds.includes(item.sessionId)) return true;
|
|
377
|
+
if (item.workspacePath && workspace.path) {
|
|
378
|
+
if (normalizeWorkspacePath(item.workspacePath) === normalizeWorkspacePath(workspace.path)) return true;
|
|
379
|
+
}
|
|
380
|
+
if (item.savedTo && workspace.path) {
|
|
381
|
+
const normSaved = normalizeWorkspacePath(item.savedTo);
|
|
382
|
+
const normWs = normalizeWorkspacePath(workspace.path);
|
|
383
|
+
if (normSaved === normWs || normSaved.startsWith(normWs + "/")) return true;
|
|
384
|
+
}
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
330
387
|
//#endregion
|
|
331
388
|
//#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
389
|
/**
|
|
@@ -456,6 +513,26 @@ window.__ModuleLoader__.load({
|
|
|
456
513
|
* This source code is licensed under the ISC license.
|
|
457
514
|
* See the LICENSE file in the root directory of this source tree.
|
|
458
515
|
*/
|
|
516
|
+
const BookmarkPlus = createLucideIcon("bookmark-plus", [
|
|
517
|
+
["path", {
|
|
518
|
+
d: "M12 7v6",
|
|
519
|
+
key: "lw1j43"
|
|
520
|
+
}],
|
|
521
|
+
["path", {
|
|
522
|
+
d: "M15 10H9",
|
|
523
|
+
key: "o6yqo3"
|
|
524
|
+
}],
|
|
525
|
+
["path", {
|
|
526
|
+
d: "M17 3a2 2 0 0 1 2 2v15a1 1 0 0 1-1.496.868l-4.512-2.578a2 2 0 0 0-1.984 0l-4.512 2.578A1 1 0 0 1 5 20V5a2 2 0 0 1 2-2z",
|
|
527
|
+
key: "oz39mx"
|
|
528
|
+
}]
|
|
529
|
+
]);
|
|
530
|
+
/**
|
|
531
|
+
* @license lucide-react v1.38.0 - ISC
|
|
532
|
+
*
|
|
533
|
+
* This source code is licensed under the ISC license.
|
|
534
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
535
|
+
*/
|
|
459
536
|
const Check = createLucideIcon("check", [["path", {
|
|
460
537
|
d: "M20 6 9 17l-5-5",
|
|
461
538
|
key: "1gmf2c"
|
|
@@ -466,6 +543,16 @@ window.__ModuleLoader__.load({
|
|
|
466
543
|
* This source code is licensed under the ISC license.
|
|
467
544
|
* See the LICENSE file in the root directory of this source tree.
|
|
468
545
|
*/
|
|
546
|
+
const ChevronDown = createLucideIcon("chevron-down", [["path", {
|
|
547
|
+
d: "m6 9 6 6 6-6",
|
|
548
|
+
key: "qrunsl"
|
|
549
|
+
}]]);
|
|
550
|
+
/**
|
|
551
|
+
* @license lucide-react v1.38.0 - ISC
|
|
552
|
+
*
|
|
553
|
+
* This source code is licensed under the ISC license.
|
|
554
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
555
|
+
*/
|
|
469
556
|
const ChevronLeft = createLucideIcon("chevron-left", [["path", {
|
|
470
557
|
d: "m15 18-6-6 6-6",
|
|
471
558
|
key: "1wnfg3"
|
|
@@ -486,6 +573,24 @@ window.__ModuleLoader__.load({
|
|
|
486
573
|
* This source code is licensed under the ISC license.
|
|
487
574
|
* See the LICENSE file in the root directory of this source tree.
|
|
488
575
|
*/
|
|
576
|
+
const Clipboard = createLucideIcon("clipboard", [["rect", {
|
|
577
|
+
width: "8",
|
|
578
|
+
height: "4",
|
|
579
|
+
x: "8",
|
|
580
|
+
y: "2",
|
|
581
|
+
rx: "1",
|
|
582
|
+
ry: "1",
|
|
583
|
+
key: "tgr4d6"
|
|
584
|
+
}], ["path", {
|
|
585
|
+
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",
|
|
586
|
+
key: "116196"
|
|
587
|
+
}]]);
|
|
588
|
+
/**
|
|
589
|
+
* @license lucide-react v1.38.0 - ISC
|
|
590
|
+
*
|
|
591
|
+
* This source code is licensed under the ISC license.
|
|
592
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
593
|
+
*/
|
|
489
594
|
const Copy = createLucideIcon("copy", [["rect", {
|
|
490
595
|
width: "14",
|
|
491
596
|
height: "14",
|
|
@@ -524,6 +629,46 @@ window.__ModuleLoader__.load({
|
|
|
524
629
|
* This source code is licensed under the ISC license.
|
|
525
630
|
* See the LICENSE file in the root directory of this source tree.
|
|
526
631
|
*/
|
|
632
|
+
const Expand = createLucideIcon("expand", [
|
|
633
|
+
["path", {
|
|
634
|
+
d: "m15 15 6 6",
|
|
635
|
+
key: "1s409w"
|
|
636
|
+
}],
|
|
637
|
+
["path", {
|
|
638
|
+
d: "m15 9 6-6",
|
|
639
|
+
key: "ko1vev"
|
|
640
|
+
}],
|
|
641
|
+
["path", {
|
|
642
|
+
d: "M21 16v5h-5",
|
|
643
|
+
key: "1ck2sf"
|
|
644
|
+
}],
|
|
645
|
+
["path", {
|
|
646
|
+
d: "M21 8V3h-5",
|
|
647
|
+
key: "1qoq8a"
|
|
648
|
+
}],
|
|
649
|
+
["path", {
|
|
650
|
+
d: "M3 16v5h5",
|
|
651
|
+
key: "1t08am"
|
|
652
|
+
}],
|
|
653
|
+
["path", {
|
|
654
|
+
d: "m3 21 6-6",
|
|
655
|
+
key: "wwnumi"
|
|
656
|
+
}],
|
|
657
|
+
["path", {
|
|
658
|
+
d: "M3 8V3h5",
|
|
659
|
+
key: "1ln10m"
|
|
660
|
+
}],
|
|
661
|
+
["path", {
|
|
662
|
+
d: "M9 9 3 3",
|
|
663
|
+
key: "v551iv"
|
|
664
|
+
}]
|
|
665
|
+
]);
|
|
666
|
+
/**
|
|
667
|
+
* @license lucide-react v1.38.0 - ISC
|
|
668
|
+
*
|
|
669
|
+
* This source code is licensed under the ISC license.
|
|
670
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
671
|
+
*/
|
|
527
672
|
const FileText = createLucideIcon("file-text", [
|
|
528
673
|
["path", {
|
|
529
674
|
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",
|
|
@@ -552,26 +697,38 @@ window.__ModuleLoader__.load({
|
|
|
552
697
|
* This source code is licensed under the ISC license.
|
|
553
698
|
* See the LICENSE file in the root directory of this source tree.
|
|
554
699
|
*/
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
700
|
+
const Heart = createLucideIcon("heart", [["path", {
|
|
701
|
+
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",
|
|
702
|
+
key: "mvr1a0"
|
|
703
|
+
}]]);
|
|
704
|
+
/**
|
|
705
|
+
* @license lucide-react v1.38.0 - ISC
|
|
706
|
+
*
|
|
707
|
+
* This source code is licensed under the ISC license.
|
|
708
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
709
|
+
*/
|
|
710
|
+
const ImagePlus = createLucideIcon("image-plus", [
|
|
711
|
+
["path", {
|
|
712
|
+
d: "M16 5h6",
|
|
713
|
+
key: "1vod17"
|
|
561
714
|
}],
|
|
562
|
-
["
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
r: "3",
|
|
566
|
-
key: "1lh9wr"
|
|
715
|
+
["path", {
|
|
716
|
+
d: "M19 2v6",
|
|
717
|
+
key: "4bpg5p"
|
|
567
718
|
}],
|
|
568
719
|
["path", {
|
|
569
|
-
d: "
|
|
570
|
-
key: "
|
|
720
|
+
d: "M21 11.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7.5",
|
|
721
|
+
key: "1ue2ih"
|
|
571
722
|
}],
|
|
572
723
|
["path", {
|
|
573
|
-
d: "
|
|
574
|
-
key: "
|
|
724
|
+
d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21",
|
|
725
|
+
key: "1xmnt7"
|
|
726
|
+
}],
|
|
727
|
+
["circle", {
|
|
728
|
+
cx: "9",
|
|
729
|
+
cy: "9",
|
|
730
|
+
r: "2",
|
|
731
|
+
key: "af1f0g"
|
|
575
732
|
}]
|
|
576
733
|
]);
|
|
577
734
|
/**
|
|
@@ -580,16 +737,6 @@ window.__ModuleLoader__.load({
|
|
|
580
737
|
* This source code is licensed under the ISC license.
|
|
581
738
|
* See the LICENSE file in the root directory of this source tree.
|
|
582
739
|
*/
|
|
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
740
|
const Image$1 = createLucideIcon("image", [
|
|
594
741
|
["rect", {
|
|
595
742
|
width: "18",
|
|
@@ -617,30 +764,106 @@ window.__ModuleLoader__.load({
|
|
|
617
764
|
* This source code is licensed under the ISC license.
|
|
618
765
|
* See the LICENSE file in the root directory of this source tree.
|
|
619
766
|
*/
|
|
620
|
-
const
|
|
767
|
+
const LoaderCircle = createLucideIcon("loader-circle", [["path", {
|
|
768
|
+
d: "M21 12a9 9 0 1 1-6.219-8.56",
|
|
769
|
+
key: "13zald"
|
|
770
|
+
}]]);
|
|
771
|
+
/**
|
|
772
|
+
* @license lucide-react v1.38.0 - ISC
|
|
773
|
+
*
|
|
774
|
+
* This source code is licensed under the ISC license.
|
|
775
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
776
|
+
*/
|
|
777
|
+
const PanelLeftClose = createLucideIcon("panel-left-close", [
|
|
778
|
+
["rect", {
|
|
779
|
+
width: "18",
|
|
780
|
+
height: "18",
|
|
781
|
+
x: "3",
|
|
782
|
+
y: "3",
|
|
783
|
+
rx: "2",
|
|
784
|
+
key: "afitv7"
|
|
785
|
+
}],
|
|
786
|
+
["path", {
|
|
787
|
+
d: "M9 3v18",
|
|
788
|
+
key: "fh3hqa"
|
|
789
|
+
}],
|
|
790
|
+
["path", {
|
|
791
|
+
d: "m16 15-3-3 3-3",
|
|
792
|
+
key: "14y99z"
|
|
793
|
+
}]
|
|
794
|
+
]);
|
|
795
|
+
/**
|
|
796
|
+
* @license lucide-react v1.38.0 - ISC
|
|
797
|
+
*
|
|
798
|
+
* This source code is licensed under the ISC license.
|
|
799
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
800
|
+
*/
|
|
801
|
+
const PanelLeft = createLucideIcon("panel-left", [["rect", {
|
|
802
|
+
width: "18",
|
|
803
|
+
height: "18",
|
|
804
|
+
x: "3",
|
|
805
|
+
y: "3",
|
|
806
|
+
rx: "2",
|
|
807
|
+
key: "afitv7"
|
|
808
|
+
}], ["path", {
|
|
809
|
+
d: "M9 3v18",
|
|
810
|
+
key: "fh3hqa"
|
|
811
|
+
}]]);
|
|
812
|
+
/**
|
|
813
|
+
* @license lucide-react v1.38.0 - ISC
|
|
814
|
+
*
|
|
815
|
+
* This source code is licensed under the ISC license.
|
|
816
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
817
|
+
*/
|
|
818
|
+
const PencilLine = createLucideIcon("pencil-line", [
|
|
819
|
+
["path", {
|
|
820
|
+
d: "M13 21h8",
|
|
821
|
+
key: "1jsn5i"
|
|
822
|
+
}],
|
|
621
823
|
["path", {
|
|
622
|
-
d: "
|
|
623
|
-
key: "
|
|
824
|
+
d: "m15 5 4 4",
|
|
825
|
+
key: "1mk7zo"
|
|
624
826
|
}],
|
|
625
827
|
["path", {
|
|
626
|
-
d: "
|
|
627
|
-
key: "
|
|
828
|
+
d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",
|
|
829
|
+
key: "1a8usu"
|
|
830
|
+
}]
|
|
831
|
+
]);
|
|
832
|
+
/**
|
|
833
|
+
* @license lucide-react v1.38.0 - ISC
|
|
834
|
+
*
|
|
835
|
+
* This source code is licensed under the ISC license.
|
|
836
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
837
|
+
*/
|
|
838
|
+
const Plus = createLucideIcon("plus", [["path", {
|
|
839
|
+
d: "M5 12h14",
|
|
840
|
+
key: "1ays0h"
|
|
841
|
+
}], ["path", {
|
|
842
|
+
d: "M12 5v14",
|
|
843
|
+
key: "s699le"
|
|
844
|
+
}]]);
|
|
845
|
+
/**
|
|
846
|
+
* @license lucide-react v1.38.0 - ISC
|
|
847
|
+
*
|
|
848
|
+
* This source code is licensed under the ISC license.
|
|
849
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
850
|
+
*/
|
|
851
|
+
const RefreshCw = createLucideIcon("refresh-cw", [
|
|
852
|
+
["path", {
|
|
853
|
+
d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",
|
|
854
|
+
key: "v9h5vc"
|
|
628
855
|
}],
|
|
629
856
|
["path", {
|
|
630
|
-
d: "
|
|
631
|
-
key: "
|
|
857
|
+
d: "M21 3v5h-5",
|
|
858
|
+
key: "1q7to0"
|
|
632
859
|
}],
|
|
633
860
|
["path", {
|
|
634
|
-
d: "
|
|
635
|
-
key: "
|
|
861
|
+
d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",
|
|
862
|
+
key: "3uifl3"
|
|
636
863
|
}],
|
|
637
|
-
["
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
width: "6",
|
|
641
|
-
height: "6",
|
|
642
|
-
rx: "1",
|
|
643
|
-
key: "cif1o7"
|
|
864
|
+
["path", {
|
|
865
|
+
d: "M8 16H3v5",
|
|
866
|
+
key: "1cv678"
|
|
644
867
|
}]
|
|
645
868
|
]);
|
|
646
869
|
/**
|
|
@@ -708,6 +931,32 @@ window.__ModuleLoader__.load({
|
|
|
708
931
|
* This source code is licensed under the ISC license.
|
|
709
932
|
* See the LICENSE file in the root directory of this source tree.
|
|
710
933
|
*/
|
|
934
|
+
const Sparkles = createLucideIcon("sparkles", [
|
|
935
|
+
["path", {
|
|
936
|
+
d: "M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z",
|
|
937
|
+
key: "1s2grr"
|
|
938
|
+
}],
|
|
939
|
+
["path", {
|
|
940
|
+
d: "M20 2v4",
|
|
941
|
+
key: "1rf3ol"
|
|
942
|
+
}],
|
|
943
|
+
["path", {
|
|
944
|
+
d: "M22 4h-4",
|
|
945
|
+
key: "gwowj6"
|
|
946
|
+
}],
|
|
947
|
+
["circle", {
|
|
948
|
+
cx: "4",
|
|
949
|
+
cy: "20",
|
|
950
|
+
r: "2",
|
|
951
|
+
key: "6kqj1y"
|
|
952
|
+
}]
|
|
953
|
+
]);
|
|
954
|
+
/**
|
|
955
|
+
* @license lucide-react v1.38.0 - ISC
|
|
956
|
+
*
|
|
957
|
+
* This source code is licensed under the ISC license.
|
|
958
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
959
|
+
*/
|
|
711
960
|
const Trash2 = createLucideIcon("trash-2", [
|
|
712
961
|
["path", {
|
|
713
962
|
d: "M10 11v6",
|
|
@@ -756,6 +1005,26 @@ window.__ModuleLoader__.load({
|
|
|
756
1005
|
* This source code is licensed under the ISC license.
|
|
757
1006
|
* See the LICENSE file in the root directory of this source tree.
|
|
758
1007
|
*/
|
|
1008
|
+
const Upload = createLucideIcon("upload", [
|
|
1009
|
+
["path", {
|
|
1010
|
+
d: "M12 3v12",
|
|
1011
|
+
key: "1x0j5s"
|
|
1012
|
+
}],
|
|
1013
|
+
["path", {
|
|
1014
|
+
d: "m17 8-5-5-5 5",
|
|
1015
|
+
key: "7q97r8"
|
|
1016
|
+
}],
|
|
1017
|
+
["path", {
|
|
1018
|
+
d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",
|
|
1019
|
+
key: "ih7n3h"
|
|
1020
|
+
}]
|
|
1021
|
+
]);
|
|
1022
|
+
/**
|
|
1023
|
+
* @license lucide-react v1.38.0 - ISC
|
|
1024
|
+
*
|
|
1025
|
+
* This source code is licensed under the ISC license.
|
|
1026
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
1027
|
+
*/
|
|
759
1028
|
const X = createLucideIcon("x", [["path", {
|
|
760
1029
|
d: "M18 6 6 18",
|
|
761
1030
|
key: "1bl5f8"
|
|
@@ -763,34 +1032,1829 @@ window.__ModuleLoader__.load({
|
|
|
763
1032
|
d: "m6 6 12 12",
|
|
764
1033
|
key: "d8bk6v"
|
|
765
1034
|
}]]);
|
|
1035
|
+
/**
|
|
1036
|
+
* @license lucide-react v1.38.0 - ISC
|
|
1037
|
+
*
|
|
1038
|
+
* This source code is licensed under the ISC license.
|
|
1039
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
1040
|
+
*/
|
|
1041
|
+
const ZoomIn = createLucideIcon("zoom-in", [
|
|
1042
|
+
["circle", {
|
|
1043
|
+
cx: "11",
|
|
1044
|
+
cy: "11",
|
|
1045
|
+
r: "8",
|
|
1046
|
+
key: "4ej97u"
|
|
1047
|
+
}],
|
|
1048
|
+
["line", {
|
|
1049
|
+
x1: "21",
|
|
1050
|
+
x2: "16.65",
|
|
1051
|
+
y1: "21",
|
|
1052
|
+
y2: "16.65",
|
|
1053
|
+
key: "13gj7c"
|
|
1054
|
+
}],
|
|
1055
|
+
["line", {
|
|
1056
|
+
x1: "11",
|
|
1057
|
+
x2: "11",
|
|
1058
|
+
y1: "8",
|
|
1059
|
+
y2: "14",
|
|
1060
|
+
key: "1vmskp"
|
|
1061
|
+
}],
|
|
1062
|
+
["line", {
|
|
1063
|
+
x1: "8",
|
|
1064
|
+
x2: "14",
|
|
1065
|
+
y1: "11",
|
|
1066
|
+
y2: "11",
|
|
1067
|
+
key: "durymu"
|
|
1068
|
+
}]
|
|
1069
|
+
]);
|
|
1070
|
+
/**
|
|
1071
|
+
* @license lucide-react v1.38.0 - ISC
|
|
1072
|
+
*
|
|
1073
|
+
* This source code is licensed under the ISC license.
|
|
1074
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
1075
|
+
*/
|
|
1076
|
+
const ZoomOut = createLucideIcon("zoom-out", [
|
|
1077
|
+
["circle", {
|
|
1078
|
+
cx: "11",
|
|
1079
|
+
cy: "11",
|
|
1080
|
+
r: "8",
|
|
1081
|
+
key: "4ej97u"
|
|
1082
|
+
}],
|
|
1083
|
+
["line", {
|
|
1084
|
+
x1: "21",
|
|
1085
|
+
x2: "16.65",
|
|
1086
|
+
y1: "21",
|
|
1087
|
+
y2: "16.65",
|
|
1088
|
+
key: "13gj7c"
|
|
1089
|
+
}],
|
|
1090
|
+
["line", {
|
|
1091
|
+
x1: "8",
|
|
1092
|
+
x2: "14",
|
|
1093
|
+
y1: "11",
|
|
1094
|
+
y2: "11",
|
|
1095
|
+
key: "durymu"
|
|
1096
|
+
}]
|
|
1097
|
+
]);
|
|
1098
|
+
let currentCacheBytes = 0;
|
|
1099
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1100
|
+
/**
|
|
1101
|
+
* Fetch or reuse an image Blob by attachment ID.
|
|
1102
|
+
* Refreshes LRU recency on hit and enforces count/byte limits on arrival.
|
|
1103
|
+
*/
|
|
1104
|
+
function fetchAttachmentBlob(attachment) {
|
|
1105
|
+
const key = String(attachment.attachmentId);
|
|
1106
|
+
const existing = cache.get(key);
|
|
1107
|
+
if (existing !== void 0) {
|
|
1108
|
+
cache.delete(key);
|
|
1109
|
+
cache.set(key, existing);
|
|
1110
|
+
return existing.promise;
|
|
1111
|
+
}
|
|
1112
|
+
const entry = {
|
|
1113
|
+
promise: Promise.resolve().then(async () => {
|
|
1114
|
+
try {
|
|
1115
|
+
const response = await fetch(IMAGE_ROUTE, {
|
|
1116
|
+
method: "POST",
|
|
1117
|
+
credentials: "same-origin",
|
|
1118
|
+
headers: { "content-type": "application/json" },
|
|
1119
|
+
body: JSON.stringify({ attachment })
|
|
1120
|
+
});
|
|
1121
|
+
if (!response.ok) throw new Error(`Image unavailable (${response.status})`);
|
|
1122
|
+
const blob = await response.blob();
|
|
1123
|
+
if (cache.get(key) === entry) {
|
|
1124
|
+
entry.bytes = blob.size;
|
|
1125
|
+
currentCacheBytes += blob.size;
|
|
1126
|
+
enforceLimits();
|
|
1127
|
+
}
|
|
1128
|
+
return blob;
|
|
1129
|
+
} catch (err) {
|
|
1130
|
+
if (cache.get(key) === entry) cache.delete(key);
|
|
1131
|
+
throw err;
|
|
1132
|
+
}
|
|
1133
|
+
}),
|
|
1134
|
+
bytes: 0
|
|
1135
|
+
};
|
|
1136
|
+
cache.set(key, entry);
|
|
1137
|
+
enforceLimits();
|
|
1138
|
+
return entry.promise;
|
|
1139
|
+
}
|
|
1140
|
+
/** Evict one item from memory cache (e.g. when deleted from gallery). */
|
|
1141
|
+
function evictAttachmentCache(attachmentId) {
|
|
1142
|
+
const key = String(attachmentId);
|
|
1143
|
+
const entry = cache.get(key);
|
|
1144
|
+
if (entry !== void 0) {
|
|
1145
|
+
currentCacheBytes = Math.max(0, currentCacheBytes - entry.bytes);
|
|
1146
|
+
cache.delete(key);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
function enforceLimits() {
|
|
1150
|
+
while (cache.size > 30 || currentCacheBytes > 134217728) {
|
|
1151
|
+
const oldestKey = cache.keys().next().value;
|
|
1152
|
+
if (oldestKey === void 0) break;
|
|
1153
|
+
const oldest = cache.get(oldestKey);
|
|
1154
|
+
if (oldest !== void 0) currentCacheBytes = Math.max(0, currentCacheBytes - oldest.bytes);
|
|
1155
|
+
cache.delete(oldestKey);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
766
1158
|
//#endregion
|
|
767
|
-
//#region lib/types/client/
|
|
1159
|
+
//#region lib/types/client/browser-image-utils.js
|
|
768
1160
|
/**
|
|
769
|
-
*
|
|
770
|
-
*
|
|
771
|
-
* responsive image grid, and placeholder routes.
|
|
1161
|
+
* Shared browser image and DOM helpers for client views.
|
|
1162
|
+
* Completely decoupled from gallery-view and studio-view to prevent circular dependencies.
|
|
772
1163
|
*/
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
1164
|
+
/** Copy an image Blob to the OS clipboard, converting to PNG if required by the browser. */
|
|
1165
|
+
async function copyImageBlob(blob) {
|
|
1166
|
+
try {
|
|
1167
|
+
if (blob.type === "image/png") {
|
|
1168
|
+
await navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]);
|
|
1169
|
+
return true;
|
|
1170
|
+
}
|
|
1171
|
+
if (typeof createImageBitmap === "function") {
|
|
1172
|
+
const bitmap = await createImageBitmap(blob);
|
|
1173
|
+
try {
|
|
1174
|
+
const canvas = document.createElement("canvas");
|
|
1175
|
+
canvas.width = bitmap.width;
|
|
1176
|
+
canvas.height = bitmap.height;
|
|
1177
|
+
const ctx = canvas.getContext("2d");
|
|
1178
|
+
if (!ctx) return false;
|
|
1179
|
+
ctx.drawImage(bitmap, 0, 0);
|
|
1180
|
+
const pngBlob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
|
|
1181
|
+
if (!pngBlob) return false;
|
|
1182
|
+
await navigator.clipboard.write([new ClipboardItem({ "image/png": pngBlob })]);
|
|
1183
|
+
return true;
|
|
1184
|
+
} finally {
|
|
1185
|
+
bitmap.close();
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
const img = new Image();
|
|
1189
|
+
const url = URL.createObjectURL(blob);
|
|
1190
|
+
try {
|
|
1191
|
+
await new Promise((resolve, reject) => {
|
|
1192
|
+
img.onload = () => resolve();
|
|
1193
|
+
img.onerror = () => reject(/* @__PURE__ */ new Error("Image decode failed"));
|
|
1194
|
+
img.src = url;
|
|
1195
|
+
});
|
|
1196
|
+
const canvas = document.createElement("canvas");
|
|
1197
|
+
canvas.width = img.naturalWidth;
|
|
1198
|
+
canvas.height = img.naturalHeight;
|
|
1199
|
+
const ctx = canvas.getContext("2d");
|
|
1200
|
+
if (!ctx) return false;
|
|
1201
|
+
ctx.drawImage(img, 0, 0);
|
|
1202
|
+
const pngBlob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
|
|
1203
|
+
if (!pngBlob) return false;
|
|
1204
|
+
await navigator.clipboard.write([new ClipboardItem({ "image/png": pngBlob })]);
|
|
1205
|
+
return true;
|
|
1206
|
+
} finally {
|
|
1207
|
+
URL.revokeObjectURL(url);
|
|
1208
|
+
}
|
|
1209
|
+
} catch {
|
|
1210
|
+
return false;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
/** Safely trigger a browser file download by mounting an anchor element into the DOM. */
|
|
1214
|
+
function downloadBlobUrl(url, filename) {
|
|
1215
|
+
const link = document.createElement("a");
|
|
1216
|
+
link.href = url;
|
|
1217
|
+
link.download = filename;
|
|
1218
|
+
document.body.appendChild(link);
|
|
1219
|
+
try {
|
|
1220
|
+
link.click();
|
|
1221
|
+
} finally {
|
|
1222
|
+
document.body.removeChild(link);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
/** Human-readable relative time formatter supporting zh and en. */
|
|
1226
|
+
function formatRelativeTime$1(timestamp, lang) {
|
|
1227
|
+
const diff = Math.max(0, Date.now() - timestamp);
|
|
1228
|
+
if (lang === "zh") {
|
|
1229
|
+
if (diff < 6e4) return "刚刚";
|
|
1230
|
+
if (diff < 36e5) return `${Math.floor(diff / 6e4)} 分钟前`;
|
|
1231
|
+
if (diff < 864e5) return `${Math.floor(diff / 36e5)} 小时前`;
|
|
1232
|
+
if (diff < 2592e6) return `${Math.floor(diff / 864e5)} 天前`;
|
|
1233
|
+
const d = new Date(timestamp);
|
|
1234
|
+
return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`;
|
|
1235
|
+
}
|
|
1236
|
+
if (diff < 6e4) return "Just now";
|
|
1237
|
+
if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
|
|
1238
|
+
if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
|
|
1239
|
+
if (diff < 2592e6) return `${Math.floor(diff / 864e5)}d ago`;
|
|
1240
|
+
const d = new Date(timestamp);
|
|
1241
|
+
return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`;
|
|
1242
|
+
}
|
|
1243
|
+
//#endregion
|
|
1244
|
+
//#region lib/types/client/studio-view.js
|
|
1245
|
+
const COPY = {
|
|
1246
|
+
zh: {
|
|
1247
|
+
title: "云端生图工作台",
|
|
1248
|
+
configured: "API 已配置",
|
|
1249
|
+
unconfigured: "未配置",
|
|
1250
|
+
recent: "最近生成",
|
|
1251
|
+
empty: "暂无生成历史",
|
|
1252
|
+
generate: "文生图",
|
|
1253
|
+
edit: "图生图",
|
|
1254
|
+
details: "图片详情",
|
|
1255
|
+
reference: "参考图",
|
|
1256
|
+
optional: "选填",
|
|
1257
|
+
upload: "点击或拖拽图片到此处",
|
|
1258
|
+
uploadHint: "支持 JPG / PNG / WebP / GIF,最大 10MB(最多 5 张)",
|
|
1259
|
+
prompt: "提示词 Prompt",
|
|
1260
|
+
clear: "清空",
|
|
1261
|
+
promptPlaceholder: "描述主体、构图、风格、光线与需要出现的文字…(支持 Ctrl+Enter 快捷生成)",
|
|
1262
|
+
provider: "Provider",
|
|
1263
|
+
model: "Model",
|
|
1264
|
+
ratio: "比例",
|
|
1265
|
+
quality: "清晰度",
|
|
1266
|
+
start: "开始生成",
|
|
1267
|
+
generating: "正在生成…",
|
|
1268
|
+
cancelGenerate: "取消生成",
|
|
1269
|
+
count: "生成数量",
|
|
1270
|
+
countUnit: "{n} 张",
|
|
1271
|
+
partialSuccess: "已生成 {success} 张图片,{failed} 张失败",
|
|
1272
|
+
generatingCount: "正在生成(共 {count} 张)…",
|
|
1273
|
+
batchResult: "本次生成(共 {count} 张)",
|
|
1274
|
+
saveToGallery: "收藏进画廊",
|
|
1275
|
+
savedToGallery: "已收藏进画廊",
|
|
1276
|
+
inGallery: "已在画廊",
|
|
1277
|
+
retry: "重新加载",
|
|
1278
|
+
configLoadFailed: "工作台配置加载失败,请检查服务后重试。",
|
|
1279
|
+
noProvider: "请先在设置中配置至少一个云端图像 Provider 的 API Key。",
|
|
1280
|
+
selectConfigured: "该 Provider 尚未配置,请先到设置中配置 API Key。",
|
|
1281
|
+
needPrompt: "请输入提示词",
|
|
1282
|
+
needReference: "请先添加至少一张参考图",
|
|
1283
|
+
result: "本次结果",
|
|
1284
|
+
continueEdit: "继续编辑(垫图)",
|
|
1285
|
+
regenerate: "再次生成",
|
|
1286
|
+
copy: "复制",
|
|
1287
|
+
copied: "已复制到剪贴板",
|
|
1288
|
+
copyFailed: "复制失败",
|
|
1289
|
+
download: "下载",
|
|
1290
|
+
remove: "删除",
|
|
1291
|
+
fit: "适应窗口",
|
|
1292
|
+
loading: "正在读取图片…",
|
|
1293
|
+
generationFailed: "生成失败",
|
|
1294
|
+
selectHistory: "从左侧选择一张图片,或在右侧开始新的生成。",
|
|
1295
|
+
created: "生成时间",
|
|
1296
|
+
elapsed: "耗时",
|
|
1297
|
+
dimensions: "尺寸",
|
|
1298
|
+
output: "输出参数",
|
|
1299
|
+
closeReference: "移除参考图",
|
|
1300
|
+
uploadInvalid: "请选择有效的图片文件(最大 10MB)",
|
|
1301
|
+
imageLoadFailed: "图片读取失败",
|
|
1302
|
+
fullscreen: "大图全屏",
|
|
1303
|
+
close: "关闭",
|
|
1304
|
+
copyPpt: "复制 Prompt",
|
|
1305
|
+
copiedPrompt: "已复制 Prompt",
|
|
1306
|
+
copiedImage: "已复制图片",
|
|
1307
|
+
favorite: "收藏",
|
|
1308
|
+
favorited: "已收藏",
|
|
1309
|
+
favoriteAdded: "已添加到收藏",
|
|
1310
|
+
favoriteRemoved: "已取消收藏",
|
|
1311
|
+
loadMore: "加载更多 ({n})",
|
|
1312
|
+
deleteModalTitle: "从图库删除",
|
|
1313
|
+
deleteModalDesc: "确定从图库中删除这张图片吗?(原聊天记录不会受影响)",
|
|
1314
|
+
deleteWorkspaceFilesLabel: "同时删除工作区本地文件",
|
|
1315
|
+
confirm: "确定删除",
|
|
1316
|
+
cancel: "取消",
|
|
1317
|
+
deletedToast: "已从图库删除",
|
|
1318
|
+
referencesCount: "参考图 ({current}/{max})",
|
|
1319
|
+
addMoreRef: "+ 添加",
|
|
1320
|
+
maxReferencesExceeded: "最多支持添加 {max} 张参考图",
|
|
1321
|
+
newGeneration: "新建生成",
|
|
1322
|
+
new: "新建",
|
|
1323
|
+
configuredCount: "API 已配置 · {count}",
|
|
1324
|
+
unconfiguredStatus: "API 未配置",
|
|
1325
|
+
providerStatus: "云端提供商与模型状态",
|
|
1326
|
+
collapseSidebar: "折叠最近生成",
|
|
1327
|
+
expandSidebar: "展开最近生成"
|
|
1328
|
+
},
|
|
1329
|
+
en: {
|
|
1330
|
+
title: "Cloud Image Studio",
|
|
1331
|
+
configured: "API configured",
|
|
1332
|
+
unconfigured: "Not configured",
|
|
1333
|
+
recent: "Recent generations",
|
|
1334
|
+
empty: "No generated images yet",
|
|
1335
|
+
generate: "Text to image",
|
|
1336
|
+
edit: "Image to image",
|
|
1337
|
+
details: "Image details",
|
|
1338
|
+
reference: "Reference image",
|
|
1339
|
+
optional: "optional",
|
|
1340
|
+
upload: "Click or drop images here",
|
|
1341
|
+
uploadHint: "JPG / PNG / WebP / GIF, up to 10MB (max 5)",
|
|
1342
|
+
prompt: "Prompt",
|
|
1343
|
+
clear: "Clear",
|
|
1344
|
+
promptPlaceholder: "Describe the subject, composition, style, lighting, and exact text… (Ctrl+Enter to generate)",
|
|
1345
|
+
provider: "Provider",
|
|
1346
|
+
model: "Model",
|
|
1347
|
+
ratio: "Aspect ratio",
|
|
1348
|
+
quality: "Quality",
|
|
1349
|
+
start: "Generate",
|
|
1350
|
+
generating: "Generating…",
|
|
1351
|
+
cancelGenerate: "Cancel",
|
|
1352
|
+
count: "Number of images",
|
|
1353
|
+
countUnit: "{n}",
|
|
1354
|
+
partialSuccess: "Generated {success} images, {failed} failed",
|
|
1355
|
+
generatingCount: "Generating ({count} images)…",
|
|
1356
|
+
batchResult: "Generated {count} images",
|
|
1357
|
+
saveToGallery: "Save to Gallery",
|
|
1358
|
+
savedToGallery: "Saved to Gallery",
|
|
1359
|
+
inGallery: "In Gallery",
|
|
1360
|
+
retry: "Retry",
|
|
1361
|
+
configLoadFailed: "Failed to load studio configuration.",
|
|
1362
|
+
noProvider: "Configure an API key for at least one cloud image provider in Settings.",
|
|
1363
|
+
selectConfigured: "This provider is not configured. Add its API key in Settings first.",
|
|
1364
|
+
needPrompt: "Enter a prompt",
|
|
1365
|
+
needReference: "Add at least one reference image first",
|
|
1366
|
+
result: "Current result",
|
|
1367
|
+
continueEdit: "Continue editing",
|
|
1368
|
+
regenerate: "Generate again",
|
|
1369
|
+
copy: "Copy",
|
|
1370
|
+
copied: "Copied to clipboard",
|
|
1371
|
+
copyFailed: "Copy failed",
|
|
1372
|
+
download: "Download",
|
|
1373
|
+
remove: "Delete",
|
|
1374
|
+
fit: "Fit",
|
|
1375
|
+
loading: "Loading image…",
|
|
1376
|
+
generationFailed: "Generation failed",
|
|
1377
|
+
selectHistory: "Select an image on the left, or start a new generation on the right.",
|
|
1378
|
+
created: "Created",
|
|
1379
|
+
elapsed: "Elapsed",
|
|
1380
|
+
dimensions: "Dimensions",
|
|
1381
|
+
output: "Output",
|
|
1382
|
+
closeReference: "Remove reference",
|
|
1383
|
+
uploadInvalid: "Choose a valid image file up to 10MB.",
|
|
1384
|
+
imageLoadFailed: "Could not load image",
|
|
1385
|
+
fullscreen: "Fullscreen",
|
|
1386
|
+
close: "Close",
|
|
1387
|
+
copyPpt: "Copy Prompt",
|
|
1388
|
+
copiedPrompt: "Prompt copied",
|
|
1389
|
+
copiedImage: "Image copied",
|
|
1390
|
+
favorite: "Favorite",
|
|
1391
|
+
favorited: "Favorited",
|
|
1392
|
+
favoriteAdded: "Added to favorites",
|
|
1393
|
+
favoriteRemoved: "Removed from favorites",
|
|
1394
|
+
loadMore: "Load more ({n})",
|
|
1395
|
+
deleteModalTitle: "Delete from gallery",
|
|
1396
|
+
deleteModalDesc: "Remove this image from the local gallery? (Chat history remains unaffected)",
|
|
1397
|
+
deleteWorkspaceFilesLabel: "Also delete local workspace files",
|
|
1398
|
+
confirm: "Delete",
|
|
1399
|
+
cancel: "Cancel",
|
|
1400
|
+
deletedToast: "Deleted from gallery",
|
|
1401
|
+
referencesCount: "References ({current}/{max})",
|
|
1402
|
+
addMoreRef: "+ Add",
|
|
1403
|
+
maxReferencesExceeded: "Up to {max} reference images allowed",
|
|
1404
|
+
newGeneration: "New generation",
|
|
1405
|
+
new: "New",
|
|
1406
|
+
configuredCount: "API configured · {count}",
|
|
1407
|
+
unconfiguredStatus: "Not configured",
|
|
1408
|
+
providerStatus: "Provider & Model Status",
|
|
1409
|
+
collapseSidebar: "Collapse sidebar",
|
|
1410
|
+
expandSidebar: "Expand recent list"
|
|
1411
|
+
}
|
|
1412
|
+
};
|
|
1413
|
+
const StudioView = ({ locale, workspace }) => {
|
|
1414
|
+
const [lang, setLang] = (0, react.useState)(() => locale?.getSnapshot?.().active?.startsWith("en") ? "en" : "zh");
|
|
1415
|
+
const [config, setConfig] = (0, react.useState)(null);
|
|
1416
|
+
const [configLoading, setConfigLoading] = (0, react.useState)(true);
|
|
1417
|
+
const [configError, setConfigError] = (0, react.useState)(null);
|
|
1418
|
+
const [items, setItems] = (0, react.useState)([]);
|
|
1419
|
+
const [sidebarCollapsed, setSidebarCollapsed] = (0, react.useState)(false);
|
|
1420
|
+
const [visibleLimit, setVisibleLimit] = (0, react.useState)(30);
|
|
1421
|
+
const [selected, setSelected] = (0, react.useState)(null);
|
|
1422
|
+
const [mode, setMode] = (0, react.useState)("generate");
|
|
1423
|
+
const [panelTab, setPanelTab] = (0, react.useState)("generate");
|
|
1424
|
+
const [provider, setProvider] = (0, react.useState)("google");
|
|
1425
|
+
const [model, setModel] = (0, react.useState)("");
|
|
1426
|
+
const [ratio, setRatio] = (0, react.useState)("1:1");
|
|
1427
|
+
const [quality, setQuality] = (0, react.useState)("1K");
|
|
1428
|
+
const [prompt, setPrompt] = (0, react.useState)("");
|
|
1429
|
+
const [references, setReferences] = (0, react.useState)([]);
|
|
1430
|
+
const [count, setCount] = (0, react.useState)(1);
|
|
1431
|
+
const [currentBatch, setCurrentBatch] = (0, react.useState)(null);
|
|
1432
|
+
const [isGenerating, setIsGenerating] = (0, react.useState)(false);
|
|
1433
|
+
const [error, setError] = (0, react.useState)(null);
|
|
1434
|
+
const [notice, setNotice] = (0, react.useState)(null);
|
|
1435
|
+
const [zoom, setZoom] = (0, react.useState)(100);
|
|
1436
|
+
const [fit, setFit] = (0, react.useState)(true);
|
|
1437
|
+
const [offset, setOffset] = (0, react.useState)({
|
|
1438
|
+
x: 0,
|
|
1439
|
+
y: 0
|
|
1440
|
+
});
|
|
1441
|
+
const [isDragging, setIsDragging] = (0, react.useState)(false);
|
|
1442
|
+
const dragStartRef = (0, react.useRef)(null);
|
|
1443
|
+
const hasDraggedRef = (0, react.useRef)(false);
|
|
1444
|
+
const [dragging, setDragging] = (0, react.useState)(false);
|
|
1445
|
+
const [lightboxOpen, setLightboxOpen] = (0, react.useState)(false);
|
|
1446
|
+
const [showDeleteModal, setShowDeleteModal] = (0, react.useState)(false);
|
|
1447
|
+
const [deleteWorkspaceFiles, setDeleteWorkspaceFiles] = (0, react.useState)(true);
|
|
1448
|
+
const requestRef = (0, react.useRef)(null);
|
|
1449
|
+
const canvasRef = (0, react.useRef)(null);
|
|
1450
|
+
const fileInputRef = (0, react.useRef)(null);
|
|
1451
|
+
const image = useAttachmentImage(selected?.attachment);
|
|
1452
|
+
const maxReferences = provider === "dashscope" ? 3 : 5;
|
|
1453
|
+
const referencesRef = (0, react.useRef)(references);
|
|
1454
|
+
referencesRef.current = references;
|
|
1455
|
+
const t = (key, values) => {
|
|
1456
|
+
let text = COPY[lang][key];
|
|
1457
|
+
for (const [name, value] of Object.entries(values ?? {})) text = text.replace(`{${name}}`, value);
|
|
1458
|
+
return text;
|
|
1459
|
+
};
|
|
1460
|
+
(0, react.useEffect)(() => locale?.subscribe?.(() => setLang(locale.getSnapshot().active?.startsWith("en") ? "en" : "zh")), [locale]);
|
|
1461
|
+
const loadConfig = (0, react.useCallback)(async () => {
|
|
1462
|
+
setConfigLoading(true);
|
|
1463
|
+
setConfigError(null);
|
|
1464
|
+
const controller = new AbortController();
|
|
1465
|
+
try {
|
|
1466
|
+
const response = await fetch(STUDIO_ROUTE, {
|
|
1467
|
+
signal: controller.signal,
|
|
1468
|
+
credentials: "same-origin"
|
|
1469
|
+
});
|
|
1470
|
+
const payload = await response.json();
|
|
1471
|
+
if (!response.ok || !("providers" in payload)) throw new Error("error" in payload && payload.error ? payload.error : "Studio unavailable");
|
|
1472
|
+
setConfig(payload);
|
|
1473
|
+
const initial = payload.providers.find((item) => item.provider === payload.activeProvider) ?? payload.providers[0];
|
|
1474
|
+
if (initial !== void 0) applyProvider(initial);
|
|
1475
|
+
} catch (fetchError) {
|
|
1476
|
+
if (!controller.signal.aborted) setConfigError(messageOf(fetchError));
|
|
1477
|
+
} finally {
|
|
1478
|
+
setConfigLoading(false);
|
|
1479
|
+
}
|
|
1480
|
+
}, []);
|
|
1481
|
+
(0, react.useEffect)(() => {
|
|
1482
|
+
loadConfig();
|
|
1483
|
+
}, [loadConfig]);
|
|
1484
|
+
(0, react.useEffect)(() => {
|
|
1485
|
+
let mounted = true;
|
|
1486
|
+
const load = () => void getGalleryItems().then((next) => {
|
|
1487
|
+
if (!mounted) return;
|
|
1488
|
+
setItems(next);
|
|
1489
|
+
setSelected((current) => current === null ? null : next.find((item) => item.id === current.id) ?? null);
|
|
1490
|
+
});
|
|
1491
|
+
load();
|
|
1492
|
+
const unsubscribe = subscribeGallery(load);
|
|
1493
|
+
return () => {
|
|
1494
|
+
mounted = false;
|
|
1495
|
+
unsubscribe();
|
|
1496
|
+
};
|
|
1497
|
+
}, []);
|
|
1498
|
+
(0, react.useEffect)(() => {
|
|
1499
|
+
return () => {
|
|
1500
|
+
requestRef.current?.abort();
|
|
1501
|
+
};
|
|
1502
|
+
}, []);
|
|
1503
|
+
(0, react.useEffect)(() => {
|
|
1504
|
+
return () => {
|
|
1505
|
+
for (const item of referencesRef.current) if (item.previewUrl.startsWith("blob:")) URL.revokeObjectURL(item.previewUrl);
|
|
1506
|
+
};
|
|
1507
|
+
}, []);
|
|
1508
|
+
(0, react.useEffect)(() => {
|
|
1509
|
+
const handleKeyDown = (e) => {
|
|
1510
|
+
if (e.key === "Escape" && lightboxOpen) setLightboxOpen(false);
|
|
1511
|
+
};
|
|
1512
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
1513
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1514
|
+
}, [lightboxOpen]);
|
|
1515
|
+
(0, react.useEffect)(() => {
|
|
1516
|
+
const canvasEl = canvasRef.current;
|
|
1517
|
+
if (!canvasEl) return;
|
|
1518
|
+
const onWheel = (e) => {
|
|
1519
|
+
if (selected === null && (!currentBatch || currentBatch.length === 0)) return;
|
|
1520
|
+
e.preventDefault();
|
|
1521
|
+
const delta = e.deltaY < 0 ? 15 : -15;
|
|
1522
|
+
setFit(false);
|
|
1523
|
+
setZoom((prev) => Math.min(500, Math.max(25, prev + delta)));
|
|
1524
|
+
};
|
|
1525
|
+
canvasEl.addEventListener("wheel", onWheel, { passive: false });
|
|
1526
|
+
return () => canvasEl.removeEventListener("wheel", onWheel);
|
|
1527
|
+
}, [selected, currentBatch]);
|
|
1528
|
+
(0, react.useEffect)(() => {
|
|
1529
|
+
const handleMouseMove = (e) => {
|
|
1530
|
+
if (!dragStartRef.current) return;
|
|
1531
|
+
const dx = e.clientX - dragStartRef.current.x;
|
|
1532
|
+
const dy = e.clientY - dragStartRef.current.y;
|
|
1533
|
+
if (Math.abs(dx) > 3 || Math.abs(dy) > 3) {
|
|
1534
|
+
hasDraggedRef.current = true;
|
|
1535
|
+
setFit(false);
|
|
1536
|
+
}
|
|
1537
|
+
setOffset({
|
|
1538
|
+
x: dragStartRef.current.startX + dx,
|
|
1539
|
+
y: dragStartRef.current.startY + dy
|
|
1540
|
+
});
|
|
1541
|
+
};
|
|
1542
|
+
const handleMouseUp = () => {
|
|
1543
|
+
dragStartRef.current = null;
|
|
1544
|
+
setIsDragging(false);
|
|
1545
|
+
};
|
|
1546
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
1547
|
+
window.addEventListener("mouseup", handleMouseUp);
|
|
1548
|
+
return () => {
|
|
1549
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
1550
|
+
window.removeEventListener("mouseup", handleMouseUp);
|
|
1551
|
+
};
|
|
1552
|
+
}, []);
|
|
1553
|
+
const activeProfile = (0, react.useMemo)(() => config?.providers.find((item) => item.provider === provider), [config, provider]);
|
|
1554
|
+
const configuredCount = config?.providers.filter((item) => item.configured).length ?? 0;
|
|
1555
|
+
const displayItems = (0, react.useMemo)(() => items.slice(0, visibleLimit), [items, visibleLimit]);
|
|
1556
|
+
const applyProvider = (profile) => {
|
|
1557
|
+
setProvider(profile.provider);
|
|
1558
|
+
setModel(profile.model);
|
|
1559
|
+
setRatio(profile.defaultRatio);
|
|
1560
|
+
setQuality(profile.defaultQuality);
|
|
1561
|
+
setError(null);
|
|
1562
|
+
};
|
|
1563
|
+
const changeProvider = (value) => {
|
|
1564
|
+
const profile = config?.providers.find((item) => item.provider === value);
|
|
1565
|
+
if (profile !== void 0) {
|
|
1566
|
+
applyProvider(profile);
|
|
1567
|
+
const providerMax = value === "dashscope" ? 3 : 5;
|
|
1568
|
+
if (referencesRef.current.length > providerMax) {
|
|
1569
|
+
const keep = referencesRef.current.slice(0, providerMax);
|
|
1570
|
+
const overflow = referencesRef.current.slice(providerMax);
|
|
1571
|
+
for (const item of overflow) if (item.previewUrl.startsWith("blob:")) URL.revokeObjectURL(item.previewUrl);
|
|
1572
|
+
setReferences(keep);
|
|
1573
|
+
referencesRef.current = keep;
|
|
1574
|
+
setError(t("maxReferencesExceeded", { max: String(providerMax) }));
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
const resetFit = () => {
|
|
1579
|
+
setFit(true);
|
|
1580
|
+
setZoom(100);
|
|
1581
|
+
setOffset({
|
|
1582
|
+
x: 0,
|
|
1583
|
+
y: 0
|
|
1584
|
+
});
|
|
1585
|
+
};
|
|
1586
|
+
const selectItem = (item) => {
|
|
1587
|
+
setCurrentBatch(null);
|
|
1588
|
+
setSelected(item);
|
|
1589
|
+
setPanelTab("details");
|
|
1590
|
+
resetFit();
|
|
1591
|
+
};
|
|
1592
|
+
const startNew = () => {
|
|
1593
|
+
setCurrentBatch(null);
|
|
1594
|
+
setSelected(null);
|
|
1595
|
+
setPanelTab("generate");
|
|
1596
|
+
resetFit();
|
|
1597
|
+
};
|
|
1598
|
+
const handleCanvasMouseDown = (e) => {
|
|
1599
|
+
if (e.button !== 0 && e.button !== 1) return;
|
|
1600
|
+
if (selected === null && (!currentBatch || currentBatch.length === 0)) return;
|
|
1601
|
+
setIsDragging(true);
|
|
1602
|
+
hasDraggedRef.current = false;
|
|
1603
|
+
dragStartRef.current = {
|
|
1604
|
+
x: e.clientX,
|
|
1605
|
+
y: e.clientY,
|
|
1606
|
+
startX: offset.x,
|
|
1607
|
+
startY: offset.y
|
|
1608
|
+
};
|
|
1609
|
+
};
|
|
1610
|
+
const handleImageClick = () => {};
|
|
1611
|
+
const addReferenceFiles = (fileList) => {
|
|
1612
|
+
if (!fileList || fileList.length === 0) return;
|
|
1613
|
+
const files = Array.from(fileList);
|
|
1614
|
+
const validFiles = [];
|
|
1615
|
+
for (const file of files) {
|
|
1616
|
+
if (![
|
|
1617
|
+
"image/jpeg",
|
|
1618
|
+
"image/png",
|
|
1619
|
+
"image/webp",
|
|
1620
|
+
"image/gif"
|
|
1621
|
+
].includes(file.type) || file.size > 10485760) {
|
|
1622
|
+
setError(t("uploadInvalid"));
|
|
1623
|
+
continue;
|
|
1624
|
+
}
|
|
1625
|
+
validFiles.push(file);
|
|
1626
|
+
}
|
|
1627
|
+
if (validFiles.length === 0) return;
|
|
1628
|
+
const currentLen = referencesRef.current.length;
|
|
1629
|
+
const available = maxReferences - currentLen;
|
|
1630
|
+
if (available <= 0) {
|
|
1631
|
+
setError(t("maxReferencesExceeded", { max: String(maxReferences) }));
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
const toAdd = validFiles.slice(0, available);
|
|
1635
|
+
if (validFiles.length > available) setError(t("maxReferencesExceeded", { max: String(maxReferences) }));
|
|
1636
|
+
else setError(null);
|
|
1637
|
+
const newItems = toAdd.map((file, idx) => ({
|
|
1638
|
+
id: `upload-${Date.now()}-${idx}-${Math.random().toString(36).slice(2, 7)}`,
|
|
1639
|
+
file,
|
|
1640
|
+
previewUrl: URL.createObjectURL(file)
|
|
1641
|
+
}));
|
|
1642
|
+
setReferences((prev) => [...prev, ...newItems]);
|
|
1643
|
+
};
|
|
1644
|
+
const removeReference = (id) => {
|
|
1645
|
+
const target = referencesRef.current.find((item) => item.id === id);
|
|
1646
|
+
if (target && target.previewUrl.startsWith("blob:")) URL.revokeObjectURL(target.previewUrl);
|
|
1647
|
+
setReferences((prev) => prev.filter((item) => item.id !== id));
|
|
1648
|
+
};
|
|
1649
|
+
const clearAllReferences = () => {
|
|
1650
|
+
for (const item of referencesRef.current) if (item.previewUrl.startsWith("blob:")) URL.revokeObjectURL(item.previewUrl);
|
|
1651
|
+
setReferences([]);
|
|
1652
|
+
};
|
|
1653
|
+
const continueEdit = async () => {
|
|
1654
|
+
if (selected === null) return;
|
|
1655
|
+
const targetItem = selected;
|
|
1656
|
+
const targetAttId = targetItem.attachment.attachmentId;
|
|
1657
|
+
const targetMax = targetItem.provider === "dashscope" ? 3 : 5;
|
|
1658
|
+
if (referencesRef.current.some((r) => r.attachment?.attachmentId === targetAttId)) {
|
|
1659
|
+
setMode("edit");
|
|
1660
|
+
setPanelTab("generate");
|
|
1661
|
+
changeProvider(targetItem.provider);
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
if (referencesRef.current.length >= targetMax) {
|
|
1665
|
+
setError(t("maxReferencesExceeded", { max: String(targetMax) }));
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1668
|
+
try {
|
|
1669
|
+
const blob = image.blob ?? await fetchAttachmentBlob(targetItem.attachment);
|
|
1670
|
+
if (referencesRef.current.some((r) => r.attachment?.attachmentId === targetAttId)) {
|
|
1671
|
+
setMode("edit");
|
|
1672
|
+
setPanelTab("generate");
|
|
1673
|
+
changeProvider(targetItem.provider);
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
if (referencesRef.current.length >= targetMax) {
|
|
1677
|
+
setError(t("maxReferencesExceeded", { max: String(targetMax) }));
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
const independentUrl = URL.createObjectURL(blob);
|
|
1681
|
+
const newItem = {
|
|
1682
|
+
id: `item-${targetItem.id}-${Date.now()}`,
|
|
1683
|
+
attachment: targetItem.attachment,
|
|
1684
|
+
previewUrl: independentUrl
|
|
1685
|
+
};
|
|
1686
|
+
const nextList = [...referencesRef.current, newItem];
|
|
1687
|
+
setReferences(nextList);
|
|
1688
|
+
referencesRef.current = nextList;
|
|
1689
|
+
setError(null);
|
|
1690
|
+
setMode("edit");
|
|
1691
|
+
setPanelTab("generate");
|
|
1692
|
+
changeProvider(targetItem.provider);
|
|
1693
|
+
} catch {
|
|
1694
|
+
setError(t("imageLoadFailed"));
|
|
1695
|
+
}
|
|
1696
|
+
};
|
|
1697
|
+
const cancelGeneration = () => {
|
|
1698
|
+
if (requestRef.current) {
|
|
1699
|
+
requestRef.current.abort();
|
|
1700
|
+
requestRef.current = null;
|
|
1701
|
+
setIsGenerating(false);
|
|
1702
|
+
}
|
|
1703
|
+
};
|
|
1704
|
+
const submit = async () => {
|
|
1705
|
+
if (isGenerating || activeProfile === void 0) return;
|
|
1706
|
+
if (prompt.trim().length === 0) return setError(t("needPrompt"));
|
|
1707
|
+
if (!activeProfile.configured) return setError(t("selectConfigured"));
|
|
1708
|
+
if (mode === "edit" && references.length === 0) return setError(t("needReference"));
|
|
1709
|
+
setIsGenerating(true);
|
|
1710
|
+
setError(null);
|
|
1711
|
+
const controller = new AbortController();
|
|
1712
|
+
requestRef.current = controller;
|
|
1713
|
+
try {
|
|
1714
|
+
const referencesPayload = mode === "edit" && references.length > 0 ? await Promise.all(references.map(async (ref) => {
|
|
1715
|
+
if (ref.attachment !== void 0) return { attachment: ref.attachment };
|
|
1716
|
+
if (ref.file !== void 0) return {
|
|
1717
|
+
mediaType: ref.file.type,
|
|
1718
|
+
data: await fileToBase64(ref.file),
|
|
1719
|
+
...ref.file.name ? { name: ref.file.name } : {}
|
|
1720
|
+
};
|
|
1721
|
+
throw new Error("无效参考图");
|
|
1722
|
+
})) : void 0;
|
|
1723
|
+
const response = await fetch(STUDIO_ROUTE, {
|
|
1724
|
+
method: "POST",
|
|
1725
|
+
credentials: "same-origin",
|
|
1726
|
+
signal: controller.signal,
|
|
1727
|
+
headers: { "content-type": "application/json" },
|
|
1728
|
+
body: JSON.stringify({
|
|
1729
|
+
mode,
|
|
1730
|
+
provider,
|
|
1731
|
+
model,
|
|
1732
|
+
prompt: prompt.trim(),
|
|
1733
|
+
ratio,
|
|
1734
|
+
quality,
|
|
1735
|
+
...count > 1 ? { count } : {},
|
|
1736
|
+
...workspace?.path ? { workspaceRoot: workspace.path } : {},
|
|
1737
|
+
...referencesPayload === void 0 ? {} : { references: referencesPayload }
|
|
1738
|
+
})
|
|
1739
|
+
});
|
|
1740
|
+
const payload = await response.json();
|
|
1741
|
+
if (!response.ok || !("attachment" in payload)) throw new Error("error" in payload && payload.error ? payload.error : t("generationFailed"));
|
|
1742
|
+
const generatedList = Array.isArray(payload.items) && payload.items.length > 0 ? payload.items : [{
|
|
1743
|
+
attachment: payload.attachment,
|
|
1744
|
+
output: payload.output,
|
|
1745
|
+
...payload.savedTo ? { savedTo: payload.savedTo } : {}
|
|
1746
|
+
}];
|
|
1747
|
+
const galleryEntries = generatedList.map((gen, idx) => ({
|
|
1748
|
+
id: String(gen.attachment.attachmentId || `${payload.createdAt}-${idx}`),
|
|
1749
|
+
attachment: gen.attachment,
|
|
1750
|
+
prompt: payload.prompt,
|
|
1751
|
+
provider: payload.provider,
|
|
1752
|
+
model: payload.model,
|
|
1753
|
+
createdAt: payload.createdAt + idx,
|
|
1754
|
+
aspectRatio: ratio,
|
|
1755
|
+
imageSize: quality,
|
|
1756
|
+
output: gen.output,
|
|
1757
|
+
...gen.savedTo ? { savedTo: gen.savedTo } : {},
|
|
1758
|
+
...workspace?.path ? { workspacePath: workspace.path } : {},
|
|
1759
|
+
...workspace?.workspaceId ? { workspaceId: workspace.workspaceId } : {}
|
|
1760
|
+
}));
|
|
1761
|
+
if (galleryEntries.length > 1) {
|
|
1762
|
+
setCurrentBatch(galleryEntries);
|
|
1763
|
+
setSelected(galleryEntries[0]);
|
|
1764
|
+
} else if (galleryEntries.length === 1) {
|
|
1765
|
+
setCurrentBatch(null);
|
|
1766
|
+
setSelected(galleryEntries[0]);
|
|
1767
|
+
}
|
|
1768
|
+
setPanelTab("details");
|
|
1769
|
+
resetFit();
|
|
1770
|
+
if (payload.failedCount && payload.failedCount > 0) flash(t("partialSuccess", {
|
|
1771
|
+
success: String(generatedList.length),
|
|
1772
|
+
failed: String(payload.failedCount)
|
|
1773
|
+
}));
|
|
1774
|
+
} catch (submitError) {
|
|
1775
|
+
if (controller.signal.aborted) return;
|
|
1776
|
+
setError(messageOf(submitError));
|
|
1777
|
+
} finally {
|
|
1778
|
+
if (requestRef.current === controller) {
|
|
1779
|
+
requestRef.current = null;
|
|
1780
|
+
setIsGenerating(false);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
};
|
|
1784
|
+
const isSelectedInGallery = (0, react.useMemo)(() => {
|
|
1785
|
+
if (selected === null) return false;
|
|
1786
|
+
return items.some((item) => item.id === selected.id);
|
|
1787
|
+
}, [items, selected]);
|
|
1788
|
+
const handleSaveToGallery = async () => {
|
|
1789
|
+
if (selected === null || isSelectedInGallery) return;
|
|
1790
|
+
let savedTo = selected.savedTo;
|
|
1791
|
+
const targetRoot = workspace?.path || config?.workspaceRoot;
|
|
1792
|
+
if (!savedTo) try {
|
|
1793
|
+
const res = await fetch(SAVE_WORKSPACE_ROUTE, {
|
|
1794
|
+
method: "POST",
|
|
1795
|
+
credentials: "same-origin",
|
|
1796
|
+
headers: { "content-type": "application/json" },
|
|
1797
|
+
body: JSON.stringify({
|
|
1798
|
+
attachment: selected.attachment,
|
|
1799
|
+
...targetRoot ? { workspaceRoot: targetRoot } : {}
|
|
1800
|
+
})
|
|
1801
|
+
});
|
|
1802
|
+
if (res.ok) {
|
|
1803
|
+
const data = await res.json();
|
|
1804
|
+
if (data.savedTo) savedTo = data.savedTo;
|
|
1805
|
+
}
|
|
1806
|
+
} catch (e) {
|
|
1807
|
+
console.warn("Failed to save to workspace:", e);
|
|
1808
|
+
}
|
|
1809
|
+
const updatedItem = {
|
|
1810
|
+
...selected,
|
|
1811
|
+
...savedTo ? { savedTo } : {},
|
|
1812
|
+
...targetRoot ? { workspacePath: targetRoot } : {},
|
|
1813
|
+
...workspace?.workspaceId ? { workspaceId: workspace.workspaceId } : {}
|
|
1814
|
+
};
|
|
1815
|
+
await saveGalleryItem(updatedItem);
|
|
1816
|
+
setSelected(updatedItem);
|
|
1817
|
+
if (currentBatch) setCurrentBatch((prev) => prev ? prev.map((i) => i.id === updatedItem.id ? updatedItem : i) : prev);
|
|
1818
|
+
flash(t("savedToGallery"));
|
|
1819
|
+
};
|
|
1820
|
+
const handleConfirmDelete = async () => {
|
|
1821
|
+
if (selected === null) return;
|
|
1822
|
+
const idToDelete = selected.id;
|
|
1823
|
+
const attachmentId = selected.attachment.attachmentId;
|
|
1824
|
+
const savedPath = selected.savedTo;
|
|
1825
|
+
setShowDeleteModal(false);
|
|
1826
|
+
try {
|
|
1827
|
+
if (deleteWorkspaceFiles && typeof savedPath === "string" && savedPath.trim().length > 0) {
|
|
1828
|
+
const res = await fetch(DELETE_ROUTE, {
|
|
1829
|
+
method: "POST",
|
|
1830
|
+
credentials: "same-origin",
|
|
1831
|
+
headers: { "content-type": "application/json" },
|
|
1832
|
+
body: JSON.stringify({ paths: [savedPath] })
|
|
1833
|
+
});
|
|
1834
|
+
if (!res.ok) throw new Error(`删除请求网络异常 (${res.status})`);
|
|
1835
|
+
const data = await res.json().catch(() => null);
|
|
1836
|
+
if (!data || data.ok === false || Array.isArray(data.failedFiles) && data.failedFiles.length > 0) {
|
|
1837
|
+
const reason = data?.failedFiles?.[0]?.error || "工作区文件删除失败";
|
|
1838
|
+
throw new Error(reason);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
if (isSelectedInGallery) await deleteGalleryItem(idToDelete);
|
|
1842
|
+
evictAttachmentCache(attachmentId);
|
|
1843
|
+
if (currentBatch) {
|
|
1844
|
+
const remaining = currentBatch.filter((i) => i.id !== idToDelete);
|
|
1845
|
+
if (remaining.length > 1) {
|
|
1846
|
+
setCurrentBatch(remaining);
|
|
1847
|
+
setSelected(remaining[0]);
|
|
1848
|
+
} else if (remaining.length === 1) {
|
|
1849
|
+
setCurrentBatch(null);
|
|
1850
|
+
setSelected(remaining[0]);
|
|
1851
|
+
} else {
|
|
1852
|
+
setCurrentBatch(null);
|
|
1853
|
+
setSelected(null);
|
|
1854
|
+
}
|
|
1855
|
+
} else setSelected(null);
|
|
1856
|
+
flash(t("deletedToast"));
|
|
1857
|
+
} catch (delError) {
|
|
1858
|
+
setError(messageOf(delError));
|
|
1859
|
+
}
|
|
1860
|
+
};
|
|
1861
|
+
const copySelected = async () => {
|
|
1862
|
+
if (image.blob === null) return;
|
|
1863
|
+
const ok = await copyImageBlob(image.blob);
|
|
1864
|
+
flash(ok ? t("copiedImage") : t("copyFailed"));
|
|
1865
|
+
};
|
|
1866
|
+
const downloadSelected = () => {
|
|
1867
|
+
if (image.url === null || selected === null) return;
|
|
1868
|
+
downloadBlobUrl(image.url, `dsh-${selected.provider}-${selected.id.slice(-8)}.${extension(selected.attachment.mediaType)}`);
|
|
1869
|
+
};
|
|
1870
|
+
const toggleFavorite = async () => {
|
|
1871
|
+
if (selected === null) return;
|
|
1872
|
+
const targetId = selected.id;
|
|
1873
|
+
if (isSelectedInGallery) {
|
|
1874
|
+
const nextFav = await toggleFavoriteGalleryItem(targetId);
|
|
1875
|
+
setSelected((curr) => curr !== null && curr.id === targetId ? {
|
|
1876
|
+
...curr,
|
|
1877
|
+
isFavorite: nextFav
|
|
1878
|
+
} : curr);
|
|
1879
|
+
if (currentBatch) setCurrentBatch((prev) => prev ? prev.map((i) => i.id === targetId ? {
|
|
1880
|
+
...i,
|
|
1881
|
+
isFavorite: nextFav
|
|
1882
|
+
} : i) : null);
|
|
1883
|
+
flash(nextFav ? t("favoriteAdded") : t("favoriteRemoved"));
|
|
1884
|
+
} else {
|
|
1885
|
+
const nextFav = !selected.isFavorite;
|
|
1886
|
+
setSelected((curr) => curr !== null && curr.id === targetId ? {
|
|
1887
|
+
...curr,
|
|
1888
|
+
isFavorite: nextFav
|
|
1889
|
+
} : curr);
|
|
1890
|
+
if (currentBatch) setCurrentBatch((prev) => prev ? prev.map((i) => i.id === targetId ? {
|
|
1891
|
+
...i,
|
|
1892
|
+
isFavorite: nextFav
|
|
1893
|
+
} : i) : null);
|
|
1894
|
+
flash(nextFav ? t("favoriteAdded") : t("favoriteRemoved"));
|
|
1895
|
+
}
|
|
1896
|
+
};
|
|
1897
|
+
const stepLightbox = (delta) => {
|
|
1898
|
+
if (!currentBatch || currentBatch.length <= 1 || selected === null) return;
|
|
1899
|
+
const currIdx = currentBatch.findIndex((i) => i.id === selected.id);
|
|
1900
|
+
if (currIdx === -1) return;
|
|
1901
|
+
const nextIdx = (currIdx + delta + currentBatch.length) % currentBatch.length;
|
|
1902
|
+
setSelected(currentBatch[nextIdx]);
|
|
1903
|
+
};
|
|
1904
|
+
(0, react.useEffect)(() => {
|
|
1905
|
+
if (!lightboxOpen || !currentBatch || currentBatch.length <= 1) return;
|
|
1906
|
+
const onKeyDown = (e) => {
|
|
1907
|
+
if (e.key === "ArrowLeft") stepLightbox(-1);
|
|
1908
|
+
else if (e.key === "ArrowRight") stepLightbox(1);
|
|
1909
|
+
};
|
|
1910
|
+
window.addEventListener("keydown", onKeyDown);
|
|
1911
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
1912
|
+
}, [
|
|
1913
|
+
lightboxOpen,
|
|
1914
|
+
currentBatch,
|
|
1915
|
+
selected
|
|
1916
|
+
]);
|
|
1917
|
+
const flash = (message) => {
|
|
1918
|
+
setNotice(message);
|
|
1919
|
+
window.setTimeout(() => setNotice(null), 2200);
|
|
1920
|
+
};
|
|
1921
|
+
return (0, react_jsx_runtime.jsxs)("section", {
|
|
1922
|
+
className: "dsh-ig-workbench",
|
|
1923
|
+
"aria-label": t("title"),
|
|
1924
|
+
children: [
|
|
1925
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1926
|
+
className: `dsh-ig-workbench-grid ${sidebarCollapsed ? "is-sidebar-collapsed" : ""}`,
|
|
1927
|
+
children: [
|
|
1928
|
+
!sidebarCollapsed && (0, react_jsx_runtime.jsxs)("aside", {
|
|
1929
|
+
className: "dsh-ig-recent-panel",
|
|
1930
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
1931
|
+
className: "dsh-ig-panel-title",
|
|
1932
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
1933
|
+
style: {
|
|
1934
|
+
display: "flex",
|
|
1935
|
+
alignItems: "center",
|
|
1936
|
+
gap: "8px"
|
|
1937
|
+
},
|
|
1938
|
+
children: [(0, react_jsx_runtime.jsx)("span", { children: t("recent") }), (0, react_jsx_runtime.jsx)("span", {
|
|
1939
|
+
className: "dsh-ig-count-badge",
|
|
1940
|
+
children: items.length
|
|
1941
|
+
})]
|
|
1942
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
1943
|
+
type: "button",
|
|
1944
|
+
className: "dsh-ig-collapse-btn",
|
|
1945
|
+
onClick: () => setSidebarCollapsed(true),
|
|
1946
|
+
title: t("collapseSidebar"),
|
|
1947
|
+
children: (0, react_jsx_runtime.jsx)(PanelLeftClose, { size: 15 })
|
|
1948
|
+
})]
|
|
1949
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
1950
|
+
className: "dsh-ig-recent-scroll",
|
|
1951
|
+
children: [items.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
|
|
1952
|
+
className: "dsh-ig-recent-empty",
|
|
1953
|
+
children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 22 }), (0, react_jsx_runtime.jsx)("span", { children: t("empty") })]
|
|
1954
|
+
}) : displayItems.map((item) => (0, react_jsx_runtime.jsx)(RecentItem, {
|
|
1955
|
+
item,
|
|
1956
|
+
active: selected?.id === item.id,
|
|
1957
|
+
lang,
|
|
1958
|
+
onClick: () => selectItem(item)
|
|
1959
|
+
}, item.id)), items.length > visibleLimit && (0, react_jsx_runtime.jsx)("button", {
|
|
1960
|
+
type: "button",
|
|
1961
|
+
className: "dsh-ig-load-more",
|
|
1962
|
+
onClick: () => setVisibleLimit((l) => l + 30),
|
|
1963
|
+
children: t("loadMore", { n: String(items.length - visibleLimit) })
|
|
1964
|
+
})]
|
|
1965
|
+
})]
|
|
1966
|
+
}),
|
|
1967
|
+
(0, react_jsx_runtime.jsxs)("main", {
|
|
1968
|
+
className: "dsh-ig-canvas-column",
|
|
1969
|
+
children: [
|
|
1970
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1971
|
+
className: "dsh-ig-canvas-toolbar",
|
|
1972
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
1973
|
+
className: "dsh-ig-canvas-toolbar-left",
|
|
1974
|
+
children: [sidebarCollapsed && (0, react_jsx_runtime.jsxs)("button", {
|
|
1975
|
+
type: "button",
|
|
1976
|
+
onClick: () => setSidebarCollapsed(false),
|
|
1977
|
+
title: t("expandSidebar"),
|
|
1978
|
+
children: [(0, react_jsx_runtime.jsx)(PanelLeft, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("recent") })]
|
|
1979
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
1980
|
+
className: "dsh-ig-status-badge-wrap",
|
|
1981
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
1982
|
+
className: "dsh-ig-status-badge",
|
|
1983
|
+
children: [
|
|
1984
|
+
(0, react_jsx_runtime.jsx)("span", { className: `dsh-ig-status-dot ${configuredCount > 0 ? "is-ready" : "is-muted"}` }),
|
|
1985
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1986
|
+
className: "dsh-ig-status-text",
|
|
1987
|
+
children: configuredCount > 0 ? t("configuredCount", { count: String(configuredCount) }) : t("unconfiguredStatus")
|
|
1988
|
+
}),
|
|
1989
|
+
(0, react_jsx_runtime.jsx)(ChevronDown, {
|
|
1990
|
+
size: 11,
|
|
1991
|
+
className: "dsh-ig-status-arrow"
|
|
1992
|
+
})
|
|
1993
|
+
]
|
|
1994
|
+
}), config !== null && (0, react_jsx_runtime.jsxs)("div", {
|
|
1995
|
+
className: "dsh-ig-status-popover",
|
|
1996
|
+
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
1997
|
+
className: "dsh-ig-status-popover-title",
|
|
1998
|
+
children: t("providerStatus")
|
|
1999
|
+
}), (0, react_jsx_runtime.jsx)("ul", {
|
|
2000
|
+
className: "dsh-ig-status-list",
|
|
2001
|
+
children: config.providers.map((p) => (0, react_jsx_runtime.jsxs)("li", {
|
|
2002
|
+
className: p.configured ? "is-configured" : "is-missing",
|
|
2003
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
2004
|
+
className: "dsh-ig-status-item-name",
|
|
2005
|
+
children: [(0, react_jsx_runtime.jsx)("strong", { children: p.label }), (0, react_jsx_runtime.jsx)("small", { children: p.model })]
|
|
2006
|
+
}), (0, react_jsx_runtime.jsx)("span", {
|
|
2007
|
+
className: "dsh-ig-status-item-tag",
|
|
2008
|
+
children: p.configured ? t("configured") : t("unconfigured")
|
|
2009
|
+
})]
|
|
2010
|
+
}, p.provider))
|
|
2011
|
+
})]
|
|
2012
|
+
})]
|
|
2013
|
+
})]
|
|
2014
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
2015
|
+
className: "dsh-ig-canvas-toolbar-right",
|
|
2016
|
+
children: [
|
|
2017
|
+
selected !== null && (0, react_jsx_runtime.jsxs)("button", {
|
|
2018
|
+
type: "button",
|
|
2019
|
+
onClick: startNew,
|
|
2020
|
+
title: t("newGeneration"),
|
|
2021
|
+
children: [(0, react_jsx_runtime.jsx)(Plus, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: t("newGeneration") })]
|
|
2022
|
+
}),
|
|
2023
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
2024
|
+
type: "button",
|
|
2025
|
+
className: fit ? "is-active" : "",
|
|
2026
|
+
onClick: resetFit,
|
|
2027
|
+
children: [
|
|
2028
|
+
t("fit"),
|
|
2029
|
+
" ",
|
|
2030
|
+
(0, react_jsx_runtime.jsx)(ChevronDown, { size: 13 })
|
|
2031
|
+
]
|
|
2032
|
+
}),
|
|
2033
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
2034
|
+
type: "button",
|
|
2035
|
+
onClick: () => {
|
|
2036
|
+
setFit(false);
|
|
2037
|
+
setZoom((value) => Math.max(25, value - 25));
|
|
2038
|
+
},
|
|
2039
|
+
title: "Zoom out",
|
|
2040
|
+
children: (0, react_jsx_runtime.jsx)(ZoomOut, { size: 16 })
|
|
2041
|
+
}),
|
|
2042
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
2043
|
+
className: "dsh-ig-zoom-display",
|
|
2044
|
+
children: fit ? "AUTO" : `${Math.round(zoom)}%`
|
|
2045
|
+
}),
|
|
2046
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
2047
|
+
type: "button",
|
|
2048
|
+
onClick: () => {
|
|
2049
|
+
setFit(false);
|
|
2050
|
+
setZoom((value) => Math.min(500, value + 25));
|
|
2051
|
+
},
|
|
2052
|
+
title: "Zoom in",
|
|
2053
|
+
children: (0, react_jsx_runtime.jsx)(ZoomIn, { size: 16 })
|
|
2054
|
+
}),
|
|
2055
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
2056
|
+
type: "button",
|
|
2057
|
+
onClick: () => setLightboxOpen(true),
|
|
2058
|
+
title: t("fullscreen"),
|
|
2059
|
+
disabled: selected === null,
|
|
2060
|
+
children: (0, react_jsx_runtime.jsx)(Expand, { size: 16 })
|
|
2061
|
+
})
|
|
2062
|
+
]
|
|
2063
|
+
})]
|
|
2064
|
+
}),
|
|
2065
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
2066
|
+
className: `dsh-ig-canvas ${isDragging ? "is-dragging" : ""}`,
|
|
2067
|
+
ref: canvasRef,
|
|
2068
|
+
onMouseDown: handleCanvasMouseDown,
|
|
2069
|
+
onDoubleClick: resetFit,
|
|
2070
|
+
children: isGenerating ? (0, react_jsx_runtime.jsxs)("div", {
|
|
2071
|
+
className: "dsh-ig-generating-state",
|
|
2072
|
+
children: [
|
|
2073
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
2074
|
+
className: "dsh-ig-generation-orbit",
|
|
2075
|
+
children: (0, react_jsx_runtime.jsx)(Sparkles, { size: 28 })
|
|
2076
|
+
}),
|
|
2077
|
+
(0, react_jsx_runtime.jsx)("strong", { children: count > 1 ? t("generatingCount", { count: String(count) }) : t("generating") }),
|
|
2078
|
+
(0, react_jsx_runtime.jsxs)("span", { children: [
|
|
2079
|
+
activeProfile?.label,
|
|
2080
|
+
" · ",
|
|
2081
|
+
model
|
|
2082
|
+
] })
|
|
2083
|
+
]
|
|
2084
|
+
}) : currentBatch && currentBatch.length > 1 ? (0, react_jsx_runtime.jsx)("div", {
|
|
2085
|
+
className: "dsh-ig-canvas-cluster",
|
|
2086
|
+
"data-count": currentBatch.length,
|
|
2087
|
+
style: {
|
|
2088
|
+
transform: `translate(${offset.x}px, ${offset.y}px) scale(${zoom / 100})`,
|
|
2089
|
+
transformOrigin: "center center",
|
|
2090
|
+
transition: isDragging ? "none" : "transform 0.08s ease-out"
|
|
2091
|
+
},
|
|
2092
|
+
children: currentBatch.map((batchItem, index) => (0, react_jsx_runtime.jsx)(BatchCanvasItem, {
|
|
2093
|
+
item: batchItem,
|
|
2094
|
+
index,
|
|
2095
|
+
isSelected: selected?.id === batchItem.id,
|
|
2096
|
+
onSelect: () => {
|
|
2097
|
+
if (!hasDraggedRef.current) setSelected(batchItem);
|
|
2098
|
+
}
|
|
2099
|
+
}, batchItem.id))
|
|
2100
|
+
}) : selected === null ? (0, react_jsx_runtime.jsxs)("div", {
|
|
2101
|
+
className: "dsh-ig-canvas-empty",
|
|
2102
|
+
children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 36 }), (0, react_jsx_runtime.jsx)("span", { children: t("selectHistory") })]
|
|
2103
|
+
}) : image.loading ? (0, react_jsx_runtime.jsxs)("div", {
|
|
2104
|
+
className: "dsh-ig-canvas-empty",
|
|
2105
|
+
children: [(0, react_jsx_runtime.jsx)(LoaderCircle, {
|
|
2106
|
+
className: "dsh-ig-spin",
|
|
2107
|
+
size: 28
|
|
2108
|
+
}), (0, react_jsx_runtime.jsx)("span", { children: t("loading") })]
|
|
2109
|
+
}) : image.url !== null ? (0, react_jsx_runtime.jsx)("img", {
|
|
2110
|
+
src: image.url,
|
|
2111
|
+
alt: selected.prompt,
|
|
2112
|
+
draggable: false,
|
|
2113
|
+
onClick: handleImageClick,
|
|
2114
|
+
style: {
|
|
2115
|
+
transform: `translate(${offset.x}px, ${offset.y}px) scale(${zoom / 100})`,
|
|
2116
|
+
transformOrigin: "center center",
|
|
2117
|
+
transition: isDragging ? "none" : "transform 0.08s ease-out"
|
|
2118
|
+
}
|
|
2119
|
+
}) : (0, react_jsx_runtime.jsxs)("div", {
|
|
2120
|
+
className: "dsh-ig-canvas-empty",
|
|
2121
|
+
children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 32 }), (0, react_jsx_runtime.jsx)("span", { children: t("imageLoadFailed") })]
|
|
2122
|
+
})
|
|
2123
|
+
}),
|
|
2124
|
+
selected !== null && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2125
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2126
|
+
className: "dsh-ig-result-strip",
|
|
2127
|
+
children: [(0, react_jsx_runtime.jsx)("span", { children: t("result") }), (0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
2128
|
+
type: "button",
|
|
2129
|
+
onClick: () => void continueEdit(),
|
|
2130
|
+
children: [(0, react_jsx_runtime.jsx)(PencilLine, { size: 15 }), t("continueEdit")]
|
|
2131
|
+
}), (0, react_jsx_runtime.jsxs)("button", {
|
|
2132
|
+
type: "button",
|
|
2133
|
+
onClick: () => {
|
|
2134
|
+
setMode("generate");
|
|
2135
|
+
setPanelTab("generate");
|
|
2136
|
+
setPrompt(selected.prompt);
|
|
2137
|
+
},
|
|
2138
|
+
children: [(0, react_jsx_runtime.jsx)(RefreshCw, { size: 15 }), t("regenerate")]
|
|
2139
|
+
})] })]
|
|
2140
|
+
}),
|
|
2141
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2142
|
+
className: "dsh-ig-result-actions",
|
|
2143
|
+
children: [(0, react_jsx_runtime.jsx)("p", { children: selected.prompt }), (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
2144
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
2145
|
+
type: "button",
|
|
2146
|
+
className: `dsh-ig-save-gallery-btn ${isSelectedInGallery ? "is-saved" : ""}`,
|
|
2147
|
+
onClick: () => void handleSaveToGallery(),
|
|
2148
|
+
title: isSelectedInGallery ? t("inGallery") : t("saveToGallery"),
|
|
2149
|
+
disabled: isSelectedInGallery,
|
|
2150
|
+
children: [isSelectedInGallery ? (0, react_jsx_runtime.jsx)(Check, { size: 15 }) : (0, react_jsx_runtime.jsx)(BookmarkPlus, { size: 15 }), (0, react_jsx_runtime.jsx)("span", { children: isSelectedInGallery ? t("inGallery") : t("saveToGallery") })]
|
|
2151
|
+
}),
|
|
2152
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
2153
|
+
type: "button",
|
|
2154
|
+
onClick: () => void toggleFavorite(),
|
|
2155
|
+
title: selected.isFavorite ? t("favorited") : t("favorite"),
|
|
2156
|
+
children: [(0, react_jsx_runtime.jsx)(Heart, {
|
|
2157
|
+
size: 15,
|
|
2158
|
+
fill: selected.isFavorite ? "#ef4444" : "none",
|
|
2159
|
+
color: selected.isFavorite ? "#ef4444" : "currentColor"
|
|
2160
|
+
}), (0, react_jsx_runtime.jsx)("span", { children: selected.isFavorite ? t("favorited") : t("favorite") })]
|
|
2161
|
+
}),
|
|
2162
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
2163
|
+
type: "button",
|
|
2164
|
+
onClick: () => void copySelected(),
|
|
2165
|
+
children: [(0, react_jsx_runtime.jsx)(Clipboard, { size: 15 }), (0, react_jsx_runtime.jsx)("span", { children: t("copy") })]
|
|
2166
|
+
}),
|
|
2167
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
2168
|
+
type: "button",
|
|
2169
|
+
onClick: downloadSelected,
|
|
2170
|
+
children: [(0, react_jsx_runtime.jsx)(Download, { size: 15 }), (0, react_jsx_runtime.jsx)("span", { children: t("download") })]
|
|
2171
|
+
}),
|
|
2172
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
2173
|
+
type: "button",
|
|
2174
|
+
onClick: () => setShowDeleteModal(true),
|
|
2175
|
+
children: [(0, react_jsx_runtime.jsx)(Trash2, { size: 15 }), (0, react_jsx_runtime.jsx)("span", { children: t("remove") })]
|
|
2176
|
+
})
|
|
2177
|
+
] })]
|
|
2178
|
+
}),
|
|
2179
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2180
|
+
className: "dsh-ig-result-meta",
|
|
2181
|
+
children: [
|
|
2182
|
+
(0, react_jsx_runtime.jsx)("span", { children: formatRelativeTime$1(selected.createdAt, lang) }),
|
|
2183
|
+
(0, react_jsx_runtime.jsx)("span", { children: selected.provider }),
|
|
2184
|
+
(0, react_jsx_runtime.jsx)("span", { children: selected.model }),
|
|
2185
|
+
(0, react_jsx_runtime.jsx)("span", { children: selected.attachment.width && selected.attachment.height ? `${selected.attachment.width} × ${selected.attachment.height}` : "—" })
|
|
2186
|
+
]
|
|
2187
|
+
})
|
|
2188
|
+
] })
|
|
2189
|
+
]
|
|
2190
|
+
}),
|
|
2191
|
+
(0, react_jsx_runtime.jsxs)("aside", {
|
|
2192
|
+
className: "dsh-ig-generate-panel",
|
|
2193
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
2194
|
+
className: "dsh-ig-panel-tabs",
|
|
2195
|
+
children: [(0, react_jsx_runtime.jsx)("button", {
|
|
2196
|
+
type: "button",
|
|
2197
|
+
className: panelTab === "generate" ? "is-active" : "",
|
|
2198
|
+
onClick: () => setPanelTab("generate"),
|
|
2199
|
+
children: t("generate")
|
|
2200
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
2201
|
+
type: "button",
|
|
2202
|
+
className: panelTab === "details" ? "is-active" : "",
|
|
2203
|
+
onClick: () => setPanelTab("details"),
|
|
2204
|
+
children: t("details")
|
|
2205
|
+
})]
|
|
2206
|
+
}), panelTab === "details" ? (0, react_jsx_runtime.jsx)(DetailsPanel, {
|
|
2207
|
+
item: selected,
|
|
2208
|
+
t
|
|
2209
|
+
}) : (0, react_jsx_runtime.jsxs)("div", {
|
|
2210
|
+
className: "dsh-ig-generator-form",
|
|
2211
|
+
children: [
|
|
2212
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2213
|
+
className: "dsh-ig-mode-switch",
|
|
2214
|
+
children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
2215
|
+
type: "button",
|
|
2216
|
+
className: mode === "generate" ? "is-active" : "",
|
|
2217
|
+
onClick: () => setMode("generate"),
|
|
2218
|
+
children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 15 }), t("generate")]
|
|
2219
|
+
}), (0, react_jsx_runtime.jsxs)("button", {
|
|
2220
|
+
type: "button",
|
|
2221
|
+
className: mode === "edit" ? "is-active" : "",
|
|
2222
|
+
onClick: () => setMode("edit"),
|
|
2223
|
+
children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 15 }), t("edit")]
|
|
2224
|
+
})]
|
|
2225
|
+
}),
|
|
2226
|
+
mode === "edit" && (0, react_jsx_runtime.jsxs)("div", {
|
|
2227
|
+
className: "dsh-ig-field",
|
|
2228
|
+
children: [
|
|
2229
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2230
|
+
className: "dsh-ig-field-label",
|
|
2231
|
+
children: [(0, react_jsx_runtime.jsxs)("label", { children: [
|
|
2232
|
+
t("referencesCount", {
|
|
2233
|
+
current: String(references.length),
|
|
2234
|
+
max: String(maxReferences)
|
|
2235
|
+
}),
|
|
2236
|
+
" ",
|
|
2237
|
+
(0, react_jsx_runtime.jsx)("b", { children: "*" })
|
|
2238
|
+
] }), references.length > 0 && (0, react_jsx_runtime.jsx)("button", {
|
|
2239
|
+
type: "button",
|
|
2240
|
+
onClick: clearAllReferences,
|
|
2241
|
+
children: t("clear")
|
|
2242
|
+
})]
|
|
2243
|
+
}),
|
|
2244
|
+
references.length === 0 ? (0, react_jsx_runtime.jsxs)("button", {
|
|
2245
|
+
type: "button",
|
|
2246
|
+
className: `dsh-ig-upload ${dragging ? "is-dragging" : ""}`,
|
|
2247
|
+
onClick: () => fileInputRef.current?.click(),
|
|
2248
|
+
onDragOver: (event) => {
|
|
2249
|
+
event.preventDefault();
|
|
2250
|
+
setDragging(true);
|
|
2251
|
+
},
|
|
2252
|
+
onDragLeave: () => setDragging(false),
|
|
2253
|
+
onDrop: (event) => {
|
|
2254
|
+
event.preventDefault();
|
|
2255
|
+
setDragging(false);
|
|
2256
|
+
addReferenceFiles(event.dataTransfer.files);
|
|
2257
|
+
},
|
|
2258
|
+
children: [
|
|
2259
|
+
(0, react_jsx_runtime.jsx)(Upload, { size: 20 }),
|
|
2260
|
+
(0, react_jsx_runtime.jsx)("strong", { children: t("upload") }),
|
|
2261
|
+
(0, react_jsx_runtime.jsx)("small", { children: t("uploadHint") })
|
|
2262
|
+
]
|
|
2263
|
+
}) : (0, react_jsx_runtime.jsxs)("div", {
|
|
2264
|
+
className: "dsh-ig-reference-grid",
|
|
2265
|
+
children: [references.map((item, index) => (0, react_jsx_runtime.jsxs)("div", {
|
|
2266
|
+
className: "dsh-ig-reference-card",
|
|
2267
|
+
children: [(0, react_jsx_runtime.jsx)("img", {
|
|
2268
|
+
src: item.previewUrl,
|
|
2269
|
+
alt: `Ref ${index + 1}`
|
|
2270
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
2271
|
+
type: "button",
|
|
2272
|
+
title: t("closeReference"),
|
|
2273
|
+
onClick: () => removeReference(item.id),
|
|
2274
|
+
children: (0, react_jsx_runtime.jsx)(X, { size: 12 })
|
|
2275
|
+
})]
|
|
2276
|
+
}, item.id)), references.length < maxReferences && (0, react_jsx_runtime.jsxs)("button", {
|
|
2277
|
+
type: "button",
|
|
2278
|
+
className: "dsh-ig-reference-add",
|
|
2279
|
+
onClick: () => fileInputRef.current?.click(),
|
|
2280
|
+
title: t("upload"),
|
|
2281
|
+
children: [(0, react_jsx_runtime.jsx)(Upload, { size: 16 }), (0, react_jsx_runtime.jsx)("span", { children: t("addMoreRef") })]
|
|
2282
|
+
})]
|
|
2283
|
+
}),
|
|
2284
|
+
(0, react_jsx_runtime.jsx)("input", {
|
|
2285
|
+
ref: fileInputRef,
|
|
2286
|
+
type: "file",
|
|
2287
|
+
multiple: true,
|
|
2288
|
+
accept: "image/png,image/jpeg,image/webp,image/gif",
|
|
2289
|
+
hidden: true,
|
|
2290
|
+
onChange: (event) => {
|
|
2291
|
+
addReferenceFiles(event.target.files);
|
|
2292
|
+
event.target.value = "";
|
|
2293
|
+
}
|
|
2294
|
+
})
|
|
2295
|
+
]
|
|
2296
|
+
}),
|
|
2297
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2298
|
+
className: "dsh-ig-field",
|
|
2299
|
+
children: [
|
|
2300
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2301
|
+
className: "dsh-ig-field-label",
|
|
2302
|
+
children: [(0, react_jsx_runtime.jsxs)("label", {
|
|
2303
|
+
htmlFor: "dsh-ig-prompt",
|
|
2304
|
+
children: [
|
|
2305
|
+
t("prompt"),
|
|
2306
|
+
" ",
|
|
2307
|
+
(0, react_jsx_runtime.jsx)("b", { children: "*" })
|
|
2308
|
+
]
|
|
2309
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
2310
|
+
type: "button",
|
|
2311
|
+
onClick: () => setPrompt(""),
|
|
2312
|
+
children: t("clear")
|
|
2313
|
+
})]
|
|
2314
|
+
}),
|
|
2315
|
+
(0, react_jsx_runtime.jsx)("textarea", {
|
|
2316
|
+
id: "dsh-ig-prompt",
|
|
2317
|
+
value: prompt,
|
|
2318
|
+
maxLength: 2e3,
|
|
2319
|
+
onChange: (event) => setPrompt(event.target.value),
|
|
2320
|
+
onKeyDown: (event) => {
|
|
2321
|
+
if (event.nativeEvent.isComposing) return;
|
|
2322
|
+
if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
|
|
2323
|
+
event.preventDefault();
|
|
2324
|
+
if (!isGenerating && config !== null && prompt.trim().length > 0) submit();
|
|
2325
|
+
}
|
|
2326
|
+
},
|
|
2327
|
+
placeholder: t("promptPlaceholder")
|
|
2328
|
+
}),
|
|
2329
|
+
(0, react_jsx_runtime.jsxs)("small", { children: [prompt.length, "/2000"] })
|
|
2330
|
+
]
|
|
2331
|
+
}),
|
|
2332
|
+
config === null ? configLoading ? (0, react_jsx_runtime.jsx)("div", {
|
|
2333
|
+
className: "dsh-ig-form-loading",
|
|
2334
|
+
children: (0, react_jsx_runtime.jsx)(LoaderCircle, {
|
|
2335
|
+
className: "dsh-ig-spin",
|
|
2336
|
+
size: 18
|
|
2337
|
+
})
|
|
2338
|
+
}) : (0, react_jsx_runtime.jsxs)("div", {
|
|
2339
|
+
className: "dsh-ig-config-error",
|
|
2340
|
+
children: [(0, react_jsx_runtime.jsx)("p", { children: configError ?? t("configLoadFailed") }), (0, react_jsx_runtime.jsxs)("button", {
|
|
2341
|
+
type: "button",
|
|
2342
|
+
onClick: () => void loadConfig(),
|
|
2343
|
+
children: [(0, react_jsx_runtime.jsx)(RefreshCw, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: t("retry") })]
|
|
2344
|
+
})]
|
|
2345
|
+
}) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2346
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2347
|
+
className: "dsh-ig-field-grid",
|
|
2348
|
+
children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
|
|
2349
|
+
label: t("provider"),
|
|
2350
|
+
value: provider,
|
|
2351
|
+
onChange: changeProvider,
|
|
2352
|
+
options: config.providers.map((item) => ({
|
|
2353
|
+
value: item.provider,
|
|
2354
|
+
label: `${item.label}${item.configured ? "" : ` · ${t("unconfigured")}`}`
|
|
2355
|
+
}))
|
|
2356
|
+
}), (0, react_jsx_runtime.jsx)(FieldSelect, {
|
|
2357
|
+
label: t("model"),
|
|
2358
|
+
value: model,
|
|
2359
|
+
onChange: setModel,
|
|
2360
|
+
options: activeProfile === void 0 ? [] : [{
|
|
2361
|
+
value: activeProfile.model,
|
|
2362
|
+
label: activeProfile.model
|
|
2363
|
+
}]
|
|
2364
|
+
})]
|
|
2365
|
+
}),
|
|
2366
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2367
|
+
className: "dsh-ig-field-grid",
|
|
2368
|
+
children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
|
|
2369
|
+
label: t("ratio"),
|
|
2370
|
+
value: ratio,
|
|
2371
|
+
onChange: setRatio,
|
|
2372
|
+
options: activeProfile?.ratioOptions ?? []
|
|
2373
|
+
}), (0, react_jsx_runtime.jsx)(FieldSelect, {
|
|
2374
|
+
label: t("quality"),
|
|
2375
|
+
value: quality,
|
|
2376
|
+
onChange: setQuality,
|
|
2377
|
+
options: activeProfile?.qualityOptions ?? []
|
|
2378
|
+
})]
|
|
2379
|
+
}),
|
|
2380
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2381
|
+
className: "dsh-ig-field",
|
|
2382
|
+
children: [(0, react_jsx_runtime.jsx)("label", { children: t("count") }), (0, react_jsx_runtime.jsx)("div", {
|
|
2383
|
+
className: "dsh-ig-count-row",
|
|
2384
|
+
children: [
|
|
2385
|
+
1,
|
|
2386
|
+
2,
|
|
2387
|
+
3,
|
|
2388
|
+
4
|
|
2389
|
+
].map((option) => (0, react_jsx_runtime.jsx)("button", {
|
|
2390
|
+
type: "button",
|
|
2391
|
+
className: `dsh-ig-count-pill ${count === option ? "is-active" : ""}`,
|
|
2392
|
+
onClick: () => setCount(option),
|
|
2393
|
+
children: t("countUnit", { n: String(option) })
|
|
2394
|
+
}, option))
|
|
2395
|
+
})]
|
|
2396
|
+
})
|
|
2397
|
+
] }),
|
|
2398
|
+
configuredCount === 0 && config !== null && (0, react_jsx_runtime.jsx)("div", {
|
|
2399
|
+
className: "dsh-ig-inline-note",
|
|
2400
|
+
children: t("noProvider")
|
|
2401
|
+
}),
|
|
2402
|
+
error !== null && (0, react_jsx_runtime.jsx)("div", {
|
|
2403
|
+
className: "dsh-ig-form-error",
|
|
2404
|
+
children: error
|
|
2405
|
+
}),
|
|
2406
|
+
isGenerating ? (0, react_jsx_runtime.jsxs)("button", {
|
|
2407
|
+
type: "button",
|
|
2408
|
+
className: "dsh-ig-generate-button is-cancel",
|
|
2409
|
+
onClick: cancelGeneration,
|
|
2410
|
+
children: [(0, react_jsx_runtime.jsx)(LoaderCircle, {
|
|
2411
|
+
className: "dsh-ig-spin",
|
|
2412
|
+
size: 17
|
|
2413
|
+
}), (0, react_jsx_runtime.jsx)("span", { children: t("cancelGenerate") })]
|
|
2414
|
+
}) : (0, react_jsx_runtime.jsxs)("button", {
|
|
2415
|
+
type: "button",
|
|
2416
|
+
className: "dsh-ig-generate-button",
|
|
2417
|
+
disabled: config === null,
|
|
2418
|
+
onClick: () => void submit(),
|
|
2419
|
+
children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 17 }), (0, react_jsx_runtime.jsxs)("span", { children: [t("start"), count > 1 ? ` (${count})` : ""] })]
|
|
2420
|
+
})
|
|
2421
|
+
]
|
|
2422
|
+
})]
|
|
2423
|
+
})
|
|
2424
|
+
]
|
|
2425
|
+
}),
|
|
2426
|
+
lightboxOpen && selected !== null && (0, react_jsx_runtime.jsxs)("div", {
|
|
2427
|
+
className: "dsh-ig-lightbox-backdrop",
|
|
2428
|
+
onClick: () => setLightboxOpen(false),
|
|
2429
|
+
children: [
|
|
2430
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2431
|
+
className: "dsh-ig-lightbox-topbar",
|
|
2432
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
2433
|
+
className: "dsh-ig-lightbox-meta",
|
|
2434
|
+
children: [
|
|
2435
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
2436
|
+
className: "dsh-ig-tag",
|
|
2437
|
+
children: selected.provider
|
|
2438
|
+
}),
|
|
2439
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
2440
|
+
className: "dsh-ig-tag dsh-ig-tag-model",
|
|
2441
|
+
children: selected.model
|
|
2442
|
+
}),
|
|
2443
|
+
currentBatch && currentBatch.length > 1 && (0, react_jsx_runtime.jsxs)("span", {
|
|
2444
|
+
className: "dsh-ig-tag",
|
|
2445
|
+
children: [
|
|
2446
|
+
currentBatch.findIndex((i) => i.id === selected.id) + 1,
|
|
2447
|
+
" / ",
|
|
2448
|
+
currentBatch.length
|
|
2449
|
+
]
|
|
2450
|
+
}),
|
|
2451
|
+
selected.attachment.width && selected.attachment.height ? (0, react_jsx_runtime.jsxs)("span", {
|
|
2452
|
+
className: "dsh-ig-tag",
|
|
2453
|
+
children: [
|
|
2454
|
+
selected.attachment.width,
|
|
2455
|
+
" × ",
|
|
2456
|
+
selected.attachment.height
|
|
2457
|
+
]
|
|
2458
|
+
}) : null
|
|
2459
|
+
]
|
|
2460
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
2461
|
+
type: "button",
|
|
2462
|
+
className: "dsh-ig-lightbox-close-btn",
|
|
2463
|
+
title: t("close"),
|
|
2464
|
+
onClick: () => setLightboxOpen(false),
|
|
2465
|
+
children: (0, react_jsx_runtime.jsx)(X, { size: 18 })
|
|
2466
|
+
})]
|
|
2467
|
+
}),
|
|
2468
|
+
currentBatch && currentBatch.length > 1 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("button", {
|
|
2469
|
+
type: "button",
|
|
2470
|
+
className: "dsh-ig-lightbox-nav is-prev",
|
|
2471
|
+
onClick: (e) => {
|
|
2472
|
+
e.stopPropagation();
|
|
2473
|
+
stepLightbox(-1);
|
|
2474
|
+
},
|
|
2475
|
+
title: "上一张",
|
|
2476
|
+
children: (0, react_jsx_runtime.jsx)(ChevronLeft, { size: 24 })
|
|
2477
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
2478
|
+
type: "button",
|
|
2479
|
+
className: "dsh-ig-lightbox-nav is-next",
|
|
2480
|
+
onClick: (e) => {
|
|
2481
|
+
e.stopPropagation();
|
|
2482
|
+
stepLightbox(1);
|
|
2483
|
+
},
|
|
2484
|
+
title: "下一张",
|
|
2485
|
+
children: (0, react_jsx_runtime.jsx)(ChevronRight, { size: 24 })
|
|
2486
|
+
})] }),
|
|
2487
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
2488
|
+
className: "dsh-ig-lightbox-img-wrap",
|
|
2489
|
+
onClick: (e) => e.stopPropagation(),
|
|
2490
|
+
children: image.url !== null ? (0, react_jsx_runtime.jsx)("img", {
|
|
2491
|
+
className: "dsh-ig-lightbox-img",
|
|
2492
|
+
src: image.url,
|
|
2493
|
+
alt: selected.prompt
|
|
2494
|
+
}) : (0, react_jsx_runtime.jsx)("div", {
|
|
2495
|
+
className: "dsh-ig-lightbox-loading",
|
|
2496
|
+
children: (0, react_jsx_runtime.jsx)("div", { className: "dsh-ig-lightbox-spinner" })
|
|
2497
|
+
})
|
|
2498
|
+
}),
|
|
2499
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2500
|
+
className: "dsh-ig-lightbox-bottombar",
|
|
2501
|
+
onClick: (e) => e.stopPropagation(),
|
|
2502
|
+
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
2503
|
+
className: "dsh-ig-lightbox-prompt-text",
|
|
2504
|
+
title: selected.prompt,
|
|
2505
|
+
children: selected.prompt
|
|
2506
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
2507
|
+
className: "dsh-ig-lightbox-actions",
|
|
2508
|
+
children: [
|
|
2509
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
2510
|
+
type: "button",
|
|
2511
|
+
className: "dsh-ig-lightbox-btn",
|
|
2512
|
+
title: t("copyPpt"),
|
|
2513
|
+
onClick: async () => {
|
|
2514
|
+
await navigator.clipboard.writeText(selected.prompt);
|
|
2515
|
+
flash(t("copiedPrompt"));
|
|
2516
|
+
},
|
|
2517
|
+
children: [(0, react_jsx_runtime.jsx)(FileText, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("copyPpt") })]
|
|
2518
|
+
}),
|
|
2519
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
2520
|
+
type: "button",
|
|
2521
|
+
className: "dsh-ig-lightbox-btn",
|
|
2522
|
+
title: t("copy"),
|
|
2523
|
+
onClick: async () => {
|
|
2524
|
+
if (!image.blob) return;
|
|
2525
|
+
const ok = await copyImageBlob(image.blob);
|
|
2526
|
+
flash(ok ? t("copiedImage") : t("copyFailed"));
|
|
2527
|
+
},
|
|
2528
|
+
children: [(0, react_jsx_runtime.jsx)(Copy, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("copy") })]
|
|
2529
|
+
}),
|
|
2530
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
2531
|
+
type: "button",
|
|
2532
|
+
className: "dsh-ig-lightbox-btn",
|
|
2533
|
+
title: t("download"),
|
|
2534
|
+
onClick: downloadSelected,
|
|
2535
|
+
children: [(0, react_jsx_runtime.jsx)(Download, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("download") })]
|
|
2536
|
+
}),
|
|
2537
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
2538
|
+
type: "button",
|
|
2539
|
+
className: `dsh-ig-lightbox-btn ${isSelectedInGallery ? "is-saved" : ""}`,
|
|
2540
|
+
title: isSelectedInGallery ? t("inGallery") : t("saveToGallery"),
|
|
2541
|
+
onClick: () => void handleSaveToGallery(),
|
|
2542
|
+
disabled: isSelectedInGallery,
|
|
2543
|
+
children: [isSelectedInGallery ? (0, react_jsx_runtime.jsx)(Check, { size: 14 }) : (0, react_jsx_runtime.jsx)(BookmarkPlus, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: isSelectedInGallery ? t("inGallery") : t("saveToGallery") })]
|
|
2544
|
+
}),
|
|
2545
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
2546
|
+
type: "button",
|
|
2547
|
+
className: "dsh-ig-lightbox-btn",
|
|
2548
|
+
title: selected.isFavorite ? t("favorited") : t("favorite"),
|
|
2549
|
+
onClick: () => void toggleFavorite(),
|
|
2550
|
+
children: [(0, react_jsx_runtime.jsx)(Heart, {
|
|
2551
|
+
size: 14,
|
|
2552
|
+
fill: selected.isFavorite ? "#ef4444" : "none",
|
|
2553
|
+
color: selected.isFavorite ? "#ef4444" : "currentColor"
|
|
2554
|
+
}), (0, react_jsx_runtime.jsx)("span", { children: selected.isFavorite ? t("favorited") : t("favorite") })]
|
|
2555
|
+
})
|
|
2556
|
+
]
|
|
2557
|
+
})]
|
|
2558
|
+
})
|
|
2559
|
+
]
|
|
2560
|
+
}),
|
|
2561
|
+
showDeleteModal && selected !== null && (0, react_jsx_runtime.jsx)("div", {
|
|
2562
|
+
className: "dsh-ig-workbench-modal-backdrop",
|
|
2563
|
+
onClick: () => setShowDeleteModal(false),
|
|
2564
|
+
children: (0, react_jsx_runtime.jsxs)("div", {
|
|
2565
|
+
className: "dsh-ig-workbench-modal-box",
|
|
2566
|
+
onClick: (e) => e.stopPropagation(),
|
|
2567
|
+
children: [
|
|
2568
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2569
|
+
className: "dsh-ig-workbench-modal-header",
|
|
2570
|
+
children: [(0, react_jsx_runtime.jsx)(TriangleAlert, {
|
|
2571
|
+
size: 20,
|
|
2572
|
+
color: "#dc2626"
|
|
2573
|
+
}), (0, react_jsx_runtime.jsx)("strong", { children: t("deleteModalTitle") })]
|
|
2574
|
+
}),
|
|
2575
|
+
(0, react_jsx_runtime.jsx)("p", {
|
|
2576
|
+
className: "dsh-ig-workbench-modal-desc",
|
|
2577
|
+
children: t("deleteModalDesc")
|
|
2578
|
+
}),
|
|
2579
|
+
Boolean(selected.savedTo) && (0, react_jsx_runtime.jsxs)("label", {
|
|
2580
|
+
className: "dsh-ig-workbench-modal-option",
|
|
2581
|
+
children: [(0, react_jsx_runtime.jsx)("input", {
|
|
2582
|
+
type: "checkbox",
|
|
2583
|
+
checked: deleteWorkspaceFiles,
|
|
2584
|
+
onChange: (e) => setDeleteWorkspaceFiles(e.target.checked)
|
|
2585
|
+
}), (0, react_jsx_runtime.jsx)("span", { children: t("deleteWorkspaceFilesLabel") })]
|
|
2586
|
+
}),
|
|
2587
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2588
|
+
className: "dsh-ig-workbench-modal-actions",
|
|
2589
|
+
children: [(0, react_jsx_runtime.jsx)("button", {
|
|
2590
|
+
type: "button",
|
|
2591
|
+
className: "dsh-ig-workbench-modal-cancel",
|
|
2592
|
+
onClick: () => setShowDeleteModal(false),
|
|
2593
|
+
children: t("cancel")
|
|
2594
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
2595
|
+
type: "button",
|
|
2596
|
+
className: "dsh-ig-workbench-modal-danger",
|
|
2597
|
+
onClick: () => void handleConfirmDelete(),
|
|
2598
|
+
children: t("confirm")
|
|
2599
|
+
})]
|
|
2600
|
+
})
|
|
2601
|
+
]
|
|
2602
|
+
})
|
|
2603
|
+
}),
|
|
2604
|
+
notice !== null && (0, react_jsx_runtime.jsx)("div", {
|
|
2605
|
+
className: "dsh-ig-workbench-toast",
|
|
2606
|
+
children: notice
|
|
2607
|
+
})
|
|
2608
|
+
]
|
|
2609
|
+
});
|
|
2610
|
+
};
|
|
2611
|
+
const BatchCanvasItem = ({ item, index, isSelected, onSelect }) => {
|
|
2612
|
+
const image = useAttachmentImage(item.attachment, true);
|
|
2613
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
2614
|
+
className: `dsh-ig-canvas-item ${isSelected ? "is-selected" : ""}`,
|
|
2615
|
+
onClick: onSelect,
|
|
2616
|
+
children: [(0, react_jsx_runtime.jsxs)("span", {
|
|
2617
|
+
className: "dsh-ig-canvas-badge",
|
|
2618
|
+
children: ["#", index + 1]
|
|
2619
|
+
}), image.url !== null ? (0, react_jsx_runtime.jsx)("img", {
|
|
2620
|
+
src: image.url,
|
|
2621
|
+
alt: item.prompt,
|
|
2622
|
+
draggable: false
|
|
2623
|
+
}) : image.loading ? (0, react_jsx_runtime.jsx)("div", {
|
|
2624
|
+
className: "dsh-ig-canvas-item-loading",
|
|
2625
|
+
children: (0, react_jsx_runtime.jsx)(LoaderCircle, {
|
|
2626
|
+
className: "dsh-ig-spin",
|
|
2627
|
+
size: 24
|
|
2628
|
+
})
|
|
2629
|
+
}) : (0, react_jsx_runtime.jsx)("div", {
|
|
2630
|
+
className: "dsh-ig-canvas-item-loading",
|
|
2631
|
+
children: (0, react_jsx_runtime.jsx)(ImagePlus, { size: 24 })
|
|
2632
|
+
})]
|
|
2633
|
+
});
|
|
2634
|
+
};
|
|
2635
|
+
const FieldSelect = ({ label, value, options, onChange }) => (0, react_jsx_runtime.jsxs)("label", {
|
|
2636
|
+
className: "dsh-ig-field-select",
|
|
2637
|
+
children: [(0, react_jsx_runtime.jsx)("span", { children: label }), (0, react_jsx_runtime.jsx)("select", {
|
|
2638
|
+
value,
|
|
2639
|
+
onChange: (event) => onChange(event.target.value),
|
|
2640
|
+
children: options.map((option) => (0, react_jsx_runtime.jsx)("option", {
|
|
2641
|
+
value: option.value,
|
|
2642
|
+
children: option.label
|
|
2643
|
+
}, option.value))
|
|
2644
|
+
})]
|
|
2645
|
+
});
|
|
2646
|
+
const DetailsPanel = ({ item, t }) => item === null ? (0, react_jsx_runtime.jsxs)("div", {
|
|
2647
|
+
className: "dsh-ig-details-empty",
|
|
2648
|
+
children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 28 }), (0, react_jsx_runtime.jsx)("span", { children: t("selectHistory") })]
|
|
2649
|
+
}) : (0, react_jsx_runtime.jsxs)("dl", {
|
|
2650
|
+
className: "dsh-ig-details",
|
|
2651
|
+
children: [
|
|
2652
|
+
(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("prompt") }), (0, react_jsx_runtime.jsx)("dd", { children: item.prompt })] }),
|
|
2653
|
+
(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("provider") }), (0, react_jsx_runtime.jsx)("dd", { children: item.provider })] }),
|
|
2654
|
+
(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("model") }), (0, react_jsx_runtime.jsx)("dd", { children: item.model })] }),
|
|
2655
|
+
(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("dimensions") }), (0, react_jsx_runtime.jsx)("dd", { children: item.attachment.width && item.attachment.height ? `${item.attachment.width} × ${item.attachment.height}` : "—" })] }),
|
|
2656
|
+
(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("output") }), (0, react_jsx_runtime.jsx)("dd", { children: item.output ?? "—" })] }),
|
|
2657
|
+
(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("created") }), (0, react_jsx_runtime.jsx)("dd", { children: new Date(item.createdAt).toLocaleString() })] })
|
|
2658
|
+
]
|
|
2659
|
+
});
|
|
2660
|
+
const RecentItem = ({ item, active, lang, onClick }) => {
|
|
2661
|
+
const [setRef, inView] = useInView();
|
|
2662
|
+
const image = useAttachmentImage(item.attachment, inView);
|
|
2663
|
+
return (0, react_jsx_runtime.jsxs)("button", {
|
|
2664
|
+
ref: setRef,
|
|
2665
|
+
type: "button",
|
|
2666
|
+
className: `dsh-ig-recent-item ${active ? "is-active" : ""}`,
|
|
2667
|
+
onClick,
|
|
2668
|
+
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
2669
|
+
className: "dsh-ig-recent-thumb",
|
|
2670
|
+
children: image.url !== null ? (0, react_jsx_runtime.jsx)("img", {
|
|
2671
|
+
src: image.url,
|
|
2672
|
+
alt: "",
|
|
2673
|
+
loading: "lazy"
|
|
2674
|
+
}) : (0, react_jsx_runtime.jsx)(ImagePlus, { size: 18 })
|
|
2675
|
+
}), (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
2676
|
+
(0, react_jsx_runtime.jsx)("strong", { children: item.prompt }),
|
|
2677
|
+
(0, react_jsx_runtime.jsx)("span", { children: formatRelativeTime$1(item.createdAt, lang) }),
|
|
2678
|
+
(0, react_jsx_runtime.jsxs)("small", { children: [
|
|
2679
|
+
item.provider,
|
|
2680
|
+
" · ",
|
|
2681
|
+
item.model
|
|
2682
|
+
] }),
|
|
2683
|
+
(0, react_jsx_runtime.jsx)("small", { children: item.attachment.width && item.attachment.height ? `${item.attachment.width} × ${item.attachment.height}` : "—" })
|
|
2684
|
+
] })]
|
|
2685
|
+
});
|
|
2686
|
+
};
|
|
2687
|
+
function useInView() {
|
|
2688
|
+
const [inView, setInView] = (0, react.useState)(false);
|
|
2689
|
+
const observerRef = (0, react.useRef)(null);
|
|
2690
|
+
(0, react.useEffect)(() => {
|
|
2691
|
+
return () => {
|
|
2692
|
+
observerRef.current?.disconnect();
|
|
2693
|
+
};
|
|
2694
|
+
}, []);
|
|
2695
|
+
return [(0, react.useCallback)((el) => {
|
|
2696
|
+
observerRef.current?.disconnect();
|
|
2697
|
+
if (!el || inView) return;
|
|
2698
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
2699
|
+
setInView(true);
|
|
2700
|
+
return;
|
|
2701
|
+
}
|
|
2702
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
2703
|
+
if (entry?.isIntersecting) {
|
|
2704
|
+
setInView(true);
|
|
2705
|
+
observer.disconnect();
|
|
2706
|
+
}
|
|
2707
|
+
}, { rootMargin: "120px" });
|
|
2708
|
+
observerRef.current = observer;
|
|
2709
|
+
observer.observe(el);
|
|
2710
|
+
}, [inView]), inView];
|
|
2711
|
+
}
|
|
2712
|
+
function useAttachmentImage(attachment, shouldLoad = true) {
|
|
2713
|
+
const [value, setValue] = (0, react.useState)({
|
|
2714
|
+
url: null,
|
|
2715
|
+
blob: null,
|
|
2716
|
+
loading: false
|
|
2717
|
+
});
|
|
2718
|
+
(0, react.useEffect)(() => {
|
|
2719
|
+
if (attachment === void 0 || !shouldLoad) {
|
|
2720
|
+
setValue({
|
|
2721
|
+
url: null,
|
|
2722
|
+
blob: null,
|
|
2723
|
+
loading: false
|
|
2724
|
+
});
|
|
2725
|
+
return;
|
|
2726
|
+
}
|
|
2727
|
+
let cancelled = false;
|
|
2728
|
+
let objectUrl = null;
|
|
2729
|
+
setValue({
|
|
2730
|
+
url: null,
|
|
2731
|
+
blob: null,
|
|
2732
|
+
loading: true
|
|
2733
|
+
});
|
|
2734
|
+
fetchAttachmentBlob(attachment).then((blob) => {
|
|
2735
|
+
if (cancelled) return;
|
|
2736
|
+
objectUrl = URL.createObjectURL(blob);
|
|
2737
|
+
setValue({
|
|
2738
|
+
url: objectUrl,
|
|
2739
|
+
blob,
|
|
2740
|
+
loading: false
|
|
2741
|
+
});
|
|
2742
|
+
}).catch(() => {
|
|
2743
|
+
if (!cancelled) setValue({
|
|
2744
|
+
url: null,
|
|
2745
|
+
blob: null,
|
|
2746
|
+
loading: false
|
|
2747
|
+
});
|
|
2748
|
+
});
|
|
2749
|
+
return () => {
|
|
2750
|
+
cancelled = true;
|
|
2751
|
+
if (objectUrl !== null) URL.revokeObjectURL(objectUrl);
|
|
2752
|
+
};
|
|
2753
|
+
}, [attachment?.attachmentId, shouldLoad]);
|
|
2754
|
+
return value;
|
|
2755
|
+
}
|
|
2756
|
+
function fileToBase64(file) {
|
|
2757
|
+
return new Promise((resolve, reject) => {
|
|
2758
|
+
const reader = new FileReader();
|
|
2759
|
+
reader.onload = () => {
|
|
2760
|
+
const result = reader.result;
|
|
2761
|
+
const commaIndex = result.indexOf(",");
|
|
2762
|
+
resolve(commaIndex >= 0 ? result.slice(commaIndex + 1) : result);
|
|
2763
|
+
};
|
|
2764
|
+
reader.onerror = () => reject(reader.error ?? /* @__PURE__ */ new Error("Failed to read image file"));
|
|
2765
|
+
reader.readAsDataURL(file);
|
|
2766
|
+
});
|
|
2767
|
+
}
|
|
2768
|
+
function extension(mediaType) {
|
|
2769
|
+
return mediaType === "image/jpeg" ? "jpg" : mediaType.split("/")[1] ?? "png";
|
|
2770
|
+
}
|
|
2771
|
+
function messageOf(error) {
|
|
2772
|
+
return error instanceof Error ? error.message : String(error);
|
|
2773
|
+
}
|
|
2774
|
+
//#endregion
|
|
2775
|
+
//#region lib/types/client/conversation-regenerate.js
|
|
2776
|
+
/** Build the workbench request that reproduces a conversation image with an edited prompt. */
|
|
2777
|
+
function conversationRegenerateRequest(image, prompt, remembered) {
|
|
2778
|
+
if (!CLOUD_IMAGE_PROVIDERS.includes(image.provider)) throw new Error("当前图片使用的 Provider 暂不支持重新生成");
|
|
2779
|
+
const provider = image.provider;
|
|
2780
|
+
const settings = remembered ?? outputSettings(provider, image.output);
|
|
2781
|
+
return {
|
|
2782
|
+
mode: "generate",
|
|
2783
|
+
provider,
|
|
2784
|
+
model: image.model,
|
|
2785
|
+
prompt: prompt.trim(),
|
|
2786
|
+
ratio: settings.ratio,
|
|
2787
|
+
quality: settings.quality
|
|
2788
|
+
};
|
|
2789
|
+
}
|
|
2790
|
+
function outputSettings(provider, output) {
|
|
2791
|
+
const normalized = (output ?? "").trim();
|
|
2792
|
+
if (provider === "google") {
|
|
2793
|
+
const [rawRatio, rawQuality] = normalized.split(",").map((value) => value.trim());
|
|
2794
|
+
return {
|
|
2795
|
+
ratio: isRatio(rawRatio) ? rawRatio : "1:1",
|
|
2796
|
+
quality: rawQuality === "1K" || rawQuality === "2K" || rawQuality === "4K" ? rawQuality : "1K"
|
|
2797
|
+
};
|
|
2798
|
+
}
|
|
2799
|
+
if (provider === "seedream") return {
|
|
2800
|
+
ratio: "auto",
|
|
2801
|
+
quality: normalized === "1K" || normalized === "4K" ? normalized : "2K"
|
|
2802
|
+
};
|
|
2803
|
+
if (provider === "openai") return {
|
|
2804
|
+
ratio: ratioFromSize(normalized, "x"),
|
|
2805
|
+
quality: "standard"
|
|
2806
|
+
};
|
|
2807
|
+
return {
|
|
2808
|
+
ratio: ratioFromSize(normalized, "*"),
|
|
2809
|
+
quality: "standard"
|
|
2810
|
+
};
|
|
2811
|
+
}
|
|
2812
|
+
function ratioFromSize(size, separator) {
|
|
2813
|
+
return (separator === "x" ? {
|
|
2814
|
+
"1024x1024": "1:1",
|
|
2815
|
+
"1536x1024": "3:2",
|
|
2816
|
+
"1024x1536": "2:3"
|
|
2817
|
+
} : {
|
|
2818
|
+
"1024*1024": "1:1",
|
|
2819
|
+
"1536*1024": "3:2",
|
|
2820
|
+
"1024*1536": "2:3",
|
|
2821
|
+
"1664*928": "16:9",
|
|
2822
|
+
"928*1664": "9:16"
|
|
2823
|
+
})[size] ?? "1:1";
|
|
2824
|
+
}
|
|
2825
|
+
function isRatio(value) {
|
|
2826
|
+
return value === "1:1" || value === "3:2" || value === "2:3" || value === "4:3" || value === "3:4" || value === "16:9" || value === "9:16";
|
|
2827
|
+
}
|
|
2828
|
+
//#endregion
|
|
2829
|
+
//#region lib/types/client/gallery-view.js
|
|
2830
|
+
/**
|
|
2831
|
+
* Native Workspace Gallery & Studio View Component for DSH `conversation.view` slot.
|
|
2832
|
+
* Fully i18n-reactive (Chinese & English) with modular tabs, multi-dimensional filters,
|
|
2833
|
+
* responsive image grid, and placeholder routes.
|
|
2834
|
+
*/
|
|
2835
|
+
const DICT$1 = {
|
|
2836
|
+
zh: {
|
|
2837
|
+
tabGallery: "图库",
|
|
2838
|
+
tabStudio: "工作台",
|
|
2839
|
+
tabFavorites: "收藏",
|
|
2840
|
+
tabCompare: "对比",
|
|
2841
|
+
tabTasks: "任务",
|
|
2842
|
+
filterAllProviders: "全部提供商",
|
|
2843
|
+
filterGoogle: "Google Gemini",
|
|
2844
|
+
filterOpenAI: "OpenAI / 中转站",
|
|
2845
|
+
filterSeedream: "字节 Seedream",
|
|
2846
|
+
filterDashScope: "阿里 DashScope",
|
|
2847
|
+
filterComfyUI: "本地 ComfyUI",
|
|
2848
|
+
filterAllModels: "全部模型",
|
|
2849
|
+
filterAllRatios: "全部比例",
|
|
2850
|
+
searchPlaceholder: "搜索 Prompt、标签…",
|
|
2851
|
+
filterCurrentWorkspace: "仅看此工作区",
|
|
2852
|
+
filterCurrentWorkspaceHint: "只展示属于当前工作区的生图记录与物理文件",
|
|
2853
|
+
sortNewest: "最新生成",
|
|
2854
|
+
sortOldest: "最早生成",
|
|
2855
|
+
totalCount: "共 {count} 张生成图片",
|
|
2856
|
+
emptyTitle: "暂无生图记录",
|
|
2857
|
+
emptyDesc: "在对话中让 Agent 生图后,生成的图片会自动收录到这里。",
|
|
794
2858
|
favEmptyTitle: "暂无收藏图片",
|
|
795
2859
|
favEmptyDesc: "在图库中点击卡片右下角的 ♡ 按钮,即可将喜爱的图片收录到这里。",
|
|
796
2860
|
noMatchTitle: "未找到匹配结果",
|
|
@@ -804,6 +2868,13 @@ window.__ModuleLoader__.load({
|
|
|
804
2868
|
download: "下载图片",
|
|
805
2869
|
copyImg: "复制图片",
|
|
806
2870
|
copyPpt: "复制 Prompt",
|
|
2871
|
+
regenerate: "重新生成",
|
|
2872
|
+
regenerateTitle: "重新生成图片",
|
|
2873
|
+
regenerateHint: "可按需修改 Prompt,基于当前模型和比例在画廊生成一张新图片。",
|
|
2874
|
+
confirmRegenerate: "开始生成",
|
|
2875
|
+
regenerating: "正在重新生成…",
|
|
2876
|
+
regenerateSuccess: "已生成新图片并收录到画廊",
|
|
2877
|
+
regenerateFailed: "重新生成失败",
|
|
807
2878
|
delete: "从画廊删除",
|
|
808
2879
|
confirmDelete: "确定要从画廊中删除这张图片吗?(不会影响原聊天记录)",
|
|
809
2880
|
deleted: "已从画廊删除",
|
|
@@ -858,6 +2929,8 @@ window.__ModuleLoader__.load({
|
|
|
858
2929
|
filterAllModels: "All Models",
|
|
859
2930
|
filterAllRatios: "All Ratios",
|
|
860
2931
|
searchPlaceholder: "Search prompt, tags…",
|
|
2932
|
+
filterCurrentWorkspace: "Current workspace only",
|
|
2933
|
+
filterCurrentWorkspaceHint: "Only show images belonging to the active workspace",
|
|
861
2934
|
sortNewest: "Newest first",
|
|
862
2935
|
sortOldest: "Oldest first",
|
|
863
2936
|
totalCount: "{count} images total",
|
|
@@ -876,6 +2949,13 @@ window.__ModuleLoader__.load({
|
|
|
876
2949
|
download: "Download",
|
|
877
2950
|
copyImg: "Copy Image",
|
|
878
2951
|
copyPpt: "Copy Prompt",
|
|
2952
|
+
regenerate: "Regenerate",
|
|
2953
|
+
regenerateTitle: "Regenerate Image",
|
|
2954
|
+
regenerateHint: "Edit prompt if needed. A new image will be generated and added to the gallery.",
|
|
2955
|
+
confirmRegenerate: "Generate",
|
|
2956
|
+
regenerating: "Regenerating…",
|
|
2957
|
+
regenerateSuccess: "New image generated and added to gallery",
|
|
2958
|
+
regenerateFailed: "Regeneration failed",
|
|
879
2959
|
delete: "Delete from gallery",
|
|
880
2960
|
confirmDelete: "Are you sure you want to remove this image from the gallery? (Chat history will not be affected)",
|
|
881
2961
|
deleted: "Deleted from gallery",
|
|
@@ -971,17 +3051,80 @@ window.__ModuleLoader__.load({
|
|
|
971
3051
|
if (item.imageSize) return item.imageSize;
|
|
972
3052
|
return "1:1";
|
|
973
3053
|
}
|
|
974
|
-
const GalleryViewTab = (
|
|
3054
|
+
const GalleryViewTab = (props) => {
|
|
3055
|
+
const { locale, sessionId, useSessions, useWorkspaces } = props;
|
|
975
3056
|
const [activeTab, setActiveTab] = (0, react.useState)("gallery");
|
|
976
3057
|
const [items, setItems] = (0, react.useState)([]);
|
|
977
3058
|
const [search, setSearch] = (0, react.useState)("");
|
|
978
3059
|
const [selectedProvider, setSelectedProvider] = (0, react.useState)("all");
|
|
979
3060
|
const [selectedRatio, setSelectedRatio] = (0, react.useState)("all");
|
|
980
3061
|
const [sortBy, setSortBy] = (0, react.useState)("newest");
|
|
3062
|
+
const [onlyCurrentWorkspace, setOnlyCurrentWorkspace] = (0, react.useState)(() => {
|
|
3063
|
+
try {
|
|
3064
|
+
const stored = localStorage.getItem("dsh-ig-only-current-workspace");
|
|
3065
|
+
return stored === null ? true : stored === "true";
|
|
3066
|
+
} catch {
|
|
3067
|
+
return true;
|
|
3068
|
+
}
|
|
3069
|
+
});
|
|
3070
|
+
const [serverWorkspaces, setServerWorkspaces] = (0, react.useState)([]);
|
|
3071
|
+
const [serverActiveRoot, setServerActiveRoot] = (0, react.useState)(null);
|
|
3072
|
+
(0, react.useEffect)(() => {
|
|
3073
|
+
try {
|
|
3074
|
+
localStorage.setItem("dsh-ig-only-current-workspace", String(onlyCurrentWorkspace));
|
|
3075
|
+
} catch {}
|
|
3076
|
+
}, [onlyCurrentWorkspace]);
|
|
3077
|
+
(0, react.useEffect)(() => {
|
|
3078
|
+
let mounted = true;
|
|
3079
|
+
fetch(STUDIO_ROUTE).then((res) => res.ok ? res.json() : null).then((data) => {
|
|
3080
|
+
if (!mounted || !data) return;
|
|
3081
|
+
if (Array.isArray(data.workspaces)) setServerWorkspaces(data.workspaces);
|
|
3082
|
+
if (typeof data.workspaceRoot === "string") setServerActiveRoot(data.workspaceRoot);
|
|
3083
|
+
}).catch(() => {});
|
|
3084
|
+
return () => {
|
|
3085
|
+
mounted = false;
|
|
3086
|
+
};
|
|
3087
|
+
}, []);
|
|
3088
|
+
const liveWorkspaces = useWorkspaces ? useWorkspaces((s) => s?.items) : null;
|
|
3089
|
+
const liveSessions = useSessions ? useSessions((s) => s) : null;
|
|
3090
|
+
const currentSessionId = sessionId || liveSessions?.current;
|
|
3091
|
+
const activeWorkspace = (0, react.useMemo)(() => {
|
|
3092
|
+
const allWs = Array.isArray(liveWorkspaces) && liveWorkspaces.length > 0 ? liveWorkspaces : serverWorkspaces;
|
|
3093
|
+
if (allWs.length > 0) {
|
|
3094
|
+
if (currentSessionId) {
|
|
3095
|
+
const matched = allWs.find((ws) => Array.isArray(ws.sessionIds) && ws.sessionIds.includes(currentSessionId));
|
|
3096
|
+
if (matched) return matched;
|
|
3097
|
+
}
|
|
3098
|
+
if (serverActiveRoot) {
|
|
3099
|
+
const normActive = serverActiveRoot.replace(/\\/g, "/").toLowerCase();
|
|
3100
|
+
const matched = allWs.find((ws) => ws.path && ws.path.replace(/\\/g, "/").toLowerCase() === normActive);
|
|
3101
|
+
if (matched) return matched;
|
|
3102
|
+
}
|
|
3103
|
+
return allWs[0];
|
|
3104
|
+
}
|
|
3105
|
+
if (serverActiveRoot) return {
|
|
3106
|
+
workspaceId: "default",
|
|
3107
|
+
path: serverActiveRoot,
|
|
3108
|
+
title: "Workspace",
|
|
3109
|
+
sessionIds: []
|
|
3110
|
+
};
|
|
3111
|
+
return null;
|
|
3112
|
+
}, [
|
|
3113
|
+
liveWorkspaces,
|
|
3114
|
+
serverWorkspaces,
|
|
3115
|
+
currentSessionId,
|
|
3116
|
+
serverActiveRoot
|
|
3117
|
+
]);
|
|
981
3118
|
const [previewItem, setPreviewItem] = (0, react.useState)(null);
|
|
982
3119
|
const [previewUrl, setPreviewUrl] = (0, react.useState)(null);
|
|
3120
|
+
const previewUrlRef = (0, react.useRef)(null);
|
|
3121
|
+
previewUrlRef.current = previewUrl;
|
|
983
3122
|
const [previewBlob, setPreviewBlob] = (0, react.useState)(null);
|
|
984
3123
|
const [toast, setToast] = (0, react.useState)(null);
|
|
3124
|
+
const [showRegenerateModal, setShowRegenerateModal] = (0, react.useState)(false);
|
|
3125
|
+
const [regeneratePrompt, setRegeneratePrompt] = (0, react.useState)("");
|
|
3126
|
+
const [isRegenerating, setIsRegenerating] = (0, react.useState)(false);
|
|
3127
|
+
const regenerateControllerRef = (0, react.useRef)(null);
|
|
985
3128
|
const [isManageMode, setIsManageMode] = (0, react.useState)(false);
|
|
986
3129
|
const [selectedIds, setSelectedIds] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
987
3130
|
const lastSelectedIndexRef = (0, react.useRef)(null);
|
|
@@ -996,7 +3139,8 @@ window.__ModuleLoader__.load({
|
|
|
996
3139
|
search,
|
|
997
3140
|
selectedProvider,
|
|
998
3141
|
selectedRatio,
|
|
999
|
-
sortBy
|
|
3142
|
+
sortBy,
|
|
3143
|
+
onlyCurrentWorkspace
|
|
1000
3144
|
]);
|
|
1001
3145
|
const [lang, setLang] = (0, react.useState)(() => {
|
|
1002
3146
|
return (locale?.getSnapshot?.()?.active)?.startsWith("en") ? "en" : "zh";
|
|
@@ -1062,6 +3206,9 @@ window.__ModuleLoader__.load({
|
|
|
1062
3206
|
const matchTags = item.tags?.some((t) => t.toLowerCase().includes(q));
|
|
1063
3207
|
if (!matchPrompt && !matchModel && !matchProvider && !matchTags) return false;
|
|
1064
3208
|
}
|
|
3209
|
+
if (onlyCurrentWorkspace && activeWorkspace) {
|
|
3210
|
+
if (!isItemInWorkspace(item, activeWorkspace)) return false;
|
|
3211
|
+
}
|
|
1065
3212
|
return true;
|
|
1066
3213
|
}).sort((a, b) => {
|
|
1067
3214
|
if (sortBy === "oldest") return a.createdAt - b.createdAt;
|
|
@@ -1073,8 +3220,14 @@ window.__ModuleLoader__.load({
|
|
|
1073
3220
|
selectedProvider,
|
|
1074
3221
|
selectedRatio,
|
|
1075
3222
|
search,
|
|
1076
|
-
sortBy
|
|
3223
|
+
sortBy,
|
|
3224
|
+
onlyCurrentWorkspace,
|
|
3225
|
+
activeWorkspace
|
|
1077
3226
|
]);
|
|
3227
|
+
const workspaceItemsCount = (0, react.useMemo)(() => {
|
|
3228
|
+
if (!activeWorkspace) return items.length;
|
|
3229
|
+
return items.filter((item) => isItemInWorkspace(item, activeWorkspace)).length;
|
|
3230
|
+
}, [items, activeWorkspace]);
|
|
1078
3231
|
const currentPreviewIndex = (0, react.useMemo)(() => {
|
|
1079
3232
|
if (!previewItem) return -1;
|
|
1080
3233
|
return filteredItems.findIndex((i) => i.id === previewItem.id);
|
|
@@ -1086,11 +3239,15 @@ window.__ModuleLoader__.load({
|
|
|
1086
3239
|
abortControllerRef.current.abort();
|
|
1087
3240
|
abortControllerRef.current = null;
|
|
1088
3241
|
}
|
|
1089
|
-
if (
|
|
3242
|
+
if (previewUrlRef.current) {
|
|
3243
|
+
URL.revokeObjectURL(previewUrlRef.current);
|
|
3244
|
+
previewUrlRef.current = null;
|
|
3245
|
+
}
|
|
1090
3246
|
setPreviewItem(item);
|
|
1091
3247
|
if (blob) {
|
|
1092
3248
|
blobCache.set(item.id, blob);
|
|
1093
3249
|
const url = URL.createObjectURL(blob);
|
|
3250
|
+
previewUrlRef.current = url;
|
|
1094
3251
|
setPreviewBlob(blob);
|
|
1095
3252
|
setPreviewUrl(url);
|
|
1096
3253
|
setPreviewLoading(false);
|
|
@@ -1099,6 +3256,7 @@ window.__ModuleLoader__.load({
|
|
|
1099
3256
|
const cached = blobCache.get(item.id);
|
|
1100
3257
|
if (cached) {
|
|
1101
3258
|
const url = URL.createObjectURL(cached);
|
|
3259
|
+
previewUrlRef.current = url;
|
|
1102
3260
|
setPreviewBlob(cached);
|
|
1103
3261
|
setPreviewUrl(url);
|
|
1104
3262
|
setPreviewLoading(false);
|
|
@@ -1109,18 +3267,11 @@ window.__ModuleLoader__.load({
|
|
|
1109
3267
|
setPreviewUrl(null);
|
|
1110
3268
|
const controller = new AbortController();
|
|
1111
3269
|
abortControllerRef.current = controller;
|
|
1112
|
-
|
|
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) => {
|
|
3270
|
+
fetchAttachmentBlob(item.attachment).then((fetchedBlob) => {
|
|
1121
3271
|
if (controller.signal.aborted) return;
|
|
1122
3272
|
blobCache.set(item.id, fetchedBlob);
|
|
1123
3273
|
const url = URL.createObjectURL(fetchedBlob);
|
|
3274
|
+
previewUrlRef.current = url;
|
|
1124
3275
|
setPreviewBlob(fetchedBlob);
|
|
1125
3276
|
setPreviewUrl(url);
|
|
1126
3277
|
setPreviewLoading(false);
|
|
@@ -1135,7 +3286,16 @@ window.__ModuleLoader__.load({
|
|
|
1135
3286
|
abortControllerRef.current.abort();
|
|
1136
3287
|
abortControllerRef.current = null;
|
|
1137
3288
|
}
|
|
1138
|
-
if (
|
|
3289
|
+
if (regenerateControllerRef.current) {
|
|
3290
|
+
regenerateControllerRef.current.abort();
|
|
3291
|
+
regenerateControllerRef.current = null;
|
|
3292
|
+
setIsRegenerating(false);
|
|
3293
|
+
}
|
|
3294
|
+
setShowRegenerateModal(false);
|
|
3295
|
+
if (previewUrlRef.current) {
|
|
3296
|
+
URL.revokeObjectURL(previewUrlRef.current);
|
|
3297
|
+
previewUrlRef.current = null;
|
|
3298
|
+
}
|
|
1139
3299
|
setPreviewItem(null);
|
|
1140
3300
|
setPreviewUrl(null);
|
|
1141
3301
|
setPreviewBlob(null);
|
|
@@ -1153,15 +3313,86 @@ window.__ModuleLoader__.load({
|
|
|
1153
3313
|
if (nextItem) openPreviewItem(nextItem);
|
|
1154
3314
|
}
|
|
1155
3315
|
};
|
|
3316
|
+
const handleOpenRegenerate = () => {
|
|
3317
|
+
if (!previewItem || isRegenerating) return;
|
|
3318
|
+
setRegeneratePrompt(previewItem.prompt);
|
|
3319
|
+
setShowRegenerateModal(true);
|
|
3320
|
+
};
|
|
3321
|
+
const handleCancelRegeneratePrompt = () => {
|
|
3322
|
+
setShowRegenerateModal(false);
|
|
3323
|
+
};
|
|
3324
|
+
const handleAbortRegenerating = () => {
|
|
3325
|
+
regenerateControllerRef.current?.abort();
|
|
3326
|
+
regenerateControllerRef.current = null;
|
|
3327
|
+
setIsRegenerating(false);
|
|
3328
|
+
showToast(t("cancel"));
|
|
3329
|
+
};
|
|
3330
|
+
const handleConfirmRegenerate = async () => {
|
|
3331
|
+
if (!previewItem || isRegenerating || regeneratePrompt.trim().length === 0) return;
|
|
3332
|
+
setShowRegenerateModal(false);
|
|
3333
|
+
setIsRegenerating(true);
|
|
3334
|
+
const controller = new AbortController();
|
|
3335
|
+
regenerateControllerRef.current = controller;
|
|
3336
|
+
try {
|
|
3337
|
+
const request = conversationRegenerateRequest(previewItem, regeneratePrompt, previewItem.aspectRatio ? {
|
|
3338
|
+
ratio: previewItem.aspectRatio,
|
|
3339
|
+
quality: previewItem.imageSize || "standard"
|
|
3340
|
+
} : void 0);
|
|
3341
|
+
if (activeWorkspace?.path) request.workspaceRoot = activeWorkspace.path;
|
|
3342
|
+
const response = await fetch(STUDIO_ROUTE, {
|
|
3343
|
+
method: "POST",
|
|
3344
|
+
credentials: "same-origin",
|
|
3345
|
+
signal: controller.signal,
|
|
3346
|
+
headers: { "content-type": "application/json" },
|
|
3347
|
+
body: JSON.stringify(request)
|
|
3348
|
+
});
|
|
3349
|
+
const payload = await response.json().catch(() => null);
|
|
3350
|
+
if (!response.ok || payload === null || !("attachment" in payload)) throw new Error(payload && "error" in payload && payload.error ? payload.error : t("regenerateFailed"));
|
|
3351
|
+
const newItem = {
|
|
3352
|
+
id: String(payload.attachment.attachmentId),
|
|
3353
|
+
attachment: payload.attachment,
|
|
3354
|
+
prompt: payload.prompt,
|
|
3355
|
+
provider: payload.provider,
|
|
3356
|
+
model: payload.model,
|
|
3357
|
+
createdAt: payload.createdAt,
|
|
3358
|
+
aspectRatio: request.ratio,
|
|
3359
|
+
imageSize: request.quality,
|
|
3360
|
+
output: payload.output,
|
|
3361
|
+
...payload.savedTo ? { savedTo: payload.savedTo } : {},
|
|
3362
|
+
...activeWorkspace?.path ? { workspacePath: activeWorkspace.path } : {},
|
|
3363
|
+
...activeWorkspace?.workspaceId ? { workspaceId: activeWorkspace.workspaceId } : {},
|
|
3364
|
+
...currentSessionId ? { sessionId: currentSessionId } : {}
|
|
3365
|
+
};
|
|
3366
|
+
await saveGalleryItem(newItem);
|
|
3367
|
+
if (activeTab !== "gallery") setActiveTab("gallery");
|
|
3368
|
+
if (search.trim().length > 0) setSearch("");
|
|
3369
|
+
openPreviewItem(newItem);
|
|
3370
|
+
showToast(t("regenerateSuccess"));
|
|
3371
|
+
} catch (err) {
|
|
3372
|
+
if (controller.signal.aborted) return;
|
|
3373
|
+
showToast(err instanceof Error ? err.message : String(err));
|
|
3374
|
+
} finally {
|
|
3375
|
+
setIsRegenerating(false);
|
|
3376
|
+
regenerateControllerRef.current = null;
|
|
3377
|
+
}
|
|
3378
|
+
};
|
|
1156
3379
|
(0, react.useEffect)(() => {
|
|
1157
3380
|
return () => {
|
|
1158
3381
|
if (abortControllerRef.current) abortControllerRef.current.abort();
|
|
1159
|
-
if (
|
|
3382
|
+
if (regenerateControllerRef.current) regenerateControllerRef.current.abort();
|
|
3383
|
+
if (previewUrlRef.current) URL.revokeObjectURL(previewUrlRef.current);
|
|
1160
3384
|
};
|
|
1161
|
-
}, [
|
|
3385
|
+
}, []);
|
|
1162
3386
|
(0, react.useEffect)(() => {
|
|
1163
3387
|
const onKeyDown = (e) => {
|
|
1164
3388
|
if (["INPUT", "TEXTAREA"].includes(e.target?.tagName)) return;
|
|
3389
|
+
if (showRegenerateModal) {
|
|
3390
|
+
if (e.key === "Escape") {
|
|
3391
|
+
e.preventDefault();
|
|
3392
|
+
setShowRegenerateModal(false);
|
|
3393
|
+
}
|
|
3394
|
+
return;
|
|
3395
|
+
}
|
|
1165
3396
|
if (showDeleteModal) {
|
|
1166
3397
|
if (e.key === "Escape" && !isDeleting) {
|
|
1167
3398
|
e.preventDefault();
|
|
@@ -1184,6 +3415,7 @@ window.__ModuleLoader__.load({
|
|
|
1184
3415
|
window.addEventListener("keydown", onKeyDown);
|
|
1185
3416
|
return () => window.removeEventListener("keydown", onKeyDown);
|
|
1186
3417
|
}, [
|
|
3418
|
+
showRegenerateModal,
|
|
1187
3419
|
showDeleteModal,
|
|
1188
3420
|
isDeleting,
|
|
1189
3421
|
isManageMode,
|
|
@@ -1263,34 +3495,41 @@ window.__ModuleLoader__.load({
|
|
|
1263
3495
|
setSelectedIds(/* @__PURE__ */ new Set([item.id]));
|
|
1264
3496
|
setShowDeleteModal(true);
|
|
1265
3497
|
};
|
|
3498
|
+
const hasWorkspaceFilesToDelete = (0, react.useMemo)(() => items.some((i) => selectedIds.has(i.id) && typeof i.savedTo === "string" && i.savedTo.trim().length > 0), [items, selectedIds]);
|
|
1266
3499
|
const handleConfirmBatchDelete = async () => {
|
|
1267
3500
|
if (selectedIds.size === 0 || isDeleting) return;
|
|
1268
3501
|
setIsDeleting(true);
|
|
1269
3502
|
const idsToDelete = Array.from(selectedIds);
|
|
1270
3503
|
const itemsToDelete = items.filter((i) => selectedIds.has(i.id));
|
|
1271
3504
|
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
3505
|
let deletedFiles = 0;
|
|
1274
|
-
|
|
1275
|
-
if (deleteWorkspaceFiles && (pathsToDelete.length > 0 || fallbackAttachmentIds.length > 0)) try {
|
|
3506
|
+
if (deleteWorkspaceFiles && pathsToDelete.length > 0) try {
|
|
1276
3507
|
const res = await fetch(DELETE_ROUTE, {
|
|
1277
3508
|
method: "POST",
|
|
1278
3509
|
headers: { "content-type": "application/json" },
|
|
1279
|
-
body: JSON.stringify({
|
|
1280
|
-
paths: pathsToDelete,
|
|
1281
|
-
attachmentIds: fallbackAttachmentIds
|
|
1282
|
-
})
|
|
3510
|
+
body: JSON.stringify({ paths: pathsToDelete })
|
|
1283
3511
|
});
|
|
1284
|
-
if (res.ok) {
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
3512
|
+
if (!res.ok) {
|
|
3513
|
+
setIsDeleting(false);
|
|
3514
|
+
showToast(`工作区文件删除失败 (${res.status})`);
|
|
3515
|
+
return;
|
|
1288
3516
|
}
|
|
3517
|
+
const data = await res.json().catch(() => null);
|
|
3518
|
+
if (!data || data.ok === false || Array.isArray(data.failedFiles) && data.failedFiles.length > 0) {
|
|
3519
|
+
setIsDeleting(false);
|
|
3520
|
+
const reason = data?.failedFiles?.[0]?.error || "工作区文件未能删除";
|
|
3521
|
+
showToast(`删除失败:${reason}`);
|
|
3522
|
+
return;
|
|
3523
|
+
}
|
|
3524
|
+
deletedFiles = data.deletedCount ?? 0;
|
|
1289
3525
|
} catch (err) {
|
|
1290
|
-
|
|
3526
|
+
setIsDeleting(false);
|
|
3527
|
+
showToast(err instanceof Error ? err.message : "工作区文件删除网络异常");
|
|
3528
|
+
return;
|
|
1291
3529
|
}
|
|
1292
3530
|
try {
|
|
1293
3531
|
await bulkDeleteGalleryItems(idsToDelete);
|
|
3532
|
+
for (const item of itemsToDelete) evictAttachmentCache(item.attachment.attachmentId);
|
|
1294
3533
|
} catch (err) {
|
|
1295
3534
|
setIsDeleting(false);
|
|
1296
3535
|
showToast(t("deleteFailedDatabase"));
|
|
@@ -1304,11 +3543,7 @@ window.__ModuleLoader__.load({
|
|
|
1304
3543
|
setSelectedIds(/* @__PURE__ */ new Set());
|
|
1305
3544
|
lastSelectedIndexRef.current = null;
|
|
1306
3545
|
}
|
|
1307
|
-
if (
|
|
1308
|
-
count: String(idsToDelete.length),
|
|
1309
|
-
files: String(failedFilesCount)
|
|
1310
|
-
}));
|
|
1311
|
-
else if (deletedFiles > 0) showToast(t("batchDeletedWithFilesToast", {
|
|
3546
|
+
if (deletedFiles > 0) showToast(t("batchDeletedWithFilesToast", {
|
|
1312
3547
|
count: String(idsToDelete.length),
|
|
1313
3548
|
files: String(deletedFiles)
|
|
1314
3549
|
}));
|
|
@@ -1316,6 +3551,7 @@ window.__ModuleLoader__.load({
|
|
|
1316
3551
|
};
|
|
1317
3552
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1318
3553
|
className: "dsh-ig-gallery-page",
|
|
3554
|
+
"data-conversation-composer-overlay": "",
|
|
1319
3555
|
children: [
|
|
1320
3556
|
(0, react_jsx_runtime.jsxs)("header", {
|
|
1321
3557
|
className: "dsh-ig-studio-tabs-bar",
|
|
@@ -1337,18 +3573,6 @@ window.__ModuleLoader__.load({
|
|
|
1337
3573
|
className: `dsh-ig-studio-tab-btn ${activeTab === "favorites" ? "is-active" : ""}`,
|
|
1338
3574
|
onClick: () => setActiveTab("favorites"),
|
|
1339
3575
|
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") })]
|
|
1352
3576
|
})
|
|
1353
3577
|
]
|
|
1354
3578
|
}),
|
|
@@ -1439,6 +3663,30 @@ window.__ModuleLoader__.load({
|
|
|
1439
3663
|
value: search,
|
|
1440
3664
|
onChange: (e) => setSearch(e.target.value)
|
|
1441
3665
|
})]
|
|
3666
|
+
}),
|
|
3667
|
+
(0, react_jsx_runtime.jsxs)("label", {
|
|
3668
|
+
className: "dsh-ig-workspace-filter-label",
|
|
3669
|
+
title: t("filterCurrentWorkspaceHint"),
|
|
3670
|
+
children: [
|
|
3671
|
+
(0, react_jsx_runtime.jsx)("input", {
|
|
3672
|
+
type: "checkbox",
|
|
3673
|
+
className: "dsh-ig-workspace-checkbox",
|
|
3674
|
+
checked: onlyCurrentWorkspace,
|
|
3675
|
+
onChange: (e) => setOnlyCurrentWorkspace(e.target.checked)
|
|
3676
|
+
}),
|
|
3677
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
3678
|
+
className: "dsh-ig-workspace-filter-text",
|
|
3679
|
+
children: t("filterCurrentWorkspace")
|
|
3680
|
+
}),
|
|
3681
|
+
(0, react_jsx_runtime.jsxs)("span", {
|
|
3682
|
+
className: "dsh-ig-workspace-filter-badge",
|
|
3683
|
+
children: [
|
|
3684
|
+
workspaceItemsCount,
|
|
3685
|
+
"/",
|
|
3686
|
+
items.length
|
|
3687
|
+
]
|
|
3688
|
+
})
|
|
3689
|
+
]
|
|
1442
3690
|
})
|
|
1443
3691
|
]
|
|
1444
3692
|
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1467,7 +3715,7 @@ window.__ModuleLoader__.load({
|
|
|
1467
3715
|
})]
|
|
1468
3716
|
}),
|
|
1469
3717
|
(0, react_jsx_runtime.jsx)("div", {
|
|
1470
|
-
className:
|
|
3718
|
+
className: `dsh-ig-gallery-page-body ${activeTab === "studio" ? "is-workbench" : ""}`,
|
|
1471
3719
|
children: activeTab === "gallery" || activeTab === "favorites" ? items.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
|
|
1472
3720
|
className: "dsh-ig-gallery-empty",
|
|
1473
3721
|
children: [
|
|
@@ -1531,22 +3779,9 @@ window.__ModuleLoader__.load({
|
|
|
1531
3779
|
onBlobLoaded: (b) => blobCache.set(item.id, b),
|
|
1532
3780
|
onToast: showToast
|
|
1533
3781
|
}, item.id))
|
|
1534
|
-
}) :
|
|
1535
|
-
|
|
1536
|
-
|
|
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")
|
|
3782
|
+
}) : (0, react_jsx_runtime.jsx)(StudioView, {
|
|
3783
|
+
locale,
|
|
3784
|
+
workspace: activeWorkspace
|
|
1550
3785
|
})
|
|
1551
3786
|
}),
|
|
1552
3787
|
toast && (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -1624,7 +3859,7 @@ window.__ModuleLoader__.load({
|
|
|
1624
3859
|
children: [(0, react_jsx_runtime.jsx)("p", {
|
|
1625
3860
|
className: "dsh-ig-modal-desc",
|
|
1626
3861
|
children: t("batchDeleteDesc")
|
|
1627
|
-
}), (0, react_jsx_runtime.jsxs)("label", {
|
|
3862
|
+
}), hasWorkspaceFilesToDelete && (0, react_jsx_runtime.jsxs)("label", {
|
|
1628
3863
|
className: "dsh-ig-modal-checkbox-label",
|
|
1629
3864
|
children: [(0, react_jsx_runtime.jsx)("input", {
|
|
1630
3865
|
type: "checkbox",
|
|
@@ -1656,6 +3891,74 @@ window.__ModuleLoader__.load({
|
|
|
1656
3891
|
]
|
|
1657
3892
|
})
|
|
1658
3893
|
}),
|
|
3894
|
+
showRegenerateModal && previewItem && (0, react_jsx_runtime.jsx)("div", {
|
|
3895
|
+
className: "dsh-ig-modal-backdrop",
|
|
3896
|
+
onClick: handleCancelRegeneratePrompt,
|
|
3897
|
+
children: (0, react_jsx_runtime.jsxs)("div", {
|
|
3898
|
+
className: "dsh-ig-modal-box dsh-ig-regenerate-modal-box",
|
|
3899
|
+
onClick: (e) => e.stopPropagation(),
|
|
3900
|
+
children: [
|
|
3901
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
3902
|
+
className: "dsh-ig-modal-header",
|
|
3903
|
+
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
3904
|
+
className: "dsh-ig-regenerate-modal-icon",
|
|
3905
|
+
children: (0, react_jsx_runtime.jsx)(Sparkles, { size: 18 })
|
|
3906
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
3907
|
+
className: "dsh-ig-regenerate-modal-title-wrap",
|
|
3908
|
+
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
3909
|
+
className: "dsh-ig-modal-title",
|
|
3910
|
+
children: t("regenerateTitle")
|
|
3911
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
3912
|
+
className: "dsh-ig-regenerate-modal-meta",
|
|
3913
|
+
children: [
|
|
3914
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
3915
|
+
className: "dsh-ig-tag",
|
|
3916
|
+
children: previewItem.provider
|
|
3917
|
+
}),
|
|
3918
|
+
previewItem.model && (0, react_jsx_runtime.jsx)("span", {
|
|
3919
|
+
className: "dsh-ig-tag dsh-ig-tag-model",
|
|
3920
|
+
children: previewItem.model
|
|
3921
|
+
}),
|
|
3922
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
3923
|
+
className: "dsh-ig-tag",
|
|
3924
|
+
children: formatCardMeta(previewItem)
|
|
3925
|
+
})
|
|
3926
|
+
]
|
|
3927
|
+
})]
|
|
3928
|
+
})]
|
|
3929
|
+
}),
|
|
3930
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
3931
|
+
className: "dsh-ig-modal-body",
|
|
3932
|
+
children: [(0, react_jsx_runtime.jsx)("p", {
|
|
3933
|
+
className: "dsh-ig-modal-desc",
|
|
3934
|
+
children: t("regenerateHint")
|
|
3935
|
+
}), (0, react_jsx_runtime.jsx)("textarea", {
|
|
3936
|
+
className: "dsh-ig-regenerate-modal-textarea",
|
|
3937
|
+
value: regeneratePrompt,
|
|
3938
|
+
onChange: (e) => setRegeneratePrompt(e.target.value),
|
|
3939
|
+
placeholder: t("prompt"),
|
|
3940
|
+
rows: 4,
|
|
3941
|
+
autoFocus: true
|
|
3942
|
+
})]
|
|
3943
|
+
}),
|
|
3944
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
3945
|
+
className: "dsh-ig-modal-footer",
|
|
3946
|
+
children: [(0, react_jsx_runtime.jsx)("button", {
|
|
3947
|
+
type: "button",
|
|
3948
|
+
className: "dsh-ig-modal-btn dsh-ig-modal-btn-cancel",
|
|
3949
|
+
onClick: handleCancelRegeneratePrompt,
|
|
3950
|
+
children: t("cancel")
|
|
3951
|
+
}), (0, react_jsx_runtime.jsxs)("button", {
|
|
3952
|
+
type: "button",
|
|
3953
|
+
className: "dsh-ig-modal-btn dsh-ig-modal-btn-primary",
|
|
3954
|
+
onClick: handleConfirmRegenerate,
|
|
3955
|
+
disabled: regeneratePrompt.trim().length === 0,
|
|
3956
|
+
children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("confirmRegenerate") })]
|
|
3957
|
+
})]
|
|
3958
|
+
})
|
|
3959
|
+
]
|
|
3960
|
+
})
|
|
3961
|
+
}),
|
|
1659
3962
|
previewItem && (0, react_jsx_runtime.jsxs)("div", {
|
|
1660
3963
|
className: "dsh-ig-lightbox-backdrop",
|
|
1661
3964
|
onClick: handleClosePreview,
|
|
@@ -1771,6 +4074,27 @@ window.__ModuleLoader__.load({
|
|
|
1771
4074
|
},
|
|
1772
4075
|
children: [(0, react_jsx_runtime.jsx)(Download, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("download") })]
|
|
1773
4076
|
}),
|
|
4077
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
4078
|
+
type: "button",
|
|
4079
|
+
className: "dsh-ig-lightbox-btn dsh-ig-lightbox-btn-regenerate",
|
|
4080
|
+
title: t("regenerate"),
|
|
4081
|
+
disabled: isRegenerating,
|
|
4082
|
+
onClick: handleOpenRegenerate,
|
|
4083
|
+
children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("regenerate") })]
|
|
4084
|
+
}),
|
|
4085
|
+
isRegenerating && (0, react_jsx_runtime.jsxs)("div", {
|
|
4086
|
+
className: "dsh-ig-lightbox-generating-indicator",
|
|
4087
|
+
children: [
|
|
4088
|
+
(0, react_jsx_runtime.jsx)("div", { className: "dsh-ig-lightbox-spinner-sm" }),
|
|
4089
|
+
(0, react_jsx_runtime.jsx)("span", { children: t("regenerating") }),
|
|
4090
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
4091
|
+
type: "button",
|
|
4092
|
+
className: "dsh-ig-lightbox-abort-btn",
|
|
4093
|
+
onClick: handleAbortRegenerating,
|
|
4094
|
+
children: t("cancel")
|
|
4095
|
+
})
|
|
4096
|
+
]
|
|
4097
|
+
}),
|
|
1774
4098
|
(0, react_jsx_runtime.jsxs)("button", {
|
|
1775
4099
|
type: "button",
|
|
1776
4100
|
className: "dsh-ig-lightbox-btn dsh-ig-lightbox-btn-danger",
|
|
@@ -1963,68 +4287,6 @@ window.__ModuleLoader__.load({
|
|
|
1963
4287
|
})]
|
|
1964
4288
|
});
|
|
1965
4289
|
};
|
|
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
|
-
};
|
|
1998
|
-
async function copyImageBlob(blob) {
|
|
1999
|
-
try {
|
|
2000
|
-
if (blob.type === "image/png") {
|
|
2001
|
-
await navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]);
|
|
2002
|
-
return true;
|
|
2003
|
-
}
|
|
2004
|
-
const img = new Image();
|
|
2005
|
-
const url = URL.createObjectURL(blob);
|
|
2006
|
-
await new Promise((resolve, reject) => {
|
|
2007
|
-
img.onload = resolve;
|
|
2008
|
-
img.onerror = reject;
|
|
2009
|
-
img.src = url;
|
|
2010
|
-
});
|
|
2011
|
-
const canvas = document.createElement("canvas");
|
|
2012
|
-
canvas.width = img.naturalWidth;
|
|
2013
|
-
canvas.height = img.naturalHeight;
|
|
2014
|
-
const ctx = canvas.getContext("2d");
|
|
2015
|
-
if (!ctx) throw new Error("Canvas unavailable");
|
|
2016
|
-
ctx.drawImage(img, 0, 0);
|
|
2017
|
-
URL.revokeObjectURL(url);
|
|
2018
|
-
const pngBlob = await new Promise((res) => {
|
|
2019
|
-
canvas.toBlob(res, "image/png");
|
|
2020
|
-
});
|
|
2021
|
-
if (!pngBlob) throw new Error("Blob conversion failed");
|
|
2022
|
-
await navigator.clipboard.write([new ClipboardItem({ "image/png": pngBlob })]);
|
|
2023
|
-
return true;
|
|
2024
|
-
} catch (_err) {
|
|
2025
|
-
return false;
|
|
2026
|
-
}
|
|
2027
|
-
}
|
|
2028
4290
|
//#endregion
|
|
2029
4291
|
//#region lib/types/client/image-ref.js
|
|
2030
4292
|
/**
|
|
@@ -2038,6 +4300,116 @@ window.__ModuleLoader__.load({
|
|
|
2038
4300
|
const fromBlockContent = Array.isArray(block.content) ? block.content.find((item) => item.type === "image") : void 0;
|
|
2039
4301
|
if (fromBlockContent?.type === "image") return fromBlockContent.attachment;
|
|
2040
4302
|
}
|
|
4303
|
+
//#endregion
|
|
4304
|
+
//#region lib/types/client/studio-style.js
|
|
4305
|
+
const STUDIO_STYLE = `
|
|
4306
|
+
.dsh-ig-gallery-page-body.is-workbench{flex:1 1 0;height:100%;min-height:0;padding:0;overflow:hidden;display:flex;flex-direction:column}
|
|
4307
|
+
.dsh-ig-workbench{--ig-blue:#2f64f5;--ig-line:color-mix(in srgb,currentColor 11%,transparent);--ig-soft:color-mix(in srgb,currentColor 4%,transparent);position:relative;display:flex;flex-direction:column;min-height:0;height:100%;overflow:hidden;padding:10px 12px 12px;color:var(--foreground,#182033);box-sizing:border-box;background:radial-gradient(circle at 60% -20%,rgba(63,104,246,.07),transparent 30%)}
|
|
4308
|
+
.dsh-ig-workbench *{box-sizing:border-box}.dsh-ig-workbench button,.dsh-ig-workbench select,.dsh-ig-workbench textarea{font:inherit;color:inherit}
|
|
4309
|
+
.dsh-ig-workbench-grid{display:grid;grid-template-columns:minmax(190px,238px) minmax(0,1fr) minmax(286px,340px);grid-template-rows:minmax(0,1fr);min-height:0;height:0;flex:1 1 0;border:1px solid var(--ig-line);border-radius:12px;overflow:hidden;background:var(--background,#fff);box-shadow:0 12px 32px rgba(30,42,76,.05);transition:grid-template-columns .2s ease}
|
|
4310
|
+
.dsh-ig-workbench-grid.is-sidebar-collapsed{grid-template-columns:minmax(0,1fr) minmax(286px,340px)}
|
|
4311
|
+
.dsh-ig-recent-panel,.dsh-ig-generate-panel{min-width:0;min-height:0;overflow:hidden;background:color-mix(in srgb,var(--background,#fff) 96%,#f5f7fb)}.dsh-ig-recent-panel{display:flex;flex-direction:column;border-right:1px solid var(--ig-line)}
|
|
4312
|
+
.dsh-ig-panel-title{height:48px;flex:0 0 48px;padding:0 12px 0 14px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--ig-line);font-size:13px;font-weight:650}
|
|
4313
|
+
.dsh-ig-count-badge{font-size:11px;min-width:22px;text-align:center;padding:2px 6px;border-radius:999px;background:var(--ig-soft);color:#7b8498;font-weight:500}
|
|
4314
|
+
.dsh-ig-collapse-btn{width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border:1px solid transparent;border-radius:6px;background:transparent;color:#717b90;cursor:pointer;transition:all .15s}
|
|
4315
|
+
.dsh-ig-collapse-btn:hover{border-color:var(--ig-line);background:var(--ig-soft);color:var(--foreground,#182033)}
|
|
4316
|
+
.dsh-ig-recent-scroll{flex:1 1 0;height:0;padding:8px;overflow-x:hidden;overflow-y:auto;min-height:0}.dsh-ig-recent-empty{min-height:180px;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:10px;color:#929bad;text-align:center;font-size:12px}
|
|
4317
|
+
.dsh-ig-recent-item{display:grid;grid-template-columns:70px minmax(0,1fr);gap:10px;width:100%;padding:8px;border:1px solid transparent;border-radius:9px;background:transparent;text-align:left;cursor:pointer;transition:background .16s,border-color .16s,transform .16s}.dsh-ig-recent-item:hover{background:var(--ig-soft);transform:translateY(-1px)}.dsh-ig-recent-item.is-active{border-color:#89a7ff;background:rgba(47,100,245,.075);box-shadow:inset 3px 0 0 var(--ig-blue)}.dsh-ig-recent-thumb{width:70px;height:70px;border-radius:7px;display:flex;align-items:center;justify-content:center;overflow:hidden;background:#eef1f7;color:#929bad}.dsh-ig-recent-thumb img{width:100%;height:100%;object-fit:cover}.dsh-ig-recent-item>div:last-child{min-width:0;padding:1px 0}.dsh-ig-recent-item strong{display:block;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;font-size:12.5px;line-height:19px;font-weight:630}.dsh-ig-recent-item span,.dsh-ig-recent-item small{display:block;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;color:#828b9e;font-size:10.5px;line-height:16px}
|
|
4318
|
+
.dsh-ig-load-more{width:100%;height:32px;margin:8px 0;display:flex;align-items:center;justify-content:center;border:1px dashed var(--ig-line);border-radius:7px;background:transparent;font-size:11px;color:#6b758b;cursor:pointer;transition:background .15s,color .15s}.dsh-ig-load-more:hover{background:var(--ig-soft);color:var(--ig-blue);border-color:#a2b9f6}
|
|
4319
|
+
.dsh-ig-mini-btn{height:22px;padding:0 7px;display:inline-flex;align-items:center;gap:3px;border:1px solid var(--ig-line);border-radius:5px;background:var(--background,#fff);font-size:10.5px;font-weight:500;color:var(--foreground,#242f44);cursor:pointer;transition:all .15s}.dsh-ig-mini-btn:hover{border-color:#9bb4f5;color:var(--ig-blue);background:rgba(47,100,245,.06)}
|
|
4320
|
+
.dsh-ig-canvas-column{min-width:0;min-height:0;height:100%;overflow:hidden;display:flex;flex-direction:column;background:color-mix(in srgb,var(--background,#fff) 97%,#e8edf7)}
|
|
4321
|
+
.dsh-ig-canvas-toolbar{height:46px;flex:0 0 46px;display:flex;align-items:center;justify-content:space-between;gap:8px;padding:0 14px;border-bottom:1px solid var(--ig-line)}
|
|
4322
|
+
.dsh-ig-canvas-toolbar-left,.dsh-ig-canvas-toolbar-right{display:flex;align-items:center;gap:6px}
|
|
4323
|
+
.dsh-ig-canvas-toolbar button{height:30px;min-width:30px;padding:0 9px;display:inline-flex;align-items:center;justify-content:center;gap:5px;border:1px solid var(--ig-line);border-radius:7px;background:var(--background,#fff);color:var(--foreground,#242f44);font-size:11.5px;font-weight:500;cursor:pointer;transition:border-color .15s,background .15s,color .15s}
|
|
4324
|
+
.dsh-ig-canvas-toolbar button:hover{border-color:#9bb4f5;background:rgba(47,100,245,.05);color:var(--ig-blue)}
|
|
4325
|
+
.dsh-ig-canvas-toolbar button.is-active{border-color:#8ba8f8;background:rgba(47,100,245,.08);color:var(--ig-blue);font-weight:550}
|
|
4326
|
+
.dsh-ig-zoom-display{font-size:11px;min-width:38px;text-align:center;color:#64748b;user-select:none;font-variant-numeric:tabular-nums}
|
|
4327
|
+
.dsh-ig-status-badge-wrap{position:relative;display:inline-flex;align-items:center}
|
|
4328
|
+
.dsh-ig-status-badge{height:30px;display:inline-flex;align-items:center;gap:7px;padding:0 10px;border:1px solid var(--ig-line);border-radius:7px;background:var(--background,#fff);cursor:pointer;user-select:none;transition:border-color .15s,background .15s,box-shadow .15s}
|
|
4329
|
+
.dsh-ig-status-badge:hover{border-color:#9bb4f5;background:rgba(47,100,245,.04)}
|
|
4330
|
+
.dsh-ig-status-dot{width:7px;height:7px;min-width:7px;max-width:7px;border-radius:50%;flex-shrink:0;box-sizing:border-box}
|
|
4331
|
+
.dsh-ig-status-dot.is-ready{background:#10b981;box-shadow:0 0 0 2px rgba(16,185,129,.2)}
|
|
4332
|
+
.dsh-ig-status-dot.is-muted{background:#ef4444;box-shadow:0 0 0 2px rgba(239,68,68,.2)}
|
|
4333
|
+
.dsh-ig-status-text{font-size:11.5px;font-weight:500;color:var(--foreground,#242f44);line-height:1}
|
|
4334
|
+
.dsh-ig-status-arrow{color:#94a3b8;transition:transform .15s,color .15s}
|
|
4335
|
+
.dsh-ig-status-badge-wrap:hover .dsh-ig-status-arrow{transform:rotate(180deg);color:var(--ig-blue)}
|
|
4336
|
+
.dsh-ig-status-popover{position:absolute;left:0;top:calc(100% + 6px);width:260px;background:var(--background,#fff);border:1px solid var(--ig-line);border-radius:9px;box-shadow:0 12px 36px rgba(20,28,45,.14);padding:10px 12px;z-index:40;opacity:0;pointer-events:none;transform:translateY(4px);transition:opacity .15s cubic-bezier(.16,1,.3,1),transform .15s cubic-bezier(.16,1,.3,1)}
|
|
4337
|
+
.dsh-ig-status-badge-wrap:hover .dsh-ig-status-popover{opacity:1;pointer-events:auto;transform:translateY(0)}
|
|
4338
|
+
.dsh-ig-status-popover-title{display:flex;align-items:center;justify-content:space-between;font-size:10px;font-weight:650;color:#8590a6;margin-bottom:8px;letter-spacing:.02em;text-transform:uppercase}
|
|
4339
|
+
.dsh-ig-status-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:5px}
|
|
4340
|
+
.dsh-ig-status-list li{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:6px 8px;border-radius:6px;transition:background .12s}
|
|
4341
|
+
.dsh-ig-status-list li.is-configured{background:rgba(16,185,129,.05);border:1px solid rgba(16,185,129,.12)}
|
|
4342
|
+
.dsh-ig-status-list li.is-missing{background:rgba(239,68,68,.04);border:1px solid rgba(239,68,68,.12);opacity:.75}
|
|
4343
|
+
.dsh-ig-status-item-name{display:flex;flex-direction:column;min-width:0}
|
|
4344
|
+
.dsh-ig-status-item-name strong{font-size:11.5px;font-weight:600;color:var(--foreground,#182033);line-height:16px}
|
|
4345
|
+
.dsh-ig-status-item-name small{font-size:9.5px;color:#7c869a;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:145px;line-height:14px}
|
|
4346
|
+
.dsh-ig-status-item-tag{font-size:10px;font-weight:600;padding:2px 6px;border-radius:4px;flex-shrink:0}
|
|
4347
|
+
.is-configured .dsh-ig-status-item-tag{color:#059669;background:rgba(16,185,129,.14)}
|
|
4348
|
+
.is-missing .dsh-ig-status-item-tag{color:#dc2626;background:rgba(239,68,68,.14)}
|
|
4349
|
+
.dsh-ig-canvas{position:relative;flex:1 1 0;height:0;min-height:0;display:flex;align-items:center;justify-content:center;overflow:hidden;padding:24px;touch-action:none;user-select:none;cursor:grab;background-image:linear-gradient(45deg,rgba(114,128,156,.035) 25%,transparent 25%),linear-gradient(-45deg,rgba(114,128,156,.035) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(114,128,156,.035) 75%),linear-gradient(-45deg,transparent 75%,rgba(114,128,156,.035) 75%);background-size:24px 24px;background-position:0 0,0 12px,12px -12px,-12px 0}
|
|
4350
|
+
.dsh-ig-canvas.is-dragging,.dsh-ig-canvas.is-dragging *{cursor:grabbing!important}
|
|
4351
|
+
.dsh-ig-canvas img{display:block;max-width:100%;max-height:100%;width:auto;height:auto;border-radius:10px;box-shadow:0 14px 45px rgba(25,35,63,.18);user-select:none;-webkit-user-drag:none;pointer-events:auto}
|
|
4352
|
+
.dsh-ig-canvas-empty,.dsh-ig-generating-state{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:12px;color:#929bad;font-size:12px;padding:26px;cursor:default}
|
|
4353
|
+
.dsh-ig-generating-state strong{font-size:15px;color:var(--foreground,#182033)}.dsh-ig-generating-state span{font-size:11px}.dsh-ig-generation-orbit{width:66px;height:66px;border-radius:50%;display:grid;place-items:center;color:var(--ig-blue);background:rgba(47,100,245,.09);box-shadow:0 0 0 10px rgba(47,100,245,.035);animation:dshIgPulse 1.8s ease-in-out infinite}
|
|
4354
|
+
.dsh-ig-result-strip{flex:0 0 auto;min-height:52px;padding:8px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--ig-line)}.dsh-ig-result-strip>span{font-size:12px;font-weight:650}.dsh-ig-result-strip>div,.dsh-ig-result-actions>div{display:flex;gap:7px}.dsh-ig-result-strip button,.dsh-ig-result-actions button{height:32px;display:inline-flex;align-items:center;gap:6px;padding:0 10px;border:1px solid var(--ig-line);border-radius:7px;background:var(--background,#fff);font-size:11px;cursor:pointer}.dsh-ig-result-strip button:hover,.dsh-ig-result-actions button:hover{border-color:#9fb2ed;color:var(--ig-blue)}.dsh-ig-result-actions{flex:0 0 auto;min-height:54px;padding:8px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--ig-line)}.dsh-ig-result-actions p{margin:0;min-width:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;font-size:11.5px;line-height:18px;color:#596277}.dsh-ig-result-meta{flex:0 0 auto;min-height:32px;padding:6px 14px;display:flex;gap:13px;align-items:center;flex-wrap:wrap;border-top:1px solid var(--ig-line);font-size:10px;color:#8790a3}.dsh-ig-result-meta span+span:before{content:'·';margin-right:13px}
|
|
4355
|
+
.dsh-ig-generate-panel{height:100%;display:flex;flex-direction:column;border-left:1px solid var(--ig-line);overflow:hidden}.dsh-ig-panel-tabs{height:48px;flex:0 0 48px;display:grid;grid-template-columns:1fr 1fr;border-bottom:1px solid var(--ig-line);padding:0 14px}.dsh-ig-panel-tabs button{position:relative;border:0;background:transparent;font-size:12px;color:#7c8598;cursor:pointer}.dsh-ig-panel-tabs button.is-active{color:var(--ig-blue);font-weight:650}.dsh-ig-panel-tabs button.is-active:after{content:'';position:absolute;left:20%;right:20%;bottom:-1px;height:2px;background:var(--ig-blue);border-radius:2px 2px 0 0}.dsh-ig-generator-form{flex:1 1 0;height:0;padding:15px;display:flex;flex-direction:column;gap:15px;overflow-x:hidden;overflow-y:auto;min-height:0}.dsh-ig-mode-switch{display:grid;grid-template-columns:1fr 1fr;gap:8px}.dsh-ig-mode-switch button{height:39px;border:1px solid var(--ig-line);border-radius:8px;background:var(--background,#fff);display:flex;align-items:center;justify-content:center;gap:7px;font-size:12px;cursor:pointer}.dsh-ig-mode-switch button.is-active{border-color:#789bff;color:var(--ig-blue);background:rgba(47,100,245,.065);box-shadow:inset 0 0 0 1px rgba(47,100,245,.1)}
|
|
4356
|
+
.dsh-ig-field{position:relative;display:flex;flex-direction:column;gap:7px}.dsh-ig-field>label,.dsh-ig-field-label label{font-size:11.5px;font-weight:630}.dsh-ig-field>label span{font-weight:400;color:#929bad}.dsh-ig-field-label{display:flex;align-items:center;justify-content:space-between}.dsh-ig-field-label b{color:#e14d4d}.dsh-ig-field-label button{border:0;background:transparent;color:#798397;font-size:10.5px;cursor:pointer}.dsh-ig-field textarea{width:100%;height:112px;padding:11px 12px 22px;resize:vertical;border:1px solid var(--ig-line);border-radius:9px;background:var(--background,#fff);font-size:12px;line-height:19px;outline:none}.dsh-ig-field textarea:focus{border-color:var(--ig-blue);box-shadow:0 0 0 3px rgba(47,100,245,.09)}.dsh-ig-field>small{position:absolute;right:9px;bottom:7px;color:#9aa2b2;font-size:9.5px}.dsh-ig-field-grid{display:grid;grid-template-columns:1fr 1fr;gap:9px}.dsh-ig-field-select{display:flex;flex-direction:column;gap:6px;min-width:0}.dsh-ig-field-select>span{font-size:11px;font-weight:620}.dsh-ig-field-select select{width:100%;height:36px;padding:0 26px 0 9px;border:1px solid var(--ig-line);border-radius:8px;background:var(--background,#fff);font-size:11px;outline:none;text-overflow:ellipsis}.dsh-ig-field-select select:focus{border-color:var(--ig-blue)}.dsh-ig-count-row{display:grid;grid-template-columns:repeat(4,1fr);gap:6px}.dsh-ig-count-pill{height:34px;border:1px solid var(--ig-line);border-radius:7px;background:var(--background,#fff);font-size:12px;font-weight:600;color:#596277;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .15s}.dsh-ig-count-pill:hover{border-color:#9fb2ed;color:var(--ig-blue)}.dsh-ig-count-pill.is-active{border-color:var(--ig-blue);background:rgba(47,100,245,.08);color:var(--ig-blue);box-shadow:inset 0 0 0 1px var(--ig-blue)}
|
|
4357
|
+
.dsh-ig-upload{height:92px;border:1px dashed #bcc6d9;border-radius:9px;background:rgba(255,255,255,.45);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;color:#7d879b;cursor:pointer}.dsh-ig-upload strong{font-size:11px;font-weight:570}.dsh-ig-upload small{font-size:9.5px}.dsh-ig-upload:hover,.dsh-ig-upload.is-dragging{border-color:var(--ig-blue);background:rgba(47,100,245,.05);color:var(--ig-blue)}
|
|
4358
|
+
.dsh-ig-reference-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(64px,1fr));gap:8px}
|
|
4359
|
+
.dsh-ig-reference-card{position:relative;aspect-ratio:1;border-radius:8px;overflow:hidden;background:#eef1f7;border:1px solid var(--ig-line)}
|
|
4360
|
+
.dsh-ig-reference-card img{width:100%;height:100%;object-fit:cover}
|
|
4361
|
+
.dsh-ig-reference-card button{position:absolute;right:4px;top:4px;width:18px;height:18px;border:0;border-radius:50%;display:grid;place-items:center;background:rgba(20,28,45,.75);color:#fff;cursor:pointer;padding:0;transition:background .15s}
|
|
4362
|
+
.dsh-ig-reference-card button:hover{background:#ef4444}
|
|
4363
|
+
.dsh-ig-reference-add{aspect-ratio:1;border:1px dashed #bcc6d9;border-radius:8px;background:rgba(255,255,255,.45);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;color:#7d879b;cursor:pointer;font-size:10px;transition:border-color .15s,color .15s,background .15s}
|
|
4364
|
+
.dsh-ig-reference-add:hover{border-color:var(--ig-blue);background:rgba(47,100,245,.05);color:var(--ig-blue)}
|
|
4365
|
+
.dsh-ig-reference-preview{position:relative;height:108px;border-radius:9px;overflow:hidden;background:#eef1f7}.dsh-ig-reference-preview img{width:100%;height:100%;object-fit:contain}.dsh-ig-reference-preview button{position:absolute;right:7px;top:7px;width:25px;height:25px;border:0;border-radius:50%;display:grid;place-items:center;background:rgba(20,28,45,.72);color:#fff;cursor:pointer}
|
|
4366
|
+
.dsh-ig-inline-note,.dsh-ig-form-error{padding:9px 10px;border-radius:7px;font-size:10.5px;line-height:16px}.dsh-ig-inline-note{background:#fff8e7;color:#8b671c;border:1px solid #f1dfae}.dsh-ig-form-error{background:#fff1f1;color:#b63737;border:1px solid #f2caca}.dsh-ig-form-loading{height:70px;display:grid;place-items:center;color:var(--ig-blue)}
|
|
4367
|
+
.dsh-ig-config-error{padding:14px;display:flex;flex-direction:column;align-items:center;gap:10px;background:#fff1f1;border:1px solid #f2caca;border-radius:8px;color:#b63737;font-size:11.5px;text-align:center}.dsh-ig-config-error p{margin:0;line-height:17px}.dsh-ig-config-error button{height:30px;display:inline-flex;align-items:center;gap:6px;padding:0 12px;border:1px solid #e0a8a8;border-radius:6px;background:#fff;color:#b63737;font-size:11.5px;font-weight:600;cursor:pointer}.dsh-ig-config-error button:hover{background:#ffebeb}
|
|
4368
|
+
.dsh-ig-generate-button{height:42px;flex:0 0 auto;border:0;border-radius:8px;background:linear-gradient(135deg,#386cf6,#2758e7);color:#fff!important;display:flex;align-items:center;justify-content:center;gap:8px;font-size:12px;font-weight:650;cursor:pointer;box-shadow:0 8px 18px rgba(47,100,245,.22);transition:transform .16s,box-shadow .16s}.dsh-ig-generate-button:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 10px 24px rgba(47,100,245,.28)}.dsh-ig-generate-button:disabled{opacity:.55;cursor:not-allowed;box-shadow:none}
|
|
4369
|
+
.dsh-ig-generate-button.is-cancel{background:linear-gradient(135deg,#ef4444,#dc2626);box-shadow:0 8px 18px rgba(239,68,68,.25)}.dsh-ig-generate-button.is-cancel:hover{background:linear-gradient(135deg,#dc2626,#b91c1c);box-shadow:0 10px 24px rgba(239,68,68,.35)}
|
|
4370
|
+
.dsh-ig-details{padding:16px;margin:0;overflow:auto}.dsh-ig-details>div{padding:12px 0;border-bottom:1px solid var(--ig-line)}.dsh-ig-details dt{font-size:10.5px;color:#8992a4;margin-bottom:5px}.dsh-ig-details dd{margin:0;font-size:11.5px;line-height:18px;word-break:break-word}.dsh-ig-details-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;height:220px;padding:20px;color:#929bad;font-size:11px;text-align:center}
|
|
4371
|
+
.dsh-ig-workbench-modal-backdrop{position:fixed;inset:0;background:rgba(15,23,42,.45);backdrop-filter:blur(3px);z-index:999;display:grid;place-items:center;padding:16px}
|
|
4372
|
+
.dsh-ig-workbench-modal-box{width:100%;max-width:380px;background:var(--background,#fff);border-radius:12px;padding:20px;box-shadow:0 20px 48px rgba(0,0,0,.22);display:flex;flex-direction:column;gap:14px}
|
|
4373
|
+
.dsh-ig-workbench-modal-header{display:flex;align-items:center;gap:10px}
|
|
4374
|
+
.dsh-ig-workbench-modal-header strong{font-size:14px;font-weight:650;color:#b91c1c}
|
|
4375
|
+
.dsh-ig-workbench-modal-desc{font-size:12px;line-height:18px;color:#555f77;margin:0}
|
|
4376
|
+
.dsh-ig-workbench-modal-option{display:flex;align-items:center;gap:8px;font-size:11.5px;color:#4b5563;cursor:pointer}
|
|
4377
|
+
.dsh-ig-workbench-modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:4px}
|
|
4378
|
+
.dsh-ig-workbench-modal-actions button{height:32px;padding:0 14px;border-radius:7px;font-size:11.5px;font-weight:600;cursor:pointer}
|
|
4379
|
+
.dsh-ig-workbench-modal-cancel{border:1px solid var(--ig-line);background:var(--background,#fff);color:#6b7280}
|
|
4380
|
+
.dsh-ig-workbench-modal-cancel:hover{background:#f3f4f6}
|
|
4381
|
+
.dsh-ig-workbench-modal-danger{border:0;background:#dc2626;color:#fff}
|
|
4382
|
+
.dsh-ig-workbench-modal-danger:hover{background:#b91c1c}
|
|
4383
|
+
@keyframes dshIgPulse{50%{transform:scale(1.06);box-shadow:0 0 0 16px rgba(47,100,245,.025)}}
|
|
4384
|
+
.dsh-ig-workbench-toast{position:fixed;bottom:28px;left:50%;transform:translateX(-50%);background:rgba(15,23,42,0.88);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);color:#fff;padding:8px 18px;border-radius:10px;font-size:12.5px;font-weight:500;box-shadow:0 12px 32px rgba(0,0,0,0.3);z-index:99999;animation:dshIgFade .15s ease-out;pointer-events:none}
|
|
4385
|
+
@keyframes dshIgFade{from{opacity:0;transform:translate(-50%,6px)}to{opacity:1;transform:translate(-50%,0)}}
|
|
4386
|
+
@media(max-width:1120px){.dsh-ig-workbench-grid{grid-template-columns:190px minmax(360px,1fr) 300px}.dsh-ig-result-actions button span{display:none}}
|
|
4387
|
+
@media(max-width:900px){.dsh-ig-gallery-page-body.is-workbench{height:auto;overflow:auto}.dsh-ig-workbench{height:auto;min-height:100%;overflow:visible;padding:12px}.dsh-ig-workbench-heading{height:auto;padding-bottom:12px;align-items:center}.dsh-ig-provider-quick>span{display:none}.dsh-ig-workbench-grid{height:auto;display:flex;flex:none;flex-direction:column;overflow:visible}.dsh-ig-recent-panel{height:auto;overflow:hidden;border-right:0;border-bottom:1px solid var(--ig-line)}.dsh-ig-recent-scroll{height:auto;display:flex;overflow-x:auto;overflow-y:hidden;padding:8px}.dsh-ig-recent-item{min-width:220px}.dsh-ig-canvas-column{height:auto;min-height:560px}.dsh-ig-generate-panel{height:auto;border-left:0;border-top:1px solid var(--ig-line);min-height:560px}.dsh-ig-generator-form{height:auto;overflow:visible}.dsh-ig-canvas{height:auto;min-height:360px}}
|
|
4388
|
+
@media(max-width:560px){.dsh-ig-workbench-heading>div{display:block}.dsh-ig-workbench-heading h1{font-size:15px}.dsh-ig-provider-quick select{min-width:128px;max-width:150px}.dsh-ig-canvas{padding:12px;min-height:300px}.dsh-ig-result-strip,.dsh-ig-result-actions{align-items:flex-start;flex-direction:column}.dsh-ig-result-strip>div,.dsh-ig-result-actions>div{width:100%}.dsh-ig-result-strip button,.dsh-ig-result-actions button{flex:1;justify-content:center}.dsh-ig-result-actions button span{display:inline}.dsh-ig-field-grid{grid-template-columns:1fr}.dsh-ig-result-meta span+span:before{display:none}}
|
|
4389
|
+
@media(prefers-reduced-motion:reduce){.dsh-ig-workbench *{scroll-behavior:auto!important;animation-duration:.01ms!important;transition-duration:.01ms!important}}
|
|
4390
|
+
.dsh-ig-workspace-filter-label{height:30px;display:inline-flex;align-items:center;gap:6px;padding:0 10px;border:1px solid var(--ig-line,rgba(0,0,0,.1));border-radius:7px;background:var(--background,#fff);font-size:11.5px;color:var(--foreground,#242f44);cursor:pointer;user-select:none;transition:border-color .15s,background .15s}
|
|
4391
|
+
.dsh-ig-workspace-filter-label:hover{border-color:#9bb4f5;background:rgba(47,100,245,.04)}
|
|
4392
|
+
.dsh-ig-workspace-checkbox{cursor:pointer;accent-color:#2f64f5;width:13.5px;height:13.5px;margin:0}
|
|
4393
|
+
.dsh-ig-workspace-filter-text{font-weight:500;white-space:nowrap}
|
|
4394
|
+
.dsh-ig-workspace-filter-badge{font-size:10px;font-weight:600;padding:1px 6px;border-radius:999px;background:color-mix(in srgb,currentColor 7%,transparent);color:#64748b;font-variant-numeric:tabular-nums}
|
|
4395
|
+
.dsh-ig-canvas-cluster{display:grid;gap:16px;align-items:center;justify-content:center;user-select:none;pointer-events:auto;flex-shrink:0}
|
|
4396
|
+
.dsh-ig-canvas-cluster[data-count="2"]{grid-template-columns:repeat(2,1fr);max-width:880px}
|
|
4397
|
+
.dsh-ig-canvas-cluster[data-count="3"],.dsh-ig-canvas-cluster[data-count="4"]{grid-template-columns:repeat(2,1fr);max-width:760px}
|
|
4398
|
+
.dsh-ig-canvas-item{position:relative;display:flex;align-items:center;justify-content:center;border-radius:10px;cursor:pointer;transition:transform .12s ease-out}
|
|
4399
|
+
.dsh-ig-canvas-item img{display:block;max-width:100%;max-height:360px;width:auto;height:auto;object-fit:contain;border-radius:10px;box-shadow:0 10px 30px rgba(25,35,63,.16);user-select:none;-webkit-user-drag:none;pointer-events:auto;transition:box-shadow .15s,outline .15s}
|
|
4400
|
+
.dsh-ig-canvas-item:hover img{box-shadow:0 14px 40px rgba(47,100,245,.2)}
|
|
4401
|
+
.dsh-ig-canvas-item.is-selected img{outline:3px solid var(--ig-blue);outline-offset:2px;box-shadow:0 14px 40px rgba(47,100,245,.35)}
|
|
4402
|
+
.dsh-ig-canvas-badge{position:absolute;top:10px;left:10px;background:rgba(15,23,42,.7);color:#fff;font-size:11px;font-weight:700;padding:2px 7px;border-radius:5px;backdrop-filter:blur(4px);pointer-events:none;transition:background .15s;z-index:2}
|
|
4403
|
+
.dsh-ig-canvas-item.is-selected .dsh-ig-canvas-badge{background:var(--ig-blue)}
|
|
4404
|
+
.dsh-ig-canvas-item-loading{width:260px;height:260px;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.45);border:1px solid var(--ig-line);border-radius:10px;color:#929bad}
|
|
4405
|
+
.dsh-ig-lightbox-nav{position:absolute;top:50%;transform:translateY(-50%);width:42px;height:42px;border-radius:50%;border:0;background:rgba(15,23,42,.65);color:#fff;display:grid;place-items:center;cursor:pointer;transition:background .15s,transform .15s;z-index:10;backdrop-filter:blur(4px)}
|
|
4406
|
+
.dsh-ig-lightbox-nav:hover{background:rgba(47,100,245,.85);transform:translateY(-50%) scale(1.08)}
|
|
4407
|
+
.dsh-ig-lightbox-nav.is-prev{left:18px}
|
|
4408
|
+
.dsh-ig-lightbox-nav.is-next{right:18px}
|
|
4409
|
+
.dsh-ig-save-gallery-btn{color:var(--ig-blue)!important;border-color:rgba(47,100,245,.35)!important;background:rgba(47,100,245,.06)!important}
|
|
4410
|
+
.dsh-ig-save-gallery-btn:hover{background:rgba(47,100,245,.12)!important}
|
|
4411
|
+
.dsh-ig-save-gallery-btn.is-saved{color:#64748b!important;border-color:var(--ig-line)!important;background:transparent!important}
|
|
4412
|
+
`;
|
|
2041
4413
|
//#endregion
|
|
2042
4414
|
//#region lib/types/client/image-result-node.js
|
|
2043
4415
|
const IMAGE_RESULT_NODE_KIND = "dsh-image-result";
|
|
@@ -2179,6 +4551,75 @@ window.__ModuleLoader__.load({
|
|
|
2179
4551
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
2180
4552
|
}
|
|
2181
4553
|
//#endregion
|
|
4554
|
+
//#region lib/types/client/conversation-image-revisions.js
|
|
4555
|
+
const STORAGE_PREFIX = "dsh_image_gen_conversation_revisions:";
|
|
4556
|
+
/** Read the locally persisted replacement history for one immutable conversation image. */
|
|
4557
|
+
function loadConversationImageRevisionChain(originId) {
|
|
4558
|
+
const empty = () => ({
|
|
4559
|
+
originId,
|
|
4560
|
+
currentIndex: 0,
|
|
4561
|
+
revisions: []
|
|
4562
|
+
});
|
|
4563
|
+
try {
|
|
4564
|
+
const raw = globalThis.localStorage?.getItem(storageKey(originId));
|
|
4565
|
+
if (!raw) return empty();
|
|
4566
|
+
const value = JSON.parse(raw);
|
|
4567
|
+
if (!isChain(value, originId)) return empty();
|
|
4568
|
+
return {
|
|
4569
|
+
originId,
|
|
4570
|
+
currentIndex: Math.min(value.currentIndex, value.revisions.length),
|
|
4571
|
+
revisions: value.revisions
|
|
4572
|
+
};
|
|
4573
|
+
} catch {
|
|
4574
|
+
return empty();
|
|
4575
|
+
}
|
|
4576
|
+
}
|
|
4577
|
+
/** Append a generated version and make it the version shown by the conversation card. */
|
|
4578
|
+
function appendConversationImageRevision(originId, revision) {
|
|
4579
|
+
const revisions = [...loadConversationImageRevisionChain(originId).revisions.filter((item) => item.attachment.attachmentId !== revision.attachment.attachmentId), revision].slice(-20);
|
|
4580
|
+
const next = {
|
|
4581
|
+
originId,
|
|
4582
|
+
currentIndex: revisions.length,
|
|
4583
|
+
revisions
|
|
4584
|
+
};
|
|
4585
|
+
persist(next);
|
|
4586
|
+
return next;
|
|
4587
|
+
}
|
|
4588
|
+
/** Select an earlier or later version without altering the underlying conversation event. */
|
|
4589
|
+
function selectConversationImageRevision(originId, requestedIndex) {
|
|
4590
|
+
const current = loadConversationImageRevisionChain(originId);
|
|
4591
|
+
const currentIndex = Math.max(0, Math.min(Math.trunc(requestedIndex), current.revisions.length));
|
|
4592
|
+
const next = {
|
|
4593
|
+
...current,
|
|
4594
|
+
currentIndex
|
|
4595
|
+
};
|
|
4596
|
+
persist(next);
|
|
4597
|
+
return next;
|
|
4598
|
+
}
|
|
4599
|
+
function persist(chain) {
|
|
4600
|
+
try {
|
|
4601
|
+
globalThis.localStorage?.setItem(storageKey(chain.originId), JSON.stringify(chain));
|
|
4602
|
+
} catch {}
|
|
4603
|
+
}
|
|
4604
|
+
function storageKey(originId) {
|
|
4605
|
+
return `${STORAGE_PREFIX}${originId}`;
|
|
4606
|
+
}
|
|
4607
|
+
function isChain(value, originId) {
|
|
4608
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
4609
|
+
const candidate = value;
|
|
4610
|
+
return candidate.originId === originId && typeof candidate.currentIndex === "number" && Number.isSafeInteger(candidate.currentIndex) && candidate.currentIndex >= 0 && Array.isArray(candidate.revisions) && candidate.revisions.every(isRevision);
|
|
4611
|
+
}
|
|
4612
|
+
function isRevision(value) {
|
|
4613
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
4614
|
+
const candidate = value;
|
|
4615
|
+
return isAttachment(candidate.attachment) && (candidate.provider === "google" || candidate.provider === "openai" || candidate.provider === "seedream" || candidate.provider === "dashscope") && typeof candidate.prompt === "string" && typeof candidate.model === "string" && typeof candidate.output === "string" && typeof candidate.createdAt === "number" && Number.isFinite(candidate.createdAt) && typeof candidate.ratio === "string" && typeof candidate.quality === "string";
|
|
4616
|
+
}
|
|
4617
|
+
function isAttachment(value) {
|
|
4618
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
4619
|
+
const candidate = value;
|
|
4620
|
+
return typeof candidate.attachmentId === "string" && (candidate.mediaType === "image/png" || candidate.mediaType === "image/jpeg" || candidate.mediaType === "image/webp" || candidate.mediaType === "image/gif") && typeof candidate.bytes === "number" && typeof candidate.width === "number" && typeof candidate.height === "number";
|
|
4621
|
+
}
|
|
4622
|
+
//#endregion
|
|
2182
4623
|
//#region lib/types/client/index.js
|
|
2183
4624
|
const KEY_REF = {
|
|
2184
4625
|
google: "GEMINI_API_KEY",
|
|
@@ -2242,7 +4683,18 @@ window.__ModuleLoader__.load({
|
|
|
2242
4683
|
download: "下载图片",
|
|
2243
4684
|
openNewTab: "新标签页打开",
|
|
2244
4685
|
copiedImage: "已复制图片",
|
|
2245
|
-
copyFailed: "复制失败"
|
|
4686
|
+
copyFailed: "复制失败",
|
|
4687
|
+
regenerate: "重新生成",
|
|
4688
|
+
regenerateTitle: "重新生成图片",
|
|
4689
|
+
regenerateHint: "如有需要可微调提示词。生成的新图片将替代当前展示,原图依然可在版本中查看。",
|
|
4690
|
+
prompt: "提示词",
|
|
4691
|
+
cancel: "取消",
|
|
4692
|
+
confirmRegenerate: "确认生成",
|
|
4693
|
+
regenerating: "重新生成中…",
|
|
4694
|
+
regenerateFailed: "重新生成失败",
|
|
4695
|
+
versionPrevious: "上一版本",
|
|
4696
|
+
versionNext: "下一版本",
|
|
4697
|
+
versionLabel: "图片版本 {current}/{total}"
|
|
2246
4698
|
},
|
|
2247
4699
|
en: {
|
|
2248
4700
|
title: "Image Generation",
|
|
@@ -2299,7 +4751,18 @@ window.__ModuleLoader__.load({
|
|
|
2299
4751
|
download: "Download Image",
|
|
2300
4752
|
openNewTab: "Open in new tab",
|
|
2301
4753
|
copiedImage: "Image copied",
|
|
2302
|
-
copyFailed: "Copy failed"
|
|
4754
|
+
copyFailed: "Copy failed",
|
|
4755
|
+
regenerate: "Regenerate",
|
|
4756
|
+
regenerateTitle: "Regenerate image",
|
|
4757
|
+
regenerateHint: "Edit the prompt if needed. The new image replaces this view while the original remains available.",
|
|
4758
|
+
prompt: "Prompt",
|
|
4759
|
+
cancel: "Cancel",
|
|
4760
|
+
confirmRegenerate: "Regenerate",
|
|
4761
|
+
regenerating: "Regenerating…",
|
|
4762
|
+
regenerateFailed: "Regeneration failed",
|
|
4763
|
+
versionPrevious: "Previous version",
|
|
4764
|
+
versionNext: "Next version",
|
|
4765
|
+
versionLabel: "Image version {current}/{total}"
|
|
2303
4766
|
}
|
|
2304
4767
|
};
|
|
2305
4768
|
const STYLE = `
|
|
@@ -2345,14 +4808,39 @@ window.__ModuleLoader__.load({
|
|
|
2345
4808
|
.dsh-ig-result{display:grid;gap:10px;max-width:520px}
|
|
2346
4809
|
.dsh-ig-promoted-results{display:grid;gap:16px}
|
|
2347
4810
|
.dsh-ig-result-title{font-size:14px;font-weight:600}
|
|
2348
|
-
.dsh-ig-container{position:relative;display:inline-block;width:fit-content;max-width:100%;justify-self:start;border-radius:12px;overflow:hidden;line-height:0}
|
|
2349
|
-
.dsh-ig-container:hover .dsh-ig-toolbar{opacity:1;pointer-events:auto}
|
|
2350
|
-
.dsh-ig-toolbar{position:absolute;top:8px;left:8px;display:flex;align-items:center;gap:5px;padding:3px 5px;border-radius:8px;background:rgba(0,0,0,0.65);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);opacity:0;pointer-events:none;transition:opacity .18s ease;z-index:
|
|
4811
|
+
.dsh-ig-container{position:relative;display:inline-block;width:fit-content;max-width:100%;justify-self:start;border-radius:12px;overflow:hidden;line-height:0;isolation:isolate}
|
|
4812
|
+
.dsh-ig-container:hover .dsh-ig-toolbar,.dsh-ig-container:focus-within .dsh-ig-toolbar{opacity:1;pointer-events:auto}
|
|
4813
|
+
.dsh-ig-toolbar{position:absolute;top:8px;left:8px;display:flex;align-items:center;gap:5px;padding:3px 5px;border-radius:8px;background:rgba(0,0,0,0.65);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);opacity:0;pointer-events:none;transition:opacity .18s ease;z-index:2;line-height:1}
|
|
2351
4814
|
.dsh-ig-tool-btn{appearance:none;border:0;background:transparent;color:#fff;padding:5px;border-radius:6px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background .15s,color .15s}
|
|
2352
4815
|
.dsh-ig-tool-btn:hover{background:rgba(255,255,255,0.25)}
|
|
2353
4816
|
.dsh-ig-tool-btn-danger:hover{background:rgba(239,68,68,0.75)!important;color:#fff!important}
|
|
2354
|
-
.dsh-ig-toast{position:absolute;top:100%;left:0;margin-top:5px;padding:3px 8px;border-radius:6px;background:rgba(0,0,0,0.85);color:#fff;font-size:11px;white-space:nowrap;pointer-events:none;z-index:
|
|
4817
|
+
.dsh-ig-toast{position:absolute;top:100%;left:0;margin-top:5px;padding:3px 8px;border-radius:6px;background:rgba(0,0,0,0.85);color:#fff;font-size:11px;white-space:nowrap;pointer-events:none;z-index:4}
|
|
2355
4818
|
.dsh-ig-image{display:block;max-width:100%;max-height:520px;border-radius:12px;background:#f2f3f5;cursor:pointer}
|
|
4819
|
+
.dsh-ig-version-nav{position:absolute;right:8px;bottom:8px;display:flex;align-items:center;gap:2px;padding:3px;border-radius:999px;background:rgba(15,23,42,.72);color:#fff;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);font-size:11px;line-height:1;z-index:2}
|
|
4820
|
+
.dsh-ig-version-nav button{appearance:none;border:0;background:transparent;color:inherit;width:25px;height:25px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;font-size:17px;line-height:1}
|
|
4821
|
+
.dsh-ig-version-nav button:hover:not(:disabled){background:rgba(255,255,255,.2)}
|
|
4822
|
+
.dsh-ig-version-nav button:disabled{opacity:.3;cursor:default}
|
|
4823
|
+
.dsh-ig-version-count{min-width:34px;text-align:center;font-variant-numeric:tabular-nums}
|
|
4824
|
+
.dsh-ig-regenerate-overlay{position:absolute;inset:0;background:rgba(15,23,42,0.52);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;color:#fff;z-index:3;animation:dsh-ig-fade .18s ease-out;line-height:1.4;border-radius:12px}
|
|
4825
|
+
.dsh-ig-regenerate-spinner{width:28px;height:28px;border:3px solid rgba(255,255,255,0.25);border-top-color:#fff;border-radius:50%;animation:dsh-ig-spin .8s linear infinite}
|
|
4826
|
+
.dsh-ig-regenerate-overlay-text{font-size:12.5px;font-weight:550;color:#fff;letter-spacing:0.2px;text-shadow:0 1px 2px rgba(0,0,0,0.4)}
|
|
4827
|
+
.dsh-ig-regenerate-overlay-cancel{appearance:none;border:1px solid rgba(255,255,255,0.4);border-radius:6px;background:rgba(255,255,255,0.15);color:#fff;padding:3px 12px;font-size:11.5px;cursor:pointer;transition:background .15s}
|
|
4828
|
+
.dsh-ig-regenerate-overlay-cancel:hover{background:rgba(255,255,255,0.3)}
|
|
4829
|
+
@keyframes dsh-ig-spin{to{transform:rotate(360deg)}}
|
|
4830
|
+
.dsh-ig-regenerate-backdrop{position:fixed;inset:0;z-index:100000;display:flex;align-items:center;justify-content:center;padding:18px;background:rgba(11,17,29,.6);backdrop-filter:blur(5px);-webkit-backdrop-filter:blur(5px);animation:dsh-ig-fade .15s ease-out}
|
|
4831
|
+
.dsh-ig-regenerate-dialog{width:min(520px,100%);border:1px solid var(--dsw-alias-border-l2,#dfe3ea);border-radius:14px;background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#172033);box-shadow:0 24px 72px rgba(11,17,29,.28);padding:20px;box-sizing:border-box;line-height:1.4}
|
|
4832
|
+
.dsh-ig-regenerate-dialog h3{margin:0;font-size:16px;font-weight:650}
|
|
4833
|
+
.dsh-ig-regenerate-dialog p{margin:7px 0 16px;color:var(--dsw-alias-label-tertiary,#737d8f);font-size:12.5px;line-height:1.55}
|
|
4834
|
+
.dsh-ig-regenerate-dialog label{display:grid;gap:7px;font-size:12.5px;font-weight:600}
|
|
4835
|
+
.dsh-ig-regenerate-dialog textarea{box-sizing:border-box;width:100%;min-height:132px;resize:vertical;border:1px solid var(--dsw-alias-border-l2,#d7dce5);border-radius:9px;padding:11px 12px;background:var(--dsw-alias-bg-layer-2,#fff);color:inherit;font:inherit;font-size:13px;line-height:1.55;outline:none}
|
|
4836
|
+
.dsh-ig-regenerate-dialog textarea:focus{border-color:var(--dsw-alias-brand-primary,#4c78ff);box-shadow:0 0 0 3px rgba(76,120,255,.12)}
|
|
4837
|
+
.dsh-ig-regenerate-error{margin-top:10px!important;color:var(--dsw-alias-label-error,#d33)!important}
|
|
4838
|
+
.dsh-ig-regenerate-actions{display:flex;justify-content:flex-end;gap:9px;margin-top:16px}
|
|
4839
|
+
.dsh-ig-regenerate-actions button{height:35px;padding:0 14px;border-radius:7px;font:inherit;font-size:13px;font-weight:550;cursor:pointer}
|
|
4840
|
+
.dsh-ig-regenerate-cancel{border:1px solid var(--dsw-alias-border-l2,#d7dce5);background:transparent;color:inherit}
|
|
4841
|
+
.dsh-ig-regenerate-confirm{border:1px solid var(--dsw-alias-brand-primary,#3569ed);background:var(--dsw-alias-brand-primary,#3569ed);color:#fff}
|
|
4842
|
+
.dsh-ig-regenerate-actions button:disabled{opacity:.5;cursor:default}
|
|
4843
|
+
@media(hover:none){.dsh-ig-container .dsh-ig-toolbar{opacity:1;pointer-events:auto}}
|
|
2356
4844
|
@keyframes dsh-ig-fade{from{opacity:0}to{opacity:1}}
|
|
2357
4845
|
.dsh-ig-error{color:var(--dsw-alias-label-error,#d33);font-size:13px}
|
|
2358
4846
|
.dsh-ig-loading{color:var(--dsw-alias-label-tertiary,#7b818b);font-size:13px}
|
|
@@ -2483,7 +4971,7 @@ window.__ModuleLoader__.load({
|
|
|
2483
4971
|
.dsh-ig-batch-btn-exit:hover{background:rgba(255,255,255,0.1);color:#fff}
|
|
2484
4972
|
|
|
2485
4973
|
/* 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:
|
|
4974
|
+
.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:100005;display:flex;align-items:center;justify-content:center;padding:16px;animation:dsh-ig-fade .15s ease-out}
|
|
2487
4975
|
.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
4976
|
@keyframes dsh-ig-scale-up{from{transform:scale(0.95);opacity:0}to{transform:scale(1);opacity:1}}
|
|
2489
4977
|
.dsh-ig-modal-header{display:flex;align-items:flex-start;gap:12px;margin-bottom:12px}
|
|
@@ -2499,9 +4987,26 @@ window.__ModuleLoader__.load({
|
|
|
2499
4987
|
.dsh-ig-modal-btn-cancel:hover{background:var(--dsw-alias-bg-layer-3,rgba(255,255,255,0.08))}
|
|
2500
4988
|
.dsh-ig-modal-btn-danger{background:#dc2626;border:1px solid #dc2626;color:#fff}
|
|
2501
4989
|
.dsh-ig-modal-btn-danger:hover{background:#b91c1c;border-color:#b91c1c}
|
|
4990
|
+
.dsh-ig-modal-btn-primary{background:linear-gradient(135deg,#3b82f6,#2563eb);border:1px solid #2563eb;color:#fff;display:inline-flex;align-items:center;gap:6px}
|
|
4991
|
+
.dsh-ig-modal-btn-primary:hover:not(:disabled){background:linear-gradient(135deg,#2563eb,#1d4ed8)}
|
|
4992
|
+
.dsh-ig-modal-btn-primary:disabled{opacity:0.5;cursor:not-allowed}
|
|
4993
|
+
.dsh-ig-regenerate-modal-box{max-width:520px}
|
|
4994
|
+
.dsh-ig-regenerate-modal-icon{width:36px;height:36px;border-radius:50%;background:rgba(59,130,246,0.15);color:#3b82f6;display:flex;align-items:center;justify-content:center;flex-shrink:0}
|
|
4995
|
+
.dsh-ig-regenerate-modal-title-wrap{display:flex;flex-direction:column;gap:4px}
|
|
4996
|
+
.dsh-ig-regenerate-modal-meta{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
|
4997
|
+
.dsh-ig-regenerate-modal-textarea{width:100%;box-sizing:border-box;background:var(--dsw-alias-bg-layer-2,rgba(0,0,0,0.25));border:1px solid var(--dsw-alias-border-subtle,rgba(255,255,255,0.15));border-radius:8px;padding:10px 12px;font-size:13px;line-height:1.5;color:var(--dsw-alias-label-primary,#fff);resize:vertical;font-family:inherit;outline:none;transition:border-color .15s,box-shadow .15s}
|
|
4998
|
+
.dsh-ig-regenerate-modal-textarea:focus{border-color:#3b82f6;box-shadow:0 0 0 2px rgba(59,130,246,0.2)}
|
|
4999
|
+
.dsh-ig-lightbox-btn-regenerate:hover{color:#60a5fa!important;border-color:rgba(96,165,250,0.5)!important;background:rgba(96,165,250,0.12)!important}
|
|
5000
|
+
.dsh-ig-lightbox-generating-indicator{display:inline-flex;align-items:center;gap:8px;padding:4px 12px;background:rgba(37,99,235,0.2);border:1px solid rgba(59,130,246,0.5);border-radius:20px;font-size:12px;color:#93c5fd;animation:dsh-ig-fade .15s ease-out}
|
|
5001
|
+
.dsh-ig-lightbox-spinner-sm{width:12px;height:12px;border:2px solid rgba(147,197,253,0.3);border-top-color:#93c5fd;border-radius:50%;animation:dsh-ig-spin .8s linear infinite;flex-shrink:0}
|
|
5002
|
+
.dsh-ig-lightbox-abort-btn{appearance:none;background:transparent;border:0;color:#fca5a5;font-size:11.5px;cursor:pointer;padding:0 4px;margin-left:4px;text-decoration:underline;text-underline-offset:2px}
|
|
5003
|
+
.dsh-ig-lightbox-abort-btn:hover{color:#ef4444}
|
|
2502
5004
|
|
|
2503
|
-
/* Hide floating chat composer when gallery page is active */
|
|
5005
|
+
/* Hide floating chat composer and width handles when gallery page is active */
|
|
2504
5006
|
[data-conversation-scroll]:has(.dsh-ig-gallery-page) [data-composer-seat]{display:none!important}
|
|
5007
|
+
:has(> [data-conversation-scroll]:has(.dsh-ig-gallery-page)) > [class*="widthHandle"],
|
|
5008
|
+
:has(.dsh-ig-gallery-page) [class*="widthHandle"],
|
|
5009
|
+
.root:has(.dsh-ig-gallery-page) [class*="widthHandle"]{display:none!important}
|
|
2505
5010
|
`;
|
|
2506
5011
|
/** Required browser services. */
|
|
2507
5012
|
const inject = [
|
|
@@ -2521,7 +5026,7 @@ window.__ModuleLoader__.load({
|
|
|
2521
5026
|
ctx.effect(() => {
|
|
2522
5027
|
const style = document.createElement("style");
|
|
2523
5028
|
style.dataset.plugin = "dsh-image-gen";
|
|
2524
|
-
style.textContent = STYLE
|
|
5029
|
+
style.textContent = `${STYLE}\n${STUDIO_STYLE}`;
|
|
2525
5030
|
document.head.appendChild(style);
|
|
2526
5031
|
return () => {
|
|
2527
5032
|
style.remove();
|
|
@@ -3106,7 +5611,8 @@ window.__ModuleLoader__.load({
|
|
|
3106
5611
|
if (props.promoted && result !== void 0) return (0, react_jsx_runtime.jsx)(PromotedResultNotice, { locale: props.locale });
|
|
3107
5612
|
return (0, react_jsx_runtime.jsx)(ImageResultCard, {
|
|
3108
5613
|
result,
|
|
3109
|
-
locale: props.locale
|
|
5614
|
+
locale: props.locale,
|
|
5615
|
+
sessionId: props.sessionId
|
|
3110
5616
|
});
|
|
3111
5617
|
}
|
|
3112
5618
|
/** Render modern image artifacts as final conversation output instead of Tool process content. */
|
|
@@ -3115,7 +5621,8 @@ window.__ModuleLoader__.load({
|
|
|
3115
5621
|
className: "dsh-ig-promoted-results",
|
|
3116
5622
|
children: props.node.data.results.map((result) => (0, react_jsx_runtime.jsx)(ImageResultCard, {
|
|
3117
5623
|
result,
|
|
3118
|
-
locale: props.locale
|
|
5624
|
+
locale: props.locale,
|
|
5625
|
+
sessionId: props.sessionId
|
|
3119
5626
|
}, result.attachment.attachmentId))
|
|
3120
5627
|
});
|
|
3121
5628
|
}
|
|
@@ -3126,14 +5633,31 @@ window.__ModuleLoader__.load({
|
|
|
3126
5633
|
children: DICT[lang].resultShown
|
|
3127
5634
|
});
|
|
3128
5635
|
}
|
|
3129
|
-
function ImageResultCard({ result, locale }) {
|
|
5636
|
+
function ImageResultCard({ result, locale, sessionId }) {
|
|
3130
5637
|
const attachment = result?.attachment;
|
|
5638
|
+
const originId = attachment?.attachmentId;
|
|
5639
|
+
const [revisionChain, setRevisionChain] = (0, react.useState)(() => loadConversationImageRevisionChain(String(originId ?? "")));
|
|
5640
|
+
const selectedRevision = revisionChain.revisions[revisionChain.currentIndex - 1];
|
|
5641
|
+
const activeAttachment = selectedRevision?.attachment ?? attachment;
|
|
5642
|
+
const activeResult = selectedRevision !== void 0 ? {
|
|
5643
|
+
prompt: selectedRevision.prompt,
|
|
5644
|
+
provider: selectedRevision.provider,
|
|
5645
|
+
model: selectedRevision.model,
|
|
5646
|
+
output: selectedRevision.output,
|
|
5647
|
+
createdAt: selectedRevision.createdAt
|
|
5648
|
+
} : result;
|
|
3131
5649
|
const savedTo = result?.savedTo;
|
|
3132
5650
|
const [url, setUrl] = (0, react.useState)();
|
|
3133
5651
|
const [blob, setBlob] = (0, react.useState)();
|
|
3134
5652
|
const [error, setError] = (0, react.useState)();
|
|
3135
5653
|
const [previewOpen, setPreviewOpen] = (0, react.useState)(false);
|
|
3136
5654
|
const [toast, setToast] = (0, react.useState)();
|
|
5655
|
+
const [regenerateOpen, setRegenerateOpen] = (0, react.useState)(false);
|
|
5656
|
+
const [regeneratePrompt, setRegeneratePrompt] = (0, react.useState)("");
|
|
5657
|
+
const [regenerateError, setRegenerateError] = (0, react.useState)();
|
|
5658
|
+
const [isRegenerating, setIsRegenerating] = (0, react.useState)(false);
|
|
5659
|
+
const regenerateControllerRef = (0, react.useRef)();
|
|
5660
|
+
const regenerateTextareaRef = (0, react.useRef)(null);
|
|
3137
5661
|
const lang = usePluginLanguage(locale);
|
|
3138
5662
|
const t = (keyName, params) => {
|
|
3139
5663
|
let text = (lang === "en" ? DICT.en : DICT.zh)[keyName] || DICT.zh[keyName] || keyName;
|
|
@@ -3150,9 +5674,36 @@ window.__ModuleLoader__.load({
|
|
|
3150
5674
|
model: result.model,
|
|
3151
5675
|
output: result.output,
|
|
3152
5676
|
...result.savedTo ? { savedTo: result.savedTo } : {},
|
|
3153
|
-
...result.seed !== void 0 ? { seed: result.seed } : {}
|
|
5677
|
+
...result.seed !== void 0 ? { seed: result.seed } : {},
|
|
5678
|
+
...sessionId ? { sessionId } : {}
|
|
3154
5679
|
});
|
|
3155
5680
|
}, [result?.attachment.attachmentId]);
|
|
5681
|
+
(0, react.useEffect)(() => {
|
|
5682
|
+
if (!originId) return;
|
|
5683
|
+
regenerateControllerRef.current?.abort();
|
|
5684
|
+
regenerateControllerRef.current = void 0;
|
|
5685
|
+
setIsRegenerating(false);
|
|
5686
|
+
setRegenerateOpen(false);
|
|
5687
|
+
setRevisionChain(loadConversationImageRevisionChain(String(originId)));
|
|
5688
|
+
}, [originId]);
|
|
5689
|
+
(0, react.useEffect)(() => () => {
|
|
5690
|
+
regenerateControllerRef.current?.abort();
|
|
5691
|
+
}, []);
|
|
5692
|
+
(0, react.useEffect)(() => {
|
|
5693
|
+
if (!regenerateOpen || isRegenerating) return;
|
|
5694
|
+
const frame = requestAnimationFrame(() => {
|
|
5695
|
+
regenerateTextareaRef.current?.focus();
|
|
5696
|
+
regenerateTextareaRef.current?.select();
|
|
5697
|
+
});
|
|
5698
|
+
const onKeyDown = (event) => {
|
|
5699
|
+
if (event.key === "Escape") setRegenerateOpen(false);
|
|
5700
|
+
};
|
|
5701
|
+
window.addEventListener("keydown", onKeyDown);
|
|
5702
|
+
return () => {
|
|
5703
|
+
cancelAnimationFrame(frame);
|
|
5704
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
5705
|
+
};
|
|
5706
|
+
}, [regenerateOpen, isRegenerating]);
|
|
3156
5707
|
(0, react.useEffect)(() => {
|
|
3157
5708
|
if (!previewOpen) return;
|
|
3158
5709
|
const onKeyDown = (e) => {
|
|
@@ -3163,30 +5714,34 @@ window.__ModuleLoader__.load({
|
|
|
3163
5714
|
window.removeEventListener("keydown", onKeyDown);
|
|
3164
5715
|
};
|
|
3165
5716
|
}, [previewOpen]);
|
|
5717
|
+
const currentUrlRef = (0, react.useRef)();
|
|
3166
5718
|
(0, react.useEffect)(() => {
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
if (
|
|
5719
|
+
return () => {
|
|
5720
|
+
if (currentUrlRef.current !== void 0) {
|
|
5721
|
+
URL.revokeObjectURL(currentUrlRef.current);
|
|
5722
|
+
currentUrlRef.current = void 0;
|
|
5723
|
+
}
|
|
5724
|
+
};
|
|
5725
|
+
}, []);
|
|
5726
|
+
(0, react.useEffect)(() => {
|
|
5727
|
+
if (activeAttachment === void 0) return;
|
|
5728
|
+
let canceled = false;
|
|
5729
|
+
fetchAttachmentBlob(activeAttachment).then((resBlob) => {
|
|
5730
|
+
if (canceled) return;
|
|
3179
5731
|
setBlob(resBlob);
|
|
3180
|
-
|
|
3181
|
-
|
|
5732
|
+
setError(void 0);
|
|
5733
|
+
const nextUrl = URL.createObjectURL(resBlob);
|
|
5734
|
+
if (currentUrlRef.current !== void 0) URL.revokeObjectURL(currentUrlRef.current);
|
|
5735
|
+
currentUrlRef.current = nextUrl;
|
|
5736
|
+
setUrl(nextUrl);
|
|
3182
5737
|
}).catch((cause) => {
|
|
3183
|
-
if (
|
|
5738
|
+
if (canceled) return;
|
|
5739
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
3184
5740
|
});
|
|
3185
5741
|
return () => {
|
|
3186
|
-
|
|
3187
|
-
if (objectUrl !== void 0) URL.revokeObjectURL(objectUrl);
|
|
5742
|
+
canceled = true;
|
|
3188
5743
|
};
|
|
3189
|
-
}, [
|
|
5744
|
+
}, [activeAttachment?.attachmentId]);
|
|
3190
5745
|
const copy = async (e) => {
|
|
3191
5746
|
e.stopPropagation();
|
|
3192
5747
|
if (!blob) return;
|
|
@@ -3201,7 +5756,7 @@ window.__ModuleLoader__.load({
|
|
|
3201
5756
|
if (!url) return;
|
|
3202
5757
|
const a = document.createElement("a");
|
|
3203
5758
|
a.href = url;
|
|
3204
|
-
a.download =
|
|
5759
|
+
a.download = activeAttachment?.name || `dsh-image-${Date.now()}.png`;
|
|
3205
5760
|
document.body.appendChild(a);
|
|
3206
5761
|
a.click();
|
|
3207
5762
|
document.body.removeChild(a);
|
|
@@ -3211,6 +5766,93 @@ window.__ModuleLoader__.load({
|
|
|
3211
5766
|
if (!url) return;
|
|
3212
5767
|
window.open(url, "_blank", "noopener,noreferrer");
|
|
3213
5768
|
};
|
|
5769
|
+
const openRegenerate = (event) => {
|
|
5770
|
+
event.stopPropagation();
|
|
5771
|
+
if (activeResult === void 0 || isRegenerating) return;
|
|
5772
|
+
setRegeneratePrompt(activeResult.prompt);
|
|
5773
|
+
setRegenerateError(void 0);
|
|
5774
|
+
setRegenerateOpen(true);
|
|
5775
|
+
};
|
|
5776
|
+
const cancelRegenerate = () => {
|
|
5777
|
+
regenerateControllerRef.current?.abort();
|
|
5778
|
+
regenerateControllerRef.current = void 0;
|
|
5779
|
+
setIsRegenerating(false);
|
|
5780
|
+
setRegenerateOpen(false);
|
|
5781
|
+
setRegenerateError(void 0);
|
|
5782
|
+
};
|
|
5783
|
+
const regenerate = async () => {
|
|
5784
|
+
if (activeResult === void 0 || !originId || isRegenerating || regeneratePrompt.trim().length === 0) return;
|
|
5785
|
+
setIsRegenerating(true);
|
|
5786
|
+
setRegenerateError(void 0);
|
|
5787
|
+
setRegenerateOpen(false);
|
|
5788
|
+
const controller = new AbortController();
|
|
5789
|
+
regenerateControllerRef.current = controller;
|
|
5790
|
+
try {
|
|
5791
|
+
const request = conversationRegenerateRequest(activeResult, regeneratePrompt, selectedRevision === void 0 ? void 0 : {
|
|
5792
|
+
ratio: selectedRevision.ratio,
|
|
5793
|
+
quality: selectedRevision.quality
|
|
5794
|
+
});
|
|
5795
|
+
const response = await fetch(STUDIO_ROUTE, {
|
|
5796
|
+
method: "POST",
|
|
5797
|
+
credentials: "same-origin",
|
|
5798
|
+
signal: controller.signal,
|
|
5799
|
+
headers: { "content-type": "application/json" },
|
|
5800
|
+
body: JSON.stringify(request)
|
|
5801
|
+
});
|
|
5802
|
+
const payload = await response.json().catch(() => null);
|
|
5803
|
+
if (!response.ok || payload === null || !("attachment" in payload)) throw new Error(payload && "error" in payload && payload.error ? payload.error : t("regenerateFailed"));
|
|
5804
|
+
const revision = {
|
|
5805
|
+
attachment: payload.attachment,
|
|
5806
|
+
prompt: payload.prompt,
|
|
5807
|
+
provider: payload.provider,
|
|
5808
|
+
model: payload.model,
|
|
5809
|
+
output: payload.output,
|
|
5810
|
+
createdAt: payload.createdAt,
|
|
5811
|
+
ratio: request.ratio,
|
|
5812
|
+
quality: request.quality
|
|
5813
|
+
};
|
|
5814
|
+
await saveGalleryItem({
|
|
5815
|
+
id: String(revision.attachment.attachmentId),
|
|
5816
|
+
attachment: revision.attachment,
|
|
5817
|
+
prompt: revision.prompt,
|
|
5818
|
+
provider: revision.provider,
|
|
5819
|
+
model: revision.model,
|
|
5820
|
+
createdAt: revision.createdAt,
|
|
5821
|
+
aspectRatio: revision.ratio,
|
|
5822
|
+
imageSize: revision.quality,
|
|
5823
|
+
output: revision.output,
|
|
5824
|
+
...savedTo ? { savedTo } : {},
|
|
5825
|
+
...sessionId ? { sessionId } : {}
|
|
5826
|
+
});
|
|
5827
|
+
const next = appendConversationImageRevision(String(originId), revision);
|
|
5828
|
+
setRevisionChain(next);
|
|
5829
|
+
setToast(t("regenerate"));
|
|
5830
|
+
setTimeout(() => {
|
|
5831
|
+
setToast(void 0);
|
|
5832
|
+
}, 2e3);
|
|
5833
|
+
} catch (cause) {
|
|
5834
|
+
if (!controller.signal.aborted) {
|
|
5835
|
+
const errMsg = cause instanceof Error ? cause.message : String(cause);
|
|
5836
|
+
setError(errMsg);
|
|
5837
|
+
setToast(errMsg);
|
|
5838
|
+
setTimeout(() => {
|
|
5839
|
+
setToast(void 0);
|
|
5840
|
+
}, 3500);
|
|
5841
|
+
}
|
|
5842
|
+
} finally {
|
|
5843
|
+
if (regenerateControllerRef.current === controller) {
|
|
5844
|
+
regenerateControllerRef.current = void 0;
|
|
5845
|
+
setIsRegenerating(false);
|
|
5846
|
+
}
|
|
5847
|
+
}
|
|
5848
|
+
};
|
|
5849
|
+
const selectVersion = (index) => {
|
|
5850
|
+
if (!originId || isRegenerating) return;
|
|
5851
|
+
setRevisionChain(selectConversationImageRevision(String(originId), index));
|
|
5852
|
+
};
|
|
5853
|
+
const canRegenerate = activeResult !== void 0 && CLOUD_IMAGE_PROVIDERS.includes(activeResult.provider) && activeResult.model.trim().length > 0;
|
|
5854
|
+
const versionTotal = revisionChain.revisions.length + 1;
|
|
5855
|
+
const versionCurrent = revisionChain.currentIndex + 1;
|
|
3214
5856
|
if (attachment === void 0) return (0, react_jsx_runtime.jsx)("div", {
|
|
3215
5857
|
className: "dsh-ig-loading",
|
|
3216
5858
|
children: t("generating")
|
|
@@ -3241,100 +5883,174 @@ window.__ModuleLoader__.load({
|
|
|
3241
5883
|
}) : null,
|
|
3242
5884
|
url !== void 0 ? (0, react_jsx_runtime.jsxs)("div", {
|
|
3243
5885
|
className: "dsh-ig-container",
|
|
3244
|
-
children: [
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
className: "dsh-ig-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
},
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
stroke: "currentColor",
|
|
3267
|
-
strokeWidth: "2",
|
|
3268
|
-
strokeLinecap: "round",
|
|
3269
|
-
strokeLinejoin: "round",
|
|
3270
|
-
children: [(0, react_jsx_runtime.jsx)("rect", {
|
|
3271
|
-
x: "9",
|
|
3272
|
-
y: "9",
|
|
3273
|
-
width: "13",
|
|
3274
|
-
height: "13",
|
|
3275
|
-
rx: "2",
|
|
3276
|
-
ry: "2"
|
|
3277
|
-
}), (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" })]
|
|
3278
|
-
})
|
|
3279
|
-
}),
|
|
3280
|
-
(0, react_jsx_runtime.jsx)("button", {
|
|
3281
|
-
type: "button",
|
|
3282
|
-
className: "dsh-ig-tool-btn",
|
|
3283
|
-
title: t("download"),
|
|
3284
|
-
onClick: download,
|
|
3285
|
-
children: (0, react_jsx_runtime.jsxs)("svg", {
|
|
3286
|
-
width: "15",
|
|
3287
|
-
height: "15",
|
|
3288
|
-
viewBox: "0 0 24 24",
|
|
3289
|
-
fill: "none",
|
|
3290
|
-
stroke: "currentColor",
|
|
3291
|
-
strokeWidth: "2",
|
|
3292
|
-
strokeLinecap: "round",
|
|
3293
|
-
strokeLinejoin: "round",
|
|
3294
|
-
children: [
|
|
3295
|
-
(0, react_jsx_runtime.jsx)("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
3296
|
-
(0, react_jsx_runtime.jsx)("polyline", { points: "7 10 12 15 17 10" }),
|
|
3297
|
-
(0, react_jsx_runtime.jsx)("line", {
|
|
3298
|
-
x1: "12",
|
|
3299
|
-
y1: "15",
|
|
3300
|
-
x2: "12",
|
|
3301
|
-
y2: "3"
|
|
3302
|
-
})
|
|
3303
|
-
]
|
|
5886
|
+
children: [
|
|
5887
|
+
(0, react_jsx_runtime.jsx)("img", {
|
|
5888
|
+
className: "dsh-ig-image",
|
|
5889
|
+
src: url,
|
|
5890
|
+
alt: activeAttachment?.name ?? "Generated image",
|
|
5891
|
+
onClick: () => {
|
|
5892
|
+
if (!isRegenerating) setPreviewOpen(true);
|
|
5893
|
+
}
|
|
5894
|
+
}),
|
|
5895
|
+
isRegenerating ? (0, react_jsx_runtime.jsxs)("div", {
|
|
5896
|
+
className: "dsh-ig-regenerate-overlay",
|
|
5897
|
+
children: [
|
|
5898
|
+
(0, react_jsx_runtime.jsx)("div", { className: "dsh-ig-regenerate-spinner" }),
|
|
5899
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
5900
|
+
className: "dsh-ig-regenerate-overlay-text",
|
|
5901
|
+
children: t("regenerating")
|
|
5902
|
+
}),
|
|
5903
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
5904
|
+
type: "button",
|
|
5905
|
+
className: "dsh-ig-regenerate-overlay-cancel",
|
|
5906
|
+
onClick: cancelRegenerate,
|
|
5907
|
+
children: t("cancel")
|
|
3304
5908
|
})
|
|
5909
|
+
]
|
|
5910
|
+
}) : null,
|
|
5911
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
5912
|
+
className: "dsh-ig-toolbar",
|
|
5913
|
+
children: [
|
|
5914
|
+
canRegenerate ? (0, react_jsx_runtime.jsx)("button", {
|
|
5915
|
+
type: "button",
|
|
5916
|
+
className: "dsh-ig-tool-btn",
|
|
5917
|
+
disabled: isRegenerating,
|
|
5918
|
+
title: isRegenerating ? t("regenerating") : t("regenerate"),
|
|
5919
|
+
onClick: openRegenerate,
|
|
5920
|
+
children: (0, react_jsx_runtime.jsxs)("svg", {
|
|
5921
|
+
width: "15",
|
|
5922
|
+
height: "15",
|
|
5923
|
+
viewBox: "0 0 24 24",
|
|
5924
|
+
fill: "none",
|
|
5925
|
+
stroke: "currentColor",
|
|
5926
|
+
strokeWidth: "2",
|
|
5927
|
+
strokeLinecap: "round",
|
|
5928
|
+
strokeLinejoin: "round",
|
|
5929
|
+
children: [(0, react_jsx_runtime.jsx)("path", { d: "M20 11a8.1 8.1 0 0 0-15.5-2M4 4v5h5" }), (0, react_jsx_runtime.jsx)("path", { d: "M4 13a8.1 8.1 0 0 0 15.5 2M20 20v-5h-5" })]
|
|
5930
|
+
})
|
|
5931
|
+
}) : null,
|
|
5932
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
5933
|
+
type: "button",
|
|
5934
|
+
className: "dsh-ig-tool-btn",
|
|
5935
|
+
title: t("copyImg"),
|
|
5936
|
+
onClick: (e) => {
|
|
5937
|
+
copy(e);
|
|
5938
|
+
},
|
|
5939
|
+
children: (0, react_jsx_runtime.jsxs)("svg", {
|
|
5940
|
+
width: "15",
|
|
5941
|
+
height: "15",
|
|
5942
|
+
viewBox: "0 0 24 24",
|
|
5943
|
+
fill: "none",
|
|
5944
|
+
stroke: "currentColor",
|
|
5945
|
+
strokeWidth: "2",
|
|
5946
|
+
strokeLinecap: "round",
|
|
5947
|
+
strokeLinejoin: "round",
|
|
5948
|
+
children: [(0, react_jsx_runtime.jsx)("rect", {
|
|
5949
|
+
x: "9",
|
|
5950
|
+
y: "9",
|
|
5951
|
+
width: "13",
|
|
5952
|
+
height: "13",
|
|
5953
|
+
rx: "2",
|
|
5954
|
+
ry: "2"
|
|
5955
|
+
}), (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" })]
|
|
5956
|
+
})
|
|
5957
|
+
}),
|
|
5958
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
5959
|
+
type: "button",
|
|
5960
|
+
className: "dsh-ig-tool-btn",
|
|
5961
|
+
title: t("download"),
|
|
5962
|
+
onClick: download,
|
|
5963
|
+
children: (0, react_jsx_runtime.jsxs)("svg", {
|
|
5964
|
+
width: "15",
|
|
5965
|
+
height: "15",
|
|
5966
|
+
viewBox: "0 0 24 24",
|
|
5967
|
+
fill: "none",
|
|
5968
|
+
stroke: "currentColor",
|
|
5969
|
+
strokeWidth: "2",
|
|
5970
|
+
strokeLinecap: "round",
|
|
5971
|
+
strokeLinejoin: "round",
|
|
5972
|
+
children: [
|
|
5973
|
+
(0, react_jsx_runtime.jsx)("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
5974
|
+
(0, react_jsx_runtime.jsx)("polyline", { points: "7 10 12 15 17 10" }),
|
|
5975
|
+
(0, react_jsx_runtime.jsx)("line", {
|
|
5976
|
+
x1: "12",
|
|
5977
|
+
y1: "15",
|
|
5978
|
+
x2: "12",
|
|
5979
|
+
y2: "3"
|
|
5980
|
+
})
|
|
5981
|
+
]
|
|
5982
|
+
})
|
|
5983
|
+
}),
|
|
5984
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
5985
|
+
type: "button",
|
|
5986
|
+
className: "dsh-ig-tool-btn",
|
|
5987
|
+
title: t("openNewTab"),
|
|
5988
|
+
onClick: openNewTab,
|
|
5989
|
+
children: (0, react_jsx_runtime.jsxs)("svg", {
|
|
5990
|
+
width: "15",
|
|
5991
|
+
height: "15",
|
|
5992
|
+
viewBox: "0 0 24 24",
|
|
5993
|
+
fill: "none",
|
|
5994
|
+
stroke: "currentColor",
|
|
5995
|
+
strokeWidth: "2",
|
|
5996
|
+
strokeLinecap: "round",
|
|
5997
|
+
strokeLinejoin: "round",
|
|
5998
|
+
children: [
|
|
5999
|
+
(0, react_jsx_runtime.jsx)("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }),
|
|
6000
|
+
(0, react_jsx_runtime.jsx)("polyline", { points: "15 3 21 3 21 9" }),
|
|
6001
|
+
(0, react_jsx_runtime.jsx)("line", {
|
|
6002
|
+
x1: "10",
|
|
6003
|
+
y1: "14",
|
|
6004
|
+
x2: "21",
|
|
6005
|
+
y2: "3"
|
|
6006
|
+
})
|
|
6007
|
+
]
|
|
6008
|
+
})
|
|
6009
|
+
}),
|
|
6010
|
+
toast ? (0, react_jsx_runtime.jsx)("div", {
|
|
6011
|
+
className: "dsh-ig-toast",
|
|
6012
|
+
children: toast
|
|
6013
|
+
}) : null
|
|
6014
|
+
]
|
|
6015
|
+
}),
|
|
6016
|
+
versionTotal > 1 ? (0, react_jsx_runtime.jsxs)("div", {
|
|
6017
|
+
className: "dsh-ig-version-nav",
|
|
6018
|
+
"aria-label": t("versionLabel", {
|
|
6019
|
+
current: String(versionCurrent),
|
|
6020
|
+
total: String(versionTotal)
|
|
3305
6021
|
}),
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
strokeLinejoin: "round",
|
|
6022
|
+
children: [
|
|
6023
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
6024
|
+
type: "button",
|
|
6025
|
+
title: t("versionPrevious"),
|
|
6026
|
+
disabled: revisionChain.currentIndex === 0 || isRegenerating,
|
|
6027
|
+
onClick: (event) => {
|
|
6028
|
+
event.stopPropagation();
|
|
6029
|
+
selectVersion(revisionChain.currentIndex - 1);
|
|
6030
|
+
},
|
|
6031
|
+
children: "‹"
|
|
6032
|
+
}),
|
|
6033
|
+
(0, react_jsx_runtime.jsxs)("span", {
|
|
6034
|
+
className: "dsh-ig-version-count",
|
|
3320
6035
|
children: [
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
x1: "10",
|
|
3325
|
-
y1: "14",
|
|
3326
|
-
x2: "21",
|
|
3327
|
-
y2: "3"
|
|
3328
|
-
})
|
|
6036
|
+
versionCurrent,
|
|
6037
|
+
"/",
|
|
6038
|
+
versionTotal
|
|
3329
6039
|
]
|
|
6040
|
+
}),
|
|
6041
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
6042
|
+
type: "button",
|
|
6043
|
+
title: t("versionNext"),
|
|
6044
|
+
disabled: revisionChain.currentIndex >= revisionChain.revisions.length || isRegenerating,
|
|
6045
|
+
onClick: (event) => {
|
|
6046
|
+
event.stopPropagation();
|
|
6047
|
+
selectVersion(revisionChain.currentIndex + 1);
|
|
6048
|
+
},
|
|
6049
|
+
children: "›"
|
|
3330
6050
|
})
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
children: toast
|
|
3335
|
-
}) : null
|
|
3336
|
-
]
|
|
3337
|
-
})]
|
|
6051
|
+
]
|
|
6052
|
+
}) : null
|
|
6053
|
+
]
|
|
3338
6054
|
}) : null,
|
|
3339
6055
|
previewOpen && url !== void 0 ? (0, react_jsx_runtime.jsx)("div", {
|
|
3340
6056
|
className: "dsh-ig-lightbox-backdrop",
|
|
@@ -3349,9 +6065,63 @@ window.__ModuleLoader__.load({
|
|
|
3349
6065
|
children: (0, react_jsx_runtime.jsx)("img", {
|
|
3350
6066
|
className: "dsh-ig-lightbox-img",
|
|
3351
6067
|
src: url,
|
|
3352
|
-
alt:
|
|
6068
|
+
alt: activeAttachment?.name ?? "Generated image preview"
|
|
3353
6069
|
})
|
|
3354
6070
|
})
|
|
6071
|
+
}) : null,
|
|
6072
|
+
regenerateOpen && activeResult !== void 0 ? (0, react_jsx_runtime.jsx)("div", {
|
|
6073
|
+
className: "dsh-ig-regenerate-backdrop",
|
|
6074
|
+
onMouseDown: (event) => {
|
|
6075
|
+
if (event.target === event.currentTarget && !isRegenerating) setRegenerateOpen(false);
|
|
6076
|
+
},
|
|
6077
|
+
children: (0, react_jsx_runtime.jsxs)("div", {
|
|
6078
|
+
className: "dsh-ig-regenerate-dialog",
|
|
6079
|
+
role: "dialog",
|
|
6080
|
+
"aria-modal": "true",
|
|
6081
|
+
"aria-labelledby": `dsh-ig-regenerate-${String(originId)}`,
|
|
6082
|
+
children: [
|
|
6083
|
+
(0, react_jsx_runtime.jsx)("h3", {
|
|
6084
|
+
id: `dsh-ig-regenerate-${String(originId)}`,
|
|
6085
|
+
children: t("regenerateTitle")
|
|
6086
|
+
}),
|
|
6087
|
+
(0, react_jsx_runtime.jsx)("p", { children: t("regenerateHint") }),
|
|
6088
|
+
(0, react_jsx_runtime.jsxs)("label", { children: [(0, react_jsx_runtime.jsx)("span", { children: t("prompt") }), (0, react_jsx_runtime.jsx)("textarea", {
|
|
6089
|
+
ref: regenerateTextareaRef,
|
|
6090
|
+
value: regeneratePrompt,
|
|
6091
|
+
maxLength: 2e3,
|
|
6092
|
+
disabled: isRegenerating,
|
|
6093
|
+
onChange: (event) => setRegeneratePrompt(event.target.value),
|
|
6094
|
+
onKeyDown: (event) => {
|
|
6095
|
+
if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) {
|
|
6096
|
+
event.preventDefault();
|
|
6097
|
+
regenerate();
|
|
6098
|
+
}
|
|
6099
|
+
}
|
|
6100
|
+
})] }),
|
|
6101
|
+
regenerateError ? (0, react_jsx_runtime.jsx)("p", {
|
|
6102
|
+
className: "dsh-ig-regenerate-error",
|
|
6103
|
+
role: "alert",
|
|
6104
|
+
children: regenerateError
|
|
6105
|
+
}) : null,
|
|
6106
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
6107
|
+
className: "dsh-ig-regenerate-actions",
|
|
6108
|
+
children: [(0, react_jsx_runtime.jsx)("button", {
|
|
6109
|
+
type: "button",
|
|
6110
|
+
className: "dsh-ig-regenerate-cancel",
|
|
6111
|
+
onClick: cancelRegenerate,
|
|
6112
|
+
children: t("cancel")
|
|
6113
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
6114
|
+
type: "button",
|
|
6115
|
+
className: "dsh-ig-regenerate-confirm",
|
|
6116
|
+
disabled: isRegenerating || regeneratePrompt.trim().length === 0,
|
|
6117
|
+
onClick: () => {
|
|
6118
|
+
regenerate();
|
|
6119
|
+
},
|
|
6120
|
+
children: t("confirmRegenerate")
|
|
6121
|
+
})]
|
|
6122
|
+
})
|
|
6123
|
+
]
|
|
6124
|
+
})
|
|
3355
6125
|
}) : null
|
|
3356
6126
|
]
|
|
3357
6127
|
});
|