dsh-image-gen 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +166 -0
- package/README.md +16 -7
- package/README.zh-CN.md +16 -7
- package/lib/client.js +870 -67
- package/lib/client.js.map +1 -1
- package/lib/index.js +131 -5
- package/lib/types/client/gallery-store.d.ts +41 -0
- package/lib/types/client/gallery-view.d.ts +63 -0
- package/lib/types/client/index.d.ts +8 -8
- package/lib/types/config.d.ts +12 -2
- package/lib/types/dashscope.d.ts +16 -0
- package/lib/types/shared.d.ts +3 -1
- package/package.json +128 -124
package/lib/client.js
CHANGED
|
@@ -16,23 +16,745 @@ 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
|
|
33
|
+
//#region lib/types/client/gallery-store.js
|
|
34
|
+
const DB_NAME = "dsh_image_gen_db";
|
|
35
|
+
const DB_VERSION = 1;
|
|
36
|
+
const STORE_NAME = "gallery_history";
|
|
37
|
+
let dbPromise = null;
|
|
38
|
+
function getDB() {
|
|
39
|
+
if (dbPromise) return dbPromise;
|
|
40
|
+
dbPromise = new Promise((resolve, reject) => {
|
|
41
|
+
if (typeof indexedDB === "undefined") {
|
|
42
|
+
reject(/* @__PURE__ */ new Error("IndexedDB is not supported in this environment."));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
46
|
+
request.onupgradeneeded = (event) => {
|
|
47
|
+
const db = event.target.result;
|
|
48
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) db.createObjectStore(STORE_NAME, { keyPath: "id" }).createIndex("createdAt", "createdAt", { unique: false });
|
|
49
|
+
};
|
|
50
|
+
request.onsuccess = () => {
|
|
51
|
+
resolve(request.result);
|
|
52
|
+
};
|
|
53
|
+
request.onerror = () => {
|
|
54
|
+
reject(request.error);
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
return dbPromise;
|
|
58
|
+
}
|
|
59
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
60
|
+
function notifyListeners() {
|
|
61
|
+
for (const listener of listeners) try {
|
|
62
|
+
listener();
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error("[dsh-image-gen] Gallery listener error:", err);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Subscribe to gallery mutations (insert/delete/clear).
|
|
69
|
+
*/
|
|
70
|
+
function subscribeGallery(listener) {
|
|
71
|
+
listeners.add(listener);
|
|
72
|
+
return () => {
|
|
73
|
+
listeners.delete(listener);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Save or update a gallery record by attachmentId.
|
|
78
|
+
*/
|
|
79
|
+
async function saveGalleryItem(item) {
|
|
80
|
+
try {
|
|
81
|
+
const db = await getDB();
|
|
82
|
+
const record = {
|
|
83
|
+
...item,
|
|
84
|
+
createdAt: item.createdAt ?? Date.now()
|
|
85
|
+
};
|
|
86
|
+
await new Promise((resolve, reject) => {
|
|
87
|
+
const req = db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME).put(record);
|
|
88
|
+
req.onsuccess = () => resolve();
|
|
89
|
+
req.onerror = () => reject(req.error);
|
|
90
|
+
});
|
|
91
|
+
notifyListeners();
|
|
92
|
+
} catch (err) {
|
|
93
|
+
console.warn("[dsh-image-gen] Failed to save gallery item to IndexedDB:", err);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Retrieve all gallery records sorted by createdAt descending.
|
|
98
|
+
*/
|
|
99
|
+
async function getGalleryItems() {
|
|
100
|
+
try {
|
|
101
|
+
const db = await getDB();
|
|
102
|
+
return await new Promise((resolve, reject) => {
|
|
103
|
+
const req = db.transaction(STORE_NAME, "readonly").objectStore(STORE_NAME).index("createdAt").openCursor(null, "prev");
|
|
104
|
+
const items = [];
|
|
105
|
+
req.onsuccess = (event) => {
|
|
106
|
+
const cursor = event.target.result;
|
|
107
|
+
if (cursor) {
|
|
108
|
+
items.push(cursor.value);
|
|
109
|
+
cursor.continue();
|
|
110
|
+
} else resolve(items);
|
|
111
|
+
};
|
|
112
|
+
req.onerror = () => reject(req.error);
|
|
113
|
+
});
|
|
114
|
+
} catch (err) {
|
|
115
|
+
console.warn("[dsh-image-gen] Failed to read gallery items from IndexedDB:", err);
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region lib/types/client/gallery-view.js
|
|
121
|
+
/**
|
|
122
|
+
* Native Workspace Gallery View Component for DSH `conversation.view` slot.
|
|
123
|
+
* Fully i18n-reactive (Chinese & English).
|
|
124
|
+
*/
|
|
125
|
+
const DICT$1 = {
|
|
126
|
+
zh: {
|
|
127
|
+
totalCount: "共 {count} 张生成图片",
|
|
128
|
+
searchPlaceholder: "搜索 Prompt 关键词…",
|
|
129
|
+
filterAll: "全部厂商",
|
|
130
|
+
filterGoogle: "Google Gemini",
|
|
131
|
+
filterOpenAI: "OpenAI / 中转站",
|
|
132
|
+
filterSeedream: "字节 Seedream",
|
|
133
|
+
emptyTitle: "暂无生图记录",
|
|
134
|
+
emptyDesc: "在对话中让 Agent 生图后,生成的图片会自动收录到这里。",
|
|
135
|
+
noMatchTitle: "未找到匹配结果",
|
|
136
|
+
noMatchDesc: "尝试更换搜索关键词或选择其他厂商。",
|
|
137
|
+
copiedPrompt: "已复制 Prompt",
|
|
138
|
+
copiedImage: "已复制图片",
|
|
139
|
+
copyFailed: "复制失败",
|
|
140
|
+
preview: "查看大图",
|
|
141
|
+
download: "下载图片",
|
|
142
|
+
copyImg: "复制图片",
|
|
143
|
+
copyPpt: "复制 Prompt",
|
|
144
|
+
model: "模型",
|
|
145
|
+
prompt: "Prompt",
|
|
146
|
+
close: "关闭 (Esc)"
|
|
147
|
+
},
|
|
148
|
+
en: {
|
|
149
|
+
totalCount: "{count} images total",
|
|
150
|
+
searchPlaceholder: "Search prompt keywords…",
|
|
151
|
+
filterAll: "All Providers",
|
|
152
|
+
filterGoogle: "Google Gemini",
|
|
153
|
+
filterOpenAI: "OpenAI / Relay",
|
|
154
|
+
filterSeedream: "ByteDance Seedream",
|
|
155
|
+
emptyTitle: "No images generated yet",
|
|
156
|
+
emptyDesc: "Images generated during conversations will automatically appear here.",
|
|
157
|
+
noMatchTitle: "No matching images",
|
|
158
|
+
noMatchDesc: "Try a different search keyword or provider filter.",
|
|
159
|
+
copiedPrompt: "Prompt copied",
|
|
160
|
+
copiedImage: "Image copied",
|
|
161
|
+
copyFailed: "Copy failed",
|
|
162
|
+
preview: "Full Preview",
|
|
163
|
+
download: "Download",
|
|
164
|
+
copyImg: "Copy Image",
|
|
165
|
+
copyPpt: "Copy Prompt",
|
|
166
|
+
model: "Model",
|
|
167
|
+
prompt: "Prompt",
|
|
168
|
+
close: "Close (Esc)"
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
const GalleryViewTab = ({ locale }) => {
|
|
172
|
+
const [items, setItems] = (0, react.useState)([]);
|
|
173
|
+
const [search, setSearch] = (0, react.useState)("");
|
|
174
|
+
const [selectedProvider, setSelectedProvider] = (0, react.useState)("all");
|
|
175
|
+
const [previewItem, setPreviewItem] = (0, react.useState)(null);
|
|
176
|
+
const [previewUrl, setPreviewUrl] = (0, react.useState)(null);
|
|
177
|
+
const [previewBlob, setPreviewBlob] = (0, react.useState)(null);
|
|
178
|
+
const [toast, setToast] = (0, react.useState)(null);
|
|
179
|
+
const [lang, setLang] = (0, react.useState)(() => {
|
|
180
|
+
return (locale?.getSnapshot?.()?.active)?.startsWith("en") ? "en" : "zh";
|
|
181
|
+
});
|
|
182
|
+
(0, react.useEffect)(() => {
|
|
183
|
+
if (!locale?.subscribe) return;
|
|
184
|
+
return locale.subscribe(() => {
|
|
185
|
+
const active = locale.getSnapshot?.()?.active;
|
|
186
|
+
setLang(active?.startsWith("en") ? "en" : "zh");
|
|
187
|
+
});
|
|
188
|
+
}, [locale]);
|
|
189
|
+
const dict = lang === "en" ? DICT$1.en : DICT$1.zh;
|
|
190
|
+
const t = (key, params) => {
|
|
191
|
+
let text = dict[key] || DICT$1.zh[key] || key;
|
|
192
|
+
if (params) for (const [k, v] of Object.entries(params)) text = text.replace(`{${k}}`, v);
|
|
193
|
+
return text;
|
|
194
|
+
};
|
|
195
|
+
const showToast = (msg) => {
|
|
196
|
+
setToast(msg);
|
|
197
|
+
setTimeout(() => {
|
|
198
|
+
setToast(null);
|
|
199
|
+
}, 2e3);
|
|
200
|
+
};
|
|
201
|
+
(0, react.useEffect)(() => {
|
|
202
|
+
const seat = document.querySelector("[data-composer-seat]");
|
|
203
|
+
if (seat) {
|
|
204
|
+
const prevDisplay = seat.style.display;
|
|
205
|
+
seat.style.display = "none";
|
|
206
|
+
return () => {
|
|
207
|
+
seat.style.display = prevDisplay;
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}, []);
|
|
211
|
+
(0, react.useEffect)(() => {
|
|
212
|
+
let active = true;
|
|
213
|
+
const load = () => {
|
|
214
|
+
getGalleryItems().then((res) => {
|
|
215
|
+
if (active) setItems(res);
|
|
216
|
+
});
|
|
217
|
+
};
|
|
218
|
+
load();
|
|
219
|
+
const unsubscribe = subscribeGallery(load);
|
|
220
|
+
return () => {
|
|
221
|
+
active = false;
|
|
222
|
+
unsubscribe();
|
|
223
|
+
};
|
|
224
|
+
}, []);
|
|
225
|
+
(0, react.useEffect)(() => {
|
|
226
|
+
if (!previewItem) return;
|
|
227
|
+
const onKeyDown = (e) => {
|
|
228
|
+
if (e.key === "Escape") {
|
|
229
|
+
setPreviewItem(null);
|
|
230
|
+
setPreviewUrl(null);
|
|
231
|
+
setPreviewBlob(null);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
window.addEventListener("keydown", onKeyDown);
|
|
235
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
236
|
+
}, [previewItem]);
|
|
237
|
+
const filteredItems = (0, react.useMemo)(() => {
|
|
238
|
+
return items.filter((item) => {
|
|
239
|
+
if (selectedProvider !== "all" && item.provider !== selectedProvider) return false;
|
|
240
|
+
if (search.trim().length > 0) {
|
|
241
|
+
const q = search.trim().toLowerCase();
|
|
242
|
+
const matchPrompt = item.prompt?.toLowerCase().includes(q);
|
|
243
|
+
const matchModel = item.model?.toLowerCase().includes(q);
|
|
244
|
+
if (!matchPrompt && !matchModel) return false;
|
|
245
|
+
}
|
|
246
|
+
return true;
|
|
247
|
+
});
|
|
248
|
+
}, [
|
|
249
|
+
items,
|
|
250
|
+
search,
|
|
251
|
+
selectedProvider
|
|
252
|
+
]);
|
|
253
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
254
|
+
className: "dsh-ig-gallery-page",
|
|
255
|
+
children: [
|
|
256
|
+
(0, react_jsx_runtime.jsxs)("header", {
|
|
257
|
+
className: "dsh-ig-gallery-page-header",
|
|
258
|
+
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
259
|
+
className: "dsh-ig-gallery-page-title-row",
|
|
260
|
+
children: (0, react_jsx_runtime.jsx)("span", {
|
|
261
|
+
className: "dsh-ig-gallery-page-count",
|
|
262
|
+
children: t("totalCount", { count: String(items.length) })
|
|
263
|
+
})
|
|
264
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
265
|
+
className: "dsh-ig-gallery-page-tools",
|
|
266
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
267
|
+
className: "dsh-ig-gallery-search-wrap",
|
|
268
|
+
children: [(0, react_jsx_runtime.jsxs)("svg", {
|
|
269
|
+
className: "dsh-ig-gallery-search-icon",
|
|
270
|
+
width: "14",
|
|
271
|
+
height: "14",
|
|
272
|
+
viewBox: "0 0 24 24",
|
|
273
|
+
fill: "none",
|
|
274
|
+
stroke: "currentColor",
|
|
275
|
+
strokeWidth: "2",
|
|
276
|
+
strokeLinecap: "round",
|
|
277
|
+
strokeLinejoin: "round",
|
|
278
|
+
children: [(0, react_jsx_runtime.jsx)("circle", {
|
|
279
|
+
cx: "11",
|
|
280
|
+
cy: "11",
|
|
281
|
+
r: "8"
|
|
282
|
+
}), (0, react_jsx_runtime.jsx)("line", {
|
|
283
|
+
x1: "21",
|
|
284
|
+
y1: "21",
|
|
285
|
+
x2: "16.65",
|
|
286
|
+
y2: "16.65"
|
|
287
|
+
})]
|
|
288
|
+
}), (0, react_jsx_runtime.jsx)("input", {
|
|
289
|
+
type: "text",
|
|
290
|
+
className: "dsh-ig-gallery-search-input",
|
|
291
|
+
placeholder: t("searchPlaceholder"),
|
|
292
|
+
value: search,
|
|
293
|
+
onChange: (e) => setSearch(e.target.value)
|
|
294
|
+
})]
|
|
295
|
+
}), (0, react_jsx_runtime.jsxs)("select", {
|
|
296
|
+
className: "dsh-ig-gallery-select",
|
|
297
|
+
value: selectedProvider,
|
|
298
|
+
onChange: (e) => setSelectedProvider(e.target.value),
|
|
299
|
+
children: [
|
|
300
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
301
|
+
value: "all",
|
|
302
|
+
children: t("filterAll")
|
|
303
|
+
}),
|
|
304
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
305
|
+
value: "google",
|
|
306
|
+
children: t("filterGoogle")
|
|
307
|
+
}),
|
|
308
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
309
|
+
value: "openai",
|
|
310
|
+
children: t("filterOpenAI")
|
|
311
|
+
}),
|
|
312
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
313
|
+
value: "seedream",
|
|
314
|
+
children: t("filterSeedream")
|
|
315
|
+
})
|
|
316
|
+
]
|
|
317
|
+
})]
|
|
318
|
+
})]
|
|
319
|
+
}),
|
|
320
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
321
|
+
className: "dsh-ig-gallery-page-body",
|
|
322
|
+
children: items.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
|
|
323
|
+
className: "dsh-ig-gallery-empty",
|
|
324
|
+
children: [
|
|
325
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
326
|
+
className: "dsh-ig-gallery-empty-icon",
|
|
327
|
+
children: "🖼️"
|
|
328
|
+
}),
|
|
329
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
330
|
+
className: "dsh-ig-gallery-empty-title",
|
|
331
|
+
children: t("emptyTitle")
|
|
332
|
+
}),
|
|
333
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
334
|
+
className: "dsh-ig-gallery-empty-desc",
|
|
335
|
+
children: t("emptyDesc")
|
|
336
|
+
})
|
|
337
|
+
]
|
|
338
|
+
}) : filteredItems.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
|
|
339
|
+
className: "dsh-ig-gallery-empty",
|
|
340
|
+
children: [
|
|
341
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
342
|
+
className: "dsh-ig-gallery-empty-icon",
|
|
343
|
+
children: "🔍"
|
|
344
|
+
}),
|
|
345
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
346
|
+
className: "dsh-ig-gallery-empty-title",
|
|
347
|
+
children: t("noMatchTitle")
|
|
348
|
+
}),
|
|
349
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
350
|
+
className: "dsh-ig-gallery-empty-desc",
|
|
351
|
+
children: t("noMatchDesc")
|
|
352
|
+
})
|
|
353
|
+
]
|
|
354
|
+
}) : (0, react_jsx_runtime.jsx)("div", {
|
|
355
|
+
className: "dsh-ig-gallery-grid",
|
|
356
|
+
children: filteredItems.map((item) => (0, react_jsx_runtime.jsx)(GalleryCard, {
|
|
357
|
+
item,
|
|
358
|
+
t,
|
|
359
|
+
onPreview: (url, blob) => {
|
|
360
|
+
setPreviewItem(item);
|
|
361
|
+
setPreviewUrl(url);
|
|
362
|
+
setPreviewBlob(blob);
|
|
363
|
+
},
|
|
364
|
+
onToast: showToast
|
|
365
|
+
}, item.id))
|
|
366
|
+
})
|
|
367
|
+
}),
|
|
368
|
+
toast && (0, react_jsx_runtime.jsx)("div", {
|
|
369
|
+
className: "dsh-ig-gallery-page-toast",
|
|
370
|
+
children: toast
|
|
371
|
+
}),
|
|
372
|
+
previewItem && previewUrl && (0, react_jsx_runtime.jsxs)("div", {
|
|
373
|
+
className: "dsh-ig-lightbox-backdrop",
|
|
374
|
+
onClick: () => {
|
|
375
|
+
setPreviewItem(null);
|
|
376
|
+
setPreviewUrl(null);
|
|
377
|
+
setPreviewBlob(null);
|
|
378
|
+
},
|
|
379
|
+
children: [
|
|
380
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
381
|
+
className: "dsh-ig-lightbox-topbar",
|
|
382
|
+
onClick: (e) => e.stopPropagation(),
|
|
383
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
384
|
+
className: "dsh-ig-lightbox-meta",
|
|
385
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
386
|
+
className: "dsh-ig-tag",
|
|
387
|
+
children: previewItem.provider
|
|
388
|
+
}), previewItem.model ? (0, react_jsx_runtime.jsx)("span", {
|
|
389
|
+
className: "dsh-ig-tag dsh-ig-tag-model",
|
|
390
|
+
children: previewItem.model
|
|
391
|
+
}) : null]
|
|
392
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
393
|
+
type: "button",
|
|
394
|
+
className: "dsh-ig-lightbox-close-btn",
|
|
395
|
+
title: t("close"),
|
|
396
|
+
onClick: () => {
|
|
397
|
+
setPreviewItem(null);
|
|
398
|
+
setPreviewUrl(null);
|
|
399
|
+
setPreviewBlob(null);
|
|
400
|
+
},
|
|
401
|
+
children: (0, react_jsx_runtime.jsxs)("svg", {
|
|
402
|
+
width: "18",
|
|
403
|
+
height: "18",
|
|
404
|
+
viewBox: "0 0 24 24",
|
|
405
|
+
fill: "none",
|
|
406
|
+
stroke: "currentColor",
|
|
407
|
+
strokeWidth: "2",
|
|
408
|
+
strokeLinecap: "round",
|
|
409
|
+
strokeLinejoin: "round",
|
|
410
|
+
children: [(0, react_jsx_runtime.jsx)("line", {
|
|
411
|
+
x1: "18",
|
|
412
|
+
y1: "6",
|
|
413
|
+
x2: "6",
|
|
414
|
+
y2: "18"
|
|
415
|
+
}), (0, react_jsx_runtime.jsx)("line", {
|
|
416
|
+
x1: "6",
|
|
417
|
+
y1: "6",
|
|
418
|
+
x2: "18",
|
|
419
|
+
y2: "18"
|
|
420
|
+
})]
|
|
421
|
+
})
|
|
422
|
+
})]
|
|
423
|
+
}),
|
|
424
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
425
|
+
className: "dsh-ig-lightbox-img-wrap",
|
|
426
|
+
onClick: (e) => e.stopPropagation(),
|
|
427
|
+
children: (0, react_jsx_runtime.jsx)("img", {
|
|
428
|
+
className: "dsh-ig-lightbox-img",
|
|
429
|
+
src: previewUrl,
|
|
430
|
+
alt: previewItem.prompt
|
|
431
|
+
})
|
|
432
|
+
}),
|
|
433
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
434
|
+
className: "dsh-ig-lightbox-bottombar",
|
|
435
|
+
onClick: (e) => e.stopPropagation(),
|
|
436
|
+
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
437
|
+
className: "dsh-ig-lightbox-prompt-text",
|
|
438
|
+
title: previewItem.prompt,
|
|
439
|
+
children: previewItem.prompt
|
|
440
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
441
|
+
className: "dsh-ig-lightbox-actions",
|
|
442
|
+
children: [
|
|
443
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
444
|
+
type: "button",
|
|
445
|
+
className: "dsh-ig-lightbox-btn",
|
|
446
|
+
title: t("copyPpt"),
|
|
447
|
+
onClick: async () => {
|
|
448
|
+
await navigator.clipboard.writeText(previewItem.prompt);
|
|
449
|
+
showToast(t("copiedPrompt"));
|
|
450
|
+
},
|
|
451
|
+
children: [(0, react_jsx_runtime.jsxs)("svg", {
|
|
452
|
+
width: "14",
|
|
453
|
+
height: "14",
|
|
454
|
+
viewBox: "0 0 24 24",
|
|
455
|
+
fill: "none",
|
|
456
|
+
stroke: "currentColor",
|
|
457
|
+
strokeWidth: "2",
|
|
458
|
+
strokeLinecap: "round",
|
|
459
|
+
strokeLinejoin: "round",
|
|
460
|
+
children: [(0, react_jsx_runtime.jsx)("path", { d: "M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" }), (0, react_jsx_runtime.jsx)("rect", {
|
|
461
|
+
x: "8",
|
|
462
|
+
y: "2",
|
|
463
|
+
width: "8",
|
|
464
|
+
height: "4",
|
|
465
|
+
rx: "1",
|
|
466
|
+
ry: "1"
|
|
467
|
+
})]
|
|
468
|
+
}), (0, react_jsx_runtime.jsx)("span", { children: t("copyPpt") })]
|
|
469
|
+
}),
|
|
470
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
471
|
+
type: "button",
|
|
472
|
+
className: "dsh-ig-lightbox-btn",
|
|
473
|
+
title: t("copyImg"),
|
|
474
|
+
onClick: async () => {
|
|
475
|
+
if (!previewBlob) return;
|
|
476
|
+
const ok = await copyImageBlob(previewBlob);
|
|
477
|
+
showToast(ok ? t("copiedImage") : t("copyFailed"));
|
|
478
|
+
},
|
|
479
|
+
children: [(0, react_jsx_runtime.jsxs)("svg", {
|
|
480
|
+
width: "14",
|
|
481
|
+
height: "14",
|
|
482
|
+
viewBox: "0 0 24 24",
|
|
483
|
+
fill: "none",
|
|
484
|
+
stroke: "currentColor",
|
|
485
|
+
strokeWidth: "2",
|
|
486
|
+
strokeLinecap: "round",
|
|
487
|
+
strokeLinejoin: "round",
|
|
488
|
+
children: [(0, react_jsx_runtime.jsx)("rect", {
|
|
489
|
+
x: "9",
|
|
490
|
+
y: "9",
|
|
491
|
+
width: "13",
|
|
492
|
+
height: "13",
|
|
493
|
+
rx: "2",
|
|
494
|
+
ry: "2"
|
|
495
|
+
}), (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" })]
|
|
496
|
+
}), (0, react_jsx_runtime.jsx)("span", { children: t("copyImg") })]
|
|
497
|
+
}),
|
|
498
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
499
|
+
type: "button",
|
|
500
|
+
className: "dsh-ig-lightbox-btn",
|
|
501
|
+
title: t("download"),
|
|
502
|
+
onClick: () => {
|
|
503
|
+
const a = document.createElement("a");
|
|
504
|
+
a.href = previewUrl;
|
|
505
|
+
a.download = `dsh-${previewItem.provider}-${previewItem.id}.png`;
|
|
506
|
+
document.body.appendChild(a);
|
|
507
|
+
a.click();
|
|
508
|
+
document.body.removeChild(a);
|
|
509
|
+
},
|
|
510
|
+
children: [(0, react_jsx_runtime.jsxs)("svg", {
|
|
511
|
+
width: "14",
|
|
512
|
+
height: "14",
|
|
513
|
+
viewBox: "0 0 24 24",
|
|
514
|
+
fill: "none",
|
|
515
|
+
stroke: "currentColor",
|
|
516
|
+
strokeWidth: "2",
|
|
517
|
+
strokeLinecap: "round",
|
|
518
|
+
strokeLinejoin: "round",
|
|
519
|
+
children: [
|
|
520
|
+
(0, react_jsx_runtime.jsx)("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
521
|
+
(0, react_jsx_runtime.jsx)("polyline", { points: "7 10 12 15 17 10" }),
|
|
522
|
+
(0, react_jsx_runtime.jsx)("line", {
|
|
523
|
+
x1: "12",
|
|
524
|
+
y1: "15",
|
|
525
|
+
x2: "12",
|
|
526
|
+
y2: "3"
|
|
527
|
+
})
|
|
528
|
+
]
|
|
529
|
+
}), (0, react_jsx_runtime.jsx)("span", { children: t("download") })]
|
|
530
|
+
})
|
|
531
|
+
]
|
|
532
|
+
})]
|
|
533
|
+
})
|
|
534
|
+
]
|
|
535
|
+
})
|
|
536
|
+
]
|
|
537
|
+
});
|
|
538
|
+
};
|
|
539
|
+
const GalleryCard = ({ item, t, onPreview, onToast }) => {
|
|
540
|
+
const [url, setUrl] = (0, react.useState)();
|
|
541
|
+
const [blob, setBlob] = (0, react.useState)();
|
|
542
|
+
const [loading, setLoading] = (0, react.useState)(true);
|
|
543
|
+
const [error, setError] = (0, react.useState)();
|
|
544
|
+
(0, react.useEffect)(() => {
|
|
545
|
+
const controller = new AbortController();
|
|
546
|
+
let objectUrl;
|
|
547
|
+
fetch(IMAGE_ROUTE, {
|
|
548
|
+
method: "POST",
|
|
549
|
+
signal: controller.signal,
|
|
550
|
+
headers: { "content-type": "application/json" },
|
|
551
|
+
body: JSON.stringify({ attachment: item.attachment })
|
|
552
|
+
}).then(async (response) => {
|
|
553
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
554
|
+
const resBlob = await response.blob();
|
|
555
|
+
if (controller.signal.aborted) return;
|
|
556
|
+
setBlob(resBlob);
|
|
557
|
+
objectUrl = URL.createObjectURL(resBlob);
|
|
558
|
+
setUrl(objectUrl);
|
|
559
|
+
setLoading(false);
|
|
560
|
+
}).catch((err) => {
|
|
561
|
+
if (!controller.signal.aborted) {
|
|
562
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
563
|
+
setLoading(false);
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
return () => {
|
|
567
|
+
controller.abort();
|
|
568
|
+
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
569
|
+
};
|
|
570
|
+
}, [item.attachment]);
|
|
571
|
+
const copyPrompt = async (e) => {
|
|
572
|
+
e.stopPropagation();
|
|
573
|
+
try {
|
|
574
|
+
await navigator.clipboard.writeText(item.prompt);
|
|
575
|
+
onToast(t("copiedPrompt"));
|
|
576
|
+
} catch {
|
|
577
|
+
onToast(t("copyFailed"));
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
const copyImage = async (e) => {
|
|
581
|
+
e.stopPropagation();
|
|
582
|
+
if (!blob) return;
|
|
583
|
+
onToast(await copyImageBlob(blob) ? t("copiedImage") : t("copyFailed"));
|
|
584
|
+
};
|
|
585
|
+
const downloadImage = (e) => {
|
|
586
|
+
e.stopPropagation();
|
|
587
|
+
if (!url) return;
|
|
588
|
+
const a = document.createElement("a");
|
|
589
|
+
a.href = url;
|
|
590
|
+
a.download = `dsh-${item.provider}-${item.id}.png`;
|
|
591
|
+
document.body.appendChild(a);
|
|
592
|
+
a.click();
|
|
593
|
+
document.body.removeChild(a);
|
|
594
|
+
};
|
|
595
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
596
|
+
className: "dsh-ig-gallery-card",
|
|
597
|
+
onClick: () => {
|
|
598
|
+
if (url && blob) onPreview(url, blob);
|
|
599
|
+
},
|
|
600
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
601
|
+
className: "dsh-ig-gallery-card-media",
|
|
602
|
+
children: [
|
|
603
|
+
loading && (0, react_jsx_runtime.jsx)("div", {
|
|
604
|
+
className: "dsh-ig-gallery-card-loading",
|
|
605
|
+
children: "..."
|
|
606
|
+
}),
|
|
607
|
+
error && (0, react_jsx_runtime.jsxs)("div", {
|
|
608
|
+
className: "dsh-ig-gallery-card-error",
|
|
609
|
+
children: ["⚠️ ", error]
|
|
610
|
+
}),
|
|
611
|
+
url && (0, react_jsx_runtime.jsx)("img", {
|
|
612
|
+
className: "dsh-ig-gallery-card-img",
|
|
613
|
+
src: url,
|
|
614
|
+
alt: item.prompt,
|
|
615
|
+
loading: "lazy"
|
|
616
|
+
}),
|
|
617
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
618
|
+
className: "dsh-ig-card-toolbar",
|
|
619
|
+
children: [
|
|
620
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
621
|
+
type: "button",
|
|
622
|
+
className: "dsh-ig-tool-btn",
|
|
623
|
+
title: t("copyImg"),
|
|
624
|
+
onClick: (e) => {
|
|
625
|
+
copyImage(e);
|
|
626
|
+
},
|
|
627
|
+
children: (0, react_jsx_runtime.jsxs)("svg", {
|
|
628
|
+
width: "14",
|
|
629
|
+
height: "14",
|
|
630
|
+
viewBox: "0 0 24 24",
|
|
631
|
+
fill: "none",
|
|
632
|
+
stroke: "currentColor",
|
|
633
|
+
strokeWidth: "2",
|
|
634
|
+
strokeLinecap: "round",
|
|
635
|
+
strokeLinejoin: "round",
|
|
636
|
+
children: [(0, react_jsx_runtime.jsx)("rect", {
|
|
637
|
+
x: "9",
|
|
638
|
+
y: "9",
|
|
639
|
+
width: "13",
|
|
640
|
+
height: "13",
|
|
641
|
+
rx: "2",
|
|
642
|
+
ry: "2"
|
|
643
|
+
}), (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" })]
|
|
644
|
+
})
|
|
645
|
+
}),
|
|
646
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
647
|
+
type: "button",
|
|
648
|
+
className: "dsh-ig-tool-btn",
|
|
649
|
+
title: t("download"),
|
|
650
|
+
onClick: downloadImage,
|
|
651
|
+
children: (0, react_jsx_runtime.jsxs)("svg", {
|
|
652
|
+
width: "14",
|
|
653
|
+
height: "14",
|
|
654
|
+
viewBox: "0 0 24 24",
|
|
655
|
+
fill: "none",
|
|
656
|
+
stroke: "currentColor",
|
|
657
|
+
strokeWidth: "2",
|
|
658
|
+
strokeLinecap: "round",
|
|
659
|
+
strokeLinejoin: "round",
|
|
660
|
+
children: [
|
|
661
|
+
(0, react_jsx_runtime.jsx)("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
662
|
+
(0, react_jsx_runtime.jsx)("polyline", { points: "7 10 12 15 17 10" }),
|
|
663
|
+
(0, react_jsx_runtime.jsx)("line", {
|
|
664
|
+
x1: "12",
|
|
665
|
+
y1: "15",
|
|
666
|
+
x2: "12",
|
|
667
|
+
y2: "3"
|
|
668
|
+
})
|
|
669
|
+
]
|
|
670
|
+
})
|
|
671
|
+
}),
|
|
672
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
673
|
+
type: "button",
|
|
674
|
+
className: "dsh-ig-tool-btn",
|
|
675
|
+
title: t("copyPpt"),
|
|
676
|
+
onClick: (e) => {
|
|
677
|
+
copyPrompt(e);
|
|
678
|
+
},
|
|
679
|
+
children: (0, react_jsx_runtime.jsxs)("svg", {
|
|
680
|
+
width: "14",
|
|
681
|
+
height: "14",
|
|
682
|
+
viewBox: "0 0 24 24",
|
|
683
|
+
fill: "none",
|
|
684
|
+
stroke: "currentColor",
|
|
685
|
+
strokeWidth: "2",
|
|
686
|
+
strokeLinecap: "round",
|
|
687
|
+
strokeLinejoin: "round",
|
|
688
|
+
children: [(0, react_jsx_runtime.jsx)("path", { d: "M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" }), (0, react_jsx_runtime.jsx)("rect", {
|
|
689
|
+
x: "8",
|
|
690
|
+
y: "2",
|
|
691
|
+
width: "8",
|
|
692
|
+
height: "4",
|
|
693
|
+
rx: "1",
|
|
694
|
+
ry: "1"
|
|
695
|
+
})]
|
|
696
|
+
})
|
|
697
|
+
})
|
|
698
|
+
]
|
|
699
|
+
})
|
|
700
|
+
]
|
|
701
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
702
|
+
className: "dsh-ig-gallery-card-meta",
|
|
703
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
704
|
+
className: "dsh-ig-gallery-card-header",
|
|
705
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
706
|
+
className: "dsh-ig-tag",
|
|
707
|
+
children: item.provider
|
|
708
|
+
}), item.model ? (0, react_jsx_runtime.jsx)("span", {
|
|
709
|
+
className: "dsh-ig-tag dsh-ig-tag-model",
|
|
710
|
+
children: item.model
|
|
711
|
+
}) : null]
|
|
712
|
+
}), (0, react_jsx_runtime.jsx)("p", {
|
|
713
|
+
className: "dsh-ig-gallery-card-prompt",
|
|
714
|
+
title: item.prompt,
|
|
715
|
+
children: item.prompt
|
|
716
|
+
})]
|
|
717
|
+
})]
|
|
718
|
+
});
|
|
719
|
+
};
|
|
720
|
+
async function copyImageBlob(blob) {
|
|
721
|
+
try {
|
|
722
|
+
if (blob.type === "image/png") {
|
|
723
|
+
await navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]);
|
|
724
|
+
return true;
|
|
725
|
+
}
|
|
726
|
+
const img = new Image();
|
|
727
|
+
const url = URL.createObjectURL(blob);
|
|
728
|
+
await new Promise((resolve, reject) => {
|
|
729
|
+
img.onload = resolve;
|
|
730
|
+
img.onerror = reject;
|
|
731
|
+
img.src = url;
|
|
732
|
+
});
|
|
733
|
+
const canvas = document.createElement("canvas");
|
|
734
|
+
canvas.width = img.naturalWidth;
|
|
735
|
+
canvas.height = img.naturalHeight;
|
|
736
|
+
const ctx = canvas.getContext("2d");
|
|
737
|
+
if (!ctx) throw new Error("Canvas unavailable");
|
|
738
|
+
ctx.drawImage(img, 0, 0);
|
|
739
|
+
URL.revokeObjectURL(url);
|
|
740
|
+
const pngBlob = await new Promise((res) => {
|
|
741
|
+
canvas.toBlob(res, "image/png");
|
|
742
|
+
});
|
|
743
|
+
if (!pngBlob) throw new Error("Blob conversion failed");
|
|
744
|
+
await navigator.clipboard.write([new ClipboardItem({ "image/png": pngBlob })]);
|
|
745
|
+
return true;
|
|
746
|
+
} catch (_err) {
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
//#endregion
|
|
30
751
|
//#region lib/types/client/index.js
|
|
31
752
|
/** Web settings and generated-image cards contributed by the Bundle. */
|
|
32
753
|
const KEY_REF = {
|
|
33
754
|
google: "GEMINI_API_KEY",
|
|
34
755
|
openai: "OPENAI_API_KEY",
|
|
35
|
-
seedream: "ARK_API_KEY"
|
|
756
|
+
seedream: "ARK_API_KEY",
|
|
757
|
+
dashscope: "DASHSCOPE_API_KEY"
|
|
36
758
|
};
|
|
37
759
|
const DICT = {
|
|
38
760
|
zh: {
|
|
@@ -42,6 +764,7 @@ window.__ModuleLoader__.load({
|
|
|
42
764
|
providerGoogle: "Google Gemini",
|
|
43
765
|
providerOpenAI: "OpenAI / 中转站",
|
|
44
766
|
providerSeedream: "字节 Seedream",
|
|
767
|
+
providerDashScope: "阿里 DashScope (通义万相 / Qwen)",
|
|
45
768
|
apiKeyLabel: "{provider} API Key",
|
|
46
769
|
apiKeyPlaceholder: "留空即可保留已配置的 Key",
|
|
47
770
|
apiKeyHint: "安全保存为 {key};页面不会读回明文。",
|
|
@@ -51,6 +774,7 @@ window.__ModuleLoader__.load({
|
|
|
51
774
|
endpointHintGoogle: "Google 官方地址或反代端点(全路径)。",
|
|
52
775
|
endpointHintOpenAI: "中转站请填其 OpenAI 兼容的 /v1 地址。",
|
|
53
776
|
endpointHintSeedream: "火山方舟兼容的 /api/v3 地址。",
|
|
777
|
+
endpointHintDashScope: "阿里云百炼 DashScope 官方接口地址。",
|
|
54
778
|
model: "模型",
|
|
55
779
|
saving: "保存中…",
|
|
56
780
|
save: "保存",
|
|
@@ -61,7 +785,12 @@ window.__ModuleLoader__.load({
|
|
|
61
785
|
generating: "正在生成图片…",
|
|
62
786
|
loading: "正在加载图片…",
|
|
63
787
|
loadFailed: "图片读取失败 ({status})",
|
|
64
|
-
generatedTitle: "
|
|
788
|
+
generatedTitle: "已生成图片",
|
|
789
|
+
copyImg: "复制图片",
|
|
790
|
+
download: "下载图片",
|
|
791
|
+
openNewTab: "新标签页打开",
|
|
792
|
+
copiedImage: "已复制图片",
|
|
793
|
+
copyFailed: "复制失败"
|
|
65
794
|
},
|
|
66
795
|
en: {
|
|
67
796
|
title: "Image Generation",
|
|
@@ -70,6 +799,7 @@ window.__ModuleLoader__.load({
|
|
|
70
799
|
providerGoogle: "Google Gemini",
|
|
71
800
|
providerOpenAI: "OpenAI / Relay",
|
|
72
801
|
providerSeedream: "ByteDance Seedream",
|
|
802
|
+
providerDashScope: "Aliyun DashScope (Wanx / Qwen)",
|
|
73
803
|
apiKeyLabel: "{provider} API Key",
|
|
74
804
|
apiKeyPlaceholder: "Leave empty to keep configured key",
|
|
75
805
|
apiKeyHint: "Securely saved as {key}; never read back in plaintext.",
|
|
@@ -79,6 +809,7 @@ window.__ModuleLoader__.load({
|
|
|
79
809
|
endpointHintGoogle: "Official Google endpoint or reverse proxy (full path).",
|
|
80
810
|
endpointHintOpenAI: "OpenAI-compatible /v1 base URL for relays.",
|
|
81
811
|
endpointHintSeedream: "Volcengine Ark compatible /api/v3 base URL.",
|
|
812
|
+
endpointHintDashScope: "Official Aliyun DashScope endpoint.",
|
|
82
813
|
model: "Model",
|
|
83
814
|
saving: "Saving…",
|
|
84
815
|
save: "Save",
|
|
@@ -89,7 +820,12 @@ window.__ModuleLoader__.load({
|
|
|
89
820
|
generating: "Generating image…",
|
|
90
821
|
loading: "Loading image…",
|
|
91
822
|
loadFailed: "Failed to load image ({status})",
|
|
92
|
-
generatedTitle: "Generated image"
|
|
823
|
+
generatedTitle: "Generated image",
|
|
824
|
+
copyImg: "Copy Image",
|
|
825
|
+
download: "Download Image",
|
|
826
|
+
openNewTab: "Open in new tab",
|
|
827
|
+
copiedImage: "Image copied",
|
|
828
|
+
copyFailed: "Copy failed"
|
|
93
829
|
}
|
|
94
830
|
};
|
|
95
831
|
const STYLE = `
|
|
@@ -115,6 +851,7 @@ window.__ModuleLoader__.load({
|
|
|
115
851
|
.dsh-ig-actions{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:16px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2,#eee)}
|
|
116
852
|
.dsh-ig-save{appearance:none;border:0;border-radius:8px;padding:6px 16px;background:var(--dsw-alias-label-primary,#111827);color:var(--dsw-alias-bg-layer-3,#fff);font:inherit;font-size:13px;font-weight:500;cursor:pointer;transition:opacity .15s}
|
|
117
853
|
.dsh-ig-save:disabled{opacity:.4;cursor:default}
|
|
854
|
+
|
|
118
855
|
.dsh-ig-result{display:grid;gap:10px;max-width:520px}
|
|
119
856
|
.dsh-ig-result-title{font-size:14px;font-weight:600}
|
|
120
857
|
.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}
|
|
@@ -124,11 +861,63 @@ window.__ModuleLoader__.load({
|
|
|
124
861
|
.dsh-ig-tool-btn:hover{background:rgba(255,255,255,0.25)}
|
|
125
862
|
.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}
|
|
126
863
|
.dsh-ig-image{display:block;max-width:100%;max-height:520px;border-radius:12px;background:#f2f3f5;cursor:pointer}
|
|
127
|
-
.dsh-ig-modal{position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.88);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;padding:20px;box-sizing:border-box;cursor:zoom-out;animation:dsh-ig-fade .15s ease-out}
|
|
128
864
|
@keyframes dsh-ig-fade{from{opacity:0}to{opacity:1}}
|
|
129
|
-
.dsh-ig-modal-img{max-width:92vw;max-height:92vh;object-fit:contain;border-radius:8px;box-shadow:0 24px 60px rgba(0,0,0,0.6);user-select:none;cursor:default}
|
|
130
865
|
.dsh-ig-error{color:var(--dsw-alias-label-error,#d33);font-size:13px}
|
|
131
|
-
.dsh-ig-loading{color:var(--dsw-alias-label-tertiary,#7b818b);font-size:13px}
|
|
866
|
+
.dsh-ig-loading{color:var(--dsw-alias-label-tertiary,#7b818b);font-size:13px}
|
|
867
|
+
|
|
868
|
+
/* Native Workspace Gallery View (Renders seamlessly inside DSH Session View) */
|
|
869
|
+
.dsh-ig-gallery-page{width:100%;height:100%;background:var(--dsw-alias-bg-layer-1,#ffffff);display:flex;flex-direction:column;overflow:hidden;flex:1}
|
|
870
|
+
.dsh-ig-gallery-page-header{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:12px 28px;border-bottom:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#ffffff);flex-shrink:0}
|
|
871
|
+
.dsh-ig-gallery-page-title-row{display:flex;align-items:center;gap:12px}
|
|
872
|
+
.dsh-ig-gallery-page-count{font-size:13px;font-weight:500;color:var(--dsw-alias-label-secondary,#4b5563);background:var(--dsw-alias-bg-layer-3,#f3f4f6);padding:3px 10px;border-radius:20px}
|
|
873
|
+
.dsh-ig-gallery-page-tools{display:flex;align-items:center;gap:12px}
|
|
874
|
+
.dsh-ig-gallery-search-wrap{position:relative;display:flex;align-items:center}
|
|
875
|
+
.dsh-ig-gallery-search-icon{position:absolute;left:10px;color:var(--dsw-alias-label-tertiary,#9ca3af);pointer-events:none}
|
|
876
|
+
.dsh-ig-gallery-search-input{padding:6px 12px 6px 32px;font-size:13px;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);border-radius:8px;background:var(--dsw-alias-bg-layer-2,#fff);color:inherit;outline:none;width:200px;transition:border-color .15s,width .2s}
|
|
877
|
+
.dsh-ig-gallery-search-input:focus{border-color:var(--dsw-alias-brand-primary,#4c78ff);width:240px}
|
|
878
|
+
.dsh-ig-gallery-select{padding:6px 12px;font-size:13px;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);border-radius:8px;background:var(--dsw-alias-bg-layer-2,#fff);color:inherit;outline:none;cursor:pointer}
|
|
879
|
+
.dsh-ig-gallery-page-body{flex:1;overflow-y:auto;padding:24px 28px}
|
|
880
|
+
.dsh-ig-gallery-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:20px}
|
|
881
|
+
.dsh-ig-gallery-card{background:var(--dsw-alias-bg-layer-2,#fff);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:12px;overflow:hidden;display:flex;flex-direction:column;cursor:pointer;transition:transform .18s ease,box-shadow .18s ease,border-color .18s ease}
|
|
882
|
+
.dsh-ig-gallery-card:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(0,0,0,0.06);border-color:var(--dsw-alias-border-l1,#cfd4dc)}
|
|
883
|
+
.dsh-ig-gallery-card-media{position:relative;width:100%;aspect-ratio:1/1;background:#f3f4f6;overflow:hidden;display:flex;align-items:center;justify-content:center}
|
|
884
|
+
.dsh-ig-gallery-card-img{width:100%;height:100%;object-fit:cover;transition:transform .2s}
|
|
885
|
+
.dsh-ig-gallery-card:hover .dsh-ig-gallery-card-img{transform:scale(1.03)}
|
|
886
|
+
.dsh-ig-gallery-card-loading{font-size:12px;color:#9ca3af}
|
|
887
|
+
.dsh-ig-gallery-card-error{font-size:12px;color:#ef4444;padding:8px;text-align:center}
|
|
888
|
+
|
|
889
|
+
/* Floating Action Toolbar (Matches Chat Image Toolbar) */
|
|
890
|
+
.dsh-ig-gallery-card:hover .dsh-ig-card-toolbar{opacity:1;pointer-events:auto}
|
|
891
|
+
.dsh-ig-card-toolbar{position:absolute;top:8px;left:8px;display:flex;align-items:center;gap:4px;padding:3px 5px;border-radius:8px;background:rgba(0,0,0,0.68);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);opacity:0;pointer-events:none;transition:opacity .18s ease;z-index:10;line-height:1}
|
|
892
|
+
|
|
893
|
+
.dsh-ig-gallery-card-meta{padding:12px 14px;display:flex;flex-direction:column;gap:6px;background:var(--dsw-alias-bg-layer-2,#fff);flex:1}
|
|
894
|
+
.dsh-ig-gallery-card-header{display:flex;align-items:center;justify-content:space-between;font-size:11px}
|
|
895
|
+
.dsh-ig-tag{display:inline-block;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-bg-layer-3,#edf0f3);color:var(--dsw-alias-label-secondary,inherit);font-weight:500;text-transform:uppercase;font-size:10px}
|
|
896
|
+
.dsh-ig-tag-model{background:rgba(76,120,255,0.1);color:#4c78ff}
|
|
897
|
+
.dsh-ig-gallery-card-prompt{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-primary,inherit);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}
|
|
898
|
+
.dsh-ig-gallery-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;min-height:360px;text-align:center;color:var(--dsw-alias-label-tertiary,#7b818b)}
|
|
899
|
+
.dsh-ig-gallery-empty-icon{font-size:48px;margin-bottom:12px}
|
|
900
|
+
.dsh-ig-gallery-empty-title{font-size:16px;font-weight:600;color:var(--dsw-alias-label-primary,inherit);margin-bottom:6px}
|
|
901
|
+
.dsh-ig-gallery-empty-desc{font-size:13px;max-width:360px;line-height:1.5}
|
|
902
|
+
|
|
903
|
+
/* Pure Centered Lightbox */
|
|
904
|
+
.dsh-ig-lightbox-backdrop{position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.88);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;box-sizing:border-box;cursor:zoom-out;animation:dsh-ig-fade .15s ease-out}
|
|
905
|
+
.dsh-ig-lightbox-topbar{position:absolute;top:20px;left:24px;right:24px;display:flex;align-items:center;justify-content:space-between;z-index:10;pointer-events:none}
|
|
906
|
+
.dsh-ig-lightbox-meta{display:flex;align-items:center;gap:8px;pointer-events:auto}
|
|
907
|
+
.dsh-ig-lightbox-close-btn{appearance:none;border:0;background:rgba(255,255,255,0.15);color:#fff;border-radius:50%;width:34px;height:34px;font-size:16px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background .15s;pointer-events:auto}
|
|
908
|
+
.dsh-ig-lightbox-close-btn:hover{background:rgba(255,255,255,0.3)}
|
|
909
|
+
.dsh-ig-lightbox-img-wrap{max-width:86vw;max-height:78vh;display:flex;align-items:center;justify-content:center;cursor:default}
|
|
910
|
+
.dsh-ig-lightbox-img{max-width:100%;max-height:78vh;object-fit:contain;border-radius:8px;box-shadow:0 24px 60px rgba(0,0,0,0.7);user-select:none}
|
|
911
|
+
.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}
|
|
912
|
+
.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}
|
|
913
|
+
.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}
|
|
914
|
+
.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}
|
|
915
|
+
.dsh-ig-lightbox-btn:hover{background:rgba(255,255,255,0.22)}
|
|
916
|
+
.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}
|
|
917
|
+
|
|
918
|
+
/* Hide floating chat composer when gallery page is active */
|
|
919
|
+
[data-conversation-scroll]:has(.dsh-ig-gallery-page) [data-composer-seat]{display:none!important}
|
|
920
|
+
`;
|
|
132
921
|
/** Required browser services. */
|
|
133
922
|
const inject = [
|
|
134
923
|
"slots",
|
|
@@ -137,7 +926,7 @@ window.__ModuleLoader__.load({
|
|
|
137
926
|
"settingsScope",
|
|
138
927
|
"locale"
|
|
139
928
|
];
|
|
140
|
-
/** Mount the settings card
|
|
929
|
+
/** Mount the settings card, generated-image card, and native conversation gallery view. */
|
|
141
930
|
function apply(ctx) {
|
|
142
931
|
const { api } = ctx.get("connection");
|
|
143
932
|
const scope = ctx.settingsScope.bind({ namespace: IMAGE_GENERATION_NAMESPACE });
|
|
@@ -155,24 +944,32 @@ window.__ModuleLoader__.load({
|
|
|
155
944
|
ctx.slots.inject("settings.plugin.item", () => register({
|
|
156
945
|
name: "settings.plugin.item",
|
|
157
946
|
key: IMAGE_GENERATION_NAMESPACE,
|
|
158
|
-
id: IMAGE_GENERATION_NAMESPACE,
|
|
159
|
-
order: 100,
|
|
160
947
|
inject: () => ({
|
|
161
948
|
scope,
|
|
162
949
|
credentials: api.credentials,
|
|
163
950
|
locale
|
|
164
951
|
})
|
|
165
952
|
}, ImageGenerationSettingsCard));
|
|
166
|
-
ctx.slots.inject("tool.call.toolview", () =>
|
|
953
|
+
ctx.slots.inject("tool.call.toolview", () => register({
|
|
167
954
|
name: "tool.call.toolview",
|
|
168
|
-
key: "generate_image"
|
|
955
|
+
key: "generate_image",
|
|
956
|
+
inject: () => ({ locale })
|
|
169
957
|
}, GeneratedImageCard));
|
|
958
|
+
ctx.slots.inject("conversation.view", () => register({
|
|
959
|
+
name: "conversation.view",
|
|
960
|
+
id: "gallery",
|
|
961
|
+
order: 20,
|
|
962
|
+
label: () => {
|
|
963
|
+
return (locale?.getSnapshot?.()?.active)?.startsWith("en") ? "Gallery" : "画廊";
|
|
964
|
+
},
|
|
965
|
+
inject: () => ({ locale })
|
|
966
|
+
}, GalleryViewTab));
|
|
170
967
|
}
|
|
171
968
|
/** Edit provider settings and its write-only API credential. */
|
|
172
969
|
function ImageGenerationSettingsCard(props) {
|
|
173
970
|
const [open, setOpen] = (0, react.useState)(false);
|
|
174
971
|
const [snapshot, setSnapshot] = (0, react.useState)(() => props.scope.getSnapshot());
|
|
175
|
-
const [lang, setLang] = (0, react.useState)(() => props.locale?.getSnapshot?.()?.active
|
|
972
|
+
const [lang, setLang] = (0, react.useState)(() => props.locale?.getSnapshot?.()?.active?.startsWith("en") ? "en" : "zh");
|
|
176
973
|
const [provider, setProvider] = (0, react.useState)("google");
|
|
177
974
|
const [model, setModel] = (0, react.useState)("");
|
|
178
975
|
const [baseURL, setBaseURL] = (0, react.useState)("");
|
|
@@ -185,7 +982,7 @@ window.__ModuleLoader__.load({
|
|
|
185
982
|
}), [props.scope]);
|
|
186
983
|
(0, react.useEffect)(() => {
|
|
187
984
|
return props.locale?.subscribe?.(() => {
|
|
188
|
-
setLang(props.locale?.getSnapshot?.()?.active
|
|
985
|
+
setLang(props.locale?.getSnapshot?.()?.active?.startsWith("en") ? "en" : "zh");
|
|
189
986
|
});
|
|
190
987
|
}, [props.locale]);
|
|
191
988
|
const t = (keyName, params) => {
|
|
@@ -196,7 +993,8 @@ window.__ModuleLoader__.load({
|
|
|
196
993
|
const providerLabels = {
|
|
197
994
|
google: t("providerGoogle"),
|
|
198
995
|
openai: t("providerOpenAI"),
|
|
199
|
-
seedream: t("providerSeedream")
|
|
996
|
+
seedream: t("providerSeedream"),
|
|
997
|
+
dashscope: t("providerDashScope")
|
|
200
998
|
};
|
|
201
999
|
(0, react.useEffect)(() => {
|
|
202
1000
|
const value = snapshot.value;
|
|
@@ -222,8 +1020,8 @@ window.__ModuleLoader__.load({
|
|
|
222
1020
|
setMessage("");
|
|
223
1021
|
try {
|
|
224
1022
|
await props.scope.set("provider", provider);
|
|
225
|
-
await props.scope.set(provider === "google" ? "googleModel" : provider === "openai" ? "openaiModel" : "seedreamModel", model);
|
|
226
|
-
await props.scope.set(provider === "google" ? "googleEndpoint" : provider === "openai" ? "openaiBaseURL" : "seedreamBaseURL", baseURL);
|
|
1023
|
+
await props.scope.set(provider === "google" ? "googleModel" : provider === "openai" ? "openaiModel" : provider === "seedream" ? "seedreamModel" : "dashscopeModel", model);
|
|
1024
|
+
await props.scope.set(provider === "google" ? "googleEndpoint" : provider === "openai" ? "openaiBaseURL" : provider === "seedream" ? "seedreamBaseURL" : "dashscopeEndpoint", baseURL);
|
|
227
1025
|
if (key.trim().length > 0) {
|
|
228
1026
|
const response = await props.credentials.set({
|
|
229
1027
|
ref: KEY_REF[provider],
|
|
@@ -309,6 +1107,10 @@ window.__ModuleLoader__.load({
|
|
|
309
1107
|
(0, react_jsx_runtime.jsx)("option", {
|
|
310
1108
|
value: "seedream",
|
|
311
1109
|
children: t("providerSeedream")
|
|
1110
|
+
}),
|
|
1111
|
+
(0, react_jsx_runtime.jsx)("option", {
|
|
1112
|
+
value: "dashscope",
|
|
1113
|
+
children: t("providerDashScope")
|
|
312
1114
|
})
|
|
313
1115
|
]
|
|
314
1116
|
}),
|
|
@@ -370,7 +1172,7 @@ window.__ModuleLoader__.load({
|
|
|
370
1172
|
}),
|
|
371
1173
|
(0, react_jsx_runtime.jsx)("span", {
|
|
372
1174
|
className: "dsh-ig-hint",
|
|
373
|
-
children: provider === "google" ? t("endpointHintGoogle") : provider === "openai" ? t("endpointHintOpenAI") : t("endpointHintSeedream")
|
|
1175
|
+
children: provider === "google" ? t("endpointHintGoogle") : provider === "openai" ? t("endpointHintOpenAI") : provider === "seedream" ? t("endpointHintSeedream") : t("endpointHintDashScope")
|
|
374
1176
|
})
|
|
375
1177
|
]
|
|
376
1178
|
}),
|
|
@@ -413,6 +1215,34 @@ window.__ModuleLoader__.load({
|
|
|
413
1215
|
const [error, setError] = (0, react.useState)();
|
|
414
1216
|
const [previewOpen, setPreviewOpen] = (0, react.useState)(false);
|
|
415
1217
|
const [toast, setToast] = (0, react.useState)();
|
|
1218
|
+
const [lang, setLang] = (0, react.useState)(() => props.locale?.getSnapshot?.()?.active?.startsWith("en") ? "en" : "zh");
|
|
1219
|
+
(0, react.useEffect)(() => {
|
|
1220
|
+
return props.locale?.subscribe?.(() => {
|
|
1221
|
+
setLang(props.locale?.getSnapshot?.()?.active?.startsWith("en") ? "en" : "zh");
|
|
1222
|
+
});
|
|
1223
|
+
}, [props.locale]);
|
|
1224
|
+
const t = (keyName, params) => {
|
|
1225
|
+
let text = (lang === "en" ? DICT.en : DICT.zh)[keyName] || DICT.zh[keyName] || keyName;
|
|
1226
|
+
if (params) for (const [k, v] of Object.entries(params)) text = text.replace(`{${k}}`, v);
|
|
1227
|
+
return text;
|
|
1228
|
+
};
|
|
1229
|
+
(0, react.useEffect)(() => {
|
|
1230
|
+
if (attachment === void 0) return;
|
|
1231
|
+
const blockAny = props.block;
|
|
1232
|
+
const meta = blockAny.meta ?? blockAny.resultView?.meta;
|
|
1233
|
+
const prompt = typeof meta?.prompt === "string" ? meta.prompt : blockAny.call?.args?.prompt ?? "Generated Image";
|
|
1234
|
+
const provider = typeof meta?.provider === "string" ? meta.provider : "google";
|
|
1235
|
+
const model = typeof meta?.model === "string" ? meta.model : "";
|
|
1236
|
+
const output = typeof meta?.output === "string" ? meta.output : "";
|
|
1237
|
+
saveGalleryItem({
|
|
1238
|
+
id: attachment.attachmentId,
|
|
1239
|
+
attachment,
|
|
1240
|
+
prompt,
|
|
1241
|
+
provider,
|
|
1242
|
+
model,
|
|
1243
|
+
output
|
|
1244
|
+
});
|
|
1245
|
+
}, [attachment?.attachmentId]);
|
|
416
1246
|
(0, react.useEffect)(() => {
|
|
417
1247
|
if (!previewOpen) return;
|
|
418
1248
|
const onKeyDown = (e) => {
|
|
@@ -433,7 +1263,7 @@ window.__ModuleLoader__.load({
|
|
|
433
1263
|
headers: { "content-type": "application/json" },
|
|
434
1264
|
body: JSON.stringify({ attachment })
|
|
435
1265
|
}).then(async (response) => {
|
|
436
|
-
if (!response.ok) throw new Error(
|
|
1266
|
+
if (!response.ok) throw new Error(t("loadFailed", { status: String(response.status) }));
|
|
437
1267
|
const resBlob = await response.blob();
|
|
438
1268
|
if (controller.signal.aborted) return;
|
|
439
1269
|
setBlob(resBlob);
|
|
@@ -446,12 +1276,12 @@ window.__ModuleLoader__.load({
|
|
|
446
1276
|
controller.abort();
|
|
447
1277
|
if (objectUrl !== void 0) URL.revokeObjectURL(objectUrl);
|
|
448
1278
|
};
|
|
449
|
-
}, [attachment?.attachmentId]);
|
|
1279
|
+
}, [attachment?.attachmentId, lang]);
|
|
450
1280
|
const copy = async (e) => {
|
|
451
1281
|
e.stopPropagation();
|
|
452
1282
|
if (!blob) return;
|
|
453
1283
|
const ok = await copyImageBlob(blob);
|
|
454
|
-
setToast(ok ? "
|
|
1284
|
+
setToast(ok ? t("copiedImage") : t("copyFailed"));
|
|
455
1285
|
setTimeout(() => {
|
|
456
1286
|
setToast(void 0);
|
|
457
1287
|
}, 2e3);
|
|
@@ -473,15 +1303,15 @@ window.__ModuleLoader__.load({
|
|
|
473
1303
|
};
|
|
474
1304
|
if (attachment === void 0) return (0, react_jsx_runtime.jsx)("div", {
|
|
475
1305
|
className: "dsh-ig-loading",
|
|
476
|
-
children: "
|
|
1306
|
+
children: t("generating")
|
|
477
1307
|
});
|
|
478
1308
|
return (0, react_jsx_runtime.jsxs)("section", {
|
|
479
1309
|
className: "dsh-ig-result",
|
|
480
|
-
"aria-label": "
|
|
1310
|
+
"aria-label": t("generatedTitle"),
|
|
481
1311
|
children: [
|
|
482
1312
|
(0, react_jsx_runtime.jsx)("div", {
|
|
483
1313
|
className: "dsh-ig-result-title",
|
|
484
|
-
children: "
|
|
1314
|
+
children: t("generatedTitle")
|
|
485
1315
|
}),
|
|
486
1316
|
error !== void 0 ? (0, react_jsx_runtime.jsx)("div", {
|
|
487
1317
|
className: "dsh-ig-error",
|
|
@@ -489,7 +1319,7 @@ window.__ModuleLoader__.load({
|
|
|
489
1319
|
}) : null,
|
|
490
1320
|
url === void 0 && error === void 0 ? (0, react_jsx_runtime.jsx)("div", {
|
|
491
1321
|
className: "dsh-ig-loading",
|
|
492
|
-
children: "
|
|
1322
|
+
children: t("loading")
|
|
493
1323
|
}) : null,
|
|
494
1324
|
url !== void 0 ? (0, react_jsx_runtime.jsxs)("div", {
|
|
495
1325
|
className: "dsh-ig-container",
|
|
@@ -506,7 +1336,7 @@ window.__ModuleLoader__.load({
|
|
|
506
1336
|
(0, react_jsx_runtime.jsx)("button", {
|
|
507
1337
|
type: "button",
|
|
508
1338
|
className: "dsh-ig-tool-btn",
|
|
509
|
-
title: "
|
|
1339
|
+
title: t("copyImg"),
|
|
510
1340
|
onClick: (e) => {
|
|
511
1341
|
copy(e);
|
|
512
1342
|
},
|
|
@@ -532,7 +1362,7 @@ window.__ModuleLoader__.load({
|
|
|
532
1362
|
(0, react_jsx_runtime.jsx)("button", {
|
|
533
1363
|
type: "button",
|
|
534
1364
|
className: "dsh-ig-tool-btn",
|
|
535
|
-
title: "
|
|
1365
|
+
title: t("download"),
|
|
536
1366
|
onClick: download,
|
|
537
1367
|
children: (0, react_jsx_runtime.jsxs)("svg", {
|
|
538
1368
|
width: "15",
|
|
@@ -558,7 +1388,7 @@ window.__ModuleLoader__.load({
|
|
|
558
1388
|
(0, react_jsx_runtime.jsx)("button", {
|
|
559
1389
|
type: "button",
|
|
560
1390
|
className: "dsh-ig-tool-btn",
|
|
561
|
-
title: "
|
|
1391
|
+
title: t("openNewTab"),
|
|
562
1392
|
onClick: openNewTab,
|
|
563
1393
|
children: (0, react_jsx_runtime.jsxs)("svg", {
|
|
564
1394
|
width: "15",
|
|
@@ -589,58 +1419,31 @@ window.__ModuleLoader__.load({
|
|
|
589
1419
|
})]
|
|
590
1420
|
}) : null,
|
|
591
1421
|
previewOpen && url !== void 0 ? (0, react_jsx_runtime.jsx)("div", {
|
|
592
|
-
className: "dsh-ig-
|
|
1422
|
+
className: "dsh-ig-lightbox-backdrop",
|
|
593
1423
|
onClick: () => {
|
|
594
1424
|
setPreviewOpen(false);
|
|
595
1425
|
},
|
|
596
|
-
children: (0, react_jsx_runtime.jsx)("
|
|
597
|
-
className: "dsh-ig-
|
|
598
|
-
src: url,
|
|
599
|
-
alt: attachment.name ?? "Generated image preview",
|
|
1426
|
+
children: (0, react_jsx_runtime.jsx)("div", {
|
|
1427
|
+
className: "dsh-ig-lightbox-img-wrap",
|
|
600
1428
|
onClick: (e) => {
|
|
601
1429
|
e.stopPropagation();
|
|
602
|
-
}
|
|
1430
|
+
},
|
|
1431
|
+
children: (0, react_jsx_runtime.jsx)("img", {
|
|
1432
|
+
className: "dsh-ig-lightbox-img",
|
|
1433
|
+
src: url,
|
|
1434
|
+
alt: attachment.name ?? "Generated image preview"
|
|
1435
|
+
})
|
|
603
1436
|
})
|
|
604
1437
|
}) : null
|
|
605
1438
|
]
|
|
606
1439
|
});
|
|
607
1440
|
}
|
|
608
|
-
async function copyImageBlob(blob) {
|
|
609
|
-
try {
|
|
610
|
-
if (blob.type === "image/png") {
|
|
611
|
-
await navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]);
|
|
612
|
-
return true;
|
|
613
|
-
}
|
|
614
|
-
const img = new Image();
|
|
615
|
-
const url = URL.createObjectURL(blob);
|
|
616
|
-
await new Promise((resolve, reject) => {
|
|
617
|
-
img.onload = resolve;
|
|
618
|
-
img.onerror = reject;
|
|
619
|
-
img.src = url;
|
|
620
|
-
});
|
|
621
|
-
const canvas = document.createElement("canvas");
|
|
622
|
-
canvas.width = img.naturalWidth;
|
|
623
|
-
canvas.height = img.naturalHeight;
|
|
624
|
-
const ctx = canvas.getContext("2d");
|
|
625
|
-
if (!ctx) throw new Error("Canvas unavailable");
|
|
626
|
-
ctx.drawImage(img, 0, 0);
|
|
627
|
-
URL.revokeObjectURL(url);
|
|
628
|
-
const pngBlob = await new Promise((res) => {
|
|
629
|
-
canvas.toBlob(res, "image/png");
|
|
630
|
-
});
|
|
631
|
-
if (!pngBlob) throw new Error("Blob conversion failed");
|
|
632
|
-
await navigator.clipboard.write([new ClipboardItem({ "image/png": pngBlob })]);
|
|
633
|
-
return true;
|
|
634
|
-
} catch (_err) {
|
|
635
|
-
return false;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
1441
|
function modelOf(provider, value) {
|
|
639
|
-
const stored = provider === "google" ? value?.googleModel : provider === "openai" ? value?.openaiModel : value?.seedreamModel;
|
|
1442
|
+
const stored = provider === "google" ? value?.googleModel : provider === "openai" ? value?.openaiModel : provider === "seedream" ? value?.seedreamModel : value?.dashscopeModel;
|
|
640
1443
|
return typeof stored === "string" && stored.length > 0 ? stored : DEFAULT_MODELS[provider];
|
|
641
1444
|
}
|
|
642
1445
|
function baseURLOf(provider, value) {
|
|
643
|
-
const stored = provider === "google" ? value?.googleEndpoint : provider === "openai" ? value?.openaiBaseURL : value?.seedreamBaseURL;
|
|
1446
|
+
const stored = provider === "google" ? value?.googleEndpoint : provider === "openai" ? value?.openaiBaseURL : provider === "seedream" ? value?.seedreamBaseURL : value?.dashscopeEndpoint;
|
|
644
1447
|
return typeof stored === "string" && stored.length > 0 ? stored : DEFAULT_BASE_URLS[provider];
|
|
645
1448
|
}
|
|
646
1449
|
function imageRef(block) {
|