dsh-image-gen 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -16,22 +16,27 @@ window.__ModuleLoader__.load({
16
16
  const DEFAULT_GOOGLE_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/interactions";
17
17
  const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
18
18
  const DEFAULT_SEEDREAM_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3";
19
+ const DEFAULT_DASHSCOPE_ENDPOINT = "https://dashscope.aliyuncs.com/api/v1";
19
20
  const DEFAULT_MODELS = {
20
21
  google: "gemini-3.1-flash-image",
21
22
  openai: "gpt-image-2",
22
- seedream: "doubao-seedream-5-0-260128"
23
+ seedream: "doubao-seedream-5-0-260128",
24
+ dashscope: "wanx2.1-t2i-turbo"
23
25
  };
24
26
  const DEFAULT_BASE_URLS = {
25
27
  google: DEFAULT_GOOGLE_ENDPOINT,
26
28
  openai: DEFAULT_OPENAI_BASE_URL,
27
- seedream: DEFAULT_SEEDREAM_BASE_URL
29
+ seedream: DEFAULT_SEEDREAM_BASE_URL,
30
+ dashscope: DEFAULT_DASHSCOPE_ENDPOINT
28
31
  };
29
32
  //#endregion
30
33
  //#region lib/types/client/gallery-store.js
31
34
  const DB_NAME = "dsh_image_gen_db";
32
- const DB_VERSION = 1;
35
+ const DB_VERSION = 2;
33
36
  const STORE_NAME = "gallery_history";
37
+ const TOMBSTONE_STORE = "gallery_tombstones";
34
38
  let dbPromise = null;
39
+ let tombstonesCache = null;
35
40
  function getDB() {
36
41
  if (dbPromise) return dbPromise;
37
42
  dbPromise = new Promise((resolve, reject) => {
@@ -43,6 +48,7 @@ window.__ModuleLoader__.load({
43
48
  request.onupgradeneeded = (event) => {
44
49
  const db = event.target.result;
45
50
  if (!db.objectStoreNames.contains(STORE_NAME)) db.createObjectStore(STORE_NAME, { keyPath: "id" }).createIndex("createdAt", "createdAt", { unique: false });
51
+ if (!db.objectStoreNames.contains(TOMBSTONE_STORE)) db.createObjectStore(TOMBSTONE_STORE, { keyPath: "id" });
46
52
  };
47
53
  request.onsuccess = () => {
48
54
  resolve(request.result);
@@ -53,6 +59,30 @@ window.__ModuleLoader__.load({
53
59
  });
54
60
  return dbPromise;
55
61
  }
62
+ async function loadTombstones(db) {
63
+ if (tombstonesCache) return tombstonesCache;
64
+ return new Promise((resolve) => {
65
+ if (!db.objectStoreNames.contains(TOMBSTONE_STORE)) {
66
+ tombstonesCache = /* @__PURE__ */ new Set();
67
+ resolve(tombstonesCache);
68
+ return;
69
+ }
70
+ try {
71
+ const req = db.transaction(TOMBSTONE_STORE, "readonly").objectStore(TOMBSTONE_STORE).getAllKeys();
72
+ req.onsuccess = () => {
73
+ tombstonesCache = new Set(req.result.map(String));
74
+ resolve(tombstonesCache);
75
+ };
76
+ req.onerror = () => {
77
+ tombstonesCache = /* @__PURE__ */ new Set();
78
+ resolve(tombstonesCache);
79
+ };
80
+ } catch {
81
+ tombstonesCache = /* @__PURE__ */ new Set();
82
+ resolve(tombstonesCache);
83
+ }
84
+ });
85
+ }
56
86
  const listeners = /* @__PURE__ */ new Set();
57
87
  function notifyListeners() {
58
88
  for (const listener of listeners) try {
@@ -72,10 +102,12 @@ window.__ModuleLoader__.load({
72
102
  }
73
103
  /**
74
104
  * Save or update a gallery record by attachmentId.
105
+ * Skipped if the item was previously deleted (tombstoned).
75
106
  */
76
107
  async function saveGalleryItem(item) {
77
108
  try {
78
109
  const db = await getDB();
110
+ if ((await loadTombstones(db)).has(item.id)) return;
79
111
  const record = {
80
112
  ...item,
81
113
  createdAt: item.createdAt ?? Date.now()
@@ -113,6 +145,30 @@ window.__ModuleLoader__.load({
113
145
  return [];
114
146
  }
115
147
  }
148
+ /**
149
+ * Delete a single gallery record by ID and record a tombstone.
150
+ */
151
+ async function deleteGalleryItem(id) {
152
+ try {
153
+ const db = await getDB();
154
+ (await loadTombstones(db)).add(id);
155
+ await new Promise((resolve, reject) => {
156
+ const tx = db.transaction([STORE_NAME, TOMBSTONE_STORE], "readwrite");
157
+ const store = tx.objectStore(STORE_NAME);
158
+ const tombstoneStore = tx.objectStore(TOMBSTONE_STORE);
159
+ store.delete(id);
160
+ tombstoneStore.put({
161
+ id,
162
+ deletedAt: Date.now()
163
+ });
164
+ tx.oncomplete = () => resolve();
165
+ tx.onerror = () => reject(tx.error);
166
+ });
167
+ notifyListeners();
168
+ } catch (err) {
169
+ console.warn("[dsh-image-gen] Failed to delete gallery item from IndexedDB:", err);
170
+ }
171
+ }
116
172
  //#endregion
117
173
  //#region lib/types/client/gallery-view.js
118
174
  /**
@@ -127,6 +183,7 @@ window.__ModuleLoader__.load({
127
183
  filterGoogle: "Google Gemini",
128
184
  filterOpenAI: "OpenAI / 中转站",
129
185
  filterSeedream: "字节 Seedream",
186
+ filterDashScope: "阿里 DashScope",
130
187
  emptyTitle: "暂无生图记录",
131
188
  emptyDesc: "在对话中让 Agent 生图后,生成的图片会自动收录到这里。",
132
189
  noMatchTitle: "未找到匹配结果",
@@ -138,6 +195,9 @@ window.__ModuleLoader__.load({
138
195
  download: "下载图片",
139
196
  copyImg: "复制图片",
140
197
  copyPpt: "复制 Prompt",
198
+ delete: "从画廊删除",
199
+ confirmDelete: "确定要从画廊中删除这张图片吗?(不会影响原聊天记录)",
200
+ deleted: "已从画廊删除",
141
201
  model: "模型",
142
202
  prompt: "Prompt",
143
203
  close: "关闭 (Esc)"
@@ -149,6 +209,7 @@ window.__ModuleLoader__.load({
149
209
  filterGoogle: "Google Gemini",
150
210
  filterOpenAI: "OpenAI / Relay",
151
211
  filterSeedream: "ByteDance Seedream",
212
+ filterDashScope: "Aliyun DashScope",
152
213
  emptyTitle: "No images generated yet",
153
214
  emptyDesc: "Images generated during conversations will automatically appear here.",
154
215
  noMatchTitle: "No matching images",
@@ -160,6 +221,9 @@ window.__ModuleLoader__.load({
160
221
  download: "Download",
161
222
  copyImg: "Copy Image",
162
223
  copyPpt: "Copy Prompt",
224
+ delete: "Delete from gallery",
225
+ confirmDelete: "Are you sure you want to remove this image from the gallery? (Chat history will not be affected)",
226
+ deleted: "Deleted from gallery",
163
227
  model: "Model",
164
228
  prompt: "Prompt",
165
229
  close: "Close (Esc)"
@@ -309,6 +373,10 @@ window.__ModuleLoader__.load({
309
373
  (0, react_jsx_runtime.jsx)("option", {
310
374
  value: "seedream",
311
375
  children: t("filterSeedream")
376
+ }),
377
+ (0, react_jsx_runtime.jsx)("option", {
378
+ value: "dashscope",
379
+ children: t("filterDashScope")
312
380
  })
313
381
  ]
314
382
  })]
@@ -524,6 +592,45 @@ window.__ModuleLoader__.load({
524
592
  })
525
593
  ]
526
594
  }), (0, react_jsx_runtime.jsx)("span", { children: t("download") })]
595
+ }),
596
+ (0, react_jsx_runtime.jsxs)("button", {
597
+ type: "button",
598
+ className: "dsh-ig-lightbox-btn dsh-ig-lightbox-btn-danger",
599
+ title: t("delete"),
600
+ onClick: async () => {
601
+ if (!window.confirm(t("confirmDelete"))) return;
602
+ await deleteGalleryItem(previewItem.id);
603
+ setPreviewItem(null);
604
+ setPreviewUrl(null);
605
+ setPreviewBlob(null);
606
+ showToast(t("deleted"));
607
+ },
608
+ children: [(0, react_jsx_runtime.jsxs)("svg", {
609
+ width: "14",
610
+ height: "14",
611
+ viewBox: "0 0 24 24",
612
+ fill: "none",
613
+ stroke: "currentColor",
614
+ strokeWidth: "2",
615
+ strokeLinecap: "round",
616
+ strokeLinejoin: "round",
617
+ children: [
618
+ (0, react_jsx_runtime.jsx)("polyline", { points: "3 6 5 6 21 6" }),
619
+ (0, react_jsx_runtime.jsx)("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }),
620
+ (0, react_jsx_runtime.jsx)("line", {
621
+ x1: "10",
622
+ y1: "11",
623
+ x2: "10",
624
+ y2: "17"
625
+ }),
626
+ (0, react_jsx_runtime.jsx)("line", {
627
+ x1: "14",
628
+ y1: "11",
629
+ x2: "14",
630
+ y2: "17"
631
+ })
632
+ ]
633
+ }), (0, react_jsx_runtime.jsx)("span", { children: t("delete") })]
527
634
  })
528
635
  ]
529
636
  })]
@@ -691,6 +798,43 @@ window.__ModuleLoader__.load({
691
798
  ry: "1"
692
799
  })]
693
800
  })
801
+ }),
802
+ (0, react_jsx_runtime.jsx)("button", {
803
+ type: "button",
804
+ className: "dsh-ig-tool-btn dsh-ig-tool-btn-danger",
805
+ title: t("delete"),
806
+ onClick: async (e) => {
807
+ e.stopPropagation();
808
+ if (!window.confirm(t("confirmDelete"))) return;
809
+ await deleteGalleryItem(item.id);
810
+ onToast(t("deleted"));
811
+ },
812
+ children: (0, react_jsx_runtime.jsxs)("svg", {
813
+ width: "14",
814
+ height: "14",
815
+ viewBox: "0 0 24 24",
816
+ fill: "none",
817
+ stroke: "currentColor",
818
+ strokeWidth: "2",
819
+ strokeLinecap: "round",
820
+ strokeLinejoin: "round",
821
+ children: [
822
+ (0, react_jsx_runtime.jsx)("polyline", { points: "3 6 5 6 21 6" }),
823
+ (0, react_jsx_runtime.jsx)("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }),
824
+ (0, react_jsx_runtime.jsx)("line", {
825
+ x1: "10",
826
+ y1: "11",
827
+ x2: "10",
828
+ y2: "17"
829
+ }),
830
+ (0, react_jsx_runtime.jsx)("line", {
831
+ x1: "14",
832
+ y1: "11",
833
+ x2: "14",
834
+ y2: "17"
835
+ })
836
+ ]
837
+ })
694
838
  })
695
839
  ]
696
840
  })
@@ -750,7 +894,8 @@ window.__ModuleLoader__.load({
750
894
  const KEY_REF = {
751
895
  google: "GEMINI_API_KEY",
752
896
  openai: "OPENAI_API_KEY",
753
- seedream: "ARK_API_KEY"
897
+ seedream: "ARK_API_KEY",
898
+ dashscope: "DASHSCOPE_API_KEY"
754
899
  };
755
900
  const DICT = {
756
901
  zh: {
@@ -760,6 +905,7 @@ window.__ModuleLoader__.load({
760
905
  providerGoogle: "Google Gemini",
761
906
  providerOpenAI: "OpenAI / 中转站",
762
907
  providerSeedream: "字节 Seedream",
908
+ providerDashScope: "阿里 DashScope (通义万相 / Qwen)",
763
909
  apiKeyLabel: "{provider} API Key",
764
910
  apiKeyPlaceholder: "留空即可保留已配置的 Key",
765
911
  apiKeyHint: "安全保存为 {key};页面不会读回明文。",
@@ -769,6 +915,7 @@ window.__ModuleLoader__.load({
769
915
  endpointHintGoogle: "Google 官方地址或反代端点(全路径)。",
770
916
  endpointHintOpenAI: "中转站请填其 OpenAI 兼容的 /v1 地址。",
771
917
  endpointHintSeedream: "火山方舟兼容的 /api/v3 地址。",
918
+ endpointHintDashScope: "阿里云百炼 DashScope 官方接口地址。",
772
919
  model: "模型",
773
920
  saving: "保存中…",
774
921
  save: "保存",
@@ -793,6 +940,7 @@ window.__ModuleLoader__.load({
793
940
  providerGoogle: "Google Gemini",
794
941
  providerOpenAI: "OpenAI / Relay",
795
942
  providerSeedream: "ByteDance Seedream",
943
+ providerDashScope: "Aliyun DashScope (Wanx / Qwen)",
796
944
  apiKeyLabel: "{provider} API Key",
797
945
  apiKeyPlaceholder: "Leave empty to keep configured key",
798
946
  apiKeyHint: "Securely saved as {key}; never read back in plaintext.",
@@ -802,6 +950,7 @@ window.__ModuleLoader__.load({
802
950
  endpointHintGoogle: "Official Google endpoint or reverse proxy (full path).",
803
951
  endpointHintOpenAI: "OpenAI-compatible /v1 base URL for relays.",
804
952
  endpointHintSeedream: "Volcengine Ark compatible /api/v3 base URL.",
953
+ endpointHintDashScope: "Official Aliyun DashScope endpoint.",
805
954
  model: "Model",
806
955
  saving: "Saving…",
807
956
  save: "Save",
@@ -849,8 +998,9 @@ window.__ModuleLoader__.load({
849
998
  .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}
850
999
  .dsh-ig-container:hover .dsh-ig-toolbar{opacity:1;pointer-events:auto}
851
1000
  .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:10;line-height:1}
852
- .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}
1001
+ .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}
853
1002
  .dsh-ig-tool-btn:hover{background:rgba(255,255,255,0.25)}
1003
+ .dsh-ig-tool-btn-danger:hover{background:rgba(239,68,68,0.75)!important;color:#fff!important}
854
1004
  .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:20}
855
1005
  .dsh-ig-image{display:block;max-width:100%;max-height:520px;border-radius:12px;background:#f2f3f5;cursor:pointer}
856
1006
  @keyframes dsh-ig-fade{from{opacity:0}to{opacity:1}}
@@ -903,8 +1053,10 @@ window.__ModuleLoader__.load({
903
1053
  .dsh-ig-lightbox-bottombar{position:absolute;bottom:24px;left:50%;transform:translateX(-50%);max-width:min(90vw,640px);background:rgba(20,22,26,0.85);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,0.15);border-radius:14px;padding:10px 16px;display:flex;flex-direction:column;gap:8px;color:#fff;box-shadow:0 16px 40px rgba(0,0,0,0.5);cursor:default}
904
1054
  .dsh-ig-lightbox-prompt-text{font-size:13px;line-height:1.4;color:rgba(255,255,255,0.92);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}
905
1055
  .dsh-ig-lightbox-actions{display:flex;align-items:center;gap:8px;justify-content:flex-end;border-top:1px solid rgba(255,255,255,0.1);padding-top:8px}
906
- .dsh-ig-lightbox-btn{appearance:none;border:1px solid rgba(255,255,255,0.18);background:rgba(255,255,255,0.08);color:#fff;border-radius:6px;padding:5px 10px;font-size:12px;cursor:pointer;display:inline-flex;align-items:center;gap:5px;transition:background .15s}
1056
+ .dsh-ig-lightbox-btn{appearance:none;border:1px solid rgba(255,255,255,0.18);background:rgba(255,255,255,0.08);color:#fff;border-radius:6px;padding:5px 10px;font-size:12px;cursor:pointer;display:inline-flex;align-items:center;gap:5px;transition:background .15s,border-color .15s,color .15s}
907
1057
  .dsh-ig-lightbox-btn:hover{background:rgba(255,255,255,0.22)}
1058
+ .dsh-ig-lightbox-btn-danger{border-color:rgba(239,68,68,0.4);color:#fca5a5}
1059
+ .dsh-ig-lightbox-btn-danger:hover{background:rgba(239,68,68,0.35)!important;color:#fff!important;border-color:rgba(239,68,68,0.7)!important}
908
1060
  .dsh-ig-gallery-page-toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.85);color:#fff;padding:6px 14px;border-radius:8px;font-size:13px;z-index:99999;animation:dsh-ig-fade .15s}
909
1061
 
910
1062
  /* Hide floating chat composer when gallery page is active */
@@ -936,8 +1088,6 @@ window.__ModuleLoader__.load({
936
1088
  ctx.slots.inject("settings.plugin.item", () => register({
937
1089
  name: "settings.plugin.item",
938
1090
  key: IMAGE_GENERATION_NAMESPACE,
939
- id: IMAGE_GENERATION_NAMESPACE,
940
- order: 100,
941
1091
  inject: () => ({
942
1092
  scope,
943
1093
  credentials: api.credentials,
@@ -987,7 +1137,8 @@ window.__ModuleLoader__.load({
987
1137
  const providerLabels = {
988
1138
  google: t("providerGoogle"),
989
1139
  openai: t("providerOpenAI"),
990
- seedream: t("providerSeedream")
1140
+ seedream: t("providerSeedream"),
1141
+ dashscope: t("providerDashScope")
991
1142
  };
992
1143
  (0, react.useEffect)(() => {
993
1144
  const value = snapshot.value;
@@ -1013,8 +1164,8 @@ window.__ModuleLoader__.load({
1013
1164
  setMessage("");
1014
1165
  try {
1015
1166
  await props.scope.set("provider", provider);
1016
- await props.scope.set(provider === "google" ? "googleModel" : provider === "openai" ? "openaiModel" : "seedreamModel", model);
1017
- await props.scope.set(provider === "google" ? "googleEndpoint" : provider === "openai" ? "openaiBaseURL" : "seedreamBaseURL", baseURL);
1167
+ await props.scope.set(provider === "google" ? "googleModel" : provider === "openai" ? "openaiModel" : provider === "seedream" ? "seedreamModel" : "dashscopeModel", model);
1168
+ await props.scope.set(provider === "google" ? "googleEndpoint" : provider === "openai" ? "openaiBaseURL" : provider === "seedream" ? "seedreamBaseURL" : "dashscopeEndpoint", baseURL);
1018
1169
  if (key.trim().length > 0) {
1019
1170
  const response = await props.credentials.set({
1020
1171
  ref: KEY_REF[provider],
@@ -1100,6 +1251,10 @@ window.__ModuleLoader__.load({
1100
1251
  (0, react_jsx_runtime.jsx)("option", {
1101
1252
  value: "seedream",
1102
1253
  children: t("providerSeedream")
1254
+ }),
1255
+ (0, react_jsx_runtime.jsx)("option", {
1256
+ value: "dashscope",
1257
+ children: t("providerDashScope")
1103
1258
  })
1104
1259
  ]
1105
1260
  }),
@@ -1161,7 +1316,7 @@ window.__ModuleLoader__.load({
1161
1316
  }),
1162
1317
  (0, react_jsx_runtime.jsx)("span", {
1163
1318
  className: "dsh-ig-hint",
1164
- children: provider === "google" ? t("endpointHintGoogle") : provider === "openai" ? t("endpointHintOpenAI") : t("endpointHintSeedream")
1319
+ children: provider === "google" ? t("endpointHintGoogle") : provider === "openai" ? t("endpointHintOpenAI") : provider === "seedream" ? t("endpointHintSeedream") : t("endpointHintDashScope")
1165
1320
  })
1166
1321
  ]
1167
1322
  }),
@@ -1428,11 +1583,11 @@ window.__ModuleLoader__.load({
1428
1583
  });
1429
1584
  }
1430
1585
  function modelOf(provider, value) {
1431
- const stored = provider === "google" ? value?.googleModel : provider === "openai" ? value?.openaiModel : value?.seedreamModel;
1586
+ const stored = provider === "google" ? value?.googleModel : provider === "openai" ? value?.openaiModel : provider === "seedream" ? value?.seedreamModel : value?.dashscopeModel;
1432
1587
  return typeof stored === "string" && stored.length > 0 ? stored : DEFAULT_MODELS[provider];
1433
1588
  }
1434
1589
  function baseURLOf(provider, value) {
1435
- const stored = provider === "google" ? value?.googleEndpoint : provider === "openai" ? value?.openaiBaseURL : value?.seedreamBaseURL;
1590
+ const stored = provider === "google" ? value?.googleEndpoint : provider === "openai" ? value?.openaiBaseURL : provider === "seedream" ? value?.seedreamBaseURL : value?.dashscopeEndpoint;
1436
1591
  return typeof stored === "string" && stored.length > 0 ? stored : DEFAULT_BASE_URLS[provider];
1437
1592
  }
1438
1593
  function imageRef(block) {