dsh-image-gen 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +1854 -68
- package/lib/client.js.map +1 -1
- package/lib/index.js +11394 -321
- package/lib/types/client/gallery-view.d.ts +3 -1
- package/lib/types/client/inspiration-api.d.ts +10 -0
- package/lib/types/client/inspiration-catalog-cache.d.ts +4 -0
- package/lib/types/client/inspiration-detail.d.ts +14 -0
- package/lib/types/client/inspiration-image-cache.d.ts +3 -0
- package/lib/types/client/inspiration-style.d.ts +1 -0
- package/lib/types/client/inspiration-view.d.ts +8 -0
- package/lib/types/client/multi-model-compare.d.ts +11 -0
- package/lib/types/client/studio-style.d.ts +1 -1
- package/lib/types/client/studio-view.d.ts +3 -0
- package/lib/types/image-route.d.ts +18 -1
- package/lib/types/index.d.ts +2 -1
- package/lib/types/inspiration/cache.d.ts +20 -0
- package/lib/types/inspiration/downloader.d.ts +27 -0
- package/lib/types/inspiration/routes.d.ts +13 -0
- package/lib/types/inspiration/sources.d.ts +33 -0
- package/lib/types/inspiration/types.d.ts +65 -0
- package/lib/types/inspiration/updater.d.ts +7 -0
- package/lib/types/inspiration-route.d.ts +6 -0
- package/lib/types/inspiration.d.ts +53 -0
- package/lib/types/shared.d.ts +19 -4
- package/lib/types/studio.d.ts +1 -0
- package/lib/types/workspace-save.d.ts +7 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -13,6 +13,10 @@ window.__ModuleLoader__.load({
|
|
|
13
13
|
const DELETE_ROUTE = "/plugins/dsh-image-gen/delete";
|
|
14
14
|
/** Same-origin route used by the browser image workbench. */
|
|
15
15
|
const STUDIO_ROUTE = "/plugins/dsh-image-gen/studio";
|
|
16
|
+
/** Same-origin route for built-in prompt inspiration metadata and images. */
|
|
17
|
+
const INSPIRATION_ROUTE = "/plugins/dsh-image-gen/inspiration";
|
|
18
|
+
/** Browser route used for saving generated images to workspace on demand. */
|
|
19
|
+
const SAVE_WORKSPACE_ROUTE = "/plugins/dsh-image-gen/save-workspace";
|
|
16
20
|
/** Namespace persisted through DSH Settings. */
|
|
17
21
|
const IMAGE_GENERATION_NAMESPACE = "image-generation";
|
|
18
22
|
/** Providers supported by the first browser workbench release. */
|
|
@@ -148,7 +152,7 @@ window.__ModuleLoader__.load({
|
|
|
148
152
|
}
|
|
149
153
|
//#endregion
|
|
150
154
|
//#region lib/types/client/gallery-store.js
|
|
151
|
-
const DB_NAME = "dsh_image_gen_db";
|
|
155
|
+
const DB_NAME$2 = "dsh_image_gen_db";
|
|
152
156
|
const DB_VERSION = 2;
|
|
153
157
|
const STORE_NAME = "gallery_history";
|
|
154
158
|
const TOMBSTONE_STORE = "gallery_tombstones";
|
|
@@ -161,7 +165,7 @@ window.__ModuleLoader__.load({
|
|
|
161
165
|
reject(/* @__PURE__ */ new Error("IndexedDB is not supported in this environment."));
|
|
162
166
|
return;
|
|
163
167
|
}
|
|
164
|
-
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
168
|
+
const request = indexedDB.open(DB_NAME$2, DB_VERSION);
|
|
165
169
|
request.onupgradeneeded = (event) => {
|
|
166
170
|
const db = event.target.result;
|
|
167
171
|
if (!db.objectStoreNames.contains(STORE_NAME)) db.createObjectStore(STORE_NAME, { keyPath: "id" }).createIndex("createdAt", "createdAt", { unique: false });
|
|
@@ -511,6 +515,26 @@ window.__ModuleLoader__.load({
|
|
|
511
515
|
* This source code is licensed under the ISC license.
|
|
512
516
|
* See the LICENSE file in the root directory of this source tree.
|
|
513
517
|
*/
|
|
518
|
+
const BookmarkPlus = createLucideIcon("bookmark-plus", [
|
|
519
|
+
["path", {
|
|
520
|
+
d: "M12 7v6",
|
|
521
|
+
key: "lw1j43"
|
|
522
|
+
}],
|
|
523
|
+
["path", {
|
|
524
|
+
d: "M15 10H9",
|
|
525
|
+
key: "o6yqo3"
|
|
526
|
+
}],
|
|
527
|
+
["path", {
|
|
528
|
+
d: "M17 3a2 2 0 0 1 2 2v15a1 1 0 0 1-1.496.868l-4.512-2.578a2 2 0 0 0-1.984 0l-4.512 2.578A1 1 0 0 1 5 20V5a2 2 0 0 1 2-2z",
|
|
529
|
+
key: "oz39mx"
|
|
530
|
+
}]
|
|
531
|
+
]);
|
|
532
|
+
/**
|
|
533
|
+
* @license lucide-react v1.38.0 - ISC
|
|
534
|
+
*
|
|
535
|
+
* This source code is licensed under the ISC license.
|
|
536
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
537
|
+
*/
|
|
514
538
|
const Check = createLucideIcon("check", [["path", {
|
|
515
539
|
d: "M20 6 9 17l-5-5",
|
|
516
540
|
key: "1gmf2c"
|
|
@@ -647,6 +671,26 @@ window.__ModuleLoader__.load({
|
|
|
647
671
|
* This source code is licensed under the ISC license.
|
|
648
672
|
* See the LICENSE file in the root directory of this source tree.
|
|
649
673
|
*/
|
|
674
|
+
const ExternalLink = createLucideIcon("external-link", [
|
|
675
|
+
["path", {
|
|
676
|
+
d: "M15 3h6v6",
|
|
677
|
+
key: "1q9fwt"
|
|
678
|
+
}],
|
|
679
|
+
["path", {
|
|
680
|
+
d: "M10 14 21 3",
|
|
681
|
+
key: "gplh6r"
|
|
682
|
+
}],
|
|
683
|
+
["path", {
|
|
684
|
+
d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",
|
|
685
|
+
key: "a6xqqp"
|
|
686
|
+
}]
|
|
687
|
+
]);
|
|
688
|
+
/**
|
|
689
|
+
* @license lucide-react v1.38.0 - ISC
|
|
690
|
+
*
|
|
691
|
+
* This source code is licensed under the ISC license.
|
|
692
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
693
|
+
*/
|
|
650
694
|
const FileText = createLucideIcon("file-text", [
|
|
651
695
|
["path", {
|
|
652
696
|
d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",
|
|
@@ -752,6 +796,30 @@ window.__ModuleLoader__.load({
|
|
|
752
796
|
* This source code is licensed under the ISC license.
|
|
753
797
|
* See the LICENSE file in the root directory of this source tree.
|
|
754
798
|
*/
|
|
799
|
+
const Maximize2 = createLucideIcon("maximize-2", [
|
|
800
|
+
["path", {
|
|
801
|
+
d: "M15 3h6v6",
|
|
802
|
+
key: "1q9fwt"
|
|
803
|
+
}],
|
|
804
|
+
["path", {
|
|
805
|
+
d: "m21 3-7 7",
|
|
806
|
+
key: "1l2asr"
|
|
807
|
+
}],
|
|
808
|
+
["path", {
|
|
809
|
+
d: "m3 21 7-7",
|
|
810
|
+
key: "tjx5ai"
|
|
811
|
+
}],
|
|
812
|
+
["path", {
|
|
813
|
+
d: "M9 21H3v-6",
|
|
814
|
+
key: "wtvkvv"
|
|
815
|
+
}]
|
|
816
|
+
]);
|
|
817
|
+
/**
|
|
818
|
+
* @license lucide-react v1.38.0 - ISC
|
|
819
|
+
*
|
|
820
|
+
* This source code is licensed under the ISC license.
|
|
821
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
822
|
+
*/
|
|
755
823
|
const PanelLeftClose = createLucideIcon("panel-left-close", [
|
|
756
824
|
["rect", {
|
|
757
825
|
width: "18",
|
|
@@ -935,6 +1003,16 @@ window.__ModuleLoader__.load({
|
|
|
935
1003
|
* This source code is licensed under the ISC license.
|
|
936
1004
|
* See the LICENSE file in the root directory of this source tree.
|
|
937
1005
|
*/
|
|
1006
|
+
const Star = createLucideIcon("star", [["path", {
|
|
1007
|
+
d: "M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z",
|
|
1008
|
+
key: "r04s7s"
|
|
1009
|
+
}]]);
|
|
1010
|
+
/**
|
|
1011
|
+
* @license lucide-react v1.38.0 - ISC
|
|
1012
|
+
*
|
|
1013
|
+
* This source code is licensed under the ISC license.
|
|
1014
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
1015
|
+
*/
|
|
938
1016
|
const Trash2 = createLucideIcon("trash-2", [
|
|
939
1017
|
["path", {
|
|
940
1018
|
d: "M10 11v6",
|
|
@@ -1219,20 +1297,42 @@ window.__ModuleLoader__.load({
|
|
|
1219
1297
|
return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`;
|
|
1220
1298
|
}
|
|
1221
1299
|
//#endregion
|
|
1300
|
+
//#region lib/types/client/multi-model-compare.js
|
|
1301
|
+
/** Map one shared output intent to settings accepted by every target model. */
|
|
1302
|
+
function buildComparisonTargets(profiles, selectedProviders, ratio, quality) {
|
|
1303
|
+
const selected = new Set(selectedProviders);
|
|
1304
|
+
return profiles.filter((profile) => profile.configured && selected.has(profile.provider)).map((profile) => {
|
|
1305
|
+
const targetRatio = profile.ratioOptions.some((option) => option.value === ratio) ? ratio : profile.defaultRatio;
|
|
1306
|
+
const targetQuality = profile.qualityOptions.some((option) => option.value === quality) ? quality : profile.defaultQuality;
|
|
1307
|
+
return {
|
|
1308
|
+
profile,
|
|
1309
|
+
ratio: targetRatio,
|
|
1310
|
+
quality: targetQuality,
|
|
1311
|
+
adjusted: targetRatio !== ratio || targetQuality !== quality
|
|
1312
|
+
};
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
/** Start with two models, not every configured API, to avoid surprise spend. */
|
|
1316
|
+
function initialComparisonProviders(profiles, activeProvider) {
|
|
1317
|
+
const configured = profiles.filter((profile) => profile.configured);
|
|
1318
|
+
const active = configured.find((profile) => profile.provider === activeProvider);
|
|
1319
|
+
return (active === void 0 ? configured : [active, ...configured.filter((profile) => profile.provider !== activeProvider)]).slice(0, 2).map((profile) => profile.provider);
|
|
1320
|
+
}
|
|
1321
|
+
//#endregion
|
|
1222
1322
|
//#region lib/types/client/studio-view.js
|
|
1223
|
-
const COPY = {
|
|
1323
|
+
const COPY$1 = {
|
|
1224
1324
|
zh: {
|
|
1225
|
-
title: "
|
|
1325
|
+
title: "云端生图工作台",
|
|
1226
1326
|
configured: "API 已配置",
|
|
1227
1327
|
unconfigured: "未配置",
|
|
1228
1328
|
recent: "最近生成",
|
|
1229
|
-
empty: "
|
|
1329
|
+
empty: "暂无生成历史",
|
|
1230
1330
|
generate: "文生图",
|
|
1231
1331
|
edit: "图生图",
|
|
1232
1332
|
details: "图片详情",
|
|
1233
1333
|
reference: "参考图",
|
|
1234
|
-
optional: "
|
|
1235
|
-
upload: "
|
|
1334
|
+
optional: "选填",
|
|
1335
|
+
upload: "点击或拖拽图片到此处",
|
|
1236
1336
|
uploadHint: "支持 JPG / PNG / WebP / GIF,最大 10MB(最多 5 张)",
|
|
1237
1337
|
prompt: "提示词 Prompt",
|
|
1238
1338
|
clear: "清空",
|
|
@@ -1244,6 +1344,28 @@ window.__ModuleLoader__.load({
|
|
|
1244
1344
|
start: "开始生成",
|
|
1245
1345
|
generating: "正在生成…",
|
|
1246
1346
|
cancelGenerate: "取消生成",
|
|
1347
|
+
count: "生成数量",
|
|
1348
|
+
countUnit: "{n} 张",
|
|
1349
|
+
partialSuccess: "已生成 {success} 张图片,{failed} 张失败",
|
|
1350
|
+
generatingCount: "正在生成(共 {count} 张)…",
|
|
1351
|
+
batchResult: "本次生成(共 {count} 张)",
|
|
1352
|
+
singleModel: "单模型",
|
|
1353
|
+
compareModels: "多模型对比",
|
|
1354
|
+
compareHint: "相同提示词,同时交给多个模型",
|
|
1355
|
+
compareSelect: "选择模型",
|
|
1356
|
+
compareSelected: "已选 {count} 个模型",
|
|
1357
|
+
compareNeedTwo: "请至少选择两个已配置模型",
|
|
1358
|
+
compareParameterHint: "不支持所选参数的模型会自动使用默认值",
|
|
1359
|
+
compareAdjusted: "已适配",
|
|
1360
|
+
comparePartial: "{success} 个模型生成成功,{failed} 个失败",
|
|
1361
|
+
comparing: "正在对比(共 {count} 个模型)…",
|
|
1362
|
+
compareStart: "开始对比",
|
|
1363
|
+
saveToGallery: "收藏进画廊",
|
|
1364
|
+
saveSelected: "收藏选中({count})",
|
|
1365
|
+
savedToGallery: "已收藏进画廊",
|
|
1366
|
+
savedSelected: "已收藏 {count} 张图片",
|
|
1367
|
+
inGallery: "已在画廊",
|
|
1368
|
+
needSelectResult: "请先勾选要收藏的图片",
|
|
1247
1369
|
retry: "重新加载",
|
|
1248
1370
|
configLoadFailed: "工作台配置加载失败,请检查服务后重试。",
|
|
1249
1371
|
noProvider: "请先在设置中配置至少一个云端图像 Provider 的 API Key。",
|
|
@@ -1294,7 +1416,8 @@ window.__ModuleLoader__.load({
|
|
|
1294
1416
|
unconfiguredStatus: "API 未配置",
|
|
1295
1417
|
providerStatus: "云端提供商与模型状态",
|
|
1296
1418
|
collapseSidebar: "折叠最近生成",
|
|
1297
|
-
expandSidebar: "展开最近生成"
|
|
1419
|
+
expandSidebar: "展开最近生成",
|
|
1420
|
+
findInspiration: "找灵感"
|
|
1298
1421
|
},
|
|
1299
1422
|
en: {
|
|
1300
1423
|
title: "Cloud Image Studio",
|
|
@@ -1319,6 +1442,28 @@ window.__ModuleLoader__.load({
|
|
|
1319
1442
|
start: "Generate",
|
|
1320
1443
|
generating: "Generating…",
|
|
1321
1444
|
cancelGenerate: "Cancel",
|
|
1445
|
+
count: "Number of images",
|
|
1446
|
+
countUnit: "{n}",
|
|
1447
|
+
partialSuccess: "Generated {success} images, {failed} failed",
|
|
1448
|
+
generatingCount: "Generating ({count} images)…",
|
|
1449
|
+
batchResult: "Generated {count} images",
|
|
1450
|
+
singleModel: "Single model",
|
|
1451
|
+
compareModels: "Compare models",
|
|
1452
|
+
compareHint: "Send the same prompt to multiple models",
|
|
1453
|
+
compareSelect: "Choose models",
|
|
1454
|
+
compareSelected: "{count} models selected",
|
|
1455
|
+
compareNeedTwo: "Select at least two configured models",
|
|
1456
|
+
compareParameterHint: "Unsupported settings use each model’s default",
|
|
1457
|
+
compareAdjusted: "Adjusted",
|
|
1458
|
+
comparePartial: "{success} models succeeded, {failed} failed",
|
|
1459
|
+
comparing: "Comparing {count} models…",
|
|
1460
|
+
compareStart: "Compare models",
|
|
1461
|
+
saveToGallery: "Save to Gallery",
|
|
1462
|
+
saveSelected: "Save selected ({count})",
|
|
1463
|
+
savedToGallery: "Saved to Gallery",
|
|
1464
|
+
savedSelected: "Saved {count} images",
|
|
1465
|
+
inGallery: "In Gallery",
|
|
1466
|
+
needSelectResult: "Select images to save first",
|
|
1322
1467
|
retry: "Retry",
|
|
1323
1468
|
configLoadFailed: "Failed to load studio configuration.",
|
|
1324
1469
|
noProvider: "Configure an API key for at least one cloud image provider in Settings.",
|
|
@@ -1369,10 +1514,11 @@ window.__ModuleLoader__.load({
|
|
|
1369
1514
|
unconfiguredStatus: "Not configured",
|
|
1370
1515
|
providerStatus: "Provider & Model Status",
|
|
1371
1516
|
collapseSidebar: "Collapse sidebar",
|
|
1372
|
-
expandSidebar: "Expand recent list"
|
|
1517
|
+
expandSidebar: "Expand recent list",
|
|
1518
|
+
findInspiration: "Find inspiration"
|
|
1373
1519
|
}
|
|
1374
1520
|
};
|
|
1375
|
-
const StudioView = ({ locale, workspace }) => {
|
|
1521
|
+
const StudioView = ({ locale, workspace, initialPrompt, onInitialPromptApplied, onOpenInspiration }) => {
|
|
1376
1522
|
const [lang, setLang] = (0, react.useState)(() => locale?.getSnapshot?.().active?.startsWith("en") ? "en" : "zh");
|
|
1377
1523
|
const [config, setConfig] = (0, react.useState)(null);
|
|
1378
1524
|
const [configLoading, setConfigLoading] = (0, react.useState)(true);
|
|
@@ -1389,6 +1535,12 @@ window.__ModuleLoader__.load({
|
|
|
1389
1535
|
const [quality, setQuality] = (0, react.useState)("1K");
|
|
1390
1536
|
const [prompt, setPrompt] = (0, react.useState)("");
|
|
1391
1537
|
const [references, setReferences] = (0, react.useState)([]);
|
|
1538
|
+
const [count, setCount] = (0, react.useState)(1);
|
|
1539
|
+
const [currentBatch, setCurrentBatch] = (0, react.useState)(null);
|
|
1540
|
+
const [selectedBatchIds, setSelectedBatchIds] = (0, react.useState)([]);
|
|
1541
|
+
const [batchKind, setBatchKind] = (0, react.useState)(null);
|
|
1542
|
+
const [comparisonEnabled, setComparisonEnabled] = (0, react.useState)(false);
|
|
1543
|
+
const [comparisonProviders, setComparisonProviders] = (0, react.useState)([]);
|
|
1392
1544
|
const [isGenerating, setIsGenerating] = (0, react.useState)(false);
|
|
1393
1545
|
const [error, setError] = (0, react.useState)(null);
|
|
1394
1546
|
const [notice, setNotice] = (0, react.useState)(null);
|
|
@@ -1409,11 +1561,18 @@ window.__ModuleLoader__.load({
|
|
|
1409
1561
|
const canvasRef = (0, react.useRef)(null);
|
|
1410
1562
|
const fileInputRef = (0, react.useRef)(null);
|
|
1411
1563
|
const image = useAttachmentImage(selected?.attachment);
|
|
1412
|
-
|
|
1564
|
+
(0, react.useEffect)(() => {
|
|
1565
|
+
if (initialPrompt === void 0) return;
|
|
1566
|
+
setMode("generate");
|
|
1567
|
+
setPanelTab("generate");
|
|
1568
|
+
setPrompt(initialPrompt);
|
|
1569
|
+
onInitialPromptApplied?.();
|
|
1570
|
+
}, [initialPrompt, onInitialPromptApplied]);
|
|
1571
|
+
const maxReferences = comparisonEnabled ? comparisonProviders.includes("dashscope") ? 3 : 5 : provider === "dashscope" ? 3 : 5;
|
|
1413
1572
|
const referencesRef = (0, react.useRef)(references);
|
|
1414
1573
|
referencesRef.current = references;
|
|
1415
1574
|
const t = (key, values) => {
|
|
1416
|
-
let text = COPY[lang][key];
|
|
1575
|
+
let text = COPY$1[lang][key];
|
|
1417
1576
|
for (const [name, value] of Object.entries(values ?? {})) text = text.replace(`{${name}}`, value);
|
|
1418
1577
|
return text;
|
|
1419
1578
|
};
|
|
@@ -1431,7 +1590,10 @@ window.__ModuleLoader__.load({
|
|
|
1431
1590
|
if (!response.ok || !("providers" in payload)) throw new Error("error" in payload && payload.error ? payload.error : "Studio unavailable");
|
|
1432
1591
|
setConfig(payload);
|
|
1433
1592
|
const initial = payload.providers.find((item) => item.provider === payload.activeProvider) ?? payload.providers[0];
|
|
1434
|
-
if (initial !== void 0)
|
|
1593
|
+
if (initial !== void 0) {
|
|
1594
|
+
applyProvider(initial);
|
|
1595
|
+
setComparisonProviders(initialComparisonProviders(payload.providers, initial.provider));
|
|
1596
|
+
}
|
|
1435
1597
|
} catch (fetchError) {
|
|
1436
1598
|
if (!controller.signal.aborted) setConfigError(messageOf(fetchError));
|
|
1437
1599
|
} finally {
|
|
@@ -1476,7 +1638,7 @@ window.__ModuleLoader__.load({
|
|
|
1476
1638
|
const canvasEl = canvasRef.current;
|
|
1477
1639
|
if (!canvasEl) return;
|
|
1478
1640
|
const onWheel = (e) => {
|
|
1479
|
-
if (selected === null ||
|
|
1641
|
+
if (selected === null && (!currentBatch || currentBatch.length === 0)) return;
|
|
1480
1642
|
e.preventDefault();
|
|
1481
1643
|
const delta = e.deltaY < 0 ? 15 : -15;
|
|
1482
1644
|
setFit(false);
|
|
@@ -1484,7 +1646,7 @@ window.__ModuleLoader__.load({
|
|
|
1484
1646
|
};
|
|
1485
1647
|
canvasEl.addEventListener("wheel", onWheel, { passive: false });
|
|
1486
1648
|
return () => canvasEl.removeEventListener("wheel", onWheel);
|
|
1487
|
-
}, [selected,
|
|
1649
|
+
}, [selected, currentBatch]);
|
|
1488
1650
|
(0, react.useEffect)(() => {
|
|
1489
1651
|
const handleMouseMove = (e) => {
|
|
1490
1652
|
if (!dragStartRef.current) return;
|
|
@@ -1513,6 +1675,13 @@ window.__ModuleLoader__.load({
|
|
|
1513
1675
|
const activeProfile = (0, react.useMemo)(() => config?.providers.find((item) => item.provider === provider), [config, provider]);
|
|
1514
1676
|
const configuredCount = config?.providers.filter((item) => item.configured).length ?? 0;
|
|
1515
1677
|
const displayItems = (0, react.useMemo)(() => items.slice(0, visibleLimit), [items, visibleLimit]);
|
|
1678
|
+
const comparisonProfiles = (0, react.useMemo)(() => (config?.providers ?? []).filter((item) => item.configured && (mode === "generate" || item.supportsEditing)), [config, mode]);
|
|
1679
|
+
const comparisonTargets = (0, react.useMemo)(() => buildComparisonTargets(comparisonProfiles, comparisonProviders, ratio, quality), [
|
|
1680
|
+
comparisonProfiles,
|
|
1681
|
+
comparisonProviders,
|
|
1682
|
+
ratio,
|
|
1683
|
+
quality
|
|
1684
|
+
]);
|
|
1516
1685
|
const applyProvider = (profile) => {
|
|
1517
1686
|
setProvider(profile.provider);
|
|
1518
1687
|
setModel(profile.model);
|
|
@@ -1544,18 +1713,24 @@ window.__ModuleLoader__.load({
|
|
|
1544
1713
|
});
|
|
1545
1714
|
};
|
|
1546
1715
|
const selectItem = (item) => {
|
|
1716
|
+
setCurrentBatch(null);
|
|
1717
|
+
setSelectedBatchIds([]);
|
|
1718
|
+
setBatchKind(null);
|
|
1547
1719
|
setSelected(item);
|
|
1548
1720
|
setPanelTab("details");
|
|
1549
1721
|
resetFit();
|
|
1550
1722
|
};
|
|
1551
1723
|
const startNew = () => {
|
|
1724
|
+
setCurrentBatch(null);
|
|
1725
|
+
setSelectedBatchIds([]);
|
|
1726
|
+
setBatchKind(null);
|
|
1552
1727
|
setSelected(null);
|
|
1553
1728
|
setPanelTab("generate");
|
|
1554
1729
|
resetFit();
|
|
1555
1730
|
};
|
|
1556
1731
|
const handleCanvasMouseDown = (e) => {
|
|
1557
1732
|
if (e.button !== 0 && e.button !== 1) return;
|
|
1558
|
-
if (selected === null ||
|
|
1733
|
+
if (selected === null && (!currentBatch || currentBatch.length === 0)) return;
|
|
1559
1734
|
setIsDragging(true);
|
|
1560
1735
|
hasDraggedRef.current = false;
|
|
1561
1736
|
dragStartRef.current = {
|
|
@@ -1565,9 +1740,7 @@ window.__ModuleLoader__.load({
|
|
|
1565
1740
|
startY: offset.y
|
|
1566
1741
|
};
|
|
1567
1742
|
};
|
|
1568
|
-
const handleImageClick = () => {
|
|
1569
|
-
if (!hasDraggedRef.current) setLightboxOpen(true);
|
|
1570
|
-
};
|
|
1743
|
+
const handleImageClick = () => {};
|
|
1571
1744
|
const addReferenceFiles = (fileList) => {
|
|
1572
1745
|
if (!fileList || fileList.length === 0) return;
|
|
1573
1746
|
const files = Array.from(fileList);
|
|
@@ -1661,10 +1834,46 @@ window.__ModuleLoader__.load({
|
|
|
1661
1834
|
setIsGenerating(false);
|
|
1662
1835
|
}
|
|
1663
1836
|
};
|
|
1837
|
+
const toggleComparison = (enabled) => {
|
|
1838
|
+
setComparisonEnabled(enabled);
|
|
1839
|
+
setError(null);
|
|
1840
|
+
if (!enabled) {
|
|
1841
|
+
if (activeProfile !== void 0) {
|
|
1842
|
+
if (!activeProfile.ratioOptions.some((option) => option.value === ratio)) setRatio(activeProfile.defaultRatio);
|
|
1843
|
+
if (!activeProfile.qualityOptions.some((option) => option.value === quality)) setQuality(activeProfile.defaultQuality);
|
|
1844
|
+
}
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1847
|
+
if (config === null) return;
|
|
1848
|
+
const initial = initialComparisonProviders(comparisonProfiles, provider).filter((item) => mode !== "edit" || referencesRef.current.length <= 3 || item !== "dashscope");
|
|
1849
|
+
setComparisonProviders(initial);
|
|
1850
|
+
if (![
|
|
1851
|
+
"1:1",
|
|
1852
|
+
"3:2",
|
|
1853
|
+
"2:3",
|
|
1854
|
+
"16:9",
|
|
1855
|
+
"9:16"
|
|
1856
|
+
].includes(ratio)) setRatio("1:1");
|
|
1857
|
+
if (![
|
|
1858
|
+
"standard",
|
|
1859
|
+
"1K",
|
|
1860
|
+
"2K",
|
|
1861
|
+
"4K"
|
|
1862
|
+
].includes(quality)) setQuality("1K");
|
|
1863
|
+
};
|
|
1864
|
+
const toggleComparisonProvider = (target) => {
|
|
1865
|
+
if (mode === "edit" && target === "dashscope" && referencesRef.current.length > 3 && !comparisonProviders.includes(target)) {
|
|
1866
|
+
setError(t("maxReferencesExceeded", { max: "3" }));
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
setComparisonProviders((current) => current.includes(target) ? current.filter((item) => item !== target) : [...current, target]);
|
|
1870
|
+
setError(null);
|
|
1871
|
+
};
|
|
1664
1872
|
const submit = async () => {
|
|
1665
1873
|
if (isGenerating || activeProfile === void 0) return;
|
|
1666
1874
|
if (prompt.trim().length === 0) return setError(t("needPrompt"));
|
|
1667
|
-
if (
|
|
1875
|
+
if (comparisonEnabled && comparisonTargets.length < 2) return setError(t("compareNeedTwo"));
|
|
1876
|
+
if (!comparisonEnabled && !activeProfile.configured) return setError(t("selectConfigured"));
|
|
1668
1877
|
if (mode === "edit" && references.length === 0) return setError(t("needReference"));
|
|
1669
1878
|
setIsGenerating(true);
|
|
1670
1879
|
setError(null);
|
|
@@ -1680,6 +1889,64 @@ window.__ModuleLoader__.load({
|
|
|
1680
1889
|
};
|
|
1681
1890
|
throw new Error("无效参考图");
|
|
1682
1891
|
})) : void 0;
|
|
1892
|
+
if (comparisonEnabled) {
|
|
1893
|
+
const settled = await Promise.allSettled(comparisonTargets.map(async (target) => {
|
|
1894
|
+
const response = await fetch(STUDIO_ROUTE, {
|
|
1895
|
+
method: "POST",
|
|
1896
|
+
credentials: "same-origin",
|
|
1897
|
+
signal: controller.signal,
|
|
1898
|
+
headers: { "content-type": "application/json" },
|
|
1899
|
+
body: JSON.stringify({
|
|
1900
|
+
mode,
|
|
1901
|
+
provider: target.profile.provider,
|
|
1902
|
+
model: target.profile.model,
|
|
1903
|
+
prompt: prompt.trim(),
|
|
1904
|
+
ratio: target.ratio,
|
|
1905
|
+
quality: target.quality,
|
|
1906
|
+
...workspace?.path ? { workspaceRoot: workspace.path } : {},
|
|
1907
|
+
...referencesPayload === void 0 ? {} : { references: referencesPayload }
|
|
1908
|
+
})
|
|
1909
|
+
});
|
|
1910
|
+
const payload = await response.json();
|
|
1911
|
+
if (!response.ok || !("attachment" in payload)) throw new Error("error" in payload && payload.error ? payload.error : `${target.profile.label}: ${t("generationFailed")}`);
|
|
1912
|
+
return {
|
|
1913
|
+
payload,
|
|
1914
|
+
target
|
|
1915
|
+
};
|
|
1916
|
+
}));
|
|
1917
|
+
if (controller.signal.aborted) return;
|
|
1918
|
+
const successes = settled.filter((result) => result.status === "fulfilled");
|
|
1919
|
+
const failed = settled.length - successes.length;
|
|
1920
|
+
if (successes.length === 0) {
|
|
1921
|
+
const firstFailure = settled.find((result) => result.status === "rejected");
|
|
1922
|
+
throw firstFailure?.status === "rejected" ? firstFailure.reason : new Error(t("generationFailed"));
|
|
1923
|
+
}
|
|
1924
|
+
const galleryEntries = successes.map(({ value }, idx) => ({
|
|
1925
|
+
id: String(value.payload.attachment.attachmentId || `${value.payload.createdAt}-${idx}`),
|
|
1926
|
+
attachment: value.payload.attachment,
|
|
1927
|
+
prompt: value.payload.prompt,
|
|
1928
|
+
provider: value.payload.provider,
|
|
1929
|
+
model: value.payload.model,
|
|
1930
|
+
createdAt: value.payload.createdAt + idx,
|
|
1931
|
+
aspectRatio: value.target.ratio,
|
|
1932
|
+
imageSize: value.target.quality,
|
|
1933
|
+
output: value.payload.output,
|
|
1934
|
+
...value.payload.savedTo ? { savedTo: value.payload.savedTo } : {},
|
|
1935
|
+
...workspace?.path ? { workspacePath: workspace.path } : {},
|
|
1936
|
+
...workspace?.workspaceId ? { workspaceId: workspace.workspaceId } : {}
|
|
1937
|
+
}));
|
|
1938
|
+
setCurrentBatch(galleryEntries.length > 1 ? galleryEntries : null);
|
|
1939
|
+
setSelectedBatchIds(galleryEntries.length > 1 ? [galleryEntries[0].id] : []);
|
|
1940
|
+
setBatchKind(galleryEntries.length > 1 ? "multi-model" : null);
|
|
1941
|
+
setSelected(galleryEntries[0]);
|
|
1942
|
+
setPanelTab("details");
|
|
1943
|
+
resetFit();
|
|
1944
|
+
if (failed > 0) flash(t("comparePartial", {
|
|
1945
|
+
success: String(successes.length),
|
|
1946
|
+
failed: String(failed)
|
|
1947
|
+
}));
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1683
1950
|
const response = await fetch(STUDIO_ROUTE, {
|
|
1684
1951
|
method: "POST",
|
|
1685
1952
|
credentials: "same-origin",
|
|
@@ -1692,30 +1959,49 @@ window.__ModuleLoader__.load({
|
|
|
1692
1959
|
prompt: prompt.trim(),
|
|
1693
1960
|
ratio,
|
|
1694
1961
|
quality,
|
|
1962
|
+
...count > 1 ? { count } : {},
|
|
1695
1963
|
...workspace?.path ? { workspaceRoot: workspace.path } : {},
|
|
1696
1964
|
...referencesPayload === void 0 ? {} : { references: referencesPayload }
|
|
1697
1965
|
})
|
|
1698
1966
|
});
|
|
1699
1967
|
const payload = await response.json();
|
|
1700
1968
|
if (!response.ok || !("attachment" in payload)) throw new Error("error" in payload && payload.error ? payload.error : t("generationFailed"));
|
|
1701
|
-
const
|
|
1702
|
-
id: String(payload.attachment.attachmentId),
|
|
1969
|
+
const generatedList = Array.isArray(payload.items) && payload.items.length > 0 ? payload.items : [{
|
|
1703
1970
|
attachment: payload.attachment,
|
|
1971
|
+
output: payload.output,
|
|
1972
|
+
...payload.savedTo ? { savedTo: payload.savedTo } : {}
|
|
1973
|
+
}];
|
|
1974
|
+
const galleryEntries = generatedList.map((gen, idx) => ({
|
|
1975
|
+
id: String(gen.attachment.attachmentId || `${payload.createdAt}-${idx}`),
|
|
1976
|
+
attachment: gen.attachment,
|
|
1704
1977
|
prompt: payload.prompt,
|
|
1705
1978
|
provider: payload.provider,
|
|
1706
1979
|
model: payload.model,
|
|
1707
|
-
createdAt: payload.createdAt,
|
|
1980
|
+
createdAt: payload.createdAt + idx,
|
|
1708
1981
|
aspectRatio: ratio,
|
|
1709
1982
|
imageSize: quality,
|
|
1710
|
-
output:
|
|
1711
|
-
...
|
|
1983
|
+
output: gen.output,
|
|
1984
|
+
...gen.savedTo ? { savedTo: gen.savedTo } : {},
|
|
1712
1985
|
...workspace?.path ? { workspacePath: workspace.path } : {},
|
|
1713
1986
|
...workspace?.workspaceId ? { workspaceId: workspace.workspaceId } : {}
|
|
1714
|
-
};
|
|
1715
|
-
|
|
1716
|
-
|
|
1987
|
+
}));
|
|
1988
|
+
if (galleryEntries.length > 1) {
|
|
1989
|
+
setCurrentBatch(galleryEntries);
|
|
1990
|
+
setSelectedBatchIds([galleryEntries[0].id]);
|
|
1991
|
+
setBatchKind("multi-image");
|
|
1992
|
+
setSelected(galleryEntries[0]);
|
|
1993
|
+
} else if (galleryEntries.length === 1) {
|
|
1994
|
+
setCurrentBatch(null);
|
|
1995
|
+
setSelectedBatchIds([]);
|
|
1996
|
+
setBatchKind(null);
|
|
1997
|
+
setSelected(galleryEntries[0]);
|
|
1998
|
+
}
|
|
1717
1999
|
setPanelTab("details");
|
|
1718
2000
|
resetFit();
|
|
2001
|
+
if (payload.failedCount && payload.failedCount > 0) flash(t("partialSuccess", {
|
|
2002
|
+
success: String(generatedList.length),
|
|
2003
|
+
failed: String(payload.failedCount)
|
|
2004
|
+
}));
|
|
1719
2005
|
} catch (submitError) {
|
|
1720
2006
|
if (controller.signal.aborted) return;
|
|
1721
2007
|
setError(messageOf(submitError));
|
|
@@ -1726,6 +2012,73 @@ window.__ModuleLoader__.load({
|
|
|
1726
2012
|
}
|
|
1727
2013
|
}
|
|
1728
2014
|
};
|
|
2015
|
+
const isSelectedInGallery = (0, react.useMemo)(() => {
|
|
2016
|
+
if (selected === null) return false;
|
|
2017
|
+
return items.some((item) => item.id === selected.id);
|
|
2018
|
+
}, [items, selected]);
|
|
2019
|
+
const selectedBatchItems = (0, react.useMemo)(() => {
|
|
2020
|
+
if (currentBatch === null) return [];
|
|
2021
|
+
const ids = new Set(selectedBatchIds);
|
|
2022
|
+
return currentBatch.filter((item) => ids.has(item.id));
|
|
2023
|
+
}, [currentBatch, selectedBatchIds]);
|
|
2024
|
+
const pendingGalleryItems = (0, react.useMemo)(() => {
|
|
2025
|
+
const galleryIds = new Set(items.map((item) => item.id));
|
|
2026
|
+
return (currentBatch === null ? selected === null ? [] : [selected] : selectedBatchItems).filter((item) => !galleryIds.has(item.id));
|
|
2027
|
+
}, [
|
|
2028
|
+
currentBatch,
|
|
2029
|
+
items,
|
|
2030
|
+
selected,
|
|
2031
|
+
selectedBatchItems
|
|
2032
|
+
]);
|
|
2033
|
+
const saveButtonLabel = currentBatch !== null ? pendingGalleryItems.length > 0 ? t("saveSelected", { count: String(pendingGalleryItems.length) }) : selectedBatchItems.length === 0 ? t("saveSelected", { count: "0" }) : t("inGallery") : isSelectedInGallery ? t("inGallery") : t("saveToGallery");
|
|
2034
|
+
const saveSelectionComplete = currentBatch === null ? isSelectedInGallery : selectedBatchItems.length > 0 && pendingGalleryItems.length === 0;
|
|
2035
|
+
const saveGalleryEntry = async (item) => {
|
|
2036
|
+
let savedTo = item.savedTo;
|
|
2037
|
+
const targetRoot = workspace?.path || config?.workspaceRoot;
|
|
2038
|
+
if (!savedTo) try {
|
|
2039
|
+
const res = await fetch(SAVE_WORKSPACE_ROUTE, {
|
|
2040
|
+
method: "POST",
|
|
2041
|
+
credentials: "same-origin",
|
|
2042
|
+
headers: { "content-type": "application/json" },
|
|
2043
|
+
body: JSON.stringify({
|
|
2044
|
+
attachment: item.attachment,
|
|
2045
|
+
...targetRoot ? { workspaceRoot: targetRoot } : {}
|
|
2046
|
+
})
|
|
2047
|
+
});
|
|
2048
|
+
if (res.ok) {
|
|
2049
|
+
const data = await res.json();
|
|
2050
|
+
if (data.savedTo) savedTo = data.savedTo;
|
|
2051
|
+
}
|
|
2052
|
+
} catch (saveError) {
|
|
2053
|
+
console.warn("Failed to save to workspace:", saveError);
|
|
2054
|
+
}
|
|
2055
|
+
const updatedItem = {
|
|
2056
|
+
...item,
|
|
2057
|
+
...savedTo ? { savedTo } : {},
|
|
2058
|
+
...targetRoot ? { workspacePath: targetRoot } : {},
|
|
2059
|
+
...workspace?.workspaceId ? { workspaceId: workspace.workspaceId } : {}
|
|
2060
|
+
};
|
|
2061
|
+
await saveGalleryItem(updatedItem);
|
|
2062
|
+
return updatedItem;
|
|
2063
|
+
};
|
|
2064
|
+
const handleSaveToGallery = async () => {
|
|
2065
|
+
if (selected === null) return;
|
|
2066
|
+
if (currentBatch !== null && selectedBatchItems.length === 0) return setError(t("needSelectResult"));
|
|
2067
|
+
if (pendingGalleryItems.length === 0) return;
|
|
2068
|
+
const activeId = selected.id;
|
|
2069
|
+
const settled = await Promise.allSettled(pendingGalleryItems.map(saveGalleryEntry));
|
|
2070
|
+
const saved = settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
2071
|
+
if (saved.length === 0) {
|
|
2072
|
+
const failed = settled.find((result) => result.status === "rejected");
|
|
2073
|
+
setError(failed?.status === "rejected" ? messageOf(failed.reason) : t("generationFailed"));
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
2076
|
+
const savedById = new Map(saved.map((item) => [item.id, item]));
|
|
2077
|
+
setCurrentBatch((current) => current?.map((item) => savedById.get(item.id) ?? item) ?? null);
|
|
2078
|
+
setSelected(savedById.get(activeId) ?? saved[0]);
|
|
2079
|
+
setError(null);
|
|
2080
|
+
flash(saved.length > 1 ? t("savedSelected", { count: String(saved.length) }) : t("savedToGallery"));
|
|
2081
|
+
};
|
|
1729
2082
|
const handleConfirmDelete = async () => {
|
|
1730
2083
|
if (selected === null) return;
|
|
1731
2084
|
const idToDelete = selected.id;
|
|
@@ -1747,8 +2100,22 @@ window.__ModuleLoader__.load({
|
|
|
1747
2100
|
throw new Error(reason);
|
|
1748
2101
|
}
|
|
1749
2102
|
}
|
|
1750
|
-
await deleteGalleryItem(idToDelete);
|
|
2103
|
+
if (isSelectedInGallery) await deleteGalleryItem(idToDelete);
|
|
1751
2104
|
evictAttachmentCache(attachmentId);
|
|
2105
|
+
setSelectedBatchIds((current) => current.filter((id) => id !== idToDelete));
|
|
2106
|
+
if (currentBatch) {
|
|
2107
|
+
const remaining = currentBatch.filter((i) => i.id !== idToDelete);
|
|
2108
|
+
if (remaining.length > 1) {
|
|
2109
|
+
setCurrentBatch(remaining);
|
|
2110
|
+
setSelected(remaining[0]);
|
|
2111
|
+
} else if (remaining.length === 1) {
|
|
2112
|
+
setCurrentBatch(null);
|
|
2113
|
+
setSelected(remaining[0]);
|
|
2114
|
+
} else {
|
|
2115
|
+
setCurrentBatch(null);
|
|
2116
|
+
setSelected(null);
|
|
2117
|
+
}
|
|
2118
|
+
} else setSelected(null);
|
|
1752
2119
|
flash(t("deletedToast"));
|
|
1753
2120
|
} catch (delError) {
|
|
1754
2121
|
setError(messageOf(delError));
|
|
@@ -1766,13 +2133,50 @@ window.__ModuleLoader__.load({
|
|
|
1766
2133
|
const toggleFavorite = async () => {
|
|
1767
2134
|
if (selected === null) return;
|
|
1768
2135
|
const targetId = selected.id;
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
2136
|
+
if (isSelectedInGallery) {
|
|
2137
|
+
const nextFav = await toggleFavoriteGalleryItem(targetId);
|
|
2138
|
+
setSelected((curr) => curr !== null && curr.id === targetId ? {
|
|
2139
|
+
...curr,
|
|
2140
|
+
isFavorite: nextFav
|
|
2141
|
+
} : curr);
|
|
2142
|
+
if (currentBatch) setCurrentBatch((prev) => prev ? prev.map((i) => i.id === targetId ? {
|
|
2143
|
+
...i,
|
|
2144
|
+
isFavorite: nextFav
|
|
2145
|
+
} : i) : null);
|
|
2146
|
+
flash(nextFav ? t("favoriteAdded") : t("favoriteRemoved"));
|
|
2147
|
+
} else {
|
|
2148
|
+
const nextFav = !selected.isFavorite;
|
|
2149
|
+
setSelected((curr) => curr !== null && curr.id === targetId ? {
|
|
2150
|
+
...curr,
|
|
2151
|
+
isFavorite: nextFav
|
|
2152
|
+
} : curr);
|
|
2153
|
+
if (currentBatch) setCurrentBatch((prev) => prev ? prev.map((i) => i.id === targetId ? {
|
|
2154
|
+
...i,
|
|
2155
|
+
isFavorite: nextFav
|
|
2156
|
+
} : i) : null);
|
|
2157
|
+
flash(nextFav ? t("favoriteAdded") : t("favoriteRemoved"));
|
|
2158
|
+
}
|
|
2159
|
+
};
|
|
2160
|
+
const stepLightbox = (delta) => {
|
|
2161
|
+
if (!currentBatch || currentBatch.length <= 1 || selected === null) return;
|
|
2162
|
+
const currIdx = currentBatch.findIndex((i) => i.id === selected.id);
|
|
2163
|
+
if (currIdx === -1) return;
|
|
2164
|
+
const nextIdx = (currIdx + delta + currentBatch.length) % currentBatch.length;
|
|
2165
|
+
setSelected(currentBatch[nextIdx]);
|
|
1775
2166
|
};
|
|
2167
|
+
(0, react.useEffect)(() => {
|
|
2168
|
+
if (!lightboxOpen || !currentBatch || currentBatch.length <= 1) return;
|
|
2169
|
+
const onKeyDown = (e) => {
|
|
2170
|
+
if (e.key === "ArrowLeft") stepLightbox(-1);
|
|
2171
|
+
else if (e.key === "ArrowRight") stepLightbox(1);
|
|
2172
|
+
};
|
|
2173
|
+
window.addEventListener("keydown", onKeyDown);
|
|
2174
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
2175
|
+
}, [
|
|
2176
|
+
lightboxOpen,
|
|
2177
|
+
currentBatch,
|
|
2178
|
+
selected
|
|
2179
|
+
]);
|
|
1776
2180
|
const flash = (message) => {
|
|
1777
2181
|
setNotice(message);
|
|
1778
2182
|
window.setTimeout(() => setNotice(null), 2200);
|
|
@@ -1933,13 +2337,30 @@ window.__ModuleLoader__.load({
|
|
|
1933
2337
|
className: "dsh-ig-generation-orbit",
|
|
1934
2338
|
children: (0, react_jsx_runtime.jsx)(Sparkles, { size: 28 })
|
|
1935
2339
|
}),
|
|
1936
|
-
(0, react_jsx_runtime.jsx)("strong", { children: t("generating") }),
|
|
1937
|
-
(0, react_jsx_runtime.
|
|
1938
|
-
activeProfile?.label,
|
|
1939
|
-
" · ",
|
|
1940
|
-
model
|
|
1941
|
-
] })
|
|
2340
|
+
(0, react_jsx_runtime.jsx)("strong", { children: comparisonEnabled ? t("comparing", { count: String(comparisonTargets.length) }) : count > 1 ? t("generatingCount", { count: String(count) }) : t("generating") }),
|
|
2341
|
+
(0, react_jsx_runtime.jsx)("span", { children: comparisonEnabled ? comparisonTargets.map((target) => target.profile.label).join(" · ") : `${activeProfile?.label ?? ""} · ${model}` })
|
|
1942
2342
|
]
|
|
2343
|
+
}) : currentBatch && currentBatch.length > 1 ? (0, react_jsx_runtime.jsx)("div", {
|
|
2344
|
+
className: "dsh-ig-canvas-cluster",
|
|
2345
|
+
"data-count": currentBatch.length,
|
|
2346
|
+
style: {
|
|
2347
|
+
transform: `translate(${offset.x}px, ${offset.y}px) scale(${zoom / 100})`,
|
|
2348
|
+
transformOrigin: "center center",
|
|
2349
|
+
transition: isDragging ? "none" : "transform 0.08s ease-out"
|
|
2350
|
+
},
|
|
2351
|
+
children: currentBatch.map((batchItem, index) => (0, react_jsx_runtime.jsx)(BatchCanvasItem, {
|
|
2352
|
+
item: batchItem,
|
|
2353
|
+
index,
|
|
2354
|
+
badge: batchKind === "multi-model" ? batchItem.model : void 0,
|
|
2355
|
+
isActive: selected?.id === batchItem.id,
|
|
2356
|
+
isChecked: selectedBatchIds.includes(batchItem.id),
|
|
2357
|
+
onSelect: () => {
|
|
2358
|
+
if (!hasDraggedRef.current) {
|
|
2359
|
+
setSelected(batchItem);
|
|
2360
|
+
setSelectedBatchIds((current) => current.includes(batchItem.id) ? current.filter((id) => id !== batchItem.id) : [...current, batchItem.id]);
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
}, batchItem.id))
|
|
1943
2364
|
}) : selected === null ? (0, react_jsx_runtime.jsxs)("div", {
|
|
1944
2365
|
className: "dsh-ig-canvas-empty",
|
|
1945
2366
|
children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 36 }), (0, react_jsx_runtime.jsx)("span", { children: t("selectHistory") })]
|
|
@@ -1984,6 +2405,14 @@ window.__ModuleLoader__.load({
|
|
|
1984
2405
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
1985
2406
|
className: "dsh-ig-result-actions",
|
|
1986
2407
|
children: [(0, react_jsx_runtime.jsx)("p", { children: selected.prompt }), (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
2408
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
2409
|
+
type: "button",
|
|
2410
|
+
className: `dsh-ig-save-gallery-btn ${saveSelectionComplete ? "is-saved" : ""}`,
|
|
2411
|
+
onClick: () => void handleSaveToGallery(),
|
|
2412
|
+
title: saveButtonLabel,
|
|
2413
|
+
disabled: saveSelectionComplete,
|
|
2414
|
+
children: [saveSelectionComplete ? (0, react_jsx_runtime.jsx)(Check, { size: 15 }) : (0, react_jsx_runtime.jsx)(BookmarkPlus, { size: 15 }), (0, react_jsx_runtime.jsx)("span", { children: saveButtonLabel })]
|
|
2415
|
+
}),
|
|
1987
2416
|
(0, react_jsx_runtime.jsxs)("button", {
|
|
1988
2417
|
type: "button",
|
|
1989
2418
|
onClick: () => void toggleFavorite(),
|
|
@@ -2141,11 +2570,16 @@ window.__ModuleLoader__.load({
|
|
|
2141
2570
|
" ",
|
|
2142
2571
|
(0, react_jsx_runtime.jsx)("b", { children: "*" })
|
|
2143
2572
|
]
|
|
2573
|
+
}), (0, react_jsx_runtime.jsxs)("span", { children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
2574
|
+
type: "button",
|
|
2575
|
+
className: "dsh-ig-find-inspiration",
|
|
2576
|
+
onClick: onOpenInspiration,
|
|
2577
|
+
children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 11 }), t("findInspiration")]
|
|
2144
2578
|
}), (0, react_jsx_runtime.jsx)("button", {
|
|
2145
2579
|
type: "button",
|
|
2146
2580
|
onClick: () => setPrompt(""),
|
|
2147
2581
|
children: t("clear")
|
|
2148
|
-
})]
|
|
2582
|
+
})] })]
|
|
2149
2583
|
}),
|
|
2150
2584
|
(0, react_jsx_runtime.jsx)("textarea", {
|
|
2151
2585
|
id: "dsh-ig-prompt",
|
|
@@ -2177,39 +2611,130 @@ window.__ModuleLoader__.load({
|
|
|
2177
2611
|
onClick: () => void loadConfig(),
|
|
2178
2612
|
children: [(0, react_jsx_runtime.jsx)(RefreshCw, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: t("retry") })]
|
|
2179
2613
|
})]
|
|
2180
|
-
}) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
2181
|
-
className: "dsh-ig-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
options: activeProfile === void 0 ? [] : [{
|
|
2195
|
-
value: activeProfile.model,
|
|
2196
|
-
label: activeProfile.model
|
|
2197
|
-
}]
|
|
2614
|
+
}) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [comparisonProfiles.length >= 2 && (0, react_jsx_runtime.jsxs)("div", {
|
|
2615
|
+
className: "dsh-ig-generation-kind",
|
|
2616
|
+
role: "group",
|
|
2617
|
+
"aria-label": t("compareModels"),
|
|
2618
|
+
children: [(0, react_jsx_runtime.jsx)("button", {
|
|
2619
|
+
type: "button",
|
|
2620
|
+
className: !comparisonEnabled ? "is-active" : "",
|
|
2621
|
+
onClick: () => toggleComparison(false),
|
|
2622
|
+
children: t("singleModel")
|
|
2623
|
+
}), (0, react_jsx_runtime.jsxs)("button", {
|
|
2624
|
+
type: "button",
|
|
2625
|
+
className: comparisonEnabled ? "is-active" : "",
|
|
2626
|
+
onClick: () => toggleComparison(true),
|
|
2627
|
+
children: [(0, react_jsx_runtime.jsx)(Copy, { size: 13 }), t("compareModels")]
|
|
2198
2628
|
})]
|
|
2629
|
+
}), comparisonEnabled ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
2630
|
+
className: "dsh-ig-compare-box",
|
|
2631
|
+
children: [
|
|
2632
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2633
|
+
className: "dsh-ig-compare-heading",
|
|
2634
|
+
children: [(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("strong", { children: t("compareSelect") }), (0, react_jsx_runtime.jsx)("small", { children: t("compareHint") })] }), (0, react_jsx_runtime.jsx)("span", { children: t("compareSelected", { count: String(comparisonTargets.length) }) })]
|
|
2635
|
+
}),
|
|
2636
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
2637
|
+
className: "dsh-ig-model-checks",
|
|
2638
|
+
children: comparisonProfiles.map((item) => {
|
|
2639
|
+
const target = comparisonTargets.find((candidate) => candidate.profile.provider === item.provider);
|
|
2640
|
+
return (0, react_jsx_runtime.jsxs)("button", {
|
|
2641
|
+
type: "button",
|
|
2642
|
+
className: target ? "is-selected" : "",
|
|
2643
|
+
"aria-pressed": Boolean(target),
|
|
2644
|
+
onClick: () => toggleComparisonProvider(item.provider),
|
|
2645
|
+
children: [
|
|
2646
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
2647
|
+
className: "dsh-ig-model-checkmark",
|
|
2648
|
+
children: target ? (0, react_jsx_runtime.jsx)(Check, { size: 12 }) : null
|
|
2649
|
+
}),
|
|
2650
|
+
(0, react_jsx_runtime.jsxs)("span", {
|
|
2651
|
+
className: "dsh-ig-model-copy",
|
|
2652
|
+
children: [(0, react_jsx_runtime.jsx)("strong", { children: item.label }), (0, react_jsx_runtime.jsx)("small", {
|
|
2653
|
+
title: item.model,
|
|
2654
|
+
children: item.model
|
|
2655
|
+
})]
|
|
2656
|
+
}),
|
|
2657
|
+
target && (0, react_jsx_runtime.jsxs)("span", {
|
|
2658
|
+
className: "dsh-ig-model-settings",
|
|
2659
|
+
children: [
|
|
2660
|
+
target.ratio,
|
|
2661
|
+
" · ",
|
|
2662
|
+
target.quality,
|
|
2663
|
+
target.adjusted ? (0, react_jsx_runtime.jsx)("em", { children: t("compareAdjusted") }) : null
|
|
2664
|
+
]
|
|
2665
|
+
})
|
|
2666
|
+
]
|
|
2667
|
+
}, item.provider);
|
|
2668
|
+
})
|
|
2669
|
+
}),
|
|
2670
|
+
(0, react_jsx_runtime.jsx)("p", { children: t("compareParameterHint") })
|
|
2671
|
+
]
|
|
2199
2672
|
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
2200
2673
|
className: "dsh-ig-field-grid",
|
|
2201
2674
|
children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
|
|
2202
2675
|
label: t("ratio"),
|
|
2203
2676
|
value: ratio,
|
|
2204
2677
|
onChange: setRatio,
|
|
2205
|
-
options:
|
|
2678
|
+
options: comparisonRatioOptions(lang)
|
|
2206
2679
|
}), (0, react_jsx_runtime.jsx)(FieldSelect, {
|
|
2207
2680
|
label: t("quality"),
|
|
2208
2681
|
value: quality,
|
|
2209
2682
|
onChange: setQuality,
|
|
2210
|
-
options:
|
|
2683
|
+
options: comparisonQualityOptions(lang)
|
|
2211
2684
|
})]
|
|
2212
|
-
})] }),
|
|
2685
|
+
})] }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2686
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2687
|
+
className: "dsh-ig-field-grid",
|
|
2688
|
+
children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
|
|
2689
|
+
label: t("provider"),
|
|
2690
|
+
value: provider,
|
|
2691
|
+
onChange: changeProvider,
|
|
2692
|
+
options: config.providers.map((item) => ({
|
|
2693
|
+
value: item.provider,
|
|
2694
|
+
label: `${item.label}${item.configured ? "" : ` · ${t("unconfigured")}`}`
|
|
2695
|
+
}))
|
|
2696
|
+
}), (0, react_jsx_runtime.jsx)(FieldSelect, {
|
|
2697
|
+
label: t("model"),
|
|
2698
|
+
value: model,
|
|
2699
|
+
onChange: setModel,
|
|
2700
|
+
options: activeProfile === void 0 ? [] : [{
|
|
2701
|
+
value: activeProfile.model,
|
|
2702
|
+
label: activeProfile.model
|
|
2703
|
+
}]
|
|
2704
|
+
})]
|
|
2705
|
+
}),
|
|
2706
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2707
|
+
className: "dsh-ig-field-grid",
|
|
2708
|
+
children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
|
|
2709
|
+
label: t("ratio"),
|
|
2710
|
+
value: ratio,
|
|
2711
|
+
onChange: setRatio,
|
|
2712
|
+
options: localizeRatioOptions(activeProfile?.ratioOptions ?? [], lang)
|
|
2713
|
+
}), (0, react_jsx_runtime.jsx)(FieldSelect, {
|
|
2714
|
+
label: t("quality"),
|
|
2715
|
+
value: quality,
|
|
2716
|
+
onChange: setQuality,
|
|
2717
|
+
options: localizeQualityOptions(activeProfile?.qualityOptions ?? [], lang)
|
|
2718
|
+
})]
|
|
2719
|
+
}),
|
|
2720
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
2721
|
+
className: "dsh-ig-field",
|
|
2722
|
+
children: [(0, react_jsx_runtime.jsx)("label", { children: t("count") }), (0, react_jsx_runtime.jsx)("div", {
|
|
2723
|
+
className: "dsh-ig-count-row",
|
|
2724
|
+
children: [
|
|
2725
|
+
1,
|
|
2726
|
+
2,
|
|
2727
|
+
3,
|
|
2728
|
+
4
|
|
2729
|
+
].map((option) => (0, react_jsx_runtime.jsx)("button", {
|
|
2730
|
+
type: "button",
|
|
2731
|
+
className: `dsh-ig-count-pill ${count === option ? "is-active" : ""}`,
|
|
2732
|
+
onClick: () => setCount(option),
|
|
2733
|
+
children: t("countUnit", { n: String(option) })
|
|
2734
|
+
}, option))
|
|
2735
|
+
})]
|
|
2736
|
+
})
|
|
2737
|
+
] })] }),
|
|
2213
2738
|
configuredCount === 0 && config !== null && (0, react_jsx_runtime.jsx)("div", {
|
|
2214
2739
|
className: "dsh-ig-inline-note",
|
|
2215
2740
|
children: t("noProvider")
|
|
@@ -2231,7 +2756,7 @@ window.__ModuleLoader__.load({
|
|
|
2231
2756
|
className: "dsh-ig-generate-button",
|
|
2232
2757
|
disabled: config === null,
|
|
2233
2758
|
onClick: () => void submit(),
|
|
2234
|
-
children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 17 }), (0, react_jsx_runtime.jsx)("span", { children: t("start") })]
|
|
2759
|
+
children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 17 }), (0, react_jsx_runtime.jsx)("span", { children: comparisonEnabled ? `${t("compareStart")} (${comparisonTargets.length})` : `${t("start")}${count > 1 ? ` (${count})` : ""}` })]
|
|
2235
2760
|
})
|
|
2236
2761
|
]
|
|
2237
2762
|
})]
|
|
@@ -2255,6 +2780,14 @@ window.__ModuleLoader__.load({
|
|
|
2255
2780
|
className: "dsh-ig-tag dsh-ig-tag-model",
|
|
2256
2781
|
children: selected.model
|
|
2257
2782
|
}),
|
|
2783
|
+
currentBatch && currentBatch.length > 1 && (0, react_jsx_runtime.jsxs)("span", {
|
|
2784
|
+
className: "dsh-ig-tag",
|
|
2785
|
+
children: [
|
|
2786
|
+
currentBatch.findIndex((i) => i.id === selected.id) + 1,
|
|
2787
|
+
" / ",
|
|
2788
|
+
currentBatch.length
|
|
2789
|
+
]
|
|
2790
|
+
}),
|
|
2258
2791
|
selected.attachment.width && selected.attachment.height ? (0, react_jsx_runtime.jsxs)("span", {
|
|
2259
2792
|
className: "dsh-ig-tag",
|
|
2260
2793
|
children: [
|
|
@@ -2272,6 +2805,25 @@ window.__ModuleLoader__.load({
|
|
|
2272
2805
|
children: (0, react_jsx_runtime.jsx)(X, { size: 18 })
|
|
2273
2806
|
})]
|
|
2274
2807
|
}),
|
|
2808
|
+
currentBatch && currentBatch.length > 1 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("button", {
|
|
2809
|
+
type: "button",
|
|
2810
|
+
className: "dsh-ig-lightbox-nav is-prev",
|
|
2811
|
+
onClick: (e) => {
|
|
2812
|
+
e.stopPropagation();
|
|
2813
|
+
stepLightbox(-1);
|
|
2814
|
+
},
|
|
2815
|
+
title: "上一张",
|
|
2816
|
+
children: (0, react_jsx_runtime.jsx)(ChevronLeft, { size: 24 })
|
|
2817
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
2818
|
+
type: "button",
|
|
2819
|
+
className: "dsh-ig-lightbox-nav is-next",
|
|
2820
|
+
onClick: (e) => {
|
|
2821
|
+
e.stopPropagation();
|
|
2822
|
+
stepLightbox(1);
|
|
2823
|
+
},
|
|
2824
|
+
title: "下一张",
|
|
2825
|
+
children: (0, react_jsx_runtime.jsx)(ChevronRight, { size: 24 })
|
|
2826
|
+
})] }),
|
|
2275
2827
|
(0, react_jsx_runtime.jsx)("div", {
|
|
2276
2828
|
className: "dsh-ig-lightbox-img-wrap",
|
|
2277
2829
|
onClick: (e) => e.stopPropagation(),
|
|
@@ -2322,6 +2874,14 @@ window.__ModuleLoader__.load({
|
|
|
2322
2874
|
onClick: downloadSelected,
|
|
2323
2875
|
children: [(0, react_jsx_runtime.jsx)(Download, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("download") })]
|
|
2324
2876
|
}),
|
|
2877
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
2878
|
+
type: "button",
|
|
2879
|
+
className: `dsh-ig-lightbox-btn ${saveSelectionComplete ? "is-saved" : ""}`,
|
|
2880
|
+
title: saveButtonLabel,
|
|
2881
|
+
onClick: () => void handleSaveToGallery(),
|
|
2882
|
+
disabled: saveSelectionComplete,
|
|
2883
|
+
children: [saveSelectionComplete ? (0, react_jsx_runtime.jsx)(Check, { size: 14 }) : (0, react_jsx_runtime.jsx)(BookmarkPlus, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: saveButtonLabel })]
|
|
2884
|
+
}),
|
|
2325
2885
|
(0, react_jsx_runtime.jsxs)("button", {
|
|
2326
2886
|
type: "button",
|
|
2327
2887
|
className: "dsh-ig-lightbox-btn",
|
|
@@ -2388,6 +2948,39 @@ window.__ModuleLoader__.load({
|
|
|
2388
2948
|
]
|
|
2389
2949
|
});
|
|
2390
2950
|
};
|
|
2951
|
+
const BatchCanvasItem = ({ item, index, badge, isActive, isChecked, onSelect }) => {
|
|
2952
|
+
const image = useAttachmentImage(item.attachment, true);
|
|
2953
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
2954
|
+
className: `dsh-ig-canvas-item ${isActive ? "is-active" : ""} ${isChecked ? "is-checked" : ""}`,
|
|
2955
|
+
onClick: onSelect,
|
|
2956
|
+
children: [
|
|
2957
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
2958
|
+
className: `dsh-ig-canvas-badge ${badge ? "is-model" : ""}`,
|
|
2959
|
+
title: badge,
|
|
2960
|
+
children: badge ?? `#${index + 1}`
|
|
2961
|
+
}),
|
|
2962
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
2963
|
+
className: "dsh-ig-canvas-check",
|
|
2964
|
+
"aria-hidden": "true",
|
|
2965
|
+
children: isChecked ? (0, react_jsx_runtime.jsx)(Check, { size: 14 }) : null
|
|
2966
|
+
}),
|
|
2967
|
+
image.url !== null ? (0, react_jsx_runtime.jsx)("img", {
|
|
2968
|
+
src: image.url,
|
|
2969
|
+
alt: item.prompt,
|
|
2970
|
+
draggable: false
|
|
2971
|
+
}) : image.loading ? (0, react_jsx_runtime.jsx)("div", {
|
|
2972
|
+
className: "dsh-ig-canvas-item-loading",
|
|
2973
|
+
children: (0, react_jsx_runtime.jsx)(LoaderCircle, {
|
|
2974
|
+
className: "dsh-ig-spin",
|
|
2975
|
+
size: 24
|
|
2976
|
+
})
|
|
2977
|
+
}) : (0, react_jsx_runtime.jsx)("div", {
|
|
2978
|
+
className: "dsh-ig-canvas-item-loading",
|
|
2979
|
+
children: (0, react_jsx_runtime.jsx)(ImagePlus, { size: 24 })
|
|
2980
|
+
})
|
|
2981
|
+
]
|
|
2982
|
+
});
|
|
2983
|
+
};
|
|
2391
2984
|
const FieldSelect = ({ label, value, options, onChange }) => (0, react_jsx_runtime.jsxs)("label", {
|
|
2392
2985
|
className: "dsh-ig-field-select",
|
|
2393
2986
|
children: [(0, react_jsx_runtime.jsx)("span", { children: label }), (0, react_jsx_runtime.jsx)("select", {
|
|
@@ -2399,6 +2992,94 @@ window.__ModuleLoader__.load({
|
|
|
2399
2992
|
}, option.value))
|
|
2400
2993
|
})]
|
|
2401
2994
|
});
|
|
2995
|
+
const LOCALIZED_RATIOS = {
|
|
2996
|
+
auto: {
|
|
2997
|
+
zh: "自动",
|
|
2998
|
+
en: "Auto"
|
|
2999
|
+
},
|
|
3000
|
+
"1:1": {
|
|
3001
|
+
zh: "1:1 方形",
|
|
3002
|
+
en: "1:1 Square"
|
|
3003
|
+
},
|
|
3004
|
+
"3:2": {
|
|
3005
|
+
zh: "3:2 横向",
|
|
3006
|
+
en: "3:2 Landscape"
|
|
3007
|
+
},
|
|
3008
|
+
"2:3": {
|
|
3009
|
+
zh: "2:3 肖像",
|
|
3010
|
+
en: "2:3 Portrait"
|
|
3011
|
+
},
|
|
3012
|
+
"4:3": {
|
|
3013
|
+
zh: "4:3 横向",
|
|
3014
|
+
en: "4:3 Landscape"
|
|
3015
|
+
},
|
|
3016
|
+
"3:4": {
|
|
3017
|
+
zh: "3:4 竖向",
|
|
3018
|
+
en: "3:4 Portrait"
|
|
3019
|
+
},
|
|
3020
|
+
"16:9": {
|
|
3021
|
+
zh: "16:9 宽屏",
|
|
3022
|
+
en: "16:9 Widescreen"
|
|
3023
|
+
},
|
|
3024
|
+
"9:16": {
|
|
3025
|
+
zh: "9:16 竖屏",
|
|
3026
|
+
en: "9:16 Portrait"
|
|
3027
|
+
}
|
|
3028
|
+
};
|
|
3029
|
+
const LOCALIZED_QUALITIES = {
|
|
3030
|
+
standard: {
|
|
3031
|
+
zh: "标准(推荐)",
|
|
3032
|
+
en: "Standard (Recommended)"
|
|
3033
|
+
},
|
|
3034
|
+
auto: {
|
|
3035
|
+
zh: "模型自动",
|
|
3036
|
+
en: "Model Auto"
|
|
3037
|
+
}
|
|
3038
|
+
};
|
|
3039
|
+
function localizeRatioOptions(options, lang) {
|
|
3040
|
+
return options.map((opt) => ({
|
|
3041
|
+
value: opt.value,
|
|
3042
|
+
label: LOCALIZED_RATIOS[opt.value]?.[lang] ?? opt.label
|
|
3043
|
+
}));
|
|
3044
|
+
}
|
|
3045
|
+
function localizeQualityOptions(options, lang) {
|
|
3046
|
+
return options.map((opt) => ({
|
|
3047
|
+
value: opt.value,
|
|
3048
|
+
label: LOCALIZED_QUALITIES[opt.value]?.[lang] ?? opt.label
|
|
3049
|
+
}));
|
|
3050
|
+
}
|
|
3051
|
+
function comparisonRatioOptions(lang) {
|
|
3052
|
+
return [
|
|
3053
|
+
"1:1",
|
|
3054
|
+
"3:2",
|
|
3055
|
+
"2:3",
|
|
3056
|
+
"16:9",
|
|
3057
|
+
"9:16"
|
|
3058
|
+
].map((value) => ({
|
|
3059
|
+
value,
|
|
3060
|
+
label: LOCALIZED_RATIOS[value]?.[lang] ?? value
|
|
3061
|
+
}));
|
|
3062
|
+
}
|
|
3063
|
+
function comparisonQualityOptions(lang) {
|
|
3064
|
+
return [
|
|
3065
|
+
{
|
|
3066
|
+
value: "standard",
|
|
3067
|
+
label: lang === "zh" ? "标准" : "Standard"
|
|
3068
|
+
},
|
|
3069
|
+
{
|
|
3070
|
+
value: "1K",
|
|
3071
|
+
label: "1K"
|
|
3072
|
+
},
|
|
3073
|
+
{
|
|
3074
|
+
value: "2K",
|
|
3075
|
+
label: "2K"
|
|
3076
|
+
},
|
|
3077
|
+
{
|
|
3078
|
+
value: "4K",
|
|
3079
|
+
label: "4K"
|
|
3080
|
+
}
|
|
3081
|
+
];
|
|
3082
|
+
}
|
|
2402
3083
|
const DetailsPanel = ({ item, t }) => item === null ? (0, react_jsx_runtime.jsxs)("div", {
|
|
2403
3084
|
className: "dsh-ig-details-empty",
|
|
2404
3085
|
children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 28 }), (0, react_jsx_runtime.jsx)("span", { children: t("selectHistory") })]
|
|
@@ -2528,6 +3209,1036 @@ window.__ModuleLoader__.load({
|
|
|
2528
3209
|
return error instanceof Error ? error.message : String(error);
|
|
2529
3210
|
}
|
|
2530
3211
|
//#endregion
|
|
3212
|
+
//#region lib/types/client/inspiration-image-cache.js
|
|
3213
|
+
/** Persistent, bounded browser cache for publicly proxied inspiration images. */
|
|
3214
|
+
const DB_NAME$1 = "dsh-image-gen-inspiration";
|
|
3215
|
+
const STORE$1 = "images";
|
|
3216
|
+
const CATALOG_STORE = "catalog";
|
|
3217
|
+
const MAX_ENTRIES = 120;
|
|
3218
|
+
const MAX_BYTES = 188743680;
|
|
3219
|
+
const pending = /* @__PURE__ */ new Map();
|
|
3220
|
+
let cacheEpoch = 0;
|
|
3221
|
+
function fetchInspirationImage(sourceId, caseId) {
|
|
3222
|
+
const key = `${sourceId}:${caseId}`;
|
|
3223
|
+
const existing = pending.get(key);
|
|
3224
|
+
if (existing !== void 0) return existing;
|
|
3225
|
+
const requestEpoch = cacheEpoch;
|
|
3226
|
+
const task = getCached(key).then(async (cached) => {
|
|
3227
|
+
if (cached !== void 0) return cached;
|
|
3228
|
+
const response = await fetch(`${INSPIRATION_ROUTE}/image/${encodeURIComponent(sourceId)}/${encodeURIComponent(caseId)}`, {
|
|
3229
|
+
credentials: "same-origin",
|
|
3230
|
+
headers: { accept: "image/avif,image/webp,image/png,image/jpeg" }
|
|
3231
|
+
});
|
|
3232
|
+
if (!response.ok) throw new Error(`灵感图片读取失败 (${String(response.status)})`);
|
|
3233
|
+
const blob = await response.blob();
|
|
3234
|
+
if (blob.size < 64 || ![
|
|
3235
|
+
"image/jpeg",
|
|
3236
|
+
"image/png",
|
|
3237
|
+
"image/webp"
|
|
3238
|
+
].includes(blob.type)) throw new Error("灵感图片格式不受支持");
|
|
3239
|
+
if (requestEpoch === cacheEpoch) putCached(key, blob);
|
|
3240
|
+
return blob;
|
|
3241
|
+
}).finally(() => pending.delete(key));
|
|
3242
|
+
pending.set(key, task);
|
|
3243
|
+
return task;
|
|
3244
|
+
}
|
|
3245
|
+
async function evictInspirationImage(sourceId, caseId) {
|
|
3246
|
+
const key = `${sourceId}:${caseId}`;
|
|
3247
|
+
pending.delete(key);
|
|
3248
|
+
const db = await openDatabase$1();
|
|
3249
|
+
if (db === void 0) return;
|
|
3250
|
+
try {
|
|
3251
|
+
await new Promise((resolve) => {
|
|
3252
|
+
const req = db.transaction(STORE$1, "readwrite").objectStore(STORE$1).delete(key);
|
|
3253
|
+
req.onsuccess = () => resolve();
|
|
3254
|
+
req.onerror = () => resolve();
|
|
3255
|
+
});
|
|
3256
|
+
} finally {
|
|
3257
|
+
db.close();
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
function openDatabase$1() {
|
|
3261
|
+
if (typeof indexedDB === "undefined") return Promise.resolve(void 0);
|
|
3262
|
+
return new Promise((resolve) => {
|
|
3263
|
+
try {
|
|
3264
|
+
const request = indexedDB.open(DB_NAME$1, 2);
|
|
3265
|
+
request.onupgradeneeded = () => {
|
|
3266
|
+
const db = request.result;
|
|
3267
|
+
if (!db.objectStoreNames.contains(STORE$1)) db.createObjectStore(STORE$1, { keyPath: "key" }).createIndex("accessedAt", "accessedAt");
|
|
3268
|
+
if (!db.objectStoreNames.contains(CATALOG_STORE)) db.createObjectStore(CATALOG_STORE, { keyPath: "id" });
|
|
3269
|
+
};
|
|
3270
|
+
request.onsuccess = () => resolve(request.result);
|
|
3271
|
+
request.onerror = () => resolve(void 0);
|
|
3272
|
+
} catch {
|
|
3273
|
+
resolve(void 0);
|
|
3274
|
+
}
|
|
3275
|
+
});
|
|
3276
|
+
}
|
|
3277
|
+
async function getCached(key) {
|
|
3278
|
+
const db = await openDatabase$1();
|
|
3279
|
+
if (db === void 0) return void 0;
|
|
3280
|
+
try {
|
|
3281
|
+
return await new Promise((resolve) => {
|
|
3282
|
+
const store = db.transaction(STORE$1, "readwrite").objectStore(STORE$1);
|
|
3283
|
+
const request = store.get(key);
|
|
3284
|
+
request.onsuccess = () => {
|
|
3285
|
+
const entry = request.result;
|
|
3286
|
+
if (entry === void 0 || !(entry.blob instanceof Blob) || entry.blob.size < 64) {
|
|
3287
|
+
if (entry !== void 0) try {
|
|
3288
|
+
store.delete(key);
|
|
3289
|
+
} catch {}
|
|
3290
|
+
return resolve(void 0);
|
|
3291
|
+
}
|
|
3292
|
+
entry.accessedAt = Date.now();
|
|
3293
|
+
store.put(entry);
|
|
3294
|
+
resolve(entry.blob);
|
|
3295
|
+
};
|
|
3296
|
+
request.onerror = () => resolve(void 0);
|
|
3297
|
+
});
|
|
3298
|
+
} finally {
|
|
3299
|
+
db.close();
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
async function putCached(key, blob) {
|
|
3303
|
+
const db = await openDatabase$1();
|
|
3304
|
+
if (db === void 0) return;
|
|
3305
|
+
try {
|
|
3306
|
+
await new Promise((resolve) => {
|
|
3307
|
+
const tx = db.transaction(STORE$1, "readwrite");
|
|
3308
|
+
tx.objectStore(STORE$1).put({
|
|
3309
|
+
key,
|
|
3310
|
+
blob,
|
|
3311
|
+
bytes: blob.size,
|
|
3312
|
+
accessedAt: Date.now()
|
|
3313
|
+
});
|
|
3314
|
+
tx.oncomplete = () => resolve();
|
|
3315
|
+
tx.onerror = () => resolve();
|
|
3316
|
+
tx.onabort = () => resolve();
|
|
3317
|
+
});
|
|
3318
|
+
await trimCache(db);
|
|
3319
|
+
} finally {
|
|
3320
|
+
db.close();
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
async function trimCache(db) {
|
|
3324
|
+
await new Promise((resolve) => {
|
|
3325
|
+
const tx = db.transaction(STORE$1, "readwrite");
|
|
3326
|
+
const store = tx.objectStore(STORE$1);
|
|
3327
|
+
const items = [];
|
|
3328
|
+
const cursor = store.openCursor();
|
|
3329
|
+
cursor.onsuccess = () => {
|
|
3330
|
+
const result = cursor.result;
|
|
3331
|
+
if (result !== null) {
|
|
3332
|
+
items.push(result.value);
|
|
3333
|
+
result.continue();
|
|
3334
|
+
return;
|
|
3335
|
+
}
|
|
3336
|
+
items.sort((a, b) => a.accessedAt - b.accessedAt);
|
|
3337
|
+
let count = items.length;
|
|
3338
|
+
let bytes = items.reduce((total, item) => total + Math.max(0, item.bytes), 0);
|
|
3339
|
+
for (const item of items) {
|
|
3340
|
+
if (count <= MAX_ENTRIES && bytes <= MAX_BYTES) break;
|
|
3341
|
+
store.delete(item.key);
|
|
3342
|
+
count -= 1;
|
|
3343
|
+
bytes -= Math.max(0, item.bytes);
|
|
3344
|
+
}
|
|
3345
|
+
};
|
|
3346
|
+
tx.oncomplete = () => resolve();
|
|
3347
|
+
tx.onerror = () => resolve();
|
|
3348
|
+
tx.onabort = () => resolve();
|
|
3349
|
+
});
|
|
3350
|
+
}
|
|
3351
|
+
async function clearInspirationImageCache() {
|
|
3352
|
+
cacheEpoch += 1;
|
|
3353
|
+
pending.clear();
|
|
3354
|
+
const db = await openDatabase$1();
|
|
3355
|
+
if (db === void 0) return;
|
|
3356
|
+
try {
|
|
3357
|
+
await new Promise((resolve) => {
|
|
3358
|
+
const tx = db.transaction(STORE$1, "readwrite");
|
|
3359
|
+
tx.objectStore(STORE$1).clear();
|
|
3360
|
+
tx.oncomplete = () => resolve();
|
|
3361
|
+
tx.onerror = () => resolve();
|
|
3362
|
+
tx.onabort = () => resolve();
|
|
3363
|
+
});
|
|
3364
|
+
} finally {
|
|
3365
|
+
db.close();
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
//#endregion
|
|
3369
|
+
//#region lib/types/client/inspiration-catalog-cache.js
|
|
3370
|
+
const DB_NAME = "dsh-image-gen-inspiration";
|
|
3371
|
+
const STORE = "catalog";
|
|
3372
|
+
const IMAGE_STORE = "images";
|
|
3373
|
+
const KEY = "latest";
|
|
3374
|
+
async function loadCachedInspirationCatalog() {
|
|
3375
|
+
const db = await openDatabase();
|
|
3376
|
+
if (db === void 0) return void 0;
|
|
3377
|
+
try {
|
|
3378
|
+
return await new Promise((resolve) => {
|
|
3379
|
+
const request = db.transaction(STORE, "readonly").objectStore(STORE).get(KEY);
|
|
3380
|
+
request.onsuccess = () => {
|
|
3381
|
+
const record = request.result;
|
|
3382
|
+
resolve(isCatalog(record?.catalog) ? record.catalog : void 0);
|
|
3383
|
+
};
|
|
3384
|
+
request.onerror = () => resolve(void 0);
|
|
3385
|
+
});
|
|
3386
|
+
} finally {
|
|
3387
|
+
db.close();
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
async function saveCachedInspirationCatalog(catalog) {
|
|
3391
|
+
if (!isCatalog(catalog)) return;
|
|
3392
|
+
const db = await openDatabase();
|
|
3393
|
+
if (db === void 0) return;
|
|
3394
|
+
try {
|
|
3395
|
+
await new Promise((resolve) => {
|
|
3396
|
+
const tx = db.transaction(STORE, "readwrite");
|
|
3397
|
+
tx.objectStore(STORE).put({
|
|
3398
|
+
id: KEY,
|
|
3399
|
+
catalog,
|
|
3400
|
+
storedAt: Date.now()
|
|
3401
|
+
});
|
|
3402
|
+
tx.oncomplete = () => resolve();
|
|
3403
|
+
tx.onerror = () => resolve();
|
|
3404
|
+
tx.onabort = () => resolve();
|
|
3405
|
+
});
|
|
3406
|
+
} finally {
|
|
3407
|
+
db.close();
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
function openDatabase() {
|
|
3411
|
+
if (typeof indexedDB === "undefined") return Promise.resolve(void 0);
|
|
3412
|
+
return new Promise((resolve) => {
|
|
3413
|
+
try {
|
|
3414
|
+
const request = indexedDB.open(DB_NAME, 2);
|
|
3415
|
+
request.onupgradeneeded = () => {
|
|
3416
|
+
const db = request.result;
|
|
3417
|
+
if (!db.objectStoreNames.contains(STORE)) db.createObjectStore(STORE, { keyPath: "id" });
|
|
3418
|
+
if (!db.objectStoreNames.contains(IMAGE_STORE)) db.createObjectStore(IMAGE_STORE, { keyPath: "key" }).createIndex("accessedAt", "accessedAt");
|
|
3419
|
+
};
|
|
3420
|
+
request.onsuccess = () => resolve(request.result);
|
|
3421
|
+
request.onerror = () => resolve(void 0);
|
|
3422
|
+
} catch {
|
|
3423
|
+
resolve(void 0);
|
|
3424
|
+
}
|
|
3425
|
+
});
|
|
3426
|
+
}
|
|
3427
|
+
function isCatalog(value) {
|
|
3428
|
+
if (typeof value !== "object" || value === null || value.schemaVersion !== 1) return false;
|
|
3429
|
+
const sources = value.sources;
|
|
3430
|
+
return Array.isArray(sources) && sources.length > 0 && sources.every((source) => typeof source === "object" && source !== null && Array.isArray(source.cases));
|
|
3431
|
+
}
|
|
3432
|
+
//#endregion
|
|
3433
|
+
//#region lib/types/client/inspiration-view.js
|
|
3434
|
+
const FAVORITES_STORAGE_KEY = "dsh-ig-inspiration-favorites";
|
|
3435
|
+
function loadInspirationFavorites() {
|
|
3436
|
+
if (typeof localStorage === "undefined") return /* @__PURE__ */ new Set();
|
|
3437
|
+
try {
|
|
3438
|
+
const raw = localStorage.getItem(FAVORITES_STORAGE_KEY);
|
|
3439
|
+
if (raw) {
|
|
3440
|
+
const parsed = JSON.parse(raw);
|
|
3441
|
+
if (Array.isArray(parsed)) return new Set(parsed);
|
|
3442
|
+
}
|
|
3443
|
+
} catch {}
|
|
3444
|
+
return /* @__PURE__ */ new Set();
|
|
3445
|
+
}
|
|
3446
|
+
function saveInspirationFavorites(favorites) {
|
|
3447
|
+
if (typeof localStorage === "undefined") return;
|
|
3448
|
+
try {
|
|
3449
|
+
localStorage.setItem(FAVORITES_STORAGE_KEY, JSON.stringify(Array.from(favorites)));
|
|
3450
|
+
} catch {}
|
|
3451
|
+
}
|
|
3452
|
+
const TAG_MAP_ZH = {
|
|
3453
|
+
"Architecture & Spaces": "建筑空间",
|
|
3454
|
+
"Brand & Logos": "品牌 Logo",
|
|
3455
|
+
"Characters & People": "角色人物",
|
|
3456
|
+
"Charts & Infographics": "图表信息图",
|
|
3457
|
+
"Documents & Publishing": "文档出版",
|
|
3458
|
+
"History & Classical Themes": "历史古典",
|
|
3459
|
+
"Illustration & Art": "插画艺术",
|
|
3460
|
+
"Other Use Cases": "其他案例",
|
|
3461
|
+
"Photography & Realism": "摄影写真",
|
|
3462
|
+
"Posters & Typography": "海报排版",
|
|
3463
|
+
"Products & E-commerce": "产品电商",
|
|
3464
|
+
"Scenes & Storytelling": "场景故事",
|
|
3465
|
+
"UI & Interfaces": "UI 界面",
|
|
3466
|
+
"3D": "3D 渲染",
|
|
3467
|
+
"Architecture": "建筑空间",
|
|
3468
|
+
"Brand": "品牌视觉",
|
|
3469
|
+
"Character": "角色人物",
|
|
3470
|
+
"Characters": "人物群像",
|
|
3471
|
+
"Charts": "图表图解",
|
|
3472
|
+
"Classical": "古典艺术",
|
|
3473
|
+
"Documents": "文档出版",
|
|
3474
|
+
"History": "历史人文",
|
|
3475
|
+
"Illustration": "插画手绘",
|
|
3476
|
+
"Infographic": "信息图表",
|
|
3477
|
+
"Photography": "摄影写真",
|
|
3478
|
+
"Poster": "海报封面",
|
|
3479
|
+
"Product": "产品设计",
|
|
3480
|
+
"Products": "商业产品",
|
|
3481
|
+
"Realistic": "写实逼真",
|
|
3482
|
+
"Scenes": "场景概念",
|
|
3483
|
+
"UI": "UI 界面",
|
|
3484
|
+
"Commerce": "商业电商",
|
|
3485
|
+
"Creative": "创意设计",
|
|
3486
|
+
"Education": "教育科普",
|
|
3487
|
+
"Fashion": "时尚潮流",
|
|
3488
|
+
"Food": "美食餐饮",
|
|
3489
|
+
"Social": "社媒传播",
|
|
3490
|
+
"Story": "故事剧情",
|
|
3491
|
+
"Tech": "科技未来",
|
|
3492
|
+
"Travel": "旅游出行",
|
|
3493
|
+
"Art": "艺术创作",
|
|
3494
|
+
"Minimalist": "极简主义",
|
|
3495
|
+
"Vintage": "复古胶片",
|
|
3496
|
+
"Cyberpunk": "赛博朋克",
|
|
3497
|
+
"Anime": "二次元日系",
|
|
3498
|
+
"Watercolor": "水彩手绘",
|
|
3499
|
+
"Oil Painting": "油画质感"
|
|
3500
|
+
};
|
|
3501
|
+
function translateTag(name, lang) {
|
|
3502
|
+
if (lang !== "zh") return name;
|
|
3503
|
+
return TAG_MAP_ZH[name] ?? name;
|
|
3504
|
+
}
|
|
3505
|
+
const COPY = {
|
|
3506
|
+
zh: {
|
|
3507
|
+
kicker: "Prompt inspiration",
|
|
3508
|
+
title: "灵感素材",
|
|
3509
|
+
subtitle: "从公开案例中找构图、质感和文字处理,再带回工作台继续调整。",
|
|
3510
|
+
refresh: "检查更新",
|
|
3511
|
+
refreshing: "正在更新…",
|
|
3512
|
+
clearCache: "清理缓存",
|
|
3513
|
+
clearingCache: "正在清理…",
|
|
3514
|
+
cacheCleared: "已清空本地图片缓存",
|
|
3515
|
+
cacheClearFailed: "清理缓存失败",
|
|
3516
|
+
clearCacheRestart: "后端未就绪,请重启 DSH 后生效",
|
|
3517
|
+
clearModalTitle: "清理本地图片缓存?",
|
|
3518
|
+
clearModalDesc: "将删除本地保存的灵感素材图片以释放磁盘空间。案例列表、Prompt 和您的收藏夹不会受到任何影响,后续浏览时会自动重新拉取。",
|
|
3519
|
+
cancel: "取消",
|
|
3520
|
+
confirmClear: "确认清理",
|
|
3521
|
+
allCategories: "全部分类",
|
|
3522
|
+
allStyles: "全部风格",
|
|
3523
|
+
allScenes: "全部场景",
|
|
3524
|
+
search: "搜索案例、Prompt、风格…",
|
|
3525
|
+
results: "找到 {count} 个案例",
|
|
3526
|
+
noResults: "没有匹配的素材,换个关键词或筛选条件试试。",
|
|
3527
|
+
selectHint: "选择一张素材,查看完整 Prompt 并带回工作台。",
|
|
3528
|
+
prompt: "完整 Prompt",
|
|
3529
|
+
copy: "复制 Prompt",
|
|
3530
|
+
copied: "已复制",
|
|
3531
|
+
use: "使用这个 Prompt",
|
|
3532
|
+
source: "查看原来源",
|
|
3533
|
+
loading: "正在读取素材库…",
|
|
3534
|
+
loadFailed: "素材库读取失败,请稍后重试。",
|
|
3535
|
+
copyFailed: "复制失败",
|
|
3536
|
+
retry: "重新加载",
|
|
3537
|
+
imageFailed: "图片暂时无法读取",
|
|
3538
|
+
featured: "精选",
|
|
3539
|
+
updated: "素材已更新({count} 条)",
|
|
3540
|
+
updateFailed: "更新失败,仍在使用当前内置素材。",
|
|
3541
|
+
allLoaded: "已展示全部 {count} 个案例",
|
|
3542
|
+
onlyFavorites: "仅看收藏",
|
|
3543
|
+
noFavorites: "暂无收藏的灵感案例,浏览时点击星标即可收藏。",
|
|
3544
|
+
favorite: "收藏",
|
|
3545
|
+
favorited: "已收藏",
|
|
3546
|
+
zoomHint: "点击放大查看",
|
|
3547
|
+
close: "关闭"
|
|
3548
|
+
},
|
|
3549
|
+
en: {
|
|
3550
|
+
kicker: "Prompt inspiration",
|
|
3551
|
+
title: "Inspiration",
|
|
3552
|
+
subtitle: "Explore public examples, then bring a prompt back to Studio to make it your own.",
|
|
3553
|
+
refresh: "Check updates",
|
|
3554
|
+
refreshing: "Updating…",
|
|
3555
|
+
clearCache: "Clear cache",
|
|
3556
|
+
clearingCache: "Clearing…",
|
|
3557
|
+
cacheCleared: "Local image cache cleared",
|
|
3558
|
+
cacheClearFailed: "Failed to clear cache",
|
|
3559
|
+
clearCacheRestart: "Backend not ready, please restart DSH",
|
|
3560
|
+
clearModalTitle: "Clear local image cache?",
|
|
3561
|
+
clearModalDesc: "This will delete locally cached inspiration images to free up disk space. The catalog, prompts, and your bookmarks will remain intact. Images will be re-fetched on demand.",
|
|
3562
|
+
cancel: "Cancel",
|
|
3563
|
+
confirmClear: "Clear Cache",
|
|
3564
|
+
allCategories: "All categories",
|
|
3565
|
+
allStyles: "All styles",
|
|
3566
|
+
allScenes: "All scenes",
|
|
3567
|
+
search: "Search examples, prompts, styles…",
|
|
3568
|
+
results: "{count} examples",
|
|
3569
|
+
noResults: "No matching examples. Try another keyword or filter.",
|
|
3570
|
+
selectHint: "Choose an example to read its full prompt and use it in Studio.",
|
|
3571
|
+
prompt: "Full prompt",
|
|
3572
|
+
copy: "Copy prompt",
|
|
3573
|
+
copied: "Copied",
|
|
3574
|
+
use: "Use this prompt",
|
|
3575
|
+
source: "View source",
|
|
3576
|
+
loading: "Loading inspiration…",
|
|
3577
|
+
loadFailed: "Could not load the inspiration library.",
|
|
3578
|
+
copyFailed: "Failed to copy",
|
|
3579
|
+
retry: "Retry",
|
|
3580
|
+
imageFailed: "Image is temporarily unavailable",
|
|
3581
|
+
featured: "Featured",
|
|
3582
|
+
updated: "Updated ({count} examples)",
|
|
3583
|
+
updateFailed: "Update failed. The current bundled library is still available.",
|
|
3584
|
+
allLoaded: "All {count} examples displayed",
|
|
3585
|
+
onlyFavorites: "Favorites only",
|
|
3586
|
+
noFavorites: "No favorited examples yet. Click the star icon on any card to save.",
|
|
3587
|
+
favorite: "Favorite",
|
|
3588
|
+
favorited: "Favorited",
|
|
3589
|
+
zoomHint: "Click to zoom in",
|
|
3590
|
+
close: "Close"
|
|
3591
|
+
}
|
|
3592
|
+
};
|
|
3593
|
+
const InspirationView = ({ locale, onUsePrompt }) => {
|
|
3594
|
+
const [language, setLanguage] = (0, react.useState)(() => locale?.getSnapshot?.().active?.startsWith("en") ? "en" : "zh");
|
|
3595
|
+
const [catalog, setCatalog] = (0, react.useState)(null);
|
|
3596
|
+
const [catalogError, setCatalogError] = (0, react.useState)(false);
|
|
3597
|
+
const [search, setSearch] = (0, react.useState)("");
|
|
3598
|
+
const [category, setCategory] = (0, react.useState)("");
|
|
3599
|
+
const [style, setStyle] = (0, react.useState)("");
|
|
3600
|
+
const [scene, setScene] = (0, react.useState)("");
|
|
3601
|
+
const [visibleLimit, setVisibleLimit] = (0, react.useState)(60);
|
|
3602
|
+
const [selected, setSelected] = (0, react.useState)(null);
|
|
3603
|
+
const [refreshing, setRefreshing] = (0, react.useState)(false);
|
|
3604
|
+
const [clearingCache, setClearingCache] = (0, react.useState)(false);
|
|
3605
|
+
const [confirmClearOpen, setConfirmClearOpen] = (0, react.useState)(false);
|
|
3606
|
+
const [copied, setCopied] = (0, react.useState)(false);
|
|
3607
|
+
const [favorites, setFavorites] = (0, react.useState)(() => loadInspirationFavorites());
|
|
3608
|
+
const [onlyFavorites, setOnlyFavorites] = (0, react.useState)(false);
|
|
3609
|
+
const [lightboxCase, setLightboxCase] = (0, react.useState)(null);
|
|
3610
|
+
const [toast, setToast] = (0, react.useState)(null);
|
|
3611
|
+
const toastTimerRef = (0, react.useRef)(null);
|
|
3612
|
+
const sentinelRef = (0, react.useRef)(null);
|
|
3613
|
+
(0, react.useEffect)(() => {
|
|
3614
|
+
if (!locale?.subscribe) return;
|
|
3615
|
+
return locale.subscribe(() => setLanguage(locale.getSnapshot?.().active?.startsWith("en") ? "en" : "zh"));
|
|
3616
|
+
}, [locale]);
|
|
3617
|
+
const t = (key, values) => {
|
|
3618
|
+
let value = COPY[language][key] ?? COPY.zh[key];
|
|
3619
|
+
for (const [name, replacement] of Object.entries(values ?? {})) value = value.replace(`{${name}}`, replacement);
|
|
3620
|
+
return value;
|
|
3621
|
+
};
|
|
3622
|
+
const showToast = (text, isError = false) => {
|
|
3623
|
+
if (toastTimerRef.current !== null) window.clearTimeout(toastTimerRef.current);
|
|
3624
|
+
setToast({
|
|
3625
|
+
text,
|
|
3626
|
+
isError
|
|
3627
|
+
});
|
|
3628
|
+
toastTimerRef.current = window.setTimeout(() => {
|
|
3629
|
+
setToast(null);
|
|
3630
|
+
toastTimerRef.current = null;
|
|
3631
|
+
}, 2400);
|
|
3632
|
+
};
|
|
3633
|
+
const toggleFavorite = (id) => {
|
|
3634
|
+
setFavorites((prev) => {
|
|
3635
|
+
const next = new Set(prev);
|
|
3636
|
+
if (next.has(id)) next.delete(id);
|
|
3637
|
+
else next.add(id);
|
|
3638
|
+
saveInspirationFavorites(next);
|
|
3639
|
+
return next;
|
|
3640
|
+
});
|
|
3641
|
+
};
|
|
3642
|
+
const loadCatalog = async (method = "GET") => {
|
|
3643
|
+
const response = await fetch(`${INSPIRATION_ROUTE}/${method === "GET" ? "catalog" : "refresh"}`, {
|
|
3644
|
+
method,
|
|
3645
|
+
credentials: "same-origin"
|
|
3646
|
+
});
|
|
3647
|
+
const payload = await response.json().catch(() => null);
|
|
3648
|
+
if (!response.ok || payload === null || !("schemaVersion" in payload) || payload.schemaVersion !== 1) throw new Error(payload && "error" in payload && payload.error ? payload.error : t("loadFailed"));
|
|
3649
|
+
return payload;
|
|
3650
|
+
};
|
|
3651
|
+
const retryCatalog = async () => {
|
|
3652
|
+
setCatalogError(false);
|
|
3653
|
+
try {
|
|
3654
|
+
const data = await loadCatalog("GET");
|
|
3655
|
+
setCatalog(data);
|
|
3656
|
+
setSelected((previous) => previous === null ? data.sources[0]?.cases.find((item) => item.featured) ?? data.sources[0]?.cases[0] ?? null : previous);
|
|
3657
|
+
} catch {
|
|
3658
|
+
setCatalogError(true);
|
|
3659
|
+
showToast(t("loadFailed"), true);
|
|
3660
|
+
}
|
|
3661
|
+
};
|
|
3662
|
+
(0, react.useEffect)(() => {
|
|
3663
|
+
let mounted = true;
|
|
3664
|
+
setCatalogError(false);
|
|
3665
|
+
Promise.allSettled([loadCatalog(), loadCachedInspirationCatalog()]).then(([server, cache]) => {
|
|
3666
|
+
if (!mounted) return;
|
|
3667
|
+
const cached = cache.status === "fulfilled" ? cache.value : void 0;
|
|
3668
|
+
const next = resolveActiveCatalog(server.status === "fulfilled" ? server.value : void 0, cached);
|
|
3669
|
+
if (next === void 0) {
|
|
3670
|
+
setCatalogError(true);
|
|
3671
|
+
showToast(t("loadFailed"), true);
|
|
3672
|
+
return;
|
|
3673
|
+
}
|
|
3674
|
+
setCatalogError(false);
|
|
3675
|
+
setCatalog(next);
|
|
3676
|
+
setSelected((previous) => previous === null ? next.sources[0]?.cases.find((item) => item.featured) ?? next.sources[0]?.cases[0] ?? null : previous);
|
|
3677
|
+
});
|
|
3678
|
+
return () => {
|
|
3679
|
+
mounted = false;
|
|
3680
|
+
};
|
|
3681
|
+
}, []);
|
|
3682
|
+
const source = catalog?.sources[0] ?? null;
|
|
3683
|
+
const matchingCases = (0, react.useMemo)(() => {
|
|
3684
|
+
if (source === null) return [];
|
|
3685
|
+
const query = search.trim().toLowerCase();
|
|
3686
|
+
return source.cases.filter((item) => {
|
|
3687
|
+
if (onlyFavorites && !favorites.has(item.id)) return false;
|
|
3688
|
+
if (category && item.category !== category) return false;
|
|
3689
|
+
if (style && !item.styles.includes(style)) return false;
|
|
3690
|
+
if (scene && !item.scenes.includes(scene)) return false;
|
|
3691
|
+
if (!query) return true;
|
|
3692
|
+
const translatedCat = translateTag(item.category, language);
|
|
3693
|
+
const translatedStyles = item.styles.map((s) => translateTag(s, language));
|
|
3694
|
+
const translatedScenes = item.scenes.map((s) => translateTag(s, language));
|
|
3695
|
+
return [
|
|
3696
|
+
item.title,
|
|
3697
|
+
item.prompt,
|
|
3698
|
+
item.category,
|
|
3699
|
+
translatedCat,
|
|
3700
|
+
...item.styles,
|
|
3701
|
+
...translatedStyles,
|
|
3702
|
+
...item.scenes,
|
|
3703
|
+
...translatedScenes
|
|
3704
|
+
].some((value) => value.toLowerCase().includes(query));
|
|
3705
|
+
});
|
|
3706
|
+
}, [
|
|
3707
|
+
source,
|
|
3708
|
+
search,
|
|
3709
|
+
category,
|
|
3710
|
+
style,
|
|
3711
|
+
scene,
|
|
3712
|
+
onlyFavorites,
|
|
3713
|
+
favorites,
|
|
3714
|
+
language
|
|
3715
|
+
]);
|
|
3716
|
+
(0, react.useEffect)(() => setVisibleLimit(60), [
|
|
3717
|
+
search,
|
|
3718
|
+
category,
|
|
3719
|
+
style,
|
|
3720
|
+
scene,
|
|
3721
|
+
onlyFavorites
|
|
3722
|
+
]);
|
|
3723
|
+
const visibleCases = matchingCases.slice(0, visibleLimit);
|
|
3724
|
+
(0, react.useEffect)(() => {
|
|
3725
|
+
const el = sentinelRef.current;
|
|
3726
|
+
if (!el) return;
|
|
3727
|
+
const observer = new IntersectionObserver((entries) => {
|
|
3728
|
+
if (entries.some((e) => e.isIntersecting)) setVisibleLimit((limit) => Math.min(limit + 48, matchingCases.length));
|
|
3729
|
+
}, { rootMargin: "360px" });
|
|
3730
|
+
observer.observe(el);
|
|
3731
|
+
return () => observer.disconnect();
|
|
3732
|
+
}, [matchingCases.length, visibleCases.length]);
|
|
3733
|
+
(0, react.useEffect)(() => {
|
|
3734
|
+
if (lightboxCase === null) return;
|
|
3735
|
+
const onKeyDown = (e) => {
|
|
3736
|
+
if (e.key === "Escape") setLightboxCase(null);
|
|
3737
|
+
};
|
|
3738
|
+
window.addEventListener("keydown", onKeyDown);
|
|
3739
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
3740
|
+
}, [lightboxCase]);
|
|
3741
|
+
const refresh = async () => {
|
|
3742
|
+
if (refreshing) return;
|
|
3743
|
+
setRefreshing(true);
|
|
3744
|
+
try {
|
|
3745
|
+
const next = await loadCatalog("POST");
|
|
3746
|
+
setCatalog(next);
|
|
3747
|
+
saveCachedInspirationCatalog(next);
|
|
3748
|
+
setSelected((previous) => previous === null ? next.sources[0]?.cases[0] ?? null : next.sources[0]?.cases.find((item) => item.id === previous.id) ?? next.sources[0]?.cases[0] ?? null);
|
|
3749
|
+
showToast(t("updated", { count: String(next.sources[0]?.cases.length ?? 0) }));
|
|
3750
|
+
} catch {
|
|
3751
|
+
showToast(t("updateFailed"), true);
|
|
3752
|
+
} finally {
|
|
3753
|
+
setRefreshing(false);
|
|
3754
|
+
}
|
|
3755
|
+
};
|
|
3756
|
+
const clearCache = async () => {
|
|
3757
|
+
if (clearingCache) return;
|
|
3758
|
+
setClearingCache(true);
|
|
3759
|
+
setConfirmClearOpen(false);
|
|
3760
|
+
try {
|
|
3761
|
+
await clearInspirationImageCache();
|
|
3762
|
+
const res = await fetch(`${INSPIRATION_ROUTE}/cache/clear`, {
|
|
3763
|
+
method: "POST",
|
|
3764
|
+
credentials: "same-origin"
|
|
3765
|
+
});
|
|
3766
|
+
if (!res.ok) {
|
|
3767
|
+
if (res.status === 404) throw new Error(t("clearCacheRestart"));
|
|
3768
|
+
throw new Error(`HTTP ${res.status}`);
|
|
3769
|
+
}
|
|
3770
|
+
showToast(t("cacheCleared"));
|
|
3771
|
+
} catch (err) {
|
|
3772
|
+
const msg = err instanceof Error && err.message ? err.message : t("cacheClearFailed");
|
|
3773
|
+
showToast(msg, true);
|
|
3774
|
+
} finally {
|
|
3775
|
+
setClearingCache(false);
|
|
3776
|
+
}
|
|
3777
|
+
};
|
|
3778
|
+
const copyPrompt = async () => {
|
|
3779
|
+
if (selected === null) return;
|
|
3780
|
+
try {
|
|
3781
|
+
await copyText(selected.prompt);
|
|
3782
|
+
setCopied(true);
|
|
3783
|
+
showToast(t("copied"));
|
|
3784
|
+
window.setTimeout(() => setCopied(false), 1800);
|
|
3785
|
+
} catch {
|
|
3786
|
+
showToast(t("copyFailed"), true);
|
|
3787
|
+
}
|
|
3788
|
+
};
|
|
3789
|
+
return (0, react_jsx_runtime.jsxs)("section", {
|
|
3790
|
+
className: "dsh-ig-inspiration",
|
|
3791
|
+
"aria-label": t("title"),
|
|
3792
|
+
children: [
|
|
3793
|
+
(0, react_jsx_runtime.jsxs)("header", {
|
|
3794
|
+
className: "dsh-ig-inspiration-head",
|
|
3795
|
+
children: [(0, react_jsx_runtime.jsxs)("div", { children: [
|
|
3796
|
+
(0, react_jsx_runtime.jsx)("p", {
|
|
3797
|
+
className: "dsh-ig-inspiration-kicker",
|
|
3798
|
+
children: t("kicker")
|
|
3799
|
+
}),
|
|
3800
|
+
(0, react_jsx_runtime.jsx)("h1", { children: t("title") }),
|
|
3801
|
+
(0, react_jsx_runtime.jsx)("p", {
|
|
3802
|
+
className: "dsh-ig-inspiration-subtitle",
|
|
3803
|
+
children: t("subtitle")
|
|
3804
|
+
})
|
|
3805
|
+
] }), (0, react_jsx_runtime.jsxs)("div", {
|
|
3806
|
+
className: "dsh-ig-inspiration-head-actions",
|
|
3807
|
+
children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
3808
|
+
type: "button",
|
|
3809
|
+
className: "dsh-ig-inspiration-clear-cache",
|
|
3810
|
+
onClick: () => setConfirmClearOpen(true),
|
|
3811
|
+
disabled: clearingCache,
|
|
3812
|
+
title: t("clearCache"),
|
|
3813
|
+
children: [clearingCache ? (0, react_jsx_runtime.jsx)(LoaderCircle, {
|
|
3814
|
+
className: "dsh-ig-spin",
|
|
3815
|
+
size: 13
|
|
3816
|
+
}) : (0, react_jsx_runtime.jsx)(Trash2, { size: 13 }), clearingCache ? t("clearingCache") : t("clearCache")]
|
|
3817
|
+
}), (0, react_jsx_runtime.jsxs)("button", {
|
|
3818
|
+
type: "button",
|
|
3819
|
+
className: "dsh-ig-inspiration-refresh",
|
|
3820
|
+
onClick: () => void refresh(),
|
|
3821
|
+
disabled: refreshing,
|
|
3822
|
+
title: t("refresh"),
|
|
3823
|
+
children: [refreshing ? (0, react_jsx_runtime.jsx)(LoaderCircle, {
|
|
3824
|
+
className: "dsh-ig-spin",
|
|
3825
|
+
size: 13
|
|
3826
|
+
}) : (0, react_jsx_runtime.jsx)(Sparkles, { size: 13 }), refreshing ? t("refreshing") : t("refresh")]
|
|
3827
|
+
})]
|
|
3828
|
+
})]
|
|
3829
|
+
}),
|
|
3830
|
+
catalogError ? (0, react_jsx_runtime.jsxs)("div", {
|
|
3831
|
+
className: "dsh-ig-inspiration-empty is-catalog-error",
|
|
3832
|
+
children: [
|
|
3833
|
+
(0, react_jsx_runtime.jsx)(TriangleAlert, { size: 28 }),
|
|
3834
|
+
(0, react_jsx_runtime.jsx)("p", { children: t("loadFailed") }),
|
|
3835
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
3836
|
+
type: "button",
|
|
3837
|
+
className: "dsh-ig-inspiration-retry-btn",
|
|
3838
|
+
onClick: () => void retryCatalog(),
|
|
3839
|
+
children: t("retry")
|
|
3840
|
+
})
|
|
3841
|
+
]
|
|
3842
|
+
}) : source === null ? (0, react_jsx_runtime.jsxs)("div", {
|
|
3843
|
+
className: "dsh-ig-inspiration-empty",
|
|
3844
|
+
children: [(0, react_jsx_runtime.jsx)(LoaderCircle, {
|
|
3845
|
+
className: "dsh-ig-spin",
|
|
3846
|
+
size: 23
|
|
3847
|
+
}), (0, react_jsx_runtime.jsx)("span", { children: t("loading") })]
|
|
3848
|
+
}) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
3849
|
+
className: "dsh-ig-inspiration-toolbar",
|
|
3850
|
+
children: [
|
|
3851
|
+
(0, react_jsx_runtime.jsxs)("label", {
|
|
3852
|
+
className: "dsh-ig-inspiration-search",
|
|
3853
|
+
children: [(0, react_jsx_runtime.jsx)(Search, { size: 15 }), (0, react_jsx_runtime.jsx)("input", {
|
|
3854
|
+
value: search,
|
|
3855
|
+
onChange: (event) => setSearch(event.target.value),
|
|
3856
|
+
placeholder: t("search")
|
|
3857
|
+
})]
|
|
3858
|
+
}),
|
|
3859
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
3860
|
+
type: "button",
|
|
3861
|
+
className: `dsh-ig-inspiration-fav-filter ${onlyFavorites ? "is-active" : ""}`,
|
|
3862
|
+
onClick: () => setOnlyFavorites((prev) => !prev),
|
|
3863
|
+
title: t("onlyFavorites"),
|
|
3864
|
+
children: [(0, react_jsx_runtime.jsx)(Star, {
|
|
3865
|
+
size: 14,
|
|
3866
|
+
className: onlyFavorites ? "fill-star" : ""
|
|
3867
|
+
}), (0, react_jsx_runtime.jsxs)("span", { children: [t("onlyFavorites"), favorites.size > 0 ? ` (${favorites.size})` : ""] })]
|
|
3868
|
+
}),
|
|
3869
|
+
(0, react_jsx_runtime.jsxs)("select", {
|
|
3870
|
+
value: category,
|
|
3871
|
+
onChange: (event) => setCategory(event.target.value),
|
|
3872
|
+
"aria-label": t("allCategories"),
|
|
3873
|
+
children: [(0, react_jsx_runtime.jsx)("option", {
|
|
3874
|
+
value: "",
|
|
3875
|
+
children: t("allCategories")
|
|
3876
|
+
}), source.categories.map((item) => (0, react_jsx_runtime.jsx)("option", {
|
|
3877
|
+
value: item,
|
|
3878
|
+
children: translateTag(item, language)
|
|
3879
|
+
}, item))]
|
|
3880
|
+
}),
|
|
3881
|
+
(0, react_jsx_runtime.jsxs)("select", {
|
|
3882
|
+
value: style,
|
|
3883
|
+
onChange: (event) => setStyle(event.target.value),
|
|
3884
|
+
"aria-label": t("allStyles"),
|
|
3885
|
+
children: [(0, react_jsx_runtime.jsx)("option", {
|
|
3886
|
+
value: "",
|
|
3887
|
+
children: t("allStyles")
|
|
3888
|
+
}), source.styles.map((item) => (0, react_jsx_runtime.jsx)("option", {
|
|
3889
|
+
value: item,
|
|
3890
|
+
children: translateTag(item, language)
|
|
3891
|
+
}, item))]
|
|
3892
|
+
}),
|
|
3893
|
+
(0, react_jsx_runtime.jsxs)("select", {
|
|
3894
|
+
value: scene,
|
|
3895
|
+
onChange: (event) => setScene(event.target.value),
|
|
3896
|
+
"aria-label": t("allScenes"),
|
|
3897
|
+
children: [(0, react_jsx_runtime.jsx)("option", {
|
|
3898
|
+
value: "",
|
|
3899
|
+
children: t("allScenes")
|
|
3900
|
+
}), source.scenes.map((item) => (0, react_jsx_runtime.jsx)("option", {
|
|
3901
|
+
value: item,
|
|
3902
|
+
children: translateTag(item, language)
|
|
3903
|
+
}, item))]
|
|
3904
|
+
})
|
|
3905
|
+
]
|
|
3906
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
3907
|
+
className: "dsh-ig-inspiration-layout",
|
|
3908
|
+
children: [(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
3909
|
+
className: "dsh-ig-inspiration-summary",
|
|
3910
|
+
children: [(0, react_jsx_runtime.jsx)("strong", { children: t("results", { count: String(matchingCases.length) }) }), (0, react_jsx_runtime.jsx)("span", { children: source.version.slice(0, 8) })]
|
|
3911
|
+
}), matchingCases.length === 0 ? (0, react_jsx_runtime.jsx)("div", {
|
|
3912
|
+
className: "dsh-ig-inspiration-empty",
|
|
3913
|
+
children: onlyFavorites ? t("noFavorites") : t("noResults")
|
|
3914
|
+
}) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("div", {
|
|
3915
|
+
className: "dsh-ig-inspiration-grid",
|
|
3916
|
+
children: visibleCases.map((item) => (0, react_jsx_runtime.jsx)(InspirationCard, {
|
|
3917
|
+
item,
|
|
3918
|
+
sourceId: source.id,
|
|
3919
|
+
selected: selected?.id === item.id,
|
|
3920
|
+
isFavorited: favorites.has(item.id),
|
|
3921
|
+
language,
|
|
3922
|
+
featuredLabel: t("featured"),
|
|
3923
|
+
retryLabel: t("retry"),
|
|
3924
|
+
onSelect: () => {
|
|
3925
|
+
setSelected(item);
|
|
3926
|
+
setCopied(false);
|
|
3927
|
+
},
|
|
3928
|
+
onToggleFavorite: () => toggleFavorite(item.id)
|
|
3929
|
+
}, item.id))
|
|
3930
|
+
}), visibleCases.length < matchingCases.length ? (0, react_jsx_runtime.jsx)("div", {
|
|
3931
|
+
ref: sentinelRef,
|
|
3932
|
+
style: {
|
|
3933
|
+
height: 32,
|
|
3934
|
+
margin: "16px 0"
|
|
3935
|
+
}
|
|
3936
|
+
}) : (0, react_jsx_runtime.jsx)("div", {
|
|
3937
|
+
className: "dsh-ig-inspiration-end-hint",
|
|
3938
|
+
children: t("allLoaded", { count: String(matchingCases.length) })
|
|
3939
|
+
})] })] }), (0, react_jsx_runtime.jsx)("aside", {
|
|
3940
|
+
className: "dsh-ig-inspiration-inspector",
|
|
3941
|
+
children: selected === null ? (0, react_jsx_runtime.jsxs)("div", {
|
|
3942
|
+
className: "dsh-ig-inspiration-inspector-empty",
|
|
3943
|
+
children: [(0, react_jsx_runtime.jsx)(Image$1, { size: 28 }), (0, react_jsx_runtime.jsx)("span", { children: t("selectHint") })]
|
|
3944
|
+
}) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
3945
|
+
className: "dsh-ig-inspiration-inspector-image",
|
|
3946
|
+
onClick: () => setLightboxCase({
|
|
3947
|
+
sourceId: source.id,
|
|
3948
|
+
caseId: selected.id,
|
|
3949
|
+
title: selected.title,
|
|
3950
|
+
alt: selected.imageAlt
|
|
3951
|
+
}),
|
|
3952
|
+
title: t("zoomHint"),
|
|
3953
|
+
children: [(0, react_jsx_runtime.jsx)(InspirationImage, {
|
|
3954
|
+
sourceId: source.id,
|
|
3955
|
+
caseId: selected.id,
|
|
3956
|
+
alt: selected.imageAlt,
|
|
3957
|
+
retryLabel: t("retry")
|
|
3958
|
+
}), (0, react_jsx_runtime.jsxs)("span", {
|
|
3959
|
+
className: "dsh-ig-inspiration-inspector-zoom-hint",
|
|
3960
|
+
children: [(0, react_jsx_runtime.jsx)(Maximize2, { size: 11 }), t("zoomHint")]
|
|
3961
|
+
})]
|
|
3962
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
3963
|
+
className: "dsh-ig-inspiration-inspector-body",
|
|
3964
|
+
children: [
|
|
3965
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
3966
|
+
className: "dsh-ig-inspiration-inspector-head",
|
|
3967
|
+
children: [(0, react_jsx_runtime.jsx)("h2", { children: selected.title }), (0, react_jsx_runtime.jsx)("button", {
|
|
3968
|
+
type: "button",
|
|
3969
|
+
className: `dsh-ig-inspiration-inspector-fav ${favorites.has(selected.id) ? "is-favorited" : ""}`,
|
|
3970
|
+
onClick: () => toggleFavorite(selected.id),
|
|
3971
|
+
title: favorites.has(selected.id) ? t("favorited") : t("favorite"),
|
|
3972
|
+
children: (0, react_jsx_runtime.jsx)(Star, {
|
|
3973
|
+
size: 15,
|
|
3974
|
+
className: favorites.has(selected.id) ? "fill-star" : ""
|
|
3975
|
+
})
|
|
3976
|
+
})]
|
|
3977
|
+
}),
|
|
3978
|
+
selected.sourceLabel && (0, react_jsx_runtime.jsx)("p", {
|
|
3979
|
+
className: "dsh-ig-inspiration-origin",
|
|
3980
|
+
children: selected.sourceLabel
|
|
3981
|
+
}),
|
|
3982
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
3983
|
+
className: "dsh-ig-inspiration-tags",
|
|
3984
|
+
children: [(0, react_jsx_runtime.jsx)("span", { children: translateTag(selected.category, language) }), selected.styles.slice(0, 3).map((item) => (0, react_jsx_runtime.jsx)("span", { children: translateTag(item, language) }, item))]
|
|
3985
|
+
}),
|
|
3986
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
3987
|
+
className: "dsh-ig-inspiration-prompt-label",
|
|
3988
|
+
children: [(0, react_jsx_runtime.jsx)("span", { children: t("prompt") }), (0, react_jsx_runtime.jsx)("span", { children: selected.prompt.length })]
|
|
3989
|
+
}),
|
|
3990
|
+
(0, react_jsx_runtime.jsx)("p", {
|
|
3991
|
+
className: "dsh-ig-inspiration-prompt",
|
|
3992
|
+
children: selected.prompt
|
|
3993
|
+
}),
|
|
3994
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
3995
|
+
className: "dsh-ig-inspiration-actions",
|
|
3996
|
+
children: [
|
|
3997
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
3998
|
+
type: "button",
|
|
3999
|
+
onClick: () => void copyPrompt(),
|
|
4000
|
+
children: [copied ? (0, react_jsx_runtime.jsx)(Check, { size: 14 }) : (0, react_jsx_runtime.jsx)(Clipboard, { size: 14 }), copied ? t("copied") : t("copy")]
|
|
4001
|
+
}),
|
|
4002
|
+
selected.sourceUrl ?? selected.githubUrl ? (0, react_jsx_runtime.jsxs)("a", {
|
|
4003
|
+
className: "dsh-ig-inspiration-source-link",
|
|
4004
|
+
href: selected.sourceUrl ?? selected.githubUrl,
|
|
4005
|
+
target: "_blank",
|
|
4006
|
+
rel: "noreferrer",
|
|
4007
|
+
children: [(0, react_jsx_runtime.jsx)(ExternalLink, { size: 14 }), t("source")]
|
|
4008
|
+
}) : (0, react_jsx_runtime.jsx)("span", {}),
|
|
4009
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
4010
|
+
type: "button",
|
|
4011
|
+
className: "dsh-ig-inspiration-use",
|
|
4012
|
+
onClick: () => onUsePrompt(selected.prompt),
|
|
4013
|
+
children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 14 }), t("use")]
|
|
4014
|
+
})
|
|
4015
|
+
]
|
|
4016
|
+
})
|
|
4017
|
+
]
|
|
4018
|
+
})] })
|
|
4019
|
+
})]
|
|
4020
|
+
})] }),
|
|
4021
|
+
lightboxCase !== null && (0, react_jsx_runtime.jsx)("div", {
|
|
4022
|
+
className: "dsh-ig-inspiration-lightbox",
|
|
4023
|
+
onClick: () => setLightboxCase(null),
|
|
4024
|
+
children: (0, react_jsx_runtime.jsxs)("div", {
|
|
4025
|
+
className: "dsh-ig-inspiration-lightbox-content",
|
|
4026
|
+
onClick: (e) => e.stopPropagation(),
|
|
4027
|
+
children: [
|
|
4028
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
4029
|
+
type: "button",
|
|
4030
|
+
className: "dsh-ig-inspiration-lightbox-close",
|
|
4031
|
+
onClick: () => setLightboxCase(null),
|
|
4032
|
+
"aria-label": t("close"),
|
|
4033
|
+
children: (0, react_jsx_runtime.jsx)(X, { size: 18 })
|
|
4034
|
+
}),
|
|
4035
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
4036
|
+
className: "dsh-ig-inspiration-lightbox-img-wrap",
|
|
4037
|
+
children: (0, react_jsx_runtime.jsx)(InspirationImage, {
|
|
4038
|
+
sourceId: lightboxCase.sourceId,
|
|
4039
|
+
caseId: lightboxCase.caseId,
|
|
4040
|
+
alt: lightboxCase.alt,
|
|
4041
|
+
retryLabel: t("retry")
|
|
4042
|
+
})
|
|
4043
|
+
}),
|
|
4044
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
4045
|
+
className: "dsh-ig-inspiration-lightbox-caption",
|
|
4046
|
+
children: lightboxCase.title
|
|
4047
|
+
})
|
|
4048
|
+
]
|
|
4049
|
+
})
|
|
4050
|
+
}),
|
|
4051
|
+
confirmClearOpen && (0, react_jsx_runtime.jsx)("div", {
|
|
4052
|
+
className: "dsh-ig-inspiration-modal-backdrop",
|
|
4053
|
+
onClick: () => setConfirmClearOpen(false),
|
|
4054
|
+
children: (0, react_jsx_runtime.jsxs)("div", {
|
|
4055
|
+
className: "dsh-ig-inspiration-modal",
|
|
4056
|
+
onClick: (e) => e.stopPropagation(),
|
|
4057
|
+
role: "dialog",
|
|
4058
|
+
"aria-modal": "true",
|
|
4059
|
+
children: [
|
|
4060
|
+
(0, react_jsx_runtime.jsx)("h3", { children: t("clearModalTitle") }),
|
|
4061
|
+
(0, react_jsx_runtime.jsx)("p", { children: t("clearModalDesc") }),
|
|
4062
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
4063
|
+
className: "dsh-ig-inspiration-modal-actions",
|
|
4064
|
+
children: [(0, react_jsx_runtime.jsx)("button", {
|
|
4065
|
+
type: "button",
|
|
4066
|
+
className: "dsh-ig-inspiration-modal-btn",
|
|
4067
|
+
onClick: () => setConfirmClearOpen(false),
|
|
4068
|
+
disabled: clearingCache,
|
|
4069
|
+
children: t("cancel")
|
|
4070
|
+
}), (0, react_jsx_runtime.jsxs)("button", {
|
|
4071
|
+
type: "button",
|
|
4072
|
+
className: "dsh-ig-inspiration-modal-btn is-danger",
|
|
4073
|
+
onClick: () => void clearCache(),
|
|
4074
|
+
disabled: clearingCache,
|
|
4075
|
+
children: [clearingCache ? (0, react_jsx_runtime.jsx)(LoaderCircle, {
|
|
4076
|
+
className: "dsh-ig-spin",
|
|
4077
|
+
size: 13
|
|
4078
|
+
}) : null, t("confirmClear")]
|
|
4079
|
+
})]
|
|
4080
|
+
})
|
|
4081
|
+
]
|
|
4082
|
+
})
|
|
4083
|
+
}),
|
|
4084
|
+
toast !== null && (0, react_jsx_runtime.jsxs)("div", {
|
|
4085
|
+
className: `dsh-ig-inspiration-toast ${toast.isError ? "is-error" : "is-success"}`,
|
|
4086
|
+
role: "status",
|
|
4087
|
+
children: [toast.isError ? (0, react_jsx_runtime.jsx)(X, { size: 14 }) : (0, react_jsx_runtime.jsx)(Check, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: toast.text })]
|
|
4088
|
+
})
|
|
4089
|
+
]
|
|
4090
|
+
});
|
|
4091
|
+
};
|
|
4092
|
+
const InspirationCard = ({ item, sourceId, selected, isFavorited, language, featuredLabel, retryLabel, onSelect, onToggleFavorite }) => (0, react_jsx_runtime.jsxs)("button", {
|
|
4093
|
+
type: "button",
|
|
4094
|
+
className: `dsh-ig-inspiration-card ${selected ? "is-selected" : ""}`,
|
|
4095
|
+
onClick: onSelect,
|
|
4096
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
4097
|
+
className: "dsh-ig-inspiration-visual",
|
|
4098
|
+
children: [
|
|
4099
|
+
item.featured && (0, react_jsx_runtime.jsxs)("span", {
|
|
4100
|
+
className: "dsh-ig-inspiration-featured",
|
|
4101
|
+
children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 10 }), featuredLabel]
|
|
4102
|
+
}),
|
|
4103
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
4104
|
+
type: "button",
|
|
4105
|
+
className: `dsh-ig-inspiration-card-star ${isFavorited ? "is-favorited" : ""}`,
|
|
4106
|
+
onClick: (e) => {
|
|
4107
|
+
e.stopPropagation();
|
|
4108
|
+
onToggleFavorite();
|
|
4109
|
+
},
|
|
4110
|
+
"aria-label": "收藏",
|
|
4111
|
+
children: (0, react_jsx_runtime.jsx)(Star, {
|
|
4112
|
+
size: 13,
|
|
4113
|
+
className: isFavorited ? "fill-star" : ""
|
|
4114
|
+
})
|
|
4115
|
+
}),
|
|
4116
|
+
(0, react_jsx_runtime.jsx)(InspirationImage, {
|
|
4117
|
+
sourceId,
|
|
4118
|
+
caseId: item.id,
|
|
4119
|
+
alt: item.imageAlt,
|
|
4120
|
+
retryLabel
|
|
4121
|
+
})
|
|
4122
|
+
]
|
|
4123
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
4124
|
+
className: "dsh-ig-inspiration-card-copy",
|
|
4125
|
+
children: [(0, react_jsx_runtime.jsx)("strong", { children: item.title }), (0, react_jsx_runtime.jsx)("span", { children: translateTag(item.category, language) })]
|
|
4126
|
+
})]
|
|
4127
|
+
});
|
|
4128
|
+
const InspirationImage = ({ sourceId, caseId, alt, retryLabel = "Retry" }) => {
|
|
4129
|
+
const [node, setNode] = (0, react.useState)(null);
|
|
4130
|
+
const [visible, setVisible] = (0, react.useState)(false);
|
|
4131
|
+
const [url, setUrl] = (0, react.useState)(null);
|
|
4132
|
+
const [failed, setFailed] = (0, react.useState)(false);
|
|
4133
|
+
const urlRef = (0, react.useRef)(null);
|
|
4134
|
+
(0, react.useEffect)(() => {
|
|
4135
|
+
if (node === null) return;
|
|
4136
|
+
const observer = new IntersectionObserver((entries) => setVisible(entries.some((entry) => entry.isIntersecting)), { rootMargin: "320px" });
|
|
4137
|
+
observer.observe(node);
|
|
4138
|
+
return () => observer.disconnect();
|
|
4139
|
+
}, [node]);
|
|
4140
|
+
(0, react.useEffect)(() => {
|
|
4141
|
+
if (!visible) return;
|
|
4142
|
+
let active = true;
|
|
4143
|
+
setFailed(false);
|
|
4144
|
+
if (urlRef.current !== null) {
|
|
4145
|
+
URL.revokeObjectURL(urlRef.current);
|
|
4146
|
+
urlRef.current = null;
|
|
4147
|
+
}
|
|
4148
|
+
setUrl(null);
|
|
4149
|
+
fetchInspirationImage(sourceId, caseId).then((blob) => {
|
|
4150
|
+
if (!active) return;
|
|
4151
|
+
const next = URL.createObjectURL(blob);
|
|
4152
|
+
if (urlRef.current !== null) URL.revokeObjectURL(urlRef.current);
|
|
4153
|
+
urlRef.current = next;
|
|
4154
|
+
setUrl(next);
|
|
4155
|
+
}).catch(() => {
|
|
4156
|
+
if (active) setFailed(true);
|
|
4157
|
+
});
|
|
4158
|
+
return () => {
|
|
4159
|
+
active = false;
|
|
4160
|
+
};
|
|
4161
|
+
}, [
|
|
4162
|
+
visible,
|
|
4163
|
+
sourceId,
|
|
4164
|
+
caseId
|
|
4165
|
+
]);
|
|
4166
|
+
(0, react.useEffect)(() => {
|
|
4167
|
+
return () => {
|
|
4168
|
+
if (urlRef.current !== null) {
|
|
4169
|
+
URL.revokeObjectURL(urlRef.current);
|
|
4170
|
+
urlRef.current = null;
|
|
4171
|
+
}
|
|
4172
|
+
};
|
|
4173
|
+
}, []);
|
|
4174
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
4175
|
+
ref: setNode,
|
|
4176
|
+
style: {
|
|
4177
|
+
width: "100%",
|
|
4178
|
+
height: "100%"
|
|
4179
|
+
},
|
|
4180
|
+
children: url !== null && !failed ? (0, react_jsx_runtime.jsx)("img", {
|
|
4181
|
+
src: url,
|
|
4182
|
+
alt,
|
|
4183
|
+
loading: "lazy",
|
|
4184
|
+
onError: () => {
|
|
4185
|
+
setFailed(true);
|
|
4186
|
+
evictInspirationImage(sourceId, caseId);
|
|
4187
|
+
}
|
|
4188
|
+
}) : (0, react_jsx_runtime.jsx)("div", {
|
|
4189
|
+
className: `dsh-ig-inspiration-image-placeholder ${failed ? "is-error" : ""}`,
|
|
4190
|
+
children: failed ? (0, react_jsx_runtime.jsx)("button", {
|
|
4191
|
+
type: "button",
|
|
4192
|
+
className: "dsh-ig-inspiration-retry-btn",
|
|
4193
|
+
onClick: (e) => {
|
|
4194
|
+
e.stopPropagation();
|
|
4195
|
+
setFailed(false);
|
|
4196
|
+
if (urlRef.current !== null) {
|
|
4197
|
+
URL.revokeObjectURL(urlRef.current);
|
|
4198
|
+
urlRef.current = null;
|
|
4199
|
+
}
|
|
4200
|
+
setUrl(null);
|
|
4201
|
+
evictInspirationImage(sourceId, caseId).then(() => fetchInspirationImage(sourceId, caseId)).then((blob) => {
|
|
4202
|
+
const next = URL.createObjectURL(blob);
|
|
4203
|
+
if (urlRef.current !== null) URL.revokeObjectURL(urlRef.current);
|
|
4204
|
+
urlRef.current = next;
|
|
4205
|
+
setUrl(next);
|
|
4206
|
+
}).catch(() => setFailed(true));
|
|
4207
|
+
},
|
|
4208
|
+
children: retryLabel
|
|
4209
|
+
}) : (0, react_jsx_runtime.jsx)(LoaderCircle, {
|
|
4210
|
+
className: "dsh-ig-spin",
|
|
4211
|
+
size: 18
|
|
4212
|
+
})
|
|
4213
|
+
})
|
|
4214
|
+
});
|
|
4215
|
+
};
|
|
4216
|
+
async function copyText(value) {
|
|
4217
|
+
if (navigator.clipboard?.writeText) return navigator.clipboard.writeText(value);
|
|
4218
|
+
const textarea = document.createElement("textarea");
|
|
4219
|
+
textarea.value = value;
|
|
4220
|
+
textarea.style.position = "fixed";
|
|
4221
|
+
textarea.style.opacity = "0";
|
|
4222
|
+
document.body.appendChild(textarea);
|
|
4223
|
+
textarea.select();
|
|
4224
|
+
const copied = document.execCommand("copy");
|
|
4225
|
+
textarea.remove();
|
|
4226
|
+
if (!copied) throw new Error("copy-failed");
|
|
4227
|
+
}
|
|
4228
|
+
function resolveActiveCatalog(bundled, cached) {
|
|
4229
|
+
if (!bundled) return cached;
|
|
4230
|
+
if (!cached) return bundled;
|
|
4231
|
+
const bundledSource = bundled.sources[0];
|
|
4232
|
+
const cachedSource = cached.sources[0];
|
|
4233
|
+
if (!bundledSource) return cached;
|
|
4234
|
+
if (!cachedSource) return bundled;
|
|
4235
|
+
if (bundledSource.version === cachedSource.version) return bundled;
|
|
4236
|
+
const bundledTime = bundledSource.updatedAt ? Date.parse(bundledSource.updatedAt) : NaN;
|
|
4237
|
+
const cachedTime = cachedSource.updatedAt ? Date.parse(cachedSource.updatedAt) : NaN;
|
|
4238
|
+
if (Number.isFinite(bundledTime) && Number.isFinite(cachedTime)) return cachedTime > bundledTime ? cached : bundled;
|
|
4239
|
+
return cachedSource.cases.length > bundledSource.cases.length ? cached : bundled;
|
|
4240
|
+
}
|
|
4241
|
+
//#endregion
|
|
2531
4242
|
//#region lib/types/client/conversation-regenerate.js
|
|
2532
4243
|
/** Build the workbench request that reproduces a conversation image with an edited prompt. */
|
|
2533
4244
|
function conversationRegenerateRequest(image, prompt, remembered) {
|
|
@@ -2592,6 +4303,7 @@ window.__ModuleLoader__.load({
|
|
|
2592
4303
|
zh: {
|
|
2593
4304
|
tabGallery: "图库",
|
|
2594
4305
|
tabStudio: "工作台",
|
|
4306
|
+
tabInspiration: "灵感",
|
|
2595
4307
|
tabFavorites: "收藏",
|
|
2596
4308
|
tabCompare: "对比",
|
|
2597
4309
|
tabTasks: "任务",
|
|
@@ -2673,6 +4385,7 @@ window.__ModuleLoader__.load({
|
|
|
2673
4385
|
en: {
|
|
2674
4386
|
tabGallery: "Gallery",
|
|
2675
4387
|
tabStudio: "Studio",
|
|
4388
|
+
tabInspiration: "Inspiration",
|
|
2676
4389
|
tabFavorites: "Favorites",
|
|
2677
4390
|
tabCompare: "Compare",
|
|
2678
4391
|
tabTasks: "Tasks",
|
|
@@ -2810,6 +4523,7 @@ window.__ModuleLoader__.load({
|
|
|
2810
4523
|
const GalleryViewTab = (props) => {
|
|
2811
4524
|
const { locale, sessionId, useSessions, useWorkspaces } = props;
|
|
2812
4525
|
const [activeTab, setActiveTab] = (0, react.useState)("gallery");
|
|
4526
|
+
const [studioDraft, setStudioDraft] = (0, react.useState)(void 0);
|
|
2813
4527
|
const [items, setItems] = (0, react.useState)([]);
|
|
2814
4528
|
const [search, setSearch] = (0, react.useState)("");
|
|
2815
4529
|
const [selectedProvider, setSelectedProvider] = (0, react.useState)("all");
|
|
@@ -2920,6 +4634,11 @@ window.__ModuleLoader__.load({
|
|
|
2920
4634
|
setToast(null);
|
|
2921
4635
|
}, 2e3);
|
|
2922
4636
|
};
|
|
4637
|
+
const useInspirationPrompt = (0, react.useCallback)((prompt) => {
|
|
4638
|
+
setStudioDraft(prompt);
|
|
4639
|
+
setActiveTab("studio");
|
|
4640
|
+
}, []);
|
|
4641
|
+
const clearStudioDraft = (0, react.useCallback)(() => setStudioDraft(void 0), []);
|
|
2923
4642
|
(0, react.useEffect)(() => {
|
|
2924
4643
|
const seat = document.querySelector("[data-composer-seat]");
|
|
2925
4644
|
if (seat) {
|
|
@@ -3324,6 +5043,12 @@ window.__ModuleLoader__.load({
|
|
|
3324
5043
|
onClick: () => setActiveTab("studio"),
|
|
3325
5044
|
children: [(0, react_jsx_runtime.jsx)(SlidersHorizontal, { size: 15 }), (0, react_jsx_runtime.jsx)("span", { children: t("tabStudio") })]
|
|
3326
5045
|
}),
|
|
5046
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
5047
|
+
type: "button",
|
|
5048
|
+
className: `dsh-ig-studio-tab-btn ${activeTab === "inspiration" ? "is-active" : ""}`,
|
|
5049
|
+
onClick: () => setActiveTab("inspiration"),
|
|
5050
|
+
children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 15 }), (0, react_jsx_runtime.jsx)("span", { children: t("tabInspiration") })]
|
|
5051
|
+
}),
|
|
3327
5052
|
(0, react_jsx_runtime.jsxs)("button", {
|
|
3328
5053
|
type: "button",
|
|
3329
5054
|
className: `dsh-ig-studio-tab-btn ${activeTab === "favorites" ? "is-active" : ""}`,
|
|
@@ -3535,9 +5260,15 @@ window.__ModuleLoader__.load({
|
|
|
3535
5260
|
onBlobLoaded: (b) => blobCache.set(item.id, b),
|
|
3536
5261
|
onToast: showToast
|
|
3537
5262
|
}, item.id))
|
|
5263
|
+
}) : activeTab === "inspiration" ? (0, react_jsx_runtime.jsx)(InspirationView, {
|
|
5264
|
+
locale,
|
|
5265
|
+
onUsePrompt: useInspirationPrompt
|
|
3538
5266
|
}) : (0, react_jsx_runtime.jsx)(StudioView, {
|
|
3539
5267
|
locale,
|
|
3540
|
-
workspace: activeWorkspace
|
|
5268
|
+
workspace: activeWorkspace,
|
|
5269
|
+
initialPrompt: studioDraft,
|
|
5270
|
+
onInitialPromptApplied: clearStudioDraft,
|
|
5271
|
+
onOpenInspiration: () => setActiveTab("inspiration")
|
|
3541
5272
|
})
|
|
3542
5273
|
}),
|
|
3543
5274
|
toast && (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -4103,13 +5834,15 @@ window.__ModuleLoader__.load({
|
|
|
4103
5834
|
.is-configured .dsh-ig-status-item-tag{color:#059669;background:rgba(16,185,129,.14)}
|
|
4104
5835
|
.is-missing .dsh-ig-status-item-tag{color:#dc2626;background:rgba(239,68,68,.14)}
|
|
4105
5836
|
.dsh-ig-canvas{position:relative;flex:1 1 0;height:0;min-height:0;display:flex;align-items:center;justify-content:center;overflow:hidden;padding:24px;touch-action:none;user-select:none;cursor:grab;background-image:linear-gradient(45deg,rgba(114,128,156,.035) 25%,transparent 25%),linear-gradient(-45deg,rgba(114,128,156,.035) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(114,128,156,.035) 75%),linear-gradient(-45deg,transparent 75%,rgba(114,128,156,.035) 75%);background-size:24px 24px;background-position:0 0,0 12px,12px -12px,-12px 0}
|
|
4106
|
-
.dsh-ig-canvas.is-dragging{cursor:grabbing!important}
|
|
5837
|
+
.dsh-ig-canvas.is-dragging,.dsh-ig-canvas.is-dragging *{cursor:grabbing!important}
|
|
4107
5838
|
.dsh-ig-canvas img{display:block;max-width:100%;max-height:100%;width:auto;height:auto;border-radius:10px;box-shadow:0 14px 45px rgba(25,35,63,.18);user-select:none;-webkit-user-drag:none;pointer-events:auto}
|
|
4108
5839
|
.dsh-ig-canvas-empty,.dsh-ig-generating-state{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:12px;color:#929bad;font-size:12px;padding:26px;cursor:default}
|
|
4109
5840
|
.dsh-ig-generating-state strong{font-size:15px;color:var(--foreground,#182033)}.dsh-ig-generating-state span{font-size:11px}.dsh-ig-generation-orbit{width:66px;height:66px;border-radius:50%;display:grid;place-items:center;color:var(--ig-blue);background:rgba(47,100,245,.09);box-shadow:0 0 0 10px rgba(47,100,245,.035);animation:dshIgPulse 1.8s ease-in-out infinite}
|
|
4110
5841
|
.dsh-ig-result-strip{flex:0 0 auto;min-height:52px;padding:8px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--ig-line)}.dsh-ig-result-strip>span{font-size:12px;font-weight:650}.dsh-ig-result-strip>div,.dsh-ig-result-actions>div{display:flex;gap:7px}.dsh-ig-result-strip button,.dsh-ig-result-actions button{height:32px;display:inline-flex;align-items:center;gap:6px;padding:0 10px;border:1px solid var(--ig-line);border-radius:7px;background:var(--background,#fff);font-size:11px;cursor:pointer}.dsh-ig-result-strip button:hover,.dsh-ig-result-actions button:hover{border-color:#9fb2ed;color:var(--ig-blue)}.dsh-ig-result-actions{flex:0 0 auto;min-height:54px;padding:8px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--ig-line)}.dsh-ig-result-actions p{margin:0;min-width:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;font-size:11.5px;line-height:18px;color:#596277}.dsh-ig-result-meta{flex:0 0 auto;min-height:32px;padding:6px 14px;display:flex;gap:13px;align-items:center;flex-wrap:wrap;border-top:1px solid var(--ig-line);font-size:10px;color:#8790a3}.dsh-ig-result-meta span+span:before{content:'·';margin-right:13px}
|
|
4111
5842
|
.dsh-ig-generate-panel{height:100%;display:flex;flex-direction:column;border-left:1px solid var(--ig-line);overflow:hidden}.dsh-ig-panel-tabs{height:48px;flex:0 0 48px;display:grid;grid-template-columns:1fr 1fr;border-bottom:1px solid var(--ig-line);padding:0 14px}.dsh-ig-panel-tabs button{position:relative;border:0;background:transparent;font-size:12px;color:#7c8598;cursor:pointer}.dsh-ig-panel-tabs button.is-active{color:var(--ig-blue);font-weight:650}.dsh-ig-panel-tabs button.is-active:after{content:'';position:absolute;left:20%;right:20%;bottom:-1px;height:2px;background:var(--ig-blue);border-radius:2px 2px 0 0}.dsh-ig-generator-form{flex:1 1 0;height:0;padding:15px;display:flex;flex-direction:column;gap:15px;overflow-x:hidden;overflow-y:auto;min-height:0}.dsh-ig-mode-switch{display:grid;grid-template-columns:1fr 1fr;gap:8px}.dsh-ig-mode-switch button{height:39px;border:1px solid var(--ig-line);border-radius:8px;background:var(--background,#fff);display:flex;align-items:center;justify-content:center;gap:7px;font-size:12px;cursor:pointer}.dsh-ig-mode-switch button.is-active{border-color:#789bff;color:var(--ig-blue);background:rgba(47,100,245,.065);box-shadow:inset 0 0 0 1px rgba(47,100,245,.1)}
|
|
4112
|
-
.dsh-ig-field{position:relative;display:flex;flex-direction:column;gap:7px}.dsh-ig-field>label,.dsh-ig-field-label label{font-size:11.5px;font-weight:630}.dsh-ig-field>label span{font-weight:400;color:#929bad}.dsh-ig-field-label{display:flex;align-items:center;justify-content:space-between}.dsh-ig-field-label b{color:#e14d4d}.dsh-ig-field-label button{border:0;background:transparent;color:#798397;font-size:10.5px;cursor:pointer}.dsh-ig-field textarea{width:100%;height:112px;padding:11px 12px 22px;resize:vertical;border:1px solid var(--ig-line);border-radius:9px;background:var(--background,#fff);font-size:12px;line-height:19px;outline:none}.dsh-ig-field textarea:focus{border-color:var(--ig-blue);box-shadow:0 0 0 3px rgba(47,100,245,.09)}.dsh-ig-field>small{position:absolute;right:9px;bottom:7px;color:#9aa2b2;font-size:9.5px}.dsh-ig-field-grid{display:grid;grid-template-columns:1fr 1fr;gap:9px}.dsh-ig-field-select{display:flex;flex-direction:column;gap:6px;min-width:0}.dsh-ig-field-select>span{font-size:11px;font-weight:620}.dsh-ig-field-select select{width:100%;height:36px;padding:0 26px 0 9px;border:1px solid var(--ig-line);border-radius:8px;background:var(--background,#fff);font-size:11px;outline:none;text-overflow:ellipsis}.dsh-ig-field-select select:focus{border-color:var(--ig-blue)}
|
|
5843
|
+
.dsh-ig-field{position:relative;display:flex;flex-direction:column;gap:7px}.dsh-ig-field>label,.dsh-ig-field-label label{font-size:11.5px;font-weight:630}.dsh-ig-field>label span{font-weight:400;color:#929bad}.dsh-ig-field-label{display:flex;align-items:center;justify-content:space-between}.dsh-ig-field-label b{color:#e14d4d}.dsh-ig-field-label button{border:0;background:transparent;color:#798397;font-size:10.5px;cursor:pointer}.dsh-ig-field textarea{width:100%;height:112px;padding:11px 12px 22px;resize:vertical;border:1px solid var(--ig-line);border-radius:9px;background:var(--background,#fff);font-size:12px;line-height:19px;outline:none}.dsh-ig-field textarea:focus{border-color:var(--ig-blue);box-shadow:0 0 0 3px rgba(47,100,245,.09)}.dsh-ig-field>small{position:absolute;right:9px;bottom:7px;color:#9aa2b2;font-size:9.5px}.dsh-ig-field-grid{display:grid;grid-template-columns:1fr 1fr;gap:9px}.dsh-ig-field-select{display:flex;flex-direction:column;gap:6px;min-width:0}.dsh-ig-field-select>span{font-size:11px;font-weight:620}.dsh-ig-field-select select{width:100%;height:36px;padding:0 26px 0 9px;border:1px solid var(--ig-line);border-radius:8px;background:var(--background,#fff);font-size:11px;outline:none;text-overflow:ellipsis}.dsh-ig-field-select select:focus{border-color:var(--ig-blue)}.dsh-ig-count-row{display:grid;grid-template-columns:repeat(4,1fr);gap:6px}.dsh-ig-count-pill{height:34px;border:1px solid var(--ig-line);border-radius:7px;background:var(--background,#fff);font-size:12px;font-weight:600;color:#596277;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .15s}.dsh-ig-count-pill:hover{border-color:#9fb2ed;color:var(--ig-blue)}.dsh-ig-count-pill.is-active{border-color:var(--ig-blue);background:rgba(47,100,245,.08);color:var(--ig-blue);box-shadow:inset 0 0 0 1px var(--ig-blue)}
|
|
5844
|
+
.dsh-ig-generation-kind{display:grid;grid-template-columns:1fr 1fr;gap:4px;padding:3px;border:1px solid var(--ig-line);border-radius:9px;background:var(--ig-soft)}.dsh-ig-generation-kind button{height:31px;border:0;border-radius:6px;background:transparent;color:#778196;font-size:11.5px;display:flex;align-items:center;justify-content:center;gap:6px;cursor:pointer;transition:background .15s,color .15s,box-shadow .15s}.dsh-ig-generation-kind button.is-active{background:var(--background,#fff);color:var(--ig-blue);font-weight:650;box-shadow:0 2px 8px rgba(30,42,76,.09)}
|
|
5845
|
+
.dsh-ig-compare-box{padding:10px;border:1px solid rgba(47,100,245,.2);border-radius:10px;background:linear-gradient(145deg,rgba(47,100,245,.055),rgba(99,102,241,.018));display:flex;flex-direction:column;gap:9px}.dsh-ig-compare-heading{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.dsh-ig-compare-heading>div{min-width:0;display:flex;flex-direction:column;gap:2px}.dsh-ig-compare-heading strong{font-size:11.5px}.dsh-ig-compare-heading small{font-size:9.5px;color:#838da1}.dsh-ig-compare-heading>span{flex:none;padding:2px 6px;border-radius:999px;background:rgba(47,100,245,.11);color:var(--ig-blue);font-size:9.5px;font-weight:650}.dsh-ig-model-checks{display:flex;flex-direction:column;gap:6px}.dsh-ig-model-checks>button{min-height:43px;width:100%;padding:6px 7px;display:grid;grid-template-columns:18px minmax(0,1fr) auto;align-items:center;gap:7px;border:1px solid var(--ig-line);border-radius:8px;background:var(--background,#fff);text-align:left;cursor:pointer;transition:border-color .15s,background .15s,transform .15s}.dsh-ig-model-checks>button:hover{border-color:#9cb2ef;transform:translateY(-1px)}.dsh-ig-model-checks>button.is-selected{border-color:#86a4fa;background:rgba(47,100,245,.055);box-shadow:inset 3px 0 0 var(--ig-blue)}.dsh-ig-model-checkmark{width:17px;height:17px;border:1px solid #bdc6d8;border-radius:5px;display:grid;place-items:center;color:#fff;background:var(--background,#fff)}.dsh-ig-model-checks>button.is-selected .dsh-ig-model-checkmark{border-color:var(--ig-blue);background:var(--ig-blue)}.dsh-ig-model-copy{display:flex;flex-direction:column;min-width:0}.dsh-ig-model-copy strong{font-size:11px;line-height:15px}.dsh-ig-model-copy small{max-width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#80899b;font-size:9.5px;line-height:14px}.dsh-ig-model-settings{display:flex;flex-direction:column;align-items:flex-end;gap:2px;color:#596277;font-size:9.5px;white-space:nowrap}.dsh-ig-model-settings em{font-style:normal;color:#a16a0a;font-size:8.5px}.dsh-ig-compare-box>p{margin:0;color:#8a7180;font-size:9.5px;line-height:14px}
|
|
4113
5846
|
.dsh-ig-upload{height:92px;border:1px dashed #bcc6d9;border-radius:9px;background:rgba(255,255,255,.45);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;color:#7d879b;cursor:pointer}.dsh-ig-upload strong{font-size:11px;font-weight:570}.dsh-ig-upload small{font-size:9.5px}.dsh-ig-upload:hover,.dsh-ig-upload.is-dragging{border-color:var(--ig-blue);background:rgba(47,100,245,.05);color:var(--ig-blue)}
|
|
4114
5847
|
.dsh-ig-reference-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(64px,1fr));gap:8px}
|
|
4115
5848
|
.dsh-ig-reference-card{position:relative;aspect-ratio:1;border-radius:8px;overflow:hidden;background:#eef1f7;border:1px solid var(--ig-line)}
|
|
@@ -4143,11 +5876,64 @@ window.__ModuleLoader__.load({
|
|
|
4143
5876
|
@media(max-width:900px){.dsh-ig-gallery-page-body.is-workbench{height:auto;overflow:auto}.dsh-ig-workbench{height:auto;min-height:100%;overflow:visible;padding:12px}.dsh-ig-workbench-heading{height:auto;padding-bottom:12px;align-items:center}.dsh-ig-provider-quick>span{display:none}.dsh-ig-workbench-grid{height:auto;display:flex;flex:none;flex-direction:column;overflow:visible}.dsh-ig-recent-panel{height:auto;overflow:hidden;border-right:0;border-bottom:1px solid var(--ig-line)}.dsh-ig-recent-scroll{height:auto;display:flex;overflow-x:auto;overflow-y:hidden;padding:8px}.dsh-ig-recent-item{min-width:220px}.dsh-ig-canvas-column{height:auto;min-height:560px}.dsh-ig-generate-panel{height:auto;border-left:0;border-top:1px solid var(--ig-line);min-height:560px}.dsh-ig-generator-form{height:auto;overflow:visible}.dsh-ig-canvas{height:auto;min-height:360px}}
|
|
4144
5877
|
@media(max-width:560px){.dsh-ig-workbench-heading>div{display:block}.dsh-ig-workbench-heading h1{font-size:15px}.dsh-ig-provider-quick select{min-width:128px;max-width:150px}.dsh-ig-canvas{padding:12px;min-height:300px}.dsh-ig-result-strip,.dsh-ig-result-actions{align-items:flex-start;flex-direction:column}.dsh-ig-result-strip>div,.dsh-ig-result-actions>div{width:100%}.dsh-ig-result-strip button,.dsh-ig-result-actions button{flex:1;justify-content:center}.dsh-ig-result-actions button span{display:inline}.dsh-ig-field-grid{grid-template-columns:1fr}.dsh-ig-result-meta span+span:before{display:none}}
|
|
4145
5878
|
@media(prefers-reduced-motion:reduce){.dsh-ig-workbench *{scroll-behavior:auto!important;animation-duration:.01ms!important;transition-duration:.01ms!important}}
|
|
5879
|
+
.dsh-ig-field-label>span{display:flex;align-items:center;gap:5px}.dsh-ig-field-label .dsh-ig-find-inspiration{display:inline-flex;align-items:center;gap:3px;color:var(--ig-blue);font-weight:600}.dsh-ig-field-label .dsh-ig-find-inspiration:hover{color:#1644c1}
|
|
4146
5880
|
.dsh-ig-workspace-filter-label{height:30px;display:inline-flex;align-items:center;gap:6px;padding:0 10px;border:1px solid var(--ig-line,rgba(0,0,0,.1));border-radius:7px;background:var(--background,#fff);font-size:11.5px;color:var(--foreground,#242f44);cursor:pointer;user-select:none;transition:border-color .15s,background .15s}
|
|
4147
5881
|
.dsh-ig-workspace-filter-label:hover{border-color:#9bb4f5;background:rgba(47,100,245,.04)}
|
|
4148
5882
|
.dsh-ig-workspace-checkbox{cursor:pointer;accent-color:#2f64f5;width:13.5px;height:13.5px;margin:0}
|
|
4149
5883
|
.dsh-ig-workspace-filter-text{font-weight:500;white-space:nowrap}
|
|
4150
5884
|
.dsh-ig-workspace-filter-badge{font-size:10px;font-weight:600;padding:1px 6px;border-radius:999px;background:color-mix(in srgb,currentColor 7%,transparent);color:#64748b;font-variant-numeric:tabular-nums}
|
|
5885
|
+
.dsh-ig-canvas-cluster{display:grid;gap:16px;align-items:center;justify-content:center;user-select:none;pointer-events:auto;flex-shrink:0}
|
|
5886
|
+
.dsh-ig-canvas-cluster[data-count="2"]{grid-template-columns:repeat(2,1fr);max-width:880px}
|
|
5887
|
+
.dsh-ig-canvas-cluster[data-count="3"],.dsh-ig-canvas-cluster[data-count="4"]{grid-template-columns:repeat(2,1fr);max-width:760px}
|
|
5888
|
+
.dsh-ig-canvas-item{position:relative;display:flex;align-items:center;justify-content:center;border-radius:10px;cursor:pointer;transition:transform .12s ease-out}
|
|
5889
|
+
.dsh-ig-canvas-item img{display:block;max-width:100%;max-height:360px;width:auto;height:auto;object-fit:contain;border-radius:10px;box-shadow:0 10px 30px rgba(25,35,63,.16);user-select:none;-webkit-user-drag:none;pointer-events:auto;transition:box-shadow .15s,outline .15s}
|
|
5890
|
+
.dsh-ig-canvas-item:hover img{box-shadow:0 14px 40px rgba(47,100,245,.2)}
|
|
5891
|
+
.dsh-ig-canvas-item.is-active img{box-shadow:0 14px 40px rgba(47,100,245,.28)}
|
|
5892
|
+
.dsh-ig-canvas-item.is-checked img{outline:3px solid var(--ig-blue);outline-offset:2px;box-shadow:0 14px 40px rgba(47,100,245,.35)}
|
|
5893
|
+
.dsh-ig-canvas-badge{position:absolute;top:10px;left:10px;background:rgba(15,23,42,.7);color:#fff;font-size:11px;font-weight:700;padding:2px 7px;border-radius:5px;backdrop-filter:blur(4px);pointer-events:none;transition:background .15s;z-index:2}
|
|
5894
|
+
.dsh-ig-canvas-badge.is-model{max-width:calc(100% - 20px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}
|
|
5895
|
+
.dsh-ig-canvas-item.is-checked .dsh-ig-canvas-badge{background:var(--ig-blue)}
|
|
5896
|
+
.dsh-ig-canvas-check{position:absolute;top:10px;right:10px;width:22px;height:22px;border:1.5px solid rgba(255,255,255,.85);border-radius:50%;display:grid;place-items:center;background:rgba(15,23,42,.48);color:#fff;box-shadow:0 2px 8px rgba(15,23,42,.18);backdrop-filter:blur(4px);z-index:2;pointer-events:none}.dsh-ig-canvas-item.is-checked .dsh-ig-canvas-check{border-color:#fff;background:var(--ig-blue)}
|
|
5897
|
+
.dsh-ig-canvas-item-loading{width:260px;height:260px;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.45);border:1px solid var(--ig-line);border-radius:10px;color:#929bad}
|
|
5898
|
+
.dsh-ig-lightbox-nav{position:absolute;top:50%;transform:translateY(-50%);width:42px;height:42px;border-radius:50%;border:0;background:rgba(15,23,42,.65);color:#fff;display:grid;place-items:center;cursor:pointer;transition:background .15s,transform .15s;z-index:10;backdrop-filter:blur(4px)}
|
|
5899
|
+
.dsh-ig-lightbox-nav:hover{background:rgba(47,100,245,.85);transform:translateY(-50%) scale(1.08)}
|
|
5900
|
+
.dsh-ig-lightbox-nav.is-prev{left:18px}
|
|
5901
|
+
.dsh-ig-lightbox-nav.is-next{right:18px}
|
|
5902
|
+
.dsh-ig-save-gallery-btn{color:var(--ig-blue)!important;border-color:rgba(47,100,245,.35)!important;background:rgba(47,100,245,.06)!important}
|
|
5903
|
+
.dsh-ig-save-gallery-btn:hover{background:rgba(47,100,245,.12)!important}
|
|
5904
|
+
.dsh-ig-save-gallery-btn.is-saved{color:#64748b!important;border-color:var(--ig-line)!important;background:transparent!important}
|
|
5905
|
+
`;
|
|
5906
|
+
//#endregion
|
|
5907
|
+
//#region lib/types/client/inspiration-style.js
|
|
5908
|
+
const INSPIRATION_STYLE = `
|
|
5909
|
+
.dsh-ig-inspiration{--inspiration-blue:#2f64f5;--inspiration-ink:#172033;--inspiration-muted:#718099;--inspiration-line:color-mix(in srgb,currentColor 11%,transparent);min-height:100%;padding:18px 24px 26px;background:radial-gradient(ellipse 80% 45% at 50% -20%,rgba(47,100,245,.12),transparent 70%),var(--dsw-alias-bg-layer-1,#fff);color:var(--foreground,var(--inspiration-ink));box-sizing:border-box}
|
|
5910
|
+
.dsh-ig-inspiration-head{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;margin-bottom:18px}.dsh-ig-inspiration-head-actions{display:flex;align-items:center;gap:8px}.dsh-ig-inspiration-kicker{margin:0 0 4px;color:var(--inspiration-blue);font-size:11px;letter-spacing:.08em;font-weight:700;text-transform:uppercase}.dsh-ig-inspiration-head h1{margin:0;font-size:18px;letter-spacing:-.025em;line-height:1.25}.dsh-ig-inspiration-subtitle{margin:5px 0 0;color:var(--inspiration-muted);font-size:12px;line-height:1.5}.dsh-ig-inspiration-refresh,.dsh-ig-inspiration-clear-cache{height:32px;padding:0 11px;display:inline-flex;align-items:center;gap:6px;border:1px solid var(--inspiration-line);border-radius:7px;background:var(--dsw-alias-bg-layer-1,#fff);color:var(--inspiration-muted);font:inherit;font-size:12px;white-space:nowrap;cursor:pointer;transition:all .15s ease}.dsh-ig-inspiration-refresh:hover:not(:disabled){border-color:rgba(47,100,245,.42);color:var(--inspiration-blue);background:rgba(47,100,245,.04)}.dsh-ig-inspiration-clear-cache:hover:not(:disabled){border-color:rgba(239,68,68,.4);color:#ef4444;background:rgba(239,68,68,.04)}.dsh-ig-inspiration-refresh:disabled,.dsh-ig-inspiration-clear-cache:disabled{opacity:.55;cursor:wait}
|
|
5911
|
+
.dsh-ig-inspiration-toolbar{display:grid;grid-template-columns:minmax(180px,1fr) auto repeat(3,minmax(110px,140px));gap:8px;padding:10px;border:1px solid var(--inspiration-line);border-radius:10px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-1,#fff) 90%,#edf2ff);margin-bottom:16px}.dsh-ig-inspiration-search{min-width:0;position:relative}.dsh-ig-inspiration-search svg{position:absolute;left:10px;top:50%;transform:translateY(-50%);color:#8995aa}.dsh-ig-inspiration-toolbar input,.dsh-ig-inspiration-toolbar select{width:100%;height:34px;box-sizing:border-box;border:1px solid transparent;border-radius:6px;background:var(--dsw-alias-bg-layer-1,#fff);color:inherit;padding:0 9px;font:inherit;font-size:12px;outline:none}.dsh-ig-inspiration-toolbar input{padding-left:31px}.dsh-ig-inspiration-toolbar input:focus,.dsh-ig-inspiration-toolbar select:focus{border-color:var(--inspiration-blue);box-shadow:0 0 0 3px rgba(47,100,245,.1)}
|
|
5912
|
+
.dsh-ig-inspiration-fav-filter{height:34px;padding:0 12px;display:inline-flex;align-items:center;gap:6px;border:1px solid var(--inspiration-line);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#fff);color:var(--inspiration-muted);font:inherit;font-size:12px;cursor:pointer;white-space:nowrap;transition:all .15s ease}.dsh-ig-inspiration-fav-filter:hover{border-color:rgba(47,100,245,.4);color:var(--inspiration-blue)}.dsh-ig-inspiration-fav-filter.is-active{background:#eef3ff;border-color:var(--inspiration-blue);color:var(--inspiration-blue);font-weight:600}.fill-star{fill:currentColor}
|
|
5913
|
+
.dsh-ig-inspiration-layout{display:grid;grid-template-columns:minmax(0,1fr) minmax(260px,310px);gap:18px;align-items:start}.dsh-ig-inspiration-summary{height:24px;display:flex;align-items:center;justify-content:space-between;color:var(--inspiration-muted);font-size:11.5px}.dsh-ig-inspiration-summary strong{color:inherit;font-weight:650}.dsh-ig-inspiration-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(185px,1fr));gap:12px}.dsh-ig-inspiration-card{position:relative;min-width:0;padding:0;border:1px solid var(--inspiration-line);border-radius:10px;overflow:hidden;background:var(--dsw-alias-bg-layer-1,#fff);text-align:left;cursor:pointer;transition:transform .16s ease,border-color .16s ease,box-shadow .16s ease;content-visibility:auto;contain-intrinsic-size:245px}.dsh-ig-inspiration-card:hover,.dsh-ig-inspiration-card.is-selected{border-color:rgba(47,100,245,.58);box-shadow:0 8px 22px rgba(37,62,123,.11)}.dsh-ig-inspiration-card:hover{transform:translateY(-2px)}.dsh-ig-inspiration-card.is-selected:after{content:'';position:absolute;inset:0;border:2px solid var(--inspiration-blue);border-radius:9px;pointer-events:none}.dsh-ig-inspiration-visual{aspect-ratio:1/1;position:relative;background:linear-gradient(135deg,#f0f4fc,#e3eafa);overflow:hidden}.dsh-ig-inspiration-visual img{width:100%;height:100%;object-fit:cover;display:block}.dsh-ig-inspiration-image-placeholder{height:100%;display:grid;place-items:center;color:#8a99b3}.dsh-ig-inspiration-image-placeholder.is-error{font-size:11px;padding:10px;text-align:center}.dsh-ig-inspiration-card-copy{padding:9px 10px 10px}.dsh-ig-inspiration-card-copy strong{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:17px}.dsh-ig-inspiration-card-copy span{display:block;margin-top:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--inspiration-muted);font-size:10.5px}.dsh-ig-inspiration-featured{position:absolute;top:8px;left:8px;display:inline-flex;align-items:center;gap:3px;padding:3px 6px;border-radius:999px;background:rgba(20,31,52,.72);backdrop-filter:blur(5px);color:#fff;font-size:10px;font-weight:600;z-index:1}.dsh-ig-inspiration-card-star{position:absolute;top:8px;right:8px;width:26px;height:26px;border-radius:50%;background:rgba(20,31,52,.6);backdrop-filter:blur(4px);border:none;color:#fff;display:grid;place-items:center;cursor:pointer;z-index:2;opacity:0;transition:opacity .15s,transform .15s}.dsh-ig-inspiration-card:hover .dsh-ig-inspiration-card-star,.dsh-ig-inspiration-card-star.is-favorited{opacity:1}.dsh-ig-inspiration-card-star.is-favorited{color:#f59e0b;background:rgba(20,31,52,.85)}.dsh-ig-inspiration-card-star:hover{transform:scale(1.15)}
|
|
5914
|
+
.dsh-ig-inspiration-empty{min-height:310px;display:grid;place-items:center;padding:30px;text-align:center;color:var(--inspiration-muted);border:1px dashed var(--inspiration-line);border-radius:10px;font-size:12px}
|
|
5915
|
+
.dsh-ig-inspiration-empty.is-catalog-error{display:flex;flex-direction:column;gap:12px;color:var(--inspiration-text)}
|
|
5916
|
+
.dsh-ig-inspiration-empty.is-catalog-error p{margin:0;font-size:13px;color:var(--inspiration-muted)}
|
|
5917
|
+
.dsh-ig-inspiration-toast{position:fixed;bottom:28px;left:50%;transform:translateX(-50%);display:inline-flex;align-items:center;gap:7px;padding:9px 16px;border-radius:999px;background:rgba(15,23,42,.92);backdrop-filter:blur(8px);color:#fff;font-size:12px;font-weight:500;box-shadow:0 10px 30px rgba(0,0,0,.35);z-index:9999;animation:dsh-ig-slide-up .2s ease}
|
|
5918
|
+
.dsh-ig-inspiration-toast.is-error{background:rgba(185,28,28,.92)}
|
|
5919
|
+
@keyframes dsh-ig-slide-up{from{opacity:0;transform:translate(-50%,12px)}to{opacity:1;transform:translate(-50%,0)}}
|
|
5920
|
+
.dsh-ig-inspiration-modal-backdrop{position:fixed;inset:0;z-index:10001;background:rgba(15,23,42,.6);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;padding:16px;box-sizing:border-box}
|
|
5921
|
+
.dsh-ig-inspiration-modal{width:100%;max-width:400px;background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--inspiration-line);border-radius:12px;padding:22px;box-shadow:0 24px 60px rgba(0,0,0,.3);animation:dsh-ig-scale-in .15s ease}
|
|
5922
|
+
@keyframes dsh-ig-scale-in{from{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}
|
|
5923
|
+
.dsh-ig-inspiration-modal h3{margin:0 0 8px;font-size:15px;font-weight:650;color:inherit}
|
|
5924
|
+
.dsh-ig-inspiration-modal p{margin:0 0 20px;color:var(--inspiration-muted);font-size:12.5px;line-height:1.55}
|
|
5925
|
+
.dsh-ig-inspiration-modal-actions{display:flex;justify-content:flex-end;gap:10px}
|
|
5926
|
+
.dsh-ig-inspiration-modal-btn{height:34px;padding:0 14px;border-radius:7px;border:1px solid var(--inspiration-line);background:var(--dsw-alias-bg-layer-1,#fff);color:inherit;font:inherit;font-size:12px;font-weight:600;cursor:pointer;transition:all .15s ease}
|
|
5927
|
+
.dsh-ig-inspiration-modal-btn:hover{background:color-mix(in srgb,currentColor 6%,transparent)}
|
|
5928
|
+
.dsh-ig-inspiration-modal-btn.is-danger{background:#ef4444;border-color:#ef4444;color:#fff}
|
|
5929
|
+
.dsh-ig-inspiration-modal-btn.is-danger:hover{background:#dc2626}
|
|
5930
|
+
.dsh-ig-inspiration-modal-btn:disabled{opacity:.5;cursor:wait}
|
|
5931
|
+
.dsh-ig-inspiration-retry-btn{border:none;background:rgba(47,100,245,.08);color:var(--inspiration-blue);padding:6px 12px;border-radius:6px;font:inherit;font-size:11px;font-weight:600;cursor:pointer;transition:background .15s}
|
|
5932
|
+
.dsh-ig-inspiration-retry-btn:hover{background:rgba(47,100,245,.18)}
|
|
5933
|
+
.dsh-ig-inspiration-end-hint{display:flex;align-items:center;justify-content:center;padding:24px 0 16px;color:var(--inspiration-muted);font-size:12px}
|
|
5934
|
+
.dsh-ig-inspiration-inspector{position:sticky;top:0;overflow:hidden;border:1px solid var(--inspiration-line);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#fff);box-shadow:0 9px 28px rgba(27,45,88,.06)}.dsh-ig-inspiration-inspector-empty{min-height:310px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:9px;padding:22px;color:var(--inspiration-muted);font-size:12px;text-align:center}.dsh-ig-inspiration-inspector-image{aspect-ratio:16/10;background:#f1f4fa;position:relative;cursor:zoom-in}.dsh-ig-inspiration-inspector-image img{width:100%;height:100%;object-fit:cover}.dsh-ig-inspiration-inspector-zoom-hint{position:absolute;bottom:8px;right:8px;display:inline-flex;align-items:center;gap:4px;padding:4px 8px;border-radius:6px;background:rgba(15,23,42,.75);backdrop-filter:blur(5px);color:#fff;font-size:10.5px;font-weight:500;opacity:0;transition:opacity .15s ease;pointer-events:none}.dsh-ig-inspiration-inspector-image:hover .dsh-ig-inspiration-inspector-zoom-hint{opacity:1}.dsh-ig-inspiration-inspector-body{padding:14px}.dsh-ig-inspiration-inspector-head{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.dsh-ig-inspiration-inspector-body h2{margin:0;font-size:14px;line-height:1.4;flex:1}.dsh-ig-inspiration-inspector-fav{width:30px;height:30px;flex-shrink:0;border:1px solid var(--inspiration-line);border-radius:7px;background:var(--dsw-alias-bg-layer-1,#fff);color:var(--inspiration-muted);display:grid;place-items:center;cursor:pointer;transition:all .15s ease}.dsh-ig-inspiration-inspector-fav:hover{border-color:rgba(245,158,11,.4);color:#f59e0b}.dsh-ig-inspiration-inspector-fav.is-favorited{color:#f59e0b;border-color:rgba(245,158,11,.5);background:rgba(245,158,11,.08)}.dsh-ig-inspiration-origin{margin:5px 0 12px;color:var(--inspiration-muted);font-size:11px}.dsh-ig-inspiration-tags{display:flex;flex-wrap:wrap;gap:5px;margin:0 0 12px}.dsh-ig-inspiration-tags span{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:3px 6px;border-radius:4px;background:#eef3ff;color:#3e5fb8;font-size:10px}.dsh-ig-inspiration-prompt-label{display:flex;align-items:center;justify-content:space-between;margin:0 0 6px;color:var(--inspiration-muted);font-size:10.5px;font-weight:650}.dsh-ig-inspiration-prompt{max-height:155px;margin:0;padding:9px;overflow:auto;white-space:pre-wrap;border:1px solid var(--inspiration-line);border-radius:7px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2,#f8fafc) 90%,#e9efff);font-size:11px;line-height:1.55;color:#526078}.dsh-ig-inspiration-actions{display:grid;grid-template-columns:1fr 1fr;gap:7px;margin-top:12px}.dsh-ig-inspiration-actions button,.dsh-ig-inspiration-source-link{height:34px;display:inline-flex;align-items:center;justify-content:center;gap:6px;border:1px solid var(--inspiration-line);border-radius:7px;background:var(--dsw-alias-bg-layer-1,#fff);color:#536075;font:inherit;font-size:11.5px;font-weight:600;cursor:pointer;text-decoration:none}.dsh-ig-inspiration-actions button:hover,.dsh-ig-inspiration-source-link:hover{border-color:rgba(47,100,245,.46);color:var(--inspiration-blue)}.dsh-ig-inspiration-actions .dsh-ig-inspiration-use{grid-column:1/-1;border-color:var(--inspiration-blue);background:linear-gradient(135deg,#386cf6,#2758e7);color:#fff;box-shadow:0 7px 15px rgba(47,100,245,.2)}.dsh-ig-inspiration-actions .dsh-ig-inspiration-use:hover{color:#fff;transform:translateY(-1px)}
|
|
5935
|
+
.dsh-ig-inspiration-lightbox{position:fixed;inset:0;z-index:10000;background:rgba(8,12,22,.88);backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;padding:24px;box-sizing:border-box}.dsh-ig-inspiration-lightbox-content{position:relative;max-width:90vw;max-height:90vh;display:flex;flex-direction:column;align-items:center}.dsh-ig-inspiration-lightbox-img-wrap{max-width:85vw;max-height:82vh;border-radius:10px;overflow:hidden;box-shadow:0 25px 60px rgba(0,0,0,.7);background:#05070d}.dsh-ig-inspiration-lightbox-img-wrap img{width:auto;height:auto;max-width:85vw;max-height:82vh;object-fit:contain;display:block}.dsh-ig-inspiration-lightbox-close{position:absolute;top:-16px;right:-16px;width:34px;height:34px;border-radius:50%;background:#1e293b;color:#fff;border:1px solid rgba(255,255,255,.2);cursor:pointer;display:grid;place-items:center;transition:background .15s,transform .15s;z-index:10}.dsh-ig-inspiration-lightbox-close:hover{background:#334155;transform:scale(1.1)}.dsh-ig-inspiration-lightbox-caption{margin-top:12px;color:rgba(255,255,255,.9);font-size:13px;font-weight:500;text-shadow:0 2px 4px rgba(0,0,0,.6)}
|
|
5936
|
+
@media(max-width:980px){.dsh-ig-inspiration-layout{grid-template-columns:1fr}.dsh-ig-inspiration-inspector{position:relative;top:auto}.dsh-ig-inspiration-inspector-empty{min-height:150px}.dsh-ig-inspiration-inspector-image{max-height:290px}.dsh-ig-inspiration-inspector-image img{object-fit:contain;background:#f1f4fa}}@media(max-width:700px){.dsh-ig-inspiration{padding:14px}.dsh-ig-inspiration-head{align-items:center}.dsh-ig-inspiration-toolbar{grid-template-columns:1fr 1fr}.dsh-ig-inspiration-search{grid-column:1/-1}.dsh-ig-inspiration-fav-filter{grid-column:1/-1}.dsh-ig-inspiration-grid{grid-template-columns:repeat(auto-fill,minmax(145px,1fr));gap:9px}}@media(max-width:420px){.dsh-ig-inspiration-toolbar{grid-template-columns:1fr}.dsh-ig-inspiration-search{grid-column:auto}}
|
|
4151
5937
|
`;
|
|
4152
5938
|
//#endregion
|
|
4153
5939
|
//#region lib/types/client/image-result-node.js
|
|
@@ -4765,7 +6551,7 @@ window.__ModuleLoader__.load({
|
|
|
4765
6551
|
ctx.effect(() => {
|
|
4766
6552
|
const style = document.createElement("style");
|
|
4767
6553
|
style.dataset.plugin = "dsh-image-gen";
|
|
4768
|
-
style.textContent = `${STYLE}\n${STUDIO_STYLE}`;
|
|
6554
|
+
style.textContent = `${STYLE}\n${STUDIO_STYLE}\n${INSPIRATION_STYLE}`;
|
|
4769
6555
|
document.head.appendChild(style);
|
|
4770
6556
|
return () => {
|
|
4771
6557
|
style.remove();
|