@ecsplico/editor 3.3.0 → 3.5.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.
Files changed (94) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/core/abilities.js +1 -0
  3. package/dist/core/abilities.js.map +1 -1
  4. package/dist/core/backend-capability.d.ts +1 -1
  5. package/dist/core/backend-capability.js +2 -1
  6. package/dist/core/backend-capability.js.map +1 -1
  7. package/dist/core/backend-types.d.ts +25 -0
  8. package/dist/core/backend-types.js.map +1 -1
  9. package/dist/core/backend.d.ts +9 -1
  10. package/dist/core/backend.js.map +1 -1
  11. package/dist/core/http-backend.js +17 -0
  12. package/dist/core/http-backend.js.map +1 -1
  13. package/dist/core/schema/abilities.d.ts +1 -0
  14. package/dist/core/schema/abilities.js +4 -0
  15. package/dist/core/schema/abilities.js.map +1 -1
  16. package/dist/features/ai/protocol/generate.d.ts +1 -1
  17. package/dist/features/composites/components/CompositeChrome.js +10 -9
  18. package/dist/features/composites/components/CompositeChrome.js.map +1 -1
  19. package/dist/features/composites/kinds.d.ts +4 -0
  20. package/dist/features/composites/kinds.js.map +1 -1
  21. package/dist/features/composites/merge.d.ts +1 -1
  22. package/dist/features/composites/merge.js +5 -2
  23. package/dist/features/composites/merge.js.map +1 -1
  24. package/dist/features/composites/nodes.js +1 -0
  25. package/dist/features/composites/nodes.js.map +1 -1
  26. package/dist/features/composites/outline.d.ts +9 -2
  27. package/dist/features/composites/outline.js +41 -3
  28. package/dist/features/composites/outline.js.map +1 -1
  29. package/dist/features/composites/react/CompositeChrome.js +11 -9
  30. package/dist/features/composites/react/CompositeChrome.js.map +1 -1
  31. package/dist/features/composites/rows.d.ts +2 -0
  32. package/dist/features/composites/rows.js +1 -0
  33. package/dist/features/composites/rows.js.map +1 -1
  34. package/dist/features/image-studio/components/ImageStudioChat.js +121 -0
  35. package/dist/features/image-studio/components/ImageStudioChat.js.map +1 -0
  36. package/dist/features/image-studio/components/ImageStudioDialog.js +371 -0
  37. package/dist/features/image-studio/components/ImageStudioDialog.js.map +1 -0
  38. package/dist/features/image-studio/components/ImageStudioDialog.vue.d.ts +25 -0
  39. package/dist/features/image-studio/components/ImageStudioTile.js +74 -0
  40. package/dist/features/image-studio/components/ImageStudioTile.js.map +1 -0
  41. package/dist/features/image-studio/components/ImageStudioView.js +117 -0
  42. package/dist/features/image-studio/components/ImageStudioView.js.map +1 -0
  43. package/dist/features/image-studio/feature.d.ts +24 -0
  44. package/dist/features/image-studio/feature.js +53 -0
  45. package/dist/features/image-studio/feature.js.map +1 -0
  46. package/dist/features/image-studio/index.d.ts +5 -0
  47. package/dist/features/image-studio/index.js +4 -0
  48. package/dist/features/image-studio/node-store.d.ts +27 -0
  49. package/dist/features/image-studio/node-store.js +64 -0
  50. package/dist/features/image-studio/node-store.js.map +1 -0
  51. package/dist/features/image-studio/schema.d.ts +29 -0
  52. package/dist/features/image-studio/schema.js +86 -0
  53. package/dist/features/image-studio/schema.js.map +1 -0
  54. package/dist/features/image-studio/state.d.ts +52 -0
  55. package/dist/features/image-studio/state.js +147 -0
  56. package/dist/features/image-studio/state.js.map +1 -0
  57. package/dist/features/image-studio/store.d.ts +29 -0
  58. package/dist/features/image-studio/vue.d.ts +17 -0
  59. package/dist/features/image-studio/vue.js +20 -0
  60. package/dist/features/image-studio/vue.js.map +1 -0
  61. package/dist/features/media/schema.d.ts +5 -1
  62. package/dist/features/media/schema.js +3 -3
  63. package/dist/features/media/schema.js.map +1 -1
  64. package/dist/features/transcript/segment-node.js +1 -0
  65. package/dist/features/transcript/segment-node.js.map +1 -1
  66. package/dist/i18n/de/image-studio.js +39 -0
  67. package/dist/i18n/de/image-studio.js.map +1 -0
  68. package/dist/i18n/de.d.ts +32 -0
  69. package/dist/i18n/de.js +13 -11
  70. package/dist/i18n/de.js.map +1 -1
  71. package/dist/i18n/en/image-studio.js +39 -0
  72. package/dist/i18n/en/image-studio.js.map +1 -0
  73. package/dist/i18n/en.js +13 -11
  74. package/dist/i18n/en.js.map +1 -1
  75. package/dist/nuxt/module.d.ts +64 -0
  76. package/dist/server/handlers/images.d.ts +22 -0
  77. package/dist/server/handlers/images.js +46 -0
  78. package/dist/server/handlers/images.js.map +1 -0
  79. package/dist/server/handlers/index.d.ts +2 -1
  80. package/dist/server/handlers/index.js +2 -1
  81. package/dist/server/index.d.ts +2 -2
  82. package/dist/server/ports.d.ts +11 -1
  83. package/dist/server/ports.js +2 -1
  84. package/dist/server/ports.js.map +1 -1
  85. package/dist/server/routes.js +19 -0
  86. package/dist/server/routes.js.map +1 -1
  87. package/dist/tree/DocumentTree.js +5 -4
  88. package/dist/tree/DocumentTree.js.map +1 -1
  89. package/dist/tree/react/DocumentTree.js +2 -1
  90. package/dist/tree/react/DocumentTree.js.map +1 -1
  91. package/dist/tree/tree-items.js +1 -0
  92. package/dist/tree/tree-items.js.map +1 -1
  93. package/dist/vue/AdvancedEditor.vue.d.ts +1 -1
  94. package/package.json +16 -1
@@ -0,0 +1,371 @@
1
+ import { fetchErrorMessage } from "../../../core/fetch-error.js";
2
+ import { useUiKit } from "../../../vue/kit/use-ui-kit.js";
3
+ import { useEditorI18n } from "../../../vue/use-editor-i18n.js";
4
+ import { addGeneration, addReference, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock } from "../state.js";
5
+ import _sfc_main$1 from "./ImageStudioChat.js";
6
+ import _sfc_main$2 from "./ImageStudioTile.js";
7
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, onBeforeUnmount, openBlock, ref, renderList, toDisplayString, unref, useId, useTemplateRef, watch, withCtx, withModifiers } from "vue";
8
+ //#region src/features/image-studio/components/ImageStudioDialog.vue
9
+ const _hoisted_1 = {
10
+ class: "flex flex-col gap-3",
11
+ "data-image-studio-dialog": ""
12
+ };
13
+ const _hoisted_2 = { class: "grid gap-4 lg:h-[68vh] lg:grid-cols-[17rem_minmax(0,1fr)_minmax(0,22rem)]" };
14
+ const _hoisted_3 = ["aria-labelledby"];
15
+ const _hoisted_4 = ["id"];
16
+ const _hoisted_5 = ["id"];
17
+ const _hoisted_6 = ["aria-labelledby"];
18
+ const _hoisted_7 = {
19
+ key: 1,
20
+ class: "text-sm text-[var(--sk-muted)]"
21
+ };
22
+ const _hoisted_8 = { class: "flex min-h-0 flex-col gap-4 lg:overflow-y-auto" };
23
+ const _hoisted_9 = ["data-dragging", "aria-labelledby"];
24
+ const _hoisted_10 = { class: "flex items-center justify-between gap-2" };
25
+ const _hoisted_11 = ["id"];
26
+ const _hoisted_12 = {
27
+ key: 0,
28
+ class: "text-xs text-[var(--sk-muted)]",
29
+ role: "status"
30
+ };
31
+ const _hoisted_13 = {
32
+ key: 2,
33
+ class: "grid grid-cols-2 gap-2 sm:grid-cols-3"
34
+ };
35
+ const _hoisted_14 = {
36
+ key: 3,
37
+ class: "text-sm text-[var(--sk-muted)]"
38
+ };
39
+ const _hoisted_15 = ["aria-labelledby"];
40
+ const _hoisted_16 = ["id"];
41
+ const _hoisted_17 = {
42
+ key: 0,
43
+ class: "grid grid-cols-2 gap-2 sm:grid-cols-3"
44
+ };
45
+ const _hoisted_18 = {
46
+ key: 1,
47
+ class: "text-sm text-[var(--sk-muted)]"
48
+ };
49
+ const _hoisted_19 = { class: "flex min-h-0 flex-col gap-3" };
50
+ const _hoisted_20 = ["aria-labelledby"];
51
+ const _hoisted_21 = ["id"];
52
+ const _hoisted_22 = { class: "flex aspect-square items-center justify-center overflow-hidden rounded-md border border-[var(--sk-border)] bg-[var(--sk-bg-elevated)]" };
53
+ const _hoisted_23 = ["src", "alt"];
54
+ const _hoisted_24 = {
55
+ key: 1,
56
+ class: "text-sm text-[var(--sk-muted)]"
57
+ };
58
+ /**
59
+ * The Bild-Studio: description and Prompt-Bausteine on the left, references and previous generations in the middle,
60
+ * the block's image and the chat on the right. The dialog applies the reducers of `../state` to `store.state` and
61
+ * hands every result to `store.update`; the store only generates, uploads and persists (see `ImageStudioStore`).
62
+ *
63
+ * The description is committed 500 ms after the last keystroke and at once on blur, on „Erzeugen“ and on close, so the
64
+ * host never builds a prompt from a stale text. Closing (or unmounting) aborts a running generation; an aborted
65
+ * generation adds no turn, a failed one adds a turn with the problem.
66
+ */
67
+ const COMMIT_DELAY_MS = 500;
68
+ const _sfc_main = /*@__PURE__*/ defineComponent({
69
+ __name: "ImageStudioDialog",
70
+ props: {
71
+ open: { type: Boolean },
72
+ store: {},
73
+ title: {}
74
+ },
75
+ emits: ["update:open"],
76
+ setup(__props, { emit: __emit }) {
77
+ const props = __props;
78
+ const emit = __emit;
79
+ const { Alert: UAlert, Button: UButton, Modal: UModal, Switch: USwitch, Textarea: UTextarea } = useUiKit();
80
+ const { t } = useEditorI18n();
81
+ const uid = useId();
82
+ const state = computed(() => props.store.state.value);
83
+ const promptBlocks = computed(() => props.store.promptBlocks.value);
84
+ const problem = computed(() => props.store.problem.value);
85
+ const images = computed(() => [...state.value.generations, ...state.value.references]);
86
+ const picture = computed(() => selectedImage(state.value));
87
+ const draft = ref(state.value.description);
88
+ /** The description as last committed or received: the host's echo of our own commit must not overwrite a newer draft. */
89
+ let known = state.value.description;
90
+ let commitTimer;
91
+ function commitDescription() {
92
+ clearTimeout(commitTimer);
93
+ commitTimer = void 0;
94
+ if (draft.value === props.store.state.value.description) return;
95
+ known = draft.value;
96
+ props.store.update(setDescription(props.store.state.value, draft.value));
97
+ }
98
+ watch(draft, (text) => {
99
+ if (text === props.store.state.value.description) return;
100
+ clearTimeout(commitTimer);
101
+ commitTimer = setTimeout(commitDescription, COMMIT_DELAY_MS);
102
+ });
103
+ watch(() => state.value.description, (description) => {
104
+ if (description === known) return;
105
+ known = description;
106
+ if (commitTimer === void 0) draft.value = description;
107
+ });
108
+ const update = (next) => props.store.update(next);
109
+ const toggleBlock = (id) => update(togglePromptBlock(props.store.state.value, id));
110
+ const toggle = (id) => update(toggleImage(props.store.state.value, id));
111
+ const use = (id) => update(select(props.store.state.value, id));
112
+ const remove = (id) => update(removeImage(props.store.state.value, id));
113
+ const fileInputRef = useTemplateRef("fileInput");
114
+ const uploading = ref(0);
115
+ const uploadProblem = ref();
116
+ const dragging = ref(false);
117
+ async function upload(files) {
118
+ const pictures = files.filter((file) => file.type.startsWith("image/"));
119
+ if (!pictures.length) return;
120
+ uploadProblem.value = void 0;
121
+ uploading.value += pictures.length;
122
+ await Promise.all(pictures.map(async (file) => {
123
+ try {
124
+ const image = await props.store.upload(file);
125
+ update(addReference(props.store.state.value, image));
126
+ } catch (error) {
127
+ uploadProblem.value = t("imageStudio.dialog.references.failed", { message: fetchErrorMessage(error) });
128
+ } finally {
129
+ uploading.value -= 1;
130
+ }
131
+ }));
132
+ }
133
+ function onFilesChosen(event) {
134
+ const input = event.target;
135
+ const files = [...input.files ?? []];
136
+ input.value = "";
137
+ upload(files);
138
+ }
139
+ function onDrop(event) {
140
+ dragging.value = false;
141
+ upload([...event.dataTransfer?.files ?? []]);
142
+ }
143
+ const running = ref(false);
144
+ let controller;
145
+ function abortGeneration() {
146
+ controller?.abort();
147
+ controller = void 0;
148
+ running.value = false;
149
+ }
150
+ async function generate(instruction) {
151
+ if (controller) return;
152
+ commitDescription();
153
+ const own = new AbortController();
154
+ controller = own;
155
+ running.value = true;
156
+ const turn = {
157
+ id: crypto.randomUUID(),
158
+ instruction,
159
+ imageId: null,
160
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
161
+ };
162
+ try {
163
+ const image = await props.store.generate(instruction, own.signal);
164
+ if (own.signal.aborted) return;
165
+ props.store.update(addGeneration(props.store.state.value, image, turn));
166
+ } catch (error) {
167
+ if (own.signal.aborted) return;
168
+ const current = props.store.state.value;
169
+ props.store.update({
170
+ ...current,
171
+ turns: [...current.turns, {
172
+ ...turn,
173
+ problem: fetchErrorMessage(error)
174
+ }]
175
+ });
176
+ } finally {
177
+ if (controller === own) {
178
+ controller = void 0;
179
+ running.value = false;
180
+ }
181
+ }
182
+ }
183
+ function close() {
184
+ commitDescription();
185
+ abortGeneration();
186
+ emit("update:open", false);
187
+ }
188
+ watch(() => props.open, (open) => {
189
+ if (open) return;
190
+ commitDescription();
191
+ abortGeneration();
192
+ });
193
+ onBeforeUnmount(() => {
194
+ commitDescription();
195
+ abortGeneration();
196
+ });
197
+ return (_ctx, _cache) => {
198
+ return openBlock(), createBlock(unref(UModal), {
199
+ open: __props.open,
200
+ title: __props.title ?? unref(t)("imageStudio.dialog.title"),
201
+ ui: { content: "sm:w-[72rem] sm:max-w-[calc(100vw-2rem)]" },
202
+ "onUpdate:open": _cache[5] || (_cache[5] = (value) => {
203
+ if (!value) close();
204
+ })
205
+ }, {
206
+ body: withCtx(() => [createElementVNode("div", _hoisted_1, [problem.value ? (openBlock(), createBlock(unref(UAlert), {
207
+ key: 0,
208
+ color: "error",
209
+ variant: "subtle",
210
+ title: problem.value
211
+ }, null, 8, ["title"])) : createCommentVNode("v-if", true), createElementVNode("div", _hoisted_2, [
212
+ createCommentVNode(" Left: what the picture shows. "),
213
+ createElementVNode("section", {
214
+ class: "flex min-h-0 flex-col gap-3 lg:overflow-y-auto",
215
+ "aria-labelledby": `${unref(uid)}-description`
216
+ }, [
217
+ createElementVNode("h3", {
218
+ id: `${unref(uid)}-description`,
219
+ class: "text-sm font-medium text-[var(--sk-fg-highlighted)]"
220
+ }, toDisplayString(unref(t)("imageStudio.dialog.description.heading")), 9, _hoisted_4),
221
+ createVNode(unref(UTextarea), {
222
+ modelValue: draft.value,
223
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => draft.value = $event),
224
+ rows: 6,
225
+ maxrows: 14,
226
+ autoresize: "",
227
+ placeholder: unref(t)("imageStudio.dialog.description.placeholder"),
228
+ "aria-label": unref(t)("imageStudio.dialog.description.heading"),
229
+ onBlur: commitDescription
230
+ }, null, 8, [
231
+ "modelValue",
232
+ "placeholder",
233
+ "aria-label"
234
+ ]),
235
+ createElementVNode("h3", {
236
+ id: `${unref(uid)}-blocks`,
237
+ class: "mt-2 text-sm font-medium text-[var(--sk-fg-highlighted)]"
238
+ }, toDisplayString(unref(t)("imageStudio.dialog.blocks.heading")), 9, _hoisted_5),
239
+ promptBlocks.value.length ? (openBlock(), createElementBlock("ul", {
240
+ key: 0,
241
+ class: "flex flex-col gap-3",
242
+ "aria-labelledby": `${unref(uid)}-blocks`
243
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList(promptBlocks.value, (block) => {
244
+ return openBlock(), createElementBlock("li", { key: block.id }, [createVNode(unref(USwitch), {
245
+ "model-value": state.value.promptBlockIds.includes(block.id),
246
+ label: block.label,
247
+ description: block.text,
248
+ "onUpdate:modelValue": ($event) => toggleBlock(block.id)
249
+ }, null, 8, [
250
+ "model-value",
251
+ "label",
252
+ "description",
253
+ "onUpdate:modelValue"
254
+ ])]);
255
+ }), 128))], 8, _hoisted_6)) : (openBlock(), createElementBlock("p", _hoisted_7, toDisplayString(unref(t)("imageStudio.dialog.blocks.empty")), 1))
256
+ ], 8, _hoisted_3),
257
+ createCommentVNode(" Middle: references and previous generations. "),
258
+ createElementVNode("div", _hoisted_8, [createElementVNode("section", {
259
+ class: "flex flex-col gap-2 rounded-md border border-dashed border-transparent p-1 data-[dragging=true]:border-[var(--sk-primary)]",
260
+ "data-dragging": dragging.value,
261
+ "aria-labelledby": `${unref(uid)}-references`,
262
+ "data-image-studio-drop": "",
263
+ onDragenter: _cache[2] || (_cache[2] = withModifiers(($event) => dragging.value = true, ["prevent"])),
264
+ onDragover: _cache[3] || (_cache[3] = withModifiers(($event) => dragging.value = true, ["prevent"])),
265
+ onDragleave: _cache[4] || (_cache[4] = ($event) => dragging.value = false),
266
+ onDrop: withModifiers(onDrop, ["prevent"])
267
+ }, [
268
+ createElementVNode("div", _hoisted_10, [
269
+ createElementVNode("h3", {
270
+ id: `${unref(uid)}-references`,
271
+ class: "text-sm font-medium text-[var(--sk-fg-highlighted)]"
272
+ }, toDisplayString(unref(t)("imageStudio.dialog.references.heading")), 9, _hoisted_11),
273
+ createVNode(unref(UButton), {
274
+ label: unref(t)("imageStudio.dialog.references.upload"),
275
+ icon: "i-lucide-upload",
276
+ size: "xs",
277
+ color: "neutral",
278
+ variant: "outline",
279
+ loading: uploading.value > 0,
280
+ onClick: _cache[1] || (_cache[1] = ($event) => fileInputRef.value?.click())
281
+ }, null, 8, ["label", "loading"]),
282
+ createElementVNode("input", {
283
+ ref: "fileInput",
284
+ type: "file",
285
+ accept: "image/*",
286
+ multiple: "",
287
+ hidden: "",
288
+ "data-image-studio-file": "",
289
+ onChange: onFilesChosen
290
+ }, null, 544)
291
+ ]),
292
+ uploading.value ? (openBlock(), createElementBlock("p", _hoisted_12, toDisplayString(unref(t)("imageStudio.dialog.references.uploading")), 1)) : createCommentVNode("v-if", true),
293
+ uploadProblem.value ? (openBlock(), createBlock(unref(UAlert), {
294
+ key: 1,
295
+ color: "error",
296
+ variant: "subtle",
297
+ title: uploadProblem.value
298
+ }, null, 8, ["title"])) : createCommentVNode("v-if", true),
299
+ state.value.references.length ? (openBlock(), createElementBlock("ul", _hoisted_13, [(openBlock(true), createElementBlock(Fragment, null, renderList(state.value.references, (image) => {
300
+ return openBlock(), createBlock(_sfc_main$2, {
301
+ key: image.id,
302
+ image,
303
+ selected: state.value.selected === image.id,
304
+ onToggle: ($event) => toggle(image.id),
305
+ onSelect: ($event) => use(image.id),
306
+ onRemove: ($event) => remove(image.id)
307
+ }, null, 8, [
308
+ "image",
309
+ "selected",
310
+ "onToggle",
311
+ "onSelect",
312
+ "onRemove"
313
+ ]);
314
+ }), 128))])) : (openBlock(), createElementBlock("p", _hoisted_14, toDisplayString(unref(t)("imageStudio.dialog.references.drop")), 1))
315
+ ], 40, _hoisted_9), createElementVNode("section", {
316
+ class: "flex flex-col gap-2 p-1",
317
+ "aria-labelledby": `${unref(uid)}-generations`
318
+ }, [createElementVNode("h3", {
319
+ id: `${unref(uid)}-generations`,
320
+ class: "text-sm font-medium text-[var(--sk-fg-highlighted)]"
321
+ }, toDisplayString(unref(t)("imageStudio.dialog.generations.heading")), 9, _hoisted_16), state.value.generations.length ? (openBlock(), createElementBlock("ul", _hoisted_17, [(openBlock(true), createElementBlock(Fragment, null, renderList(state.value.generations, (image) => {
322
+ return openBlock(), createBlock(_sfc_main$2, {
323
+ key: image.id,
324
+ image,
325
+ selected: state.value.selected === image.id,
326
+ onToggle: ($event) => toggle(image.id),
327
+ onSelect: ($event) => use(image.id),
328
+ onRemove: ($event) => remove(image.id)
329
+ }, null, 8, [
330
+ "image",
331
+ "selected",
332
+ "onToggle",
333
+ "onSelect",
334
+ "onRemove"
335
+ ]);
336
+ }), 128))])) : (openBlock(), createElementBlock("p", _hoisted_18, toDisplayString(unref(t)("imageStudio.dialog.generations.empty")), 1))], 8, _hoisted_15)]),
337
+ createCommentVNode(" Right: the block's image and the chat. "),
338
+ createElementVNode("div", _hoisted_19, [createElementVNode("section", {
339
+ class: "flex flex-col gap-2",
340
+ "aria-labelledby": `${unref(uid)}-preview`
341
+ }, [createElementVNode("h3", {
342
+ id: `${unref(uid)}-preview`,
343
+ class: "text-sm font-medium text-[var(--sk-fg-highlighted)]"
344
+ }, toDisplayString(unref(t)("imageStudio.dialog.preview.heading")), 9, _hoisted_21), createElementVNode("div", _hoisted_22, [picture.value ? (openBlock(), createElementBlock("img", {
345
+ key: 0,
346
+ src: picture.value.url,
347
+ alt: picture.value.title || unref(t)("imageStudio.dialog.tile.untitled"),
348
+ class: "size-full object-contain",
349
+ "data-image-studio-preview": ""
350
+ }, null, 8, _hoisted_23)) : (openBlock(), createElementBlock("p", _hoisted_24, toDisplayString(unref(t)("imageStudio.dialog.preview.empty")), 1))])], 8, _hoisted_20), createVNode(_sfc_main$1, {
351
+ turns: state.value.turns,
352
+ images: images.value,
353
+ running: running.value,
354
+ "empty-allowed": draft.value.trim() !== "",
355
+ onSend: generate
356
+ }, null, 8, [
357
+ "turns",
358
+ "images",
359
+ "running",
360
+ "empty-allowed"
361
+ ])])
362
+ ])])]),
363
+ _: 1
364
+ }, 8, ["open", "title"]);
365
+ };
366
+ }
367
+ });
368
+ //#endregion
369
+ export { _sfc_main as default };
370
+
371
+ //# sourceMappingURL=ImageStudioDialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImageStudioDialog.js","names":["_openBlock","_createBlock","_unref","_withCtx","_createElementVNode","_createCommentVNode","_toDisplayString","_createVNode","_createElementBlock","_Fragment","_renderList","_withModifiers","ImageStudioTile","ImageStudioChat"],"sources":["../../../../src/features/image-studio/components/ImageStudioDialog.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, useId, useTemplateRef, watch } from 'vue'\nimport { fetchErrorMessage } from '../../../core'\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { useEditorI18n } from '../../../vue/use-editor-i18n'\nimport {\n addGeneration,\n addReference,\n type ImageStudioState,\n type ImageStudioTurn,\n removeImage,\n select,\n selectedImage,\n setDescription,\n toggleImage,\n togglePromptBlock,\n} from '../state'\nimport type { ImageStudioStore } from '../store'\nimport ImageStudioChat from './ImageStudioChat.vue'\nimport ImageStudioTile from './ImageStudioTile.vue'\n\n/**\n * The Bild-Studio: description and Prompt-Bausteine on the left, references and previous generations in the middle,\n * the block's image and the chat on the right. The dialog applies the reducers of `../state` to `store.state` and\n * hands every result to `store.update`; the store only generates, uploads and persists (see `ImageStudioStore`).\n *\n * The description is committed 500 ms after the last keystroke and at once on blur, on „Erzeugen“ and on close, so the\n * host never builds a prompt from a stale text. Closing (or unmounting) aborts a running generation; an aborted\n * generation adds no turn, a failed one adds a turn with the problem.\n */\nconst props = defineProps<{ open: boolean, store: ImageStudioStore, title?: string }>()\nconst emit = defineEmits<{ 'update:open': [open: boolean] }>()\n\nconst { Alert: UAlert, Button: UButton, Modal: UModal, Switch: USwitch, Textarea: UTextarea } = useUiKit()\nconst { t } = useEditorI18n()\n\nconst uid = useId()\nconst state = computed(() => props.store.state.value)\nconst promptBlocks = computed(() => props.store.promptBlocks.value)\nconst problem = computed(() => props.store.problem.value)\nconst images = computed(() => [...state.value.generations, ...state.value.references])\nconst picture = computed(() => selectedImage(state.value))\n\n// --- Description ----------------------------------------------------------------------------------------------------\n\nconst COMMIT_DELAY_MS = 500\nconst draft = ref(state.value.description)\n/** The description as last committed or received: the host's echo of our own commit must not overwrite a newer draft. */\nlet known = state.value.description\nlet commitTimer: ReturnType<typeof setTimeout> | undefined\n\nfunction commitDescription() {\n clearTimeout(commitTimer)\n commitTimer = undefined\n if (draft.value === props.store.state.value.description) return\n known = draft.value\n props.store.update(setDescription(props.store.state.value, draft.value))\n}\n\nwatch(draft, (text) => {\n if (text === props.store.state.value.description) return\n clearTimeout(commitTimer)\n commitTimer = setTimeout(commitDescription, COMMIT_DELAY_MS)\n})\n// A change of the host (undo, another tab) shows unless the user has typed something that is not committed yet.\nwatch(() => state.value.description, (description) => {\n if (description === known) return\n known = description\n if (commitTimer === undefined) draft.value = description\n})\n\n// --- Reducers -------------------------------------------------------------------------------------------------------\n\nconst update = (next: ImageStudioState) => props.store.update(next)\nconst toggleBlock = (id: string) => update(togglePromptBlock(props.store.state.value, id))\nconst toggle = (id: string) => update(toggleImage(props.store.state.value, id))\nconst use = (id: string) => update(select(props.store.state.value, id))\nconst remove = (id: string) => update(removeImage(props.store.state.value, id))\n\n// --- References: upload and drop ------------------------------------------------------------------------------------\n\nconst fileInputRef = useTemplateRef<HTMLInputElement>('fileInput')\nconst uploading = ref(0)\nconst uploadProblem = ref<string>()\nconst dragging = ref(false)\n\nasync function upload(files: readonly File[]) {\n const pictures = files.filter(file => file.type.startsWith('image/'))\n if (!pictures.length) return\n uploadProblem.value = undefined\n uploading.value += pictures.length\n await Promise.all(pictures.map(async (file) => {\n try {\n const image = await props.store.upload(file)\n update(addReference(props.store.state.value, image))\n } catch (error) {\n uploadProblem.value = t('imageStudio.dialog.references.failed', { message: fetchErrorMessage(error) })\n } finally {\n uploading.value -= 1\n }\n }))\n}\n\nfunction onFilesChosen(event: Event) {\n const input = event.target as HTMLInputElement\n const files = [...(input.files ?? [])]\n input.value = '' // the same file can be chosen again\n void upload(files)\n}\n\nfunction onDrop(event: DragEvent) {\n dragging.value = false\n void upload([...(event.dataTransfer?.files ?? [])])\n}\n\n// --- Generation -----------------------------------------------------------------------------------------------------\n\nconst running = ref(false)\nlet controller: AbortController | undefined\n\nfunction abortGeneration() {\n controller?.abort()\n controller = undefined\n running.value = false\n}\n\nasync function generate(instruction: string) {\n if (controller) return\n commitDescription()\n const own = new AbortController()\n controller = own\n running.value = true\n const turn: ImageStudioTurn = { id: crypto.randomUUID(), instruction, imageId: null, createdAt: new Date().toISOString() }\n try {\n const image = await props.store.generate(instruction, own.signal)\n if (own.signal.aborted) return\n props.store.update(addGeneration(props.store.state.value, image, turn))\n } catch (error) {\n if (own.signal.aborted) return\n const current = props.store.state.value\n props.store.update({ ...current, turns: [...current.turns, { ...turn, problem: fetchErrorMessage(error) }] })\n } finally {\n if (controller === own) {\n controller = undefined\n running.value = false\n }\n }\n}\n\n// --- Closing --------------------------------------------------------------------------------------------------------\n\nfunction close() {\n commitDescription()\n abortGeneration()\n emit('update:open', false)\n}\n\nwatch(() => props.open, (open) => {\n if (open) return\n commitDescription()\n abortGeneration()\n})\n\nonBeforeUnmount(() => {\n commitDescription()\n abortGeneration()\n})\n</script>\n\n<template>\n <UModal\n :open=\"open\"\n :title=\"title ?? t('imageStudio.dialog.title')\"\n :ui=\"{ content: 'sm:w-[72rem] sm:max-w-[calc(100vw-2rem)]' }\"\n @update:open=\"(value: boolean) => { if (!value) close() }\"\n >\n <template #body>\n <div class=\"flex flex-col gap-3\" data-image-studio-dialog>\n <UAlert v-if=\"problem\" color=\"error\" variant=\"subtle\" :title=\"problem\" />\n <div class=\"grid gap-4 lg:h-[68vh] lg:grid-cols-[17rem_minmax(0,1fr)_minmax(0,22rem)]\">\n <!-- Left: what the picture shows. -->\n <section class=\"flex min-h-0 flex-col gap-3 lg:overflow-y-auto\" :aria-labelledby=\"`${uid}-description`\">\n <h3 :id=\"`${uid}-description`\" class=\"text-sm font-medium text-[var(--sk-fg-highlighted)]\">\n {{ t('imageStudio.dialog.description.heading') }}\n </h3>\n <UTextarea\n v-model=\"draft\"\n :rows=\"6\"\n :maxrows=\"14\"\n autoresize\n :placeholder=\"t('imageStudio.dialog.description.placeholder')\"\n :aria-label=\"t('imageStudio.dialog.description.heading')\"\n @blur=\"commitDescription\"\n />\n <h3 :id=\"`${uid}-blocks`\" class=\"mt-2 text-sm font-medium text-[var(--sk-fg-highlighted)]\">\n {{ t('imageStudio.dialog.blocks.heading') }}\n </h3>\n <ul v-if=\"promptBlocks.length\" class=\"flex flex-col gap-3\" :aria-labelledby=\"`${uid}-blocks`\">\n <li v-for=\"block in promptBlocks\" :key=\"block.id\">\n <USwitch\n :model-value=\"state.promptBlockIds.includes(block.id)\"\n :label=\"block.label\"\n :description=\"block.text\"\n @update:model-value=\"toggleBlock(block.id)\"\n />\n </li>\n </ul>\n <p v-else class=\"text-sm text-[var(--sk-muted)]\">\n {{ t('imageStudio.dialog.blocks.empty') }}\n </p>\n </section>\n\n <!-- Middle: references and previous generations. -->\n <div class=\"flex min-h-0 flex-col gap-4 lg:overflow-y-auto\">\n <section\n class=\"flex flex-col gap-2 rounded-md border border-dashed border-transparent p-1 data-[dragging=true]:border-[var(--sk-primary)]\"\n :data-dragging=\"dragging\"\n :aria-labelledby=\"`${uid}-references`\"\n data-image-studio-drop\n @dragenter.prevent=\"dragging = true\"\n @dragover.prevent=\"dragging = true\"\n @dragleave=\"dragging = false\"\n @drop.prevent=\"onDrop\"\n >\n <div class=\"flex items-center justify-between gap-2\">\n <h3 :id=\"`${uid}-references`\" class=\"text-sm font-medium text-[var(--sk-fg-highlighted)]\">\n {{ t('imageStudio.dialog.references.heading') }}\n </h3>\n <UButton\n :label=\"t('imageStudio.dialog.references.upload')\"\n icon=\"i-lucide-upload\"\n size=\"xs\"\n color=\"neutral\"\n variant=\"outline\"\n :loading=\"uploading > 0\"\n @click=\"fileInputRef?.click()\"\n />\n <input ref=\"fileInput\" type=\"file\" accept=\"image/*\" multiple hidden data-image-studio-file @change=\"onFilesChosen\">\n </div>\n <p v-if=\"uploading\" class=\"text-xs text-[var(--sk-muted)]\" role=\"status\">\n {{ t('imageStudio.dialog.references.uploading') }}\n </p>\n <UAlert v-if=\"uploadProblem\" color=\"error\" variant=\"subtle\" :title=\"uploadProblem\" />\n <ul v-if=\"state.references.length\" class=\"grid grid-cols-2 gap-2 sm:grid-cols-3\">\n <ImageStudioTile\n v-for=\"image in state.references\"\n :key=\"image.id\"\n :image=\"image\"\n :selected=\"state.selected === image.id\"\n @toggle=\"toggle(image.id)\"\n @select=\"use(image.id)\"\n @remove=\"remove(image.id)\"\n />\n </ul>\n <p v-else class=\"text-sm text-[var(--sk-muted)]\">\n {{ t('imageStudio.dialog.references.drop') }}\n </p>\n </section>\n\n <section class=\"flex flex-col gap-2 p-1\" :aria-labelledby=\"`${uid}-generations`\">\n <h3 :id=\"`${uid}-generations`\" class=\"text-sm font-medium text-[var(--sk-fg-highlighted)]\">\n {{ t('imageStudio.dialog.generations.heading') }}\n </h3>\n <ul v-if=\"state.generations.length\" class=\"grid grid-cols-2 gap-2 sm:grid-cols-3\">\n <ImageStudioTile\n v-for=\"image in state.generations\"\n :key=\"image.id\"\n :image=\"image\"\n :selected=\"state.selected === image.id\"\n @toggle=\"toggle(image.id)\"\n @select=\"use(image.id)\"\n @remove=\"remove(image.id)\"\n />\n </ul>\n <p v-else class=\"text-sm text-[var(--sk-muted)]\">\n {{ t('imageStudio.dialog.generations.empty') }}\n </p>\n </section>\n </div>\n\n <!-- Right: the block's image and the chat. -->\n <div class=\"flex min-h-0 flex-col gap-3\">\n <section class=\"flex flex-col gap-2\" :aria-labelledby=\"`${uid}-preview`\">\n <h3 :id=\"`${uid}-preview`\" class=\"text-sm font-medium text-[var(--sk-fg-highlighted)]\">\n {{ t('imageStudio.dialog.preview.heading') }}\n </h3>\n <div class=\"flex aspect-square items-center justify-center overflow-hidden rounded-md border border-[var(--sk-border)] bg-[var(--sk-bg-elevated)]\">\n <img\n v-if=\"picture\"\n :src=\"picture.url\"\n :alt=\"picture.title || t('imageStudio.dialog.tile.untitled')\"\n class=\"size-full object-contain\"\n data-image-studio-preview\n >\n <p v-else class=\"text-sm text-[var(--sk-muted)]\">\n {{ t('imageStudio.dialog.preview.empty') }}\n </p>\n </div>\n </section>\n <ImageStudioChat\n :turns=\"state.turns\"\n :images=\"images\"\n :running=\"running\"\n :empty-allowed=\"draft.trim() !== ''\"\n @send=\"generate\"\n />\n </div>\n </div>\n </div>\n </template>\n </UModal>\n</template>\n"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { ImageStudioStore } from "../store.js";
2
+ //#region src/features/image-studio/components/ImageStudioDialog.vue.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 `ImageStudioStore`).
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
+ type __VLS_Props = {
13
+ open: boolean;
14
+ store: ImageStudioStore;
15
+ title?: string;
16
+ };
17
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
18
+ "update:open": (open: boolean) => any;
19
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
20
+ "onUpdate:open"?: ((open: boolean) => any) | undefined;
21
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
22
+ declare const _default: typeof __VLS_export;
23
+ //#endregion
24
+ export { _default as default };
25
+ //# sourceMappingURL=ImageStudioDialog.vue.d.ts.map
@@ -0,0 +1,74 @@
1
+ import { useUiKit } from "../../../vue/kit/use-ui-kit.js";
2
+ import { useEditorI18n } from "../../../vue/use-editor-i18n.js";
3
+ import { computed, createElementBlock, createElementVNode, createVNode, defineComponent, openBlock, unref } from "vue";
4
+ //#region src/features/image-studio/components/ImageStudioTile.vue
5
+ const _hoisted_1 = ["data-active", "data-selected"];
6
+ const _hoisted_2 = ["aria-pressed", "aria-label"];
7
+ const _hoisted_3 = ["src", "alt"];
8
+ const _hoisted_4 = { class: "flex items-center justify-between gap-1" };
9
+ /**
10
+ * One reference or previous generation of the Bild-Studio. A click on the picture flips `active` (goes into the next
11
+ * generation, ring via `data-active`), „Als Bild verwenden“ makes it the block's image, „Entfernen“ drops it from the block.
12
+ */
13
+ const _sfc_main = /*@__PURE__*/ defineComponent({
14
+ __name: "ImageStudioTile",
15
+ props: {
16
+ image: {},
17
+ selected: { type: Boolean }
18
+ },
19
+ emits: [
20
+ "toggle",
21
+ "select",
22
+ "remove"
23
+ ],
24
+ setup(__props, { emit: __emit }) {
25
+ const props = __props;
26
+ const emit = __emit;
27
+ const { Button: UButton } = useUiKit();
28
+ const { t } = useEditorI18n();
29
+ const title = computed(() => props.image.title || t("imageStudio.dialog.tile.untitled"));
30
+ return (_ctx, _cache) => {
31
+ return openBlock(), createElementBlock("li", {
32
+ class: "flex flex-col gap-1.5 rounded-md border border-[var(--sk-border)] p-1.5 data-[active=true]:border-[var(--sk-primary)] data-[active=true]:ring-2 data-[active=true]:ring-[var(--sk-primary)]",
33
+ "data-active": __props.image.active,
34
+ "data-selected": __props.selected,
35
+ "data-image-studio-tile": ""
36
+ }, [createElementVNode("button", {
37
+ type: "button",
38
+ class: "block overflow-hidden rounded bg-[var(--sk-bg-elevated)] focus-visible:outline-2 focus-visible:outline-[var(--sk-primary)]",
39
+ "aria-pressed": __props.image.active,
40
+ "aria-label": unref(t)("imageStudio.dialog.tile.toggle", { title: title.value }),
41
+ onClick: _cache[0] || (_cache[0] = ($event) => emit("toggle"))
42
+ }, [createElementVNode("img", {
43
+ src: __props.image.url,
44
+ alt: title.value,
45
+ class: "aspect-square w-full object-cover",
46
+ draggable: "false"
47
+ }, null, 8, _hoisted_3)], 8, _hoisted_2), createElementVNode("div", _hoisted_4, [createVNode(unref(UButton), {
48
+ label: unref(t)("imageStudio.dialog.tile.use"),
49
+ icon: __props.selected ? "i-lucide-check" : void 0,
50
+ size: "xs",
51
+ color: "primary",
52
+ variant: __props.selected ? "solid" : "outline",
53
+ "aria-pressed": __props.selected,
54
+ onClick: _cache[1] || (_cache[1] = ($event) => emit("select"))
55
+ }, null, 8, [
56
+ "label",
57
+ "icon",
58
+ "variant",
59
+ "aria-pressed"
60
+ ]), createVNode(unref(UButton), {
61
+ icon: "i-lucide-trash-2",
62
+ size: "xs",
63
+ color: "neutral",
64
+ variant: "ghost",
65
+ "aria-label": unref(t)("imageStudio.dialog.tile.remove"),
66
+ onClick: _cache[2] || (_cache[2] = ($event) => emit("remove"))
67
+ }, null, 8, ["aria-label"])])], 8, _hoisted_1);
68
+ };
69
+ }
70
+ });
71
+ //#endregion
72
+ export { _sfc_main as default };
73
+
74
+ //# sourceMappingURL=ImageStudioTile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImageStudioTile.js","names":["_openBlock","_createElementBlock","_createElementVNode","_unref","_createVNode"],"sources":["../../../../src/features/image-studio/components/ImageStudioTile.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { useEditorI18n } from '../../../vue/use-editor-i18n'\nimport type { ImageStudioImage } from '../state'\n\n/**\n * One reference or previous generation of the Bild-Studio. A click on the picture flips `active` (goes into the next\n * generation, ring via `data-active`), „Als Bild verwenden“ makes it the block's image, „Entfernen“ drops it from the block.\n */\nconst props = defineProps<{ image: ImageStudioImage, selected: boolean }>()\nconst emit = defineEmits<{ toggle: [], select: [], remove: [] }>()\n\nconst { Button: UButton } = useUiKit()\nconst { t } = useEditorI18n()\n\nconst title = computed(() => props.image.title || t('imageStudio.dialog.tile.untitled'))\n</script>\n\n<template>\n <li\n class=\"flex flex-col gap-1.5 rounded-md border border-[var(--sk-border)] p-1.5 data-[active=true]:border-[var(--sk-primary)] data-[active=true]:ring-2 data-[active=true]:ring-[var(--sk-primary)]\"\n :data-active=\"image.active\"\n :data-selected=\"selected\"\n data-image-studio-tile\n >\n <button\n type=\"button\"\n class=\"block overflow-hidden rounded bg-[var(--sk-bg-elevated)] focus-visible:outline-2 focus-visible:outline-[var(--sk-primary)]\"\n :aria-pressed=\"image.active\"\n :aria-label=\"t('imageStudio.dialog.tile.toggle', { title })\"\n @click=\"emit('toggle')\"\n >\n <img :src=\"image.url\" :alt=\"title\" class=\"aspect-square w-full object-cover\" draggable=\"false\">\n </button>\n <div class=\"flex items-center justify-between gap-1\">\n <UButton\n :label=\"t('imageStudio.dialog.tile.use')\"\n :icon=\"selected ? 'i-lucide-check' : undefined\"\n size=\"xs\"\n color=\"primary\"\n :variant=\"selected ? 'solid' : 'outline'\"\n :aria-pressed=\"selected\"\n @click=\"emit('select')\"\n />\n <UButton\n icon=\"i-lucide-trash-2\"\n size=\"xs\"\n color=\"neutral\"\n variant=\"ghost\"\n :aria-label=\"t('imageStudio.dialog.tile.remove')\"\n @click=\"emit('remove')\"\n />\n </div>\n </li>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;EAUA,MAAM,QAAQ;EACd,MAAM,OAAO;EAEb,MAAM,EAAE,QAAQ,YAAY,SAAS;EACrC,MAAM,EAAE,MAAM,cAAc;EAE5B,MAAM,QAAQ,eAAe,MAAM,MAAM,SAAS,EAAE,kCAAkC,CAAC;;GAIrF,OAAAA,UAAA,GAAAC,mBAkCK,MAAA;IAjCH,OAAM;IACL,eAAa,QAAA,MAAM;IACnB,iBAAe,QAAA;IAChB,0BAAA;GAEA,GAAA,CAAAC,mBAQS,UAAA;IAPP,MAAK;IACL,OAAM;IACL,gBAAc,QAAA,MAAM;IACpB,cAAYC,MAAA,CAAA,CAAC,CAAA,kCAAA,EAAA,OAAqC,MAAA,MAAK,CAAA;IACvD,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,QAAA;GAEZ,GAAA,CAAAD,mBAA+F,OAAA;IAAzF,KAAK,QAAA,MAAM;IAAM,KAAK,MAAA;IAAO,OAAM;IAAoC,WAAU;GAEzF,GAAA,MAAA,GAAA,UAAA,CAAA,GAAA,GAAA,UAAA,GAAAA,mBAkBM,OAlBN,YAkBM,CAjBJE,YAQED,MAAA,OAAA,GAAA;IAPC,OAAOA,MAAA,CAAA,CAAC,CAAA,6BAAA;IACR,MAAM,QAAA,WAAQ,mBAAsB,KAAA;IACrC,MAAK;IACL,OAAM;IACL,SAAS,QAAA,WAAQ,UAAA;IACjB,gBAAc,QAAA;IACd,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,QAAA;;;;;;GAEd,CAAA,GAAAC,YAOED,MAAA,OAAA,GAAA;IANA,MAAK;IACL,MAAK;IACL,OAAM;IACN,SAAQ;IACP,cAAYA,MAAA,CAAA,CAAC,CAAA,gCAAA;IACb,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,QAAA"}
@@ -0,0 +1,117 @@
1
+ import { editorEntryOf } from "../../../core/editor-ui.js";
2
+ import "../../media/assets.js";
3
+ import { useUiKit } from "../../../vue/kit/use-ui-kit.js";
4
+ import { useEditorI18n } from "../../../vue/use-editor-i18n.js";
5
+ import { useNotifier } from "../../../vue/use-notifier.js";
6
+ import { normalizeImageStudioState } from "../state.js";
7
+ import { createImageStudioNodeStore } from "../node-store.js";
8
+ import _sfc_main$1 from "./ImageStudioDialog.js";
9
+ import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, normalizeClass, onBeforeUnmount, openBlock, ref, shallowRef, toDisplayString, toRaw, unref, withCtx } from "vue";
10
+ import { NodeViewWrapper, nodeViewProps } from "@tiptap/vue-3";
11
+ //#region src/features/image-studio/components/ImageStudioView.vue
12
+ const _hoisted_1 = {
13
+ class: "flex flex-col gap-2",
14
+ contenteditable: "false"
15
+ };
16
+ const _hoisted_2 = ["src", "alt"];
17
+ const _hoisted_3 = {
18
+ key: 1,
19
+ class: "flex min-h-32 flex-col items-center justify-center gap-2 rounded-md border border-dashed border-[var(--sk-border)] p-4 text-center text-sm"
20
+ };
21
+ const _hoisted_4 = {
22
+ key: 0,
23
+ class: "whitespace-pre-wrap text-[var(--sk-muted)]"
24
+ };
25
+ const _hoisted_5 = {
26
+ key: 1,
27
+ class: "text-[var(--sk-fg-dimmed)]"
28
+ };
29
+ const _hoisted_6 = {
30
+ key: 2,
31
+ class: "whitespace-pre-wrap px-1 text-sm text-[var(--sk-muted)]"
32
+ };
33
+ const _hoisted_7 = {
34
+ key: 3,
35
+ class: "flex"
36
+ };
37
+ const _sfc_main = /*@__PURE__*/ defineComponent({
38
+ __name: "ImageStudioView",
39
+ props: nodeViewProps,
40
+ setup(__props) {
41
+ const props = __props;
42
+ const { Button: UButton, Icon: UIcon } = useUiKit();
43
+ const { t } = useEditorI18n();
44
+ const notify = useNotifier(() => props.editor);
45
+ const studio = computed(() => normalizeImageStudioState(props.node.attrs.studio));
46
+ const description = computed(() => studio.value.description.trim());
47
+ const open = ref(false);
48
+ const store = shallowRef();
49
+ /** Opens the studio dialog; a read-only editor opens none, a backend without image generation explains itself. */
50
+ function openStudio() {
51
+ if (!props.editor.isEditable) return;
52
+ const ctx = editorEntryOf(toRaw(props.editor))?.ctx;
53
+ const backend = ctx?.backend.images;
54
+ if (!ctx || !backend) {
55
+ notify({
56
+ id: "image-studio.unavailable",
57
+ title: t("imageStudio.action.notConfigured"),
58
+ kind: "warning"
59
+ });
60
+ return;
61
+ }
62
+ store.value ??= createImageStudioNodeStore({
63
+ editor: toRaw(props.editor),
64
+ getPos: props.getPos,
65
+ backend,
66
+ documentId: ctx.documentId ?? "scratch"
67
+ });
68
+ open.value = true;
69
+ }
70
+ onBeforeUnmount(() => store.value?.dispose());
71
+ return (_ctx, _cache) => {
72
+ return openBlock(), createBlock(unref(NodeViewWrapper), {
73
+ as: "figure",
74
+ "data-image-studio-block": "",
75
+ class: normalizeClass(["my-3 flex flex-col gap-2 rounded-md", { "outline-2 outline-offset-2 outline-[var(--sk-primary)]": _ctx.selected }]),
76
+ onDblclick: openStudio,
77
+ onSkImageStudioOpen: openStudio
78
+ }, {
79
+ default: withCtx(() => [createElementVNode("div", _hoisted_1, [
80
+ _ctx.node.attrs.src ? (openBlock(), createElementBlock("img", {
81
+ key: 0,
82
+ src: _ctx.node.attrs.src,
83
+ alt: _ctx.node.attrs.alt || description.value,
84
+ class: "m-0 max-w-full rounded-md"
85
+ }, null, 8, _hoisted_2)) : (openBlock(), createElementBlock("div", _hoisted_3, [createVNode(unref(UIcon), {
86
+ name: "i-lucide-image-plus",
87
+ class: "size-6 shrink-0 text-[var(--sk-fg-dimmed)]"
88
+ }), description.value ? (openBlock(), createElementBlock("span", _hoisted_4, toDisplayString(description.value), 1)) : (openBlock(), createElementBlock("span", _hoisted_5, toDisplayString(unref(t)("imageStudio.node.noDescription")), 1))])),
89
+ _ctx.node.attrs.src && description.value ? (openBlock(), createElementBlock("figcaption", _hoisted_6, toDisplayString(description.value), 1)) : createCommentVNode("v-if", true),
90
+ _ctx.editor.isEditable ? (openBlock(), createElementBlock("div", _hoisted_7, [createVNode(unref(UButton), {
91
+ label: unref(t)("imageStudio.node.open"),
92
+ icon: "i-lucide-wand-sparkles",
93
+ size: "xs",
94
+ color: "neutral",
95
+ variant: "soft",
96
+ onClick: openStudio
97
+ }, null, 8, ["label"])])) : createCommentVNode("v-if", true)
98
+ ]), store.value ? (openBlock(), createBlock(_sfc_main$1, {
99
+ key: 0,
100
+ open: open.value,
101
+ "onUpdate:open": _cache[0] || (_cache[0] = ($event) => open.value = $event),
102
+ store: store.value,
103
+ title: unref(t)("imageStudio.node.dialogTitle")
104
+ }, null, 8, [
105
+ "open",
106
+ "store",
107
+ "title"
108
+ ])) : createCommentVNode("v-if", true)]),
109
+ _: 1
110
+ }, 8, ["class"]);
111
+ };
112
+ }
113
+ });
114
+ //#endregion
115
+ export { _sfc_main as default };
116
+
117
+ //# sourceMappingURL=ImageStudioView.js.map