@ecsplico/editor 3.5.0 → 3.5.1
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/CHANGELOG.md +7 -2
- package/dist/code/CodeSurface.vue.d.ts +1 -1
- package/dist/document/DocumentSurface.vue.d.ts +1 -1
- package/dist/features/ai/protocol/generate.d.ts +1 -1
- package/dist/features/image-studio/node-core.d.ts +46 -0
- package/dist/features/image-studio/node-core.js +79 -0
- package/dist/features/image-studio/node-core.js.map +1 -0
- package/dist/features/image-studio/node-store.d.ts +3 -11
- package/dist/features/image-studio/node-store.js +15 -44
- package/dist/features/image-studio/node-store.js.map +1 -1
- package/dist/features/image-studio/react/ImageStudioChat.js +101 -0
- package/dist/features/image-studio/react/ImageStudioChat.js.map +1 -0
- package/dist/features/image-studio/react/ImageStudioDialog.d.ts +19 -0
- package/dist/features/image-studio/react/ImageStudioDialog.js +357 -0
- package/dist/features/image-studio/react/ImageStudioDialog.js.map +1 -0
- package/dist/features/image-studio/react/ImageStudioTile.js +51 -0
- package/dist/features/image-studio/react/ImageStudioTile.js.map +1 -0
- package/dist/features/image-studio/react/ImageStudioView.js +126 -0
- package/dist/features/image-studio/react/ImageStudioView.js.map +1 -0
- package/dist/features/image-studio/react/store.d.ts +37 -0
- package/dist/features/image-studio/react/store.js +46 -0
- package/dist/features/image-studio/react/store.js.map +1 -0
- package/dist/features/image-studio/react.d.ts +18 -0
- package/dist/features/image-studio/react.js +21 -0
- package/dist/features/image-studio/react.js.map +1 -0
- package/dist/markdown/MarkdownSurface.vue.d.ts +1 -1
- package/dist/vue/AdvancedEditor.vue.d.ts +1 -1
- package/package.json +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.5.1 - 2026-10-21
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- React entry of the Bild-Studio, `@ecsplico/editor/image-studio/react`: `imageStudioFeature()` (the `imageStudio` block with its shadcn node view), `ImageStudioDialog` (props `open`, `onOpenChange`, `store`, `title?`; same behaviour and texts as the Vue dialog) and `ImageStudioReactStore`, the dialog's contract with plain values instead of refs (`state`, `promptBlocks`, `problem`, `update`, `generate`, `upload`; the host re-renders with the new `state` after `update`). `useImageStudioNodeStore({ editor, getPos, backend, documentId })` is the store of one node.
|
|
8
|
+
- `createImageStudioNodeCore(options)` (`ImageStudioNodeCore`, `ImageStudioNodeSnapshot`, `ImageStudioNodeCoreOptions`, exported from `/image-studio/react`): the framework-free store of one `imageStudio` node (`get()`, `subscribe(listener)`, `update`, `generate`, `upload`, `dispose()`). `createImageStudioNodeStore` (Vue) is now a wrapper over it; its public API is unchanged.
|
|
9
|
+
|
|
3
10
|
## 3.5.0 - 2026-10-21
|
|
4
11
|
|
|
5
12
|
### Added
|
|
@@ -12,8 +19,6 @@
|
|
|
12
19
|
|
|
13
20
|
- `attrComment` and `mediaTokenizer` are exported from `@ecsplico/editor/media/schema` (shared with the Bild-Studio's Markdown form); `attrComment` escapes U+2028 and U+2029 in the JSON comment so such text survives a Markdown round trip.
|
|
14
21
|
|
|
15
|
-
React mirror of the dialog and node view follows in 3.5.1.
|
|
16
|
-
|
|
17
22
|
## 3.4.0 - 2026-10-21
|
|
18
23
|
|
|
19
24
|
### Added
|
|
@@ -19,8 +19,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
|
19
19
|
"onUpdate:modelValue"?: ((text: string) => any) | undefined;
|
|
20
20
|
onSave?: ((text: string) => any) | undefined;
|
|
21
21
|
}>, {
|
|
22
|
-
modelValue: string;
|
|
23
22
|
editable: boolean;
|
|
23
|
+
modelValue: string;
|
|
24
24
|
wordWrap: boolean;
|
|
25
25
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
26
26
|
declare const _default: typeof __VLS_export;
|
|
@@ -40,8 +40,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
40
40
|
"onUpdate:modelValue"?: ((text: string) => any) | undefined;
|
|
41
41
|
onSave?: ((text: string) => any) | undefined;
|
|
42
42
|
}>, {
|
|
43
|
-
modelValue: string;
|
|
44
43
|
editable: boolean;
|
|
44
|
+
modelValue: string;
|
|
45
45
|
wordWrap: boolean;
|
|
46
46
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
47
47
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -16,8 +16,8 @@ export type ChatStream = (messages: ChatMessage[], signal: AbortSignal) => Async
|
|
|
16
16
|
/** Body schema of the generate request (`POST /api/ai/generate`, `EditorBackend['ai'].stream('generate', …)`). */
|
|
17
17
|
export declare const generateBodySchema: z.ZodObject<{
|
|
18
18
|
task: z.ZodEnum<{
|
|
19
|
-
generate: "generate";
|
|
20
19
|
custom: "custom";
|
|
20
|
+
generate: "generate";
|
|
21
21
|
continue: "continue";
|
|
22
22
|
reformulate: "reformulate";
|
|
23
23
|
"transcript-summary": "transcript-summary";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ReadonlyStore } from "../../core/store.js";
|
|
2
|
+
import { EditorBackend } from "../../core/backend.js";
|
|
3
|
+
import { ImageStudioImage, ImageStudioPromptBlock } from "../../core/backend-types.js";
|
|
4
|
+
import { ImageStudioState } from "./state.js";
|
|
5
|
+
import { Editor } from "@tiptap/core";
|
|
6
|
+
//#region src/features/image-studio/node-core.d.ts
|
|
7
|
+
export interface ImageStudioNodeCoreOptions {
|
|
8
|
+
editor: Editor;
|
|
9
|
+
/** Position of the `imageStudio` node (a node view's `getPos`); `undefined` once the node is gone. */
|
|
10
|
+
getPos: () => number | undefined;
|
|
11
|
+
/** The backend's image port; `undefined` (backend without the `images` capability) makes `generate` and `upload` reject. */
|
|
12
|
+
backend: EditorBackend['images'];
|
|
13
|
+
/** Document whose package stores the uploaded references. */
|
|
14
|
+
documentId: string;
|
|
15
|
+
}
|
|
16
|
+
/** What a view renders from a node's studio: the observable part of an `ImageStudioNodeCore`. */
|
|
17
|
+
export interface ImageStudioNodeSnapshot {
|
|
18
|
+
readonly state: ImageStudioState;
|
|
19
|
+
/** The managed list of Prompt-Bausteine, as the backend answered; empty until then. */
|
|
20
|
+
readonly promptBlocks: readonly ImageStudioPromptBlock[];
|
|
21
|
+
/** The Prompt-Bausteine could not be loaded: the message. */
|
|
22
|
+
readonly problem: string | undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The framework-free store of one `imageStudio` node (`createImageStudioNodeCore`): an observable `ImageStudioNodeSnapshot`
|
|
26
|
+
* (`get`/`subscribe`, see `ReadonlyStore`; every change replaces the snapshot) and the actions the dialog needs.
|
|
27
|
+
* The Vue and React bindings wrap it into the store shape of their dialog.
|
|
28
|
+
*/
|
|
29
|
+
export interface ImageStudioNodeCore extends ReadonlyStore<ImageStudioNodeSnapshot> {
|
|
30
|
+
/** Writes `next` into the node's `studio` attribute as one transaction (`src` follows the selected image). */
|
|
31
|
+
update: (next: ImageStudioState) => void;
|
|
32
|
+
generate: (instruction: string, signal: AbortSignal) => Promise<ImageStudioImage>;
|
|
33
|
+
upload: (file: File) => Promise<ImageStudioImage>;
|
|
34
|
+
/** Stops following the editor and ignores the answers still outstanding; call when the node view unmounts. */
|
|
35
|
+
dispose: () => void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The core of the store of one `imageStudio` node: `state` follows the node's `studio` attribute (re-read on every
|
|
39
|
+
* editor transaction, so undo, collaboration and other writers show), `update` writes it back through
|
|
40
|
+
* `setImageStudioState` as one transaction (one undo step; the node's `src` follows the selected image), and
|
|
41
|
+
* `generate`, `upload` and the Prompt-Bausteine go to `backend.images`. Listeners run synchronously. Call `dispose`
|
|
42
|
+
* when the view goes.
|
|
43
|
+
*/
|
|
44
|
+
export declare function createImageStudioNodeCore(options: ImageStudioNodeCoreOptions): ImageStudioNodeCore;
|
|
45
|
+
//#endregion
|
|
46
|
+
//# sourceMappingURL=node-core.d.ts.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { EMPTY_IMAGE_STUDIO_STATE, normalizeImageStudioState } from "./state.js";
|
|
2
|
+
//#region src/features/image-studio/node-core.ts
|
|
3
|
+
const NO_IMAGES = "@ecsplico/editor: the backend has no images capability";
|
|
4
|
+
/**
|
|
5
|
+
* The core of the store of one `imageStudio` node: `state` follows the node's `studio` attribute (re-read on every
|
|
6
|
+
* editor transaction, so undo, collaboration and other writers show), `update` writes it back through
|
|
7
|
+
* `setImageStudioState` as one transaction (one undo step; the node's `src` follows the selected image), and
|
|
8
|
+
* `generate`, `upload` and the Prompt-Bausteine go to `backend.images`. Listeners run synchronously. Call `dispose`
|
|
9
|
+
* when the view goes.
|
|
10
|
+
*/
|
|
11
|
+
function createImageStudioNodeCore(options) {
|
|
12
|
+
const { editor, getPos, backend, documentId } = options;
|
|
13
|
+
let snapshot = {
|
|
14
|
+
state: EMPTY_IMAGE_STUDIO_STATE,
|
|
15
|
+
promptBlocks: [],
|
|
16
|
+
problem: void 0
|
|
17
|
+
};
|
|
18
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
19
|
+
let disposed = false;
|
|
20
|
+
let source;
|
|
21
|
+
function publish(patch) {
|
|
22
|
+
snapshot = {
|
|
23
|
+
...snapshot,
|
|
24
|
+
...patch
|
|
25
|
+
};
|
|
26
|
+
for (const listener of [...listeners]) listener(snapshot);
|
|
27
|
+
}
|
|
28
|
+
function sync() {
|
|
29
|
+
const pos = getPos();
|
|
30
|
+
const node = pos === void 0 || editor.isDestroyed ? null : editor.state.doc.nodeAt(pos);
|
|
31
|
+
if (node?.type.name !== "imageStudio" || node.attrs.studio === source) return;
|
|
32
|
+
source = node.attrs.studio;
|
|
33
|
+
publish({ state: normalizeImageStudioState(source) });
|
|
34
|
+
}
|
|
35
|
+
editor.on("transaction", sync);
|
|
36
|
+
sync();
|
|
37
|
+
if (backend) backend.promptBlocks().then((blocks) => {
|
|
38
|
+
if (!disposed) publish({ promptBlocks: blocks });
|
|
39
|
+
}, (error) => {
|
|
40
|
+
if (!disposed) publish({ problem: error instanceof Error ? error.message : String(error) });
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
get: () => snapshot,
|
|
44
|
+
subscribe(listener) {
|
|
45
|
+
listeners.add(listener);
|
|
46
|
+
return () => {
|
|
47
|
+
listeners.delete(listener);
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
update(next) {
|
|
51
|
+
const pos = getPos();
|
|
52
|
+
if (pos === void 0 || editor.isDestroyed) return;
|
|
53
|
+
editor.commands.setImageStudioState(pos, next);
|
|
54
|
+
},
|
|
55
|
+
generate(instruction, signal) {
|
|
56
|
+
if (!backend) return Promise.reject(/* @__PURE__ */ new Error(NO_IMAGES));
|
|
57
|
+
const { description, promptBlockIds, references, generations } = snapshot.state;
|
|
58
|
+
const referenceUrls = [...references, ...generations].filter((image) => image.active).map((image) => image.url);
|
|
59
|
+
return backend.generate({
|
|
60
|
+
description,
|
|
61
|
+
instruction,
|
|
62
|
+
promptBlockIds: [...promptBlockIds],
|
|
63
|
+
referenceUrls
|
|
64
|
+
}, signal);
|
|
65
|
+
},
|
|
66
|
+
upload(file) {
|
|
67
|
+
return backend ? backend.upload(documentId, file) : Promise.reject(/* @__PURE__ */ new Error(NO_IMAGES));
|
|
68
|
+
},
|
|
69
|
+
dispose() {
|
|
70
|
+
disposed = true;
|
|
71
|
+
listeners.clear();
|
|
72
|
+
editor.off("transaction", sync);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
export { createImageStudioNodeCore };
|
|
78
|
+
|
|
79
|
+
//# sourceMappingURL=node-core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-core.js","names":[],"sources":["../../../src/features/image-studio/node-core.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { EditorBackend } from '../../core/backend'\nimport type { ReadonlyStore } from '../../core/store'\nimport { EMPTY_IMAGE_STUDIO_STATE, type ImageStudioImage, type ImageStudioPromptBlock, type ImageStudioState, normalizeImageStudioState } from './state'\n\nexport interface ImageStudioNodeCoreOptions {\n editor: Editor\n /** Position of the `imageStudio` node (a node view's `getPos`); `undefined` once the node is gone. */\n getPos: () => number | undefined\n /** The backend's image port; `undefined` (backend without the `images` capability) makes `generate` and `upload` reject. */\n backend: EditorBackend['images']\n /** Document whose package stores the uploaded references. */\n documentId: string\n}\n\n/** What a view renders from a node's studio: the observable part of an `ImageStudioNodeCore`. */\nexport interface ImageStudioNodeSnapshot {\n readonly state: ImageStudioState\n /** The managed list of Prompt-Bausteine, as the backend answered; empty until then. */\n readonly promptBlocks: readonly ImageStudioPromptBlock[]\n /** The Prompt-Bausteine could not be loaded: the message. */\n readonly problem: string | undefined\n}\n\n/**\n * The framework-free store of one `imageStudio` node (`createImageStudioNodeCore`): an observable `ImageStudioNodeSnapshot`\n * (`get`/`subscribe`, see `ReadonlyStore`; every change replaces the snapshot) and the actions the dialog needs.\n * The Vue and React bindings wrap it into the store shape of their dialog.\n */\nexport interface ImageStudioNodeCore extends ReadonlyStore<ImageStudioNodeSnapshot> {\n /** Writes `next` into the node's `studio` attribute as one transaction (`src` follows the selected image). */\n update: (next: ImageStudioState) => void\n generate: (instruction: string, signal: AbortSignal) => Promise<ImageStudioImage>\n upload: (file: File) => Promise<ImageStudioImage>\n /** Stops following the editor and ignores the answers still outstanding; call when the node view unmounts. */\n dispose: () => void\n}\n\n// What a store without the backend's `images` port rejects with (the node view does not open one without the port).\nconst NO_IMAGES = '@ecsplico/editor: the backend has no images capability'\n\n/**\n * The core of the store of one `imageStudio` node: `state` follows the node's `studio` attribute (re-read on every\n * editor transaction, so undo, collaboration and other writers show), `update` writes it back through\n * `setImageStudioState` as one transaction (one undo step; the node's `src` follows the selected image), and\n * `generate`, `upload` and the Prompt-Bausteine go to `backend.images`. Listeners run synchronously. Call `dispose`\n * when the view goes.\n */\nexport function createImageStudioNodeCore(options: ImageStudioNodeCoreOptions): ImageStudioNodeCore {\n const { editor, getPos, backend, documentId } = options\n let snapshot: ImageStudioNodeSnapshot = { state: EMPTY_IMAGE_STUDIO_STATE, promptBlocks: [], problem: undefined }\n const listeners = new Set<(value: ImageStudioNodeSnapshot) => void>()\n let disposed = false\n // The attribute value `state` was normalized from: a transaction that left it alone keeps `state` as it is.\n let source: unknown\n\n function publish(patch: Partial<ImageStudioNodeSnapshot>): void {\n snapshot = { ...snapshot, ...patch }\n // A copy: listeners may (un)subscribe while being notified.\n for (const listener of [...listeners]) listener(snapshot)\n }\n\n function sync(): void {\n const pos = getPos()\n const node = pos === undefined || editor.isDestroyed ? null : editor.state.doc.nodeAt(pos)\n if (node?.type.name !== 'imageStudio' || node.attrs.studio === source) return\n source = node.attrs.studio\n publish({ state: normalizeImageStudioState(source) })\n }\n\n editor.on('transaction', sync)\n sync()\n\n if (backend) {\n backend.promptBlocks().then(\n (blocks) => { if (!disposed) publish({ promptBlocks: blocks }) },\n (error: unknown) => { if (!disposed) publish({ problem: error instanceof Error ? error.message : String(error) }) },\n )\n }\n\n return {\n get: () => snapshot,\n subscribe(listener) {\n listeners.add(listener)\n return () => { listeners.delete(listener) }\n },\n update(next) {\n const pos = getPos()\n if (pos === undefined || editor.isDestroyed) return\n editor.commands.setImageStudioState(pos, next)\n },\n generate(instruction, signal) {\n if (!backend) return Promise.reject(new Error(NO_IMAGES))\n const { description, promptBlockIds, references, generations } = snapshot.state\n const referenceUrls = [...references, ...generations].filter(image => image.active).map(image => image.url)\n return backend.generate({ description, instruction, promptBlockIds: [...promptBlockIds], referenceUrls }, signal)\n },\n upload(file): Promise<ImageStudioImage> {\n return backend ? backend.upload(documentId, file) : Promise.reject(new Error(NO_IMAGES))\n },\n dispose() {\n disposed = true\n listeners.clear()\n editor.off('transaction', sync)\n },\n }\n}\n"],"mappings":";;AAuCA,MAAM,YAAY;;;;;;;;AASlB,SAAgB,0BAA0B,SAA0D;CAClG,MAAM,EAAE,QAAQ,QAAQ,SAAS,eAAe;CAChD,IAAI,WAAoC;EAAE,OAAO;EAA0B,cAAc,CAAC;EAAG,SAAS,KAAA;CAAU;CAChH,MAAM,4BAAY,IAAI,IAA8C;CACpE,IAAI,WAAW;CAEf,IAAI;CAEJ,SAAS,QAAQ,OAA+C;EAC9D,WAAW;GAAE,GAAG;GAAU,GAAG;EAAM;EAEnC,KAAK,MAAM,YAAY,CAAC,GAAG,SAAS,GAAG,SAAS,QAAQ;CAC1D;CAEA,SAAS,OAAa;EACpB,MAAM,MAAM,OAAO;EACnB,MAAM,OAAO,QAAQ,KAAA,KAAa,OAAO,cAAc,OAAO,OAAO,MAAM,IAAI,OAAO,GAAG;EACzF,IAAI,MAAM,KAAK,SAAS,iBAAiB,KAAK,MAAM,WAAW,QAAQ;EACvE,SAAS,KAAK,MAAM;EACpB,QAAQ,EAAE,OAAO,0BAA0B,MAAM,EAAE,CAAC;CACtD;CAEA,OAAO,GAAG,eAAe,IAAI;CAC7B,KAAK;CAEL,IAAI,SACF,QAAQ,aAAa,CAAC,CAAC,MACpB,WAAW;EAAE,IAAI,CAAC,UAAU,QAAQ,EAAE,cAAc,OAAO,CAAC;CAAE,IAC9D,UAAmB;EAAE,IAAI,CAAC,UAAU,QAAQ,EAAE,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,CAAC;CAAE,CACpH;CAGF,OAAO;EACL,WAAW;EACX,UAAU,UAAU;GAClB,UAAU,IAAI,QAAQ;GACtB,aAAa;IAAE,UAAU,OAAO,QAAQ;GAAE;EAC5C;EACA,OAAO,MAAM;GACX,MAAM,MAAM,OAAO;GACnB,IAAI,QAAQ,KAAA,KAAa,OAAO,aAAa;GAC7C,OAAO,SAAS,oBAAoB,KAAK,IAAI;EAC/C;EACA,SAAS,aAAa,QAAQ;GAC5B,IAAI,CAAC,SAAS,OAAO,QAAQ,uBAAO,IAAI,MAAM,SAAS,CAAC;GACxD,MAAM,EAAE,aAAa,gBAAgB,YAAY,gBAAgB,SAAS;GAC1E,MAAM,gBAAgB,CAAC,GAAG,YAAY,GAAG,WAAW,CAAC,CAAC,QAAO,UAAS,MAAM,MAAM,CAAC,CAAC,KAAI,UAAS,MAAM,GAAG;GAC1G,OAAO,QAAQ,SAAS;IAAE;IAAa;IAAa,gBAAgB,CAAC,GAAG,cAAc;IAAG;GAAc,GAAG,MAAM;EAClH;EACA,OAAO,MAAiC;GACtC,OAAO,UAAU,QAAQ,OAAO,YAAY,IAAI,IAAI,QAAQ,uBAAO,IAAI,MAAM,SAAS,CAAC;EACzF;EACA,UAAU;GACR,WAAW;GACX,UAAU,MAAM;GAChB,OAAO,IAAI,eAAe,IAAI;EAChC;CACF;AACF"}
|
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ImageStudioNodeCoreOptions } from "./node-core.js";
|
|
2
2
|
import { ImageStudioStore } from "./store.js";
|
|
3
|
-
import { Editor } from "@tiptap/core";
|
|
4
3
|
//#region src/features/image-studio/node-store.d.ts
|
|
5
|
-
export
|
|
6
|
-
editor: Editor;
|
|
7
|
-
/** Position of the `imageStudio` node (a node view's `getPos`); `undefined` once the node is gone. */
|
|
8
|
-
getPos: () => number | undefined;
|
|
9
|
-
/** The backend's image port; `undefined` (backend without the `images` capability) makes `generate` and `upload` reject. */
|
|
10
|
-
backend: EditorBackend['images'];
|
|
11
|
-
/** Document whose package stores the uploaded references. */
|
|
12
|
-
documentId: string;
|
|
13
|
-
}
|
|
4
|
+
export type ImageStudioNodeStoreOptions = ImageStudioNodeCoreOptions;
|
|
14
5
|
/** A store over one node, with the function that detaches it from the editor. */
|
|
15
6
|
export interface ImageStudioNodeStore extends ImageStudioStore {
|
|
16
7
|
/** Stops following the editor and ignores the answers still outstanding; call when the node view unmounts. */
|
|
@@ -21,6 +12,7 @@ export interface ImageStudioNodeStore extends ImageStudioStore {
|
|
|
21
12
|
* editor transaction, so undo, collaboration and other writers show), `update` writes it back through
|
|
22
13
|
* `setImageStudioState` as one transaction (one undo step; the node's `src` follows the selected image), and
|
|
23
14
|
* `generate`, `upload` and the Prompt-Bausteine go to `backend.images`. Call `dispose` when the view goes.
|
|
15
|
+
* The Vue wrapper of `createImageStudioNodeCore`.
|
|
24
16
|
*/
|
|
25
17
|
export declare function createImageStudioNodeStore(options: ImageStudioNodeStoreOptions): ImageStudioNodeStore;
|
|
26
18
|
//#endregion
|
|
@@ -1,61 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createImageStudioNodeCore } from "./node-core.js";
|
|
2
2
|
import { shallowRef } from "vue";
|
|
3
3
|
//#region src/features/image-studio/node-store.ts
|
|
4
|
-
const NO_IMAGES = "@ecsplico/editor: the backend has no images capability";
|
|
5
4
|
/**
|
|
6
5
|
* The `ImageStudioStore` of one `imageStudio` node: `state` follows the node's `studio` attribute (re-read on every
|
|
7
6
|
* editor transaction, so undo, collaboration and other writers show), `update` writes it back through
|
|
8
7
|
* `setImageStudioState` as one transaction (one undo step; the node's `src` follows the selected image), and
|
|
9
8
|
* `generate`, `upload` and the Prompt-Bausteine go to `backend.images`. Call `dispose` when the view goes.
|
|
9
|
+
* The Vue wrapper of `createImageStudioNodeCore`.
|
|
10
10
|
*/
|
|
11
11
|
function createImageStudioNodeStore(options) {
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (node?.type.name !== "imageStudio" || node.attrs.studio === source) return;
|
|
22
|
-
source = node.attrs.studio;
|
|
23
|
-
state.value = normalizeImageStudioState(source);
|
|
24
|
-
}
|
|
25
|
-
editor.on("transaction", sync);
|
|
26
|
-
sync();
|
|
27
|
-
if (backend) backend.promptBlocks().then((blocks) => {
|
|
28
|
-
if (!disposed) promptBlocks.value = blocks;
|
|
29
|
-
}, (error) => {
|
|
30
|
-
if (!disposed) problem.value = error instanceof Error ? error.message : String(error);
|
|
12
|
+
const core = createImageStudioNodeCore(options);
|
|
13
|
+
const initial = core.get();
|
|
14
|
+
const state = shallowRef(initial.state);
|
|
15
|
+
const promptBlocks = shallowRef(initial.promptBlocks);
|
|
16
|
+
const problem = shallowRef(initial.problem);
|
|
17
|
+
core.subscribe((snapshot) => {
|
|
18
|
+
state.value = snapshot.state;
|
|
19
|
+
promptBlocks.value = snapshot.promptBlocks;
|
|
20
|
+
problem.value = snapshot.problem;
|
|
31
21
|
});
|
|
32
22
|
return {
|
|
33
23
|
state,
|
|
34
24
|
promptBlocks,
|
|
35
25
|
problem,
|
|
36
|
-
update
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
},
|
|
41
|
-
generate(instruction, signal) {
|
|
42
|
-
if (!backend) return Promise.reject(/* @__PURE__ */ new Error(NO_IMAGES));
|
|
43
|
-
const { description, promptBlockIds, references, generations } = state.value;
|
|
44
|
-
const referenceUrls = [...references, ...generations].filter((image) => image.active).map((image) => image.url);
|
|
45
|
-
return backend.generate({
|
|
46
|
-
description,
|
|
47
|
-
instruction,
|
|
48
|
-
promptBlockIds: [...promptBlockIds],
|
|
49
|
-
referenceUrls
|
|
50
|
-
}, signal);
|
|
51
|
-
},
|
|
52
|
-
upload(file) {
|
|
53
|
-
return backend ? backend.upload(documentId, file) : Promise.reject(/* @__PURE__ */ new Error(NO_IMAGES));
|
|
54
|
-
},
|
|
55
|
-
dispose() {
|
|
56
|
-
disposed = true;
|
|
57
|
-
editor.off("transaction", sync);
|
|
58
|
-
}
|
|
26
|
+
update: core.update,
|
|
27
|
+
generate: core.generate,
|
|
28
|
+
upload: core.upload,
|
|
29
|
+
dispose: core.dispose
|
|
59
30
|
};
|
|
60
31
|
}
|
|
61
32
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-store.js","names":[],"sources":["../../../src/features/image-studio/node-store.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"node-store.js","names":[],"sources":["../../../src/features/image-studio/node-store.ts"],"sourcesContent":["import { shallowRef } from 'vue'\nimport { createImageStudioNodeCore, type ImageStudioNodeCoreOptions } from './node-core'\nimport type { ImageStudioPromptBlock, ImageStudioState } from './state'\nimport type { ImageStudioStore } from './store'\n\nexport type ImageStudioNodeStoreOptions = ImageStudioNodeCoreOptions\n\n/** A store over one node, with the function that detaches it from the editor. */\nexport interface ImageStudioNodeStore extends ImageStudioStore {\n /** Stops following the editor and ignores the answers still outstanding; call when the node view unmounts. */\n dispose: () => void\n}\n\n/**\n * The `ImageStudioStore` of one `imageStudio` node: `state` follows the node's `studio` attribute (re-read on every\n * editor transaction, so undo, collaboration and other writers show), `update` writes it back through\n * `setImageStudioState` as one transaction (one undo step; the node's `src` follows the selected image), and\n * `generate`, `upload` and the Prompt-Bausteine go to `backend.images`. Call `dispose` when the view goes.\n * The Vue wrapper of `createImageStudioNodeCore`.\n */\nexport function createImageStudioNodeStore(options: ImageStudioNodeStoreOptions): ImageStudioNodeStore {\n const core = createImageStudioNodeCore(options)\n const initial = core.get()\n const state = shallowRef<ImageStudioState>(initial.state)\n const promptBlocks = shallowRef<readonly ImageStudioPromptBlock[]>(initial.promptBlocks)\n const problem = shallowRef<string | undefined>(initial.problem)\n core.subscribe((snapshot) => {\n state.value = snapshot.state\n promptBlocks.value = snapshot.promptBlocks\n problem.value = snapshot.problem\n })\n return {\n state,\n promptBlocks,\n problem,\n update: core.update,\n generate: core.generate,\n upload: core.upload,\n dispose: core.dispose,\n }\n}\n"],"mappings":";;;;;;;;;;AAoBA,SAAgB,2BAA2B,SAA4D;CACrG,MAAM,OAAO,0BAA0B,OAAO;CAC9C,MAAM,UAAU,KAAK,IAAI;CACzB,MAAM,QAAQ,WAA6B,QAAQ,KAAK;CACxD,MAAM,eAAe,WAA8C,QAAQ,YAAY;CACvF,MAAM,UAAU,WAA+B,QAAQ,OAAO;CAC9D,KAAK,WAAW,aAAa;EAC3B,MAAM,QAAQ,SAAS;EACvB,aAAa,QAAQ,SAAS;EAC9B,QAAQ,QAAQ,SAAS;CAC3B,CAAC;CACD,OAAO;EACL;EACA;EACA;EACA,QAAQ,KAAK;EACb,UAAU,KAAK;EACf,QAAQ,KAAK;EACb,SAAS,KAAK;CAChB;AACF"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { useEditorI18n } from "../../../react/use-editor-i18n.js";
|
|
2
|
+
import { cn } from "../../../react/ui/utils.js";
|
|
3
|
+
import { Icon } from "../../../react/Icon.js";
|
|
4
|
+
import { Button } from "../../../react/ui/button.js";
|
|
5
|
+
import { Textarea } from "../../../react/ui/textarea.js";
|
|
6
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
//#region src/features/image-studio/react/ImageStudioChat.tsx
|
|
9
|
+
/**
|
|
10
|
+
* The chat of the Bild-Studio: the turn log (instruction, the picture it produced if the block still has it, the
|
|
11
|
+
* problem when it failed) and the instruction input. `Enter` sends, `Shift+Enter` breaks the line; while `running` the
|
|
12
|
+
* input and the button are disabled. An empty instruction is sent when `emptyAllowed` (the block has a description to generate from).
|
|
13
|
+
*/
|
|
14
|
+
function ImageStudioChat({ turns, images, running, emptyAllowed, onSend }) {
|
|
15
|
+
const { t } = useEditorI18n();
|
|
16
|
+
const headingId = useId();
|
|
17
|
+
const [instruction, setInstruction] = useState("");
|
|
18
|
+
const log = useRef(null);
|
|
19
|
+
const thumbnail = (turn) => turn.imageId === null ? void 0 : images.find((image) => image.id === turn.imageId);
|
|
20
|
+
const instructionOf = (turn) => turn.instruction || t("imageStudio.dialog.chat.fromDescription");
|
|
21
|
+
function send() {
|
|
22
|
+
if (running || !emptyAllowed && instruction.trim() === "") return;
|
|
23
|
+
const text = instruction.trim();
|
|
24
|
+
setInstruction("");
|
|
25
|
+
onSend(text);
|
|
26
|
+
}
|
|
27
|
+
function onKeyDown(event) {
|
|
28
|
+
if (event.key !== "Enter" || event.shiftKey || event.nativeEvent.isComposing) return;
|
|
29
|
+
event.preventDefault();
|
|
30
|
+
send();
|
|
31
|
+
}
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (log.current) log.current.scrollTop = log.current.scrollHeight;
|
|
34
|
+
}, [turns.length]);
|
|
35
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
36
|
+
className: "flex min-h-0 flex-1 flex-col gap-2",
|
|
37
|
+
"aria-labelledby": headingId,
|
|
38
|
+
children: [
|
|
39
|
+
/* @__PURE__ */ jsx("h3", {
|
|
40
|
+
id: headingId,
|
|
41
|
+
className: "text-sm font-medium text-foreground",
|
|
42
|
+
children: t("imageStudio.dialog.chat.heading")
|
|
43
|
+
}),
|
|
44
|
+
/* @__PURE__ */ jsxs("ol", {
|
|
45
|
+
ref: log,
|
|
46
|
+
className: "flex min-h-24 flex-1 flex-col gap-2 overflow-y-auto",
|
|
47
|
+
"data-image-studio-log": true,
|
|
48
|
+
children: [!turns.length && /* @__PURE__ */ jsx("li", {
|
|
49
|
+
className: "text-sm text-muted-foreground",
|
|
50
|
+
children: t("imageStudio.dialog.chat.empty")
|
|
51
|
+
}), turns.map((turn) => {
|
|
52
|
+
const picture = thumbnail(turn);
|
|
53
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
54
|
+
className: "flex items-start gap-2 rounded-md border border-border p-2",
|
|
55
|
+
"data-problem": turn.problem !== void 0,
|
|
56
|
+
"data-image-studio-turn": true,
|
|
57
|
+
children: [picture && /* @__PURE__ */ jsx("img", {
|
|
58
|
+
src: picture.url,
|
|
59
|
+
alt: t("imageStudio.dialog.chat.turnImage", { instruction: instructionOf(turn) }),
|
|
60
|
+
className: "size-12 shrink-0 rounded object-cover"
|
|
61
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
62
|
+
className: "min-w-0 flex-1",
|
|
63
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
64
|
+
className: cn("whitespace-pre-wrap break-words text-sm text-foreground", !turn.instruction && "italic text-muted-foreground"),
|
|
65
|
+
children: instructionOf(turn)
|
|
66
|
+
}), turn.problem && /* @__PURE__ */ jsx("p", {
|
|
67
|
+
className: "mt-1 whitespace-pre-wrap break-words text-sm text-destructive",
|
|
68
|
+
role: "alert",
|
|
69
|
+
children: turn.problem
|
|
70
|
+
})]
|
|
71
|
+
})]
|
|
72
|
+
}, turn.id);
|
|
73
|
+
})]
|
|
74
|
+
}),
|
|
75
|
+
/* @__PURE__ */ jsxs("div", {
|
|
76
|
+
className: "flex items-end gap-2",
|
|
77
|
+
children: [/* @__PURE__ */ jsx(Textarea, {
|
|
78
|
+
value: instruction,
|
|
79
|
+
className: "min-w-0 flex-1",
|
|
80
|
+
rows: 2,
|
|
81
|
+
placeholder: t("imageStudio.dialog.chat.placeholder"),
|
|
82
|
+
"aria-label": t("imageStudio.dialog.chat.label"),
|
|
83
|
+
disabled: running,
|
|
84
|
+
onChange: (event) => setInstruction(event.target.value),
|
|
85
|
+
onKeyDown
|
|
86
|
+
}), /* @__PURE__ */ jsxs(Button, {
|
|
87
|
+
disabled: running || !emptyAllowed && instruction.trim() === "",
|
|
88
|
+
onClick: send,
|
|
89
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
90
|
+
name: running ? "i-lucide-loader-circle" : "i-lucide-sparkles",
|
|
91
|
+
className: cn(running && "animate-spin")
|
|
92
|
+
}), running ? t("imageStudio.dialog.chat.running") : t("imageStudio.dialog.chat.send")]
|
|
93
|
+
})]
|
|
94
|
+
})
|
|
95
|
+
]
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
export { ImageStudioChat };
|
|
100
|
+
|
|
101
|
+
//# sourceMappingURL=ImageStudioChat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageStudioChat.js","names":[],"sources":["../../../../src/features/image-studio/react/ImageStudioChat.tsx"],"sourcesContent":["import { type KeyboardEvent, useEffect, useId, useRef, useState } from 'react'\nimport { Icon } from '../../../react/Icon'\nimport { Button } from '../../../react/ui/button'\nimport { Textarea } from '../../../react/ui/textarea'\nimport { cn } from '../../../react/ui/utils'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport type { ImageStudioImage, ImageStudioTurn } from '../state'\n\n/**\n * The chat of the Bild-Studio: the turn log (instruction, the picture it produced if the block still has it, the\n * problem when it failed) and the instruction input. `Enter` sends, `Shift+Enter` breaks the line; while `running` the\n * input and the button are disabled. An empty instruction is sent when `emptyAllowed` (the block has a description to generate from).\n */\nexport function ImageStudioChat({ turns, images, running, emptyAllowed, onSend }: {\n turns: readonly ImageStudioTurn[]\n /** The block's images, to show the thumbnail of a turn. */\n images: readonly ImageStudioImage[]\n running: boolean\n emptyAllowed: boolean\n onSend: (instruction: string) => void\n}) {\n const { t } = useEditorI18n()\n const headingId = useId()\n const [instruction, setInstruction] = useState('')\n const log = useRef<HTMLOListElement>(null)\n\n const thumbnail = (turn: ImageStudioTurn): ImageStudioImage | undefined =>\n turn.imageId === null ? undefined : images.find(image => image.id === turn.imageId)\n const instructionOf = (turn: ImageStudioTurn): string => turn.instruction || t('imageStudio.dialog.chat.fromDescription')\n\n function send() {\n if (running || (!emptyAllowed && instruction.trim() === '')) return\n const text = instruction.trim()\n setInstruction('')\n onSend(text)\n }\n\n function onKeyDown(event: KeyboardEvent<HTMLTextAreaElement>) {\n if (event.key !== 'Enter' || event.shiftKey || event.nativeEvent.isComposing) return\n event.preventDefault()\n send()\n }\n\n // The newest turn stays in view.\n useEffect(() => {\n if (log.current) log.current.scrollTop = log.current.scrollHeight\n }, [turns.length])\n\n return (\n <section className=\"flex min-h-0 flex-1 flex-col gap-2\" aria-labelledby={headingId}>\n <h3 id={headingId} className=\"text-sm font-medium text-foreground\">\n {t('imageStudio.dialog.chat.heading')}\n </h3>\n <ol ref={log} className=\"flex min-h-24 flex-1 flex-col gap-2 overflow-y-auto\" data-image-studio-log>\n {!turns.length && <li className=\"text-sm text-muted-foreground\">{t('imageStudio.dialog.chat.empty')}</li>}\n {turns.map((turn) => {\n const picture = thumbnail(turn)\n return (\n <li\n key={turn.id}\n className=\"flex items-start gap-2 rounded-md border border-border p-2\"\n data-problem={turn.problem !== undefined}\n data-image-studio-turn\n >\n {picture && (\n <img\n src={picture.url}\n alt={t('imageStudio.dialog.chat.turnImage', { instruction: instructionOf(turn) })}\n className=\"size-12 shrink-0 rounded object-cover\"\n />\n )}\n <div className=\"min-w-0 flex-1\">\n <p className={cn('whitespace-pre-wrap break-words text-sm text-foreground', !turn.instruction && 'italic text-muted-foreground')}>\n {instructionOf(turn)}\n </p>\n {turn.problem && (\n <p className=\"mt-1 whitespace-pre-wrap break-words text-sm text-destructive\" role=\"alert\">\n {turn.problem}\n </p>\n )}\n </div>\n </li>\n )\n })}\n </ol>\n <div className=\"flex items-end gap-2\">\n <Textarea\n value={instruction}\n className=\"min-w-0 flex-1\"\n rows={2}\n placeholder={t('imageStudio.dialog.chat.placeholder')}\n aria-label={t('imageStudio.dialog.chat.label')}\n disabled={running}\n onChange={event => setInstruction(event.target.value)}\n onKeyDown={onKeyDown}\n />\n <Button disabled={running || (!emptyAllowed && instruction.trim() === '')} onClick={send}>\n <Icon name={running ? 'i-lucide-loader-circle' : 'i-lucide-sparkles'} className={cn(running && 'animate-spin')} />\n {running ? t('imageStudio.dialog.chat.running') : t('imageStudio.dialog.chat.send')}\n </Button>\n </div>\n </section>\n )\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,SAAgB,gBAAgB,EAAE,OAAO,QAAQ,SAAS,cAAc,UAOrE;CACD,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,YAAY,MAAM;CACxB,MAAM,CAAC,aAAa,kBAAkB,SAAS,EAAE;CACjD,MAAM,MAAM,OAAyB,IAAI;CAEzC,MAAM,aAAa,SACjB,KAAK,YAAY,OAAO,KAAA,IAAY,OAAO,MAAK,UAAS,MAAM,OAAO,KAAK,OAAO;CACpF,MAAM,iBAAiB,SAAkC,KAAK,eAAe,EAAE,yCAAyC;CAExH,SAAS,OAAO;EACd,IAAI,WAAY,CAAC,gBAAgB,YAAY,KAAK,MAAM,IAAK;EAC7D,MAAM,OAAO,YAAY,KAAK;EAC9B,eAAe,EAAE;EACjB,OAAO,IAAI;CACb;CAEA,SAAS,UAAU,OAA2C;EAC5D,IAAI,MAAM,QAAQ,WAAW,MAAM,YAAY,MAAM,YAAY,aAAa;EAC9E,MAAM,eAAe;EACrB,KAAK;CACP;CAGA,gBAAgB;EACd,IAAI,IAAI,SAAS,IAAI,QAAQ,YAAY,IAAI,QAAQ;CACvD,GAAG,CAAC,MAAM,MAAM,CAAC;CAEjB,OACE,qBAAC,WAAD;EAAS,WAAU;EAAqC,mBAAiB;EAAzE,UAAA;GACE,oBAAC,MAAD;IAAI,IAAI;IAAW,WAAU;IAC1B,UAAA,EAAE,iCAAiC;GAClC,CAAA;GACJ,qBAAC,MAAD;IAAI,KAAK;IAAK,WAAU;IAAsD,yBAAA;IAA9E,UAAA,CACG,CAAC,MAAM,UAAU,oBAAC,MAAD;KAAI,WAAU;KAAiC,UAAA,EAAE,+BAA+B;IAAM,CAAA,GACvG,MAAM,KAAK,SAAS;KACnB,MAAM,UAAU,UAAU,IAAI;KAC9B,OACE,qBAAC,MAAD;MAEE,WAAU;MACV,gBAAc,KAAK,YAAY,KAAA;MAC/B,0BAAA;MAJF,UAAA,CAMG,WACC,oBAAC,OAAD;OACE,KAAK,QAAQ;OACb,KAAK,EAAE,qCAAqC,EAAE,aAAa,cAAc,IAAI,EAAE,CAAC;OAChF,WAAU;MACX,CAAA,GAEH,qBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,oBAAC,KAAD;QAAG,WAAW,GAAG,2DAA2D,CAAC,KAAK,eAAe,8BAA8B;QAC5H,UAAA,cAAc,IAAI;OAClB,CAAA,GACF,KAAK,WACJ,oBAAC,KAAD;QAAG,WAAU;QAAgE,MAAK;QAC/E,UAAA,KAAK;OACL,CAAA,CAEF;MACH,CAAA,CAAA;KAtBG,GAAA,KAAK,EAsBR;IAER,CAAC,CACC;;GACJ,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,oBAAC,UAAD;KACE,OAAO;KACP,WAAU;KACV,MAAM;KACN,aAAa,EAAE,qCAAqC;KACpD,cAAY,EAAE,+BAA+B;KAC7C,UAAU;KACV,WAAU,UAAS,eAAe,MAAM,OAAO,KAAK;KACzC;IACZ,CAAA,GACD,qBAAC,QAAD;KAAQ,UAAU,WAAY,CAAC,gBAAgB,YAAY,KAAK,MAAM;KAAK,SAAS;KAApF,UAAA,CACE,oBAAC,MAAD;MAAM,MAAM,UAAU,2BAA2B;MAAqB,WAAW,GAAG,WAAW,cAAc;KAAI,CAAA,GAChH,UAAU,EAAE,iCAAiC,IAAI,EAAE,8BAA8B,CAC5E;IACL,CAAA,CAAA;;EACE;;AAEb"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ImageStudioReactStore } from "./store.js";
|
|
2
|
+
//#region src/features/image-studio/react/ImageStudioDialog.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* The Bild-Studio: description and Prompt-Bausteine on the left, references and previous generations in the middle,
|
|
5
|
+
* the block's image and the chat on the right. The dialog applies the reducers of `../state` to `store.state` and
|
|
6
|
+
* hands every result to `store.update`; the store only generates, uploads and persists (see `ImageStudioReactStore`).
|
|
7
|
+
*
|
|
8
|
+
* The description is committed 500 ms after the last keystroke and at once on blur, on „Erzeugen“ and on close, so the
|
|
9
|
+
* host never builds a prompt from a stale text. Closing (or unmounting) aborts a running generation; an aborted
|
|
10
|
+
* generation adds no turn, a failed one adds a turn with the problem.
|
|
11
|
+
*/
|
|
12
|
+
export declare function ImageStudioDialog({ open, onOpenChange, store, title }: {
|
|
13
|
+
open: boolean;
|
|
14
|
+
onOpenChange: (open: boolean) => void;
|
|
15
|
+
store: ImageStudioReactStore;
|
|
16
|
+
title?: string;
|
|
17
|
+
}): import("react").JSX.Element;
|
|
18
|
+
//#endregion
|
|
19
|
+
//# sourceMappingURL=ImageStudioDialog.d.ts.map
|