dsh-image-gen 0.6.8 → 0.6.10
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/LICENSE +201 -21
- package/README.en.md +8 -4
- package/README.md +8 -4
- package/lib/client.js +516 -142
- package/lib/client.js.map +1 -1
- package/lib/index.js +346 -229
- package/lib/types/canvas-asset-route.d.ts +12 -0
- package/lib/types/canvas-state.d.ts +3 -0
- package/lib/types/client/gallery-store.d.ts +3 -1
- package/lib/types/client/gallery-view.d.ts +2 -0
- package/lib/types/client/index.d.ts +7 -1
- package/lib/types/client/studio-style.d.ts +1 -1
- package/lib/types/client/tl/canvas-selection.d.ts +13 -0
- package/lib/types/client/tl/canvas-sync.d.ts +9 -1
- package/lib/types/client/tl/studio-tl-canvas.d.ts +3 -1
- package/lib/types/config.d.ts +19 -2
- package/lib/types/openai-compatible.d.ts +3 -0
- package/lib/types/seedream.d.ts +7 -0
- package/lib/types/shared.d.ts +49 -0
- package/package.json +2 -2
- package/skills/install-dsh-image-gen/SKILL.md +1 -1
package/lib/client.js
CHANGED
|
@@ -87,6 +87,8 @@ window.__ModuleLoader__.load({
|
|
|
87
87
|
const TEST_CONNECTION_ROUTE = "/plugins/dsh-image-gen/test";
|
|
88
88
|
/** Browser route the workbench infinite canvas pushes live state through. */
|
|
89
89
|
const CANVAS_STATE_ROUTE = "/plugins/dsh-image-gen/canvas-state";
|
|
90
|
+
/** Upload a selected canvas original to the host attachment store. */
|
|
91
|
+
const CANVAS_ASSET_ROUTE = "/plugins/dsh-image-gen/canvas-asset";
|
|
90
92
|
const SUBSCRIPTION_LOGIN_ROUTE = "/plugins/dsh-image-gen/subscription-login";
|
|
91
93
|
const SUBSCRIPTION_STATUS_ROUTE = "/plugins/dsh-image-gen/subscription-status";
|
|
92
94
|
/** Namespace persisted through DSH Settings. */
|
|
@@ -320,6 +322,7 @@ window.__ModuleLoader__.load({
|
|
|
320
322
|
resolve(request.result);
|
|
321
323
|
};
|
|
322
324
|
request.onerror = () => {
|
|
325
|
+
dbPromise = null;
|
|
323
326
|
reject(request.error);
|
|
324
327
|
};
|
|
325
328
|
});
|
|
@@ -370,11 +373,13 @@ window.__ModuleLoader__.load({
|
|
|
370
373
|
* Save or update a gallery record by attachmentId.
|
|
371
374
|
* Skipped if the item was previously deleted (tombstoned).
|
|
372
375
|
* Preserves existing isFavorite, tags, and original createdAt on re-renders.
|
|
376
|
+
* Returns false when the record was not persisted (e.g. IndexedDB unavailable)
|
|
377
|
+
* so callers can surface the failure and let the user retry.
|
|
373
378
|
*/
|
|
374
379
|
async function saveGalleryItem(item) {
|
|
375
380
|
try {
|
|
376
381
|
const db = await getDB();
|
|
377
|
-
if ((await loadTombstones(db)).has(item.id)) return;
|
|
382
|
+
if ((await loadTombstones(db)).has(item.id)) return true;
|
|
378
383
|
await new Promise((resolve, reject) => {
|
|
379
384
|
const store = db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME);
|
|
380
385
|
const getReq = store.get(item.id);
|
|
@@ -398,8 +403,10 @@ window.__ModuleLoader__.load({
|
|
|
398
403
|
getReq.onerror = () => reject(getReq.error);
|
|
399
404
|
});
|
|
400
405
|
notifyListeners();
|
|
406
|
+
return true;
|
|
401
407
|
} catch (err) {
|
|
402
408
|
console.warn("[dsh-image-gen] Failed to save gallery item to IndexedDB:", err);
|
|
409
|
+
return false;
|
|
403
410
|
}
|
|
404
411
|
}
|
|
405
412
|
/**
|
|
@@ -115633,16 +115640,113 @@ img.ProseMirror-separator {
|
|
|
115633
115640
|
});
|
|
115634
115641
|
}
|
|
115635
115642
|
//#endregion
|
|
115643
|
+
//#region lib/types/client/tl/canvas-selection.js
|
|
115644
|
+
/** Bound raster dimensions before exporting; large multi-image PNGs get a
|
|
115645
|
+
* JPEG preview instead of being silently discarded. Originals are separate. */
|
|
115646
|
+
async function exportSelectionPreview(editor, shapes, maxBytes, signal) {
|
|
115647
|
+
const bounds = editor.getSelectionPageBounds();
|
|
115648
|
+
const scale = Math.min(1, 2048 / Math.max(1, (bounds?.w ?? 2048) + 32, (bounds?.h ?? 2048) + 32));
|
|
115649
|
+
const attempts = [
|
|
115650
|
+
{
|
|
115651
|
+
format: "png",
|
|
115652
|
+
scale,
|
|
115653
|
+
quality: 1
|
|
115654
|
+
},
|
|
115655
|
+
{
|
|
115656
|
+
format: "jpeg",
|
|
115657
|
+
scale,
|
|
115658
|
+
quality: .85
|
|
115659
|
+
},
|
|
115660
|
+
{
|
|
115661
|
+
format: "jpeg",
|
|
115662
|
+
scale: scale * .75,
|
|
115663
|
+
quality: .7
|
|
115664
|
+
},
|
|
115665
|
+
{
|
|
115666
|
+
format: "jpeg",
|
|
115667
|
+
scale: scale * .5,
|
|
115668
|
+
quality: .6
|
|
115669
|
+
},
|
|
115670
|
+
{
|
|
115671
|
+
format: "jpeg",
|
|
115672
|
+
scale: scale * .25,
|
|
115673
|
+
quality: .5
|
|
115674
|
+
}
|
|
115675
|
+
];
|
|
115676
|
+
const maxChars = Math.floor(Math.min(maxBytes, 4e6) / 3) * 4;
|
|
115677
|
+
for (const attempt of attempts) {
|
|
115678
|
+
signal.throwIfAborted();
|
|
115679
|
+
const { url } = await editor.toImageDataUrl(shapes, {
|
|
115680
|
+
background: true,
|
|
115681
|
+
padding: 16,
|
|
115682
|
+
pixelRatio: 1,
|
|
115683
|
+
...attempt
|
|
115684
|
+
});
|
|
115685
|
+
signal.throwIfAborted();
|
|
115686
|
+
if (url.length - url.indexOf(",") - 1 <= maxChars) return url;
|
|
115687
|
+
}
|
|
115688
|
+
throw new Error("preview-too-large");
|
|
115689
|
+
}
|
|
115690
|
+
async function readCanvasImageLimit(signal) {
|
|
115691
|
+
const response = await fetch(CANVAS_ASSET_ROUTE, {
|
|
115692
|
+
credentials: "same-origin",
|
|
115693
|
+
signal
|
|
115694
|
+
});
|
|
115695
|
+
if (!response.ok) throw new Error(`host-unavailable (${response.status})`);
|
|
115696
|
+
const value = await response.json();
|
|
115697
|
+
if (typeof value.maxImageBytes !== "number" || !Number.isSafeInteger(value.maxImageBytes) || value.maxImageBytes <= 0) throw new Error("host-unavailable");
|
|
115698
|
+
return value.maxImageBytes;
|
|
115699
|
+
}
|
|
115700
|
+
/** Use tldraw's resolver for IndexedDB-backed imports, not just asset.props.src.
|
|
115701
|
+
* Upload each original once per mount/source, with individual byte limits. */
|
|
115702
|
+
async function syncCanvasOriginal(editor, shape, maxBytes, signal, cache) {
|
|
115703
|
+
if (shape.type !== "image" || shape.props.assetId === null) throw new Error("original-unavailable");
|
|
115704
|
+
const asset = editor.getAsset(shape.props.assetId);
|
|
115705
|
+
if (asset?.type !== "image") throw new Error("original-unavailable");
|
|
115706
|
+
const cached = cache.get(asset.id);
|
|
115707
|
+
if (cached?.src === asset.props.src) return cached.attachment;
|
|
115708
|
+
const url = await editor.resolveAssetUrl(asset.id, { shouldResolveToOriginal: true });
|
|
115709
|
+
signal.throwIfAborted();
|
|
115710
|
+
if (url === null) throw new Error("original-unavailable");
|
|
115711
|
+
const response = await fetch(url, { signal });
|
|
115712
|
+
if (!response.ok) throw new Error("original-unavailable");
|
|
115713
|
+
const blob = await response.blob();
|
|
115714
|
+
if (blob.size > maxBytes) throw new Error("original-too-large");
|
|
115715
|
+
const mediaType = blob.type || asset.props.mimeType || "";
|
|
115716
|
+
if (![
|
|
115717
|
+
"image/png",
|
|
115718
|
+
"image/jpeg",
|
|
115719
|
+
"image/webp",
|
|
115720
|
+
"image/gif"
|
|
115721
|
+
].includes(mediaType)) throw new Error("original-format-unsupported");
|
|
115722
|
+
const uploaded = await fetch(CANVAS_ASSET_ROUTE, {
|
|
115723
|
+
method: "POST",
|
|
115724
|
+
credentials: "same-origin",
|
|
115725
|
+
headers: { "content-type": mediaType },
|
|
115726
|
+
body: blob,
|
|
115727
|
+
signal
|
|
115728
|
+
});
|
|
115729
|
+
if (!uploaded.ok) throw new Error(uploaded.status === 413 ? "original-too-large" : `original-upload-failed (${uploaded.status})`);
|
|
115730
|
+
const ref = (await uploaded.json()).attachment;
|
|
115731
|
+
if (!ref || typeof ref.attachmentId !== "string" || ![
|
|
115732
|
+
"image/png",
|
|
115733
|
+
"image/jpeg",
|
|
115734
|
+
"image/webp",
|
|
115735
|
+
"image/gif"
|
|
115736
|
+
].includes(ref.mediaType) || !Number.isSafeInteger(ref.bytes) || ref.bytes <= 0 || !Number.isInteger(ref.width) || ref.width <= 0 || !Number.isInteger(ref.height) || ref.height <= 0) throw new Error("original-upload-failed");
|
|
115737
|
+
cache.set(asset.id, {
|
|
115738
|
+
src: asset.props.src,
|
|
115739
|
+
attachment: ref
|
|
115740
|
+
});
|
|
115741
|
+
return ref;
|
|
115742
|
+
}
|
|
115743
|
+
//#endregion
|
|
115636
115744
|
//#region lib/types/client/tl/canvas-sync.js
|
|
115637
115745
|
/** Debounce for ordinary document/session changes. */
|
|
115638
115746
|
const STATE_DEBOUNCE_MS = 600;
|
|
115639
115747
|
/** Extra settle time before an expensive selection screenshot export. */
|
|
115640
115748
|
const SELECTION_EXPORT_DELAY_MS = 1200;
|
|
115641
|
-
/** Minimum spacing between two HTTP pushes. */
|
|
115642
|
-
const MIN_PUSH_INTERVAL_MS = 400;
|
|
115643
115749
|
const MAX_TEXT_CHARS = 96;
|
|
115644
|
-
/** Hard cap on the exported screenshot payload (base64 chars, ~5MB binary). */
|
|
115645
|
-
const MAX_SCREENSHOT_DATAURL_CHARS = 7e6;
|
|
115646
115750
|
/** Map a tldraw shape type onto the coarse model-facing node kind. */
|
|
115647
115751
|
function kindOf(shapeType) {
|
|
115648
115752
|
switch (shapeType) {
|
|
@@ -115749,69 +115853,66 @@ img.ProseMirror-separator {
|
|
|
115749
115853
|
const ids = editor.getSelectedShapeIds().map(String).sort().join("|");
|
|
115750
115854
|
return ids.length === 0 ? "none" : ids;
|
|
115751
115855
|
}
|
|
115752
|
-
|
|
115753
|
-
|
|
115754
|
-
* Returns a disposer that stops listening and marks the instance offline.
|
|
115755
|
-
*/
|
|
115756
|
-
function startCanvasSync(editor) {
|
|
115757
|
-
const clientInstance = String(editor.id);
|
|
115856
|
+
function startCanvasSync(editor, onStatus = () => {}) {
|
|
115857
|
+
const clientInstance = `${String(editor.id).slice(0, 36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
115758
115858
|
let disposed = false;
|
|
115759
115859
|
let stateTimer;
|
|
115760
115860
|
let exportTimer;
|
|
115761
115861
|
let lastPushKey = "";
|
|
115762
|
-
let lastSelectionKey =
|
|
115763
|
-
let
|
|
115764
|
-
let
|
|
115765
|
-
|
|
115862
|
+
let lastSelectionKey = selectionKeyOf(editor);
|
|
115863
|
+
let revision = 0;
|
|
115864
|
+
let sequence = 0;
|
|
115865
|
+
let imageLimit;
|
|
115866
|
+
let inFlight = new AbortController();
|
|
115867
|
+
let exporting = false;
|
|
115868
|
+
let exportAgain = false;
|
|
115869
|
+
let prepared = {};
|
|
115870
|
+
const originalCache = /* @__PURE__ */ new Map();
|
|
115871
|
+
let preparedItems;
|
|
115872
|
+
const stopListening = editor.store.listen((event) => {
|
|
115766
115873
|
if (disposed) return;
|
|
115767
|
-
const
|
|
115768
|
-
|
|
115769
|
-
|
|
115770
|
-
|
|
115771
|
-
|
|
115772
|
-
|
|
115773
|
-
|
|
115774
|
-
|
|
115874
|
+
const key = selectionKeyOf(editor);
|
|
115875
|
+
const changes = event.changes;
|
|
115876
|
+
const documentChanged = [
|
|
115877
|
+
...Object.values(changes.added),
|
|
115878
|
+
...Object.values(changes.removed),
|
|
115879
|
+
...Object.values(changes.updated).map((pair) => pair[1])
|
|
115880
|
+
].some((record) => record.typeName === "shape" || record.typeName === "asset" || record.typeName === "page");
|
|
115881
|
+
if (key !== lastSelectionKey || documentChanged) {
|
|
115882
|
+
lastSelectionKey = key;
|
|
115883
|
+
invalidate();
|
|
115775
115884
|
}
|
|
115776
115885
|
if (stateTimer === void 0) stateTimer = setTimeout(() => {
|
|
115777
115886
|
stateTimer = void 0;
|
|
115778
|
-
|
|
115887
|
+
pushState();
|
|
115779
115888
|
}, STATE_DEBOUNCE_MS);
|
|
115780
115889
|
}, {
|
|
115781
115890
|
source: "all",
|
|
115782
115891
|
scope: "all"
|
|
115783
115892
|
});
|
|
115784
|
-
|
|
115893
|
+
function invalidate() {
|
|
115894
|
+
revision++;
|
|
115895
|
+
inFlight.abort();
|
|
115896
|
+
inFlight = new AbortController();
|
|
115897
|
+
prepared = {};
|
|
115898
|
+
preparedItems = void 0;
|
|
115899
|
+
const count = editor.getSelectedShapeIds().length;
|
|
115900
|
+
onStatus({
|
|
115901
|
+
phase: count > 0 ? "preparing" : "idle",
|
|
115902
|
+
count
|
|
115903
|
+
});
|
|
115904
|
+
if (exportTimer !== void 0) clearTimeout(exportTimer);
|
|
115905
|
+
if (count > 0) exportTimer = setTimeout(() => {
|
|
115906
|
+
exportTimer = void 0;
|
|
115907
|
+
prepare();
|
|
115908
|
+
}, SELECTION_EXPORT_DELAY_MS);
|
|
115909
|
+
}
|
|
115910
|
+
async function pushState(force = false) {
|
|
115785
115911
|
if (disposed) return;
|
|
115912
|
+
const currentRevision = revision;
|
|
115786
115913
|
const summary = summarize(editor);
|
|
115787
|
-
|
|
115788
|
-
|
|
115789
|
-
if (elapsed < MIN_PUSH_INTERVAL_MS) {
|
|
115790
|
-
if (stateTimer !== void 0) clearTimeout(stateTimer);
|
|
115791
|
-
stateTimer = setTimeout(() => {
|
|
115792
|
-
stateTimer = void 0;
|
|
115793
|
-
flush(withScreenshot);
|
|
115794
|
-
}, MIN_PUSH_INTERVAL_MS - elapsed);
|
|
115795
|
-
return;
|
|
115796
|
-
}
|
|
115797
|
-
let selectionImage;
|
|
115798
|
-
if (withScreenshot && summary.selectionCount > 0) try {
|
|
115799
|
-
const selectedBefore = editor.getSelectedShapes();
|
|
115800
|
-
const { url } = await editor.toImageDataUrl(selectedBefore, {
|
|
115801
|
-
background: true,
|
|
115802
|
-
padding: 16,
|
|
115803
|
-
scale: 1,
|
|
115804
|
-
pixelRatio: 2
|
|
115805
|
-
});
|
|
115806
|
-
if (disposed) return;
|
|
115807
|
-
const selectedAfter = editor.getSelectedShapes();
|
|
115808
|
-
const idsBefore = new Set(selectedBefore.map((shape) => String(shape.id)));
|
|
115809
|
-
if (!(selectedBefore.length === selectedAfter.length && selectedAfter.every((shape) => idsBefore.has(String(shape.id))))) return;
|
|
115810
|
-
if (url.length > MAX_SCREENSHOT_DATAURL_CHARS) console.warn("[dsh-image-gen] canvas selection screenshot skipped: too large");
|
|
115811
|
-
else selectionImage = url;
|
|
115812
|
-
} catch (error) {
|
|
115813
|
-
console.warn("[dsh-image-gen] canvas selection screenshot failed:", error);
|
|
115814
|
-
}
|
|
115914
|
+
const key = `${revision}:${summary.stateKey}`;
|
|
115915
|
+
if (!force && key === lastPushKey) return;
|
|
115815
115916
|
const payload = {
|
|
115816
115917
|
clientInstance,
|
|
115817
115918
|
connected: true,
|
|
@@ -115820,43 +115921,104 @@ img.ProseMirror-separator {
|
|
|
115820
115921
|
selection: {
|
|
115821
115922
|
count: summary.selectionCount,
|
|
115822
115923
|
kinds: summary.selectionKinds,
|
|
115823
|
-
items: summary.selectionItems
|
|
115924
|
+
items: preparedItems ?? summary.selectionItems
|
|
115824
115925
|
},
|
|
115926
|
+
selectionRevision: String(revision),
|
|
115927
|
+
...summary.selectionCount > 0 ? { selectionStatus: "preparing" } : {},
|
|
115928
|
+
...prepared,
|
|
115929
|
+
sequence: ++sequence,
|
|
115825
115930
|
updatedAt: Date.now()
|
|
115826
115931
|
};
|
|
115827
|
-
|
|
115828
|
-
lastPushKey = summary.stateKey;
|
|
115829
|
-
lastPostAt = Date.now();
|
|
115830
|
-
const controller = new AbortController();
|
|
115831
|
-
inFlight = controller;
|
|
115932
|
+
const requestSequence = sequence;
|
|
115832
115933
|
try {
|
|
115833
|
-
await fetch(CANVAS_STATE_ROUTE, {
|
|
115934
|
+
const response = await fetch(CANVAS_STATE_ROUTE, {
|
|
115834
115935
|
method: "POST",
|
|
115835
115936
|
credentials: "same-origin",
|
|
115836
115937
|
headers: { "content-type": "application/json" },
|
|
115837
115938
|
body: JSON.stringify(payload),
|
|
115838
|
-
signal:
|
|
115939
|
+
signal: AbortSignal.any([inFlight.signal, AbortSignal.timeout(3e4)])
|
|
115940
|
+
});
|
|
115941
|
+
if (!response.ok) throw new Error(`state-sync-failed (${response.status})`);
|
|
115942
|
+
if (disposed || revision !== currentRevision || sequence !== requestSequence) return;
|
|
115943
|
+
lastPushKey = key;
|
|
115944
|
+
onStatus({
|
|
115945
|
+
phase: payload.selectionStatus ?? "idle",
|
|
115946
|
+
count: summary.selectionCount,
|
|
115947
|
+
...payload.selectionError ? { error: payload.selectionError } : {}
|
|
115839
115948
|
});
|
|
115840
115949
|
} catch (error) {
|
|
115841
|
-
if (
|
|
115950
|
+
if (disposed || revision !== currentRevision || sequence !== requestSequence) return;
|
|
115842
115951
|
lastPushKey = "";
|
|
115952
|
+
onStatus({
|
|
115953
|
+
phase: "error",
|
|
115954
|
+
count: summary.selectionCount,
|
|
115955
|
+
error: error instanceof Error ? error.message : "state-sync-failed"
|
|
115956
|
+
});
|
|
115957
|
+
}
|
|
115958
|
+
}
|
|
115959
|
+
async function prepare() {
|
|
115960
|
+
if (disposed) return;
|
|
115961
|
+
if (exporting) {
|
|
115962
|
+
exportAgain = true;
|
|
115963
|
+
return;
|
|
115964
|
+
}
|
|
115965
|
+
exporting = true;
|
|
115966
|
+
const currentRevision = revision;
|
|
115967
|
+
const signal = AbortSignal.any([inFlight.signal, AbortSignal.timeout(3e4)]);
|
|
115968
|
+
const shapes = editor.getSelectedShapes();
|
|
115969
|
+
const items = shapes.slice(0, 16).map((shape) => describeShape(editor, shape));
|
|
115970
|
+
let selectionImage;
|
|
115971
|
+
let failure;
|
|
115972
|
+
try {
|
|
115973
|
+
imageLimit ??= await readCanvasImageLimit(signal);
|
|
115974
|
+
try {
|
|
115975
|
+
selectionImage = await exportSelectionPreview(editor, shapes, imageLimit, signal);
|
|
115976
|
+
} catch (error) {
|
|
115977
|
+
signal.throwIfAborted();
|
|
115978
|
+
failure = error instanceof Error ? error.message : "preview-export-failed";
|
|
115979
|
+
}
|
|
115980
|
+
if (shapes.length > 16 && shapes.some((shape) => shape.type === "image")) throw new Error("too-many-selected-shapes");
|
|
115981
|
+
for (const [index, shape] of shapes.entries()) {
|
|
115982
|
+
signal.throwIfAborted();
|
|
115983
|
+
if (shape.type !== "image") continue;
|
|
115984
|
+
const item = items[index];
|
|
115985
|
+
if (item === void 0) continue;
|
|
115986
|
+
const attachment = await syncCanvasOriginal(editor, shape, imageLimit, signal, originalCache);
|
|
115987
|
+
item.attachment = attachment;
|
|
115988
|
+
item.attachmentId = attachment.attachmentId;
|
|
115989
|
+
}
|
|
115990
|
+
} catch (error) {
|
|
115991
|
+
failure = error instanceof Error ? error.message : "selection-sync-failed";
|
|
115992
|
+
} finally {
|
|
115993
|
+
exporting = false;
|
|
115994
|
+
}
|
|
115995
|
+
if (!disposed && revision === currentRevision) {
|
|
115996
|
+
preparedItems = items;
|
|
115997
|
+
prepared = {
|
|
115998
|
+
selectionStatus: failure === void 0 ? "ready" : "error",
|
|
115999
|
+
...failure === void 0 ? {} : { selectionError: failure.slice(0, 300) },
|
|
116000
|
+
...selectionImage === void 0 ? {} : { selectionImage }
|
|
116001
|
+
};
|
|
116002
|
+
await pushState(true);
|
|
116003
|
+
}
|
|
116004
|
+
if (exportAgain && !disposed) {
|
|
116005
|
+
exportAgain = false;
|
|
116006
|
+
prepare();
|
|
115843
116007
|
}
|
|
115844
116008
|
}
|
|
116009
|
+
invalidate();
|
|
115845
116010
|
stateTimer = setTimeout(() => {
|
|
115846
116011
|
stateTimer = void 0;
|
|
115847
|
-
|
|
116012
|
+
pushState();
|
|
115848
116013
|
}, STATE_DEBOUNCE_MS);
|
|
115849
|
-
|
|
115850
|
-
exportTimer = void 0;
|
|
115851
|
-
flush(true);
|
|
115852
|
-
}, SELECTION_EXPORT_DELAY_MS);
|
|
115853
|
-
return () => {
|
|
116014
|
+
const stop = () => {
|
|
115854
116015
|
if (disposed) return;
|
|
115855
116016
|
disposed = true;
|
|
115856
116017
|
if (stateTimer !== void 0) clearTimeout(stateTimer);
|
|
115857
116018
|
if (exportTimer !== void 0) clearTimeout(exportTimer);
|
|
115858
116019
|
stopListening();
|
|
115859
|
-
inFlight
|
|
116020
|
+
inFlight.abort();
|
|
116021
|
+
originalCache.clear();
|
|
115860
116022
|
fetch(CANVAS_STATE_ROUTE, {
|
|
115861
116023
|
method: "POST",
|
|
115862
116024
|
credentials: "same-origin",
|
|
@@ -115866,10 +116028,15 @@ img.ProseMirror-separator {
|
|
|
115866
116028
|
clientInstance,
|
|
115867
116029
|
connected: false,
|
|
115868
116030
|
nodeCount: 0,
|
|
116031
|
+
sequence: ++sequence,
|
|
115869
116032
|
updatedAt: Date.now()
|
|
115870
116033
|
})
|
|
115871
116034
|
}).catch(() => {});
|
|
115872
116035
|
};
|
|
116036
|
+
return Object.assign(stop, { retry: () => {
|
|
116037
|
+
invalidate();
|
|
116038
|
+
pushState(true);
|
|
116039
|
+
} });
|
|
115873
116040
|
}
|
|
115874
116041
|
//#endregion
|
|
115875
116042
|
//#region lib/types/client/tl/tl-canvas-bridge.js
|
|
@@ -116112,56 +116279,84 @@ img.ProseMirror-separator {
|
|
|
116112
116279
|
}
|
|
116113
116280
|
return true;
|
|
116114
116281
|
}
|
|
116115
|
-
const
|
|
116282
|
+
const TL_COMPONENTS = { Toolbar: StudioToolbar };
|
|
116283
|
+
const StudioTlCanvas = (0, react.memo)(function StudioTlCanvas({ lang = "zh" }) {
|
|
116116
116284
|
const sessionId = (0, react.useId)();
|
|
116117
|
-
|
|
116285
|
+
const [syncStatus, setSyncStatus] = (0, react.useState)({
|
|
116286
|
+
phase: "idle",
|
|
116287
|
+
count: 0
|
|
116288
|
+
});
|
|
116289
|
+
const retrySync = (0, react.useRef)(() => {});
|
|
116290
|
+
const onMount = (0, react.useCallback)((editor) => {
|
|
116291
|
+
let active = true;
|
|
116292
|
+
applyBrandTheme(editor);
|
|
116293
|
+
editor.updateInstanceState({ isGridMode: true });
|
|
116294
|
+
console.info(`[dsh-image-gen] tldraw mounted (instance ${editor.id})`);
|
|
116295
|
+
const stopLanding = registerTlLandingConsumer((items) => {
|
|
116296
|
+
const generation = getTlLandingGeneration();
|
|
116297
|
+
return landTlItems(editor, items, () => active && generation === getTlLandingGeneration());
|
|
116298
|
+
});
|
|
116299
|
+
let stopSync = startCanvasSync(editor, setSyncStatus);
|
|
116300
|
+
retrySync.current = () => stopSync.retry();
|
|
116301
|
+
const clear = () => {
|
|
116302
|
+
stopSync();
|
|
116303
|
+
editor.complete();
|
|
116304
|
+
editor.run(() => {
|
|
116305
|
+
editor.selectNone();
|
|
116306
|
+
const pages = editor.getPages();
|
|
116307
|
+
for (const page of pages) editor.deleteShapes([...editor.getPageShapeIds(page.id)]);
|
|
116308
|
+
for (const page of pages.slice(1)) editor.deletePage(page.id);
|
|
116309
|
+
editor.deleteAssets(editor.getAssets());
|
|
116310
|
+
editor.setCamera({
|
|
116311
|
+
x: 0,
|
|
116312
|
+
y: 0,
|
|
116313
|
+
z: 1
|
|
116314
|
+
});
|
|
116315
|
+
}, {
|
|
116316
|
+
history: "ignore",
|
|
116317
|
+
ignoreShapeLock: true
|
|
116318
|
+
});
|
|
116319
|
+
editor.clearHistory();
|
|
116320
|
+
stopSync = startCanvasSync(editor, setSyncStatus);
|
|
116321
|
+
};
|
|
116322
|
+
mountedCanvases.add(clear);
|
|
116323
|
+
return () => {
|
|
116324
|
+
active = false;
|
|
116325
|
+
mountedCanvases.delete(clear);
|
|
116326
|
+
stopLanding();
|
|
116327
|
+
stopSync();
|
|
116328
|
+
retrySync.current = () => {};
|
|
116329
|
+
};
|
|
116330
|
+
}, []);
|
|
116331
|
+
const statusText = lang === "en" ? syncStatus.phase === "ready" ? `Selection ready · ${syncStatus.count} shapes` : syncStatus.phase === "preparing" ? `Preparing selection · ${syncStatus.count} shapes…` : "Selection sync failed" : syncStatus.phase === "ready" ? `选区已就绪 · ${syncStatus.count} 个元素` : syncStatus.phase === "preparing" ? `正在准备选区 · ${syncStatus.count} 个元素…` : "选区同步失败";
|
|
116332
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
116118
116333
|
className: "dsh-ig-tl-canvas",
|
|
116119
|
-
children: (0, react_jsx_runtime.jsx)(Tldraw, {
|
|
116334
|
+
children: [(0, react_jsx_runtime.jsx)(Tldraw, {
|
|
116120
116335
|
persistenceKey: "dsh-image-gen-workbench-v1",
|
|
116121
116336
|
sessionId,
|
|
116122
|
-
components:
|
|
116123
|
-
onMount
|
|
116124
|
-
|
|
116125
|
-
|
|
116126
|
-
|
|
116127
|
-
|
|
116128
|
-
|
|
116129
|
-
|
|
116130
|
-
|
|
116131
|
-
|
|
116132
|
-
|
|
116133
|
-
|
|
116134
|
-
|
|
116135
|
-
editor.complete();
|
|
116136
|
-
editor.run(() => {
|
|
116137
|
-
editor.selectNone();
|
|
116138
|
-
const pages = editor.getPages();
|
|
116139
|
-
for (const page of pages) editor.deleteShapes([...editor.getPageShapeIds(page.id)]);
|
|
116140
|
-
for (const page of pages.slice(1)) editor.deletePage(page.id);
|
|
116141
|
-
editor.deleteAssets(editor.getAssets());
|
|
116142
|
-
editor.setCamera({
|
|
116143
|
-
x: 0,
|
|
116144
|
-
y: 0,
|
|
116145
|
-
z: 1
|
|
116146
|
-
});
|
|
116147
|
-
}, {
|
|
116148
|
-
history: "ignore",
|
|
116149
|
-
ignoreShapeLock: true
|
|
116150
|
-
});
|
|
116151
|
-
editor.clearHistory();
|
|
116152
|
-
stopSync = startCanvasSync(editor);
|
|
116153
|
-
};
|
|
116154
|
-
mountedCanvases.add(clear);
|
|
116155
|
-
return () => {
|
|
116156
|
-
active = false;
|
|
116157
|
-
mountedCanvases.delete(clear);
|
|
116158
|
-
stopLanding();
|
|
116159
|
-
stopSync();
|
|
116160
|
-
};
|
|
116161
|
-
}
|
|
116162
|
-
})
|
|
116337
|
+
components: TL_COMPONENTS,
|
|
116338
|
+
onMount
|
|
116339
|
+
}), syncStatus.phase !== "idle" && (0, react_jsx_runtime.jsxs)("div", {
|
|
116340
|
+
className: "dsh-ig-canvas-sync-status",
|
|
116341
|
+
"data-phase": syncStatus.phase,
|
|
116342
|
+
role: "status",
|
|
116343
|
+
"aria-live": "polite",
|
|
116344
|
+
children: [(0, react_jsx_runtime.jsxs)("span", { children: [statusText, syncStatus.phase === "error" ? `:${canvasSyncError(syncStatus.error, lang)}` : ""] }), syncStatus.phase === "error" && (0, react_jsx_runtime.jsx)("button", {
|
|
116345
|
+
type: "button",
|
|
116346
|
+
onClick: () => retrySync.current(),
|
|
116347
|
+
children: lang === "en" ? "Retry" : "重试"
|
|
116348
|
+
})]
|
|
116349
|
+
})]
|
|
116163
116350
|
});
|
|
116164
116351
|
});
|
|
116352
|
+
function canvasSyncError(error, lang) {
|
|
116353
|
+
const en = lang === "en";
|
|
116354
|
+
if (error?.includes("too-many-selected")) return en ? "Select at most 16 shapes to edit with originals." : "使用原图编辑时,请一次选择不超过 16 个元素。";
|
|
116355
|
+
if (error?.includes("too-large")) return en ? "An image exceeds the size limit. Reduce its size and retry." : "图片超出大小限制,请缩小后重试。";
|
|
116356
|
+
if (error?.includes("unsupported")) return en ? "Use PNG, JPEG, WebP or GIF images." : "请使用 PNG、JPEG、WebP 或 GIF 图片。";
|
|
116357
|
+
if (error?.includes("host-unavailable")) return en ? "Restart DSH to load the updated plugin, then retry." : "请重启 DSH 加载新版插件后重试。";
|
|
116358
|
+
return en ? "Check the connection or re-import an unavailable image, then retry." : "请检查连接;若原图已失效,请重新导入后重试。";
|
|
116359
|
+
}
|
|
116165
116360
|
//#endregion
|
|
116166
116361
|
//#region lib/types/client/studio-view.js
|
|
116167
116362
|
/** Below this workbench width the recent-generations rail folds automatically. */
|
|
@@ -116246,6 +116441,7 @@ img.ProseMirror-separator {
|
|
|
116246
116441
|
favorited: "已收藏",
|
|
116247
116442
|
favoriteAdded: "已添加到收藏",
|
|
116248
116443
|
favoriteRemoved: "已取消收藏",
|
|
116444
|
+
gallerySaveFailed: "保存到图库失败,请重试",
|
|
116249
116445
|
loadMore: "加载更多 ({n})",
|
|
116250
116446
|
deleteModalTitle: "从图库删除",
|
|
116251
116447
|
deleteModalDesc: "确定从图库中删除这张图片吗?(原聊天记录不会受影响)",
|
|
@@ -116354,6 +116550,7 @@ img.ProseMirror-separator {
|
|
|
116354
116550
|
favorited: "Favorited",
|
|
116355
116551
|
favoriteAdded: "Added to favorites",
|
|
116356
116552
|
favoriteRemoved: "Removed from favorites",
|
|
116553
|
+
gallerySaveFailed: "Failed to save to the gallery, please retry",
|
|
116357
116554
|
loadMore: "Load more ({n})",
|
|
116358
116555
|
deleteModalTitle: "Delete from gallery",
|
|
116359
116556
|
deleteModalDesc: "Remove this image from the local gallery? (Chat history remains unaffected)",
|
|
@@ -117018,7 +117215,7 @@ img.ProseMirror-separator {
|
|
|
117018
117215
|
...targetRoot ? { workspacePath: targetRoot } : {},
|
|
117019
117216
|
...workspace?.workspaceId ? { workspaceId: workspace.workspaceId } : {}
|
|
117020
117217
|
};
|
|
117021
|
-
await saveGalleryItem(updatedItem);
|
|
117218
|
+
if (!await saveGalleryItem(updatedItem)) throw new Error(t("gallerySaveFailed"));
|
|
117022
117219
|
return updatedItem;
|
|
117023
117220
|
};
|
|
117024
117221
|
const handleSaveToGallery = async () => {
|
|
@@ -117112,16 +117309,29 @@ img.ProseMirror-separator {
|
|
|
117112
117309
|
} : i) : null);
|
|
117113
117310
|
flash(nextFav ? t("favoriteAdded") : t("favoriteRemoved"));
|
|
117114
117311
|
} else {
|
|
117115
|
-
|
|
117116
|
-
|
|
117117
|
-
|
|
117118
|
-
|
|
117119
|
-
|
|
117120
|
-
|
|
117121
|
-
|
|
117122
|
-
|
|
117123
|
-
|
|
117124
|
-
|
|
117312
|
+
if (!!selected.isFavorite) {
|
|
117313
|
+
setSelected((curr) => curr !== null && curr.id === targetId ? {
|
|
117314
|
+
...curr,
|
|
117315
|
+
isFavorite: false
|
|
117316
|
+
} : curr);
|
|
117317
|
+
if (currentBatch) setCurrentBatch((prev) => prev ? prev.map((i) => i.id === targetId ? {
|
|
117318
|
+
...i,
|
|
117319
|
+
isFavorite: false
|
|
117320
|
+
} : i) : null);
|
|
117321
|
+
flash(t("favoriteRemoved"));
|
|
117322
|
+
return;
|
|
117323
|
+
}
|
|
117324
|
+
try {
|
|
117325
|
+
const updated = await saveGalleryEntry({
|
|
117326
|
+
...selected,
|
|
117327
|
+
isFavorite: true
|
|
117328
|
+
});
|
|
117329
|
+
setSelected((curr) => curr !== null && curr.id === targetId ? updated : curr);
|
|
117330
|
+
if (currentBatch) setCurrentBatch((prev) => prev ? prev.map((i) => i.id === targetId ? updated : i) : prev);
|
|
117331
|
+
flash(t("favoriteAdded"));
|
|
117332
|
+
} catch (err) {
|
|
117333
|
+
setError(err instanceof Error ? err.message : t("gallerySaveFailed"));
|
|
117334
|
+
}
|
|
117125
117335
|
}
|
|
117126
117336
|
};
|
|
117127
117337
|
const stepLightbox = (delta) => {
|
|
@@ -117353,7 +117563,7 @@ img.ProseMirror-separator {
|
|
|
117353
117563
|
})
|
|
117354
117564
|
]
|
|
117355
117565
|
}),
|
|
117356
|
-
canvasSurface === "infinite" ? (0, react_jsx_runtime.jsx)(StudioTlCanvas, {}) : (0, react_jsx_runtime.jsx)("div", {
|
|
117566
|
+
canvasSurface === "infinite" ? (0, react_jsx_runtime.jsx)(StudioTlCanvas, { lang }) : (0, react_jsx_runtime.jsx)("div", {
|
|
117357
117567
|
className: `dsh-ig-canvas ${isDragging ? "is-dragging" : ""}`,
|
|
117358
117568
|
ref: canvasRef,
|
|
117359
117569
|
onMouseDown: handleCanvasMouseDown,
|
|
@@ -119434,6 +119644,7 @@ img.ProseMirror-separator {
|
|
|
119434
119644
|
confirmRegenerate: "开始生成",
|
|
119435
119645
|
regenerating: "正在重新生成…",
|
|
119436
119646
|
regenerateSuccess: "已生成新图片并收录到画廊",
|
|
119647
|
+
regenerateSaveFailed: "图片已生成,但保存到图库失败,请重试",
|
|
119437
119648
|
regenerateFailed: "重新生成失败",
|
|
119438
119649
|
delete: "从画廊删除",
|
|
119439
119650
|
confirmDelete: "确定要从画廊中删除这张图片吗?(不会影响原聊天记录)",
|
|
@@ -119519,6 +119730,7 @@ img.ProseMirror-separator {
|
|
|
119519
119730
|
confirmRegenerate: "Generate",
|
|
119520
119731
|
regenerating: "Regenerating…",
|
|
119521
119732
|
regenerateSuccess: "New image generated and added to gallery",
|
|
119733
|
+
regenerateSaveFailed: "Image generated, but saving to the gallery failed. Please retry.",
|
|
119522
119734
|
regenerateFailed: "Regeneration failed",
|
|
119523
119735
|
delete: "Delete from gallery",
|
|
119524
119736
|
confirmDelete: "Are you sure you want to remove this image from the gallery? (Chat history will not be affected)",
|
|
@@ -119934,11 +120146,11 @@ img.ProseMirror-separator {
|
|
|
119934
120146
|
...activeWorkspace?.workspaceId ? { workspaceId: activeWorkspace.workspaceId } : {},
|
|
119935
120147
|
...currentSessionId ? { sessionId: currentSessionId } : {}
|
|
119936
120148
|
};
|
|
119937
|
-
await saveGalleryItem(newItem);
|
|
120149
|
+
const savedOk = await saveGalleryItem(newItem);
|
|
119938
120150
|
if (activeTab !== "gallery") setActiveTab("gallery");
|
|
119939
120151
|
if (search.trim().length > 0) setSearch("");
|
|
119940
120152
|
openPreviewItem(newItem);
|
|
119941
|
-
showToast(t("regenerateSuccess"));
|
|
120153
|
+
showToast(savedOk ? t("regenerateSuccess") : t("regenerateSaveFailed"));
|
|
119942
120154
|
} catch (err) {
|
|
119943
120155
|
if (controller.signal.aborted) return;
|
|
119944
120156
|
showToast(err instanceof Error ? err.message : String(err));
|
|
@@ -121284,6 +121496,9 @@ img.ProseMirror-separator {
|
|
|
121284
121496
|
|
|
121285
121497
|
/* tldraw infinite canvas surface (P0 spike) */
|
|
121286
121498
|
.dsh-ig-tl-canvas{position:relative;flex:1 1 0;height:0;min-height:0;overflow:hidden;isolation:isolate}
|
|
121499
|
+
.dsh-ig-canvas-sync-status{position:absolute;z-index:500;bottom:48px;left:60px;right:16px;display:flex;align-items:center;justify-content:center;gap:8px;max-width:560px;margin:auto;padding:7px 12px;border:1px solid var(--dsh-ig-border);border-radius:10px;background:var(--dsh-ig-surface,#fff);color:var(--dsh-ig-text,#475569);box-shadow:0 2px 8px #0001;font-size:12px;pointer-events:none}
|
|
121500
|
+
.dsh-ig-canvas-sync-status[data-phase='error']{color:#b42318;border-color:#fecdca}
|
|
121501
|
+
.dsh-ig-canvas-sync-status button{pointer-events:auto;flex-shrink:0;border:1px solid currentColor;border-radius:6px;padding:3px 9px;background:transparent;color:inherit;cursor:pointer}
|
|
121287
121502
|
|
|
121288
121503
|
/* ---- Right-sidebar seat variant (DSH 0.1.5 'sidebar.right.pane.tab') ----
|
|
121289
121504
|
The seat is a narrow panel beside the conversation column, not a full
|
|
@@ -122005,7 +122220,7 @@ img.ProseMirror-separator {
|
|
|
122005
122220
|
`;
|
|
122006
122221
|
//#endregion
|
|
122007
122222
|
//#region lib/types/client/index.js
|
|
122008
|
-
console.info(`[dsh-image-gen] canvas bundle 2026-09-
|
|
122223
|
+
console.info(`[dsh-image-gen] canvas bundle 2026-09-20T16:35:15.504Z`);
|
|
122009
122224
|
/** Right-sidebar tab-type registration id (DSH 0.1.5 `sidebarRightTabs`). */
|
|
122010
122225
|
const SIDEBAR_STUDIO_TAB_ID = "dsh-image-gen/studio";
|
|
122011
122226
|
/** The tab kind: a page type opened by kind, recognizing no resource address. */
|
|
@@ -122066,6 +122281,7 @@ img.ProseMirror-separator {
|
|
|
122066
122281
|
keyCleared: "已清除 Key",
|
|
122067
122282
|
clearKeyFailed: "清除 Key 失败",
|
|
122068
122283
|
saveKeyFailed: "保存 Key 失败",
|
|
122284
|
+
saveKeyFirst: "请先保存 API Key,再拉取模型或测试连接。",
|
|
122069
122285
|
clearKeyUnsupported: "当前版本 DSH 不支持在此清除 Key,请到凭据管理中删除。",
|
|
122070
122286
|
endpoint: "接口地址",
|
|
122071
122287
|
reset: "重置",
|
|
@@ -122080,6 +122296,18 @@ img.ProseMirror-separator {
|
|
|
122080
122296
|
editExtra: "附加 JSON 字段",
|
|
122081
122297
|
editExtraPlaceholder: "{\"watermark\": false, \"prompt_extend\": true}",
|
|
122082
122298
|
editExtraHint: "仅 JSON 形态生效;会合并到请求体末尾,可覆盖默认字段。留空表示不附加。",
|
|
122299
|
+
arkOutputFormat: "输出格式",
|
|
122300
|
+
arkOutputFormatPng: "PNG(无损,带透明通道)",
|
|
122301
|
+
arkOutputFormatJpeg: "JPEG(方舟默认,有损)",
|
|
122302
|
+
arkOutputFormatHint: "JPEG 会在主体边缘留下压缩色斑,抠图时变成难去掉的彩色毛边。要后期加工就选 PNG。",
|
|
122303
|
+
arkWatermark: "AI 生成水印",
|
|
122304
|
+
arkWatermarkOn: "添加(方舟默认)",
|
|
122305
|
+
arkWatermarkOff: "不添加",
|
|
122306
|
+
arkWatermarkHint: "方舟默认在右下角烙上“AI 生成”标识。做游戏素材或需要后期处理时关掉。",
|
|
122307
|
+
arkBackground: "背景",
|
|
122308
|
+
arkBackgroundOpaque: "不透明(方舟默认)",
|
|
122309
|
+
arkBackgroundTransparent: "透明",
|
|
122310
|
+
arkBackgroundHint: "仅图生图有效,且要求参考图本身带透明通道;文生图会被方舟拒绝,因此这一项只在图生图时发出。",
|
|
122083
122311
|
editExtraInvalid: "附加 JSON 字段必须是合法的 JSON 对象。",
|
|
122084
122312
|
endpointHintSeedream: "火山方舟兼容的 /api/v3 地址。",
|
|
122085
122313
|
endpointHintDashScope: "阿里云百炼 DashScope 官方接口地址。",
|
|
@@ -122136,6 +122364,7 @@ img.ProseMirror-separator {
|
|
|
122136
122364
|
confirmRegenerate: "确认生成",
|
|
122137
122365
|
regenerating: "重新生成中…",
|
|
122138
122366
|
regenerateFailed: "重新生成失败",
|
|
122367
|
+
regenerateSaveFailed: "已重新生成,但保存到图库失败,请重试",
|
|
122139
122368
|
versionPrevious: "上一版本",
|
|
122140
122369
|
versionNext: "下一版本",
|
|
122141
122370
|
versionLabel: "图片版本 {current}/{total}"
|
|
@@ -122195,6 +122424,7 @@ img.ProseMirror-separator {
|
|
|
122195
122424
|
keyCleared: "Key cleared",
|
|
122196
122425
|
clearKeyFailed: "Failed to clear key",
|
|
122197
122426
|
saveKeyFailed: "Failed to save the key",
|
|
122427
|
+
saveKeyFirst: "Save the API key first, then fetch models or test the connection.",
|
|
122198
122428
|
clearKeyUnsupported: "This DSH build cannot clear keys here; remove it from credential management instead.",
|
|
122199
122429
|
endpoint: "Endpoint / Base URL",
|
|
122200
122430
|
reset: "Reset",
|
|
@@ -122209,6 +122439,18 @@ img.ProseMirror-separator {
|
|
|
122209
122439
|
editExtra: "Extra JSON fields",
|
|
122210
122440
|
editExtraPlaceholder: "{\"watermark\": false, \"prompt_extend\": true}",
|
|
122211
122441
|
editExtraHint: "Only used with the JSON format; merged into the request body last and may override defaults. Leave empty for none.",
|
|
122442
|
+
arkOutputFormat: "Output format",
|
|
122443
|
+
arkOutputFormatPng: "PNG (lossless, keeps an alpha channel)",
|
|
122444
|
+
arkOutputFormatJpeg: "JPEG (Ark default, lossy)",
|
|
122445
|
+
arkOutputFormatHint: "JPEG ringing around the subject turns into coloured fringing that is hard to remove when cutting the background out. Pick PNG if you plan to edit the result.",
|
|
122446
|
+
arkWatermark: "AI generated watermark",
|
|
122447
|
+
arkWatermarkOn: "Add (Ark default)",
|
|
122448
|
+
arkWatermarkOff: "Do not add",
|
|
122449
|
+
arkWatermarkHint: "Ark stamps an \"AI generated\" mark into the bottom-right corner by default. Turn it off for game assets or any post-processing.",
|
|
122450
|
+
arkBackground: "Background",
|
|
122451
|
+
arkBackgroundOpaque: "Opaque (Ark default)",
|
|
122452
|
+
arkBackgroundTransparent: "Transparent",
|
|
122453
|
+
arkBackgroundHint: "Image-to-image only, and every reference image must already carry an alpha channel; Ark rejects it for text-to-image, so it is sent on edits alone.",
|
|
122212
122454
|
editExtraInvalid: "Extra JSON fields must be a valid JSON object.",
|
|
122213
122455
|
endpointHintSeedream: "Volcengine Ark compatible /api/v3 base URL.",
|
|
122214
122456
|
endpointHintDashScope: "Official Aliyun DashScope endpoint.",
|
|
@@ -122265,6 +122507,7 @@ img.ProseMirror-separator {
|
|
|
122265
122507
|
confirmRegenerate: "Regenerate",
|
|
122266
122508
|
regenerating: "Regenerating…",
|
|
122267
122509
|
regenerateFailed: "Regeneration failed",
|
|
122510
|
+
regenerateSaveFailed: "Regenerated, but saving to the gallery failed. Please retry.",
|
|
122268
122511
|
versionPrevious: "Previous version",
|
|
122269
122512
|
versionNext: "Next version",
|
|
122270
122513
|
versionLabel: "Image version {current}/{total}"
|
|
@@ -122621,16 +122864,25 @@ img.ProseMirror-separator {
|
|
|
122621
122864
|
} };
|
|
122622
122865
|
const injectSettingsItem = (owner) => {
|
|
122623
122866
|
const ownerRegister = owner.slots.register.bind(owner.slots);
|
|
122867
|
+
const injectSettingsFace = () => ({
|
|
122868
|
+
scope,
|
|
122869
|
+
credentials: credentialsProxy,
|
|
122870
|
+
credentialsAvailable,
|
|
122871
|
+
locale,
|
|
122872
|
+
credentialEvents
|
|
122873
|
+
});
|
|
122874
|
+
owner.slots.inject("settings.plugins.tab", () => ownerRegister({
|
|
122875
|
+
name: "settings.plugins.tab",
|
|
122876
|
+
id: IMAGE_GENERATION_NAMESPACE,
|
|
122877
|
+
order: 30,
|
|
122878
|
+
label: () => locale?.getSnapshot?.()?.active?.startsWith("en") ? "Image generation" : "图像生成",
|
|
122879
|
+
locale,
|
|
122880
|
+
inject: injectSettingsFace
|
|
122881
|
+
}, ImageGenerationSettingsCard));
|
|
122624
122882
|
owner.slots.inject("settings.plugin.item", () => ownerRegister({
|
|
122625
122883
|
name: "settings.plugin.item",
|
|
122626
122884
|
key: IMAGE_GENERATION_NAMESPACE,
|
|
122627
|
-
inject:
|
|
122628
|
-
scope,
|
|
122629
|
-
credentials: credentialsProxy,
|
|
122630
|
-
credentialsAvailable,
|
|
122631
|
-
locale,
|
|
122632
|
-
credentialEvents
|
|
122633
|
-
})
|
|
122885
|
+
inject: injectSettingsFace
|
|
122634
122886
|
}, ImageGenerationSettingsCard));
|
|
122635
122887
|
};
|
|
122636
122888
|
const injectComposerPill = (owner) => {
|
|
@@ -122793,7 +123045,10 @@ img.ProseMirror-separator {
|
|
|
122793
123045
|
activeWorkflow: "",
|
|
122794
123046
|
timeoutSeconds: DEFAULT_COMFYUI_TIMEOUT_MS / 1e3,
|
|
122795
123047
|
editFormat: "multipart",
|
|
122796
|
-
editExtraText: ""
|
|
123048
|
+
editExtraText: "",
|
|
123049
|
+
outputFormat: "jpeg",
|
|
123050
|
+
watermark: true,
|
|
123051
|
+
background: "opaque"
|
|
122797
123052
|
};
|
|
122798
123053
|
}
|
|
122799
123054
|
/** Settings field each cloud provider persists its model under. */
|
|
@@ -122862,6 +123117,11 @@ img.ProseMirror-separator {
|
|
|
122862
123117
|
...provider === "openai-compat" ? {
|
|
122863
123118
|
editFormat: value?.openaiCompatEditFormat === "jsonImageUrlArray" ? "jsonImageUrlArray" : "multipart",
|
|
122864
123119
|
editExtraText: editExtraTextOf(value?.openaiCompatEditExtra)
|
|
123120
|
+
} : {},
|
|
123121
|
+
...provider === "seedream" ? {
|
|
123122
|
+
outputFormat: value?.seedreamOutputFormat === "png" ? "png" : "jpeg",
|
|
123123
|
+
watermark: value?.seedreamWatermark !== false,
|
|
123124
|
+
background: value?.seedreamBackground === "transparent" ? "transparent" : "opaque"
|
|
122865
123125
|
} : {}
|
|
122866
123126
|
};
|
|
122867
123127
|
return rows;
|
|
@@ -123074,6 +123334,11 @@ img.ProseMirror-separator {
|
|
|
123074
123334
|
workflows: resolveComfyUIWorkflows(value ?? {}),
|
|
123075
123335
|
activeWorkflow: activeComfyUIWorkflow(value ?? {})?.name ?? "",
|
|
123076
123336
|
timeoutSeconds: Math.max(1, Math.round((value?.comfyuiTimeoutMs ?? 3e5) / 1e3))
|
|
123337
|
+
} : {},
|
|
123338
|
+
...provider === "seedream" ? {
|
|
123339
|
+
outputFormat: value?.seedreamOutputFormat === "png" ? "png" : "jpeg",
|
|
123340
|
+
watermark: value?.seedreamWatermark !== false,
|
|
123341
|
+
background: value?.seedreamBackground === "transparent" ? "transparent" : "opaque"
|
|
123077
123342
|
} : {}
|
|
123078
123343
|
};
|
|
123079
123344
|
return next;
|
|
@@ -123205,6 +123470,11 @@ img.ProseMirror-separator {
|
|
|
123205
123470
|
await props.scope.set("openaiCompatEditFormat", row.editFormat);
|
|
123206
123471
|
await props.scope.set("openaiCompatEditExtra", editExtra);
|
|
123207
123472
|
}
|
|
123473
|
+
if (provider === "seedream") {
|
|
123474
|
+
await props.scope.set("seedreamOutputFormat", row.outputFormat);
|
|
123475
|
+
await props.scope.set("seedreamWatermark", row.watermark);
|
|
123476
|
+
await props.scope.set("seedreamBackground", row.background);
|
|
123477
|
+
}
|
|
123208
123478
|
if (row.keyInput.trim().length > 0) {
|
|
123209
123479
|
const keyRef = cloudCredentialRef(provider);
|
|
123210
123480
|
if (keyRef === void 0) throw new Error(t("comfyuiNoKey"));
|
|
@@ -123219,7 +123489,10 @@ img.ProseMirror-separator {
|
|
|
123219
123489
|
}
|
|
123220
123490
|
updateRow(provider, {
|
|
123221
123491
|
message: t("saved"),
|
|
123222
|
-
messageIsError: false
|
|
123492
|
+
messageIsError: false,
|
|
123493
|
+
testResult: void 0,
|
|
123494
|
+
modelFetchMessage: "",
|
|
123495
|
+
modelFetchIsError: false
|
|
123223
123496
|
});
|
|
123224
123497
|
} catch (cause) {
|
|
123225
123498
|
updateRow(provider, {
|
|
@@ -123232,6 +123505,14 @@ img.ProseMirror-separator {
|
|
|
123232
123505
|
};
|
|
123233
123506
|
/** Probe through the host route so the browser side never touches credential values. */
|
|
123234
123507
|
const testConnection = async (provider) => {
|
|
123508
|
+
if (rows[provider].keyInput.trim().length > 0) {
|
|
123509
|
+
updateRow(provider, {
|
|
123510
|
+
message: t("saveKeyFirst"),
|
|
123511
|
+
messageIsError: false,
|
|
123512
|
+
testResult: void 0
|
|
123513
|
+
});
|
|
123514
|
+
return;
|
|
123515
|
+
}
|
|
123235
123516
|
updateRow(provider, {
|
|
123236
123517
|
testing: true,
|
|
123237
123518
|
testResult: void 0,
|
|
@@ -123258,6 +123539,13 @@ img.ProseMirror-separator {
|
|
|
123258
123539
|
};
|
|
123259
123540
|
/** Pull the provider's image-capable model ids through the host route (Google and the OpenAI family). */
|
|
123260
123541
|
const fetchProviderModels = async (provider) => {
|
|
123542
|
+
if (rows[provider].keyInput.trim().length > 0) {
|
|
123543
|
+
updateRow(provider, {
|
|
123544
|
+
modelFetchMessage: t("saveKeyFirst"),
|
|
123545
|
+
modelFetchIsError: false
|
|
123546
|
+
});
|
|
123547
|
+
return;
|
|
123548
|
+
}
|
|
123261
123549
|
updateRow(provider, {
|
|
123262
123550
|
fetchingModels: true,
|
|
123263
123551
|
modelFetchMessage: "",
|
|
@@ -123606,7 +123894,7 @@ img.ProseMirror-separator {
|
|
|
123606
123894
|
(0, react_jsx_runtime.jsx)("button", {
|
|
123607
123895
|
type: "button",
|
|
123608
123896
|
className: "dsh-ig-btn-secondary",
|
|
123609
|
-
disabled: row.fetchingModels || !snapshot.writable,
|
|
123897
|
+
disabled: row.fetchingModels || row.saving || !snapshot.writable,
|
|
123610
123898
|
onClick: () => {
|
|
123611
123899
|
fetchProviderModels(provider);
|
|
123612
123900
|
},
|
|
@@ -123660,6 +123948,92 @@ img.ProseMirror-separator {
|
|
|
123660
123948
|
})
|
|
123661
123949
|
]
|
|
123662
123950
|
}) : null,
|
|
123951
|
+
provider === "seedream" ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
123952
|
+
(0, react_jsx_runtime.jsxs)("label", {
|
|
123953
|
+
className: "dsh-ig-field",
|
|
123954
|
+
children: [
|
|
123955
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
123956
|
+
className: "dsh-ig-label",
|
|
123957
|
+
children: t("arkOutputFormat")
|
|
123958
|
+
}),
|
|
123959
|
+
(0, react_jsx_runtime.jsxs)("select", {
|
|
123960
|
+
className: "dsh-ig-input",
|
|
123961
|
+
value: row.outputFormat,
|
|
123962
|
+
onChange: (event) => {
|
|
123963
|
+
updateRow(provider, { outputFormat: event.target.value === "png" ? "png" : "jpeg" });
|
|
123964
|
+
},
|
|
123965
|
+
disabled: !snapshot.writable,
|
|
123966
|
+
children: [(0, react_jsx_runtime.jsx)("option", {
|
|
123967
|
+
value: "jpeg",
|
|
123968
|
+
children: t("arkOutputFormatJpeg")
|
|
123969
|
+
}), (0, react_jsx_runtime.jsx)("option", {
|
|
123970
|
+
value: "png",
|
|
123971
|
+
children: t("arkOutputFormatPng")
|
|
123972
|
+
})]
|
|
123973
|
+
}),
|
|
123974
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
123975
|
+
className: "dsh-ig-hint",
|
|
123976
|
+
children: t("arkOutputFormatHint")
|
|
123977
|
+
})
|
|
123978
|
+
]
|
|
123979
|
+
}),
|
|
123980
|
+
(0, react_jsx_runtime.jsxs)("label", {
|
|
123981
|
+
className: "dsh-ig-field",
|
|
123982
|
+
children: [
|
|
123983
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
123984
|
+
className: "dsh-ig-label",
|
|
123985
|
+
children: t("arkWatermark")
|
|
123986
|
+
}),
|
|
123987
|
+
(0, react_jsx_runtime.jsxs)("select", {
|
|
123988
|
+
className: "dsh-ig-input",
|
|
123989
|
+
value: row.watermark ? "on" : "off",
|
|
123990
|
+
onChange: (event) => {
|
|
123991
|
+
updateRow(provider, { watermark: event.target.value === "on" });
|
|
123992
|
+
},
|
|
123993
|
+
disabled: !snapshot.writable,
|
|
123994
|
+
children: [(0, react_jsx_runtime.jsx)("option", {
|
|
123995
|
+
value: "on",
|
|
123996
|
+
children: t("arkWatermarkOn")
|
|
123997
|
+
}), (0, react_jsx_runtime.jsx)("option", {
|
|
123998
|
+
value: "off",
|
|
123999
|
+
children: t("arkWatermarkOff")
|
|
124000
|
+
})]
|
|
124001
|
+
}),
|
|
124002
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
124003
|
+
className: "dsh-ig-hint",
|
|
124004
|
+
children: t("arkWatermarkHint")
|
|
124005
|
+
})
|
|
124006
|
+
]
|
|
124007
|
+
}),
|
|
124008
|
+
(0, react_jsx_runtime.jsxs)("label", {
|
|
124009
|
+
className: "dsh-ig-field",
|
|
124010
|
+
children: [
|
|
124011
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
124012
|
+
className: "dsh-ig-label",
|
|
124013
|
+
children: t("arkBackground")
|
|
124014
|
+
}),
|
|
124015
|
+
(0, react_jsx_runtime.jsxs)("select", {
|
|
124016
|
+
className: "dsh-ig-input",
|
|
124017
|
+
value: row.background,
|
|
124018
|
+
onChange: (event) => {
|
|
124019
|
+
updateRow(provider, { background: event.target.value === "transparent" ? "transparent" : "opaque" });
|
|
124020
|
+
},
|
|
124021
|
+
disabled: !snapshot.writable,
|
|
124022
|
+
children: [(0, react_jsx_runtime.jsx)("option", {
|
|
124023
|
+
value: "opaque",
|
|
124024
|
+
children: t("arkBackgroundOpaque")
|
|
124025
|
+
}), (0, react_jsx_runtime.jsx)("option", {
|
|
124026
|
+
value: "transparent",
|
|
124027
|
+
children: t("arkBackgroundTransparent")
|
|
124028
|
+
})]
|
|
124029
|
+
}),
|
|
124030
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
124031
|
+
className: "dsh-ig-hint",
|
|
124032
|
+
children: t("arkBackgroundHint")
|
|
124033
|
+
})
|
|
124034
|
+
]
|
|
124035
|
+
})
|
|
124036
|
+
] }) : null,
|
|
123663
124037
|
provider === "openai-compat" && row.editFormat === "jsonImageUrlArray" ? (0, react_jsx_runtime.jsxs)("label", {
|
|
123664
124038
|
className: "dsh-ig-field",
|
|
123665
124039
|
children: [
|
|
@@ -123695,7 +124069,7 @@ img.ProseMirror-separator {
|
|
|
123695
124069
|
(0, react_jsx_runtime.jsx)("button", {
|
|
123696
124070
|
type: "button",
|
|
123697
124071
|
className: "dsh-ig-btn-secondary",
|
|
123698
|
-
disabled: row.testing,
|
|
124072
|
+
disabled: row.testing || row.saving,
|
|
123699
124073
|
onClick: () => {
|
|
123700
124074
|
testConnection(provider);
|
|
123701
124075
|
},
|
|
@@ -123713,7 +124087,7 @@ img.ProseMirror-separator {
|
|
|
123713
124087
|
(0, react_jsx_runtime.jsx)("button", {
|
|
123714
124088
|
className: "dsh-ig-save",
|
|
123715
124089
|
type: "submit",
|
|
123716
|
-
disabled: row.saving || !snapshot.writable,
|
|
124090
|
+
disabled: row.saving || row.testing || row.fetchingModels || !snapshot.writable,
|
|
123717
124091
|
children: row.saving ? t("saving") : t("save")
|
|
123718
124092
|
})
|
|
123719
124093
|
]
|
|
@@ -124445,7 +124819,7 @@ img.ProseMirror-separator {
|
|
|
124445
124819
|
ratio: request.ratio,
|
|
124446
124820
|
quality: request.quality
|
|
124447
124821
|
};
|
|
124448
|
-
await saveGalleryItem({
|
|
124822
|
+
const savedOk = await saveGalleryItem({
|
|
124449
124823
|
id: String(revision.attachment.attachmentId),
|
|
124450
124824
|
attachment: revision.attachment,
|
|
124451
124825
|
prompt: revision.prompt,
|
|
@@ -124460,7 +124834,7 @@ img.ProseMirror-separator {
|
|
|
124460
124834
|
});
|
|
124461
124835
|
const next = appendConversationImageRevision(String(originId), revision);
|
|
124462
124836
|
setRevisionChain(next);
|
|
124463
|
-
setToast(t("regenerate"));
|
|
124837
|
+
setToast(savedOk ? t("regenerate") : t("regenerateSaveFailed"));
|
|
124464
124838
|
setTimeout(() => {
|
|
124465
124839
|
setToast(void 0);
|
|
124466
124840
|
}, 2e3);
|