dsh-image-gen 0.2.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +172 -172
- package/README.md +182 -182
- package/README.zh-CN.md +172 -172
- package/lib/client.js +2809 -300
- package/lib/client.js.map +1 -1
- package/lib/index.js +579 -108
- 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 +0 -1
- package/lib/types/index.d.ts +1 -0
- package/lib/types/shared.d.ts +71 -0
- package/lib/types/studio-route.d.ts +12 -0
- package/lib/types/studio.d.ts +8 -0
- package/lib/types/workspace-save.d.ts +3 -0
- package/package.json +8 -10
- package/skills/install-dsh-image-gen/SKILL.md +22 -22
package/lib/client.js
CHANGED
|
@@ -7,13 +7,21 @@ 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";
|
|
15
16
|
/** Namespace persisted through DSH Settings. */
|
|
16
17
|
const IMAGE_GENERATION_NAMESPACE = "image-generation";
|
|
18
|
+
/** Providers supported by the first browser workbench release. */
|
|
19
|
+
const CLOUD_IMAGE_PROVIDERS = [
|
|
20
|
+
"google",
|
|
21
|
+
"openai",
|
|
22
|
+
"seedream",
|
|
23
|
+
"dashscope"
|
|
24
|
+
];
|
|
17
25
|
/** Default endpoints and base URLs. */
|
|
18
26
|
const DEFAULT_GOOGLE_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/interactions";
|
|
19
27
|
const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
@@ -300,6 +308,29 @@ window.__ModuleLoader__.load({
|
|
|
300
308
|
}
|
|
301
309
|
}
|
|
302
310
|
/**
|
|
311
|
+
* Delete a single gallery record by ID and record a tombstone.
|
|
312
|
+
*/
|
|
313
|
+
async function deleteGalleryItem(id) {
|
|
314
|
+
const db = await getDB();
|
|
315
|
+
const tombstones = await loadTombstones(db);
|
|
316
|
+
await new Promise((resolve, reject) => {
|
|
317
|
+
const tx = db.transaction([STORE_NAME, TOMBSTONE_STORE], "readwrite");
|
|
318
|
+
const store = tx.objectStore(STORE_NAME);
|
|
319
|
+
const tombstoneStore = tx.objectStore(TOMBSTONE_STORE);
|
|
320
|
+
store.delete(id);
|
|
321
|
+
tombstoneStore.put({
|
|
322
|
+
id,
|
|
323
|
+
deletedAt: Date.now()
|
|
324
|
+
});
|
|
325
|
+
tx.oncomplete = () => {
|
|
326
|
+
tombstones.add(id);
|
|
327
|
+
resolve();
|
|
328
|
+
};
|
|
329
|
+
tx.onerror = () => reject(tx.error);
|
|
330
|
+
});
|
|
331
|
+
notifyListeners();
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
303
334
|
* Bulk delete multiple gallery records by IDs and record tombstones in a single transaction.
|
|
304
335
|
*/
|
|
305
336
|
async function bulkDeleteGalleryItems(ids) {
|
|
@@ -327,6 +358,30 @@ window.__ModuleLoader__.load({
|
|
|
327
358
|
});
|
|
328
359
|
notifyListeners();
|
|
329
360
|
}
|
|
361
|
+
/**
|
|
362
|
+
* Standardize path format across Windows and POSIX (lowercase, forward slashes, no trailing slash).
|
|
363
|
+
*/
|
|
364
|
+
function normalizeWorkspacePath(rawPath) {
|
|
365
|
+
return rawPath.replace(/\\/g, "/").toLowerCase().replace(/\/+$/, "");
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Determine if a gallery item belongs to the given workspace context.
|
|
369
|
+
* Compares workspaceId, sessionId membership, workspacePath, and savedTo file path prefix.
|
|
370
|
+
*/
|
|
371
|
+
function isItemInWorkspace(item, workspace) {
|
|
372
|
+
if (!workspace || !workspace.workspaceId && !workspace.path && (!workspace.sessionIds || workspace.sessionIds.length === 0)) return true;
|
|
373
|
+
if (item.workspaceId && workspace.workspaceId && item.workspaceId === workspace.workspaceId) return true;
|
|
374
|
+
if (item.sessionId && Array.isArray(workspace.sessionIds) && workspace.sessionIds.includes(item.sessionId)) return true;
|
|
375
|
+
if (item.workspacePath && workspace.path) {
|
|
376
|
+
if (normalizeWorkspacePath(item.workspacePath) === normalizeWorkspacePath(workspace.path)) return true;
|
|
377
|
+
}
|
|
378
|
+
if (item.savedTo && workspace.path) {
|
|
379
|
+
const normSaved = normalizeWorkspacePath(item.savedTo);
|
|
380
|
+
const normWs = normalizeWorkspacePath(workspace.path);
|
|
381
|
+
if (normSaved === normWs || normSaved.startsWith(normWs + "/")) return true;
|
|
382
|
+
}
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
330
385
|
//#endregion
|
|
331
386
|
//#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
387
|
/**
|
|
@@ -466,6 +521,16 @@ window.__ModuleLoader__.load({
|
|
|
466
521
|
* This source code is licensed under the ISC license.
|
|
467
522
|
* See the LICENSE file in the root directory of this source tree.
|
|
468
523
|
*/
|
|
524
|
+
const ChevronDown = createLucideIcon("chevron-down", [["path", {
|
|
525
|
+
d: "m6 9 6 6 6-6",
|
|
526
|
+
key: "qrunsl"
|
|
527
|
+
}]]);
|
|
528
|
+
/**
|
|
529
|
+
* @license lucide-react v1.38.0 - ISC
|
|
530
|
+
*
|
|
531
|
+
* This source code is licensed under the ISC license.
|
|
532
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
533
|
+
*/
|
|
469
534
|
const ChevronLeft = createLucideIcon("chevron-left", [["path", {
|
|
470
535
|
d: "m15 18-6-6 6-6",
|
|
471
536
|
key: "1wnfg3"
|
|
@@ -486,6 +551,24 @@ window.__ModuleLoader__.load({
|
|
|
486
551
|
* This source code is licensed under the ISC license.
|
|
487
552
|
* See the LICENSE file in the root directory of this source tree.
|
|
488
553
|
*/
|
|
554
|
+
const Clipboard = createLucideIcon("clipboard", [["rect", {
|
|
555
|
+
width: "8",
|
|
556
|
+
height: "4",
|
|
557
|
+
x: "8",
|
|
558
|
+
y: "2",
|
|
559
|
+
rx: "1",
|
|
560
|
+
ry: "1",
|
|
561
|
+
key: "tgr4d6"
|
|
562
|
+
}], ["path", {
|
|
563
|
+
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",
|
|
564
|
+
key: "116196"
|
|
565
|
+
}]]);
|
|
566
|
+
/**
|
|
567
|
+
* @license lucide-react v1.38.0 - ISC
|
|
568
|
+
*
|
|
569
|
+
* This source code is licensed under the ISC license.
|
|
570
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
571
|
+
*/
|
|
489
572
|
const Copy = createLucideIcon("copy", [["rect", {
|
|
490
573
|
width: "14",
|
|
491
574
|
height: "14",
|
|
@@ -524,6 +607,46 @@ window.__ModuleLoader__.load({
|
|
|
524
607
|
* This source code is licensed under the ISC license.
|
|
525
608
|
* See the LICENSE file in the root directory of this source tree.
|
|
526
609
|
*/
|
|
610
|
+
const Expand = createLucideIcon("expand", [
|
|
611
|
+
["path", {
|
|
612
|
+
d: "m15 15 6 6",
|
|
613
|
+
key: "1s409w"
|
|
614
|
+
}],
|
|
615
|
+
["path", {
|
|
616
|
+
d: "m15 9 6-6",
|
|
617
|
+
key: "ko1vev"
|
|
618
|
+
}],
|
|
619
|
+
["path", {
|
|
620
|
+
d: "M21 16v5h-5",
|
|
621
|
+
key: "1ck2sf"
|
|
622
|
+
}],
|
|
623
|
+
["path", {
|
|
624
|
+
d: "M21 8V3h-5",
|
|
625
|
+
key: "1qoq8a"
|
|
626
|
+
}],
|
|
627
|
+
["path", {
|
|
628
|
+
d: "M3 16v5h5",
|
|
629
|
+
key: "1t08am"
|
|
630
|
+
}],
|
|
631
|
+
["path", {
|
|
632
|
+
d: "m3 21 6-6",
|
|
633
|
+
key: "wwnumi"
|
|
634
|
+
}],
|
|
635
|
+
["path", {
|
|
636
|
+
d: "M3 8V3h5",
|
|
637
|
+
key: "1ln10m"
|
|
638
|
+
}],
|
|
639
|
+
["path", {
|
|
640
|
+
d: "M9 9 3 3",
|
|
641
|
+
key: "v551iv"
|
|
642
|
+
}]
|
|
643
|
+
]);
|
|
644
|
+
/**
|
|
645
|
+
* @license lucide-react v1.38.0 - ISC
|
|
646
|
+
*
|
|
647
|
+
* This source code is licensed under the ISC license.
|
|
648
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
649
|
+
*/
|
|
527
650
|
const FileText = createLucideIcon("file-text", [
|
|
528
651
|
["path", {
|
|
529
652
|
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 +675,38 @@ window.__ModuleLoader__.load({
|
|
|
552
675
|
* This source code is licensed under the ISC license.
|
|
553
676
|
* See the LICENSE file in the root directory of this source tree.
|
|
554
677
|
*/
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
678
|
+
const Heart = createLucideIcon("heart", [["path", {
|
|
679
|
+
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",
|
|
680
|
+
key: "mvr1a0"
|
|
681
|
+
}]]);
|
|
682
|
+
/**
|
|
683
|
+
* @license lucide-react v1.38.0 - ISC
|
|
684
|
+
*
|
|
685
|
+
* This source code is licensed under the ISC license.
|
|
686
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
687
|
+
*/
|
|
688
|
+
const ImagePlus = createLucideIcon("image-plus", [
|
|
689
|
+
["path", {
|
|
690
|
+
d: "M16 5h6",
|
|
691
|
+
key: "1vod17"
|
|
561
692
|
}],
|
|
562
|
-
["
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
r: "3",
|
|
566
|
-
key: "1lh9wr"
|
|
693
|
+
["path", {
|
|
694
|
+
d: "M19 2v6",
|
|
695
|
+
key: "4bpg5p"
|
|
567
696
|
}],
|
|
568
697
|
["path", {
|
|
569
|
-
d: "
|
|
570
|
-
key: "
|
|
698
|
+
d: "M21 11.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7.5",
|
|
699
|
+
key: "1ue2ih"
|
|
571
700
|
}],
|
|
572
701
|
["path", {
|
|
573
|
-
d: "
|
|
574
|
-
key: "
|
|
702
|
+
d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21",
|
|
703
|
+
key: "1xmnt7"
|
|
704
|
+
}],
|
|
705
|
+
["circle", {
|
|
706
|
+
cx: "9",
|
|
707
|
+
cy: "9",
|
|
708
|
+
r: "2",
|
|
709
|
+
key: "af1f0g"
|
|
575
710
|
}]
|
|
576
711
|
]);
|
|
577
712
|
/**
|
|
@@ -580,16 +715,6 @@ window.__ModuleLoader__.load({
|
|
|
580
715
|
* This source code is licensed under the ISC license.
|
|
581
716
|
* See the LICENSE file in the root directory of this source tree.
|
|
582
717
|
*/
|
|
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
718
|
const Image$1 = createLucideIcon("image", [
|
|
594
719
|
["rect", {
|
|
595
720
|
width: "18",
|
|
@@ -617,30 +742,106 @@ window.__ModuleLoader__.load({
|
|
|
617
742
|
* This source code is licensed under the ISC license.
|
|
618
743
|
* See the LICENSE file in the root directory of this source tree.
|
|
619
744
|
*/
|
|
620
|
-
const
|
|
745
|
+
const LoaderCircle = createLucideIcon("loader-circle", [["path", {
|
|
746
|
+
d: "M21 12a9 9 0 1 1-6.219-8.56",
|
|
747
|
+
key: "13zald"
|
|
748
|
+
}]]);
|
|
749
|
+
/**
|
|
750
|
+
* @license lucide-react v1.38.0 - ISC
|
|
751
|
+
*
|
|
752
|
+
* This source code is licensed under the ISC license.
|
|
753
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
754
|
+
*/
|
|
755
|
+
const PanelLeftClose = createLucideIcon("panel-left-close", [
|
|
756
|
+
["rect", {
|
|
757
|
+
width: "18",
|
|
758
|
+
height: "18",
|
|
759
|
+
x: "3",
|
|
760
|
+
y: "3",
|
|
761
|
+
rx: "2",
|
|
762
|
+
key: "afitv7"
|
|
763
|
+
}],
|
|
764
|
+
["path", {
|
|
765
|
+
d: "M9 3v18",
|
|
766
|
+
key: "fh3hqa"
|
|
767
|
+
}],
|
|
768
|
+
["path", {
|
|
769
|
+
d: "m16 15-3-3 3-3",
|
|
770
|
+
key: "14y99z"
|
|
771
|
+
}]
|
|
772
|
+
]);
|
|
773
|
+
/**
|
|
774
|
+
* @license lucide-react v1.38.0 - ISC
|
|
775
|
+
*
|
|
776
|
+
* This source code is licensed under the ISC license.
|
|
777
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
778
|
+
*/
|
|
779
|
+
const PanelLeft = createLucideIcon("panel-left", [["rect", {
|
|
780
|
+
width: "18",
|
|
781
|
+
height: "18",
|
|
782
|
+
x: "3",
|
|
783
|
+
y: "3",
|
|
784
|
+
rx: "2",
|
|
785
|
+
key: "afitv7"
|
|
786
|
+
}], ["path", {
|
|
787
|
+
d: "M9 3v18",
|
|
788
|
+
key: "fh3hqa"
|
|
789
|
+
}]]);
|
|
790
|
+
/**
|
|
791
|
+
* @license lucide-react v1.38.0 - ISC
|
|
792
|
+
*
|
|
793
|
+
* This source code is licensed under the ISC license.
|
|
794
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
795
|
+
*/
|
|
796
|
+
const PencilLine = createLucideIcon("pencil-line", [
|
|
797
|
+
["path", {
|
|
798
|
+
d: "M13 21h8",
|
|
799
|
+
key: "1jsn5i"
|
|
800
|
+
}],
|
|
621
801
|
["path", {
|
|
622
|
-
d: "
|
|
623
|
-
key: "
|
|
802
|
+
d: "m15 5 4 4",
|
|
803
|
+
key: "1mk7zo"
|
|
624
804
|
}],
|
|
625
805
|
["path", {
|
|
626
|
-
d: "
|
|
627
|
-
key: "
|
|
806
|
+
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",
|
|
807
|
+
key: "1a8usu"
|
|
808
|
+
}]
|
|
809
|
+
]);
|
|
810
|
+
/**
|
|
811
|
+
* @license lucide-react v1.38.0 - ISC
|
|
812
|
+
*
|
|
813
|
+
* This source code is licensed under the ISC license.
|
|
814
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
815
|
+
*/
|
|
816
|
+
const Plus = createLucideIcon("plus", [["path", {
|
|
817
|
+
d: "M5 12h14",
|
|
818
|
+
key: "1ays0h"
|
|
819
|
+
}], ["path", {
|
|
820
|
+
d: "M12 5v14",
|
|
821
|
+
key: "s699le"
|
|
822
|
+
}]]);
|
|
823
|
+
/**
|
|
824
|
+
* @license lucide-react v1.38.0 - ISC
|
|
825
|
+
*
|
|
826
|
+
* This source code is licensed under the ISC license.
|
|
827
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
828
|
+
*/
|
|
829
|
+
const RefreshCw = createLucideIcon("refresh-cw", [
|
|
830
|
+
["path", {
|
|
831
|
+
d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",
|
|
832
|
+
key: "v9h5vc"
|
|
628
833
|
}],
|
|
629
834
|
["path", {
|
|
630
|
-
d: "
|
|
631
|
-
key: "
|
|
835
|
+
d: "M21 3v5h-5",
|
|
836
|
+
key: "1q7to0"
|
|
632
837
|
}],
|
|
633
838
|
["path", {
|
|
634
|
-
d: "
|
|
635
|
-
key: "
|
|
839
|
+
d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",
|
|
840
|
+
key: "3uifl3"
|
|
636
841
|
}],
|
|
637
|
-
["
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
width: "6",
|
|
641
|
-
height: "6",
|
|
642
|
-
rx: "1",
|
|
643
|
-
key: "cif1o7"
|
|
842
|
+
["path", {
|
|
843
|
+
d: "M8 16H3v5",
|
|
844
|
+
key: "1cv678"
|
|
644
845
|
}]
|
|
645
846
|
]);
|
|
646
847
|
/**
|
|
@@ -708,6 +909,32 @@ window.__ModuleLoader__.load({
|
|
|
708
909
|
* This source code is licensed under the ISC license.
|
|
709
910
|
* See the LICENSE file in the root directory of this source tree.
|
|
710
911
|
*/
|
|
912
|
+
const Sparkles = createLucideIcon("sparkles", [
|
|
913
|
+
["path", {
|
|
914
|
+
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",
|
|
915
|
+
key: "1s2grr"
|
|
916
|
+
}],
|
|
917
|
+
["path", {
|
|
918
|
+
d: "M20 2v4",
|
|
919
|
+
key: "1rf3ol"
|
|
920
|
+
}],
|
|
921
|
+
["path", {
|
|
922
|
+
d: "M22 4h-4",
|
|
923
|
+
key: "gwowj6"
|
|
924
|
+
}],
|
|
925
|
+
["circle", {
|
|
926
|
+
cx: "4",
|
|
927
|
+
cy: "20",
|
|
928
|
+
r: "2",
|
|
929
|
+
key: "6kqj1y"
|
|
930
|
+
}]
|
|
931
|
+
]);
|
|
932
|
+
/**
|
|
933
|
+
* @license lucide-react v1.38.0 - ISC
|
|
934
|
+
*
|
|
935
|
+
* This source code is licensed under the ISC license.
|
|
936
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
937
|
+
*/
|
|
711
938
|
const Trash2 = createLucideIcon("trash-2", [
|
|
712
939
|
["path", {
|
|
713
940
|
d: "M10 11v6",
|
|
@@ -756,6 +983,26 @@ window.__ModuleLoader__.load({
|
|
|
756
983
|
* This source code is licensed under the ISC license.
|
|
757
984
|
* See the LICENSE file in the root directory of this source tree.
|
|
758
985
|
*/
|
|
986
|
+
const Upload = createLucideIcon("upload", [
|
|
987
|
+
["path", {
|
|
988
|
+
d: "M12 3v12",
|
|
989
|
+
key: "1x0j5s"
|
|
990
|
+
}],
|
|
991
|
+
["path", {
|
|
992
|
+
d: "m17 8-5-5-5 5",
|
|
993
|
+
key: "7q97r8"
|
|
994
|
+
}],
|
|
995
|
+
["path", {
|
|
996
|
+
d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",
|
|
997
|
+
key: "ih7n3h"
|
|
998
|
+
}]
|
|
999
|
+
]);
|
|
1000
|
+
/**
|
|
1001
|
+
* @license lucide-react v1.38.0 - ISC
|
|
1002
|
+
*
|
|
1003
|
+
* This source code is licensed under the ISC license.
|
|
1004
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
1005
|
+
*/
|
|
759
1006
|
const X = createLucideIcon("x", [["path", {
|
|
760
1007
|
d: "M18 6 6 18",
|
|
761
1008
|
key: "1bl5f8"
|
|
@@ -763,21 +1010,1592 @@ window.__ModuleLoader__.load({
|
|
|
763
1010
|
d: "m6 6 12 12",
|
|
764
1011
|
key: "d8bk6v"
|
|
765
1012
|
}]]);
|
|
1013
|
+
/**
|
|
1014
|
+
* @license lucide-react v1.38.0 - ISC
|
|
1015
|
+
*
|
|
1016
|
+
* This source code is licensed under the ISC license.
|
|
1017
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
1018
|
+
*/
|
|
1019
|
+
const ZoomIn = createLucideIcon("zoom-in", [
|
|
1020
|
+
["circle", {
|
|
1021
|
+
cx: "11",
|
|
1022
|
+
cy: "11",
|
|
1023
|
+
r: "8",
|
|
1024
|
+
key: "4ej97u"
|
|
1025
|
+
}],
|
|
1026
|
+
["line", {
|
|
1027
|
+
x1: "21",
|
|
1028
|
+
x2: "16.65",
|
|
1029
|
+
y1: "21",
|
|
1030
|
+
y2: "16.65",
|
|
1031
|
+
key: "13gj7c"
|
|
1032
|
+
}],
|
|
1033
|
+
["line", {
|
|
1034
|
+
x1: "11",
|
|
1035
|
+
x2: "11",
|
|
1036
|
+
y1: "8",
|
|
1037
|
+
y2: "14",
|
|
1038
|
+
key: "1vmskp"
|
|
1039
|
+
}],
|
|
1040
|
+
["line", {
|
|
1041
|
+
x1: "8",
|
|
1042
|
+
x2: "14",
|
|
1043
|
+
y1: "11",
|
|
1044
|
+
y2: "11",
|
|
1045
|
+
key: "durymu"
|
|
1046
|
+
}]
|
|
1047
|
+
]);
|
|
1048
|
+
/**
|
|
1049
|
+
* @license lucide-react v1.38.0 - ISC
|
|
1050
|
+
*
|
|
1051
|
+
* This source code is licensed under the ISC license.
|
|
1052
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
1053
|
+
*/
|
|
1054
|
+
const ZoomOut = createLucideIcon("zoom-out", [
|
|
1055
|
+
["circle", {
|
|
1056
|
+
cx: "11",
|
|
1057
|
+
cy: "11",
|
|
1058
|
+
r: "8",
|
|
1059
|
+
key: "4ej97u"
|
|
1060
|
+
}],
|
|
1061
|
+
["line", {
|
|
1062
|
+
x1: "21",
|
|
1063
|
+
x2: "16.65",
|
|
1064
|
+
y1: "21",
|
|
1065
|
+
y2: "16.65",
|
|
1066
|
+
key: "13gj7c"
|
|
1067
|
+
}],
|
|
1068
|
+
["line", {
|
|
1069
|
+
x1: "8",
|
|
1070
|
+
x2: "14",
|
|
1071
|
+
y1: "11",
|
|
1072
|
+
y2: "11",
|
|
1073
|
+
key: "durymu"
|
|
1074
|
+
}]
|
|
1075
|
+
]);
|
|
1076
|
+
let currentCacheBytes = 0;
|
|
1077
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1078
|
+
/**
|
|
1079
|
+
* Fetch or reuse an image Blob by attachment ID.
|
|
1080
|
+
* Refreshes LRU recency on hit and enforces count/byte limits on arrival.
|
|
1081
|
+
*/
|
|
1082
|
+
function fetchAttachmentBlob(attachment) {
|
|
1083
|
+
const key = String(attachment.attachmentId);
|
|
1084
|
+
const existing = cache.get(key);
|
|
1085
|
+
if (existing !== void 0) {
|
|
1086
|
+
cache.delete(key);
|
|
1087
|
+
cache.set(key, existing);
|
|
1088
|
+
return existing.promise;
|
|
1089
|
+
}
|
|
1090
|
+
const entry = {
|
|
1091
|
+
promise: Promise.resolve().then(async () => {
|
|
1092
|
+
try {
|
|
1093
|
+
const response = await fetch(IMAGE_ROUTE, {
|
|
1094
|
+
method: "POST",
|
|
1095
|
+
credentials: "same-origin",
|
|
1096
|
+
headers: { "content-type": "application/json" },
|
|
1097
|
+
body: JSON.stringify({ attachment })
|
|
1098
|
+
});
|
|
1099
|
+
if (!response.ok) throw new Error(`Image unavailable (${response.status})`);
|
|
1100
|
+
const blob = await response.blob();
|
|
1101
|
+
if (cache.get(key) === entry) {
|
|
1102
|
+
entry.bytes = blob.size;
|
|
1103
|
+
currentCacheBytes += blob.size;
|
|
1104
|
+
enforceLimits();
|
|
1105
|
+
}
|
|
1106
|
+
return blob;
|
|
1107
|
+
} catch (err) {
|
|
1108
|
+
if (cache.get(key) === entry) cache.delete(key);
|
|
1109
|
+
throw err;
|
|
1110
|
+
}
|
|
1111
|
+
}),
|
|
1112
|
+
bytes: 0
|
|
1113
|
+
};
|
|
1114
|
+
cache.set(key, entry);
|
|
1115
|
+
enforceLimits();
|
|
1116
|
+
return entry.promise;
|
|
1117
|
+
}
|
|
1118
|
+
/** Evict one item from memory cache (e.g. when deleted from gallery). */
|
|
1119
|
+
function evictAttachmentCache(attachmentId) {
|
|
1120
|
+
const key = String(attachmentId);
|
|
1121
|
+
const entry = cache.get(key);
|
|
1122
|
+
if (entry !== void 0) {
|
|
1123
|
+
currentCacheBytes = Math.max(0, currentCacheBytes - entry.bytes);
|
|
1124
|
+
cache.delete(key);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
function enforceLimits() {
|
|
1128
|
+
while (cache.size > 30 || currentCacheBytes > 134217728) {
|
|
1129
|
+
const oldestKey = cache.keys().next().value;
|
|
1130
|
+
if (oldestKey === void 0) break;
|
|
1131
|
+
const oldest = cache.get(oldestKey);
|
|
1132
|
+
if (oldest !== void 0) currentCacheBytes = Math.max(0, currentCacheBytes - oldest.bytes);
|
|
1133
|
+
cache.delete(oldestKey);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
766
1136
|
//#endregion
|
|
767
|
-
//#region lib/types/client/
|
|
1137
|
+
//#region lib/types/client/browser-image-utils.js
|
|
768
1138
|
/**
|
|
769
|
-
*
|
|
770
|
-
*
|
|
771
|
-
* responsive image grid, and placeholder routes.
|
|
1139
|
+
* Shared browser image and DOM helpers for client views.
|
|
1140
|
+
* Completely decoupled from gallery-view and studio-view to prevent circular dependencies.
|
|
772
1141
|
*/
|
|
773
|
-
|
|
1142
|
+
/** Copy an image Blob to the OS clipboard, converting to PNG if required by the browser. */
|
|
1143
|
+
async function copyImageBlob(blob) {
|
|
1144
|
+
try {
|
|
1145
|
+
if (blob.type === "image/png") {
|
|
1146
|
+
await navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]);
|
|
1147
|
+
return true;
|
|
1148
|
+
}
|
|
1149
|
+
if (typeof createImageBitmap === "function") {
|
|
1150
|
+
const bitmap = await createImageBitmap(blob);
|
|
1151
|
+
try {
|
|
1152
|
+
const canvas = document.createElement("canvas");
|
|
1153
|
+
canvas.width = bitmap.width;
|
|
1154
|
+
canvas.height = bitmap.height;
|
|
1155
|
+
const ctx = canvas.getContext("2d");
|
|
1156
|
+
if (!ctx) return false;
|
|
1157
|
+
ctx.drawImage(bitmap, 0, 0);
|
|
1158
|
+
const pngBlob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
|
|
1159
|
+
if (!pngBlob) return false;
|
|
1160
|
+
await navigator.clipboard.write([new ClipboardItem({ "image/png": pngBlob })]);
|
|
1161
|
+
return true;
|
|
1162
|
+
} finally {
|
|
1163
|
+
bitmap.close();
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
const img = new Image();
|
|
1167
|
+
const url = URL.createObjectURL(blob);
|
|
1168
|
+
try {
|
|
1169
|
+
await new Promise((resolve, reject) => {
|
|
1170
|
+
img.onload = () => resolve();
|
|
1171
|
+
img.onerror = () => reject(/* @__PURE__ */ new Error("Image decode failed"));
|
|
1172
|
+
img.src = url;
|
|
1173
|
+
});
|
|
1174
|
+
const canvas = document.createElement("canvas");
|
|
1175
|
+
canvas.width = img.naturalWidth;
|
|
1176
|
+
canvas.height = img.naturalHeight;
|
|
1177
|
+
const ctx = canvas.getContext("2d");
|
|
1178
|
+
if (!ctx) return false;
|
|
1179
|
+
ctx.drawImage(img, 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
|
+
URL.revokeObjectURL(url);
|
|
1186
|
+
}
|
|
1187
|
+
} catch {
|
|
1188
|
+
return false;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
/** Safely trigger a browser file download by mounting an anchor element into the DOM. */
|
|
1192
|
+
function downloadBlobUrl(url, filename) {
|
|
1193
|
+
const link = document.createElement("a");
|
|
1194
|
+
link.href = url;
|
|
1195
|
+
link.download = filename;
|
|
1196
|
+
document.body.appendChild(link);
|
|
1197
|
+
try {
|
|
1198
|
+
link.click();
|
|
1199
|
+
} finally {
|
|
1200
|
+
document.body.removeChild(link);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
/** Human-readable relative time formatter supporting zh and en. */
|
|
1204
|
+
function formatRelativeTime$1(timestamp, lang) {
|
|
1205
|
+
const diff = Math.max(0, Date.now() - timestamp);
|
|
1206
|
+
if (lang === "zh") {
|
|
1207
|
+
if (diff < 6e4) return "刚刚";
|
|
1208
|
+
if (diff < 36e5) return `${Math.floor(diff / 6e4)} 分钟前`;
|
|
1209
|
+
if (diff < 864e5) return `${Math.floor(diff / 36e5)} 小时前`;
|
|
1210
|
+
if (diff < 2592e6) return `${Math.floor(diff / 864e5)} 天前`;
|
|
1211
|
+
const d = new Date(timestamp);
|
|
1212
|
+
return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`;
|
|
1213
|
+
}
|
|
1214
|
+
if (diff < 6e4) return "Just now";
|
|
1215
|
+
if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
|
|
1216
|
+
if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
|
|
1217
|
+
if (diff < 2592e6) return `${Math.floor(diff / 864e5)}d ago`;
|
|
1218
|
+
const d = new Date(timestamp);
|
|
1219
|
+
return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`;
|
|
1220
|
+
}
|
|
1221
|
+
//#endregion
|
|
1222
|
+
//#region lib/types/client/studio-view.js
|
|
1223
|
+
const COPY = {
|
|
774
1224
|
zh: {
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1225
|
+
title: "云端图像工作台",
|
|
1226
|
+
configured: "API 已配置",
|
|
1227
|
+
unconfigured: "未配置",
|
|
1228
|
+
recent: "最近生成",
|
|
1229
|
+
empty: "还没有生成记录",
|
|
1230
|
+
generate: "文生图",
|
|
1231
|
+
edit: "图生图",
|
|
1232
|
+
details: "图片详情",
|
|
1233
|
+
reference: "参考图",
|
|
1234
|
+
optional: "可选",
|
|
1235
|
+
upload: "点击上传或拖拽图片到此处",
|
|
1236
|
+
uploadHint: "支持 JPG / PNG / WebP / GIF,最大 10MB(最多 5 张)",
|
|
1237
|
+
prompt: "提示词 Prompt",
|
|
1238
|
+
clear: "清空",
|
|
1239
|
+
promptPlaceholder: "描述主体、构图、风格、光线与需要出现的文字…(支持 Ctrl+Enter 快捷生成)",
|
|
1240
|
+
provider: "Provider",
|
|
1241
|
+
model: "Model",
|
|
1242
|
+
ratio: "比例",
|
|
1243
|
+
quality: "清晰度",
|
|
1244
|
+
start: "开始生成",
|
|
1245
|
+
generating: "正在生成…",
|
|
1246
|
+
cancelGenerate: "取消生成",
|
|
1247
|
+
retry: "重新加载",
|
|
1248
|
+
configLoadFailed: "工作台配置加载失败,请检查服务后重试。",
|
|
1249
|
+
noProvider: "请先在设置中配置至少一个云端图像 Provider 的 API Key。",
|
|
1250
|
+
selectConfigured: "该 Provider 尚未配置,请先到设置中配置 API Key。",
|
|
1251
|
+
needPrompt: "请输入提示词",
|
|
1252
|
+
needReference: "请先添加至少一张参考图",
|
|
1253
|
+
result: "本次结果",
|
|
1254
|
+
continueEdit: "继续编辑(垫图)",
|
|
1255
|
+
regenerate: "再次生成",
|
|
1256
|
+
copy: "复制",
|
|
1257
|
+
copied: "已复制到剪贴板",
|
|
1258
|
+
copyFailed: "复制失败",
|
|
1259
|
+
download: "下载",
|
|
1260
|
+
remove: "删除",
|
|
1261
|
+
fit: "适应窗口",
|
|
1262
|
+
loading: "正在读取图片…",
|
|
1263
|
+
generationFailed: "生成失败",
|
|
1264
|
+
selectHistory: "从左侧选择一张图片,或在右侧开始新的生成。",
|
|
1265
|
+
created: "生成时间",
|
|
1266
|
+
elapsed: "耗时",
|
|
1267
|
+
dimensions: "尺寸",
|
|
1268
|
+
output: "输出参数",
|
|
1269
|
+
closeReference: "移除参考图",
|
|
1270
|
+
uploadInvalid: "请选择有效的图片文件(最大 10MB)",
|
|
1271
|
+
imageLoadFailed: "图片读取失败",
|
|
1272
|
+
fullscreen: "大图全屏",
|
|
1273
|
+
close: "关闭",
|
|
1274
|
+
copyPpt: "复制 Prompt",
|
|
1275
|
+
copiedPrompt: "已复制 Prompt",
|
|
1276
|
+
copiedImage: "已复制图片",
|
|
1277
|
+
favorite: "收藏",
|
|
1278
|
+
favorited: "已收藏",
|
|
1279
|
+
favoriteAdded: "已添加到收藏",
|
|
1280
|
+
favoriteRemoved: "已取消收藏",
|
|
1281
|
+
loadMore: "加载更多 ({n})",
|
|
1282
|
+
deleteModalTitle: "从图库删除",
|
|
1283
|
+
deleteModalDesc: "确定从图库中删除这张图片吗?(原聊天记录不会受影响)",
|
|
1284
|
+
deleteWorkspaceFilesLabel: "同时删除工作区本地文件",
|
|
1285
|
+
confirm: "确定删除",
|
|
1286
|
+
cancel: "取消",
|
|
1287
|
+
deletedToast: "已从图库删除",
|
|
1288
|
+
referencesCount: "参考图 ({current}/{max})",
|
|
1289
|
+
addMoreRef: "+ 添加",
|
|
1290
|
+
maxReferencesExceeded: "最多支持添加 {max} 张参考图",
|
|
1291
|
+
newGeneration: "新建生成",
|
|
1292
|
+
new: "新建",
|
|
1293
|
+
configuredCount: "API 已配置 · {count}",
|
|
1294
|
+
unconfiguredStatus: "API 未配置",
|
|
1295
|
+
providerStatus: "云端提供商与模型状态",
|
|
1296
|
+
collapseSidebar: "折叠最近生成",
|
|
1297
|
+
expandSidebar: "展开最近生成"
|
|
1298
|
+
},
|
|
1299
|
+
en: {
|
|
1300
|
+
title: "Cloud Image Studio",
|
|
1301
|
+
configured: "API configured",
|
|
1302
|
+
unconfigured: "Not configured",
|
|
1303
|
+
recent: "Recent generations",
|
|
1304
|
+
empty: "No generated images yet",
|
|
1305
|
+
generate: "Text to image",
|
|
1306
|
+
edit: "Image to image",
|
|
1307
|
+
details: "Image details",
|
|
1308
|
+
reference: "Reference image",
|
|
1309
|
+
optional: "optional",
|
|
1310
|
+
upload: "Click or drop images here",
|
|
1311
|
+
uploadHint: "JPG / PNG / WebP / GIF, up to 10MB (max 5)",
|
|
1312
|
+
prompt: "Prompt",
|
|
1313
|
+
clear: "Clear",
|
|
1314
|
+
promptPlaceholder: "Describe the subject, composition, style, lighting, and exact text… (Ctrl+Enter to generate)",
|
|
1315
|
+
provider: "Provider",
|
|
1316
|
+
model: "Model",
|
|
1317
|
+
ratio: "Aspect ratio",
|
|
1318
|
+
quality: "Quality",
|
|
1319
|
+
start: "Generate",
|
|
1320
|
+
generating: "Generating…",
|
|
1321
|
+
cancelGenerate: "Cancel",
|
|
1322
|
+
retry: "Retry",
|
|
1323
|
+
configLoadFailed: "Failed to load studio configuration.",
|
|
1324
|
+
noProvider: "Configure an API key for at least one cloud image provider in Settings.",
|
|
1325
|
+
selectConfigured: "This provider is not configured. Add its API key in Settings first.",
|
|
1326
|
+
needPrompt: "Enter a prompt",
|
|
1327
|
+
needReference: "Add at least one reference image first",
|
|
1328
|
+
result: "Current result",
|
|
1329
|
+
continueEdit: "Continue editing",
|
|
1330
|
+
regenerate: "Generate again",
|
|
1331
|
+
copy: "Copy",
|
|
1332
|
+
copied: "Copied to clipboard",
|
|
1333
|
+
copyFailed: "Copy failed",
|
|
1334
|
+
download: "Download",
|
|
1335
|
+
remove: "Delete",
|
|
1336
|
+
fit: "Fit",
|
|
1337
|
+
loading: "Loading image…",
|
|
1338
|
+
generationFailed: "Generation failed",
|
|
1339
|
+
selectHistory: "Select an image on the left, or start a new generation on the right.",
|
|
1340
|
+
created: "Created",
|
|
1341
|
+
elapsed: "Elapsed",
|
|
1342
|
+
dimensions: "Dimensions",
|
|
1343
|
+
output: "Output",
|
|
1344
|
+
closeReference: "Remove reference",
|
|
1345
|
+
uploadInvalid: "Choose a valid image file up to 10MB.",
|
|
1346
|
+
imageLoadFailed: "Could not load image",
|
|
1347
|
+
fullscreen: "Fullscreen",
|
|
1348
|
+
close: "Close",
|
|
1349
|
+
copyPpt: "Copy Prompt",
|
|
1350
|
+
copiedPrompt: "Prompt copied",
|
|
1351
|
+
copiedImage: "Image copied",
|
|
1352
|
+
favorite: "Favorite",
|
|
1353
|
+
favorited: "Favorited",
|
|
1354
|
+
favoriteAdded: "Added to favorites",
|
|
1355
|
+
favoriteRemoved: "Removed from favorites",
|
|
1356
|
+
loadMore: "Load more ({n})",
|
|
1357
|
+
deleteModalTitle: "Delete from gallery",
|
|
1358
|
+
deleteModalDesc: "Remove this image from the local gallery? (Chat history remains unaffected)",
|
|
1359
|
+
deleteWorkspaceFilesLabel: "Also delete local workspace files",
|
|
1360
|
+
confirm: "Delete",
|
|
1361
|
+
cancel: "Cancel",
|
|
1362
|
+
deletedToast: "Deleted from gallery",
|
|
1363
|
+
referencesCount: "References ({current}/{max})",
|
|
1364
|
+
addMoreRef: "+ Add",
|
|
1365
|
+
maxReferencesExceeded: "Up to {max} reference images allowed",
|
|
1366
|
+
newGeneration: "New generation",
|
|
1367
|
+
new: "New",
|
|
1368
|
+
configuredCount: "API configured · {count}",
|
|
1369
|
+
unconfiguredStatus: "Not configured",
|
|
1370
|
+
providerStatus: "Provider & Model Status",
|
|
1371
|
+
collapseSidebar: "Collapse sidebar",
|
|
1372
|
+
expandSidebar: "Expand recent list"
|
|
1373
|
+
}
|
|
1374
|
+
};
|
|
1375
|
+
const StudioView = ({ locale, workspace }) => {
|
|
1376
|
+
const [lang, setLang] = (0, react.useState)(() => locale?.getSnapshot?.().active?.startsWith("en") ? "en" : "zh");
|
|
1377
|
+
const [config, setConfig] = (0, react.useState)(null);
|
|
1378
|
+
const [configLoading, setConfigLoading] = (0, react.useState)(true);
|
|
1379
|
+
const [configError, setConfigError] = (0, react.useState)(null);
|
|
1380
|
+
const [items, setItems] = (0, react.useState)([]);
|
|
1381
|
+
const [sidebarCollapsed, setSidebarCollapsed] = (0, react.useState)(false);
|
|
1382
|
+
const [visibleLimit, setVisibleLimit] = (0, react.useState)(30);
|
|
1383
|
+
const [selected, setSelected] = (0, react.useState)(null);
|
|
1384
|
+
const [mode, setMode] = (0, react.useState)("generate");
|
|
1385
|
+
const [panelTab, setPanelTab] = (0, react.useState)("generate");
|
|
1386
|
+
const [provider, setProvider] = (0, react.useState)("google");
|
|
1387
|
+
const [model, setModel] = (0, react.useState)("");
|
|
1388
|
+
const [ratio, setRatio] = (0, react.useState)("1:1");
|
|
1389
|
+
const [quality, setQuality] = (0, react.useState)("1K");
|
|
1390
|
+
const [prompt, setPrompt] = (0, react.useState)("");
|
|
1391
|
+
const [references, setReferences] = (0, react.useState)([]);
|
|
1392
|
+
const [isGenerating, setIsGenerating] = (0, react.useState)(false);
|
|
1393
|
+
const [error, setError] = (0, react.useState)(null);
|
|
1394
|
+
const [notice, setNotice] = (0, react.useState)(null);
|
|
1395
|
+
const [zoom, setZoom] = (0, react.useState)(100);
|
|
1396
|
+
const [fit, setFit] = (0, react.useState)(true);
|
|
1397
|
+
const [offset, setOffset] = (0, react.useState)({
|
|
1398
|
+
x: 0,
|
|
1399
|
+
y: 0
|
|
1400
|
+
});
|
|
1401
|
+
const [isDragging, setIsDragging] = (0, react.useState)(false);
|
|
1402
|
+
const dragStartRef = (0, react.useRef)(null);
|
|
1403
|
+
const hasDraggedRef = (0, react.useRef)(false);
|
|
1404
|
+
const [dragging, setDragging] = (0, react.useState)(false);
|
|
1405
|
+
const [lightboxOpen, setLightboxOpen] = (0, react.useState)(false);
|
|
1406
|
+
const [showDeleteModal, setShowDeleteModal] = (0, react.useState)(false);
|
|
1407
|
+
const [deleteWorkspaceFiles, setDeleteWorkspaceFiles] = (0, react.useState)(true);
|
|
1408
|
+
const requestRef = (0, react.useRef)(null);
|
|
1409
|
+
const canvasRef = (0, react.useRef)(null);
|
|
1410
|
+
const fileInputRef = (0, react.useRef)(null);
|
|
1411
|
+
const image = useAttachmentImage(selected?.attachment);
|
|
1412
|
+
const maxReferences = provider === "dashscope" ? 3 : 5;
|
|
1413
|
+
const referencesRef = (0, react.useRef)(references);
|
|
1414
|
+
referencesRef.current = references;
|
|
1415
|
+
const t = (key, values) => {
|
|
1416
|
+
let text = COPY[lang][key];
|
|
1417
|
+
for (const [name, value] of Object.entries(values ?? {})) text = text.replace(`{${name}}`, value);
|
|
1418
|
+
return text;
|
|
1419
|
+
};
|
|
1420
|
+
(0, react.useEffect)(() => locale?.subscribe?.(() => setLang(locale.getSnapshot().active?.startsWith("en") ? "en" : "zh")), [locale]);
|
|
1421
|
+
const loadConfig = (0, react.useCallback)(async () => {
|
|
1422
|
+
setConfigLoading(true);
|
|
1423
|
+
setConfigError(null);
|
|
1424
|
+
const controller = new AbortController();
|
|
1425
|
+
try {
|
|
1426
|
+
const response = await fetch(STUDIO_ROUTE, {
|
|
1427
|
+
signal: controller.signal,
|
|
1428
|
+
credentials: "same-origin"
|
|
1429
|
+
});
|
|
1430
|
+
const payload = await response.json();
|
|
1431
|
+
if (!response.ok || !("providers" in payload)) throw new Error("error" in payload && payload.error ? payload.error : "Studio unavailable");
|
|
1432
|
+
setConfig(payload);
|
|
1433
|
+
const initial = payload.providers.find((item) => item.provider === payload.activeProvider) ?? payload.providers[0];
|
|
1434
|
+
if (initial !== void 0) applyProvider(initial);
|
|
1435
|
+
} catch (fetchError) {
|
|
1436
|
+
if (!controller.signal.aborted) setConfigError(messageOf(fetchError));
|
|
1437
|
+
} finally {
|
|
1438
|
+
setConfigLoading(false);
|
|
1439
|
+
}
|
|
1440
|
+
}, []);
|
|
1441
|
+
(0, react.useEffect)(() => {
|
|
1442
|
+
loadConfig();
|
|
1443
|
+
}, [loadConfig]);
|
|
1444
|
+
(0, react.useEffect)(() => {
|
|
1445
|
+
let mounted = true;
|
|
1446
|
+
const load = () => void getGalleryItems().then((next) => {
|
|
1447
|
+
if (!mounted) return;
|
|
1448
|
+
setItems(next);
|
|
1449
|
+
setSelected((current) => current === null ? null : next.find((item) => item.id === current.id) ?? null);
|
|
1450
|
+
});
|
|
1451
|
+
load();
|
|
1452
|
+
const unsubscribe = subscribeGallery(load);
|
|
1453
|
+
return () => {
|
|
1454
|
+
mounted = false;
|
|
1455
|
+
unsubscribe();
|
|
1456
|
+
};
|
|
1457
|
+
}, []);
|
|
1458
|
+
(0, react.useEffect)(() => {
|
|
1459
|
+
return () => {
|
|
1460
|
+
requestRef.current?.abort();
|
|
1461
|
+
};
|
|
1462
|
+
}, []);
|
|
1463
|
+
(0, react.useEffect)(() => {
|
|
1464
|
+
return () => {
|
|
1465
|
+
for (const item of referencesRef.current) if (item.previewUrl.startsWith("blob:")) URL.revokeObjectURL(item.previewUrl);
|
|
1466
|
+
};
|
|
1467
|
+
}, []);
|
|
1468
|
+
(0, react.useEffect)(() => {
|
|
1469
|
+
const handleKeyDown = (e) => {
|
|
1470
|
+
if (e.key === "Escape" && lightboxOpen) setLightboxOpen(false);
|
|
1471
|
+
};
|
|
1472
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
1473
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1474
|
+
}, [lightboxOpen]);
|
|
1475
|
+
(0, react.useEffect)(() => {
|
|
1476
|
+
const canvasEl = canvasRef.current;
|
|
1477
|
+
if (!canvasEl) return;
|
|
1478
|
+
const onWheel = (e) => {
|
|
1479
|
+
if (selected === null || !image.url) return;
|
|
1480
|
+
e.preventDefault();
|
|
1481
|
+
const delta = e.deltaY < 0 ? 15 : -15;
|
|
1482
|
+
setFit(false);
|
|
1483
|
+
setZoom((prev) => Math.min(500, Math.max(25, prev + delta)));
|
|
1484
|
+
};
|
|
1485
|
+
canvasEl.addEventListener("wheel", onWheel, { passive: false });
|
|
1486
|
+
return () => canvasEl.removeEventListener("wheel", onWheel);
|
|
1487
|
+
}, [selected, image.url]);
|
|
1488
|
+
(0, react.useEffect)(() => {
|
|
1489
|
+
const handleMouseMove = (e) => {
|
|
1490
|
+
if (!dragStartRef.current) return;
|
|
1491
|
+
const dx = e.clientX - dragStartRef.current.x;
|
|
1492
|
+
const dy = e.clientY - dragStartRef.current.y;
|
|
1493
|
+
if (Math.abs(dx) > 3 || Math.abs(dy) > 3) {
|
|
1494
|
+
hasDraggedRef.current = true;
|
|
1495
|
+
setFit(false);
|
|
1496
|
+
}
|
|
1497
|
+
setOffset({
|
|
1498
|
+
x: dragStartRef.current.startX + dx,
|
|
1499
|
+
y: dragStartRef.current.startY + dy
|
|
1500
|
+
});
|
|
1501
|
+
};
|
|
1502
|
+
const handleMouseUp = () => {
|
|
1503
|
+
dragStartRef.current = null;
|
|
1504
|
+
setIsDragging(false);
|
|
1505
|
+
};
|
|
1506
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
1507
|
+
window.addEventListener("mouseup", handleMouseUp);
|
|
1508
|
+
return () => {
|
|
1509
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
1510
|
+
window.removeEventListener("mouseup", handleMouseUp);
|
|
1511
|
+
};
|
|
1512
|
+
}, []);
|
|
1513
|
+
const activeProfile = (0, react.useMemo)(() => config?.providers.find((item) => item.provider === provider), [config, provider]);
|
|
1514
|
+
const configuredCount = config?.providers.filter((item) => item.configured).length ?? 0;
|
|
1515
|
+
const displayItems = (0, react.useMemo)(() => items.slice(0, visibleLimit), [items, visibleLimit]);
|
|
1516
|
+
const applyProvider = (profile) => {
|
|
1517
|
+
setProvider(profile.provider);
|
|
1518
|
+
setModel(profile.model);
|
|
1519
|
+
setRatio(profile.defaultRatio);
|
|
1520
|
+
setQuality(profile.defaultQuality);
|
|
1521
|
+
setError(null);
|
|
1522
|
+
};
|
|
1523
|
+
const changeProvider = (value) => {
|
|
1524
|
+
const profile = config?.providers.find((item) => item.provider === value);
|
|
1525
|
+
if (profile !== void 0) {
|
|
1526
|
+
applyProvider(profile);
|
|
1527
|
+
const providerMax = value === "dashscope" ? 3 : 5;
|
|
1528
|
+
if (referencesRef.current.length > providerMax) {
|
|
1529
|
+
const keep = referencesRef.current.slice(0, providerMax);
|
|
1530
|
+
const overflow = referencesRef.current.slice(providerMax);
|
|
1531
|
+
for (const item of overflow) if (item.previewUrl.startsWith("blob:")) URL.revokeObjectURL(item.previewUrl);
|
|
1532
|
+
setReferences(keep);
|
|
1533
|
+
referencesRef.current = keep;
|
|
1534
|
+
setError(t("maxReferencesExceeded", { max: String(providerMax) }));
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
};
|
|
1538
|
+
const resetFit = () => {
|
|
1539
|
+
setFit(true);
|
|
1540
|
+
setZoom(100);
|
|
1541
|
+
setOffset({
|
|
1542
|
+
x: 0,
|
|
1543
|
+
y: 0
|
|
1544
|
+
});
|
|
1545
|
+
};
|
|
1546
|
+
const selectItem = (item) => {
|
|
1547
|
+
setSelected(item);
|
|
1548
|
+
setPanelTab("details");
|
|
1549
|
+
resetFit();
|
|
1550
|
+
};
|
|
1551
|
+
const startNew = () => {
|
|
1552
|
+
setSelected(null);
|
|
1553
|
+
setPanelTab("generate");
|
|
1554
|
+
resetFit();
|
|
1555
|
+
};
|
|
1556
|
+
const handleCanvasMouseDown = (e) => {
|
|
1557
|
+
if (e.button !== 0 && e.button !== 1) return;
|
|
1558
|
+
if (selected === null || !image.url) return;
|
|
1559
|
+
setIsDragging(true);
|
|
1560
|
+
hasDraggedRef.current = false;
|
|
1561
|
+
dragStartRef.current = {
|
|
1562
|
+
x: e.clientX,
|
|
1563
|
+
y: e.clientY,
|
|
1564
|
+
startX: offset.x,
|
|
1565
|
+
startY: offset.y
|
|
1566
|
+
};
|
|
1567
|
+
};
|
|
1568
|
+
const handleImageClick = () => {
|
|
1569
|
+
if (!hasDraggedRef.current) setLightboxOpen(true);
|
|
1570
|
+
};
|
|
1571
|
+
const addReferenceFiles = (fileList) => {
|
|
1572
|
+
if (!fileList || fileList.length === 0) return;
|
|
1573
|
+
const files = Array.from(fileList);
|
|
1574
|
+
const validFiles = [];
|
|
1575
|
+
for (const file of files) {
|
|
1576
|
+
if (![
|
|
1577
|
+
"image/jpeg",
|
|
1578
|
+
"image/png",
|
|
1579
|
+
"image/webp",
|
|
1580
|
+
"image/gif"
|
|
1581
|
+
].includes(file.type) || file.size > 10485760) {
|
|
1582
|
+
setError(t("uploadInvalid"));
|
|
1583
|
+
continue;
|
|
1584
|
+
}
|
|
1585
|
+
validFiles.push(file);
|
|
1586
|
+
}
|
|
1587
|
+
if (validFiles.length === 0) return;
|
|
1588
|
+
const currentLen = referencesRef.current.length;
|
|
1589
|
+
const available = maxReferences - currentLen;
|
|
1590
|
+
if (available <= 0) {
|
|
1591
|
+
setError(t("maxReferencesExceeded", { max: String(maxReferences) }));
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1594
|
+
const toAdd = validFiles.slice(0, available);
|
|
1595
|
+
if (validFiles.length > available) setError(t("maxReferencesExceeded", { max: String(maxReferences) }));
|
|
1596
|
+
else setError(null);
|
|
1597
|
+
const newItems = toAdd.map((file, idx) => ({
|
|
1598
|
+
id: `upload-${Date.now()}-${idx}-${Math.random().toString(36).slice(2, 7)}`,
|
|
1599
|
+
file,
|
|
1600
|
+
previewUrl: URL.createObjectURL(file)
|
|
1601
|
+
}));
|
|
1602
|
+
setReferences((prev) => [...prev, ...newItems]);
|
|
1603
|
+
};
|
|
1604
|
+
const removeReference = (id) => {
|
|
1605
|
+
const target = referencesRef.current.find((item) => item.id === id);
|
|
1606
|
+
if (target && target.previewUrl.startsWith("blob:")) URL.revokeObjectURL(target.previewUrl);
|
|
1607
|
+
setReferences((prev) => prev.filter((item) => item.id !== id));
|
|
1608
|
+
};
|
|
1609
|
+
const clearAllReferences = () => {
|
|
1610
|
+
for (const item of referencesRef.current) if (item.previewUrl.startsWith("blob:")) URL.revokeObjectURL(item.previewUrl);
|
|
1611
|
+
setReferences([]);
|
|
1612
|
+
};
|
|
1613
|
+
const continueEdit = async () => {
|
|
1614
|
+
if (selected === null) return;
|
|
1615
|
+
const targetItem = selected;
|
|
1616
|
+
const targetAttId = targetItem.attachment.attachmentId;
|
|
1617
|
+
const targetMax = targetItem.provider === "dashscope" ? 3 : 5;
|
|
1618
|
+
if (referencesRef.current.some((r) => r.attachment?.attachmentId === targetAttId)) {
|
|
1619
|
+
setMode("edit");
|
|
1620
|
+
setPanelTab("generate");
|
|
1621
|
+
changeProvider(targetItem.provider);
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
if (referencesRef.current.length >= targetMax) {
|
|
1625
|
+
setError(t("maxReferencesExceeded", { max: String(targetMax) }));
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
try {
|
|
1629
|
+
const blob = image.blob ?? await fetchAttachmentBlob(targetItem.attachment);
|
|
1630
|
+
if (referencesRef.current.some((r) => r.attachment?.attachmentId === targetAttId)) {
|
|
1631
|
+
setMode("edit");
|
|
1632
|
+
setPanelTab("generate");
|
|
1633
|
+
changeProvider(targetItem.provider);
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
if (referencesRef.current.length >= targetMax) {
|
|
1637
|
+
setError(t("maxReferencesExceeded", { max: String(targetMax) }));
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
const independentUrl = URL.createObjectURL(blob);
|
|
1641
|
+
const newItem = {
|
|
1642
|
+
id: `item-${targetItem.id}-${Date.now()}`,
|
|
1643
|
+
attachment: targetItem.attachment,
|
|
1644
|
+
previewUrl: independentUrl
|
|
1645
|
+
};
|
|
1646
|
+
const nextList = [...referencesRef.current, newItem];
|
|
1647
|
+
setReferences(nextList);
|
|
1648
|
+
referencesRef.current = nextList;
|
|
1649
|
+
setError(null);
|
|
1650
|
+
setMode("edit");
|
|
1651
|
+
setPanelTab("generate");
|
|
1652
|
+
changeProvider(targetItem.provider);
|
|
1653
|
+
} catch {
|
|
1654
|
+
setError(t("imageLoadFailed"));
|
|
1655
|
+
}
|
|
1656
|
+
};
|
|
1657
|
+
const cancelGeneration = () => {
|
|
1658
|
+
if (requestRef.current) {
|
|
1659
|
+
requestRef.current.abort();
|
|
1660
|
+
requestRef.current = null;
|
|
1661
|
+
setIsGenerating(false);
|
|
1662
|
+
}
|
|
1663
|
+
};
|
|
1664
|
+
const submit = async () => {
|
|
1665
|
+
if (isGenerating || activeProfile === void 0) return;
|
|
1666
|
+
if (prompt.trim().length === 0) return setError(t("needPrompt"));
|
|
1667
|
+
if (!activeProfile.configured) return setError(t("selectConfigured"));
|
|
1668
|
+
if (mode === "edit" && references.length === 0) return setError(t("needReference"));
|
|
1669
|
+
setIsGenerating(true);
|
|
1670
|
+
setError(null);
|
|
1671
|
+
const controller = new AbortController();
|
|
1672
|
+
requestRef.current = controller;
|
|
1673
|
+
try {
|
|
1674
|
+
const referencesPayload = mode === "edit" && references.length > 0 ? await Promise.all(references.map(async (ref) => {
|
|
1675
|
+
if (ref.attachment !== void 0) return { attachment: ref.attachment };
|
|
1676
|
+
if (ref.file !== void 0) return {
|
|
1677
|
+
mediaType: ref.file.type,
|
|
1678
|
+
data: await fileToBase64(ref.file),
|
|
1679
|
+
...ref.file.name ? { name: ref.file.name } : {}
|
|
1680
|
+
};
|
|
1681
|
+
throw new Error("无效参考图");
|
|
1682
|
+
})) : void 0;
|
|
1683
|
+
const response = await fetch(STUDIO_ROUTE, {
|
|
1684
|
+
method: "POST",
|
|
1685
|
+
credentials: "same-origin",
|
|
1686
|
+
signal: controller.signal,
|
|
1687
|
+
headers: { "content-type": "application/json" },
|
|
1688
|
+
body: JSON.stringify({
|
|
1689
|
+
mode,
|
|
1690
|
+
provider,
|
|
1691
|
+
model,
|
|
1692
|
+
prompt: prompt.trim(),
|
|
1693
|
+
ratio,
|
|
1694
|
+
quality,
|
|
1695
|
+
...workspace?.path ? { workspaceRoot: workspace.path } : {},
|
|
1696
|
+
...referencesPayload === void 0 ? {} : { references: referencesPayload }
|
|
1697
|
+
})
|
|
1698
|
+
});
|
|
1699
|
+
const payload = await response.json();
|
|
1700
|
+
if (!response.ok || !("attachment" in payload)) throw new Error("error" in payload && payload.error ? payload.error : t("generationFailed"));
|
|
1701
|
+
const item = {
|
|
1702
|
+
id: String(payload.attachment.attachmentId),
|
|
1703
|
+
attachment: payload.attachment,
|
|
1704
|
+
prompt: payload.prompt,
|
|
1705
|
+
provider: payload.provider,
|
|
1706
|
+
model: payload.model,
|
|
1707
|
+
createdAt: payload.createdAt,
|
|
1708
|
+
aspectRatio: ratio,
|
|
1709
|
+
imageSize: quality,
|
|
1710
|
+
output: payload.output,
|
|
1711
|
+
...payload.savedTo ? { savedTo: payload.savedTo } : {},
|
|
1712
|
+
...workspace?.path ? { workspacePath: workspace.path } : {},
|
|
1713
|
+
...workspace?.workspaceId ? { workspaceId: workspace.workspaceId } : {}
|
|
1714
|
+
};
|
|
1715
|
+
await saveGalleryItem(item);
|
|
1716
|
+
setSelected(item);
|
|
1717
|
+
setPanelTab("details");
|
|
1718
|
+
resetFit();
|
|
1719
|
+
} catch (submitError) {
|
|
1720
|
+
if (controller.signal.aborted) return;
|
|
1721
|
+
setError(messageOf(submitError));
|
|
1722
|
+
} finally {
|
|
1723
|
+
if (requestRef.current === controller) {
|
|
1724
|
+
requestRef.current = null;
|
|
1725
|
+
setIsGenerating(false);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
};
|
|
1729
|
+
const handleConfirmDelete = async () => {
|
|
1730
|
+
if (selected === null) return;
|
|
1731
|
+
const idToDelete = selected.id;
|
|
1732
|
+
const attachmentId = selected.attachment.attachmentId;
|
|
1733
|
+
const savedPath = selected.savedTo;
|
|
1734
|
+
setShowDeleteModal(false);
|
|
1735
|
+
try {
|
|
1736
|
+
if (deleteWorkspaceFiles && typeof savedPath === "string" && savedPath.trim().length > 0) {
|
|
1737
|
+
const res = await fetch(DELETE_ROUTE, {
|
|
1738
|
+
method: "POST",
|
|
1739
|
+
credentials: "same-origin",
|
|
1740
|
+
headers: { "content-type": "application/json" },
|
|
1741
|
+
body: JSON.stringify({ paths: [savedPath] })
|
|
1742
|
+
});
|
|
1743
|
+
if (!res.ok) throw new Error(`删除请求网络异常 (${res.status})`);
|
|
1744
|
+
const data = await res.json().catch(() => null);
|
|
1745
|
+
if (!data || data.ok === false || Array.isArray(data.failedFiles) && data.failedFiles.length > 0) {
|
|
1746
|
+
const reason = data?.failedFiles?.[0]?.error || "工作区文件删除失败";
|
|
1747
|
+
throw new Error(reason);
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
await deleteGalleryItem(idToDelete);
|
|
1751
|
+
evictAttachmentCache(attachmentId);
|
|
1752
|
+
flash(t("deletedToast"));
|
|
1753
|
+
} catch (delError) {
|
|
1754
|
+
setError(messageOf(delError));
|
|
1755
|
+
}
|
|
1756
|
+
};
|
|
1757
|
+
const copySelected = async () => {
|
|
1758
|
+
if (image.blob === null) return;
|
|
1759
|
+
const ok = await copyImageBlob(image.blob);
|
|
1760
|
+
flash(ok ? t("copiedImage") : t("copyFailed"));
|
|
1761
|
+
};
|
|
1762
|
+
const downloadSelected = () => {
|
|
1763
|
+
if (image.url === null || selected === null) return;
|
|
1764
|
+
downloadBlobUrl(image.url, `dsh-${selected.provider}-${selected.id.slice(-8)}.${extension(selected.attachment.mediaType)}`);
|
|
1765
|
+
};
|
|
1766
|
+
const toggleFavorite = async () => {
|
|
1767
|
+
if (selected === null) return;
|
|
1768
|
+
const targetId = selected.id;
|
|
1769
|
+
const nextFav = await toggleFavoriteGalleryItem(targetId);
|
|
1770
|
+
setSelected((curr) => curr !== null && curr.id === targetId ? {
|
|
1771
|
+
...curr,
|
|
1772
|
+
isFavorite: nextFav
|
|
1773
|
+
} : curr);
|
|
1774
|
+
flash(nextFav ? t("favoriteAdded") : t("favoriteRemoved"));
|
|
1775
|
+
};
|
|
1776
|
+
const flash = (message) => {
|
|
1777
|
+
setNotice(message);
|
|
1778
|
+
window.setTimeout(() => setNotice(null), 2200);
|
|
1779
|
+
};
|
|
1780
|
+
return (0, react_jsx_runtime.jsxs)("section", {
|
|
1781
|
+
className: "dsh-ig-workbench",
|
|
1782
|
+
"aria-label": t("title"),
|
|
1783
|
+
children: [
|
|
1784
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1785
|
+
className: `dsh-ig-workbench-grid ${sidebarCollapsed ? "is-sidebar-collapsed" : ""}`,
|
|
1786
|
+
children: [
|
|
1787
|
+
!sidebarCollapsed && (0, react_jsx_runtime.jsxs)("aside", {
|
|
1788
|
+
className: "dsh-ig-recent-panel",
|
|
1789
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
1790
|
+
className: "dsh-ig-panel-title",
|
|
1791
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
1792
|
+
style: {
|
|
1793
|
+
display: "flex",
|
|
1794
|
+
alignItems: "center",
|
|
1795
|
+
gap: "8px"
|
|
1796
|
+
},
|
|
1797
|
+
children: [(0, react_jsx_runtime.jsx)("span", { children: t("recent") }), (0, react_jsx_runtime.jsx)("span", {
|
|
1798
|
+
className: "dsh-ig-count-badge",
|
|
1799
|
+
children: items.length
|
|
1800
|
+
})]
|
|
1801
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
1802
|
+
type: "button",
|
|
1803
|
+
className: "dsh-ig-collapse-btn",
|
|
1804
|
+
onClick: () => setSidebarCollapsed(true),
|
|
1805
|
+
title: t("collapseSidebar"),
|
|
1806
|
+
children: (0, react_jsx_runtime.jsx)(PanelLeftClose, { size: 15 })
|
|
1807
|
+
})]
|
|
1808
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
1809
|
+
className: "dsh-ig-recent-scroll",
|
|
1810
|
+
children: [items.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
|
|
1811
|
+
className: "dsh-ig-recent-empty",
|
|
1812
|
+
children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 22 }), (0, react_jsx_runtime.jsx)("span", { children: t("empty") })]
|
|
1813
|
+
}) : displayItems.map((item) => (0, react_jsx_runtime.jsx)(RecentItem, {
|
|
1814
|
+
item,
|
|
1815
|
+
active: selected?.id === item.id,
|
|
1816
|
+
lang,
|
|
1817
|
+
onClick: () => selectItem(item)
|
|
1818
|
+
}, item.id)), items.length > visibleLimit && (0, react_jsx_runtime.jsx)("button", {
|
|
1819
|
+
type: "button",
|
|
1820
|
+
className: "dsh-ig-load-more",
|
|
1821
|
+
onClick: () => setVisibleLimit((l) => l + 30),
|
|
1822
|
+
children: t("loadMore", { n: String(items.length - visibleLimit) })
|
|
1823
|
+
})]
|
|
1824
|
+
})]
|
|
1825
|
+
}),
|
|
1826
|
+
(0, react_jsx_runtime.jsxs)("main", {
|
|
1827
|
+
className: "dsh-ig-canvas-column",
|
|
1828
|
+
children: [
|
|
1829
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1830
|
+
className: "dsh-ig-canvas-toolbar",
|
|
1831
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
1832
|
+
className: "dsh-ig-canvas-toolbar-left",
|
|
1833
|
+
children: [sidebarCollapsed && (0, react_jsx_runtime.jsxs)("button", {
|
|
1834
|
+
type: "button",
|
|
1835
|
+
onClick: () => setSidebarCollapsed(false),
|
|
1836
|
+
title: t("expandSidebar"),
|
|
1837
|
+
children: [(0, react_jsx_runtime.jsx)(PanelLeft, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("recent") })]
|
|
1838
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
1839
|
+
className: "dsh-ig-status-badge-wrap",
|
|
1840
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
1841
|
+
className: "dsh-ig-status-badge",
|
|
1842
|
+
children: [
|
|
1843
|
+
(0, react_jsx_runtime.jsx)("span", { className: `dsh-ig-status-dot ${configuredCount > 0 ? "is-ready" : "is-muted"}` }),
|
|
1844
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1845
|
+
className: "dsh-ig-status-text",
|
|
1846
|
+
children: configuredCount > 0 ? t("configuredCount", { count: String(configuredCount) }) : t("unconfiguredStatus")
|
|
1847
|
+
}),
|
|
1848
|
+
(0, react_jsx_runtime.jsx)(ChevronDown, {
|
|
1849
|
+
size: 11,
|
|
1850
|
+
className: "dsh-ig-status-arrow"
|
|
1851
|
+
})
|
|
1852
|
+
]
|
|
1853
|
+
}), config !== null && (0, react_jsx_runtime.jsxs)("div", {
|
|
1854
|
+
className: "dsh-ig-status-popover",
|
|
1855
|
+
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
1856
|
+
className: "dsh-ig-status-popover-title",
|
|
1857
|
+
children: t("providerStatus")
|
|
1858
|
+
}), (0, react_jsx_runtime.jsx)("ul", {
|
|
1859
|
+
className: "dsh-ig-status-list",
|
|
1860
|
+
children: config.providers.map((p) => (0, react_jsx_runtime.jsxs)("li", {
|
|
1861
|
+
className: p.configured ? "is-configured" : "is-missing",
|
|
1862
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
1863
|
+
className: "dsh-ig-status-item-name",
|
|
1864
|
+
children: [(0, react_jsx_runtime.jsx)("strong", { children: p.label }), (0, react_jsx_runtime.jsx)("small", { children: p.model })]
|
|
1865
|
+
}), (0, react_jsx_runtime.jsx)("span", {
|
|
1866
|
+
className: "dsh-ig-status-item-tag",
|
|
1867
|
+
children: p.configured ? t("configured") : t("unconfigured")
|
|
1868
|
+
})]
|
|
1869
|
+
}, p.provider))
|
|
1870
|
+
})]
|
|
1871
|
+
})]
|
|
1872
|
+
})]
|
|
1873
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
1874
|
+
className: "dsh-ig-canvas-toolbar-right",
|
|
1875
|
+
children: [
|
|
1876
|
+
selected !== null && (0, react_jsx_runtime.jsxs)("button", {
|
|
1877
|
+
type: "button",
|
|
1878
|
+
onClick: startNew,
|
|
1879
|
+
title: t("newGeneration"),
|
|
1880
|
+
children: [(0, react_jsx_runtime.jsx)(Plus, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: t("newGeneration") })]
|
|
1881
|
+
}),
|
|
1882
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
1883
|
+
type: "button",
|
|
1884
|
+
className: fit ? "is-active" : "",
|
|
1885
|
+
onClick: resetFit,
|
|
1886
|
+
children: [
|
|
1887
|
+
t("fit"),
|
|
1888
|
+
" ",
|
|
1889
|
+
(0, react_jsx_runtime.jsx)(ChevronDown, { size: 13 })
|
|
1890
|
+
]
|
|
1891
|
+
}),
|
|
1892
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1893
|
+
type: "button",
|
|
1894
|
+
onClick: () => {
|
|
1895
|
+
setFit(false);
|
|
1896
|
+
setZoom((value) => Math.max(25, value - 25));
|
|
1897
|
+
},
|
|
1898
|
+
title: "Zoom out",
|
|
1899
|
+
children: (0, react_jsx_runtime.jsx)(ZoomOut, { size: 16 })
|
|
1900
|
+
}),
|
|
1901
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1902
|
+
className: "dsh-ig-zoom-display",
|
|
1903
|
+
children: fit ? "AUTO" : `${Math.round(zoom)}%`
|
|
1904
|
+
}),
|
|
1905
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1906
|
+
type: "button",
|
|
1907
|
+
onClick: () => {
|
|
1908
|
+
setFit(false);
|
|
1909
|
+
setZoom((value) => Math.min(500, value + 25));
|
|
1910
|
+
},
|
|
1911
|
+
title: "Zoom in",
|
|
1912
|
+
children: (0, react_jsx_runtime.jsx)(ZoomIn, { size: 16 })
|
|
1913
|
+
}),
|
|
1914
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1915
|
+
type: "button",
|
|
1916
|
+
onClick: () => setLightboxOpen(true),
|
|
1917
|
+
title: t("fullscreen"),
|
|
1918
|
+
disabled: selected === null,
|
|
1919
|
+
children: (0, react_jsx_runtime.jsx)(Expand, { size: 16 })
|
|
1920
|
+
})
|
|
1921
|
+
]
|
|
1922
|
+
})]
|
|
1923
|
+
}),
|
|
1924
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
1925
|
+
className: `dsh-ig-canvas ${isDragging ? "is-dragging" : ""}`,
|
|
1926
|
+
ref: canvasRef,
|
|
1927
|
+
onMouseDown: handleCanvasMouseDown,
|
|
1928
|
+
onDoubleClick: resetFit,
|
|
1929
|
+
children: isGenerating ? (0, react_jsx_runtime.jsxs)("div", {
|
|
1930
|
+
className: "dsh-ig-generating-state",
|
|
1931
|
+
children: [
|
|
1932
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
1933
|
+
className: "dsh-ig-generation-orbit",
|
|
1934
|
+
children: (0, react_jsx_runtime.jsx)(Sparkles, { size: 28 })
|
|
1935
|
+
}),
|
|
1936
|
+
(0, react_jsx_runtime.jsx)("strong", { children: t("generating") }),
|
|
1937
|
+
(0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1938
|
+
activeProfile?.label,
|
|
1939
|
+
" · ",
|
|
1940
|
+
model
|
|
1941
|
+
] })
|
|
1942
|
+
]
|
|
1943
|
+
}) : selected === null ? (0, react_jsx_runtime.jsxs)("div", {
|
|
1944
|
+
className: "dsh-ig-canvas-empty",
|
|
1945
|
+
children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 36 }), (0, react_jsx_runtime.jsx)("span", { children: t("selectHistory") })]
|
|
1946
|
+
}) : image.loading ? (0, react_jsx_runtime.jsxs)("div", {
|
|
1947
|
+
className: "dsh-ig-canvas-empty",
|
|
1948
|
+
children: [(0, react_jsx_runtime.jsx)(LoaderCircle, {
|
|
1949
|
+
className: "dsh-ig-spin",
|
|
1950
|
+
size: 28
|
|
1951
|
+
}), (0, react_jsx_runtime.jsx)("span", { children: t("loading") })]
|
|
1952
|
+
}) : image.url !== null ? (0, react_jsx_runtime.jsx)("img", {
|
|
1953
|
+
src: image.url,
|
|
1954
|
+
alt: selected.prompt,
|
|
1955
|
+
draggable: false,
|
|
1956
|
+
onClick: handleImageClick,
|
|
1957
|
+
style: {
|
|
1958
|
+
transform: `translate(${offset.x}px, ${offset.y}px) scale(${zoom / 100})`,
|
|
1959
|
+
transformOrigin: "center center",
|
|
1960
|
+
transition: isDragging ? "none" : "transform 0.08s ease-out"
|
|
1961
|
+
}
|
|
1962
|
+
}) : (0, react_jsx_runtime.jsxs)("div", {
|
|
1963
|
+
className: "dsh-ig-canvas-empty",
|
|
1964
|
+
children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 32 }), (0, react_jsx_runtime.jsx)("span", { children: t("imageLoadFailed") })]
|
|
1965
|
+
})
|
|
1966
|
+
}),
|
|
1967
|
+
selected !== null && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1968
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1969
|
+
className: "dsh-ig-result-strip",
|
|
1970
|
+
children: [(0, react_jsx_runtime.jsx)("span", { children: t("result") }), (0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
1971
|
+
type: "button",
|
|
1972
|
+
onClick: () => void continueEdit(),
|
|
1973
|
+
children: [(0, react_jsx_runtime.jsx)(PencilLine, { size: 15 }), t("continueEdit")]
|
|
1974
|
+
}), (0, react_jsx_runtime.jsxs)("button", {
|
|
1975
|
+
type: "button",
|
|
1976
|
+
onClick: () => {
|
|
1977
|
+
setMode("generate");
|
|
1978
|
+
setPanelTab("generate");
|
|
1979
|
+
setPrompt(selected.prompt);
|
|
1980
|
+
},
|
|
1981
|
+
children: [(0, react_jsx_runtime.jsx)(RefreshCw, { size: 15 }), t("regenerate")]
|
|
1982
|
+
})] })]
|
|
1983
|
+
}),
|
|
1984
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1985
|
+
className: "dsh-ig-result-actions",
|
|
1986
|
+
children: [(0, react_jsx_runtime.jsx)("p", { children: selected.prompt }), (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
1987
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
1988
|
+
type: "button",
|
|
1989
|
+
onClick: () => void toggleFavorite(),
|
|
1990
|
+
title: selected.isFavorite ? t("favorited") : t("favorite"),
|
|
1991
|
+
children: [(0, react_jsx_runtime.jsx)(Heart, {
|
|
1992
|
+
size: 15,
|
|
1993
|
+
fill: selected.isFavorite ? "#ef4444" : "none",
|
|
1994
|
+
color: selected.isFavorite ? "#ef4444" : "currentColor"
|
|
1995
|
+
}), (0, react_jsx_runtime.jsx)("span", { children: selected.isFavorite ? t("favorited") : t("favorite") })]
|
|
1996
|
+
}),
|
|
1997
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
1998
|
+
type: "button",
|
|
1999
|
+
onClick: () => void copySelected(),
|
|
2000
|
+
children: [(0, react_jsx_runtime.jsx)(Clipboard, { size: 15 }), (0, react_jsx_runtime.jsx)("span", { children: t("copy") })]
|
|
2001
|
+
}),
|
|
2002
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
2003
|
+
type: "button",
|
|
2004
|
+
onClick: downloadSelected,
|
|
2005
|
+
children: [(0, react_jsx_runtime.jsx)(Download, { size: 15 }), (0, react_jsx_runtime.jsx)("span", { children: t("download") })]
|
|
2006
|
+
}),
|
|
2007
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
2008
|
+
type: "button",
|
|
2009
|
+
onClick: () => setShowDeleteModal(true),
|
|
2010
|
+
children: [(0, react_jsx_runtime.jsx)(Trash2, { size: 15 }), (0, react_jsx_runtime.jsx)("span", { children: t("remove") })]
|
|
2011
|
+
})
|
|
2012
|
+
] })]
|
|
2013
|
+
}),
|
|
2014
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2015
|
+
className: "dsh-ig-result-meta",
|
|
2016
|
+
children: [
|
|
2017
|
+
(0, react_jsx_runtime.jsx)("span", { children: formatRelativeTime$1(selected.createdAt, lang) }),
|
|
2018
|
+
(0, react_jsx_runtime.jsx)("span", { children: selected.provider }),
|
|
2019
|
+
(0, react_jsx_runtime.jsx)("span", { children: selected.model }),
|
|
2020
|
+
(0, react_jsx_runtime.jsx)("span", { children: selected.attachment.width && selected.attachment.height ? `${selected.attachment.width} × ${selected.attachment.height}` : "—" })
|
|
2021
|
+
]
|
|
2022
|
+
})
|
|
2023
|
+
] })
|
|
2024
|
+
]
|
|
2025
|
+
}),
|
|
2026
|
+
(0, react_jsx_runtime.jsxs)("aside", {
|
|
2027
|
+
className: "dsh-ig-generate-panel",
|
|
2028
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
2029
|
+
className: "dsh-ig-panel-tabs",
|
|
2030
|
+
children: [(0, react_jsx_runtime.jsx)("button", {
|
|
2031
|
+
type: "button",
|
|
2032
|
+
className: panelTab === "generate" ? "is-active" : "",
|
|
2033
|
+
onClick: () => setPanelTab("generate"),
|
|
2034
|
+
children: t("generate")
|
|
2035
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
2036
|
+
type: "button",
|
|
2037
|
+
className: panelTab === "details" ? "is-active" : "",
|
|
2038
|
+
onClick: () => setPanelTab("details"),
|
|
2039
|
+
children: t("details")
|
|
2040
|
+
})]
|
|
2041
|
+
}), panelTab === "details" ? (0, react_jsx_runtime.jsx)(DetailsPanel, {
|
|
2042
|
+
item: selected,
|
|
2043
|
+
t
|
|
2044
|
+
}) : (0, react_jsx_runtime.jsxs)("div", {
|
|
2045
|
+
className: "dsh-ig-generator-form",
|
|
2046
|
+
children: [
|
|
2047
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2048
|
+
className: "dsh-ig-mode-switch",
|
|
2049
|
+
children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
2050
|
+
type: "button",
|
|
2051
|
+
className: mode === "generate" ? "is-active" : "",
|
|
2052
|
+
onClick: () => setMode("generate"),
|
|
2053
|
+
children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 15 }), t("generate")]
|
|
2054
|
+
}), (0, react_jsx_runtime.jsxs)("button", {
|
|
2055
|
+
type: "button",
|
|
2056
|
+
className: mode === "edit" ? "is-active" : "",
|
|
2057
|
+
onClick: () => setMode("edit"),
|
|
2058
|
+
children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 15 }), t("edit")]
|
|
2059
|
+
})]
|
|
2060
|
+
}),
|
|
2061
|
+
mode === "edit" && (0, react_jsx_runtime.jsxs)("div", {
|
|
2062
|
+
className: "dsh-ig-field",
|
|
2063
|
+
children: [
|
|
2064
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2065
|
+
className: "dsh-ig-field-label",
|
|
2066
|
+
children: [(0, react_jsx_runtime.jsxs)("label", { children: [
|
|
2067
|
+
t("referencesCount", {
|
|
2068
|
+
current: String(references.length),
|
|
2069
|
+
max: String(maxReferences)
|
|
2070
|
+
}),
|
|
2071
|
+
" ",
|
|
2072
|
+
(0, react_jsx_runtime.jsx)("b", { children: "*" })
|
|
2073
|
+
] }), references.length > 0 && (0, react_jsx_runtime.jsx)("button", {
|
|
2074
|
+
type: "button",
|
|
2075
|
+
onClick: clearAllReferences,
|
|
2076
|
+
children: t("clear")
|
|
2077
|
+
})]
|
|
2078
|
+
}),
|
|
2079
|
+
references.length === 0 ? (0, react_jsx_runtime.jsxs)("button", {
|
|
2080
|
+
type: "button",
|
|
2081
|
+
className: `dsh-ig-upload ${dragging ? "is-dragging" : ""}`,
|
|
2082
|
+
onClick: () => fileInputRef.current?.click(),
|
|
2083
|
+
onDragOver: (event) => {
|
|
2084
|
+
event.preventDefault();
|
|
2085
|
+
setDragging(true);
|
|
2086
|
+
},
|
|
2087
|
+
onDragLeave: () => setDragging(false),
|
|
2088
|
+
onDrop: (event) => {
|
|
2089
|
+
event.preventDefault();
|
|
2090
|
+
setDragging(false);
|
|
2091
|
+
addReferenceFiles(event.dataTransfer.files);
|
|
2092
|
+
},
|
|
2093
|
+
children: [
|
|
2094
|
+
(0, react_jsx_runtime.jsx)(Upload, { size: 20 }),
|
|
2095
|
+
(0, react_jsx_runtime.jsx)("strong", { children: t("upload") }),
|
|
2096
|
+
(0, react_jsx_runtime.jsx)("small", { children: t("uploadHint") })
|
|
2097
|
+
]
|
|
2098
|
+
}) : (0, react_jsx_runtime.jsxs)("div", {
|
|
2099
|
+
className: "dsh-ig-reference-grid",
|
|
2100
|
+
children: [references.map((item, index) => (0, react_jsx_runtime.jsxs)("div", {
|
|
2101
|
+
className: "dsh-ig-reference-card",
|
|
2102
|
+
children: [(0, react_jsx_runtime.jsx)("img", {
|
|
2103
|
+
src: item.previewUrl,
|
|
2104
|
+
alt: `Ref ${index + 1}`
|
|
2105
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
2106
|
+
type: "button",
|
|
2107
|
+
title: t("closeReference"),
|
|
2108
|
+
onClick: () => removeReference(item.id),
|
|
2109
|
+
children: (0, react_jsx_runtime.jsx)(X, { size: 12 })
|
|
2110
|
+
})]
|
|
2111
|
+
}, item.id)), references.length < maxReferences && (0, react_jsx_runtime.jsxs)("button", {
|
|
2112
|
+
type: "button",
|
|
2113
|
+
className: "dsh-ig-reference-add",
|
|
2114
|
+
onClick: () => fileInputRef.current?.click(),
|
|
2115
|
+
title: t("upload"),
|
|
2116
|
+
children: [(0, react_jsx_runtime.jsx)(Upload, { size: 16 }), (0, react_jsx_runtime.jsx)("span", { children: t("addMoreRef") })]
|
|
2117
|
+
})]
|
|
2118
|
+
}),
|
|
2119
|
+
(0, react_jsx_runtime.jsx)("input", {
|
|
2120
|
+
ref: fileInputRef,
|
|
2121
|
+
type: "file",
|
|
2122
|
+
multiple: true,
|
|
2123
|
+
accept: "image/png,image/jpeg,image/webp,image/gif",
|
|
2124
|
+
hidden: true,
|
|
2125
|
+
onChange: (event) => {
|
|
2126
|
+
addReferenceFiles(event.target.files);
|
|
2127
|
+
event.target.value = "";
|
|
2128
|
+
}
|
|
2129
|
+
})
|
|
2130
|
+
]
|
|
2131
|
+
}),
|
|
2132
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2133
|
+
className: "dsh-ig-field",
|
|
2134
|
+
children: [
|
|
2135
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2136
|
+
className: "dsh-ig-field-label",
|
|
2137
|
+
children: [(0, react_jsx_runtime.jsxs)("label", {
|
|
2138
|
+
htmlFor: "dsh-ig-prompt",
|
|
2139
|
+
children: [
|
|
2140
|
+
t("prompt"),
|
|
2141
|
+
" ",
|
|
2142
|
+
(0, react_jsx_runtime.jsx)("b", { children: "*" })
|
|
2143
|
+
]
|
|
2144
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
2145
|
+
type: "button",
|
|
2146
|
+
onClick: () => setPrompt(""),
|
|
2147
|
+
children: t("clear")
|
|
2148
|
+
})]
|
|
2149
|
+
}),
|
|
2150
|
+
(0, react_jsx_runtime.jsx)("textarea", {
|
|
2151
|
+
id: "dsh-ig-prompt",
|
|
2152
|
+
value: prompt,
|
|
2153
|
+
maxLength: 2e3,
|
|
2154
|
+
onChange: (event) => setPrompt(event.target.value),
|
|
2155
|
+
onKeyDown: (event) => {
|
|
2156
|
+
if (event.nativeEvent.isComposing) return;
|
|
2157
|
+
if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
|
|
2158
|
+
event.preventDefault();
|
|
2159
|
+
if (!isGenerating && config !== null && prompt.trim().length > 0) submit();
|
|
2160
|
+
}
|
|
2161
|
+
},
|
|
2162
|
+
placeholder: t("promptPlaceholder")
|
|
2163
|
+
}),
|
|
2164
|
+
(0, react_jsx_runtime.jsxs)("small", { children: [prompt.length, "/2000"] })
|
|
2165
|
+
]
|
|
2166
|
+
}),
|
|
2167
|
+
config === null ? configLoading ? (0, react_jsx_runtime.jsx)("div", {
|
|
2168
|
+
className: "dsh-ig-form-loading",
|
|
2169
|
+
children: (0, react_jsx_runtime.jsx)(LoaderCircle, {
|
|
2170
|
+
className: "dsh-ig-spin",
|
|
2171
|
+
size: 18
|
|
2172
|
+
})
|
|
2173
|
+
}) : (0, react_jsx_runtime.jsxs)("div", {
|
|
2174
|
+
className: "dsh-ig-config-error",
|
|
2175
|
+
children: [(0, react_jsx_runtime.jsx)("p", { children: configError ?? t("configLoadFailed") }), (0, react_jsx_runtime.jsxs)("button", {
|
|
2176
|
+
type: "button",
|
|
2177
|
+
onClick: () => void loadConfig(),
|
|
2178
|
+
children: [(0, react_jsx_runtime.jsx)(RefreshCw, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: t("retry") })]
|
|
2179
|
+
})]
|
|
2180
|
+
}) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
2181
|
+
className: "dsh-ig-field-grid",
|
|
2182
|
+
children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
|
|
2183
|
+
label: t("provider"),
|
|
2184
|
+
value: provider,
|
|
2185
|
+
onChange: changeProvider,
|
|
2186
|
+
options: config.providers.map((item) => ({
|
|
2187
|
+
value: item.provider,
|
|
2188
|
+
label: `${item.label}${item.configured ? "" : ` · ${t("unconfigured")}`}`
|
|
2189
|
+
}))
|
|
2190
|
+
}), (0, react_jsx_runtime.jsx)(FieldSelect, {
|
|
2191
|
+
label: t("model"),
|
|
2192
|
+
value: model,
|
|
2193
|
+
onChange: setModel,
|
|
2194
|
+
options: activeProfile === void 0 ? [] : [{
|
|
2195
|
+
value: activeProfile.model,
|
|
2196
|
+
label: activeProfile.model
|
|
2197
|
+
}]
|
|
2198
|
+
})]
|
|
2199
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
2200
|
+
className: "dsh-ig-field-grid",
|
|
2201
|
+
children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
|
|
2202
|
+
label: t("ratio"),
|
|
2203
|
+
value: ratio,
|
|
2204
|
+
onChange: setRatio,
|
|
2205
|
+
options: activeProfile?.ratioOptions ?? []
|
|
2206
|
+
}), (0, react_jsx_runtime.jsx)(FieldSelect, {
|
|
2207
|
+
label: t("quality"),
|
|
2208
|
+
value: quality,
|
|
2209
|
+
onChange: setQuality,
|
|
2210
|
+
options: activeProfile?.qualityOptions ?? []
|
|
2211
|
+
})]
|
|
2212
|
+
})] }),
|
|
2213
|
+
configuredCount === 0 && config !== null && (0, react_jsx_runtime.jsx)("div", {
|
|
2214
|
+
className: "dsh-ig-inline-note",
|
|
2215
|
+
children: t("noProvider")
|
|
2216
|
+
}),
|
|
2217
|
+
error !== null && (0, react_jsx_runtime.jsx)("div", {
|
|
2218
|
+
className: "dsh-ig-form-error",
|
|
2219
|
+
children: error
|
|
2220
|
+
}),
|
|
2221
|
+
isGenerating ? (0, react_jsx_runtime.jsxs)("button", {
|
|
2222
|
+
type: "button",
|
|
2223
|
+
className: "dsh-ig-generate-button is-cancel",
|
|
2224
|
+
onClick: cancelGeneration,
|
|
2225
|
+
children: [(0, react_jsx_runtime.jsx)(LoaderCircle, {
|
|
2226
|
+
className: "dsh-ig-spin",
|
|
2227
|
+
size: 17
|
|
2228
|
+
}), (0, react_jsx_runtime.jsx)("span", { children: t("cancelGenerate") })]
|
|
2229
|
+
}) : (0, react_jsx_runtime.jsxs)("button", {
|
|
2230
|
+
type: "button",
|
|
2231
|
+
className: "dsh-ig-generate-button",
|
|
2232
|
+
disabled: config === null,
|
|
2233
|
+
onClick: () => void submit(),
|
|
2234
|
+
children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 17 }), (0, react_jsx_runtime.jsx)("span", { children: t("start") })]
|
|
2235
|
+
})
|
|
2236
|
+
]
|
|
2237
|
+
})]
|
|
2238
|
+
})
|
|
2239
|
+
]
|
|
2240
|
+
}),
|
|
2241
|
+
lightboxOpen && selected !== null && (0, react_jsx_runtime.jsxs)("div", {
|
|
2242
|
+
className: "dsh-ig-lightbox-backdrop",
|
|
2243
|
+
onClick: () => setLightboxOpen(false),
|
|
2244
|
+
children: [
|
|
2245
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2246
|
+
className: "dsh-ig-lightbox-topbar",
|
|
2247
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
2248
|
+
className: "dsh-ig-lightbox-meta",
|
|
2249
|
+
children: [
|
|
2250
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
2251
|
+
className: "dsh-ig-tag",
|
|
2252
|
+
children: selected.provider
|
|
2253
|
+
}),
|
|
2254
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
2255
|
+
className: "dsh-ig-tag dsh-ig-tag-model",
|
|
2256
|
+
children: selected.model
|
|
2257
|
+
}),
|
|
2258
|
+
selected.attachment.width && selected.attachment.height ? (0, react_jsx_runtime.jsxs)("span", {
|
|
2259
|
+
className: "dsh-ig-tag",
|
|
2260
|
+
children: [
|
|
2261
|
+
selected.attachment.width,
|
|
2262
|
+
" × ",
|
|
2263
|
+
selected.attachment.height
|
|
2264
|
+
]
|
|
2265
|
+
}) : null
|
|
2266
|
+
]
|
|
2267
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
2268
|
+
type: "button",
|
|
2269
|
+
className: "dsh-ig-lightbox-close-btn",
|
|
2270
|
+
title: t("close"),
|
|
2271
|
+
onClick: () => setLightboxOpen(false),
|
|
2272
|
+
children: (0, react_jsx_runtime.jsx)(X, { size: 18 })
|
|
2273
|
+
})]
|
|
2274
|
+
}),
|
|
2275
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
2276
|
+
className: "dsh-ig-lightbox-img-wrap",
|
|
2277
|
+
onClick: (e) => e.stopPropagation(),
|
|
2278
|
+
children: image.url !== null ? (0, react_jsx_runtime.jsx)("img", {
|
|
2279
|
+
className: "dsh-ig-lightbox-img",
|
|
2280
|
+
src: image.url,
|
|
2281
|
+
alt: selected.prompt
|
|
2282
|
+
}) : (0, react_jsx_runtime.jsx)("div", {
|
|
2283
|
+
className: "dsh-ig-lightbox-loading",
|
|
2284
|
+
children: (0, react_jsx_runtime.jsx)("div", { className: "dsh-ig-lightbox-spinner" })
|
|
2285
|
+
})
|
|
2286
|
+
}),
|
|
2287
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2288
|
+
className: "dsh-ig-lightbox-bottombar",
|
|
2289
|
+
onClick: (e) => e.stopPropagation(),
|
|
2290
|
+
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
2291
|
+
className: "dsh-ig-lightbox-prompt-text",
|
|
2292
|
+
title: selected.prompt,
|
|
2293
|
+
children: selected.prompt
|
|
2294
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
2295
|
+
className: "dsh-ig-lightbox-actions",
|
|
2296
|
+
children: [
|
|
2297
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
2298
|
+
type: "button",
|
|
2299
|
+
className: "dsh-ig-lightbox-btn",
|
|
2300
|
+
title: t("copyPpt"),
|
|
2301
|
+
onClick: async () => {
|
|
2302
|
+
await navigator.clipboard.writeText(selected.prompt);
|
|
2303
|
+
flash(t("copiedPrompt"));
|
|
2304
|
+
},
|
|
2305
|
+
children: [(0, react_jsx_runtime.jsx)(FileText, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("copyPpt") })]
|
|
2306
|
+
}),
|
|
2307
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
2308
|
+
type: "button",
|
|
2309
|
+
className: "dsh-ig-lightbox-btn",
|
|
2310
|
+
title: t("copy"),
|
|
2311
|
+
onClick: async () => {
|
|
2312
|
+
if (!image.blob) return;
|
|
2313
|
+
const ok = await copyImageBlob(image.blob);
|
|
2314
|
+
flash(ok ? t("copiedImage") : t("copyFailed"));
|
|
2315
|
+
},
|
|
2316
|
+
children: [(0, react_jsx_runtime.jsx)(Copy, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("copy") })]
|
|
2317
|
+
}),
|
|
2318
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
2319
|
+
type: "button",
|
|
2320
|
+
className: "dsh-ig-lightbox-btn",
|
|
2321
|
+
title: t("download"),
|
|
2322
|
+
onClick: downloadSelected,
|
|
2323
|
+
children: [(0, react_jsx_runtime.jsx)(Download, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("download") })]
|
|
2324
|
+
}),
|
|
2325
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
2326
|
+
type: "button",
|
|
2327
|
+
className: "dsh-ig-lightbox-btn",
|
|
2328
|
+
title: selected.isFavorite ? t("favorited") : t("favorite"),
|
|
2329
|
+
onClick: () => void toggleFavorite(),
|
|
2330
|
+
children: [(0, react_jsx_runtime.jsx)(Heart, {
|
|
2331
|
+
size: 14,
|
|
2332
|
+
fill: selected.isFavorite ? "#ef4444" : "none",
|
|
2333
|
+
color: selected.isFavorite ? "#ef4444" : "currentColor"
|
|
2334
|
+
}), (0, react_jsx_runtime.jsx)("span", { children: selected.isFavorite ? t("favorited") : t("favorite") })]
|
|
2335
|
+
})
|
|
2336
|
+
]
|
|
2337
|
+
})]
|
|
2338
|
+
})
|
|
2339
|
+
]
|
|
2340
|
+
}),
|
|
2341
|
+
showDeleteModal && selected !== null && (0, react_jsx_runtime.jsx)("div", {
|
|
2342
|
+
className: "dsh-ig-workbench-modal-backdrop",
|
|
2343
|
+
onClick: () => setShowDeleteModal(false),
|
|
2344
|
+
children: (0, react_jsx_runtime.jsxs)("div", {
|
|
2345
|
+
className: "dsh-ig-workbench-modal-box",
|
|
2346
|
+
onClick: (e) => e.stopPropagation(),
|
|
2347
|
+
children: [
|
|
2348
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2349
|
+
className: "dsh-ig-workbench-modal-header",
|
|
2350
|
+
children: [(0, react_jsx_runtime.jsx)(TriangleAlert, {
|
|
2351
|
+
size: 20,
|
|
2352
|
+
color: "#dc2626"
|
|
2353
|
+
}), (0, react_jsx_runtime.jsx)("strong", { children: t("deleteModalTitle") })]
|
|
2354
|
+
}),
|
|
2355
|
+
(0, react_jsx_runtime.jsx)("p", {
|
|
2356
|
+
className: "dsh-ig-workbench-modal-desc",
|
|
2357
|
+
children: t("deleteModalDesc")
|
|
2358
|
+
}),
|
|
2359
|
+
Boolean(selected.savedTo) && (0, react_jsx_runtime.jsxs)("label", {
|
|
2360
|
+
className: "dsh-ig-workbench-modal-option",
|
|
2361
|
+
children: [(0, react_jsx_runtime.jsx)("input", {
|
|
2362
|
+
type: "checkbox",
|
|
2363
|
+
checked: deleteWorkspaceFiles,
|
|
2364
|
+
onChange: (e) => setDeleteWorkspaceFiles(e.target.checked)
|
|
2365
|
+
}), (0, react_jsx_runtime.jsx)("span", { children: t("deleteWorkspaceFilesLabel") })]
|
|
2366
|
+
}),
|
|
2367
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2368
|
+
className: "dsh-ig-workbench-modal-actions",
|
|
2369
|
+
children: [(0, react_jsx_runtime.jsx)("button", {
|
|
2370
|
+
type: "button",
|
|
2371
|
+
className: "dsh-ig-workbench-modal-cancel",
|
|
2372
|
+
onClick: () => setShowDeleteModal(false),
|
|
2373
|
+
children: t("cancel")
|
|
2374
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
2375
|
+
type: "button",
|
|
2376
|
+
className: "dsh-ig-workbench-modal-danger",
|
|
2377
|
+
onClick: () => void handleConfirmDelete(),
|
|
2378
|
+
children: t("confirm")
|
|
2379
|
+
})]
|
|
2380
|
+
})
|
|
2381
|
+
]
|
|
2382
|
+
})
|
|
2383
|
+
}),
|
|
2384
|
+
notice !== null && (0, react_jsx_runtime.jsx)("div", {
|
|
2385
|
+
className: "dsh-ig-workbench-toast",
|
|
2386
|
+
children: notice
|
|
2387
|
+
})
|
|
2388
|
+
]
|
|
2389
|
+
});
|
|
2390
|
+
};
|
|
2391
|
+
const FieldSelect = ({ label, value, options, onChange }) => (0, react_jsx_runtime.jsxs)("label", {
|
|
2392
|
+
className: "dsh-ig-field-select",
|
|
2393
|
+
children: [(0, react_jsx_runtime.jsx)("span", { children: label }), (0, react_jsx_runtime.jsx)("select", {
|
|
2394
|
+
value,
|
|
2395
|
+
onChange: (event) => onChange(event.target.value),
|
|
2396
|
+
children: options.map((option) => (0, react_jsx_runtime.jsx)("option", {
|
|
2397
|
+
value: option.value,
|
|
2398
|
+
children: option.label
|
|
2399
|
+
}, option.value))
|
|
2400
|
+
})]
|
|
2401
|
+
});
|
|
2402
|
+
const DetailsPanel = ({ item, t }) => item === null ? (0, react_jsx_runtime.jsxs)("div", {
|
|
2403
|
+
className: "dsh-ig-details-empty",
|
|
2404
|
+
children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 28 }), (0, react_jsx_runtime.jsx)("span", { children: t("selectHistory") })]
|
|
2405
|
+
}) : (0, react_jsx_runtime.jsxs)("dl", {
|
|
2406
|
+
className: "dsh-ig-details",
|
|
2407
|
+
children: [
|
|
2408
|
+
(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 })] }),
|
|
2409
|
+
(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 })] }),
|
|
2410
|
+
(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 })] }),
|
|
2411
|
+
(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}` : "—" })] }),
|
|
2412
|
+
(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 ?? "—" })] }),
|
|
2413
|
+
(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() })] })
|
|
2414
|
+
]
|
|
2415
|
+
});
|
|
2416
|
+
const RecentItem = ({ item, active, lang, onClick }) => {
|
|
2417
|
+
const [setRef, inView] = useInView();
|
|
2418
|
+
const image = useAttachmentImage(item.attachment, inView);
|
|
2419
|
+
return (0, react_jsx_runtime.jsxs)("button", {
|
|
2420
|
+
ref: setRef,
|
|
2421
|
+
type: "button",
|
|
2422
|
+
className: `dsh-ig-recent-item ${active ? "is-active" : ""}`,
|
|
2423
|
+
onClick,
|
|
2424
|
+
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
2425
|
+
className: "dsh-ig-recent-thumb",
|
|
2426
|
+
children: image.url !== null ? (0, react_jsx_runtime.jsx)("img", {
|
|
2427
|
+
src: image.url,
|
|
2428
|
+
alt: "",
|
|
2429
|
+
loading: "lazy"
|
|
2430
|
+
}) : (0, react_jsx_runtime.jsx)(ImagePlus, { size: 18 })
|
|
2431
|
+
}), (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
2432
|
+
(0, react_jsx_runtime.jsx)("strong", { children: item.prompt }),
|
|
2433
|
+
(0, react_jsx_runtime.jsx)("span", { children: formatRelativeTime$1(item.createdAt, lang) }),
|
|
2434
|
+
(0, react_jsx_runtime.jsxs)("small", { children: [
|
|
2435
|
+
item.provider,
|
|
2436
|
+
" · ",
|
|
2437
|
+
item.model
|
|
2438
|
+
] }),
|
|
2439
|
+
(0, react_jsx_runtime.jsx)("small", { children: item.attachment.width && item.attachment.height ? `${item.attachment.width} × ${item.attachment.height}` : "—" })
|
|
2440
|
+
] })]
|
|
2441
|
+
});
|
|
2442
|
+
};
|
|
2443
|
+
function useInView() {
|
|
2444
|
+
const [inView, setInView] = (0, react.useState)(false);
|
|
2445
|
+
const observerRef = (0, react.useRef)(null);
|
|
2446
|
+
(0, react.useEffect)(() => {
|
|
2447
|
+
return () => {
|
|
2448
|
+
observerRef.current?.disconnect();
|
|
2449
|
+
};
|
|
2450
|
+
}, []);
|
|
2451
|
+
return [(0, react.useCallback)((el) => {
|
|
2452
|
+
observerRef.current?.disconnect();
|
|
2453
|
+
if (!el || inView) return;
|
|
2454
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
2455
|
+
setInView(true);
|
|
2456
|
+
return;
|
|
2457
|
+
}
|
|
2458
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
2459
|
+
if (entry?.isIntersecting) {
|
|
2460
|
+
setInView(true);
|
|
2461
|
+
observer.disconnect();
|
|
2462
|
+
}
|
|
2463
|
+
}, { rootMargin: "120px" });
|
|
2464
|
+
observerRef.current = observer;
|
|
2465
|
+
observer.observe(el);
|
|
2466
|
+
}, [inView]), inView];
|
|
2467
|
+
}
|
|
2468
|
+
function useAttachmentImage(attachment, shouldLoad = true) {
|
|
2469
|
+
const [value, setValue] = (0, react.useState)({
|
|
2470
|
+
url: null,
|
|
2471
|
+
blob: null,
|
|
2472
|
+
loading: false
|
|
2473
|
+
});
|
|
2474
|
+
(0, react.useEffect)(() => {
|
|
2475
|
+
if (attachment === void 0 || !shouldLoad) {
|
|
2476
|
+
setValue({
|
|
2477
|
+
url: null,
|
|
2478
|
+
blob: null,
|
|
2479
|
+
loading: false
|
|
2480
|
+
});
|
|
2481
|
+
return;
|
|
2482
|
+
}
|
|
2483
|
+
let cancelled = false;
|
|
2484
|
+
let objectUrl = null;
|
|
2485
|
+
setValue({
|
|
2486
|
+
url: null,
|
|
2487
|
+
blob: null,
|
|
2488
|
+
loading: true
|
|
2489
|
+
});
|
|
2490
|
+
fetchAttachmentBlob(attachment).then((blob) => {
|
|
2491
|
+
if (cancelled) return;
|
|
2492
|
+
objectUrl = URL.createObjectURL(blob);
|
|
2493
|
+
setValue({
|
|
2494
|
+
url: objectUrl,
|
|
2495
|
+
blob,
|
|
2496
|
+
loading: false
|
|
2497
|
+
});
|
|
2498
|
+
}).catch(() => {
|
|
2499
|
+
if (!cancelled) setValue({
|
|
2500
|
+
url: null,
|
|
2501
|
+
blob: null,
|
|
2502
|
+
loading: false
|
|
2503
|
+
});
|
|
2504
|
+
});
|
|
2505
|
+
return () => {
|
|
2506
|
+
cancelled = true;
|
|
2507
|
+
if (objectUrl !== null) URL.revokeObjectURL(objectUrl);
|
|
2508
|
+
};
|
|
2509
|
+
}, [attachment?.attachmentId, shouldLoad]);
|
|
2510
|
+
return value;
|
|
2511
|
+
}
|
|
2512
|
+
function fileToBase64(file) {
|
|
2513
|
+
return new Promise((resolve, reject) => {
|
|
2514
|
+
const reader = new FileReader();
|
|
2515
|
+
reader.onload = () => {
|
|
2516
|
+
const result = reader.result;
|
|
2517
|
+
const commaIndex = result.indexOf(",");
|
|
2518
|
+
resolve(commaIndex >= 0 ? result.slice(commaIndex + 1) : result);
|
|
2519
|
+
};
|
|
2520
|
+
reader.onerror = () => reject(reader.error ?? /* @__PURE__ */ new Error("Failed to read image file"));
|
|
2521
|
+
reader.readAsDataURL(file);
|
|
2522
|
+
});
|
|
2523
|
+
}
|
|
2524
|
+
function extension(mediaType) {
|
|
2525
|
+
return mediaType === "image/jpeg" ? "jpg" : mediaType.split("/")[1] ?? "png";
|
|
2526
|
+
}
|
|
2527
|
+
function messageOf(error) {
|
|
2528
|
+
return error instanceof Error ? error.message : String(error);
|
|
2529
|
+
}
|
|
2530
|
+
//#endregion
|
|
2531
|
+
//#region lib/types/client/conversation-regenerate.js
|
|
2532
|
+
/** Build the workbench request that reproduces a conversation image with an edited prompt. */
|
|
2533
|
+
function conversationRegenerateRequest(image, prompt, remembered) {
|
|
2534
|
+
if (!CLOUD_IMAGE_PROVIDERS.includes(image.provider)) throw new Error("当前图片使用的 Provider 暂不支持重新生成");
|
|
2535
|
+
const provider = image.provider;
|
|
2536
|
+
const settings = remembered ?? outputSettings(provider, image.output);
|
|
2537
|
+
return {
|
|
2538
|
+
mode: "generate",
|
|
2539
|
+
provider,
|
|
2540
|
+
model: image.model,
|
|
2541
|
+
prompt: prompt.trim(),
|
|
2542
|
+
ratio: settings.ratio,
|
|
2543
|
+
quality: settings.quality
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
function outputSettings(provider, output) {
|
|
2547
|
+
const normalized = (output ?? "").trim();
|
|
2548
|
+
if (provider === "google") {
|
|
2549
|
+
const [rawRatio, rawQuality] = normalized.split(",").map((value) => value.trim());
|
|
2550
|
+
return {
|
|
2551
|
+
ratio: isRatio(rawRatio) ? rawRatio : "1:1",
|
|
2552
|
+
quality: rawQuality === "1K" || rawQuality === "2K" || rawQuality === "4K" ? rawQuality : "1K"
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
if (provider === "seedream") return {
|
|
2556
|
+
ratio: "auto",
|
|
2557
|
+
quality: normalized === "1K" || normalized === "4K" ? normalized : "2K"
|
|
2558
|
+
};
|
|
2559
|
+
if (provider === "openai") return {
|
|
2560
|
+
ratio: ratioFromSize(normalized, "x"),
|
|
2561
|
+
quality: "standard"
|
|
2562
|
+
};
|
|
2563
|
+
return {
|
|
2564
|
+
ratio: ratioFromSize(normalized, "*"),
|
|
2565
|
+
quality: "standard"
|
|
2566
|
+
};
|
|
2567
|
+
}
|
|
2568
|
+
function ratioFromSize(size, separator) {
|
|
2569
|
+
return (separator === "x" ? {
|
|
2570
|
+
"1024x1024": "1:1",
|
|
2571
|
+
"1536x1024": "3:2",
|
|
2572
|
+
"1024x1536": "2:3"
|
|
2573
|
+
} : {
|
|
2574
|
+
"1024*1024": "1:1",
|
|
2575
|
+
"1536*1024": "3:2",
|
|
2576
|
+
"1024*1536": "2:3",
|
|
2577
|
+
"1664*928": "16:9",
|
|
2578
|
+
"928*1664": "9:16"
|
|
2579
|
+
})[size] ?? "1:1";
|
|
2580
|
+
}
|
|
2581
|
+
function isRatio(value) {
|
|
2582
|
+
return value === "1:1" || value === "3:2" || value === "2:3" || value === "4:3" || value === "3:4" || value === "16:9" || value === "9:16";
|
|
2583
|
+
}
|
|
2584
|
+
//#endregion
|
|
2585
|
+
//#region lib/types/client/gallery-view.js
|
|
2586
|
+
/**
|
|
2587
|
+
* Native Workspace Gallery & Studio View Component for DSH `conversation.view` slot.
|
|
2588
|
+
* Fully i18n-reactive (Chinese & English) with modular tabs, multi-dimensional filters,
|
|
2589
|
+
* responsive image grid, and placeholder routes.
|
|
2590
|
+
*/
|
|
2591
|
+
const DICT$1 = {
|
|
2592
|
+
zh: {
|
|
2593
|
+
tabGallery: "图库",
|
|
2594
|
+
tabStudio: "工作台",
|
|
2595
|
+
tabFavorites: "收藏",
|
|
2596
|
+
tabCompare: "对比",
|
|
2597
|
+
tabTasks: "任务",
|
|
2598
|
+
filterAllProviders: "全部提供商",
|
|
781
2599
|
filterGoogle: "Google Gemini",
|
|
782
2600
|
filterOpenAI: "OpenAI / 中转站",
|
|
783
2601
|
filterSeedream: "字节 Seedream",
|
|
@@ -786,6 +2604,8 @@ window.__ModuleLoader__.load({
|
|
|
786
2604
|
filterAllModels: "全部模型",
|
|
787
2605
|
filterAllRatios: "全部比例",
|
|
788
2606
|
searchPlaceholder: "搜索 Prompt、标签…",
|
|
2607
|
+
filterCurrentWorkspace: "仅看此工作区",
|
|
2608
|
+
filterCurrentWorkspaceHint: "只展示属于当前工作区的生图记录与物理文件",
|
|
789
2609
|
sortNewest: "最新生成",
|
|
790
2610
|
sortOldest: "最早生成",
|
|
791
2611
|
totalCount: "共 {count} 张生成图片",
|
|
@@ -804,6 +2624,13 @@ window.__ModuleLoader__.load({
|
|
|
804
2624
|
download: "下载图片",
|
|
805
2625
|
copyImg: "复制图片",
|
|
806
2626
|
copyPpt: "复制 Prompt",
|
|
2627
|
+
regenerate: "重新生成",
|
|
2628
|
+
regenerateTitle: "重新生成图片",
|
|
2629
|
+
regenerateHint: "可按需修改 Prompt,基于当前模型和比例在画廊生成一张新图片。",
|
|
2630
|
+
confirmRegenerate: "开始生成",
|
|
2631
|
+
regenerating: "正在重新生成…",
|
|
2632
|
+
regenerateSuccess: "已生成新图片并收录到画廊",
|
|
2633
|
+
regenerateFailed: "重新生成失败",
|
|
807
2634
|
delete: "从画廊删除",
|
|
808
2635
|
confirmDelete: "确定要从画廊中删除这张图片吗?(不会影响原聊天记录)",
|
|
809
2636
|
deleted: "已从画廊删除",
|
|
@@ -858,6 +2685,8 @@ window.__ModuleLoader__.load({
|
|
|
858
2685
|
filterAllModels: "All Models",
|
|
859
2686
|
filterAllRatios: "All Ratios",
|
|
860
2687
|
searchPlaceholder: "Search prompt, tags…",
|
|
2688
|
+
filterCurrentWorkspace: "Current workspace only",
|
|
2689
|
+
filterCurrentWorkspaceHint: "Only show images belonging to the active workspace",
|
|
861
2690
|
sortNewest: "Newest first",
|
|
862
2691
|
sortOldest: "Oldest first",
|
|
863
2692
|
totalCount: "{count} images total",
|
|
@@ -876,6 +2705,13 @@ window.__ModuleLoader__.load({
|
|
|
876
2705
|
download: "Download",
|
|
877
2706
|
copyImg: "Copy Image",
|
|
878
2707
|
copyPpt: "Copy Prompt",
|
|
2708
|
+
regenerate: "Regenerate",
|
|
2709
|
+
regenerateTitle: "Regenerate Image",
|
|
2710
|
+
regenerateHint: "Edit prompt if needed. A new image will be generated and added to the gallery.",
|
|
2711
|
+
confirmRegenerate: "Generate",
|
|
2712
|
+
regenerating: "Regenerating…",
|
|
2713
|
+
regenerateSuccess: "New image generated and added to gallery",
|
|
2714
|
+
regenerateFailed: "Regeneration failed",
|
|
879
2715
|
delete: "Delete from gallery",
|
|
880
2716
|
confirmDelete: "Are you sure you want to remove this image from the gallery? (Chat history will not be affected)",
|
|
881
2717
|
deleted: "Deleted from gallery",
|
|
@@ -971,17 +2807,80 @@ window.__ModuleLoader__.load({
|
|
|
971
2807
|
if (item.imageSize) return item.imageSize;
|
|
972
2808
|
return "1:1";
|
|
973
2809
|
}
|
|
974
|
-
const GalleryViewTab = (
|
|
2810
|
+
const GalleryViewTab = (props) => {
|
|
2811
|
+
const { locale, sessionId, useSessions, useWorkspaces } = props;
|
|
975
2812
|
const [activeTab, setActiveTab] = (0, react.useState)("gallery");
|
|
976
2813
|
const [items, setItems] = (0, react.useState)([]);
|
|
977
2814
|
const [search, setSearch] = (0, react.useState)("");
|
|
978
2815
|
const [selectedProvider, setSelectedProvider] = (0, react.useState)("all");
|
|
979
2816
|
const [selectedRatio, setSelectedRatio] = (0, react.useState)("all");
|
|
980
2817
|
const [sortBy, setSortBy] = (0, react.useState)("newest");
|
|
2818
|
+
const [onlyCurrentWorkspace, setOnlyCurrentWorkspace] = (0, react.useState)(() => {
|
|
2819
|
+
try {
|
|
2820
|
+
const stored = localStorage.getItem("dsh-ig-only-current-workspace");
|
|
2821
|
+
return stored === null ? true : stored === "true";
|
|
2822
|
+
} catch {
|
|
2823
|
+
return true;
|
|
2824
|
+
}
|
|
2825
|
+
});
|
|
2826
|
+
const [serverWorkspaces, setServerWorkspaces] = (0, react.useState)([]);
|
|
2827
|
+
const [serverActiveRoot, setServerActiveRoot] = (0, react.useState)(null);
|
|
2828
|
+
(0, react.useEffect)(() => {
|
|
2829
|
+
try {
|
|
2830
|
+
localStorage.setItem("dsh-ig-only-current-workspace", String(onlyCurrentWorkspace));
|
|
2831
|
+
} catch {}
|
|
2832
|
+
}, [onlyCurrentWorkspace]);
|
|
2833
|
+
(0, react.useEffect)(() => {
|
|
2834
|
+
let mounted = true;
|
|
2835
|
+
fetch(STUDIO_ROUTE).then((res) => res.ok ? res.json() : null).then((data) => {
|
|
2836
|
+
if (!mounted || !data) return;
|
|
2837
|
+
if (Array.isArray(data.workspaces)) setServerWorkspaces(data.workspaces);
|
|
2838
|
+
if (typeof data.workspaceRoot === "string") setServerActiveRoot(data.workspaceRoot);
|
|
2839
|
+
}).catch(() => {});
|
|
2840
|
+
return () => {
|
|
2841
|
+
mounted = false;
|
|
2842
|
+
};
|
|
2843
|
+
}, []);
|
|
2844
|
+
const liveWorkspaces = useWorkspaces ? useWorkspaces((s) => s?.items) : null;
|
|
2845
|
+
const liveSessions = useSessions ? useSessions((s) => s) : null;
|
|
2846
|
+
const currentSessionId = sessionId || liveSessions?.current;
|
|
2847
|
+
const activeWorkspace = (0, react.useMemo)(() => {
|
|
2848
|
+
const allWs = Array.isArray(liveWorkspaces) && liveWorkspaces.length > 0 ? liveWorkspaces : serverWorkspaces;
|
|
2849
|
+
if (allWs.length > 0) {
|
|
2850
|
+
if (currentSessionId) {
|
|
2851
|
+
const matched = allWs.find((ws) => Array.isArray(ws.sessionIds) && ws.sessionIds.includes(currentSessionId));
|
|
2852
|
+
if (matched) return matched;
|
|
2853
|
+
}
|
|
2854
|
+
if (serverActiveRoot) {
|
|
2855
|
+
const normActive = serverActiveRoot.replace(/\\/g, "/").toLowerCase();
|
|
2856
|
+
const matched = allWs.find((ws) => ws.path && ws.path.replace(/\\/g, "/").toLowerCase() === normActive);
|
|
2857
|
+
if (matched) return matched;
|
|
2858
|
+
}
|
|
2859
|
+
return allWs[0];
|
|
2860
|
+
}
|
|
2861
|
+
if (serverActiveRoot) return {
|
|
2862
|
+
workspaceId: "default",
|
|
2863
|
+
path: serverActiveRoot,
|
|
2864
|
+
title: "Workspace",
|
|
2865
|
+
sessionIds: []
|
|
2866
|
+
};
|
|
2867
|
+
return null;
|
|
2868
|
+
}, [
|
|
2869
|
+
liveWorkspaces,
|
|
2870
|
+
serverWorkspaces,
|
|
2871
|
+
currentSessionId,
|
|
2872
|
+
serverActiveRoot
|
|
2873
|
+
]);
|
|
981
2874
|
const [previewItem, setPreviewItem] = (0, react.useState)(null);
|
|
982
2875
|
const [previewUrl, setPreviewUrl] = (0, react.useState)(null);
|
|
2876
|
+
const previewUrlRef = (0, react.useRef)(null);
|
|
2877
|
+
previewUrlRef.current = previewUrl;
|
|
983
2878
|
const [previewBlob, setPreviewBlob] = (0, react.useState)(null);
|
|
984
2879
|
const [toast, setToast] = (0, react.useState)(null);
|
|
2880
|
+
const [showRegenerateModal, setShowRegenerateModal] = (0, react.useState)(false);
|
|
2881
|
+
const [regeneratePrompt, setRegeneratePrompt] = (0, react.useState)("");
|
|
2882
|
+
const [isRegenerating, setIsRegenerating] = (0, react.useState)(false);
|
|
2883
|
+
const regenerateControllerRef = (0, react.useRef)(null);
|
|
985
2884
|
const [isManageMode, setIsManageMode] = (0, react.useState)(false);
|
|
986
2885
|
const [selectedIds, setSelectedIds] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
987
2886
|
const lastSelectedIndexRef = (0, react.useRef)(null);
|
|
@@ -996,7 +2895,8 @@ window.__ModuleLoader__.load({
|
|
|
996
2895
|
search,
|
|
997
2896
|
selectedProvider,
|
|
998
2897
|
selectedRatio,
|
|
999
|
-
sortBy
|
|
2898
|
+
sortBy,
|
|
2899
|
+
onlyCurrentWorkspace
|
|
1000
2900
|
]);
|
|
1001
2901
|
const [lang, setLang] = (0, react.useState)(() => {
|
|
1002
2902
|
return (locale?.getSnapshot?.()?.active)?.startsWith("en") ? "en" : "zh";
|
|
@@ -1062,6 +2962,9 @@ window.__ModuleLoader__.load({
|
|
|
1062
2962
|
const matchTags = item.tags?.some((t) => t.toLowerCase().includes(q));
|
|
1063
2963
|
if (!matchPrompt && !matchModel && !matchProvider && !matchTags) return false;
|
|
1064
2964
|
}
|
|
2965
|
+
if (onlyCurrentWorkspace && activeWorkspace) {
|
|
2966
|
+
if (!isItemInWorkspace(item, activeWorkspace)) return false;
|
|
2967
|
+
}
|
|
1065
2968
|
return true;
|
|
1066
2969
|
}).sort((a, b) => {
|
|
1067
2970
|
if (sortBy === "oldest") return a.createdAt - b.createdAt;
|
|
@@ -1073,8 +2976,14 @@ window.__ModuleLoader__.load({
|
|
|
1073
2976
|
selectedProvider,
|
|
1074
2977
|
selectedRatio,
|
|
1075
2978
|
search,
|
|
1076
|
-
sortBy
|
|
2979
|
+
sortBy,
|
|
2980
|
+
onlyCurrentWorkspace,
|
|
2981
|
+
activeWorkspace
|
|
1077
2982
|
]);
|
|
2983
|
+
const workspaceItemsCount = (0, react.useMemo)(() => {
|
|
2984
|
+
if (!activeWorkspace) return items.length;
|
|
2985
|
+
return items.filter((item) => isItemInWorkspace(item, activeWorkspace)).length;
|
|
2986
|
+
}, [items, activeWorkspace]);
|
|
1078
2987
|
const currentPreviewIndex = (0, react.useMemo)(() => {
|
|
1079
2988
|
if (!previewItem) return -1;
|
|
1080
2989
|
return filteredItems.findIndex((i) => i.id === previewItem.id);
|
|
@@ -1086,11 +2995,15 @@ window.__ModuleLoader__.load({
|
|
|
1086
2995
|
abortControllerRef.current.abort();
|
|
1087
2996
|
abortControllerRef.current = null;
|
|
1088
2997
|
}
|
|
1089
|
-
if (
|
|
2998
|
+
if (previewUrlRef.current) {
|
|
2999
|
+
URL.revokeObjectURL(previewUrlRef.current);
|
|
3000
|
+
previewUrlRef.current = null;
|
|
3001
|
+
}
|
|
1090
3002
|
setPreviewItem(item);
|
|
1091
3003
|
if (blob) {
|
|
1092
3004
|
blobCache.set(item.id, blob);
|
|
1093
3005
|
const url = URL.createObjectURL(blob);
|
|
3006
|
+
previewUrlRef.current = url;
|
|
1094
3007
|
setPreviewBlob(blob);
|
|
1095
3008
|
setPreviewUrl(url);
|
|
1096
3009
|
setPreviewLoading(false);
|
|
@@ -1099,6 +3012,7 @@ window.__ModuleLoader__.load({
|
|
|
1099
3012
|
const cached = blobCache.get(item.id);
|
|
1100
3013
|
if (cached) {
|
|
1101
3014
|
const url = URL.createObjectURL(cached);
|
|
3015
|
+
previewUrlRef.current = url;
|
|
1102
3016
|
setPreviewBlob(cached);
|
|
1103
3017
|
setPreviewUrl(url);
|
|
1104
3018
|
setPreviewLoading(false);
|
|
@@ -1109,18 +3023,11 @@ window.__ModuleLoader__.load({
|
|
|
1109
3023
|
setPreviewUrl(null);
|
|
1110
3024
|
const controller = new AbortController();
|
|
1111
3025
|
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) => {
|
|
3026
|
+
fetchAttachmentBlob(item.attachment).then((fetchedBlob) => {
|
|
1121
3027
|
if (controller.signal.aborted) return;
|
|
1122
3028
|
blobCache.set(item.id, fetchedBlob);
|
|
1123
3029
|
const url = URL.createObjectURL(fetchedBlob);
|
|
3030
|
+
previewUrlRef.current = url;
|
|
1124
3031
|
setPreviewBlob(fetchedBlob);
|
|
1125
3032
|
setPreviewUrl(url);
|
|
1126
3033
|
setPreviewLoading(false);
|
|
@@ -1135,7 +3042,16 @@ window.__ModuleLoader__.load({
|
|
|
1135
3042
|
abortControllerRef.current.abort();
|
|
1136
3043
|
abortControllerRef.current = null;
|
|
1137
3044
|
}
|
|
1138
|
-
if (
|
|
3045
|
+
if (regenerateControllerRef.current) {
|
|
3046
|
+
regenerateControllerRef.current.abort();
|
|
3047
|
+
regenerateControllerRef.current = null;
|
|
3048
|
+
setIsRegenerating(false);
|
|
3049
|
+
}
|
|
3050
|
+
setShowRegenerateModal(false);
|
|
3051
|
+
if (previewUrlRef.current) {
|
|
3052
|
+
URL.revokeObjectURL(previewUrlRef.current);
|
|
3053
|
+
previewUrlRef.current = null;
|
|
3054
|
+
}
|
|
1139
3055
|
setPreviewItem(null);
|
|
1140
3056
|
setPreviewUrl(null);
|
|
1141
3057
|
setPreviewBlob(null);
|
|
@@ -1153,15 +3069,86 @@ window.__ModuleLoader__.load({
|
|
|
1153
3069
|
if (nextItem) openPreviewItem(nextItem);
|
|
1154
3070
|
}
|
|
1155
3071
|
};
|
|
3072
|
+
const handleOpenRegenerate = () => {
|
|
3073
|
+
if (!previewItem || isRegenerating) return;
|
|
3074
|
+
setRegeneratePrompt(previewItem.prompt);
|
|
3075
|
+
setShowRegenerateModal(true);
|
|
3076
|
+
};
|
|
3077
|
+
const handleCancelRegeneratePrompt = () => {
|
|
3078
|
+
setShowRegenerateModal(false);
|
|
3079
|
+
};
|
|
3080
|
+
const handleAbortRegenerating = () => {
|
|
3081
|
+
regenerateControllerRef.current?.abort();
|
|
3082
|
+
regenerateControllerRef.current = null;
|
|
3083
|
+
setIsRegenerating(false);
|
|
3084
|
+
showToast(t("cancel"));
|
|
3085
|
+
};
|
|
3086
|
+
const handleConfirmRegenerate = async () => {
|
|
3087
|
+
if (!previewItem || isRegenerating || regeneratePrompt.trim().length === 0) return;
|
|
3088
|
+
setShowRegenerateModal(false);
|
|
3089
|
+
setIsRegenerating(true);
|
|
3090
|
+
const controller = new AbortController();
|
|
3091
|
+
regenerateControllerRef.current = controller;
|
|
3092
|
+
try {
|
|
3093
|
+
const request = conversationRegenerateRequest(previewItem, regeneratePrompt, previewItem.aspectRatio ? {
|
|
3094
|
+
ratio: previewItem.aspectRatio,
|
|
3095
|
+
quality: previewItem.imageSize || "standard"
|
|
3096
|
+
} : void 0);
|
|
3097
|
+
if (activeWorkspace?.path) request.workspaceRoot = activeWorkspace.path;
|
|
3098
|
+
const response = await fetch(STUDIO_ROUTE, {
|
|
3099
|
+
method: "POST",
|
|
3100
|
+
credentials: "same-origin",
|
|
3101
|
+
signal: controller.signal,
|
|
3102
|
+
headers: { "content-type": "application/json" },
|
|
3103
|
+
body: JSON.stringify(request)
|
|
3104
|
+
});
|
|
3105
|
+
const payload = await response.json().catch(() => null);
|
|
3106
|
+
if (!response.ok || payload === null || !("attachment" in payload)) throw new Error(payload && "error" in payload && payload.error ? payload.error : t("regenerateFailed"));
|
|
3107
|
+
const newItem = {
|
|
3108
|
+
id: String(payload.attachment.attachmentId),
|
|
3109
|
+
attachment: payload.attachment,
|
|
3110
|
+
prompt: payload.prompt,
|
|
3111
|
+
provider: payload.provider,
|
|
3112
|
+
model: payload.model,
|
|
3113
|
+
createdAt: payload.createdAt,
|
|
3114
|
+
aspectRatio: request.ratio,
|
|
3115
|
+
imageSize: request.quality,
|
|
3116
|
+
output: payload.output,
|
|
3117
|
+
...payload.savedTo ? { savedTo: payload.savedTo } : {},
|
|
3118
|
+
...activeWorkspace?.path ? { workspacePath: activeWorkspace.path } : {},
|
|
3119
|
+
...activeWorkspace?.workspaceId ? { workspaceId: activeWorkspace.workspaceId } : {},
|
|
3120
|
+
...currentSessionId ? { sessionId: currentSessionId } : {}
|
|
3121
|
+
};
|
|
3122
|
+
await saveGalleryItem(newItem);
|
|
3123
|
+
if (activeTab !== "gallery") setActiveTab("gallery");
|
|
3124
|
+
if (search.trim().length > 0) setSearch("");
|
|
3125
|
+
openPreviewItem(newItem);
|
|
3126
|
+
showToast(t("regenerateSuccess"));
|
|
3127
|
+
} catch (err) {
|
|
3128
|
+
if (controller.signal.aborted) return;
|
|
3129
|
+
showToast(err instanceof Error ? err.message : String(err));
|
|
3130
|
+
} finally {
|
|
3131
|
+
setIsRegenerating(false);
|
|
3132
|
+
regenerateControllerRef.current = null;
|
|
3133
|
+
}
|
|
3134
|
+
};
|
|
1156
3135
|
(0, react.useEffect)(() => {
|
|
1157
3136
|
return () => {
|
|
1158
3137
|
if (abortControllerRef.current) abortControllerRef.current.abort();
|
|
1159
|
-
if (
|
|
3138
|
+
if (regenerateControllerRef.current) regenerateControllerRef.current.abort();
|
|
3139
|
+
if (previewUrlRef.current) URL.revokeObjectURL(previewUrlRef.current);
|
|
1160
3140
|
};
|
|
1161
|
-
}, [
|
|
3141
|
+
}, []);
|
|
1162
3142
|
(0, react.useEffect)(() => {
|
|
1163
3143
|
const onKeyDown = (e) => {
|
|
1164
3144
|
if (["INPUT", "TEXTAREA"].includes(e.target?.tagName)) return;
|
|
3145
|
+
if (showRegenerateModal) {
|
|
3146
|
+
if (e.key === "Escape") {
|
|
3147
|
+
e.preventDefault();
|
|
3148
|
+
setShowRegenerateModal(false);
|
|
3149
|
+
}
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
1165
3152
|
if (showDeleteModal) {
|
|
1166
3153
|
if (e.key === "Escape" && !isDeleting) {
|
|
1167
3154
|
e.preventDefault();
|
|
@@ -1184,6 +3171,7 @@ window.__ModuleLoader__.load({
|
|
|
1184
3171
|
window.addEventListener("keydown", onKeyDown);
|
|
1185
3172
|
return () => window.removeEventListener("keydown", onKeyDown);
|
|
1186
3173
|
}, [
|
|
3174
|
+
showRegenerateModal,
|
|
1187
3175
|
showDeleteModal,
|
|
1188
3176
|
isDeleting,
|
|
1189
3177
|
isManageMode,
|
|
@@ -1263,34 +3251,41 @@ window.__ModuleLoader__.load({
|
|
|
1263
3251
|
setSelectedIds(/* @__PURE__ */ new Set([item.id]));
|
|
1264
3252
|
setShowDeleteModal(true);
|
|
1265
3253
|
};
|
|
3254
|
+
const hasWorkspaceFilesToDelete = (0, react.useMemo)(() => items.some((i) => selectedIds.has(i.id) && typeof i.savedTo === "string" && i.savedTo.trim().length > 0), [items, selectedIds]);
|
|
1266
3255
|
const handleConfirmBatchDelete = async () => {
|
|
1267
3256
|
if (selectedIds.size === 0 || isDeleting) return;
|
|
1268
3257
|
setIsDeleting(true);
|
|
1269
3258
|
const idsToDelete = Array.from(selectedIds);
|
|
1270
3259
|
const itemsToDelete = items.filter((i) => selectedIds.has(i.id));
|
|
1271
3260
|
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
3261
|
let deletedFiles = 0;
|
|
1274
|
-
|
|
1275
|
-
if (deleteWorkspaceFiles && (pathsToDelete.length > 0 || fallbackAttachmentIds.length > 0)) try {
|
|
3262
|
+
if (deleteWorkspaceFiles && pathsToDelete.length > 0) try {
|
|
1276
3263
|
const res = await fetch(DELETE_ROUTE, {
|
|
1277
3264
|
method: "POST",
|
|
1278
3265
|
headers: { "content-type": "application/json" },
|
|
1279
|
-
body: JSON.stringify({
|
|
1280
|
-
paths: pathsToDelete,
|
|
1281
|
-
attachmentIds: fallbackAttachmentIds
|
|
1282
|
-
})
|
|
3266
|
+
body: JSON.stringify({ paths: pathsToDelete })
|
|
1283
3267
|
});
|
|
1284
|
-
if (res.ok) {
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
3268
|
+
if (!res.ok) {
|
|
3269
|
+
setIsDeleting(false);
|
|
3270
|
+
showToast(`工作区文件删除失败 (${res.status})`);
|
|
3271
|
+
return;
|
|
1288
3272
|
}
|
|
3273
|
+
const data = await res.json().catch(() => null);
|
|
3274
|
+
if (!data || data.ok === false || Array.isArray(data.failedFiles) && data.failedFiles.length > 0) {
|
|
3275
|
+
setIsDeleting(false);
|
|
3276
|
+
const reason = data?.failedFiles?.[0]?.error || "工作区文件未能删除";
|
|
3277
|
+
showToast(`删除失败:${reason}`);
|
|
3278
|
+
return;
|
|
3279
|
+
}
|
|
3280
|
+
deletedFiles = data.deletedCount ?? 0;
|
|
1289
3281
|
} catch (err) {
|
|
1290
|
-
|
|
3282
|
+
setIsDeleting(false);
|
|
3283
|
+
showToast(err instanceof Error ? err.message : "工作区文件删除网络异常");
|
|
3284
|
+
return;
|
|
1291
3285
|
}
|
|
1292
3286
|
try {
|
|
1293
3287
|
await bulkDeleteGalleryItems(idsToDelete);
|
|
3288
|
+
for (const item of itemsToDelete) evictAttachmentCache(item.attachment.attachmentId);
|
|
1294
3289
|
} catch (err) {
|
|
1295
3290
|
setIsDeleting(false);
|
|
1296
3291
|
showToast(t("deleteFailedDatabase"));
|
|
@@ -1304,11 +3299,7 @@ window.__ModuleLoader__.load({
|
|
|
1304
3299
|
setSelectedIds(/* @__PURE__ */ new Set());
|
|
1305
3300
|
lastSelectedIndexRef.current = null;
|
|
1306
3301
|
}
|
|
1307
|
-
if (
|
|
1308
|
-
count: String(idsToDelete.length),
|
|
1309
|
-
files: String(failedFilesCount)
|
|
1310
|
-
}));
|
|
1311
|
-
else if (deletedFiles > 0) showToast(t("batchDeletedWithFilesToast", {
|
|
3302
|
+
if (deletedFiles > 0) showToast(t("batchDeletedWithFilesToast", {
|
|
1312
3303
|
count: String(idsToDelete.length),
|
|
1313
3304
|
files: String(deletedFiles)
|
|
1314
3305
|
}));
|
|
@@ -1316,6 +3307,7 @@ window.__ModuleLoader__.load({
|
|
|
1316
3307
|
};
|
|
1317
3308
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1318
3309
|
className: "dsh-ig-gallery-page",
|
|
3310
|
+
"data-conversation-composer-overlay": "",
|
|
1319
3311
|
children: [
|
|
1320
3312
|
(0, react_jsx_runtime.jsxs)("header", {
|
|
1321
3313
|
className: "dsh-ig-studio-tabs-bar",
|
|
@@ -1337,18 +3329,6 @@ window.__ModuleLoader__.load({
|
|
|
1337
3329
|
className: `dsh-ig-studio-tab-btn ${activeTab === "favorites" ? "is-active" : ""}`,
|
|
1338
3330
|
onClick: () => setActiveTab("favorites"),
|
|
1339
3331
|
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
3332
|
})
|
|
1353
3333
|
]
|
|
1354
3334
|
}),
|
|
@@ -1439,6 +3419,30 @@ window.__ModuleLoader__.load({
|
|
|
1439
3419
|
value: search,
|
|
1440
3420
|
onChange: (e) => setSearch(e.target.value)
|
|
1441
3421
|
})]
|
|
3422
|
+
}),
|
|
3423
|
+
(0, react_jsx_runtime.jsxs)("label", {
|
|
3424
|
+
className: "dsh-ig-workspace-filter-label",
|
|
3425
|
+
title: t("filterCurrentWorkspaceHint"),
|
|
3426
|
+
children: [
|
|
3427
|
+
(0, react_jsx_runtime.jsx)("input", {
|
|
3428
|
+
type: "checkbox",
|
|
3429
|
+
className: "dsh-ig-workspace-checkbox",
|
|
3430
|
+
checked: onlyCurrentWorkspace,
|
|
3431
|
+
onChange: (e) => setOnlyCurrentWorkspace(e.target.checked)
|
|
3432
|
+
}),
|
|
3433
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
3434
|
+
className: "dsh-ig-workspace-filter-text",
|
|
3435
|
+
children: t("filterCurrentWorkspace")
|
|
3436
|
+
}),
|
|
3437
|
+
(0, react_jsx_runtime.jsxs)("span", {
|
|
3438
|
+
className: "dsh-ig-workspace-filter-badge",
|
|
3439
|
+
children: [
|
|
3440
|
+
workspaceItemsCount,
|
|
3441
|
+
"/",
|
|
3442
|
+
items.length
|
|
3443
|
+
]
|
|
3444
|
+
})
|
|
3445
|
+
]
|
|
1442
3446
|
})
|
|
1443
3447
|
]
|
|
1444
3448
|
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1467,7 +3471,7 @@ window.__ModuleLoader__.load({
|
|
|
1467
3471
|
})]
|
|
1468
3472
|
}),
|
|
1469
3473
|
(0, react_jsx_runtime.jsx)("div", {
|
|
1470
|
-
className:
|
|
3474
|
+
className: `dsh-ig-gallery-page-body ${activeTab === "studio" ? "is-workbench" : ""}`,
|
|
1471
3475
|
children: activeTab === "gallery" || activeTab === "favorites" ? items.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
|
|
1472
3476
|
className: "dsh-ig-gallery-empty",
|
|
1473
3477
|
children: [
|
|
@@ -1531,22 +3535,9 @@ window.__ModuleLoader__.load({
|
|
|
1531
3535
|
onBlobLoaded: (b) => blobCache.set(item.id, b),
|
|
1532
3536
|
onToast: showToast
|
|
1533
3537
|
}, 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")
|
|
3538
|
+
}) : (0, react_jsx_runtime.jsx)(StudioView, {
|
|
3539
|
+
locale,
|
|
3540
|
+
workspace: activeWorkspace
|
|
1550
3541
|
})
|
|
1551
3542
|
}),
|
|
1552
3543
|
toast && (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -1624,7 +3615,7 @@ window.__ModuleLoader__.load({
|
|
|
1624
3615
|
children: [(0, react_jsx_runtime.jsx)("p", {
|
|
1625
3616
|
className: "dsh-ig-modal-desc",
|
|
1626
3617
|
children: t("batchDeleteDesc")
|
|
1627
|
-
}), (0, react_jsx_runtime.jsxs)("label", {
|
|
3618
|
+
}), hasWorkspaceFilesToDelete && (0, react_jsx_runtime.jsxs)("label", {
|
|
1628
3619
|
className: "dsh-ig-modal-checkbox-label",
|
|
1629
3620
|
children: [(0, react_jsx_runtime.jsx)("input", {
|
|
1630
3621
|
type: "checkbox",
|
|
@@ -1656,6 +3647,74 @@ window.__ModuleLoader__.load({
|
|
|
1656
3647
|
]
|
|
1657
3648
|
})
|
|
1658
3649
|
}),
|
|
3650
|
+
showRegenerateModal && previewItem && (0, react_jsx_runtime.jsx)("div", {
|
|
3651
|
+
className: "dsh-ig-modal-backdrop",
|
|
3652
|
+
onClick: handleCancelRegeneratePrompt,
|
|
3653
|
+
children: (0, react_jsx_runtime.jsxs)("div", {
|
|
3654
|
+
className: "dsh-ig-modal-box dsh-ig-regenerate-modal-box",
|
|
3655
|
+
onClick: (e) => e.stopPropagation(),
|
|
3656
|
+
children: [
|
|
3657
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
3658
|
+
className: "dsh-ig-modal-header",
|
|
3659
|
+
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
3660
|
+
className: "dsh-ig-regenerate-modal-icon",
|
|
3661
|
+
children: (0, react_jsx_runtime.jsx)(Sparkles, { size: 18 })
|
|
3662
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
3663
|
+
className: "dsh-ig-regenerate-modal-title-wrap",
|
|
3664
|
+
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
3665
|
+
className: "dsh-ig-modal-title",
|
|
3666
|
+
children: t("regenerateTitle")
|
|
3667
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
3668
|
+
className: "dsh-ig-regenerate-modal-meta",
|
|
3669
|
+
children: [
|
|
3670
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
3671
|
+
className: "dsh-ig-tag",
|
|
3672
|
+
children: previewItem.provider
|
|
3673
|
+
}),
|
|
3674
|
+
previewItem.model && (0, react_jsx_runtime.jsx)("span", {
|
|
3675
|
+
className: "dsh-ig-tag dsh-ig-tag-model",
|
|
3676
|
+
children: previewItem.model
|
|
3677
|
+
}),
|
|
3678
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
3679
|
+
className: "dsh-ig-tag",
|
|
3680
|
+
children: formatCardMeta(previewItem)
|
|
3681
|
+
})
|
|
3682
|
+
]
|
|
3683
|
+
})]
|
|
3684
|
+
})]
|
|
3685
|
+
}),
|
|
3686
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
3687
|
+
className: "dsh-ig-modal-body",
|
|
3688
|
+
children: [(0, react_jsx_runtime.jsx)("p", {
|
|
3689
|
+
className: "dsh-ig-modal-desc",
|
|
3690
|
+
children: t("regenerateHint")
|
|
3691
|
+
}), (0, react_jsx_runtime.jsx)("textarea", {
|
|
3692
|
+
className: "dsh-ig-regenerate-modal-textarea",
|
|
3693
|
+
value: regeneratePrompt,
|
|
3694
|
+
onChange: (e) => setRegeneratePrompt(e.target.value),
|
|
3695
|
+
placeholder: t("prompt"),
|
|
3696
|
+
rows: 4,
|
|
3697
|
+
autoFocus: true
|
|
3698
|
+
})]
|
|
3699
|
+
}),
|
|
3700
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
3701
|
+
className: "dsh-ig-modal-footer",
|
|
3702
|
+
children: [(0, react_jsx_runtime.jsx)("button", {
|
|
3703
|
+
type: "button",
|
|
3704
|
+
className: "dsh-ig-modal-btn dsh-ig-modal-btn-cancel",
|
|
3705
|
+
onClick: handleCancelRegeneratePrompt,
|
|
3706
|
+
children: t("cancel")
|
|
3707
|
+
}), (0, react_jsx_runtime.jsxs)("button", {
|
|
3708
|
+
type: "button",
|
|
3709
|
+
className: "dsh-ig-modal-btn dsh-ig-modal-btn-primary",
|
|
3710
|
+
onClick: handleConfirmRegenerate,
|
|
3711
|
+
disabled: regeneratePrompt.trim().length === 0,
|
|
3712
|
+
children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("confirmRegenerate") })]
|
|
3713
|
+
})]
|
|
3714
|
+
})
|
|
3715
|
+
]
|
|
3716
|
+
})
|
|
3717
|
+
}),
|
|
1659
3718
|
previewItem && (0, react_jsx_runtime.jsxs)("div", {
|
|
1660
3719
|
className: "dsh-ig-lightbox-backdrop",
|
|
1661
3720
|
onClick: handleClosePreview,
|
|
@@ -1771,6 +3830,27 @@ window.__ModuleLoader__.load({
|
|
|
1771
3830
|
},
|
|
1772
3831
|
children: [(0, react_jsx_runtime.jsx)(Download, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("download") })]
|
|
1773
3832
|
}),
|
|
3833
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
3834
|
+
type: "button",
|
|
3835
|
+
className: "dsh-ig-lightbox-btn dsh-ig-lightbox-btn-regenerate",
|
|
3836
|
+
title: t("regenerate"),
|
|
3837
|
+
disabled: isRegenerating,
|
|
3838
|
+
onClick: handleOpenRegenerate,
|
|
3839
|
+
children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("regenerate") })]
|
|
3840
|
+
}),
|
|
3841
|
+
isRegenerating && (0, react_jsx_runtime.jsxs)("div", {
|
|
3842
|
+
className: "dsh-ig-lightbox-generating-indicator",
|
|
3843
|
+
children: [
|
|
3844
|
+
(0, react_jsx_runtime.jsx)("div", { className: "dsh-ig-lightbox-spinner-sm" }),
|
|
3845
|
+
(0, react_jsx_runtime.jsx)("span", { children: t("regenerating") }),
|
|
3846
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
3847
|
+
type: "button",
|
|
3848
|
+
className: "dsh-ig-lightbox-abort-btn",
|
|
3849
|
+
onClick: handleAbortRegenerating,
|
|
3850
|
+
children: t("cancel")
|
|
3851
|
+
})
|
|
3852
|
+
]
|
|
3853
|
+
}),
|
|
1774
3854
|
(0, react_jsx_runtime.jsxs)("button", {
|
|
1775
3855
|
type: "button",
|
|
1776
3856
|
className: "dsh-ig-lightbox-btn dsh-ig-lightbox-btn-danger",
|
|
@@ -1963,68 +4043,6 @@ window.__ModuleLoader__.load({
|
|
|
1963
4043
|
})]
|
|
1964
4044
|
});
|
|
1965
4045
|
};
|
|
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
4046
|
//#endregion
|
|
2029
4047
|
//#region lib/types/client/image-ref.js
|
|
2030
4048
|
/**
|
|
@@ -2038,6 +4056,99 @@ window.__ModuleLoader__.load({
|
|
|
2038
4056
|
const fromBlockContent = Array.isArray(block.content) ? block.content.find((item) => item.type === "image") : void 0;
|
|
2039
4057
|
if (fromBlockContent?.type === "image") return fromBlockContent.attachment;
|
|
2040
4058
|
}
|
|
4059
|
+
//#endregion
|
|
4060
|
+
//#region lib/types/client/studio-style.js
|
|
4061
|
+
const STUDIO_STYLE = `
|
|
4062
|
+
.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}
|
|
4063
|
+
.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%)}
|
|
4064
|
+
.dsh-ig-workbench *{box-sizing:border-box}.dsh-ig-workbench button,.dsh-ig-workbench select,.dsh-ig-workbench textarea{font:inherit;color:inherit}
|
|
4065
|
+
.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}
|
|
4066
|
+
.dsh-ig-workbench-grid.is-sidebar-collapsed{grid-template-columns:minmax(0,1fr) minmax(286px,340px)}
|
|
4067
|
+
.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)}
|
|
4068
|
+
.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}
|
|
4069
|
+
.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}
|
|
4070
|
+
.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}
|
|
4071
|
+
.dsh-ig-collapse-btn:hover{border-color:var(--ig-line);background:var(--ig-soft);color:var(--foreground,#182033)}
|
|
4072
|
+
.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}
|
|
4073
|
+
.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}
|
|
4074
|
+
.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}
|
|
4075
|
+
.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)}
|
|
4076
|
+
.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)}
|
|
4077
|
+
.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)}
|
|
4078
|
+
.dsh-ig-canvas-toolbar-left,.dsh-ig-canvas-toolbar-right{display:flex;align-items:center;gap:6px}
|
|
4079
|
+
.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}
|
|
4080
|
+
.dsh-ig-canvas-toolbar button:hover{border-color:#9bb4f5;background:rgba(47,100,245,.05);color:var(--ig-blue)}
|
|
4081
|
+
.dsh-ig-canvas-toolbar button.is-active{border-color:#8ba8f8;background:rgba(47,100,245,.08);color:var(--ig-blue);font-weight:550}
|
|
4082
|
+
.dsh-ig-zoom-display{font-size:11px;min-width:38px;text-align:center;color:#64748b;user-select:none;font-variant-numeric:tabular-nums}
|
|
4083
|
+
.dsh-ig-status-badge-wrap{position:relative;display:inline-flex;align-items:center}
|
|
4084
|
+
.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}
|
|
4085
|
+
.dsh-ig-status-badge:hover{border-color:#9bb4f5;background:rgba(47,100,245,.04)}
|
|
4086
|
+
.dsh-ig-status-dot{width:7px;height:7px;min-width:7px;max-width:7px;border-radius:50%;flex-shrink:0;box-sizing:border-box}
|
|
4087
|
+
.dsh-ig-status-dot.is-ready{background:#10b981;box-shadow:0 0 0 2px rgba(16,185,129,.2)}
|
|
4088
|
+
.dsh-ig-status-dot.is-muted{background:#ef4444;box-shadow:0 0 0 2px rgba(239,68,68,.2)}
|
|
4089
|
+
.dsh-ig-status-text{font-size:11.5px;font-weight:500;color:var(--foreground,#242f44);line-height:1}
|
|
4090
|
+
.dsh-ig-status-arrow{color:#94a3b8;transition:transform .15s,color .15s}
|
|
4091
|
+
.dsh-ig-status-badge-wrap:hover .dsh-ig-status-arrow{transform:rotate(180deg);color:var(--ig-blue)}
|
|
4092
|
+
.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)}
|
|
4093
|
+
.dsh-ig-status-badge-wrap:hover .dsh-ig-status-popover{opacity:1;pointer-events:auto;transform:translateY(0)}
|
|
4094
|
+
.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}
|
|
4095
|
+
.dsh-ig-status-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:5px}
|
|
4096
|
+
.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}
|
|
4097
|
+
.dsh-ig-status-list li.is-configured{background:rgba(16,185,129,.05);border:1px solid rgba(16,185,129,.12)}
|
|
4098
|
+
.dsh-ig-status-list li.is-missing{background:rgba(239,68,68,.04);border:1px solid rgba(239,68,68,.12);opacity:.75}
|
|
4099
|
+
.dsh-ig-status-item-name{display:flex;flex-direction:column;min-width:0}
|
|
4100
|
+
.dsh-ig-status-item-name strong{font-size:11.5px;font-weight:600;color:var(--foreground,#182033);line-height:16px}
|
|
4101
|
+
.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}
|
|
4102
|
+
.dsh-ig-status-item-tag{font-size:10px;font-weight:600;padding:2px 6px;border-radius:4px;flex-shrink:0}
|
|
4103
|
+
.is-configured .dsh-ig-status-item-tag{color:#059669;background:rgba(16,185,129,.14)}
|
|
4104
|
+
.is-missing .dsh-ig-status-item-tag{color:#dc2626;background:rgba(239,68,68,.14)}
|
|
4105
|
+
.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}
|
|
4106
|
+
.dsh-ig-canvas.is-dragging{cursor:grabbing!important}
|
|
4107
|
+
.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}
|
|
4108
|
+
.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}
|
|
4109
|
+
.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}
|
|
4110
|
+
.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}
|
|
4111
|
+
.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)}
|
|
4112
|
+
.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)}
|
|
4113
|
+
.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)}
|
|
4114
|
+
.dsh-ig-reference-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(64px,1fr));gap:8px}
|
|
4115
|
+
.dsh-ig-reference-card{position:relative;aspect-ratio:1;border-radius:8px;overflow:hidden;background:#eef1f7;border:1px solid var(--ig-line)}
|
|
4116
|
+
.dsh-ig-reference-card img{width:100%;height:100%;object-fit:cover}
|
|
4117
|
+
.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}
|
|
4118
|
+
.dsh-ig-reference-card button:hover{background:#ef4444}
|
|
4119
|
+
.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}
|
|
4120
|
+
.dsh-ig-reference-add:hover{border-color:var(--ig-blue);background:rgba(47,100,245,.05);color:var(--ig-blue)}
|
|
4121
|
+
.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}
|
|
4122
|
+
.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)}
|
|
4123
|
+
.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}
|
|
4124
|
+
.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}
|
|
4125
|
+
.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)}
|
|
4126
|
+
.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}
|
|
4127
|
+
.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}
|
|
4128
|
+
.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}
|
|
4129
|
+
.dsh-ig-workbench-modal-header{display:flex;align-items:center;gap:10px}
|
|
4130
|
+
.dsh-ig-workbench-modal-header strong{font-size:14px;font-weight:650;color:#b91c1c}
|
|
4131
|
+
.dsh-ig-workbench-modal-desc{font-size:12px;line-height:18px;color:#555f77;margin:0}
|
|
4132
|
+
.dsh-ig-workbench-modal-option{display:flex;align-items:center;gap:8px;font-size:11.5px;color:#4b5563;cursor:pointer}
|
|
4133
|
+
.dsh-ig-workbench-modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:4px}
|
|
4134
|
+
.dsh-ig-workbench-modal-actions button{height:32px;padding:0 14px;border-radius:7px;font-size:11.5px;font-weight:600;cursor:pointer}
|
|
4135
|
+
.dsh-ig-workbench-modal-cancel{border:1px solid var(--ig-line);background:var(--background,#fff);color:#6b7280}
|
|
4136
|
+
.dsh-ig-workbench-modal-cancel:hover{background:#f3f4f6}
|
|
4137
|
+
.dsh-ig-workbench-modal-danger{border:0;background:#dc2626;color:#fff}
|
|
4138
|
+
.dsh-ig-workbench-modal-danger:hover{background:#b91c1c}
|
|
4139
|
+
@keyframes dshIgPulse{50%{transform:scale(1.06);box-shadow:0 0 0 16px rgba(47,100,245,.025)}}
|
|
4140
|
+
.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}
|
|
4141
|
+
@keyframes dshIgFade{from{opacity:0;transform:translate(-50%,6px)}to{opacity:1;transform:translate(-50%,0)}}
|
|
4142
|
+
@media(max-width:1120px){.dsh-ig-workbench-grid{grid-template-columns:190px minmax(360px,1fr) 300px}.dsh-ig-result-actions button span{display:none}}
|
|
4143
|
+
@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}}
|
|
4144
|
+
@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}}
|
|
4145
|
+
@media(prefers-reduced-motion:reduce){.dsh-ig-workbench *{scroll-behavior:auto!important;animation-duration:.01ms!important;transition-duration:.01ms!important}}
|
|
4146
|
+
.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}
|
|
4147
|
+
.dsh-ig-workspace-filter-label:hover{border-color:#9bb4f5;background:rgba(47,100,245,.04)}
|
|
4148
|
+
.dsh-ig-workspace-checkbox{cursor:pointer;accent-color:#2f64f5;width:13.5px;height:13.5px;margin:0}
|
|
4149
|
+
.dsh-ig-workspace-filter-text{font-weight:500;white-space:nowrap}
|
|
4150
|
+
.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}
|
|
4151
|
+
`;
|
|
2041
4152
|
//#endregion
|
|
2042
4153
|
//#region lib/types/client/image-result-node.js
|
|
2043
4154
|
const IMAGE_RESULT_NODE_KIND = "dsh-image-result";
|
|
@@ -2179,6 +4290,75 @@ window.__ModuleLoader__.load({
|
|
|
2179
4290
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
2180
4291
|
}
|
|
2181
4292
|
//#endregion
|
|
4293
|
+
//#region lib/types/client/conversation-image-revisions.js
|
|
4294
|
+
const STORAGE_PREFIX = "dsh_image_gen_conversation_revisions:";
|
|
4295
|
+
/** Read the locally persisted replacement history for one immutable conversation image. */
|
|
4296
|
+
function loadConversationImageRevisionChain(originId) {
|
|
4297
|
+
const empty = () => ({
|
|
4298
|
+
originId,
|
|
4299
|
+
currentIndex: 0,
|
|
4300
|
+
revisions: []
|
|
4301
|
+
});
|
|
4302
|
+
try {
|
|
4303
|
+
const raw = globalThis.localStorage?.getItem(storageKey(originId));
|
|
4304
|
+
if (!raw) return empty();
|
|
4305
|
+
const value = JSON.parse(raw);
|
|
4306
|
+
if (!isChain(value, originId)) return empty();
|
|
4307
|
+
return {
|
|
4308
|
+
originId,
|
|
4309
|
+
currentIndex: Math.min(value.currentIndex, value.revisions.length),
|
|
4310
|
+
revisions: value.revisions
|
|
4311
|
+
};
|
|
4312
|
+
} catch {
|
|
4313
|
+
return empty();
|
|
4314
|
+
}
|
|
4315
|
+
}
|
|
4316
|
+
/** Append a generated version and make it the version shown by the conversation card. */
|
|
4317
|
+
function appendConversationImageRevision(originId, revision) {
|
|
4318
|
+
const revisions = [...loadConversationImageRevisionChain(originId).revisions.filter((item) => item.attachment.attachmentId !== revision.attachment.attachmentId), revision].slice(-20);
|
|
4319
|
+
const next = {
|
|
4320
|
+
originId,
|
|
4321
|
+
currentIndex: revisions.length,
|
|
4322
|
+
revisions
|
|
4323
|
+
};
|
|
4324
|
+
persist(next);
|
|
4325
|
+
return next;
|
|
4326
|
+
}
|
|
4327
|
+
/** Select an earlier or later version without altering the underlying conversation event. */
|
|
4328
|
+
function selectConversationImageRevision(originId, requestedIndex) {
|
|
4329
|
+
const current = loadConversationImageRevisionChain(originId);
|
|
4330
|
+
const currentIndex = Math.max(0, Math.min(Math.trunc(requestedIndex), current.revisions.length));
|
|
4331
|
+
const next = {
|
|
4332
|
+
...current,
|
|
4333
|
+
currentIndex
|
|
4334
|
+
};
|
|
4335
|
+
persist(next);
|
|
4336
|
+
return next;
|
|
4337
|
+
}
|
|
4338
|
+
function persist(chain) {
|
|
4339
|
+
try {
|
|
4340
|
+
globalThis.localStorage?.setItem(storageKey(chain.originId), JSON.stringify(chain));
|
|
4341
|
+
} catch {}
|
|
4342
|
+
}
|
|
4343
|
+
function storageKey(originId) {
|
|
4344
|
+
return `${STORAGE_PREFIX}${originId}`;
|
|
4345
|
+
}
|
|
4346
|
+
function isChain(value, originId) {
|
|
4347
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
4348
|
+
const candidate = value;
|
|
4349
|
+
return candidate.originId === originId && typeof candidate.currentIndex === "number" && Number.isSafeInteger(candidate.currentIndex) && candidate.currentIndex >= 0 && Array.isArray(candidate.revisions) && candidate.revisions.every(isRevision);
|
|
4350
|
+
}
|
|
4351
|
+
function isRevision(value) {
|
|
4352
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
4353
|
+
const candidate = value;
|
|
4354
|
+
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";
|
|
4355
|
+
}
|
|
4356
|
+
function isAttachment(value) {
|
|
4357
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
4358
|
+
const candidate = value;
|
|
4359
|
+
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";
|
|
4360
|
+
}
|
|
4361
|
+
//#endregion
|
|
2182
4362
|
//#region lib/types/client/index.js
|
|
2183
4363
|
const KEY_REF = {
|
|
2184
4364
|
google: "GEMINI_API_KEY",
|
|
@@ -2242,7 +4422,18 @@ window.__ModuleLoader__.load({
|
|
|
2242
4422
|
download: "下载图片",
|
|
2243
4423
|
openNewTab: "新标签页打开",
|
|
2244
4424
|
copiedImage: "已复制图片",
|
|
2245
|
-
copyFailed: "复制失败"
|
|
4425
|
+
copyFailed: "复制失败",
|
|
4426
|
+
regenerate: "重新生成",
|
|
4427
|
+
regenerateTitle: "重新生成图片",
|
|
4428
|
+
regenerateHint: "如有需要可微调提示词。生成的新图片将替代当前展示,原图依然可在版本中查看。",
|
|
4429
|
+
prompt: "提示词",
|
|
4430
|
+
cancel: "取消",
|
|
4431
|
+
confirmRegenerate: "确认生成",
|
|
4432
|
+
regenerating: "重新生成中…",
|
|
4433
|
+
regenerateFailed: "重新生成失败",
|
|
4434
|
+
versionPrevious: "上一版本",
|
|
4435
|
+
versionNext: "下一版本",
|
|
4436
|
+
versionLabel: "图片版本 {current}/{total}"
|
|
2246
4437
|
},
|
|
2247
4438
|
en: {
|
|
2248
4439
|
title: "Image Generation",
|
|
@@ -2299,7 +4490,18 @@ window.__ModuleLoader__.load({
|
|
|
2299
4490
|
download: "Download Image",
|
|
2300
4491
|
openNewTab: "Open in new tab",
|
|
2301
4492
|
copiedImage: "Image copied",
|
|
2302
|
-
copyFailed: "Copy failed"
|
|
4493
|
+
copyFailed: "Copy failed",
|
|
4494
|
+
regenerate: "Regenerate",
|
|
4495
|
+
regenerateTitle: "Regenerate image",
|
|
4496
|
+
regenerateHint: "Edit the prompt if needed. The new image replaces this view while the original remains available.",
|
|
4497
|
+
prompt: "Prompt",
|
|
4498
|
+
cancel: "Cancel",
|
|
4499
|
+
confirmRegenerate: "Regenerate",
|
|
4500
|
+
regenerating: "Regenerating…",
|
|
4501
|
+
regenerateFailed: "Regeneration failed",
|
|
4502
|
+
versionPrevious: "Previous version",
|
|
4503
|
+
versionNext: "Next version",
|
|
4504
|
+
versionLabel: "Image version {current}/{total}"
|
|
2303
4505
|
}
|
|
2304
4506
|
};
|
|
2305
4507
|
const STYLE = `
|
|
@@ -2345,14 +4547,39 @@ window.__ModuleLoader__.load({
|
|
|
2345
4547
|
.dsh-ig-result{display:grid;gap:10px;max-width:520px}
|
|
2346
4548
|
.dsh-ig-promoted-results{display:grid;gap:16px}
|
|
2347
4549
|
.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:
|
|
4550
|
+
.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}
|
|
4551
|
+
.dsh-ig-container:hover .dsh-ig-toolbar,.dsh-ig-container:focus-within .dsh-ig-toolbar{opacity:1;pointer-events:auto}
|
|
4552
|
+
.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
4553
|
.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
4554
|
.dsh-ig-tool-btn:hover{background:rgba(255,255,255,0.25)}
|
|
2353
4555
|
.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:
|
|
4556
|
+
.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
4557
|
.dsh-ig-image{display:block;max-width:100%;max-height:520px;border-radius:12px;background:#f2f3f5;cursor:pointer}
|
|
4558
|
+
.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}
|
|
4559
|
+
.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}
|
|
4560
|
+
.dsh-ig-version-nav button:hover:not(:disabled){background:rgba(255,255,255,.2)}
|
|
4561
|
+
.dsh-ig-version-nav button:disabled{opacity:.3;cursor:default}
|
|
4562
|
+
.dsh-ig-version-count{min-width:34px;text-align:center;font-variant-numeric:tabular-nums}
|
|
4563
|
+
.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}
|
|
4564
|
+
.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}
|
|
4565
|
+
.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)}
|
|
4566
|
+
.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}
|
|
4567
|
+
.dsh-ig-regenerate-overlay-cancel:hover{background:rgba(255,255,255,0.3)}
|
|
4568
|
+
@keyframes dsh-ig-spin{to{transform:rotate(360deg)}}
|
|
4569
|
+
.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}
|
|
4570
|
+
.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}
|
|
4571
|
+
.dsh-ig-regenerate-dialog h3{margin:0;font-size:16px;font-weight:650}
|
|
4572
|
+
.dsh-ig-regenerate-dialog p{margin:7px 0 16px;color:var(--dsw-alias-label-tertiary,#737d8f);font-size:12.5px;line-height:1.55}
|
|
4573
|
+
.dsh-ig-regenerate-dialog label{display:grid;gap:7px;font-size:12.5px;font-weight:600}
|
|
4574
|
+
.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}
|
|
4575
|
+
.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)}
|
|
4576
|
+
.dsh-ig-regenerate-error{margin-top:10px!important;color:var(--dsw-alias-label-error,#d33)!important}
|
|
4577
|
+
.dsh-ig-regenerate-actions{display:flex;justify-content:flex-end;gap:9px;margin-top:16px}
|
|
4578
|
+
.dsh-ig-regenerate-actions button{height:35px;padding:0 14px;border-radius:7px;font:inherit;font-size:13px;font-weight:550;cursor:pointer}
|
|
4579
|
+
.dsh-ig-regenerate-cancel{border:1px solid var(--dsw-alias-border-l2,#d7dce5);background:transparent;color:inherit}
|
|
4580
|
+
.dsh-ig-regenerate-confirm{border:1px solid var(--dsw-alias-brand-primary,#3569ed);background:var(--dsw-alias-brand-primary,#3569ed);color:#fff}
|
|
4581
|
+
.dsh-ig-regenerate-actions button:disabled{opacity:.5;cursor:default}
|
|
4582
|
+
@media(hover:none){.dsh-ig-container .dsh-ig-toolbar{opacity:1;pointer-events:auto}}
|
|
2356
4583
|
@keyframes dsh-ig-fade{from{opacity:0}to{opacity:1}}
|
|
2357
4584
|
.dsh-ig-error{color:var(--dsw-alias-label-error,#d33);font-size:13px}
|
|
2358
4585
|
.dsh-ig-loading{color:var(--dsw-alias-label-tertiary,#7b818b);font-size:13px}
|
|
@@ -2483,7 +4710,7 @@ window.__ModuleLoader__.load({
|
|
|
2483
4710
|
.dsh-ig-batch-btn-exit:hover{background:rgba(255,255,255,0.1);color:#fff}
|
|
2484
4711
|
|
|
2485
4712
|
/* 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:
|
|
4713
|
+
.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
4714
|
.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
4715
|
@keyframes dsh-ig-scale-up{from{transform:scale(0.95);opacity:0}to{transform:scale(1);opacity:1}}
|
|
2489
4716
|
.dsh-ig-modal-header{display:flex;align-items:flex-start;gap:12px;margin-bottom:12px}
|
|
@@ -2499,9 +4726,26 @@ window.__ModuleLoader__.load({
|
|
|
2499
4726
|
.dsh-ig-modal-btn-cancel:hover{background:var(--dsw-alias-bg-layer-3,rgba(255,255,255,0.08))}
|
|
2500
4727
|
.dsh-ig-modal-btn-danger{background:#dc2626;border:1px solid #dc2626;color:#fff}
|
|
2501
4728
|
.dsh-ig-modal-btn-danger:hover{background:#b91c1c;border-color:#b91c1c}
|
|
4729
|
+
.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}
|
|
4730
|
+
.dsh-ig-modal-btn-primary:hover:not(:disabled){background:linear-gradient(135deg,#2563eb,#1d4ed8)}
|
|
4731
|
+
.dsh-ig-modal-btn-primary:disabled{opacity:0.5;cursor:not-allowed}
|
|
4732
|
+
.dsh-ig-regenerate-modal-box{max-width:520px}
|
|
4733
|
+
.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}
|
|
4734
|
+
.dsh-ig-regenerate-modal-title-wrap{display:flex;flex-direction:column;gap:4px}
|
|
4735
|
+
.dsh-ig-regenerate-modal-meta{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
|
4736
|
+
.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}
|
|
4737
|
+
.dsh-ig-regenerate-modal-textarea:focus{border-color:#3b82f6;box-shadow:0 0 0 2px rgba(59,130,246,0.2)}
|
|
4738
|
+
.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}
|
|
4739
|
+
.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}
|
|
4740
|
+
.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}
|
|
4741
|
+
.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}
|
|
4742
|
+
.dsh-ig-lightbox-abort-btn:hover{color:#ef4444}
|
|
2502
4743
|
|
|
2503
|
-
/* Hide floating chat composer when gallery page is active */
|
|
4744
|
+
/* Hide floating chat composer and width handles when gallery page is active */
|
|
2504
4745
|
[data-conversation-scroll]:has(.dsh-ig-gallery-page) [data-composer-seat]{display:none!important}
|
|
4746
|
+
:has(> [data-conversation-scroll]:has(.dsh-ig-gallery-page)) > [class*="widthHandle"],
|
|
4747
|
+
:has(.dsh-ig-gallery-page) [class*="widthHandle"],
|
|
4748
|
+
.root:has(.dsh-ig-gallery-page) [class*="widthHandle"]{display:none!important}
|
|
2505
4749
|
`;
|
|
2506
4750
|
/** Required browser services. */
|
|
2507
4751
|
const inject = [
|
|
@@ -2521,7 +4765,7 @@ window.__ModuleLoader__.load({
|
|
|
2521
4765
|
ctx.effect(() => {
|
|
2522
4766
|
const style = document.createElement("style");
|
|
2523
4767
|
style.dataset.plugin = "dsh-image-gen";
|
|
2524
|
-
style.textContent = STYLE
|
|
4768
|
+
style.textContent = `${STYLE}\n${STUDIO_STYLE}`;
|
|
2525
4769
|
document.head.appendChild(style);
|
|
2526
4770
|
return () => {
|
|
2527
4771
|
style.remove();
|
|
@@ -3106,7 +5350,8 @@ window.__ModuleLoader__.load({
|
|
|
3106
5350
|
if (props.promoted && result !== void 0) return (0, react_jsx_runtime.jsx)(PromotedResultNotice, { locale: props.locale });
|
|
3107
5351
|
return (0, react_jsx_runtime.jsx)(ImageResultCard, {
|
|
3108
5352
|
result,
|
|
3109
|
-
locale: props.locale
|
|
5353
|
+
locale: props.locale,
|
|
5354
|
+
sessionId: props.sessionId
|
|
3110
5355
|
});
|
|
3111
5356
|
}
|
|
3112
5357
|
/** Render modern image artifacts as final conversation output instead of Tool process content. */
|
|
@@ -3115,7 +5360,8 @@ window.__ModuleLoader__.load({
|
|
|
3115
5360
|
className: "dsh-ig-promoted-results",
|
|
3116
5361
|
children: props.node.data.results.map((result) => (0, react_jsx_runtime.jsx)(ImageResultCard, {
|
|
3117
5362
|
result,
|
|
3118
|
-
locale: props.locale
|
|
5363
|
+
locale: props.locale,
|
|
5364
|
+
sessionId: props.sessionId
|
|
3119
5365
|
}, result.attachment.attachmentId))
|
|
3120
5366
|
});
|
|
3121
5367
|
}
|
|
@@ -3126,14 +5372,31 @@ window.__ModuleLoader__.load({
|
|
|
3126
5372
|
children: DICT[lang].resultShown
|
|
3127
5373
|
});
|
|
3128
5374
|
}
|
|
3129
|
-
function ImageResultCard({ result, locale }) {
|
|
5375
|
+
function ImageResultCard({ result, locale, sessionId }) {
|
|
3130
5376
|
const attachment = result?.attachment;
|
|
5377
|
+
const originId = attachment?.attachmentId;
|
|
5378
|
+
const [revisionChain, setRevisionChain] = (0, react.useState)(() => loadConversationImageRevisionChain(String(originId ?? "")));
|
|
5379
|
+
const selectedRevision = revisionChain.revisions[revisionChain.currentIndex - 1];
|
|
5380
|
+
const activeAttachment = selectedRevision?.attachment ?? attachment;
|
|
5381
|
+
const activeResult = selectedRevision !== void 0 ? {
|
|
5382
|
+
prompt: selectedRevision.prompt,
|
|
5383
|
+
provider: selectedRevision.provider,
|
|
5384
|
+
model: selectedRevision.model,
|
|
5385
|
+
output: selectedRevision.output,
|
|
5386
|
+
createdAt: selectedRevision.createdAt
|
|
5387
|
+
} : result;
|
|
3131
5388
|
const savedTo = result?.savedTo;
|
|
3132
5389
|
const [url, setUrl] = (0, react.useState)();
|
|
3133
5390
|
const [blob, setBlob] = (0, react.useState)();
|
|
3134
5391
|
const [error, setError] = (0, react.useState)();
|
|
3135
5392
|
const [previewOpen, setPreviewOpen] = (0, react.useState)(false);
|
|
3136
5393
|
const [toast, setToast] = (0, react.useState)();
|
|
5394
|
+
const [regenerateOpen, setRegenerateOpen] = (0, react.useState)(false);
|
|
5395
|
+
const [regeneratePrompt, setRegeneratePrompt] = (0, react.useState)("");
|
|
5396
|
+
const [regenerateError, setRegenerateError] = (0, react.useState)();
|
|
5397
|
+
const [isRegenerating, setIsRegenerating] = (0, react.useState)(false);
|
|
5398
|
+
const regenerateControllerRef = (0, react.useRef)();
|
|
5399
|
+
const regenerateTextareaRef = (0, react.useRef)(null);
|
|
3137
5400
|
const lang = usePluginLanguage(locale);
|
|
3138
5401
|
const t = (keyName, params) => {
|
|
3139
5402
|
let text = (lang === "en" ? DICT.en : DICT.zh)[keyName] || DICT.zh[keyName] || keyName;
|
|
@@ -3150,9 +5413,36 @@ window.__ModuleLoader__.load({
|
|
|
3150
5413
|
model: result.model,
|
|
3151
5414
|
output: result.output,
|
|
3152
5415
|
...result.savedTo ? { savedTo: result.savedTo } : {},
|
|
3153
|
-
...result.seed !== void 0 ? { seed: result.seed } : {}
|
|
5416
|
+
...result.seed !== void 0 ? { seed: result.seed } : {},
|
|
5417
|
+
...sessionId ? { sessionId } : {}
|
|
3154
5418
|
});
|
|
3155
5419
|
}, [result?.attachment.attachmentId]);
|
|
5420
|
+
(0, react.useEffect)(() => {
|
|
5421
|
+
if (!originId) return;
|
|
5422
|
+
regenerateControllerRef.current?.abort();
|
|
5423
|
+
regenerateControllerRef.current = void 0;
|
|
5424
|
+
setIsRegenerating(false);
|
|
5425
|
+
setRegenerateOpen(false);
|
|
5426
|
+
setRevisionChain(loadConversationImageRevisionChain(String(originId)));
|
|
5427
|
+
}, [originId]);
|
|
5428
|
+
(0, react.useEffect)(() => () => {
|
|
5429
|
+
regenerateControllerRef.current?.abort();
|
|
5430
|
+
}, []);
|
|
5431
|
+
(0, react.useEffect)(() => {
|
|
5432
|
+
if (!regenerateOpen || isRegenerating) return;
|
|
5433
|
+
const frame = requestAnimationFrame(() => {
|
|
5434
|
+
regenerateTextareaRef.current?.focus();
|
|
5435
|
+
regenerateTextareaRef.current?.select();
|
|
5436
|
+
});
|
|
5437
|
+
const onKeyDown = (event) => {
|
|
5438
|
+
if (event.key === "Escape") setRegenerateOpen(false);
|
|
5439
|
+
};
|
|
5440
|
+
window.addEventListener("keydown", onKeyDown);
|
|
5441
|
+
return () => {
|
|
5442
|
+
cancelAnimationFrame(frame);
|
|
5443
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
5444
|
+
};
|
|
5445
|
+
}, [regenerateOpen, isRegenerating]);
|
|
3156
5446
|
(0, react.useEffect)(() => {
|
|
3157
5447
|
if (!previewOpen) return;
|
|
3158
5448
|
const onKeyDown = (e) => {
|
|
@@ -3163,30 +5453,34 @@ window.__ModuleLoader__.load({
|
|
|
3163
5453
|
window.removeEventListener("keydown", onKeyDown);
|
|
3164
5454
|
};
|
|
3165
5455
|
}, [previewOpen]);
|
|
5456
|
+
const currentUrlRef = (0, react.useRef)();
|
|
3166
5457
|
(0, react.useEffect)(() => {
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
if (
|
|
5458
|
+
return () => {
|
|
5459
|
+
if (currentUrlRef.current !== void 0) {
|
|
5460
|
+
URL.revokeObjectURL(currentUrlRef.current);
|
|
5461
|
+
currentUrlRef.current = void 0;
|
|
5462
|
+
}
|
|
5463
|
+
};
|
|
5464
|
+
}, []);
|
|
5465
|
+
(0, react.useEffect)(() => {
|
|
5466
|
+
if (activeAttachment === void 0) return;
|
|
5467
|
+
let canceled = false;
|
|
5468
|
+
fetchAttachmentBlob(activeAttachment).then((resBlob) => {
|
|
5469
|
+
if (canceled) return;
|
|
3179
5470
|
setBlob(resBlob);
|
|
3180
|
-
|
|
3181
|
-
|
|
5471
|
+
setError(void 0);
|
|
5472
|
+
const nextUrl = URL.createObjectURL(resBlob);
|
|
5473
|
+
if (currentUrlRef.current !== void 0) URL.revokeObjectURL(currentUrlRef.current);
|
|
5474
|
+
currentUrlRef.current = nextUrl;
|
|
5475
|
+
setUrl(nextUrl);
|
|
3182
5476
|
}).catch((cause) => {
|
|
3183
|
-
if (
|
|
5477
|
+
if (canceled) return;
|
|
5478
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
3184
5479
|
});
|
|
3185
5480
|
return () => {
|
|
3186
|
-
|
|
3187
|
-
if (objectUrl !== void 0) URL.revokeObjectURL(objectUrl);
|
|
5481
|
+
canceled = true;
|
|
3188
5482
|
};
|
|
3189
|
-
}, [
|
|
5483
|
+
}, [activeAttachment?.attachmentId]);
|
|
3190
5484
|
const copy = async (e) => {
|
|
3191
5485
|
e.stopPropagation();
|
|
3192
5486
|
if (!blob) return;
|
|
@@ -3201,7 +5495,7 @@ window.__ModuleLoader__.load({
|
|
|
3201
5495
|
if (!url) return;
|
|
3202
5496
|
const a = document.createElement("a");
|
|
3203
5497
|
a.href = url;
|
|
3204
|
-
a.download =
|
|
5498
|
+
a.download = activeAttachment?.name || `dsh-image-${Date.now()}.png`;
|
|
3205
5499
|
document.body.appendChild(a);
|
|
3206
5500
|
a.click();
|
|
3207
5501
|
document.body.removeChild(a);
|
|
@@ -3211,6 +5505,93 @@ window.__ModuleLoader__.load({
|
|
|
3211
5505
|
if (!url) return;
|
|
3212
5506
|
window.open(url, "_blank", "noopener,noreferrer");
|
|
3213
5507
|
};
|
|
5508
|
+
const openRegenerate = (event) => {
|
|
5509
|
+
event.stopPropagation();
|
|
5510
|
+
if (activeResult === void 0 || isRegenerating) return;
|
|
5511
|
+
setRegeneratePrompt(activeResult.prompt);
|
|
5512
|
+
setRegenerateError(void 0);
|
|
5513
|
+
setRegenerateOpen(true);
|
|
5514
|
+
};
|
|
5515
|
+
const cancelRegenerate = () => {
|
|
5516
|
+
regenerateControllerRef.current?.abort();
|
|
5517
|
+
regenerateControllerRef.current = void 0;
|
|
5518
|
+
setIsRegenerating(false);
|
|
5519
|
+
setRegenerateOpen(false);
|
|
5520
|
+
setRegenerateError(void 0);
|
|
5521
|
+
};
|
|
5522
|
+
const regenerate = async () => {
|
|
5523
|
+
if (activeResult === void 0 || !originId || isRegenerating || regeneratePrompt.trim().length === 0) return;
|
|
5524
|
+
setIsRegenerating(true);
|
|
5525
|
+
setRegenerateError(void 0);
|
|
5526
|
+
setRegenerateOpen(false);
|
|
5527
|
+
const controller = new AbortController();
|
|
5528
|
+
regenerateControllerRef.current = controller;
|
|
5529
|
+
try {
|
|
5530
|
+
const request = conversationRegenerateRequest(activeResult, regeneratePrompt, selectedRevision === void 0 ? void 0 : {
|
|
5531
|
+
ratio: selectedRevision.ratio,
|
|
5532
|
+
quality: selectedRevision.quality
|
|
5533
|
+
});
|
|
5534
|
+
const response = await fetch(STUDIO_ROUTE, {
|
|
5535
|
+
method: "POST",
|
|
5536
|
+
credentials: "same-origin",
|
|
5537
|
+
signal: controller.signal,
|
|
5538
|
+
headers: { "content-type": "application/json" },
|
|
5539
|
+
body: JSON.stringify(request)
|
|
5540
|
+
});
|
|
5541
|
+
const payload = await response.json().catch(() => null);
|
|
5542
|
+
if (!response.ok || payload === null || !("attachment" in payload)) throw new Error(payload && "error" in payload && payload.error ? payload.error : t("regenerateFailed"));
|
|
5543
|
+
const revision = {
|
|
5544
|
+
attachment: payload.attachment,
|
|
5545
|
+
prompt: payload.prompt,
|
|
5546
|
+
provider: payload.provider,
|
|
5547
|
+
model: payload.model,
|
|
5548
|
+
output: payload.output,
|
|
5549
|
+
createdAt: payload.createdAt,
|
|
5550
|
+
ratio: request.ratio,
|
|
5551
|
+
quality: request.quality
|
|
5552
|
+
};
|
|
5553
|
+
await saveGalleryItem({
|
|
5554
|
+
id: String(revision.attachment.attachmentId),
|
|
5555
|
+
attachment: revision.attachment,
|
|
5556
|
+
prompt: revision.prompt,
|
|
5557
|
+
provider: revision.provider,
|
|
5558
|
+
model: revision.model,
|
|
5559
|
+
createdAt: revision.createdAt,
|
|
5560
|
+
aspectRatio: revision.ratio,
|
|
5561
|
+
imageSize: revision.quality,
|
|
5562
|
+
output: revision.output,
|
|
5563
|
+
...savedTo ? { savedTo } : {},
|
|
5564
|
+
...sessionId ? { sessionId } : {}
|
|
5565
|
+
});
|
|
5566
|
+
const next = appendConversationImageRevision(String(originId), revision);
|
|
5567
|
+
setRevisionChain(next);
|
|
5568
|
+
setToast(t("regenerate"));
|
|
5569
|
+
setTimeout(() => {
|
|
5570
|
+
setToast(void 0);
|
|
5571
|
+
}, 2e3);
|
|
5572
|
+
} catch (cause) {
|
|
5573
|
+
if (!controller.signal.aborted) {
|
|
5574
|
+
const errMsg = cause instanceof Error ? cause.message : String(cause);
|
|
5575
|
+
setError(errMsg);
|
|
5576
|
+
setToast(errMsg);
|
|
5577
|
+
setTimeout(() => {
|
|
5578
|
+
setToast(void 0);
|
|
5579
|
+
}, 3500);
|
|
5580
|
+
}
|
|
5581
|
+
} finally {
|
|
5582
|
+
if (regenerateControllerRef.current === controller) {
|
|
5583
|
+
regenerateControllerRef.current = void 0;
|
|
5584
|
+
setIsRegenerating(false);
|
|
5585
|
+
}
|
|
5586
|
+
}
|
|
5587
|
+
};
|
|
5588
|
+
const selectVersion = (index) => {
|
|
5589
|
+
if (!originId || isRegenerating) return;
|
|
5590
|
+
setRevisionChain(selectConversationImageRevision(String(originId), index));
|
|
5591
|
+
};
|
|
5592
|
+
const canRegenerate = activeResult !== void 0 && CLOUD_IMAGE_PROVIDERS.includes(activeResult.provider) && activeResult.model.trim().length > 0;
|
|
5593
|
+
const versionTotal = revisionChain.revisions.length + 1;
|
|
5594
|
+
const versionCurrent = revisionChain.currentIndex + 1;
|
|
3214
5595
|
if (attachment === void 0) return (0, react_jsx_runtime.jsx)("div", {
|
|
3215
5596
|
className: "dsh-ig-loading",
|
|
3216
5597
|
children: t("generating")
|
|
@@ -3241,100 +5622,174 @@ window.__ModuleLoader__.load({
|
|
|
3241
5622
|
}) : null,
|
|
3242
5623
|
url !== void 0 ? (0, react_jsx_runtime.jsxs)("div", {
|
|
3243
5624
|
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
|
-
]
|
|
5625
|
+
children: [
|
|
5626
|
+
(0, react_jsx_runtime.jsx)("img", {
|
|
5627
|
+
className: "dsh-ig-image",
|
|
5628
|
+
src: url,
|
|
5629
|
+
alt: activeAttachment?.name ?? "Generated image",
|
|
5630
|
+
onClick: () => {
|
|
5631
|
+
if (!isRegenerating) setPreviewOpen(true);
|
|
5632
|
+
}
|
|
5633
|
+
}),
|
|
5634
|
+
isRegenerating ? (0, react_jsx_runtime.jsxs)("div", {
|
|
5635
|
+
className: "dsh-ig-regenerate-overlay",
|
|
5636
|
+
children: [
|
|
5637
|
+
(0, react_jsx_runtime.jsx)("div", { className: "dsh-ig-regenerate-spinner" }),
|
|
5638
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
5639
|
+
className: "dsh-ig-regenerate-overlay-text",
|
|
5640
|
+
children: t("regenerating")
|
|
5641
|
+
}),
|
|
5642
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
5643
|
+
type: "button",
|
|
5644
|
+
className: "dsh-ig-regenerate-overlay-cancel",
|
|
5645
|
+
onClick: cancelRegenerate,
|
|
5646
|
+
children: t("cancel")
|
|
3304
5647
|
})
|
|
5648
|
+
]
|
|
5649
|
+
}) : null,
|
|
5650
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
5651
|
+
className: "dsh-ig-toolbar",
|
|
5652
|
+
children: [
|
|
5653
|
+
canRegenerate ? (0, react_jsx_runtime.jsx)("button", {
|
|
5654
|
+
type: "button",
|
|
5655
|
+
className: "dsh-ig-tool-btn",
|
|
5656
|
+
disabled: isRegenerating,
|
|
5657
|
+
title: isRegenerating ? t("regenerating") : t("regenerate"),
|
|
5658
|
+
onClick: openRegenerate,
|
|
5659
|
+
children: (0, react_jsx_runtime.jsxs)("svg", {
|
|
5660
|
+
width: "15",
|
|
5661
|
+
height: "15",
|
|
5662
|
+
viewBox: "0 0 24 24",
|
|
5663
|
+
fill: "none",
|
|
5664
|
+
stroke: "currentColor",
|
|
5665
|
+
strokeWidth: "2",
|
|
5666
|
+
strokeLinecap: "round",
|
|
5667
|
+
strokeLinejoin: "round",
|
|
5668
|
+
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" })]
|
|
5669
|
+
})
|
|
5670
|
+
}) : null,
|
|
5671
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
5672
|
+
type: "button",
|
|
5673
|
+
className: "dsh-ig-tool-btn",
|
|
5674
|
+
title: t("copyImg"),
|
|
5675
|
+
onClick: (e) => {
|
|
5676
|
+
copy(e);
|
|
5677
|
+
},
|
|
5678
|
+
children: (0, react_jsx_runtime.jsxs)("svg", {
|
|
5679
|
+
width: "15",
|
|
5680
|
+
height: "15",
|
|
5681
|
+
viewBox: "0 0 24 24",
|
|
5682
|
+
fill: "none",
|
|
5683
|
+
stroke: "currentColor",
|
|
5684
|
+
strokeWidth: "2",
|
|
5685
|
+
strokeLinecap: "round",
|
|
5686
|
+
strokeLinejoin: "round",
|
|
5687
|
+
children: [(0, react_jsx_runtime.jsx)("rect", {
|
|
5688
|
+
x: "9",
|
|
5689
|
+
y: "9",
|
|
5690
|
+
width: "13",
|
|
5691
|
+
height: "13",
|
|
5692
|
+
rx: "2",
|
|
5693
|
+
ry: "2"
|
|
5694
|
+
}), (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" })]
|
|
5695
|
+
})
|
|
5696
|
+
}),
|
|
5697
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
5698
|
+
type: "button",
|
|
5699
|
+
className: "dsh-ig-tool-btn",
|
|
5700
|
+
title: t("download"),
|
|
5701
|
+
onClick: download,
|
|
5702
|
+
children: (0, react_jsx_runtime.jsxs)("svg", {
|
|
5703
|
+
width: "15",
|
|
5704
|
+
height: "15",
|
|
5705
|
+
viewBox: "0 0 24 24",
|
|
5706
|
+
fill: "none",
|
|
5707
|
+
stroke: "currentColor",
|
|
5708
|
+
strokeWidth: "2",
|
|
5709
|
+
strokeLinecap: "round",
|
|
5710
|
+
strokeLinejoin: "round",
|
|
5711
|
+
children: [
|
|
5712
|
+
(0, react_jsx_runtime.jsx)("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
5713
|
+
(0, react_jsx_runtime.jsx)("polyline", { points: "7 10 12 15 17 10" }),
|
|
5714
|
+
(0, react_jsx_runtime.jsx)("line", {
|
|
5715
|
+
x1: "12",
|
|
5716
|
+
y1: "15",
|
|
5717
|
+
x2: "12",
|
|
5718
|
+
y2: "3"
|
|
5719
|
+
})
|
|
5720
|
+
]
|
|
5721
|
+
})
|
|
5722
|
+
}),
|
|
5723
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
5724
|
+
type: "button",
|
|
5725
|
+
className: "dsh-ig-tool-btn",
|
|
5726
|
+
title: t("openNewTab"),
|
|
5727
|
+
onClick: openNewTab,
|
|
5728
|
+
children: (0, react_jsx_runtime.jsxs)("svg", {
|
|
5729
|
+
width: "15",
|
|
5730
|
+
height: "15",
|
|
5731
|
+
viewBox: "0 0 24 24",
|
|
5732
|
+
fill: "none",
|
|
5733
|
+
stroke: "currentColor",
|
|
5734
|
+
strokeWidth: "2",
|
|
5735
|
+
strokeLinecap: "round",
|
|
5736
|
+
strokeLinejoin: "round",
|
|
5737
|
+
children: [
|
|
5738
|
+
(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" }),
|
|
5739
|
+
(0, react_jsx_runtime.jsx)("polyline", { points: "15 3 21 3 21 9" }),
|
|
5740
|
+
(0, react_jsx_runtime.jsx)("line", {
|
|
5741
|
+
x1: "10",
|
|
5742
|
+
y1: "14",
|
|
5743
|
+
x2: "21",
|
|
5744
|
+
y2: "3"
|
|
5745
|
+
})
|
|
5746
|
+
]
|
|
5747
|
+
})
|
|
5748
|
+
}),
|
|
5749
|
+
toast ? (0, react_jsx_runtime.jsx)("div", {
|
|
5750
|
+
className: "dsh-ig-toast",
|
|
5751
|
+
children: toast
|
|
5752
|
+
}) : null
|
|
5753
|
+
]
|
|
5754
|
+
}),
|
|
5755
|
+
versionTotal > 1 ? (0, react_jsx_runtime.jsxs)("div", {
|
|
5756
|
+
className: "dsh-ig-version-nav",
|
|
5757
|
+
"aria-label": t("versionLabel", {
|
|
5758
|
+
current: String(versionCurrent),
|
|
5759
|
+
total: String(versionTotal)
|
|
3305
5760
|
}),
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
strokeLinejoin: "round",
|
|
5761
|
+
children: [
|
|
5762
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
5763
|
+
type: "button",
|
|
5764
|
+
title: t("versionPrevious"),
|
|
5765
|
+
disabled: revisionChain.currentIndex === 0 || isRegenerating,
|
|
5766
|
+
onClick: (event) => {
|
|
5767
|
+
event.stopPropagation();
|
|
5768
|
+
selectVersion(revisionChain.currentIndex - 1);
|
|
5769
|
+
},
|
|
5770
|
+
children: "‹"
|
|
5771
|
+
}),
|
|
5772
|
+
(0, react_jsx_runtime.jsxs)("span", {
|
|
5773
|
+
className: "dsh-ig-version-count",
|
|
3320
5774
|
children: [
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
x1: "10",
|
|
3325
|
-
y1: "14",
|
|
3326
|
-
x2: "21",
|
|
3327
|
-
y2: "3"
|
|
3328
|
-
})
|
|
5775
|
+
versionCurrent,
|
|
5776
|
+
"/",
|
|
5777
|
+
versionTotal
|
|
3329
5778
|
]
|
|
5779
|
+
}),
|
|
5780
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
5781
|
+
type: "button",
|
|
5782
|
+
title: t("versionNext"),
|
|
5783
|
+
disabled: revisionChain.currentIndex >= revisionChain.revisions.length || isRegenerating,
|
|
5784
|
+
onClick: (event) => {
|
|
5785
|
+
event.stopPropagation();
|
|
5786
|
+
selectVersion(revisionChain.currentIndex + 1);
|
|
5787
|
+
},
|
|
5788
|
+
children: "›"
|
|
3330
5789
|
})
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
children: toast
|
|
3335
|
-
}) : null
|
|
3336
|
-
]
|
|
3337
|
-
})]
|
|
5790
|
+
]
|
|
5791
|
+
}) : null
|
|
5792
|
+
]
|
|
3338
5793
|
}) : null,
|
|
3339
5794
|
previewOpen && url !== void 0 ? (0, react_jsx_runtime.jsx)("div", {
|
|
3340
5795
|
className: "dsh-ig-lightbox-backdrop",
|
|
@@ -3349,9 +5804,63 @@ window.__ModuleLoader__.load({
|
|
|
3349
5804
|
children: (0, react_jsx_runtime.jsx)("img", {
|
|
3350
5805
|
className: "dsh-ig-lightbox-img",
|
|
3351
5806
|
src: url,
|
|
3352
|
-
alt:
|
|
5807
|
+
alt: activeAttachment?.name ?? "Generated image preview"
|
|
3353
5808
|
})
|
|
3354
5809
|
})
|
|
5810
|
+
}) : null,
|
|
5811
|
+
regenerateOpen && activeResult !== void 0 ? (0, react_jsx_runtime.jsx)("div", {
|
|
5812
|
+
className: "dsh-ig-regenerate-backdrop",
|
|
5813
|
+
onMouseDown: (event) => {
|
|
5814
|
+
if (event.target === event.currentTarget && !isRegenerating) setRegenerateOpen(false);
|
|
5815
|
+
},
|
|
5816
|
+
children: (0, react_jsx_runtime.jsxs)("div", {
|
|
5817
|
+
className: "dsh-ig-regenerate-dialog",
|
|
5818
|
+
role: "dialog",
|
|
5819
|
+
"aria-modal": "true",
|
|
5820
|
+
"aria-labelledby": `dsh-ig-regenerate-${String(originId)}`,
|
|
5821
|
+
children: [
|
|
5822
|
+
(0, react_jsx_runtime.jsx)("h3", {
|
|
5823
|
+
id: `dsh-ig-regenerate-${String(originId)}`,
|
|
5824
|
+
children: t("regenerateTitle")
|
|
5825
|
+
}),
|
|
5826
|
+
(0, react_jsx_runtime.jsx)("p", { children: t("regenerateHint") }),
|
|
5827
|
+
(0, react_jsx_runtime.jsxs)("label", { children: [(0, react_jsx_runtime.jsx)("span", { children: t("prompt") }), (0, react_jsx_runtime.jsx)("textarea", {
|
|
5828
|
+
ref: regenerateTextareaRef,
|
|
5829
|
+
value: regeneratePrompt,
|
|
5830
|
+
maxLength: 2e3,
|
|
5831
|
+
disabled: isRegenerating,
|
|
5832
|
+
onChange: (event) => setRegeneratePrompt(event.target.value),
|
|
5833
|
+
onKeyDown: (event) => {
|
|
5834
|
+
if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) {
|
|
5835
|
+
event.preventDefault();
|
|
5836
|
+
regenerate();
|
|
5837
|
+
}
|
|
5838
|
+
}
|
|
5839
|
+
})] }),
|
|
5840
|
+
regenerateError ? (0, react_jsx_runtime.jsx)("p", {
|
|
5841
|
+
className: "dsh-ig-regenerate-error",
|
|
5842
|
+
role: "alert",
|
|
5843
|
+
children: regenerateError
|
|
5844
|
+
}) : null,
|
|
5845
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
5846
|
+
className: "dsh-ig-regenerate-actions",
|
|
5847
|
+
children: [(0, react_jsx_runtime.jsx)("button", {
|
|
5848
|
+
type: "button",
|
|
5849
|
+
className: "dsh-ig-regenerate-cancel",
|
|
5850
|
+
onClick: cancelRegenerate,
|
|
5851
|
+
children: t("cancel")
|
|
5852
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
5853
|
+
type: "button",
|
|
5854
|
+
className: "dsh-ig-regenerate-confirm",
|
|
5855
|
+
disabled: isRegenerating || regeneratePrompt.trim().length === 0,
|
|
5856
|
+
onClick: () => {
|
|
5857
|
+
regenerate();
|
|
5858
|
+
},
|
|
5859
|
+
children: t("confirmRegenerate")
|
|
5860
|
+
})]
|
|
5861
|
+
})
|
|
5862
|
+
]
|
|
5863
|
+
})
|
|
3355
5864
|
}) : null
|
|
3356
5865
|
]
|
|
3357
5866
|
});
|