dsh-image-gen 0.3.1 → 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/README.en.md +172 -172
- package/README.md +182 -182
- package/README.zh-CN.md +172 -172
- package/lib/client.js +1589 -64
- package/lib/client.js.map +1 -1
- package/lib/index.js +11190 -296
- 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/index.d.ts +1 -0
- 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 +2 -0
- package/package.json +8 -10
- package/skills/install-dsh-image-gen/SKILL.md +22 -22
package/lib/client.js
CHANGED
|
@@ -13,6 +13,8 @@ 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";
|
|
16
18
|
/** Browser route used for saving generated images to workspace on demand. */
|
|
17
19
|
const SAVE_WORKSPACE_ROUTE = "/plugins/dsh-image-gen/save-workspace";
|
|
18
20
|
/** Namespace persisted through DSH Settings. */
|
|
@@ -150,7 +152,7 @@ window.__ModuleLoader__.load({
|
|
|
150
152
|
}
|
|
151
153
|
//#endregion
|
|
152
154
|
//#region lib/types/client/gallery-store.js
|
|
153
|
-
const DB_NAME = "dsh_image_gen_db";
|
|
155
|
+
const DB_NAME$2 = "dsh_image_gen_db";
|
|
154
156
|
const DB_VERSION = 2;
|
|
155
157
|
const STORE_NAME = "gallery_history";
|
|
156
158
|
const TOMBSTONE_STORE = "gallery_tombstones";
|
|
@@ -163,7 +165,7 @@ window.__ModuleLoader__.load({
|
|
|
163
165
|
reject(/* @__PURE__ */ new Error("IndexedDB is not supported in this environment."));
|
|
164
166
|
return;
|
|
165
167
|
}
|
|
166
|
-
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
168
|
+
const request = indexedDB.open(DB_NAME$2, DB_VERSION);
|
|
167
169
|
request.onupgradeneeded = (event) => {
|
|
168
170
|
const db = event.target.result;
|
|
169
171
|
if (!db.objectStoreNames.contains(STORE_NAME)) db.createObjectStore(STORE_NAME, { keyPath: "id" }).createIndex("createdAt", "createdAt", { unique: false });
|
|
@@ -669,6 +671,26 @@ window.__ModuleLoader__.load({
|
|
|
669
671
|
* This source code is licensed under the ISC license.
|
|
670
672
|
* See the LICENSE file in the root directory of this source tree.
|
|
671
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
|
+
*/
|
|
672
694
|
const FileText = createLucideIcon("file-text", [
|
|
673
695
|
["path", {
|
|
674
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",
|
|
@@ -774,6 +796,30 @@ window.__ModuleLoader__.load({
|
|
|
774
796
|
* This source code is licensed under the ISC license.
|
|
775
797
|
* See the LICENSE file in the root directory of this source tree.
|
|
776
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
|
+
*/
|
|
777
823
|
const PanelLeftClose = createLucideIcon("panel-left-close", [
|
|
778
824
|
["rect", {
|
|
779
825
|
width: "18",
|
|
@@ -957,6 +1003,16 @@ window.__ModuleLoader__.load({
|
|
|
957
1003
|
* This source code is licensed under the ISC license.
|
|
958
1004
|
* See the LICENSE file in the root directory of this source tree.
|
|
959
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
|
+
*/
|
|
960
1016
|
const Trash2 = createLucideIcon("trash-2", [
|
|
961
1017
|
["path", {
|
|
962
1018
|
d: "M10 11v6",
|
|
@@ -1241,8 +1297,30 @@ window.__ModuleLoader__.load({
|
|
|
1241
1297
|
return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`;
|
|
1242
1298
|
}
|
|
1243
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
|
|
1244
1322
|
//#region lib/types/client/studio-view.js
|
|
1245
|
-
const COPY = {
|
|
1323
|
+
const COPY$1 = {
|
|
1246
1324
|
zh: {
|
|
1247
1325
|
title: "云端生图工作台",
|
|
1248
1326
|
configured: "API 已配置",
|
|
@@ -1271,9 +1349,23 @@ window.__ModuleLoader__.load({
|
|
|
1271
1349
|
partialSuccess: "已生成 {success} 张图片,{failed} 张失败",
|
|
1272
1350
|
generatingCount: "正在生成(共 {count} 张)…",
|
|
1273
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: "开始对比",
|
|
1274
1363
|
saveToGallery: "收藏进画廊",
|
|
1364
|
+
saveSelected: "收藏选中({count})",
|
|
1275
1365
|
savedToGallery: "已收藏进画廊",
|
|
1366
|
+
savedSelected: "已收藏 {count} 张图片",
|
|
1276
1367
|
inGallery: "已在画廊",
|
|
1368
|
+
needSelectResult: "请先勾选要收藏的图片",
|
|
1277
1369
|
retry: "重新加载",
|
|
1278
1370
|
configLoadFailed: "工作台配置加载失败,请检查服务后重试。",
|
|
1279
1371
|
noProvider: "请先在设置中配置至少一个云端图像 Provider 的 API Key。",
|
|
@@ -1324,7 +1416,8 @@ window.__ModuleLoader__.load({
|
|
|
1324
1416
|
unconfiguredStatus: "API 未配置",
|
|
1325
1417
|
providerStatus: "云端提供商与模型状态",
|
|
1326
1418
|
collapseSidebar: "折叠最近生成",
|
|
1327
|
-
expandSidebar: "展开最近生成"
|
|
1419
|
+
expandSidebar: "展开最近生成",
|
|
1420
|
+
findInspiration: "找灵感"
|
|
1328
1421
|
},
|
|
1329
1422
|
en: {
|
|
1330
1423
|
title: "Cloud Image Studio",
|
|
@@ -1354,9 +1447,23 @@ window.__ModuleLoader__.load({
|
|
|
1354
1447
|
partialSuccess: "Generated {success} images, {failed} failed",
|
|
1355
1448
|
generatingCount: "Generating ({count} images)…",
|
|
1356
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",
|
|
1357
1461
|
saveToGallery: "Save to Gallery",
|
|
1462
|
+
saveSelected: "Save selected ({count})",
|
|
1358
1463
|
savedToGallery: "Saved to Gallery",
|
|
1464
|
+
savedSelected: "Saved {count} images",
|
|
1359
1465
|
inGallery: "In Gallery",
|
|
1466
|
+
needSelectResult: "Select images to save first",
|
|
1360
1467
|
retry: "Retry",
|
|
1361
1468
|
configLoadFailed: "Failed to load studio configuration.",
|
|
1362
1469
|
noProvider: "Configure an API key for at least one cloud image provider in Settings.",
|
|
@@ -1407,10 +1514,11 @@ window.__ModuleLoader__.load({
|
|
|
1407
1514
|
unconfiguredStatus: "Not configured",
|
|
1408
1515
|
providerStatus: "Provider & Model Status",
|
|
1409
1516
|
collapseSidebar: "Collapse sidebar",
|
|
1410
|
-
expandSidebar: "Expand recent list"
|
|
1517
|
+
expandSidebar: "Expand recent list",
|
|
1518
|
+
findInspiration: "Find inspiration"
|
|
1411
1519
|
}
|
|
1412
1520
|
};
|
|
1413
|
-
const StudioView = ({ locale, workspace }) => {
|
|
1521
|
+
const StudioView = ({ locale, workspace, initialPrompt, onInitialPromptApplied, onOpenInspiration }) => {
|
|
1414
1522
|
const [lang, setLang] = (0, react.useState)(() => locale?.getSnapshot?.().active?.startsWith("en") ? "en" : "zh");
|
|
1415
1523
|
const [config, setConfig] = (0, react.useState)(null);
|
|
1416
1524
|
const [configLoading, setConfigLoading] = (0, react.useState)(true);
|
|
@@ -1429,6 +1537,10 @@ window.__ModuleLoader__.load({
|
|
|
1429
1537
|
const [references, setReferences] = (0, react.useState)([]);
|
|
1430
1538
|
const [count, setCount] = (0, react.useState)(1);
|
|
1431
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)([]);
|
|
1432
1544
|
const [isGenerating, setIsGenerating] = (0, react.useState)(false);
|
|
1433
1545
|
const [error, setError] = (0, react.useState)(null);
|
|
1434
1546
|
const [notice, setNotice] = (0, react.useState)(null);
|
|
@@ -1449,11 +1561,18 @@ window.__ModuleLoader__.load({
|
|
|
1449
1561
|
const canvasRef = (0, react.useRef)(null);
|
|
1450
1562
|
const fileInputRef = (0, react.useRef)(null);
|
|
1451
1563
|
const image = useAttachmentImage(selected?.attachment);
|
|
1452
|
-
|
|
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;
|
|
1453
1572
|
const referencesRef = (0, react.useRef)(references);
|
|
1454
1573
|
referencesRef.current = references;
|
|
1455
1574
|
const t = (key, values) => {
|
|
1456
|
-
let text = COPY[lang][key];
|
|
1575
|
+
let text = COPY$1[lang][key];
|
|
1457
1576
|
for (const [name, value] of Object.entries(values ?? {})) text = text.replace(`{${name}}`, value);
|
|
1458
1577
|
return text;
|
|
1459
1578
|
};
|
|
@@ -1471,7 +1590,10 @@ window.__ModuleLoader__.load({
|
|
|
1471
1590
|
if (!response.ok || !("providers" in payload)) throw new Error("error" in payload && payload.error ? payload.error : "Studio unavailable");
|
|
1472
1591
|
setConfig(payload);
|
|
1473
1592
|
const initial = payload.providers.find((item) => item.provider === payload.activeProvider) ?? payload.providers[0];
|
|
1474
|
-
if (initial !== void 0)
|
|
1593
|
+
if (initial !== void 0) {
|
|
1594
|
+
applyProvider(initial);
|
|
1595
|
+
setComparisonProviders(initialComparisonProviders(payload.providers, initial.provider));
|
|
1596
|
+
}
|
|
1475
1597
|
} catch (fetchError) {
|
|
1476
1598
|
if (!controller.signal.aborted) setConfigError(messageOf(fetchError));
|
|
1477
1599
|
} finally {
|
|
@@ -1553,6 +1675,13 @@ window.__ModuleLoader__.load({
|
|
|
1553
1675
|
const activeProfile = (0, react.useMemo)(() => config?.providers.find((item) => item.provider === provider), [config, provider]);
|
|
1554
1676
|
const configuredCount = config?.providers.filter((item) => item.configured).length ?? 0;
|
|
1555
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
|
+
]);
|
|
1556
1685
|
const applyProvider = (profile) => {
|
|
1557
1686
|
setProvider(profile.provider);
|
|
1558
1687
|
setModel(profile.model);
|
|
@@ -1585,12 +1714,16 @@ window.__ModuleLoader__.load({
|
|
|
1585
1714
|
};
|
|
1586
1715
|
const selectItem = (item) => {
|
|
1587
1716
|
setCurrentBatch(null);
|
|
1717
|
+
setSelectedBatchIds([]);
|
|
1718
|
+
setBatchKind(null);
|
|
1588
1719
|
setSelected(item);
|
|
1589
1720
|
setPanelTab("details");
|
|
1590
1721
|
resetFit();
|
|
1591
1722
|
};
|
|
1592
1723
|
const startNew = () => {
|
|
1593
1724
|
setCurrentBatch(null);
|
|
1725
|
+
setSelectedBatchIds([]);
|
|
1726
|
+
setBatchKind(null);
|
|
1594
1727
|
setSelected(null);
|
|
1595
1728
|
setPanelTab("generate");
|
|
1596
1729
|
resetFit();
|
|
@@ -1701,10 +1834,46 @@ window.__ModuleLoader__.load({
|
|
|
1701
1834
|
setIsGenerating(false);
|
|
1702
1835
|
}
|
|
1703
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
|
+
};
|
|
1704
1872
|
const submit = async () => {
|
|
1705
1873
|
if (isGenerating || activeProfile === void 0) return;
|
|
1706
1874
|
if (prompt.trim().length === 0) return setError(t("needPrompt"));
|
|
1707
|
-
if (
|
|
1875
|
+
if (comparisonEnabled && comparisonTargets.length < 2) return setError(t("compareNeedTwo"));
|
|
1876
|
+
if (!comparisonEnabled && !activeProfile.configured) return setError(t("selectConfigured"));
|
|
1708
1877
|
if (mode === "edit" && references.length === 0) return setError(t("needReference"));
|
|
1709
1878
|
setIsGenerating(true);
|
|
1710
1879
|
setError(null);
|
|
@@ -1720,6 +1889,64 @@ window.__ModuleLoader__.load({
|
|
|
1720
1889
|
};
|
|
1721
1890
|
throw new Error("无效参考图");
|
|
1722
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
|
+
}
|
|
1723
1950
|
const response = await fetch(STUDIO_ROUTE, {
|
|
1724
1951
|
method: "POST",
|
|
1725
1952
|
credentials: "same-origin",
|
|
@@ -1760,9 +1987,13 @@ window.__ModuleLoader__.load({
|
|
|
1760
1987
|
}));
|
|
1761
1988
|
if (galleryEntries.length > 1) {
|
|
1762
1989
|
setCurrentBatch(galleryEntries);
|
|
1990
|
+
setSelectedBatchIds([galleryEntries[0].id]);
|
|
1991
|
+
setBatchKind("multi-image");
|
|
1763
1992
|
setSelected(galleryEntries[0]);
|
|
1764
1993
|
} else if (galleryEntries.length === 1) {
|
|
1765
1994
|
setCurrentBatch(null);
|
|
1995
|
+
setSelectedBatchIds([]);
|
|
1996
|
+
setBatchKind(null);
|
|
1766
1997
|
setSelected(galleryEntries[0]);
|
|
1767
1998
|
}
|
|
1768
1999
|
setPanelTab("details");
|
|
@@ -1785,9 +2016,24 @@ window.__ModuleLoader__.load({
|
|
|
1785
2016
|
if (selected === null) return false;
|
|
1786
2017
|
return items.some((item) => item.id === selected.id);
|
|
1787
2018
|
}, [items, selected]);
|
|
1788
|
-
const
|
|
1789
|
-
if (
|
|
1790
|
-
|
|
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;
|
|
1791
2037
|
const targetRoot = workspace?.path || config?.workspaceRoot;
|
|
1792
2038
|
if (!savedTo) try {
|
|
1793
2039
|
const res = await fetch(SAVE_WORKSPACE_ROUTE, {
|
|
@@ -1795,7 +2041,7 @@ window.__ModuleLoader__.load({
|
|
|
1795
2041
|
credentials: "same-origin",
|
|
1796
2042
|
headers: { "content-type": "application/json" },
|
|
1797
2043
|
body: JSON.stringify({
|
|
1798
|
-
attachment:
|
|
2044
|
+
attachment: item.attachment,
|
|
1799
2045
|
...targetRoot ? { workspaceRoot: targetRoot } : {}
|
|
1800
2046
|
})
|
|
1801
2047
|
});
|
|
@@ -1803,19 +2049,35 @@ window.__ModuleLoader__.load({
|
|
|
1803
2049
|
const data = await res.json();
|
|
1804
2050
|
if (data.savedTo) savedTo = data.savedTo;
|
|
1805
2051
|
}
|
|
1806
|
-
} catch (
|
|
1807
|
-
console.warn("Failed to save to workspace:",
|
|
2052
|
+
} catch (saveError) {
|
|
2053
|
+
console.warn("Failed to save to workspace:", saveError);
|
|
1808
2054
|
}
|
|
1809
2055
|
const updatedItem = {
|
|
1810
|
-
...
|
|
2056
|
+
...item,
|
|
1811
2057
|
...savedTo ? { savedTo } : {},
|
|
1812
2058
|
...targetRoot ? { workspacePath: targetRoot } : {},
|
|
1813
2059
|
...workspace?.workspaceId ? { workspaceId: workspace.workspaceId } : {}
|
|
1814
2060
|
};
|
|
1815
2061
|
await saveGalleryItem(updatedItem);
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
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"));
|
|
1819
2081
|
};
|
|
1820
2082
|
const handleConfirmDelete = async () => {
|
|
1821
2083
|
if (selected === null) return;
|
|
@@ -1840,6 +2102,7 @@ window.__ModuleLoader__.load({
|
|
|
1840
2102
|
}
|
|
1841
2103
|
if (isSelectedInGallery) await deleteGalleryItem(idToDelete);
|
|
1842
2104
|
evictAttachmentCache(attachmentId);
|
|
2105
|
+
setSelectedBatchIds((current) => current.filter((id) => id !== idToDelete));
|
|
1843
2106
|
if (currentBatch) {
|
|
1844
2107
|
const remaining = currentBatch.filter((i) => i.id !== idToDelete);
|
|
1845
2108
|
if (remaining.length > 1) {
|
|
@@ -2074,12 +2337,8 @@ window.__ModuleLoader__.load({
|
|
|
2074
2337
|
className: "dsh-ig-generation-orbit",
|
|
2075
2338
|
children: (0, react_jsx_runtime.jsx)(Sparkles, { size: 28 })
|
|
2076
2339
|
}),
|
|
2077
|
-
(0, react_jsx_runtime.jsx)("strong", { children: count > 1 ? t("generatingCount", { count: String(count) }) : t("generating") }),
|
|
2078
|
-
(0, react_jsx_runtime.
|
|
2079
|
-
activeProfile?.label,
|
|
2080
|
-
" · ",
|
|
2081
|
-
model
|
|
2082
|
-
] })
|
|
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}` })
|
|
2083
2342
|
]
|
|
2084
2343
|
}) : currentBatch && currentBatch.length > 1 ? (0, react_jsx_runtime.jsx)("div", {
|
|
2085
2344
|
className: "dsh-ig-canvas-cluster",
|
|
@@ -2092,9 +2351,14 @@ window.__ModuleLoader__.load({
|
|
|
2092
2351
|
children: currentBatch.map((batchItem, index) => (0, react_jsx_runtime.jsx)(BatchCanvasItem, {
|
|
2093
2352
|
item: batchItem,
|
|
2094
2353
|
index,
|
|
2095
|
-
|
|
2354
|
+
badge: batchKind === "multi-model" ? batchItem.model : void 0,
|
|
2355
|
+
isActive: selected?.id === batchItem.id,
|
|
2356
|
+
isChecked: selectedBatchIds.includes(batchItem.id),
|
|
2096
2357
|
onSelect: () => {
|
|
2097
|
-
if (!hasDraggedRef.current)
|
|
2358
|
+
if (!hasDraggedRef.current) {
|
|
2359
|
+
setSelected(batchItem);
|
|
2360
|
+
setSelectedBatchIds((current) => current.includes(batchItem.id) ? current.filter((id) => id !== batchItem.id) : [...current, batchItem.id]);
|
|
2361
|
+
}
|
|
2098
2362
|
}
|
|
2099
2363
|
}, batchItem.id))
|
|
2100
2364
|
}) : selected === null ? (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2143,11 +2407,11 @@ window.__ModuleLoader__.load({
|
|
|
2143
2407
|
children: [(0, react_jsx_runtime.jsx)("p", { children: selected.prompt }), (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
2144
2408
|
(0, react_jsx_runtime.jsxs)("button", {
|
|
2145
2409
|
type: "button",
|
|
2146
|
-
className: `dsh-ig-save-gallery-btn ${
|
|
2410
|
+
className: `dsh-ig-save-gallery-btn ${saveSelectionComplete ? "is-saved" : ""}`,
|
|
2147
2411
|
onClick: () => void handleSaveToGallery(),
|
|
2148
|
-
title:
|
|
2149
|
-
disabled:
|
|
2150
|
-
children: [
|
|
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 })]
|
|
2151
2415
|
}),
|
|
2152
2416
|
(0, react_jsx_runtime.jsxs)("button", {
|
|
2153
2417
|
type: "button",
|
|
@@ -2306,11 +2570,16 @@ window.__ModuleLoader__.load({
|
|
|
2306
2570
|
" ",
|
|
2307
2571
|
(0, react_jsx_runtime.jsx)("b", { children: "*" })
|
|
2308
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")]
|
|
2309
2578
|
}), (0, react_jsx_runtime.jsx)("button", {
|
|
2310
2579
|
type: "button",
|
|
2311
2580
|
onClick: () => setPrompt(""),
|
|
2312
2581
|
children: t("clear")
|
|
2313
|
-
})]
|
|
2582
|
+
})] })]
|
|
2314
2583
|
}),
|
|
2315
2584
|
(0, react_jsx_runtime.jsx)("textarea", {
|
|
2316
2585
|
id: "dsh-ig-prompt",
|
|
@@ -2342,7 +2611,78 @@ window.__ModuleLoader__.load({
|
|
|
2342
2611
|
onClick: () => void loadConfig(),
|
|
2343
2612
|
children: [(0, react_jsx_runtime.jsx)(RefreshCw, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: t("retry") })]
|
|
2344
2613
|
})]
|
|
2345
|
-
}) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
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")]
|
|
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
|
+
]
|
|
2672
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
2673
|
+
className: "dsh-ig-field-grid",
|
|
2674
|
+
children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
|
|
2675
|
+
label: t("ratio"),
|
|
2676
|
+
value: ratio,
|
|
2677
|
+
onChange: setRatio,
|
|
2678
|
+
options: comparisonRatioOptions(lang)
|
|
2679
|
+
}), (0, react_jsx_runtime.jsx)(FieldSelect, {
|
|
2680
|
+
label: t("quality"),
|
|
2681
|
+
value: quality,
|
|
2682
|
+
onChange: setQuality,
|
|
2683
|
+
options: comparisonQualityOptions(lang)
|
|
2684
|
+
})]
|
|
2685
|
+
})] }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2346
2686
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
2347
2687
|
className: "dsh-ig-field-grid",
|
|
2348
2688
|
children: [(0, react_jsx_runtime.jsx)(FieldSelect, {
|
|
@@ -2369,12 +2709,12 @@ window.__ModuleLoader__.load({
|
|
|
2369
2709
|
label: t("ratio"),
|
|
2370
2710
|
value: ratio,
|
|
2371
2711
|
onChange: setRatio,
|
|
2372
|
-
options: activeProfile?.ratioOptions ?? []
|
|
2712
|
+
options: localizeRatioOptions(activeProfile?.ratioOptions ?? [], lang)
|
|
2373
2713
|
}), (0, react_jsx_runtime.jsx)(FieldSelect, {
|
|
2374
2714
|
label: t("quality"),
|
|
2375
2715
|
value: quality,
|
|
2376
2716
|
onChange: setQuality,
|
|
2377
|
-
options: activeProfile?.qualityOptions ?? []
|
|
2717
|
+
options: localizeQualityOptions(activeProfile?.qualityOptions ?? [], lang)
|
|
2378
2718
|
})]
|
|
2379
2719
|
}),
|
|
2380
2720
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2394,7 +2734,7 @@ window.__ModuleLoader__.load({
|
|
|
2394
2734
|
}, option))
|
|
2395
2735
|
})]
|
|
2396
2736
|
})
|
|
2397
|
-
] }),
|
|
2737
|
+
] })] }),
|
|
2398
2738
|
configuredCount === 0 && config !== null && (0, react_jsx_runtime.jsx)("div", {
|
|
2399
2739
|
className: "dsh-ig-inline-note",
|
|
2400
2740
|
children: t("noProvider")
|
|
@@ -2416,7 +2756,7 @@ window.__ModuleLoader__.load({
|
|
|
2416
2756
|
className: "dsh-ig-generate-button",
|
|
2417
2757
|
disabled: config === null,
|
|
2418
2758
|
onClick: () => void submit(),
|
|
2419
|
-
children: [(0, react_jsx_runtime.jsx)(Sparkles, { size: 17 }), (0, react_jsx_runtime.
|
|
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})` : ""}` })]
|
|
2420
2760
|
})
|
|
2421
2761
|
]
|
|
2422
2762
|
})]
|
|
@@ -2536,11 +2876,11 @@ window.__ModuleLoader__.load({
|
|
|
2536
2876
|
}),
|
|
2537
2877
|
(0, react_jsx_runtime.jsxs)("button", {
|
|
2538
2878
|
type: "button",
|
|
2539
|
-
className: `dsh-ig-lightbox-btn ${
|
|
2540
|
-
title:
|
|
2879
|
+
className: `dsh-ig-lightbox-btn ${saveSelectionComplete ? "is-saved" : ""}`,
|
|
2880
|
+
title: saveButtonLabel,
|
|
2541
2881
|
onClick: () => void handleSaveToGallery(),
|
|
2542
|
-
disabled:
|
|
2543
|
-
children: [
|
|
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 })]
|
|
2544
2884
|
}),
|
|
2545
2885
|
(0, react_jsx_runtime.jsxs)("button", {
|
|
2546
2886
|
type: "button",
|
|
@@ -2608,28 +2948,37 @@ window.__ModuleLoader__.load({
|
|
|
2608
2948
|
]
|
|
2609
2949
|
});
|
|
2610
2950
|
};
|
|
2611
|
-
const BatchCanvasItem = ({ item, index,
|
|
2951
|
+
const BatchCanvasItem = ({ item, index, badge, isActive, isChecked, onSelect }) => {
|
|
2612
2952
|
const image = useAttachmentImage(item.attachment, true);
|
|
2613
2953
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
2614
|
-
className: `dsh-ig-canvas-item ${
|
|
2954
|
+
className: `dsh-ig-canvas-item ${isActive ? "is-active" : ""} ${isChecked ? "is-checked" : ""}`,
|
|
2615
2955
|
onClick: onSelect,
|
|
2616
|
-
children: [
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
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 })
|
|
2628
2980
|
})
|
|
2629
|
-
|
|
2630
|
-
className: "dsh-ig-canvas-item-loading",
|
|
2631
|
-
children: (0, react_jsx_runtime.jsx)(ImagePlus, { size: 24 })
|
|
2632
|
-
})]
|
|
2981
|
+
]
|
|
2633
2982
|
});
|
|
2634
2983
|
};
|
|
2635
2984
|
const FieldSelect = ({ label, value, options, onChange }) => (0, react_jsx_runtime.jsxs)("label", {
|
|
@@ -2643,6 +2992,94 @@ window.__ModuleLoader__.load({
|
|
|
2643
2992
|
}, option.value))
|
|
2644
2993
|
})]
|
|
2645
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
|
+
}
|
|
2646
3083
|
const DetailsPanel = ({ item, t }) => item === null ? (0, react_jsx_runtime.jsxs)("div", {
|
|
2647
3084
|
className: "dsh-ig-details-empty",
|
|
2648
3085
|
children: [(0, react_jsx_runtime.jsx)(ImagePlus, { size: 28 }), (0, react_jsx_runtime.jsx)("span", { children: t("selectHistory") })]
|
|
@@ -2772,6 +3209,1036 @@ window.__ModuleLoader__.load({
|
|
|
2772
3209
|
return error instanceof Error ? error.message : String(error);
|
|
2773
3210
|
}
|
|
2774
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
|
|
2775
4242
|
//#region lib/types/client/conversation-regenerate.js
|
|
2776
4243
|
/** Build the workbench request that reproduces a conversation image with an edited prompt. */
|
|
2777
4244
|
function conversationRegenerateRequest(image, prompt, remembered) {
|
|
@@ -2836,6 +4303,7 @@ window.__ModuleLoader__.load({
|
|
|
2836
4303
|
zh: {
|
|
2837
4304
|
tabGallery: "图库",
|
|
2838
4305
|
tabStudio: "工作台",
|
|
4306
|
+
tabInspiration: "灵感",
|
|
2839
4307
|
tabFavorites: "收藏",
|
|
2840
4308
|
tabCompare: "对比",
|
|
2841
4309
|
tabTasks: "任务",
|
|
@@ -2917,6 +4385,7 @@ window.__ModuleLoader__.load({
|
|
|
2917
4385
|
en: {
|
|
2918
4386
|
tabGallery: "Gallery",
|
|
2919
4387
|
tabStudio: "Studio",
|
|
4388
|
+
tabInspiration: "Inspiration",
|
|
2920
4389
|
tabFavorites: "Favorites",
|
|
2921
4390
|
tabCompare: "Compare",
|
|
2922
4391
|
tabTasks: "Tasks",
|
|
@@ -3054,6 +4523,7 @@ window.__ModuleLoader__.load({
|
|
|
3054
4523
|
const GalleryViewTab = (props) => {
|
|
3055
4524
|
const { locale, sessionId, useSessions, useWorkspaces } = props;
|
|
3056
4525
|
const [activeTab, setActiveTab] = (0, react.useState)("gallery");
|
|
4526
|
+
const [studioDraft, setStudioDraft] = (0, react.useState)(void 0);
|
|
3057
4527
|
const [items, setItems] = (0, react.useState)([]);
|
|
3058
4528
|
const [search, setSearch] = (0, react.useState)("");
|
|
3059
4529
|
const [selectedProvider, setSelectedProvider] = (0, react.useState)("all");
|
|
@@ -3164,6 +4634,11 @@ window.__ModuleLoader__.load({
|
|
|
3164
4634
|
setToast(null);
|
|
3165
4635
|
}, 2e3);
|
|
3166
4636
|
};
|
|
4637
|
+
const useInspirationPrompt = (0, react.useCallback)((prompt) => {
|
|
4638
|
+
setStudioDraft(prompt);
|
|
4639
|
+
setActiveTab("studio");
|
|
4640
|
+
}, []);
|
|
4641
|
+
const clearStudioDraft = (0, react.useCallback)(() => setStudioDraft(void 0), []);
|
|
3167
4642
|
(0, react.useEffect)(() => {
|
|
3168
4643
|
const seat = document.querySelector("[data-composer-seat]");
|
|
3169
4644
|
if (seat) {
|
|
@@ -3568,6 +5043,12 @@ window.__ModuleLoader__.load({
|
|
|
3568
5043
|
onClick: () => setActiveTab("studio"),
|
|
3569
5044
|
children: [(0, react_jsx_runtime.jsx)(SlidersHorizontal, { size: 15 }), (0, react_jsx_runtime.jsx)("span", { children: t("tabStudio") })]
|
|
3570
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
|
+
}),
|
|
3571
5052
|
(0, react_jsx_runtime.jsxs)("button", {
|
|
3572
5053
|
type: "button",
|
|
3573
5054
|
className: `dsh-ig-studio-tab-btn ${activeTab === "favorites" ? "is-active" : ""}`,
|
|
@@ -3779,9 +5260,15 @@ window.__ModuleLoader__.load({
|
|
|
3779
5260
|
onBlobLoaded: (b) => blobCache.set(item.id, b),
|
|
3780
5261
|
onToast: showToast
|
|
3781
5262
|
}, item.id))
|
|
5263
|
+
}) : activeTab === "inspiration" ? (0, react_jsx_runtime.jsx)(InspirationView, {
|
|
5264
|
+
locale,
|
|
5265
|
+
onUsePrompt: useInspirationPrompt
|
|
3782
5266
|
}) : (0, react_jsx_runtime.jsx)(StudioView, {
|
|
3783
5267
|
locale,
|
|
3784
|
-
workspace: activeWorkspace
|
|
5268
|
+
workspace: activeWorkspace,
|
|
5269
|
+
initialPrompt: studioDraft,
|
|
5270
|
+
onInitialPromptApplied: clearStudioDraft,
|
|
5271
|
+
onOpenInspiration: () => setActiveTab("inspiration")
|
|
3785
5272
|
})
|
|
3786
5273
|
}),
|
|
3787
5274
|
toast && (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -4354,6 +5841,8 @@ window.__ModuleLoader__.load({
|
|
|
4354
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}
|
|
4355
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)}
|
|
4356
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}
|
|
4357
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)}
|
|
4358
5847
|
.dsh-ig-reference-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(64px,1fr));gap:8px}
|
|
4359
5848
|
.dsh-ig-reference-card{position:relative;aspect-ratio:1;border-radius:8px;overflow:hidden;background:#eef1f7;border:1px solid var(--ig-line)}
|
|
@@ -4387,6 +5876,7 @@ window.__ModuleLoader__.load({
|
|
|
4387
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}}
|
|
4388
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}}
|
|
4389
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}
|
|
4390
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}
|
|
4391
5881
|
.dsh-ig-workspace-filter-label:hover{border-color:#9bb4f5;background:rgba(47,100,245,.04)}
|
|
4392
5882
|
.dsh-ig-workspace-checkbox{cursor:pointer;accent-color:#2f64f5;width:13.5px;height:13.5px;margin:0}
|
|
@@ -4398,9 +5888,12 @@ window.__ModuleLoader__.load({
|
|
|
4398
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}
|
|
4399
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}
|
|
4400
5890
|
.dsh-ig-canvas-item:hover img{box-shadow:0 14px 40px rgba(47,100,245,.2)}
|
|
4401
|
-
.dsh-ig-canvas-item.is-
|
|
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)}
|
|
4402
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}
|
|
4403
|
-
.dsh-ig-canvas-
|
|
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)}
|
|
4404
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}
|
|
4405
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)}
|
|
4406
5899
|
.dsh-ig-lightbox-nav:hover{background:rgba(47,100,245,.85);transform:translateY(-50%) scale(1.08)}
|
|
@@ -4409,6 +5902,38 @@ window.__ModuleLoader__.load({
|
|
|
4409
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}
|
|
4410
5903
|
.dsh-ig-save-gallery-btn:hover{background:rgba(47,100,245,.12)!important}
|
|
4411
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}}
|
|
4412
5937
|
`;
|
|
4413
5938
|
//#endregion
|
|
4414
5939
|
//#region lib/types/client/image-result-node.js
|
|
@@ -5026,7 +6551,7 @@ window.__ModuleLoader__.load({
|
|
|
5026
6551
|
ctx.effect(() => {
|
|
5027
6552
|
const style = document.createElement("style");
|
|
5028
6553
|
style.dataset.plugin = "dsh-image-gen";
|
|
5029
|
-
style.textContent = `${STYLE}\n${STUDIO_STYLE}`;
|
|
6554
|
+
style.textContent = `${STYLE}\n${STUDIO_STYLE}\n${INSPIRATION_STYLE}`;
|
|
5030
6555
|
document.head.appendChild(style);
|
|
5031
6556
|
return () => {
|
|
5032
6557
|
style.remove();
|