dsh-image-gen 0.1.1 → 0.1.3
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.md +160 -158
- package/README.zh-CN.md +160 -158
- package/lib/client.js +335 -43
- package/lib/client.js.map +1 -1
- package/lib/types/client/index.d.ts +7 -0
- package/package.json +124 -122
- package/README.en.md +0 -158
package/lib/client.js
CHANGED
|
@@ -34,24 +34,114 @@ window.__ModuleLoader__.load({
|
|
|
34
34
|
openai: "OPENAI_API_KEY",
|
|
35
35
|
seedream: "ARK_API_KEY"
|
|
36
36
|
};
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
const DICT = {
|
|
38
|
+
zh: {
|
|
39
|
+
title: "图像生成",
|
|
40
|
+
description: "选择厂商并配置生图模型。",
|
|
41
|
+
provider: "Provider",
|
|
42
|
+
providerGoogle: "Google Gemini",
|
|
43
|
+
providerOpenAI: "OpenAI / 中转站",
|
|
44
|
+
providerSeedream: "字节 Seedream",
|
|
45
|
+
apiKeyLabel: "{provider} API Key",
|
|
46
|
+
apiKeyPlaceholder: "留空即可保留已配置的 Key",
|
|
47
|
+
apiKeyHint: "安全保存为 {key};页面不会读回明文。",
|
|
48
|
+
endpoint: "接口地址",
|
|
49
|
+
reset: "重置",
|
|
50
|
+
resetTitle: "重置为默认官方地址",
|
|
51
|
+
endpointHintGoogle: "Google 官方地址或反代端点(全路径)。",
|
|
52
|
+
endpointHintOpenAI: "中转站请填其 OpenAI 兼容的 /v1 地址。",
|
|
53
|
+
endpointHintSeedream: "火山方舟兼容的 /api/v3 地址。",
|
|
54
|
+
model: "模型",
|
|
55
|
+
saving: "保存中…",
|
|
56
|
+
save: "保存",
|
|
57
|
+
saved: "已保存",
|
|
58
|
+
checkingKey: "正在检查 API Key…",
|
|
59
|
+
keyConfigured: "已配置 API Key",
|
|
60
|
+
keyNotConfigured: "尚未配置 API Key",
|
|
61
|
+
generating: "正在生成图片…",
|
|
62
|
+
loading: "正在加载图片…",
|
|
63
|
+
loadFailed: "图片读取失败 ({status})",
|
|
64
|
+
generatedTitle: "Generated image"
|
|
65
|
+
},
|
|
66
|
+
en: {
|
|
67
|
+
title: "Image Generation",
|
|
68
|
+
description: "Select provider and configure image generation models.",
|
|
69
|
+
provider: "Provider",
|
|
70
|
+
providerGoogle: "Google Gemini",
|
|
71
|
+
providerOpenAI: "OpenAI / Relay",
|
|
72
|
+
providerSeedream: "ByteDance Seedream",
|
|
73
|
+
apiKeyLabel: "{provider} API Key",
|
|
74
|
+
apiKeyPlaceholder: "Leave empty to keep configured key",
|
|
75
|
+
apiKeyHint: "Securely saved as {key}; never read back in plaintext.",
|
|
76
|
+
endpoint: "Endpoint / Base URL",
|
|
77
|
+
reset: "Reset",
|
|
78
|
+
resetTitle: "Reset to official default URL",
|
|
79
|
+
endpointHintGoogle: "Official Google endpoint or reverse proxy (full path).",
|
|
80
|
+
endpointHintOpenAI: "OpenAI-compatible /v1 base URL for relays.",
|
|
81
|
+
endpointHintSeedream: "Volcengine Ark compatible /api/v3 base URL.",
|
|
82
|
+
model: "Model",
|
|
83
|
+
saving: "Saving…",
|
|
84
|
+
save: "Save",
|
|
85
|
+
saved: "Saved",
|
|
86
|
+
checkingKey: "Checking API Key…",
|
|
87
|
+
keyConfigured: "API Key configured",
|
|
88
|
+
keyNotConfigured: "API Key not configured",
|
|
89
|
+
generating: "Generating image…",
|
|
90
|
+
loading: "Loading image…",
|
|
91
|
+
loadFailed: "Failed to load image ({status})",
|
|
92
|
+
generatedTitle: "Generated image"
|
|
93
|
+
}
|
|
41
94
|
};
|
|
42
95
|
const STYLE = `
|
|
43
|
-
.dsh-ig-card{list-style:none;border:1px solid var(--border-
|
|
96
|
+
.dsh-ig-card{list-style:none;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:12px;background:var(--dsw-alias-bg-layer-3,#fff);transition:border-color .16s,background .16s;overflow:hidden}
|
|
97
|
+
.dsh-ig-card:hover{border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
|
|
98
|
+
.dsh-ig-card-open{background:var(--dsw-alias-bg-layer-2,#fff);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
|
|
99
|
+
.dsh-ig-head{width:100%;appearance:none;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 16px;border-radius:12px}
|
|
100
|
+
.dsh-ig-head:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4c78ff);outline-offset:-2px}
|
|
101
|
+
.dsh-ig-head-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}
|
|
102
|
+
.dsh-ig-title{display:block;font-size:15px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary,inherit)}
|
|
103
|
+
.dsh-ig-desc{display:block;font-size:13px;line-height:1.5;color:var(--dsw-alias-label-tertiary,#7b818b)}
|
|
104
|
+
.dsh-ig-chevron{flex:none;color:var(--dsw-alias-label-tertiary,#7b818b);transition:transform .16s;display:inline-flex;align-items:center}
|
|
105
|
+
.dsh-ig-chevron-open{transform:rotate(180deg)}
|
|
106
|
+
.dsh-ig-body{border-top:1px solid var(--dsw-alias-border-l2,#eee);padding:0 16px 16px}
|
|
107
|
+
.dsh-ig-field{display:grid;gap:6px;margin-top:14px}
|
|
108
|
+
.dsh-ig-label{font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary,inherit)}
|
|
109
|
+
.dsh-ig-input{box-sizing:border-box;width:100%;padding:8px 12px;font-size:13px;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);border-radius:8px;background:var(--dsw-alias-bg-layer-3,transparent);color:inherit;outline:none;transition:border-color .15s}
|
|
110
|
+
.dsh-ig-input:focus{border-color:var(--dsw-alias-brand-primary,#4c78ff)}
|
|
111
|
+
.dsh-ig-input-group{display:flex;gap:8px;align-items:center}
|
|
112
|
+
.dsh-ig-btn-reset{appearance:none;border:1px solid var(--dsw-alias-border-l2,#d7dbe0);border-radius:8px;padding:7px 12px;background:var(--dsw-alias-bg-layer-3,#f9fafb);color:var(--dsw-alias-label-secondary,inherit);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap;transition:background .15s,border-color .15s}
|
|
113
|
+
.dsh-ig-btn-reset:hover{background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
|
|
114
|
+
.dsh-ig-hint,.dsh-ig-status{margin:0;color:var(--dsw-alias-label-tertiary,#7b818b);font-size:12px;line-height:1.4}
|
|
115
|
+
.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
|
+
.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
|
+
.dsh-ig-save:disabled{opacity:.4;cursor:default}
|
|
118
|
+
.dsh-ig-result{display:grid;gap:10px;max-width:520px}
|
|
119
|
+
.dsh-ig-result-title{font-size:14px;font-weight:600}
|
|
120
|
+
.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}
|
|
121
|
+
.dsh-ig-container:hover .dsh-ig-toolbar{opacity:1;pointer-events:auto}
|
|
122
|
+
.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}
|
|
123
|
+
.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}
|
|
124
|
+
.dsh-ig-tool-btn:hover{background:rgba(255,255,255,0.25)}
|
|
125
|
+
.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
|
+
.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
|
+
@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
|
+
.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}`;
|
|
44
132
|
/** Required browser services. */
|
|
45
133
|
const inject = [
|
|
46
134
|
"slots",
|
|
47
135
|
"connection",
|
|
48
136
|
"remote",
|
|
49
|
-
"settingsScope"
|
|
137
|
+
"settingsScope",
|
|
138
|
+
"locale"
|
|
50
139
|
];
|
|
51
140
|
/** Mount the settings card and generated-image card. */
|
|
52
141
|
function apply(ctx) {
|
|
53
142
|
const { api } = ctx.get("connection");
|
|
54
143
|
const scope = ctx.settingsScope.bind({ namespace: IMAGE_GENERATION_NAMESPACE });
|
|
144
|
+
const locale = ctx.get("locale");
|
|
55
145
|
ctx.effect(() => {
|
|
56
146
|
const style = document.createElement("style");
|
|
57
147
|
style.dataset.plugin = "dsh-image-gen";
|
|
@@ -66,9 +156,11 @@ window.__ModuleLoader__.load({
|
|
|
66
156
|
name: "settings.plugin.item",
|
|
67
157
|
key: IMAGE_GENERATION_NAMESPACE,
|
|
68
158
|
id: IMAGE_GENERATION_NAMESPACE,
|
|
159
|
+
order: 100,
|
|
69
160
|
inject: () => ({
|
|
70
161
|
scope,
|
|
71
|
-
credentials: api.credentials
|
|
162
|
+
credentials: api.credentials,
|
|
163
|
+
locale
|
|
72
164
|
})
|
|
73
165
|
}, ImageGenerationSettingsCard));
|
|
74
166
|
ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
|
|
@@ -80,6 +172,7 @@ window.__ModuleLoader__.load({
|
|
|
80
172
|
function ImageGenerationSettingsCard(props) {
|
|
81
173
|
const [open, setOpen] = (0, react.useState)(false);
|
|
82
174
|
const [snapshot, setSnapshot] = (0, react.useState)(() => props.scope.getSnapshot());
|
|
175
|
+
const [lang, setLang] = (0, react.useState)(() => props.locale?.getSnapshot?.()?.active ?? "zh");
|
|
83
176
|
const [provider, setProvider] = (0, react.useState)("google");
|
|
84
177
|
const [model, setModel] = (0, react.useState)("");
|
|
85
178
|
const [baseURL, setBaseURL] = (0, react.useState)("");
|
|
@@ -90,6 +183,21 @@ window.__ModuleLoader__.load({
|
|
|
90
183
|
(0, react.useEffect)(() => props.scope.subscribe(() => {
|
|
91
184
|
setSnapshot(props.scope.getSnapshot());
|
|
92
185
|
}), [props.scope]);
|
|
186
|
+
(0, react.useEffect)(() => {
|
|
187
|
+
return props.locale?.subscribe?.(() => {
|
|
188
|
+
setLang(props.locale?.getSnapshot?.()?.active ?? "zh");
|
|
189
|
+
});
|
|
190
|
+
}, [props.locale]);
|
|
191
|
+
const t = (keyName, params) => {
|
|
192
|
+
let text = (lang === "en" ? DICT.en : DICT.zh)[keyName] || DICT.zh[keyName] || keyName;
|
|
193
|
+
if (params) for (const [k, v] of Object.entries(params)) text = text.replace(`{${k}}`, v);
|
|
194
|
+
return text;
|
|
195
|
+
};
|
|
196
|
+
const providerLabels = {
|
|
197
|
+
google: t("providerGoogle"),
|
|
198
|
+
openai: t("providerOpenAI"),
|
|
199
|
+
seedream: t("providerSeedream")
|
|
200
|
+
};
|
|
93
201
|
(0, react.useEffect)(() => {
|
|
94
202
|
const value = snapshot.value;
|
|
95
203
|
const next = value?.provider ?? "google";
|
|
@@ -125,16 +233,16 @@ window.__ModuleLoader__.load({
|
|
|
125
233
|
setKey("");
|
|
126
234
|
setConfigured(true);
|
|
127
235
|
}
|
|
128
|
-
setMessage("
|
|
236
|
+
setMessage(t("saved"));
|
|
129
237
|
} catch (cause) {
|
|
130
238
|
setMessage(cause instanceof Error ? cause.message : String(cause));
|
|
131
239
|
} finally {
|
|
132
240
|
setSaving(false);
|
|
133
241
|
}
|
|
134
242
|
};
|
|
135
|
-
const keyStatus = configured === void 0 ? "
|
|
243
|
+
const keyStatus = configured === void 0 ? t("checkingKey") : configured ? t("keyConfigured") : t("keyNotConfigured");
|
|
136
244
|
return (0, react_jsx_runtime.jsxs)("li", {
|
|
137
|
-
className: "dsh-ig-card"
|
|
245
|
+
className: `dsh-ig-card ${open ? "dsh-ig-card-open" : ""}`,
|
|
138
246
|
children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
139
247
|
type: "button",
|
|
140
248
|
className: "dsh-ig-head",
|
|
@@ -142,15 +250,29 @@ window.__ModuleLoader__.load({
|
|
|
142
250
|
onClick: () => {
|
|
143
251
|
setOpen((value) => !value);
|
|
144
252
|
},
|
|
145
|
-
children: [(0, react_jsx_runtime.jsxs)("span", {
|
|
146
|
-
className: "dsh-ig-
|
|
147
|
-
children: "
|
|
253
|
+
children: [(0, react_jsx_runtime.jsxs)("span", {
|
|
254
|
+
className: "dsh-ig-head-text",
|
|
255
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
256
|
+
className: "dsh-ig-title",
|
|
257
|
+
children: t("title")
|
|
258
|
+
}), (0, react_jsx_runtime.jsx)("span", {
|
|
259
|
+
className: "dsh-ig-desc",
|
|
260
|
+
children: t("description")
|
|
261
|
+
})]
|
|
148
262
|
}), (0, react_jsx_runtime.jsx)("span", {
|
|
149
|
-
className: "dsh-ig-
|
|
150
|
-
children: "选择厂商并配置生图模型。"
|
|
151
|
-
})] }), (0, react_jsx_runtime.jsx)("span", {
|
|
263
|
+
className: `dsh-ig-chevron ${open ? "dsh-ig-chevron-open" : ""}`,
|
|
152
264
|
"aria-hidden": "true",
|
|
153
|
-
children:
|
|
265
|
+
children: (0, react_jsx_runtime.jsx)("svg", {
|
|
266
|
+
width: "14",
|
|
267
|
+
height: "14",
|
|
268
|
+
viewBox: "0 0 16 16",
|
|
269
|
+
fill: "none",
|
|
270
|
+
stroke: "currentColor",
|
|
271
|
+
strokeWidth: "2",
|
|
272
|
+
strokeLinecap: "round",
|
|
273
|
+
strokeLinejoin: "round",
|
|
274
|
+
children: (0, react_jsx_runtime.jsx)("path", { d: "M4 6l4 4 4-4" })
|
|
275
|
+
})
|
|
154
276
|
})]
|
|
155
277
|
}), open ? (0, react_jsx_runtime.jsxs)("form", {
|
|
156
278
|
className: "dsh-ig-body",
|
|
@@ -163,7 +285,7 @@ window.__ModuleLoader__.load({
|
|
|
163
285
|
children: [
|
|
164
286
|
(0, react_jsx_runtime.jsx)("span", {
|
|
165
287
|
className: "dsh-ig-label",
|
|
166
|
-
children: "
|
|
288
|
+
children: t("provider")
|
|
167
289
|
}),
|
|
168
290
|
(0, react_jsx_runtime.jsxs)("select", {
|
|
169
291
|
className: "dsh-ig-input",
|
|
@@ -178,30 +300,30 @@ window.__ModuleLoader__.load({
|
|
|
178
300
|
children: [
|
|
179
301
|
(0, react_jsx_runtime.jsx)("option", {
|
|
180
302
|
value: "google",
|
|
181
|
-
children: "
|
|
303
|
+
children: t("providerGoogle")
|
|
182
304
|
}),
|
|
183
305
|
(0, react_jsx_runtime.jsx)("option", {
|
|
184
306
|
value: "openai",
|
|
185
|
-
children: "
|
|
307
|
+
children: t("providerOpenAI")
|
|
186
308
|
}),
|
|
187
309
|
(0, react_jsx_runtime.jsx)("option", {
|
|
188
310
|
value: "seedream",
|
|
189
|
-
children: "
|
|
311
|
+
children: t("providerSeedream")
|
|
190
312
|
})
|
|
191
313
|
]
|
|
192
314
|
}),
|
|
193
315
|
(0, react_jsx_runtime.jsx)("span", {
|
|
194
316
|
className: "dsh-ig-hint",
|
|
195
|
-
children:
|
|
317
|
+
children: providerLabels[provider]
|
|
196
318
|
})
|
|
197
319
|
]
|
|
198
320
|
}),
|
|
199
321
|
(0, react_jsx_runtime.jsxs)("label", {
|
|
200
322
|
className: "dsh-ig-field",
|
|
201
323
|
children: [
|
|
202
|
-
(0, react_jsx_runtime.
|
|
324
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
203
325
|
className: "dsh-ig-label",
|
|
204
|
-
children:
|
|
326
|
+
children: t("apiKeyLabel", { provider: providerLabels[provider] })
|
|
205
327
|
}),
|
|
206
328
|
(0, react_jsx_runtime.jsx)("input", {
|
|
207
329
|
className: "dsh-ig-input",
|
|
@@ -211,15 +333,11 @@ window.__ModuleLoader__.load({
|
|
|
211
333
|
onChange: (event) => {
|
|
212
334
|
setKey(event.target.value);
|
|
213
335
|
},
|
|
214
|
-
placeholder: configured ? "
|
|
336
|
+
placeholder: configured ? t("apiKeyPlaceholder") : ""
|
|
215
337
|
}),
|
|
216
|
-
(0, react_jsx_runtime.
|
|
338
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
217
339
|
className: "dsh-ig-hint",
|
|
218
|
-
children: [
|
|
219
|
-
"安全保存为 ",
|
|
220
|
-
KEY_REF[provider],
|
|
221
|
-
";页面不会读回明文。"
|
|
222
|
-
]
|
|
340
|
+
children: t("apiKeyHint", { key: KEY_REF[provider] })
|
|
223
341
|
})
|
|
224
342
|
]
|
|
225
343
|
}),
|
|
@@ -228,7 +346,7 @@ window.__ModuleLoader__.load({
|
|
|
228
346
|
children: [
|
|
229
347
|
(0, react_jsx_runtime.jsx)("span", {
|
|
230
348
|
className: "dsh-ig-label",
|
|
231
|
-
children: "
|
|
349
|
+
children: t("endpoint")
|
|
232
350
|
}),
|
|
233
351
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
234
352
|
className: "dsh-ig-input-group",
|
|
@@ -243,16 +361,16 @@ window.__ModuleLoader__.load({
|
|
|
243
361
|
}), (0, react_jsx_runtime.jsx)("button", {
|
|
244
362
|
type: "button",
|
|
245
363
|
className: "dsh-ig-btn-reset",
|
|
246
|
-
title: "
|
|
364
|
+
title: t("resetTitle"),
|
|
247
365
|
onClick: () => {
|
|
248
366
|
setBaseURL(DEFAULT_BASE_URLS[provider]);
|
|
249
367
|
},
|
|
250
|
-
children: "
|
|
368
|
+
children: t("reset")
|
|
251
369
|
})]
|
|
252
370
|
}),
|
|
253
371
|
(0, react_jsx_runtime.jsx)("span", {
|
|
254
372
|
className: "dsh-ig-hint",
|
|
255
|
-
children: provider === "google" ? "
|
|
373
|
+
children: provider === "google" ? t("endpointHintGoogle") : provider === "openai" ? t("endpointHintOpenAI") : t("endpointHintSeedream")
|
|
256
374
|
})
|
|
257
375
|
]
|
|
258
376
|
}),
|
|
@@ -260,7 +378,7 @@ window.__ModuleLoader__.load({
|
|
|
260
378
|
className: "dsh-ig-field",
|
|
261
379
|
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
262
380
|
className: "dsh-ig-label",
|
|
263
|
-
children: "
|
|
381
|
+
children: t("model")
|
|
264
382
|
}), (0, react_jsx_runtime.jsx)("input", {
|
|
265
383
|
className: "dsh-ig-input",
|
|
266
384
|
value: model,
|
|
@@ -280,7 +398,7 @@ window.__ModuleLoader__.load({
|
|
|
280
398
|
className: "dsh-ig-save",
|
|
281
399
|
type: "submit",
|
|
282
400
|
disabled: saving || !snapshot.writable,
|
|
283
|
-
children: saving ? "
|
|
401
|
+
children: saving ? t("saving") : t("save")
|
|
284
402
|
})]
|
|
285
403
|
})
|
|
286
404
|
]
|
|
@@ -291,7 +409,20 @@ window.__ModuleLoader__.load({
|
|
|
291
409
|
function GeneratedImageCard(props) {
|
|
292
410
|
const attachment = imageRef(props.block);
|
|
293
411
|
const [url, setUrl] = (0, react.useState)();
|
|
412
|
+
const [blob, setBlob] = (0, react.useState)();
|
|
294
413
|
const [error, setError] = (0, react.useState)();
|
|
414
|
+
const [previewOpen, setPreviewOpen] = (0, react.useState)(false);
|
|
415
|
+
const [toast, setToast] = (0, react.useState)();
|
|
416
|
+
(0, react.useEffect)(() => {
|
|
417
|
+
if (!previewOpen) return;
|
|
418
|
+
const onKeyDown = (e) => {
|
|
419
|
+
if (e.key === "Escape") setPreviewOpen(false);
|
|
420
|
+
};
|
|
421
|
+
window.addEventListener("keydown", onKeyDown);
|
|
422
|
+
return () => {
|
|
423
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
424
|
+
};
|
|
425
|
+
}, [previewOpen]);
|
|
295
426
|
(0, react.useEffect)(() => {
|
|
296
427
|
if (attachment === void 0) return;
|
|
297
428
|
const controller = new AbortController();
|
|
@@ -303,9 +434,10 @@ window.__ModuleLoader__.load({
|
|
|
303
434
|
body: JSON.stringify({ attachment })
|
|
304
435
|
}).then(async (response) => {
|
|
305
436
|
if (!response.ok) throw new Error(`图片读取失败 (${String(response.status)})`);
|
|
306
|
-
const
|
|
437
|
+
const resBlob = await response.blob();
|
|
307
438
|
if (controller.signal.aborted) return;
|
|
308
|
-
|
|
439
|
+
setBlob(resBlob);
|
|
440
|
+
objectUrl = URL.createObjectURL(resBlob);
|
|
309
441
|
setUrl(objectUrl);
|
|
310
442
|
}).catch((cause) => {
|
|
311
443
|
if (!controller.signal.aborted) setError(cause instanceof Error ? cause.message : String(cause));
|
|
@@ -315,6 +447,30 @@ window.__ModuleLoader__.load({
|
|
|
315
447
|
if (objectUrl !== void 0) URL.revokeObjectURL(objectUrl);
|
|
316
448
|
};
|
|
317
449
|
}, [attachment?.attachmentId]);
|
|
450
|
+
const copy = async (e) => {
|
|
451
|
+
e.stopPropagation();
|
|
452
|
+
if (!blob) return;
|
|
453
|
+
const ok = await copyImageBlob(blob);
|
|
454
|
+
setToast(ok ? "已复制图片" : "复制失败");
|
|
455
|
+
setTimeout(() => {
|
|
456
|
+
setToast(void 0);
|
|
457
|
+
}, 2e3);
|
|
458
|
+
};
|
|
459
|
+
const download = (e) => {
|
|
460
|
+
e.stopPropagation();
|
|
461
|
+
if (!url) return;
|
|
462
|
+
const a = document.createElement("a");
|
|
463
|
+
a.href = url;
|
|
464
|
+
a.download = attachment?.name || `dsh-image-${Date.now()}.png`;
|
|
465
|
+
document.body.appendChild(a);
|
|
466
|
+
a.click();
|
|
467
|
+
document.body.removeChild(a);
|
|
468
|
+
};
|
|
469
|
+
const openNewTab = (e) => {
|
|
470
|
+
e.stopPropagation();
|
|
471
|
+
if (!url) return;
|
|
472
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
473
|
+
};
|
|
318
474
|
if (attachment === void 0) return (0, react_jsx_runtime.jsx)("div", {
|
|
319
475
|
className: "dsh-ig-loading",
|
|
320
476
|
children: "正在生成图片…"
|
|
@@ -335,14 +491,150 @@ window.__ModuleLoader__.load({
|
|
|
335
491
|
className: "dsh-ig-loading",
|
|
336
492
|
children: "正在加载图片…"
|
|
337
493
|
}) : null,
|
|
338
|
-
url !== void 0 ? (0, react_jsx_runtime.
|
|
339
|
-
className: "dsh-ig-
|
|
340
|
-
|
|
341
|
-
|
|
494
|
+
url !== void 0 ? (0, react_jsx_runtime.jsxs)("div", {
|
|
495
|
+
className: "dsh-ig-container",
|
|
496
|
+
children: [(0, react_jsx_runtime.jsx)("img", {
|
|
497
|
+
className: "dsh-ig-image",
|
|
498
|
+
src: url,
|
|
499
|
+
alt: attachment.name ?? "Generated image",
|
|
500
|
+
onClick: () => {
|
|
501
|
+
setPreviewOpen(true);
|
|
502
|
+
}
|
|
503
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
504
|
+
className: "dsh-ig-toolbar",
|
|
505
|
+
children: [
|
|
506
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
507
|
+
type: "button",
|
|
508
|
+
className: "dsh-ig-tool-btn",
|
|
509
|
+
title: "复制图片",
|
|
510
|
+
onClick: (e) => {
|
|
511
|
+
copy(e);
|
|
512
|
+
},
|
|
513
|
+
children: (0, react_jsx_runtime.jsxs)("svg", {
|
|
514
|
+
width: "15",
|
|
515
|
+
height: "15",
|
|
516
|
+
viewBox: "0 0 24 24",
|
|
517
|
+
fill: "none",
|
|
518
|
+
stroke: "currentColor",
|
|
519
|
+
strokeWidth: "2",
|
|
520
|
+
strokeLinecap: "round",
|
|
521
|
+
strokeLinejoin: "round",
|
|
522
|
+
children: [(0, react_jsx_runtime.jsx)("rect", {
|
|
523
|
+
x: "9",
|
|
524
|
+
y: "9",
|
|
525
|
+
width: "13",
|
|
526
|
+
height: "13",
|
|
527
|
+
rx: "2",
|
|
528
|
+
ry: "2"
|
|
529
|
+
}), (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" })]
|
|
530
|
+
})
|
|
531
|
+
}),
|
|
532
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
533
|
+
type: "button",
|
|
534
|
+
className: "dsh-ig-tool-btn",
|
|
535
|
+
title: "下载图片",
|
|
536
|
+
onClick: download,
|
|
537
|
+
children: (0, react_jsx_runtime.jsxs)("svg", {
|
|
538
|
+
width: "15",
|
|
539
|
+
height: "15",
|
|
540
|
+
viewBox: "0 0 24 24",
|
|
541
|
+
fill: "none",
|
|
542
|
+
stroke: "currentColor",
|
|
543
|
+
strokeWidth: "2",
|
|
544
|
+
strokeLinecap: "round",
|
|
545
|
+
strokeLinejoin: "round",
|
|
546
|
+
children: [
|
|
547
|
+
(0, react_jsx_runtime.jsx)("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
548
|
+
(0, react_jsx_runtime.jsx)("polyline", { points: "7 10 12 15 17 10" }),
|
|
549
|
+
(0, react_jsx_runtime.jsx)("line", {
|
|
550
|
+
x1: "12",
|
|
551
|
+
y1: "15",
|
|
552
|
+
x2: "12",
|
|
553
|
+
y2: "3"
|
|
554
|
+
})
|
|
555
|
+
]
|
|
556
|
+
})
|
|
557
|
+
}),
|
|
558
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
559
|
+
type: "button",
|
|
560
|
+
className: "dsh-ig-tool-btn",
|
|
561
|
+
title: "新标签页打开",
|
|
562
|
+
onClick: openNewTab,
|
|
563
|
+
children: (0, react_jsx_runtime.jsxs)("svg", {
|
|
564
|
+
width: "15",
|
|
565
|
+
height: "15",
|
|
566
|
+
viewBox: "0 0 24 24",
|
|
567
|
+
fill: "none",
|
|
568
|
+
stroke: "currentColor",
|
|
569
|
+
strokeWidth: "2",
|
|
570
|
+
strokeLinecap: "round",
|
|
571
|
+
strokeLinejoin: "round",
|
|
572
|
+
children: [
|
|
573
|
+
(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" }),
|
|
574
|
+
(0, react_jsx_runtime.jsx)("polyline", { points: "15 3 21 3 21 9" }),
|
|
575
|
+
(0, react_jsx_runtime.jsx)("line", {
|
|
576
|
+
x1: "10",
|
|
577
|
+
y1: "14",
|
|
578
|
+
x2: "21",
|
|
579
|
+
y2: "3"
|
|
580
|
+
})
|
|
581
|
+
]
|
|
582
|
+
})
|
|
583
|
+
}),
|
|
584
|
+
toast ? (0, react_jsx_runtime.jsx)("div", {
|
|
585
|
+
className: "dsh-ig-toast",
|
|
586
|
+
children: toast
|
|
587
|
+
}) : null
|
|
588
|
+
]
|
|
589
|
+
})]
|
|
590
|
+
}) : null,
|
|
591
|
+
previewOpen && url !== void 0 ? (0, react_jsx_runtime.jsx)("div", {
|
|
592
|
+
className: "dsh-ig-modal",
|
|
593
|
+
onClick: () => {
|
|
594
|
+
setPreviewOpen(false);
|
|
595
|
+
},
|
|
596
|
+
children: (0, react_jsx_runtime.jsx)("img", {
|
|
597
|
+
className: "dsh-ig-modal-img",
|
|
598
|
+
src: url,
|
|
599
|
+
alt: attachment.name ?? "Generated image preview",
|
|
600
|
+
onClick: (e) => {
|
|
601
|
+
e.stopPropagation();
|
|
602
|
+
}
|
|
603
|
+
})
|
|
342
604
|
}) : null
|
|
343
605
|
]
|
|
344
606
|
});
|
|
345
607
|
}
|
|
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
|
+
}
|
|
346
638
|
function modelOf(provider, value) {
|
|
347
639
|
const stored = provider === "google" ? value?.googleModel : provider === "openai" ? value?.openaiModel : value?.seedreamModel;
|
|
348
640
|
return typeof stored === "string" && stored.length > 0 ? stored : DEFAULT_MODELS[provider];
|