@ecsplico/editor 3.4.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 +19 -0
- package/dist/code/CodeSurface.vue.d.ts +3 -3
- package/dist/core/abilities.js +1 -0
- package/dist/core/abilities.js.map +1 -1
- package/dist/core/backend-capability.d.ts +1 -1
- package/dist/core/backend-capability.js +2 -1
- package/dist/core/backend-capability.js.map +1 -1
- package/dist/core/backend-types.d.ts +25 -0
- package/dist/core/backend-types.js.map +1 -1
- package/dist/core/backend.d.ts +9 -1
- package/dist/core/backend.js.map +1 -1
- package/dist/core/http-backend.js +17 -0
- package/dist/core/http-backend.js.map +1 -1
- package/dist/core/schema/abilities.d.ts +1 -0
- package/dist/core/schema/abilities.js +4 -0
- package/dist/core/schema/abilities.js.map +1 -1
- package/dist/document/DocumentSurface.vue.d.ts +3 -3
- package/dist/features/composites/nodes.js +1 -0
- package/dist/features/composites/nodes.js.map +1 -1
- package/dist/features/image-studio/components/ImageStudioChat.js +121 -0
- package/dist/features/image-studio/components/ImageStudioChat.js.map +1 -0
- package/dist/features/image-studio/components/ImageStudioDialog.js +371 -0
- package/dist/features/image-studio/components/ImageStudioDialog.js.map +1 -0
- package/dist/features/image-studio/components/ImageStudioDialog.vue.d.ts +25 -0
- package/dist/features/image-studio/components/ImageStudioTile.js +74 -0
- package/dist/features/image-studio/components/ImageStudioTile.js.map +1 -0
- package/dist/features/image-studio/components/ImageStudioView.js +117 -0
- package/dist/features/image-studio/components/ImageStudioView.js.map +1 -0
- package/dist/features/image-studio/feature.d.ts +24 -0
- package/dist/features/image-studio/feature.js +53 -0
- package/dist/features/image-studio/feature.js.map +1 -0
- package/dist/features/image-studio/index.d.ts +5 -0
- package/dist/features/image-studio/index.js +4 -0
- 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 +19 -0
- package/dist/features/image-studio/node-store.js +35 -0
- package/dist/features/image-studio/node-store.js.map +1 -0
- 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/features/image-studio/schema.d.ts +29 -0
- package/dist/features/image-studio/schema.js +86 -0
- package/dist/features/image-studio/schema.js.map +1 -0
- package/dist/features/image-studio/state.d.ts +52 -0
- package/dist/features/image-studio/state.js +147 -0
- package/dist/features/image-studio/state.js.map +1 -0
- package/dist/features/image-studio/store.d.ts +29 -0
- package/dist/features/image-studio/vue.d.ts +17 -0
- package/dist/features/image-studio/vue.js +20 -0
- package/dist/features/image-studio/vue.js.map +1 -0
- package/dist/features/media/schema.d.ts +5 -1
- package/dist/features/media/schema.js +3 -3
- package/dist/features/media/schema.js.map +1 -1
- package/dist/features/transcript/segment-node.js +1 -0
- package/dist/features/transcript/segment-node.js.map +1 -1
- package/dist/i18n/de/image-studio.js +39 -0
- package/dist/i18n/de/image-studio.js.map +1 -0
- package/dist/i18n/de.d.ts +32 -0
- package/dist/i18n/de.js +13 -11
- package/dist/i18n/de.js.map +1 -1
- package/dist/i18n/en/image-studio.js +39 -0
- package/dist/i18n/en/image-studio.js.map +1 -0
- package/dist/i18n/en.js +13 -11
- package/dist/i18n/en.js.map +1 -1
- package/dist/markdown/MarkdownSurface.vue.d.ts +9 -9
- package/dist/nuxt/module.d.ts +64 -0
- package/dist/server/handlers/ai.d.ts +2 -2
- package/dist/server/handlers/images.d.ts +22 -0
- package/dist/server/handlers/images.js +46 -0
- package/dist/server/handlers/images.js.map +1 -0
- package/dist/server/handlers/index.d.ts +2 -1
- package/dist/server/handlers/index.js +2 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/ports.d.ts +11 -1
- package/dist/server/ports.js +2 -1
- package/dist/server/ports.js.map +1 -1
- package/dist/server/routes.js +19 -0
- package/dist/server/routes.js.map +1 -1
- package/dist/tree/tree-host.js +1 -1
- package/dist/tree/tree-items.js +1 -1
- package/dist/vue/AdvancedEditor.vue.d.ts +6 -6
- package/dist/vue/SimpleEditor.vue.d.ts +6 -6
- package/dist/vue/components/EditorSurface.vue.d.ts +6 -6
- package/package.json +21 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageStudioView.js","names":["selected","node","editor","_openBlock","_createBlock","_unref","_normalizeClass","_withCtx","_createElementVNode","_createElementBlock","_createVNode","_toDisplayString","_createCommentVNode","ImageStudioDialog"],"sources":["../../../../src/features/image-studio/components/ImageStudioView.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { NodeViewWrapper, nodeViewProps } from '@tiptap/vue-3'\nimport { computed, onBeforeUnmount, ref, shallowRef, toRaw } from 'vue'\nimport { editorEntryOf } from '../../../core'\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { useEditorI18n } from '../../../vue/use-editor-i18n'\nimport { useNotifier } from '../../../vue/use-notifier'\nimport { SCRATCH_DOCUMENT_ID } from '../../media/assets'\nimport { createImageStudioNodeStore, type ImageStudioNodeStore } from '../node-store'\nimport { normalizeImageStudioState } from '../state'\nimport ImageStudioDialog from './ImageStudioDialog.vue'\n\nconst props = defineProps(nodeViewProps)\n\nconst { Button: UButton, Icon: UIcon } = useUiKit()\nconst { t } = useEditorI18n()\nconst notify = useNotifier(() => props.editor)\n\nconst studio = computed(() => normalizeImageStudioState(props.node.attrs.studio))\nconst description = computed(() => studio.value.description.trim())\n\n// The store (and with it the Prompt-Bausteine list) is built when the studio opens first and lives as long as the view.\nconst open = ref(false)\nconst store = shallowRef<ImageStudioNodeStore>()\n\n/** Opens the studio dialog; a read-only editor opens none, a backend without image generation explains itself. */\nfunction openStudio() {\n if (!props.editor.isEditable) return\n const ctx = editorEntryOf(toRaw(props.editor))?.ctx\n const backend = ctx?.backend.images\n if (!ctx || !backend) {\n notify({ id: 'image-studio.unavailable', title: t('imageStudio.action.notConfigured'), kind: 'warning' })\n return\n }\n store.value ??= createImageStudioNodeStore({\n editor: toRaw(props.editor),\n getPos: props.getPos,\n backend,\n documentId: ctx.documentId ?? SCRATCH_DOCUMENT_ID,\n })\n open.value = true\n}\n\nonBeforeUnmount(() => store.value?.dispose())\n</script>\n\n<template>\n <NodeViewWrapper\n as=\"figure\"\n data-image-studio-block\n :class=\"['my-3 flex flex-col gap-2 rounded-md', { 'outline-2 outline-offset-2 outline-[var(--sk-primary)]': selected }]\"\n @dblclick=\"openStudio\"\n @sk-image-studio-open=\"openStudio\"\n >\n <div class=\"flex flex-col gap-2\" contenteditable=\"false\">\n <img\n v-if=\"node.attrs.src\"\n :src=\"node.attrs.src\"\n :alt=\"node.attrs.alt || description\"\n class=\"m-0 max-w-full rounded-md\"\n >\n <div\n v-else\n 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\"\n >\n <UIcon name=\"i-lucide-image-plus\" class=\"size-6 shrink-0 text-[var(--sk-fg-dimmed)]\" />\n <span v-if=\"description\" class=\"whitespace-pre-wrap text-[var(--sk-muted)]\">{{ description }}</span>\n <span v-else class=\"text-[var(--sk-fg-dimmed)]\">{{ t('imageStudio.node.noDescription') }}</span>\n </div>\n <figcaption v-if=\"node.attrs.src && description\" class=\"whitespace-pre-wrap px-1 text-sm text-[var(--sk-muted)]\">\n {{ description }}\n </figcaption>\n <div v-if=\"editor.isEditable\" class=\"flex\">\n <UButton\n :label=\"t('imageStudio.node.open')\"\n icon=\"i-lucide-wand-sparkles\"\n size=\"xs\"\n color=\"neutral\"\n variant=\"soft\"\n @click=\"openStudio\"\n />\n </div>\n </div>\n <ImageStudioDialog\n v-if=\"store\"\n v-model:open=\"open\"\n :store=\"store\"\n :title=\"t('imageStudio.node.dialogTitle')\"\n />\n </NodeViewWrapper>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAYA,MAAM,QAAQ;EAEd,MAAM,EAAE,QAAQ,SAAS,MAAM,UAAU,SAAS;EAClD,MAAM,EAAE,MAAM,cAAc;EAC5B,MAAM,SAAS,kBAAkB,MAAM,MAAM;EAE7C,MAAM,SAAS,eAAe,0BAA0B,MAAM,KAAK,MAAM,MAAM,CAAC;EAChF,MAAM,cAAc,eAAe,OAAO,MAAM,YAAY,KAAK,CAAC;EAGlE,MAAM,OAAO,IAAI,KAAK;EACtB,MAAM,QAAQ,WAAiC;;EAG/C,SAAS,aAAa;GACpB,IAAI,CAAC,MAAM,OAAO,YAAY;GAC9B,MAAM,MAAM,cAAc,MAAM,MAAM,MAAM,CAAC,CAAC,EAAE;GAChD,MAAM,UAAU,KAAK,QAAQ;GAC7B,IAAI,CAAC,OAAO,CAAC,SAAS;IACpB,OAAO;KAAE,IAAI;KAA4B,OAAO,EAAE,kCAAkC;KAAG,MAAM;IAAU,CAAC;IACxG;GACF;GACA,MAAM,UAAU,2BAA2B;IACzC,QAAQ,MAAM,MAAM,MAAM;IAC1B,QAAQ,MAAM;IACd;IACA,YAAY,IAAI,cAAA;GAClB,CAAC;GACD,KAAK,QAAQ;EACf;EAEA,sBAAsB,MAAM,OAAO,QAAQ,CAAC;;GAI1C,OAAAG,UAAA,GAAAC,YA0CkBC,MAAA,eAAA,GAAA;IAzChB,IAAG;IACH,2BAAA;IACC,OAAKC,eAAA,CAAA,uCAAA,EAAA,0DAAsGN,KAAAA,SAAQ,CAAA,CAAA;IACnH,YAAU;IACV,qBAAsB;;IAEvB,SAAAO,cA4BM,CA5BNC,mBA4BM,OA5BN,YA4BM;KA1BIP,KAAAA,KAAK,MAAM,OADnBE,UAAA,GAAAM,mBAKC,OAAA;;MAHE,KAAKR,KAAAA,KAAK,MAAM;MAChB,KAAKA,KAAAA,KAAK,MAAM,OAAO,YAAA;MACxB,OAAM;KAER,GAAA,MAAA,GAAA,UAAA,MAAAE,UAAA,GAAAM,mBAOM,OAPN,YAOM,CAHJC,YAAuFL,MAAA,KAAA,GAAA;MAAhF,MAAK;MAAsB,OAAM;KAC5B,CAAA,GAAA,YAAA,SAAZF,UAAA,GAAAM,mBAAoG,QAApG,YAAoGE,gBAArB,YAAA,KAAW,GAAA,CAAA,MAC1FR,UAAA,GAAAM,mBAAgG,QAAhG,YAAgGE,gBAA7CN,MAAA,CAAA,CAAC,CAAA,gCAAA,CAAA,GAAA,CAAA,EAAA,CAAA;KAEpCJ,KAAAA,KAAK,MAAM,OAAO,YAAA,SAApCE,UAAA,GAAAM,mBAEa,cAFb,YAEaE,gBADR,YAAA,KAAW,GAAA,CAAA,KAAAC,mBAAA,QAAA,IAAA;KAELV,KAAAA,OAAO,cAAlBC,UAAA,GAAAM,mBASM,OATN,YASM,CARJC,YAOEL,MAAA,OAAA,GAAA;MANC,OAAOA,MAAA,CAAA,CAAC,CAAA,uBAAA;MACT,MAAK;MACL,MAAK;MACL,OAAM;MACN,SAAQ;MACP,SAAO;;IAKN,CAAA,GAAA,MAAA,SADRF,UAAA,GAAAC,YAKES,aAAA;;KAHQ,MAAM,KAAA;KAAA,iBAAA,OAAA,OAAA,OAAA,MAAA,WAAA,KAAI,QAAA;KACjB,OAAO,MAAA;KACP,OAAOR,MAAA,CAAA,CAAC,CAAA,8BAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { EditorFeature } from "../../core/feature.js";
|
|
2
|
+
import "../../core/index.js";
|
|
3
|
+
import { NodeViewRenderer } from "@tiptap/core";
|
|
4
|
+
//#region src/features/image-studio/feature.d.ts
|
|
5
|
+
/** What a framework binding supplies to the Bild-Studio feature. */
|
|
6
|
+
export interface ImageStudioUi {
|
|
7
|
+
/** The block's view of the framework; without one the node renders as the plain `<img>` of its HTML form. */
|
|
8
|
+
nodeView?: NodeViewRenderer;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The event the `image-studio.open` block action fires on the block's node view element; the view answers it by opening
|
|
12
|
+
* the studio dialog. A node view that does not listen (a headless editor) ignores it.
|
|
13
|
+
*/
|
|
14
|
+
export declare const IMAGE_STUDIO_OPEN = "sk-image-studio-open";
|
|
15
|
+
/**
|
|
16
|
+
* The Bild-Studio block (`imageStudio` node: a picture with its description, Prompt-Bausteine, references, previous
|
|
17
|
+
* generations and chat log in one `studio` attribute), the `/` entry `bild-studio` that inserts an empty one, offered
|
|
18
|
+
* where the caret has `insert.imageStudio`, and the block action `image-studio.open` («Im Bild-Studio öffnen») that asks
|
|
19
|
+
* the block's view to open the studio dialog. The node view and the dialog come from the framework binding (`ui`).
|
|
20
|
+
* Works without the `images` backend capability: the view then explains that generation is not set up instead of opening.
|
|
21
|
+
*/
|
|
22
|
+
export declare function imageStudioFeatureCore(ui?: ImageStudioUi): EditorFeature<never>;
|
|
23
|
+
//#endregion
|
|
24
|
+
//# sourceMappingURL=feature.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ABILITY } from "../../core/schema/abilities.js";
|
|
2
|
+
import { command } from "../../core/commands.js";
|
|
3
|
+
import { ImageStudioNode, imageStudioSchemaExtensions } from "./schema.js";
|
|
4
|
+
//#region src/features/image-studio/feature.ts
|
|
5
|
+
/**
|
|
6
|
+
* The event the `image-studio.open` block action fires on the block's node view element; the view answers it by opening
|
|
7
|
+
* the studio dialog. A node view that does not listen (a headless editor) ignores it.
|
|
8
|
+
*/
|
|
9
|
+
const IMAGE_STUDIO_OPEN = "sk-image-studio-open";
|
|
10
|
+
const commands = (ctx) => [command({
|
|
11
|
+
id: "imageStudio",
|
|
12
|
+
slash: { id: "bild-studio" },
|
|
13
|
+
creates: { type: "imageStudio" },
|
|
14
|
+
icon: "i-lucide-image-plus",
|
|
15
|
+
label: ctx.t("imageStudio.slash.insert"),
|
|
16
|
+
ability: ABILITY.imageStudio,
|
|
17
|
+
command: (c) => c.insertImageStudio()
|
|
18
|
+
})];
|
|
19
|
+
const blockActions = (ctx) => [{
|
|
20
|
+
id: "image-studio.open",
|
|
21
|
+
label: ctx.t("imageStudio.action.open"),
|
|
22
|
+
icon: "i-lucide-wand-sparkles",
|
|
23
|
+
isVisible: (block) => block.node.type.name === "imageStudio",
|
|
24
|
+
placement: "both",
|
|
25
|
+
run: (block) => {
|
|
26
|
+
const dom = block.editor.view.nodeDOM(block.pos);
|
|
27
|
+
if (dom instanceof HTMLElement) dom.dispatchEvent(new CustomEvent(IMAGE_STUDIO_OPEN));
|
|
28
|
+
}
|
|
29
|
+
}];
|
|
30
|
+
/**
|
|
31
|
+
* The Bild-Studio block (`imageStudio` node: a picture with its description, Prompt-Bausteine, references, previous
|
|
32
|
+
* generations and chat log in one `studio` attribute), the `/` entry `bild-studio` that inserts an empty one, offered
|
|
33
|
+
* where the caret has `insert.imageStudio`, and the block action `image-studio.open` («Im Bild-Studio öffnen») that asks
|
|
34
|
+
* the block's view to open the studio dialog. The node view and the dialog come from the framework binding (`ui`).
|
|
35
|
+
* Works without the `images` backend capability: the view then explains that generation is not set up instead of opening.
|
|
36
|
+
*/
|
|
37
|
+
function imageStudioFeatureCore(ui = {}) {
|
|
38
|
+
const { nodeView } = ui;
|
|
39
|
+
return {
|
|
40
|
+
id: "image-studio",
|
|
41
|
+
dimension: "content",
|
|
42
|
+
schemaExtensions: imageStudioSchemaExtensions,
|
|
43
|
+
extensions: () => [nodeView ? ImageStudioNode.extend({ addNodeView() {
|
|
44
|
+
return nodeView;
|
|
45
|
+
} }) : ImageStudioNode],
|
|
46
|
+
commands,
|
|
47
|
+
blockActions
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { IMAGE_STUDIO_OPEN, imageStudioFeatureCore };
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=feature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature.js","names":[],"sources":["../../../src/features/image-studio/feature.ts"],"sourcesContent":["import type { NodeViewRenderer } from '@tiptap/core'\nimport { type BlockAction, command, type CommandDef, type EditorFeature, type FeatureContext } from '../../core'\nimport { ABILITY } from '../../core/schema'\nimport { ImageStudioNode, imageStudioSchemaExtensions } from './schema'\n\n/** What a framework binding supplies to the Bild-Studio feature. */\nexport interface ImageStudioUi {\n /** The block's view of the framework; without one the node renders as the plain `<img>` of its HTML form. */\n nodeView?: NodeViewRenderer\n}\n\n/**\n * The event the `image-studio.open` block action fires on the block's node view element; the view answers it by opening\n * the studio dialog. A node view that does not listen (a headless editor) ignores it.\n */\nexport const IMAGE_STUDIO_OPEN = 'sk-image-studio-open'\n\nconst commands = (ctx: FeatureContext): CommandDef<never>[] => [\n command({\n id: 'imageStudio',\n slash: { id: 'bild-studio' },\n creates: { type: 'imageStudio' },\n icon: 'i-lucide-image-plus',\n label: ctx.t('imageStudio.slash.insert'),\n ability: ABILITY.imageStudio,\n command: c => c.insertImageStudio(),\n }),\n]\n\nconst blockActions = (ctx: FeatureContext): BlockAction[] => [{\n id: 'image-studio.open',\n label: ctx.t('imageStudio.action.open'),\n icon: 'i-lucide-wand-sparkles',\n isVisible: block => block.node.type.name === 'imageStudio',\n placement: 'both',\n run: (block) => {\n const dom = block.editor.view.nodeDOM(block.pos)\n if (dom instanceof HTMLElement) dom.dispatchEvent(new CustomEvent(IMAGE_STUDIO_OPEN))\n },\n}]\n\n/**\n * The Bild-Studio block (`imageStudio` node: a picture with its description, Prompt-Bausteine, references, previous\n * generations and chat log in one `studio` attribute), the `/` entry `bild-studio` that inserts an empty one, offered\n * where the caret has `insert.imageStudio`, and the block action `image-studio.open` («Im Bild-Studio öffnen») that asks\n * the block's view to open the studio dialog. The node view and the dialog come from the framework binding (`ui`).\n * Works without the `images` backend capability: the view then explains that generation is not set up instead of opening.\n */\nexport function imageStudioFeatureCore(ui: ImageStudioUi = {}): EditorFeature<never> {\n const { nodeView } = ui\n return {\n id: 'image-studio',\n dimension: 'content',\n schemaExtensions: imageStudioSchemaExtensions,\n extensions: () => [nodeView\n ? ImageStudioNode.extend({\n addNodeView(): NodeViewRenderer {\n return nodeView\n },\n })\n : ImageStudioNode],\n commands,\n blockActions,\n }\n}\n"],"mappings":";;;;;;;;AAeA,MAAa,oBAAoB;AAEjC,MAAM,YAAY,QAA6C,CAC7D,QAAQ;CACN,IAAI;CACJ,OAAO,EAAE,IAAI,cAAc;CAC3B,SAAS,EAAE,MAAM,cAAc;CAC/B,MAAM;CACN,OAAO,IAAI,EAAE,0BAA0B;CACvC,SAAS,QAAQ;CACjB,UAAS,MAAK,EAAE,kBAAkB;AACpC,CAAC,CACH;AAEA,MAAM,gBAAgB,QAAuC,CAAC;CAC5D,IAAI;CACJ,OAAO,IAAI,EAAE,yBAAyB;CACtC,MAAM;CACN,YAAW,UAAS,MAAM,KAAK,KAAK,SAAS;CAC7C,WAAW;CACX,MAAM,UAAU;EACd,MAAM,MAAM,MAAM,OAAO,KAAK,QAAQ,MAAM,GAAG;EAC/C,IAAI,eAAe,aAAa,IAAI,cAAc,IAAI,YAAY,iBAAiB,CAAC;CACtF;AACF,CAAC;;;;;;;;AASD,SAAgB,uBAAuB,KAAoB,CAAC,GAAyB;CACnF,MAAM,EAAE,aAAa;CACrB,OAAO;EACL,IAAI;EACJ,WAAW;EACX,kBAAkB;EAClB,kBAAkB,CAAC,WACf,gBAAgB,OAAO,EACrB,cAAgC;GAC9B,OAAO;EACT,EACF,CAAC,IACD,eAAe;EACnB;EACA;CACF;AACF"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ImageStudioImage, ImageStudioPromptBlock } from "../../core/backend-types.js";
|
|
2
|
+
import { IMAGE_STUDIO_OPEN, ImageStudioUi, imageStudioFeatureCore } from "./feature.js";
|
|
3
|
+
import { EMPTY_IMAGE_STUDIO_STATE, ImageStudioState, ImageStudioTurn, addGeneration, addReference, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock } from "./state.js";
|
|
4
|
+
import { ImageStudioAttrs, ImageStudioNode, imageStudioSchemaExtensions } from "./schema.js";
|
|
5
|
+
export { EMPTY_IMAGE_STUDIO_STATE, IMAGE_STUDIO_OPEN, type ImageStudioAttrs, type ImageStudioImage, ImageStudioNode, type ImageStudioPromptBlock, type ImageStudioState, type ImageStudioTurn, type ImageStudioUi, addGeneration, addReference, imageStudioFeatureCore, imageStudioSchemaExtensions, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { EMPTY_IMAGE_STUDIO_STATE, addGeneration, addReference, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock } from "./state.js";
|
|
2
|
+
import { ImageStudioNode, imageStudioSchemaExtensions } from "./schema.js";
|
|
3
|
+
import { IMAGE_STUDIO_OPEN, imageStudioFeatureCore } from "./feature.js";
|
|
4
|
+
export { EMPTY_IMAGE_STUDIO_STATE, IMAGE_STUDIO_OPEN, ImageStudioNode, addGeneration, addReference, imageStudioFeatureCore, imageStudioSchemaExtensions, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock };
|
|
@@ -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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ImageStudioNodeCoreOptions } from "./node-core.js";
|
|
2
|
+
import { ImageStudioStore } from "./store.js";
|
|
3
|
+
//#region src/features/image-studio/node-store.d.ts
|
|
4
|
+
export type ImageStudioNodeStoreOptions = ImageStudioNodeCoreOptions;
|
|
5
|
+
/** A store over one node, with the function that detaches it from the editor. */
|
|
6
|
+
export interface ImageStudioNodeStore extends ImageStudioStore {
|
|
7
|
+
/** Stops following the editor and ignores the answers still outstanding; call when the node view unmounts. */
|
|
8
|
+
dispose: () => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The `ImageStudioStore` of one `imageStudio` node: `state` follows the node's `studio` attribute (re-read on every
|
|
12
|
+
* editor transaction, so undo, collaboration and other writers show), `update` writes it back through
|
|
13
|
+
* `setImageStudioState` as one transaction (one undo step; the node's `src` follows the selected image), and
|
|
14
|
+
* `generate`, `upload` and the Prompt-Bausteine go to `backend.images`. Call `dispose` when the view goes.
|
|
15
|
+
* The Vue wrapper of `createImageStudioNodeCore`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createImageStudioNodeStore(options: ImageStudioNodeStoreOptions): ImageStudioNodeStore;
|
|
18
|
+
//#endregion
|
|
19
|
+
//# sourceMappingURL=node-store.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createImageStudioNodeCore } from "./node-core.js";
|
|
2
|
+
import { shallowRef } from "vue";
|
|
3
|
+
//#region src/features/image-studio/node-store.ts
|
|
4
|
+
/**
|
|
5
|
+
* The `ImageStudioStore` 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`. Call `dispose` when the view goes.
|
|
9
|
+
* The Vue wrapper of `createImageStudioNodeCore`.
|
|
10
|
+
*/
|
|
11
|
+
function createImageStudioNodeStore(options) {
|
|
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;
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
state,
|
|
24
|
+
promptBlocks,
|
|
25
|
+
problem,
|
|
26
|
+
update: core.update,
|
|
27
|
+
generate: core.generate,
|
|
28
|
+
upload: core.upload,
|
|
29
|
+
dispose: core.dispose
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { createImageStudioNodeStore };
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=node-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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
|